diff --git a/docs/data/material/components/avatars/Spacing.js b/docs/data/material/components/avatars/Spacing.js new file mode 100644 index 00000000000000..c0aebda531a9cf --- /dev/null +++ b/docs/data/material/components/avatars/Spacing.js @@ -0,0 +1,26 @@ +import * as React from 'react'; +import Avatar from '@mui/material/Avatar'; +import AvatarGroup from '@mui/material/AvatarGroup'; +import Stack from '@mui/material/Stack'; + +export default function Spacing() { + return ( + + + + + + + + + + + + + + + + + + ); +} diff --git a/docs/data/material/components/avatars/Spacing.tsx b/docs/data/material/components/avatars/Spacing.tsx new file mode 100644 index 00000000000000..c0aebda531a9cf --- /dev/null +++ b/docs/data/material/components/avatars/Spacing.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import Avatar from '@mui/material/Avatar'; +import AvatarGroup from '@mui/material/AvatarGroup'; +import Stack from '@mui/material/Stack'; + +export default function Spacing() { + return ( + + + + + + + + + + + + + + + + + + ); +} diff --git a/docs/data/material/components/avatars/Spacing.tsx.preview b/docs/data/material/components/avatars/Spacing.tsx.preview new file mode 100644 index 00000000000000..557dd187523778 --- /dev/null +++ b/docs/data/material/components/avatars/Spacing.tsx.preview @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/data/material/components/avatars/avatars.md b/docs/data/material/components/avatars/avatars.md index 08362acff64506..1901658a04164e 100644 --- a/docs/data/material/components/avatars/avatars.md +++ b/docs/data/material/components/avatars/avatars.md @@ -77,6 +77,12 @@ The `renderSurplus` prop is useful when you need to render the surplus based on {{"demo": "CustomSurplusAvatars.js"}} +### Spacing + +You can change the spacing between avatars using the `spacing` prop. You can use one of the presets (`"medium"`, the default, or `"small"`) or set a custom numeric value. + +{{"demo": "Spacing.js"}} + ## With badge {{"demo": "BadgeAvatars.js"}} diff --git a/packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts b/packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts new file mode 100644 index 00000000000000..2d094540339743 --- /dev/null +++ b/packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts @@ -0,0 +1,24 @@ +import * as React from 'react'; + +/** + * Returns the ref of a React node handling differences between React 19 and older versions. + * It will return null if the node is not a valid React element. + * + * @param element React.ReactNode + * @returns React.Ref | null + * + * @deprecated Use getReactElementRef instead + */ +export default function getReactNodeRef(element: React.ReactNode): React.Ref | null { + if (!element || !React.isValidElement(element)) { + return null; + } + + // 'ref' is passed as prop in React 19, whereas 'ref' is directly attached to children in older versions + return (element.props as any).propertyIsEnumerable('ref') + ? (element.props as any).ref + : // @ts-expect-error element.ref is not included in the ReactElement type + // We cannot check for it, but isValidElement is true at this point + // https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/70189 + element.ref; +} diff --git a/packages/mui-utils/src/getReactNodeRef/index.ts b/packages/mui-utils/src/getReactNodeRef/index.ts new file mode 100644 index 00000000000000..4b8dbacb937578 --- /dev/null +++ b/packages/mui-utils/src/getReactNodeRef/index.ts @@ -0,0 +1 @@ +export { default } from './getReactNodeRef'; diff --git a/packages/mui-utils/src/index.ts b/packages/mui-utils/src/index.ts index e62a34808a4252..d1b651419059a1 100644 --- a/packages/mui-utils/src/index.ts +++ b/packages/mui-utils/src/index.ts @@ -27,6 +27,7 @@ export { default as unstable_useForkRef } from './useForkRef'; export { default as unstable_useLazyRef } from './useLazyRef'; export { default as unstable_useTimeout, Timeout as unstable_Timeout } from './useTimeout'; export { default as unstable_useOnMount } from './useOnMount'; +export { default as unstable_useIsFocusVisible } from './useIsFocusVisible'; export { default as unstable_isFocusVisible } from './isFocusVisible'; export { default as unstable_getScrollbarSize } from './getScrollbarSize'; export { default as usePreviousProps } from './usePreviousProps'; @@ -45,5 +46,6 @@ export { default as unstable_useSlotProps } from './useSlotProps'; export type { UseSlotPropsParameters, UseSlotPropsResult } from './useSlotProps'; export { default as unstable_resolveComponentProps } from './resolveComponentProps'; export { default as unstable_extractEventHandlers } from './extractEventHandlers'; +export { default as unstable_getReactNodeRef } from './getReactNodeRef'; export { default as unstable_getReactElementRef } from './getReactElementRef'; export * from './types'; diff --git a/packages/mui-utils/src/useIsFocusVisible/index.ts b/packages/mui-utils/src/useIsFocusVisible/index.ts new file mode 100644 index 00000000000000..9f7a0b9ab5e7ad --- /dev/null +++ b/packages/mui-utils/src/useIsFocusVisible/index.ts @@ -0,0 +1,2 @@ +export { default } from './useIsFocusVisible'; +export * from './useIsFocusVisible'; diff --git a/packages/mui-utils/src/useIsFocusVisible/useIsFocusVisible.test.js b/packages/mui-utils/src/useIsFocusVisible/useIsFocusVisible.test.js new file mode 100644 index 00000000000000..d0c9350a1e0ad8 --- /dev/null +++ b/packages/mui-utils/src/useIsFocusVisible/useIsFocusVisible.test.js @@ -0,0 +1,126 @@ +import { expect } from 'chai'; +import * as React from 'react'; +import * as ReactDOMClient from 'react-dom/client'; +import { + act, + createRenderer, + focusVisible, + simulatePointerDevice, + programmaticFocusTriggersFocusVisible, +} from '@mui/internal-test-utils'; +import useIsFocusVisible, { teardown as teardownFocusVisible } from './useIsFocusVisible'; +import useForkRef from '../useForkRef'; + +const SimpleButton = React.forwardRef(function SimpleButton(props, ref) { + const { + isFocusVisibleRef, + onBlur: handleBlurVisible, + onFocus: handleFocusVisible, + ref: focusVisibleRef, + } = useIsFocusVisible(); + + const handleRef = useForkRef(focusVisibleRef, ref); + + const [isFocusVisible, setIsFocusVisible] = React.useState(false); + + const handleBlur = (event) => { + handleBlurVisible(event); + if (isFocusVisibleRef.current === false) { + setIsFocusVisible(false); + } + }; + + const handleFocus = (event) => { + handleFocusVisible(event); + if (isFocusVisibleRef.current === true) { + setIsFocusVisible(true); + } + }; + + return ( +