diff --git a/.eslintrc.js b/.eslintrc.js
index b5d903b7ff..a1e99d0f51 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -23,6 +23,7 @@ module.exports = {
...baseline.rules,
// TODO move to @mui/monorepo, codebase is moving away from default exports https://github.com/mui/material-ui/issues/21862
'import/prefer-default-export': 'off',
+ 'import/export': 'off', // Mostly handled by Typescript itself. ESLint produces false positives with declaration merging.
'no-restricted-imports': [
'error',
{
@@ -43,6 +44,7 @@ module.exports = {
],
},
],
+ '@typescript-eslint/no-redeclare': 'off',
},
overrides: [
...baseline.overrides,
diff --git a/docs/.link-check-errors.txt b/docs/.link-check-errors.txt
index 0b8d083f4a..1d822a9dde 100644
--- a/docs/.link-check-errors.txt
+++ b/docs/.link-check-errors.txt
@@ -1,2 +1,8 @@
Broken links found by `pnpm docs:link-check` that exist:
+- https://mui.com/material-ui/customization/css-theme-variables/configuration/#advanced-configuration
+- https://mui.com/material-ui/customization/css-theme-variables/configuration/#changing-variable-prefixes
+- https://mui.com/material-ui/customization/theme-components/#creating-new-component-variants
+- https://mui.com/material-ui/customization/theme-components/#overrides-based-on-props
+- https://mui.com/material-ui/migrating-to-v6/
+- https://mui.com/material-ui/react-grid2/#whats-changed
diff --git a/docs/data/base/components/menu/MenuIntroduction/css/index.js b/docs/data/base/components/menu/MenuIntroduction/css/index.js
new file mode 100644
index 0000000000..b30fd457ef
--- /dev/null
+++ b/docs/data/base/components/menu/MenuIntroduction/css/index.js
@@ -0,0 +1,174 @@
+import * as React from 'react';
+import * as Menu from '@base_ui/react/Menu';
+import { useTheme } from '@mui/system';
+
+export default function MenuIntroduction() {
+ const createHandleMenuClick = (menuItem) => {
+ return () => {
+ console.log(`Clicked on ${menuItem}`);
+ };
+ };
+
+ return (
+
+ My account
+
+
+
+
+ Profile
+
+
+ Language settings
+
+
+ Log out
+
+
+
+
+
+ );
+}
+
+const cyan = {
+ 50: '#E9F8FC',
+ 100: '#BDEBF4',
+ 200: '#99D8E5',
+ 300: '#66BACC',
+ 400: '#1F94AD',
+ 500: '#0D5463',
+ 600: '#094855',
+ 700: '#063C47',
+ 800: '#043039',
+ 900: '#022127',
+};
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+function Styles() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/menu/MenuIntroduction/css/index.tsx b/docs/data/base/components/menu/MenuIntroduction/css/index.tsx
new file mode 100644
index 0000000000..dccfabe449
--- /dev/null
+++ b/docs/data/base/components/menu/MenuIntroduction/css/index.tsx
@@ -0,0 +1,174 @@
+import * as React from 'react';
+import * as Menu from '@base_ui/react/Menu';
+import { useTheme } from '@mui/system';
+
+export default function MenuIntroduction() {
+ const createHandleMenuClick = (menuItem: string) => {
+ return () => {
+ console.log(`Clicked on ${menuItem}`);
+ };
+ };
+
+ return (
+
+ My account
+
+
+
+
+ Profile
+
+
+ Language settings
+
+
+ Log out
+
+
+
+
+
+ );
+}
+
+const cyan = {
+ 50: '#E9F8FC',
+ 100: '#BDEBF4',
+ 200: '#99D8E5',
+ 300: '#66BACC',
+ 400: '#1F94AD',
+ 500: '#0D5463',
+ 600: '#094855',
+ 700: '#063C47',
+ 800: '#043039',
+ 900: '#022127',
+};
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+function Styles() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/menu/MenuIntroduction/system/index.js b/docs/data/base/components/menu/MenuIntroduction/system/index.js
new file mode 100644
index 0000000000..1b4655a3f5
--- /dev/null
+++ b/docs/data/base/components/menu/MenuIntroduction/system/index.js
@@ -0,0 +1,185 @@
+import * as React from 'react';
+import * as Menu from '@base_ui/react/Menu';
+import { styled } from '@mui/system';
+
+export default function MenuIntroduction() {
+ const createHandleMenuClick = (menuItem) => {
+ return () => {
+ console.log(`Clicked on ${menuItem}`);
+ };
+ };
+
+ return (
+
+ My account
+
+
+ Profile
+
+ Language settings
+
+ Log out
+
+
+
+
+ );
+}
+
+const blue = {
+ 50: '#F0F7FF',
+ 100: '#C2E0FF',
+ 200: '#99CCF3',
+ 300: '#66B2FF',
+ 400: '#3399FF',
+ 500: '#007FFF',
+ 600: '#0072E6',
+ 700: '#0059B3',
+ 800: '#004C99',
+ 900: '#003A75',
+};
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+const MenuPopup = styled(Menu.Popup)(
+ ({ theme }) => `
+ position: relative;
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-size: 0.875rem;
+ box-sizing: border-box;
+ padding: 6px;
+ min-width: 200px;
+ border-radius: 12px;
+ outline: 0;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ box-shadow: 0px 4px 30px ${theme.palette.mode === 'dark' ? grey[900] : grey[200]};
+ z-index: 1;
+ transform-origin: var(--transform-origin);
+ opacity: 1;
+ transform: scale(1, 1);
+ transition: opacity 100ms ease-in, transform 100ms ease-in;
+
+ @starting-style {
+ & {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+ }
+
+ &[data-exiting] {
+ opacity: 0;
+ transform: scale(0.8);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+ }
+ `,
+);
+
+const MenuItem = styled(Menu.Item)(
+ ({ theme }) => `
+ list-style: none;
+ padding: 8px;
+ border-radius: 8px;
+ cursor: default;
+ user-select: none;
+
+ &:last-of-type {
+ border-bottom: none;
+ }
+
+ &:focus {
+ outline: 3px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
+ background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ }
+
+ &.[data-disabled] {
+ color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
+ }
+ `,
+);
+
+const MenuButton = styled(Menu.Trigger)(
+ ({ theme }) => `
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-weight: 600;
+ font-size: 0.875rem;
+ line-height: 1.5;
+ padding: 8px 16px;
+ border-radius: 8px;
+ color: white;
+ transition: all 150ms ease;
+ cursor: pointer;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ color: ${theme.palette.mode === 'dark' ? grey[200] : grey[900]};
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+
+ &:hover {
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};
+ border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]};
+ }
+
+ &:active {
+ background: ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ }
+
+ &:focus-visible {
+ box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
+ outline: none;
+ }
+ `,
+);
+
+const MenuPositioner = styled(Menu.Positioner)`
+ &:focus-visible {
+ outline: 0;
+ }
+
+ &[data-state='closed'] {
+ pointer-events: none;
+ }
+`;
+
+export const MenuArrow = styled(Menu.Arrow)(
+ ({ theme }) => `
+ width: 10px;
+ height: 10px;
+ transform: rotate(45deg);
+ background: white;
+ z-index: -1;
+ border: 1px ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+
+ &[data-side='top'] {
+ border-style: none solid solid none;
+ bottom: -6px;
+ }
+
+ &[data-side='right'] {
+ border-style: none none solid solid;
+ left: -6px;
+ }
+
+ &[data-side='bottom'] {
+ border-style: solid none none solid;
+ top: -6px;
+ }
+
+ &[data-side='left'] {
+ border-style: solid solid none none;
+ right: -6px;
+ }
+`,
+);
diff --git a/docs/data/base/components/menu/MenuIntroduction/system/index.tsx b/docs/data/base/components/menu/MenuIntroduction/system/index.tsx
new file mode 100644
index 0000000000..0ba533b00d
--- /dev/null
+++ b/docs/data/base/components/menu/MenuIntroduction/system/index.tsx
@@ -0,0 +1,185 @@
+import * as React from 'react';
+import * as Menu from '@base_ui/react/Menu';
+import { styled } from '@mui/system';
+
+export default function MenuIntroduction() {
+ const createHandleMenuClick = (menuItem: string) => {
+ return () => {
+ console.log(`Clicked on ${menuItem}`);
+ };
+ };
+
+ return (
+
+ My account
+
+
+ Profile
+
+ Language settings
+
+ Log out
+
+
+
+
+ );
+}
+
+const blue = {
+ 50: '#F0F7FF',
+ 100: '#C2E0FF',
+ 200: '#99CCF3',
+ 300: '#66B2FF',
+ 400: '#3399FF',
+ 500: '#007FFF',
+ 600: '#0072E6',
+ 700: '#0059B3',
+ 800: '#004C99',
+ 900: '#003A75',
+};
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+const MenuPopup = styled(Menu.Popup)(
+ ({ theme }) => `
+ position: relative;
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-size: 0.875rem;
+ box-sizing: border-box;
+ padding: 6px;
+ min-width: 200px;
+ border-radius: 12px;
+ outline: 0;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ box-shadow: 0px 4px 30px ${theme.palette.mode === 'dark' ? grey[900] : grey[200]};
+ z-index: 1;
+ transform-origin: var(--transform-origin);
+ opacity: 1;
+ transform: scale(1, 1);
+ transition: opacity 100ms ease-in, transform 100ms ease-in;
+
+ @starting-style {
+ & {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+ }
+
+ &[data-exiting] {
+ opacity: 0;
+ transform: scale(0.8);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+ }
+ `,
+);
+
+const MenuItem = styled(Menu.Item)(
+ ({ theme }) => `
+ list-style: none;
+ padding: 8px;
+ border-radius: 8px;
+ cursor: default;
+ user-select: none;
+
+ &:last-of-type {
+ border-bottom: none;
+ }
+
+ &:focus {
+ outline: 3px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
+ background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ }
+
+ &.[data-disabled] {
+ color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
+ }
+ `,
+);
+
+const MenuButton = styled(Menu.Trigger)(
+ ({ theme }) => `
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-weight: 600;
+ font-size: 0.875rem;
+ line-height: 1.5;
+ padding: 8px 16px;
+ border-radius: 8px;
+ color: white;
+ transition: all 150ms ease;
+ cursor: pointer;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ color: ${theme.palette.mode === 'dark' ? grey[200] : grey[900]};
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+
+ &:hover {
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};
+ border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]};
+ }
+
+ &:active {
+ background: ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ }
+
+ &:focus-visible {
+ box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
+ outline: none;
+ }
+ `,
+);
+
+const MenuPositioner = styled(Menu.Positioner)`
+ &:focus-visible {
+ outline: 0;
+ }
+
+ &[data-state='closed'] {
+ pointer-events: none;
+ }
+`;
+
+export const MenuArrow = styled(Menu.Arrow)(
+ ({ theme }) => `
+ width: 10px;
+ height: 10px;
+ transform: rotate(45deg);
+ background: white;
+ z-index: -1;
+ border: 1px ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+
+ &[data-side='top'] {
+ border-style: none solid solid none;
+ bottom: -6px;
+ }
+
+ &[data-side='right'] {
+ border-style: none none solid solid;
+ left: -6px;
+ }
+
+ &[data-side='bottom'] {
+ border-style: solid none none solid;
+ top: -6px;
+ }
+
+ &[data-side='left'] {
+ border-style: solid solid none none;
+ right: -6px;
+ }
+`,
+);
diff --git a/docs/data/base/components/menu/MenuIntroduction/system/index.tsx.preview b/docs/data/base/components/menu/MenuIntroduction/system/index.tsx.preview
new file mode 100644
index 0000000000..4e68fd1f26
--- /dev/null
+++ b/docs/data/base/components/menu/MenuIntroduction/system/index.tsx.preview
@@ -0,0 +1,13 @@
+
+ My account
+
+
+ Profile
+
+ Language settings
+
+ Log out
+
+
+
+
\ No newline at end of file
diff --git a/docs/data/base/components/menu/MenuIntroduction/tailwind/index.js b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.js
new file mode 100644
index 0000000000..08751452b6
--- /dev/null
+++ b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.js
@@ -0,0 +1,75 @@
+import * as React from 'react';
+import * as Menu from '@base_ui/react/Menu';
+import { useTheme } from '@mui/system';
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+export default function MenuIntroduction() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+
+ const createHandleMenuClick = (menuItem) => {
+ return () => {
+ console.log(`Clicked on ${menuItem}`);
+ };
+ };
+
+ return (
+
+
+ My account
+
+
+ Profile
+
+ Language settings
+
+ Log out
+
+
+
+
+ );
+}
+
+const MenuPopup = React.forwardRef((props, ref) => {
+ const classes = `
+ text-sm box-border font-sans p-1.5 my-3 mx-0 rounded-xl overflow-auto outline-0
+ bg-white dark:bg-slate-900 border border-solid border-slate-200 dark:border-slate-700 text-slate-900 dark:text-slate-300
+ min-w-listbox shadow-md dark:shadow-slate-900
+ [.open_&]:opacity-100 [.open_&]:scale-100 [.closed_&]:opacity-0 [.closed_&]:scale-90
+ transition-[opacity,transform] [.placement-top_&]:origin-bottom [.placement-bottom_&]:origin-top`;
+
+ return ;
+});
+
+const MenuButton = React.forwardRef((props, ref) => {
+ const classes = `
+ cursor-pointer text-sm font-sans box-border rounded-lg font-semibold px-4 py-2 bg-white dark:bg-slate-900
+ border border-solid border-slate-200 dark:border-slate-700 text-slate-900 dark:text-slate-200 hover:bg-slate-50 hover:dark:bg-slate-800 hover:border-slate-300 dark:hover:border-slate-600
+ focus-visible:shadow-[0_0_0_4px_#ddd6fe] dark:focus-visible:shadow-[0_0_0_4px_#a78bfa] focus-visible:outline-none shadow-sm
+ `;
+
+ return ;
+});
+
+const MenuItem = React.forwardRef((props, ref) => {
+ const classes = `
+ list-none p-2 rounded-lg cursor-default select-none last-of-type:border-b-0 focus:shadow-outline-purple
+ focus:outline-0 focus:bg-slate-100 focus:dark:bg-slate-800 focus:text-slate-900 focus:dark:text-slate-300 disabled:text-slate-400 disabled:dark:text-slate-700 disabled:hover:text-slate-400 disabled:hover:dark:text-slate-700
+ `;
+ return ;
+});
+
+const MenuPositioner = React.forwardRef((props, ref) => {
+ return (
+
+ );
+});
diff --git a/docs/data/base/components/menu/MenuIntroduction/tailwind/index.tsx b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.tsx
new file mode 100644
index 0000000000..6bf012ba31
--- /dev/null
+++ b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.tsx
@@ -0,0 +1,81 @@
+import * as React from 'react';
+import * as Menu from '@base_ui/react/Menu';
+import { useTheme } from '@mui/system';
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+export default function MenuIntroduction() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+
+ const createHandleMenuClick = (menuItem: string) => {
+ return () => {
+ console.log(`Clicked on ${menuItem}`);
+ };
+ };
+
+ return (
+
+
+ My account
+
+
+ Profile
+
+ Language settings
+
+ Log out
+
+
+
+
+ );
+}
+
+const MenuPopup = React.forwardRef(
+ (props, ref) => {
+ const classes = `
+ text-sm box-border font-sans p-1.5 my-3 mx-0 rounded-xl overflow-auto outline-0
+ bg-white dark:bg-slate-900 border border-solid border-slate-200 dark:border-slate-700 text-slate-900 dark:text-slate-300
+ min-w-listbox shadow-md dark:shadow-slate-900
+ [.open_&]:opacity-100 [.open_&]:scale-100 [.closed_&]:opacity-0 [.closed_&]:scale-90
+ transition-[opacity,transform] [.placement-top_&]:origin-bottom [.placement-bottom_&]:origin-top`;
+
+ return ;
+ },
+);
+
+const MenuButton = React.forwardRef(
+ (props, ref) => {
+ const classes = `
+ cursor-pointer text-sm font-sans box-border rounded-lg font-semibold px-4 py-2 bg-white dark:bg-slate-900
+ border border-solid border-slate-200 dark:border-slate-700 text-slate-900 dark:text-slate-200 hover:bg-slate-50 hover:dark:bg-slate-800 hover:border-slate-300 dark:hover:border-slate-600
+ focus-visible:shadow-[0_0_0_4px_#ddd6fe] dark:focus-visible:shadow-[0_0_0_4px_#a78bfa] focus-visible:outline-none shadow-sm
+ `;
+
+ return ;
+ },
+);
+
+const MenuItem = React.forwardRef((props, ref) => {
+ const classes = `
+ list-none p-2 rounded-lg cursor-default select-none last-of-type:border-b-0 focus:shadow-outline-purple
+ focus:outline-0 focus:bg-slate-100 focus:dark:bg-slate-800 focus:text-slate-900 focus:dark:text-slate-300 disabled:text-slate-400 disabled:dark:text-slate-700 disabled:hover:text-slate-400 disabled:hover:dark:text-slate-700
+ `;
+ return ;
+});
+
+const MenuPositioner = React.forwardRef(
+ (props, ref) => {
+ return (
+
+ );
+ },
+);
diff --git a/docs/data/base/components/menu/MenuIntroduction/tailwind/index.tsx.preview b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.tsx.preview
new file mode 100644
index 0000000000..03ba6c96c4
--- /dev/null
+++ b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.tsx.preview
@@ -0,0 +1,12 @@
+
+ My account
+
+
+ Profile
+
+ Language settings
+
+ Log out
+
+
+
\ No newline at end of file
diff --git a/docs/data/base/components/menu/NestedMenu.js b/docs/data/base/components/menu/NestedMenu.js
new file mode 100644
index 0000000000..7dc96d13fe
--- /dev/null
+++ b/docs/data/base/components/menu/NestedMenu.js
@@ -0,0 +1,261 @@
+import * as React from 'react';
+import * as Menu from '@base_ui/react/Menu';
+import { styled } from '@mui/system';
+
+export default function NestedMenu() {
+ const createHandleMenuClick = (menuItem) => {
+ return () => {
+ console.log(`Clicked on ${menuItem}`);
+ };
+ };
+
+ return (
+
+ Format
+
+
+
+ Text color
+
+
+
+ Black
+
+
+ Dark grey
+
+
+ Accent
+
+
+
+
+
+
+ Style
+
+
+
+ Heading
+
+
+
+ Level 1
+
+
+ Level 2
+
+
+ Level 3
+
+
+
+
+
+ Paragraph
+
+
+ List
+
+
+
+ Ordered
+
+
+ Unordered
+
+
+
+
+
+
+
+
+
+ Clear formatting
+
+
+
+
+ );
+}
+
+const blue = {
+ 50: '#F0F7FF',
+ 100: '#C2E0FF',
+ 200: '#99CCF3',
+ 300: '#66B2FF',
+ 400: '#3399FF',
+ 500: '#007FFF',
+ 600: '#0072E6',
+ 700: '#0059B3',
+ 800: '#004C99',
+ 900: '#003A75',
+};
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+const MenuPopup = styled(Menu.Popup)(
+ ({ theme }) => `
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-size: 0.875rem;
+ box-sizing: border-box;
+ padding: 6px;
+ margin: 12px 0;
+ min-width: 200px;
+ border-radius: 12px;
+ overflow: auto;
+ outline: 0;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ box-shadow: 0px 4px 30px ${theme.palette.mode === 'dark' ? grey[900] : grey[200]};
+ z-index: 1;
+ transform-origin: var(--transform-origin);
+ opacity: 1;
+ transform: scale(1, 1);
+ transition: opacity 100ms ease-in, transform 100ms ease-in;
+
+ @starting-style {
+ & {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+ }
+
+ &[data-exiting] {
+ opacity: 0;
+ transform: scale(0.8);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+ }
+ `,
+);
+
+const MenuPositioner = styled(Menu.Positioner)`
+ &:focus-visible {
+ outline: 0;
+ }
+
+ &[data-state='closed'] {
+ pointer-events: none;
+ }
+`;
+
+const MenuItem = styled(Menu.Item)(
+ ({ theme }) => `
+ list-style: none;
+ padding: 8px;
+ border-radius: 8px;
+ cursor: default;
+ user-select: none;
+
+ &:last-of-type {
+ border-bottom: none;
+ }
+
+ &:focus,
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ }
+
+ &:focus-visible {
+ outline: none;
+ }
+
+ &[data-disabled] {
+ color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
+ }
+ `,
+);
+
+const SubmenuTrigger = styled(Menu.SubmenuTrigger)(
+ ({ theme }) => `
+ list-style: none;
+ padding: 8px;
+ border-radius: 8px;
+ cursor: default;
+ user-select: none;
+
+ &:last-of-type {
+ border-bottom: none;
+ }
+
+ &::after {
+ content: '›';
+ float: right;
+ }
+
+ &[data-state='open'] {
+ background-color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ }
+
+ &:focus,
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ }
+
+ &:focus-visible {
+ outline: none;
+ }
+
+ &[data-disabled] {
+ color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
+ }
+ `,
+);
+
+const MenuButton = styled(Menu.Trigger)(
+ ({ theme }) => `
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-weight: 600;
+ font-size: 0.875rem;
+ line-height: 1.5;
+ padding: 8px 16px;
+ border-radius: 8px;
+ color: white;
+ transition: all 150ms ease;
+ cursor: pointer;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ color: ${theme.palette.mode === 'dark' ? grey[200] : grey[900]};
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+
+ &:hover {
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};
+ border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]};
+ }
+
+ &:active {
+ background: ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ }
+
+ &:focus-visible {
+ box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
+ outline: none;
+ }
+ `,
+);
diff --git a/docs/data/base/components/menu/NestedMenu.tsx b/docs/data/base/components/menu/NestedMenu.tsx
new file mode 100644
index 0000000000..6c8efdb370
--- /dev/null
+++ b/docs/data/base/components/menu/NestedMenu.tsx
@@ -0,0 +1,261 @@
+import * as React from 'react';
+import * as Menu from '@base_ui/react/Menu';
+import { styled } from '@mui/system';
+
+export default function NestedMenu() {
+ const createHandleMenuClick = (menuItem: string) => {
+ return () => {
+ console.log(`Clicked on ${menuItem}`);
+ };
+ };
+
+ return (
+
+ Format
+
+
+
+ Text color
+
+
+
+ Black
+
+
+ Dark grey
+
+
+ Accent
+
+
+
+
+
+
+ Style
+
+
+
+ Heading
+
+
+
+ Level 1
+
+
+ Level 2
+
+
+ Level 3
+
+
+
+
+
+ Paragraph
+
+
+ List
+
+
+
+ Ordered
+
+
+ Unordered
+
+
+
+
+
+
+
+
+
+ Clear formatting
+
+
+
+
+ );
+}
+
+const blue = {
+ 50: '#F0F7FF',
+ 100: '#C2E0FF',
+ 200: '#99CCF3',
+ 300: '#66B2FF',
+ 400: '#3399FF',
+ 500: '#007FFF',
+ 600: '#0072E6',
+ 700: '#0059B3',
+ 800: '#004C99',
+ 900: '#003A75',
+};
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+const MenuPopup = styled(Menu.Popup)(
+ ({ theme }) => `
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-size: 0.875rem;
+ box-sizing: border-box;
+ padding: 6px;
+ margin: 12px 0;
+ min-width: 200px;
+ border-radius: 12px;
+ overflow: auto;
+ outline: 0;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ box-shadow: 0px 4px 30px ${theme.palette.mode === 'dark' ? grey[900] : grey[200]};
+ z-index: 1;
+ transform-origin: var(--transform-origin);
+ opacity: 1;
+ transform: scale(1, 1);
+ transition: opacity 100ms ease-in, transform 100ms ease-in;
+
+ @starting-style {
+ & {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+ }
+
+ &[data-exiting] {
+ opacity: 0;
+ transform: scale(0.8);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+ }
+ `,
+);
+
+const MenuPositioner = styled(Menu.Positioner)`
+ &:focus-visible {
+ outline: 0;
+ }
+
+ &[data-state='closed'] {
+ pointer-events: none;
+ }
+`;
+
+const MenuItem = styled(Menu.Item)(
+ ({ theme }) => `
+ list-style: none;
+ padding: 8px;
+ border-radius: 8px;
+ cursor: default;
+ user-select: none;
+
+ &:last-of-type {
+ border-bottom: none;
+ }
+
+ &:focus,
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ }
+
+ &:focus-visible {
+ outline: none;
+ }
+
+ &[data-disabled] {
+ color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
+ }
+ `,
+);
+
+const SubmenuTrigger = styled(Menu.SubmenuTrigger)(
+ ({ theme }) => `
+ list-style: none;
+ padding: 8px;
+ border-radius: 8px;
+ cursor: default;
+ user-select: none;
+
+ &:last-of-type {
+ border-bottom: none;
+ }
+
+ &::after {
+ content: '›';
+ float: right;
+ }
+
+ &[data-state='open'] {
+ background-color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ }
+
+ &:focus,
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ }
+
+ &:focus-visible {
+ outline: none;
+ }
+
+ &[data-disabled] {
+ color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
+ }
+ `,
+);
+
+const MenuButton = styled(Menu.Trigger)(
+ ({ theme }) => `
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-weight: 600;
+ font-size: 0.875rem;
+ line-height: 1.5;
+ padding: 8px 16px;
+ border-radius: 8px;
+ color: white;
+ transition: all 150ms ease;
+ cursor: pointer;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ color: ${theme.palette.mode === 'dark' ? grey[200] : grey[900]};
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+
+ &:hover {
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};
+ border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]};
+ }
+
+ &:active {
+ background: ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ }
+
+ &:focus-visible {
+ box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
+ outline: none;
+ }
+ `,
+);
diff --git a/docs/data/base/components/menu/menu.md b/docs/data/base/components/menu/menu.md
index 08e6396062..43813505f4 100644
--- a/docs/data/base/components/menu/menu.md
+++ b/docs/data/base/components/menu/menu.md
@@ -1,16 +1,363 @@
---
productId: base-ui
title: React Menu components and hooks
-components: Menu, MenuItem, MenuButton, Dropdown
-hooks: useMenu, useMenuItem, useMenuButton, useDropdown, useMenuItemContextStabilizer
+components: MenuItem, MenuPositioner, MenuPopup, MenuRoot, MenuTrigger, SubmenuTrigger, MenuArrow
githubLabel: 'component: menu'
waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/
---
# Menu
-The Dropdown Menu components provide end users with a list of options on temporary surfaces.
+The Menu component provide end users with a list of options on temporary surfaces.
{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
{{"component": "modules/components/ComponentPageTabs.js"}}
+
+{{"demo": "MenuIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}
+
+## Installation
+
+Base UI components are all available as a single package.
+
+
+
+```bash npm
+npm install @base_ui/react
+```
+
+```bash yarn
+yarn add @base_ui/react
+```
+
+```bash pnpm
+pnpm add @base_ui/react
+```
+
+
+
+Once you have the package installed, import the component.
+
+```ts
+import * as Menu from '@base_ui/react/Menu';
+```
+
+## Anatomy
+
+Menus are implemented using a collection of related components:
+
+- ` ` is a top-level component that facilitates communication between other components. It does not render to the DOM.
+- ` ` is an optional component (a button by default) that, when clicked, shows the menu. When not used, menu can be shown programmatically using the `open` prop.
+- ` ` renders the element responsible for positioning the popup.
+- ` ` is the menu popup.
+- ` ` is the menu item.
+- ` ` renders an optional pointing arrow, placed inside the popup.
+- ` ` is a menu item that opens a submenu. See [Nested menu](#nested-menu) for more details.
+
+```tsx
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Placement
+
+By default, the menu is placed on the bottom side of its trigger, the default anchor. To change this, use the `side` prop:
+
+```jsx
+
+
+
+
+ Item 1
+
+
+
+```
+
+You can also change the alignment of the menu in relation to its anchor. By default, aligned to the leading edge of an anchor, but it can be configured otherwise using the `alignment` prop:
+
+```jsx
+
+
+
+
+ Item 1
+
+
+
+```
+
+Due to collision detection, the menu may change its placement to avoid overflow. Therefore, your explicitly specified `side` and `alignment` props act as "ideal", or preferred, values.
+
+To access the true rendered values, which may change as the result of a collision, the menu element receives data attributes:
+
+```jsx
+// Rendered HTML (simplified)
+
+```
+
+This allows you to conditionally style the menu based on its rendered side or alignment.
+
+## Offset
+
+The `sideOffset` prop creates a gap between the anchor and menu popup, while `alignmentOffset` slides the menu popup from its alignment, acting logically for `start` and `end` alignments.
+
+```jsx
+
+```
+
+## Orientation
+
+By default, menus are vertical, so the up/down arrow keys navigate through options and left/right keys open and close submenus.
+You can change this with the `orientation` prop"
+
+```jsx
+
+
+
+
+ Item 1
+
+
+
+```
+
+## Hover
+
+To open the Menu on hover, add the `openOnHover` prop:
+
+```jsx
+
+```
+
+By default submenus are opened on hover, but top-level menus aren't.
+
+### Delay
+
+To change how long the menu waits until it opens or closes when `openOnHover` is enabled, use the `delay` prop, which represent how long the Menu waits after the cursor enters the trigger, in milliseconds:
+
+```jsx
+
+```
+
+## Nested menu
+
+Menu items can open submenus.
+To make this happen, place the `` with all its required children where a submenu trigger has to be placed, but instead of ``, use ``, as on the demo below.
+
+{{"demo": "NestedMenu.js"}}
+
+### Escape key behavior
+
+You can control if pressing the Escape key closes just the current submenu or the whole tree.
+By default, the whole menu closes, but setting the `closeParentOnEsc` prop modifies this behavior:
+
+```jsx
+
+
+
+
+ Item 1
+
+ Submenu
+
+
+
+ Submenu item 1
+ Submenu item 2
+
+
+
+
+
+
+```
+
+## Arrow
+
+To add an arrow (caret or triangle) inside the menu popup that points toward the center of the anchor element, use the `Menu.Arrow` component:
+
+```jsx
+
+
+
+ Item 1
+ Item 2
+
+
+```
+
+It automatically positions a wrapper element that can be styled or contain a custom SVG shape.
+
+## Animations
+
+The menu can animate when opening or closing with either:
+
+- CSS transitions
+- CSS animations
+- JavaScript animations
+
+### CSS transitions
+
+Here is an example of how to apply a symmetric scale and fade transition with the default conditionally-rendered behavior:
+
+```jsx
+
+ Item 1
+
+```
+
+```css
+.MenuPopup {
+ transform-origin: var(--transform-origin);
+ transition-property: opacity, transform;
+ transition-duration: 0.2s;
+ /* Represents the final styles once exited */
+ opacity: 0;
+ transform: scale(0.9);
+}
+
+/* Represents the final styles once entered */
+.MenuPopup[data-state='open'] {
+ opacity: 1;
+ transform: scale(1);
+}
+
+/* Represents the initial styles when entering */
+.MenuPopup[data-entering] {
+ opacity: 0;
+ transform: scale(0.9);
+}
+```
+
+Styles need to be applied in three states:
+
+- The exiting styles, placed on the base element class
+- The open styles, placed on the base element class with `[data-state="open"]`
+- The entering styles, placed on the base element class with `[data-entering]`
+
+In newer browsers, there is a feature called `@starting-style` which allows transitions to occur on open for conditionally-mounted components:
+
+```css
+/* Base UI API - Polyfill */
+.MenuPopup[data-entering] {
+ opacity: 0;
+ transform: scale(0.9);
+}
+
+/* Official Browser API - no Firefox support as of May 2024 */
+@starting-style {
+ .MenuPopup[data-state='open'] {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+}
+```
+
+### CSS animations
+
+CSS animations can also be used, requiring only two separate declarations:
+
+```css
+@keyframes scale-in {
+ from {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+}
+
+@keyframes scale-out {
+ to {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+}
+
+.MenuPopup {
+ animation: scale-in 0.2s forwards;
+}
+
+.MenuPopup[data-exiting] {
+ animation: scale-out 0.2s forwards;
+}
+```
+
+### JavaScript animations
+
+The `keepMounted` prop lets an external library control the mounting, for example `framer-motion`'s `AnimatePresence` component.
+
+```js
+function App() {
+ const [open, setOpen] = useState(false);
+ return (
+
+ Trigger
+
+ {open && (
+
+
+ }
+ >
+ Item 1
+ Item 2
+
+
+ )}
+
+
+ );
+}
+```
+
+### Animation states
+
+Four states are available as data attributes to animate the popup, which enables full control depending on whether the popup is being animated with CSS transitions or animations, JavaScript, or is using the `keepMounted` prop.
+
+- `[data-state="open"]` - `open` state is `true`.
+- `[data-state="closed"]` - `open` state is `false`. Can still be mounted to the DOM if closing.
+- `[data-entering]` - the popup was just inserted to the DOM. The attribute is removed 1 animation frame later. Enables "starting styles" upon insertion for conditional rendering.
+- `[data-exiting]` - the popup is in the process of being removed from the DOM, but is still mounted.
+
+## Overriding default components
+
+Use the `render` prop to override the rendered elements with your own components.
+
+```jsx
+// Element shorthand
+ } />
+```
+
+```jsx
+// Function
+ } />
+```
diff --git a/docs/data/base/getting-started/accessibility/KeyboardNavigation.js b/docs/data/base/getting-started/accessibility/KeyboardNavigation.js
index 88fbc842b6..4349b64e60 100644
--- a/docs/data/base/getting-started/accessibility/KeyboardNavigation.js
+++ b/docs/data/base/getting-started/accessibility/KeyboardNavigation.js
@@ -2,13 +2,7 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import { Select as BaseSelect, selectClasses } from '@base_ui/react/legacy/Select';
import { Option as BaseOption, optionClasses } from '@base_ui/react/legacy/Option';
-import { Dropdown } from '@base_ui/react/legacy/Dropdown';
-import { Menu } from '@base_ui/react/legacy/Menu';
-import { MenuButton as BaseMenuButton } from '@base_ui/react/legacy/MenuButton';
-import {
- MenuItem as BaseMenuItem,
- menuItemClasses,
-} from '@base_ui/react/legacy/MenuItem';
+import * as Menu from '@base_ui/react/Menu';
import { styled, alpha } from '@mui/system';
import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded';
@@ -27,14 +21,16 @@ export default function KeyboardNavigation() {
Source Code Pro
-
+
Open menu
-
- Cut
- Copy
- Paste
-
-
+
+
+ Cut
+ Copy
+ Paste
+
+
+
);
}
@@ -193,7 +189,27 @@ const Option = styled(BaseOption)(
`,
);
-const MenuItem = styled(BaseMenuItem)(
+const MenuPopup = styled(Menu.Popup)(
+ ({ theme }) => `
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-size: 0.875rem;
+ box-sizing: border-box;
+ padding: 6px;
+ margin: 12px 0;
+ min-width: 200px;
+ border-radius: 12px;
+ overflow: auto;
+ outline: 0;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[200]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ box-shadow: 0px 2px 6px ${
+ theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)'
+ };
+ `,
+);
+
+const MenuItem = styled(Menu.Item)(
({ theme }) => `
list-style: none;
padding: 8px;
@@ -205,24 +221,24 @@ const MenuItem = styled(BaseMenuItem)(
border-bottom: none;
}
- &.${menuItemClasses.focusVisible} {
+ &:focus-visible {
outline: 3px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
}
- &.${menuItemClasses.disabled} {
+ &[data-disabled='true'] {
color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
}
- &:hover:not(.${menuItemClasses.disabled}) {
+ &:hover:not([data-disabled='true']) {
background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
}
`,
);
-const MenuButton = styled(BaseMenuButton)(
+const MenuButton = styled(Menu.Trigger)(
({ theme }) => `
font-family: 'IBM Plex Sans', sans-serif;
font-weight: 600;
diff --git a/docs/data/base/getting-started/accessibility/KeyboardNavigation.tsx b/docs/data/base/getting-started/accessibility/KeyboardNavigation.tsx
index 9efe51b0f7..d3c1a35e42 100644
--- a/docs/data/base/getting-started/accessibility/KeyboardNavigation.tsx
+++ b/docs/data/base/getting-started/accessibility/KeyboardNavigation.tsx
@@ -6,13 +6,7 @@ import {
selectClasses,
} from '@base_ui/react/legacy/Select';
import { Option as BaseOption, optionClasses } from '@base_ui/react/legacy/Option';
-import { Dropdown } from '@base_ui/react/legacy/Dropdown';
-import { Menu } from '@base_ui/react/legacy/Menu';
-import { MenuButton as BaseMenuButton } from '@base_ui/react/legacy/MenuButton';
-import {
- MenuItem as BaseMenuItem,
- menuItemClasses,
-} from '@base_ui/react/legacy/MenuItem';
+import * as Menu from '@base_ui/react/Menu';
import { styled, alpha } from '@mui/system';
import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded';
@@ -31,14 +25,16 @@ export default function KeyboardNavigation() {
Source Code Pro
-
+
Open menu
-
- Cut
- Copy
- Paste
-
-
+
+
+ Cut
+ Copy
+ Paste
+
+
+
);
}
@@ -203,7 +199,27 @@ const Option = styled(BaseOption)(
`,
);
-const MenuItem = styled(BaseMenuItem)(
+const MenuPopup = styled(Menu.Popup)(
+ ({ theme }) => `
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-size: 0.875rem;
+ box-sizing: border-box;
+ padding: 6px;
+ margin: 12px 0;
+ min-width: 200px;
+ border-radius: 12px;
+ overflow: auto;
+ outline: 0;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[200]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ box-shadow: 0px 2px 6px ${
+ theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)'
+ };
+ `,
+);
+
+const MenuItem = styled(Menu.Item)(
({ theme }) => `
list-style: none;
padding: 8px;
@@ -215,24 +231,24 @@ const MenuItem = styled(BaseMenuItem)(
border-bottom: none;
}
- &.${menuItemClasses.focusVisible} {
+ &:focus-visible {
outline: 3px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
}
- &.${menuItemClasses.disabled} {
+ &[data-disabled='true'] {
color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
}
- &:hover:not(.${menuItemClasses.disabled}) {
+ &:hover:not([data-disabled='true']) {
background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
}
`,
);
-const MenuButton = styled(BaseMenuButton)(
+const MenuButton = styled(Menu.Trigger)(
({ theme }) => `
font-family: 'IBM Plex Sans', sans-serif;
font-weight: 600;
diff --git a/docs/data/base/pages.ts b/docs/data/base/pages.ts
index 3990ab14cf..4a770cbf10 100644
--- a/docs/data/base/pages.ts
+++ b/docs/data/base/pages.ts
@@ -52,7 +52,7 @@ const pages: readonly MuiPage[] = [
pathname: '/base-ui/components/navigation',
subheader: 'navigation',
children: [
- // { pathname: '/base-ui/react-menu', title: 'Menu' },
+ { pathname: '/base-ui/react-menu', title: 'Menu' },
// { pathname: '/base-ui/react-table-pagination', title: 'Table Pagination' },
{ pathname: '/base-ui/react-tabs', title: 'Tabs' },
],
diff --git a/docs/data/base/pagesApi.js b/docs/data/base/pagesApi.js
index c58afdd633..1f62a5d773 100644
--- a/docs/data/base/pagesApi.js
+++ b/docs/data/base/pagesApi.js
@@ -68,7 +68,6 @@ module.exports = [
pathname: '/base-ui/react-dialog/components-api/#dialog-trigger',
title: 'DialogTrigger',
},
- { pathname: '/base-ui/react-menu/components-api/#dropdown', title: 'Dropdown' },
{
pathname: '/base-ui/react-focus-trap/components-api/#focus-trap',
title: 'FocusTrap',
@@ -77,12 +76,18 @@ module.exports = [
pathname: '/base-ui/react-form-control/components-api/#form-control',
title: 'FormControl',
},
- { pathname: '/base-ui/react-menu/components-api/#menu', title: 'Menu' },
+ { pathname: '/base-ui/react-menu/components-api/#menu-arrow', title: 'MenuArrow' },
+ { pathname: '/base-ui/react-menu/components-api/#menu-item', title: 'MenuItem' },
+ { pathname: '/base-ui/react-menu/components-api/#menu-popup', title: 'MenuPopup' },
{
- pathname: '/base-ui/react-menu/components-api/#menu-button',
- title: 'MenuButton',
+ pathname: '/base-ui/react-menu/components-api/#menu-positioner',
+ title: 'MenuPositioner',
+ },
+ { pathname: '/base-ui/react-menu/components-api/#menu-root', title: 'MenuRoot' },
+ {
+ pathname: '/base-ui/react-menu/components-api/#menu-trigger',
+ title: 'MenuTrigger',
},
- { pathname: '/base-ui/react-menu/components-api/#menu-item', title: 'MenuItem' },
{ pathname: '/base-ui/react-no-ssr/components-api/#no-ssr', title: 'NoSsr' },
{
pathname: '/base-ui/react-number-field/components-api/#number-field-decrement',
@@ -221,6 +226,10 @@ module.exports = [
pathname: '/base-ui/react-snackbar/components-api/#snackbar',
title: 'Snackbar',
},
+ {
+ pathname: '/base-ui/react-menu/components-api/#submenu-trigger',
+ title: 'SubmenuTrigger',
+ },
{
pathname: '/base-ui/react-switch/components-api/#switch-root',
title: 'SwitchRoot',
@@ -293,21 +302,10 @@ module.exports = [
pathname: '/base-ui/react-dialog/hooks-api/#use-dialog-trigger',
title: 'useDialogTrigger',
},
- { pathname: '/base-ui/react-menu/hooks-api/#use-dropdown', title: 'useDropdown' },
{
pathname: '/base-ui/react-form-control/hooks-api/#use-form-control-context',
title: 'useFormControlContext',
},
- { pathname: '/base-ui/react-menu/hooks-api/#use-menu', title: 'useMenu' },
- {
- pathname: '/base-ui/react-menu/hooks-api/#use-menu-button',
- title: 'useMenuButton',
- },
- { pathname: '/base-ui/react-menu/hooks-api/#use-menu-item', title: 'useMenuItem' },
- {
- pathname: '/base-ui/react-menu/hooks-api/#use-menu-item-context-stabilizer',
- title: 'useMenuItemContextStabilizer',
- },
{
pathname: '/base-ui/react-number-field/hooks-api/#use-number-field-root',
title: 'useNumberFieldRoot',
diff --git a/docs/package.json b/docs/package.json
index 926f46863a..f9ced48539 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -24,17 +24,17 @@
"@emotion/react": "^11.11.4",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.5",
- "@mui/docs": "6.0.0-beta.2",
- "@mui/icons-material": "6.0.0-beta.2",
+ "@mui/docs": "6.0.0-beta.4",
+ "@mui/icons-material": "6.0.0-beta.4",
"@mui/internal-markdown": "^1.0.8",
- "@mui/internal-scripts": "^1.0.13",
+ "@mui/internal-scripts": "^1.0.14",
"@mui/joy": "5.0.0-beta.48",
- "@mui/material": "6.0.0-beta.2",
- "@mui/material-nextjs": "6.0.0-alpha.14",
- "@mui/styles": "6.0.0-beta.2",
- "@mui/system": "6.0.0-beta.1",
+ "@mui/material": "6.0.0-beta.4",
+ "@mui/material-nextjs": "6.0.0-beta.4",
+ "@mui/styles": "6.0.0-beta.4",
+ "@mui/system": "6.0.0-beta.4",
"@mui/types": "7.2.15",
- "@mui/utils": "6.0.0-beta.1",
+ "@mui/utils": "6.0.0-beta.4",
"@react-spring/web": "^9.7.4",
"autoprefixer": "^10.4.19",
"autosuggest-highlight": "^3.3.4",
@@ -58,10 +58,10 @@
"postcss": "^8.4.40",
"postcss-import": "^16.1.0",
"prop-types": "^15.8.1",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
"react-draggable": "^4.4.6",
- "react-is": "^18.2.0",
+ "react-is": "^18.3.1",
"react-router-dom": "^6.23.1",
"react-runner": "^1.0.5",
"react-simple-code-editor": "^0.13.1",
@@ -76,7 +76,7 @@
"@babel/plugin-transform-react-constant-elements": "^7.25.1",
"@babel/preset-typescript": "^7.24.7",
"@mui/internal-docs-utils": "^1.0.8",
- "@mui/internal-test-utils": "1.0.5",
+ "@mui/internal-test-utils": "1.0.6",
"@types/autosuggest-highlight": "^3.2.3",
"@types/chai": "^4.3.16",
"@types/node": "^18.19.42",
diff --git a/docs/pages/base-ui/api/dropdown.json b/docs/pages/base-ui/api/dropdown.json
deleted file mode 100644
index 0f2455835b..0000000000
--- a/docs/pages/base-ui/api/dropdown.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "props": {
- "defaultOpen": { "type": { "name": "bool" } },
- "onOpenChange": { "type": { "name": "func" } },
- "open": { "type": { "name": "bool" } }
- },
- "name": "Dropdown",
- "imports": ["import { Dropdown } from '@base_ui/react/legacy/Dropdown';"],
- "classes": [],
- "spread": false,
- "themeDefaultProps": null,
- "muiName": "Dropdown",
- "filename": "/packages/mui-base/src/legacy/Dropdown/Dropdown.tsx",
- "inheritance": null,
- "demos": "",
- "cssComponent": false
-}
diff --git a/docs/pages/base-ui/api/menu-arrow.json b/docs/pages/base-ui/api/menu-arrow.json
new file mode 100644
index 0000000000..e03d97c5e5
--- /dev/null
+++ b/docs/pages/base-ui/api/menu-arrow.json
@@ -0,0 +1,18 @@
+{
+ "props": {
+ "className": { "type": { "name": "union", "description": "func | string" } },
+ "hideWhenUncentered": { "type": { "name": "bool" }, "default": "false" },
+ "render": { "type": { "name": "union", "description": "element | func" } }
+ },
+ "name": "MenuArrow",
+ "imports": ["import * as Menu from '@base_ui/react/Menu';\nconst MenuArrow = Menu.Arrow;"],
+ "classes": [],
+ "spread": true,
+ "themeDefaultProps": true,
+ "muiName": "MenuArrow",
+ "forwardsRefTo": "HTMLDivElement",
+ "filename": "/packages/mui-base/src/Menu/Arrow/MenuArrow.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/base-ui/api/menu-button.json b/docs/pages/base-ui/api/menu-button.json
deleted file mode 100644
index 0111ac0f14..0000000000
--- a/docs/pages/base-ui/api/menu-button.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "props": {
- "className": { "type": { "name": "string" } },
- "disabled": { "type": { "name": "bool" }, "default": "false" },
- "focusableWhenDisabled": { "type": { "name": "bool" }, "default": "false" },
- "label": { "type": { "name": "string" } },
- "slotProps": {
- "type": { "name": "shape", "description": "{ root?: func | object }" },
- "default": "{}"
- },
- "slots": {
- "type": { "name": "shape", "description": "{ root?: elementType }" },
- "default": "{}",
- "additionalInfo": { "slotsApi": true }
- }
- },
- "name": "MenuButton",
- "imports": ["import { MenuButton } from '@base_ui/react/legacy/MenuButton';"],
- "classes": [],
- "spread": true,
- "muiName": "MenuButton",
- "forwardsRefTo": "HTMLButtonElement",
- "filename": "/packages/mui-base/src/legacy/MenuButton/MenuButton.tsx",
- "inheritance": null,
- "demos": "",
- "cssComponent": false
-}
diff --git a/docs/pages/base-ui/api/menu-item.json b/docs/pages/base-ui/api/menu-item.json
index b0a6b3cfdb..33242a8ebd 100644
--- a/docs/pages/base-ui/api/menu-item.json
+++ b/docs/pages/base-ui/api/menu-item.json
@@ -1,25 +1,19 @@
{
"props": {
+ "closeOnClick": { "type": { "name": "bool" }, "default": "true" },
"disabled": { "type": { "name": "bool" }, "default": "false" },
- "disableFocusOnHover": { "type": { "name": "bool" }, "default": "false" },
+ "id": { "type": { "name": "string" } },
"label": { "type": { "name": "string" } },
- "slotProps": {
- "type": { "name": "shape", "description": "{ root?: func | object }" },
- "default": "{}"
- },
- "slots": {
- "type": { "name": "shape", "description": "{ root?: elementType }" },
- "default": "{}",
- "additionalInfo": { "slotsApi": true }
- }
+ "onClick": { "type": { "name": "func" } }
},
"name": "MenuItem",
- "imports": ["import { MenuItem } from '@base_ui/react/legacy/MenuItem';"],
+ "imports": ["import * as Menu from '@base_ui/react/Menu';\nconst MenuItem = Menu.Item;"],
"classes": [],
"spread": true,
+ "themeDefaultProps": true,
"muiName": "MenuItem",
- "forwardsRefTo": "HTMLLIElement",
- "filename": "/packages/mui-base/src/legacy/MenuItem/MenuItem.tsx",
+ "forwardsRefTo": "HTMLDivElement",
+ "filename": "/packages/mui-base/src/Menu/Item/MenuItem.tsx",
"inheritance": null,
"demos": "",
"cssComponent": false
diff --git a/docs/pages/base-ui/api/menu-popup.json b/docs/pages/base-ui/api/menu-popup.json
new file mode 100644
index 0000000000..b9877029ee
--- /dev/null
+++ b/docs/pages/base-ui/api/menu-popup.json
@@ -0,0 +1,18 @@
+{
+ "props": {
+ "className": { "type": { "name": "union", "description": "func | string" } },
+ "id": { "type": { "name": "string" } },
+ "render": { "type": { "name": "union", "description": "element | func" } }
+ },
+ "name": "MenuPopup",
+ "imports": ["import * as Menu from '@base_ui/react/Menu';\nconst MenuPopup = Menu.Popup;"],
+ "classes": [],
+ "spread": true,
+ "themeDefaultProps": true,
+ "muiName": "MenuPopup",
+ "forwardsRefTo": "HTMLDivElement",
+ "filename": "/packages/mui-base/src/Menu/Popup/MenuPopup.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/base-ui/api/menu-positioner.json b/docs/pages/base-ui/api/menu-positioner.json
new file mode 100644
index 0000000000..3a18b00470
--- /dev/null
+++ b/docs/pages/base-ui/api/menu-positioner.json
@@ -0,0 +1,64 @@
+{
+ "props": {
+ "alignment": {
+ "type": {
+ "name": "enum",
+ "description": "'center' | 'end' | 'start'"
+ },
+ "default": "'center'"
+ },
+ "alignmentOffset": { "type": { "name": "number" }, "default": "0" },
+ "anchor": {
+ "type": {
+ "name": "union",
+ "description": "HTML element | object | func"
+ }
+ },
+ "arrowPadding": { "type": { "name": "number" }, "default": "5" },
+ "className": { "type": { "name": "union", "description": "func | string" } },
+ "collisionBoundary": {
+ "type": {
+ "name": "union",
+ "description": "HTML element | Array<HTML element> | string | { height?: number, width?: number, x?: number, y?: number }"
+ },
+ "default": "'clippingAncestors'"
+ },
+ "collisionPadding": {
+ "type": {
+ "name": "union",
+ "description": "number | { bottom?: number, left?: number, right?: number, top?: number }"
+ },
+ "default": "5"
+ },
+ "container": { "type": { "name": "union", "description": "HTML element | func" } },
+ "hideWhenDetached": { "type": { "name": "bool" }, "default": "false" },
+ "keepMounted": { "type": { "name": "bool" }, "default": "false" },
+ "positionStrategy": {
+ "type": { "name": "enum", "description": "'absolute' | 'fixed'" },
+ "default": "'absolute'"
+ },
+ "render": { "type": { "name": "union", "description": "element | func" } },
+ "side": {
+ "type": {
+ "name": "enum",
+ "description": "'bottom' | 'left' | 'right' | 'top'"
+ },
+ "default": "'bottom'"
+ },
+ "sideOffset": { "type": { "name": "number" }, "default": "0" },
+ "sticky": { "type": { "name": "bool" }, "default": "false" }
+ },
+ "name": "MenuPositioner",
+ "imports": [
+ "import * as Menu from '@base_ui/react/Menu';\nconst MenuPositioner = Menu.Positioner;"
+ ],
+ "classes": [],
+ "spread": true,
+ "themeDefaultProps": true,
+ "muiName": "MenuPositioner",
+ "forwardsRefTo": "HTMLDivElement",
+ "filename": "/packages/mui-base/src/Menu/Positioner/MenuPositioner.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/base-ui/api/menu-root.json b/docs/pages/base-ui/api/menu-root.json
new file mode 100644
index 0000000000..d09796e5eb
--- /dev/null
+++ b/docs/pages/base-ui/api/menu-root.json
@@ -0,0 +1,31 @@
+{
+ "props": {
+ "animated": { "type": { "name": "bool" }, "default": "true" },
+ "closeParentOnEsc": { "type": { "name": "bool" }, "default": "true" },
+ "defaultOpen": { "type": { "name": "bool" }, "default": "false" },
+ "delay": { "type": { "name": "number" }, "default": "100" },
+ "dir": {
+ "type": { "name": "enum", "description": "'ltr' | 'rtl'" },
+ "default": "'ltr'"
+ },
+ "disabled": { "type": { "name": "bool" }, "default": "false" },
+ "loop": { "type": { "name": "bool" }, "default": "true" },
+ "onOpenChange": { "type": { "name": "func" } },
+ "open": { "type": { "name": "bool" } },
+ "openOnHover": { "type": { "name": "bool" }, "default": "nested" },
+ "orientation": {
+ "type": { "name": "enum", "description": "'horizontal' | 'vertical'" },
+ "default": "'vertical'"
+ }
+ },
+ "name": "MenuRoot",
+ "imports": ["import * as Menu from '@base_ui/react/Menu';\nconst MenuRoot = Menu.Root;"],
+ "classes": [],
+ "spread": true,
+ "themeDefaultProps": null,
+ "muiName": "MenuRoot",
+ "filename": "/packages/mui-base/src/Menu/Root/MenuRoot.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/base-ui/api/menu-trigger.json b/docs/pages/base-ui/api/menu-trigger.json
new file mode 100644
index 0000000000..231b072df3
--- /dev/null
+++ b/docs/pages/base-ui/api/menu-trigger.json
@@ -0,0 +1,20 @@
+{
+ "props": {
+ "className": { "type": { "name": "union", "description": "func | string" } },
+ "disabled": { "type": { "name": "bool" }, "default": "false" },
+ "focusableWhenDisabled": { "type": { "name": "bool" }, "default": "false" },
+ "label": { "type": { "name": "string" } },
+ "render": { "type": { "name": "union", "description": "element | func" } }
+ },
+ "name": "MenuTrigger",
+ "imports": ["import * as Menu from '@base_ui/react/Menu';\nconst MenuTrigger = Menu.Trigger;"],
+ "classes": [],
+ "spread": true,
+ "themeDefaultProps": true,
+ "muiName": "MenuTrigger",
+ "forwardsRefTo": "HTMLButtonElement",
+ "filename": "/packages/mui-base/src/Menu/Trigger/MenuTrigger.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/base-ui/api/menu.json b/docs/pages/base-ui/api/menu.json
deleted file mode 100644
index 86e9c7ae90..0000000000
--- a/docs/pages/base-ui/api/menu.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "props": {
- "actions": { "type": { "name": "custom", "description": "ref" } },
- "anchor": {
- "type": {
- "name": "union",
- "description": "HTML element | object | func"
- }
- },
- "onItemsChange": { "type": { "name": "func" } },
- "slotProps": {
- "type": {
- "name": "shape",
- "description": "{ listbox?: func | object, root?: func | object }"
- },
- "default": "{}"
- },
- "slots": {
- "type": { "name": "shape", "description": "{ listbox?: elementType, root?: elementType }" },
- "default": "{}",
- "additionalInfo": { "slotsApi": true }
- }
- },
- "name": "Menu",
- "imports": ["import { Menu } from '@base_ui/react/legacy/Menu';"],
- "classes": [],
- "spread": true,
- "muiName": "Menu",
- "forwardsRefTo": "HTMLDivElement",
- "filename": "/packages/mui-base/src/legacy/Menu/Menu.tsx",
- "inheritance": null,
- "demos": "",
- "cssComponent": false
-}
diff --git a/docs/pages/base-ui/api/submenu-trigger.json b/docs/pages/base-ui/api/submenu-trigger.json
new file mode 100644
index 0000000000..fc5a0e413f
--- /dev/null
+++ b/docs/pages/base-ui/api/submenu-trigger.json
@@ -0,0 +1,17 @@
+{
+ "props": {
+ "className": { "type": { "name": "union", "description": "func | string" } },
+ "disabled": { "type": { "name": "bool" }, "default": "false" },
+ "disableFocusOnHover": { "type": { "name": "bool" }, "default": "false" },
+ "label": { "type": { "name": "string" } },
+ "render": { "type": { "name": "union", "description": "element | func" } }
+ },
+ "name": "SubmenuTrigger",
+ "imports": ["import { SubmenuTrigger } from '@base_ui/react/Menu';"],
+ "classes": [],
+ "muiName": "SubmenuTrigger",
+ "filename": "/packages/mui-base/src/Menu/SubmenuTrigger/SubmenuTrigger.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/base-ui/api/use-dropdown.json b/docs/pages/base-ui/api/use-dropdown.json
deleted file mode 100644
index 33286c2758..0000000000
--- a/docs/pages/base-ui/api/use-dropdown.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "parameters": {},
- "returnValue": {},
- "name": "useDropdown",
- "filename": "/packages/mui-base/src/legacy/useDropdown/useDropdown.ts",
- "imports": ["import { useDropdown } from '@base_ui/react/legacy/useDropdown';"],
- "demos": ""
-}
diff --git a/docs/pages/base-ui/api/use-menu-arrow.json b/docs/pages/base-ui/api/use-menu-arrow.json
new file mode 100644
index 0000000000..2caebb442c
--- /dev/null
+++ b/docs/pages/base-ui/api/use-menu-arrow.json
@@ -0,0 +1,8 @@
+{
+ "parameters": {},
+ "returnValue": {},
+ "name": "useMenuArrow",
+ "filename": "/packages/mui-base/src/Menu/Arrow/useMenuArrow.ts",
+ "imports": ["import { useMenuArrow } from '@base_ui/react/Menu';"],
+ "demos": ""
+}
diff --git a/docs/pages/base-ui/api/use-menu-button.json b/docs/pages/base-ui/api/use-menu-button.json
deleted file mode 100644
index 5fe1443dd4..0000000000
--- a/docs/pages/base-ui/api/use-menu-button.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "parameters": {},
- "returnValue": {},
- "name": "useMenuButton",
- "filename": "/packages/mui-base/src/legacy/useMenuButton/useMenuButton.ts",
- "imports": ["import { useMenuButton } from '@base_ui/react/legacy/useMenuButton';"],
- "demos": ""
-}
diff --git a/docs/pages/base-ui/api/use-menu-item-context-stabilizer.json b/docs/pages/base-ui/api/use-menu-item-context-stabilizer.json
deleted file mode 100644
index 8e062090c7..0000000000
--- a/docs/pages/base-ui/api/use-menu-item-context-stabilizer.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "parameters": {},
- "returnValue": {},
- "name": "useMenuItemContextStabilizer",
- "filename": "/packages/mui-base/src/legacy/useMenuItem/useMenuItemContextStabilizer.ts",
- "imports": ["import { useMenuItemContextStabilizer } from '@base_ui/react/legacy/useMenuItem';"],
- "demos": ""
-}
diff --git a/docs/pages/base-ui/api/use-menu-item.json b/docs/pages/base-ui/api/use-menu-item.json
index 40b8bda287..447e11f141 100644
--- a/docs/pages/base-ui/api/use-menu-item.json
+++ b/docs/pages/base-ui/api/use-menu-item.json
@@ -2,7 +2,7 @@
"parameters": {},
"returnValue": {},
"name": "useMenuItem",
- "filename": "/packages/mui-base/src/legacy/useMenuItem/useMenuItem.ts",
- "imports": ["import { useMenuItem } from '@base_ui/react/legacy/useMenuItem';"],
- "demos": ""
+ "filename": "/packages/mui-base/src/Menu/Item/useMenuItem.ts",
+ "imports": ["import { useMenuItem } from '@base_ui/react/Menu';"],
+ "demos": ""
}
diff --git a/docs/pages/base-ui/api/use-menu-popup.json b/docs/pages/base-ui/api/use-menu-popup.json
new file mode 100644
index 0000000000..d0a0a34d06
--- /dev/null
+++ b/docs/pages/base-ui/api/use-menu-popup.json
@@ -0,0 +1,8 @@
+{
+ "parameters": {},
+ "returnValue": {},
+ "name": "useMenuPopup",
+ "filename": "/packages/mui-base/src/Menu/Popup/useMenuPopup.ts",
+ "imports": ["import { useMenuPopup } from '@base_ui/react/Menu';"],
+ "demos": ""
+}
diff --git a/docs/pages/base-ui/api/use-menu-positioner.json b/docs/pages/base-ui/api/use-menu-positioner.json
new file mode 100644
index 0000000000..307a3ac646
--- /dev/null
+++ b/docs/pages/base-ui/api/use-menu-positioner.json
@@ -0,0 +1,8 @@
+{
+ "parameters": {},
+ "returnValue": {},
+ "name": "useMenuPositioner",
+ "filename": "/packages/mui-base/src/Menu/Positioner/useMenuPositioner.ts",
+ "imports": ["import { useMenuPositioner } from '@base_ui/react/Menu';"],
+ "demos": ""
+}
diff --git a/docs/pages/base-ui/api/use-menu-root.json b/docs/pages/base-ui/api/use-menu-root.json
new file mode 100644
index 0000000000..e93c096533
--- /dev/null
+++ b/docs/pages/base-ui/api/use-menu-root.json
@@ -0,0 +1,8 @@
+{
+ "parameters": {},
+ "returnValue": {},
+ "name": "useMenuRoot",
+ "filename": "/packages/mui-base/src/Menu/Root/useMenuRoot.ts",
+ "imports": ["import { useMenuRoot } from '@base_ui/react/Menu';"],
+ "demos": ""
+}
diff --git a/docs/pages/base-ui/api/use-menu-trigger.json b/docs/pages/base-ui/api/use-menu-trigger.json
new file mode 100644
index 0000000000..f83b4b083e
--- /dev/null
+++ b/docs/pages/base-ui/api/use-menu-trigger.json
@@ -0,0 +1,8 @@
+{
+ "parameters": {},
+ "returnValue": {},
+ "name": "useMenuTrigger",
+ "filename": "/packages/mui-base/src/Menu/Trigger/useMenuTrigger.ts",
+ "imports": ["import { useMenuTrigger } from '@base_ui/react/Menu';"],
+ "demos": ""
+}
diff --git a/docs/pages/base-ui/api/use-menu.json b/docs/pages/base-ui/api/use-menu.json
deleted file mode 100644
index 76ef066b9f..0000000000
--- a/docs/pages/base-ui/api/use-menu.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "parameters": {},
- "returnValue": {},
- "name": "useMenu",
- "filename": "/packages/mui-base/src/legacy/useMenu/useMenu.ts",
- "imports": ["import { useMenu } from '@base_ui/react/legacy/useMenu';"],
- "demos": ""
-}
diff --git a/docs/pages/base-ui/api/use-submenu-trigger.json b/docs/pages/base-ui/api/use-submenu-trigger.json
new file mode 100644
index 0000000000..11e7831041
--- /dev/null
+++ b/docs/pages/base-ui/api/use-submenu-trigger.json
@@ -0,0 +1,8 @@
+{
+ "parameters": {},
+ "returnValue": {},
+ "name": "useSubmenuTrigger",
+ "filename": "/packages/mui-base/src/Menu/SubmenuTrigger/useSubmenuTrigger.ts",
+ "imports": ["import { useSubmenuTrigger } from '@base_ui/react/Menu';"],
+ "demos": ""
+}
diff --git a/docs/pages/base-ui/react-menu/[docsTab]/index.js b/docs/pages/base-ui/react-menu/[docsTab]/index.js
index 01c21cb53f..06242862d2 100644
--- a/docs/pages/base-ui/react-menu/[docsTab]/index.js
+++ b/docs/pages/base-ui/react-menu/[docsTab]/index.js
@@ -3,15 +3,13 @@ import MarkdownDocs from 'docs/src/modules/components/MarkdownDocsV2';
import AppFrame from 'docs/src/modules/components/AppFrame';
import * as pageProps from 'docs-base/data/base/components/menu/menu.md?@mui/markdown';
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
-import DropdownApiJsonPageContent from '../../api/dropdown.json';
-import MenuApiJsonPageContent from '../../api/menu.json';
-import MenuButtonApiJsonPageContent from '../../api/menu-button.json';
+import MenuArrowApiJsonPageContent from '../../api/menu-arrow.json';
import MenuItemApiJsonPageContent from '../../api/menu-item.json';
-import useDropdownApiJsonPageContent from '../../api/use-dropdown.json';
-import useMenuApiJsonPageContent from '../../api/use-menu.json';
-import useMenuButtonApiJsonPageContent from '../../api/use-menu-button.json';
-import useMenuItemApiJsonPageContent from '../../api/use-menu-item.json';
-import useMenuItemContextStabilizerApiJsonPageContent from '../../api/use-menu-item-context-stabilizer.json';
+import MenuPopupApiJsonPageContent from '../../api/menu-popup.json';
+import MenuPositionerApiJsonPageContent from '../../api/menu-positioner.json';
+import MenuRootApiJsonPageContent from '../../api/menu-root.json';
+import MenuTriggerApiJsonPageContent from '../../api/menu-trigger.json';
+import SubmenuTriggerApiJsonPageContent from '../../api/submenu-trigger.json';
export default function Page(props) {
const { userLanguage, ...other } = props;
@@ -30,26 +28,12 @@ export const getStaticPaths = () => {
};
export const getStaticProps = () => {
- const DropdownApiReq = require.context(
- 'docs-base/translations/api-docs/dropdown',
+ const MenuArrowApiReq = require.context(
+ 'docs-base/translations/api-docs/menu-arrow',
false,
- /\.\/dropdown.*.json$/,
+ /\.\/menu-arrow.*.json$/,
);
- const DropdownApiDescriptions = mapApiPageTranslations(DropdownApiReq);
-
- const MenuApiReq = require.context(
- 'docs-base/translations/api-docs/menu',
- false,
- /\.\/menu.*.json$/,
- );
- const MenuApiDescriptions = mapApiPageTranslations(MenuApiReq);
-
- const MenuButtonApiReq = require.context(
- 'docs-base/translations/api-docs/menu-button',
- false,
- /\.\/menu-button.*.json$/,
- );
- const MenuButtonApiDescriptions = mapApiPageTranslations(MenuButtonApiReq);
+ const MenuArrowApiDescriptions = mapApiPageTranslations(MenuArrowApiReq);
const MenuItemApiReq = require.context(
'docs-base/translations/api-docs/menu-item',
@@ -58,71 +42,63 @@ export const getStaticProps = () => {
);
const MenuItemApiDescriptions = mapApiPageTranslations(MenuItemApiReq);
- const useDropdownApiReq = require.context(
- 'docs-base/translations/api-docs/use-dropdown',
+ const MenuPopupApiReq = require.context(
+ 'docs-base/translations/api-docs/menu-popup',
false,
- /\.\/use-dropdown.*.json$/,
+ /\.\/menu-popup.*.json$/,
);
- const useDropdownApiDescriptions = mapApiPageTranslations(useDropdownApiReq);
+ const MenuPopupApiDescriptions = mapApiPageTranslations(MenuPopupApiReq);
- const useMenuApiReq = require.context(
- 'docs-base/translations/api-docs/use-menu',
+ const MenuPositionerApiReq = require.context(
+ 'docs-base/translations/api-docs/menu-positioner',
false,
- /\.\/use-menu.*.json$/,
+ /\.\/menu-positioner.*.json$/,
);
- const useMenuApiDescriptions = mapApiPageTranslations(useMenuApiReq);
+ const MenuPositionerApiDescriptions = mapApiPageTranslations(MenuPositionerApiReq);
- const useMenuButtonApiReq = require.context(
- 'docs-base/translations/api-docs/use-menu-button',
+ const MenuRootApiReq = require.context(
+ 'docs-base/translations/api-docs/menu-root',
false,
- /\.\/use-menu-button.*.json$/,
+ /\.\/menu-root.*.json$/,
);
- const useMenuButtonApiDescriptions = mapApiPageTranslations(useMenuButtonApiReq);
+ const MenuRootApiDescriptions = mapApiPageTranslations(MenuRootApiReq);
- const useMenuItemApiReq = require.context(
- 'docs-base/translations/api-docs/use-menu-item',
+ const MenuTriggerApiReq = require.context(
+ 'docs-base/translations/api-docs/menu-trigger',
false,
- /\.\/use-menu-item.*.json$/,
+ /\.\/menu-trigger.*.json$/,
);
- const useMenuItemApiDescriptions = mapApiPageTranslations(useMenuItemApiReq);
+ const MenuTriggerApiDescriptions = mapApiPageTranslations(MenuTriggerApiReq);
- const useMenuItemContextStabilizerApiReq = require.context(
- 'docs-base/translations/api-docs/use-menu-item-context-stabilizer',
+ const SubmenuTriggerApiReq = require.context(
+ 'docs-base/translations/api-docs/submenu-trigger',
false,
- /\.\/use-menu-item-context-stabilizer.*.json$/,
- );
- const useMenuItemContextStabilizerApiDescriptions = mapApiPageTranslations(
- useMenuItemContextStabilizerApiReq,
+ /\.\/submenu-trigger.*.json$/,
);
+ const SubmenuTriggerApiDescriptions = mapApiPageTranslations(SubmenuTriggerApiReq);
return {
props: {
componentsApiDescriptions: {
- Dropdown: DropdownApiDescriptions,
- Menu: MenuApiDescriptions,
- MenuButton: MenuButtonApiDescriptions,
+ MenuArrow: MenuArrowApiDescriptions,
MenuItem: MenuItemApiDescriptions,
+ MenuPopup: MenuPopupApiDescriptions,
+ MenuPositioner: MenuPositionerApiDescriptions,
+ MenuRoot: MenuRootApiDescriptions,
+ MenuTrigger: MenuTriggerApiDescriptions,
+ SubmenuTrigger: SubmenuTriggerApiDescriptions,
},
componentsApiPageContents: {
- Dropdown: DropdownApiJsonPageContent,
- Menu: MenuApiJsonPageContent,
- MenuButton: MenuButtonApiJsonPageContent,
+ MenuArrow: MenuArrowApiJsonPageContent,
MenuItem: MenuItemApiJsonPageContent,
+ MenuPopup: MenuPopupApiJsonPageContent,
+ MenuPositioner: MenuPositionerApiJsonPageContent,
+ MenuRoot: MenuRootApiJsonPageContent,
+ MenuTrigger: MenuTriggerApiJsonPageContent,
+ SubmenuTrigger: SubmenuTriggerApiJsonPageContent,
},
- hooksApiDescriptions: {
- useDropdown: useDropdownApiDescriptions,
- useMenu: useMenuApiDescriptions,
- useMenuButton: useMenuButtonApiDescriptions,
- useMenuItem: useMenuItemApiDescriptions,
- useMenuItemContextStabilizer: useMenuItemContextStabilizerApiDescriptions,
- },
- hooksApiPageContents: {
- useDropdown: useDropdownApiJsonPageContent,
- useMenu: useMenuApiJsonPageContent,
- useMenuButton: useMenuButtonApiJsonPageContent,
- useMenuItem: useMenuItemApiJsonPageContent,
- useMenuItemContextStabilizer: useMenuItemContextStabilizerApiJsonPageContent,
- },
+ hooksApiDescriptions: {},
+ hooksApiPageContents: {},
},
};
};
diff --git a/docs/translations/api-docs/dropdown/dropdown.json b/docs/translations/api-docs/dropdown/dropdown.json
deleted file mode 100644
index 2a8cab7592..0000000000
--- a/docs/translations/api-docs/dropdown/dropdown.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "componentDescription": "",
- "propDescriptions": {
- "defaultOpen": { "description": "If true
, the dropdown is initially open." },
- "onOpenChange": {
- "description": "Callback fired when the component requests to be opened or closed."
- },
- "open": {
- "description": "Allows to control whether the dropdown is open. This is a controlled counterpart of defaultOpen
."
- }
- },
- "classDescriptions": {}
-}
diff --git a/docs/translations/api-docs/menu-arrow/menu-arrow.json b/docs/translations/api-docs/menu-arrow/menu-arrow.json
new file mode 100644
index 0000000000..ab725d8495
--- /dev/null
+++ b/docs/translations/api-docs/menu-arrow/menu-arrow.json
@@ -0,0 +1,13 @@
+{
+ "componentDescription": "Renders an arrow that points to the center of the anchor element.",
+ "propDescriptions": {
+ "className": {
+ "description": "Class names applied to the element or a function that returns them based on the component's state."
+ },
+ "hideWhenUncentered": {
+ "description": "If true
, the arrow is hidden when it can't point to the center of the anchor element."
+ },
+ "render": { "description": "A function to customize rendering of the component." }
+ },
+ "classDescriptions": {}
+}
diff --git a/docs/translations/api-docs/menu-item/menu-item.json b/docs/translations/api-docs/menu-item/menu-item.json
index b394c325a0..46541ccb2f 100644
--- a/docs/translations/api-docs/menu-item/menu-item.json
+++ b/docs/translations/api-docs/menu-item/menu-item.json
@@ -1,17 +1,15 @@
{
"componentDescription": "An unstyled menu item to be used within a Menu.",
"propDescriptions": {
- "disabled": { "description": "If true
, the menu item will be disabled." },
- "disableFocusOnHover": {
- "description": "If true
, the menu item won't receive focus when the mouse moves over it."
+ "closeOnClick": {
+ "description": "If true
, the menu will close when the menu item is clicked."
},
+ "disabled": { "description": "If true
, the menu item will be disabled." },
+ "id": { "description": "The id of the menu item." },
"label": {
"description": "A text representation of the menu item's content. Used for keyboard text navigation matching."
},
- "slotProps": { "description": "The props used for each slot inside the MenuItem." },
- "slots": {
- "description": "The components used for each slot inside the MenuItem. Either a string to use a HTML element or a component."
- }
+ "onClick": { "description": "The click handler for the menu item." }
},
"classDescriptions": {}
}
diff --git a/docs/translations/api-docs/menu-popup/menu-popup.json b/docs/translations/api-docs/menu-popup/menu-popup.json
new file mode 100644
index 0000000000..4a1c0a2068
--- /dev/null
+++ b/docs/translations/api-docs/menu-popup/menu-popup.json
@@ -0,0 +1,11 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "className": {
+ "description": "Class names applied to the element or a function that returns them based on the component's state."
+ },
+ "id": { "description": "The id of the popup element." },
+ "render": { "description": "A function to customize rendering of the component." }
+ },
+ "classDescriptions": {}
+}
diff --git a/docs/translations/api-docs/menu-positioner/menu-positioner.json b/docs/translations/api-docs/menu-positioner/menu-positioner.json
new file mode 100644
index 0000000000..23fd5b5ac1
--- /dev/null
+++ b/docs/translations/api-docs/menu-positioner/menu-positioner.json
@@ -0,0 +1,43 @@
+{
+ "componentDescription": "Renders the element that positions the Menu popup.",
+ "propDescriptions": {
+ "alignment": {
+ "description": "The alignment of the Menu element to the anchor element along its cross axis."
+ },
+ "alignmentOffset": {
+ "description": "The offset of the Menu element along its alignment axis."
+ },
+ "anchor": { "description": "The anchor element to which the Menu popup will be placed at." },
+ "arrowPadding": {
+ "description": "Determines the padding between the arrow and the Menu popup's edges. Useful when the popover popup has rounded corners via border-radius
."
+ },
+ "className": {
+ "description": "Class names applied to the element or a function that returns them based on the component's state."
+ },
+ "collisionBoundary": {
+ "description": "The boundary that the Menu element should be constrained to."
+ },
+ "collisionPadding": { "description": "The padding of the collision boundary." },
+ "container": {
+ "description": "The container element to which the Menu popup will be appended to."
+ },
+ "hideWhenDetached": {
+ "description": "If true
, the Menu will be hidden if it is detached from its anchor element due to differing clipping contexts."
+ },
+ "keepMounted": {
+ "description": "Whether the menu popup remains mounted in the DOM while closed."
+ },
+ "positionStrategy": {
+ "description": "The CSS position strategy for positioning the Menu popup element."
+ },
+ "render": { "description": "A function to customize rendering of the component." },
+ "side": {
+ "description": "The side of the anchor element that the Menu element should align to."
+ },
+ "sideOffset": { "description": "The gap between the anchor element and the Menu element." },
+ "sticky": {
+ "description": "If true
, allow the Menu to remain in stuck view while the anchor element is scrolled out of view."
+ }
+ },
+ "classDescriptions": {}
+}
diff --git a/docs/translations/api-docs/menu-root/menu-root.json b/docs/translations/api-docs/menu-root/menu-root.json
new file mode 100644
index 0000000000..303ba55e4d
--- /dev/null
+++ b/docs/translations/api-docs/menu-root/menu-root.json
@@ -0,0 +1,31 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "animated": {
+ "description": "If true
, the Menu supports CSS-based animations and transitions. It is kept in the DOM until the animation completes."
+ },
+ "closeParentOnEsc": {
+ "description": "Determines if pressing the Esc key closes the parent menus. This is only applicable for nested menus. If set to false
pressing Esc closes only the current menu."
+ },
+ "defaultOpen": { "description": "If true
, the Menu is initially open." },
+ "delay": {
+ "description": "The delay in milliseconds until the menu popup is opened when openOnHover
is true
."
+ },
+ "dir": { "description": "The direction of the Menu (left-to-right or right-to-left)." },
+ "disabled": { "description": "If true
, the Menu is disabled." },
+ "loop": {
+ "description": "If true
, using keyboard navigation will wrap focus to the other end of the list once the end is reached."
+ },
+ "onOpenChange": {
+ "description": "Callback fired when the component requests to be opened or closed."
+ },
+ "open": {
+ "description": "Allows to control whether the dropdown is open. This is a controlled counterpart of defaultOpen
."
+ },
+ "openOnHover": {
+ "description": "Whether the menu popup opens when the trigger is hovered after the provided delay
."
+ },
+ "orientation": { "description": "The orientation of the Menu (horizontal or vertical)." }
+ },
+ "classDescriptions": {}
+}
diff --git a/docs/translations/api-docs/menu-button/menu-button.json b/docs/translations/api-docs/menu-trigger/menu-trigger.json
similarity index 53%
rename from docs/translations/api-docs/menu-button/menu-button.json
rename to docs/translations/api-docs/menu-trigger/menu-trigger.json
index b8483773fb..814aa3e491 100644
--- a/docs/translations/api-docs/menu-button/menu-button.json
+++ b/docs/translations/api-docs/menu-trigger/menu-trigger.json
@@ -1,16 +1,15 @@
{
"componentDescription": "",
"propDescriptions": {
- "className": { "description": "Class name applied to the root element." },
+ "className": {
+ "description": "Class names applied to the element or a function that returns them based on the component's state."
+ },
"disabled": { "description": "If true
, the component is disabled." },
"focusableWhenDisabled": {
"description": "If true
, allows a disabled button to receive focus."
},
"label": { "description": "Label of the button" },
- "slotProps": {
- "description": "The components used for each slot inside the MenuButton. Either a string to use a HTML element or a component."
- },
- "slots": { "description": "The props used for each slot inside the MenuButton." }
+ "render": { "description": "A function to customize rendering of the component." }
},
"classDescriptions": {}
}
diff --git a/docs/translations/api-docs/menu/menu.json b/docs/translations/api-docs/menu/menu.json
deleted file mode 100644
index 8a099d8f0a..0000000000
--- a/docs/translations/api-docs/menu/menu.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "componentDescription": "",
- "propDescriptions": {
- "actions": {
- "description": "A ref with imperative actions that can be performed on the menu."
- },
- "anchor": { "description": "The element based on which the menu is positioned." },
- "onItemsChange": {
- "description": "Function called when the items displayed in the menu change."
- },
- "slotProps": { "description": "The props used for each slot inside the Menu." },
- "slots": {
- "description": "The components used for each slot inside the Menu. Either a string to use a HTML element or a component."
- }
- },
- "classDescriptions": {}
-}
diff --git a/docs/translations/api-docs/submenu-trigger/submenu-trigger.json b/docs/translations/api-docs/submenu-trigger/submenu-trigger.json
new file mode 100644
index 0000000000..fc150adb6b
--- /dev/null
+++ b/docs/translations/api-docs/submenu-trigger/submenu-trigger.json
@@ -0,0 +1,17 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "className": {
+ "description": "Class names applied to the element or a function that returns them based on the component's state."
+ },
+ "disabled": { "description": "If true
, the menu item will be disabled." },
+ "disableFocusOnHover": {
+ "description": "If true
, the menu item won't receive focus when the mouse moves over it."
+ },
+ "label": {
+ "description": "A text representation of the menu item's content. Used for keyboard text navigation matching."
+ },
+ "render": { "description": "A function to customize rendering of the component." }
+ },
+ "classDescriptions": {}
+}
diff --git a/docs/translations/api-docs/use-dropdown/use-dropdown.json b/docs/translations/api-docs/use-menu-arrow/use-menu-arrow.json
similarity index 100%
rename from docs/translations/api-docs/use-dropdown/use-dropdown.json
rename to docs/translations/api-docs/use-menu-arrow/use-menu-arrow.json
diff --git a/docs/translations/api-docs/use-menu-item-context-stabilizer/use-menu-item-context-stabilizer.json b/docs/translations/api-docs/use-menu-item-context-stabilizer/use-menu-item-context-stabilizer.json
deleted file mode 100644
index e9fb6768d1..0000000000
--- a/docs/translations/api-docs/use-menu-item-context-stabilizer/use-menu-item-context-stabilizer.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "hookDescription": "Stabilizes the ListContext value for the MenuItem component, so it doesn't change when sibling items update.",
- "parametersDescriptions": {},
- "returnValueDescriptions": {}
-}
diff --git a/docs/translations/api-docs/use-menu-button/use-menu-button.json b/docs/translations/api-docs/use-menu-popup/use-menu-popup.json
similarity index 100%
rename from docs/translations/api-docs/use-menu-button/use-menu-button.json
rename to docs/translations/api-docs/use-menu-popup/use-menu-popup.json
diff --git a/docs/translations/api-docs/use-menu/use-menu.json b/docs/translations/api-docs/use-menu-positioner/use-menu-positioner.json
similarity index 100%
rename from docs/translations/api-docs/use-menu/use-menu.json
rename to docs/translations/api-docs/use-menu-positioner/use-menu-positioner.json
diff --git a/docs/translations/api-docs/use-menu-root/use-menu-root.json b/docs/translations/api-docs/use-menu-root/use-menu-root.json
new file mode 100644
index 0000000000..e3eb65c6e4
--- /dev/null
+++ b/docs/translations/api-docs/use-menu-root/use-menu-root.json
@@ -0,0 +1 @@
+{ "hookDescription": "", "parametersDescriptions": {}, "returnValueDescriptions": {} }
diff --git a/docs/translations/api-docs/use-menu-trigger/use-menu-trigger.json b/docs/translations/api-docs/use-menu-trigger/use-menu-trigger.json
new file mode 100644
index 0000000000..e3eb65c6e4
--- /dev/null
+++ b/docs/translations/api-docs/use-menu-trigger/use-menu-trigger.json
@@ -0,0 +1 @@
+{ "hookDescription": "", "parametersDescriptions": {}, "returnValueDescriptions": {} }
diff --git a/docs/translations/api-docs/use-submenu-trigger/use-submenu-trigger.json b/docs/translations/api-docs/use-submenu-trigger/use-submenu-trigger.json
new file mode 100644
index 0000000000..e3eb65c6e4
--- /dev/null
+++ b/docs/translations/api-docs/use-submenu-trigger/use-submenu-trigger.json
@@ -0,0 +1 @@
+{ "hookDescription": "", "parametersDescriptions": {}, "returnValueDescriptions": {} }
diff --git a/docs/translations/translations.json b/docs/translations/translations.json
index eb4c2c061d..fc91ba4b6e 100644
--- a/docs/translations/translations.json
+++ b/docs/translations/translations.json
@@ -233,6 +233,7 @@
"/base-ui/react-dialog": "Dialog",
"/base-ui/react-progress": "Progress",
"navigation": "Navigation",
+ "/base-ui/react-menu": "Menu",
"/base-ui/react-tabs": "Tabs",
"/base-ui/guides": "How-to guides",
"/base-ui/guides/next-js-app-router": "Next.js App Router"
diff --git a/package.json b/package.json
index 6442df8a9e..1d69b148b6 100644
--- a/package.json
+++ b/package.json
@@ -78,11 +78,11 @@
"@babel/register": "^7.24.6",
"@mui/internal-docs-utils": "^1.0.8",
"@mui/internal-markdown": "^1.0.8",
- "@mui/internal-scripts": "^1.0.13",
- "@mui/internal-test-utils": "1.0.5",
- "@mui/material": "6.0.0-beta.2",
- "@mui/monorepo": "github:mui/material-ui#v6.0.0-beta.2",
- "@mui/utils": "6.0.0-beta.1",
+ "@mui/internal-scripts": "^1.0.14",
+ "@mui/internal-test-utils": "1.0.6",
+ "@mui/material": "6.0.0-beta.4",
+ "@mui/monorepo": "github:mui/material-ui#v6.0.0-beta.4",
+ "@mui/utils": "6.0.0-beta.4",
"@next/eslint-plugin-next": "^14.2.5",
"@octokit/rest": "^20.1.1",
"@playwright/test": "1.45.3",
diff --git a/packages/mui-base/package.json b/packages/mui-base/package.json
index ccb7fe7155..ab08b27b90 100644
--- a/packages/mui-base/package.json
+++ b/packages/mui-base/package.json
@@ -49,7 +49,7 @@
},
"devDependencies": {
"@mui/internal-babel-macros": "^1.0.1",
- "@mui/internal-test-utils": "1.0.5",
+ "@mui/internal-test-utils": "1.0.6",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2",
"@types/chai": "^4.3.16",
@@ -61,8 +61,8 @@
"chai": "^4.4.1",
"fast-glob": "^3.3.2",
"lodash": "^4.17.21",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
"sinon": "^17.0.1",
"typescript": "^5.4.5"
},
diff --git a/packages/mui-base/src/Menu/Arrow/MenuArrow.test.tsx b/packages/mui-base/src/Menu/Arrow/MenuArrow.test.tsx
new file mode 100644
index 0000000000..b121e8e80d
--- /dev/null
+++ b/packages/mui-base/src/Menu/Arrow/MenuArrow.test.tsx
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import * as Menu from '@base_ui/react/Menu';
+import { createRenderer, describeConformance } from '../../../test';
+
+describe(' ', () => {
+ const { render } = createRenderer();
+
+ describeConformance( , () => ({
+ inheritComponent: 'div',
+ refInstanceof: window.HTMLDivElement,
+ render(node) {
+ return render(
+
+
+ {node}
+
+ ,
+ );
+ },
+ }));
+});
diff --git a/packages/mui-base/src/Menu/Arrow/MenuArrow.tsx b/packages/mui-base/src/Menu/Arrow/MenuArrow.tsx
new file mode 100644
index 0000000000..dcf8bb5719
--- /dev/null
+++ b/packages/mui-base/src/Menu/Arrow/MenuArrow.tsx
@@ -0,0 +1,104 @@
+'use client';
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { useMenuArrow } from './useMenuArrow';
+import { useMenuPositionerContext } from '../Positioner/MenuPositionerContext';
+import { useMenuRootContext } from '../Root/MenuRootContext';
+import { commonStyleHooks } from '../utils/commonStyleHooks';
+import { useComponentRenderer } from '../../utils/useComponentRenderer';
+import { useForkRef } from '../../utils/useForkRef';
+import type { Side, Alignment } from '../../utils/useAnchorPositioning';
+import type { BaseUIComponentProps } from '../../utils/types';
+
+/**
+ * Renders an arrow that points to the center of the anchor element.
+ *
+ * Demos:
+ *
+ * - [Menu](https://mui.com/base-ui/react-menu/)
+ *
+ * API:
+ *
+ * - [MenuArrow API](https://mui.com/base-ui/react-menu/components-api/#menu-arrow)
+ */
+const MenuArrow = React.forwardRef(function MenuArrow(
+ props: MenuArrow.Props,
+ forwardedRef: React.ForwardedRef,
+) {
+ const { className, render, hideWhenUncentered = false, ...otherProps } = props;
+
+ const { open } = useMenuRootContext();
+ const { arrowRef, side, alignment, arrowUncentered, arrowStyles } = useMenuPositionerContext();
+
+ const { getArrowProps } = useMenuArrow({
+ arrowStyles,
+ hidden: hideWhenUncentered && arrowUncentered,
+ });
+
+ const ownerState: MenuArrow.OwnerState = React.useMemo(
+ () => ({
+ open,
+ side,
+ alignment,
+ arrowUncentered,
+ }),
+ [open, side, alignment, arrowUncentered],
+ );
+
+ const mergedRef = useForkRef(arrowRef, forwardedRef);
+
+ const { renderElement } = useComponentRenderer({
+ propGetter: getArrowProps,
+ render: render ?? 'div',
+ className,
+ ownerState,
+ ref: mergedRef,
+ extraProps: otherProps,
+ customStyleHookMapping: commonStyleHooks,
+ });
+
+ return renderElement();
+});
+
+namespace MenuArrow {
+ export type OwnerState = {
+ open: boolean;
+ side: Side;
+ alignment: Alignment;
+ arrowUncentered: boolean;
+ };
+
+ export interface Props extends BaseUIComponentProps<'div', OwnerState> {
+ /**
+ * If `true`, the arrow is hidden when it can't point to the center of the anchor element.
+ * @default false
+ */
+ hideWhenUncentered?: boolean;
+ }
+}
+
+MenuArrow.propTypes /* remove-proptypes */ = {
+ // ┌────────────────────────────── Warning ──────────────────────────────┐
+ // │ These PropTypes are generated from the TypeScript type definitions. │
+ // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
+ // └─────────────────────────────────────────────────────────────────────┘
+ /**
+ * @ignore
+ */
+ children: PropTypes.node,
+ /**
+ * Class names applied to the element or a function that returns them based on the component's state.
+ */
+ className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+ /**
+ * If `true`, the arrow is hidden when it can't point to the center of the anchor element.
+ * @default false
+ */
+ hideWhenUncentered: PropTypes.bool,
+ /**
+ * A function to customize rendering of the component.
+ */
+ render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
+} as any;
+
+export { MenuArrow };
diff --git a/packages/mui-base/src/Menu/Arrow/useMenuArrow.ts b/packages/mui-base/src/Menu/Arrow/useMenuArrow.ts
new file mode 100644
index 0000000000..becddbf0ba
--- /dev/null
+++ b/packages/mui-base/src/Menu/Arrow/useMenuArrow.ts
@@ -0,0 +1,41 @@
+'use client';
+import * as React from 'react';
+import { mergeReactProps } from '../../utils/mergeReactProps';
+import type { GenericHTMLProps } from '../../utils/types';
+
+/**
+ *
+ * API:
+ *
+ * - [useMenuArrow API](https://mui.com/base-ui/api/use-menu-arrow/)
+ */
+export function useMenuArrow(params: useMenuArrow.Parameters): useMenuArrow.ReturnValue {
+ const { arrowStyles } = params;
+
+ const getArrowProps = React.useCallback(
+ (externalProps = {}) => {
+ return mergeReactProps<'div'>(externalProps, {
+ style: arrowStyles,
+ });
+ },
+ [arrowStyles],
+ );
+
+ return React.useMemo(
+ () => ({
+ getArrowProps,
+ }),
+ [getArrowProps],
+ );
+}
+
+export namespace useMenuArrow {
+ export interface Parameters {
+ arrowStyles: React.CSSProperties;
+ hidden?: boolean;
+ }
+
+ export interface ReturnValue {
+ getArrowProps: (externalProps?: GenericHTMLProps) => GenericHTMLProps;
+ }
+}
diff --git a/packages/mui-base/src/Menu/Item/MenuItem.test.tsx b/packages/mui-base/src/Menu/Item/MenuItem.test.tsx
new file mode 100644
index 0000000000..99bbbb0ed2
--- /dev/null
+++ b/packages/mui-base/src/Menu/Item/MenuItem.test.tsx
@@ -0,0 +1,156 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { spy } from 'sinon';
+import userEvent from '@testing-library/user-event';
+import { fireEvent, act, waitFor } from '@mui/internal-test-utils';
+import { FloatingRootContext, FloatingTree } from '@floating-ui/react';
+import * as Menu from '@base_ui/react/Menu';
+import { MenuRootContext } from '@base_ui/react/Menu';
+import { describeConformance, createRenderer } from '../../../test';
+
+const testRootContext: MenuRootContext = {
+ floatingRootContext: {} as FloatingRootContext,
+ getPositionerProps: (p) => ({ ...p }),
+ getTriggerProps: (p) => ({ ...p }),
+ getItemProps: (p) => ({ ...p }),
+ parentContext: null,
+ nested: false,
+ triggerElement: null,
+ setTriggerElement: () => {},
+ setPositionerElement: () => {},
+ activeIndex: null,
+ disabled: false,
+ itemDomElements: { current: [] },
+ itemLabels: { current: [] },
+ open: true,
+ setOpen: () => {},
+ clickAndDragEnabled: false,
+ setClickAndDragEnabled: () => {},
+ popupRef: { current: null },
+ mounted: true,
+ transitionStatus: undefined,
+};
+
+describe(' ', () => {
+ const { render } = createRenderer();
+ const user = userEvent.setup();
+
+ describeConformance( , () => ({
+ render: (node) => {
+ return render(
+
+ {node}
+ ,
+ );
+ },
+ refInstanceof: window.HTMLDivElement,
+ }));
+
+ it('perf: does not rerender menu items unnecessarily', async () => {
+ const renderItem1Spy = spy();
+ const renderItem2Spy = spy();
+ const renderItem3Spy = spy();
+ const renderItem4Spy = spy();
+
+ const LoggingRoot = React.forwardRef(function LoggingRoot(
+ props: any & { renderSpy: () => void },
+ ref: React.ForwardedRef,
+ ) {
+ const { renderSpy, ownerState, ...other } = props;
+ renderSpy();
+ return ;
+ });
+
+ const { getAllByRole } = await render(
+
+
+
+ } id="item-1">
+ 1
+
+ } id="item-2">
+ 2
+
+ } id="item-3">
+ 3
+
+ } id="item-4">
+ 4
+
+
+
+ ,
+ );
+
+ const menuItems = getAllByRole('menuitem');
+ await act(() => {
+ menuItems[0].focus();
+ });
+
+ renderItem1Spy.resetHistory();
+ renderItem2Spy.resetHistory();
+ renderItem3Spy.resetHistory();
+ renderItem4Spy.resetHistory();
+
+ expect(renderItem1Spy.callCount).to.equal(0);
+
+ fireEvent.keyDown(menuItems[0], { key: 'ArrowDown' }); // highlights '2'
+
+ // React renders twice in strict mode, so we expect twice the number of spy calls
+ // Also, useButton's focusVisible polyfill causes an extra render when focus is gained/lost.
+
+ await waitFor(() => {
+ expect(renderItem1Spy.callCount).to.equal(4); // '1' rerenders as it loses highlight
+ expect(renderItem2Spy.callCount).to.equal(4); // '2' rerenders as it receives highlight
+ });
+
+ // neither the highlighted nor the selected state of these options changed,
+ // so they don't need to rerender:
+ expect(renderItem3Spy.callCount).to.equal(0);
+ expect(renderItem4Spy.callCount).to.equal(0);
+ });
+
+ describe('prop: closeOnClick', () => {
+ it('closes the menu when the item is clicked by default', async () => {
+ const { getByRole, queryByRole } = await render(
+
+ Open
+
+
+ Item
+
+
+ ,
+ );
+
+ const trigger = getByRole('button', { name: 'Open' });
+ await user.click(trigger);
+
+ const item = getByRole('menuitem');
+ await user.click(item);
+
+ expect(queryByRole('menu')).to.equal(null);
+ });
+
+ it('when `closeOnClick=false` does not close the menu when the item is clicked', async () => {
+ const { getByRole, queryByRole } = await render(
+
+ Open
+
+
+ Item
+
+
+ ,
+ );
+
+ const trigger = getByRole('button', { name: 'Open' });
+ await user.click(trigger);
+
+ const item = getByRole('menuitem');
+ await user.click(item);
+
+ expect(queryByRole('menu')).not.to.equal(null);
+ });
+ });
+});
diff --git a/packages/mui-base/src/Menu/Item/MenuItem.tsx b/packages/mui-base/src/Menu/Item/MenuItem.tsx
new file mode 100644
index 0000000000..e758a7d14f
--- /dev/null
+++ b/packages/mui-base/src/Menu/Item/MenuItem.tsx
@@ -0,0 +1,175 @@
+'use client';
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { FloatingEvents, useFloatingTree, useListItem } from '@floating-ui/react';
+import { useMenuItem } from './useMenuItem';
+import { useMenuRootContext } from '../Root/MenuRootContext';
+import { useComponentRenderer } from '../../utils/useComponentRenderer';
+import { useId } from '../../utils/useId';
+import type { BaseUIComponentProps, GenericHTMLProps } from '../../utils/types';
+import { useForkRef } from '../../utils/useForkRef';
+
+const InnerMenuItem = React.memo(
+ React.forwardRef(function InnerMenuItem(
+ props: InnerMenuItemProps,
+ forwardedRef: React.ForwardedRef,
+ ) {
+ const {
+ className,
+ closeOnClick = true,
+ disabled = false,
+ highlighted,
+ id,
+ menuEvents,
+ propGetter,
+ render,
+ treatMouseupAsClick,
+ ...other
+ } = props;
+
+ const { getRootProps } = useMenuItem({
+ closeOnClick,
+ disabled,
+ highlighted,
+ id,
+ menuEvents,
+ ref: forwardedRef,
+ treatMouseupAsClick,
+ });
+
+ const ownerState: MenuItem.OwnerState = { disabled, highlighted };
+
+ const { renderElement } = useComponentRenderer({
+ render: render || 'div',
+ className,
+ ownerState,
+ propGetter: (externalProps) => propGetter(getRootProps(externalProps)),
+ extraProps: other,
+ });
+
+ return renderElement();
+ }),
+);
+
+/**
+ * An unstyled menu item to be used within a Menu.
+ *
+ * Demos:
+ *
+ * - [Menu](https://mui.com/base-ui/react-menu/)
+ *
+ * API:
+ *
+ * - [MenuItem API](https://mui.com/base-ui/react-menu/components-api/#menu-item)
+ */
+const MenuItem = React.forwardRef(function MenuItem(
+ props: MenuItem.Props,
+ forwardedRef: React.ForwardedRef,
+) {
+ const { id: idProp, label, ...other } = props;
+
+ const itemRef = React.useRef(null);
+ const listItem = useListItem({ label: label ?? itemRef.current?.innerText });
+ const mergedRef = useForkRef(forwardedRef, listItem.ref, itemRef);
+
+ const { getItemProps, activeIndex, clickAndDragEnabled } = useMenuRootContext();
+ const id = useId(idProp);
+
+ const highlighted = listItem.index === activeIndex;
+ const { events: menuEvents } = useFloatingTree()!;
+
+ // This wrapper component is used as a performance optimization.
+ // MenuItem reads the context and re-renders the actual MenuItem
+ // only when it needs to.
+
+ return (
+
+ );
+});
+
+interface InnerMenuItemProps extends MenuItem.Props {
+ highlighted: boolean;
+ propGetter: (externalProps?: GenericHTMLProps) => GenericHTMLProps;
+ menuEvents: FloatingEvents;
+ treatMouseupAsClick: boolean;
+}
+
+namespace MenuItem {
+ export type OwnerState = {
+ disabled: boolean;
+ highlighted: boolean;
+ };
+
+ export interface Props extends BaseUIComponentProps<'div', OwnerState> {
+ children?: React.ReactNode;
+ /**
+ * The click handler for the menu item.
+ */
+ onClick?: React.MouseEventHandler;
+ /**
+ * If `true`, the menu item will be disabled.
+ * @default false
+ */
+ disabled?: boolean;
+ /**
+ * A text representation of the menu item's content.
+ * Used for keyboard text navigation matching.
+ */
+ label?: string;
+ /**
+ * The id of the menu item.
+ */
+ id?: string;
+ /**
+ * If `true`, the menu will close when the menu item is clicked.
+ *
+ * @default true
+ */
+ closeOnClick?: boolean;
+ }
+}
+
+MenuItem.propTypes /* remove-proptypes */ = {
+ // ┌────────────────────────────── Warning ──────────────────────────────┐
+ // │ These PropTypes are generated from the TypeScript type definitions. │
+ // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
+ // └─────────────────────────────────────────────────────────────────────┘
+ /**
+ * @ignore
+ */
+ children: PropTypes.node,
+ /**
+ * If `true`, the menu will close when the menu item is clicked.
+ *
+ * @default true
+ */
+ closeOnClick: PropTypes.bool,
+ /**
+ * If `true`, the menu item will be disabled.
+ * @default false
+ */
+ disabled: PropTypes.bool,
+ /**
+ * The id of the menu item.
+ */
+ id: PropTypes.string,
+ /**
+ * A text representation of the menu item's content.
+ * Used for keyboard text navigation matching.
+ */
+ label: PropTypes.string,
+ /**
+ * The click handler for the menu item.
+ */
+ onClick: PropTypes.func,
+} as any;
+
+export { MenuItem };
diff --git a/packages/mui-base/src/Menu/Item/useMenuItem.ts b/packages/mui-base/src/Menu/Item/useMenuItem.ts
new file mode 100644
index 0000000000..ee72abddbb
--- /dev/null
+++ b/packages/mui-base/src/Menu/Item/useMenuItem.ts
@@ -0,0 +1,103 @@
+'use client';
+import * as React from 'react';
+import { FloatingEvents } from '@floating-ui/react';
+import { useButton } from '../../useButton';
+import { mergeReactProps } from '../../utils/mergeReactProps';
+import { GenericHTMLProps } from '../../utils/types';
+
+/**
+ *
+ * API:
+ *
+ * - [useMenuItem API](https://mui.com/base-ui/api/use-menu-item/)
+ */
+export function useMenuItem(params: useMenuItem.Parameters): useMenuItem.ReturnValue {
+ const {
+ closeOnClick,
+ disabled = false,
+ highlighted,
+ id,
+ menuEvents,
+ ref: externalRef,
+ treatMouseupAsClick,
+ } = params;
+
+ const { getRootProps: getButtonProps, rootRef: mergedRef } = useButton({
+ disabled,
+ focusableWhenDisabled: true,
+ rootRef: externalRef,
+ });
+
+ const getRootProps = React.useCallback(
+ (externalProps?: GenericHTMLProps): GenericHTMLProps => {
+ return getButtonProps(
+ mergeReactProps(externalProps, {
+ 'data-handle-mouseup': treatMouseupAsClick || undefined,
+ id,
+ role: 'menuitem',
+ tabIndex: highlighted ? 0 : -1,
+ onClick: (event: React.MouseEvent) => {
+ if (closeOnClick) {
+ menuEvents.emit('close', event);
+ }
+ },
+ }),
+ );
+ },
+ [closeOnClick, getButtonProps, highlighted, id, menuEvents, treatMouseupAsClick],
+ );
+
+ return React.useMemo(
+ () => ({
+ getRootProps,
+ rootRef: mergedRef,
+ }),
+ [getRootProps, mergedRef],
+ );
+}
+
+export namespace useMenuItem {
+ export interface Parameters {
+ /**
+ * If `true`, the menu will close when the menu item is clicked.
+ */
+ closeOnClick: boolean;
+ /**
+ * If `true`, the menu item will be disabled.
+ */
+ disabled: boolean;
+ /**
+ * Determines if the menu item is highlighted.
+ */
+ highlighted: boolean;
+ /**
+ * The id of the menu item.
+ */
+ id: string | undefined;
+ /**
+ * The FloatingEvents instance of the menu's root.
+ */
+ menuEvents: FloatingEvents;
+ /**
+ * The ref of the trigger element.
+ */
+ ref?: React.Ref;
+ /**
+ * If `true`, the menu item will listen for mouseup events and treat them as clicks.
+ */
+ treatMouseupAsClick: boolean;
+ }
+
+ export interface ReturnValue {
+ /**
+ * Resolver for the root slot's props.
+ * @param externalProps event handlers for the root slot
+ * @returns props that should be spread on the root slot
+ */
+ getRootProps: (externalProps?: GenericHTMLProps) => GenericHTMLProps;
+ /**
+ * The ref to the component's root DOM element.
+ */
+ rootRef: React.RefCallback | null;
+ }
+}
diff --git a/packages/mui-base/src/Menu/Popup/MenuPopup.test.tsx b/packages/mui-base/src/Menu/Popup/MenuPopup.test.tsx
new file mode 100644
index 0000000000..04a4141ca7
--- /dev/null
+++ b/packages/mui-base/src/Menu/Popup/MenuPopup.test.tsx
@@ -0,0 +1,58 @@
+import * as React from 'react';
+import * as Menu from '@base_ui/react/Menu';
+import { MenuRootContext } from '@base_ui/react/Menu';
+import { FloatingRootContext, FloatingTree } from '@floating-ui/react';
+import { createRenderer, describeConformance } from '../../../test';
+import { MenuPositionerContext } from '../Positioner/MenuPositionerContext';
+
+const testRootContext: MenuRootContext = {
+ floatingRootContext: {} as FloatingRootContext,
+ getPositionerProps: (p) => ({ ...p }),
+ getTriggerProps: (p) => ({ ...p }),
+ getItemProps: (p) => ({ ...p }),
+ parentContext: null,
+ nested: false,
+ triggerElement: null,
+ setTriggerElement: () => {},
+ setPositionerElement: () => {},
+ activeIndex: null,
+ disabled: false,
+ itemDomElements: { current: [] },
+ itemLabels: { current: [] },
+ open: true,
+ setOpen: () => {},
+ clickAndDragEnabled: false,
+ setClickAndDragEnabled: () => {},
+ popupRef: { current: null },
+ mounted: true,
+ transitionStatus: undefined,
+};
+
+const testPositionerContext: MenuPositionerContext = {
+ side: 'bottom',
+ alignment: 'start',
+ arrowRef: { current: null },
+ arrowStyles: {},
+ arrowUncentered: false,
+};
+
+describe(' ', () => {
+ const { render } = createRenderer();
+
+ describeConformance( , () => ({
+ inheritComponent: 'div',
+ render: (node) => {
+ return render(
+
+
+
+ {node}
+
+
+ ,
+ ,
+ );
+ },
+ refInstanceof: window.HTMLDivElement,
+ }));
+});
diff --git a/packages/mui-base/src/Menu/Popup/MenuPopup.tsx b/packages/mui-base/src/Menu/Popup/MenuPopup.tsx
new file mode 100644
index 0000000000..ad77be5a40
--- /dev/null
+++ b/packages/mui-base/src/Menu/Popup/MenuPopup.tsx
@@ -0,0 +1,101 @@
+'use client';
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { Side, useFloatingTree } from '@floating-ui/react';
+import { useMenuPopup } from './useMenuPopup';
+import { useMenuRootContext } from '../Root/MenuRootContext';
+import { useMenuPositionerContext } from '../Positioner/MenuPositionerContext';
+import { commonStyleHooks } from '../utils/commonStyleHooks';
+import { useComponentRenderer } from '../../utils/useComponentRenderer';
+import { useForkRef } from '../../utils/useForkRef';
+import { BaseUIComponentProps } from '../../utils/types';
+import { CustomStyleHookMapping } from '../../utils/getStyleHookProps';
+
+const customStyleHookMapping: CustomStyleHookMapping = {
+ ...commonStyleHooks,
+ entering(value) {
+ return value ? { 'data-menu-entering': '' } : null;
+ },
+ exiting(value) {
+ return value ? { 'data-menu-exiting': '' } : null;
+ },
+};
+
+const MenuPopup = React.forwardRef(function MenuPopup(
+ props: MenuPopup.Props,
+ forwardedRef: React.ForwardedRef,
+) {
+ const { render, className, ...other } = props;
+ const { open, setOpen, popupRef, transitionStatus } = useMenuRootContext();
+ const { side, alignment } = useMenuPositionerContext();
+ const { events: menuEvents } = useFloatingTree()!;
+
+ useMenuPopup({
+ setOpen,
+ menuEvents,
+ });
+
+ const mergedRef = useForkRef(forwardedRef, popupRef);
+
+ const ownerState: MenuPopup.OwnerState = {
+ entering: transitionStatus === 'entering',
+ exiting: transitionStatus === 'exiting',
+ side,
+ alignment,
+ open,
+ };
+
+ const { renderElement } = useComponentRenderer({
+ render: render || 'div',
+ className,
+ ownerState,
+ extraProps: other,
+ customStyleHookMapping,
+ ref: mergedRef,
+ });
+
+ return renderElement();
+});
+
+namespace MenuPopup {
+ export interface Props extends BaseUIComponentProps<'div', OwnerState> {
+ children?: React.ReactNode;
+ /**
+ * The id of the popup element.
+ */
+ id?: string;
+ }
+
+ export type OwnerState = {
+ entering: boolean;
+ exiting: boolean;
+ side: Side;
+ alignment: 'start' | 'end' | 'center';
+ open: boolean;
+ };
+}
+
+MenuPopup.propTypes /* remove-proptypes */ = {
+ // ┌────────────────────────────── Warning ──────────────────────────────┐
+ // │ These PropTypes are generated from the TypeScript type definitions. │
+ // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
+ // └─────────────────────────────────────────────────────────────────────┘
+ /**
+ * @ignore
+ */
+ children: PropTypes.node,
+ /**
+ * Class names applied to the element or a function that returns them based on the component's state.
+ */
+ className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+ /**
+ * The id of the popup element.
+ */
+ id: PropTypes.string,
+ /**
+ * A function to customize rendering of the component.
+ */
+ render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
+} as any;
+
+export { MenuPopup };
diff --git a/packages/mui-base/src/Menu/Popup/useMenuPopup.ts b/packages/mui-base/src/Menu/Popup/useMenuPopup.ts
new file mode 100644
index 0000000000..a7e200919a
--- /dev/null
+++ b/packages/mui-base/src/Menu/Popup/useMenuPopup.ts
@@ -0,0 +1,40 @@
+'use client';
+import * as React from 'react';
+import { FloatingEvents } from '@floating-ui/react';
+
+/**
+ *
+ * API:
+ *
+ * - [useMenuPopup API](https://mui.com/base-ui/api/use-menu-popup/)
+ */
+export function useMenuPopup(parameters: useMenuPopup.Parameters): useMenuPopup.ReturnValue {
+ const { menuEvents, setOpen } = parameters;
+
+ React.useEffect(() => {
+ function handleClose(event: Event | undefined) {
+ setOpen(false, event);
+ }
+
+ menuEvents.on('close', handleClose);
+
+ return () => {
+ menuEvents.off('close', handleClose);
+ };
+ }, [menuEvents, setOpen]);
+}
+
+export namespace useMenuPopup {
+ export interface Parameters {
+ /**
+ * The FloatingEvents instance of the menu's root.
+ */
+ menuEvents: FloatingEvents;
+ /**
+ * Callback to set the open state of the menu.
+ */
+ setOpen: (open: boolean, event: Event | undefined) => void;
+ }
+
+ export type ReturnValue = void;
+}
diff --git a/packages/mui-base/src/Menu/Positioner/MenuPositioner.test.tsx b/packages/mui-base/src/Menu/Positioner/MenuPositioner.test.tsx
new file mode 100644
index 0000000000..64e61b80b9
--- /dev/null
+++ b/packages/mui-base/src/Menu/Positioner/MenuPositioner.test.tsx
@@ -0,0 +1,177 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { FloatingRootContext, FloatingTree } from '@floating-ui/react';
+import userEvent from '@testing-library/user-event';
+import { flushMicrotasks } from '@mui/internal-test-utils';
+import * as Menu from '@base_ui/react/Menu';
+import { MenuRootContext } from '@base_ui/react/Menu';
+import { describeConformance, createRenderer } from '../../../test';
+
+const testRootContext: MenuRootContext = {
+ floatingRootContext: undefined as unknown as FloatingRootContext,
+ getPositionerProps: (p) => ({ ...p }),
+ getTriggerProps: (p) => ({ ...p }),
+ getItemProps: (p) => ({ ...p }),
+ parentContext: null,
+ nested: false,
+ triggerElement: null,
+ setTriggerElement: () => {},
+ setPositionerElement: () => {},
+ activeIndex: null,
+ disabled: false,
+ itemDomElements: { current: [] },
+ itemLabels: { current: [] },
+ open: true,
+ setOpen: () => {},
+ clickAndDragEnabled: false,
+ setClickAndDragEnabled: () => {},
+ popupRef: { current: null },
+ mounted: true,
+ transitionStatus: undefined,
+};
+
+describe(' ', () => {
+ const { render } = createRenderer();
+
+ describeConformance( , () => ({
+ render: (node) => {
+ return render(
+
+ {node}
+ ,
+ );
+ },
+ refInstanceof: window.HTMLDivElement,
+ }));
+
+ describe('prop: anchor', () => {
+ it('should be placed near the specified element', async function test() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ this.skip();
+ }
+
+ function TestComponent() {
+ const anchor = React.useRef(null);
+
+ return (
+
+ );
+ }
+
+ const { getByRole, getByTestId } = await render( );
+
+ const popup = getByRole('menu');
+ const anchor = getByTestId('anchor');
+
+ const anchorPosition = anchor.getBoundingClientRect();
+
+ await flushMicrotasks();
+
+ expect(popup.style.getPropertyValue('transform')).to.equal(
+ `translate(${anchorPosition.left}px, ${anchorPosition.bottom}px)`,
+ );
+ });
+
+ it('should be placed at the specified position', async function test() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ this.skip();
+ }
+
+ const boundingRect = {
+ x: 200,
+ y: 100,
+ top: 100,
+ left: 200,
+ bottom: 100,
+ right: 200,
+ height: 0,
+ width: 0,
+ toJSON: () => {},
+ };
+
+ const virtualElement = { getBoundingClientRect: () => boundingRect };
+
+ const { getByRole } = await render(
+
+
+
+ 1
+ 2
+
+
+ ,
+ );
+
+ const popup = getByRole('menu');
+ expect(popup.style.getPropertyValue('transform')).to.equal(`translate(200px, 100px)`);
+ });
+ });
+
+ describe('prop: keepMounted', () => {
+ const user = userEvent.setup();
+
+ it('when keepMounted=true, should keep the content mounted when closed', async () => {
+ const { getByRole, queryByRole } = await render(
+
+ Toggle
+
+
+ 1
+ 2
+
+
+ ,
+ );
+
+ const trigger = getByRole('button', { name: 'Toggle' });
+
+ expect(queryByRole('menu', { hidden: true })).not.to.equal(null);
+ expect(queryByRole('menu', { hidden: true })).toBeInaccessible();
+
+ await user.click(trigger);
+ await flushMicrotasks();
+ expect(queryByRole('menu', { hidden: false })).not.to.equal(null);
+ expect(queryByRole('menu', { hidden: false })).not.toBeInaccessible();
+
+ await user.click(trigger);
+ expect(queryByRole('menu', { hidden: true })).not.to.equal(null);
+ expect(queryByRole('menu', { hidden: true })).toBeInaccessible();
+ });
+
+ it('when keepMounted=false, should unmount the content when closed', async () => {
+ const { getByRole, queryByRole } = await render(
+
+ Toggle
+
+
+ 1
+ 2
+
+
+ ,
+ );
+
+ const trigger = getByRole('button', { name: 'Toggle' });
+
+ expect(queryByRole('menu', { hidden: true })).to.equal(null);
+
+ await user.click(trigger);
+ await flushMicrotasks();
+ expect(queryByRole('menu', { hidden: false })).not.to.equal(null);
+ expect(queryByRole('menu', { hidden: false })).not.toBeInaccessible();
+
+ await user.click(trigger);
+ expect(queryByRole('menu', { hidden: true })).to.equal(null);
+ });
+ });
+});
diff --git a/packages/mui-base/src/Menu/Positioner/MenuPositioner.tsx b/packages/mui-base/src/Menu/Positioner/MenuPositioner.tsx
new file mode 100644
index 0000000000..48baa4ce14
--- /dev/null
+++ b/packages/mui-base/src/Menu/Positioner/MenuPositioner.tsx
@@ -0,0 +1,277 @@
+'use client';
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import {
+ FloatingFocusManager,
+ FloatingList,
+ FloatingNode,
+ FloatingPortal,
+ Side,
+ useFloatingNodeId,
+} from '@floating-ui/react';
+import { MenuPositionerContext } from './MenuPositionerContext';
+import { useMenuRootContext } from '../Root/MenuRootContext';
+import { commonStyleHooks } from '../utils/commonStyleHooks';
+import { useComponentRenderer } from '../../utils/useComponentRenderer';
+import { useForkRef } from '../../utils/useForkRef';
+import { useMenuPositioner } from './useMenuPositioner';
+import { HTMLElementType } from '../../utils/proptypes';
+import { BaseUIComponentProps, GenericHTMLProps } from '../../utils/types';
+
+/**
+ * Renders the element that positions the Menu popup.
+ *
+ * Demos:
+ *
+ * - [Menu](https://mui.com/base-ui/react-Menu/)
+ *
+ * API:
+ *
+ * - [MenuPositioner API](https://mui.com/base-ui/react-Menu/components-api/#Menu-positioner)
+ */
+const MenuPositioner = React.forwardRef(function MenuPositioner(
+ props: MenuPositioner.Props,
+ forwardedRef: React.ForwardedRef,
+) {
+ const {
+ anchor,
+ positionStrategy = 'absolute',
+ className,
+ render,
+ keepMounted = false,
+ side = 'bottom',
+ alignment = 'center',
+ sideOffset = 0,
+ alignmentOffset = 0,
+ collisionBoundary,
+ collisionPadding = 5,
+ arrowPadding = 5,
+ hideWhenDetached = false,
+ sticky = false,
+ container,
+ ...otherProps
+ } = props;
+
+ const {
+ open,
+ floatingRootContext,
+ getPositionerProps,
+ setPositionerElement,
+ nested,
+ itemDomElements,
+ itemLabels,
+ triggerElement,
+ mounted,
+ } = useMenuRootContext();
+
+ const nodeId = useFloatingNodeId();
+
+ const positioner = useMenuPositioner({
+ anchor: anchor || triggerElement,
+ floatingRootContext,
+ positionStrategy,
+ container,
+ open,
+ mounted,
+ side,
+ sideOffset,
+ alignment,
+ alignmentOffset,
+ arrowPadding,
+ collisionBoundary,
+ collisionPadding,
+ hideWhenDetached,
+ sticky,
+ nodeId,
+ });
+
+ const ownerState: MenuPositioner.OwnerState = React.useMemo(
+ () => ({
+ open,
+ side: positioner.side,
+ alignment: positioner.alignment,
+ }),
+ [open, positioner.side, positioner.alignment],
+ );
+
+ const contextValue: MenuPositionerContext = React.useMemo(
+ () => ({
+ side: positioner.side,
+ alignment: positioner.alignment,
+ arrowRef: positioner.arrowRef,
+ arrowUncentered: positioner.arrowUncentered,
+ arrowStyles: positioner.arrowStyles,
+ floatingContext: positioner.floatingContext,
+ }),
+ [
+ positioner.side,
+ positioner.alignment,
+ positioner.arrowRef,
+ positioner.arrowUncentered,
+ positioner.arrowStyles,
+ positioner.floatingContext,
+ ],
+ );
+
+ const mergedRef = useForkRef(forwardedRef, setPositionerElement);
+
+ const { renderElement } = useComponentRenderer({
+ propGetter: (externalProps: GenericHTMLProps) =>
+ positioner.getPositionerProps(getPositionerProps(externalProps)),
+ render: render ?? 'div',
+ className,
+ ownerState,
+ customStyleHookMapping: commonStyleHooks,
+ ref: mergedRef,
+ extraProps: otherProps,
+ });
+
+ const shouldRender = keepMounted || mounted;
+ if (!shouldRender) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ {renderElement()}
+
+
+
+
+
+ );
+});
+
+export { MenuPositioner };
+
+export namespace MenuPositioner {
+ export type OwnerState = {
+ open: boolean;
+ side: Side;
+ alignment: 'start' | 'end' | 'center';
+ };
+
+ export interface Props
+ extends useMenuPositioner.SharedParameters,
+ BaseUIComponentProps<'div', OwnerState> {}
+}
+
+MenuPositioner.propTypes /* remove-proptypes */ = {
+ // ┌────────────────────────────── Warning ──────────────────────────────┐
+ // │ These PropTypes are generated from the TypeScript type definitions. │
+ // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
+ // └─────────────────────────────────────────────────────────────────────┘
+ /**
+ * The alignment of the Menu element to the anchor element along its cross axis.
+ * @default 'center'
+ */
+ alignment: PropTypes.oneOf(['center', 'end', 'start']),
+ /**
+ * The offset of the Menu element along its alignment axis.
+ * @default 0
+ */
+ alignmentOffset: PropTypes.number,
+ /**
+ * The anchor element to which the Menu popup will be placed at.
+ */
+ anchor: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ HTMLElementType,
+ PropTypes.object,
+ PropTypes.func,
+ ]),
+ /**
+ * Determines the padding between the arrow and the Menu popup's edges. Useful when the popover
+ * popup has rounded corners via `border-radius`.
+ * @default 5
+ */
+ arrowPadding: PropTypes.number,
+ /**
+ * @ignore
+ */
+ children: PropTypes.node,
+ /**
+ * Class names applied to the element or a function that returns them based on the component's state.
+ */
+ className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+ /**
+ * The boundary that the Menu element should be constrained to.
+ * @default 'clippingAncestors'
+ */
+ collisionBoundary: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ HTMLElementType,
+ PropTypes.arrayOf(HTMLElementType),
+ PropTypes.string,
+ PropTypes.shape({
+ height: PropTypes.number,
+ width: PropTypes.number,
+ x: PropTypes.number,
+ y: PropTypes.number,
+ }),
+ ]),
+ /**
+ * The padding of the collision boundary.
+ * @default 5
+ */
+ collisionPadding: PropTypes.oneOfType([
+ PropTypes.number,
+ PropTypes.shape({
+ bottom: PropTypes.number,
+ left: PropTypes.number,
+ right: PropTypes.number,
+ top: PropTypes.number,
+ }),
+ ]),
+ /**
+ * The container element to which the Menu popup will be appended to.
+ */
+ container: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ HTMLElementType,
+ PropTypes.func,
+ ]),
+ /**
+ * If `true`, the Menu will be hidden if it is detached from its anchor element due to
+ * differing clipping contexts.
+ * @default false
+ */
+ hideWhenDetached: PropTypes.bool,
+ /**
+ * Whether the menu popup remains mounted in the DOM while closed.
+ * @default false
+ */
+ keepMounted: PropTypes.bool,
+ /**
+ * The CSS position strategy for positioning the Menu popup element.
+ * @default 'absolute'
+ */
+ positionStrategy: PropTypes.oneOf(['absolute', 'fixed']),
+ /**
+ * A function to customize rendering of the component.
+ */
+ render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
+ /**
+ * The side of the anchor element that the Menu element should align to.
+ * @default 'bottom'
+ */
+ side: PropTypes.oneOf(['bottom', 'left', 'right', 'top']),
+ /**
+ * The gap between the anchor element and the Menu element.
+ * @default 0
+ */
+ sideOffset: PropTypes.number,
+ /**
+ * If `true`, allow the Menu to remain in stuck view while the anchor element is scrolled out
+ * of view.
+ * @default false
+ */
+ sticky: PropTypes.bool,
+} as any;
diff --git a/packages/mui-base/src/Menu/Positioner/MenuPositionerContext.ts b/packages/mui-base/src/Menu/Positioner/MenuPositionerContext.ts
new file mode 100644
index 0000000000..8e3ad8a1c2
--- /dev/null
+++ b/packages/mui-base/src/Menu/Positioner/MenuPositionerContext.ts
@@ -0,0 +1,31 @@
+'use client';
+import * as React from 'react';
+import type { Side } from '@floating-ui/react';
+
+export interface MenuPositionerContext {
+ /**
+ * The side of the anchor element the popup is positioned relative to.
+ */
+ side: Side;
+ /**
+ * The alignment of the anchor element the popup is positioned relative to.
+ */
+ alignment: 'start' | 'end' | 'center';
+ arrowRef: React.MutableRefObject;
+ arrowUncentered: boolean;
+ arrowStyles: React.CSSProperties;
+}
+
+export const MenuPositionerContext = React.createContext(null);
+
+if (process.env.NODE_ENV !== 'production') {
+ MenuPositionerContext.displayName = 'MenuPositionerContext';
+}
+
+export function useMenuPositionerContext() {
+ const context = React.useContext(MenuPositionerContext);
+ if (context === null) {
+ throw new Error(' must be used within the component');
+ }
+ return context;
+}
diff --git a/packages/mui-base/src/Menu/Positioner/useMenuPositioner.ts b/packages/mui-base/src/Menu/Positioner/useMenuPositioner.ts
new file mode 100644
index 0000000000..781b27c83f
--- /dev/null
+++ b/packages/mui-base/src/Menu/Positioner/useMenuPositioner.ts
@@ -0,0 +1,204 @@
+'use client';
+import * as React from 'react';
+import type {
+ Boundary,
+ Padding,
+ VirtualElement,
+ FloatingContext,
+ Side,
+ FloatingRootContext,
+} from '@floating-ui/react';
+import { mergeReactProps } from '../../utils/mergeReactProps';
+import { useAnchorPositioning } from '../../utils/useAnchorPositioning';
+import type { GenericHTMLProps } from '../../utils/types';
+
+/**
+ *
+ * API:
+ *
+ * - [useMenuPositioner API](https://mui.com/base-ui/api/use-menu-positioner/)
+ */
+export function useMenuPositioner(
+ params: useMenuPositioner.Parameters,
+): useMenuPositioner.ReturnValue {
+ const { open = false, keepMounted } = params;
+
+ const {
+ positionerStyles,
+ arrowStyles,
+ hidden,
+ arrowRef,
+ arrowUncentered,
+ renderedSide,
+ renderedAlignment,
+ positionerContext: floatingContext,
+ } = useAnchorPositioning(params);
+
+ const getPositionerProps: useMenuPositioner.ReturnValue['getPositionerProps'] = React.useCallback(
+ (externalProps = {}) => {
+ const hiddenStyles: React.CSSProperties = {};
+
+ if ((keepMounted && !open) || hidden) {
+ hiddenStyles.pointerEvents = 'none';
+ }
+
+ return mergeReactProps(externalProps, {
+ style: {
+ ...positionerStyles,
+ ...hiddenStyles,
+ zIndex: 2147483647, // max z-index
+ },
+ 'aria-hidden': !open || undefined,
+ });
+ },
+ [positionerStyles, open, keepMounted, hidden],
+ );
+
+ return React.useMemo(
+ () => ({
+ getPositionerProps,
+ arrowRef,
+ arrowUncentered,
+ arrowStyles,
+ side: renderedSide,
+ alignment: renderedAlignment,
+ floatingContext,
+ }),
+ [
+ getPositionerProps,
+ arrowRef,
+ arrowUncentered,
+ arrowStyles,
+ renderedSide,
+ renderedAlignment,
+ floatingContext,
+ ],
+ );
+}
+
+export namespace useMenuPositioner {
+ export interface SharedParameters {
+ /**
+ * If `true`, the Menu is open.
+ */
+ open?: boolean;
+ /**
+ * The anchor element to which the Menu popup will be placed at.
+ */
+ anchor?:
+ | Element
+ | null
+ | VirtualElement
+ | React.MutableRefObject
+ | (() => Element | VirtualElement | null);
+ /**
+ * The CSS position strategy for positioning the Menu popup element.
+ * @default 'absolute'
+ */
+ positionStrategy?: 'absolute' | 'fixed';
+ /**
+ * The container element to which the Menu popup will be appended to.
+ */
+ container?: HTMLElement | null | React.MutableRefObject;
+ /**
+ * The side of the anchor element that the Menu element should align to.
+ * @default 'bottom'
+ */
+ side?: Side;
+ /**
+ * The gap between the anchor element and the Menu element.
+ * @default 0
+ */
+ sideOffset?: number;
+ /**
+ * The alignment of the Menu element to the anchor element along its cross axis.
+ * @default 'center'
+ */
+ alignment?: 'start' | 'end' | 'center';
+ /**
+ * The offset of the Menu element along its alignment axis.
+ * @default 0
+ */
+ alignmentOffset?: number;
+ /**
+ * The boundary that the Menu element should be constrained to.
+ * @default 'clippingAncestors'
+ */
+ collisionBoundary?: Boundary;
+ /**
+ * The padding of the collision boundary.
+ * @default 5
+ */
+ collisionPadding?: Padding;
+ /**
+ * If `true`, the Menu will be hidden if it is detached from its anchor element due to
+ * differing clipping contexts.
+ * @default false
+ */
+ hideWhenDetached?: boolean;
+ /**
+ * Whether the menu popup remains mounted in the DOM while closed.
+ * @default false
+ */
+ keepMounted?: boolean;
+ /**
+ * If `true`, allow the Menu to remain in stuck view while the anchor element is scrolled out
+ * of view.
+ * @default false
+ */
+ sticky?: boolean;
+ /**
+ * Determines the padding between the arrow and the Menu popup's edges. Useful when the popover
+ * popup has rounded corners via `border-radius`.
+ * @default 5
+ */
+ arrowPadding?: number;
+ }
+
+ export interface Parameters extends SharedParameters {
+ /**
+ * If `true`, the Menu is mounted.
+ * @default true
+ */
+ mounted?: boolean;
+ /**
+ * The Menu root context.
+ */
+ floatingRootContext?: FloatingRootContext;
+ /**
+ * Floating node id.
+ */
+ nodeId?: string;
+ }
+
+ export interface ReturnValue {
+ /**
+ * Props to spread on the Menu positioner element.
+ */
+ getPositionerProps: (externalProps?: GenericHTMLProps) => GenericHTMLProps;
+ /**
+ * The ref of the Menu arrow element.
+ */
+ arrowRef: React.MutableRefObject;
+ /**
+ * Determines if the arrow cannot be centered.
+ */
+ arrowUncentered: boolean;
+ /**
+ * The rendered side of the Menu element.
+ */
+ side: 'top' | 'right' | 'bottom' | 'left';
+ /**
+ * The rendered alignment of the Menu element.
+ */
+ alignment: 'start' | 'end' | 'center';
+ /**
+ * The styles to apply to the Menu arrow element.
+ */
+ arrowStyles: React.CSSProperties;
+ /**
+ * The floating context.
+ */
+ floatingContext: FloatingContext;
+ }
+}
diff --git a/packages/mui-base/src/Menu/Root/MenuRoot.test.tsx b/packages/mui-base/src/Menu/Root/MenuRoot.test.tsx
new file mode 100644
index 0000000000..2bc1bee771
--- /dev/null
+++ b/packages/mui-base/src/Menu/Root/MenuRoot.test.tsx
@@ -0,0 +1,609 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { act, waitFor } from '@mui/internal-test-utils';
+import * as Menu from '@base_ui/react/Menu';
+import userEvent from '@testing-library/user-event';
+import { createRenderer } from '../../../test';
+
+describe(' ', () => {
+ const { render } = createRenderer();
+ const user = userEvent.setup();
+
+ describe('keyboard navigation', () => {
+ it('changes the highlighted item using the arrow keys', async () => {
+ const { getByRole, getByTestId } = await render(
+
+ Toggle
+
+
+ 1
+ 2
+ 3
+
+
+ ,
+ );
+
+ const trigger = getByRole('button', { name: 'Toggle' });
+ await act(() => {
+ trigger.focus();
+ });
+
+ await userEvent.keyboard('[Enter]');
+
+ const item1 = getByTestId('item-1');
+ const item2 = getByTestId('item-2');
+ const item3 = getByTestId('item-3');
+
+ await waitFor(() => {
+ expect(item1).toHaveFocus();
+ });
+
+ await userEvent.keyboard('{ArrowDown}');
+ await waitFor(() => {
+ expect(item2).toHaveFocus();
+ });
+
+ await userEvent.keyboard('{ArrowDown}');
+ await waitFor(() => {
+ expect(item3).toHaveFocus();
+ });
+
+ await userEvent.keyboard('{ArrowUp}');
+ await waitFor(() => {
+ expect(item2).toHaveFocus();
+ });
+ });
+
+ it('changes the highlighted item using the Home and End keys', async () => {
+ const { getByRole, getByTestId } = await render(
+
+ Toggle
+
+
+ 1
+ 2
+ 3
+
+
+ ,
+ );
+
+ const trigger = getByRole('button', { name: 'Toggle' });
+ await act(() => {
+ trigger.focus();
+ });
+
+ await userEvent.keyboard('[Enter]');
+ const item1 = getByTestId('item-1');
+ const item3 = getByTestId('item-3');
+
+ await waitFor(() => {
+ expect(item1).toHaveFocus();
+ });
+
+ await userEvent.keyboard('{End}');
+ await waitFor(() => {
+ expect(item3).toHaveFocus();
+ });
+
+ await userEvent.keyboard('{Home}');
+ await waitFor(() => {
+ expect(item1).toHaveFocus();
+ });
+ });
+
+ it('includes disabled items during keyboard navigation', async () => {
+ const { getByRole, getByTestId } = await render(
+
+ Toggle
+
+
+ 1
+
+ 2
+
+
+
+ ,
+ );
+
+ const trigger = getByRole('button', { name: 'Toggle' });
+ await act(() => {
+ trigger.focus();
+ });
+
+ await userEvent.keyboard('[Enter]');
+
+ const item1 = getByTestId('item-1');
+ const item2 = getByTestId('item-2');
+
+ await waitFor(() => {
+ expect(item1).toHaveFocus();
+ });
+
+ await userEvent.keyboard('[ArrowDown]');
+
+ await waitFor(() => {
+ expect(item2).toHaveFocus();
+ expect(item2).to.have.attribute('aria-disabled', 'true');
+ });
+ });
+
+ describe('text navigation', () => {
+ it('changes the highlighted item', async function test() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ // useMenuPopup Text navigation match menu items using HTMLElement.innerText
+ // innerText is not supported by JsDom
+ this.skip();
+ }
+
+ const { getByText, getAllByRole } = await render(
+
+
+
+ Aa
+ Ba
+ Bb
+ Ca
+ Cb
+ Cd
+
+
+ ,
+ );
+
+ const items = getAllByRole('menuitem');
+
+ await act(() => {
+ items[0].focus();
+ });
+
+ await user.keyboard('c');
+ await waitFor(() => {
+ expect(document.activeElement).to.equal(getByText('Ca'));
+ expect(getByText('Ca')).to.have.attribute('tabindex', '0');
+ });
+
+ await user.keyboard('d');
+ await waitFor(() => {
+ expect(document.activeElement).to.equal(getByText('Cd'));
+ expect(getByText('Cd')).to.have.attribute('tabindex', '0');
+ });
+ });
+
+ it('changes the highlighted item using text navigation on label prop', async () => {
+ const { getByRole, getAllByRole } = await render(
+
+ Toggle
+
+
+ 1
+ 2
+ 3
+ 4
+
+
+ ,
+ );
+
+ const trigger = getByRole('button', { name: 'Toggle' });
+ await user.click(trigger);
+
+ const items = getAllByRole('menuitem');
+
+ await user.keyboard('b');
+ await waitFor(() => {
+ expect(items[1]).toHaveFocus();
+ expect(items[1]).to.have.attribute('tabindex', '0');
+ });
+
+ await user.keyboard('b');
+ await waitFor(() => {
+ expect(items[2]).toHaveFocus();
+ expect(items[2]).to.have.attribute('tabindex', '0');
+ });
+
+ await user.keyboard('b');
+ await waitFor(() => {
+ expect(items[2]).toHaveFocus();
+ expect(items[2]).to.have.attribute('tabindex', '0');
+ });
+ });
+
+ it('skips the non-stringifiable items', async function test() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ // useMenuPopup Text navigation match menu items using HTMLElement.innerText
+ // innerText is not supported by JsDom
+ this.skip();
+ }
+
+ const { getByText, getAllByRole } = await render(
+
+
+
+ Aa
+ Ba
+
+
+ Nested Content
+
+ {undefined}
+ {null}
+ Bc
+
+
+ ,
+ );
+
+ const items = getAllByRole('menuitem');
+
+ await act(() => {
+ items[0].focus();
+ });
+
+ await user.keyboard('b');
+ await waitFor(() => {
+ expect(getByText('Ba')).toHaveFocus();
+ expect(getByText('Ba')).to.have.attribute('tabindex', '0');
+ });
+
+ await user.keyboard('c');
+ await waitFor(() => {
+ expect(getByText('Bc')).toHaveFocus();
+ expect(getByText('Bc')).to.have.attribute('tabindex', '0');
+ });
+ });
+
+ it('navigate to options with diacritic characters', async function test() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ // useMenuPopup Text navigation match menu items using HTMLElement.innerText
+ // innerText is not supported by JsDom
+ this.skip();
+ }
+
+ const { getByText, getAllByRole } = await render(
+
+
+
+ Aa
+ Ba
+ Bb
+ Bą
+
+
+ ,
+ );
+
+ const items = getAllByRole('menuitem');
+
+ await act(() => {
+ items[0].focus();
+ });
+
+ await user.keyboard('b');
+ await waitFor(() => {
+ expect(getByText('Ba')).toHaveFocus();
+ expect(getByText('Ba')).to.have.attribute('tabindex', '0');
+ });
+
+ await user.keyboard('ą');
+ await waitFor(() => {
+ expect(getByText('Bą')).toHaveFocus();
+ expect(getByText('Bą')).to.have.attribute('tabindex', '0');
+ });
+ });
+
+ it('navigate to next options beginning with diacritic characters', async function test() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ // useMenuPopup Text navigation match menu items using HTMLElement.innerText
+ // innerText is not supported by JsDom
+ this.skip();
+ }
+
+ const { getByText, getAllByRole } = await render(
+
+
+
+ Aa
+ ąa
+ ąb
+ ąc
+
+
+ ,
+ );
+
+ const items = getAllByRole('menuitem');
+
+ await act(() => {
+ items[0].focus();
+ });
+
+ await user.keyboard('ą');
+ await waitFor(() => {
+ expect(getByText('ąa')).toHaveFocus();
+ expect(getByText('ąa')).to.have.attribute('tabindex', '0');
+ });
+ });
+ });
+ });
+
+ describe('nested menus', () => {
+ (
+ [
+ ['vertical', 'ltr', 'ArrowRight', 'ArrowLeft'],
+ ['vertical', 'rtl', 'ArrowLeft', 'ArrowRight'],
+ ['horizontal', 'ltr', 'ArrowDown', 'ArrowUp'],
+ ['horizontal', 'rtl', 'ArrowDown', 'ArrowUp'],
+ ] as const
+ ).forEach(([orientation, direction, openKey, closeKey]) => {
+ it(`opens a nested menu of a ${orientation} ${direction.toUpperCase()} menu with ${openKey} key and closes it with ${closeKey}`, async () => {
+ const { getByTestId, queryByTestId } = await render(
+
+
+
+ 1
+
+ 2
+
+
+ 2.1
+ 2.2
+
+
+
+
+
+ ,
+ );
+
+ const submenuTrigger = getByTestId('submenu-trigger');
+
+ await act(() => {
+ submenuTrigger.focus();
+ });
+
+ await user.keyboard(`[${openKey}]`);
+
+ let submenu = queryByTestId('submenu');
+ expect(submenu).not.to.equal(null);
+
+ const submenuItem1 = queryByTestId('submenu-item-1');
+ expect(submenuItem1).not.to.equal(null);
+ await waitFor(() => {
+ expect(submenuItem1).toHaveFocus();
+ });
+
+ await user.keyboard(`[${closeKey}]`);
+
+ submenu = queryByTestId('submenu');
+ expect(submenu).to.equal(null);
+
+ expect(submenuTrigger).toHaveFocus();
+ });
+ });
+ });
+
+ describe('focus management', () => {
+ function Test() {
+ return (
+
+ Toggle
+
+
+ 1
+ 2
+ 3
+
+
+
+ );
+ }
+
+ it('focuses the first item after the menu is opened by keyboard', async () => {
+ const { getAllByRole, getByRole } = await render( );
+
+ const trigger = getByRole('button', { name: 'Toggle' });
+ await act(() => {
+ trigger.focus();
+ });
+
+ await userEvent.keyboard('[Enter]');
+
+ const [firstItem, ...otherItems] = getAllByRole('menuitem');
+ expect(firstItem.tabIndex).to.equal(0);
+ otherItems.forEach((item) => {
+ expect(item.tabIndex).to.equal(-1);
+ });
+ });
+
+ it('focuses the first item when down arrow key opens the menu', async () => {
+ const { getByRole, getAllByRole } = await render( );
+
+ const trigger = getByRole('button', { name: 'Toggle' });
+ await act(() => {
+ trigger.focus();
+ });
+
+ await user.keyboard('[ArrowDown]');
+
+ const [firstItem, ...otherItems] = getAllByRole('menuitem');
+ await waitFor(() => expect(firstItem).toHaveFocus());
+ expect(firstItem.tabIndex).to.equal(0);
+ otherItems.forEach((item) => {
+ expect(item.tabIndex).to.equal(-1);
+ });
+ });
+
+ it('focuses the last item when up arrow key opens the menu', async () => {
+ const { getByRole, getAllByRole } = await render( );
+
+ const trigger = getByRole('button', { name: 'Toggle' });
+
+ await act(() => {
+ trigger.focus();
+ });
+
+ await user.keyboard('[ArrowUp]');
+
+ const [firstItem, secondItem, lastItem] = getAllByRole('menuitem');
+ await waitFor(() => {
+ expect(lastItem).toHaveFocus();
+ });
+
+ expect(lastItem.tabIndex).to.equal(0);
+ [firstItem, secondItem].forEach((item) => {
+ expect(item.tabIndex).to.equal(-1);
+ });
+ });
+
+ it('focuses the trigger after the menu is closed', async () => {
+ const { getByRole } = await render(
+
+
+
+ Toggle
+
+
+ Close
+
+
+
+
+ ,
+ );
+
+ const button = getByRole('button', { name: 'Toggle' });
+ await user.click(button);
+
+ const menuItem = getByRole('menuitem');
+ await user.click(menuItem);
+
+ expect(button).toHaveFocus();
+ });
+
+ it('focuses the trigger after the menu is closed but not unmounted', async () => {
+ const { getByRole } = await render(
+
+
+
+ Toggle
+
+
+ Close
+
+
+
+
+ ,
+ );
+
+ const button = getByRole('button', { name: 'Toggle' });
+ await user.click(button);
+
+ const menuItem = getByRole('menuitem');
+ await user.click(menuItem);
+
+ expect(button).toHaveFocus();
+ });
+ });
+
+ describe('prop: closeParentOnEsc', () => {
+ it('closes the parent menu when the Escape key is pressed by default', async () => {
+ const { getByRole, queryByRole } = await render(
+
+ Open
+
+
+ 1
+
+ 2
+
+
+ 2.1
+ 2.2
+
+
+
+
+
+ ,
+ );
+
+ const trigger = getByRole('button', { name: 'Open' });
+ await act(() => {
+ trigger.focus();
+ });
+
+ await user.keyboard('[ArrowDown]');
+ await waitFor(() => {
+ expect(getByRole('menuitem', { name: '1' })).toHaveFocus();
+ });
+
+ await user.keyboard('[ArrowDown]');
+ await waitFor(() => {
+ expect(getByRole('menuitem', { name: '2' })).toHaveFocus();
+ });
+
+ await user.keyboard('[ArrowRight]');
+ await waitFor(() => {
+ expect(getByRole('menuitem', { name: '2.1' })).toHaveFocus();
+ });
+
+ await user.keyboard('[Escape]');
+ await act(async () => {});
+
+ expect(queryByRole('menu', { hidden: false })).to.equal(null);
+ });
+
+ it('does not close the parent menu when the Escape key is pressed if `closeParentOnEsc=false`', async () => {
+ const { getByRole, queryAllByRole } = await render(
+
+ Open
+
+ ,
+ );
+
+ const trigger = getByRole('button', { name: 'Open' });
+ await act(() => {
+ trigger.focus();
+ });
+
+ await user.keyboard('[ArrowDown]');
+ await waitFor(() => {
+ expect(getByRole('menuitem', { name: '1' })).toHaveFocus();
+ });
+
+ await user.keyboard('[ArrowDown]');
+ await waitFor(() => {
+ expect(getByRole('menuitem', { name: '2' })).toHaveFocus();
+ });
+
+ await user.keyboard('[ArrowRight]');
+ await waitFor(() => {
+ expect(getByRole('menuitem', { name: '2.1' })).toHaveFocus();
+ });
+
+ await user.keyboard('[Escape]');
+ await waitFor(() => {
+ const menus = queryAllByRole('menu', { hidden: false });
+ expect(menus.length).to.equal(1);
+ expect(menus[0].id).to.equal('parent-menu');
+ });
+ });
+ });
+});
diff --git a/packages/mui-base/src/Menu/Root/MenuRoot.tsx b/packages/mui-base/src/Menu/Root/MenuRoot.tsx
new file mode 100644
index 0000000000..e487da8a1a
--- /dev/null
+++ b/packages/mui-base/src/Menu/Root/MenuRoot.tsx
@@ -0,0 +1,226 @@
+'use client';
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { FloatingTree } from '@floating-ui/react';
+import { MenuRootContext, useMenuRootContext } from './MenuRootContext';
+import { MenuDirection, MenuOrientation, useMenuRoot } from './useMenuRoot';
+
+function MenuRoot(props: MenuRoot.Props) {
+ const {
+ animated = true,
+ children,
+ defaultOpen = false,
+ dir: direction = 'ltr',
+ disabled = false,
+ closeParentOnEsc = true,
+ loop = true,
+ onOpenChange,
+ open,
+ orientation = 'vertical',
+ delay = 100,
+ openOnHover: openOnHoverProp,
+ } = props;
+
+ const parentContext = useMenuRootContext(true);
+ const nested = parentContext != null;
+
+ const openOnHover = openOnHoverProp ?? nested;
+
+ const menuRoot = useMenuRoot({
+ animated,
+ direction,
+ disabled,
+ closeParentOnEsc,
+ onOpenChange,
+ loop,
+ defaultOpen,
+ open,
+ orientation,
+ nested,
+ openOnHover,
+ delay,
+ });
+
+ const [localClickAndDragEnabled, setLocalClickAndDragEnabled] = React.useState(false);
+ let clickAndDragEnabled = localClickAndDragEnabled;
+ let setClickAndDragEnabled = setLocalClickAndDragEnabled;
+
+ if (parentContext != null) {
+ clickAndDragEnabled = parentContext.clickAndDragEnabled;
+ setClickAndDragEnabled = parentContext.setClickAndDragEnabled;
+ }
+
+ const context: MenuRootContext = React.useMemo(
+ () => ({
+ ...menuRoot,
+ nested,
+ parentContext,
+ disabled,
+ clickAndDragEnabled,
+ setClickAndDragEnabled,
+ }),
+ [menuRoot, nested, parentContext, disabled, clickAndDragEnabled, setClickAndDragEnabled],
+ );
+
+ if (!nested) {
+ // set up a FloatingTree to provide the context to nested menus
+ return (
+
+ {children}
+
+ );
+ }
+
+ return {children} ;
+}
+
+namespace MenuRoot {
+ export interface Props {
+ /**
+ * If `true`, the Menu supports CSS-based animations and transitions.
+ * It is kept in the DOM until the animation completes.
+ *
+ * @default true
+ */
+ animated?: boolean;
+ children: React.ReactNode;
+ /**
+ * If `true`, the Menu is initially open.
+ *
+ * @default false
+ */
+ defaultOpen?: boolean;
+ /**
+ * If `true`, using keyboard navigation will wrap focus to the other end of the list once the end is reached.
+ * @default true
+ */
+ loop?: boolean;
+ /**
+ * Callback fired when the component requests to be opened or closed.
+ */
+ onOpenChange?: (open: boolean, event: Event | undefined) => void;
+ /**
+ * Allows to control whether the dropdown is open.
+ * This is a controlled counterpart of `defaultOpen`.
+ */
+ open?: boolean;
+ /**
+ * The orientation of the Menu (horizontal or vertical).
+ *
+ * @default 'vertical'
+ */
+ orientation?: MenuOrientation;
+ /**
+ * The direction of the Menu (left-to-right or right-to-left).
+ *
+ * @default 'ltr'
+ */
+ dir?: MenuDirection;
+ /**
+ * If `true`, the Menu is disabled.
+ *
+ * @default false
+ */
+ disabled?: boolean;
+ /**
+ * Determines if pressing the Esc key closes the parent menus.
+ * This is only applicable for nested menus.
+ *
+ * If set to `false` pressing Esc closes only the current menu.
+ *
+ * @default true
+ */
+ closeParentOnEsc?: boolean;
+ /**
+ * The delay in milliseconds until the menu popup is opened when `openOnHover` is `true`.
+ *
+ * @default 100
+ */
+ delay?: number;
+ /**
+ * Whether the menu popup opens when the trigger is hovered after the provided `delay`.
+ *
+ * @default nested
+ */
+ openOnHover?: boolean;
+ }
+}
+
+MenuRoot.propTypes /* remove-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 Menu supports CSS-based animations and transitions.
+ * It is kept in the DOM until the animation completes.
+ *
+ * @default true
+ */
+ animated: PropTypes.bool,
+ /**
+ * @ignore
+ */
+ children: PropTypes.node,
+ /**
+ * Determines if pressing the Esc key closes the parent menus.
+ * This is only applicable for nested menus.
+ *
+ * If set to `false` pressing Esc closes only the current menu.
+ *
+ * @default true
+ */
+ closeParentOnEsc: PropTypes.bool,
+ /**
+ * If `true`, the Menu is initially open.
+ *
+ * @default false
+ */
+ defaultOpen: PropTypes.bool,
+ /**
+ * The delay in milliseconds until the menu popup is opened when `openOnHover` is `true`.
+ *
+ * @default 100
+ */
+ delay: PropTypes.number,
+ /**
+ * The direction of the Menu (left-to-right or right-to-left).
+ *
+ * @default 'ltr'
+ */
+ dir: PropTypes.oneOf(['ltr', 'rtl']),
+ /**
+ * If `true`, the Menu is disabled.
+ *
+ * @default false
+ */
+ disabled: PropTypes.bool,
+ /**
+ * If `true`, using keyboard navigation will wrap focus to the other end of the list once the end is reached.
+ * @default true
+ */
+ loop: PropTypes.bool,
+ /**
+ * Callback fired when the component requests to be opened or closed.
+ */
+ onOpenChange: PropTypes.func,
+ /**
+ * Allows to control whether the dropdown is open.
+ * This is a controlled counterpart of `defaultOpen`.
+ */
+ open: PropTypes.bool,
+ /**
+ * Whether the menu popup opens when the trigger is hovered after the provided `delay`.
+ *
+ * @default nested
+ */
+ openOnHover: PropTypes.bool,
+ /**
+ * The orientation of the Menu (horizontal or vertical).
+ *
+ * @default 'vertical'
+ */
+ orientation: PropTypes.oneOf(['horizontal', 'vertical']),
+} as any;
+
+export { MenuRoot };
diff --git a/packages/mui-base/src/Menu/Root/MenuRootContext.ts b/packages/mui-base/src/Menu/Root/MenuRootContext.ts
new file mode 100644
index 0000000000..94e5f741e3
--- /dev/null
+++ b/packages/mui-base/src/Menu/Root/MenuRootContext.ts
@@ -0,0 +1,30 @@
+'use client';
+import * as React from 'react';
+import type { useMenuRoot } from './useMenuRoot';
+
+export interface MenuRootContext extends useMenuRoot.ReturnValue {
+ clickAndDragEnabled: boolean;
+ disabled: boolean;
+ nested: boolean;
+ parentContext: MenuRootContext | null;
+ setClickAndDragEnabled: React.Dispatch>;
+}
+
+export const MenuRootContext = React.createContext(null);
+
+if (process.env.NODE_ENV !== 'production') {
+ MenuRootContext.displayName = 'MenuRootContext';
+}
+
+function useMenuRootContext(optional?: false): MenuRootContext;
+function useMenuRootContext(optional: true): MenuRootContext | null;
+function useMenuRootContext(optional?: boolean) {
+ const context = React.useContext(MenuRootContext);
+ if (context === null && !optional) {
+ throw new Error('Base UI: MenuRootContext is not defined.');
+ }
+
+ return context;
+}
+
+export { useMenuRootContext };
diff --git a/packages/mui-base/src/Menu/Root/useMenuRoot.ts b/packages/mui-base/src/Menu/Root/useMenuRoot.ts
new file mode 100644
index 0000000000..954d5cc71e
--- /dev/null
+++ b/packages/mui-base/src/Menu/Root/useMenuRoot.ts
@@ -0,0 +1,277 @@
+'use client';
+import * as React from 'react';
+import {
+ safePolygon,
+ useClick,
+ useDismiss,
+ useFloatingRootContext,
+ useHover,
+ useInteractions,
+ useListNavigation,
+ useRole,
+ useTypeahead,
+ FloatingRootContext,
+} from '@floating-ui/react';
+import { mergeReactProps } from '../../utils/mergeReactProps';
+import { GenericHTMLProps } from '../../utils/types';
+import { useTransitionStatus } from '../../utils/useTransitionStatus';
+import { useEventCallback } from '../../utils/useEventCallback';
+import { useAnimationsFinished } from '../../utils/useAnimationsFinished';
+import { useControlled } from '../../utils/useControlled';
+
+const EMPTY_ARRAY: never[] = [];
+
+/**
+ *
+ * API:
+ *
+ * - [useMenuRoot API](https://mui.com/base-ui/api/use-menu-root/)
+ */
+export function useMenuRoot(parameters: useMenuRoot.Parameters): useMenuRoot.ReturnValue {
+ const {
+ animated,
+ open: openParam,
+ defaultOpen,
+ onOpenChange,
+ orientation,
+ direction,
+ disabled,
+ nested,
+ closeParentOnEsc,
+ loop,
+ delay,
+ openOnHover,
+ } = parameters;
+
+ const [triggerElement, setTriggerElement] = React.useState(null);
+ const [positionerElement, setPositionerElement] = React.useState(null);
+ const popupRef = React.useRef(null);
+ const [hoverEnabled, setHoverEnabled] = React.useState(true);
+ const [activeIndex, setActiveIndex] = React.useState(null);
+
+ const [open, setOpenUnwrapped] = useControlled({
+ controlled: openParam,
+ default: defaultOpen,
+ name: 'useMenuRoot',
+ state: 'open',
+ });
+
+ const { mounted, setMounted, transitionStatus } = useTransitionStatus(open, animated);
+
+ const runOnceAnimationsFinish = useAnimationsFinished(popupRef);
+ const setOpen = useEventCallback((nextOpen: boolean, event?: Event) => {
+ onOpenChange?.(nextOpen, event);
+ setOpenUnwrapped(nextOpen);
+ if (!nextOpen) {
+ if (animated) {
+ runOnceAnimationsFinish(() => setMounted(false));
+ } else {
+ setMounted(false);
+ }
+ }
+ });
+
+ const floatingRootContext = useFloatingRootContext({
+ elements: {
+ reference: triggerElement,
+ floating: positionerElement,
+ },
+ open,
+ onOpenChange: setOpen,
+ });
+
+ const hover = useHover(floatingRootContext, {
+ enabled: hoverEnabled && openOnHover && !disabled,
+ handleClose: safePolygon({ blockPointerEvents: true }),
+ mouseOnly: true,
+ delay: {
+ open: delay,
+ },
+ });
+
+ const click = useClick(floatingRootContext, {
+ enabled: !disabled,
+ event: 'mousedown',
+ toggle: !nested,
+ ignoreMouse: nested,
+ });
+
+ const dismiss = useDismiss(floatingRootContext, { bubbles: closeParentOnEsc });
+
+ const role = useRole(floatingRootContext, {
+ role: 'menu',
+ });
+
+ const itemDomElements = React.useRef<(HTMLElement | null)[]>([]);
+ const itemLabels = React.useRef<(string | null)[]>([]);
+
+ const listNavigation = useListNavigation(floatingRootContext, {
+ enabled: !disabled,
+ listRef: itemDomElements,
+ activeIndex,
+ nested,
+ loop,
+ orientation,
+ rtl: direction === 'rtl',
+ disabledIndices: EMPTY_ARRAY,
+ onNavigate: setActiveIndex,
+ });
+
+ const typeahead = useTypeahead(floatingRootContext, {
+ listRef: itemLabels,
+ activeIndex,
+ resetMs: 350,
+ onMatch: (index) => {
+ if (open && index !== activeIndex) {
+ setActiveIndex(index);
+ }
+ },
+ });
+
+ const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([
+ hover,
+ click,
+ dismiss,
+ role,
+ listNavigation,
+ typeahead,
+ ]);
+
+ const getTriggerProps = React.useCallback(
+ (externalProps?: GenericHTMLProps) =>
+ getReferenceProps(
+ mergeReactProps(externalProps, {
+ onMouseEnter: () => {
+ setHoverEnabled(true);
+ },
+ }),
+ ),
+ [getReferenceProps],
+ );
+
+ const getPositionerProps = React.useCallback(
+ (externalProps?: GenericHTMLProps) =>
+ getFloatingProps(
+ mergeReactProps(externalProps, {
+ onMouseEnter: () => {
+ setHoverEnabled(false);
+ },
+ }),
+ ),
+ [getFloatingProps],
+ );
+
+ return React.useMemo(
+ () => ({
+ activeIndex,
+ floatingRootContext,
+ triggerElement,
+ setTriggerElement,
+ getTriggerProps,
+ setPositionerElement,
+ getPositionerProps,
+ getItemProps,
+ itemDomElements,
+ itemLabels,
+ mounted,
+ transitionStatus,
+ popupRef,
+ open,
+ setOpen,
+ }),
+ [
+ activeIndex,
+ floatingRootContext,
+ triggerElement,
+ getTriggerProps,
+ getPositionerProps,
+ getItemProps,
+ itemDomElements,
+ itemLabels,
+ mounted,
+ transitionStatus,
+ open,
+ setOpen,
+ ],
+ );
+}
+
+export type MenuOrientation = 'horizontal' | 'vertical';
+
+export type MenuDirection = 'ltr' | 'rtl';
+
+export namespace useMenuRoot {
+ export interface Parameters {
+ /**
+ * If `true`, the Menu supports CSS-based animations and transitions.
+ * It is kept in the DOM until the animation completes.
+ */
+ animated: boolean;
+ /**
+ * Allows to control whether the Menu is open.
+ * This is a controlled counterpart of `defaultOpen`.
+ */
+ open: boolean | undefined;
+ /**
+ * Callback fired when the component requests to be opened or closed.
+ */
+ onOpenChange: ((open: boolean, event: Event | undefined) => void) | undefined;
+ /**
+ * If `true`, the Menu is initially open.
+ */
+ defaultOpen: boolean;
+ /**
+ * If `true`, using keyboard navigation will wrap focus to the other end of the list once the end is reached.
+ */
+ loop: boolean;
+ /**
+ * The delay in milliseconds until the menu popup is opened when `openOnHover` is `true`.
+ */
+ delay: number;
+ /**
+ * The orientation of the Menu (horizontal or vertical).
+ */
+ orientation: MenuOrientation;
+ /**
+ * The direction of the Menu (left-to-right or right-to-left).
+ */
+ direction: MenuDirection;
+ /**
+ * If `true`, the Menu is disabled.
+ */
+ disabled: boolean;
+ /**
+ * Determines if the Menu is nested inside another Menu.
+ */
+ nested: boolean;
+ /**
+ * Determines if pressing the Esc key closes the parent menus.
+ * This is only applicable for nested menus.
+ *
+ * If set to `false` pressing Esc closes only the current menu.
+ */
+ closeParentOnEsc: boolean;
+ /**
+ * Whether the menu popup opens when the trigger is hovered after the provided `delay`.
+ */
+ openOnHover: boolean;
+ }
+
+ export interface ReturnValue {
+ activeIndex: number | null;
+ floatingRootContext: FloatingRootContext;
+ getItemProps: (externalProps?: GenericHTMLProps) => GenericHTMLProps;
+ getPositionerProps: (externalProps?: GenericHTMLProps) => GenericHTMLProps;
+ getTriggerProps: (externalProps?: GenericHTMLProps) => GenericHTMLProps;
+ itemDomElements: React.MutableRefObject<(HTMLElement | null)[]>;
+ itemLabels: React.MutableRefObject<(string | null)[]>;
+ mounted: boolean;
+ open: boolean;
+ popupRef: React.RefObject;
+ setOpen: (open: boolean, event: Event | undefined) => void;
+ setPositionerElement: (element: HTMLElement | null) => void;
+ setTriggerElement: (element: HTMLElement | null) => void;
+ transitionStatus: 'entering' | 'exiting' | undefined;
+ triggerElement: HTMLElement | null;
+ }
+}
diff --git a/packages/mui-base/src/Menu/SubmenuTrigger/SubmenuTrigger.tsx b/packages/mui-base/src/Menu/SubmenuTrigger/SubmenuTrigger.tsx
new file mode 100644
index 0000000000..a2160de2af
--- /dev/null
+++ b/packages/mui-base/src/Menu/SubmenuTrigger/SubmenuTrigger.tsx
@@ -0,0 +1,133 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { useFloatingTree, useListItem } from '@floating-ui/react';
+import { BaseUIComponentProps, GenericHTMLProps } from '../../utils/types';
+import { useMenuRootContext } from '../Root/MenuRootContext';
+import { commonStyleHooks } from '../utils/commonStyleHooks';
+import { useId } from '../../utils/useId';
+import { useComponentRenderer } from '../../utils/useComponentRenderer';
+import { useSubmenuTrigger } from './useSubmenuTrigger';
+import { useForkRef } from '../../utils/useForkRef';
+
+const SubmenuTrigger = React.forwardRef(function SubmenuTriggerComponent(
+ props: SubmenuTrigger.Props,
+ forwardedRef: React.ForwardedRef,
+) {
+ const { render, className, disabled = false, label, id: idProp, ...other } = props;
+ const id = useId(idProp);
+
+ const { getTriggerProps, parentContext, setTriggerElement, clickAndDragEnabled, open } =
+ useMenuRootContext();
+
+ if (parentContext === null) {
+ throw new Error('Base UI: ItemTrigger must be placed in a nested Menu.');
+ }
+
+ const { activeIndex, getItemProps } = parentContext;
+ const item = useListItem();
+
+ const highlighted = activeIndex === item.index;
+
+ const mergedRef = useForkRef(forwardedRef, item.ref);
+
+ const { events: menuEvents } = useFloatingTree()!;
+
+ const { getRootProps } = useSubmenuTrigger({
+ id,
+ highlighted,
+ ref: mergedRef,
+ disabled,
+ menuEvents,
+ setTriggerElement,
+ treatMouseupAsClick: clickAndDragEnabled,
+ });
+
+ const ownerState: SubmenuTrigger.OwnerState = { disabled, highlighted, open };
+
+ const { renderElement } = useComponentRenderer({
+ render: render || 'div',
+ className,
+ ownerState,
+ propGetter: (externalProps: GenericHTMLProps) =>
+ getTriggerProps(getItemProps(getRootProps(externalProps))),
+ customStyleHookMapping: commonStyleHooks,
+ extraProps: other,
+ });
+
+ return renderElement();
+});
+
+namespace SubmenuTrigger {
+ export interface Props extends BaseUIComponentProps<'div', OwnerState> {
+ children?: React.ReactNode;
+ onClick?: React.MouseEventHandler;
+ /**
+ * If `true`, the menu item will be disabled.
+ * @default false
+ */
+ disabled?: boolean;
+ /**
+ * A text representation of the menu item's content.
+ * Used for keyboard text navigation matching.
+ */
+ label?: string;
+ /**
+ * If `true`, the menu item won't receive focus when the mouse moves over it.
+ *
+ * @default false
+ */
+ disableFocusOnHover?: boolean;
+ id?: string;
+ }
+
+ export interface OwnerState {
+ disabled: boolean;
+ highlighted: boolean;
+ open: boolean;
+ }
+}
+
+SubmenuTrigger.propTypes /* remove-proptypes */ = {
+ // ┌────────────────────────────── Warning ──────────────────────────────┐
+ // │ These PropTypes are generated from the TypeScript type definitions. │
+ // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
+ // └─────────────────────────────────────────────────────────────────────┘
+ /**
+ * @ignore
+ */
+ children: PropTypes.node,
+ /**
+ * Class names applied to the element or a function that returns them based on the component's state.
+ */
+ className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+ /**
+ * If `true`, the menu item will be disabled.
+ * @default false
+ */
+ disabled: PropTypes.bool,
+ /**
+ * If `true`, the menu item won't receive focus when the mouse moves over it.
+ *
+ * @default false
+ */
+ disableFocusOnHover: PropTypes.bool,
+ /**
+ * @ignore
+ */
+ id: PropTypes.string,
+ /**
+ * A text representation of the menu item's content.
+ * Used for keyboard text navigation matching.
+ */
+ label: PropTypes.string,
+ /**
+ * @ignore
+ */
+ onClick: PropTypes.func,
+ /**
+ * A function to customize rendering of the component.
+ */
+ render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
+} as any;
+
+export { SubmenuTrigger };
diff --git a/packages/mui-base/src/Menu/SubmenuTrigger/useSubmenuTrigger.ts b/packages/mui-base/src/Menu/SubmenuTrigger/useSubmenuTrigger.ts
new file mode 100644
index 0000000000..5ecb534017
--- /dev/null
+++ b/packages/mui-base/src/Menu/SubmenuTrigger/useSubmenuTrigger.ts
@@ -0,0 +1,89 @@
+import * as React from 'react';
+import { FloatingEvents } from '@floating-ui/react';
+import { useMenuItem } from '../Item/useMenuItem';
+import { useForkRef } from '../../utils/useForkRef';
+import { GenericHTMLProps } from '../../utils/types';
+
+/**
+ *
+ * API:
+ *
+ * - [useSubmenuTrigger API](https://mui.com/base-ui/api/use-submenu-trigger/)
+ */
+export function useSubmenuTrigger(
+ parameters: useSubmenuTrigger.Parameters,
+): useSubmenuTrigger.ReturnValue {
+ const {
+ id,
+ highlighted,
+ disabled,
+ ref: externalRef,
+ menuEvents,
+ setTriggerElement,
+ treatMouseupAsClick,
+ } = parameters;
+
+ const { getRootProps: getMenuItemProps, rootRef: menuItemRef } = useMenuItem({
+ closeOnClick: false,
+ disabled,
+ highlighted,
+ id,
+ menuEvents,
+ ref: externalRef,
+ treatMouseupAsClick,
+ });
+
+ const menuTriggerRef = useForkRef(menuItemRef, setTriggerElement);
+
+ const getRootProps = React.useCallback(
+ (externalProps?: GenericHTMLProps) => {
+ return {
+ ...getMenuItemProps({
+ 'aria-haspopup': 'menu' as const,
+ ...externalProps,
+ }),
+ ref: menuTriggerRef,
+ };
+ },
+ [getMenuItemProps, menuTriggerRef],
+ );
+
+ return React.useMemo(
+ () => ({
+ getRootProps,
+ rootRef: menuTriggerRef,
+ }),
+ [getRootProps, menuTriggerRef],
+ );
+}
+
+export namespace useSubmenuTrigger {
+ export interface Parameters {
+ id: string | undefined;
+ highlighted: boolean;
+ /**
+ * If `true`, the menu item will be disabled.
+ */
+ disabled: boolean;
+ /**
+ * The ref of the item.
+ */
+ ref?: React.Ref;
+ /**
+ * The FloatingEvents instance of the menu's root.
+ */
+ menuEvents: FloatingEvents;
+ /**
+ * A callback to set the trigger element whenever it's mounted.
+ */
+ setTriggerElement: (element: HTMLElement | null) => void;
+ /**
+ * If `true`, the menu item will listen for mouseup events and treat them as clicks.
+ */
+ treatMouseupAsClick: boolean;
+ }
+
+ export interface ReturnValue {
+ getRootProps: (externalProps?: GenericHTMLProps) => GenericHTMLProps;
+ }
+}
diff --git a/packages/mui-base/src/Menu/Trigger/MenuTrigger.test.tsx b/packages/mui-base/src/Menu/Trigger/MenuTrigger.test.tsx
new file mode 100644
index 0000000000..15236d2c1d
--- /dev/null
+++ b/packages/mui-base/src/Menu/Trigger/MenuTrigger.test.tsx
@@ -0,0 +1,167 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { FloatingRootContext, FloatingTree } from '@floating-ui/react';
+import userEvent from '@testing-library/user-event';
+import { act } from '@mui/internal-test-utils';
+import * as Menu from '@base_ui/react/Menu';
+import { MenuRootContext } from '@base_ui/react/Menu';
+import { describeConformance, createRenderer } from '../../../test';
+
+const testRootContext: MenuRootContext = {
+ floatingRootContext: {} as FloatingRootContext,
+ getPositionerProps: (p) => ({ ...p }),
+ getTriggerProps: (p) => ({ ...p }),
+ getItemProps: (p) => ({ ...p }),
+ parentContext: null,
+ nested: false,
+ triggerElement: null,
+ setTriggerElement: () => {},
+ setPositionerElement: () => {},
+ activeIndex: null,
+ disabled: false,
+ itemDomElements: { current: [] },
+ itemLabels: { current: [] },
+ open: true,
+ setOpen: () => {},
+ clickAndDragEnabled: false,
+ setClickAndDragEnabled: () => {},
+ popupRef: { current: null },
+ mounted: true,
+ transitionStatus: undefined,
+};
+
+describe(' ', () => {
+ const { render } = createRenderer();
+ const user = userEvent.setup();
+
+ describeConformance( , () => ({
+ render: (node) => {
+ return render(
+
+ {node}
+ ,
+ );
+ },
+ refInstanceof: window.HTMLButtonElement,
+ }));
+
+ describe('prop: disabled', () => {
+ it('should render a disabled button', async () => {
+ const { getByRole } = await render(
+
+
+ ,
+ );
+
+ const button = getByRole('button');
+ expect(button).to.have.property('disabled', true);
+ });
+
+ it('should not open the menu when clicked', async () => {
+ const { getByRole, queryByRole } = await render(
+
+
+
+
+
+ ,
+ );
+
+ const button = getByRole('button');
+ await user.click(button);
+
+ expect(queryByRole('menu', { hidden: false })).to.equal(null);
+ });
+ });
+
+ it('toggles the menu state when clicked', async () => {
+ const { getByRole, queryByRole } = await render(
+
+ Open
+
+
+
+ ,
+ );
+
+ const button = getByRole('button', { name: 'Open' });
+ await user.click(button);
+
+ const menuPopup = queryByRole('menu', { hidden: false });
+ expect(menuPopup).not.to.equal(null);
+
+ expect(menuPopup).to.have.attribute('data-menu', 'open');
+ });
+
+ describe('keyboard navigation', () => {
+ [
+ Open ,
+ }>Open,
+ ].forEach((buttonComponent) => {
+ const buttonType = buttonComponent.props.slots?.root ? 'non-native' : 'native';
+ ['ArrowUp', 'ArrowDown', 'Enter', ' '].forEach((key) => {
+ if (buttonType === 'native' && (key === ' ' || key === 'Enter')) {
+ return;
+ }
+
+ it(`opens the menu when pressing "${key}" on a ${buttonType} button`, async () => {
+ const { getByRole, queryByRole } = await render(
+
+ {buttonComponent}
+
+
+ 1
+
+
+ ,
+ );
+
+ const button = getByRole('button', { name: 'Open' });
+ await act(() => {
+ button.focus();
+ });
+
+ await user.keyboard(`[${key}]`);
+
+ const menuPopup = queryByRole('menu', { hidden: false });
+ expect(menuPopup).not.to.equal(null);
+ });
+ });
+ });
+ });
+
+ describe('accessibility attributes', () => {
+ it('has the aria-haspopup attribute', async () => {
+ const { getByRole } = await render(
+
+
+ ,
+ );
+
+ const button = getByRole('button');
+ expect(button).to.have.attribute('aria-haspopup');
+ });
+
+ it('has the aria-expanded=false attribute when closed', async () => {
+ const { getByRole } = await render(
+
+
+ ,
+ );
+
+ const button = getByRole('button');
+ expect(button).to.have.attribute('aria-expanded', 'false');
+ });
+
+ it('has the aria-expanded=true attribute when open', async () => {
+ const { getByRole } = await render(
+
+
+ ,
+ );
+
+ const button = getByRole('button');
+ expect(button).to.have.attribute('aria-expanded', 'true');
+ });
+ });
+});
diff --git a/packages/mui-base/src/Menu/Trigger/MenuTrigger.tsx b/packages/mui-base/src/Menu/Trigger/MenuTrigger.tsx
new file mode 100644
index 0000000000..a73a95d25c
--- /dev/null
+++ b/packages/mui-base/src/Menu/Trigger/MenuTrigger.tsx
@@ -0,0 +1,111 @@
+'use client';
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { useFloatingTree } from '@floating-ui/react';
+import { useMenuTrigger } from './useMenuTrigger';
+import { useMenuRootContext } from '../Root/MenuRootContext';
+import { commonStyleHooks } from '../utils/commonStyleHooks';
+import { useComponentRenderer } from '../../utils/useComponentRenderer';
+import { BaseUIComponentProps } from '../../utils/types';
+
+const MenuTrigger = React.forwardRef(function MenuTrigger(
+ props: MenuTrigger.Props,
+ forwardedRef: React.ForwardedRef,
+) {
+ const { render, className, disabled = false, label, ...other } = props;
+
+ const {
+ getTriggerProps,
+ disabled: menuDisabled,
+ setTriggerElement,
+ open,
+ setOpen,
+ setClickAndDragEnabled,
+ } = useMenuRootContext();
+
+ const { events: menuEvents } = useFloatingTree()!;
+
+ const { getRootProps } = useMenuTrigger({
+ disabled: disabled || menuDisabled,
+ rootRef: forwardedRef,
+ menuEvents,
+ setTriggerElement,
+ open,
+ setOpen,
+ setClickAndDragEnabled,
+ });
+
+ const ownerState: MenuTrigger.OwnerState = {
+ open,
+ };
+
+ const { renderElement } = useComponentRenderer({
+ render: render || 'button',
+ className,
+ ownerState,
+ propGetter: (externalProps) => getTriggerProps(getRootProps(externalProps)),
+ customStyleHookMapping: commonStyleHooks,
+ extraProps: other,
+ });
+
+ return renderElement();
+});
+
+namespace MenuTrigger {
+ export interface Props extends BaseUIComponentProps<'button', OwnerState> {
+ children?: React.ReactNode;
+ /**
+ * If `true`, the component is disabled.
+ * @default false
+ */
+ disabled?: boolean;
+ /**
+ * If `true`, allows a disabled button to receive focus.
+ * @default false
+ */
+ focusableWhenDisabled?: boolean;
+ /**
+ * Label of the button
+ */
+ label?: string;
+ }
+
+ export type OwnerState = {
+ open: boolean;
+ };
+}
+
+MenuTrigger.propTypes /* remove-proptypes */ = {
+ // ┌────────────────────────────── Warning ──────────────────────────────┐
+ // │ These PropTypes are generated from the TypeScript type definitions. │
+ // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
+ // └─────────────────────────────────────────────────────────────────────┘
+ /**
+ * @ignore
+ */
+ children: PropTypes.node,
+ /**
+ * Class names applied to the element or a function that returns them based on the component's state.
+ */
+ className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+ /**
+ * If `true`, the component is disabled.
+ * @default false
+ */
+ disabled: PropTypes.bool,
+ /**
+ * If `true`, allows a disabled button to receive focus.
+ * @default false
+ */
+ focusableWhenDisabled: PropTypes.bool,
+ /**
+ * Label of the button
+ */
+ label: PropTypes.string,
+ /**
+ * A function to customize rendering of the component.
+ */
+ render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
+} as any;
+
+export { MenuTrigger };
diff --git a/packages/mui-base/src/Menu/Trigger/useMenuTrigger.ts b/packages/mui-base/src/Menu/Trigger/useMenuTrigger.ts
new file mode 100644
index 0000000000..90c3045892
--- /dev/null
+++ b/packages/mui-base/src/Menu/Trigger/useMenuTrigger.ts
@@ -0,0 +1,142 @@
+'use client';
+import * as React from 'react';
+import { unstable_useForkRef as useForkRef } from '@mui/utils';
+import { FloatingEvents } from '@floating-ui/react';
+import { useButton } from '../../useButton/useButton';
+import { GenericHTMLProps } from '../../utils/types';
+import { mergeReactProps } from '../../utils/mergeReactProps';
+import { ownerDocument } from '../../utils/owner';
+
+/**
+ *
+ * API:
+ *
+ * - [useMenuTrigger API](https://mui.com/base-ui/api/use-menu-trigger/)
+ */
+export function useMenuTrigger(parameters: useMenuTrigger.Parameters): useMenuTrigger.ReturnValue {
+ const {
+ disabled = false,
+ rootRef: externalRef,
+ open,
+ setOpen,
+ setTriggerElement,
+ setClickAndDragEnabled,
+ } = parameters;
+
+ const triggerRef = React.useRef(null);
+
+ const mergedRef = useForkRef(externalRef, triggerRef);
+
+ const { getRootProps: getButtonRootProps, rootRef: buttonRootRef } = useButton({
+ disabled,
+ focusableWhenDisabled: false,
+ rootRef: mergedRef,
+ });
+
+ const handleRef = useForkRef(buttonRootRef, setTriggerElement);
+ const ignoreNextClick = React.useRef(false);
+
+ const getRootProps = React.useCallback(
+ (externalProps?: GenericHTMLProps): GenericHTMLProps => {
+ return mergeReactProps(
+ externalProps,
+ {
+ 'aria-haspopup': 'menu' as const,
+ tabIndex: 0, // this is needed to make the button focused after click in Safari
+ ref: handleRef,
+ onMouseDown: (event: MouseEvent) => {
+ if (open) {
+ return;
+ }
+
+ // prevents closing the menu right after it was opened
+ ignoreNextClick.current = true;
+ event.preventDefault();
+
+ setClickAndDragEnabled(true);
+ const mousedownTarget = event.target as Element;
+
+ function handleDocumentMouseUp(mouseUpEvent: MouseEvent) {
+ const mouseupTarget = mouseUpEvent.target as HTMLElement;
+ if (mouseupTarget?.dataset?.handleMouseup === 'true') {
+ mouseupTarget.click();
+ } else if (
+ mouseupTarget !== triggerRef.current &&
+ !triggerRef.current?.contains(mouseupTarget)
+ ) {
+ setOpen(false, mouseUpEvent);
+ }
+
+ setClickAndDragEnabled(false);
+ ownerDocument(mousedownTarget).removeEventListener('mouseup', handleDocumentMouseUp);
+ }
+
+ ownerDocument(mousedownTarget).addEventListener('mouseup', handleDocumentMouseUp);
+ },
+ onClick: () => {
+ if (ignoreNextClick.current) {
+ ignoreNextClick.current = false;
+ }
+ },
+ },
+ getButtonRootProps(),
+ );
+ },
+ [getButtonRootProps, handleRef, open, setOpen, setClickAndDragEnabled],
+ );
+
+ return React.useMemo(
+ () => ({
+ getRootProps,
+ rootRef: handleRef,
+ }),
+ [getRootProps, handleRef],
+ );
+}
+
+export namespace useMenuTrigger {
+ export interface Parameters {
+ /**
+ * If `true`, the component is disabled.
+ * @default false
+ */
+ disabled?: boolean;
+ /**
+ * The ref to the root element.
+ */
+ rootRef?: React.Ref;
+ /**
+ * A callback to set the trigger element whenever it's mounted.
+ */
+ setTriggerElement: (element: HTMLElement | null) => void;
+ /**
+ * If `true`, the Menu is open.
+ */
+ open: boolean;
+ /**
+ * A callback to set the open state of the Menu.
+ */
+ setOpen: (open: boolean, event: Event | undefined) => void;
+ /**
+ * The FloatingEvents instance of the menu's root.
+ */
+ menuEvents: FloatingEvents;
+ /**
+ * A callback to enable/disable click and drag functionality.
+ */
+ setClickAndDragEnabled: (enabled: boolean) => void;
+ }
+
+ export interface ReturnValue {
+ /**
+ * Resolver for the root slot's props.
+ * @param externalProps props for the root slot
+ * @returns props that should be spread on the root slot
+ */
+ getRootProps: (externalProps?: GenericHTMLProps) => GenericHTMLProps;
+ /**
+ * The ref to the root element.
+ */
+ rootRef: React.RefCallback | null;
+ }
+}
diff --git a/packages/mui-base/src/Menu/index.ts b/packages/mui-base/src/Menu/index.ts
new file mode 100644
index 0000000000..33250fa387
--- /dev/null
+++ b/packages/mui-base/src/Menu/index.ts
@@ -0,0 +1,25 @@
+export { MenuArrow as Arrow } from './Arrow/MenuArrow';
+export { useMenuArrow } from './Arrow/useMenuArrow';
+
+export { MenuItem as Item } from './Item/MenuItem';
+export { useMenuItem } from './Item/useMenuItem';
+
+export { MenuPopup as Popup } from './Popup/MenuPopup';
+export { useMenuPopup } from './Popup/useMenuPopup';
+
+export { MenuPositioner as Positioner } from './Positioner/MenuPositioner';
+export { useMenuPositioner } from './Positioner/useMenuPositioner';
+export {
+ MenuPositionerContext,
+ useMenuPositionerContext,
+} from './Positioner/MenuPositionerContext';
+
+export { MenuRoot as Root } from './Root/MenuRoot';
+export { useMenuRoot } from './Root/useMenuRoot';
+export { MenuRootContext, useMenuRootContext } from './Root/MenuRootContext';
+
+export { MenuTrigger as Trigger } from './Trigger/MenuTrigger';
+export { useMenuTrigger } from './Trigger/useMenuTrigger';
+
+export { SubmenuTrigger } from './SubmenuTrigger/SubmenuTrigger';
+export { useSubmenuTrigger } from './SubmenuTrigger/useSubmenuTrigger';
diff --git a/packages/mui-base/src/Menu/utils/commonStyleHooks.ts b/packages/mui-base/src/Menu/utils/commonStyleHooks.ts
new file mode 100644
index 0000000000..ca6997419e
--- /dev/null
+++ b/packages/mui-base/src/Menu/utils/commonStyleHooks.ts
@@ -0,0 +1,3 @@
+export const commonStyleHooks = {
+ open: (value: boolean) => ({ 'data-menu': value ? 'open' : 'closed' }),
+};
diff --git a/packages/mui-base/src/legacy/Dropdown/Dropdown.test.tsx b/packages/mui-base/src/legacy/Dropdown/Dropdown.test.tsx
deleted file mode 100644
index 5dcba8589b..0000000000
--- a/packages/mui-base/src/legacy/Dropdown/Dropdown.test.tsx
+++ /dev/null
@@ -1,176 +0,0 @@
-import * as React from 'react';
-import { expect } from 'chai';
-import {
- act,
- createRenderer,
- flushMicrotasks,
- MuiRenderResult,
- RenderOptions,
-} from '@mui/internal-test-utils';
-import { Dropdown } from '@base_ui/react/legacy/Dropdown';
-import { DropdownContext } from '@base_ui/react/legacy/useDropdown';
-import { MenuButton } from '@base_ui/react/legacy/MenuButton';
-import { MenuItem } from '@base_ui/react/legacy/MenuItem';
-import { Menu } from '@base_ui/react/legacy/Menu';
-import { MenuProvider, useMenu } from '@base_ui/react/legacy/useMenu';
-import { Unstable_Popup as Popup } from '@base_ui/react/legacy/Unstable_Popup';
-
-describe(' ', () => {
- const { render: internalRender } = createRenderer();
-
- async function render(
- element: React.ReactElement>,
- options?: RenderOptions,
- ): Promise {
- const rendered = internalRender(element, options);
- await flushMicrotasks();
- return rendered;
- }
-
- it('registers a popup id correctly', async () => {
- function TestComponent() {
- const { registerPopup, popupId } = React.useContext(DropdownContext)!;
- expect(context).not.to.equal(null);
-
- React.useEffect(() => {
- registerPopup('test-popup');
- }, [registerPopup]);
-
- return {popupId}
;
- }
-
- const { container } = await render(
-
-
- ,
- );
-
- expect(container.innerHTML).to.equal('test-popup
');
- });
-
- it('registers a trigger element correctly', async () => {
- const trigger = document.createElement('button');
- trigger.setAttribute('data-testid', 'test-button');
-
- function TestComponent() {
- const { registerTrigger, triggerElement } = React.useContext(DropdownContext)!;
- expect(context).not.to.equal(null);
-
- React.useEffect(() => {
- registerTrigger(trigger);
- }, [registerTrigger]);
-
- return {triggerElement?.getAttribute('data-testid')}
;
- }
-
- const { container } = await render(
-
-
- ,
- );
-
- expect(container.innerHTML).to.equal('test-button
');
- });
-
- it('focuses the first item after the menu is opened', async () => {
- const { getByRole, getAllByRole } = await render(
-
-
- Toggle
-
- One
- Two
- Three
-
-
-
,
- );
-
- const button = getByRole('button');
- act(() => {
- button.click();
- });
-
- const menuItems = getAllByRole('menuitem');
-
- await flushMicrotasks();
-
- expect(menuItems[0]).toHaveFocus();
- });
-
- it('should focus on second item when 1st item is disabled and disabledItemsFocusable set to false', async () => {
- const CustomMenu = React.forwardRef(function CustomMenu(
- props: React.ComponentPropsWithoutRef<'ul'>,
- ref: React.Ref,
- ) {
- const { children, ...other } = props;
-
- const { open, triggerElement, contextValue, getListboxProps } = useMenu({
- listboxRef: ref,
- disabledItemsFocusable: false,
- });
-
- return (
-
-
-
- );
- });
-
- const { getByRole, getAllByRole } = await render(
-
-
- Toggle
-
- One
- Two
- Three
-
-
-
,
- );
-
- const button = getByRole('button');
- act(() => {
- button.click();
- });
-
- const menuItems = getAllByRole('menuitem');
-
- await flushMicrotasks();
-
- expect(menuItems[1]).toHaveFocus();
- });
-
- it('focuses the trigger after the menu is closed', async () => {
- const { getByRole } = await render(
-
-
-
- Toggle
-
- Close
-
-
-
-
,
- );
-
- const button = getByRole('button');
- act(() => {
- button.click();
- });
-
- const menuItem = getByRole('menuitem');
-
- await flushMicrotasks();
-
- act(() => {
- menuItem.click();
- });
-
- expect(button).toHaveFocus();
- });
-});
diff --git a/packages/mui-base/src/legacy/Dropdown/Dropdown.tsx b/packages/mui-base/src/legacy/Dropdown/Dropdown.tsx
deleted file mode 100644
index 0419a72ce3..0000000000
--- a/packages/mui-base/src/legacy/Dropdown/Dropdown.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-'use client';
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { exactProp } from '@mui/utils';
-import { DropdownProps } from './Dropdown.types';
-import { DropdownContext } from '../useDropdown/DropdownContext';
-import { useDropdown } from '../useDropdown/useDropdown';
-/**
- *
- * Demos:
- *
- * - [Menu](https://mui.com/base-ui/react-menu/)
- *
- * API:
- *
- * - [Dropdown API](https://mui.com/base-ui/react-menu/components-api/#dropdown)
- */
-function Dropdown(props: DropdownProps) {
- const { children, open, defaultOpen, onOpenChange } = props;
-
- const { contextValue } = useDropdown({
- defaultOpen,
- onOpenChange,
- open,
- });
-
- return {children} ;
-}
-
-Dropdown.propTypes /* remove-proptypes */ = {
- // ┌────────────────────────────── Warning ──────────────────────────────┐
- // │ These PropTypes are generated from the TypeScript type definitions. │
- // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
- // └─────────────────────────────────────────────────────────────────────┘
- /**
- * @ignore
- */
- children: PropTypes.node,
- /**
- * If `true`, the dropdown is initially open.
- */
- defaultOpen: PropTypes.bool,
- /**
- * Callback fired when the component requests to be opened or closed.
- */
- onOpenChange: PropTypes.func,
- /**
- * Allows to control whether the dropdown is open.
- * This is a controlled counterpart of `defaultOpen`.
- */
- open: PropTypes.bool,
-} as any;
-
-if (process.env.NODE_ENV !== 'production') {
- // eslint-disable-next-line
- (Dropdown as any)['propTypes' + ''] = exactProp(Dropdown.propTypes);
-}
-
-export { Dropdown };
diff --git a/packages/mui-base/src/legacy/Dropdown/Dropdown.types.ts b/packages/mui-base/src/legacy/Dropdown/Dropdown.types.ts
deleted file mode 100644
index 58ca0c668a..0000000000
--- a/packages/mui-base/src/legacy/Dropdown/Dropdown.types.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-export interface DropdownProps {
- children: React.ReactNode;
- /**
- * If `true`, the dropdown is initially open.
- */
- defaultOpen?: boolean;
- /**
- * Callback fired when the component requests to be opened or closed.
- */
- onOpenChange?: (
- event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent | null,
- open: boolean,
- ) => void;
- /**
- * Allows to control whether the dropdown is open.
- * This is a controlled counterpart of `defaultOpen`.
- */
- open?: boolean;
-}
diff --git a/packages/mui-base/src/legacy/Dropdown/index.ts b/packages/mui-base/src/legacy/Dropdown/index.ts
deleted file mode 100644
index 7b97b2e607..0000000000
--- a/packages/mui-base/src/legacy/Dropdown/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './Dropdown';
-export * from './Dropdown.types';
diff --git a/packages/mui-base/src/legacy/Menu/Menu.spec.tsx b/packages/mui-base/src/legacy/Menu/Menu.spec.tsx
deleted file mode 100644
index 5f49cce301..0000000000
--- a/packages/mui-base/src/legacy/Menu/Menu.spec.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import * as React from 'react';
-import { expectType } from '@mui/types';
-import { Menu } from '@base_ui/react/legacy/Menu';
-
-const polymorphicComponentTest = () => {
- const CustomComponent: React.FC<{ stringProp: string; numberProp: number }> =
- function CustomComponent() {
- return
;
- };
-
- return (
-
- {/* @ts-expect-error */}
-
-
-
- slots={{
- root: 'a',
- }}
- href="#"
- />
-
-
- slots={{
- root: CustomComponent,
- }}
- stringProp="test"
- numberProp={0}
- />
-
- {/* @ts-expect-error required props not specified */}
-
- slots={{
- root: CustomComponent,
- }}
- />
-
-
- slots={{
- root: 'button',
- }}
- onClick={(e: React.MouseEvent) => e.currentTarget.checkValidity()}
- />
-
-
- slots={{
- root: 'button',
- }}
- ref={(elem) => {
- expectType(elem);
- }}
- onMouseDown={(e) => {
- expectType, typeof e>(e);
- e.currentTarget.checkValidity();
- }}
- />
-
- );
-};
diff --git a/packages/mui-base/src/legacy/Menu/Menu.test.tsx b/packages/mui-base/src/legacy/Menu/Menu.test.tsx
deleted file mode 100644
index 6427056987..0000000000
--- a/packages/mui-base/src/legacy/Menu/Menu.test.tsx
+++ /dev/null
@@ -1,694 +0,0 @@
-import * as React from 'react';
-import { expect } from 'chai';
-import { spy } from 'sinon';
-import {
- createRenderer,
- fireEvent,
- act,
- MuiRenderResult,
- RenderOptions,
- flushMicrotasks,
-} from '@mui/internal-test-utils';
-import { Menu, menuClasses } from '@base_ui/react/legacy/Menu';
-import { MenuItem, MenuItemRootSlotProps } from '@base_ui/react/legacy/MenuItem';
-import { DropdownContext, DropdownContextValue } from '@base_ui/react/legacy/useDropdown';
-import { Unstable_Popup as Popup } from '@base_ui/react/legacy/Unstable_Popup';
-import { MenuProvider, useMenu } from '@base_ui/react/legacy/useMenu';
-import { describeConformanceUnstyled } from '../../../test/describeConformanceUnstyled';
-
-function createAnchor() {
- const anchor = document.createElement('div');
- document.body.appendChild(anchor);
- return anchor;
-}
-
-const testContext: DropdownContextValue = {
- dispatch: () => {},
- popupId: 'menu-popup',
- registerPopup: () => {},
- registerTrigger: () => {},
- state: { open: true, changeReason: null },
- triggerElement: null,
-};
-
-describe(' ', () => {
- const { render: internalRender } = createRenderer();
-
- async function render(
- element: React.ReactElement>,
- options?: RenderOptions,
- ): Promise {
- const rendered = await internalRender(element, options);
- await flushMicrotasks();
- return rendered;
- }
-
- describeConformanceUnstyled( , () => ({
- inheritComponent: 'div',
- render: (node) => {
- return render(
- {node} ,
- );
- },
- refInstanceof: window.HTMLDivElement,
- slots: {
- root: {
- expectedClassName: menuClasses.root,
- },
- listbox: {
- expectedClassName: menuClasses.listbox,
- },
- },
- skip: ['componentProp', 'slotsProp'],
- }));
-
- describe('after initialization', () => {
- function Test() {
- return (
-
-
- 1
- 2
- 3
-
-
- );
- }
-
- it('highlights the first item when the menu is opened', async () => {
- const { getAllByRole } = await render( );
- const [firstItem, ...otherItems] = getAllByRole('menuitem');
-
- expect(firstItem.tabIndex).to.equal(0);
- otherItems.forEach((item) => {
- expect(item.tabIndex).to.equal(-1);
- });
- });
-
- it('highlights first item when down arrow key opens the menu', async () => {
- const context: DropdownContextValue = {
- ...testContext,
- state: {
- ...testContext.state,
- open: true,
- changeReason: {
- type: 'keydown',
- key: 'ArrowDown',
- } as React.KeyboardEvent,
- },
- };
- const { getAllByRole } = await render(
-
-
- 1
- 2
- 3
-
- ,
- );
- const [firstItem, ...otherItems] = getAllByRole('menuitem');
-
- expect(firstItem.tabIndex).to.equal(0);
- otherItems.forEach((item) => {
- expect(item.tabIndex).to.equal(-1);
- });
- });
-
- it('highlights last item when up arrow key opens the menu', async () => {
- const context: DropdownContextValue = {
- ...testContext,
- state: {
- ...testContext.state,
- open: true,
- changeReason: {
- key: 'ArrowUp',
- type: 'keydown',
- } as React.KeyboardEvent,
- },
- };
- const { getAllByRole } = await render(
-
-
- 1
- 2
- 3
-
- ,
- );
-
- const [firstItem, secondItem, lastItem] = getAllByRole('menuitem');
-
- expect(lastItem.tabIndex).to.equal(0);
- [firstItem, secondItem].forEach((item) => {
- expect(item.tabIndex).to.equal(-1);
- });
- });
-
- it('highlights last non-disabled item when disabledItemsFocusable is set to false', async () => {
- const CustomMenu = React.forwardRef(function CustomMenu(
- props: React.ComponentPropsWithoutRef<'ul'>,
- ref: React.Ref,
- ) {
- const { children, ...other } = props;
-
- const { open, triggerElement, contextValue, getListboxProps } = useMenu({
- listboxRef: ref,
- disabledItemsFocusable: false,
- });
-
- const anchorEl = triggerElement ?? createAnchor();
-
- return (
-
-
-
- );
- });
-
- const context: DropdownContextValue = {
- ...testContext,
- state: {
- ...testContext.state,
- open: true,
- changeReason: {
- key: 'ArrowUp',
- type: 'keydown',
- } as React.KeyboardEvent,
- },
- };
- const { getAllByRole } = await render(
-
-
- 1
- 2
- 3
-
- ,
- );
- const [firstItem, secondItem, lastItem] = getAllByRole('menuitem');
-
- expect(secondItem.tabIndex).to.equal(0);
- [firstItem, lastItem].forEach((item) => {
- expect(item.tabIndex).to.equal(-1);
- });
- });
- });
-
- describe('keyboard navigation', () => {
- it('changes the highlighted item using the arrow keys', async () => {
- const { getByTestId } = await render(
-
-
- 1
- 2
- 3
-
- ,
- );
-
- const item1 = getByTestId('item-1');
- const item2 = getByTestId('item-2');
- const item3 = getByTestId('item-3');
-
- act(() => {
- item1.focus();
- });
-
- fireEvent.keyDown(item1, { key: 'ArrowDown' });
- expect(document.activeElement).to.equal(item2);
-
- fireEvent.keyDown(item2, { key: 'ArrowDown' });
- expect(document.activeElement).to.equal(item3);
-
- fireEvent.keyDown(item3, { key: 'ArrowUp' });
- expect(document.activeElement).to.equal(item2);
- });
-
- it('changes the highlighted item using the Home and End keys', async () => {
- const { getByTestId } = await render(
-
-
- 1
- 2
- 3
-
- ,
- );
-
- const item1 = getByTestId('item-1');
- const item3 = getByTestId('item-3');
-
- act(() => {
- item1.focus();
- });
-
- fireEvent.keyDown(item1, { key: 'End' });
- expect(document.activeElement).to.equal(getByTestId('item-3'));
-
- fireEvent.keyDown(item3, { key: 'Home' });
- expect(document.activeElement).to.equal(getByTestId('item-1'));
- });
-
- it('includes disabled items during keyboard navigation', async () => {
- const { getByTestId } = await render(
-
-
- 1
-
- 2
-
-
- ,
- );
-
- const item1 = getByTestId('item-1');
- const item2 = getByTestId('item-2');
-
- act(() => {
- item1.focus();
- });
-
- fireEvent.keyDown(item1, { key: 'ArrowDown' });
- expect(document.activeElement).to.equal(item2);
-
- expect(item2).to.have.attribute('aria-disabled', 'true');
- });
-
- describe('text navigation', () => {
- it('changes the highlighted item', async function test() {
- if (/jsdom/.test(window.navigator.userAgent)) {
- // useMenu Text navigation match menu items using HTMLElement.innerText
- // innerText is not supported by JsDom
- this.skip();
- }
-
- const { getByText, getAllByRole } = await render(
-
-
- Aa
- Ba
- Bb
- Ca
- Cb
- Cd
-
- ,
- );
-
- const items = getAllByRole('menuitem');
-
- act(() => {
- items[0].focus();
- });
-
- fireEvent.keyDown(items[0], { key: 'c' });
- expect(document.activeElement).to.equal(getByText('Ca'));
- expect(getByText('Ca')).to.have.attribute('tabindex', '0');
-
- fireEvent.keyDown(items[3], { key: 'd' });
- expect(document.activeElement).to.equal(getByText('Cd'));
- expect(getByText('Cd')).to.have.attribute('tabindex', '0');
- });
-
- it('repeated keys circulate all items starting with that letter', async function test() {
- if (/jsdom/.test(window.navigator.userAgent)) {
- // useMenu Text navigation match menu items using HTMLElement.innerText
- // innerText is not supported by JsDom
- this.skip();
- }
-
- const { getByText, getAllByRole } = await render(
-
-
- Aa
- Ba
- Bb
- Ca
-
- ,
- );
-
- const items = getAllByRole('menuitem');
-
- act(() => {
- items[0].focus();
- });
-
- fireEvent.keyDown(items[0], { key: 'b' });
- expect(document.activeElement).to.equal(getByText('Ba'));
- expect(getByText('Ba')).to.have.attribute('tabindex', '0');
-
- fireEvent.keyDown(items[1], { key: 'b' });
- expect(document.activeElement).to.equal(getByText('Bb'));
- expect(getByText('Bb')).to.have.attribute('tabindex', '0');
-
- fireEvent.keyDown(items[2], { key: 'b' });
- expect(document.activeElement).to.equal(getByText('Ba'));
- expect(getByText('Ba')).to.have.attribute('tabindex', '0');
- });
-
- it('changes the highlighted item using text navigation on label prop', async () => {
- const { getAllByRole } = await render(
-
-
- 1
- 2
- 3
- 4
-
- ,
- );
-
- const items = getAllByRole('menuitem');
-
- act(() => {
- items[0].focus();
- });
-
- fireEvent.keyDown(items[0], { key: 'b' });
- expect(document.activeElement).to.equal(items[1]);
- expect(items[1]).to.have.attribute('tabindex', '0');
-
- fireEvent.keyDown(items[1], { key: 'b' });
- expect(document.activeElement).to.equal(items[2]);
- expect(items[2]).to.have.attribute('tabindex', '0');
-
- fireEvent.keyDown(items[2], { key: 'b' });
- expect(document.activeElement).to.equal(items[1]);
- expect(items[1]).to.have.attribute('tabindex', '0');
- });
-
- it('skips the non-stringifiable items', async function test() {
- if (/jsdom/.test(window.navigator.userAgent)) {
- // useMenu Text navigation match menu items using HTMLElement.innerText
- // innerText is not supported by JsDom
- this.skip();
- }
-
- const { getByText, getAllByRole } = await render(
-
-
- Aa
- Ba
-
-
- Nested Content
-
- {undefined}
- {null}
- Bc
-
- ,
- );
-
- const items = getAllByRole('menuitem');
-
- act(() => {
- items[0].focus();
- });
-
- fireEvent.keyDown(items[0], { key: 'b' });
- expect(document.activeElement).to.equal(getByText('Ba'));
- expect(getByText('Ba')).to.have.attribute('tabindex', '0');
-
- fireEvent.keyDown(items[1], { key: 'b' });
- expect(document.activeElement).to.equal(getByText('Bc'));
- expect(getByText('Bc')).to.have.attribute('tabindex', '0');
-
- fireEvent.keyDown(items[6], { key: 'b' });
- expect(document.activeElement).to.equal(getByText('Ba'));
- expect(getByText('Ba')).to.have.attribute('tabindex', '0');
- });
-
- it('navigate to options with diacritic characters', async function test() {
- if (/jsdom/.test(window.navigator.userAgent)) {
- // useMenu Text navigation match menu items using HTMLElement.innerText
- // innerText is not supported by JsDom
- this.skip();
- }
-
- const { getByText, getAllByRole } = await render(
-
-
- Aa
- Ba
- Bb
- Bą
-
- ,
- );
-
- const items = getAllByRole('menuitem');
-
- act(() => {
- items[0].focus();
- });
-
- fireEvent.keyDown(items[0], { key: 'b' });
- expect(document.activeElement).to.equal(getByText('Ba'));
- expect(getByText('Ba')).to.have.attribute('tabindex', '0');
-
- fireEvent.keyDown(items[1], { key: 'Control' });
- fireEvent.keyDown(items[1], { key: 'Alt' });
- fireEvent.keyDown(items[1], { key: 'ą' });
- expect(document.activeElement).to.equal(getByText('Bą'));
- expect(getByText('Bą')).to.have.attribute('tabindex', '0');
- });
-
- it('navigate to next options with beginning diacritic characters', async function test() {
- if (/jsdom/.test(window.navigator.userAgent)) {
- // useMenu Text navigation match menu items using HTMLElement.innerText
- // innerText is not supported by JsDom
- this.skip();
- }
-
- const { getByText, getAllByRole } = await render(
-
-
- Aa
- ąa
- ąb
- ąc
-
- ,
- );
-
- const items = getAllByRole('menuitem');
-
- act(() => {
- items[0].focus();
- });
-
- fireEvent.keyDown(items[0], { key: 'Control' });
- fireEvent.keyDown(items[0], { key: 'Alt' });
- fireEvent.keyDown(items[0], { key: 'ą' });
- expect(document.activeElement).to.equal(getByText('ąa'));
- expect(getByText('ąa')).to.have.attribute('tabindex', '0');
-
- fireEvent.keyDown(items[1], { key: 'Alt' });
- fireEvent.keyDown(items[1], { key: 'Control' });
- fireEvent.keyDown(items[1], { key: 'ą' });
- expect(document.activeElement).to.equal(getByText('ąb'));
- expect(getByText('ąb')).to.have.attribute('tabindex', '0');
-
- fireEvent.keyDown(items[2], { key: 'Control' });
- fireEvent.keyDown(items[2], { key: 'AltGraph' });
- fireEvent.keyDown(items[2], { key: 'ą' });
- expect(document.activeElement).to.equal(getByText('ąc'));
- expect(getByText('ąc')).to.have.attribute('tabindex', '0');
- });
- });
- });
-
- describe('prop: onItemsChange', () => {
- it('should be called when the menu items change', async () => {
- const handleItemsChange = spy();
-
- const { setProps } = await render(
-
-
- 1
- 2
-
- ,
- );
-
- // The first call is the initial render.
- expect(handleItemsChange.callCount).to.equal(1);
-
- setProps({
- children: (
-
- 1
- 3
-
- ),
- });
-
- expect(handleItemsChange.callCount).to.equal(2);
- });
- });
-
- describe('prop: anchor', () => {
- it('should be placed near the specified element', async function test() {
- if (/jsdom/.test(window.navigator.userAgent)) {
- this.skip();
- }
-
- function TestComponent() {
- const [anchor, setAnchor] = React.useState(null);
-
- return (
-
- );
- }
-
- const { getByTestId } = await render( );
-
- const popup = getByTestId('popup');
- const anchor = getByTestId('anchor');
-
- const anchorPosition = anchor.getBoundingClientRect();
-
- await new Promise((resolve) => {
- // position gets updated in the next frame
- requestAnimationFrame(() => {
- expect(popup.style.getPropertyValue('transform')).to.equal(
- `translate(${anchorPosition.left}px, ${anchorPosition.bottom}px)`,
- );
- resolve();
- });
- });
- });
-
- it('should be placed at the specified position', async function test() {
- if (/jsdom/.test(window.navigator.userAgent)) {
- this.skip();
- }
-
- const boundingRect = {
- x: 200,
- y: 100,
- top: 100,
- left: 200,
- bottom: 100,
- right: 200,
- height: 0,
- width: 0,
- toJSON: () => {},
- };
-
- const virtualElement = { getBoundingClientRect: () => boundingRect };
-
- const { getByTestId } = await render(
-
-
- 1
- 2
-
- ,
- );
- const popup = getByTestId('popup');
-
- await new Promise((resolve) => {
- // position gets updated in the next frame
- requestAnimationFrame(() => {
- expect(popup.style.getPropertyValue('transform')).to.equal(`translate(200px, 100px)`);
- resolve();
- });
- });
- });
- });
-
- it('perf: does not rerender menu items unnecessarily', async () => {
- const renderItem1Spy = spy();
- const renderItem2Spy = spy();
- const renderItem3Spy = spy();
- const renderItem4Spy = spy();
-
- const LoggingRoot = React.forwardRef(function LoggingRoot(
- props: MenuItemRootSlotProps & { renderSpy: () => void },
- ref: React.ForwardedRef,
- ) {
- const { renderSpy, ownerState, ...other } = props;
- renderSpy();
- return ;
- });
-
- const { getAllByRole } = await render(
-
-
-
- 1
-
-
- 2
-
-
- 3
-
-
- 4
-
-
- ,
- );
-
- const menuItems = getAllByRole('menuitem');
- act(() => {
- menuItems[0].focus();
- });
-
- renderItem1Spy.resetHistory();
- renderItem2Spy.resetHistory();
- renderItem3Spy.resetHistory();
- renderItem4Spy.resetHistory();
-
- expect(renderItem1Spy.callCount).to.equal(0);
-
- fireEvent.keyDown(menuItems[0], { key: 'ArrowDown' }); // highlights '2'
-
- // React renders twice in strict mode, so we expect twice the number of spy calls
- // Also, useButton's focusVisible polyfill causes an extra render when focus is gained/lost.
-
- expect(renderItem1Spy.callCount).to.equal(4); // '1' rerenders as it loses highlight
- expect(renderItem2Spy.callCount).to.equal(4); // '2' rerenders as it receives highlight
-
- // neither the highlighted nor the selected state of these options changed,
- // so they don't need to rerender:
- expect(renderItem3Spy.callCount).to.equal(0);
- expect(renderItem4Spy.callCount).to.equal(0);
- });
-});
diff --git a/packages/mui-base/src/legacy/Menu/Menu.tsx b/packages/mui-base/src/legacy/Menu/Menu.tsx
deleted file mode 100644
index 4c46516eea..0000000000
--- a/packages/mui-base/src/legacy/Menu/Menu.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-'use client';
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { HTMLElementType, refType } from '@mui/utils';
-import { PolymorphicComponent } from '../utils/PolymorphicComponent';
-import { MenuOwnerState, MenuProps, MenuRootSlotProps, MenuTypeMap } from './Menu.types';
-import { getMenuUtilityClass } from './menuClasses';
-import { useMenu } from '../useMenu';
-import { MenuProvider } from '../useMenu/MenuProvider';
-import { unstable_composeClasses as composeClasses } from '../composeClasses';
-import { Unstable_Popup as Popup } from '../Unstable_Popup';
-import { useSlotProps } from '../utils/useSlotProps';
-import { useClassNamesOverride } from '../utils/ClassNameConfigurator';
-import { WithOptionalOwnerState } from '../utils/types';
-import { ListActionTypes } from '../../useList';
-
-function useUtilityClasses(ownerState: MenuOwnerState) {
- const { open } = ownerState;
- const slots = {
- root: ['root', open && 'expanded'],
- listbox: ['listbox', open && 'expanded'],
- };
-
- return composeClasses(slots, useClassNamesOverride(getMenuUtilityClass));
-}
-
-/**
- *
- * Demos:
- *
- * - [Menu](https://mui.com/base-ui/react-menu/)
- *
- * API:
- *
- * - [Menu API](https://mui.com/base-ui/react-menu/components-api/#menu)
- */
-const Menu = React.forwardRef(function Menu(
- props: MenuProps,
- forwardedRef: React.ForwardedRef,
-) {
- const {
- actions,
- anchor: anchorProp,
- children,
- onItemsChange,
- slotProps = {},
- slots = {},
- ...other
- } = props;
-
- const { contextValue, getListboxProps, dispatch, open, triggerElement } = useMenu({
- onItemsChange,
- componentName: 'Menu',
- });
-
- const anchor = anchorProp ?? triggerElement;
-
- React.useImperativeHandle(
- actions,
- () => ({
- dispatch,
- resetHighlight: () => dispatch({ type: ListActionTypes.resetHighlight, event: null }),
- }),
- [dispatch],
- );
-
- const ownerState: MenuOwnerState = { ...props, open };
-
- const classes = useUtilityClasses(ownerState);
-
- const Root = slots.root ?? 'div';
- const rootProps = useSlotProps({
- elementType: Root,
- externalSlotProps: slotProps.root,
- externalForwardedProps: other,
- additionalProps: {
- ref: forwardedRef,
- role: undefined,
- },
- className: classes.root,
- ownerState,
- });
-
- const Listbox = slots.listbox ?? 'ul';
- const listboxProps: WithOptionalOwnerState = useSlotProps({
- elementType: Listbox,
- getSlotProps: getListboxProps,
- externalSlotProps: slotProps.listbox,
- className: classes.listbox,
- ownerState,
- });
-
- if (open === true && anchor == null) {
- return (
-
-
- {children}
-
-
- );
- }
-
- return (
-
-
- {children}
-
-
- );
-}) as PolymorphicComponent;
-
-Menu.propTypes /* remove-proptypes */ = {
- // ┌────────────────────────────── Warning ──────────────────────────────┐
- // │ These PropTypes are generated from the TypeScript type definitions. │
- // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
- // └─────────────────────────────────────────────────────────────────────┘
- /**
- * A ref with imperative actions that can be performed on the menu.
- */
- actions: refType,
- /**
- * The element based on which the menu is positioned.
- */
- anchor: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
- HTMLElementType,
- PropTypes.object,
- PropTypes.func,
- ]),
- /**
- * @ignore
- */
- children: PropTypes.node,
- /**
- * @ignore
- */
- className: PropTypes.string,
- /**
- * Function called when the items displayed in the menu change.
- */
- onItemsChange: PropTypes.func,
- /**
- * The props used for each slot inside the Menu.
- * @default {}
- */
- slotProps: PropTypes.shape({
- listbox: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
- root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
- }),
- /**
- * The components used for each slot inside the Menu.
- * Either a string to use a HTML element or a component.
- * @default {}
- */
- slots: PropTypes.shape({
- listbox: PropTypes.elementType,
- root: PropTypes.elementType,
- }),
-} as any;
-
-export { Menu };
diff --git a/packages/mui-base/src/legacy/Menu/Menu.types.ts b/packages/mui-base/src/legacy/Menu/Menu.types.ts
deleted file mode 100644
index 4dfacfe21a..0000000000
--- a/packages/mui-base/src/legacy/Menu/Menu.types.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import * as React from 'react';
-import { Simplify } from '@mui/types';
-import { PolymorphicProps } from '../utils/PolymorphicComponent';
-import { SlotComponentProps } from '../utils/types';
-import { UseMenuListboxSlotProps } from '../useMenu';
-import { ListAction } from '../../useList';
-import { PopupProps } from '../Unstable_Popup';
-
-export interface MenuRootSlotPropsOverrides {}
-export interface MenuListboxSlotPropsOverrides {}
-
-export interface MenuActions {
- /**
- * Dispatches an action that can cause a change to the menu's internal state.
- */
- dispatch: (action: ListAction) => void;
- /**
- * Resets the highlighted item.
- */
- resetHighlight: () => void;
-}
-
-export interface MenuOwnProps {
- /**
- * A ref with imperative actions that can be performed on the menu.
- */
- actions?: React.Ref;
- /**
- * The element based on which the menu is positioned.
- */
- anchor?: PopupProps['anchor'];
- children?: React.ReactNode;
- className?: string;
- /**
- * Function called when the items displayed in the menu change.
- */
- onItemsChange?: (items: string[]) => void;
- /**
- * The props used for each slot inside the Menu.
- * @default {}
- */
- slotProps?: {
- root?: SlotComponentProps<'div', MenuRootSlotPropsOverrides & PopupProps, MenuOwnerState>;
- listbox?: SlotComponentProps<'ul', MenuListboxSlotPropsOverrides, MenuOwnerState>;
- };
- /**
- * The components used for each slot inside the Menu.
- * Either a string to use a HTML element or a component.
- * @default {}
- */
- slots?: MenuSlots;
-}
-
-export interface MenuSlots {
- /**
- * The component that renders the popup element.
- * @default 'div'
- */
- root?: React.ElementType;
- /**
- * The component that renders the listbox.
- * @default 'ul'
- */
- listbox?: React.ElementType;
-}
-
-export interface MenuTypeMap<
- AdditionalProps = {},
- RootComponentType extends React.ElementType = 'div',
-> {
- props: MenuOwnProps & AdditionalProps;
- defaultComponent: RootComponentType;
-}
-
-export type MenuProps<
- RootComponentType extends React.ElementType = MenuTypeMap['defaultComponent'],
-> = PolymorphicProps, RootComponentType>;
-
-export type MenuOwnerState = Simplify<
- MenuOwnProps & {
- open: boolean;
- }
->;
-
-export type MenuRootSlotProps = {
- children?: React.ReactNode;
- className?: string;
- ownerState: MenuOwnerState;
- ref: React.Ref;
-};
-
-export type MenuListboxSlotProps = UseMenuListboxSlotProps & {
- children?: React.ReactNode;
- className?: string;
- ownerState: MenuOwnerState;
-};
diff --git a/packages/mui-base/src/legacy/Menu/index.tsx b/packages/mui-base/src/legacy/Menu/index.tsx
deleted file mode 100644
index a57130128b..0000000000
--- a/packages/mui-base/src/legacy/Menu/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-export { Menu } from './Menu';
-
-export * from './menuClasses';
-
-export * from './Menu.types';
diff --git a/packages/mui-base/src/legacy/Menu/menuClasses.ts b/packages/mui-base/src/legacy/Menu/menuClasses.ts
deleted file mode 100644
index 53110b6024..0000000000
--- a/packages/mui-base/src/legacy/Menu/menuClasses.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { generateUtilityClass } from '../generateUtilityClass';
-import { generateUtilityClasses } from '../generateUtilityClasses';
-
-const COMPONENT_NAME = 'Menu';
-
-export interface MenuClasses {
- /** Class name applied to the root element. */
- root: string;
- /** Class name applied to the listbox element. */
- listbox: string;
- /** State class applied to the root element if `open={true}`. */
- expanded: string;
-}
-
-export type MenuClassKey = keyof MenuClasses;
-
-export function getMenuUtilityClass(slot: string): string {
- return generateUtilityClass(COMPONENT_NAME, slot);
-}
-
-export const menuClasses: MenuClasses = generateUtilityClasses(COMPONENT_NAME, [
- 'root',
- 'listbox',
- 'expanded',
-]);
diff --git a/packages/mui-base/src/legacy/MenuButton/MenuButton.test.tsx b/packages/mui-base/src/legacy/MenuButton/MenuButton.test.tsx
deleted file mode 100644
index c69d08ee3b..0000000000
--- a/packages/mui-base/src/legacy/MenuButton/MenuButton.test.tsx
+++ /dev/null
@@ -1,237 +0,0 @@
-import * as React from 'react';
-import { expect } from 'chai';
-import { spy } from 'sinon';
-import userEvent from '@testing-library/user-event';
-import { act, createRenderer } from '@mui/internal-test-utils';
-import { MenuButton, menuButtonClasses } from '@base_ui/react/legacy/MenuButton';
-import {
- DropdownContext,
- DropdownContextValue,
- DropdownActionTypes,
-} from '@base_ui/react/legacy/useDropdown';
-import { describeConformanceUnstyled } from '../../../test/describeConformanceUnstyled';
-
-const testContext: DropdownContextValue = {
- dispatch: () => {},
- popupId: 'menu-popup',
- registerPopup: () => {},
- registerTrigger: () => {},
- state: { open: true, changeReason: null },
- triggerElement: null,
-};
-
-describe(' ', () => {
- const { render } = createRenderer();
-
- describeConformanceUnstyled( , () => ({
- inheritComponent: 'button',
- render: (node) => {
- return render(
- {node} ,
- );
- },
- refInstanceof: window.HTMLButtonElement,
- slots: {
- root: {
- expectedClassName: menuButtonClasses.root,
- testWithElement: null,
- },
- },
- skip: ['componentProp'],
- }));
-
- describe('prop: disabled', () => {
- it('should render a disabled button', () => {
- const { getByRole } = render(
-
-
- ,
- );
-
- const button = getByRole('button');
- expect(button).to.have.property('disabled', true);
- });
-
- it('should not open the menu when clicked', () => {
- const dispatchSpy = spy();
- const context = {
- ...testContext,
- state: { open: false, changeReason: null },
- dispatch: dispatchSpy,
- };
-
- const { getByRole } = render(
-
-
- ,
- );
-
- const button = getByRole('button');
- button.click();
-
- expect(dispatchSpy.called).to.equal(false);
- });
- });
-
- describe('prop: focusableWhenDisabled', () => {
- it('has the aria-disabled instead of disabled attribute when disabled', () => {
- const { getByRole } = render(
-
-
- ,
- );
-
- const button = getByRole('button');
- expect(button).to.have.attribute('aria-disabled');
- expect(button).not.to.have.attribute('disabled');
- });
-
- it('can receive focus when focusableWhenDisabled is set', () => {
- const { getByRole } = render(
-
-
- ,
- );
-
- const button = getByRole('button');
- act(() => {
- button.focus();
- });
-
- expect(document.activeElement).to.equal(button);
- });
- });
-
- it('toggles the menu state when clicked', () => {
- const dispatchSpy = spy();
- const context = {
- ...testContext,
- state: { open: false, changeReason: null },
- dispatch: dispatchSpy,
- };
-
- const { getByRole } = render(
-
-
- ,
- );
-
- const button = getByRole('button');
- button.click();
-
- expect(dispatchSpy.calledOnce).to.equal(true);
- expect(dispatchSpy.args[0][0]).to.contain({ type: DropdownActionTypes.toggle });
- });
-
- describe('keyboard navigation', () => {
- [ , ].forEach((buttonComponent) => {
- const buttonType = buttonComponent.props.slots?.root ? 'non-native' : 'native';
- ['ArrowUp', 'ArrowDown'].forEach((key) =>
- it(`opens the menu when pressing "${key}" on a ${buttonType} button`, async () => {
- const dispatchSpy = spy();
- const context = {
- ...testContext,
- state: { open: false, changeReason: null },
- dispatch: dispatchSpy,
- };
-
- const user = userEvent.setup();
-
- const { getByRole } = render(
- {buttonComponent} ,
- );
-
- const button = getByRole('button');
- act(() => {
- button.focus();
- });
-
- await user.keyboard(`{${key}}`);
-
- expect(dispatchSpy.calledOnce).to.equal(true);
- expect(dispatchSpy.args[0][0]).to.contain({ type: DropdownActionTypes.open });
- }),
- );
-
- ['Enter', ' '].forEach((key) =>
- it(`opens the menu when pressing "${key}" on a ${buttonType} button`, async () => {
- const dispatchSpy = spy();
- const context = {
- ...testContext,
- state: { open: false, changeReason: null },
- dispatch: dispatchSpy,
- };
-
- const user = userEvent.setup();
-
- const { getByRole } = render(
- {buttonComponent} ,
- );
-
- const button = getByRole('button');
- act(() => {
- button.focus();
- });
-
- await user.keyboard(`{${key}}`);
-
- expect(dispatchSpy.calledOnce).to.equal(true);
- expect(dispatchSpy.args[0][0]).to.contain({ type: DropdownActionTypes.toggle });
- }),
- );
- });
- });
-
- describe('accessibility attributes', () => {
- it('has the aria-haspopup attribute', () => {
- const { getByRole } = render(
-
-
- ,
- );
-
- const button = getByRole('button');
- expect(button).to.have.attribute('aria-haspopup');
- });
-
- it('has the aria-expanded=false attribute when closed', () => {
- const context = {
- ...testContext,
- state: { open: false, changeReason: null },
- };
-
- const { getByRole } = render(
-
-
- ,
- );
- const button = getByRole('button');
- expect(button).to.have.attribute('aria-expanded', 'false');
- });
-
- it('has the aria-expanded=true attribute when open', () => {
- const context = {
- ...testContext,
- state: { open: true, changeReason: null },
- };
-
- const { getByRole } = render(
-
-
- ,
- );
- const button = getByRole('button');
- expect(button).to.have.attribute('aria-expanded', 'true');
- });
-
- it('has the aria-controls attribute', () => {
- const { getByRole } = render(
-
-
- ,
- );
- const button = getByRole('button');
- expect(button).to.have.attribute('aria-controls', 'menu-popup');
- });
- });
-});
diff --git a/packages/mui-base/src/legacy/MenuButton/MenuButton.tsx b/packages/mui-base/src/legacy/MenuButton/MenuButton.tsx
deleted file mode 100644
index a62dc97a75..0000000000
--- a/packages/mui-base/src/legacy/MenuButton/MenuButton.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-'use client';
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { MenuButtonOwnerState, MenuButtonProps } from './MenuButton.types';
-import { useSlotProps } from '../utils/useSlotProps';
-import { useMenuButton } from '../useMenuButton';
-import { unstable_composeClasses as composeClasses } from '../composeClasses';
-import { useClassNamesOverride } from '../utils/ClassNameConfigurator';
-import { getMenuButtonUtilityClass } from './menuButtonClasses';
-
-const useUtilityClasses = (ownerState: MenuButtonOwnerState) => {
- const { active, disabled, open } = ownerState;
-
- const slots = {
- root: ['root', disabled && 'disabled', active && 'active', open && 'expanded'],
- };
-
- return composeClasses(slots, useClassNamesOverride(getMenuButtonUtilityClass));
-};
-
-/**
- *
- * Demos:
- *
- * - [Menu](https://mui.com/base-ui/react-menu/)
- *
- * API:
- *
- * - [MenuButton API](https://mui.com/base-ui/react-menu/components-api/#menu-button)
- */
-const MenuButton = React.forwardRef(function MenuButton(
- props: MenuButtonProps,
- forwardedRef: React.ForwardedRef,
-) {
- const {
- children,
- disabled = false,
- label,
- slots = {},
- slotProps = {},
- focusableWhenDisabled = false,
- ...other
- } = props;
-
- const { getRootProps, open, active } = useMenuButton({
- disabled,
- focusableWhenDisabled,
- rootRef: forwardedRef,
- });
-
- const ownerState: MenuButtonOwnerState = {
- ...props,
- open,
- active,
- disabled,
- focusableWhenDisabled,
- };
-
- const classes = useUtilityClasses(ownerState);
-
- const Root = slots.root || 'button';
- const rootProps = useSlotProps({
- elementType: Root,
- getSlotProps: getRootProps,
- externalForwardedProps: other,
- externalSlotProps: slotProps.root,
- additionalProps: {
- ref: forwardedRef,
- type: 'button',
- },
- ownerState,
- className: classes.root,
- });
-
- return {children} ;
-});
-
-MenuButton.propTypes /* remove-proptypes */ = {
- // ┌────────────────────────────── Warning ──────────────────────────────┐
- // │ These PropTypes are generated from the TypeScript type definitions. │
- // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
- // └─────────────────────────────────────────────────────────────────────┘
- /**
- * @ignore
- */
- children: PropTypes.node,
- /**
- * Class name applied to the root element.
- */
- className: PropTypes.string,
- /**
- * If `true`, the component is disabled.
- * @default false
- */
- disabled: PropTypes.bool,
- /**
- * If `true`, allows a disabled button to receive focus.
- * @default false
- */
- focusableWhenDisabled: PropTypes.bool,
- /**
- * Label of the button
- */
- label: PropTypes.string,
- /**
- * The components used for each slot inside the MenuButton.
- * Either a string to use a HTML element or a component.
- * @default {}
- */
- slotProps: PropTypes.shape({
- root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
- }),
- /**
- * The props used for each slot inside the MenuButton.
- * @default {}
- */
- slots: PropTypes.shape({
- root: PropTypes.elementType,
- }),
-} as any;
-
-export { MenuButton };
diff --git a/packages/mui-base/src/legacy/MenuButton/MenuButton.types.ts b/packages/mui-base/src/legacy/MenuButton/MenuButton.types.ts
deleted file mode 100644
index 5d1d951824..0000000000
--- a/packages/mui-base/src/legacy/MenuButton/MenuButton.types.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { SlotComponentProps } from '../utils/types';
-
-export interface MenuButtonRootSlotPropsOverrides {}
-
-export interface MenuButtonProps {
- children?: React.ReactNode;
- /**
- * Class name applied to the root element.
- */
- className?: string;
- /**
- * If `true`, the component is disabled.
- * @default false
- */
- disabled?: boolean;
- /**
- * If `true`, allows a disabled button to receive focus.
- * @default false
- */
- focusableWhenDisabled?: boolean;
- /**
- * Label of the button
- */
- label?: string;
- /**
- * The props used for each slot inside the MenuButton.
- * @default {}
- */
- slots?: MenuButtonSlots;
- /**
- * The components used for each slot inside the MenuButton.
- * Either a string to use a HTML element or a component.
- * @default {}
- */
- slotProps?: {
- root?: SlotComponentProps<'button', MenuButtonRootSlotPropsOverrides, MenuButtonOwnerState>;
- };
-}
-
-export interface MenuButtonSlots {
- /**
- * The component that renders the root.
- * @default 'button'
- */
- root?: React.ElementType;
-}
-
-export type MenuButtonOwnerState = MenuButtonProps & {
- active: boolean;
- focusableWhenDisabled: boolean;
- open: boolean;
-};
diff --git a/packages/mui-base/src/legacy/MenuButton/index.ts b/packages/mui-base/src/legacy/MenuButton/index.ts
deleted file mode 100644
index 721bc436d4..0000000000
--- a/packages/mui-base/src/legacy/MenuButton/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-'use client';
-export { MenuButton } from './MenuButton';
-export * from './MenuButton.types';
-
-export * from './menuButtonClasses';
diff --git a/packages/mui-base/src/legacy/MenuButton/menuButtonClasses.ts b/packages/mui-base/src/legacy/MenuButton/menuButtonClasses.ts
deleted file mode 100644
index c0da95d392..0000000000
--- a/packages/mui-base/src/legacy/MenuButton/menuButtonClasses.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { generateUtilityClass } from '../generateUtilityClass';
-import { generateUtilityClasses } from '../generateUtilityClasses';
-
-const COMPONENT_NAME = 'MenuButton';
-
-export interface MenuButtonClasses {
- /** Class name applied to the root element. */
- root: string;
- /** State class applied to the root element if `active={true}`. */
- active: string;
- /** State class applied to the root element if `disabled={true}`. */
- disabled: string;
- /** State class applied to the root element if the associated menu is open. */
- expanded: string;
-}
-
-export type MenuButtonClassKey = keyof MenuButtonClasses;
-
-export function getMenuButtonUtilityClass(slot: string): string {
- return generateUtilityClass(COMPONENT_NAME, slot);
-}
-
-export const menuButtonClasses: MenuButtonClasses = generateUtilityClasses(COMPONENT_NAME, [
- 'root',
- 'active',
- 'disabled',
- 'expanded',
-]);
diff --git a/packages/mui-base/src/legacy/MenuItem/MenuItem.spec.tsx b/packages/mui-base/src/legacy/MenuItem/MenuItem.spec.tsx
deleted file mode 100644
index 3a009cf2b2..0000000000
--- a/packages/mui-base/src/legacy/MenuItem/MenuItem.spec.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import * as React from 'react';
-import { expectType } from '@mui/types';
-import { MenuItem } from '@base_ui/react/legacy/MenuItem';
-
-const polymorphicComponentTest = () => {
- const CustomComponent: React.FC<{ stringProp: string; numberProp: number }> =
- function CustomComponent() {
- return
;
- };
-
- return (
-
- {/* @ts-expect-error */}
-
-
-
- slots={{
- root: 'a',
- }}
- href="#"
- />
-
-
- slots={{
- root: CustomComponent,
- }}
- stringProp="test"
- numberProp={0}
- />
-
- {/* @ts-expect-error required props not specified */}
-
- slots={{
- root: CustomComponent,
- }}
- />
-
-
- slots={{
- root: 'button',
- }}
- onClick={(e: React.MouseEvent) => e.currentTarget.checkValidity()}
- />
-
-
- slots={{
- root: 'button',
- }}
- ref={(elem) => {
- expectType(elem);
- }}
- onMouseDown={(e) => {
- expectType, typeof e>(e);
- e.currentTarget.checkValidity();
- }}
- />
-
- );
-};
diff --git a/packages/mui-base/src/legacy/MenuItem/MenuItem.test.tsx b/packages/mui-base/src/legacy/MenuItem/MenuItem.test.tsx
deleted file mode 100644
index e04dd75c4c..0000000000
--- a/packages/mui-base/src/legacy/MenuItem/MenuItem.test.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import * as React from 'react';
-import { createRenderer } from '@mui/internal-test-utils';
-import { MenuItem, menuItemClasses } from '@base_ui/react/legacy/MenuItem';
-import { MenuProvider } from '@base_ui/react/legacy/useMenu';
-import { describeConformanceUnstyled } from '../../../test/describeConformanceUnstyled';
-
-const dummyGetItemState = () => ({
- disabled: false,
- highlighted: false,
- selected: false,
- index: 0,
- focusable: true,
-});
-
-const testContext = {
- dispatch: () => {},
- getItemIndex: () => 0,
- getItemProps: () => ({}),
- getItemState: dummyGetItemState,
- open: false,
- registerItem: () => ({ id: '', deregister: () => {} }),
- totalSubitemCount: 0,
-};
-
-describe(' ', () => {
- const { render } = createRenderer();
-
- describeConformanceUnstyled( , () => ({
- inheritComponent: 'li',
- render: (node) => {
- return render({node} );
- },
- refInstanceof: window.HTMLLIElement,
- testComponentPropWith: 'span',
- slots: {
- root: {
- expectedClassName: menuItemClasses.root,
- },
- },
- skip: ['componentProp'],
- }));
-});
diff --git a/packages/mui-base/src/legacy/MenuItem/MenuItem.tsx b/packages/mui-base/src/legacy/MenuItem/MenuItem.tsx
deleted file mode 100644
index 071603751e..0000000000
--- a/packages/mui-base/src/legacy/MenuItem/MenuItem.tsx
+++ /dev/null
@@ -1,149 +0,0 @@
-'use client';
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { PolymorphicComponent } from '../utils/PolymorphicComponent';
-import {
- MenuItemOwnerState,
- MenuItemProps,
- MenuItemRootSlotProps,
- MenuItemTypeMap,
-} from './MenuItem.types';
-import { getMenuItemUtilityClass } from './menuItemClasses';
-import { useMenuItem, useMenuItemContextStabilizer } from '../useMenuItem';
-import { unstable_composeClasses as composeClasses } from '../composeClasses';
-import { useSlotProps } from '../utils/useSlotProps';
-import { useClassNamesOverride } from '../utils/ClassNameConfigurator';
-import { WithOptionalOwnerState } from '../utils/types';
-import { ListContext } from '../../useList';
-
-function useUtilityClasses(ownerState: MenuItemOwnerState) {
- const { disabled, focusVisible } = ownerState;
-
- const slots = {
- root: ['root', disabled && 'disabled', focusVisible && 'focusVisible'],
- };
-
- return composeClasses(slots, useClassNamesOverride(getMenuItemUtilityClass));
-}
-
-const InnerMenuItem = React.memo(
- React.forwardRef(function MenuItem(
- props: MenuItemProps,
- forwardedRef: React.ForwardedRef,
- ) {
- const {
- children,
- disabled: disabledProp = false,
- label,
- id,
- slotProps = {},
- slots = {},
- ...other
- } = props;
-
- const { getRootProps, disabled, focusVisible, highlighted } = useMenuItem({
- id,
- disabled: disabledProp,
- rootRef: forwardedRef,
- label,
- });
-
- const ownerState: MenuItemOwnerState = { ...props, disabled, focusVisible, highlighted };
-
- const classes = useUtilityClasses(ownerState);
-
- const Root = slots.root ?? 'li';
- const rootProps: WithOptionalOwnerState = useSlotProps({
- elementType: Root,
- getSlotProps: getRootProps,
- externalSlotProps: slotProps.root,
- externalForwardedProps: other,
- className: classes.root,
- ownerState,
- });
-
- return {children} ;
- }),
-);
-
-/**
- * An unstyled menu item to be used within a Menu.
- *
- * Demos:
- *
- * - [Menu](https://mui.com/base-ui/react-menu/)
- *
- * API:
- *
- * - [MenuItem API](https://mui.com/base-ui/react-menu/components-api/#menu-item)
- */
-const MenuItem = React.forwardRef(function MenuItem(
- props: MenuItemProps,
- ref: React.ForwardedRef,
-) {
- const { id: idProp } = props;
-
- // This wrapper component is used as a performance optimization.
- // `useMenuItemContextStabilizer` ensures that the context value
- // is stable across renders, so that the actual MenuItem re-renders
- // only when it needs to.
- const { contextValue, id } = useMenuItemContextStabilizer(idProp);
-
- return (
-
-
-
- );
-}) as PolymorphicComponent;
-
-MenuItem.propTypes /* remove-proptypes */ = {
- // ┌────────────────────────────── Warning ──────────────────────────────┐
- // │ These PropTypes are generated from the TypeScript type definitions. │
- // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
- // └─────────────────────────────────────────────────────────────────────┘
- /**
- * @ignore
- */
- children: PropTypes.node,
- /**
- * @ignore
- */
- className: PropTypes.string,
- /**
- * If `true`, the menu item will be disabled.
- * @default false
- */
- disabled: PropTypes.bool,
- /**
- * If `true`, the menu item won't receive focus when the mouse moves over it.
- *
- * @default false
- */
- disableFocusOnHover: PropTypes.bool,
- /**
- * A text representation of the menu item's content.
- * Used for keyboard text navigation matching.
- */
- label: PropTypes.string,
- /**
- * @ignore
- */
- onClick: PropTypes.func,
- /**
- * The props used for each slot inside the MenuItem.
- * @default {}
- */
- slotProps: PropTypes.shape({
- root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
- }),
- /**
- * The components used for each slot inside the MenuItem.
- * Either a string to use a HTML element or a component.
- * @default {}
- */
- slots: PropTypes.shape({
- root: PropTypes.elementType,
- }),
-} as any;
-
-export { MenuItem };
diff --git a/packages/mui-base/src/legacy/MenuItem/MenuItem.types.ts b/packages/mui-base/src/legacy/MenuItem/MenuItem.types.ts
deleted file mode 100644
index 3c40da64b8..0000000000
--- a/packages/mui-base/src/legacy/MenuItem/MenuItem.types.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import * as React from 'react';
-import { Simplify } from '@mui/types';
-import { PolymorphicProps } from '../utils/PolymorphicComponent';
-import { SlotComponentProps } from '../utils/types';
-import { UseMenuItemRootSlotProps } from '../useMenuItem';
-
-export interface MenuItemRootSlotPropsOverrides {}
-
-export type MenuItemOwnerState = Simplify<
- MenuItemOwnProps & {
- disabled: boolean;
- focusVisible: boolean;
- highlighted: boolean;
- }
->;
-
-export interface MenuItemOwnProps {
- children?: React.ReactNode;
- className?: string;
- onClick?: React.MouseEventHandler;
- /**
- * If `true`, the menu item will be disabled.
- * @default false
- */
- disabled?: boolean;
- /**
- * The components used for each slot inside the MenuItem.
- * Either a string to use a HTML element or a component.
- * @default {}
- */
- slots?: MenuItemSlots;
- /**
- * The props used for each slot inside the MenuItem.
- * @default {}
- */
- slotProps?: {
- root?: SlotComponentProps<'li', MenuItemRootSlotPropsOverrides, MenuItemOwnerState>;
- };
- /**
- * A text representation of the menu item's content.
- * Used for keyboard text navigation matching.
- */
- label?: string;
- /**
- * If `true`, the menu item won't receive focus when the mouse moves over it.
- *
- * @default false
- */
- disableFocusOnHover?: boolean;
-}
-
-export interface MenuItemSlots {
- /**
- * The component that renders the root.
- * @default 'li'
- */
- root?: React.ElementType;
-}
-
-export interface MenuItemTypeMap<
- AdditionalProps = {},
- RootComponentType extends React.ElementType = 'li',
-> {
- props: MenuItemOwnProps & AdditionalProps;
- defaultComponent: RootComponentType;
-}
-
-export type MenuItemProps<
- RootComponentType extends React.ElementType = MenuItemTypeMap['defaultComponent'],
-> = PolymorphicProps, RootComponentType>;
-
-export interface MenuItemState {
- disabled: boolean;
- highlighted: boolean;
-}
-
-export type MenuItemRootSlotProps = Simplify<
- UseMenuItemRootSlotProps & {
- children?: React.ReactNode;
- className: string;
- ref: React.Ref;
- ownerState: MenuItemOwnerState;
- }
->;
diff --git a/packages/mui-base/src/legacy/MenuItem/index.ts b/packages/mui-base/src/legacy/MenuItem/index.ts
deleted file mode 100644
index c24ff54e41..0000000000
--- a/packages/mui-base/src/legacy/MenuItem/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-'use client';
-export * from './MenuItem';
-export * from './MenuItem.types';
-export * from './menuItemClasses';
diff --git a/packages/mui-base/src/legacy/MenuItem/menuItemClasses.ts b/packages/mui-base/src/legacy/MenuItem/menuItemClasses.ts
deleted file mode 100644
index 00e802225d..0000000000
--- a/packages/mui-base/src/legacy/MenuItem/menuItemClasses.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { generateUtilityClass } from '../generateUtilityClass';
-import { generateUtilityClasses } from '../generateUtilityClasses';
-
-const COMPONENT_NAME = 'MenuItem';
-
-export interface MenuItemClasses {
- /** Class name applied to the root element. */
- root: string;
- /** State class applied to the root `button` element if `disabled={true}`. */
- disabled: string;
- /** State class applied to the root `button` element if `focusVisible={true}`. */
- focusVisible: string;
-}
-
-export type MenuItemClassKey = keyof MenuItemClasses;
-
-export function getMenuItemUtilityClass(slot: string): string {
- return generateUtilityClass(COMPONENT_NAME, slot);
-}
-
-export const menuItemClasses: MenuItemClasses = generateUtilityClasses(COMPONENT_NAME, [
- 'root',
- 'disabled',
- 'focusVisible',
-]);
diff --git a/packages/mui-base/src/legacy/useAutocomplete/useAutocomplete.test.js b/packages/mui-base/src/legacy/useAutocomplete/useAutocomplete.test.js
index 0a5f91eda1..491671f3c3 100644
--- a/packages/mui-base/src/legacy/useAutocomplete/useAutocomplete.test.js
+++ b/packages/mui-base/src/legacy/useAutocomplete/useAutocomplete.test.js
@@ -263,9 +263,9 @@ describe('useAutocomplete', () => {
{groupedOptions.length > 0 ? (
{groupedOptions.map((option, index) => {
- const { key, ...other } = getOptionProps({ option, index });
+ const { key, ...optionProps } = getOptionProps({ option, index });
return (
-
+
{option}
);
@@ -277,52 +277,28 @@ describe('useAutocomplete', () => {
}
const node16ErrorMessage =
- "TypeError: Cannot read properties of null (reading 'removeAttribute')";
- const olderNodeErrorMessage = "TypeError: Cannot read property 'removeAttribute' of null";
+ "Error: Uncaught [TypeError: Cannot read properties of null (reading 'removeAttribute')]";
+ const olderNodeErrorMessage =
+ "Error: Uncaught [TypeError: Cannot read property 'removeAttribute' of null]";
const nodeVersion = Number(process.versions.node.split('.')[0]);
const errorMessage = nodeVersion >= 16 ? node16ErrorMessage : olderNodeErrorMessage;
- const wrappedErrorMessage = `Error: Uncaught [${errorMessage}]`;
-
- const react17ErrorMessages = [
- wrappedErrorMessage,
- 'MUI: Unable to find the input element.',
- wrappedErrorMessage,
- 'The above error occurred in the component',
- 'The above error occurred in the component',
- ];
-
- const react182ErrorMessages = [
- wrappedErrorMessage,
+ const devErrorMessages = [
+ errorMessage,
'MUI: Unable to find the input element.',
- wrappedErrorMessage,
+ errorMessage,
// strict effects runs effects twice
- 'MUI: Unable to find the input element.',
- wrappedErrorMessage,
+ React.version.startsWith('18') && 'MUI: Unable to find the input element.',
+ React.version.startsWith('18') && errorMessage,
'The above error occurred in the component',
+ React.version.startsWith('16') && 'The above error occurred in the component',
'The above error occurred in the component',
// strict effects runs effects twice
- 'The above error occurred in the component',
- ];
-
- const react183ErrorMessages = [
- 'MUI: Unable to find the input element.',
- 'MUI: Unable to find the input element.',
- errorMessage,
- errorMessage,
- errorMessage,
+ React.version.startsWith('18') && 'The above error occurred in the component',
+ React.version.startsWith('16') && 'The above error occurred in the component',
];
- let devErrorMessages;
- if (React.version.startsWith('18.3')) {
- devErrorMessages = react183ErrorMessages;
- } else if (React.version.startsWith('18')) {
- devErrorMessages = react182ErrorMessages;
- } else {
- devErrorMessages = react17ErrorMessages;
- }
-
expect(() => {
render(
diff --git a/packages/mui-base/src/legacy/useDropdown/DropdownContext.ts b/packages/mui-base/src/legacy/useDropdown/DropdownContext.ts
deleted file mode 100644
index 059a7c4c0d..0000000000
--- a/packages/mui-base/src/legacy/useDropdown/DropdownContext.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import * as React from 'react';
-import { DropdownAction, DropdownState } from './useDropdown.types';
-
-export interface DropdownContextValue {
- dispatch: React.Dispatch;
- popupId: string;
- registerPopup: (popupId: string) => void;
- registerTrigger: (element: HTMLElement | null) => void;
- state: DropdownState;
- triggerElement: HTMLElement | null;
-}
-
-const DropdownContext = React.createContext(null);
-
-export { DropdownContext };
diff --git a/packages/mui-base/src/legacy/useDropdown/dropdownReducer.ts b/packages/mui-base/src/legacy/useDropdown/dropdownReducer.ts
deleted file mode 100644
index 66ee1a28aa..0000000000
--- a/packages/mui-base/src/legacy/useDropdown/dropdownReducer.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { DropdownAction, DropdownActionTypes, DropdownState } from './useDropdown.types';
-
-export function dropdownReducer(state: DropdownState, action: DropdownAction): DropdownState {
- switch (action.type) {
- case DropdownActionTypes.blur:
- return { open: false, changeReason: action.event };
- case DropdownActionTypes.escapeKeyDown:
- return { open: false, changeReason: action.event };
- case DropdownActionTypes.toggle:
- return { open: !state.open, changeReason: action.event };
- case DropdownActionTypes.open:
- return { open: true, changeReason: action.event };
- case DropdownActionTypes.close:
- return { open: false, changeReason: action.event };
- default:
- throw new Error(`Unhandled action`);
- }
-}
diff --git a/packages/mui-base/src/legacy/useDropdown/index.ts b/packages/mui-base/src/legacy/useDropdown/index.ts
deleted file mode 100644
index 79fc0ef7f3..0000000000
--- a/packages/mui-base/src/legacy/useDropdown/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-'use client';
-export * from './useDropdown';
-export * from './useDropdown.types';
-export * from './DropdownContext';
diff --git a/packages/mui-base/src/legacy/useDropdown/useDropdown.ts b/packages/mui-base/src/legacy/useDropdown/useDropdown.ts
deleted file mode 100644
index 06aa213433..0000000000
--- a/packages/mui-base/src/legacy/useDropdown/useDropdown.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-'use client';
-import * as React from 'react';
-import type { DropdownContextValue } from './DropdownContext';
-import { useControllableReducer } from '../../utils/useControllableReducer';
-import { StateChangeCallback } from '../../utils/useControllableReducer.types';
-import { DropdownActionTypes, DropdownState, UseDropdownParameters } from './useDropdown.types';
-import { dropdownReducer } from './dropdownReducer';
-
-/**
- *
- * Demos:
- *
- * - [Menu](https://mui.com/base-ui/react-menu/#hooks)
- *
- * API:
- *
- * - [useDropdown API](https://mui.com/base-ui/react-menu/hooks-api/#use-dropdown)
- */
-export function useDropdown(parameters: UseDropdownParameters = {}) {
- const { defaultOpen, onOpenChange, open: openProp, componentName = 'useDropdown' } = parameters;
- const [popupId, setPopupId] = React.useState('');
- const [triggerElement, setTriggerElement] = React.useState(null);
- const lastActionType = React.useRef(null);
-
- const handleStateChange: StateChangeCallback = React.useCallback(
- (event, field, value, reason) => {
- if (field === 'open') {
- onOpenChange?.(
- event as React.MouseEvent | React.KeyboardEvent | React.FocusEvent,
- value as boolean,
- );
- }
-
- lastActionType.current = reason;
- },
- [onOpenChange],
- );
-
- const controlledProps = React.useMemo(
- () => (openProp !== undefined ? { open: openProp } : {}),
- [openProp],
- );
-
- const [state, dispatch] = useControllableReducer({
- controlledProps,
- initialState: defaultOpen
- ? { open: true, changeReason: null }
- : { open: false, changeReason: null },
- onStateChange: handleStateChange,
- reducer: dropdownReducer,
- componentName,
- });
-
- React.useEffect(() => {
- if (
- !state.open &&
- lastActionType.current !== null &&
- lastActionType.current !== DropdownActionTypes.blur
- ) {
- triggerElement?.focus();
- }
- }, [state.open, triggerElement]);
-
- const contextValue: DropdownContextValue = {
- state,
- dispatch,
- popupId,
- registerPopup: setPopupId,
- registerTrigger: setTriggerElement,
- triggerElement,
- };
-
- return {
- contextValue,
- open: state.open,
- };
-}
diff --git a/packages/mui-base/src/legacy/useDropdown/useDropdown.types.ts b/packages/mui-base/src/legacy/useDropdown/useDropdown.types.ts
deleted file mode 100644
index 3118ca9791..0000000000
--- a/packages/mui-base/src/legacy/useDropdown/useDropdown.types.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import type { DropdownContextValue } from './DropdownContext';
-
-export interface UseDropdownParameters {
- /**
- * If `true`, the dropdown is initially open.
- */
- defaultOpen?: boolean;
- /**
- * Callback fired when the component requests to be opened or closed.
- */
- onOpenChange?: (
- event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent | null,
- open: boolean,
- ) => void;
- /**
- * Allows to control whether the dropdown is open.
- * This is a controlled counterpart of `defaultOpen`.
- */
- open?: boolean;
- /**
- * The name of the component using useDropdown.
- * For debugging purposes.
- * @default 'useDropdown'
- */
- componentName?: string;
-}
-
-export interface UseDropdownReturnValue {
- /**
- * The value to be passed into the DropdownContext provider.
- */
- contextValue: DropdownContextValue;
- /**
- * If `true`, the dropdown is open.
- */
- open: boolean;
-}
-
-export const DropdownActionTypes = {
- blur: 'dropdown:blur',
- escapeKeyDown: 'dropdown:escapeKeyDown',
- toggle: 'dropdown:toggle',
- open: 'dropdown:open',
- close: 'dropdown:close',
-} as const;
-
-interface DropdownBlurAction {
- type: typeof DropdownActionTypes.blur;
- event: React.FocusEvent;
-}
-
-interface DropdownEscapeKeyDownAction {
- type: typeof DropdownActionTypes.escapeKeyDown;
- event: React.KeyboardEvent;
-}
-
-interface DropdownToggleAction {
- type: typeof DropdownActionTypes.toggle;
- event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent | null;
-}
-
-interface DropdownOpenAction {
- type: typeof DropdownActionTypes.open;
- event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent | null;
-}
-
-interface DropdownCloseAction {
- type: typeof DropdownActionTypes.close;
- event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent | null;
-}
-
-export type DropdownAction =
- | DropdownBlurAction
- | DropdownEscapeKeyDownAction
- | DropdownToggleAction
- | DropdownOpenAction
- | DropdownCloseAction;
-
-export type DropdownState = {
- open: boolean;
- changeReason: React.MouseEvent | React.KeyboardEvent | React.FocusEvent | null;
-};
diff --git a/packages/mui-base/src/legacy/useMenu/MenuProvider.tsx b/packages/mui-base/src/legacy/useMenu/MenuProvider.tsx
deleted file mode 100644
index e3d8d93083..0000000000
--- a/packages/mui-base/src/legacy/useMenu/MenuProvider.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-'use client';
-import * as React from 'react';
-import { ListContext, ListContextValue } from '../../useList/ListContext';
-import { MenuItemMetadata } from '../useMenuItem';
-import { CompoundComponentContext, CompoundComponentContextValue } from '../../useCompound';
-
-export type MenuProviderValue = CompoundComponentContextValue &
- ListContextValue;
-
-export interface MenuProviderProps {
- value: MenuProviderValue;
- children: React.ReactNode;
-}
-
-/**
- * Sets up the contexts for the underlying MenuItem components.
- *
- * @ignore - do not document.
- */
-export function MenuProvider(props: MenuProviderProps) {
- const { value, children } = props;
- const { dispatch, getItemIndex, getItemState, registerItem, totalSubitemCount } = value;
-
- const listContextValue: ListContextValue = React.useMemo(
- () => ({
- dispatch,
- getItemState,
- getItemIndex,
- }),
- [dispatch, getItemIndex, getItemState],
- );
-
- const compoundComponentContextValue: CompoundComponentContextValue =
- React.useMemo(
- () => ({
- getItemIndex,
- registerItem,
- totalSubitemCount,
- }),
- [registerItem, getItemIndex, totalSubitemCount],
- );
-
- return (
-
- {children}
-
- );
-}
diff --git a/packages/mui-base/src/legacy/useMenu/index.ts b/packages/mui-base/src/legacy/useMenu/index.ts
deleted file mode 100644
index 0d08ba73cf..0000000000
--- a/packages/mui-base/src/legacy/useMenu/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-'use client';
-export { useMenu } from './useMenu';
-export * from './useMenu.types';
-export * from './MenuProvider';
diff --git a/packages/mui-base/src/legacy/useMenu/menuReducer.ts b/packages/mui-base/src/legacy/useMenu/menuReducer.ts
deleted file mode 100644
index 77c44afcd7..0000000000
--- a/packages/mui-base/src/legacy/useMenu/menuReducer.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { ListAction, ListActionContext, ListActionTypes, listReducer } from '../../useList';
-import { ActionWithContext } from '../../utils/useControllableReducer.types';
-import { MenuInternalState } from './useMenu.types';
-
-export type MenuActionContext = ListActionContext & {
- listboxRef: React.RefObject;
-};
-
-export function menuReducer(
- state: MenuInternalState,
- action: ActionWithContext, MenuActionContext>,
-) {
- if (action.type === ListActionTypes.itemHover) {
- return {
- ...state,
- highlightedValue: action.item,
- };
- }
-
- const newState = listReducer(state, action);
-
- // make sure an item is always highlighted
- if (newState.highlightedValue === null && action.context.items.length > 0) {
- return {
- ...newState,
- highlightedValue: action.context.items[0],
- };
- }
-
- if (action.type === ListActionTypes.keyDown) {
- if (action.event.key === 'Escape') {
- return {
- ...newState,
- open: false,
- };
- }
- }
-
- if (action.type === ListActionTypes.blur) {
- if (!action.context.listboxRef.current?.contains(action.event.relatedTarget)) {
- // To prevent the menu from closing when the focus leaves the menu to the button.
- // For more details, see https://github.com/mui/material-ui/pull/36917#issuecomment-1566992698
- const listboxId = action.context.listboxRef.current?.getAttribute('id');
- const controlledBy = action.event.relatedTarget?.getAttribute('aria-controls');
- if (listboxId && controlledBy && listboxId === controlledBy) {
- return newState;
- }
-
- return {
- ...newState,
- open: false,
- highlightedValue: action.context.items[0],
- };
- }
- }
-
- return newState;
-}
diff --git a/packages/mui-base/src/legacy/useMenu/useMenu.test.js b/packages/mui-base/src/legacy/useMenu/useMenu.test.js
deleted file mode 100644
index 6a07680a6e..0000000000
--- a/packages/mui-base/src/legacy/useMenu/useMenu.test.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import * as React from 'react';
-import { expect } from 'chai';
-import { spy } from 'sinon';
-import { createRenderer, screen, fireEvent } from '@mui/internal-test-utils';
-import { MenuItem } from '../MenuItem';
-import { useMenu } from './useMenu';
-
-describe('useMenu', () => {
- const { render } = createRenderer();
-
- describe('getListboxProps', () => {
- it('returns props for root slot', () => {
- function TestMenu() {
- const listboxRef = React.createRef();
- const { getListboxProps } = useMenu({ listboxRef });
- return
;
- }
-
- function Test() {
- return (
-
-
-
- );
- }
-
- const { getByRole } = render( );
-
- const menu = getByRole('menu');
- expect(menu).not.to.equal(null);
- });
-
- it('forwards external props including event handlers', () => {
- const handleClick = spy();
-
- function TestMenu() {
- const listboxRef = React.createRef();
- const { getListboxProps } = useMenu({ listboxRef });
- return (
-
- );
- }
-
- function Test() {
- return (
-
-
-
- );
- }
-
- render( );
-
- const listbox = screen.getByTestId('test-listbox');
- expect(listbox).not.to.equal(null);
-
- fireEvent.click(listbox);
- expect(handleClick.callCount).to.equal(1);
- });
- });
-});
diff --git a/packages/mui-base/src/legacy/useMenu/useMenu.ts b/packages/mui-base/src/legacy/useMenu/useMenu.ts
deleted file mode 100644
index 13c20ce7c5..0000000000
--- a/packages/mui-base/src/legacy/useMenu/useMenu.ts
+++ /dev/null
@@ -1,221 +0,0 @@
-'use client';
-import * as React from 'react';
-import {
- unstable_useForkRef as useForkRef,
- unstable_useId as useId,
- unstable_useEnhancedEffect as useEnhancedEffect,
-} from '@mui/utils';
-import { UseMenuListboxSlotProps, UseMenuParameters, UseMenuReturnValue } from './useMenu.types';
-import { menuReducer } from './menuReducer';
-import { DropdownContext, DropdownContextValue } from '../useDropdown/DropdownContext';
-import { ListActionTypes, useList } from '../../useList';
-import { MenuItemMetadata } from '../useMenuItem';
-import { DropdownActionTypes } from '../useDropdown';
-import { EventHandlers } from '../../utils/types';
-import { useCompoundParent } from '../../useCompound';
-import { MuiCancellableEvent } from '../../utils/MuiCancellableEvent';
-import { combineHooksSlotProps } from '../utils/combineHooksSlotProps';
-import { extractEventHandlers } from '../../utils/extractEventHandlers';
-
-const FALLBACK_MENU_CONTEXT: DropdownContextValue = {
- dispatch: () => {},
- popupId: '',
- registerPopup: () => {},
- registerTrigger: () => {},
- state: { open: true, changeReason: null },
- triggerElement: null,
-};
-
-/**
- *
- * Demos:
- *
- * - [Menu](https://mui.com/base-ui/react-menu/#hooks)
- *
- * API:
- *
- * - [useMenu API](https://mui.com/base-ui/react-menu/hooks-api/#use-menu)
- */
-export function useMenu(parameters: UseMenuParameters = {}): UseMenuReturnValue {
- const {
- listboxRef: listboxRefProp,
- onItemsChange,
- id: idParam,
- disabledItemsFocusable = true,
- disableListWrap = false,
- autoFocus = true,
- componentName = 'useMenu',
- } = parameters;
-
- const rootRef = React.useRef(null);
- const handleRef = useForkRef(rootRef, listboxRefProp);
-
- const listboxId = useId(idParam) ?? '';
-
- const {
- state: { open, changeReason },
- dispatch: menuDispatch,
- triggerElement,
- registerPopup,
- } = React.useContext(DropdownContext) ?? FALLBACK_MENU_CONTEXT;
-
- // store the initial open state to prevent focus stealing
- // (the first menu items gets focued only when the menu is opened by the user)
- const isInitiallyOpen = React.useRef(open);
-
- const { subitems, contextValue: compoundComponentContextValue } = useCompoundParent<
- string,
- MenuItemMetadata
- >();
-
- const subitemKeys = React.useMemo(() => Array.from(subitems.keys()), [subitems]);
-
- const getItemDomElement = React.useCallback(
- (itemId: string) => {
- if (itemId == null) {
- return null;
- }
-
- return subitems.get(itemId)?.ref.current ?? null;
- },
- [subitems],
- );
-
- const isItemDisabled = React.useCallback(
- (id: string) => subitems?.get(id)?.disabled || false,
- [subitems],
- );
-
- const getItemAsString = React.useCallback(
- (id: string) => subitems.get(id)?.label || subitems.get(id)?.ref.current?.innerText,
- [subitems],
- );
-
- const reducerActionContext = React.useMemo(() => ({ listboxRef: rootRef }), [rootRef]);
-
- const {
- dispatch: listDispatch,
- getRootProps: getListRootProps,
- contextValue: listContextValue,
- state: { highlightedValue },
- rootRef: mergedListRef,
- } = useList({
- disabledItemsFocusable,
- disableListWrap,
- focusManagement: 'DOM',
- getItemDomElement,
- getInitialState: () => ({
- selectedValues: [],
- highlightedValue: null,
- }),
- isItemDisabled,
- items: subitemKeys,
- getItemAsString,
- rootRef: handleRef,
- onItemsChange,
- reducerActionContext,
- selectionMode: 'none',
- stateReducer: menuReducer,
- componentName,
- });
-
- useEnhancedEffect(() => {
- registerPopup(listboxId);
- }, [listboxId, registerPopup]);
-
- useEnhancedEffect(() => {
- if (
- open &&
- changeReason?.type === 'keydown' &&
- (changeReason as React.KeyboardEvent).key === 'ArrowUp'
- ) {
- listDispatch({
- type: ListActionTypes.highlightLast,
- event: changeReason as React.KeyboardEvent,
- });
- }
- }, [open, changeReason, listDispatch]);
-
- React.useEffect(() => {
- if (open && autoFocus && highlightedValue && !isInitiallyOpen.current) {
- subitems.get(highlightedValue)?.ref?.current?.focus();
- }
- }, [open, autoFocus, highlightedValue, subitems, subitemKeys]);
-
- React.useEffect(() => {
- // set focus to the highlighted item (but prevent stealing focus from other elements on the page)
- if (rootRef.current?.contains(document.activeElement) && highlightedValue !== null) {
- subitems?.get(highlightedValue)?.ref.current?.focus();
- }
- }, [highlightedValue, subitems]);
-
- const createHandleBlur =
- (otherHandlers: EventHandlers) => (event: React.FocusEvent & MuiCancellableEvent) => {
- otherHandlers.onBlur?.(event);
- if (event.defaultMuiPrevented) {
- return;
- }
-
- if (
- rootRef.current?.contains(event.relatedTarget as HTMLElement) ||
- event.relatedTarget === triggerElement
- ) {
- return;
- }
-
- menuDispatch({
- type: DropdownActionTypes.blur,
- event,
- });
- };
-
- const createHandleKeyDown =
- (otherHandlers: EventHandlers) => (event: React.KeyboardEvent & MuiCancellableEvent) => {
- otherHandlers.onKeyDown?.(event);
- if (event.defaultMuiPrevented) {
- return;
- }
-
- if (event.key === 'Escape') {
- menuDispatch({
- type: DropdownActionTypes.escapeKeyDown,
- event,
- });
- }
- };
-
- const getOwnListboxHandlers = (otherHandlers: EventHandlers = {}) => ({
- onBlur: createHandleBlur(otherHandlers),
- onKeyDown: createHandleKeyDown(otherHandlers),
- });
-
- const getListboxProps = >(
- externalProps: ExternalProps = {} as ExternalProps,
- ): UseMenuListboxSlotProps => {
- const getCombinedRootProps = combineHooksSlotProps(getOwnListboxHandlers, getListRootProps);
- const externalEventHandlers = extractEventHandlers(externalProps);
- return {
- ...externalProps,
- ...externalEventHandlers,
- ...getCombinedRootProps(externalEventHandlers),
- id: listboxId,
- role: 'menu',
- };
- };
-
- React.useDebugValue({ subitems, highlightedValue });
-
- return {
- contextValue: {
- ...compoundComponentContextValue,
- ...listContextValue,
- },
- dispatch: listDispatch,
- getListboxProps,
- highlightedValue,
- listboxRef: mergedListRef,
- menuItems: subitems,
- open,
- triggerElement,
- };
-}
diff --git a/packages/mui-base/src/legacy/useMenu/useMenu.types.ts b/packages/mui-base/src/legacy/useMenu/useMenu.types.ts
deleted file mode 100644
index 7a0429db63..0000000000
--- a/packages/mui-base/src/legacy/useMenu/useMenu.types.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import * as React from 'react';
-import { ListAction, ListState, UseListRootSlotProps } from '../../useList';
-import { MenuItemMetadata } from '../useMenuItem';
-import { MenuProviderValue } from './MenuProvider';
-
-export interface UseMenuParameters {
- /**
- * If `true` (Default) will focus the highligted item. If you set this prop to `false`
- * the focus will not be moved inside the Menu component. This has severe accessibility implications
- * and should only be considered if you manage focus otherwise.
- * @default true
- */
- autoFocus?: boolean;
- /**
- * The id of the menu. If not provided, it will be generated.
- */
- id?: string;
- /**
- * If `true`, it will be possible to highlight disabled items.
- * @default true
- */
- disabledItemsFocusable?: boolean;
- /**
- * If `true`, the highlight will not wrap around the list if arrow keys are used.
- * @default false
- */
- disableListWrap?: boolean;
- /**
- * Callback fired when the menu items change.
- */
- onItemsChange?: (items: string[]) => void;
- /**
- * The ref to the menu's listbox node.
- */
- listboxRef?: React.Ref;
- /**
- * The name of the component using useMenu.
- * For debugging purposes.
- * @default 'useMenu'
- */
- componentName?: string;
-}
-
-export interface UseMenuReturnValue {
- /**
- * The value to be passed into the MenuProvider.
- */
- contextValue: MenuProviderValue;
- /**
- * Action dispatcher for the menu component.
- * Allows to programmatically control the menu.
- */
- dispatch: (action: ListAction) => void;
- /**
- * Resolver for the listbox slot's props.
- * @param externalProps additional props for the listbox component
- * @returns props that should be spread on the listbox component
- */
- getListboxProps: = {}>(
- externalProps?: ExternalProps,
- ) => UseMenuListboxSlotProps;
- /**
- * The highlighted option in the menu listbox.
- */
- highlightedValue: string | null;
- /**
- * The ref to the menu's listbox node.
- */
- listboxRef: React.RefCallback | null;
- /**
- * Items in the menu listbox.
- */
- menuItems: Map;
- /**
- * If `true`, the menu is open.
- */
- open: boolean;
- /**
- * An element that triggers the visibility of the menu.
- */
- triggerElement: HTMLElement | null;
-}
-
-interface UseMenuListboxSlotEventHandlers {
- onBlur: React.FocusEventHandler;
- onKeyDown: React.KeyboardEventHandler;
-}
-
-export type UseMenuListboxSlotProps = UseListRootSlotProps<
- Omit & UseMenuListboxSlotEventHandlers
-> & {
- ref: React.RefCallback | null;
- role: React.AriaRole;
-};
-
-export interface MenuInternalState extends ListState {}
diff --git a/packages/mui-base/src/legacy/useMenuButton/index.ts b/packages/mui-base/src/legacy/useMenuButton/index.ts
deleted file mode 100644
index 0f9d874d50..0000000000
--- a/packages/mui-base/src/legacy/useMenuButton/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-'use client';
-export { useMenuButton } from './useMenuButton';
-export * from './useMenuButton.types';
diff --git a/packages/mui-base/src/legacy/useMenuButton/useMenuButton.test.tsx b/packages/mui-base/src/legacy/useMenuButton/useMenuButton.test.tsx
deleted file mode 100644
index fe175a708a..0000000000
--- a/packages/mui-base/src/legacy/useMenuButton/useMenuButton.test.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import * as React from 'react';
-import { expect } from 'chai';
-import { spy } from 'sinon';
-import { createRenderer, screen, fireEvent } from '@mui/internal-test-utils';
-import { DropdownContext, DropdownContextValue } from '@base_ui/react/legacy/useDropdown';
-import { useMenuButton } from './useMenuButton';
-
-const testContext: DropdownContextValue = {
- dispatch: () => {},
- popupId: 'menu-popup',
- registerPopup: () => {},
- registerTrigger: () => {},
- state: { open: true, changeReason: null },
- triggerElement: null,
-};
-
-describe('useMenuButton', () => {
- const { render } = createRenderer();
-
- describe('getRootProps', () => {
- it('returns props for root slot', () => {
- function TestMenuButton() {
- const { getRootProps } = useMenuButton();
- return
;
- }
-
- function Test() {
- return (
-
-
-
- );
- }
-
- const { getByRole } = render( );
-
- const button = getByRole('button');
- expect(button).not.to.equal(null);
- });
-
- it('forwards external props including event handlers', () => {
- const handleClick = spy();
-
- function TestMenuButton() {
- const { getRootProps } = useMenuButton();
- return (
-
- );
- }
-
- function Test() {
- return (
-
-
-
- );
- }
-
- render( );
-
- const button = screen.getByTestId('test-menu-button');
- expect(button).not.to.equal(null);
-
- fireEvent.click(button);
- expect(handleClick.callCount).to.equal(1);
- });
- });
-});
diff --git a/packages/mui-base/src/legacy/useMenuButton/useMenuButton.ts b/packages/mui-base/src/legacy/useMenuButton/useMenuButton.ts
deleted file mode 100644
index 415894c478..0000000000
--- a/packages/mui-base/src/legacy/useMenuButton/useMenuButton.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-'use client';
-import * as React from 'react';
-import { unstable_useForkRef as useForkRef } from '@mui/utils';
-import { UseMenuButtonParameters, UseMenuButtonReturnValue } from './useMenuButton.types';
-import { DropdownContext } from '../useDropdown/DropdownContext';
-import { DropdownActionTypes } from '../useDropdown/useDropdown.types';
-import { useButton } from '../../useButton/useButton';
-import { EventHandlers } from '../../utils/types';
-import { MuiCancellableEvent } from '../../utils/MuiCancellableEvent';
-import { combineHooksSlotProps } from '../utils/combineHooksSlotProps';
-import { extractEventHandlers } from '../../utils/extractEventHandlers';
-
-/**
- *
- * Demos:
- *
- * - [Menu](https://mui.com/base-ui/react-menu/#hooks)
- *
- * API:
- *
- * - [useMenuButton API](https://mui.com/base-ui/react-menu/hooks-api/#use-menu-button)
- */
-export function useMenuButton(parameters: UseMenuButtonParameters = {}): UseMenuButtonReturnValue {
- const { disabled = false, focusableWhenDisabled, rootRef: externalRef } = parameters;
-
- const menuContext = React.useContext(DropdownContext);
- if (menuContext === null) {
- throw new Error('useMenuButton: no menu context available.');
- }
-
- const { state, dispatch, registerTrigger, popupId } = menuContext;
-
- const {
- getRootProps: getButtonRootProps,
- rootRef: buttonRootRef,
- active,
- } = useButton({
- disabled,
- focusableWhenDisabled,
- rootRef: externalRef,
- });
-
- const handleRef = useForkRef(buttonRootRef, registerTrigger);
-
- const createHandleClick =
- (otherHandlers: EventHandlers) => (event: React.MouseEvent & MuiCancellableEvent) => {
- otherHandlers.onClick?.(event);
- if (event.defaultMuiPrevented) {
- return;
- }
-
- dispatch({
- type: DropdownActionTypes.toggle,
- event,
- });
- };
-
- const createHandleKeyDown =
- (otherHandlers: EventHandlers) => (event: React.KeyboardEvent & MuiCancellableEvent) => {
- otherHandlers.onKeyDown?.(event);
- if (event.defaultMuiPrevented) {
- return;
- }
-
- if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
- event.preventDefault();
- dispatch({
- type: DropdownActionTypes.open,
- event,
- });
- }
- };
-
- const getOwnRootProps = (otherHandlers: EventHandlers = {}) => ({
- onClick: createHandleClick(otherHandlers),
- onKeyDown: createHandleKeyDown(otherHandlers),
- });
-
- const getRootProps = = {}>(
- externalProps: ExternalProps = {} as ExternalProps,
- ) => {
- const externalEventHandlers = extractEventHandlers(externalProps);
- const getCombinedProps = combineHooksSlotProps(getOwnRootProps, getButtonRootProps);
-
- return {
- 'aria-haspopup': 'menu' as const,
- 'aria-expanded': state.open,
- 'aria-controls': popupId,
- ...externalProps,
- ...externalEventHandlers,
- ...getCombinedProps(externalEventHandlers),
- tabIndex: 0, // this is needed to make the button focused after click in Safari
- ref: handleRef,
- };
- };
-
- return {
- active,
- getRootProps,
- open: state.open,
- rootRef: handleRef,
- };
-}
diff --git a/packages/mui-base/src/legacy/useMenuButton/useMenuButton.types.ts b/packages/mui-base/src/legacy/useMenuButton/useMenuButton.types.ts
deleted file mode 100644
index 3ae91422c4..0000000000
--- a/packages/mui-base/src/legacy/useMenuButton/useMenuButton.types.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-export interface UseMenuButtonParameters {
- /**
- * If `true`, the component is disabled.
- * @default false
- */
- disabled?: boolean;
- /**
- * If `true`, allows a disabled button to receive focus.
- * @default false
- */
- focusableWhenDisabled?: boolean;
- /**
- * The ref to the root element.
- */
- rootRef?: React.Ref;
-}
-
-type UseMenuButtonRootSlotProps = ExternalProps & UseMenuButtonRootSlotOwnProps;
-
-interface UseMenuButtonRootSlotOwnProps {
- 'aria-haspopup': 'menu';
- 'aria-expanded': boolean;
- 'aria-controls': string;
- /**
- * Callback fired when the button is clicked.
- */
- onClick: React.MouseEventHandler;
- /**
- * The ref to the button element.
- */
- ref: React.RefCallback | null;
-}
-
-export interface UseMenuButtonReturnValue {
- /**
- * If `true`, the component is active (pressed).
- */
- active: boolean;
- /**
- * Resolver for the root slot's props.
- * @param externalProps props for the root slot
- * @returns props that should be spread on the root slot
- */
- getRootProps: = {}>(
- externalProps?: ExternalProps,
- ) => UseMenuButtonRootSlotProps;
- /*
- * If `true`, the menu is open.
- */
- open: boolean;
- /**
- * The ref to the root element.
- */
- rootRef: React.RefCallback | null;
-}
diff --git a/packages/mui-base/src/legacy/useMenuItem/index.ts b/packages/mui-base/src/legacy/useMenuItem/index.ts
deleted file mode 100644
index f480bce7a2..0000000000
--- a/packages/mui-base/src/legacy/useMenuItem/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-'use client';
-export { useMenuItem } from './useMenuItem';
-export * from './useMenuItem.types';
-export * from './useMenuItemContextStabilizer';
diff --git a/packages/mui-base/src/legacy/useMenuItem/useMenuItem.test.tsx b/packages/mui-base/src/legacy/useMenuItem/useMenuItem.test.tsx
deleted file mode 100644
index 0da80599a0..0000000000
--- a/packages/mui-base/src/legacy/useMenuItem/useMenuItem.test.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import * as React from 'react';
-import { expect } from 'chai';
-import { spy } from 'sinon';
-import { createRenderer, screen, fireEvent } from '@mui/internal-test-utils';
-import { Menu } from '../Menu';
-import { useMenuItem } from './useMenuItem';
-
-describe('useMenuItem', () => {
- const { render } = createRenderer();
-
- describe('getRootProps', () => {
- it('returns props for root slot', () => {
- function TestMenuItem() {
- const rootRef = React.createRef();
- const { getRootProps } = useMenuItem({ rootRef });
- return
;
- }
-
- function Test() {
- return (
-
-
-
- );
- }
-
- const { getByRole } = render( );
-
- const menuItem = getByRole('menuitem');
- expect(menuItem).not.to.equal(null);
- });
-
- it('forwards external props including event handlers', () => {
- const handleClick = spy();
-
- function TestMenuItem() {
- const rootRef = React.createRef();
- const { getRootProps } = useMenuItem({ rootRef, id: 'test-menu-item-1' });
- return
;
- }
-
- function Test() {
- return (
-
-
-
- );
- }
-
- render( );
-
- const menuItem = screen.getByTestId('test-menu-item');
- expect(menuItem).not.to.equal(null);
-
- fireEvent.click(menuItem);
- expect(handleClick.callCount).to.equal(1);
- });
- });
-});
diff --git a/packages/mui-base/src/legacy/useMenuItem/useMenuItem.ts b/packages/mui-base/src/legacy/useMenuItem/useMenuItem.ts
deleted file mode 100644
index 64abc655ba..0000000000
--- a/packages/mui-base/src/legacy/useMenuItem/useMenuItem.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-'use client';
-import * as React from 'react';
-import { unstable_useId as useId, unstable_useForkRef as useForkRef } from '@mui/utils';
-import { useButton } from '../../useButton';
-import type {
- MenuItemMetadata,
- UseMenuItemParameters,
- UseMenuItemReturnValue,
- UseMenuItemRootSlotProps,
-} from './useMenuItem.types';
-import { useListItem } from '../../useList';
-import { DropdownActionTypes } from '../useDropdown';
-import { DropdownContext, DropdownContextValue } from '../useDropdown/DropdownContext';
-import { combineHooksSlotProps } from '../utils/combineHooksSlotProps';
-import { useCompoundItem } from '../../useCompound';
-import { MuiCancellableEvent } from '../../utils/MuiCancellableEvent';
-import { EventHandlers } from '../../utils/types';
-import { extractEventHandlers } from '../../utils/extractEventHandlers';
-
-function idGenerator(existingKeys: Set) {
- return `menu-item-${existingKeys.size}`;
-}
-
-const FALLBACK_MENU_CONTEXT: DropdownContextValue = {
- dispatch: () => {},
- popupId: '',
- registerPopup: () => {},
- registerTrigger: () => {},
- state: { open: true, changeReason: null },
- triggerElement: null,
-};
-
-/**
- *
- * Demos:
- *
- * - [Menu](https://mui.com/base-ui/react-menu/#hooks)
- *
- * API:
- *
- * - [useMenuItem API](https://mui.com/base-ui/react-menu/hooks-api/#use-menu-item)
- */
-export function useMenuItem(params: UseMenuItemParameters): UseMenuItemReturnValue {
- const {
- disabled = false,
- id: idParam,
- rootRef: externalRef,
- label,
- disableFocusOnHover = false,
- } = params;
-
- const id = useId(idParam);
- const itemRef = React.useRef(null);
-
- const itemMetadata: MenuItemMetadata = React.useMemo(
- () => ({ disabled, id: id ?? '', label, ref: itemRef }),
- [disabled, id, label],
- );
-
- const { dispatch } = React.useContext(DropdownContext) ?? FALLBACK_MENU_CONTEXT;
-
- const { getRootProps: getListRootProps, highlighted } = useListItem({
- item: id,
- handlePointerOverEvents: !disableFocusOnHover,
- });
-
- const { index, totalItemCount } = useCompoundItem(id ?? idGenerator, itemMetadata);
-
- const {
- getRootProps: getButtonProps,
- focusVisible,
- rootRef: buttonRefHandler,
- } = useButton({
- disabled,
- focusableWhenDisabled: true,
- });
-
- const handleRef = useForkRef(buttonRefHandler, externalRef, itemRef);
-
- React.useDebugValue({ id, highlighted, disabled, label });
-
- const createHandleClick =
- (otherHandlers: EventHandlers) => (event: React.MouseEvent & MuiCancellableEvent) => {
- otherHandlers.onClick?.(event);
- if (event.defaultMuiPrevented) {
- return;
- }
-
- dispatch({
- type: DropdownActionTypes.close,
- event,
- });
- };
-
- const getOwnHandlers = (
- otherHandlers: ExternalProps = {} as ExternalProps,
- ) => ({
- ...otherHandlers,
- onClick: createHandleClick(otherHandlers),
- });
-
- function getRootProps = {}>(
- externalProps: ExternalProps = {} as ExternalProps,
- ): UseMenuItemRootSlotProps {
- const externalEventHandlers = extractEventHandlers(externalProps);
- const getCombinedRootProps = combineHooksSlotProps(
- getOwnHandlers,
- combineHooksSlotProps(getButtonProps, getListRootProps),
- );
- return {
- ...externalProps,
- ...externalEventHandlers,
- ...getCombinedRootProps(externalEventHandlers),
- id,
- ref: handleRef,
- role: 'menuitem',
- };
- }
-
- // If `id` is undefined (during SSR in React < 18), we fall back to rendering a simplified menu item
- // which does not have access to infortmation about its position or highlighted state.
- if (id === undefined) {
- return {
- getRootProps,
- disabled: false,
- focusVisible,
- highlighted: false,
- index: -1,
- totalItemCount: 0,
- rootRef: handleRef,
- };
- }
-
- return {
- getRootProps,
- disabled,
- focusVisible,
- highlighted,
- index,
- totalItemCount,
- rootRef: handleRef,
- };
-}
diff --git a/packages/mui-base/src/legacy/useMenuItem/useMenuItem.types.ts b/packages/mui-base/src/legacy/useMenuItem/useMenuItem.types.ts
deleted file mode 100644
index fc10bf4045..0000000000
--- a/packages/mui-base/src/legacy/useMenuItem/useMenuItem.types.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { UseButtonRootSlotProps } from '../../useButton';
-import { MuiCancellableEventHandler } from '../../utils/MuiCancellableEvent';
-
-interface UseMenuItemRootSlotOwnProps {
- id: string | undefined;
- role: 'menuitem';
- ref: React.RefCallback | null;
-}
-
-export interface MenuItemMetadata {
- id: string;
- disabled: boolean;
- label?: string;
- ref: React.RefObject;
-}
-
-export type UseMenuItemRootSlotProps = ExternalProps &
- UseMenuItemRootSlotOwnProps &
- UseButtonRootSlotProps & {
- onClick: MuiCancellableEventHandler;
- };
-
-export interface UseMenuItemParameters {
- disabled?: boolean;
- id?: string;
- label?: string;
- onClick?: React.MouseEventHandler;
- rootRef: React.Ref;
- /**
- * If `true`, the menu item won't receive focus when the mouse moves over it.
- *
- * @default false
- */
- disableFocusOnHover?: boolean;
-}
-
-export interface UseMenuItemReturnValue {
- /**
- * Resolver for the root slot's props.
- * @param externalProps event handlers for the root slot
- * @returns props that should be spread on the root slot
- */
- getRootProps: = {}>(
- externalProps?: ExternalProps,
- ) => UseMenuItemRootSlotProps;
- /**
- * If `true`, the component is disabled.
- */
- disabled: boolean;
- /**
- * If `true`, the component is being focused using keyboard.
- */
- focusVisible: boolean;
- /**
- * If `true`, the component is being highlighted.
- */
- highlighted: boolean;
- /**
- * 0-based index of the item in the menu.
- */
- index: number;
- /**
- * The ref to the component's root DOM element.
- */
- rootRef: React.RefCallback | null;
- /**
- * Total number of items in the menu.
- */
- totalItemCount: number;
-}
diff --git a/packages/mui-base/src/legacy/useMenuItem/useMenuItemContextStabilizer.ts b/packages/mui-base/src/legacy/useMenuItem/useMenuItemContextStabilizer.ts
deleted file mode 100644
index 0a8310cf40..0000000000
--- a/packages/mui-base/src/legacy/useMenuItem/useMenuItemContextStabilizer.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-'use client';
-import * as React from 'react';
-import { unstable_useId as useId } from '@mui/utils';
-import { ListContext, ListContextValue, ListItemState } from '../../useList';
-
-/**
- * Stabilizes the ListContext value for the MenuItem component, so it doesn't change when sibling items update.
- *
- * Demos:
- *
- * - [Menu](https://mui.com/base-ui/react-menu/#hooks)
- *
- * API:
- *
- * - [useMenuItemContextStabilizer API](https://mui.com/base-ui/react-menu/hooks-api/#use-menu-item-context-stabilizer)
- *
- * @param id - The id of the MenuItem. If undefined, it will be generated with useId.
- * @returns The stable ListContext value and the id of the MenuItem.
- */
-export function useMenuItemContextStabilizer(id: string | undefined) {
- const listContext = React.useContext(ListContext as React.Context>);
-
- if (!listContext) {
- throw new Error('MenuItem: ListContext was not found.');
- }
- const itemId = useId(id);
-
- const { getItemState, dispatch } = listContext;
-
- let itemState: ListItemState;
- if (itemId != null) {
- itemState = getItemState(itemId);
- } else {
- itemState = { focusable: true, highlighted: false, selected: false };
- }
-
- const { highlighted, selected, focusable } = itemState;
-
- // The local version of getItemState can be only called with the current Option's value.
- // It doesn't make much sense to render an Option depending on other Options' state anyway.
- const localGetItemState = React.useCallback(
- (itemValue: string) => {
- if (itemValue !== itemId) {
- throw new Error(
- [
- 'Base UI MenuItem: Tried to access the state of another MenuItem.',
- `itemValue: ${itemValue} | id: ${itemId}`,
- 'This is unsupported when the MenuItem uses the MenuItemContextStabilizer as a performance optimization.',
- ].join('/n'),
- );
- }
-
- return {
- highlighted,
- selected,
- focusable,
- };
- },
- [highlighted, selected, focusable, itemId],
- );
-
- // Create a local (per MenuItem) instance of the ListContext that changes only when
- // the getItemState's return value changes.
- // This makes MenuItems re-render only when their state actually change, not when any MenuItem's state changes.
- const localContextValue = React.useMemo(
- () => ({
- dispatch,
- getItemState: localGetItemState,
- }),
- [dispatch, localGetItemState],
- );
-
- return {
- contextValue: localContextValue,
- id: itemId,
- };
-}
diff --git a/packages/mui-base/src/utils/mergeReactProps.test.ts b/packages/mui-base/src/utils/mergeReactProps.test.ts
index a97595fb33..e8e2e9efc3 100644
--- a/packages/mui-base/src/utils/mergeReactProps.test.ts
+++ b/packages/mui-base/src/utils/mergeReactProps.test.ts
@@ -25,6 +25,31 @@ describe('mergeReactProps', () => {
expect(ourProps.onPaste.callCount).to.equal(1);
});
+ it('merges multiple event handlers', () => {
+ const log: string[] = [];
+
+ const mergedProps = mergeReactProps<'button'>(
+ {
+ onClick() {
+ log.push('1');
+ },
+ },
+ {
+ onClick() {
+ log.push('2');
+ },
+ },
+ {
+ onClick() {
+ log.push('3');
+ },
+ },
+ );
+
+ mergedProps.onClick?.({} as any);
+ expect(log).to.deep.equal(['1', '2', '3']);
+ });
+
it('merges styles', () => {
const theirProps = {
style: { color: 'red' },
@@ -99,10 +124,29 @@ describe('mergeReactProps', () => {
ran = true;
},
},
+ {
+ onClick() {
+ ran = true;
+ },
+ },
);
mergedProps.onClick?.({} as any);
expect(ran).to.equal(false);
});
+
+ it('merges internal event handlers so that the ones defined first override the ones defined later', () => {
+ const mergedProps = mergeReactProps<'button'>(
+ {},
+ {
+ title: 'internal title 1',
+ },
+ {
+ title: 'internal title 2',
+ },
+ );
+
+ expect(mergedProps.title).to.equal('internal title 1');
+ });
});
diff --git a/packages/mui-base/src/utils/mergeReactProps.ts b/packages/mui-base/src/utils/mergeReactProps.ts
index 2396f8ff9e..61cac24252 100644
--- a/packages/mui-base/src/utils/mergeReactProps.ts
+++ b/packages/mui-base/src/utils/mergeReactProps.ts
@@ -2,7 +2,7 @@ import type * as React from 'react';
import type { BaseUIEvent, WithBaseUIEvent } from './types';
/**
- * Merges two sets of React props such that their event handlers are called in sequence (the user's
+ * Merges multiple sets of React props such that their event handlers are called in sequence (the user's
* before our internal ones), and allows the user to prevent the internal event handlers from being
* executed by attaching a `preventBaseUIHandler` method. It also merges the `style` prop, whereby
* the user's styles overwrite the internal ones.
@@ -12,9 +12,25 @@ import type { BaseUIEvent, WithBaseUIEvent } from './types';
* @returns the merged props.
*/
export function mergeReactProps(
- externalProps: WithBaseUIEvent>,
+ externalProps: WithBaseUIEvent> | undefined,
+ ...internalProps: React.ComponentPropsWithRef[]
+): WithBaseUIEvent> {
+ let mergedInternalProps: WithBaseUIEvent> = internalProps[0];
+ for (let i = 1; i < internalProps.length; i += 1) {
+ mergedInternalProps = merge(mergedInternalProps, internalProps[i]);
+ }
+
+ return merge(externalProps, mergedInternalProps as React.ComponentPropsWithRef);
+}
+
+function merge(
+ externalProps: WithBaseUIEvent> | undefined,
internalProps: React.ComponentPropsWithRef,
): WithBaseUIEvent> {
+ if (!externalProps) {
+ return internalProps;
+ }
+
return Object.entries(externalProps).reduce(
(acc, [key, value]) => {
if (
diff --git a/packages/mui-base/src/utils/useAnchorPositioning.ts b/packages/mui-base/src/utils/useAnchorPositioning.ts
index dc9c4b8ad0..6e2f329ff6 100644
--- a/packages/mui-base/src/utils/useAnchorPositioning.ts
+++ b/packages/mui-base/src/utils/useAnchorPositioning.ts
@@ -47,6 +47,7 @@ interface UseAnchorPositioningParameters {
arrowPadding?: number;
floatingRootContext?: FloatingRootContext;
mounted?: boolean;
+ nodeId?: string;
}
interface UseAnchorPositioningReturnValue {
@@ -85,6 +86,7 @@ export function useAnchorPositioning(
keepMounted = false,
arrowPadding = 5,
mounted = true,
+ nodeId,
} = params;
const placement = alignment === 'center' ? side : (`${side}-${alignment}` as Placement);
@@ -198,6 +200,7 @@ export function useAnchorPositioning(
middleware,
strategy: positionStrategy,
whileElementsMounted: keepMounted ? undefined : autoUpdate,
+ nodeId,
});
// We can assume that element anchors are stable across renders, and thus can be reactive.
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index edeb5d9ff7..98a88e1849 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -62,20 +62,20 @@ importers:
specifier: ^1.0.8
version: 1.0.8
'@mui/internal-scripts':
- specifier: ^1.0.13
- version: 1.0.13
+ specifier: ^1.0.14
+ version: 1.0.14
'@mui/internal-test-utils':
- specifier: 1.0.5
- version: 1.0.5(@babel/core@7.25.2)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ specifier: 1.0.6
+ version: 1.0.6(@babel/core@7.25.2)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/material':
- specifier: 6.0.0-beta.2
- version: 6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ specifier: 6.0.0-beta.4
+ version: 6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/monorepo':
- specifier: github:mui/material-ui#v6.0.0-beta.2
- version: https://codeload.github.com/mui/material-ui/tar.gz/18056e14de623cab995db4bc01c879467aeb273e(encoding@0.1.13)
+ specifier: github:mui/material-ui#v6.0.0-beta.4
+ version: https://codeload.github.com/mui/material-ui/tar.gz/4f0c10d0b40327e672bdb28ca05651d037b47a08(encoding@0.1.13)
'@mui/utils':
- specifier: 6.0.0-beta.1
- version: 6.0.0-beta.1(@types/react@18.3.1)(react@18.2.0)
+ specifier: 6.0.0-beta.4
+ version: 6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)
'@next/eslint-plugin-next':
specifier: ^14.2.5
version: 14.2.5
@@ -327,55 +327,55 @@ importers:
version: link:../packages/mui-base/build
'@docsearch/react':
specifier: ^3.6.1
- version: 3.6.1(@algolia/client-search@4.23.2)(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(search-insights@2.13.0)
+ version: 3.6.1(@algolia/client-search@4.23.2)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.13.0)
'@emotion/cache':
specifier: ^11.11.0
- version: 11.11.0
+ version: 11.13.1
'@emotion/react':
specifier: ^11.11.4
- version: 11.11.4(@types/react@18.3.1)(react@18.2.0)
+ version: 11.13.0(@types/react@18.3.1)(react@18.3.1)
'@emotion/server':
specifier: ^11.11.0
version: 11.11.0
'@emotion/styled':
specifier: ^11.11.5
- version: 11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ version: 11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
'@mui/docs':
- specifier: 6.0.0-beta.2
- version: 6.0.0-beta.2(zofz3nxyhvt4ajdv3dpnpcd7gm)
+ specifier: 6.0.0-beta.4
+ version: 6.0.0-beta.4(vunuo4wz5aeoo5x5zjit7veshq)
'@mui/icons-material':
- specifier: 6.0.0-beta.2
- version: 6.0.0-beta.2(@mui/material@6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ specifier: 6.0.0-beta.4
+ version: 6.0.0-beta.4(@mui/material@6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
'@mui/internal-markdown':
specifier: ^1.0.8
version: 1.0.8
'@mui/internal-scripts':
- specifier: ^1.0.13
- version: 1.0.13
+ specifier: ^1.0.14
+ version: 1.0.14
'@mui/joy':
specifier: 5.0.0-beta.48
- version: 5.0.0-beta.48(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 5.0.0-beta.48(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/material':
- specifier: 6.0.0-beta.2
- version: 6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ specifier: 6.0.0-beta.4
+ version: 6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/material-nextjs':
- specifier: 6.0.0-alpha.14
- version: 6.0.0-alpha.14(@emotion/cache@11.11.0)(@emotion/server@11.11.0)(@mui/material@6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.1)(next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
+ specifier: 6.0.0-beta.4
+ version: 6.0.0-beta.4(@emotion/cache@11.13.1)(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/server@11.11.0)(@types/react@18.3.1)(next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@mui/styles':
- specifier: 6.0.0-beta.2
- version: 6.0.0-beta.2(@types/react@18.3.1)(react@18.2.0)
+ specifier: 6.0.0-beta.4
+ version: 6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)
'@mui/system':
- specifier: 6.0.0-beta.1
- version: 6.0.0-beta.1(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ specifier: 6.0.0-beta.4
+ version: 6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
'@mui/types':
specifier: 7.2.15
version: 7.2.15(@types/react@18.3.1)
'@mui/utils':
- specifier: 6.0.0-beta.1
- version: 6.0.0-beta.1(@types/react@18.3.1)(react@18.2.0)
+ specifier: 6.0.0-beta.4
+ version: 6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)
'@react-spring/web':
specifier: ^9.7.4
- version: 9.7.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 9.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
autoprefixer:
specifier: ^10.4.19
version: 10.4.19(postcss@8.4.40)
@@ -429,7 +429,7 @@ importers:
version: 1.5.0
next:
specifier: ^14.2.5
- version: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
nprogress:
specifier: ^0.2.0
version: 0.2.0
@@ -443,35 +443,35 @@ importers:
specifier: ^15.8.1
version: 15.8.1
react:
- specifier: ^18.2.0
- version: 18.2.0
+ specifier: ^18.3.1
+ version: 18.3.1
react-dom:
- specifier: ^18.2.0
- version: 18.2.0(react@18.2.0)
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
react-draggable:
specifier: ^4.4.6
- version: 4.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-is:
- specifier: ^18.2.0
+ specifier: ^18.3.1
version: 18.3.1
react-router-dom:
specifier: ^6.23.1
- version: 6.23.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 6.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-runner:
specifier: ^1.0.5
- version: 1.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 1.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-simple-code-editor:
specifier: ^0.13.1
- version: 0.13.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 0.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-transition-group:
specifier: ^4.4.5
- version: 4.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
rimraf:
specifier: ^5.0.10
version: 5.0.10
styled-components:
specifier: ^6.1.12
- version: 6.1.12(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 6.1.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
stylis:
specifier: 4.3.2
version: 4.3.2
@@ -492,8 +492,8 @@ importers:
specifier: ^1.0.8
version: 1.0.8
'@mui/internal-test-utils':
- specifier: 1.0.5
- version: 1.0.5(@babel/core@7.25.2)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ specifier: 1.0.6
+ version: 1.0.6(@babel/core@7.25.2)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/autosuggest-highlight':
specifier: ^3.2.3
version: 3.2.3
@@ -526,7 +526,7 @@ importers:
version: 4.0.0(encoding@0.1.13)
framer-motion:
specifier: ^11.2.10
- version: 11.2.10(@emotion/is-prop-valid@1.2.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 11.2.10(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
marked:
specifier: ^12.0.2
version: 12.0.2
@@ -550,10 +550,10 @@ importers:
version: 8.56.11
'@typescript-eslint/experimental-utils':
specifier: ^5.62.0
- version: 5.62.0(eslint@8.57.0)(typescript@5.4.5)
+ version: 5.62.0(eslint@9.8.0)(typescript@5.4.5)
'@typescript-eslint/parser':
specifier: ^7.8.0
- version: 7.8.0(eslint@8.57.0)(typescript@5.4.5)
+ version: 7.8.0(eslint@9.8.0)(typescript@5.4.5)
packages/mui-base:
dependencies:
@@ -562,10 +562,10 @@ importers:
version: 7.25.0
'@floating-ui/react':
specifier: ^0.26.20
- version: 0.26.20(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 0.26.20(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@floating-ui/react-dom':
specifier: ^2.1.1
- version: 2.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@floating-ui/utils':
specifier: ^0.2.5
version: 0.2.5
@@ -574,7 +574,7 @@ importers:
version: 7.2.15(@types/react@18.3.1)
'@mui/utils':
specifier: ^5.16.6
- version: 5.16.6(@types/react@18.3.1)(react@18.2.0)
+ version: 5.16.6(@types/react@18.3.1)(react@18.3.1)
clsx:
specifier: ^2.1.1
version: 2.1.1
@@ -584,16 +584,16 @@ importers:
devDependencies:
'@mui/internal-babel-macros':
specifier: ^1.0.1
- version: 1.0.1(@mui/utils@5.16.6(@types/react@18.3.1)(react@18.2.0))
+ version: 1.0.1(@mui/utils@5.16.6(@types/react@18.3.1)(react@18.3.1))
'@mui/internal-test-utils':
- specifier: 1.0.5
- version: 1.0.5(@babel/core@7.25.2)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ specifier: 1.0.6
+ version: 1.0.6(@babel/core@7.25.2)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@testing-library/react':
specifier: ^15.0.7
- version: 15.0.7(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 15.0.7(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@testing-library/user-event':
specifier: ^14.5.2
- version: 14.5.2(@testing-library/dom@10.3.1)
+ version: 14.5.2(@testing-library/dom@10.4.0)
'@types/chai':
specifier: ^4.3.16
version: 4.3.16
@@ -622,11 +622,11 @@ importers:
specifier: ^4.17.21
version: 4.17.21
react:
- specifier: ^18.2.0
- version: 18.2.0
+ specifier: ^18.3.1
+ version: 18.3.1
react-dom:
- specifier: ^18.2.0
- version: 18.2.0(react@18.2.0)
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
sinon:
specifier: ^17.0.1
version: 17.0.1
@@ -645,25 +645,25 @@ importers:
version: link:../packages/mui-base/build
'@emotion/cache':
specifier: ^11.11.0
- version: 11.11.0
+ version: 11.13.1
'@emotion/react':
specifier: ^11.11.4
- version: 11.11.4(@types/react@18.3.1)(react@18.2.0)
+ version: 11.13.0(@types/react@18.3.1)(react@18.3.1)
'@mui/internal-test-utils':
- specifier: 1.0.5
- version: 1.0.5(@babel/core@7.25.2)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ specifier: 1.0.6
+ version: 1.0.6(@babel/core@7.25.2)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/joy':
specifier: 5.0.0-beta.48
- version: 5.0.0-beta.48(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 5.0.0-beta.48(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/material':
- specifier: 6.0.0-beta.2
- version: 6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ specifier: 6.0.0-beta.4
+ version: 6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@playwright/test':
specifier: 1.45.3
version: 1.45.3
'@testing-library/dom':
specifier: ^10.1.0
- version: 10.3.1
+ version: 10.4.0
'@types/chai':
specifier: ^4.3.16
version: 4.3.16
@@ -671,8 +671,8 @@ importers:
specifier: ^18.3.1
version: 18.3.1
'@types/react-is':
- specifier: ^18.2.4
- version: 18.2.4
+ specifier: ^18.3.0
+ version: 18.3.0
'@types/sinon':
specifier: ^17.0.3
version: 17.0.3
@@ -704,23 +704,23 @@ importers:
specifier: ^15.8.1
version: 15.8.1
react:
- specifier: ^18.2.0
- version: 18.2.0
+ specifier: ^18.3.1
+ version: 18.3.1
react-dom:
- specifier: ^18.2.0
- version: 18.2.0(react@18.2.0)
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
react-is:
- specifier: ^18.2.0
+ specifier: ^18.3.1
version: 18.3.1
react-router-dom:
specifier: ^6.23.1
- version: 6.23.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 6.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
sinon:
specifier: ^17.0.1
version: 17.0.1
styled-components:
specifier: ^6.1.12
- version: 6.1.12(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 6.1.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
stylis:
specifier: 4.3.2
version: 4.3.2
@@ -1546,14 +1546,14 @@ packages:
'@emnapi/runtime@1.1.1':
resolution: {integrity: sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==}
- '@emotion/babel-plugin@11.11.0':
- resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==}
+ '@emotion/babel-plugin@11.12.0':
+ resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==}
- '@emotion/cache@11.11.0':
- resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==}
+ '@emotion/cache@11.13.1':
+ resolution: {integrity: sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==}
- '@emotion/hash@0.9.1':
- resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==}
+ '@emotion/hash@0.9.2':
+ resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
'@emotion/is-prop-valid@1.2.2':
resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==}
@@ -1561,8 +1561,11 @@ packages:
'@emotion/memoize@0.8.1':
resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
- '@emotion/react@11.11.4':
- resolution: {integrity: sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==}
+ '@emotion/memoize@0.9.0':
+ resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
+
+ '@emotion/react@11.13.0':
+ resolution: {integrity: sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==}
peerDependencies:
'@types/react': '*'
react: '>=16.8.0'
@@ -1570,8 +1573,8 @@ packages:
'@types/react':
optional: true
- '@emotion/serialize@1.1.4':
- resolution: {integrity: sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==}
+ '@emotion/serialize@1.3.0':
+ resolution: {integrity: sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==}
'@emotion/server@11.11.0':
resolution: {integrity: sha512-6q89fj2z8VBTx9w93kJ5n51hsmtYuFPtZgnc1L8VzRx9ti4EU6EyvF6Nn1H1x3vcCQCF7u2dB2lY4AYJwUW4PA==}
@@ -1581,8 +1584,8 @@ packages:
'@emotion/css':
optional: true
- '@emotion/sheet@1.2.2':
- resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==}
+ '@emotion/sheet@1.4.0':
+ resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
'@emotion/styled@11.11.5':
resolution: {integrity: sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==}
@@ -1597,16 +1600,19 @@ packages:
'@emotion/unitless@0.8.1':
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
- '@emotion/use-insertion-effect-with-fallbacks@1.0.1':
- resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==}
+ '@emotion/unitless@0.9.0':
+ resolution: {integrity: sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==}
+
+ '@emotion/use-insertion-effect-with-fallbacks@1.1.0':
+ resolution: {integrity: sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==}
peerDependencies:
react: '>=16.8.0'
- '@emotion/utils@1.2.1':
- resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==}
+ '@emotion/utils@1.4.0':
+ resolution: {integrity: sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==}
- '@emotion/weak-memoize@0.3.1':
- resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==}
+ '@emotion/weak-memoize@0.4.0':
+ resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
'@esbuild/aix-ppc64@0.20.2':
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
@@ -1752,18 +1758,34 @@ packages:
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
- '@eslint-community/regexpp@4.10.0':
- resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==}
+ '@eslint-community/regexpp@4.11.0':
+ resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+ '@eslint/config-array@0.17.1':
+ resolution: {integrity: sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@eslint/eslintrc@2.1.4':
resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ '@eslint/eslintrc@3.1.0':
+ resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@eslint/js@8.57.0':
resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ '@eslint/js@9.8.0':
+ resolution: {integrity: sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.4':
+ resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@floating-ui/core@1.6.3':
resolution: {integrity: sha512-1ZpCvYf788/ZXOhRQGFxnYQOVgeU+pi0i+d0Ow34La7qjIXETi6RNswGVKkA6KcDO8/+Ysu2E/CeUmmeEBDvTg==}
@@ -1814,6 +1836,10 @@ packages:
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
deprecated: Use @eslint/object-schema instead
+ '@humanwhocodes/retry@0.3.0':
+ resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
+ engines: {node: '>=18.18'}
+
'@hutson/parse-repository-url@3.0.2':
resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==}
engines: {node: '>=6.9.0'}
@@ -2008,9 +2034,9 @@ packages:
'@mui/core-downloads-tracker@6.0.0-dev.240424162023-9968b4889d':
resolution: {integrity: sha512-doh3M3U7HUGSBIWGe1yvesSbfDguMRjP0N09ogWSBM2hovXAlgULhMgcRTepAZLLwfRxFII0bCohq6B9NqoKuw==}
- '@mui/docs@6.0.0-beta.2':
- resolution: {integrity: sha512-VLSVlYs09wVyhxrjIEMoyHcJV60mMePjszbzOcGImbRgCdaSw1cwCTzntmB09dRkaDsMhRul1I+poFyQu0nCAA==}
- engines: {node: '>=12.0.0'}
+ '@mui/docs@6.0.0-beta.4':
+ resolution: {integrity: sha512-zQ/0CPWRmNBDYwLE10FrBm4wnLUCm5200ZsyqFowcHr2Utajmlsby4BIkhjlZ7/XurQr2SW67bA+VIWT+GKvDQ==}
+ engines: {node: '>=14.0.0'}
peerDependencies:
'@mui/base': '*'
'@mui/icons-material': ^5.0.0
@@ -2018,17 +2044,18 @@ packages:
'@mui/system': ^5.0.0
'@types/react': ^18.3.1
chai: ^4.4.1
+ csstype: ^3.1.3
next: ^13.5.1 || ^14
react: ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
- '@mui/icons-material@6.0.0-beta.2':
- resolution: {integrity: sha512-TPY6Tnpc9Va71pRSwPvkuLZUW7n5J7ue5IH6A9juxBLcTN5Vo1AKPmx2cf7BMDGVJPkjtIzLqwmIHvGQQuvNQw==}
- engines: {node: '>=12.0.0'}
+ '@mui/icons-material@6.0.0-beta.4':
+ resolution: {integrity: sha512-04lX8JqiKEZYUO2PZSksWt6bvecCmSkBolk6XiCIjjnCX95sGUdhoGFrnIwldBwEyn5N1/M4zUeBPkn09CrFrQ==}
+ engines: {node: '>=14.0.0'}
peerDependencies:
- '@mui/material': 6.0.0-beta.2
+ '@mui/material': 6.0.0-beta.4
'@types/react': ^18.3.1
react: ^17.0.0 || ^18.0.0
peerDependenciesMeta:
@@ -2047,11 +2074,11 @@ packages:
'@mui/internal-markdown@1.0.8':
resolution: {integrity: sha512-OU/ieH8HhRbciy3fR5i8vSU/Z4wlfE+iwEFvrqP578jBeqg7X0gvev/uvv9d+FSgRnvgmWJzCYXEPlzlI3MmFA==}
- '@mui/internal-scripts@1.0.13':
- resolution: {integrity: sha512-3g8kl1UEFK6WxoHfstWMDZCW8DimVgfRDh/j2D0CNaKbupfmfdC5e6Of9KDKXV45ATs5LaYfozAKbxMQK7xyhA==}
+ '@mui/internal-scripts@1.0.14':
+ resolution: {integrity: sha512-v2HQWzssX8l+WAZh4JpgAtnxvwi4ZEm7+spi3e/1zxTXz/k3aWlcc50lpfeAtD6TGbegN2/j0OCORfDHTDQdbQ==}
- '@mui/internal-test-utils@1.0.5':
- resolution: {integrity: sha512-5reErcYMSi+PbcuHigno150UDii4pC9TYfL9sY1YcQUtDhQMdNkByvblQZw3X0K2h5puiBYs/cnj670J1JKztA==}
+ '@mui/internal-test-utils@1.0.6':
+ resolution: {integrity: sha512-S2I9ytriELHH3kr+RNRQ9On0wRMVTNTSQagD7H6/rwoQC8BDs1J72etjamjItagEFXAyuUXX2FzWwhUTZFJHlg==}
peerDependencies:
react: ^18.2.0
react-dom: ^18.2.0
@@ -2073,13 +2100,13 @@ packages:
'@types/react':
optional: true
- '@mui/material-nextjs@6.0.0-alpha.14':
- resolution: {integrity: sha512-Ie8YwRmGGwsL9mEzYR3U7BAqYjaC52BVhtOYoVIISj8RhfGI44O3qUyUzKHi3GHhyXjGM7Ozi735qm0oVfyvNQ==}
- engines: {node: '>=12.0.0'}
+ '@mui/material-nextjs@6.0.0-beta.4':
+ resolution: {integrity: sha512-GSI/tX32T5iYtqjpbt0AbSMWvRX0yLdyCIYt8AB5HOHNYXlP7kDd+9INhReTbjdg8ZJqWeH87mxzKQ3/gbStGg==}
+ engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/cache': ^11.11.0
+ '@emotion/react': ^11.11.4
'@emotion/server': ^11.11.0
- '@mui/material': 6.0.0-beta.1
'@types/react': ^18.3.1
next: ^13.0.0 || ^14.0.0
react: ^17.0.0 || ^18.0.0
@@ -2091,13 +2118,13 @@ packages:
'@types/react':
optional: true
- '@mui/material@6.0.0-beta.2':
- resolution: {integrity: sha512-2LJ+o8VY1sV7ntld1xv9jjp+kRkogPfNw+7h/OuZs546N4L7ewwVVl6RwZiXc2d9adulTCevvDOKXOZp+XexGg==}
+ '@mui/material@6.0.0-beta.4':
+ resolution: {integrity: sha512-TqETyQcwH6+3MdsZbTyJK8y41j//xeWKG9kvH4CEGeYDXConBc4TFS/2t+lf1w051Vd74/GiyGaDqlSX1Fn3dQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
'@emotion/styled': ^11.3.0
- '@mui/material-pigment-css': ^6.0.0-beta.1
+ '@mui/material-pigment-css': ^6.0.0-beta.4
'@types/react': ^18.3.1
react: ^17.0.0 || ^18.0.0
react-dom: ^17.0.0 || ^18.0.0
@@ -2111,9 +2138,9 @@ packages:
'@types/react':
optional: true
- '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/18056e14de623cab995db4bc01c879467aeb273e':
- resolution: {tarball: https://codeload.github.com/mui/material-ui/tar.gz/18056e14de623cab995db4bc01c879467aeb273e}
- version: 6.0.0-beta.2
+ '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/4f0c10d0b40327e672bdb28ca05651d037b47a08':
+ resolution: {tarball: https://codeload.github.com/mui/material-ui/tar.gz/4f0c10d0b40327e672bdb28ca05651d037b47a08}
+ version: 6.0.0-beta.4
engines: {pnpm: 9.5.0}
'@mui/private-theming@5.16.1':
@@ -2126,9 +2153,9 @@ packages:
'@types/react':
optional: true
- '@mui/private-theming@6.0.0-beta.1':
- resolution: {integrity: sha512-IY4JMVmNuxeXm7yvKsMt+F+cejM0FjD1VTykjTQaYQA//JXpyphQK+oj8ML/n2NJUDkoE4O+aqW0nm/sBjyEQw==}
- engines: {node: '>=12.0.0'}
+ '@mui/private-theming@6.0.0-beta.4':
+ resolution: {integrity: sha512-mHt4NH6KD1dOc/Y9yG5xkVtKRTmo2khLiJrbgfU1BLTKD++jAoadnZihQvEubQQzw0+dcBP/5mI9Gv5udTSdiA==}
+ engines: {node: '>=14.0.0'}
peerDependencies:
'@types/react': ^18.3.1
react: ^17.0.0 || ^18.0.0
@@ -2149,9 +2176,9 @@ packages:
'@emotion/styled':
optional: true
- '@mui/styled-engine@6.0.0-beta.1':
- resolution: {integrity: sha512-Q2Hrt0BRJ6kSq0BYjIZHwHoEwmIZAyxBf15k6Vt0kkrqMGPKqDZxpg/miO0Mh/gkfXL/C0NfUO9XYti5DOVEsA==}
- engines: {node: '>=12.0.0'}
+ '@mui/styled-engine@6.0.0-beta.4':
+ resolution: {integrity: sha512-L3twZGfmRk+qkkDZY+c8yWkYdRI0cGs5kyquqVUg1z4KZbFP/hDd2uDTpT/oZ0vJdPbwF9+INPexzkQG1KzlQg==}
+ engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.4.1
'@emotion/styled': ^11.3.0
@@ -2162,9 +2189,9 @@ packages:
'@emotion/styled':
optional: true
- '@mui/styles@6.0.0-beta.2':
- resolution: {integrity: sha512-1QxqwaPVTvkUqZ5jW9COZNdyJlWtfLcCpNGKANIRsZvLoTVHrc0QwZO6aAgJl94oSHF89jv8NZx3wTqb8wQYvA==}
- engines: {node: '>=12.0.0'}
+ '@mui/styles@6.0.0-beta.4':
+ resolution: {integrity: sha512-YNN7jqgRZJFXkuVP4bjCGgt/thAOaBTPLZkEzonDdoKfU4N85yEEy/GFpwZUNTlVN5akfg9wpFWeMKxyFe8DEg==}
+ engines: {node: '>=14.0.0'}
peerDependencies:
'@types/react': ^18.3.1
react: ^17.0.0
@@ -2188,9 +2215,9 @@ packages:
'@types/react':
optional: true
- '@mui/system@6.0.0-beta.1':
- resolution: {integrity: sha512-gqp++9yZ91gXt9b8BrI8LelrnLnIcYWR0pxVSR+3OXwepOC17gONO3w5KOCpJcR8f0irmIccNg3a5LeaLWt1KA==}
- engines: {node: '>=12.0.0'}
+ '@mui/system@6.0.0-beta.4':
+ resolution: {integrity: sha512-0tKYCXOIQILKTkU6m6gjIQ3f9gU1qBa7vZFkTiyi2t3BTGRgOHduNaddQrAZYrTYGEBypaoOeCfllmtEW+dVmQ==}
+ engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
'@emotion/styled': ^11.3.0
@@ -2232,8 +2259,8 @@ packages:
'@types/react':
optional: true
- '@mui/utils@6.0.0-beta.1':
- resolution: {integrity: sha512-NpvigKbCf90GaDb57Pmiuc0SmctyKpvABxFJa0d6Clri3tW5KYoZaVUZrWtqvuLkQEl9LvrLC/rhwX8NkbCzkg==}
+ '@mui/utils@6.0.0-beta.4':
+ resolution: {integrity: sha512-tJsYnoUh0d9JeEcz70jbF9S2uLviBDgu1FNWJUDvuz31a/ClaJsGY0sodHPhamvob3XrPjzdQX2xSZ91m9Jzag==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^18.3.1
@@ -2751,8 +2778,8 @@ packages:
'@swc/helpers@0.5.5':
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
- '@testing-library/dom@10.3.1':
- resolution: {integrity: sha512-q/WL+vlXMpC0uXDyfsMtc1rmotzLV8Y0gq6q1gfrrDjQeHoeLrqHbxdPvPNAh1i+xuJl7+BezywcXArz7vLqKQ==}
+ '@testing-library/dom@10.4.0':
+ resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
engines: {node: '>=18'}
'@testing-library/react@15.0.7':
@@ -2910,8 +2937,8 @@ packages:
'@types/react-dom@18.3.0':
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
- '@types/react-is@18.2.4':
- resolution: {integrity: sha512-wBc7HgmbCcrvw0fZjxbgz/xrrlZKzEqmABBMeSvpTvdm25u6KI6xdIi9pRE2G0C1Lw5ETFdcn4UbYZ4/rpqUYw==}
+ '@types/react-is@18.3.0':
+ resolution: {integrity: sha512-KZJpHUkAdzyKj/kUHJDc6N7KyidftICufJfOFpiG6haL/BDQNQt5i4n1XDUL/nDZAtGLHDSWRYpLzKTAKSvX6w==}
'@types/react-transition-group@4.4.10':
resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
@@ -3168,8 +3195,8 @@ packages:
resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
engines: {node: '>=0.4.0'}
- acorn@8.11.3:
- resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
+ acorn@8.12.1:
+ resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
engines: {node: '>=0.4.0'}
hasBin: true
@@ -4520,6 +4547,10 @@ packages:
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ eslint-scope@8.0.2:
+ resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
eslint-utils@3.0.0:
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
@@ -4534,11 +4565,24 @@ packages:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ eslint-visitor-keys@4.0.0:
+ resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
eslint@8.57.0:
resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
+ eslint@9.8.0:
+ resolution: {integrity: sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+
+ espree@10.1.0:
+ resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
espree@9.6.1:
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -4995,6 +5039,10 @@ packages:
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
engines: {node: '>=8'}
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
globalthis@1.0.3:
resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==}
engines: {node: '>= 0.4'}
@@ -5018,8 +5066,8 @@ packages:
globjoin@0.1.4:
resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
- google-auth-library@9.11.0:
- resolution: {integrity: sha512-epX3ww/mNnhl6tL45EQ/oixsY8JLEgUFoT4A5E/5iAR4esld9Kqv6IJGk7EmGuOgDvaarwF95hU2+v7Irql9lw==}
+ google-auth-library@9.13.0:
+ resolution: {integrity: sha512-p9Y03Uzp/Igcs36zAaB0XTSwZ8Y0/tpYiz5KIde5By+H9DCVUSYtDWZu6aFXsWTqENMb8BD/pDT3hR8NVrPkfA==}
engines: {node: '>=14'}
googleapis-common@7.1.0:
@@ -7130,10 +7178,10 @@ packages:
engines: {node: '>=8.10.0'}
hasBin: true
- react-dom@18.2.0:
- resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
+ react-dom@18.3.1:
+ resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
peerDependencies:
- react: ^18.2.0
+ react: ^18.3.1
react-draggable@4.4.6:
resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==}
@@ -7181,8 +7229,8 @@ packages:
react: '>=16.6.0'
react-dom: '>=16.6.0'
- react@18.2.0:
- resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
+ react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
read-cache@1.0.0:
@@ -7432,8 +7480,8 @@ packages:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
- scheduler@0.23.0:
- resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
+ scheduler@0.23.2:
+ resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
schema-utils@3.3.0:
resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==}
@@ -9557,7 +9605,7 @@ snapshots:
'@docsearch/css@3.6.1': {}
- '@docsearch/react@3.6.1(@algolia/client-search@4.23.2)(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(search-insights@2.13.0)':
+ '@docsearch/react@3.6.1(@algolia/client-search@4.23.2)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.13.0)':
dependencies:
'@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.23.2)(search-insights@2.13.0)
'@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.23.2)
@@ -9565,8 +9613,8 @@ snapshots:
algoliasearch: 4.23.2
optionalDependencies:
'@types/react': 18.3.1
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
search-insights: 2.13.0
transitivePeerDependencies:
- '@algolia/client-search'
@@ -9578,13 +9626,13 @@ snapshots:
tslib: 2.6.2
optional: true
- '@emotion/babel-plugin@11.11.0':
+ '@emotion/babel-plugin@11.12.0':
dependencies:
'@babel/helper-module-imports': 7.24.7
'@babel/runtime': 7.25.0
- '@emotion/hash': 0.9.1
- '@emotion/memoize': 0.8.1
- '@emotion/serialize': 1.1.4
+ '@emotion/hash': 0.9.2
+ '@emotion/memoize': 0.9.0
+ '@emotion/serialize': 1.3.0
babel-plugin-macros: 3.1.0
convert-source-map: 1.9.0
escape-string-regexp: 4.0.0
@@ -9594,15 +9642,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@emotion/cache@11.11.0':
+ '@emotion/cache@11.13.1':
dependencies:
- '@emotion/memoize': 0.8.1
- '@emotion/sheet': 1.2.2
- '@emotion/utils': 1.2.1
- '@emotion/weak-memoize': 0.3.1
+ '@emotion/memoize': 0.9.0
+ '@emotion/sheet': 1.4.0
+ '@emotion/utils': 1.4.0
+ '@emotion/weak-memoize': 0.4.0
stylis: 4.2.0
- '@emotion/hash@0.9.1': {}
+ '@emotion/hash@0.9.2': {}
'@emotion/is-prop-valid@1.2.2':
dependencies:
@@ -9610,49 +9658,51 @@ snapshots:
'@emotion/memoize@0.8.1': {}
- '@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0)':
+ '@emotion/memoize@0.9.0': {}
+
+ '@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@emotion/babel-plugin': 11.11.0
- '@emotion/cache': 11.11.0
- '@emotion/serialize': 1.1.4
- '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
- '@emotion/utils': 1.2.1
- '@emotion/weak-memoize': 0.3.1
+ '@emotion/babel-plugin': 11.12.0
+ '@emotion/cache': 11.13.1
+ '@emotion/serialize': 1.3.0
+ '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
+ '@emotion/utils': 1.4.0
+ '@emotion/weak-memoize': 0.4.0
hoist-non-react-statics: 3.3.2
- react: 18.2.0
+ react: 18.3.1
optionalDependencies:
'@types/react': 18.3.1
transitivePeerDependencies:
- supports-color
- '@emotion/serialize@1.1.4':
+ '@emotion/serialize@1.3.0':
dependencies:
- '@emotion/hash': 0.9.1
- '@emotion/memoize': 0.8.1
- '@emotion/unitless': 0.8.1
- '@emotion/utils': 1.2.1
+ '@emotion/hash': 0.9.2
+ '@emotion/memoize': 0.9.0
+ '@emotion/unitless': 0.9.0
+ '@emotion/utils': 1.4.0
csstype: 3.1.3
'@emotion/server@11.11.0':
dependencies:
- '@emotion/utils': 1.2.1
+ '@emotion/utils': 1.4.0
html-tokenize: 2.0.1
multipipe: 1.0.2
through: 2.3.8
- '@emotion/sheet@1.2.2': {}
+ '@emotion/sheet@1.4.0': {}
- '@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)':
+ '@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@emotion/babel-plugin': 11.11.0
+ '@emotion/babel-plugin': 11.12.0
'@emotion/is-prop-valid': 1.2.2
- '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.2.0)
- '@emotion/serialize': 1.1.4
- '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
- '@emotion/utils': 1.2.1
- react: 18.2.0
+ '@emotion/react': 11.13.0(@types/react@18.3.1)(react@18.3.1)
+ '@emotion/serialize': 1.3.0
+ '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
+ '@emotion/utils': 1.4.0
+ react: 18.3.1
optionalDependencies:
'@types/react': 18.3.1
transitivePeerDependencies:
@@ -9660,13 +9710,15 @@ snapshots:
'@emotion/unitless@0.8.1': {}
- '@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0)':
+ '@emotion/unitless@0.9.0': {}
+
+ '@emotion/use-insertion-effect-with-fallbacks@1.1.0(react@18.3.1)':
dependencies:
- react: 18.2.0
+ react: 18.3.1
- '@emotion/utils@1.2.1': {}
+ '@emotion/utils@1.4.0': {}
- '@emotion/weak-memoize@0.3.1': {}
+ '@emotion/weak-memoize@0.4.0': {}
'@esbuild/aix-ppc64@0.20.2':
optional: true
@@ -9742,7 +9794,20 @@ snapshots:
eslint: 8.57.0
eslint-visitor-keys: 3.4.3
- '@eslint-community/regexpp@4.10.0': {}
+ '@eslint-community/eslint-utils@4.4.0(eslint@9.8.0)':
+ dependencies:
+ eslint: 9.8.0
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.11.0': {}
+
+ '@eslint/config-array@0.17.1':
+ dependencies:
+ '@eslint/object-schema': 2.1.4
+ debug: 4.3.5(supports-color@8.1.1)
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
'@eslint/eslintrc@2.1.4':
dependencies:
@@ -9758,8 +9823,26 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@eslint/eslintrc@3.1.0':
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.3.5(supports-color@8.1.1)
+ espree: 10.1.0
+ globals: 14.0.0
+ ignore: 5.3.1
+ import-fresh: 3.3.0
+ js-yaml: 4.1.0
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
'@eslint/js@8.57.0': {}
+ '@eslint/js@9.8.0': {}
+
+ '@eslint/object-schema@2.1.4': {}
+
'@floating-ui/core@1.6.3':
dependencies:
'@floating-ui/utils': 0.2.5
@@ -9769,18 +9852,18 @@ snapshots:
'@floating-ui/core': 1.6.3
'@floating-ui/utils': 0.2.5
- '@floating-ui/react-dom@2.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ '@floating-ui/react-dom@2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@floating-ui/dom': 1.6.6
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
- '@floating-ui/react@0.26.20(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ '@floating-ui/react@0.26.20(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
- '@floating-ui/react-dom': 2.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ '@floating-ui/react-dom': 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@floating-ui/utils': 0.2.5
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
tabbable: 6.2.0
'@floating-ui/utils@0.2.5': {}
@@ -9820,6 +9903,8 @@ snapshots:
'@humanwhocodes/object-schema@2.0.3': {}
+ '@humanwhocodes/retry@0.3.0': {}
+
'@hutson/parse-repository-url@3.0.2': {}
'@img/sharp-darwin-arm64@0.33.3':
@@ -10032,31 +10117,31 @@ snapshots:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.24.8
- '@mui/base@5.0.0-beta.40(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ '@mui/base@5.0.0-beta.40(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@floating-ui/react-dom': 2.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ '@floating-ui/react-dom': 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/types': 7.2.15(@types/react@18.3.1)
- '@mui/utils': 5.16.6(@types/react@18.3.1)(react@18.2.0)
+ '@mui/utils': 5.16.6(@types/react@18.3.1)(react@18.3.1)
'@popperjs/core': 2.11.8
clsx: 2.1.1
prop-types: 15.8.1
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.1
- '@mui/base@5.0.0-beta.53(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ '@mui/base@5.0.0-beta.53(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@floating-ui/react-dom': 2.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ '@floating-ui/react-dom': 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/types': 7.2.15(@types/react@18.3.1)
- '@mui/utils': 6.0.0-beta.0(@types/react@18.3.1)(react@18.2.0)
+ '@mui/utils': 6.0.0-beta.0(@types/react@18.3.1)(react@18.3.1)
'@popperjs/core': 2.11.8
clsx: 2.1.1
prop-types: 15.8.1
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.1
@@ -10064,37 +10149,38 @@ snapshots:
'@mui/core-downloads-tracker@6.0.0-dev.240424162023-9968b4889d': {}
- '@mui/docs@6.0.0-beta.2(zofz3nxyhvt4ajdv3dpnpcd7gm)':
+ '@mui/docs@6.0.0-beta.4(vunuo4wz5aeoo5x5zjit7veshq)':
dependencies:
'@babel/runtime': 7.25.0
- '@mui/base': 5.0.0-beta.53(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
- '@mui/icons-material': 6.0.0-beta.2(@mui/material@6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ '@mui/base': 5.0.0-beta.53(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@mui/icons-material': 6.0.0-beta.4(@mui/material@6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
'@mui/internal-markdown': 1.0.8
- '@mui/material': 6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
- '@mui/system': 6.0.0-beta.1(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ '@mui/material': 6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@mui/system': 6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
chai: 4.4.1
clipboard-copy: 4.0.1
clsx: 2.1.1
- next: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ csstype: 3.1.3
+ next: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
nprogress: 0.2.0
prop-types: 15.8.1
- react: 18.2.0
+ react: 18.3.1
optionalDependencies:
'@types/react': 18.3.1
- '@mui/icons-material@6.0.0-beta.2(@mui/material@6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)':
+ '@mui/icons-material@6.0.0-beta.4(@mui/material@6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@mui/material': 6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
- react: 18.2.0
+ '@mui/material': 6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
optionalDependencies:
'@types/react': 18.3.1
- '@mui/internal-babel-macros@1.0.1(@mui/utils@5.16.6(@types/react@18.3.1)(react@18.2.0))':
+ '@mui/internal-babel-macros@1.0.1(@mui/utils@5.16.6(@types/react@18.3.1)(react@18.3.1))':
dependencies:
'@babel/helper-module-imports': 7.24.7
'@babel/runtime': 7.25.0
- '@mui/utils': 5.16.6(@types/react@18.3.1)(react@18.2.0)
+ '@mui/utils': 5.16.6(@types/react@18.3.1)(react@18.3.1)
babel-plugin-macros: 3.1.0
transitivePeerDependencies:
- supports-color
@@ -10111,7 +10197,7 @@ snapshots:
marked: 13.0.2
prismjs: 1.29.0
- '@mui/internal-scripts@1.0.13':
+ '@mui/internal-scripts@1.0.14':
dependencies:
'@babel/core': 7.25.2
'@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.25.2)
@@ -10126,17 +10212,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@mui/internal-test-utils@1.0.5(@babel/core@7.25.2)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ '@mui/internal-test-utils@1.0.6(@babel/core@7.25.2)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/plugin-transform-modules-commonjs': 7.24.8(@babel/core@7.25.2)
'@babel/preset-typescript': 7.24.7(@babel/core@7.25.2)
'@babel/register': 7.24.6(@babel/core@7.25.2)
'@babel/runtime': 7.25.0
- '@emotion/cache': 11.11.0
- '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.2.0)
- '@testing-library/dom': 10.3.1
- '@testing-library/react': 16.0.0(@testing-library/dom@10.3.1)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
- '@testing-library/user-event': 14.5.2(@testing-library/dom@10.3.1)
+ '@emotion/cache': 11.13.1
+ '@emotion/react': 11.13.0(@types/react@18.3.1)(react@18.3.1)
+ '@testing-library/dom': 10.4.0
+ '@testing-library/react': 16.0.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0)
chai: 4.4.1
chai-dom: 1.12.0(chai@4.4.1)
dom-accessibility-api: 0.6.3
@@ -10147,8 +10233,8 @@ snapshots:
mocha: 10.6.0
playwright: 1.45.3
prop-types: 15.8.1
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
sinon: 16.1.3
transitivePeerDependencies:
- '@babel/core'
@@ -10159,62 +10245,62 @@ snapshots:
- supports-color
- utf-8-validate
- '@mui/joy@5.0.0-beta.48(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ '@mui/joy@5.0.0-beta.48(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@mui/base': 5.0.0-beta.40(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ '@mui/base': 5.0.0-beta.40(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/core-downloads-tracker': 5.16.1
- '@mui/system': 5.16.1(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ '@mui/system': 5.16.1(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
'@mui/types': 7.2.15(@types/react@18.3.1)
- '@mui/utils': 5.16.6(@types/react@18.3.1)(react@18.2.0)
+ '@mui/utils': 5.16.6(@types/react@18.3.1)(react@18.3.1)
clsx: 2.1.1
prop-types: 15.8.1
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
- '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.2.0)
- '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ '@emotion/react': 11.13.0(@types/react@18.3.1)(react@18.3.1)
+ '@emotion/styled': 11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
'@types/react': 18.3.1
- '@mui/material-nextjs@6.0.0-alpha.14(@emotion/cache@11.11.0)(@emotion/server@11.11.0)(@mui/material@6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.1)(next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)':
+ '@mui/material-nextjs@6.0.0-beta.4(@emotion/cache@11.13.1)(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/server@11.11.0)(@types/react@18.3.1)(next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@mui/material': 6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
- next: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
- react: 18.2.0
+ '@emotion/react': 11.13.0(@types/react@18.3.1)(react@18.3.1)
+ next: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
optionalDependencies:
- '@emotion/cache': 11.11.0
+ '@emotion/cache': 11.13.1
'@emotion/server': 11.11.0
'@types/react': 18.3.1
- '@mui/material@6.0.0-beta.2(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ '@mui/material@6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
'@mui/core-downloads-tracker': 6.0.0-dev.240424162023-9968b4889d
- '@mui/system': 6.0.0-beta.1(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ '@mui/system': 6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
'@mui/types': 7.2.15(@types/react@18.3.1)
- '@mui/utils': 6.0.0-beta.1(@types/react@18.3.1)(react@18.2.0)
+ '@mui/utils': 6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)
'@popperjs/core': 2.11.8
'@types/react-transition-group': 4.4.10
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
react-is: 18.3.1
- react-transition-group: 4.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
optionalDependencies:
- '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.2.0)
- '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ '@emotion/react': 11.13.0(@types/react@18.3.1)(react@18.3.1)
+ '@emotion/styled': 11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
'@types/react': 18.3.1
- '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/18056e14de623cab995db4bc01c879467aeb273e(encoding@0.1.13)':
+ '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/4f0c10d0b40327e672bdb28ca05651d037b47a08(encoding@0.1.13)':
dependencies:
'@googleapis/sheets': 8.0.0(encoding@0.1.13)
'@netlify/functions': 2.8.1
'@slack/bolt': 3.19.0
execa: 9.3.0
- google-auth-library: 9.11.0(encoding@0.1.13)
+ google-auth-library: 9.13.0(encoding@0.1.13)
transitivePeerDependencies:
- bufferutil
- debug
@@ -10222,53 +10308,53 @@ snapshots:
- supports-color
- utf-8-validate
- '@mui/private-theming@5.16.1(@types/react@18.3.1)(react@18.2.0)':
+ '@mui/private-theming@5.16.1(@types/react@18.3.1)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@mui/utils': 5.16.6(@types/react@18.3.1)(react@18.2.0)
+ '@mui/utils': 5.16.6(@types/react@18.3.1)(react@18.3.1)
prop-types: 15.8.1
- react: 18.2.0
+ react: 18.3.1
optionalDependencies:
'@types/react': 18.3.1
- '@mui/private-theming@6.0.0-beta.1(@types/react@18.3.1)(react@18.2.0)':
+ '@mui/private-theming@6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@mui/utils': 6.0.0-beta.1(@types/react@18.3.1)(react@18.2.0)
+ '@mui/utils': 6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)
prop-types: 15.8.1
- react: 18.2.0
+ react: 18.3.1
optionalDependencies:
'@types/react': 18.3.1
- '@mui/styled-engine@5.16.1(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(react@18.2.0)':
+ '@mui/styled-engine@5.16.1(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@emotion/cache': 11.11.0
+ '@emotion/cache': 11.13.1
csstype: 3.1.3
prop-types: 15.8.1
- react: 18.2.0
+ react: 18.3.1
optionalDependencies:
- '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.2.0)
- '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ '@emotion/react': 11.13.0(@types/react@18.3.1)(react@18.3.1)
+ '@emotion/styled': 11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
- '@mui/styled-engine@6.0.0-beta.1(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(react@18.2.0)':
+ '@mui/styled-engine@6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@emotion/cache': 11.11.0
+ '@emotion/cache': 11.13.1
csstype: 3.1.3
prop-types: 15.8.1
- react: 18.2.0
+ react: 18.3.1
optionalDependencies:
- '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.2.0)
- '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ '@emotion/react': 11.13.0(@types/react@18.3.1)(react@18.3.1)
+ '@emotion/styled': 11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
- '@mui/styles@6.0.0-beta.2(@types/react@18.3.1)(react@18.2.0)':
+ '@mui/styles@6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@emotion/hash': 0.9.1
- '@mui/private-theming': 6.0.0-beta.1(@types/react@18.3.1)(react@18.2.0)
+ '@emotion/hash': 0.9.2
+ '@mui/private-theming': 6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)
'@mui/types': 7.2.15(@types/react@18.3.1)
- '@mui/utils': 6.0.0-beta.1(@types/react@18.3.1)(react@18.2.0)
+ '@mui/utils': 6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)
clsx: 2.1.1
csstype: 3.1.3
hoist-non-react-statics: 3.3.2
@@ -10281,75 +10367,76 @@ snapshots:
jss-plugin-rule-value-function: 10.10.0
jss-plugin-vendor-prefixer: 10.10.0
prop-types: 15.8.1
- react: 18.2.0
+ react: 18.3.1
optionalDependencies:
'@types/react': 18.3.1
- '@mui/system@5.16.1(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)':
+ '@mui/system@5.16.1(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@mui/private-theming': 5.16.1(@types/react@18.3.1)(react@18.2.0)
- '@mui/styled-engine': 5.16.1(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(react@18.2.0)
+ '@mui/private-theming': 5.16.1(@types/react@18.3.1)(react@18.3.1)
+ '@mui/styled-engine': 5.16.1(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1)
'@mui/types': 7.2.15(@types/react@18.3.1)
- '@mui/utils': 5.16.6(@types/react@18.3.1)(react@18.2.0)
+ '@mui/utils': 5.16.6(@types/react@18.3.1)(react@18.3.1)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
- react: 18.2.0
+ react: 18.3.1
optionalDependencies:
- '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.2.0)
- '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ '@emotion/react': 11.13.0(@types/react@18.3.1)(react@18.3.1)
+ '@emotion/styled': 11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
'@types/react': 18.3.1
- '@mui/system@6.0.0-beta.1(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)':
+ '@mui/system@6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@mui/private-theming': 6.0.0-beta.1(@types/react@18.3.1)(react@18.2.0)
- '@mui/styled-engine': 6.0.0-beta.1(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0))(react@18.2.0)
+ '@mui/private-theming': 6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)
+ '@mui/styled-engine': 6.0.0-beta.4(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1)
'@mui/types': 7.2.15(@types/react@18.3.1)
- '@mui/utils': 6.0.0-beta.1(@types/react@18.3.1)(react@18.2.0)
+ '@mui/utils': 6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
- react: 18.2.0
+ react: 18.3.1
optionalDependencies:
- '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.2.0)
- '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.2.0))(@types/react@18.3.1)(react@18.2.0)
+ '@emotion/react': 11.13.0(@types/react@18.3.1)(react@18.3.1)
+ '@emotion/styled': 11.11.5(@emotion/react@11.13.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
'@types/react': 18.3.1
'@mui/types@7.2.15(@types/react@18.3.1)':
optionalDependencies:
'@types/react': 18.3.1
- '@mui/utils@5.16.6(@types/react@18.3.1)(react@18.2.0)':
+ '@mui/utils@5.16.6(@types/react@18.3.1)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
'@mui/types': 7.2.15(@types/react@18.3.1)
'@types/prop-types': 15.7.12
clsx: 2.1.1
prop-types: 15.8.1
- react: 18.2.0
+ react: 18.3.1
react-is: 18.3.1
optionalDependencies:
'@types/react': 18.3.1
- '@mui/utils@6.0.0-beta.0(@types/react@18.3.1)(react@18.2.0)':
+ '@mui/utils@6.0.0-beta.0(@types/react@18.3.1)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
'@types/prop-types': 15.7.12
prop-types: 15.8.1
- react: 18.2.0
+ react: 18.3.1
react-is: 18.3.1
optionalDependencies:
'@types/react': 18.3.1
- '@mui/utils@6.0.0-beta.1(@types/react@18.3.1)(react@18.2.0)':
+ '@mui/utils@6.0.0-beta.4(@types/react@18.3.1)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
+ '@mui/types': 7.2.15(@types/react@18.3.1)
'@types/prop-types': 15.7.12
clsx: 2.1.1
prop-types: 15.8.1
- react: 18.2.0
+ react: 18.3.1
react-is: 18.3.1
optionalDependencies:
'@types/react': 18.3.1
@@ -10836,37 +10923,37 @@ snapshots:
'@popperjs/core@2.11.8': {}
- '@react-spring/animated@9.7.4(react@18.2.0)':
+ '@react-spring/animated@9.7.4(react@18.3.1)':
dependencies:
- '@react-spring/shared': 9.7.4(react@18.2.0)
+ '@react-spring/shared': 9.7.4(react@18.3.1)
'@react-spring/types': 9.7.4
- react: 18.2.0
+ react: 18.3.1
- '@react-spring/core@9.7.4(react@18.2.0)':
+ '@react-spring/core@9.7.4(react@18.3.1)':
dependencies:
- '@react-spring/animated': 9.7.4(react@18.2.0)
- '@react-spring/shared': 9.7.4(react@18.2.0)
+ '@react-spring/animated': 9.7.4(react@18.3.1)
+ '@react-spring/shared': 9.7.4(react@18.3.1)
'@react-spring/types': 9.7.4
- react: 18.2.0
+ react: 18.3.1
'@react-spring/rafz@9.7.4': {}
- '@react-spring/shared@9.7.4(react@18.2.0)':
+ '@react-spring/shared@9.7.4(react@18.3.1)':
dependencies:
'@react-spring/rafz': 9.7.4
'@react-spring/types': 9.7.4
- react: 18.2.0
+ react: 18.3.1
'@react-spring/types@9.7.4': {}
- '@react-spring/web@9.7.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ '@react-spring/web@9.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
- '@react-spring/animated': 9.7.4(react@18.2.0)
- '@react-spring/core': 9.7.4(react@18.2.0)
- '@react-spring/shared': 9.7.4(react@18.2.0)
+ '@react-spring/animated': 9.7.4(react@18.3.1)
+ '@react-spring/core': 9.7.4(react@18.3.1)
+ '@react-spring/shared': 9.7.4(react@18.3.1)
'@react-spring/types': 9.7.4
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
'@remix-run/router@1.16.1': {}
@@ -11018,7 +11105,7 @@ snapshots:
'@swc/counter': 0.1.3
tslib: 2.6.2
- '@testing-library/dom@10.3.1':
+ '@testing-library/dom@10.4.0':
dependencies:
'@babel/code-frame': 7.24.7
'@babel/runtime': 7.25.0
@@ -11029,29 +11116,29 @@ snapshots:
lz-string: 1.5.0
pretty-format: 27.5.1
- '@testing-library/react@15.0.7(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ '@testing-library/react@15.0.7(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@testing-library/dom': 10.3.1
+ '@testing-library/dom': 10.4.0
'@types/react-dom': 18.3.0
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.1
- '@testing-library/react@16.0.0(@testing-library/dom@10.3.1)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ '@testing-library/react@16.0.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
- '@testing-library/dom': 10.3.1
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ '@testing-library/dom': 10.4.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.1
'@types/react-dom': 18.3.0
- '@testing-library/user-event@14.5.2(@testing-library/dom@10.3.1)':
+ '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)':
dependencies:
- '@testing-library/dom': 10.3.1
+ '@testing-library/dom': 10.4.0
'@tootallnate/once@2.0.0': {}
@@ -11176,7 +11263,7 @@ snapshots:
dependencies:
'@types/react': 18.3.1
- '@types/react-is@18.2.4':
+ '@types/react-is@18.3.0':
dependencies:
'@types/react': 18.3.1
@@ -11230,7 +11317,7 @@ snapshots:
'@typescript-eslint/eslint-plugin@7.8.0(@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)':
dependencies:
- '@eslint-community/regexpp': 4.10.0
+ '@eslint-community/regexpp': 4.11.0
'@typescript-eslint/parser': 7.8.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/scope-manager': 7.8.0
'@typescript-eslint/type-utils': 7.8.0(eslint@8.57.0)(typescript@5.4.5)
@@ -11248,10 +11335,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/experimental-utils@5.62.0(eslint@8.57.0)(typescript@5.4.5)':
+ '@typescript-eslint/experimental-utils@5.62.0(eslint@9.8.0)(typescript@5.4.5)':
dependencies:
- '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.4.5)
- eslint: 8.57.0
+ '@typescript-eslint/utils': 5.62.0(eslint@9.8.0)(typescript@5.4.5)
+ eslint: 9.8.0
transitivePeerDependencies:
- supports-color
- typescript
@@ -11269,6 +11356,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/parser@7.8.0(eslint@9.8.0)(typescript@5.4.5)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 7.8.0
+ '@typescript-eslint/types': 7.8.0
+ '@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5)
+ '@typescript-eslint/visitor-keys': 7.8.0
+ debug: 4.3.5(supports-color@8.1.1)
+ eslint: 9.8.0
+ optionalDependencies:
+ typescript: 5.4.5
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/scope-manager@5.62.0':
dependencies:
'@typescript-eslint/types': 5.62.0
@@ -11324,15 +11424,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.4.5)':
+ '@typescript-eslint/utils@5.62.0(eslint@9.8.0)(typescript@5.4.5)':
dependencies:
- '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0)
'@types/json-schema': 7.0.15
'@types/semver': 7.5.8
'@typescript-eslint/scope-manager': 5.62.0
'@typescript-eslint/types': 5.62.0
'@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.5)
- eslint: 8.57.0
+ eslint: 9.8.0
eslint-scope: 5.1.1
semver: 7.6.0
transitivePeerDependencies:
@@ -11485,17 +11585,17 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
- acorn-import-assertions@1.9.0(acorn@8.11.3):
+ acorn-import-assertions@1.9.0(acorn@8.12.1):
dependencies:
- acorn: 8.11.3
+ acorn: 8.12.1
- acorn-jsx@5.3.2(acorn@8.11.3):
+ acorn-jsx@5.3.2(acorn@8.12.1):
dependencies:
- acorn: 8.11.3
+ acorn: 8.12.1
acorn-walk@8.3.2: {}
- acorn@8.11.3: {}
+ acorn@8.12.1: {}
add-stream@1.0.0: {}
@@ -13134,6 +13234,11 @@ snapshots:
esrecurse: 4.3.0
estraverse: 5.3.0
+ eslint-scope@8.0.2:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
eslint-utils@3.0.0(eslint@8.57.0):
dependencies:
eslint: 8.57.0
@@ -13143,10 +13248,12 @@ snapshots:
eslint-visitor-keys@3.4.3: {}
+ eslint-visitor-keys@4.0.0: {}
+
eslint@8.57.0:
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
- '@eslint-community/regexpp': 4.10.0
+ '@eslint-community/regexpp': 4.11.0
'@eslint/eslintrc': 2.1.4
'@eslint/js': 8.57.0
'@humanwhocodes/config-array': 0.11.14
@@ -13186,10 +13293,55 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ eslint@9.8.0:
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0)
+ '@eslint-community/regexpp': 4.11.0
+ '@eslint/config-array': 0.17.1
+ '@eslint/eslintrc': 3.1.0
+ '@eslint/js': 9.8.0
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.3.0
+ '@nodelib/fs.walk': 1.2.8
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.3
+ debug: 4.3.5(supports-color@8.1.1)
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.0.2
+ eslint-visitor-keys: 4.0.0
+ espree: 10.1.0
+ esquery: 1.5.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.1
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ is-path-inside: 3.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ levn: 0.4.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.3
+ strip-ansi: 6.0.1
+ text-table: 0.2.0
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.1.0:
+ dependencies:
+ acorn: 8.12.1
+ acorn-jsx: 5.3.2(acorn@8.12.1)
+ eslint-visitor-keys: 4.0.0
+
espree@9.6.1:
dependencies:
- acorn: 8.11.3
- acorn-jsx: 5.3.2(acorn@8.11.3)
+ acorn: 8.12.1
+ acorn-jsx: 5.3.2(acorn@8.12.1)
eslint-visitor-keys: 3.4.3
esprima@4.0.1: {}
@@ -13515,13 +13667,13 @@ snapshots:
fraction.js@4.3.7: {}
- framer-motion@11.2.10(@emotion/is-prop-valid@1.2.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ framer-motion@11.2.10(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
tslib: 2.6.2
optionalDependencies:
'@emotion/is-prop-valid': 1.2.2
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
fresh@0.5.2: {}
@@ -13742,6 +13894,8 @@ snapshots:
dependencies:
type-fest: 0.20.2
+ globals@14.0.0: {}
+
globalthis@1.0.3:
dependencies:
define-properties: 1.2.1
@@ -13783,7 +13937,7 @@ snapshots:
globjoin@0.1.4: {}
- google-auth-library@9.11.0(encoding@0.1.13):
+ google-auth-library@9.13.0(encoding@0.1.13):
dependencies:
base64-js: 1.5.1
ecdsa-sig-formatter: 1.0.11
@@ -13799,7 +13953,7 @@ snapshots:
dependencies:
extend: 3.0.2
gaxios: 6.5.0(encoding@0.1.13)
- google-auth-library: 9.11.0(encoding@0.1.13)
+ google-auth-library: 9.13.0(encoding@0.1.13)
qs: 6.12.1
url-template: 2.0.8
uuid: 9.0.1
@@ -15257,7 +15411,7 @@ snapshots:
nested-error-stacks@2.1.1: {}
- next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(@playwright/test@1.45.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@next/env': 14.2.5
'@swc/helpers': 0.5.5
@@ -15265,9 +15419,9 @@ snapshots:
caniuse-lite: 1.0.30001646
graceful-fs: 4.2.11
postcss: 8.4.31
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
- styled-jsx: 5.1.1(@babel/core@7.25.2)(babel-plugin-macros@3.1.0)(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ styled-jsx: 5.1.1(@babel/core@7.25.2)(babel-plugin-macros@3.1.0)(react@18.3.1)
optionalDependencies:
'@next/swc-darwin-arm64': 14.2.5
'@next/swc-darwin-x64': 14.2.5
@@ -16170,18 +16324,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
- react-dom@18.2.0(react@18.2.0):
+ react-dom@18.3.1(react@18.3.1):
dependencies:
loose-envify: 1.4.0
- react: 18.2.0
- scheduler: 0.23.0
+ react: 18.3.1
+ scheduler: 0.23.2
- react-draggable@4.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ react-draggable@4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
clsx: 1.2.1
prop-types: 15.8.1
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
react-is@16.13.1: {}
@@ -16189,39 +16343,39 @@ snapshots:
react-is@18.3.1: {}
- react-router-dom@6.23.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ react-router-dom@6.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@remix-run/router': 1.16.1
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
- react-router: 6.23.1(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-router: 6.23.1(react@18.3.1)
- react-router@6.23.1(react@18.2.0):
+ react-router@6.23.1(react@18.3.1):
dependencies:
'@remix-run/router': 1.16.1
- react: 18.2.0
+ react: 18.3.1
- react-runner@1.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ react-runner@1.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
sucrase: 3.35.0
- react-simple-code-editor@0.13.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ react-simple-code-editor@0.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
- react-transition-group@4.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.25.0
dom-helpers: 5.2.1
loose-envify: 1.4.0
prop-types: 15.8.1
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
- react@18.2.0:
+ react@18.3.1:
dependencies:
loose-envify: 1.4.0
@@ -16503,7 +16657,7 @@ snapshots:
dependencies:
xmlchars: 2.2.0
- scheduler@0.23.0:
+ scheduler@0.23.2:
dependencies:
loose-envify: 1.4.0
@@ -16929,7 +17083,7 @@ snapshots:
minimist: 1.2.8
through: 2.3.8
- styled-components@6.1.12(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ styled-components@6.1.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@emotion/is-prop-valid': 1.2.2
'@emotion/unitless': 0.8.1
@@ -16937,16 +17091,16 @@ snapshots:
css-to-react-native: 3.2.0
csstype: 3.1.3
postcss: 8.4.38
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
shallowequal: 1.1.0
stylis: 4.3.2
tslib: 2.6.2
- styled-jsx@5.1.1(@babel/core@7.25.2)(babel-plugin-macros@3.1.0)(react@18.2.0):
+ styled-jsx@5.1.1(@babel/core@7.25.2)(babel-plugin-macros@3.1.0)(react@18.3.1):
dependencies:
client-only: 0.0.1
- react: 18.2.0
+ react: 18.3.1
optionalDependencies:
'@babel/core': 7.25.2
babel-plugin-macros: 3.1.0
@@ -17137,7 +17291,7 @@ snapshots:
terser@5.31.0:
dependencies:
'@jridgewell/source-map': 0.3.6
- acorn: 8.11.3
+ acorn: 8.12.1
commander: 2.20.3
source-map-support: 0.5.21
@@ -17499,7 +17653,7 @@ snapshots:
webpack-bundle-analyzer@4.10.2:
dependencies:
'@discoveryjs/json-ext': 0.5.7
- acorn: 8.11.3
+ acorn: 8.12.1
acorn-walk: 8.3.2
commander: 7.2.0
debounce: 1.2.1
@@ -17552,8 +17706,8 @@ snapshots:
'@webassemblyjs/ast': 1.12.1
'@webassemblyjs/wasm-edit': 1.12.1
'@webassemblyjs/wasm-parser': 1.12.1
- acorn: 8.11.3
- acorn-import-assertions: 1.9.0(acorn@8.11.3)
+ acorn: 8.12.1
+ acorn-import-assertions: 1.9.0(acorn@8.12.1)
browserslist: 4.23.2
chrome-trace-event: 1.0.3
enhanced-resolve: 5.16.0
@@ -17585,8 +17739,8 @@ snapshots:
'@webassemblyjs/ast': 1.12.1
'@webassemblyjs/wasm-edit': 1.12.1
'@webassemblyjs/wasm-parser': 1.12.1
- acorn: 8.11.3
- acorn-import-assertions: 1.9.0(acorn@8.11.3)
+ acorn: 8.12.1
+ acorn-import-assertions: 1.9.0(acorn@8.12.1)
browserslist: 4.23.2
chrome-trace-event: 1.0.3
enhanced-resolve: 5.16.0
diff --git a/renovate.json b/renovate.json
index bc24249c0b..623022d4f9 100644
--- a/renovate.json
+++ b/renovate.json
@@ -68,7 +68,14 @@
},
{
"groupName": "React",
- "matchPackageNames": ["react", "react-dom", "react-is", "@types/react", "@types/react-dom"]
+ "matchPackageNames": [
+ "react",
+ "react-dom",
+ "react-is",
+ "@types/react",
+ "@types/react-dom",
+ "@types/react-is"
+ ]
},
{
"groupName": "typescript-eslint",
diff --git a/test/package.json b/test/package.json
index 383ad1cea3..d2b5d11e28 100644
--- a/test/package.json
+++ b/test/package.json
@@ -10,14 +10,14 @@
"@base_ui/react": "workspace:*",
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.4",
- "@mui/internal-test-utils": "1.0.5",
+ "@mui/internal-test-utils": "1.0.6",
"@mui/joy": "5.0.0-beta.48",
- "@mui/material": "6.0.0-beta.2",
+ "@mui/material": "6.0.0-beta.4",
"@playwright/test": "1.45.3",
"@testing-library/dom": "^10.1.0",
"@types/chai": "^4.3.16",
"@types/react": "^18.3.1",
- "@types/react-is": "^18.2.4",
+ "@types/react-is": "^18.3.0",
"@types/sinon": "^17.0.3",
"chai": "^4.4.1",
"docs": "workspace:^",
@@ -28,9 +28,9 @@
"lodash": "^4.17.21",
"playwright": "^1.45.3",
"prop-types": "^15.8.1",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-is": "^18.2.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-is": "^18.3.1",
"react-router-dom": "^6.23.1",
"sinon": "^17.0.1",
"styled-components": "^6.1.12",
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 82acd61f79..9a3e6cab30 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -24,5 +24,5 @@
"docs/*": ["./node_modules/@mui/monorepo/docs/*"]
}
},
- "exclude": ["**/.*/", "**/build", "**/node_modules", "docs/export"]
+ "exclude": ["**/.*/", "**/build", "**/build-tests", "**/node_modules", "docs/export"]
}