-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
React: Auto calculate imports, support barrel files, auto detect. components and other small tweaks #32912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React: Auto calculate imports, support barrel files, auto detect. components and other small tweaks #32912
Changes from 11 commits
a5f4646
17a3089
9331bf7
2b072ba
835417c
8ba3b0b
8d40e43
ce25a2f
7e9a30a
acf5925
2a05fc4
3de6fad
80b0ad2
08bdc82
8151722
50be8d7
70e8504
7ab7295
d9cc661
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -40,7 +40,7 @@ export function renderManifestComponentsPage(manifest: ComponentsManifest) { | |||||||||||||||||||
| entries.map(([, it]) => it).filter((it) => it.error), | ||||||||||||||||||||
| (manifest) => manifest.error?.name ?? 'Error' | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| ).sort(([, a], [, b]) => b.length - a.length); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const errorGroupsHTML = errorGroups | ||||||||||||||||||||
| .map(([error, grouped]) => { | ||||||||||||||||||||
|
|
@@ -517,15 +517,23 @@ export function renderManifestComponentsPage(manifest: ComponentsManifest) { | |||||||||||||||||||
| .card > .tg-err:checked ~ .panels .panel-err { | ||||||||||||||||||||
| display: grid; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| .card > .tg-warn:checked ~ .panels .panel-warn { | ||||||||||||||||||||
| display: grid; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| .card > .tg-stories:checked ~ .panels .panel-stories { | ||||||||||||||||||||
| display: grid; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+520
to
527
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove or complete empty CSS rule blocks. These CSS rule blocks appear to be incomplete or placeholder code. They should either be removed or completed with the intended styles. -
- .card > .tg-warn:checked ~ .panels .panel-warn {
- display: grid;
- }
-
- .card > .tg-stories:checked ~ .panels .panel-stories {
- display: grid;
- }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Add vertical spacing around panels only when any panel is visible */ | ||||||||||||||||||||
| .card > .tg-err:checked ~ .panels, | ||||||||||||||||||||
| .card > .tg-warn:checked ~ .panels, | ||||||||||||||||||||
| .card > .tg-stories:checked ~ .panels, | ||||||||||||||||||||
| .card > .tg-props:checked ~ .panels { | ||||||||||||||||||||
| margin: 10px 0; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Optional: a subtle 1px ring on the active badge, using :has() if available */ | ||||||||||||||||||||
| @supports selector(.card:has(.tg-err:checked)) { | ||||||||||||||||||||
| .card:has(.tg-err:checked) label[for$='-err'], | ||||||||||||||||||||
|
|
@@ -536,6 +544,25 @@ export function renderManifestComponentsPage(manifest: ComponentsManifest) { | |||||||||||||||||||
| border-color: currentColor; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Wrap long lines in code blocks at ~120 characters */ | ||||||||||||||||||||
| pre, code { | ||||||||||||||||||||
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| pre { | ||||||||||||||||||||
| white-space: pre-wrap; | ||||||||||||||||||||
| overflow-wrap: anywhere; | ||||||||||||||||||||
| word-break: break-word; | ||||||||||||||||||||
| overflow-x: auto; /* fallback for extremely long tokens */ | ||||||||||||||||||||
| margin: 8px 0 0; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| pre > code { | ||||||||||||||||||||
| display: block; | ||||||||||||||||||||
| white-space: inherit; | ||||||||||||||||||||
| overflow-wrap: inherit; | ||||||||||||||||||||
| word-break: inherit; | ||||||||||||||||||||
| inline-size: min(100%, 120ch); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| </style> | ||||||||||||||||||||
| </head> | ||||||||||||||||||||
| <body> | ||||||||||||||||||||
|
|
@@ -752,9 +779,22 @@ function renderComponentCard(key: string, c: ComponentManifest, id: string) { | |||||||||||||||||||
| </div>` | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| .join('')} | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| ${ | ||||||||||||||||||||
| c.import | ||||||||||||||||||||
| ? `<div class="note ok"> | ||||||||||||||||||||
| <div class="row"> | ||||||||||||||||||||
| <span class="ex-name">Imports</span> | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
| <pre><code>${c.import}</code></pre> | ||||||||||||||||||||
| </div>` | ||||||||||||||||||||
| : '' | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+784
to
+795
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Escape import content to prevent XSS. The import string Apply the ${
c.import
? `<div class="note ok">
<div class="row">
<span class="ex-name">Imports</span>
</div>
- <pre><code>${c.import}</code></pre>
+ <pre><code>${esc(c.import)}</code></pre>
</div>`
: ''
}
🤖 Prompt for AI Agents |
||||||||||||||||||||
|
|
||||||||||||||||||||
| ${okStories | ||||||||||||||||||||
| .map( | ||||||||||||||||||||
| (ex, k) => ` | ||||||||||||||||||||
| (ex) => ` | ||||||||||||||||||||
| <div class="note ok"> | ||||||||||||||||||||
| <div class="row"> | ||||||||||||||||||||
| <span class="ex-name">${esc(ex.name)}</span> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,8 +7,33 @@ | |||||||||||||
|
|
||||||||||||||
| import { componentManifestGenerator } from './generator'; | ||||||||||||||
|
|
||||||||||||||
| vi.mock('storybook/internal/common', async (importOriginal) => ({ | ||||||||||||||
| ...(await importOriginal()), | ||||||||||||||
| // Keep it simple: hardcode known inputs to expected outputs for this test. | ||||||||||||||
| resolveImport: (id: string, opts: { basedir: string }) => { | ||||||||||||||
| const basedir = opts?.basedir; | ||||||||||||||
| return basedir === '/app/src/stories' && id === './Button' | ||||||||||||||
| ? './src/stories/Button.tsx' | ||||||||||||||
| : basedir === '/app/src/stories' && id === './Header' | ||||||||||||||
| ? './src/stories/Header.tsx' | ||||||||||||||
| : id; | ||||||||||||||
| }, | ||||||||||||||
| JsPackageManagerFactory: { | ||||||||||||||
| getPackageManager: () => ({ | ||||||||||||||
| primaryPackageJson: { | ||||||||||||||
| packageJson: { | ||||||||||||||
| name: 'some-package', | ||||||||||||||
| }, | ||||||||||||||
| }, | ||||||||||||||
| }), | ||||||||||||||
| }, | ||||||||||||||
| })); | ||||||||||||||
| vi.mock('node:fs/promises', async () => (await import('memfs')).fs.promises); | ||||||||||||||
| vi.mock('node:fs', async () => (await import('memfs')).fs); | ||||||||||||||
|
|
||||||||||||||
| vi.mock('empathic/find', async () => ({ | ||||||||||||||
| up: (path: string) => '/app/package.json', | ||||||||||||||
| })); | ||||||||||||||
|
Comment on lines
+33
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mock empathic/find without spy: true option. According to the coding guidelines, all package and file mocks in Vitest tests should use As per coding guidelines Apply this diff: -vi.mock('empathic/find', async () => ({
+vi.mock('empathic/find', { spy: true }, async () => ({
up: (path: string) => '/app/package.json',
}));📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| vi.mock('tsconfig-paths', () => ({ loadConfig: () => ({ resultType: null!, message: null! }) })); | ||||||||||||||
|
|
||||||||||||||
| // Use the provided indexJson from this file | ||||||||||||||
|
|
@@ -97,6 +122,7 @@ | |||||||||||||
| vi.spyOn(process, 'cwd').mockReturnValue('/app'); | ||||||||||||||
| vol.fromJSON( | ||||||||||||||
| { | ||||||||||||||
| ['./package.json']: JSON.stringify({ name: 'some-package' }), | ||||||||||||||
| ['./src/stories/Button.stories.ts']: dedent` | ||||||||||||||
| import type { Meta, StoryObj } from '@storybook/react'; | ||||||||||||||
| import { fn } from 'storybook/test'; | ||||||||||||||
|
|
@@ -205,19 +231,19 @@ | |||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| test('componentManifestGenerator generates correct id, name, description and examples ', async () => { | ||||||||||||||
| const generator = await componentManifestGenerator(); | ||||||||||||||
| const manifest = await generator({ | ||||||||||||||
| const generator = await componentManifestGenerator(undefined, { configDir: '.storybook' } as any); | ||||||||||||||
| const manifest = await generator?.({ | ||||||||||||||
| getIndex: async () => indexJson, | ||||||||||||||
| } as unknown as StoryIndexGenerator); | ||||||||||||||
|
|
||||||||||||||
| expect(manifest).toMatchInlineSnapshot(` | ||||||||||||||
|
Check failure on line 239 in code/renderers/react/src/componentManifest/generator.test.ts
|
||||||||||||||
| { | ||||||||||||||
| "components": { | ||||||||||||||
| "example-button": { | ||||||||||||||
| "description": "Primary UI component for user interaction", | ||||||||||||||
| "error": undefined, | ||||||||||||||
| "id": "example-button", | ||||||||||||||
| "import": undefined, | ||||||||||||||
| "import": "import { Button } from "some-package";", | ||||||||||||||
| "jsDocTags": {}, | ||||||||||||||
| "name": "Button", | ||||||||||||||
| "path": "./src/stories/Button.stories.ts", | ||||||||||||||
|
|
@@ -321,7 +347,7 @@ | |||||||||||||
| "description": "Description from meta and very long.", | ||||||||||||||
| "error": undefined, | ||||||||||||||
| "id": "example-header", | ||||||||||||||
| "import": "import { Header } from '@design-system/components/Header';", | ||||||||||||||
| "import": "import { Header } from "some-package";", | ||||||||||||||
| "jsDocTags": { | ||||||||||||||
| "import": [ | ||||||||||||||
| "import { Header } from '@design-system/components/Header';", | ||||||||||||||
|
|
@@ -418,6 +444,7 @@ | |||||||||||||
| async function getManifestForStory(code: string) { | ||||||||||||||
| vol.fromJSON( | ||||||||||||||
| { | ||||||||||||||
| ['./package.json']: JSON.stringify({ name: 'some-package' }), | ||||||||||||||
| ['./src/stories/Button.stories.ts']: code, | ||||||||||||||
| ['./src/stories/Button.tsx']: dedent` | ||||||||||||||
| import React from 'react'; | ||||||||||||||
|
|
@@ -441,7 +468,7 @@ | |||||||||||||
| '/app' | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| const generator = await componentManifestGenerator(); | ||||||||||||||
| const generator = await componentManifestGenerator(undefined, { configDir: '.storybook' } as any); | ||||||||||||||
| const indexJson = { | ||||||||||||||
| v: 5, | ||||||||||||||
| entries: { | ||||||||||||||
|
|
@@ -459,11 +486,11 @@ | |||||||||||||
| }, | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| const manifest = await generator({ | ||||||||||||||
| const manifest = await generator?.({ | ||||||||||||||
| getIndex: async () => indexJson, | ||||||||||||||
| } as unknown as StoryIndexGenerator); | ||||||||||||||
|
|
||||||||||||||
| return manifest.components['example-button']; | ||||||||||||||
| return manifest?.components?.['example-button']; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| function withCSF3(body: string) { | ||||||||||||||
|
|
@@ -492,12 +519,12 @@ | |||||||||||||
|
|
||||||||||||||
| export const Primary = () => <Button csf1="story" />; | ||||||||||||||
| `; | ||||||||||||||
| expect(await getManifestForStory(code)).toMatchInlineSnapshot(` | ||||||||||||||
|
Check failure on line 522 in code/renderers/react/src/componentManifest/generator.test.ts
|
||||||||||||||
| { | ||||||||||||||
| "description": "Primary UI component for user interaction", | ||||||||||||||
| "error": undefined, | ||||||||||||||
| "id": "example-button", | ||||||||||||||
| "import": undefined, | ||||||||||||||
| "import": "import { Button } from "some-package";", | ||||||||||||||
| "jsDocTags": {}, | ||||||||||||||
| "name": "Button", | ||||||||||||||
| "path": "./src/stories/Button.stories.ts", | ||||||||||||||
|
|
@@ -537,12 +564,12 @@ | |||||||||||||
| const code = withCSF3(dedent` | ||||||||||||||
| export { Primary } from './other-file'; | ||||||||||||||
| `); | ||||||||||||||
| expect(await getManifestForStory(code)).toMatchInlineSnapshot(` | ||||||||||||||
|
Check failure on line 567 in code/renderers/react/src/componentManifest/generator.test.ts
|
||||||||||||||
| { | ||||||||||||||
| "description": "Primary UI component for user interaction", | ||||||||||||||
| "error": undefined, | ||||||||||||||
| "id": "example-button", | ||||||||||||||
| "import": undefined, | ||||||||||||||
| "import": "import { Button } from "some-package";", | ||||||||||||||
| "jsDocTags": {}, | ||||||||||||||
| "name": "Button", | ||||||||||||||
| "path": "./src/stories/Button.stories.ts", | ||||||||||||||
|
|
@@ -589,12 +616,12 @@ | |||||||||||||
| const code = withCSF3(dedent` | ||||||||||||||
| export const Primary = someWeirdExpression; | ||||||||||||||
| `); | ||||||||||||||
| expect(await getManifestForStory(code)).toMatchInlineSnapshot(` | ||||||||||||||
|
Check failure on line 619 in code/renderers/react/src/componentManifest/generator.test.ts
|
||||||||||||||
| { | ||||||||||||||
| "description": "Primary UI component for user interaction", | ||||||||||||||
| "error": undefined, | ||||||||||||||
| "id": "example-button", | ||||||||||||||
| "import": undefined, | ||||||||||||||
| "import": "import { Button } from "some-package";", | ||||||||||||||
| "jsDocTags": {}, | ||||||||||||||
| "name": "Button", | ||||||||||||||
| "path": "./src/stories/Button.stories.ts", | ||||||||||||||
|
|
||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.