diff --git a/packages/react-components/react-storybook-addon/README.md b/packages/react-components/react-storybook-addon/README.md index 452ce80d947b88..a11182fce4201d 100644 --- a/packages/react-components/react-storybook-addon/README.md +++ b/packages/react-components/react-storybook-addon/README.md @@ -68,3 +68,35 @@ module.exports = { - > 💡 this will run `prestorybook` script that compiles addon implementation with all of its direct dependencies that live within monorepo, so it can be consumed by local storybook 2. Every time you do any change to implementation, after you ran your local storybook you'll need to manually run `yarn workspace @fluentui/react-storybook-addon build` to reflect those changes + +## Parameter Configuration + +- Three custom optional parameters can be set to alter behavior of the addon + 1. `dir` - determines whether to render story in `ltr` or `rtl` mode. Default is `undefined`. + 2. `fluentTheme` - determines whether to render story theme in `web-light`, `web-dark`, `teams-high-contrast`, `teams-dark`, or `teams-light`. Setting this + parameter will disable ability to dynamically change the theme within story canvas or doc. + 3. `mode` - when set to `vr-test`, this removes injected padding and background theme that's automatically applied from rendered story. Default is `default`. + +```js +import { FluentParameters, parameters } from '@fluentui/react-storybook-addon'; +import { Button } from '@fluentui/react-components'; + +export const Button = () => ; + +export const ButtonDarkMode = { + render: Button, + parameters: { fluentTheme: 'web-dark' } as FluentParameters, // story renders in Dark mode. +}; + +export const ButtonHighContrast = { + render: Button, + parameters: { fluentTheme: 'teams-high-contrast', mode: 'vr-test' } as FluentParameters; // story renders in High Contrast mode without injected padding and background style. +} + +export const ButtonRTL = { + render: Button, + // parameters identity function will have all TS type annotations built in for intellisense. + parameters: parameters({ fluentTheme: 'web-light', dir: 'rtl', mode: 'vr-test'}), // story renders in RTL, Web light mode and without injected padding and background style. +}; + +``` diff --git a/packages/react-components/react-storybook-addon/etc/react-storybook-addon.api.md b/packages/react-components/react-storybook-addon/etc/react-storybook-addon.api.md index c9d140b21ddb9c..02800552969e24 100644 --- a/packages/react-components/react-storybook-addon/etc/react-storybook-addon.api.md +++ b/packages/react-components/react-storybook-addon/etc/react-storybook-addon.api.md @@ -5,6 +5,7 @@ ```ts import { Args } from '@storybook/api'; +import { Parameters as Parameters_2 } from '@storybook/api'; import { StoryContext } from '@storybook/addons'; import type { Theme } from '@fluentui/react-theme'; @@ -14,12 +15,31 @@ export interface FluentGlobals extends Args { [THEME_ID]?: ThemeIds; } +// @public +export interface FluentParameters extends Parameters_2 { + // (undocumented) + dir?: 'ltr' | 'rtl'; + // (undocumented) + fluentTheme?: ThemeIds; + // (undocumented) + mode?: 'default' | 'vr-test'; +} + // @public (undocumented) export interface FluentStoryContext extends StoryContext { // (undocumented) globals: FluentGlobals; + // (undocumented) + parameters: FluentParameters; } +// @public (undocumented) +export function parameters(options?: FluentParameters): { + dir: string; + fluentTheme: string; + mode: string; +}; + // @public (undocumented) export const THEME_ID: "storybook/fluentui-react-addon/theme"; diff --git a/packages/react-components/react-storybook-addon/src/components/ThemePicker.tsx b/packages/react-components/react-storybook-addon/src/components/ThemePicker.tsx index 8d06603b3e645f..d48dbf24dac82e 100644 --- a/packages/react-components/react-storybook-addon/src/components/ThemePicker.tsx +++ b/packages/react-components/react-storybook-addon/src/components/ThemePicker.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { IconButton, Icons, TooltipLinkList, WithTooltip } from '@storybook/components'; +import { useParameter } from '@storybook/api'; import { ThemeIds, themes, defaultTheme } from '../theme'; import { THEME_ID } from '../constants'; -import { useGlobals } from '../hooks'; +import { useGlobals, FluentParameters } from '../hooks'; export interface ThemeSelectorItem { id: string; @@ -33,7 +34,9 @@ function createThemeItems( export const ThemePicker = () => { const [globals, updateGlobals] = useGlobals(); - const selectedThemeId = globals[THEME_ID] ?? defaultTheme.id; + const fluentTheme: FluentParameters['fluentTheme'] = useParameter('fluentTheme'); + + const selectedThemeId = fluentTheme ? fluentTheme : globals[THEME_ID] ?? defaultTheme.id; const selectedTheme = themes.find(entry => entry.id === selectedThemeId); const isActive = selectedThemeId !== defaultTheme.id; diff --git a/packages/react-components/react-storybook-addon/src/decorators/withFluentProvider.tsx b/packages/react-components/react-storybook-addon/src/decorators/withFluentProvider.tsx index 7132f18de9f27f..8d513eb8961f6a 100644 --- a/packages/react-components/react-storybook-addon/src/decorators/withFluentProvider.tsx +++ b/packages/react-components/react-storybook-addon/src/decorators/withFluentProvider.tsx @@ -2,24 +2,36 @@ import * as React from 'react'; import { FluentProvider } from '@fluentui/react-provider'; import { Theme } from '@fluentui/react-theme'; - -import { themes, defaultTheme } from '../theme'; +import { themes, defaultTheme, ThemeIds } from '../theme'; import { THEME_ID } from '../constants'; import { FluentGlobals, FluentStoryContext } from '../hooks'; +const findTheme = (themeId?: ThemeIds) => { + if (!themeId) { + return; + } + return themes.find(value => value.id === themeId); +}; + const getActiveFluentTheme = (globals: FluentGlobals) => { const selectedThemeId = globals[THEME_ID]; - const { theme } = themes.find(value => value.id === selectedThemeId) ?? defaultTheme; + const { theme } = findTheme(selectedThemeId) ?? defaultTheme; return { theme }; }; export const withFluentProvider = (StoryFn: () => JSX.Element, context: FluentStoryContext) => { - const { theme } = getActiveFluentTheme(context.globals); + const { globals, parameters } = context; + const { mode } = parameters; + const isVrTest = mode === 'vr-test'; + + const globalTheme = getActiveFluentTheme(globals); + const paramTheme = findTheme(parameters.fluentTheme); + const { theme } = paramTheme ?? globalTheme; return ( - - {StoryFn()} + + {isVrTest ? StoryFn() : {StoryFn()}} ); }; @@ -28,5 +40,5 @@ const FluentExampleContainer: React.FC<{ theme: Theme }> = props => { const { theme } = props; const backgroundColor = theme.colorNeutralBackground2; - return
{props.children}
; + return
{props.children}
; }; diff --git a/packages/react-components/react-storybook-addon/src/hooks.ts b/packages/react-components/react-storybook-addon/src/hooks.ts index 649100df48ae84..ac9a7055cef9ff 100644 --- a/packages/react-components/react-storybook-addon/src/hooks.ts +++ b/packages/react-components/react-storybook-addon/src/hooks.ts @@ -1,4 +1,4 @@ -import { useGlobals as useStorybookGlobals, Args as StorybookArgs } from '@storybook/api'; +import { useGlobals as useStorybookGlobals, Args as StorybookArgs, Parameters } from '@storybook/api'; import { StoryContext as StorybookContext } from '@storybook/addons'; import { THEME_ID } from './constants'; @@ -6,15 +6,29 @@ import { ThemeIds } from './theme'; export interface FluentStoryContext extends StorybookContext { globals: FluentGlobals; + parameters: FluentParameters; } /** - * Extends the storybook globals object to include fluent specific propoerties + * Extends the storybook globals object to include fluent specific properties */ export interface FluentGlobals extends StorybookArgs { [THEME_ID]?: ThemeIds; } +/** + * Extends the storybook parameters object to include fluent specific properties + */ +export interface FluentParameters extends Parameters { + dir?: 'ltr' | 'rtl'; + fluentTheme?: ThemeIds; + mode?: 'default' | 'vr-test'; +} + export function useGlobals(): [FluentGlobals, (newGlobals: FluentGlobals) => void] { return useStorybookGlobals(); } + +export function parameters(options?: FluentParameters) { + return { dir: 'ltr', fluentTheme: 'web-light', mode: 'default', ...options }; +} diff --git a/packages/react-components/react-storybook-addon/src/index.ts b/packages/react-components/react-storybook-addon/src/index.ts index ecb1744ced790a..7e02b8967b3eaa 100644 --- a/packages/react-components/react-storybook-addon/src/index.ts +++ b/packages/react-components/react-storybook-addon/src/index.ts @@ -1,4 +1,5 @@ -export type { FluentGlobals, FluentStoryContext } from './hooks'; +export type { FluentGlobals, FluentParameters, FluentStoryContext } from './hooks'; export type { ThemeIds } from './theme'; export { themes } from './theme'; export { THEME_ID } from './constants'; +export { parameters } from './hooks';