diff --git a/examples/expo-example/.storybook-web/main.ts b/examples/expo-example/.storybook-web/main.ts
index d83ffb945e..b9307b76ea 100644
--- a/examples/expo-example/.storybook-web/main.ts
+++ b/examples/expo-example/.storybook-web/main.ts
@@ -21,6 +21,7 @@ const main: ServerStorybookConfig = {
'@storybook/addon-react-native-web',
// note why does this break with get absolute?
'@storybook/addon-react-native-server',
+ 'storybook-addon-deep-controls',
],
// logLevel: 'debug',
framework: getAbsolutePath('@storybook/react-webpack5'),
diff --git a/examples/expo-example/.storybook/main.ts b/examples/expo-example/.storybook/main.ts
index 3b8df3d9dd..8c0b52f8e4 100644
--- a/examples/expo-example/.storybook/main.ts
+++ b/examples/expo-example/.storybook/main.ts
@@ -21,6 +21,7 @@ const main: StorybookConfig = {
'@storybook/addon-ondevice-backgrounds',
'@storybook/addon-ondevice-actions',
'@storybook/addon-ondevice-notes',
+ 'storybook-addon-deep-controls',
],
reactNative: {
playFn: false,
diff --git a/examples/expo-example/.storybook/storybook.requires.ts b/examples/expo-example/.storybook/storybook.requires.ts
index 6c6301d3a9..fc9f4e8f91 100644
--- a/examples/expo-example/.storybook/storybook.requires.ts
+++ b/examples/expo-example/.storybook/storybook.requires.ts
@@ -6,6 +6,7 @@ import "@storybook/addon-ondevice-controls/register";
import "@storybook/addon-ondevice-backgrounds/register";
import "@storybook/addon-ondevice-actions/register";
import "@storybook/addon-ondevice-notes/register";
+import "storybook-addon-deep-controls/register";
const normalizedStories = [
{
@@ -57,7 +58,8 @@ declare global {
const annotations = [
require("./preview"),
require("@storybook/react-native/preview"),
- require("@storybook/addon-actions/preview"),
+ require("@storybook/addon-ondevice-actions/preview"),
+ require("storybook-addon-deep-controls/preview"),
];
global.STORIES = normalizedStories;
diff --git a/examples/expo-example/other_components/DeepControls/DeepControls.stories.tsx b/examples/expo-example/other_components/DeepControls/DeepControls.stories.tsx
new file mode 100644
index 0000000000..d7fcdf2db9
--- /dev/null
+++ b/examples/expo-example/other_components/DeepControls/DeepControls.stories.tsx
@@ -0,0 +1,72 @@
+import { Meta, StoryObj } from '@storybook/react';
+import { Text, View } from 'react-native';
+
+const DeepControls = ({
+ objectArg,
+}: {
+ objectArg: {
+ string: string;
+ number: number;
+ boolean: boolean;
+ enumString: string;
+ nested: { number: number; boolean: boolean };
+ };
+}) => {
+ return (
+
+ Testing story with deep controls (storybook-addon-deep-controls)
+ {JSON.stringify(objectArg, null, 2)}
+
+ );
+};
+
+export default {
+ title: 'DeepControls',
+ component: DeepControls,
+} satisfies Meta;
+
+export const Basic: StoryObj = {
+ parameters: {
+ deepControls: { enabled: true },
+ },
+ args: {
+ objectArg: {
+ string: 'foo',
+ number: 42,
+ boolean: true,
+ enumString: 'value2', // we only want specific values for this
+ nested: {
+ number: 222,
+ boolean: false,
+ },
+ },
+ },
+ argTypes: {
+ // so we define an argType for the property to use a radio control with specific values
+ // @ts-expect-error
+ 'objectArg.enumString': {
+ control: 'radio',
+ options: ['value1', 'value2', 'value3'],
+ },
+
+ 'objectArg.boolean': {
+ control: 'boolean',
+ },
+
+ 'objectArg.number': {
+ control: 'number',
+ },
+
+ 'objectArg.string': {
+ control: 'text',
+ },
+
+ 'objectArg.nested.boolean': {
+ control: 'boolean',
+ },
+
+ 'objectArg.nested.number': {
+ control: 'number',
+ },
+ },
+};
diff --git a/examples/expo-example/package.json b/examples/expo-example/package.json
index 3f621efb80..5e6054c0d8 100644
--- a/examples/expo-example/package.json
+++ b/examples/expo-example/package.json
@@ -56,6 +56,7 @@
"react-native-web": "~0.19.13",
"react-router": "^6.26.2",
"storybook": "^8.4.0",
+ "storybook-addon-deep-controls": "^0.9.2",
"ws": "^8.18.0"
},
"devDependencies": {
diff --git a/packages/ondevice-actions/preview.js b/packages/ondevice-actions/preview.js
new file mode 100644
index 0000000000..0d7d5c6c33
--- /dev/null
+++ b/packages/ondevice-actions/preview.js
@@ -0,0 +1 @@
+export * from '@storybook/addon-actions/preview';
diff --git a/packages/ondevice-actions/src/preview.ts b/packages/ondevice-actions/src/preview.ts
new file mode 100644
index 0000000000..0d7d5c6c33
--- /dev/null
+++ b/packages/ondevice-actions/src/preview.ts
@@ -0,0 +1 @@
+export * from '@storybook/addon-actions/preview';
diff --git a/packages/react-native/package.json b/packages/react-native/package.json
index f146faf3d4..7725caab2c 100644
--- a/packages/react-native/package.json
+++ b/packages/react-native/package.json
@@ -41,7 +41,8 @@
"dev": "npx --yes tsx buildscripts/gendtsdev.ts && tsup --watch",
"prepare": "rm -rf dist/ && tsup",
"test": "jest",
- "test:ci": "jest"
+ "test:ci": "jest",
+ "test:update": "jest --updateSnapshot"
},
"dependencies": {
"@storybook/core": "^8.4.0",
diff --git a/packages/react-native/scripts/__snapshots__/generate.test.js.snap b/packages/react-native/scripts/__snapshots__/generate.test.js.snap
index 46070d6c9f..9b7ad7d9c0 100644
--- a/packages/react-native/scripts/__snapshots__/generate.test.js.snap
+++ b/packages/react-native/scripts/__snapshots__/generate.test.js.snap
@@ -11,6 +11,7 @@ import "@storybook/addon-ondevice-controls/register";
import "@storybook/addon-ondevice-backgrounds/register";
import "@storybook/addon-ondevice-actions/register";
+
const normalizedStories = [{
titlePrefix: "",
directory: "./scripts/mocks/file-extensions",
@@ -27,7 +28,7 @@ import "@storybook/addon-ondevice-actions/register";
}
- const annotations = [require('./preview'),require("@storybook/react-native/preview"), require('@storybook/addon-actions/preview')];
+ const annotations = [require('./preview'), require("@storybook/react-native/preview"), require('@storybook/addon-ondevice-actions/preview')];
global.STORIES = normalizedStories;
@@ -61,6 +62,7 @@ import "@storybook/addon-ondevice-controls/register";
import "@storybook/addon-ondevice-backgrounds/register";
import "@storybook/addon-ondevice-actions/register";
+
const normalizedStories = [{
titlePrefix: "ComponentsPrefix",
directory: "./scripts/mocks/configuration-objects/components",
@@ -77,7 +79,7 @@ import "@storybook/addon-ondevice-actions/register";
}
- const annotations = [require('./preview'),require("@storybook/react-native/preview"), require('@storybook/addon-actions/preview')];
+ const annotations = [require('./preview'), require("@storybook/react-native/preview"), require('@storybook/addon-ondevice-actions/preview')];
global.STORIES = normalizedStories;
@@ -111,6 +113,7 @@ import "@storybook/addon-ondevice-controls/register";
import "@storybook/addon-ondevice-backgrounds/register";
import "@storybook/addon-ondevice-actions/register";
+
const normalizedStories = [{
titlePrefix: "",
directory: "./scripts/mocks/all-config-files",
@@ -127,7 +130,7 @@ import "@storybook/addon-ondevice-actions/register";
}
- const annotations = [require('./preview'),require("@storybook/react-native/preview"), require('@storybook/addon-actions/preview')];
+ const annotations = [require('./preview'), require("@storybook/react-native/preview"), require('@storybook/addon-ondevice-actions/preview')];
global.STORIES = normalizedStories;
@@ -161,6 +164,7 @@ import "@storybook/addon-ondevice-controls/register";
import "@storybook/addon-ondevice-backgrounds/register";
import "@storybook/addon-ondevice-actions/register";
+
const normalizedStories = [{
titlePrefix: "",
directory: "./scripts/mocks/no-preview",
@@ -177,7 +181,7 @@ import "@storybook/addon-ondevice-actions/register";
}
- const annotations = [require("@storybook/react-native/preview"), require('@storybook/addon-actions/preview')];
+ const annotations = [require("@storybook/react-native/preview"), require('@storybook/addon-ondevice-actions/preview')];
global.STORIES = normalizedStories;
@@ -211,6 +215,7 @@ import "@storybook/addon-ondevice-controls/register";
import "@storybook/addon-ondevice-backgrounds/register";
import "@storybook/addon-ondevice-actions/register";
+
const normalizedStories = [{
titlePrefix: "",
directory: "./scripts/mocks/all-config-files",
@@ -222,7 +227,7 @@ import "@storybook/addon-ondevice-actions/register";
- const annotations = [require('./preview'),require("@storybook/react-native/preview"), require('@storybook/addon-actions/preview')];
+ const annotations = [require('./preview'), require("@storybook/react-native/preview"), require('@storybook/addon-ondevice-actions/preview')];
global.STORIES = normalizedStories;
diff --git a/packages/react-native/scripts/common.js b/packages/react-native/scripts/common.js
index 1d3245bcce..4637ddccdc 100644
--- a/packages/react-native/scripts/common.js
+++ b/packages/react-native/scripts/common.js
@@ -58,6 +58,28 @@ function getPreviewExists({ configPath }) {
return !!getFilePathExtension({ configPath }, 'preview');
}
+function resolveAddonFile(addon, file, extensions = ['js', 'mjs', 'ts']) {
+ try {
+ const basePath = path.join(addon, file);
+
+ require.resolve(basePath);
+
+ return basePath;
+ } catch (error) {}
+
+ for (const ext of extensions) {
+ try {
+ const filePath = path.join(addon, `${file}.${ext}`);
+
+ require.resolve(filePath);
+
+ return filePath;
+ } catch (error) {}
+ }
+
+ return null;
+}
+
module.exports = {
toRequireContext,
requireUncached,
@@ -65,4 +87,5 @@ module.exports = {
getMain,
ensureRelativePathHasDot,
getPreviewExists,
+ resolveAddonFile,
};
diff --git a/packages/react-native/scripts/generate.js b/packages/react-native/scripts/generate.js
index 088b8f0d0f..f9bc6a4ed5 100644
--- a/packages/react-native/scripts/generate.js
+++ b/packages/react-native/scripts/generate.js
@@ -3,6 +3,7 @@ const {
ensureRelativePathHasDot,
getMain,
getPreviewExists,
+ resolveAddonFile,
} = require('./common');
const { normalizeStories, globToRegexp } = require('@storybook/core/common');
const fs = require('fs');
@@ -46,14 +47,28 @@ function generate({ configPath, absolute = false, useJs = false }) {
}`;
});
- const registerAddons = main.addons?.map((addon) => `import "${addon}/register";`).join('\n');
+ let registerAddons = '';
- const doctools = 'require("@storybook/react-native/preview")';
+ for (const addon of main.addons) {
+ const registerPath = resolveAddonFile(addon, 'register', ['js', 'mjs', 'jsx', 'ts', 'tsx']);
- // TODO: implement presets or something similar
- const enhancer = main.addons?.includes('@storybook/addon-ondevice-actions')
- ? "require('@storybook/addon-actions/preview')"
- : '';
+ if (registerPath) {
+ registerAddons += `import "${registerPath}";\n`;
+ }
+ }
+
+ const docTools = 'require("@storybook/react-native/preview")';
+
+ const enhancers = [docTools];
+
+ for (const addon of main.addons) {
+ const previewPath = resolveAddonFile(addon, 'preview', ['js', 'mjs', 'jsx', 'ts', 'tsx']);
+
+ if (previewPath) {
+ enhancers.push(`require('${previewPath}')`);
+ continue;
+ }
+ }
let options = '';
let optionsVar = '';
@@ -66,7 +81,11 @@ function generate({ configPath, absolute = false, useJs = false }) {
const previewExists = getPreviewExists({ configPath });
- const annotations = `[${previewExists ? "require('./preview')," : ''}${doctools}, ${enhancer}]`;
+ if (previewExists) {
+ enhancers.unshift("require('./preview')");
+ }
+
+ const annotations = `[${enhancers.join(', ')}]`;
const globalTypes = `
declare global {
diff --git a/yarn.lock b/yarn.lock
index 16823c5578..ffc167dd36 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9949,6 +9949,7 @@ __metadata:
react-native-web: "npm:~0.19.13"
react-router: "npm:^6.26.2"
storybook: "npm:^8.4.0"
+ storybook-addon-deep-controls: "npm:^0.9.2"
typescript: "npm:^5.3.3"
ws: "npm:^8.18.0"
languageName: unknown
@@ -17498,6 +17499,17 @@ __metadata:
languageName: node
linkType: hard
+"storybook-addon-deep-controls@npm:^0.9.2":
+ version: 0.9.2
+ resolution: "storybook-addon-deep-controls@npm:0.9.2"
+ peerDependencies:
+ "@storybook/addon-controls": 7.x.x || 8.x.x
+ "@storybook/types": 7.x.x || 8.x.x
+ storybook: 7.x.x || 8.x.x
+ checksum: 10/458c401195d34559afd637fdd2fbe5bfe447d5400b80abdd0a600a34a51671d27762c2476b654be45878590eb5124fcf809bafb3cd591abcd92f922fe287db5a
+ languageName: node
+ linkType: hard
+
"storybook@npm:^8.4.0":
version: 8.4.0
resolution: "storybook@npm:8.4.0"