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"