Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c2f16e3
Export additional types from core-annotations to reduce `cannot be na…
icopp Dec 12, 2025
d81ec4c
Merge branch 'next' into patch-4
ndelangen Dec 24, 2025
3867eb6
Merge branch 'next' into patch-4
ndelangen Jan 15, 2026
e55073d
Prevent value on draghandles from going below minimum width/height
ghengeveld Jan 15, 2026
4f956a4
Fix viewport size indicaors when zooming
ghengeveld Jan 15, 2026
1ea9ed8
Avoid using short variable names in an API and avoid non-null assertion
ghengeveld Jan 16, 2026
87c9a71
Fix scrollIntoView when dragging
ghengeveld Jan 17, 2026
45525c0
Prevent panel drag handle from rendering outside of container
ghengeveld Jan 17, 2026
c473ffb
Prevent floating point numbers on draghandles
ghengeveld Jan 17, 2026
e28f335
Restore border
ghengeveld Jan 17, 2026
8fba269
Merge branch 'next' into viewport-fixes
ghengeveld Jan 19, 2026
04b7376
Merge branch 'next' into patch-4
ndelangen Jan 19, 2026
c8d2ee4
Add '@opentelemetry/api' to Vite dependencies in preset.ts
ndelangen Jan 19, 2026
f337cff
Add comments to Layout component for clarity on CSS variables and dra…
ghengeveld Jan 19, 2026
a9bb066
Merge pull request #33557 from storybookjs/viewport-fixes
ghengeveld Jan 19, 2026
f06fa2f
Merge pull request #33577 from storybookjs/norbert/add-extra-optimize…
ndelangen Jan 19, 2026
e880840
Merge pull request #33344 from icopp/patch-4
ndelangen Jan 19, 2026
ffd0013
Initial plan
Copilot Oct 29, 2025
f64870d
Fix: Add storybook plugin to Next.js defineConfig ESLint configs
Copilot Oct 29, 2025
90beaf0
Add type guards for safer argument handling
Copilot Oct 29, 2025
1deac31
Simplify type guard logic per code review feedback
Copilot Oct 29, 2025
f3a48e1
Add null check for unwrapTSExpression return value
Copilot Oct 29, 2025
17e5fb6
Only modify defineConfig from eslint/config imports
Copilot Oct 29, 2025
372dbc8
Add support for direct export default defineConfig([...])
Copilot Dec 22, 2025
c053d38
Merge branch 'next-release' into next
storybook-bot Jan 19, 2026
a1c13a8
Merge pull request #32878 from storybookjs/copilot/fix-storybook-plug…
yannbf Jan 19, 2026
5dd4043
Write changelog for 10.2.0-beta.3 [skip ci]
storybook-bot Jan 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.prerelease.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 10.2.0-beta.3

- Core: Support defineConfig when setting up ESLint plugin - [#32878](https://github.com/storybookjs/storybook/pull/32878), thanks @copilot-swe-agent!
- Core: Viewport UX fixes - [#33557](https://github.com/storybookjs/storybook/pull/33557), thanks @ghengeveld!
- NextJSVite: Add `@opentelemetry/api` to `optimizeDeps` - [#33577](https://github.com/storybookjs/storybook/pull/33577), thanks @ndelangen!
- TypeScript: Reduce `cannot be named` errors - [#33344](https://github.com/storybookjs/storybook/pull/33344), thanks @icopp!

## 10.2.0-beta.2

- CSF-Factories: Skip non-factory exports instead of throwing error - [#33550](https://github.com/storybookjs/storybook/pull/33550), thanks @kasperpeulen!
Expand Down
129 changes: 129 additions & 0 deletions code/core/src/cli/eslintPlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,135 @@ describe('configureEslintPlugin', () => {
`);
});

it('should configure ESLint plugin correctly with Next.js defineConfig style', async () => {
const mockPackageManager = {
getAllDependencies: vi.fn(),
} satisfies Partial<JsPackageManager>;

const mockConfigFile = dedent`import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
globalIgnores([
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);

export default eslintConfig;`;

vi.mocked(readFile).mockResolvedValue(mockConfigFile);

await configureEslintPlugin({
eslintConfigFile: 'eslint.config.mjs',
packageManager: mockPackageManager as any,
isFlatConfig: true,
});
const [, content] = vi.mocked(writeFile).mock.calls[0];
expect(content).toMatchInlineSnapshot(`
"// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
import storybook from "eslint-plugin-storybook";

import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const eslintConfig = defineConfig([...nextVitals, ...nextTs, globalIgnores([
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]), ...storybook.configs["flat/recommended"]]);

export default eslintConfig;"
`);
});

it('should configure ESLint plugin correctly with direct export default defineConfig', async () => {
const mockPackageManager = {
getAllDependencies: vi.fn(),
} satisfies Partial<JsPackageManager>;

const mockConfigFile = dedent`import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

export default defineConfig([
...nextVitals,
...nextTs,
globalIgnores([
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);`;

vi.mocked(readFile).mockResolvedValue(mockConfigFile);

await configureEslintPlugin({
eslintConfigFile: 'eslint.config.mjs',
packageManager: mockPackageManager as any,
isFlatConfig: true,
});
const [, content] = vi.mocked(writeFile).mock.calls[0];
expect(content).toMatchInlineSnapshot(`
"// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
import storybook from "eslint-plugin-storybook";

import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

export default defineConfig([...nextVitals, ...nextTs, globalIgnores([
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]), ...storybook.configs["flat/recommended"]]);"
`);
});

it('should just add an import if config uses defineConfig from non-eslint/config source', async () => {
const mockPackageManager = {
getAllDependencies: vi.fn(),
} satisfies Partial<JsPackageManager>;

const mockConfigFile = dedent`import { defineConfig } from "some-other-config-lib";

const eslintConfig = defineConfig([
{ rules: { "no-console": "error" } },
]);

export default eslintConfig;`;

vi.mocked(readFile).mockResolvedValue(mockConfigFile);

await configureEslintPlugin({
eslintConfigFile: 'eslint.config.js',
packageManager: mockPackageManager as any,
isFlatConfig: true,
});
const [, content] = vi.mocked(writeFile).mock.calls[0];
expect(content).toMatchInlineSnapshot(`
"// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
import storybook from "eslint-plugin-storybook";

import { defineConfig } from "some-other-config-lib";

const eslintConfig = defineConfig([
{ rules: { "no-console": "error" } },
]);

export default eslintConfig;"
`);
});

it('should just add an import if config is of custom unknown format', async () => {
const mockPackageManager = {
getAllDependencies: vi.fn(),
Expand Down
43 changes: 42 additions & 1 deletion code/core/src/cli/eslintPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const configureFlatConfig = async (code: string) => {
const ast = babelParse(code);

let tsEslintLocalName = '';
let eslintDefineConfigLocalName = '';
let eslintConfigExpression: any = null;

/**
Expand All @@ -77,6 +78,14 @@ export const configureFlatConfig = async (code: string) => {
tsEslintLocalName = defaultSpecifier.local.name;
}
}
if (path.node.source.value === 'eslint/config') {
const defineConfigSpecifier = path.node.specifiers.find(
(s) => t.isImportSpecifier(s) && t.isIdentifier(s.imported, { name: 'defineConfig' })
);
if (defineConfigSpecifier && t.isImportSpecifier(defineConfigSpecifier)) {
eslintDefineConfigLocalName = defineConfigSpecifier.local.name;
}
}
},

ExportDefaultDeclaration(path) {
Expand Down Expand Up @@ -105,14 +114,46 @@ export const configureFlatConfig = async (code: string) => {
eslintConfigExpression.arguments.push(storybookConfig);
}

// Case 3: export default config (resolve to array)
// Case 2b: export default defineConfig([...]) from "eslint/config"
if (
t.isCallExpression(eslintConfigExpression) &&
t.isIdentifier(eslintConfigExpression.callee) &&
eslintDefineConfigLocalName &&
eslintConfigExpression.callee.name === eslintDefineConfigLocalName &&
eslintConfigExpression.arguments.length > 0
) {
const firstArg = eslintConfigExpression.arguments[0];
if (t.isExpression(firstArg)) {
const unwrappedArg = unwrapTSExpression(firstArg);
if (unwrappedArg && t.isArrayExpression(unwrappedArg)) {
unwrappedArg.elements.push(t.spreadElement(storybookConfig));
}
}
}

// Case 3: export default config (resolve to array or call expression with array)
if (t.isIdentifier(eslintConfigExpression)) {
const binding = path.scope.getBinding(eslintConfigExpression.name);
if (binding && t.isVariableDeclarator(binding.path.node)) {
const init = unwrapTSExpression(binding.path.node.init);

if (t.isArrayExpression(init)) {
init.elements.push(t.spreadElement(storybookConfig));
} else if (
t.isCallExpression(init) &&
init.arguments.length > 0 &&
t.isIdentifier(init.callee) &&
eslintDefineConfigLocalName &&
init.callee.name === eslintDefineConfigLocalName
) {
// Handle cases like defineConfig([...]) from "eslint/config"
const firstArg = init.arguments[0];
if (t.isExpression(firstArg)) {
const unwrappedArg = unwrapTSExpression(firstArg);
if (unwrappedArg && t.isArrayExpression(unwrappedArg)) {
unwrappedArg.elements.push(t.spreadElement(storybookConfig));
}
}
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions code/core/src/csf/core-annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ import outlineAnnotations, { type OutlineTypes } from '../outline/preview';
import testAnnotations, { type TestTypes } from '../test/preview';
import viewportAnnotations, { type ViewportTypes } from '../viewport/preview';

export type { ActionsTypes } from '../actions/preview';
export type { BackgroundsGlobals, BackgroundTypes } from '../backgrounds/preview';
export type { ControlsTypes } from '../controls/preview';
export type { HighlightTypes } from '../highlight/preview';
export type { MeasureTypes } from '../measure/preview';
export type { OutlineTypes } from '../outline/preview';
export type { TestTypes } from '../test/preview';
export type { ViewportGlobals, ViewportTypes } from '../viewport/preview';

export type CoreTypes = StorybookTypes &
ActionsTypes &
BackgroundTypes &
Expand Down
Loading