@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/src/StorybookTheme.tsx
CHANGED
|
@@ -2,66 +2,158 @@ 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 { ThemedRoot, themedStyleSheetFactory, useThemeState } from '@fluentui-react-native/design';
|
|
6
|
-
import type {
|
|
5
|
+
import { FlexThemeReference, ThemedRoot, ThemeProvider, themedStyleSheetFactory, useThemeState } from '@fluentui-react-native/design';
|
|
6
|
+
import type { ColorTone, ThemeAppearanceOptions, ThemeSource } from '@fluentui-react-native/design';
|
|
7
7
|
|
|
8
8
|
import { useDesktopStorybookTestID } from './DesktopStorybookConfig';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
theme
|
|
10
|
+
/** The theme configuration selected in the Storybook toolbar. */
|
|
11
|
+
export type StorybookThemeSelection = {
|
|
12
|
+
/** The story theme: the package's Flex defaults or the legacy Fluent default theme. The chrome always uses Flex. */
|
|
13
|
+
source: 'flex' | 'fluent';
|
|
14
|
+
colorScheme: 'light' | 'dark' | 'system';
|
|
15
|
+
contrast: 'standard' | 'highContrast';
|
|
16
|
+
/** Flex palette and interaction tone. Legacy Fluent themes have no tone. */
|
|
17
|
+
colorTone: ColorTone;
|
|
13
18
|
};
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
type ThemeChoiceName = (typeof themeChoiceNames)[number];
|
|
20
|
+
type ThemeAxisKey = keyof StorybookThemeSelection;
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
type ThemeAxis = {
|
|
23
|
+
readonly key: ThemeAxisKey;
|
|
24
|
+
/** Stable test-ID segment for the axis and its options. */
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly label: string;
|
|
27
|
+
readonly options: readonly { readonly value: string; readonly label: string }[];
|
|
23
28
|
};
|
|
24
29
|
|
|
30
|
+
/** A deterministic default that does not depend on the host appearance. */
|
|
31
|
+
export const defaultStorybookThemeSelection: Readonly<StorybookThemeSelection> = Object.freeze({
|
|
32
|
+
source: 'flex',
|
|
33
|
+
colorScheme: 'light',
|
|
34
|
+
contrast: 'standard',
|
|
35
|
+
colorTone: 'cool',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function themeAxis<Key extends ThemeAxisKey>(
|
|
39
|
+
key: Key,
|
|
40
|
+
id: string,
|
|
41
|
+
label: string,
|
|
42
|
+
options: readonly { value: StorybookThemeSelection[Key]; label: string }[],
|
|
43
|
+
): ThemeAxis {
|
|
44
|
+
return { key, id, label, options };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const themeAxes: readonly ThemeAxis[] = [
|
|
48
|
+
themeAxis('source', 'source', 'Theme', [
|
|
49
|
+
{ value: 'flex', label: 'Flex' },
|
|
50
|
+
{ value: 'fluent', label: 'Fluent' },
|
|
51
|
+
]),
|
|
52
|
+
themeAxis('colorScheme', 'scheme', 'Scheme', [
|
|
53
|
+
{ value: 'light', label: 'Light' },
|
|
54
|
+
{ value: 'dark', label: 'Dark' },
|
|
55
|
+
{ value: 'system', label: 'System' },
|
|
56
|
+
]),
|
|
57
|
+
themeAxis('contrast', 'contrast', 'Contrast', [
|
|
58
|
+
{ value: 'standard', label: 'Standard' },
|
|
59
|
+
{ value: 'highContrast', label: 'High' },
|
|
60
|
+
]),
|
|
61
|
+
themeAxis('colorTone', 'tone', 'Tone', [
|
|
62
|
+
{ value: 'cool', label: 'Cool' },
|
|
63
|
+
{ value: 'warm', label: 'Warm' },
|
|
64
|
+
]),
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
// Each source resolves every appearance itself, so selection changes only the appearance request.
|
|
68
|
+
const themeSources: Record<StorybookThemeSelection['source'], ThemeSource> = {
|
|
69
|
+
flex: new FlexThemeReference(),
|
|
70
|
+
fluent: createDefaultTheme({ appearance: 'dynamic' }),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function appearanceFromSelection({ source, colorScheme, contrast, colorTone }: StorybookThemeSelection): ThemeAppearanceOptions {
|
|
74
|
+
// Omitting tone for Fluent keeps the resolved appearance at its cool default.
|
|
75
|
+
return source === 'flex' ? { colorScheme, contrast, colorTone } : { colorScheme, contrast };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isAxisDisabled(key: ThemeAxisKey, selection: StorybookThemeSelection): boolean {
|
|
79
|
+
return key === 'colorTone' && selection.source !== 'flex';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type StoryTheme = {
|
|
83
|
+
readonly theme: ThemeSource;
|
|
84
|
+
readonly appearance: ThemeAppearanceOptions;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const StoryThemeContext = React.createContext<StoryTheme | undefined>(undefined);
|
|
88
|
+
|
|
25
89
|
export function StorybookThemeHost({ children }: React.PropsWithChildren) {
|
|
26
|
-
const [
|
|
90
|
+
const [selection, setSelection] = React.useState<StorybookThemeSelection>(defaultStorybookThemeSelection);
|
|
91
|
+
const storyTheme = React.useMemo<StoryTheme>(
|
|
92
|
+
() => ({ theme: themeSources[selection.source], appearance: appearanceFromSelection(selection) }),
|
|
93
|
+
[selection],
|
|
94
|
+
);
|
|
27
95
|
const testID = useDesktopStorybookTestID('app-root');
|
|
28
96
|
|
|
97
|
+
// The chrome always uses Flex at the selected appearance; only story content follows the selected source.
|
|
29
98
|
return (
|
|
30
|
-
<ThemedRoot style={layoutStyles.root} testID={testID} theme={
|
|
31
|
-
<
|
|
32
|
-
{
|
|
33
|
-
|
|
99
|
+
<ThemedRoot appearance={storyTheme.appearance} style={layoutStyles.root} testID={testID} theme={themeSources.flex}>
|
|
100
|
+
<StoryThemeContext.Provider value={storyTheme}>
|
|
101
|
+
<StorybookThemeContent selection={selection} onSelectionChange={setSelection}>
|
|
102
|
+
{children}
|
|
103
|
+
</StorybookThemeContent>
|
|
104
|
+
</StoryThemeContext.Provider>
|
|
34
105
|
</ThemedRoot>
|
|
35
106
|
);
|
|
36
107
|
}
|
|
37
108
|
|
|
109
|
+
/** Applies the selected story theme inside the Flex chrome without adding a view or a second input root. */
|
|
110
|
+
export function StorybookStoryTheme({ children }: React.PropsWithChildren) {
|
|
111
|
+
const storyTheme = React.useContext(StoryThemeContext);
|
|
112
|
+
if (!storyTheme) {
|
|
113
|
+
return <>{children}</>;
|
|
114
|
+
}
|
|
115
|
+
return (
|
|
116
|
+
<ThemeProvider appearance={storyTheme.appearance} theme={storyTheme.theme}>
|
|
117
|
+
{children}
|
|
118
|
+
</ThemeProvider>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
38
122
|
function StorybookThemeContent({
|
|
39
123
|
children,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}: React.PropsWithChildren<{
|
|
124
|
+
selection,
|
|
125
|
+
onSelectionChange,
|
|
126
|
+
}: React.PropsWithChildren<{
|
|
127
|
+
selection: StorybookThemeSelection;
|
|
128
|
+
onSelectionChange: React.Dispatch<React.SetStateAction<StorybookThemeSelection>>;
|
|
129
|
+
}>) {
|
|
43
130
|
const styles = getStyles(useThemeState());
|
|
44
131
|
const toolbarTestID = useDesktopStorybookTestID('theme-toolbar');
|
|
45
|
-
const
|
|
132
|
+
const axisTestID = useDesktopStorybookTestID('theme');
|
|
133
|
+
const [openAxis, setOpenAxis] = React.useState<ThemeAxisKey>();
|
|
134
|
+
|
|
135
|
+
const select = (key: ThemeAxisKey, value: string) => {
|
|
136
|
+
onSelectionChange((current) => (current[key] === value ? current : ({ ...current, [key]: value } as StorybookThemeSelection)));
|
|
137
|
+
setOpenAxis(undefined);
|
|
138
|
+
};
|
|
46
139
|
|
|
47
140
|
return (
|
|
48
141
|
<View style={styles.root}>
|
|
49
142
|
<View accessibilityRole="toolbar" style={styles.header} testID={toolbarTestID}>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const choice = themeChoices[name];
|
|
53
|
-
const selected = name === selectedName;
|
|
143
|
+
{themeAxes.map((axis) => {
|
|
144
|
+
const disabled = isAxisDisabled(axis.key, selection);
|
|
54
145
|
return (
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
key={
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
testID={`${
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
146
|
+
<ThemeAxisDropdown
|
|
147
|
+
axis={axis}
|
|
148
|
+
disabled={disabled}
|
|
149
|
+
key={axis.key}
|
|
150
|
+
open={!disabled && openAxis === axis.key}
|
|
151
|
+
styles={styles}
|
|
152
|
+
testID={`${axisTestID}-${axis.id}`}
|
|
153
|
+
value={disabled ? defaultStorybookThemeSelection[axis.key] : selection[axis.key]}
|
|
154
|
+
onSelect={(value) => select(axis.key, value)}
|
|
155
|
+
onToggle={() => setOpenAxis((current) => (current === axis.key ? undefined : axis.key))}
|
|
156
|
+
/>
|
|
65
157
|
);
|
|
66
158
|
})}
|
|
67
159
|
</View>
|
|
@@ -70,6 +162,65 @@ function StorybookThemeContent({
|
|
|
70
162
|
);
|
|
71
163
|
}
|
|
72
164
|
|
|
165
|
+
type ThemeAxisDropdownProps = {
|
|
166
|
+
axis: ThemeAxis;
|
|
167
|
+
disabled: boolean;
|
|
168
|
+
open: boolean;
|
|
169
|
+
styles: ReturnType<typeof getStyles>;
|
|
170
|
+
testID: string;
|
|
171
|
+
value: string;
|
|
172
|
+
onSelect(value: string): void;
|
|
173
|
+
onToggle(): void;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
function ThemeAxisDropdown({ axis, disabled, open, styles, testID, value, onSelect, onToggle }: ThemeAxisDropdownProps) {
|
|
177
|
+
const selectedLabel = axis.options.find((option) => option.value === value)?.label ?? value;
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<View style={styles.field}>
|
|
181
|
+
<Text style={[styles.label, disabled && styles.disabledText]}>{axis.label}</Text>
|
|
182
|
+
<View>
|
|
183
|
+
<Pressable
|
|
184
|
+
accessibilityLabel={axis.label}
|
|
185
|
+
accessibilityRole="combobox"
|
|
186
|
+
accessibilityState={{ disabled, expanded: open }}
|
|
187
|
+
accessibilityValue={{ text: selectedLabel }}
|
|
188
|
+
disabled={disabled}
|
|
189
|
+
onPress={onToggle}
|
|
190
|
+
style={({ pressed }) => [styles.option, styles.trigger, pressed && styles.pressedOption, disabled && styles.disabledOption]}
|
|
191
|
+
testID={testID}
|
|
192
|
+
>
|
|
193
|
+
<Text style={[styles.optionText, disabled && styles.disabledText]}>{selectedLabel}</Text>
|
|
194
|
+
<Text style={[styles.chevron, disabled && styles.disabledText]}>{open ? '\u25b4' : '\u25be'}</Text>
|
|
195
|
+
</Pressable>
|
|
196
|
+
{open ? (
|
|
197
|
+
<View accessibilityRole="list" style={styles.menu} testID={`${testID}-menu`}>
|
|
198
|
+
{axis.options.map((option) => {
|
|
199
|
+
const selected = option.value === value;
|
|
200
|
+
return (
|
|
201
|
+
<Pressable
|
|
202
|
+
accessibilityRole="button"
|
|
203
|
+
accessibilityState={{ selected }}
|
|
204
|
+
key={option.value}
|
|
205
|
+
onPress={() => onSelect(option.value)}
|
|
206
|
+
style={({ pressed }) => [
|
|
207
|
+
styles.menuOption,
|
|
208
|
+
selected && styles.selectedOption,
|
|
209
|
+
pressed && (selected ? styles.pressedSelectedOption : styles.pressedMenuOption),
|
|
210
|
+
]}
|
|
211
|
+
testID={`${testID}-${option.value}`}
|
|
212
|
+
>
|
|
213
|
+
<Text style={[styles.optionText, selected && styles.selectedOptionText]}>{option.label}</Text>
|
|
214
|
+
</Pressable>
|
|
215
|
+
);
|
|
216
|
+
})}
|
|
217
|
+
</View>
|
|
218
|
+
) : null}
|
|
219
|
+
</View>
|
|
220
|
+
</View>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
73
224
|
const layoutStyles = StyleSheet.create({
|
|
74
225
|
root: {
|
|
75
226
|
flex: 1,
|
|
@@ -80,48 +231,91 @@ const getStyles = themedStyleSheetFactory('StorybookTheme', ({ tokens }) =>
|
|
|
80
231
|
StyleSheet.create({
|
|
81
232
|
root: {
|
|
82
233
|
flex: 1,
|
|
83
|
-
backgroundColor: tokens.color.surfaceNeutralFar,
|
|
234
|
+
backgroundColor: tokens.color.surfaceNeutralFar.rest,
|
|
84
235
|
},
|
|
85
236
|
header: {
|
|
86
|
-
alignItems: '
|
|
87
|
-
backgroundColor: tokens.color.backgroundNeutralSubtle,
|
|
88
|
-
borderBottomColor: tokens.color.strokeNeutralSubtle,
|
|
237
|
+
alignItems: 'flex-start',
|
|
238
|
+
backgroundColor: tokens.color.backgroundNeutralSubtle.rest,
|
|
239
|
+
borderBottomColor: tokens.color.strokeNeutralSubtle.rest,
|
|
89
240
|
borderBottomWidth: 1,
|
|
90
241
|
flexDirection: 'row',
|
|
91
242
|
flexWrap: 'wrap',
|
|
92
|
-
gap:
|
|
243
|
+
gap: 16,
|
|
93
244
|
minHeight: 44,
|
|
94
245
|
paddingHorizontal: 12,
|
|
95
|
-
paddingVertical:
|
|
246
|
+
paddingVertical: 8,
|
|
247
|
+
},
|
|
248
|
+
field: {
|
|
249
|
+
alignItems: 'flex-start',
|
|
250
|
+
flexDirection: 'row',
|
|
251
|
+
gap: 6,
|
|
96
252
|
},
|
|
97
253
|
label: {
|
|
98
|
-
color: tokens.color.foregroundNeutralPrimary,
|
|
254
|
+
color: tokens.color.foregroundNeutralPrimary.rest,
|
|
99
255
|
fontSize: 14,
|
|
100
256
|
fontWeight: '600',
|
|
101
|
-
|
|
257
|
+
paddingVertical: 5,
|
|
102
258
|
},
|
|
103
259
|
option: {
|
|
104
|
-
backgroundColor: tokens.color.
|
|
105
|
-
borderColor: tokens.color.
|
|
260
|
+
backgroundColor: tokens.color.surfaceNeutralNearer.rest,
|
|
261
|
+
borderColor: tokens.color.strokeNeutralLoud.rest,
|
|
106
262
|
borderRadius: 4,
|
|
107
263
|
borderWidth: 1,
|
|
108
264
|
paddingHorizontal: 10,
|
|
109
265
|
paddingVertical: 5,
|
|
110
266
|
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
267
|
+
trigger: {
|
|
268
|
+
alignItems: 'center',
|
|
269
|
+
flexDirection: 'row',
|
|
270
|
+
gap: 8,
|
|
271
|
+
justifyContent: 'space-between',
|
|
272
|
+
minWidth: 96,
|
|
114
273
|
},
|
|
115
274
|
pressedOption: {
|
|
116
|
-
|
|
275
|
+
backgroundColor: tokens.color.surfaceNeutralNearer.pressed,
|
|
276
|
+
borderColor: tokens.color.strokeNeutralLoud.pressed,
|
|
277
|
+
},
|
|
278
|
+
disabledOption: {
|
|
279
|
+
backgroundColor: tokens.color.backgroundNeutralDisabled.rest,
|
|
280
|
+
borderColor: tokens.color.strokeNeutralDisabled.rest,
|
|
281
|
+
},
|
|
282
|
+
menu: {
|
|
283
|
+
backgroundColor: tokens.color.surfaceNeutralNearer.rest,
|
|
284
|
+
borderColor: tokens.color.strokeNeutralSubtle.rest,
|
|
285
|
+
borderRadius: 4,
|
|
286
|
+
borderWidth: 1,
|
|
287
|
+
gap: 2,
|
|
288
|
+
marginTop: 4,
|
|
289
|
+
padding: 2,
|
|
290
|
+
},
|
|
291
|
+
menuOption: {
|
|
292
|
+
backgroundColor: tokens.color.backgroundNeutralTransparent.rest,
|
|
293
|
+
borderRadius: 3,
|
|
294
|
+
paddingHorizontal: 10,
|
|
295
|
+
paddingVertical: 5,
|
|
296
|
+
},
|
|
297
|
+
pressedMenuOption: {
|
|
298
|
+
backgroundColor: tokens.color.backgroundNeutralTransparent.pressed,
|
|
299
|
+
},
|
|
300
|
+
selectedOption: {
|
|
301
|
+
backgroundColor: tokens.color.backgroundNeutralSoft.rest,
|
|
302
|
+
},
|
|
303
|
+
pressedSelectedOption: {
|
|
304
|
+
backgroundColor: tokens.color.backgroundNeutralSoft.pressed,
|
|
117
305
|
},
|
|
118
306
|
optionText: {
|
|
119
|
-
color: tokens.color.foregroundNeutralPrimary,
|
|
307
|
+
color: tokens.color.foregroundNeutralPrimary.rest,
|
|
308
|
+
fontSize: 12,
|
|
309
|
+
},
|
|
310
|
+
chevron: {
|
|
311
|
+
color: tokens.color.foregroundNeutralSecondary.rest,
|
|
120
312
|
fontSize: 12,
|
|
121
313
|
},
|
|
122
314
|
selectedOptionText: {
|
|
123
|
-
color: tokens.color.foregroundBrandOnloud,
|
|
124
315
|
fontWeight: '600',
|
|
125
316
|
},
|
|
317
|
+
disabledText: {
|
|
318
|
+
color: tokens.color.foregroundNeutralDisabled.rest,
|
|
319
|
+
},
|
|
126
320
|
}),
|
|
127
321
|
);
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { SBUI } from '@storybook/react-native-ui-common';
|
|
2
2
|
|
|
3
|
+
import { StorybookStoryTheme } from './StorybookTheme';
|
|
3
4
|
import { StorybookUIComponent } from './StorybookUI';
|
|
4
5
|
import { useStorybookTheme } from './useStorybookTheme';
|
|
5
6
|
|
|
6
|
-
export const ThemedStorybookUI: SBUI = (props) => {
|
|
7
|
+
export const ThemedStorybookUI: SBUI = ({ children, ...props }) => {
|
|
7
8
|
const theme = useStorybookTheme();
|
|
8
|
-
return
|
|
9
|
+
return (
|
|
10
|
+
<StorybookUIComponent {...props} theme={theme}>
|
|
11
|
+
<StorybookStoryTheme>{children}</StorybookStoryTheme>
|
|
12
|
+
</StorybookUIComponent>
|
|
13
|
+
);
|
|
9
14
|
};
|
|
@@ -47,7 +47,7 @@ describe('Storybook theme palette', () => {
|
|
|
47
47
|
expect(themes[themes.length - 1].background.content).toBe('#654321');
|
|
48
48
|
expect(themes[themes.length - 1].background.preview).toBe('#654321');
|
|
49
49
|
expect(themes[themes.length - 1]).not.toBe(themes[0]);
|
|
50
|
-
expect(themes[themes.length - 1].appBorderColor).toBe(states[states.length - 1].tokens.color.strokeNeutralSubtle);
|
|
50
|
+
expect(themes[themes.length - 1].appBorderColor).toBe(states[states.length - 1].tokens.color.strokeNeutralSubtle.rest);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
it('warns and substitutes a contrasting theme color for native opaque colors', async () => {
|
|
@@ -60,7 +60,7 @@ describe('Storybook theme palette', () => {
|
|
|
60
60
|
<Probe themes={themes} states={states} />
|
|
61
61
|
</ThemedRoot>,
|
|
62
62
|
);
|
|
63
|
-
expect(themes[0].color.primary).toBe(states[0].tokens.color.fixedBlack);
|
|
63
|
+
expect(themes[0].color.primary).toBe(states[0].tokens.color.fixedBlack.rest);
|
|
64
64
|
expect(warn).toHaveBeenCalledTimes(1);
|
|
65
65
|
expect(warn).toHaveBeenCalledWith(expect.stringContaining('foregroundBrandPrimary'));
|
|
66
66
|
});
|
|
@@ -84,8 +84,8 @@ describe('Storybook theme palette', () => {
|
|
|
84
84
|
</ThemedRoot>,
|
|
85
85
|
);
|
|
86
86
|
|
|
87
|
-
expect(themes[0].background.content).toBe(states[0].tokens.color[fallback]);
|
|
88
|
-
expect(themes[0].background.preview).toBe(states[0].tokens.color[fallback]);
|
|
87
|
+
expect(themes[0].background.content).toBe(states[0].tokens.color[fallback].rest);
|
|
88
|
+
expect(themes[0].background.preview).toBe(states[0].tokens.color[fallback].rest);
|
|
89
89
|
expect(warn).toHaveBeenCalledTimes(1);
|
|
90
90
|
expect(warn).toHaveBeenCalledWith(expect.stringContaining('surfaceNeutralFar'));
|
|
91
91
|
});
|
package/src/useStorybookTheme.ts
CHANGED
|
@@ -24,14 +24,14 @@ function storybookThemeFromThemeState({ appearance, highContrast, tokens }: Them
|
|
|
24
24
|
const background = base.base === 'dark' ? 'fixedBlack' : 'fixedWhite';
|
|
25
25
|
const nativeColors = new Set<keyof SemanticColorTokenValues>();
|
|
26
26
|
const color = (name: keyof SemanticColorTokenValues, role: 'foreground' | 'background' = 'foreground'): string => {
|
|
27
|
-
const value = tokens.color[name];
|
|
27
|
+
const value = tokens.color[name].rest;
|
|
28
28
|
// Storybook also passes these colors to JS color utilities, not just native styles.
|
|
29
29
|
if (typeof value === 'string') {
|
|
30
30
|
return value;
|
|
31
31
|
}
|
|
32
32
|
nativeColors.add(name);
|
|
33
33
|
const fallbackName = role === 'foreground' ? foreground : background;
|
|
34
|
-
const fallback = tokens.color[fallbackName];
|
|
34
|
+
const fallback = tokens.color[fallbackName].rest;
|
|
35
35
|
if (typeof fallback !== 'string') {
|
|
36
36
|
throw new Error(`Storybook chrome requires a literal "${fallbackName}" fallback for the "${name}" theme token.`);
|
|
37
37
|
}
|