diff --git a/packages/react-components/react-skeleton/.storybook/main.js b/packages/react-components/react-skeleton/.storybook/main.js new file mode 100644 index 0000000000000..26536b61b387f --- /dev/null +++ b/packages/react-components/react-skeleton/.storybook/main.js @@ -0,0 +1,14 @@ +const rootMain = require('../../../../.storybook/main'); + +module.exports = /** @type {Omit} */ ({ + ...rootMain, + stories: [...rootMain.stories, '../stories/**/*.stories.mdx', '../stories/**/index.stories.@(ts|tsx)'], + addons: [...rootMain.addons], + webpackFinal: (config, options) => { + const localConfig = { ...rootMain.webpackFinal(config, options) }; + + // add your own webpack tweaks if needed + + return localConfig; + }, +}); diff --git a/packages/react-components/react-skeleton/.storybook/preview.js b/packages/react-components/react-skeleton/.storybook/preview.js new file mode 100644 index 0000000000000..1939500a3d18c --- /dev/null +++ b/packages/react-components/react-skeleton/.storybook/preview.js @@ -0,0 +1,7 @@ +import * as rootPreview from '../../../../.storybook/preview'; + +/** @type {typeof rootPreview.decorators} */ +export const decorators = [...rootPreview.decorators]; + +/** @type {typeof rootPreview.parameters} */ +export const parameters = { ...rootPreview.parameters }; diff --git a/packages/react-components/react-skeleton/.storybook/tsconfig.json b/packages/react-components/react-skeleton/.storybook/tsconfig.json new file mode 100644 index 0000000000000..ea89218a3d916 --- /dev/null +++ b/packages/react-components/react-skeleton/.storybook/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "", + "allowJs": true, + "checkJs": true, + "types": ["static-assets", "environment", "storybook__addons"] + }, + "include": ["../stories/**/*.stories.ts", "../stories/**/*.stories.tsx", "*.js"] +} diff --git a/packages/react-components/react-skeleton/etc/react-skeleton.api.md b/packages/react-components/react-skeleton/etc/react-skeleton.api.md index 6ab210b20a754..27edb10e5ce2d 100644 --- a/packages/react-components/react-skeleton/etc/react-skeleton.api.md +++ b/packages/react-components/react-skeleton/etc/react-skeleton.api.md @@ -4,6 +4,91 @@ ```ts +import type { ComponentProps } from '@fluentui/react-utilities'; +import type { ComponentState } from '@fluentui/react-utilities'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React_2 from 'react'; +import type { Slot } from '@fluentui/react-utilities'; +import type { SlotClassNames } from '@fluentui/react-utilities'; + +// @public +export const renderSkeleton_unstable: (state: SkeletonState) => JSX.Element; + +// @public +export const renderSkeletonCircle_unstable: (state: SkeletonCircleState) => JSX.Element; + +// @public +export const renderSkeletonLine_unstable: (state: SkeletonLineState) => JSX.Element; + +// @public +export const Skeleton: ForwardRefComponent; + +// @public +export const SkeletonCircle: ForwardRefComponent; + +// @public (undocumented) +export const skeletonCircleClassNames: SlotClassNames; + +// @public +export type SkeletonCircleProps = ComponentProps & {}; + +// @public (undocumented) +export type SkeletonCircleSlots = { + root: Slot<'div'>; +}; + +// @public +export type SkeletonCircleState = ComponentState; + +// @public (undocumented) +export const skeletonClassNames: SlotClassNames; + +// @public +export const SkeletonLine: ForwardRefComponent; + +// @public (undocumented) +export const skeletonLineClassNames: SlotClassNames; + +// @public +export type SkeletonLineProps = ComponentProps & {}; + +// @public (undocumented) +export type SkeletonLineSlots = { + root: Slot<'div'>; +}; + +// @public +export type SkeletonLineState = ComponentState; + +// @public +export type SkeletonProps = ComponentProps & {}; + +// @public (undocumented) +export type SkeletonSlots = { + root: Slot<'div'>; +}; + +// @public +export type SkeletonState = ComponentState; + +// @public +export const useSkeleton_unstable: (props: SkeletonProps, ref: React_2.Ref) => SkeletonState; + +// @public +export const useSkeletonCircle_unstable: (props: SkeletonCircleProps, ref: React_2.Ref) => SkeletonCircleState; + +// @public +export const useSkeletonCircleStyles_unstable: (state: SkeletonCircleState) => SkeletonCircleState; + +// @public +export const useSkeletonLine_unstable: (props: SkeletonLineProps, ref: React_2.Ref) => SkeletonLineState; + +// @public +export const useSkeletonLineStyles_unstable: (state: SkeletonLineState) => SkeletonLineState; + +// @public +export const useSkeletonStyles_unstable: (state: SkeletonState) => SkeletonState; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/react-components/react-skeleton/package.json b/packages/react-components/react-skeleton/package.json index cdc828b22bb14..b384cbd0c5b59 100644 --- a/packages/react-components/react-skeleton/package.json +++ b/packages/react-components/react-skeleton/package.json @@ -20,7 +20,9 @@ "lint": "just-scripts lint", "test": "jest --passWithNoTests", "generate-api": "tsc -p ./tsconfig.lib.json --emitDeclarationOnly && just-scripts api-extractor", - "type-check": "tsc -b tsconfig.json" + "type-check": "tsc -b tsconfig.json", + "storybook": "start-storybook", + "start": "yarn storybook" }, "devDependencies": { "@fluentui/eslint-plugin": "*", diff --git a/packages/react-components/react-skeleton/src/Skeleton.ts b/packages/react-components/react-skeleton/src/Skeleton.ts new file mode 100644 index 0000000000000..8db95f5bcbb7c --- /dev/null +++ b/packages/react-components/react-skeleton/src/Skeleton.ts @@ -0,0 +1 @@ +export * from './components/Skeleton/index'; diff --git a/packages/react-components/react-skeleton/src/SkeletonCircle.ts b/packages/react-components/react-skeleton/src/SkeletonCircle.ts new file mode 100644 index 0000000000000..888c1a35b3cb0 --- /dev/null +++ b/packages/react-components/react-skeleton/src/SkeletonCircle.ts @@ -0,0 +1 @@ +export * from './components/SkeletonCircle/index'; diff --git a/packages/react-components/react-skeleton/src/SkeletonLine.ts b/packages/react-components/react-skeleton/src/SkeletonLine.ts new file mode 100644 index 0000000000000..1e8b14ee67881 --- /dev/null +++ b/packages/react-components/react-skeleton/src/SkeletonLine.ts @@ -0,0 +1 @@ +export * from './components/SkeletonLine/index'; diff --git a/packages/react-components/react-skeleton/src/components/Skeleton/Skeleton.test.tsx b/packages/react-components/react-skeleton/src/components/Skeleton/Skeleton.test.tsx new file mode 100644 index 0000000000000..8a27cacee2b41 --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/Skeleton/Skeleton.test.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import { Skeleton } from './Skeleton'; +import { isConformant } from '../../testing/isConformant'; + +describe('Skeleton', () => { + isConformant({ + Component: Skeleton, + displayName: 'Skeleton', + }); + + // TODO add more tests here, and create visual regression tests in /apps/vr-tests + + it('renders a default state', () => { + const result = render(Default Skeleton); + expect(result.container).toMatchSnapshot(); + }); +}); diff --git a/packages/react-components/react-skeleton/src/components/Skeleton/Skeleton.tsx b/packages/react-components/react-skeleton/src/components/Skeleton/Skeleton.tsx new file mode 100644 index 0000000000000..dc82aadb722cb --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/Skeleton/Skeleton.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useSkeleton_unstable } from './useSkeleton'; +import { renderSkeleton_unstable } from './renderSkeleton'; +import { useSkeletonStyles_unstable } from './useSkeletonStyles'; +import type { SkeletonProps } from './Skeleton.types'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; + +/** + * Skeleton component - TODO: add more docs + */ +export const Skeleton: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useSkeleton_unstable(props, ref); + + useSkeletonStyles_unstable(state); + return renderSkeleton_unstable(state); +}); + +Skeleton.displayName = 'Skeleton'; diff --git a/packages/react-components/react-skeleton/src/components/Skeleton/Skeleton.types.ts b/packages/react-components/react-skeleton/src/components/Skeleton/Skeleton.types.ts new file mode 100644 index 0000000000000..af32781aacfe2 --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/Skeleton/Skeleton.types.ts @@ -0,0 +1,17 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; + +export type SkeletonSlots = { + root: Slot<'div'>; +}; + +/** + * Skeleton Props + */ +export type SkeletonProps = ComponentProps & {}; + +/** + * State used in rendering Skeleton + */ +export type SkeletonState = ComponentState; +// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from SkeletonProps. +// & Required> diff --git a/packages/react-components/react-skeleton/src/components/Skeleton/__snapshots__/Skeleton.test.tsx.snap b/packages/react-components/react-skeleton/src/components/Skeleton/__snapshots__/Skeleton.test.tsx.snap new file mode 100644 index 0000000000000..7a9fe39f33238 --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/Skeleton/__snapshots__/Skeleton.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Skeleton renders a default state 1`] = ` +
+
+ Default Skeleton +
+
+`; diff --git a/packages/react-components/react-skeleton/src/components/Skeleton/index.ts b/packages/react-components/react-skeleton/src/components/Skeleton/index.ts new file mode 100644 index 0000000000000..233c28a1e8cca --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/Skeleton/index.ts @@ -0,0 +1,5 @@ +export * from './Skeleton'; +export * from './Skeleton.types'; +export * from './renderSkeleton'; +export * from './useSkeleton'; +export * from './useSkeletonStyles'; diff --git a/packages/react-components/react-skeleton/src/components/Skeleton/renderSkeleton.tsx b/packages/react-components/react-skeleton/src/components/Skeleton/renderSkeleton.tsx new file mode 100644 index 0000000000000..5d0b2bc373b7e --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/Skeleton/renderSkeleton.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utilities'; +import type { SkeletonState, SkeletonSlots } from './Skeleton.types'; + +/** + * Render the final JSX of Skeleton + */ +export const renderSkeleton_unstable = (state: SkeletonState) => { + const { slots, slotProps } = getSlots(state); + + // TODO Add additional slots in the appropriate place + return ; +}; diff --git a/packages/react-components/react-skeleton/src/components/Skeleton/useSkeleton.ts b/packages/react-components/react-skeleton/src/components/Skeleton/useSkeleton.ts new file mode 100644 index 0000000000000..0cdada0fbb85a --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/Skeleton/useSkeleton.ts @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { getNativeElementProps } from '@fluentui/react-utilities'; +import type { SkeletonProps, SkeletonState } from './Skeleton.types'; + +/** + * Create the state required to render Skeleton. + * + * The returned state can be modified with hooks such as useSkeletonStyles_unstable, + * before being passed to renderSkeleton_unstable. + * + * @param props - props from this instance of Skeleton + * @param ref - reference to root HTMLElement of Skeleton + */ +export const useSkeleton_unstable = (props: SkeletonProps, ref: React.Ref): SkeletonState => { + return { + // TODO add appropriate props/defaults + components: { + // TODO add each slot's element type or component + root: 'div', + }, + // TODO add appropriate slots, for example: + // mySlot: resolveShorthand(props.mySlot), + root: getNativeElementProps('div', { + ref, + ...props, + }), + }; +}; diff --git a/packages/react-components/react-skeleton/src/components/Skeleton/useSkeletonStyles.ts b/packages/react-components/react-skeleton/src/components/Skeleton/useSkeletonStyles.ts new file mode 100644 index 0000000000000..c498a079043f8 --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/Skeleton/useSkeletonStyles.ts @@ -0,0 +1,33 @@ +import { makeStyles, mergeClasses } from '@griffel/react'; +import type { SkeletonSlots, SkeletonState } from './Skeleton.types'; +import type { SlotClassNames } from '@fluentui/react-utilities'; + +export const skeletonClassNames: SlotClassNames = { + root: 'fui-Skeleton', + // TODO: add class names for all slots on SkeletonSlots. + // Should be of the form `: 'fui-Skeleton__` +}; + +/** + * Styles for the root slot + */ +const useStyles = makeStyles({ + root: { + // TODO Add default styles for the root element + }, + + // TODO add additional classes for different states and/or slots +}); + +/** + * Apply styling to the Skeleton slots based on the state + */ +export const useSkeletonStyles_unstable = (state: SkeletonState): SkeletonState => { + const styles = useStyles(); + state.root.className = mergeClasses(skeletonClassNames.root, styles.root, state.root.className); + + // TODO Add class names to slots, for example: + // state.mySlot.className = mergeClasses(styles.mySlot, state.mySlot.className); + + return state; +}; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonCircle/SkeletonCircle.test.tsx b/packages/react-components/react-skeleton/src/components/SkeletonCircle/SkeletonCircle.test.tsx new file mode 100644 index 0000000000000..883e9d8e1979f --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonCircle/SkeletonCircle.test.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import { SkeletonCircle } from './SkeletonCircle'; +import { isConformant } from '../../testing/isConformant'; + +describe('SkeletonCircle', () => { + isConformant({ + Component: SkeletonCircle, + displayName: 'SkeletonCircle', + }); + + // TODO add more tests here, and create visual regression tests in /apps/vr-tests + + it('renders a default state', () => { + const result = render(Default SkeletonCircle); + expect(result.container).toMatchSnapshot(); + }); +}); diff --git a/packages/react-components/react-skeleton/src/components/SkeletonCircle/SkeletonCircle.tsx b/packages/react-components/react-skeleton/src/components/SkeletonCircle/SkeletonCircle.tsx new file mode 100644 index 0000000000000..dae9de2ae2ee4 --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonCircle/SkeletonCircle.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useSkeletonCircle_unstable } from './useSkeletonCircle'; +import { renderSkeletonCircle_unstable } from './renderSkeletonCircle'; +import { useSkeletonCircleStyles_unstable } from './useSkeletonCircleStyles'; +import type { SkeletonCircleProps } from './SkeletonCircle.types'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; + +/** + * SkeletonCircle component - TODO: add more docs + */ +export const SkeletonCircle: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useSkeletonCircle_unstable(props, ref); + + useSkeletonCircleStyles_unstable(state); + return renderSkeletonCircle_unstable(state); +}); + +SkeletonCircle.displayName = 'SkeletonCircle'; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonCircle/SkeletonCircle.types.ts b/packages/react-components/react-skeleton/src/components/SkeletonCircle/SkeletonCircle.types.ts new file mode 100644 index 0000000000000..7f0b5508537dc --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonCircle/SkeletonCircle.types.ts @@ -0,0 +1,17 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; + +export type SkeletonCircleSlots = { + root: Slot<'div'>; +}; + +/** + * SkeletonCircle Props + */ +export type SkeletonCircleProps = ComponentProps & {}; + +/** + * State used in rendering SkeletonCircle + */ +export type SkeletonCircleState = ComponentState; +// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from SkeletonCircleProps. +// & Required> diff --git a/packages/react-components/react-skeleton/src/components/SkeletonCircle/__snapshots__/SkeletonCircle.test.tsx.snap b/packages/react-components/react-skeleton/src/components/SkeletonCircle/__snapshots__/SkeletonCircle.test.tsx.snap new file mode 100644 index 0000000000000..32b8f9dbb1b3e --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonCircle/__snapshots__/SkeletonCircle.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SkeletonCircle renders a default state 1`] = ` +
+
+ Default SkeletonCircle +
+
+`; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonCircle/index.ts b/packages/react-components/react-skeleton/src/components/SkeletonCircle/index.ts new file mode 100644 index 0000000000000..a05971a3aad1e --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonCircle/index.ts @@ -0,0 +1,5 @@ +export * from './SkeletonCircle'; +export * from './SkeletonCircle.types'; +export * from './renderSkeletonCircle'; +export * from './useSkeletonCircle'; +export * from './useSkeletonCircleStyles'; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonCircle/renderSkeletonCircle.tsx b/packages/react-components/react-skeleton/src/components/SkeletonCircle/renderSkeletonCircle.tsx new file mode 100644 index 0000000000000..24b37f99c7640 --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonCircle/renderSkeletonCircle.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utilities'; +import type { SkeletonCircleState, SkeletonCircleSlots } from './SkeletonCircle.types'; + +/** + * Render the final JSX of SkeletonCircle + */ +export const renderSkeletonCircle_unstable = (state: SkeletonCircleState) => { + const { slots, slotProps } = getSlots(state); + + // TODO Add additional slots in the appropriate place + return ; +}; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonCircle/useSkeletonCircle.ts b/packages/react-components/react-skeleton/src/components/SkeletonCircle/useSkeletonCircle.ts new file mode 100644 index 0000000000000..0df0cc7c1fd89 --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonCircle/useSkeletonCircle.ts @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { getNativeElementProps } from '@fluentui/react-utilities'; +import type { SkeletonCircleProps, SkeletonCircleState } from './SkeletonCircle.types'; + +/** + * Create the state required to render SkeletonCircle. + * + * The returned state can be modified with hooks such as useSkeletonCircleStyles_unstable, + * before being passed to renderSkeletonCircle_unstable. + * + * @param props - props from this instance of SkeletonCircle + * @param ref - reference to root HTMLElement of SkeletonCircle + */ +export const useSkeletonCircle_unstable = ( + props: SkeletonCircleProps, + ref: React.Ref, +): SkeletonCircleState => { + return { + // TODO add appropriate props/defaults + components: { + // TODO add each slot's element type or component + root: 'div', + }, + // TODO add appropriate slots, for example: + // mySlot: resolveShorthand(props.mySlot), + root: getNativeElementProps('div', { + ref, + ...props, + }), + }; +}; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonCircle/useSkeletonCircleStyles.ts b/packages/react-components/react-skeleton/src/components/SkeletonCircle/useSkeletonCircleStyles.ts new file mode 100644 index 0000000000000..41a840bd582c8 --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonCircle/useSkeletonCircleStyles.ts @@ -0,0 +1,33 @@ +import { makeStyles, mergeClasses } from '@griffel/react'; +import type { SkeletonCircleSlots, SkeletonCircleState } from './SkeletonCircle.types'; +import type { SlotClassNames } from '@fluentui/react-utilities'; + +export const skeletonCircleClassNames: SlotClassNames = { + root: 'fui-SkeletonCircle', + // TODO: add class names for all slots on SkeletonCircleSlots. + // Should be of the form `: 'fui-SkeletonCircle__` +}; + +/** + * Styles for the root slot + */ +const useStyles = makeStyles({ + root: { + // TODO Add default styles for the root element + }, + + // TODO add additional classes for different states and/or slots +}); + +/** + * Apply styling to the SkeletonCircle slots based on the state + */ +export const useSkeletonCircleStyles_unstable = (state: SkeletonCircleState): SkeletonCircleState => { + const styles = useStyles(); + state.root.className = mergeClasses(skeletonCircleClassNames.root, styles.root, state.root.className); + + // TODO Add class names to slots, for example: + // state.mySlot.className = mergeClasses(styles.mySlot, state.mySlot.className); + + return state; +}; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonLine/SkeletonLine.test.tsx b/packages/react-components/react-skeleton/src/components/SkeletonLine/SkeletonLine.test.tsx new file mode 100644 index 0000000000000..93b139f44b52f --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonLine/SkeletonLine.test.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import { SkeletonLine } from './SkeletonLine'; +import { isConformant } from '../../testing/isConformant'; + +describe('SkeletonLine', () => { + isConformant({ + Component: SkeletonLine, + displayName: 'SkeletonLine', + }); + + // TODO add more tests here, and create visual regression tests in /apps/vr-tests + + it('renders a default state', () => { + const result = render(Default SkeletonLine); + expect(result.container).toMatchSnapshot(); + }); +}); diff --git a/packages/react-components/react-skeleton/src/components/SkeletonLine/SkeletonLine.tsx b/packages/react-components/react-skeleton/src/components/SkeletonLine/SkeletonLine.tsx new file mode 100644 index 0000000000000..d628fd70f3196 --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonLine/SkeletonLine.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useSkeletonLine_unstable } from './useSkeletonLine'; +import { renderSkeletonLine_unstable } from './renderSkeletonLine'; +import { useSkeletonLineStyles_unstable } from './useSkeletonLineStyles'; +import type { SkeletonLineProps } from './SkeletonLine.types'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; + +/** + * SkeletonLine component - TODO: add more docs + */ +export const SkeletonLine: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useSkeletonLine_unstable(props, ref); + + useSkeletonLineStyles_unstable(state); + return renderSkeletonLine_unstable(state); +}); + +SkeletonLine.displayName = 'SkeletonLine'; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonLine/SkeletonLine.types.ts b/packages/react-components/react-skeleton/src/components/SkeletonLine/SkeletonLine.types.ts new file mode 100644 index 0000000000000..b06a66cf50e5e --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonLine/SkeletonLine.types.ts @@ -0,0 +1,17 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; + +export type SkeletonLineSlots = { + root: Slot<'div'>; +}; + +/** + * SkeletonLine Props + */ +export type SkeletonLineProps = ComponentProps & {}; + +/** + * State used in rendering SkeletonLine + */ +export type SkeletonLineState = ComponentState; +// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from SkeletonLineProps. +// & Required> diff --git a/packages/react-components/react-skeleton/src/components/SkeletonLine/__snapshots__/SkeletonLine.test.tsx.snap b/packages/react-components/react-skeleton/src/components/SkeletonLine/__snapshots__/SkeletonLine.test.tsx.snap new file mode 100644 index 0000000000000..8fab5f361e7ae --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonLine/__snapshots__/SkeletonLine.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SkeletonLine renders a default state 1`] = ` +
+
+ Default SkeletonLine +
+
+`; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonLine/index.ts b/packages/react-components/react-skeleton/src/components/SkeletonLine/index.ts new file mode 100644 index 0000000000000..35db575ff422e --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonLine/index.ts @@ -0,0 +1,5 @@ +export * from './SkeletonLine'; +export * from './SkeletonLine.types'; +export * from './renderSkeletonLine'; +export * from './useSkeletonLine'; +export * from './useSkeletonLineStyles'; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonLine/renderSkeletonLine.tsx b/packages/react-components/react-skeleton/src/components/SkeletonLine/renderSkeletonLine.tsx new file mode 100644 index 0000000000000..27f5d2b2f23e0 --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonLine/renderSkeletonLine.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utilities'; +import type { SkeletonLineState, SkeletonLineSlots } from './SkeletonLine.types'; + +/** + * Render the final JSX of SkeletonLine + */ +export const renderSkeletonLine_unstable = (state: SkeletonLineState) => { + const { slots, slotProps } = getSlots(state); + + // TODO Add additional slots in the appropriate place + return ; +}; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonLine/useSkeletonLine.ts b/packages/react-components/react-skeleton/src/components/SkeletonLine/useSkeletonLine.ts new file mode 100644 index 0000000000000..4803dcc934a54 --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonLine/useSkeletonLine.ts @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { getNativeElementProps } from '@fluentui/react-utilities'; +import type { SkeletonLineProps, SkeletonLineState } from './SkeletonLine.types'; + +/** + * Create the state required to render SkeletonLine. + * + * The returned state can be modified with hooks such as useSkeletonLineStyles_unstable, + * before being passed to renderSkeletonLine_unstable. + * + * @param props - props from this instance of SkeletonLine + * @param ref - reference to root HTMLElement of SkeletonLine + */ +export const useSkeletonLine_unstable = (props: SkeletonLineProps, ref: React.Ref): SkeletonLineState => { + return { + // TODO add appropriate props/defaults + components: { + // TODO add each slot's element type or component + root: 'div', + }, + // TODO add appropriate slots, for example: + // mySlot: resolveShorthand(props.mySlot), + root: getNativeElementProps('div', { + ref, + ...props, + }), + }; +}; diff --git a/packages/react-components/react-skeleton/src/components/SkeletonLine/useSkeletonLineStyles.ts b/packages/react-components/react-skeleton/src/components/SkeletonLine/useSkeletonLineStyles.ts new file mode 100644 index 0000000000000..573ca1450123e --- /dev/null +++ b/packages/react-components/react-skeleton/src/components/SkeletonLine/useSkeletonLineStyles.ts @@ -0,0 +1,33 @@ +import { makeStyles, mergeClasses } from '@griffel/react'; +import type { SkeletonLineSlots, SkeletonLineState } from './SkeletonLine.types'; +import type { SlotClassNames } from '@fluentui/react-utilities'; + +export const skeletonLineClassNames: SlotClassNames = { + root: 'fui-SkeletonLine', + // TODO: add class names for all slots on SkeletonLineSlots. + // Should be of the form `: 'fui-SkeletonLine__` +}; + +/** + * Styles for the root slot + */ +const useStyles = makeStyles({ + root: { + // TODO Add default styles for the root element + }, + + // TODO add additional classes for different states and/or slots +}); + +/** + * Apply styling to the SkeletonLine slots based on the state + */ +export const useSkeletonLineStyles_unstable = (state: SkeletonLineState): SkeletonLineState => { + const styles = useStyles(); + state.root.className = mergeClasses(skeletonLineClassNames.root, styles.root, state.root.className); + + // TODO Add class names to slots, for example: + // state.mySlot.className = mergeClasses(styles.mySlot, state.mySlot.className); + + return state; +}; diff --git a/packages/react-components/react-skeleton/src/index.ts b/packages/react-components/react-skeleton/src/index.ts index aacbad0068e24..1d816f78f50aa 100644 --- a/packages/react-components/react-skeleton/src/index.ts +++ b/packages/react-components/react-skeleton/src/index.ts @@ -1,2 +1,24 @@ -// TODO: replace with real exports -export {}; +export { + Skeleton, + renderSkeleton_unstable, + skeletonClassNames, + useSkeletonStyles_unstable, + useSkeleton_unstable, +} from './Skeleton'; +export type { SkeletonProps, SkeletonSlots, SkeletonState } from './Skeleton'; +export { + SkeletonLine, + renderSkeletonLine_unstable, + skeletonLineClassNames, + useSkeletonLineStyles_unstable, + useSkeletonLine_unstable, +} from './SkeletonLine'; +export type { SkeletonLineProps, SkeletonLineSlots, SkeletonLineState } from './SkeletonLine'; +export { + SkeletonCircle, + renderSkeletonCircle_unstable, + skeletonCircleClassNames, + useSkeletonCircleStyles_unstable, + useSkeletonCircle_unstable, +} from './SkeletonCircle'; +export type { SkeletonCircleProps, SkeletonCircleSlots, SkeletonCircleState } from './SkeletonCircle'; diff --git a/packages/react-components/react-skeleton/src/testing/isConformant.ts b/packages/react-components/react-skeleton/src/testing/isConformant.ts new file mode 100644 index 0000000000000..77f88408cbd83 --- /dev/null +++ b/packages/react-components/react-skeleton/src/testing/isConformant.ts @@ -0,0 +1,14 @@ +import { isConformant as baseIsConformant } from '@fluentui/react-conformance'; +import type { IsConformantOptions, TestObject } from '@fluentui/react-conformance'; +import griffelTests from '@fluentui/react-conformance-griffel'; + +export function isConformant( + testInfo: Omit, 'componentPath'> & { componentPath?: string }, +) { + const defaultOptions: Partial> = { + componentPath: require.main?.filename.replace('.test', ''), + extraTests: griffelTests as TestObject, + }; + + baseIsConformant(defaultOptions, testInfo); +} diff --git a/packages/react-components/react-skeleton/stories/Skeleton/SkeletonBestPractices.md b/packages/react-components/react-skeleton/stories/Skeleton/SkeletonBestPractices.md new file mode 100644 index 0000000000000..08ff8ddeeb5f8 --- /dev/null +++ b/packages/react-components/react-skeleton/stories/Skeleton/SkeletonBestPractices.md @@ -0,0 +1,5 @@ +## Best practices + +### Do + +### Don't diff --git a/packages/react-components/react-skeleton/stories/Skeleton/SkeletonDefault.stories.tsx b/packages/react-components/react-skeleton/stories/Skeleton/SkeletonDefault.stories.tsx new file mode 100644 index 0000000000000..08ea657f06b84 --- /dev/null +++ b/packages/react-components/react-skeleton/stories/Skeleton/SkeletonDefault.stories.tsx @@ -0,0 +1,4 @@ +import * as React from 'react'; +import { Skeleton, SkeletonProps } from '@fluentui/react-skeleton'; + +export const Default = (props: Partial) => ; diff --git a/packages/react-components/react-skeleton/stories/Skeleton/SkeletonDescription.md b/packages/react-components/react-skeleton/stories/Skeleton/SkeletonDescription.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/react-components/react-skeleton/stories/Skeleton/index.stories.tsx b/packages/react-components/react-skeleton/stories/Skeleton/index.stories.tsx new file mode 100644 index 0000000000000..7f9a6dbdfa386 --- /dev/null +++ b/packages/react-components/react-skeleton/stories/Skeleton/index.stories.tsx @@ -0,0 +1,18 @@ +import { Skeleton } from '@fluentui/react-skeleton'; + +import descriptionMd from './SkeletonDescription.md'; +import bestPracticesMd from './SkeletonBestPractices.md'; + +export { Default } from './SkeletonDefault.stories'; + +export default { + title: 'Preview Components/Skeleton', + component: Skeleton, + parameters: { + docs: { + description: { + component: [descriptionMd, bestPracticesMd].join('\n'), + }, + }, + }, +}; diff --git a/packages/react-components/react-skeleton/stories/SkeletonCircle/SkeletonCircleBestPractices.md b/packages/react-components/react-skeleton/stories/SkeletonCircle/SkeletonCircleBestPractices.md new file mode 100644 index 0000000000000..08ff8ddeeb5f8 --- /dev/null +++ b/packages/react-components/react-skeleton/stories/SkeletonCircle/SkeletonCircleBestPractices.md @@ -0,0 +1,5 @@ +## Best practices + +### Do + +### Don't diff --git a/packages/react-components/react-skeleton/stories/SkeletonCircle/SkeletonCircleDefault.stories.tsx b/packages/react-components/react-skeleton/stories/SkeletonCircle/SkeletonCircleDefault.stories.tsx new file mode 100644 index 0000000000000..913b92579b5b9 --- /dev/null +++ b/packages/react-components/react-skeleton/stories/SkeletonCircle/SkeletonCircleDefault.stories.tsx @@ -0,0 +1,4 @@ +import * as React from 'react'; +import { SkeletonCircle, SkeletonCircleProps } from '@fluentui/react-skeleton'; + +export const Default = (props: Partial) => ; diff --git a/packages/react-components/react-skeleton/stories/SkeletonCircle/SkeletonCircleDescription.md b/packages/react-components/react-skeleton/stories/SkeletonCircle/SkeletonCircleDescription.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/react-components/react-skeleton/stories/SkeletonCircle/index.stories.tsx b/packages/react-components/react-skeleton/stories/SkeletonCircle/index.stories.tsx new file mode 100644 index 0000000000000..29f257d199770 --- /dev/null +++ b/packages/react-components/react-skeleton/stories/SkeletonCircle/index.stories.tsx @@ -0,0 +1,18 @@ +import { SkeletonCircle } from '@fluentui/react-skeleton'; + +import descriptionMd from './SkeletonCircleDescription.md'; +import bestPracticesMd from './SkeletonCircleBestPractices.md'; + +export { Default } from './SkeletonCircleDefault.stories'; + +export default { + title: 'Preview Components/SkeletonCircle', + component: SkeletonCircle, + parameters: { + docs: { + description: { + component: [descriptionMd, bestPracticesMd].join('\n'), + }, + }, + }, +}; diff --git a/packages/react-components/react-skeleton/stories/SkeletonLine/SkeletonLineBestPractices.md b/packages/react-components/react-skeleton/stories/SkeletonLine/SkeletonLineBestPractices.md new file mode 100644 index 0000000000000..08ff8ddeeb5f8 --- /dev/null +++ b/packages/react-components/react-skeleton/stories/SkeletonLine/SkeletonLineBestPractices.md @@ -0,0 +1,5 @@ +## Best practices + +### Do + +### Don't diff --git a/packages/react-components/react-skeleton/stories/SkeletonLine/SkeletonLineDefault.stories.tsx b/packages/react-components/react-skeleton/stories/SkeletonLine/SkeletonLineDefault.stories.tsx new file mode 100644 index 0000000000000..e45b2069d4054 --- /dev/null +++ b/packages/react-components/react-skeleton/stories/SkeletonLine/SkeletonLineDefault.stories.tsx @@ -0,0 +1,4 @@ +import * as React from 'react'; +import { SkeletonLine, SkeletonLineProps } from '@fluentui/react-skeleton'; + +export const Default = (props: Partial) => ; diff --git a/packages/react-components/react-skeleton/stories/SkeletonLine/SkeletonLineDescription.md b/packages/react-components/react-skeleton/stories/SkeletonLine/SkeletonLineDescription.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/react-components/react-skeleton/stories/SkeletonLine/index.stories.tsx b/packages/react-components/react-skeleton/stories/SkeletonLine/index.stories.tsx new file mode 100644 index 0000000000000..88998159d1946 --- /dev/null +++ b/packages/react-components/react-skeleton/stories/SkeletonLine/index.stories.tsx @@ -0,0 +1,18 @@ +import { SkeletonLine } from '@fluentui/react-skeleton'; + +import descriptionMd from './SkeletonLineDescription.md'; +import bestPracticesMd from './SkeletonLineBestPractices.md'; + +export { Default } from './SkeletonLineDefault.stories'; + +export default { + title: 'Preview Components/SkeletonLine', + component: SkeletonLine, + parameters: { + docs: { + description: { + component: [descriptionMd, bestPracticesMd].join('\n'), + }, + }, + }, +}; diff --git a/packages/react-components/react-skeleton/tsconfig.json b/packages/react-components/react-skeleton/tsconfig.json index 12ca516af1c5b..1941a041d46c1 100644 --- a/packages/react-components/react-skeleton/tsconfig.json +++ b/packages/react-components/react-skeleton/tsconfig.json @@ -17,6 +17,9 @@ }, { "path": "./tsconfig.spec.json" + }, + { + "path": "./.storybook/tsconfig.json" } ] } diff --git a/packages/react-components/react-skeleton/tsconfig.lib.json b/packages/react-components/react-skeleton/tsconfig.lib.json index b2da24eff1b32..6f90cf95c005b 100644 --- a/packages/react-components/react-skeleton/tsconfig.lib.json +++ b/packages/react-components/react-skeleton/tsconfig.lib.json @@ -9,6 +9,14 @@ "inlineSources": true, "types": ["static-assets", "environment"] }, - "exclude": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.test.ts", "**/*.test.tsx"], + "exclude": [ + "./src/testing/**", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.stories.ts", + "**/*.stories.tsx" + ], "include": ["./src/**/*.ts", "./src/**/*.tsx"] } diff --git a/packages/react-components/react-skeleton/tsconfig.spec.json b/packages/react-components/react-skeleton/tsconfig.spec.json index 469fcba4d7ba7..911456fe4b4d9 100644 --- a/packages/react-components/react-skeleton/tsconfig.spec.json +++ b/packages/react-components/react-skeleton/tsconfig.spec.json @@ -5,5 +5,13 @@ "outDir": "dist", "types": ["jest", "node"] }, - "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.test.ts", "**/*.test.tsx", "**/*.d.ts"] + "include": [ + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.d.ts", + "./src/testing/**/*.ts", + "./src/testing/**/*.tsx" + ] }