diff --git a/.github/workflows/cherry-pick-master-to-v6.yml b/.github/workflows/cherry-pick-master-to-v6.yml deleted file mode 100644 index 1b594e5b82004..0000000000000 --- a/.github/workflows/cherry-pick-master-to-v6.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Cherry pick master to v6 - -on: - pull_request_target: - branches: - - master - types: ['closed'] - -permissions: {} - -jobs: - cherry_pick_to_v6: - runs-on: ubuntu-latest - name: Cherry pick into v6 - permissions: - pull-requests: write - contents: write - if: ${{ contains(github.event.pull_request.labels.*.name, 'needs cherry-pick') && github.event.pull_request.merged == true }} - steps: - - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - with: - fetch-depth: 0 - - name: Cherry pick and create the new PR - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: carloscastrojumo/github-cherry-pick-action@503773289f4a459069c832dc628826685b75b4b3 # v1.0.10 - with: - branch: v6.x - body: 'Cherry-pick of #{old_pull_request_id}' - cherry-pick-branch: ${{ format('cherry-pick-{0}', github.event.number) }} - title: '{old_title} (@${{ github.event.pull_request.user.login }})' - labels: | - cherry-pick diff --git a/.github/workflows/cherry-pick-next-to-master.yml b/.github/workflows/cherry-pick-next-to-master.yml deleted file mode 100644 index 614c58330d3a3..0000000000000 --- a/.github/workflows/cherry-pick-next-to-master.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Cherry pick next to master - -on: - pull_request_target: - branches: - - next - types: ['closed'] - -permissions: {} - -jobs: - cherry_pick_to_master: - runs-on: ubuntu-latest - name: Cherry pick into master - permissions: - pull-requests: write - contents: write - if: ${{ contains(github.event.pull_request.labels.*.name, 'needs cherry-pick') && github.event.pull_request.merged == true }} - steps: - - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - with: - fetch-depth: 0 - - name: Cherry pick and create the new PR - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: carloscastrojumo/github-cherry-pick-action@503773289f4a459069c832dc628826685b75b4b3 # v1.0.10 - with: - branch: master - body: 'Cherry-pick of #{old_pull_request_id}' - cherry-pick-branch: ${{ format('cherry-pick-{0}', github.event.number) }} - title: '{old_title} (@${{ github.event.pull_request.user.login }})' - labels: | - cherry-pick diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ce61542f2bc3a..99c62e16f892d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,10 +16,10 @@ jobs: security-events: write steps: - name: Checkout repository - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 + uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 with: languages: typescript # If you wish to specify custom queries, you can do so here or in a config file. @@ -29,4 +29,4 @@ jobs: # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 + uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index 42a715a8669bf..c0df4ca87ef7b 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -34,12 +34,12 @@ jobs: (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'component: charts')) }} steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 with: run_install: false - name: Use Node.js 20.x - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: node-version: 20 cache: 'pnpm' # https://github.com/actions/setup-node/blob/main/docs/advanced-usage.md#caching-packages-dependencies diff --git a/.github/workflows/create-cherry-pick-pr.yml b/.github/workflows/create-cherry-pick-pr.yml new file mode 100644 index 0000000000000..0d5381c25b9da --- /dev/null +++ b/.github/workflows/create-cherry-pick-pr.yml @@ -0,0 +1,18 @@ +name: Create cherry-pick PR +on: + pull_request_target: + branches: + - 'v*.x' + - 'master' + types: ['closed'] + +permissions: {} + +jobs: + create_pr: + name: Create cherry-pick PR + uses: mui/mui-public/.github/workflows/prs_create-cherry-pick-pr.yml@master + permissions: + contents: write + issues: write + pull-requests: write diff --git a/.github/workflows/l10n.yml b/.github/workflows/l10n.yml index 20da63d2e4742..435a320e346de 100644 --- a/.github/workflows/l10n.yml +++ b/.github/workflows/l10n.yml @@ -17,12 +17,12 @@ jobs: issues: write steps: - run: echo "${{ github.actor }}" - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 with: run_install: false - name: Use Node.js 20.x - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: node-version: 20 cache: 'pnpm' # https://github.com/actions/setup-node/blob/main/docs/advanced-usage.md#caching-packages-dependencies diff --git a/.github/workflows/priority-support-validation-prompt.yml b/.github/workflows/priority-support-validation-prompt.yml index 0210030751087..03db70fae5a10 100644 --- a/.github/workflows/priority-support-validation-prompt.yml +++ b/.github/workflows/priority-support-validation-prompt.yml @@ -31,7 +31,7 @@ jobs: body: | You have created a support request under the ["Priority Support"](https://mui.com/legal/technical-support-sla/#priority-support) terms, which is a paid add-on to MUI X Premium ⏰. Please validate your support key using the link below: - https://tools-public.mui.com/prod/pages/jyhs86t?repo=mui-x&issueId=${{ github.event.issue.number }} + https://tools-public.mui.com/prod/pages/validateSupport?repo=mui-x&issueId=${{ github.event.issue.number }} Do not share your support key in this issue! diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index d4b8443a2a78e..0868a58d28b90 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false @@ -44,6 +44,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 + uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 with: sarif_file: results.sarif diff --git a/.github/workflows/vale-action.yml b/.github/workflows/vale-action.yml index fb8b37aa93fe3..555ecbce6be52 100644 --- a/.github/workflows/vale-action.yml +++ b/.github/workflows/vale-action.yml @@ -12,7 +12,7 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: errata-ai/vale-action@d89dee975228ae261d22c15adcd03578634d429c # v2.1.1 continue-on-error: true # GitHub Action flag needed until https://github.com/errata-ai/vale-action/issues/89 is fixed with: diff --git a/CHANGELOG.md b/CHANGELOG.md index cad4db17e8860..2dac6eb4e0c48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,95 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 7.21.0 + +_Oct 17, 2024_ + +We'd like to offer a big thanks to the 13 contributors who made this release possible. Here are some highlights ✨: + +- 💫 Added [`dataset` prop support for the Scatter Chart component](https://mui.com/x/react-charts/scatter/#using-a-dataset) +- 🐞 Bugfixes +- 📚 Documentation improvements + +Special thanks go out to the community contributors who have helped make this release possible: +@k-rajat19, @kalyan90, @rotembarsela, @wangkailang. +Following are all team members who have contributed to this release: +@arthurbalduini, @cherniavskii, @flaviendelangle, @JCQuintas, @LukasTy, @MBilalShafi, @arminmeh, @romgrk, @KenanYusuf, @oliviertassinari, @samuelsycamore. + + + +### Data Grid + +#### `@mui/x-data-grid@7.21.0` + +- [DataGrid] Fix `onRowSelectionModelChange` firing unnecessarily on initial render (#14909) @MBilalShafi +- [DataGrid] Fix `onRowSelectionModelChange` not being called after row is removed (#14972) @arminmeh +- [DataGrid] Fix pagination scrollbar issue on small zoom (#14911) @cherniavskii +- [DataGrid] Fix scroll jumping (#14929) @romgrk +- [DataGrid] Fix excessive white space at the end of the Data Grid (#14864) @kalyan90 + +#### `@mui/x-data-grid-pro@7.21.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@7.21.0`, plus: + +- [DataGridPro] Fix indeterminate checkbox state for server-side data (#14956) @MBilalShafi +- [DataGridPro] Fix scrolling performance when `rowHeight={undefined}` (#14983) @cherniavskii +- [DataGridPro] List view (#14393) @KenanYusuf @cherniavskii + +#### `@mui/x-data-grid-premium@7.21.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@7.21.0`. + +### Date and Time Pickers + +#### `@mui/x-date-pickers@7.21.0` + +- [pickers] Cleanup `PageUp` and `PageDown` event handlers on time components (#14928) @arthurbalduini +- [pickers] Create the new picker's `ownerState` object (#14889) @flaviendelangle +- [pickers] Fix `PickerValidDate` usage in the Date Range Picker Toolbar (#14925) @flaviendelangle + +#### `@mui/x-date-pickers-pro@7.21.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@7.21.0`. + +### Charts + +#### `@mui/x-charts@7.21.0` + +- [charts] Allow `dataset` to be used with the Scatter Chart (#14915) @JCQuintas +- [charts] Ensure `reduce motion` preference disables animation on page load (#14417) @JCQuintas + +#### `@mui/x-charts-pro@7.0.0-beta.5` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-charts@7.21.0`. + +### Tree View + +#### `@mui/x-tree-view@7.21.0` + +- [TreeView] Fix `alpha()` usage with CSS variables (#14969) @wangkailang +- [TreeView] Fix usage of the `aria-selected` attribute (#14991) @flaviendelangle +- [TreeView] Fix hydration error (#15002) @flaviendelangle + +### `@mui/x-codemod@7.21.0` + +- [codemod] Add a new utility to rename imports (#14919) @flaviendelangle + +### Docs + +- [docs] Add recipe showing how to toggle detail panels on row click (#14666) @k-rajat19 +- [docs] Fix broken link to the validation section in the Data grid component (#14973) @arminmeh +- [docs] Update v5 migration codesandbox @oliviertassinari +- [docs] Enforce component style rules for the Tree View (#14963) @samuelsycamore + +### Core + +- [core] Fix shortcut with localization keyboard (#14220) @rotembarsela +- [core] Fix docs deploy command (#14920) @arminmeh +- [code-infra] Prepare some tests to work in `vitest/playwright` (#14926) @JCQuintas +- [test] Fix `AdapterDayjs` coverage calculation (#14957) @LukasTy +- [test] Fix split infinitive API convention use @oliviertassinari + ## 7.20.0 _Oct 11, 2024_ @@ -79,7 +168,7 @@ Same changes as in `@mui/x-charts@7.20.0`. - [docs] Add custom columns panel demo (#14825) @cherniavskii - [docs] Capitalize all instances of "Data Grid" (#14884) @samuelsycamore - [docs] Divide charts `tooltip` and `highlighting` pages (#14824) @JCQuintas -- [docs] Document the `TreeItem2` component and the `useTreeItem2` hook (#14551) @noraleonte +- [docs] Document the `` component and the `useTreeItem2` hook (#14551) @noraleonte - [docs] Fix column pinning for "Disable detail panel content scroll" section (#14854 and #14885) @kalyan90 - [docs] Fix detail panel demo not working well with pinned columns (#14883) @cherniavskii - [docs] New recipe of a read-only field (#14606) @flaviendelangle @@ -200,7 +289,7 @@ Same changes as in `@mui/x-charts@7.19.0`. - [code-infra] Remove custom playwright installation steps (#14728) @Janpot - [code-infra] Replace or remove all instances of `e` identifier (#14724) @samuelsycamore - [infra] Adds community contribution section to the changelog script (#14799) @michelengelen -- [infra] Fix line break in Stack Overflow message @oliviertassinari +- [infra] Fix line break in Stack Overflow message @oliviertassinari - [test] Fix `Escape` event firing event (#14797) @oliviertassinari ## 7.18.0 @@ -292,7 +381,7 @@ Same changes as in `@mui/x-charts@7.18.0`. - [core] Fix 301 link to Next.js and git diff @oliviertassinari - [core] Fix failing CI on `master` (#14644) @cherniavskii - [core] Fix `package.json` repository rule @oliviertassinari -- [core] MUI X repository moved to a new location @oliviertassinari +- [core] MUI X repository moved to a new location @oliviertassinari - [docs-infra] Strengthen CSP (#14581) @oliviertassinari - [license] Finish renaming of LicensingModel (#14615) @oliviertassinari @@ -316,7 +405,7 @@ We'd like to offer a big thanks to the 12 contributors who made this release pos - [DataGrid] Add "does not equal" and "does not contain" filter operators (#14489) @KenanYusuf - [DataGrid] Add demo to the "Custom columns" page that does not use generator (#13695) @arminmeh -- [DataGrid] Fix Voice Over reading the column name twice (#14482) @arminmeh +- [DataGrid] Fix VoiceOver reading the column name twice (#14482) @arminmeh - [DataGrid] Fix bug in CRUD example (#14513) @michelengelen - [DataGrid] Fix failing jsdom tests caused by `:has()` selectors (#14559) @KenanYusuf - [DataGrid] Refactor string operator filter functions (#14564) @KenanYusuf @@ -367,7 +456,7 @@ Same changes as in `@mui/x-charts@7.17.0`. ### Docs -- [docs] Add missing callout on "Imperative API" tree view sections (#14503) @flaviendelangle +- [docs] Add missing callout on "Imperative API" Tree View sections (#14503) @flaviendelangle - [docs] Fix broken redirection to MUI X v5 @oliviertassinari - [docs] Fix multiple `console.error` messages on `charts` docs (#14554) @JCQuintas - [docs] Fixed typo in Row Grouping recipes (#14549) @Miodini @@ -708,7 +797,7 @@ Same changes as in `@mui/x-charts@7.13.0`. - [core] Fix ESLint issue (#14207) @LukasTy - [core] Fix Netlify build cache issue (#14182) @cherniavskii - [code-infra] Refactor Netlify `cache-docs` plugin setup (#14105) @LukasTy -- [internals] Move utils needed for tree view virtualization to shared package (#14202) @flaviendelangle +- [internals] Move utils needed for Tree View virtualization to shared package (#14202) @flaviendelangle ## 7.12.1 @@ -799,7 +888,7 @@ This expansion of the Pro plan comes with some adjustments to our pricing strate We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨: -- 🎁 Introduce [item reordering using drag and drop](https://mui.com/x/react-tree-view/rich-tree-view/ordering/) on the `RichTreeViewPro` component +- 🎁 Introduce [item reordering using drag and drop](https://mui.com/x/react-tree-view/rich-tree-view/ordering/) on the `` component Item reordering using drag and drop @@ -1136,7 +1225,7 @@ _Jul 5, 2024_ We'd like to offer a big thanks to the 7 contributors who made this release possible. Here are some highlights ✨: - 🔄 Add loading overlay variants, including a skeleton loader option to the Data Grid component. See [Loading overlay docs](https://mui.com/x/react-data-grid/overlays/#loading-overlay) for more details. -- 🌳 Add `selectItem` and `getItemDOMElement` methods to the TreeView component public API +- 🌳 Add `selectItem()` and `getItemDOMElement()` methods to the TreeView component public API - ⛏️ Make the `usePickersTranslations` hook public in the pickers component - 🐞 Bugfixes @@ -1181,7 +1270,7 @@ Same changes as in `@mui/x-date-pickers@7.9.0`. #### `@mui/x-tree-view@7.9.0` -- [TreeView] Add `selectItem` and `getItemDOMElement` methods to the public API (#13485) @flaviendelangle +- [TreeView] Add `selectItem()` and `getItemDOMElement()` methods to the public API (#13485) @flaviendelangle ### Docs @@ -1292,7 +1381,7 @@ Same changes as in `@mui/x-date-pickers@7.8.0`. - [core] Add eslint rule to restrict import from `../internals` root (#13633) @JCQuintas - [docs-infra] Sync `\_app` folder with monorepo (#13582) @Janpot -- [license] Allow usage of charts and tree view pro package for old premium licenses (#13619) @flaviendelangle +- [license] Allow usage of Charts and Tree View Pro package for old premium licenses (#13619) @flaviendelangle ## 7.7.1 @@ -1360,7 +1449,7 @@ Same changes as in `@mui/x-date-pickers@7.7.1`, plus: - [TreeView] Improve typing to support optional dependencies in plugins and in the item (#13523) @flaviendelangle - [TreeView] Move `useTreeViewId` to the core plugins (#13566) @flaviendelangle - [TreeView] Remove unused state from `useTreeViewId` (#13579) @flaviendelangle -- [TreeView] Support `itemId` with escaping characters when using `SimpleTreeView` (#13487) @oukunan +- [TreeView] Support `itemId` with escaping characters when using Simple Tree View (#13487) @oukunan ### Docs @@ -1440,7 +1529,7 @@ Same changes as in `@mui/x-date-pickers@7.7.0`. - [TreeView] Improve TypeScript for plugins (#13380) @flaviendelangle - [TreeView] Improve the typing of the cancelable events (#13152) @flaviendelangle - [TreeView] Prepare support for PigmentCSS (#13412) @flaviendelangle -- [TreeView] Refactor the tree view internals to prepare for headless API (#13311) @flaviendelangle +- [TreeView] Refactor the Tree View internals to prepare for headless API (#13311) @flaviendelangle ### Docs @@ -1455,7 +1544,7 @@ Same changes as in `@mui/x-date-pickers@7.7.0`. - [core] Add `eslint-plugin-react-compiler` experimental version and rules (#13415) @JCQuintas - [core] Minor setup cleanup (#13467) @LukasTy - [infra] Adjust CI setup (#13448) @LukasTy -- [test] Add tests for the custom slots of `TreeItem2` (#13314) @flaviendelangle +- [test] Add tests for the custom slots of `` (#13314) @flaviendelangle ## 7.6.2 @@ -1640,7 +1729,7 @@ _May 23, 2024_ We'd like to offer a big thanks to the 6 contributors who made this release possible. Here are some highlights ✨: -- 🧰 Improve tree view testing +- 🧰 Improve Tree View testing - 📊 Add `label` to be displayed in BarChart ### Data Grid @@ -1816,7 +1905,7 @@ Same changes as in `@mui/x-date-pickers@7.4.0`. #### `@mui/x-tree-view@7.4.0` -- [TreeView] Fix props propagation and theme entry in `TreeItem2` (#12889) @flaviendelangle +- [TreeView] Fix props propagation and theme entry in `` (#12889) @flaviendelangle ### Docs @@ -2126,7 +2215,7 @@ Same changes as in `@mui/x-date-pickers@7.2.0`, plus: ### Docs - [docs] Add `AxisFormatter` documentation for customizing tick/tooltip value formatting (#12700) @JCQuintas -- [docs] Add file explorer example to rich tree view customization docs (#12707) @noraleonte +- [docs] Add file explorer example to rich Tree View customization docs (#12707) @noraleonte - [docs] Do not use import of depth 3 in the doc (#12716) @flaviendelangle - [docs] Explain how to clip plots with composition (#12679) @alexfauquette - [docs] Fix typo in Data Grid v7 migration page (#12720) @bfaulk96 @@ -2202,7 +2291,7 @@ Same changes as in `@mui/x-date-pickers@7.1.1`, plus: #### `@mui/x-tree-view@7.1.1` - [TreeView] Add JSDoc to all `publicAPI` methods (#12649) @flaviendelangle -- [TreeView] Create `RichTreeViewPro` component (not released yet) (#12610) @flaviendelangle +- [TreeView] Create `` component (not released yet) (#12610) @flaviendelangle - [TreeView] Create Pro package (not released yet) (#12240) @flaviendelangle - [TreeView] Fix typo in errors (#12623) @alissa-tung - [TreeView] New API method: `setItemExpansion` (#12595) @flaviendelangle @@ -2222,13 +2311,13 @@ Same changes as in `@mui/x-date-pickers@7.1.1`, plus: - [docs] Move Data Grid interfaces to standard API page layout (#12016) @alexfauquette - [docs] Remove ` around @default values (#12158) @alexfauquette - [docs] Remove `day` from the default `dayOfWeekFormatter` function params (#12644) @LukasTy -- [docs] Use `TreeItem2` for icon expansion example on `RichTreeView` (#12563) @flaviendelangle +- [docs] Use `` for icon expansion example on `` (#12563) @flaviendelangle ### Core - [core] Add cherry-pick `master` to `v6` action (#12648) @LukasTy - [core] Fix typo in `@mui/x-tree-view-pro/themeAugmentation` (#12674) @flaviendelangle -- [core] Introduce `describeTreeView` to run test on `SimpleTreeView` and `RichTreeView`, using `TreeItem` and `TreeItem2` + migrate expansion tests (#12428) @flaviendelangle +- [core] Introduce `describeTreeView` to run test on `` and ``, using `` and `` + migrate expansion tests (#12428) @flaviendelangle - [core] Limit `test-types` CI step allowed memory (#12651) @LukasTy - [core] Remove explicit `express` package (#12602) @LukasTy - [core] Update to new embedded translations in the docs package (#12232) @Janpot @@ -2293,7 +2382,7 @@ Same changes as in `@mui/x-date-pickers@7.1.0`, plus: #### `@mui/x-tree-view@7.1.0` -- [TreeView] Do not use outdated version of the state to compute new label first char in `RichTreeView` (#12512) @flaviendelangle +- [TreeView] Do not use outdated version of the state to compute new label first char in Rich Tree View (#12512) @flaviendelangle ### Docs @@ -2420,7 +2509,7 @@ Same changes as in `@mui/x-date-pickers@7.0.0`, plus: #### Breaking changes -- The required `nodeId` prop used by the `TreeItem` has been renamed to `itemId` for consistency: +- The required `nodeId` prop used by `` has been renamed to `itemId` for consistency: ```diff @@ -2573,7 +2662,7 @@ The `onNodeFocus` callback has been renamed to `onItemFocus` for consistency: #### `@mui/x-tree-view@7.0.0-beta.7` - [TreeView] Clean the usage of the term "item" and "node" in API introduced during v7 (#12368) @noraleonte -- [TreeView] Introduce a new `TreeItem2` component and a new `useTreeItem2` hook (#11721) @flaviendelangle +- [TreeView] Introduce a new `` component and a new `useTreeItem2` hook (#11721) @flaviendelangle - [TreeView] Rename `onNodeFocus` to `onItemFocus` (#12419) @noraleonte ### Docs @@ -2631,9 +2720,9 @@ Same changes as in `@mui/x-data-grid-pro@7.0.0-beta.6`. #### Breaking changes -- The component used to animate the item children is now defined as a slot on the `TreeItem` component. +- The component used to animate the item children is now defined as a slot on the `` component. - If you were passing a `TransitionComponent` or `TransitionProps` to your `TreeItem` component, + If you were passing a `TransitionComponent` or `TransitionProps` to your `` component, you need to use the new `groupTransition` slot on this component: ```diff @@ -2649,7 +2738,7 @@ Same changes as in `@mui/x-data-grid-pro@7.0.0-beta.6`. ``` -- The `group` class of the `TreeItem` component has been renamed to `groupTransition` to match with its new slot name. +- The `group` class of the `` component has been renamed to `groupTransition` to match with its new slot name. ```diff const StyledTreeItem = styled(TreeItem)({ @@ -2663,14 +2752,14 @@ Same changes as in `@mui/x-data-grid-pro@7.0.0-beta.6`. #### `@mui/x-tree-view@7.0.0-beta.6` - [TreeView] Fix invalid nodes state when updating `props.items` (#12359) @flaviendelangle -- [TreeView] In the `RichTreeView`, do not use the item id as the HTML id attribute (#12319) @flaviendelangle +- [TreeView] In the Rich Tree View, do not use the item id as the HTML id attribute (#12319) @flaviendelangle - [TreeView] New instance and publicAPI method: `getItem` (#12251) @flaviendelangle - [TreeView] Replace `TransitionComponent` and `TransitionProps` with a `groupTransition` slot (#12336) @flaviendelangle ### Docs - [docs] Add a note about `z-index` usage in SVG (#12337) @alexfauquette -- [docs] `RichTreeView` customization docs (#12231) @noraleonte +- [docs] Rich Tree View customization docs (#12231) @noraleonte ### Core @@ -2813,7 +2902,8 @@ Same changes as in `@mui/x-data-grid-pro@7.0.0-beta.4`. ``` - The headless field hooks (e.g.: `useDateField`) now returns a new prop called `enableAccessibleFieldDOMStructure`. - This property is utilized to determine whether the anticipated UI is constructed using an accessible DOM structure. Learn more about this new [accessible DOM structure](/x/react-date-pickers/fields/#accessible-dom-structure). + This property is utilized to determine whether the anticipated UI is constructed using an accessible DOM structure. + Learn more about this new accessible DOM structure in the [v8 migration guide](https://next.mui.com/x/migration/migration-pickers-v7/#new-dom-structure-for-the-field). When building a custom UI, you are most-likely only supporting one DOM structure, so you can remove `enableAccessibleFieldDOMStructure` before it is passed to the DOM: @@ -3323,7 +3413,7 @@ Same changes as in `@mui/x-date-pickers@7.0.0-beta.0`, plus: ### Docs -- [docs] Add `contextValue` to the headless tree view doc (#11705) @flaviendelangle +- [docs] Add `contextValue` to the headless Tree View doc (#11705) @flaviendelangle - [docs] Add section for the `disableSelection` prop (#11821) @flaviendelangle - [docs] Fix brand name non-breaking space (#11758) @oliviertassinari - [docs] Fix typo in Data Grid components page (#11775) @flaviendelangle @@ -3346,7 +3436,7 @@ We'd like to offer a big thanks to the 11 contributors who made this release pos - 🎁 The Data Grid headers have been refactored to bring immense improvements to scrolling, state management, and overall performance of the grid. - ⚙️ The Data Grid disabled column-specific features like filtering, sorting, grouping, etc. could now be accessed programmatically. See the related [docs](https://next.mui.com/x/react-data-grid/api-object/#access-the-disabled-column-features) section. -- 🚀 Uplift the `SimpleTreeView` customization examples (#11424) @noraleonte +- 🚀 Uplift the Simple Tree View customization examples (#11424) @noraleonte - 🌍 Add Croatian (hr-HR), Portuguese (pt-PT), and Chinese (Hong Kong) (zh-HK) locales (#11668) on the Data Grid @BCaspari - 🐞 Bugfixes - 💔 Bump `@mui/material` peer dependency for all packages (#11692) @LukasTy @@ -3557,7 +3647,7 @@ Same changes as in `@mui/x-date-pickers@7.0.0-alpha.9`. ``` - The `useTreeItem` hook has been renamed `useTreeItemState`. - This will help create a new headless version of the `TreeItem` component based on a future `useTreeItem` hook. + This will help create a new headless version of the Tree Item component based on a future `useTreeItem` hook. ```diff -import { TreeItem, useTreeItem } from '@mui/x-tree-view/TreeItem'; @@ -3657,13 +3747,13 @@ Same changes as in `@mui/x-date-pickers@7.0.0-alpha.9`. - [docs] Cleanup and fix Pickers Playground styling (#11700) @LukasTy - [docs] First draft of the Tree View custom plugin doc (#11564) @flaviendelangle - [docs] Fix Pickers migration syntax and diffs (#11695) @LukasTy -- [docs] Fix generated tree view API docs (#11737) @LukasTy +- [docs] Fix generated Tree View API docs (#11737) @LukasTy - [docs] Generate docs for Tree View slots (#11730) @flaviendelangle - [docs] Improve codemod for v7 (#11650) @oliviertassinari - [docs] Improve Data Grid `pageSizeOptions` prop documentation (#11682) @oliviertassinari - [docs] Parse markdown on API docs demo titles (#11728) @LukasTy - [docs] Remove the description from the `className` prop (#11693) @oliviertassinari -- [docs] Uplift `SimpleTreeView` customization examples (#11424) @noraleonte +- [docs] Uplift Simple Tree View customization examples (#11424) @noraleonte - [docs] Uplift the Date Pickers playground (#11555) @danilo-leal ### Core @@ -3740,8 +3830,8 @@ Same changes as in `@mui/x-date-pickers@7.0.0-alpha.8`. ### Tree View / `@mui/x-tree-view@7.0.0-alpha.8` -- [tree view] Cleanup `onKeyDown` handler (#11481) @flaviendelangle -- [tree view] Define the parameters used by each plugin to avoid listing them in each component (#11473) @flaviendelangle +- [TreeView] Cleanup `onKeyDown` handler (#11481) @flaviendelangle +- [TreeView] Define the parameters used by each plugin to avoid listing them in each component (#11473) @flaviendelangle ### Docs @@ -3768,7 +3858,7 @@ We'd like to offer a big thanks to the 7 contributors who made this release poss - 🎁 New component to create a Tree View from a structured data source: - You can now directly pass your data to the `RichTreeView` component instead of manually converting it into JSX `TreeItem` components: + You can now directly pass your data to the `` component instead of manually converting it into JSX `` components: ```tsx const ITEMS = [ @@ -5099,7 +5189,7 @@ Here is an example of the renaming for the `` component. ### Core -- [core] Adds migration docs for charts, pickers and tree view (#10926) @michelengelen +- [core] Adds migration docs for Charts, Pickers, and Tree View (#10926) @michelengelen - [core] Bump monorepo (#10959) @LukasTy - [core] Changed prettier branch value to next (#10917) @michelengelen - [core] Fix GitHub title tag consistency @oliviertassinari diff --git a/changelogOld/CHANGELOG.v5.md b/changelogOld/CHANGELOG.v5.md index f8f3abc95eebd..e4378fb582ed9 100644 --- a/changelogOld/CHANGELOG.v5.md +++ b/changelogOld/CHANGELOG.v5.md @@ -956,7 +956,7 @@ We'd like to offer a big thanks to the 10 contributors who made this release pos - [docs] New location for the legal content (#5595) @oliviertassinari - [docs] Update description of `maxDateTime` prop (#5639) @jurecuhalev -- [docs] Add missing `date-fns` dependency when opening Codesandbox demo (#5692) @cherniavskii +- [docs] Add missing `date-fns` dependency when opening CodeSandbox demo (#5692) @cherniavskii ### Core @@ -2819,7 +2819,7 @@ _Nov 4, 2021_ - [core] Group update of MUI Core (#3055) @oliviertassinari - [core] Ignore `*.tsbuildinfo` files (#3068) @m4theushw - [core] Implement tree-based row management (#2996) @flaviendelangle -- [core] Invert Codesandbox examples on README (#3073) @flaviendelangle +- [core] Invert CodeSandbox examples on README (#3073) @flaviendelangle - [core] Prefix selectors from `useGridContainerProps` with `unsafe` (#3002) @flaviendelangle - [core] Reduce `setGridState` and `applyFilters` call when updating `filterModel` (#3023) @flaviendelangle - [core] Reduce styles complexity (#3012) @DanailH diff --git a/changelogOld/CHANGELOG.v6.md b/changelogOld/CHANGELOG.v6.md index 0401e317fe08f..df02a516936d1 100644 --- a/changelogOld/CHANGELOG.v6.md +++ b/changelogOld/CHANGELOG.v6.md @@ -20,7 +20,7 @@ Same changes as in `@mui/x-date-pickers@6.19.12`. ### Docs -- [docs] Use MUI X v6 in Codesandbox and Stackblitz demos (#12838) @cherniavskii +- [docs] Use MUI X v6 in CodeSandbox and StackBlitz demos (#12838) @cherniavskii ## 6.19.11 @@ -1485,7 +1485,7 @@ Same changes as in `@mui/x-date-pickers@6.13.0`, plus: - [docs] Fix charts demo using too deep import (#10263) @LukasTy - [docs] Fix `e.g.` typo @oliviertassinari - [docs] Fix npm package indentation @oliviertassinari -- [docs] Fix typo in tree view docs @oliviertassinari +- [docs] Fix typo in Tree View docs @oliviertassinari - [docs] Improve the week picker example (#8257) @flaviendelangle - [docs] Include code links in the Data Grid demo (#10219) @cherniavskii - [docs] Polish page for SEO (#10216) @oliviertassinari @@ -1546,7 +1546,7 @@ Same changes as in `@mui/x-date-pickers@6.12.1`. - [docs] Add `DemoContainer` and `DemoItem` JSDoc (#10186) @LukasTy - [docs] Add link to `custom layout` page (#10184) @LukasTy -- [docs] Add tree view nav item (#10181) @LukasTy +- [docs] Add Tree View nav item (#10181) @LukasTy - [docs] Fix wrong chart tooltip reference (#10169) @oliviertassinari - [docs] Improve chart SEO (#10170) @oliviertassinari - [docs] Precise expired license key condition (#10165) @oliviertassinari @@ -1748,7 +1748,7 @@ _Aug 4, 2023_ We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨: -- ⌚️ Move the tree view component from `@mui/lab` package +- ⌚️ Move the Tree View component from `@mui/lab` package The `` component has been moved to the MUI X repository. It is now accessible from its own package: `@mui/x-tree-view`. @@ -1804,7 +1804,7 @@ Same changes as in `@mui/x-date-pickers@6.11.0`. ### Tree View / `@mui/x-tree-view@6.0.0-alpha.0` - [TreeView] Add missing exported types (#9862) @flaviendelangle -- [TreeView] Add tree view to changelog generator script (#9903) @MBilalShafi +- [TreeView] Add Tree View to changelog generator script (#9903) @MBilalShafi - [TreeView] Create the package on the X repository (#9798) @flaviendelangle - [TreeView] Improve props typing (#9855) @flaviendelangle @@ -2596,7 +2596,7 @@ We'd like to offer a big thanks to the 12 contributors who made this release pos - [docs] Fix date pickers typo in the docs (#8939) @richbustos - [docs] Fix master detail demo (#8894) @m4theushw - [docs] Fix typo in clipboard docs (#8971) @MBilalShafi -- [docs] Reduce list of dependencies in Codesandbox/Stackblitz demos (#8535) @cherniavskii +- [docs] Reduce list of dependencies in CodeSandbox/StackBlitz demos (#8535) @cherniavskii ### Core diff --git a/codecov.yml b/codecov.yml index 4a05503c5b9e2..b4af658126156 100644 --- a/codecov.yml +++ b/codecov.yml @@ -10,15 +10,15 @@ coverage: adapters: target: 100% paths: - - 'packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts' - - 'packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.ts' - - 'packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts' - - 'packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.ts' - - 'packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts' - - 'packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts' - - 'packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts' - - 'packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.ts' - - 'packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.ts' + - packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts + - packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.ts + - packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts + - packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.ts + - packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts + - packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts + - packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts + - packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.ts + - packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.ts patch: off comment: false diff --git a/docs/.link-check-errors.txt b/docs/.link-check-errors.txt index 6ecc503cc78a5..0438fa82a0918 100644 --- a/docs/.link-check-errors.txt +++ b/docs/.link-check-errors.txt @@ -1,2 +1,3 @@ Broken links found by `docs:link-check` that exist: +- https://mui.com/x/react-date-pickers/fields/#accessible-dom-structure diff --git a/docs/data/charts/areas-demo/areas-demo.md b/docs/data/charts/areas-demo/areas-demo.md index 92432f02ad72a..d784d0996c931 100644 --- a/docs/data/charts/areas-demo/areas-demo.md +++ b/docs/data/charts/areas-demo/areas-demo.md @@ -36,7 +36,7 @@ You can pass this gradient definition as a children of the `` and u To do so you will need to use the [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient) and [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop) SVG elements. The first part is to get the SVG total height. -Which can be done with the `useDrawingArea` hook. +Which can be done with the `useDrawingArea()` hook. It's useful to define the `` as a vector that goes from the top to the bottom of our SVG container. Then to define where the gradient should switch from one color to another, you can use the `useYScale` hook to get the y coordinate of value 0. diff --git a/docs/data/charts/axis/axis.md b/docs/data/charts/axis/axis.md index e415596c25837..e8edd28364096 100644 --- a/docs/data/charts/axis/axis.md +++ b/docs/data/charts/axis/axis.md @@ -56,7 +56,7 @@ Which expects an array of value coherent with the `scaleType`: Some series types also require specific axis attributes: - line plots require an `xAxis` to have `data` provided -- bar plots require an `xAxis` with `scaleType='band'` and some `data` provided. +- bar plots require an `xAxis` with `scaleType="band"` and some `data` provided. ### Axis formatter diff --git a/docs/data/charts/bars/BarAnimation.tsx b/docs/data/charts/bars/BarAnimation.tsx index 9bdd402a65e7f..13694483bb7b5 100644 --- a/docs/data/charts/bars/BarAnimation.tsx +++ b/docs/data/charts/bars/BarAnimation.tsx @@ -5,6 +5,7 @@ import Slider from '@mui/material/Slider'; import FormControlLabel from '@mui/material/FormControlLabel'; import Checkbox from '@mui/material/Checkbox'; import { BarChart } from '@mui/x-charts/BarChart'; +import { HighlightScope } from '@mui/x-charts/context'; export default function BarAnimation() { const [seriesNb, setSeriesNb] = React.useState(2); @@ -67,10 +68,10 @@ export default function BarAnimation() { ); } -const highlightScope = { +const highlightScope: HighlightScope = { highlight: 'series', fade: 'global', -} as const; +}; const series = [ { diff --git a/docs/data/charts/bars/bars.md b/docs/data/charts/bars/bars.md index 11c42dd85e93b..606f2895de13c 100644 --- a/docs/data/charts/bars/bars.md +++ b/docs/data/charts/bars/bars.md @@ -100,7 +100,7 @@ Learn more about the `colorMap` properties in the [Styling docs](/x/react-charts {{"demo": "ColorScale.js"}} -### Border Radius +### Border radius To give your bar chart rounded corners, you can change the value of the `borderRadius` property on the [BarChart](/x/api/charts/bar-chart/#bar-chart-prop-slots). @@ -117,7 +117,7 @@ Or you can pass `'value'` to display the raw value of the bar. {{"demo": "BarLabel.js"}} -### Custom Labels +### Custom labels You can display, change, or hide labels based on conditional logic. To do so, provide a function to the `barLabel`. @@ -174,7 +174,7 @@ import ChartsOnAxisClickHandler from '@mui/x-charts/ChartsOnAxisClickHandler'; To skip animation at the creation and update of your chart, you can use the `skipAnimation` prop. When set to `true` it skips animation powered by `@react-spring/web`. -Charts containers already use the `useReducedMotion` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). +Charts containers already use the `useReducedMotion()` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). ```jsx // For a single component chart diff --git a/docs/data/charts/components/components.md b/docs/data/charts/components/components.md index 5cfff2f9f6d96..be2f67d72870d 100644 --- a/docs/data/charts/components/components.md +++ b/docs/data/charts/components/components.md @@ -20,7 +20,7 @@ Charts dimensions are defined by a few props: The term **drawing area** refers to the space available to plot data (scatter points, lines, or pie arcs). The `margin` is used to leave some space for extra elements, such as the axes, the legend, or the title. -You can use the `useDrawingArea` hook in the charts subcomponents to get the coordinates of the **drawing area**. +You can use the `useDrawingArea()` hook in the charts subcomponents to get the coordinates of the **drawing area**. ```jsx import { useDrawingArea } from '@mui/x-charts'; diff --git a/docs/data/charts/gauge/gauge.md b/docs/data/charts/gauge/gauge.md index 30c5df560756a..465560c8527d9 100644 --- a/docs/data/charts/gauge/gauge.md +++ b/docs/data/charts/gauge/gauge.md @@ -101,7 +101,7 @@ import { ### Creating your components -To create your own components, use the `useGaugeState` hook which provides all you need about the gauge configuration: +To create your own components, use the `useGaugeState()` hook which provides all you need about the gauge configuration: - information about the value: `value`, `valueMin`, `valueMax` - information to plot the arc: `startAngle`, `endAngle`, `outerRadius`, `innerRadius`, `cornerRadius`, `cx`, and `cy` diff --git a/docs/data/charts/getting-started/getting-started.md b/docs/data/charts/getting-started/getting-started.md index 6c6057abfc00b..83dbff7001c70 100644 --- a/docs/data/charts/getting-started/getting-started.md +++ b/docs/data/charts/getting-started/getting-started.md @@ -4,20 +4,20 @@ githubLabel: 'component: charts' packageName: '@mui/x-charts' --- -# Charts - Getting Started +# Charts - Getting started -

Get started with the MUI X Charts components. Install the package, configure your application, and start using the components.

+

Install the MUI X Charts package to start building React data visualization components.

## Installation -Using your favorite package manager, install `@mui/x-charts-pro` for the commercial version, or `@mui/x-charts` for the free community version. +Run one of the following commands to install the free Community version or the paid Pro version of the MUI X Charts: {{"component": "modules/components/ChartsInstallationInstructions.js"}} -The Charts package has a peer dependency on `@mui/material`. -If you are not already using it in your project, you can install it with: +The Charts packages have a peer dependency on `@mui/material`. +If you're not already using it, install it with the following command: @@ -37,7 +37,7 @@ yarn add @mui/material @emotion/react @emotion/styled -Please note that [react](https://www.npmjs.com/package/react) and [react-dom](https://www.npmjs.com/package/react-dom) are peer dependencies too: +[`react`](https://www.npmjs.com/package/react) and [`react-dom`](https://www.npmjs.com/package/react-dom) are also peer dependencies: ```json "peerDependencies": { @@ -46,85 +46,66 @@ Please note that [react](https://www.npmjs.com/package/react) and [react-dom](ht }, ``` -### Style engine - -Material UI is using [Emotion](https://emotion.sh/docs/introduction) as a styling engine by default. If you want to use [`styled-components`](https://styled-components.com/) instead, run: - - -```bash npm -npm install @mui/styled-engine-sc styled-components -``` - -```bash pnpm -pnpm add @mui/styled-engine-sc styled-components -``` - -```bash yarn -yarn add @mui/styled-engine-sc styled-components -``` - - - -Take a look at the [Styled engine guide](/material-ui/integrations/styled-components/) for more information about how to configure `styled-components` as the style engine. - ### Usage with D3 To help folks using CommonJS, the `@mui/x-charts` package uses a vendored package named `@mui/x-charts-vendor` to access D3 libraries. +You can import D3 functions from `@mui/x-charts-vendor/d3-color`. -If you need some D3 functions, you can import them with `@mui/x-charts-vendor/d3-color`. +## Rendering Charts -## Displaying charts +MUI X Charts can be rendered as _self-contained_ or _composable_ components. +[Self-contained components](#self-contained-charts) are simpler to get started with and are recommended for most common use cases; more complex visualization (such as combining Bar and Line Charts on a single plot) requires [custom composition](#composable-charts). -A Chart can be rendered in one of two ways: as a single component, or by composing subcomponents. +### Self-contained Charts -### Single charts +Self-contained Chart components are imported and rendered as a single React component (such as `` or ``) which contains all of the necessary subcomponents. -For common use cases, the single component is the recommended way. -Those components' names end with "Chart", as opposed to "Plot", and only require the series prop describing the data to render. +These components require a `series` prop describing the data to render, as well as a numerical value (rendered in pixels) for the `height` prop. +The `width` prop is optional; if no value is provided, the Charts expand to fill the available space. {{"demo": "SimpleCharts.js"}} -### Composed charts +### Composable Charts -To combine different Charts, like Lines with Bars, you can use composition with the `ChartContainer` wrapper. +More complex use cases require composition of the necessary subcomponents inside of a Chart Container wrapper. +Subcomponents include: -Inside this wrapper, render either axis components, such as `XAxis` and `YAxis`, or any plot component like `BarPlot`, `LinePlot`, `AreaPlot`, and `ScatterPlot`. +- Axis components – to define the X and Y axes +- Plot components – to create Bars, Lines, or any other Chart type +- Auxillary components - to add Tooltips, Highlights, and more +- Utilities - such as classes and types -Visit the [Composition page](/x/react-charts/composition/) for more details. +See the [Charts composition documentation](/x/react-charts/composition/) for complete details. -{{"demo": "Combining.js"}} +The demo below shows how to use composition to create a custom Chart that combines a Bar and a Line Chart on a single plot: -### Positions +{{"demo": "Combining.js"}} -Charts are composed of two main areas. -The SVG defined by its `width` and `height` delimits the available space. +## Chart layouts -Within this SVG, a dedicated "drawing area" (aka "plot area") serves as the canvas for data representation. -Here, elements like lines, bars, and areas visually depict the information. -It's controlled by the `margin = {top, bottom, left, right}` object defining the margin between the SVG and the drawing area. +The layout of a Chart is defined by two main spaces: the plot area, and the outer margins. -The space left by margins can display axes, titles, a legend, or any other additional information. +The `width` and `height` props define the dimensions of the SVG which is the root of the chart. +Within this SVG, the plot area (or drawing area) serves as the canvas for data visualization, where the lines, bars or other visual elements are rendered. +The size of the plot area is determined by the `margin = {top, bottom, left, right}` object which defines its outer margins inside the SVG. +The outer margin space is where information like axes, titles, and legends are displayed. -For more information about the position configuration, visit the [styling page](/x/react-charts/styling/#styling). +See the [Styling documentation](/x/react-charts/styling/#placement) for complete details. ## Axis management -MUI X Charts have a flexible approach to axis management, supporting multiple-axis charts with any combination of scales and ranges. - -Visit the [Axis page](/x/react-charts/axis/) for more details. - -## Styling +MUI X Charts take a flexible approach to axis management, with support for multiple axes and any combination of scales and ranges. -MUI X Charts follows the Material UI styling and features all of the customization tools you'd find there, making tweaking charts as straightforward as designing buttons. - -Visit the [Styling page](/x/react-charts/styling/) for more details. +See the [Axis documentation](/x/react-charts/axis/) for complete details. ## TypeScript -In order to benefit from the [CSS overrides](/material-ui/customization/theme-components/#theme-style-overrides) and [default prop customization](/material-ui/customization/theme-components/#theme-default-props) with the theme, TypeScript users need to import the following types. -Internally, it uses module augmentation to extend the default theme structure. +To benefit from [CSS overrides](/material-ui/customization/theme-components/#theme-style-overrides) and [default prop customization](/material-ui/customization/theme-components/#theme-default-props) with the theme, TypeScript users must import the following types. +These types use module augmentation to extend the default theme structure. ```tsx +// only one import is necessary, +// from the version you're currently using. import type {} from '@mui/x-charts/themeAugmentation'; import type {} from '@mui/x-charts-pro/themeAugmentation'; @@ -140,8 +121,3 @@ const theme = createTheme({ }, }); ``` - -:::info -You don't have to import the theme augmentation from both `@mui/x-charts` and `@mui/x-charts-pro` when using `@mui/x-charts-pro`. -Importing it from `@mui/x-charts-pro` is enough. -::: diff --git a/docs/data/charts/highlighting/ControlledHighlight.js b/docs/data/charts/highlighting/ControlledHighlight.js index 6106b70cd9d81..e7b0a1fa951ec 100644 --- a/docs/data/charts/highlighting/ControlledHighlight.js +++ b/docs/data/charts/highlighting/ControlledHighlight.js @@ -18,8 +18,8 @@ export default function ControlledHighlight() { seriesId: 'A', dataIndex: 0, }); - const [highlighted, setHighlighted] = React.useState('item'); - const [faded, setFaded] = React.useState('global'); + const [highlight, setHighlight] = React.useState('item'); + const [fade, setFade] = React.useState('global'); const handleHighLightedSeries = (event, newHighLightedSeries) => { if (newHighLightedSeries !== null) { @@ -80,8 +80,8 @@ export default function ControlledHighlight() { series={barChartsProps.series.map((series) => ({ ...series, highlightScope: { - highlighted, - faded, + highlight, + fade, }, }))} highlightedItem={highlightedItem} @@ -98,8 +98,8 @@ export default function ControlledHighlight() { setHighlighted(event.target.value)} + value={highlight} + onChange={(event) => setHighlight(event.target.value)} sx={{ minWidth: 150 }} > none @@ -109,8 +109,8 @@ export default function ControlledHighlight() { setFaded(event.target.value)} + value={fade} + onChange={(event) => setFade(event.target.value)} sx={{ minWidth: 150 }} > none diff --git a/docs/data/charts/highlighting/ControlledHighlight.tsx b/docs/data/charts/highlighting/ControlledHighlight.tsx index 4de4eb1b81ae2..b29d70302bbc3 100644 --- a/docs/data/charts/highlighting/ControlledHighlight.tsx +++ b/docs/data/charts/highlighting/ControlledHighlight.tsx @@ -19,8 +19,8 @@ export default function ControlledHighlight() { seriesId: 'A', dataIndex: 0, }); - const [highlighted, setHighlighted] = React.useState('item'); - const [faded, setFaded] = React.useState('global'); + const [highlight, setHighlight] = React.useState('item'); + const [fade, setFade] = React.useState('global'); const handleHighLightedSeries = (event: any, newHighLightedSeries: string) => { if (newHighLightedSeries !== null) { @@ -81,8 +81,8 @@ export default function ControlledHighlight() { series={barChartsProps.series.map((series) => ({ ...series, highlightScope: { - highlighted, - faded, + highlight, + fade, } as HighlightScope, }))} highlightedItem={highlightedItem} @@ -99,8 +99,8 @@ export default function ControlledHighlight() { setHighlighted(event.target.value)} + value={highlight} + onChange={(event) => setHighlight(event.target.value)} sx={{ minWidth: 150 }} > none @@ -110,8 +110,8 @@ export default function ControlledHighlight() { setFaded(event.target.value)} + value={fade} + onChange={(event) => setFade(event.target.value)} sx={{ minWidth: 150 }} > none diff --git a/docs/data/charts/highlighting/ElementHighlights.js b/docs/data/charts/highlighting/ElementHighlights.js index 7379647649bd9..fe1f78527ae41 100644 --- a/docs/data/charts/highlighting/ElementHighlights.js +++ b/docs/data/charts/highlighting/ElementHighlights.js @@ -88,8 +88,8 @@ const pieChartsParams = { export default function ElementHighlights() { const [chartType, setChartType] = React.useState('bar'); const [withArea, setWithArea] = React.useState(false); - const [highlighted, setHighlighted] = React.useState('item'); - const [faded, setFaded] = React.useState('global'); + const [highlight, setHighlight] = React.useState('item'); + const [fade, setFade] = React.useState('global'); const handleChartType = (event, newChartType) => { if (newChartType !== null) { @@ -123,8 +123,8 @@ export default function ElementHighlights() { series={barChartsParams.series.map((series) => ({ ...series, highlightScope: { - highlighted, - faded, + highlight, + fade, }, }))} /> @@ -137,8 +137,8 @@ export default function ElementHighlights() { ...series, area: withArea, highlightScope: { - highlighted, - faded, + highlight, + fade, }, }))} /> @@ -150,8 +150,8 @@ export default function ElementHighlights() { series={scatterChartsParams.series.map((series) => ({ ...series, highlightScope: { - highlighted, - faded, + highlight, + fade, }, }))} /> @@ -163,8 +163,8 @@ export default function ElementHighlights() { series={pieChartsParams.series.map((series) => ({ ...series, highlightScope: { - highlighted, - faded, + highlight, + fade, }, }))} /> @@ -179,9 +179,9 @@ export default function ElementHighlights() { > setHighlighted(event.target.value)} + label="highlight" + value={highlight} + onChange={(event) => setHighlight(event.target.value)} sx={{ minWidth: 150 }} > none @@ -190,9 +190,9 @@ export default function ElementHighlights() { setFaded(event.target.value)} + label="fade" + value={fade} + onChange={(event) => setFade(event.target.value)} sx={{ minWidth: 150 }} > none diff --git a/docs/data/charts/highlighting/ElementHighlights.tsx b/docs/data/charts/highlighting/ElementHighlights.tsx index d576ed201c501..b961f5ccfd0c0 100644 --- a/docs/data/charts/highlighting/ElementHighlights.tsx +++ b/docs/data/charts/highlighting/ElementHighlights.tsx @@ -89,8 +89,8 @@ const pieChartsParams = { export default function ElementHighlights() { const [chartType, setChartType] = React.useState('bar'); const [withArea, setWithArea] = React.useState(false); - const [highlighted, setHighlighted] = React.useState('item'); - const [faded, setFaded] = React.useState('global'); + const [highlight, setHighlight] = React.useState('item'); + const [fade, setFade] = React.useState('global'); const handleChartType = (event: any, newChartType: string) => { if (newChartType !== null) { @@ -124,8 +124,8 @@ export default function ElementHighlights() { series={barChartsParams.series.map((series) => ({ ...series, highlightScope: { - highlighted, - faded, + highlight, + fade, } as HighlightScope, }))} /> @@ -138,8 +138,8 @@ export default function ElementHighlights() { ...series, area: withArea, highlightScope: { - highlighted, - faded, + highlight, + fade, } as HighlightScope, }))} /> @@ -151,8 +151,8 @@ export default function ElementHighlights() { series={scatterChartsParams.series.map((series) => ({ ...series, highlightScope: { - highlighted, - faded, + highlight, + fade, } as HighlightScope, }))} /> @@ -164,8 +164,8 @@ export default function ElementHighlights() { series={pieChartsParams.series.map((series) => ({ ...series, highlightScope: { - highlighted, - faded, + highlight, + fade, } as HighlightScope, }))} /> @@ -180,9 +180,9 @@ export default function ElementHighlights() { > setHighlighted(event.target.value)} + label="highlight" + value={highlight} + onChange={(event) => setHighlight(event.target.value)} sx={{ minWidth: 150 }} > none @@ -191,9 +191,9 @@ export default function ElementHighlights() { setFaded(event.target.value)} + label="fade" + value={fade} + onChange={(event) => setFade(event.target.value)} sx={{ minWidth: 150 }} > none diff --git a/docs/data/charts/highlighting/SyncHighlight.js b/docs/data/charts/highlighting/SyncHighlight.js index b6275d7b6c13a..16c16744f7f3a 100644 --- a/docs/data/charts/highlighting/SyncHighlight.js +++ b/docs/data/charts/highlighting/SyncHighlight.js @@ -37,11 +37,7 @@ const barChartsProps = { ], xAxis: [{ scaleType: 'band', data: ['A', 'B', 'C', 'D', 'E'] }], height: 400, - slotProps: { - legend: { - hidden: true, - }, - }, + slotProps: { legend: { hidden: true } }, }; const pieChartProps = { @@ -59,9 +55,5 @@ const pieChartProps = { }, ], height: 400, - slotProps: { - legend: { - hidden: true, - }, - }, + slotProps: { legend: { hidden: true } }, }; diff --git a/docs/data/charts/highlighting/SyncHighlight.tsx b/docs/data/charts/highlighting/SyncHighlight.tsx index 1da00da5021c1..83ea2a015fae5 100644 --- a/docs/data/charts/highlighting/SyncHighlight.tsx +++ b/docs/data/charts/highlighting/SyncHighlight.tsx @@ -38,11 +38,7 @@ const barChartsProps: BarChartProps = { ], xAxis: [{ scaleType: 'band', data: ['A', 'B', 'C', 'D', 'E'] }], height: 400, - slotProps: { - legend: { - hidden: true, - }, - }, + slotProps: { legend: { hidden: true } }, }; const pieChartProps: PieChartProps = { @@ -60,9 +56,5 @@ const pieChartProps: PieChartProps = { }, ], height: 400, - slotProps: { - legend: { - hidden: true, - }, - }, + slotProps: { legend: { hidden: true } }, }; diff --git a/docs/data/charts/lines/LineDataset.js b/docs/data/charts/lines/LineDataset.js index 936c9cd946a7a..acc86ac368c76 100644 --- a/docs/data/charts/lines/LineDataset.js +++ b/docs/data/charts/lines/LineDataset.js @@ -14,7 +14,7 @@ const stackStrategy = { const customize = { height: 300, - legend: { hidden: true }, + slotProps: { legend: { hidden: true } }, margin: { top: 5 }, }; diff --git a/docs/data/charts/lines/LineDataset.tsx b/docs/data/charts/lines/LineDataset.tsx index fb00e63ac5343..5873c34a71d67 100644 --- a/docs/data/charts/lines/LineDataset.tsx +++ b/docs/data/charts/lines/LineDataset.tsx @@ -14,7 +14,7 @@ const stackStrategy = { const customize = { height: 300, - legend: { hidden: true }, + slotProps: { legend: { hidden: true } }, margin: { top: 5 }, }; diff --git a/docs/data/charts/lines/lines.md b/docs/data/charts/lines/lines.md index be651d4dd57a7..de9a55dfb928e 100644 --- a/docs/data/charts/lines/lines.md +++ b/docs/data/charts/lines/lines.md @@ -235,7 +235,7 @@ sx={{ To skip animation at the creation and update of your chart, you can use the `skipAnimation` prop. When set to `true` it skips animation powered by `@react-spring/web`. -Charts containers already use the `useReducedMotion` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). +Charts containers already use the `useReducedMotion()` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). :::warning If you support interactive ways to add or remove series from your chart, you have to provide the series' id. diff --git a/docs/data/charts/overview/overview.md b/docs/data/charts/overview/overview.md index ef86f4a0793ff..92f1d289714df 100644 --- a/docs/data/charts/overview/overview.md +++ b/docs/data/charts/overview/overview.md @@ -7,34 +7,33 @@ packageName: '@mui/x-charts' # MUI X Charts -

A fast and extendable library of react chart components for data visualization.

+

A collection of React chart components for data visualization.

{{"component": "@mui/docs/ComponentLinkHeader", "design": false}} ## Overview -The `@mui/x-charts` is an MIT library for rendering charts relying on [D3.js](https://d3js.org/) for data manipulation and SVG for rendering. -And, like other MUI X components, charts are production-ready components that integrate smoothly into your app. +MUI X Charts is a library of production-ready components for rendering charts with React. +It uses [D3.js](https://d3js.org/) for data manipulation and SVGs for rendering. -With a high level of customization, MUI X Charts provides three levels of customization layers: **single components** with great defaults, extensive **configuration props**, and **subcomponents** for flexible composition. -Additionally, you can also use all the [MUI System](https://mui.com/system/getting-started/) tools, such as the theme override or the `sx` prop. +The components provide a high level of customization, with beautiful defaults as well as extensive configuration props and flexible composition options. +They also have access to all [MUI System](https://mui.com/system/getting-started/) tools such as theme overrides and the `sx` prop. {{"demo": "ChartsOverviewDemo.js", "defaultCodeOpen": true}} -## Using the documentation +## All MUI X Charts -The MUI X Charts documentation has a slightly different structure than other MUI X components. -Instead of having a long page for each, the pages are divided in two: +{{"component": "modules/components/ChartComponentsGrid.js"}} -1. General description of the built-in features the component provides. -2. A set of examples demonstrating the component with customizations. +## Using this documentation -## All MUI X Charts components +Each Chart type has two accompanying documents: -{{"component": "modules/components/ChartComponentsGrid.js"}} +1. **Overview** – a general description of built-in features +2. **Demo** – a collection of custom examples ## Supported features -The features that are shared across multiple Charts components, such as axes and legends, are also documented on separate pages. +Features shared across Chart components such as axes and legends are described in standalone documents: {{"component": "modules/components/ChartFeaturesGrid.js"}} diff --git a/docs/data/charts/pie-demo/PieChartWithCustomizedLabel.js b/docs/data/charts/pie-demo/PieChartWithCustomizedLabel.js index d8382e0a39c5c..22fa253558c6f 100644 --- a/docs/data/charts/pie-demo/PieChartWithCustomizedLabel.js +++ b/docs/data/charts/pie-demo/PieChartWithCustomizedLabel.js @@ -13,7 +13,7 @@ const sizing = { margin: { right: 5 }, width: 200, height: 200, - legend: { hidden: true }, + slotProps: { legend: { hidden: true } }, }; const TOTAL = data.map((item) => item.value).reduce((a, b) => a + b, 0); diff --git a/docs/data/charts/pie-demo/PieChartWithCustomizedLabel.tsx b/docs/data/charts/pie-demo/PieChartWithCustomizedLabel.tsx index 9610b494dcd60..2c833f776c53f 100644 --- a/docs/data/charts/pie-demo/PieChartWithCustomizedLabel.tsx +++ b/docs/data/charts/pie-demo/PieChartWithCustomizedLabel.tsx @@ -13,7 +13,7 @@ const sizing = { margin: { right: 5 }, width: 200, height: 200, - legend: { hidden: true }, + slotProps: { legend: { hidden: true } }, }; const TOTAL = data.map((item) => item.value).reduce((a, b) => a + b, 0); diff --git a/docs/data/charts/pie-demo/PieChartWithPaddingAngle.js b/docs/data/charts/pie-demo/PieChartWithPaddingAngle.js index f0606ec0e296c..de23607e92600 100644 --- a/docs/data/charts/pie-demo/PieChartWithPaddingAngle.js +++ b/docs/data/charts/pie-demo/PieChartWithPaddingAngle.js @@ -24,7 +24,7 @@ export default function PieChartWithPaddingAngle() { margin={{ right: 5 }} width={200} height={200} - legend={{ hidden: true }} + slotProps={{ legend: { hidden: true } }} /> ); diff --git a/docs/data/charts/pie-demo/PieChartWithPaddingAngle.tsx b/docs/data/charts/pie-demo/PieChartWithPaddingAngle.tsx index f0606ec0e296c..de23607e92600 100644 --- a/docs/data/charts/pie-demo/PieChartWithPaddingAngle.tsx +++ b/docs/data/charts/pie-demo/PieChartWithPaddingAngle.tsx @@ -24,7 +24,7 @@ export default function PieChartWithPaddingAngle() { margin={{ right: 5 }} width={200} height={200} - legend={{ hidden: true }} + slotProps={{ legend: { hidden: true } }} /> ); diff --git a/docs/data/charts/pie-demo/TwoLevelPieChart.js b/docs/data/charts/pie-demo/TwoLevelPieChart.js index 7a409b13b88a3..f5c617245e774 100644 --- a/docs/data/charts/pie-demo/TwoLevelPieChart.js +++ b/docs/data/charts/pie-demo/TwoLevelPieChart.js @@ -39,9 +39,7 @@ export default function TwoLevelPieChart() { ]} width={400} height={300} - slotProps={{ - legend: { hidden: true }, - }} + slotProps={{ legend: { hidden: true } }} /> ); } diff --git a/docs/data/charts/pie-demo/TwoLevelPieChart.tsx b/docs/data/charts/pie-demo/TwoLevelPieChart.tsx index 2a8db580d08b3..a5302bb71de05 100644 --- a/docs/data/charts/pie-demo/TwoLevelPieChart.tsx +++ b/docs/data/charts/pie-demo/TwoLevelPieChart.tsx @@ -38,9 +38,7 @@ export default function TwoLevelPieChart() { ]} width={400} height={300} - slotProps={{ - legend: { hidden: true }, - }} + slotProps={{ legend: { hidden: true } }} /> ); } diff --git a/docs/data/charts/pie-demo/TwoSimplePieChart.js b/docs/data/charts/pie-demo/TwoSimplePieChart.js index 7cbc1c6f3e928..234043c52c2c3 100644 --- a/docs/data/charts/pie-demo/TwoSimplePieChart.js +++ b/docs/data/charts/pie-demo/TwoSimplePieChart.js @@ -36,9 +36,7 @@ export default function TwoSimplePieChart() { }, ]} height={300} - slotProps={{ - legend: { hidden: true }, - }} + slotProps={{ legend: { hidden: true } }} /> ); } diff --git a/docs/data/charts/pie-demo/TwoSimplePieChart.tsx b/docs/data/charts/pie-demo/TwoSimplePieChart.tsx index bb88d77dfd8b4..fd421c0d53538 100644 --- a/docs/data/charts/pie-demo/TwoSimplePieChart.tsx +++ b/docs/data/charts/pie-demo/TwoSimplePieChart.tsx @@ -37,9 +37,7 @@ export default function TwoSimplePieChart() { }, ]} height={300} - slotProps={{ - legend: { hidden: true }, - }} + slotProps={{ legend: { hidden: true } }} /> ); } diff --git a/docs/data/charts/pie/PieClickNoSnap.js b/docs/data/charts/pie/PieClickNoSnap.js index 8b13dfb3d48b0..0a6ee078ab7fd 100644 --- a/docs/data/charts/pie/PieClickNoSnap.js +++ b/docs/data/charts/pie/PieClickNoSnap.js @@ -22,9 +22,7 @@ export default function PieClickNoSnap() { series={series} width={400} height={300} - slotProps={{ - legend: { hidden: true }, - }} + slotProps={{ legend: { hidden: true } }} onItemClick={(event, d) => setItemData(d)} />{' '} diff --git a/docs/data/charts/pie/pie.md b/docs/data/charts/pie/pie.md index 047a4cc576e2b..eda5601ffc78e 100644 --- a/docs/data/charts/pie/pie.md +++ b/docs/data/charts/pie/pie.md @@ -110,7 +110,7 @@ const onItemClick = ( To skip animation at the creation and update of your chart you can use the `skipAnimation` prop. When set to `true` it skips animation powered by `@react-spring/web`. -Charts containers already use the `useReducedMotion` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). +Charts containers already use the `useReducedMotion()` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). ```jsx // For a single component chart diff --git a/docs/data/charts/tooltip/CustomAxisTooltip.js b/docs/data/charts/tooltip/CustomAxisTooltip.js index bb893b9e83b16..b65f7d3df4b23 100644 --- a/docs/data/charts/tooltip/CustomAxisTooltip.js +++ b/docs/data/charts/tooltip/CustomAxisTooltip.js @@ -3,22 +3,95 @@ import NoSsr from '@mui/material/NoSsr'; import Popper from '@mui/material/Popper'; import Paper from '@mui/material/Paper'; import Typography from '@mui/material/Typography'; -import { useAxisTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; -import { generateVirtualElement } from './generateVirtualElement'; +import { useAxisTooltip } from '@mui/x-charts/ChartsTooltip'; +import { useSvgRef } from '@mui/x-charts/hooks'; + +function usePointer() { + const svgRef = useSvgRef(); + const popperRef = React.useRef(null); + const positionRef = React.useRef({ x: 0, y: 0 }); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event) => { + positionRef.current = { + x: event.clientX, + y: event.clientY, + }; + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { + ...pointer, + popperRef, + anchorEl: { + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; +} export function CustomAxisTooltip() { const tooltipData = useAxisTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -29,7 +102,8 @@ export function CustomAxisTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', @@ -81,7 +155,7 @@ export function CustomAxisTooltip() { {tooltipData.seriesItems.map((seriesItem) => ( - +
{ + const svgRef = useSvgRef(); + const popperRef: PopperProps['popperRef'] = React.useRef(null); + const positionRef = React.useRef({ x: 0, y: 0 }); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event: PointerEvent) => { + positionRef.current = { + x: event.clientX, + y: event.clientY, + }; + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { + ...pointer, + popperRef, + anchorEl: { + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; +} export function CustomAxisTooltip() { const tooltipData = useAxisTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -29,7 +108,8 @@ export function CustomAxisTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', @@ -81,7 +161,7 @@ export function CustomAxisTooltip() { {tooltipData.seriesItems.map((seriesItem) => ( - +
{ + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event) => { + positionRef.current = { + x: event.clientX, + y: event.clientY, + }; + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { + ...pointer, + popperRef, + anchorEl: { + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; +} export function CustomItemTooltip() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } - // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -30,7 +102,8 @@ export function CustomItemTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', diff --git a/docs/data/charts/tooltip/CustomItemTooltip.tsx b/docs/data/charts/tooltip/CustomItemTooltip.tsx index fcaf28469db1f..077243411c5d3 100644 --- a/docs/data/charts/tooltip/CustomItemTooltip.tsx +++ b/docs/data/charts/tooltip/CustomItemTooltip.tsx @@ -1,25 +1,103 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; -import Popper from '@mui/material/Popper'; +import Popper, { PopperProps } from '@mui/material/Popper'; import Paper from '@mui/material/Paper'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; -import { generateVirtualElement } from './generateVirtualElement'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; +import { useSvgRef } from '@mui/x-charts/hooks'; + +type PointerState = { + isActive: boolean; + isMousePointer: boolean; + pointerHeight: number; +}; + +function usePointer(): PointerState & Pick { + const svgRef = useSvgRef(); + const popperRef: PopperProps['popperRef'] = React.useRef(null); + const positionRef = React.useRef({ x: 0, y: 0 }); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event: PointerEvent) => { + positionRef.current = { + x: event.clientX, + y: event.clientY, + }; + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { + ...pointer, + popperRef, + anchorEl: { + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; +} export function CustomItemTooltip() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } - // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -30,7 +108,8 @@ export function CustomItemTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', diff --git a/docs/data/charts/tooltip/Interaction.js b/docs/data/charts/tooltip/Interaction.js index a7e6142713d69..04a4a516f511f 100644 --- a/docs/data/charts/tooltip/Interaction.js +++ b/docs/data/charts/tooltip/Interaction.js @@ -16,11 +16,7 @@ const barChartsParams = { ], margin: { top: 10, right: 10 }, height: 200, - slotProps: { - legend: { - hidden: true, - }, - }, + slotProps: { legend: { hidden: true } }, }; export default function Interaction() { return ( diff --git a/docs/data/charts/tooltip/Interaction.tsx b/docs/data/charts/tooltip/Interaction.tsx index 2920b6d6b2e24..70cbd6c201f89 100644 --- a/docs/data/charts/tooltip/Interaction.tsx +++ b/docs/data/charts/tooltip/Interaction.tsx @@ -16,11 +16,7 @@ const barChartsParams = { ], margin: { top: 10, right: 10 }, height: 200, - slotProps: { - legend: { - hidden: true, - }, - }, + slotProps: { legend: { hidden: true } }, }; export default function Interaction() { return ( diff --git a/docs/data/charts/tooltip/ItemTooltip.js b/docs/data/charts/tooltip/ItemTooltip.js index 68fbab1d77ae0..4e3b52476e277 100644 --- a/docs/data/charts/tooltip/ItemTooltip.js +++ b/docs/data/charts/tooltip/ItemTooltip.js @@ -1,23 +1,95 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; +import { useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement } from './generateVirtualElement'; + +function usePointer() { + const svgRef = useSvgRef(); + const popperRef = React.useRef(null); + const positionRef = React.useRef({ x: 0, y: 0 }); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event) => { + positionRef.current = { + x: event.clientX, + y: event.clientY, + }; + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { + ...pointer, + popperRef, + anchorEl: { + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; +} export function ItemTooltip() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } - // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -28,7 +100,8 @@ export function ItemTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', diff --git a/docs/data/charts/tooltip/ItemTooltip.tsx b/docs/data/charts/tooltip/ItemTooltip.tsx index 68fbab1d77ae0..1f4277d578c7f 100644 --- a/docs/data/charts/tooltip/ItemTooltip.tsx +++ b/docs/data/charts/tooltip/ItemTooltip.tsx @@ -1,23 +1,101 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; -import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import Popper, { PopperProps } from '@mui/material/Popper'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; +import { useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement } from './generateVirtualElement'; + +type PointerState = { + isActive: boolean; + isMousePointer: boolean; + pointerHeight: number; +}; + +function usePointer(): PointerState & Pick { + const svgRef = useSvgRef(); + const popperRef: PopperProps['popperRef'] = React.useRef(null); + const positionRef = React.useRef({ x: 0, y: 0 }); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event: PointerEvent) => { + positionRef.current = { + x: event.clientX, + y: event.clientY, + }; + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { + ...pointer, + popperRef, + anchorEl: { + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; +} export function ItemTooltip() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } - // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -28,7 +106,8 @@ export function ItemTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', diff --git a/docs/data/charts/tooltip/ItemTooltipFixedY.js b/docs/data/charts/tooltip/ItemTooltipFixedY.js index 4e19cd1bc9f62..aa11e750dc8dd 100644 --- a/docs/data/charts/tooltip/ItemTooltipFixedY.js +++ b/docs/data/charts/tooltip/ItemTooltipFixedY.js @@ -1,28 +1,90 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useDrawingArea, useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement } from './generateVirtualElement'; + +function usePointer() { + const svgRef = useSvgRef(); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + }; + }, [svgRef]); + + return pointer; +} export function ItemTooltipFixedY() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); + const { isActive } = usePointer(); + + const popperRef = React.useRef(null); + const positionRef = React.useRef({ x: 0, y: 0 }); const svgRef = useSvgRef(); // Get the ref of the component. const drawingArea = useDrawingArea(); // Get the dimensions of the chart inside the . - if (!tooltipData || !mousePosition) { + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleMove = (event) => { + positionRef.current = { + x: event.clientX, + y: event.clientY, + }; + popperRef.current?.update(); + }; + + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef, drawingArea.top]); + + if (!tooltipData || !isActive) { // No data to display return null; } - const tooltipPosition = { - ...mousePosition, - // Add the y-coordinate of the to the to margin between the and the drawing area - y: svgRef.current.getBoundingClientRect().top + drawingArea.top, - }; - return ( ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }} + popperRef={popperRef} > diff --git a/docs/data/charts/tooltip/ItemTooltipFixedY.tsx b/docs/data/charts/tooltip/ItemTooltipFixedY.tsx index 3059c260afcac..925b27453789c 100644 --- a/docs/data/charts/tooltip/ItemTooltipFixedY.tsx +++ b/docs/data/charts/tooltip/ItemTooltipFixedY.tsx @@ -1,28 +1,96 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; -import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import Popper, { PopperProps } from '@mui/material/Popper'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useDrawingArea, useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement, MousePosition } from './generateVirtualElement'; + +type PointerState = { + isActive: boolean; + isMousePointer: boolean; + pointerHeight: number; +}; + +function usePointer(): PointerState { + const svgRef = useSvgRef(); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + }; + }, [svgRef]); + + return pointer; +} export function ItemTooltipFixedY() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); + const { isActive } = usePointer(); + + const popperRef: PopperProps['popperRef'] = React.useRef(null); + const positionRef = React.useRef({ x: 0, y: 0 }); const svgRef = useSvgRef(); // Get the ref of the component. const drawingArea = useDrawingArea(); // Get the dimensions of the chart inside the . - if (!tooltipData || !mousePosition) { + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleMove = (event: PointerEvent) => { + positionRef.current = { + x: event.clientX, + y: event.clientY, + }; + popperRef.current?.update(); + }; + + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef, drawingArea.top]); + + if (!tooltipData || !isActive) { // No data to display return null; } - const tooltipPosition: MousePosition = { - ...mousePosition, - // Add the y-coordinate of the to the to margin between the and the drawing area - y: svgRef.current.getBoundingClientRect().top + drawingArea.top, - }; - return ( ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }} + popperRef={popperRef} > diff --git a/docs/data/charts/tooltip/ItemTooltipTopElement.js b/docs/data/charts/tooltip/ItemTooltipTopElement.js index 7d86e69b81e40..538a729564c9b 100644 --- a/docs/data/charts/tooltip/ItemTooltipTopElement.js +++ b/docs/data/charts/tooltip/ItemTooltipTopElement.js @@ -2,14 +2,58 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef, useXAxis, useXScale, useYScale } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement } from './generateVirtualElement'; + +function usePointer() { + const svgRef = useSvgRef(); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + }; + }, [svgRef]); + + return pointer; +} export function ItemTooltipTopElement() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); + const { isActive } = usePointer(); // Get xAxis config to access its data array. const xAxis = useXAxis(); // Get the scale which map values to SVG coordinates. @@ -21,7 +65,7 @@ export function ItemTooltipTopElement() { // Get the ref of the component. const svgRef = useSvgRef(); - if (!tooltipData || !mousePosition || !xAxis.data) { + if (!tooltipData || !isActive || !xAxis.data) { // No data to display return null; } @@ -41,7 +85,6 @@ export function ItemTooltipTopElement() { const svgXPosition = xScale(xValue) ?? 0; const tooltipPosition = { - ...mousePosition, // Add half of `yScale.step()` to be in the middle of the band. x: svgRef.current.getBoundingClientRect().left + svgXPosition + xScale.step() / 2, @@ -58,7 +101,19 @@ export function ItemTooltipTopElement() { }} open placement="top" - anchorEl={generateVirtualElement(tooltipPosition)} + anchorEl={{ + getBoundingClientRect: () => ({ + x: tooltipPosition.x, + y: tooltipPosition.y, + top: tooltipPosition.y, + left: tooltipPosition.x, + right: tooltipPosition.x, + bottom: tooltipPosition.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }} > diff --git a/docs/data/charts/tooltip/ItemTooltipTopElement.tsx b/docs/data/charts/tooltip/ItemTooltipTopElement.tsx index d69ab33e96da4..9c73fa1d43831 100644 --- a/docs/data/charts/tooltip/ItemTooltipTopElement.tsx +++ b/docs/data/charts/tooltip/ItemTooltipTopElement.tsx @@ -2,14 +2,64 @@ import * as React from 'react'; import { ScaleBand } from '@mui/x-charts-vendor/d3-scale'; import NoSsr from '@mui/material/NoSsr'; import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef, useXAxis, useXScale, useYScale } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement, MousePosition } from './generateVirtualElement'; + +type PointerState = { + isActive: boolean; + isMousePointer: boolean; + pointerHeight: number; +}; + +function usePointer(): PointerState { + const svgRef = useSvgRef(); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + }; + }, [svgRef]); + + return pointer; +} export function ItemTooltipTopElement() { const tooltipData = useItemTooltip<'bar'>(); - const mousePosition = useMouseTracker(); + const { isActive } = usePointer(); // Get xAxis config to access its data array. const xAxis = useXAxis(); // Get the scale which map values to SVG coordinates. @@ -21,7 +71,7 @@ export function ItemTooltipTopElement() { // Get the ref of the component. const svgRef = useSvgRef(); - if (!tooltipData || !mousePosition || !xAxis.data) { + if (!tooltipData || !isActive || !xAxis.data) { // No data to display return null; } @@ -40,8 +90,7 @@ export function ItemTooltipTopElement() { const svgYPosition = yScale(tooltipData.value) ?? 0; const svgXPosition = xScale(xValue) ?? 0; - const tooltipPosition: MousePosition = { - ...mousePosition, + const tooltipPosition = { // Add half of `yScale.step()` to be in the middle of the band. x: svgRef.current.getBoundingClientRect().left + @@ -60,7 +109,19 @@ export function ItemTooltipTopElement() { }} open placement="top" - anchorEl={generateVirtualElement(tooltipPosition)} + anchorEl={{ + getBoundingClientRect: () => ({ + x: tooltipPosition.x, + y: tooltipPosition.y, + top: tooltipPosition.y, + left: tooltipPosition.x, + right: tooltipPosition.x, + bottom: tooltipPosition.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }} > diff --git a/docs/data/charts/tooltip/generateVirtualElement.ts b/docs/data/charts/tooltip/generateVirtualElement.ts deleted file mode 100644 index 8052c84a24e14..0000000000000 --- a/docs/data/charts/tooltip/generateVirtualElement.ts +++ /dev/null @@ -1,44 +0,0 @@ -export type MousePosition = { - x: number; - y: number; - pointerType: 'mouse' | 'touch' | 'pen'; - height: number; -}; - -/** - * Helper faking an element bounding box for the Popper. - */ -export function generateVirtualElement(mousePosition: { x: number; y: number } | null) { - if (mousePosition === null) { - return { - getBoundingClientRect: () => ({ - width: 0, - height: 0, - x: 0, - y: 0, - top: 0, - right: 0, - bottom: 0, - left: 0, - toJSON: () => '', - }), - }; - } - const { x, y } = mousePosition; - const boundingBox = { - width: 0, - height: 0, - x, - y, - top: y, - right: x, - bottom: y, - left: x, - }; - return { - getBoundingClientRect: () => ({ - ...boundingBox, - toJSON: () => JSON.stringify(boundingBox), - }), - }; -} diff --git a/docs/data/charts/tooltip/tooltip.md b/docs/data/charts/tooltip/tooltip.md index 6558860652234..f53171fca6e52 100644 --- a/docs/data/charts/tooltip/tooltip.md +++ b/docs/data/charts/tooltip/tooltip.md @@ -15,8 +15,8 @@ If you are using composition, you can add the `` component and The tooltip can be triggered by two kinds of events: -- `'item'`—when the user's mouse hovers over an item on the chart, the tooltip will display data about this specific item. -- `'axis'`—the user's mouse position is associated with a value of the x-axis. The tooltip will display data about all series at this specific x value. +- `'item'`—when the user's mouse hovers over an item on the chart, the tooltip displays data about this specific item. +- `'axis'`—the user's mouse position is associated with a value of the x-axis. The tooltip displays data about all series at this specific x value. - `'none'`—disable the tooltip. {{"demo": "Interaction.js"}} @@ -79,7 +79,7 @@ See [Label—Conditional formatting](/x/react-charts/label/#conditional-formatti ### Hiding values You can hide the axis value with `hideTooltip` in the `xAxis` props. -It will remove the header showing the x-axis value from the tooltip. +It removes the header showing the x-axis value from the tooltip. ```jsx ({ - color: ownerState.open ? 'secondary' : 'primary', + color: ownerState.isPickerOpen ? 'secondary' : 'primary', }), }} /> diff --git a/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx index e1272f2776bad..ea3b72c58ed1c 100644 --- a/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx +++ b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx @@ -9,7 +9,7 @@ export default function CustomSlotPropsCallback() { ({ - color: ownerState.open ? 'secondary' : 'primary', + color: ownerState.isPickerOpen ? 'secondary' : 'primary', }), }} /> diff --git a/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx.preview b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx.preview index 100188718fae0..70bc3e35f25ec 100644 --- a/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx.preview +++ b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx.preview @@ -1,7 +1,7 @@ ({ - color: ownerState.open ? 'secondary' : 'primary', + color: ownerState.isPickerOpen ? 'secondary' : 'primary', }), }} /> \ No newline at end of file diff --git a/docs/data/common-concepts/custom-components/custom-components.md b/docs/data/common-concepts/custom-components/custom-components.md index cd89c78cbe620..1875037ac5bfa 100644 --- a/docs/data/common-concepts/custom-components/custom-components.md +++ b/docs/data/common-concepts/custom-components/custom-components.md @@ -11,7 +11,7 @@ This is the role of all the `baseXXX` component on the Data Grid component (`bas These slots receive props that should be as generic as possible so that it is easy to interface any other design system. Other slots allow you to override parts of the MUI X UI components with a custom UI built specifically for this component. -This is the role of slots like `calendarHeader` on the `DateCalendar` component or `item` on the `RichTreeView` component. +This is the role of slots like `calendarHeader` on the `DateCalendar` component or `item` on the Rich Tree View component. These slots receive props specific to this part of the UI and will most likely not be re-use throughout your application. ## Basic usage diff --git a/docs/data/data-grid/column-definition/AutogeneratedRows.js b/docs/data/data-grid/column-definition/AutogeneratedRows.js index d5689bc6ecbdb..b9059be151f09 100644 --- a/docs/data/data-grid/column-definition/AutogeneratedRows.js +++ b/docs/data/data-grid/column-definition/AutogeneratedRows.js @@ -1,6 +1,7 @@ import * as React from 'react'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, isAutogeneratedRow, useGridApiRef, useKeepGroupedColumnsHidden, @@ -8,7 +9,7 @@ import { import { useMovieData } from '@mui/x-data-grid-generator'; const columns = [ - { field: '__row_group_by_columns_group__', width: 200 }, + { field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, width: 200 }, { field: 'company', width: 200 }, { field: 'title', diff --git a/docs/data/data-grid/column-definition/AutogeneratedRows.tsx b/docs/data/data-grid/column-definition/AutogeneratedRows.tsx index c58c4bf79e2de..cb127be3cb889 100644 --- a/docs/data/data-grid/column-definition/AutogeneratedRows.tsx +++ b/docs/data/data-grid/column-definition/AutogeneratedRows.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, GridColDef, isAutogeneratedRow, useGridApiRef, @@ -9,7 +10,7 @@ import { import { useMovieData } from '@mui/x-data-grid-generator'; const columns: GridColDef[] = [ - { field: '__row_group_by_columns_group__', width: 200 }, + { field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, width: 200 }, { field: 'company', width: 200 }, { field: 'title', diff --git a/docs/data/data-grid/column-definition/column-definition.md b/docs/data/data-grid/column-definition/column-definition.md index 75364b0c7c816..89c245b09175b 100644 --- a/docs/data/data-grid/column-definition/column-definition.md +++ b/docs/data/data-grid/column-definition/column-definition.md @@ -102,8 +102,8 @@ Read more in the [handling autogenerated rows](/x/react-data-grid/column-definit ::: :::warning -[Row grouping](/x/react-data-grid/row-grouping/) uses the [`groupingValueGetter`](/x/react-data-grid/row-grouping/#using-groupingvaluegetter-for-complex-grouping-value) instead of `valueGetter` to get the value for the grouping. -The value passed to the `groupingValueGetter` is the raw row value (`row[field]`) even if the column definition has a `valueGetter` defined. +[Row grouping](/x/react-data-grid/row-grouping/) uses the [`groupingValueGetter()`](/x/react-data-grid/row-grouping/#using-groupingvaluegetter-for-complex-grouping-value) instead of `valueGetter` to get the value for the grouping. +The value passed to the `groupingValueGetter()` is the raw row value (`row[field]`) even if the column definition has a `valueGetter` defined. ::: ### Value formatter diff --git a/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.js b/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.js index bfa80b2c25232..96ed3a295aa25 100644 --- a/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.js +++ b/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.js @@ -4,10 +4,11 @@ import { GridToolbar, useKeepGroupedColumnsHidden, useGridApiRef, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, } from '@mui/x-data-grid-premium'; import { useDemoData } from '@mui/x-data-grid-generator'; -const hiddenFields = ['id', '__row_group_by_columns_group__', 'status']; +const hiddenFields = ['id', GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, 'status']; const getTogglableColumns = (columns) => { return columns diff --git a/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.tsx b/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.tsx index 9c667a5f013ef..83eb196585b7a 100644 --- a/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.tsx +++ b/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.tsx @@ -5,10 +5,11 @@ import { GridColDef, useKeepGroupedColumnsHidden, useGridApiRef, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, } from '@mui/x-data-grid-premium'; import { useDemoData } from '@mui/x-data-grid-generator'; -const hiddenFields = ['id', '__row_group_by_columns_group__', 'status']; +const hiddenFields = ['id', GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, 'status']; const getTogglableColumns = (columns: GridColDef[]) => { return columns diff --git a/docs/data/data-grid/column-visibility/column-visibility.md b/docs/data/data-grid/column-visibility/column-visibility.md index 08d912a07057d..3b7a149dd7012 100644 --- a/docs/data/data-grid/column-visibility/column-visibility.md +++ b/docs/data/data-grid/column-visibility/column-visibility.md @@ -86,8 +86,13 @@ In the following demo, the columns panel is disabled, and access to columns `id` To show or hide specific columns in the column visibility panel, use the `slotProps.columnsManagement.getTogglableColumns` prop. It should return an array of column field names. ```tsx -// stop `id`, `__row_group_by_columns_group__`, and `status` columns to be togglable -const hiddenFields = ['id', '__row_group_by_columns_group__', 'status']; +import { + DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, +} from '@mui/x-data-grid-premium'; + +// stop `id`, GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, and `status` columns to be togglable +const hiddenFields = ['id', GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, 'status']; const getTogglableColumns = (columns: GridColDef[]) => { return columns @@ -95,7 +100,7 @@ const getTogglableColumns = (columns: GridColDef[]) => { .map((column) => column.field); }; -Using the Data Grid with Joy UI components

+

Using the Data Grid with Joy UI components.

{{"demo": "GridJoyUISlots.js", "bg": "inline"}} diff --git a/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.js b/docs/data/data-grid/layout/GridOverlayHeight.js similarity index 95% rename from docs/data/data-grid/layout/AutoHeightOverlayNoSnap.js rename to docs/data/data-grid/layout/GridOverlayHeight.js index 9b93efc6eaaf3..385ed124616a7 100644 --- a/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.js +++ b/docs/data/data-grid/layout/GridOverlayHeight.js @@ -56,11 +56,10 @@ function CustomNoRowsOverlay() { ); } -export default function AutoHeightOverlayNoSnap() { +export default function GridOverlayHeight() { return ( - + + { + console.log(`send message to ${params.row.phone}`); + }; + return ( + + + + ); +} + +function ListViewCell(params) { + return ( + + + + + {params.row.name} + + + {params.row.position} + + + + + ); +} + +const listColDef = { + field: 'listColumn', + renderCell: ListViewCell, +}; + +const VISIBLE_FIELDS = ['avatar', 'name', 'position']; + +export default function ListView() { + const [isListView, setIsListView] = React.useState(true); + + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 20, + visibleFields: VISIBLE_FIELDS, + }); + + const columns = React.useMemo(() => { + return [ + ...data.columns, + { + type: 'actions', + field: 'actions', + width: 75, + getActions: (params) => [], + }, + ]; + }, [data.columns]); + + const rowHeight = isListView ? 64 : 52; + + return ( + + setIsListView(event.target.checked)} + /> + } + label="Enable list view" + /> + + + + + ); +} diff --git a/docs/data/data-grid/list-view/ListView.tsx b/docs/data/data-grid/list-view/ListView.tsx new file mode 100644 index 0000000000000..40239d5a8e41a --- /dev/null +++ b/docs/data/data-grid/list-view/ListView.tsx @@ -0,0 +1,113 @@ +import * as React from 'react'; +import { + DataGridPro, + GridRenderCellParams, + GridListColDef, + GridColDef, + GridRowParams, +} from '@mui/x-data-grid-pro'; +import Box from '@mui/material/Box'; +import { useDemoData } from '@mui/x-data-grid-generator'; +import Stack from '@mui/material/Stack'; +import Avatar from '@mui/material/Avatar'; +import Typography from '@mui/material/Typography'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import IconButton from '@mui/material/IconButton'; +import MessageIcon from '@mui/icons-material/Message'; + +function MessageAction(params: Pick) { + const handleMessage = () => { + console.log(`send message to ${params.row.phone}`); + }; + return ( + + + + ); +} + +function ListViewCell(params: GridRenderCellParams) { + return ( + + + + + {params.row.name} + + + {params.row.position} + + + + + ); +} + +const listColDef: GridListColDef = { + field: 'listColumn', + renderCell: ListViewCell, +}; + +const VISIBLE_FIELDS = ['avatar', 'name', 'position']; + +export default function ListView() { + const [isListView, setIsListView] = React.useState(true); + + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 20, + visibleFields: VISIBLE_FIELDS, + }); + + const columns: GridColDef[] = React.useMemo(() => { + return [ + ...data.columns, + { + type: 'actions', + field: 'actions', + width: 75, + getActions: (params) => [], + }, + ]; + }, [data.columns]); + + const rowHeight = isListView ? 64 : 52; + + return ( + + setIsListView(event.target.checked)} + /> + } + label="Enable list view" + /> + + + + + ); +} diff --git a/docs/data/data-grid/list-view/ListViewAdvanced.js b/docs/data/data-grid/list-view/ListViewAdvanced.js new file mode 100644 index 0000000000000..2185d42a142d7 --- /dev/null +++ b/docs/data/data-grid/list-view/ListViewAdvanced.js @@ -0,0 +1,361 @@ +import * as React from 'react'; +import { + GridActionsCellItem, + useGridApiRef, + DataGridPremium, + gridClasses, +} from '@mui/x-data-grid-premium'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import Avatar from '@mui/material/Avatar'; +import Stack from '@mui/material/Stack'; +import OpenIcon from '@mui/icons-material/Visibility'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import CSSBaseline from '@mui/material/CssBaseline'; +import { randomId } from '@mui/x-data-grid-generator'; +import { FileIcon } from './components/FileIcon'; +import { DetailsDrawer } from './components/DetailsDrawer'; +import { ListCell } from './components/ListCell'; +import { Toolbar } from './components/Toolbar'; +import { INITIAL_ROWS } from './data'; +import { FILE_TYPES } from './constants'; + +import { formatDate, formatSize, stringAvatar } from './utils'; +import { ActionDrawer } from './components/ActionDrawer'; +import { RenameDialog } from './components/RenameDialog'; + +export default function ListViewAdvanced() { + // This is used only for the example - renders the drawer inside the container + const containerRef = React.useRef(null); + const container = () => containerRef.current; + + const isListView = useMediaQuery('(min-width: 700px)'); + + const apiRef = useGridApiRef(); + + const [rows, setRows] = React.useState(INITIAL_ROWS); + + const [loading, setLoading] = React.useState(false); + + const [overlayState, setOverlayState] = React.useState({ + overlay: null, + params: null, + }); + + const handleCloseOverlay = () => { + setOverlayState({ overlay: null, params: null }); + }; + + const handleDelete = React.useCallback((ids) => { + setRows((prevRows) => prevRows.filter((row) => !ids.includes(row.id))); + }, []); + + const handleUpdate = React.useCallback((id, field, value) => { + setRows((prevRows) => + prevRows.map((row) => + row.id === id + ? { ...row, [field]: value, updatedAt: new Date().toISOString() } + : row, + ), + ); + }, []); + + const handleUpload = React.useCallback((event) => { + if (!event.target.files) { + return; + } + + const file = event.target.files[0]; + const createdAt = new Date().toISOString(); + + const fileType = file.type.split('/')[1]; + + // validate file type + if (!FILE_TYPES.includes(fileType)) { + alert('Invalid file type'); + return; + } + + const row = { + id: randomId(), + name: file.name, + description: '', + type: fileType, + size: file.size, + createdBy: 'Kenan Yusuf', + createdAt, + updatedAt: createdAt, + state: 'pending', + }; + + event.target.value = ''; + + // Add temporary row + setLoading(true); + setRows((prevRows) => [...prevRows, row]); + + // Simulate server response time + const timeout = Math.floor(Math.random() * 3000) + 2000; + setTimeout(() => { + const uploadedRow = { ...row, state: 'uploaded' }; + setRows((prevRows) => + prevRows.map((r) => (r.id === row.id ? uploadedRow : r)), + ); + setOverlayState({ overlay: 'actions', params: { row } }); + setLoading(false); + }, timeout); + }, []); + + const columns = React.useMemo( + () => [ + { + field: 'name', + headerName: 'Name', + width: 350, + editable: true, + hideable: false, + renderCell: (params) => { + return ( + + + {params.value} + + ); + }, + }, + { + field: 'createdBy', + headerName: 'Owner', + width: 200, + renderCell: (params) => { + const avatarProps = stringAvatar(params.value); + return ( + + + {params.value} + + ); + }, + }, + { + field: 'createdAt', + headerName: 'Added', + type: 'date', + width: 200, + valueFormatter: formatDate, + }, + { + field: 'updatedAt', + headerName: 'Modified', + type: 'date', + width: 200, + valueFormatter: formatDate, + }, + { + field: 'type', + headerName: 'Type', + width: 150, + }, + { + field: 'size', + headerName: 'Size', + width: 120, + valueFormatter: formatSize, + }, + { + type: 'actions', + field: 'actions', + resizable: false, + width: 50, + getActions: (params) => [ + } + onClick={() => { + setOverlayState({ overlay: 'actions', params }); + }} + showInMenu + />, + } + onClick={() => + apiRef.current?.startCellEditMode({ + id: params.id, + field: 'name', + }) + } + showInMenu + />, + } + onClick={() => handleDelete([params.id])} + showInMenu + />, + ], + }, + ], + [handleDelete, apiRef], + ); + + const listColDef = React.useMemo( + () => ({ + field: 'listCell', + renderCell: (params) => ( + { + setOverlayState({ overlay: 'actions', params }); + }} + /> + ), + }), + [], + ); + + const getEstimatedRowHeight = () => { + const density = apiRef.current?.state?.density; + + if (isListView) { + switch (density) { + case 'compact': + return 47; + case 'standard': + return 67; + case 'comfortable': + return 97; + default: + return 67; + } + } else { + switch (density) { + case 'compact': + return 47; + case 'standard': + return 55; + case 'comfortable': + return 63; + default: + return 55; + } + } + }; + + const getRowHeight = React.useCallback( + () => (isListView ? 'auto' : undefined), + [isListView], + ); + + return ( + + +
+ + setOverlayState({ overlay: 'actions', params }) + } + hideFooterSelectedRowCount + /> + handleUpdate(id, 'description', value)} + onClose={handleCloseOverlay} + /> + + setOverlayState({ overlay: 'details', params: overlayState.params }) + } + onRename={() => + setOverlayState({ overlay: 'rename', params: overlayState.params }) + } + onDelete={(id) => { + handleDelete([id]); + handleCloseOverlay(); + }} + onClose={handleCloseOverlay} + /> + handleUpdate(id, 'name', value)} + onClose={handleCloseOverlay} + /> +
+
+ ); +} diff --git a/docs/data/data-grid/list-view/ListViewAdvanced.tsx b/docs/data/data-grid/list-view/ListViewAdvanced.tsx new file mode 100644 index 0000000000000..653911e8d8c3a --- /dev/null +++ b/docs/data/data-grid/list-view/ListViewAdvanced.tsx @@ -0,0 +1,381 @@ +import * as React from 'react'; +import { + GridActionsCellItem, + GridColDef, + useGridApiRef, + GridRowParams, + DataGridPremium, + GridRowId, + gridClasses, + GridRowModel, +} from '@mui/x-data-grid-premium'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import Avatar from '@mui/material/Avatar'; +import Stack from '@mui/material/Stack'; +import OpenIcon from '@mui/icons-material/Visibility'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import CSSBaseline from '@mui/material/CssBaseline'; +import { randomId } from '@mui/x-data-grid-generator'; +import { FileIcon } from './components/FileIcon'; +import { DetailsDrawer } from './components/DetailsDrawer'; +import { ListCell } from './components/ListCell'; +import { Toolbar } from './components/Toolbar'; +import { INITIAL_ROWS } from './data'; +import { FILE_TYPES } from './constants'; +import { RowModel, FileType } from './types'; +import { formatDate, formatSize, stringAvatar } from './utils'; +import { ActionDrawer } from './components/ActionDrawer'; +import { RenameDialog } from './components/RenameDialog'; + +export default function ListViewAdvanced() { + // This is used only for the example - renders the drawer inside the container + const containerRef = React.useRef(null); + const container = () => containerRef.current as HTMLElement; + + const isListView = useMediaQuery('(min-width: 700px)'); + + const apiRef = useGridApiRef(); + + const [rows, setRows] = React.useState[]>(INITIAL_ROWS); + + const [loading, setLoading] = React.useState(false); + + const [overlayState, setOverlayState] = React.useState<{ + overlay: 'actions' | 'details' | 'rename' | null; + params: Pick, 'row'> | null; + }>({ + overlay: null, + params: null, + }); + + const handleCloseOverlay = () => { + setOverlayState({ overlay: null, params: null }); + }; + + const handleDelete = React.useCallback((ids: GridRowId[]) => { + setRows((prevRows) => prevRows.filter((row) => !ids.includes(row.id))); + }, []); + + const handleUpdate = React.useCallback( + ( + id: GridRowId, + field: GridRowParams['columns'][number]['field'], + value: string, + ) => { + setRows((prevRows) => + prevRows.map((row) => + row.id === id + ? { ...row, [field]: value, updatedAt: new Date().toISOString() } + : row, + ), + ); + }, + [], + ); + + const handleUpload = React.useCallback( + (event: React.ChangeEvent) => { + if (!event.target.files) { + return; + } + + const file = event.target.files[0]; + const createdAt = new Date().toISOString(); + + const fileType = file.type.split('/')[1]; + + // validate file type + if (!FILE_TYPES.includes(fileType as FileType)) { + alert('Invalid file type'); + return; + } + + const row: RowModel = { + id: randomId(), + name: file.name, + description: '', + type: fileType as FileType, + size: file.size, + createdBy: 'Kenan Yusuf', + createdAt, + updatedAt: createdAt, + state: 'pending', + }; + + event.target.value = ''; + + // Add temporary row + setLoading(true); + setRows((prevRows) => [...prevRows, row]); + + // Simulate server response time + const timeout = Math.floor(Math.random() * 3000) + 2000; + setTimeout(() => { + const uploadedRow: RowModel = { ...row, state: 'uploaded' }; + setRows((prevRows) => + prevRows.map((r) => (r.id === row.id ? uploadedRow : r)), + ); + setOverlayState({ overlay: 'actions', params: { row } }); + setLoading(false); + }, timeout); + }, + [], + ); + + const columns: GridColDef[] = React.useMemo( + () => [ + { + field: 'name', + headerName: 'Name', + width: 350, + editable: true, + hideable: false, + renderCell: (params) => { + return ( + + + {params.value} + + ); + }, + }, + { + field: 'createdBy', + headerName: 'Owner', + width: 200, + renderCell: (params) => { + const avatarProps = stringAvatar(params.value); + return ( + + + {params.value} + + ); + }, + }, + { + field: 'createdAt', + headerName: 'Added', + type: 'date', + width: 200, + valueFormatter: formatDate, + }, + { + field: 'updatedAt', + headerName: 'Modified', + type: 'date', + width: 200, + valueFormatter: formatDate, + }, + { + field: 'type', + headerName: 'Type', + width: 150, + }, + { + field: 'size', + headerName: 'Size', + width: 120, + valueFormatter: formatSize, + }, + { + type: 'actions', + field: 'actions', + resizable: false, + width: 50, + getActions: (params) => [ + } + onClick={() => { + setOverlayState({ overlay: 'actions', params }); + }} + showInMenu + />, + } + onClick={() => + apiRef.current?.startCellEditMode({ + id: params.id, + field: 'name', + }) + } + showInMenu + />, + } + onClick={() => handleDelete([params.id])} + showInMenu + />, + ], + }, + ], + [handleDelete, apiRef], + ); + + const listColDef: GridColDef = React.useMemo( + () => ({ + field: 'listCell', + renderCell: (params) => ( + { + setOverlayState({ overlay: 'actions', params }); + }} + /> + ), + }), + [], + ); + + const getEstimatedRowHeight = () => { + const density = apiRef.current?.state?.density; + + if (isListView) { + switch (density) { + case 'compact': + return 47; + case 'standard': + return 67; + case 'comfortable': + return 97; + default: + return 67; + } + } else { + switch (density) { + case 'compact': + return 47; + case 'standard': + return 55; + case 'comfortable': + return 63; + default: + return 55; + } + } + }; + + const getRowHeight = React.useCallback( + () => (isListView ? 'auto' : undefined), + [isListView], + ); + + return ( + + +
+ + setOverlayState({ overlay: 'actions', params }) + } + hideFooterSelectedRowCount + /> + + handleUpdate(id, 'description', value)} + onClose={handleCloseOverlay} + /> + + + setOverlayState({ overlay: 'details', params: overlayState.params }) + } + onRename={() => + setOverlayState({ overlay: 'rename', params: overlayState.params }) + } + onDelete={(id) => { + handleDelete([id]); + handleCloseOverlay(); + }} + onClose={handleCloseOverlay} + /> + + handleUpdate(id, 'name', value)} + onClose={handleCloseOverlay} + /> +
+
+ ); +} diff --git a/docs/data/data-grid/list-view/components/ActionDrawer.js b/docs/data/data-grid/list-view/components/ActionDrawer.js new file mode 100644 index 0000000000000..8c4e29e9955d9 --- /dev/null +++ b/docs/data/data-grid/list-view/components/ActionDrawer.js @@ -0,0 +1,88 @@ +import * as React from 'react'; + +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import OpenIcon from '@mui/icons-material/Visibility'; +import { FileIcon } from './FileIcon'; +import { formatDate, formatSize } from '../utils'; +import { Drawer, DrawerHeader } from './Drawer'; + +function DrawerContent(props) { + const { params, onDelete, onPreview, onRename } = props; + return ( + + + + + {params.row.name} + + + {params.row.createdBy} + + + · + + + {formatSize(params.row.size)} + + + + {params.row.updatedAt + ? `Updated ${formatDate(params.row.updatedAt)}` + : `Added ${formatDate(params.row.createdAt)}`} + + + + + + + + + + + Preview + + + + + + + + Rename + + + + onDelete(params.row.id)}> + + + + Delete + + + + + ); +} + +export function ActionDrawer(props) { + const { params, onPreview, onRename, onDelete, ...other } = props; + return ( + + {params && ( + + )} + + ); +} diff --git a/docs/data/data-grid/list-view/components/ActionDrawer.tsx b/docs/data/data-grid/list-view/components/ActionDrawer.tsx new file mode 100644 index 0000000000000..533c8cddad5fb --- /dev/null +++ b/docs/data/data-grid/list-view/components/ActionDrawer.tsx @@ -0,0 +1,101 @@ +import * as React from 'react'; +import { GridRowId, GridRowParams } from '@mui/x-data-grid-premium'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import OpenIcon from '@mui/icons-material/Visibility'; +import { FileIcon } from './FileIcon'; +import { formatDate, formatSize } from '../utils'; +import { Drawer, DrawerHeader, DrawerProps } from './Drawer'; +import { RowModel } from '../types'; + +interface ActionDrawerProps extends DrawerProps { + params: Pick, 'row'> | null; + onPreview: () => void; + onRename: () => void; + onDelete: (id: GridRowId) => void; +} + +function DrawerContent( + props: { params: Pick, 'row'> } & Pick< + ActionDrawerProps, + 'onDelete' | 'onPreview' | 'onRename' + >, +) { + const { params, onDelete, onPreview, onRename } = props; + return ( + + + + + {params.row.name} + + + {params.row.createdBy} + + + · + + + {formatSize(params.row.size)} + + + + {params.row.updatedAt + ? `Updated ${formatDate(params.row.updatedAt)}` + : `Added ${formatDate(params.row.createdAt)}`} + + + + + + + + + + + Preview + + + + + + + + Rename + + + + onDelete(params.row.id)}> + + + + Delete + + + + + ); +} + +export function ActionDrawer(props: ActionDrawerProps) { + const { params, onPreview, onRename, onDelete, ...other } = props; + return ( + + {params && ( + + )} + + ); +} diff --git a/docs/data/data-grid/list-view/components/Card.js b/docs/data/data-grid/list-view/components/Card.js new file mode 100644 index 0000000000000..5d2e880bf605b --- /dev/null +++ b/docs/data/data-grid/list-view/components/Card.js @@ -0,0 +1,70 @@ +import * as React from 'react'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; + +export function Card(props) { + const { children, ...other } = props; + return ( + + {children} + + ); +} + +export function CardMedia(props) { + const { children, ...other } = props; + return ( + + {children} + + ); +} + +export function CardContent(props) { + const { children, ...other } = props; + return ( + + {children} + + ); +} + +export function CardTitle(props) { + const { children, ...other } = props; + return ( + + {children} + + ); +} + +export function CardDetailList(props) { + const { children, ...other } = props; + return ( + + {children} + + ); +} + +export function CardDetail(props) { + const { children, ...other } = props; + return ( + + {children} + + ); +} diff --git a/docs/data/data-grid/list-view/components/Card.tsx b/docs/data/data-grid/list-view/components/Card.tsx new file mode 100644 index 0000000000000..bd0df76f5db15 --- /dev/null +++ b/docs/data/data-grid/list-view/components/Card.tsx @@ -0,0 +1,70 @@ +import * as React from 'react'; +import Stack, { StackProps } from '@mui/material/Stack'; +import Typography, { TypographyProps } from '@mui/material/Typography'; + +export function Card(props: StackProps) { + const { children, ...other } = props; + return ( + + {children} + + ); +} + +export function CardMedia(props: StackProps) { + const { children, ...other } = props; + return ( + + {children} + + ); +} + +export function CardContent(props: StackProps) { + const { children, ...other } = props; + return ( + + {children} + + ); +} + +export function CardTitle(props: TypographyProps) { + const { children, ...other } = props; + return ( + + {children} + + ); +} + +export function CardDetailList(props: StackProps) { + const { children, ...other } = props; + return ( + + {children} + + ); +} + +export function CardDetail(props: TypographyProps) { + const { children, ...other } = props; + return ( + + {children} + + ); +} diff --git a/docs/data/data-grid/list-view/components/DetailsDrawer.js b/docs/data/data-grid/list-view/components/DetailsDrawer.js new file mode 100644 index 0000000000000..44b1808c622b7 --- /dev/null +++ b/docs/data/data-grid/list-view/components/DetailsDrawer.js @@ -0,0 +1,142 @@ +import * as React from 'react'; + +import Divider from '@mui/material/Divider'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import TextField from '@mui/material/TextField'; +import Avatar from '@mui/material/Avatar'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; +import { Drawer, DrawerHeader } from './Drawer'; +import { FileIcon } from './FileIcon'; +import { formatDate, formatSize, stringAvatar } from '../utils'; + +function Thumbnail() { + return ( + ({ + aspectRatio: '16/9', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: 'text.secondary', + borderRadius: 2, + gap: 1, + backgroundColor: 'grey.200', + ...theme.applyStyles('dark', { + backgroundColor: 'grey.800', + }), + })} + > + + + No preview available + + + ); +} + +function DrawerContent(props) { + const { params, onDescriptionChange, onClose } = props; + const [description, setDescription] = React.useState(params.row.description || ''); + const avatarProps = stringAvatar(params.row.createdBy); + + const handleSave = (event) => { + onDescriptionChange(params.row.id, description); + onClose(event); + }; + + return ( + + + + {params.row.name} + + + + + + setDescription(event.target.value)} + sx={{ mt: 1 }} + multiline + /> + + + + Type + + {params.row.type} + + + + + Size + + {formatSize(params.row.size)} + + + + + Created + + {formatDate(params.row.createdAt)} + + + + + Modified + + {formatDate(params.row.updatedAt)} + + + + + Owner + + + + + {params.row.createdBy} + + + + ); +} + +export function DetailsDrawer(props) { + const { params, listView, onDescriptionChange, onClose, ...other } = props; + return ( + + {params && ( + + )} + + ); +} diff --git a/docs/data/data-grid/list-view/components/DetailsDrawer.tsx b/docs/data/data-grid/list-view/components/DetailsDrawer.tsx new file mode 100644 index 0000000000000..6e4f2545dfeb1 --- /dev/null +++ b/docs/data/data-grid/list-view/components/DetailsDrawer.tsx @@ -0,0 +1,158 @@ +import * as React from 'react'; +import { GridRowId, GridRowParams } from '@mui/x-data-grid-premium'; +import Divider from '@mui/material/Divider'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import TextField from '@mui/material/TextField'; +import Avatar from '@mui/material/Avatar'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; +import { Drawer, DrawerHeader, DrawerProps } from './Drawer'; +import { FileIcon } from './FileIcon'; +import { formatDate, formatSize, stringAvatar } from '../utils'; +import { RowModel } from '../types'; + +interface DetailsDrawerProps extends DrawerProps { + params: Pick, 'row'> | null; + listView: boolean; + onDescriptionChange: (id: GridRowId, value: string) => void; +} + +function Thumbnail() { + return ( + ({ + aspectRatio: '16/9', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: 'text.secondary', + borderRadius: 2, + gap: 1, + backgroundColor: 'grey.200', + ...theme.applyStyles('dark', { + backgroundColor: 'grey.800', + }), + })} + > + + + No preview available + + + ); +} + +function DrawerContent( + props: { params: Pick, 'row'> } & Pick< + DetailsDrawerProps, + 'onDescriptionChange' | 'onClose' + >, +) { + const { params, onDescriptionChange, onClose } = props; + const [description, setDescription] = React.useState(params.row.description || ''); + const avatarProps = stringAvatar(params.row.createdBy); + + const handleSave = (event: React.MouseEvent) => { + onDescriptionChange(params.row.id, description); + onClose(event); + }; + + return ( + + + + {params.row.name} + + + + + + + setDescription(event.target.value)} + sx={{ mt: 1 }} + multiline + /> + + + + + + Type + + {params.row.type} + + + + + Size + + {formatSize(params.row.size)} + + + + + Created + + {formatDate(params.row.createdAt)} + + + + + Modified + + {formatDate(params.row.updatedAt)} + + + + + + Owner + + + + + {params.row.createdBy} + + + + ); +} + +export function DetailsDrawer(props: DetailsDrawerProps) { + const { params, listView, onDescriptionChange, onClose, ...other } = props; + return ( + + {params && ( + + )} + + ); +} diff --git a/docs/data/data-grid/list-view/components/Drawer.js b/docs/data/data-grid/list-view/components/Drawer.js new file mode 100644 index 0000000000000..fcf9d182a6a30 --- /dev/null +++ b/docs/data/data-grid/list-view/components/Drawer.js @@ -0,0 +1,88 @@ +import * as React from 'react'; +import MUISwipeableDrawer from '@mui/material/SwipeableDrawer'; +import Box from '@mui/material/Box'; +import Stack from '@mui/material/Stack'; +import Paper from '@mui/material/Paper'; +import { useMediaQuery } from '@mui/system'; + +function SwipeIndicator() { + return ( + + + + ); +} + +export function DrawerHeader(props) { + const { children, ...other } = props; + + return ( + + + {children} + + + ); +} + +export function Drawer(props) { + const { children, anchor, width = 320, container, ...other } = props; + const isBottomDrawer = anchor === 'bottom'; + const isTouch = useMediaQuery('(hover: none)'); + + return ( + {}} // required by SwipeableDrawer but not used in this demo + > + {isTouch && isBottomDrawer && } + {children} + + ); +} diff --git a/docs/data/data-grid/list-view/components/Drawer.tsx b/docs/data/data-grid/list-view/components/Drawer.tsx new file mode 100644 index 0000000000000..a859781e4417d --- /dev/null +++ b/docs/data/data-grid/list-view/components/Drawer.tsx @@ -0,0 +1,97 @@ +import * as React from 'react'; +import MUISwipeableDrawer, { + SwipeableDrawerProps as MUISwipeableDrawerProps, +} from '@mui/material/SwipeableDrawer'; +import Box from '@mui/material/Box'; +import Stack, { StackProps } from '@mui/material/Stack'; +import Paper from '@mui/material/Paper'; +import { useMediaQuery } from '@mui/system'; + +function SwipeIndicator() { + return ( + + + + ); +} + +export interface DrawerHeaderProps extends StackProps {} + +export function DrawerHeader(props: DrawerHeaderProps) { + const { children, ...other } = props; + + return ( + + + {children} + + + ); +} + +export interface DrawerProps extends Omit { + width?: number; + container?: () => HTMLElement; +} + +export function Drawer(props: DrawerProps) { + const { children, anchor, width = 320, container, ...other } = props; + const isBottomDrawer = anchor === 'bottom'; + const isTouch = useMediaQuery('(hover: none)'); + + return ( + {}} // required by SwipeableDrawer but not used in this demo + > + {isTouch && isBottomDrawer && } + {children} + + ); +} diff --git a/docs/data/data-grid/list-view/components/FileIcon.js b/docs/data/data-grid/list-view/components/FileIcon.js new file mode 100644 index 0000000000000..bc1818bb50f4e --- /dev/null +++ b/docs/data/data-grid/list-view/components/FileIcon.js @@ -0,0 +1,47 @@ +import * as React from 'react'; +import SvgIcon from '@mui/material/SvgIcon'; +import TextSnippetIcon from '@mui/icons-material/TextSnippet'; +import VideocamIcon from '@mui/icons-material/Videocam'; +import ImageIcon from '@mui/icons-material/Image'; +import FolderZipIcon from '@mui/icons-material/FolderZip'; + +const FILE_TYPE_ICONS = { + video: { + component: VideocamIcon, + color: 'error', + }, + image: { + component: ImageIcon, + color: 'error', + }, + document: { + component: TextSnippetIcon, + color: 'primary', + }, + archive: { + component: FolderZipIcon, + color: 'inherit', + }, +}; + +const FILE_ICON = { + pdf: FILE_TYPE_ICONS.document, + docx: FILE_TYPE_ICONS.document, + txt: FILE_TYPE_ICONS.document, + mp4: FILE_TYPE_ICONS.video, + mov: FILE_TYPE_ICONS.video, + webm: FILE_TYPE_ICONS.video, + jpg: FILE_TYPE_ICONS.image, + jpeg: FILE_TYPE_ICONS.image, + png: FILE_TYPE_ICONS.image, + gif: FILE_TYPE_ICONS.image, + tiff: FILE_TYPE_ICONS.image, + webp: FILE_TYPE_ICONS.image, + zip: FILE_TYPE_ICONS.archive, +}; + +export function FileIcon(props) { + const { type } = props; + const iconProps = FILE_ICON[type]; + return ; +} diff --git a/docs/data/data-grid/list-view/components/FileIcon.tsx b/docs/data/data-grid/list-view/components/FileIcon.tsx new file mode 100644 index 0000000000000..ac70cced33bf7 --- /dev/null +++ b/docs/data/data-grid/list-view/components/FileIcon.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; +import TextSnippetIcon from '@mui/icons-material/TextSnippet'; +import VideocamIcon from '@mui/icons-material/Videocam'; +import ImageIcon from '@mui/icons-material/Image'; +import FolderZipIcon from '@mui/icons-material/FolderZip'; +import { FileType } from '../types'; + +const FILE_TYPE_ICONS = { + video: { + component: VideocamIcon, + color: 'error', + }, + image: { + component: ImageIcon, + color: 'error', + }, + document: { + component: TextSnippetIcon, + color: 'primary', + }, + archive: { + component: FolderZipIcon, + color: 'inherit', + }, +} satisfies Record< + string, + { component: React.ElementType; color: SvgIconProps['color'] } +>; + +const FILE_ICON: Record< + FileType, + { component: React.ElementType; color: SvgIconProps['color'] } +> = { + pdf: FILE_TYPE_ICONS.document, + docx: FILE_TYPE_ICONS.document, + txt: FILE_TYPE_ICONS.document, + mp4: FILE_TYPE_ICONS.video, + mov: FILE_TYPE_ICONS.video, + webm: FILE_TYPE_ICONS.video, + jpg: FILE_TYPE_ICONS.image, + jpeg: FILE_TYPE_ICONS.image, + png: FILE_TYPE_ICONS.image, + gif: FILE_TYPE_ICONS.image, + tiff: FILE_TYPE_ICONS.image, + webp: FILE_TYPE_ICONS.image, + zip: FILE_TYPE_ICONS.archive, +}; + +export function FileIcon(props: SvgIconProps & { type: FileType }) { + const { type } = props; + const iconProps = FILE_ICON[type]; + return ; +} diff --git a/docs/data/data-grid/list-view/components/ListCell.js b/docs/data/data-grid/list-view/components/ListCell.js new file mode 100644 index 0000000000000..19c1688b1d748 --- /dev/null +++ b/docs/data/data-grid/list-view/components/ListCell.js @@ -0,0 +1,110 @@ +import * as React from 'react'; +import { + gridColumnVisibilityModelSelector, + gridDensitySelector, + useGridApiContext, + useGridSelector, +} from '@mui/x-data-grid-premium'; +import IconButton from '@mui/material/IconButton'; +import Box from '@mui/material/Box'; +import GridMoreVertIcon from '@mui/icons-material/MoreVert'; +import { + Card, + CardContent, + CardDetailList, + CardDetail, + CardTitle, + CardMedia, +} from './Card'; +import { FileIcon } from './FileIcon'; +import { formatDate, formatSize } from '../utils'; + +const ICON_SIZE_BY_DENSITY = { + compact: 24, + standard: 32, + comfortable: 16, +}; + +function Thumbnail(props) { + const { fileIcon } = props; + return ( + ({ + position: 'relative', + borderRadius: 1, + width: 64, + height: 64, + overflow: 'hidden', + backgroundColor: 'grey.200', + ...theme.applyStyles('dark', { + backgroundColor: 'grey.800', + }), + })} + > + + {fileIcon} + + + ); +} + +export function ListCell(props) { + const { onOpenActions, ...params } = props; + const apiRef = useGridApiContext(); + const density = useGridSelector(apiRef, gridDensitySelector); + const columnVisibilityModel = useGridSelector( + apiRef, + gridColumnVisibilityModelSelector, + ); + + const showCreatedBy = columnVisibilityModel.createdBy !== false; + const showSize = columnVisibilityModel.size !== false; + const showCreatedAt = columnVisibilityModel.createdAt !== false; + const showUpdatedAt = columnVisibilityModel.updatedAt !== false; + const showThumbnail = density === 'comfortable'; + + const icon = ( + + ); + + return ( + + {showThumbnail ? : icon} + + {params.row.name} + {density !== 'compact' && (showCreatedBy || showSize) && ( + + {showCreatedBy && {params.row.createdBy}} + {showSize && {formatSize(params.row.size)}} + + )} + {density === 'comfortable' && (showCreatedAt || showUpdatedAt) && ( + + {showUpdatedAt && `Updated ${formatDate(params.row.updatedAt)}`} + + )} + + + { + event.stopPropagation(); + onOpenActions(); + }} + sx={{ mr: -0.75 }} + > + + + + ); +} diff --git a/docs/data/data-grid/list-view/components/ListCell.tsx b/docs/data/data-grid/list-view/components/ListCell.tsx new file mode 100644 index 0000000000000..250d5b82c2178 --- /dev/null +++ b/docs/data/data-grid/list-view/components/ListCell.tsx @@ -0,0 +1,117 @@ +import * as React from 'react'; +import { + gridColumnVisibilityModelSelector, + GridDensity, + gridDensitySelector, + GridRenderCellParams, + useGridApiContext, + useGridSelector, +} from '@mui/x-data-grid-premium'; +import IconButton from '@mui/material/IconButton'; +import Box from '@mui/material/Box'; +import GridMoreVertIcon from '@mui/icons-material/MoreVert'; +import { + Card, + CardContent, + CardDetailList, + CardDetail, + CardTitle, + CardMedia, +} from './Card'; +import { FileIcon } from './FileIcon'; +import { formatDate, formatSize } from '../utils'; +import { RowModel } from '../types'; + +interface ListCellProps extends GridRenderCellParams { + onOpenActions: () => void; +} + +const ICON_SIZE_BY_DENSITY: Record = { + compact: 24, + standard: 32, + comfortable: 16, +}; + +function Thumbnail(props: { fileIcon: React.ReactNode }) { + const { fileIcon } = props; + return ( + ({ + position: 'relative', + borderRadius: 1, + width: 64, + height: 64, + overflow: 'hidden', + backgroundColor: 'grey.200', + ...theme.applyStyles('dark', { + backgroundColor: 'grey.800', + }), + })} + > + + {fileIcon} + + + ); +} + +export function ListCell(props: ListCellProps) { + const { onOpenActions, ...params } = props; + const apiRef = useGridApiContext(); + const density = useGridSelector(apiRef, gridDensitySelector); + const columnVisibilityModel = useGridSelector( + apiRef, + gridColumnVisibilityModelSelector, + ); + + const showCreatedBy = columnVisibilityModel.createdBy !== false; + const showSize = columnVisibilityModel.size !== false; + const showCreatedAt = columnVisibilityModel.createdAt !== false; + const showUpdatedAt = columnVisibilityModel.updatedAt !== false; + const showThumbnail = density === 'comfortable'; + + const icon = ( + + ); + + return ( + + {showThumbnail ? : icon} + + {params.row.name} + {density !== 'compact' && (showCreatedBy || showSize) && ( + + {showCreatedBy && {params.row.createdBy}} + {showSize && {formatSize(params.row.size)}} + + )} + {density === 'comfortable' && (showCreatedAt || showUpdatedAt) && ( + + {showUpdatedAt && `Updated ${formatDate(params.row.updatedAt)}`} + + )} + + + { + event.stopPropagation(); + onOpenActions(); + }} + sx={{ mr: -0.75 }} + > + + + + ); +} diff --git a/docs/data/data-grid/list-view/components/RenameDialog.js b/docs/data/data-grid/list-view/components/RenameDialog.js new file mode 100644 index 0000000000000..b7d1a49972c47 --- /dev/null +++ b/docs/data/data-grid/list-view/components/RenameDialog.js @@ -0,0 +1,58 @@ +import * as React from 'react'; + +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; + +export function RenameDialog(props) { + const { params, open, container, onSave, onClose } = props; + + const handleSave = (event) => { + event.preventDefault(); + + const formData = new FormData(event.currentTarget); + const value = formData.get('name'); + + onSave(params.row.id, value); + + onClose(); + }; + + return ( + + {params && ( + + Rename file + + + + + + + + + )} + + ); +} diff --git a/docs/data/data-grid/list-view/components/RenameDialog.tsx b/docs/data/data-grid/list-view/components/RenameDialog.tsx new file mode 100644 index 0000000000000..670e77c81ecd3 --- /dev/null +++ b/docs/data/data-grid/list-view/components/RenameDialog.tsx @@ -0,0 +1,67 @@ +import * as React from 'react'; +import { GridRowId, GridRowParams } from '@mui/x-data-grid-premium'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import { RowModel } from '../types'; + +export interface RenameDialogProps { + params: Pick, 'row'> | null; + open: boolean; + container?: () => HTMLElement; + onSave: (id: GridRowId, value: string) => void; + onClose: () => void; +} + +export function RenameDialog(props: RenameDialogProps) { + const { params, open, container, onSave, onClose } = props; + + const handleSave = (event: React.FormEvent) => { + event.preventDefault(); + + const formData = new FormData(event.currentTarget); + const value = formData.get('name') as string; + + onSave(params!.row.id, value); + + onClose(); + }; + + return ( + + {params && ( + + Rename file + + + + + + + + + )} + + ); +} diff --git a/docs/data/data-grid/list-view/components/Toolbar.js b/docs/data/data-grid/list-view/components/Toolbar.js new file mode 100644 index 0000000000000..412b5dc9f3c54 --- /dev/null +++ b/docs/data/data-grid/list-view/components/Toolbar.js @@ -0,0 +1,105 @@ +import * as React from 'react'; +import { + GridClearIcon, + GridDeleteIcon, + GridToolbarContainer, + GridToolbarQuickFilter, + selectedGridRowsSelector, + useGridApiContext, + useGridSelector, +} from '@mui/x-data-grid-premium'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import { outlinedInputClasses } from '@mui/material/OutlinedInput'; +import { iconButtonClasses } from '@mui/material/IconButton'; +import { ToolbarAddItem } from './ToolbarAddItem'; +import { ToolbarColumnsItem } from './ToolbarColumnsItem'; +import { ToolbarSortItem } from './ToolbarSortItem'; +import { ToolbarDensityItem } from './ToolbarDensityItem'; +import { ToolbarFilterItem } from './ToolbarFilterItem'; +import { ToolbarButton } from './ToolbarButton'; + +export function Toolbar(props) { + const { listView = false, container, handleUpload, handleDelete } = props; + const apiRef = useGridApiContext(); + const selectedRows = useGridSelector(apiRef, selectedGridRowsSelector); + const selectionCount = selectedRows.size; + const showSelectionOptions = selectionCount > 0; + + const handleClearSelection = () => { + apiRef.current.setRowSelectionModel([]); + }; + + const handleDeleteSelectedRows = () => { + handleClearSelection(); + handleDelete?.(Array.from(selectedRows.keys())); + }; + + const itemProps = { + listView, + container, + }; + + return ( + + {showSelectionOptions ? ( + + + + + + {selectionCount} selected + + + + + + ) : ( + + + + + + + + + + + + )} + + ); +} diff --git a/docs/data/data-grid/list-view/components/Toolbar.tsx b/docs/data/data-grid/list-view/components/Toolbar.tsx new file mode 100644 index 0000000000000..62980799d4b29 --- /dev/null +++ b/docs/data/data-grid/list-view/components/Toolbar.tsx @@ -0,0 +1,115 @@ +import * as React from 'react'; +import { + GridClearIcon, + GridDeleteIcon, + GridRowId, + GridToolbarContainer, + GridToolbarProps, + GridToolbarQuickFilter, + selectedGridRowsSelector, + useGridApiContext, + useGridSelector, +} from '@mui/x-data-grid-premium'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import { outlinedInputClasses } from '@mui/material/OutlinedInput'; +import { iconButtonClasses } from '@mui/material/IconButton'; +import { ToolbarAddItem } from './ToolbarAddItem'; +import { ToolbarColumnsItem } from './ToolbarColumnsItem'; +import { ToolbarSortItem } from './ToolbarSortItem'; +import { ToolbarDensityItem } from './ToolbarDensityItem'; +import { ToolbarFilterItem } from './ToolbarFilterItem'; +import { ToolbarButton } from './ToolbarButton'; +import { DrawerProps } from './Drawer'; + +export interface ToolbarProps extends GridToolbarProps { + container?: DrawerProps['container']; + listView?: boolean; + handleDelete?: (ids: GridRowId[]) => void; + handleUpload?: (event: React.ChangeEvent) => void; +} + +export function Toolbar(props: ToolbarProps) { + const { listView = false, container, handleUpload, handleDelete } = props; + const apiRef = useGridApiContext(); + const selectedRows = useGridSelector(apiRef, selectedGridRowsSelector); + const selectionCount = selectedRows.size; + const showSelectionOptions = selectionCount > 0; + + const handleClearSelection = () => { + apiRef.current.setRowSelectionModel([]); + }; + + const handleDeleteSelectedRows = () => { + handleClearSelection(); + handleDelete?.(Array.from(selectedRows.keys())); + }; + + const itemProps = { + listView, + container, + }; + + return ( + + {showSelectionOptions ? ( + + + + + + {selectionCount} selected + + + + + + ) : ( + + + + + + + + + + + + )} + + ); +} diff --git a/docs/data/data-grid/list-view/components/ToolbarAddItem.js b/docs/data/data-grid/list-view/components/ToolbarAddItem.js new file mode 100644 index 0000000000000..48053f1bb08fc --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarAddItem.js @@ -0,0 +1,75 @@ +import * as React from 'react'; +import AddIcon from '@mui/icons-material/Add'; +import CloudUploadIcon from '@mui/icons-material/CloudUpload'; +import NewFolderIcon from '@mui/icons-material/CreateNewFolder'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import Typography from '@mui/material/Typography'; + +import { ToolbarButton } from './ToolbarButton'; +import { Drawer, DrawerHeader } from './Drawer'; + +const ListItemUploadButton = React.forwardRef( + function ListItemUploadButton(props, ref) { + const { children, ...other } = props; + return ( + + {children} + + ); + }, +); + +export function ToolbarAddItem(props) { + const { container } = props; + const [open, setOpen] = React.useState(false); + const { handleUpload, listView } = props; + + const handleFileSelect = (event) => { + handleUpload?.(event); + setOpen(false); + }; + + return ( + + setOpen(true)}> + + + + setOpen(false)} + > + + Add new + + + + + + + + + Upload file + + + + + + {}} disabled> + + + + New folder + + + + + + ); +} diff --git a/docs/data/data-grid/list-view/components/ToolbarAddItem.tsx b/docs/data/data-grid/list-view/components/ToolbarAddItem.tsx new file mode 100644 index 0000000000000..a3c43ffda857d --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarAddItem.tsx @@ -0,0 +1,81 @@ +import * as React from 'react'; +import AddIcon from '@mui/icons-material/Add'; +import CloudUploadIcon from '@mui/icons-material/CloudUpload'; +import NewFolderIcon from '@mui/icons-material/CreateNewFolder'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import Typography from '@mui/material/Typography'; +import { ButtonProps } from '@mui/material/Button'; +import { ToolbarButton } from './ToolbarButton'; +import { Drawer, DrawerHeader, DrawerProps } from './Drawer'; + +const ListItemUploadButton = React.forwardRef( + function ListItemUploadButton(props, ref) { + const { children, ...other } = props; + return ( + + {children} + + ); + }, +); + +export interface ToolbarAddItemProps { + container: DrawerProps['container']; + listView: boolean; + handleUpload?: (event: React.ChangeEvent) => void; +} + +export function ToolbarAddItem(props: ToolbarAddItemProps) { + const { container } = props; + const [open, setOpen] = React.useState(false); + const { handleUpload, listView } = props; + + const handleFileSelect = (event: React.ChangeEvent) => { + handleUpload?.(event); + setOpen(false); + }; + + return ( + + setOpen(true)}> + + + + setOpen(false)} + > + + Add new + + + + + + + + + Upload file + + + + + + {}} disabled> + + + + New folder + + + + + + ); +} diff --git a/docs/data/data-grid/list-view/components/ToolbarButton.js b/docs/data/data-grid/list-view/components/ToolbarButton.js new file mode 100644 index 0000000000000..7ed7b6f21a51a --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarButton.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import IconButton from '@mui/material/IconButton'; + +export function ToolbarButton(props) { + const { className, children, ...other } = props; + return {children}; +} diff --git a/docs/data/data-grid/list-view/components/ToolbarButton.tsx b/docs/data/data-grid/list-view/components/ToolbarButton.tsx new file mode 100644 index 0000000000000..48b5dcf1ed302 --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarButton.tsx @@ -0,0 +1,8 @@ +import * as React from 'react'; +import IconButton from '@mui/material/IconButton'; +import { ButtonProps } from '@mui/material/Button'; + +export function ToolbarButton(props: ButtonProps) { + const { className, children, ...other } = props; + return {children}; +} diff --git a/docs/data/data-grid/list-view/components/ToolbarColumnsItem.js b/docs/data/data-grid/list-view/components/ToolbarColumnsItem.js new file mode 100644 index 0000000000000..e6fe57471fd59 --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarColumnsItem.js @@ -0,0 +1,78 @@ +import * as React from 'react'; +import { + GridColumnIcon, + useGridApiContext, + useGridSelector, + gridColumnDefinitionsSelector, + gridColumnVisibilityModelSelector, +} from '@mui/x-data-grid-premium'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import CheckBoxBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import Typography from '@mui/material/Typography'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import { ToolbarButton } from './ToolbarButton'; +import { Drawer, DrawerHeader } from './Drawer'; + +export function ToolbarColumnsItem(props) { + const { listView, container } = props; + const [open, setOpen] = React.useState(false); + const apiRef = useGridApiContext(); + const columns = useGridSelector(apiRef, gridColumnDefinitionsSelector); + const columnVisibilityModel = useGridSelector( + apiRef, + gridColumnVisibilityModelSelector, + ); + + const toggleFieldVisibility = (field) => { + apiRef.current.setColumnVisibility( + field, + columnVisibilityModel[field] === false, + ); + }; + + return ( + + setOpen(true)}> + + + + setOpen(false)} + > + + Fields + + + + {columns.map((column) => { + const isVisible = columnVisibilityModel[column.field] !== false; + return ( + + toggleFieldVisibility(column.field)} + disabled={column.hideable === false} + > + + {isVisible ? ( + + ) : ( + + )} + + {column.headerName || column.field} + + + ); + })} + + + + ); +} diff --git a/docs/data/data-grid/list-view/components/ToolbarColumnsItem.tsx b/docs/data/data-grid/list-view/components/ToolbarColumnsItem.tsx new file mode 100644 index 0000000000000..f4f55499d06ed --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarColumnsItem.tsx @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { + GridColumnIcon, + useGridApiContext, + useGridSelector, + gridColumnDefinitionsSelector, + gridColumnVisibilityModelSelector, +} from '@mui/x-data-grid-premium'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import CheckBoxBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import Typography from '@mui/material/Typography'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import { ToolbarButton } from './ToolbarButton'; +import { Drawer, DrawerHeader, DrawerProps } from './Drawer'; + +interface ToolbarColumnsItemProps { + listView: boolean; + container: DrawerProps['container']; +} + +export function ToolbarColumnsItem(props: ToolbarColumnsItemProps) { + const { listView, container } = props; + const [open, setOpen] = React.useState(false); + const apiRef = useGridApiContext(); + const columns = useGridSelector(apiRef, gridColumnDefinitionsSelector); + const columnVisibilityModel = useGridSelector( + apiRef, + gridColumnVisibilityModelSelector, + ); + + const toggleFieldVisibility = (field: string) => { + apiRef.current.setColumnVisibility( + field, + columnVisibilityModel[field] === false, + ); + }; + + return ( + + setOpen(true)}> + + + + setOpen(false)} + > + + Fields + + + + {columns.map((column) => { + const isVisible = columnVisibilityModel[column.field] !== false; + return ( + + toggleFieldVisibility(column.field)} + disabled={column.hideable === false} + > + + {isVisible ? ( + + ) : ( + + )} + + {column.headerName || column.field} + + + ); + })} + + + + ); +} diff --git a/docs/data/data-grid/list-view/components/ToolbarDensityItem.js b/docs/data/data-grid/list-view/components/ToolbarDensityItem.js new file mode 100644 index 0000000000000..afb8447721ee0 --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarDensityItem.js @@ -0,0 +1,87 @@ +import * as React from 'react'; +import { + GridViewStreamIcon, + useGridApiContext, + useGridSelector, + gridDensitySelector, + GridCheckIcon, + GridViewHeadlineIcon, + GridTableRowsIcon, +} from '@mui/x-data-grid-premium'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import Typography from '@mui/material/Typography'; +import { Drawer, DrawerHeader } from './Drawer'; +import { ToolbarButton } from './ToolbarButton'; + +const DENSITY_ICONS = { + compact: GridViewHeadlineIcon, + standard: GridTableRowsIcon, + comfortable: GridViewStreamIcon, +}; + +const DENSITY_OPTIONS = [ + { + label: 'Compact', + value: 'compact', + }, + { + label: 'Standard', + value: 'standard', + }, + { + label: 'Comfortable', + value: 'comfortable', + }, +]; + +export function ToolbarDensityItem(props) { + const { listView, container } = props; + const [open, setOpen] = React.useState(false); + const apiRef = useGridApiContext(); + const density = useGridSelector(apiRef, gridDensitySelector); + + const handleDensityChange = (value) => { + apiRef.current.setDensity(value); + setOpen(false); + }; + + const Icon = DENSITY_ICONS[density]; + + return ( + + setOpen(true)}> + + + + setOpen(false)} + > + + Density + + + + {DENSITY_OPTIONS.map((option) => { + const isActive = density === option.value; + + return ( + + handleDensityChange(option.value)}> + {isActive ? : null} + {option.label} + + + ); + })} + + + + ); +} diff --git a/docs/data/data-grid/list-view/components/ToolbarDensityItem.tsx b/docs/data/data-grid/list-view/components/ToolbarDensityItem.tsx new file mode 100644 index 0000000000000..8f9ce7a1846ed --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarDensityItem.tsx @@ -0,0 +1,94 @@ +import * as React from 'react'; +import { + GridViewStreamIcon, + useGridApiContext, + useGridSelector, + GridDensityOption, + gridDensitySelector, + GridCheckIcon, + GridDensity, + GridViewHeadlineIcon, + GridTableRowsIcon, +} from '@mui/x-data-grid-premium'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import Typography from '@mui/material/Typography'; +import { Drawer, DrawerHeader, DrawerProps } from './Drawer'; +import { ToolbarButton } from './ToolbarButton'; + +const DENSITY_ICONS = { + compact: GridViewHeadlineIcon, + standard: GridTableRowsIcon, + comfortable: GridViewStreamIcon, +}; + +const DENSITY_OPTIONS: Omit[] = [ + { + label: 'Compact', + value: 'compact', + }, + { + label: 'Standard', + value: 'standard', + }, + { + label: 'Comfortable', + value: 'comfortable', + }, +]; + +interface ToolbarDensityItemProps { + listView: boolean; + container: DrawerProps['container']; +} + +export function ToolbarDensityItem(props: ToolbarDensityItemProps) { + const { listView, container } = props; + const [open, setOpen] = React.useState(false); + const apiRef = useGridApiContext(); + const density = useGridSelector(apiRef, gridDensitySelector); + + const handleDensityChange = (value: GridDensity) => { + apiRef.current.setDensity(value); + setOpen(false); + }; + + const Icon = DENSITY_ICONS[density]; + + return ( + + setOpen(true)}> + + + + setOpen(false)} + > + + Density + + + + {DENSITY_OPTIONS.map((option) => { + const isActive = density === option.value; + + return ( + + handleDensityChange(option.value)}> + {isActive ? : null} + {option.label} + + + ); + })} + + + + ); +} diff --git a/docs/data/data-grid/list-view/components/ToolbarFilterItem.js b/docs/data/data-grid/list-view/components/ToolbarFilterItem.js new file mode 100644 index 0000000000000..06cdb3c4690ef --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarFilterItem.js @@ -0,0 +1,217 @@ +import * as React from 'react'; +import { + GridFilterListIcon, + useGridApiContext, + useGridSelector, + gridFilterActiveItemsSelector, + GridCheckIcon, +} from '@mui/x-data-grid-premium'; +import Badge from '@mui/material/Badge'; +import Typography from '@mui/material/Typography'; +import Divider from '@mui/material/Divider'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import ListSubheader from '@mui/material/ListSubheader'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import CheckBoxBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import FilterAltOffIcon from '@mui/icons-material/FilterAltOff'; +import { Drawer, DrawerHeader } from './Drawer'; +import { ToolbarButton } from './ToolbarButton'; + +import { FILE_TYPES } from '../constants'; + +const DATE_MODIFIED_FILTERS = [ + { + label: 'All', + id: 'all', + }, + { + label: 'Today', + id: 'today', + operator: 'onOrAfter', + value: new Date(new Date().setHours(0, 0, 0, 0)).toISOString(), + }, + { + label: 'Last week', + id: 'last-week', + operator: 'onOrAfter', + value: new Date( + new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).setHours(0, 0, 0, 0), + ).toISOString(), + }, + { + label: 'Last month', + id: 'last-month', + operator: 'onOrAfter', + value: new Date( + new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).setHours(0, 0, 0, 0), + ).toISOString(), + }, + { + label: 'Last 3 months', + id: 'last-3-months', + operator: 'onOrAfter', + value: new Date( + new Date(Date.now() - 3 * 30 * 24 * 60 * 60 * 1000).setHours(0, 0, 0, 0), + ).toISOString(), + }, +]; + +export function ToolbarFilterItem(props) { + const { listView, container } = props; + const [open, setOpen] = React.useState(false); + const apiRef = useGridApiContext(); + const activeFilters = useGridSelector(apiRef, gridFilterActiveItemsSelector); + const currentFileTypeFilter = + activeFilters.filter((filter) => filter.field === 'type')?.[0]?.value ?? []; + const currentDateModifiedFilter = activeFilters.find( + (filter) => filter.field === 'updatedAt', + ); + + const applyDateModifiedFilter = (filterItem) => { + if (currentDateModifiedFilter) { + apiRef.current.deleteFilterItem(currentDateModifiedFilter); + } + + apiRef.current.upsertFilterItem({ + field: 'updatedAt', + ...filterItem, + }); + }; + + const resetDateModifiedFilter = () => { + if (currentDateModifiedFilter) { + apiRef.current.deleteFilterItem(currentDateModifiedFilter); + } + }; + + const applyFileTypeFilter = (fileType, enable) => { + apiRef.current.upsertFilterItem({ + id: 'file-type', + field: 'type', + operator: 'isAnyOf', + value: enable + ? [...currentFileTypeFilter, fileType] + : currentFileTypeFilter.filter((type) => type !== fileType), + }); + }; + + const clearFilters = () => { + apiRef.current.setFilterModel({ + items: [], + }); + }; + + return ( + + setOpen(true)}> + + + + + + setOpen(false)} + > + + Filters + + + + Date modified + + } + > + {DATE_MODIFIED_FILTERS.map((option) => { + const isActive = + option.id === 'all' + ? !currentDateModifiedFilter + : activeFilters.some((filter) => filter.id === option.id); + + return ( + + + option.id === 'all' + ? resetDateModifiedFilter() + : applyDateModifiedFilter({ + id: option.id, + operator: option.operator, + value: option.value, + }) + } + > + {isActive ? : null} + {option.label} + + + ); + })} + + + + + File types + + } + > + {FILE_TYPES.map((type) => { + const isActive = currentFileTypeFilter.includes(type); + + return ( + + applyFileTypeFilter(type, !isActive)}> + + {isActive ? ( + + ) : ( + + )} + + {type} + + + ); + })} + + + + + + + + + + Clear filters + + + + + + ); +} diff --git a/docs/data/data-grid/list-view/components/ToolbarFilterItem.tsx b/docs/data/data-grid/list-view/components/ToolbarFilterItem.tsx new file mode 100644 index 0000000000000..2bb3040df9cd8 --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarFilterItem.tsx @@ -0,0 +1,225 @@ +import * as React from 'react'; +import { + GridFilterListIcon, + useGridApiContext, + useGridSelector, + gridFilterActiveItemsSelector, + GridCheckIcon, + GridFilterItem, +} from '@mui/x-data-grid-premium'; +import Badge from '@mui/material/Badge'; +import Typography from '@mui/material/Typography'; +import Divider from '@mui/material/Divider'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import ListSubheader from '@mui/material/ListSubheader'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import CheckBoxBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import FilterAltOffIcon from '@mui/icons-material/FilterAltOff'; +import { Drawer, DrawerHeader, DrawerProps } from './Drawer'; +import { ToolbarButton } from './ToolbarButton'; +import { FileType } from '../types'; +import { FILE_TYPES } from '../constants'; + +const DATE_MODIFIED_FILTERS = [ + { + label: 'All', + id: 'all', + }, + { + label: 'Today', + id: 'today', + operator: 'onOrAfter', + value: new Date(new Date().setHours(0, 0, 0, 0)).toISOString(), + }, + { + label: 'Last week', + id: 'last-week', + operator: 'onOrAfter', + value: new Date( + new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).setHours(0, 0, 0, 0), + ).toISOString(), + }, + { + label: 'Last month', + id: 'last-month', + operator: 'onOrAfter', + value: new Date( + new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).setHours(0, 0, 0, 0), + ).toISOString(), + }, + { + label: 'Last 3 months', + id: 'last-3-months', + operator: 'onOrAfter', + value: new Date( + new Date(Date.now() - 3 * 30 * 24 * 60 * 60 * 1000).setHours(0, 0, 0, 0), + ).toISOString(), + }, +]; + +interface ToolbarFilterItemProps { + listView: boolean; + container: DrawerProps['container']; +} + +export function ToolbarFilterItem(props: ToolbarFilterItemProps) { + const { listView, container } = props; + const [open, setOpen] = React.useState(false); + const apiRef = useGridApiContext(); + const activeFilters = useGridSelector(apiRef, gridFilterActiveItemsSelector); + const currentFileTypeFilter = + activeFilters.filter((filter) => filter.field === 'type')?.[0]?.value ?? []; + const currentDateModifiedFilter = activeFilters.find( + (filter) => filter.field === 'updatedAt', + ); + + const applyDateModifiedFilter = (filterItem: Omit) => { + if (currentDateModifiedFilter) { + apiRef.current.deleteFilterItem(currentDateModifiedFilter); + } + + apiRef.current.upsertFilterItem({ + field: 'updatedAt', + ...filterItem, + }); + }; + + const resetDateModifiedFilter = () => { + if (currentDateModifiedFilter) { + apiRef.current.deleteFilterItem(currentDateModifiedFilter); + } + }; + + const applyFileTypeFilter = (fileType: FileType, enable: boolean) => { + apiRef.current.upsertFilterItem({ + id: 'file-type', + field: 'type', + operator: 'isAnyOf', + value: enable + ? [...currentFileTypeFilter, fileType] + : currentFileTypeFilter.filter((type: string) => type !== fileType), + }); + }; + + const clearFilters = () => { + apiRef.current.setFilterModel({ + items: [], + }); + }; + + return ( + + setOpen(true)}> + + + + + + setOpen(false)} + > + + Filters + + + + Date modified + + } + > + {DATE_MODIFIED_FILTERS.map((option) => { + const isActive = + option.id === 'all' + ? !currentDateModifiedFilter + : activeFilters.some((filter) => filter.id === option.id); + + return ( + + + option.id === 'all' + ? resetDateModifiedFilter() + : applyDateModifiedFilter({ + id: option.id, + operator: option.operator!, + value: option.value, + }) + } + > + {isActive ? : null} + {option.label} + + + ); + })} + + + + + + File types + + } + > + {FILE_TYPES.map((type) => { + const isActive = currentFileTypeFilter.includes(type); + + return ( + + applyFileTypeFilter(type, !isActive)}> + + {isActive ? ( + + ) : ( + + )} + + {type} + + + ); + })} + + + + + + + + + + + Clear filters + + + + + + ); +} diff --git a/docs/data/data-grid/list-view/components/ToolbarSortItem.js b/docs/data/data-grid/list-view/components/ToolbarSortItem.js new file mode 100644 index 0000000000000..91c07622847f2 --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarSortItem.js @@ -0,0 +1,93 @@ +import * as React from 'react'; +import { + useGridApiContext, + GridArrowUpwardIcon, + GridArrowDownwardIcon, + useGridSelector, + gridSortModelSelector, + gridColumnDefinitionsSelector, +} from '@mui/x-data-grid-premium'; +import SwapVertIcon from '@mui/icons-material/SwapVert'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import Typography from '@mui/material/Typography'; +import Badge from '@mui/material/Badge'; +import { Drawer, DrawerHeader } from './Drawer'; +import { ToolbarButton } from './ToolbarButton'; + +export function ToolbarSortItem(props) { + const { listView, container } = props; + const [open, setOpen] = React.useState(false); + const apiRef = useGridApiContext(); + const fields = useGridSelector(apiRef, gridColumnDefinitionsSelector); + const sortModel = useGridSelector(apiRef, gridSortModelSelector); + const sortableFields = fields.filter((field) => field.sortable); + const sortCount = sortModel.length; + + const handleSortChange = (field, sort) => { + apiRef.current.sortColumn(field, sort, true); + }; + + return ( + + setOpen(true)}> + + + + + + setOpen(false)} + > + + Sort by + + + + {sortableFields.map((field) => { + const fieldIndexInSortModel = sortModel.findIndex( + (sort) => sort.field === field.field, + ); + const fieldInSortModel = sortModel[fieldIndexInSortModel]; + let nextSort = 'asc'; + + if (fieldInSortModel) { + nextSort = fieldInSortModel.sort === 'asc' ? 'desc' : null; + } + + return ( + + handleSortChange(field.field, nextSort)} + > + + {fieldInSortModel && ( + 1 ? fieldIndexInSortModel + 1 : null + } + > + {fieldInSortModel.sort === 'asc' ? ( + + ) : ( + + )} + + )} + + {field.headerName} + + + ); + })} + + + + ); +} diff --git a/docs/data/data-grid/list-view/components/ToolbarSortItem.tsx b/docs/data/data-grid/list-view/components/ToolbarSortItem.tsx new file mode 100644 index 0000000000000..4c3633f6ec3d6 --- /dev/null +++ b/docs/data/data-grid/list-view/components/ToolbarSortItem.tsx @@ -0,0 +1,99 @@ +import * as React from 'react'; +import { + useGridApiContext, + GridArrowUpwardIcon, + GridArrowDownwardIcon, + useGridSelector, + gridSortModelSelector, + gridColumnDefinitionsSelector, + GridSortDirection, +} from '@mui/x-data-grid-premium'; +import SwapVertIcon from '@mui/icons-material/SwapVert'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import Typography from '@mui/material/Typography'; +import Badge from '@mui/material/Badge'; +import { Drawer, DrawerHeader, DrawerProps } from './Drawer'; +import { ToolbarButton } from './ToolbarButton'; + +interface ToolbarSortItemProps { + listView: boolean; + container: DrawerProps['container']; +} + +export function ToolbarSortItem(props: ToolbarSortItemProps) { + const { listView, container } = props; + const [open, setOpen] = React.useState(false); + const apiRef = useGridApiContext(); + const fields = useGridSelector(apiRef, gridColumnDefinitionsSelector); + const sortModel = useGridSelector(apiRef, gridSortModelSelector); + const sortableFields = fields.filter((field) => field.sortable); + const sortCount = sortModel.length; + + const handleSortChange = (field: string, sort: GridSortDirection) => { + apiRef.current.sortColumn(field, sort, true); + }; + + return ( + + setOpen(true)}> + + + + + + setOpen(false)} + > + + Sort by + + + + {sortableFields.map((field) => { + const fieldIndexInSortModel = sortModel.findIndex( + (sort) => sort.field === field.field, + ); + const fieldInSortModel = sortModel[fieldIndexInSortModel]; + let nextSort: GridSortDirection = 'asc'; + + if (fieldInSortModel) { + nextSort = fieldInSortModel.sort === 'asc' ? 'desc' : null; + } + + return ( + + handleSortChange(field.field, nextSort)} + > + + {fieldInSortModel && ( + 1 ? fieldIndexInSortModel + 1 : null + } + > + {fieldInSortModel.sort === 'asc' ? ( + + ) : ( + + )} + + )} + + {field.headerName} + + + ); + })} + + + + ); +} diff --git a/docs/data/data-grid/list-view/constants.ts b/docs/data/data-grid/list-view/constants.ts new file mode 100644 index 0000000000000..ee67fc4fb92c0 --- /dev/null +++ b/docs/data/data-grid/list-view/constants.ts @@ -0,0 +1,17 @@ +import { FileType } from './types'; + +export const FILE_TYPES: FileType[] = [ + 'pdf', + 'docx', + 'txt', + 'mp4', + 'mov', + 'webm', + 'jpg', + 'jpeg', + 'png', + 'gif', + 'tiff', + 'webp', + 'zip', +]; diff --git a/docs/data/data-grid/list-view/data.ts b/docs/data/data-grid/list-view/data.ts new file mode 100644 index 0000000000000..3e54d301d2c7f --- /dev/null +++ b/docs/data/data-grid/list-view/data.ts @@ -0,0 +1,204 @@ +import { randomId } from '@mui/x-data-grid-generator'; +import { GridRowModel } from '@mui/x-data-grid-premium'; +import { RowModel } from './types'; + +export const INITIAL_ROWS: GridRowModel[] = [ + { + id: randomId(), + type: 'zip', + name: 'archive.zip', + description: 'Compressed archive of project files', + size: 128_313_213, + createdBy: 'Kenan Yusuf', + createdAt: new Date('2023-09-15T08:30:00').toISOString(), + updatedAt: new Date('2023-09-15T08:30:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'docx', + name: 'invoice-322.docx', + description: 'Invoice document for client 322', + size: 1_694_986, + createdBy: 'José Freitas', + createdAt: new Date('2024-01-18T11:30:00').toISOString(), + updatedAt: new Date().toISOString(), // Today + state: 'uploaded', + }, + { + id: randomId(), + type: 'png', + name: 'screenshot_2024-02-14_12-34-56.png', + description: 'Screenshot of application interface', + size: 522_078, + createdBy: 'José Freitas', + createdAt: new Date('2024-02-14T12:35:16').toISOString(), + updatedAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), // Last week + state: 'uploaded', + }, + { + id: randomId(), + type: 'mp4', + name: 'strategy-meeting.mp4', + description: 'Recording of the strategy planning meeting', + size: 2_442_044, + createdBy: 'José Freitas', + createdAt: new Date('2023-12-05T15:40:30').toISOString(), + updatedAt: new Date(Date.now() - 20 * 24 * 60 * 60 * 1000).toISOString(), // Last month + state: 'uploaded', + }, + { + id: randomId(), + type: 'docx', + name: 'project-proposal.docx', + description: 'Detailed project proposal document', + size: 3_567_890, + createdBy: 'Olivier Tassinari', + createdAt: new Date('2024-03-01T09:15:00').toISOString(), + updatedAt: new Date('2024-03-02T14:30:45').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'png', + name: 'logo-design-final.png', + description: 'Final version of the company logo design', + size: 1_234_567, + createdBy: 'Kenan Yusuf', + createdAt: new Date('2024-02-28T16:20:00').toISOString(), + updatedAt: new Date('2024-02-28T16:20:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'mp4', + name: 'product-demo.mp4', + description: 'Video demonstration of the new product features', + size: 15_789_012, + createdBy: 'Olivier Tassinari', + createdAt: new Date('2024-02-25T11:45:00').toISOString(), + updatedAt: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString(), // Last 3 months + state: 'uploaded', + }, + { + id: randomId(), + type: 'zip', + name: 'project-assets.zip', + description: 'Compressed folder containing all project assets', + size: 87_654_321, + createdBy: 'José Freitas', + createdAt: new Date('2024-03-03T13:00:00').toISOString(), + updatedAt: new Date('2024-03-03T13:00:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'docx', + name: 'meeting-minutes-2024-03-10.docx', + description: 'Minutes from the team meeting on March 10, 2024', + size: 567_890, + createdBy: 'Kenan Yusuf', + createdAt: new Date('2024-03-10T14:00:00').toISOString(), + updatedAt: new Date('2024-03-10T16:30:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'png', + name: 'ui-mockup-v2.png', + description: 'Updated user interface mockup', + size: 3_456_789, + createdBy: 'Olivier Tassinari', + createdAt: new Date('2024-03-05T10:20:00').toISOString(), + updatedAt: new Date('2024-03-05T10:20:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'mp4', + name: 'user-feedback-session.mp4', + description: 'Recording of user feedback session', + size: 234_567_890, + createdBy: 'José Freitas', + createdAt: new Date('2024-03-08T13:45:00').toISOString(), + updatedAt: new Date('2024-03-08T15:30:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'zip', + name: 'legacy-codebase.zip', + description: 'Archive of the legacy project codebase', + size: 567_890_123, + createdBy: 'Kenan Yusuf', + createdAt: new Date('2024-02-20T09:00:00').toISOString(), + updatedAt: new Date('2024-02-20T09:00:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'docx', + name: 'q1-2024-report.docx', + description: 'Quarterly report for Q1 2024', + size: 4_567_890, + createdBy: 'Olivier Tassinari', + createdAt: new Date('2024-03-31T23:59:59').toISOString(), + updatedAt: new Date('2024-04-01T10:15:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'png', + name: 'data-visualization.png', + description: 'Chart showing project progress', + size: 789_012, + createdBy: 'José Freitas', + createdAt: new Date('2024-03-15T11:30:00').toISOString(), + updatedAt: new Date('2024-03-15T11:30:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'mp4', + name: 'code-review-session.mp4', + description: 'Recording of code review meeting', + size: 345_678_901, + createdBy: 'Kenan Yusuf', + createdAt: new Date('2024-03-20T14:00:00').toISOString(), + updatedAt: new Date('2024-03-20T16:45:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'zip', + name: 'design-assets-v3.zip', + description: 'Compressed folder of updated design assets', + size: 98_765_432, + createdBy: 'Olivier Tassinari', + createdAt: new Date('2024-03-25T09:30:00').toISOString(), + updatedAt: new Date('2024-03-25T09:30:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'docx', + name: 'api-documentation.docx', + description: 'Comprehensive API documentation', + size: 2_345_678, + createdBy: 'José Freitas', + createdAt: new Date('2024-03-28T16:20:00').toISOString(), + updatedAt: new Date('2024-03-29T11:45:00').toISOString(), + state: 'uploaded', + }, + { + id: randomId(), + type: 'png', + name: 'error-screenshot.png', + description: 'Screenshot of error message for debugging', + size: 345_678, + createdBy: 'Kenan Yusuf', + createdAt: new Date('2024-03-30T08:15:00').toISOString(), + updatedAt: new Date('2024-03-30T08:15:00').toISOString(), + state: 'uploaded', + }, +]; diff --git a/docs/data/data-grid/list-view/list-view.md b/docs/data/data-grid/list-view/list-view.md new file mode 100644 index 0000000000000..11f4b7ca3e88c --- /dev/null +++ b/docs/data/data-grid/list-view/list-view.md @@ -0,0 +1,29 @@ +--- +title: Data Grid - List view +--- + +# Data Grid - List view [](/x/introduction/licensing/#pro-plan 'Pro plan') + +

Display data in a single-column list view. Can be used to present a more compact grid on smaller screens and mobile devices.

+ +List view can be enabled by providing the `unstable_listView` prop. + +Unlike the default grid view, the list view makes no assumptions on how data is presented to end users. + +In order to display data in a list view, a `unstable_listColumn` prop must be provided with a `renderCell` function. + +:::warning +This feature is under development and is marked as **unstable**. While you can use the list view feature in production, the API could change in the future. +::: + +{{"demo": "ListView.js", "bg": "inline"}} + +## Advanced usage + +The list view feature can be combined with [custom subcomponents](/x/react-data-grid/components/) to provide an improved user experience on small screens. + +{{"demo": "ListViewAdvanced.js", "bg": "inline", "iframe": true, "maxWidth": 360, "height": 600, "hideToolbar": true}} + +:::info +See the code for this demo in [CodeSandbox](https://codesandbox.io/p/sandbox/x-react-data-grid-list-view-zmkzhz). +::: diff --git a/docs/data/data-grid/list-view/types.ts b/docs/data/data-grid/list-view/types.ts new file mode 100644 index 0000000000000..fd0975f3a0b95 --- /dev/null +++ b/docs/data/data-grid/list-view/types.ts @@ -0,0 +1,30 @@ +import { GridRowId } from '@mui/x-data-grid-premium'; + +export type FileType = + | 'pdf' + | 'docx' + | 'txt' + | 'mp4' + | 'mov' + | 'webm' + | 'jpg' + | 'jpeg' + | 'png' + | 'gif' + | 'tiff' + | 'webp' + | 'zip'; + +export type FileState = 'uploaded' | 'pending'; + +export type RowModel = { + id: GridRowId; + type: FileType; + name: string; + description: string; + size: number; + createdBy: string; + createdAt: string; + updatedAt: string; + state: FileState; +}; diff --git a/docs/data/data-grid/list-view/utils.ts b/docs/data/data-grid/list-view/utils.ts new file mode 100644 index 0000000000000..08c93be0d27c4 --- /dev/null +++ b/docs/data/data-grid/list-view/utils.ts @@ -0,0 +1,56 @@ +export function formatDate(value: string | null) { + if (!value) { + return '—'; + } + const date = new Date(value); + const formatter = new Intl.DateTimeFormat('en-US', { + month: 'short', + day: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + return formatter.format(date); +} + +export function formatSize(size: number) { + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let unitIndex = 0; + let formattedSize = size; + + while (formattedSize >= 1024 && unitIndex < units.length - 1) { + formattedSize /= 1024; + unitIndex += 1; + } + + return `${formattedSize.toFixed(2)} ${units[unitIndex]}`; +} + +export function stringToColor(string: string) { + let hash = 0; + let i: number; + + /* eslint-disable no-bitwise */ + for (i = 0; i < string.length; i += 1) { + hash = string.charCodeAt(i) + ((hash << 5) - hash); + } + + let color = '#'; + + for (i = 0; i < 3; i += 1) { + const value = (hash >> (i * 8)) & 0xff; + color += `00${value.toString(16)}`.slice(-2); + } + /* eslint-enable no-bitwise */ + + return color; +} + +export function stringAvatar(name: string) { + return { + sx: { + bgcolor: stringToColor(name), + }, + children: `${name.split(' ')[0][0]}${name.split(' ')[1][0]}`, + }; +} diff --git a/docs/data/data-grid/localization/data.json b/docs/data/data-grid/localization/data.json index 15945bbcef968..fc9f1657230c3 100644 --- a/docs/data/data-grid/localization/data.json +++ b/docs/data/data-grid/localization/data.json @@ -67,7 +67,7 @@ "languageTag": "da-DK", "importName": "daDK", "localeName": "Danish", - "missingKeysCount": 5, + "missingKeysCount": 0, "totalKeysCount": 122, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/daDK.ts" }, @@ -139,7 +139,7 @@ "languageTag": "it-IT", "importName": "itIT", "localeName": "Italian", - "missingKeysCount": 8, + "missingKeysCount": 0, "totalKeysCount": 122, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/itIT.ts" }, @@ -187,7 +187,7 @@ "languageTag": "pl-PL", "importName": "plPL", "localeName": "Polish", - "missingKeysCount": 35, + "missingKeysCount": 11, "totalKeysCount": 122, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/plPL.ts" }, @@ -203,7 +203,7 @@ "languageTag": "pt-BR", "importName": "ptBR", "localeName": "Portuguese (Brazil)", - "missingKeysCount": 4, + "missingKeysCount": 0, "totalKeysCount": 122, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/ptBR.ts" }, diff --git a/docs/data/data-grid/localization/localization.md b/docs/data/data-grid/localization/localization.md index 3fa3c18931d07..c2baa4fc81890 100644 --- a/docs/data/data-grid/localization/localization.md +++ b/docs/data/data-grid/localization/localization.md @@ -59,7 +59,7 @@ const theme = createTheme( ; ``` -Note that `createTheme` accepts any number of arguments. +Note that `createTheme()` accepts any number of arguments. If you are already using the [translations of the core components](/material-ui/guides/localization/#locale-text), you can add `bgBG` as a new argument. The same import works for Data Grid Pro as it's an extension of Data Grid. @@ -86,7 +86,7 @@ const theme = createTheme( ; ``` -If you want to pass language translations directly to the Data Grid without using `createTheme` and `ThemeProvider`, you can directly load the language translations from `@mui/x-data-grid/locales`. +If you want to pass language translations directly to the Data Grid without using `createTheme()` and `ThemeProvider`, you can directly load the language translations from `@mui/x-data-grid/locales`. ```jsx import { DataGrid } from '@mui/x-data-grid'; diff --git a/docs/data/data-grid/master-detail/master-detail.md b/docs/data/data-grid/master-detail/master-detail.md index cb8614ff1114f..d2366d51cdc0f 100644 --- a/docs/data/data-grid/master-detail/master-detail.md +++ b/docs/data/data-grid/master-detail/master-detail.md @@ -189,6 +189,7 @@ More examples of how to customize the detail panel: - [One expanded detail panel at a time](/x/react-data-grid/row-recipes/#one-expanded-detail-panel-at-a-time) - [Expand or collapse all detail panels](/x/react-data-grid/row-recipes/#expand-or-collapse-all-detail-panels) +- [Toggling detail panels on row click](/x/react-data-grid/row-recipes/#toggling-detail-panels-on-row-click) ## apiRef diff --git a/docs/data/data-grid/overview/DataGridPremiumDemo.js b/docs/data/data-grid/overview/DataGridPremiumDemo.js index 98a6959cff621..3e99541b76cbf 100644 --- a/docs/data/data-grid/overview/DataGridPremiumDemo.js +++ b/docs/data/data-grid/overview/DataGridPremiumDemo.js @@ -2,6 +2,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, GridToolbar, useGridApiRef, useKeepGroupedColumnsHidden, @@ -40,7 +41,7 @@ export default function DataGridPremiumDemo() { model: ['commodity'], }, sorting: { - sortModel: [{ field: '__row_group_by_columns_group__', sort: 'asc' }], + sortModel: [{ field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, sort: 'asc' }], }, aggregation: { model: { diff --git a/docs/data/data-grid/overview/DataGridPremiumDemo.tsx b/docs/data/data-grid/overview/DataGridPremiumDemo.tsx index 98a6959cff621..3e99541b76cbf 100644 --- a/docs/data/data-grid/overview/DataGridPremiumDemo.tsx +++ b/docs/data/data-grid/overview/DataGridPremiumDemo.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, GridToolbar, useGridApiRef, useKeepGroupedColumnsHidden, @@ -40,7 +41,7 @@ export default function DataGridPremiumDemo() { model: ['commodity'], }, sorting: { - sortModel: [{ field: '__row_group_by_columns_group__', sort: 'asc' }], + sortModel: [{ field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, sort: 'asc' }], }, aggregation: { model: { diff --git a/docs/data/data-grid/pagination/PageSizeCustomOptions.js b/docs/data/data-grid/pagination/PageSizeCustomOptions.js index 667abfb9a412e..2cdb2ddfa58bf 100644 --- a/docs/data/data-grid/pagination/PageSizeCustomOptions.js +++ b/docs/data/data-grid/pagination/PageSizeCustomOptions.js @@ -17,7 +17,7 @@ export default function PageSizeCustomOptions() { ...data.initialState, pagination: { paginationModel: { pageSize: 5 } }, }} - pageSizeOptions={[5, 10, 25]} + pageSizeOptions={[5, 10, 25, { value: -1, label: 'All' }]} />
); diff --git a/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx b/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx index 667abfb9a412e..2cdb2ddfa58bf 100644 --- a/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx +++ b/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx @@ -17,7 +17,7 @@ export default function PageSizeCustomOptions() { ...data.initialState, pagination: { paginationModel: { pageSize: 5 } }, }} - pageSizeOptions={[5, 10, 25]} + pageSizeOptions={[5, 10, 25, { value: -1, label: 'All' }]} />
); diff --git a/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx.preview b/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx.preview index 88965a8c26f4c..1f4dad3d89948 100644 --- a/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx.preview +++ b/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx.preview @@ -4,5 +4,5 @@ ...data.initialState, pagination: { paginationModel: { pageSize: 5 } }, }} - pageSizeOptions={[5, 10, 25]} + pageSizeOptions={[5, 10, 25, { value: -1, label: 'All' }]} /> \ No newline at end of file diff --git a/docs/data/data-grid/pagination/pagination.md b/docs/data/data-grid/pagination/pagination.md index 90b26b56d8d7e..7259d7fca5b02 100644 --- a/docs/data/data-grid/pagination/pagination.md +++ b/docs/data/data-grid/pagination/pagination.md @@ -41,10 +41,10 @@ You should provide an array of items, each item should be one of these types: ``` -- **object**, the `value` and `label` keys will be used respectively for the value and label of the option. +- **object**, the `value` and `label` keys will be used respectively for the value and label of the option. Define `value` as `-1` to display all results. ```jsx - + ``` {{"demo": "PageSizeCustomOptions.js", "bg": "inline"}} diff --git a/docs/data/data-grid/row-grouping/RowGroupingFullExample.js b/docs/data/data-grid/row-grouping/RowGroupingFullExample.js index d1a08e3ff0624..52040b1639af1 100644 --- a/docs/data/data-grid/row-grouping/RowGroupingFullExample.js +++ b/docs/data/data-grid/row-grouping/RowGroupingFullExample.js @@ -1,6 +1,7 @@ import * as React from 'react'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, useGridApiRef, useKeepGroupedColumnsHidden, } from '@mui/x-data-grid-premium'; @@ -23,7 +24,7 @@ export default function RowGroupingFullExample() { model: ['commodity'], }, sorting: { - sortModel: [{ field: '__row_group_by_columns_group__', sort: 'asc' }], + sortModel: [{ field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, sort: 'asc' }], }, }, }); diff --git a/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx b/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx index d1a08e3ff0624..52040b1639af1 100644 --- a/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx +++ b/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, useGridApiRef, useKeepGroupedColumnsHidden, } from '@mui/x-data-grid-premium'; @@ -23,7 +24,7 @@ export default function RowGroupingFullExample() { model: ['commodity'], }, sorting: { - sortModel: [{ field: '__row_group_by_columns_group__', sort: 'asc' }], + sortModel: [{ field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, sort: 'asc' }], }, }, }); diff --git a/docs/data/data-grid/row-grouping/row-grouping.md b/docs/data/data-grid/row-grouping/row-grouping.md index b70899fb38b47..13f56a2ebac88 100644 --- a/docs/data/data-grid/row-grouping/row-grouping.md +++ b/docs/data/data-grid/row-grouping/row-grouping.md @@ -11,6 +11,10 @@ In the following example, movies are grouped based on their production `company` {{"demo": "RowGroupingBasicExample.js", "bg": "inline", "defaultCodeOpen": false}} +:::info +If you are looking for row grouping on the server-side, see [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/). +::: + ## Grouping criteria ### Initialize the row grouping @@ -252,6 +256,10 @@ Use the `setRowChildrenExpansion` method on `apiRef` to programmatically set the {{"demo": "RowGroupingSetChildrenExpansion.js", "bg": "inline", "defaultCodeOpen": false}} +:::warning +The `apiRef.current.setRowChildrenExpansion` method is not compatible with the [server-side tree data](/x/react-data-grid/server-side-data/tree-data/) and [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/). Use `apiRef.current.unstable_dataSource.fetchRows` instead. +::: + ### Customize grouping cell indent To change the default cell indent, you can use the `--DataGrid-cellOffsetMultiplier` CSS variable: @@ -280,10 +288,6 @@ If you are rendering leaves with the `leafField` property of `groupingColDef`, t You can force the filtering to be applied on another grouping criteria with the `mainGroupingCriteria` property of `groupingColDef` -:::warning -This feature is not yet compatible with `sortingMode = "server"` and `filteringMode = "server"`. -::: - {{"demo": "RowGroupingFilteringSingleGroupingColDef.js", "bg": "inline", "defaultCodeOpen": false}} ### Multiple grouping columns @@ -376,6 +380,10 @@ const rows = apiRef.current.getRowGroupChildren({ {{"demo": "RowGroupingGetRowGroupChildren.js", "bg": "inline", "defaultCodeOpen": false}} +:::warning +The `apiRef.current.getRowGroupChildren` method is not compatible with the [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/) since all the rows might not be available to get at a given instance. +::: + ## Row group panel 🚧 :::warning diff --git a/docs/data/data-grid/row-recipes/DetailPanelExpandOnRowClick.js b/docs/data/data-grid/row-recipes/DetailPanelExpandOnRowClick.js new file mode 100644 index 0000000000000..3c9bdfd9c37ee --- /dev/null +++ b/docs/data/data-grid/row-recipes/DetailPanelExpandOnRowClick.js @@ -0,0 +1,194 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Grid from '@mui/material/Grid'; +import Typography from '@mui/material/Typography'; +import Paper from '@mui/material/Paper'; +import Stack from '@mui/material/Stack'; +import { DataGridPro, useGridApiRef } from '@mui/x-data-grid-pro'; +import { + randomCreatedDate, + randomPrice, + randomCurrency, + randomCountry, + randomCity, + randomEmail, + randomInt, + randomAddress, + randomCommodity, +} from '@mui/x-data-grid-generator'; + +function DetailPanelContent({ row: rowProp }) { + return ( + + + + {`Order #${rowProp.id}`} + + + + Customer information + + {rowProp.customer} + {rowProp.email} + + + + Shipping address + + + {rowProp.address} + + + {`${rowProp.city}, ${rowProp.country.label}`} + + + + row.quantity * row.unitPrice, + }, + ]} + rows={rowProp.products} + sx={{ flex: 1 }} + hideFooter + /> + + + + ); +} + +const columns = [ + { field: 'id', headerName: 'Order ID' }, + { field: 'customer', headerName: 'Customer', width: 200 }, + { field: 'date', type: 'date', headerName: 'Placed at' }, + { field: 'currency', headerName: 'Currency' }, + { + field: 'total', + type: 'number', + headerName: 'Total', + valueGetter: (value, row) => { + const subtotal = row.products.reduce( + (acc, product) => product.unitPrice * product.quantity, + 0, + ); + const taxes = subtotal * 0.05; + return subtotal + taxes; + }, + }, +]; + +function generateProducts() { + const quantity = randomInt(1, 5); + return [...Array(quantity)].map((_, index) => ({ + id: index, + name: randomCommodity(), + quantity: randomInt(1, 5), + unitPrice: randomPrice(1, 1000), + })); +} + +const rows = [ + { + id: 1, + customer: 'Matheus', + email: randomEmail(), + date: randomCreatedDate(), + address: randomAddress(), + country: randomCountry(), + city: randomCity(), + currency: randomCurrency(), + products: generateProducts(), + }, + { + id: 2, + customer: 'Olivier', + email: randomEmail(), + date: randomCreatedDate(), + address: randomAddress(), + country: randomCountry(), + city: randomCity(), + currency: randomCurrency(), + products: generateProducts(), + }, + { + id: 3, + customer: 'Flavien', + email: randomEmail(), + date: randomCreatedDate(), + address: randomAddress(), + country: randomCountry(), + city: randomCity(), + currency: randomCurrency(), + products: generateProducts(), + }, + { + id: 4, + customer: 'Danail', + email: randomEmail(), + date: randomCreatedDate(), + address: randomAddress(), + country: randomCountry(), + city: randomCity(), + currency: randomCurrency(), + products: generateProducts(), + }, + { + id: 5, + customer: 'Alexandre', + email: randomEmail(), + date: randomCreatedDate(), + address: randomAddress(), + country: randomCountry(), + city: randomCity(), + currency: randomCurrency(), + products: generateProducts(), + }, +]; + +export default function DetailPanelExpandOnRowClick() { + const getDetailPanelContent = React.useCallback( + ({ row }) => , + [], + ); + + const getDetailPanelHeight = React.useCallback(() => 400, []); + + const apiRef = useGridApiRef(); + + const onRowClick = React.useCallback( + (params) => { + apiRef.current.toggleDetailPanel(params.id); + }, + [apiRef], + ); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/row-recipes/DetailPanelExpandOnRowClick.tsx b/docs/data/data-grid/row-recipes/DetailPanelExpandOnRowClick.tsx new file mode 100644 index 0000000000000..dc5df4e819e97 --- /dev/null +++ b/docs/data/data-grid/row-recipes/DetailPanelExpandOnRowClick.tsx @@ -0,0 +1,202 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Grid from '@mui/material/Grid'; +import Typography from '@mui/material/Typography'; +import Paper from '@mui/material/Paper'; +import Stack from '@mui/material/Stack'; +import { + DataGridPro, + DataGridProProps, + GridColDef, + GridEventListener, + useGridApiRef, +} from '@mui/x-data-grid-pro'; +import { + randomCreatedDate, + randomPrice, + randomCurrency, + randomCountry, + randomCity, + randomEmail, + randomInt, + randomAddress, + randomCommodity, +} from '@mui/x-data-grid-generator'; + +function DetailPanelContent({ row: rowProp }: { row: Customer }) { + return ( + + + + {`Order #${rowProp.id}`} + + + + Customer information + + {rowProp.customer} + {rowProp.email} + + + + Shipping address + + + {rowProp.address} + + {`${rowProp.city}, ${rowProp.country.label}`} + + + row.quantity * row.unitPrice, + }, + ]} + rows={rowProp.products} + sx={{ flex: 1 }} + hideFooter + /> + + + + ); +} + +const columns: GridColDef<(typeof rows)[number]>[] = [ + { field: 'id', headerName: 'Order ID' }, + { field: 'customer', headerName: 'Customer', width: 200 }, + { field: 'date', type: 'date', headerName: 'Placed at' }, + { field: 'currency', headerName: 'Currency' }, + { + field: 'total', + type: 'number', + headerName: 'Total', + valueGetter: (value, row) => { + const subtotal = row.products.reduce( + (acc: number, product: any) => product.unitPrice * product.quantity, + 0, + ); + const taxes = subtotal * 0.05; + return subtotal + taxes; + }, + }, +]; + +function generateProducts() { + const quantity = randomInt(1, 5); + return [...Array(quantity)].map((_, index) => ({ + id: index, + name: randomCommodity(), + quantity: randomInt(1, 5), + unitPrice: randomPrice(1, 1000), + })); +} + +const rows = [ + { + id: 1, + customer: 'Matheus', + email: randomEmail(), + date: randomCreatedDate(), + address: randomAddress(), + country: randomCountry(), + city: randomCity(), + currency: randomCurrency(), + products: generateProducts(), + }, + { + id: 2, + customer: 'Olivier', + email: randomEmail(), + date: randomCreatedDate(), + address: randomAddress(), + country: randomCountry(), + city: randomCity(), + currency: randomCurrency(), + products: generateProducts(), + }, + { + id: 3, + customer: 'Flavien', + email: randomEmail(), + date: randomCreatedDate(), + address: randomAddress(), + country: randomCountry(), + city: randomCity(), + currency: randomCurrency(), + products: generateProducts(), + }, + { + id: 4, + customer: 'Danail', + email: randomEmail(), + date: randomCreatedDate(), + address: randomAddress(), + country: randomCountry(), + city: randomCity(), + currency: randomCurrency(), + products: generateProducts(), + }, + { + id: 5, + customer: 'Alexandre', + email: randomEmail(), + date: randomCreatedDate(), + address: randomAddress(), + country: randomCountry(), + city: randomCity(), + currency: randomCurrency(), + products: generateProducts(), + }, +]; + +type Customer = (typeof rows)[number]; + +export default function DetailPanelExpandOnRowClick() { + const getDetailPanelContent = React.useCallback< + NonNullable + >(({ row }) => , []); + + const getDetailPanelHeight = React.useCallback(() => 400, []); + + const apiRef = useGridApiRef(); + + const onRowClick = React.useCallback>( + (params) => { + apiRef.current.toggleDetailPanel(params.id); + }, + [apiRef], + ); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/row-recipes/DetailPanelExpandOnRowClick.tsx.preview b/docs/data/data-grid/row-recipes/DetailPanelExpandOnRowClick.tsx.preview new file mode 100644 index 0000000000000..db3ecef167cfa --- /dev/null +++ b/docs/data/data-grid/row-recipes/DetailPanelExpandOnRowClick.tsx.preview @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/row-recipes/row-recipes.md b/docs/data/data-grid/row-recipes/row-recipes.md index 6404572a316ca..a21217ac80882 100644 --- a/docs/data/data-grid/row-recipes/row-recipes.md +++ b/docs/data/data-grid/row-recipes/row-recipes.md @@ -25,3 +25,9 @@ It checks the status of open panels using the [`useGridSelector` hook](/x/react- When clicked, it uses [`setExpandedDetailPanels`](/x/api/data-grid/grid-api/#grid-api-prop-setExpandedDetailPanels) from the [Grid API](/x/react-data-grid/api-object/#how-to-use-the-api-object) to expand or collapse all detail panels. {{"demo": "DetailPanelExpandCollapseAll.js", "bg": "inline", "defaultCodeOpen": false}} + +## Toggling detail panels on row click + +In the demo below, you can toggle the detail panel by clicking anywhere on the row: + +{{"demo": "DetailPanelExpandOnRowClick.js", "bg": "inline", "defaultCodeOpen": false}} diff --git a/docs/data/data-grid/row-selection/KeepNonExistentRowsSelected.js b/docs/data/data-grid/row-selection/KeepNonExistentRowsSelected.js new file mode 100644 index 0000000000000..ed3ae91344e34 --- /dev/null +++ b/docs/data/data-grid/row-selection/KeepNonExistentRowsSelected.js @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { DataGrid, GridToolbar } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function KeepNonExistentRowsSelected() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 10, + maxColumns: 6, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/row-selection/KeepNonExistentRowsSelected.tsx b/docs/data/data-grid/row-selection/KeepNonExistentRowsSelected.tsx new file mode 100644 index 0000000000000..ed3ae91344e34 --- /dev/null +++ b/docs/data/data-grid/row-selection/KeepNonExistentRowsSelected.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { DataGrid, GridToolbar } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function KeepNonExistentRowsSelected() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 10, + maxColumns: 6, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/row-selection/KeepNonExistentRowsSelected.tsx.preview b/docs/data/data-grid/row-selection/KeepNonExistentRowsSelected.tsx.preview new file mode 100644 index 0000000000000..b3ad7e26a5efe --- /dev/null +++ b/docs/data/data-grid/row-selection/KeepNonExistentRowsSelected.tsx.preview @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/row-selection/row-selection.md b/docs/data/data-grid/row-selection/row-selection.md index 02f4a9ec20787..84210a5a343d8 100644 --- a/docs/data/data-grid/row-selection/row-selection.md +++ b/docs/data/data-grid/row-selection/row-selection.md @@ -36,6 +36,13 @@ In the demo below only rows with quantity above 50,000 can be selected: {{"demo": "DisableRowSelection.js", "bg": "inline"}} +## Row selection with filtering + +By default, when the rows are filtered the selection is cleared from the rows that don't meet the filter criteria. +To keep those rows selected even when they're not visible, set the `keepNonExistentRowsSelected` prop. + +{{"demo": "KeepNonExistentRowsSelected.js", "bg": "inline"}} + ## Controlled row selection Use the `rowSelectionModel` prop to control the selection. diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js new file mode 100644 index 0000000000000..fc75932d136bc --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js @@ -0,0 +1,69 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx new file mode 100644 index 0000000000000..91c7f66a6d996 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx.preview new file mode 100644 index 0000000000000..920c80a41342d --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx.preview @@ -0,0 +1,16 @@ + + +
+ +
\ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js new file mode 100644 index 0000000000000..793347a263015 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js @@ -0,0 +1,137 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { alpha, styled, darken, lighten } from '@mui/material/styles'; + +export default function ServerSideRowGroupingErrorHandling() { + const apiRef = useGridApiRef(); + const [rootError, setRootError] = React.useState(); + const [childrenError, setChildrenError] = React.useState(); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { fetchRows, columns } = useMockServer( + { + rowGrouping: true, + }, + {}, + shouldRequestsFail, + ); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+
+ + setShouldRequestsFail(event.target.checked)} + /> + } + label="Make the requests fail" + /> +
+
+ { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(error.message); + } else { + setChildrenError( + `${error.message} (Requested level: ${params.groupKeys.join(' > ')})`, + ); + } + }} + unstable_dataSourceCache={null} + apiRef={apiRef} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> +
+
+ ); +} + +function getBorderColor(theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }) { + if (!error) { + return null; + } + return {error}; +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx new file mode 100644 index 0000000000000..621b74b052f4a --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx @@ -0,0 +1,138 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; + +export default function ServerSideRowGroupingErrorHandling() { + const apiRef = useGridApiRef(); + const [rootError, setRootError] = React.useState(); + const [childrenError, setChildrenError] = React.useState(); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { fetchRows, columns } = useMockServer( + { + rowGrouping: true, + }, + {}, + shouldRequestsFail, + ); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+
+ + setShouldRequestsFail(event.target.checked)} + /> + } + label="Make the requests fail" + /> +
+
+ { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(error.message); + } else { + setChildrenError( + `${error.message} (Requested level: ${params.groupKeys.join(' > ')})`, + ); + } + }} + unstable_dataSourceCache={null} + apiRef={apiRef} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> +
+
+ ); +} + +function getBorderColor(theme: Theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }: { error: string }) { + if (!error) { + return null; + } + return {error}; +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js new file mode 100644 index 0000000000000..7db1ea09c9c37 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, + GridToolbar, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingFullDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns, loadNewData } = useMockServer({ + rowGrouping: true, + rowLength: 1000, + dataSet: 'Commodity', + maxColumns: 20, + }); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['commodity', 'status'], + }, + columns: { + columnVisibilityModel: { + id: false, + }, + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx new file mode 100644 index 0000000000000..4545cf49f9e77 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx @@ -0,0 +1,84 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, + GridToolbar, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingFullDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns, loadNewData } = useMockServer({ + rowGrouping: true, + rowLength: 1000, + dataSet: 'Commodity', + maxColumns: 20, + }); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['commodity', 'status'], + }, + columns: { + columnVisibilityModel: { + id: false, + }, + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js new file mode 100644 index 0000000000000..bdb42747fc079 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingGroupExpansion() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company'], + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx new file mode 100644 index 0000000000000..64aa58a45d8e1 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingGroupExpansion() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company'], + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index 537ef8ad0d54c..25b6044f3f43c 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -2,14 +2,90 @@ title: React Server-side row grouping --- -# Data Grid - Server-side row grouping [](/x/introduction/licensing/#pro-plan 'Pro plan')🚧 +# Data Grid - Server-side row grouping [](/x/introduction/licensing/#pro-plan 'Pro plan')

Lazy-loaded row grouping with server-side data source.

-:::warning -This feature isn't implemented yet. It's coming. +To dynamically load row grouping data from the server, including lazy-loading of children, create a data source and pass the `unstable_dataSource` prop to the Data Grid, as mentioned in the [overview](/x/react-data-grid/server-side-data/) section. + +:::info +If you are looking for row grouping on the client-side, see [client-side row grouping](/x/react-data-grid/row-grouping/). +::: + +Similar to the [tree data](/x/react-data-grid/server-side-data/tree-data/), you need to pass some additional properties to enable the data source row grouping feature: + +- `getGroupKey()`: Returns the group key for the row. +- `getChildrenCount()`: Returns the number of children for the row. If the children count is not available for some reason, but there are some children, returns `-1`. + +```tsx +const customDataSource: GridDataSource = { + getRows: async (params) => { + // Fetch the data from the server + }, + getGroupKey: (row) => { + // Return the group key for the row, e.g. `name` + return row.name; + }, + getChildrenCount: (row) => { + // Return the number of children for the row + return row.childrenCount; + }, +}; +``` -👍 Upvote [issue #10859](https://github.com/mui/mui-x/issues/10859) if you want to see it land faster. +In addition to `groupKeys`, the `getRows()` callback receives a `groupFields` parameter. This corresponds to the current `rowGroupingModel`. Use `groupFields` on the server to group the data for each `getRows()` call. -Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with your current solution. +```tsx +const getRows: async (params) => { + const urlParams = new URLSearchParams({ + // Example: JSON.stringify(['20th Century Fox', 'James Cameron']) + groupKeys: JSON.stringify(params.groupKeys), + // Example: JSON.stringify(['company', 'director']) + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + // Server should group the data based on `groupFields` and + // extract the rows for the nested level based on `groupKeys` + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; +} +``` + +{{"demo": "ServerSideRowGroupingDataGrid.js", "bg": "inline"}} + +:::warning +For complex data, consider using `colDef.groupingValueGetter` to extract the grouping value. This value is passed in the `groupKeys` parameter when `getRows` is called. + +Ensure your backend can interpret the `groupKeys` parameter generated by `colDef.groupingValueGetter` to retrieve grouping values for child rows. ::: + +## Error handling + +If an error occurs during a `getRows` call, the Data Grid displays an error message in the row group cell. `unstable_onDataSourceError` is also triggered with the error and the fetch params. + +This example shows error handling with toast notifications and default error messages in grouping cells. Caching is disabled for simplicity. + +{{"demo": "ServerSideRowGroupingErrorHandling.js", "bg": "inline"}} + +## Group expansion + +The group expansion works similar to the [data source tree data](/x/react-data-grid/server-side-data/tree-data/#group-expansion). +The following demo uses `defaultGroupingExpansionDepth='-1'` to expand all the groups. + +{{"demo": "ServerSideRowGroupingGroupExpansion.js", "bg": "inline"}} + +## Demo + +In the following demo, use the auto generated data based on the `Commodities` dataset to simulate the server-side row grouping. + +{{"demo": "ServerSideRowGroupingFullDataGrid.js", "bg": "inline"}} + +## API + +- [DataGrid](/x/api/data-grid/data-grid/) +- [DataGridPro](/x/api/data-grid/data-grid-pro/) +- [DataGridPremium](/x/api/data-grid/data-grid-premium/) diff --git a/docs/data/data-grid/server-side-data/tree-data.md b/docs/data/data-grid/server-side-data/tree-data.md index 8e77fe0542bfa..d5c725ac456ed 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -8,8 +8,14 @@ title: React Server-side tree data To dynamically load tree data from the server, including lazy-loading of children, you must create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview section](/x/react-data-grid/server-side-data/). -The data source also requires some additional props to handle tree data, namely `getGroupKey` and `getChildrenCount`. -If the children count is not available for some reason, but there are some children, `getChildrenCount` should return `-1`. +:::info +If you are looking for tree data on the client-side, see [client-side tree data](/x/react-data-grid/tree-data/). +::: + +The data source also requires some additional props to handle tree data: + +- `getGroupKey()`: Returns the group key for the row. +- `getChildrenCount()`: Returns the number of children for the row. If the children count is not available for some reason, but there are some children, returns `-1`. ```tsx const customDataSource: GridDataSource = { @@ -27,6 +33,26 @@ const customDataSource: GridDataSource = { }; ``` +Like the other parameters such as `filterModel`, `sortModel`, and `paginationModel`, the `getRows()` callback receives a `groupKeys` parameter that corresponds to the keys provided for each nested level in `getGroupKey()`. +Use `groupKeys` on the server to extract the rows for a given nested level. + +```tsx +const getRows: async (params) => { + const urlParams = new URLSearchParams({ + // Example: JSON.stringify(['Billy Houston', 'Lora Dean']) + groupKeys: JSON.stringify(params.groupKeys), + }); + const getRowsResponse = await fetchRows( + // Server should extract the rows for the nested level based on `groupKeys` + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; +} +``` + The following tree data example supports filtering, sorting, and pagination on the server. It also caches the data by default. diff --git a/docs/data/data-grid-component-api-pages.ts b/docs/data/dataGridApiPages.ts similarity index 89% rename from docs/data/data-grid-component-api-pages.ts rename to docs/data/dataGridApiPages.ts index 5aed416321e15..37479bd4801b7 100644 --- a/docs/data/data-grid-component-api-pages.ts +++ b/docs/data/dataGridApiPages.ts @@ -1,6 +1,6 @@ import type { MuiPage } from 'docs/src/MuiPage'; -const apiPages: MuiPage[] = [ +const dataGridApiPages: MuiPage[] = [ { pathname: '/x/api/data-grid/data-grid', title: 'DataGrid', @@ -28,4 +28,4 @@ const apiPages: MuiPage[] = [ title: 'GridToolbarQuickFilter', }, ]; -export default apiPages; +export default dataGridApiPages; diff --git a/docs/data/date-pickers/custom-components/custom-components.md b/docs/data/date-pickers/custom-components/custom-components.md index 023e89cb0a05f..e05e9c6e808ab 100644 --- a/docs/data/date-pickers/custom-components/custom-components.md +++ b/docs/data/date-pickers/custom-components/custom-components.md @@ -14,7 +14,7 @@ For example, available Date Picker slots can be found [here](/x/api/date-pickers ::: :::success -See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +See [Common concepts—Slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. ::: ## Action bar diff --git a/docs/data/date-pickers/custom-field/BrowserV6Field.js b/docs/data/date-pickers/custom-field/BrowserV6Field.js deleted file mode 100644 index 63be3f56e8846..0000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6Field.js +++ /dev/null @@ -1,90 +0,0 @@ -import * as React from 'react'; - -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import Box from '@mui/material/Box'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { unstable_useDateField as useDateField } from '@mui/x-date-pickers/DateField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; - -const BrowserField = React.forwardRef((props, ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); -}); - -const BrowserDateField = React.forwardRef((props, ref) => { - const { slots, slotProps, ...textFieldProps } = props; - - const fieldResponse = useDateField({ - ...textFieldProps, - enableAccessibleFieldDOMStructure: false, - }); - - /* If you don't need a clear button, you can skip the use of this hook */ - const processedFieldProps = useClearableField({ - ...fieldResponse, - slots, - slotProps, - }); - - return ; -}); - -const BrowserDatePicker = React.forwardRef((props, ref) => { - return ( - - ); -}); - -export default function BrowserV6Field() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6Field.tsx b/docs/data/date-pickers/custom-field/BrowserV6Field.tsx deleted file mode 100644 index 49138cd42863e..0000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6Field.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import * as React from 'react'; -import { Dayjs } from 'dayjs'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import Box from '@mui/material/Box'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DatePicker, DatePickerProps } from '@mui/x-date-pickers/DatePicker'; -import { - unstable_useDateField as useDateField, - UseDateFieldProps, -} from '@mui/x-date-pickers/DateField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; -import { - BaseSingleInputFieldProps, - DateValidationError, - FieldSection, -} from '@mui/x-date-pickers/models'; - -interface BrowserFieldProps - extends Omit, 'size'> { - label?: React.ReactNode; - inputRef?: React.Ref; - InputProps?: { - ref?: React.Ref; - endAdornment?: React.ReactNode; - startAdornment?: React.ReactNode; - }; - error?: boolean; - focused?: boolean; - ownerState?: any; - sx?: any; - enableAccessibleFieldDOMStructure: boolean; -} - -type BrowserFieldComponent = (( - props: BrowserFieldProps & React.RefAttributes, -) => React.JSX.Element) & { propTypes?: any }; - -const BrowserField = React.forwardRef( - (props: BrowserFieldProps, ref: React.Ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); - }, -) as BrowserFieldComponent; - -interface BrowserDateFieldProps - extends UseDateFieldProps, - BaseSingleInputFieldProps< - Dayjs | null, - Dayjs, - FieldSection, - false, - DateValidationError - > {} - -const BrowserDateField = React.forwardRef( - (props: BrowserDateFieldProps, ref: React.Ref) => { - const { slots, slotProps, ...textFieldProps } = props; - - const fieldResponse = useDateField({ - ...textFieldProps, - enableAccessibleFieldDOMStructure: false, - }); - - /* If you don't need a clear button, you can skip the use of this hook */ - const processedFieldProps = useClearableField({ - ...fieldResponse, - slots, - slotProps, - }); - - return ; - }, -); - -const BrowserDatePicker = React.forwardRef( - (props: DatePickerProps, ref: React.Ref) => { - return ( - - ref={ref} - {...props} - slots={{ ...props.slots, field: BrowserDateField }} - /> - ); - }, -); - -export default function BrowserV6Field() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6Field.tsx.preview b/docs/data/date-pickers/custom-field/BrowserV6Field.tsx.preview deleted file mode 100644 index 0bb9e399d3cb4..0000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6Field.tsx.preview +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.js deleted file mode 100644 index 5421fd0d4348c..0000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.js +++ /dev/null @@ -1,140 +0,0 @@ -import * as React from 'react'; - -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import useSlotProps from '@mui/utils/useSlotProps'; -import Box from '@mui/material/Box'; -import Stack from '@mui/material/Stack'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; -import { unstable_useMultiInputDateRangeField as useMultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField'; - -const BrowserField = React.forwardRef((props, ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); -}); - -const BrowserMultiInputDateRangeField = React.forwardRef((props, ref) => { - const { - slotProps, - value, - defaultValue, - format, - onChange, - readOnly, - disabled, - onError, - shouldDisableDate, - minDate, - maxDate, - disableFuture, - disablePast, - selectedSections, - onSelectedSectionsChange, - className, - unstableStartFieldRef, - unstableEndFieldRef, - } = props; - - const startTextFieldProps = useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, - }); - - const endTextFieldProps = useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, - }); - - const fieldResponse = useMultiInputDateRangeField({ - sharedProps: { - value, - defaultValue, - format, - onChange, - readOnly, - disabled, - onError, - shouldDisableDate, - minDate, - maxDate, - disableFuture, - disablePast, - selectedSections, - onSelectedSectionsChange, - enableAccessibleFieldDOMStructure: false, - }, - startTextFieldProps, - endTextFieldProps, - unstableStartFieldRef, - unstableEndFieldRef, - }); - - return ( - - - - - - ); -}); - -const BrowserDateRangePicker = React.forwardRef((props, ref) => { - return ( - - ); -}); - -export default function BrowserV6MultiInputRangeField() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx deleted file mode 100644 index b52029d14cde9..0000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import * as React from 'react'; -import { Dayjs } from 'dayjs'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import useSlotProps from '@mui/utils/useSlotProps'; -import Box from '@mui/material/Box'; -import Stack from '@mui/material/Stack'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { - DateRangePicker, - DateRangePickerProps, -} from '@mui/x-date-pickers-pro/DateRangePicker'; -import { unstable_useMultiInputDateRangeField as useMultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField'; -import { - BaseMultiInputFieldProps, - DateRange, - DateRangeValidationError, - MultiInputFieldSlotTextFieldProps, - RangeFieldSection, - UseDateRangeFieldProps, -} from '@mui/x-date-pickers-pro/models'; - -interface BrowserFieldProps - extends Omit, 'size'> { - label?: React.ReactNode; - inputRef?: React.Ref; - InputProps?: { - ref?: React.Ref; - endAdornment?: React.ReactNode; - startAdornment?: React.ReactNode; - }; - error?: boolean; - focused?: boolean; - ownerState?: any; - sx?: any; - enableAccessibleFieldDOMStructure: boolean; -} - -type BrowserFieldComponent = (( - props: BrowserFieldProps & React.RefAttributes, -) => React.JSX.Element) & { propTypes?: any }; - -const BrowserField = React.forwardRef( - (props: BrowserFieldProps, ref: React.Ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); - }, -) as BrowserFieldComponent; - -interface BrowserMultiInputDateRangeFieldProps - extends UseDateRangeFieldProps, - BaseMultiInputFieldProps< - DateRange, - Dayjs, - RangeFieldSection, - false, - DateRangeValidationError - > {} - -type BrowserMultiInputDateRangeFieldComponent = (( - props: BrowserMultiInputDateRangeFieldProps & React.RefAttributes, -) => React.JSX.Element) & { propTypes?: any }; - -const BrowserMultiInputDateRangeField = React.forwardRef( - (props: BrowserMultiInputDateRangeFieldProps, ref: React.Ref) => { - const { - slotProps, - value, - defaultValue, - format, - onChange, - readOnly, - disabled, - onError, - shouldDisableDate, - minDate, - maxDate, - disableFuture, - disablePast, - selectedSections, - onSelectedSectionsChange, - className, - unstableStartFieldRef, - unstableEndFieldRef, - } = props; - - const startTextFieldProps = useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, - }) as MultiInputFieldSlotTextFieldProps; - - const endTextFieldProps = useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, - }) as MultiInputFieldSlotTextFieldProps; - - const fieldResponse = useMultiInputDateRangeField< - Dayjs, - false, - MultiInputFieldSlotTextFieldProps - >({ - sharedProps: { - value, - defaultValue, - format, - onChange, - readOnly, - disabled, - onError, - shouldDisableDate, - minDate, - maxDate, - disableFuture, - disablePast, - selectedSections, - onSelectedSectionsChange, - enableAccessibleFieldDOMStructure: false, - }, - startTextFieldProps, - endTextFieldProps, - unstableStartFieldRef, - unstableEndFieldRef, - }); - - return ( - - - - - - ); - }, -) as BrowserMultiInputDateRangeFieldComponent; - -const BrowserDateRangePicker = React.forwardRef( - (props: DateRangePickerProps, ref: React.Ref) => { - return ( - - ); - }, -); - -export default function BrowserV6MultiInputRangeField() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx.preview b/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx.preview deleted file mode 100644 index d797406fa9997..0000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx.preview +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.js deleted file mode 100644 index 311029bbc6fa8..0000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.js +++ /dev/null @@ -1,137 +0,0 @@ -import * as React from 'react'; - -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import useSlotProps from '@mui/utils/useSlotProps'; -import Box from '@mui/material/Box'; -import IconButton from '@mui/material/IconButton'; -import InputAdornment from '@mui/material/InputAdornment'; -import { DateRangeIcon } from '@mui/x-date-pickers/icons'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; -import { unstable_useSingleInputDateRangeField as useSingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; - -const BrowserField = React.forwardRef((props, ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); -}); - -const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => { - const { slots, slotProps, onAdornmentClick, ...other } = props; - - const textFieldProps = useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - externalForwardedProps: other, - ownerState: props, - }); - - textFieldProps.InputProps = { - ...textFieldProps.InputProps, - endAdornment: ( - - - - - - ), - }; - - const fieldResponse = useSingleInputDateRangeField({ - ...textFieldProps, - enableAccessibleFieldDOMStructure: false, - }); - - /* If you don't need a clear button, you can skip the use of this hook */ - const processedFieldProps = useClearableField({ - ...fieldResponse, - slots, - slotProps, - }); - - return ( - - ); -}); - -BrowserSingleInputDateRangeField.fieldType = 'single-input'; - -const BrowserSingleInputDateRangePicker = React.forwardRef((props, ref) => { - const [isOpen, setIsOpen] = React.useState(false); - - const toggleOpen = () => setIsOpen((currentOpen) => !currentOpen); - - const handleOpen = () => setIsOpen(true); - - const handleClose = () => setIsOpen(false); - - return ( - - ); -}); - -export default function BrowserV6SingleInputRangeField() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx deleted file mode 100644 index 6db84b3f6c575..0000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import * as React from 'react'; -import { Dayjs } from 'dayjs'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import useSlotProps from '@mui/utils/useSlotProps'; -import Box from '@mui/material/Box'; -import IconButton from '@mui/material/IconButton'; -import InputAdornment from '@mui/material/InputAdornment'; -import { DateRangeIcon } from '@mui/x-date-pickers/icons'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { - DateRangePicker, - DateRangePickerProps, -} from '@mui/x-date-pickers-pro/DateRangePicker'; -import { - unstable_useSingleInputDateRangeField as useSingleInputDateRangeField, - SingleInputDateRangeFieldProps, - UseSingleInputDateRangeFieldProps, -} from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; -import { BaseSingleInputFieldProps } from '@mui/x-date-pickers/models'; -import { - DateRangeValidationError, - RangeFieldSection, - DateRange, - FieldType, -} from '@mui/x-date-pickers-pro/models'; - -interface BrowserFieldProps - extends Omit, 'size'> { - label?: React.ReactNode; - inputRef?: React.Ref; - InputProps?: { - ref?: React.Ref; - endAdornment?: React.ReactNode; - startAdornment?: React.ReactNode; - }; - error?: boolean; - focused?: boolean; - ownerState?: any; - sx?: any; - enableAccessibleFieldDOMStructure: boolean; -} - -type BrowserFieldComponent = (( - props: BrowserFieldProps & React.RefAttributes, -) => React.JSX.Element) & { propTypes?: any }; - -const BrowserField = React.forwardRef( - (props: BrowserFieldProps, ref: React.Ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); - }, -) as BrowserFieldComponent; - -interface BrowserSingleInputDateRangeFieldProps - extends UseSingleInputDateRangeFieldProps, - BaseSingleInputFieldProps< - DateRange, - Dayjs, - RangeFieldSection, - false, - DateRangeValidationError - > { - onAdornmentClick?: () => void; -} - -type BrowserSingleInputDateRangeFieldComponent = (( - props: BrowserSingleInputDateRangeFieldProps & React.RefAttributes, -) => React.JSX.Element) & { fieldType?: FieldType }; - -const BrowserSingleInputDateRangeField = React.forwardRef( - (props: BrowserSingleInputDateRangeFieldProps, ref: React.Ref) => { - const { slots, slotProps, onAdornmentClick, ...other } = props; - - const textFieldProps: SingleInputDateRangeFieldProps = - useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - externalForwardedProps: other, - ownerState: props as any, - }); - - textFieldProps.InputProps = { - ...textFieldProps.InputProps, - endAdornment: ( - - - - - - ), - }; - - const fieldResponse = useSingleInputDateRangeField< - Dayjs, - false, - typeof textFieldProps - >({ ...textFieldProps, enableAccessibleFieldDOMStructure: false }); - - /* If you don't need a clear button, you can skip the use of this hook */ - const processedFieldProps = useClearableField({ - ...fieldResponse, - slots, - slotProps, - }); - - return ( - - ); - }, -) as BrowserSingleInputDateRangeFieldComponent; - -BrowserSingleInputDateRangeField.fieldType = 'single-input'; - -const BrowserSingleInputDateRangePicker = React.forwardRef( - (props: DateRangePickerProps, ref: React.Ref) => { - const [isOpen, setIsOpen] = React.useState(false); - - const toggleOpen = () => setIsOpen((currentOpen) => !currentOpen); - - const handleOpen = () => setIsOpen(true); - - const handleClose = () => setIsOpen(false); - - return ( - - ); - }, -); - -export default function BrowserV6SingleInputRangeField() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx.preview b/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx.preview deleted file mode 100644 index bcaf8043948fb..0000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx.preview +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx index c4ef19f2f15b6..b45092eb35efd 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx @@ -201,7 +201,7 @@ const BrowserMultiInputDateRangeField = React.forwardRef( ) as BrowserMultiInputDateRangeFieldComponent; const BrowserDateRangePicker = React.forwardRef( - (props: DateRangePickerProps, ref: React.Ref) => { + (props: DateRangePickerProps, ref: React.Ref) => { return ( { - const { - setOpen, - label, - id, - disabled, - InputProps: { ref: containerRef } = {}, - inputProps: { 'aria-label': ariaLabel } = {}, - } = props; - - const handleRef = useForkRef(ref, containerRef); - - return ( - - ); -}); - -DateRangeButtonField.fieldType = 'single-input'; - -const ButtonDateRangePicker = React.forwardRef((props, ref) => { - const [open, setOpen] = React.useState(false); - - return ( - setOpen(false)} - onOpen={() => setOpen(true)} - /> - ); -}); - -export default function DateRangePickerWithButtonField() { - const [value, setValue] = React.useState([null, null]); - - return ( - - (date ? date.format('MM/DD/YYYY') : 'null')) - .join(' - ') - } - value={value} - onChange={(newValue) => setValue(newValue)} - /> - - ); -} diff --git a/docs/data/date-pickers/custom-field/DateRangePickerWithButtonField.tsx b/docs/data/date-pickers/custom-field/DateRangePickerWithButtonField.tsx deleted file mode 100644 index 1b894f55e6a15..0000000000000 --- a/docs/data/date-pickers/custom-field/DateRangePickerWithButtonField.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import * as React from 'react'; -import { Dayjs } from 'dayjs'; -import Button from '@mui/material/Button'; -import useForkRef from '@mui/utils/useForkRef'; -import { DateRange, FieldType } from '@mui/x-date-pickers-pro/models'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { - DateRangePicker, - DateRangePickerProps, -} from '@mui/x-date-pickers-pro/DateRangePicker'; -import { SingleInputDateRangeFieldProps } from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; - -interface DateRangeButtonFieldProps extends SingleInputDateRangeFieldProps { - setOpen?: React.Dispatch>; -} - -type DateRangeButtonFieldComponent = (( - props: DateRangeButtonFieldProps & React.RefAttributes, -) => React.JSX.Element) & { fieldType?: FieldType }; - -const DateRangeButtonField = React.forwardRef( - (props: DateRangeButtonFieldProps, ref: React.Ref) => { - const { - setOpen, - label, - id, - disabled, - InputProps: { ref: containerRef } = {}, - inputProps: { 'aria-label': ariaLabel } = {}, - } = props; - - const handleRef = useForkRef(ref, containerRef); - - return ( - - ); - }, -) as DateRangeButtonFieldComponent; - -DateRangeButtonField.fieldType = 'single-input'; - -const ButtonDateRangePicker = React.forwardRef( - ( - props: Omit, 'open' | 'onOpen' | 'onClose'>, - ref: React.Ref, - ) => { - const [open, setOpen] = React.useState(false); - - return ( - setOpen(false)} - onOpen={() => setOpen(true)} - /> - ); - }, -); - -export default function DateRangePickerWithButtonField() { - const [value, setValue] = React.useState>([null, null]); - - return ( - - (date ? date.format('MM/DD/YYYY') : 'null')) - .join(' - ') - } - value={value} - onChange={(newValue) => setValue(newValue)} - /> - - ); -} diff --git a/docs/data/date-pickers/custom-field/DateRangePickerWithButtonField.tsx.preview b/docs/data/date-pickers/custom-field/DateRangePickerWithButtonField.tsx.preview deleted file mode 100644 index 3a8d6daacd70e..0000000000000 --- a/docs/data/date-pickers/custom-field/DateRangePickerWithButtonField.tsx.preview +++ /dev/null @@ -1,11 +0,0 @@ - (date ? date.format('MM/DD/YYYY') : 'null')) - .join(' - ') - } - value={value} - onChange={(newValue) => setValue(newValue)} -/> \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/MaterialV6Field.js b/docs/data/date-pickers/custom-field/MaterialV6Field.js index 35d4e0ca73ead..98a5d2d58c27f 100644 --- a/docs/data/date-pickers/custom-field/MaterialV6Field.js +++ b/docs/data/date-pickers/custom-field/MaterialV6Field.js @@ -9,8 +9,8 @@ export default function MaterialV6Field() { return ( - - + + ); diff --git a/docs/data/date-pickers/custom-field/MaterialV6Field.tsx b/docs/data/date-pickers/custom-field/MaterialV6Field.tsx index 35d4e0ca73ead..98a5d2d58c27f 100644 --- a/docs/data/date-pickers/custom-field/MaterialV6Field.tsx +++ b/docs/data/date-pickers/custom-field/MaterialV6Field.tsx @@ -9,8 +9,8 @@ export default function MaterialV6Field() { return ( - - + + ); diff --git a/docs/data/date-pickers/custom-field/MaterialV6Field.tsx.preview b/docs/data/date-pickers/custom-field/MaterialV6Field.tsx.preview index 027cb7beacfac..9041ed886d8b0 100644 --- a/docs/data/date-pickers/custom-field/MaterialV6Field.tsx.preview +++ b/docs/data/date-pickers/custom-field/MaterialV6Field.tsx.preview @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.js b/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.js deleted file mode 100644 index df1b5053a7451..0000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.js +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from 'react'; -import MuiTextField from '@mui/material/TextField'; -import { DemoContainer } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DateField } from '@mui/x-date-pickers/DateField'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; - -const TextField = React.forwardRef((props, ref) => ( - -)); - -export default function MaterialV6FieldWrapped() { - return ( - - - - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx b/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx deleted file mode 100644 index 0c90908a7d4b5..0000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from 'react'; -import MuiTextField, { TextFieldProps } from '@mui/material/TextField'; -import { DemoContainer } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DateField } from '@mui/x-date-pickers/DateField'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; - -const TextField = React.forwardRef( - (props: TextFieldProps, ref: React.Ref) => ( - - ), -); - -export default function MaterialV6FieldWrapped() { - return ( - - - - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx.preview b/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx.preview deleted file mode 100644 index cd5ff731abbcd..0000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx.preview +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/MaterialV7Field.js b/docs/data/date-pickers/custom-field/MaterialV7Field.js deleted file mode 100644 index 07ba449750d65..0000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV7Field.js +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import { DemoContainer } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { DateField } from '@mui/x-date-pickers/DateField'; - -export default function MaterialV7Field() { - return ( - - - - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/MaterialV7Field.tsx b/docs/data/date-pickers/custom-field/MaterialV7Field.tsx deleted file mode 100644 index 07ba449750d65..0000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV7Field.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import { DemoContainer } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { DateField } from '@mui/x-date-pickers/DateField'; - -export default function MaterialV7Field() { - return ( - - - - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/MaterialV7Field.tsx.preview b/docs/data/date-pickers/custom-field/MaterialV7Field.tsx.preview deleted file mode 100644 index 9708ed359729e..0000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV7Field.tsx.preview +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.js b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.js index d125a8479b5c8..22560a3bcc800 100644 --- a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.js +++ b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.js @@ -14,14 +14,8 @@ export default function MaterialV7FieldWrapped() { return ( - - + + ); diff --git a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx index 82e06da7316b3..d054e12c04de2 100644 --- a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx +++ b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx @@ -19,14 +19,8 @@ export default function MaterialV7FieldWrapped() { return ( - - + + ); diff --git a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx.preview b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx.preview index 17535291f5f3b..a31c9c5bc3512 100644 --- a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx.preview +++ b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx.preview @@ -1,8 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/PickerWithButtonField.js b/docs/data/date-pickers/custom-field/PickerWithButtonField.js deleted file mode 100644 index f13ca0315b2bb..0000000000000 --- a/docs/data/date-pickers/custom-field/PickerWithButtonField.js +++ /dev/null @@ -1,59 +0,0 @@ -import * as React from 'react'; - -import Button from '@mui/material/Button'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; - -function ButtonField(props) { - const { - setOpen, - label, - id, - disabled, - InputProps: { ref } = {}, - inputProps: { 'aria-label': ariaLabel } = {}, - } = props; - - return ( - - ); -} - -function ButtonDatePicker(props) { - const [open, setOpen] = React.useState(false); - - return ( - setOpen(false)} - onOpen={() => setOpen(true)} - /> - ); -} - -export default function PickerWithButtonField() { - const [value, setValue] = React.useState(null); - - return ( - - setValue(newValue)} - /> - - ); -} diff --git a/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx b/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx deleted file mode 100644 index aa09717fb29ce..0000000000000 --- a/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import * as React from 'react'; -import { Dayjs } from 'dayjs'; -import Button from '@mui/material/Button'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DatePicker, DatePickerProps } from '@mui/x-date-pickers/DatePicker'; -import { UseDateFieldProps } from '@mui/x-date-pickers/DateField'; -import { - BaseSingleInputFieldProps, - DateValidationError, - FieldSection, -} from '@mui/x-date-pickers/models'; - -interface ButtonFieldProps - extends UseDateFieldProps, - BaseSingleInputFieldProps< - Dayjs | null, - Dayjs, - FieldSection, - false, - DateValidationError - > { - setOpen?: React.Dispatch>; -} - -function ButtonField(props: ButtonFieldProps) { - const { - setOpen, - label, - id, - disabled, - InputProps: { ref } = {}, - inputProps: { 'aria-label': ariaLabel } = {}, - } = props; - - return ( - - ); -} - -function ButtonDatePicker( - props: Omit, 'open' | 'onOpen' | 'onClose'>, -) { - const [open, setOpen] = React.useState(false); - - return ( - setOpen(false)} - onOpen={() => setOpen(true)} - /> - ); -} - -export default function PickerWithButtonField() { - const [value, setValue] = React.useState(null); - - return ( - - setValue(newValue)} - /> - - ); -} diff --git a/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx.preview b/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx.preview deleted file mode 100644 index 173a0ba169624..0000000000000 --- a/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx.preview +++ /dev/null @@ -1,5 +0,0 @@ - setValue(newValue)} -/> \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/PickerWithJoyField.tsx.preview b/docs/data/date-pickers/custom-field/PickerWithJoyField.tsx.preview deleted file mode 100644 index cff735f8a7af0..0000000000000 --- a/docs/data/date-pickers/custom-field/PickerWithJoyField.tsx.preview +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/PickerWithAutocompleteField.js b/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.js similarity index 79% rename from docs/data/date-pickers/custom-field/PickerWithAutocompleteField.js rename to docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.js index ea7512ace1590..0e849ab1228c0 100644 --- a/docs/data/date-pickers/custom-field/PickerWithAutocompleteField.js +++ b/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.js @@ -6,19 +6,31 @@ import Stack from '@mui/material/Stack'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; function AutocompleteField(props) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + const { value, timezone, onChange } = internalProps; const { + InputProps, + slotProps, + slots, + ownerState, label, - disabled, - readOnly, - id, - value, - onChange, - InputProps: { ref, startAdornment, endAdornment } = {}, - inputProps, + focused, + name, options = [], - } = props; + inputProps, + ...other + } = forwardedProps; + + const { hasValidationError } = useValidation({ + validator: validateDate, + value, + timezone, + props: internalProps, + }); const mergeAdornments = (...adornments) => { const nonNullAdornments = adornments.filter((el) => el != null); @@ -41,25 +53,24 @@ function AutocompleteField(props) { return ( ( , - BaseSingleInputFieldProps< - Dayjs | null, - Dayjs, - FieldSection, - false, - DateValidationError - > { +interface AutocompleteFieldProps extends DatePickerFieldProps { /** * @typescript-to-proptypes-ignore */ options?: Dayjs[]; } -function AutocompleteField(props: AutoCompleteFieldProps) { +function AutocompleteField(props: AutocompleteFieldProps) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + const { value, timezone, onChange } = internalProps; const { + InputProps, + slotProps, + slots, + ownerState, label, - disabled, - readOnly, - id, - value, - onChange, - InputProps: { ref, startAdornment, endAdornment } = {}, - inputProps, + focused, + name, options = [], - } = props; + inputProps, + ...other + } = forwardedProps; + + const { hasValidationError } = useValidation({ + validator: validateDate, + value, + timezone, + props: internalProps, + }); const mergeAdornments = (...adornments: React.ReactNode[]) => { const nonNullAdornments = adornments.filter((el) => el != null); @@ -62,25 +64,24 @@ function AutocompleteField(props: AutoCompleteFieldProps) { return ( ( + !optionsLookup[date.startOf('day').toISOString()]} @@ -138,7 +139,7 @@ function AutocompleteDatePicker(props: AutocompleteDatePickerProps) { const today = dayjs().startOf('day'); -export default function PickerWithAutocompleteField() { +export default function MaterialDatePicker() { return ( { + if (pickersContext.open) { + pickersContext.onClose(event); + } else { + pickersContext.onOpen(event); + } + }; + + const valueStr = value == null ? parsedFormat : value.format(format); + + return ( + + ); +} + +function ButtonFieldDatePicker(props) { + return ( + + ); +} + +export default function MaterialDatePicker() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx new file mode 100644 index 0000000000000..1f31f5d63e6a8 --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import { Dayjs } from 'dayjs'; +import Button from '@mui/material/Button'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { + DatePicker, + DatePickerProps, + DatePickerFieldProps, +} from '@mui/x-date-pickers/DatePicker'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; +import { + useSplitFieldProps, + useParsedFormat, + usePickersContext, +} from '@mui/x-date-pickers/hooks'; + +function ButtonDateField(props: DatePickerFieldProps) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + const { value, timezone, format } = internalProps; + const { + InputProps, + slotProps, + slots, + ownerState, + label, + focused, + name, + ...other + } = forwardedProps; + + const pickersContext = usePickersContext(); + + const parsedFormat = useParsedFormat(internalProps); + const { hasValidationError } = useValidation({ + validator: validateDate, + value, + timezone, + props: internalProps, + }); + + const handleTogglePicker = (event: React.UIEvent) => { + if (pickersContext.open) { + pickersContext.onClose(event); + } else { + pickersContext.onOpen(event); + } + }; + + const valueStr = value == null ? parsedFormat : value.format(format); + + return ( + + ); +} + +function ButtonFieldDatePicker(props: DatePickerProps) { + return ( + + ); +} + +export default function MaterialDatePicker() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx.preview b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx.preview new file mode 100644 index 0000000000000..5f578e21a1d63 --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js new file mode 100644 index 0000000000000..f9fc03f45d578 --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js @@ -0,0 +1,82 @@ +import * as React from 'react'; + +import Button from '@mui/material/Button'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; +import { useValidation } from '@mui/x-date-pickers/validation'; +import { validateDateRange } from '@mui/x-date-pickers-pro/validation'; +import { + useSplitFieldProps, + useParsedFormat, + usePickersContext, +} from '@mui/x-date-pickers/hooks'; + +function ButtonDateRangeField(props) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + const { value, timezone, format } = internalProps; + const { + InputProps, + slotProps, + slots, + ownerState, + label, + focused, + name, + ...other + } = forwardedProps; + + const pickersContext = usePickersContext(); + + const parsedFormat = useParsedFormat(internalProps); + const { hasValidationError } = useValidation({ + validator: validateDateRange, + value, + timezone, + props: internalProps, + }); + + const handleTogglePicker = (event) => { + if (pickersContext.open) { + pickersContext.onClose(event); + } else { + pickersContext.onOpen(event); + } + }; + + const formattedValue = (value ?? [null, null]) + .map((date) => (date == null ? parsedFormat : date.format(format))) + .join(' – '); + + return ( + + ); +} + +// TODO v8: Will be removed before the end of the alpha since single input will become the default field. +ButtonDateRangeField.fieldType = 'single-input'; + +function ButtonFieldDateRangePicker(props) { + return ( + + ); +} + +export default function MaterialDateRangePicker() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx new file mode 100644 index 0000000000000..bc154d9a2b9f5 --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx @@ -0,0 +1,86 @@ +import * as React from 'react'; +import { Dayjs } from 'dayjs'; +import Button from '@mui/material/Button'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { + DateRangePicker, + DateRangePickerProps, + DateRangePickerFieldProps, +} from '@mui/x-date-pickers-pro/DateRangePicker'; +import { useValidation } from '@mui/x-date-pickers/validation'; +import { validateDateRange } from '@mui/x-date-pickers-pro/validation'; +import { + useSplitFieldProps, + useParsedFormat, + usePickersContext, +} from '@mui/x-date-pickers/hooks'; + +function ButtonDateRangeField(props: DateRangePickerFieldProps) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + const { value, timezone, format } = internalProps; + const { + InputProps, + slotProps, + slots, + ownerState, + label, + focused, + name, + ...other + } = forwardedProps; + + const pickersContext = usePickersContext(); + + const parsedFormat = useParsedFormat(internalProps); + const { hasValidationError } = useValidation({ + validator: validateDateRange, + value, + timezone, + props: internalProps, + }); + + const handleTogglePicker = (event: React.UIEvent) => { + if (pickersContext.open) { + pickersContext.onClose(event); + } else { + pickersContext.onOpen(event); + } + }; + + const formattedValue = (value ?? [null, null]) + .map((date) => (date == null ? parsedFormat : date.format(format))) + .join(' – '); + + return ( + + ); +} + +// TODO v8: Will be removed before the end of the alpha since single input will become the default field. +ButtonDateRangeField.fieldType = 'single-input'; + +function ButtonFieldDateRangePicker(props: DateRangePickerProps) { + return ( + + ); +} + +export default function MaterialDateRangePicker() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx.preview b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx.preview new file mode 100644 index 0000000000000..6512674c0bb6b --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.js b/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.js new file mode 100644 index 0000000000000..0723d4565c514 --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.js @@ -0,0 +1,164 @@ +import * as React from 'react'; +import dayjs from 'dayjs'; +import { useRifm } from 'rifm'; +import TextField from '@mui/material/TextField'; +import useControlled from '@mui/utils/useControlled'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import { useSplitFieldProps, useParsedFormat } from '@mui/x-date-pickers/hooks'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; + +const MASK_USER_INPUT_SYMBOL = '_'; +const ACCEPT_REGEX = /[\d]/gi; + +const staticDateWith2DigitTokens = dayjs('2019-11-21T11:30:00.000'); +const staticDateWith1DigitTokens = dayjs('2019-01-01T09:00:00.000'); + +function getValueStrFromValue(value, format) { + if (value == null) { + return ''; + } + + return value.isValid() ? value.format(format) : ''; +} + +function MaskedField(props) { + const { slots, slotProps, ...other } = props; + + const { forwardedProps, internalProps } = useSplitFieldProps(other, 'date'); + + const { + format, + value: valueProp, + defaultValue, + onChange, + timezone, + onError, + } = internalProps; + + const [value, setValue] = useControlled({ + controlled: valueProp, + default: defaultValue ?? null, + name: 'MaskedField', + state: 'value', + }); + + // Control the input text + const [inputValue, setInputValue] = React.useState(() => + getValueStrFromValue(value, format), + ); + + React.useEffect(() => { + if (value && value.isValid()) { + const newDisplayDate = getValueStrFromValue(value, format); + setInputValue(newDisplayDate); + } + }, [format, value]); + + const parsedFormat = useParsedFormat(internalProps); + + const { hasValidationError, getValidationErrorForNewValue } = useValidation({ + value, + timezone, + onError, + props: internalProps, + validator: validateDate, + }); + + const handleValueStrChange = (newValueStr) => { + setInputValue(newValueStr); + + const newValue = dayjs(newValueStr, format); + setValue(newValue); + + if (onChange) { + onChange(newValue, { + validationError: getValidationErrorForNewValue(newValue), + }); + } + }; + + const rifmFormat = React.useMemo(() => { + const formattedDateWith1Digit = staticDateWith1DigitTokens.format(format); + const inferredFormatPatternWith1Digits = formattedDateWith1Digit.replace( + ACCEPT_REGEX, + MASK_USER_INPUT_SYMBOL, + ); + const inferredFormatPatternWith2Digits = staticDateWith2DigitTokens + .format(format) + .replace(ACCEPT_REGEX, '_'); + + if (inferredFormatPatternWith1Digits !== inferredFormatPatternWith2Digits) { + throw new Error( + `Mask does not support numbers with variable length such as 'M'.`, + ); + } + + const maskToUse = inferredFormatPatternWith1Digits; + + return function formatMaskedDate(valueToFormat) { + let outputCharIndex = 0; + return valueToFormat + .split('') + .map((character, characterIndex) => { + ACCEPT_REGEX.lastIndex = 0; + + if (outputCharIndex > maskToUse.length - 1) { + return ''; + } + + const maskChar = maskToUse[outputCharIndex]; + const nextMaskChar = maskToUse[outputCharIndex + 1]; + + const acceptedChar = ACCEPT_REGEX.test(character) ? character : ''; + const formattedChar = + maskChar === MASK_USER_INPUT_SYMBOL + ? acceptedChar + : maskChar + acceptedChar; + + outputCharIndex += formattedChar.length; + + const isLastCharacter = characterIndex === valueToFormat.length - 1; + if ( + isLastCharacter && + nextMaskChar && + nextMaskChar !== MASK_USER_INPUT_SYMBOL + ) { + // when cursor at the end of mask part (e.g. month) prerender next symbol "21" -> "21/" + return formattedChar ? formattedChar + nextMaskChar : ''; + } + + return formattedChar; + }) + .join(''); + }; + }, [format]); + + const rifmProps = useRifm({ + value: inputValue, + onChange: handleValueStrChange, + format: rifmFormat, + }); + + return ( + + ); +} + +function MaskedFieldDatePicker(props) { + return ; +} + +export default function MaskedMaterialTextField() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.tsx b/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.tsx new file mode 100644 index 0000000000000..a589022eada63 --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.tsx @@ -0,0 +1,168 @@ +import * as React from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import { useRifm } from 'rifm'; +import TextField from '@mui/material/TextField'; +import useControlled from '@mui/utils/useControlled'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { + DatePicker, + DatePickerProps, + DatePickerFieldProps, +} from '@mui/x-date-pickers/DatePicker'; +import { useSplitFieldProps, useParsedFormat } from '@mui/x-date-pickers/hooks'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; + +const MASK_USER_INPUT_SYMBOL = '_'; +const ACCEPT_REGEX = /[\d]/gi; + +const staticDateWith2DigitTokens = dayjs('2019-11-21T11:30:00.000'); +const staticDateWith1DigitTokens = dayjs('2019-01-01T09:00:00.000'); + +function getValueStrFromValue(value: Dayjs | null, format: string) { + if (value == null) { + return ''; + } + + return value.isValid() ? value.format(format) : ''; +} + +function MaskedField(props: DatePickerFieldProps) { + const { slots, slotProps, ...other } = props; + + const { forwardedProps, internalProps } = useSplitFieldProps(other, 'date'); + + const { + format, + value: valueProp, + defaultValue, + onChange, + timezone, + onError, + } = internalProps; + + const [value, setValue] = useControlled({ + controlled: valueProp, + default: defaultValue ?? null, + name: 'MaskedField', + state: 'value', + }); + + // Control the input text + const [inputValue, setInputValue] = React.useState(() => + getValueStrFromValue(value, format), + ); + + React.useEffect(() => { + if (value && value.isValid()) { + const newDisplayDate = getValueStrFromValue(value, format); + setInputValue(newDisplayDate); + } + }, [format, value]); + + const parsedFormat = useParsedFormat(internalProps); + + const { hasValidationError, getValidationErrorForNewValue } = useValidation({ + value, + timezone, + onError, + props: internalProps, + validator: validateDate, + }); + + const handleValueStrChange = (newValueStr: string) => { + setInputValue(newValueStr); + + const newValue = dayjs(newValueStr, format); + setValue(newValue); + + if (onChange) { + onChange(newValue, { + validationError: getValidationErrorForNewValue(newValue), + }); + } + }; + + const rifmFormat = React.useMemo(() => { + const formattedDateWith1Digit = staticDateWith1DigitTokens.format(format); + const inferredFormatPatternWith1Digits = formattedDateWith1Digit.replace( + ACCEPT_REGEX, + MASK_USER_INPUT_SYMBOL, + ); + const inferredFormatPatternWith2Digits = staticDateWith2DigitTokens + .format(format) + .replace(ACCEPT_REGEX, '_'); + + if (inferredFormatPatternWith1Digits !== inferredFormatPatternWith2Digits) { + throw new Error( + `Mask does not support numbers with variable length such as 'M'.`, + ); + } + + const maskToUse = inferredFormatPatternWith1Digits; + + return function formatMaskedDate(valueToFormat: string) { + let outputCharIndex = 0; + return valueToFormat + .split('') + .map((character, characterIndex) => { + ACCEPT_REGEX.lastIndex = 0; + + if (outputCharIndex > maskToUse.length - 1) { + return ''; + } + + const maskChar = maskToUse[outputCharIndex]; + const nextMaskChar = maskToUse[outputCharIndex + 1]; + + const acceptedChar = ACCEPT_REGEX.test(character) ? character : ''; + const formattedChar = + maskChar === MASK_USER_INPUT_SYMBOL + ? acceptedChar + : maskChar + acceptedChar; + + outputCharIndex += formattedChar.length; + + const isLastCharacter = characterIndex === valueToFormat.length - 1; + if ( + isLastCharacter && + nextMaskChar && + nextMaskChar !== MASK_USER_INPUT_SYMBOL + ) { + // when cursor at the end of mask part (e.g. month) prerender next symbol "21" -> "21/" + return formattedChar ? formattedChar + nextMaskChar : ''; + } + + return formattedChar; + }) + .join(''); + }; + }, [format]); + + const rifmProps = useRifm({ + value: inputValue, + onChange: handleValueStrChange, + format: rifmFormat, + }); + + return ( + + ); +} + +function MaskedFieldDatePicker(props: DatePickerProps) { + return ; +} + +export default function MaskedMaterialTextField() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.tsx.preview b/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.tsx.preview new file mode 100644 index 0000000000000..1340a82dd55df --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.js b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js similarity index 97% rename from docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.js rename to docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js index b5050747ff1ff..5f1cdc46e7320 100644 --- a/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.js +++ b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js @@ -58,7 +58,7 @@ function ReadOnlyFieldDatePicker(props) { ); } -export default function ReadOnlyMaterialTextField() { +export default function MaterialDatePicker() { return ( diff --git a/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx similarity index 93% rename from docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx rename to docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx index c9cdc16eae4e9..d8a54a2bebdb2 100644 --- a/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx +++ b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx @@ -16,7 +16,7 @@ import { } from '@mui/x-date-pickers/hooks'; import { CalendarIcon } from '@mui/x-date-pickers/icons'; -function ReadOnlyDateField(props: DatePickerFieldProps) { +function ReadOnlyDateField(props: DatePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, format } = internalProps; const { InputProps, slotProps, slots, ...other } = forwardedProps; @@ -62,7 +62,7 @@ function ReadOnlyFieldDatePicker(props: DatePickerProps) { ); } -export default function ReadOnlyMaterialTextField() { +export default function MaterialDatePicker() { return ( diff --git a/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx.preview b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx.preview similarity index 100% rename from docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx.preview rename to docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx.preview diff --git a/docs/data/date-pickers/custom-field/custom-field.md b/docs/data/date-pickers/custom-field/custom-field.md index 4f5b5f782fb35..c8e92edb523c9 100644 --- a/docs/data/date-pickers/custom-field/custom-field.md +++ b/docs/data/date-pickers/custom-field/custom-field.md @@ -8,10 +8,10 @@ components: PickersSectionList, PickersTextField # Custom field -

The Date and Time Pickers let you customize the field by passing props or custom components

+

The Date and Time Pickers let you customize the field by passing props or custom components.

:::success -See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +See [Common concepts—Slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. ::: ## Customize the default field @@ -66,30 +66,9 @@ Setting `formatDensity` to `"spacious"` will add a space before and after each ` {{"demo": "FieldFormatDensity.js"}} -## Usage with Material UI +## With Material UI -### Using Material `TextField` - -You can import the `TextField` component to create custom wrappers: - -{{"demo": "MaterialV6FieldWrapped.js"}} - -:::success -This approach is only recommended if you need complex customizations on your `TextField`, -or if you already have a wrapper also used outside the Date and Time Pickers. - -If you just need to set some default props, you can use [the `slotProps` prop](/x/react-date-pickers/custom-field/#customize-the-textfield). -::: - -### Using Material `PickersTextField` - -Pass the `enableAccessibleFieldDOMStructure` to any Field or Picker component to enable the accessible DOM structure: - -{{"demo": "MaterialV7Field.js"}} - -:::success -Learn more about the [accessible DOM structure](/x/react-date-pickers/fields/#accessible-dom-structure). -::: +### Wrapping `PickersTextField` You can import the `PickersTextField` component to create custom wrappers: @@ -101,80 +80,80 @@ This approach is only recommended if you need complex customizations on your `Pi If you just need to set some default props, you can use [the `slotProps` prop](/x/react-date-pickers/custom-field/#customize-the-textfield). ::: -## Usage with Joy UI +### Using Material `TextField` -### Using Joy `Input` +Pass the `enableAccessibleFieldDOMStructure={false}` to any Field or Picker component to use an `` for the editing instead of the new accessible DOM structure: -You can use the [Joy UI](https://mui.com/joy-ui/getting-started/) components instead of the Material UI ones: +{{"demo": "MaterialV6Field.js"}} -:::info -A higher-level solution for _Joy UI_ will be provided in the near future for even simpler usage. +:::warning +The non-accessible DOM structure will be deprecated in a follow up minor version and remove in `v9.x`. +If you are unable to migrate for some reason, please open an issue to describe what is missing from the new DOM structure so that we can improve it before dropping the old one. ::: -{{"demo": "JoyV6Field.js", "defaultCodeOpen": false}} - -{{"demo": "JoyV6SingleInputRangeField.js", "defaultCodeOpen": false}} +## With another Design System -{{"demo": "JoyV6MultiInputRangeField.js", "defaultCodeOpen": false}} - -### Using Joy `PickersTextField` +### Using a custom input :::warning -This component is not available yet. +You will need to use a component that supports the `sx` prop as a wrapper for your input +to be able to benefit from the **hover** and **focus** behavior of the clear button. +You will have access to the `clearable` and `onClear` props using native HTML elements, +but the on **focus** and **hover** behavior depends on styles applied via the `sx` prop. ::: -## Usage with an unstyled input +{{"demo": "BrowserV7Field.js", "defaultCodeOpen": false}} -### Using the browser input +{{"demo": "BrowserV7SingleInputRangeField.js", "defaultCodeOpen": false}} -{{"demo": "BrowserV6Field.js", "defaultCodeOpen": false}} +{{"demo": "BrowserV7MultiInputRangeField.js", "defaultCodeOpen": false}} -{{"demo": "BrowserV6SingleInputRangeField.js", "defaultCodeOpen": false}} +### Using Joy UI -{{"demo": "BrowserV6MultiInputRangeField.js", "defaultCodeOpen": false}} +You can use the [Joy UI](https://mui.com/joy-ui/getting-started/) components instead of the Material UI ones: -:::warning -You will need to use a component that supports the `sx` prop as a wrapper for your input, in order to be able to benefit from the **hover** and **focus** behavior of the clear button. You will have access to the `clearable` and `onClear` props using native HTML elements, but the on **focus** and **hover** behavior depends on styles applied via the `sx` prop. -::: +{{"demo": "JoyV6Field.js", "defaultCodeOpen": false}} -### Using custom `PickersTextField` +{{"demo": "JoyV6SingleInputRangeField.js", "defaultCodeOpen": false}} -:::success -Learn more about the accessible DOM structure and its difference compared to the current one on the [dedicated doc section](/x/react-date-pickers/fields/#accessible-dom-structure). +{{"demo": "JoyV6MultiInputRangeField.js", "defaultCodeOpen": false}} + +:::warning +All the Joy UI examples use the non-accessible DOM structure. +The new accessible DOM structure will become compatible with Joy UI in the future. ::: -{{"demo": "BrowserV7Field.js", "defaultCodeOpen": false}} +## With a custom editing experience -{{"demo": "BrowserV7SingleInputRangeField.js", "defaultCodeOpen": false}} +### Using an Autocomplete -{{"demo": "BrowserV7MultiInputRangeField.js", "defaultCodeOpen": false}} +If your user can only select a value in a small list of available dates, you can replace the field with the [Autocomplete](/material-ui/react-autocomplete/) component to list those dates: -## Usage with another UI +{{"demo": "behavior-autocomplete/MaterialDatePicker.js", "defaultCodeOpen": false}} -### Using an `Autocomplete` +### Using a masked Text Field -If your user can only select a value in a small list of available dates, -you can replace the field with an `Autocomplete` listing those dates: +If you want to use a simple mask approach for the field editing instead of the built-in logic, you can replace the default field with the [TextField](/material-ui/react-text-field/) component using a masked input value built with the [rifm](https://github.com/realadvisor/rifm) package. -{{"demo": "PickerWithAutocompleteField.js", "defaultCodeOpen": false}} +{{"demo": "behavior-masked-text-field/MaskedMaterialTextField.js", "defaultCodeOpen": false}} -### Using a read-only `TextField` +### Using a read-only Text Field If you want users to select a value exclusively through the views -but you still want the UI to look like a `TextField`, you can replace the field with a read-only `TextField`: +but you still want the UI to look like a Text Field, you can replace the field with a read-only [Text Field](/material-ui/react-text-field/) component: -{{"demo": "custom-behavior/ReadOnlyMaterialTextField.js", "defaultCodeOpen": false}} +{{"demo": "behavior-read-only-text-field/MaterialDatePicker.js", "defaultCodeOpen": false}} -### Using a `Button` +### Using a Button If you want users to select a value exclusively through the views -and you don't want the UI to look like a `TextField`, you can replace the field with a `Button`: +and you don't want the UI to look like a Text Field, you can replace the field with the [Button](/material-ui/react-button/) component: -{{"demo": "PickerWithButtonField.js", "defaultCodeOpen": false}} +{{"demo": "behavior-button/MaterialDatePicker.js", "defaultCodeOpen": false}} -The same can be applied to the `DateRangePicker`: +The same logic can be applied to any Range Picker: -{{"demo": "DateRangePickerWithButtonField.js", "defaultCodeOpen": false}} +{{"demo": "behavior-button/MaterialDateRangePicker.js", "defaultCodeOpen": false}} ## How to build a custom field @@ -184,22 +163,22 @@ On the examples below, you can see that the typing of the props received by a cu ```tsx interface JoyDateFieldProps - extends UseDateFieldProps, // The headless field props + extends UseDateFieldProps, // The headless field props BaseSingleInputFieldProps< Dayjs | null, Dayjs, FieldSection, - false, // `true` for `enableAccessibleFieldDOMStructure` + true, // `false` for `enableAccessibleFieldDOMStructure={false}` DateValidationError > {} // The DOM field props interface JoyDateTimeFieldProps - extends UseDateTimeFieldProps, // The headless field props + extends UseDateTimeFieldProps, // The headless field props BaseSingleInputFieldProps< Dayjs | null, Dayjs, FieldSection, - false, // `true` for `enableAccessibleFieldDOMStructure` + true, // `false` for `enableAccessibleFieldDOMStructure={false}` DateTimeValidationError > {} // The DOM field props ``` diff --git a/docs/data/date-pickers/custom-layout/custom-layout.md b/docs/data/date-pickers/custom-layout/custom-layout.md index 421e80851236c..ca327aaa57b69 100644 --- a/docs/data/date-pickers/custom-layout/custom-layout.md +++ b/docs/data/date-pickers/custom-layout/custom-layout.md @@ -8,10 +8,10 @@ packageName: '@mui/x-date-pickers' # Custom layout -

The Date and Time Pickers let you reorganize the layout

+

The Date and Time Pickers let you reorganize the layout.

:::success -See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +See [Common concepts—Slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. ::: ## Default layout structure diff --git a/docs/data/date-pickers/custom-opening-button/custom-opening-button.md b/docs/data/date-pickers/custom-opening-button/custom-opening-button.md index 2e71fe686d5b3..21f4b44eeda54 100644 --- a/docs/data/date-pickers/custom-opening-button/custom-opening-button.md +++ b/docs/data/date-pickers/custom-opening-button/custom-opening-button.md @@ -8,7 +8,7 @@ title: Date and Time Pickers - Custom opening button

The date picker lets you customize the button to open the views.

:::success -See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +See [Common concepts—Slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. ::: ## Set a custom opening icon diff --git a/docs/data/date-pickers/experimentation/experimentation.md b/docs/data/date-pickers/experimentation/experimentation.md index 88b3ea3fe5ad2..ddecd52bb1e9b 100644 --- a/docs/data/date-pickers/experimentation/experimentation.md +++ b/docs/data/date-pickers/experimentation/experimentation.md @@ -4,4 +4,4 @@ productId: x-date-pickers # Date and Time Pickers experimentation -

Demos not accessible through the navbar of the doc

+

Demos not accessible through the navbar of the doc.

diff --git a/docs/data/date-pickers/fields/BasicV7DOMStructure.js b/docs/data/date-pickers/fields/BasicV7DOMStructure.js index 1f32eca967da6..62c0924c3c90d 100644 --- a/docs/data/date-pickers/fields/BasicV7DOMStructure.js +++ b/docs/data/date-pickers/fields/BasicV7DOMStructure.js @@ -8,7 +8,7 @@ export default function BasicV7DOMStructure() { return ( - + ); diff --git a/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx b/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx index 1f32eca967da6..62c0924c3c90d 100644 --- a/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx +++ b/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx @@ -8,7 +8,7 @@ export default function BasicV7DOMStructure() { return ( - + ); diff --git a/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx.preview b/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx.preview index f33bc6c8fdc7e..2dc079345c091 100644 --- a/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx.preview +++ b/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx.preview @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/data/date-pickers/fields/fields.md b/docs/data/date-pickers/fields/fields.md index 5f54b1411f66e..9bd7260fff468 100644 --- a/docs/data/date-pickers/fields/fields.md +++ b/docs/data/date-pickers/fields/fields.md @@ -25,201 +25,6 @@ All fields to edit a range are available in a single input version and in a mult {{"demo": "DateRangeFieldExamples.js", "defaultCodeOpen": false}} -## Accessible DOM structure - -By default, the fields' DOM structure consists of an ``, which holds the whole value for the component, but unfortunately presents a few limitations in terms of accessibility when managing multiple section values. - -From v7 version, you can opt-in for a new and experimental DOM structure on any field or picker component using the `enableAccessibleFieldDOMStructure` prop. - -```tsx - - - -``` - -This new feature allows the field component to set aria attributes on individual sections, providing a far better experience with screen readers. - -{{"demo": "BasicV7DOMStructure.js", "defaultCodeOpen": false }} - -### Usage with `slotProps.field` - -When using `slotProps.field` to pass props to your field component, -the field consumes some props (e.g: `shouldRespectLeadingZeros`) and forwards the rest to the `TextField`. - -- For the props consumed by the field, the behavior should remain exactly the same with both DOM structures. - - Both components below will respect the leading zeroes on digit sections: - - ```js - - - ``` - -- For the props forwarded to the `TextField`, - you can have a look at the next section to see how the migration impact them. - - Both components below will render a small size UI: - - ```js - - - ``` - -### Usage with `slotProps.textField` - -If you are passing props to `slotProps.textField`, -these props will now be received by `PickersTextField` and should keep working the same way as before. - -Both components below will render a small size UI: - -```js - - -``` - -:::info -If you are passing `inputProps` to `slotProps.textField`, -these props will now be passed to the hidden `` element. -::: - -### Usage with `slots.field` - -If you are passing a custom field component to your pickers, you need to create a new one that is using the accessible DOM structure. -This new component will need to use the `PickersSectionList` component instead of an `` HTML element. - -You can have a look at the [custom PickersTextField](/x/react-date-pickers/custom-field/#using-custom-pickerstextfield) to have a concrete example. - -:::info -If your custom field was used to create a Joy UI design component, -you may want to wait a few weeks for the release of an out-of-the-box Joy `PickersTextField` component instead of implementing it yourself. -::: - -### Usage with `slots.textField` - -If you are passing a custom `TextField` component to your fields and pickers, -you need to create a new one that is using the accessible DOM structure. - -You can have a look at the second demo of the [Material PickersTextField section](/x/react-date-pickers/custom-field/#using-material-pickerstextfield) to have a concrete example. - -:::info -If your custom `TextField` was used to apply a totally different input that did not use `@mui/material/TextField`, -please consider having a look at the [custom PickersTextField](/x/react-date-pickers/custom-field/#using-custom-pickerstextfield) section which uses `slots.field`. -This approach can be more appropriate for deeper changes. -::: - -### Usage with theme - -If you are using the theme to customize `MuiTextField`, -you need to pass the same config to `MuiPickersTextField`: - -```js -const theme = createTheme({ - components: { - MuiTextField: { - defaultProps: { - variant: 'outlined', - }, - styleOverrides: { - root: { - '& .MuiInputLabel-outlined.Mui-focused': { - color: 'red', - }, - }, - }, - }, - MuiPickersTextField: { - defaultProps: { - variant: 'outlined', - }, - styleOverrides: { - root: { - '& .MuiInputLabel-outlined.Mui-focused': { - color: 'red', - }, - }, - }, - }, - }, -}); -``` - -If you are using the theme to customize `MuiInput`, `MuiOutlinedInput` or `MuiFilledInput`, -you need to pass the same config to `MuiPickersInput`, `MuiPickersOutlinedInput` or `MuiPickersFilledInput`: - -```js -const theme = createTheme({ - components: { - // Replace with `MuiOutlinedInput` or `MuiFilledInput` if needed - MuiInput: { - defaultProps: { - margin: 'dense', - }, - styleOverrides: { - root: { - color: 'red', - }, - }, - }, - // Replace with `MuiPickersOutlinedInput` or `MuiPickersFilledInput` if needed - MuiPickersInput: { - defaultProps: { - margin: 'dense', - }, - styleOverrides: { - root: { - color: 'red', - }, - }, - }, - }, -}); -``` - -If you are using the theme to customize `MuiInputBase`, -you need to pass the same config to `MuiPickersInputBase`: - -```js -const theme = createTheme({ - components: { - MuiInputBase: { - defaultProps: { - margin: 'dense', - }, - styleOverrides: { - root: { - color: 'red', - }, - }, - }, - MuiPickersInputBase: { - defaultProps: { - margin: 'dense', - }, - styleOverrides: { - root: { - color: 'red', - }, - }, - }, - }, -}); -``` - ## Advanced ### What is a section? diff --git a/docs/data/date-pickers/localization/data.json b/docs/data/date-pickers/localization/data.json index f8d4e61e2317e..14a69bf378d62 100644 --- a/docs/data/date-pickers/localization/data.json +++ b/docs/data/date-pickers/localization/data.json @@ -107,7 +107,7 @@ "languageTag": "el-GR", "importName": "elGR", "localeName": "Greek", - "missingKeysCount": 14, + "missingKeysCount": 6, "totalKeysCount": 50, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/elGR.ts" }, @@ -203,7 +203,7 @@ "languageTag": "pl-PL", "importName": "plPL", "localeName": "Polish", - "missingKeysCount": 22, + "missingKeysCount": 10, "totalKeysCount": 50, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/plPL.ts" }, diff --git a/docs/data/date-pickers/localization/localization.md b/docs/data/date-pickers/localization/localization.md index 9c17517d832af..dab97f4bcb5dd 100644 --- a/docs/data/date-pickers/localization/localization.md +++ b/docs/data/date-pickers/localization/localization.md @@ -46,7 +46,7 @@ function App({ children }) { } ``` -Note that `createTheme` accepts any number of arguments. +Note that `createTheme()` accepts any number of arguments. If you are already using the [translations of the core components](/material-ui/guides/localization/#locale-text) or the [translations of the Data Grid](/x/react-data-grid/localization/#locale-text), you can add `deDE` as a new argument. ```jsx @@ -73,7 +73,7 @@ function App({ children }) { ### Using LocalizationProvider -If you want to pass language translations without using `createTheme` and `ThemeProvider`, +If you want to pass language translations without using `createTheme()` and `ThemeProvider`, you can directly load the language translations from the `@mui/x-date-pickers` or `@mui/x-date-pickers-pro` package and pass them to the `LocalizationProvider`. ```jsx diff --git a/docs/data/date-pickers-component-api-pages.ts b/docs/data/datePickersApiPages.ts similarity index 98% rename from docs/data/date-pickers-component-api-pages.ts rename to docs/data/datePickersApiPages.ts index f7d4b339ac4fb..339472cbea73d 100644 --- a/docs/data/date-pickers-component-api-pages.ts +++ b/docs/data/datePickersApiPages.ts @@ -1,6 +1,6 @@ import type { MuiPage } from 'docs/src/MuiPage'; -const apiPages: MuiPage[] = [ +const datePickersApiPages: MuiPage[] = [ { pathname: '/x/api/date-pickers/date-calendar', title: 'DateCalendar', @@ -233,4 +233,4 @@ const apiPages: MuiPage[] = [ title: 'YearCalendar', }, ]; -export default apiPages; +export default datePickersApiPages; diff --git a/docs/data/introduction/support/support.md b/docs/data/introduction/support/support.md index 3f65903d7dfbf..ebba1a99214b5 100644 --- a/docs/data/introduction/support/support.md +++ b/docs/data/introduction/support/support.md @@ -42,7 +42,7 @@ You can browse the documentation, find an example close to your use case, and th - [Data Grid](/x/react-data-grid/#mit-license-free-forever) - [Date Pickers](/x/react-date-pickers/getting-started/#render-your-first-component) -- [Charts](/x/react-charts/getting-started/#single-charts) +- [Charts](/x/react-charts/getting-started/#self-contained-charts) - [Tree View](/x/react-tree-view/#simple-tree-view) #### Use starter templates diff --git a/docs/data/migration/migration-charts-v7/migration-charts-v7.md b/docs/data/migration/migration-charts-v7/migration-charts-v7.md new file mode 100644 index 0000000000000..a23f9406feff9 --- /dev/null +++ b/docs/data/migration/migration-charts-v7/migration-charts-v7.md @@ -0,0 +1,34 @@ +--- +productId: x-charts +--- + +# Migration from v7 to v8 + +

This guide describes the changes needed to migrate Charts from v7 to v8.

+ +## Introduction + +This is a reference guide for upgrading `@mui/x-charts` from v7 to v8. +The change between v7 and v8 is mostly here to match the version with other MUI X packages. +No big breaking changes are expected. + +## Start using the new release + +In `package.json`, change the version of the charts package to `next`. + +```diff +-"@mui/x-charts": "^7.0.0", ++"@mui/x-charts": "next", +``` + +Using `next` ensures that it will always use the latest v8 pre-release version, but you can also use a fixed version, like `8.0.0-alpha.0`. + +## Breaking changes + +Since v8 is a major release, it contains some changes that affect the public API. +These changes were done for consistency, improve stability and make room for new features. +Below are described the steps you need to make to migrate from v7 to v8. + +:::info +The list is currently empty, but as we move forward with development during the alpha and beta phases, we'll feed this page with all changes in the API. +::: diff --git a/docs/data/migration/migration-data-grid-v7/migration-data-grid-v7.md b/docs/data/migration/migration-data-grid-v7/migration-data-grid-v7.md new file mode 100644 index 0000000000000..eea548d0fbc03 --- /dev/null +++ b/docs/data/migration/migration-data-grid-v7/migration-data-grid-v7.md @@ -0,0 +1,78 @@ +--- +productId: x-data-grid +--- + +# Migration from v7 to v8 + +

This guide describes the changes needed to migrate the Data Grid from v7 to v8.

+ +## Introduction + +This is a reference guide for upgrading `@mui/x-data-grid` from v7 to v8. + +## Start using the new release + +In `package.json`, change the version of the Data Grid package to `next`. + +```diff +-"@mui/x-data-grid": "^7.0.0", ++"@mui/x-data-grid": "next", + +-"@mui/x-data-grid-pro": "^7.0.0", ++"@mui/x-data-grid-pro": "next", + +-"@mui/x-data-grid-premium": "^7.0.0", ++"@mui/x-data-grid-premium": "next", +``` + +Using `next` ensures that it will always use the latest v8 pre-release version, but you can also use a fixed version, like `8.0.0-alpha.0`. + +## Breaking changes + +Since v8 is a major release, it contains some changes that affect the public API. +These changes were done for consistency, improve stability and make room for new features. +Below are described the steps you need to make to migrate from v7 to v8. + +:::info +The list is currently empty, but as we move forward with development during the alpha and beta phases, we'll feed this page with all changes in the API. +::: + + diff --git a/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md b/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md index 787bbefb3182d..ab73ca9e1900a 100644 --- a/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md +++ b/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md @@ -440,7 +440,7 @@ The headless field hooks (e.g.: `useDateField`) now return a new prop called `en This is used to know if the current UI expected is built using the accessible DOM structure or not. :::info -See [Fields—Accessible DOM structure](/x/react-date-pickers/fields/#accessible-dom-structure) for more details. +See [Migration from v7 to v8—New DOM structure for the field](/x/migration/migration-pickers-v7/#new-dom-structure-for-the-field) for more details. ::: When building a custom UI, you are most-likely only supporting one DOM structure, so you can remove `enableAccessibleFieldDOMStructure` before it is passed to the DOM: diff --git a/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md new file mode 100644 index 0000000000000..a7c30b0a931ba --- /dev/null +++ b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md @@ -0,0 +1,422 @@ +--- +productId: x-date-pickers +--- + +# Migration from v7 to v8 + +

This guide describes the changes needed to migrate the Date and Time Pickers from v7 to v8.

+ +## Introduction + +This is a reference guide for upgrading `@mui/x-date-pickers` from v7 to v8. + +## Start using the new release + +In `package.json`, change the version of the date pickers package to `next`. + +```diff +-"@mui/x-date-pickers": "7.x.x", ++"@mui/x-date-pickers": "next", +``` + +Using `next` ensures that it will always use the latest v8 pre-release version, but you can also use a fixed version, like `8.0.0-alpha.0`. + +Since `v8` is a major release, it contains changes that affect the public API. +These changes were done for consistency, improved stability and to make room for new features. +Described below are the steps needed to migrate from v7 to v8. + +## Run codemods + +The `preset-safe` codemod will automatically adjust the bulk of your code to account for breaking changes in v8. You can run `v8.0.0/pickers/preset-safe` targeting only Date and Time Pickers or `v8.0.0/preset-safe` to target the other packages as well. + +You can either run it on a specific file, folder, or your entire codebase when choosing the `` argument. + + + +```bash +// Date and Time Pickers specific +npx @mui/x-codemod@latest v8.0.0/pickers/preset-safe + +// Target the other packages as well +npx @mui/x-codemod@latest v8.0.0/preset-safe +``` + +:::info +If you want to run the transformers one by one, check out the transformers included in the [preset-safe codemod for pickers](https://github.com/mui/mui-x/blob/HEAD/packages/x-codemod/README.md#preset-safe-for-pickers-v800) for more details. +::: + +Breaking changes that are handled by this codemod are denoted by a ✅ emoji in the table of contents on the right side of the screen. + +If you have already applied the `v8.0.0/pickers/preset-safe` (or `v8.0.0/preset-safe`) codemod, then you should not need to take any further action on these items. + +All other changes must be handled manually. + +:::warning +Not all use cases are covered by codemods. In some scenarios, like props spreading, cross-file dependencies, etc., the changes are not properly identified and therefore must be handled manually. + +For example, if a codemod tries to rename a prop, but this prop is hidden with the spread operator, it won't be transformed as expected. + +```tsx + +``` + +After running the codemods, make sure to test your application and that you don't have any console errors. + +Feel free to [open an issue](https://github.com/mui/mui-x/issues/new/choose) for support if you need help to proceed with your migration. +::: + +## New DOM structure for the field + +Before version `v8.x`, the fields' DOM structure consisted of an ``, which held the whole value for the component, +but unfortunately presents a few limitations in terms of accessibility when managing multiple section values. + +Starting with version `v8.x`, all the field and picker components come with a new DOM structure that allows the field component to set aria attributes on individual sections, providing a far better experience with screen readers. + +### Fallback to the non-accessible DOM structure + +```tsx + + + +``` + +### Migrate `slotProps.field` + +When using `slotProps.field` to pass props to your field component, +the field consumes some props (e.g: `shouldRespectLeadingZeros`) and forwards the rest to the `TextField`. + +- For the props consumed by the field, the behavior should remain exactly the same with both DOM structures. + + Both components below will respect the leading zeroes on digit sections: + + ```js + + + ``` + +- For the props forwarded to the `TextField`, + you can have a look at the next section to see how the migration impact them. + + Both components below will render a small size UI: + + ```js + + + ``` + +### Migrate `slotProps.textField` + +If you are passing props to `slotProps.textField`, +these props will now be received by `PickersTextField` and should keep working the same way as before. + +Both components below will render a small size UI: + +```js + + +``` + +:::info +If you are passing `inputProps` to `slotProps.textField`, +these props will now be passed to the hidden `` element. +::: + +### Migrate `slots.field` + +If you are passing a custom field component to your pickers, you need to create a new one that is using the accessible DOM structure. +This new component will need to use the `PickersSectionList` component instead of an `` HTML element. + +You can have a look at the [Using a custom input](/x/react-date-pickers/custom-field/#using-a-custom-input) to have a concrete example. + +:::info +If your custom field was used to create a Joy UI design component, +you may want to wait a few weeks for the release of an out-of-the-box Joy `PickersTextField` component instead of implementing it yourself. +::: + +### Migrate `slots.textField` + +If you are passing a custom `TextField` component to your fields and pickers, +you need to create a new one that is using the accessible DOM structure. + +You can have a look at the second demo of the [Wrapping PickersTextField](/x/react-date-pickers/custom-field/#wrapping-pickerstextfield) to have a concrete example. + +:::info +If your custom `TextField` was used to apply a totally different input that did not use `@mui/material/TextField`, +please consider having a look at the [Using a custom input](/x/react-date-pickers/custom-field/#using-a-custom-input) section which uses `slots.field`. +This approach can be more appropriate for deeper changes. +::: + +### Migrate the theme + +If you are using the theme to customize `MuiTextField`, +you need to pass the same config to `MuiPickersTextField`: + +```js +const theme = createTheme({ + components: { + MuiTextField: { + defaultProps: { + variant: 'outlined', + }, + styleOverrides: { + root: { + '& .MuiInputLabel-outlined.Mui-focused': { + color: 'red', + }, + }, + }, + }, + MuiPickersTextField: { + defaultProps: { + variant: 'outlined', + }, + styleOverrides: { + root: { + '& .MuiInputLabel-outlined.Mui-focused': { + color: 'red', + }, + }, + }, + }, + }, +}); +``` + +If you are using the theme to customize `MuiInput`, `MuiOutlinedInput` or `MuiFilledInput`, +you need to pass the same config to `MuiPickersInput`, `MuiPickersOutlinedInput` or `MuiPickersFilledInput`: + +```js +const theme = createTheme({ + components: { + // Replace with `MuiOutlinedInput` or `MuiFilledInput` if needed + MuiInput: { + defaultProps: { + margin: 'dense', + }, + styleOverrides: { + root: { + color: 'red', + }, + }, + }, + // Replace with `MuiPickersOutlinedInput` or `MuiPickersFilledInput` if needed + MuiPickersInput: { + defaultProps: { + margin: 'dense', + }, + styleOverrides: { + root: { + color: 'red', + }, + }, + }, + }, +}); +``` + +If you are using the theme to customize `MuiInputBase`, +you need to pass the same config to `MuiPickersInputBase`: + +```js +const theme = createTheme({ + components: { + MuiInputBase: { + defaultProps: { + margin: 'dense', + }, + styleOverrides: { + root: { + color: 'red', + }, + }, + }, + MuiPickersInputBase: { + defaultProps: { + margin: 'dense', + }, + styleOverrides: { + root: { + color: 'red', + }, + }, + }, + }, +}); +``` + +## Removed types + +The following types are no longer exported by `@mui/x-date-pickers` and/or `@mui/x-date-pickers-pro`. +If you were using them, you need to replace them with the following code: + +- `UseDateFieldComponentProps` + + ```ts + import { UseDateFieldProps } from '@mui/x-date-pickers/DateField'; + import { PickerValidDate } from '@mui/x-date-pickers/models'; + + type UseDateFieldComponentProps< + TDate extends PickerValidDate, + TEnableAccessibleFieldDOMStructure extends boolean, + TChildProps extends {}, + > = Omit< + TChildProps, + keyof UseDateFieldProps + > & + UseDateFieldProps; + ``` + +- `UseTimeFieldComponentProps` + + ```ts + import { UseTimeFieldProps } from '@mui/x-date-pickers/TimeField'; + import { PickerValidDate } from '@mui/x-date-pickers/models'; + + type UseTimeFieldComponentProps< + TDate extends PickerValidDate, + TEnableAccessibleFieldDOMStructure extends boolean, + TChildProps extends {}, + > = Omit< + TChildProps, + keyof UseTimeFieldProps + > & + UseTimeFieldProps; + ``` + +- `UseDateTimeFieldComponentProps` + + ```ts + import { UseDateTimeFieldProps } from '@mui/x-date-pickers/DateTimeField'; + import { PickerValidDate } from '@mui/x-date-pickers/models'; + + type UseDateTimeFieldComponentProps< + TDate extends PickerValidDate, + TEnableAccessibleFieldDOMStructure extends boolean, + TChildProps extends {}, + > = Omit< + TChildProps, + keyof UseDateTimeFieldProps + > & + UseDateTimeFieldProps; + ``` + +## Stop passing `utils` and the date object to some translation keys + +Some translation keys no longer require `utils` and the date object as parameters, but only the formatted value as a string. The keys affected by this changes are: `clockLabelText`, `openDatePickerDialogue` and `openTimePickerDialogue`. +If you have customized those translation keys, you have to update them following the examples below: + +- If you are setting a custom value in a picker component: + +```diff +-clockLabelText: (view, time, utils) => +- `Select ${view}. ${ +- time === null || !utils.isValid(time) +- ? 'No time selected' +- : `Selected time is ${utils.format(time, 'fullTime')}` +- }` ++clockLabelText: (view, formattedTime) => ++ `Select ${view}. ${ ++ formattedTime == null ? 'No time selected' : `Selected time is ${formattedTime}` ++ }` + +-openDatePickerDialogue: (value, utils) => +- value !== null && utils.isValid(value) +- ? `Choose date, selected date is ${utils.format(value, 'fullDate')}` +- : 'Choose date', ++openDatePickerDialogue: (formattedDate) => ++ formattedDate ? `Choose date, selected date is ${formattedDate}` : 'Choose date' + +-openTimePickerDialogue: (value, utils) => +- value !== null && utils.isValid(value) +- ? `Choose time, selected time is ${utils.format(value, 'fullTime')}` +- : 'Choose time', ++openTimePickerDialogue: (formattedTime) => ++ formattedTime ? `Choose time, selected time is ${formattedTime}` : 'Choose time' +``` + +- If you are setting a custom value in the `LocalizationProvider`: + +```diff + +- `Select ${view}. ${ +- time === null || !utils.isValid(time) +- ? 'No time selected' +- : `Selected time is ${utils.format(time, 'fullTime')}` +- }` ++ clockLabelText: (view, formattedTime) => ++ `Select ${view}. ${ ++ formattedTime == null ? 'No time selected' : `Selected time is ${formattedTime}` ++ }` +- openDatePickerDialogue: (value, utils) => +- value !== null && utils.isValid(value) +- ? `Choose date, selected date is ${utils.format(value, 'fullDate')}` +- : 'Choose date', ++ openDatePickerDialogue: (formattedDate) => ++ formattedDate ? `Choose date, selected date is ${formattedDate}` : 'Choose date' +- openTimePickerDialogue: (value, utils) => +- value !== null && utils.isValid(value) +- ? `Choose time, selected time is ${utils.format(value, 'fullTime')}` +- : 'Choose time', ++ openTimePickerDialogue: (formattedTime) => ++ formattedTime ? `Choose time, selected time is ${formattedTime}` : 'Choose time' + }} > +``` + +- If you using this translation key in a custom component: + +```diff + const translations = usePickersTranslations(); + +-const clockLabelText = translations.clockLabelText( +- view, +- value, +- {} as any, +- value == null ? null : value.format('hh:mm:ss') +-); ++const clockLabelText = translations.clockLabelText( ++ view, ++ value == null ? null : value.format('hh:mm:ss') ++); + +-const openDatePickerDialogue = translations.openDatePickerDialogue( +- value, +- {} as any, +- value == null ? null : value.format('MM/DD/YYY') +-); ++const openDatePickerDialogue = translations.openDatePickerDialogue( ++ value == null ? null : value.format('MM/DD/YYY') ++); + +-const openTimePickerDialogue = translations.openTimePickerDialogue( +- value, +- {} as any, +- value == null ? null : value.format('hh:mm:ss') +-); ++const openTimePickerDialogue = translations.openTimePickerDialogue( ++ value == null ? null : value.format('hh:mm:ss') ++); +``` + +Also the following types and interfaces no longer receive a generic type parameter: + +- `PickersComponentAgnosticLocaleText` +- `PickersInputComponentLocaleText` +- `PickersInputLocaleText` +- `PickersLocaleText` +- `PickersTranslationKeys` diff --git a/docs/data/migration/migration-tree-view-lab/migration-tree-view-lab.md b/docs/data/migration/migration-tree-view-lab/migration-tree-view-lab.md index 2b1a932e33ffe..3fc640a648322 100644 --- a/docs/data/migration/migration-tree-view-lab/migration-tree-view-lab.md +++ b/docs/data/migration/migration-tree-view-lab/migration-tree-view-lab.md @@ -8,7 +8,7 @@ productId: x-tree-view ## Introduction -This is a reference for migrating your site's tree view from `@mui/lab` to `@mui/x-tree-view`. +This is a reference for migrating your site's Tree View from `@mui/lab` to `@mui/x-tree-view`. This migration is about the npm packages used, it **does not** affect the behavior of the components in your application. [//]: # 'You can find why we are moving in this direction in the [announcement blog post](/blog/lab-tree-view-to-mui-x/).' diff --git a/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md b/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md index 7d979f8af087e..2efbd13455fdd 100644 --- a/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md +++ b/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md @@ -13,7 +13,7 @@ To read more about the changes from the new major, check out [the blog post abou ## Start using the new release -In `package.json`, change the version of the tree view package to `^7.0.0`. +In `package.json`, change the version of the Tree View package to `^7.0.0`. ```diff -"@mui/x-tree-view": "^6.0.0", @@ -113,7 +113,7 @@ Here is an example of how you can transpile these features on Webpack 4 using th ### ✅ Rename `nodeId` to `itemId` -The required `nodeId` prop used by the `TreeItem` has been renamed to `itemId` for consistency: +The required `nodeId` prop used by the Tree Item has been renamed to `itemId` for consistency: ```diff @@ -140,10 +140,10 @@ The same change has been applied to the `ContentComponent` prop: } ``` -### ✅ Use `SimpleTreeView` instead of `TreeView` +### ✅ Use Simple Tree View instead of Tree View -The `TreeView` component has been deprecated and will be removed in the next major. -You can start replacing it with the new `SimpleTreeView` component which has exactly the same API: +The `` component has been deprecated and will be removed in the next major. +You can start replacing it with the new `` component which has exactly the same API: ```diff -import { TreeView } from '@mui/x-tree-view'; @@ -193,7 +193,7 @@ If you were using the `treeViewClasses` object, you can replace it with the new #### Define `expandIcon` The icon used to expand the children of an item (rendered when this item is collapsed) -is now defined as a slot both on the Tree View and the `TreeItem` components. +is now defined as a slot both on the `` and the `` components. If you were using the `ChevronRight` icon from `@mui/icons-material`, you can stop passing it to your component because it is now the default value: @@ -224,7 +224,7 @@ you need to use the new `expandIcon` slot on this component: Note that the `slots` prop expects a React component, not the JSX element returned when rendering this component. ::: -If you were passing another icon to your `TreeItem` component, +If you were passing another icon to your `` component, you need to use the new `expandIcon` slot on this component: ```diff @@ -241,7 +241,7 @@ you need to use the new `expandIcon` slot on this component: #### Define `collapseIcon` The icon used to collapse the children of an item (rendered when this item is expanded) -is now defined as a slot both on the Tree View and the `TreeItem` components. +is now defined as a slot both on the `` and `` components. If you were using the `ExpandMore` icon from `@mui/icons-material`, you can stop passing it to your component because it is now the default value: @@ -272,7 +272,7 @@ you need to use the new `collapseIcon` slot on this component: Note that the `slots` prop expects a React component, not the JSX element returned when rendering this component. ::: -If you were passing another icon to your `TreeItem` component, +If you were passing another icon to your `` component, you need to use the new `collapseIcon` slot on this component: ```diff @@ -306,7 +306,7 @@ by passing the same icon to both the `collapseIcon` and the `expandIcon` slots o #### Define `endIcon` The icon rendered next to an item without children -is now defined as a slot both on the Tree View and the `TreeItem` components. +is now defined as a slot both on the `` and `` components. If you were passing an icon to your Tree View component, you need to use the new `endIcon` slot on this component: @@ -324,7 +324,7 @@ you need to use the new `endIcon` slot on this component: Note that the `slots` prop expects a React component, not the JSX element returned when rendering this component. ::: -If you were passing an icon to your `TreeItem` component, +If you were passing an icon to your `` component, you need to use the new `endIcon` slot on this component: ```diff @@ -341,9 +341,9 @@ you need to use the new `endIcon` slot on this component: #### Define `icon` The icon rendered next to an item -is now defined as a slot on the `TreeItem` component. +is now defined as a slot on the `` component. -If you were passing an icon to your `TreeItem` component, +If you were passing an icon to your `` component, you need to use the new `icon` slot on this component: ```diff @@ -364,9 +364,9 @@ Note that the `slots` prop expects a React component, not the JSX element return ### ✅ Use slots to define the group transition The component used to animate the item children -is now defined as a slot on the `TreeItem` component. +is now defined as a slot on the `` component. -If you were passing a `TransitionComponent` or `TransitionProps` to your `TreeItem` component, +If you were passing a `TransitionComponent` or `TransitionProps` to your `` component, you need to use the new `groupTransition` slot on this component: ```diff @@ -382,9 +382,9 @@ you need to use the new `groupTransition` slot on this component: ``` -### Rename the `group` class of the `TreeItem` component +### Rename the `group` class of the Tree Item component -The `group` class of the `TreeItem` component has been renamed to `groupTransition` to match with its new slot name. +The `group` class of the `` component has been renamed to `groupTransition` to match with its new slot name. ```diff const StyledTreeItem = styled(TreeItem)({ @@ -423,7 +423,7 @@ If you were using the `onNodeToggle` prop to react to the expansion or collapse you can use the new `onItemExpansionToggle` prop which is called whenever an item is expanded or collapsed with its id and expansion status ```tsx -// It is also available on the deprecated `TreeView` component +// It is also available on the deprecated Tree View component console.log(itemId, isExpanded) @@ -461,7 +461,7 @@ If you were using the `onNodeSelect` prop to react to the selection or deselecti you can use the new `onItemSelectionToggle` prop which is called whenever an item is selected or deselected with its id and selection status. ```tsx -// It is also available on the deprecated `TreeView` component +// It is also available on the deprecated `` component console.log(itemId, isSelected) @@ -512,7 +512,7 @@ For example, if you were writing a test with `react-testing-library`, here is wh ### ✅ Use `useTreeItemState` instead of `useTreeItem` The `useTreeItem` hook has been renamed `useTreeItemState`. -This will help create a new headless version of the `TreeItem` component based on a future `useTreeItem` hook. +This will help create a new headless version of the Tree Item component based on a future `useTreeItem` hook. ```diff -import { TreeItem, useTreeItem } from '@mui/x-tree-view/TreeItem'; diff --git a/docs/data/migration/migration-tree-view-v7/migration-tree-view-v7.md b/docs/data/migration/migration-tree-view-v7/migration-tree-view-v7.md new file mode 100644 index 0000000000000..c766981007264 --- /dev/null +++ b/docs/data/migration/migration-tree-view-v7/migration-tree-view-v7.md @@ -0,0 +1,180 @@ +--- +productId: x-tree-view +--- + +# Migration from v7 to v8 + +

This guide describes the changes needed to migrate the Tree View from v7 to v8.

+ +## Introduction + +This is a reference guide for upgrading `@mui/x-tree-view` from v7 to v8. + +## Start using the new release + +In `package.json`, change the version of the Tree View package to `next`. + +```diff +-"@mui/x-tree-view": "7.x.x", ++"@mui/x-tree-view": "next", +``` + +Using `next` ensures that it will always use the latest v8 pre-release version, but you can also use a fixed version, like `8.0.0-alpha.0`. + +Since `v8` is a major release, it contains changes that affect the public API. +These changes were done for consistency, improved stability and to make room for new features. +Described below are the steps needed to migrate from v7 to v8. + +## Run codemods + +The `preset-safe` codemod will automatically adjust the bulk of your code to account for breaking changes in v8. You can run `v8.0.0/tree-view/preset-safe` targeting only Tree View or `v8.0.0/preset-safe` to target the other packages as well. + +You can either run it on a specific file, folder, or your entire codebase when choosing the `` argument. + + + +```bash +// Tree View specific +npx @mui/x-codemod@latest v8.0.0/tree-view/preset-safe + +// Target the other packages as well +npx @mui/x-codemod@latest v8.0.0/preset-safe +``` + +:::info +If you want to run the transformers one by one, check out the transformers included in the [preset-safe codemod for the Tree View](https://github.com/mui/mui-x/blob/HEAD/packages/x-codemod/README.md#preset-safe-for-tree-view-v800) for more details. +::: + +Breaking changes that are handled by this codemod are denoted by a ✅ emoji in the table of contents on the right side of the screen. + +If you have already applied the `v8.0.0/tree-view/preset-safe` (or `v8.0.0/preset-safe`) codemod, then you should not need to take any further action on these items. + +All other changes must be handled manually. + +:::warning +Not all use cases are covered by codemods. In some scenarios, like props spreading, cross-file dependencies, etc., the changes are not properly identified and therefore must be handled manually. + +For example, if a codemod tries to rename a prop, but this prop is hidden with the spread operator, it won't be transformed as expected. + +```tsx + +``` + +After running the codemods, make sure to test your application and that you don't have any console errors. + +Feel free to [open an issue](https://github.com/mui/mui-x/issues/new/choose) for support if you need help to proceed with your migration. +::: + +## New API to customize the Tree Item + +The `ContentComponent` or `ContentProps` props of the `TreeItem` component have been removed in favor of the new `slots`, `slotProps` props and of the `useTreeItem` hook. + +Learn more about the anatomy of the Tree Items and the customization utilities provided on the [Tree Item Customization page](/x/react-tree-view/tree-item-customization/). + +## Behavior change on the `onClick` and `onMouseDown` props of `TreeItem` + +The `onClick` and `onMouseDown` were the only event callback that were passed to the content of the Tree Item instead of its root. +The goal was to make sure that the callback was not fired when clicking on a descendant of a giving item. +This inconsistency has been solved, all the event manager now target the root of the item, and you can use the `onItemClick` prop on the Tree View component to target the content of an item: + +```diff +- ++ +- ++ + +``` + +## Rename the `TreeItem2` (and related utils) + +All the new Tree Item-related components and utils (introduced in the previous major to improve the DX of the Tree Item component) are becoming the default way of using the Tree Item and are therefore losing their `2` suffix: + +```diff + import * as React from 'react'; + import { +- TreeItem2, ++ TreeItem, +- TreeItem2Root, ++ TreeItemRoot, +- TreeItem2Content, ++ TreeItemContent, +- TreeItem2IconContainer, ++ TreeItemIconContainer, +- TreeItem2GroupTransition, ++ TreeItemGroupTransition, +- TreeItem2Checkbox, ++ TreeItemCheckbox, +- TreeItem2Label, ++ TreeItemLabel, +- TreeItem2Props, ++ TreeItemProps, +- TreeItem2Slots, ++ TreeItemSlots, +- TreeItem2SlotProps, ++ TreeItemSlotProps, +- } from '@mui/x-tree-view/TreeItem2'; ++ } from '@mui/x-tree-view/TreeItem'; + import { +- useTreeItem2, ++ useTreeItem, +- unstable_useTreeItem2 as useAliasedTreeItem, ++ unstable_useTreeItem as useAliasedTreeItem, +- UseTreeItem2Parameters, ++ UseTreeItemParameters, +- UseTreeItem2ReturnValue, ++ UseTreeItemReturnValue, +- UseTreeItem2Status, ++ UseTreeItemStatus, +- UseTreeItem2RootSlotOwnProps, ++ UseTreeItemRootSlotOwnProps, +- UseTreeItem2ContentSlotOwnProps, ++ UseTreeItemContentSlotOwnProps, +- UseTreeItem2LabelInputSlotOwnProps, ++ UseTreeItemLabelInputSlotOwnProps, +- UseTreeItem2LabelSlotOwnProps, ++ UseTreeItemLabelSlotOwnProps, +- UseTreeItem2CheckboxSlotOwnProps, ++ UseTreeItemCheckboxSlotOwnProps, +- UseTreeItem2IconContainerSlotOwnProps, ++ UseTreeItemIconContainerSlotOwnProps, +- UseTreeItem2GroupTransitionSlotOwnProps, ++ UseTreeItemGroupTransitionSlotOwnProps, +- UseTreeItem2DragAndDropOverlaySlotOwnProps, ++ UseTreeItemDragAndDropOverlaySlotOwnProps, +- } from '@mui/x-tree-view/useTreeItem2'; ++ } from '@mui/x-tree-view/useTreeItem'; +- import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; ++ import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; + import { +- TreeItem2Provider, ++ TreeItemProvider, +- TreeItem2ProviderProps, ++ TreeItemProviderProps, +- } from '@mui/x-tree-view/TreeItem2Provider'; ++ } from '@mui/x-tree-view/TreeItemProvider'; + import { +- TreeItem2Icon, ++ TreeItemIcon, +- TreeItem2IconProps, ++ TreeItemIconProps, +- TreeItem2IconSlots, ++ TreeItemIconSlots, +- TreeItem2IconSlotProps, ++ TreeItemIconSlotProps, +- } from '@mui/x-tree-view/TreeItem2Icon'; ++ } from '@mui/x-tree-view/TreeItemIcon'; + import { +- TreeItem2DragAndDropOverlay, ++ TreeItemDragAndDropOverlay, +- TreeItem2DragAndDropOverlayProps, ++ TreeItemDragAndDropOverlayProps, +- } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; ++ } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; + import { +- TreeItem2LabelInput, ++ TreeItemLabelInput, +- TreeItem2LabelInputProps, ++ TreeItemLabelInputProps, +- } from '@mui/x-tree-view/TreeItem2LabelInput'; ++ } from '@mui/x-tree-view/TreeItemLabelInput'; +``` diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 890060cd717ba..62ebde3d4dee7 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -1,8 +1,8 @@ import type { MuiPage } from 'docs/src/MuiPage'; -import chartsComponentApi from './charts-component-api-pages'; -import dataGridComponentApi from './data-grid-component-api-pages'; -import pickersComponentApi from './date-pickers-component-api-pages'; -import treeViewComponentApi from './tree-view-component-api-pages'; +import chartsComponentApi from './chartsApiPages'; +import dataGridComponentApi from './dataGridApiPages'; +import pickersComponentApi from './datePickersApiPages'; +import treeViewComponentApi from './treeViewApiPages'; const pages: MuiPage[] = [ { @@ -23,139 +23,168 @@ const pages: MuiPage[] = [ { pathname: '/x/common-features-group', title: 'Common concepts', - children: [{ pathname: `/x/common-concepts/custom-components`, title: 'Custom subcomponents' }], + children: [ + { pathname: `/x/common-concepts/custom-components`, title: 'Slots and subcomponents' }, + ], }, { pathname: '/x/react-data-grid-group', title: 'Data Grid', - children: [ { pathname: '/x/react-data-grid', title: 'Overview' }, - { pathname: '/x/react-data-grid/demo' }, { pathname: '/x/react-data-grid/getting-started' }, - { pathname: '/x/react-data-grid/layout' }, - { - pathname: '/x/react-data-grid/columns', - children: [ - { pathname: '/x/react-data-grid/column-definition' }, - { pathname: '/x/react-data-grid/column-dimensions' }, - { pathname: '/x/react-data-grid/column-visibility' }, - { pathname: '/x/react-data-grid/custom-columns' }, - { pathname: '/x/react-data-grid/column-header' }, - { pathname: '/x/react-data-grid/column-menu' }, - { pathname: '/x/react-data-grid/column-spanning' }, - { pathname: '/x/react-data-grid/column-groups' }, - { pathname: '/x/react-data-grid/column-ordering', plan: 'pro' }, - { pathname: '/x/react-data-grid/column-pinning', plan: 'pro' }, - ], - }, - { - pathname: '/x/react-data-grid/rows', - children: [ - { pathname: '/x/react-data-grid/row-definition' }, - { pathname: '/x/react-data-grid/row-updates' }, - { pathname: '/x/react-data-grid/row-height' }, - { pathname: '/x/react-data-grid/row-spanning', newFeature: true }, - { pathname: '/x/react-data-grid/master-detail', plan: 'pro' }, - { pathname: '/x/react-data-grid/row-ordering', plan: 'pro' }, - { pathname: '/x/react-data-grid/row-pinning', plan: 'pro' }, - { pathname: '/x/react-data-grid/row-recipes', title: 'Recipes' }, - ], - }, - { pathname: '/x/react-data-grid/editing' }, - { pathname: '/x/react-data-grid/sorting' }, + { pathname: '/x/react-data-grid/demo' }, + { pathname: '/x/react-data-grid/faq', title: 'FAQs' }, { - pathname: '/x/react-data-grid/filtering-group', - title: 'Filtering', + pathname: '/x/react-data-grid/main-features', + subheader: 'Main features', children: [ - { pathname: '/x/react-data-grid/filtering', title: 'Overview' }, - { pathname: '/x/react-data-grid/filtering/customization' }, - { pathname: '/x/react-data-grid/filtering/quick-filter' }, - { pathname: '/x/react-data-grid/filtering/server-side', title: 'Server-side filter' }, - { pathname: '/x/react-data-grid/filtering/multi-filters', plan: 'pro' }, + { pathname: '/x/react-data-grid/layout' }, { - pathname: '/x/react-data-grid/filtering/header-filters', - plan: 'pro', - newFeature: true, + pathname: '/x/react-data-grid/columns', + children: [ + { pathname: '/x/react-data-grid/column-definition' }, + { pathname: '/x/react-data-grid/column-dimensions' }, + { pathname: '/x/react-data-grid/column-visibility' }, + { pathname: '/x/react-data-grid/custom-columns' }, + { pathname: '/x/react-data-grid/column-header' }, + { pathname: '/x/react-data-grid/column-menu' }, + { pathname: '/x/react-data-grid/column-spanning' }, + { pathname: '/x/react-data-grid/column-groups' }, + { pathname: '/x/react-data-grid/column-ordering', plan: 'pro' }, + { pathname: '/x/react-data-grid/column-pinning', plan: 'pro' }, + ], }, - { pathname: '/x/react-data-grid/filtering-recipes', title: 'Recipes' }, - ], - }, - { pathname: '/x/react-data-grid/pagination' }, - { - pathname: '/x/react-data-grid/selection', - children: [ - { pathname: '/x/react-data-grid/row-selection' }, - { pathname: '/x/react-data-grid/cell-selection', plan: 'premium', newFeature: true }, - ], - }, - { pathname: '/x/react-data-grid/export' }, - { pathname: '/x/react-data-grid/clipboard', title: 'Copy and paste', newFeature: true }, - { pathname: '/x/react-data-grid/overlays', title: 'Overlays' }, - { pathname: '/x/react-data-grid/components', title: 'Custom subcomponents' }, - { - pathname: '/x/react-data-grid/style-group', - title: 'Style', - children: [ - { pathname: '/x/react-data-grid/style', title: 'Overview' }, - { pathname: '/x/react-data-grid/style-recipes', title: 'Recipes' }, + { + pathname: '/x/react-data-grid/rows', + children: [ + { pathname: '/x/react-data-grid/row-definition' }, + { pathname: '/x/react-data-grid/row-updates' }, + { pathname: '/x/react-data-grid/row-height' }, + { pathname: '/x/react-data-grid/row-spanning', newFeature: true }, + { pathname: '/x/react-data-grid/master-detail', plan: 'pro' }, + { pathname: '/x/react-data-grid/row-ordering', plan: 'pro' }, + { pathname: '/x/react-data-grid/row-pinning', plan: 'pro' }, + { pathname: '/x/react-data-grid/row-recipes', title: 'Recipes' }, + ], + }, + { + pathname: '/x/react-data-grid/editing-group', + title: 'Editing', + children: [ + { pathname: '/x/react-data-grid/editing', title: 'Overview' }, + { pathname: '/x/react-data-grid/recipes-editing', title: 'Recipes' }, + ], + }, + { pathname: '/x/react-data-grid/sorting' }, + { + pathname: '/x/react-data-grid/filtering-group', + title: 'Filtering', + children: [ + { pathname: '/x/react-data-grid/filtering', title: 'Overview' }, + { pathname: '/x/react-data-grid/filtering/customization' }, + { pathname: '/x/react-data-grid/filtering/quick-filter' }, + { pathname: '/x/react-data-grid/filtering/server-side', title: 'Server-side filter' }, + { pathname: '/x/react-data-grid/filtering/multi-filters', plan: 'pro' }, + { + pathname: '/x/react-data-grid/filtering/header-filters', + plan: 'pro', + newFeature: true, + }, + { pathname: '/x/react-data-grid/filtering-recipes', title: 'Recipes' }, + ], + }, + { pathname: '/x/react-data-grid/pagination' }, + { + pathname: '/x/react-data-grid/selection', + children: [ + { pathname: '/x/react-data-grid/row-selection' }, + { pathname: '/x/react-data-grid/cell-selection', plan: 'premium', newFeature: true }, + ], + }, + { pathname: '/x/react-data-grid/virtualization' }, + { pathname: '/x/react-data-grid/accessibility' }, + { pathname: '/x/react-data-grid/localization' }, ], }, - { pathname: '/x/react-data-grid/localization' }, - { pathname: '/x/react-data-grid/scrolling' }, - { pathname: '/x/react-data-grid/virtualization' }, - { pathname: '/x/react-data-grid/accessibility' }, - { pathname: '/x/react-data-grid/performance' }, - { pathname: '/x/react-data-grid/tree-data', plan: 'pro' }, - { pathname: '/x/react-data-grid/row-grouping', plan: 'premium' }, - { pathname: '/x/react-data-grid/aggregation', plan: 'premium' }, - { pathname: '/x/react-data-grid/pivoting', plan: 'premium', planned: true }, { - pathname: '/x/react-data-grid/server-side-data-group', - title: 'Server-side data', - plan: 'pro', + pathname: '/x/react-data-grid/advanced-features-group', + subheader: 'Advanced features', children: [ - { pathname: '/x/react-data-grid/server-side-data', title: 'Overview' }, - { pathname: '/x/react-data-grid/server-side-data/tree-data', plan: 'pro' }, + { pathname: '/x/react-data-grid/tree-data', plan: 'pro' }, { - pathname: '/x/react-data-grid/server-side-data/lazy-loading', - plan: 'pro', - planned: true, + pathname: '/x/react-data-grid/row-grouping-group', + title: 'Row grouping', + plan: 'premium', + children: [ + { pathname: '/x/react-data-grid/row-grouping', title: 'Overview' }, + { + pathname: '/x/react-data-grid/recipes-row-grouping', + title: 'Recipes', + }, + ], }, + { pathname: '/x/react-data-grid/aggregation', plan: 'premium' }, + { pathname: '/x/react-data-grid/pivoting', plan: 'premium', planned: true }, + { pathname: '/x/react-data-grid/export' }, + { pathname: '/x/react-data-grid/clipboard', title: 'Copy and paste', newFeature: true }, + { pathname: '/x/react-data-grid/scrolling' }, + { - pathname: '/x/react-data-grid/server-side-data/infinite-loading', + pathname: '/x/react-data-grid/list-view', + title: 'List view', plan: 'pro', - planned: true, + unstable: true, }, { - pathname: '/x/react-data-grid/server-side-data/row-grouping', + pathname: '/x/react-data-grid/server-side-data-group', + title: 'Server-side data', plan: 'pro', - planned: true, - }, - { - pathname: '/x/react-data-grid/server-side-data/aggregation', - plan: 'premium', - planned: true, + children: [ + { pathname: '/x/react-data-grid/server-side-data', title: 'Overview' }, + { pathname: '/x/react-data-grid/server-side-data/tree-data', plan: 'pro' }, + { + pathname: '/x/react-data-grid/server-side-data/lazy-loading', + plan: 'pro', + planned: true, + }, + { + pathname: '/x/react-data-grid/server-side-data/infinite-loading', + plan: 'pro', + planned: true, + }, + { + pathname: '/x/react-data-grid/server-side-data/row-grouping', + plan: 'premium', + planned: true, + }, + { + pathname: '/x/react-data-grid/server-side-data/aggregation', + plan: 'premium', + planned: true, + }, + ], }, ], }, { - pathname: '/x/react-data-grid/custom-behavior', + pathname: '/x/react-data-grid/customization-group', + subheader: 'Customization', children: [ - { pathname: '/x/react-data-grid/api-object', title: 'API object' }, - { pathname: '/x/react-data-grid/events' }, - { pathname: '/x/react-data-grid/state' }, + { pathname: '/x/react-data-grid/style', title: 'Styling basics' }, + { pathname: '/x/react-data-grid/style-recipes', title: 'Styling recipes' }, + { pathname: '/x/react-data-grid/overlays', title: 'Overlays' }, + { pathname: '/x/react-data-grid/components', title: 'Custom subcomponents' }, ], }, { - pathname: '/x/react-data-grid/recipes', + pathname: '/x/api/resources-group', + subheader: 'Resources', children: [ - { pathname: '/x/react-data-grid/recipes-editing', title: 'Editing' }, - { - pathname: '/x/react-data-grid/recipes-row-grouping', - title: 'Row grouping', - plan: 'premium', - }, + { pathname: '/x/react-data-grid/api-object', title: 'API object' }, + { pathname: '/x/react-data-grid/events' }, + { pathname: '/x/react-data-grid/state' }, + { pathname: '/x/react-data-grid/performance' }, ], }, { @@ -217,7 +246,6 @@ const pages: MuiPage[] = [ }, ], }, - { pathname: '/x/react-data-grid/faq', title: 'FAQ' }, ], }, { @@ -227,8 +255,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-date-pickers', title: 'Overview' }, { pathname: '/x/react-date-pickers/getting-started' }, { pathname: '/x/react-date-pickers/base-concepts' }, - { pathname: '/x/react-date-pickers/accessibility' }, - { pathname: '/x/react-date-pickers/faq', title: 'FAQ' }, + { pathname: '/x/react-date-pickers/faq', title: 'FAQs' }, { pathname: '/x/react-date-pickers-components', subheader: 'Components', @@ -321,20 +348,16 @@ const pages: MuiPage[] = [ ], }, { pathname: '/x/react-date-pickers/fields', title: 'Field components' }, - { - pathname: '/x/api/date-pickers-group', - title: 'API reference', - children: [{ pathname: '/x/api/date-pickers', title: 'Index' }, ...pickersComponentApi], - }, ], }, { - pathname: '/x/react-date-pickers/common-features', - subheader: 'Common features', + pathname: '/x/react-date-pickers/main-features', + subheader: 'Main features', children: [ { pathname: '/x/react-date-pickers/validation' }, { pathname: '/x/react-date-pickers/lifecycle', title: 'Components lifecycle' }, { pathname: '/x/react-date-pickers/shortcuts' }, + { pathname: '/x/react-date-pickers/accessibility' }, ], }, { @@ -357,8 +380,8 @@ const pages: MuiPage[] = [ ], }, { - pathname: '/x/react-date-pickers/visual-customization', - subheader: 'Visual customization', + pathname: '/x/react-date-pickers/customization-group', + subheader: 'Customization', children: [ { pathname: '/x/react-date-pickers/custom-components', @@ -370,6 +393,17 @@ const pages: MuiPage[] = [ { pathname: '/x/react-date-pickers/playground', title: 'Customization playground' }, ], }, + { + pathname: '/x/api/picker-resources', + subheader: 'Resources', + children: [ + { + pathname: '/x/api/date-pickers-group', + title: 'API reference', + children: [{ pathname: '/x/api/date-pickers', title: 'Index' }, ...pickersComponentApi], + }, + ], + }, ], }, { @@ -380,8 +414,8 @@ const pages: MuiPage[] = [ { pathname: '/x/react-charts', title: 'Overview' }, { pathname: '/x/react-charts/getting-started' }, { - pathname: '/x/react-charts-available', - subheader: 'Available components', + pathname: '/x/react-chart-components', + subheader: 'Components', children: [ { pathname: '/x/react-charts-bars', @@ -428,8 +462,8 @@ const pages: MuiPage[] = [ unstable: true, }, { - pathname: '/x/react-charts/common-features', - subheader: 'Common features', + pathname: '/x/react-charts/main-features', + subheader: 'Main features', children: [ { pathname: '/x/react-charts/axis' }, { pathname: '/x/react-charts/components', title: 'Custom components' }, @@ -449,19 +483,25 @@ const pages: MuiPage[] = [ ], }, { - pathname: '/x/api/charts-group', - title: 'API reference', + pathname: '/x/api/chart-resources', + subheader: 'Resources', children: [ - ...chartsComponentApi, { - pathname: '/x/api/charts-interfaces-group', - subheader: 'Interfaces', + pathname: '/x/api/charts-group', + title: 'API reference', children: [ - { pathname: '/x/api/charts/axis-config', title: 'AxisConfig' }, - { pathname: '/x/api/charts/bar-series-type', title: 'BarSeriesType' }, - { pathname: '/x/api/charts/line-series-type', title: 'LineSeriesType' }, - { pathname: '/x/api/charts/pie-series-type', title: 'PieSeriesType' }, - { pathname: '/x/api/charts/scatter-series-type', title: 'ScatterSeriesType' }, + ...chartsComponentApi, + { + pathname: '/x/api/charts-interfaces-group', + subheader: 'Interfaces', + children: [ + { pathname: '/x/api/charts/axis-config', title: 'AxisConfig' }, + { pathname: '/x/api/charts/bar-series-type', title: 'BarSeriesType' }, + { pathname: '/x/api/charts/line-series-type', title: 'LineSeriesType' }, + { pathname: '/x/api/charts/pie-series-type', title: 'PieSeriesType' }, + { pathname: '/x/api/charts/scatter-series-type', title: 'ScatterSeriesType' }, + ], + }, ], }, ], @@ -513,17 +553,23 @@ const pages: MuiPage[] = [ ], }, { - pathname: '/x/react-tree-view/common-features', - subheader: 'Common features', + pathname: '/x/react-tree-view/main-features', + subheader: 'Main features', children: [ { pathname: '/x/react-tree-view/accessibility' }, { pathname: '/x/react-tree-view/tree-item-customization', title: 'Item customization' }, ], }, { - pathname: '/x/api/tree-view-group', - title: 'API reference', - children: [...treeViewComponentApi], + pathname: '/x/api/tree-view-resources', + subheader: 'Resources', + children: [ + { + pathname: '/x/api/tree-view-group', + title: 'API reference', + children: [...treeViewComponentApi], + }, + ], }, ], }, @@ -531,9 +577,28 @@ const pages: MuiPage[] = [ pathname: '/x/migration-group', title: 'Migration', children: [ + { + pathname: '/x/migration-v8', + subheader: 'Upgrade to v8', + children: [ + { pathname: '/x/migration/migration-data-grid-v7', title: 'Breaking changes: Data Grid' }, + { + pathname: '/x/migration/migration-pickers-v7', + title: 'Breaking changes: Date and Time Pickers', + }, + { + pathname: '/x/migration/migration-tree-view-v7', + title: 'Breaking changes: Tree View', + }, + { + pathname: '/x/migration/migration-charts-v7', + title: 'Breaking changes: Charts', + }, + ], + }, { pathname: '/x/migration-v7', - subheader: 'Upgrade to v7', + title: 'Upgrade to v7', children: [ { pathname: '/x/migration/migration-data-grid-v6', title: 'Breaking changes: Data Grid' }, { diff --git a/docs/data/tree-view/accessibility/accessibility.md b/docs/data/tree-view/accessibility/accessibility.md index b1fc45bfc6903..cc9c2306612bd 100644 --- a/docs/data/tree-view/accessibility/accessibility.md +++ b/docs/data/tree-view/accessibility/accessibility.md @@ -54,7 +54,7 @@ Type-ahead is supported for single characters. When typing a character, focus mo ## Selection -The tree view supports both single and multi-selection. To learn more about the selection API, visit the dedicated page for the [Simple Tree View](/x/react-tree-view/simple-tree-view/selection/) or the [Rich Tree View](/x/react-tree-view/rich-tree-view/selection/). +The Tree View supports both single and multi-selection. To learn more about the selection API, visit the dedicated page for the [Simple Tree View](/x/react-tree-view/simple-tree-view/selection/) or the [Rich Tree View](/x/react-tree-view/rich-tree-view/selection/). To read more about the distinction between selection and focus, you can refer to the [WAI-ARIA Authoring Practices guide](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_vs_selection). diff --git a/docs/data/tree-view/datasets/employees.ts b/docs/data/tree-view/datasets/employees.ts new file mode 100644 index 0000000000000..2af62416f36e8 --- /dev/null +++ b/docs/data/tree-view/datasets/employees.ts @@ -0,0 +1,40 @@ +import { TreeViewBaseItem } from '@mui/x-tree-view/models'; + +export const EMPLOYEES_DATASET: TreeViewBaseItem[] = [ + { + id: '0', + label: 'Sarah', + }, + { + id: '1', + label: 'Thomas', + children: [ + { id: '2', label: 'Robert' }, + { id: '3', label: 'Karen' }, + { id: '4', label: 'Nancy' }, + { id: '5', label: 'Daniel' }, + { id: '6', label: 'Christopher' }, + { id: '7', label: 'Donald' }, + ], + }, + { + id: '8', + label: 'Mary', + children: [ + { + id: '9', + label: 'Jennifer', + children: [{ id: '10', label: 'Anna' }], + }, + { id: '11', label: 'Michael' }, + { + id: '12', + label: 'Linda', + children: [ + { id: '13', label: 'Elizabeth' }, + { id: '14', label: 'William' }, + ], + }, + ], + }, +]; diff --git a/docs/data/tree-view/getting-started/getting-started.md b/docs/data/tree-view/getting-started/getting-started.md index 79dc191bdc929..6acc9e5382c17 100644 --- a/docs/data/tree-view/getting-started/getting-started.md +++ b/docs/data/tree-view/getting-started/getting-started.md @@ -72,7 +72,7 @@ Take a look at the [Styled engine guide](/material-ui/integrations/styled-compon ## Render your first component -To make sure that everything is set up correctly, try rendering a `SimpleTreeView` component: +To make sure that everything is set up correctly, try rendering a Simple Tree View component: {{"demo": "FirstComponent.js"}} @@ -82,7 +82,7 @@ To make sure that everything is set up correctly, try rendering a `SimpleTreeVie The component follows the WAI-ARIA authoring practices. -To have an accessible tree view you must use `aria-labelledby` +To have an accessible Tree View you must use `aria-labelledby` or `aria-label` to reference or provide a label on the TreeView, otherwise, screen readers will announce it as "tree", making it hard to understand the context of a specific tree item. diff --git a/docs/data/tree-view/overview/overview.md b/docs/data/tree-view/overview/overview.md index 64c3749656524..e1c49a7e2bd69 100644 --- a/docs/data/tree-view/overview/overview.md +++ b/docs/data/tree-view/overview/overview.md @@ -41,75 +41,3 @@ This is the recommended version for larger trees, as well as those that require :::info At the moment, the Simple and Rich Tree Views are similar in terms of feature support. But as the component grows, you can expect to see the more advanced ones appear primarily on the Rich Tree View. ::: - -### Tree Item components - -The `@mui/x-tree-view` package exposes two different components to define your tree items: - -- `TreeItem` -- `TreeItem2` - -#### `TreeItem` - -This is the long-standing component that is very similar to the one used in previous versions (`@mui/x-tree-view@6` and `@mui/lab`). - -When using `SimpleTreeView`, -you can import it from `@mui/x-tree-view/TreeItem` and use it as a child of the `SimpleTreeView` component: - -```tsx -import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; -import { TreeItem } from '@mui/x-tree-view/TreeItem'; - -export default function App() { - return ( - - - - - ); -} -``` - -When using `RichTreeView`, -you don't have to import anything; it's the default component used to render the items: - -```tsx -import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; - -export default function App() { - return ; -} -``` - -#### `TreeItem2` - -This is a new component that provides a more powerful customization API, and will eventually replace `TreeItem`. - -When using `SimpleTreeView`, -you can import it from `@mui/x-tree-view/TreeItem2` and use it as a child of the `SimpleTreeView` component: - -```tsx -import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; - -export default function App() { - return ( - - - - - ); -} -``` - -When using `RichTreeView`, -you can import it from `@mui/x-tree-view/TreeItem2` and pass it as a slot of the `RichTreeView` component: - -```tsx -import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; - -export default function App() { - return ; -} -``` diff --git a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js index 305d77fc61a39..304f82faaf74d 100644 --- a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js +++ b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js @@ -14,18 +14,18 @@ import ImageIcon from '@mui/icons-material/Image'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import VideoCameraBackIcon from '@mui/icons-material/VideoCameraBack'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { treeItemClasses } from '@mui/x-tree-view/TreeItem'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + treeItemClasses, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; const ITEMS = [ { @@ -79,7 +79,7 @@ function DotIcon() { ); } -const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const StyledTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { @@ -90,7 +90,7 @@ const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ }), })); -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ flexDirection: 'row-reverse', borderRadius: theme.spacing(0.7), marginBottom: theme.spacing(0.5), @@ -156,7 +156,7 @@ const StyledTreeItemLabelText = styled(Typography)({ function CustomLabel({ icon: Icon, expandable, children, ...other }) { return ( - {children} {expandable && } - + ); } @@ -219,7 +219,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getDragAndDropOverlayProps, status, publicAPI, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const item = publicAPI.getItem(itemId); const expandable = isExpandable(children); @@ -231,7 +231,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { } return ( - + - - - - + + + + - + {children && } - + ); }); diff --git a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx index 6711c554ba9d6..09291542de682 100644 --- a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx +++ b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx @@ -14,18 +14,18 @@ import ImageIcon from '@mui/icons-material/Image'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import VideoCameraBackIcon from '@mui/icons-material/VideoCameraBack'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { treeItemClasses } from '@mui/x-tree-view/TreeItem'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + treeItemClasses, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; import { TreeViewBaseItem } from '@mui/x-tree-view/models'; type FileType = 'image' | 'pdf' | 'doc' | 'video' | 'folder' | 'pinned' | 'trash'; @@ -94,7 +94,7 @@ declare module 'react' { } } -const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const StyledTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { @@ -103,9 +103,9 @@ const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ ...theme.applyStyles('light', { color: theme.palette.grey[800], }), -})) as unknown as typeof TreeItem2Root; +})) as unknown as typeof TreeItemRoot; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ flexDirection: 'row-reverse', borderRadius: theme.spacing(0.7), marginBottom: theme.spacing(0.5), @@ -182,7 +182,7 @@ function CustomLabel({ ...other }: CustomLabelProps) { return ( - {children} {expandable && } - + ); } @@ -233,7 +233,7 @@ const getIconFromFileType = (fileType: FileType) => { }; interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -252,7 +252,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getDragAndDropOverlayProps, status, publicAPI, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const item = publicAPI.getItem(itemId); const expandable = isExpandable(children); @@ -264,7 +264,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( } return ( - + - - - - + + + + - + {children && } - + ); }); diff --git a/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.js b/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.js index afb1f90aec17a..5619fbf9b31d7 100644 --- a/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.js +++ b/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.js @@ -4,18 +4,18 @@ import Box from '@mui/material/Box'; import Avatar from '@mui/material/Avatar'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; const ITEMS = [ { @@ -37,7 +37,7 @@ const ITEMS = [ }, ]; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ padding: theme.spacing(0.5, 1), })); @@ -53,15 +53,15 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + + - - - + + + ({ @@ -73,14 +73,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { > {label[0]} - - + + - + - {children && } - - + {children && } + + ); }); diff --git a/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.tsx b/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.tsx index d07cd21249f84..c4ac1965cf835 100644 --- a/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.tsx +++ b/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.tsx @@ -4,18 +4,18 @@ import Box from '@mui/material/Box'; import Avatar from '@mui/material/Avatar'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; import { TreeViewBaseItem } from '@mui/x-tree-view/models'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; const ITEMS: TreeViewBaseItem[] = [ { @@ -37,12 +37,12 @@ const ITEMS: TreeViewBaseItem[] = [ }, ]; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ padding: theme.spacing(0.5, 1), })); interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -60,15 +60,15 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + + - - - + + + ({ @@ -80,14 +80,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( > {(label as string)[0]} - - + + - + - {children && } - - + {children && } + + ); }); diff --git a/docs/data/tree-view/rich-tree-view/customization/customization.md b/docs/data/tree-view/rich-tree-view/customization/customization.md index adfe395f9c196..ae18c0407ee10 100644 --- a/docs/data/tree-view/rich-tree-view/customization/customization.md +++ b/docs/data/tree-view/rich-tree-view/customization/customization.md @@ -1,7 +1,7 @@ --- productId: x-tree-view title: Rich Tree View - Customization -components: RichTreeView, TreeItem, TreeItem2 +components: RichTreeView, TreeItem packageName: '@mui/x-tree-view' githubLabel: 'component: tree view' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ @@ -12,7 +12,7 @@ waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/

Learn how to customize the Rich Tree View component.

:::success -See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +See [Common concepts—Slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. ::: ## Basics @@ -26,7 +26,7 @@ The demo below shows how to add icons using both an existing icon library, such ### Custom toggle animations -Use the `groupTransition` slot on the `TreeItem` to pass a component that handles your animation. +Use the `groupTransition` slot on the `` to pass a component that handles your animation. The demo below is animated using Material UI's [Collapse](/material-ui/transitions/#collapse) component together with the [react-spring](https://www.react-spring.dev/) library. @@ -45,7 +45,7 @@ Learn more about the anatomy of the Tree Item components and the customization u ### Headless API -Use the `useTreeItem2` hook to create your own component. +Use the `useTreeItem` hook to create your own component. The demo below shows how to add an avatar and custom typography elements. {{"demo": "HeadlessAPI.js", "defaultCodeOpen": false}} @@ -54,13 +54,6 @@ The demo below shows how to add an avatar and custom typography elements. ### File explorer -:::warning -This example is built using the new `TreeItem2` component -which adds several slots to modify the content of the Tree Item or change its behavior. - -You can learn more about this new component in the [Overview page](/x/react-tree-view/#tree-item-components). -::: - The demo below shows many of the previous customization examples brought together to make the Tree View component look completely different than its default design. {{"demo": "FileExplorer.js", "defaultCodeOpen": false}} diff --git a/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.js b/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.js index 4c52e4c94ec83..e6ed064a16138 100644 --- a/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.js +++ b/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.js @@ -1,13 +1,13 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; import { MUI_X_PRODUCTS } from './products'; -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { - const { interactions } = useTreeItem2Utils({ +const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -17,7 +17,7 @@ const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { }; return ( - , ) { - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); - const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + const handleInputBlur: UseTreeItemLabelInputSlotOwnProps['onBlur'] = (event) => { interactions.handleCancelItemLabelEditing(event); }; return ( - ({ ...theme.typography.body1, @@ -53,7 +53,7 @@ export const ITEMS = [ function Label({ children, ...other }) { return ( - {children} - + ); } @@ -136,12 +136,12 @@ const LabelInput = React.forwardRef(function LabelInput( ); }); -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { - const { interactions } = useTreeItem2Utils({ +const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); - const { publicAPI } = useTreeItem2(props); + const { publicAPI } = useTreeItem(props); const handleInputBlur = (event) => { event.defaultMuiPrevented = true; @@ -152,7 +152,7 @@ const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { }; return ( - ({ @@ -67,9 +63,9 @@ export const ITEMS: TreeViewBaseItem[] = [ }, ]; -function Label({ children, ...other }: UseTreeItem2LabelSlotOwnProps) { +function Label({ children, ...other }: UseTreeItemLabelSlotOwnProps) { return ( - {children} - + ); } -interface CustomLabelInputProps extends UseTreeItem2LabelInputSlotOwnProps { +interface CustomLabelInputProps extends UseTreeItemLabelInputSlotOwnProps { handleCancelItemLabelEditing: (event: React.SyntheticEvent) => void; handleSaveItemLabel: (event: React.SyntheticEvent, label: string) => void; item: TreeViewBaseItem; @@ -163,28 +159,28 @@ const LabelInput = React.forwardRef(function LabelInput( ); }); -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( - props: TreeItem2Props, +const CustomTreeItem = React.forwardRef(function CustomTreeItem( + props: TreeItemProps, ref: React.Ref, ) { - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); - const { publicAPI } = useTreeItem2(props); + const { publicAPI } = useTreeItem(props); - const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + const handleInputBlur: UseTreeItemLabelInputSlotOwnProps['onBlur'] = (event) => { event.defaultMuiPrevented = true; }; - const handleInputKeyDown: UseTreeItem2LabelInputSlotOwnProps['onKeyDown'] = ( + const handleInputKeyDown: UseTreeItemLabelInputSlotOwnProps['onKeyDown'] = ( event, ) => { event.defaultMuiPrevented = true; }; return ( - )} - + ); } @@ -43,7 +43,7 @@ function CustomLabelInput(props) { return ( - + void; @@ -32,7 +28,7 @@ function CustomLabel({ ...other }: CustomLabelProps) { return ( - )} - + ); } -interface CustomLabelInputProps extends UseTreeItem2LabelInputSlotOwnProps { +interface CustomLabelInputProps extends UseTreeItemLabelInputSlotOwnProps { handleCancelItemLabelEditing: (event: React.SyntheticEvent) => void; handleSaveItemLabel: (event: React.SyntheticEvent, label: string) => void; value: string; @@ -68,7 +64,7 @@ function CustomLabelInput(props: Omit) { return ( - + ) { ); } -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( - props: TreeItem2Props, +const CustomTreeItem = React.forwardRef(function CustomTreeItem( + props: TreeItemProps, ref: React.Ref, ) { - const { interactions, status } = useTreeItem2Utils({ + const { interactions, status } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); - const handleContentDoubleClick: UseTreeItem2LabelSlotOwnProps['onDoubleClick'] = ( + const handleContentDoubleClick: UseTreeItemLabelSlotOwnProps['onDoubleClick'] = ( event, ) => { event.defaultMuiPrevented = true; }; - const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + const handleInputBlur: UseTreeItemLabelInputSlotOwnProps['onBlur'] = (event) => { event.defaultMuiPrevented = true; }; - const handleInputKeyDown: UseTreeItem2LabelInputSlotOwnProps['onKeyDown'] = ( + const handleInputKeyDown: UseTreeItemLabelInputSlotOwnProps['onKeyDown'] = ( event, ) => { event.defaultMuiPrevented = true; }; return ( - - + {error ? ( @@ -34,9 +34,9 @@ function CustomLabelInput(props) { ); } -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { +const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { const [error, setError] = React.useState(null); - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -77,7 +77,7 @@ const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { }; return ( - ) { return ( - + {error ? ( @@ -38,12 +38,12 @@ function CustomLabelInput(props: Omit) { ); } -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( - props: TreeItem2Props, +const CustomTreeItem = React.forwardRef(function CustomTreeItem( + props: TreeItemProps, ref: React.Ref, ) { const [error, setError] = React.useState(null); - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -57,13 +57,13 @@ const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( } }; - const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + const handleInputBlur: UseTreeItemLabelInputSlotOwnProps['onBlur'] = (event) => { if (error) { event.defaultMuiPrevented = true; } }; - const handleInputKeyDown: UseTreeItem2LabelInputSlotOwnProps['onKeyDown'] = ( + const handleInputKeyDown: UseTreeItemLabelInputSlotOwnProps['onKeyDown'] = ( event, ) => { event.defaultMuiPrevented = true; @@ -86,7 +86,7 @@ const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( }; return ( - Create your custom tree view.

+

Create your custom Tree View.

:::warning The `useTreeView` hook is not public API for now, @@ -143,7 +143,7 @@ Once `focusedItemId` becomes a model, we could consider removing the notion of s ### Populate the Tree View instance -The Tree View instance is an object accessible in all the plugins and in the `TreeItem`. +The Tree View instance is an object accessible in all the plugins and in the Tree Item. It is the main way a plugin can provide features to the rest of the component. ```ts @@ -268,7 +268,7 @@ type UseCustomPluginSignature = TreeViewPluginSignature<{ params: UseCustomPluginParams; // The params specific to your plugins after running `getDefaultizedParams` defaultizedParams: UseCustomPluginDefaultizedParams; - // The methods added to the tree view instance by your plugin + // The methods added to the Tree View instance by your plugin instance: UseCustomPluginInstance; // The events emitted by your plugin events: UseCustomPluginEvents; @@ -325,6 +325,6 @@ type UseCustomPluginSignature = TreeViewPluginSignature<{ ### Log expanded items -Interact with the tree view to see the expanded items being logged: +Interact with the Tree View to see the expanded items being logged: {{"demo": "LogExpandedItems.js"}} diff --git a/docs/data/tree-view/rich-tree-view/items/items.md b/docs/data/tree-view/rich-tree-view/items/items.md index fbf179e3685e8..49db9da0a4e2b 100644 --- a/docs/data/tree-view/rich-tree-view/items/items.md +++ b/docs/data/tree-view/rich-tree-view/items/items.md @@ -28,7 +28,7 @@ Each item must have a unique identifier. This identifier is used internally to identify the item in the various models and to track the item across updates. -By default, the `RichTreeView` component looks for a property named `id` in the data set to get that identifier: +By default, the Rich Tree View component looks for a property named `id` in the data set to get that identifier: ```tsx const ITEMS = [{ id: 'tree-view-community' }]; @@ -36,7 +36,7 @@ const ITEMS = [{ id: 'tree-view-community' }]; ; ``` -If the item's identifier is not called `id`, then you need to use the `getItemId` prop to tell the `RichTreeView` component where it is located. +If the item's identifier is not called `id`, then you need to use the `getItemId` prop to tell the Rich Tree View component where it is located. The following demo shows how to use `getItemId` to grab the unique identifier from a property named `internalId`: @@ -63,7 +63,7 @@ It could be achieved by either defining the prop outside the component scope or Each item must have a label which does not need to be unique. -By default, the `RichTreeView` component looks for a property named `label` in the data set to get that label: +By default, the Rich Tree View component looks for a property named `label` in the data set to get that label: ```tsx const ITEMS = [{ label: '@mui/x-tree-view' }]; @@ -71,7 +71,7 @@ const ITEMS = [{ label: '@mui/x-tree-view' }]; ; ``` -If the item's label is not called `label`, then you need to use the `getItemLabel` prop to tell the `RichTreeView` component where it's located: +If the item's label is not called `label`, then you need to use the `getItemLabel` prop to tell the Rich Tree View component where it's located: The following demo shows how to use `getItemLabel` to grab the unique identifier from a property named `name`: @@ -95,7 +95,7 @@ It could be achieved by either defining the prop outside the component scope or ::: :::warning -Unlike the `SimpleTreeView` component, the `RichTreeView` component only supports string labels, you cannot pass React nodes to it. +Unlike the Simple Tree View component, the Rich Tree View component only supports string labels, you cannot pass React nodes to it. ::: ## Disabled items @@ -179,7 +179,7 @@ const item = apiRef.current.getItem( ### Get an item's DOM element by ID -Use the `getItemDOMElement` API method to get an item's DOM element by its ID. +Use the `getItemDOMElement()` API method to get an item's DOM element by its ID. ```ts const itemElement = apiRef.current.getItemDOMElement( diff --git a/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.js b/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.js index 9a05f402be2c8..423926095ed70 100644 --- a/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.js +++ b/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.js @@ -10,19 +10,19 @@ import ImageIcon from '@mui/icons-material/Image'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import VideoCameraBackIcon from '@mui/icons-material/VideoCameraBack'; import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro'; -import { treeItemClasses } from '@mui/x-tree-view/TreeItem'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + TreeItemGroupTransition, + treeItemClasses, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; import { useTreeViewApiRef } from '@mui/x-tree-view/hooks'; @@ -80,7 +80,7 @@ function DotIcon() { ); } -const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const StyledTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { @@ -90,7 +90,7 @@ const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ color: theme.palette.grey[800], }), })); -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ flexDirection: 'row-reverse', borderRadius: theme.spacing(0.7), marginBottom: theme.spacing(0.5), @@ -142,7 +142,7 @@ const StyledTreeItemLabelText = styled(Typography)({ function CustomLabel({ icon: Icon, expandable, children, ...other }) { return ( - {children} {expandable && } - + ); } @@ -203,14 +203,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getDragAndDropOverlayProps, status, publicAPI, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const item = publicAPI.getItem(itemId); const expandable = isExpandable(children); const icon = getIconFromFileType(item.fileType); return ( - + - - - - + + + + - + - {children && } + {children && } - + ); }); diff --git a/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.tsx b/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.tsx index f555c2afd64b8..5f5a0434956bc 100644 --- a/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.tsx +++ b/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.tsx @@ -10,19 +10,19 @@ import ImageIcon from '@mui/icons-material/Image'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import VideoCameraBackIcon from '@mui/icons-material/VideoCameraBack'; import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro'; -import { treeItemClasses } from '@mui/x-tree-view/TreeItem'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + TreeItemGroupTransition, + treeItemClasses, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; import { TreeViewBaseItem } from '@mui/x-tree-view/models'; import { useTreeViewApiRef } from '@mui/x-tree-view/hooks'; @@ -94,7 +94,7 @@ declare module 'react' { } } -const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const StyledTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { @@ -103,8 +103,8 @@ const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ ...theme.applyStyles('light', { color: theme.palette.grey[800], }), -})) as unknown as typeof TreeItem2Root; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +})) as unknown as typeof TreeItemRoot; +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ flexDirection: 'row-reverse', borderRadius: theme.spacing(0.7), marginBottom: theme.spacing(0.5), @@ -167,7 +167,7 @@ function CustomLabel({ ...other }: CustomLabelProps) { return ( - {children} {expandable && } - + ); } @@ -216,7 +216,7 @@ const getIconFromFileType = (fileType: FileType) => { }; interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -235,14 +235,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getDragAndDropOverlayProps, status, publicAPI, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const item = publicAPI.getItem(itemId); const expandable = isExpandable(children); const icon = getIconFromFileType(item.fileType); return ( - + - - - - + + + + - + - {children && } + {children && } - + ); }); diff --git a/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.js b/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.js index c9f03403a38fb..500411bd806c2 100644 --- a/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.js +++ b/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.js @@ -3,18 +3,18 @@ import Box from '@mui/material/Box'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; const MUI_X_PRODUCTS = [ { @@ -58,7 +58,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const { draggable, onDragStart, onDragOver, onDragEnd, ...otherRootProps } = getRootProps(other); @@ -73,27 +73,27 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { }; return ( - - - - - - - + + + + + + - - - - - - {children && } - - + + + + + + {children && } + + ); }); diff --git a/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.tsx b/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.tsx index 8780d381c49bf..8b0395890c154 100644 --- a/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.tsx +++ b/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.tsx @@ -3,18 +3,18 @@ import Box from '@mui/material/Box'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro'; import { TreeViewBaseItem } from '@mui/x-tree-view/models'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; const MUI_X_PRODUCTS: TreeViewBaseItem[] = [ { @@ -47,7 +47,7 @@ const MUI_X_PRODUCTS: TreeViewBaseItem[] = [ ]; interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -65,7 +65,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const { draggable, onDragStart, onDragOver, onDragEnd, ...otherRootProps } = getRootProps(other); @@ -84,27 +84,27 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - - - - - - - + + + + + + - - - - - - {children && } - - + + + + + + {children && } + + ); }); diff --git a/docs/data/tree-view/rich-tree-view/ordering/ordering.md b/docs/data/tree-view/rich-tree-view/ordering/ordering.md index ed28ace0806b5..be62d10fd51bd 100644 --- a/docs/data/tree-view/rich-tree-view/ordering/ordering.md +++ b/docs/data/tree-view/rich-tree-view/ordering/ordering.md @@ -1,7 +1,7 @@ --- productId: x-tree-view title: Rich Tree View - Ordering -components: TreeItem2, TreeItem, RichTreeViewPro +components: TreeItem, RichTreeViewPro, TreeItemDragAndDropOverlay packageName: '@mui/x-tree-view' githubLabel: 'component: tree view' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ @@ -50,7 +50,7 @@ You can use the `onItemPositionChange` to send the new position of an item to yo {{"demo": "OnItemPositionChange.js"}} If you want to send the entire dataset to your backend, you can use the [`getItemTree`](/x/react-tree-view/rich-tree-view/items/#get-the-current-item-tree) API method. -The following demo demonstrates it by synchronizing the first tree view with the second one whenever you do a re-ordering: +The following demo demonstrates it by synchronizing the first Tree View with the second one whenever you do a re-ordering: {{"demo": "SendAllItemsToServer.js"}} diff --git a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.js b/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.js deleted file mode 100644 index 8f9dd61e4df94..0000000000000 --- a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.js +++ /dev/null @@ -1,98 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeViewApiRef } from '@mui/x-tree-view/hooks'; - -const MUI_X_PRODUCTS = [ - { - id: 'grid', - label: 'Data Grid', - children: [ - { id: 'grid-community', label: '@mui/x-data-grid' }, - { id: 'grid-pro', label: '@mui/x-data-grid-pro' }, - { id: 'grid-premium', label: '@mui/x-data-grid-premium' }, - ], - }, - { - id: 'pickers', - label: 'Date and Time Pickers', - children: [ - { id: 'pickers-community', label: '@mui/x-date-pickers' }, - { id: 'pickers-pro', label: '@mui/x-date-pickers-pro' }, - ], - }, - { - id: 'charts', - label: 'Charts', - children: [{ id: 'charts-community', label: '@mui/x-charts' }], - }, - { - id: 'tree-view', - label: 'Tree View', - children: [{ id: 'tree-view-community', label: '@mui/x-tree-view' }], - }, -]; - -function getItemDescendantsIds(item) { - const ids = []; - item.children?.forEach((child) => { - ids.push(child.id); - ids.push(...getItemDescendantsIds(child)); - }); - - return ids; -} - -export default function ParentChildrenSelectionRelationship() { - const [selectedItems, setSelectedItems] = React.useState([]); - const toggledItemRef = React.useRef({}); - const apiRef = useTreeViewApiRef(); - - const handleItemSelectionToggle = (event, itemId, isSelected) => { - toggledItemRef.current[itemId] = isSelected; - }; - - const handleSelectedItemsChange = (event, newSelectedItems) => { - setSelectedItems(newSelectedItems); - - // Select / unselect the children of the toggled item - const itemsToSelect = []; - const itemsToUnSelect = {}; - Object.entries(toggledItemRef.current).forEach(([itemId, isSelected]) => { - const item = apiRef.current.getItem(itemId); - if (isSelected) { - itemsToSelect.push(...getItemDescendantsIds(item)); - } else { - getItemDescendantsIds(item).forEach((descendantId) => { - itemsToUnSelect[descendantId] = true; - }); - } - }); - - const newSelectedItemsWithChildren = Array.from( - new Set( - [...newSelectedItems, ...itemsToSelect].filter( - (itemId) => !itemsToUnSelect[itemId], - ), - ), - ); - - setSelectedItems(newSelectedItemsWithChildren); - - toggledItemRef.current = {}; - }; - - return ( - - - - ); -} diff --git a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx b/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx deleted file mode 100644 index e45bdbb4fb3c3..0000000000000 --- a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeViewApiRef } from '@mui/x-tree-view/hooks'; -import { TreeViewBaseItem } from '@mui/x-tree-view/models'; - -const MUI_X_PRODUCTS: TreeViewBaseItem[] = [ - { - id: 'grid', - label: 'Data Grid', - children: [ - { id: 'grid-community', label: '@mui/x-data-grid' }, - { id: 'grid-pro', label: '@mui/x-data-grid-pro' }, - { id: 'grid-premium', label: '@mui/x-data-grid-premium' }, - ], - }, - { - id: 'pickers', - label: 'Date and Time Pickers', - children: [ - { id: 'pickers-community', label: '@mui/x-date-pickers' }, - { id: 'pickers-pro', label: '@mui/x-date-pickers-pro' }, - ], - }, - { - id: 'charts', - label: 'Charts', - children: [{ id: 'charts-community', label: '@mui/x-charts' }], - }, - { - id: 'tree-view', - label: 'Tree View', - children: [{ id: 'tree-view-community', label: '@mui/x-tree-view' }], - }, -]; - -function getItemDescendantsIds(item: TreeViewBaseItem) { - const ids: string[] = []; - item.children?.forEach((child) => { - ids.push(child.id); - ids.push(...getItemDescendantsIds(child)); - }); - - return ids; -} - -export default function ParentChildrenSelectionRelationship() { - const [selectedItems, setSelectedItems] = React.useState([]); - const toggledItemRef = React.useRef<{ [itemId: string]: boolean }>({}); - const apiRef = useTreeViewApiRef(); - - const handleItemSelectionToggle = ( - event: React.SyntheticEvent, - itemId: string, - isSelected: boolean, - ) => { - toggledItemRef.current[itemId] = isSelected; - }; - - const handleSelectedItemsChange = ( - event: React.SyntheticEvent, - newSelectedItems: string[], - ) => { - setSelectedItems(newSelectedItems); - - // Select / unselect the children of the toggled item - const itemsToSelect: string[] = []; - const itemsToUnSelect: { [itemId: string]: boolean } = {}; - Object.entries(toggledItemRef.current).forEach(([itemId, isSelected]) => { - const item = apiRef.current!.getItem(itemId); - if (isSelected) { - itemsToSelect.push(...getItemDescendantsIds(item)); - } else { - getItemDescendantsIds(item).forEach((descendantId) => { - itemsToUnSelect[descendantId] = true; - }); - } - }); - - const newSelectedItemsWithChildren = Array.from( - new Set( - [...newSelectedItems, ...itemsToSelect].filter( - (itemId) => !itemsToUnSelect[itemId], - ), - ), - ); - - setSelectedItems(newSelectedItemsWithChildren); - - toggledItemRef.current = {}; - }; - - return ( - - - - ); -} diff --git a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx.preview b/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx.preview deleted file mode 100644 index 6fa38db9c231b..0000000000000 --- a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx.preview +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.js b/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.js new file mode 100644 index 0000000000000..2c54a087f326b --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.js @@ -0,0 +1,59 @@ +import * as React from 'react'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import Stack from '@mui/material/Stack'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; + +import { EMPLOYEES_DATASET } from '../../datasets/employees'; + +export default function SelectionPropagation() { + const [selectionPropagation, setSelectionPropagation] = React.useState({ + parents: true, + descendants: true, + }); + + return ( +
+ + + setSelectionPropagation((prev) => ({ + ...prev, + descendants: event.target.checked, + })) + } + /> + } + label="Auto select descendants" + /> + + setSelectionPropagation((prev) => ({ + ...prev, + parents: event.target.checked, + })) + } + /> + } + label="Auto select parents" + /> + + + + +
+ ); +} diff --git a/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.tsx b/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.tsx new file mode 100644 index 0000000000000..c41780b8fb9cc --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import Stack from '@mui/material/Stack'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { TreeViewSelectionPropagation } from '@mui/x-tree-view/models'; +import { EMPLOYEES_DATASET } from '../../datasets/employees'; + +export default function SelectionPropagation() { + const [selectionPropagation, setSelectionPropagation] = + React.useState({ + parents: true, + descendants: true, + }); + + return ( +
+ + + setSelectionPropagation((prev) => ({ + ...prev, + descendants: event.target.checked, + })) + } + /> + } + label="Auto select descendants" + /> + + setSelectionPropagation((prev) => ({ + ...prev, + parents: event.target.checked, + })) + } + /> + } + label="Auto select parents" + /> + + + + +
+ ); +} diff --git a/docs/data/tree-view/rich-tree-view/selection/selection.md b/docs/data/tree-view/rich-tree-view/selection/selection.md index 2175d7ad39787..02036050d5023 100644 --- a/docs/data/tree-view/rich-tree-view/selection/selection.md +++ b/docs/data/tree-view/rich-tree-view/selection/selection.md @@ -75,25 +75,36 @@ Use the `onItemSelectionToggle` prop if you want to react to an item selection c {{"demo": "TrackItemSelectionToggle.js"}} -## Parent / children selection relationship +## Automatic parents and children selection -Automatically select an item when all of its children are selected and automatically select all children when the parent is selected. +By default, selecting a parent item does not select its children. You can override this behavior using the `selectionPropagation` prop. -:::warning -This feature isn't implemented yet. It's coming. +Here's how it's structured: -👍 Upvote [issue #4821](https://github.com/mui/mui-x/issues/4821) if you want to see it land faster. +```ts +type TreeViewSelectionPropagation = { + descendants?: boolean; // default: false + parents?: boolean; // default: false +}; +``` -Don't hesitate to leave a comment on the same issue to influence what gets built. -Especially if you already have a use case for this component, -or if you are facing a pain point with your current solution. -::: +When `selectionPropagation.descendants` is set to `true`. + +- Selecting a parent selects all its descendants automatically. +- Deselecting a parent deselects all its descendants automatically. -If you cannot wait for the official implementation, -you can create your own custom solution using the `selectedItems`, -`onSelectedItemsChange` and `onItemSelectionToggle` props: +When `selectionPropagation.parents` is set to `true`. -{{"demo": "ParentChildrenSelectionRelationship.js"}} +- Selecting all the descendants of a parent selects the parent automatically. +- Deselecting a descendant of a selected parent deselects the parent automatically. + +The example below demonstrates the usage of the `selectionPropagation` prop. + +{{"demo": "SelectionPropagation.js", "defaultCodeOpen": false}} + +:::warning +This feature only works when multi selection is enabled using `props.multiSelect`. +::: ## Imperative API @@ -106,13 +117,13 @@ const apiRef = useTreeViewApiRef(); return ; ``` -When your component first renders, `apiRef` will be `undefined`. +When your component first renders, `apiRef` is `undefined`. After this initial render, `apiRef` holds methods to interact imperatively with the Tree View. ::: ### Select or deselect an item -Use the `selectItem` API method to select or deselect an item: +Use the `selectItem()` API method to select or deselect an item: ```ts apiRef.current.selectItem({ diff --git a/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.js b/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.js index 7dc64dbbe9b04..b12875731714d 100644 --- a/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.js +++ b/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.js @@ -15,20 +15,20 @@ import ArrowRightIcon from '@mui/icons-material/ArrowRight'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; + TreeItemContent, + TreeItemIconContainer, + TreeItemRoot, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; -const CustomTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const CustomTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.text.secondary, })); -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ marginBottom: theme.spacing(0.3), color: theme.palette.text.secondary, borderRadius: theme.spacing(2), @@ -46,11 +46,11 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ }, })); -const CustomTreeItemIconContainer = styled(TreeItem2IconContainer)(({ theme }) => ({ +const CustomTreeItemIconContainer = styled(TreeItemIconContainer)(({ theme }) => ({ marginRight: theme.spacing(1), })); -const CustomTreeItemGroupTransition = styled(TreeItem2GroupTransition)( +const CustomTreeItemGroupTransition = styled(TreeItemGroupTransition)( ({ theme }) => ({ marginLeft: 0, [`& .content`]: { @@ -83,7 +83,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getLabelProps, getGroupTransitionProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const style = { '--tree-view-color': theme.palette.mode !== 'dark' ? color : colorForDarkMode, @@ -92,7 +92,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { }; return ( - + - + )} - + ); }); diff --git a/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.tsx b/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.tsx index 88d4d92db4535..c2c669a251828 100644 --- a/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.tsx +++ b/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.tsx @@ -15,14 +15,14 @@ import ArrowRightIcon from '@mui/icons-material/ArrowRight'; import { SvgIconProps } from '@mui/material/SvgIcon'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; + TreeItemContent, + TreeItemIconContainer, + TreeItemRoot, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; declare module 'react' { interface CSSProperties { @@ -32,7 +32,7 @@ declare module 'react' { } interface StyledTreeItemProps - extends Omit, + extends Omit, React.HTMLAttributes { bgColor?: string; bgColorForDarkMode?: string; @@ -42,11 +42,11 @@ interface StyledTreeItemProps labelInfo?: string; } -const CustomTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const CustomTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.text.secondary, })); -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ marginBottom: theme.spacing(0.3), color: theme.palette.text.secondary, borderRadius: theme.spacing(2), @@ -64,11 +64,11 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ }, })); -const CustomTreeItemIconContainer = styled(TreeItem2IconContainer)(({ theme }) => ({ +const CustomTreeItemIconContainer = styled(TreeItemIconContainer)(({ theme }) => ({ marginRight: theme.spacing(1), })); -const CustomTreeItemGroupTransition = styled(TreeItem2GroupTransition)( +const CustomTreeItemGroupTransition = styled(TreeItemGroupTransition)( ({ theme }) => ({ marginLeft: 0, [`& .content`]: { @@ -104,7 +104,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getLabelProps, getGroupTransitionProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const style = { '--tree-view-color': theme.palette.mode !== 'dark' ? color : colorForDarkMode, @@ -113,7 +113,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - + - + )} - + ); }); diff --git a/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.js b/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.js index c6e2012740160..b8af5dff4380d 100644 --- a/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.js +++ b/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.js @@ -3,19 +3,19 @@ import { styled } from '@mui/material/styles'; import Box from '@mui/material/Box'; import Avatar from '@mui/material/Avatar'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ padding: theme.spacing(0.5, 1), })); @@ -30,16 +30,16 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getLabelProps, getGroupTransitionProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + + - - - - + + + + ({ @@ -51,12 +51,12 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { > {label[0]} - + - {children && } - - + {children && } + + ); }); diff --git a/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.tsx b/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.tsx index a2e9d06a57148..42d5fa6e37680 100644 --- a/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.tsx +++ b/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.tsx @@ -3,24 +3,24 @@ import { styled } from '@mui/material/styles'; import Box from '@mui/material/Box'; import Avatar from '@mui/material/Avatar'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ padding: theme.spacing(0.5, 1), })); interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -37,16 +37,16 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getLabelProps, getGroupTransitionProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + + - - - - + + + + ({ @@ -58,12 +58,12 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( > {(label as string)[0]} - + - {children && } - - + {children && } + + ); }); diff --git a/docs/data/tree-view/simple-tree-view/customization/customization.md b/docs/data/tree-view/simple-tree-view/customization/customization.md index 4a18e36b438b5..641136df7b941 100644 --- a/docs/data/tree-view/simple-tree-view/customization/customization.md +++ b/docs/data/tree-view/simple-tree-view/customization/customization.md @@ -1,7 +1,7 @@ --- productId: x-tree-view title: Simple Tree View - Customization -components: SimpleTreeView, TreeItem, TreeItem2 +components: SimpleTreeView, TreeItem packageName: '@mui/x-tree-view' githubLabel: 'component: tree view' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ @@ -12,7 +12,7 @@ waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/

Learn how to customize the Simple Tree View component.

:::success -See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +See [Common concepts—Slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. ::: ## Basics @@ -26,7 +26,7 @@ The demo below shows how to add icons using both an existing icon library, such ### Custom toggle animations -Use the `groupTransition` slot on the `TreeItem` to pass a component that handles your animation. +Use the `groupTransition` slot on the Tree Item to pass a component that handles your animation. The demo below is animated using Material UI's [Collapse](/material-ui/transitions/#collapse) component together with the [react-spring](https://www.react-spring.dev/) library. @@ -46,7 +46,7 @@ Learn more about the anatomy of the Tree Items and the customization utilities p ### Headless API -Use the `useTreeItem2` hook to create your own component. +Use the `useTreeItem` hook to create your own component. The demo below shows how to add an avatar and custom typography elements. {{"demo": "HeadlessAPI.js", "defaultCodeOpen": false}} @@ -61,13 +61,6 @@ Target the `treeItemClasses.groupTransition` class to add connection borders bet ### Gmail clone -:::warning -This example is built using the new `TreeItem2` component -which adds several slots to modify the content of the Tree Item or change its behavior. - -You can learn more about this new component in the [Overview page](/x/react-tree-view/#tree-item-components). -::: - Google's Gmail side nav is potentially one of the web's most famous tree view components. The demo below shows how to replicate it. diff --git a/docs/data/tree-view/simple-tree-view/focus/focus.md b/docs/data/tree-view/simple-tree-view/focus/focus.md index ef9cd7d5558e2..8cc4576f617e0 100644 --- a/docs/data/tree-view/simple-tree-view/focus/focus.md +++ b/docs/data/tree-view/simple-tree-view/focus/focus.md @@ -41,7 +41,7 @@ apiRef.current.focusItem( :::info This method only works with items that are currently visible. -Calling `apiRef.focusItem` on an item whose parent is collapsed will do nothing. +Calling `apiRef.focusItem()` on an item whose parent is collapsed does nothing. ::: {{"demo": "ApiMethodFocusItem.js"}} diff --git a/docs/data/tree-view/simple-tree-view/items/items.md b/docs/data/tree-view/simple-tree-view/items/items.md index c177b4769e2fb..cd516f83665a3 100644 --- a/docs/data/tree-view/simple-tree-view/items/items.md +++ b/docs/data/tree-view/simple-tree-view/items/items.md @@ -51,7 +51,7 @@ Use the `disabled` prop on the Tree Item component to disable interaction and fo #### The disabledItemsFocusable prop -Note that the demo above also includes a switch. +Note that the demo below also includes a switch. This toggles the `disabledItemsFocusable` prop, which controls whether or not a disabled Tree Item can be focused. When this prop is set to false: @@ -91,13 +91,13 @@ const apiRef = useTreeViewApiRef(); return {children}; ``` -When your component first renders, `apiRef` will be `undefined`. +When your component first renders, `apiRef` is `undefined`. After this initial render, `apiRef` holds methods to interact imperatively with the Tree View. ::: ### Get an item's DOM element by ID -Use the `getItemDOMElement` API method to get an item's DOM element by its ID. +Use the `getItemDOMElement()` API method to get an item's DOM element by its ID. ```ts const itemElement = apiRef.current.getItemDOMElement( diff --git a/docs/data/tree-view/simple-tree-view/selection/selection.md b/docs/data/tree-view/simple-tree-view/selection/selection.md index aceb02cb7215d..c9f44a4e63efc 100644 --- a/docs/data/tree-view/simple-tree-view/selection/selection.md +++ b/docs/data/tree-view/simple-tree-view/selection/selection.md @@ -91,7 +91,7 @@ After this initial render, `apiRef` holds methods to interact imperatively with ### Select or deselect an item -Use the `selectItem` API method to select or deselect an item: +Use the `selectItem()` API method to select or deselect an item: ```ts apiRef.current.selectItem({ diff --git a/docs/data/tree-view/tree-item-customization/CheckboxSlot.js b/docs/data/tree-view/tree-item-customization/CheckboxSlot.js index 81245357631ee..fe14e7b79ae3c 100644 --- a/docs/data/tree-view/tree-item-customization/CheckboxSlot.js +++ b/docs/data/tree-view/tree-item-customization/CheckboxSlot.js @@ -1,7 +1,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; import { MUI_X_PRODUCTS } from './products'; const CustomCheckbox = React.forwardRef(function CustomCheckbox(props, ref) { @@ -10,7 +10,7 @@ const CustomCheckbox = React.forwardRef(function CustomCheckbox(props, ref) { const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { return ( - , ) { return ( - , ) { return ( - , checkedIcon: , }, - } as TreeItem2SlotProps + } as TreeItemSlotProps } /> ); diff --git a/docs/data/tree-view/tree-item-customization/ContentSlot.js b/docs/data/tree-view/tree-item-customization/ContentSlot.js index 78857c927c615..5404af12ac70e 100644 --- a/docs/data/tree-view/tree-item-customization/ContentSlot.js +++ b/docs/data/tree-view/tree-item-customization/ContentSlot.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { alpha, styled } from '@mui/material/styles'; import Stack from '@mui/material/Stack'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; import { MUI_X_PRODUCTS } from './products'; @@ -34,7 +34,7 @@ const CustomContent = styled('div')(({ theme }) => ({ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { return ( - ({ @@ -17,14 +17,14 @@ const CustomContent = styled('div')(({ theme }) => ({ }, variants: [ { - props: ({ status }: UseTreeItem2ContentSlotOwnProps) => status.disabled, + props: ({ status }: UseTreeItemContentSlotOwnProps) => status.disabled, style: { opacity: 0.5, backgroundColor: theme.palette.action.disabledBackground, }, }, { - props: ({ status }: UseTreeItem2ContentSlotOwnProps) => status.selected, + props: ({ status }: UseTreeItemContentSlotOwnProps) => status.selected, style: { backgroundColor: alpha((theme.vars || theme).palette.primary.main, 0.4), }, @@ -33,11 +33,11 @@ const CustomContent = styled('div')(({ theme }) => ({ })); const CustomTreeItem = React.forwardRef(function CustomTreeItem( - props: TreeItem2Props, + props: TreeItemProps, ref: React.Ref, ) { return ( - , ) { return ( - ); diff --git a/docs/data/tree-view/tree-item-customization/CustomTreeItemDemo.js b/docs/data/tree-view/tree-item-customization/CustomTreeItemDemo.js index b5e9421b1df5f..785462f879107 100644 --- a/docs/data/tree-view/tree-item-customization/CustomTreeItemDemo.js +++ b/docs/data/tree-view/tree-item-customization/CustomTreeItemDemo.js @@ -5,19 +5,19 @@ import Popover from '@mui/material/Popover'; import Typography from '@mui/material/Typography'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; -import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { TreeItemLabelInput } from '@mui/x-tree-view/TreeItemLabelInput'; const ITEMS = [ { @@ -35,7 +35,7 @@ const AnnotationText = styled(Typography)(({ theme }) => ({ fontSize: theme.typography.pxToRem(11), })); -const CustomTreeItem2Transition = styled(TreeItem2GroupTransition)(({ theme }) => ({ +const CustomTreeItemTransition = styled(TreeItemGroupTransition)(({ theme }) => ({ padding: 6, border: '1px solid transparent', '&:hover:not(:has(:hover))': { @@ -43,34 +43,34 @@ const CustomTreeItem2Transition = styled(TreeItem2GroupTransition)(({ theme }) = }, })); -const CustomTreeItem2LabelInput = styled(TreeItem2LabelInput)(({ theme }) => ({ +const CustomTreeItemLabelInput = styled(TreeItemLabelInput)(({ theme }) => ({ color: theme.palette.text.primary, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Content = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ border: '1px solid transparent', '&:hover:not(:has(:hover))': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2IconContainer = styled(TreeItem2IconContainer)(({ theme }) => ({ +const CustomTreeItemIconContainer = styled(TreeItemIconContainer)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Label = styled(TreeItem2Label)(({ theme }) => ({ +const CustomTreeItemLabel = styled(TreeItemLabel)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Checkbox = styled(TreeItem2Checkbox)(({ theme }) => ({ +const CustomTreeItemCheckbox = styled(TreeItemCheckbox)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { @@ -100,65 +100,65 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getDragAndDropOverlayProps, getLabelInputProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + - - - - - + + {status?.editable ? ( - ) : ( - )} - - + {children && ( - )} - - + + ({ fontSize: theme.typography.pxToRem(11), })); -const CustomTreeItem2Transition = styled(TreeItem2GroupTransition)(({ theme }) => ({ +const CustomTreeItemTransition = styled(TreeItemGroupTransition)(({ theme }) => ({ padding: 6, border: '1px solid transparent', '&:hover:not(:has(:hover))': { @@ -48,34 +48,34 @@ const CustomTreeItem2Transition = styled(TreeItem2GroupTransition)(({ theme }) = }, })); -const CustomTreeItem2LabelInput = styled(TreeItem2LabelInput)(({ theme }) => ({ +const CustomTreeItemLabelInput = styled(TreeItemLabelInput)(({ theme }) => ({ color: theme.palette.text.primary, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Content = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ border: '1px solid transparent', '&:hover:not(:has(:hover))': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2IconContainer = styled(TreeItem2IconContainer)(({ theme }) => ({ +const CustomTreeItemIconContainer = styled(TreeItemIconContainer)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Label = styled(TreeItem2Label)(({ theme }) => ({ +const CustomTreeItemLabel = styled(TreeItemLabel)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Checkbox = styled(TreeItem2Checkbox)(({ theme }) => ({ +const CustomTreeItemCheckbox = styled(TreeItemCheckbox)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { @@ -84,7 +84,7 @@ const CustomTreeItem2Checkbox = styled(TreeItem2Checkbox)(({ theme }) => ({ })); interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -112,65 +112,65 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getDragAndDropOverlayProps, getLabelInputProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + - - - - - + + {status?.editable ? ( - ) : ( - )} - - + {children && ( - )} - - + + , ) { - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -23,7 +23,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( // Do something when the checkbox is clicked }; - const handleCheckboxOnChange: UseTreeItem2CheckboxSlotOwnProps['onChange'] = ( + const handleCheckboxOnChange: UseTreeItemCheckboxSlotOwnProps['onChange'] = ( event, ) => { event.defaultMuiPrevented = true; @@ -32,13 +32,13 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - ); diff --git a/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.js b/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.js index 07f41849946f8..e160321aa89ca 100644 --- a/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.js +++ b/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.js @@ -4,16 +4,16 @@ import IconButton from '@mui/material/IconButton'; import AddBoxOutlinedIcon from '@mui/icons-material/AddBoxOutlined'; import IndeterminateCheckBoxOutlinedIcon from '@mui/icons-material/IndeterminateCheckBoxOutlined'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2Label, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; -import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; + TreeItemContent, + TreeItemLabel, + TreeItemRoot, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; import { MUI_X_PRODUCTS } from './products'; const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -27,9 +27,9 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId, children, }); @@ -39,8 +39,8 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - - + + {status.expandable && ( )} - - - - - {children && } - - + + + + + {children && } + + ); }); diff --git a/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.tsx b/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.tsx index 59d5869eeff84..9b4dad21c33d5 100644 --- a/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.tsx +++ b/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.tsx @@ -4,21 +4,21 @@ import IconButton from '@mui/material/IconButton'; import AddBoxOutlinedIcon from '@mui/icons-material/AddBoxOutlined'; import IndeterminateCheckBoxOutlinedIcon from '@mui/icons-material/IndeterminateCheckBoxOutlined'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2Label, - TreeItem2Root, - TreeItem2Props, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; -import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; + TreeItemContent, + TreeItemLabel, + TreeItemRoot, + TreeItemProps, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; import { MUI_X_PRODUCTS } from './products'; const CustomTreeItem = React.forwardRef(function CustomTreeItem( - { id, itemId, label, disabled, children }: TreeItem2Props, + { id, itemId, label, disabled, children }: TreeItemProps, ref: React.Ref, ) { const { @@ -28,9 +28,9 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId, children, }); @@ -40,8 +40,8 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - - + + {status.expandable && ( )} - - + + - - - {children && } - - + + + {children && } + + ); }); diff --git a/docs/data/tree-view/tree-item-customization/HandleSelectionDemo.js b/docs/data/tree-view/tree-item-customization/HandleSelectionDemo.js index 09caf51788b1c..8723527c35dc4 100644 --- a/docs/data/tree-view/tree-item-customization/HandleSelectionDemo.js +++ b/docs/data/tree-view/tree-item-customization/HandleSelectionDemo.js @@ -5,10 +5,10 @@ import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; import PanoramaFishEyeIcon from '@mui/icons-material/PanoramaFishEye'; import CircleIcon from '@mui/icons-material/Circle'; -import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; import { MUI_X_PRODUCTS } from './products'; function CustomLabel({ children, status, onClick, ...props }) { @@ -33,7 +33,7 @@ function CustomLabel({ children, status, onClick, ...props }) { } const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { - const { interactions, status } = useTreeItem2Utils({ + const { interactions, status } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -46,7 +46,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { }; return ( - ; } @@ -46,14 +46,14 @@ function CustomLabel({ children, status, onClick, ...props }: CustomLabelProps) } const CustomTreeItem = React.forwardRef(function CustomTreeItem( - props: TreeItem2Props, + props: TreeItemProps, ref: React.Ref, ) { - const { interactions, status } = useTreeItem2Utils({ + const { interactions, status } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); - const handleContentClick: UseTreeItem2ContentSlotOwnProps['onClick'] = (event) => { + const handleContentClick: UseTreeItemContentSlotOwnProps['onClick'] = (event) => { event.defaultMuiPrevented = true; }; @@ -62,7 +62,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - ); diff --git a/docs/data/tree-view/tree-item-customization/LabelSlot.js b/docs/data/tree-view/tree-item-customization/LabelSlot.js index de54cf4eb4248..fcac64f4f9e43 100644 --- a/docs/data/tree-view/tree-item-customization/LabelSlot.js +++ b/docs/data/tree-view/tree-item-customization/LabelSlot.js @@ -2,9 +2,9 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; -import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; export const MUI_X_PRODUCTS = [ { @@ -70,7 +70,7 @@ function CustomLabel({ children, className, secondaryLabel }) { } const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { - const { publicAPI } = useTreeItem2Utils({ + const { publicAPI } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -78,7 +78,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { const item = publicAPI.getItem(props.itemId); return ( - , ) { - const { publicAPI } = useTreeItem2Utils({ + const { publicAPI } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -95,7 +95,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( const item = publicAPI.getItem(props.itemId); return ( - , ) { return ( - - - -{{"component": "modules/components/TreeItem2Anatomy.js"}} + +Tree Item anatomy + + +Tree Item anatomy + ### Content @@ -81,12 +83,11 @@ By default, a nested item is indented by `12px` from its parent item. {{"demo": "ItemChildrenIndentationProp.js"}} :::success -This feature is compatible with both the `TreeItem` and `TreeItem2` components If you are using a custom Tree Item component, and you want to override the padding, then apply the following padding to your `groupTransition` element: ```ts -const CustomTreeItem2GroupTransition = styled(TreeItem2GroupTransition)(({ theme }) => ({ +const CustomTreeItemGroupTransition = styled(TreeItemGroupTransition)(({ theme }) => ({ // ...other styles paddingLeft: `var(--TreeView-itemChildrenIndentation)`, } @@ -95,7 +96,7 @@ const CustomTreeItem2GroupTransition = styled(TreeItem2GroupTransition)(({ theme If you are using the `indentationAtItemLevel` prop, then instead apply the following padding to your `content` element: ```ts -const CustomTreeItem2Content = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ // ...other styles paddingLeft: `calc(${theme.spacing(1)} + var(--TreeView-itemChildrenIndentation) * var(--TreeView-itemDepth))`, @@ -116,12 +117,11 @@ It will become the default behavior in the next major version of the Tree View c {{"demo": "IndentationAtItemLevel.js"}} :::success -This feature is compatible with both the `TreeItem` and `TreeItem2` components and with the `itemChildrenIndentation` prop. If you are using a custom Tree Item component, and you want to override the padding, then apply the following padding to your `content` element: ```ts -const CustomTreeItem2Content = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ // ...other styles paddingLeft: `calc(${theme.spacing(1)} + var(--TreeView-itemChildrenIndentation) * var(--TreeView-itemDepth))`, @@ -132,19 +132,19 @@ const CustomTreeItem2Content = styled(TreeItem2Content)(({ theme }) => ({ ## Hooks -### useTreeItem2 +### useTreeItem -The `useTreeItem2` hook lets you manage and customize individual Tree Items. +The `useTreeItem` hook lets you manage and customize individual Tree Items. You can use it to get the properties needed for all slots, the status of any given Item, or to tap into the interactive API of the Tree View. #### Slot properties -The `useTreeItem2` hook gives you granular control over an Item's layout by providing resolvers to get the appropriate props for each slot. +The `useTreeItem` hook gives you granular control over an Item's layout by providing resolvers to get the appropriate props for each slot. This makes it possible to build a fully custom layout for your Tree Items. The demo below shows how to get the props needed for each slot, and how to pass them correctly. -{{"demo": "useTreeItem2HookProperties.js"}} +{{"demo": "useTreeItemHookProperties.js"}} You can pass additional props to a slot—or override existing slots—by passing an object argument to the slot's props resolver, as shown below: @@ -159,34 +159,34 @@ You can pass additional props to a slot—or override existing slots—by passin #### Item status -The `useTreeItem2` hook also returns a `status` object that holds boolean values for each possible state of a Tree Item. +The `useTreeItem` hook also returns a `status` object that holds boolean values for each possible state of a Tree Item. ```jsx const { status: { expanded, expandable, focused, selected, disabled, editable, editing }, -} = useTreeItem2(props); +} = useTreeItem(props); ``` You can use these statuses to apply custom styling to the item or conditionally render subcomponents. -{{"demo": "useTreeItem2HookStatus.js"}} +{{"demo": "useTreeItemHookStatus.js"}} #### Imperative API The `publicAPI` object provides a number of methods to programmatically interact with the Tree View. -You can use the `useTreeItem2` hook to access the `publicAPI` object from within a Tree Item. +You can use the `useTreeItem` hook to access the `publicAPI` object from within a Tree Item. -{{"demo": "useTreeItem2HookPublicAPI.js"}} +{{"demo": "useTreeItemHookPublicAPI.js"}} See the **Imperative API** section on each feature page to learn more about the public API methods available on the Tree View. -### `useTreeItem2Utils` +### `useTreeItemUtils` -The `useTreeItem2Utils` hook provides a set of interaction methods for implementing custom behaviors for the Tree View. +The `useTreeItemUtils` hook provides a set of interaction methods for implementing custom behaviors for the Tree View. It also returns the status of the Item. ```jsx -const { interactions, status } = useTreeItem2Utils({ +const { interactions, status } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -225,7 +225,7 @@ The demo below shows how to introduce a new element that expands and collapses t #### Label editing -The `useTreeItem2Utils` hook provides the following interaction methods relevant to label editing behavior: +The `useTreeItemUtils` hook provides the following interaction methods relevant to label editing behavior: ```jsx const { @@ -234,7 +234,7 @@ const { handleCancelItemLabelEditing, handleSaveItemLabel, }, -} = useTreeItem2Utils({ +} = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.js b/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.js deleted file mode 100644 index b93e60f39e001..0000000000000 --- a/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.js +++ /dev/null @@ -1,72 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; -import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; -import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; -import { MUI_X_PRODUCTS } from './products'; - -const CustomTreeItem = React.forwardRef(function CustomTreeItem( - { id, itemId, label, disabled, children }, - ref, -) { - const { - getRootProps, - getContentProps, - getIconContainerProps, - getCheckboxProps, - getLabelProps, - getLabelInputProps, - getGroupTransitionProps, - getDragAndDropOverlayProps, - status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); - - return ( - - - - - - - - {status.editing ? ( - - ) : ( - - )} - - - - {children && } - - - ); -}); - -export default function useTreeItem2HookProperties() { - return ( - - - - ); -} diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.tsx b/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.tsx deleted file mode 100644 index e2ec284be456a..0000000000000 --- a/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; -import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, - TreeItem2Props, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; -import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; -import { MUI_X_PRODUCTS } from './products'; - -const CustomTreeItem = React.forwardRef(function CustomTreeItem( - { id, itemId, label, disabled, children }: TreeItem2Props, - ref: React.Ref, -) { - const { - getRootProps, - getContentProps, - getIconContainerProps, - getCheckboxProps, - getLabelProps, - getLabelInputProps, - getGroupTransitionProps, - getDragAndDropOverlayProps, - status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); - - return ( - - - - - - - - {status.editing ? ( - - ) : ( - - )} - - - - {children && } - - - ); -}); - -export default function useTreeItem2HookProperties() { - return ( - - - - ); -} diff --git a/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.js b/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.js new file mode 100644 index 0000000000000..f9683d989e7bb --- /dev/null +++ b/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.js @@ -0,0 +1,72 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; +import { + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { TreeItemLabelInput } from '@mui/x-tree-view/TreeItemLabelInput'; +import { MUI_X_PRODUCTS } from './products'; + +const CustomTreeItem = React.forwardRef(function CustomTreeItem( + { id, itemId, label, disabled, children }, + ref, +) { + const { + getRootProps, + getContentProps, + getIconContainerProps, + getCheckboxProps, + getLabelProps, + getLabelInputProps, + getGroupTransitionProps, + getDragAndDropOverlayProps, + status, + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); + + return ( + + + + + + + + {status.editing ? ( + + ) : ( + + )} + + + + {children && } + + + ); +}); + +export default function useTreeItemHookProperties() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.tsx b/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.tsx new file mode 100644 index 0000000000000..8e49e71df5474 --- /dev/null +++ b/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.tsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; +import { + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + TreeItemProps, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { TreeItemLabelInput } from '@mui/x-tree-view/TreeItemLabelInput'; +import { MUI_X_PRODUCTS } from './products'; + +const CustomTreeItem = React.forwardRef(function CustomTreeItem( + { id, itemId, label, disabled, children }: TreeItemProps, + ref: React.Ref, +) { + const { + getRootProps, + getContentProps, + getIconContainerProps, + getCheckboxProps, + getLabelProps, + getLabelInputProps, + getGroupTransitionProps, + getDragAndDropOverlayProps, + status, + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); + + return ( + + + + + + + + {status.editing ? ( + + ) : ( + + )} + + + + {children && } + + + ); +}); + +export default function useTreeItemHookProperties() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.tsx.preview b/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.tsx.preview similarity index 100% rename from docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.tsx.preview rename to docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.tsx.preview diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookPublicAPI.js b/docs/data/tree-view/tree-item-customization/useTreeItemHookPublicAPI.js similarity index 84% rename from docs/data/tree-view/tree-item-customization/useTreeItem2HookPublicAPI.js rename to docs/data/tree-view/tree-item-customization/useTreeItemHookPublicAPI.js index 086d8d6ff0867..72886928944de 100644 --- a/docs/data/tree-view/tree-item-customization/useTreeItem2HookPublicAPI.js +++ b/docs/data/tree-view/tree-item-customization/useTreeItemHookPublicAPI.js @@ -4,8 +4,8 @@ import Typography from '@mui/material/Typography'; import Chip from '@mui/material/Chip'; import Stack from '@mui/material/Stack'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { MUI_X_PRODUCTS } from './products'; function CustomLabel({ children, className, numberOfChildren }) { @@ -25,12 +25,12 @@ function CustomLabel({ children, className, numberOfChildren }) { } const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { - const { publicAPI } = useTreeItem2(props); + const { publicAPI } = useTreeItem(props); const childrenNumber = publicAPI.getItemOrderedChildrenIds(props.itemId).length; return ( - , ) { - const { publicAPI } = useTreeItem2(props); + const { publicAPI } = useTreeItem(props); const childrenNumber = publicAPI.getItemOrderedChildrenIds(props.itemId).length; return ( - - - - - - + + + + + + {status.editing ? ( - + ) : ( - + )} @@ -123,14 +123,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( return null; })} - - {children && } - - + + {children && } + + ); }); -export default function useTreeItem2HookStatus() { +export default function useTreeItemHookStatus() { return ( diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookStatus.tsx b/docs/data/tree-view/tree-item-customization/useTreeItemHookStatus.tsx similarity index 76% rename from docs/data/tree-view/tree-item-customization/useTreeItem2HookStatus.tsx rename to docs/data/tree-view/tree-item-customization/useTreeItemHookStatus.tsx index 2ea3153a42cef..d03d53ca63d01 100644 --- a/docs/data/tree-view/tree-item-customization/useTreeItem2HookStatus.tsx +++ b/docs/data/tree-view/tree-item-customization/useTreeItemHookStatus.tsx @@ -11,18 +11,18 @@ import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import DrawOutlinedIcon from '@mui/icons-material/DrawOutlined'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2, UseTreeItem2Status } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemStatus } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2Root, - TreeItem2Props, - TreeItem2GroupTransition, - TreeItem2IconContainer, - TreeItem2Label, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; + TreeItemContent, + TreeItemRoot, + TreeItemProps, + TreeItemGroupTransition, + TreeItemIconContainer, + TreeItemLabel, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemLabelInput } from '@mui/x-tree-view/TreeItemLabelInput'; import { MUI_X_PRODUCTS } from './products'; function StatusLegend() { @@ -75,7 +75,7 @@ function StatusLegend() { } const STATUS_ICONS: { - [K in keyof UseTreeItem2Status]: React.ReactNode; + [K in keyof UseTreeItemStatus]: React.ReactNode; } = { focused: , selected: , @@ -87,7 +87,7 @@ const STATUS_ICONS: { }; const CustomTreeItem = React.forwardRef(function CustomTreeItem( - { id, itemId, label, disabled, children }: TreeItem2Props, + { id, itemId, label, disabled, children }: TreeItemProps, ref: React.Ref, ) { const { @@ -98,24 +98,24 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getIconContainerProps, getLabelInputProps, status, - } = useTreeItem2({ id, itemId, label, disabled, children, rootRef: ref }); + } = useTreeItem({ id, itemId, label, disabled, children, rootRef: ref }); return ( - - - - - - + + + + + + {status.editing ? ( - + ) : ( - + )} - {(Object.keys(STATUS_ICONS) as [keyof UseTreeItem2Status]).map( + {(Object.keys(STATUS_ICONS) as [keyof UseTreeItemStatus]).map( (iconKey, index) => { if (status[iconKey]) { return ( @@ -128,14 +128,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }, )} - - {children && } - - + + {children && } + + ); }); -export default function useTreeItem2HookStatus() { +export default function useTreeItemHookStatus() { return ( diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookStatus.tsx.preview b/docs/data/tree-view/tree-item-customization/useTreeItemHookStatus.tsx.preview similarity index 100% rename from docs/data/tree-view/tree-item-customization/useTreeItem2HookStatus.tsx.preview rename to docs/data/tree-view/tree-item-customization/useTreeItemHookStatus.tsx.preview diff --git a/docs/data/tree-view-component-api-pages.ts b/docs/data/treeViewApiPages.ts similarity index 65% rename from docs/data/tree-view-component-api-pages.ts rename to docs/data/treeViewApiPages.ts index c46698f0ed3dd..b503ea53e2c8b 100644 --- a/docs/data/tree-view-component-api-pages.ts +++ b/docs/data/treeViewApiPages.ts @@ -1,6 +1,6 @@ import type { MuiPage } from 'docs/src/MuiPage'; -const apiPages: MuiPage[] = [ +const treeViewApiPages: MuiPage[] = [ { pathname: '/x/api/tree-view/rich-tree-view', title: 'RichTreeView', @@ -19,12 +19,16 @@ const apiPages: MuiPage[] = [ title: 'TreeItem', }, { - pathname: '/x/api/tree-view/tree-item-2', - title: 'TreeItem2', + pathname: '/x/api/tree-view/tree-item-drag-and-drop-overlay', + title: 'TreeItemDragAndDropOverlay', + }, + { + pathname: '/x/api/tree-view/tree-item-icon', + title: 'TreeItemIcon', }, { pathname: '/x/api/tree-view/tree-view', title: 'TreeView', }, ]; -export default apiPages; +export default treeViewApiPages; diff --git a/docs/package.json b/docs/package.json index f1559e60742dc..4e7d0638b4abd 100644 --- a/docs/package.json +++ b/docs/package.json @@ -9,7 +9,7 @@ "build:clean": "rimraf .next && pnpm build", "build-sw": "node ./scripts/buildServiceWorker.js", "dev": "next dev --port 3001", - "deploy": "git push -f upstream master:docs-v7", + "deploy": "git push -f upstream master:docs-next", "icons": "rimraf public/static/icons/* && node ./scripts/buildIcons.js", "serve": "serve ./export -l 3010", "create-playground": "cpy --cwd=scripts playground.template.tsx ../../pages/playground --rename=index.tsx", @@ -19,15 +19,15 @@ "populate:demos": "tsx scripts/populatePickersDemos" }, "dependencies": { - "@babel/core": "^7.25.8", - "@babel/runtime": "^7.25.7", - "@babel/runtime-corejs2": "^7.25.7", + "@babel/core": "^7.26.0", + "@babel/runtime": "^7.26.0", + "@babel/runtime-corejs2": "^7.26.0", "@docsearch/react": "^3.6.2", "@emotion/cache": "^11.13.1", "@emotion/react": "^11.13.3", "@emotion/server": "^11.11.0", "@emotion/styled": "^11.13.0", - "@mui/docs": "6.1.2", + "@mui/docs": "6.1.6", "@mui/icons-material": "^5.16.7", "@mui/joy": "^5.0.0-beta.48", "@mui/lab": "^5.0.0-alpha.173", @@ -46,7 +46,7 @@ "@mui/x-date-pickers-pro": "workspace:*", "@mui/x-tree-view": "workspace:*", "@react-spring/web": "^9.7.5", - "@tanstack/query-core": "^5.59.13", + "@tanstack/query-core": "^5.59.16", "ast-types": "^0.14.2", "autoprefixer": "^10.4.20", "babel-plugin-module-resolver": "^5.0.2", @@ -76,7 +76,7 @@ "moment-hijri": "^2.30.0", "moment-jalaali": "^0.10.1", "moment-timezone": "^0.5.46", - "next": "^14.2.15", + "next": "^14.2.16", "nprogress": "^0.2.0", "postcss": "^8.4.47", "prismjs": "^1.29.0", @@ -84,13 +84,14 @@ "react": "^18.3.1", "react-docgen": "^5.4.3", "react-dom": "^18.3.1", - "react-hook-form": "^7.53.0", + "react-hook-form": "^7.53.1", "react-is": "^18.3.1", "react-router": "^6.27.0", "react-router-dom": "^6.27.0", "react-runner": "^1.0.5", "react-simple-code-editor": "^0.14.1", "recast": "^0.23.9", + "rifm": "0.12.1", "rimraf": "^6.0.1", "rxjs": "^7.8.1", "styled-components": "^6.1.13", @@ -99,16 +100,16 @@ "webpack-bundle-analyzer": "^4.10.2" }, "devDependencies": { - "@babel/plugin-transform-react-constant-elements": "^7.25.7", - "@babel/preset-typescript": "^7.25.7", - "@mui/internal-docs-utils": "^1.0.14", - "@mui/internal-scripts": "^1.0.22", + "@babel/plugin-transform-react-constant-elements": "^7.25.9", + "@babel/preset-typescript": "^7.26.0", + "@mui/internal-docs-utils": "^1.0.15", + "@mui/internal-scripts": "^1.0.26", "@types/chance": "^1.1.6", "@types/d3-scale": "^4.0.8", "@types/d3-scale-chromatic": "^3.0.3", "@types/doctrine": "^0.0.9", "@types/gtag.js": "^0.0.20", - "@types/lodash": "^4.17.10", + "@types/lodash": "^4.17.12", "@types/luxon": "^3.4.2", "@types/moment-hijri": "^2.1.4", "@types/moment-jalaali": "^0.7.9", @@ -118,6 +119,6 @@ "@types/stylis": "^4.2.6", "@types/webpack-bundle-analyzer": "^4.7.0", "gm": "^1.25.0", - "serve": "^14.2.3" + "serve": "^14.2.4" } } diff --git a/docs/pages/_app.js b/docs/pages/_app.js index 08a025daad506..8f1471a414c70 100644 --- a/docs/pages/_app.js +++ b/docs/pages/_app.js @@ -30,9 +30,9 @@ LicenseInfo.setLicenseKey(process.env.NEXT_PUBLIC_MUI_LICENSE); function getMuiPackageVersion(packageName, commitRef) { if (commitRef === undefined) { // #default-branch-switch - // Use the "latest" npm tag for the master git branch - // Use the "next" npm tag for the next git branch - return 'latest'; + // Use the "next" tag for the master git branch after we start working on the next major version + // Once the major release is finished we can go back to "latest" + return 'next'; } const shortSha = commitRef.slice(0, 8); return `https://pkg.csb.dev/mui/mui-x/commit/${shortSha}/@mui/${packageName}`; @@ -53,7 +53,7 @@ ponyfillGlobal.muiDocConfig = { return newDeps; }, csbGetVersions: (versions, { muiCommitRef }) => { - const output = { + return { ...versions, '@mui/x-data-grid': getMuiPackageVersion('x-data-grid', muiCommitRef), '@mui/x-data-grid-pro': getMuiPackageVersion('x-data-grid-pro', muiCommitRef), @@ -68,7 +68,6 @@ ponyfillGlobal.muiDocConfig = { '@mui/x-internals': getMuiPackageVersion('x-internals', muiCommitRef), exceljs: 'latest', }; - return output; }, postProcessImport, }; @@ -206,17 +205,34 @@ function AppWrapper(props) { const pageContextValue = React.useMemo(() => { const { activePage, activePageParents } = findActivePage(pages, router.pathname); const languagePrefix = pageProps.userLanguage === 'en' ? '' : `/${pageProps.userLanguage}`; + const productIdSubpathMap = { + introduction: '/x/introduction', + 'x-data-grid': '/x/react-data-grid', + 'x-date-pickers': '/x/react-date-pickers', + 'x-charts': '/x/react-charts', + 'x-tree-view': '/x/react-tree-view', + }; + + const getVersionOptions = (id, versions) => + versions.map((version) => { + if (version === process.env.LIB_VERSION) { + return { + current: true, + text: `v${version}`, + href: `${languagePrefix}${productIdSubpathMap[id]}/`, + }; + } + return { + text: version, + href: `https://${version}.mui.com${languagePrefix}${productIdSubpathMap[id]}/`, + }; + }); let productIdentifier = { metadata: '', name: 'MUI X', versions: [ - { - text: `v${process.env.LIB_VERSION}`, - current: true, - }, - { text: 'v6', href: `https://v6.mui.com${languagePrefix}/x/introduction/` }, - { text: 'v5', href: `https://v5.mui.com${languagePrefix}/x/introduction/` }, + ...getVersionOptions('introduction', ['next', process.env.LIB_VERSION, 'v6', 'v5']), { text: 'v4', href: `https://v4.mui.com${languagePrefix}/components/data-grid/` }, ], }; @@ -226,12 +242,7 @@ function AppWrapper(props) { metadata: 'MUI X', name: 'Data Grid', versions: [ - { - text: `v${process.env.DATA_GRID_VERSION}`, - current: true, - }, - { text: 'v6', href: `https://v6.mui.com${languagePrefix}/x/react-data-grid/` }, - { text: 'v5', href: `https://v5.mui.com${languagePrefix}/x/react-data-grid/` }, + ...getVersionOptions('x-data-grid', ['next', process.env.DATA_GRID_VERSION, 'v6', 'v5']), { text: 'v4', href: `https://v4.mui.com${languagePrefix}/components/data-grid/` }, ], }; @@ -240,14 +251,7 @@ function AppWrapper(props) { metadata: 'MUI X', name: 'Date Pickers', versions: [ - { - text: `v${process.env.DATE_PICKERS_VERSION}`, - current: true, - }, - { - text: 'v6', - href: `https://v6.mui.com${languagePrefix}/x/react-date-pickers/`, - }, + ...getVersionOptions('x-date-pickers', ['next', process.env.DATE_PICKERS_VERSION, 'v6']), { text: 'v5', href: `https://v5.mui.com${languagePrefix}/x/react-date-pickers/getting-started/`, @@ -258,23 +262,14 @@ function AppWrapper(props) { productIdentifier = { metadata: 'MUI X', name: 'Charts', - versions: [ - { - text: `v${process.env.CHARTS_VERSION}`, - current: true, - }, - { text: 'v6', href: `https://v6.mui.com${languagePrefix}/x/react-charts/` }, - ], + versions: getVersionOptions('x-charts', ['next', process.env.CHARTS_VERSION, 'v6']), }; } else if (productId === 'x-tree-view') { productIdentifier = { metadata: 'MUI X', name: 'Tree View', versions: [ - { - text: `v${process.env.TREE_VIEW_VERSION}`, - current: true, - }, + ...getVersionOptions('x-tree-view', ['next', process.env.TREE_VIEW_VERSION]), { text: 'v6', href: `https://v6.mui.com${languagePrefix}/x/react-tree-view/getting-started`, diff --git a/docs/pages/x/api/charts/bar-series-type.json b/docs/pages/x/api/charts/bar-series-type.json index cf2252d4cc89d..29c14e4317f73 100644 --- a/docs/pages/x/api/charts/bar-series-type.json +++ b/docs/pages/x/api/charts/bar-series-type.json @@ -17,8 +17,6 @@ "stackOrder": { "type": { "description": "StackOrderType" }, "default": "'none'" }, "valueFormatter": { "type": { "description": "SeriesValueFormatter<TValue>" } }, "xAxisId": { "type": { "description": "string" } }, - "xAxisKey": { "type": { "description": "string" } }, - "yAxisId": { "type": { "description": "string" } }, - "yAxisKey": { "type": { "description": "string" } } + "yAxisId": { "type": { "description": "string" } } } } diff --git a/docs/pages/x/api/charts/line-series-type.json b/docs/pages/x/api/charts/line-series-type.json index 147f90302c1d5..7833c73708042 100644 --- a/docs/pages/x/api/charts/line-series-type.json +++ b/docs/pages/x/api/charts/line-series-type.json @@ -22,8 +22,6 @@ "stackOrder": { "type": { "description": "StackOrderType" }, "default": "'none'" }, "valueFormatter": { "type": { "description": "SeriesValueFormatter<TValue>" } }, "xAxisId": { "type": { "description": "string" } }, - "xAxisKey": { "type": { "description": "string" } }, - "yAxisId": { "type": { "description": "string" } }, - "yAxisKey": { "type": { "description": "string" } } + "yAxisId": { "type": { "description": "string" } } } } diff --git a/docs/pages/x/api/charts/pie-chart.json b/docs/pages/x/api/charts/pie-chart.json index 1e8d0d773d8a7..33ae8035bbe3f 100644 --- a/docs/pages/x/api/charts/pie-chart.json +++ b/docs/pages/x/api/charts/pie-chart.json @@ -4,21 +4,6 @@ "type": { "name": "arrayOf", "description": "Array<object>" }, "required": true }, - "axisHighlight": { - "type": { - "name": "shape", - "description": "{ x?: 'band'
| 'line'
| 'none', y?: 'band'
| 'line'
| 'none' }" - }, - "default": "{ x: 'none', y: 'none' }", - "seeMoreLink": { - "url": "https://mui.com/x/react-charts/highlighting", - "text": "highlighting docs" - } - }, - "bottomAxis": { - "type": { "name": "union", "description": "object
| string" }, - "default": "null" - }, "colors": { "type": { "name": "union", "description": "Array<string>
| func" }, "default": "blueberryTwilightPalette" @@ -32,19 +17,6 @@ "description": "{ dataIndex?: number, seriesId?: number
| string }" } }, - "leftAxis": { - "type": { "name": "union", "description": "object
| string" }, - "default": "null" - }, - "legend": { - "type": { - "name": "shape", - "description": "{ classes?: object, direction?: 'column'
| 'row', hidden?: bool, itemGap?: number, itemMarkHeight?: number, itemMarkWidth?: number, labelStyle?: object, markGap?: number, onItemClick?: func, 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, - "deprecationInfo": "Consider using slotProps.legend instead." - }, "loading": { "type": { "name": "bool" }, "default": "false" }, "margin": { "type": { @@ -62,10 +34,6 @@ }, "onItemClick": { "type": { "name": "func" } }, "resolveSizeBeforeRender": { "type": { "name": "bool" }, "default": "false" }, - "rightAxis": { - "type": { "name": "union", "description": "object
| string" }, - "default": "null" - }, "skipAnimation": { "type": { "name": "bool" } }, "slotProps": { "type": { "name": "object" }, "default": "{}" }, "slots": { @@ -81,10 +49,6 @@ "default": "{ trigger: 'item' }", "seeMoreLink": { "url": "https://mui.com/x/react-charts/tooltip/", "text": "tooltip docs" } }, - "topAxis": { - "type": { "name": "union", "description": "object
| string" }, - "default": "null" - }, "width": { "type": { "name": "number" } }, "xAxis": { "type": { @@ -112,30 +76,6 @@ "default": "DefaultChartsAxisTooltipContent", "class": null }, - { - "name": "axisLabel", - "description": "Custom component for axis label.", - "default": "ChartsText", - "class": null - }, - { - "name": "axisLine", - "description": "Custom component for the axis main line.", - "default": "'line'", - "class": null - }, - { - "name": "axisTick", - "description": "Custom component for the axis tick.", - "default": "'line'", - "class": null - }, - { - "name": "axisTickLabel", - "description": "Custom component for tick label.", - "default": "ChartsText", - "class": null - }, { "name": "itemContent", "description": "Custom component for displaying tooltip content when triggered by item event.", diff --git a/docs/pages/x/api/charts/scatter-series-type.json b/docs/pages/x/api/charts/scatter-series-type.json index 9d560abcee2dd..5f422acb68962 100644 --- a/docs/pages/x/api/charts/scatter-series-type.json +++ b/docs/pages/x/api/charts/scatter-series-type.json @@ -19,10 +19,7 @@ "markerSize": { "type": { "description": "number" } }, "valueFormatter": { "type": { "description": "SeriesValueFormatter<TValue>" } }, "xAxisId": { "type": { "description": "string" } }, - "xAxisKey": { "type": { "description": "string" } }, "yAxisId": { "type": { "description": "string" } }, - "yAxisKey": { "type": { "description": "string" } }, - "zAxisId": { "type": { "description": "string" } }, - "zAxisKey": { "type": { "description": "string" } } + "zAxisId": { "type": { "description": "string" } } } } diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index 1667cca1371e9..ded5c1bd3c907 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -588,7 +588,11 @@ "rowGroupingModel": { "type": { "name": "arrayOf", "description": "Array<string>" } }, "rowHeight": { "type": { "name": "number" }, "default": "52" }, "rowModesModel": { "type": { "name": "object" } }, - "rowPositionsDebounceMs": { "type": { "name": "number" }, "default": "166" }, + "rowPositionsDebounceMs": { + "type": { "name": "number" }, + "default": "166", + "deprecated": true + }, "rowReordering": { "type": { "name": "bool" }, "default": "false" }, "rows": { "type": { "name": "arrayOf", "description": "Array<object>" }, @@ -645,6 +649,13 @@ }, "throttleRowsMs": { "type": { "name": "number" }, "default": "0" }, "treeData": { "type": { "name": "bool" }, "default": "false" }, + "unstable_listColumn": { + "type": { + "name": "shape", + "description": "{ align?: 'center'
| 'left'
| 'right', cellClassName?: func
| string, display?: 'flex'
| 'text', field: string, renderCell?: func }" + } + }, + "unstable_listView": { "type": { "name": "bool" } }, "unstable_rowSpanning": { "type": { "name": "bool" }, "default": "false" } }, "name": "DataGridPremium", @@ -785,12 +796,36 @@ "default": "GridRow", "class": "MuiDataGridPremium-row" }, + { + "name": "baseBadge", + "description": "The custom Badge component used in the grid for both header and cells.", + "default": "Badge", + "class": null + }, { "name": "baseCheckbox", "description": "The custom Checkbox component used in the grid for both header and cells.", "default": "Checkbox", "class": null }, + { + "name": "baseDivider", + "description": "The custom Divider component used in the grid.", + "default": "Divider", + "class": null + }, + { + "name": "baseMenuList", + "description": "The custom MenuList component used in the grid.", + "default": "MenuList", + "class": null + }, + { + "name": "baseMenuItem", + "description": "The custom MenuItem component used in the grid.", + "default": "MenuItem", + "class": null + }, { "name": "baseInputAdornment", "description": "The custom InputAdornment component used in the grid.", diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index cc714abf8fba2..42df3c1393111 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -523,7 +523,11 @@ "rowCount": { "type": { "name": "number" } }, "rowHeight": { "type": { "name": "number" }, "default": "52" }, "rowModesModel": { "type": { "name": "object" } }, - "rowPositionsDebounceMs": { "type": { "name": "number" }, "default": "166" }, + "rowPositionsDebounceMs": { + "type": { "name": "number" }, + "default": "166", + "deprecated": true + }, "rowReordering": { "type": { "name": "bool" }, "default": "false" }, "rows": { "type": { "name": "arrayOf", "description": "Array<object>" }, @@ -576,6 +580,13 @@ }, "throttleRowsMs": { "type": { "name": "number" }, "default": "0" }, "treeData": { "type": { "name": "bool" }, "default": "false" }, + "unstable_listColumn": { + "type": { + "name": "shape", + "description": "{ align?: 'center'
| 'left'
| 'right', cellClassName?: func
| string, display?: 'flex'
| 'text', field: string, renderCell?: func }" + } + }, + "unstable_listView": { "type": { "name": "bool" } }, "unstable_rowSpanning": { "type": { "name": "bool" }, "default": "false" } }, "name": "DataGridPro", @@ -717,12 +728,36 @@ "default": "GridRow", "class": "MuiDataGridPro-row" }, + { + "name": "baseBadge", + "description": "The custom Badge component used in the grid for both header and cells.", + "default": "Badge", + "class": null + }, { "name": "baseCheckbox", "description": "The custom Checkbox component used in the grid for both header and cells.", "default": "Checkbox", "class": null }, + { + "name": "baseDivider", + "description": "The custom Divider component used in the grid.", + "default": "Divider", + "class": null + }, + { + "name": "baseMenuList", + "description": "The custom MenuList component used in the grid.", + "default": "MenuList", + "class": null + }, + { + "name": "baseMenuItem", + "description": "The custom MenuItem component used in the grid.", + "default": "MenuItem", + "class": null + }, { "name": "baseInputAdornment", "description": "The custom InputAdornment component used in the grid.", diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 3fd158be8aa04..9994e0dc63178 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -439,7 +439,11 @@ "rowCount": { "type": { "name": "number" } }, "rowHeight": { "type": { "name": "number" }, "default": "52" }, "rowModesModel": { "type": { "name": "object" } }, - "rowPositionsDebounceMs": { "type": { "name": "number" }, "default": "166" }, + "rowPositionsDebounceMs": { + "type": { "name": "number" }, + "default": "166", + "deprecated": true + }, "rows": { "type": { "name": "arrayOf", "description": "Array<object>" }, "default": "[]" @@ -611,12 +615,36 @@ "default": "GridRow", "class": "MuiDataGrid-row" }, + { + "name": "baseBadge", + "description": "The custom Badge component used in the grid for both header and cells.", + "default": "Badge", + "class": null + }, { "name": "baseCheckbox", "description": "The custom Checkbox component used in the grid for both header and cells.", "default": "Checkbox", "class": null }, + { + "name": "baseDivider", + "description": "The custom Divider component used in the grid.", + "default": "Divider", + "class": null + }, + { + "name": "baseMenuList", + "description": "The custom MenuList component used in the grid.", + "default": "MenuList", + "class": null + }, + { + "name": "baseMenuItem", + "description": "The custom MenuItem component used in the grid.", + "default": "MenuItem", + "class": null + }, { "name": "baseInputAdornment", "description": "The custom InputAdornment component used in the grid.", diff --git a/docs/pages/x/api/data-grid/grid-print-export-options.json b/docs/pages/x/api/data-grid/grid-print-export-options.json index 6d19c1df3ffc0..09e234b76a5a0 100644 --- a/docs/pages/x/api/data-grid/grid-print-export-options.json +++ b/docs/pages/x/api/data-grid/grid-print-export-options.json @@ -18,6 +18,6 @@ "hideFooter": { "type": { "description": "boolean" }, "default": "false" }, "hideToolbar": { "type": { "description": "boolean" }, "default": "false" }, "includeCheckboxes": { "type": { "description": "boolean" }, "default": "false" }, - "pageStyle": { "type": { "description": "string | Function" } } + "pageStyle": { "type": { "description": "string | (() => string)" } } } } diff --git a/docs/pages/x/api/tree-view/rich-tree-view-pro.json b/docs/pages/x/api/tree-view/rich-tree-view-pro.json index 7cd100636eb8f..79efa85605209 100644 --- a/docs/pages/x/api/tree-view/rich-tree-view-pro.json +++ b/docs/pages/x/api/tree-view/rich-tree-view-pro.json @@ -134,6 +134,10 @@ } }, "selectedItems": { "type": { "name": "any" } }, + "selectionPropagation": { + "type": { "name": "shape", "description": "{ descendants?: bool, parents?: bool }" }, + "default": "{ parents: false, descendants: false }" + }, "slotProps": { "type": { "name": "object" }, "default": "{}" }, "slots": { "type": { "name": "object" }, @@ -178,7 +182,7 @@ }, { "name": "endIcon", - "description": "The default icon displayed next to an end item.\nThis is applied to all tree items and can be overridden by the TreeItem `icon` slot prop.", + "description": "The default icon displayed next to an end item.\nThis is applied to all Tree Items and can be overridden by the TreeItem `icon` slot prop.", "class": null } ], diff --git a/docs/pages/x/api/tree-view/rich-tree-view.json b/docs/pages/x/api/tree-view/rich-tree-view.json index 53335f98fc5e9..83ada41f5bc56 100644 --- a/docs/pages/x/api/tree-view/rich-tree-view.json +++ b/docs/pages/x/api/tree-view/rich-tree-view.json @@ -109,6 +109,10 @@ } }, "selectedItems": { "type": { "name": "any" } }, + "selectionPropagation": { + "type": { "name": "shape", "description": "{ descendants?: bool, parents?: bool }" }, + "default": "{ parents: false, descendants: false }" + }, "slotProps": { "type": { "name": "object" }, "default": "{}" }, "slots": { "type": { "name": "object" }, @@ -153,7 +157,7 @@ }, { "name": "endIcon", - "description": "The default icon displayed next to an end item.\nThis is applied to all tree items and can be overridden by the TreeItem `icon` slot prop.", + "description": "The default icon displayed next to an end item.\nThis is applied to all Tree Items and can be overridden by the TreeItem `icon` slot prop.", "class": null } ], @@ -164,6 +168,6 @@ "forwardsRefTo": "HTMLUListElement", "filename": "/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx", "inheritance": null, - "demos": "", + "demos": "", "cssComponent": false } diff --git a/docs/pages/x/api/tree-view/simple-tree-view.json b/docs/pages/x/api/tree-view/simple-tree-view.json index 0e9d838a73275..bcf5153e9cb5e 100644 --- a/docs/pages/x/api/tree-view/simple-tree-view.json +++ b/docs/pages/x/api/tree-view/simple-tree-view.json @@ -73,6 +73,10 @@ } }, "selectedItems": { "type": { "name": "any" } }, + "selectionPropagation": { + "type": { "name": "shape", "description": "{ descendants?: bool, parents?: bool }" }, + "default": "{ parents: false, descendants: false }" + }, "slotProps": { "type": { "name": "object" } }, "slots": { "type": { "name": "object" }, "additionalInfo": { "slotsApi": true } }, "sx": { @@ -108,7 +112,7 @@ }, { "name": "endIcon", - "description": "The default icon displayed next to an end item.\nThis is applied to all tree items and can be overridden by the TreeItem `icon` slot prop.", + "description": "The default icon displayed next to an end item.\nThis is applied to all Tree Items and can be overridden by the TreeItem `icon` slot prop.", "class": null } ], diff --git a/docs/pages/x/api/tree-view/tree-item-2.json b/docs/pages/x/api/tree-view/tree-item-2.json deleted file mode 100644 index 547383a665af3..0000000000000 --- a/docs/pages/x/api/tree-view/tree-item-2.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "props": { - "itemId": { "type": { "name": "string" }, "required": true }, - "children": { "type": { "name": "node" } }, - "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, - "disabled": { "type": { "name": "bool" }, "default": "false" }, - "id": { "type": { "name": "string" } }, - "label": { "type": { "name": "node" } }, - "onBlur": { "type": { "name": "func" } }, - "onFocus": { "type": { "name": "custom", "description": "unsupportedProp" } }, - "onKeyDown": { "type": { "name": "func" } }, - "slotProps": { "type": { "name": "object" }, "default": "{}" }, - "slots": { - "type": { "name": "object" }, - "default": "{}", - "additionalInfo": { "slotsApi": true } - } - }, - "name": "TreeItem2", - "imports": [ - "import { TreeItem2 } from '@mui/x-tree-view/TreeItem2';", - "import { TreeItem2 } from '@mui/x-tree-view';", - "import { TreeItem2 } from '@mui/x-tree-view-pro';" - ], - "slots": [ - { - "name": "root", - "description": "The component that renders the root.", - "default": "TreeItem2Root", - "class": "MuiTreeItem2-root" - }, - { - "name": "content", - "description": "The component that renders the content of the item.\n(e.g.: everything related to this item, not to its children).", - "default": "TreeItem2Content", - "class": "MuiTreeItem2-content" - }, - { - "name": "groupTransition", - "description": "The component that renders the children of the item.", - "default": "TreeItem2GroupTransition", - "class": "MuiTreeItem2-groupTransition" - }, - { - "name": "iconContainer", - "description": "The component that renders the icon.", - "default": "TreeItem2IconContainer", - "class": "MuiTreeItem2-iconContainer" - }, - { - "name": "checkbox", - "description": "The component that renders the item checkbox for selection.", - "default": "TreeItem2Checkbox", - "class": "MuiTreeItem2-checkbox" - }, - { - "name": "label", - "description": "The component that renders the item label.", - "default": "TreeItem2Label", - "class": "MuiTreeItem2-label" - }, - { - "name": "labelInput", - "description": "The component that renders the input to edit the label when the item is editable and is currently being edited.", - "default": "TreeItem2LabelInput", - "class": "MuiTreeItem2-labelInput" - }, - { - "name": "dragAndDropOverlay", - "description": "The component that renders the overlay when an item reordering is ongoing.\nWarning: This slot is only useful when using the `RichTreeViewPro` component.", - "default": "TreeItem2DragAndDropOverlay", - "class": "MuiTreeItem2-dragAndDropOverlay" - }, - { "name": "collapseIcon", "description": "The icon used to collapse the item.", "class": null }, - { "name": "expandIcon", "description": "The icon used to expand the item.", "class": null }, - { "name": "endIcon", "description": "The icon displayed next to an end item.", "class": null }, - { - "name": "icon", - "description": "The icon to display next to the tree item's label.", - "class": null - } - ], - "classes": [ - { - "key": "disabled", - "className": "Mui-disabled", - "description": "State class applied to the element when disabled.", - "isGlobal": true - }, - { - "key": "editable", - "className": "MuiTreeItem2-editable", - "description": "Styles applied to the content of the items that are editable.", - "isGlobal": false - }, - { - "key": "editing", - "className": "MuiTreeItem2-editing", - "description": "Styles applied to the content element when editing is enabled.", - "isGlobal": false - }, - { - "key": "expanded", - "className": "Mui-expanded", - "description": "State class applied to the content element when expanded.", - "isGlobal": true - }, - { - "key": "focused", - "className": "Mui-focused", - "description": "State class applied to the content element when focused.", - "isGlobal": true - }, - { - "key": "selected", - "className": "Mui-selected", - "description": "State class applied to the content element when selected.", - "isGlobal": true - } - ], - "spread": true, - "themeDefaultProps": true, - "muiName": "MuiTreeItem2", - "forwardsRefTo": "HTMLLIElement", - "filename": "/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx", - "inheritance": null, - "demos": "", - "cssComponent": false -} diff --git a/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.js b/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.js new file mode 100644 index 0000000000000..256f275cd77ed --- /dev/null +++ b/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.js @@ -0,0 +1,23 @@ +import * as React from 'react'; +import ApiPage from 'docs/src/modules/components/ApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './tree-item-drag-and-drop-overlay.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docsx/translations/api-docs/tree-view/tree-item-drag-and-drop-overlay', + false, + /\.\/tree-item-drag-and-drop-overlay.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.json b/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.json new file mode 100644 index 0000000000000..8c5d38162293a --- /dev/null +++ b/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.json @@ -0,0 +1,15 @@ +{ + "props": {}, + "name": "TreeItemDragAndDropOverlay", + "imports": [ + "import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay';", + "import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view';", + "import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view-pro';" + ], + "classes": [], + "muiName": "MuiTreeItemDragAndDropOverlay", + "filename": "/packages/x-tree-view/src/TreeItemDragAndDropOverlay/TreeItemDragAndDropOverlay.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/x/api/tree-view/tree-item-2.js b/docs/pages/x/api/tree-view/tree-item-icon.js similarity index 78% rename from docs/pages/x/api/tree-view/tree-item-2.js rename to docs/pages/x/api/tree-view/tree-item-icon.js index 157c5febd2e14..866a7447a59d4 100644 --- a/docs/pages/x/api/tree-view/tree-item-2.js +++ b/docs/pages/x/api/tree-view/tree-item-icon.js @@ -1,7 +1,7 @@ import * as React from 'react'; import ApiPage from 'docs/src/modules/components/ApiPage'; import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; -import jsonPageContent from './tree-item-2.json'; +import jsonPageContent from './tree-item-icon.json'; export default function Page(props) { const { descriptions, pageContent } = props; @@ -10,9 +10,9 @@ export default function Page(props) { Page.getInitialProps = () => { const req = require.context( - 'docsx/translations/api-docs/tree-view/tree-item-2', + 'docsx/translations/api-docs/tree-view/tree-item-icon', false, - /\.\/tree-item-2.*.json$/, + /\.\/tree-item-icon.*.json$/, ); const descriptions = mapApiPageTranslations(req); diff --git a/docs/pages/x/api/tree-view/tree-item-icon.json b/docs/pages/x/api/tree-view/tree-item-icon.json new file mode 100644 index 0000000000000..adf8622e98fdb --- /dev/null +++ b/docs/pages/x/api/tree-view/tree-item-icon.json @@ -0,0 +1,32 @@ +{ + "props": { + "slotProps": { "type": { "name": "object" }, "default": "{}" }, + "slots": { + "type": { "name": "object" }, + "default": "{}", + "additionalInfo": { "slotsApi": true } + } + }, + "name": "TreeItemIcon", + "imports": [ + "import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon';", + "import { TreeItemIcon } from '@mui/x-tree-view';", + "import { TreeItemIcon } from '@mui/x-tree-view-pro';" + ], + "slots": [ + { "name": "collapseIcon", "description": "The icon used to collapse the item.", "class": null }, + { "name": "expandIcon", "description": "The icon used to expand the item.", "class": null }, + { "name": "endIcon", "description": "The icon displayed next to an end item.", "class": null }, + { + "name": "icon", + "description": "The icon to display next to the Tree Item's label.", + "class": null + } + ], + "classes": [], + "muiName": "MuiTreeItemIcon", + "filename": "/packages/x-tree-view/src/TreeItemIcon/TreeItemIcon.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/x/api/tree-view/tree-item.json b/docs/pages/x/api/tree-view/tree-item.json index 33853fb02fb1e..be0a273ec4c03 100644 --- a/docs/pages/x/api/tree-view/tree-item.json +++ b/docs/pages/x/api/tree-view/tree-item.json @@ -3,19 +3,10 @@ "itemId": { "type": { "name": "string" }, "required": true }, "children": { "type": { "name": "node" } }, "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, - "ContentComponent": { - "type": { "name": "custom", "description": "element type" }, - "default": "TreeItemContent", - "deprecated": true, - "deprecationInfo": "Consider using the TreeItem2 component or the useTreeItem2 hook instead. For more detail, see https://mui.com/x/react-tree-view/tree-item-customization/." - }, - "ContentProps": { - "type": { "name": "object" }, - "deprecated": true, - "deprecationInfo": "Consider using the TreeItem2 component or the useTreeItem2 hook instead. For more detail, see https://mui.com/x/react-tree-view/tree-item-customization/." - }, "disabled": { "type": { "name": "bool" }, "default": "false" }, + "id": { "type": { "name": "string" } }, "label": { "type": { "name": "node" } }, + "onBlur": { "type": { "name": "func" } }, "onFocus": { "type": { "name": "custom", "description": "unsupportedProp" } }, "onKeyDown": { "type": { "name": "func" } }, "slotProps": { "type": { "name": "object" }, "default": "{}" }, @@ -23,13 +14,6 @@ "type": { "name": "object" }, "default": "{}", "additionalInfo": { "slotsApi": true } - }, - "sx": { - "type": { - "name": "union", - "description": "Array<func
| object
| bool>
| func
| object" - }, - "additionalInfo": { "sx": true } } }, "name": "TreeItem", @@ -39,46 +23,70 @@ "import { TreeItem } from '@mui/x-tree-view-pro';" ], "slots": [ - { "name": "collapseIcon", "description": "The icon used to collapse the item.", "class": null }, - { "name": "expandIcon", "description": "The icon used to expand the item.", "class": null }, - { "name": "endIcon", "description": "The icon displayed next to an end item.", "class": null }, { - "name": "icon", - "description": "The icon to display next to the tree item's label.", - "class": null + "name": "root", + "description": "The component that renders the root.", + "default": "TreeItemRoot", + "class": "MuiTreeItem-root" + }, + { + "name": "content", + "description": "The component that renders the content of the item.\n(e.g.: everything related to this item, not to its children).", + "default": "TreeItemContent", + "class": "MuiTreeItem-content" }, { "name": "groupTransition", - "description": "The component that animates the appearance / disappearance of the item's children.", - "default": "TreeItem2Group", + "description": "The component that renders the children of the item.", + "default": "TreeItemGroupTransition", "class": "MuiTreeItem-groupTransition" - } - ], - "classes": [ + }, { - "key": "checkbox", - "className": "MuiTreeItem-checkbox", - "description": "Styles applied to the checkbox element.", - "isGlobal": false + "name": "iconContainer", + "description": "The component that renders the icon.", + "default": "TreeItemIconContainer", + "class": "MuiTreeItem-iconContainer" }, { - "key": "content", - "className": "MuiTreeItem-content", - "description": "Styles applied to the content element.", - "isGlobal": false + "name": "checkbox", + "description": "The component that renders the item checkbox for selection.", + "default": "TreeItemCheckbox", + "class": "MuiTreeItem-checkbox" }, + { + "name": "label", + "description": "The component that renders the item label.", + "default": "TreeItemLabel", + "class": "MuiTreeItem-label" + }, + { + "name": "labelInput", + "description": "The component that renders the input to edit the label when the item is editable and is currently being edited.", + "default": "TreeItemLabelInput", + "class": "MuiTreeItem-labelInput" + }, + { + "name": "dragAndDropOverlay", + "description": "The component that renders the overlay when an item reordering is ongoing.\nWarning: This slot is only useful when using the `` component.", + "default": "TreeItemDragAndDropOverlay", + "class": "MuiTreeItem-dragAndDropOverlay" + }, + { "name": "collapseIcon", "description": "The icon used to collapse the item.", "class": null }, + { "name": "expandIcon", "description": "The icon used to expand the item.", "class": null }, + { "name": "endIcon", "description": "The icon displayed next to an end item.", "class": null }, + { + "name": "icon", + "description": "The icon to display next to the Tree Item's label.", + "class": null + } + ], + "classes": [ { "key": "disabled", "className": "Mui-disabled", "description": "State class applied to the element when disabled.", "isGlobal": true }, - { - "key": "dragAndDropOverlay", - "className": "MuiTreeItem-dragAndDropOverlay", - "description": "Styles applied to the drag and drop overlay.", - "isGlobal": false - }, { "key": "editable", "className": "MuiTreeItem-editable", @@ -103,30 +111,6 @@ "description": "State class applied to the content element when focused.", "isGlobal": true }, - { - "key": "iconContainer", - "className": "MuiTreeItem-iconContainer", - "description": "Styles applied to the tree item icon.", - "isGlobal": false - }, - { - "key": "label", - "className": "MuiTreeItem-label", - "description": "Styles applied to the label element.", - "isGlobal": false - }, - { - "key": "labelInput", - "className": "MuiTreeItem-labelInput", - "description": "Styles applied to the input element that is visible when editing is enabled.", - "isGlobal": false - }, - { - "key": "root", - "className": "MuiTreeItem-root", - "description": "Styles applied to the root element.", - "isGlobal": false - }, { "key": "selected", "className": "Mui-selected", @@ -140,6 +124,6 @@ "forwardsRefTo": "HTMLLIElement", "filename": "/packages/x-tree-view/src/TreeItem/TreeItem.tsx", "inheritance": null, - "demos": "", + "demos": "", "cssComponent": false } diff --git a/docs/pages/x/api/tree-view/tree-view.json b/docs/pages/x/api/tree-view/tree-view.json index c03fa0e611a9e..c9f0691c2b7b6 100644 --- a/docs/pages/x/api/tree-view/tree-view.json +++ b/docs/pages/x/api/tree-view/tree-view.json @@ -73,6 +73,10 @@ } }, "selectedItems": { "type": { "name": "any" } }, + "selectionPropagation": { + "type": { "name": "shape", "description": "{ descendants?: bool, parents?: bool }" }, + "default": "{ parents: false, descendants: false }" + }, "slotProps": { "type": { "name": "object" } }, "slots": { "type": { "name": "object" }, "additionalInfo": { "slotsApi": true } }, "sx": { @@ -108,7 +112,7 @@ }, { "name": "endIcon", - "description": "The default icon displayed next to an end item.\nThis is applied to all tree items and can be overridden by the TreeItem `icon` slot prop.", + "description": "The default icon displayed next to an end item.\nThis is applied to all Tree Items and can be overridden by the TreeItem `icon` slot prop.", "class": null } ], diff --git a/docs/pages/x/migration/migration-charts-v7.js b/docs/pages/x/migration/migration-charts-v7.js new file mode 100644 index 0000000000000..09b47f90866e3 --- /dev/null +++ b/docs/pages/x/migration/migration-charts-v7.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/migration/migration-charts-v7/migration-charts-v7.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/migration/migration-data-grid-v7.js b/docs/pages/x/migration/migration-data-grid-v7.js new file mode 100644 index 0000000000000..454b6a6e813e5 --- /dev/null +++ b/docs/pages/x/migration/migration-data-grid-v7.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/migration/migration-data-grid-v7/migration-data-grid-v7.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/migration/migration-pickers-v7.js b/docs/pages/x/migration/migration-pickers-v7.js new file mode 100644 index 0000000000000..24de8699a05ff --- /dev/null +++ b/docs/pages/x/migration/migration-pickers-v7.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/migration/migration-pickers-v7/migration-pickers-v7.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/migration/migration-tree-view-v7.js b/docs/pages/x/migration/migration-tree-view-v7.js new file mode 100644 index 0000000000000..6dd80f8e2a2d9 --- /dev/null +++ b/docs/pages/x/migration/migration-tree-view-v7.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/migration/migration-tree-view-v7/migration-tree-view-v7.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/react-data-grid/list-view.js b/docs/pages/x/react-data-grid/list-view.js new file mode 100644 index 0000000000000..cb417266ff2ef --- /dev/null +++ b/docs/pages/x/react-data-grid/list-view.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/data-grid/list-view/list-view.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/public/static/x/tree-view-illustrations/tree-item-dark.png b/docs/public/static/x/tree-view-illustrations/tree-item-dark.png index 9cd285c3d2797..4067c0fb25a5d 100644 Binary files a/docs/public/static/x/tree-view-illustrations/tree-item-dark.png and b/docs/public/static/x/tree-view-illustrations/tree-item-dark.png differ diff --git a/docs/public/static/x/tree-view-illustrations/tree-item-light.png b/docs/public/static/x/tree-view-illustrations/tree-item-light.png index 052d8359525c4..355f6c8813406 100644 Binary files a/docs/public/static/x/tree-view-illustrations/tree-item-light.png and b/docs/public/static/x/tree-view-illustrations/tree-item-light.png differ diff --git a/docs/src/modules/components/ChartFeaturesGrid.js b/docs/src/modules/components/ChartFeaturesGrid.js index 36af272ba23c0..7da1be8678268 100644 --- a/docs/src/modules/components/ChartFeaturesGrid.js +++ b/docs/src/modules/components/ChartFeaturesGrid.js @@ -1,12 +1,16 @@ import * as React from 'react'; import Grid from '@mui/material/Grid'; import { InfoCard } from '@mui/docs/InfoCard'; -import LineAxisRoundedIcon from '@mui/icons-material/LineAxisRounded'; +import ChatBubbleRoundedIcon from '@mui/icons-material/ChatBubble'; import DashboardCustomizeRoundedIcon from '@mui/icons-material/DashboardCustomizeRounded'; +import ExtensionRoundedIcon from '@mui/icons-material/Extension'; +import HighlightRoundedIcon from '@mui/icons-material/Highlight'; +import LabelImportantRoundedIcon from '@mui/icons-material/LabelImportant'; import LegendToggleRoundedIcon from '@mui/icons-material/LegendToggleRounded'; +import LineAxisRoundedIcon from '@mui/icons-material/LineAxisRounded'; import StackedBarChartRoundedIcon from '@mui/icons-material/StackedBarChartRounded'; import StyleRoundedIcon from '@mui/icons-material/StyleRounded'; -import TipsAndUpdatesRoundedIcon from '@mui/icons-material/TipsAndUpdatesRounded'; +import ZoomInRoundedIcon from '@mui/icons-material/ZoomIn'; const content = [ { @@ -19,6 +23,16 @@ const content = [ link: '/x/react-charts/components/', icon: , }, + { + title: 'Composition', + link: '/x/react-charts/composition/', + icon: , + }, + { + title: 'Label', + link: '/x/react-charts/label/', + icon: , + }, { title: 'Legend', link: '/x/react-charts/legend/', @@ -35,9 +49,19 @@ const content = [ icon: , }, { - title: 'Tooltips and highlights', + title: 'Tooltips', link: '/x/react-charts/tooltip/', - icon: , + icon: , + }, + { + title: 'Highlighting', + link: '/x/react-charts/highlighting/', + icon: , + }, + { + title: 'Zoom and pan', + link: '/x/react-charts/zoom-and-pan/', + icon: , }, ]; @@ -45,7 +69,7 @@ export default function ChartFeaturesGrid() { return ( {content.map(({ icon, title, link }) => ( - + ))} diff --git a/docs/src/modules/components/ChartsInstallationInstructions.js b/docs/src/modules/components/ChartsInstallationInstructions.js index b11fd2a211354..eeb2d1af86270 100644 --- a/docs/src/modules/components/ChartsInstallationInstructions.js +++ b/docs/src/modules/components/ChartsInstallationInstructions.js @@ -4,8 +4,8 @@ import InstallationInstructions from './InstallationInstructions'; // #default-branch-switch const packages = { - Community: '@mui/x-charts', - Pro: '@mui/x-charts-pro', + Community: '@mui/x-charts@next', + Pro: '@mui/x-charts-pro@next', }; export default function DataGridInstallationInstructions() { diff --git a/docs/src/modules/components/DataGridInstallationInstructions.js b/docs/src/modules/components/DataGridInstallationInstructions.js index fd41a7bddc3e2..030004edd155f 100644 --- a/docs/src/modules/components/DataGridInstallationInstructions.js +++ b/docs/src/modules/components/DataGridInstallationInstructions.js @@ -4,9 +4,9 @@ import InstallationInstructions from './InstallationInstructions'; // #default-branch-switch const packages = { - Community: '@mui/x-data-grid', - Pro: '@mui/x-data-grid-pro', - Premium: '@mui/x-data-grid-premium', + Community: '@mui/x-data-grid@next', + Pro: '@mui/x-data-grid-pro@next', + Premium: '@mui/x-data-grid-premium@next', }; export default function DataGridInstallationInstructions() { diff --git a/docs/src/modules/components/PickersInstallationInstructions.js b/docs/src/modules/components/PickersInstallationInstructions.js index 1282d02797a64..bac6c64d7c20f 100644 --- a/docs/src/modules/components/PickersInstallationInstructions.js +++ b/docs/src/modules/components/PickersInstallationInstructions.js @@ -4,8 +4,8 @@ import InstallationInstructions from './InstallationInstructions'; // #default-branch-switch const packages = { - Community: '@mui/x-date-pickers', - Pro: '@mui/x-date-pickers-pro', + Community: '@mui/x-date-pickers@next', + Pro: '@mui/x-date-pickers-pro@next', }; const peerDependency = { diff --git a/docs/src/modules/components/TreeItem2Anatomy.js b/docs/src/modules/components/TreeItem2Anatomy.js deleted file mode 100644 index 2f46bb4dce947..0000000000000 --- a/docs/src/modules/components/TreeItem2Anatomy.js +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from 'react'; -import { useTheme } from '@mui/material/styles'; -import Card from '@mui/material/Card'; -import CardMedia from '@mui/material/CardMedia'; - -export default function TreeItem2Anatomy() { - const { palette } = useTheme(); - - const src = - palette.mode === 'light' - ? '/static/x/tree-view-illustrations/tree-item-light.png' - : '/static/x/tree-view-illustrations/tree-item-dark.png'; - return ( - - - - ); -} diff --git a/docs/src/modules/components/TreeViewInstallationInstructions.js b/docs/src/modules/components/TreeViewInstallationInstructions.js index d05d2ebba846e..fdec6c8995099 100644 --- a/docs/src/modules/components/TreeViewInstallationInstructions.js +++ b/docs/src/modules/components/TreeViewInstallationInstructions.js @@ -4,8 +4,8 @@ import InstallationInstructions from './InstallationInstructions'; // #default-branch-switch const packages = { - Community: '@mui/x-tree-view', - Pro: '@mui/x-tree-view-pro', + Community: '@mui/x-tree-view@next', + Pro: '@mui/x-tree-view-pro@next', }; export default function TreeViewInstallationInstructions() { diff --git a/docs/src/modules/components/overview/Keyboard.tsx b/docs/src/modules/components/overview/Keyboard.tsx index 2d0a141601f0f..1f4beb6a67467 100644 --- a/docs/src/modules/components/overview/Keyboard.tsx +++ b/docs/src/modules/components/overview/Keyboard.tsx @@ -487,7 +487,6 @@ export default function Keyboard() { onKeyUp={() => { setSelectedKey(null); }} - enableAccessibleFieldDOMStructure onSelectedSectionsChange={(newSelectedSection) => { selectedSection.current = newSelectedSection; }} diff --git a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx index 0dc82b364e0f4..27b6857feed54 100644 --- a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx +++ b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx @@ -12,8 +12,8 @@ import { } from '@mui/x-date-pickers/models'; interface ButtonFieldProps - extends UseDateFieldProps, - BaseSingleInputFieldProps { + extends UseDateFieldProps, + BaseSingleInputFieldProps { setOpen?: React.Dispatch>; } diff --git a/docs/translations/api-docs/charts/bar-series-type.json b/docs/translations/api-docs/charts/bar-series-type.json index 179d3e15bd8ee..ebb278d10e61c 100644 --- a/docs/translations/api-docs/charts/bar-series-type.json +++ b/docs/translations/api-docs/charts/bar-series-type.json @@ -22,8 +22,6 @@ "description": "Formatter used to render values in tooltip or other data display." }, "xAxisId": { "description": "The id of the x-axis used to render the series." }, - "xAxisKey": { "description": "The id of the x-axis used to render the series." }, - "yAxisId": { "description": "The id of the y-axis used to render the series." }, - "yAxisKey": { "description": "The id of the y-axis used to render the series." } + "yAxisId": { "description": "The id of the y-axis used to render the series." } } } diff --git a/docs/translations/api-docs/charts/line-series-type.json b/docs/translations/api-docs/charts/line-series-type.json index c7c871a6b8e8c..62a540a7437ad 100644 --- a/docs/translations/api-docs/charts/line-series-type.json +++ b/docs/translations/api-docs/charts/line-series-type.json @@ -37,8 +37,6 @@ "description": "Formatter used to render values in tooltip or other data display." }, "xAxisId": { "description": "The id of the x-axis used to render the series." }, - "xAxisKey": { "description": "The id of the x-axis used to render the series." }, - "yAxisId": { "description": "The id of the y-axis used to render the series." }, - "yAxisKey": { "description": "The id of the y-axis used to render the series." } + "yAxisId": { "description": "The id of the y-axis used to render the series." } } } diff --git a/docs/translations/api-docs/charts/pie-chart/pie-chart.json b/docs/translations/api-docs/charts/pie-chart/pie-chart.json index 86d14a746ffb3..fe7624f98324b 100644 --- a/docs/translations/api-docs/charts/pie-chart/pie-chart.json +++ b/docs/translations/api-docs/charts/pie-chart/pie-chart.json @@ -1,13 +1,6 @@ { "componentDescription": "", "propDescriptions": { - "axisHighlight": { - "description": "The configuration of axes highlight.", - "seeMoreText": "See {{link}} for more details." - }, - "bottomAxis": { - "description": "Indicate which axis to display the bottom of the charts. Can be a string (the id of the axis) or an object ChartsXAxisProps." - }, "colors": { "description": "Color palette used to colorize multiple series." }, "dataset": { "description": "An array of objects that can be used to populate series and axes data using their dataKey property." @@ -21,10 +14,6 @@ "highlightedItem": { "description": "The item currently highlighted. Turns highlighting into a controlled prop." }, - "leftAxis": { - "description": "Indicate which axis to display the left of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps." - }, - "legend": { "description": "The props of the legend." }, "loading": { "description": "If true, a loading overlay is displayed." }, "margin": { "description": "The margin between the SVG and the drawing area. It's used for leaving some space for extra information such as the x- and y-axis or legend. Accepts an object with the optional properties: top, bottom, left, and right." @@ -37,9 +26,6 @@ "resolveSizeBeforeRender": { "description": "The chart will try to wait for the parent container to resolve its size before it renders for the first time.
This can be useful in some scenarios where the chart appear to grow after the first render, like when used inside a grid." }, - "rightAxis": { - "description": "Indicate which axis to display the right of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps." - }, "series": { "description": "The series to display in the pie chart. An array of PieSeriesType objects." }, @@ -52,9 +38,6 @@ "description": "The configuration of the tooltip.", "seeMoreText": "See {{link}} for more details." }, - "topAxis": { - "description": "Indicate which axis to display the top of the charts. Can be a string (the id of the axis) or an object ChartsXAxisProps." - }, "width": { "description": "The width of the chart in px. If not defined, it takes the width of the parent element." }, @@ -68,10 +51,6 @@ "classDescriptions": {}, "slotDescriptions": { "axisContent": "Custom component for displaying tooltip content when triggered by axis event.", - "axisLabel": "Custom component for axis label.", - "axisLine": "Custom component for the axis main line.", - "axisTick": "Custom component for the axis tick.", - "axisTickLabel": "Custom component for tick label.", "itemContent": "Custom component for displaying tooltip content when triggered by item event.", "legend": "Custom rendering of the legend.", "loadingOverlay": "Overlay component rendered when the chart is in a loading state.", diff --git a/docs/translations/api-docs/charts/scatter-series-type.json b/docs/translations/api-docs/charts/scatter-series-type.json index 4f3fed0aa9e92..58a593f0bdbf6 100644 --- a/docs/translations/api-docs/charts/scatter-series-type.json +++ b/docs/translations/api-docs/charts/scatter-series-type.json @@ -20,10 +20,7 @@ "description": "Formatter used to render values in tooltip or other data display." }, "xAxisId": { "description": "The id of the x-axis used to render the series." }, - "xAxisKey": { "description": "The id of the x-axis used to render the series." }, "yAxisId": { "description": "The id of the y-axis used to render the series." }, - "yAxisKey": { "description": "The id of the y-axis used to render the series." }, - "zAxisId": { "description": "The id of the z-axis used to render the series." }, - "zAxisKey": { "description": "The id of the z-axis used to render the series." } + "zAxisId": { "description": "The id of the z-axis used to render the series." } } } diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index 78865102281cb..3b9442aa58e3b 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -661,6 +661,12 @@ "treeData": { "description": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop." }, + "unstable_listColumn": { + "description": "Definition of the column rendered when the unstable_listView prop is enabled." + }, + "unstable_listView": { + "description": "If true, displays the data in a list view. Use in combination with unstable_listColumn." + }, "unstable_rowSpanning": { "description": "If true, the Data Grid will auto span the cells over the rows having the same value." } @@ -1224,13 +1230,17 @@ } }, "slotDescriptions": { + "baseBadge": "The custom Badge component used in the grid for both header and cells.", "baseButton": "The custom Button component used in the grid.", "baseCheckbox": "The custom Checkbox component used in the grid for both header and cells.", "baseChip": "The custom Chip component used in the grid.", + "baseDivider": "The custom Divider component used in the grid.", "baseFormControl": "The custom FormControl component used in the grid.", "baseIconButton": "The custom IconButton component used in the grid.", "baseInputAdornment": "The custom InputAdornment component used in the grid.", "baseInputLabel": "The custom InputLabel component used in the grid.", + "baseMenuItem": "The custom MenuItem component used in the grid.", + "baseMenuList": "The custom MenuList component used in the grid.", "basePopper": "The custom Popper component used in the grid.", "baseSelect": "The custom Select component used in the grid.", "baseSelectOption": "The custom SelectOption component used in the grid.", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index a0509f2e45491..c1d26982ac46e 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -599,6 +599,12 @@ "treeData": { "description": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop." }, + "unstable_listColumn": { + "description": "Definition of the column rendered when the unstable_listView prop is enabled." + }, + "unstable_listView": { + "description": "If true, displays the data in a list view. Use in combination with unstable_listColumn." + }, "unstable_rowSpanning": { "description": "If true, the Data Grid will auto span the cells over the rows having the same value." } @@ -1162,13 +1168,17 @@ } }, "slotDescriptions": { + "baseBadge": "The custom Badge component used in the grid for both header and cells.", "baseButton": "The custom Button component used in the grid.", "baseCheckbox": "The custom Checkbox component used in the grid for both header and cells.", "baseChip": "The custom Chip component used in the grid.", + "baseDivider": "The custom Divider component used in the grid.", "baseFormControl": "The custom FormControl component used in the grid.", "baseIconButton": "The custom IconButton component used in the grid.", "baseInputAdornment": "The custom InputAdornment component used in the grid.", "baseInputLabel": "The custom InputLabel component used in the grid.", + "baseMenuItem": "The custom MenuItem component used in the grid.", + "baseMenuList": "The custom MenuList component used in the grid.", "basePopper": "The custom Popper component used in the grid.", "baseSelect": "The custom Select component used in the grid.", "baseSelectOption": "The custom SelectOption component used in the grid.", diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json index 9cf3ea258f2a7..d082ef5d495db 100644 --- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json @@ -1048,13 +1048,17 @@ } }, "slotDescriptions": { + "baseBadge": "The custom Badge component used in the grid for both header and cells.", "baseButton": "The custom Button component used in the grid.", "baseCheckbox": "The custom Checkbox component used in the grid for both header and cells.", "baseChip": "The custom Chip component used in the grid.", + "baseDivider": "The custom Divider component used in the grid.", "baseFormControl": "The custom FormControl component used in the grid.", "baseIconButton": "The custom IconButton component used in the grid.", "baseInputAdornment": "The custom InputAdornment component used in the grid.", "baseInputLabel": "The custom InputLabel component used in the grid.", + "baseMenuItem": "The custom MenuItem component used in the grid.", + "baseMenuList": "The custom MenuList component used in the grid.", "basePopper": "The custom Popper component used in the grid.", "baseSelect": "The custom Select component used in the grid.", "baseSelectOption": "The custom SelectOption component used in the grid.", diff --git a/docs/translations/api-docs/tree-view/rich-tree-view-pro/rich-tree-view-pro.json b/docs/translations/api-docs/tree-view/rich-tree-view-pro/rich-tree-view-pro.json index 3a33b96c08bd8..b43dd9784328a 100644 --- a/docs/translations/api-docs/tree-view/rich-tree-view-pro/rich-tree-view-pro.json +++ b/docs/translations/api-docs/tree-view/rich-tree-view-pro/rich-tree-view-pro.json @@ -15,7 +15,7 @@ } }, "checkboxSelection": { - "description": "If true, the tree view renders a checkbox at the left of its label that allows selecting it." + "description": "If true, the Tree View renders a checkbox at the left of its label that allows selecting it." }, "classes": { "description": "Override or extend the styles applied to the component." }, "defaultExpandedItems": { @@ -75,21 +75,21 @@ "description": "If true, ctrl and shift will trigger multiselect." }, "onExpandedItemsChange": { - "description": "Callback fired when tree items are expanded/collapsed.", + "description": "Callback fired when Tree Items are expanded/collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the expanded items." } }, "onItemClick": { - "description": "Callback fired when the content slot of a given tree item is clicked.", + "description": "Callback fired when the content slot of a given Tree Item is clicked.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The id of the focused item." } }, "onItemExpansionToggle": { - "description": "Callback fired when a tree item is expanded or collapsed.", + "description": "Callback fired when a Tree Item is expanded or collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -97,7 +97,7 @@ } }, "onItemFocus": { - "description": "Callback fired when a given tree item is focused.", + "description": "Callback fired when a given Tree Item is focused.", "typeDescriptions": { "event": "The DOM event that triggered the change. Warning: This is a generic event not a focus event.", "itemId": "The id of the focused item." @@ -111,7 +111,7 @@ } }, "onItemPositionChange": { - "description": "Callback fired when a tree item is moved in the tree.", + "description": "Callback fired when a Tree Item is moved in the tree.", "typeDescriptions": { "params": "The params describing the item re-ordering.", "params.itemId": "The id of the item moved.", @@ -120,7 +120,7 @@ } }, "onItemSelectionToggle": { - "description": "Callback fired when a tree item is selected or deselected.", + "description": "Callback fired when a Tree Item is selected or deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -128,7 +128,7 @@ } }, "onSelectedItemsChange": { - "description": "Callback fired when tree items are selected/deselected.", + "description": "Callback fired when Tree Items are selected/deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the selected items. When multiSelect is true, this is an array of strings; when false (default) a string." @@ -137,6 +137,9 @@ "selectedItems": { "description": "Selected item ids. (Controlled) When multiSelect is true this takes an array of strings; when false (default) a string." }, + "selectionPropagation": { + "description": "When selectionPropagation.descendants is set to true.
- Selecting a parent selects all its descendants automatically. - Deselecting a parent deselects all its descendants automatically.
When selectionPropagation.parents is set to true.
- Selecting all the descendants of a parent selects the parent automatically. - Deselecting a descendant of a selected parent deselects the parent automatically.
Only works when multiSelect is true. On the <SimpleTreeView />, only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all)" + }, "slotProps": { "description": "The props used for each component slot." }, "slots": { "description": "Overridable component slots." }, "sx": { @@ -146,7 +149,7 @@ "classDescriptions": {}, "slotDescriptions": { "collapseIcon": "The default icon used to collapse the item.", - "endIcon": "The default icon displayed next to an end item. This is applied to all tree items and can be overridden by the TreeItem icon slot prop.", + "endIcon": "The default icon displayed next to an end item. This is applied to all Tree Items and can be overridden by the TreeItem icon slot prop.", "expandIcon": "The default icon used to expand the item.", "item": "Custom component for the item.", "root": "Element rendered at the root." diff --git a/docs/translations/api-docs/tree-view/rich-tree-view/rich-tree-view.json b/docs/translations/api-docs/tree-view/rich-tree-view/rich-tree-view.json index 3ba5993e51879..a2fdbd15c255a 100644 --- a/docs/translations/api-docs/tree-view/rich-tree-view/rich-tree-view.json +++ b/docs/translations/api-docs/tree-view/rich-tree-view/rich-tree-view.json @@ -5,7 +5,7 @@ "description": "The ref object that allows Tree View manipulation. Can be instantiated with useTreeViewApiRef()." }, "checkboxSelection": { - "description": "If true, the tree view renders a checkbox at the left of its label that allows selecting it." + "description": "If true, the Tree View renders a checkbox at the left of its label that allows selecting it." }, "classes": { "description": "Override or extend the styles applied to the component." }, "defaultExpandedItems": { @@ -55,21 +55,21 @@ "description": "If true, ctrl and shift will trigger multiselect." }, "onExpandedItemsChange": { - "description": "Callback fired when tree items are expanded/collapsed.", + "description": "Callback fired when Tree Items are expanded/collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the expanded items." } }, "onItemClick": { - "description": "Callback fired when the content slot of a given tree item is clicked.", + "description": "Callback fired when the content slot of a given Tree Item is clicked.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The id of the focused item." } }, "onItemExpansionToggle": { - "description": "Callback fired when a tree item is expanded or collapsed.", + "description": "Callback fired when a Tree Item is expanded or collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -77,7 +77,7 @@ } }, "onItemFocus": { - "description": "Callback fired when a given tree item is focused.", + "description": "Callback fired when a given Tree Item is focused.", "typeDescriptions": { "event": "The DOM event that triggered the change. Warning: This is a generic event not a focus event.", "itemId": "The id of the focused item." @@ -91,7 +91,7 @@ } }, "onItemSelectionToggle": { - "description": "Callback fired when a tree item is selected or deselected.", + "description": "Callback fired when a Tree Item is selected or deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -99,7 +99,7 @@ } }, "onSelectedItemsChange": { - "description": "Callback fired when tree items are selected/deselected.", + "description": "Callback fired when Tree Items are selected/deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the selected items. When multiSelect is true, this is an array of strings; when false (default) a string." @@ -108,6 +108,9 @@ "selectedItems": { "description": "Selected item ids. (Controlled) When multiSelect is true this takes an array of strings; when false (default) a string." }, + "selectionPropagation": { + "description": "When selectionPropagation.descendants is set to true.
- Selecting a parent selects all its descendants automatically. - Deselecting a parent deselects all its descendants automatically.
When selectionPropagation.parents is set to true.
- Selecting all the descendants of a parent selects the parent automatically. - Deselecting a descendant of a selected parent deselects the parent automatically.
Only works when multiSelect is true. On the <SimpleTreeView />, only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all)" + }, "slotProps": { "description": "The props used for each component slot." }, "slots": { "description": "Overridable component slots." }, "sx": { @@ -117,7 +120,7 @@ "classDescriptions": {}, "slotDescriptions": { "collapseIcon": "The default icon used to collapse the item.", - "endIcon": "The default icon displayed next to an end item. This is applied to all tree items and can be overridden by the TreeItem icon slot prop.", + "endIcon": "The default icon displayed next to an end item. This is applied to all Tree Items and can be overridden by the TreeItem icon slot prop.", "expandIcon": "The default icon used to expand the item.", "item": "Custom component for the item.", "root": "Element rendered at the root." diff --git a/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json b/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json index a56fd8d2d75af..86afc50c65dbd 100644 --- a/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json +++ b/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json @@ -5,7 +5,7 @@ "description": "The ref object that allows Tree View manipulation. Can be instantiated with useTreeViewApiRef()." }, "checkboxSelection": { - "description": "If true, the tree view renders a checkbox at the left of its label that allows selecting it." + "description": "If true, the Tree View renders a checkbox at the left of its label that allows selecting it." }, "children": { "description": "The content of the component." }, "classes": { "description": "Override or extend the styles applied to the component." }, @@ -38,21 +38,21 @@ "description": "If true, ctrl and shift will trigger multiselect." }, "onExpandedItemsChange": { - "description": "Callback fired when tree items are expanded/collapsed.", + "description": "Callback fired when Tree Items are expanded/collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the expanded items." } }, "onItemClick": { - "description": "Callback fired when the content slot of a given tree item is clicked.", + "description": "Callback fired when the content slot of a given Tree Item is clicked.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The id of the focused item." } }, "onItemExpansionToggle": { - "description": "Callback fired when a tree item is expanded or collapsed.", + "description": "Callback fired when a Tree Item is expanded or collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -60,14 +60,14 @@ } }, "onItemFocus": { - "description": "Callback fired when a given tree item is focused.", + "description": "Callback fired when a given Tree Item is focused.", "typeDescriptions": { "event": "The DOM event that triggered the change. Warning: This is a generic event not a focus event.", "itemId": "The id of the focused item." } }, "onItemSelectionToggle": { - "description": "Callback fired when a tree item is selected or deselected.", + "description": "Callback fired when a Tree Item is selected or deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -75,7 +75,7 @@ } }, "onSelectedItemsChange": { - "description": "Callback fired when tree items are selected/deselected.", + "description": "Callback fired when Tree Items are selected/deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the selected items. When multiSelect is true, this is an array of strings; when false (default) a string." @@ -84,6 +84,9 @@ "selectedItems": { "description": "Selected item ids. (Controlled) When multiSelect is true this takes an array of strings; when false (default) a string." }, + "selectionPropagation": { + "description": "When selectionPropagation.descendants is set to true.
- Selecting a parent selects all its descendants automatically. - Deselecting a parent deselects all its descendants automatically.
When selectionPropagation.parents is set to true.
- Selecting all the descendants of a parent selects the parent automatically. - Deselecting a descendant of a selected parent deselects the parent automatically.
Only works when multiSelect is true. On the <SimpleTreeView />, only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all)" + }, "slotProps": { "description": "The props used for each component slot." }, "slots": { "description": "Overridable component slots." }, "sx": { @@ -93,7 +96,7 @@ "classDescriptions": {}, "slotDescriptions": { "collapseIcon": "The default icon used to collapse the item.", - "endIcon": "The default icon displayed next to an end item. This is applied to all tree items and can be overridden by the TreeItem icon slot prop.", + "endIcon": "The default icon displayed next to an end item. This is applied to all Tree Items and can be overridden by the TreeItem icon slot prop.", "expandIcon": "The default icon used to expand the item.", "root": "Element rendered at the root." } diff --git a/docs/translations/api-docs/tree-view/tree-item-2/tree-item-2.json b/docs/translations/api-docs/tree-view/tree-item-2/tree-item-2.json deleted file mode 100644 index 1ec2e89e98d8e..0000000000000 --- a/docs/translations/api-docs/tree-view/tree-item-2/tree-item-2.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "componentDescription": "", - "propDescriptions": { - "children": { "description": "The content of the component." }, - "classes": { "description": "Override or extend the styles applied to the component." }, - "disabled": { "description": "If true, the item is disabled." }, - "id": { "description": "The id attribute of the item. If not provided, it will be generated." }, - "itemId": { "description": "The id of the item. Must be unique." }, - "label": { "description": "The label of the item." }, - "onBlur": { "description": "Callback fired when the item root is blurred." }, - "onFocus": { - "description": "This prop isn't supported. Use the onItemFocus callback on the tree if you need to monitor an item's focus." - }, - "onKeyDown": { - "description": "Callback fired when a key is pressed on the keyboard and the tree is in focus." - }, - "slotProps": { "description": "The props used for each component slot." }, - "slots": { "description": "Overridable component slots." } - }, - "classDescriptions": { - "disabled": { - "description": "State class applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the element", - "conditions": "disabled" - }, - "editable": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the content of the items that are editable" - }, - "editing": { - "description": "Styles applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the content element", - "conditions": "editing is enabled" - }, - "expanded": { - "description": "State class applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the content element", - "conditions": "expanded" - }, - "focused": { - "description": "State class applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the content element", - "conditions": "focused" - }, - "selected": { - "description": "State class applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the content element", - "conditions": "selected" - } - }, - "slotDescriptions": { - "checkbox": "The component that renders the item checkbox for selection.", - "collapseIcon": "The icon used to collapse the item.", - "content": "The component that renders the content of the item. (e.g.: everything related to this item, not to its children).", - "dragAndDropOverlay": "The component that renders the overlay when an item reordering is ongoing. Warning: This slot is only useful when using the RichTreeViewPro component.", - "endIcon": "The icon displayed next to an end item.", - "expandIcon": "The icon used to expand the item.", - "groupTransition": "The component that renders the children of the item.", - "icon": "The icon to display next to the tree item's label.", - "iconContainer": "The component that renders the icon.", - "label": "The component that renders the item label.", - "labelInput": "The component that renders the input to edit the label when the item is editable and is currently being edited.", - "root": "The component that renders the root." - } -} diff --git a/docs/translations/api-docs/tree-view/tree-item-drag-and-drop-overlay/tree-item-drag-and-drop-overlay.json b/docs/translations/api-docs/tree-view/tree-item-drag-and-drop-overlay/tree-item-drag-and-drop-overlay.json new file mode 100644 index 0000000000000..f93d4cbd8c798 --- /dev/null +++ b/docs/translations/api-docs/tree-view/tree-item-drag-and-drop-overlay/tree-item-drag-and-drop-overlay.json @@ -0,0 +1 @@ +{ "componentDescription": "", "propDescriptions": {}, "classDescriptions": {} } diff --git a/docs/translations/api-docs/tree-view/tree-item-icon/tree-item-icon.json b/docs/translations/api-docs/tree-view/tree-item-icon/tree-item-icon.json new file mode 100644 index 0000000000000..c39a07ae223f2 --- /dev/null +++ b/docs/translations/api-docs/tree-view/tree-item-icon/tree-item-icon.json @@ -0,0 +1,14 @@ +{ + "componentDescription": "", + "propDescriptions": { + "slotProps": { "description": "The props used for each component slot." }, + "slots": { "description": "Overridable component slots." } + }, + "classDescriptions": {}, + "slotDescriptions": { + "collapseIcon": "The icon used to collapse the item.", + "endIcon": "The icon displayed next to an end item.", + "expandIcon": "The icon used to expand the item.", + "icon": "The icon to display next to the Tree Item's label." + } +} diff --git a/docs/translations/api-docs/tree-view/tree-item/tree-item.json b/docs/translations/api-docs/tree-view/tree-item/tree-item.json index f0febee14167b..5177a55030aaa 100644 --- a/docs/translations/api-docs/tree-view/tree-item/tree-item.json +++ b/docs/translations/api-docs/tree-view/tree-item/tree-item.json @@ -3,44 +3,26 @@ "propDescriptions": { "children": { "description": "The content of the component." }, "classes": { "description": "Override or extend the styles applied to the component." }, - "ContentComponent": { - "description": "The component used to render the content of the item.", - "requiresRef": true - }, - "ContentProps": { "description": "Props applied to ContentComponent." }, "disabled": { "description": "If true, the item is disabled." }, - "itemId": { "description": "The id of the item." }, - "label": { "description": "The tree item label." }, + "id": { "description": "The id attribute of the item. If not provided, it will be generated." }, + "itemId": { "description": "The id of the item. Must be unique." }, + "label": { "description": "The label of the item." }, + "onBlur": { "description": "Callback fired when the item root is blurred." }, "onFocus": { - "description": "This prop isn't supported. Use the onItemFocus callback on the tree if you need to monitor a item's focus." + "description": "This prop isn't supported. Use the onItemFocus callback on the tree if you need to monitor an item's focus." }, "onKeyDown": { - "description": "Callback fired when a key of the keyboard is pressed on the item." + "description": "Callback fired when a key is pressed on the keyboard and the tree is in focus." }, "slotProps": { "description": "The props used for each component slot." }, - "slots": { "description": "Overridable component slots." }, - "sx": { - "description": "The system prop that allows defining system overrides as well as additional CSS styles." - } + "slots": { "description": "Overridable component slots." } }, "classDescriptions": { - "checkbox": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the checkbox element" - }, - "content": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the content element" - }, "disabled": { "description": "State class applied to {{nodeName}} when {{conditions}}.", "nodeName": "the element", "conditions": "disabled" }, - "dragAndDropOverlay": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the drag and drop overlay" - }, "editable": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the content of the items that are editable" @@ -60,17 +42,6 @@ "nodeName": "the content element", "conditions": "focused" }, - "iconContainer": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the tree item icon" - }, - "label": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the label element" }, - "labelInput": { - "description": "Styles applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the input element that is visible", - "conditions": "editing is enabled" - }, - "root": { "description": "Styles applied to the root element." }, "selected": { "description": "State class applied to {{nodeName}} when {{conditions}}.", "nodeName": "the content element", @@ -78,10 +49,17 @@ } }, "slotDescriptions": { + "checkbox": "The component that renders the item checkbox for selection.", "collapseIcon": "The icon used to collapse the item.", + "content": "The component that renders the content of the item. (e.g.: everything related to this item, not to its children).", + "dragAndDropOverlay": "The component that renders the overlay when an item reordering is ongoing. Warning: This slot is only useful when using the <RichTreeViewPro /> component.", "endIcon": "The icon displayed next to an end item.", "expandIcon": "The icon used to expand the item.", - "groupTransition": "The component that animates the appearance / disappearance of the item's children.", - "icon": "The icon to display next to the tree item's label." + "groupTransition": "The component that renders the children of the item.", + "icon": "The icon to display next to the Tree Item's label.", + "iconContainer": "The component that renders the icon.", + "label": "The component that renders the item label.", + "labelInput": "The component that renders the input to edit the label when the item is editable and is currently being edited.", + "root": "The component that renders the root." } } diff --git a/docs/translations/api-docs/tree-view/tree-view/tree-view.json b/docs/translations/api-docs/tree-view/tree-view/tree-view.json index 765c53c0efd9e..7558c542dc419 100644 --- a/docs/translations/api-docs/tree-view/tree-view/tree-view.json +++ b/docs/translations/api-docs/tree-view/tree-view/tree-view.json @@ -1,11 +1,11 @@ { - "componentDescription": "This component has been deprecated in favor of the new `SimpleTreeView` component.\nYou can have a look at how to migrate to the new component in the v7 [migration guide](https://mui.com/x/migration/migration-tree-view-v6/#use-simpletreeview-instead-of-treeview)", + "componentDescription": "This component has been deprecated in favor of the new Simple Tree View component.\nYou can have a look at how to migrate to the new component in the v7 [migration guide](https://mui.com/x/migration/migration-tree-view-v6/#use-simpletreeview-instead-of-treeview)", "propDescriptions": { "apiRef": { "description": "The ref object that allows Tree View manipulation. Can be instantiated with useTreeViewApiRef()." }, "checkboxSelection": { - "description": "If true, the tree view renders a checkbox at the left of its label that allows selecting it." + "description": "If true, the Tree View renders a checkbox at the left of its label that allows selecting it." }, "children": { "description": "The content of the component." }, "classes": { "description": "Override or extend the styles applied to the component." }, @@ -38,21 +38,21 @@ "description": "If true, ctrl and shift will trigger multiselect." }, "onExpandedItemsChange": { - "description": "Callback fired when tree items are expanded/collapsed.", + "description": "Callback fired when Tree Items are expanded/collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the expanded items." } }, "onItemClick": { - "description": "Callback fired when the content slot of a given tree item is clicked.", + "description": "Callback fired when the content slot of a given Tree Item is clicked.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The id of the focused item." } }, "onItemExpansionToggle": { - "description": "Callback fired when a tree item is expanded or collapsed.", + "description": "Callback fired when a Tree Item is expanded or collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -60,14 +60,14 @@ } }, "onItemFocus": { - "description": "Callback fired when a given tree item is focused.", + "description": "Callback fired when a given Tree Item is focused.", "typeDescriptions": { "event": "The DOM event that triggered the change. Warning: This is a generic event not a focus event.", "itemId": "The id of the focused item." } }, "onItemSelectionToggle": { - "description": "Callback fired when a tree item is selected or deselected.", + "description": "Callback fired when a Tree Item is selected or deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -75,7 +75,7 @@ } }, "onSelectedItemsChange": { - "description": "Callback fired when tree items are selected/deselected.", + "description": "Callback fired when Tree Items are selected/deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the selected items. When multiSelect is true, this is an array of strings; when false (default) a string." @@ -84,6 +84,9 @@ "selectedItems": { "description": "Selected item ids. (Controlled) When multiSelect is true this takes an array of strings; when false (default) a string." }, + "selectionPropagation": { + "description": "When selectionPropagation.descendants is set to true.
- Selecting a parent selects all its descendants automatically. - Deselecting a parent deselects all its descendants automatically.
When selectionPropagation.parents is set to true.
- Selecting all the descendants of a parent selects the parent automatically. - Deselecting a descendant of a selected parent deselects the parent automatically.
Only works when multiSelect is true. On the <SimpleTreeView />, only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all)" + }, "slotProps": { "description": "The props used for each component slot." }, "slots": { "description": "Overridable component slots." }, "sx": { @@ -93,7 +96,7 @@ "classDescriptions": {}, "slotDescriptions": { "collapseIcon": "The default icon used to collapse the item.", - "endIcon": "The default icon displayed next to an end item. This is applied to all tree items and can be overridden by the TreeItem icon slot prop.", + "endIcon": "The default icon displayed next to an end item. This is applied to all Tree Items and can be overridden by the TreeItem icon slot prop.", "expandIcon": "The default icon used to expand the item.", "root": "Element rendered at the root." } diff --git a/package.json b/package.json index 02e54753d4f5e..c133b697217a2 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "7.20.0", + "version": "7.21.0", "private": true, "scripts": { "preinstall": "npx only-allow pnpm", @@ -62,8 +62,8 @@ "release:changelog": "node scripts/releaseChangelog.mjs", "release:version": "lerna version --exact --no-changelog --no-push --no-git-tag-version --no-private", "release:build": "lerna run --parallel --no-private --scope \"@mui/*\" build", - "release:publish": "pnpm publish --recursive --tag latest", - "release:publish:dry-run": "pnpm publish --recursive --tag latest --registry=\"http://localhost:4873/\"", + "release:publish": "pnpm publish --recursive --tag next", + "release:publish:dry-run": "pnpm publish --recursive --tag next --registry=\"http://localhost:4873/\"", "release:tag": "node scripts/releaseTag.mjs", "validate": "concurrently \"pnpm prettier && pnpm eslint\" \"pnpm proptypes\" \"pnpm docs:typescript:formatted\" \"pnpm docs:api\"", "clean:node_modules": "rimraf --glob \"**/node_modules\"" @@ -71,33 +71,33 @@ "devDependencies": { "@actions/core": "^1.11.1", "@actions/github": "^6.0.0", - "@argos-ci/core": "^2.9.0", - "@babel/cli": "^7.25.7", - "@babel/core": "^7.25.8", - "@babel/node": "^7.25.7", - "@babel/plugin-transform-class-properties": "^7.25.7", - "@babel/plugin-transform-object-rest-spread": "^7.25.8", - "@babel/plugin-transform-private-methods": "^7.25.7", - "@babel/plugin-transform-private-property-in-object": "^7.25.8", - "@babel/plugin-transform-react-constant-elements": "^7.25.7", - "@babel/plugin-transform-runtime": "^7.25.7", - "@babel/preset-env": "^7.25.8", - "@babel/preset-react": "^7.25.7", - "@babel/preset-typescript": "^7.25.7", - "@babel/register": "^7.25.7", - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.8", + "@argos-ci/core": "^2.10.0", + "@babel/cli": "^7.25.9", + "@babel/core": "^7.26.0", + "@babel/node": "^7.26.0", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-react-constant-elements": "^7.25.9", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.26.0", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.26.0", + "@babel/register": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "@emotion/cache": "^11.13.1", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@mui/icons-material": "^5.16.7", "@mui/internal-babel-plugin-resolve-imports": "1.0.18", - "@mui/internal-markdown": "^1.0.15", - "@mui/internal-test-utils": "^1.0.15", + "@mui/internal-markdown": "^1.0.19", + "@mui/internal-test-utils": "^1.0.19", "@mui/material": "^5.16.7", - "@mui/monorepo": "github:mui/material-ui#010de4505361345951824d905d1508d6f258ba67", + "@mui/monorepo": "github:mui/material-ui#32112b76aa821c2a1e98120545019ef1a71ea274", "@mui/utils": "^5.16.6", - "@next/eslint-plugin-next": "14.2.15", + "@next/eslint-plugin-next": "15.0.2", "@octokit/plugin-retry": "^7.1.2", "@octokit/rest": "^21.0.2", "@playwright/test": "^1.44.1", @@ -106,10 +106,10 @@ "@types/chai": "^4.3.20", "@types/chai-dom": "^1.11.3", "@types/fs-extra": "^11.0.4", - "@types/karma": "^6.3.8", - "@types/lodash": "^4.17.10", + "@types/karma": "^6.3.9", + "@types/lodash": "^4.17.12", "@types/mocha": "^10.0.9", - "@types/node": "^20.16.11", + "@types/node": "^20.17.3", "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", "@types/react-test-renderer": "^18.3.0", @@ -119,7 +119,7 @@ "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "autoprefixer": "^10.4.20", - "axe-core": "4.10.0", + "axe-core": "4.10.2", "babel-loader": "^9.2.1", "babel-plugin-istanbul": "^7.0.0", "babel-plugin-module-resolver": "^5.0.2", @@ -144,21 +144,21 @@ "eslint-import-resolver-webpack": "^0.13.9", "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-import": "^2.31.0", - "eslint-plugin-jsdoc": "^50.4.1", - "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-jsdoc": "^50.4.3", + "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-material-ui": "workspace:^", "eslint-plugin-mocha": "^10.5.0", "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "^7.37.1", + "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-compiler": "0.0.0-experimental-9ed098e-20240725", "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-testing-library": "^6.3.0", + "eslint-plugin-testing-library": "^6.4.0", "fast-glob": "^3.3.2", "format-util": "^1.0.5", "fs-extra": "^11.2.0", "glob-gitignore": "^1.0.15", "globby": "^14.0.2", - "html-webpack-plugin": "^5.6.0", + "html-webpack-plugin": "^5.6.3", "jsdom": "24.1.3", "jss": "^10.10.0", "jss-plugin-template": "^10.10.0", @@ -183,12 +183,12 @@ "react-dom": "^18.3.1", "remark": "^15.0.1", "rimraf": "^6.0.1", - "serve": "^14.2.3", + "serve": "^14.2.4", "sinon": "^18.0.1", "stream-browserify": "^3.0.0", "string-replace-loader": "^3.1.0", "terser-webpack-plugin": "^5.3.10", - "tsx": "^4.19.1", + "tsx": "^4.19.2", "typescript": "^5.6.3", "unist-util-visit": "^5.0.0", "util": "^0.12.5", @@ -199,11 +199,11 @@ }, "resolutions": { "react-is": "^18.3.1", - "@types/node": "^20.16.11" + "@types/node": "^20.17.3" }, - "packageManager": "pnpm@9.12.1", + "packageManager": "pnpm@9.12.2", "engines": { - "pnpm": "9.12.1" + "pnpm": "9.12.2" }, "pnpm": { "patchedDependencies": { diff --git a/packages/rsc-builder/package.json b/packages/rsc-builder/package.json index 92d10aa52ebb8..934708ddd01dc 100644 --- a/packages/rsc-builder/package.json +++ b/packages/rsc-builder/package.json @@ -9,6 +9,6 @@ }, "devDependencies": { "@types/mocha": "^10.0.9", - "@types/node": "^20.16.11" + "@types/node": "^20.17.3" } } diff --git a/packages/x-charts-pro/package.json b/packages/x-charts-pro/package.json index 946258e4fc7c0..ae9cc81b1bdc3 100644 --- a/packages/x-charts-pro/package.json +++ b/packages/x-charts-pro/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-charts-pro", - "version": "7.0.0-beta.4", + "version": "7.0.0-beta.5", "description": "The Pro plan edition of the Charts components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -39,7 +39,7 @@ "directory": "packages/x-charts-pro" }, "dependencies": { - "@babel/runtime": "^7.25.7", + "@babel/runtime": "^7.26.0", "@mui/utils": "^5.16.6 || ^6.0.0", "@mui/x-charts": "workspace:*", "@mui/x-charts-vendor": "workspace:*", diff --git a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx index 0c3af2884f477..8424bfbb844ff 100644 --- a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx +++ b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx @@ -24,6 +24,46 @@ function BarChartPlotZoom(props: BarPlotProps) { return ; } +BarChartPlotZoom.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + /** + * If provided, the function will be used to format the label of the bar. + * It can be set to 'value' to display the current value. + * @param {BarItem} item The item to format. + * @param {BarLabelContext} context data about the bar. + * @returns {string} The formatted label. + */ + barLabel: PropTypes.oneOfType([PropTypes.oneOf(['value']), PropTypes.func]), + /** + * Defines the border radius of the bar element. + */ + borderRadius: PropTypes.number, + /** + * Callback fired when a bar item is clicked. + * @param {React.MouseEvent} event The event source of the callback. + * @param {BarItemIdentifier} barItemIdentifier The bar item identifier. + */ + onItemClick: PropTypes.func, + /** + * If `true`, animations are skipped. + * @default undefined + */ + skipAnimation: PropTypes.bool, + /** + * The props used for each component slot. + * @default {} + */ + slotProps: PropTypes.object, + /** + * Overridable component slots. + * @default {} + */ + slots: PropTypes.object, +} as any; + export interface BarChartProProps extends BarChartProps, ZoomProps {} /** @@ -159,35 +199,6 @@ BarChartPro.propTypes = { * @default yAxisIds[0] The id of the first provided axis */ leftAxis: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - /** - * @deprecated Consider using `slotProps.legend` instead. - */ - legend: PropTypes.shape({ - 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, - onItemClick: PropTypes.func, - 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, - }), - slotProps: PropTypes.object, - slots: PropTypes.object, - }), /** * If `true`, a loading overlay is displayed. * @default false diff --git a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx index f310b073bed57..202b40914d81a 100644 --- a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx +++ b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useThemeProps } from '@mui/material/styles'; import useId from '@mui/utils/useId'; +import { MakeOptional } from '@mui/x-internals/types'; import { interpolateRgbBasis } from '@mui/x-charts-vendor/d3-interpolate'; import { ChartsAxis, ChartsAxisProps } from '@mui/x-charts/ChartsAxis'; import { @@ -12,7 +13,6 @@ import { ChartsTooltipSlots, } from '@mui/x-charts/ChartsTooltip'; import { - MakeOptional, ChartsAxisSlots, ChartsAxisSlotProps, ChartsXAxisProps, diff --git a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx index 732756fe17315..b7bbf72236ceb 100644 --- a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx +++ b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx @@ -31,16 +31,106 @@ function AreaPlotZoom(props: AreaPlotProps) { return ; } +AreaPlotZoom.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + /** + * Callback fired when a line area item is clicked. + * @param {React.MouseEvent} event The event source of the callback. + * @param {LineItemIdentifier} lineItemIdentifier The line item identifier. + */ + onItemClick: PropTypes.func, + /** + * If `true`, animations are skipped. + * @default false + */ + skipAnimation: PropTypes.bool, + /** + * The props used for each component slot. + * @default {} + */ + slotProps: PropTypes.object, + /** + * Overridable component slots. + * @default {} + */ + slots: PropTypes.object, +} as any; + function LinePlotZoom(props: LinePlotProps) { const { isInteracting } = useZoom(); return ; } +LinePlotZoom.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + /** + * Callback fired when a line item is clicked. + * @param {React.MouseEvent} event The event source of the callback. + * @param {LineItemIdentifier} lineItemIdentifier The line item identifier. + */ + onItemClick: PropTypes.func, + /** + * If `true`, animations are skipped. + * @default false + */ + skipAnimation: PropTypes.bool, + /** + * The props used for each component slot. + * @default {} + */ + slotProps: PropTypes.object, + /** + * Overridable component slots. + * @default {} + */ + slots: PropTypes.object, +} as any; + function MarkPlotZoom(props: MarkPlotProps) { const { isInteracting } = useZoom(); return ; } +MarkPlotZoom.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + /** + * If `true` the mark element will only be able to render circle. + * Giving fewer customization options, but saving around 40ms per 1.000 marks. + * @default false + */ + experimentalRendering: PropTypes.bool, + /** + * Callback fired when a line mark item is clicked. + * @param {React.MouseEvent} event The event source of the callback. + * @param {LineItemIdentifier} lineItemIdentifier The line mark item identifier. + */ + onItemClick: PropTypes.func, + /** + * If `true`, animations are skipped. + * @default false + */ + skipAnimation: PropTypes.bool, + /** + * The props used for each component slot. + * @default {} + */ + slotProps: PropTypes.object, + /** + * Overridable component slots. + * @default {} + */ + slots: PropTypes.object, +} as any; + export interface LineChartProProps extends LineChartProps, ZoomProps {} /** @@ -174,35 +264,6 @@ LineChartPro.propTypes = { * @default yAxisIds[0] The id of the first provided axis */ leftAxis: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - /** - * @deprecated Consider using `slotProps.legend` instead. - */ - legend: PropTypes.shape({ - 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, - onItemClick: PropTypes.func, - 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, - }), - slotProps: PropTypes.object, - slots: PropTypes.object, - }), /** * If `true`, a loading overlay is displayed. * @default false diff --git a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx index 74401ac085dc0..b415635daf082 100644 --- a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx +++ b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx @@ -141,35 +141,6 @@ ScatterChartPro.propTypes = { * @default yAxisIds[0] The id of the first provided axis */ leftAxis: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - /** - * @deprecated Consider using `slotProps.legend` instead. - */ - legend: PropTypes.shape({ - 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, - onItemClick: PropTypes.func, - 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, - }), - slotProps: PropTypes.object, - slots: PropTypes.object, - }), /** * If `true`, a loading overlay is displayed. * @default false diff --git a/packages/x-charts-pro/src/models/seriesType/heatmap.ts b/packages/x-charts-pro/src/models/seriesType/heatmap.ts index 9444f4af60a20..2db79f66bd740 100644 --- a/packages/x-charts-pro/src/models/seriesType/heatmap.ts +++ b/packages/x-charts-pro/src/models/seriesType/heatmap.ts @@ -1,5 +1,5 @@ +import { DefaultizedProps } from '@mui/x-internals/types'; import { - DefaultizedProps, CommonDefaultizedProps, CommonSeriesType, CartesianSeriesType, diff --git a/packages/x-charts-pro/src/typeOverloads/modules.ts b/packages/x-charts-pro/src/typeOverloads/modules.ts index 8cdc24c0f4091..734e3e911b7e7 100644 --- a/packages/x-charts-pro/src/typeOverloads/modules.ts +++ b/packages/x-charts-pro/src/typeOverloads/modules.ts @@ -1,4 +1,4 @@ -import { DefaultizedProps } from '@mui/x-charts/internals'; +import { DefaultizedProps } from '@mui/x-internals/types'; import { HeatmapItemIdentifier, HeatmapSeriesType, diff --git a/packages/x-charts-vendor/package.json b/packages/x-charts-vendor/package.json index 79dd902e3dd11..7b2db9acb00d3 100644 --- a/packages/x-charts-vendor/package.json +++ b/packages/x-charts-vendor/package.json @@ -24,7 +24,7 @@ } }, "dependencies": { - "@babel/runtime": "^7.25.7", + "@babel/runtime": "^7.26.0", "@types/d3-color": "^3.1.3", "@types/d3-delaunay": "^6.0.4", "@types/d3-interpolate": "^3.0.4", @@ -41,7 +41,7 @@ "robust-predicates": "^3.0.2" }, "devDependencies": { - "@babel/plugin-transform-runtime": "^7.25.7", + "@babel/plugin-transform-runtime": "^7.25.9", "@types/d3-array": "^3.2.1", "@types/d3-format": "^3.0.4", "@types/d3-path": "^3.1.0", @@ -50,7 +50,7 @@ "d3-format": "^3.1.0", "d3-path": "^3.1.0", "d3-time-format": "^4.1.0", - "execa": "^9.4.0", + "execa": "^9.4.1", "internmap": "^2.0.3", "rimraf": "^6.0.1" }, diff --git a/packages/x-charts/package.json b/packages/x-charts/package.json index 65038b813fb06..e860ba6bf1835 100644 --- a/packages/x-charts/package.json +++ b/packages/x-charts/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-charts", - "version": "7.20.0", + "version": "7.21.0", "description": "The community edition of the Charts components (MUI X).", "author": "MUI Team", "main": "src/index.js", @@ -39,7 +39,7 @@ "directory": "packages/x-charts" }, "dependencies": { - "@babel/runtime": "^7.25.7", + "@babel/runtime": "^7.26.0", "@mui/utils": "^5.16.6 || ^6.0.0", "@mui/x-charts-vendor": "workspace:*", "@mui/x-internals": "workspace:*", @@ -65,7 +65,7 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.15", + "@mui/internal-test-utils": "^1.0.19", "@mui/material": "^5.16.7", "@mui/system": "^5.16.7", "@react-spring/core": "^9.7.5", diff --git a/packages/x-charts/src/BarChart/BarChart.tsx b/packages/x-charts/src/BarChart/BarChart.tsx index ca3e113ac3027..58dea244f59a4 100644 --- a/packages/x-charts/src/BarChart/BarChart.tsx +++ b/packages/x-charts/src/BarChart/BarChart.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useThemeProps } from '@mui/material/styles'; +import { MakeOptional } from '@mui/x-internals/types'; import { BarPlot, BarPlotProps, BarPlotSlotProps, BarPlotSlots } from './BarPlot'; import { ResponsiveChartContainer, @@ -9,19 +10,13 @@ import { } from '../ResponsiveChartContainer'; import { ChartsAxis, ChartsAxisProps } from '../ChartsAxis'; import { BarSeriesType } from '../models/seriesType/bar'; -import { MakeOptional } from '../models/helpers'; import { ChartsTooltip, ChartsTooltipProps, ChartsTooltipSlotProps, ChartsTooltipSlots, } from '../ChartsTooltip'; -import { - ChartsLegend, - ChartsLegendProps, - ChartsLegendSlots, - ChartsLegendSlotProps, -} from '../ChartsLegend'; +import { ChartsLegend, ChartsLegendSlots, ChartsLegendSlotProps } from '../ChartsLegend'; import { ChartsAxisHighlight, ChartsAxisHighlightProps } from '../ChartsAxisHighlight'; import { ChartsClipPath } from '../ChartsClipPath'; import { ChartsAxisSlots, ChartsAxisSlotProps } from '../models/axis'; @@ -79,10 +74,6 @@ export interface BarChartProps * */ axisHighlight?: ChartsAxisHighlightProps; - /** - * @deprecated Consider using `slotProps.legend` instead. - */ - legend?: ChartsLegendProps; /** * Overridable component slots. * @default {} @@ -226,35 +217,6 @@ BarChart.propTypes = { * @default yAxisIds[0] The id of the first provided axis */ leftAxis: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - /** - * @deprecated Consider using `slotProps.legend` instead. - */ - legend: PropTypes.shape({ - 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, - onItemClick: PropTypes.func, - 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, - }), - slotProps: PropTypes.object, - slots: PropTypes.object, - }), /** * If `true`, a loading overlay is displayed. * @default false diff --git a/packages/x-charts/src/BarChart/BarElement.tsx b/packages/x-charts/src/BarChart/BarElement.tsx index f33865e1747f4..ce5f91fcfd2f1 100644 --- a/packages/x-charts/src/BarChart/BarElement.tsx +++ b/packages/x-charts/src/BarChart/BarElement.tsx @@ -8,7 +8,7 @@ import { styled } from '@mui/material/styles'; import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; import { color as d3Color } from '@mui/x-charts-vendor/d3-color'; import { AnimatedProps, animated } from '@react-spring/web'; -import { SlotComponentPropsFromProps } from '../internals/SlotComponentPropsFromProps'; +import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { useInteractionItemProps } from '../hooks/useInteractionItemProps'; import { SeriesId } from '../models/seriesType/common'; import { useItemHighlighted } from '../context'; diff --git a/packages/x-charts/src/BarChart/BarLabel/BarLabelItem.tsx b/packages/x-charts/src/BarChart/BarLabel/BarLabelItem.tsx index a71c3c7a4a296..33a993fd7694f 100644 --- a/packages/x-charts/src/BarChart/BarLabel/BarLabelItem.tsx +++ b/packages/x-charts/src/BarChart/BarLabel/BarLabelItem.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import useSlotProps from '@mui/utils/useSlotProps'; import PropTypes from 'prop-types'; -import { SlotComponentPropsFromProps } from '../../internals/SlotComponentPropsFromProps'; +import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { useUtilityClasses } from './barLabelClasses'; import { BarLabelOwnerState, BarItem, BarLabelContext } from './BarLabel.types'; import { getBarLabel } from './getBarLabel'; diff --git a/packages/x-charts/src/BarChart/BarPlot.tsx b/packages/x-charts/src/BarChart/BarPlot.tsx index c84f74c7711f4..e81f631d0c30a 100644 --- a/packages/x-charts/src/BarChart/BarPlot.tsx +++ b/packages/x-charts/src/BarChart/BarPlot.tsx @@ -103,8 +103,8 @@ const useAggregatedData = (): { const data = stackingGroups.flatMap(({ ids: groupIds }, groupIndex) => { return groupIds.flatMap((seriesId) => { - const xAxisId = series[seriesId].xAxisId ?? series[seriesId].xAxisKey ?? defaultXAxisId; - const yAxisId = series[seriesId].yAxisId ?? series[seriesId].yAxisKey ?? defaultYAxisId; + const xAxisId = series[seriesId].xAxisId ?? defaultXAxisId; + const yAxisId = series[seriesId].yAxisId ?? defaultYAxisId; const xAxisConfig = xAxis[xAxisId]; const yAxisConfig = yAxis[yAxisId]; diff --git a/packages/x-charts/src/BarChart/extremums.ts b/packages/x-charts/src/BarChart/extremums.ts index 1ec2cc001b9fd..7d29d237c8679 100644 --- a/packages/x-charts/src/BarChart/extremums.ts +++ b/packages/x-charts/src/BarChart/extremums.ts @@ -28,7 +28,7 @@ const getValueExtremum = return Object.keys(series) .filter((seriesId) => { - const yAxisId = series[seriesId].yAxisId ?? series[seriesId].yAxisKey; + const yAxisId = series[seriesId].yAxisId; return yAxisId === axis.id || (isDefaultAxis && yAxisId === undefined); }) .reduce( @@ -38,8 +38,8 @@ const getValueExtremum = const filter = getFilters?.({ currentAxisId: axis.id, isDefaultAxis, - seriesXAxisId: series[seriesId].xAxisId ?? series[seriesId].xAxisKey, - seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey, + seriesXAxisId: series[seriesId].xAxisId, + seriesYAxisId: series[seriesId].yAxisId, }); const [seriesMin, seriesMax] = stackedData?.reduce( diff --git a/packages/x-charts/src/BarChart/formatter.ts b/packages/x-charts/src/BarChart/formatter.ts index d2c5639f79732..64175e646d303 100644 --- a/packages/x-charts/src/BarChart/formatter.ts +++ b/packages/x-charts/src/BarChart/formatter.ts @@ -1,9 +1,9 @@ import { stack as d3Stack } from '@mui/x-charts-vendor/d3-shape'; import { warnOnce } from '@mui/x-internals/warning'; +import { DefaultizedProps } from '@mui/x-internals/types'; import { getStackingGroups } from '../internals/stackSeries'; import { ChartSeries, DatasetElementType, DatasetType } from '../models/seriesType/config'; import { defaultizeValueFormatter } from '../internals/defaultizeValueFormatter'; -import { DefaultizedProps } from '../models/helpers'; import { SeriesId } from '../models/seriesType/common'; import { SeriesFormatter } from '../context/PluginProvider/SeriesFormatter.types'; diff --git a/packages/x-charts/src/BarChart/useBarChartProps.ts b/packages/x-charts/src/BarChart/useBarChartProps.ts index 16d17fc3ffe59..d6495a9c6148b 100644 --- a/packages/x-charts/src/BarChart/useBarChartProps.ts +++ b/packages/x-charts/src/BarChart/useBarChartProps.ts @@ -34,7 +34,6 @@ export const useBarChartProps = (props: BarChartProps) => { tooltip, onAxisClick, axisHighlight, - legend, grid, topAxis, leftAxis, @@ -146,7 +145,6 @@ export const useBarChartProps = (props: BarChartProps) => { }; const legendProps: ChartsLegendProps = { - ...legend, slots, slotProps, }; diff --git a/packages/x-charts/src/ChartContainer/ChartContainer.tsx b/packages/x-charts/src/ChartContainer/ChartContainer.tsx index 7343ceb1a2fdf..661df7f5987a6 100644 --- a/packages/x-charts/src/ChartContainer/ChartContainer.tsx +++ b/packages/x-charts/src/ChartContainer/ChartContainer.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; +import { MakeOptional } from '@mui/x-internals/types'; import { DrawingProvider, DrawingProviderProps } from '../context/DrawingProvider'; import { SeriesProvider, SeriesProviderProps } from '../context/SeriesProvider'; import { InteractionProvider } from '../context/InteractionProvider'; @@ -16,7 +17,6 @@ import { import { PluginProvider, PluginProviderProps } from '../context/PluginProvider'; import { useChartContainerProps } from './useChartContainerProps'; import { AxisConfig, ChartsXAxisProps, ChartsYAxisProps, ScaleName } from '../models/axis'; -import { MakeOptional } from '../models/helpers'; import { AnimationProvider, AnimationProviderProps } from '../context/AnimationProvider'; export type ChartContainerProps = Omit< diff --git a/packages/x-charts/src/ChartContainer/useDefaultizeAxis.ts b/packages/x-charts/src/ChartContainer/useDefaultizeAxis.ts index a260517f0ba05..99d08ec6c099a 100644 --- a/packages/x-charts/src/ChartContainer/useDefaultizeAxis.ts +++ b/packages/x-charts/src/ChartContainer/useDefaultizeAxis.ts @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; +import { MakeOptional } from '@mui/x-internals/types'; import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '../constants'; -import { MakeOptional } from '../models/helpers'; import { AxisConfig, ScaleName } from '../models'; import { ChartsAxisProps } from '../models/axis'; import { DatasetType } from '../models/seriesType/config'; diff --git a/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx b/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx index de75c3dfc288a..1a68804b785fe 100644 --- a/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx +++ b/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx @@ -3,12 +3,11 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import useSlotProps from '@mui/utils/useSlotProps'; import composeClasses from '@mui/utils/composeClasses'; +import { DefaultizedProps } from '@mui/x-internals/types'; import { useThemeProps, useTheme, Theme } from '@mui/material/styles'; import { getSeriesToDisplay } from './utils'; 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'; @@ -74,7 +73,6 @@ function ChartsLegend(inProps: ChartsLegendProps) { const theme = useTheme(); const classes = useUtilityClasses({ ...defaultizedProps, theme }); - const drawingArea = useDrawingArea(); const series = useSeries(); const seriesToDisplay = getSeriesToDisplay(series); @@ -86,7 +84,6 @@ function ChartsLegend(inProps: ChartsLegendProps) { additionalProps: { ...other, classes, - drawingArea, series, seriesToDisplay, }, diff --git a/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx b/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx index 7278a105536fa..ff2c62ade76bc 100644 --- a/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx +++ b/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx @@ -3,7 +3,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { FormattedSeries } from '../context/SeriesProvider'; import { LegendPerItem, LegendPerItemProps } from './LegendPerItem'; -import { DrawingArea } from '../context/DrawingProvider'; import { LegendItemParams, SeriesLegendItemContext } from './chartsLegend.types'; const seriesContextBuilder = (context: LegendItemParams): SeriesLegendItemContext => @@ -19,10 +18,6 @@ export interface LegendRendererProps extends Omit { series: FormattedSeries; seriesToDisplay: LegendPerItemProps['itemsToDisplay']; - /** - * @deprecated Use the `useDrawingArea` hook instead. - */ - drawingArea: Omit; /** * Callback fired when a legend item is clicked. * @param {React.MouseEvent} event The click event. @@ -42,7 +37,7 @@ export interface LegendRendererProps } function DefaultChartsLegend(props: LegendRendererProps) { - const { drawingArea, seriesToDisplay, hidden, onItemClick, ...other } = props; + const { seriesToDisplay, hidden, onItemClick, ...other } = props; if (hidden) { return null; @@ -75,17 +70,6 @@ DefaultChartsLegend.propTypes = { * The default depends on the chart. */ direction: PropTypes.oneOf(['column', 'row']).isRequired, - /** - * @deprecated Use the `useDrawingArea` hook instead. - */ - drawingArea: PropTypes.shape({ - bottom: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - left: PropTypes.number.isRequired, - right: PropTypes.number.isRequired, - top: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - }).isRequired, /** * Set to true to hide the legend. * @default false diff --git a/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx b/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx index 60abf0ff950d9..77ac080ec7b70 100644 --- a/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx +++ b/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { DefaultizedProps } from '@mui/x-internals/types'; import NoSsr from '@mui/material/NoSsr'; import { useTheme, styled } from '@mui/material/styles'; import { DrawingArea } from '../context/DrawingProvider'; @@ -12,7 +13,6 @@ import { useDrawingArea } from '../hooks/useDrawingArea'; import { AnchorPosition, Direction, LegendPlacement } from './legend.types'; import { ChartsLegendItem } from './ChartsLegendItem'; import { ChartsLegendClasses } from './chartsLegendClasses'; -import { DefaultizedProps } from '../models/helpers'; export type ChartsLegendRootOwnerState = { position: AnchorPosition; diff --git a/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx b/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx index 326f12ef6f4bf..8c26ff5650e65 100644 --- a/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx +++ b/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx @@ -6,7 +6,7 @@ import { useSeries } from '../hooks/useSeries'; import { useSvgRef } from '../hooks'; import { useCartesianContext } from '../context/CartesianProvider'; -type AxisData = { +export type ChartsAxisData = { dataIndex: number; axisValue?: number | Date | string; seriesValues: Record; @@ -19,7 +19,7 @@ export interface ChartsOnAxisClickHandlerProps { * @param {MouseEvent} event The mouse event recorded on the `` element. * @param {null | AxisData} data The data about the clicked axis and items associated with it. */ - onAxisClick?: (event: MouseEvent, data: null | AxisData) => void; + onAxisClick?: (event: MouseEvent, data: null | ChartsAxisData) => void; } function ChartsOnAxisClickHandler(props: ChartsOnAxisClickHandlerProps) { @@ -55,8 +55,8 @@ function ChartsOnAxisClickHandler(props: ChartsOnAxisClickHandlerProps) { series[seriesType]?.seriesOrder.forEach((seriesId) => { const seriesItem = series[seriesType]!.series[seriesId]; - const providedXAxisId = seriesItem.xAxisId ?? seriesItem.xAxisKey; - const providedYAxisId = seriesItem.yAxisId ?? seriesItem.yAxisKey; + const providedXAxisId = seriesItem.xAxisId; + const providedYAxisId = seriesItem.yAxisId; const axisKey = isXaxis ? providedXAxisId : providedYAxisId; if (axisKey === undefined || axisKey === USED_AXIS_ID) { diff --git a/packages/x-charts/src/ChartsOverlay/ChartsOverlay.tsx b/packages/x-charts/src/ChartsOverlay/ChartsOverlay.tsx index 05287192e40b3..f78c62b03624d 100644 --- a/packages/x-charts/src/ChartsOverlay/ChartsOverlay.tsx +++ b/packages/x-charts/src/ChartsOverlay/ChartsOverlay.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import { SxProps, Theme } from '@mui/material/styles'; -import { SlotComponentPropsFromProps } from '../internals/SlotComponentPropsFromProps'; +import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { ChartsLoadingOverlay } from './ChartsLoadingOverlay'; import { useSeries } from '../hooks/useSeries'; import { SeriesId } from '../models/seriesType/common'; diff --git a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx index b9daca3379cba..28b6f6f833e0b 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx @@ -77,8 +77,8 @@ function ChartsAxisTooltipContent(props: { series[seriesType]!.seriesOrder.forEach((seriesId) => { const item = series[seriesType]!.series[seriesId]; - const providedXAxisId = item.xAxisId ?? item.xAxisKey; - const providedYAxisId = item.yAxisId ?? item.yAxisKey; + const providedXAxisId = item.xAxisId; + const providedYAxisId = item.yAxisId; const axisKey = isXaxis ? providedXAxisId : providedYAxisId; @@ -87,8 +87,7 @@ function ChartsAxisTooltipContent(props: { const xAxisId = providedXAxisId ?? xAxisIds[0]; const yAxisId = providedYAxisId ?? yAxisIds[0]; - const zAxisId = - (seriesToAdd as any).zAxisId ?? (seriesToAdd as any).zAxisKey ?? zAxisIds[0]; + const zAxisId = (seriesToAdd as any).zAxisId ?? zAxisIds[0]; const getColor = colorProcessors[seriesType]?.( diff --git a/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx index 8a941f4d9c68b..30140400a9481 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx @@ -54,9 +54,9 @@ function ChartsItemTooltipContent( const { zAxis, zAxisIds } = React.useContext(ZAxisContext); const colorProcessors = useColorProcessor(); - const xAxisId = (series as any).xAxisId ?? (series as any).xAxisKey ?? xAxisIds[0]; - const yAxisId = (series as any).yAxisId ?? (series as any).yAxisKey ?? yAxisIds[0]; - const zAxisId = (series as any).zAxisId ?? (series as any).zAxisKey ?? zAxisIds[0]; + const xAxisId = (series as any).xAxisId ?? xAxisIds[0]; + const yAxisId = (series as any).yAxisId ?? yAxisIds[0]; + const zAxisId = (series as any).zAxisId ?? zAxisIds[0]; const getColor = colorProcessors[series.type]?.( diff --git a/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx b/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx index 4566eddb96d63..560a83ccc9a44 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import composeClasses from '@mui/utils/composeClasses'; +import useLazyRef from '@mui/utils/useLazyRef'; import { styled, useThemeProps, SxProps, Theme } from '@mui/material/styles'; import Popper, { PopperProps as BasePopperProps } from '@mui/material/Popper'; import NoSsr from '@mui/material/NoSsr'; @@ -11,12 +12,8 @@ import { InteractionContext, ItemInteractionData, } from '../context/InteractionProvider'; -import { - generateVirtualElement, - useMouseTracker, - getTooltipHasData, - TriggerOptions, -} from './utils'; +import { useSvgRef } from '../hooks/useSvgRef'; +import { getTooltipHasData, TriggerOptions, usePointerType } from './utils'; import { ChartSeriesType } from '../models/seriesType/config'; import { ChartsItemContentProps, ChartsItemTooltipContent } from './ChartsItemTooltipContent'; import { ChartsAxisContentProps, ChartsAxisTooltipContent } from './ChartsAxisTooltipContent'; @@ -133,14 +130,19 @@ function ChartsTooltip(inProps: ChartsTooltipProps }); const { trigger = 'axis', itemContent, axisContent, slots, slotProps } = props; - const mousePosition = useMouseTracker(); + const svgRef = useSvgRef(); + const pointerType = usePointerType(); + + const popperRef: PopperProps['popperRef'] = React.useRef(null); + + const positionRef = useLazyRef(() => ({ x: 0, y: 0 })); const { item, axis } = React.useContext(InteractionContext); const displayedData = trigger === 'item' ? item : axis; const tooltipHasData = getTooltipHasData(trigger, displayedData); - const popperOpen = mousePosition !== null && tooltipHasData; + const popperOpen = pointerType !== null && tooltipHasData; const classes = useUtilityClasses({ classes: props.classes }); @@ -150,14 +152,26 @@ function ChartsTooltip(inProps: ChartsTooltipProps externalSlotProps: slotProps?.popper, additionalProps: { open: popperOpen, - placement: - mousePosition?.pointerType === 'mouse' ? ('right-start' as const) : ('top' as const), - anchorEl: generateVirtualElement(mousePosition), + placement: pointerType?.pointerType === 'mouse' ? ('right-start' as const) : ('top' as const), + popperRef, + anchorEl: { + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, modifiers: [ { name: 'offset', options: { - offset: [0, mousePosition?.pointerType === 'touch' ? 40 - mousePosition.height : 0], + offset: [0, pointerType?.pointerType === 'touch' ? 40 - pointerType.height : 0], }, }, ], @@ -165,6 +179,27 @@ function ChartsTooltip(inProps: ChartsTooltipProps ownerState: {}, }); + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleMove = (event: PointerEvent) => { + // eslint-disable-next-line react-compiler/react-compiler + positionRef.current = { + x: event.clientX, + y: event.clientY, + }; + popperRef.current?.update(); + }; + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef, positionRef]); + if (trigger === 'none') { return null; } diff --git a/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx b/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx index 6b4634241e3d5..bde48ebc9f357 100644 --- a/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx +++ b/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx @@ -54,6 +54,7 @@ describe('ChartsTooltip', () => { ); const svg = document.querySelector('svg')!; + fireEvent.pointerEnter(svg); // Trigger the tooltip firePointerEvent(svg, 'pointermove', { clientX: 198, clientY: 60, @@ -120,6 +121,7 @@ describe('ChartsTooltip', () => { ); const svg = document.querySelector('svg')!; + fireEvent.pointerEnter(svg); // Trigger the tooltip firePointerEvent(svg, 'pointermove', { clientX: 150, clientY: 60, @@ -191,6 +193,7 @@ describe('ChartsTooltip', () => { fireEvent.pointerEnter(rectangles[0]); + fireEvent.pointerEnter(svg); // Trigger the tooltip firePointerEvent(svg, 'pointermove', { clientX: 150, clientY: 60, @@ -235,6 +238,7 @@ describe('ChartsTooltip', () => { fireEvent.pointerEnter(rectangles[0]); + fireEvent.pointerEnter(svg); // Trigger the tooltip firePointerEvent(svg, 'pointermove', { clientX: 150, clientY: 60, diff --git a/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx b/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx index 7b86b0bb0648f..109665b90e5d3 100644 --- a/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx +++ b/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx @@ -61,8 +61,8 @@ export function useAxisTooltip(): null | UseAxisTooltipReturnValue { return seriesOfType.seriesOrder.map((seriesId) => { const seriesToAdd = seriesOfType.series[seriesId]!; - const providedXAxisId = seriesToAdd.xAxisId ?? seriesToAdd.xAxisKey; - const providedYAxisId = seriesToAdd.yAxisId ?? seriesToAdd.yAxisKey; + const providedXAxisId = seriesToAdd.xAxisId; + const providedYAxisId = seriesToAdd.yAxisId; const axisKey = isXaxis ? providedXAxisId : providedYAxisId; @@ -70,8 +70,7 @@ export function useAxisTooltip(): null | UseAxisTooltipReturnValue { if (axisKey === undefined || axisKey === USED_AXIS_ID) { const xAxisId = providedXAxisId ?? xAxisIds[0]; const yAxisId = providedYAxisId ?? yAxisIds[0]; - const zAxisId = - (seriesToAdd as any).zAxisId ?? (seriesToAdd as any).zAxisKey ?? zAxisIds[0]; + const zAxisId = (seriesToAdd as any).zAxisId ?? zAxisIds[0]; const color = colorProcessors[seriesType]?.( diff --git a/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx b/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx index 5743592e31dcf..a3ab97c287b18 100644 --- a/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx +++ b/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx @@ -29,9 +29,9 @@ export function useItemTooltip(): null | UseItemToolt const { zAxis, zAxisIds } = React.useContext(ZAxisContext); const colorProcessors = useColorProcessor(); - const xAxisId = (series as any).xAxisId ?? (series as any).xAxisKey ?? xAxisIds[0]; - const yAxisId = (series as any).yAxisId ?? (series as any).yAxisKey ?? yAxisIds[0]; - const zAxisId = (series as any).zAxisId ?? (series as any).zAxisKey ?? zAxisIds[0]; + const xAxisId = (series as any).xAxisId ?? xAxisIds[0]; + const yAxisId = (series as any).yAxisId ?? yAxisIds[0]; + const zAxisId = (series as any).zAxisId ?? zAxisIds[0]; if (!item || item.dataIndex === undefined) { return null; diff --git a/packages/x-charts/src/ChartsTooltip/utils.tsx b/packages/x-charts/src/ChartsTooltip/utils.tsx index 89faff2a7b17e..227c7f58e24ea 100644 --- a/packages/x-charts/src/ChartsTooltip/utils.tsx +++ b/packages/x-charts/src/ChartsTooltip/utils.tsx @@ -10,43 +10,11 @@ type MousePosition = { height: number; }; -export function generateVirtualElement(mousePosition: MousePosition | null) { - if (mousePosition === null) { - return { - getBoundingClientRect: () => ({ - width: 0, - height: 0, - x: 0, - y: 0, - top: 0, - right: 0, - bottom: 0, - left: 0, - toJSON: () => '', - }), - }; - } - const { x, y } = mousePosition; - const boundingBox = { - width: 0, - height: 0, - x, - y, - top: y, - right: x, - bottom: y, - left: x, - }; - return { - getBoundingClientRect: () => ({ - ...boundingBox, - toJSON: () => JSON.stringify(boundingBox), - }), - }; -} - export type UseMouseTrackerReturnValue = null | MousePosition; +/** + * @deprecated We recommend using vanilla JS to let popper track mouse position. + */ export function useMouseTracker(): UseMouseTrackerReturnValue { const svgRef = useSvgRef(); @@ -59,6 +27,8 @@ export function useMouseTracker(): UseMouseTrackerReturnValue { return () => {}; } + const controller = new AbortController(); + const handleOut = (event: PointerEvent) => { if (event.pointerType !== 'mouse') { setMousePosition(null); @@ -74,18 +44,57 @@ export function useMouseTracker(): UseMouseTrackerReturnValue { }); }; - element.addEventListener('pointerdown', handleMove); - element.addEventListener('pointermove', handleMove); + element.addEventListener('pointerdown', handleMove, { signal: controller.signal }); + element.addEventListener('pointermove', handleMove, { signal: controller.signal }); + element.addEventListener('pointerup', handleOut, { signal: controller.signal }); + + return () => { + // Calling `.abort()` removes ALL event listeners + // For more info, see https://kettanaito.com/blog/dont-sleep-on-abort-controller + controller.abort(); + }; + }, [svgRef]); + + return mousePosition; +} + +type PointerType = Pick; + +export function usePointerType(): null | PointerType { + const svgRef = useSvgRef(); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointerType, setPointerType] = React.useState(null); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointerType(null); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointerType({ + height: event.height, + pointerType: event.pointerType as PointerType['pointerType'], + }); + }; + + element.addEventListener('pointerenter', handleEnter); element.addEventListener('pointerup', handleOut); return () => { - element.removeEventListener('pointerdown', handleMove); - element.removeEventListener('pointermove', handleMove); + element.removeEventListener('pointerenter', handleEnter); element.removeEventListener('pointerup', handleOut); }; }, [svgRef]); - return mousePosition; + return pointerType; } export type TriggerOptions = 'item' | 'axis' | 'none'; diff --git a/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx index 4a70336255dea..ad1dea102fc65 100644 --- a/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx +++ b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx @@ -64,10 +64,10 @@ function ChartsVoronoiHandler(props: ChartsVoronoiHandlerProps) { voronoiRef.current = {}; let points: number[] = []; seriesOrder.forEach((seriesId) => { - const { data, xAxisId, yAxisId, xAxisKey, yAxisKey } = series[seriesId]; + const { data, xAxisId, yAxisId } = series[seriesId]; - const xScale = xAxis[xAxisId ?? xAxisKey ?? defaultXAxisId].scale; - const yScale = yAxis[yAxisId ?? yAxisKey ?? defaultYAxisId].scale; + const xScale = xAxis[xAxisId ?? defaultXAxisId].scale; + const yScale = yAxis[yAxisId ?? defaultYAxisId].scale; const getXPosition = getValueToPositionMapper(xScale); const getYPosition = getValueToPositionMapper(yScale); diff --git a/packages/x-charts/src/LineChart/AreaElement.tsx b/packages/x-charts/src/LineChart/AreaElement.tsx index 6fe9bfabf0301..fe7a0216bee58 100644 --- a/packages/x-charts/src/LineChart/AreaElement.tsx +++ b/packages/x-charts/src/LineChart/AreaElement.tsx @@ -5,7 +5,7 @@ import composeClasses from '@mui/utils/composeClasses'; import useSlotProps from '@mui/utils/useSlotProps'; import generateUtilityClass from '@mui/utils/generateUtilityClass'; import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; -import { SlotComponentPropsFromProps } from '../internals/SlotComponentPropsFromProps'; +import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { useInteractionItemProps } from '../hooks/useInteractionItemProps'; import { AnimatedArea, AnimatedAreaProps } from './AnimatedArea'; import { SeriesId } from '../models/seriesType/common'; diff --git a/packages/x-charts/src/LineChart/AreaPlot.tsx b/packages/x-charts/src/LineChart/AreaPlot.tsx index e46e1e4df24a8..7db6cbaf1c821 100644 --- a/packages/x-charts/src/LineChart/AreaPlot.tsx +++ b/packages/x-charts/src/LineChart/AreaPlot.tsx @@ -56,19 +56,14 @@ const useAggregatedData = () => { .reverse() // Revert stacked area for a more pleasant animation .map((seriesId) => { const { - xAxisId: xAxisIdProp, - yAxisId: yAxisIdProp, - xAxisKey = defaultXAxisId, - yAxisKey = defaultYAxisId, + xAxisId = defaultXAxisId, + yAxisId = defaultYAxisId, stackedData, data, connectNulls, baseline, } = series[seriesId]; - const xAxisId = xAxisIdProp ?? xAxisKey; - const yAxisId = yAxisIdProp ?? yAxisKey; - const xScale = getValueToPositionMapper(xAxis[xAxisId].scale); const yScale = yAxis[yAxisId].scale; const xData = xAxis[xAxisId].data; diff --git a/packages/x-charts/src/LineChart/LineChart.tsx b/packages/x-charts/src/LineChart/LineChart.tsx index 127c029e98672..f18aef0a79694 100644 --- a/packages/x-charts/src/LineChart/LineChart.tsx +++ b/packages/x-charts/src/LineChart/LineChart.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useThemeProps } from '@mui/material/styles'; +import { MakeOptional } from '@mui/x-internals/types'; import { AreaPlot, AreaPlotProps, AreaPlotSlotProps, AreaPlotSlots } from './AreaPlot'; import { LinePlot, LinePlotProps, LinePlotSlotProps, LinePlotSlots } from './LinePlot'; import { @@ -11,19 +12,13 @@ import { import { MarkPlot, MarkPlotProps, MarkPlotSlotProps, MarkPlotSlots } from './MarkPlot'; import { ChartsAxis, ChartsAxisProps } from '../ChartsAxis/ChartsAxis'; import { LineSeriesType } from '../models/seriesType/line'; -import { MakeOptional } from '../models/helpers'; import { ChartsTooltip, ChartsTooltipProps, ChartsTooltipSlotProps, ChartsTooltipSlots, } from '../ChartsTooltip'; -import { - ChartsLegend, - ChartsLegendProps, - ChartsLegendSlotProps, - ChartsLegendSlots, -} from '../ChartsLegend'; +import { ChartsLegend, ChartsLegendSlotProps, ChartsLegendSlots } from '../ChartsLegend'; import { ChartsAxisHighlight, ChartsAxisHighlightProps } from '../ChartsAxisHighlight'; import { ChartsClipPath } from '../ChartsClipPath'; import { ChartsAxisSlotProps, ChartsAxisSlots } from '../models/axis'; @@ -90,10 +85,6 @@ export interface LineChartProps * @default { x: 'line' } */ axisHighlight?: ChartsAxisHighlightProps; - /** - * @deprecated Consider using `slotProps.legend` instead. - */ - legend?: ChartsLegendProps; /** * If `true`, render the line highlight item. */ @@ -255,35 +246,6 @@ LineChart.propTypes = { * @default yAxisIds[0] The id of the first provided axis */ leftAxis: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - /** - * @deprecated Consider using `slotProps.legend` instead. - */ - legend: PropTypes.shape({ - 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, - onItemClick: PropTypes.func, - 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, - }), - slotProps: PropTypes.object, - slots: PropTypes.object, - }), /** * If `true`, a loading overlay is displayed. * @default false diff --git a/packages/x-charts/src/LineChart/LineElement.tsx b/packages/x-charts/src/LineChart/LineElement.tsx index 269f5a9102262..a251ebf47ebc9 100644 --- a/packages/x-charts/src/LineChart/LineElement.tsx +++ b/packages/x-charts/src/LineChart/LineElement.tsx @@ -5,7 +5,7 @@ import composeClasses from '@mui/utils/composeClasses'; import useSlotProps from '@mui/utils/useSlotProps'; import generateUtilityClass from '@mui/utils/generateUtilityClass'; import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; -import { SlotComponentPropsFromProps } from '../internals/SlotComponentPropsFromProps'; +import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { useInteractionItemProps } from '../hooks/useInteractionItemProps'; import { AnimatedLine, AnimatedLineProps } from './AnimatedLine'; import { SeriesId } from '../models/seriesType/common'; diff --git a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx index 5e1f55c1530f2..a5911052eb101 100644 --- a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx +++ b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import { SlotComponentPropsFromProps } from '../internals/SlotComponentPropsFromProps'; +import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { useCartesianContext } from '../context/CartesianProvider'; import { LineHighlightElement, LineHighlightElementProps } from './LineHighlightElement'; import { getValueToPositionMapper } from '../hooks/useScale'; @@ -70,18 +70,13 @@ function LineHighlightPlot(props: LineHighlightPlotProps) { {stackingGroups.flatMap(({ ids: groupIds }) => { return groupIds.flatMap((seriesId) => { const { - xAxisId: xAxisIdProp, - yAxisId: yAxisIdProp, - xAxisKey = defaultXAxisId, - yAxisKey = defaultYAxisId, + xAxisId = defaultXAxisId, + yAxisId = defaultYAxisId, stackedData, data, disableHighlight, } = series[seriesId]; - const xAxisId = xAxisIdProp ?? xAxisKey; - const yAxisId = yAxisIdProp ?? yAxisKey; - if (disableHighlight || data[highlightedIndex] == null) { return null; } diff --git a/packages/x-charts/src/LineChart/LinePlot.tsx b/packages/x-charts/src/LineChart/LinePlot.tsx index a8451c0a787d4..75ada74aa047b 100644 --- a/packages/x-charts/src/LineChart/LinePlot.tsx +++ b/packages/x-charts/src/LineChart/LinePlot.tsx @@ -54,18 +54,13 @@ const useAggregatedData = () => { return stackingGroups.flatMap(({ ids: groupIds }) => { return groupIds.flatMap((seriesId) => { const { - xAxisId: xAxisIdProp, - yAxisId: yAxisIdProp, - xAxisKey = defaultXAxisId, - yAxisKey = defaultYAxisId, + xAxisId = defaultXAxisId, + yAxisId = defaultYAxisId, stackedData, data, connectNulls, } = series[seriesId]; - const xAxisId = xAxisIdProp ?? xAxisKey; - const yAxisId = yAxisIdProp ?? yAxisKey; - const xScale = getValueToPositionMapper(xAxis[xAxisId].scale); const yScale = yAxis[yAxisId].scale; const xData = xAxis[xAxisId].data; diff --git a/packages/x-charts/src/LineChart/MarkPlot.tsx b/packages/x-charts/src/LineChart/MarkPlot.tsx index c9e446c2a3ffa..7731eba6392fb 100644 --- a/packages/x-charts/src/LineChart/MarkPlot.tsx +++ b/packages/x-charts/src/LineChart/MarkPlot.tsx @@ -93,10 +93,8 @@ function MarkPlot(props: MarkPlotProps) { {stackingGroups.flatMap(({ ids: groupIds }) => { return groupIds.map((seriesId) => { const { - xAxisId: xAxisIdProp, - yAxisId: yAxisIdProp, - xAxisKey = defaultXAxisId, - yAxisKey = defaultYAxisId, + xAxisId = defaultXAxisId, + yAxisId = defaultYAxisId, stackedData, data, showMark = true, @@ -106,9 +104,6 @@ function MarkPlot(props: MarkPlotProps) { return null; } - const xAxisId = xAxisIdProp ?? xAxisKey; - const yAxisId = yAxisIdProp ?? yAxisKey; - const xScale = getValueToPositionMapper(xAxis[xAxisId].scale); const yScale = yAxis[yAxisId].scale; const xData = xAxis[xAxisId].data; diff --git a/packages/x-charts/src/LineChart/extremums.ts b/packages/x-charts/src/LineChart/extremums.ts index e903b5b6e7482..1640dbc7b91d9 100644 --- a/packages/x-charts/src/LineChart/extremums.ts +++ b/packages/x-charts/src/LineChart/extremums.ts @@ -36,7 +36,7 @@ export const getExtremumY: ExtremumGetter<'line'> = (params) => { return Object.keys(series) .filter((seriesId) => { - const yAxisId = series[seriesId].yAxisId ?? series[seriesId].yAxisKey; + const yAxisId = series[seriesId].yAxisId; return yAxisId === axis.id || (isDefaultAxis && yAxisId === undefined); }) .reduce( @@ -47,8 +47,8 @@ export const getExtremumY: ExtremumGetter<'line'> = (params) => { const filter = getFilters?.({ currentAxisId: axis.id, isDefaultAxis, - seriesXAxisId: series[seriesId].xAxisId ?? series[seriesId].xAxisKey, - seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey, + seriesXAxisId: series[seriesId].xAxisId, + seriesYAxisId: series[seriesId].yAxisId, }); // Since this series is not used to display an area, we do not consider the base (the d[0]). diff --git a/packages/x-charts/src/LineChart/formatter.ts b/packages/x-charts/src/LineChart/formatter.ts index ec0ec804abb50..9364ed579e3e5 100644 --- a/packages/x-charts/src/LineChart/formatter.ts +++ b/packages/x-charts/src/LineChart/formatter.ts @@ -1,9 +1,9 @@ import { stack as d3Stack } from '@mui/x-charts-vendor/d3-shape'; import { warnOnce } from '@mui/x-internals/warning'; +import { DefaultizedProps } from '@mui/x-internals/types'; import { getStackingGroups } from '../internals/stackSeries'; import { ChartSeries, DatasetElementType, DatasetType } from '../models/seriesType/config'; import { defaultizeValueFormatter } from '../internals/defaultizeValueFormatter'; -import { DefaultizedProps } from '../models/helpers'; import { SeriesId } from '../models/seriesType/common'; import { SeriesFormatter } from '../context/PluginProvider/SeriesFormatter.types'; diff --git a/packages/x-charts/src/LineChart/useLineChartProps.ts b/packages/x-charts/src/LineChart/useLineChartProps.ts index 2b609dc7ff5d1..75ccf3bab7809 100644 --- a/packages/x-charts/src/LineChart/useLineChartProps.ts +++ b/packages/x-charts/src/LineChart/useLineChartProps.ts @@ -41,7 +41,6 @@ export const useLineChartProps = (props: LineChartProps) => { onMarkClick, axisHighlight, disableLineItemHighlight, - legend, grid, topAxis, leftAxis, @@ -160,7 +159,6 @@ export const useLineChartProps = (props: LineChartProps) => { }; const legendProps: ChartsLegendProps = { - ...legend, slots, slotProps, }; diff --git a/packages/x-charts/src/PieChart/PieArc.tsx b/packages/x-charts/src/PieChart/PieArc.tsx index 6919b17363ed7..99c7eda9f856f 100644 --- a/packages/x-charts/src/PieChart/PieArc.tsx +++ b/packages/x-charts/src/PieChart/PieArc.tsx @@ -9,7 +9,6 @@ import { styled } from '@mui/material/styles'; import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; import { useInteractionItemProps } from '../hooks/useInteractionItemProps'; import { PieItemId } from '../models'; -import { HighlightScope } from '../context'; export interface PieArcClasses { /** Styles applied to the root element. */ @@ -64,10 +63,6 @@ export type PieArcProps = Omit, 'ref' | 'id'> & PieArcOwnerState & { cornerRadius: SpringValue; endAngle: SpringValue; - /** - * @deprecated Use the `isFaded` or `isHighlighted` props instead. - */ - highlightScope?: Partial; innerRadius: SpringValue; onClick?: (event: React.MouseEvent) => void; outerRadius: SpringValue; @@ -90,7 +85,6 @@ function PieArc(props: PieArcProps) { outerRadius, paddingAngle, startAngle, - highlightScope, ...other } = props; @@ -137,15 +131,6 @@ PieArc.propTypes = { // ---------------------------------------------------------------------- classes: PropTypes.object, dataIndex: PropTypes.number.isRequired, - /** - * @deprecated Use the `isFaded` or `isHighlighted` props instead. - */ - highlightScope: PropTypes.shape({ - fade: PropTypes.oneOf(['global', 'none', 'series']), - faded: PropTypes.oneOf(['global', 'none', 'series']), - highlight: PropTypes.oneOf(['item', 'none', 'series']), - highlighted: PropTypes.oneOf(['item', 'none', 'series']), - }), id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, isFaded: PropTypes.bool.isRequired, isHighlighted: PropTypes.bool.isRequired, diff --git a/packages/x-charts/src/PieChart/PieArcPlot.tsx b/packages/x-charts/src/PieChart/PieArcPlot.tsx index 13e233d5b642a..c2c7279a9ef97 100644 --- a/packages/x-charts/src/PieChart/PieArcPlot.tsx +++ b/packages/x-charts/src/PieChart/PieArcPlot.tsx @@ -15,7 +15,6 @@ import { ValueWithHighlight, useTransformData, } from './dataTransform/useTransformData'; -import { useHighlighted } from '../context'; export interface PieArcPlotSlots { pieArc?: React.JSXElementConstructor; @@ -95,7 +94,6 @@ function PieArcPlot(props: PieArcPlotProps) { ...defaultTransitionConfig, immediate: skipAnimation, }); - const { highlightScope } = useHighlighted(); if (data.length === 0) { return null; @@ -133,7 +131,6 @@ function PieArcPlot(props: PieArcPlotProps) { id={id} color={item.color} dataIndex={index} - highlightScope={highlightScope} isFaded={item.isFaded} isHighlighted={item.isHighlighted} onClick={ diff --git a/packages/x-charts/src/PieChart/PieChart.tsx b/packages/x-charts/src/PieChart/PieChart.tsx index 94b5ae48ac893..b0496fa39bb5f 100644 --- a/packages/x-charts/src/PieChart/PieChart.tsx +++ b/packages/x-charts/src/PieChart/PieChart.tsx @@ -3,35 +3,21 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useRtl } from '@mui/system/RtlProvider'; import { useThemeProps } from '@mui/material/styles'; +import { MakeOptional } from '@mui/x-internals/types'; import { ResponsiveChartContainer, ResponsiveChartContainerProps, } from '../ResponsiveChartContainer'; -import { ChartsAxis, ChartsAxisProps } from '../ChartsAxis/ChartsAxis'; import { PieSeriesType } from '../models/seriesType'; -import { MakeOptional } from '../models/helpers'; -import { DEFAULT_X_AXIS_KEY } from '../constants'; import { ChartsTooltip, ChartsTooltipProps, ChartsTooltipSlotProps, ChartsTooltipSlots, } from '../ChartsTooltip'; -import { - ChartsLegend, - ChartsLegendProps, - ChartsLegendSlotProps, - ChartsLegendSlots, -} from '../ChartsLegend'; -import { ChartsAxisHighlight, ChartsAxisHighlightProps } from '../ChartsAxisHighlight'; +import { ChartsLegend, ChartsLegendSlotProps, ChartsLegendSlots } from '../ChartsLegend'; import { PiePlot, PiePlotProps, PiePlotSlotProps, PiePlotSlots } from './PiePlot'; import { PieValueType } from '../models/seriesType/pie'; -import { - ChartsAxisSlots, - ChartsAxisSlotProps, - ChartsXAxisProps, - ChartsYAxisProps, -} from '../models/axis'; import { ChartsOverlay, ChartsOverlayProps, @@ -40,15 +26,13 @@ import { } from '../ChartsOverlay'; export interface PieChartSlots - extends ChartsAxisSlots, - PiePlotSlots, + extends PiePlotSlots, ChartsLegendSlots, ChartsTooltipSlots<'pie'>, ChartsOverlaySlots {} export interface PieChartSlotProps - extends ChartsAxisSlotProps, - PiePlotSlotProps, + extends PiePlotSlotProps, ChartsLegendSlotProps, ChartsTooltipSlotProps<'pie'>, ChartsOverlaySlotProps {} @@ -58,21 +42,8 @@ export interface PieChartProps ResponsiveChartContainerProps, 'series' | 'leftAxis' | 'bottomAxis' | 'plugins' | 'zAxis' >, - Omit, Omit, Pick { - /** - * Indicate which axis to display the bottom of the charts. - * Can be a string (the id of the axis) or an object `ChartsXAxisProps`. - * @default null - */ - bottomAxis?: null | string | ChartsXAxisProps; - /** - * Indicate which axis to display the left of the charts. - * Can be a string (the id of the axis) or an object `ChartsYAxisProps`. - * @default null - */ - leftAxis?: null | string | ChartsYAxisProps; /** * The series to display in the pie chart. * An array of [[PieSeriesType]] objects. @@ -84,18 +55,6 @@ export interface PieChartProps * @default { trigger: 'item' } */ tooltip?: ChartsTooltipProps<'pie'>; - /** - * The configuration of axes highlight. - * @see See {@link https://mui.com/x/react-charts/highlighting highlighting docs} for more details. - * @default { x: 'none', y: 'none' } - */ - axisHighlight?: ChartsAxisHighlightProps; - /** - * The props of the legend. - * @default { direction: 'column', position: { vertical: 'middle', horizontal: 'right' } } - * @deprecated Consider using `slotProps.legend` instead. - */ - legend?: ChartsLegendProps; /** * Callback fired when a pie arc is clicked. */ @@ -137,13 +96,7 @@ const PieChart = React.forwardRef(function PieChart(inProps: PieChartProps, ref) colors, sx, tooltip = { trigger: 'item' }, - axisHighlight = { x: 'none', y: 'none' }, skipAnimation, - legend: legendProps, - topAxis = null, - leftAxis = null, - rightAxis = null, - bottomAxis = null, children, slots, slotProps, @@ -157,11 +110,6 @@ const PieChart = React.forwardRef(function PieChart(inProps: PieChartProps, ref) const isRtl = useRtl(); const margin = { ...(isRtl ? defaultRTLMargin : defaultMargin), ...marginProps }; - const legend: ChartsLegendProps = { - direction: 'column', - position: { vertical: 'middle', horizontal: isRtl ? 'left' : 'right' }, - ...legendProps, - }; return ( s.data.length)))].map( - (_, index) => index, - ), - }, - ] - } - yAxis={yAxis} colors={colors} sx={sx} - disableAxisListener={ - tooltip?.trigger !== 'axis' && axisHighlight?.x === 'none' && axisHighlight?.y === 'none' - } + disableAxisListener highlightedItem={highlightedItem} onHighlightChange={onHighlightChange} className={className} skipAnimation={skipAnimation} > - + + - - - - {!loading && } {children} @@ -216,21 +146,6 @@ PieChart.propTypes = { // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "pnpm proptypes" | // ---------------------------------------------------------------------- - /** - * The configuration of axes highlight. - * @see See {@link https://mui.com/x/react-charts/highlighting highlighting docs} for more details. - * @default { x: 'none', y: 'none' } - */ - axisHighlight: PropTypes.shape({ - x: PropTypes.oneOf(['band', 'line', 'none']), - y: PropTypes.oneOf(['band', 'line', 'none']), - }), - /** - * Indicate which axis to display the bottom of the charts. - * Can be a string (the id of the axis) or an object `ChartsXAxisProps`. - * @default null - */ - bottomAxis: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), children: PropTypes.node, className: PropTypes.string, /** @@ -260,43 +175,6 @@ PieChart.propTypes = { dataIndex: PropTypes.number, seriesId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), }), - /** - * Indicate which axis to display the left of the charts. - * Can be a string (the id of the axis) or an object `ChartsYAxisProps`. - * @default null - */ - leftAxis: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - /** - * The props of the legend. - * @default { direction: 'column', position: { vertical: 'middle', horizontal: 'right' } } - * @deprecated Consider using `slotProps.legend` instead. - */ - legend: PropTypes.shape({ - 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, - onItemClick: PropTypes.func, - 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, - }), - slotProps: PropTypes.object, - slots: PropTypes.object, - }), /** * If `true`, a loading overlay is displayed. * @default false @@ -334,12 +212,6 @@ PieChart.propTypes = { * @default false */ resolveSizeBeforeRender: PropTypes.bool, - /** - * Indicate which axis to display the right of the charts. - * Can be a string (the id of the axis) or an object `ChartsYAxisProps`. - * @default null - */ - rightAxis: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), /** * The series to display in the pie chart. * An array of [[PieSeriesType]] objects. @@ -379,12 +251,6 @@ PieChart.propTypes = { slots: PropTypes.object, trigger: PropTypes.oneOf(['axis', 'item', 'none']), }), - /** - * Indicate which axis to display the top of the charts. - * Can be a string (the id of the axis) or an object `ChartsXAxisProps`. - * @default null - */ - topAxis: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), viewBox: PropTypes.shape({ height: PropTypes.number, width: PropTypes.number, diff --git a/packages/x-charts/src/ScatterChart/ScatterChart.tsx b/packages/x-charts/src/ScatterChart/ScatterChart.tsx index 88a9465211217..97583d34e035b 100644 --- a/packages/x-charts/src/ScatterChart/ScatterChart.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterChart.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useThemeProps } from '@mui/material/styles'; +import { MakeOptional } from '@mui/x-internals/types'; import { ScatterPlot, ScatterPlotProps, @@ -14,19 +15,13 @@ import { } from '../ResponsiveChartContainer'; import { ChartsAxis, ChartsAxisProps } from '../ChartsAxis'; import { ScatterSeriesType } from '../models/seriesType/scatter'; -import { MakeOptional } from '../models/helpers'; import { ChartsTooltip, ChartsTooltipProps, ChartsTooltipSlotProps, ChartsTooltipSlots, } from '../ChartsTooltip'; -import { - ChartsLegend, - ChartsLegendProps, - ChartsLegendSlotProps, - ChartsLegendSlots, -} from '../ChartsLegend'; +import { ChartsLegend, ChartsLegendSlotProps, ChartsLegendSlots } from '../ChartsLegend'; import { ChartsOverlay, ChartsOverlayProps, @@ -88,10 +83,6 @@ export interface ScatterChartProps * @default false */ disableVoronoi?: boolean; - /** - * @deprecated Consider using `slotProps.legend` instead. - */ - legend?: ChartsLegendProps; /** * Overridable component slots. * @default {} @@ -222,35 +213,6 @@ ScatterChart.propTypes = { * @default yAxisIds[0] The id of the first provided axis */ leftAxis: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - /** - * @deprecated Consider using `slotProps.legend` instead. - */ - legend: PropTypes.shape({ - 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, - onItemClick: PropTypes.func, - 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, - }), - slotProps: PropTypes.object, - slots: PropTypes.object, - }), /** * If `true`, a loading overlay is displayed. * @default false diff --git a/packages/x-charts/src/ScatterChart/ScatterPlot.tsx b/packages/x-charts/src/ScatterChart/ScatterPlot.tsx index dcc8f4ebf0838..639121a8c79e8 100644 --- a/packages/x-charts/src/ScatterChart/ScatterPlot.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterPlot.tsx @@ -59,17 +59,16 @@ function ScatterPlot(props: ScatterPlotProps) { return ( {seriesOrder.map((seriesId) => { - const { id, xAxisKey, yAxisKey, zAxisKey, xAxisId, yAxisId, zAxisId, markerSize, color } = - series[seriesId]; + const { id, xAxisId, yAxisId, zAxisId, markerSize, color } = series[seriesId]; const colorGetter = getColor( series[seriesId], - xAxis[xAxisId ?? xAxisKey ?? defaultXAxisId], - yAxis[yAxisId ?? yAxisKey ?? defaultYAxisId], - zAxis[zAxisId ?? zAxisKey ?? defaultZAxisId], + xAxis[xAxisId ?? defaultXAxisId], + yAxis[yAxisId ?? defaultYAxisId], + zAxis[zAxisId ?? defaultZAxisId], ); - const xScale = xAxis[xAxisId ?? xAxisKey ?? defaultXAxisId].scale; - const yScale = yAxis[yAxisId ?? yAxisKey ?? defaultYAxisId].scale; + const xScale = xAxis[xAxisId ?? defaultXAxisId].scale; + const yScale = yAxis[yAxisId ?? defaultYAxisId].scale; return ( = (params) => { return Object.keys(series) .filter((seriesId) => { - const axisId = series[seriesId].xAxisId ?? series[seriesId].xAxisKey; + const axisId = series[seriesId].xAxisId; return axisId === axis.id || (axisId === undefined && isDefaultAxis); }) .reduce( @@ -23,8 +23,8 @@ export const getExtremumX: ExtremumGetter<'scatter'> = (params) => { const filter = getFilters?.({ currentAxisId: axis.id, isDefaultAxis, - seriesXAxisId: series[seriesId].xAxisId ?? series[seriesId].xAxisKey, - seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey, + seriesXAxisId: series[seriesId].xAxisId, + seriesYAxisId: series[seriesId].yAxisId, }); const seriesMinMax = series[seriesId].data?.reduce( @@ -47,7 +47,7 @@ export const getExtremumY: ExtremumGetter<'scatter'> = (params) => { return Object.keys(series) .filter((seriesId) => { - const axisId = series[seriesId].yAxisId ?? series[seriesId].yAxisKey; + const axisId = series[seriesId].yAxisId; return axisId === axis.id || (axisId === undefined && isDefaultAxis); }) .reduce( @@ -55,8 +55,8 @@ export const getExtremumY: ExtremumGetter<'scatter'> = (params) => { const filter = getFilters?.({ currentAxisId: axis.id, isDefaultAxis, - seriesXAxisId: series[seriesId].xAxisId ?? series[seriesId].xAxisKey, - seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey, + seriesXAxisId: series[seriesId].xAxisId, + seriesYAxisId: series[seriesId].yAxisId, }); const seriesMinMax = series[seriesId].data?.reduce( diff --git a/packages/x-charts/src/ScatterChart/useScatterChartProps.ts b/packages/x-charts/src/ScatterChart/useScatterChartProps.ts index c54a3b8c7fd0c..e6ae591aa08d1 100644 --- a/packages/x-charts/src/ScatterChart/useScatterChartProps.ts +++ b/packages/x-charts/src/ScatterChart/useScatterChartProps.ts @@ -28,7 +28,6 @@ export const useScatterChartProps = (props: ScatterChartProps) => { axisHighlight, voronoiMaxRadius, disableVoronoi, - legend, width, height, margin, @@ -98,7 +97,6 @@ export const useScatterChartProps = (props: ScatterChartProps) => { }; const legendProps: ChartsLegendProps = { - ...legend, slots, slotProps, }; diff --git a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx index d6e698333a55e..af5b7b3d8760e 100644 --- a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx +++ b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; +import { MakeOptional } from '@mui/x-internals/types'; import { BarPlot } from '../BarChart'; import { LinePlot, AreaPlot, LineHighlightPlot } from '../LineChart'; import { @@ -16,7 +17,6 @@ import { } from '../ChartsTooltip'; import { ChartsAxisHighlight, ChartsAxisHighlightProps } from '../ChartsAxisHighlight'; import { AxisConfig, ChartsXAxisProps, ChartsYAxisProps, ScaleName } from '../models/axis'; -import { MakeOptional } from '../models/helpers'; import { LineSeriesType, BarSeriesType } from '../models/seriesType'; import { CardinalDirections } from '../models/layout'; import { AreaPlotSlots, AreaPlotSlotProps } from '../LineChart/AreaPlot'; diff --git a/packages/x-charts/src/context/CartesianProvider/defaultizeAxis.ts b/packages/x-charts/src/context/CartesianProvider/defaultizeAxis.ts index 6848835d34a6c..0ae491cce71d4 100644 --- a/packages/x-charts/src/context/CartesianProvider/defaultizeAxis.ts +++ b/packages/x-charts/src/context/CartesianProvider/defaultizeAxis.ts @@ -1,5 +1,5 @@ +import { MakeOptional } from '@mui/x-internals/types'; import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '../../constants'; -import { MakeOptional } from '../../models/helpers'; import { AxisConfig, ScaleName } from '../../models'; import { ChartsAxisProps } from '../../models/axis'; diff --git a/packages/x-charts/src/context/HighlightedProvider/HighlightedContext.ts b/packages/x-charts/src/context/HighlightedProvider/HighlightedContext.ts index 335b8ba592239..f89055efd50b4 100644 --- a/packages/x-charts/src/context/HighlightedProvider/HighlightedContext.ts +++ b/packages/x-charts/src/context/HighlightedProvider/HighlightedContext.ts @@ -34,10 +34,6 @@ export type HighlightOptions = 'none' | 'item' | 'series'; export type FadeOptions = 'none' | 'series' | 'global'; export type HighlightScope = { - /** - * @deprecated Use `highlight` instead. - */ - highlighted?: HighlightOptions; /** * The scope of highlighted elements. * - 'none': no highlight. @@ -46,10 +42,6 @@ export type HighlightScope = { * @default 'none' */ highlight?: HighlightOptions; - /** - * @deprecated Use `fade` instead. - */ - faded?: FadeOptions; /** * The scope of faded elements. * - 'none': no fading. diff --git a/packages/x-charts/src/context/HighlightedProvider/HighlightedProvider.tsx b/packages/x-charts/src/context/HighlightedProvider/HighlightedProvider.tsx index 23a99d9b3e39e..a5e097e067f6d 100644 --- a/packages/x-charts/src/context/HighlightedProvider/HighlightedProvider.tsx +++ b/packages/x-charts/src/context/HighlightedProvider/HighlightedProvider.tsx @@ -29,15 +29,6 @@ export type HighlightedProviderProps = { onHighlightChange?: (highlightedItem: HighlightItemData | null) => void; }; -const mergeDeprecatedOptions = (options?: Partial): HighlightScope => { - const { highlighted, faded, ...other } = options ?? {}; - return { - highlight: highlighted, - fade: faded, - ...other, - }; -}; - function HighlightedProvider({ children, highlightedItem: highlightedItemProps, @@ -58,7 +49,7 @@ function HighlightedProvider({ const seriesData = series[seriesType as ChartSeriesType]; Object.keys(seriesData?.series ?? {}).forEach((seriesId) => { const seriesItem = seriesData?.series[seriesId]; - map.set(seriesId, mergeDeprecatedOptions(seriesItem?.highlightScope)); + map.set(seriesId, seriesItem?.highlightScope); }); }); return map; diff --git a/packages/x-charts/src/context/ZAxisContextProvider.tsx b/packages/x-charts/src/context/ZAxisContextProvider.tsx index 0c028a6e7fc9b..67763d9f7cc68 100644 --- a/packages/x-charts/src/context/ZAxisContextProvider.tsx +++ b/packages/x-charts/src/context/ZAxisContextProvider.tsx @@ -1,8 +1,8 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; +import { MakeOptional } from '@mui/x-internals/types'; import { DatasetType } from '../models/seriesType/config'; -import { MakeOptional } from '../models/helpers'; import { getColorScale, getOrdinalColorScale } from '../internals/colorScale'; import { ZAxisConfig, ZAxisDefaultized } from '../models/z-axis'; diff --git a/packages/x-charts/src/internals/SlotComponentPropsFromProps.ts b/packages/x-charts/src/internals/SlotComponentPropsFromProps.ts deleted file mode 100644 index 7ea98c11f1bac..0000000000000 --- a/packages/x-charts/src/internals/SlotComponentPropsFromProps.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type SlotComponentPropsFromProps< - TProps extends {}, - TOverrides extends {}, - TOwnerState extends {}, -> = (Partial & TOverrides) | ((ownerState: TOwnerState) => Partial & TOverrides); diff --git a/packages/x-charts/src/internals/getSymbol.ts b/packages/x-charts/src/internals/getSymbol.ts index 72d216c2e02ab..09e83d4fadba0 100644 --- a/packages/x-charts/src/internals/getSymbol.ts +++ b/packages/x-charts/src/internals/getSymbol.ts @@ -1,7 +1,15 @@ export type SymbolsTypes = 'circle' | 'cross' | 'diamond' | 'square' | 'star' | 'triangle' | 'wye'; -// Returns the index of a defined shape -export function getSymbol(shape: SymbolsTypes): number { - const symbolNames = 'circle cross diamond square star triangle wye'.split(/ /); - return symbolNames.indexOf(shape) || 0; +export function getSymbol(shape: SymbolsTypes): number { + // prettier-ignore + switch (shape) { + case 'circle': return 0; + case 'cross': return 1; + case 'diamond': return 2; + case 'square': return 3; + case 'star': return 4; + case 'triangle': return 5; + case 'wye': return 6; + default: return 0; + } } diff --git a/packages/x-charts/src/internals/index.ts b/packages/x-charts/src/internals/index.ts index c8f1ffd8626bf..2c9e50be6b427 100644 --- a/packages/x-charts/src/internals/index.ts +++ b/packages/x-charts/src/internals/index.ts @@ -40,6 +40,5 @@ export { getAxisExtremum } from '../context/CartesianProvider/getAxisExtremum'; export * from '../models/seriesType/config'; export * from '../models/seriesType/common'; -export * from '../models/helpers'; export * from '../models/z-axis'; export * from '../models/axis'; diff --git a/packages/x-charts/src/models/seriesType/bar.ts b/packages/x-charts/src/models/seriesType/bar.ts index 94023220b54bd..238d7c7e4cbe0 100644 --- a/packages/x-charts/src/models/seriesType/bar.ts +++ b/packages/x-charts/src/models/seriesType/bar.ts @@ -1,4 +1,4 @@ -import { DefaultizedProps } from '../helpers'; +import { DefaultizedProps } from '@mui/x-internals/types'; import type { StackOffsetType } from '../stacking'; import { CartesianSeriesType, diff --git a/packages/x-charts/src/models/seriesType/common.ts b/packages/x-charts/src/models/seriesType/common.ts index 5ec59cc45455e..356c8a4b4b23b 100644 --- a/packages/x-charts/src/models/seriesType/common.ts +++ b/packages/x-charts/src/models/seriesType/common.ts @@ -13,7 +13,7 @@ export type SeriesValueFormatterContext = { export type SeriesValueFormatter = ( value: TValue, context: SeriesValueFormatterContext, -) => string; +) => string | null; export type CommonSeriesType = { id?: SeriesId; @@ -22,7 +22,7 @@ export type CommonSeriesType = { * Formatter used to render values in tooltip or other data display. * @param {TValue} value The series' value to render. * @param {SeriesValueFormatterContext} context The rendering context of the value. - * @returns {string} The string to display. + * @returns {string | null} The string to display or null if the value should not be shown. */ valueFormatter?: SeriesValueFormatter; /** @@ -34,16 +34,6 @@ export type CommonSeriesType = { export type CommonDefaultizedProps = 'id' | 'valueFormatter' | 'data'; export type CartesianSeriesType = { - /** - * The id of the x-axis used to render the series. - * @deprecated Use `xAxisId` instead - */ - xAxisKey?: string; - /** - * The id of the y-axis used to render the series. - * @deprecated Use `xAxisId` instead - */ - yAxisKey?: string; /** * The id of the x-axis used to render the series. */ diff --git a/packages/x-charts/src/models/seriesType/config.ts b/packages/x-charts/src/models/seriesType/config.ts index 563ca7b6ec62a..f266d53fa487a 100644 --- a/packages/x-charts/src/models/seriesType/config.ts +++ b/packages/x-charts/src/models/seriesType/config.ts @@ -1,3 +1,4 @@ +import { DefaultizedProps, MakeOptional } from '@mui/x-internals/types'; import { ScatterSeriesType, DefaultizedScatterSeriesType, @@ -13,7 +14,6 @@ import { PieValueType, DefaultizedPieValueType, } from './pie'; -import { DefaultizedProps, MakeOptional } from '../helpers'; export interface ChartsSeriesConfig { bar: { diff --git a/packages/x-charts/src/models/seriesType/line.ts b/packages/x-charts/src/models/seriesType/line.ts index f27c6f4eec49c..298d95b30cac6 100644 --- a/packages/x-charts/src/models/seriesType/line.ts +++ b/packages/x-charts/src/models/seriesType/line.ts @@ -1,4 +1,4 @@ -import { DefaultizedProps } from '../helpers'; +import { DefaultizedProps } from '@mui/x-internals/types'; import type { StackOffsetType } from '../stacking'; import { CartesianSeriesType, diff --git a/packages/x-charts/src/models/seriesType/pie.ts b/packages/x-charts/src/models/seriesType/pie.ts index bf87340066cb4..1ab1a6a4b5a87 100644 --- a/packages/x-charts/src/models/seriesType/pie.ts +++ b/packages/x-charts/src/models/seriesType/pie.ts @@ -1,5 +1,5 @@ import { PieArcDatum as D3PieArcDatum } from '@mui/x-charts-vendor/d3-shape'; -import { DefaultizedProps } from '../helpers'; +import { DefaultizedProps } from '@mui/x-internals/types'; import { CommonDefaultizedProps, CommonSeriesType, SeriesId } from './common'; export type PieItemId = string | number; diff --git a/packages/x-charts/src/models/seriesType/scatter.ts b/packages/x-charts/src/models/seriesType/scatter.ts index 211763f1ef36c..61f4d0c4b8089 100644 --- a/packages/x-charts/src/models/seriesType/scatter.ts +++ b/packages/x-charts/src/models/seriesType/scatter.ts @@ -1,4 +1,4 @@ -import { DefaultizedProps } from '../helpers'; +import { DefaultizedProps } from '@mui/x-internals/types'; import { CartesianSeriesType, CommonDefaultizedProps, CommonSeriesType, SeriesId } from './common'; export type ScatterValueType = { @@ -24,11 +24,6 @@ export interface ScatterSeriesType extends CommonSeriesType, C * @default false */ disableHover?: boolean; - /** - * The id of the z-axis used to render the series. - * @deprecated Use `zAxisId` instead. - */ - zAxisKey?: string; /** * The id of the z-axis used to render the series. */ diff --git a/packages/x-codemod/README.md b/packages/x-codemod/README.md index 453f783b567d3..af1290cabb817 100644 --- a/packages/x-codemod/README.md +++ b/packages/x-codemod/README.md @@ -13,7 +13,7 @@ This repository contains a collection of codemod scripts based for use with ```bash -npx @mui/x-codemod@latest +npx @mui/x-codemod@next Applies a `@mui/x-codemod` to the specified paths @@ -29,8 +29,8 @@ Options: --jscodeshift Pass options directly to jscodeshift [array] Examples: - npx @mui/x-codemod@latest v7.0.0/preset-safe src - npx @mui/x-codemod@latest v6.0.0/component-rename-prop src -- + npx @mui/x-codemod@next v7.0.0/preset-safe src + npx @mui/x-codemod@next v6.0.0/component-rename-prop src -- --component=DataGrid --from=prop --to=newProp ``` @@ -40,9 +40,9 @@ To pass more options directly to jscodeshift, use `--jscodeshift=...`. For examp ```bash // single option -npx @mui/x-codemod@latest --jscodeshift=--run-in-band +npx @mui/x-codemod@next --jscodeshift=--run-in-band // multiple options -npx @mui/x-codemod@latest --jscodeshift=--cpus=1 --jscodeshift=--print --jscodeshift=--dry --jscodeshift=--verbose=2 +npx @mui/x-codemod@next --jscodeshift=--cpus=1 --jscodeshift=--print --jscodeshift=--dry --jscodeshift=--verbose=2 ``` See all available options [here](https://github.com/facebook/jscodeshift#usage-cli). @@ -53,7 +53,50 @@ Options to [recast](https://github.com/benjamn/recast)'s printer can be provided through jscodeshift's `printOptions` command line argument ```bash -npx @mui/x-codemod@latest --jscodeshift="--printOptions='{\"quote\":\"double\"}'" +npx @mui/x-codemod@next --jscodeshift="--printOptions='{\"quote\":\"double\"}'" +``` + +## v8.0.0 + +### 🚀 `preset-safe` for v8.0.0 + +A combination of all important transformers for migrating v7 to v8. +⚠️ This codemod should be run only once. +It runs codemods for both Data Grid and Date and Time Pickers packages. +To run codemods for a specific package, refer to the respective section. + +```bash +npx @mui/x-codemod@latest v8.0.0/preset-safe +``` + +The corresponding sub-sections are listed below + +- [`preset-safe-for-tree-view`](#preset-safe-for-tree-view-v800) + +### Tree View codemods + +#### `preset-safe` for tree view v8.0.0 + +The `preset-safe` codemods for tree view. + +```bash +npx @mui/x-codemod@latest v8.0.0/tree-view/preset-safe +``` + +The list includes these transformers + +- [`rename-tree-item-2`](#rename-tree-item-2) + +#### `rename-tree-item-2` + +Renames the `TreeItem2` component to `TreeItem` (same for any subcomponents or utils like `useTreeItem2` or `TreeItem2Icon`). + +```diff +-import { TreeItem2 } from '@mui/x-tree-view'; ++import { TreeItem } from '@mui/x-tree-view'; + +-import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; ++import { TreeItem } from '@mui/x-tree-view/TreeItem'; ``` ## v7.0.0 @@ -227,9 +270,9 @@ npx @mui/x-codemod@latest v7.0.0/data-grid/remove-stabilized-experimentalFeature ### Tree View codemods -#### `preset-safe` for tree view v7.0.0 +#### `preset-safe` for Tree View v7.0.0 -The `preset-safe` codemods for tree view. +The `preset-safe` codemods for Tree View. ```bash npx @mui/x-codemod@latest v7.0.0/tree-view/preset-safe @@ -247,7 +290,7 @@ The list includes these transformers #### `rename-tree-view-simple-tree-view` -Renames the `TreeView` component to `SimpleTreeView` +Renames the Tree View component to Simple Tree View ```diff -import { TreeView } from '@mui/x-tree-view'; diff --git a/packages/x-codemod/package.json b/packages/x-codemod/package.json index 4f9a0b5687a4b..15d3299c56d54 100644 --- a/packages/x-codemod/package.json +++ b/packages/x-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-codemod", - "version": "7.20.0", + "version": "7.21.0", "bin": "./codemod.js", "private": false, "author": "MUI Team", @@ -32,9 +32,10 @@ "url": "https://opencollective.com/mui-org" }, "dependencies": { - "@babel/core": "^7.25.8", - "@babel/runtime": "^7.25.7", - "@babel/traverse": "^7.25.7", + "@babel/core": "^7.26.0", + "@babel/runtime": "^7.26.0", + "@babel/traverse": "^7.25.9", + "@mui/x-internals": "workspace:*", "jscodeshift": "17.0.0", "yargs": "^17.7.2" }, diff --git a/packages/x-codemod/src/types.ts b/packages/x-codemod/src/types.ts index 0e5315efaf193..6a93f6de2e1fa 100644 --- a/packages/x-codemod/src/types.ts +++ b/packages/x-codemod/src/types.ts @@ -1,9 +1,8 @@ import type { FileInfo, API } from 'jscodeshift'; +import { MakeOptional } from '@mui/x-internals/types'; -type MakeRequired = Pick & Partial>; - -type MakeOptional = Omit & Partial>; +type KeepRequired = Pick & Partial>; export type JsCodeShiftFileInfo = MakeOptional; -export type JsCodeShiftAPI = MakeRequired; +export type JsCodeShiftAPI = KeepRequired; diff --git a/packages/x-codemod/src/v8.0.0/charts/preset-safe/actual.spec.tsx b/packages/x-codemod/src/v8.0.0/charts/preset-safe/actual.spec.tsx new file mode 100644 index 0000000000000..dbeda401f0286 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/charts/preset-safe/actual.spec.tsx @@ -0,0 +1,9 @@ +// @ts-nocheck +import * as React from 'react'; +import { PieChart } from '@mui/x-charts/PieChart'; + +// prettier-ignore +
+ + +
; diff --git a/packages/x-codemod/src/v8.0.0/charts/preset-safe/expected.spec.tsx b/packages/x-codemod/src/v8.0.0/charts/preset-safe/expected.spec.tsx new file mode 100644 index 0000000000000..cc9413b2bf3b9 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/charts/preset-safe/expected.spec.tsx @@ -0,0 +1,16 @@ +// @ts-nocheck +import * as React from 'react'; +import { PieChart } from '@mui/x-charts/PieChart'; + +// prettier-ignore +
+ + +
; diff --git a/packages/x-codemod/src/v8.0.0/charts/preset-safe/index.ts b/packages/x-codemod/src/v8.0.0/charts/preset-safe/index.ts new file mode 100644 index 0000000000000..6e02a9f2935f9 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/charts/preset-safe/index.ts @@ -0,0 +1,9 @@ +import transformLegendToSlots from '../rename-legend-to-slots-legend'; + +import { JsCodeShiftAPI, JsCodeShiftFileInfo } from '../../../types'; + +export default function transformer(file: JsCodeShiftFileInfo, api: JsCodeShiftAPI, options: any) { + file.source = transformLegendToSlots(file, api, options); + + return file.source; +} diff --git a/packages/x-codemod/src/v8.0.0/charts/preset-safe/preset-safe.test.ts b/packages/x-codemod/src/v8.0.0/charts/preset-safe/preset-safe.test.ts new file mode 100644 index 0000000000000..95b4e3f0d69be --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/charts/preset-safe/preset-safe.test.ts @@ -0,0 +1,41 @@ +import path from 'path'; +import { expect } from 'chai'; +import jscodeshift from 'jscodeshift'; +import transform from './index'; +import readFile from '../../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +describe('v8.0.0/charts', () => { + describe('preset-safe', () => { + it('transforms code as needed', () => { + const actual = transform( + { + source: read('./actual.spec.tsx'), + path: require.resolve('./actual.spec.tsx'), + }, + { jscodeshift: jscodeshift.withParser('tsx') }, + {}, + ); + + const expected = read('./expected.spec.tsx'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent for expression', () => { + const actual = transform( + { + source: read('./expected.spec.tsx'), + path: require.resolve('./expected.spec.tsx'), + }, + { jscodeshift: jscodeshift.withParser('tsx') }, + {}, + ); + + const expected = read('./expected.spec.tsx'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); +}); diff --git a/packages/x-codemod/src/v8.0.0/charts/rename-legend-to-slots-legend/index.ts b/packages/x-codemod/src/v8.0.0/charts/rename-legend-to-slots-legend/index.ts new file mode 100644 index 0000000000000..2c6d0aeec4ac8 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/charts/rename-legend-to-slots-legend/index.ts @@ -0,0 +1,79 @@ +import { JsCodeShiftAPI, JsCodeShiftFileInfo } from '../../../types'; +import { transformNestedProp } from '../../../util/addComponentsSlots'; +/** + * @param {import('jscodeshift').FileInfo} file + * @param {import('jscodeshift').API} api + */ +export default function transformer(file: JsCodeShiftFileInfo, api: JsCodeShiftAPI, options: any) { + const j = api.jscodeshift; + + const printOptions = options.printOptions; + + const root = j(file.source); + + root + .find(j.ImportDeclaration) + .filter(({ node }) => { + return typeof node.source.value === 'string' && node.source.value.startsWith('@mui/x-charts'); + }) + .forEach((path) => { + path.node.specifiers?.forEach((node) => { + root.findJSXElements(node.local?.name).forEach((elementPath) => { + if (elementPath.node.type !== 'JSXElement') { + return; + } + + const legendProps = elementPath.node.openingElement.attributes?.find( + (elementNode) => + elementNode.type === 'JSXAttribute' && elementNode.name.name === 'legend', + ); + + if (!legendProps) { + // No legend props to manage + return; + } + + const slotProps = elementPath.node.openingElement.attributes?.find( + (elementNode) => + elementNode.type === 'JSXAttribute' && elementNode.name.name === 'slotProps', + ); + + if (slotProps === null) { + // We create a new slotProps object + elementPath.node.openingElement.attributes?.push( + j.jsxAttribute( + j.jsxIdentifier('slotProps'), + j.jsxExpressionContainer( + j.objectExpression([ + // @ts-ignore legend receives an object. + j.objectProperty(j.identifier('legend'), legendProps.value.expression), + ]), + ), + ), + ); + } else { + transformNestedProp( + elementPath, + 'slotProps', + 'legend', + // @ts-ignore legend receives an object. + legendProps.value.expression, + j, + ); + } + + // Remove the legend prop + j(elementPath) + .find(j.JSXAttribute) + .filter((a) => a.value.name.name === 'legend') + .forEach((pathToRemove) => { + j(pathToRemove).remove(); + }); + }); + }); + }); + + const transformed = root.findJSXElements(); + + return transformed.toSource(printOptions); +} diff --git a/packages/x-codemod/src/v8.0.0/preset-safe/actual.spec.js b/packages/x-codemod/src/v8.0.0/preset-safe/actual.spec.js new file mode 100644 index 0000000000000..f8a5b4f02aa27 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/preset-safe/actual.spec.js @@ -0,0 +1,15 @@ +// @ts-nocheck +import * as React from 'react'; +import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; +import { PieChart } from '@mui/x-charts/PieChart'; + +const className = treeViewClasses.root; + +// prettier-ignore + + + + + + diff --git a/packages/x-codemod/src/v8.0.0/preset-safe/expected.spec.js b/packages/x-codemod/src/v8.0.0/preset-safe/expected.spec.js new file mode 100644 index 0000000000000..b85fe4c34ed38 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/preset-safe/expected.spec.js @@ -0,0 +1,18 @@ +// @ts-nocheck +import * as React from 'react'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; +import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; +import { PieChart } from '@mui/x-charts/PieChart'; + +const className = treeViewClasses.root; + +// prettier-ignore + + + + + + diff --git a/packages/x-codemod/src/v8.0.0/preset-safe/index.ts b/packages/x-codemod/src/v8.0.0/preset-safe/index.ts new file mode 100644 index 0000000000000..92c7cb70ec217 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/preset-safe/index.ts @@ -0,0 +1,10 @@ +import transformTreeView from '../tree-view/preset-safe'; +import transformCharts from '../charts/preset-safe'; +import { JsCodeShiftAPI, JsCodeShiftFileInfo } from '../../types'; + +export default function transformer(file: JsCodeShiftFileInfo, api: JsCodeShiftAPI, options: any) { + file.source = transformTreeView(file, api, options); + file.source = transformCharts(file, api, options); + + return file.source; +} diff --git a/packages/x-codemod/src/v8.0.0/preset-safe/preset-safe.test.ts b/packages/x-codemod/src/v8.0.0/preset-safe/preset-safe.test.ts new file mode 100644 index 0000000000000..b0ea5e858c7a1 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/preset-safe/preset-safe.test.ts @@ -0,0 +1,27 @@ +import path from 'path'; +import { expect } from 'chai'; +import jscodeshift from 'jscodeshift'; +import transform from '.'; +import readFile from '../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +describe('v8.0.0', () => { + describe('preset-safe', () => { + it('transforms code as needed', () => { + const actual = transform({ source: read('./actual.spec.js') }, { jscodeshift }, {}); + + const expected = read('./expected.spec.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent', () => { + const actual = transform({ source: read('./expected.spec.js') }, { jscodeshift }, {}); + + const expected = read('./expected.spec.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); +}); diff --git a/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/actual.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/actual.spec.tsx new file mode 100644 index 0000000000000..4b5b1a7515621 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/actual.spec.tsx @@ -0,0 +1,11 @@ +// @ts-nocheck +import * as React from 'react'; +import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; + +const className = treeViewClasses.root; + +// prettier-ignore + + +; diff --git a/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/expected.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/expected.spec.tsx new file mode 100644 index 0000000000000..4a5aa4e135b91 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/expected.spec.tsx @@ -0,0 +1,11 @@ +// @ts-nocheck +import * as React from 'react'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; +import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; + +const className = treeViewClasses.root; + +// prettier-ignore + + +; diff --git a/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/index.ts b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/index.ts new file mode 100644 index 0000000000000..d94e0b16c0303 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/index.ts @@ -0,0 +1,9 @@ +import transformRenameTreeItem2 from '../rename-tree-item-2'; + +import { JsCodeShiftAPI, JsCodeShiftFileInfo } from '../../../types'; + +export default function transformer(file: JsCodeShiftFileInfo, api: JsCodeShiftAPI, options: any) { + file.source = transformRenameTreeItem2(file, api, options); + + return file.source; +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/preset-safe.test.ts b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/preset-safe.test.ts new file mode 100644 index 0000000000000..7f61d9c0e721e --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/preset-safe.test.ts @@ -0,0 +1,41 @@ +import path from 'path'; +import { expect } from 'chai'; +import jscodeshift from 'jscodeshift'; +import transform from './index'; +import readFile from '../../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +describe('v8.0.0/tree-view', () => { + describe('preset-safe', () => { + it('transforms code as needed', () => { + const actual = transform( + { + source: read('./actual.spec.tsx'), + path: require.resolve('./actual.spec.tsx'), + }, + { jscodeshift: jscodeshift.withParser('tsx') }, + {}, + ); + + const expected = read('./expected.spec.tsx'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent for expression', () => { + const actual = transform( + { + source: read('./expected.spec.tsx'), + path: require.resolve('./expected.spec.tsx'), + }, + { jscodeshift: jscodeshift.withParser('tsx') }, + {}, + ); + + const expected = read('./expected.spec.tsx'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); +}); diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-nested-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-nested-imports.spec.tsx new file mode 100644 index 0000000000000..48001cc74bf52 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-nested-imports.spec.tsx @@ -0,0 +1,94 @@ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem2, + TreeItem2Root, + TreeItem2Content, + TreeItem2IconContainer, + TreeItem2GroupTransition, + TreeItem2Checkbox, + TreeItem2Label, + TreeItem2Props, + TreeItem2Slots, + TreeItem2SlotProps, +} from '@mui/x-tree-view/TreeItem2'; +import { + useTreeItem2, + unstable_useTreeItem2 as useAliasedTreeItem, + UseTreeItem2Parameters, + UseTreeItem2ReturnValue, + UseTreeItem2Status, + UseTreeItem2RootSlotOwnProps, + UseTreeItem2ContentSlotOwnProps, + UseTreeItem2LabelInputSlotOwnProps, + UseTreeItem2LabelSlotOwnProps, + UseTreeItem2CheckboxSlotOwnProps, + UseTreeItem2IconContainerSlotOwnProps, + UseTreeItem2GroupTransitionSlotOwnProps, + UseTreeItem2DragAndDropOverlaySlotOwnProps, +} from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { + TreeItem2Provider, + TreeItem2ProviderProps, + FakeImportToForcePrettierNewLine, +} from '@mui/x-tree-view/TreeItem2Provider'; +import { + TreeItem2Icon, + TreeItem2IconProps, + TreeItem2IconSlots, + TreeItem2IconSlotProps, +} from '@mui/x-tree-view/TreeItem2Icon'; +import { + TreeItem2DragAndDropOverlay, + TreeItem2DragAndDropOverlayProps, +} from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; +import { + TreeItem2LabelInput, + TreeItem2LabelInputProps, + FakeImport2ToForcePrettierNewLine, +} from '@mui/x-tree-view/TreeItem2LabelInput'; + +// prettier-ignore +function App() { + useTreeItem2({}); + useAliasedTreeItem({}); + useTreeItem2Utils(); + + const treeItemProps: TreeItem2Props = {}; + const treeItemSlots: TreeItem2Slots = {}; + const treeItemSlotProps: TreeItem2SlotProps = {}; + + const params: UseTreeItem2Parameters = {}; + const returnValue: UseTreeItem2ReturnValue = {}; + const status: UseTreeItem2Status = {}; + const root: UseTreeItem2RootSlotOwnProps = {}; + const content: UseTreeItem2ContentSlotOwnProps = {}; + const labelInput: UseTreeItem2LabelInputSlotOwnProps = {}; + const label: UseTreeItem2LabelSlotOwnProps = {}; + const checkbox: UseTreeItem2CheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItem2IconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItem2GroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItem2DragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItem2ProviderProps = {}; + const treeItemIconProps: TreeItem2IconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItem2DragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItem2LabelInputProps = {}; + + return ( + + + + + + + + + + + + + + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-root-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-root-imports.spec.tsx new file mode 100644 index 0000000000000..9b5484ad5f45f --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-root-imports.spec.tsx @@ -0,0 +1,83 @@ +/* eslint-disable no-restricted-imports */ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem2, + TreeItem2Root, + TreeItem2Content, + TreeItem2IconContainer, + TreeItem2GroupTransition, + TreeItem2Checkbox, + TreeItem2Label, + TreeItem2Props, + TreeItem2Slots, + TreeItem2SlotProps, + useTreeItem2, + unstable_useTreeItem2 as useAliasedTreeItem, + UseTreeItem2Parameters, + UseTreeItem2ReturnValue, + UseTreeItem2Status, + UseTreeItem2RootSlotOwnProps, + UseTreeItem2ContentSlotOwnProps, + UseTreeItem2LabelInputSlotOwnProps, + UseTreeItem2LabelSlotOwnProps, + UseTreeItem2CheckboxSlotOwnProps, + UseTreeItem2IconContainerSlotOwnProps, + UseTreeItem2GroupTransitionSlotOwnProps, + UseTreeItem2DragAndDropOverlaySlotOwnProps, + useTreeItem2Utils, + TreeItem2Provider, + TreeItem2ProviderProps, + TreeItem2Icon, + TreeItem2IconProps, + TreeItem2IconSlots, + TreeItem2IconSlotProps, + TreeItem2DragAndDropOverlay, + TreeItem2DragAndDropOverlayProps, + TreeItem2LabelInput, + TreeItem2LabelInputProps, +} from '@mui/x-tree-view'; + +// prettier-ignore +function App() { + useTreeItem2({}); + useAliasedTreeItem({}); + useTreeItem2Utils(); + + const treeItemProps: TreeItem2Props = {}; + const treeItemSlots: TreeItem2Slots = {}; + const treeItemSlotProps: TreeItem2SlotProps = {}; + + const params: UseTreeItem2Parameters = {}; + const returnValue: UseTreeItem2ReturnValue = {}; + const status: UseTreeItem2Status = {}; + const root: UseTreeItem2RootSlotOwnProps = {}; + const content: UseTreeItem2ContentSlotOwnProps = {}; + const labelInput: UseTreeItem2LabelInputSlotOwnProps = {}; + const label: UseTreeItem2LabelSlotOwnProps = {}; + const checkbox: UseTreeItem2CheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItem2IconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItem2GroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItem2DragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItem2ProviderProps = {}; + const treeItemIconProps: TreeItem2IconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItem2DragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItem2LabelInputProps = {}; + + return ( + + + + + + + + + + + + + + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-pro-root-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-pro-root-imports.spec.tsx new file mode 100644 index 0000000000000..46275e5644615 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-pro-root-imports.spec.tsx @@ -0,0 +1,83 @@ +/* eslint-disable no-restricted-imports */ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem2, + TreeItem2Root, + TreeItem2Content, + TreeItem2IconContainer, + TreeItem2GroupTransition, + TreeItem2Checkbox, + TreeItem2Label, + TreeItem2Props, + TreeItem2Slots, + TreeItem2SlotProps, + useTreeItem2, + unstable_useTreeItem2 as useAliasedTreeItem, + UseTreeItem2Parameters, + UseTreeItem2ReturnValue, + UseTreeItem2Status, + UseTreeItem2RootSlotOwnProps, + UseTreeItem2ContentSlotOwnProps, + UseTreeItem2LabelInputSlotOwnProps, + UseTreeItem2LabelSlotOwnProps, + UseTreeItem2CheckboxSlotOwnProps, + UseTreeItem2IconContainerSlotOwnProps, + UseTreeItem2GroupTransitionSlotOwnProps, + UseTreeItem2DragAndDropOverlaySlotOwnProps, + useTreeItem2Utils, + TreeItem2Provider, + TreeItem2ProviderProps, + TreeItem2Icon, + TreeItem2IconProps, + TreeItem2IconSlots, + TreeItem2IconSlotProps, + TreeItem2DragAndDropOverlay, + TreeItem2DragAndDropOverlayProps, + TreeItem2LabelInput, + TreeItem2LabelInputProps, +} from '@mui/x-tree-view-pro'; + +// prettier-ignore +function App() { + useTreeItem2({}); + useAliasedTreeItem({}); + useTreeItem2Utils(); + + const treeItemProps: TreeItem2Props = {}; + const treeItemSlots: TreeItem2Slots = {}; + const treeItemSlotProps: TreeItem2SlotProps = {}; + + const params: UseTreeItem2Parameters = {}; + const returnValue: UseTreeItem2ReturnValue = {}; + const status: UseTreeItem2Status = {}; + const root: UseTreeItem2RootSlotOwnProps = {}; + const content: UseTreeItem2ContentSlotOwnProps = {}; + const labelInput: UseTreeItem2LabelInputSlotOwnProps = {}; + const label: UseTreeItem2LabelSlotOwnProps = {}; + const checkbox: UseTreeItem2CheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItem2IconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItem2GroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItem2DragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItem2ProviderProps = {}; + const treeItemIconProps: TreeItem2IconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItem2DragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItem2LabelInputProps = {}; + + return ( + + + + + + + + + + + + + + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-nested-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-nested-imports.spec.tsx new file mode 100644 index 0000000000000..b3f4a9d62063d --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-nested-imports.spec.tsx @@ -0,0 +1,94 @@ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem, + TreeItemRoot, + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemCheckbox, + TreeItemLabel, + TreeItemProps, + TreeItemSlots, + TreeItemSlotProps, +} from '@mui/x-tree-view/TreeItem'; +import { + useTreeItem, + useTreeItem as useAliasedTreeItem, + UseTreeItemParameters, + UseTreeItemReturnValue, + UseTreeItemStatus, + UseTreeItemRootSlotOwnProps, + UseTreeItemContentSlotOwnProps, + UseTreeItemLabelInputSlotOwnProps, + UseTreeItemLabelSlotOwnProps, + UseTreeItemCheckboxSlotOwnProps, + UseTreeItemIconContainerSlotOwnProps, + UseTreeItemGroupTransitionSlotOwnProps, + UseTreeItemDragAndDropOverlaySlotOwnProps, +} from '@mui/x-tree-view/useTreeItem'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; +import { + TreeItemProvider, + TreeItemProviderProps, + FakeImportToForcePrettierNewLine, +} from '@mui/x-tree-view/TreeItemProvider'; +import { + TreeItemIcon, + TreeItemIconProps, + TreeItemIconSlots, + TreeItemIconSlotProps, +} from '@mui/x-tree-view/TreeItemIcon'; +import { + TreeItemDragAndDropOverlay, + TreeItemDragAndDropOverlayProps, +} from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { + TreeItemLabelInput, + TreeItemLabelInputProps, + FakeImport2ToForcePrettierNewLine, +} from '@mui/x-tree-view/TreeItemLabelInput'; + +// prettier-ignore +function App() { + useTreeItem({}); + useAliasedTreeItem({}); + useTreeItemUtils(); + + const treeItemProps: TreeItemProps = {}; + const treeItemSlots: TreeItemSlots = {}; + const treeItemSlotProps: TreeItemSlotProps = {}; + + const params: UseTreeItemParameters = {}; + const returnValue: UseTreeItemReturnValue = {}; + const status: UseTreeItemStatus = {}; + const root: UseTreeItemRootSlotOwnProps = {}; + const content: UseTreeItemContentSlotOwnProps = {}; + const labelInput: UseTreeItemLabelInputSlotOwnProps = {}; + const label: UseTreeItemLabelSlotOwnProps = {}; + const checkbox: UseTreeItemCheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItemIconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItemGroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItemDragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItemProviderProps = {}; + const treeItemIconProps: TreeItemIconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItemDragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItemLabelInputProps = {}; + + return ( + ( + + + + + + + + + + + + ) + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-root-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-root-imports.spec.tsx new file mode 100644 index 0000000000000..864d5e1a5c40b --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-root-imports.spec.tsx @@ -0,0 +1,83 @@ +/* eslint-disable no-restricted-imports */ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem, + TreeItemRoot, + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemCheckbox, + TreeItemLabel, + TreeItemProps, + TreeItemSlots, + TreeItemSlotProps, + useTreeItem, + useTreeItem as useAliasedTreeItem, + UseTreeItemParameters, + UseTreeItemReturnValue, + UseTreeItemStatus, + UseTreeItemRootSlotOwnProps, + UseTreeItemContentSlotOwnProps, + UseTreeItemLabelInputSlotOwnProps, + UseTreeItemLabelSlotOwnProps, + UseTreeItemCheckboxSlotOwnProps, + UseTreeItemIconContainerSlotOwnProps, + UseTreeItemGroupTransitionSlotOwnProps, + UseTreeItemDragAndDropOverlaySlotOwnProps, + useTreeItemUtils, + TreeItemProvider, + TreeItemProviderProps, + TreeItemIcon, + TreeItemIconProps, + TreeItemIconSlots, + TreeItemIconSlotProps, + TreeItemDragAndDropOverlay, + TreeItemDragAndDropOverlayProps, + TreeItemLabelInput, + TreeItemLabelInputProps, +} from '@mui/x-tree-view'; + +// prettier-ignore +function App() { + useTreeItem({}); + useAliasedTreeItem({}); + useTreeItemUtils(); + + const treeItemProps: TreeItemProps = {}; + const treeItemSlots: TreeItemSlots = {}; + const treeItemSlotProps: TreeItemSlotProps = {}; + + const params: UseTreeItemParameters = {}; + const returnValue: UseTreeItemReturnValue = {}; + const status: UseTreeItemStatus = {}; + const root: UseTreeItemRootSlotOwnProps = {}; + const content: UseTreeItemContentSlotOwnProps = {}; + const labelInput: UseTreeItemLabelInputSlotOwnProps = {}; + const label: UseTreeItemLabelSlotOwnProps = {}; + const checkbox: UseTreeItemCheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItemIconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItemGroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItemDragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItemProviderProps = {}; + const treeItemIconProps: TreeItemIconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItemDragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItemLabelInputProps = {}; + + return ( + ( + + + + + + + + + + + + ) + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-pro-root-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-pro-root-imports.spec.tsx new file mode 100644 index 0000000000000..a05a8d97ba11a --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-pro-root-imports.spec.tsx @@ -0,0 +1,83 @@ +/* eslint-disable no-restricted-imports */ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem, + TreeItemRoot, + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemCheckbox, + TreeItemLabel, + TreeItemProps, + TreeItemSlots, + TreeItemSlotProps, + useTreeItem, + useTreeItem as useAliasedTreeItem, + UseTreeItemParameters, + UseTreeItemReturnValue, + UseTreeItemStatus, + UseTreeItemRootSlotOwnProps, + UseTreeItemContentSlotOwnProps, + UseTreeItemLabelInputSlotOwnProps, + UseTreeItemLabelSlotOwnProps, + UseTreeItemCheckboxSlotOwnProps, + UseTreeItemIconContainerSlotOwnProps, + UseTreeItemGroupTransitionSlotOwnProps, + UseTreeItemDragAndDropOverlaySlotOwnProps, + useTreeItemUtils, + TreeItemProvider, + TreeItemProviderProps, + TreeItemIcon, + TreeItemIconProps, + TreeItemIconSlots, + TreeItemIconSlotProps, + TreeItemDragAndDropOverlay, + TreeItemDragAndDropOverlayProps, + TreeItemLabelInput, + TreeItemLabelInputProps, +} from '@mui/x-tree-view-pro'; + +// prettier-ignore +function App() { + useTreeItem({}); + useAliasedTreeItem({}); + useTreeItemUtils(); + + const treeItemProps: TreeItemProps = {}; + const treeItemSlots: TreeItemSlots = {}; + const treeItemSlotProps: TreeItemSlotProps = {}; + + const params: UseTreeItemParameters = {}; + const returnValue: UseTreeItemReturnValue = {}; + const status: UseTreeItemStatus = {}; + const root: UseTreeItemRootSlotOwnProps = {}; + const content: UseTreeItemContentSlotOwnProps = {}; + const labelInput: UseTreeItemLabelInputSlotOwnProps = {}; + const label: UseTreeItemLabelSlotOwnProps = {}; + const checkbox: UseTreeItemCheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItemIconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItemGroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItemDragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItemProviderProps = {}; + const treeItemIconProps: TreeItemIconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItemDragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItemLabelInputProps = {}; + + return ( + ( + + + + + + + + + + + + ) + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/index.ts b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/index.ts new file mode 100644 index 0000000000000..fcc1d0b10b128 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/index.ts @@ -0,0 +1,98 @@ +import type { JsCodeShiftAPI, JsCodeShiftFileInfo } from '../../../types'; +import { renameImports } from '../../../util/renameImports'; + +export default function transformer(file: JsCodeShiftFileInfo, api: JsCodeShiftAPI, options: any) { + const j = api.jscodeshift; + const root = j(file.source); + + const printOptions = options.printOptions || { + quote: 'single', + trailingComma: true, + wrapColumn: 40, + }; + + renameImports({ + j, + root, + packageNames: ['@mui/x-tree-view', '@mui/x-tree-view-pro'], + imports: [ + { + oldEndpoint: 'TreeItem2', + newEndpoint: 'TreeItem', + importsMapping: { + TreeItem2: 'TreeItem', + TreeItem2Root: 'TreeItemRoot', + TreeItem2Content: 'TreeItemContent', + TreeItem2IconContainer: 'TreeItemIconContainer', + TreeItem2GroupTransition: 'TreeItemGroupTransition', + TreeItem2Checkbox: 'TreeItemCheckbox', + TreeItem2Label: 'TreeItemLabel', + TreeItem2Props: 'TreeItemProps', + TreeItem2Slots: 'TreeItemSlots', + TreeItem2SlotProps: 'TreeItemSlotProps', + }, + }, + { + oldEndpoint: 'useTreeItem2', + newEndpoint: 'useTreeItem', + importsMapping: { + useTreeItem2: 'useTreeItem', + unstable_useTreeItem2: 'useTreeItem', + UseTreeItem2Parameters: 'UseTreeItemParameters', + UseTreeItem2ReturnValue: 'UseTreeItemReturnValue', + UseTreeItem2Status: 'UseTreeItemStatus', + UseTreeItem2RootSlotOwnProps: 'UseTreeItemRootSlotOwnProps', + UseTreeItem2ContentSlotOwnProps: 'UseTreeItemContentSlotOwnProps', + UseTreeItem2LabelInputSlotOwnProps: 'UseTreeItemLabelInputSlotOwnProps', + UseTreeItem2LabelSlotOwnProps: 'UseTreeItemLabelSlotOwnProps', + UseTreeItem2CheckboxSlotOwnProps: 'UseTreeItemCheckboxSlotOwnProps', + UseTreeItem2IconContainerSlotOwnProps: 'UseTreeItemIconContainerSlotOwnProps', + UseTreeItem2GroupTransitionSlotOwnProps: 'UseTreeItemGroupTransitionSlotOwnProps', + UseTreeItem2DragAndDropOverlaySlotOwnProps: 'UseTreeItemDragAndDropOverlaySlotOwnProps', + }, + }, + { + oldEndpoint: 'TreeItem2Provider', + newEndpoint: 'TreeItemProvider', + importsMapping: { + TreeItem2Provider: 'TreeItemProvider', + TreeItem2ProviderProps: 'TreeItemProviderProps', + }, + }, + { + oldEndpoint: 'TreeItem2Icon', + newEndpoint: 'TreeItemIcon', + importsMapping: { + TreeItem2Icon: 'TreeItemIcon', + TreeItem2IconProps: 'TreeItemIconProps', + TreeItem2IconSlots: 'TreeItemIconSlots', + TreeItem2IconSlotProps: 'TreeItemIconSlotProps', + }, + }, + { + oldEndpoint: 'TreeItem2DragAndDropOverlay', + newEndpoint: 'TreeItemDragAndDropOverlay', + importsMapping: { + TreeItem2DragAndDropOverlay: 'TreeItemDragAndDropOverlay', + TreeItem2DragAndDropOverlayProps: 'TreeItemDragAndDropOverlayProps', + }, + }, + { + oldEndpoint: 'TreeItem2LabelInput', + newEndpoint: 'TreeItemLabelInput', + importsMapping: { + TreeItem2LabelInput: 'TreeItemLabelInput', + TreeItem2LabelInputProps: 'TreeItemLabelInputProps', + }, + }, + { + oldEndpoint: 'hooks', + importsMapping: { + useTreeItem2Utils: 'useTreeItemUtils', + }, + }, + ], + }); + + return root.toSource(printOptions); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/rename-tree-item-2.test.ts b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/rename-tree-item-2.test.ts new file mode 100644 index 0000000000000..d91b9e1456b50 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/rename-tree-item-2.test.ts @@ -0,0 +1,44 @@ +import path from 'path'; +import { expect } from 'chai'; +import jscodeshift from 'jscodeshift'; +import transform from '.'; +import readFile from '../../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +const TEST_FILES = ['community-nested-imports', 'community-root-imports', 'pro-root-imports']; + +describe('v7.0.0/tree-view', () => { + describe('rename-tree-item-2', () => { + TEST_FILES.forEach((testFile) => { + const actualPath = `./actual-${testFile}.spec.tsx`; + const expectedPath = `./expected-${testFile}.spec.tsx`; + + describe(`${testFile.replace(/-/g, ' ')}`, () => { + it('transforms imports as needed', () => { + const actual = transform( + { source: read(actualPath) }, + { jscodeshift: jscodeshift.withParser('tsx') }, + {}, + ); + + const expected = read(expectedPath); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent', () => { + const actual = transform( + { source: read(expectedPath) }, + { jscodeshift: jscodeshift.withParser('tsx') }, + {}, + ); + + const expected = read(expectedPath); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); + }); + }); +}); diff --git a/packages/x-data-grid-generator/package.json b/packages/x-data-grid-generator/package.json index cf21b769479f4..45bb125c6f14b 100644 --- a/packages/x-data-grid-generator/package.json +++ b/packages/x-data-grid-generator/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-data-grid-generator", - "version": "7.20.0", + "version": "7.21.0", "description": "Generate fake data for demo purposes only.", "author": "MUI Team", "main": "src/index.ts", @@ -33,7 +33,7 @@ "directory": "packages/x-data-grid-generator" }, "dependencies": { - "@babel/runtime": "^7.25.7", + "@babel/runtime": "^7.26.0", "@mui/x-data-grid-premium": "workspace:*", "chance": "^1.1.12", "clsx": "^2.1.1", diff --git a/packages/x-data-grid-generator/src/columns/employees.columns.tsx b/packages/x-data-grid-generator/src/columns/employees.columns.tsx index 1f4ea311b299a..4f29ce843a4af 100644 --- a/packages/x-data-grid-generator/src/columns/employees.columns.tsx +++ b/packages/x-data-grid-generator/src/columns/employees.columns.tsx @@ -170,7 +170,7 @@ export const getEmployeeColumns = (): GridColDefGenerator[] => [ if (!value || typeof value !== 'number') { return value; } - return `${value.toLocaleString()}$`; + return `$${value.toLocaleString()}`; }, }, ]; diff --git a/packages/x-data-grid-generator/src/hooks/index.ts b/packages/x-data-grid-generator/src/hooks/index.ts index 84dd7368aea45..223d6170c94b7 100644 --- a/packages/x-data-grid-generator/src/hooks/index.ts +++ b/packages/x-data-grid-generator/src/hooks/index.ts @@ -1,6 +1,7 @@ export * from './useDemoData'; export * from './useBasicDemoData'; -export * from './useMovieData'; +export { useMovieData } from './useMovieData'; +export type { Movie } from './useMovieData'; export * from './useQuery'; export * from './useMockServer'; export { loadServerRows } from './serverUtils'; diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 0958931266435..80651b133af5e 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -10,7 +10,6 @@ import { GridValidRowModel, } from '@mui/x-data-grid-pro'; import { GridStateColDef } from '@mui/x-data-grid-pro/internals'; -import { UseDemoDataOptions } from './useDemoData'; import { randomInt } from '../services/random-generator'; export interface FakeServerResponse { @@ -53,14 +52,9 @@ export interface ServerSideQueryOptions { sortModel?: GridSortModel; firstRowToRender?: number; lastRowToRender?: number; + groupFields?: string[]; } -export const DEFAULT_DATASET_OPTIONS: UseDemoDataOptions = { - dataSet: 'Commodity', - rowLength: 100, - maxColumns: 6, -}; - declare const DISABLE_CHANCE_RANDOM: any; export const disableDelay = typeof DISABLE_CHANCE_RANDOM !== 'undefined' && DISABLE_CHANCE_RANDOM; @@ -323,7 +317,7 @@ export const loadServerRows = ( }); }; -interface ProcessTreeDataRowsResponse { +interface NestedDataRowsResponse { rows: GridRowModel[]; rootRowCount: number; } @@ -333,6 +327,7 @@ const findTreeDataRowChildren = ( parentPath: string[], pathKey: string = 'path', depth: number = 1, // the depth of the children to find relative to parentDepth, `-1` to find all + rowQualifier?: (row: GridRowModel) => boolean, ) => { const parentDepth = parentPath.length; const children = []; @@ -346,7 +341,9 @@ const findTreeDataRowChildren = ( ((depth < 0 && rowPath.length > parentDepth) || rowPath.length === parentDepth + depth) && parentPath.every((value, index) => value === rowPath[index]) ) { - children.push(row); + if (!rowQualifier || rowQualifier(row)) { + children.push(row); + } } } return children; @@ -427,14 +424,14 @@ const getTreeDataFilteredRows: GetTreeDataFilteredRows = ( }; /** - * Simulates server data loading + * Simulates server data for tree-data feature */ export const processTreeDataRows = ( rows: GridRowModel[], queryOptions: ServerSideQueryOptions, serverOptions: ServerOptions, columnsWithDefaultColDef: GridColDef[], -): Promise => { +): Promise => { const { minDelay = 100, maxDelay = 300 } = serverOptions; const pathKey = 'path'; // TODO: Support filtering and cursor based pagination @@ -490,3 +487,124 @@ export const processTreeDataRows = ( }, delay); // simulate network latency }); }; + +/** + * Simulates server data for row grouping feature + */ +export const processRowGroupingRows = ( + rows: GridValidRowModel[], + queryOptions: ServerSideQueryOptions, + serverOptions: ServerOptions, + columnsWithDefaultColDef: GridColDef[], +): Promise => { + const { minDelay = 100, maxDelay = 300 } = serverOptions; + const pathKey = 'path'; + + if (maxDelay < minDelay) { + throw new Error('serverOptions.minDelay is larger than serverOptions.maxDelay '); + } + + if (queryOptions.groupKeys == null) { + throw new Error('serverOptions.groupKeys must be defined to compute row grouping data'); + } + + if (queryOptions.groupFields == null) { + throw new Error('serverOptions.groupFields must be defined to compute row grouping data'); + } + + const delay = randomInt(minDelay, maxDelay); + + const pathsToAutogenerate = new Set(); + let rowsWithPaths = rows; + const rowsWithMissingGroups: GridValidRowModel[] = []; + + // add paths and generate parent rows based on `groupFields` + const groupFields = queryOptions.groupFields; + if (groupFields.length > 0) { + rowsWithPaths = rows.reduce((acc, row) => { + const partialPath = groupFields.map((field) => String(row[field])); + for (let index = 0; index < partialPath.length; index += 1) { + const value = partialPath[index]; + if (value === undefined) { + if (index === 0) { + rowsWithMissingGroups.push({ ...row, group: false }); + } + return acc; + } + const parentPath = partialPath.slice(0, index + 1); + const strigifiedPath = parentPath.join(','); + if (!pathsToAutogenerate.has(strigifiedPath)) { + pathsToAutogenerate.add(strigifiedPath); + } + } + acc.push({ ...row, path: [...partialPath, ''] }); + return acc; + }, []); + } else { + rowsWithPaths = rows.map((row) => ({ ...row, path: [''] })); + } + + const autogeneratedRows = Array.from(pathsToAutogenerate).map((path) => { + const pathArray = path.split(','); + return { + id: `auto-generated-parent-${pathArray.join('-')}`, + path: pathArray.slice(0, pathArray.length), + group: pathArray.slice(-1)[0], + }; + }); + + // apply plain filtering + const filteredRows = getTreeDataFilteredRows( + [...autogeneratedRows, ...rowsWithPaths, ...rowsWithMissingGroups], + queryOptions.filterModel, + columnsWithDefaultColDef, + ) as GridValidRowModel[]; + + // get root row count + const rootRows = findTreeDataRowChildren(filteredRows, []); + const rootRowCount = rootRows.length; + + let filteredRowsWithMissingGroups: GridValidRowModel[] = []; + let childRows = rootRows; + if (queryOptions.groupKeys.length === 0) { + filteredRowsWithMissingGroups = filteredRows.filter(({ group }) => group === false); + } else { + childRows = findTreeDataRowChildren(filteredRows, queryOptions.groupKeys); + } + + let childRowsWithDescendantCounts = childRows.map((row) => { + const descendants = findTreeDataRowChildren( + filteredRows, + row[pathKey], + pathKey, + -1, + ({ id }) => typeof id !== 'string' || !id.startsWith('auto-generated-parent-'), + ); + const descendantCount = descendants.length; + return { ...row, descendantCount } as GridRowModel; + }); + + if (queryOptions.sortModel) { + const rowComparator = getRowComparator(queryOptions.sortModel, columnsWithDefaultColDef); + const sortedMissingGroups = [...filteredRowsWithMissingGroups].sort(rowComparator); + const sortedChildRows = [...childRowsWithDescendantCounts].sort(rowComparator); + childRowsWithDescendantCounts = [...sortedMissingGroups, ...sortedChildRows]; + } + + if (queryOptions.paginationModel && queryOptions.groupKeys.length === 0) { + // Only paginate root rows, grid should refetch root rows when `paginationModel` updates + const { pageSize, page } = queryOptions.paginationModel; + if (pageSize < childRowsWithDescendantCounts.length) { + childRowsWithDescendantCounts = childRowsWithDescendantCounts.slice( + page * pageSize, + (page + 1) * pageSize, + ); + } + } + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ rows: childRowsWithDescendantCounts, rootRowCount }); + }, delay); // simulate network latency + }); +}; diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index 83c43fd30c051..e9c168f0ca08a 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -9,23 +9,24 @@ import { GridInitialState, GridColumnVisibilityModel, } from '@mui/x-data-grid-pro'; -import { - UseDemoDataOptions, - getColumnsFromOptions, - extrapolateSeed, - deepFreeze, -} from './useDemoData'; +import { extrapolateSeed, deepFreeze } from './useDemoData'; +import { getCommodityColumns } from '../columns/commodities.columns'; +import { getEmployeeColumns } from '../columns/employees.columns'; import { GridColDefGenerator } from '../services/gridColDefGenerator'; import { getRealGridData, GridDemoData } from '../services/real-data-service'; -import { addTreeDataOptionsToDemoData } from '../services/tree-data-generator'; +import { + addTreeDataOptionsToDemoData, + AddPathToDemoDataOptions, +} from '../services/tree-data-generator'; import { loadServerRows, processTreeDataRows, - DEFAULT_DATASET_OPTIONS, + processRowGroupingRows, DEFAULT_SERVER_OPTIONS, } from './serverUtils'; import type { ServerOptions } from './serverUtils'; import { randomInt } from '../services'; +import { getMovieRows, getMovieColumns } from './useMovieData'; const dataCache = new LRUCache({ max: 10, @@ -43,6 +44,66 @@ type UseMockServerResponse = { loadNewData: () => void; }; +type DataSet = 'Commodity' | 'Employee' | 'Movies'; + +interface UseMockServerOptions { + dataSet: DataSet; + /** + * Has no effect when DataSet='Movies' + */ + rowLength: number; + maxColumns?: number; + visibleFields?: string[]; + editable?: boolean; + treeData?: AddPathToDemoDataOptions; + rowGrouping?: boolean; +} + +interface GridMockServerData { + rows: GridRowModel[]; + columns: GridColDefGenerator[] | GridColDef[]; + initialState?: GridInitialState; +} + +interface ColumnsOptions + extends Pick {} + +const GET_DEFAULT_DATASET_OPTIONS: (isRowGrouping: boolean) => UseMockServerOptions = ( + isRowGrouping, +) => ({ + dataSet: isRowGrouping ? 'Movies' : 'Commodity', + rowLength: isRowGrouping ? getMovieRows().length : 100, + maxColumns: 6, +}); + +const getColumnsFromOptions = (options: ColumnsOptions): GridColDefGenerator[] | GridColDef[] => { + let columns; + + switch (options.dataSet) { + case 'Commodity': + columns = getCommodityColumns(options.editable); + break; + case 'Employee': + columns = getEmployeeColumns(); + break; + case 'Movies': + columns = getMovieColumns(); + break; + default: + throw new Error('Unknown dataset'); + } + + if (options.visibleFields) { + columns = columns.map((col) => + options.visibleFields?.includes(col.field) ? col : { ...col, hide: true }, + ); + } + if (options.maxColumns) { + columns = columns.slice(0, options.maxColumns); + } + return columns; +}; + function decodeParams(url: string): GridGetRowsParams { const params = new URL(url).searchParams; const decodedParams = {} as any; @@ -76,12 +137,18 @@ const getInitialState = (columns: GridColDefGenerator[], groupingField?: string) const defaultColDef = getGridDefaultColumnTypes(); +function sendEmptyResponse() { + return new Promise((resolve) => { + resolve({ rows: [], rowCount: 0 }); + }); +} + export const useMockServer = ( - dataSetOptions?: Partial, + dataSetOptions?: Partial, serverOptions?: ServerOptions & { verbose?: boolean }, shouldRequestsFail?: boolean, ): UseMockServerResponse => { - const [data, setData] = React.useState(); + const [data, setData] = React.useState(); const [index, setIndex] = React.useState(0); const shouldRequestsFailRef = React.useRef(shouldRequestsFail ?? false); @@ -91,7 +158,11 @@ export const useMockServer = ( } }, [shouldRequestsFail]); - const options = { ...DEFAULT_DATASET_OPTIONS, ...dataSetOptions }; + const isRowGrouping = dataSetOptions?.rowGrouping ?? false; + + const options = { ...GET_DEFAULT_DATASET_OPTIONS(isRowGrouping), ...dataSetOptions }; + + const isTreeData = options.treeData?.groupingField != null; const columns = React.useMemo(() => { return getColumnsFromOptions({ @@ -116,8 +187,6 @@ export const useMockServer = ( [columns], ); - const isTreeData = options.treeData?.groupingField != null; - const getGroupKey = React.useMemo(() => { if (isTreeData) { return (row: GridRowModel): string => row[options.treeData!.groupingField!]; @@ -144,6 +213,13 @@ export const useMockServer = ( return undefined; } + if (options.dataSet === 'Movies') { + const rowsData = { rows: getMovieRows(), columns }; + setData(rowsData); + dataCache.set(cacheKey, rowsData); + return undefined; + } + let active = true; (async () => { @@ -193,10 +269,8 @@ export const useMockServer = ( const fetchRows = React.useCallback( async (requestUrl: string): Promise => { - if (!data || !requestUrl) { - return new Promise((resolve) => { - resolve({ rows: [], rowCount: 0 }); - }); + if (!requestUrl || !data?.rows) { + return sendEmptyResponse(); } const params = decodeParams(requestUrl); const verbose = serverOptions?.verbose ?? true; @@ -224,9 +298,21 @@ export const useMockServer = ( }); } - if (isTreeData /* || TODO: `isRowGrouping` */) { + if (isTreeData) { const { rows, rootRowCount } = await processTreeDataRows( - data.rows, + data?.rows ?? [], + params, + serverOptionsWithDefault, + columnsWithDefaultColDef, + ); + + getRowsResponse = { + rows: rows.slice().map((row) => ({ ...row, path: undefined })), + rowCount: rootRowCount, + }; + } else if (isRowGrouping) { + const { rows, rootRowCount } = await processRowGroupingRows( + data?.rows ?? [], params, serverOptionsWithDefault, columnsWithDefaultColDef, @@ -237,9 +323,8 @@ export const useMockServer = ( rowCount: rootRowCount, }; } else { - // plain data const { returnedRows, nextCursor, totalRowCount } = await loadServerRows( - data.rows, + data?.rows ?? [], { ...params, ...params.paginationModel }, serverOptionsWithDefault, columnsWithDefaultColDef, @@ -262,12 +347,13 @@ export const useMockServer = ( serverOptions?.useCursorPagination, isTreeData, columnsWithDefaultColDef, + isRowGrouping, ], ); return { columns: columnsWithDefaultColDef, - initialState, + initialState: options.dataSet === 'Movies' ? {} : initialState, getGroupKey, getChildrenCount, fetchRows, diff --git a/packages/x-data-grid-generator/src/hooks/useMovieData.ts b/packages/x-data-grid-generator/src/hooks/useMovieData.ts index 7820a7471fde2..b9b1967168392 100644 --- a/packages/x-data-grid-generator/src/hooks/useMovieData.ts +++ b/packages/x-data-grid-generator/src/hooks/useMovieData.ts @@ -546,6 +546,9 @@ const ROWS: GridRowModel[] = [ }, ]; +export const getMovieColumns = (): GridColDef[] => COLUMNS; +export const getMovieRows = (): GridRowModel[] => ROWS; + export const useMovieData = () => { return { rows: ROWS, diff --git a/packages/x-data-grid-generator/src/hooks/useQuery.ts b/packages/x-data-grid-generator/src/hooks/useQuery.ts index 62ae140bcdcd7..5387a7e1f485b 100644 --- a/packages/x-data-grid-generator/src/hooks/useQuery.ts +++ b/packages/x-data-grid-generator/src/hooks/useQuery.ts @@ -7,9 +7,15 @@ import { getColumnsFromOptions, getInitialState, } from './useDemoData'; -import { DEFAULT_DATASET_OPTIONS, DEFAULT_SERVER_OPTIONS, loadServerRows } from './serverUtils'; +import { DEFAULT_SERVER_OPTIONS, loadServerRows } from './serverUtils'; import type { ServerOptions, QueryOptions, PageInfo } from './serverUtils'; +const DEFAULT_DATASET_OPTIONS: UseDemoDataOptions = { + dataSet: 'Commodity', + rowLength: 100, + maxColumns: 6, +}; + export const createFakeServer = ( dataSetOptions?: Partial, serverOptions?: ServerOptions, diff --git a/packages/x-data-grid-premium/package.json b/packages/x-data-grid-premium/package.json index 2831040fbfb47..a4b7281976271 100644 --- a/packages/x-data-grid-premium/package.json +++ b/packages/x-data-grid-premium/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-data-grid-premium", - "version": "7.20.0", + "version": "7.21.0", "description": "The Premium plan edition of the Data Grid Components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -43,7 +43,7 @@ "directory": "packages/x-data-grid-premium" }, "dependencies": { - "@babel/runtime": "^7.25.7", + "@babel/runtime": "^7.26.0", "@mui/utils": "^5.16.6 || ^6.0.0", "@mui/x-data-grid": "workspace:*", "@mui/x-data-grid-pro": "workspace:*", @@ -72,7 +72,7 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.15", + "@mui/internal-test-utils": "^1.0.19", "@mui/material": "^5.16.7", "@mui/system": "^5.16.7", "@types/prop-types": "^15.7.13", diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 89e77912b74c9..0f05ca94e442d 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -967,6 +967,7 @@ DataGridPremiumRaw.propTypes = { * Setting it to a lower value could be useful when using dynamic row height, * but might reduce performance when displaying a large number of rows. * @default 166 + * @deprecated */ rowPositionsDebounceMs: PropTypes.number, /** @@ -1105,6 +1106,21 @@ DataGridPremiumRaw.propTypes = { get: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), + /** + * Definition of the column rendered when the `unstable_listView` prop is enabled. + */ + unstable_listColumn: PropTypes.shape({ + align: PropTypes.oneOf(['center', 'left', 'right']), + cellClassName: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + display: PropTypes.oneOf(['flex', 'text']), + field: PropTypes.string.isRequired, + renderCell: PropTypes.func, + }), + /** + * If `true`, displays the data in a list view. + * Use in combination with `unstable_listColumn`. + */ + unstable_listView: PropTypes.bool, unstable_onDataSourceError: PropTypes.func, /** * If `true`, the Data Grid will auto span the cells over the rows having the same value. diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 12899dd06ce2f..46c76914dda78 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -70,6 +70,8 @@ import { dataSourceStateInitializer, useGridRowSpanning, rowSpanningStateInitializer, + useGridListView, + listViewStateInitializer, } from '@mui/x-data-grid-pro/internals'; import { GridApiPremium, GridPrivateApiPremium } from '../models/gridApiPremium'; import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps'; @@ -84,6 +86,7 @@ import { rowGroupingStateInitializer, } from '../hooks/features/rowGrouping/useGridRowGrouping'; import { useGridRowGroupingPreProcessors } from '../hooks/features/rowGrouping/useGridRowGroupingPreProcessors'; +import { useGridDataSourceRowGroupingPreProcessors } from '../hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors'; import { useGridExcelExport } from '../hooks/features/export/useGridExcelExport'; import { cellSelectionStateInitializer, @@ -103,6 +106,7 @@ export const useDataGridPremiumComponent = ( useGridRowSelectionPreProcessors(apiRef, props); useGridRowReorderPreProcessors(apiRef, props); useGridRowGroupingPreProcessors(apiRef, props); + useGridDataSourceRowGroupingPreProcessors(apiRef, props); useGridTreeDataPreProcessors(apiRef, props); useGridDataSourceTreeDataPreProcessors(apiRef, props); useGridLazyLoaderPreProcessors(apiRef, props); @@ -143,6 +147,7 @@ export const useDataGridPremiumComponent = ( useGridInitializeState(columnGroupsStateInitializer, apiRef, props); useGridInitializeState(virtualizationStateInitializer, apiRef, props); useGridInitializeState(dataSourceStateInitializer, apiRef, props); + useGridInitializeState(listViewStateInitializer, apiRef, props); useGridRowGrouping(apiRef, props); useGridHeaderFiltering(apiRef, props); @@ -156,7 +161,7 @@ export const useDataGridPremiumComponent = ( useGridColumns(apiRef, props); useGridRows(apiRef, props); useGridRowSpanning(apiRef, props); - useGridParamsApi(apiRef); + useGridParamsApi(apiRef, props); useGridDetailPanel(apiRef, props); useGridColumnSpanning(apiRef); useGridColumnGrouping(apiRef, props); @@ -185,6 +190,7 @@ export const useDataGridPremiumComponent = ( useGridStatePersistence(apiRef); useGridDataSource(apiRef, props); useGridVirtualization(apiRef, props); + useGridListView(apiRef, props); return apiRef; }; diff --git a/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx b/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx index f2976b0119135..27fd029bb344d 100644 --- a/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx +++ b/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx @@ -1,13 +1,11 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { GridColumnMenuItemProps, useGridSelector } from '@mui/x-data-grid-pro'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import FormControl from '@mui/material/FormControl'; import InputLabel from '@mui/material/InputLabel'; import { unstable_useId as useId } from '@mui/utils'; -import Select, { SelectChangeEvent } from '@mui/material/Select'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { @@ -53,8 +51,8 @@ function GridColumnMenuAggregationItem(props: GridColumnMenuItemProps) { return ''; }, [rootProps.aggregationFunctions, aggregationModel, colDef]); - const handleAggregationItemChange = (event: SelectChangeEvent) => { - const newAggregationItem = event.target?.value || undefined; + const handleAggregationItemChange = (event: Event) => { + const newAggregationItem = (event.target as HTMLSelectElement | null)?.value || undefined; const currentModel = gridAggregationModelSelector(apiRef); const { [colDef.field]: columnItem, ...otherColumnItems } = currentModel; const newModel: GridAggregationModel = @@ -69,26 +67,26 @@ function GridColumnMenuAggregationItem(props: GridColumnMenuItemProps) { const label = apiRef.current.getLocaleText('aggregationMenuItemHeader'); return ( - + {label} - + - + ); } diff --git a/packages/x-data-grid-premium/src/components/GridColumnMenuRowGroupItem.tsx b/packages/x-data-grid-premium/src/components/GridColumnMenuRowGroupItem.tsx index 0cc9895713124..7f661095f957f 100644 --- a/packages/x-data-grid-premium/src/components/GridColumnMenuRowGroupItem.tsx +++ b/packages/x-data-grid-premium/src/components/GridColumnMenuRowGroupItem.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { @@ -32,12 +31,16 @@ export function GridColumnMenuRowGroupItem(props: GridColumnMenuItemProps) { const groupedColumn = columnsLookup[field]; const name = groupedColumn.headerName ?? field; return ( - + {apiRef.current.getLocaleText('unGroupColumn')(name)} - + ); }; diff --git a/packages/x-data-grid-premium/src/components/GridColumnMenuRowUngroupItem.tsx b/packages/x-data-grid-premium/src/components/GridColumnMenuRowUngroupItem.tsx index 48a8e21f85ff3..9f100470d0b6d 100644 --- a/packages/x-data-grid-premium/src/components/GridColumnMenuRowUngroupItem.tsx +++ b/packages/x-data-grid-premium/src/components/GridColumnMenuRowUngroupItem.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { @@ -36,21 +35,21 @@ export function GridColumnMenuRowUngroupItem(props: GridColumnMenuItemProps) { if (rowGroupingModel.includes(colDef.field)) { return ( - + {apiRef.current.getLocaleText('unGroupColumn')(name)} - + ); } return ( - + {apiRef.current.getLocaleText('groupColumn')(name)} - + ); } diff --git a/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx new file mode 100644 index 0000000000000..0f7f159ee84c8 --- /dev/null +++ b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx @@ -0,0 +1,147 @@ +import * as React from 'react'; +import { unstable_composeClasses as composeClasses } from '@mui/utils'; +import Box from '@mui/material/Box'; +import CircularProgress from '@mui/material/CircularProgress'; +import { useGridPrivateApiContext } from '@mui/x-data-grid-pro/internals'; +import { + useGridSelector, + getDataGridUtilityClass, + GridRenderCellParams, + GridGroupNode, +} from '@mui/x-data-grid-pro'; +import { useGridApiContext } from '../hooks/utils/useGridApiContext'; +import { useGridRootProps } from '../hooks/utils/useGridRootProps'; +import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps'; +import { GridPrivateApiPremium } from '../models/gridApiPremium'; +import { GridStatePremium } from '../models/gridStatePremium'; + +type OwnerState = DataGridPremiumProcessedProps; + +const useUtilityClasses = (ownerState: OwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['groupingCriteriaCell'], + toggle: ['groupingCriteriaCellToggle'], + loadingContainer: ['groupingCriteriaCellLoadingContainer'], + }; + + return composeClasses(slots, getDataGridUtilityClass, classes); +}; + +interface GridGroupingCriteriaCellProps extends GridRenderCellParams { + hideDescendantCount?: boolean; +} + +interface GridGroupingCriteriaCellIconProps + extends Pick { + descendantCount: number; +} + +function GridGroupingCriteriaCellIcon(props: GridGroupingCriteriaCellIconProps) { + const apiRef = useGridPrivateApiContext() as React.MutableRefObject; + const rootProps = useGridRootProps(); + const classes = useUtilityClasses(rootProps); + const { rowNode, id, field, descendantCount } = props; + + const loadingSelector = (state: GridStatePremium) => state.dataSource.loading[id] ?? false; + const errorSelector = (state: GridStatePremium) => state.dataSource.errors[id]; + const isDataLoading = useGridSelector(apiRef, loadingSelector); + const error = useGridSelector(apiRef, errorSelector); + + const handleClick = (event: React.MouseEvent) => { + if (!rowNode.childrenExpanded) { + // always fetch/get from cache the children when the node is expanded + apiRef.current.unstable_dataSource.fetchRows(id); + } else { + apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); + } + apiRef.current.setCellFocus(id, field); + event.stopPropagation(); + }; + + const Icon = rowNode.childrenExpanded + ? rootProps.slots.groupingCriteriaCollapseIcon + : rootProps.slots.groupingCriteriaExpandIcon; + + if (isDataLoading) { + return ( +
+ +
+ ); + } + + return descendantCount > 0 ? ( + + + + + + + + ) : null; +} + +export function GridDataSourceGroupingCriteriaCell(props: GridGroupingCriteriaCellProps) { + const { id, field, rowNode, hideDescendantCount, formattedValue } = props; + + const rootProps = useGridRootProps(); + const apiRef = useGridApiContext(); + const rowSelector = (state: GridStatePremium) => state.rows.dataRowIdToModelLookup[id]; + const row = useGridSelector(apiRef, rowSelector); + const classes = useUtilityClasses(rootProps); + + let descendantCount = 0; + if (row) { + descendantCount = Math.max(rootProps.unstable_dataSource?.getChildrenCount?.(row) ?? 0, 0); + } + + let cellContent: React.ReactNode; + + const colDef = apiRef.current.getColumn(rowNode.groupingField!); + if (typeof colDef?.renderCell === 'function') { + cellContent = colDef.renderCell(props); + } else if (typeof formattedValue !== 'undefined') { + cellContent = {formattedValue}; + } else { + cellContent = {rowNode.groupingKey}; + } + + return ( + + `calc(var(--DataGrid-cellOffsetMultiplier) * ${theme.spacing(rowNode.depth)})`, + }} + > +
+ +
+ {cellContent} + {!hideDescendantCount && descendantCount > 0 ? ( + ({descendantCount}) + ) : null} +
+ ); +} diff --git a/packages/x-data-grid-premium/src/components/GridExcelExportMenuItem.tsx b/packages/x-data-grid-premium/src/components/GridExcelExportMenuItem.tsx index 37e265b9233be..a73c4619b3bff 100644 --- a/packages/x-data-grid-premium/src/components/GridExcelExportMenuItem.tsx +++ b/packages/x-data-grid-premium/src/components/GridExcelExportMenuItem.tsx @@ -1,18 +1,19 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import { GridExportMenuItemProps } from '@mui/x-data-grid-pro'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; +import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { GridExcelExportOptions } from '../hooks/features/export'; export type GridExcelExportMenuItemProps = GridExportMenuItemProps; function GridExcelExportMenuItem(props: GridExcelExportMenuItemProps) { const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); const { hideMenu, options, ...other } = props; return ( - { apiRef.current.exportDataAsExcel(options); hideMenu?.(); @@ -20,7 +21,7 @@ function GridExcelExportMenuItem(props: GridExcelExportMenuItemProps) { {...other} > {apiRef.current.getLocaleText('toolbarExportExcel')} - + ); } diff --git a/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts b/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts index 634fbdad61b93..928b1cd11bfba 100644 --- a/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts +++ b/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts @@ -1,5 +1,6 @@ import * as React from 'react'; -import { ownerDocument, useEventCallback } from '@mui/material/utils'; +import ownerDocument from '@mui/utils/ownerDocument'; +import useEventCallback from '@mui/utils/useEventCallback'; import { GridPipeProcessor, GridStateInitializer, diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx index caa1a42af8f5a..194c62efa5215 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx @@ -12,10 +12,12 @@ import { GridColumnRawLookup, isSingleSelectColDef } from '@mui/x-data-grid-pro/ import { GridApiPremium } from '../../../models/gridApiPremium'; import { GridGroupingColumnFooterCell } from '../../../components/GridGroupingColumnFooterCell'; import { GridGroupingCriteriaCell } from '../../../components/GridGroupingCriteriaCell'; +import { GridDataSourceGroupingCriteriaCell } from '../../../components/GridDataSourceGroupingCriteriaCell'; import { GridGroupingColumnLeafCell } from '../../../components/GridGroupingColumnLeafCell'; import { getRowGroupingFieldFromGroupingCriteria, GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, + RowGroupingStrategy, } from './gridRowGroupingUtils'; import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; @@ -25,11 +27,24 @@ const GROUPING_COL_DEF_DEFAULT_PROPERTIES: Omit = { disableReorder: true, }; -const GROUPING_COL_DEF_FORCED_PROPERTIES: Pick = { +const GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT: Pick< + GridColDef, + 'type' | 'editable' | 'groupable' +> = { editable: false, groupable: false, }; +const GROUPING_COL_DEF_FORCED_PROPERTIES_DATA_SOURCE: Pick< + GridColDef, + 'type' | 'editable' | 'groupable' | 'filterable' | 'sortable' | 'aggregable' +> = { + ...GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT, + // TODO: Support these features on the grouping column(s) + filterable: false, + sortable: false, +}; + /** * When sorting two cells with different grouping criteria, we consider that the cell with the grouping criteria coming first in the model should be displayed below. * This can occur when some rows don't have all the fields. In which case we want the rows with the missing field to be displayed above. @@ -122,6 +137,7 @@ interface CreateGroupingColDefMonoCriteriaParams { * This value comes `prop.groupingColDef`. */ colDefOverride: GridGroupingColDefOverride | null | undefined; + strategy?: RowGroupingStrategy; } /** @@ -132,11 +148,17 @@ export const createGroupingColDefForOneGroupingCriteria = ({ groupedByColDef, groupingCriteria, colDefOverride, + strategy = RowGroupingStrategy.Default, }: CreateGroupingColDefMonoCriteriaParams): GridColDef => { const { leafField, mainGroupingCriteria, hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {}; const leafColDef = leafField ? columnsLookup[leafField] : null; + const CriteriaCell = + strategy === RowGroupingStrategy.Default + ? GridGroupingCriteriaCell + : GridDataSourceGroupingCriteriaCell; + // The properties that do not depend on the presence of a `leafColDef` and that can be overridden by `colDefOverride` const commonProperties: Partial = { width: Math.max( @@ -170,7 +192,7 @@ export const createGroupingColDefForOneGroupingCriteria = ({ // Render current grouping criteria groups if (params.rowNode.groupingField === groupingCriteria) { return ( - )} hideDescendantCount={hideDescendantCount} /> @@ -222,7 +244,7 @@ export const createGroupingColDefForOneGroupingCriteria = ({ // The properties that can't be overridden with `colDefOverride` const forcedProperties: Pick = { field: getRowGroupingFieldFromGroupingCriteria(groupingCriteria), - ...GROUPING_COL_DEF_FORCED_PROPERTIES, + ...GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT, }; return { @@ -246,6 +268,7 @@ interface CreateGroupingColDefSeveralCriteriaParams { * This value comes `prop.groupingColDef`. */ colDefOverride: GridGroupingColDefOverride | null | undefined; + strategy?: RowGroupingStrategy; } /** @@ -256,11 +279,17 @@ export const createGroupingColDefForAllGroupingCriteria = ({ columnsLookup, rowGroupingModel, colDefOverride, + strategy = RowGroupingStrategy.Default, }: CreateGroupingColDefSeveralCriteriaParams): GridColDef => { const { leafField, mainGroupingCriteria, hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {}; const leafColDef = leafField ? columnsLookup[leafField] : null; + const CriteriaCell = + strategy === RowGroupingStrategy.Default + ? GridGroupingCriteriaCell + : GridDataSourceGroupingCriteriaCell; + // The properties that do not depend on the presence of a `leafColDef` and that can be overridden by `colDefOverride` const commonProperties: Partial = { headerName: apiRef.current.getLocaleText('groupingColumnHeaderName'), @@ -296,7 +325,7 @@ export const createGroupingColDefForAllGroupingCriteria = ({ // Render the groups return ( - )} hideDescendantCount={hideDescendantCount} /> @@ -344,7 +373,9 @@ export const createGroupingColDefForAllGroupingCriteria = ({ // The properties that can't be overridden with `colDefOverride` const forcedProperties: Pick = { field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, - ...GROUPING_COL_DEF_FORCED_PROPERTIES, + ...(strategy === RowGroupingStrategy.Default + ? GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT + : GROUPING_COL_DEF_FORCED_PROPERTIES_DATA_SOURCE), }; return { diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts index 1cdec6a7c6631..0310fb7b338d6 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts @@ -9,12 +9,16 @@ import { GridRowModel, GridColDef, GridKeyValue, + GridDataSource, } from '@mui/x-data-grid-pro'; import { passFilterLogic, GridAggregatedFilterItemApplier, GridAggregatedFilterItemApplierResult, GridColumnRawLookup, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, + getRowGroupingCriteriaFromGroupingField, + isGroupingColumn, } from '@mui/x-data-grid-pro/internals'; import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; import { @@ -26,9 +30,16 @@ import { GridStatePremium } from '../../../models/gridStatePremium'; import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; -export const GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD = '__row_group_by_columns_group__'; +export { + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, + getRowGroupingCriteriaFromGroupingField, + isGroupingColumn, +}; -export const ROW_GROUPING_STRATEGY = 'grouping-columns'; +export enum RowGroupingStrategy { + Default = 'grouping-columns', + DataSource = 'grouping-columns-data-source', +} export const getRowGroupingFieldFromGroupingCriteria = (groupingCriteria: string | null) => { if (groupingCriteria === null) { @@ -38,20 +49,6 @@ export const getRowGroupingFieldFromGroupingCriteria = (groupingCriteria: string return `__row_group_by_columns_group_${groupingCriteria}__`; }; -export const getRowGroupingCriteriaFromGroupingField = (groupingColDefField: string) => { - const match = groupingColDefField.match(/^__row_group_by_columns_group_(.*)__$/); - - if (!match) { - return null; - } - - return match[1]; -}; - -export const isGroupingColumn = (field: string) => - field === GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD || - getRowGroupingCriteriaFromGroupingField(field) !== null; - interface FilterRowTreeFromTreeDataParams { rowTree: GridRowTreeConfig; isRowMatchingFilters: GridAggregatedFilterItemApplier | null; @@ -178,10 +175,11 @@ export const filterRowTreeFromGroupingColumns = ( export const getColDefOverrides = ( groupingColDefProp: DataGridPremiumProcessedProps['groupingColDef'], fields: string[], + strategy?: RowGroupingStrategy, ) => { if (typeof groupingColDefProp === 'function') { return groupingColDefProp({ - groupingName: ROW_GROUPING_STRATEGY, + groupingName: strategy ?? RowGroupingStrategy.Default, fields, }); } @@ -199,6 +197,7 @@ export const mergeStateWithRowGroupingModel = export const setStrategyAvailability = ( privateApiRef: React.MutableRefObject, disableRowGrouping: boolean, + dataSource?: GridDataSource, ) => { let isAvailable: () => boolean; if (disableRowGrouping) { @@ -210,7 +209,9 @@ export const setStrategyAvailability = ( }; } - privateApiRef.current.setStrategyAvailability('rowTree', ROW_GROUPING_STRATEGY, isAvailable); + const strategy = dataSource ? RowGroupingStrategy.DataSource : RowGroupingStrategy.Default; + + privateApiRef.current.setStrategyAvailability('rowTree', strategy, isAvailable); }; export const getCellGroupingCriteria = ({ diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts new file mode 100644 index 0000000000000..07ad4690d0594 --- /dev/null +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts @@ -0,0 +1,134 @@ +import * as React from 'react'; +import { GridRowId, gridRowTreeSelector, gridColumnLookupSelector } from '@mui/x-data-grid-pro'; +import { + GridStrategyProcessor, + useGridRegisterStrategyProcessor, + createRowTree, + updateRowTree, + getVisibleRowsLookup, + skipSorting, + skipFiltering, + GridRowsPartialUpdates, +} from '@mui/x-data-grid-pro/internals'; +import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; +import { getGroupingRules, RowGroupingStrategy } from './gridRowGroupingUtils'; +import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; +import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; + +export const useGridDataSourceRowGroupingPreProcessors = ( + apiRef: React.MutableRefObject, + props: Pick< + DataGridPremiumProcessedProps, + | 'disableRowGrouping' + | 'groupingColDef' + | 'rowGroupingColumnMode' + | 'defaultGroupingExpansionDepth' + | 'isGroupExpandedByDefault' + | 'unstable_dataSource' + >, +) => { + const createRowTreeForRowGrouping = React.useCallback>( + (params) => { + const getGroupKey = props.unstable_dataSource?.getGroupKey; + if (!getGroupKey) { + throw new Error('MUI X: No `getGroupKey` method provided with the dataSource.'); + } + + const getChildrenCount = props.unstable_dataSource?.getChildrenCount; + if (!getChildrenCount) { + throw new Error('MUI X: No `getChildrenCount` method provided with the dataSource.'); + } + + const sanitizedRowGroupingModel = gridRowGroupingSanitizedModelSelector(apiRef); + const columnsLookup = gridColumnLookupSelector(apiRef); + const groupingRules = getGroupingRules({ + sanitizedRowGroupingModel, + columnsLookup, + }); + apiRef.current.caches.rowGrouping.rulesOnLastRowTreeCreation = groupingRules; + + const getRowTreeBuilderNode = (rowId: GridRowId) => { + const parentPath = (params.updates as GridRowsPartialUpdates).groupKeys ?? []; + const row = params.dataRowIdToModelLookup[rowId]; + const groupingRule = groupingRules[parentPath.length]; + const groupingValueGetter = groupingRule?.groupingValueGetter; + const leafKey = + groupingValueGetter?.( + row[groupingRule.field] as never, + row, + columnsLookup[groupingRule.field], + apiRef, + ) ?? getGroupKey(params.dataRowIdToModelLookup[rowId]); + return { + id: rowId, + path: [...parentPath, leafKey ?? rowId.toString()].map((key, i) => ({ + key, + field: groupingRules[i]?.field ?? null, + })), + serverChildrenCount: getChildrenCount(params.dataRowIdToModelLookup[rowId]) ?? 0, + }; + }; + + if (params.updates.type === 'full') { + return createRowTree({ + previousTree: params.previousTree, + nodes: params.updates.rows.map(getRowTreeBuilderNode), + defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, + isGroupExpandedByDefault: props.isGroupExpandedByDefault, + groupingName: RowGroupingStrategy.DataSource, + }); + } + + return updateRowTree({ + nodes: { + inserted: (params.updates as GridRowsPartialUpdates).actions.insert.map( + getRowTreeBuilderNode, + ), + modified: (params.updates as GridRowsPartialUpdates).actions.modify.map( + getRowTreeBuilderNode, + ), + removed: (params.updates as GridRowsPartialUpdates).actions.remove, + }, + previousTree: params.previousTree!, + previousGroupsToFetch: params.previousGroupsToFetch, + previousTreeDepth: params.previousTreeDepths!, + defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, + isGroupExpandedByDefault: props.isGroupExpandedByDefault, + groupingName: RowGroupingStrategy.DataSource, + }); + }, + [ + apiRef, + props.unstable_dataSource, + props.defaultGroupingExpansionDepth, + props.isGroupExpandedByDefault, + ], + ); + + const filterRows = React.useCallback>(() => { + const rowTree = gridRowTreeSelector(apiRef); + + return skipFiltering(rowTree); + }, [apiRef]); + + const sortRows = React.useCallback>(() => { + const rowTree = gridRowTreeSelector(apiRef); + + return skipSorting(rowTree); + }, [apiRef]); + + useGridRegisterStrategyProcessor( + apiRef, + RowGroupingStrategy.DataSource, + 'rowTreeCreation', + createRowTreeForRowGrouping, + ); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.DataSource, 'filtering', filterRows); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.DataSource, 'sorting', sortRows); + useGridRegisterStrategyProcessor( + apiRef, + RowGroupingStrategy.DataSource, + 'visibleRowsLookupCreation', + getVisibleRowsLookup, + ); +}; diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx index b779e23a2064a..fe562ab900c3e 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx @@ -19,7 +19,7 @@ import { import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; import { getRowGroupingFieldFromGroupingCriteria, - ROW_GROUPING_STRATEGY, + RowGroupingStrategy, isGroupingColumn, mergeStateWithRowGroupingModel, setStrategyAvailability, @@ -63,6 +63,7 @@ export const useGridRowGrouping = ( | 'disableRowGrouping' | 'slotProps' | 'slots' + | 'unstable_dataSource' >, ) => { apiRef.current.registerControlState({ @@ -73,7 +74,7 @@ export const useGridRowGrouping = ( changeEvent: 'rowGroupingModelChange', }); - /** + /* * API METHODS */ const setRowGroupingModel = React.useCallback( @@ -165,6 +166,16 @@ export const useGridRowGrouping = ( [props.disableRowGrouping], ); + const addGetRowsParams = React.useCallback>( + (params) => { + return { + ...params, + groupFields: gridRowGroupingModelSelector(apiRef), + }; + }, + [apiRef], + ); + const stateExportPreProcessing = React.useCallback>( (prevState, context) => { const rowGroupingModelToExport = gridRowGroupingModelSelector(apiRef); @@ -209,10 +220,11 @@ export const useGridRowGrouping = ( ); useGridRegisterPipeProcessor(apiRef, 'columnMenu', addColumnMenuButtons); + useGridRegisterPipeProcessor(apiRef, 'getRowsParams', addGetRowsParams); useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing); useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing); - /** + /* * EVENTS */ const handleCellKeyDown = React.useCallback>( @@ -233,10 +245,15 @@ export const useGridRowGrouping = ( return; } + if (props.unstable_dataSource && !params.rowNode.childrenExpanded) { + apiRef.current.unstable_dataSource.fetchRows(params.id); + return; + } + apiRef.current.setRowChildrenExpansion(params.id, !params.rowNode.childrenExpanded); } }, - [apiRef, props.rowGroupingColumnMode], + [apiRef, props.rowGroupingColumnMode, props.unstable_dataSource], ); const checkGroupingColumnsModelDiff = React.useCallback< @@ -258,7 +275,7 @@ export const useGridRowGrouping = ( // Refresh the row tree creation strategy processing // TODO: Add a clean way to re-run a strategy processing without publishing a private event - if (apiRef.current.getActiveStrategy('rowTree') === ROW_GROUPING_STRATEGY) { + if (apiRef.current.getActiveStrategy('rowTree') === RowGroupingStrategy.Default) { apiRef.current.publishEvent('activeStrategyProcessorChange', 'rowTreeCreation'); } } @@ -267,8 +284,11 @@ export const useGridRowGrouping = ( useGridApiEventHandler(apiRef, 'cellKeyDown', handleCellKeyDown); useGridApiEventHandler(apiRef, 'columnsChange', checkGroupingColumnsModelDiff); useGridApiEventHandler(apiRef, 'rowGroupingModelChange', checkGroupingColumnsModelDiff); + useGridApiEventHandler(apiRef, 'rowGroupingModelChange', () => + apiRef.current.unstable_dataSource.fetchRows(), + ); - /** + /* * EFFECTS */ React.useEffect(() => { diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts index 2fa403ec72d5e..ac43f038695ab 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts @@ -31,7 +31,7 @@ import { import { filterRowTreeFromGroupingColumns, getColDefOverrides, - ROW_GROUPING_STRATEGY, + RowGroupingStrategy, isGroupingColumn, setStrategyAvailability, getCellGroupingCriteria, @@ -48,6 +48,7 @@ export const useGridRowGroupingPreProcessors = ( | 'rowGroupingColumnMode' | 'defaultGroupingExpansionDepth' | 'isGroupExpandedByDefault' + | 'unstable_dataSource' >, ) => { const getGroupingColDefs = React.useCallback( @@ -56,6 +57,10 @@ export const useGridRowGroupingPreProcessors = ( return []; } + const strategy = props.unstable_dataSource + ? RowGroupingStrategy.DataSource + : RowGroupingStrategy.Default; + const groupingColDefProp = props.groupingColDef; // We can't use `gridGroupingRowsSanitizedModelSelector` here because the new columns are not in the state yet @@ -73,8 +78,9 @@ export const useGridRowGroupingPreProcessors = ( createGroupingColDefForAllGroupingCriteria({ apiRef, rowGroupingModel, - colDefOverride: getColDefOverrides(groupingColDefProp, rowGroupingModel), + colDefOverride: getColDefOverrides(groupingColDefProp, rowGroupingModel, strategy), columnsLookup: columnsState.lookup, + strategy, }), ]; } @@ -86,6 +92,7 @@ export const useGridRowGroupingPreProcessors = ( colDefOverride: getColDefOverrides(groupingColDefProp, [groupingCriteria]), groupedByColDef: columnsState.lookup[groupingCriteria], columnsLookup: columnsState.lookup, + strategy, }), ); } @@ -95,7 +102,13 @@ export const useGridRowGroupingPreProcessors = ( } } }, - [apiRef, props.groupingColDef, props.rowGroupingColumnMode, props.disableRowGrouping], + [ + apiRef, + props.groupingColDef, + props.rowGroupingColumnMode, + props.disableRowGrouping, + props.unstable_dataSource, + ], ); const updateGroupingColumn = React.useCallback>( @@ -177,7 +190,7 @@ export const useGridRowGroupingPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: ROW_GROUPING_STRATEGY, + groupingName: RowGroupingStrategy.Default, }); } @@ -191,7 +204,7 @@ export const useGridRowGroupingPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: ROW_GROUPING_STRATEGY, + groupingName: RowGroupingStrategy.Default, }); }, [apiRef, props.defaultGroupingExpansionDepth, props.isGroupExpandedByDefault], @@ -228,35 +241,29 @@ export const useGridRowGroupingPreProcessors = ( useGridRegisterPipeProcessor(apiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( apiRef, - ROW_GROUPING_STRATEGY, + RowGroupingStrategy.Default, 'rowTreeCreation', createRowTreeForRowGrouping, ); - useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY, 'filtering', filterRows); - useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY, 'sorting', sortRows); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.Default, 'filtering', filterRows); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.Default, 'sorting', sortRows); useGridRegisterStrategyProcessor( apiRef, - ROW_GROUPING_STRATEGY, + RowGroupingStrategy.Default, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); - /** - * 1ST RENDER - */ useFirstRender(() => { - setStrategyAvailability(apiRef, props.disableRowGrouping); + setStrategyAvailability(apiRef, props.disableRowGrouping, props.unstable_dataSource); }); - /** - * EFFECTS - */ const isFirstRender = React.useRef(true); React.useEffect(() => { if (!isFirstRender.current) { - setStrategyAvailability(apiRef, props.disableRowGrouping); + setStrategyAvailability(apiRef, props.disableRowGrouping, props.unstable_dataSource); } else { isFirstRender.current = false; } - }, [apiRef, props.disableRowGrouping]); + }, [apiRef, props.disableRowGrouping, props.unstable_dataSource]); }; diff --git a/packages/x-data-grid-premium/src/tests/rowSelection.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/rowSelection.DataGridPremium.test.tsx index d330b84ebd8c7..88bb7b1dd69e0 100644 --- a/packages/x-data-grid-premium/src/tests/rowSelection.DataGridPremium.test.tsx +++ b/packages/x-data-grid-premium/src/tests/rowSelection.DataGridPremium.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { createRenderer, fireEvent } from '@mui/internal-test-utils'; +import { act, createRenderer, fireEvent } from '@mui/internal-test-utils'; import { getCell } from 'test/utils/helperFn'; import { expect } from 'chai'; import { @@ -122,6 +122,24 @@ describe(' - Row selection', () => { expect(apiRef.current.getSelectedRows()).to.have.keys([0, 2]); }); + // Context: https://github.com/mui/mui-x/issues/15206 + it('should keep the correct selection items and the selection count when rows are updated', () => { + render(); + + const expectedKeys = ['auto-generated-row-category1/Cat B', 3, 4]; + const expectedCount = 3; + + fireEvent.click(getCell(1, 0).querySelector('input')!); + expect(apiRef.current.getSelectedRows()).to.have.keys(expectedKeys); + expect(apiRef.current.state.rowSelection.length).to.equal(expectedCount); + + act(() => { + apiRef.current.updateRows([...rows]); + }); + expect(apiRef.current.getSelectedRows()).to.have.keys(expectedKeys); + expect(apiRef.current.state.rowSelection.length).to.equal(expectedCount); + }); + describe("prop: indeterminateCheckboxAction = 'select'", () => { it('should select all the children when selecting an indeterminate parent', () => { render( diff --git a/packages/x-data-grid-pro/package.json b/packages/x-data-grid-pro/package.json index b6833d5bf47d8..ae699d6cc972a 100644 --- a/packages/x-data-grid-pro/package.json +++ b/packages/x-data-grid-pro/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-data-grid-pro", - "version": "7.20.0", + "version": "7.21.0", "description": "The Pro plan edition of the Data Grid components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -43,7 +43,7 @@ "directory": "packages/x-data-grid-pro" }, "dependencies": { - "@babel/runtime": "^7.25.7", + "@babel/runtime": "^7.26.0", "@mui/utils": "^5.16.6 || ^6.0.0", "@mui/x-data-grid": "workspace:*", "@mui/x-internals": "workspace:*", @@ -70,7 +70,7 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.15", + "@mui/internal-test-utils": "^1.0.19", "@mui/material": "^5.16.7", "@mui/system": "^5.16.7", "@types/prop-types": "^15.7.13", diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 4590c79ccfdf5..cf10a788ce68c 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -873,6 +873,7 @@ DataGridProRaw.propTypes = { * Setting it to a lower value could be useful when using dynamic row height, * but might reduce performance when displaying a large number of rows. * @default 166 + * @deprecated */ rowPositionsDebounceMs: PropTypes.number, /** @@ -1004,6 +1005,21 @@ DataGridProRaw.propTypes = { get: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), + /** + * Definition of the column rendered when the `unstable_listView` prop is enabled. + */ + unstable_listColumn: PropTypes.shape({ + align: PropTypes.oneOf(['center', 'left', 'right']), + cellClassName: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + display: PropTypes.oneOf(['flex', 'text']), + field: PropTypes.string.isRequired, + renderCell: PropTypes.func, + }), + /** + * If `true`, displays the data in a list view. + * Use in combination with `unstable_listColumn`. + */ + unstable_listView: PropTypes.bool, unstable_onDataSourceError: PropTypes.func, /** * If `true`, the Data Grid will auto span the cells over the rows having the same value. diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index 6b8b06fc21bb5..0240d83802fcc 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -49,6 +49,8 @@ import { columnResizeStateInitializer, useGridRowSpanning, rowSpanningStateInitializer, + useGridListView, + listViewStateInitializer, } from '@mui/x-data-grid/internals'; import { GridApiPro, GridPrivateApiPro } from '../models/gridApiPro'; import { DataGridProProcessedProps } from '../models/dataGridProProps'; @@ -132,6 +134,7 @@ export const useDataGridProComponent = ( useGridInitializeState(columnGroupsStateInitializer, apiRef, props); useGridInitializeState(virtualizationStateInitializer, apiRef, props); useGridInitializeState(dataSourceStateInitializer, apiRef, props); + useGridInitializeState(listViewStateInitializer, apiRef, props); useGridHeaderFiltering(apiRef, props); useGridTreeData(apiRef, props); @@ -142,7 +145,7 @@ export const useDataGridProComponent = ( useGridColumns(apiRef, props); useGridRows(apiRef, props); useGridRowSpanning(apiRef, props); - useGridParamsApi(apiRef); + useGridParamsApi(apiRef, props); useGridDetailPanel(apiRef, props); useGridColumnSpanning(apiRef); useGridColumnGrouping(apiRef, props); @@ -169,6 +172,7 @@ export const useDataGridProComponent = ( useGridStatePersistence(apiRef); useGridVirtualization(apiRef, props); useGridDataSource(apiRef, props); + useGridListView(apiRef, props); return apiRef; }; diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index a54285727759f..295702230804c 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -55,6 +55,7 @@ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValu rowsLoadingMode: 'client', scrollEndThreshold: 80, treeData: false, + unstable_listView: false, }; const defaultSlots = DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS; diff --git a/packages/x-data-grid-pro/src/components/GridColumnMenuPinningItem.tsx b/packages/x-data-grid-pro/src/components/GridColumnMenuPinningItem.tsx index 0a07624dc3067..89168d11fa8d5 100644 --- a/packages/x-data-grid-pro/src/components/GridColumnMenuPinningItem.tsx +++ b/packages/x-data-grid-pro/src/components/GridColumnMenuPinningItem.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { useRtl } from '@mui/system/RtlProvider'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { GridPinnedColumnPosition, GridColumnMenuItemProps } from '@mui/x-data-grid'; @@ -27,21 +26,21 @@ function GridColumnMenuPinningItem(props: GridColumnMenuItemProps) { onClick(event); }; const pinToLeftMenuItem = ( - + {apiRef.current.getLocaleText('pinToLeft')} - + ); const pinToRightMenuItem = ( - + {apiRef.current.getLocaleText('pinToRight')} - + ); if (!colDef) { @@ -62,16 +61,16 @@ function GridColumnMenuPinningItem(props: GridColumnMenuItemProps) { : rootProps.slots.columnMenuPinRightIcon; return ( - + {apiRef.current.getLocaleText(label)} - - + + {apiRef.current.getLocaleText('unpin')} - + ); } diff --git a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx index 79e002211adf2..d5ee7aa79ab5f 100644 --- a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import composeClasses from '@mui/utils/composeClasses'; import Box from '@mui/material/Box'; -import Badge from '@mui/material/Badge'; import { getDataGridUtilityClass, GridRenderCellParams, @@ -93,9 +92,9 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) {...rootProps?.slotProps?.baseIconButton} > - + - + ) : null; 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 58ba0623f1bd2..2c8c8cbce7a93 100644 --- a/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx +++ b/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx @@ -193,7 +193,9 @@ const GridHeaderFilterCell = React.forwardRef { apiRef.current.hideHeaderFilterMenu(); @@ -56,7 +56,11 @@ function GridHeaderFilterMenu({ return ( - + {operators.map((op, i) => { const label = op?.headerLabel ?? @@ -65,7 +69,7 @@ function GridHeaderFilterMenu({ ); return ( - { applyFilterChanges({ ...item, operator: op.value }); hideMenu(); @@ -75,10 +79,10 @@ function GridHeaderFilterMenu({ key={`${field}-${op.value}`} > {label} - + ); })} - + ); } diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts index dde8cad3d39fe..5645235abf019 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts @@ -15,6 +15,7 @@ function getKey(params: GridGetRowsParams) { params.filterModel, params.sortModel, params.groupKeys, + params.groupFields, ]); } diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts index 6b6aa5a9404d9..09e2d34c90997 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts @@ -21,8 +21,6 @@ export const gridGetRowsParamsSelector = createSelector( (filterModel, sortModel, paginationModel) => { return { groupKeys: [], - // TODO: Implement with `rowGrouping` - groupFields: [], paginationModel, sortModel, filterModel, diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 4e365d10d7eed..31f3d209a695b 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -85,7 +85,10 @@ export const useGridDataSource = ( apiRef.current.resetDataSourceState(); } - const fetchParams = gridGetRowsParamsSelector(apiRef); + const fetchParams = { + ...gridGetRowsParamsSelector(apiRef), + ...apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}), + }; const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); @@ -122,7 +125,8 @@ export const useGridDataSource = ( const fetchRowChildren = React.useCallback( async (id) => { - if (!props.treeData) { + const pipedParams = apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}); + if (!props.treeData && (pipedParams.groupFields?.length ?? 0) === 0) { nestedDataManager.clearPendingRequest(id); return; } @@ -138,7 +142,11 @@ export const useGridDataSource = ( return; } - const fetchParams = { ...gridGetRowsParamsSelector(apiRef), groupKeys: rowNode.path }; + const fetchParams = { + ...gridGetRowsParamsSelector(apiRef), + ...pipedParams, + groupKeys: rowNode.path, + }; const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx b/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx index 76e80c840299e..16998e55615fb 100644 --- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; import { GRID_STRING_COL_DEF, GridColDef } from '@mui/x-data-grid'; +import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '@mui/x-data-grid/internals'; import { GridApiPro } from '../../../models/gridApiPro'; import { GridDetailPanelToggleCell } from '../../../components/GridDetailPanelToggleCell'; import { gridDetailPanelExpandedRowIdsSelector } from './gridDetailPanelSelector'; -export const GRID_DETAIL_PANEL_TOGGLE_FIELD = '__detail_panel_toggle__'; +export { GRID_DETAIL_PANEL_TOGGLE_FIELD }; export const GRID_DETAIL_PANEL_TOGGLE_COL_DEF: GridColDef = { ...GRID_STRING_COL_DEF, diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts b/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts index f618ffe20a7f2..2efc3fbe92505 100644 --- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts +++ b/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts @@ -313,7 +313,13 @@ export const useGridDetailPanel = ( const isFirstRender = React.useRef(true); if (isFirstRender.current) { - isFirstRender.current = false; updateCachesIfNeeded(); } + React.useEffect(() => { + if (!isFirstRender.current) { + updateCachesIfNeeded(); + apiRef.current.hydrateRowsMeta(); + } + isFirstRender.current = false; + }, [apiRef, updateCachesIfNeeded]); }; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx index c09bc45b8dedf..0c6b841f939d8 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx @@ -34,8 +34,7 @@ import { } from '../../../utils/tree/models'; import { updateRowTree } from '../../../utils/tree/updateRowTree'; import { getVisibleRowsLookup } from '../../../utils/tree/utils'; - -const DATA_SOURCE_TREE_DATA_STRATEGY = 'dataSourceTreeData'; +import { TreeDataStrategy } from '../treeData/gridTreeDataUtils'; export const useGridDataSourceTreeDataPreProcessors = ( privateApiRef: React.MutableRefObject, @@ -53,7 +52,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability( 'rowTree', - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, props.treeData && props.unstable_dataSource ? () => true : () => false, ); }, [privateApiRef, props.treeData, props.unstable_dataSource]); @@ -64,7 +63,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( let colDefOverride: GridGroupingColDefOverride | null | undefined; if (typeof groupingColDefProp === 'function') { const params: GridGroupingColDefOverrideParams = { - groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.DataSource, fields: [], }; @@ -171,7 +170,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.DataSource, onDuplicatePath, }); } @@ -187,7 +186,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.DataSource, }); }, [ @@ -212,25 +211,20 @@ export const useGridDataSourceTreeDataPreProcessors = ( useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, 'rowTreeCreation', createRowTreeForTreeData, ); useGridRegisterStrategyProcessor( privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, 'filtering', filterRows, ); + useGridRegisterStrategyProcessor(privateApiRef, TreeDataStrategy.DataSource, 'sorting', sortRows); useGridRegisterStrategyProcessor( privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, - 'sorting', - sortRows, - ); - useGridRegisterStrategyProcessor( - privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataGroupColDef.ts b/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataGroupColDef.ts index 4fe1addd677f4..f9f27fdd5d744 100644 --- a/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataGroupColDef.ts +++ b/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataGroupColDef.ts @@ -1,4 +1,5 @@ import { GRID_STRING_COL_DEF, GridColDef } from '@mui/x-data-grid'; +import { GRID_TREE_DATA_GROUPING_FIELD } from '@mui/x-data-grid/internals'; /** * TODO: Add sorting and filtering on the value and the filteredDescendantCount @@ -19,7 +20,7 @@ export const GRID_TREE_DATA_GROUPING_COL_DEF: Omit; } -export const TREE_DATA_STRATEGY = 'tree-data'; +export enum TreeDataStrategy { + Default = 'tree-data', + DataSource = 'tree-data-source', +} /** * A node is visible if one of the following criteria is met: diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx index 74c0fcfcda382..6a5753c9eb60f 100644 --- a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx @@ -19,7 +19,7 @@ import { GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES, } from './gridTreeDataGroupColDef'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { filterRowTreeFromTreeData, TREE_DATA_STRATEGY } from './gridTreeDataUtils'; +import { filterRowTreeFromTreeData, TreeDataStrategy } from './gridTreeDataUtils'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { GridGroupingColDefOverride, @@ -52,7 +52,7 @@ export const useGridTreeDataPreProcessors = ( const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability( 'rowTree', - TREE_DATA_STRATEGY, + TreeDataStrategy.Default, props.treeData && !props.unstable_dataSource ? () => true : () => false, ); }, [privateApiRef, props.treeData, props.unstable_dataSource]); @@ -63,7 +63,7 @@ export const useGridTreeDataPreProcessors = ( let colDefOverride: GridGroupingColDefOverride | null | undefined; if (typeof groupingColDefProp === 'function') { const params: GridGroupingColDefOverrideParams = { - groupingName: TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.Default, fields: [], }; @@ -158,7 +158,7 @@ export const useGridTreeDataPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.Default, onDuplicatePath, }); } @@ -173,7 +173,7 @@ export const useGridTreeDataPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.Default, }); }, [props.getTreeDataPath, props.defaultGroupingExpansionDepth, props.isGroupExpandedByDefault], @@ -211,15 +211,20 @@ export const useGridTreeDataPreProcessors = ( useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( privateApiRef, - TREE_DATA_STRATEGY, + TreeDataStrategy.Default, 'rowTreeCreation', createRowTreeForTreeData, ); - useGridRegisterStrategyProcessor(privateApiRef, TREE_DATA_STRATEGY, 'filtering', filterRows); - useGridRegisterStrategyProcessor(privateApiRef, TREE_DATA_STRATEGY, 'sorting', sortRows); useGridRegisterStrategyProcessor( privateApiRef, - TREE_DATA_STRATEGY, + TreeDataStrategy.Default, + 'filtering', + filterRows, + ); + useGridRegisterStrategyProcessor(privateApiRef, TreeDataStrategy.Default, 'sorting', sortRows); + useGridRegisterStrategyProcessor( + privateApiRef, + TreeDataStrategy.Default, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index c0de9e27064b8..ed619bb0cc9e4 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -33,7 +33,6 @@ export { useGridRowReorder } from '../hooks/features/rowReorder/useGridRowReorde export { useGridRowReorderPreProcessors } from '../hooks/features/rowReorder/useGridRowReorderPreProcessors'; export { useGridTreeData } from '../hooks/features/treeData/useGridTreeData'; export { useGridTreeDataPreProcessors } from '../hooks/features/treeData/useGridTreeDataPreProcessors'; -export { TREE_DATA_STRATEGY } from '../hooks/features/treeData/gridTreeDataUtils'; export { useGridRowPinning, rowPinningStateInitializer, @@ -61,4 +60,6 @@ export { sortRowTree } from '../utils/tree/sortRowTree'; export { insertNodeInTree, removeNodeFromTree, getVisibleRowsLookup } from '../utils/tree/utils'; export type { RowTreeBuilderGroupingCriterion } from '../utils/tree/models'; +export { skipSorting, skipFiltering } from '../hooks/features/serverSideTreeData/utils'; + export * from './propValidation'; diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index 3315d72ac0d75..d2e2c490600d3 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -7,6 +7,7 @@ import { GridValidRowModel, GridGroupNode, GridFeatureMode, + GridListColDef, } from '@mui/x-data-grid'; import type { GridExperimentalFeatures, @@ -137,6 +138,11 @@ export interface DataGridProPropsWithDefaultValue; } diff --git a/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx index 067f511243c12..b52adbb9d57a0 100644 --- a/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx @@ -13,7 +13,7 @@ import { GridCellModes, } from '@mui/x-data-grid-pro'; import { getBasicGridData } from '@mui/x-data-grid-generator'; -import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, act, waitFor } from '@mui/internal-test-utils'; import { getCell, spyApi } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; @@ -896,6 +896,25 @@ describe(' - Cell editing', () => { deleteValue: true, }); }); + + it('should call preProcessEditCellProps', async () => { + const preProcessEditCellProps = spy(({ props }: GridPreProcessEditCellProps) => props); + render(); + + const cell = getCell(0, 1); + fireUserEvent.mousePress(cell); + fireEvent.keyDown(cell, { key: 'Delete' }); + + await waitFor(() => { + expect(preProcessEditCellProps.callCount).to.equal(1); + }); + + expect(preProcessEditCellProps.lastCall.args[0].props).to.deep.equal({ + value: '', + error: false, + isProcessingProps: true, + }); + }); }); describe('by pressing a printable character', () => { diff --git a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx index 1f71824e507fa..b8e38ff9df4a6 100644 --- a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx @@ -86,10 +86,10 @@ describe(' - Detail panel', () => { await microtasks(); const virtualScrollerContent = $('.MuiDataGrid-virtualScrollerContent')!; - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + detailPanelHeight}px`, }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); const detailPanels = $$('.MuiDataGrid-detailPanel'); expect(detailPanels[0]).toHaveComputedStyle({ @@ -128,11 +128,9 @@ describe(' - Detail panel', () => { }); await waitFor(() => { - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', - height: `${rowHeight + 100}px`, - }); + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 100}px` }); }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); const detailPanels = $$('.MuiDataGrid-detailPanel'); expect(detailPanels[0]).toHaveComputedStyle({ @@ -142,11 +140,9 @@ describe(' - Detail panel', () => { fireEvent.click(screen.getByRole('button', { name: 'Increase' })); await waitFor(() => { - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', - height: `${rowHeight + 200}px`, - }); + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 200}px` }); }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); expect(detailPanels[0]).toHaveComputedStyle({ height: `200px`, diff --git a/packages/x-data-grid-pro/src/tests/listView.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/listView.DataGridPro.test.tsx new file mode 100644 index 0000000000000..043ca3ab6179c --- /dev/null +++ b/packages/x-data-grid-pro/src/tests/listView.DataGridPro.test.tsx @@ -0,0 +1,98 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { createRenderer, screen } from '@mui/internal-test-utils'; +import { + DataGridPro, + DataGridProProps, + GridColDef, + GridListColDef, + GridRowsProp, +} from '@mui/x-data-grid-pro'; + +const rows: GridRowsProp = [{ id: '123567', title: 'test' }]; + +const columns: GridColDef[] = [{ field: 'id' }, { field: 'title' }]; + +const listColumn: GridListColDef = { + field: 'listColumn', + renderCell: (params) =>
Title: {params.row.title}
, +}; + +describe(' - List view', () => { + const { render } = createRenderer(); + + function Test(props: Partial) { + return ( +
+ +
+ ); + } + + it('should not render list column when list view is not enabled', () => { + render( +
+ +
, + ); + expect(screen.queryByTestId('list-column')).to.equal(null); + }); + + it('should render list column when list view is enabled', () => { + render( +
+ +
, + ); + expect(screen.getByTestId('list-column')).not.to.equal(null); + expect(screen.getByTestId('list-column')).to.have.text('Title: test'); + }); + + it('should render list column when `unstable_listView` prop updates', () => { + const { setProps } = render(); + expect(screen.queryByTestId('list-column')).to.equal(null); + + setProps({ unstable_listView: true }); + + expect(screen.getByTestId('list-column')).not.to.equal(null); + expect(screen.getByTestId('list-column')).to.have.text('Title: test'); + + setProps({ unstable_listView: false }); + + expect(screen.queryByTestId('list-column')).to.equal(null); + }); + + it('should update cell contents when the `renderCell` function changes', () => { + const { setProps } = render(); + + setProps({ + unstable_listColumn: { + ...listColumn, + renderCell: (params) =>
ID: {params.row.id}
, + }, + } as Partial); + + expect(screen.getByTestId('list-column')).to.have.text('ID: 123567'); + }); + + it('should warn if the `unstable_listColumn` prop is not provided when `unstable_listView` is enabled', () => { + expect(() => { + render( +
+ +
, + ); + }).toWarnDev( + [ + 'MUI X: The `unstable_listColumn` prop must be set if `unstable_listView` is enabled.', + 'To fix, pass a column definition to the `unstable_listColumn` prop, e.g. `{ field: "example", renderCell: (params) =>
{params.row.id}
}`.', + 'For more details, see https://mui.com/x/react-data-grid/list-view/', + ].join('\n'), + ); + }); +}); diff --git a/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx index c4e0882c54fcd..0605434f2a7e7 100644 --- a/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx @@ -13,7 +13,7 @@ import { } from '@mui/x-data-grid-pro'; import Portal from '@mui/material/Portal'; import { getBasicGridData } from '@mui/x-data-grid-generator'; -import { createRenderer, fireEvent, act, screen } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, act, screen, waitFor } from '@mui/internal-test-utils'; import { getCell, getRow, spyApi } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; @@ -906,6 +906,25 @@ describe(' - Row editing', () => { deleteValue: true, }); }); + + it('should call preProcessEditCellProps', async () => { + const preProcessEditCellProps = spy(({ props }: GridPreProcessEditCellProps) => props); + render(); + + const cell = getCell(0, 1); + fireUserEvent.mousePress(cell); + fireEvent.keyDown(cell, { key: 'Delete' }); + + await waitFor(() => { + expect(preProcessEditCellProps.callCount).to.equal(1); + }); + + expect(preProcessEditCellProps.lastCall.args[0].props).to.deep.equal({ + value: '', + error: false, + isProcessingProps: true, + }); + }); }); describe('by pressing a printable character', () => { diff --git a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx index 3acb545b4ed5e..ba199d341d218 100644 --- a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx @@ -887,77 +887,6 @@ describe(' - Rows', () => { }); }); - describe('apiRef: setRowHeight', () => { - const ROW_HEIGHT = 52; - - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - - beforeEach(() => { - baselineProps = { - rows: [ - { - id: 0, - brand: 'Nike', - }, - { - id: 1, - brand: 'Adidas', - }, - { - id: 2, - brand: 'Puma', - }, - ], - columns: [{ field: 'brand', headerName: 'Brand' }], - }; - }); - - let apiRef: React.MutableRefObject; - - function TestCase(props: Partial) { - apiRef = useGridApiRef(); - return ( -
- -
- ); - } - - it('should change row height', () => { - const resizedRowId = 1; - render(); - - expect(getRow(1).clientHeight).to.equal(ROW_HEIGHT); - - act(() => apiRef.current.unstable_setRowHeight(resizedRowId, 100)); - expect(getRow(resizedRowId).clientHeight).to.equal(100); - }); - - it('should preserve changed row height after sorting', () => { - const resizedRowId = 0; - const getRowHeight = spy(); - render(); - - const row = getRow(resizedRowId); - expect(row.clientHeight).to.equal(ROW_HEIGHT); - - getRowHeight.resetHistory(); - act(() => apiRef.current.unstable_setRowHeight(resizedRowId, 100)); - expect(row.clientHeight).to.equal(100); - - // sort - fireEvent.click(getColumnHeaderCell(resizedRowId)); - - expect(row.clientHeight).to.equal(100); - expect(getRowHeight.neverCalledWithMatch({ id: resizedRowId })).to.equal(true); - }); - }); - describe('prop: rowCount', () => { function TestCase(props: DataGridProProps) { return ( diff --git a/packages/x-data-grid/package.json b/packages/x-data-grid/package.json index 61dbcb5a33215..6e9ee4e5c28ea 100644 --- a/packages/x-data-grid/package.json +++ b/packages/x-data-grid/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-data-grid", - "version": "7.20.0", + "version": "7.21.0", "description": "The Community plan edition of the Data Grid components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -47,7 +47,7 @@ "directory": "packages/x-data-grid" }, "dependencies": { - "@babel/runtime": "^7.25.7", + "@babel/runtime": "^7.26.0", "@mui/utils": "^5.16.6 || ^6.0.0", "@mui/x-internals": "workspace:*", "clsx": "^2.1.1", @@ -71,7 +71,7 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.15", + "@mui/internal-test-utils": "^1.0.19", "@mui/joy": "^5.0.0-beta.48", "@mui/material": "^5.16.7", "@mui/system": "^5.16.7", diff --git a/packages/x-data-grid/src/DataGrid/DataGrid.tsx b/packages/x-data-grid/src/DataGrid/DataGrid.tsx index 72a2bb43cb78f..ccda733ed0deb 100644 --- a/packages/x-data-grid/src/DataGrid/DataGrid.tsx +++ b/packages/x-data-grid/src/DataGrid/DataGrid.tsx @@ -739,6 +739,7 @@ DataGridRaw.propTypes = { * Setting it to a lower value could be useful when using dynamic row height, * but might reduce performance when displaying a large number of rows. * @default 166 + * @deprecated */ rowPositionsDebounceMs: PropTypes.number, /** diff --git a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx index 85f9a09cb3eaa..2c888c05b2384 100644 --- a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx +++ b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx @@ -57,6 +57,10 @@ import { rowSpanningStateInitializer, useGridRowSpanning, } from '../hooks/features/rows/useGridRowSpanning'; +import { + listViewStateInitializer, + useGridListView, +} from '../hooks/features/listView/useGridListView'; export const useDataGridComponent = ( inputApiRef: React.MutableRefObject | undefined, @@ -93,13 +97,14 @@ export const useDataGridComponent = ( useGridInitializeState(columnMenuStateInitializer, apiRef, props); useGridInitializeState(columnGroupsStateInitializer, apiRef, props); useGridInitializeState(virtualizationStateInitializer, apiRef, props); + useGridInitializeState(listViewStateInitializer, apiRef, props); useGridKeyboardNavigation(apiRef, props); useGridRowSelection(apiRef, props); useGridColumns(apiRef, props); useGridRows(apiRef, props); useGridRowSpanning(apiRef, props); - useGridParamsApi(apiRef); + useGridParamsApi(apiRef, props); useGridColumnSpanning(apiRef); useGridColumnGrouping(apiRef, props); useGridEditing(apiRef, props); @@ -120,6 +125,7 @@ export const useDataGridComponent = ( useGridEvents(apiRef, props); useGridStatePersistence(apiRef); useGridVirtualization(apiRef, props); + useGridListView(apiRef, props); return apiRef; }; diff --git a/packages/x-data-grid/src/DataGrid/useDataGridProps.ts b/packages/x-data-grid/src/DataGrid/useDataGridProps.ts index 1ae57fbf5d8fa..7a2d045607e54 100644 --- a/packages/x-data-grid/src/DataGrid/useDataGridProps.ts +++ b/packages/x-data-grid/src/DataGrid/useDataGridProps.ts @@ -22,6 +22,7 @@ const DATA_GRID_FORCED_PROPS: { [key in DataGridForcedPropsKey]?: DataGridProces disableColumnReorder: true, keepColumnPositionIfDraggedOutside: false, signature: 'DataGrid', + unstable_listView: false, }; const defaultSlots = DATA_GRID_DEFAULT_SLOTS_COMPONENTS; diff --git a/packages/x-data-grid/src/colDef/gridBooleanOperators.ts b/packages/x-data-grid/src/colDef/gridBooleanOperators.ts index 840019455226c..bbf225fd71857 100644 --- a/packages/x-data-grid/src/colDef/gridBooleanOperators.ts +++ b/packages/x-data-grid/src/colDef/gridBooleanOperators.ts @@ -1,4 +1,7 @@ -import { GridFilterInputBoolean } from '../components/panel/filterPanel/GridFilterInputBoolean'; +import { + GridFilterInputBoolean, + sanitizeFilterItemValue, +} from '../components/panel/filterPanel/GridFilterInputBoolean'; import { GridFilterItem } from '../models/gridFilterItem'; import { GridFilterOperator } from '../models/gridFilterOperator'; @@ -6,14 +9,12 @@ export const getGridBooleanOperators = (): GridFilterOperator { - if (!filterItem.value) { + const sanitizedValue = sanitizeFilterItemValue(filterItem.value); + if (sanitizedValue === undefined) { return null; } - const valueAsBoolean = String(filterItem.value) === 'true'; - return (value): boolean => { - return Boolean(value) === valueAsBoolean; - }; + return (value): boolean => Boolean(value) === sanitizedValue; }, InputComponent: GridFilterInputBoolean, }, diff --git a/packages/x-data-grid/src/components/GridRow.tsx b/packages/x-data-grid/src/components/GridRow.tsx index 03c8b20797fc8..c3447c435a7a2 100644 --- a/packages/x-data-grid/src/components/GridRow.tsx +++ b/packages/x-data-grid/src/components/GridRow.tsx @@ -6,7 +6,6 @@ import { fastMemo } from '@mui/x-internals/fastMemo'; import { GridRowEventLookup } from '../models/events'; import { GridRowId, GridRowModel } from '../models/gridRows'; import { GridEditModes, GridRowModes, GridCellModes } from '../models/gridEditRowModel'; -import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { gridClasses } from '../constants/gridClasses'; import { composeGridClasses } from '../utils/composeGridClasses'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; @@ -20,7 +19,7 @@ import { useGridVisibleRows } from '../hooks/utils/useGridVisibleRows'; import { findParentElementFromClassName, isEventTargetInPortal } from '../utils/domUtils'; import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../colDef/gridCheckboxSelectionColDef'; import { GRID_ACTIONS_COLUMN_TYPE } from '../colDef/gridActionsColDef'; -import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../constants/gridDetailPanelToggleField'; +import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../internals/constants'; import type { GridDimensions } from '../hooks/features/dimensions'; import { gridSortModelSelector } from '../hooks/features/sorting/gridSortingSelector'; import { gridRowMaximumTreeDepthSelector } from '../hooks/features/rows/gridRowsSelector'; @@ -29,6 +28,7 @@ import { PinnedPosition, gridPinnedColumnPositionLookup } from './cell/GridCell' import { GridScrollbarFillerCell as ScrollbarFiller } from './GridScrollbarFillerCell'; import { getPinnedCellOffset } from '../internals/utils/getPinnedCellOffset'; import { useGridConfiguration } from '../hooks/utils/useGridConfiguration'; +import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext'; export interface GridRowProps extends React.HTMLAttributes { row: GridRowModel; @@ -81,6 +81,14 @@ function EmptyCell({ width }: { width: number }) { ); } +EmptyCell.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + width: PropTypes.number.isRequired, +} as any; + const GridRow = React.forwardRef(function GridRow(props, refProp) { const { selected, @@ -111,7 +119,7 @@ const GridRow = React.forwardRef(function GridRow( onMouseOver, ...other } = props; - const apiRef = useGridApiContext(); + const apiRef = useGridPrivateApiContext(); const configuration = useGridConfiguration(); const ref = React.useRef(null); const rootProps = useGridRootProps(); @@ -153,37 +161,19 @@ const GridRow = React.forwardRef(function GridRow( React.useLayoutEffect(() => { if (currentPage.range) { - // The index prop is relative to the rows from all pages. As example, the index prop of the - // first row is 5 if `paginationModel.pageSize=5` and `paginationModel.page=1`. However, the index used by the virtualization - // doesn't care about pagination and considers the rows from the current page only, so the - // first row always has index=0. We need to subtract the index of the first row to make it - // compatible with the index used by the virtualization. const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(rowId); - // pinned rows are not part of the visible rows - if (rowIndex != null) { + // Pinned rows are not part of the visible rows + if (rowIndex !== undefined) { apiRef.current.unstable_setLastMeasuredRowIndex(rowIndex); } } - const rootElement = ref.current; - const hasFixedHeight = rowHeight !== 'auto'; - if (!rootElement || hasFixedHeight || typeof ResizeObserver === 'undefined') { - return undefined; + if (ref.current && rowHeight === 'auto') { + return apiRef.current.observeRowHeight(ref.current, rowId); } - const resizeObserver = new ResizeObserver((entries) => { - const [entry] = entries; - const height = - entry.borderBoxSize && entry.borderBoxSize.length > 0 - ? entry.borderBoxSize[0].blockSize - : entry.contentRect.height; - apiRef.current.unstable_storeRowHeightMeasurement(rowId, height); - }); - - resizeObserver.observe(rootElement); - - return () => resizeObserver.disconnect(); - }, [apiRef, currentPage.range, index, rowHeight, rowId]); + return undefined; + }, [apiRef, currentPage.range, rowHeight, rowId]); const publish = React.useCallback( ( @@ -254,22 +244,12 @@ const GridRow = React.forwardRef(function GridRow( const rowReordering = (rootProps as any).rowReordering as boolean; - const sizes = useGridSelector( + const heightEntry = useGridSelector( apiRef, - () => ({ ...apiRef.current.unstable_getRowInternalSizes(rowId) }), + () => ({ ...apiRef.current.getRowHeightEntry(rowId) }), objectShallowCompare, ); - let minHeight = rowHeight; - if (minHeight === 'auto' && sizes) { - const numberOfBaseSizes = 1; - const maximumSize = sizes.baseCenter ?? 0; - - if (maximumSize > 0 && numberOfBaseSizes > 1) { - minHeight = maximumSize; - } - } - const style = React.useMemo(() => { if (isNotVisible) { return { @@ -282,28 +262,28 @@ const GridRow = React.forwardRef(function GridRow( const rowStyle = { ...styleProp, maxHeight: rowHeight === 'auto' ? 'none' : rowHeight, // max-height doesn't support "auto" - minHeight, + minHeight: rowHeight, '--height': typeof rowHeight === 'number' ? `${rowHeight}px` : rowHeight, }; - if (sizes?.spacingTop) { + if (heightEntry.spacingTop) { const property = rootProps.rowSpacingType === 'border' ? 'borderTopWidth' : 'marginTop'; - rowStyle[property] = sizes.spacingTop; + rowStyle[property] = heightEntry.spacingTop; } - if (sizes?.spacingBottom) { + if (heightEntry.spacingBottom) { const property = rootProps.rowSpacingType === 'border' ? 'borderBottomWidth' : 'marginBottom'; let propertyValue = rowStyle[property]; // avoid overriding existing value if (typeof propertyValue !== 'number') { propertyValue = parseInt(propertyValue || '0', 10); } - propertyValue += sizes.spacingBottom; + propertyValue += heightEntry.spacingBottom; rowStyle[property] = propertyValue; } return rowStyle; - }, [isNotVisible, rowHeight, styleProp, minHeight, sizes, rootProps.rowSpacingType]); + }, [isNotVisible, rowHeight, styleProp, heightEntry, rootProps.rowSpacingType]); const rowClassNames = apiRef.current.unstable_applyPipeProcessors('rowClassName', [], rowId); const ariaAttributes = rowNode ? getRowAriaAttributes(rowNode, index) : undefined; @@ -439,12 +419,18 @@ const GridRow = React.forwardRef(function GridRow( ), ); } + for (let i = renderContext.firstColumnIndex; i < renderContext.lastColumnIndex; i += 1) { const column = visibleColumns[i]; const indexInSection = i - pinnedColumns.left.length; + if (!column) { + continue; + } + cells.push(getCell(column, indexInSection, i, middleColumnsLength)); } + if (hasVirtualFocusCellRight) { cells.push( getCell( diff --git a/packages/x-data-grid/src/components/base/GridOverlays.tsx b/packages/x-data-grid/src/components/base/GridOverlays.tsx index e21561bcdf273..b8b88d17eaa8b 100644 --- a/packages/x-data-grid/src/components/base/GridOverlays.tsx +++ b/packages/x-data-grid/src/components/base/GridOverlays.tsx @@ -3,13 +3,12 @@ import PropTypes from 'prop-types'; import { styled } from '@mui/system'; import composeClasses from '@mui/utils/composeClasses'; import clsx from 'clsx'; +import { minimalContentHeight } from '../../hooks/features/rows/gridRowsUtils'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { gridDimensionsSelector } from '../../hooks/features/dimensions'; import { GridOverlayType } from '../../hooks/features/overlays/useGridOverlays'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; -import { useGridVisibleRows } from '../../hooks/utils/useGridVisibleRows'; -import { getMinimalContentHeight } from '../../hooks/features/rows/gridRowsUtils'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { GridLoadingOverlayVariant } from '../GridLoadingOverlay'; @@ -29,7 +28,7 @@ const GridOverlayWrapperRoot = styled('div', { loadingOverlayVariant !== 'skeleton' ? { position: 'sticky', // To stay in place while scrolling - top: 'var(--DataGrid-headersTotalHeight)', + top: 'var(--DataGrid-headersTotalHeight)', // TODO: take pinned rows into account left: 0, width: 0, // To stay above the content instead of shifting it down height: 0, // To stay above the content instead of shifting it down @@ -64,17 +63,18 @@ const useUtilityClasses = (ownerState: OwnerState) => { function GridOverlayWrapper(props: React.PropsWithChildren) { const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); - const currentPage = useGridVisibleRows(apiRef, rootProps); const dimensions = useGridSelector(apiRef, gridDimensionsSelector); - let height: React.CSSProperties['height'] = + let height: React.CSSProperties['height'] = Math.max( dimensions.viewportOuterSize.height - - dimensions.topContainerHeight - - dimensions.bottomContainerHeight - - (dimensions.hasScrollX ? dimensions.scrollbarSize : 0); + dimensions.topContainerHeight - + dimensions.bottomContainerHeight - + (dimensions.hasScrollX ? dimensions.scrollbarSize : 0), + 0, + ); - if ((rootProps.autoHeight && currentPage.rows.length === 0) || height === 0) { - height = getMinimalContentHeight(apiRef); + if (height === 0) { + height = minimalContentHeight; } const classes = useUtilityClasses({ ...props, classes: rootProps.classes }); @@ -93,6 +93,15 @@ function GridOverlayWrapper(props: React.PropsWithChildren) { ); } +GridOverlayWrapper.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + loadingOverlayVariant: PropTypes.oneOf(['circular-progress', 'linear-progress', 'skeleton']), + overlayType: PropTypes.oneOf(['loadingOverlay', 'noResultsOverlay', 'noRowsOverlay']), +} as any; + GridOverlays.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | diff --git a/packages/x-data-grid/src/components/cell/GridActionsCell.tsx b/packages/x-data-grid/src/components/cell/GridActionsCell.tsx index 39c20b5c639e2..5707b3939a5c1 100644 --- a/packages/x-data-grid/src/components/cell/GridActionsCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridActionsCell.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuList from '@mui/material/MenuList'; import { useRtl } from '@mui/system/RtlProvider'; import { unstable_useId as useId } from '@mui/utils'; import { GridRenderCellParams } from '../../models/params/gridCellParams'; @@ -224,7 +223,7 @@ function GridActionsCell(props: GridActionsCellProps) { {menuButtons.length > 0 && ( - React.cloneElement(button, { key: index, closeMenu: hideMenu }), )} - + )} diff --git a/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx b/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx index 3d39805c11f0c..2c0edaec52bb9 100644 --- a/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx +++ b/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { IconButtonProps } from '@mui/material/IconButton'; -import MenuItem, { MenuItemProps } from '@mui/material/MenuItem'; +import { MenuItemProps } from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -70,10 +70,10 @@ const GridActionsCellItem = React.forwardRef + {icon && {icon}} {label} - + ); }, ); diff --git a/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx b/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx index 543e199229c23..7bdd414d0ff85 100644 --- a/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx @@ -2,13 +2,16 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { SvgIconProps } from '@mui/material/SvgIcon'; import composeClasses from '@mui/utils/composeClasses'; +import { useGridSelector } from '../../hooks/utils/useGridSelector'; +import { gridRowMaximumTreeDepthSelector } from '../../hooks/features/rows/gridRowsSelector'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; -import { GridRenderCellParams } from '../../models/params/gridCellParams'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; -import { DataGridProcessedProps } from '../../models/props/DataGridProps'; -import { GridColDef } from '../../models/colDef/gridColDef'; import { isAutogeneratedRowNode } from '../../hooks/features/rows/gridRowsUtils'; +import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; +import type { GridColDef } from '../../models/colDef/gridColDef'; +import type { GridRenderCellParams } from '../../models/params/gridCellParams'; +import { GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD } from '../../internals/constants'; type OwnerState = { classes: DataGridProcessedProps['classes'] }; @@ -49,11 +52,20 @@ function GridBooleanCellRaw(props: GridBooleanCellProps) { const ownerState = { classes: rootProps.classes }; const classes = useUtilityClasses(ownerState); + const maxDepth = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector); + const isServerSideRowGroupingRow = + // @ts-expect-error - Access tree data prop + maxDepth > 0 && rowNode.type === 'group' && rootProps.treeData === false; + const Icon = React.useMemo( () => (value ? rootProps.slots.booleanCellTrueIcon : rootProps.slots.booleanCellFalseIcon), [rootProps.slots.booleanCellFalseIcon, rootProps.slots.booleanCellTrueIcon, value], ); + if (isServerSideRowGroupingRow && value === undefined) { + return null; + } + return ( { - if (params.field !== '__row_group_by_columns_group__' && isAutogeneratedRowNode(params.rowNode)) { + if ( + params.field !== GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD && + isAutogeneratedRowNode(params.rowNode) + ) { return ''; } diff --git a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx index d48bc0ccdfe2a..92641c45e46e8 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses, unstable_useId as useId } from '@mui/utils'; -import Badge from '@mui/material/Badge'; import { useGridSelector } from '../../hooks'; import { gridPreferencePanelStateSelector } from '../../hooks/features/preferencesPanel/gridPreferencePanelSelector'; import { GridPreferencePanelsValue } from '../../hooks/features/preferencesPanel/gridPreferencePanelsValue'; @@ -97,9 +96,9 @@ function GridColumnHeaderFilterIconButton(props: ColumnHeaderFilterIconButtonPro > {counter > 1 && ( - + {iconButton} - + )} {counter === 1 && iconButton} diff --git a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx index 367931b7e3c99..778579bcc0e35 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import composeClasses from '@mui/utils/composeClasses'; -import Badge from '@mui/material/Badge'; import { GridSlotsComponent } from '../../models/gridSlotsComponent'; import { GridSortDirection } from '../../models/gridSortModel'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; @@ -80,9 +79,9 @@ function GridColumnHeaderSortIconRaw(props: GridColumnHeaderSortIconProps) { return ( {index != null && ( - + {iconButton} - + )} {index == null && iconButton} diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index f66edba975eac..84cb0ec975ebf 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -130,6 +130,9 @@ export const GridRootStyles = styled('div', { { [`& .${c.treeDataGroupingCellLoadingContainer}`]: styles.treeDataGroupingCellLoadingContainer, }, + { + [`& .${c.groupingCriteriaCellLoadingContainer}`]: styles.groupingCriteriaCellLoadingContainer, + }, { [`& .${c.detailPanelToggleCell}`]: styles.detailPanelToggleCell }, { [`& .${c['detailPanelToggleCell--expanded']}`]: styles['detailPanelToggleCell--expanded'], @@ -535,6 +538,7 @@ export const GridRootStyles = styled('div', { /* Cell styles */ [`& .${c.cell}`]: { + flex: '0 0 auto', height: 'var(--height)', width: 'var(--width)', lineHeight: 'calc(var(--height) - 1px)', // -1px for the border @@ -707,7 +711,7 @@ export const GridRootStyles = styled('div', { alignSelf: 'stretch', marginRight: t.spacing(2), }, - [`& .${c.treeDataGroupingCellLoadingContainer}`]: { + [`& .${c.treeDataGroupingCellLoadingContainer}, .${c.groupingCriteriaCellLoadingContainer}`]: { display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -742,7 +746,7 @@ export const GridRootStyles = styled('div', { }, [`& .${c.filler}`]: { - flex: 1, + flex: '1 0 auto', }, [`& .${c['filler--borderBottom']}`]: { borderBottom: '1px solid var(--DataGrid-rowBorderColor)', diff --git a/packages/x-data-grid/src/components/menu/columnMenu/GridColumnMenu.tsx b/packages/x-data-grid/src/components/menu/columnMenu/GridColumnMenu.tsx index 3842fe844f930..b5a94ecc60df2 100644 --- a/packages/x-data-grid/src/components/menu/columnMenu/GridColumnMenu.tsx +++ b/packages/x-data-grid/src/components/menu/columnMenu/GridColumnMenu.tsx @@ -42,6 +42,39 @@ const GridGenericColumnMenu = React.forwardRef( function GridColumnMenu(props, ref) { return ( diff --git a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuFilterItem.tsx b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuFilterItem.tsx index d86f61e78098a..f7db300a230c5 100644 --- a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuFilterItem.tsx +++ b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuFilterItem.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { useGridApiContext } from '../../../../hooks/utils/useGridApiContext'; @@ -25,12 +24,12 @@ function GridColumnMenuFilterItem(props: GridColumnMenuItemProps) { } return ( - + {apiRef.current.getLocaleText('columnMenuFilter')} - + ); } diff --git a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuHideItem.tsx b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuHideItem.tsx index ce76609293123..f6594765ba051 100644 --- a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuHideItem.tsx +++ b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuHideItem.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { GridColumnMenuItemProps } from '../GridColumnMenuItemProps'; @@ -43,12 +42,12 @@ function GridColumnMenuHideItem(props: GridColumnMenuItemProps) { } return ( - + {apiRef.current.getLocaleText('columnMenuHideColumn')} - + ); } diff --git a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuManageItem.tsx b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuManageItem.tsx index 860751420ddaf..df95549cbf30a 100644 --- a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuManageItem.tsx +++ b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuManageItem.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { GridPreferencePanelsValue } from '../../../../hooks/features/preferencesPanel/gridPreferencePanelsValue'; @@ -26,12 +25,12 @@ function GridColumnMenuManageItem(props: GridColumnMenuItemProps) { } return ( - + {apiRef.current.getLocaleText('columnMenuManageColumns')} - + ); } diff --git a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuSortItem.tsx b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuSortItem.tsx index de67a637774ec..7ac4c77444da5 100644 --- a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuSortItem.tsx +++ b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuSortItem.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { useGridSelector } from '../../../../hooks/utils/useGridSelector'; @@ -55,26 +54,26 @@ function GridColumnMenuSortItem(props: GridColumnMenuItemProps) { return ( {sortingOrder.includes('asc') && sortDirection !== 'asc' ? ( - + {getLabel('columnMenuSortAsc')} - + ) : null} {sortingOrder.includes('desc') && sortDirection !== 'desc' ? ( - + {getLabel('columnMenuSortDesc')} - + ) : null} {sortingOrder.includes(null) && sortDirection != null ? ( - + {apiRef.current.getLocaleText('columnMenuUnsort')} - + ) : null} ); diff --git a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx index 89d0a2114e331..c0f6ae6818ddf 100644 --- a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx +++ b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx @@ -16,6 +16,16 @@ export type GridFilterInputBooleanProps = GridFilterInputValueProps & isFilterActive?: boolean; }; +export const sanitizeFilterItemValue = (value: any): boolean | undefined => { + if (String(value).toLowerCase() === 'true') { + return true; + } + if (String(value).toLowerCase() === 'false') { + return false; + } + return undefined; +}; + const BooleanOperatorContainer = styled('div')({ display: 'flex', alignItems: 'center', @@ -39,7 +49,9 @@ function GridFilterInputBoolean(props: GridFilterInputBooleanProps) { InputLabelProps, ...others } = props; - const [filterValueState, setFilterValueState] = React.useState(item.value || ''); + const [filterValueState, setFilterValueState] = React.useState( + sanitizeFilterItemValue(item.value), + ); const rootProps = useGridRootProps(); const labelId = useId(); @@ -52,15 +64,16 @@ function GridFilterInputBoolean(props: GridFilterInputBooleanProps) { const onFilterChange = React.useCallback( (event: React.ChangeEvent) => { - const value = event.target.value; + const value = sanitizeFilterItemValue(event.target.value); setFilterValueState(value); + applyValue({ ...item, value }); }, [applyValue, item], ); React.useEffect(() => { - setFilterValueState(item.value || ''); + setFilterValueState(sanitizeFilterItemValue(item.value)); }, [item.value]); const label = labelProp ?? apiRef.current.getLocaleText('filterPanelInputLabel'); @@ -80,7 +93,7 @@ function GridFilterInputBoolean(props: GridFilterInputBooleanProps) { labelId={labelId} id={selectId} label={label} - value={filterValueState} + value={filterValueState === undefined ? '' : String(filterValueState)} onChange={onFilterChange} variant={variant} notched={variant === 'outlined' ? true : undefined} diff --git a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleValue.tsx b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleValue.tsx index 9fc87274ac2b0..50da622479b8e 100644 --- a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleValue.tsx +++ b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleValue.tsx @@ -47,9 +47,17 @@ function GridFilterInputMultipleValue(props: GridFilterInputMultipleValueProps) >( (event, value) => { setFilterValueState(value.map(String)); - applyValue({ ...item, value: [...value] }); + + applyValue({ + ...item, + value: [ + ...value.map((filterItemValue) => + type === 'number' ? Number(filterItemValue) : filterItemValue, + ), + ], + }); }, - [applyValue, item], + [applyValue, item, type], ); return ( diff --git a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx index a16cc1a850dde..4391eed4166d3 100644 --- a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx +++ b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx @@ -48,12 +48,16 @@ function GridFilterInputValue(props: GridTypeFilterInputValueProps) { setIsApplying(true); filterTimeout.start(rootProps.filterDebounceMs, () => { - const newItem = { ...item, value, fromInput: id! }; + const newItem = { + ...item, + value: type === 'number' ? Number(value) : value, + fromInput: id!, + }; applyValue(newItem); setIsApplying(false); }); }, - [id, applyValue, item, rootProps.filterDebounceMs, filterTimeout], + [filterTimeout, rootProps.filterDebounceMs, item, type, id, applyValue], ); React.useEffect(() => { diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbar.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbar.tsx index 216335a99ba3e..260f224372102 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbar.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbar.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import Box from '@mui/material/Box'; import { GridToolbarContainer, GridToolbarContainerProps, @@ -61,7 +60,7 @@ const GridToolbar = React.forwardRef( // TODO: remove the reference to excelOptions in community package excelOptions={excelOptions} /> - +
{showQuickFilter && } ); diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx index 67a176845fac0..58feb8f10f10c 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import useId from '@mui/utils/useId'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; -import { unstable_useId as useId } from '@mui/material/utils'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { gridPreferencePanelStateSelector } from '../../hooks/features/preferencesPanel/gridPreferencePanelSelector'; import { GridPreferencePanelsValue } from '../../hooks/features/preferencesPanel/gridPreferencePanelsValue'; diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx index 5e185987fa160..d6c952c6f543c 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx @@ -1,10 +1,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { unstable_useId as useId, unstable_useForkRef as useForkRef } from '@mui/utils'; -import MenuList from '@mui/material/MenuList'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import { gridDensitySelector } from '../../hooks/features/density/densitySelector'; import { GridDensity } from '../../models/gridDensity'; @@ -97,14 +95,14 @@ const GridToolbarDensitySelector = React.forwardRef< } const densityElements = densityOptions.map((option, index) => ( - handleDensityUpdate(option.value)} selected={option.value === density} > {option.icon} {option.label} - + )); return ( @@ -137,7 +135,7 @@ const GridToolbarDensitySelector = React.forwardRef< onClose={handleDensitySelectorClose} position="bottom-start" > - {densityElements} - + ); diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx index 855233ca9eccf..fada5a9866365 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; -import MenuItem from '@mui/material/MenuItem'; +import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { GridCsvExportOptions, GridPrintExportOptions } from '../../models/gridExport'; import { GridToolbarExportContainer } from './GridToolbarExportContainer'; @@ -35,12 +35,13 @@ export interface GridToolbarExportProps { [key: string]: any; } -export function GridCsvExportMenuItem(props: GridCsvExportMenuItemProps) { +function GridCsvExportMenuItem(props: GridCsvExportMenuItemProps) { const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); const { hideMenu, options, ...other } = props; return ( - { apiRef.current.exportDataAsCsv(options); hideMenu?.(); @@ -48,16 +49,38 @@ export function GridCsvExportMenuItem(props: GridCsvExportMenuItemProps) { {...other} > {apiRef.current.getLocaleText('toolbarExportCSV')} - + ); } -export function GridPrintExportMenuItem(props: GridPrintExportMenuItemProps) { +GridCsvExportMenuItem.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + hideMenu: PropTypes.func, + options: PropTypes.shape({ + allColumns: PropTypes.bool, + delimiter: PropTypes.string, + disableToolbarButton: PropTypes.bool, + escapeFormulas: PropTypes.bool, + fields: PropTypes.arrayOf(PropTypes.string), + fileName: PropTypes.string, + getRowsToExport: PropTypes.func, + includeColumnGroupsHeaders: PropTypes.bool, + includeHeaders: PropTypes.bool, + shouldAppendQuotes: PropTypes.bool, + utf8WithBom: PropTypes.bool, + }), +} as any; + +function GridPrintExportMenuItem(props: GridPrintExportMenuItemProps) { const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); const { hideMenu, options, ...other } = props; return ( - { apiRef.current.exportDataAsPrint(options); hideMenu?.(); @@ -65,10 +88,31 @@ export function GridPrintExportMenuItem(props: GridPrintExportMenuItemProps) { {...other} > {apiRef.current.getLocaleText('toolbarExportPrint')} - + ); } +GridPrintExportMenuItem.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + hideMenu: PropTypes.func, + options: PropTypes.shape({ + allColumns: PropTypes.bool, + bodyClassName: PropTypes.string, + copyStyles: PropTypes.bool, + disableToolbarButton: PropTypes.bool, + fields: PropTypes.arrayOf(PropTypes.string), + fileName: PropTypes.string, + getRowsToExport: PropTypes.func, + hideFooter: PropTypes.bool, + hideToolbar: PropTypes.bool, + includeCheckboxes: PropTypes.bool, + pageStyle: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + }), +} as any; + const GridToolbarExport = React.forwardRef( function GridToolbarExport(props, ref) { const { csvOptions = {}, printOptions = {}, excelOptions, ...other } = props; @@ -107,4 +151,4 @@ GridToolbarExport.propTypes = { slotProps: PropTypes.object, } as any; -export { GridToolbarExport }; +export { GridToolbarExport, GridCsvExportMenuItem, GridPrintExportMenuItem }; diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx index 2c00e4ba0b8b0..df889bd02870b 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { unstable_useId as useId, unstable_useForkRef as useForkRef } from '@mui/utils'; -import MenuList from '@mui/material/MenuList'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; import { isHideMenuKey } from '../../utils/keyboardUtils'; @@ -85,7 +84,7 @@ const GridToolbarExportContainer = React.forwardRef< onClose={handleMenuClose} position="bottom-start" > - (child, { hideMenu: handleMenuClose }); })} - + ); diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx index 2e286a44519d1..5f7bbb6f473f7 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx @@ -6,7 +6,6 @@ import { unstable_capitalize as capitalize, unstable_useId as useId, } from '@mui/utils'; -import Badge from '@mui/material/Badge'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; import { gridColumnLookupSelector } from '../../hooks/features/columns/gridColumnsSelector'; @@ -144,9 +143,9 @@ const GridToolbarFilterButton = React.forwardRef + - + } {...buttonProps} onClick={toggleFilter} diff --git a/packages/x-data-grid/src/components/virtualization/GridBottomContainer.tsx b/packages/x-data-grid/src/components/virtualization/GridBottomContainer.tsx index ca0c0d5179eb3..cc2a9c8c1789f 100644 --- a/packages/x-data-grid/src/components/virtualization/GridBottomContainer.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridBottomContainer.tsx @@ -3,9 +3,6 @@ import clsx from 'clsx'; import { styled } from '@mui/system'; import composeClasses from '@mui/utils/composeClasses'; import { gridClasses, getDataGridUtilityClass } from '../../constants/gridClasses'; -import { gridDimensionsSelector } from '../../hooks/features/dimensions/gridDimensionsSelectors'; -import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; -import { useGridSelector } from '../../hooks/utils/useGridSelector'; const useUtilityClasses = () => { const slots = { @@ -23,25 +20,10 @@ const Element = styled('div')({ export function GridBottomContainer(props: React.PropsWithChildren) { const classes = useUtilityClasses(); - const apiRef = useGridApiContext(); - const { viewportOuterSize, minimumSize, hasScrollX, scrollbarSize } = useGridSelector( - apiRef, - gridDimensionsSelector, - ); - const scrollHeight = hasScrollX ? scrollbarSize : 0; - const offset = Math.max( - viewportOuterSize.height - - minimumSize.height - - // Subtract scroll height twice to account for GridVirtualScrollerFiller and horizontal scrollbar - 2 * scrollHeight, - 0, - ); - return ( ); diff --git a/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx b/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx index c8b4ca5e33bef..70d727b3afd5d 100644 --- a/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx @@ -8,6 +8,7 @@ const GridPanelAnchor = styled('div')({ position: 'absolute', top: `var(--DataGrid-headersTotalHeight)`, left: 0, + width: 'calc(100% - (var(--DataGrid-hasScrollY) * var(--DataGrid-scrollbarSize)))', }); type OwnerState = DataGridProcessedProps; diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollbar.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollbar.tsx index 0c85eec856130..ba3534bfbc79f 100644 --- a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollbar.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollbar.tsx @@ -103,6 +103,8 @@ const GridVirtualScrollbar = React.forwardRef { @@ -154,6 +154,11 @@ const GridVirtualScrollbar = React.forwardRef