From 3146415aaf9dc078cd43d0e40e77e696fa67b2e4 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 11 Oct 2023 09:17:18 +0200 Subject: [PATCH 1/6] Add argsToTemplate helper function --- .../angular/src/client/argsToTemplate.test.ts | 90 +++++++++++++++++++ .../angular/src/client/argsToTemplate.ts | 71 +++++++++++++++ code/frameworks/angular/src/client/index.ts | 1 + 3 files changed, 162 insertions(+) create mode 100644 code/frameworks/angular/src/client/argsToTemplate.test.ts create mode 100644 code/frameworks/angular/src/client/argsToTemplate.ts diff --git a/code/frameworks/angular/src/client/argsToTemplate.test.ts b/code/frameworks/angular/src/client/argsToTemplate.test.ts new file mode 100644 index 000000000000..213d34188088 --- /dev/null +++ b/code/frameworks/angular/src/client/argsToTemplate.test.ts @@ -0,0 +1,90 @@ +import { argsToTemplate, ArgsToTemplateOptions } from './argsToTemplate'; // adjust path + +describe('argsToTemplate', () => { + it('should correctly convert args to template string and exclude undefined values', () => { + const args: Record = { + prop1: 'value1', + prop2: undefined, + prop3: 'value3', + }; + const options: ArgsToTemplateOptions = {}; + const result = argsToTemplate(args, options); + expect(result).toBe('[prop1]="prop1" [prop3]="prop3"'); + }); + + it('should include properties from include option', () => { + const args = { + prop1: 'value1', + prop2: 'value2', + prop3: 'value3', + }; + const options: ArgsToTemplateOptions = { + include: ['prop1', 'prop3'], + }; + const result = argsToTemplate(args, options); + expect(result).toBe('[prop1]="prop1" [prop3]="prop3"'); + }); + + it('should include non-undefined properties from include option', () => { + const args: Record = { + prop1: 'value1', + prop2: 'value2', + prop3: undefined, + }; + const options: ArgsToTemplateOptions = { + include: ['prop1', 'prop3'], + }; + const result = argsToTemplate(args, options); + expect(result).toBe('[prop1]="prop1"'); + }); + + it('should exclude properties from exclude option', () => { + const args = { + prop1: 'value1', + prop2: 'value2', + prop3: 'value3', + }; + const options: ArgsToTemplateOptions = { + exclude: ['prop2'], + }; + const result = argsToTemplate(args, options); + expect(result).toBe('[prop1]="prop1" [prop3]="prop3"'); + }); + + it('should exclude properties from exclude option and undefined properties', () => { + const args: Record = { + prop1: 'value1', + prop2: 'value2', + prop3: undefined, + }; + const options: ArgsToTemplateOptions = { + exclude: ['prop2'], + }; + const result = argsToTemplate(args, options); + expect(result).toBe('[prop1]="prop1"'); + }); + + it('should prioritize include over exclude when both options are given', () => { + const args = { + prop1: 'value1', + prop2: 'value2', + prop3: 'value3', + }; + const options: ArgsToTemplateOptions = { + include: ['prop1', 'prop2'], + exclude: ['prop2', 'prop3'], + }; + const result = argsToTemplate(args, options); + expect(result).toBe('[prop1]="prop1" [prop2]="prop2"'); + }); + + it('should work when neither include nor exclude options are given', () => { + const args = { + prop1: 'value1', + prop2: 'value2', + }; + const options: ArgsToTemplateOptions = {}; + const result = argsToTemplate(args, options); + expect(result).toBe('[prop1]="prop1" [prop2]="prop2"'); + }); +}); diff --git a/code/frameworks/angular/src/client/argsToTemplate.ts b/code/frameworks/angular/src/client/argsToTemplate.ts new file mode 100644 index 000000000000..b00a6987942b --- /dev/null +++ b/code/frameworks/angular/src/client/argsToTemplate.ts @@ -0,0 +1,71 @@ +/** + * Options for controlling the behavior of the argsToTemplate function. + * + * @template T The type of the keys in the target object. + */ +export interface ArgsToTemplateOptions { + /** + * An array of keys to specifically include in the output. + * If provided, only the keys from this array will be included in the output, + * irrespective of the `exclude` option. Undefined values will still be excluded from the output. + */ + include?: Array; + /** + * An array of keys to specifically exclude from the output. + * If provided, these keys will be omitted from the output. This option is + * ignored if the `include` option is also provided + */ + exclude?: Array; +} + +/** + * Converts an object of arguments to a string of property bindings and excludes undefined values. + * Why? Because Angular treats undefined values in property bindings as an actual value + * and does not apply the default value of the property as soon as the binding is set. + * This feels counter-intuitive and is a common source of bugs in stories. + * @example + * ```ts + * // component.ts + *ㅤ@Component({ selector: 'example' }) + * export class ExampleComponent { + * ㅤ@Input() input1: string = 'Default Input1'; + * ㅤ@Input() input2: string = 'Default Input2'; + * } + * + * // component.stories.ts + * import { argsToTemplate } from '@storybook/angular'; + * export const Input1: Story = { + * render: (args) => ({ + * props: args, + * // Problem1: This will set input2 to undefined and the internal default value will not be used. + * // Problem2: The default value of input2 will be used, but it is not overridable by the user via controls. + * template: ``, + * }), + * args: { + * // In this Story, we want to set the input1 property, and the internal default property of input2 should be used. + * input1: 'Input 1', + * }, + *}; + * ``` + */ +export function argsToTemplate>( + args: A, + options: ArgsToTemplateOptions = {} +) { + return Object.entries(args) + .flatMap(([key, value]) => { + if (value === undefined) return []; + + if (options.include) { + if (options.include.includes(key)) { + return [`[${key}]="${key}"`]; + } + return []; + } + + if (options.exclude?.includes(key)) return []; + + return [`[${key}]="${key}"`]; + }) + .join(' '); +} diff --git a/code/frameworks/angular/src/client/index.ts b/code/frameworks/angular/src/client/index.ts index bfc209efb4d4..2377678bda2e 100644 --- a/code/frameworks/angular/src/client/index.ts +++ b/code/frameworks/angular/src/client/index.ts @@ -10,6 +10,7 @@ export * from './public-types'; export type { StoryFnAngularReturnType as IStory } from './types'; export { moduleMetadata, componentWrapperDecorator, applicationConfig } from './decorators'; +export { argsToTemplate } from './argsToTemplate'; // optimization: stop HMR propagation in webpack if (typeof module !== 'undefined') module?.hot?.decline(); From c769e4b5daff0c2184424f42594195bfc605bae2 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 11 Oct 2023 09:18:13 +0200 Subject: [PATCH 2/6] Rewrite Angular Stories to CSF 3 --- .../argTypes/doc-button/doc-button.stories.ts | 35 +++-- .../doc-directive/doc-directive.stories.ts | 21 +-- .../doc-injectable/doc-injectable.stories.ts | 21 +-- .../argTypes/doc-pipe/doc-pipe.stories.ts | 21 +-- .../custom-cva-component.stories.ts | 23 ++-- .../attribute-selectors.component.stories.ts | 9 +- .../enums.component.stories.ts | 29 ++-- .../base-button.stories.ts | 11 +- .../icon-button.stories.ts | 13 +- .../ng-content-about-parent.stories.ts | 36 ++--- .../ng-content-simple.stories.ts | 32 +++-- .../component-with-on-destroy.stories.ts | 8 +- .../component-with-on-push/on-push.stories.ts | 18 +-- .../custom-pipes.stories.ts | 37 ++--- .../di.component.stories.ts | 36 ++--- .../styled.component.stories.ts | 11 +- ...ut-selector-ng-component-outlet.stories.ts | 45 +++--- ...ut-selector-ng-factory-resolver.stories.ts | 71 ---------- .../without-selector.stories.ts | 34 +++-- .../ng-module/import-module-chip.stories.ts | 24 ++-- .../import-module-for-root.stories.ts | 42 +++--- .../basics/ng-module/import-module.stories.ts | 43 +++--- .../decorators.stories.ts | 129 ++++++++++-------- .../parameters/bootstrap-options.stories.ts | 12 +- .../app-initializer-use-factory.stories.ts | 6 +- .../issues/12009-unknown-component.stories.ts | 17 +-- 26 files changed, 408 insertions(+), 376 deletions(-) delete mode 100644 code/frameworks/angular/template/stories/basics/component-without-selector/without-selector-ng-factory-resolver.stories.ts diff --git a/code/frameworks/angular/template/stories/argTypes/doc-button/doc-button.stories.ts b/code/frameworks/angular/template/stories/argTypes/doc-button/doc-button.stories.ts index d23f3896359f..11dc4d7cd32d 100644 --- a/code/frameworks/angular/template/stories/argTypes/doc-button/doc-button.stories.ts +++ b/code/frameworks/angular/template/stories/argTypes/doc-button/doc-button.stories.ts @@ -1,24 +1,29 @@ -import { Args } from '@storybook/angular'; +import { Meta, StoryObj, argsToTemplate } from '@storybook/angular'; import { DocButtonComponent } from './doc-button.component'; -export default { +const meta: Meta> = { component: DocButtonComponent, }; -export const Basic = (args: Args) => ({ - props: args, -}); -Basic.args = { label: 'Args test', isDisabled: false }; -Basic.ArgTypes = { - theDefaultValue: { - table: { - defaultValue: { summary: 'Basic default value' }, +export default meta; + +type Story = StoryObj>; + +export const Basic: Story = { + args: { label: 'Args test', isDisabled: false }, + argTypes: { + theDefaultValue: { + table: { + defaultValue: { summary: 'Basic default value' }, + }, }, }, }; -export const WithTemplate = (args: Args) => ({ - props: args, - template: '', -}); -WithTemplate.args = { label: 'Template test', appearance: 'primary' }; +export const WithTemplate: Story = { + args: { label: 'Template test', appearance: 'primary' }, + render: (args) => ({ + props: args, + template: ``, + }), +}; diff --git a/code/frameworks/angular/template/stories/argTypes/doc-directive/doc-directive.stories.ts b/code/frameworks/angular/template/stories/argTypes/doc-directive/doc-directive.stories.ts index b734b93bf40d..e949d8e88252 100644 --- a/code/frameworks/angular/template/stories/argTypes/doc-directive/doc-directive.stories.ts +++ b/code/frameworks/angular/template/stories/argTypes/doc-directive/doc-directive.stories.ts @@ -1,14 +1,19 @@ +import { Meta, StoryObj } from '@storybook/angular'; import { DocDirective } from './doc-directive.directive'; -export default { +const meta: Meta = { component: DocDirective, }; -const modules = { - declarations: [DocDirective], -}; +export default meta; + +type Story = StoryObj; -export const Basic = () => ({ - moduleMetadata: modules, - template: '

DocDirective

', -}); +export const Basic: Story = { + render: () => ({ + moduleMetadata: { + declarations: [DocDirective], + }, + template: '

DocDirective

', + }), +}; diff --git a/code/frameworks/angular/template/stories/argTypes/doc-injectable/doc-injectable.stories.ts b/code/frameworks/angular/template/stories/argTypes/doc-injectable/doc-injectable.stories.ts index eca5e10fb11d..7741bca9ba88 100644 --- a/code/frameworks/angular/template/stories/argTypes/doc-injectable/doc-injectable.stories.ts +++ b/code/frameworks/angular/template/stories/argTypes/doc-injectable/doc-injectable.stories.ts @@ -1,14 +1,19 @@ +import { Meta, StoryObj } from '@storybook/angular'; import { DocInjectableService } from './doc-injectable.service'; -export default { +const meta: Meta = { component: DocInjectableService, }; -const modules = { - provider: [DocInjectableService], -}; +export default meta; + +type Story = StoryObj; -export const Basic = () => ({ - moduleMetadata: modules, - template: '

DocInjectable

', -}); +export const Basic: Story = { + render: () => ({ + moduleMetadata: { + providers: [DocInjectableService], + }, + template: '

DocInjectable

', + }), +}; diff --git a/code/frameworks/angular/template/stories/argTypes/doc-pipe/doc-pipe.stories.ts b/code/frameworks/angular/template/stories/argTypes/doc-pipe/doc-pipe.stories.ts index 6ab616f16ee2..018ab04a9951 100644 --- a/code/frameworks/angular/template/stories/argTypes/doc-pipe/doc-pipe.stories.ts +++ b/code/frameworks/angular/template/stories/argTypes/doc-pipe/doc-pipe.stories.ts @@ -1,14 +1,19 @@ +import { Meta, StoryObj } from '@storybook/angular'; import { DocPipe } from './doc-pipe.pipe'; -export default { +const meta: Meta = { component: DocPipe, }; -const modules = { - declarations: [DocPipe], -}; +export default meta; + +type Story = StoryObj; -export const Basic = () => ({ - moduleMetadata: modules, - template: `

{{ 'DocPipe' | docPipe }}

`, -}); +export const Basic: Story = { + render: () => ({ + moduleMetadata: { + declarations: [DocPipe], + }, + template: `

{{ 'DocPipe' | docPipe }}

`, + }), +}; diff --git a/code/frameworks/angular/template/stories/basics/angular-forms/customControlValueAccessor/custom-cva-component.stories.ts b/code/frameworks/angular/template/stories/basics/angular-forms/customControlValueAccessor/custom-cva-component.stories.ts index 228e2d7b9045..c5fb49653135 100644 --- a/code/frameworks/angular/template/stories/basics/angular-forms/customControlValueAccessor/custom-cva-component.stories.ts +++ b/code/frameworks/angular/template/stories/basics/angular-forms/customControlValueAccessor/custom-cva-component.stories.ts @@ -1,8 +1,8 @@ import { FormsModule } from '@angular/forms'; -import { Meta, StoryFn, moduleMetadata } from '@storybook/angular'; +import { Meta, StoryFn, StoryObj, moduleMetadata } from '@storybook/angular'; import { CustomCvaComponent } from './custom-cva.component'; -export default { +const meta: Meta = { // title: 'Basics / Angular forms / ControlValueAccessor', component: CustomCvaComponent, decorators: [ @@ -17,11 +17,16 @@ export default { ], } as Meta; -export const SimpleInput: StoryFn = () => ({ - props: { - ngModel: 'Type anything', - ngModelChange: () => {}, - }, -}); +export default meta; -SimpleInput.storyName = 'Simple input'; +type Story = StoryObj; + +export const SimpleInput: Story = { + storyName: 'Simple input', + render: () => ({ + props: { + ngModel: 'Type anything', + ngModelChange: () => {}, + }, + }), +}; diff --git a/code/frameworks/angular/template/stories/basics/component-with-complex-selectors/attribute-selectors.component.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-complex-selectors/attribute-selectors.component.stories.ts index d935de5215d7..73894b83b34a 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-complex-selectors/attribute-selectors.component.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-complex-selectors/attribute-selectors.component.stories.ts @@ -1,8 +1,13 @@ +import { Meta, StoryObj } from '@storybook/angular'; import { AttributeSelectorComponent } from './attribute-selector.component'; -export default { +const meta: Meta = { // title: 'Basics / Component / With Complex Selectors', component: AttributeSelectorComponent, }; -export const AttributeSelectors = {}; +export default meta; + +type Story = StoryObj; + +export const AttributeSelectors: Story = {}; diff --git a/code/frameworks/angular/template/stories/basics/component-with-enums/enums.component.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-enums/enums.component.stories.ts index 647ca86d8d67..b79bd371baf3 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-enums/enums.component.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-enums/enums.component.stories.ts @@ -1,4 +1,4 @@ -import { Meta, StoryFn } from '@storybook/angular'; +import { Meta, StoryObj } from '@storybook/angular'; import { EnumsComponent, EnumNumeric, @@ -6,19 +6,22 @@ import { EnumStringValues, } from './enums.component'; -export default { +const meta: Meta = { // title: 'Basics / Component / With Enum Types', component: EnumsComponent, -} as Meta; +}; + +export default meta; + +type Story = StoryObj; -export const Basic: StoryFn = (args) => ({ - props: args, -}); -Basic.args = { - unionType: 'union a', - aliasedUnionType: 'Type Alias 1', - enumNumeric: EnumNumeric.FIRST, - enumNumericInitial: EnumNumericInitial.UNO, - enumStrings: EnumStringValues.PRIMARY, - enumAlias: EnumNumeric.FIRST, +export const Basic: Story = { + args: { + unionType: 'Union A', + aliasedUnionType: 'Type Alias 1', + enumNumeric: EnumNumeric.FIRST, + enumNumericInitial: EnumNumericInitial.UNO, + enumStrings: EnumStringValues.PRIMARY, + enumAlias: EnumNumeric.FIRST, + }, }; diff --git a/code/frameworks/angular/template/stories/basics/component-with-inheritance/base-button.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-inheritance/base-button.stories.ts index a206115f5de7..271e6a3fcc9d 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-inheritance/base-button.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-inheritance/base-button.stories.ts @@ -1,12 +1,15 @@ +import { Meta, StoryObj } from '@storybook/angular'; import { BaseButtonComponent } from './base-button.component'; -export default { +const meta: Meta = { // title: 'Basics / Component / With Inheritance', component: BaseButtonComponent, }; -export const BaseButton = () => ({ - props: { +export default meta; + +export const BaseButton: StoryObj = { + args: { label: 'this is label', }, -}); +}; diff --git a/code/frameworks/angular/template/stories/basics/component-with-inheritance/icon-button.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-inheritance/icon-button.stories.ts index baefe65c4f58..bb5c5fb02bcb 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-inheritance/icon-button.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-inheritance/icon-button.stories.ts @@ -1,13 +1,18 @@ +import { Meta, StoryObj } from '@storybook/angular'; import { IconButtonComponent } from './icon-button.component'; -export default { +const meta: Meta = { // title: 'Basics / Component / With Inheritance', component: IconButtonComponent, }; -export const IconButton = () => ({ - props: { +export default meta; + +type Story = StoryObj; + +export const IconButton: Story = { + args: { icon: 'this is icon', label: 'this is label', }, -}); +}; diff --git a/code/frameworks/angular/template/stories/basics/component-with-ng-content/ng-content-about-parent.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-ng-content/ng-content-about-parent.stories.ts index 0512582aca6e..ae936092f96b 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-ng-content/ng-content-about-parent.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-ng-content/ng-content-about-parent.stories.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import { componentWrapperDecorator, Meta, StoryFn } from '@storybook/angular'; +import { componentWrapperDecorator, Meta, StoryFn, StoryObj } from '@storybook/angular'; @Component({ selector: 'sb-button', @@ -17,7 +17,7 @@ class SbButtonComponent { color = '#5eadf5'; } -export default { +const meta: Meta = { // title: 'Basics / Component / With ng-content / Button with different contents', // Implicitly declares the component to Angular // This will be the component described by the addon docs @@ -35,24 +35,24 @@ export default { }, } as Meta; +export default meta; + +type Story = StoryObj; + // By default storybook uses the default export component if no template or component is defined in the story // So Storybook nests the component twice because it is first added by the componentWrapperDecorator. -export const AlwaysDefineTemplateOrComponent: StoryFn = () => ({}); +export const AlwaysDefineTemplateOrComponent: Story = {}; -export const EmptyButton: StoryFn = () => ({ - template: '', -}); - -export const WithDynamicContentAndArgs: StoryFn = (args) => ({ - template: `${args['content']}`, -}); -WithDynamicContentAndArgs.argTypes = { - content: { control: 'text' }, +export const EmptyButton: Story = { + render: () => ({ + template: '', + }), }; -WithDynamicContentAndArgs.args = { content: 'My button text' }; -export const InH1: StoryFn = () => ({ - template: 'My button in h1', -}); -InH1.decorators = [componentWrapperDecorator((story) => `

${story}

`)]; -InH1.storyName = 'In

'; +export const InH1: Story = { + render: () => ({ + template: 'My button in h1', + }), + decorators: [componentWrapperDecorator((story) => `

${story}

`)], + storyName: 'In

', +}; diff --git a/code/frameworks/angular/template/stories/basics/component-with-ng-content/ng-content-simple.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-ng-content/ng-content-simple.stories.ts index 13cc2eb550c5..71b0fe84df38 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-ng-content/ng-content-simple.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-ng-content/ng-content-simple.stories.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { Meta, StoryFn } from '@storybook/angular/'; +import { Meta, StoryObj } from '@storybook/angular'; @Component({ selector: 'storybook-with-ng-content', @@ -9,21 +9,29 @@ import { Meta, StoryFn } from '@storybook/angular/'; }) class WithNgContentComponent {} -export default { +const meta: Meta = { // title: 'Basics / Component / With ng-content / Simple', component: WithNgContentComponent, } as Meta; -export const OnlyComponent: StoryFn = () => ({}); +export default meta; -export const Default: StoryFn = () => ({ - template: `

This is rendered in ng-content

`, -}); +type Story = StoryObj; -export const WithDynamicContentAndArgs: StoryFn = (args) => ({ - template: `

${args['content']}

`, -}); -WithDynamicContentAndArgs.argTypes = { - content: { control: 'text' }, +export const OnlyComponent: Story = {}; + +export const Default: Story = { + render: () => ({ + template: `

This is rendered in ng-content

`, + }), +}; + +export const WithDynamicContentAndArgs: Story = { + render: (args) => ({ + template: `

${args['content']}

`, + }), + args: { content: 'Default content' }, + argTypes: { + content: { control: 'text' }, + }, }; -WithDynamicContentAndArgs.args = { content: 'Default content' }; diff --git a/code/frameworks/angular/template/stories/basics/component-with-ng-on-destroy/component-with-on-destroy.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-ng-on-destroy/component-with-on-destroy.stories.ts index b833424367bb..9ac53d2dde03 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-ng-on-destroy/component-with-on-destroy.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-ng-on-destroy/component-with-on-destroy.stories.ts @@ -27,7 +27,7 @@ class OnDestroyComponent implements OnInit, OnDestroy { } } -export default { +const meta: Meta = { // title: 'Basics / Component / with ngOnDestroy', component: OnDestroyComponent, parameters: { @@ -37,4 +37,8 @@ export default { }, } as Meta; -export const SimpleComponent: StoryObj = {}; +export default meta; + +type Story = StoryObj; + +export const SimpleComponent: Story = {}; diff --git a/code/frameworks/angular/template/stories/basics/component-with-on-push/on-push.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-on-push/on-push.stories.ts index 6ad452797bc2..5da4366a606f 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-on-push/on-push.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-on-push/on-push.stories.ts @@ -1,7 +1,7 @@ -import { Meta, StoryFn } from '@storybook/angular'; +import { Meta, StoryObj } from '@storybook/angular'; import { OnPushBoxComponent } from './on-push-box.component'; -export default { +const meta: Meta = { // title: 'Basics / Component / With OnPush strategy', component: OnPushBoxComponent, argTypes: { @@ -12,10 +12,12 @@ export default { word: 'The text', bgColor: '#FFF000', }, -} as Meta; +}; -export const ClassSpecifiedComponentWithOnPushAndArgs: StoryFn = (args) => ({ - props: args, -}); -ClassSpecifiedComponentWithOnPushAndArgs.storyName = - 'Class-specified component with OnPush and Args'; +export default meta; + +type Story = StoryObj; + +export const ClassSpecifiedComponentWithOnPushAndArgs: Story = { + storyName: 'Class-specified component with OnPush and Args', +}; diff --git a/code/frameworks/angular/template/stories/basics/component-with-pipe/custom-pipes.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-pipe/custom-pipes.stories.ts index 6c8f9254208f..e9c3037b28ec 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-pipe/custom-pipes.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-pipe/custom-pipes.stories.ts @@ -1,9 +1,9 @@ -import { Meta, StoryFn, moduleMetadata } from '@storybook/angular'; +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; import { CustomPipePipe } from './custom.pipe'; import { WithPipeComponent } from './with-pipe.component'; -export default { +const meta: Meta = { // title: 'Basics / Component / With Pipes', component: WithPipeComponent, decorators: [ @@ -11,21 +11,26 @@ export default { declarations: [CustomPipePipe], }), ], -} as Meta; +}; -export const Simple: StoryFn = () => ({ - props: { - field: 'foobar', - }, -}); +export default meta; + +type Story = StoryObj; -export const WithArgsStory: StoryFn = (args) => ({ - props: args, -}); -WithArgsStory.storyName = 'With args'; -WithArgsStory.argTypes = { - field: { control: 'text' }, +export const Simple: Story = { + render: () => ({ + props: { + field: 'foobar', + }, + }), }; -WithArgsStory.args = { - field: 'Foo Bar', + +export const WithArgsStory: Story = { + storyName: 'With args', + argTypes: { + field: { control: 'text' }, + }, + args: { + field: 'Foo Bar', + }, }; diff --git a/code/frameworks/angular/template/stories/basics/component-with-provider/di.component.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-provider/di.component.stories.ts index fea1bc95c15f..2b676f567d23 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-provider/di.component.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-provider/di.component.stories.ts @@ -1,26 +1,30 @@ -import { Args } from '@storybook/angular'; +import { Args, Meta, StoryObj } from '@storybook/angular'; import { DiComponent } from './di.component'; -export default { +const meta: Meta = { // title: 'Basics / Component / With Provider', component: DiComponent, }; -export const InputsAndInjectDependencies = () => ({ - props: { - title: 'Component dependencies', - }, -}); +export default meta; -InputsAndInjectDependencies.storyName = 'inputs and inject dependencies'; +type Story = StoryObj; -export const InputsAndInjectDependenciesWithArgs = (args: Args) => ({ - props: args, -}); -InputsAndInjectDependenciesWithArgs.storyName = 'inputs and inject dependencies with args'; -InputsAndInjectDependenciesWithArgs.argTypes = { - title: { control: 'text' }, +export const InputsAndInjectDependencies: Story = { + render: () => ({ + props: { + title: 'Component dependencies', + }, + }), + storyName: 'inputs and inject dependencies', }; -InputsAndInjectDependenciesWithArgs.args = { - title: 'Component dependencies', + +export const InputsAndInjectDependenciesWithArgs: Story = { + storyName: 'inputs and inject dependencies with args', + argTypes: { + title: { control: 'text' }, + }, + args: { + title: 'Component dependencies', + }, }; diff --git a/code/frameworks/angular/template/stories/basics/component-with-style/styled.component.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-style/styled.component.stories.ts index 0818ff28cc6f..32116ba05090 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-style/styled.component.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-style/styled.component.stories.ts @@ -1,10 +1,15 @@ +import { Meta, StoryObj } from '@storybook/angular'; import { StyledComponent } from './styled.component'; -export default { +const meta: Meta = { // title: 'Basics / Component / With StyleUrls', component: StyledComponent, }; -export const ComponentWithStyles = () => ({}); +export default meta; -ComponentWithStyles.storyName = 'Component with styles'; +type Story = StoryObj; + +export const ComponentWithStyles: Story = { + storyName: 'Component with styles', +}; diff --git a/code/frameworks/angular/template/stories/basics/component-without-selector/without-selector-ng-component-outlet.stories.ts b/code/frameworks/angular/template/stories/basics/component-without-selector/without-selector-ng-component-outlet.stories.ts index beff6c157988..5e7cbabee628 100644 --- a/code/frameworks/angular/template/stories/basics/component-without-selector/without-selector-ng-component-outlet.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-without-selector/without-selector-ng-component-outlet.stories.ts @@ -1,8 +1,8 @@ import { OnInit, Type, Component, Injector, Input } from '@angular/core'; -import { StoryFn, Meta, componentWrapperDecorator, moduleMetadata } from '@storybook/angular'; +import { Meta, componentWrapperDecorator, moduleMetadata, StoryObj } from '@storybook/angular'; import { WithoutSelectorComponent, WITHOUT_SELECTOR_DATA } from './without-selector.component'; -export default { +const meta: Meta = { // title: 'Basics / Component / without selector / Custom wrapper *NgComponentOutlet', component: WithoutSelectorComponent, decorators: [ @@ -12,6 +12,10 @@ export default { ], } as Meta; +export default meta; + +type Story = StoryObj; + // Advanced example with custom *ngComponentOutlet @Component({ @@ -51,23 +55,22 @@ class NgComponentOutletWrapperComponent implements OnInit { // Live changing of args by controls does not work at the moment. When changing args storybook does not fully // reload and therefore does not take into account the change of provider. -export const WithCustomNgComponentOutletWrapper: StoryFn = (args) => ({ - props: args, -}); -WithCustomNgComponentOutletWrapper.storyName = 'Custom wrapper *NgComponentOutlet'; -WithCustomNgComponentOutletWrapper.argTypes = { - name: { control: 'text' }, - color: { control: 'color' }, -}; -WithCustomNgComponentOutletWrapper.args = { name: 'Color', color: 'green' }; -WithCustomNgComponentOutletWrapper.decorators = [ - moduleMetadata({ - declarations: [NgComponentOutletWrapperComponent], - }), - componentWrapperDecorator(NgComponentOutletWrapperComponent, (args) => ({ - name: args.name, +export const WithCustomNgComponentOutletWrapper: Story = { + storyName: 'Custom wrapper *NgComponentOutlet', + argTypes: { + name: { control: 'text' }, + color: { control: 'color' }, + }, + args: { name: 'Color', color: 'green' }, + decorators: [ + moduleMetadata({ + declarations: [NgComponentOutletWrapperComponent], + }), + componentWrapperDecorator(NgComponentOutletWrapperComponent, (args) => ({ + name: args.name, - color: args['color'], - componentOutlet: WithoutSelectorComponent, - })), -]; + color: args['color'], + componentOutlet: WithoutSelectorComponent, + })), + ], +}; diff --git a/code/frameworks/angular/template/stories/basics/component-without-selector/without-selector-ng-factory-resolver.stories.ts b/code/frameworks/angular/template/stories/basics/component-without-selector/without-selector-ng-factory-resolver.stories.ts deleted file mode 100644 index 5256c9de2894..000000000000 --- a/code/frameworks/angular/template/stories/basics/component-without-selector/without-selector-ng-factory-resolver.stories.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - AfterViewInit, - ComponentFactoryResolver, - Type, - Component, - Input, - ViewChild, - ViewContainerRef, -} from '@angular/core'; -import { StoryFn, Meta, componentWrapperDecorator, moduleMetadata } from '@storybook/angular'; - -import { WithoutSelectorComponent } from './without-selector.component'; - -export default { - // title: 'Basics / Component / without selector / Custom wrapper ComponentFactoryResolver', - component: WithoutSelectorComponent, - decorators: [ - moduleMetadata({ - entryComponents: [WithoutSelectorComponent], - }), - ], -} as Meta; - -// Advanced example with custom ComponentFactoryResolver - -@Component({ selector: 'component-factory-wrapper', template: '' }) -class ComponentFactoryWrapperComponent implements AfterViewInit { - @ViewChild('dynamicInsert', { read: ViewContainerRef }) dynamicInsert: any; - - @Input() - componentOutlet?: Type; - - @Input() - args: any; - - constructor( - private viewContainerRef: ViewContainerRef, - private componentFactoryResolver: ComponentFactoryResolver - ) {} - - ngAfterViewInit() { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory( - this.componentOutlet! - ); - const containerRef = this.viewContainerRef; - containerRef.clear(); - const dynamicComponent = containerRef.createComponent(componentFactory); - Object.assign(dynamicComponent.instance as any, this.args); - } -} - -// Live changing of args by controls does not work at the moment. When changing args storybook does not fully -// reload and therefore does not take into account the change of provider. -export const WithComponentFactoryResolver: StoryFn = (args) => ({ - props: args, -}); -WithComponentFactoryResolver.storyName = 'Custom wrapper ComponentFactoryResolver'; -WithComponentFactoryResolver.argTypes = { - name: { control: 'text' }, - color: { control: 'color' }, -}; -WithComponentFactoryResolver.args = { name: 'Color', color: 'chartreuse' }; -WithComponentFactoryResolver.decorators = [ - moduleMetadata({ - declarations: [ComponentFactoryWrapperComponent], - }), - componentWrapperDecorator(ComponentFactoryWrapperComponent, ({ args }) => ({ - args, - componentOutlet: WithoutSelectorComponent, - })), -]; diff --git a/code/frameworks/angular/template/stories/basics/component-without-selector/without-selector.stories.ts b/code/frameworks/angular/template/stories/basics/component-without-selector/without-selector.stories.ts index dec59310b0c3..a7f635252a69 100644 --- a/code/frameworks/angular/template/stories/basics/component-without-selector/without-selector.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-without-selector/without-selector.stories.ts @@ -1,7 +1,7 @@ -import { StoryFn, Meta, moduleMetadata } from '@storybook/angular'; +import { StoryObj, Meta, moduleMetadata } from '@storybook/angular'; import { WithoutSelectorComponent, WITHOUT_SELECTOR_DATA } from './without-selector.component'; -export default { +const meta: Meta = { // title: 'Basics / Component / without selector', component: WithoutSelectorComponent, decorators: [ @@ -11,20 +11,26 @@ export default { ], } as Meta; -export const SimpleComponent: StoryFn = () => ({}); +export default meta; + +type Story = StoryObj; + +export const SimpleComponent: Story = {}; // Live changing of args by controls does not work for now. When changing args storybook does not fully // reload and therefore does not take into account the change of provider. -export const WithInjectionTokenAndArgs: StoryFn = (args) => ({ - props: args, - moduleMetadata: { - providers: [ - { provide: WITHOUT_SELECTOR_DATA, useValue: { color: args['color'], name: args['name'] } }, - ], +export const WithInjectionTokenAndArgs: StoryObj = { + render: (args) => ({ + props: args, + moduleMetadata: { + providers: [ + { provide: WITHOUT_SELECTOR_DATA, useValue: { color: args['color'], name: args['name'] } }, + ], + }, + }), + argTypes: { + name: { control: 'text' }, + color: { control: 'color' }, }, -}); -WithInjectionTokenAndArgs.argTypes = { - name: { control: 'text' }, - color: { control: 'color' }, + args: { name: 'Color', color: 'red' }, }; -WithInjectionTokenAndArgs.args = { name: 'Color', color: 'red' }; diff --git a/code/frameworks/angular/template/stories/basics/ng-module/import-module-chip.stories.ts b/code/frameworks/angular/template/stories/basics/ng-module/import-module-chip.stories.ts index 14e163e3b9d8..3c3f58851125 100644 --- a/code/frameworks/angular/template/stories/basics/ng-module/import-module-chip.stories.ts +++ b/code/frameworks/angular/template/stories/basics/ng-module/import-module-chip.stories.ts @@ -1,23 +1,25 @@ -import { StoryFn, Meta, moduleMetadata } from '@storybook/angular'; +import { StoryFn, Meta, moduleMetadata, StoryObj } from '@storybook/angular'; import { ChipsModule } from './angular-src/chips.module'; import { ChipComponent } from './angular-src/chip.component'; -export default { +const meta: Meta = { component: ChipComponent, decorators: [ moduleMetadata({ imports: [ChipsModule], }), ], -} as Meta; +}; -export const Chip: StoryFn = (args) => ({ - props: args, -}); +export default meta; -Chip.args = { - displayText: 'Chip', -}; -Chip.argTypes = { - removeClicked: { action: 'Remove icon clicked' }, +type Story = StoryObj; + +export const Chip: Story = { + args: { + displayText: 'Chip', + }, + argTypes: { + removeClicked: { action: 'Remove icon clicked' }, + }, }; diff --git a/code/frameworks/angular/template/stories/basics/ng-module/import-module-for-root.stories.ts b/code/frameworks/angular/template/stories/basics/ng-module/import-module-for-root.stories.ts index 4777d60ccb5b..f94dd25d1b50 100644 --- a/code/frameworks/angular/template/stories/basics/ng-module/import-module-for-root.stories.ts +++ b/code/frameworks/angular/template/stories/basics/ng-module/import-module-for-root.stories.ts @@ -1,9 +1,9 @@ -import { StoryFn, Meta, moduleMetadata } from '@storybook/angular'; +import { StoryFn, Meta, moduleMetadata, StoryObj } from '@storybook/angular'; import { ChipsModule } from './angular-src/chips.module'; import { ChipsGroupComponent } from './angular-src/chips-group.component'; import { CHIP_COLOR } from './angular-src/chip-color.token'; -export default { +const meta: Meta = { // title: 'Basics / NgModule / forRoot() pattern', component: ChipsGroupComponent, decorators: [ @@ -27,24 +27,26 @@ export default { removeChipClick: { action: 'Remove chip' }, removeAllChipsClick: { action: 'Remove all chips clicked' }, }, -} as Meta; +}; -const Template = (): StoryFn => (args) => ({ - props: args, -}); +export default meta; -export const Base = Template(); -Base.storyName = 'Chips group'; +type Story = StoryObj; -export const WithCustomProvider = Template(); -WithCustomProvider.decorators = [ - moduleMetadata({ - providers: [ - { - provide: CHIP_COLOR, - useValue: 'yellow', - }, - ], - }), -]; -WithCustomProvider.storyName = 'Chips group with overridden provider'; +export const Base = { + storyName: 'Chips group', +}; + +export const WithCustomProvider = { + decorators: [ + moduleMetadata({ + providers: [ + { + provide: CHIP_COLOR, + useValue: 'yellow', + }, + ], + }), + ], + storyName: 'Chips group with overridden provider', +}; diff --git a/code/frameworks/angular/template/stories/basics/ng-module/import-module.stories.ts b/code/frameworks/angular/template/stories/basics/ng-module/import-module.stories.ts index 936f27235df7..83c250fc0856 100644 --- a/code/frameworks/angular/template/stories/basics/ng-module/import-module.stories.ts +++ b/code/frameworks/angular/template/stories/basics/ng-module/import-module.stories.ts @@ -1,8 +1,8 @@ -import { StoryFn, Meta, moduleMetadata } from '@storybook/angular'; +import { StoryFn, Meta, moduleMetadata, StoryObj } from '@storybook/angular'; import { ChipsModule } from './angular-src/chips.module'; import { ChipsGroupComponent } from './angular-src/chips-group.component'; -export default { +const meta: Meta = { // title: 'Basics / NgModule / Module with multiple component', component: ChipsGroupComponent, decorators: [ @@ -10,26 +10,27 @@ export default { imports: [ChipsModule], }), ], -} as Meta; +}; -export const ChipsGroup: StoryFn = (args) => ({ - props: args, -}); +export default meta; -ChipsGroup.args = { - chips: [ - { - id: 1, - text: 'Chip 1', - }, - { - id: 2, - text: 'Chip 2', - }, - ], -}; +type Story = StoryObj; -ChipsGroup.argTypes = { - removeChipClick: { action: 'Remove chip' }, - removeAllChipsClick: { action: 'Remove all chips clicked' }, +export const ChipsGroup: Story = { + args: { + chips: [ + { + id: 1, + text: 'Chip 1', + }, + { + id: 2, + text: 'Chip 2', + }, + ], + }, + argTypes: { + removeChipClick: { action: 'Remove chip' }, + removeAllChipsClick: { action: 'Remove all chips clicked' }, + }, }; diff --git a/code/frameworks/angular/template/stories/core/decorators/componentWrapperDecorator/decorators.stories.ts b/code/frameworks/angular/template/stories/core/decorators/componentWrapperDecorator/decorators.stories.ts index 0a69ddb34aba..78fa47cf8c00 100644 --- a/code/frameworks/angular/template/stories/core/decorators/componentWrapperDecorator/decorators.stories.ts +++ b/code/frameworks/angular/template/stories/core/decorators/componentWrapperDecorator/decorators.stories.ts @@ -1,10 +1,16 @@ // your-component.stories.ts -import { Args, Meta, Story, componentWrapperDecorator, moduleMetadata } from '@storybook/angular'; +import { + Args, + Meta, + StoryObj, + componentWrapperDecorator, + moduleMetadata, +} from '@storybook/angular'; import ChildComponent from './child.component'; import ParentComponent from './parent.component'; -export default { +const meta: Meta = { // title: 'Core / Decorators / ComponentWrapperDecorator', component: ChildComponent, decorators: [ @@ -14,72 +20,79 @@ export default { ], args: { childText: 'Child text', childPrivateText: 'Child private text' }, argTypes: { onClickChild: { action: 'onClickChild' } }, -} as Meta; +}; -export const WithTemplate = (args: Args) => ({ - template: `Child Template`, - props: { - ...args, - }, -}); +export default meta; -export const WithComponent = (args: Args) => ({ - props: { - ...args, - }, -}); +type Story = StoryObj; -export const WithLegacyComponent = (args: Args) => ({ - component: ChildComponent, - props: { - ...args, - }, -}); +export const WithTemplate: Story = { + render: (args: Args) => ({ + template: `Child Template`, + props: args, + }), +}; -export const WithComponentWrapperDecorator = (args: Args) => ({ - component: ChildComponent, - props: { - ...args, - }, -}); -WithComponentWrapperDecorator.decorators = [ - moduleMetadata({ declarations: [ParentComponent] }), - componentWrapperDecorator(ParentComponent), -]; +export const WithComponent: Story = {}; -export const WithComponentWrapperDecoratorAndProps = (args: Args) => ({ - component: ChildComponent, - props: { - ...args, - }, -}); -WithComponentWrapperDecoratorAndProps.decorators = [ - moduleMetadata({ declarations: [ParentComponent] }), - componentWrapperDecorator(ParentComponent, { - parentText: 'Parent text', - onClickParent: () => { - console.log('onClickParent'); +export const WithLegacyComponent: Story = { + render: (args: Args) => ({ + component: ChildComponent, + props: args, + }), +}; + +export const WithComponentWrapperDecorator: Story = { + render: (args: Args) => ({ + component: ChildComponent, + props: args, + }), + decorators: [ + moduleMetadata({ declarations: [ParentComponent] }), + componentWrapperDecorator(ParentComponent), + ], +}; + +export const WithComponentWrapperDecoratorAndProps: Story = { + render: (args: Args) => ({ + component: ChildComponent, + props: { + ...args, }, }), -]; + decorators: [ + moduleMetadata({ declarations: [ParentComponent] }), + componentWrapperDecorator(ParentComponent, { + parentText: 'Parent text', + onClickParent: () => { + console.log('onClickParent'); + }, + }), + ], +}; -export const WithComponentWrapperDecoratorAndArgs = (args: Args) => ({ - component: ChildComponent, - props: { - ...args, +export const WithComponentWrapperDecoratorAndArgs: StoryObj<{ + parentText: string; + onClickParent: () => void; +}> = { + render: (args: Args) => ({ + component: ChildComponent, + props: { + ...args, + }, + }), + argTypes: { + parentText: { control: { type: 'text' } }, + onClickParent: { action: 'onClickParent' }, }, -}); -WithComponentWrapperDecoratorAndArgs.argTypes = { - parentText: { control: { type: 'text' } }, - onClickParent: { action: 'onClickParent' }, + decorators: [ + moduleMetadata({ declarations: [ParentComponent] }), + componentWrapperDecorator(ParentComponent, ({ args }) => ({ + parentText: args.parentText, + onClickParent: args.onClickParent, + })), + ], }; -WithComponentWrapperDecoratorAndArgs.decorators = [ - moduleMetadata({ declarations: [ParentComponent] }), - componentWrapperDecorator(ParentComponent, ({ args }) => ({ - parentText: args.parentText, - onClickParent: args.onClickParent, - })), -]; export const WithCustomDecorator = (args: Args) => ({ template: `Child Template`, diff --git a/code/frameworks/angular/template/stories/core/parameters/bootstrap-options.stories.ts b/code/frameworks/angular/template/stories/core/parameters/bootstrap-options.stories.ts index d49a6b4ef4f7..527b10a0082f 100644 --- a/code/frameworks/angular/template/stories/core/parameters/bootstrap-options.stories.ts +++ b/code/frameworks/angular/template/stories/core/parameters/bootstrap-options.stories.ts @@ -1,4 +1,4 @@ -import { Meta, StoryFn } from '@storybook/angular'; +import { Meta, StoryObj } from '@storybook/angular'; import { Component } from '@angular/core'; @Component({ @@ -10,9 +10,13 @@ import { Component } from '@angular/core'; }) class ComponentWithWhitespace {} -export default { +const meta: Meta = { // title: 'Core / Parameters / With Bootstrap Options', component: ComponentWithWhitespace, -} as Meta; +}; -export const WithPreserveWhitespaces: StoryFn = (_args) => ({}); +export default meta; + +type Story = StoryObj; + +export const WithPreserveWhitespaces: Story = {}; diff --git a/code/frameworks/angular/template/stories/others/app-initializer-use-factory/app-initializer-use-factory.stories.ts b/code/frameworks/angular/template/stories/others/app-initializer-use-factory/app-initializer-use-factory.stories.ts index 2dc73b7ad3a0..32c5d1ebe296 100644 --- a/code/frameworks/angular/template/stories/others/app-initializer-use-factory/app-initializer-use-factory.stories.ts +++ b/code/frameworks/angular/template/stories/others/app-initializer-use-factory/app-initializer-use-factory.stories.ts @@ -1,4 +1,4 @@ -import { moduleMetadata, Meta } from '@storybook/angular'; +import { moduleMetadata, Meta, StoryObj } from '@storybook/angular'; import { APP_INITIALIZER } from '@angular/core'; import { action } from '@storybook/addon-actions'; import Button from '../../button.component'; @@ -27,7 +27,9 @@ const meta: Meta
+ `, + styles: [], + standalone: true, +}) +export class Template { + @Input() label = 'default label'; + + @Input() label2 = 'default label2'; + + @Output() changed = new EventEmitter(); + + inc() { + this.changed.emit('Increase'); + } +} diff --git a/code/frameworks/angular/template/stories/basics/component-with-template/template.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-template/template.stories.ts new file mode 100644 index 000000000000..55639870f262 --- /dev/null +++ b/code/frameworks/angular/template/stories/basics/component-with-template/template.stories.ts @@ -0,0 +1,24 @@ +import { Meta, StoryObj, argsToTemplate } from '@storybook/angular'; +import { Template } from './template.component'; + +const meta: Meta