diff --git a/.changeset/calm-zones-focus.md b/.changeset/calm-zones-focus.md new file mode 100644 index 00000000000..a2961d3090f --- /dev/null +++ b/.changeset/calm-zones-focus.md @@ -0,0 +1,5 @@ +--- +'@fluentui-react-native/focus-zone': patch +--- + +Convert FocusZone to an unstyled phased primitive and add package-owned macOS and Windows Fabric implementations alongside Paper. diff --git a/.github/skills/agentic-storybook-development/SKILL.md b/.github/skills/agentic-storybook-development/SKILL.md index 2a15402300a..42d8ae2dcc3 100644 --- a/.github/skills/agentic-storybook-development/SKILL.md +++ b/.github/skills/agentic-storybook-development/SKILL.md @@ -10,15 +10,20 @@ Work on the native Storybook application in `apps/storybook`. ## Workflow -1. Read the repository `AGENTS.md`, then the +1. Read the repository `AGENTS.md`, then the compact + [agent map](../../../apps/storybook/agent-map.yaml). Read the [Storybook instructions](../../../apps/storybook/AGENTS.md) and - [README](../../../apps/storybook/README.md). + [README](../../../apps/storybook/README.md) only as needed for the task. 2. Inspect the Storybook `package.json` and run its declared workspace scripts; do not invent direct runner commands. -3. Reproduce the first failure from the Storybook workspace so pnpm-linked React Native tooling resolves from the app. +3. Use `storybook-agent` to query runtime story IDs, select stories, and sweep renderability. Reproduce the first + failure from the Storybook workspace so pnpm-linked React Native tooling resolves from the app. 4. Treat native workspaces, Pods, lockfiles, generated solutions, build directories, and DerivedData as disposable outputs. Fix the owning manifest, Podfile, configuration, or script instead. 5. After dependency or workaround changes, regenerate the affected native dependency source and use a clean build so stale output cannot produce a false success. + When adding a codegen-enabled workspace dependency to existing macOS Pods, the first pod install can generate its + spec without refreshing ReactCodegen's source list. If the clean build then reports a missing generated spec header, + run `pods:macos` once more before changing native configuration. 6. Validate the JavaScript bundle and the affected native build. Run Storybook format/lint and the root build when manifests or shared package references changed. diff --git a/apps/storybook/AGENTS.md b/apps/storybook/AGENTS.md index 7936bf07399..a2428bda7c8 100644 --- a/apps/storybook/AGENTS.md +++ b/apps/storybook/AGENTS.md @@ -2,7 +2,20 @@ These instructions apply to `apps/storybook` and its descendants. -Read this file, `README.md`, and `package.json` before changing the Storybook application or its native projects. +Read [`agent-map.yaml`](agent-map.yaml) first for the compact architecture, lookup, and interaction map. Read this file, +`README.md`, `package.json`, and `storybook.config.mts` before changing the Storybook application or its native projects. + +## Agent-efficient discovery and interaction + +- Generate the platform manifest with `yarn storybook manifest --` instead of guessing IDs or searching the + sidebar. +- Query and run stories through `yarn desktop-driver` against the listener started by + `yarn storybook driver --`. +- Use screenshots only for visual evidence. Use WebSocket events and accessibility bounds for lookup and interaction. +- Treat `src/storybook.requires.ts` as generated output; use `storybook.config.mts` for story package discovery and + `src/main.ts` only as the shared config adapter. +- Keep `agent-map.yaml` synchronized when stable paths, scripts, services, or interaction contracts change. Do not list + individual stories there; the runtime index is authoritative. ## Command and dependency discipline diff --git a/apps/storybook/README.md b/apps/storybook/README.md index 7b6dedbdd25..b9b9a100fea 100644 --- a/apps/storybook/README.md +++ b/apps/storybook/README.md @@ -3,7 +3,8 @@ On-device [Storybook](https://storybook.js.org/) test app (Storybook for React Native v10) for `@fluentui-react-native/components` and linked standalone native packages. It loads every `*.stories.(ts|tsx)` file from the agentic components package plus the standalone Callout -package so its native stories run in the Fabric host. +package. FocusZone stories are agentic primitive stories, while its standalone native package +remains linked so they run in the Fabric and Paper hosts. The reusable desktop CLI and configuration live in `packages/agentic/storybook-desktop`, with peer-dependent React Native @@ -85,8 +86,9 @@ and services, even when the default ports are already occupied. ## Running on Windows The Windows app also uses `react-native-test-app`. Its generated Win32 project uses React Native -Windows 0.81's New Architecture and Fabric renderer. The Callout package is autolinked as a -Windows Fabric native library; its Paper implementation remains built into the platform. +Windows 0.81's New Architecture and Fabric renderer. The Callout and FocusZone packages are +autolinked as Windows Fabric native libraries; their Paper implementations remain available on +their supported legacy endpoints. ```powershell # from this directory diff --git a/apps/storybook/agent-map.yaml b/apps/storybook/agent-map.yaml new file mode 100644 index 00000000000..edc85a0d7f9 --- /dev/null +++ b/apps/storybook/agent-map.yaml @@ -0,0 +1,74 @@ +version: 1 +purpose: Agent-optimized map of stable Storybook structure and runtime discovery paths. + +sources: + config: storybook.config.mts + config_adapter: src/main.ts + app_entry: index.js + app_shell: src/StorybookApp.tsx + theme_host: src/StorybookTheme.tsx + shared_cli: ../../packages/agentic/storybook-desktop + shared_runtime: ../../packages/agentic/storybook-desktop-runtime + generated_requires: + path: src/storybook.requires.ts + policy: generated-do-not-edit + story_packages: + - '@fluentui-react-native/components' + - '@fluentui-react-native/callout' + +services: + supervisor: + command: yarn storybook driver -- + owns: + - metro + - storybook-channel + - desktop-driver + instance: + command: yarn storybook instance -- + policy: use-reported-ports-and-target-id + +agent_commands: + generate_manifest: yarn storybook manifest -- + start_driver: yarn storybook driver -- + inspect_instance: yarn storybook instance -- + list_stories: yarn desktop-driver stories list --url --target + run_stories: yarn desktop-driver stories run --url --target --tag desktop-e2e + +lookup_order: + - Generate the exact platform manifest; never guess ID slugging. + - Read this map for stable paths, services, and commands. + - Read storybook.config.mts for story-package and platform changes. + - Read a matched story file for component-specific behavior. + - Read generated requires only to diagnose generation output. + +interaction_order: + - Use the desktop-driver JSON CLI for listing and running authored story plans. + - Use accessibility elements for native focus and control state. + - Use screenshots for visual evidence, not element lookup. + +native: + macos: + process: ReactTestApp + app_bundle: macos/DerivedData/Build/Products/Debug/ReactTestApp.app + coordinate_notes: + accessibility: logical-points + screenshots: backing-pixels + rule: Query element bounds and display scale; do not copy screenshot coordinates. + windows: + project_policy: generated + generation_command: yarn storybook prep --windows + +validation: + javascript: + - format + - lint + - yarn storybook manifest -- + - yarn storybook bundle -- + native: + normal: yarn storybook build -- + lifecycle: yarn storybook smoke -- --mode stories-and-tests + +output_hygiene: + - Use structured CLI output instead of logging raw channel events. + - Search large native build logs for errors and final status instead of reading them in full. + - Keep warning suppression exact; console diagnostics remain available even when LogBox hides a warning. diff --git a/apps/storybook/index.js b/apps/storybook/index.js index 3003e18cb20..4c0970bebb2 100644 --- a/apps/storybook/index.js +++ b/apps/storybook/index.js @@ -1,5 +1,12 @@ -import { AppRegistry } from 'react-native'; +import { AppRegistry, LogBox } from 'react-native'; import StorybookApp from './src/StorybookApp'; import { name as appName } from './app.json'; +LogBox.ignoreLogs([ + // The supported React Native peer floor predates the public root codegen exports. + /^Deep imports from the 'react-native' package are deprecated \('react-native\/Libraries\/Utilities\/codegenNative(?:Commands|Component)'\)\./, + // Storybook's sidebar expands its LegendList pool on demand without affecting rendering. + '[legend-list] No unused container available, so creating one on demand.', +]); + AppRegistry.registerComponent(appName, () => StorybookApp); diff --git a/packages/agentic/components/AGENTS.md b/packages/agentic/components/AGENTS.md index 72526f95a51..7a5ede82eb2 100644 --- a/packages/agentic/components/AGENTS.md +++ b/packages/agentic/components/AGENTS.md @@ -12,8 +12,8 @@ invariants; detailed authoring recipes live in the - Portable desktop story tests are static `parameters.desktopDriver` plans. Use the public authoring types, stable `testID` selectors, declarative capability requirements, and no platform branches or executable callbacks. -- Storybook application, native project, Metro, bundle, or CocoaPods work follows `storybook/AGENTS.md` and the - `agentic-storybook-development` skill. +- Storybook application, native project, Metro, bundle, or CocoaPods work follows the + [Storybook instructions](../../../apps/storybook/AGENTS.md) and the `agentic-storybook-development` skill. - Native React Native Windows Fabric component work follows the [Windows Fabric native component reference](../../../.github/skills/agentic-component-authoring/references/windows-fabric-native-components.md). diff --git a/packages/agentic/components/package.json b/packages/agentic/components/package.json index 6d2ab79f66f..9af12fdb2df 100644 --- a/packages/agentic/components/package.json +++ b/packages/agentic/components/package.json @@ -44,6 +44,7 @@ "devDependencies": { "@babel/core": "catalog:", "@fluentui-react-native/desktop-driver": "workspace:*", + "@fluentui-react-native/focus-zone": "workspace:*", "@fluentui-react-native/scripts": "workspace:*", "@react-native-community/cli": "^20.0.0", "@react-native-community/cli-platform-android": "^20.0.0", diff --git a/packages/agentic/components/src/primitives/focus-zone/focus-zone.stories.tsx b/packages/agentic/components/src/primitives/focus-zone/focus-zone.stories.tsx new file mode 100644 index 00000000000..295f2d1340a --- /dev/null +++ b/packages/agentic/components/src/primitives/focus-zone/focus-zone.stories.tsx @@ -0,0 +1,317 @@ +/** @jsxImportSource @fluentui-react-native/framework-base */ +import * as React from 'react'; +import { Platform, Pressable, StyleSheet, Text, View } from 'react-native'; +import type { StyleProp, ViewStyle } from 'react-native'; + +import type { DesktopStoryTests } from '@fluentui-react-native/desktop-driver/authoring'; +import { FocusZone } from '@fluentui-react-native/focus-zone'; +import type { FocusZoneDirection, FocusZoneProps, FocusZoneTabNavigation } from '@fluentui-react-native/focus-zone'; +import type { Meta, StoryObj } from '@storybook/react-native'; + +import { Button } from '../../components/button/button'; + +const defaultTabbableNativeID = 'focus-zone-default-item'; +const directions: readonly FocusZoneDirection[] = ['bidirectional', 'horizontal', 'vertical', 'none']; +const tabNavigationModes: readonly FocusZoneTabNavigation[] = ['None', 'NavigateWrap', 'NavigateStopAtEnds', 'Normal']; + +const styles = StyleSheet.create({ + focusButton: { + marginTop: 16, + }, + focused: { + borderColor: '#0f6cbd', + borderWidth: 3, + }, + focusZone: { + marginVertical: 12, + }, + grid: { + alignItems: 'center', + }, + gridRow: { + flexDirection: 'row', + }, + item: { + height: 64, + margin: 4, + width: 104, + }, + itemText: { + fontSize: 16, + fontWeight: '600', + }, + outsideButton: { + marginVertical: 4, + paddingHorizontal: 12, + paddingVertical: 8, + }, + pressed: { + opacity: 0.7, + }, + status: { + marginTop: 12, + }, + story: { + alignItems: 'center', + justifyContent: 'center', + minHeight: 420, + padding: 24, + width: 520, + }, +}); + +type FocusZoneStoryButtonProps = { + content: string; + focused: boolean; + nativeID?: string; + onFocus: () => void; + style: StyleProp; + testID: string; +}; + +const FocusZoneStoryButton = ({ content, focused, nativeID, onFocus, style, testID }: FocusZoneStoryButtonProps) => { + const localRef = React.useRef(null); + + if (Platform.OS === 'macos') { + return ( + localRef.current?.focus()} + ref={localRef} + style={({ pressed }) => [style, focused && styles.focused, pressed && styles.pressed]} + testID={testID} + > + {content} + + ); + } + + return