7.10.0
mantinedev/mantine7.10.0May 23, 2024by rtivital
AI Summary
Introduces a new Tree component, form input node handler, container query support in SimpleGrid, and new Checkbox/Radio indicators and card variants.
Key Highlights
- New Tree component for hierarchical data visualization with custom rendering capabilities.
- New `form.getInputNode` method to programmatically access input DOM nodes for focus management.
- Container query support added to SimpleGrid for responsive layouts based on container width.
- New `Checkbox.Indicator`, `Radio.Indicator`, `Checkbox.Card`, and `Radio.Card` components for custom UI patterns.
New Features
- Tree component
- form.getInputNode
- Container queries in SimpleGrid
- Checkbox.Indicator
- Radio.Indicator
- Checkbox.Card
- Radio.Card
Full Release Notes
[View changelog with demos on mantine.dev website](https://mantine.dev/changelog/7-10-0)
## Tree component
New [Tree](https://mantine.dev/core/tree) component:
```tsx
import { IconFolder, IconFolderOpen } from '@tabler/icons-react';
import { Group, RenderTreeNodePayload, Tree } from '@mantine/core';
import { CssIcon, NpmIcon, TypeScriptCircleIcon } from '@mantinex/dev-icons';
import { data, dataCode } from './data';
import classes from './Demo.module.css';
interface FileIconProps {
name: string;
isFolder: boolean;
expanded: boolean;
}
function FileIcon({ name, isFolder, expanded }: FileIconProps) {
if (name.endsWith('package.json')) {
return <NpmIcon size={14} />;
}
if (name.endsWith('.ts') || name.endsWith('.tsx') || name.endsWith('tsconfig.json')) {
return <TypeScriptCircleIcon size={14} />;
}
if (name.endsWith('.css')) {
return <CssIcon size={14} />;
}
if (isFolder) {
return expanded ? (
<IconFolderOpen color="var(--mantine-color-yellow-9)" size={14} stroke={2.5} />
) : (
<IconFolder color="var(--mantine-color-yellow-9)" size={14} stroke={2.5} />
);
}
return null;
}
function Leaf({ node, expanded, hasChildren, elementProps }: RenderTreeNodePayload) {
return (
<Group gap={5} {...elementProps}>
<FileIcon name={node.value} isFolder={hasChildren} expanded={expanded} />
<span>{node.label}</span>
</Group>
);
}
function Demo() {
return (
<Tree
classNames={classes}
selectOnClick
clearSelectionOnOutsideClick
data={data}
renderNode={(payload) => <Leaf {...payload} />}
/>
);
}
```
## form.getInputNode
New `form.getInputNode(path)` handler returns input DOM node for the given field path.
Form example, it can be used to focus input on form submit if there is an error:
```tsx
import { Button, Group, TextInput } from '@mantine/core';
import { isEmail, isNotEmpty, useForm } from '@mantine/form';
function Demo() {
const form = useForm({
mode: 'uncontrolled',
initialValues: {
name: '',
email: '',
},
validate: {
name: isNotEmpty('Name is required'),
email: isEmail('Invalid email'),
},
});
return (
<form
onSubmit={form.onSubmit(
(values) => console.log(values),
(errors) => {
const firstErrorPath = Object.keys(errors)[0];
form.getInputNode(firstErrorPath)?.focus();
}
)}
>
<TextInput
withAsterisk
label="Your name"
placeholder="Your name"
key={form.key('name')}
{...form.getInputProps('name')}
/>
<TextInput
withAsterisk
label="Your email"
placeholder="your@email.com"
key={form.key('email')}
{...form.getInputProps('email')}
/>
<Group justify="flex-end" mt="md">
<Button type="submit">Submit</Button>
</Group>
</form>
);
}
```
## Container queries in SimpleGrid
You can now use [container queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries)
in [SimpleGrid](https://mantine.dev/core/simple-grid) component. With container queries, grid columns and spacing
will be 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:
```tsx
import { SimpleGrid } from '@mantine/core';
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%' }}>
<SimpleGrid
type="container"
cols={{ base: 1, '300px': 2, '500px': 5 }}
spacing={{ base: 10, '300px': 'xl' }}
>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</SimpleGrid>
</div>
);
}
```
## Checkbox and Radio indicators
New [Checkbox.Indicator](https://mantine.dev/core/checkbox/#checkboxindicator) and [Radio.Indicator](https://mantine.dev/core/radio/#radioindicator)
components look exactly the same as `Checkbox` and `Radio` components, but they do not
have any semantic meaning, they are just visual representations of checkbox and radio states.
`Checkbox.Indicator` component:
```tsx
import { Checkbox, Group } from '@mantine/core';
function Demo() {
return (
<Group>
<Checkbox.Indicator />
<Checkbox.Indicator checked />
<Checkbox.Indicator indeterminate />
<Checkbox.Indicator disabled />
<Checkbox.Indicator disabled checked />
<Checkbox.Indicator disabled indeterminate />
</Group>
);
}
```
`Radio.Indicator` component:
```tsx
import { Group, Radio } from '@mantine/core';
function Demo() {
return (
<Group>
<Radio.Indicator />
<Radio.Indicator checked />
<Radio.Indicator disabled />
<Radio.Indicator disabled checked />
</Group>
);
}
```
## Checkbox and Radio cards
New [Checkbox.Card](https://mantine.dev/core/checkbox/#checkboxcard-component) and [Radio.Card](https://mantine.dev/core/radio/#radiocard-component)
components can be used as replacements for `Checkbox` and `Radio` to build custom cards/buttons/etc.
that work as checkboxes and radios. Components are accessible by default and support the same
keyboard interactions as `input[type="checkbox"]` and `input[type="radio"]`.
`Checkbox.Card` component:
```tsx
import { useState } from 'react';
import { Checkbox, Group, Text } from '@mantine/core';
import classes from './Demo.module.css';
function Demo() {
const [checked, setChecked] = useState(false);
return (
<Checkbox.Card
className={classes.root}
radius="md"
checked={checked}
onClick={() => setChecked((c) => !c)}
>
<Group wrap="nowrap" align="flex-start">
<Checkbox.Indicator />
<div>
<Text className={classes.label}>@mantine/core</Text>
<Text className={classes.description}>
Core components library: inputs, buttons, overlays, etc.
</Text>
</div>
</Group>
</Checkbox.Card>
);
}
```
`Checkbox.Card` component with `Checkbox.Group`:
```tsx
import { useState } from 'react';
import { Checkbox, Group, Stack, Text } from '@mantine/core';
import classes from './Demo.module.css';
const data = [
{
name: '@mantine/core',
description: 'Core components library: inputs, buttons, overlays, etc.',
},
{ name: '@mantine/hooks', description: 'Collection of reusable hooks for React applications.' },
{ name: '@mantine/notifications', description: 'Notifications system' },
];
function Demo() {
const [value, setValue] = useState<string[]>([]);
const cards = data.map((item) => (
<Checkbox.Card className={classes.root} radius="md" value={item.name} key={item.name}>
<Group wrap="nowrap" align="flex-start">
<Checkbox.Indicator />
<div>
<Text className={classes.label}>{item.name}</Text>
<Text className={classes.description}>{item.description}</Text>
</div>
</Group>
</Checkbox.Card>
));
return (
<>
<Checkbox.Group
value={value}
onChange={setValue}
label="Pick packages to install"
description="Choose all packages that you will need in your application"
>
<Stack pt="md" gap="xs">
{cards}
</Stack>
</Checkbox.Group>
<Text fz="xs" mt="md">
CurrentValue: {value.join(', ') || '–'}
</Text>
</>
);
}
```
`Radio.Card` component:
```tsx
import { useState } from 'react';
import { Group, Radio, Text } from '@mantine/core';
import classes from './Demo.module.css';
function Demo() {
const [checked, setChecked] = useState(false);
return (
<Radio.Card
className={classes.root}
radius="md"
checked={checked}
onClick={() => setChecked((c) => !c)}
>
<Group wrap="nowrap" align="flex-start">
<Radio.Indicator />
<div>
<Text className={classes.label}>@mantine/core</Text>
<Text className={classes.description}>
Core components library: inputs, buttons, overlays, etc.
</Text>
</div>
</Group>
</Radio.Card>
);
}
```
`Radio.Card` component with `Radio.Group`:
```tsx
import { useState } from 'react';
import { Group, Radio, Stack, Text } from '@mantine/core';
import classes from './Demo.module.css';
const data = [
{
name: '@mantine/core',
description: 'Core components library: inputs, buttons, overlays, etc.',
},
{ name: '@mantine/hooks', description: 'Collection of reusable hooks for React applications.' },
{ name: '@mantine/notifications', description: 'Notifications system' },
];
function Demo() {
const [value, setValue] = useState<string | null>(null);
const cards = data.map((item) => (
<Radio.Card className={classes.root} radius="md" value={item.name} key={item.name}>
<Group wrap="nowrap" align="flex-start">
<Radio.Indicator />
<div>
<Text className={classes.label}>{item.name}</Text>
<Text className={classes.description}>{item.description}</Text>
</div>
</Group>
</Radio.Card>
));
return (
<>
<Radio.Group
value={value}
onChange={setValue}
label="Pick one package to install"
description="Choose a package that you will need in your application"
>
<Stack pt="md" gap="xs">
{cards}
</Stack>
</Radio.Group>
<Text fz="xs" mt="md">
CurrentValue: {value || '–'}
</Text>
</>
);
}
```
## bd style prop
New [bd style prop](https://mantine.dev/styles/style-props/) can be used to set `border` CSS property.
It is available in all components that support style props.
Border width value is automatically converted to rem. For border color you can reference
theme colors similar to other style props:
```tsx
import { Box } from '@mantine/core';
function Demo() {
return <Box bd="1px solid red.5" />;
}
```