7.7.0
mantinedev/mantine7.7.0Mar 26, 2024by rtivital
AI Summary
Major release introducing 'Virtual Colors' for dynamic light/dark schemes, the new `FloatingIndicator` component, and the `ScatterChart` component.
Key Highlights
- New `virtualColor` function for dynamic light and dark color schemes
- New `FloatingIndicator` component for animated indicators
- New `ScatterChart` component for scatter plot visualizations
New Features
- virtualColor function
- FloatingIndicator component
- ScatterChart component
Full Release Notes
[View changelog with demos on mantine.dev website](https://mantine.dev/changelog/7-7-0)
## Virtual colors
Virtual color is a special color which values should be different for light and dark color schemes.
To define a virtual color, use `virtualColor` function which accepts an object with the following
properties as a single argument:
- `name` – color name, must be the same as the key in `theme.colors` object
- `light` – a key of `theme.colors` object for light color scheme
- `dark` – a key of `theme.colors` object for dark color scheme
To see the demo in action, switch between light and dark color schemes (`Ctrl + J`):
```tsx
import { createTheme, MantineProvider, virtualColor } from '@mantine/core';
const theme = createTheme({
colors: {
primary: virtualColor({
name: 'primary',
dark: 'pink',
light: 'cyan',
}),
},
});
function App() {
return <MantineProvider theme={theme}>{/* Your app here */}</MantineProvider>;
}
```
## FloatingIndicator component
New [FloatingIndicator](https://mantine.dev/core/floating-indicator) component:
```tsx
import { useState } from 'react';
import {
IconArrowDown,
IconArrowDownLeft,
IconArrowDownRight,
IconArrowLeft,
IconArrowRight,
IconArrowUp,
IconArrowUpLeft,
IconArrowUpRight,
IconCircle,
} from '@tabler/icons-react';
import { FloatingIndicator, UnstyledButton } from '@mantine/core';
import classes from './Demo.module.css';
function Demo() {
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
const [active, setActive] = useState('center');
const setControlRef = (name: string) => (node: HTMLButtonElement) => {
controlsRefs[name] = node;
setControlsRefs(controlsRefs);
};
return (
<div className={classes.root} dir="ltr" ref={setRootRef}>
<FloatingIndicator
target={controlsRefs[active]}
parent={rootRef}
className={classes.indicator}
/>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-left')}
ref={setControlRef('up-left')}
mod={{ active: active === 'up-left' }}
>
<IconArrowUpLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up')}
ref={setControlRef('up')}
mod={{ active: active === 'up' }}
>
<IconArrowUp size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-right')}
ref={setControlRef('up-right')}
mod={{ active: active === 'up-right' }}
>
<IconArrowUpRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('left')}
ref={setControlRef('left')}
mod={{ active: active === 'left' }}
>
<IconArrowLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('center')}
ref={setControlRef('center')}
mod={{ active: active === 'center' }}
>
<IconCircle size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('right')}
ref={setControlRef('right')}
mod={{ active: active === 'right' }}
>
<IconArrowRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-left')}
ref={setControlRef('down-left')}
mod={{ active: active === 'down-left' }}
>
<IconArrowDownLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down')}
ref={setControlRef('down')}
mod={{ active: active === 'down' }}
>
<IconArrowDown size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-right')}
ref={setControlRef('down-right')}
mod={{ active: active === 'down-right' }}
>
<IconArrowDownRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
</div>
);
}
```
## ScatterChart component
New [ScatterChart](https://mantine.dev/charts/scatter-chart) component:
```tsx
import { useState } from 'react';
import {
IconArrowDown,
IconArrowDownLeft,
IconArrowDownRight,
IconArrowLeft,
IconArrowRight,
IconArrowUp,
IconArrowUpLeft,
IconArrowUpRight,
IconCircle,
} from '@tabler/icons-react';
import { FloatingIndicator, UnstyledButton } from '@mantine/core';
import classes from './Demo.module.css';
function Demo() {
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
const [active, setActive] = useState('center');
const setControlRef = (name: string) => (node: HTMLButtonElement) => {
controlsRefs[name] = node;
setControlsRefs(controlsRefs);
};
return (
<div className={classes.root} dir="ltr" ref={setRootRef}>
<FloatingIndicator
target={controlsRefs[active]}
parent={rootRef}
className={classes.indicator}
/>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-left')}
ref={setControlRef('up-left')}
mod={{ active: active === 'up-left' }}
>
<IconArrowUpLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up')}
ref={setControlRef('up')}
mod={{ active: active === 'up' }}
>
<IconArrowUp size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-right')}
ref={setControlRef('up-right')}
mod={{ active: active === 'up-right' }}
>
<IconArrowUpRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('left')}
ref={setControlRef('left')}
mod={{ active: active === 'left' }}
>
<IconArrowLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('center')}
ref={setControlRef('center')}
mod={{ active: active === 'center' }}
>
<IconCircle size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('right')}
ref={setControlRef('right')}
mod={{ active: active === 'right' }}
>
<IconArrowRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-left')}
ref={setControlRef('down-left')}
mod={{ active: active === 'down-left' }}
>
<IconArrowDownLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down')}
ref={setControlRef('down')}
mod={{ active: active === 'down' }}
>
<IconArrowDown size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-right')}
ref={setControlRef('down-right')}
mod={{ active: active === 'down-right' }}
>
<IconArrowDownRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
</div>
);
}
```
## colorsTuple function
New `colorsTuple` function can be used to:
- Use single color as the same color for all shades
- Transform dynamic string arrays to Mantine color tuple (the array should still have 10 values)
```tsx
import { colorsTuple, createTheme } from '@mantine/core';
const theme = createTheme({
colors: {
custom: colorsTuple('#FFC0CB'),
dynamic: colorsTuple(Array.from({ length: 10 }, (_, index) => '#FFC0CB')),
},
});
```
## use-mutation-observer hook
New [useMutationObserver](https://mantine.dev/hooks/use-mutation-observer) hook:
```tsx
import { useState } from 'react';
import { Kbd, Text } from '@mantine/core';
import { useMutationObserver } from '@mantine/hooks';
function Demo() {
const [lastMutation, setLastMutation] = useState('');
useMutationObserver(
(mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'dir') {
mutation.target instanceof HTMLElement &&
setLastMutation(mutation.target.getAttribute('dir') || '');
}
});
},
{
attributes: true,
attributeFilter: ['dir'],
},
() => document.documentElement
);
return (
<>
<Text>
Press <Kbd>Ctrl</Kbd> + <Kbd>Shift</Kbd> + <Kbd>L</Kbd> to change direction
</Text>
<Text mt={10}>Direction was changed to: {lastMutation || 'Not changed yet'}</Text>
</>
);
}
```
## use-state-history hook
New [useStateHistory](https://mantine.dev/hooks/use-state-history) hook:
```tsx
import { Button, Code, Group, Text } from '@mantine/core';
import { useStateHistory } from '@mantine/hooks';
function Demo() {
const [value, handlers, history] = useStateHistory(1);
return (
<>
<Text>Current value: {value}</Text>
<Group my="md">
<Button onClick={() => handlers.set(Math.ceil(Math.random() * 100) + 1)}>Set value</Button>
<Button onClick={() => handlers.back()}>Back</Button>
<Button onClick={() => handlers.forward()}>Forward</Button>
</Group>
<Code block>{JSON.stringify(history, null, 2)}</Code>
</>
);
}
```
## Axis labels
[AreaChart](https://mantine.dev/charts/area-chart), [BarChart](https://mantine.dev/charts/bar-chart) and [LineChart](https://mantine.dev/charts/line-chart)
components now support `xAxisLabel` and `yAxisLabel` props:
```tsx
import { AreaChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<AreaChart
h={300}
data={data}
dataKey="date"
type="stacked"
xAxisLabel="Date"
yAxisLabel="Amount"
series={[
{ name: 'Apples', color: 'indigo.6' },
{ name: 'Oranges', color: 'blue.6' },
{ name: 'Tomatoes', color: 'teal.6' },
]}
/>
);
}
```
## Documentation updates
- New section has been added to the [responsive guide](https://mantine.dev/styles/responsive#hidden-and-visible-from-as-classes) on how to use `mantine-hidden-from-{x}` and `mantine-visible-from-{x}` classes.
- [Remix guide](https://mantine.dev/guides/remix) has been updated to use new Vite bundler
- [Jest](https://mantine.dev/guides/jest) and [Vitest](https://mantine.dev/guides/vitest) guides configuration has been updated to include mocks for `window.HTMLElement.prototype.scrollIntoView`
- [CSS variables](https://mantine.dev/styles/css-variables) documentation has been updated to include more information about typography and colors variables
## Help center updates
New articles added to the [help center](https://help.mantine.dev):
- [Can I use SegmentedControl with empty value?](https://help.mantine.dev/q/segmented-control-no-value)
- [Is there a comparison with other libraries?](https://help.mantine.dev/q/other-libs)
- [Can I use Mantine with Vue/Svelte/Angular/etc.?](https://help.mantine.dev/q/vue-svelte-angular)
- [How can I test Select/MultiSelect components?](https://help.mantine.dev/q/combobox-testing)
## Other changes
- [SegmentedControl](https://mantine.dev/core/segmented-control) indicator positioning logic has been migrated to [FloatingIndicator](https://mantine.dev/core/floating-indicator). It is now more performant and works better when used inside elements with `transform: scale()`.
- New [use-mounted](https://mantine.dev/hooks/use-mounted) hook
- [Sparkline](https://mantine.dev/charts/sparkline) now supports `connectNulls` and `areaProps` props
- [Remix template](https://github.com/mantinedev/remix-template) has been updated to use new Vite bundler
- [Select](https://mantine.dev/core/select), [MultiSelect](https://mantine.dev/core/multi-select), [Autocomplete](https://mantine.dev/core/autocomplete) and [TagsInput](https://mantine.dev/core/tags-input) components now support `scrollAreaProps` prop to pass props down to the [ScrollArea](https://mantine.dev/core/scroll-area) component in the dropdown
- [Transition](https://mantine.dev/core/transition) component now supports 4 new transitions: `fade-up`, `fade-down`, `fade-left`, `fade-right`
- Default [Modal](https://mantine.dev/core/modal) transition was changed to `fade-down`. This change resolves issues with [SegmentedControl](https://mantine.dev/core/segmented-control) indicator positioning when used inside modals.
- You can now reference headings font sizes and line heights in `fz` and `lh` style props with `h1`, `h2`, `h3`, `h4`, `h5`, `h6` values