@fluentui-react-native/storybook-desktop-runtime 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.cache/tsconfig.tsbuildinfo +1 -1
  2. package/CHANGELOG.md +47 -0
  3. package/README.md +10 -0
  4. package/config/metro.cjs +6 -1
  5. package/jest.config.cjs +1 -0
  6. package/lib/DesktopStoryRoot.d.ts.map +1 -1
  7. package/lib/DesktopStoryRoot.js +3 -2
  8. package/lib/DesktopStoryRoot.js.map +1 -1
  9. package/lib/StorybookTheme.d.ts +0 -1
  10. package/lib/StorybookTheme.d.ts.map +1 -1
  11. package/lib/StorybookTheme.js +29 -27
  12. package/lib/StorybookTheme.js.map +1 -1
  13. package/lib/StorybookTheme.test.d.ts +2 -0
  14. package/lib/StorybookTheme.test.d.ts.map +1 -0
  15. package/lib/StorybookTheme.test.js +111 -0
  16. package/lib/StorybookTheme.test.js.map +1 -0
  17. package/lib/StorybookUI.win32.js +3 -3
  18. package/lib/StorybookUI.win32.js.map +1 -1
  19. package/lib/ThemedStorybookUI.d.ts +3 -0
  20. package/lib/ThemedStorybookUI.d.ts.map +1 -0
  21. package/lib/ThemedStorybookUI.js +8 -0
  22. package/lib/ThemedStorybookUI.js.map +1 -0
  23. package/lib/Win32CalloutPortal.d.ts.map +1 -1
  24. package/lib/Win32CalloutPortal.js +3 -2
  25. package/lib/Win32CalloutPortal.js.map +1 -1
  26. package/lib/createDesktopStorybookApp.d.ts.map +1 -1
  27. package/lib/createDesktopStorybookApp.js +4 -14
  28. package/lib/createDesktopStorybookApp.js.map +1 -1
  29. package/lib/createDesktopStorybookPreview.d.ts.map +1 -1
  30. package/lib/createDesktopStorybookPreview.js +1 -2
  31. package/lib/createDesktopStorybookPreview.js.map +1 -1
  32. package/lib/useStorybookTheme.d.ts +3 -0
  33. package/lib/useStorybookTheme.d.ts.map +1 -0
  34. package/lib/useStorybookTheme.js +105 -0
  35. package/lib/useStorybookTheme.js.map +1 -0
  36. package/lib/useStorybookTheme.test.d.ts +2 -0
  37. package/lib/useStorybookTheme.test.d.ts.map +1 -0
  38. package/lib/useStorybookTheme.test.js +43 -0
  39. package/lib/useStorybookTheme.test.js.map +1 -0
  40. package/package.json +13 -6
  41. package/src/DesktopStoryRoot.tsx +3 -2
  42. package/src/StorybookTheme.test.tsx +133 -0
  43. package/src/StorybookTheme.tsx +91 -78
  44. package/src/StorybookUI.win32.tsx +3 -3
  45. package/src/ThemedStorybookUI.tsx +9 -0
  46. package/src/Win32CalloutPortal.tsx +4 -3
  47. package/src/createDesktopStorybookApp.tsx +6 -25
  48. package/src/createDesktopStorybookPreview.tsx +5 -8
  49. package/src/useStorybookTheme.test.tsx +63 -0
  50. package/src/useStorybookTheme.ts +112 -0
@@ -1,11 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { StyleSheet, View } from 'react-native';
3
3
  import { DesktopStoryRoot } from './DesktopStoryRoot';
4
- import { StorybookThemeProvider } from './StorybookTheme';
5
4
  export function createDesktopStorybookPreview() {
6
5
  return {
7
6
  decorators: [
8
- (Story, context) => (_jsx(StorybookThemeProvider, { children: _jsx(DesktopStoryRoot, { storyId: context.id, children: _jsx(View, { style: styles.story, children: _jsx(Story, {}) }) }) })),
7
+ (Story, context) => (_jsx(DesktopStoryRoot, { storyId: context.id, children: _jsx(View, { style: styles.story, children: _jsx(Story, {}) }) })),
9
8
  ],
10
9
  parameters: {},
11
10
  };
@@ -1 +1 @@
1
- {"version":3,"file":"createDesktopStorybookPreview.js","sourceRoot":"","sources":["../src/createDesktopStorybookPreview.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,MAAM,UAAU,6BAA6B;IAC3C,OAAO;QACL,UAAU,EAAE;YACV,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAClB,KAAC,sBAAsB,cACrB,KAAC,gBAAgB,IAAC,OAAO,EAAE,OAAO,CAAC,EAAE,YACnC,KAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,KAAK,YACvB,KAAC,KAAK,KAAG,GACJ,GACU,GACI,CAC1B;SACF;QACD,UAAU,EAAE,EAAE;KACf,CAAC;AACJ,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC/B,KAAK,EAAE;QACL,UAAU,EAAE,QAAQ;QACpB,IAAI,EAAE,CAAC;QACP,cAAc,EAAE,QAAQ;QACxB,OAAO,EAAE,EAAE;KACZ;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"createDesktopStorybookPreview.js","sourceRoot":"","sources":["../src/createDesktopStorybookPreview.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,UAAU,6BAA6B;IAC3C,OAAO;QACL,UAAU,EAAE;YACV,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAClB,KAAC,gBAAgB,IAAC,OAAO,EAAE,OAAO,CAAC,EAAE,YACnC,KAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,KAAK,YACvB,KAAC,KAAK,KAAG,GACJ,GACU,CACpB;SACF;QACD,UAAU,EAAE,EAAE;KACf,CAAC;AACJ,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC/B,KAAK,EAAE;QACL,UAAU,EAAE,QAAQ;QACpB,IAAI,EAAE,CAAC;QACP,cAAc,EAAE,QAAQ;QACxB,OAAO,EAAE,EAAE;KACZ;CACF,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Theme } from '@storybook/react-native-theming';
2
+ export declare function useStorybookTheme(): Theme;
3
+ //# sourceMappingURL=useStorybookTheme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStorybookTheme.d.ts","sourceRoot":"","sources":["../src/useStorybookTheme.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAC;AAE7D,wBAAgB,iBAAiB,IAAI,KAAK,CAWzC"}
@@ -0,0 +1,105 @@
1
+ import * as React from 'react';
2
+ import { useThemeState } from '@fluentui-react-native/design';
3
+ import { darkTheme, theme as lightTheme } from '@storybook/react-native-theming';
4
+ export function useStorybookTheme() {
5
+ const state = useThemeState();
6
+ const { theme, nativeColors } = React.useMemo(() => storybookThemeFromThemeState(state), [state]);
7
+ React.useEffect(() => {
8
+ if (nativeColors.length > 0) {
9
+ console.warn(`Storybook chrome cannot resolve native system colors; using fixed contrasting theme colors for: ${nativeColors.join(', ')}.`);
10
+ }
11
+ }, [nativeColors]);
12
+ return theme;
13
+ }
14
+ function storybookThemeFromThemeState({ appearance, highContrast, tokens }) {
15
+ const base = highContrast || appearance.colorScheme === 'dark' ? darkTheme : lightTheme;
16
+ const foreground = base.base === 'dark' ? 'fixedWhite' : 'fixedBlack';
17
+ const background = base.base === 'dark' ? 'fixedBlack' : 'fixedWhite';
18
+ const nativeColors = new Set();
19
+ const color = (name, role = 'foreground') => {
20
+ const value = tokens.color[name];
21
+ // Storybook also passes these colors to JS color utilities, not just native styles.
22
+ if (typeof value === 'string') {
23
+ return value;
24
+ }
25
+ nativeColors.add(name);
26
+ const fallbackName = role === 'foreground' ? foreground : background;
27
+ const fallback = tokens.color[fallbackName];
28
+ if (typeof fallback !== 'string') {
29
+ throw new Error(`Storybook chrome requires a literal "${fallbackName}" fallback for the "${name}" theme token.`);
30
+ }
31
+ return fallback;
32
+ };
33
+ const theme = {
34
+ ...base,
35
+ color: {
36
+ ...base.color,
37
+ primary: color('foregroundBrandPrimary'),
38
+ secondary: color('backgroundBrandHeavy'),
39
+ secondaryLighter: color('backgroundBrandSoft', 'background'),
40
+ tertiary: color('foregroundSuccessPrimary'),
41
+ ancillary: color('foregroundBrandPrimary'),
42
+ orange: color('foregroundWarningPrimary'),
43
+ gold: color('foregroundWarningPrimary'),
44
+ green: color('foregroundSuccessPrimary'),
45
+ seafoam: color('foregroundSuccessPrimary'),
46
+ purple: color('foregroundBrandPrimary'),
47
+ ultraviolet: color('foregroundBrandPrimary'),
48
+ lightest: color('foregroundBrandOnloud', 'background'),
49
+ lighter: color('surfaceNeutralNear', 'background'),
50
+ light: color('backgroundNeutralSubtle', 'background'),
51
+ mediumlight: color('strokeNeutralSubtle'),
52
+ medium: color('strokeNeutralSoft'),
53
+ mediumdark: color('foregroundNeutralSecondary'),
54
+ dark: color('foregroundNeutralPrimary'),
55
+ darker: color('foregroundNeutralPrimary'),
56
+ darkest: color('foregroundNeutralPrimary'),
57
+ border: color('strokeNeutralSoft'),
58
+ positive: color('foregroundSuccessPrimary'),
59
+ negative: color('foregroundDangerPrimary'),
60
+ warning: color('foregroundWarningPrimary'),
61
+ critical: color('foregroundDangerPrimary'),
62
+ defaultText: color('foregroundNeutralPrimary'),
63
+ inverseText: color('foregroundNeutralOnloud', 'background'),
64
+ positiveText: color('foregroundSuccessPrimary'),
65
+ negativeText: color('foregroundDangerPrimary'),
66
+ warningText: color('foregroundWarningPrimary'),
67
+ },
68
+ background: {
69
+ ...base.background,
70
+ app: color('surfaceNeutralFar', 'background'),
71
+ bar: color('backgroundNeutralSubtle', 'background'),
72
+ content: color('surfaceNeutralNear', 'background'),
73
+ preview: color('surfaceNeutralFar', 'background'),
74
+ hoverable: color('backgroundNeutralSoft', 'background'),
75
+ positive: color('backgroundSuccessSubtle', 'background'),
76
+ negative: color('backgroundDangerSubtle', 'background'),
77
+ warning: color('backgroundWarningSubtle', 'background'),
78
+ critical: color('backgroundDangerSubtle', 'background'),
79
+ },
80
+ textMutedColor: color('foregroundNeutralTertiary'),
81
+ input: {
82
+ ...base.input,
83
+ border: color('strokeNeutralSoft'),
84
+ background: color('backgroundNeutralLoud', 'background'),
85
+ color: color('foregroundNeutralPrimary'),
86
+ },
87
+ button: {
88
+ ...base.button,
89
+ background: color('backgroundNeutralLoud', 'background'),
90
+ border: color('strokeNeutralSoft'),
91
+ },
92
+ boolean: {
93
+ ...base.boolean,
94
+ background: color('backgroundNeutralSoft', 'background'),
95
+ selectedBackground: color('backgroundBrandHeavy'),
96
+ },
97
+ appBorderColor: color('strokeNeutralSubtle'),
98
+ barTextColor: color('foregroundNeutralPrimary'),
99
+ barHoverColor: color('foregroundBrandPrimary'),
100
+ barSelectedColor: color('foregroundBrandPrimary'),
101
+ barBg: color('backgroundNeutralSubtle', 'background'),
102
+ };
103
+ return { theme, nativeColors: [...nativeColors] };
104
+ }
105
+ //# sourceMappingURL=useStorybookTheme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStorybookTheme.js","sourceRoot":"","sources":["../src/useStorybookTheme.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useStorybookTheme.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStorybookTheme.test.d.ts","sourceRoot":"","sources":["../src/useStorybookTheme.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PlatformColor } from 'react-native';
3
+ import { act, render } from '@testing-library/react-native';
4
+ import { FlexThemeReference, ThemedRoot, useThemeState } from '@fluentui-react-native/design';
5
+ import { useStorybookTheme } from './useStorybookTheme';
6
+ function Probe({ themes, states }) {
7
+ themes.push(useStorybookTheme());
8
+ const state = useThemeState();
9
+ states?.push(state);
10
+ return null;
11
+ }
12
+ describe('Storybook theme palette', () => {
13
+ afterEach(() => {
14
+ jest.restoreAllMocks();
15
+ });
16
+ it('derives colors from the current tokens and updates after source invalidation', async () => {
17
+ const themes = [];
18
+ const states = [];
19
+ const source = new FlexThemeReference({
20
+ base: { color: { surfaceNeutralFar: '#123456', foregroundNeutralPrimary: '#abcdef' } },
21
+ });
22
+ const scene = await render(_jsx(ThemedRoot, { theme: source, children: _jsx(Probe, { themes: themes, states: states }) }));
23
+ expect(themes[0].background.app).toBe('#123456');
24
+ expect(themes[0].color.defaultText).toBe('#abcdef');
25
+ await scene.rerender(_jsx(ThemedRoot, { theme: source, children: _jsx(Probe, { themes: themes, states: states }) }));
26
+ expect(themes[themes.length - 1]).toBe(themes[0]);
27
+ await act(() => source.update({ color: { surfaceNeutralFar: '#654321' } }));
28
+ expect(themes[themes.length - 1].background.app).toBe('#654321');
29
+ expect(themes[themes.length - 1]).not.toBe(themes[0]);
30
+ expect(themes[themes.length - 1].appBorderColor).toBe(states[states.length - 1].tokens.color.strokeNeutralSubtle);
31
+ });
32
+ it('warns and substitutes a contrasting theme color for native opaque colors', async () => {
33
+ const warn = jest.spyOn(console, 'warn').mockImplementation();
34
+ const themes = [];
35
+ const states = [];
36
+ const source = new FlexThemeReference({ base: { color: { foregroundBrandPrimary: PlatformColor('labelColor') } } });
37
+ await render(_jsx(ThemedRoot, { theme: source, children: _jsx(Probe, { themes: themes, states: states }) }));
38
+ expect(themes[0].color.primary).toBe(states[0].tokens.color.fixedBlack);
39
+ expect(warn).toHaveBeenCalledTimes(1);
40
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('foregroundBrandPrimary'));
41
+ });
42
+ });
43
+ //# sourceMappingURL=useStorybookTheme.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStorybookTheme.test.js","sourceRoot":"","sources":["../src/useStorybookTheme.test.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluentui-react-native/storybook-desktop-runtime",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "React Native runtime and Metro services for desktop Storybook applications",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -27,12 +27,13 @@
27
27
  "build": "tsc -b",
28
28
  "clean": "fluentui-scripts clean",
29
29
  "format": "fluentui-scripts format",
30
- "lint": "fluentui-scripts lint"
30
+ "lint": "fluentui-scripts lint",
31
+ "test": "fluentui-scripts jest"
31
32
  },
32
33
  "dependencies": {
33
- "@fluentui-react-native/callout": "0.29.4",
34
- "@fluentui-react-native/default-theme": "0.27.11",
35
- "@fluentui-react-native/design": "0.4.0",
34
+ "@fluentui-react-native/callout": "0.29.5",
35
+ "@fluentui-react-native/default-theme": "0.27.13",
36
+ "@fluentui-react-native/design": "0.5.0",
36
37
  "@rnx-kit/metro-config": "^2.2.4",
37
38
  "@rnx-kit/metro-resolver-symlinks": "^0.3.1",
38
39
  "@storybook/react-native": "^10.4.7",
@@ -46,12 +47,17 @@
46
47
  "@babel/core": "^7.20.0",
47
48
  "@fluentui-react-native/scripts": "0.1.0",
48
49
  "@office-iss/react-native-win32": "^0.81.0",
50
+ "@react-native-community/cli": "^20.0.0",
51
+ "@react-native-community/cli-platform-android": "^20.0.0",
52
+ "@react-native-community/cli-platform-ios": "^20.0.0",
49
53
  "@react-native/babel-preset": "^0.81.0",
54
+ "@testing-library/react-native": "^14.0.1",
50
55
  "@types/react": "~19.1.4",
51
56
  "react": "19.1.4",
52
57
  "react-native": "^0.81.6",
53
58
  "react-native-macos": "^0.81.0",
54
- "react-native-windows": "^0.81.0"
59
+ "react-native-windows": "^0.81.0",
60
+ "test-renderer": "1.1.0"
55
61
  },
56
62
  "peerDependencies": {
57
63
  "@office-iss/react-native-win32": "^0.81.0",
@@ -76,6 +82,7 @@
76
82
  }
77
83
  },
78
84
  "furn": {
85
+ "jestPlatform": "macos",
79
86
  "knip": {
80
87
  "ignoreDependencies": [
81
88
  "@fluentui-react-native/callout"
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { StyleSheet, View } from 'react-native';
2
+ import { Platform, StyleSheet, View } from 'react-native';
3
3
  import { addons } from 'storybook/preview-api';
4
4
 
5
5
  import { desktopStoryErrorEvent, desktopStoryReadyEvent } from './DesktopDriverBridge';
@@ -13,6 +13,7 @@ type DesktopStoryRootProps = {
13
13
  export function DesktopStoryRoot({ children, storyId }: DesktopStoryRootProps) {
14
14
  const { runtimeInstance, selection } = useDesktopStorybookConfig();
15
15
  const testID = useDesktopStorybookTestID('story-root');
16
+ const exposeNativeMarker = Platform.OS === 'macos' || Platform.OS === 'windows' || Platform.OS === ('win32' as any);
16
17
  const activeSelection = selection?.storyId === storyId ? selection : undefined;
17
18
 
18
19
  React.useEffect(() => {
@@ -49,7 +50,7 @@ export function DesktopStoryRoot({ children, storyId }: DesktopStoryRootProps) {
49
50
  }
50
51
  }}
51
52
  >
52
- <View accessibilityLabel={marker} style={styles.root} testID={testID}>
53
+ <View accessibilityLabel={marker} accessible={exposeNativeMarker || undefined} style={styles.root} testID={testID}>
53
54
  {children}
54
55
  </View>
55
56
  </StoryRenderErrorBoundary>
@@ -0,0 +1,133 @@
1
+ import * as React from 'react';
2
+ import { StyleSheet, Text } from 'react-native';
3
+
4
+ import { fireEvent, render } from '@testing-library/react-native';
5
+ import { useRootSettings, useThemeState } from '@fluentui-react-native/design';
6
+ import type { RootSettings, ThemeState } from '@fluentui-react-native/design';
7
+ import { defaultFlexTokens } from '@fluentui-react-native/design/testing';
8
+ import type { View as StorybookView } from '@storybook/react-native';
9
+ import { theme as lightTheme } from '@storybook/react-native-theming';
10
+
11
+ import { createDesktopStorybookApp } from './createDesktopStorybookApp';
12
+ import { DesktopDriverBridge } from './DesktopDriverBridge';
13
+ import { StorybookUIComponent } from './StorybookUI';
14
+
15
+ jest.mock('./DesktopDriverBridge', () => ({ DesktopDriverBridge: jest.fn(() => null) }));
16
+ jest.mock('./StorybookUI', () => ({ StorybookUIComponent: jest.fn(() => null) }));
17
+
18
+ function createTestApp() {
19
+ const roots: RootSettings[] = [];
20
+ const bridgeRoots: RootSettings[] = [];
21
+ const themes: ThemeState[] = [];
22
+ const mounted = jest.fn();
23
+ const unmounted = jest.fn();
24
+
25
+ function Story() {
26
+ roots.push(useRootSettings());
27
+ themes.push(useThemeState());
28
+ React.useEffect(() => {
29
+ mounted();
30
+ return unmounted;
31
+ }, []);
32
+ return <Text testID="selected-story">Selected story</Text>;
33
+ }
34
+
35
+ jest.mocked(DesktopDriverBridge).mockImplementation(() => {
36
+ bridgeRoots.push(useRootSettings());
37
+ return null;
38
+ });
39
+ jest.mocked(StorybookUIComponent).mockImplementation(({ children }) => <>{children}</>);
40
+
41
+ const getStorybookUI = jest.fn<ReturnType<StorybookView['getStorybookUI']>, Parameters<StorybookView['getStorybookUI']>>((params) => {
42
+ const UI = params?.CustomUIComponent;
43
+ if (!UI) {
44
+ throw new Error('Expected the app factory to configure the themed UI.');
45
+ }
46
+ return () => (
47
+ <UI storyHash={{}} setStory={jest.fn()} storage={{ getItem: () => null, setItem: () => undefined }} theme={lightTheme}>
48
+ <Story />
49
+ </UI>
50
+ );
51
+ });
52
+ const App = createDesktopStorybookApp({ getStorybookUI }, { enableWebsockets: false, testIDPrefix: 'test' });
53
+ return { App, roots, bridgeRoots, themes, mounted, unmounted, getStorybookUI };
54
+ }
55
+
56
+ afterEach(() => {
57
+ jest.clearAllMocks();
58
+ jest.restoreAllMocks();
59
+ });
60
+
61
+ describe('Storybook app theme root', () => {
62
+ it('places the toolbar, UI, story, and runtime bridge in the same default Flex scene', async () => {
63
+ const { App, roots, bridgeRoots, themes } = createTestApp();
64
+ const scene = await render(<App />);
65
+
66
+ expect(scene.getByTestId('test-app-root')).toBeOnTheScreen();
67
+ expect(scene.getByTestId('test-theme-toolbar')).toBeOnTheScreen();
68
+ expect(scene.getByTestId('selected-story')).toBeOnTheScreen();
69
+ expect(scene.getByRole('button', { name: 'Default Flex' })).toBeOnTheScreen();
70
+ expect(themes[0].tokens).toEqual(defaultFlexTokens);
71
+ expect(bridgeRoots.every((root) => root === roots[0])).toBe(true);
72
+ expect(roots[0].inputModality).toBe('pointer');
73
+ });
74
+
75
+ it('observes app-root input without rerendering the story', async () => {
76
+ const { App, roots, bridgeRoots } = createTestApp();
77
+ const scene = await render(<App />);
78
+ const renderCount = roots.length;
79
+ const root = scene.getByTestId('test-app-root');
80
+
81
+ await fireEvent(root, 'keyDownCapture', { nativeEvent: { key: 'Tab' } });
82
+ expect(roots[0].inputModality).toBe('keyboard');
83
+ expect(bridgeRoots[0].inputModality).toBe('keyboard');
84
+ await fireEvent(root, 'pointerDownCapture', {});
85
+ expect(roots[0].inputModality).toBe('pointer');
86
+ await fireEvent(root, 'keyDownCapture', { nativeEvent: { key: 'ArrowRight' } });
87
+ await fireEvent(root, 'startShouldSetResponderCapture', {});
88
+ expect(roots[0].inputModality).toBe('pointer');
89
+ expect(roots).toHaveLength(renderCount);
90
+ });
91
+
92
+ it('switches chrome and story colors together without recreating Storybook or resetting the scene', async () => {
93
+ const warn = jest.spyOn(console, 'warn').mockImplementation();
94
+ const { App, roots, themes, mounted, unmounted, getStorybookUI } = createTestApp();
95
+ const scene = await render(<App />);
96
+ const root = roots[0];
97
+ const defaultBackground = themes[0].tokens.color.surfaceNeutralFar;
98
+ const backgrounds: Record<string, unknown> = {};
99
+ await fireEvent(scene.getByTestId('test-app-root'), 'keyDownCapture', { nativeEvent: { key: 'Tab' } });
100
+
101
+ for (const choice of ['dark', 'highContrast', 'light', 'none']) {
102
+ await fireEvent.press(scene.getByTestId(`test-theme-${choice}`));
103
+ const state = themes[themes.length - 1];
104
+ const calls = jest.mocked(StorybookUIComponent).mock.calls;
105
+ const theme = calls[calls.length - 1][0].theme;
106
+ backgrounds[choice] = theme.background.app;
107
+
108
+ expect(scene.getByTestId(`test-theme-${choice}`).props.accessibilityState.selected).toBe(true);
109
+ expect(StyleSheet.flatten(scene.getByTestId('test-theme-toolbar').props.style).backgroundColor).toBe(
110
+ state.tokens.color.backgroundNeutralSubtle,
111
+ );
112
+ expect(StyleSheet.flatten(scene.getByText('Theme').props.style).color).toBe(state.tokens.color.foregroundNeutralPrimary);
113
+ const foreground = theme.base === 'dark' ? state.tokens.color.fixedWhite : state.tokens.color.fixedBlack;
114
+ const background = theme.base === 'dark' ? state.tokens.color.fixedBlack : state.tokens.color.fixedWhite;
115
+ const literal = (value: unknown, fallback: unknown) => (typeof value === 'string' ? value : fallback);
116
+ expect(theme.background.app).toBe(literal(state.tokens.color.surfaceNeutralFar, background));
117
+ expect(theme.background.content).toBe(literal(state.tokens.color.surfaceNeutralNear, background));
118
+ expect(theme.background.preview).toBe(literal(state.tokens.color.surfaceNeutralFar, background));
119
+ expect(theme.color.defaultText).toBe(literal(state.tokens.color.foregroundNeutralPrimary, foreground));
120
+ expect(theme.input.color).toBe(literal(state.tokens.color.foregroundNeutralPrimary, foreground));
121
+ expect(theme.button.background).toBe(literal(state.tokens.color.backgroundNeutralLoud, background));
122
+ expect(root.inputModality).toBe('keyboard');
123
+ expect(roots.every((settings) => settings === root)).toBe(true);
124
+ }
125
+
126
+ expect(backgrounds.dark).not.toBe(backgrounds.light);
127
+ expect(backgrounds.none).toBe(defaultBackground);
128
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('Storybook chrome cannot resolve native system colors'));
129
+ expect(getStorybookUI).toHaveBeenCalledTimes(1);
130
+ expect(mounted).toHaveBeenCalledTimes(1);
131
+ expect(unmounted).not.toHaveBeenCalled();
132
+ });
133
+ });
@@ -2,7 +2,7 @@ import * as React from 'react';
2
2
  import { Pressable, StyleSheet, Text, View } from 'react-native';
3
3
 
4
4
  import { createDefaultTheme } from '@fluentui-react-native/default-theme';
5
- import { ThemeProvider } from '@fluentui-react-native/design/theming';
5
+ import { ThemedRoot, themedStyleSheetFactory, useThemeState } from '@fluentui-react-native/design';
6
6
  import type { ThemeReference } from '@fluentui-react-native/design/theming';
7
7
 
8
8
  import { useDesktopStorybookTestID } from './DesktopStorybookConfig';
@@ -16,99 +16,112 @@ const themeChoiceNames = ['none', 'light', 'dark', 'highContrast'] as const;
16
16
  type ThemeChoiceName = (typeof themeChoiceNames)[number];
17
17
 
18
18
  const themeChoices: Record<ThemeChoiceName, ThemeChoice> = {
19
- none: { label: 'No theme' },
19
+ none: { label: 'Default Flex' },
20
20
  light: { label: 'Light', theme: createDefaultTheme({ appearance: 'light' }) },
21
21
  dark: { label: 'Dark', theme: createDefaultTheme({ appearance: 'dark' }) },
22
22
  highContrast: { label: 'High contrast', theme: createDefaultTheme({ appearance: 'highContrast' }) },
23
23
  };
24
24
 
25
- const StorybookThemeContext = React.createContext<ThemeReference | undefined>(undefined);
26
-
27
25
  export function StorybookThemeHost({ children }: React.PropsWithChildren) {
28
26
  const [selectedName, setSelectedName] = React.useState<ThemeChoiceName>('none');
29
- const selectedTheme = themeChoices[selectedName].theme;
27
+ const testID = useDesktopStorybookTestID('app-root');
28
+
29
+ return (
30
+ <ThemedRoot style={layoutStyles.root} testID={testID} theme={themeChoices[selectedName].theme}>
31
+ <StorybookThemeContent selectedName={selectedName} onSelect={setSelectedName}>
32
+ {children}
33
+ </StorybookThemeContent>
34
+ </ThemedRoot>
35
+ );
36
+ }
37
+
38
+ function StorybookThemeContent({
39
+ children,
40
+ selectedName,
41
+ onSelect,
42
+ }: React.PropsWithChildren<{ selectedName: ThemeChoiceName; onSelect: (name: ThemeChoiceName) => void }>) {
43
+ const styles = getStyles(useThemeState());
30
44
  const toolbarTestID = useDesktopStorybookTestID('theme-toolbar');
31
45
  const optionTestID = useDesktopStorybookTestID('theme');
32
46
 
33
47
  return (
34
- <StorybookThemeContext.Provider value={selectedTheme}>
35
- <View style={styles.root}>
36
- <View accessibilityRole="toolbar" style={styles.header} testID={toolbarTestID}>
37
- <Text style={styles.label}>Theme</Text>
38
- {themeChoiceNames.map((name) => {
39
- const choice = themeChoices[name];
40
- const selected = name === selectedName;
41
- return (
42
- <Pressable
43
- accessibilityRole="button"
44
- accessibilityState={{ selected }}
45
- key={name}
46
- onPress={() => setSelectedName(name)}
47
- style={({ pressed }) => [styles.option, selected && styles.selectedOption, pressed && styles.pressedOption]}
48
- testID={`${optionTestID}-${name}`}
49
- >
50
- <Text style={[styles.optionText, selected && styles.selectedOptionText]}>{choice.label}</Text>
51
- </Pressable>
52
- );
53
- })}
54
- </View>
55
- <View style={styles.storybook}>{children}</View>
48
+ <View style={styles.root}>
49
+ <View accessibilityRole="toolbar" style={styles.header} testID={toolbarTestID}>
50
+ <Text style={styles.label}>Theme</Text>
51
+ {themeChoiceNames.map((name) => {
52
+ const choice = themeChoices[name];
53
+ const selected = name === selectedName;
54
+ return (
55
+ <Pressable
56
+ accessibilityRole="button"
57
+ accessibilityState={{ selected }}
58
+ key={name}
59
+ onPress={() => onSelect(name)}
60
+ style={({ pressed }) => [styles.option, selected && styles.selectedOption, pressed && styles.pressedOption]}
61
+ testID={`${optionTestID}-${name}`}
62
+ >
63
+ <Text style={[styles.optionText, selected && styles.selectedOptionText]}>{choice.label}</Text>
64
+ </Pressable>
65
+ );
66
+ })}
56
67
  </View>
57
- </StorybookThemeContext.Provider>
68
+ <View style={layoutStyles.root}>{children}</View>
69
+ </View>
58
70
  );
59
71
  }
60
72
 
61
- export function StorybookThemeProvider({ children }: React.PropsWithChildren) {
62
- const theme = React.useContext(StorybookThemeContext);
63
- return theme ? <ThemeProvider theme={theme}>{children}</ThemeProvider> : children;
64
- }
65
-
66
- const styles = StyleSheet.create({
73
+ const layoutStyles = StyleSheet.create({
67
74
  root: {
68
75
  flex: 1,
69
76
  },
70
- header: {
71
- alignItems: 'center',
72
- backgroundColor: '#f5f5f5',
73
- borderBottomColor: '#d1d1d1',
74
- borderBottomWidth: 1,
75
- flexDirection: 'row',
76
- flexWrap: 'wrap',
77
- gap: 8,
78
- minHeight: 44,
79
- paddingHorizontal: 12,
80
- paddingVertical: 6,
81
- },
82
- label: {
83
- color: '#242424',
84
- fontSize: 14,
85
- fontWeight: '600',
86
- marginEnd: 4,
87
- },
88
- option: {
89
- backgroundColor: '#ffffff',
90
- borderColor: '#b3b3b3',
91
- borderRadius: 4,
92
- borderWidth: 1,
93
- paddingHorizontal: 10,
94
- paddingVertical: 5,
95
- },
96
- selectedOption: {
97
- backgroundColor: '#0f6cbd',
98
- borderColor: '#0f6cbd',
99
- },
100
- pressedOption: {
101
- opacity: 0.75,
102
- },
103
- optionText: {
104
- color: '#242424',
105
- fontSize: 12,
106
- },
107
- selectedOptionText: {
108
- color: '#ffffff',
109
- fontWeight: '600',
110
- },
111
- storybook: {
112
- flex: 1,
113
- },
114
77
  });
78
+
79
+ const getStyles = themedStyleSheetFactory('StorybookTheme', ({ tokens }) =>
80
+ StyleSheet.create({
81
+ root: {
82
+ flex: 1,
83
+ backgroundColor: tokens.color.surfaceNeutralFar,
84
+ },
85
+ header: {
86
+ alignItems: 'center',
87
+ backgroundColor: tokens.color.backgroundNeutralSubtle,
88
+ borderBottomColor: tokens.color.strokeNeutralSubtle,
89
+ borderBottomWidth: 1,
90
+ flexDirection: 'row',
91
+ flexWrap: 'wrap',
92
+ gap: 8,
93
+ minHeight: 44,
94
+ paddingHorizontal: 12,
95
+ paddingVertical: 6,
96
+ },
97
+ label: {
98
+ color: tokens.color.foregroundNeutralPrimary,
99
+ fontSize: 14,
100
+ fontWeight: '600',
101
+ marginEnd: 4,
102
+ },
103
+ option: {
104
+ backgroundColor: tokens.color.backgroundNeutralLoud,
105
+ borderColor: tokens.color.strokeNeutralSoft,
106
+ borderRadius: 4,
107
+ borderWidth: 1,
108
+ paddingHorizontal: 10,
109
+ paddingVertical: 5,
110
+ },
111
+ selectedOption: {
112
+ backgroundColor: tokens.color.backgroundBrandHeavy,
113
+ borderColor: tokens.color.strokeBrandLoud,
114
+ },
115
+ pressedOption: {
116
+ opacity: 0.75,
117
+ },
118
+ optionText: {
119
+ color: tokens.color.foregroundNeutralPrimary,
120
+ fontSize: 12,
121
+ },
122
+ selectedOptionText: {
123
+ color: tokens.color.foregroundBrandOnloud,
124
+ fontWeight: '600',
125
+ },
126
+ }),
127
+ );
@@ -14,7 +14,7 @@ import { Win32ResizeHandle } from './Win32ResizeHandle';
14
14
  const emptyRefs = {};
15
15
  const emptyStatus = {};
16
16
 
17
- export const StorybookUIComponent: SBUI = ({ children, setStory, storage, story, storyHash, theme }) => {
17
+ export const StorybookUIComponent: SBUI = ({ children, setStory, storage, story, storyHash, storyBackgroundColor, theme }) => {
18
18
  const storiesAnchorRef = React.useRef<View>(null);
19
19
  const addonsAnchorRef = React.useRef<View>(null);
20
20
  const [sidebarVisible, setSidebarVisible] = React.useState(true);
@@ -67,7 +67,7 @@ export const StorybookUIComponent: SBUI = ({ children, setStory, storage, story,
67
67
  return (
68
68
  <ThemeProvider theme={theme}>
69
69
  <StorageProvider storage={storage}>
70
- <View style={styles.root} testID={chromeTestID}>
70
+ <View style={[styles.root, { backgroundColor: theme.background.app }]} testID={chromeTestID}>
71
71
  {sidebarVisible ? (
72
72
  <>
73
73
  <View
@@ -174,7 +174,7 @@ export const StorybookUIComponent: SBUI = ({ children, setStory, storage, story,
174
174
  </Text>
175
175
  </View>
176
176
  ) : null}
177
- <View style={styles.preview} testID={previewTestID}>
177
+ <View style={[styles.preview, { backgroundColor: storyBackgroundColor ?? theme.background.preview }]} testID={previewTestID}>
178
178
  {children}
179
179
  </View>
180
180
  {addonsVisible ? (
@@ -0,0 +1,9 @@
1
+ import type { SBUI } from '@storybook/react-native-ui-common';
2
+
3
+ import { StorybookUIComponent } from './StorybookUI';
4
+ import { useStorybookTheme } from './useStorybookTheme';
5
+
6
+ export const ThemedStorybookUI: SBUI = (props) => {
7
+ const theme = useStorybookTheme();
8
+ return <StorybookUIComponent {...props} theme={theme} />;
9
+ };