Skip to content

Commit 6b2753d

Browse files
authored
Components: Switch agentic/components to 0.81+, update ref instructions and usage (#4300)
* remove support for < 0.74 in components package * switch components package to rn 0.81+ and ref guidelines and usage
1 parent 0a6286c commit 6b2753d

50 files changed

Lines changed: 407 additions & 102 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/bright-refs-flow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@fluentui-react-native/components": minor
3+
---
4+
5+
Expose React 19 ref props for components and primitives with stable native roots.

.github/skills/agentic-component-authoring/references/rendering.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ Set `displayName` for diagnostics and Storybook metadata. Keep unstable stage fu
5959
future composition work can identify them. Do not wrap the component in memoization or another boundary without measured
6060
need and repository precedent.
6161

62+
Under this package's React 19.1.4 baseline, `ref` is an ordinary component prop. Include it in the public root props and
63+
let the state hook forward it to the resolved root slot; do not wrap assembly components in `forwardRef`. If rendering a
64+
second internal ref on the same slot, pass it in JSX so the slot runtime composes it with the captured consumer ref.
65+
6266
After assembly, export `use<Component>_unstable`, `use<Component>Styles_unstable`, and
6367
`render<Component>_unstable` from the package root together with `<Component>State`, according to the
6468
[types and slots export rules](types-and-slots.md#exports). These stages are intentionally unstable but public so other

.github/skills/agentic-component-authoring/references/types-and-slots.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,27 @@ enables behavior; Button uses `selected !== undefined` to distinguish an ordinar
3434

3535
Do not blindly inherit every root prop when the component owns part of the native contract.
3636

37-
1. Start with the root native props.
37+
1. Start with `PropsWithRefOf<typeof Root>` so the native root's ref is part of the public contract.
3838
2. Omit props the component controls, such as `children` or token-derived `style`.
3939
3. Reintroduce a narrowed form only when consumers need it.
4040

41-
Button uses `Omit<PressableProps, 'children' | 'style'>` and adds `StyleProp<ViewStyle>` back explicitly. This prevents
42-
native children from bypassing slot order while preserving a user style that can be applied after component styles.
41+
Button uses `OwnedRootProps<PropsWithRefOf<typeof Pressable>>`. This prevents native children from bypassing slot order,
42+
preserves the native style type, and retains `ref`.
4343

4444
Compose the final public props with:
4545

4646
```ts
4747
export type MyComponentProps = MyComponentStateProps & ComponentProps<MyComponentSlots, ExposedRootProps>;
4848
```
4949

50+
This package targets React 19.1.4 or newer, where function components receive `ref` as a prop. Let the state hook carry
51+
that prop to the declared root slot; do not wrap the component in `forwardRef`. A top-level component ref always targets
52+
the declared root. Consumers that need an inner public slot use that slot's own `ref`.
53+
54+
When a component needs an internal root ref as well, keep both refs and pass one through the resolved slot and the other
55+
when rendering the slot so Framework Base composes them. Never overwrite the consumer ref. If a primitive can render
56+
unrelated native instance types, omit a top-level ref unless it can define one sound, stable imperative contract.
57+
5058
## Make resolved state complete
5159

5260
Build state from:
@@ -89,5 +97,6 @@ internal style definitions.
8997
- Variant unions match the spec exactly.
9098
- Omitted and false values retain distinct semantics where required.
9199
- Root native props cannot bypass owned children or style ordering.
100+
- Stable native roots expose a correctly typed `ref`, and the state/render pipeline does not replace it.
92101
- Resolved state contains every defaulted and derived value needed downstream.
93102
- Public component, state, style-application, render, and resolved-state exports are explicit and tree-shakeable.

packages/agentic/components/AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ invariants; detailed authoring recipes live in the
2929
- Use `src/components/button` as the canonical higher-order implementation and `src/primitives/icon` as the canonical
3030
primitive.
3131
- Keep public props and slots small, typed, and spec-driven.
32+
- This package targets React 19.1.4 or newer. Expose a stable native root ref through the `ref` prop, receive it as part
33+
of the component props, and forward it to the declared root slot. Do not use `forwardRef`.
3234
- Export higher-order components and public types explicitly from `src/index.ts`; export primitives and their public types
3335
explicitly from `src/primitives/index.ts`. Never use wildcard exports.
3436
- Colocate runtime tests, type tests, and Storybook stories with the implementation.

packages/agentic/components/package.json

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@babel/core": "catalog:",
4848
"@fluentui-react-native/desktop-driver": "workspace:*",
4949
"@fluentui-react-native/scripts": "workspace:*",
50+
"@office-iss/react-native-win32": "^0.81.0",
5051
"@react-native-community/cli": "^20.0.0",
5152
"@react-native-community/cli-platform-android": "^20.0.0",
5253
"@react-native-community/cli-platform-ios": "^20.0.0",
@@ -58,16 +59,17 @@
5859
"react-native": "^0.81.6",
5960
"react-native-macos": "^0.81.0",
6061
"react-native-svg": "^15.12.1",
62+
"react-native-windows": "^0.81.0",
6163
"test-renderer": "catalog:"
6264
},
6365
"peerDependencies": {
64-
"@office-iss/react-native-win32": "^0.74.0 || ^0.81.0",
65-
"@types/react": "~18.2.0 || ~19.0.0 || ~19.1.4",
66-
"react": "18.2.0 || 19.0.0 || 19.1.4",
67-
"react-native": "^0.73.0 || ^0.74.0 || ^0.78.0 || ^0.81.6",
68-
"react-native-macos": "^0.73.0 || ^0.74.0 || ^0.78.0 || ^0.81.0",
69-
"react-native-svg": ">=15.0.0 <15.13.0 || >=15.4.0 <15.13.0 || ^15.11.2 || ^15.12.1",
70-
"react-native-windows": "^0.73.0 || ^0.74.0 || ^0.78.0 || ^0.81.0"
66+
"@office-iss/react-native-win32": "^0.81.0",
67+
"@types/react": "~19.1.4",
68+
"react": "19.1.4",
69+
"react-native": "^0.81.6",
70+
"react-native-macos": "^0.81.0",
71+
"react-native-svg": "^15.12.1",
72+
"react-native-windows": "^0.81.0"
7173
},
7274
"peerDependenciesMeta": {
7375
"@office-iss/react-native-win32": {
@@ -94,9 +96,16 @@
9496
"rnx-kit": {
9597
"kitType": "library",
9698
"alignDeps": {
99+
"requirements": {
100+
"production": [
101+
"react-native@0.81"
102+
]
103+
},
97104
"capabilities": [
98105
"core",
99106
"core-macos",
107+
"core-win32",
108+
"core-windows",
100109
"svg"
101110
]
102111
},

packages/agentic/components/src/components/AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ audit.
3838
- Create style factories only at module scope and cache theme-only styles.
3939
- Declare state precedence explicitly; disabled wins over pressed, which wins over hovered.
4040
- Apply user styles after component styles.
41+
- Include the declared native root's `ref` in public props with `PropsWithRefOf<typeof Root>`. React 19.1.4 supplies
42+
`ref` as a prop, so pass it through the state hook to the root slot without `forwardRef`. If the component also needs
43+
an internal root ref, compose the refs through the slot render path rather than replacing the consumer ref.
4144
- Render focus feedback through `FocusVisual`; do not add `outline*` props or enable RNW native
4245
focus visuals because RNW 0.81 can fail-fast when either path creates border visuals after mount.
4346
- Keep render functions free of hooks, token reads, style creation, and slot mutation.

packages/agentic/components/src/components/accordion/accordion.types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import type { AccessibilityState, Pressable, StyleProp, Text, View, ViewProps, ViewStyle } from 'react-native';
1+
import type { AccessibilityState, Pressable, StyleProp, Text, View, ViewStyle } from 'react-native';
22
import type {
33
ComponentProps,
44
ComponentState,
55
OptionalSlot,
66
OwnedRootProps,
77
PressableState,
8+
PropsWithRefOf,
89
Slot,
910
} from '@fluentui-react-native/framework-base';
1011
import type { ThemeState } from '@fluentui-react-native/design';
@@ -85,7 +86,7 @@ export type AccordionStateProps = {
8586
};
8687

8788
export type AccordionRootProps = OwnedRootProps<
88-
ViewProps,
89+
PropsWithRefOf<typeof View>,
8990
'accessible' | 'accessibilityHint' | 'accessibilityLabel' | 'accessibilityRole' | 'accessibilityState' | 'focusable'
9091
>;
9192

packages/agentic/components/src/components/avatar/avatar.types.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
import type { Image, StyleProp, Text, View, ViewProps, ViewStyle } from 'react-native';
2-
import type { ComponentProps, ComponentState, OptionalSlot, OwnedRootProps, Slot } from '@fluentui-react-native/framework-base';
1+
import type { Image, StyleProp, Text, View, ViewStyle } from 'react-native';
2+
import type {
3+
ComponentProps,
4+
ComponentState,
5+
OptionalSlot,
6+
OwnedRootProps,
7+
PropsWithRefOf,
8+
Slot,
9+
} from '@fluentui-react-native/framework-base';
310
import type { ThemeState } from '@fluentui-react-native/design';
411

512
import type { Icon } from '../../primitives/icon/icon';
@@ -26,7 +33,7 @@ export type AvatarStateProps = {
2633
size?: AvatarSize;
2734
};
2835

29-
export type AvatarRootProps = OwnedRootProps<ViewProps>;
36+
export type AvatarRootProps = OwnedRootProps<PropsWithRefOf<typeof View>>;
3037

3138
export type AvatarProps = AvatarStateProps & ComponentProps<AvatarSlots, AvatarRootProps>;
3239

packages/agentic/components/src/components/badge/badge.types.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
import type { StyleProp, Text, View, ViewProps, ViewStyle } from 'react-native';
2-
import type { ComponentProps, ComponentState, OptionalSlot, OwnedRootProps, Slot, SlotProp } from '@fluentui-react-native/framework-base';
1+
import type { StyleProp, Text, View, ViewStyle } from 'react-native';
2+
import type {
3+
ComponentProps,
4+
ComponentState,
5+
OptionalSlot,
6+
OwnedRootProps,
7+
PropsWithRefOf,
8+
Slot,
9+
SlotProp,
10+
} from '@fluentui-react-native/framework-base';
311
import type { ThemeState } from '@fluentui-react-native/design';
412

513
import type { Icon } from '../../primitives/icon/icon';
@@ -59,7 +67,7 @@ type BadgeIconOnlyProps = BadgeCommonProps & {
5967

6068
export type BadgeStateProps = BadgeIconAndTextProps | BadgeIconOnlyProps;
6169

62-
export type BadgeExposedViewProps = OwnedRootProps<ViewProps, 'accessibilityRole' | 'focusable'>;
70+
export type BadgeExposedViewProps = OwnedRootProps<PropsWithRefOf<typeof View>, 'accessibilityRole' | 'focusable'>;
6371

6472
export type BadgeProps = BadgeStateProps & ComponentProps<BadgeSlots, BadgeExposedViewProps>;
6573

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/** @jsxImportSource @fluentui-react-native/framework-base */
2+
import * as React from 'react';
23
import { StyleSheet, View } from 'react-native';
3-
import type { PressableProps, ViewStyle } from 'react-native';
4+
import type { Pressable, PressableProps, ViewStyle } from 'react-native';
45

56
import { fireEvent, render } from '@testing-library/react-native';
67
import type { RenderResult } from '@testing-library/react-native';
@@ -23,6 +24,14 @@ function getRootStyle(component: RenderResult): ViewStyle {
2324
}
2425

2526
describe('Button', () => {
27+
it('forwards its ref prop to the native root', async () => {
28+
const ref = React.createRef<React.ElementRef<typeof Pressable>>();
29+
30+
await renderButton({ content: 'Save', ref });
31+
32+
expect(ref.current).not.toBeNull();
33+
});
34+
2635
it('reuses cached theme styles without recreating them for another button instance', async () => {
2736
const createStyleSheet = jest.spyOn(StyleSheet, 'create');
2837

0 commit comments

Comments
 (0)