From 649697e160bfa62a3c0908539396195c2dba36fd Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sun, 1 Sep 2024 19:37:09 +0200 Subject: [PATCH 01/14] [docs] Remove notion of seats (#14351) --- docs/data/introduction/licensing/licensing.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/data/introduction/licensing/licensing.md b/docs/data/introduction/licensing/licensing.md index 14142fdb118cc..d5aa641adefa1 100644 --- a/docs/data/introduction/licensing/licensing.md +++ b/docs/data/introduction/licensing/licensing.md @@ -106,21 +106,21 @@ You can also use it for the development of code not intended for production (for You don't need to contact us to use these components for the above cases. You will need to purchase a commercial license in order to remove the watermarks and console warnings. -## How many developer seats do I need? +## How many developer licenses do I need? -The number of seats purchased on your license must correspond to the number of concurrent developers contributing changes to the front-end code of the project that uses MUI X Pro or Premium. +The number of licenses purchased must correspond to the number of concurrent developers contributing changes to the front-end code of the project that uses MUI X Pro or Premium. - **Example 1.** Company 'A' is developing an application named 'AppA'. The app needs to render 10K rows of data in a table and allow users to group, filter, and sort. The dev team adds MUI X Pro to the project to satisfy this requirement. Five front-end and ten back-end developers are working on 'AppA'. Only one developer is tasked with maintaining the Data Grid, but there are five total developers who work on the front-end. - Company 'A' must purchase five seats. + Company 'A' must purchase five licenses. - **Example 2.** A UI development team at Company 'B' creates its own UI library for internal development that includes MUI X Pro components. The teams working on 'AppY' and 'AppZ' both adopt this new library. 'AppY' has five front-end developers, and 'AppZ' has three; additionally, there are two front-end developers on the company's UI development team. - Company 'B' must purchase ten seats. + Company 'B' must purchase ten licenses. This is [the relevant clause in the EULA.](https://mui.com/legal/mui-x-eula/#required-quantity-of-licenses) From 6a406a5823bf36d1a1c92319c31847bdd863e8f4 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sun, 1 Sep 2024 19:49:18 +0200 Subject: [PATCH 02/14] [core] Fix changelog spelling https://en.wikipedia.org/wiki/Changelog. For consistency as we don't hav CHANGE_LOG.md files. --- CHANGELOG.md | 2 +- changelogOld/CHANGELOG.v4.md | 2 +- changelogOld/CHANGELOG.v5.md | 2 +- changelogOld/CHANGELOG.v6.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8373a3661fcb1..72a65f2523895 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Change Log +# Changelog All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. diff --git a/changelogOld/CHANGELOG.v4.md b/changelogOld/CHANGELOG.v4.md index c54c6f19d175d..756445e851a6f 100644 --- a/changelogOld/CHANGELOG.v4.md +++ b/changelogOld/CHANGELOG.v4.md @@ -1,4 +1,4 @@ -# Change Log for v4 releases +# Changelog for v4 releases ## 4.0.0 diff --git a/changelogOld/CHANGELOG.v5.md b/changelogOld/CHANGELOG.v5.md index 553feccf78fd2..324ef65bc5fe1 100644 --- a/changelogOld/CHANGELOG.v5.md +++ b/changelogOld/CHANGELOG.v5.md @@ -1,4 +1,4 @@ -# Change Log for v5 releases +# Changelog for v5 releases ## 5.17.25 diff --git a/changelogOld/CHANGELOG.v6.md b/changelogOld/CHANGELOG.v6.md index a2f1b629a8980..df1260fa0d65c 100644 --- a/changelogOld/CHANGELOG.v6.md +++ b/changelogOld/CHANGELOG.v6.md @@ -1,4 +1,4 @@ -# Change Log for v6 releases +# Changelog for v6 releases ## 6.19.12 From 8ed64847a0c7ecbbb1bf73912f9098bf7ea3ae57 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sun, 1 Sep 2024 22:17:52 +0200 Subject: [PATCH 03/14] [core] Reset permissions for codspeed GitHub Action (#14420) --- .github/workflows/codspeed.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index 3f56d206a17d9..e05f761ab3024 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -17,6 +17,8 @@ on: - 'master' - 'next' +permissions: {} + jobs: benchmarks: name: Benchmarks Charts @@ -26,10 +28,10 @@ jobs: # L3: Run the benchmarks for pull requests with the label 'component: charts' # Yaml syntax looks a little weird, but it is correct. if: >- - ${{ - (github.event_name == 'push') || - (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'component: charts') || - (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'component: charts')) + ${{ + (github.event_name == 'push') || + (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'component: charts') || + (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'component: charts')) }} steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 From a32e9d518480584436dfe30b78524ce96a95321f Mon Sep 17 00:00:00 2001 From: Michel Engelen <32863416+michelengelen@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:13:51 +0200 Subject: [PATCH 04/14] [infra] Added `secrets: inherit` to workflow call (#14454) --- .github/workflows/new-issue-triage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/new-issue-triage.yml b/.github/workflows/new-issue-triage.yml index 8dc6fbf163242..42f94ff7987b2 100644 --- a/.github/workflows/new-issue-triage.yml +++ b/.github/workflows/new-issue-triage.yml @@ -18,6 +18,7 @@ jobs: needs: issue_cleanup if: needs.issue_cleanup.outputs.orderId != '' uses: mui/mui-public/.github/workflows/issues_order-id-validation.yml@master + secrets: inherit with: orderId: ${{ needs.issue_cleanup.outputs.orderId }} permissions: From 315ffa1e163a0bdd54a89909cf1dd7ffa582cd3a Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Mon, 2 Sep 2024 13:57:50 +0200 Subject: [PATCH 05/14] [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 68360d2c72033..1d22e87939585 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 06/14] [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 bf13515b44c83..8332954d9937e 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 07/14] [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 df49ae3bd67f1..4e151e983c04f 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 f25df849a2ad3..15da4f8a4708b 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 b61a7e91d0143..0555cc2d964ed 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 b61a7e91d0143..0555cc2d964ed 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 dac686d77a445..0f617bfbd6f2f 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 de00ca705a95a..8b13dfb3d48b0 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 d12e6dfab6d7a..447224cbb194e 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 d12e6dfab6d7a..447224cbb194e 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 9952d0c2a3fed..b36ace970cfb1 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 0000000000000..98d3642865cf1 --- /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 08/14] [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 0000000000000..6372a1c7a21f1 --- /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 0000000000000..6372a1c7a21f1 --- /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 bf62a4b35c072..99f5e44e7acef 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 d867e75aa65e4..7e32466b48149 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 d867e75aa65e4..7e32466b48149 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 4c401c580fd94..0000000000000 --- 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 0f5994a1dddb0..4265db92ef699 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 0f5994a1dddb0..4265db92ef699 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 f6861e10a6334..0000000000000 --- 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 45295e2a18797..26c27f04ac2d0 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 45295e2a18797..26c27f04ac2d0 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 4c0ffdf96c371..0000000000000 --- 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 bf30b978c5803..39a8b24956603 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 09/14] [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 1245f256ea809..cf564eb4e6acb 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 3fd21f704315a..edaee013c9bee 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 57ba9986e51f6..a6e5288f2ed9d 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 7fbd799e2208e..0000000000000 --- 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 f1a173e452491..31d01710d5bfe 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 bd049cba6fd98..53ff42ee719a0 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 6b4105506de25..90d8c9bc6040b 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 10/14] [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 2be1daaa83f5e..6831bcee80552 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 2c62bd29c452f..247c593f687da 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 03e2414054e93..099b8c5b2b5e0 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 fb2d994d28c6f..245633ecf95eb 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 ad02c708dcffd..c0858f84d9604 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 96e143e02dbf0..3a663be337b80 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 37a348a2da615..12f5296ef61ab 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 e9424c9d28f12..64c696d245f07 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 e59f98ab285a2..9457cc1572509 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 1db2fafc7962b..16f61d3c47275 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 7599088f30106..49a0d1ae5a7b3 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 11/14] [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 c11a1cc616f97..ce69858fcc062 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 be48bf222e739..084948ec6dfed 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 cc208bd3f97c9..fa14397997df0 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 cd19020d0cd50..40a8462375db1 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 f48685ad6338b..91893688a7a6a 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 4f48245b770e5..3d15cae316d15 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 e240ffb1f7e91..5937eed5266aa 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 e7c1e3c3cac0d..b99bb5f4b49ac 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 a85dbc3121e5c..455c93e70a4f7 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 cf9a928faa737..756ca6c207acc 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 ffb89e8494d40..8e418a819efb8 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 f2b8d964d7da8..ccd77cb2f2d8f 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 2c485452fd041..0ecf4677b1038 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 da5ef97569b50..ca5d41222a09c 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 bfbda66985037..2515d22590abc 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 94235ce0c79d6..6cb98ffb2d42f 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 51fe9e0d87a31..ecf72fbe4758e 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 d8ae6735b52b7..c0f6eae56d25d 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 b4aba91475fb0..5a6002d4f4486 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 0db09b39d5bdd..77e4e3e29edc7 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" }, From f7f58b34ba7dac4b24077490a1fd83e26ec0eef2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 19:50:41 +0200 Subject: [PATCH 12/14] Bump stylis to ^4.3.4 (#14323) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docs/package.json | 2 +- pnpm-lock.yaml | 21 +++++++++++++-------- test/package.json | 2 +- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/package.json b/docs/package.json index 9ff42848c707c..63d9d526b6d90 100644 --- a/docs/package.json +++ b/docs/package.json @@ -94,7 +94,7 @@ "rimraf": "^5.0.10", "rxjs": "^7.8.1", "styled-components": "^6.1.12", - "stylis": "^4.3.2", + "stylis": "^4.3.4", "stylis-plugin-rtl": "^2.1.1", "webpack-bundle-analyzer": "^4.10.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4a8b3282f860..f42f53d73f433 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -630,11 +630,11 @@ importers: specifier: ^6.1.12 version: 6.1.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) stylis: - specifier: ^4.3.2 - version: 4.3.2 + specifier: ^4.3.4 + version: 4.3.4 stylis-plugin-rtl: specifier: ^2.1.1 - version: 2.1.1(stylis@4.3.2) + version: 2.1.1(stylis@4.3.4) webpack-bundle-analyzer: specifier: ^4.10.2 version: 4.10.2 @@ -1579,11 +1579,11 @@ importers: specifier: ^7.6.3 version: 7.6.3 stylis: - specifier: ^4.3.2 - version: 4.3.2 + specifier: ^4.3.4 + version: 4.3.4 stylis-plugin-rtl: specifier: ^2.1.1 - version: 2.1.1(stylis@4.3.2) + version: 2.1.1(stylis@4.3.4) test/performance-charts: devDependencies: @@ -9353,6 +9353,9 @@ packages: stylis@4.3.2: resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} + stylis@4.3.4: + resolution: {integrity: sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -19369,15 +19372,17 @@ snapshots: '@babel/core': 7.25.2 babel-plugin-macros: 3.1.0 - stylis-plugin-rtl@2.1.1(stylis@4.3.2): + stylis-plugin-rtl@2.1.1(stylis@4.3.4): dependencies: cssjanus: 2.1.0 - stylis: 4.3.2 + stylis: 4.3.4 stylis@4.2.0: {} stylis@4.3.2: {} + stylis@4.3.4: {} + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.5 diff --git a/test/package.json b/test/package.json index bdd99925b20a8..3351f88826def 100644 --- a/test/package.json +++ b/test/package.json @@ -35,7 +35,7 @@ "react-dom": "^18.3.1", "react-router-dom": "^6.26.1", "semver": "^7.6.3", - "stylis": "^4.3.2", + "stylis": "^4.3.4", "stylis-plugin-rtl": "^2.1.1" } } From 153d3815398330aee824b9db375202d37241875d Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskii Date: Mon, 2 Sep 2024 20:36:22 +0200 Subject: [PATCH 13/14] [DataGrid] Fix error on simultaneous `columns` and `columnGroupingModel` update (#14368) --- .../columnHeaders/useGridColumnHeaders.tsx | 9 ++-- .../tests/columnGrouping.DataGrid.test.tsx | 53 ++++++++++++++++++- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx index ff711ab42c486..1b8d2a88a567c 100644 --- a/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx +++ b/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -28,6 +28,7 @@ import { GridColumnVisibilityModel, gridColumnPositionsSelector, gridVisiblePinnedColumnDefinitionsSelector, + gridColumnLookupSelector, } from '../columns'; import { GridGroupingStructure } from '../columnGrouping/gridColumnGroupsInterfaces'; import { gridColumnGroupsUnwrappedModelSelector } from '../columnGrouping/gridColumnGroupsSelector'; @@ -106,6 +107,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { const columnPositions = useGridSelector(apiRef, gridColumnPositionsSelector); const renderContext = useGridSelector(apiRef, gridRenderContextColumnsSelector); const pinnedColumns = useGridSelector(apiRef, gridVisiblePinnedColumnDefinitionsSelector); + const columnsLookup = useGridSelector(apiRef, gridColumnLookupSelector); const offsetLeft = computeOffsetLeft( columnPositions, renderContext, @@ -392,7 +394,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { firstVisibleColumnIndex, ); const leftOverflow = hiddenGroupColumns.reduce((acc, field) => { - const column = apiRef.current.getColumn(field); + const column = columnsLookup[field]; return acc + (column.computedWidth ?? 0); }, 0); @@ -411,10 +413,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { const headerInfo: HeaderInfo = { groupId, - width: columnFields.reduce( - (acc, field) => acc + apiRef.current.getColumn(field).computedWidth, - 0, - ), + width: columnFields.reduce((acc, field) => acc + columnsLookup[field].computedWidth, 0), fields: columnFields, colIndex: columnIndex, hasFocus, diff --git a/packages/x-data-grid/src/tests/columnGrouping.DataGrid.test.tsx b/packages/x-data-grid/src/tests/columnGrouping.DataGrid.test.tsx index d3ee1dd081c54..7d0a1002e2c43 100644 --- a/packages/x-data-grid/src/tests/columnGrouping.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/columnGrouping.DataGrid.test.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; +import * as ReactDOM from 'react-dom'; import { expect } from 'chai'; -import { createRenderer, ErrorBoundary, screen } from '@mui/internal-test-utils'; +import { createRenderer, ErrorBoundary, fireEvent, screen } from '@mui/internal-test-utils'; import { DataGrid, DataGridProps, GridRowModel, GridColDef } from '@mui/x-data-grid'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); @@ -325,6 +326,56 @@ describe(' - Column grouping', () => { Array.from(row2Headers).map((header) => header.getAttribute('aria-colindex')), ).to.deep.equal(['1', '2', '3']); }); + + // https://github.com/mui/mui-x/issues/13985 + it('should not throw when both `columns` and `columnGroupingModel` are updated twice', () => { + function Demo() { + const [props, setProps] = React.useState< + Pick + >({ + columns: [], + columnGroupingModel: [], + }); + + const handleClick = () => { + ReactDOM.flushSync(() => { + setProps({ + columns: [{ field: `field_0` }], + columnGroupingModel: [{ groupId: 'Group', children: [{ field: `field_0` }] }], + }); + }); + + setProps({ + columns: [{ field: `field_1` }], + columnGroupingModel: [{ groupId: 'Group', children: [{ field: `field_1` }] }], + }); + }; + + return ( +
+ + +
+ ); + } + render(); + + fireEvent.click(screen.getByRole('button', { name: /Update columns/ })); + + const row1Headers = document.querySelectorAll( + '[aria-rowindex="1"] [role="columnheader"]', + ); + const row2Headers = document.querySelectorAll( + '[aria-rowindex="2"] [role="columnheader"]', + ); + + expect( + Array.from(row1Headers).map((header) => header.getAttribute('aria-label')), + ).to.deep.equal(['Group']); + expect( + Array.from(row2Headers).map((header) => header.getAttribute('aria-label')), + ).to.deep.equal(['field_1']); + }); }); // TODO: remove the skip. I failed to test if an error is thrown From 24943b0ec7e94b148dfdbbe828d07c9a9c31d853 Mon Sep 17 00:00:00 2001 From: Jan Potoms <2109932+Janpot@users.noreply.github.com> Date: Tue, 3 Sep 2024 09:30:55 +0200 Subject: [PATCH 14/14] [l10n] Improve Dutch (nl-NL) locale (#14398) Signed-off-by: Jan Potoms <2109932+Janpot@users.noreply.github.com> --- docs/data/data-grid/localization/data.json | 2 +- packages/x-data-grid/src/locales/nlNL.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/data/data-grid/localization/data.json b/docs/data/data-grid/localization/data.json index f8ddeba538511..fab29456d103e 100644 --- a/docs/data/data-grid/localization/data.json +++ b/docs/data/data-grid/localization/data.json @@ -75,7 +75,7 @@ "languageTag": "nl-NL", "importName": "nlNL", "localeName": "Dutch", - "missingKeysCount": 4, + "missingKeysCount": 0, "totalKeysCount": 118, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/nlNL.ts" }, diff --git a/packages/x-data-grid/src/locales/nlNL.ts b/packages/x-data-grid/src/locales/nlNL.ts index c1d65e9cc0db6..d60dd87015c17 100644 --- a/packages/x-data-grid/src/locales/nlNL.ts +++ b/packages/x-data-grid/src/locales/nlNL.ts @@ -39,10 +39,10 @@ const nlNLGrid: Partial = { toolbarExportExcel: 'Downloaden als Excel-bestand', // Columns management text - // columnsManagementSearchTitle: 'Search', - // columnsManagementNoColumns: 'No columns', - // columnsManagementShowHideAllText: 'Show/Hide All', - // columnsManagementReset: 'Reset', + columnsManagementSearchTitle: 'Zoeken', + columnsManagementNoColumns: 'Geen kolommen', + columnsManagementShowHideAllText: 'Toon/Verberg Alle', + columnsManagementReset: 'Reset', // Filter panel text filterPanelAddFilter: 'Filter toevoegen',