From 6450effe3638ef4862403999e3ce801926926903 Mon Sep 17 00:00:00 2001 From: storybook-bot <32066757+storybook-bot@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:59:49 +0000 Subject: [PATCH 01/10] Update ./docs/versions/next.json for v9.1.0-alpha.8 --- docs/versions/next.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/versions/next.json b/docs/versions/next.json index c555efc2bbd7..82fe03f17e0b 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"9.1.0-alpha.7","info":{"plain":"- A11y: Improved toolbar a11y by fixing semantics - [#28672](https://github.com/storybookjs/storybook/pull/28672), thanks @mehm8128!\n- Addon Vitest: Remove Optimize deps candidates due to Vitest warnings - [#31809](https://github.com/storybookjs/storybook/pull/31809), thanks @valentinpalkovic!\n- Addon Vitest: Support init in Vitest >= 3.2 - [#31715](https://github.com/storybookjs/storybook/pull/31715), thanks @valentinpalkovic!\n- Addons: Use chromatic-com/storybook without version specifier - [#31627](https://github.com/storybookjs/storybook/pull/31627), thanks @valentinpalkovic!\n- Angular: Bundle using TSup - [#31690](https://github.com/storybookjs/storybook/pull/31690), thanks @ndelangen!\n- Angular: Fix Storybook experimentalZoneless is not compatible with Angular 20 - [#31772](https://github.com/storybookjs/storybook/pull/31772), thanks @guysenpai!\n- Angular: Tailwind 4 compatibility - [#31759](https://github.com/storybookjs/storybook/pull/31759), thanks @valentinpalkovic!\n- Angular: Update MiniCssExtractPlugin configuration for cache busting - [#31752](https://github.com/storybookjs/storybook/pull/31752), thanks @valentinpalkovic!\n- CLI: Add RN/RNW \\\"both\\\" init option - [#31778](https://github.com/storybookjs/storybook/pull/31778), thanks @shilman!\n- CLI: Do not fail incompatible package check in doctor if only core packages used - [#31886](https://github.com/storybookjs/storybook/pull/31886), thanks @mrginglymus!\n- CLI: Fix `sb` CLI by explicitly exporting `bin/index.cjs` from `storybook` package - [#31922](https://github.com/storybookjs/storybook/pull/31922), thanks @ghengeveld!\n- CLI: Prebundle more in cli-storybook package - [#31746](https://github.com/storybookjs/storybook/pull/31746), thanks @ndelangen!\n- CLI: Show Storybook version in the upgrade command - [#31774](https://github.com/storybookjs/storybook/pull/31774), thanks @yannbf!\n- CSF: Improve controls parameters - [#31745](https://github.com/storybookjs/storybook/pull/31745), thanks @kasperpeulen!\n- CSF: Story ComponentAnnotations['subcomponents'] to correctly use its own type for subcomponents rather than attempt to inherit from the component - [#31723](https://github.com/storybookjs/storybook/pull/31723), thanks @mihkeleidast!\n- Controls: Improve the accessibility of the object control - [#31581](https://github.com/storybookjs/storybook/pull/31581), thanks @Sidnioulz!\n- Core: Cleanup of type following up v9 and small verbatimModuleSyntax type fix - [#31823](https://github.com/storybookjs/storybook/pull/31823), thanks @alcpereira!\n- Core: Disable interactions debugger on composed stories to avoid cross-origin error - [#31685](https://github.com/storybookjs/storybook/pull/31685), thanks @ghengeveld!\n- Core: Enhance package manager install methods to support optional force flag - [#31796](https://github.com/storybookjs/storybook/pull/31796), thanks @valentinpalkovic!\n- Core: Fix FIPS compliance - [#31806](https://github.com/storybookjs/storybook/pull/31806), thanks @JReinhold!\n- Core: Fix addon scrollbars and align scrollbar colors with toolbars - [#31844](https://github.com/storybookjs/storybook/pull/31844), thanks @Sidnioulz!\n- Core: Fix aria-controls attribute on sidebar nodes to include all children - [#31491](https://github.com/storybookjs/storybook/pull/31491), thanks @candrepa1!\n- Core: Fix cyclical dependency in core addons - [#31750](https://github.com/storybookjs/storybook/pull/31750), thanks @JReinhold!\n- Core: Fix horizontal scrollbar covering part of the toolbar - [#31704](https://github.com/storybookjs/storybook/pull/31704), thanks @Sidnioulz!\n- Core: Fix issue where collapsed test controls can be tabbed into - [#31921](https://github.com/storybookjs/storybook/pull/31921), thanks @zenocross!\n- Core: Gracefully handle disallowed cross-origin clipboard access - [#31834](https://github.com/storybookjs/storybook/pull/31834), thanks @ghengeveld!\n- Core: Restore original clipboard after invoking `userEvent.setup()` - [#31730](https://github.com/storybookjs/storybook/pull/31730), thanks @ghengeveld!\n- Core: Serve dynamic favicon based on testing module status - [#31763](https://github.com/storybookjs/storybook/pull/31763), thanks @ghengeveld!\n- Core: Support array-based catch-all Next.js route segments in AppRouterProvider - [#31524](https://github.com/storybookjs/storybook/pull/31524), thanks @yatishgoel!\n- Core: Support container queries in addon panels - [#23261](https://github.com/storybookjs/storybook/pull/23261), thanks @neil-morrison44!\n- Core: Various fixes - [#31870](https://github.com/storybookjs/storybook/pull/31870), thanks @ghengeveld!\n- Deps: Extend `vite` peerDependencies range to include `7.0.0` - [#31859](https://github.com/storybookjs/storybook/pull/31859), thanks @ghengeveld!\n- Deps: Update vite-plugin-babel to 1.3.2 to fix vite 7.0.0 peerDependency issue - [#31888](https://github.com/storybookjs/storybook/pull/31888), thanks @ghengeveld!\n- Docs: Prevent JSON tree control from swallowing keyboard events when not in focus - [#31841](https://github.com/storybookjs/storybook/pull/31841), thanks @takashi-kasajima!\n- Docs: Update `react-element-to-jsx-string` - [#31170](https://github.com/storybookjs/storybook/pull/31170), thanks @7rulnik!\n- Ember: Allow ember v5 as peer deps - [#25893](https://github.com/storybookjs/storybook/pull/25893), thanks @gossi!\n- Next.js-Vite: Support Next.js v15.4 - [#31828](https://github.com/storybookjs/storybook/pull/31828), thanks @valentinpalkovic!\n- Next.js: Add webpack alias to resolve Next.js package conflicts - [#31755](https://github.com/storybookjs/storybook/pull/31755), thanks @valentinpalkovic!\n- Next.js: Enhance Vite configuration with styled-jsx aliasing - [#31757](https://github.com/storybookjs/storybook/pull/31757), thanks @valentinpalkovic!\n- Next.js: upgrade sass-loader to 16.0.5 - [#31855](https://github.com/storybookjs/storybook/pull/31855), thanks @terrymun!\n- NextJs-Vite: Enable next/font loading when using next-vite - [#31906](https://github.com/storybookjs/storybook/pull/31906), thanks @k35o!\n- Nextjs-Vite: Use tsconfig paths plugin - [#31764](https://github.com/storybookjs/storybook/pull/31764), thanks @kasperpeulen!\n- Portable stories: Fix playwright CT to allow functions to be passed as props - [#31335](https://github.com/storybookjs/storybook/pull/31335), thanks @adamscybot!\n- React Native Web: Fix shift spread operator in react-native-web-vite presets - [#31804](https://github.com/storybookjs/storybook/pull/31804), thanks @xlecunff-pass!\n- React Native: Fix window event listeners that dont exist on rn - [#31780](https://github.com/storybookjs/storybook/pull/31780), thanks @dannyhw!\n- React: Bump @joshwooding/vite-plugin-react-docgen-typescript to 0.6.1 - [#31899](https://github.com/storybookjs/storybook/pull/31899), thanks @mrginglymus!\n- Telemetry: Fix prompting without checking isTTY - [#31781](https://github.com/storybookjs/storybook/pull/31781), thanks @Synar!\n- UI: Apply user updates for mobile navigation accessibility - [#31401](https://github.com/storybookjs/storybook/pull/31401), thanks @yatishgoel!\n- UI: Hide keyboard shortcuts entry from menu when shortcuts are disabled - [#23411](https://github.com/storybookjs/storybook/pull/23411), thanks @Spielboerg!\n- UI: Set color scheme to sync scrollbar color with user-selected theme - [#28666](https://github.com/storybookjs/storybook/pull/28666), thanks @elisezhg!\n- UI: Visual focus indicators (VFIs) aren't visible in high contrast mode (rebase) - [#31848](https://github.com/storybookjs/storybook/pull/31848), thanks @Sidnioulz!\n- Vite: Remove addon-themes and theming from optimized deps list - [#31833](https://github.com/storybookjs/storybook/pull/31833), thanks @ghengeveld!"}} +{"version":"9.1.0-alpha.8","info":{"plain":"- Addon Vitest: Fix support for plain `stories.tsx` files - [#32041](https://github.com/storybookjs/storybook/pull/32041), thanks @ghengeveld!\n- Angular: Prevent directory import in Angular builders - [#32012](https://github.com/storybookjs/storybook/pull/32012), thanks @ghengeveld!\n- Automigration: Fail with non-zero exit code on migration failure - [#31923](https://github.com/storybookjs/storybook/pull/31923), thanks @mrginglymus!\n- Builder-Vite: Fix logic related to setting allowedHosts when IP address used - [#31472](https://github.com/storybookjs/storybook/pull/31472), thanks @JSMike!\n- Onboarding: Intent survey - [#31944](https://github.com/storybookjs/storybook/pull/31944), thanks @ghengeveld!\n- UI: Fix interaction step collapse icon - [#31853](https://github.com/storybookjs/storybook/pull/31853), thanks @AvitalHass!\n- UI: Fix text color for failing stories in sidebar - [#32042](https://github.com/storybookjs/storybook/pull/32042), thanks @ghengeveld!"}} From 3886d4ada90d1dc25b1a825dd7046be4e92b9d64 Mon Sep 17 00:00:00 2001 From: jonniebigodes Date: Tue, 8 Jul 2025 14:18:24 +0100 Subject: [PATCH 02/10] Merge pull request #31960 from morfey13/patch-1 Docs: Remove typo in the Svelte Vite framework (cherry picked from commit e8a06be6c46cbd4d5b26c7967dffd62e9404ff6d) --- docs/get-started/frameworks/svelte-vite.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/get-started/frameworks/svelte-vite.mdx b/docs/get-started/frameworks/svelte-vite.mdx index 67d6a15bda2c..18fcc28406c5 100644 --- a/docs/get-started/frameworks/svelte-vite.mdx +++ b/docs/get-started/frameworks/svelte-vite.mdx @@ -95,7 +95,7 @@ Update your Storybook configuration file (i.e., `.storybook/main.js|ts`) to enab ### Configure -By default, the Svelte [addon](https://storybook.js.org/addons/@storybook/addon-svelte-csf) addon offers zero-config support for Storybook's Svelte framework. However, you can extend your Storybook configuration file (i.e., `.storybook/main.js|ts`) and provide additional addon options. Listed below are the available options and examples of how to use them. +By default, the Svelte [addon](https://storybook.js.org/addons/@storybook/addon-svelte-csf) offers zero-config support for Storybook's Svelte framework. However, you can extend your Storybook configuration file (i.e., `.storybook/main.js|ts`) and provide additional addon options. Listed below are the available options and examples of how to use them. {/* prettier-ignore-start */} From 1b67feeeda584fbdc18e107f08e99af1a9d618a7 Mon Sep 17 00:00:00 2001 From: jonniebigodes Date: Mon, 14 Jul 2025 18:18:35 +0100 Subject: [PATCH 03/10] Merge pull request #31838 from storybookjs/fix-vitest-setup-docs Docs: Fix incorrect `vitest.setup.ts` snippet (cherry picked from commit c72e13e1b6176c04bf1978b3fd2cc42199eec253) --- ...> addon-vitest-set-project-annotations-simple.md} | 12 ------------ docs/writing-tests/integrations/vitest-addon.mdx | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) rename docs/_snippets/{portable-stories-vitest-set-project-annotations-simple.md => addon-vitest-set-project-annotations-simple.md} (75%) diff --git a/docs/_snippets/portable-stories-vitest-set-project-annotations-simple.md b/docs/_snippets/addon-vitest-set-project-annotations-simple.md similarity index 75% rename from docs/_snippets/portable-stories-vitest-set-project-annotations-simple.md rename to docs/_snippets/addon-vitest-set-project-annotations-simple.md index 296a97340c26..a38e9ae39c6d 100644 --- a/docs/_snippets/portable-stories-vitest-set-project-annotations-simple.md +++ b/docs/_snippets/addon-vitest-set-project-annotations-simple.md @@ -1,34 +1,22 @@ ```tsx filename=".storybook/vitest.setup.ts" renderer="react" language="ts" -import { beforeAll } from 'vitest'; // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. import { setProjectAnnotations } from '@storybook/your-framework'; import * as previewAnnotations from './preview'; const annotations = setProjectAnnotations([previewAnnotations]); - -// Run Storybook's beforeAll hook -beforeAll(annotations.beforeAll); ``` ```tsx filename=".storybook/vitest.setup.ts" renderer="svelte" language="ts" -import { beforeAll } from 'vitest'; // Replace your-framework with the framework you are using, e.g. sveltekit or svelte-vite import { setProjectAnnotations } from '@storybook/your-framework'; import * as previewAnnotations from './preview'; const annotations = setProjectAnnotations([previewAnnotations]); - -// Run Storybook's beforeAll hook -beforeAll(annotations.beforeAll); ``` ```tsx filename=".storybook/vitest.setup.ts" renderer="vue" language="ts" -import { beforeAll } from 'vitest'; import { setProjectAnnotations } from '@storybook/vue3-vite'; import * as previewAnnotations from './preview'; const annotations = setProjectAnnotations([previewAnnotations]); - -// Run Storybook's beforeAll hook -beforeAll(annotations.beforeAll); ``` diff --git a/docs/writing-tests/integrations/vitest-addon.mdx b/docs/writing-tests/integrations/vitest-addon.mdx index 5d89f12cd5dc..ae7fe4241a7f 100644 --- a/docs/writing-tests/integrations/vitest-addon.mdx +++ b/docs/writing-tests/integrations/vitest-addon.mdx @@ -76,7 +76,7 @@ When the addon is set up automatically, it will create or adjust your Vitest con {/* prettier-ignore-start */} - + {/* prettier-ignore-end */} From a83700d717cdc85a441d9b94c8223b1b8fd6c4d8 Mon Sep 17 00:00:00 2001 From: jonniebigodes Date: Mon, 14 Jul 2025 18:54:45 +0100 Subject: [PATCH 04/10] Merge pull request #32022 from storybookjs/docs-fix-csf-links Docs: Fix broken links in CSF API reference (cherry picked from commit bd03a61573cbb1fc79bda8663ccdbc443ca22eda) --- docs/api/csf/index.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/api/csf/index.mdx b/docs/api/csf/index.mdx index ba5472a0218b..2f6d6942e454 100644 --- a/docs/api/csf/index.mdx +++ b/docs/api/csf/index.mdx @@ -9,7 +9,7 @@ tab: title: CSF 3 --- -Component Story Format (CSF) is the recommended way to [write stories](../writing-stories/index.mdx). It's an [open standard](https://github.com/ComponentDriven/csf) based on ES6 modules that is portable beyond Storybook. +Component Story Format (CSF) is the recommended way to [write stories](../../writing-stories/index.mdx). It's an [open standard](https://github.com/ComponentDriven/csf) based on ES6 modules that is portable beyond Storybook. If you have stories written in the older `storiesOf()` syntax, it was removed in Storybook 8.0 and is no longer maintained. We recommend migrating your stories to CSF. See the [migration guide](../../migration-guide/index.mdx#major-breaking-changes) for more information. @@ -19,7 +19,7 @@ In CSF, stories and component metadata are defined as ES Modules. Every componen ## Default export -The default export defines metadata about your component, including the `component` itself, its `title` (where it will show up in the [navigation UI story hierarchy](../writing-stories/naming-components-and-hierarchy.mdx#sorting-stories)), [decorators](../writing-stories/decorators.mdx), and [parameters](../writing-stories/parameters.mdx). +The default export defines metadata about your component, including the `component` itself, its `title` (where it will show up in the [navigation UI story hierarchy](../../writing-stories/naming-components-and-hierarchy.mdx#sorting-stories)), [decorators](../../writing-stories/decorators.mdx), and [parameters](../../writing-stories/parameters.mdx). The `component` field is required and used by addons for automatic prop table generation and display of other component metadata. The `title` field is optional and should be unique (i.e., not re-used across files). @@ -29,7 +29,7 @@ The `component` field is required and used by addons for automatic prop table ge {/* prettier-ignore-end */} -For more examples, see [writing stories](../writing-stories/index.mdx). +For more examples, see [writing stories](../../writing-stories/index.mdx). ## Named story exports @@ -53,7 +53,7 @@ The exported identifiers will be converted to "start case" using Lodash's [start We recommend that all export names to start with a capital letter. -Story objects can be annotated with a few different fields to define story-level [decorators](../writing-stories/decorators.mdx) and [parameters](../writing-stories/parameters.mdx), and also to define the `name` of the story. +Story objects can be annotated with a few different fields to define story-level [decorators](../../writing-stories/decorators.mdx) and [parameters](../../writing-stories/parameters.mdx), and also to define the `name` of the story. Storybook's `name` configuration element is helpful in specific circumstances. Common use cases are names with special characters or Javascript restricted words. If not specified, Storybook defaults to the named export. @@ -67,7 +67,7 @@ Storybook's `name` configuration element is helpful in specific circumstances. C Starting in SB 6.0, stories accept named inputs called Args. Args are dynamic data that are provided (and possibly updated by) Storybook and its addons. -Consider Storybook’s ["Button" example](../writing-stories/index.mdx#defining-stories) of a text button that logs its click events: +Consider Storybook’s ["Button" example](../../writing-stories/index.mdx#defining-stories) of a text button that logs its click events: {/* prettier-ignore-start */} @@ -93,7 +93,7 @@ Or even more simply: Not only are these versions shorter and more accessible to write than their no-args counterparts, but they are also more portable since the code doesn't depend on the actions addon specifically. -For more information on setting up [Docs](../writing-docs/index.mdx) and [Actions](../essentials/actions.mdx), see their respective documentation. +For more information on setting up [Docs](../../writing-docs/index.mdx) and [Actions](../../essentials/actions.mdx), see their respective documentation. ## Play function @@ -281,4 +281,4 @@ Finally, CSF 3 can automatically generate titles. {/* prettier-ignore-end */} -You can still specify a title like in CSF 2, but if you don't specify one, it can be inferred from the story's path on disk. For more information, see the section on [configuring story loading](../configure/index.mdx#configure-story-loading). +You can still specify a title like in CSF 2, but if you don't specify one, it can be inferred from the story's path on disk. For more information, see the section on [configuring story loading](../../configure/index.mdx#configure-story-loading). From 801da72d372f8a7f98772eaa0ff89afa178a377a Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 15 Jul 2025 10:43:08 +0200 Subject: [PATCH 05/10] Merge pull request #32042 from storybookjs/fix-test-error-text-color UI: Fix text color for failing stories in sidebar (cherry picked from commit edbd7c438c5f8f06ca4dd6f66419047ccc2dd33b) --- code/core/src/manager/utils/status.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/manager/utils/status.tsx b/code/core/src/manager/utils/status.tsx index 421f79720801..35a5a7223226 100644 --- a/code/core/src/manager/utils/status.tsx +++ b/code/core/src/manager/utils/status.tsx @@ -51,7 +51,7 @@ export const statusMapping: Record , - 'brown', + '#D43900', ], }; From f809cc97591de670f951b3d870f021dd7e928ff5 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 15 Jul 2025 10:48:46 +0200 Subject: [PATCH 06/10] Merge pull request #32041 from storybookjs/vitest-transform-plain-stories-tsx Addon Vitest: Fix support for plain `stories.tsx` files (cherry picked from commit 6ac71c29370a084d66066d0873401fe1c2dd1687) --- code/.storybook/main.ts | 5 +++ code/addons/vitest/src/stories.tsx | 20 +++++++++++ .../vitest-plugin/transformer.test.ts | 33 +++++++------------ .../csf-tools/vitest-plugin/transformer.ts | 5 --- 4 files changed, 36 insertions(+), 27 deletions(-) create mode 100644 code/addons/vitest/src/stories.tsx diff --git a/code/.storybook/main.ts b/code/.storybook/main.ts index 93518795c535..ec9d1d9fd85c 100644 --- a/code/.storybook/main.ts +++ b/code/.storybook/main.ts @@ -93,6 +93,11 @@ const config = defineMain({ directory: '../addons/vitest/template/stories', titlePrefix: 'addons/vitest', }, + { + directory: '../addons/vitest/src', + titlePrefix: 'addons/vitest', + files: 'stories.tsx', + }, ], addons: [ '@storybook/addon-themes', diff --git a/code/addons/vitest/src/stories.tsx b/code/addons/vitest/src/stories.tsx new file mode 100644 index 000000000000..ad879b3dea13 --- /dev/null +++ b/code/addons/vitest/src/stories.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { expect } from 'storybook/test'; + +const meta = { + title: 'StoriesTsx', + render: () =>
This is a story coming from a /stories.tsx file detected via custom glob
, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + play: async () => { + expect(true).toBe(true); + }, +}; diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.test.ts b/code/core/src/csf-tools/vitest-plugin/transformer.test.ts index a47eabdd8b3a..7d9d89464aa2 100644 --- a/code/core/src/csf-tools/vitest-plugin/transformer.test.ts +++ b/code/core/src/csf-tools/vitest-plugin/transformer.test.ts @@ -48,17 +48,6 @@ const transform = async ({ }; describe('transformer', () => { - describe('no-op', () => { - it('should return original code if the file is not a story file', async () => { - const code = `console.log('Not a story file');`; - const fileName = 'src/components/Button.js'; - - const result = await transform({ code, fileName }); - - expect(result.code).toMatchInlineSnapshot(`console.log('Not a story file');`); - }); - }); - describe('CSF v1/v2/v3', () => { describe('default exports (meta)', () => { it('should add title to inline default export if not present', async () => { @@ -124,7 +113,7 @@ describe('transformer', () => { component: Button, }; export default meta; - + export const Story = {}; `; @@ -153,9 +142,9 @@ describe('transformer', () => { const meta = { title: 'Button', component: Button, - }; + }; export default meta; - + export const Story = {}; `; @@ -272,7 +261,7 @@ describe('transformer', () => { label: 'Primary Button', }, }; - + export { Primary }; `; @@ -306,7 +295,7 @@ describe('transformer', () => { label: 'Primary Button', }, }; - + export { Primary as PrimaryStory }; `; @@ -340,9 +329,9 @@ describe('transformer', () => { label: 'Primary Button', }, }; - + export const Secondary = {} - + export { Primary }; `; @@ -430,7 +419,7 @@ describe('transformer', () => { const code = ` export default {}; export const Included = { tags: ['include-me'] }; - + export const NotIncluded = {} `; @@ -461,7 +450,7 @@ describe('transformer', () => { const code = ` export default {}; export const Included = {}; - + export const NotIncluded = { tags: ['exclude-me'] } `; @@ -636,7 +625,7 @@ describe('transformer', () => { const code = ` import { config } from '#.storybook/preview'; const meta = config.meta({ component: Button }); - const Primary = meta.story({ + const Primary = meta.story({ args: { label: 'Primary Button', } @@ -672,7 +661,7 @@ describe('transformer', () => { const code = ` import { config } from '#.storybook/preview'; const meta = config.meta({ component: Button }); - const Primary = meta.story({ + const Primary = meta.story({ args: { label: 'Primary Button', } diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.ts b/code/core/src/csf-tools/vitest-plugin/transformer.ts index c9acce129ee4..22745c1537e0 100644 --- a/code/core/src/csf-tools/vitest-plugin/transformer.ts +++ b/code/core/src/csf-tools/vitest-plugin/transformer.ts @@ -46,11 +46,6 @@ export async function vitestTransform({ stories: StoriesEntry[]; previewLevelTags: Tag[]; }): Promise> { - const isStoryFile = /\.stor(y|ies)\./.test(fileName); - if (!isStoryFile) { - return code; - } - const parsed = loadCsf(code, { fileName, transformInlineMeta: true, From 47c7f88b5328d528dacf4e91a5b97f5b636f108f Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 14 Jul 2025 18:32:02 +0200 Subject: [PATCH 07/10] Merge pull request #32036 from storybookjs/rollup-resolution CI: Add resolution to pin Rollup to v4.44.2 in sandboxes (cherry picked from commit 7e625fb68fa38e5d1d23cc54a55a3a3d0e9a1950) --- scripts/utils/yarn.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/utils/yarn.ts b/scripts/utils/yarn.ts index a490397961a5..b3496296d364 100644 --- a/scripts/utils/yarn.ts +++ b/scripts/utils/yarn.ts @@ -34,6 +34,7 @@ export const addPackageResolutions = async ({ cwd, dryRun }: YarnOptions) => { playwright: '1.52.0', 'playwright-core': '1.52.0', '@playwright/test': '1.52.0', + rollup: '4.44.2', }; await writeJSON(packageJsonPath, packageJson, { spaces: 2 }); }; @@ -95,6 +96,7 @@ export const addWorkaroundResolutions = async ({ '@testing-library/jest-dom': '^6.6.3', '@testing-library/user-event': '^14.5.2', typescript: '~5.7.3', + rollup: '4.44.2', }; await writeJSON(packageJsonPath, packageJson, { spaces: 2 }); From c443c1480b7bc5be0e8066af028641a8ab2ba274 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Thu, 10 Jul 2025 11:22:54 +0200 Subject: [PATCH 08/10] Merge pull request #32004 from storybookjs/swc-es2023 CI: Remove `@swc/core` package resolution to resolve `es2023` compatibility (cherry picked from commit 38bd82485c366e5f12dc803583ec3d4b47700392) --- scripts/utils/yarn.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/utils/yarn.ts b/scripts/utils/yarn.ts index b3496296d364..8920d6aa5aba 100644 --- a/scripts/utils/yarn.ts +++ b/scripts/utils/yarn.ts @@ -30,7 +30,6 @@ export const addPackageResolutions = async ({ cwd, dryRun }: YarnOptions) => { ...packageJson.resolutions, ...storybookVersions, // this is for our CI test, ensure we use the same version as docker image, it should match version specified in `./code/package.json` and `.circleci/config.yml` - '@swc/core': '1.5.7', playwright: '1.52.0', 'playwright-core': '1.52.0', '@playwright/test': '1.52.0', From 1f16ed3fba7e97428238c5f87bfe0d43b0968332 Mon Sep 17 00:00:00 2001 From: storybook-bot <32066757+storybook-bot@users.noreply.github.com> Date: Tue, 15 Jul 2025 10:26:57 +0000 Subject: [PATCH 09/10] Write changelog for 9.0.17 [skip ci] --- CHANGELOG.md | 6 ++++++ code/package.json | 3 ++- docs/versions/latest.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e65068572f49..5a8cd4bdfc18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 9.0.17 + +- Addon Vitest: Fix support for plain `stories.tsx` files - [#32041](https://github.com/storybookjs/storybook/pull/32041), thanks @ghengeveld! +- Onboarding: Intent survey - [#31944](https://github.com/storybookjs/storybook/pull/31944), thanks @ghengeveld! +- UI: Fix text color for failing stories in sidebar - [#32042](https://github.com/storybookjs/storybook/pull/32042), thanks @ghengeveld! + ## 9.0.16 - Automigration: Fail with non-zero exit code on migration failure - [#31923](https://github.com/storybookjs/storybook/pull/31923), thanks @mrginglymus! diff --git a/code/package.json b/code/package.json index 0c97405d7d70..21d15e6a5355 100644 --- a/code/package.json +++ b/code/package.json @@ -283,5 +283,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "9.0.17" } diff --git a/docs/versions/latest.json b/docs/versions/latest.json index 730937db92ee..fb5bbdacefa6 100644 --- a/docs/versions/latest.json +++ b/docs/versions/latest.json @@ -1 +1 @@ -{"version":"9.0.16","info":{"plain":"- Automigration: Fail with non-zero exit code on migration failure - [#31923](https://github.com/storybookjs/storybook/pull/31923), thanks @mrginglymus!\n- CLI: Fix `sb` CLI by explicitly exporting `bin/index.cjs` from `storybook` package - [#31922](https://github.com/storybookjs/storybook/pull/31922), thanks @ghengeveld!\n- Core: Fix issue where collapsed test controls can be tabbed into - [#31921](https://github.com/storybookjs/storybook/pull/31921), thanks @zenocross!\n- Core: Various fixes - [#31870](https://github.com/storybookjs/storybook/pull/31870), thanks @ghengeveld!\n- Docs: Prevent JSON tree control from swallowing keyboard events when not in focus - [#31841](https://github.com/storybookjs/storybook/pull/31841), thanks @takashi-kasajima!\n- Ember: Allow ember v5 as peer deps - [#25893](https://github.com/storybookjs/storybook/pull/25893), thanks @gossi!\n- Next.js: upgrade sass-loader to 16.0.5 - [#31855](https://github.com/storybookjs/storybook/pull/31855), thanks @terrymun!\n- NextJs-Vite: Enable next/font loading when using next-vite - [#31906](https://github.com/storybookjs/storybook/pull/31906), thanks @k35o!\n- Portable stories: Fix playwright CT to allow functions to be passed as props - [#31335](https://github.com/storybookjs/storybook/pull/31335), thanks @adamscybot!\n- UI: Set color scheme to sync scrollbar color with user-selected theme - [#28666](https://github.com/storybookjs/storybook/pull/28666), thanks @elisezhg!"}} +{"version":"9.0.17","info":{"plain":"- Addon Vitest: Fix support for plain `stories.tsx` files - [#32041](https://github.com/storybookjs/storybook/pull/32041), thanks @ghengeveld!\n- Onboarding: Intent survey - [#31944](https://github.com/storybookjs/storybook/pull/31944), thanks @ghengeveld!\n- UI: Fix text color for failing stories in sidebar - [#32042](https://github.com/storybookjs/storybook/pull/32042), thanks @ghengeveld!"}} From a0400bf01c70bee4a11de1f25c738f68a2758fda Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Wed, 9 Jul 2025 08:17:04 +0200 Subject: [PATCH 10/10] Merge pull request #31944 from storybookjs/onboarding-intent-survey Onboarding: Intent survey (cherry picked from commit 9e65cc0c6eb262bd0b350511846ae1ca656555f7) --- code/addons/onboarding/src/Onboarding.tsx | 32 ++- code/addons/onboarding/src/constants.ts | 3 +- .../IntentSurvey/IntentSurvey.stories.tsx | 65 +++++ .../features/IntentSurvey/IntentSurvey.tsx | 252 ++++++++++++++++++ code/addons/onboarding/src/preset.ts | 10 +- .../src/manager/PseudoStateTool.tsx | 4 +- .../src/components/TestProviderRender.tsx | 8 +- .../components/Checkbox/Checkbox.stories.tsx | 80 ------ .../components/Form/Checkbox.stories.tsx | 74 +++++ .../{Checkbox => Form}/Checkbox.tsx | 3 +- .../components/Form/Field.stories.tsx | 47 ++++ .../{form/field/field.tsx => Form/Field.tsx} | 3 +- .../{form/index.tsx => Form/Form.tsx} | 10 +- .../components/Form/Input.stories.tsx | 14 + .../src/components/components/Form/Input.tsx | 42 +++ .../components/Form/Radio.stories.tsx | 69 +++++ .../src/components/components/Form/Radio.tsx | 39 +++ .../components/Form/Select.stories.tsx | 29 ++ .../src/components/components/Form/Select.tsx | 139 ++++++++++ .../components/Form/Textarea.stories.tsx | 14 + .../components/components/Form/Textarea.tsx | 63 +++++ .../src/components/components/Form/styles.ts | 121 +++++++++ .../components/Modal/Modal.styled.tsx | 2 +- .../components/form/form.stories.tsx | 124 --------- .../components/form/input/input.tsx | 219 --------------- code/core/src/components/index.ts | 3 +- code/core/src/manager/globals/exports.ts | 1 - code/core/src/telemetry/types.ts | 4 +- code/e2e-tests/addon-onboarding.spec.ts | 5 + 29 files changed, 1023 insertions(+), 456 deletions(-) create mode 100644 code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.stories.tsx create mode 100644 code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.tsx delete mode 100644 code/core/src/components/components/Checkbox/Checkbox.stories.tsx create mode 100644 code/core/src/components/components/Form/Checkbox.stories.tsx rename code/core/src/components/components/{Checkbox => Form}/Checkbox.tsx (96%) create mode 100644 code/core/src/components/components/Form/Field.stories.tsx rename code/core/src/components/components/{form/field/field.tsx => Form/Field.tsx} (92%) rename code/core/src/components/components/{form/index.tsx => Form/Form.tsx} (52%) create mode 100644 code/core/src/components/components/Form/Input.stories.tsx create mode 100644 code/core/src/components/components/Form/Input.tsx create mode 100644 code/core/src/components/components/Form/Radio.stories.tsx create mode 100644 code/core/src/components/components/Form/Radio.tsx create mode 100644 code/core/src/components/components/Form/Select.stories.tsx create mode 100644 code/core/src/components/components/Form/Select.tsx create mode 100644 code/core/src/components/components/Form/Textarea.stories.tsx create mode 100644 code/core/src/components/components/Form/Textarea.tsx create mode 100644 code/core/src/components/components/Form/styles.ts delete mode 100644 code/core/src/components/components/form/form.stories.tsx delete mode 100644 code/core/src/components/components/form/input/input.tsx diff --git a/code/addons/onboarding/src/Onboarding.tsx b/code/addons/onboarding/src/Onboarding.tsx index 72538308a711..27fee09f2333 100644 --- a/code/addons/onboarding/src/Onboarding.tsx +++ b/code/addons/onboarding/src/Onboarding.tsx @@ -13,6 +13,7 @@ import { HighlightElement } from './components/HighlightElement/HighlightElement import type { STORYBOOK_ADDON_ONBOARDING_STEPS } from './constants'; import { STORYBOOK_ADDON_ONBOARDING_CHANNEL } from './constants'; import { GuidedTour } from './features/GuidedTour/GuidedTour'; +import { IntentSurvey } from './features/IntentSurvey/IntentSurvey'; import { SplashScreen } from './features/SplashScreen/SplashScreen'; const SpanHighlight = styled.span(({ theme }) => ({ @@ -106,14 +107,21 @@ export default function Onboarding({ api }: { api: API }) { setEnabled(false); }, [api, setEnabled]); - const completeOnboarding = useCallback(() => { - api.emit(STORYBOOK_ADDON_ONBOARDING_CHANNEL, { - step: '6:FinishedOnboarding' satisfies StepKey, - type: 'telemetry', - }); - selectStory('configure-your-project--docs'); - disableOnboarding(); - }, [api, selectStory, disableOnboarding]); + const completeOnboarding = useCallback( + (answers: Record) => { + api.emit(STORYBOOK_ADDON_ONBOARDING_CHANNEL, { + step: '7:FinishedOnboarding' satisfies StepKey, + type: 'telemetry', + }); + api.emit(STORYBOOK_ADDON_ONBOARDING_CHANNEL, { + answers, + type: 'survey', + }); + selectStory('configure-your-project--docs'); + disableOnboarding(); + }, + [api, selectStory, disableOnboarding] + ); useEffect(() => { api.setQueryParams({ onboarding: 'true' }); @@ -136,7 +144,9 @@ export default function Onboarding({ api }: { api: API }) { useEffect(() => { setStep((current) => { - if (['1:Intro', '5:StoryCreated', '6:FinishedOnboarding'].includes(current)) { + if ( + ['1:Intro', '5:StoryCreated', '6:IntentSurvey', '7:FinishedOnboarding'].includes(current) + ) { return current; } @@ -272,12 +282,14 @@ export default function Onboarding({ api }: { api: API }) { {showConfetti && } {step === '1:Intro' ? ( setStep('2:Controls')} /> + ) : step === '6:IntentSurvey' ? ( + ) : ( setStep('6:IntentSurvey')} /> )} diff --git a/code/addons/onboarding/src/constants.ts b/code/addons/onboarding/src/constants.ts index fa3cca4032e8..abf5ea631579 100644 --- a/code/addons/onboarding/src/constants.ts +++ b/code/addons/onboarding/src/constants.ts @@ -6,5 +6,6 @@ export const STORYBOOK_ADDON_ONBOARDING_STEPS = [ '3:SaveFromControls', '4:CreateStory', '5:StoryCreated', - '6:FinishedOnboarding', + '6:IntentSurvey', + '7:FinishedOnboarding', ] as const; diff --git a/code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.stories.tsx b/code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.stories.tsx new file mode 100644 index 000000000000..3c98af8ad38c --- /dev/null +++ b/code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.stories.tsx @@ -0,0 +1,65 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { expect, fn, screen, userEvent, waitFor } from 'storybook/test'; + +import { IntentSurvey } from './IntentSurvey'; + +const meta = { + component: IntentSurvey, + args: { + onComplete: fn(), + onDismiss: fn(), + }, +} as Meta; + +type Story = StoryObj; +export default meta; + +export const Default: Story = {}; + +export const Submitting: Story = { + play: async ({ args }) => { + const button = await screen.findByRole('button', { name: 'Submit' }); + await expect(button).toBeDisabled(); + + await userEvent.click(await screen.findByText('Design system')); + await expect(button).toBeDisabled(); + + await userEvent.click(await screen.findByText('Functional testing')); + await userEvent.click(await screen.findByText('Accessibility testing')); + await userEvent.click(await screen.findByText('Visual testing')); + await expect(button).toBeDisabled(); + + await userEvent.selectOptions(screen.getByRole('combobox'), ['We use it at work']); + await expect(button).not.toBeDisabled(); + + await userEvent.click(button); + + await waitFor(async () => { + await expect(button).toBeDisabled(); + await expect(args.onComplete).toHaveBeenCalledWith({ + building: { + 'application-ui': false, + 'design-system': true, + }, + interest: { + 'accessibility-testing': true, + 'ai-augmented-development': false, + 'design-handoff': false, + 'functional-testing': true, + 'team-collaboration': false, + 'ui-documentation': false, + 'visual-testing': true, + }, + referrer: { + 'ai-agent': false, + 'via-friend-or-colleague': false, + 'via-social-media': false, + 'we-use-it-at-work': true, + 'web-search': false, + youtube: false, + }, + }); + }); + }, +}; diff --git a/code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.tsx b/code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.tsx new file mode 100644 index 000000000000..b9b703831ca1 --- /dev/null +++ b/code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.tsx @@ -0,0 +1,252 @@ +import React, { useState } from 'react'; + +import { Button, Form, Modal } from 'storybook/internal/components'; + +import { styled } from 'storybook/theming'; + +import { isChromatic } from '../../../../../.storybook/isChromatic'; + +interface BaseField { + label: string; + options: Record; + required?: boolean; +} + +interface CheckboxField extends BaseField { + type: 'checkbox'; + values: Record; +} + +interface SelectField extends BaseField { + type: 'select'; + values: Record; +} + +type FormFields = { + building: CheckboxField; + interest: CheckboxField; + referrer: SelectField; +}; + +const Content = styled(Modal.Content)(({ theme }) => ({ + fontSize: theme.typography.size.s2, + color: theme.color.defaultText, + gap: 8, +})); + +const Row = styled.div({ + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gap: 14, + marginBottom: 8, +}); + +const Question = styled.div(({ theme }) => ({ + marginTop: 8, + marginBottom: 2, + fontWeight: theme.typography.weight.bold, +})); + +const Label = styled.label({ + display: 'flex', + gap: 8, + + '&:has(input[type="checkbox"]:not(:disabled), input[type="radio"]:not(:disabled))': { + cursor: 'pointer', + }, +}); + +const Actions = styled(Modal.Actions)({ + marginTop: 8, +}); + +const Checkbox = styled(Form.Checkbox)({ + margin: 2, +}); + +export const IntentSurvey = ({ + onComplete, + onDismiss, +}: { + onComplete: (formData: Record>) => void; + onDismiss: () => void; +}) => { + const [isSubmitting, setIsSubmitting] = useState(false); + + const [formFields, setFormFields] = useState({ + building: { + label: 'What are you building?', + type: 'checkbox', + required: true, + options: shuffleObject({ + 'design-system': { label: 'Design system' }, + 'application-ui': { label: 'Application UI' }, + }), + values: { + 'design-system': false, + 'application-ui': false, + }, + }, + interest: { + label: 'Which of these are you interested in?', + type: 'checkbox', + required: true, + options: shuffleObject({ + 'ui-documentation': { label: 'Generating UI docs' }, + 'functional-testing': { label: 'Functional testing' }, + 'accessibility-testing': { label: 'Accessibility testing' }, + 'visual-testing': { label: 'Visual testing' }, + 'ai-augmented-development': { label: 'Building UI with AI' }, + 'team-collaboration': { label: 'Team collaboration' }, + 'design-handoff': { label: 'Design handoff' }, + }), + values: { + 'ui-documentation': false, + 'functional-testing': false, + 'accessibility-testing': false, + 'visual-testing': false, + 'ai-augmented-development': false, + 'team-collaboration': false, + 'design-handoff': false, + }, + }, + referrer: { + label: 'How did you learn about Storybook?', + type: 'select', + required: true, + options: shuffleObject({ + 'we-use-it-at-work': { label: 'We use it at work' }, + 'via-friend-or-colleague': { label: 'Via friend or colleague' }, + 'via-social-media': { label: 'Via social media' }, + youtube: { label: 'YouTube' }, + 'web-search': { label: 'Web Search' }, + 'ai-agent': { label: 'AI Agent (e.g. ChatGPT)' }, + }), + values: { + 'we-use-it-at-work': false, + 'via-friend-or-colleague': false, + 'via-social-media': false, + youtube: false, + 'web-search': false, + 'ai-agent': false, + }, + }, + }); + + const updateFormData = (key: keyof FormFields, optionOrValue: string, value?: boolean) => { + const field = formFields[key]; + setFormFields((fields) => { + if (field.type === 'checkbox') { + const values = { ...field.values, [optionOrValue]: !!value }; + return { ...fields, [key]: { ...field, values } }; + } + if (field.type === 'select') { + const values = Object.fromEntries( + Object.entries(field.values).map(([opt]) => [opt, opt === optionOrValue]) + ); + return { ...fields, [key]: { ...field, values } }; + } + return fields; + }); + }; + + const isValid = Object.values(formFields).every((field) => { + if (!field.required) { + return true; + } + // Check if at least one option is selected (true) + return Object.values(field.values).some((value) => value === true); + }); + + const onSubmitForm = (e: React.FormEvent) => { + if (!isValid) { + return; + } + e.preventDefault(); + setIsSubmitting(true); + onComplete( + Object.fromEntries(Object.entries(formFields).map(([key, field]) => [key, field.values])) + ); + }; + + return ( + +
+ + + Help improve Storybook + + + {(Object.keys(formFields) as Array).map((key) => { + const field = formFields[key]; + return ( + + {field.label} + {field.type === 'checkbox' && ( + + {Object.entries(field.options).map(([opt, option]) => { + const id = `${key}:${opt}`; + return ( +
+ +
+ ); + })} +
+ )} + {field.type === 'select' && ( + isSelected)?.[0] || '' + } + required={field.required} + disabled={isSubmitting} + onChange={(e) => updateFormData(key, e.target.value)} + > + + {Object.entries(field.options).map(([opt, option]) => ( + + ))} + + )} +
+ ); + })} + + + + +
+
+
+ ); +}; + +function shuffle(array: T[]): T[] { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array; +} + +function shuffleObject(object: T): T { + return isChromatic() ? object : (Object.fromEntries(shuffle(Object.entries(object))) as T); +} diff --git a/code/addons/onboarding/src/preset.ts b/code/addons/onboarding/src/preset.ts index d1ec7a5e2586..795d3229b5a0 100644 --- a/code/addons/onboarding/src/preset.ts +++ b/code/addons/onboarding/src/preset.ts @@ -7,7 +7,7 @@ import type { CoreConfig, Options } from 'storybook/internal/types'; import { STORYBOOK_ADDON_ONBOARDING_CHANNEL } from './constants'; type Event = { - type: 'telemetry'; + type: 'telemetry' | 'survey'; step: string; payload?: any; }; @@ -24,11 +24,9 @@ export const experimental_serverChannel = async (channel: Channel, options: Opti channel.on(STORYBOOK_ADDON_ONBOARDING_CHANNEL, ({ type, ...event }: Event) => { if (type === 'telemetry') { - // @ts-expect-error (bad string) - telemetry('addon-onboarding', { - ...event, - addonVersion, - }); + telemetry('addon-onboarding', { ...event, addonVersion }); + } else if (type === 'survey') { + telemetry('onboarding-survey', { ...event, addonVersion }); } }); } diff --git a/code/addons/pseudo-states/src/manager/PseudoStateTool.tsx b/code/addons/pseudo-states/src/manager/PseudoStateTool.tsx index 780d6a8f3494..3ce72f4a6df7 100644 --- a/code/addons/pseudo-states/src/manager/PseudoStateTool.tsx +++ b/code/addons/pseudo-states/src/manager/PseudoStateTool.tsx @@ -1,6 +1,6 @@ import React, { type ComponentProps, useCallback } from 'react'; -import { Checkbox, IconButton, TooltipLinkList, WithTooltip } from 'storybook/internal/components'; +import { Form, IconButton, TooltipLinkList, WithTooltip } from 'storybook/internal/components'; import { color, styled } from 'storybook/internal/theming'; import { ButtonIcon, RefreshIcon } from '@storybook/icons'; @@ -50,7 +50,7 @@ export const PseudoStateTool = () => { return { id: option, title: :{PSEUDO_STATES[option]}, - input: , + input: , active, }; }); diff --git a/code/addons/vitest/src/components/TestProviderRender.tsx b/code/addons/vitest/src/components/TestProviderRender.tsx index 891428abe139..1c61ff72b65f 100644 --- a/code/addons/vitest/src/components/TestProviderRender.tsx +++ b/code/addons/vitest/src/components/TestProviderRender.tsx @@ -1,7 +1,7 @@ import React, { type ComponentProps, type FC } from 'react'; import { - Checkbox, + Form, IconButton, ListItem, ProgressSpinner, @@ -264,7 +264,7 @@ export const TestProviderRender: FC = ({ } + icon={entry ? null : } /> = ({ as="label" title={watching ? Coverage (unavailable) : 'Coverage'} icon={ - @@ -386,7 +386,7 @@ export const TestProviderRender: FC = ({ title="Accessibility" icon={ entry ? null : ( - diff --git a/code/core/src/components/components/Checkbox/Checkbox.stories.tsx b/code/core/src/components/components/Checkbox/Checkbox.stories.tsx deleted file mode 100644 index e8859d4797d4..000000000000 --- a/code/core/src/components/components/Checkbox/Checkbox.stories.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; - -import { Checkbox } from './Checkbox'; - -const meta = { - component: Checkbox, -} satisfies Meta; - -export default meta; - -type Story = StoryObj; - -export const All: Story = { - render: () => ( -
- - - - - - - - (custom) - - - - - - - - (native) -
- ), - afterEach: async ({ canvasElement }) => { - canvasElement.querySelectorAll('[data-indeterminate]').forEach((checkbox) => { - checkbox.indeterminate = true; - }); - }, - parameters: { - pseudo: { - focus: '[data-focus]', - }, - }, -}; - -export const Default: Story = {}; - -export const Checked: Story = { - args: { - defaultChecked: true, - }, -}; - -export const Indeterminate: Story = { - afterEach: async ({ canvasElement }) => { - canvasElement.getElementsByTagName('input')[0].indeterminate = true; - }, -}; - -export const Disabled: Story = { - args: { - disabled: true, - }, -}; - -export const DisabledChecked: Story = { - args: { - defaultChecked: true, - disabled: true, - }, -}; - -export const DisabledIndeterminate: Story = { - args: { - disabled: true, - }, - afterEach: async ({ canvasElement }) => { - canvasElement.getElementsByTagName('input')[0].indeterminate = true; - }, -}; diff --git a/code/core/src/components/components/Form/Checkbox.stories.tsx b/code/core/src/components/components/Form/Checkbox.stories.tsx new file mode 100644 index 000000000000..00458f58b8f6 --- /dev/null +++ b/code/core/src/components/components/Form/Checkbox.stories.tsx @@ -0,0 +1,74 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { Checkbox as Component } from './Checkbox'; + +const meta = { + component: Component, + title: 'Form/Checkbox', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Checkbox: Story = { + render: () => ( +
+ + Custom: + Native: + + Checked, focus: + +
+ +
+ + Checked: + +
+ +
+ + Indeterminate: + +
+ +
+ + Default: + +
+ +
+ + Disabled, checked: + +
+ +
+ + Disabled, indeterminate: + +
+ +
+ + Disabled: + +
+ +
+
+ ), + afterEach: async ({ canvasElement }) => { + canvasElement.querySelectorAll('[data-indeterminate]').forEach((checkbox) => { + checkbox.indeterminate = true; + }); + }, + parameters: { + pseudo: { + focus: '[data-focus]', + }, + }, +}; diff --git a/code/core/src/components/components/Checkbox/Checkbox.tsx b/code/core/src/components/components/Form/Checkbox.tsx similarity index 96% rename from code/core/src/components/components/Checkbox/Checkbox.tsx rename to code/core/src/components/components/Form/Checkbox.tsx index 654c47d943b4..04e8258d35ab 100644 --- a/code/core/src/components/components/Checkbox/Checkbox.tsx +++ b/code/core/src/components/components/Form/Checkbox.tsx @@ -8,6 +8,7 @@ const Input = styled.input({ placeContent: 'center', width: 14, height: 14, + flexShrink: 0, margin: 0, border: `1px solid ${color.border}`, borderRadius: 2, @@ -38,7 +39,7 @@ const Input = styled.input({ height: 2, background: 'white', }, - '&:enabled:focus': { + '&:enabled:focus-visible': { outline: `1px solid ${color.secondary}`, outlineOffset: 1, }, diff --git a/code/core/src/components/components/Form/Field.stories.tsx b/code/core/src/components/components/Form/Field.stories.tsx new file mode 100644 index 000000000000..c0367c635b4b --- /dev/null +++ b/code/core/src/components/components/Form/Field.stories.tsx @@ -0,0 +1,47 @@ +import React from 'react'; + +import { fn } from 'storybook/test'; +import { styled } from 'storybook/theming'; + +import { Field as FieldComponent } from './Field'; +import { Input as InputComponent } from './Input'; +import { Select as SelectComponent } from './Select'; +import { Textarea as TextareaComponent } from './Textarea'; + +const Flexed = styled(FieldComponent)({ display: 'flex' }); + +export default { + title: 'Form/Field', + component: FieldComponent, + args: { + label: 'Label', + }, +}; + +export const Input = { + render: (args: any) => ( + + + + ), +}; + +export const Select = { + render: (args: any) => ( + + + + + + + + ), +}; + +export const Textarea = { + render: (args: any) => ( + + + + ), +}; diff --git a/code/core/src/components/components/form/field/field.tsx b/code/core/src/components/components/Form/Field.tsx similarity index 92% rename from code/core/src/components/components/form/field/field.tsx rename to code/core/src/components/components/Form/Field.tsx index 77f24a9a65c5..9e311d4006c7 100644 --- a/code/core/src/components/components/form/field/field.tsx +++ b/code/core/src/components/components/Form/Field.tsx @@ -1,5 +1,4 @@ -import type { ReactNode } from 'react'; -import React from 'react'; +import React, { type ReactNode } from 'react'; import { styled } from 'storybook/theming'; diff --git a/code/core/src/components/components/form/index.tsx b/code/core/src/components/components/Form/Form.tsx similarity index 52% rename from code/core/src/components/components/form/index.tsx rename to code/core/src/components/components/Form/Form.tsx index 0d1b141c1a61..898d28e908dc 100644 --- a/code/core/src/components/components/form/index.tsx +++ b/code/core/src/components/components/Form/Form.tsx @@ -1,8 +1,12 @@ import { styled } from 'storybook/theming'; import { Button } from '../Button/Button'; -import { Field } from './field/field'; -import { Input, Select, Textarea } from './input/input'; +import { Checkbox } from './Checkbox'; +import { Field } from './Field'; +import { Input } from './Input'; +import { Radio } from './Radio'; +import { Select } from './Select'; +import { Textarea } from './Textarea'; export const Form = Object.assign( styled.form({ @@ -15,5 +19,7 @@ export const Form = Object.assign( Select, Textarea, Button, + Checkbox, + Radio, } ); diff --git a/code/core/src/components/components/Form/Input.stories.tsx b/code/core/src/components/components/Form/Input.stories.tsx new file mode 100644 index 000000000000..b8fad2b7f506 --- /dev/null +++ b/code/core/src/components/components/Form/Input.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { Input as Component } from './Input'; + +const meta = { + title: 'Form/Input', + component: Component, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Input: Story = {}; diff --git a/code/core/src/components/components/Form/Input.tsx b/code/core/src/components/components/Form/Input.tsx new file mode 100644 index 000000000000..219ef0abeff2 --- /dev/null +++ b/code/core/src/components/components/Form/Input.tsx @@ -0,0 +1,42 @@ +import React, { type HTMLProps } from 'react'; +import { forwardRef } from 'react'; + +import { styled } from 'storybook/theming'; + +import { + type Alignments, + type Sizes, + type ValidationStates, + alignment, + sizes, + styles, + validation, +} from './styles'; + +type InputProps = Omit< + HTMLProps, + keyof { + size?: Sizes; + align?: Alignments; + valid?: ValidationStates; + height?: number; + } +> & { + size?: Sizes; + align?: Alignments; + valid?: ValidationStates; + height?: number; +}; + +export const Input = Object.assign( + styled( + forwardRef(function Input({ size, valid, align, ...props }, ref) { + return ; + }) + )(styles, sizes, alignment, validation, { + minHeight: 32, + }), + { + displayName: 'Input', + } +); diff --git a/code/core/src/components/components/Form/Radio.stories.tsx b/code/core/src/components/components/Form/Radio.stories.tsx new file mode 100644 index 000000000000..8e6b62b14624 --- /dev/null +++ b/code/core/src/components/components/Form/Radio.stories.tsx @@ -0,0 +1,69 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { Radio as Component } from './Radio'; + +const meta = { + component: Component, + title: 'Form/Radio', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Radio: Story = { + render: () => ( +
+ + Custom: + Native: + + Checked, focus: + +
+ +
+ + Checked: + +
+ +
+ + Indeterminate: + +
+ +
+ + Default: + +
+ +
+ + Disabled, checked: + +
+ +
+ + Disabled, indeterminate: + +
+ +
+ + Disabled: + +
+ +
+
+ ), + parameters: { + pseudo: { + focus: '[data-focus]', + }, + }, +}; diff --git a/code/core/src/components/components/Form/Radio.tsx b/code/core/src/components/components/Form/Radio.tsx new file mode 100644 index 000000000000..8a077c1f9561 --- /dev/null +++ b/code/core/src/components/components/Form/Radio.tsx @@ -0,0 +1,39 @@ +import React from 'react'; + +import { color, styled } from 'storybook/internal/theming'; + +const Input = styled.input({ + appearance: 'none', + display: 'grid', + placeContent: 'center', + width: 16, + height: 16, + flexShrink: 0, + margin: -1, + border: `1px solid ${color.border}`, + borderRadius: 8, + backgroundColor: 'white', + transition: 'background-color 0.1s', + + '&:enabled': { + cursor: 'pointer', + }, + '&:disabled': { + backgroundColor: color.medium, + }, + '&:disabled:checked': { + backgroundColor: color.mediumdark, + }, + '&:checked': { + backgroundColor: color.secondary, + boxShadow: `inset 0 0 0 2px white`, + }, + '&:enabled:focus-visible': { + outline: `1px solid ${color.secondary}`, + outlineOffset: 1, + }, +}); + +export const Radio = (props: React.InputHTMLAttributes) => { + return ; +}; diff --git a/code/core/src/components/components/Form/Select.stories.tsx b/code/core/src/components/components/Form/Select.stories.tsx new file mode 100644 index 000000000000..fee24c873bb8 --- /dev/null +++ b/code/core/src/components/components/Form/Select.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { action } from 'storybook/actions'; + +import { Select as Component } from './Select'; + +const meta = { + title: 'Form/Select', + component: Component, +} satisfies Meta; + +type Story = StoryObj; + +export default meta; + +export const Select: Story = { + render: (args) => ( + + + + + + + + + ), +}; diff --git a/code/core/src/components/components/Form/Select.tsx b/code/core/src/components/components/Form/Select.tsx new file mode 100644 index 000000000000..9ea263307e86 --- /dev/null +++ b/code/core/src/components/components/Form/Select.tsx @@ -0,0 +1,139 @@ +import React, { type CSSProperties, type SelectHTMLAttributes } from 'react'; + +import { lighten, styled } from 'storybook/theming'; + +import { isTestEnvironment } from '../../../preview-api/modules/preview-web/render/animation-utils'; +import { type Alignments, type Sizes, type ValidationStates, sizes } from './styles'; + +type SelectProps = Omit< + SelectHTMLAttributes, + keyof { + size?: Sizes; + align?: Alignments; + valid?: ValidationStates; + height?: number; + } +> & { + size?: Sizes; + align?: Alignments; + valid?: ValidationStates; + height?: number; +}; +const BaseSelect = styled.select(sizes, ({ theme }) => ({ + appearance: 'none', + background: `calc(100% - 12px) center no-repeat url("data:image/svg+xml,%3Csvg width='8' height='4' viewBox='0 0 8 4' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1.30303 0.196815C1.13566 0.0294472 0.864304 0.0294472 0.696937 0.196815C0.529569 0.364182 0.529569 0.635539 0.696937 0.802906L3.69694 3.80291C3.8643 3.97027 4.13566 3.97027 4.30303 3.80291L7.30303 0.802906C7.4704 0.635539 7.4704 0.364182 7.30303 0.196815C7.13566 0.0294473 6.8643 0.0294473 6.69694 0.196815L3.99998 2.89377L1.30303 0.196815Z' fill='%2373828C'/%3E%3C/svg%3E%0A")`, + backgroundSize: 10, + padding: '6px 30px 6px 10px', + '@supports (appearance: base-select)': { + appearance: 'base-select' as CSSProperties['appearance'], + background: theme.input.background, + padding: '6px 10px', + }, + transition: 'box-shadow 200ms ease-out, opacity 200ms ease-out', + color: theme.input.color || 'inherit', + boxShadow: `${theme.input.border} 0 0 0 1px inset`, + borderRadius: theme.input.borderRadius, + fontSize: theme.typography.size.s2 - 1, + lineHeight: '20px', + boxSizing: 'border-box', + border: 'none', + cursor: 'pointer', + '& > button': { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + width: '100%', + gap: 8, + '& > svg': { + width: 14, + height: 14, + color: theme.color.mediumdark, + }, + }, + '&:has(option:not([hidden]):checked)': { + color: theme.color.defaultText, + }, + '&:focus-visible, &:focus-within': { + outline: 'none', + boxShadow: `${theme.color.secondary} 0 0 0 1px inset`, + }, + '&::picker-icon': { + display: 'none', + }, + '&::picker(select)': { + appearance: 'base-select' as CSSProperties['appearance'], + border: '1px solid #e4e4e7', + padding: 4, + marginTop: 4, + background: theme.base === 'light' ? lighten(theme.background.app) : theme.background.app, + filter: ` + drop-shadow(0 5px 5px rgba(0,0,0,0.05)) + drop-shadow(0 0 3px rgba(0,0,0,0.1)) + `, + borderRadius: theme.appBorderRadius + 2, + fontSize: theme.typography.size.s1, + cursor: 'default', + transition: 'opacity 100ms ease-in-out, transform 100ms ease-in-out', + transformOrigin: 'top', + transform: 'translateY(0)', + opacity: 1, + '@starting-style': { + transform: 'translateY(-0.25rem) scale(0.95)', + opacity: 0, + }, + }, + '& optgroup label': { + display: 'block', + padding: '3px 6px', + }, + '& option': { + lineHeight: '18px', + padding: '7px 10px', + borderRadius: 4, + outline: 'none', + cursor: 'pointer', + color: theme.color.defaultText, + '&::checkmark': { + display: 'none', + }, + '&:hover, &:focus-visible': { + backgroundColor: theme.background.hoverable, + }, + '&:checked': { + color: theme.color.secondary, + fontWeight: theme.typography.weight.bold, + }, + '&:disabled': { + backgroundColor: 'transparent', + cursor: 'default', + color: theme.color.defaultText, + }, + }, +})); +export const Select = ({ children, ...props }: SelectProps) => { + return ( + // @ts-expect-error Weird props mismatch + + {/* TODO Remove condition when this issue is resolved: https://github.com/facebook/react/issues/33609 */} + {!isTestEnvironment() && ( + + )} + {children} + + ); +}; diff --git a/code/core/src/components/components/Form/Textarea.stories.tsx b/code/core/src/components/components/Form/Textarea.stories.tsx new file mode 100644 index 000000000000..a02bff32de13 --- /dev/null +++ b/code/core/src/components/components/Form/Textarea.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { Textarea as Component } from './Textarea'; + +const meta = { + title: 'Form/Textarea', + component: Component, +} satisfies Meta; + +type Story = StoryObj; + +export default meta; + +export const Textarea: Story = {}; diff --git a/code/core/src/components/components/Form/Textarea.tsx b/code/core/src/components/components/Form/Textarea.tsx new file mode 100644 index 000000000000..6f628ac4f8d3 --- /dev/null +++ b/code/core/src/components/components/Form/Textarea.tsx @@ -0,0 +1,63 @@ +import React, { forwardRef } from 'react'; + +import TextareaAutoResize from 'react-textarea-autosize'; +import { styled } from 'storybook/theming'; + +import { + type Alignments, + type Sizes, + type ValidationStates, + alignment, + sizes, + styles, + validation, +} from './styles'; + +/** + * These types are copied from `react-textarea-autosize`. I copied them because of + * https://github.com/storybookjs/storybook/issues/18734 Maybe there's some bug in `tsup` or + * `react-textarea-autosize`? + */ +type TextareaPropsRaw = React.TextareaHTMLAttributes; +type Style = Omit, 'maxHeight' | 'minHeight'> & { + height?: number; +}; +type TextareaHeightChangeMeta = { + rowHeight: number; +}; +export interface TextareaAutosizeProps extends Omit { + maxRows?: number; + minRows?: number; + onHeightChange?: (height: number, meta: TextareaHeightChangeMeta) => void; + cacheMeasurements?: boolean; + style?: Style; +} + +type TextareaProps = Omit< + TextareaAutosizeProps, + keyof { + size?: Sizes; + align?: Alignments; + valid?: ValidationStates; + height?: number; + } +> & { + size?: Sizes; + align?: Alignments; + valid?: ValidationStates; + height?: number; +} & React.RefAttributes; + +export const Textarea = Object.assign( + styled( + forwardRef(function Textarea({ size, valid, align, ...props }, ref) { + return ; + }) + )(styles, sizes, alignment, validation, ({ height = 400 }) => ({ + overflow: 'visible', + maxHeight: height, + })), + { + displayName: 'Textarea', + } +); diff --git a/code/core/src/components/components/Form/styles.ts b/code/core/src/components/components/Form/styles.ts new file mode 100644 index 000000000000..57924b844aba --- /dev/null +++ b/code/core/src/components/components/Form/styles.ts @@ -0,0 +1,121 @@ +import type { CSSObject, StorybookTheme } from 'storybook/theming'; + +export type Sizes = '100%' | 'flex' | 'auto'; +export type Alignments = 'end' | 'center' | 'start'; +export type ValidationStates = 'valid' | 'error' | 'warn'; + +export const sizes = (({ size }: { size?: Sizes }) => { + switch (size) { + case '100%': { + return { width: '100%' }; + } + case 'flex': { + return { flex: 1 }; + } + case 'auto': + default: { + return { display: 'inline' }; + } + } +}) as any; + +export const alignment = (({ + align, +}: { + size?: Sizes; + align?: Alignments; + valid?: ValidationStates; + height?: number; +}) => { + switch (align) { + case 'end': { + return { textAlign: 'right' }; + } + case 'center': { + return { textAlign: 'center' }; + } + case 'start': + default: { + return { textAlign: 'left' }; + } + } +}) as any; + +export const validation = (({ + valid, + theme, +}: { + valid: ValidationStates; + theme: StorybookTheme; +}) => { + switch (valid) { + case 'valid': { + return { boxShadow: `${theme.color.positive} 0 0 0 1px inset !important` }; + } + case 'error': { + return { boxShadow: `${theme.color.negative} 0 0 0 1px inset !important` }; + } + case 'warn': { + return { + boxShadow: `${theme.color.warning} 0 0 0 1px inset`, + }; + } + case undefined: + case null: + default: { + return {}; + } + } +}) as any; + +const styleResets: CSSObject = { + // resets + appearance: 'none', + border: '0 none', + boxSizing: 'inherit', + display: ' block', + margin: ' 0', + background: 'transparent', + padding: 0, + fontSize: 'inherit', + position: 'relative', +}; + +export const styles = (({ theme }: { theme: StorybookTheme }) => ({ + ...(styleResets as any), + + transition: 'box-shadow 200ms ease-out, opacity 200ms ease-out', + color: theme.input.color || 'inherit', + background: theme.input.background, + boxShadow: `${theme.input.border} 0 0 0 1px inset`, + borderRadius: theme.input.borderRadius, + fontSize: theme.typography.size.s2 - 1, + lineHeight: '20px', + padding: '6px 10px', // 32 + boxSizing: 'border-box', + height: 32, + + '&[type="file"]': { + height: 'auto', + }, + + '&:focus': { + boxShadow: `${theme.color.secondary} 0 0 0 1px inset`, + outline: 'none', + '@media (forced-colors: active)': { + outline: '1px solid highlight', + }, + }, + + '&[disabled]': { + cursor: 'not-allowed', + opacity: 0.5, + }, + + '&:-webkit-autofill': { WebkitBoxShadow: `0 0 0 3em ${theme.color.lightest} inset` }, + + '&::placeholder': { + color: theme.textMutedColor, + opacity: 1, + }, +})) as any; diff --git a/code/core/src/components/components/Modal/Modal.styled.tsx b/code/core/src/components/components/Modal/Modal.styled.tsx index 9ded17edd0e4..05cd72f04c9e 100644 --- a/code/core/src/components/components/Modal/Modal.styled.tsx +++ b/code/core/src/components/components/Modal/Modal.styled.tsx @@ -52,7 +52,7 @@ export const Container = styled.div<{ width?: number; height?: number }>( height: height ?? 'auto', maxWidth: 'calc(100% - 40px)', maxHeight: '85vh', - overflow: 'hidden', + overflow: 'auto', zIndex: 11, animation: `${zoomIn} 200ms`, diff --git a/code/core/src/components/components/form/form.stories.tsx b/code/core/src/components/components/form/form.stories.tsx deleted file mode 100644 index 1abffb1df064..000000000000 --- a/code/core/src/components/components/form/form.stories.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import React from 'react'; - -import { action } from 'storybook/actions'; -import { styled } from 'storybook/theming'; - -import { Field as FieldComponent } from './field/field'; -import * as InputComponents from './input/input'; - -const Flexed = styled(FieldComponent)({ display: 'flex' }); - -export default { - title: 'Form', -}; - -const sharedArgTypes = { - disabled: { - defaultValue: false, - control: { - type: 'boolean', - }, - }, - size: { - defaultValue: 'auto', - control: { - type: 'radio', - // TODO: weak typings - options: ['100%', 'auto', 'flex'] as InputComponents.Sizes[], - }, - }, - valid: { - control: { - type: 'radio', - // TODO: weak typings - options: [null, 'valid', 'warn', 'error'] as InputComponents.ValidationStates[], - }, - }, - align: { - control: { - type: 'radio', - // TODO: weak typings - options: [null, 'start', 'center', 'end'] as InputComponents.Alignments[], - }, - }, -}; - -export const Field = { - render: (args: any) => ( - - - - - - - - ), - argTypes: { - label: { - defaultValue: 'label', - control: { - type: 'text', - }, - }, - }, -}; - -export const Select = { - render: (args: any) => ( - - - - - - - - ), - argTypes: { - ...sharedArgTypes, - value: { - defaultValue: 'val2', - control: { - type: 'radio', - options: ['val1', 'val2', 'val3'], - }, - }, - }, -}; - -export const Textarea = { - render: (args: any) => ( - - - - ), - argTypes: { - ...sharedArgTypes, - height: { - control: { - type: 'number', - }, - }, - }, -}; - -export const Input = { - render: (args: any) => ( - - - - ), - argTypes: { - ...sharedArgTypes, - value: { - control: { - type: 'text', - }, - }, - placeholder: { - control: { - type: 'text', - }, - defaultValue: 'Placeholder', - }, - }, -}; diff --git a/code/core/src/components/components/form/input/input.tsx b/code/core/src/components/components/form/input/input.tsx deleted file mode 100644 index b4a6fd3245e3..000000000000 --- a/code/core/src/components/components/form/input/input.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import type { HTMLProps, SelectHTMLAttributes } from 'react'; -import React, { forwardRef } from 'react'; - -import TextareaAutoResize from 'react-textarea-autosize'; -import type { CSSObject, StorybookTheme } from 'storybook/theming'; -import { styled } from 'storybook/theming'; - -/** - * These types are copied from `react-textarea-autosize`. I copied them because of - * https://github.com/storybookjs/storybook/issues/18734 Maybe there's some bug in `tsup` or - * `react-textarea-autosize`? - */ -type TextareaPropsRaw = React.TextareaHTMLAttributes; -type Style = Omit, 'maxHeight' | 'minHeight'> & { - height?: number; -}; -type TextareaHeightChangeMeta = { - rowHeight: number; -}; -export interface TextareaAutosizeProps extends Omit { - maxRows?: number; - minRows?: number; - onHeightChange?: (height: number, meta: TextareaHeightChangeMeta) => void; - cacheMeasurements?: boolean; - style?: Style; -} - -const styleResets: CSSObject = { - // resets - appearance: 'none', - border: '0 none', - boxSizing: 'inherit', - display: ' block', - margin: ' 0', - background: 'transparent', - padding: 0, - fontSize: 'inherit', - position: 'relative', -}; - -const styles = (({ theme }: { theme: StorybookTheme }) => ({ - ...(styleResets as any), - - transition: 'box-shadow 200ms ease-out, opacity 200ms ease-out', - color: theme.input.color || 'inherit', - background: theme.input.background, - boxShadow: `${theme.input.border} 0 0 0 1px inset`, - borderRadius: theme.input.borderRadius, - fontSize: theme.typography.size.s2 - 1, - lineHeight: '20px', - padding: '6px 10px', // 32 - boxSizing: 'border-box', - height: 32, - - '&[type="file"]': { - height: 'auto', - }, - - '&:focus': { - boxShadow: `${theme.color.secondary} 0 0 0 1px inset`, - outline: 'none', - }, - '&[disabled]': { - cursor: 'not-allowed', - opacity: 0.5, - }, - - '&:-webkit-autofill': { WebkitBoxShadow: `0 0 0 3em ${theme.color.lightest} inset` }, - - '&::placeholder': { - color: theme.textMutedColor, - opacity: 1, - }, -})) as any; - -export type Sizes = '100%' | 'flex' | 'auto'; -export type Alignments = 'end' | 'center' | 'start'; -export type ValidationStates = 'valid' | 'error' | 'warn'; - -const sizes = (({ size }: { size?: Sizes }) => { - switch (size) { - case '100%': { - return { width: '100%' }; - } - case 'flex': { - return { flex: 1 }; - } - case 'auto': - default: { - return { display: 'inline' }; - } - } -}) as any; -const alignment = (({ - align, -}: { - size?: Sizes; - align?: Alignments; - valid?: ValidationStates; - height?: number; -}) => { - switch (align) { - case 'end': { - return { textAlign: 'right' }; - } - case 'center': { - return { textAlign: 'center' }; - } - case 'start': - default: { - return { textAlign: 'left' }; - } - } -}) as any; -const validation = (({ valid, theme }: { valid: ValidationStates; theme: StorybookTheme }) => { - switch (valid) { - case 'valid': { - return { boxShadow: `${theme.color.positive} 0 0 0 1px inset !important` }; - } - case 'error': { - return { boxShadow: `${theme.color.negative} 0 0 0 1px inset !important` }; - } - case 'warn': { - return { - boxShadow: `${theme.color.warning} 0 0 0 1px inset`, - }; - } - case undefined: - case null: - default: { - return {}; - } - } -}) as any; - -type InputProps = Omit< - HTMLProps, - keyof { - size?: Sizes; - align?: Alignments; - valid?: ValidationStates; - height?: number; - } -> & { - size?: Sizes; - align?: Alignments; - valid?: ValidationStates; - height?: number; -}; -export const Input = Object.assign( - styled( - forwardRef(function Input({ size, valid, align, ...props }, ref) { - return ; - }) - )(styles, sizes, alignment, validation, { - minHeight: 32, - }), - { - displayName: 'Input', - } -); - -type SelectProps = Omit< - SelectHTMLAttributes, - keyof { - size?: Sizes; - align?: Alignments; - valid?: ValidationStates; - height?: number; - } -> & { - size?: Sizes; - align?: Alignments; - valid?: ValidationStates; - height?: number; -}; -export const Select = Object.assign( - styled( - forwardRef(function Select({ size, valid, align, ...props }, ref) { - return