diff --git a/.storybook/preview-head-template.html b/.storybook/preview-head-template.html index fc104f390603a7..a1cf7c4a6c27ce 100644 --- a/.storybook/preview-head-template.html +++ b/.storybook/preview-head-template.html @@ -171,10 +171,6 @@ color: #616161; } - #docs-root .docblock-argstable tbody tr:nth-child(even) td { - background: #fafafa; - } - #docs-root .docblock-argstable tbody tr td { vertical-align: middle; } @@ -197,10 +193,6 @@ background: none; } - #docs-root .docs-story > div > div[scale] { - padding: 48px 24px; - } - #docs-root .docs-story > div:last-child { right: 31px; border-radius: 24px; diff --git a/change/@fluentui-react-components-5f56cdc5-254d-4f5d-85fd-05fc8095827d.json b/change/@fluentui-react-components-5f56cdc5-254d-4f5d-85fd-05fc8095827d.json new file mode 100644 index 00000000000000..7e47302d13abcd --- /dev/null +++ b/change/@fluentui-react-components-5f56cdc5-254d-4f5d-85fd-05fc8095827d.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore(react-components): Theme picker for storybook `--docs` mode", + "packageName": "@fluentui/react-components", + "email": "lingfangao@hotmail.com", + "dependentChangeType": "none" +} diff --git a/packages/react-components/.storybook/preview.js b/packages/react-components/.storybook/preview.js index dc19d1c79118ac..495458b1c06684 100644 --- a/packages/react-components/.storybook/preview.js +++ b/packages/react-components/.storybook/preview.js @@ -1,5 +1,5 @@ import * as rootPreview from '../../../.storybook/preview'; -// import { FluentDocsContainer } from '../src/DocsComponents/FluentDocsContainer.stories'; +import { FluentDocsContainer } from '../src/DocsComponents/FluentDocsContainer.stories'; import { FluentDocsPage } from '../src/DocsComponents/FluentDocsPage.stories'; // load global styles @@ -30,6 +30,7 @@ export const parameters = { ...rootPreview.parameters, docs: { ...rootPreview.parameters.docs, + container: FluentDocsContainer, page: FluentDocsPage, // container: FluentDocsContainer, }, diff --git a/packages/react-components/src/DocsComponents/FluentDocsContainer.stories.tsx b/packages/react-components/src/DocsComponents/FluentDocsContainer.stories.tsx index 14199c20ee7b09..10b783571355b3 100644 --- a/packages/react-components/src/DocsComponents/FluentDocsContainer.stories.tsx +++ b/packages/react-components/src/DocsComponents/FluentDocsContainer.stories.tsx @@ -1,14 +1,24 @@ import * as React from 'react'; -import { DocsContainer, DocsContainerProps } from '@storybook/addon-docs'; +import { DocsContainer, DocsContextProps } from '@storybook/addon-docs'; +import { FluentStoryContext, THEME_ID, themes } from '@fluentui/react-storybook-addon'; import { FluentDocsHeader } from './FluentDocsHeader.stories'; +import { FluentProvider, webLightTheme } from '../index'; + +interface FluentDocsContainerProps { + context: FluentStoryContext & DocsContextProps; +} /** * A container that wraps storybook's native docs container to add extra components to the docs experience */ -export const FluentDocsContainer = ({ children, context }: DocsContainerProps & { children: React.ReactNode }) => { +export const FluentDocsContainer: React.FC = ({ children, context }) => { + const selectedTheme = themes.find(theme => theme.id === context.globals[THEME_ID]); + return ( <> - + + + {/** TODO add table of contents */} {children} diff --git a/packages/react-components/src/DocsComponents/FluentDocsHeader.stories.tsx b/packages/react-components/src/DocsComponents/FluentDocsHeader.stories.tsx index b4f976813cb46b..c452fd98bd4f1c 100644 --- a/packages/react-components/src/DocsComponents/FluentDocsHeader.stories.tsx +++ b/packages/react-components/src/DocsComponents/FluentDocsHeader.stories.tsx @@ -1,18 +1,29 @@ import * as React from 'react'; +import { FluentGlobals, THEME_ID } from '@fluentui/react-storybook-addon'; import { makeStyles } from '../index'; +import { ThemePicker } from './ThemePicker.stories'; const useStyles = makeStyles({ - root: { - position: 'sticky', + root: theme => ({ + position: 'fixed', top: 0, padding: '5px', - }, + width: '100%', + background: theme.colorNeutralBackground3, + boxShadow: `${theme.shadow8}`, + borderBottom: `1px solid ${theme.colorTransparentStroke}`, + zIndex: 1000, + }), }); /** * Sticky header over the entire docs page */ -export const FluentDocsHeader = () => { +export const FluentDocsHeader: React.FC<{ storybookGlobals: FluentGlobals }> = ({ storybookGlobals }) => { const styles = useStyles(); - return
{/** TODO add theme+version picker */}
; + return ( +
+ +
+ ); }; diff --git a/packages/react-components/src/DocsComponents/ThemePicker.stories.tsx b/packages/react-components/src/DocsComponents/ThemePicker.stories.tsx new file mode 100644 index 00000000000000..34e2079da0e4fa --- /dev/null +++ b/packages/react-components/src/DocsComponents/ThemePicker.stories.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { MenuButton, Menu, MenuPopover, MenuItemRadio, MenuList, MenuTrigger, makeStyles, MenuProps } from '../index'; +import { themes, ThemeIds, THEME_ID } from '@fluentui/react-storybook-addon'; +import addons from '@storybook/addons'; + +const useStyles = makeStyles({ + menuButton: { + minWidth: '210px', + justifyContent: 'flex-start', + }, + + chevronIcon: { + marginLeft: 'auto', + }, + + menuPopover: { + minWidth: '210px', + }, +}); + +/** + * Theme picker used in the react-components docs header + */ +export const ThemePicker: React.FC<{ selectedThemeId?: string }> = ({ selectedThemeId }) => { + const styles = useStyles(); + + const setGlobalTheme = (themeId: ThemeIds): void => { + addons.getChannel().emit('updateGlobals', { globals: { [THEME_ID]: themeId } }); + }; + const onCheckedValueChange: MenuProps['onCheckedValueChange'] = (e, data) => { + setGlobalTheme(data.checkedItems[0] as ThemeIds); + }; + + const selectedTheme = themes.find(theme => theme.id === selectedThemeId); + + return ( + + + + {selectedTheme?.label ?? 'Theme'} + + + + + {themes.map(theme => ( + + {theme.label} + + ))} + + + + ); +}; diff --git a/packages/react-storybook-addon/README.md b/packages/react-storybook-addon/README.md index 9a052541499df5..9bd2f2410b30d5 100644 --- a/packages/react-storybook-addon/README.md +++ b/packages/react-storybook-addon/README.md @@ -11,6 +11,36 @@ These are not production-ready components and **should never be used in product* - adds fluent theme switcher - ![Fluent Theme Switcher](https://user-images.githubusercontent.com/20744592/138872560-8ef40c25-193c-47db-a216-7c1e86fe8cda.png) +**Theme management in storybook globals** + +Exports types and utilities to set and consume the correct Fluent theme in storybook globals. Here's some example picker +that sets the Fluent them in storybook globals. + +```tsx +import * as React from 'react'; +import { themes, setGlobalTheme, FluentStoryContext, THEME_ID } from '@fluentui/react-storybook-addon'; + +// storybook context which can be accessed for example in decorators +// https://storybook.js.org/docs/react/writing-stories/decorators#context-for-mocking +export const ThemePicker: React.FC<{ context: FluentStoryContext }> = ({ context }) => { + const handleChange = e => { + setGlobalTheme(e.target.value); + }; + + const selectedTheme = themes.find(theme => theme.id === context.globals[THEME_ID]); + + return ( + + ); +}; +``` + ## Getting Started ### Installation diff --git a/packages/react-storybook-addon/etc/react-storybook-addon.api.md b/packages/react-storybook-addon/etc/react-storybook-addon.api.md index 4d5ec18c6dbadc..b1244a204d005a 100644 --- a/packages/react-storybook-addon/etc/react-storybook-addon.api.md +++ b/packages/react-storybook-addon/etc/react-storybook-addon.api.md @@ -4,9 +4,54 @@ ```ts +import { Args } from '@storybook/api'; +import { StoryContext } from '@storybook/addons'; +import type { Theme } from '@fluentui/react-theme'; + +// @public +export interface FluentGlobals extends Args { + // (undocumented) + [THEME_ID]?: ThemeIds; +} + +// @public (undocumented) +export interface FluentStoryContext extends StoryContext { + // (undocumented) + globals: FluentGlobals; +} + +// @public (undocumented) +export const THEME_ID: "storybook/fluentui-react-addon/theme"; + +// @public (undocumented) +export type ThemeIds = typeof themes[number]['id']; + // @public (undocumented) -const _default: {}; -export default _default; +export const themes: readonly [{ + readonly id: "web-light"; + readonly label: "Web Light"; + readonly theme: Theme; +}, { + readonly id: "web-dark"; + readonly label: "Web Dark"; + readonly theme: Theme; +}, { + readonly id: "web-high-contrast"; + readonly label: "Web High Contrast"; + readonly theme: Theme; +}, { + readonly id: "teams-light"; + readonly label: "Teams Light"; + readonly theme: Theme; +}, { + readonly id: "teams-dark"; + readonly label: "Teams Dark"; + readonly theme: Theme; +}, { + readonly id: "teams-high-contrast"; + readonly label: "Teams High Contrast"; + readonly theme: Theme; +}]; // (No @packageDocumentation comment for this package) diff --git a/packages/react-storybook-addon/src/decorators/withFluentProvider.tsx b/packages/react-storybook-addon/src/decorators/withFluentProvider.tsx index c520b7dde643a3..11f8b5a83c8101 100644 --- a/packages/react-storybook-addon/src/decorators/withFluentProvider.tsx +++ b/packages/react-storybook-addon/src/decorators/withFluentProvider.tsx @@ -27,6 +27,6 @@ export const withFluentProvider = (StoryFn: StoryFunction, c const FluentExampleContainer: React.FC<{ theme: Theme }> = props => { const { theme } = props; - const backgroundColor = theme.colorNeutralBackground1; - return
{props.children}
; + const backgroundColor = theme.colorNeutralBackground2; + return
{props.children}
; }; diff --git a/packages/react-storybook-addon/src/index.ts b/packages/react-storybook-addon/src/index.ts index ff8b4c56321a33..3da679ee45e465 100644 --- a/packages/react-storybook-addon/src/index.ts +++ b/packages/react-storybook-addon/src/index.ts @@ -1 +1,3 @@ -export default {}; +export type { FluentGlobals, FluentStoryContext } from './hooks'; +export { themes, ThemeIds } from './theme'; +export { THEME_ID } from './constants';