Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new feature for Size and Shape for Skeleton. #32455

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export type SkeletonSlots = {
root: NonNullable<Slot<'div'>>;
};

/**
* Sizes for the Skeleton
*/
export type SkeletonSize = 8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 48 | 56 | 64 | 72 | 96 | 120 | 128;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be good to import existing SkeletonItemSize or to import this type in SkeletonItem.types.ts. Currently it duplicates SkeletonItemSize


/**
* Skeleton Props
*/
Expand All @@ -31,6 +36,20 @@ export type SkeletonProps = Omit<ComponentProps<Partial<SkeletonSlots>>, 'width'
* @deprecated Use `className` prop to set width
*/
width?: number | string;

/**
* Sets the size of the Skeleton in pixels.
* Size is restricted to a limited set of values recommended for most uses(see SkeletonSize).
* To set a non-supported size, set `width` and `height` to override the rendered size.
* @default 16
*/
size?: SkeletonSize;

/**
* Sets the shape of the Skeleton.
* @default rectangle
*/
shape?: 'circle' | 'square' | 'rectangle';
};

export type SkeletonContextValues = {
Expand All @@ -40,4 +59,5 @@ export type SkeletonContextValues = {
/**
* State used in rendering Skeleton
*/
export type SkeletonState = ComponentState<SkeletonSlots> & Required<Pick<SkeletonProps, 'animation' | 'appearance'>>;
export type SkeletonState = ComponentState<SkeletonSlots> &
Required<Pick<SkeletonProps, 'animation' | 'appearance' | 'size' | 'shape'>>;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import { useSkeletonContext } from '../../contexts/SkeletonContext';
*/
export const useSkeleton_unstable = (props: SkeletonProps, ref: React.Ref<HTMLElement>): SkeletonState => {
const { animation: contextAnimation, appearance: contextAppearance } = useSkeletonContext();
const { animation = contextAnimation ?? 'wave', appearance = contextAppearance ?? 'opaque' } = props;
const {
animation = contextAnimation ?? 'wave',
appearance = contextAppearance ?? 'opaque',
size = 16,
shape = 'rectangle',
} = props;

const root = slot.always(
getIntrinsicElementProps('div', {
Expand All @@ -28,5 +33,5 @@ export const useSkeleton_unstable = (props: SkeletonProps, ref: React.Ref<HTMLEl
}),
{ elementType: 'div' },
);
return { animation, appearance, components: { root: 'div' }, root };
return { animation, appearance, size, shape, components: { root: 'div' }, root };
};
Original file line number Diff line number Diff line change
@@ -1,17 +1,167 @@
import { mergeClasses } from '@griffel/react';
import { makeStyles, mergeClasses } from '@griffel/react';
import type { SkeletonSlots, SkeletonState } from './Skeleton.types';
import type { SlotClassNames } from '@fluentui/react-utilities';
import { tokens } from '@fluentui/react-theme';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to change styles? This feature requires only to pass size and shape props. You'll need to add context for this

export const skeletonClassNames: SlotClassNames<SkeletonSlots> = {
root: 'fui-Skeleton',
};

const skeletonWaveAnimation = {
to: {
transform: 'translate(100%)',
},
};

const skeletonPulseAnimation = {
'0%': {
opacity: '1',
},
'50%': {
opacity: '0.4',
},
'100%': {
opacity: '1',
},
};

/**
* Styles for the root slot
*/
const useStyles = makeStyles({
root: {
position: 'relative',
overflow: 'hidden',

'::after': {
content: '""',
display: 'block',
position: 'absolute',
inset: '0',
animationIterationCount: 'infinite',
animationDuration: '3s',
animationTimingFunction: 'ease-in-out',
'@media screen and (prefers-reduced-motion: reduce)': {
animationDuration: '0.01ms',
animationIterationCount: '1',
},
},
},
wave: {
backgroundColor: tokens.colorNeutralStencil1,

'::after': {
animationName: skeletonWaveAnimation,
backgroundImage: `linear-gradient(
to right,
${tokens.colorNeutralStencil1} 0%,
${tokens.colorNeutralStencil2} 50%,
${tokens.colorNeutralStencil1} 100%)`,
transform: 'translate(-100%)',

'@media screen and (forced-colors: active)': {
backgroundColor: 'WindowText',
},
},
},
pulse: {
'::after': {
animationName: skeletonPulseAnimation,
animationDuration: '1s',
backgroundColor: tokens.colorNeutralStencil1,
},
},
translucent: {
backgroundColor: tokens.colorNeutralStencil1Alpha,

'::after': {
backgroundImage: `linear-gradient(
to right,
transparent 0%,
${tokens.colorNeutralStencil1Alpha} 50%,
transparent 100%)`,
},
},
translucentPulse: {
backgroundColor: 'none',

'::after': {
backgroundColor: tokens.colorNeutralStencil1Alpha,
},
},
});

const useRectangleStyles = makeStyles({
root: {
width: '100%',
borderRadius: '4px',
},
8: { height: '8px' },
12: { height: '12px' },
16: { height: '16px' },
20: { height: '20px' },
24: { height: '24px' },
28: { height: '28px' },
32: { height: '32px' },
36: { height: '36px' },
40: { height: '40px' },
48: { height: '48px' },
56: { height: '56px' },
64: { height: '64px' },
72: { height: '72px' },
96: { height: '96px' },
120: { height: '120px' },
128: { height: '128px' },
});

const useSizeStyles = makeStyles({
8: { width: '8px', height: '8px' },
12: { width: '12px', height: '12px' },
16: { width: '16px', height: '16px' },
20: { width: '20px', height: '20px' },
24: { width: '24px', height: '24px' },
28: { width: '28px', height: '28px' },
32: { width: '32px', height: '32px' },
36: { width: '36px', height: '36px' },
40: { width: '40px', height: '40px' },
48: { width: '48px', height: '48px' },
56: { width: '56px', height: '56px' },
64: { width: '64px', height: '64px' },
72: { width: '72px', height: '72px' },
96: { width: '96px', height: '96px' },
120: { width: '120px', height: '120px' },
128: { width: '128px', height: '128px' },
});

const useCircleSizeStyles = makeStyles({
root: {
borderRadius: '50%',
},
});

/**
* Apply styling to the Skeleton slots based on the state
*/
export const useSkeletonStyles_unstable = (state: SkeletonState): SkeletonState => {
'use no memo';

state.root.className = mergeClasses(skeletonClassNames.root, state.root.className);
const { size, shape } = state;

const rootStyles = useStyles();
const rectStyles = useRectangleStyles();
const sizeStyles = useSizeStyles();
const circleStyles = useCircleSizeStyles();

state.root.className = mergeClasses(
skeletonClassNames.root,
rootStyles.root,
shape === 'rectangle' && rectStyles.root,
shape === 'rectangle' && rectStyles[size],
shape === 'square' && sizeStyles[size],
shape === 'circle' && circleStyles.root,
shape === 'circle' && sizeStyles[size],
state.root.className,
);

return state;
};
Loading