diff --git a/.gitignore b/.gitignore index 7a75e5cb13a..4cceae6b898 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ apps/mantine.dev/.cache apps/mantine.dev/out apps/mantine.dev/.next apps/mantine.dev/src/.docgen/ +.samples diff --git a/.storybook/main.ts b/.storybook/main.ts index 642f666a546..9f352326845 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -32,6 +32,11 @@ const storiesPath = !argv._[1] export default { stories: storiesPath, + core: { + disableWhatsNewNotifications: true, + disableTelemetry: true, + enableCrashReports: false, + }, addons: [ getAbsolutePath('storybook-dark-mode'), diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index dfa5d5a7e3c..0d9a4e78aa2 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -15,7 +15,12 @@ import { Notifications } from '@mantine/notifications'; import { ShikiProvider } from '@mantinex/shiki'; import { theme } from '../apps/mantine.dev/theme'; -export const parameters = { layout: 'fullscreen' }; +export const parameters = { + layout: 'fullscreen', + options: { + showPanel: false, + }, +}; const channel = addons.getChannel(); diff --git a/apps/help.mantine.dev/src/demos/NestedPopovers.demo.tsx b/apps/help.mantine.dev/src/demos/NestedPopovers.demo.tsx new file mode 100644 index 00000000000..05d8f6dce45 --- /dev/null +++ b/apps/help.mantine.dev/src/demos/NestedPopovers.demo.tsx @@ -0,0 +1,108 @@ +import { Button, Popover, Select } from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; + +const codeWithIssue = ` +import { Button, Popover, Select } from '@mantine/core'; +import { InputBase } from '@mantine/core'; + +function Demo() { + return ( + + + + + + + + + ); +} + +export const NestedPopovers: MantineDemo = { + type: 'code', + component: DemoWithIssue, + code: codeWithIssue, + centered: true, +}; + +const codeWithoutIssue = ` +import { Button, Popover, Select } from '@mantine/core'; +import { InputBase } from '@mantine/core'; + +function Demo() { + return ( + + + + + + + + + ); +} + +export const NestedPopoversWorking: MantineDemo = { + type: 'code', + component: DemoWithoutIssue, + code: codeWithoutIssue, + centered: true, +}; diff --git a/apps/help.mantine.dev/src/mdx.ts b/apps/help.mantine.dev/src/mdx.ts index c4669a0d05e..0b3e212d2cb 100644 --- a/apps/help.mantine.dev/src/mdx.ts +++ b/apps/help.mantine.dev/src/mdx.ts @@ -1,9 +1,11 @@ import { meta as alignInputButton } from './pages/q/align-input-button.mdx'; import { meta as areMantineComponentsAccessible } from './pages/q/are-mantine-components-accessible.mdx'; import { meta as autocompleteValueLabel } from './pages/q/autocomplete-value-label.mdx'; +import { meta as bodyBackground } from './pages/q/body-background.mdx'; import { meta as browserZoomsOnFocus } from './pages/q/browser-zooms-on-focus.mdx'; import { meta as canIUseMantineWithAstro } from './pages/q/can-i-use-mantine-with-astro.mdx'; import { meta as canIUseMantineWithCra } from './pages/q/can-i-use-mantine-with-cra.mdx'; +import { meta as carouselMissingStyles } from './pages/q/carousel-missing-styles.mdx'; import { meta as colorSchemeColor } from './pages/q/color-scheme-color.mdx'; import { meta as comboboxTesting } from './pages/q/combobox-testing.mdx'; import { meta as customInputUseForm } from './pages/q/custom-input-use-form.mdx'; @@ -31,12 +33,16 @@ import { meta as lightDarkIsNotEnough } from './pages/q/light-dark-is-not-enough import { meta as listOfStringsInUseForm } from './pages/q/list-of-strings-in-use-form.mdx'; import { meta as mantineProviderMissing } from './pages/q/mantine-provider-missing.mdx'; import { meta as multiselectValuePlaceholder } from './pages/q/multiselect-value-placeholder.mdx'; +import { meta as nestedInlineStyles } from './pages/q/nested-inline-styles.mdx'; +import { meta as nestedPopoverCloses } from './pages/q/nested-popover-closes.mdx'; import { meta as nextLoadFonts } from './pages/q/next-load-fonts.mdx'; import { meta as notificationsEmptyScreen } from './pages/q/notifications-empty-screen.mdx'; import { meta as notificationsMissingStyles } from './pages/q/notifications-missing-styles.mdx'; import { meta as otherLibs } from './pages/q/other-libs.mdx'; import { meta as pinchToZoomModal } from './pages/q/pinch-to-zoom-modal.mdx'; import { meta as polymorphicInPolymorphic } from './pages/q/polymorphic-in-polymorphic.mdx'; +import { meta as postcssFnsInline } from './pages/q/postcss-fns-inline.mdx'; +import { meta as primaryVirtualColor } from './pages/q/primary-virtual-color.mdx'; import { meta as privateCssVariables } from './pages/q/private-css-variables.mdx'; import { meta as remixLoadFonts } from './pages/q/remix-load-fonts.mdx'; import { meta as roadmap } from './pages/q/roadmap.mdx'; @@ -47,6 +53,7 @@ import { meta as serverComponents } from './pages/q/server-components.mdx'; import { meta as stylesOrder } from './pages/q/styles-order.mdx'; import { meta as tabsBorderColor } from './pages/q/tabs-border-color.mdx'; import { meta as thirdPartyStyles } from './pages/q/third-party-styles.mdx'; +import { meta as transparentButtons } from './pages/q/transparent-buttons.mdx'; import { meta as viteLoadFonts } from './pages/q/vite-load-fonts.mdx'; import { meta as vueSvelteAngular } from './pages/q/vue-svelte-angular.mdx'; import { meta as whyVscodeCannotAutoimportText } from './pages/q/why-vscode-cannot-autoimport-text.mdx'; @@ -55,9 +62,11 @@ export const MDX_DATA = [ alignInputButton, areMantineComponentsAccessible, autocompleteValueLabel, + bodyBackground, browserZoomsOnFocus, canIUseMantineWithAstro, canIUseMantineWithCra, + carouselMissingStyles, colorSchemeColor, comboboxTesting, customInputUseForm, @@ -85,12 +94,16 @@ export const MDX_DATA = [ listOfStringsInUseForm, mantineProviderMissing, multiselectValuePlaceholder, + nestedInlineStyles, + nestedPopoverCloses, nextLoadFonts, notificationsEmptyScreen, notificationsMissingStyles, otherLibs, pinchToZoomModal, polymorphicInPolymorphic, + postcssFnsInline, + primaryVirtualColor, privateCssVariables, remixLoadFonts, roadmap, @@ -101,6 +114,7 @@ export const MDX_DATA = [ stylesOrder, tabsBorderColor, thirdPartyStyles, + transparentButtons, viteLoadFonts, vueSvelteAngular, whyVscodeCannotAutoimportText, diff --git a/apps/help.mantine.dev/src/pages/q/body-background.mdx b/apps/help.mantine.dev/src/pages/q/body-background.mdx new file mode 100644 index 00000000000..3414498f4da --- /dev/null +++ b/apps/help.mantine.dev/src/pages/q/body-background.mdx @@ -0,0 +1,42 @@ +import { Layout } from '@/layout'; + +export const meta = { + title: 'How can I change body background color?', + description: 'Use CSS to change body background color', + slug: 'body-background', + category: 'styles', + tags: ['body', 'html', 'global styles'], + created_at: 'September 8, 2024', + last_updated_at: 'September 8, 2024', +}; + +export default Layout(meta); + +## Change body background with CSS + +To change `body` background color you can use CSS. To do that, create `styles.css` +file in your project and import it at the root of your application: + +```css +body { + background-color: #f9f9f9; +} +``` + +## Change body background with CSS variable + +`--mantine-color-body` CSS variable is used for body background and +as background color of some components ([Modal](https://mantine.dev/core/modal/), [Paper](https://mantine.dev/core/paper/), etc.). +To override this variable, create `styles.css` file in your project and import it at the root of your application: + +```scss +:root { + @mixin light { + --mantine-color-body: #f9f9f9; + } + + @mixin dark { + --mantine-color-body: #333; + } +} +``` diff --git a/apps/help.mantine.dev/src/pages/q/carousel-missing-styles.mdx b/apps/help.mantine.dev/src/pages/q/carousel-missing-styles.mdx new file mode 100644 index 00000000000..7ef103bcce5 --- /dev/null +++ b/apps/help.mantine.dev/src/pages/q/carousel-missing-styles.mdx @@ -0,0 +1,30 @@ +import { Layout } from '@/layout'; + +export const meta = { + title: 'Why my Carousel slides are in vertical orientation?', + description: 'You forgot to import carousel styles', + slug: 'carousel-missing-styles', + category: 'styles', + tags: ['carousel', '@mantine/carousel', 'broken'], + created_at: 'September 7, 2024', + last_updated_at: 'September 7, 2024', +}; + +export default Layout(meta); + +## Carousel component looks broken + +If your [Carousel](https://mantine.dev/x/carousel/) component renders slides in vertical orientation +or has incorrect controls/indicators position, you forgot to import carousel styles. +Follow [@mantine/carousel](https://mantine.dev/x/carousel/#installation) installation +instructions to fix the issue. Import `@mantine/core` and `@mantine/carousel` styles at +the root of your application: + +```tsx +import '@mantine/core/styles.css'; +import '@mantine/carousel/styles.css'; +``` + +## That's it! It works now! + +Nice! 👍 diff --git a/apps/help.mantine.dev/src/pages/q/nested-inline-styles.mdx b/apps/help.mantine.dev/src/pages/q/nested-inline-styles.mdx new file mode 100644 index 00000000000..bcfa2ec0f35 --- /dev/null +++ b/apps/help.mantine.dev/src/pages/q/nested-inline-styles.mdx @@ -0,0 +1,99 @@ +import { Layout } from '@/layout'; + +export const meta = { + title: 'Can I use nested inline styles with Mantine components?', + description: 'Nested styles are supported only in CSS files', + slug: 'nested-inline-styles', + category: 'styles', + tags: ['hover', 'focus', 'data-', '::selection'], + created_at: 'September 7, 2024', + last_updated_at: 'September 7, 2024', +}; + +export default Layout(meta); + +## What are nested inline styles? + +Nested inline styles are commonly used in CSS-in-JS libraries like [emotion](https://emotion.sh/). +Nested inline styles syntax looks something like this (example from [emotion documentation](https://emotion.sh/docs/css-prop#object-styles)): + +```tsx +render( +
+ This has a hotpink background. +
+); +``` + +## Styles in Mantine components + +Mantine components do not support nested inline styles out of the box. The following +example will not work: + +```tsx +import { Button } from '@mantine/core'; + +function Demo() { + return ( + + ); +} +``` + +## Why nested inline styles are not supported? + +Mantine does not use CSS-in-JS library for styling – all styles are either in CSS files +or inline in the `style` attribute which does not support nested styles. Mantine does not +use CSS-in-JS to keep bundle size small, provide support for server-side rendering and +improve performance. You can learn more about performance [in the styles performance guide](https://mantine.dev/styles/styles-performance/). + +## What is the alternative? + +You can use nested selectors in [CSS files](https://mantine.dev/styles/css-modules/): + +```scss +.button { + background-color: hotpink; + + &:hover { + color: lightgreen; + } +} +``` + +To learn more about styles in Mantine, follow [CSS modules](https://mantine.dev/styles/css-modules/), +[PostCSS preset](https://mantine.dev/styles/postcss-preset/) and [Styles API](https://mantine.dev/styles/styles-api/) guides. + +## I still want to use nested inline styles + +Mantine has support for emotion. To set it up, follow [emotion installation guide](https://mantine.dev/styles/emotion/). +Note that this will increase bundle size and will affect performance. diff --git a/apps/help.mantine.dev/src/pages/q/nested-popover-closes.mdx b/apps/help.mantine.dev/src/pages/q/nested-popover-closes.mdx new file mode 100644 index 00000000000..1ed8db3bf74 --- /dev/null +++ b/apps/help.mantine.dev/src/pages/q/nested-popover-closes.mdx @@ -0,0 +1,44 @@ +import { + NestedPopovers, + NestedPopoversWorking, +} from '@/demos/NestedPopovers.demo'; +import { Layout } from '@/layout'; + +export const meta = { + title: + 'My Popover dropdown closes when I click on the dropdown of nested Popover', + description: + 'Popover dropdown is closed when it detects click outside events', + slug: 'nested-popover-closes', + category: 'components', + tags: ['popover', 'menu', 'DatePicker', 'Select'], + created_at: 'September 8, 2024', + last_updated_at: 'September 8, 2024', +}; + +export default Layout(meta); + +## Nested popovers + +By default, all popovers and dropdowns are rendered within [Portal](https://mantine.dev/core/portal/) +component which is attached to the `document.body`. +This allows popovers to be rendered on top of all other elements and to be positioned correctly even if parent element has `overflow: hidden`. + +[Popover](https://mantine.dev/core/popover/) component uses [use-click-outside](https://mantine.dev/hooks/use-click-outside/) hook to detect clicks outside of the popover. +When you click on the nested popover, it detects that click as outside click and closes the parent popover. +This happens with every component that uses [Popover](https://mantine.dev/core/popover/) under the hood, including [DatePicker](https://mantine.dev/dates/date-picker/), [Select](https://mantine.dev/select/select/), +[Menu](https://mantine.dev/menu/menu/), and others. + +Example of the issue: + + + +## How to fix + +To fix the issue, set `withinPortal={false}` prop on the nested popover. Note that +this option might be a part of the other prop (for example `comboboxProps` in [Select](https://mantine.dev/select/select/)). +To learn which prop to use, check the documentation of the component you are using. + +Example of the fixed issue: + + diff --git a/apps/help.mantine.dev/src/pages/q/postcss-fns-inline.mdx b/apps/help.mantine.dev/src/pages/q/postcss-fns-inline.mdx new file mode 100644 index 00000000000..2e3324db8e6 --- /dev/null +++ b/apps/help.mantine.dev/src/pages/q/postcss-fns-inline.mdx @@ -0,0 +1,57 @@ +import { Layout } from '@/layout'; + +export const meta = { + title: 'Can I use PostCSS function in inline styles?', + description: 'Learn where PostCSS functions can be used in Mantine', + slug: 'postcss-fns-inline', + category: 'styles', + tags: ['postcss', 'light-dark', 'mixin'], + created_at: 'September 7, 2024', + last_updated_at: 'September 7, 2024', +}; + +export default Layout(meta); + +## What are PostCSS functions? + +[postcss-preset-mantine](https://mantine.dev/styles/postcss-preset/) provides functions, +mixins and other helpers to simplify working with Mantine styles. Example of using +`light-dark` function in styles: + +```scss +// What you write +.demo { + background: light-dark(white, black); +} + +// What you get after PostCSS processing +[data-mantine-color-scheme='light'] .demo { + background: black; +} + +[data-mantine-color-scheme='dark'] .demo { + background: black; +} +``` + +## Can I use PostCSS functions in inline styles? + +No, PostCSS functions are not supported in inline styles. **You can use PostCSS functions only in `.css` files**. +The following example will not work: + +```tsx +import { Button } from '@mantine/core'; + +function Demo() { + return ( + + ); +} +``` diff --git a/apps/help.mantine.dev/src/pages/q/primary-virtual-color.mdx b/apps/help.mantine.dev/src/pages/q/primary-virtual-color.mdx new file mode 100644 index 00000000000..733d6421d1c --- /dev/null +++ b/apps/help.mantine.dev/src/pages/q/primary-virtual-color.mdx @@ -0,0 +1,51 @@ +import { Layout } from '@/layout'; + +export const meta = { + title: + 'Can I have different primary color for light and dark color schemes?', + description: + 'Learn how to use virtual color with primary color in theme object', + slug: 'primary-virtual-color', + category: 'styles', + tags: ['colors', 'primaryColor'], + created_at: 'September 8, 2024', + last_updated_at: 'September 8, 2024', +}; + +export default Layout(meta); + +## Virtual colors + +To have different primary color for light and dark color schemes you can use [virtual color](https://mantine.dev/theming/colors/#virtual-colors). +Virtual color is a color that changes its value based on current color scheme. + +```tsx +import { + createTheme, + MantineProvider, + virtualColor, +} from '@mantine/core'; + +const theme = createTheme({ + primaryColor: 'primary', + + colors: { + primary: virtualColor({ + name: 'primary', + dark: 'pink', + light: 'cyan', + }), + }, +}); + +function App() { + return ( + + + This box has virtual background color, it is pink in dark mode + and cyan in light mode + + + ); +} +``` diff --git a/apps/help.mantine.dev/src/pages/q/transparent-buttons.mdx b/apps/help.mantine.dev/src/pages/q/transparent-buttons.mdx new file mode 100644 index 00000000000..059198d009f --- /dev/null +++ b/apps/help.mantine.dev/src/pages/q/transparent-buttons.mdx @@ -0,0 +1,30 @@ +import { Layout } from '@/layout'; + +export const meta = { + title: + 'My buttons are transparent and the background is visible only on hover, what is wrong?', + description: + 'You have installed a third-party library that overrides Mantine styles', + slug: 'transparent-buttons', + category: 'styles', + tags: ['tailwind', 'hover'], + created_at: 'September 8, 2024', + last_updated_at: 'September 8, 2024', +}; + +export default Layout(meta); + +## Why my buttons are transparent? + +If your buttons are transparent and the background is visible only on hover, you have installed a third-party library that overrides Mantine styles. +Tailwind CSS is the most common library that causes this issue. + +## How to fix it? + +To fix the issue follow one of the [guides from the community](https://github.com/orgs/mantinedev/discussions/1672) +that suits your project setup the best. + +## I do not use Tailwind, what else can cause this issue? + +This issue can be caused by any third-party library that overrides Mantine styles. +Explore element in dev tools to find out which styles are applied to the button and which library is responsible for it. diff --git a/apps/mantine.dev/src/mdx/data/mdx-charts-data.ts b/apps/mantine.dev/src/mdx/data/mdx-charts-data.ts index e0e0814f287..abb356e25cb 100644 --- a/apps/mantine.dev/src/mdx/data/mdx-charts-data.ts +++ b/apps/mantine.dev/src/mdx/data/mdx-charts-data.ts @@ -119,6 +119,18 @@ export const MDX_CHARTS_DATA: Record = { docs: 'charts/bubble-chart.mdx', }, + FunnelChart: { + title: 'FunnelChart', + props: ['FunnelChart'], + styles: ['FunnelChart'], + package: '@mantine/charts', + slug: '/charts/funnel-chart', + description: 'Funnel chart component', + import: "import { FunnelChart } from '@mantine/charts';", + source: '@mantine/charts/src/FunnelChart/FunnelChart.tsx', + docs: 'charts/funnel-chart.mdx', + }, + CompositeChart: { title: 'CompositeChart', props: ['CompositeChart'], @@ -130,4 +142,16 @@ export const MDX_CHARTS_DATA: Record = { source: '@mantine/charts/src/CompositeChart/CompositeChart.tsx', docs: 'charts/composite-chart.mdx', }, + + RadialBarChart: { + title: 'RadialBarChart', + props: ['RadialBarChart'], + styles: ['RadialBarChart'], + package: '@mantine/charts', + slug: '/charts/radial-bar-chart', + description: 'Radial bar chart component', + import: "import { RadialBarChart } from '@mantine/charts';", + source: '@mantine/charts/src/RadialBarChart/RadialBarChart.tsx', + docs: 'charts/radial-bar-chart.mdx', + }, }; diff --git a/apps/mantine.dev/src/mdx/data/mdx-core-data.ts b/apps/mantine.dev/src/mdx/data/mdx-core-data.ts index 9110e2ae0a1..53023fa46ad 100644 --- a/apps/mantine.dev/src/mdx/data/mdx-core-data.ts +++ b/apps/mantine.dev/src/mdx/data/mdx-core-data.ts @@ -1175,4 +1175,16 @@ export const MDX_CORE_DATA: Record = { source: '@mantine/core/src/components/SemiCircleProgress/SemiCircleProgress.tsx', docs: 'core/semi-circle-progress.mdx', }, + + AngleSlider: { + title: 'AngleSlider', + package: '@mantine/core', + slug: '/core/angle-slider', + props: ['AngleSlider'], + styles: ['AngleSlider'], + description: 'Pick angle value between 0 and 360', + import: "import { AngleSlider } from '@mantine/core';", + source: '@mantine/core/src/components/AngleSlider/AngleSlider.tsx', + docs: 'core/angle-slider.mdx', + }, }; diff --git a/apps/mantine.dev/src/mdx/data/mdx-meta-data.ts b/apps/mantine.dev/src/mdx/data/mdx-meta-data.ts index f94b564733a..949f5eedaa0 100644 --- a/apps/mantine.dev/src/mdx/data/mdx-meta-data.ts +++ b/apps/mantine.dev/src/mdx/data/mdx-meta-data.ts @@ -62,6 +62,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-0-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.0.0', date: 'September 18th, 2023', + searchTags: '7-0-0, 700', }, Changelog710: { @@ -69,6 +70,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-1-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.1.0', date: 'September 28th, 2023', + searchTags: '7-1-0, 710', }, Changelog720: { @@ -76,6 +78,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-2-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.2.0', date: 'November 7th, 2023', + searchTags: '7-2-0, 720', }, Changelog730: { @@ -83,6 +86,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-3-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.3.0', date: 'December 5th, 2023', + searchTags: '7-3-0, 730', }, Changelog740: { @@ -90,6 +94,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-4-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.4.0', date: 'January 3rd, 2024', + searchTags: '7-4-0, 740', }, Changelog750: { @@ -97,6 +102,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-5-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.5.0', date: 'January 26th, 2024', + searchTags: '7-5-0, 750', }, Changelog760: { @@ -104,6 +110,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-6-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.6.0', date: 'February 27th, 2024', + searchTags: '7-6-0, 760', }, Changelog770: { @@ -111,6 +118,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-7-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.7.0', date: 'March 26th, 2024', + searchTags: '7-7-0, 770', }, Changelog780: { @@ -118,6 +126,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-8-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.8.0', date: 'April 12th, 2024', + searchTags: '7-8-0, 780', }, Changelog790: { @@ -125,6 +134,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-9-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.9.0', date: 'May 2nd, 2024', + searchTags: '7-9-0, 790', }, Changelog7100: { @@ -132,6 +142,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-10-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.10.0', date: 'May 23rd, 2024', + searchTags: '7-10-0, 7100', }, Changelog7110: { @@ -139,6 +150,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-11-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.11.0', date: 'June 26th, 2024', + searchTags: '7-11-0, 7110', }, Changelog7120: { @@ -146,6 +158,7 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-12-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.12.0', date: 'August 6th, 2024', + searchTags: '7-12-0, 7120', }, Changelog7130: { @@ -153,6 +166,15 @@ export const MDX_META_DATA: Record = { slug: '/changelog/7-13-0', release: 'https://github.com/mantinedev/mantine/releases/tag/7.13.0', date: 'September 26th, 2024', + searchTags: '7-13-0, 7130', + }, + + Changelog7140: { + title: 'Version v7.14.0', + slug: '/changelog/7-14-0', + release: 'https://github.com/mantinedev/mantine/releases/tag/7.14.0', + date: 'November 12th, 2024', + searchTags: '7-14-0, 7140', }, PreviousChangelogs: { diff --git a/apps/mantine.dev/src/mdx/mdx-pages-group.ts b/apps/mantine.dev/src/mdx/mdx-pages-group.ts index 477bd0830fe..28c8500f4e3 100644 --- a/apps/mantine.dev/src/mdx/mdx-pages-group.ts +++ b/apps/mantine.dev/src/mdx/mdx-pages-group.ts @@ -215,6 +215,7 @@ export const MDX_PAGES_GROUPS: MdxPagesGroup[] = [ MDX_DATA.FileInput, MDX_DATA.ColorInput, MDX_DATA.NumberInput, + MDX_DATA.AngleSlider, ], }, { @@ -356,9 +357,11 @@ export const MDX_PAGES_GROUPS: MdxPagesGroup[] = [ MDX_DATA.CompositeChart, MDX_DATA.DonutChart, MDX_DATA.PieChart, + MDX_DATA.FunnelChart, MDX_DATA.RadarChart, MDX_DATA.ScatterChart, MDX_DATA.BubbleChart, + MDX_DATA.RadialBarChart, MDX_DATA.Sparkline, ], }, @@ -378,6 +381,7 @@ export const MDX_PAGES_GROUPS: MdxPagesGroup[] = [ { group: 'changelog', pages: [ + MDX_DATA.Changelog7140, MDX_DATA.Changelog7130, MDX_DATA.Changelog7120, MDX_DATA.Changelog7110, diff --git a/apps/mantine.dev/src/pages/changelog/7-14-0.mdx b/apps/mantine.dev/src/pages/changelog/7-14-0.mdx new file mode 100644 index 00000000000..3830dc98cb3 --- /dev/null +++ b/apps/mantine.dev/src/pages/changelog/7-14-0.mdx @@ -0,0 +1,126 @@ +import { + AngleSliderDemos, + BarChartDemos, + FunnelChartDemos, + ModalDemos, + RadialBarChartDemos, + SliderDemos, +} from '@docs/demos'; +import { Layout } from '@/layout'; +import { MDX_DATA } from '@/mdx'; + +export default Layout(MDX_DATA.Changelog7140); + +## AngleSlider component + +New [AngleSlider](/core/angle-slider/) component: + + + +## RadialBarChart component + +New [RadialBarChart](/charts/radial-bar-chart/) component: + + + +## FunnelChart component + +New [FunnelChart](/charts/funnel-chart/) component: + + + +## Modal.Stack and Drawer.Stack components + +New [Modal.Stack](/core/modal/) and [Drawer.Stack](/core/drawer) components simplify usage of multiple modals/drawers at the same time. + +Use `Modal.Stack` component to render multiple modals at the same time. +`Modal.Stack` keeps track of opened modals, manages z-index values, focus trapping +and `closeOnEscape` behavior. `Modal.Stack` is designed to be used with `useModalsStack` hook. + +Differences from using multiple `Modal` components: + +- `Modal.Stack` manages z-index values – modals that are opened later will always have higher z-index value disregarding their order in the DOM +- `Modal.Stack` disables focus trap and `Escape` key handling for all modals except the one that is currently opened +- Modals that are not currently opened are present in the DOM but are hidden with `opacity: 0` and `pointer-events: none` +- Only one overlay is rendered at a time + + + +## useModalsStack/useDrawersStack hooks + +`useModalsStack` hook provides an easy way to control multiple modals at the same time. +It accepts an array of unique modals ids and returns an object with the following properties: + +```tsx +interface ModalStackReturnType { + // Current opened state of each modal + state: Record; + + // Opens modal with the given id + open: (id: T) => void; + + // Closes modal with the given id + close: (id: T) => void; + + // Toggles modal with the given id + toggle: (id: T) => void; + + // Closes all modals within the stack + closeAll: () => void; + + // Returns props for modal with the given id + register: (id: T) => { + opened: boolean; + onClose: () => void; + stackId: T; + }; +} +``` + +Example of using `useModalsStack` with `Modal` component: + +```tsx +import { Modal, useModalsStack } from '@mantine/core'; + +function Demo() { + const stack = useModalsStack(['first', 'second']); + + return ( + <> + First + Second + + + ); +} +``` + +## Restrict Slider selection to marks + +[Slider](/core/slider) component now supports `restrictToMarks` prop that restricts slider value to marks only. +Note that in this case `step` prop is ignored: + + + +## BarChart SVG pattern fill + +[BarChart](/charts/bar-chart) now can be used with SVG pattern fill: + + + +## Help center updates + +- New [Can I use nested inline styles with Mantine components?](https://help.mantine.dev/q/nested-inline-styles) question +- New [Can I use PostCSS function in inline styles?](https://help.mantine.dev/q/postcss-fns-inline) question +- New [Why my Carousel slides are in vertical orientation?](https://help.mantine.dev/q/carousel-missing-styles) question +- New [My buttons are transparent and the background is visible only on hover, what is wrong?](https://help.mantine.dev/q/transparent-buttons) question +- New [Can I have different primary color for light and dark color schemes?](https://help.mantine.dev/q/primary-virtual-color) question +- New [How can I change body background color?](https://help.mantine.dev/q/body-background) question +- New [My Popover dropdown closes when I click on the dropdown of nested Popover](https://help.mantine.dev/q/nested-popover-closes) question + +## Other changes + +- [useTree](/core/tree/) hook now accepts `onNodeExpand` and `onNodeCollapse` callbacks +- [useTree](/core/tree/) hook now returns additional `checkAllNodes`, `uncheckAllNodes` and `setCheckedState` handlers +- [Tree](/core/tree) component now includes `getTreeExpandedState` to generate expanded state based on the tree data +- [use-form](/form/use-form) now supports `form.replaceListItem` handler to replace list item at given index diff --git a/apps/mantine.dev/src/pages/charts/bar-chart.mdx b/apps/mantine.dev/src/pages/charts/bar-chart.mdx index b413095da6c..43b75611bb1 100644 --- a/apps/mantine.dev/src/pages/charts/bar-chart.mdx +++ b/apps/mantine.dev/src/pages/charts/bar-chart.mdx @@ -44,6 +44,15 @@ Use the `standalone` prop inside data to decouple the bar from the flow. +## SVG patter as bar fill + +You can use SVG patterns as bar fill. To do so, set `fill` property in series object to +a url of the SVG pattern that is defined in the `defs` section of the chart `children`. + +Example of using diagonal stripes and crosshatch patterns as bar fill: + + + ## Legend To display chart legend, set `withLegend` prop. When one of the items in the legend diff --git a/apps/mantine.dev/src/pages/charts/funnel-chart.mdx b/apps/mantine.dev/src/pages/charts/funnel-chart.mdx new file mode 100644 index 00000000000..f40cf3cfe06 --- /dev/null +++ b/apps/mantine.dev/src/pages/charts/funnel-chart.mdx @@ -0,0 +1,70 @@ +import { FunnelChartDemos } from '@docs/demos'; +import { Layout } from '@/layout'; +import { MDX_DATA } from '@/mdx'; + +export default Layout(MDX_DATA.FunnelChart); + +## Usage + +`FunnelChart` is based on [FunnelChart recharts component](https://recharts.org/en-US/api/FunnelChart): + + + +## Segments labels + +Set `withLabels` prop to display labels next to each segment. +Use `labelPosition` prop to control the position of labels relative to the corresponding segment. + + + +## Size and thickness + +Set `size` prop to control width and height of the chart. +You can override this behavior by setting `h` [style prop](/styles/style-props). + + + +## Segment color + +You can reference colors from [theme](/theming/theme-object) the same way as in +other components, for example, `blue`, `red.5`, `orange.7`, etc. Any valid CSS +color value is also accepted. + + + +## Tooltip data source + +By default, the tooltip displays data for all segments when hovered over any segment. +To display data only for the hovered segment, set `tooltipDataSource="segment"`: + + + +## Without tooltip + +To remove the tooltip, set `withTooltip={false}`: + + + +## Segments stroke + +Use `strokeWidth` prop to control the width of the stroke around each segment: + + + +To change color of the stroke, use `strokeColor` prop. You can reference colors from [theme](/theming/theme-object) the same way as in +other components, for example, `blue`, `red.5`, `orange.7`, etc. Any valid CSS +color value is also accepted. + +```tsx +import { FunnelChart } from '@mantine/charts'; + +function Demo() { + return ; +} +``` + +By default, segments stroke color is the same as the background color of the body element +(`--mantine-color-body` CSS variable). If you want to change it depending on the color scheme, +define CSS variable and pass it to the `strokeColor` prop: + + diff --git a/apps/mantine.dev/src/pages/charts/radial-bar-chart.mdx b/apps/mantine.dev/src/pages/charts/radial-bar-chart.mdx new file mode 100644 index 00000000000..324abe62563 --- /dev/null +++ b/apps/mantine.dev/src/pages/charts/radial-bar-chart.mdx @@ -0,0 +1,35 @@ +import { RadialBarChartDemos } from '@docs/demos'; +import { Layout } from '@/layout'; +import { MDX_DATA } from '@/mdx'; + +export default Layout(MDX_DATA.RadialBarChart); + +## Usage + +`RadialBarChart` is based on [RadialBarChart recharts component](https://recharts.org/en-US/api/RadialBarChart): + + + +## Change color + +You can reference theme colors or use any valid CSS color in `color` property of `data`: + + + +## Legend + +To show legend, set `withLegend` prop: + + + +## Labels + +To show labels, set `withLabels` prop: + + + +## Hide tooltip + +To hide tooltip, set `withTooltip={false}` prop: + + diff --git a/apps/mantine.dev/src/pages/core/angle-slider.mdx b/apps/mantine.dev/src/pages/core/angle-slider.mdx new file mode 100644 index 00000000000..cea77751a04 --- /dev/null +++ b/apps/mantine.dev/src/pages/core/angle-slider.mdx @@ -0,0 +1,62 @@ +import { AngleSliderDemos } from '@docs/demos'; +import { Layout } from '@/layout'; +import { MDX_DATA } from '@/mdx'; + +export default Layout(MDX_DATA.AngleSlider); + +## Usage + +Use `AngleSlider` component to pick angle value between 0 and 360: + + + +## Controlled + +```tsx +import { useState } from 'react'; +import { AngleSlider } from '@mantine/core'; + +function Demo() { + const [value, setValue] = useState(180); + return ; +} +``` + +## formatLabel + +To change angle label format use `formatLabel` prop. +It accepts function that takes the angle value and returns React node: + + + +## Marks + +Set `marks` prop to display marks on the slider. In mark object `value` is required, +`label` is optional. To restrict selection to marks only, set `restrictToMarks` prop: + + + +## onChangeEnd + +`onChangeEnd` callback is called when user the slider is stopped from being dragged or value is changed with keyboard. +You can use it as a debounced callback to avoid too frequent updates. + + + +## Accessibility + +`AngleSlider` is fully accessible and supports keyboard interactions: + +- `ArrowLeft`/`ArrowDown` and `ArrowRight`/`Arrowup` decrease/increase value by `step` +- `Home` to set value to 0 +- `End` to set value to 359 + +To make the component visible to screen readers, set `aria-label` prop: + +```tsx +import { AngleSlider } from '@mantine/core'; + +function Demo() { + return ; +} +``` diff --git a/apps/mantine.dev/src/pages/core/drawer.mdx b/apps/mantine.dev/src/pages/core/drawer.mdx index 9eff0aef302..5a177d40c2e 100644 --- a/apps/mantine.dev/src/pages/core/drawer.mdx +++ b/apps/mantine.dev/src/pages/core/drawer.mdx @@ -136,6 +136,70 @@ You can use the following compound components to have full control over the `Dra +## Drawer.Stack + +Use `Drawer.Stack` component to render multiple drawers at the same time. +`Drawer.Stack` keeps track of opened drawers, manages z-index values, focus trapping +and `closeOnEscape` behavior. `Drawer.Stack` is designed to be used with `useDrawersStack` hook. + +Differences from using multiple `Drawer` components: + +- `Drawer.Stack` manages z-index values – drawers that are opened later will always have higher z-index value disregarding their order in the DOM +- `Drawer.Stack` disables focus trap and `Escape` key handling for all drawers except the one that is currently opened +- Drawers that are not currently opened are present in the DOM but are hidden with `opacity: 0` and `pointer-events: none` +- Only one overlay is rendered at a time + + + +## useDrawersStack hook + +`useDrawersStack` hook provides an easy way to control multiple drawers at the same time. +It accepts an array of unique drawers ids and returns an object with the following properties: + +```tsx +interface UseDrawersStackReturnType { + // Current opened state of each drawer + state: Record; + + // Opens drawer with the given id + open: (id: T) => void; + + // Closes drawer with the given id + close: (id: T) => void; + + // Toggles drawer with the given id + toggle: (id: T) => void; + + // Closes all drawers within the stack + closeAll: () => void; + + // Returns props for drawer with the given id + register: (id: T) => { + opened: boolean; + onClose: () => void; + stackId: T; + }; +} +``` + +Example of using `useDrawersStack` with `Drawer` component: + +```tsx +import { Drawer, useDrawersStack } from '@mantine/core'; + +function Demo() { + const stack = useDrawersStack(['first', 'second']); + + return ( + <> + First + Second + + + ); +} +``` + ## Fixed elements offset `Drawer` component uses [react-remove-scroll](https://github.com/theKashey/react-remove-scroll) @@ -180,7 +244,7 @@ import { Drawer } from '@mantine/core'; function Demo() { return ( {}} /> diff --git a/apps/mantine.dev/src/pages/core/modal.mdx b/apps/mantine.dev/src/pages/core/modal.mdx index 454e5d6e3b9..6f76e8a2e10 100644 --- a/apps/mantine.dev/src/pages/core/modal.mdx +++ b/apps/mantine.dev/src/pages/core/modal.mdx @@ -137,6 +137,70 @@ You can use the following compound components to have full control over the `Mod +## Modal.Stack + +Use `Modal.Stack` component to render multiple modals at the same time. +`Modal.Stack` keeps track of opened modals, manages z-index values, focus trapping +and `closeOnEscape` behavior. `Modal.Stack` is designed to be used with `useModalsStack` hook. + +Differences from using multiple `Modal` components: + +- `Modal.Stack` manages z-index values – modals that are opened later will always have higher z-index value disregarding their order in the DOM +- `Modal.Stack` disables focus trap and `Escape` key handling for all modals except the one that is currently opened +- Modals that are not currently opened are present in the DOM but are hidden with `opacity: 0` and `pointer-events: none` +- Only one overlay is rendered at a time + + + +## useModalsStack hook + +`useModalsStack` hook provides an easy way to control multiple modals at the same time. +It accepts an array of unique modals ids and returns an object with the following properties: + +```tsx +interface UseModalsStackReturnType { + // Current opened state of each modal + state: Record; + + // Opens modal with the given id + open: (id: T) => void; + + // Closes modal with the given id + close: (id: T) => void; + + // Toggles modal with the given id + toggle: (id: T) => void; + + // Closes all modals within the stack + closeAll: () => void; + + // Returns props for modal with the given id + register: (id: T) => { + opened: boolean; + onClose: () => void; + stackId: T; + }; +} +``` + +Example of using `useModalsStack` with `Modal` component: + +```tsx +import { Modal, useModalsStack } from '@mantine/core'; + +function Demo() { + const stack = useModalsStack(['first', 'second']); + + return ( + <> + First + Second + + + ); +} +``` + ## Fixed elements offset `Modal` component uses [react-remove-scroll](https://github.com/theKashey/react-remove-scroll) diff --git a/apps/mantine.dev/src/pages/core/slider.mdx b/apps/mantine.dev/src/pages/core/slider.mdx index a0955a03537..3d2e5defc79 100644 --- a/apps/mantine.dev/src/pages/core/slider.mdx +++ b/apps/mantine.dev/src/pages/core/slider.mdx @@ -88,6 +88,13 @@ Note that mark value is relative to slider value, not width: +## Restrict selection to marks + +Set `restrictToMarks` prop to restrict slider value to marks only. Note that in +this case `step` prop is ignored: + + + ## Thumb size diff --git a/apps/mantine.dev/src/pages/core/tree.mdx b/apps/mantine.dev/src/pages/core/tree.mdx index cb02c6848e9..245e71c0fc9 100644 --- a/apps/mantine.dev/src/pages/core/tree.mdx +++ b/apps/mantine.dev/src/pages/core/tree.mdx @@ -120,7 +120,7 @@ The hook accepts an object with the following properties: ```tsx export interface UseTreeInput { /** Initial expanded state of all nodes */ - initialExpandedState?: TreeExpandedState; + initialExpandedState?: Record; /** Initial selected state of nodes */ initialSelectedState?: string[]; @@ -130,6 +130,12 @@ export interface UseTreeInput { /** Determines whether multiple node can be selected at a time */ multiple?: boolean; + + /** Called with the node value when it is expanded */ + onNodeExpand?: (value: string) => void; + + /** Called with the node value when it is collapsed */ + onNodeCollapse?: (value: string) => void; } ``` @@ -204,6 +210,15 @@ export interface UseTreeReturnType { /** Unchecks node with provided value */ uncheckNode: (value: string) => void; + /** Checks all nodes */ + checkAllNodes: () => void; + + /** Unchecks all nodes */ + uncheckAllNodes: () => void; + + /** Sets checked state */ + setCheckedState: React.Dispatch>; + /** Returns all checked nodes with status */ getCheckedNodes: () => CheckedNodeStatus[]; @@ -227,6 +242,31 @@ To implement checked state, you need to render `Checkbox.Indicator` in the `rend +To check/uncheck nodes, use `checkAllNodes` and `uncheckAllNodes` functions: + + + +## Initial expanded state + +Expanded state is an object of `node.value` and boolean values that represent nodes expanded state. +To change initial expanded state, pass `initialExpandedState` to the `useTree` hook. +To generate expanded state from data with expanded nodes, you can use `getTreeExpandedState` function: +it accepts data and an array of expanded nodes values and returns expanded state object. + +If `'*'` is passed as the second argument to `getTreeExpandedState`, all nodes will be expanded: + +```tsx +import { getTreeExpandedState } from '@mantine/core'; + +// Expand two given nodes +getTreeExpandedState(data, ['src', 'src/components']); + +// Expand all nodes +getTreeExpandedState(data, '*'); +``` + + + ## Example: files tree diff --git a/apps/mantine.dev/src/pages/form/nested.mdx b/apps/mantine.dev/src/pages/form/nested.mdx index 8058c2a68ad..8bd6a329c3f 100644 --- a/apps/mantine.dev/src/pages/form/nested.mdx +++ b/apps/mantine.dev/src/pages/form/nested.mdx @@ -109,6 +109,7 @@ form.errors; // -> { 'user.name': 'Name is too short', 'user.occupation': 'Occup - `removeListItem` – removes list item at given index - `insertListItem` – inserts list item at given index (appends item to the end of the list if index is not specified) - `reorderListItem` – reorders list item with given position at specified field +- `replaceListItem` – replaces list item at given index with new value ## List values validation diff --git a/apps/mantine.dev/src/pages/form/use-form.mdx b/apps/mantine.dev/src/pages/form/use-form.mdx index ca2acf3f1df..3873d267960 100644 --- a/apps/mantine.dev/src/pages/form/use-form.mdx +++ b/apps/mantine.dev/src/pages/form/use-form.mdx @@ -84,6 +84,9 @@ form.insertListItem('fruits', { name: 'Orange', available: true }, 1); // Removes the list item at the specified path and index. form.removeListItem('fruits', 1); +// Replaces the list item at the specified path and index with the given item. +form.replaceListItem('fruits', 1, { name: 'Apple', available: true }); + // Swaps two items of the list at the specified path. // You should make sure that there are elements at at the `from` and `to` index. form.reorderListItem('fruits', { from: 1, to: 0 }); diff --git a/changelog/7.14.0.md b/changelog/7.14.0.md new file mode 100644 index 00000000000..964dfaadae0 --- /dev/null +++ b/changelog/7.14.0.md @@ -0,0 +1,304 @@ +[View changelog with demos on mantine.dev website](https://mantine.dev/changelog/7-14-0) + +## AngleSlider component + +New [AngleSlider](https://mantine.dev/core/angle-slider/) component: + +```tsx +import { AngleSlider, Group } from '@mantine/core'; + +function Demo() { + return ( + + `${value}°`} + size={100} + restrictToMarks + marks={[ + { value: 0 }, + { value: 45 }, + { value: 90 }, + { value: 135 }, + { value: 180 }, + { value: 225 }, + { value: 270 }, + { value: 315 }, + ]} + /> + + `${value}°`} + size={100} + marks={[ + { value: 0, label: '0°' }, + { value: 45, label: '45°' }, + { value: 90, label: '90°' }, + { value: 135, label: '135°' }, + { value: 180, label: '180°' }, + { value: 225, label: '225°' }, + { value: 270, label: '270°' }, + { value: 315, label: '315°' }, + ]} + /> + + ); +} +``` + +## RadialBarChart component + +New [RadialBarChart](https://mantine.dev/charts/radial-bar-chart/) component: + +```tsx +import { RadialBarChart } from '@mantine/charts'; + +const data = [ + { name: '18-24', value: 31.47, color: 'blue.7' }, + { name: '25-29', value: 26.69, color: 'orange.6' }, + { name: '30-34', value: 15.69, color: 'yellow.7' }, + { name: '35-39', value: 8.22, color: 'cyan.6' }, + { name: '40-49', value: 8.63, color: 'green' }, + { name: '50+', value: 2.63, color: 'pink' }, + { name: 'unknown', value: 6.67, color: 'gray' }, +]; + +function Demo() { + return ; +} +``` + +## FunnelChart component + +New [FunnelChart](https://mantine.dev/charts/funnel-chart/) component: + +```tsx +import { FunnelChart } from '@mantine/charts'; + +const data = [ + { name: 'USA', value: 400, color: 'indigo.6' }, + { name: 'India', value: 300, color: 'yellow.6' }, + { name: 'Japan', value: 100, color: 'teal.6' }, + { name: 'Other', value: 200, color: 'gray.6' }, +]; + +function Demo() { + return ; +} +``` + +## Modal.Stack and Drawer.Stack components + +New [Modal.Stack](https://mantine.dev/core/modal/) and [Drawer.Stack](https://mantine.dev/core/drawer) components simplify usage of multiple modals/drawers at the same time. + +Use `Modal.Stack` component to render multiple modals at the same time. +`Modal.Stack` keeps track of opened modals, manages z-index values, focus trapping +and `closeOnEscape` behavior. `Modal.Stack` is designed to be used with `useModalsStack` hook. + +Differences from using multiple `Modal` components: + +- `Modal.Stack` manages z-index values – modals that are opened later will always have higher z-index value disregarding their order in the DOM +- `Modal.Stack` disables focus trap and `Escape` key handling for all modals except the one that is currently opened +- Modals that are not currently opened are present in the DOM but are hidden with `opacity: 0` and `pointer-events: none` +- Only one overlay is rendered at a time + +```tsx +import { Button, Group, Modal, useModalsStack } from '@mantine/core'; + +function Demo() { + const stack = useModalsStack(['delete-page', 'confirm-action', 'really-confirm-action']); + + return ( + <> + + + Are you sure you want to delete this page? This action cannot be undone. + + + + + + + + Are you sure you want to perform this action? This action cannot be undone. If you are + sure, press confirm button below. + + + + + + + + Jokes aside. You have confirmed this action. This is your last chance to cancel it. After + you press confirm button below, action will be performed and cannot be undone. For real + this time. Are you sure you want to proceed? + + + + + + + + + + ); +} +``` + +## useModalsStack/useDrawersStack hooks + +`useModalsStack` hook provides an easy way to control multiple modals at the same time. +It accepts an array of unique modals ids and returns an object with the following properties: + +```tsx +interface ModalStackReturnType { + // Current opened state of each modal + state: Record; + + // Opens modal with the given id + open: (id: T) => void; + + // Closes modal with the given id + close: (id: T) => void; + + // Toggles modal with the given id + toggle: (id: T) => void; + + // Closes all modals within the stack + closeAll: () => void; + + // Returns props for modal with the given id + register: (id: T) => { + opened: boolean; + onClose: () => void; + stackId: T; + }; +} +``` + +Example of using `useModalsStack` with `Modal` component: + +```tsx +import { Modal, useModalsStack } from '@mantine/core'; + +function Demo() { + const stack = useModalsStack(['first', 'second']); + + return ( + <> + First + Second + + + ); +} +``` + +## Restrict Slider selection to marks + +[Slider](https://mantine.dev/core/slider) component now supports `restrictToMarks` prop that restricts slider value to marks only. +Note that in this case `step` prop is ignored: + +```tsx +import { Slider } from '@mantine/core'; + +function Demo() { + return ( + ({ value: index * 25 }))} + /> + ); +} +``` + +## BarChart SVG pattern fill + +[BarChart](https://mantine.dev/charts/bar-chart) now can be used with SVG pattern fill: + +```tsx +import { BarChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ( + + + + + + + + + + + + + + ); +} +``` + +## Help center updates + +- New [Can I use nested inline styles with Mantine components?](https://help.mantine.dev/q/nested-inline-styles) question +- New [Can I use PostCSS function in inline styles?](https://help.mantine.dev/q/postcss-fns-inline) question +- New [Why my Carousel slides are in vertical orientation?](https://help.mantine.dev/q/carousel-missing-styles) question +- New [My buttons are transparent and the background is visible only on hover, what is wrong?](https://help.mantine.dev/q/transparent-buttons) question +- New [Can I have different primary color for light and dark color schemes?](https://help.mantine.dev/q/primary-virtual-color) question +- New [How can I change body background color?](https://help.mantine.dev/q/body-background) question +- New [My Popover dropdown closes when I click on the dropdown of nested Popover](https://help.mantine.dev/q/nested-popover-closes) question + +## Other changes + +- [useTree](https://mantine.dev/core/tree/) hook now accepts `onNodeExpand` and `onNodeCollapse` callbacks +- [useTree](https://mantine.dev/core/tree/) hook now returns additional `checkAllNodes`, `uncheckAllNodes` and `setCheckedState` handlers +- [Tree](https://mantine.dev/core/tree) component now includes `getTreeExpandedState` to generate expanded state based on the tree data +- [use-form](https://mantine.dev/form/use-form) now supports `form.replaceListItem` handler to replace list item at given index diff --git a/changelog/7.6.0.md b/changelog/7.6.0.md index fea8e525341..57790dd71b1 100644 --- a/changelog/7.6.0.md +++ b/changelog/7.6.0.md @@ -351,7 +351,24 @@ with `barProps`, `areaProps` and `lineProps` props on [BarChart](https://mantine All props accepts either an object with props or a function that receives series data as an argument and returns an object with props. - +```tsx +import { BarChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ( + + ); +} +``` ## PieChart percent labels diff --git a/eslint.config.js b/eslint.config.js index 2087153c8f1..9f06023fb99 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -20,6 +20,7 @@ module.exports = tseslint.config( 'jest/no-export': 'off', 'jest/expect-expect': 'off', 'jest/valid-title': 'off', + '@typescript-eslint/no-namespace': 'off', }, } ); diff --git a/package.json b/package.json index 892605042e1..ebaf71065ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mantine-a91763c0e2", - "version": "7.13.5", + "version": "7.14.0", "description": "Mantine Components Monorepo", "packageManager": "yarn@4.5.1", "license": "MIT", diff --git a/packages/@docs/demos/src/demos/charts/BarChart/BarChart.demo.stripes.tsx b/packages/@docs/demos/src/demos/charts/BarChart/BarChart.demo.stripes.tsx new file mode 100644 index 00000000000..55524bd4355 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/BarChart/BarChart.demo.stripes.tsx @@ -0,0 +1,119 @@ +import { BarChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; +import { mixedStackData, mixedStackDataCode } from './_data'; + +const code = ` +import { BarChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ( + + + + + + + + + + + + + + ); +} +`; + +function Demo() { + return ( + + + + + + + + + + + + + + ); +} + +export const stripes: MantineDemo = { + type: 'code', + component: Demo, + code: [ + { code, language: 'tsx', fileName: 'Demo.tsx' }, + { code: mixedStackDataCode, language: 'tsx', fileName: 'data.ts' }, + ], +}; diff --git a/packages/@docs/demos/src/demos/charts/BarChart/BarChart.demos.story.tsx b/packages/@docs/demos/src/demos/charts/BarChart/BarChart.demos.story.tsx index 363cc63b4b7..282c46773e3 100644 --- a/packages/@docs/demos/src/demos/charts/BarChart/BarChart.demos.story.tsx +++ b/packages/@docs/demos/src/demos/charts/BarChart/BarChart.demos.story.tsx @@ -137,3 +137,8 @@ export const Demo_mixedStack = { name: '⭐ Demo: mixedStack', render: renderDemo(demos.mixedStack), }; + +export const Demo_stripes = { + name: '⭐ Demo: stripes', + render: renderDemo(demos.stripes), +}; diff --git a/packages/@docs/demos/src/demos/charts/BarChart/index.ts b/packages/@docs/demos/src/demos/charts/BarChart/index.ts index c5e6af65f55..dc1b258fc3b 100644 --- a/packages/@docs/demos/src/demos/charts/BarChart/index.ts +++ b/packages/@docs/demos/src/demos/charts/BarChart/index.ts @@ -25,3 +25,4 @@ export { axisLabels } from './BarChart.demo.axisLabels'; export { barValueLabel } from './BarChart.demo.barValueLabel'; export { minBarSize } from './BarChart.demo.minBarSize'; export { mixedStack } from './BarChart.demo.mixedStack'; +export { stripes } from './BarChart.demo.stripes'; diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.color.tsx b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.color.tsx new file mode 100644 index 00000000000..9e132d08c41 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.color.tsx @@ -0,0 +1,37 @@ +import { FunnelChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; + +const code = (props: any) => ` +import { FunnelChart } from '@mantine/charts'; + +function Demo() { + return ( + + ); +} +`; + +function Wrapper(props: any) { + return ( + + ); +} + +export const color: MantineDemo = { + type: 'configurator', + component: Wrapper, + code, + centered: true, + controls: [{ type: 'color', prop: 'color', initialValue: 'blue', libraryValue: '__' }], +}; diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.noTooltip.tsx b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.noTooltip.tsx new file mode 100644 index 00000000000..8831fd2d95d --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.noTooltip.tsx @@ -0,0 +1,26 @@ +import { FunnelChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './_data'; + +const code = ` +import { FunnelChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ; +} +`; + +function Demo() { + return ; +} + +export const noTooltip: MantineDemo = { + type: 'code', + component: Demo, + code: [ + { code, language: 'tsx', fileName: 'Demo.tsx' }, + { code: dataCode, language: 'tsx', fileName: 'data.ts' }, + ], + centered: true, +}; diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.size.tsx b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.size.tsx new file mode 100644 index 00000000000..5f50c76b0d1 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.size.tsx @@ -0,0 +1,42 @@ +import { FunnelChart } from '@mantine/charts'; +import { Center } from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './_data'; + +const code = ` +import { FunnelChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ; +} +`; + +function Wrapper(props: any) { + return ( +
+ +
+ ); +} + +export const size: MantineDemo = { + type: 'configurator', + component: Wrapper, + code: [ + { fileName: 'Demo.tsx', code, language: 'tsx' }, + { fileName: 'data.ts', code: dataCode, language: 'tsx' }, + ], + centered: true, + controls: [ + { + type: 'number', + prop: 'size', + initialValue: 160, + min: 80, + max: 300, + step: 1, + libraryValue: '__', + }, + ], +}; diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.story.tsx b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.story.tsx new file mode 100644 index 00000000000..e071d4447e6 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.story.tsx @@ -0,0 +1,44 @@ +import { renderDemo } from '../../../render-demo'; +import * as demos from './index'; + +export default { title: 'FunnelChart' }; + +export const Demo_usage = { + name: '⭐ Demo: usage', + render: renderDemo(demos.usage), +}; + +export const Demo_withLabels = { + name: '⭐ Demo: withLabels', + render: renderDemo(demos.withLabels), +}; + +export const Demo_size = { + name: '⭐ Demo: size', + render: renderDemo(demos.size), +}; + +export const Demo_color = { + name: '⭐ Demo: color', + render: renderDemo(demos.color), +}; + +export const Demo_tooltipDataSource = { + name: '⭐ Demo: tooltipDataSource', + render: renderDemo(demos.tooltipDataSource), +}; + +export const Demo_strokeWidth = { + name: '⭐ Demo: strokeWidth', + render: renderDemo(demos.strokeWidth), +}; + +export const Demo_noTooltip = { + name: '⭐ Demo: noTooltip', + render: renderDemo(demos.noTooltip), +}; + +export const Demo_strokeColor = { + name: '⭐ Demo: strokeColor', + render: renderDemo(demos.strokeColor), +}; diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.strokeColor.module.css b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.strokeColor.module.css new file mode 100644 index 00000000000..f638ee5e5f9 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.strokeColor.module.css @@ -0,0 +1,7 @@ +.root { + --card-bg: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5)); + + background-color: var(--card-bg); + padding: var(--mantine-spacing-md); + border-radius: var(--mantine-radius-md); +} diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.strokeColor.tsx b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.strokeColor.tsx new file mode 100644 index 00000000000..57093f8b651 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.strokeColor.tsx @@ -0,0 +1,48 @@ +import { FunnelChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './_data'; +import classes from './FunnelChart.demo.strokeColor.module.css'; + +const cssCode = ` +.root { + --card-bg: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5)); + + background-color: var(--card-bg); + padding: var(--mantine-spacing-md); + border-radius: var(--mantine-radius-md); +} +`; + +const code = ` +import { FunnelChart } from '@mantine/charts'; +import { data } from './data'; +import classes from './Demo.module.css'; + +function Demo() { + return ( +
+ +
+ ); +} + +`; + +function Demo() { + return ( +
+ +
+ ); +} + +export const strokeColor: MantineDemo = { + type: 'code', + component: Demo, + code: [ + { code, language: 'tsx', fileName: 'Demo.tsx' }, + { code: cssCode, language: 'scss', fileName: 'Demo.module.css' }, + { code: dataCode, language: 'tsx', fileName: 'data.ts' }, + ], + centered: true, +}; diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.strokeWidth.tsx b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.strokeWidth.tsx new file mode 100644 index 00000000000..942b168c2d2 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.strokeWidth.tsx @@ -0,0 +1,37 @@ +import { FunnelChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './_data'; + +const code = ` +import { FunnelChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ; +} +`; + +function Wrapper(props: any) { + return ; +} + +export const strokeWidth: MantineDemo = { + type: 'configurator', + component: Wrapper, + code: [ + { fileName: 'Demo.tsx', code, language: 'tsx' }, + { fileName: 'data.ts', code: dataCode, language: 'tsx' }, + ], + centered: true, + controls: [ + { + type: 'number', + prop: 'strokeWidth', + initialValue: 1, + min: 0, + max: 5, + step: 0.1, + libraryValue: '__', + }, + ], +}; diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.tooltipDataSource.tsx b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.tooltipDataSource.tsx new file mode 100644 index 00000000000..6bbc638710a --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.tooltipDataSource.tsx @@ -0,0 +1,60 @@ +import { FunnelChart } from '@mantine/charts'; +import { Group, Text } from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './_data'; + +const code = ` +import { Group, Text } from '@mantine/core'; +import { FunnelChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ( + +
+ + Data only for hovered segment + + +
+ +
+ + Data only for all segments + + +
+
+ ); +} +`; + +function Demo() { + return ( + +
+ + Data only for hovered segment + + +
+ +
+ + Data only for all segments + + +
+
+ ); +} + +export const tooltipDataSource: MantineDemo = { + type: 'code', + component: Demo, + code: [ + { code, language: 'tsx', fileName: 'Demo.tsx' }, + { code: dataCode, language: 'tsx', fileName: 'data.ts' }, + ], + centered: true, +}; diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.usage.tsx b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.usage.tsx new file mode 100644 index 00000000000..643bf10cb7b --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.usage.tsx @@ -0,0 +1,26 @@ +import { FunnelChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './_data'; + +const code = ` +import { FunnelChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ; +} +`; + +function Demo() { + return ; +} + +export const usage: MantineDemo = { + type: 'code', + component: Demo, + code: [ + { code, language: 'tsx', fileName: 'Demo.tsx' }, + { code: dataCode, language: 'tsx', fileName: 'data.ts' }, + ], + centered: true, +}; diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.withLabels.tsx b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.withLabels.tsx new file mode 100644 index 00000000000..86b2f123eb8 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/FunnelChart.demo.withLabels.tsx @@ -0,0 +1,35 @@ +import { FunnelChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './_data'; + +const code = ` +import { FunnelChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ; +} +`; + +function Wrapper(props: any) { + return ; +} + +export const withLabels: MantineDemo = { + type: 'configurator', + component: Wrapper, + code: [ + { fileName: 'Demo.tsx', code, language: 'tsx' }, + { fileName: 'data.ts', code: dataCode, language: 'tsx' }, + ], + centered: true, + controls: [ + { + type: 'segmented', + prop: 'labelsPosition', + initialValue: 'right', + libraryValue: '__', + data: ['left', 'inside', 'right'], + }, + ], +}; diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/_data.ts b/packages/@docs/demos/src/demos/charts/FunnelChart/_data.ts new file mode 100644 index 00000000000..dd2ea79d81a --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/_data.ts @@ -0,0 +1,15 @@ +export const data = [ + { name: 'USA', value: 400, color: 'indigo.6' }, + { name: 'India', value: 300, color: 'yellow.6' }, + { name: 'Japan', value: 100, color: 'teal.6' }, + { name: 'Other', value: 200, color: 'gray.6' }, +]; + +export const dataCode = ` +export const data = [ + { name: 'USA', value: 400, color: 'indigo.6' }, + { name: 'India', value: 300, color: 'yellow.6' }, + { name: 'Japan', value: 100, color: 'teal.6' }, + { name: 'Other', value: 200, color: 'gray.6' }, +]; +`; diff --git a/packages/@docs/demos/src/demos/charts/FunnelChart/index.ts b/packages/@docs/demos/src/demos/charts/FunnelChart/index.ts new file mode 100644 index 00000000000..e5b8e60ea76 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/FunnelChart/index.ts @@ -0,0 +1,8 @@ +export { color } from './FunnelChart.demo.color'; +export { noTooltip } from './FunnelChart.demo.noTooltip'; +export { size } from './FunnelChart.demo.size'; +export { strokeColor } from './FunnelChart.demo.strokeColor'; +export { strokeWidth } from './FunnelChart.demo.strokeWidth'; +export { tooltipDataSource } from './FunnelChart.demo.tooltipDataSource'; +export { usage } from './FunnelChart.demo.usage'; +export { withLabels } from './FunnelChart.demo.withLabels'; diff --git a/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.color.tsx b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.color.tsx new file mode 100644 index 00000000000..031b2c24121 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.color.tsx @@ -0,0 +1,42 @@ +import { RadialBarChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; + +const code = (props: any) => ` +import { RadialBarChart } from '@mantine/charts'; + +const data = [ + { name: '18-24', value: 31.47, color: '${props.color}' }, + { name: '25-29', value: 26.69, color: '${props.color}' }, + { name: '30-34', value: 15.69, color: '${props.color}' }, + { name: '35-39', value: 8.22, color: '${props.color}' }, + { name: '40-49', value: 8.63, color: '${props.color}' }, + { name: '50+', value: 2.63, color: '${props.color}' }, + { name: 'unknown', value: 6.67, color: '${props.color}' }, + ]; + +function Demo() { + return ; +} +`; + +function Wrapper(props: any) { + const data = [ + { name: '18-24', value: 31.47, color: props.color }, + { name: '25-29', value: 26.69, color: props.color }, + { name: '30-34', value: 15.69, color: props.color }, + { name: '35-39', value: 8.22, color: props.color }, + { name: '40-49', value: 8.63, color: props.color }, + { name: '50+', value: 2.63, color: props.color }, + { name: 'unknown', value: 6.67, color: props.color }, + ]; + + return ; +} + +export const color: MantineDemo = { + type: 'configurator', + component: Wrapper, + code, + centered: true, + controls: [{ type: 'color', prop: 'color', initialValue: 'blue', libraryValue: null }], +}; diff --git a/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.labels.tsx b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.labels.tsx new file mode 100644 index 00000000000..44c01aef39e --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.labels.tsx @@ -0,0 +1,25 @@ +import { RadialBarChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './_data'; + +const code = ` +import { RadialBarChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ; +} +`; + +function Demo() { + return ; +} + +export const labels: MantineDemo = { + type: 'code', + component: Demo, + code: [ + { fileName: 'Demo.tsx', code, language: 'tsx' }, + { fileName: 'data.ts', code: dataCode, language: 'tsx' }, + ], +}; diff --git a/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.legend.tsx b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.legend.tsx new file mode 100644 index 00000000000..554e5340d47 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.legend.tsx @@ -0,0 +1,25 @@ +import { RadialBarChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './_data'; + +const code = ` +import { RadialBarChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ; +} +`; + +function Demo() { + return ; +} + +export const legend: MantineDemo = { + type: 'code', + component: Demo, + code: [ + { fileName: 'Demo.tsx', code, language: 'tsx' }, + { fileName: 'data.ts', code: dataCode, language: 'tsx' }, + ], +}; diff --git a/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.noTooltip.tsx b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.noTooltip.tsx new file mode 100644 index 00000000000..e29bd7bf633 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.noTooltip.tsx @@ -0,0 +1,25 @@ +import { RadialBarChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './_data'; + +const code = ` +import { RadialBarChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ; +} +`; + +function Demo() { + return ; +} + +export const noTooltip: MantineDemo = { + type: 'code', + component: Demo, + code: [ + { fileName: 'Demo.tsx', code, language: 'tsx' }, + { fileName: 'data.ts', code: dataCode, language: 'tsx' }, + ], +}; diff --git a/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.usage.tsx b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.usage.tsx new file mode 100644 index 00000000000..b1d192e601a --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demo.usage.tsx @@ -0,0 +1,25 @@ +import { RadialBarChart } from '@mantine/charts'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './_data'; + +const code = ` +import { RadialBarChart } from '@mantine/charts'; +import { data } from './data'; + +function Demo() { + return ; +} +`; + +function Demo() { + return ; +} + +export const usage: MantineDemo = { + type: 'code', + component: Demo, + code: [ + { fileName: 'Demo.tsx', code, language: 'tsx' }, + { fileName: 'data.ts', code: dataCode, language: 'tsx' }, + ], +}; diff --git a/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demos.story.tsx b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demos.story.tsx new file mode 100644 index 00000000000..49a6bbffe5e --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/RadialBarChart/RadialBarChart.demos.story.tsx @@ -0,0 +1,29 @@ +import { renderDemo } from '../../../render-demo'; +import * as demos from './index'; + +export default { title: 'RadialBarChart' }; + +export const Demo_usage = { + name: '⭐ Demo: usage', + render: renderDemo(demos.usage), +}; + +export const Demo_color = { + name: '⭐ Demo: color', + render: renderDemo(demos.color), +}; + +export const Demo_legend = { + name: '⭐ Demo: legend', + render: renderDemo(demos.legend), +}; + +export const Demo_noTooltip = { + name: '⭐ Demo: noTooltip', + render: renderDemo(demos.noTooltip), +}; + +export const Demo_labels = { + name: '⭐ Demo: labels', + render: renderDemo(demos.labels), +}; diff --git a/packages/@docs/demos/src/demos/charts/RadialBarChart/_data.ts b/packages/@docs/demos/src/demos/charts/RadialBarChart/_data.ts new file mode 100644 index 00000000000..1ac4117a92b --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/RadialBarChart/_data.ts @@ -0,0 +1,21 @@ +export const data = [ + { name: '18-24', value: 31.47, color: 'blue.7' }, + { name: '25-29', value: 26.69, color: 'orange.6' }, + { name: '30-34', value: 15.69, color: 'yellow.7' }, + { name: '35-39', value: 8.22, color: 'cyan.6' }, + { name: '40-49', value: 8.63, color: 'green' }, + { name: '50+', value: 2.63, color: 'pink' }, + { name: 'unknown', value: 6.67, color: 'gray' }, +]; + +export const dataCode = ` +export const data = [ + { name: '18-24', value: 31.47, color: 'blue.7' }, + { name: '25-29', value: 26.69, color: 'orange.6' }, + { name: '30-34', value: 15.69, color: 'yellow.7' }, + { name: '35-39', value: 8.22, color: 'cyan.6' }, + { name: '40-49', value: 8.63, color: 'green' }, + { name: '50+', value: 2.63, color: 'pink' }, + { name: 'unknown', value: 6.67, color: 'gray' }, +]; +`; diff --git a/packages/@docs/demos/src/demos/charts/RadialBarChart/index.ts b/packages/@docs/demos/src/demos/charts/RadialBarChart/index.ts new file mode 100644 index 00000000000..be346bafc92 --- /dev/null +++ b/packages/@docs/demos/src/demos/charts/RadialBarChart/index.ts @@ -0,0 +1,5 @@ +export { usage } from './RadialBarChart.demo.usage'; +export { color } from './RadialBarChart.demo.color'; +export { legend } from './RadialBarChart.demo.legend'; +export { noTooltip } from './RadialBarChart.demo.noTooltip'; +export { labels } from './RadialBarChart.demo.labels'; diff --git a/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.formatLabel.tsx b/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.formatLabel.tsx new file mode 100644 index 00000000000..278b76fa140 --- /dev/null +++ b/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.formatLabel.tsx @@ -0,0 +1,21 @@ +import { AngleSlider } from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; + +const code = ` +import { AngleSlider } from '@mantine/core'; + +function Demo() { + return \`\${value}°\`} />; +} +`; + +function Demo() { + return `${value}°`} />; +} + +export const formatLabel: MantineDemo = { + type: 'code', + component: Demo, + code, + centered: true, +}; diff --git a/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.marks.tsx b/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.marks.tsx new file mode 100644 index 00000000000..9a44a9afe90 --- /dev/null +++ b/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.marks.tsx @@ -0,0 +1,91 @@ +import { AngleSlider, Group } from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; + +const code = ` +import { AngleSlider, Group } from '@mantine/core'; + +function Demo() { + return ( + + \`\${value}°\`} + size={100} + restrictToMarks + marks={[ + { value: 0 }, + { value: 45 }, + { value: 90 }, + { value: 135 }, + { value: 180 }, + { value: 225 }, + { value: 270 }, + { value: 315 }, + ]} + /> + + \`\${value}°\`} + size={100} + marks={[ + { value: 0, label: '0°' }, + { value: 45, label: '45°' }, + { value: 90, label: '90°' }, + { value: 135, label: '135°' }, + { value: 180, label: '180°' }, + { value: 225, label: '225°' }, + { value: 270, label: '270°' }, + { value: 315, label: '315°' }, + ]} + /> + + ); +} +`; + +function Demo() { + return ( + + `${value}°`} + size={100} + restrictToMarks + marks={[ + { value: 0 }, + { value: 45 }, + { value: 90 }, + { value: 135 }, + { value: 180 }, + { value: 225 }, + { value: 270 }, + { value: 315 }, + ]} + /> + + `${value}°`} + size={100} + marks={[ + { value: 0, label: '0°' }, + { value: 45, label: '45°' }, + { value: 90, label: '90°' }, + { value: 135, label: '135°' }, + { value: 180, label: '180°' }, + { value: 225, label: '225°' }, + { value: 270, label: '270°' }, + { value: 315, label: '315°' }, + ]} + /> + + ); +} + +export const marks: MantineDemo = { + type: 'code', + component: Demo, + code, + centered: true, +}; diff --git a/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.onChangeEnd.tsx b/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.onChangeEnd.tsx new file mode 100644 index 00000000000..d43c4d5c650 --- /dev/null +++ b/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.onChangeEnd.tsx @@ -0,0 +1,42 @@ +import { useState } from 'react'; +import { AngleSlider, Text } from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; + +const code = ` +import { useState } from 'react'; +import { AngleSlider, Text } from '@mantine/core'; + +function Demo() { + const [value, setValue] = useState(0); + const [endValue, setEndValue] = useState(0); + + return ( + <> + + Current value: {value} + End value: {endValue} + + ); +} +`; + +function Demo() { + const [value, setValue] = useState(0); + const [endValue, setEndValue] = useState(0); + + return ( + <> + + Current value: {value} + End value: {endValue} + + ); +} + +export const onChangeEnd: MantineDemo = { + type: 'code', + component: Demo, + code, + centered: true, + maxWidth: 200, +}; diff --git a/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.usage.tsx b/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.usage.tsx new file mode 100644 index 00000000000..b07db07a71d --- /dev/null +++ b/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demo.usage.tsx @@ -0,0 +1,26 @@ +import { AngleSlider } from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; + +const code = ` +import { AngleSlider } from '@mantine/core'; + +function Demo() { + return ; +} +`; + +function Wrapper(props: any) { + return ; +} + +export const usage: MantineDemo = { + type: 'configurator', + component: Wrapper, + code, + centered: true, + controls: [ + { type: 'number', prop: 'size', initialValue: 60, libraryValue: '__', min: 50, max: 200 }, + { type: 'number', prop: 'thumbSize', initialValue: 8, libraryValue: '__', min: 1, max: 100 }, + { type: 'boolean', prop: 'withLabel', initialValue: true, libraryValue: true }, + ], +}; diff --git a/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demos.story.tsx b/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demos.story.tsx new file mode 100644 index 00000000000..dc887670a88 --- /dev/null +++ b/packages/@docs/demos/src/demos/core/AngleSlider/AngleSlider.demos.story.tsx @@ -0,0 +1,24 @@ +import { renderDemo } from '../../../render-demo'; +import * as demos from './index'; + +export default { title: 'AngleSlider' }; + +export const Demo_usage = { + name: '⭐ Demo: usage', + render: renderDemo(demos.usage), +}; + +export const Demo_marks = { + name: '⭐ Demo: marks', + render: renderDemo(demos.marks), +}; + +export const Demo_formatLabel = { + name: '⭐ Demo: formatLabel', + render: renderDemo(demos.formatLabel), +}; + +export const Demo_onChangeEnd = { + name: '⭐ Demo: onChangeEnd', + render: renderDemo(demos.onChangeEnd), +}; diff --git a/packages/@docs/demos/src/demos/core/AngleSlider/index.ts b/packages/@docs/demos/src/demos/core/AngleSlider/index.ts new file mode 100644 index 00000000000..45ceb3c0cc8 --- /dev/null +++ b/packages/@docs/demos/src/demos/core/AngleSlider/index.ts @@ -0,0 +1,4 @@ +export { usage } from './AngleSlider.demo.usage'; +export { marks } from './AngleSlider.demo.marks'; +export { formatLabel } from './AngleSlider.demo.formatLabel'; +export { onChangeEnd } from './AngleSlider.demo.onChangeEnd'; diff --git a/packages/@docs/demos/src/demos/core/Drawer/Drawer.demo.stack.tsx b/packages/@docs/demos/src/demos/core/Drawer/Drawer.demo.stack.tsx new file mode 100644 index 00000000000..5f04a507d56 --- /dev/null +++ b/packages/@docs/demos/src/demos/core/Drawer/Drawer.demo.stack.tsx @@ -0,0 +1,115 @@ +import { Button, Drawer, Group, useDrawersStack } from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; + +const code = ` +import { Button, Group, Drawer, useDrawersStack } from '@mantine/core'; + +function Demo() { + const stack = useDrawersStack(['delete-page', 'confirm-action', 'really-confirm-action']); + + return ( + <> + + + Are you sure you want to delete this page? This action cannot be undone. + + + + + + + + Are you sure you want to perform this action? This action cannot be undone. If you are + sure, press confirm button below. + + + + + + + + Jokes aside. You have confirmed this action. This is your last chance to cancel it. After + you press confirm button below, action will be performed and cannot be undone. For real + this time. Are you sure you want to proceed? + + + + + + + + + + ); +} +`; + +function Demo() { + const stack = useDrawersStack(['delete-page', 'confirm-action', 'really-confirm-action']); + + return ( + <> + + + Are you sure you want to delete this page? This action cannot be undone. + + + + + + + + Are you sure you want to perform this action? This action cannot be undone. If you are + sure, press confirm button below. + + + + + + + + Jokes aside. You have confirmed this action. This is your last chance to cancel it. After + you press confirm button below, action will be performed and cannot be undone. For real + this time. Are you sure you want to proceed? + + + + + + + + + + ); +} + +export const stack: MantineDemo = { + type: 'code', + code, + centered: true, + component: Demo, +}; diff --git a/packages/@docs/demos/src/demos/core/Drawer/Drawer.demos.story.tsx b/packages/@docs/demos/src/demos/core/Drawer/Drawer.demos.story.tsx index 5165eeda903..387e097bf8d 100644 --- a/packages/@docs/demos/src/demos/core/Drawer/Drawer.demos.story.tsx +++ b/packages/@docs/demos/src/demos/core/Drawer/Drawer.demos.story.tsx @@ -67,3 +67,8 @@ export const Demo_initialFocusTrap = { name: '⭐ Demo: initialFocusTrap', render: renderDemo(demos.initialFocusTrap), }; + +export const Demo_stack = { + name: '⭐ Demo: stack', + render: renderDemo(demos.stack), +}; diff --git a/packages/@docs/demos/src/demos/core/Drawer/index.ts b/packages/@docs/demos/src/demos/core/Drawer/index.ts index 4d98e2d0350..105deac4331 100644 --- a/packages/@docs/demos/src/demos/core/Drawer/index.ts +++ b/packages/@docs/demos/src/demos/core/Drawer/index.ts @@ -11,3 +11,4 @@ export { overflow } from './Drawer.demo.overflow'; export { offset } from './Drawer.demo.offset'; export { closeIcon } from './Drawer.demo.closeIcon'; export { initialFocusTrap } from './Drawer.demo.initialFocusTrap'; +export { stack } from './Drawer.demo.stack'; diff --git a/packages/@docs/demos/src/demos/core/Modal/Modal.demo.stack.tsx b/packages/@docs/demos/src/demos/core/Modal/Modal.demo.stack.tsx new file mode 100644 index 00000000000..e18a192c423 --- /dev/null +++ b/packages/@docs/demos/src/demos/core/Modal/Modal.demo.stack.tsx @@ -0,0 +1,115 @@ +import { Button, Group, Modal, useModalsStack } from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; + +const code = ` +import { Button, Group, Modal, useModalsStack } from '@mantine/core'; + +function Demo() { + const stack = useModalsStack(['delete-page', 'confirm-action', 'really-confirm-action']); + + return ( + <> + + + Are you sure you want to delete this page? This action cannot be undone. + + + + + + + + Are you sure you want to perform this action? This action cannot be undone. If you are + sure, press confirm button below. + + + + + + + + Jokes aside. You have confirmed this action. This is your last chance to cancel it. After + you press confirm button below, action will be performed and cannot be undone. For real + this time. Are you sure you want to proceed? + + + + + + + + + + ); +} +`; + +function Demo() { + const stack = useModalsStack(['delete-page', 'confirm-action', 'really-confirm-action']); + + return ( + <> + + + Are you sure you want to delete this page? This action cannot be undone. + + + + + + + + Are you sure you want to perform this action? This action cannot be undone. If you are + sure, press confirm button below. + + + + + + + + Jokes aside. You have confirmed this action. This is your last chance to cancel it. After + you press confirm button below, action will be performed and cannot be undone. For real + this time. Are you sure you want to proceed? + + + + + + + + + + ); +} + +export const stack: MantineDemo = { + type: 'code', + code, + centered: true, + component: Demo, +}; diff --git a/packages/@docs/demos/src/demos/core/Modal/Modal.demos.story.tsx b/packages/@docs/demos/src/demos/core/Modal/Modal.demos.story.tsx index ea7f456e916..077ff76004c 100644 --- a/packages/@docs/demos/src/demos/core/Modal/Modal.demos.story.tsx +++ b/packages/@docs/demos/src/demos/core/Modal/Modal.demos.story.tsx @@ -82,3 +82,8 @@ export const Demo_initialFocusTrap = { name: '⭐ Demo: initialFocusTrap', render: renderDemo(demos.initialFocusTrap), }; + +export const Demo_stack = { + name: '⭐ Demo: stack', + render: renderDemo(demos.stack), +}; diff --git a/packages/@docs/demos/src/demos/core/Modal/index.ts b/packages/@docs/demos/src/demos/core/Modal/index.ts index d9afd083cad..b3f537f81b7 100644 --- a/packages/@docs/demos/src/demos/core/Modal/index.ts +++ b/packages/@docs/demos/src/demos/core/Modal/index.ts @@ -14,3 +14,4 @@ export { initialFocus } from './Modal.demo.initialFocus'; export { fullScreenMobile } from './Modal.demo.fullScreenMobile'; export { closeIcon } from './Modal.demo.closeIcon'; export { initialFocusTrap } from './Modal.demo.initialFocusTrap'; +export { stack } from './Modal.demo.stack'; diff --git a/packages/@docs/demos/src/demos/core/Slider/Slider.demo.restrictToMarks.tsx b/packages/@docs/demos/src/demos/core/Slider/Slider.demo.restrictToMarks.tsx new file mode 100644 index 00000000000..3fee480bee3 --- /dev/null +++ b/packages/@docs/demos/src/demos/core/Slider/Slider.demo.restrictToMarks.tsx @@ -0,0 +1,34 @@ +import { Slider } from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; + +const code = ` +import { Slider } from '@mantine/core'; + +function Demo() { + return ( + ({ value: index * 25 }))} + /> + ); +} +`; + +function Demo() { + return ( + ({ value: index * 25 }))} + /> + ); +} + +export const restrictToMarks: MantineDemo = { + type: 'code', + component: Demo, + code, + centered: true, + maxWidth: 400, +}; diff --git a/packages/@docs/demos/src/demos/core/Slider/Slider.demos.story.tsx b/packages/@docs/demos/src/demos/core/Slider/Slider.demos.story.tsx index 872afbedc56..e265a34926a 100644 --- a/packages/@docs/demos/src/demos/core/Slider/Slider.demos.story.tsx +++ b/packages/@docs/demos/src/demos/core/Slider/Slider.demos.story.tsx @@ -77,3 +77,8 @@ export const Demo_customSlider = { name: '⭐ Demo: customSlider', render: renderDemo(demos.customSlider), }; + +export const Demo_restrictToMarks = { + name: '⭐ Demo: restrictToMarks', + render: renderDemo(demos.restrictToMarks), +}; diff --git a/packages/@docs/demos/src/demos/core/Slider/index.ts b/packages/@docs/demos/src/demos/core/Slider/index.ts index fda9b117c84..def7eddb300 100644 --- a/packages/@docs/demos/src/demos/core/Slider/index.ts +++ b/packages/@docs/demos/src/demos/core/Slider/index.ts @@ -13,3 +13,4 @@ export { stylesApi } from './Slider.demo.stylesApi'; export { decimal } from './Slider.demo.decimal'; export { decimalRange } from './Slider.demo.decimalRange'; export { customSlider } from './Slider.demo.customSlider'; +export { restrictToMarks } from './Slider.demo.restrictToMarks'; diff --git a/packages/@docs/demos/src/demos/core/Tree/Tree.demo.checkAllNodes.tsx b/packages/@docs/demos/src/demos/core/Tree/Tree.demo.checkAllNodes.tsx new file mode 100644 index 00000000000..85b725cf997 --- /dev/null +++ b/packages/@docs/demos/src/demos/core/Tree/Tree.demo.checkAllNodes.tsx @@ -0,0 +1,155 @@ +import { IconChevronDown } from '@tabler/icons-react'; +import { + Button, + Checkbox, + getTreeExpandedState, + Group, + RenderTreeNodePayload, + Tree, + useTree, +} from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './data'; + +const code = ` +import { IconChevronDown } from '@tabler/icons-react'; +import { + Button, + Checkbox, + getTreeExpandedState, + Group, + RenderTreeNodePayload, + Tree, + useTree, +} from '@mantine/core'; +import { data } from './data'; + +const renderTreeNode = ({ + node, + expanded, + hasChildren, + elementProps, + tree, +}: RenderTreeNodePayload) => { + const checked = tree.isNodeChecked(node.value); + const indeterminate = tree.isNodeIndeterminate(node.value); + + return ( + + (!checked ? tree.checkNode(node.value) : tree.uncheckNode(node.value))} + /> + + tree.toggleExpanded(node.value)}> + {node.label} + + {hasChildren && ( + + )} + + + ); +}; + +function Demo() { + const tree = useTree({ + initialExpandedState: getTreeExpandedState(data, '*'), + initialCheckedState: [ + 'node_modules', + 'node_modules/@mantine/core/index.d.ts', + 'node_modules/@mantine/form/package.json', + ], + }); + + return ( + <> + + + + + + + + ); +} +`; + +const renderTreeNode = ({ + node, + expanded, + hasChildren, + elementProps, + tree, +}: RenderTreeNodePayload) => { + const checked = tree.isNodeChecked(node.value); + const indeterminate = tree.isNodeIndeterminate(node.value); + + return ( + + (!checked ? tree.checkNode(node.value) : tree.uncheckNode(node.value))} + /> + + tree.toggleExpanded(node.value)}> + {node.label} + + {hasChildren && ( + + )} + + + ); +}; + +function Demo() { + const tree = useTree({ + initialExpandedState: getTreeExpandedState(data, '*'), + initialCheckedState: [ + 'node_modules', + 'node_modules/@mantine/core/index.d.ts', + 'node_modules/@mantine/form/package.json', + ], + }); + + return ( + <> + + + + + + + + ); +} + +export const checkAllNodes: MantineDemo = { + type: 'code', + component: Demo, + code: [ + { fileName: 'Demo.tsx', language: 'tsx', code }, + { fileName: 'data.ts', language: 'tsx', code: dataCode }, + ], +}; diff --git a/packages/@docs/demos/src/demos/core/Tree/Tree.demo.expandedState.tsx b/packages/@docs/demos/src/demos/core/Tree/Tree.demo.expandedState.tsx new file mode 100644 index 00000000000..43b8633ff60 --- /dev/null +++ b/packages/@docs/demos/src/demos/core/Tree/Tree.demo.expandedState.tsx @@ -0,0 +1,33 @@ +import { getTreeExpandedState, Tree, useTree } from '@mantine/core'; +import { MantineDemo } from '@mantinex/demo'; +import { data, dataCode } from './data'; + +const code = ` +import { getTreeExpandedState, Tree, useTree } from '@mantine/core'; +import { data } from './data'; + +function Demo() { + const tree = useTree({ + initialExpandedState: getTreeExpandedState(data, ['src', 'src/components']), + }); + + return ; +} +`; + +function Demo() { + const tree = useTree({ + initialExpandedState: getTreeExpandedState(data, ['src', 'src/components']), + }); + + return ; +} + +export const expandedState: MantineDemo = { + type: 'code', + component: Demo, + code: [ + { fileName: 'Demo.tsx', language: 'tsx', code }, + { fileName: 'data.ts', language: 'tsx', code: dataCode }, + ], +}; diff --git a/packages/@docs/demos/src/demos/core/Tree/Tree.demos.story.tsx b/packages/@docs/demos/src/demos/core/Tree/Tree.demos.story.tsx index c5d123e8b8e..f77bb712d20 100644 --- a/packages/@docs/demos/src/demos/core/Tree/Tree.demos.story.tsx +++ b/packages/@docs/demos/src/demos/core/Tree/Tree.demos.story.tsx @@ -27,3 +27,13 @@ export const Demo_checked = { name: '⭐ Demo: checked', render: renderDemo(demos.checked), }; + +export const Demo_expandedState = { + name: '⭐ Demo: expandedState', + render: renderDemo(demos.expandedState), +}; + +export const Demo_checkAllNodes = { + name: '⭐ Demo: checkAllNodes', + render: renderDemo(demos.checkAllNodes), +}; diff --git a/packages/@docs/demos/src/demos/core/Tree/index.ts b/packages/@docs/demos/src/demos/core/Tree/index.ts index fc1df29e2b1..ab79744373b 100644 --- a/packages/@docs/demos/src/demos/core/Tree/index.ts +++ b/packages/@docs/demos/src/demos/core/Tree/index.ts @@ -3,3 +3,5 @@ export { files } from './Tree.demo.files'; export { renderNode } from './Tree.demo.renderNode'; export { controller } from './Tree.demo.controller'; export { checked } from './Tree.demo.checked'; +export { expandedState } from './Tree.demo.expandedState'; +export { checkAllNodes } from './Tree.demo.checkAllNodes'; diff --git a/packages/@docs/demos/src/index.ts b/packages/@docs/demos/src/index.ts index 1f6ddeb6012..d6f6f9ede5f 100644 --- a/packages/@docs/demos/src/index.ts +++ b/packages/@docs/demos/src/index.ts @@ -107,6 +107,7 @@ export * as NumberInputDemos from './demos/core/NumberInput'; export * as NumberFormatterDemos from './demos/core/NumberFormatter'; export * as FloatingIndicatorDemos from './demos/core/FloatingIndicator'; export * as TreeDemos from './demos/core/Tree'; +export * as AngleSliderDemos from './demos/core/AngleSlider'; // @mantine/dates export * as YearPickerDemos from './demos/dates/YearPicker'; @@ -132,6 +133,8 @@ export * as RadarChartDemos from './demos/charts/RadarChart'; export * as ScatterChartDemos from './demos/charts/ScatterChart'; export * as BubbleChartDemos from './demos/charts/BubbleChart'; export * as CompositeChartDemos from './demos/charts/CompositeChart'; +export * as RadialBarChartDemos from './demos/charts/RadialBarChart'; +export * as FunnelChartDemos from './demos/charts/FunnelChart'; // Extensions demos export * as NotificationsDemos from './demos/notifications'; diff --git a/packages/@docs/styles-api/src/data/AngleSlider.styles-api.ts b/packages/@docs/styles-api/src/data/AngleSlider.styles-api.ts new file mode 100644 index 00000000000..9144ed6be86 --- /dev/null +++ b/packages/@docs/styles-api/src/data/AngleSlider.styles-api.ts @@ -0,0 +1,19 @@ +import type { AngleSliderFactory } from '@mantine/core'; +import type { StylesApiData } from '../types'; + +export const AngleSliderStylesApi: StylesApiData = { + selectors: { + root: 'Root element', + label: 'Label inside the slider', + marks: 'Wrapper for all marks', + mark: 'Mark element', + thumb: 'Slider thumb', + }, + + vars: { + root: { + '--slider-size': 'Controls slider width and height', + '--thumb-size': 'Controls thumb size', + }, + }, +}; diff --git a/packages/@docs/styles-api/src/data/FunnelChart.styles-api.ts b/packages/@docs/styles-api/src/data/FunnelChart.styles-api.ts new file mode 100644 index 00000000000..f0f98578b71 --- /dev/null +++ b/packages/@docs/styles-api/src/data/FunnelChart.styles-api.ts @@ -0,0 +1,18 @@ +import { FunnelChartFactory } from '@mantine/charts'; +import type { StylesApiData } from '../types'; + +export const FunnelChartStylesApi: StylesApiData = { + selectors: { + root: 'Root element', + }, + + vars: { + root: { + '--chart-labels-color': 'Controls color of the chart labels', + '--chart-size': 'Controls size of the chart', + '--chart-stroke-color': 'Controls color of the chart stroke', + }, + }, + + modifiers: [], +}; diff --git a/packages/@docs/styles-api/src/data/RadialBarChart.styles-api.ts b/packages/@docs/styles-api/src/data/RadialBarChart.styles-api.ts new file mode 100644 index 00000000000..1bfd1270d4e --- /dev/null +++ b/packages/@docs/styles-api/src/data/RadialBarChart.styles-api.ts @@ -0,0 +1,17 @@ +import type { RadialBarChartFactory } from '@mantine/charts'; +import type { StylesApiData } from '../types'; + +export const RadialBarChartStylesApi: StylesApiData = { + selectors: { + root: 'Root element', + tooltip: 'Tooltip root element', + }, + + vars: { + root: { + '--chart-empty-background': 'Background color of the empty space in the chart', + }, + }, + + modifiers: [], +}; diff --git a/packages/@docs/styles-api/src/index.ts b/packages/@docs/styles-api/src/index.ts index a80d9feedca..1db58426ca8 100644 --- a/packages/@docs/styles-api/src/index.ts +++ b/packages/@docs/styles-api/src/index.ts @@ -5,6 +5,7 @@ export * from './data/ActionIcon.styles-api'; export * from './data/Affix.styles-api'; export * from './data/Alert.styles-api'; export * from './data/Anchor.styles-api'; +export * from './data/AngleSlider.styles-api'; export * from './data/AppShell.styles-api'; export * from './data/AreaChart.styles-api'; export * from './data/AspectRatio.styles-api'; @@ -42,6 +43,7 @@ export * from './data/Fieldset.styles-api'; export * from './data/FileInput.styles-api'; export * from './data/Flex.styles-api'; export * from './data/FloatingIndicator.styles-api'; +export * from './data/FunnelChart.styles-api'; export * from './data/Grid.styles-api'; export * from './data/Group.styles-api'; export * from './data/Highlight.styles-api'; @@ -76,6 +78,7 @@ export * from './data/PinInput.styles-api'; export * from './data/Popover.styles-api'; export * from './data/Progress.styles-api'; export * from './data/RadarChart.styles-api'; +export * from './data/RadialBarChart.styles-api'; export * from './data/Radio.styles-api'; export * from './data/Rating.styles-api'; export * from './data/RichTextEditor.styles-api'; diff --git a/packages/@mantine/carousel/package.json b/packages/@mantine/carousel/package.json index f25272536aa..91e3c340202 100644 --- a/packages/@mantine/carousel/package.json +++ b/packages/@mantine/carousel/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/carousel", - "version": "7.13.5", + "version": "7.14.0", "description": "Embla based carousel", "homepage": "https://mantine.dev/x/carousel/", "license": "MIT", @@ -44,8 +44,8 @@ "directory": "packages/@mantine/carousel" }, "peerDependencies": { - "@mantine/core": "7.13.5", - "@mantine/hooks": "7.13.5", + "@mantine/core": "7.14.0", + "@mantine/hooks": "7.14.0", "embla-carousel-react": ">=7.0.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" diff --git a/packages/@mantine/charts/package.json b/packages/@mantine/charts/package.json index de2e9a8b285..d313b10aaf6 100644 --- a/packages/@mantine/charts/package.json +++ b/packages/@mantine/charts/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/charts", - "version": "7.13.5", + "version": "7.14.0", "description": "Charts components built with recharts and Mantine", "homepage": "https://mantine.dev/", "license": "MIT", @@ -35,8 +35,8 @@ "directory": "packages/@mantine/charts" }, "peerDependencies": { - "@mantine/core": "7.13.5", - "@mantine/hooks": "7.13.5", + "@mantine/core": "7.14.0", + "@mantine/hooks": "7.14.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x", "recharts": "^2.13.3" diff --git a/packages/@mantine/charts/src/BarChart/BarChart.story.tsx b/packages/@mantine/charts/src/BarChart/BarChart.story.tsx index abad6e7d695..a44de407cd4 100644 --- a/packages/@mantine/charts/src/BarChart/BarChart.story.tsx +++ b/packages/@mantine/charts/src/BarChart/BarChart.story.tsx @@ -33,14 +33,32 @@ const waterfallData = [ export function Usage() { return ( -
+
+ barProps={() => ({ fill: 'url(#diagonalStripes)' })} + > + + + + + +
); } diff --git a/packages/@mantine/charts/src/BarChart/BarChart.tsx b/packages/@mantine/charts/src/BarChart/BarChart.tsx index e1e34d99544..ebe2e552e78 100644 --- a/packages/@mantine/charts/src/BarChart/BarChart.tsx +++ b/packages/@mantine/charts/src/BarChart/BarChart.tsx @@ -155,6 +155,14 @@ function calculateCumulativeTotal(waterfallData: Record[], dataKey: }); } +function getBarFill(barProps: BarChartProps['barProps'], series: BarChartSeries) { + if (typeof barProps === 'function') { + return barProps(series).fill; + } + + return barProps?.fill; +} + export const BarChart = factory((_props, ref) => { const props = useProps('BarChart', defaultProps, _props); const { @@ -262,7 +270,9 @@ export const BarChart = factory((_props, ref) => { {inputData.map((entry, index) => ( ))} diff --git a/packages/@mantine/charts/src/ChartLegend/ChartLegend.tsx b/packages/@mantine/charts/src/ChartLegend/ChartLegend.tsx index 990bcb30532..15382f2702d 100644 --- a/packages/@mantine/charts/src/ChartLegend/ChartLegend.tsx +++ b/packages/@mantine/charts/src/ChartLegend/ChartLegend.tsx @@ -15,7 +15,7 @@ import classes from './ChartLegend.module.css'; function updateChartLegendPayload(payload: Record[]): Record[] { return payload.map((item) => { - const newDataKey = item.dataKey.split('.').pop(); + const newDataKey = item.dataKey?.split('.').pop(); return { ...item, dataKey: newDataKey, diff --git a/packages/@mantine/charts/src/ChartTooltip/ChartTooltip.module.css b/packages/@mantine/charts/src/ChartTooltip/ChartTooltip.module.css index a34e9ba7d52..0c6ed3948cd 100644 --- a/packages/@mantine/charts/src/ChartTooltip/ChartTooltip.module.css +++ b/packages/@mantine/charts/src/ChartTooltip/ChartTooltip.module.css @@ -29,6 +29,11 @@ } } +.tooltipItemColor { + width: 12px; + height: 12px; +} + .tooltipItem { font-size: var(--mantine-font-size-sm); display: flex; diff --git a/packages/@mantine/charts/src/ChartTooltip/ChartTooltip.tsx b/packages/@mantine/charts/src/ChartTooltip/ChartTooltip.tsx index e85fcbfabbc..ec55365f3c7 100644 --- a/packages/@mantine/charts/src/ChartTooltip/ChartTooltip.tsx +++ b/packages/@mantine/charts/src/ChartTooltip/ChartTooltip.tsx @@ -1,7 +1,6 @@ import { Box, BoxProps, - ColorSwatch, ElementProps, factory, Factory, @@ -165,12 +164,16 @@ export const ChartTooltip = factory((_props, ref) => {
{showColor && ( - + + + )}
{labels[item.name] || item.name}
diff --git a/packages/@mantine/charts/src/FunnelChart/FunnelChart.module.css b/packages/@mantine/charts/src/FunnelChart/FunnelChart.module.css new file mode 100644 index 00000000000..8bcbae560a2 --- /dev/null +++ b/packages/@mantine/charts/src/FunnelChart/FunnelChart.module.css @@ -0,0 +1,10 @@ +.root { + min-height: var(--chart-size, auto); + height: var(--chart-size, auto); + width: var(--chart-size, auto); + min-width: var(--chart-size, auto); + + & :where(*) { + outline: 0; + } +} diff --git a/packages/@mantine/charts/src/FunnelChart/FunnelChart.story.tsx b/packages/@mantine/charts/src/FunnelChart/FunnelChart.story.tsx new file mode 100644 index 00000000000..f13f1c8b9d1 --- /dev/null +++ b/packages/@mantine/charts/src/FunnelChart/FunnelChart.story.tsx @@ -0,0 +1,42 @@ +import { FunnelChart, FunnelChartCell } from './FunnelChart'; + +export default { title: 'FunnelChart' }; + +const data: FunnelChartCell[] = [ + { name: 'Visits', value: 100, color: 'indigo.6' }, + { name: 'Cart', value: 80, color: 'yellow.6' }, + { name: 'Checkout', value: 50, color: 'teal.6' }, + { name: 'Purchase', value: 40, color: 'pink.6' }, + { name: 'Review', value: 26, color: 'red.6' }, +]; + +export function Usage() { + return ( +
+ +
+ ); +} + +export function WithLabels() { + return ( +
+ +
+ ); +} + +export function CustomSize() { + return ( +
+ +
+ ); +} diff --git a/packages/@mantine/charts/src/FunnelChart/FunnelChart.test.tsx b/packages/@mantine/charts/src/FunnelChart/FunnelChart.test.tsx new file mode 100644 index 00000000000..8c9605964d7 --- /dev/null +++ b/packages/@mantine/charts/src/FunnelChart/FunnelChart.test.tsx @@ -0,0 +1,38 @@ +import { patchConsoleWarn, tests } from '@mantine-tests/core'; +import { FunnelChart, FunnelChartProps, FunnelChartStylesNames } from './FunnelChart'; + +const data = [ + { name: 'Visits', value: 5000, color: 'indigo.6' }, + { name: 'Cart', value: 2000, color: 'yellow.6' }, + { name: 'Checkout', value: 1000, color: 'teal.6' }, + { name: 'Purchase', value: 500, color: 'pink.6' }, +]; + +const defaultProps: FunnelChartProps = { + data, +}; + +describe('@mantine/charts/FunnelChart', () => { + beforeAll(() => { + patchConsoleWarn(); + }); + + afterAll(() => { + patchConsoleWarn.release(); + }); + + tests.itSupportsSystemProps({ + component: FunnelChart, + props: defaultProps, + mod: true, + styleProps: true, + extend: true, + withProps: true, + variant: true, + size: true, + classes: true, + refType: HTMLDivElement, + displayName: '@mantine/charts/FunnelChart', + stylesApiSelectors: ['root'], + }); +}); diff --git a/packages/@mantine/charts/src/FunnelChart/FunnelChart.tsx b/packages/@mantine/charts/src/FunnelChart/FunnelChart.tsx new file mode 100644 index 00000000000..abba30cc497 --- /dev/null +++ b/packages/@mantine/charts/src/FunnelChart/FunnelChart.tsx @@ -0,0 +1,230 @@ +import { + Cell, + Funnel, + FunnelProps, + LabelList, + FunnelChart as RechartsFunnelChart, + ResponsiveContainer, + Tooltip, + TooltipProps, +} from 'recharts'; +import { + Box, + BoxProps, + createVarsResolver, + ElementProps, + factory, + Factory, + getThemeColor, + MantineColor, + rem, + StylesApiProps, + useMantineTheme, + useProps, + useResolvedStylesApi, + useStyles, +} from '@mantine/core'; +import { ChartTooltip } from '../ChartTooltip/ChartTooltip'; +import classes from './FunnelChart.module.css'; + +export interface FunnelChartCell { + key?: string | number; + name: string; + value: number; + color: MantineColor; +} + +export type FunnelChartStylesNames = 'root'; +export type FunnelChartCssVariables = { + root: '--chart-stroke-color' | '--chart-labels-color' | '--chart-size'; +}; + +export interface FunnelChartProps + extends BoxProps, + StylesApiProps, + ElementProps<'div'> { + /** Data used to render chart */ + data: FunnelChartCell[]; + + /** Determines whether the tooltip should be displayed when a section is hovered, `true` by default */ + withTooltip?: boolean; + + /** Tooltip animation duration in ms, `0` by default */ + tooltipAnimationDuration?: number; + + /** Props passed down to `Tooltip` recharts component */ + tooltipProps?: Omit, 'ref'>; + + /** Props passed down to recharts `Pie` component */ + funnelProps?: Partial>; + + /** Controls color of the segments stroke, by default depends on color scheme */ + strokeColor?: MantineColor; + + /** Controls text color of all labels, white by default */ + labelColor?: MantineColor; + + /** Controls chart width and height, `300` by default */ + size?: number; + + /** Controls width of segments stroke, `1` by default */ + strokeWidth?: number; + + /** Determines whether each segment should have associated label, `false` by default */ + withLabels?: boolean; + + /** Controls labels position relative to the segment, `'right'` by default */ + labelsPosition?: 'right' | 'left' | 'inside'; + + /** A function to format values inside the tooltip and labels */ + valueFormatter?: (value: number) => string; + + /** Determines which data is displayed in the tooltip. `'all'` – display all values, `'segment'` – display only hovered segment. `'all'` by default. */ + tooltipDataSource?: 'segment' | 'all'; + + /** Additional elements rendered inside `FunnelChart` component */ + children?: React.ReactNode; + + /** Props passed down to recharts `FunnelChart` component */ + funnelChartProps?: React.ComponentPropsWithoutRef; +} + +export type FunnelChartFactory = Factory<{ + props: FunnelChartProps; + ref: HTMLDivElement; + stylesNames: FunnelChartStylesNames; + vars: FunnelChartCssVariables; +}>; + +const defaultProps: Partial = { + withTooltip: true, + size: 300, + strokeWidth: 1, + withLabels: false, + labelsPosition: 'right', + tooltipDataSource: 'all', +}; + +const varsResolver = createVarsResolver( + (theme, { strokeColor, labelColor, size }) => ({ + root: { + '--chart-stroke-color': strokeColor ? getThemeColor(strokeColor, theme) : undefined, + '--chart-labels-color': labelColor ? getThemeColor(labelColor, theme) : undefined, + '--chart-size': rem(size!), + }, + }) +); + +export const FunnelChart = factory((_props, ref) => { + const props = useProps('FunnelChart', defaultProps, _props); + const { + classNames, + className, + style, + styles, + unstyled, + vars, + data, + withTooltip, + tooltipAnimationDuration, + tooltipProps, + strokeWidth, + withLabels, + size, + valueFormatter, + children, + funnelChartProps, + funnelProps, + labelsPosition, + tooltipDataSource, + ...others + } = props; + + const theme = useMantineTheme(); + + const getStyles = useStyles({ + name: 'FunnelChart', + classes, + props, + className, + style, + classNames, + styles, + unstyled, + vars, + varsResolver, + }); + + const { resolvedClassNames, resolvedStyles } = useResolvedStylesApi({ + classNames, + styles, + props, + }); + + return ( + + + + + {withLabels && ( + { + return typeof valueFormatter === 'function' + ? valueFormatter(entry.value as number) + : entry.value; + }} + /> + )} + {data.map((entry, index) => ( + + ))} + + + {withTooltip && ( + ( + + )} + {...tooltipProps} + /> + )} + + {children} + + + + ); +}); + +FunnelChart.displayName = '@mantine/charts/FunnelChart'; +FunnelChart.classes = classes; diff --git a/packages/@mantine/charts/src/FunnelChart/index.ts b/packages/@mantine/charts/src/FunnelChart/index.ts new file mode 100644 index 00000000000..a6a19186909 --- /dev/null +++ b/packages/@mantine/charts/src/FunnelChart/index.ts @@ -0,0 +1,8 @@ +export { FunnelChart } from './FunnelChart'; +export type { + FunnelChartCssVariables, + FunnelChartFactory, + FunnelChartProps, + FunnelChartStylesNames, + FunnelChartCell, +} from './FunnelChart'; diff --git a/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.module.css b/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.module.css new file mode 100644 index 00000000000..1c34f93a900 --- /dev/null +++ b/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.module.css @@ -0,0 +1,29 @@ +.root { + @mixin where-light { + --chart-empty-background: var(--mantine-color-gray-1); + --chart-cursor-color: var(--mantine-color-gray-4); + } + + @mixin where-dark { + --chart-empty-background: var(--mantine-color-dark-6); + --chart-cursor-color: var(--mantine-color-dark-4); + } +} + +.tooltip { + padding: var(--mantine-spacing-md); + box-shadow: var(--mantine-shadow-md); + min-width: 200px; + font-size: var(--mantine-font-size-sm); + display: flex; + align-items: center; + justify-content: space-between; + + @mixin where-light { + border: 1px solid var(--mantine-color-gray-2); + } + + @mixin where-dark { + border: 1px solid var(--mantine-color-dark-4); + } +} diff --git a/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.story.tsx b/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.story.tsx new file mode 100644 index 00000000000..603c7b25dce --- /dev/null +++ b/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.story.tsx @@ -0,0 +1,49 @@ +import { RadialBarChart } from './RadialBarChart'; + +export default { title: 'RadialBarChart' }; + +const data = [ + { + name: '18-24', + value: 31.47, + color: 'blue.7', + }, + { + name: '25-29', + value: 26.69, + color: 'orange.6', + }, + { + name: '30-34', + value: 15.69, + color: 'yellow.7', + }, + { + name: '35-39', + value: 8.22, + color: 'cyan.6', + }, + { + name: '40-49', + value: 8.63, + color: 'green', + }, + { + name: '50+', + value: 2.63, + color: 'pink', + }, + { + name: 'unknown', + value: 6.67, + color: 'gray', + }, +]; + +export function Usage() { + return ( +
+ +
+ ); +} diff --git a/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.test.tsx b/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.test.tsx new file mode 100644 index 00000000000..3c16f94054c --- /dev/null +++ b/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.test.tsx @@ -0,0 +1,34 @@ +import { patchConsoleWarn, tests } from '@mantine-tests/core'; +import { RadialBarChart, RadialBarChartProps, RadialBarChartStylesNames } from './RadialBarChart'; + +const defaultProps: RadialBarChartProps = { + data: [ + { name: 'A', value: 10 }, + { name: 'B', value: 20 }, + { name: 'C', value: 30 }, + ], + dataKey: 'value', +}; + +describe('@mantine/core/RadialBarChart', () => { + beforeAll(() => { + patchConsoleWarn(); + }); + + afterAll(() => { + patchConsoleWarn.release(); + }); + + tests.itSupportsSystemProps({ + component: RadialBarChart, + props: defaultProps, + styleProps: true, + extend: true, + variant: true, + size: true, + classes: true, + refType: HTMLDivElement, + displayName: '@mantine/core/RadialBarChart', + stylesApiSelectors: ['root'], + }); +}); diff --git a/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.tsx b/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.tsx new file mode 100644 index 00000000000..f4eea75ca9d --- /dev/null +++ b/packages/@mantine/charts/src/RadialBarChart/RadialBarChart.tsx @@ -0,0 +1,245 @@ +import { useState } from 'react'; +import { + Legend, + LegendProps, + RadialBar, + RadialBarProps, + RadialBarChart as ReChartsRadialBarChart, + ResponsiveContainer, + Tooltip, + TooltipProps, +} from 'recharts'; +import { + Box, + BoxProps, + ColorSwatch, + createVarsResolver, + ElementProps, + factory, + Factory, + getThemeColor, + Group, + Paper, + StylesApiProps, + useMantineTheme, + useProps, + useResolvedStylesApi, + useStyles, +} from '@mantine/core'; +import { ChartLegend } from '../ChartLegend'; +import classes from './RadialBarChart.module.css'; + +export type RadialBarChartStylesNames = 'root' | 'tooltip'; +export type RadialBarChartCssVariables = { + root: '--chart-empty-background'; +}; + +export interface RadialBarChartProps + extends BoxProps, + StylesApiProps, + ElementProps<'div'> { + /** Chart data */ + data: Record[]; + + /** Key from data object to use as data key */ + dataKey: string; + + /** Size of bars in px, `20` by default */ + barSize?: number; + + /** Determines whether empty bars area should be visible, `true` by default */ + withBackground?: boolean; + + /** Determines whether labels should be displayed, `false` by default */ + withLabels?: boolean; + + /** Determines whether the legend should be displayed, `false` by default */ + withLegend?: boolean; + + /** Determines whether the tooltip should be displayed when one of the bars is hovered, `true` by default */ + withTooltip?: boolean; + + /** Color of the empty background, by default depends on the color scheme */ + emptyBackgroundColor?: string; + + /** Angle at which chart starts, `90` by default */ + startAngle?: number; + + /** Angle at which chart ends, `-270` by default */ + endAngle?: number; + + /** Props passed down to recharts RadialBar component */ + radialBarProps?: Omit; + + /** Props passed down to recharts RadarChartChart component */ + radialBarChartProps?: React.ComponentPropsWithoutRef; + + /** Props passed down to recharts Legend component */ + legendProps?: Omit; + + /** Props passed down to `Tooltip` recharts component */ + tooltipProps?: Omit, 'ref'>; +} + +export type RadialBarChartFactory = Factory<{ + props: RadialBarChartProps; + ref: HTMLDivElement; + stylesNames: RadialBarChartStylesNames; + vars: RadialBarChartCssVariables; +}>; + +const defaultProps: Partial = { + barSize: 20, + startAngle: 90, + endAngle: -270, + withBackground: true, + withTooltip: true, +}; + +const varsResolver = createVarsResolver( + (theme, { emptyBackgroundColor }) => ({ + root: { + '--chart-empty-background': emptyBackgroundColor + ? getThemeColor(emptyBackgroundColor, theme) + : undefined, + }, + }) +); + +export const RadialBarChart = factory((_props, ref) => { + const props = useProps('RadialBarChart', defaultProps, _props); + const { + classNames, + className, + style, + styles, + unstyled, + vars, + data, + barSize, + withBackground, + dataKey, + radialBarProps, + radialBarChartProps, + withLabels, + withLegend, + legendProps, + withTooltip, + tooltipProps, + startAngle, + endAngle, + ...others + } = props; + const [highlightedArea, setHighlightedArea] = useState(null); + + const getStyles = useStyles({ + name: 'RadialBarChart', + classes, + props, + className, + style, + classNames, + styles, + unstyled, + vars, + varsResolver, + }); + + const theme = useMantineTheme(); + const dataWithResolvedColor = data.map(({ color, ...item }) => { + const resolvedColor = getThemeColor(color, theme); + + return { + ...item, + fill: resolvedColor, + fillOpacity: highlightedArea + ? highlightedArea === item.name + ? item.opacity || 1 + : 0.05 + : item.opacity || 1, + }; + }); + + const { resolvedClassNames, resolvedStyles } = useResolvedStylesApi({ + classNames, + styles, + props, + }); + + return ( + + + + + + {withLegend && ( + ( + ({ + ...item, + dataKey: (item.payload as any)?.name, + }))} + onHighlight={setHighlightedArea} + legendPosition={legendProps?.verticalAlign || 'bottom'} + classNames={resolvedClassNames} + styles={resolvedStyles} + centered + /> + )} + {...legendProps} + /> + )} + + {withTooltip && ( + ( + + + + {payload?.[0]?.payload.name} + + + {payload?.[0]?.payload[dataKey]} + + )} + {...tooltipProps} + /> + )} + + + + ); +}); + +RadialBarChart.displayName = '@mantine/core/RadialBarChart'; +RadialBarChart.classes = classes; diff --git a/packages/@mantine/charts/src/RadialBarChart/index.ts b/packages/@mantine/charts/src/RadialBarChart/index.ts new file mode 100644 index 00000000000..820288de410 --- /dev/null +++ b/packages/@mantine/charts/src/RadialBarChart/index.ts @@ -0,0 +1,7 @@ +export { RadialBarChart } from './RadialBarChart'; +export type { + RadialBarChartCssVariables, + RadialBarChartFactory, + RadialBarChartProps, + RadialBarChartStylesNames, +} from './RadialBarChart'; diff --git a/packages/@mantine/charts/src/index.ts b/packages/@mantine/charts/src/index.ts index 74491e0a996..4d94ae7449d 100644 --- a/packages/@mantine/charts/src/index.ts +++ b/packages/@mantine/charts/src/index.ts @@ -10,4 +10,6 @@ export * from './RadarChart/index.js'; export * from './ScatterChart/index.js'; export * from './BubbleChart/index.js'; export * from './CompositeChart/index.js'; +export * from './RadialBarChart/index.js'; +export * from './FunnelChart/index.js'; export * from './types'; diff --git a/packages/@mantine/code-highlight/package.json b/packages/@mantine/code-highlight/package.json index 1bc01339c39..7c63d794779 100644 --- a/packages/@mantine/code-highlight/package.json +++ b/packages/@mantine/code-highlight/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/code-highlight", - "version": "7.13.5", + "version": "7.14.0", "description": "Code highlight with Mantine theme", "homepage": "https://mantine.dev/x/code-highlight/", "license": "MIT", @@ -45,8 +45,8 @@ "directory": "packages/@mantine/code-highlight" }, "peerDependencies": { - "@mantine/core": "7.13.5", - "@mantine/hooks": "7.13.5", + "@mantine/core": "7.14.0", + "@mantine/hooks": "7.14.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" }, diff --git a/packages/@mantine/colors-generator/package.json b/packages/@mantine/colors-generator/package.json index afb7ab1fb84..3827ae21673 100644 --- a/packages/@mantine/colors-generator/package.json +++ b/packages/@mantine/colors-generator/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/colors-generator", - "version": "7.13.5", + "version": "7.14.0", "description": "A library to generate 10 shades of color based on provided color value", "homepage": "https://mantine.dev", "license": "MIT", diff --git a/packages/@mantine/core/package.json b/packages/@mantine/core/package.json index 1b1e55dbdb8..2d20d11a1ea 100644 --- a/packages/@mantine/core/package.json +++ b/packages/@mantine/core/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/core", - "version": "7.13.5", + "version": "7.14.0", "description": "React components library focused on usability, accessibility and developer experience", "homepage": "https://mantine.dev/", "license": "MIT", @@ -43,7 +43,7 @@ "directory": "packages/@mantine/core" }, "peerDependencies": { - "@mantine/hooks": "7.13.5", + "@mantine/hooks": "7.14.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" }, diff --git a/packages/@mantine/core/src/components/AngleSlider/AngleSlider.module.css b/packages/@mantine/core/src/components/AngleSlider/AngleSlider.module.css new file mode 100644 index 00000000000..013a5d07f50 --- /dev/null +++ b/packages/@mantine/core/src/components/AngleSlider/AngleSlider.module.css @@ -0,0 +1,104 @@ +.root { + width: var(--slider-size); + height: var(--slider-size); + position: relative; + border-radius: 100%; + display: flex; + align-items: center; + justify-content: center; + user-select: none; + + &:focus-within { + outline: 2px solid var(--mantine-primary-color-filled); + outline-offset: 2px; + } + + --slider-size: 60px; + --thumb-size: calc(var(--slider-size) / 5); + + @mixin where-light { + background-color: var(--mantine-color-gray-1); + } + + @mixin where-dark { + background-color: var(--mantine-color-dark-5); + } +} + +.marks { + position: absolute; + inset: 1px; + border-radius: var(--slider-size); + pointer-events: none; +} + +.mark { + width: 2px; + position: absolute; + top: 0; + bottom: 0; + left: calc(50% - 1px); + transform: rotate(var(--angle)); + + &::before { + content: ''; + position: absolute; + top: calc(var(--thumb-size) / 3); + left: 0.5px; + width: 1px; + height: calc(var(--thumb-size) / 1.5); + transform: translate(-50%, -50%); + + @mixin where-light { + background-color: var(--mantine-color-gray-4); + } + + @mixin where-dark { + background-color: var(--mantine-color-dark-3); + } + } + + &[data-label]::after { + min-width: 18px; + text-align: center; + content: attr(data-label); + position: absolute; + top: -24px; + left: -7px; + transform: rotate(calc(360deg - var(--angle))); + font-size: var(--mantine-font-size-xs); + } +} + +.thumb { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: calc(50% - 1.5px); + height: 100%; + width: 3px; + outline: none; + pointer-events: none; + + &::before { + content: ''; + position: absolute; + right: 0; + top: 0; + height: min(var(--thumb-size), calc(var(--slider-size) / 2)); + width: 3px; + + @mixin where-light { + background-color: var(--mantine-color-gray-7); + } + + @mixin where-dark { + background-color: var(--mantine-color-dark-1); + } + } +} + +.label { + font-size: var(--mantine-font-size-xs); +} diff --git a/packages/@mantine/core/src/components/AngleSlider/AngleSlider.story.tsx b/packages/@mantine/core/src/components/AngleSlider/AngleSlider.story.tsx new file mode 100644 index 00000000000..610242a0553 --- /dev/null +++ b/packages/@mantine/core/src/components/AngleSlider/AngleSlider.story.tsx @@ -0,0 +1,55 @@ +import { AngleSlider } from './AngleSlider'; + +export default { title: 'AngleSlider' }; + +export function Usage() { + return ( +
+ `${value}°`} /> +
+ ); +} + +export function WithMarks() { + return ( +
+ `${value}°`} + marks={[ + { value: 0 }, + { value: 45 }, + { value: 90 }, + { value: 135 }, + { value: 180 }, + { value: 225 }, + { value: 270 }, + { value: 315 }, + ]} + restrictToMarks + /> +
+ ); +} + +export function RestrictToMarks() { + return ( +
+ `${value}°`} + marks={[ + { value: 0, label: '0°' }, + { value: 45, label: '45°' }, + { value: 90, label: '90°' }, + { value: 135, label: '135°' }, + { value: 180, label: '180°' }, + { value: 225, label: '225°' }, + { value: 270, label: '270°' }, + { value: 315, label: '315°' }, + ]} + restrictToMarks + /> +
+ ); +} diff --git a/packages/@mantine/core/src/components/AngleSlider/AngleSlider.test.tsx b/packages/@mantine/core/src/components/AngleSlider/AngleSlider.test.tsx new file mode 100644 index 00000000000..f1ca9ce6cd0 --- /dev/null +++ b/packages/@mantine/core/src/components/AngleSlider/AngleSlider.test.tsx @@ -0,0 +1,31 @@ +import { render, tests } from '@mantine-tests/core'; +import { AngleSlider, AngleSliderProps, AngleSliderStylesNames } from './AngleSlider'; + +const defaultProps: AngleSliderProps = { + withLabel: true, + marks: [{ value: 0, label: 'test-mark' }], +}; + +describe('@mantine/core/AngleSlider', () => { + tests.axe([]); + + tests.itSupportsSystemProps({ + component: AngleSlider, + props: defaultProps, + styleProps: true, + extend: true, + variant: true, + size: true, + classes: true, + refType: HTMLDivElement, + displayName: '@mantine/core/AngleSlider', + stylesApiSelectors: ['root', 'thumb', 'label', 'mark', 'marks'], + }); + + it('renders hidden input with correct name', () => { + const { container } = render(); + const input = container.querySelector('input[type="hidden"]'); + expect(input).toHaveAttribute('name', 'test-name'); + expect(input).toHaveAttribute('value', '120'); + }); +}); diff --git a/packages/@mantine/core/src/components/AngleSlider/AngleSlider.tsx b/packages/@mantine/core/src/components/AngleSlider/AngleSlider.tsx new file mode 100644 index 00000000000..22071392e59 --- /dev/null +++ b/packages/@mantine/core/src/components/AngleSlider/AngleSlider.tsx @@ -0,0 +1,289 @@ +import { useCallback, useRef } from 'react'; +import { clamp, useMergedRef, useUncontrolled } from '@mantine/hooks'; +import { + Box, + BoxProps, + createVarsResolver, + ElementProps, + factory, + Factory, + findClosestNumber, + rem, + StylesApiProps, + useProps, + useStyles, +} from '../../core'; +import classes from './AngleSlider.module.css'; + +export type AngleSliderStylesNames = 'root' | 'thumb' | 'label' | 'marks' | 'mark'; +export type AngleSliderCssVariables = { + root: '--slider-size' | '--thumb-size'; +}; + +export interface AngleSliderProps + extends BoxProps, + StylesApiProps, + ElementProps<'div', 'onChange'> { + /** Step between values, used when the component is controlled with keyboard, `1` by default */ + step?: number; + + /** Value of the controlled component */ + value?: number; + + /** Default value for uncontrolled component */ + defaultValue?: number; + + /** Called on value change */ + onChange?: (value: number) => void; + + /** Called after the selection is finished */ + onChangeEnd?: (value: number) => void; + + /** Determines whether the label should be displayed inside the slider, `true` by default */ + withLabel?: boolean; + + /** Array of marks that are displayed on the slider */ + marks?: { value: number; label?: string }[]; + + /** Slider size in px, `60px` */ + size?: number; + + /** Size of the thumb in px, by default is calculated based on the `size` value */ + thumbSize?: number; + + /** Formats label based on the current value */ + formatLabel?: (value: number) => React.ReactNode; + + /** Disables interactions */ + disabled?: boolean; + + /** Determines whether the selection should be only allowed from the given marks array, `false` by default */ + restrictToMarks?: boolean; + + /** Props passed down to the hidden input */ + hiddenInputProps?: React.ComponentPropsWithoutRef<'input'>; + + /** Hidden input name, use with uncontrolled component */ + name?: string; +} + +export type AngleSliderFactory = Factory<{ + props: AngleSliderProps; + ref: HTMLDivElement; + stylesNames: AngleSliderStylesNames; + vars: AngleSliderCssVariables; +}>; + +function radiansToDegrees(radians: number) { + return radians * (180 / Math.PI); +} + +function getElementCenter(element: HTMLElement) { + const rect = element.getBoundingClientRect(); + return [rect.left + rect.width / 2, rect.top + rect.height / 2]; +} + +function getAngle(coordinates: [number, number], element: HTMLElement) { + const center = getElementCenter(element); + const x = coordinates[0] - center[0]; + const y = coordinates[1] - center[1]; + const deg = radiansToDegrees(Math.atan2(x, y)) + 180; + return 360 - deg; +} + +function normalize(degree: number, step: number) { + const clamped = clamp(degree, 0, 360); + const high = Math.ceil(clamped / step); + const low = Math.round(clamped / step); + return high >= clamped / step ? (high * step === 360 ? 0 : high * step) : low * step; +} + +const defaultProps: Partial = { + step: 1, + withLabel: true, +}; + +const varsResolver = createVarsResolver((_, { size, thumbSize }) => ({ + root: { + '--slider-size': rem(size), + '--thumb-size': rem(thumbSize), + }, +})); + +export const AngleSlider = factory((_props, ref) => { + const props = useProps('AngleSlider', defaultProps, _props); + const { + classNames, + className, + style, + styles, + unstyled, + vars, + step, + value, + defaultValue, + onChange, + onMouseDown, + withLabel, + marks, + thumbSize, + restrictToMarks, + formatLabel, + onChangeEnd, + disabled, + onTouchStart, + name, + hiddenInputProps, + 'aria-label': ariaLabel, + tabIndex, + ...others + } = props; + + const rootRef = useRef(null); + const [_value, setValue] = useUncontrolled({ + value, + defaultValue, + finalValue: 0, + onChange, + }); + + const getStyles = useStyles({ + name: 'AngleSlider', + classes, + props, + className, + style, + classNames, + styles, + unstyled, + vars, + varsResolver, + }); + + const update = (event: MouseEvent, done = false) => { + if (rootRef.current) { + const deg = getAngle([event.clientX, event.clientY], rootRef.current); + const val = normalize(deg, step || 1); + const newValue = + restrictToMarks && Array.isArray(marks) + ? findClosestNumber( + val, + marks.map((mark) => mark.value) + ) + : val; + + setValue(newValue); + done && onChangeEnd?.(newValue); + } + }; + + const handleMouseMove = useCallback((event: MouseEvent) => { + update(event); + }, []); + + const handleMouseUp = useCallback((event: MouseEvent) => { + update(event, true); + endTracking(); + }, []); + + const handleTouchMove = useCallback((event: TouchEvent) => { + update(event.touches[0] as any); + }, []); + + const handleTouchEnd = useCallback((event: TouchEvent) => { + update(event.changedTouches[0] as any, true); + endTracking(); + }, []); + + const handleTouchStart = (event: React.TouchEvent) => { + onTouchStart?.(event); + beginTracking(); + }; + + const beginTracking = () => { + document.addEventListener('mousemove', handleMouseMove, false); + document.addEventListener('mouseup', handleMouseUp, false); + document.addEventListener('touchmove', handleTouchMove, false); + document.addEventListener('touchend', handleTouchEnd, false); + }; + + const endTracking = () => { + document.removeEventListener('mousemove', handleMouseMove, false); + document.removeEventListener('mouseup', handleMouseUp, false); + document.removeEventListener('touchmove', handleTouchMove, false); + document.removeEventListener('touchend', handleTouchEnd, false); + }; + + const handleMouseDown = (event: React.MouseEvent) => { + onMouseDown?.(event); + beginTracking(); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (disabled) { + return; + } + + if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') { + const normalized = normalize(_value - step!, step!); + setValue(normalized); + onChangeEnd?.(normalized); + } + + if (event.key === 'ArrowRight' || event.key === 'ArrowUp') { + const normalized = normalize(_value + step!, step!); + setValue(normalized); + onChangeEnd?.(normalized); + } + + if (event.key === 'Home') { + setValue(0); + onChangeEnd?.(0); + } + + if (event.key === 'End') { + setValue(359); + onChangeEnd?.(359); + } + }; + + const marksItems = marks?.map((mark, index) => ( +
+ )); + + return ( + + {marksItems && marksItems.length > 0 &&
{marksItems}
} + + {withLabel && ( +
+ {typeof formatLabel === 'function' ? formatLabel(_value) : _value} +
+ )} +
+ + + ); +}); + +AngleSlider.displayName = '@mantine/core/AngleSlider'; +AngleSlider.classes = classes; diff --git a/packages/@mantine/core/src/components/AngleSlider/index.ts b/packages/@mantine/core/src/components/AngleSlider/index.ts new file mode 100644 index 00000000000..801382035c8 --- /dev/null +++ b/packages/@mantine/core/src/components/AngleSlider/index.ts @@ -0,0 +1,7 @@ +export { AngleSlider } from './AngleSlider'; +export type { + AngleSliderCssVariables, + AngleSliderFactory, + AngleSliderProps, + AngleSliderStylesNames, +} from './AngleSlider'; diff --git a/packages/@mantine/core/src/components/Drawer/Drawer.module.css b/packages/@mantine/core/src/components/Drawer/Drawer.module.css index bf563a2ee7c..f5429640693 100644 --- a/packages/@mantine/core/src/components/Drawer/Drawer.module.css +++ b/packages/@mantine/core/src/components/Drawer/Drawer.module.css @@ -19,6 +19,11 @@ max-width: calc(100% - var(--drawer-offset) * 2); max-height: calc(100% - var(--drawer-offset) * 2); overflow-y: auto; + + &[data-hidden] { + opacity: 0 !important; + pointer-events: none; + } } .inner { diff --git a/packages/@mantine/core/src/components/Drawer/Drawer.story.tsx b/packages/@mantine/core/src/components/Drawer/Drawer.story.tsx index 857b4480193..07c2f191225 100644 --- a/packages/@mantine/core/src/components/Drawer/Drawer.story.tsx +++ b/packages/@mantine/core/src/components/Drawer/Drawer.story.tsx @@ -1,5 +1,6 @@ import { useDisclosure } from '@mantine/hooks'; import { Button } from '../Button'; +import { useModalsStack } from '../Modal'; import { ScrollArea } from '../ScrollArea'; import { Tabs } from '../Tabs'; import { Drawer } from './Drawer'; @@ -166,3 +167,36 @@ export function AutosizeScrollarea() {
); } + +export function Stack() { + const stack = useModalsStack(['first', 'second', 'third']); + + return ( +
+ + + + First modal + {content} + + + + + Second modal + + + + + Third modal + + + +
+ ); +} diff --git a/packages/@mantine/core/src/components/Drawer/Drawer.tsx b/packages/@mantine/core/src/components/Drawer/Drawer.tsx index 045039f0084..2c32ca0d347 100644 --- a/packages/@mantine/core/src/components/Drawer/Drawer.tsx +++ b/packages/@mantine/core/src/components/Drawer/Drawer.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react'; import { factory, Factory, getDefaultZIndex, useProps } from '../../core'; import { ModalBaseCloseButtonProps, ModalBaseOverlayProps } from '../ModalBase'; import { DrawerBody } from './DrawerBody'; @@ -11,6 +12,7 @@ import { DrawerRootProps, DrawerRootStylesNames, } from './DrawerRoot'; +import { DrawerStack, useDrawerStackContext } from './DrawerStack'; import { DrawerTitle } from './DrawerTitle'; import classes from './Drawer.module.css'; @@ -35,6 +37,9 @@ export interface DrawerProps extends DrawerRootProps { /** Props passed down to the close button */ closeButtonProps?: ModalBaseCloseButtonProps; + + /** Id of the drawer in the `Drawer.Stack` */ + stackId?: string; } export type DrawerFactory = Factory<{ @@ -50,6 +55,7 @@ export type DrawerFactory = Factory<{ Header: typeof DrawerHeader; Title: typeof DrawerTitle; CloseButton: typeof DrawerCloseButton; + Stack: typeof DrawerStack; }; }>; @@ -74,15 +80,50 @@ export const Drawer = factory((_props, ref) => { withCloseButton, closeButtonProps, children, + opened, + stackId, + zIndex, ...others } = useProps('Drawer', defaultProps, _props); + const ctx = useDrawerStackContext(); const hasHeader = !!title || withCloseButton; + const stackProps = + ctx && stackId + ? { + closeOnEscape: ctx.currentId === stackId, + trapFocus: ctx.currentId === stackId, + zIndex: ctx.getZIndex(stackId), + } + : {}; + + const overlayVisible = + withOverlay === false ? false : stackId && ctx ? ctx.currentId === stackId : opened; + + useEffect(() => { + if (ctx && stackId) { + opened + ? ctx.addModal(stackId, zIndex || getDefaultZIndex('modal')) + : ctx.removeModal(stackId); + } + }, [opened, stackId, zIndex]); return ( - - {withOverlay && } - + + {withOverlay && ( + + )} + {hasHeader && ( {title && {title}} @@ -105,3 +146,4 @@ Drawer.Body = DrawerBody; Drawer.Header = DrawerHeader; Drawer.Title = DrawerTitle; Drawer.CloseButton = DrawerCloseButton; +Drawer.Stack = DrawerStack; diff --git a/packages/@mantine/core/src/components/Drawer/DrawerContent.tsx b/packages/@mantine/core/src/components/Drawer/DrawerContent.tsx index e94532f78c5..d95f08f16bd 100644 --- a/packages/@mantine/core/src/components/Drawer/DrawerContent.tsx +++ b/packages/@mantine/core/src/components/Drawer/DrawerContent.tsx @@ -7,7 +7,9 @@ export type DrawerContentStylesNames = 'content' | 'inner'; export interface DrawerContentProps extends ModalBaseContentProps, - CompoundStylesApiProps {} + CompoundStylesApiProps { + __hidden?: boolean; +} export type DrawerContentFactory = Factory<{ props: DrawerContentProps; @@ -20,7 +22,8 @@ const defaultProps: Partial = {}; export const DrawerContent = factory((_props, ref) => { const props = useProps('DrawerContent', defaultProps, _props); - const { classNames, className, style, styles, vars, children, radius, ...others } = props; + const { classNames, className, style, styles, vars, children, radius, __hidden, ...others } = + props; const ctx = useDrawerContext(); const Scroll: React.FC = ctx.scrollAreaComponent || NativeScrollArea; @@ -32,6 +35,7 @@ export const DrawerContent = factory((_props, ref) => { ref={ref} {...others} radius={radius || ctx.radius || 0} + data-hidden={__hidden || undefined} > {children} diff --git a/packages/@mantine/core/src/components/Drawer/DrawerStack.tsx b/packages/@mantine/core/src/components/Drawer/DrawerStack.tsx new file mode 100644 index 00000000000..d67652996db --- /dev/null +++ b/packages/@mantine/core/src/components/Drawer/DrawerStack.tsx @@ -0,0 +1,48 @@ +import { useState } from 'react'; +import { createOptionalContext, getDefaultZIndex } from '../../core'; + +interface DrawerStackContext { + stack: string[]; + addModal: (id: string, zIndex: number | string) => void; + removeModal: (id: string) => void; + getZIndex: (id: string) => string; + currentId: string; + maxZIndex: string | number; +} + +const [DrawerStackProvider, useDrawerStackContext] = createOptionalContext(); + +export { useDrawerStackContext }; + +interface DrawerStackProps { + children: React.ReactNode; +} + +export function DrawerStack({ children }: DrawerStackProps) { + const [stack, setStack] = useState([]); + const [maxZIndex, setMaxZIndex] = useState(getDefaultZIndex('modal')); + + return ( + { + setStack((current) => [...new Set([...current, id])]); + setMaxZIndex((current) => + typeof zIndex === 'number' && typeof current === 'number' + ? Math.max(current, zIndex) + : current + ); + }, + removeModal: (id) => setStack((current) => current.filter((currentId) => currentId !== id)), + getZIndex: (id) => `calc(${maxZIndex} + ${stack.indexOf(id)} + 1)`, + currentId: stack[stack.length - 1], + maxZIndex, + }} + > + {children} + + ); +} + +DrawerStack.displayName = '@mantine/core/DrawerStack'; diff --git a/packages/@mantine/core/src/components/Modal/Modal.module.css b/packages/@mantine/core/src/components/Modal/Modal.module.css index 9834a7f119b..3502b42474b 100644 --- a/packages/@mantine/core/src/components/Modal/Modal.module.css +++ b/packages/@mantine/core/src/components/Modal/Modal.module.css @@ -46,6 +46,11 @@ &[data-full-screen] { border-radius: 0; } + + &[data-hidden] { + opacity: 0 !important; + pointer-events: none; + } } .inner { diff --git a/packages/@mantine/core/src/components/Modal/Modal.story.tsx b/packages/@mantine/core/src/components/Modal/Modal.story.tsx index 4fe06045686..e1de48a762b 100644 --- a/packages/@mantine/core/src/components/Modal/Modal.story.tsx +++ b/packages/@mantine/core/src/components/Modal/Modal.story.tsx @@ -5,6 +5,7 @@ import { ScrollArea } from '../ScrollArea'; import { Select } from '../Select'; import { Tabs } from '../Tabs'; import { Modal } from './Modal'; +import { useModalsStack } from './use-modals-stack'; export default { title: 'Modal' }; @@ -37,6 +38,39 @@ export function Usage() { ); } +export function Stack() { + const stack = useModalsStack(['first', 'second', 'third']); + + return ( +
+ + + + First modal + {content} + + + + + Second modal + + + + + Third modal + + + +
+ ); +} + export function WithSelect() { const [opened, { open, close }] = useDisclosure(true); return ( diff --git a/packages/@mantine/core/src/components/Modal/Modal.tsx b/packages/@mantine/core/src/components/Modal/Modal.tsx index 76a4af3ab42..57582c21229 100644 --- a/packages/@mantine/core/src/components/Modal/Modal.tsx +++ b/packages/@mantine/core/src/components/Modal/Modal.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react'; import { factory, Factory, getDefaultZIndex, useProps } from '../../core'; import { ModalBaseCloseButtonProps, ModalBaseOverlayProps } from '../ModalBase'; import { ModalBody } from './ModalBody'; @@ -11,6 +12,7 @@ import { ModalRootProps, ModalRootStylesNames, } from './ModalRoot'; +import { ModalStack, useModalStackContext } from './ModalStack'; import { ModalTitle } from './ModalTitle'; import classes from './Modal.module.css'; @@ -37,6 +39,9 @@ export interface ModalProps extends ModalRootProps { /** Props passed down to the close button */ closeButtonProps?: ModalBaseCloseButtonProps; + + /** Id of the modal in the `Modal.Stack` */ + stackId?: string; } export type ModalFactory = Factory<{ @@ -52,6 +57,7 @@ export type ModalFactory = Factory<{ Header: typeof ModalHeader; Title: typeof ModalTitle; CloseButton: typeof ModalCloseButton; + Stack: typeof ModalStack; }; }>; @@ -78,15 +84,53 @@ export const Modal = factory((_props, ref) => { closeButtonProps, children, radius, + opened, + stackId, + zIndex, ...others } = useProps('Modal', defaultProps, _props); - + const ctx = useModalStackContext(); const hasHeader = !!title || withCloseButton; + const stackProps = + ctx && stackId + ? { + closeOnEscape: ctx.currentId === stackId, + trapFocus: ctx.currentId === stackId, + zIndex: ctx.getZIndex(stackId), + } + : {}; + + const overlayVisible = + withOverlay === false ? false : stackId && ctx ? ctx.currentId === stackId : opened; + + useEffect(() => { + if (ctx && stackId) { + opened + ? ctx.addModal(stackId, zIndex || getDefaultZIndex('modal')) + : ctx.removeModal(stackId); + } + }, [opened, stackId, zIndex]); return ( - - {withOverlay && } - + + {withOverlay && ( + + )} + {hasHeader && ( {title && {title}} @@ -109,3 +153,4 @@ Modal.Body = ModalBody; Modal.Header = ModalHeader; Modal.Title = ModalTitle; Modal.CloseButton = ModalCloseButton; +Modal.Stack = ModalStack; diff --git a/packages/@mantine/core/src/components/Modal/ModalContent.tsx b/packages/@mantine/core/src/components/Modal/ModalContent.tsx index de1819710de..cc014ce7dad 100644 --- a/packages/@mantine/core/src/components/Modal/ModalContent.tsx +++ b/packages/@mantine/core/src/components/Modal/ModalContent.tsx @@ -7,7 +7,9 @@ export type ModalContentStylesNames = 'content' | 'inner'; export interface ModalContentProps extends ModalBaseContentProps, - CompoundStylesApiProps {} + CompoundStylesApiProps { + __hidden?: boolean; +} export type ModalContentFactory = Factory<{ props: ModalContentProps; @@ -20,7 +22,7 @@ const defaultProps: Partial = {}; export const ModalContent = factory((_props, ref) => { const props = useProps('ModalContent', defaultProps, _props); - const { classNames, className, style, styles, vars, children, ...others } = props; + const { classNames, className, style, styles, vars, children, __hidden, ...others } = props; const ctx = useModalContext(); const Scroll: React.FC = ctx.scrollAreaComponent || NativeScrollArea; @@ -31,6 +33,7 @@ export const ModalContent = factory((_props, ref) => { innerProps={ctx.getStyles('inner', { className, style, styles, classNames })} data-full-screen={ctx.fullScreen || undefined} data-modal-content + data-hidden={__hidden || undefined} ref={ref} {...others} > diff --git a/packages/@mantine/core/src/components/Modal/ModalStack.tsx b/packages/@mantine/core/src/components/Modal/ModalStack.tsx new file mode 100644 index 00000000000..052afbba4e2 --- /dev/null +++ b/packages/@mantine/core/src/components/Modal/ModalStack.tsx @@ -0,0 +1,48 @@ +import { useState } from 'react'; +import { createOptionalContext, getDefaultZIndex } from '../../core'; + +interface ModalStackContext { + stack: string[]; + addModal: (id: string, zIndex: number | string) => void; + removeModal: (id: string) => void; + getZIndex: (id: string) => string; + currentId: string; + maxZIndex: string | number; +} + +const [ModalStackProvider, useModalStackContext] = createOptionalContext(); + +export { useModalStackContext }; + +interface ModalStackProps { + children: React.ReactNode; +} + +export function ModalStack({ children }: ModalStackProps) { + const [stack, setStack] = useState([]); + const [maxZIndex, setMaxZIndex] = useState(getDefaultZIndex('modal')); + + return ( + { + setStack((current) => [...new Set([...current, id])]); + setMaxZIndex((current) => + typeof zIndex === 'number' && typeof current === 'number' + ? Math.max(current, zIndex) + : current + ); + }, + removeModal: (id) => setStack((current) => current.filter((currentId) => currentId !== id)), + getZIndex: (id) => `calc(${maxZIndex} + ${stack.indexOf(id)} + 1)`, + currentId: stack[stack.length - 1], + maxZIndex, + }} + > + {children} + + ); +} + +ModalStack.displayName = '@mantine/core/ModalStack'; diff --git a/packages/@mantine/core/src/components/Modal/index.ts b/packages/@mantine/core/src/components/Modal/index.ts index 2615b479cc7..3f67937a8a5 100644 --- a/packages/@mantine/core/src/components/Modal/index.ts +++ b/packages/@mantine/core/src/components/Modal/index.ts @@ -6,6 +6,8 @@ export { ModalContent } from './ModalContent'; export { ModalHeader } from './ModalHeader'; export { ModalOverlay } from './ModalOverlay'; export { ModalTitle } from './ModalTitle'; +export { ModalStack } from './ModalStack'; +export { useModalsStack, useDrawersStack } from './use-modals-stack'; export type { ModalCssVariables, ModalFactory, ModalProps, ModalStylesNames } from './Modal'; export type { ModalRootProps } from './ModalRoot'; diff --git a/packages/@mantine/core/src/components/Modal/use-modals-stack.ts b/packages/@mantine/core/src/components/Modal/use-modals-stack.ts new file mode 100644 index 00000000000..1c0e8aac41b --- /dev/null +++ b/packages/@mantine/core/src/components/Modal/use-modals-stack.ts @@ -0,0 +1,48 @@ +import { useCallback, useState } from 'react'; + +interface ModalStackReturnType { + state: Record; + open: (id: T) => void; + close: (id: T) => void; + toggle: (id: T) => void; + closeAll: () => void; + register: (id: T) => { opened: boolean; onClose: () => void; stackId: T }; +} + +export function useModalsStack(modals: T[]): ModalStackReturnType { + const initialState = modals.reduce( + (acc, modal) => ({ ...acc, [modal]: false }), + {} as Record + ); + + const [state, setState] = useState(initialState); + + const open = useCallback((modal: T) => { + setState((current) => ({ ...current, [modal]: true })); + }, []); + + const close = useCallback( + (modal: T) => setState((current) => ({ ...current, [modal]: false })), + [] + ); + + const toggle = useCallback( + (modal: T) => setState((current) => ({ ...current, [modal]: !current[modal] })), + [] + ); + + const closeAll = useCallback(() => setState(initialState), []); + + const register = useCallback( + (modal: T) => ({ + opened: state[modal], + onClose: () => close(modal), + stackId: modal, + }), + [state] + ); + + return { state, open, close, closeAll, toggle, register }; +} + +export const useDrawersStack = useModalsStack; diff --git a/packages/@mantine/core/src/components/ModalBase/ModalBaseOverlay.tsx b/packages/@mantine/core/src/components/ModalBase/ModalBaseOverlay.tsx index 1dc1e2f2444..7080e0b174f 100644 --- a/packages/@mantine/core/src/components/ModalBase/ModalBaseOverlay.tsx +++ b/packages/@mantine/core/src/components/ModalBase/ModalBaseOverlay.tsx @@ -10,15 +10,22 @@ export interface ModalBaseOverlayProps ElementProps<'div', 'color'> { /** Props passed down to the `Transition` component */ transitionProps?: TransitionOverride; + + /** Determines whether the overlay should be visible. By default, has the same value as `opened` state. */ + visible?: boolean; } export const ModalBaseOverlay = forwardRef( - ({ onClick, transitionProps, style, ...others }, ref) => { + ({ onClick, transitionProps, style, visible, ...others }, ref) => { const ctx = useModalBaseContext(); const transition = useModalTransition(transitionProps); return ( - + {(transitionStyles) => ( - - + + {/* - + */}
); } diff --git a/packages/@mantine/core/src/components/Slider/Slider/Slider.tsx b/packages/@mantine/core/src/components/Slider/Slider/Slider.tsx index a0c9360b19d..9cdbb831160 100644 --- a/packages/@mantine/core/src/components/Slider/Slider/Slider.tsx +++ b/packages/@mantine/core/src/components/Slider/Slider/Slider.tsx @@ -6,6 +6,7 @@ import { ElementProps, factory, Factory, + findClosestNumber, getRadius, getSize, getThemeColor, @@ -104,6 +105,9 @@ export interface SliderProps /** Props passed down to the hidden input */ hiddenInputProps?: React.ComponentPropsWithoutRef<'input'>; + + /** Determines whether the selection should be only allowed from the given marks array, `false` by default */ + restrictToMarks?: boolean; } export type SliderFactory = Factory<{ @@ -170,6 +174,7 @@ export const Slider = factory((_props, ref) => { style, vars, hiddenInputProps, + restrictToMarks, ...others } = props; @@ -213,16 +218,33 @@ export const Slider = factory((_props, ref) => { step: step!, precision, }); - setValue(nextValue); + setValue( + restrictToMarks && marks?.length + ? findClosestNumber( + nextValue, + marks.map((mark) => mark.value) + ) + : nextValue + ); valueRef.current = nextValue; } }, - [disabled, min, max, step, precision, setValue] + [disabled, min, max, step, precision, setValue, marks, restrictToMarks] ); const { ref: container, active } = useMove( handleChange, - { onScrubEnd: () => onChangeEnd?.(valueRef.current) }, + { + onScrubEnd: () => + onChangeEnd?.( + restrictToMarks && marks?.length + ? findClosestNumber( + valueRef.current, + marks.map((mark) => mark.value) + ) + : valueRef.current + ), + }, dir ); diff --git a/packages/@mantine/core/src/components/Tree/Tree.story.tsx b/packages/@mantine/core/src/components/Tree/Tree.story.tsx index 618e45047ee..6ab03069a6d 100644 --- a/packages/@mantine/core/src/components/Tree/Tree.story.tsx +++ b/packages/@mantine/core/src/components/Tree/Tree.story.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { IconChevronDown } from '@tabler/icons-react'; import { Button } from '../Button'; import { Group } from '../Group'; @@ -58,7 +59,10 @@ export function Usage() { } export function Controller() { - const tree = useTree(); + const tree = useTree({ + onNodeCollapse: (value) => console.log('Node collapsed:', value), + onNodeExpand: (value) => console.log('Node expanded:', value), + }); return (
diff --git a/packages/@mantine/core/src/components/Tree/get-children-nodes-values/get-children-nodes-values.ts b/packages/@mantine/core/src/components/Tree/get-children-nodes-values/get-children-nodes-values.ts index 260398172c4..352944a5710 100644 --- a/packages/@mantine/core/src/components/Tree/get-children-nodes-values/get-children-nodes-values.ts +++ b/packages/@mantine/core/src/components/Tree/get-children-nodes-values/get-children-nodes-values.ts @@ -42,3 +42,15 @@ export function getChildrenNodesValues( return acc; } + +export function getAllChildrenNodes(data: TreeNodeData[]) { + return data.reduce((acc, node) => { + if (Array.isArray(node.children) && node.children.length > 0) { + acc.push(...getAllChildrenNodes(node.children)); + } else { + acc.push(node.value); + } + + return acc; + }, [] as string[]); +} diff --git a/packages/@mantine/core/src/components/Tree/index.ts b/packages/@mantine/core/src/components/Tree/index.ts index 223c0e88c2e..02f7549ff1d 100644 --- a/packages/@mantine/core/src/components/Tree/index.ts +++ b/packages/@mantine/core/src/components/Tree/index.ts @@ -1,5 +1,5 @@ export { Tree } from './Tree'; -export { useTree } from './use-tree'; +export { useTree, getTreeExpandedState } from './use-tree'; export type { TreeCssVariables, TreeFactory, diff --git a/packages/@mantine/core/src/components/Tree/use-tree.ts b/packages/@mantine/core/src/components/Tree/use-tree.ts index 136c712a0cd..816b180598a 100644 --- a/packages/@mantine/core/src/components/Tree/use-tree.ts +++ b/packages/@mantine/core/src/components/Tree/use-tree.ts @@ -3,14 +3,17 @@ import { CheckedNodeStatus, getAllCheckedNodes, } from './get-all-checked-nodes/get-all-checked-nodes'; -import { getChildrenNodesValues } from './get-children-nodes-values/get-children-nodes-values'; +import { + getAllChildrenNodes, + getChildrenNodesValues, +} from './get-children-nodes-values/get-children-nodes-values'; import { memoizedIsNodeChecked } from './is-node-checked/is-node-checked'; import { memoizedIsNodeIndeterminate } from './is-node-indeterminate/is-node-indeterminate'; import type { TreeNodeData } from './Tree'; export type TreeExpandedState = Record; -function getInitialExpandedState( +function getInitialTreeExpandedState( initialState: TreeExpandedState, data: TreeNodeData[], value: string | string[] | undefined, @@ -20,19 +23,33 @@ function getInitialExpandedState( acc[node.value] = node.value in initialState ? initialState[node.value] : node.value === value; if (Array.isArray(node.children)) { - getInitialExpandedState(initialState, node.children, value, acc); + getInitialTreeExpandedState(initialState, node.children, value, acc); } }); return acc; } +export function getTreeExpandedState(data: TreeNodeData[], expandedNodesValues: string[] | '*') { + const state = getInitialTreeExpandedState({}, data, []); + + if (expandedNodesValues === '*') { + return Object.keys(state).reduce((acc, key) => ({ ...acc, [key]: true }), {}); + } + + expandedNodesValues.forEach((node) => { + state[node] = true; + }); + + return state; +} + function getInitialCheckedState(initialState: string[], data: TreeNodeData[]) { const acc: string[] = []; initialState.forEach((node) => acc.push(...getChildrenNodesValues(node, data))); - return acc; + return Array.from(new Set(acc)); } export interface UseTreeInput { @@ -47,6 +64,12 @@ export interface UseTreeInput { /** Determines whether multiple node can be selected at a time */ multiple?: boolean; + + /** Called with the node value when it is expanded */ + onNodeExpand?: (value: string) => void; + + /** Called with the node value when it is collapsed */ + onNodeCollapse?: (value: string) => void; } export interface UseTreeReturnType { @@ -115,6 +138,15 @@ export interface UseTreeReturnType { /** Unchecks node with provided value */ uncheckNode: (value: string) => void; + /** Checks all nodes */ + checkAllNodes: () => void; + + /** Unchecks all nodes */ + uncheckAllNodes: () => void; + + /** Sets checked state */ + setCheckedState: React.Dispatch>; + /** Returns all checked nodes with status */ getCheckedNodes: () => CheckedNodeStatus[]; @@ -130,6 +162,8 @@ export function useTree({ initialCheckedState = [], initialExpandedState = {}, multiple = false, + onNodeCollapse, + onNodeExpand, }: UseTreeInput = {}): UseTreeReturnType { const [data, setData] = useState([]); const [expandedState, setExpandedState] = useState(initialExpandedState); @@ -140,24 +174,49 @@ export function useTree({ const initialize = useCallback( (_data: TreeNodeData[]) => { - setExpandedState((current) => getInitialExpandedState(current, _data, selectedState)); + setExpandedState((current) => getInitialTreeExpandedState(current, _data, selectedState)); setCheckedState((current) => getInitialCheckedState(current, _data)); setData(_data); }, [selectedState, checkedState] ); - const toggleExpanded = useCallback((value: string) => { - setExpandedState((current) => ({ ...current, [value]: !current[value] })); - }, []); + const toggleExpanded = useCallback( + (value: string) => { + setExpandedState((current) => { + const nextState = { ...current, [value]: !current[value] }; + nextState[value] ? onNodeExpand?.(value) : onNodeCollapse?.(value); + return nextState; + }); + }, + [onNodeCollapse, onNodeExpand] + ); - const collapse = useCallback((value: string) => { - setExpandedState((current) => ({ ...current, [value]: false })); - }, []); + const collapse = useCallback( + (value: string) => { + setExpandedState((current) => { + if (current[value] !== false) { + onNodeCollapse?.(value); + } - const expand = useCallback((value: string) => { - setExpandedState((current) => ({ ...current, [value]: true })); - }, []); + return { ...current, [value]: false }; + }); + }, + [onNodeCollapse] + ); + + const expand = useCallback( + (value: string) => { + setExpandedState((current) => { + if (current[value] !== true) { + onNodeExpand?.(value); + } + + return { ...current, [value]: true }; + }); + }, + [onNodeExpand] + ); const expandAllNodes = useCallback(() => { setExpandedState((current) => { @@ -239,6 +298,14 @@ export function useTree({ [data] ); + const checkAllNodes = useCallback(() => { + setCheckedState(() => getAllChildrenNodes(data)); + }, [data]); + + const uncheckAllNodes = useCallback(() => { + setCheckedState([]); + }, []); + const getCheckedNodes = () => getAllCheckedNodes(data, checkedState).result; const isNodeChecked = (value: string) => memoizedIsNodeChecked(value, data, checkedState); const isNodeIndeterminate = (value: string) => @@ -258,8 +325,12 @@ export function useTree({ expandAllNodes, collapseAllNodes, setExpandedState, + checkNode, uncheckNode, + checkAllNodes, + uncheckAllNodes, + setCheckedState, toggleSelected, select, diff --git a/packages/@mantine/core/src/components/index.ts b/packages/@mantine/core/src/components/index.ts index 8ee06b5d6b5..6b90a60210e 100644 --- a/packages/@mantine/core/src/components/index.ts +++ b/packages/@mantine/core/src/components/index.ts @@ -21,6 +21,7 @@ export * from './Accordion'; export * from './Affix'; export * from './Alert'; export * from './Anchor'; +export * from './AngleSlider'; export * from './AppShell'; export * from './AspectRatio'; export * from './Autocomplete'; diff --git a/packages/@mantine/core/src/core/utils/find-closest-number/find-closest-number.test.ts b/packages/@mantine/core/src/core/utils/find-closest-number/find-closest-number.test.ts new file mode 100644 index 00000000000..2ccbf22b154 --- /dev/null +++ b/packages/@mantine/core/src/core/utils/find-closest-number/find-closest-number.test.ts @@ -0,0 +1,33 @@ +import { findClosestNumber } from './find-closest-number'; + +describe('@mantine/core/find-closes-number', () => { + it('returns the exact number if it exists in the array', () => { + const result = findClosestNumber(5, [1, 3, 5, 7, 9]); + expect(result).toBe(5); + }); + + it('returns the closest number when the exact number does not exist', () => { + const result = findClosestNumber(6, [1, 3, 5, 7, 9]); + expect(result).toBe(5); + }); + + it('returns the smaller number when two numbers are equally close', () => { + const result = findClosestNumber(6, [1, 4, 8, 10]); + expect(result).toBe(4); + }); + + it('handles negative numbers correctly', () => { + const result = findClosestNumber(-2, [-10, -5, 0, 3, 5]); + expect(result).toBe(0); + }); + + it('returns the only number if the array contains just one number', () => { + const result = findClosestNumber(10, [7]); + expect(result).toBe(7); + }); + + it('returns value when the array is empty', () => { + const result = findClosestNumber(10, []); + expect(result).toBe(10); + }); +}); diff --git a/packages/@mantine/core/src/core/utils/find-closest-number/find-closest-number.ts b/packages/@mantine/core/src/core/utils/find-closest-number/find-closest-number.ts new file mode 100644 index 00000000000..5dcf92fb5b6 --- /dev/null +++ b/packages/@mantine/core/src/core/utils/find-closest-number/find-closest-number.ts @@ -0,0 +1,9 @@ +export function findClosestNumber(value: number, numbers: number[]): number { + if (numbers.length === 0) { + return value; + } + + return numbers.reduce((prev, curr) => + Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev + ); +} diff --git a/packages/@mantine/core/src/core/utils/index.ts b/packages/@mantine/core/src/core/utils/index.ts index 079156598ee..d78f9e1f108 100644 --- a/packages/@mantine/core/src/core/utils/index.ts +++ b/packages/@mantine/core/src/core/utils/index.ts @@ -30,4 +30,5 @@ export { useHovered } from './use-hovered/use-hovered'; export { createUseExternalEvents } from './create-use-external-events/create-use-external-events'; export { getEnv } from './get-env/get-env'; export { memoize } from './memoize/memoize'; +export { findClosestNumber } from './find-closest-number/find-closest-number'; export { getRefProp } from './get-ref-prop/get-ref-prop'; diff --git a/packages/@mantine/dates/package.json b/packages/@mantine/dates/package.json index 81bef14a53a..40549d37824 100644 --- a/packages/@mantine/dates/package.json +++ b/packages/@mantine/dates/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/dates", - "version": "7.13.5", + "version": "7.14.0", "description": "Calendars, date and time pickers based on Mantine components", "homepage": "https://mantine.dev/dates/getting-started/", "license": "MIT", @@ -45,8 +45,8 @@ "directory": "packages/@mantine/dates" }, "peerDependencies": { - "@mantine/core": "7.13.5", - "@mantine/hooks": "7.13.5", + "@mantine/core": "7.14.0", + "@mantine/hooks": "7.14.0", "dayjs": ">=1.0.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" diff --git a/packages/@mantine/dropzone/package.json b/packages/@mantine/dropzone/package.json index ea1c42af111..69cacd69c69 100644 --- a/packages/@mantine/dropzone/package.json +++ b/packages/@mantine/dropzone/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/dropzone", - "version": "7.13.5", + "version": "7.14.0", "description": "Dropzone component built with Mantine theme and components", "homepage": "https://mantine.dev/x/dropzone/", "license": "MIT", @@ -44,8 +44,8 @@ "directory": "packages/@mantine/dropzone" }, "peerDependencies": { - "@mantine/core": "7.13.5", - "@mantine/hooks": "7.13.5", + "@mantine/core": "7.14.0", + "@mantine/hooks": "7.14.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" }, diff --git a/packages/@mantine/emotion/package.json b/packages/@mantine/emotion/package.json index 98793d7bb20..de1b79f90eb 100644 --- a/packages/@mantine/emotion/package.json +++ b/packages/@mantine/emotion/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/emotion", - "version": "7.13.5", + "version": "7.14.0", "description": "Emotion bindings for Mantine", "homepage": "https://mantine.dev/", "license": "MIT", @@ -35,8 +35,8 @@ "@emotion/react": "^11.11.4", "@emotion/serialize": "^1.1.4", "@emotion/utils": "^1.2.1", - "@mantine/core": "7.13.5", - "@mantine/hooks": "7.13.5", + "@mantine/core": "7.14.0", + "@mantine/hooks": "7.14.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" }, diff --git a/packages/@mantine/form/package.json b/packages/@mantine/form/package.json index 144665d9b7b..d750dca3b42 100644 --- a/packages/@mantine/form/package.json +++ b/packages/@mantine/form/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/form", - "version": "7.13.5", + "version": "7.14.0", "description": "Mantine form management library", "homepage": "https://mantine.dev", "license": "MIT", diff --git a/packages/@mantine/form/src/hooks/use-form-list/use-form-list.ts b/packages/@mantine/form/src/hooks/use-form-list/use-form-list.ts index 1392252f44a..a34defbffff 100644 --- a/packages/@mantine/form/src/hooks/use-form-list/use-form-list.ts +++ b/packages/@mantine/form/src/hooks/use-form-list/use-form-list.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { changeErrorIndices, reorderErrors } from '../../lists'; -import { insertPath, removePath, reorderPath } from '../../paths'; -import { InsertListItem, RemoveListItem, ReorderListItem } from '../../types'; +import { insertPath, removePath, reorderPath, replacePath } from '../../paths'; +import { InsertListItem, RemoveListItem, ReorderListItem, ReplaceListItem } from '../../types'; import type { $FormErrors } from '../use-form-errors/use-form-errors'; import type { $FormStatus } from '../use-form-status/use-form-status'; import type { $FormValues } from '../use-form-values/use-form-values'; @@ -44,5 +44,13 @@ export function useFormList>({ }); }, []); - return { reorderListItem, removeListItem, insertListItem }; + const replaceListItem: ReplaceListItem = useCallback((path, index, item) => { + $status.clearFieldDirty(path); + $values.setValues({ + values: replacePath(path, item, index, $values.refValues.current), + updateState: true, + }); + }, []); + + return { reorderListItem, removeListItem, insertListItem, replaceListItem }; } diff --git a/packages/@mantine/form/src/paths/index.ts b/packages/@mantine/form/src/paths/index.ts index 57391835606..155a9cf7437 100644 --- a/packages/@mantine/form/src/paths/index.ts +++ b/packages/@mantine/form/src/paths/index.ts @@ -4,3 +4,4 @@ export { reorderPath } from './reorder-path'; export { insertPath } from './insert-path'; export { removePath } from './remove-path'; export { getDataPath } from './get-data-path'; +export { replacePath } from './replace-path'; diff --git a/packages/@mantine/form/src/paths/replace-path.test.ts b/packages/@mantine/form/src/paths/replace-path.test.ts new file mode 100644 index 00000000000..04cd3180a6b --- /dev/null +++ b/packages/@mantine/form/src/paths/replace-path.test.ts @@ -0,0 +1,17 @@ +import { replacePath } from './replace-path'; + +describe('@mantine/form/replace-path', () => { + it('replaces item at specified index in array', () => { + expect(replacePath('field', 'new', 1, { field: ['old', 'old'] })).toEqual({ + field: ['old', 'new'], + }); + }); + + it('does nothing if path is not an array', () => { + expect(replacePath('field', 'new', 1, { field: 'old' })).toEqual({ field: 'old' }); + }); + + it('does nothing if index is out of bounds', () => { + expect(replacePath('field', 'new', 2, { field: ['old'] })).toEqual({ field: ['old'] }); + }); +}); diff --git a/packages/@mantine/form/src/paths/replace-path.ts b/packages/@mantine/form/src/paths/replace-path.ts new file mode 100644 index 00000000000..a20fc53291d --- /dev/null +++ b/packages/@mantine/form/src/paths/replace-path.ts @@ -0,0 +1,19 @@ +import { getPath } from './get-path'; +import { setPath } from './set-path'; + +export function replacePath(path: unknown, item: unknown, index: number, values: T) { + const currentValue = getPath(path, values); + + if (!Array.isArray(currentValue)) { + return values; + } + + if (currentValue.length <= index) { + return values; + } + + const cloned = [...currentValue]; + cloned[index] = item; + + return setPath(path, cloned, values); +} diff --git a/packages/@mantine/form/src/tests/use-form/replaceListItem.test.ts b/packages/@mantine/form/src/tests/use-form/replaceListItem.test.ts new file mode 100644 index 00000000000..6532bd8f36f --- /dev/null +++ b/packages/@mantine/form/src/tests/use-form/replaceListItem.test.ts @@ -0,0 +1,22 @@ +import { act, renderHook } from '@testing-library/react'; +import { FormMode } from '../../types'; +import { useForm } from '../../use-form'; + +function tests(mode: FormMode) { + it('replaces items at given list', () => { + const hook = renderHook(() => + useForm({ mode, initialValues: { a: [{ b: 1 }, { b: 2 }, { b: 3 }] } }) + ); + + act(() => hook.result.current.replaceListItem('a', 0, { b: 100 })); + expect(hook.result.current.getValues()).toStrictEqual({ a: [{ b: 100 }, { b: 2 }, { b: 3 }] }); + }); +} + +describe('@mantine/form/replaceListItem-controlled', () => { + tests('controlled'); +}); + +describe('@mantine/form/replaceListItem-uncontrolled', () => { + tests('uncontrolled'); +}); diff --git a/packages/@mantine/form/src/types.ts b/packages/@mantine/form/src/types.ts index 1dc69c6e669..b8a8568e786 100644 --- a/packages/@mantine/form/src/types.ts +++ b/packages/@mantine/form/src/types.ts @@ -145,6 +145,12 @@ export type InsertListItem = >( index?: number ) => void; +export type ReplaceListItem = >( + path: Field, + index: number, + item: unknown +) => void; + export type RemoveListItem = >( path: Field, index: number @@ -222,6 +228,7 @@ export interface UseFormReturnType< validateField: ValidateField; reorderListItem: ReorderListItem; removeListItem: RemoveListItem; + replaceListItem: ReplaceListItem; insertListItem: InsertListItem; getInputProps: GetInputProps; onSubmit: OnSubmit; diff --git a/packages/@mantine/form/src/use-form.ts b/packages/@mantine/form/src/use-form.ts index 4f5e5f93b70..4d9e2b4b678 100644 --- a/packages/@mantine/form/src/use-form.ts +++ b/packages/@mantine/form/src/use-form.ts @@ -265,6 +265,7 @@ export function useForm< reorderListItem: $list.reorderListItem, insertListItem: $list.insertListItem, removeListItem: $list.removeListItem, + replaceListItem: $list.replaceListItem, reset, validate, diff --git a/packages/@mantine/hooks/package.json b/packages/@mantine/hooks/package.json index 70703e8b980..daaa2f07dba 100644 --- a/packages/@mantine/hooks/package.json +++ b/packages/@mantine/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/hooks", - "version": "7.13.5", + "version": "7.14.0", "description": "A collection of 50+ hooks for state and UI management", "homepage": "https://mantine.dev", "license": "MIT", diff --git a/packages/@mantine/modals/package.json b/packages/@mantine/modals/package.json index daa0aed5d8a..716f2f228a6 100644 --- a/packages/@mantine/modals/package.json +++ b/packages/@mantine/modals/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/modals", - "version": "7.13.5", + "version": "7.14.0", "description": "Modals manager based on Mantine components", "homepage": "https://mantine.dev/x/modals/", "license": "MIT", @@ -39,8 +39,8 @@ "directory": "packages/@mantine/modals" }, "peerDependencies": { - "@mantine/core": "7.13.5", - "@mantine/hooks": "7.13.5", + "@mantine/core": "7.14.0", + "@mantine/hooks": "7.14.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" }, diff --git a/packages/@mantine/notifications/package.json b/packages/@mantine/notifications/package.json index 600c62ba174..85fc23fcc99 100644 --- a/packages/@mantine/notifications/package.json +++ b/packages/@mantine/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/notifications", - "version": "7.13.5", + "version": "7.14.0", "description": "Mantine notifications system", "homepage": "https://mantine.dev", "license": "MIT", @@ -44,13 +44,13 @@ "directory": "packages/@mantine/notifications" }, "peerDependencies": { - "@mantine/core": "7.13.5", - "@mantine/hooks": "7.13.5", + "@mantine/core": "7.14.0", + "@mantine/hooks": "7.14.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" }, "dependencies": { - "@mantine/store": "7.13.5", + "@mantine/store": "7.14.0", "react-transition-group": "4.4.5" }, "devDependencies": { diff --git a/packages/@mantine/nprogress/package.json b/packages/@mantine/nprogress/package.json index 760c58a8ed7..0c7c19c1b38 100644 --- a/packages/@mantine/nprogress/package.json +++ b/packages/@mantine/nprogress/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/nprogress", - "version": "7.13.5", + "version": "7.14.0", "description": "Navigation progress bar", "homepage": "https://mantine.dev/x/nprogress/", "license": "MIT", @@ -43,13 +43,13 @@ "directory": "packages/@mantine/nprogress" }, "peerDependencies": { - "@mantine/core": "7.13.5", - "@mantine/hooks": "7.13.5", + "@mantine/core": "7.14.0", + "@mantine/hooks": "7.14.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" }, "dependencies": { - "@mantine/store": "7.13.5" + "@mantine/store": "7.14.0" }, "devDependencies": { "@mantine-tests/core": "workspace:*", diff --git a/packages/@mantine/spotlight/package.json b/packages/@mantine/spotlight/package.json index f6b4340d280..6ae7374d848 100644 --- a/packages/@mantine/spotlight/package.json +++ b/packages/@mantine/spotlight/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/spotlight", - "version": "7.13.5", + "version": "7.14.0", "description": "Command center components for react and Mantine", "homepage": "https://mantine.dev", "license": "MIT", @@ -41,13 +41,13 @@ "directory": "packages/@mantine/spotlight" }, "peerDependencies": { - "@mantine/core": "7.13.5", - "@mantine/hooks": "7.13.5", + "@mantine/core": "7.14.0", + "@mantine/hooks": "7.14.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" }, "dependencies": { - "@mantine/store": "7.13.5" + "@mantine/store": "7.14.0" }, "devDependencies": { "@mantine-tests/core": "workspace:*", diff --git a/packages/@mantine/store/package.json b/packages/@mantine/store/package.json index 55e145d0ea9..8ef61fafca8 100644 --- a/packages/@mantine/store/package.json +++ b/packages/@mantine/store/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/store", - "version": "7.13.5", + "version": "7.14.0", "description": "A library to sync external React state updates", "homepage": "https://mantine.dev", "license": "MIT", diff --git a/packages/@mantine/tiptap/package.json b/packages/@mantine/tiptap/package.json index fe732489a4b..440eacfb59f 100644 --- a/packages/@mantine/tiptap/package.json +++ b/packages/@mantine/tiptap/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/tiptap", - "version": "7.13.5", + "version": "7.14.0", "description": "Rich text editor based on tiptap", "homepage": "https://mantine.dev/x/tiptap", "license": "MIT", @@ -44,8 +44,8 @@ "directory": "packages/@mantine/tiptap" }, "peerDependencies": { - "@mantine/core": "7.13.5", - "@mantine/hooks": "7.13.5", + "@mantine/core": "7.14.0", + "@mantine/hooks": "7.14.0", "@tiptap/extension-link": ">=2.1.12", "@tiptap/react": ">=2.1.12", "react": "^18.x || ^19.x", diff --git a/packages/@mantine/vanilla-extract/package.json b/packages/@mantine/vanilla-extract/package.json index de4da39aa69..23b5c797dff 100644 --- a/packages/@mantine/vanilla-extract/package.json +++ b/packages/@mantine/vanilla-extract/package.json @@ -1,6 +1,6 @@ { "name": "@mantine/vanilla-extract", - "version": "7.13.5", + "version": "7.14.0", "description": "Vanilla Extract integration for Mantine theme", "homepage": "https://mantine.dev", "license": "MIT", @@ -35,6 +35,6 @@ "directory": "packages/@mantine/vanilla-extract" }, "peerDependencies": { - "@mantine/core": "7.13.5" + "@mantine/core": "7.14.0" } } diff --git a/packages/@mantinex/mantine-meta/src/versions.ts b/packages/@mantinex/mantine-meta/src/versions.ts index e79179364a8..9ab07f14072 100644 --- a/packages/@mantinex/mantine-meta/src/versions.ts +++ b/packages/@mantinex/mantine-meta/src/versions.ts @@ -12,6 +12,13 @@ export interface Version { } export const allVersions = [ + { + version: '7.14.0', + date: 'November 12, 2024', + github: 'https://github.com/mantinedev/mantine/releases/tag/7.14.0', + link: 'https://mantine.dev/changelog/7-14-0', + patches: [], + }, { version: '7.13.0', date: 'September 26, 2024', @@ -26,6 +33,18 @@ export const allVersions = [ version: '7.13.2', date: 'October 3, 2024', }, + { + version: '7.13.3', + date: 'October 17, 2024', + }, + { + version: '7.13.4', + date: 'October 23, 2024', + }, + { + version: '7.13.5', + date: 'November 8, 2024', + }, ], }, { diff --git a/packages/@mantinex/shiki/src/themes.ts b/packages/@mantinex/shiki/src/themes.ts index 4ea1b9b236b..bdcfbaa6279 100644 --- a/packages/@mantinex/shiki/src/themes.ts +++ b/packages/@mantinex/shiki/src/themes.ts @@ -583,600 +583,657 @@ export const light = { }; export const dark = { + type: 'dark', colors: { - 'activityBar.activeBorder': '#f78166', - 'activityBar.background': '#0d1117', - 'activityBar.border': '#30363d', - 'activityBar.foreground': '#e6edf3', - 'activityBar.inactiveForeground': '#7d8590', - 'activityBarBadge.background': '#1f6feb', - 'activityBarBadge.foreground': '#ffffff', - 'badge.background': '#1f6feb', - 'badge.foreground': '#ffffff', - 'breadcrumb.activeSelectionForeground': '#7d8590', - 'breadcrumb.focusForeground': '#e6edf3', - 'breadcrumb.foreground': '#7d8590', - 'breadcrumbPicker.background': '#161b22', - 'button.background': '#238636', - 'button.foreground': '#ffffff', - 'button.hoverBackground': '#2ea043', - 'button.secondaryBackground': '#282e33', - 'button.secondaryForeground': '#c9d1d9', - 'button.secondaryHoverBackground': '#30363d', - 'checkbox.background': '#161b22', - 'checkbox.border': '#30363d', - 'debugConsole.errorForeground': '#ffa198', - 'debugConsole.infoForeground': '#8b949e', - 'debugConsole.sourceForeground': '#e3b341', - 'debugConsole.warningForeground': '#d29922', - 'debugConsoleInputIcon.foreground': '#bc8cff', - 'debugIcon.breakpointForeground': '#f85149', - 'debugTokenExpression.boolean': '#56d364', - 'debugTokenExpression.error': '#ffa198', - 'debugTokenExpression.name': '#79c0ff', - 'debugTokenExpression.number': '#56d364', - 'debugTokenExpression.string': '#a5d6ff', - 'debugTokenExpression.value': '#a5d6ff', - 'debugToolBar.background': '#161b22', - descriptionForeground: '#7d8590', - 'diffEditor.insertedLineBackground': '#23863626', - 'diffEditor.insertedTextBackground': '#3fb9504d', - 'diffEditor.removedLineBackground': '#da363326', - 'diffEditor.removedTextBackground': '#ff7b724d', - 'dropdown.background': '#161b22', - 'dropdown.border': '#30363d', - 'dropdown.foreground': '#e6edf3', - 'dropdown.listBackground': '#161b22', - 'editor.background': '#0d1117', - 'editor.findMatchBackground': '#9e6a03', - 'editor.findMatchHighlightBackground': '#f2cc6080', - 'editor.focusedStackFrameHighlightBackground': '#2ea04366', - 'editor.foldBackground': '#6e76811a', - 'editor.foreground': '#e6edf3', - 'editor.lineHighlightBackground': '#6e76811a', - 'editor.linkedEditingBackground': '#2f81f712', - 'editor.selectionHighlightBackground': '#3fb95040', - 'editor.stackFrameHighlightBackground': '#bb800966', - 'editor.wordHighlightBackground': '#6e768180', - 'editor.wordHighlightBorder': '#6e768199', - 'editor.wordHighlightStrongBackground': '#6e76814d', - 'editor.wordHighlightStrongBorder': '#6e768199', - 'editorBracketHighlight.foreground1': '#79c0ff', - 'editorBracketHighlight.foreground2': '#56d364', - 'editorBracketHighlight.foreground3': '#e3b341', - 'editorBracketHighlight.foreground4': '#ffa198', - 'editorBracketHighlight.foreground5': '#ff9bce', - 'editorBracketHighlight.foreground6': '#d2a8ff', - 'editorBracketHighlight.unexpectedBracket.foreground': '#7d8590', - 'editorBracketMatch.background': '#3fb95040', - 'editorBracketMatch.border': '#3fb95099', - 'editorCursor.foreground': '#2f81f7', - 'editorGroup.border': '#30363d', - 'editorGroupHeader.tabsBackground': '#010409', - 'editorGroupHeader.tabsBorder': '#30363d', - 'editorGutter.addedBackground': '#2ea04366', - 'editorGutter.deletedBackground': '#f8514966', - 'editorGutter.modifiedBackground': '#bb800966', - 'editorIndentGuide.activeBackground': '#e6edf33d', - 'editorIndentGuide.background': '#e6edf31f', - 'editorInlayHint.background': '#8b949e33', - 'editorInlayHint.foreground': '#7d8590', - 'editorInlayHint.paramBackground': '#8b949e33', - 'editorInlayHint.paramForeground': '#7d8590', - 'editorInlayHint.typeBackground': '#8b949e33', - 'editorInlayHint.typeForeground': '#7d8590', - 'editorLineNumber.activeForeground': '#e6edf3', - 'editorLineNumber.foreground': '#6e7681', - 'editorOverviewRuler.border': '#010409', - 'editorWhitespace.foreground': '#484f58', - 'editorWidget.background': '#161b22', - errorForeground: '#f85149', - focusBorder: '#1f6feb', - foreground: '#e6edf3', - 'gitDecoration.addedResourceForeground': '#3fb950', - 'gitDecoration.conflictingResourceForeground': '#db6d28', - 'gitDecoration.deletedResourceForeground': '#f85149', - 'gitDecoration.ignoredResourceForeground': '#6e7681', - 'gitDecoration.modifiedResourceForeground': '#d29922', - 'gitDecoration.submoduleResourceForeground': '#7d8590', - 'gitDecoration.untrackedResourceForeground': '#3fb950', - 'icon.foreground': '#7d8590', - 'input.background': '#0d1117', - 'input.border': '#30363d', - 'input.foreground': '#e6edf3', - 'input.placeholderForeground': '#6e7681', - 'keybindingLabel.foreground': '#e6edf3', - 'list.activeSelectionBackground': '#6e768166', - 'list.activeSelectionForeground': '#e6edf3', - 'list.focusBackground': '#388bfd26', - 'list.focusForeground': '#e6edf3', - 'list.highlightForeground': '#2f81f7', - 'list.hoverBackground': '#6e76811a', - 'list.hoverForeground': '#e6edf3', - 'list.inactiveFocusBackground': '#388bfd26', - 'list.inactiveSelectionBackground': '#6e768166', - 'list.inactiveSelectionForeground': '#e6edf3', - 'minimapSlider.activeBackground': '#8b949e47', - 'minimapSlider.background': '#8b949e33', - 'minimapSlider.hoverBackground': '#8b949e3d', - 'notificationCenterHeader.background': '#161b22', - 'notificationCenterHeader.foreground': '#7d8590', - 'notifications.background': '#161b22', - 'notifications.border': '#30363d', - 'notifications.foreground': '#e6edf3', - 'notificationsErrorIcon.foreground': '#f85149', - 'notificationsInfoIcon.foreground': '#2f81f7', - 'notificationsWarningIcon.foreground': '#d29922', - 'panel.background': '#010409', - 'panel.border': '#30363d', - 'panelInput.border': '#30363d', - 'panelTitle.activeBorder': '#f78166', - 'panelTitle.activeForeground': '#e6edf3', - 'panelTitle.inactiveForeground': '#7d8590', - 'peekViewEditor.background': '#6e76811a', - 'peekViewEditor.matchHighlightBackground': '#bb800966', - 'peekViewResult.background': '#0d1117', - 'peekViewResult.matchHighlightBackground': '#bb800966', - 'pickerGroup.border': '#30363d', - 'pickerGroup.foreground': '#7d8590', - 'progressBar.background': '#1f6feb', - 'quickInput.background': '#161b22', - 'quickInput.foreground': '#e6edf3', - 'scrollbar.shadow': '#484f5833', - 'scrollbarSlider.activeBackground': '#8b949e47', - 'scrollbarSlider.background': '#8b949e33', - 'scrollbarSlider.hoverBackground': '#8b949e3d', - 'settings.headerForeground': '#e6edf3', - 'settings.modifiedItemIndicator': '#bb800966', - 'sideBar.background': '#010409', - 'sideBar.border': '#30363d', - 'sideBar.foreground': '#e6edf3', - 'sideBarSectionHeader.background': '#010409', - 'sideBarSectionHeader.border': '#30363d', - 'sideBarSectionHeader.foreground': '#e6edf3', - 'sideBarTitle.foreground': '#e6edf3', - 'statusBar.background': '#0d1117', - 'statusBar.border': '#30363d', - 'statusBar.debuggingBackground': '#da3633', - 'statusBar.debuggingForeground': '#ffffff', - 'statusBar.focusBorder': '#1f6feb80', - 'statusBar.foreground': '#7d8590', - 'statusBar.noFolderBackground': '#0d1117', - 'statusBarItem.activeBackground': '#e6edf31f', - 'statusBarItem.focusBorder': '#1f6feb', - 'statusBarItem.hoverBackground': '#e6edf314', - 'statusBarItem.prominentBackground': '#6e768166', - 'statusBarItem.remoteBackground': '#30363d', - 'statusBarItem.remoteForeground': '#e6edf3', - 'symbolIcon.arrayForeground': '#f0883e', - 'symbolIcon.booleanForeground': '#58a6ff', - 'symbolIcon.classForeground': '#f0883e', - 'symbolIcon.colorForeground': '#79c0ff', - 'symbolIcon.constantForeground': [ - '#aff5b4', - '#7ee787', - '#56d364', - '#3fb950', - '#2ea043', - '#238636', - '#196c2e', - '#0f5323', - '#033a16', - '#04260f', - ], - 'symbolIcon.constructorForeground': '#d2a8ff', - 'symbolIcon.enumeratorForeground': '#f0883e', - 'symbolIcon.enumeratorMemberForeground': '#58a6ff', - 'symbolIcon.eventForeground': '#6e7681', - 'symbolIcon.fieldForeground': '#f0883e', - 'symbolIcon.fileForeground': '#d29922', - 'symbolIcon.folderForeground': '#d29922', - 'symbolIcon.functionForeground': '#bc8cff', - 'symbolIcon.interfaceForeground': '#f0883e', - 'symbolIcon.keyForeground': '#58a6ff', - 'symbolIcon.keywordForeground': '#ff7b72', - 'symbolIcon.methodForeground': '#bc8cff', - 'symbolIcon.moduleForeground': '#ff7b72', - 'symbolIcon.namespaceForeground': '#ff7b72', - 'symbolIcon.nullForeground': '#58a6ff', - 'symbolIcon.numberForeground': '#3fb950', - 'symbolIcon.objectForeground': '#f0883e', - 'symbolIcon.operatorForeground': '#79c0ff', - 'symbolIcon.packageForeground': '#f0883e', - 'symbolIcon.propertyForeground': '#f0883e', - 'symbolIcon.referenceForeground': '#58a6ff', - 'symbolIcon.snippetForeground': '#58a6ff', - 'symbolIcon.stringForeground': '#79c0ff', - 'symbolIcon.structForeground': '#f0883e', - 'symbolIcon.textForeground': '#79c0ff', - 'symbolIcon.typeParameterForeground': '#79c0ff', - 'symbolIcon.unitForeground': '#58a6ff', - 'symbolIcon.variableForeground': '#f0883e', - 'tab.activeBackground': '#0d1117', - 'tab.activeBorder': '#0d1117', - 'tab.activeBorderTop': '#f78166', - 'tab.activeForeground': '#e6edf3', - 'tab.border': '#30363d', - 'tab.hoverBackground': '#0d1117', - 'tab.inactiveBackground': '#010409', - 'tab.inactiveForeground': '#7d8590', - 'tab.unfocusedActiveBorder': '#0d1117', - 'tab.unfocusedActiveBorderTop': '#30363d', - 'tab.unfocusedHoverBackground': '#6e76811a', - 'terminal.ansiBlack': '#484f58', - 'terminal.ansiBlue': '#58a6ff', - 'terminal.ansiBrightBlack': '#6e7681', - 'terminal.ansiBrightBlue': '#79c0ff', - 'terminal.ansiBrightCyan': '#56d4dd', - 'terminal.ansiBrightGreen': '#56d364', - 'terminal.ansiBrightMagenta': '#d2a8ff', - 'terminal.ansiBrightRed': '#ffa198', - 'terminal.ansiBrightWhite': '#ffffff', - 'terminal.ansiBrightYellow': '#e3b341', - 'terminal.ansiCyan': '#39c5cf', - 'terminal.ansiGreen': '#3fb950', - 'terminal.ansiMagenta': '#bc8cff', - 'terminal.ansiRed': '#ff7b72', - 'terminal.ansiWhite': '#b1bac4', - 'terminal.ansiYellow': '#d29922', - 'terminal.foreground': '#e6edf3', - 'textBlockQuote.background': '#010409', - 'textBlockQuote.border': '#30363d', - 'textCodeBlock.background': '#6e768166', - 'textLink.activeForeground': '#2f81f7', - 'textLink.foreground': '#2f81f7', - 'textPreformat.foreground': '#7d8590', - 'textSeparator.foreground': '#21262d', - 'titleBar.activeBackground': '#0d1117', - 'titleBar.activeForeground': '#7d8590', - 'titleBar.border': '#30363d', - 'titleBar.inactiveBackground': '#010409', - 'titleBar.inactiveForeground': '#7d8590', - 'tree.indentGuidesStroke': '#21262d', - 'welcomePage.buttonBackground': '#21262d', - 'welcomePage.buttonHoverBackground': '#30363d', + 'dropdown.background': '#525252', + 'list.activeSelectionBackground': '#707070', + 'quickInputList.focusBackground': '#707070', + 'list.inactiveSelectionBackground': '#4e4e4e', + 'list.hoverBackground': '#444444', + 'list.highlightForeground': '#e58520', + 'button.background': '#565656', + 'editor.background': '#1e1e1e', + 'editor.foreground': '#c5c8c6', + 'editor.selectionBackground': '#676b7180', + 'minimap.selectionHighlight': '#676b7180', + 'editor.selectionHighlightBackground': '#575b6180', + 'editor.lineHighlightBackground': '#303030', + 'editorLineNumber.activeForeground': '#949494', + 'editor.wordHighlightBackground': '#4747a180', + 'editor.wordHighlightStrongBackground': '#6767ce80', + 'editorCursor.foreground': '#c07020', + 'editorWhitespace.foreground': '#505037', + 'editorIndentGuide.background': '#505037', + 'editorIndentGuide.activeBackground': '#707057', + 'editorGroupHeader.tabsBackground': '#282828', + 'tab.inactiveBackground': '#404040', + 'tab.border': '#303030', + 'tab.inactiveForeground': '#d8d8d8', + 'tab.lastPinnedBorder': '#505050', + 'peekView.border': '#3655b5', + 'panelTitle.activeForeground': '#ffffff', + 'statusBar.background': '#505050', + 'statusBar.debuggingBackground': '#505050', + 'statusBar.noFolderBackground': '#505050', + 'titleBar.activeBackground': '#505050', + 'statusBarItem.remoteBackground': '#3655b5', + 'ports.iconRunningProcessForeground': '#CCCCCC', + 'activityBar.background': '#353535', + 'activityBar.foreground': '#ffffff', + 'activityBarBadge.background': '#3655b5', + 'sideBar.background': '#272727', + 'sideBarSectionHeader.background': '#505050', + 'menu.background': '#272727', + 'menu.foreground': '#CCCCCC', + 'pickerGroup.foreground': '#b0b0b0', + 'inputOption.activeBorder': '#3655b5', + focusBorder: '#3655b5', + 'terminal.ansiBlack': '#1e1e1e', + 'terminal.ansiRed': '#C4265E', // the bright color with ~75% transparent on the background + 'terminal.ansiGreen': '#86B42B', + 'terminal.ansiYellow': '#B3B42B', + 'terminal.ansiBlue': '#6A7EC8', + 'terminal.ansiMagenta': '#8C6BC8', + 'terminal.ansiCyan': '#56ADBC', + 'terminal.ansiWhite': '#e3e3dd', + 'terminal.ansiBrightBlack': '#666666', + 'terminal.ansiBrightRed': '#f92672', + 'terminal.ansiBrightGreen': '#A6E22E', + 'terminal.ansiBrightYellow': '#e2e22e', // hue shifted #A6E22E + 'terminal.ansiBrightBlue': '#819aff', // hue shifted #AE81FF + 'terminal.ansiBrightMagenta': '#AE81FF', + 'terminal.ansiBrightCyan': '#66D9EF', + 'terminal.ansiBrightWhite': '#f8f8f2', + 'terminal.inactiveSelectionBackground': '#676b7140', }, - displayName: 'GitHub Dark Default', - semanticHighlighting: true, tokenColors: [ { - scope: ['comment', 'punctuation.definition.comment', 'string.comment'], settings: { - foreground: '#8b949e', + foreground: '#C5C8C6', }, }, { - scope: ['constant.other.placeholder', 'constant.character'], + scope: ['meta.embedded', 'source.groovy.embedded', 'variable.legacy.builtin.python'], settings: { - foreground: '#ff7b72', + foreground: '#C5C8C6', }, }, { - scope: [ - 'constant', - 'entity.name.constant', - 'variable.other.constant', - 'variable.other.enummember', - 'variable.language', - 'entity', - ], + name: 'Comment', + scope: 'comment', settings: { - foreground: '#79c0ff', + fontStyle: '', + foreground: '#9A9B99', }, }, { - scope: ['entity.name', 'meta.export.default', 'meta.definition.variable'], + name: 'String', + scope: 'string', settings: { - foreground: '#ffa657', + fontStyle: '', + foreground: '#9AA83A', }, }, { - scope: [ - 'variable.parameter.function', - 'meta.jsx.children', - 'meta.block', - 'meta.tag.attributes', - 'entity.name.constant', - 'meta.object.member', - 'meta.embedded.expression', - ], + name: 'String Embedded Source', + scope: 'string source', settings: { - foreground: '#e6edf3', + fontStyle: '', + foreground: '#D08442', }, }, { - scope: 'entity.name.function', + name: 'Number', + scope: 'constant.numeric', settings: { - foreground: '#d2a8ff', + fontStyle: '', + foreground: '#6089B4', }, }, { - scope: ['entity.name.tag', 'support.class.component'], + name: 'Built-in constant', + scope: 'constant.language', settings: { - foreground: '#7ee787', + fontStyle: '', + foreground: '#408080', }, }, { + name: 'User-defined constant', + scope: 'constant.character, constant.other', + settings: { + fontStyle: '', + foreground: '#8080FF', + }, + }, + { + name: 'Keyword', scope: 'keyword', settings: { - foreground: '#ff7b72', + fontStyle: '', + foreground: '#6089B4', }, }, { - scope: ['storage', 'storage.type'], + name: 'Support', + scope: 'support', settings: { - foreground: '#ff7b72', + fontStyle: '', + foreground: '#C7444A', }, }, { - scope: ['storage.modifier.package', 'storage.modifier.import', 'storage.type.java'], + name: 'Storage', + scope: 'storage', settings: { - foreground: '#e6edf3', + fontStyle: '', + foreground: '#9872A2', }, }, { - scope: ['string', 'string punctuation.section.embedded source'], + name: 'Class name', + scope: + 'entity.name.class, entity.name.type, entity.name.namespace, entity.name.scope-resolution', settings: { - foreground: '#a5d6ff', + fontStyle: '', + foreground: '#9B0000', }, }, { - scope: 'support', + name: 'Inherited class', + scope: 'entity.other.inherited-class', settings: { - foreground: '#79c0ff', + fontStyle: '', + foreground: '#C7444A', }, }, { - scope: 'meta.property-name', + name: 'Function name', + scope: 'entity.name.function', settings: { - foreground: '#79c0ff', + fontStyle: '', + foreground: '#CE6700', }, }, { - scope: 'variable', + name: 'Function argument', + scope: 'variable.parameter', settings: { - foreground: '#ffa657', + fontStyle: '', + foreground: '#6089B4', }, }, { - scope: 'variable.other', + name: 'Tag name', + scope: 'entity.name.tag', settings: { - foreground: '#e6edf3', + fontStyle: '', + foreground: '#9872A2', }, }, { - scope: 'invalid.broken', + name: 'Tag attribute', + scope: 'entity.other.attribute-name', settings: { - fontStyle: 'italic', - foreground: '#ffa198', + fontStyle: '', + foreground: '#9872A2', }, }, { - scope: 'invalid.deprecated', + name: 'Library function', + scope: 'support.function', settings: { - fontStyle: 'italic', - foreground: '#ffa198', + fontStyle: '', + foreground: '#9872A2', }, }, { - scope: 'invalid.illegal', + name: 'Keyword', + scope: 'keyword', settings: { - fontStyle: 'italic', - foreground: '#ffa198', + fontStyle: '', + foreground: '#676867', }, }, { - scope: 'invalid.unimplemented', + name: 'Class Variable', + scope: 'variable.other, variable.js, punctuation.separator.variable', settings: { - fontStyle: 'italic', - foreground: '#ffa198', + fontStyle: '', + foreground: '#6089B4', }, }, { - scope: 'carriage-return', + name: 'Meta Brace', + scope: + 'punctuation.section.embedded -(source string source punctuation.section.embedded), meta.brace.erb.html', settings: { - background: '#ff7b72', - content: '^M', - fontStyle: 'italic underline', - foreground: '#f0f6fc', + fontStyle: '', + foreground: '#008200', }, }, { - scope: 'message.error', + name: 'Invalid', + scope: 'invalid', settings: { - foreground: '#ffa198', + fontStyle: '', + foreground: '#FF0B00', }, }, { - scope: 'string variable', + name: 'Normal Variable', + scope: 'variable.other.php, variable.other.normal', settings: { - foreground: '#79c0ff', + fontStyle: '', + foreground: '#6089B4', }, }, { - scope: ['source.regexp', 'string.regexp'], + name: 'Function Object', + scope: 'meta.function-call.object', settings: { - foreground: '#a5d6ff', + fontStyle: '', + foreground: '#9872A2', }, }, { + name: 'Function Call Variable', + scope: 'variable.other.property', + settings: { + fontStyle: '', + foreground: '#9872A2', + }, + }, + { + name: 'Keyword Control / Special', scope: [ - 'string.regexp.character-class', - 'string.regexp constant.character.escape', - 'string.regexp source.ruby.embedded', - 'string.regexp string.regexp.arbitrary-repitition', + 'keyword.control', + 'keyword.operator.new.cpp', + 'keyword.operator.delete.cpp', + 'keyword.other.using', + 'keyword.other.directive.using', + 'keyword.other.operator', ], settings: { - foreground: '#a5d6ff', + fontStyle: '', + foreground: '#9872A2', }, }, { - scope: 'string.regexp constant.character.escape', + name: 'Tag', + scope: 'meta.tag', settings: { - fontStyle: 'bold', - foreground: '#7ee787', + fontStyle: '', + foreground: '#D0B344', }, }, { - scope: 'support.constant', + name: 'Tag Name', + scope: 'entity.name.tag', settings: { - foreground: '#79c0ff', + fontStyle: '', + foreground: '#6089B4', }, }, { - scope: 'support.variable', + name: 'Doctype', + scope: 'meta.doctype, meta.tag.sgml-declaration.doctype, meta.tag.sgml.doctype', settings: { - foreground: '#79c0ff', + fontStyle: '', + foreground: '#9AA83A', }, }, { - scope: 'support.type.property-name.json', + name: 'Tag Inline Source', + scope: 'meta.tag.inline source, text.html.php.source', settings: { - foreground: '#7ee787', + fontStyle: '', + foreground: '#9AA83A', }, }, { - scope: 'meta.module-reference', + name: 'Tag Other', + scope: + 'meta.tag.other, entity.name.tag.style, entity.name.tag.script, meta.tag.block.script, source.js.embedded punctuation.definition.tag.html, source.css.embedded punctuation.definition.tag.html', settings: { - foreground: '#79c0ff', + fontStyle: '', + foreground: '#9872A2', }, }, { - scope: 'punctuation.definition.list.begin.markdown', + name: 'Tag Attribute', + scope: 'entity.other.attribute-name, meta.tag punctuation.definition.string', settings: { - foreground: '#ffa657', + fontStyle: '', + foreground: '#D0B344', }, }, { - scope: ['markup.heading', 'markup.heading entity.name'], + name: 'Tag Value', + scope: 'meta.tag string -source -punctuation, text source text meta.tag string -punctuation', settings: { - fontStyle: 'bold', - foreground: '#79c0ff', + fontStyle: '', + foreground: '#6089B4', }, }, { - scope: 'markup.quote', + name: 'Meta Brace', + scope: + 'punctuation.section.embedded -(source string source punctuation.section.embedded), meta.brace.erb.html', settings: { - foreground: '#7ee787', + fontStyle: '', + foreground: '#D0B344', }, }, { - scope: 'markup.italic', + name: 'HTML ID', + scope: 'meta.toc-list.id', + settings: { + foreground: '#9AA83A', + }, + }, + { + name: 'HTML String', + scope: + 'string.quoted.double.html, punctuation.definition.string.begin.html, punctuation.definition.string.end.html, punctuation.definition.string.end.html source, string.quoted.double.html source', + settings: { + fontStyle: '', + foreground: '#9AA83A', + }, + }, + { + name: 'HTML Tags', + scope: + 'punctuation.definition.tag.html, punctuation.definition.tag.begin, punctuation.definition.tag.end', + settings: { + fontStyle: '', + foreground: '#6089B4', + }, + }, + { + name: 'CSS ID', + scope: 'meta.selector entity.other.attribute-name.id', + settings: { + fontStyle: '', + foreground: '#9872A2', + }, + }, + { + name: 'CSS Property Name', + scope: 'source.css support.type.property-name', + settings: { + fontStyle: '', + foreground: '#676867', + }, + }, + { + name: 'CSS Property Value', + scope: + 'meta.property-group support.constant.property-value, meta.property-value support.constant.property-value', + settings: { + fontStyle: '', + foreground: '#C7444A', + }, + }, + { + name: 'JavaScript Variable', + scope: 'variable.language.js', + settings: { + foreground: '#CC555A', + }, + }, + { + name: 'Template Definition', + scope: ['punctuation.definition.template-expression', 'punctuation.section.embedded.coffee'], + settings: { + foreground: '#D08442', + }, + }, + { + name: 'Reset JavaScript string interpolation expression', + scope: ['meta.template.expression'], + settings: { + foreground: '#C5C8C6', + }, + }, + { + name: 'PHP Function Call', + scope: 'meta.function-call.object.php', + settings: { + fontStyle: '', + foreground: '#D0B344', + }, + }, + { + name: 'PHP Single Quote HMTL Fix', + scope: 'punctuation.definition.string.end.php, punctuation.definition.string.begin.php', + settings: { + foreground: '#9AA83A', + }, + }, + { + name: 'PHP Parenthesis HMTL Fix', + scope: 'source.php.embedded.line.html', + settings: { + foreground: '#676867', + }, + }, + { + name: 'PHP Punctuation Embedded', + scope: 'punctuation.section.embedded.begin.php, punctuation.section.embedded.end.php', + settings: { + fontStyle: '', + foreground: '#D08442', + }, + }, + { + name: 'Ruby Symbol', + scope: 'constant.other.symbol.ruby', + settings: { + fontStyle: '', + foreground: '#9AA83A', + }, + }, + { + name: 'Ruby Variable', + scope: 'variable.language.ruby', + settings: { + fontStyle: '', + foreground: '#D0B344', + }, + }, + { + name: 'Ruby Special Method', + scope: 'keyword.other.special-method.ruby', + settings: { + fontStyle: '', + foreground: '#D9B700', + }, + }, + { + name: 'Ruby Embedded Source', + scope: ['punctuation.section.embedded.begin.ruby', 'punctuation.section.embedded.end.ruby'], + settings: { + foreground: '#D08442', + }, + }, + { + name: 'SQL', + scope: 'keyword.other.DML.sql', + settings: { + fontStyle: '', + foreground: '#D0B344', + }, + }, + { + name: 'diff: header', + scope: 'meta.diff, meta.diff.header', settings: { fontStyle: 'italic', - foreground: '#e6edf3', + foreground: '#E0EDDD', }, }, { - scope: 'markup.bold', + name: 'diff: deleted', + scope: 'markup.deleted', settings: { - fontStyle: 'bold', - foreground: '#e6edf3', + fontStyle: '', + foreground: '#dc322f', }, }, { - scope: ['markup.underline'], + name: 'diff: changed', + scope: 'markup.changed', settings: { - fontStyle: 'underline', + fontStyle: '', + foreground: '#cb4b16', }, }, { - scope: ['markup.strikethrough'], + name: 'diff: inserted', + scope: 'markup.inserted', settings: { - fontStyle: 'strikethrough', + foreground: '#219186', }, }, { - scope: 'markup.inline.raw', + name: 'Markup Quote', + scope: 'markup.quote', settings: { - foreground: '#79c0ff', + foreground: '#9872A2', }, }, { - scope: ['markup.deleted', 'meta.diff.header.from-file', 'punctuation.definition.deleted'], + name: 'Markup Lists', + scope: 'markup.list', settings: { - background: '#490202', - foreground: '#ffa198', + foreground: '#9AA83A', }, }, { - scope: ['punctuation.section.embedded'], + name: 'Markup Styling', + scope: 'markup.bold, markup.italic', settings: { - foreground: '#ff7b72', + foreground: '#6089B4', }, }, { - scope: ['markup.inserted', 'meta.diff.header.to-file', 'punctuation.definition.inserted'], + name: 'Markup Inline', + scope: 'markup.inline.raw', settings: { - background: '#04260f', - foreground: '#7ee787', + fontStyle: '', + foreground: '#FF0080', }, }, { - scope: ['markup.changed', 'punctuation.definition.changed'], + name: 'Markup Headings', + scope: 'markup.heading', settings: { - background: '#5a1e02', - foreground: '#ffa657', + foreground: '#D0B344', }, }, { - scope: ['markup.ignored', 'markup.untracked'], + name: 'Markup Setext Header', + scope: 'markup.heading.setext', settings: { - background: '#79c0ff', - foreground: '#161b22', + fontStyle: '', + foreground: '#D0B344', }, }, { - scope: 'meta.diff.range', + name: 'Markdown Headings', + scope: 'markup.heading.markdown', settings: { fontStyle: 'bold', - foreground: '#d2a8ff', }, }, { - scope: 'meta.diff.header', + name: 'Markdown Quote', + scope: 'markup.quote.markdown', settings: { - foreground: '#79c0ff', + fontStyle: 'italic', + foreground: '', }, }, { - scope: 'meta.separator', + name: 'Markdown Bold', + scope: 'markup.bold.markdown', settings: { fontStyle: 'bold', - foreground: '#79c0ff', }, }, { - scope: 'meta.output', + name: 'Markdown Link Title/Description', + scope: 'string.other.link.title.markdown,string.other.link.description.markdown', settings: { - foreground: '#79c0ff', + foreground: '#AE81FF', }, }, { - scope: [ - 'brackethighlighter.tag', - 'brackethighlighter.curly', - 'brackethighlighter.round', - 'brackethighlighter.square', - 'brackethighlighter.angle', - 'brackethighlighter.quote', - ], + name: 'Markdown Underline Link/Image', + scope: 'markup.underline.link.markdown,markup.underline.link.image.markdown', settings: { - foreground: '#8b949e', + foreground: '', }, }, { - scope: 'brackethighlighter.unmatched', + name: 'Markdown Emphasis', + scope: 'markup.italic.markdown', settings: { - foreground: '#ffa198', + fontStyle: 'italic', }, }, { - scope: ['constant.other.reference.link', 'string.other.link'], + scope: 'markup.strikethrough', + settings: { + fontStyle: 'strikethrough', + }, + }, + { + name: 'Markdown Punctuation Definition Link', + scope: 'markup.list.unnumbered.markdown, markup.list.numbered.markdown', settings: { - foreground: '#a5d6ff', + foreground: '', + }, + }, + { + name: 'Markdown List Punctuation', + scope: ['punctuation.definition.list.begin.markdown'], + settings: { + foreground: '', + }, + }, + { + scope: 'token.info-token', + settings: { + foreground: '#6796e6', + }, + }, + { + scope: 'token.warn-token', + settings: { + foreground: '#cd9731', + }, + }, + { + scope: 'token.error-token', + settings: { + foreground: '#f44747', + }, + }, + { + scope: 'token.debug-token', + settings: { + foreground: '#b267e6', + }, + }, + { + name: 'this.self', + scope: 'variable.language', + settings: { + foreground: '#c7444a', }, }, ], - type: 'dark', + semanticHighlighting: true, }; diff --git a/scripts/docs/format-changelogs.ts b/scripts/docs/format-changelogs.ts index fd252eb8f0a..c5e0525641b 100644 --- a/scripts/docs/format-changelogs.ts +++ b/scripts/docs/format-changelogs.ts @@ -8,6 +8,11 @@ const files = fs.readdirSync(changelogFolder).filter((file) => file.endsWith('.m files.forEach((file) => { const content = fs.readFileSync(path.join(changelogFolder, file), 'utf8'); + + if (content.includes(' component`); + } + const replacedLinks = content.replaceAll('](/', '](https://mantine.dev/'); fs.writeFileSync(path.join(changelogFolder, file), replacedLinks); }); diff --git a/yarn.lock b/yarn.lock index 01aba1af1cb..896e53a574c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4037,8 +4037,8 @@ __metadata: "@mantine/core": "workspace:*" "@mantine/hooks": "workspace:*" peerDependencies: - "@mantine/core": 7.13.5 - "@mantine/hooks": 7.13.5 + "@mantine/core": 7.14.0 + "@mantine/hooks": 7.14.0 embla-carousel-react: ">=7.0.0" react: ^18.x || ^19.x react-dom: ^18.x || ^19.x @@ -4053,8 +4053,8 @@ __metadata: "@mantine/core": "workspace:*" "@mantine/hooks": "workspace:*" peerDependencies: - "@mantine/core": 7.13.5 - "@mantine/hooks": 7.13.5 + "@mantine/core": 7.14.0 + "@mantine/hooks": 7.14.0 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x recharts: ^2.13.3 @@ -4071,8 +4071,8 @@ __metadata: clsx: "npm:^2.1.1" highlight.js: "npm:^11.10.0" peerDependencies: - "@mantine/core": 7.13.5 - "@mantine/hooks": 7.13.5 + "@mantine/core": 7.14.0 + "@mantine/hooks": 7.14.0 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x languageName: unknown @@ -4099,7 +4099,7 @@ __metadata: react-textarea-autosize: "npm:8.5.4" type-fest: "npm:^4.26.1" peerDependencies: - "@mantine/hooks": 7.13.5 + "@mantine/hooks": 7.14.0 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x languageName: unknown @@ -4115,8 +4115,8 @@ __metadata: "@mantine/hooks": "workspace:*" clsx: "npm:^2.1.1" peerDependencies: - "@mantine/core": 7.13.5 - "@mantine/hooks": 7.13.5 + "@mantine/core": 7.14.0 + "@mantine/hooks": 7.14.0 dayjs: ">=1.0.0" react: ^18.x || ^19.x react-dom: ^18.x || ^19.x @@ -4132,8 +4132,8 @@ __metadata: "@mantine/hooks": "workspace:*" react-dropzone-esm: "npm:15.0.1" peerDependencies: - "@mantine/core": 7.13.5 - "@mantine/hooks": 7.13.5 + "@mantine/core": 7.14.0 + "@mantine/hooks": 7.14.0 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x languageName: unknown @@ -4152,8 +4152,8 @@ __metadata: "@emotion/react": ^11.11.4 "@emotion/serialize": ^1.1.4 "@emotion/utils": ^1.2.1 - "@mantine/core": 7.13.5 - "@mantine/hooks": 7.13.5 + "@mantine/core": 7.14.0 + "@mantine/hooks": 7.14.0 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x languageName: unknown @@ -4185,8 +4185,8 @@ __metadata: "@mantine/core": "workspace:*" "@mantine/hooks": "workspace:*" peerDependencies: - "@mantine/core": 7.13.5 - "@mantine/hooks": 7.13.5 + "@mantine/core": 7.14.0 + "@mantine/hooks": 7.14.0 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x languageName: unknown @@ -4199,11 +4199,11 @@ __metadata: "@mantine-tests/core": "workspace:*" "@mantine/core": "workspace:*" "@mantine/hooks": "workspace:*" - "@mantine/store": "npm:7.13.5" + "@mantine/store": "npm:7.14.0" react-transition-group: "npm:4.4.5" peerDependencies: - "@mantine/core": 7.13.5 - "@mantine/hooks": 7.13.5 + "@mantine/core": 7.14.0 + "@mantine/hooks": 7.14.0 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x languageName: unknown @@ -4216,10 +4216,10 @@ __metadata: "@mantine-tests/core": "workspace:*" "@mantine/core": "workspace:*" "@mantine/hooks": "workspace:*" - "@mantine/store": "npm:7.13.5" + "@mantine/store": "npm:7.14.0" peerDependencies: - "@mantine/core": 7.13.5 - "@mantine/hooks": 7.13.5 + "@mantine/core": 7.14.0 + "@mantine/hooks": 7.14.0 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x languageName: unknown @@ -4232,16 +4232,16 @@ __metadata: "@mantine-tests/core": "workspace:*" "@mantine/core": "workspace:*" "@mantine/hooks": "workspace:*" - "@mantine/store": "npm:7.13.5" + "@mantine/store": "npm:7.14.0" peerDependencies: - "@mantine/core": 7.13.5 - "@mantine/hooks": 7.13.5 + "@mantine/core": 7.14.0 + "@mantine/hooks": 7.14.0 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x languageName: unknown linkType: soft -"@mantine/store@npm:7.13.5, @mantine/store@workspace:*, @mantine/store@workspace:packages/@mantine/store": +"@mantine/store@npm:7.14.0, @mantine/store@workspace:*, @mantine/store@workspace:packages/@mantine/store": version: 0.0.0-use.local resolution: "@mantine/store@workspace:packages/@mantine/store" peerDependencies: @@ -4257,8 +4257,8 @@ __metadata: "@mantine/core": "workspace:*" "@mantine/hooks": "workspace:*" peerDependencies: - "@mantine/core": 7.13.5 - "@mantine/hooks": 7.13.5 + "@mantine/core": 7.14.0 + "@mantine/hooks": 7.14.0 "@tiptap/extension-link": ">=2.1.12" "@tiptap/react": ">=2.1.12" react: ^18.x || ^19.x @@ -4270,7 +4270,7 @@ __metadata: version: 0.0.0-use.local resolution: "@mantine/vanilla-extract@workspace:packages/@mantine/vanilla-extract" peerDependencies: - "@mantine/core": 7.13.5 + "@mantine/core": 7.14.0 languageName: unknown linkType: soft @@ -16950,11 +16950,11 @@ __metadata: linkType: hard "npm-run-path@npm:^5.1.0": - version: 5.3.0 - resolution: "npm-run-path@npm:5.3.0" + version: 5.1.0 + resolution: "npm-run-path@npm:5.1.0" dependencies: path-key: "npm:^4.0.0" - checksum: 10c0/124df74820c40c2eb9a8612a254ea1d557ddfab1581c3e751f825e3e366d9f00b0d76a3c94ecd8398e7f3eee193018622677e95816e8491f0797b21e30b2deba + checksum: 10c0/ff6d77514489f47fa1c3b1311d09cd4b6d09a874cc1866260f9dea12cbaabda0436ed7f8c2ee44d147bf99a3af29307c6f63b0f83d242b0b6b0ab25dff2629e3 languageName: node linkType: hard