diff --git a/change/@fluentui-react-skeleton-35c22083-9e08-4a77-9057-e7ee7a429c76.json b/change/@fluentui-react-skeleton-35c22083-9e08-4a77-9057-e7ee7a429c76.json new file mode 100644 index 00000000000000..ecde1adb16cf55 --- /dev/null +++ b/change/@fluentui-react-skeleton-35c22083-9e08-4a77-9057-e7ee7a429c76.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat(react-skeleton): Add size and shape props to Skeleton component", + "packageName": "@fluentui/react-skeleton", + "email": "v.kozlova13@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-skeleton/library/etc/react-skeleton.api.md b/packages/react-components/react-skeleton/library/etc/react-skeleton.api.md index fe78c6e0445799..c8a7cd1d9e5789 100644 --- a/packages/react-components/react-skeleton/library/etc/react-skeleton.api.md +++ b/packages/react-components/react-skeleton/library/etc/react-skeleton.api.md @@ -33,6 +33,10 @@ export interface SkeletonContextValue { animation?: 'wave' | 'pulse'; // (undocumented) appearance?: 'opaque' | 'translucent'; + // (undocumented) + shape?: 'circle' | 'square' | 'rectangle'; + // (undocumented) + size?: SkeletonItemSize; } // @public (undocumented) @@ -42,11 +46,9 @@ export const SkeletonItem: ForwardRefComponent; export const skeletonItemClassNames: SlotClassNames; // @public -export type SkeletonItemProps = ComponentProps & { +export type SkeletonItemProps = ComponentProps & Pick & { animation?: 'wave' | 'pulse'; appearance?: 'opaque' | 'translucent'; - size?: SkeletonItemSize; - shape?: 'circle' | 'square' | 'rectangle'; }; // @public (undocumented) @@ -62,6 +64,8 @@ export type SkeletonProps = Omit>, 'width' animation?: 'wave' | 'pulse'; appearance?: 'opaque' | 'translucent'; width?: number | string; + size?: SkeletonItemSize; + shape?: 'circle' | 'square' | 'rectangle'; }; // @public (undocumented) @@ -70,7 +74,7 @@ export type SkeletonSlots = { }; // @public -export type SkeletonState = ComponentState & Required>; +export type SkeletonState = ComponentState & Required> & Pick; // @public export const useSkeleton_unstable: (props: SkeletonProps, ref: React_2.Ref) => SkeletonState; diff --git a/packages/react-components/react-skeleton/library/src/Skeleton.ts b/packages/react-components/react-skeleton/library/src/Skeleton.ts index fe0a7eaa49f374..66ae99e820b53e 100644 --- a/packages/react-components/react-skeleton/library/src/Skeleton.ts +++ b/packages/react-components/react-skeleton/library/src/Skeleton.ts @@ -1,4 +1,10 @@ -export type { SkeletonContextValues, SkeletonProps, SkeletonSlots, SkeletonState } from './components/Skeleton/index'; +export type { + SkeletonContextValues, + SkeletonItemSize, + SkeletonProps, + SkeletonSlots, + SkeletonState, +} from './components/Skeleton/index'; export { Skeleton, renderSkeleton_unstable, diff --git a/packages/react-components/react-skeleton/library/src/SkeletonItem.ts b/packages/react-components/react-skeleton/library/src/SkeletonItem.ts index dfc0f84ceec0a4..ee90e2f8960d36 100644 --- a/packages/react-components/react-skeleton/library/src/SkeletonItem.ts +++ b/packages/react-components/react-skeleton/library/src/SkeletonItem.ts @@ -1,9 +1,4 @@ -export type { - SkeletonItemProps, - SkeletonItemSize, - SkeletonItemSlots, - SkeletonItemState, -} from './components/SkeletonItem/index'; +export type { SkeletonItemProps, SkeletonItemSlots, SkeletonItemState } from './components/SkeletonItem/index'; export { SkeletonItem, renderSkeletonItem_unstable, diff --git a/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.test.tsx b/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.test.tsx index f9fe36b1c36e9e..209e33e6bf08df 100644 --- a/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.test.tsx +++ b/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.test.tsx @@ -1,6 +1,9 @@ import * as React from 'react'; import { render } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; import { Skeleton } from './Skeleton'; +import { useSkeletonItem_unstable } from '../SkeletonItem/useSkeletonItem'; +import { SkeletonContextProvider } from '../../contexts/SkeletonContext'; import { isConformant } from '../../testing/isConformant'; describe('Skeleton', () => { @@ -23,4 +26,32 @@ describe('Skeleton', () => { const result = render(); expect(result.getByRole('progressbar').getAttribute('aria-busy')).toBeDefined(); }); + it('passes size prop to SkeletonItem via context', () => { + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + const { result } = renderHook(() => useSkeletonItem_unstable({}, React.createRef()), { wrapper }); + expect(result.current.size).toBe(24); + }); + it('passes shape prop to SkeletonItem via context', () => { + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + const { result } = renderHook(() => useSkeletonItem_unstable({}, React.createRef()), { wrapper }); + expect(result.current.shape).toBe('circle'); + }); + it('allows SkeletonItem to override Skeleton size prop', () => { + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + const { result } = renderHook(() => useSkeletonItem_unstable({ size: 48 }, React.createRef()), { wrapper }); + expect(result.current.size).toBe(48); + }); + it('allows SkeletonItem to override Skeleton shape prop', () => { + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + const { result } = renderHook(() => useSkeletonItem_unstable({ shape: 'square' }, React.createRef()), { wrapper }); + expect(result.current.shape).toBe('square'); + }); }); diff --git a/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.types.ts b/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.types.ts index 8a3f571c25f8c9..069e990666d89f 100644 --- a/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.types.ts +++ b/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.types.ts @@ -9,6 +9,11 @@ export type SkeletonSlots = { root: NonNullable>; }; +/** + * Sizes for the SkeletonItem + */ +export type SkeletonItemSize = 8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 48 | 56 | 64 | 72 | 96 | 120 | 128; + /** * Skeleton Props */ @@ -31,6 +36,19 @@ export type SkeletonProps = Omit>, 'width' * @deprecated Use `className` prop to set width */ width?: number | string; + + /** + * Sets the size of the SkeletonItems inside the Skeleton in pixels. + * Size is restricted to a limited set of values recommended for most uses (see SkeletonItemSize). + * This value can be overridden by the individual SkeletonItem's `size` prop. + */ + size?: SkeletonItemSize; + + /** + * Sets the shape of the SkeletonItems inside the Skeleton. + * This value can be overridden by the individual SkeletonItem's `shape` prop. + */ + shape?: 'circle' | 'square' | 'rectangle'; }; export type SkeletonContextValues = { @@ -40,4 +58,6 @@ export type SkeletonContextValues = { /** * State used in rendering Skeleton */ -export type SkeletonState = ComponentState & Required>; +export type SkeletonState = ComponentState & + Required> & + Pick; diff --git a/packages/react-components/react-skeleton/library/src/components/Skeleton/index.ts b/packages/react-components/react-skeleton/library/src/components/Skeleton/index.ts index 14f197b578a05b..d2623ecf2ff769 100644 --- a/packages/react-components/react-skeleton/library/src/components/Skeleton/index.ts +++ b/packages/react-components/react-skeleton/library/src/components/Skeleton/index.ts @@ -1,5 +1,11 @@ export { Skeleton } from './Skeleton'; -export type { SkeletonContextValues, SkeletonProps, SkeletonSlots, SkeletonState } from './Skeleton.types'; +export type { + SkeletonContextValues, + SkeletonItemSize, + SkeletonProps, + SkeletonSlots, + SkeletonState, +} from './Skeleton.types'; export { renderSkeleton_unstable } from './renderSkeleton'; export { useSkeleton_unstable } from './useSkeleton'; export { useSkeletonContextValues } from './useSkeletonContextValues'; diff --git a/packages/react-components/react-skeleton/library/src/components/Skeleton/useSkeleton.ts b/packages/react-components/react-skeleton/library/src/components/Skeleton/useSkeleton.ts index a6bed8c0d4f159..3481ac79a8fd2b 100644 --- a/packages/react-components/react-skeleton/library/src/components/Skeleton/useSkeleton.ts +++ b/packages/react-components/react-skeleton/library/src/components/Skeleton/useSkeleton.ts @@ -16,7 +16,7 @@ import { useSkeletonContext } from '../../contexts/SkeletonContext'; */ export const useSkeleton_unstable = (props: SkeletonProps, ref: React.Ref): SkeletonState => { const { animation: contextAnimation, appearance: contextAppearance } = useSkeletonContext(); - const { animation = contextAnimation ?? 'wave', appearance = contextAppearance ?? 'opaque' } = props; + const { animation = contextAnimation ?? 'wave', appearance = contextAppearance ?? 'opaque', size, shape } = props; const root = slot.always( getIntrinsicElementProps('div', { @@ -30,5 +30,5 @@ export const useSkeleton_unstable = (props: SkeletonProps, ref: React.Ref { - const { animation, appearance } = state; + const { animation, appearance, size, shape } = state; const skeletonGroup = React.useMemo( () => ({ animation, appearance, + size, + shape, }), - [animation, appearance], + [animation, appearance, size, shape], ); return { skeletonGroup }; diff --git a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/SkeletonItem.types.ts b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/SkeletonItem.types.ts index 59241135dfc7b0..90c96541835b7e 100644 --- a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/SkeletonItem.types.ts +++ b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/SkeletonItem.types.ts @@ -1,44 +1,27 @@ import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import type { SkeletonProps } from '../Skeleton/Skeleton.types'; export type SkeletonItemSlots = { root: Slot<'div', 'span'>; }; -/** - * Sizes for the SkeletonItem - */ -export type SkeletonItemSize = 8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 48 | 56 | 64 | 72 | 96 | 120 | 128; - /** * SkeletonItem Props */ -export type SkeletonItemProps = ComponentProps & { - /** - * Sets the animation of the SkeletonItem - * @default wave - */ - animation?: 'wave' | 'pulse'; +export type SkeletonItemProps = ComponentProps & + Pick & { + /** + * Sets the animation of the SkeletonItem + * @default wave + */ + animation?: 'wave' | 'pulse'; - /** - * Sets the appearance of the SkeletonItem - * @default opaque - */ - appearance?: 'opaque' | 'translucent'; - - /** - * Sets the size of the SkeletonItem in pixels. - * Size is restricted to a limited set of values recommended for most uses(see SkeletonItemSize). - * To set a non-supported size, set `width` and `height` to override the rendered size. - * @default 16 - */ - size?: SkeletonItemSize; - - /** - * Sets the shape of the SkeletonItem. - * @default rectangle - */ - shape?: 'circle' | 'square' | 'rectangle'; -}; + /** + * Sets the appearance of the SkeletonItem + * @default opaque + */ + appearance?: 'opaque' | 'translucent'; + }; /** * State used in rendering SkeletonItem diff --git a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/index.ts b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/index.ts index 3d22f9b76ec57b..9f136a182013eb 100644 --- a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/index.ts +++ b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/index.ts @@ -1,5 +1,5 @@ export { SkeletonItem } from './SkeletonItem'; -export type { SkeletonItemProps, SkeletonItemSize, SkeletonItemSlots, SkeletonItemState } from './SkeletonItem.types'; +export type { SkeletonItemProps, SkeletonItemSlots, SkeletonItemState } from './SkeletonItem.types'; export { renderSkeletonItem_unstable } from './renderSkeletonItem'; export { useSkeletonItem_unstable } from './useSkeletonItem'; export { skeletonItemClassNames, useSkeletonItemStyles_unstable } from './useSkeletonItemStyles.styles'; diff --git a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/useSkeletonItem.tsx b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/useSkeletonItem.tsx index c38635d67ca873..78da929c233e2a 100644 --- a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/useSkeletonItem.tsx +++ b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/useSkeletonItem.tsx @@ -15,12 +15,17 @@ import type { SkeletonItemProps, SkeletonItemState } from './SkeletonItem.types' * @param ref - reference to root HTMLElement of SkeletonItem */ export const useSkeletonItem_unstable = (props: SkeletonItemProps, ref: React.Ref): SkeletonItemState => { - const { animation: contextAnimation, appearance: contextAppearance } = useSkeletonContext(); + const { + animation: contextAnimation, + appearance: contextAppearance, + size: contextSize, + shape: contextShape, + } = useSkeletonContext(); const { animation = contextAnimation ?? 'wave', appearance = contextAppearance ?? 'opaque', - size = 16, - shape = 'rectangle', + size = contextSize ?? 16, + shape = contextShape ?? 'rectangle', } = props; const root = slot.always( diff --git a/packages/react-components/react-skeleton/library/src/contexts/SkeletonContext.ts b/packages/react-components/react-skeleton/library/src/contexts/SkeletonContext.ts index 79157c2f107f5c..3872834ffb3610 100644 --- a/packages/react-components/react-skeleton/library/src/contexts/SkeletonContext.ts +++ b/packages/react-components/react-skeleton/library/src/contexts/SkeletonContext.ts @@ -1,12 +1,15 @@ 'use client'; import * as React from 'react'; +import type { SkeletonItemSize } from '../components/Skeleton/Skeleton.types'; const SkeletonContext = React.createContext(undefined); export interface SkeletonContextValue { animation?: 'wave' | 'pulse'; appearance?: 'opaque' | 'translucent'; + size?: SkeletonItemSize; + shape?: 'circle' | 'square' | 'rectangle'; } const skeletonContextDefaultValue: SkeletonContextValue = {}; diff --git a/packages/react-components/react-skeleton/stories/src/Skeleton/SkeletonRow.stories.tsx b/packages/react-components/react-skeleton/stories/src/Skeleton/SkeletonRow.stories.tsx index db57b17533bc39..cc7098ece62c73 100644 --- a/packages/react-components/react-skeleton/stories/src/Skeleton/SkeletonRow.stories.tsx +++ b/packages/react-components/react-skeleton/stories/src/Skeleton/SkeletonRow.stories.tsx @@ -29,24 +29,24 @@ export const Row = (props: Partial): JSXElement => { const styles = useStyles(); return (
- +
- +
- - - - + + + +
- - - - + + + +