Skip to content

Commit ea738f0

Browse files
authored
Ensure that useThemeState will work with existing furn themes (#4186)
* update mapping values for flex-tokens * add ability to get flex tokens if provided with a fluent theme * add the start of theming for the storybook app * fix tests after styling changes * fix lint error in test
1 parent 778d82b commit ea738f0

30 files changed

Lines changed: 1149 additions & 249 deletions

.changeset/round-buttons-align.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@fluentui-react-native/components": patch
3+
"@fluentui-react-native/design": patch
4+
---
5+
6+
Align agentic Button corner radii with desktop V1 values and add context-backed FURN Theme-to-Flex token conversion

.github/skills/agentic-component-authoring/references/styles-and-tokens.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ value.
1717
- Prefer Flex tokens for every mapped semantic value.
1818
- Import named global tokens only when Flex has no equivalent, such as current Button icon dimensions or a true zero.
1919
- Record a genuine token gap in the component spec. Do not replace it with a nearby token or numeric literal.
20+
- For compatibility with an existing V1 component, derive cross-platform mappings from its Win32, macOS, or Windows
21+
implementation. Do not use iOS as the canonical value unless the change explicitly targets iOS.
22+
- `useThemeState` projects a FURN Theme from context into Flex tokens and stores one symbol-keyed `ThemeState` on that
23+
Theme object. Consumers of the same Theme therefore share token and style-cache identity.
2024

2125
## Build styles in layers
2226

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ This is the **FluentUI React Native** repository, a monorepo containing React Na
4141

4242
**Slots**: The slot pattern is used to compose higher-order components. A slot represents an inner component (actual entry in the render tree). For example, a Button might have slots for `root`, `icon`, and `content`. This allows advanced customization scenarios. Components wrapping a single native component typically have one slot.
4343

44-
**Tokens**: Design tokens handle styling and customization. Tokens are design-time values set via theme or component customization (e.g., "brandColor"). Tokens can also be props (specified via "TokensThatAreAlsoProps"). This system enables simpler customization and better memoization. For Flex token authoring, use `packages/agentic-design/src/tokens/mappings/flex-token-map.yaml` as the canonical mapping from generic CSS and Fluent token sources to grouped React Native Flex token paths.
44+
**Tokens**: Design tokens handle styling and customization. Tokens are design-time values set via theme or component customization (e.g., "brandColor"). Tokens can also be props (specified via "TokensThatAreAlsoProps"). This system enables simpler customization and better memoization. For Flex token authoring, use `packages/agentic-design/src/tokens/mappings/flex-token-map.yaml` as the canonical mapping from generic CSS and Fluent token sources to grouped React Native Flex token paths. When mapping an agentic component to an existing V1 component, use the Win32, macOS, or Windows implementation as the canonical compatibility reference; do not use iOS as the cross-platform canonical value unless the work explicitly targets iOS.
4545

4646
**Theme-specific styles**: Cache `StyleSheet.create` results that depend only on `ThemeState` with a module-scoped getter created by `themedStyleSheetFactory`. Treat them as immutable, and apply props, interaction state, and user styles separately so the cached sheet is safe to share between component instances.
4747

packages/agentic-components/src/components/button/__snapshots__/button.test.tsx.snap

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ exports[`Button matches the disabled visual state snapshot across appearances 1`
2020
"alignSelf": "flex-start",
2121
"backgroundColor": "#f0f0f0",
2222
"borderColor": "#00000000",
23-
"borderRadius": 12,
23+
"borderRadius": 4,
2424
"borderStyle": "solid",
2525
"borderWidth": 1,
2626
"flexDirection": "row",
@@ -50,7 +50,7 @@ exports[`Button matches the disabled visual state snapshot across appearances 1`
5050
"alignSelf": "flex-start",
5151
"backgroundColor": "#f0f0f0",
5252
"borderColor": "#00000000",
53-
"borderRadius": 12,
53+
"borderRadius": 4,
5454
"borderStyle": "solid",
5555
"borderWidth": 1,
5656
"flexDirection": "row",
@@ -80,7 +80,7 @@ exports[`Button matches the disabled visual state snapshot across appearances 1`
8080
"alignSelf": "flex-start",
8181
"backgroundColor": "#00000000",
8282
"borderColor": "#e0e0e0",
83-
"borderRadius": 12,
83+
"borderRadius": 4,
8484
"borderStyle": "solid",
8585
"borderWidth": 1,
8686
"flexDirection": "row",
@@ -110,7 +110,7 @@ exports[`Button matches the disabled visual state snapshot across appearances 1`
110110
"alignSelf": "flex-start",
111111
"backgroundColor": "#00000000",
112112
"borderColor": "#00000000",
113-
"borderRadius": 12,
113+
"borderRadius": 4,
114114
"borderStyle": "solid",
115115
"borderWidth": 1,
116116
"flexDirection": "row",
@@ -145,7 +145,7 @@ exports[`Button matches the focused visual state snapshot across appearances 1`]
145145
"alignSelf": "flex-start",
146146
"backgroundColor": "#185abd",
147147
"borderColor": "#ffffff",
148-
"borderRadius": 12,
148+
"borderRadius": 4,
149149
"borderStyle": "solid",
150150
"borderWidth": 1,
151151
"flexDirection": "row",
@@ -179,7 +179,7 @@ exports[`Button matches the focused visual state snapshot across appearances 1`]
179179
"alignSelf": "flex-start",
180180
"backgroundColor": "#fafafa",
181181
"borderColor": "#ffffff",
182-
"borderRadius": 12,
182+
"borderRadius": 4,
183183
"borderStyle": "solid",
184184
"borderWidth": 1,
185185
"flexDirection": "row",
@@ -213,7 +213,7 @@ exports[`Button matches the focused visual state snapshot across appearances 1`]
213213
"alignSelf": "flex-start",
214214
"backgroundColor": "#00000000",
215215
"borderColor": "#ffffff",
216-
"borderRadius": 12,
216+
"borderRadius": 4,
217217
"borderStyle": "solid",
218218
"borderWidth": 1,
219219
"flexDirection": "row",
@@ -247,7 +247,7 @@ exports[`Button matches the focused visual state snapshot across appearances 1`]
247247
"alignSelf": "flex-start",
248248
"backgroundColor": "#00000000",
249249
"borderColor": "#ffffff",
250-
"borderRadius": 12,
250+
"borderRadius": 4,
251251
"borderStyle": "solid",
252252
"borderWidth": 1,
253253
"flexDirection": "row",
@@ -286,7 +286,7 @@ exports[`Button matches the hovered visual state snapshot across appearances 1`]
286286
"alignSelf": "flex-start",
287287
"backgroundColor": "#1651aa",
288288
"borderColor": "#00000000",
289-
"borderRadius": 12,
289+
"borderRadius": 4,
290290
"borderStyle": "solid",
291291
"borderWidth": 1,
292292
"flexDirection": "row",
@@ -316,7 +316,7 @@ exports[`Button matches the hovered visual state snapshot across appearances 1`]
316316
"alignSelf": "flex-start",
317317
"backgroundColor": "#f0f0f0",
318318
"borderColor": "#00000000",
319-
"borderRadius": 12,
319+
"borderRadius": 4,
320320
"borderStyle": "solid",
321321
"borderWidth": 1,
322322
"flexDirection": "row",
@@ -346,7 +346,7 @@ exports[`Button matches the hovered visual state snapshot across appearances 1`]
346346
"alignSelf": "flex-start",
347347
"backgroundColor": "#00000000",
348348
"borderColor": "#c7c7c7",
349-
"borderRadius": 12,
349+
"borderRadius": 4,
350350
"borderStyle": "solid",
351351
"borderWidth": 1,
352352
"flexDirection": "row",
@@ -376,7 +376,7 @@ exports[`Button matches the hovered visual state snapshot across appearances 1`]
376376
"alignSelf": "flex-start",
377377
"backgroundColor": "#f0f0f0",
378378
"borderColor": "#00000000",
379-
"borderRadius": 12,
379+
"borderRadius": 4,
380380
"borderStyle": "solid",
381381
"borderWidth": 1,
382382
"flexDirection": "row",
@@ -411,7 +411,7 @@ exports[`Button matches the pressed visual state snapshot across appearances 1`]
411411
"alignSelf": "flex-start",
412412
"backgroundColor": "#0e336a",
413413
"borderColor": "#00000000",
414-
"borderRadius": 12,
414+
"borderRadius": 4,
415415
"borderStyle": "solid",
416416
"borderWidth": 1,
417417
"flexDirection": "row",
@@ -441,7 +441,7 @@ exports[`Button matches the pressed visual state snapshot across appearances 1`]
441441
"alignSelf": "flex-start",
442442
"backgroundColor": "#dbdbdb",
443443
"borderColor": "#00000000",
444-
"borderRadius": 12,
444+
"borderRadius": 4,
445445
"borderStyle": "solid",
446446
"borderWidth": 1,
447447
"flexDirection": "row",
@@ -471,7 +471,7 @@ exports[`Button matches the pressed visual state snapshot across appearances 1`]
471471
"alignSelf": "flex-start",
472472
"backgroundColor": "#00000000",
473473
"borderColor": "#b3b3b3",
474-
"borderRadius": 12,
474+
"borderRadius": 4,
475475
"borderStyle": "solid",
476476
"borderWidth": 1,
477477
"flexDirection": "row",
@@ -501,7 +501,7 @@ exports[`Button matches the pressed visual state snapshot across appearances 1`]
501501
"alignSelf": "flex-start",
502502
"backgroundColor": "#dbdbdb",
503503
"borderColor": "#00000000",
504-
"borderRadius": 12,
504+
"borderRadius": 4,
505505
"borderStyle": "solid",
506506
"borderWidth": 1,
507507
"flexDirection": "row",
@@ -536,7 +536,7 @@ exports[`Button matches the rest visual state snapshot across appearances 1`] =
536536
"alignSelf": "flex-start",
537537
"backgroundColor": "#185abd",
538538
"borderColor": "#00000000",
539-
"borderRadius": 12,
539+
"borderRadius": 4,
540540
"borderStyle": "solid",
541541
"borderWidth": 1,
542542
"flexDirection": "row",
@@ -566,7 +566,7 @@ exports[`Button matches the rest visual state snapshot across appearances 1`] =
566566
"alignSelf": "flex-start",
567567
"backgroundColor": "#fafafa",
568568
"borderColor": "#00000000",
569-
"borderRadius": 12,
569+
"borderRadius": 4,
570570
"borderStyle": "solid",
571571
"borderWidth": 1,
572572
"flexDirection": "row",
@@ -596,7 +596,7 @@ exports[`Button matches the rest visual state snapshot across appearances 1`] =
596596
"alignSelf": "flex-start",
597597
"backgroundColor": "#00000000",
598598
"borderColor": "#d1d1d1",
599-
"borderRadius": 12,
599+
"borderRadius": 4,
600600
"borderStyle": "solid",
601601
"borderWidth": 1,
602602
"flexDirection": "row",
@@ -626,7 +626,7 @@ exports[`Button matches the rest visual state snapshot across appearances 1`] =
626626
"alignSelf": "flex-start",
627627
"backgroundColor": "#00000000",
628628
"borderColor": "#00000000",
629-
"borderRadius": 12,
629+
"borderRadius": 4,
630630
"borderStyle": "solid",
631631
"borderWidth": 1,
632632
"flexDirection": "row",
@@ -676,7 +676,7 @@ exports[`Button matches the selected visual state snapshot across appearances 1`
676676
"alignSelf": "flex-start",
677677
"backgroundColor": "#185abd",
678678
"borderColor": "#00000000",
679-
"borderRadius": 12,
679+
"borderRadius": 4,
680680
"borderStyle": "solid",
681681
"borderWidth": 1,
682682
"flexDirection": "row",
@@ -721,7 +721,7 @@ exports[`Button matches the selected visual state snapshot across appearances 1`
721721
"alignSelf": "flex-start",
722722
"backgroundColor": "#616161",
723723
"borderColor": "#00000000",
724-
"borderRadius": 12,
724+
"borderRadius": 4,
725725
"borderStyle": "solid",
726726
"borderWidth": 1,
727727
"flexDirection": "row",
@@ -766,7 +766,7 @@ exports[`Button matches the selected visual state snapshot across appearances 1`
766766
"alignSelf": "flex-start",
767767
"backgroundColor": "#616161",
768768
"borderColor": "#616161",
769-
"borderRadius": 12,
769+
"borderRadius": 4,
770770
"borderStyle": "solid",
771771
"borderWidth": 1,
772772
"flexDirection": "row",
@@ -811,7 +811,7 @@ exports[`Button matches the selected visual state snapshot across appearances 1`
811811
"alignSelf": "flex-start",
812812
"backgroundColor": "#0000001a",
813813
"borderColor": "#00000000",
814-
"borderRadius": 12,
814+
"borderRadius": 4,
815815
"borderStyle": "solid",
816816
"borderWidth": 1,
817817
"flexDirection": "row",

packages/agentic-components/src/components/button/button.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,9 @@ describe('Button', () => {
293293
);
294294

295295
it.each([
296-
['small', 12, 8, 8],
297-
['medium', 14, 10, 12],
298-
['large', 16, 12, 12],
296+
['small', 12, 8, 4],
297+
['medium', 14, 10, 4],
298+
['large', 16, 12, 6],
299299
] as const)('resolves the %s size', async (size, fontSize, paddingHorizontal, borderRadius) => {
300300
const component = await renderButton({ content: size, size });
301301
expect(StyleSheet.flatten(component.getByText(size).props.style)).toMatchObject({
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`ListItem renders the default row with selection hidden and stable content styling 1`] = `
4+
{
5+
"alignItems": "center",
6+
"alignSelf": "stretch",
7+
"backgroundColor": "#00000000",
8+
"borderRadius": 4,
9+
"flexDirection": "row",
10+
"justifyContent": "flex-start",
11+
"paddingHorizontal": 12,
12+
"paddingVertical": 10,
13+
"width": "100%",
14+
}
15+
`;

packages/agentic-components/src/components/list-item/list-item.test.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,14 @@ function getRootStyle(component: RenderResult): ViewStyle {
2323

2424
describe('ListItem', () => {
2525
it('renders the default row with selection hidden and stable content styling', async () => {
26-
const colors = useFlexTokens().color;
2726
const component = await renderListItem({ content: 'Inbox' });
2827
const root = getRoot(component);
2928

3029
expect(root.props.accessibilityRole).toBe('button');
3130
expect(root.props.accessibilityState).toEqual({ disabled: false, selected: false });
3231
expect(root.props.focusable).toBe(true);
3332
expect(component.getAllByText('Inbox', { includeHiddenElements: true })).toHaveLength(2);
34-
expect(getRootStyle(component)).toMatchObject({
35-
alignItems: 'center',
36-
backgroundColor: colors.backgroundNeutralTransparent,
37-
borderRadius: 12,
38-
paddingHorizontal: 12,
39-
paddingVertical: 10,
40-
width: '100%',
41-
});
33+
expect(getRootStyle(component)).toMatchSnapshot();
4234
expect(component.queryByText('☐')).toBeNull();
4335
});
4436

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Skeleton renders a decorative bar with themed fill and rounded corners 1`] = `
4+
{
5+
"backgroundColor": "#fafafa",
6+
"borderRadius": 2,
7+
"height": 16,
8+
"overflow": "hidden",
9+
"position": "relative",
10+
"width": 120,
11+
}
12+
`;

packages/agentic-components/src/components/skeleton/skeleton.test.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { ViewStyle } from 'react-native';
55

66
import { fireEvent, render } from '@testing-library/react-native';
77

8-
import { useFlexTokens } from '@fluentui-react-native/design';
98
import { useReducedMotion } from '@fluentui-react-native/framework-base';
109

1110
import { Skeleton } from './skeleton';
@@ -40,17 +39,9 @@ describe('Skeleton', () => {
4039

4140
it('renders a decorative bar with themed fill and rounded corners', async () => {
4241
const component = await renderSkeleton({ style: { height: 16, width: 120 } });
43-
const colors = useFlexTokens().color;
4442

4543
expect(getRoot(component).props.accessible).toBe(false);
46-
expect(getRootStyle(component)).toMatchObject({
47-
backgroundColor: colors.backgroundNeutralSubtle,
48-
borderRadius: 4,
49-
height: 16,
50-
overflow: 'hidden',
51-
position: 'relative',
52-
width: 120,
53-
});
44+
expect(getRootStyle(component)).toMatchSnapshot();
5445
});
5546

5647
it('forwards user layout handlers while preserving the wave animation', async () => {

packages/agentic-components/src/components/tag/tag.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ describe('Tag', () => {
163163
});
164164

165165
it.each([
166-
['rounded', 12],
166+
['rounded', 4],
167167
['circular', 9999],
168168
] as const)('resolves the %s shape', async (shape, borderRadius) => {
169169
const component = await renderTag({ content: shape, shape });

0 commit comments

Comments
 (0)