diff --git a/code/core/package.json b/code/core/package.json index 756de3d5f75f..20e047c0fa75 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -381,6 +381,7 @@ "ts-dedent": "^2.0.0", "tsconfig-paths": "^4.2.0", "type-fest": "^5.6.0", + "type-plus": "^8.0.0-beta.8", "typescript": "^5.8.3", "unique-string": "^3.0.0", "use-resize-observer": "^9.1.0", diff --git a/code/core/src/csf/csf-factories.test.ts b/code/core/src/csf/csf-factories.test.ts index b3c146c8f6dd..c80a6f6b3ecb 100644 --- a/code/core/src/csf/csf-factories.test.ts +++ b/code/core/src/csf/csf-factories.test.ts @@ -1,7 +1,9 @@ //* @vitest-environment happy-dom */ -import { describe, expect, test, vi } from 'vitest'; +import { describe, expect, expectTypeOf, test, vi } from 'vitest'; +import { testType } from 'type-plus'; import { definePreview, definePreviewAddon, getStoryChildren } from './csf-factories.ts'; +import type { Tag } from './story.ts'; interface Addon1Types { parameters: { foo?: { value: string } }; @@ -76,3 +78,60 @@ describe('test function', () => { expect(testFn).toHaveBeenCalled(); }); }); + +describe('customize tags type', () => { + // Customizing tags type enables autocompletion of tags. + test('with addon', () => { + const addon = definePreviewAddon<{ tags: Array<'foo' | 'bar' | (string & {})> }>({}); + const preview = definePreview({ addons: [addon] }); + const meta = preview.meta({ + tags: ['foo', 'something-else'], + }); + meta.story({ + tags: ['foo', 'something-else'], + }); + testType.canAssign< + Parameters[0]['tags'], + Array<'foo' | 'bar' | (string & {})> + >(true); + testType.canAssign< + Parameters[0] extends Object + ? Parameters[0]['tags'] + : never, + Array<'foo' | 'bar' | (string & {})> + >(true); + testType.canAssign< + Parameters[0] extends Object + ? Parameters[0]['tags'] + : never, + Tag[] + >(true); + }); + test('with type method', () => { + const preview = definePreview({ addons: [] }).type<{ + tags: Array<'foo' | 'bar' | (string & {})>; + }>(); + const meta = preview.meta({ + tags: ['foo', 'something-else'], + }); + meta.story({ + tags: ['foo', 'something-else'], + }); + testType.canAssign< + Parameters[0]['tags'], + Array<'foo' | 'bar' | (string & {})> + >(true); + testType.canAssign< + Parameters[0] extends Object + ? Parameters[0]['tags'] + : never, + Array<'foo' | 'bar' | (string & {})> + >(true); + testType.canAssign< + Parameters[0] extends Object + ? Parameters[0]['tags'] + : never, + Tag[] + >(true); + }); +}); diff --git a/code/core/src/csf/story.ts b/code/core/src/csf/story.ts index b88609e8ac74..5d8a3d27704d 100644 --- a/code/core/src/csf/story.ts +++ b/code/core/src/csf/story.ts @@ -185,6 +185,7 @@ export interface GlobalTypes { * type-checked across all stories. */ export interface AddonTypes { + tags?: Tag[] | undefined; args?: unknown; parameters?: Record; globals?: Record; @@ -413,7 +414,7 @@ export interface BaseAnnotations; /** Named tags for a story, used to filter stories in different contexts. */ - tags?: Tag[]; + tags?: (TRenderer['tags'] extends Tag[] ? TRenderer['tags'] : Tag[]) | undefined; mount?: (context: StoryContext) => TRenderer['mount']; } diff --git a/yarn.lock b/yarn.lock index 0de77348dc30..55faf815722a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20594,6 +20594,13 @@ __metadata: languageName: node linkType: hard +"is-buffer@npm:^2.0.5": + version: 2.0.5 + resolution: "is-buffer@npm:2.0.5" + checksum: 10c0/e603f6fced83cf94c53399cff3bda1a9f08e391b872b64a73793b0928be3e5f047f2bcece230edb7632eaea2acdbfcb56c23b33d8a20c820023b230f1485679a + languageName: node + linkType: hard + "is-bun-module@npm:^2.0.0": version: 2.0.0 resolution: "is-bun-module@npm:2.0.0" @@ -29693,6 +29700,7 @@ __metadata: ts-dedent: "npm:^2.0.0" tsconfig-paths: "npm:^4.2.0" type-fest: "npm:^5.6.0" + type-plus: "npm:^8.0.0-beta.8" typescript: "npm:^5.8.3" unique-string: "npm:^3.0.0" use-resize-observer: "npm:^9.1.0" @@ -30430,6 +30438,17 @@ __metadata: languageName: node linkType: hard +"tersify@npm:^3.11.1": + version: 3.12.1 + resolution: "tersify@npm:3.12.1" + dependencies: + acorn: "npm:^8.8.2" + is-buffer: "npm:^2.0.5" + unpartial: "npm:^1.0.4" + checksum: 10c0/b2701bacb9c8f1e063ca2c1c1d1559cfac491230b95d1b33a273343aa9ad8c62130a9f54a64f381a221d56869df74f938c3a284187e769697db49bc8cc7e7e4a + languageName: node + linkType: hard + "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0" @@ -31063,6 +31082,18 @@ __metadata: languageName: node linkType: hard +"type-plus@npm:^8.0.0-beta.8": + version: 8.0.0-beta.8 + resolution: "type-plus@npm:8.0.0-beta.8" + dependencies: + tersify: "npm:^3.11.1" + unpartial: "npm:^1.0.4" + peerDependencies: + typescript: ">= 5.6.0" + checksum: 10c0/cbd44a56f6264f0909f23ffcfe04e0f57bdde1de9763343258b8e9ed121877dc47c4947623c515bd4ae37a01eac3e5ec974743a694897574e5efab59a0fb94c6 + languageName: node + linkType: hard + "typed-array-buffer@npm:^1.0.3": version: 1.0.3 resolution: "typed-array-buffer@npm:1.0.3" @@ -31429,6 +31460,13 @@ __metadata: languageName: node linkType: hard +"unpartial@npm:^1.0.4": + version: 1.0.5 + resolution: "unpartial@npm:1.0.5" + checksum: 10c0/bdfe89d0712679e22eee654a645493a2aad2428bf9cc0d27d5b56e986001e40c1c70b41407708f33d5ccffe1ced71ee3245e99bb62559fef3d6960feac625a07 + languageName: node + linkType: hard + "unpipe@npm:1.0.0, unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0"