返回
近期加入了一个三期项目,项目业务已比较冗余。前人也抽离了很多公用组件,But
So,定了个小目标,给项目配置storybook,实现组件可视化ヾ(◍°∇°◍)ノ゙ 但是,再配置storybook,发现它已经从安装、配置到写story的方式都发生了翻天覆地的变化🤷♀️
在当前项目下执行
npx sb init
会自动完成,storybook的配置
然后执行
npm run storybook
完成storybook的启动~ http://localhost:6006
目前自动安装的storybook的版本是“6.0.28”(使用的corejs版本是3.7.0),但是angular版本是“8.0.0”(配置的corejs的版本系列是2),导致启动后,满屏爆红~ 解决方法:将corejs升级到了版本3系列
组件文件
// src/app/components/task.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-task',
template: `
<div class="list-item">
<input type="text" [value]="task.title" readonly="true" />
</div>
`,
})
export class TaskComponent implements OnInit {
title: string;
@Input() task: any;
// tslint:disable-next-line: no-output-on-prefix
@Output() onPinTask: EventEmitter<any> = new EventEmitter();
// tslint:disable-next-line: no-output-on-prefix
@Output() onArchiveTask: EventEmitter<any> = new EventEmitter();
constructor() {}
ngOnInit() {}
}
story文件
// *.stories.ts
// src/app/components/task.stories.ts
import { action } from '@storybook/addon-actions';
import { TaskComponent } from './task.component';
export default {
title: 'Task',
excludeStories: /.*Data$/,
};
export const actionsData = {
onPinTask: action('onPinTask'),
onArchiveTask: action('onArchiveTask'),
};
export const taskData = {
id: '1',
title: 'Test Task',
state: 'Task_INBOX',
updated_at: new Date(2019, 0, 1, 9, 0),
};
export const Default = () => ({
component: TaskComponent,
props: {
task: taskData,
onPinTask: actionsData.onPinTask,
onArchiveTask: actionsData.onArchiveTask,
},
});
// pinned task state
export const Pinned = () => ({
component: TaskComponent,
props: {
task: {
...taskData,
state: 'TASK_PINNED',
},
onPinTask: actionsData.onPinTask,
onArchiveTask: actionsData.onArchiveTask,
},
});
// archived task state
export const Archived = () => ({
component: TaskComponent,
props: {
task: {
...taskData,
state: 'TASK_ARCHIVED',
},
onPinTask: actionsData.onPinTask,
onArchiveTask: actionsData.onArchiveTask,
},
});
对于组件内部使用了 <ng-content/>
(里面可以填充任何内容),在写story时,需要使用template写入代码
// *.component.html
<button class="button">
<img src={{iconSrc}} *ngIf="iconSrc" class="icon" />
<ng-content></ng-content>
</button>
// *.stories.ts
import {moduleMetadata} from '@storybook/angular'
export default {
decorators: [
moduleMetadata({
declarations: [ButtonComponent],
})
]
}
const Template = (args) => ({
component: ButtonComponent,
props: args,
template: `<button-component> Put your content here </button-component>`,
});
export const Normal = Template.bind({})
Normal.args = {}
// *.stories.ts
import {moduleMetadata} from '@storybook/angular'
import {RouterModule} from '@angular/router'
import {APP_BASE_HREF} from '@angular/common'
export default {
decorators: [
moduleMetadata({
imports: [RouterModule.forRoot([],{useHash: true})],
providers: [{provide:APP_BASE_HREF, useValue: '/}]
})
]
}
对于公用的 service 和 directive 组件,如果想要进行可视化,可以针对使用这些工具的*.component.ts 文件写 *.stories.ts
// *.stories.ts
import {NgZorroAntdModule} from 'ng-zerro-antd'
export default {
decorators: [
moduleMetadata({
imports: [NgZorroAntdModule.forRoot()]
})
]
}
对于组件传入的@Input 可以根据数据类型,设置到 controls里面,这样,修改controls里面的数据的值,可以实时看到 组件的样式变化。
前提,安装插件 @storybook/addon-controls
// *.stories.ts
export default {
title: 'Gizmo',
component: Gizmo,
argTypes: {
// 布尔
isAgree: {
control: { type: 'boolean' },
},
// 数值
score: {
control: { type: 'number' },
},
// 区间
width: {
control: { type: 'range', min: 400, max: 1200, step: 50 },
},
// 单选
loadingState: {
control: {
type: 'inline-radio',
options: ['loading', 'error', 'ready'],
},
},
// 多选
icon: {
control: {
type: 'select',
options: Object.keys(iconMap),
},
},
},
};
针对具有自适应样式的组件,可以给它下面的每个story配置不同的屏幕大小,以便可视化各个屏幕大小下的样式。
前提,安装插件 @storybook/addon-viewport
在 .storybook/preview.js 中增加自定义屏幕大小设置
import { MINIMAL_VIEWPORTS} from '@storybook/addon-viewport';
const customViewports = {
kindleFire2: {
name: 'Kindle Fire 2',
styles: {
width: '600px',
height: '963px',
},
},
kindleFireHD: {
name: 'Kindle Fire HD',
styles: {
width: '533px',
height: '801px',
},
},
mobile: {
name: 'mobile',
styles: {
width: '375px',
height: '801px',
},
},
};
export const parameters = {
viewport: {
viewports: {
...MINIMAL_VIEWPORTS,
...customViewports,
},
},
};
使用自定义的 mobile 屏幕
// *.stories.ts
export default {
parameters: {
viewport: {
defaultViewport: 'mobile'
}
}
}
有些组件的宽高依赖于它的父组件时,则需要在组件的外面包括一层代码,做简单的样式限制,可以在默认模块写入 template
// *.stories.ts
export const Default = () => ({
component: TaskListComponent,
template: `
<div style="padding: 3rem">
<app-task-list [tasks]="tasks" (onPinTask)="onPinTask($event)" (onArchiveTask)="onArchiveTask($event)"></app-task-list>
</div>
`,
props: {
tasks: defaultTasksData,
onPinTask: actionsData.onPinTask,
onArchiveTask: actionsData.onArchiveTask,
},
});
🌰有一个组件——taks.component.ts,用于展示任务列表,包含的业务
// *.stories.ts
import { action } from '@storybook/addon-actions';
import {moduleMetadata,Meta,Story} from '@storybook/angular'
import {RouterModule} from '@angular/router'
import {APP_BASE_HREF} from '@angular/common'
import {NgZorroAntdModule} from 'ng-zerro-antd'
import { TaskComponent } from './task.component';
import { EmptyComponent } from './empty.component'; //使用其他组件时,需要引入该组件,并在declarations中声明
import {listMap} from './const'
// @output属性事件
const actionsData = {
handleGetMore: action('handleGetMore'),
};
export default {
title: 'Components/Task',
component: TaskListComponent,
decorators: [
moduleMetadata({
declarations:[EmptyComponent,TaskListComponent]
imports: [RouterModule.forRoot([],{useHash: true}),NgZorroAntdModule.forRoot()],
providers: [{provide:APP_BASE_HREF, useValue: '/}]
})
],
argTypes: {
// 布尔
isEmpty: {
control: { type: 'boolean' },
},
// 区间
pageCount: {
control: { type: 'range', min: 400, max: 1200, step: 50 },
},
// 多选
listType: {
control: {
type: 'select',
options: Object.keys(listMap),
},
},
},
} as Meta;
const Template:Story<TaskListComponent> = (args:TaskListComponent) => ({
component:TaskListComponent,
props: args,
template: `
<div style="padding: 3rem">
<app-task-list [isEmpty]="isEmpty" [listType]="listType" (handleGetMore)="handleGetMore($event)">
<h1>这里是title</h1>
</app-task-list>
</div>
`
})
export const Init = Template.bind({})
Init.args = {
isEmpty: true,
pageCount: 30,
handleGetMore: actionsData.handleGetMore
}
export const InitMobile = Template.bind({})
InitMobile.args = {
isEmpty: false,
pageCount: 30,
handleGetMore: actionsData.handleGetMore
}
InitMobile.parameters = {
viewport: {
defaultViewport: 'mobile'
}
}