@fluentui-react-native/storybook-desktop-runtime 0.3.4 → 0.3.5
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.
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +21 -0
- package/lib/StorybookTheme.d.ts +14 -0
- package/lib/StorybookTheme.d.ts.map +1 -1
- package/lib/StorybookTheme.js +136 -33
- package/lib/StorybookTheme.js.map +1 -1
- package/lib/StorybookTheme.test.js +159 -42
- package/lib/StorybookTheme.test.js.map +1 -1
- package/lib/ThemedStorybookUI.d.ts.map +1 -1
- package/lib/ThemedStorybookUI.js +3 -2
- package/lib/ThemedStorybookUI.js.map +1 -1
- package/lib/useStorybookTheme.js +2 -2
- package/lib/useStorybookTheme.js.map +1 -1
- package/lib/useStorybookTheme.test.js +4 -4
- package/lib/useStorybookTheme.test.js.map +1 -1
- package/package.json +3 -3
- package/references/runtime-contract.md +27 -6
- package/src/StorybookTheme.test.tsx +181 -43
- package/src/StorybookTheme.tsx +245 -51
- package/src/ThemedStorybookUI.tsx +7 -2
- package/src/useStorybookTheme.test.tsx +4 -4
- package/src/useStorybookTheme.ts +2 -2
package/AGENTS.md
CHANGED
|
@@ -15,8 +15,9 @@ authenticated bridge, Metro, theme, and platform chrome.
|
|
|
15
15
|
test reporting belong to the integration and harness packages.
|
|
16
16
|
- Preserve the authenticated nonce, correlated request/run IDs, readiness,
|
|
17
17
|
error signaling, and native story-root verification.
|
|
18
|
-
- Keep one `ThemedRoot` around app content and chrome
|
|
19
|
-
|
|
18
|
+
- Keep one Flex `ThemedRoot` around app content and chrome; apply the selected
|
|
19
|
+
theme only to story content through the nested provider. Preserve explicit
|
|
20
|
+
story background overrides and safe fallback behavior for opaque native colors.
|
|
20
21
|
- Keep platform-specific behavior in platform files. Preserve Win32's
|
|
21
22
|
desktop-only chrome and keep macOS/Windows on upstream LiteUI.
|
|
22
23
|
- Metro configuration has side effects: loading it can generate the runtime
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @fluentui-react-native/storybook-desktop-runtime
|
|
2
2
|
|
|
3
|
+
## 0.3.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 06b458f: Keep the Storybook chrome and story canvas on the Flex theme. The Theme selector now applies Flex or Fluent only to
|
|
8
|
+
story content, while scheme, contrast, and tone still apply to the chrome.
|
|
9
|
+
- 06b458f: Read Storybook chrome colors through the design package's interactive color envelope.
|
|
10
|
+
- 06b458f: Replace the fixed Storybook theme buttons with Theme (Flex or Fluent), Scheme (light, dark, or system), Contrast, and Tone dropdowns. The toolbar now defaults to Flex light, standard contrast, and cool tone, independent of the host appearance.
|
|
11
|
+
- Updated dependencies [06b458f]
|
|
12
|
+
- Updated dependencies [06b458f]
|
|
13
|
+
- Updated dependencies [06b458f]
|
|
14
|
+
- Updated dependencies [ccb458e]
|
|
15
|
+
- Updated dependencies [06b458f]
|
|
16
|
+
- Updated dependencies [06b458f]
|
|
17
|
+
- Updated dependencies [06b458f]
|
|
18
|
+
- Updated dependencies [ccb458e]
|
|
19
|
+
- Updated dependencies [06b458f]
|
|
20
|
+
- @fluentui-react-native/default-theme@0.28.0
|
|
21
|
+
- @fluentui-react-native/design@0.6.0
|
|
22
|
+
- @fluentui-react-native/native-core@0.2.0
|
|
23
|
+
|
|
3
24
|
## 0.3.4
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/lib/StorybookTheme.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import type { ColorTone } from '@fluentui-react-native/design';
|
|
3
|
+
/** The theme configuration selected in the Storybook toolbar. */
|
|
4
|
+
export type StorybookThemeSelection = {
|
|
5
|
+
/** The story theme: the package's Flex defaults or the legacy Fluent default theme. The chrome always uses Flex. */
|
|
6
|
+
source: 'flex' | 'fluent';
|
|
7
|
+
colorScheme: 'light' | 'dark' | 'system';
|
|
8
|
+
contrast: 'standard' | 'highContrast';
|
|
9
|
+
/** Flex palette and interaction tone. Legacy Fluent themes have no tone. */
|
|
10
|
+
colorTone: ColorTone;
|
|
11
|
+
};
|
|
12
|
+
/** A deterministic default that does not depend on the host appearance. */
|
|
13
|
+
export declare const defaultStorybookThemeSelection: Readonly<StorybookThemeSelection>;
|
|
2
14
|
export declare function StorybookThemeHost({ children }: React.PropsWithChildren): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
/** Applies the selected story theme inside the Flex chrome without adding a view or a second input root. */
|
|
16
|
+
export declare function StorybookStoryTheme({ children }: React.PropsWithChildren): import("react/jsx-runtime").JSX.Element;
|
|
3
17
|
//# sourceMappingURL=StorybookTheme.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StorybookTheme.d.ts","sourceRoot":"","sources":["../src/StorybookTheme.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"StorybookTheme.d.ts","sourceRoot":"","sources":["../src/StorybookTheme.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EAAE,SAAS,EAAuC,MAAM,+BAA+B,CAAC;AAIpG,iEAAiE;AACjE,MAAM,MAAM,uBAAuB,GAAG;IACpC,oHAAoH;IACpH,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,WAAW,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACzC,QAAQ,EAAE,UAAU,GAAG,cAAc,CAAC;IACtC,4EAA4E;IAC5E,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAYF,2EAA2E;AAC3E,eAAO,MAAM,8BAA8B,EAAE,QAAQ,CAAC,uBAAuB,CAK3E,CAAC;AAqDH,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,iBAAiB,2CAkBvE;AAED,4GAA4G;AAC5G,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,iBAAiB,2CAUxE"}
|
package/lib/StorybookTheme.js
CHANGED
|
@@ -1,30 +1,90 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
4
4
|
import { createDefaultTheme } from '@fluentui-react-native/default-theme';
|
|
5
|
-
import { ThemedRoot, themedStyleSheetFactory, useThemeState } from '@fluentui-react-native/design';
|
|
5
|
+
import { FlexThemeReference, ThemedRoot, ThemeProvider, themedStyleSheetFactory, useThemeState } from '@fluentui-react-native/design';
|
|
6
6
|
import { useDesktopStorybookTestID } from './DesktopStorybookConfig';
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
/** A deterministic default that does not depend on the host appearance. */
|
|
8
|
+
export const defaultStorybookThemeSelection = Object.freeze({
|
|
9
|
+
source: 'flex',
|
|
10
|
+
colorScheme: 'light',
|
|
11
|
+
contrast: 'standard',
|
|
12
|
+
colorTone: 'cool',
|
|
13
|
+
});
|
|
14
|
+
function themeAxis(key, id, label, options) {
|
|
15
|
+
return { key, id, label, options };
|
|
16
|
+
}
|
|
17
|
+
const themeAxes = [
|
|
18
|
+
themeAxis('source', 'source', 'Theme', [
|
|
19
|
+
{ value: 'flex', label: 'Flex' },
|
|
20
|
+
{ value: 'fluent', label: 'Fluent' },
|
|
21
|
+
]),
|
|
22
|
+
themeAxis('colorScheme', 'scheme', 'Scheme', [
|
|
23
|
+
{ value: 'light', label: 'Light' },
|
|
24
|
+
{ value: 'dark', label: 'Dark' },
|
|
25
|
+
{ value: 'system', label: 'System' },
|
|
26
|
+
]),
|
|
27
|
+
themeAxis('contrast', 'contrast', 'Contrast', [
|
|
28
|
+
{ value: 'standard', label: 'Standard' },
|
|
29
|
+
{ value: 'highContrast', label: 'High' },
|
|
30
|
+
]),
|
|
31
|
+
themeAxis('colorTone', 'tone', 'Tone', [
|
|
32
|
+
{ value: 'cool', label: 'Cool' },
|
|
33
|
+
{ value: 'warm', label: 'Warm' },
|
|
34
|
+
]),
|
|
35
|
+
];
|
|
36
|
+
// Each source resolves every appearance itself, so selection changes only the appearance request.
|
|
37
|
+
const themeSources = {
|
|
38
|
+
flex: new FlexThemeReference(),
|
|
39
|
+
fluent: createDefaultTheme({ appearance: 'dynamic' }),
|
|
13
40
|
};
|
|
41
|
+
function appearanceFromSelection({ source, colorScheme, contrast, colorTone }) {
|
|
42
|
+
// Omitting tone for Fluent keeps the resolved appearance at its cool default.
|
|
43
|
+
return source === 'flex' ? { colorScheme, contrast, colorTone } : { colorScheme, contrast };
|
|
44
|
+
}
|
|
45
|
+
function isAxisDisabled(key, selection) {
|
|
46
|
+
return key === 'colorTone' && selection.source !== 'flex';
|
|
47
|
+
}
|
|
48
|
+
const StoryThemeContext = React.createContext(undefined);
|
|
14
49
|
export function StorybookThemeHost({ children }) {
|
|
15
|
-
const [
|
|
50
|
+
const [selection, setSelection] = React.useState(defaultStorybookThemeSelection);
|
|
51
|
+
const storyTheme = React.useMemo(() => ({ theme: themeSources[selection.source], appearance: appearanceFromSelection(selection) }), [selection]);
|
|
16
52
|
const testID = useDesktopStorybookTestID('app-root');
|
|
17
|
-
|
|
53
|
+
// The chrome always uses Flex at the selected appearance; only story content follows the selected source.
|
|
54
|
+
return (_jsx(ThemedRoot, { appearance: storyTheme.appearance, style: layoutStyles.root, testID: testID, theme: themeSources.flex, children: _jsx(StoryThemeContext.Provider, { value: storyTheme, children: _jsx(StorybookThemeContent, { selection: selection, onSelectionChange: setSelection, children: children }) }) }));
|
|
18
55
|
}
|
|
19
|
-
|
|
56
|
+
/** Applies the selected story theme inside the Flex chrome without adding a view or a second input root. */
|
|
57
|
+
export function StorybookStoryTheme({ children }) {
|
|
58
|
+
const storyTheme = React.useContext(StoryThemeContext);
|
|
59
|
+
if (!storyTheme) {
|
|
60
|
+
return _jsx(_Fragment, { children: children });
|
|
61
|
+
}
|
|
62
|
+
return (_jsx(ThemeProvider, { appearance: storyTheme.appearance, theme: storyTheme.theme, children: children }));
|
|
63
|
+
}
|
|
64
|
+
function StorybookThemeContent({ children, selection, onSelectionChange, }) {
|
|
20
65
|
const styles = getStyles(useThemeState());
|
|
21
66
|
const toolbarTestID = useDesktopStorybookTestID('theme-toolbar');
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
67
|
+
const axisTestID = useDesktopStorybookTestID('theme');
|
|
68
|
+
const [openAxis, setOpenAxis] = React.useState();
|
|
69
|
+
const select = (key, value) => {
|
|
70
|
+
onSelectionChange((current) => (current[key] === value ? current : { ...current, [key]: value }));
|
|
71
|
+
setOpenAxis(undefined);
|
|
72
|
+
};
|
|
73
|
+
return (_jsxs(View, { style: styles.root, children: [_jsx(View, { accessibilityRole: "toolbar", style: styles.header, testID: toolbarTestID, children: themeAxes.map((axis) => {
|
|
74
|
+
const disabled = isAxisDisabled(axis.key, selection);
|
|
75
|
+
return (_jsx(ThemeAxisDropdown, { axis: axis, disabled: disabled, open: !disabled && openAxis === axis.key, styles: styles, testID: `${axisTestID}-${axis.id}`, value: disabled ? defaultStorybookThemeSelection[axis.key] : selection[axis.key], onSelect: (value) => select(axis.key, value), onToggle: () => setOpenAxis((current) => (current === axis.key ? undefined : axis.key)) }, axis.key));
|
|
76
|
+
}) }), _jsx(View, { style: layoutStyles.root, children: children })] }));
|
|
77
|
+
}
|
|
78
|
+
function ThemeAxisDropdown({ axis, disabled, open, styles, testID, value, onSelect, onToggle }) {
|
|
79
|
+
const selectedLabel = axis.options.find((option) => option.value === value)?.label ?? value;
|
|
80
|
+
return (_jsxs(View, { style: styles.field, children: [_jsx(Text, { style: [styles.label, disabled && styles.disabledText], children: axis.label }), _jsxs(View, { children: [_jsxs(Pressable, { accessibilityLabel: axis.label, accessibilityRole: "combobox", accessibilityState: { disabled, expanded: open }, accessibilityValue: { text: selectedLabel }, disabled: disabled, onPress: onToggle, style: ({ pressed }) => [styles.option, styles.trigger, pressed && styles.pressedOption, disabled && styles.disabledOption], testID: testID, children: [_jsx(Text, { style: [styles.optionText, disabled && styles.disabledText], children: selectedLabel }), _jsx(Text, { style: [styles.chevron, disabled && styles.disabledText], children: open ? '\u25b4' : '\u25be' })] }), open ? (_jsx(View, { accessibilityRole: "list", style: styles.menu, testID: `${testID}-menu`, children: axis.options.map((option) => {
|
|
81
|
+
const selected = option.value === value;
|
|
82
|
+
return (_jsx(Pressable, { accessibilityRole: "button", accessibilityState: { selected }, onPress: () => onSelect(option.value), style: ({ pressed }) => [
|
|
83
|
+
styles.menuOption,
|
|
84
|
+
selected && styles.selectedOption,
|
|
85
|
+
pressed && (selected ? styles.pressedSelectedOption : styles.pressedMenuOption),
|
|
86
|
+
], testID: `${testID}-${option.value}`, children: _jsx(Text, { style: [styles.optionText, selected && styles.selectedOptionText], children: option.label }) }, option.value));
|
|
87
|
+
}) })) : null] })] }));
|
|
28
88
|
}
|
|
29
89
|
const layoutStyles = StyleSheet.create({
|
|
30
90
|
root: {
|
|
@@ -34,48 +94,91 @@ const layoutStyles = StyleSheet.create({
|
|
|
34
94
|
const getStyles = themedStyleSheetFactory('StorybookTheme', ({ tokens }) => StyleSheet.create({
|
|
35
95
|
root: {
|
|
36
96
|
flex: 1,
|
|
37
|
-
backgroundColor: tokens.color.surfaceNeutralFar,
|
|
97
|
+
backgroundColor: tokens.color.surfaceNeutralFar.rest,
|
|
38
98
|
},
|
|
39
99
|
header: {
|
|
40
|
-
alignItems: '
|
|
41
|
-
backgroundColor: tokens.color.backgroundNeutralSubtle,
|
|
42
|
-
borderBottomColor: tokens.color.strokeNeutralSubtle,
|
|
100
|
+
alignItems: 'flex-start',
|
|
101
|
+
backgroundColor: tokens.color.backgroundNeutralSubtle.rest,
|
|
102
|
+
borderBottomColor: tokens.color.strokeNeutralSubtle.rest,
|
|
43
103
|
borderBottomWidth: 1,
|
|
44
104
|
flexDirection: 'row',
|
|
45
105
|
flexWrap: 'wrap',
|
|
46
|
-
gap:
|
|
106
|
+
gap: 16,
|
|
47
107
|
minHeight: 44,
|
|
48
108
|
paddingHorizontal: 12,
|
|
49
|
-
paddingVertical:
|
|
109
|
+
paddingVertical: 8,
|
|
110
|
+
},
|
|
111
|
+
field: {
|
|
112
|
+
alignItems: 'flex-start',
|
|
113
|
+
flexDirection: 'row',
|
|
114
|
+
gap: 6,
|
|
50
115
|
},
|
|
51
116
|
label: {
|
|
52
|
-
color: tokens.color.foregroundNeutralPrimary,
|
|
117
|
+
color: tokens.color.foregroundNeutralPrimary.rest,
|
|
53
118
|
fontSize: 14,
|
|
54
119
|
fontWeight: '600',
|
|
55
|
-
|
|
120
|
+
paddingVertical: 5,
|
|
56
121
|
},
|
|
57
122
|
option: {
|
|
58
|
-
backgroundColor: tokens.color.
|
|
59
|
-
borderColor: tokens.color.
|
|
123
|
+
backgroundColor: tokens.color.surfaceNeutralNearer.rest,
|
|
124
|
+
borderColor: tokens.color.strokeNeutralLoud.rest,
|
|
60
125
|
borderRadius: 4,
|
|
61
126
|
borderWidth: 1,
|
|
62
127
|
paddingHorizontal: 10,
|
|
63
128
|
paddingVertical: 5,
|
|
64
129
|
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
130
|
+
trigger: {
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
flexDirection: 'row',
|
|
133
|
+
gap: 8,
|
|
134
|
+
justifyContent: 'space-between',
|
|
135
|
+
minWidth: 96,
|
|
68
136
|
},
|
|
69
137
|
pressedOption: {
|
|
70
|
-
|
|
138
|
+
backgroundColor: tokens.color.surfaceNeutralNearer.pressed,
|
|
139
|
+
borderColor: tokens.color.strokeNeutralLoud.pressed,
|
|
140
|
+
},
|
|
141
|
+
disabledOption: {
|
|
142
|
+
backgroundColor: tokens.color.backgroundNeutralDisabled.rest,
|
|
143
|
+
borderColor: tokens.color.strokeNeutralDisabled.rest,
|
|
144
|
+
},
|
|
145
|
+
menu: {
|
|
146
|
+
backgroundColor: tokens.color.surfaceNeutralNearer.rest,
|
|
147
|
+
borderColor: tokens.color.strokeNeutralSubtle.rest,
|
|
148
|
+
borderRadius: 4,
|
|
149
|
+
borderWidth: 1,
|
|
150
|
+
gap: 2,
|
|
151
|
+
marginTop: 4,
|
|
152
|
+
padding: 2,
|
|
153
|
+
},
|
|
154
|
+
menuOption: {
|
|
155
|
+
backgroundColor: tokens.color.backgroundNeutralTransparent.rest,
|
|
156
|
+
borderRadius: 3,
|
|
157
|
+
paddingHorizontal: 10,
|
|
158
|
+
paddingVertical: 5,
|
|
159
|
+
},
|
|
160
|
+
pressedMenuOption: {
|
|
161
|
+
backgroundColor: tokens.color.backgroundNeutralTransparent.pressed,
|
|
162
|
+
},
|
|
163
|
+
selectedOption: {
|
|
164
|
+
backgroundColor: tokens.color.backgroundNeutralSoft.rest,
|
|
165
|
+
},
|
|
166
|
+
pressedSelectedOption: {
|
|
167
|
+
backgroundColor: tokens.color.backgroundNeutralSoft.pressed,
|
|
71
168
|
},
|
|
72
169
|
optionText: {
|
|
73
|
-
color: tokens.color.foregroundNeutralPrimary,
|
|
170
|
+
color: tokens.color.foregroundNeutralPrimary.rest,
|
|
171
|
+
fontSize: 12,
|
|
172
|
+
},
|
|
173
|
+
chevron: {
|
|
174
|
+
color: tokens.color.foregroundNeutralSecondary.rest,
|
|
74
175
|
fontSize: 12,
|
|
75
176
|
},
|
|
76
177
|
selectedOptionText: {
|
|
77
|
-
color: tokens.color.foregroundBrandOnloud,
|
|
78
178
|
fontWeight: '600',
|
|
79
179
|
},
|
|
180
|
+
disabledText: {
|
|
181
|
+
color: tokens.color.foregroundNeutralDisabled.rest,
|
|
182
|
+
},
|
|
80
183
|
}));
|
|
81
184
|
//# sourceMappingURL=StorybookTheme.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StorybookTheme.js","sourceRoot":"","sources":["../src/StorybookTheme.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"StorybookTheme.js","sourceRoot":"","sources":["../src/StorybookTheme.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { StyleSheet, Text } from 'react-native';
|
|
3
|
+
import { Appearance, StyleSheet, Text } from 'react-native';
|
|
4
4
|
import { fireEvent, render } from '@testing-library/react-native';
|
|
5
|
-
import { useRootSettings, useThemeState } from '@fluentui-react-native/design';
|
|
5
|
+
import { FlexThemeReference, useRootSettings, useThemeAppearance, useThemeState } from '@fluentui-react-native/design';
|
|
6
|
+
import { ThemeContext } from '@fluentui-react-native/design/theming';
|
|
6
7
|
import { defaultFlexTokens } from '@fluentui-react-native/design/testing';
|
|
7
8
|
import { theme as lightTheme } from '@storybook/react-native-theming';
|
|
8
9
|
import { createDesktopStorybookApp } from './createDesktopStorybookApp';
|
|
@@ -14,11 +15,16 @@ function createTestApp(storyBackgroundColor) {
|
|
|
14
15
|
const roots = [];
|
|
15
16
|
const bridgeRoots = [];
|
|
16
17
|
const themes = [];
|
|
18
|
+
const chromeThemes = [];
|
|
19
|
+
const appearances = [];
|
|
20
|
+
const legacyThemes = [];
|
|
17
21
|
const mounted = jest.fn();
|
|
18
22
|
const unmounted = jest.fn();
|
|
19
23
|
function Story() {
|
|
20
24
|
roots.push(useRootSettings());
|
|
21
25
|
themes.push(useThemeState());
|
|
26
|
+
appearances.push(useThemeAppearance());
|
|
27
|
+
legacyThemes.push(React.useContext(ThemeContext));
|
|
22
28
|
React.useEffect(() => {
|
|
23
29
|
mounted();
|
|
24
30
|
return unmounted;
|
|
@@ -29,7 +35,10 @@ function createTestApp(storyBackgroundColor) {
|
|
|
29
35
|
bridgeRoots.push(useRootSettings());
|
|
30
36
|
return null;
|
|
31
37
|
});
|
|
32
|
-
jest.mocked(StorybookUIComponent).mockImplementation(({ children }) =>
|
|
38
|
+
jest.mocked(StorybookUIComponent).mockImplementation(({ children }) => {
|
|
39
|
+
chromeThemes.push(useThemeState());
|
|
40
|
+
return _jsx(_Fragment, { children: children });
|
|
41
|
+
});
|
|
33
42
|
const getStorybookUI = jest.fn((params) => {
|
|
34
43
|
const UI = params?.CustomUIComponent;
|
|
35
44
|
if (!UI) {
|
|
@@ -38,21 +47,55 @@ function createTestApp(storyBackgroundColor) {
|
|
|
38
47
|
return () => (_jsx(UI, { storyBackgroundColor: storyBackgroundColor, storyHash: {}, setStory: jest.fn(), storage: { getItem: () => null, setItem: () => undefined }, theme: lightTheme, children: _jsx(Story, {}) }));
|
|
39
48
|
});
|
|
40
49
|
const App = createDesktopStorybookApp({ getStorybookUI }, { enableWebsockets: false, testIDPrefix: 'test' });
|
|
41
|
-
return { App, roots, bridgeRoots, themes, mounted, unmounted, getStorybookUI };
|
|
50
|
+
return { App, roots, bridgeRoots, themes, chromeThemes, appearances, legacyThemes, mounted, unmounted, getStorybookUI };
|
|
51
|
+
}
|
|
52
|
+
async function selectTheme(scene, axis, value) {
|
|
53
|
+
await fireEvent.press(scene.getByTestId(`test-theme-${axis}`));
|
|
54
|
+
await fireEvent.press(scene.getByTestId(`test-theme-${axis}-${value}`));
|
|
42
55
|
}
|
|
56
|
+
function latestStorybookTheme() {
|
|
57
|
+
const calls = jest.mocked(StorybookUIComponent).mock.calls;
|
|
58
|
+
return calls[calls.length - 1][0].theme;
|
|
59
|
+
}
|
|
60
|
+
const selectionSteps = [
|
|
61
|
+
['scheme', 'dark'],
|
|
62
|
+
['tone', 'warm'],
|
|
63
|
+
['contrast', 'highContrast'],
|
|
64
|
+
['source', 'fluent'],
|
|
65
|
+
['contrast', 'standard'],
|
|
66
|
+
['scheme', 'light'],
|
|
67
|
+
['source', 'flex'],
|
|
68
|
+
['tone', 'cool'],
|
|
69
|
+
];
|
|
43
70
|
afterEach(() => {
|
|
44
71
|
jest.clearAllMocks();
|
|
45
72
|
jest.restoreAllMocks();
|
|
46
73
|
});
|
|
47
74
|
describe('Storybook app theme root', () => {
|
|
48
|
-
it('places the toolbar, UI, story, and runtime bridge in
|
|
49
|
-
const { App, roots, bridgeRoots, themes } = createTestApp();
|
|
75
|
+
it('places the toolbar, UI, story, and runtime bridge in one deterministic default Flex scene', async () => {
|
|
76
|
+
const { App, roots, bridgeRoots, themes, chromeThemes, appearances, legacyThemes } = createTestApp();
|
|
50
77
|
const scene = await render(_jsx(App, {}));
|
|
51
78
|
expect(scene.getByTestId('test-app-root')).toBeOnTheScreen();
|
|
52
79
|
expect(scene.getByTestId('test-theme-toolbar')).toBeOnTheScreen();
|
|
53
80
|
expect(scene.getByTestId('selected-story')).toBeOnTheScreen();
|
|
54
|
-
|
|
81
|
+
for (const [axis, label] of [
|
|
82
|
+
['source', 'Flex'],
|
|
83
|
+
['scheme', 'Light'],
|
|
84
|
+
['contrast', 'Standard'],
|
|
85
|
+
['tone', 'Cool'],
|
|
86
|
+
]) {
|
|
87
|
+
const trigger = scene.getByTestId(`test-theme-${axis}`);
|
|
88
|
+
expect(trigger.props.accessibilityValue).toEqual({ text: label });
|
|
89
|
+
expect(trigger.props.accessibilityState).toEqual({ disabled: false, expanded: false });
|
|
90
|
+
}
|
|
91
|
+
expect(appearances[0]).toEqual({
|
|
92
|
+
requested: { colorScheme: 'light', contrast: 'standard', interfaceLevel: 'system', colorTone: 'cool' },
|
|
93
|
+
resolved: { colorScheme: 'light', contrast: 'standard', interfaceLevel: 'base', colorTone: 'cool' },
|
|
94
|
+
});
|
|
95
|
+
expect(themes[0].appearance).toBe(appearances[0].resolved);
|
|
55
96
|
expect(themes[0].tokens).toEqual(defaultFlexTokens);
|
|
97
|
+
expect(chromeThemes[0]).toBe(themes[0]);
|
|
98
|
+
expect(legacyThemes[0]).toBeUndefined();
|
|
56
99
|
expect(bridgeRoots.every((root) => root === roots[0])).toBe(true);
|
|
57
100
|
expect(roots[0].inputModality).toBe('pointer');
|
|
58
101
|
});
|
|
@@ -71,58 +114,132 @@ describe('Storybook app theme root', () => {
|
|
|
71
114
|
expect(roots[0].inputModality).toBe('pointer');
|
|
72
115
|
expect(roots).toHaveLength(renderCount);
|
|
73
116
|
});
|
|
74
|
-
it
|
|
117
|
+
it('opens one axis menu at a time and closes it after a selection', async () => {
|
|
118
|
+
const { App } = createTestApp();
|
|
119
|
+
const scene = await render(_jsx(App, {}));
|
|
120
|
+
await fireEvent.press(scene.getByTestId('test-theme-scheme'));
|
|
121
|
+
expect(scene.getByTestId('test-theme-scheme').props.accessibilityState.expanded).toBe(true);
|
|
122
|
+
expect(scene.getByTestId('test-theme-scheme-light').props.accessibilityState.selected).toBe(true);
|
|
123
|
+
expect(scene.getByTestId('test-theme-scheme-system')).toBeOnTheScreen();
|
|
124
|
+
await fireEvent.press(scene.getByTestId('test-theme-source'));
|
|
125
|
+
expect(scene.queryByTestId('test-theme-scheme-menu')).toBeNull();
|
|
126
|
+
expect(scene.getByTestId('test-theme-source-menu')).toBeOnTheScreen();
|
|
127
|
+
await fireEvent.press(scene.getByTestId('test-theme-source'));
|
|
128
|
+
expect(scene.queryByTestId('test-theme-source-menu')).toBeNull();
|
|
129
|
+
await selectTheme(scene, 'scheme', 'dark');
|
|
130
|
+
expect(scene.queryByTestId('test-theme-scheme-menu')).toBeNull();
|
|
131
|
+
expect(scene.getByTestId('test-theme-scheme').props.accessibilityValue).toEqual({ text: 'Dark' });
|
|
132
|
+
});
|
|
133
|
+
it('applies scheme, contrast, and tone to the Flex defaults', async () => {
|
|
75
134
|
const { App, themes } = createTestApp();
|
|
76
135
|
const scene = await render(_jsx(App, {}));
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
expect(
|
|
136
|
+
const coolLight = themes[themes.length - 1].tokens;
|
|
137
|
+
await selectTheme(scene, 'tone', 'warm');
|
|
138
|
+
const warmLight = themes[themes.length - 1];
|
|
139
|
+
expect(warmLight.appearance).toEqual({ colorScheme: 'light', contrast: 'standard', interfaceLevel: 'base', colorTone: 'warm' });
|
|
140
|
+
expect(warmLight.tokens.color.surfaceNeutralFar.rest).not.toBe(coolLight.color.surfaceNeutralFar.rest);
|
|
141
|
+
await selectTheme(scene, 'scheme', 'dark');
|
|
142
|
+
const warmDark = themes[themes.length - 1];
|
|
143
|
+
expect(warmDark.appearance.colorScheme).toBe('dark');
|
|
144
|
+
expect(warmDark.tokens.color.surfaceNeutralFar.rest).not.toBe(warmLight.tokens.color.surfaceNeutralFar.rest);
|
|
145
|
+
expect(warmDark.tokens.color.backgroundBrandLoud.hover).not.toBe(warmDark.tokens.color.backgroundBrandLoud.rest);
|
|
146
|
+
await selectTheme(scene, 'contrast', 'highContrast');
|
|
147
|
+
const highContrast = themes[themes.length - 1];
|
|
148
|
+
expect(highContrast.appearance.contrast).toBe('highContrast');
|
|
149
|
+
expect(highContrast.tokens.color.backgroundBrandLoud.hover).toBe(highContrast.tokens.color.backgroundBrandLoud.rest);
|
|
150
|
+
expect(highContrast.tokens.color.backgroundBrandLoud.pressed).toBe(highContrast.tokens.color.backgroundBrandLoud.rest);
|
|
151
|
+
});
|
|
152
|
+
it('follows the platform scheme only when System is selected', async () => {
|
|
153
|
+
jest.spyOn(Appearance, 'getColorScheme').mockReturnValue('dark');
|
|
154
|
+
const { App, themes, appearances } = createTestApp();
|
|
155
|
+
const scene = await render(_jsx(App, {}));
|
|
156
|
+
expect(themes[themes.length - 1].appearance.colorScheme).toBe('light');
|
|
157
|
+
await selectTheme(scene, 'scheme', 'system');
|
|
158
|
+
expect(appearances[appearances.length - 1].requested.colorScheme).toBe('system');
|
|
159
|
+
expect(themes[themes.length - 1].appearance.colorScheme).toBe('dark');
|
|
160
|
+
});
|
|
161
|
+
it('applies the legacy Fluent default theme to stories without tone and restores the Flex tone', async () => {
|
|
162
|
+
const { App, themes, chromeThemes, legacyThemes } = createTestApp();
|
|
163
|
+
const scene = await render(_jsx(App, {}));
|
|
164
|
+
await selectTheme(scene, 'tone', 'warm');
|
|
165
|
+
await selectTheme(scene, 'source', 'fluent');
|
|
166
|
+
const tone = scene.getByTestId('test-theme-tone');
|
|
167
|
+
expect(tone.props.accessibilityState).toEqual({ disabled: true, expanded: false });
|
|
168
|
+
expect(tone.props.accessibilityValue).toEqual({ text: 'Cool' });
|
|
169
|
+
await fireEvent.press(tone);
|
|
170
|
+
expect(scene.queryByTestId('test-theme-tone-menu')).toBeNull();
|
|
171
|
+
expect(themes[themes.length - 1].appearance.colorTone).toBe('cool');
|
|
172
|
+
expect(themes[themes.length - 1].tokens).not.toEqual(defaultFlexTokens);
|
|
173
|
+
expect(chromeThemes[chromeThemes.length - 1].tokens).toEqual(defaultFlexTokens);
|
|
174
|
+
expect(legacyThemes[legacyThemes.length - 1]?.host.appearance).toBe('light');
|
|
175
|
+
await selectTheme(scene, 'scheme', 'dark');
|
|
176
|
+
expect(legacyThemes[legacyThemes.length - 1]?.host.appearance).toBe('dark');
|
|
177
|
+
await selectTheme(scene, 'contrast', 'highContrast');
|
|
178
|
+
expect(legacyThemes[legacyThemes.length - 1]?.host.appearance).toBe('highContrast');
|
|
179
|
+
await selectTheme(scene, 'source', 'flex');
|
|
180
|
+
expect(legacyThemes[legacyThemes.length - 1]).toBeUndefined();
|
|
181
|
+
expect(scene.getByTestId('test-theme-tone').props.accessibilityValue).toEqual({ text: 'Warm' });
|
|
182
|
+
expect(themes[themes.length - 1].appearance.colorTone).toBe('warm');
|
|
183
|
+
});
|
|
184
|
+
it.each([
|
|
185
|
+
['flex', 'light'],
|
|
186
|
+
['flex', 'dark'],
|
|
187
|
+
['fluent', 'light'],
|
|
188
|
+
['fluent', 'dark'],
|
|
189
|
+
])('keeps the Flex canvas distinct from the %s %s Secondary Button fill', async (source, scheme) => {
|
|
190
|
+
const { App, themes, chromeThemes } = createTestApp();
|
|
191
|
+
const scene = await render(_jsx(App, {}));
|
|
192
|
+
await selectTheme(scene, 'source', source);
|
|
193
|
+
await selectTheme(scene, 'scheme', scheme);
|
|
194
|
+
const story = themes[themes.length - 1];
|
|
195
|
+
const chrome = chromeThemes[chromeThemes.length - 1];
|
|
196
|
+
const theme = latestStorybookTheme();
|
|
197
|
+
expect(theme.background.content).toBe(chrome.tokens.color.surfaceNeutralFar.rest);
|
|
82
198
|
expect(theme.background.content).toBe(theme.background.preview);
|
|
83
|
-
expect(theme.background.content).not.toBe(
|
|
199
|
+
expect(theme.background.content).not.toBe(story.tokens.color.backgroundNeutralSubtle.rest);
|
|
84
200
|
});
|
|
85
201
|
it.each(['#ffeedd', 'transparent'])('preserves the explicit %s story background across theme changes', async (storyBackgroundColor) => {
|
|
86
202
|
const { App } = createTestApp(storyBackgroundColor);
|
|
87
203
|
const scene = await render(_jsx(App, {}));
|
|
88
|
-
for (const
|
|
89
|
-
await
|
|
204
|
+
for (const [axis, value] of selectionSteps) {
|
|
205
|
+
await selectTheme(scene, axis, value);
|
|
90
206
|
const calls = jest.mocked(StorybookUIComponent).mock.calls;
|
|
91
207
|
expect(calls[calls.length - 1][0].storyBackgroundColor).toBe(storyBackgroundColor);
|
|
92
208
|
}
|
|
93
209
|
});
|
|
94
|
-
it('switches chrome and story colors together without recreating Storybook or resetting the scene', async () => {
|
|
95
|
-
const
|
|
96
|
-
const { App, roots, themes, mounted, unmounted, getStorybookUI } = createTestApp();
|
|
210
|
+
it('switches Flex chrome and selected story colors together without recreating Storybook or resetting the scene', async () => {
|
|
211
|
+
const flex = new FlexThemeReference();
|
|
212
|
+
const { App, roots, themes, chromeThemes, mounted, unmounted, getStorybookUI } = createTestApp();
|
|
97
213
|
const scene = await render(_jsx(App, {}));
|
|
98
214
|
const root = roots[0];
|
|
99
|
-
const defaultBackground = themes[0].tokens.color.surfaceNeutralFar;
|
|
100
|
-
const backgrounds =
|
|
215
|
+
const defaultBackground = themes[0].tokens.color.surfaceNeutralFar.rest;
|
|
216
|
+
const backgrounds = new Set([defaultBackground]);
|
|
217
|
+
let source = 'flex';
|
|
101
218
|
await fireEvent(scene.getByTestId('test-app-root'), 'keyDownCapture', { nativeEvent: { key: 'Tab' } });
|
|
102
|
-
for (const
|
|
103
|
-
await
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
expect(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
expect(
|
|
115
|
-
expect(theme.background.
|
|
116
|
-
expect(theme.background.
|
|
117
|
-
expect(theme.
|
|
118
|
-
expect(theme.
|
|
119
|
-
expect(theme.
|
|
219
|
+
for (const [axis, value] of selectionSteps) {
|
|
220
|
+
await selectTheme(scene, axis, value);
|
|
221
|
+
source = axis === 'source' ? value : source;
|
|
222
|
+
const story = themes[themes.length - 1];
|
|
223
|
+
const chrome = chromeThemes[chromeThemes.length - 1];
|
|
224
|
+
const { color } = chrome.tokens;
|
|
225
|
+
const theme = latestStorybookTheme();
|
|
226
|
+
backgrounds.add(theme.background.app);
|
|
227
|
+
expect(chrome.appearance).toEqual(story.appearance);
|
|
228
|
+
expect(chrome.tokens).toEqual(flex.resolveFlexTokens(chrome.appearance));
|
|
229
|
+
expect(story === chrome).toBe(source === 'flex');
|
|
230
|
+
expect(StyleSheet.flatten(scene.getByTestId('test-theme-toolbar').props.style).backgroundColor).toBe(color.backgroundNeutralSubtle.rest);
|
|
231
|
+
expect(StyleSheet.flatten(scene.getByText('Theme').props.style).color).toBe(color.foregroundNeutralPrimary.rest);
|
|
232
|
+
expect(theme.background.app).toBe(color.surfaceNeutralFar.rest);
|
|
233
|
+
expect(theme.background.content).toBe(color.surfaceNeutralFar.rest);
|
|
234
|
+
expect(theme.background.preview).toBe(color.surfaceNeutralFar.rest);
|
|
235
|
+
expect(theme.color.defaultText).toBe(color.foregroundNeutralPrimary.rest);
|
|
236
|
+
expect(theme.input.color).toBe(color.foregroundNeutralPrimary.rest);
|
|
237
|
+
expect(theme.button.background).toBe(color.backgroundNeutralLoud.rest);
|
|
120
238
|
expect(root.inputModality).toBe('keyboard');
|
|
121
239
|
expect(roots.every((settings) => settings === root)).toBe(true);
|
|
122
240
|
}
|
|
123
|
-
expect(
|
|
124
|
-
expect(backgrounds.
|
|
125
|
-
expect(warn).toHaveBeenCalledWith(expect.stringContaining('Storybook chrome cannot resolve native system colors'));
|
|
241
|
+
expect(themes[themes.length - 1].tokens.color.surfaceNeutralFar.rest).toBe(defaultBackground);
|
|
242
|
+
expect(backgrounds.size).toBeGreaterThan(3);
|
|
126
243
|
expect(getStorybookUI).toHaveBeenCalledTimes(1);
|
|
127
244
|
expect(mounted).toHaveBeenCalledTimes(1);
|
|
128
245
|
expect(unmounted).not.toHaveBeenCalled();
|