diff --git a/docs/src/components/home/Hero.tsx b/docs/src/components/home/Hero.tsx
index 98f8d74f8cd516..cb50c3921d9a99 100644
--- a/docs/src/components/home/Hero.tsx
+++ b/docs/src/components/home/Hero.tsx
@@ -34,7 +34,7 @@ const TaskCard = dynamic(() => import('../showcase/TaskCard'), {
});
const PlayerCard = dynamic(() => import('../showcase/PlayerCard'), {
ssr: false,
- loading: createLoading({ width: 400, height: 240 }),
+ loading: createLoading({ width: 400, height: 134 }),
});
const ThemeToggleButton = dynamic(() => import('../showcase/ThemeToggleButton'), {
ssr: false,
@@ -42,19 +42,19 @@ const ThemeToggleButton = dynamic(() => import('../showcase/ThemeToggleButton'),
});
const ThemeChip = dynamic(() => import('../showcase/ThemeChip'), {
ssr: false,
- loading: createLoading({ width: 400, height: 24 }),
+ loading: createLoading({ width: 360, height: 24 }),
});
const ThemeTimeline = dynamic(() => import('../showcase/ThemeTimeline'), {
ssr: false,
- loading: createLoading({ width: 400, height: 180 }),
+ loading: createLoading({ width: 400, height: 175 }),
});
const FolderTable = dynamic(() => import('../showcase/FolderTable'), {
ssr: false,
- loading: createLoading({ width: 360, height: 210 }),
+ loading: createLoading({ width: 400, height: 294 }),
});
const ThemeDatePicker = dynamic(() => import('../showcase/ThemeDatePicker'), {
ssr: false,
- loading: createLoading({ width: 360, height: 260 }),
+ loading: createLoading({ width: 360, height: 245 }),
});
const ThemeTabs = dynamic(() => import('../showcase/ThemeTabs'), {
ssr: false,
@@ -66,11 +66,11 @@ const ThemeSlider = dynamic(() => import('../showcase/ThemeSlider'), {
});
const ThemeAccordion = dynamic(() => import('../showcase/ThemeAccordion'), {
ssr: false,
- loading: createLoading({ width: { md: 360, xl: 400 }, height: 231 }),
+ loading: createLoading({ width: 360, height: 252 }),
});
const NotificationCard = dynamic(() => import('../showcase/NotificationCard'), {
ssr: false,
- loading: createLoading({ width: { md: 360, xl: 400 }, height: 103 }),
+ loading: createLoading({ width: 360, height: 98 }),
});
export default function Hero() {
@@ -80,12 +80,12 @@ export default function Hero() {
-
+
+
Move faster
with intuitive React UI tools
-
+
MUI offers a comprehensive suite of free UI tools to help you ship new features faster.
Start with Material UI, our fully-loaded component library, or bring your own design
system to our production-ready components.
@@ -115,7 +115,7 @@ export default function Hero() {
right={
{isMdUp && (
- .MuiPaper-root': { maxWidth: 'none' } }}>
+ .MuiPaper-root': { maxWidth: 'none' } }}>
@@ -124,7 +124,11 @@ export default function Hero() {
)}
{isMdUp && (
- .MuiPaper-root': { maxWidth: 'none' } }}>
+ .MuiPaper-root': { maxWidth: 'none' } }}
+ >
diff --git a/docs/src/components/home/MaterialDesignDemo.tsx b/docs/src/components/home/MaterialDesignDemo.tsx
index 2e6a6d515b9782..ec6f9a3d8f516b 100644
--- a/docs/src/components/home/MaterialDesignDemo.tsx
+++ b/docs/src/components/home/MaterialDesignDemo.tsx
@@ -1,56 +1,62 @@
import * as React from 'react';
-import MuiAvatar from '@mui/material/Avatar';
import MuiChip from '@mui/material/Chip';
+import MuiCardMedia from '@mui/material/CardMedia';
import MuiCard, { CardProps } from '@mui/material/Card';
import MuiSwitch from '@mui/material/Switch';
import MuiTypography from '@mui/material/Typography';
import MuiStack from '@mui/material/Stack';
+import MuiRating from '@mui/material/Rating';
import { withPointer } from 'docs/src/components/home/ElementPointer';
-export const componentCode = `
-
-
-
-
- Lucas Smith
+export const componentCode = `
+
+
+
+
+ Yosemite National Park, California, USA
+
+
-
- Scranton, PA, United States
-
-
-
+
+
`;
-const Avatar = withPointer(MuiAvatar, { id: 'avatar', name: 'Avatar' });
-const Chip = withPointer(MuiChip, { id: 'chip', name: 'Chip' });
const Card = withPointer(MuiCard, { id: 'card', name: 'Card' });
-const Switch = withPointer(MuiSwitch, { id: 'switch', name: 'Switch' });
-const Typography = withPointer(MuiTypography, { id: 'typography', name: 'Typography' });
-const Typography2 = withPointer(MuiTypography, { id: 'typography2', name: 'Typography' });
+const CardMedia = withPointer(MuiCardMedia, { id: 'cardmedia', name: 'CardMedia' });
const Stack = withPointer(MuiStack, { id: 'stack', name: 'Stack' });
const Stack2 = withPointer(MuiStack, { id: 'stack2', name: 'Stack' });
+const Stack3 = withPointer(MuiStack, { id: 'stack3', name: 'Stack' });
+const Typography = withPointer(MuiTypography, { id: 'typography', name: 'Typography' });
+const Chip = withPointer(MuiChip, { id: 'chip', name: 'Chip' });
+const Rating = withPointer(MuiRating, { id: 'rating', name: 'Rating' });
+const Switch = withPointer(MuiSwitch, { id: 'switch', name: 'Switch' });
export default function MaterialDesignDemo(props: CardProps) {
- const [active, setActive] = React.useState(false);
+ const [active, setActive] = React.useState(true);
return (
-
-
-
-
-
- Lucas Smith
+
+
+
+
+ Yosemite National Park, California, USA
+
-
-
- Scranton, PA, United States
-
-
+
+
+
+
Every component you need is ready for production
}
diff --git a/docs/src/components/home/ShowcaseContainer.tsx b/docs/src/components/home/ShowcaseContainer.tsx
index ee337eaa13a59c..02eaf8fefbaa75 100644
--- a/docs/src/components/home/ShowcaseContainer.tsx
+++ b/docs/src/components/home/ShowcaseContainer.tsx
@@ -37,7 +37,7 @@ export default function ShowcaseContainer({
position: 'relative',
justifyContent: 'center',
alignItems: 'center',
- minHeight: 240,
+ minHeight: 220,
p: 2,
}}
>
diff --git a/docs/src/components/productMaterial/MaterialStyling.tsx b/docs/src/components/productMaterial/MaterialStyling.tsx
index 47ae8ae3cc1c36..b26d938a3fb7d8 100644
--- a/docs/src/components/productMaterial/MaterialStyling.tsx
+++ b/docs/src/components/productMaterial/MaterialStyling.tsx
@@ -171,17 +171,15 @@ export default function MaterialStyling() {
-
-
- Rapidly add and tweak any styles using CSS utilities
-
- }
- description="CSS utilities allow you to move faster and make for a smooth developer experience when styling any component."
- />
-
+
+ Rapidly add and tweak any styles using CSS utilities
+
+ }
+ description="CSS utilities allow you to move faster and make for a smooth developer experience when styling any component."
+ />
setIndex(0)}>
-
-
-
- Build your design system just as you want it to be
-
- }
- description="Start quickly with Material Design or use the advanced theming feature to easily tailor the components to your needs."
- />
-
+
+ Build your design system just as you want it to be
+
+ }
+ description="Start quickly with Material Design or use the advanced theming feature to easily tailor the components to your needs."
+ />
setCustomized(true)}>
-
Date: Fri, 2 Feb 2024 08:20:14 +0100
Subject: [PATCH 023/120] [base-ui][Menu] Use Popup instead of Popper (#40731)
---
.../menu/MenuIntroduction/css/index.js | 54 +++++-
.../menu/MenuIntroduction/css/index.tsx | 54 +++++-
.../menu/MenuIntroduction/system/index.js | 52 ++++-
.../menu/MenuIntroduction/system/index.tsx | 52 ++++-
.../MenuIntroduction/system/index.tsx.preview | 2 +-
.../menu/MenuIntroduction/tailwind/index.js | 33 +++-
.../menu/MenuIntroduction/tailwind/index.tsx | 34 +++-
.../base/components/menu/MenuTransitions.js | 183 ++++++++++++++++++
.../base/components/menu/MenuTransitions.tsx | 181 +++++++++++++++++
.../menu/MenuTransitions.tsx.preview | 10 +
docs/data/base/components/menu/menu.md | 7 +
docs/pages/base-ui/api/popup.json | 3 +-
.../api-docs-base/popup/popup.json | 5 +-
.../mui-base/src/Dropdown/Dropdown.test.tsx | 54 ++++--
packages/mui-base/src/Menu/Menu.test.tsx | 103 ++++++----
packages/mui-base/src/Menu/Menu.tsx | 6 +-
packages/mui-base/src/Menu/Menu.types.ts | 9 +-
packages/mui-base/src/Select/Select.test.tsx | 8 +-
.../src/Unstable_Popup/Popup.test.tsx | 6 +-
.../mui-base/src/Unstable_Popup/Popup.tsx | 17 +-
.../src/Unstable_Popup/Popup.types.ts | 18 +-
.../src/useTransition/useTransitionTrigger.ts | 2 -
packages/test-utils/src/flushMicrotasks.ts | 8 +
packages/test-utils/src/index.ts | 1 +
24 files changed, 791 insertions(+), 111 deletions(-)
create mode 100644 docs/data/base/components/menu/MenuTransitions.js
create mode 100644 docs/data/base/components/menu/MenuTransitions.tsx
create mode 100644 docs/data/base/components/menu/MenuTransitions.tsx.preview
create mode 100644 packages/test-utils/src/flushMicrotasks.ts
diff --git a/docs/data/base/components/menu/MenuIntroduction/css/index.js b/docs/data/base/components/menu/MenuIntroduction/css/index.js
index 8dd3f79cbab282..8642947eea37c1 100644
--- a/docs/data/base/components/menu/MenuIntroduction/css/index.js
+++ b/docs/data/base/components/menu/MenuIntroduction/css/index.js
@@ -1,9 +1,12 @@
import * as React from 'react';
+import PropTypes from 'prop-types';
import { Menu } from '@mui/base/Menu';
import { MenuItem, menuItemClasses } from '@mui/base/MenuItem';
import { MenuButton } from '@mui/base/MenuButton';
import { Dropdown } from '@mui/base/Dropdown';
import { useTheme } from '@mui/system';
+import { CssTransition } from '@mui/base/Transitions';
+import { PopupContext } from '@mui/base/Unstable_Popup';
export default function MenuIntroduction() {
const createHandleMenuClick = (menuItem) => {
@@ -18,6 +21,9 @@ export default function MenuIntroduction() {
+
+
+ );
+});
+
+Listbox.propTypes = {
+ ownerState: PropTypes.object.isRequired,
+};
+
const cyan = {
50: '#E9F8FC',
100: '#BDEBF4',
@@ -97,6 +130,26 @@ function Styles() {
border: 1px solid ${isDarkMode ? grey[700] : grey[200]};
color: ${isDarkMode ? grey[300] : grey[900]};
box-shadow: 0px 4px 30px ${isDarkMode ? grey[900] : grey[200]};
+
+ .closed & {
+ opacity: 0;
+ transform: scale(0.95, 0.8);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+ }
+
+ .open & {
+ opacity: 1;
+ transform: scale(1, 1);
+ transition: opacity 100ms ease-out, transform 100ms cubic-bezier(0.43, 0.29, 0.37, 1.48);
+ }
+
+ .placement-top & {
+ transform-origin: bottom;
+ }
+
+ .placement-bottom & {
+ transform-origin: top;
+ }
}
.CustomMenuIntroduction--item {
@@ -151,7 +204,6 @@ function Styles() {
}
}
-
.CustomMenuIntroduction {
z-index: 1;
}
diff --git a/docs/data/base/components/menu/MenuIntroduction/css/index.tsx b/docs/data/base/components/menu/MenuIntroduction/css/index.tsx
index e899d9d500df1d..cf0951e09cd2d1 100644
--- a/docs/data/base/components/menu/MenuIntroduction/css/index.tsx
+++ b/docs/data/base/components/menu/MenuIntroduction/css/index.tsx
@@ -1,9 +1,11 @@
import * as React from 'react';
-import { Menu } from '@mui/base/Menu';
+import { Menu, MenuListboxSlotProps } from '@mui/base/Menu';
import { MenuItem, menuItemClasses } from '@mui/base/MenuItem';
import { MenuButton } from '@mui/base/MenuButton';
import { Dropdown } from '@mui/base/Dropdown';
import { useTheme } from '@mui/system';
+import { CssTransition } from '@mui/base/Transitions';
+import { PopupContext } from '@mui/base/Unstable_Popup';
export default function MenuIntroduction() {
const createHandleMenuClick = (menuItem: string) => {
@@ -18,6 +20,9 @@ export default function MenuIntroduction() {
,
+) {
+ const { ownerState, ...other } = props;
+ const popupContext = React.useContext(PopupContext);
+
+ if (popupContext == null) {
+ throw new Error(
+ 'The `Listbox` component cannot be rendered outside a `Popup` component',
+ );
+ }
+
+ const verticalPlacement = popupContext.placement.split('-')[0];
+
+ return (
+
+
+
+ );
+});
+
const cyan = {
50: '#E9F8FC',
100: '#BDEBF4',
@@ -97,6 +128,26 @@ function Styles() {
border: 1px solid ${isDarkMode ? grey[700] : grey[200]};
color: ${isDarkMode ? grey[300] : grey[900]};
box-shadow: 0px 4px 30px ${isDarkMode ? grey[900] : grey[200]};
+
+ .closed & {
+ opacity: 0;
+ transform: scale(0.95, 0.8);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+ }
+
+ .open & {
+ opacity: 1;
+ transform: scale(1, 1);
+ transition: opacity 100ms ease-out, transform 100ms cubic-bezier(0.43, 0.29, 0.37, 1.48);
+ }
+
+ .placement-top & {
+ transform-origin: bottom;
+ }
+
+ .placement-bottom & {
+ transform-origin: top;
+ }
}
.CustomMenuIntroduction--item {
@@ -151,7 +202,6 @@ function Styles() {
}
}
-
.CustomMenuIntroduction {
z-index: 1;
}
diff --git a/docs/data/base/components/menu/MenuIntroduction/system/index.js b/docs/data/base/components/menu/MenuIntroduction/system/index.js
index 96fd20707e6d0b..ff1f679b61121f 100644
--- a/docs/data/base/components/menu/MenuIntroduction/system/index.js
+++ b/docs/data/base/components/menu/MenuIntroduction/system/index.js
@@ -1,9 +1,12 @@
import * as React from 'react';
+import PropTypes from 'prop-types';
import { Dropdown } from '@mui/base/Dropdown';
import { Menu } from '@mui/base/Menu';
import { MenuButton as BaseMenuButton } from '@mui/base/MenuButton';
import { MenuItem as BaseMenuItem, menuItemClasses } from '@mui/base/MenuItem';
import { styled } from '@mui/system';
+import { CssTransition } from '@mui/base/Transitions';
+import { PopupContext } from '@mui/base/Unstable_Popup';
export default function MenuIntroduction() {
const createHandleMenuClick = (menuItem) => {
@@ -15,7 +18,7 @@ export default function MenuIntroduction() {
return (
My account
-
+
Profile
Language settings
@@ -68,9 +71,56 @@ const Listbox = styled('ul')(
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;
+
+ .closed & {
+ opacity: 0;
+ transform: scale(0.95, 0.8);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+ }
+
+ .open & {
+ opacity: 1;
+ transform: scale(1, 1);
+ transition: opacity 100ms ease-out, transform 100ms cubic-bezier(0.43, 0.29, 0.37, 1.48);
+ }
+
+ .placement-top & {
+ transform-origin: bottom;
+ }
+
+ .placement-bottom & {
+ transform-origin: top;
+ }
`,
);
+const AnimatedListbox = React.forwardRef(function AnimatedListbox(props, ref) {
+ const { ownerState, ...other } = props;
+ const popupContext = React.useContext(PopupContext);
+
+ if (popupContext == null) {
+ throw new Error(
+ 'The `AnimatedListbox` component cannot be rendered outside a `Popup` component',
+ );
+ }
+
+ const verticalPlacement = popupContext.placement.split('-')[0];
+
+ return (
+
+
+
+ );
+});
+
+AnimatedListbox.propTypes = {
+ ownerState: PropTypes.object.isRequired,
+};
+
const MenuItem = styled(BaseMenuItem)(
({ theme }) => `
list-style: none;
diff --git a/docs/data/base/components/menu/MenuIntroduction/system/index.tsx b/docs/data/base/components/menu/MenuIntroduction/system/index.tsx
index ff3420f6caf4fa..77cf182c65a69b 100644
--- a/docs/data/base/components/menu/MenuIntroduction/system/index.tsx
+++ b/docs/data/base/components/menu/MenuIntroduction/system/index.tsx
@@ -1,9 +1,11 @@
import * as React from 'react';
import { Dropdown } from '@mui/base/Dropdown';
-import { Menu } from '@mui/base/Menu';
+import { Menu, MenuListboxSlotProps } from '@mui/base/Menu';
import { MenuButton as BaseMenuButton } from '@mui/base/MenuButton';
import { MenuItem as BaseMenuItem, menuItemClasses } from '@mui/base/MenuItem';
import { styled } from '@mui/system';
+import { CssTransition } from '@mui/base/Transitions';
+import { PopupContext } from '@mui/base/Unstable_Popup';
export default function MenuIntroduction() {
const createHandleMenuClick = (menuItem: string) => {
@@ -15,7 +17,7 @@ export default function MenuIntroduction() {
return (
My account
-
+
Profile
Language settings
@@ -68,9 +70,55 @@ const Listbox = styled('ul')(
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;
+
+ .closed & {
+ opacity: 0;
+ transform: scale(0.95, 0.8);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+ }
+
+ .open & {
+ opacity: 1;
+ transform: scale(1, 1);
+ transition: opacity 100ms ease-out, transform 100ms cubic-bezier(0.43, 0.29, 0.37, 1.48);
+ }
+
+ .placement-top & {
+ transform-origin: bottom;
+ }
+
+ .placement-bottom & {
+ transform-origin: top;
+ }
`,
);
+const AnimatedListbox = React.forwardRef(function AnimatedListbox(
+ props: MenuListboxSlotProps,
+ ref: React.ForwardedRef,
+) {
+ const { ownerState, ...other } = props;
+ const popupContext = React.useContext(PopupContext);
+
+ if (popupContext == null) {
+ throw new Error(
+ 'The `AnimatedListbox` component cannot be rendered outside a `Popup` component',
+ );
+ }
+
+ const verticalPlacement = popupContext.placement.split('-')[0];
+
+ return (
+
+
+
+ );
+});
+
const MenuItem = styled(BaseMenuItem)(
({ theme }) => `
list-style: none;
diff --git a/docs/data/base/components/menu/MenuIntroduction/system/index.tsx.preview b/docs/data/base/components/menu/MenuIntroduction/system/index.tsx.preview
index 7dd86352523212..76021a2873d157 100644
--- a/docs/data/base/components/menu/MenuIntroduction/system/index.tsx.preview
+++ b/docs/data/base/components/menu/MenuIntroduction/system/index.tsx.preview
@@ -1,6 +1,6 @@
My account
-
+
Profile
Language settings
diff --git a/docs/data/base/components/menu/MenuIntroduction/tailwind/index.js b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.js
index 9bdca1841737af..f3271f0bc53524 100644
--- a/docs/data/base/components/menu/MenuIntroduction/tailwind/index.js
+++ b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.js
@@ -6,6 +6,8 @@ import { MenuButton as BaseMenuButton } from '@mui/base/MenuButton';
import { MenuItem as BaseMenuItem } from '@mui/base/MenuItem';
import { Dropdown } from '@mui/base/Dropdown';
import { useTheme } from '@mui/system';
+import { PopupContext } from '@mui/base/Unstable_Popup';
+import { CssTransition } from '@mui/base/Transitions';
function useIsDarkMode() {
const theme = useTheme();
@@ -48,6 +50,9 @@ const Menu = React.forwardRef((props, ref) => {
{
@@ -71,7 +76,7 @@ const Menu = React.forwardRef((props, ref) => {
return {
...resolvedSlotProps,
className: clsx(
- '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',
+ '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 transition-[opacity,transform] [.closed_&]:opacity-0 [.closed_&]:scale-90 [.placement-top_&]:origin-bottom [.placement-bottom_&]:origin-top',
resolvedSlotProps?.className,
),
};
@@ -130,3 +135,29 @@ const MenuItem = React.forwardRef((props, ref) => {
MenuItem.propTypes = {
className: PropTypes.string,
};
+
+const Listbox = React.forwardRef(function Listbox(props, ref) {
+ const { ownerState, ...other } = props;
+ const popupContext = React.useContext(PopupContext);
+
+ if (popupContext == null) {
+ throw new Error(
+ 'The `Listbox` component cannot be rendered outside a `Popup` component',
+ );
+ }
+
+ const verticalPlacement = popupContext.placement.split('-')[0];
+
+ return (
+
+
+
+ );
+});
+
+Listbox.propTypes = {
+ ownerState: PropTypes.object.isRequired,
+};
diff --git a/docs/data/base/components/menu/MenuIntroduction/tailwind/index.tsx b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.tsx
index 1340be36f3a2bc..627a19bacb7d3e 100644
--- a/docs/data/base/components/menu/MenuIntroduction/tailwind/index.tsx
+++ b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.tsx
@@ -1,10 +1,12 @@
import * as React from 'react';
import clsx from 'clsx';
-import { Menu as BaseMenu, MenuProps } from '@mui/base/Menu';
+import { Menu as BaseMenu, MenuListboxSlotProps, MenuProps } from '@mui/base/Menu';
import { MenuButton as BaseMenuButton, MenuButtonProps } from '@mui/base/MenuButton';
import { MenuItem as BaseMenuItem, MenuItemProps } from '@mui/base/MenuItem';
import { Dropdown } from '@mui/base/Dropdown';
import { useTheme } from '@mui/system';
+import { PopupContext } from '@mui/base/Unstable_Popup';
+import { CssTransition } from '@mui/base/Transitions';
function useIsDarkMode() {
const theme = useTheme();
@@ -48,6 +50,9 @@ const Menu = React.forwardRef((props, ref) => {
{
@@ -71,7 +76,7 @@ const Menu = React.forwardRef((props, ref) => {
return {
...resolvedSlotProps,
className: clsx(
- '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',
+ '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 transition-[opacity,transform] [.closed_&]:opacity-0 [.closed_&]:scale-90 [.placement-top_&]:origin-bottom [.placement-bottom_&]:origin-top',
resolvedSlotProps?.className,
),
};
@@ -110,3 +115,28 @@ const MenuItem = React.forwardRef((props, ref) =>
/>
);
});
+
+const Listbox = React.forwardRef(function Listbox(
+ props: MenuListboxSlotProps,
+ ref: React.ForwardedRef,
+) {
+ const { ownerState, ...other } = props;
+ const popupContext = React.useContext(PopupContext);
+
+ if (popupContext == null) {
+ throw new Error(
+ 'The `Listbox` component cannot be rendered outside a `Popup` component',
+ );
+ }
+
+ const verticalPlacement = popupContext.placement.split('-')[0];
+
+ return (
+
+
+
+ );
+});
diff --git a/docs/data/base/components/menu/MenuTransitions.js b/docs/data/base/components/menu/MenuTransitions.js
new file mode 100644
index 00000000000000..983e72793bb145
--- /dev/null
+++ b/docs/data/base/components/menu/MenuTransitions.js
@@ -0,0 +1,183 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { Dropdown } from '@mui/base/Dropdown';
+import { Menu } from '@mui/base/Menu';
+import { MenuButton as BaseMenuButton } from '@mui/base/MenuButton';
+import { MenuItem as BaseMenuItem, menuItemClasses } from '@mui/base/MenuItem';
+import { styled } from '@mui/system';
+import { CssTransition } from '@mui/base/Transitions';
+import { PopupContext } from '@mui/base/Unstable_Popup';
+
+export default function MenuTransitions() {
+ 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 Listbox = styled('ul')(
+ ({ 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: 0px;
+ 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;
+
+ .closed & {
+ opacity: 0;
+ transform: scale(0.95, 0.8);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+ }
+
+ .open & {
+ opacity: 1;
+ transform: scale(1, 1);
+ transition: opacity 100ms ease-out, transform 100ms cubic-bezier(0.43, 0.29, 0.37, 1.48);
+ }
+
+ .placement-top & {
+ transform-origin: bottom;
+ }
+
+ .placement-bottom & {
+ transform-origin: top;
+ }
+ `,
+);
+
+const AnimatedListbox = React.forwardRef(function AnimatedListbox(props, ref) {
+ const { ownerState, ...other } = props;
+ const popupContext = React.useContext(PopupContext);
+
+ if (popupContext == null) {
+ throw new Error(
+ 'The `AnimatedListbox` component cannot be rendered outside a `Popup` component',
+ );
+ }
+
+ const verticalPlacement = popupContext.placement.split('-')[0];
+
+ return (
+
+
+
+ );
+});
+
+AnimatedListbox.propTypes = {
+ ownerState: PropTypes.object.isRequired,
+};
+
+const MenuItem = styled(BaseMenuItem)(
+ ({ theme }) => `
+ list-style: none;
+ padding: 8px;
+ border-radius: 8px;
+ cursor: default;
+ user-select: none;
+
+ &:last-of-type {
+ border-bottom: none;
+ }
+
+ &.${menuItemClasses.focusVisible} {
+ 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} {
+ color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
+ }
+
+ &:hover:not(.${menuItemClasses.disabled}) {
+ background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]};
+ color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
+ }
+ `,
+);
+
+const MenuButton = styled(BaseMenuButton)(
+ ({ 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/MenuTransitions.tsx b/docs/data/base/components/menu/MenuTransitions.tsx
new file mode 100644
index 00000000000000..3ceafd9583e4be
--- /dev/null
+++ b/docs/data/base/components/menu/MenuTransitions.tsx
@@ -0,0 +1,181 @@
+import * as React from 'react';
+import { Dropdown } from '@mui/base/Dropdown';
+import { Menu, MenuListboxSlotProps } from '@mui/base/Menu';
+import { MenuButton as BaseMenuButton } from '@mui/base/MenuButton';
+import { MenuItem as BaseMenuItem, menuItemClasses } from '@mui/base/MenuItem';
+import { styled } from '@mui/system';
+import { CssTransition } from '@mui/base/Transitions';
+import { PopupContext } from '@mui/base/Unstable_Popup';
+
+export default function MenuTransitions() {
+ 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 Listbox = styled('ul')(
+ ({ 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: 0px;
+ 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;
+
+ .closed & {
+ opacity: 0;
+ transform: scale(0.95, 0.8);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+ }
+
+ .open & {
+ opacity: 1;
+ transform: scale(1, 1);
+ transition: opacity 100ms ease-out, transform 100ms cubic-bezier(0.43, 0.29, 0.37, 1.48);
+ }
+
+ .placement-top & {
+ transform-origin: bottom;
+ }
+
+ .placement-bottom & {
+ transform-origin: top;
+ }
+ `,
+);
+
+const AnimatedListbox = React.forwardRef(function AnimatedListbox(
+ props: MenuListboxSlotProps,
+ ref: React.ForwardedRef,
+) {
+ const { ownerState, ...other } = props;
+ const popupContext = React.useContext(PopupContext);
+
+ if (popupContext == null) {
+ throw new Error(
+ 'The `AnimatedListbox` component cannot be rendered outside a `Popup` component',
+ );
+ }
+
+ const verticalPlacement = popupContext.placement.split('-')[0];
+
+ return (
+
+
+
+ );
+});
+
+const MenuItem = styled(BaseMenuItem)(
+ ({ theme }) => `
+ list-style: none;
+ padding: 8px;
+ border-radius: 8px;
+ cursor: default;
+ user-select: none;
+
+ &:last-of-type {
+ border-bottom: none;
+ }
+
+ &.${menuItemClasses.focusVisible} {
+ 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} {
+ color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
+ }
+
+ &:hover:not(.${menuItemClasses.disabled}) {
+ background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]};
+ color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
+ }
+ `,
+);
+
+const MenuButton = styled(BaseMenuButton)(
+ ({ 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/MenuTransitions.tsx.preview b/docs/data/base/components/menu/MenuTransitions.tsx.preview
new file mode 100644
index 00000000000000..76021a2873d157
--- /dev/null
+++ b/docs/data/base/components/menu/MenuTransitions.tsx.preview
@@ -0,0 +1,10 @@
+
+ My account
+
+ Profile
+
+ Language settings
+
+ Log out
+
+
\ No newline at end of file
diff --git a/docs/data/base/components/menu/menu.md b/docs/data/base/components/menu/menu.md
index b85fdaab08ac69..a90790dad30f05 100644
--- a/docs/data/base/components/menu/menu.md
+++ b/docs/data/base/components/menu/menu.md
@@ -102,6 +102,13 @@ The same applies to props specific to custom primitive elements:
slots={{ root: 'ol' }} start={5} />
```
+### Transitions
+
+The Menu component supports the [Transitions API](/base-ui/react-transitions/), so it's possible to animate the appearing and disappearing Listbox.
+To do this, override the Listbox slot of the Menu and wrap it with a transition component ([CssTransition](/base-ui/react-transitions/#css-transition), [CssAnimation](/base-ui/react-transitions/#css-animation), or a custom-built one).
+
+{{"demo": "MenuTransitions.js", "defaultCodeOpen": false}}
+
## Hooks
```jsx
diff --git a/docs/pages/base-ui/api/popup.json b/docs/pages/base-ui/api/popup.json
index 4d809894527b08..d0d07b7879fa80 100644
--- a/docs/pages/base-ui/api/popup.json
+++ b/docs/pages/base-ui/api/popup.json
@@ -42,8 +42,7 @@
"strategy": {
"type": { "name": "enum", "description": "'absolute' | 'fixed'" },
"default": "'absolute'"
- },
- "withTransition": { "type": { "name": "bool" }, "default": "false" }
+ }
},
"name": "Popup",
"imports": [
diff --git a/docs/translations/api-docs-base/popup/popup.json b/docs/translations/api-docs-base/popup/popup.json
index 34ac6f9aa55942..c3e74968524b89 100644
--- a/docs/translations/api-docs-base/popup/popup.json
+++ b/docs/translations/api-docs-base/popup/popup.json
@@ -27,10 +27,7 @@
"slots": {
"description": "The components used for each slot inside the Popup. Either a string to use a HTML element or a component."
},
- "strategy": { "description": "The type of CSS position property to use (absolute or fixed)." },
- "withTransition": {
- "description": "If true
, the popup will not disappear immediately when it needs to be closed but wait until the exit transition has finished. In such a case, a function form of children
must be used and onExited
callback function must be called when the transition or animation finish."
- }
+ "strategy": { "description": "The type of CSS position property to use (absolute or fixed)." }
},
"classDescriptions": {
"open": {
diff --git a/packages/mui-base/src/Dropdown/Dropdown.test.tsx b/packages/mui-base/src/Dropdown/Dropdown.test.tsx
index e09835cc33a723..a8596e735f550f 100644
--- a/packages/mui-base/src/Dropdown/Dropdown.test.tsx
+++ b/packages/mui-base/src/Dropdown/Dropdown.test.tsx
@@ -1,18 +1,33 @@
import * as React from 'react';
import { expect } from 'chai';
-import { act, createRenderer } from '@mui-internal/test-utils';
+import {
+ act,
+ createRenderer,
+ flushMicrotasks,
+ MuiRenderResult,
+ RenderOptions,
+} from '@mui-internal/test-utils';
import { Dropdown } from '@mui/base/Dropdown';
import { DropdownContext } from '@mui/base/useDropdown';
import { MenuButton } from '@mui/base/MenuButton';
import { MenuItem } from '@mui/base/MenuItem';
import { Menu } from '@mui/base/Menu';
import { MenuProvider, useMenu } from '@mui/base/useMenu';
-import { Popper } from '@mui/base/Popper';
+import { Unstable_Popup as Popup } from '@mui/base/Unstable_Popup';
describe(' ', () => {
- const { render } = createRenderer();
-
- it('registers a popup id correctly', () => {
+ 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);
@@ -24,7 +39,7 @@ describe(' ', () => {
return {popupId}
;
}
- const { container } = render(
+ const { container } = await render(
,
@@ -33,7 +48,7 @@ describe(' ', () => {
expect(container.innerHTML).to.equal('test-popup
');
});
- it('registers a trigger element correctly', () => {
+ it('registers a trigger element correctly', async () => {
const trigger = document.createElement('button');
trigger.setAttribute('data-testid', 'test-button');
@@ -48,7 +63,7 @@ describe(' ', () => {
return {triggerElement?.getAttribute('data-testid')}
;
}
- const { container } = render(
+ const { container } = await render(
,
@@ -57,8 +72,8 @@ describe(' ', () => {
expect(container.innerHTML).to.equal('test-button
');
});
- it('focuses the first item after the menu is opened', () => {
- const { getByRole, getAllByRole } = render(
+ it('focuses the first item after the menu is opened', async () => {
+ const { getByRole, getAllByRole } = await render(
Toggle
@@ -78,10 +93,12 @@ describe(' ', () => {
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', () => {
+ 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,
@@ -94,15 +111,15 @@ describe(' ', () => {
});
return (
-
+
-
+
);
});
- const { getByRole, getAllByRole } = render(
+ const { getByRole, getAllByRole } = await render(
Toggle
@@ -122,11 +139,13 @@ describe(' ', () => {
const menuItems = getAllByRole('menuitem');
+ await flushMicrotasks();
+
expect(menuItems[1]).toHaveFocus();
});
- it('focuses the trigger after the menu is closed', () => {
- const { getByRole } = render(
+ it('focuses the trigger after the menu is closed', async () => {
+ const { getByRole } = await render(
@@ -145,6 +164,9 @@ describe(' ', () => {
});
const menuItem = getByRole('menuitem');
+
+ await flushMicrotasks();
+
act(() => {
menuItem.click();
});
diff --git a/packages/mui-base/src/Menu/Menu.test.tsx b/packages/mui-base/src/Menu/Menu.test.tsx
index 3ed34f1e10686b..b69f19a89670cd 100644
--- a/packages/mui-base/src/Menu/Menu.test.tsx
+++ b/packages/mui-base/src/Menu/Menu.test.tsx
@@ -7,6 +7,9 @@ import {
describeConformanceUnstyled,
fireEvent,
act,
+ MuiRenderResult,
+ RenderOptions,
+ flushMicrotasks,
} from '@mui-internal/test-utils';
import { Menu, menuClasses } from '@mui/base/Menu';
import { MenuItem, MenuItemRootSlotProps } from '@mui/base/MenuItem';
@@ -25,7 +28,16 @@ const testContext: DropdownContextValue = {
describe(' ', () => {
const mount = createMount();
- const { render } = createRenderer();
+ const { render: internalRender } = createRenderer();
+
+ async function render(
+ element: React.ReactElement>,
+ options?: RenderOptions,
+ ): Promise {
+ const rendered = internalRender(element, options);
+ await flushMicrotasks();
+ return rendered;
+ }
describeConformanceUnstyled( , () => ({
inheritComponent: 'div',
@@ -65,8 +77,8 @@ describe(' ', () => {
);
}
- it('highlights the first item when the menu is opened', () => {
- const { getAllByRole } = render( );
+ 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);
@@ -187,8 +199,8 @@ describe(' ', () => {
});
describe('keyboard navigation', () => {
- it('changes the highlighted item using the arrow keys', () => {
- const { getByTestId } = render(
+ it('changes the highlighted item using the arrow keys', async () => {
+ const { getByTestId } = await render(
1
@@ -216,8 +228,8 @@ describe(' ', () => {
expect(document.activeElement).to.equal(item2);
});
- it('changes the highlighted item using the Home and End keys', () => {
- const { getByTestId } = render(
+ it('changes the highlighted item using the Home and End keys', async () => {
+ const { getByTestId } = await render(
1
@@ -241,8 +253,8 @@ describe(' ', () => {
expect(document.activeElement).to.equal(getByTestId('item-1'));
});
- it('includes disabled items during keyboard navigation', () => {
- const { getByTestId } = render(
+ it('includes disabled items during keyboard navigation', async () => {
+ const { getByTestId } = await render(
1
@@ -267,14 +279,14 @@ describe(' ', () => {
});
describe('text navigation', () => {
- it('changes the highlighted item', function test() {
+ 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 } = render(
+ const { getByText, getAllByRole } = await render(
Aa
@@ -302,14 +314,14 @@ describe(' ', () => {
expect(getByText('Cd')).to.have.attribute('tabindex', '0');
});
- it('repeated keys circulate all items starting with that letter', function test() {
+ 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 } = render(
+ const { getByText, getAllByRole } = await render(
Aa
@@ -339,8 +351,8 @@ describe(' ', () => {
expect(getByText('Ba')).to.have.attribute('tabindex', '0');
});
- it('changes the highlighted item using text navigation on label prop', () => {
- const { getAllByRole } = render(
+ it('changes the highlighted item using text navigation on label prop', async () => {
+ const { getAllByRole } = await render(
1
@@ -370,14 +382,14 @@ describe(' ', () => {
expect(items[1]).to.have.attribute('tabindex', '0');
});
- it('skips the non-stringifiable items', function test() {
+ 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 } = render(
+ const { getByText, getAllByRole } = await render(
Aa
@@ -412,14 +424,14 @@ describe(' ', () => {
expect(getByText('Ba')).to.have.attribute('tabindex', '0');
});
- it('navigate to options with diacritic characters', function test() {
+ 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 } = render(
+ const { getByText, getAllByRole } = await render(
Aa
@@ -447,14 +459,14 @@ describe(' ', () => {
expect(getByText('Bą')).to.have.attribute('tabindex', '0');
});
- it('navigate to next options with beginning diacritic characters', function test() {
+ 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 } = render(
+ const { getByText, getAllByRole } = await render(
Aa
@@ -493,10 +505,10 @@ describe(' ', () => {
});
describe('prop: onItemsChange', () => {
- it('should be called when the menu items change', () => {
+ it('should be called when the menu items change', async () => {
const handleItemsChange = spy();
- const { setProps } = render(
+ const { setProps } = await render(
1
@@ -522,7 +534,11 @@ describe(' ', () => {
});
describe('prop: anchor', () => {
- it('should be placed near the specified element', async () => {
+ 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);
@@ -531,7 +547,7 @@ describe(' ', () => {
1
2
@@ -542,19 +558,29 @@ describe(' ', () => {
);
}
- const { getByTestId } = render( );
+ const { getByTestId } = await render( );
const popup = getByTestId('popup');
const anchor = getByTestId('anchor');
const anchorPosition = anchor.getBoundingClientRect();
- expect(popup.style.getPropertyValue('transform')).to.equal(
- `translate(${anchorPosition.left}px, ${anchorPosition.bottom}px)`,
- );
+ 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 () => {
+ 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,
@@ -569,11 +595,11 @@ describe(' ', () => {
const virtualElement = { getBoundingClientRect: () => boundingRect };
- const { getByTestId } = render(
+ const { getByTestId } = await render(
1
2
@@ -581,11 +607,18 @@ describe(' ', () => {
,
);
const popup = getByTestId('popup');
- expect(popup.style.getPropertyValue('transform')).to.equal(`translate(200px, 100px)`);
+
+ 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', () => {
+ it('perf: does not rerender menu items unnecessarily', async () => {
const renderItem1Spy = spy();
const renderItem2Spy = spy();
const renderItem3Spy = spy();
@@ -600,7 +633,7 @@ describe(' ', () => {
return ;
});
- const { getAllByRole } = render(
+ const { getAllByRole } = await render(
+
{children}
-
+
);
}) as PolymorphicComponent;
diff --git a/packages/mui-base/src/Menu/Menu.types.ts b/packages/mui-base/src/Menu/Menu.types.ts
index 1d2e85ae657931..46003dcce09fce 100644
--- a/packages/mui-base/src/Menu/Menu.types.ts
+++ b/packages/mui-base/src/Menu/Menu.types.ts
@@ -3,7 +3,7 @@ import { Simplify } from '@mui/types';
import { PolymorphicProps, SlotComponentProps } from '../utils';
import { UseMenuListboxSlotProps } from '../useMenu';
import { ListAction } from '../useList';
-import { Popper, PopperProps } from '../Popper';
+import { PopupProps } from '../Unstable_Popup';
export interface MenuRootSlotPropsOverrides {}
export interface MenuListboxSlotPropsOverrides {}
@@ -27,7 +27,7 @@ export interface MenuOwnProps {
/**
* The element based on which the menu is positioned.
*/
- anchor?: PopperProps['anchorEl'];
+ anchor?: PopupProps['anchor'];
children?: React.ReactNode;
className?: string;
/**
@@ -39,8 +39,7 @@ export interface MenuOwnProps {
* @default {}
*/
slotProps?: {
- root?: SlotComponentProps<'div', MenuRootSlotPropsOverrides, MenuOwnerState> &
- Partial>;
+ root?: SlotComponentProps<'div', MenuRootSlotPropsOverrides & PopupProps, MenuOwnerState>;
listbox?: SlotComponentProps<'ul', MenuListboxSlotPropsOverrides, MenuOwnerState>;
};
/**
@@ -89,7 +88,7 @@ export type MenuRootSlotProps = {
ref: React.Ref;
};
-export type MenuPopupSlotProps = UseMenuListboxSlotProps & {
+export type MenuListboxSlotProps = UseMenuListboxSlotProps & {
children?: React.ReactNode;
className?: string;
ownerState: MenuOwnerState;
diff --git a/packages/mui-base/src/Select/Select.test.tsx b/packages/mui-base/src/Select/Select.test.tsx
index fee6832086d679..c87259044a8eeb 100644
--- a/packages/mui-base/src/Select/Select.test.tsx
+++ b/packages/mui-base/src/Select/Select.test.tsx
@@ -10,6 +10,7 @@ import {
screen,
MuiRenderResult,
RenderOptions,
+ flushMicrotasks,
} from '@mui-internal/test-utils';
import userEvent from '@testing-library/user-event';
import { Select, SelectListboxSlotProps, selectClasses } from '@mui/base/Select';
@@ -21,13 +22,6 @@ import { OptionGroup } from '@mui/base/OptionGroup';
// currently the setup() method uses the ClipboardEvent constructor which is incompatible with our lowest supported version of iOS Safari (12.2) https://github.com/mui/material-ui/blob/master/.browserslistrc#L44
// userEvent.setup() requires Safari 14 or up to work
-async function flushMicrotasks() {
- if (/jsdom/.test(window.navigator.userAgent)) {
- // This is only needed for JSDOM and causes issues in real browsers
- await act(() => async () => {});
- }
-}
-
describe(' ', () => {
const mount = createMount();
const { render: internalRender } = createRenderer();
diff --git a/packages/mui-base/src/Unstable_Popup/Popup.test.tsx b/packages/mui-base/src/Unstable_Popup/Popup.test.tsx
index 9261e9f49e3850..7fb9225c73a7c1 100644
--- a/packages/mui-base/src/Unstable_Popup/Popup.test.tsx
+++ b/packages/mui-base/src/Unstable_Popup/Popup.test.tsx
@@ -293,12 +293,12 @@ describe(' ', () => {
});
});
- describe('prop: withTransition', () => {
+ describe('transitions', () => {
clock.withFakeTimers();
it('should work', async () => {
const { queryByRole, getByRole, setProps } = render(
-
+
Hello World
@@ -334,7 +334,7 @@ describe(' ', () => {
Toggle Tooltip
-
+
Hello World
diff --git a/packages/mui-base/src/Unstable_Popup/Popup.tsx b/packages/mui-base/src/Unstable_Popup/Popup.tsx
index b24ae6361120f8..0b0a90cfde1dc9 100644
--- a/packages/mui-base/src/Unstable_Popup/Popup.tsx
+++ b/packages/mui-base/src/Unstable_Popup/Popup.tsx
@@ -16,10 +16,10 @@ import {
} from '@mui/utils';
import { unstable_composeClasses as composeClasses } from '../composeClasses';
import { Portal } from '../Portal';
-import { useSlotProps } from '../utils';
+import { useSlotProps, WithOptionalOwnerState } from '../utils';
import { useClassNamesOverride } from '../utils/ClassNameConfigurator';
import { getPopupUtilityClass } from './popupClasses';
-import { PopupOwnerState, PopupProps } from './Popup.types';
+import { PopupOwnerState, PopupProps, PopupRootSlotProps } from './Popup.types';
import { useTransitionTrigger, TransitionContext } from '../useTransition';
import { PopupContext, PopupContextValue } from './PopupContext';
@@ -72,7 +72,6 @@ const Popup = React.forwardRef(function Popup = useSlotProps({
elementType: Root,
externalSlotProps: slotProps.root,
externalForwardedProps: other,
@@ -285,15 +283,6 @@ Popup.propTypes /* remove-proptypes */ = {
* @see https://floating-ui.com/docs/computePosition#strategy
*/
strategy: PropTypes.oneOf(['absolute', 'fixed']),
- /**
- * If `true`, the popup will not disappear immediately when it needs to be closed
- * but wait until the exit transition has finished.
- * In such a case, a function form of `children` must be used and `onExited`
- * callback function must be called when the transition or animation finish.
- *
- * @default false
- */
- withTransition: PropTypes.bool,
} as any;
export { Popup };
diff --git a/packages/mui-base/src/Unstable_Popup/Popup.types.ts b/packages/mui-base/src/Unstable_Popup/Popup.types.ts
index bf1d24105713a9..0f94e70b98dad6 100644
--- a/packages/mui-base/src/Unstable_Popup/Popup.types.ts
+++ b/packages/mui-base/src/Unstable_Popup/Popup.types.ts
@@ -103,15 +103,6 @@ export interface PopupOwnProps {
* @see https://floating-ui.com/docs/computePosition#strategy
*/
strategy?: PopupStrategy;
- /**
- * If `true`, the popup will not disappear immediately when it needs to be closed
- * but wait until the exit transition has finished.
- * In such a case, a function form of `children` must be used and `onExited`
- * callback function must be called when the transition or animation finish.
- *
- * @default false
- */
- withTransition?: boolean;
}
export interface PopupSlots {
@@ -142,5 +133,12 @@ export interface PopupOwnerState extends PopupOwnProps {
placement: PopupPlacement;
finalPlacement: PopupPlacement;
strategy: PopupStrategy;
- withTransition: boolean;
}
+
+export type PopupRootSlotProps = {
+ className?: string;
+ children?: React.ReactNode;
+ ownerState: PopupOwnerState;
+ style: React.CSSProperties;
+ role: React.AriaRole;
+};
diff --git a/packages/mui-base/src/useTransition/useTransitionTrigger.ts b/packages/mui-base/src/useTransition/useTransitionTrigger.ts
index 0b433daf9ff13a..2430731fe1a91c 100644
--- a/packages/mui-base/src/useTransition/useTransitionTrigger.ts
+++ b/packages/mui-base/src/useTransition/useTransitionTrigger.ts
@@ -63,8 +63,6 @@ export function useTransitionTrigger(requestEnter: boolean): UseTransitionTrigge
hasExited = !hasPendingExitTransition.current && exitTransitionFinished;
}
- // console.log({ hasExited, requestEnter, hasPendingExitTransition, exitTransitionFinished });
-
const contextValue: TransitionContextValue = React.useMemo(
() => ({
requestedEnter: requestEnter,
diff --git a/packages/test-utils/src/flushMicrotasks.ts b/packages/test-utils/src/flushMicrotasks.ts
new file mode 100644
index 00000000000000..891e0c2323756c
--- /dev/null
+++ b/packages/test-utils/src/flushMicrotasks.ts
@@ -0,0 +1,8 @@
+import { act } from './createRenderer';
+
+export default async function flushMicrotasks() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ // This is only needed for JSDOM and causes issues in real browsers
+ await act(() => async () => {});
+ }
+}
diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts
index 8bcfd338a4218d..c09a82e27aca25 100644
--- a/packages/test-utils/src/index.ts
+++ b/packages/test-utils/src/index.ts
@@ -15,6 +15,7 @@ export {
export {} from './initMatchers';
export * as fireDiscreteEvent from './fireDiscreteEvent';
export * as userEvent from './userEvent';
+export { default as flushMicrotasks } from './flushMicrotasks';
/**
* Set to true if console logs during [lifecycles that are invoked twice in `React.StrictMode`](https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects) are suppressed.
From be0ef2d5fa2a3bb6764aabfe24c6fc94fc420c8a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20Dudak?=
Date: Fri, 2 Feb 2024 08:57:24 +0100
Subject: [PATCH 024/120] [Menu][base-ui] Fix improperly merged tests (#40896)
---
packages/mui-base/src/Menu/Menu.test.tsx | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/packages/mui-base/src/Menu/Menu.test.tsx b/packages/mui-base/src/Menu/Menu.test.tsx
index b69f19a89670cd..5a81a5e17e35c7 100644
--- a/packages/mui-base/src/Menu/Menu.test.tsx
+++ b/packages/mui-base/src/Menu/Menu.test.tsx
@@ -87,7 +87,7 @@ describe(' ', () => {
});
});
- it('highlights first item when down arrow key opens the menu', () => {
+ it('highlights first item when down arrow key opens the menu', async () => {
const context: DropdownContextValue = {
...testContext,
state: {
@@ -99,7 +99,7 @@ describe(' ', () => {
} as React.KeyboardEvent,
},
};
- const { getAllByRole } = render(
+ const { getAllByRole } = await render(
1
@@ -116,7 +116,7 @@ describe(' ', () => {
});
});
- it('highlights last item when up arrow key opens the menu', () => {
+ it('highlights last item when up arrow key opens the menu', async () => {
const context: DropdownContextValue = {
...testContext,
state: {
@@ -128,7 +128,7 @@ describe(' ', () => {
} as React.KeyboardEvent,
},
};
- const { getAllByRole } = render(
+ const { getAllByRole } = await render(
1
@@ -146,7 +146,7 @@ describe(' ', () => {
});
});
- it('highlights last non-disabled item when disabledItemsFocusable is set to false', () => {
+ 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,
@@ -180,7 +180,7 @@ describe(' ', () => {
} as React.KeyboardEvent,
},
};
- const { getAllByRole } = render(
+ const { getAllByRole } = await render(
1
From 475e186f1e31d194e1d80621effa8a866411ec30 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20Dudak?=
Date: Fri, 2 Feb 2024 10:33:56 +0100
Subject: [PATCH 025/120] [code-infra] Prepare publishing
@mui-internal/typescript-to-proptypes to npm (#40842)
---
.codesandbox/ci.json | 8 +-
babel.config.js | 1 +
docs/package.json | 4 +-
docs/scripts/formattedTSDemos.js | 7 +-
.../components/ApiPage/list/ClassesList.tsx | 2 +-
.../ApiPage/sections/ClassesSection.tsx | 2 +-
.../components/ApiPage/table/ClassesTable.tsx | 2 +-
package.json | 10 +-
.../ApiBuilders/ComponentApiBuilder.ts | 2 +-
packages/api-docs-builder/ProjectSettings.ts | 2 +-
packages/api-docs-builder/buildApiUtils.ts | 2 +-
packages/api-docs-builder/package.json | 2 +-
packages/api-docs-builder/tsconfig.json | 2 +-
.../utils/parseSlotsAndClasses.ts | 2 +-
packages/docs-utilities/README.md | 5 -
packages/docs-utilities/index.d.ts | 9 -
packages/docs-utilities/package.json | 6 -
packages/docs-utilities/tsconfig.json | 19 -
packages/docs-utils/.npmignore | 1 +
packages/docs-utils/CHANGELOG.md | 5 +
packages/docs-utils/README.md | 9 +
packages/docs-utils/package.json | 30 ++
.../src}/ComponentClassDefinition.ts | 0
.../docs-utils/src/createTypeScriptProject.ts | 131 +++++++
.../src/getPropsFromComponentNode.ts | 358 ++++++++++++++++++
.../index.js => docs-utils/src/index.ts} | 29 +-
packages/docs-utils/tsconfig.build.json | 15 +
packages/docs-utils/tsconfig.json | 11 +
packages/typescript-to-proptypes/.npmignore | 1 +
packages/typescript-to-proptypes/CHANGELOG.md | 176 +--------
.../typescript-to-proptypes/CHANGELOG.old.md | 179 +++++++++
packages/typescript-to-proptypes/README.md | 12 +-
packages/typescript-to-proptypes/package.json | 39 +-
.../src/getPropTypesFromFile.ts | 4 +-
.../tsconfig.build.json | 15 +
.../typescript-to-proptypes/tsconfig.json | 14 +-
pnpm-lock.yaml | 39 +-
scripts/generateProptypes.ts | 4 +-
tsconfig.json | 3 +-
webpackBaseConfig.js | 6 +-
40 files changed, 876 insertions(+), 292 deletions(-)
delete mode 100644 packages/docs-utilities/README.md
delete mode 100644 packages/docs-utilities/index.d.ts
delete mode 100644 packages/docs-utilities/package.json
delete mode 100644 packages/docs-utilities/tsconfig.json
create mode 100644 packages/docs-utils/.npmignore
create mode 100644 packages/docs-utils/CHANGELOG.md
create mode 100644 packages/docs-utils/README.md
create mode 100644 packages/docs-utils/package.json
rename packages/{docs-utilities => docs-utils/src}/ComponentClassDefinition.ts (100%)
create mode 100644 packages/docs-utils/src/createTypeScriptProject.ts
create mode 100644 packages/docs-utils/src/getPropsFromComponentNode.ts
rename packages/{docs-utilities/index.js => docs-utils/src/index.ts} (66%)
create mode 100644 packages/docs-utils/tsconfig.build.json
create mode 100644 packages/docs-utils/tsconfig.json
create mode 100644 packages/typescript-to-proptypes/.npmignore
create mode 100644 packages/typescript-to-proptypes/CHANGELOG.old.md
create mode 100644 packages/typescript-to-proptypes/tsconfig.build.json
diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json
index d2b89fcb8f7adc..c086cfc1cc843a 100644
--- a/.codesandbox/ci.json
+++ b/.codesandbox/ci.json
@@ -18,7 +18,9 @@
"packages/mui-styles",
"packages/mui-system",
"packages/mui-types",
- "packages/mui-utils"
+ "packages/mui-utils",
+ "packages/docs-utils",
+ "packages/typescript-to-proptypes"
],
"publishDirectory": {
"@mui/base": "packages/mui-base/build",
@@ -36,7 +38,9 @@
"@mui/styles": "packages/mui-styles/build",
"@mui/system": "packages/mui-system/build",
"@mui/types": "packages/mui-types/build",
- "@mui/utils": "packages/mui-utils/build"
+ "@mui/utils": "packages/mui-utils/build",
+ "@mui-internal/docs-utils": "packages/docs-utils",
+ "@mui-internal/typescript-to-proptypes": "packages/typescript-to-proptypes"
},
"sandboxes": [
"material-ui-issue-latest-s2dsx",
diff --git a/babel.config.js b/babel.config.js
index eca2870ce49edb..f04e19b097f92f 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -33,6 +33,7 @@ module.exports = function getBabelConfig(api) {
'@mui/material-next': resolveAliasPath('./packages/mui-material-next/src'),
'@mui/joy': resolveAliasPath('./packages/mui-joy/src'),
'@mui/zero-runtime': resolveAliasPath('./packages/zero-runtime/src'),
+ '@mui-internal/docs-utils': resolveAliasPath('./packages/docs-utils/src'),
docs: resolveAliasPath('./docs'),
test: resolveAliasPath('./test'),
};
diff --git a/docs/package.json b/docs/package.json
index a7d3b9c16b9042..52445aab867592 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -117,8 +117,9 @@
"devDependencies": {
"@babel/plugin-transform-react-constant-elements": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
- "@mui-internal/docs-utilities": "workspace:^",
+ "@mui-internal/docs-utils": "workspace:^",
"@mui-internal/test-utils": "workspace:^",
+ "@mui-internal/typescript-to-proptypes": "workspace:^",
"@types/autosuggest-highlight": "^3.2.3",
"@types/chai": "^4.3.11",
"@types/css-mediaquery": "^0.1.4",
@@ -139,7 +140,6 @@
"playwright": "^1.41.1",
"prettier": "^2.8.8",
"tailwindcss": "^3.4.1",
- "typescript-to-proptypes": "workspace:^",
"yargs": "^17.7.2"
}
}
diff --git a/docs/scripts/formattedTSDemos.js b/docs/scripts/formattedTSDemos.js
index 31e9cce36ea25d..818bd67e6c4159 100644
--- a/docs/scripts/formattedTSDemos.js
+++ b/docs/scripts/formattedTSDemos.js
@@ -15,12 +15,15 @@ const path = require('path');
const fse = require('fs-extra');
const babel = require('@babel/core');
const prettier = require('prettier');
-const { getPropTypesFromFile, injectPropTypesInFile } = require('typescript-to-proptypes');
+const {
+ getPropTypesFromFile,
+ injectPropTypesInFile,
+} = require('@mui-internal/typescript-to-proptypes');
const {
createTypeScriptProjectBuilder,
} = require('@mui-internal/api-docs-builder/utils/createTypeScriptProject');
const yargs = require('yargs');
-const { fixBabelGeneratorIssues, fixLineEndings } = require('@mui-internal/docs-utilities');
+const { fixBabelGeneratorIssues, fixLineEndings } = require('@mui-internal/docs-utils');
const { default: CORE_TYPESCRIPT_PROJECTS } = require('../../scripts/coreTypeScriptProjects');
const babelConfig = {
diff --git a/docs/src/modules/components/ApiPage/list/ClassesList.tsx b/docs/src/modules/components/ApiPage/list/ClassesList.tsx
index ed4fc21b901be7..b10494b62882f3 100644
--- a/docs/src/modules/components/ApiPage/list/ClassesList.tsx
+++ b/docs/src/modules/components/ApiPage/list/ClassesList.tsx
@@ -2,7 +2,7 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
import kebabCase from 'lodash/kebabCase';
-import { ComponentClassDefinition } from '@mui-internal/docs-utilities';
+import { ComponentClassDefinition } from '@mui-internal/docs-utils';
import { useTranslate } from 'docs/src/modules/utils/i18n';
import ExpandableApiItem, {
ApiItemContaier,
diff --git a/docs/src/modules/components/ApiPage/sections/ClassesSection.tsx b/docs/src/modules/components/ApiPage/sections/ClassesSection.tsx
index 318c159fc6e78d..8c1a714b58d022 100644
--- a/docs/src/modules/components/ApiPage/sections/ClassesSection.tsx
+++ b/docs/src/modules/components/ApiPage/sections/ClassesSection.tsx
@@ -1,7 +1,7 @@
/* eslint-disable react/no-danger */
import * as React from 'react';
import { useTranslate } from 'docs/src/modules/utils/i18n';
-import { ComponentClassDefinition } from '@mui-internal/docs-utilities';
+import { ComponentClassDefinition } from '@mui-internal/docs-utils';
import Box from '@mui/material/Box';
import ToggleDisplayOption, {
API_LAYOUT_STORAGE_KEYS,
diff --git a/docs/src/modules/components/ApiPage/table/ClassesTable.tsx b/docs/src/modules/components/ApiPage/table/ClassesTable.tsx
index 881be9202db048..604259f7682599 100644
--- a/docs/src/modules/components/ApiPage/table/ClassesTable.tsx
+++ b/docs/src/modules/components/ApiPage/table/ClassesTable.tsx
@@ -1,6 +1,6 @@
/* eslint-disable react/no-danger */
import * as React from 'react';
-import { ComponentClassDefinition } from '@mui-internal/docs-utilities';
+import { ComponentClassDefinition } from '@mui-internal/docs-utils';
import { styled, alpha } from '@mui/material/styles';
import {
brandingDarkTheme as darkTheme,
diff --git a/package.json b/package.json
index d48f0129b3ab65..86277485ebb7ac 100644
--- a/package.json
+++ b/package.json
@@ -4,13 +4,13 @@
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
- "proptypes": "cross-env BABEL_ENV=development babel-node --extensions \".tsx,.ts,.js\" ./scripts/generateProptypes.ts",
+ "proptypes": "tsx ./scripts/generateProptypes.ts",
"deduplicate": "pnpm dedupe",
"benchmark:browser": "pnpm --filter benchmark browser",
"build": "lerna run --scope \"@mui/*\" build",
"build:zero": "lerna run --scope \"@mui/zero-*\" build",
"clean:zero": "pnpm --filter \"@mui/zero-*\" clean",
- "build:codesandbox": "NODE_OPTIONS=\"–max_old_space_size=4096\" lerna run --concurrency 8 --scope \"@mui/*\" build",
+ "build:codesandbox": "NODE_OPTIONS=\"--max_old_space_size=4096\" lerna run --concurrency 8 --scope \"@mui/*\" --scope \"@mui-internal/*\" --no-private build",
"release:version": "lerna version --no-changelog --no-push --no-git-tag-version --no-private --force-publish=@mui/core-downloads-tracker",
"release:build": "lerna run --concurrency 8 --scope \"@mui/*\" build --skip-nx-cache",
"release:changelog": "node scripts/releaseChangelog.mjs",
@@ -33,7 +33,7 @@
"docs:link-check": "pnpm --filter docs link-check",
"docs:typescript": "pnpm docs:typescript:formatted --watch",
"docs:typescript:check": "pnpm --filter docs typescript",
- "docs:typescript:formatted": "cross-env BABEL_ENV=development babel-node --extensions \".tsx,.ts,.js\" ./docs/scripts/formattedTSDemos",
+ "docs:typescript:formatted": "tsx ./docs/scripts/formattedTSDemos",
"docs:mdicons:synonyms": "cross-env BABEL_ENV=development babel-node --extensions \".tsx,.ts,.js,.mjs\" ./docs/scripts/updateIconSynonyms && pnpm prettier",
"extract-error-codes": "cross-env MUI_EXTRACT_ERROR_CODES=true lerna run --concurrency 8 build:modern",
"rsc:build": "tsx ./packages/rsc-builder/buildRsc.ts",
@@ -102,8 +102,9 @@
"@mnajdova/enzyme-adapter-react-18": "^0.2.0",
"@mui-internal/api-docs-builder": "workspace:^",
"@mui-internal/api-docs-builder-core": "workspace:^",
- "@mui-internal/docs-utilities": "workspace:^",
+ "@mui-internal/docs-utils": "workspace:^",
"@mui-internal/test-utils": "workspace:^",
+ "@mui-internal/typescript-to-proptypes": "workspace:^",
"@mui/joy": "workspace:*",
"@mui/material": "workspace:^",
"@mui/utils": "workspace:^",
@@ -180,7 +181,6 @@
"tsx": "^4.7.0",
"tsup": "^8.0.1",
"typescript": "^5.3.3",
- "typescript-to-proptypes": "workspace:^",
"webpack": "^5.90.0",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-cli": "^5.1.4",
diff --git a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts
index 8a425543ce8e3d..a0921b4325c1ed 100644
--- a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts
+++ b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts
@@ -10,7 +10,7 @@ import remarkVisit from 'unist-util-visit';
import type { Link } from 'mdast';
import { defaultHandlers, parse as docgenParse, ReactDocgenApi } from 'react-docgen';
import { renderMarkdown } from '@mui/markdown';
-import { ComponentClassDefinition } from '@mui-internal/docs-utilities';
+import { ComponentClassDefinition } from '@mui-internal/docs-utils';
import { ProjectSettings, SortingStrategiesType } from '../ProjectSettings';
import { ComponentInfo, toGitHubPath, writePrettifiedFile } from '../buildApiUtils';
import muiDefaultPropsHandler from '../utils/defaultPropsHandler';
diff --git a/packages/api-docs-builder/ProjectSettings.ts b/packages/api-docs-builder/ProjectSettings.ts
index f7a210a03b1377..3a848328893462 100644
--- a/packages/api-docs-builder/ProjectSettings.ts
+++ b/packages/api-docs-builder/ProjectSettings.ts
@@ -1,4 +1,4 @@
-import { ComponentClassDefinition } from '@mui-internal/docs-utilities/ComponentClassDefinition';
+import { ComponentClassDefinition } from '@mui-internal/docs-utils';
import { ComponentInfo, HookInfo } from './buildApiUtils';
import { CreateTypeScriptProjectOptions } from './utils/createTypeScriptProject';
import { CreateDescribeablePropSettings } from './utils/createDescribeableProp';
diff --git a/packages/api-docs-builder/buildApiUtils.ts b/packages/api-docs-builder/buildApiUtils.ts
index 6afbfdd2d99565..1ef45d3484ca66 100644
--- a/packages/api-docs-builder/buildApiUtils.ts
+++ b/packages/api-docs-builder/buildApiUtils.ts
@@ -3,7 +3,7 @@ import path from 'path';
import * as ts from 'typescript';
import * as prettier from 'prettier';
import kebabCase from 'lodash/kebabCase';
-import { getLineFeed } from '@mui-internal/docs-utilities';
+import { getLineFeed } from '@mui-internal/docs-utils';
import { replaceComponentLinks } from './utils/replaceUrl';
import { TypeScriptProject } from './utils/createTypeScriptProject';
diff --git a/packages/api-docs-builder/package.json b/packages/api-docs-builder/package.json
index e82d3e68475c74..fbfde5233bca91 100644
--- a/packages/api-docs-builder/package.json
+++ b/packages/api-docs-builder/package.json
@@ -11,7 +11,7 @@
"@babel/core": "^7.23.9",
"@babel/preset-typescript": "^7.23.3",
"@babel/traverse": "^7.23.9",
- "@mui-internal/docs-utilities": "workspace:^",
+ "@mui-internal/docs-utils": "workspace:^",
"@mui/markdown": "workspace:^",
"ast-types": "^0.14.2",
"doctrine": "^3.0.0",
diff --git a/packages/api-docs-builder/tsconfig.json b/packages/api-docs-builder/tsconfig.json
index f3d2e445296da4..41e0c9ff506e27 100644
--- a/packages/api-docs-builder/tsconfig.json
+++ b/packages/api-docs-builder/tsconfig.json
@@ -14,7 +14,7 @@
"strict": true,
"baseUrl": "./",
"paths": {
- "@mui/utils": ["../mui-utils/src"]
+ "@mui-internal/docs-utils": ["../docs-utils/src"]
}
},
"include": ["./**/*.ts", "./**/*.js"],
diff --git a/packages/api-docs-builder/utils/parseSlotsAndClasses.ts b/packages/api-docs-builder/utils/parseSlotsAndClasses.ts
index dc9718adc605f8..d2e44deda109cd 100644
--- a/packages/api-docs-builder/utils/parseSlotsAndClasses.ts
+++ b/packages/api-docs-builder/utils/parseSlotsAndClasses.ts
@@ -1,5 +1,5 @@
import * as ts from 'typescript';
-import { ComponentClassDefinition } from '@mui-internal/docs-utilities';
+import { ComponentClassDefinition } from '@mui-internal/docs-utils';
import { renderMarkdown } from '@mui/markdown';
import { getSymbolDescription, getSymbolJSDocTags } from '../buildApiUtils';
import { TypeScriptProject } from './createTypeScriptProject';
diff --git a/packages/docs-utilities/README.md b/packages/docs-utilities/README.md
deleted file mode 100644
index 4f6c361f66aaa3..00000000000000
--- a/packages/docs-utilities/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# @mui-internal/docs-utilities
-
-This package contains utilities shared between docs generation scripts.
-
-It is private and not meant to be published.
diff --git a/packages/docs-utilities/index.d.ts b/packages/docs-utilities/index.d.ts
deleted file mode 100644
index 1f3c455258ef5c..00000000000000
--- a/packages/docs-utilities/index.d.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export function getLineFeed(source: string): string;
-
-export function fixBabelGeneratorIssues(source: string): string;
-
-export function fixLineEndings(source: string, target: string): string;
-
-export function getUnstyledFilename(filename: string, definitionFile?: boolean): string;
-
-export * from './ComponentClassDefinition';
diff --git a/packages/docs-utilities/package.json b/packages/docs-utilities/package.json
deleted file mode 100644
index 740cec8962e5ce..00000000000000
--- a/packages/docs-utilities/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "@mui-internal/docs-utilities",
- "version": "1.0.0",
- "private": "true",
- "main": "index.js"
-}
diff --git a/packages/docs-utilities/tsconfig.json b/packages/docs-utilities/tsconfig.json
deleted file mode 100644
index 4a58f12da89c7e..00000000000000
--- a/packages/docs-utilities/tsconfig.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "compilerOptions": {
- "allowJs": true,
- "isolatedModules": true,
- "noEmit": true,
- "noUnusedLocals": false,
- "resolveJsonModule": true,
- "skipLibCheck": true,
- "esModuleInterop": true,
- "types": ["node", "mocha"],
- "target": "ES2020",
- "module": "CommonJS",
- "moduleResolution": "node",
- "strict": true,
- "baseUrl": "./"
- },
- "include": ["./**/*.ts", "./**/*.js"],
- "exclude": ["node_modules"]
-}
diff --git a/packages/docs-utils/.npmignore b/packages/docs-utils/.npmignore
new file mode 100644
index 00000000000000..81f0fda795522a
--- /dev/null
+++ b/packages/docs-utils/.npmignore
@@ -0,0 +1 @@
+.tsbuildinfo
diff --git a/packages/docs-utils/CHANGELOG.md b/packages/docs-utils/CHANGELOG.md
new file mode 100644
index 00000000000000..a6c93f9bf457a6
--- /dev/null
+++ b/packages/docs-utils/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog
+
+## 1.0.0
+
+Initial release as an npm package.
diff --git a/packages/docs-utils/README.md b/packages/docs-utils/README.md
new file mode 100644
index 00000000000000..8a19bc3cbc5e1a
--- /dev/null
+++ b/packages/docs-utils/README.md
@@ -0,0 +1,9 @@
+# @mui-internal/docs-utils
+
+This package contains utilities shared between MUI docs generation scripts.
+This is an internal package not meant for general use.
+
+## Release
+
+1. Build the project: `pnpm build`
+2. Publish the build artifacts to npm: `pnpm release:publish`
diff --git a/packages/docs-utils/package.json b/packages/docs-utils/package.json
new file mode 100644
index 00000000000000..5054f175df2ef5
--- /dev/null
+++ b/packages/docs-utils/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@mui-internal/docs-utils",
+ "version": "1.0.0",
+ "author": "MUI Team",
+ "description": "Utilities for MUI docs. This is an internal package not meant for general use.",
+ "main": "./build/index.js",
+ "exports": {
+ ".": "./build/index.js"
+ },
+ "types": "./build/index.d.ts",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/mui/material-ui.git",
+ "directory": "packages/docs-utils"
+ },
+ "scripts": {
+ "prebuild": "rimraf ./build",
+ "build": "tsc -b tsconfig.build.json",
+ "typescript": "tsc -b tsconfig.json",
+ "release:publish": "pnpm publish --tag latest",
+ "release:publish:dry-run": "pnpm publish --tag latest --registry=\"http://localhost:4873/\""
+ },
+ "dependencies": {
+ "rimraf": "^5.0.5",
+ "typescript": "^5.1.6"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/docs-utilities/ComponentClassDefinition.ts b/packages/docs-utils/src/ComponentClassDefinition.ts
similarity index 100%
rename from packages/docs-utilities/ComponentClassDefinition.ts
rename to packages/docs-utils/src/ComponentClassDefinition.ts
diff --git a/packages/docs-utils/src/createTypeScriptProject.ts b/packages/docs-utils/src/createTypeScriptProject.ts
new file mode 100644
index 00000000000000..ba3b64e54df3c2
--- /dev/null
+++ b/packages/docs-utils/src/createTypeScriptProject.ts
@@ -0,0 +1,131 @@
+import path from 'path';
+import fs from 'fs';
+import * as ts from 'typescript';
+
+export interface TypeScriptProject {
+ name: string;
+ rootPath: string;
+ exports: Record;
+ program: ts.Program;
+ checker: ts.TypeChecker;
+}
+
+export interface CreateTypeScriptProjectOptions {
+ name: string;
+ rootPath: string;
+ /**
+ * Config to use to build this package.
+ * The path must be relative to the root path.
+ * @default 'tsconfig.build.json`
+ */
+ tsConfigPath?: string;
+ /**
+ * File used as root of the package.
+ * This property is used to gather the exports of the project.
+ * The path must be relative to the root path.
+ */
+ entryPointPath?: string;
+ /**
+ * Files to include in the project.
+ * By default, it will use the files defined in the tsconfig.
+ */
+ files?: string[];
+}
+
+export const createTypeScriptProject = (
+ options: CreateTypeScriptProjectOptions,
+): TypeScriptProject => {
+ const {
+ name,
+ rootPath,
+ tsConfigPath: inputTsConfigPath = 'tsconfig.build.json',
+ entryPointPath: inputEntryPointPath,
+ files,
+ } = options;
+
+ const tsConfigPath = path.join(rootPath, inputTsConfigPath);
+
+ const tsConfigFile = ts.readConfigFile(tsConfigPath, (filePath) =>
+ fs.readFileSync(filePath).toString(),
+ );
+
+ if (tsConfigFile.error) {
+ throw tsConfigFile.error;
+ }
+
+ // The build config does not parse the `.d.ts` files, but we sometimes need them to get the exports.
+ if (tsConfigFile.config.exclude) {
+ tsConfigFile.config.exclude = tsConfigFile.config.exclude.filter(
+ (pattern: string) => pattern !== 'src/**/*.d.ts',
+ );
+ }
+
+ const tsConfigFileContent = ts.parseJsonConfigFileContent(
+ tsConfigFile.config,
+ ts.sys,
+ path.dirname(tsConfigPath),
+ );
+
+ if (tsConfigFileContent.errors.length > 0) {
+ throw tsConfigFileContent.errors[0];
+ }
+
+ const program = ts.createProgram({
+ rootNames: files ?? tsConfigFileContent.fileNames,
+ options: tsConfigFileContent.options,
+ });
+
+ const checker = program.getTypeChecker();
+
+ let exports: TypeScriptProject['exports'];
+ if (inputEntryPointPath) {
+ const entryPointPath = path.join(rootPath, inputEntryPointPath);
+ const sourceFile = program.getSourceFile(entryPointPath);
+
+ exports = Object.fromEntries(
+ checker.getExportsOfModule(checker.getSymbolAtLocation(sourceFile!)!).map((symbol) => {
+ return [symbol.name, symbol];
+ }),
+ );
+ } else {
+ exports = {};
+ }
+
+ return {
+ name,
+ rootPath,
+ exports,
+ program,
+ checker,
+ };
+};
+
+export type TypeScriptProjectBuilder = (
+ projectName: string,
+ options?: { files?: string[] },
+) => TypeScriptProject;
+
+export const createTypeScriptProjectBuilder = (
+ projectsConfig: Record>,
+): TypeScriptProjectBuilder => {
+ const projects = new Map();
+
+ return (projectName: string, options: { files?: string[] } = {}) => {
+ const cachedProject = projects.get(projectName);
+ if (cachedProject != null) {
+ return cachedProject;
+ }
+
+ // eslint-disable-next-line no-console
+ console.log(`Building new TS project: ${projectName}`);
+
+ const project = createTypeScriptProject({
+ name: projectName,
+ ...projectsConfig[projectName],
+ ...options,
+ });
+
+ projects.set(projectName, project);
+ return project;
+ };
+};
diff --git a/packages/docs-utils/src/getPropsFromComponentNode.ts b/packages/docs-utils/src/getPropsFromComponentNode.ts
new file mode 100644
index 00000000000000..2e2036fcaa9472
--- /dev/null
+++ b/packages/docs-utils/src/getPropsFromComponentNode.ts
@@ -0,0 +1,358 @@
+import * as ts from 'typescript';
+import { TypeScriptProject } from './createTypeScriptProject';
+
+export interface ParsedProp {
+ /**
+ * If `true`, some signatures do not contain this property.
+ * e.g: `id` in `{ id: number, value: string } | { value: string }`
+ */
+ onlyUsedInSomeSignatures: boolean;
+ signatures: { symbol: ts.Symbol; componentType: ts.Type }[];
+}
+
+export interface ParsedComponent {
+ name: string;
+ location: ts.Node;
+ type: ts.Type;
+ sourceFile: ts.SourceFile | undefined;
+ props: Record;
+}
+
+function isTypeJSXElementLike(type: ts.Type, project: TypeScriptProject): boolean {
+ const symbol = type.symbol ?? type.aliasSymbol;
+ if (symbol) {
+ const name = project.checker.getFullyQualifiedName(symbol);
+ return (
+ // Remove once global JSX namespace is no longer used by React
+ name === 'global.JSX.Element' ||
+ name === 'React.JSX.Element' ||
+ name === 'React.ReactElement' ||
+ name === 'React.ReactNode'
+ );
+ }
+
+ if (type.isUnion()) {
+ return type.types.every(
+ // eslint-disable-next-line no-bitwise
+ (subType) => subType.flags & ts.TypeFlags.Null || isTypeJSXElementLike(subType, project),
+ );
+ }
+
+ return false;
+}
+
+function isStyledFunction(node: ts.VariableDeclaration): boolean {
+ return (
+ !!node.initializer &&
+ ts.isCallExpression(node.initializer) &&
+ ts.isCallExpression(node.initializer.expression) &&
+ ts.isIdentifier(node.initializer.expression.expression) &&
+ node.initializer.expression.expression.escapedText === 'styled'
+ );
+}
+
+function getJSXLikeReturnValueFromFunction(type: ts.Type, project: TypeScriptProject) {
+ return type
+ .getCallSignatures()
+ .filter((signature) => isTypeJSXElementLike(signature.getReturnType(), project));
+}
+
+function parsePropsType({
+ name,
+ type,
+ shouldInclude = () => true,
+ location,
+ sourceFile,
+}: {
+ name: string;
+ type: ts.Type;
+ location: ts.Node;
+ shouldInclude?: (data: { name: string; depth: number }) => boolean;
+ sourceFile: ts.SourceFile | undefined;
+}): ParsedComponent {
+ const parsedProps: Record = {};
+
+ type
+ .getProperties()
+ .filter((property) => shouldInclude({ name: property.getName(), depth: 1 }))
+ .forEach((property) => {
+ parsedProps[property.getName()] = {
+ signatures: [
+ {
+ symbol: property,
+ componentType: type,
+ },
+ ],
+ onlyUsedInSomeSignatures: false,
+ };
+ });
+
+ return {
+ name,
+ location,
+ type,
+ sourceFile,
+ props: parsedProps,
+ };
+}
+
+function parseFunctionComponent({
+ node,
+ shouldInclude,
+ project,
+}: {
+ node: ts.VariableDeclaration | ts.FunctionDeclaration;
+ shouldInclude?: (data: { name: string; depth: number }) => boolean;
+ project: TypeScriptProject;
+}): ParsedComponent | null {
+ if (!node.name) {
+ return null;
+ }
+
+ const symbol = project.checker.getSymbolAtLocation(node.name);
+ if (!symbol) {
+ return null;
+ }
+ const componentName = node.name.getText();
+
+ // Discriminate render functions to components
+ if (componentName[0].toUpperCase() !== componentName[0]) {
+ return null;
+ }
+
+ const signatures = getJSXLikeReturnValueFromFunction(
+ project.checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!),
+ project,
+ );
+ if (signatures.length === 0) {
+ return null;
+ }
+
+ const parsedComponents = signatures.map((signature) =>
+ parsePropsType({
+ shouldInclude,
+ name: componentName,
+ type: project.checker.getTypeOfSymbolAtLocation(
+ signature.parameters[0],
+ signature.parameters[0].valueDeclaration!,
+ ),
+ location: signature.parameters[0].valueDeclaration!,
+ sourceFile: node.getSourceFile(),
+ }),
+ );
+
+ const squashedProps: Record = {};
+ parsedComponents.forEach((parsedComponent) => {
+ Object.keys(parsedComponent.props).forEach((propName) => {
+ if (!squashedProps[propName]) {
+ squashedProps[propName] = parsedComponent.props[propName];
+ } else {
+ squashedProps[propName].signatures = [
+ ...squashedProps[propName].signatures,
+ ...parsedComponent.props[propName].signatures,
+ ];
+ }
+ });
+ });
+
+ const squashedParsedComponent: ParsedComponent = {
+ ...parsedComponents[0],
+ props: squashedProps,
+ };
+
+ Object.keys(squashedParsedComponent.props).forEach((propName) => {
+ squashedParsedComponent.props[propName].onlyUsedInSomeSignatures =
+ squashedParsedComponent.props[propName].signatures.length < signatures.length;
+ });
+
+ return squashedParsedComponent;
+}
+
+export interface GetPropsFromComponentDeclarationOptions {
+ project: TypeScriptProject;
+ node: ts.Node;
+ /**
+ * Called before a PropType is added to a component/object
+ * @returns true to include the prop, false to skip it
+ */
+ shouldInclude?: (data: { name: string; depth: number }) => boolean;
+ /**
+ * Control if const declarations should be checked
+ * @default false
+ * @example declare const Component: React.JSXElementConstructor;
+ */
+ checkDeclarations?: boolean;
+}
+
+function getPropsFromVariableDeclaration({
+ node,
+ project,
+ checkDeclarations,
+ shouldInclude,
+}: { node: ts.VariableDeclaration } & Pick<
+ GetPropsFromComponentDeclarationOptions,
+ 'project' | 'checkDeclarations' | 'shouldInclude'
+>) {
+ const type = project.checker.getTypeAtLocation(node.name);
+ if (!node.initializer) {
+ if (
+ checkDeclarations &&
+ type.aliasSymbol &&
+ type.aliasTypeArguments &&
+ project.checker.getFullyQualifiedName(type.aliasSymbol) === 'React.JSXElementConstructor'
+ ) {
+ const propsType = type.aliasTypeArguments[0];
+ if (propsType === undefined) {
+ throw new TypeError(
+ 'Unable to find symbol for `props`. This is a bug in @mui-internal/typescript-to-proptypes.',
+ );
+ }
+ return parsePropsType({
+ name: node.name.getText(),
+ type: propsType,
+ location: node.name,
+ shouldInclude,
+ sourceFile: node.getSourceFile(),
+ });
+ }
+
+ if (checkDeclarations) {
+ return parseFunctionComponent({
+ node,
+ shouldInclude,
+ project,
+ });
+ }
+
+ return null;
+ }
+
+ if (
+ (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)) &&
+ node.initializer.parameters.length === 1
+ ) {
+ return parseFunctionComponent({
+ node,
+ shouldInclude,
+ project,
+ });
+ }
+ // x = React.memo((props:type) { return
})
+ // x = React.forwardRef((props:type) { return
})
+ if (ts.isCallExpression(node.initializer) && node.initializer.arguments.length > 0) {
+ const potentialComponent = node.initializer.arguments[0];
+ if (
+ (ts.isArrowFunction(potentialComponent) || ts.isFunctionExpression(potentialComponent)) &&
+ potentialComponent.parameters.length > 0 &&
+ getJSXLikeReturnValueFromFunction(
+ project.checker.getTypeAtLocation(potentialComponent),
+ project,
+ ).length > 0
+ ) {
+ const propsSymbol = project.checker.getSymbolAtLocation(
+ potentialComponent.parameters[0].name,
+ );
+ if (propsSymbol) {
+ return parsePropsType({
+ name: node.name.getText(),
+ type: project.checker.getTypeOfSymbolAtLocation(
+ propsSymbol,
+ propsSymbol.valueDeclaration!,
+ ),
+ location: propsSymbol.valueDeclaration!,
+ shouldInclude,
+ sourceFile: node.getSourceFile(),
+ });
+ }
+ }
+ }
+
+ // handle component factories: x = createComponent()
+ if (
+ checkDeclarations &&
+ node.initializer &&
+ !isStyledFunction(node) &&
+ getJSXLikeReturnValueFromFunction(type, project).length > 0
+ ) {
+ return parseFunctionComponent({
+ node,
+ shouldInclude,
+ project,
+ });
+ }
+
+ return null;
+}
+
+export function getPropsFromComponentNode({
+ node,
+ shouldInclude,
+ project,
+ checkDeclarations,
+}: GetPropsFromComponentDeclarationOptions): ParsedComponent | null {
+ let parsedComponent: ParsedComponent | null = null;
+ // function x(props: type) { return
}
+ if (
+ ts.isFunctionDeclaration(node) &&
+ node.name &&
+ node.parameters.length === 1 &&
+ getJSXLikeReturnValueFromFunction(project.checker.getTypeAtLocation(node.name), project)
+ .length > 0
+ ) {
+ parsedComponent = parseFunctionComponent({ node, shouldInclude, project });
+ } else if (ts.isVariableDeclaration(node)) {
+ parsedComponent = getPropsFromVariableDeclaration({
+ node,
+ project,
+ checkDeclarations,
+ shouldInclude,
+ });
+ } else if (ts.isVariableStatement(node)) {
+ // const x = ...
+ ts.forEachChild(node.declarationList, (variableNode) => {
+ if (parsedComponent != null) {
+ return;
+ }
+
+ // x = (props: type) => { return
}
+ // x = function(props: type) { return
}
+ // x = function y(props: type) { return
}
+ // x = react.memo((props:type) { return
})
+ if (ts.isVariableDeclaration(variableNode) && variableNode.name) {
+ parsedComponent = getPropsFromVariableDeclaration({
+ node: variableNode,
+ project,
+ checkDeclarations,
+ shouldInclude,
+ });
+ }
+
+ if (
+ ts.isClassDeclaration(variableNode) &&
+ variableNode.name &&
+ variableNode.heritageClauses &&
+ variableNode.heritageClauses.length === 1
+ ) {
+ const heritage = variableNode.heritageClauses[0];
+ if (heritage.types.length !== 1) {
+ return;
+ }
+
+ const arg = heritage.types[0];
+ if (!arg.typeArguments) {
+ return;
+ }
+
+ parsedComponent = parsePropsType({
+ shouldInclude,
+ name: variableNode.name.getText(),
+ location: arg.typeArguments[0],
+ type: project.checker.getTypeAtLocation(arg.typeArguments[0]),
+ sourceFile: node.getSourceFile(),
+ });
+ }
+ });
+ }
+
+ return parsedComponent;
+}
diff --git a/packages/docs-utilities/index.js b/packages/docs-utils/src/index.ts
similarity index 66%
rename from packages/docs-utilities/index.js
rename to packages/docs-utils/src/index.ts
index e2629893b9242a..f056c1cf5aad07 100644
--- a/packages/docs-utilities/index.js
+++ b/packages/docs-utils/src/index.ts
@@ -1,34 +1,29 @@
-const { EOL } = require('os');
+import { EOL } from 'os';
-/**
- * @param {string} source
- */
-function getLineFeed(source) {
+export * from './createTypeScriptProject';
+export { type ComponentClassDefinition } from './ComponentClassDefinition';
+export * from './getPropsFromComponentNode';
+
+export function getLineFeed(source: string): string {
const match = source.match(/\r?\n/);
return match === null ? EOL : match[0];
}
const fixBabelIssuesRegExp = /(?<=(\/>)|,)(\r?\n){2}/g;
-/**
- * @param {string} source
- */
-function fixBabelGeneratorIssues(source) {
+
+export function fixBabelGeneratorIssues(source: string): string {
return source.replace(fixBabelIssuesRegExp, '\n');
}
-/**
- * @param {string} source
- * @param {string} target
- */
-function fixLineEndings(source, target) {
+export function fixLineEndings(source: string, target: string): string {
return target.replace(/\r?\n/g, getLineFeed(source));
}
/**
* Converts styled or regular component d.ts file to unstyled d.ts
- * @param {string} filename - the file of the styled or regular mui component
+ * @param filename - the file of the styled or regular mui component
*/
-function getUnstyledFilename(filename, definitionFile = false) {
+export function getUnstyledFilename(filename: string, definitionFile: boolean = false): string {
if (filename.indexOf('mui-base') > -1) {
return filename;
}
@@ -58,5 +53,3 @@ function getUnstyledFilename(filename, definitionFile = false) {
return definitionFile ? `${unstyledFile}.d.ts` : `${unstyledFile}.js`;
}
-
-export { getLineFeed, fixBabelGeneratorIssues, fixLineEndings, getUnstyledFilename };
diff --git a/packages/docs-utils/tsconfig.build.json b/packages/docs-utils/tsconfig.build.json
new file mode 100644
index 00000000000000..8992c3d55a7be0
--- /dev/null
+++ b/packages/docs-utils/tsconfig.build.json
@@ -0,0 +1,15 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./build",
+ "declaration": true,
+ "noEmit": false,
+ "composite": true,
+ "tsBuildInfoFile": "./build/.tsbuildinfo",
+ "target": "ES2020",
+ "module": "commonjs",
+ "types": ["node"]
+ },
+ "exclude": ["./test/*.ts"]
+}
diff --git a/packages/docs-utils/tsconfig.json b/packages/docs-utils/tsconfig.json
new file mode 100644
index 00000000000000..16c32dce4d09be
--- /dev/null
+++ b/packages/docs-utils/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "noEmit": true,
+ "moduleResolution": "node",
+ "types": ["node"],
+ "strict": true,
+ "esModuleInterop": true,
+ "isolatedModules": true
+ },
+ "include": ["./src/**/*.ts"]
+}
diff --git a/packages/typescript-to-proptypes/.npmignore b/packages/typescript-to-proptypes/.npmignore
new file mode 100644
index 00000000000000..81f0fda795522a
--- /dev/null
+++ b/packages/typescript-to-proptypes/.npmignore
@@ -0,0 +1 @@
+.tsbuildinfo
diff --git a/packages/typescript-to-proptypes/CHANGELOG.md b/packages/typescript-to-proptypes/CHANGELOG.md
index 05bcc91e529f42..92be29c879bcf8 100644
--- a/packages/typescript-to-proptypes/CHANGELOG.md
+++ b/packages/typescript-to-proptypes/CHANGELOG.md
@@ -1,176 +1,18 @@
# Changelog
-All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+This file documents changes in the `@mui-internal/typescript-to-proptypes` package.
+For changes before the package was forked, while it was and published as `typescript-to-proptypes`, see [CHANGELOG.old.md](./CHANGELOG.old.md).
-## [2.0.1](https://github.com/merceyz/typescript-to-proptypes/compare/v2.0.0...v2.0.1) (2020-06-02)
+Unfortunately changes done between forking the `typescript-to-proptypes` and publishing `@mui-internal/typescript-to-proptypes` are not documented.
-### Bug Fixes
+## 1.0.2
-- Use symbol type when there's no baseconstraint ([#23](https://github.com/merceyz/typescript-to-proptypes/issues/23)) ([0b170af](https://github.com/merceyz/typescript-to-proptypes/commit/0b170afb02a2edd1ea0b80406f1a86375c3a13f3))
+Fix the incorrectly published package.
-## [2.0.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.5.0...v2.0.0) (2020-05-31)
+## 1.0.1
-### ⚠ BREAKING CHANGES
+Fix the incorrectly published package.
-- Support for Node versions less than 10.3.0 has been dropped
+## 1.0.0
-### Features
-
-- consider squashed call signatures of function components ([#20](https://github.com/merceyz/typescript-to-proptypes/issues/20)) ([514d8ed](https://github.com/merceyz/typescript-to-proptypes/commit/514d8ed55375406a70640d64c4a166aa52e24ae2))
-
-### Bug Fixes
-
-- allow non-string literals ([#21](https://github.com/merceyz/typescript-to-proptypes/issues/21)) ([546e7ad](https://github.com/merceyz/typescript-to-proptypes/commit/546e7addc86198e641d3bfd3dd08ecb55c970600))
-
-### Build System
-
-- drop support for node versions less than 10.3.0 ([2fbca64](https://github.com/merceyz/typescript-to-proptypes/commit/2fbca64e0964509e1a74d29f564be41a78e9fa29))
-
-## [1.5.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.4.2...v1.5.0) (2020-04-06)
-
-### Features
-
-- **injector:** add reconcilePropTypes ([#10](https://github.com/merceyz/typescript-to-proptypes/issues/10)) ([7b0bff9](https://github.com/merceyz/typescript-to-proptypes/commit/7b0bff9666d1beb1bde445e92fbb702cf1fb3d89))
-- add `filenames` to component and proptype nodes ([#9](https://github.com/merceyz/typescript-to-proptypes/issues/9)) ([ce9a700](https://github.com/merceyz/typescript-to-proptypes/commit/ce9a7002c7fda27965b50e0b1af3ecef540a90e5))
-- **injector:** add `component` to `shouldInclude` ([#8](https://github.com/merceyz/typescript-to-proptypes/issues/8)) ([18a7fce](https://github.com/merceyz/typescript-to-proptypes/commit/18a7fcee1b3f7d64541fb0f9bd1de72e0ea0db5b))
-- **injector:** allow providing babel options ([2ab6f43](https://github.com/merceyz/typescript-to-proptypes/commit/2ab6f43ef4b785d20dd6f951b2f4b928a5521b53))
-
-### Bug Fixes
-
-- check nodeType for dom elements ([#13](https://github.com/merceyz/typescript-to-proptypes/issues/13)) ([fd028e6](https://github.com/merceyz/typescript-to-proptypes/commit/fd028e639bb28383d6e4f925368b6e2afacdbf23))
-- replace existing propTypes when removeExistingPropTypes ([#15](https://github.com/merceyz/typescript-to-proptypes/issues/15)) ([3166104](https://github.com/merceyz/typescript-to-proptypes/commit/3166104889d4f58fc22f85800664d2bb1fce6aff))
-- **injector:** always call injectPropTypes to allow shouldInclude to run ([277258d](https://github.com/merceyz/typescript-to-proptypes/commit/277258ddc73c3da816aba6fccb739c69dfe8e83a))
-- handle all props getting ignored by shouldInclude ([b69112e](https://github.com/merceyz/typescript-to-proptypes/commit/b69112e1011f089b6d5cb60f88ce75b6394252be))
-- **parser:** export ParserOptions ([3a5d55e](https://github.com/merceyz/typescript-to-proptypes/commit/3a5d55e68a723208a4b76e79d4bafe92ddf4f85a))
-
-## [1.4.2](https://github.com/merceyz/typescript-to-proptypes/compare/v1.4.1...v1.4.2) (2020-03-27)
-
-### Bug Fixes
-
-- build had a broken output ([97b0326](https://github.com/merceyz/typescript-to-proptypes/commit/97b0326c8b3b811fd5167cefa95a5dc1aa22a212))
-
-## [1.4.1](https://github.com/merceyz/typescript-to-proptypes/compare/v1.4.0...v1.4.1) (2020-03-27)
-
-### Bug Fixes
-
-- include string literal object keys as used ([#5](https://github.com/merceyz/typescript-to-proptypes/issues/5)) ([3fd7b70](https://github.com/merceyz/typescript-to-proptypes/commit/3fd7b703d30e650e6692f87d3929d4ae67314cb6))
-- unknown can be optional ([#7](https://github.com/merceyz/typescript-to-proptypes/issues/7)) ([c5e8ca3](https://github.com/merceyz/typescript-to-proptypes/commit/c5e8ca31e2cae20216b1f7e45c9f3ef5198b2f93))
-
-## [1.4.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.3.0...v1.4.0) (2019-11-16)
-
-### Bug Fixes
-
-- **parser:** handle prop of type ReactElement ([adfcca4](https://github.com/merceyz/typescript-to-proptypes/commit/adfcca4))
-
-### Features
-
-- **parser:** support forwardRef ([3f5c0c9](https://github.com/merceyz/typescript-to-proptypes/commit/3f5c0c9)), closes [#2](https://github.com/merceyz/typescript-to-proptypes/issues/2)
-
-## [1.3.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.5...v1.3.0) (2019-09-03)
-
-### Features
-
-- **generator:** add comment to proptype blocks ([2c5627e](https://github.com/merceyz/typescript-to-proptypes/commit/2c5627e))
-
-### [1.2.5](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.4...v1.2.5) (2019-09-03)
-
-### Bug Fixes
-
-- **parser:** use doctrine to unwrap comments ([53a9d43](https://github.com/merceyz/typescript-to-proptypes/commit/53a9d43))
-
-### Tests
-
-- add missing test config ([d00c7f2](https://github.com/merceyz/typescript-to-proptypes/commit/d00c7f2))
-
-## [1.2.4](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.3...v1.2.4) (2019-08-16)
-
-### Bug Fixes
-
-- **injector:** use require.resolve ([b9d04ea](https://github.com/merceyz/typescript-to-proptypes/commit/b9d04ea))
-
-## [1.2.3](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.2...v1.2.3) (2019-07-24)
-
-### Bug Fixes
-
-- **parser:** handle return type of JSX.Element | null ([cbe5564](https://github.com/merceyz/typescript-to-proptypes/commit/cbe5564))
-
-## [1.2.2](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.1...v1.2.2) (2019-07-23)
-
-### Bug Fixes
-
-- **parser:** remove leftover asterisk ([2e720df](https://github.com/merceyz/typescript-to-proptypes/commit/2e720df))
-
-## [1.2.1](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.0...v1.2.1) (2019-07-23)
-
-### Bug Fixes
-
-- **parser:** handle single line comments ([0025d53](https://github.com/merceyz/typescript-to-proptypes/commit/0025d53))
-
-## [1.2.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.1.0...v1.2.0) (2019-07-23)
-
-### Bug Fixes
-
-- **generator:** multiline comments ([d576597](https://github.com/merceyz/typescript-to-proptypes/commit/d576597))
-- **generator:** sort interface correctly ([f88c5fb](https://github.com/merceyz/typescript-to-proptypes/commit/f88c5fb))
-- **generator:** wrap prop name in quotes ([709a819](https://github.com/merceyz/typescript-to-proptypes/commit/709a819))
-- **parser:** don't modify comments ([95cd63e](https://github.com/merceyz/typescript-to-proptypes/commit/95cd63e))
-- **parser:** fallback to object if element is undefined ([eadaf3f](https://github.com/merceyz/typescript-to-proptypes/commit/eadaf3f))
-- **parser:** handle comments with just tags ([d0b0a82](https://github.com/merceyz/typescript-to-proptypes/commit/d0b0a82))
-- **parser:** handle comments with tags ([ad4dddd](https://github.com/merceyz/typescript-to-proptypes/commit/ad4dddd))
-- **parser:** handle optional any ([30f56ec](https://github.com/merceyz/typescript-to-proptypes/commit/30f56ec))
-- **parser:** handle optional React.ElementType ([c7a87fd](https://github.com/merceyz/typescript-to-proptypes/commit/c7a87fd))
-- **parser:** treat ComponentType as elementType ([53f1e21](https://github.com/merceyz/typescript-to-proptypes/commit/53f1e21))
-- export typescript as ts ([ba90e22](https://github.com/merceyz/typescript-to-proptypes/commit/ba90e22))
-
-### Features
-
-- **generator:** support instanceOf ([6bd563a](https://github.com/merceyz/typescript-to-proptypes/commit/6bd563a))
-- **injector:** control included props ([4f8eaa1](https://github.com/merceyz/typescript-to-proptypes/commit/4f8eaa1))
-- **injector:** remove existing proptypes ([d2a978c](https://github.com/merceyz/typescript-to-proptypes/commit/d2a978c))
-- **parser:** check const declarations of React.ComponentType ([cbd2eb6](https://github.com/merceyz/typescript-to-proptypes/commit/cbd2eb6))
-- **parser:** handle React.Component and Element instanceOf ([570d73b](https://github.com/merceyz/typescript-to-proptypes/commit/570d73b))
-- **parser:** support elementType ([448d5a6](https://github.com/merceyz/typescript-to-proptypes/commit/448d5a6))
-
-## [1.1.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.4...v1.1.0) (2019-07-15)
-
-### Bug Fixes
-
-- **generator:** don't pass shouldInclude on interfaceNode ([1302502](https://github.com/merceyz/typescript-to-proptypes/commit/1302502))
-
-### Features
-
-- **parser:** circular references ([7de51cc](https://github.com/merceyz/typescript-to-proptypes/commit/7de51cc))
-- **parser:** control included proptypes ([2952e78](https://github.com/merceyz/typescript-to-proptypes/commit/2952e78))
-- **parser:** objects / shapes ([81f1a82](https://github.com/merceyz/typescript-to-proptypes/commit/81f1a82))
-
-## [1.0.4](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.3...v1.0.4) (2019-07-10)
-
-### Bug Fixes
-
-- **generator:** omit null if proptype is optional ([21351a4](https://github.com/merceyz/typescript-to-proptypes/commit/21351a4))
-- **parser:** reactnode should make proptype optional ([c84b611](https://github.com/merceyz/typescript-to-proptypes/commit/c84b611))
-
-## [1.0.3](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.2...v1.0.3) (2019-07-10)
-
-### Bug Fixes
-
-- export types ([7583291](https://github.com/merceyz/typescript-to-proptypes/commit/7583291))
-
-## [1.0.2](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.1...v1.0.2) (2019-07-09)
-
-### Bug Fixes
-
-- **injector:** don't visit FunctionDeclarations more than once ([236276b](https://github.com/merceyz/typescript-to-proptypes/commit/236276b))
-
-## [1.0.1](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.0...v1.0.1) (2019-07-09)
-
-### Bug Fixes
-
-- **injector:** don't import prop-types if it's already imported ([9d4dfd1](https://github.com/merceyz/typescript-to-proptypes/commit/9d4dfd1))
-- **injector:** insert import after the first one ([6cb31a0](https://github.com/merceyz/typescript-to-proptypes/commit/6cb31a0))
-
-## 1.0.0 (2019-07-08)
-
-### Build System
-
-- disable incremental ([37b0277](https://github.com/merceyz/typescript-to-proptypes/commit/37b0277))
+Initial release as a `@mui-internal/typescript-to-proptypes` package.
diff --git a/packages/typescript-to-proptypes/CHANGELOG.old.md b/packages/typescript-to-proptypes/CHANGELOG.old.md
new file mode 100644
index 00000000000000..38d10c92ae4789
--- /dev/null
+++ b/packages/typescript-to-proptypes/CHANGELOG.old.md
@@ -0,0 +1,179 @@
+# Changelog
+
+This file documents changes in the @merceyz's `typescript-to-proptypes` package.
+For changes after the package was forked and published as `@mui-internal/typescript-to-proptypes`, see [CHANGELOG.md](./CHANGELOG.md).
+
+All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+
+## [2.0.1](https://github.com/merceyz/typescript-to-proptypes/compare/v2.0.0...v2.0.1) (2020-06-02)
+
+### Bug Fixes
+
+- Use symbol type when there's no baseconstraint ([#23](https://github.com/merceyz/typescript-to-proptypes/issues/23)) ([0b170af](https://github.com/merceyz/typescript-to-proptypes/commit/0b170afb02a2edd1ea0b80406f1a86375c3a13f3))
+
+## [2.0.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.5.0...v2.0.0) (2020-05-31)
+
+### ⚠ BREAKING CHANGES
+
+- Support for Node versions less than 10.3.0 has been dropped
+
+### Features
+
+- consider squashed call signatures of function components ([#20](https://github.com/merceyz/typescript-to-proptypes/issues/20)) ([514d8ed](https://github.com/merceyz/typescript-to-proptypes/commit/514d8ed55375406a70640d64c4a166aa52e24ae2))
+
+### Bug Fixes
+
+- allow non-string literals ([#21](https://github.com/merceyz/typescript-to-proptypes/issues/21)) ([546e7ad](https://github.com/merceyz/typescript-to-proptypes/commit/546e7addc86198e641d3bfd3dd08ecb55c970600))
+
+### Build System
+
+- drop support for node versions less than 10.3.0 ([2fbca64](https://github.com/merceyz/typescript-to-proptypes/commit/2fbca64e0964509e1a74d29f564be41a78e9fa29))
+
+## [1.5.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.4.2...v1.5.0) (2020-04-06)
+
+### Features
+
+- **injector:** add reconcilePropTypes ([#10](https://github.com/merceyz/typescript-to-proptypes/issues/10)) ([7b0bff9](https://github.com/merceyz/typescript-to-proptypes/commit/7b0bff9666d1beb1bde445e92fbb702cf1fb3d89))
+- add `filenames` to component and proptype nodes ([#9](https://github.com/merceyz/typescript-to-proptypes/issues/9)) ([ce9a700](https://github.com/merceyz/typescript-to-proptypes/commit/ce9a7002c7fda27965b50e0b1af3ecef540a90e5))
+- **injector:** add `component` to `shouldInclude` ([#8](https://github.com/merceyz/typescript-to-proptypes/issues/8)) ([18a7fce](https://github.com/merceyz/typescript-to-proptypes/commit/18a7fcee1b3f7d64541fb0f9bd1de72e0ea0db5b))
+- **injector:** allow providing babel options ([2ab6f43](https://github.com/merceyz/typescript-to-proptypes/commit/2ab6f43ef4b785d20dd6f951b2f4b928a5521b53))
+
+### Bug Fixes
+
+- check nodeType for dom elements ([#13](https://github.com/merceyz/typescript-to-proptypes/issues/13)) ([fd028e6](https://github.com/merceyz/typescript-to-proptypes/commit/fd028e639bb28383d6e4f925368b6e2afacdbf23))
+- replace existing propTypes when removeExistingPropTypes ([#15](https://github.com/merceyz/typescript-to-proptypes/issues/15)) ([3166104](https://github.com/merceyz/typescript-to-proptypes/commit/3166104889d4f58fc22f85800664d2bb1fce6aff))
+- **injector:** always call injectPropTypes to allow shouldInclude to run ([277258d](https://github.com/merceyz/typescript-to-proptypes/commit/277258ddc73c3da816aba6fccb739c69dfe8e83a))
+- handle all props getting ignored by shouldInclude ([b69112e](https://github.com/merceyz/typescript-to-proptypes/commit/b69112e1011f089b6d5cb60f88ce75b6394252be))
+- **parser:** export ParserOptions ([3a5d55e](https://github.com/merceyz/typescript-to-proptypes/commit/3a5d55e68a723208a4b76e79d4bafe92ddf4f85a))
+
+## [1.4.2](https://github.com/merceyz/typescript-to-proptypes/compare/v1.4.1...v1.4.2) (2020-03-27)
+
+### Bug Fixes
+
+- build had a broken output ([97b0326](https://github.com/merceyz/typescript-to-proptypes/commit/97b0326c8b3b811fd5167cefa95a5dc1aa22a212))
+
+## [1.4.1](https://github.com/merceyz/typescript-to-proptypes/compare/v1.4.0...v1.4.1) (2020-03-27)
+
+### Bug Fixes
+
+- include string literal object keys as used ([#5](https://github.com/merceyz/typescript-to-proptypes/issues/5)) ([3fd7b70](https://github.com/merceyz/typescript-to-proptypes/commit/3fd7b703d30e650e6692f87d3929d4ae67314cb6))
+- unknown can be optional ([#7](https://github.com/merceyz/typescript-to-proptypes/issues/7)) ([c5e8ca3](https://github.com/merceyz/typescript-to-proptypes/commit/c5e8ca31e2cae20216b1f7e45c9f3ef5198b2f93))
+
+## [1.4.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.3.0...v1.4.0) (2019-11-16)
+
+### Bug Fixes
+
+- **parser:** handle prop of type ReactElement ([adfcca4](https://github.com/merceyz/typescript-to-proptypes/commit/adfcca4))
+
+### Features
+
+- **parser:** support forwardRef ([3f5c0c9](https://github.com/merceyz/typescript-to-proptypes/commit/3f5c0c9)), closes [#2](https://github.com/merceyz/typescript-to-proptypes/issues/2)
+
+## [1.3.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.5...v1.3.0) (2019-09-03)
+
+### Features
+
+- **generator:** add comment to proptype blocks ([2c5627e](https://github.com/merceyz/typescript-to-proptypes/commit/2c5627e))
+
+### [1.2.5](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.4...v1.2.5) (2019-09-03)
+
+### Bug Fixes
+
+- **parser:** use doctrine to unwrap comments ([53a9d43](https://github.com/merceyz/typescript-to-proptypes/commit/53a9d43))
+
+### Tests
+
+- add missing test config ([d00c7f2](https://github.com/merceyz/typescript-to-proptypes/commit/d00c7f2))
+
+## [1.2.4](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.3...v1.2.4) (2019-08-16)
+
+### Bug Fixes
+
+- **injector:** use require.resolve ([b9d04ea](https://github.com/merceyz/typescript-to-proptypes/commit/b9d04ea))
+
+## [1.2.3](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.2...v1.2.3) (2019-07-24)
+
+### Bug Fixes
+
+- **parser:** handle return type of JSX.Element | null ([cbe5564](https://github.com/merceyz/typescript-to-proptypes/commit/cbe5564))
+
+## [1.2.2](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.1...v1.2.2) (2019-07-23)
+
+### Bug Fixes
+
+- **parser:** remove leftover asterisk ([2e720df](https://github.com/merceyz/typescript-to-proptypes/commit/2e720df))
+
+## [1.2.1](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.0...v1.2.1) (2019-07-23)
+
+### Bug Fixes
+
+- **parser:** handle single line comments ([0025d53](https://github.com/merceyz/typescript-to-proptypes/commit/0025d53))
+
+## [1.2.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.1.0...v1.2.0) (2019-07-23)
+
+### Bug Fixes
+
+- **generator:** multiline comments ([d576597](https://github.com/merceyz/typescript-to-proptypes/commit/d576597))
+- **generator:** sort interface correctly ([f88c5fb](https://github.com/merceyz/typescript-to-proptypes/commit/f88c5fb))
+- **generator:** wrap prop name in quotes ([709a819](https://github.com/merceyz/typescript-to-proptypes/commit/709a819))
+- **parser:** don't modify comments ([95cd63e](https://github.com/merceyz/typescript-to-proptypes/commit/95cd63e))
+- **parser:** fallback to object if element is undefined ([eadaf3f](https://github.com/merceyz/typescript-to-proptypes/commit/eadaf3f))
+- **parser:** handle comments with just tags ([d0b0a82](https://github.com/merceyz/typescript-to-proptypes/commit/d0b0a82))
+- **parser:** handle comments with tags ([ad4dddd](https://github.com/merceyz/typescript-to-proptypes/commit/ad4dddd))
+- **parser:** handle optional any ([30f56ec](https://github.com/merceyz/typescript-to-proptypes/commit/30f56ec))
+- **parser:** handle optional React.ElementType ([c7a87fd](https://github.com/merceyz/typescript-to-proptypes/commit/c7a87fd))
+- **parser:** treat ComponentType as elementType ([53f1e21](https://github.com/merceyz/typescript-to-proptypes/commit/53f1e21))
+- export typescript as ts ([ba90e22](https://github.com/merceyz/typescript-to-proptypes/commit/ba90e22))
+
+### Features
+
+- **generator:** support instanceOf ([6bd563a](https://github.com/merceyz/typescript-to-proptypes/commit/6bd563a))
+- **injector:** control included props ([4f8eaa1](https://github.com/merceyz/typescript-to-proptypes/commit/4f8eaa1))
+- **injector:** remove existing proptypes ([d2a978c](https://github.com/merceyz/typescript-to-proptypes/commit/d2a978c))
+- **parser:** check const declarations of React.ComponentType ([cbd2eb6](https://github.com/merceyz/typescript-to-proptypes/commit/cbd2eb6))
+- **parser:** handle React.Component and Element instanceOf ([570d73b](https://github.com/merceyz/typescript-to-proptypes/commit/570d73b))
+- **parser:** support elementType ([448d5a6](https://github.com/merceyz/typescript-to-proptypes/commit/448d5a6))
+
+## [1.1.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.4...v1.1.0) (2019-07-15)
+
+### Bug Fixes
+
+- **generator:** don't pass shouldInclude on interfaceNode ([1302502](https://github.com/merceyz/typescript-to-proptypes/commit/1302502))
+
+### Features
+
+- **parser:** circular references ([7de51cc](https://github.com/merceyz/typescript-to-proptypes/commit/7de51cc))
+- **parser:** control included proptypes ([2952e78](https://github.com/merceyz/typescript-to-proptypes/commit/2952e78))
+- **parser:** objects / shapes ([81f1a82](https://github.com/merceyz/typescript-to-proptypes/commit/81f1a82))
+
+## [1.0.4](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.3...v1.0.4) (2019-07-10)
+
+### Bug Fixes
+
+- **generator:** omit null if proptype is optional ([21351a4](https://github.com/merceyz/typescript-to-proptypes/commit/21351a4))
+- **parser:** reactnode should make proptype optional ([c84b611](https://github.com/merceyz/typescript-to-proptypes/commit/c84b611))
+
+## [1.0.3](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.2...v1.0.3) (2019-07-10)
+
+### Bug Fixes
+
+- export types ([7583291](https://github.com/merceyz/typescript-to-proptypes/commit/7583291))
+
+## [1.0.2](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.1...v1.0.2) (2019-07-09)
+
+### Bug Fixes
+
+- **injector:** don't visit FunctionDeclarations more than once ([236276b](https://github.com/merceyz/typescript-to-proptypes/commit/236276b))
+
+## [1.0.1](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.0...v1.0.1) (2019-07-09)
+
+### Bug Fixes
+
+- **injector:** don't import prop-types if it's already imported ([9d4dfd1](https://github.com/merceyz/typescript-to-proptypes/commit/9d4dfd1))
+- **injector:** insert import after the first one ([6cb31a0](https://github.com/merceyz/typescript-to-proptypes/commit/6cb31a0))
+
+## 1.0.0 (2019-07-08)
+
+### Build System
+
+- disable incremental ([37b0277](https://github.com/merceyz/typescript-to-proptypes/commit/37b0277))
diff --git a/packages/typescript-to-proptypes/README.md b/packages/typescript-to-proptypes/README.md
index 9e00c1692dadd0..4521bd893bca63 100644
--- a/packages/typescript-to-proptypes/README.md
+++ b/packages/typescript-to-proptypes/README.md
@@ -1,6 +1,9 @@
-# typescript-to-proptypes
+# @mui-internal/typescript-to-proptypes
-An API for converting [TypeScript](https://www.npmjs.com/package/typescript) definitions to [PropTypes](https://www.npmjs.com/package/prop-types) using the TypeScript Compiler API
+An API for converting [TypeScript](https://www.npmjs.com/package/typescript) definitions to [PropTypes](https://www.npmjs.com/package/prop-types) using the TypeScript Compiler API.
+
+This package has been adapted for MUI needs.
+It is not meant for general use.
## Support
@@ -15,3 +18,8 @@ An API for converting [TypeScript](https://www.npmjs.com/package/typescript) def
## License
This project is licensed under the terms of the [MIT license](/LICENSE).
+
+## Release
+
+1. Build the project: `pnpm build`
+2. Publish the build artifacts to npm: `pnpm release:publish`
diff --git a/packages/typescript-to-proptypes/package.json b/packages/typescript-to-proptypes/package.json
index 6146ecaeedb2e1..4cbcbda402c33b 100644
--- a/packages/typescript-to-proptypes/package.json
+++ b/packages/typescript-to-proptypes/package.json
@@ -1,32 +1,34 @@
{
- "name": "typescript-to-proptypes",
- "version": "5.0.0",
- "private": true,
- "description": "Generate proptypes from TypeScript declarations",
+ "name": "@mui-internal/typescript-to-proptypes",
+ "version": "1.0.2",
+ "author": "MUI Team",
+ "description": "Generate proptypes from TypeScript declarations. This is a fork of the typescript-to-proptypes package adapted for MUI needs. This is an internal package not meant for general use.",
+ "main": "build/index.js",
+ "exports": {
+ ".": "./build/index.js"
+ },
+ "types": "./build/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/mui/material-ui.git",
"directory": "packages/typescript-to-proptypes"
},
- "main": "src/index.ts",
- "author": "merceyz ",
"license": "MIT",
- "keywords": [
- "proptypes",
- "typescript",
- "react"
- ],
"scripts": {
+ "prebuild": "rimraf ./build",
+ "build": "tsc -b tsconfig.build.json",
+ "release:publish": "pnpm publish --tag latest",
+ "release:publish:dry-run": "pnpm publish --tag latest --registry=\"http://localhost:4873/\"",
"test": "cd ../../ && cross-env NODE_ENV=test mocha --config packages/typescript-to-proptypes/.mocharc.js 'packages/typescript-to-proptypes/**/*.test.ts'",
- "typescript": "tsc -p tsconfig.json"
+ "typescript": "tsc -b tsconfig.json"
},
"dependencies": {
- "@babel/core": "^7.23.9",
+ "@babel/core": "^7.23.7",
"@babel/plugin-syntax-class-properties": "^7.12.13",
"@babel/plugin-syntax-jsx": "^7.23.3",
"@babel/plugin-syntax-typescript": "^7.23.3",
- "@babel/types": "^7.23.9",
- "@mui-internal/api-docs-builder": "workspace:*",
+ "@babel/types": "^7.23.6",
+ "@mui-internal/docs-utils": "workspace:*",
"doctrine": "^3.0.0",
"lodash": "^4.17.21",
"typescript": "^5.3.3",
@@ -38,13 +40,16 @@
"@types/chai": "^4.3.11",
"@types/doctrine": "^0.0.9",
"@types/lodash": "^4.14.202",
- "@types/node": "^18.19.10",
+ "@types/node": "^18.19.8",
"@types/prettier": "^2.7.3",
"@types/react": "^18.2.48",
- "@types/uuid": "^9.0.8",
+ "@types/uuid": "^9.0.7",
"chai": "^4.4.1",
"fast-glob": "^3.3.2",
"prettier": "^2.8.8",
"rimraf": "^5.0.5"
+ },
+ "publishConfig": {
+ "access": "public"
}
}
diff --git a/packages/typescript-to-proptypes/src/getPropTypesFromFile.ts b/packages/typescript-to-proptypes/src/getPropTypesFromFile.ts
index 04214f5ca1290b..faecb397b603a4 100644
--- a/packages/typescript-to-proptypes/src/getPropTypesFromFile.ts
+++ b/packages/typescript-to-proptypes/src/getPropTypesFromFile.ts
@@ -3,8 +3,8 @@ import * as doctrine from 'doctrine';
import {
GetPropsFromComponentDeclarationOptions,
getPropsFromComponentNode,
-} from '@mui-internal/api-docs-builder/utils/getPropsFromComponentNode';
-import { TypeScriptProject } from '@mui-internal/api-docs-builder/utils/createTypeScriptProject';
+ TypeScriptProject,
+} from '@mui-internal/docs-utils';
import {
createUnionType,
createUndefinedType,
diff --git a/packages/typescript-to-proptypes/tsconfig.build.json b/packages/typescript-to-proptypes/tsconfig.build.json
new file mode 100644
index 00000000000000..8992c3d55a7be0
--- /dev/null
+++ b/packages/typescript-to-proptypes/tsconfig.build.json
@@ -0,0 +1,15 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./build",
+ "declaration": true,
+ "noEmit": false,
+ "composite": true,
+ "tsBuildInfoFile": "./build/.tsbuildinfo",
+ "target": "ES2020",
+ "module": "commonjs",
+ "types": ["node"]
+ },
+ "exclude": ["./test/*.ts"]
+}
diff --git a/packages/typescript-to-proptypes/tsconfig.json b/packages/typescript-to-proptypes/tsconfig.json
index 9a70107eff7e6c..9939012a1f603f 100644
--- a/packages/typescript-to-proptypes/tsconfig.json
+++ b/packages/typescript-to-proptypes/tsconfig.json
@@ -1,12 +1,12 @@
{
"compilerOptions": {
- "module": "commonjs",
- "esModuleInterop": true,
- "lib": ["ES2018"],
- "strict": true,
+ "noEmit": true,
"moduleResolution": "node",
- "types": ["node"],
- "noEmit": true
+ "types": ["node", "mocha"],
+ "strict": true,
+ "esModuleInterop": true,
+ "isolatedModules": true
},
- "include": ["src"]
+ "include": ["./src/*.ts", "./test/*.ts"],
+ "references": [{ "path": "../docs-utils/tsconfig.build.json" }]
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 50b7cc7884be7c..7379218aebf565 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -95,12 +95,15 @@ importers:
'@mui-internal/api-docs-builder-core':
specifier: workspace:^
version: link:packages/api-docs-builder-core
- '@mui-internal/docs-utilities':
+ '@mui-internal/docs-utils':
specifier: workspace:^
- version: link:packages/docs-utilities
+ version: link:packages/docs-utils
'@mui-internal/test-utils':
specifier: workspace:^
version: link:packages/test-utils
+ '@mui-internal/typescript-to-proptypes':
+ specifier: workspace:^
+ version: link:packages/typescript-to-proptypes
'@mui/joy':
specifier: workspace:*
version: link:packages/mui-joy/build
@@ -329,9 +332,6 @@ importers:
typescript:
specifier: ^5.3.3
version: 5.3.3
- typescript-to-proptypes:
- specifier: workspace:^
- version: link:packages/typescript-to-proptypes
webpack:
specifier: ^5.90.0
version: 5.90.0(esbuild@0.19.11)(webpack-cli@5.1.4)
@@ -830,12 +830,15 @@ importers:
'@babel/preset-typescript':
specifier: ^7.23.3
version: 7.23.3(@babel/core@7.23.9)
- '@mui-internal/docs-utilities':
+ '@mui-internal/docs-utils':
specifier: workspace:^
- version: link:../packages/docs-utilities
+ version: link:../packages/docs-utils
'@mui-internal/test-utils':
specifier: workspace:^
version: link:../packages/test-utils
+ '@mui-internal/typescript-to-proptypes':
+ specifier: workspace:^
+ version: link:../packages/typescript-to-proptypes
'@types/autosuggest-highlight':
specifier: ^3.2.3
version: 3.2.3
@@ -896,9 +899,6 @@ importers:
tailwindcss:
specifier: ^3.4.1
version: 3.4.1
- typescript-to-proptypes:
- specifier: workspace:^
- version: link:../packages/typescript-to-proptypes
yargs:
specifier: ^17.7.2
version: 17.7.2
@@ -914,9 +914,9 @@ importers:
'@babel/traverse':
specifier: ^7.23.9
version: 7.23.9
- '@mui-internal/docs-utilities':
+ '@mui-internal/docs-utils':
specifier: workspace:^
- version: link:../docs-utilities
+ version: link:../docs-utils
'@mui/markdown':
specifier: workspace:^
version: link:../markdown
@@ -1025,7 +1025,14 @@ importers:
specifier: ^5.3.3
version: 5.3.3
- packages/docs-utilities: {}
+ packages/docs-utils:
+ dependencies:
+ rimraf:
+ specifier: ^5.0.5
+ version: 5.0.5
+ typescript:
+ specifier: ^5.1.6
+ version: 5.3.3
packages/eslint-plugin-material-ui:
dependencies:
@@ -2245,9 +2252,9 @@ importers:
'@babel/types':
specifier: ^7.23.9
version: 7.23.9
- '@mui-internal/api-docs-builder':
+ '@mui-internal/docs-utils':
specifier: workspace:*
- version: link:../api-docs-builder
+ version: link:../docs-utils
doctrine:
specifier: ^3.0.0
version: 3.0.0
@@ -2286,7 +2293,7 @@ importers:
specifier: ^18.2.48
version: 18.2.48
'@types/uuid':
- specifier: ^9.0.8
+ specifier: ^9.0.7
version: 9.0.8
chai:
specifier: ^4.4.1
diff --git a/scripts/generateProptypes.ts b/scripts/generateProptypes.ts
index c0409553545ed3..aa9d7d63766a14 100644
--- a/scripts/generateProptypes.ts
+++ b/scripts/generateProptypes.ts
@@ -10,12 +10,12 @@ import {
fixBabelGeneratorIssues,
fixLineEndings,
getUnstyledFilename,
-} from '@mui-internal/docs-utilities';
+} from '@mui-internal/docs-utils';
import {
getPropTypesFromFile,
injectPropTypesInFile,
InjectPropTypesInFileOptions,
-} from 'typescript-to-proptypes';
+} from '@mui-internal/typescript-to-proptypes';
import {
createTypeScriptProjectBuilder,
TypeScriptProject,
diff --git a/tsconfig.json b/tsconfig.json
index 532d36d97c5a9d..991d9c8a764455 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -51,7 +51,8 @@
"@mui/zero-tag-processor/*": ["./packages/zero-tag-processor/src/*"],
"@mui/zero-vite-plugin": ["./packages/zero-vite-plugin/src"],
"@mui/zero-vite-plugin/*": ["./packages/zero-vite-plugin/src/*"],
- "typescript-to-proptypes": ["./packages/typescript-to-proptypes/src"]
+ "@mui-internal/docs-utils": ["./packages/docs-utils/src"],
+ "@mui-internal/typescript-to-proptypes": ["./packages/typescript-to-proptypes/src"]
},
// Otherwise we get react-native typings which conflict with dom.lib.
"types": ["node", "react"]
diff --git a/webpackBaseConfig.js b/webpackBaseConfig.js
index 4feb6aa97b14e2..ac26167c3cabd7 100644
--- a/webpackBaseConfig.js
+++ b/webpackBaseConfig.js
@@ -25,7 +25,11 @@ module.exports = {
'@mui/material-nextjs': path.resolve(__dirname, './packages/mui-material-nextjs/src'),
'@mui/joy': path.resolve(__dirname, './packages/mui-joy/src'),
'@mui/zero-runtime': path.resolve(__dirname, './packages/zero-runtime/src'),
- 'typescript-to-proptypes': path.resolve(__dirname, './packages/typescript-to-proptypes/src'),
+ '@mui-internal/docs-utils': path.resolve(__dirname, './packages/docs-utils/src'),
+ '@mui-internal/typescript-to-proptypes': path.resolve(
+ __dirname,
+ './packages/typescript-to-proptypes/src',
+ ),
docs: path.resolve(__dirname, './docs'),
},
extensions: ['.js', '.ts', '.tsx', '.d.ts'],
From 6385ce1244c8f4c5846fca253c291b2eacf4809e Mon Sep 17 00:00:00 2001
From: Smileek
Date: Fri, 2 Feb 2024 14:07:58 +0300
Subject: [PATCH 026/120] [docs] Fix URL and typo in CONTRIBUTING.md (#40899)
Co-authored-by: Andrei Sieedugin
---
CONTRIBUTING.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cc0577d979c44f..60a57fd49d0273 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -40,10 +40,10 @@ Working on your first pull request? You can learn how in this free video series:
Get started with [good first issues](https://github.com/mui/material-ui/issues?q=is:open+is:issue+label:"good+first+issue"), which have a limited scope and a working solution that's already been discussed.
This makes them ideal for newer developers, or those who are new to these libraries and want to see how the contribution process works.
-We also have a list of [good to take issues](https://github.com/mui/material-ui/issues?q=is:open+is:issue+label:"good+to+take"), which are issues that have already been at least partially resolved in discussion, to the point that it's clear what to do next.
+We also have a list of [ready to take issues](https://github.com/mui/material-ui/issues?q=is:open+is:issue+label:"ready+to+take"), which are issues that have already been at least partially resolved in discussion, to the point that it's clear what to do next.
These issues are great for developers who want to reduce their chances of falling down a rabbit hole in search of a solution.
-Of course, you can work on any other issue you like—the "good first" and "good to take" issues are simply those where the scope and timeline may be better defined.
+Of course, you can work on any other issue you like—the "good first" and "ready to take" issues are simply those where the scope and timeline may be better defined.
Pull requests for other issues, or completely novel problems, may take a bit longer to review if they don't fit into our current development cycle.
If you decide to fix an issue, please make sure to check the comment thread in case somebody is already working on a fix.
From bc212467e7ebc75bec71cb6cd951041ec1c01a22 Mon Sep 17 00:00:00 2001
From: Raffaella Luzi Stoutland <136349774+rluzists1@users.noreply.github.com>
Date: Fri, 2 Feb 2024 14:38:53 +0100
Subject: [PATCH 027/120] [website] Add Customer Support Agent role to careers
page (#40890)
Signed-off-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
---
docs/pages/careers.tsx | 22 +++++-----
docs/pages/careers/support-agent.js | 7 +++
docs/pages/careers/support-agent.md | 66 ++++++++++++++++++-----------
3 files changed, 60 insertions(+), 35 deletions(-)
create mode 100644 docs/pages/careers/support-agent.js
diff --git a/docs/pages/careers.tsx b/docs/pages/careers.tsx
index 1ebc9fd6879637..9f9abcbb89e8df 100644
--- a/docs/pages/careers.tsx
+++ b/docs/pages/careers.tsx
@@ -188,6 +188,17 @@ const openRolesData = [
},
],
},
+ {
+ title: 'Support',
+ roles: [
+ {
+ title: 'Customer Support Agent',
+ description:
+ 'You will help MUI provide timely and efficient support to our customers and continue to streamline our customer operations across the board.',
+ url: '/careers/support-agent/',
+ },
+ ],
+ },
];
const nextRolesData = [
@@ -262,16 +273,7 @@ const nextRolesData = [
},
],
},
- {
- title: 'Support',
- roles: [
- {
- title: 'Support Agent',
- description:
- 'You will provide support for the customers. You will directly impact customer satisfaction and success.',
- },
- ],
- },
+
{
title: 'Marketing',
roles: [
diff --git a/docs/pages/careers/support-agent.js b/docs/pages/careers/support-agent.js
new file mode 100644
index 00000000000000..6eade6b4005051
--- /dev/null
+++ b/docs/pages/careers/support-agent.js
@@ -0,0 +1,7 @@
+import * as React from 'react';
+import TopLayoutCareers from 'docs/src/modules/components/TopLayoutCareers';
+import * as pageProps from 'docs/pages/careers/support-agent.md?@mui/markdown';
+
+export default function Page() {
+ return ;
+}
diff --git a/docs/pages/careers/support-agent.md b/docs/pages/careers/support-agent.md
index a3fe251e5fd92e..e8b2e260a41d67 100644
--- a/docs/pages/careers/support-agent.md
+++ b/docs/pages/careers/support-agent.md
@@ -1,26 +1,26 @@
-# Support Agent — Store (future role)
+# Customer Support Agent
-You will provide support for the customers of MUI Store. You will directly impact customers' satisfaction and success.
+You will help MUI provide timely and efficient support to our customers and continue to streamline our customer operations across the board.
## Details of the role
-- **Location**: Remote (preference for UTC-6 to UTC+5).
+- **Location**: Remote (working hours are UTC 15:00 to UTC 23:00).
- **Type of work**: Full-time (contractor or employee [depending on circumstances](https://mui-org.notion.site/Hiring-FAQ-64763b756ae44c37b47b081f98915501#494af1f358794028beb4b7697b5d3102)).
-- We're a **remote** company, we prefer asynchronous communication over meetings.
+- We're a **remote** company, operating mostly asynchronously.
## The company
-MUI's story began in 2014 with Material UI, the most successful React implementation of Google's Material Design.
-Today, Material UI stands as one of the most popular open-source libraries on GitHub and has paved the way for the fully-fledged startup known as MUI (founded in 2019), which now boasts an ever-expanding ecosystem of React UI products.
-We're a company of 31+ people as of late 2023, and we're growing.
+MUI's story began in 2014 with Material UI, the most successful React implementation of Google's Material Design.
+Today, Material UI stands as one of the most popular open-source libraries on GitHub and has paved the way for the fully-fledged startup known as MUI (founded in 2019), which now boasts an ever-expanding ecosystem of React UI products.
+We're a company of 31+ people as of early 2024, and we're growing.
## The products
-MUI is best known for our flagship product, Material UI—but this is just one of three core component libraries we maintain.
-Base UI is our headless component library, and Joy UI is a sister library to Material UI that implements our own in-house Joy Design system.
+MUI is best known for our flagship product, Material UI—but this is just one of three core component libraries we maintain.
+Base UI is our headless component library, and Joy UI is a sister library to Material UI that implements our own in-house Joy Design system.
We also host Design Kits and pre-built Templates.
-Beyond the core libraries, MUI X offers advanced components like the Data Grid, Date and Time Pickers, and Charts, for more complex user interactions and data visualization needs.
+Beyond the core libraries, MUI X offers advanced components like the Data Grid, Date and Time Pickers, and Charts, for more complex user interactions and data visualization needs.
We're also making ambitious moves to incorporate our full suite of components into Toolpad, a low-code admin builder tool for assembling full-stack apps faster than ever.
@@ -37,45 +37,61 @@ For additional details about the culture, you can check our [careers](https://mu
## Why we're hiring
-The support on the store is currently almost exclusively done by the executive team of the company (e.g. CSO). This team does no longer has enough bandwidth. You will be responsible to step up and carry forward the support provided to customers of the store on multiple fronts: answering license questions, processing purchase orders, granting refunds, identifying recurring problems, and a lot more.
+One of MUI's company values is to [#putcommunityfirst](https://www.notion.so/mui-org/Values-behaviors-d3a1e1c60e2a4c0782f770cceada54bd?pvs=4#63393bde7da14f0698de0653f07a8dc7), and that includes high quality and timely responses to customer support requests. While technical support for bugs and features is provided by our product and engineering teams, all sales and product inquiries are currently handled by a small team of two support and operations agents. We're looking to add a support-focused agent to this stellar team, to ensure our resources and bandwidth continue to match our customer needs.
Overall, both our open-source community and our premium products are growing fast (x2 YoY).
We need talented people to keep that going!
-## The role
-
### Why this is interesting
-You will be in the tech industry. We offer flexibility in your work day.
+This is an opportunity to work fully remote with an agile, industry-leading company.
+We offer a dynamic work environment that is best suited to independent learners who are eager to proactively dig into customer requests and who enjoy problem-solving independently.
Our products empower React developers to build awesome applications faster – we see millions of developers on MUI's docs every year, one million a month.
+## The role
+
+As a customer support agent, you will focus on delivering consistent, high quality support to our customers, providing product information, sales quotes, and filling out compliance requests with the support of our Head of Operations.
+This could include handling requests that are not yet documented in our existing knowledge base, and working with the team to create clear guidelines around common support requests and agreed upon responses.
+While this is not a technical role, having a basic understanding of what a UI library is and how they work will be fundamental.
+
+Taking initiative, actively documenting, and being comfortable with new challenges are the main keys to success in the role.
+
### What you'll do on a day-to-day basis
Depending on the day, you'll:
-- Respond to customer inquiries via email in a clear, concise, and comprehensive manner.
- You might provide refunds, answer license questions, process purchase-orders, etc.
-- Assist in writing and maintaining the FAQs and guides.
-- Suggest opportunities to make customers happier for our product team.
-- Suggest opportunities to improve the quality and efficiency of our customer service operation.
+- Manage our queue of customer support tickets and execute associated tasks for resolution, including but not limited to processing refunds, deleting user accounts, processing quote requests, and answering FAQs
+- Contribute to our support request types and macros database, making sure it's detailed, clear, and up to date
+- Maintain our ticket database for analytics and historical reference by leveraging tags, internal comments, and creating canned responses where appropriate
+- Suggest and implement workflow improvements, including automations and helpful reports to stay on top of ticket volume
+- Moderate our store reviews and escalate important feedback to the relevant parties (internal or store contributors)
+- Respond to Paypal & Stripe disputes
+- Actively follow-up on critical customer communications, such as overdue invoices
+- Collaborate with a Customer Success Engineer and a Product Engineer for our Store to find new solutions to recurring customer pain points (updating legal documents for clarity, creating new internal apps for frequent use cases, adding feature requests to GitHub)
+- Stay up to date on any major releases or changes to our product offerings by attending monthly company meetings and incorporating any changes to our [legal pages](https://mui.com/legal/) in your work
+- Create Notion pages to suggest opportunities to improve the quality and efficiency of our customer service operation overall
## Who we're looking for
### Required
-- 1+ years' experience in a Customer Support role.
-- Exceptional writing skills (be clear and concise).
-- Experience working remotely.
+- **Independent learning skills:** we operate mainly on documentation training and feedback, so having someone who can learn by reading through Notion pages, previous tickets, and written instructions and then responding to async feedback is key.
+- **Excellent communication skills:** the bulk of the work will be interpreting and responding to customer inquiries, so clear writing skills are invaluable.
+- **Patience and curiosity:** support requests can be tedious and confusing sometimes! We need someone who knows how to follow-up for clarification, and who is committed to resolving requests with kindness and knowledgeability.
+- **Organised thinking:** responding to tickets requires an ability to interpret an incoming ticket based on existing or new patterns (agreement terms, support types, product promises, etc) and to match requests with the appropriate responses (from macros, previous tickets, or templates).
+- **Self-management:** as a remote and async company, we firmly avoid micromanaging practices, relying on everyone to leverage their own strategies for productivity and focusing on output, not process.
+- **Organised documenter:** we thrive when using thorough documentation and categorisation, meaning tickets are labeled, patterns become templates, and ideas for automation/improvement become entries in our project database.
+- **Comfortable around technical language:** engineering skills are absolutely not required, however you will need some basic literacy around web development and digital products work (downloads, installs, upgrades, updates, bugs, etc).
### Nice to have (but not required)
-- Basic knowledge of web development.
+Experience (1+ years) providing customer support for similar companies (developer tools, IT tools, digital products) will be considered a bonus.
## Benefits and compensation
-Competitive compensation depending on the profile and location of up to $20/h.
-You can find the other perks & benefits on the [careers](https://mui.com/careers/#perks-and-benefits) page.
+**Compensation bands:** 20-30k USD, or equivalent in local currency, per year.
+You can find our perks & benefits on the [careers](/careers/#perks-and-benefits/) page.
## How to apply
From 62f67a3244169ecba8deb32ed8fad468d7eb23b2 Mon Sep 17 00:00:00 2001
From: Olivier Tassinari
Date: Sat, 3 Feb 2024 17:46:28 +0100
Subject: [PATCH 028/120] [code-infra] Simplify bug reproduction (#40833)
---
.github/ISSUE_TEMPLATE/1.bug.yml | 2 +-
.../base/getting-started/support/support.md | 11 +++++++++++
.../joy/getting-started/support/support.md | 11 +++++++++++
.../getting-started/support/support.md | 11 +++++++++++
.../system/getting-started/support/support.md | 11 +++++++++++
docs/public/_redirects | 6 +++---
.../static/docs-infra/forking-an-example.png | Bin 0 -> 48222 bytes
docs/public/static/docs/forking-an-example.png | Bin 122037 -> 0 bytes
8 files changed, 48 insertions(+), 4 deletions(-)
create mode 100644 docs/public/static/docs-infra/forking-an-example.png
delete mode 100644 docs/public/static/docs/forking-an-example.png
diff --git a/.github/ISSUE_TEMPLATE/1.bug.yml b/.github/ISSUE_TEMPLATE/1.bug.yml
index faf654a00f515b..e765d2527d1d06 100644
--- a/.github/ISSUE_TEMPLATE/1.bug.yml
+++ b/.github/ISSUE_TEMPLATE/1.bug.yml
@@ -26,7 +26,7 @@ body:
description: |
**⚠️ Issues that we can't reproduce can't be fixed.**
- Please provide a link to a live example and an unambiguous set of steps to reproduce this bug. As a starting point, we recommend you browse our [documentation](https://mui.com/material-ui/getting-started/installation/), and [select](https://mui.com/static/docs/forking-an-example.png) the closest example to your use case. Or you can use the [official template](https://mui.com/r/issue-template) to build a reproduction case.
+ Please provide a link to a live example and an unambiguous set of steps to reproduce this bug. See our [documentation](https://mui.com/material-ui/getting-started/support/#bug-reproductions) on how to build a reproduction case.
value: |
Link to live example: (required)
diff --git a/docs/data/base/getting-started/support/support.md b/docs/data/base/getting-started/support/support.md
index 1d5193c4f2b38d..4281934a762db4 100644
--- a/docs/data/base/getting-started/support/support.md
+++ b/docs/data/base/getting-started/support/support.md
@@ -21,6 +21,17 @@ If you think you've found a bug, or you have a new feature idea:
- Please don't group multiple topics in one issue.
- Please don't comment "+1" on an issue. It spams the maintainers and doesn't help move the issue forward. Use GitHub reactions instead (👍).
+### Bug reproductions
+
+We require bug reports to be accompanied by a minimal reproduction.
+It significantly increases the odds of fixing the problem.
+You have a few options possible to provide it:
+
+- You can browse the documentation, find an example close to your use case, and then open it in a live editor:
+ [![Forking an example](/static/docs-infra/forking-an-example.png)](/base-ui/react-button/#introduction)
+
+- You can use a starter React template to build a reproduction case with [JavaScript](https://stackblitz.com/fork/github/stackblitz/starters/tree/main/react) or [TypeScript](https://stackblitz.com/fork/github/stackblitz/starters/tree/main/react-ts).
+
## Stack Overflow
We use Stack Overflow for how-to questions. Answers are crowdsourced from expert developers in the Base UI community as well as Base UI maintainers.
diff --git a/docs/data/joy/getting-started/support/support.md b/docs/data/joy/getting-started/support/support.md
index 5e3932692e1117..8671089069ee8c 100644
--- a/docs/data/joy/getting-started/support/support.md
+++ b/docs/data/joy/getting-started/support/support.md
@@ -21,6 +21,17 @@ If you think you've found a bug, or you have a new feature idea:
- Please don't group multiple topics in one issue.
- Please don't comment "+1" on an issue. It spams the maintainers and doesn't help move the issue forward. Use GitHub reactions instead (👍).
+### Bug reproductions
+
+We require bug reports to be accompanied by a minimal reproduction.
+It significantly increases the odds of fixing the problem.
+You have a few options possible to provide it:
+
+- You can browse the documentation, find an example close to your use case, and then open it in a live editor:
+ [![Forking an example](/static/docs-infra/forking-an-example.png)](/joy-ui/react-button/#basics)
+
+- You can use a starter React template to build a reproduction case with [JavaScript](https://stackblitz.com/fork/github/stackblitz/starters/tree/main/react) or [TypeScript](https://stackblitz.com/fork/github/stackblitz/starters/tree/main/react-ts).
+
## Stack Overflow
We use Stack Overflow for how-to questions. Answers are crowdsourced from expert developers in the Joy UI community as well as Joy UI maintainers.
diff --git a/docs/data/material/getting-started/support/support.md b/docs/data/material/getting-started/support/support.md
index aabb3e6614e9f3..ab8f926563215a 100644
--- a/docs/data/material/getting-started/support/support.md
+++ b/docs/data/material/getting-started/support/support.md
@@ -21,6 +21,17 @@ If you think you've found a bug, or you have a new feature idea:
- Please don't group multiple topics in one issue.
- Please don't comment "+1" on an issue. It spams the maintainers and doesn't help move the issue forward. Use GitHub reactions instead (👍).
+### Bug reproductions
+
+We require bug reports to be accompanied by a minimal reproduction.
+It significantly increases the odds of fixing the problem.
+You have a few options possible to provide it:
+
+- You can browse the documentation, find an example close to your use case, and then open it in a live editor:
+ [![Forking an example](/static/docs-infra/forking-an-example.png)](/material-ui/react-button/#basic-button)
+
+- You can use a starter React template to build a reproduction case with [JavaScript](https://stackblitz.com/fork/github/stackblitz/starters/tree/main/react) or [TypeScript](https://stackblitz.com/fork/github/stackblitz/starters/tree/main/react-ts).
+
## Stack Overflow
We use Stack Overflow for how-to questions. Answers are crowdsourced from expert developers in the Material UI community as well as Material UI maintainers.
diff --git a/docs/data/system/getting-started/support/support.md b/docs/data/system/getting-started/support/support.md
index 03449997609e5d..b858ed20c173b8 100644
--- a/docs/data/system/getting-started/support/support.md
+++ b/docs/data/system/getting-started/support/support.md
@@ -21,6 +21,17 @@ If you think you've found a bug, or you have a new feature idea:
- Please don't group multiple topics in one issue.
- Please don't comment "+1" on an issue. It spams the maintainers and doesn't help move the issue forward. Use GitHub reactions instead (👍).
+### Bug reproductions
+
+We require bug reports to be accompanied by a minimal reproduction.
+It significantly increases the odds of fixing the problem.
+You have a few options possible to provide it:
+
+- You can browse the documentation, find an example close to your use case, and then open it in a live editor:
+ [![Forking an example](/static/docs-infra/forking-an-example.png)](/system/borders/#additive)
+
+- You can use a starter React template to build a reproduction case with [JavaScript](https://stackblitz.com/fork/github/stackblitz/starters/tree/main/react) or [TypeScript](https://stackblitz.com/fork/github/stackblitz/starters/tree/main/react-ts).
+
## Stack Overflow
We use Stack Overflow for how-to questions. Answers are crowdsourced from expert developers in the MUI System community as well as MUI System maintainers.
diff --git a/docs/public/_redirects b/docs/public/_redirects
index 8434c880d8d00c..37ff08871dc40a 100644
--- a/docs/public/_redirects
+++ b/docs/public/_redirects
@@ -16,9 +16,9 @@
/r/pseudo-classes-guide /material-ui/customization/how-to-customize/#state-classes 302
/r/state-classes-guide /material-ui/customization/how-to-customize/#state-classes 302
/r/input-component-ref-interface /material-ui/react-text-field/#integration-with-3rd-party-input-libraries 302
-/r/issue-template https://codesandbox.io/p/sandbox/material-ui-issue-latest-s2dsx 302
-/r/issue-template-next https://codesandbox.io/p/sandbox/material-ui-issue-next-o7xkt 302
-/r/issue-template-latest https://codesandbox.io/p/sandbox/material-ui-issue-latest-s2dsx 302
+/r/issue-template https://stackblitz.com/fork/github/stackblitz/starters/tree/main/react-ts 302
+/r/issue-template-next https://stackblitz.com/fork/github/stackblitz/starters/tree/main/react-ts 302
+/r/issue-template-latest https://stackblitz.com/fork/github/stackblitz/starters/tree/main/react-ts 302
/r/ts-issue-template https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcA5FDvmQNwBQokscA3nXHAPSdwwAWWOLhKQAdllEx0ATwgBXOHNRYAJnQC+cIiXIABEHOCcQyGFijBkAGzJ06BOaPzAIouABEsICAAoAlKzsXDwAmvJQQhAqWBpAA 302
/r/custom-component-variants /material-ui/customization/how-to-customize/#adding-new-component-variants 302
/r/migration-v4 /material-ui/migration/migration-v4/ 302
diff --git a/docs/public/static/docs-infra/forking-an-example.png b/docs/public/static/docs-infra/forking-an-example.png
new file mode 100644
index 0000000000000000000000000000000000000000..d1a46bb379590bac20c7ae9a7f80a82571a01aa7
GIT binary patch
literal 48222
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