- {(showThemePicker || showDirSwitcher || showCopyAsMarkdown) && (
+ {(showThemePicker || showDirSwitcher || showVisualLanguagePicker || showCopyAsMarkdown) && (
{showThemePicker && }
+ {showVisualLanguagePicker && }
{showDirSwitcher && }
{showCopyAsMarkdown && }
diff --git a/packages/react-components/react-storybook-addon/src/docs/VisualLanguagePicker.tsx b/packages/react-components/react-storybook-addon/src/docs/VisualLanguagePicker.tsx
new file mode 100644
index 00000000000000..945dfce04072d0
--- /dev/null
+++ b/packages/react-components/react-storybook-addon/src/docs/VisualLanguagePicker.tsx
@@ -0,0 +1,71 @@
+import * as React from 'react';
+import { addons } from 'storybook/preview-api';
+
+import { Menu, MenuItemRadio, MenuList, MenuPopover, MenuTrigger } from '@fluentui/react-menu';
+import type { MenuProps } from '@fluentui/react-menu';
+import { MenuButton } from '@fluentui/react-button';
+import { makeStyles } from '@griffel/react';
+
+import type { CapIds } from '../cap';
+import { capOptions, CAP_ID } from '..';
+
+const useStyles = makeStyles({
+ menuButton: {
+ minWidth: '160px',
+ justifyContent: 'flex-start',
+ },
+
+ chevronIcon: {
+ marginLeft: 'auto',
+ },
+
+ menuPopover: {
+ minWidth: '160px',
+ },
+});
+
+/**
+ * CAP visual-language picker used in the react-components docs header.
+ *
+ * Mirrors `ThemePicker`: stores the selected option id (`'cap' | 'base'`)
+ * as a Storybook global so it persists in the URL as a readable value.
+ */
+export const VisualLanguagePicker: React.FC<{ selectedCapId?: CapIds }> = ({ selectedCapId }) => {
+ const styles = useStyles();
+ const [currentCapId, setCurrentCapId] = React.useState
(selectedCapId ?? null);
+
+ const setGlobalCap = (capId: CapIds): void => {
+ addons.getChannel().emit('updateGlobals', { globals: { [CAP_ID]: capId } });
+ };
+ const onCheckedValueChange: MenuProps['onCheckedValueChange'] = (_e, data) => {
+ const newCapId = data.checkedItems[0] as CapIds;
+ setGlobalCap(newCapId);
+ setCurrentCapId(newCapId);
+ };
+
+ const selectedCap = capOptions.find(o => o.id === currentCapId);
+
+ return (
+
+ );
+};
diff --git a/packages/react-components/react-storybook-addon/src/docs/utils.ts b/packages/react-components/react-storybook-addon/src/docs/utils.ts
index 2521c45389ca4f..fd59c6911ea849 100644
--- a/packages/react-components/react-storybook-addon/src/docs/utils.ts
+++ b/packages/react-components/react-storybook-addon/src/docs/utils.ts
@@ -7,6 +7,7 @@ const docsDefaults = {
tableOfContents: true,
dirSwitcher: true,
themePicker: true,
+ visualLanguagePicker: true,
argTable: {
slotsApi: true,
nativePropsApi: true,
@@ -46,6 +47,7 @@ export function getDocsPageConfig(context: DocsContextProps): {
tableOfContents: boolean;
dirSwitcher: boolean;
themePicker: boolean;
+ visualLanguagePicker: boolean;
copyAsMarkdown: boolean;
argTable: {
slotsApi: boolean;
@@ -66,6 +68,7 @@ export function getDocsPageConfig(context: DocsContextProps): {
tableOfContents: docsConfig.tableOfContents !== false,
dirSwitcher: docsConfig.dirSwitcher !== false,
themePicker: docsConfig.themePicker !== false,
+ visualLanguagePicker: docsConfig.visualLanguagePicker !== false,
argTable: getArgTableConfig(docsConfig.argTable),
};
}
diff --git a/packages/react-components/react-storybook-addon/src/hooks.ts b/packages/react-components/react-storybook-addon/src/hooks.ts
index c054b4bda33f2b..930af569dbaca0 100644
--- a/packages/react-components/react-storybook-addon/src/hooks.ts
+++ b/packages/react-components/react-storybook-addon/src/hooks.ts
@@ -1,8 +1,9 @@
import { useGlobals as useStorybookGlobals } from 'storybook/manager-api';
import type { Args as StorybookArgs, StoryContext as StorybookContext, Parameters } from '@storybook/react-webpack5';
-import type { DIR_ID, STRICT_MODE_ID, THEME_ID } from './constants';
+import type { CAP_ID, DIR_ID, STRICT_MODE_ID, THEME_ID } from './constants';
import type { ThemeIds } from './theme';
+import type { CapIds } from './cap';
export interface FluentStoryContext extends StorybookContext {
globals: FluentGlobals;
@@ -16,6 +17,7 @@ export interface FluentGlobals extends StorybookArgs {
[DIR_ID]?: 'ltr' | 'rtl';
[THEME_ID]?: ThemeIds;
[STRICT_MODE_ID]?: boolean;
+ [CAP_ID]?: CapIds;
}
/**
@@ -24,6 +26,7 @@ export interface FluentGlobals extends StorybookArgs {
export interface FluentParameters extends Parameters {
dir?: 'ltr' | 'rtl';
fluentTheme?: ThemeIds;
+ cap?: CapIds;
mode?: 'default' | 'vr-test';
reactStorybookAddon?: {
disabledDecorators?: ['AriaLive' | 'FluentProvider' | 'ReactStrictMode'];
@@ -40,6 +43,7 @@ type FluentDocsConfig =
tableOfContents?: boolean;
dirSwitcher?: boolean;
themePicker?: boolean;
+ visualLanguagePicker?: boolean;
copyAsMarkdown?: boolean;
argTable?:
| boolean
diff --git a/packages/react-components/react-storybook-addon/src/index.ts b/packages/react-components/react-storybook-addon/src/index.ts
index d15dce947e7687..e583b19da468e1 100644
--- a/packages/react-components/react-storybook-addon/src/index.ts
+++ b/packages/react-components/react-storybook-addon/src/index.ts
@@ -1,7 +1,9 @@
export type { FluentGlobals, FluentParameters, FluentStoryContext } from './hooks';
export type { ThemeIds } from './theme';
export { themes } from './theme';
-export { DIR_ID, THEME_ID } from './constants';
+export type { CapIds } from './cap';
+export { capOptions, defaultCap } from './cap';
+export { CAP_ID, DIR_ID, THEME_ID } from './constants';
export { parameters } from './hooks';
export { FluentCanvas, FluentDocsPage, FluentStory } from './docs';
export type { FluentDocsPageProps } from './docs';
diff --git a/packages/react-components/react-storybook-addon/src/preset/manager.ts b/packages/react-components/react-storybook-addon/src/preset/manager.ts
index caacbc158d77bd..c3957f17ad9a25 100644
--- a/packages/react-components/react-storybook-addon/src/preset/manager.ts
+++ b/packages/react-components/react-storybook-addon/src/preset/manager.ts
@@ -1,9 +1,10 @@
import { addons, types } from 'storybook/manager-api';
-import { ADDON_ID, DIR_ID, STRICT_MODE_ID, THEME_ID } from '../constants';
+import { ADDON_ID, CAP_ID, DIR_ID, STRICT_MODE_ID, THEME_ID } from '../constants';
import { ThemePicker } from '../components/ThemePicker';
import { ReactStrictMode } from '../components/ReactStrictMode';
import { DirectionSwitch } from '../components/DirectionSwitch';
+import { VisualLanguagePicker } from '../components/VisualLanguagePicker';
addons.register(ADDON_ID, () => {
addons.add(THEME_ID, {
@@ -13,6 +14,13 @@ addons.register(ADDON_ID, () => {
match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)),
render: ThemePicker,
});
+ addons.add(CAP_ID, {
+ title: 'CAP Visual Language',
+
+ type: types.TOOL,
+ match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)),
+ render: VisualLanguagePicker,
+ });
addons.add(DIR_ID, {
title: 'Direction Switch',
diff --git a/packages/react-components/react-storybook-addon/src/preset/preview.ts b/packages/react-components/react-storybook-addon/src/preset/preview.ts
index 5b3fc2e687c8f2..98005e756ba2c3 100644
--- a/packages/react-components/react-storybook-addon/src/preset/preview.ts
+++ b/packages/react-components/react-storybook-addon/src/preset/preview.ts
@@ -5,11 +5,16 @@ import { withReactStrictMode } from '../decorators/withReactStrictMode';
import { withAriaLive } from '../decorators/withAriaLive';
import { FluentDocsContainer, FluentDocsPage } from '../docs';
-import { DIR_ID, STRICT_MODE_ID, THEME_ID } from '../constants';
+import { CAP_ID, DIR_ID, STRICT_MODE_ID, THEME_ID } from '../constants';
export const decorators = [withFluentProvider, withAriaLive, withReactStrictMode] as Preview['decorators'];
-export const initialGlobals = { [THEME_ID]: undefined, [DIR_ID]: undefined, [STRICT_MODE_ID]: undefined }; // allow theme to be set by URL query param
+export const initialGlobals = {
+ [THEME_ID]: undefined,
+ [DIR_ID]: undefined,
+ [STRICT_MODE_ID]: undefined,
+ [CAP_ID]: undefined,
+}; // allow theme to be set by URL query param
const preview: Preview = {
decorators,
diff --git a/yarn.lock b/yarn.lock
index 8fb3ef306d2a5b..2b22debaa0fd98 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1806,6 +1806,13 @@
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62"
integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==
+"@fluentui-contrib/react-cap-theme@^0.4.2":
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/@fluentui-contrib/react-cap-theme/-/react-cap-theme-0.4.2.tgz#424f7044049396cb724da1922961d5ca4d0f4436"
+ integrity sha512-yRXtfes9QLHFB4Bt0DP3uJZ8ft7lXuEw80HKZGbeBKb764+Jtx2EgzCcFYoRddhwkKOc0fBv/DpiNjdSHhLHnw==
+ dependencies:
+ "@swc/helpers" "~0.5.11"
+
"@fluentui/accessibility@^0.66.5":
version "0.66.5"
resolved "https://registry.yarnpkg.com/@fluentui/accessibility/-/accessibility-0.66.5.tgz#dd7db595b5404a7a8ddf05032da4278a4408172f"
@@ -4005,6 +4012,13 @@
dependencies:
tslib "^2.4.0"
+"@swc/helpers@~0.5.11":
+ version "0.5.23"
+ resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.23.tgz#19287d0d86d962b111376039a50c792902c9a86a"
+ integrity sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==
+ dependencies:
+ tslib "^2.8.0"
+
"@swc/jest@0.2.38":
version "0.2.38"
resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.38.tgz#8b137e344c6c021d4e49ee2bc62b0e5e564d2c7c"
@@ -19766,7 +19780,7 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
-tslib@2.8.1, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.5.0, tslib@^2.6.2, tslib@^2.8.1:
+tslib@2.8.1, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.5.0, tslib@^2.6.2, tslib@^2.8.0, tslib@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==