diff --git a/addons/storyshots/storyshots-core/src/frameworks/angular/helpers.ts b/addons/storyshots/storyshots-core/src/frameworks/angular/helpers.ts index 734be07cf239..4304a96ff4cb 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/angular/helpers.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/angular/helpers.ts @@ -32,9 +32,24 @@ const createComponentFromTemplate = (template: string) => { }; export const initModuleData = (storyObj: NgStory): any => { - const { component, template, props, moduleMetadata = {} } = storyObj; + const { + component, + template, + props, + moduleMetadata = {}, + requiresComponentDeclaration = true, + } = storyObj; + + const isCreatingComponentFromTemplate = Boolean(template); + + const AnnotatedComponent = isCreatingComponentFromTemplate + ? createComponentFromTemplate(template) + : component; - const AnnotatedComponent = template ? createComponentFromTemplate(template) : component; + const componentDeclarations = + isCreatingComponentFromTemplate || requiresComponentDeclaration + ? [AppComponent, AnnotatedComponent] + : [AppComponent]; const story = { component: AnnotatedComponent, @@ -42,7 +57,7 @@ export const initModuleData = (storyObj: NgStory): any => { }; const moduleMeta = getModuleMeta( - [AppComponent, AnnotatedComponent], + componentDeclarations, [AnnotatedComponent], [AppComponent], story, diff --git a/addons/storyshots/storyshots-core/src/frameworks/angular/types.ts b/addons/storyshots/storyshots-core/src/frameworks/angular/types.ts index 3582cb89f8a3..36793bf7fb76 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/angular/types.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/angular/types.ts @@ -12,6 +12,7 @@ export interface ICollection { export interface NgStory { component?: any; + requiresComponentDeclaration?: boolean; props: ICollection; propsMeta?: ICollection; moduleMetadata?: NgModuleMetadata; diff --git a/app/angular/index.d.ts b/app/angular/index.d.ts index 6675e3358162..58467e592502 100644 --- a/app/angular/index.d.ts +++ b/app/angular/index.d.ts @@ -45,6 +45,7 @@ export interface IStory { props?: ICollection; moduleMetadata?: Partial; component?: any; + requiresComponentDeclaration?: boolean; template?: string; } diff --git a/app/angular/src/client/preview/angular/helpers.ts b/app/angular/src/client/preview/angular/helpers.ts index 9208cd451075..b13c51e78e76 100644 --- a/app/angular/src/client/preview/angular/helpers.ts +++ b/app/angular/src/client/preview/angular/helpers.ts @@ -49,9 +49,25 @@ const createComponentFromTemplate = (template: string, styles: string[]) => { const initModule = (storyFn: IStoryFn) => { const storyObj = storyFn(); - const { component, template, props, styles, moduleMetadata = {} } = storyObj; + const { + component, + template, + props, + styles, + moduleMetadata = {}, + requiresComponentDeclaration = true, + } = storyObj; + + const isCreatingComponentFromTemplate = Boolean(template); + + const AnnotatedComponent = isCreatingComponentFromTemplate + ? createComponentFromTemplate(template, styles) + : component; - const AnnotatedComponent = template ? createComponentFromTemplate(template, styles) : component; + const componentDeclarations = + isCreatingComponentFromTemplate || requiresComponentDeclaration + ? [AppComponent, AnnotatedComponent] + : [AppComponent]; const story = { component: AnnotatedComponent, @@ -59,7 +75,7 @@ const initModule = (storyFn: IStoryFn) => { }; return getModule( - [AppComponent, AnnotatedComponent], + componentDeclarations, [AnnotatedComponent], [AppComponent], story, diff --git a/app/angular/src/client/preview/angular/types.ts b/app/angular/src/client/preview/angular/types.ts index 0633eabea0ea..408911dcbf0a 100644 --- a/app/angular/src/client/preview/angular/types.ts +++ b/app/angular/src/client/preview/angular/types.ts @@ -12,6 +12,7 @@ export interface ICollection { export interface NgStory { component?: any; + requiresComponentDeclaration?: boolean; props: ICollection; propsMeta?: ICollection; moduleMetadata?: NgModuleMetadata; diff --git a/examples/angular-cli/src/stories/module-context/__snapshots__/module-context.stories.storyshot b/examples/angular-cli/src/stories/module-context/__snapshots__/module-context.stories.storyshot new file mode 100644 index 000000000000..a7645463e23a --- /dev/null +++ b/examples/angular-cli/src/stories/module-context/__snapshots__/module-context.stories.storyshot @@ -0,0 +1,134 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Custom|Feature Module as Context Component with default providers 1`] = ` + + + + My Chíp + +
+ + ✕ + +
+
+
+`; + +exports[`Storyshots Custom|Feature Module as Context Component with overridden provider 1`] = ` + + + + My Chíp + +
+ + ✕ + +
+
+
+`; + +exports[`Storyshots Custom|Feature Module as Context Component with self and dependencies declared in its feature module 1`] = ` + + + + + + Chíp 1 + +
+ + ✕ + +
+
+ + + Chíp 2 + +
+ + ✕ + +
+
+ +
+ Remove All +
+
+
+`; diff --git a/examples/angular-cli/src/stories/module-context/chip-color.token.ts b/examples/angular-cli/src/stories/module-context/chip-color.token.ts new file mode 100644 index 000000000000..5d0a1991490b --- /dev/null +++ b/examples/angular-cli/src/stories/module-context/chip-color.token.ts @@ -0,0 +1,3 @@ +import { InjectionToken } from '@angular/core'; + +export const CHIP_COLOR = new InjectionToken('chip-color'); diff --git a/examples/angular-cli/src/stories/module-context/chip-text.pipe.ts b/examples/angular-cli/src/stories/module-context/chip-text.pipe.ts new file mode 100644 index 000000000000..68e23182385d --- /dev/null +++ b/examples/angular-cli/src/stories/module-context/chip-text.pipe.ts @@ -0,0 +1,29 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'chipText', +}) +export class ChipTextPipe implements PipeTransform { + transform(value: string): string { + return Array.from(value) + .map(char => this.accentVowel(char)) + .join(''); + } + + accentVowel(char: string): string { + switch (char) { + case 'a': + return 'á'; + case 'e': + return 'é'; + case 'i': + return 'í'; + case 'o': + return 'ó'; + case 'u': + return 'ú'; + default: + return char; + } + } +} diff --git a/examples/angular-cli/src/stories/module-context/chip.component.ts b/examples/angular-cli/src/stories/module-context/chip.component.ts new file mode 100644 index 000000000000..cb73f323e643 --- /dev/null +++ b/examples/angular-cli/src/stories/module-context/chip.component.ts @@ -0,0 +1,68 @@ +import { + Component, + Input, + ChangeDetectionStrategy, + Output, + EventEmitter, + Inject, + HostBinding, +} from '@angular/core'; +import { CHIP_COLOR } from './chip-color.token'; + +@Component({ + selector: 'storybook-chip', + template: ` + {{ displayText | chipText }} +
+ +
+ `, + styles: [ + ` + :host { + display: inline-flex; + cursor: default; + align-items: center; + justify-content: center; + padding: 0.2rem 0.5rem; + border-radius: 1rem; + border: solid 0.1rem transparent; + } + :host:hover { + border-color: black; + } + .text { + font-family: inherit; + } + .remove { + margin-left: 1rem; + background-color: lightgrey; + border-radius: 50%; + width: 1rem; + height: 1rem; + text-align: center; + } + .remove:hover { + background-color: palevioletred; + } + .x { + display: inline-block; + color: #eeeeee; + text-align: center; + vertical-align: baseline; + line-height: 1rem; + } + `, + ], +}) +export class ChipComponent { + @Input() displayText: string; + + @Output() removeClicked = new EventEmitter(); + + @HostBinding('style.background-color') backgroundColor: string; + + constructor(@Inject(CHIP_COLOR) chipColor: string) { + this.backgroundColor = chipColor; + } +} diff --git a/examples/angular-cli/src/stories/module-context/chips-group.component.ts b/examples/angular-cli/src/stories/module-context/chips-group.component.ts new file mode 100644 index 000000000000..0015fd32cd8c --- /dev/null +++ b/examples/angular-cli/src/stories/module-context/chips-group.component.ts @@ -0,0 +1,49 @@ +import { Component, Input, ChangeDetectionStrategy, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'storybook-chips-group', + template: ` + +
+ Remove All +
+ `, + styles: [ + ` + :host { + display: flex; + align-content: center; + padding: 0.5rem; + background-color: lightgrey; + border-radius: 0.5rem; + width: fit-content; + } + .chip:not(:first-of-type) { + margin-left: 0.5rem; + } + .remove-all { + margin-left: 0.5rem; + padding: 0.2rem 0.5rem; + border: solid 0.1rem #eeeeee; + } + .remove-all:hover { + background-color: palevioletred; + } + `, + ], +}) +export class ChipsGroupComponent { + @Input() chips: { + id: number; + text: string; + }[]; + + @Output() removeChipClick = new EventEmitter(); + + @Output() removeAllChipsClick = new EventEmitter(); +} diff --git a/examples/angular-cli/src/stories/module-context/chips.module.ts b/examples/angular-cli/src/stories/module-context/chips.module.ts new file mode 100644 index 000000000000..606a96797665 --- /dev/null +++ b/examples/angular-cli/src/stories/module-context/chips.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ChipComponent } from './chip.component'; +import { ChipsGroupComponent } from './chips-group.component'; +import { ChipTextPipe } from './chip-text.pipe'; +import { CHIP_COLOR } from './chip-color.token'; + +@NgModule({ + imports: [CommonModule], + exports: [ChipsGroupComponent], + declarations: [ChipsGroupComponent, ChipComponent, ChipTextPipe], + providers: [ + { + provide: CHIP_COLOR, + useValue: '#eeeeee', + }, + ], +}) +export class ChipsModule {} diff --git a/examples/angular-cli/src/stories/module-context/module-context.stories.ts b/examples/angular-cli/src/stories/module-context/module-context.stories.ts new file mode 100644 index 000000000000..e3dd4fbf15c8 --- /dev/null +++ b/examples/angular-cli/src/stories/module-context/module-context.stories.ts @@ -0,0 +1,78 @@ +import { storiesOf, moduleMetadata } from '@storybook/angular'; +import { withKnobs, text, object } from '@storybook/addon-knobs'; +import { action } from '@storybook/addon-actions'; + +import { ChipsModule } from './chips.module'; +import { ChipsGroupComponent } from './chips-group.component'; +import { ChipComponent } from './chip.component'; +import { CHIP_COLOR } from './chip-color.token'; + +storiesOf('Custom|Feature Module as Context', module) + .addDecorator(withKnobs) + .addDecorator( + moduleMetadata({ + imports: [ChipsModule], + }) + ) + .add( + 'Component with self and dependencies declared in its feature module', + () => { + const props: { [K in keyof ChipsGroupComponent]?: any } = { + chips: object('Chips', [ + { + id: 1, + text: 'Chip 1', + }, + { + id: 2, + text: 'Chip 2', + }, + ]), + removeChipClick: action('Remove chip'), + removeAllChipsClick: action('Remove all chips clicked'), + }; + return { + component: ChipsGroupComponent, + requiresComponentDeclaration: false, + props, + }; + }, + { + notes: ` + This component includes a child component, a pipe, and a default provider, all which come from + the specified feature module. + + This behavior is possible by setting the "requiresComponentDeclaration" flag to false. + `.replace(/ {1,}/g, ' '), + } + ) + .add('Component with default providers', () => { + const props: { [K in keyof ChipComponent]?: any } = { + displayText: text('Display Text', 'My Chip'), + removeClicked: action('Remove icon clicked'), + }; + return { + component: ChipComponent, + requiresComponentDeclaration: false, + props, + }; + }) + .add('Component with overridden provider', () => { + const props: { [K in keyof ChipComponent]?: any } = { + displayText: text('Display Text', 'My Chip'), + removeClicked: action('Remove icon clicked'), + }; + return { + component: ChipComponent, + moduleMetadata: { + providers: [ + { + provide: CHIP_COLOR, + useValue: 'yellow', + }, + ], + }, + requiresComponentDeclaration: false, + props, + }; + }); diff --git a/examples/angular-cli/src/stories/on-push/__snapshots__/on-push.stories.storyshot b/examples/angular-cli/src/stories/on-push/__snapshots__/on-push.stories.storyshot index 1458c361f366..3fe8857f4718 100644 --- a/examples/angular-cli/src/stories/on-push/__snapshots__/on-push.stories.storyshot +++ b/examples/angular-cli/src/stories/on-push/__snapshots__/on-push.stories.storyshot @@ -7,7 +7,7 @@ exports[`Storyshots Core|OnPush Class-specified component with OnPush and Knobs target={[Function ViewContainerRef_]} > Word of the day: OnPush