Skip to content

Releases: mantinedev/mantine

7.17.0 🌢️

17 Feb 16:42
Compare
Choose a tag to compare

View changelog with demos on mantine.dev website

Last 7.x minor release

This is the last minor release in the 7.x series. The next release will be 8.0 with breaking changes and new features.

You are welcome to review the changelog/code and provide feedback and bug reports in Discord or GitHub discussions:

How to update your project dependencies to the new alpha version:

  • Open your package.json
  • Find all @mantine/ packages
  • Update version of all @mantine/ packages to 8.0.0-alpha.0
  • Install dependencies with your package manager, for example, yarn or npm install

Important notes:

  • 8.0 release is scheduled for May 5th. Until then you can influence the included breaking changes by proving feedback using Discord or GitHub discussions.
  • Currently most of planned breaking changes are implemented – it is not planned to introduce other significant breaking changes (unless new versions of recharts or tiptap are released before May 5th).
  • You can find the updated source code in 8.0 branch on GitHub
  • Since the changes to codebase are not that significant compared to previous major releases, it is not planned to deprecate 7.x version as soon as 8.0 is release. 7.17.x patches are planned to be released for some time – if you are not ready to migrate to 8.x, you will still be able to receive bug fixes for 7.x (no new features though).

Portal reuseTargetNode prop

Portal component now supports reuseTargetNode prop which allows to reuse the same target node for all instances.
This option is more performant than the previous behavior, it is recommended to be enabled.
This option will be enabled by default in the 8.0 major release.

To enable reuseTargetNode option in all components that depend on Portal, add the following code
to your theme:

import { createTheme, Portal } from '@mantine/core';

export const theme = createTheme({
  components: {
    Portal: Portal.extend({
      defaultProps: {
        reuseTargetNode: true,
      },
    }),
  },
});

Example usage. In the following example, all three paragraphs will be rendered in the same target node:

import { Portal } from '@mantine/core';

function Demo() {
  return (
    <>
      <Portal reuseTargetNode>
        <p>First</p>
      </Portal>

      <Portal reuseTargetNode>
        <p>Second</p>
      </Portal>

      <Portal reuseTargetNode>
        <p>Third</p>
      </Portal>
    </>
  );
}

use-form formRootRule

formRootRule is a special rule path that can be used to validate objects and arrays
alongside with their nested fields. For example, it is useful when you want to capture
a list of values, validate each value individually and then validate the list itself
to not be empty:

import { IconTrash } from '@tabler/icons-react';
import { ActionIcon, Button, Group, Switch, Text, TextInput } from '@mantine/core';
import { formRootRule, isNotEmpty, useForm } from '@mantine/form';
import { randomId } from '@mantine/hooks';

function Demo() {
  const form = useForm({
    mode: 'uncontrolled',
    initialValues: {
      employees: [{ name: '', active: false, key: randomId() }],
    },
    validate: {
      employees: {
        [formRootRule]: isNotEmpty('At least one employee is required'),
        name: isNotEmpty('Name is required'),
      },
    },
  });

  const fields = form.getValues().employees.map((item, index) => (
    <Group key={item.key} mt="xs">
      <TextInput
        placeholder="John Doe"
        withAsterisk
        style={{ flex: 1 }}
        key={form.key(`employees.${index}.name`)}
        {...form.getInputProps(`employees.${index}.name`)}
      />
      <Switch
        label="Active"
        key={form.key(`employees.${index}.active`)}
        {...form.getInputProps(`employees.${index}.active`, { type: 'checkbox' })}
      />
      <ActionIcon color="red" onClick={() => form.removeListItem('employees', index)}>
        <IconTrash size={16} />
      </ActionIcon>
    </Group>
  ));

  return (
    <form onSubmit={form.onSubmit(() => {})}>
      {fields.length > 0 ? (
        <Group mb="xs">
          <Text fw={500} size="sm" style={{ flex: 1 }}>
            Name
          </Text>
          <Text fw={500} size="sm" pr={90}>
            Status
          </Text>
        </Group>
      ) : (
        <Text c="dimmed" ta="center">
          No one here...
        </Text>
      )}

      {fields}

      {form.errors.employees && (
        <Text c="red" size="sm" mt="sm">
          {form.errors.employees}
        </Text>
      )}

      <Group justify="space-between" mt="md">
        <Button
          variant="default"
          onClick={() => {
            form.insertListItem('employees', { name: '', active: false, key: randomId() });
            form.clearFieldError('employees');
          }}
        >
          Add employee
        </Button>
        <Button type="submit">Submit</Button>
      </Group>
    </form>
  );
}

Another example is to validate an object fields combination:

import { Button, Text, TextInput } from '@mantine/core';
import { formRootRule, isNotEmpty, useForm } from '@mantine/form';

function Demo() {
  const form = useForm({
    mode: 'uncontrolled',
    initialValues: {
      user: {
        firstName: '',
        lastName: '',
      },
    },

    validate: {
      user: {
        [formRootRule]: (value) =>
          value.firstName.trim().length > 0 && value.firstName === value.lastName
            ? 'First name and last name cannot be the same'
            : null,
        firstName: isNotEmpty('First name is required'),
        lastName: isNotEmpty('Last name is required'),
      },
    },
  });

  return (
    <form onSubmit={form.onSubmit(() => {})}>
      <TextInput
        label="First name"
        placeholder="First name"
        {...form.getInputProps('user.firstName')}
      />
      <TextInput
        label="Last name"
        placeholder="Last name"
        mt="md"
        {...form.getInputProps('user.lastName')}
      />
      {form.errors.user && (
        <Text c="red" mt={5} fz="sm">
          {form.errors.user}
        </Text>
      )}
      <Button type="submit" mt="lg">
        Submit
      </Button>
    </form>
  );
}

isJSONString and isNotEmptyHTML form validators

New isJSONString and isNotEmptyHTML form validators:

  • isNotEmptyHTML checks that form value is not an empty HTML string. Empty string, string with only HTML tags and whitespace are considered to be empty.
  • isJSONString checks that form value is a valid JSON string.
import { isJSONString, useForm } from '@mantine/form';

const form = useForm({
  mode: 'uncontrolled',
  initialValues: {
    json: '',
    html: '',
  },

  validate: {
    json: isJSONString('Invalid JSON string'),
    html: isNotEmptyHTML('HTML cannot be empty'),
  },
});

Popover onDismiss

Popover now supports onDismiss prop, which makes it easier
to subscribe to outside clicks and escape key presses to close popover:

import { useState } from 'react';
import { Button, Popover } from '@mantine/core';

function Demo() {
  const [opened, setOpened] = useState(false);
  return (
    <Popover opened={opened} onDismiss={() => setOpened(false)}>
      <Popover.Target>
        <Button onClick={() => setOpened((o) => !o)}>Toggle popover</Button>
      </Popover.Target>

      <Popover.Dropdown>Dropdown</Popover.Dropdown>
    </Popover>
  );
}

MantineProvider env

MantineProvider component now supports env prop.
It can be used in test environment to disable some features that
might impact tests and/or make it harder to test components:

  • transitions that mount/unmount child component with delay
  • portals that render child component in a different part of the DOM

To enable test environment, set env to test:

import { MantineProvider } from '@mantine/core';

function Demo() {
  return <MantineProvider env="test">{/* Your app here */}</MantineProvider>;
}

use-file-dialog hook

New use-file-dialog allows capturing one or more files from the user
without file input element:

import { Button, Group, List } from '@mantine/core';
import { useFileDialog } from '@mantine/hooks';

function Demo() {
  const fileDialog = useFileDialog();

  const pickedFiles = Array.from(fileDialog.files || []).map((file) => (
    <List.Item key={file.name}>{file.name}</List.Item>
  ));

  return (
    <div>
      <Group>
        <Button onClick={fileDialog.open}>Pick files</Button>
        {pickedFiles.length > 0 && (
          <Button variant="default" onClick={fileDialog.reset}>
            Reset
          </Button>
        )}
      </Group>
      {pickedFiles.length > 0 && <List mt="lg">{pickedFiles}</List>}
    </div>
  );
}

Remix deprecation

Remix is deprecated, the documentation related to Remix integration
was removed, use React Router instead. To simplify maintenance,
Remix/React Router templates were archived and will not be updated.

Help center updates

Read more

7.16.3

08 Feb 11:13
Compare
Choose a tag to compare

What's Changed

  • [@mantine/core] Remove use client from isLightColor function
  • [@mantine/core] TextInput: Fix autocomplete for variant prop not working
  • [@mantine/carousel] Fix aria-hidden warning displayed in Chrome console when indicator is clicked (#7414)
  • [@mantine/core] Fix clear button overlaying input content in Autocomplete and other similar components (#7431)
  • [@mantine/core] Combobox: Fix incorrect dropdown padding when used with ScrollArea (#7450)
  • [@mantine/core] Fix 0 gradient deg value not working correctly (#7444)
  • [@mantine/core] ScrollArea: Fix user-select being left as none after interaction with scrollbar in some edge cases (#7423)
  • [@mantine/dates] DateInput: Fix infinite loop in Safari (#7442)

New Contributors

Full Changelog: 7.16.2...7.16.3

7.16.2

26 Jan 12:24
Compare
Choose a tag to compare

What's Changed

  • [@mantine/core] Tooltip: Migrate from deprecated useDelayGroupContext hook to useDelayGroup
  • [@mantine/core] Tabs: Fix tabIndex={0} set on Tabs.Tab being ignored (#7407)
  • [@mantine/core] Fix chevron icon not being clickable in Select and MultiSelect components (#7395)
  • [@mantine/dates] MonthPicker: Fix infinite useEffect with use-form in some cases (#7389)
  • [@mantine/hooks] use-hotkeys: Add better support for non-QUERTY keyboards (#7390)
  • [@mantine/dates] DateTimePicker: Fix timezone conversion being applied twice (#7400)
  • [@mantine/hooks] Fix potential dangerous access of ref value in useEffect cleanup (#7404)

New Contributors

Full Changelog: 7.16.1...7.16.2

7.16.1

19 Jan 09:39
Compare
Choose a tag to compare

What's Changed

  • [@mantine/core] Menu: Add withInitialFocusPlaceholder prop support
  • [@mantine/core] Slider: Fix onChangeEnd being called when disabled prop is set
  • [@mantine/core] Add option to customize chevron color with chevronColor prop in Select and MultiSelect components
  • [@mantine/core] Fix incorrect styles of nested tables (#7354)
  • [@mantine/core]: NumberInput: Fix onChange being called in onBlur if the value has not been changed (#7383)
  • [@mantine/core] Menu: Add data-disabled prop handling to Menu.Item component (#7377)
  • [@mantine/form] Fix incorrect values handling in form.resetDirty (#7373)
  • [@mantine/chart] AreaChart: Fix gridColor and textColor props being passed as attributes to the DOM node (#7378)
  • [@mantine/hooks] use-in-viewport: Fix tracking being stopped when used with a dnd library (#7359)
  • [@mantine/core] MantineProvider: Fix jest tests not running in case there is incorrect window.matchMedia polyfill implementation (#7360)
  • [@mantine/core] Modal: Fix Escape key not working in old Safari versions (#7358)

New Contributors

Full Changelog: 7.16.0...7.16.1

7.16.0 🌢️

14 Jan 11:36
Compare
Choose a tag to compare

View changelog with demos on mantine.dev website

use-scroll-spy hook

New use-scroll-spy hook tracks scroll position and returns index of the
element that is currently in the viewport. It is useful for creating
table of contents components (like in mantine.dev sidebar on the right side)
and similar features.

import { Text, UnstyledButton } from '@mantine/core';
import { useScrollSpy } from '@mantine/hooks';

function Demo() {
  const spy = useScrollSpy({
    selector: '#mdx :is(h1, h2, h3, h4, h5, h6)',
  });

  const headings = spy.data.map((heading, index) => (
    <li
      key={heading.id}
      style={{
        listStylePosition: 'inside',
        paddingInlineStart: heading.depth * 20,
        background: index === spy.active ? 'var(--mantine-color-blue-light)' : undefined,
      }}
    >
      <UnstyledButton onClick={() => heading.getNode().scrollIntoView()}>
        {heading.value}
      </UnstyledButton>
    </li>
  ));

  return (
    <div>
      <Text>Scroll to heading:</Text>
      <ul style={{ margin: 0, padding: 0 }}>{headings}</ul>
    </div>
  );
}

TableOfContents component

New TableOfContents component is built on top of use-scroll-spy hook
and can be used to create table of contents components like the one on the right side of mantine.dev
documentation sidebar:

import { TableOfContents } from '@mantine/core';

function Demo() {
  return (
    <TableOfContents
      variant="filled"
      color="blue"
      size="sm"
      radius="sm"
      scrollSpyOptions={{
        selector: '#mdx :is(h1, h2, h3, h4, h5, h6)',
      }}
      getControlProps={({ data }) => ({
        onClick: () => data.getNode().scrollIntoView(),
        children: data.value,
      })}
    />
  );
}

Input.ClearButton component

New Input.ClearButton component can be used to add clear button to custom inputs
based on Input component. size of the clear button is automatically
inherited from the input:

import { Input } from '@mantine/core';

function Demo() {
  const [value, setValue] = useState('clearable');

  return (
    <Input
      placeholder="Clearable input"
      value={value}
      onChange={(event) => setValue(event.currentTarget.value)}
      rightSection={value !== '' ? <Input.ClearButton onClick={() => setValue('')} /> : undefined}
      rightSectionPointerEvents="auto"
      size="sm"
    />
  );
}

Popover with overlay

Popover and other components based on it now support withOverlay prop:

import { Anchor, Avatar, Group, Popover, Stack, Text } from '@mantine/core';

function Demo() {
  return (
    <Popover
      width={320}
      shadow="md"
      withArrow
      withOverlay
      overlayProps={{ zIndex: 10000, blur: '8px' }}
      zIndex={10001}
    >
      <Popover.Target>
        <UnstyledButton style={{ zIndex: 10001, position: 'relative' }}>
          <Avatar src="https://avatars.githubusercontent.com/u/79146003?s=200&v=4" radius="xl" />
        </UnstyledButton>
      </Popover.Target>
      <Popover.Dropdown>
        <Group>
          <Avatar src="https://avatars.githubusercontent.com/u/79146003?s=200&v=4" radius="xl" />
          <Stack gap={5}>
            <Text size="sm" fw={700} style={{ lineHeight: 1 }}>
              Mantine
            </Text>
            <Anchor href="https://x.com/mantinedev" c="dimmed" size="xs" style={{ lineHeight: 1 }}>
              @mantinedev
            </Anchor>
          </Stack>
        </Group>

        <Text size="sm" mt="md">
          Customizable React components and hooks library with focus on usability, accessibility and
          developer experience
        </Text>

        <Group mt="md" gap="xl">
          <Text size="sm">
            <b>0</b> Following
          </Text>
          <Text size="sm">
            <b>1,174</b> Followers
          </Text>
        </Group>
      </Popover.Dropdown>
    </Popover>
  );
}

Container queries in Carousel

You can now use container queries
in Carousel component. With container queries, all responsive values
are adjusted based on the container width, not the viewport width.

Example of using container queries. To see how the grid changes, resize the root element
of the demo with the resize handle located at the bottom right corner of the demo:

import { Carousel } from '@mantine/carousel';

function Demo() {
  return (
    // Wrapper div is added for demonstration purposes only,
    // It is not required in real projects
    <div
      style={{
        resize: 'horizontal',
        overflow: 'hidden',
        maxWidth: '100%',
        minWidth: 250,
        padding: 10,
      }}
    >
      <Carousel
        withIndicators
        height={200}
        type="container"
        slideSize={{ base: '100%', '300px': '50%', '500px': '33.333333%' }}
        slideGap={{ base: 0, '300px': 'md', '500px': 'lg' }}
        loop
        align="start"
      >
        <Carousel.Slide>1</Carousel.Slide>
        <Carousel.Slide>2</Carousel.Slide>
        <Carousel.Slide>3</Carousel.Slide>
        {/* ...other slides */}
      </Carousel>
    </div>
  );
}

RangeSlider restrictToMarks

RangeSlider component now supports restrictToMarks prop:

import { Slider } from '@mantine/core';

function Demo() {
  return (
    <Stack>
      <Slider
        restrictToMarks
        defaultValue={25}
        marks={Array.from({ length: 5 }).map((_, index) => ({ value: index * 25 }))}
      />

      <RangeSlider
        restrictToMarks
        defaultValue={[5, 15]}
        marks={[
          { value: 5 },
          { value: 15 },
          { value: 25 },
          { value: 35 },
          { value: 70 },
          { value: 80 },
          { value: 90 },
        ]}
      />
    </Stack>
  );
}

Pagination withPages prop

Pagination component now supports withPages prop which allows hiding pages
controls and displaying only previous and next buttons:

import { useState } from 'react';
import { Group, Pagination, Text } from '@mantine/core';

const limit = 10;
const total = 145;
const totalPages = Math.ceil(total / limit);

function Demo() {
  const [page, setPage] = useState(1);
  const message = `Showing ${limit * (page - 1) + 1} – ${Math.min(total, limit * page)} of ${total}`;

  return (
    <Group justify="flex-end">
      <Text size="sm">{message}</Text>
      <Pagination total={totalPages} value={page} onChange={setPage} />
    </Group>
  );
}

useForm touchTrigger option

use-form hook now supports touchTrigger option that allows customizing events that change touched state.
It accepts two options:

  • change (default) – field will be considered touched when its value changes or it has been focused
  • focus – field will be considered touched only when it has been focused

Example of using focus trigger:

import { useForm } from '@mantine/form';

const form = useForm({
  mode: 'uncontrolled',
  initialValues: { a: 1 },
  touchTrigger: 'focus',
});

form.isTouched('a'); // -> false
form.setFieldValue('a', 2);
form.isTouched('a'); // -> false

// onFocus is called automatically when the user focuses the field
form.getInputProps('a').onFocus();
form.isTouched('a'); // -> true

Help Center updates

Other changes

7.15.3

07 Jan 06:09
Compare
Choose a tag to compare

What's Changed

  • [@mantine/charts] BarChart: Fix textColor prop being passed down as attribute to the DOM node
  • [@mantine/core] TypographyStylesProvider: Fix incorrect top and bottom margins of first and last elements (#7334)
  • [@mantine/core] Transition: Fix some transitions being incompatible with headless mode (#7306)
  • [@mantine/dates] DateTimePicker: Set milliseconds to 0 on the result date object (#7328)
  • [@mantine/dates] Fix hasNextLevel prop type leak to DateTimePicker component (#7319)
  • [@mantine/core] Avatar: Change initials function to use the full name to generate color (#7322)
  • [@mantine/hooks] use-merged-ref: Add support for ref cleanup function in React 19 (#7304)
  • [@mantine/hooks] use-debounced-callback: Add flush method to returned callback (#7272)
  • [@mantine/dates] Improve compatibility with dayjs plugins in all components (#7302)
  • [@mantine/core] Update peer dependencies to support React 19 (#7321)

New Contributors

Full Changelog: 7.15.2...7.15.3

7.15.2

23 Dec 10:10
Compare
Choose a tag to compare

What's Changed

  • [@mantine/dates] DatePicker: Fix incorrect handling of receiving partial value when type="range" (#7278)
  • [@mantine/hooks] use-local-storage: Fix value not being updated when key changes (#7286)
  • [@mantine/charts] Fix gridColor prop being passed down as attribute to html element (#7288)
  • [@mantine/core] Update react-textarea-autosize to support React 19 (#7297)
  • [@mantine/core] TypographyStylesProvider: Fix margin removal affecting non-typography elements (#7290)
  • [@mantine/core] Tooltip: Add middlewares prop support (#7281)
  • [@mantine/core] FloatingIndicator: Fix incorrect position calculations when the parent element has border (#7267)
  • [@mantine/core] ScrollArea: Fix scrollbar not changing with the scroll position on first render (#7257, #7260)
  • [@mantine/tiptap] Fix incorrect paragraph styles inside lists (#7255)
  • [@mantine/hooks] Fix incorrect ref types in use-move, use-radial-move, use-in-viewport and use-scroll-into-view (#7252)
  • [@mantine/form] Fix incorrect validators types (#7242)

New Contributors

Full Changelog: 7.15.1...7.15.2

7.15.1

12 Dec 09:48
Compare
Choose a tag to compare

What's Changed

  • [@mantine/dates] Improve focus behavior of DatePickerInput, DateInput and other components
  • [@mantine/form] Add touchTrigger option support
  • [@mantine/hooks] Add option to specify prefix in randonId function
  • [@mantine/core] Fix withProps function requiring all component props instead of partial
  • [@mantine/core] Add useModalStackContext and useDrawerStackContext hooks exports
  • [@mantine/core] ActionIcon: Add input-* autocomplete for size prop
  • [@mantine/core] AppShell: Fix incorrect default offsetScrollbars value for layout="alt"
  • [@mantine/core] Fix virtualColor function not working in server components (#7184)
  • [@mantine/core] Checkbox: Fix incorrect Checkbox.Card behavior inside Checkbox.Group (#7187)
  • [@mantine/core] Checkbox: Fix incorrect Checkbox.Card behavior inside Checkbox.Group (#7187)
  • [@mantine/core] Slider: Add option to pass attributes down to thumb with thumbProps (#7214)
  • [@mantine/core] Switch: Add data-checked attribute to the input (#7228)
  • [@mantine/dates] Fix hasNextLevel prop type leak to DatePicker component (#7229)
  • [@mantine/dates] Fix timezone not being applied to the formatted value (#7162)
  • [@mantine/modals] Fix modalId being passed to the DOM node as attribute (#7189)
  • [@mantine/core] TypographyStylesProvider: Fix incorrect paragraphs inside lists styles (#7226)
  • [@mantine/core] Slider: Fix icon used as thumb child not being visible with the dark color scheme (#7231, #7232)
  • [@mantine/tiptap] Fix missing border in custom controls (#7239)

New Contributors

Full Changelog: 7.15.0...7.15.1

7.15.0 πŸ’‹

10 Dec 10:06
Compare
Choose a tag to compare

View changelog with demos on mantine.dev website

Support Mantine development

You can now sponsor Mantine development with OpenCollective.
All funds will be used to improve Mantine and create new features and components.

use-radial-move hook

New use-radial-move hook can be used to create custom radial sliders:

import { useState } from 'react';
import { Box } from '@mantine/core';
import { useRadialMove } from '@mantine/hooks';
import classes from './Demo.module.css';

function Demo() {
  const [value, setValue] = useState(115);
  const { ref } = useRadialMove(setValue);

  return (
    <Box className={classes.root} ref={ref} style={{ '--angle': `${value}deg` }}>
      <div className={classes.value}>{value}Β°</div>
      <div className={classes.thumb} />
    </Box>
  );
}

BarChart color based on value

BarChart component now supports getBarColor prop to assign color based on value.
getBarColor function is called with two arguments: value and series object. It should return a color
string (theme color reference or any valid CSS color value).

import { BarChart } from '@mantine/charts';
import { data } from './data';

function Demo() {
  return (
    <BarChart
      h={300}
      data={data}
      dataKey="month"
      getBarColor={(value) => (value > 700 ? 'teal.8' : 'red.8')}
      series={[{ name: 'Laptops', color: 'gray.6' }]}
    />
  );
}

Button.GroupSection and ActionIcon.GroupSection

ActionIcon.GroupSection and Button.GroupSection are new components that
can be used in ActionIcon.Group/Button.Group to create sections that are
not ActionIcon/Button components:

import { IconChevronDown, IconChevronUp } from '@tabler/icons-react';
import { ActionIcon } from '@mantine/core';
import { useCounter } from '@mantine/hooks';

function Demo() {
  const [value, { increment, decrement }] = useCounter(135, { min: 0 });

  return (
    <ActionIcon.Group>
      <ActionIcon variant="default" size="lg" radius="md" onClick={decrement}>
        <IconChevronDown color="var(--mantine-color-red-text)" />
      </ActionIcon>
      <ActionIcon.GroupSection variant="default" size="lg" bg="var(--mantine-color-body)" miw={60}>
        {value}
      </ActionIcon.GroupSection>
      <ActionIcon variant="default" size="lg" radius="md" onClick={increment}>
        <IconChevronUp color="var(--mantine-color-teal-text)" />
      </ActionIcon>
    </ActionIcon.Group>
  );
}

Table vertical variant

Table component now support variant="vertical":

import { Table } from '@mantine/core';

export function Demo() {
  return (
    <Table variant="vertical" layout="fixed" withTableBorder>
      <Table.Tbody>
        <Table.Tr>
          <Table.Th w={160}>Epic name</Table.Th>
          <Table.Td>7.x migration</Table.Td>
        </Table.Tr>

        <Table.Tr>
          <Table.Th>Status</Table.Th>
          <Table.Td>Open</Table.Td>
        </Table.Tr>

        <Table.Tr>
          <Table.Th>Total issues</Table.Th>
          <Table.Td>135</Table.Td>
        </Table.Tr>

        <Table.Tr>
          <Table.Th>Total story points</Table.Th>
          <Table.Td>874</Table.Td>
        </Table.Tr>

        <Table.Tr>
          <Table.Th>Last updated at</Table.Th>
          <Table.Td>September 26, 2024 17:41:26</Table.Td>
        </Table.Tr>
      </Table.Tbody>
    </Table>
  );
}

Table tabular numbers

Table component now supports tabularNums prop to render numbers in tabular style. It sets
font-variant-numeric: tabular-nums which makes numbers to have equal width.
This is useful when you have columns with numbers and you want them to be aligned:

import { NumberFormatter, Table } from '@mantine/core';

const data = [
  { product: 'Apples', unitsSold: 2214411234 },
  { product: 'Oranges', unitsSold: 9983812411 },
  { product: 'Bananas', unitsSold: 1234567890 },
  { product: 'Pineapples', unitsSold: 9948810000 },
  { product: 'Pears', unitsSold: 9933771111 },
];

function Demo() {
  const rows = data.map((item) => (
    <Table.Tr key={item.product}>
      <Table.Td>{item.product}</Table.Td>
      <Table.Td>
        <NumberFormatter value={item.unitsSold} thousandSeparator />
      </Table.Td>
    </Table.Tr>
  ));

  return (
    <Table tabularNums>
      <Table.Thead>
        <Table.Tr>
          <Table.Th>Product</Table.Th>
          <Table.Th>Units sold</Table.Th>
        </Table.Tr>
      </Table.Thead>
      <Table.Tbody>{rows}</Table.Tbody>
    </Table>
  );
}

Update function in modals manager

Modals manager now supports modals.updateModal and modals.updateContextModal
function to update modal after it was opened:

import { Button } from '@mantine/core';
import { modals } from '@mantine/modals';

function Demo() {
  return (
    <Button
      onClick={() => {
        const modalId = modals.open({
          title: 'Initial Modal Title',
          children: <Text>This text will update in 2 seconds.</Text>,
        });

        setTimeout(() => {
          modals.updateModal({
            modalId,
            title: 'Updated Modal Title',
            children: (
              <Text size="sm" c="dimmed">
                This is the updated content of the modal.
              </Text>
            ),
          });
        }, 2000);
      }}
    >
      Open updating modal
    </Button>
  );
}

useForm submitting state

use-form hook now supports form.submitting field
and form.setSubmitting function to track form submission state.

form.submitting field will be set to true if function passed to
form.onSubmit returns a promise. After the promise is resolved or rejected,
form.submitting will be set to false:

import { useState } from 'react';
import { Button, Group, Stack, Text, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';

const asyncSubmit = (values: any) =>
  new Promise((resolve) => setTimeout(() => resolve(values), 3000));

function Demo() {
  const form = useForm({
    mode: 'uncontrolled',
    initialValues: { name: 'John' },
  });

  const [completed, setCompleted] = useState(false);

  const handleSubmit = async (values: typeof form.values) => {
    await asyncSubmit(values);
    setCompleted(true);
  };

  if (completed) {
    return (
      <Stack>
        <Text>Form submitted!</Text>
        <Button onClick={() => setCompleted(false)}>Reset to initial state</Button>
      </Stack>
    );
  }

  return (
    <form onSubmit={form.onSubmit(handleSubmit)}>
      <TextInput
        withAsterisk
        label="Name"
        placeholder="Your name"
        key={form.key('name')}
        disabled={form.submitting}
        {...form.getInputProps('name')}
      />

      <Group justify="flex-end" mt="md">
        <Button type="submit" loading={form.submitting}>
          Submit
        </Button>
      </Group>
    </form>
  );
}

You can also manually set form.submitting to true or false:

import { useForm } from '@mantine/form';

const form = useForm({ mode: 'uncontrolled' });
form.submitting; // -> false

form.setSubmitting(true);
form.submitting; // -> true

form.setSubmitting(false);
form.submitting; // -> false

useForm onSubmitPreventDefault option

use-form hook now supports onSubmitPreventDefault option.
This option is useful if you want to integrate useForm hook with server actions.
By default, event.preventDefault() is called on the form onSubmit handler.
If you want to change this behavior, you can pass onSubmitPreventDefault option
to useForm hook. It can have the following values:

  • always (default) - always call event.preventDefault()
  • never - never call event.preventDefault()
  • validation-failed - call event.preventDefault() only if validation failed
import { useForm } from '@mantine/form';

const form = useForm({
  mode: 'uncontrolled',
  onSubmitPreventDefault: 'never',
});

Subtle RichTextEditor variant

RichTextEditor component now supports subtle variant:

import Highlight from '@tiptap/extension-highlight';
import Underline from '@tiptap/extension-underline';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@mantine/tiptap';

const content = '<p>Subtle rich text editor variant</p>';

function Demo() {
  const editor = useEditor({
    extensions: [StarterKit, Underline, Highlight],
    content,
  });

  return (
    <RichTextEditor editor={editor} variant="subtle">
      <RichTextEditor.Toolbar sticky stickyOffset={60}>
        <RichTextEditor.ControlsGroup>
          <RichTextEditor.Bold />
          <RichTextEditor.Italic />
          <RichTextEditor.Underline />
          <RichTextEditor.Strikethrough />
          <RichTextEditor.ClearFormatting />
          <RichTextEditor.Highlight />
          <RichTextEditor.Code />
        </RichTextEditor.ControlsGroup>
      </RichTextEditor.Toolbar>

      <RichTextEditor.Content />
    </RichTextEditor>
  );
}

onExitTransitionEnd and onEnterTransitionEnd

Modal and Drawer components now support onExitTransitionEnd and onEnterTransitionEnd prop...

Read more

7.14.3

28 Nov 12:04
Compare
Choose a tag to compare

What's Changed

  • [@mantine/core] Slider: Fix restrictToMarks prop not working with arrow and Home/End keys correctly
  • [@mantine/core] Checkbox: Fix Checkbox.Card component not working with form.getInputProps
  • [@mantine/core] Tree: Add checkOnSpace prop support (#7132)
  • [@mantine/core] ScrollArea: Fix opacity style of thumb being too specific (#7149)
  • [@mantine/dates] Add withWeekNumbers prop support to all components based on Calendar (#7179)
  • [@mantine/core] Replace global JSX types with React.JSX to support React 19 types (#7178)

New Contributors

Full Changelog: 7.14.2...7.14.3