chore: Enable Storybook web for storybook-react-native#1134
Conversation
📖 Storybook Preview |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
|
All alerts resolved. Learn more about Socket for GitHub. This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored. Ignoring alerts on:
|
86941c2 to
ec088b9
Compare
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Dropped
TextColorimport causes runtime crash in stories- Added
TextColorto the../../Textimports in Blockies, Jazzicon, and Maskicon stories to prevent the runtime ReferenceError.
- Added
Or push these changes by commenting:
@cursor push a17b7fa1e0
Preview (a17b7fa1e0)
diff --git a/packages/design-system-react-native/src/components/temp-components/Blockies/Blockies.stories.tsx b/packages/design-system-react-native/src/components/temp-components/Blockies/Blockies.stories.tsx
--- a/packages/design-system-react-native/src/components/temp-components/Blockies/Blockies.stories.tsx
+++ b/packages/design-system-react-native/src/components/temp-components/Blockies/Blockies.stories.tsx
@@ -7,7 +7,7 @@
import { ScrollView } from 'react-native';
import { Box } from '../../Box';
-import { Text, TextVariant, FontWeight } from '../../Text';
+import { Text, TextColor, TextVariant, FontWeight } from '../../Text';
import { Blockies } from './Blockies';
import type { BlockiesProps } from './Blockies.types';
diff --git a/packages/design-system-react-native/src/components/temp-components/Jazzicon/Jazzicon.stories.tsx b/packages/design-system-react-native/src/components/temp-components/Jazzicon/Jazzicon.stories.tsx
--- a/packages/design-system-react-native/src/components/temp-components/Jazzicon/Jazzicon.stories.tsx
+++ b/packages/design-system-react-native/src/components/temp-components/Jazzicon/Jazzicon.stories.tsx
@@ -7,7 +7,7 @@
import { ScrollView } from 'react-native';
import { Box } from '../../Box';
-import { Text, TextVariant, FontWeight } from '../../Text';
+import { Text, TextColor, TextVariant, FontWeight } from '../../Text';
import { Jazzicon } from './Jazzicon';
import type { JazziconProps } from './Jazzicon.types';
diff --git a/packages/design-system-react-native/src/components/temp-components/Maskicon/Maskicon.stories.tsx b/packages/design-system-react-native/src/components/temp-components/Maskicon/Maskicon.stories.tsx
--- a/packages/design-system-react-native/src/components/temp-components/Maskicon/Maskicon.stories.tsx
+++ b/packages/design-system-react-native/src/components/temp-components/Maskicon/Maskicon.stories.tsx
@@ -7,7 +7,7 @@
import { ScrollView } from 'react-native';
import { Box } from '../../Box';
-import { Text, TextVariant, FontWeight } from '../../Text';
+import { Text, TextColor, TextVariant, FontWeight } from '../../Text';
import { Maskicon } from './Maskicon';
import type { MaskiconProps } from './Maskicon.types';You can send follow-ups to the cloud agent here.
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Interpolate mock returns wrong value for out-of-range input
- Updated interpolate to return the last output when value exceeds the input range (i === -1) while preserving first-output behavior for i === 0.
Or push these changes by commenting:
@cursor push 3c902a14bd
Preview (3c902a14bd)
diff --git a/apps/storybook-react-native/.storybook/reanimated-mock.ts b/apps/storybook-react-native/.storybook/reanimated-mock.ts
--- a/apps/storybook-react-native/.storybook/reanimated-mock.ts
+++ b/apps/storybook-react-native/.storybook/reanimated-mock.ts
@@ -94,8 +94,8 @@
outputRange: number[],
): number => {
const i = inputRange.findIndex((v) => v >= value);
- if (i <= 0) return outputRange[0];
- if (i >= inputRange.length) return outputRange[outputRange.length - 1];
+ if (i === 0) return outputRange[0];
+ if (i === -1) return outputRange[outputRange.length - 1];
const t = (value - inputRange[i - 1]) / (inputRange[i] - inputRange[i - 1]);
return outputRange[i - 1] + t * (outputRange[i] - outputRange[i - 1]);
};You can send follow-ups to the cloud agent here.
| <GestureHandlerRootView style={{ flex: 1 }}> | ||
| <ThemeProvider theme={theme}>{children}</ThemeProvider> | ||
| <GestureHandlerRootView style={{ flex: 1, backgroundColor }}> | ||
| <SafeAreaProvider> |
There was a problem hiding this comment.
SafeAreaProvider is required because several components — most notably BottomSheet and BottomSheetDialog — call useSafeAreaInsets() internally to calculate bottom padding for home indicator clearance. Without a provider in the tree, the hook returns zero insets and the sheet renders flush to the screen edge, which doesn't match production. In a real app the provider lives at the root; this replicates that context for all stories.
| return ( | ||
| <GestureHandlerRootView style={{ flex: 1 }}> | ||
| <ThemeProvider theme={theme}>{children}</ThemeProvider> | ||
| <GestureHandlerRootView style={{ flex: 1, backgroundColor }}> |
There was a problem hiding this comment.
The canvas background is set to the background.default design token value rather than Storybook's default white. Components like Box with bg-default or bg-alternative use the same token as their surface colour — rendering them on white would make them invisible or misleading. The raw hex is read directly from @metamask/design-tokens rather than resolved through TWRNC because React Native's style prop requires an actual colour string, not a Tailwind class.
| * Each @font-face must use the matching PostScript name so the browser resolves it. | ||
| */ | ||
|
|
||
| @font-face { font-family: 'Geist-Regular'; src: url('/fonts/Geist/Geist-Regular.woff2') format('woff2'); } |
There was a problem hiding this comment.
Browsers normally resolve font variants by matching a single font-family name against font-weight and font-style descriptors — e.g. font-family: 'Geist'; font-weight: 500 would resolve to the Medium variant. react-native-web bypasses this entirely: it passes the fontFamily value computed by TWRNC directly as a CSS font-family inline style, so font-default-medium becomes font-family: 'Geist-Medium' in the browser. A conventionally-named @font-face block would never match. Each declaration here must use the PostScript name as its font-family value so the browser can resolve it.
| @font-face { font-family: 'Geist-SemiBold'; src: url('/fonts/Geist/Geist-SemiBold.woff2') format('woff2'); } | ||
| @font-face { font-family: 'Geist-SemiBoldItalic'; src: url('/fonts/Geist/Geist-SemiBoldItalic.woff2') format('woff2'); } | ||
|
|
||
| @font-face { font-family: 'MMSans-Regular'; src: url('/fonts/MMSans/MMSans-Regular.woff2') format('woff2'); } |
There was a problem hiding this comment.
woff2 is used here rather than the OTF files in the fonts/ directory because woff2 is the browser-native format — it's compressed and doesn't require any additional loader. The OTF files exist for the native Expo Go runtime (useFonts in index.tsx); the woff2 files in public/fonts/ are the web equivalent served by the Storybook static server.
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Native font embedding removed from app.json plugins
- Re-added the expo-font plugin to apps/storybook-react-native/app.json and listed all native font files so iOS/Android builds bundle and register Geist, MMSans, and MMPoly.
Or push these changes by commenting:
@cursor push 213509e6aa
Preview (213509e6aa)
diff --git a/apps/storybook-react-native/app.json b/apps/storybook-react-native/app.json
--- a/apps/storybook-react-native/app.json
+++ b/apps/storybook-react-native/app.json
@@ -16,6 +16,24 @@
"backgroundColor": "#ffffff"
}
},
- "plugins": []
+ "plugins": [
+ [
+ "expo-font",
+ {
+ "fonts": [
+ "./fonts/Geist/Geist-Regular.otf",
+ "./fonts/Geist/Geist-RegularItalic.otf",
+ "./fonts/Geist/Geist-Medium.otf",
+ "./fonts/Geist/Geist-MediumItalic.otf",
+ "./fonts/Geist/Geist-SemiBold.otf",
+ "./fonts/Geist/Geist-SemiBoldItalic.otf",
+ "./fonts/MMSans/MMSans-Regular.otf",
+ "./fonts/MMSans/MMSans-Medium.otf",
+ "./fonts/MMSans/MMSans-Bold.otf",
+ "./fonts/MMPoly/MMPoly-Regular.otf"
+ ]
+ }
+ ]
+ ]
}
}You can send follow-ups to the cloud agent here.
📖 Storybook Preview |
📖 Storybook Preview |
📖 Storybook Preview |
📖 Storybook Preview |
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Vite 8 upgrade breaks
rollupOptionsin storybook-react config- Replaced build.rollupOptions with build.rolldownOptions in apps/storybook-react/vite.config.ts to keep .md/.mdx externalization working on Vite 8 and pushed the change.
Or push these changes by commenting:
@cursor push 5720eaeb04
Preview (5720eaeb04)
diff --git a/apps/storybook-react/vite.config.ts b/apps/storybook-react/vite.config.ts
--- a/apps/storybook-react/vite.config.ts
+++ b/apps/storybook-react/vite.config.ts
@@ -18,7 +18,7 @@
},
assetsInclude: ['**/*.md', '**/*.mdx'],
build: {
- rollupOptions: {
+ rolldownOptions: {
external: [/\.md$/, /\.mdx$/],
},
},You can send follow-ups to the cloud agent here.
📖 Storybook Preview |
| inputMap.sourcesContent = []; | ||
| for (const sourceFile of inputMap.sources) { | ||
| - inputMap.sourcesContent.push(fs.readFileSync(sourceFile).toString("utf-8")); | ||
| + inputMap.sourcesContent.push(fs.readFileSync(sourceFile.replace(/[?#].*$/, "")).toString("utf-8")); |
There was a problem hiding this comment.
The worklets Babel plugin calls fs.readFileSync on source file paths when generating sourcemaps, but Vite appends ?v=abc123 cache-busting query strings to filenames before passing them to transform plugins. The file on disk has no such suffix, so the read throws ENOENT. Stripping any ? or # suffix with .replace(/[?#].*$/, "") before the read fixes this without affecting sourcemap content. This matches the upstream fix proposed in the react-native-reanimated fork and tracked in dannyhw/vite-plugin-rnw#13. The patch stays in place until react-native-worklets ships a release containing the fix.
| -import NoControlsWarning from './NoControlsWarning'; | ||
| -import PropForm from './PropForm'; | ||
| -import { useArgs } from './hooks'; | ||
| +import NoControlsWarning from './NoControlsWarning.js'; |
There was a problem hiding this comment.
@storybook/addon-ondevice-controls ships its dist/ as ESM but omits file extensions on all relative imports (e.g. './PropForm' instead of './PropForm.js'). Node.js's strict ESM resolver requires explicit extensions for relative imports, so every internal module fails to load at runtime in the on-device Storybook environment. The patch adds .js to every affected import throughout the dist. The issue is present in all published versions through 10.4.4 and has not been fixed upstream.
The worklets Babel plugin threw ENOENT when Vite appended ?v=… cache- busting query strings to source file paths before passing them to readFileSync. This blocked the modulesToTranspile approach from working and forced us to alias react-native-reanimated to a no-op stub. Patch react-native-worklets@0.5.1 (plugin/index.js line 697) to strip ?query and #hash suffixes before readFileSync — matching the upstream fix proposed in NiGhTTraX:react-native-reanimated:fix/filename-querystring and discussed in dannyhw/vite-plugin-rnw#13. With the patch applied, vite-plugin-rnw's esbuild resolveExtensions (which puts .web.js before .js) correctly selects index.web.js for Reanimated's directory imports during pre-bundling, and the worklets Babel plugin processes the pre-bundled file without error. Remove reanimated-mock.ts and the alias — real Reanimated now runs in web Storybook with full animation support.
…ugin in Rolldown dep phase The Storybook preset's modulesToTranspile exclude regex matches the outer `/node_modules/` segment in the Vite dep cache path (.cache/storybook/.../sb-vite/deps/react-native-reanimated.js), so the worklets Babel plugin never fires on the pre-bundled Reanimated file during the transform phase. Without the worklets transformation, Reanimated's module initialization fails and the default Animated export is undefined, crashing any component that calls Animated.createAnimatedComponent. Upgrade Vite to 8 so vite-plugin-rnw uses Rolldown for dep optimization, then inject the worklets Babel plugin into optimizeDeps.rolldownOptions.plugins. This runs the plugin on the original source files during pre-bundling — before they are concatenated into the giant cache file — where the exclude regex based on .* correctly identifies the packages by name. Also add @rolldown/plugin-babel as a dev dependency. Solution from: dannyhw/vite-plugin-rnw#13
…for web Add position:relative to GestureHandlerRootView so absolute-positioned components (BottomSheet, BottomSheetDialog, BottomSheetOverlay) resolve their CSS positioning context correctly on web. Add html/body/storybook-root height rules to fonts.css so the flex:1 chain propagates correctly through SafeAreaProvider, ensuring useSafeAreaFrame() returns the actual canvas dimensions on first render. Closes #1196
The file now contains both global layout rules (html/body/storybook-root height chain) and font declarations, so fonts.css was no longer accurate.
9e2aaaf to
f4a33d5
Compare
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Native preview missing background options
- Added backgrounds options and initialGlobals to native preview and synced ThemeProvider to the selected globals.backgrounds key to match web.
Or push these changes by commenting:
@cursor push 225051d005
Preview (225051d005)
diff --git a/apps/storybook-react-native/.rnstorybook/preview.tsx b/apps/storybook-react-native/.rnstorybook/preview.tsx
--- a/apps/storybook-react-native/.rnstorybook/preview.tsx
+++ b/apps/storybook-react-native/.rnstorybook/preview.tsx
@@ -2,13 +2,24 @@
import { Theme, ThemeProvider } from '@metamask/design-system-twrnc-preset';
import { darkTheme, lightTheme } from '@metamask/design-tokens';
import React, { type PropsWithChildren } from 'react';
-import { useColorScheme } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
-const ThemeDecorator = ({ children }: PropsWithChildren) => {
- const colorScheme = useColorScheme();
- const theme = colorScheme === 'dark' ? Theme.Dark : Theme.Light;
+// Background options keyed by name so on-device backgrounds toolbar
+// and context.globals.backgrounds.value (which returns the key) both work.
+const backgroundOptions = {
+ light: { name: 'light', value: lightTheme.colors.background.default },
+ dark: { name: 'dark', value: darkTheme.colors.background.default },
+};
+
+function themeFromKey(key?: string): Theme {
+ return key === 'dark' ? Theme.Dark : Theme.Light;
+}
+
+type ThemeDecoratorProps = PropsWithChildren<{ selectedKey?: string }>;
+
+const ThemeDecorator = ({ children, selectedKey }: ThemeDecoratorProps) => {
+ const theme = themeFromKey(selectedKey);
const backgroundColor =
theme === Theme.Dark
? darkTheme.colors.background.default
@@ -25,12 +36,26 @@
const preview: Preview = {
decorators: [
- (Story: React.ComponentType) => (
- <ThemeDecorator>
- <Story />
- </ThemeDecorator>
- ),
+ (Story: React.ComponentType, context: any) => {
+ const selectedKey = context.globals?.backgrounds?.value as
+ | string
+ | undefined;
+ return (
+ <ThemeDecorator selectedKey={selectedKey}>
+ <Story />
+ </ThemeDecorator>
+ );
+ },
],
+ parameters: {
+ backgrounds: {
+ options: backgroundOptions,
+ },
+ layout: 'fullscreen',
+ },
+ initialGlobals: {
+ backgrounds: { value: 'light' },
+ },
};
export default preview;You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit f4a33d5. Configure here.
| const preview: Preview = { | ||
| decorators: [ | ||
| (Story) => ( | ||
| (Story: React.ComponentType) => ( |
There was a problem hiding this comment.
Native preview missing background options
Medium Severity
This PR registers Storybook background options and syncs ThemeProvider to globals.backgrounds on web, but the native .rnstorybook/preview.tsx still exports only decorators. On-device backgrounds needs parameters.backgrounds.options in preview (as on web), and native theme still follows useColorScheme() instead of the selected background key, so canvas and theme can disagree.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit f4a33d5. Configure here.
There was a problem hiding this comment.
This breaks theming if we add backgrounds. I tried added but then intentionally removed
…rom tsconfig Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
📖 Storybook Preview |
📖 Storybook Preview |
…#1188) ## **Description** Adds React Native component stories to the existing React Storybook by using Storybook composition. The React Storybook now acts as the host Storybook and includes a composed **React Native Components** section. This makes the React Native web Storybook available from the same Storybook UI in local development, PR preview builds, and the deployed web Storybook. Storybook composition lets one Storybook reference another Storybook by URL so stories from both appear together in the sidebar, even when they use different renderers or project setup: https://storybook.js.org/docs/sharing/storybook-composition ## **What changed** - Adds a `react-native` Storybook `refs` entry in `apps/storybook-react/.storybook/main.ts`. - Points the composed React Native Storybook to `http://localhost:6007` during local development. - Points the composed React Native Storybook to `./react-native` for static hosted builds. - Updates the main Storybook workflow to build the React Storybook and React Native web Storybook, then nest the React Native static build under `storybook-static/react-native/` before deploying to GitHub Pages. - Updates the PR Storybook workflow to do the same nesting before uploading PR preview builds, so React Native component stories are available in PR builds too. ## **Local dev workflow** ```bash # Terminal 1 yarn storybook:web # Terminal 2 yarn storybook ``` Open `http://localhost:6006` and expand **React Native Components** in the sidebar. ## **Related issues** Depends on: #1134 ## **Manual testing steps** 1. Start `yarn storybook:web` and wait for the React Native web Storybook to run on port 6007. 2. Start `yarn storybook` and open `http://localhost:6006`. 3. Confirm **React Native Components** appears in the sidebar. 4. Expand **React Native Components** and open a story. 5. Confirm the story renders in the main canvas. 6. Open the PR Storybook preview build. 7. Confirm **React Native Components** is available there as well. ## **Screenshots/Recordings** ### **After** The hosted React Storybook includes a composed **React Native Components** section backed by the React Native web Storybook. https://github.com/user-attachments/assets/ec3eb3d6-e368-482e-a090-801e51c67107 ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.



Description
Adds Storybook web support for
apps/storybook-react-nativeand fixes the runtime issues needed to render the React Native component stories in the browser.This PR:
background/defaulttoken-backed definitionsRelated issues
Part of: #87
Manual testing steps
yarn storybook:webhttp://localhost:6006/Examples/Backgrounds,Components/BannerAlert, andComponents/BottomSheetrender without the earlier web runtime errorsScreenshots/Recordings
Before
After
storybook.react.native.web.mov
Pre-merge author checklist
Pre-merge reviewer checklist
Note
Medium Risk
Touches build tooling, Vite optimizeDeps/Babel for worklets, and patched node_modules—failures would block Storybook web/dev builds, not shipped product UI.
Overview
Adds browser Storybook for
storybook-react-nativevia a new.storybook/stack (@storybook/react-native-web-vite,react-native-web,storybook:web/build-storybookscripts) and a Vite config that aliases workspace design packages, serves fonts/SVGs, and runs the react-native-worklets Babel plugin during Rolldown optimizeDeps so Reanimated/worklets pre-bundle correctly on web.Web preview wires design-token CSS, fullscreen layout, token-backed background globals (synced to
ThemeProvider), andSafeAreaProvider/ gesture-handler roots; the on-device preview gets the same safe-area wrapper and default background colors from tokens.Yarn patches fix ESM resolution in
@storybook/addon-ondevice-controls(.jsimport suffixes) and worklets source-map reads when Vite adds?v=query strings. Storybook on-device packages bump to 10.4.4; ESLint/tsconfig include the new.storybookpaths; README marks web Storybook as supported.Reviewed by Cursor Bugbot for commit 3ad43a7. Bugbot is set up for automated code reviews on this repo. Configure here.