From 315ffa1e163a0bdd54a89909cf1dd7ffa582cd3a Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Mon, 2 Sep 2024 13:57:50 +0200 Subject: [PATCH 1/7] [DataGrid] Remove cell min-width / max-width styles (#14448) --- .../components/headerFiltering/GridHeaderFilterCell.tsx | 2 -- .../columnHeaders/GridGenericColumnHeaderItem.tsx | 2 -- .../src/components/containers/GridRootStyles.ts | 3 +-- .../hooks/features/columnResize/useGridColumnResize.tsx | 8 -------- 4 files changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx b/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx index 68360d2c7203..1d22e8793958 100644 --- a/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx +++ b/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx @@ -313,8 +313,6 @@ const GridHeaderFilterCell = React.forwardRef { @@ -335,8 +331,6 @@ export const useGridColumnResize = ( } div.style.width = finalWidth; - div.style.minWidth = finalWidth; - div.style.maxWidth = finalWidth; }); refs.cellElements!.forEach((element) => { @@ -427,8 +421,6 @@ export const useGridColumnResize = ( const finalWidth: `${number}px` = `${newWidth}px`; div.style.width = finalWidth; - div.style.minWidth = finalWidth; - div.style.maxWidth = finalWidth; }); } From 662df95f44fdf82f1a1960e9fded16c17b0b449e Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Mon, 2 Sep 2024 15:42:50 +0200 Subject: [PATCH 2/7] [core] Fix failing tests on the pickers (#14457) --- .../src/DateCalendar/tests/DateCalendar.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx index bf13515b44c8..8332954d9937 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx @@ -259,7 +259,7 @@ describe('', () => { it('should complete weeks when showDaysOutsideCurrentMonth=true', () => { render( , @@ -270,7 +270,7 @@ describe('', () => { it('should complete weeks up to match `fixedWeekNumber`', () => { render( Date: Mon, 2 Sep 2024 15:46:06 +0200 Subject: [PATCH 3/7] [charts] Use real world data for `PieChart` examples (#14297) Signed-off-by: Jose C Quintas Jr Co-authored-by: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com> --- docs/data/charts/pie/PieActiveArc.js | 10 +-- docs/data/charts/pie/PieActiveArc.tsx | 10 +-- docs/data/charts/pie/PieActiveArc.tsx.preview | 3 +- docs/data/charts/pie/PieAnimation.js | 29 ++------ docs/data/charts/pie/PieAnimation.tsx | 28 ++------ docs/data/charts/pie/PieArcLabel.js | 31 ++++---- docs/data/charts/pie/PieArcLabel.tsx | 31 ++++---- docs/data/charts/pie/PieArcLabel.tsx.preview | 8 +-- docs/data/charts/pie/PieClickNoSnap.js | 55 +++++---------- docs/data/charts/pie/PieColor.js | 31 ++++++-- docs/data/charts/pie/PieColor.tsx | 31 ++++++-- docs/data/charts/pie/PieShapeNoSnap.js | 15 ++-- docs/data/charts/pie/webUsageStats.ts | 70 +++++++++++++++++++ 13 files changed, 198 insertions(+), 154 deletions(-) create mode 100644 docs/data/charts/pie/webUsageStats.ts diff --git a/docs/data/charts/pie/PieActiveArc.js b/docs/data/charts/pie/PieActiveArc.js index df49ae3bd67f..4e151e983c04 100644 --- a/docs/data/charts/pie/PieActiveArc.js +++ b/docs/data/charts/pie/PieActiveArc.js @@ -1,20 +1,16 @@ import * as React from 'react'; import { PieChart } from '@mui/x-charts/PieChart'; - -const data = [ - { id: 0, value: 10, label: 'series A' }, - { id: 1, value: 15, label: 'series B' }, - { id: 2, value: 20, label: 'series C' }, -]; +import { desktopOS, valueFormatter } from './webUsageStats'; export default function PieActiveArc() { return ( params.label ?? '', + arcLabelMinAngle: 20, + valueFormatter, }, ]} skipAnimation={skipAnimation} @@ -75,7 +56,7 @@ export default function PieAnimation() { onChange={handleItemNbChange} valueLabelDisplay="auto" min={1} - max={10} + max={8} aria-labelledby="input-item-number" /> diff --git a/docs/data/charts/pie/PieAnimation.tsx b/docs/data/charts/pie/PieAnimation.tsx index f25df849a2ad..15da4f8a4708 100644 --- a/docs/data/charts/pie/PieAnimation.tsx +++ b/docs/data/charts/pie/PieAnimation.tsx @@ -5,26 +5,7 @@ import Slider from '@mui/material/Slider'; import FormControlLabel from '@mui/material/FormControlLabel'; import Checkbox from '@mui/material/Checkbox'; import { PieChart } from '@mui/x-charts/PieChart'; - -const data1 = [ - { label: 'Group A', value: 400 }, - { label: 'Group B', value: 300 }, - { label: 'Group C', value: 300 }, - { label: 'Group D', value: 200 }, -]; -const data2 = [ - { label: '1', value: 100 }, - { label: '2', value: 300 }, - { label: '3', value: 100 }, - { label: '4', value: 80 }, - { label: '5', value: 40 }, - { label: '6', value: 30 }, - { label: '7', value: 50 }, - { label: '8', value: 100 }, - { label: '9', value: 200 }, - { label: '10', value: 150 }, - { label: '11', value: 50 }, -]; +import { mobileAndDesktopOS, valueFormatter } from './webUsageStats'; export default function PieAnimation() { const [radius, setRadius] = React.useState(50); @@ -49,11 +30,12 @@ export default function PieAnimation() { params.label ?? '', + arcLabelMinAngle: 20, + valueFormatter, }, ]} skipAnimation={skipAnimation} @@ -74,7 +56,7 @@ export default function PieAnimation() { onChange={handleItemNbChange} valueLabelDisplay="auto" min={1} - max={10} + max={8} aria-labelledby="input-item-number" /> diff --git a/docs/data/charts/pie/PieArcLabel.js b/docs/data/charts/pie/PieArcLabel.js index b61a7e91d014..0555cc2d964e 100644 --- a/docs/data/charts/pie/PieArcLabel.js +++ b/docs/data/charts/pie/PieArcLabel.js @@ -1,31 +1,20 @@ import * as React from 'react'; import { PieChart, pieArcLabelClasses } from '@mui/x-charts/PieChart'; - -const data = [ - { value: 5, label: 'A' }, - { value: 10, label: 'B' }, - { value: 15, label: 'C' }, - { value: 20, label: 'D' }, -]; - -const size = { - width: 400, - height: 200, -}; +import { desktopOS, valueFormatter } from './webUsageStats'; export default function PieArcLabel() { return ( `${item.label} (${item.value})`, - arcLabelMinAngle: 45, - data, + arcLabel: (item) => `${item.value}%`, + arcLabelMinAngle: 35, + arcLabelRadius: '60%', + ...data, }, ]} sx={{ [`& .${pieArcLabelClasses.root}`]: { - fill: 'white', fontWeight: 'bold', }, }} @@ -33,3 +22,13 @@ export default function PieArcLabel() { /> ); } + +const size = { + width: 400, + height: 200, +}; + +const data = { + data: desktopOS, + valueFormatter, +}; diff --git a/docs/data/charts/pie/PieArcLabel.tsx b/docs/data/charts/pie/PieArcLabel.tsx index b61a7e91d014..0555cc2d964e 100644 --- a/docs/data/charts/pie/PieArcLabel.tsx +++ b/docs/data/charts/pie/PieArcLabel.tsx @@ -1,31 +1,20 @@ import * as React from 'react'; import { PieChart, pieArcLabelClasses } from '@mui/x-charts/PieChart'; - -const data = [ - { value: 5, label: 'A' }, - { value: 10, label: 'B' }, - { value: 15, label: 'C' }, - { value: 20, label: 'D' }, -]; - -const size = { - width: 400, - height: 200, -}; +import { desktopOS, valueFormatter } from './webUsageStats'; export default function PieArcLabel() { return ( `${item.label} (${item.value})`, - arcLabelMinAngle: 45, - data, + arcLabel: (item) => `${item.value}%`, + arcLabelMinAngle: 35, + arcLabelRadius: '60%', + ...data, }, ]} sx={{ [`& .${pieArcLabelClasses.root}`]: { - fill: 'white', fontWeight: 'bold', }, }} @@ -33,3 +22,13 @@ export default function PieArcLabel() { /> ); } + +const size = { + width: 400, + height: 200, +}; + +const data = { + data: desktopOS, + valueFormatter, +}; diff --git a/docs/data/charts/pie/PieArcLabel.tsx.preview b/docs/data/charts/pie/PieArcLabel.tsx.preview index dac686d77a44..0f617bfbd6f2 100644 --- a/docs/data/charts/pie/PieArcLabel.tsx.preview +++ b/docs/data/charts/pie/PieArcLabel.tsx.preview @@ -1,14 +1,14 @@ `${item.label} (${item.value})`, - arcLabelMinAngle: 45, - data, + arcLabel: (item) => `${item.value}%`, + arcLabelMinAngle: 35, + arcLabelRadius: '60%', + ...data, }, ]} sx={{ [`& .${pieArcLabelClasses.root}`]: { - fill: 'white', fontWeight: 'bold', }, }} diff --git a/docs/data/charts/pie/PieClickNoSnap.js b/docs/data/charts/pie/PieClickNoSnap.js index de00ca705a95..8b13dfb3d48b 100644 --- a/docs/data/charts/pie/PieClickNoSnap.js +++ b/docs/data/charts/pie/PieClickNoSnap.js @@ -4,45 +4,9 @@ import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import IconButton from '@mui/material/IconButton'; import UndoOutlinedIcon from '@mui/icons-material/UndoOutlined'; - import { PieChart } from '@mui/x-charts/PieChart'; - import { HighlightedCode } from '@mui/docs/HighlightedCode'; - -const data1 = [ - { label: 'Group A', value: 400 }, - { label: 'Group B', value: 300 }, - { label: 'Group C', value: 300 }, - { label: 'Group D', value: 200 }, -]; - -const data2 = [ - { label: 'A1', value: 100 }, - { label: 'A2', value: 300 }, - { label: 'B1', value: 100 }, - { label: 'B2', value: 80 }, - { label: 'B3', value: 40 }, - { label: 'B4', value: 30 }, - { label: 'B5', value: 50 }, - { label: 'C1', value: 100 }, - { label: 'C2', value: 200 }, - { label: 'D1', value: 150 }, - { label: 'D2', value: 50 }, -]; -const series = [ - { - innerRadius: 0, - outerRadius: 80, - id: 'series-1', - data: data1, - }, - { - innerRadius: 100, - outerRadius: 120, - id: 'series-2', - data: data2, - }, -]; +import { mobileAndDesktopOS, platforms, valueFormatter } from './webUsageStats'; export default function PieClickNoSnap() { const [itemData, setItemData] = React.useState(); @@ -95,3 +59,20 @@ ${itemData ? JSON.stringify(itemData, null, 2) : '// The data will appear here'} ); } + +const series = [ + { + innerRadius: 0, + outerRadius: 80, + id: 'platform-series', + data: platforms, + valueFormatter, + }, + { + innerRadius: 100, + outerRadius: 120, + id: 'OS-series', + data: mobileAndDesktopOS, + valueFormatter, + }, +]; diff --git a/docs/data/charts/pie/PieColor.js b/docs/data/charts/pie/PieColor.js index d12e6dfab6d7..447224cbb194 100644 --- a/docs/data/charts/pie/PieColor.js +++ b/docs/data/charts/pie/PieColor.js @@ -3,9 +3,14 @@ import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; import { PieChart } from '@mui/x-charts/PieChart'; +import { platforms } from './webUsageStats'; -const pieParams = { height: 200, margin: { right: 5 } }; -const palette = ['red', 'blue', 'green']; +const palette = ['lightcoral', 'slateblue']; + +const colorPerItem = [ + { ...platforms[0], color: 'orange' }, + { ...platforms[1], color: 'gray' }, +]; export default function PieColor() { return ( @@ -13,7 +18,11 @@ export default function PieColor() { Default @@ -21,7 +30,11 @@ export default function PieColor() { Palette @@ -29,7 +42,9 @@ export default function PieColor() { Item @@ -37,3 +52,9 @@ export default function PieColor() { ); } + +const pieParams = { + height: 200, + margin: { right: 5 }, + slotProps: { legend: { hidden: true } }, +}; diff --git a/docs/data/charts/pie/PieColor.tsx b/docs/data/charts/pie/PieColor.tsx index d12e6dfab6d7..447224cbb194 100644 --- a/docs/data/charts/pie/PieColor.tsx +++ b/docs/data/charts/pie/PieColor.tsx @@ -3,9 +3,14 @@ import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; import { PieChart } from '@mui/x-charts/PieChart'; +import { platforms } from './webUsageStats'; -const pieParams = { height: 200, margin: { right: 5 } }; -const palette = ['red', 'blue', 'green']; +const palette = ['lightcoral', 'slateblue']; + +const colorPerItem = [ + { ...platforms[0], color: 'orange' }, + { ...platforms[1], color: 'gray' }, +]; export default function PieColor() { return ( @@ -13,7 +18,11 @@ export default function PieColor() { Default @@ -21,7 +30,11 @@ export default function PieColor() { Palette @@ -29,7 +42,9 @@ export default function PieColor() { Item @@ -37,3 +52,9 @@ export default function PieColor() { ); } + +const pieParams = { + height: 200, + margin: { right: 5 }, + slotProps: { legend: { hidden: true } }, +}; diff --git a/docs/data/charts/pie/PieShapeNoSnap.js b/docs/data/charts/pie/PieShapeNoSnap.js index 9952d0c2a3fe..b36ace970cfb 100644 --- a/docs/data/charts/pie/PieShapeNoSnap.js +++ b/docs/data/charts/pie/PieShapeNoSnap.js @@ -1,6 +1,7 @@ import * as React from 'react'; import ChartsUsageDemo from 'docsx/src/modules/components/ChartsUsageDemo'; import { PieChart } from '@mui/x-charts/PieChart'; +import { desktopOS, valueFormatter } from './webUsageStats'; export default function PieShapeNoSnap() { return ( @@ -11,8 +12,8 @@ export default function PieShapeNoSnap() { { propName: `outerRadius`, knob: 'number', defaultValue: 100 }, { propName: `paddingAngle`, knob: 'number', defaultValue: 5 }, { propName: `cornerRadius`, knob: 'number', defaultValue: 5 }, - { propName: `startAngle`, knob: 'number', defaultValue: -90 }, - { propName: `endAngle`, knob: 'number', defaultValue: 180 }, + { propName: `startAngle`, knob: 'number', defaultValue: -45 }, + { propName: `endAngle`, knob: 'number', defaultValue: 225 }, { propName: `cx`, knob: 'number', defaultValue: 150 }, { propName: `cy`, knob: 'number', defaultValue: 150 }, ]} @@ -21,18 +22,14 @@ export default function PieShapeNoSnap() { series={[ { ...props, - data: [ - { value: 5 }, - { value: 5 }, - { value: 10 }, - { value: 15 }, - { value: 30 }, - ], + data: desktopOS, + valueFormatter, }, ]} width={300} height={300} margin={{ right: 5 }} + slotProps={{ legend: { hidden: true } }} /> )} getCode={({ props }) => { diff --git a/docs/data/charts/pie/webUsageStats.ts b/docs/data/charts/pie/webUsageStats.ts new file mode 100644 index 000000000000..98d3642865cf --- /dev/null +++ b/docs/data/charts/pie/webUsageStats.ts @@ -0,0 +1,70 @@ +// Data derived from https://gs.statcounter.com/os-market-share/desktop/worldwide/2023 +// And https://gs.statcounter.com/os-market-share/mobile/worldwide/2023 +// And https://gs.statcounter.com/platform-market-share/desktop-mobile-tablet/worldwide/2023 +// For the month of December 2023 + +export const desktopOS = [ + { + label: 'Windows', + value: 72.72, + }, + { + label: 'OS X', + value: 16.38, + }, + { + label: 'Linux', + value: 3.83, + }, + { + label: 'Chrome OS', + value: 2.42, + }, + { + label: 'Other', + value: 4.65, + }, +]; + +export const mobileOS = [ + { + label: 'Android', + value: 70.48, + }, + { + label: 'iOS', + value: 28.8, + }, + { + label: 'Other', + value: 0.71, + }, +]; + +export const platforms = [ + { + label: 'Mobile', + value: 59.12, + }, + { + label: 'Desktop', + value: 40.88, + }, +]; + +const normalize = (v: number, v2: number) => Number.parseFloat(((v * v2) / 100).toFixed(2)); + +export const mobileAndDesktopOS = [ + ...mobileOS.map((v) => ({ + ...v, + label: v.label === 'Other' ? 'Other (Mobile)' : v.label, + value: normalize(v.value, platforms[0].value), + })), + ...desktopOS.map((v) => ({ + ...v, + label: v.label === 'Other' ? 'Other (Desktop)' : v.label, + value: normalize(v.value, platforms[1].value), + })), +]; + +export const valueFormatter = (item: { value: number }) => `${item.value}%`; From aff6ddbb57253bfe062da4b080d441f6b1aeb91e Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Mon, 2 Sep 2024 16:17:44 +0200 Subject: [PATCH 4/7] [docs] Add RTL documentation for the pickers (#13855) --- .../adapters-locale/PickersRTL.js | 51 +++++++++++++++++ .../adapters-locale/PickersRTL.tsx | 51 +++++++++++++++++ .../adapters-locale/adapters-locale.md | 9 +++ .../calendar-systems/AdapterHijri.js | 55 +++++++++++++------ .../calendar-systems/AdapterHijri.tsx | 55 +++++++++++++------ .../calendar-systems/AdapterHijri.tsx.preview | 13 ----- .../calendar-systems/AdapterJalali.js | 49 ++++++++++++----- .../calendar-systems/AdapterJalali.tsx | 49 ++++++++++++----- .../AdapterJalali.tsx.preview | 10 ---- .../calendar-systems/AdapterMomentJalali.js | 51 ++++++++++++----- .../calendar-systems/AdapterMomentJalali.tsx | 51 ++++++++++++----- .../AdapterMomentJalali.tsx.preview | 10 ---- .../MultiSectionDigitalClock.tsx | 8 +-- 13 files changed, 337 insertions(+), 125 deletions(-) create mode 100644 docs/data/date-pickers/adapters-locale/PickersRTL.js create mode 100644 docs/data/date-pickers/adapters-locale/PickersRTL.tsx delete mode 100644 docs/data/date-pickers/calendar-systems/AdapterHijri.tsx.preview delete mode 100644 docs/data/date-pickers/calendar-systems/AdapterJalali.tsx.preview delete mode 100644 docs/data/date-pickers/calendar-systems/AdapterMomentJalali.tsx.preview diff --git a/docs/data/date-pickers/adapters-locale/PickersRTL.js b/docs/data/date-pickers/adapters-locale/PickersRTL.js new file mode 100644 index 000000000000..6372a1c7a21f --- /dev/null +++ b/docs/data/date-pickers/adapters-locale/PickersRTL.js @@ -0,0 +1,51 @@ +import * as React from 'react'; +import { prefixer } from 'stylis'; +import rtlPlugin from 'stylis-plugin-rtl'; +import createCache from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; +import dayjs from 'dayjs'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; +import { createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; + +// Create rtl cache +const cacheRtl = createCache({ + key: 'pickers-rtl-demo', + stylisPlugins: [prefixer, rtlPlugin], +}); + +export default function PickersRTL() { + // Inherit the theme from the docs site (dark/light mode) + const existingTheme = useTheme(); + + const theme = React.useMemo( + () => createTheme(existingTheme, { direction: 'rtl' }), + [existingTheme], + ); + + return ( + + +
+ + `, you can skip it. + slotProps={{ + desktopPaper: { + dir: 'rtl', + }, + mobilePaper: { + dir: 'rtl', + }, + }} + /> + +
+
+
+ ); +} diff --git a/docs/data/date-pickers/adapters-locale/PickersRTL.tsx b/docs/data/date-pickers/adapters-locale/PickersRTL.tsx new file mode 100644 index 000000000000..6372a1c7a21f --- /dev/null +++ b/docs/data/date-pickers/adapters-locale/PickersRTL.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import { prefixer } from 'stylis'; +import rtlPlugin from 'stylis-plugin-rtl'; +import createCache from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; +import dayjs from 'dayjs'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; +import { createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; + +// Create rtl cache +const cacheRtl = createCache({ + key: 'pickers-rtl-demo', + stylisPlugins: [prefixer, rtlPlugin], +}); + +export default function PickersRTL() { + // Inherit the theme from the docs site (dark/light mode) + const existingTheme = useTheme(); + + const theme = React.useMemo( + () => createTheme(existingTheme, { direction: 'rtl' }), + [existingTheme], + ); + + return ( + + +
+ + `, you can skip it. + slotProps={{ + desktopPaper: { + dir: 'rtl', + }, + mobilePaper: { + dir: 'rtl', + }, + }} + /> + +
+
+
+ ); +} diff --git a/docs/data/date-pickers/adapters-locale/adapters-locale.md b/docs/data/date-pickers/adapters-locale/adapters-locale.md index bf62a4b35c07..99f5e44e7ace 100644 --- a/docs/data/date-pickers/adapters-locale/adapters-locale.md +++ b/docs/data/date-pickers/adapters-locale/adapters-locale.md @@ -369,3 +369,12 @@ moment.updateLocale('en', { }, }); ``` + +## RTL Support + +Right-to-left languages such as Arabic, Persian, or Hebrew are supported. +Follow [this guide](/material-ui/customization/right-to-left/) to use them. + +The example below demonstrates how to use an RTL language (Arabic) with some of the Date and Time Pickers components. + +{{"demo": "PickersRTL.js"}} diff --git a/docs/data/date-pickers/calendar-systems/AdapterHijri.js b/docs/data/date-pickers/calendar-systems/AdapterHijri.js index d867e75aa65e..7e32466b4814 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.js +++ b/docs/data/date-pickers/calendar-systems/AdapterHijri.js @@ -1,31 +1,54 @@ import * as React from 'react'; +import { prefixer } from 'stylis'; +import rtlPlugin from 'stylis-plugin-rtl'; +import createCache from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; import moment from 'moment-hijri'; import { AdapterMomentHijri } from '@mui/x-date-pickers/AdapterMomentHijri'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import useTheme from '@mui/system/useTheme'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; +import { createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; + +// Create rtl cache +const cacheRtl = createCache({ + key: 'adapter-moment-hijri-demo', + stylisPlugins: [prefixer, rtlPlugin], +}); export default function AdapterHijri() { + // Inherit the theme from the docs site (dark/light mode) const existingTheme = useTheme(); + const theme = React.useMemo( - () => createTheme({ direction: 'rtl' }, existingTheme), + () => createTheme(existingTheme, { direction: 'rtl' }), [existingTheme], ); return ( - -
- - - -
-
+ + +
+ + `, you can skip it. + slotProps={{ + desktopPaper: { + dir: 'rtl', + }, + mobilePaper: { + dir: 'rtl', + }, + }} + /> + +
+
+
); } diff --git a/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx b/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx index d867e75aa65e..7e32466b4814 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx +++ b/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx @@ -1,31 +1,54 @@ import * as React from 'react'; +import { prefixer } from 'stylis'; +import rtlPlugin from 'stylis-plugin-rtl'; +import createCache from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; import moment from 'moment-hijri'; import { AdapterMomentHijri } from '@mui/x-date-pickers/AdapterMomentHijri'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import useTheme from '@mui/system/useTheme'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; +import { createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; + +// Create rtl cache +const cacheRtl = createCache({ + key: 'adapter-moment-hijri-demo', + stylisPlugins: [prefixer, rtlPlugin], +}); export default function AdapterHijri() { + // Inherit the theme from the docs site (dark/light mode) const existingTheme = useTheme(); + const theme = React.useMemo( - () => createTheme({ direction: 'rtl' }, existingTheme), + () => createTheme(existingTheme, { direction: 'rtl' }), [existingTheme], ); return ( - -
- - - -
-
+ + +
+ + `, you can skip it. + slotProps={{ + desktopPaper: { + dir: 'rtl', + }, + mobilePaper: { + dir: 'rtl', + }, + }} + /> + +
+
+
); } diff --git a/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx.preview b/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx.preview deleted file mode 100644 index 4c401c580fd9..000000000000 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx.preview +++ /dev/null @@ -1,13 +0,0 @@ - -
- - - -
-
\ No newline at end of file diff --git a/docs/data/date-pickers/calendar-systems/AdapterJalali.js b/docs/data/date-pickers/calendar-systems/AdapterJalali.js index 0f5994a1dddb..4265db92ef69 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterJalali.js +++ b/docs/data/date-pickers/calendar-systems/AdapterJalali.js @@ -1,27 +1,50 @@ import * as React from 'react'; +import { prefixer } from 'stylis'; +import rtlPlugin from 'stylis-plugin-rtl'; +import createCache from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; import { AdapterDateFnsJalali } from '@mui/x-date-pickers/AdapterDateFnsJalali'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import useTheme from '@mui/system/useTheme'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; +import { createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; + +// Create rtl cache +const cacheRtl = createCache({ + key: 'adapter-date-fns-jalali-demo', + stylisPlugins: [prefixer, rtlPlugin], +}); export default function AdapterJalali() { + // Inherit the theme from the docs site (dark/light mode) const existingTheme = useTheme(); + const theme = React.useMemo( - () => createTheme({ direction: 'rtl' }, existingTheme), + () => createTheme(existingTheme, { direction: 'rtl' }), [existingTheme], ); return ( - -
- - - -
-
+ + +
+ + `, you can skip it. + slotProps={{ + desktopPaper: { + dir: 'rtl', + }, + mobilePaper: { + dir: 'rtl', + }, + }} + /> + +
+
+
); } diff --git a/docs/data/date-pickers/calendar-systems/AdapterJalali.tsx b/docs/data/date-pickers/calendar-systems/AdapterJalali.tsx index 0f5994a1dddb..4265db92ef69 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterJalali.tsx +++ b/docs/data/date-pickers/calendar-systems/AdapterJalali.tsx @@ -1,27 +1,50 @@ import * as React from 'react'; +import { prefixer } from 'stylis'; +import rtlPlugin from 'stylis-plugin-rtl'; +import createCache from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; import { AdapterDateFnsJalali } from '@mui/x-date-pickers/AdapterDateFnsJalali'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import useTheme from '@mui/system/useTheme'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; +import { createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; + +// Create rtl cache +const cacheRtl = createCache({ + key: 'adapter-date-fns-jalali-demo', + stylisPlugins: [prefixer, rtlPlugin], +}); export default function AdapterJalali() { + // Inherit the theme from the docs site (dark/light mode) const existingTheme = useTheme(); + const theme = React.useMemo( - () => createTheme({ direction: 'rtl' }, existingTheme), + () => createTheme(existingTheme, { direction: 'rtl' }), [existingTheme], ); return ( - -
- - - -
-
+ + +
+ + `, you can skip it. + slotProps={{ + desktopPaper: { + dir: 'rtl', + }, + mobilePaper: { + dir: 'rtl', + }, + }} + /> + +
+
+
); } diff --git a/docs/data/date-pickers/calendar-systems/AdapterJalali.tsx.preview b/docs/data/date-pickers/calendar-systems/AdapterJalali.tsx.preview deleted file mode 100644 index f6861e10a633..000000000000 --- a/docs/data/date-pickers/calendar-systems/AdapterJalali.tsx.preview +++ /dev/null @@ -1,10 +0,0 @@ - -
- - - -
-
\ No newline at end of file diff --git a/docs/data/date-pickers/calendar-systems/AdapterMomentJalali.js b/docs/data/date-pickers/calendar-systems/AdapterMomentJalali.js index 45295e2a1879..26c27f04ac2d 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterMomentJalali.js +++ b/docs/data/date-pickers/calendar-systems/AdapterMomentJalali.js @@ -1,30 +1,51 @@ import * as React from 'react'; +import { prefixer } from 'stylis'; +import rtlPlugin from 'stylis-plugin-rtl'; +import createCache from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; import moment from 'moment-jalaali'; import { AdapterMomentJalaali } from '@mui/x-date-pickers/AdapterMomentJalaali'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import useTheme from '@mui/system/useTheme'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; +import { createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; -export default function AdapterMomentJalali() { - moment.loadPersian({ dialect: 'persian-modern' }); +// Create rtl cache +const cacheRtl = createCache({ + key: 'adapter-moment-jalali-demo', + stylisPlugins: [prefixer, rtlPlugin], +}); +export default function AdapterMomentJalali() { + // Inherit the theme from the docs site (dark/light mode) const existingTheme = useTheme(); + const theme = React.useMemo( - () => createTheme({ direction: 'rtl' }, existingTheme), + () => createTheme(existingTheme, { direction: 'rtl' }), [existingTheme], ); return ( - -
- - - -
-
+ + +
+ + `, you can skip it. + slotProps={{ + desktopPaper: { + dir: 'rtl', + }, + mobilePaper: { + dir: 'rtl', + }, + }} + /> + +
+
+
); } diff --git a/docs/data/date-pickers/calendar-systems/AdapterMomentJalali.tsx b/docs/data/date-pickers/calendar-systems/AdapterMomentJalali.tsx index 45295e2a1879..26c27f04ac2d 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterMomentJalali.tsx +++ b/docs/data/date-pickers/calendar-systems/AdapterMomentJalali.tsx @@ -1,30 +1,51 @@ import * as React from 'react'; +import { prefixer } from 'stylis'; +import rtlPlugin from 'stylis-plugin-rtl'; +import createCache from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; import moment from 'moment-jalaali'; import { AdapterMomentJalaali } from '@mui/x-date-pickers/AdapterMomentJalaali'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import useTheme from '@mui/system/useTheme'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; +import { createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; -export default function AdapterMomentJalali() { - moment.loadPersian({ dialect: 'persian-modern' }); +// Create rtl cache +const cacheRtl = createCache({ + key: 'adapter-moment-jalali-demo', + stylisPlugins: [prefixer, rtlPlugin], +}); +export default function AdapterMomentJalali() { + // Inherit the theme from the docs site (dark/light mode) const existingTheme = useTheme(); + const theme = React.useMemo( - () => createTheme({ direction: 'rtl' }, existingTheme), + () => createTheme(existingTheme, { direction: 'rtl' }), [existingTheme], ); return ( - -
- - - -
-
+ + +
+ + `, you can skip it. + slotProps={{ + desktopPaper: { + dir: 'rtl', + }, + mobilePaper: { + dir: 'rtl', + }, + }} + /> + +
+
+
); } diff --git a/docs/data/date-pickers/calendar-systems/AdapterMomentJalali.tsx.preview b/docs/data/date-pickers/calendar-systems/AdapterMomentJalali.tsx.preview deleted file mode 100644 index 4c0ffdf96c37..000000000000 --- a/docs/data/date-pickers/calendar-systems/AdapterMomentJalali.tsx.preview +++ /dev/null @@ -1,10 +0,0 @@ - -
- - - -
-
\ No newline at end of file diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx index bf30b978c580..39a8b2495660 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx @@ -396,12 +396,12 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi if (!isRtl) { return views; } - const digitViews = views.filter((v) => v !== 'meridiem'); - const result: TimeViewWithMeridiem[] = digitViews.toReversed(); + const digitViews: TimeViewWithMeridiem[] = views.filter((v) => v !== 'meridiem'); + digitViews.reverse(); if (views.includes('meridiem')) { - result.push('meridiem'); + digitViews.push('meridiem'); } - return result; + return digitViews; }, [isRtl, views]); const viewTimeOptions = React.useMemo(() => { From 033f897c2f600bb788bd3a74461f0ad642b139c6 Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Mon, 2 Sep 2024 16:18:01 +0200 Subject: [PATCH 5/7] [pickers] Keep the calendar header and content in sync when switching locale (#14125) --- .../src/AdapterDayjs/AdapterDayjs.ts | 9 +++-- .../src/AdapterLuxon/AdapterLuxon.ts | 9 +++-- .../src/AdapterMoment/AdapterMoment.ts | 9 +++-- .../src/DateCalendar/DateCalendar.spec.tsx | 13 -------- .../src/DateCalendar/DayCalendar.tsx | 3 +- .../DateCalendar/tests/DateCalendar.spec.tsx | 11 +++++++ .../tests/localization.DateCalendar.test.tsx | 33 ++++++++++++++++--- 7 files changed, 53 insertions(+), 34 deletions(-) delete mode 100644 packages/x-date-pickers/src/DateCalendar/DateCalendar.spec.tsx diff --git a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts index 1245f256ea80..cf564eb4e6ac 100644 --- a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts +++ b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts @@ -513,7 +513,7 @@ export class AdapterDayjs implements MuiPickersAdapter { }; public startOfWeek = (value: Dayjs) => { - return this.adjustOffset(value.startOf('week')); + return this.adjustOffset(this.setLocaleToValue(value).startOf('week')); }; public startOfDay = (value: Dayjs) => { @@ -529,7 +529,7 @@ export class AdapterDayjs implements MuiPickersAdapter { }; public endOfWeek = (value: Dayjs) => { - return this.adjustOffset(value.endOf('week')); + return this.adjustOffset(this.setLocaleToValue(value).endOf('week')); }; public endOfDay = (value: Dayjs) => { @@ -639,9 +639,8 @@ export class AdapterDayjs implements MuiPickersAdapter { }; public getWeekArray = (value: Dayjs) => { - const cleanValue = this.setLocaleToValue(value); - const start = this.startOfWeek(this.startOfMonth(cleanValue)); - const end = this.endOfWeek(this.endOfMonth(cleanValue)); + const start = this.startOfWeek(this.startOfMonth(value)); + const end = this.endOfWeek(this.endOfMonth(value)); let count = 0; let current = start; diff --git a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts index 3fd21f704315..edaee013c9be 100644 --- a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts +++ b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts @@ -348,7 +348,7 @@ export class AdapterLuxon implements MuiPickersAdapter { }; public startOfWeek = (value: DateTime) => { - return value.startOf('week', { useLocaleWeeks: true }); + return this.setLocaleToValue(value).startOf('week', { useLocaleWeeks: true }); }; public startOfDay = (value: DateTime) => { @@ -364,7 +364,7 @@ export class AdapterLuxon implements MuiPickersAdapter { }; public endOfWeek = (value: DateTime) => { - return value.endOf('week', { useLocaleWeeks: true }); + return this.setLocaleToValue(value).endOf('week', { useLocaleWeeks: true }); }; public endOfDay = (value: DateTime) => { @@ -461,9 +461,8 @@ export class AdapterLuxon implements MuiPickersAdapter { }; public getWeekArray = (value: DateTime) => { - const cleanValue = this.setLocaleToValue(value); - const firstDay = this.startOfWeek(this.startOfMonth(cleanValue)); - const lastDay = this.endOfWeek(this.endOfMonth(cleanValue)); + const firstDay = this.startOfWeek(this.startOfMonth(value)); + const lastDay = this.endOfWeek(this.endOfMonth(value)); const { days } = lastDay.diff(firstDay, 'days').toObject(); diff --git a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts index 57ba9986e51f..a6e5288f2ed9 100644 --- a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts +++ b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts @@ -390,7 +390,7 @@ export class AdapterMoment implements MuiPickersAdapter { }; public startOfWeek = (value: Moment) => { - return value.clone().startOf('week'); + return this.setLocaleToValue(value.clone()).startOf('week'); }; public startOfDay = (value: Moment) => { @@ -406,7 +406,7 @@ export class AdapterMoment implements MuiPickersAdapter { }; public endOfWeek = (value: Moment) => { - return value.clone().endOf('week'); + return this.setLocaleToValue(value.clone()).endOf('week'); }; public endOfDay = (value: Moment) => { @@ -516,9 +516,8 @@ export class AdapterMoment implements MuiPickersAdapter { }; public getWeekArray = (value: Moment) => { - const cleanValue = this.setLocaleToValue(value); - const start = this.startOfWeek(this.startOfMonth(cleanValue)); - const end = this.endOfWeek(this.endOfMonth(cleanValue)); + const start = this.startOfWeek(this.startOfMonth(value)); + const end = this.endOfWeek(this.endOfMonth(value)); let count = 0; let current = start; diff --git a/packages/x-date-pickers/src/DateCalendar/DateCalendar.spec.tsx b/packages/x-date-pickers/src/DateCalendar/DateCalendar.spec.tsx deleted file mode 100644 index 7fbd799e2208..000000000000 --- a/packages/x-date-pickers/src/DateCalendar/DateCalendar.spec.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as React from 'react'; -import moment, { Moment } from 'moment'; -import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; - -// External components are generic as well - - view="day" - views={['day']} - value={moment()} - minDate={moment()} - maxDate={moment()} - onChange={(date) => date?.format()} -/>; diff --git a/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx b/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx index f1a173e45249..31d01710d5bf 100644 --- a/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx +++ b/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx @@ -502,7 +502,6 @@ export function DayCalendar(inProps: DayCalendarP const transitionKey = `${currentYearNumber}-${currentMonthNumber}`; // eslint-disable-next-line react-hooks/exhaustive-deps const slideNodeRef = React.useMemo(() => React.createRef(), [transitionKey]); - const startOfCurrentWeek = utils.startOfWeek(now); const focusableDay = React.useMemo(() => { const startOfMonth = utils.startOfMonth(currentMonth); @@ -574,7 +573,7 @@ export function DayCalendar(inProps: DayCalendarP key={i.toString()} variant="caption" role="columnheader" - aria-label={utils.format(utils.addDays(startOfCurrentWeek, i), 'weekday')} + aria-label={utils.format(weekday, 'weekday')} className={classes.weekDayLabel} > {dayOfWeekFormatter(weekday)} diff --git a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.spec.tsx b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.spec.tsx index bd049cba6fd9..53ff42ee719a 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.spec.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.spec.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import moment, { Moment } from 'moment'; import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; ; @@ -8,3 +9,13 @@ import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; ; ; + +// External components are generic as well + + view="day" + views={['day']} + value={moment()} + minDate={moment()} + maxDate={moment()} + onChange={(date) => date?.format()} +/>; diff --git a/packages/x-date-pickers/src/DateCalendar/tests/localization.DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/localization.DateCalendar.test.tsx index 6b4105506de2..90d8c9bc6040 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/localization.DateCalendar.test.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/localization.DateCalendar.test.tsx @@ -1,11 +1,14 @@ import * as React from 'react'; import { expect } from 'chai'; -import { screen } from '@mui/internal-test-utils'; -import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; -import { createPickerRenderer, AdapterName } from 'test/utils/pickers'; -import { he } from 'date-fns/locale'; +import { screen, createRenderer } from '@mui/internal-test-utils'; +import { DateCalendar, dayCalendarClasses } from '@mui/x-date-pickers/DateCalendar'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { createPickerRenderer, AdapterName, availableAdapters } from 'test/utils/pickers'; +import { he, fr } from 'date-fns/locale'; import 'dayjs/locale/he'; +import 'dayjs/locale/fr'; import 'moment/locale/he'; +import 'moment/locale/fr'; const ADAPTERS_TO_USE: AdapterName[] = ['date-fns', 'dayjs', 'luxon', 'moment']; @@ -17,11 +20,33 @@ describe(' - localization', () => { adapterName, }); + const { render: renderWithoutWrapper } = createRenderer(); + it('should display correct week day labels in Hebrew locale ', () => { render(); expect(screen.getByText('א')).toBeVisible(); }); + + it('should correctly switch between locale with week starting in Monday and week starting in Sunday', () => { + const { setProps } = renderWithoutWrapper( + + + , + ); + + expect(document.querySelector(`.${dayCalendarClasses.weekDayLabel}`)!.ariaLabel).to.equal( + 'Sunday', + ); + + setProps({ + adapterLocale: adapterName === 'date-fns' ? fr : 'fr', + }); + + expect(document.querySelector(`.${dayCalendarClasses.weekDayLabel}`)!.ariaLabel).to.equal( + 'lundi', + ); + }); }); }); }); From 904efc407da46c73022577b3967300811950ed5a Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Mon, 2 Sep 2024 16:18:14 +0200 Subject: [PATCH 6/7] [TreeView] Clean label editing code (#14264) --- .../x-tree-view/src/TreeItem/TreeItem.tsx | 34 ++++++--- .../src/TreeItem/TreeItemContent.tsx | 45 +++--------- .../TreeItem2LabelInput.types.ts | 11 ++- .../useTreeItem2Utils/useTreeItem2Utils.tsx | 2 +- .../src/internals/models/itemPlugin.ts | 6 ++ .../useTreeViewLabel.itemPlugin.ts | 38 ++++++++-- .../useTreeViewLabel.types.ts | 9 ++- .../src/useTreeItem2/useTreeItem2.ts | 70 ++++++------------- .../src/useTreeItem2/useTreeItem2.types.ts | 5 +- scripts/x-tree-view-pro.exports.json | 2 +- scripts/x-tree-view.exports.json | 2 +- 11 files changed, 115 insertions(+), 109 deletions(-) diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.tsx b/packages/x-tree-view/src/TreeItem/TreeItem.tsx index 2be1daaa83f5..6831bcee8055 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItem.tsx @@ -27,6 +27,7 @@ import { TreeItem2Provider } from '../TreeItem2Provider'; import { TreeViewItemDepthContext } from '../internals/TreeViewItemDepthContext'; import { useTreeItemState } from './useTreeItemState'; import { isTargetInDescendants } from '../internals/utils/tree'; +import { TreeViewItemPluginSlotPropsEnhancerParams } from '../internals/models'; const useThemeProps = createUseThemeProps('MuiTreeItem'); @@ -221,8 +222,16 @@ export const TreeItem = React.forwardRef(function TreeItem( ...other } = props; - const { expanded, focused, selected, disabled, editing, handleExpansion } = - useTreeItemState(itemId); + const { + expanded, + focused, + selected, + disabled, + editing, + handleExpansion, + handleCancelItemLabelEditing, + handleSaveItemLabel, + } = useTreeItemState(itemId); const { contentRef, rootRef, propsEnhancers } = runItemPlugins(props); const rootRefObject = React.useRef(null); @@ -375,28 +384,33 @@ export const TreeItem = React.forwardRef(function TreeItem( const idAttribute = instance.getTreeItemIdAttribute(itemId, id); const tabIndex = instance.canItemBeTabbed(itemId) ? 0 : -1; + const sharedPropsEnhancerParams: Omit< + TreeViewItemPluginSlotPropsEnhancerParams, + 'externalEventHandlers' + > = { + rootRefObject, + contentRefObject, + interactions: { handleSaveItemLabel, handleCancelItemLabelEditing }, + }; + const enhancedRootProps = propsEnhancers.root?.({ - rootRefObject, - contentRefObject, + ...sharedPropsEnhancerParams, externalEventHandlers: extractEventHandlers(other), }) ?? {}; const enhancedContentProps = propsEnhancers.content?.({ - rootRefObject, - contentRefObject, + ...sharedPropsEnhancerParams, externalEventHandlers: extractEventHandlers(ContentProps), }) ?? {}; const enhancedDragAndDropOverlayProps = propsEnhancers.dragAndDropOverlay?.({ - rootRefObject, - contentRefObject, + ...sharedPropsEnhancerParams, externalEventHandlers: {}, }) ?? {}; const enhancedLabelInputProps = propsEnhancers.labelInput?.({ - rootRefObject, - contentRefObject, + ...sharedPropsEnhancerParams, externalEventHandlers: {}, }) ?? {}; diff --git a/packages/x-tree-view/src/TreeItem/TreeItemContent.tsx b/packages/x-tree-view/src/TreeItem/TreeItemContent.tsx index 2c62bd29c452..247c593f687d 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItemContent.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItemContent.tsx @@ -103,8 +103,6 @@ const TreeItemContent = React.forwardRef(function TreeItemContent( preventSelection, expansionTrigger, toggleItemEditing, - handleSaveItemLabel, - handleCancelItemLabelEditing, } = useTreeItemState(itemId); const icon = iconProp || expansionIcon || displayIcon; @@ -144,32 +142,6 @@ const TreeItemContent = React.forwardRef(function TreeItemContent( } toggleItemEditing(); }; - const handleLabelInputBlur = ( - event: React.FocusEvent & MuiCancellableEvent, - ) => { - if (event.defaultMuiPrevented) { - return; - } - - if (event.target.value) { - handleSaveItemLabel(event, event.target.value); - } - }; - - const handleLabelInputKeydown = ( - event: React.KeyboardEvent & MuiCancellableEvent, - ) => { - if (event.defaultMuiPrevented) { - return; - } - - const target = event.target as HTMLInputElement; - if (event.key === 'Enter' && target.value) { - handleSaveItemLabel(event, target.value); - } else if (event.key === 'Escape') { - handleCancelItemLabelEditing(event); - } - }; return ( /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions -- Key event is handled by the TreeView */ @@ -200,12 +172,7 @@ const TreeItemContent = React.forwardRef(function TreeItemContent( )} {editing ? ( - + ) : (
{label} @@ -251,7 +218,15 @@ TreeItemContent.propTypes = { * The tree item label. */ label: PropTypes.node, - labelInputProps: PropTypes.object, + labelInputProps: PropTypes.shape({ + autoFocus: PropTypes.oneOf([true]), + 'data-element': PropTypes.oneOf(['labelInput']), + onBlur: PropTypes.func, + onChange: PropTypes.func, + onKeyDown: PropTypes.func, + type: PropTypes.oneOf(['text']), + value: PropTypes.string, + }), } as any; export { TreeItemContent }; diff --git a/packages/x-tree-view/src/TreeItem2LabelInput/TreeItem2LabelInput.types.ts b/packages/x-tree-view/src/TreeItem2LabelInput/TreeItem2LabelInput.types.ts index 03e2414054e9..099b8c5b2b5e 100644 --- a/packages/x-tree-view/src/TreeItem2LabelInput/TreeItem2LabelInput.types.ts +++ b/packages/x-tree-view/src/TreeItem2LabelInput/TreeItem2LabelInput.types.ts @@ -1,8 +1,15 @@ -export interface TreeItem2LabelInputProps extends React.InputHTMLAttributes { +import * as React from 'react'; +import { MuiCancellableEventHandler } from '../internals/models/MuiCancellableEvent'; + +export interface TreeItem2LabelInputProps { value?: string; - onChange?: React.ChangeEventHandler; /** * Used to determine if the target of keydown or blur events is the input and prevent the event from propagating to the root. */ 'data-element'?: 'labelInput'; + onChange?: React.ChangeEventHandler; + onKeyDown?: MuiCancellableEventHandler>; + onBlur?: MuiCancellableEventHandler>; + autoFocus?: true; + type?: 'text'; } diff --git a/packages/x-tree-view/src/hooks/useTreeItem2Utils/useTreeItem2Utils.tsx b/packages/x-tree-view/src/hooks/useTreeItem2Utils/useTreeItem2Utils.tsx index fb2d994d28c6..245633ecf95e 100644 --- a/packages/x-tree-view/src/hooks/useTreeItem2Utils/useTreeItem2Utils.tsx +++ b/packages/x-tree-view/src/hooks/useTreeItem2Utils/useTreeItem2Utils.tsx @@ -12,7 +12,7 @@ import { import type { UseTreeItem2Status } from '../../useTreeItem2'; import { hasPlugin } from '../../internals/utils/plugins'; -interface UseTreeItem2Interactions { +export interface UseTreeItem2Interactions { handleExpansion: (event: React.MouseEvent) => void; handleSelection: (event: React.MouseEvent) => void; handleCheckboxSelection: (event: React.ChangeEvent) => void; diff --git a/packages/x-tree-view/src/internals/models/itemPlugin.ts b/packages/x-tree-view/src/internals/models/itemPlugin.ts index ad02c708dcff..c0858f84d960 100644 --- a/packages/x-tree-view/src/internals/models/itemPlugin.ts +++ b/packages/x-tree-view/src/internals/models/itemPlugin.ts @@ -6,11 +6,17 @@ import type { UseTreeItem2LabelInputSlotOwnProps, UseTreeItem2RootSlotOwnProps, } from '../../useTreeItem2'; +import type { UseTreeItem2Interactions } from '../../hooks/useTreeItem2Utils/useTreeItem2Utils'; export interface TreeViewItemPluginSlotPropsEnhancerParams { rootRefObject: React.MutableRefObject; contentRefObject: React.MutableRefObject; externalEventHandlers: EventHandlers; + // TODO v9: Remove "Pick" once the old TreeItem is removed. + interactions: Pick< + UseTreeItem2Interactions, + 'handleSaveItemLabel' | 'handleCancelItemLabelEditing' + >; } type TreeViewItemPluginSlotPropsEnhancer = ( diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.ts index 96e143e02dbf..3a663be337b8 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.ts @@ -1,14 +1,12 @@ import * as React from 'react'; import { useTreeViewContext } from '../../TreeViewProvider'; -import { TreeViewItemPlugin } from '../../models'; +import { MuiCancellableEvent, TreeViewItemPlugin } from '../../models'; import { UseTreeViewItemsSignature } from '../useTreeViewItems'; import { - UseTreeItem2LabelInputSlotPropsFromItemsReordering, + UseTreeItem2LabelInputSlotPropsFromLabelEditing, UseTreeViewLabelSignature, } from './useTreeViewLabel.types'; -export const isAndroid = () => navigator.userAgent.toLowerCase().includes('android'); - export const useTreeViewLabelItemPlugin: TreeViewItemPlugin = ({ props }) => { const { instance } = useTreeViewContext<[UseTreeViewItemsSignature, UseTreeViewLabelSignature]>(); const { label, itemId } = props; @@ -27,13 +25,41 @@ export const useTreeViewLabelItemPlugin: TreeViewItemPlugin = ({ props }) = propsEnhancers: { labelInput: ({ externalEventHandlers, - }): UseTreeItem2LabelInputSlotPropsFromItemsReordering => { + interactions, + }): UseTreeItem2LabelInputSlotPropsFromLabelEditing => { const editable = instance.isItemEditable(itemId); if (!editable) { return {}; } + const handleKeydown = ( + event: React.KeyboardEvent & MuiCancellableEvent, + ) => { + externalEventHandlers.onKeyDown?.(event); + if (event.defaultMuiPrevented) { + return; + } + const target = event.target as HTMLInputElement; + + if (event.key === 'Enter' && target.value) { + interactions.handleSaveItemLabel(event, target.value); + } else if (event.key === 'Escape') { + interactions.handleCancelItemLabelEditing(event); + } + }; + + const handleBlur = (event: React.FocusEvent & MuiCancellableEvent) => { + externalEventHandlers.onBlur?.(event); + if (event.defaultMuiPrevented) { + return; + } + + if (event.target.value) { + interactions.handleSaveItemLabel(event, event.target.value); + } + }; + const handleInputChange = (event: React.ChangeEvent) => { externalEventHandlers.onChange?.(event); setLabelInputValue(event.target.value); @@ -43,6 +69,8 @@ export const useTreeViewLabelItemPlugin: TreeViewItemPlugin = ({ props }) = value: labelInputValue ?? '', 'data-element': 'labelInput', onChange: handleInputChange, + onKeyDown: handleKeydown, + onBlur: handleBlur, autoFocus: true, type: 'text', }; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.ts index 37a348a2da61..12f5296ef61a 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.ts @@ -76,5 +76,10 @@ export type UseTreeViewLabelSignature = TreeViewPluginSignature<{ experimentalFeatures: 'labelEditing'; dependencies: [UseTreeViewItemsSignature]; }>; -export interface UseTreeItem2LabelInputSlotPropsFromItemsReordering - extends TreeItem2LabelInputProps {} + +export interface UseTreeItem2LabelInputSlotPropsFromLabelEditing extends TreeItem2LabelInputProps {} + +declare module '@mui/x-tree-view/useTreeItem2' { + interface UseTreeItem2LabelInputSlotOwnProps + extends UseTreeItem2LabelInputSlotPropsFromLabelEditing {} +} diff --git a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts b/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts index e9424c9d28f1..64c696d245f0 100644 --- a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts +++ b/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts @@ -19,7 +19,10 @@ import { UseTreeItem2ContentSlotPropsFromUseTreeItem, } from './useTreeItem2.types'; import { useTreeViewContext } from '../internals/TreeViewProvider'; -import { MuiCancellableEvent } from '../internals/models'; +import { + MuiCancellableEvent, + TreeViewItemPluginSlotPropsEnhancerParams, +} from '../internals/models'; import { useTreeItem2Utils } from '../hooks/useTreeItem2Utils'; import { TreeViewItemDepthContext } from '../internals/TreeViewItemDepthContext'; import { isTargetInDescendants } from '../internals/utils/tree'; @@ -52,6 +55,11 @@ export const useTreeItem2 = < const checkboxRef = React.useRef(null); const rootTabIndex = instance.canItemBeTabbed(itemId) ? 0 : -1; + const sharedPropsEnhancerParams: Omit< + TreeViewItemPluginSlotPropsEnhancerParams, + 'externalEventHandlers' + > = { rootRefObject, contentRefObject, interactions }; + const createRootHandleFocus = (otherHandlers: EventHandlers) => (event: React.FocusEvent & MuiCancellableEvent) => { @@ -164,35 +172,6 @@ export const useTreeItem2 = < interactions.handleCheckboxSelection(event); }; - const createInputHandleKeydown = - (otherHandlers: EventHandlers) => - (event: React.KeyboardEvent & MuiCancellableEvent) => { - otherHandlers.onKeyDown?.(event); - if (event.defaultMuiPrevented) { - return; - } - const target = event.target as HTMLInputElement; - - if (event.key === 'Enter' && target.value) { - interactions.handleSaveItemLabel(event, target.value); - } else if (event.key === 'Escape') { - interactions.handleCancelItemLabelEditing(event); - } - }; - - const createInputHandleBlur = - (otherHandlers: EventHandlers) => - (event: React.FocusEvent & MuiCancellableEvent) => { - otherHandlers.onBlur?.(event); - if (event.defaultMuiPrevented) { - return; - } - - if (event.target.value) { - interactions.handleSaveItemLabel(event, event.target.value); - } - }; - const createIconContainerHandleClick = (otherHandlers: EventHandlers) => (event: React.MouseEvent & MuiCancellableEvent) => { otherHandlers.onClick?.(event); @@ -248,7 +227,7 @@ export const useTreeItem2 = < } const enhancedRootProps = - propsEnhancers.root?.({ rootRefObject, contentRefObject, externalEventHandlers }) ?? {}; + propsEnhancers.root?.({ ...sharedPropsEnhancerParams, externalEventHandlers }) ?? {}; return { ...props, @@ -275,7 +254,7 @@ export const useTreeItem2 = < } const enhancedContentProps = - propsEnhancers.content?.({ rootRefObject, contentRefObject, externalEventHandlers }) ?? {}; + propsEnhancers.content?.({ ...sharedPropsEnhancerParams, externalEventHandlers }) ?? {}; return { ...props, @@ -326,19 +305,17 @@ export const useTreeItem2 = < ): UseTreeItem2LabelInputSlotProps => { const externalEventHandlers = extractEventHandlers(externalProps); - const props = { - ...externalEventHandlers, - ...externalProps, - onKeyDown: createInputHandleKeydown(externalEventHandlers), - onBlur: createInputHandleBlur(externalEventHandlers), - }; - - const enhancedlabelInputProps = - propsEnhancers.labelInput?.({ rootRefObject, contentRefObject, externalEventHandlers }) ?? {}; + const enhancedLabelInputProps = + propsEnhancers.labelInput?.({ + rootRefObject, + contentRefObject, + externalEventHandlers, + interactions, + }) ?? {}; return { - ...props, - ...enhancedlabelInputProps, + ...externalProps, + ...enhancedLabelInputProps, } as UseTreeItem2LabelInputSlotProps; }; @@ -379,14 +356,11 @@ export const useTreeItem2 = < const getDragAndDropOverlayProps = = {}>( externalProps: ExternalProps = {} as ExternalProps, ): UseTreeItem2DragAndDropOverlaySlotProps => { - const externalEventHandlers = { - ...extractEventHandlers(externalProps), - }; + const externalEventHandlers = extractEventHandlers(externalProps); const enhancedDragAndDropOverlayProps = propsEnhancers.dragAndDropOverlay?.({ - rootRefObject, - contentRefObject, + ...sharedPropsEnhancerParams, externalEventHandlers, }) ?? {}; diff --git a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.types.ts b/packages/x-tree-view/src/useTreeItem2/useTreeItem2.types.ts index e59f98ab285a..9457cc157250 100644 --- a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.types.ts +++ b/packages/x-tree-view/src/useTreeItem2/useTreeItem2.types.ts @@ -92,10 +92,7 @@ export interface UseTreeItem2LabelSlotOwnProps { export type UseTreeItem2LabelSlotProps = ExternalProps & UseTreeItem2LabelSlotOwnProps; -export type UseTreeItem2LabelInputSlotOwnProps = { - onBlur: MuiCancellableEventHandler>; - onKeyDown: MuiCancellableEventHandler>; -}; +export interface UseTreeItem2LabelInputSlotOwnProps {} export type UseTreeItem2LabelInputSlotProps = ExternalProps & UseTreeItem2LabelInputSlotOwnProps; diff --git a/scripts/x-tree-view-pro.exports.json b/scripts/x-tree-view-pro.exports.json index 1db2fafc7962..16f61d3c4727 100644 --- a/scripts/x-tree-view-pro.exports.json +++ b/scripts/x-tree-view-pro.exports.json @@ -66,7 +66,7 @@ { "name": "UseTreeItem2DragAndDropOverlaySlotOwnProps", "kind": "Interface" }, { "name": "UseTreeItem2GroupTransitionSlotOwnProps", "kind": "Interface" }, { "name": "UseTreeItem2IconContainerSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2LabelInputSlotOwnProps", "kind": "TypeAlias" }, + { "name": "UseTreeItem2LabelInputSlotOwnProps", "kind": "Interface" }, { "name": "UseTreeItem2LabelSlotOwnProps", "kind": "Interface" }, { "name": "UseTreeItem2Parameters", "kind": "Interface" }, { "name": "UseTreeItem2ReturnValue", "kind": "Interface" }, diff --git a/scripts/x-tree-view.exports.json b/scripts/x-tree-view.exports.json index 7599088f3010..49a0d1ae5a7b 100644 --- a/scripts/x-tree-view.exports.json +++ b/scripts/x-tree-view.exports.json @@ -70,7 +70,7 @@ { "name": "UseTreeItem2DragAndDropOverlaySlotOwnProps", "kind": "Interface" }, { "name": "UseTreeItem2GroupTransitionSlotOwnProps", "kind": "Interface" }, { "name": "UseTreeItem2IconContainerSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2LabelInputSlotOwnProps", "kind": "TypeAlias" }, + { "name": "UseTreeItem2LabelInputSlotOwnProps", "kind": "Interface" }, { "name": "UseTreeItem2LabelSlotOwnProps", "kind": "Interface" }, { "name": "UseTreeItem2Parameters", "kind": "Interface" }, { "name": "UseTreeItem2ReturnValue", "kind": "Interface" }, From 2cb93f48795b650025c65c43d359c9373130068f Mon Sep 17 00:00:00 2001 From: Jose C Quintas Jr Date: Mon, 2 Sep 2024 16:48:50 +0200 Subject: [PATCH 7/7] [charts] Pass all props to legend (#14392) --- docs/pages/x/api/charts/charts-legend.json | 12 ++++ .../x/api/charts/default-charts-legend.json | 24 ++++++- docs/pages/x/api/charts/pie-chart.json | 2 +- .../x/api/charts/piecewise-color-legend.json | 25 +++++-- .../charts/charts-legend/charts-legend.json | 8 +++ .../default-charts-legend.json | 15 +++- .../piecewise-color-legend.json | 16 +++-- .../src/BarChartPro/BarChartPro.tsx | 14 ++++ .../src/LineChartPro/LineChartPro.tsx | 14 ++++ .../src/ScatterChartPro/ScatterChartPro.tsx | 14 ++++ packages/x-charts/src/BarChart/BarChart.tsx | 14 ++++ .../src/ChartsLegend/ChartsLegend.tsx | 68 ++++++++++++++----- .../src/ChartsLegend/DefaultChartsLegend.tsx | 11 ++- .../src/ChartsLegend/LegendPerItem.tsx | 21 +++--- .../src/ChartsLegend/PiecewiseColorLegend.tsx | 5 -- packages/x-charts/src/LineChart/LineChart.tsx | 14 ++++ packages/x-charts/src/PieChart/PieChart.tsx | 14 ++++ .../src/ScatterChart/ScatterChart.tsx | 14 ++++ scripts/x-charts-pro.exports.json | 1 + scripts/x-charts.exports.json | 1 + 20 files changed, 256 insertions(+), 51 deletions(-) diff --git a/docs/pages/x/api/charts/charts-legend.json b/docs/pages/x/api/charts/charts-legend.json index c11a1cc616f9..ce69858fcc06 100644 --- a/docs/pages/x/api/charts/charts-legend.json +++ b/docs/pages/x/api/charts/charts-legend.json @@ -3,6 +3,18 @@ "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, "direction": { "type": { "name": "enum", "description": "'column'
| 'row'" } }, "hidden": { "type": { "name": "bool" }, "default": "false" }, + "itemGap": { "type": { "name": "number" }, "default": "10" }, + "itemMarkHeight": { "type": { "name": "number" }, "default": "20" }, + "itemMarkWidth": { "type": { "name": "number" }, "default": "20" }, + "labelStyle": { "type": { "name": "object" }, "default": "theme.typography.subtitle1" }, + "markGap": { "type": { "name": "number" }, "default": "5" }, + "padding": { + "type": { + "name": "union", + "description": "number
| { bottom?: number, left?: number, right?: number, top?: number }" + }, + "default": "10" + }, "position": { "type": { "name": "shape", diff --git a/docs/pages/x/api/charts/default-charts-legend.json b/docs/pages/x/api/charts/default-charts-legend.json index be48bf222e73..084948ec6dfe 100644 --- a/docs/pages/x/api/charts/default-charts-legend.json +++ b/docs/pages/x/api/charts/default-charts-legend.json @@ -33,22 +33,40 @@ "import { DefaultChartsLegend } from '@mui/x-charts-pro';" ], "classes": [ + { + "key": "column", + "className": "MuiDefaultChartsLegend-column", + "description": "Styles applied to the legend with column layout.", + "isGlobal": false + }, + { + "key": "label", + "className": "MuiDefaultChartsLegend-label", + "description": "Styles applied to the series label.", + "isGlobal": false + }, { "key": "mark", "className": "MuiDefaultChartsLegend-mark", - "description": "", + "description": "Styles applied to series mark element.", "isGlobal": false }, { "key": "root", "className": "MuiDefaultChartsLegend-root", - "description": "", + "description": "Styles applied to the root element.", + "isGlobal": false + }, + { + "key": "row", + "className": "MuiDefaultChartsLegend-row", + "description": "Styles applied to the legend with row layout.", "isGlobal": false }, { "key": "series", "className": "MuiDefaultChartsLegend-series", - "description": "", + "description": "Styles applied to a series element.", "isGlobal": false } ], diff --git a/docs/pages/x/api/charts/pie-chart.json b/docs/pages/x/api/charts/pie-chart.json index cc208bd3f97c..fa14397997df 100644 --- a/docs/pages/x/api/charts/pie-chart.json +++ b/docs/pages/x/api/charts/pie-chart.json @@ -39,7 +39,7 @@ "legend": { "type": { "name": "shape", - "description": "{ classes?: object, direction?: 'column'
| 'row', hidden?: bool, position?: { horizontal: 'left'
| 'middle'
| 'right', vertical: 'bottom'
| 'middle'
| 'top' }, slotProps?: object, slots?: object }" + "description": "{ classes?: object, direction?: 'column'
| 'row', hidden?: bool, itemGap?: number, itemMarkHeight?: number, itemMarkWidth?: number, labelStyle?: object, markGap?: number, padding?: number
| { bottom?: number, left?: number, right?: number, top?: number }, position?: { horizontal: 'left'
| 'middle'
| 'right', vertical: 'bottom'
| 'middle'
| 'top' }, slotProps?: object, slots?: object }" }, "default": "{ direction: 'column', position: { vertical: 'middle', horizontal: 'right' } }", "deprecated": true, diff --git a/docs/pages/x/api/charts/piecewise-color-legend.json b/docs/pages/x/api/charts/piecewise-color-legend.json index cd19020d0cd5..40a8462375db 100644 --- a/docs/pages/x/api/charts/piecewise-color-legend.json +++ b/docs/pages/x/api/charts/piecewise-color-legend.json @@ -20,7 +20,6 @@ "default": "The first axis item." }, "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, - "hidden": { "type": { "name": "bool" }, "default": "false" }, "hideFirst": { "type": { "name": "bool" }, "default": "false" }, "hideLast": { "type": { "name": "bool" }, "default": "false" }, "itemGap": { "type": { "name": "number" }, "default": "10" }, @@ -51,22 +50,40 @@ "import { PiecewiseColorLegend } from '@mui/x-charts-pro';" ], "classes": [ + { + "key": "column", + "className": "MuiPiecewiseColorLegend-column", + "description": "Styles applied to the legend with column layout.", + "isGlobal": false + }, + { + "key": "label", + "className": "MuiPiecewiseColorLegend-label", + "description": "Styles applied to the series label.", + "isGlobal": false + }, { "key": "mark", "className": "MuiPiecewiseColorLegend-mark", - "description": "", + "description": "Styles applied to series mark element.", "isGlobal": false }, { "key": "root", "className": "MuiPiecewiseColorLegend-root", - "description": "", + "description": "Styles applied to the root element.", + "isGlobal": false + }, + { + "key": "row", + "className": "MuiPiecewiseColorLegend-row", + "description": "Styles applied to the legend with row layout.", "isGlobal": false }, { "key": "series", "className": "MuiPiecewiseColorLegend-series", - "description": "", + "description": "Styles applied to a series element.", "isGlobal": false } ], diff --git a/docs/translations/api-docs/charts/charts-legend/charts-legend.json b/docs/translations/api-docs/charts/charts-legend/charts-legend.json index f48685ad6338..91893688a7a6 100644 --- a/docs/translations/api-docs/charts/charts-legend/charts-legend.json +++ b/docs/translations/api-docs/charts/charts-legend/charts-legend.json @@ -6,6 +6,14 @@ "description": "The direction of the legend layout. The default depends on the chart." }, "hidden": { "description": "Set to true to hide the legend." }, + "itemGap": { "description": "Space between two legend items (in px)." }, + "itemMarkHeight": { "description": "Height of the item mark (in px)." }, + "itemMarkWidth": { "description": "Width of the item mark (in px)." }, + "labelStyle": { "description": "Style applied to legend labels." }, + "markGap": { "description": "Space between the mark and the label (in px)." }, + "padding": { + "description": "Legend padding (in px). Can either be a single number, or an object with top, left, bottom, right properties." + }, "position": { "description": "The position of the legend." }, "slotProps": { "description": "The props used for each component slot." }, "slots": { "description": "Overridable component slots." } diff --git a/docs/translations/api-docs/charts/default-charts-legend/default-charts-legend.json b/docs/translations/api-docs/charts/default-charts-legend/default-charts-legend.json index 4f48245b770e..3d15cae316d1 100644 --- a/docs/translations/api-docs/charts/default-charts-legend/default-charts-legend.json +++ b/docs/translations/api-docs/charts/default-charts-legend/default-charts-legend.json @@ -17,8 +17,17 @@ "position": { "description": "The position of the legend." } }, "classDescriptions": { - "mark": { "description": "" }, - "root": { "description": "" }, - "series": { "description": "" } + "column": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the legend with column layout" + }, + "label": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the series label" }, + "mark": { "description": "Styles applied to {{nodeName}}.", "nodeName": "series mark element" }, + "root": { "description": "Styles applied to the root element." }, + "row": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the legend with row layout" + }, + "series": { "description": "Styles applied to {{nodeName}}.", "nodeName": "a series element" } } } diff --git a/docs/translations/api-docs/charts/piecewise-color-legend/piecewise-color-legend.json b/docs/translations/api-docs/charts/piecewise-color-legend/piecewise-color-legend.json index e240ffb1f7e9..5937eed5266a 100644 --- a/docs/translations/api-docs/charts/piecewise-color-legend/piecewise-color-legend.json +++ b/docs/translations/api-docs/charts/piecewise-color-legend/piecewise-color-legend.json @@ -11,7 +11,6 @@ "direction": { "description": "The direction of the legend layout. The default depends on the chart." }, - "hidden": { "description": "Set to true to hide the legend." }, "hideFirst": { "description": "Hide the first item of the legend, corresponding to the [-infinity, min] piece." }, @@ -36,8 +35,17 @@ "position": { "description": "The position of the legend." } }, "classDescriptions": { - "mark": { "description": "" }, - "root": { "description": "" }, - "series": { "description": "" } + "column": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the legend with column layout" + }, + "label": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the series label" }, + "mark": { "description": "Styles applied to {{nodeName}}.", "nodeName": "series mark element" }, + "root": { "description": "Styles applied to the root element." }, + "row": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the legend with row layout" + }, + "series": { "description": "Styles applied to {{nodeName}}.", "nodeName": "a series element" } } } diff --git a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx index e7c1e3c3cac0..b99bb5f4b49a 100644 --- a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx +++ b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx @@ -165,6 +165,20 @@ BarChartPro.propTypes = { classes: PropTypes.object, direction: PropTypes.oneOf(['column', 'row']), hidden: PropTypes.bool, + itemGap: PropTypes.number, + itemMarkHeight: PropTypes.number, + itemMarkWidth: PropTypes.number, + labelStyle: PropTypes.object, + markGap: PropTypes.number, + padding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + top: PropTypes.number, + }), + ]), position: PropTypes.shape({ horizontal: PropTypes.oneOf(['left', 'middle', 'right']).isRequired, vertical: PropTypes.oneOf(['bottom', 'middle', 'top']).isRequired, diff --git a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx index a85dbc3121e5..455c93e70a4f 100644 --- a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx +++ b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx @@ -176,6 +176,20 @@ LineChartPro.propTypes = { classes: PropTypes.object, direction: PropTypes.oneOf(['column', 'row']), hidden: PropTypes.bool, + itemGap: PropTypes.number, + itemMarkHeight: PropTypes.number, + itemMarkWidth: PropTypes.number, + labelStyle: PropTypes.object, + markGap: PropTypes.number, + padding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + top: PropTypes.number, + }), + ]), position: PropTypes.shape({ horizontal: PropTypes.oneOf(['left', 'middle', 'right']).isRequired, vertical: PropTypes.oneOf(['bottom', 'middle', 'top']).isRequired, diff --git a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx index cf9a928faa73..756ca6c207ac 100644 --- a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx +++ b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx @@ -147,6 +147,20 @@ ScatterChartPro.propTypes = { classes: PropTypes.object, direction: PropTypes.oneOf(['column', 'row']), hidden: PropTypes.bool, + itemGap: PropTypes.number, + itemMarkHeight: PropTypes.number, + itemMarkWidth: PropTypes.number, + labelStyle: PropTypes.object, + markGap: PropTypes.number, + padding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + top: PropTypes.number, + }), + ]), position: PropTypes.shape({ horizontal: PropTypes.oneOf(['left', 'middle', 'right']).isRequired, vertical: PropTypes.oneOf(['bottom', 'middle', 'top']).isRequired, diff --git a/packages/x-charts/src/BarChart/BarChart.tsx b/packages/x-charts/src/BarChart/BarChart.tsx index ffb89e8494d4..8e418a819efb 100644 --- a/packages/x-charts/src/BarChart/BarChart.tsx +++ b/packages/x-charts/src/BarChart/BarChart.tsx @@ -232,6 +232,20 @@ BarChart.propTypes = { classes: PropTypes.object, direction: PropTypes.oneOf(['column', 'row']), hidden: PropTypes.bool, + itemGap: PropTypes.number, + itemMarkHeight: PropTypes.number, + itemMarkWidth: PropTypes.number, + labelStyle: PropTypes.object, + markGap: PropTypes.number, + padding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + top: PropTypes.number, + }), + ]), position: PropTypes.shape({ horizontal: PropTypes.oneOf(['left', 'middle', 'right']).isRequired, vertical: PropTypes.oneOf(['bottom', 'middle', 'top']).isRequired, diff --git a/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx b/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx index f2b8d964d7da..ccd77cb2f2d8 100644 --- a/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx +++ b/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx @@ -4,35 +4,32 @@ import useSlotProps from '@mui/utils/useSlotProps'; import composeClasses from '@mui/utils/composeClasses'; import { useThemeProps, useTheme, Theme } from '@mui/material/styles'; import { getSeriesToDisplay } from './utils'; -import { ChartsLegendClasses, getLegendUtilityClass } from './chartsLegendClasses'; +import { getLegendUtilityClass } from './chartsLegendClasses'; import { DefaultizedProps } from '../models/helpers'; import { DefaultChartsLegend, LegendRendererProps } from './DefaultChartsLegend'; import { useDrawingArea } from '../hooks'; import { useSeries } from '../hooks/useSeries'; import { LegendPlacement } from './legend.types'; +export type ChartsLegendPropsBase = Omit< + LegendRendererProps, + keyof LegendPlacement | 'series' | 'seriesToDisplay' | 'drawingArea' +> & + LegendPlacement; + export interface ChartsLegendSlots { /** * Custom rendering of the legend. * @default DefaultChartsLegend */ - legend?: React.JSXElementConstructor; + legend?: React.JSXElementConstructor; } export interface ChartsLegendSlotProps { - legend?: Partial; + legend?: Partial; } -export interface ChartsLegendProps extends LegendPlacement { - /** - * Override or extend the styles applied to the component. - */ - classes?: Partial; - /** - * Set to true to hide the legend. - * @default false - */ - hidden?: boolean; +export interface ChartsLegendProps extends ChartsLegendPropsBase { /** * Overridable component slots. * @default {} @@ -70,7 +67,7 @@ function ChartsLegend(inProps: ChartsLegendProps) { ...props, position: { horizontal: 'middle', vertical: 'top', ...props.position }, }; - const { position, direction, hidden, slots, slotProps } = defaultizedProps; + const { slots, slotProps, ...other } = defaultizedProps; const theme = useTheme(); const classes = useUtilityClasses({ ...defaultizedProps, theme }); @@ -85,12 +82,10 @@ function ChartsLegend(inProps: ChartsLegendProps) { elementType: ChartLegendRender, externalSlotProps: slotProps?.legend, additionalProps: { - position, - direction, + ...other, classes, drawingArea, series, - hidden, seriesToDisplay, }, ownerState: {}, @@ -118,6 +113,45 @@ ChartsLegend.propTypes = { * @default false */ hidden: PropTypes.bool, + /** + * Space between two legend items (in px). + * @default 10 + */ + itemGap: PropTypes.number, + /** + * Height of the item mark (in px). + * @default 20 + */ + itemMarkHeight: PropTypes.number, + /** + * Width of the item mark (in px). + * @default 20 + */ + itemMarkWidth: PropTypes.number, + /** + * Style applied to legend labels. + * @default theme.typography.subtitle1 + */ + labelStyle: PropTypes.object, + /** + * Space between the mark and the label (in px). + * @default 5 + */ + markGap: PropTypes.number, + /** + * Legend padding (in px). + * Can either be a single number, or an object with top, left, bottom, right properties. + * @default 10 + */ + padding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + top: PropTypes.number, + }), + ]), /** * The position of the legend. */ diff --git a/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx b/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx index 2c485452fd04..0ecf4677b103 100644 --- a/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx +++ b/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx @@ -11,10 +11,19 @@ export interface LegendRendererProps extends Omit; + /** + * Set to true to hide the legend. + * @default false + */ + hidden?: boolean; } function DefaultChartsLegend(props: LegendRendererProps) { - const { drawingArea, seriesToDisplay, ...other } = props; + const { drawingArea, seriesToDisplay, hidden, ...other } = props; + + if (hidden) { + return null; + } return ; } diff --git a/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx b/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx index da5ef97569b5..ca5d41222a09 100644 --- a/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx +++ b/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx @@ -3,15 +3,15 @@ import NoSsr from '@mui/material/NoSsr'; import { useTheme, styled } from '@mui/material/styles'; import { useRtl } from '@mui/system/RtlProvider'; import { DrawingArea } from '../context/DrawingProvider'; -import { DefaultizedProps } from '../models/helpers'; import { ChartsText, ChartsTextStyle } from '../ChartsText'; import { CardinalDirections } from '../models/layout'; import { getWordsByLines } from '../internals/getWordsByLines'; -import type { ChartsLegendProps } from './ChartsLegend'; import { GetItemSpaceType, LegendItemParams } from './chartsLegend.types'; import { legendItemPlacements } from './legendItemsPlacement'; import { useDrawingArea } from '../hooks/useDrawingArea'; -import { AnchorPosition, Direction } from './legend.types'; +import { AnchorPosition, Direction, LegendPlacement } from './legend.types'; +import { ChartsLegendClasses } from './chartsLegendClasses'; +import { DefaultizedProps } from '../models/helpers'; export type ChartsLegendRootOwnerState = { position: AnchorPosition; @@ -29,15 +29,15 @@ export const ChartsLegendRoot = styled('g', { })({}); export interface LegendPerItemProps - extends DefaultizedProps< - Omit, - 'direction' | 'position' - > { + extends DefaultizedProps { /** * The ordered array of item to display in the legend. */ itemsToDisplay: LegendItemParams[]; - classes?: Record<'mark' | 'series' | 'root', string>; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial; /** * Style applied to legend labels. * @default theme.typography.subtitle1 @@ -99,7 +99,6 @@ const getStandardizedPadding = (padding: LegendPerItemProps['padding']) => { */ export function LegendPerItem(props: LegendPerItemProps) { const { - hidden, position, direction, itemsToDisplay, @@ -190,10 +189,6 @@ export function LegendPerItem(props: LegendPerItemProps) { } }, [position.vertical, padding.top, padding.bottom, totalHeight, legendHeight]); - if (hidden) { - return null; - } - return ( diff --git a/packages/x-charts/src/ChartsLegend/PiecewiseColorLegend.tsx b/packages/x-charts/src/ChartsLegend/PiecewiseColorLegend.tsx index bfbda6698503..2515d22590ab 100644 --- a/packages/x-charts/src/ChartsLegend/PiecewiseColorLegend.tsx +++ b/packages/x-charts/src/ChartsLegend/PiecewiseColorLegend.tsx @@ -111,11 +111,6 @@ PiecewiseColorLegend.propTypes = { * The default depends on the chart. */ direction: PropTypes.oneOf(['column', 'row']).isRequired, - /** - * Set to true to hide the legend. - * @default false - */ - hidden: PropTypes.bool, /** * Hide the first item of the legend, corresponding to the [-infinity, min] piece. * @default false diff --git a/packages/x-charts/src/LineChart/LineChart.tsx b/packages/x-charts/src/LineChart/LineChart.tsx index 94235ce0c79d..6cb98ffb2d42 100644 --- a/packages/x-charts/src/LineChart/LineChart.tsx +++ b/packages/x-charts/src/LineChart/LineChart.tsx @@ -253,6 +253,20 @@ LineChart.propTypes = { classes: PropTypes.object, direction: PropTypes.oneOf(['column', 'row']), hidden: PropTypes.bool, + itemGap: PropTypes.number, + itemMarkHeight: PropTypes.number, + itemMarkWidth: PropTypes.number, + labelStyle: PropTypes.object, + markGap: PropTypes.number, + padding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + top: PropTypes.number, + }), + ]), position: PropTypes.shape({ horizontal: PropTypes.oneOf(['left', 'middle', 'right']).isRequired, vertical: PropTypes.oneOf(['bottom', 'middle', 'top']).isRequired, diff --git a/packages/x-charts/src/PieChart/PieChart.tsx b/packages/x-charts/src/PieChart/PieChart.tsx index 51fe9e0d87a3..ecf72fbe4758 100644 --- a/packages/x-charts/src/PieChart/PieChart.tsx +++ b/packages/x-charts/src/PieChart/PieChart.tsx @@ -278,6 +278,20 @@ PieChart.propTypes = { classes: PropTypes.object, direction: PropTypes.oneOf(['column', 'row']), hidden: PropTypes.bool, + itemGap: PropTypes.number, + itemMarkHeight: PropTypes.number, + itemMarkWidth: PropTypes.number, + labelStyle: PropTypes.object, + markGap: PropTypes.number, + padding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + top: PropTypes.number, + }), + ]), position: PropTypes.shape({ horizontal: PropTypes.oneOf(['left', 'middle', 'right']).isRequired, vertical: PropTypes.oneOf(['bottom', 'middle', 'top']).isRequired, diff --git a/packages/x-charts/src/ScatterChart/ScatterChart.tsx b/packages/x-charts/src/ScatterChart/ScatterChart.tsx index d8ae6735b52b..c0f6eae56d25 100644 --- a/packages/x-charts/src/ScatterChart/ScatterChart.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterChart.tsx @@ -228,6 +228,20 @@ ScatterChart.propTypes = { classes: PropTypes.object, direction: PropTypes.oneOf(['column', 'row']), hidden: PropTypes.bool, + itemGap: PropTypes.number, + itemMarkHeight: PropTypes.number, + itemMarkWidth: PropTypes.number, + labelStyle: PropTypes.object, + markGap: PropTypes.number, + padding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + top: PropTypes.number, + }), + ]), position: PropTypes.shape({ horizontal: PropTypes.oneOf(['left', 'middle', 'right']).isRequired, vertical: PropTypes.oneOf(['bottom', 'middle', 'top']).isRequired, diff --git a/scripts/x-charts-pro.exports.json b/scripts/x-charts-pro.exports.json index b4aba91475fb..5a6002d4f448 100644 --- a/scripts/x-charts-pro.exports.json +++ b/scripts/x-charts-pro.exports.json @@ -85,6 +85,7 @@ { "name": "ChartsLegendClasses", "kind": "Interface" }, { "name": "ChartsLegendClassKey", "kind": "TypeAlias" }, { "name": "ChartsLegendProps", "kind": "Interface" }, + { "name": "ChartsLegendPropsBase", "kind": "TypeAlias" }, { "name": "ChartsLegendSlotProps", "kind": "Interface" }, { "name": "ChartsLegendSlots", "kind": "Interface" }, { "name": "ChartsOnAxisClickHandler", "kind": "Function" }, diff --git a/scripts/x-charts.exports.json b/scripts/x-charts.exports.json index 0db09b39d5bd..77e4e3e29edc 100644 --- a/scripts/x-charts.exports.json +++ b/scripts/x-charts.exports.json @@ -83,6 +83,7 @@ { "name": "ChartsLegendClasses", "kind": "Interface" }, { "name": "ChartsLegendClassKey", "kind": "TypeAlias" }, { "name": "ChartsLegendProps", "kind": "Interface" }, + { "name": "ChartsLegendPropsBase", "kind": "TypeAlias" }, { "name": "ChartsLegendSlotProps", "kind": "Interface" }, { "name": "ChartsLegendSlots", "kind": "Interface" }, { "name": "ChartsOnAxisClickHandler", "kind": "Function" },