The previous article summarized the Storybook installation and basic use, now it is time to practice, based on the design sketch, complete a task list UI. Let’s try to use a “component-driven development” process, from bottom to top, from component development to final assembly into a page. This way of development can control the complexity of the current development work and focus more on the UI modules that need to be built at present.
Use Excalidraw.com for sketching. Use it to draw patterns with a hand-drawn style. It’s a great tool for creating sketches.
Develop based on use cases
The core component of the list is the task card, and each task takes on a different appearance depending on the current task state. From the design drawing, it mainly contains checkbox (selected or unselected), task description information, task executor picture, and some task information markers (whether there are subtasks, expiration date, repeat status, etc.).
Based on this information, we can first build a data structure that describes the task:
export interface Task{
taskID: string./ / task ID
isComplete: boolean.// Whether it is completed
description: string.// Task descriptionexecutorID? :string.// Task executor IDsubTaskIDs? :string[].// Subtask IDdeadline? :Date.// Deadline. }Copy the code
Note that all information other than the task ID, description, and task completion status is optional (with “? Create a task with a task description. There are several scenarios that this component can present. Let’s rock the code! 😎
The preparatory work
Before you develop the component, plan the possible uses of the component and write a story. We then refine the component based on the story’s description, test the component page using mock data, and render the component independently in the Storybook interface to see if it fits the design.
Create a component draft
Start by creating a simple task card component: SRC /app/ Components/task-card.component.ts
This component has some basic implementations,
- An input property of the type “Task” you just created.
- Two output events: task click event and task completion event.
// src/app/components/task-card.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Task } from 'src/app/models/task';
@Component({
selector: 'app-task-card'.template: `
{{task.description}}
`,})export class TaskCardComponent {
@Input() task: Task;
@Output() clickTask = new EventEmitter<Task>();
@Output() checkTask = new EventEmitter<Task>();
handleTaskCheck(e): void{
e.stopPropagation();
this.checkTask.emit(this.task)
}
handleTaskClick(): void{
this.clickTask.emit(this.task)
}
}
Copy the code
Create the corresponding story
How have a crude, almost shabby component, we in the component folder, to create a new story file (src/app/components/task-card.com ponent. Stories. Ts), to give it to create some test status.
// src/app/components/task-card.component.stories.ts
import { FormsModule } from '@angular/forms';
import { action } from '@storybook/addon-actions';
import { Task } from '.. /models/task';
import { TaskCardComponent } from './task-card.component';
export default {
title: 'Mission card'.component: TaskCardComponent,
// Exclude exports named Data to prevent them from also generating story links
excludeStories: /.*Data$/,
decorators: [
moduleMetadata({
imports: [
// Add dependencies to the [(ngModel)] binding syntax.
FormsModule
],
})
]
};
// Set of events
export const actionsData = {
clickTaskAction: action('clickTask'),
checkTaskAction: action('checkTask'),};// Simulate data
export const taskData: Task = {
id: '1'.description: 'Test Task'.isComplete: false
};
// Template method
const Template = (args: TaskCardComponent) = > ({
props: args,
});
// Create the first basic test, simply displaying the task card interface
export const basic = Template.bind({});
basic.args = {
task: taskData,
checkTask: actionsData.checkTaskAction,
clickTask: actionsData.clickTaskAction
};
basic.storyName = 'Basic presentation';
Copy the code
Note ✨ : here we export two simulations (events, simulations) for reuse between stories, and use “excludeStories” to exclude the two exports that do not need to be rendered by Storybook.
runnpm run storybook
You can see that the component is already running independently in the Storybook sandbox, showing the appearance, input and output properties of the component. And you can modify the input value in real time to see the binding effect of the interface. Click the component to see its event output.
Perfect the quest card
In addition to the most basic state, there are several different scenarios to test to ensure that the component meets the design requirements.
// Display complete task information
export const fullInfo = Template.bind({});
fullInfo.args = {
task: {
...basic.args.task,
isComplete: false.subTasks: [{id:'2'.isComplete: true}, {id:'3'.isComplete: false,}],priority: 'low'.endTime: new Date(),
remindTime: new Date(),
repeat: 'daily',}}; fullInfo.storyName ='Complete Information';
// Display the status of the completed task card
export const completeTask = Template.bind({});
completeTask.args = {
task: { ...basic.args.task, isComplete: true}}; completeTask.storyName ='Completed state';
Copy the code
So far, the three test states have been written, but as you can see, the current component only has a simple checkbox and span, and the renderings of the three stories are not very different from the design at all. So, it’s time to develop the components based on the blueprints. The Storybook is also updated hot as components are modified, making it easy to see the changes in real time until each story is rendered as it should be.
For the sake of space, let’s omit the tedious HTML and CSS code here, and add “billion” little development details to the component. Now the three stories have the following effects respectively.
This article focuses on the development process, so common CSS styling is irrelevant. Note that this component uses Angular Material’s Card, Icon, and Checkbox components, so in the story. ts file, be careful to introduce the required dependencies:
export default {
title: 'Mission card'.decorators: [
moduleMetadata({
imports: [MatCardModule, MatIconModule, MatCheckboxModule],})]}as Meta;
Copy the code
As you can see, you write the @NgModule decorator the same way you would write the @NgModule decorator. You just treat the story. ts as a separate Angular Module.
The task list
Now that the task card component is complete, let’s look at the task list component. The main difference is that it needs to be used in combination with the task card component. The draft design is as follows:
This list contains multiple task cards, you can drag to switch the order, you can display them in the table head according to the completion of task cards, you can click on the upper right corner to add tasks in detail, or you can quickly create tasks at the bottom, in the case of a large number of tasks, to the scroll bar.
So similarly, based on design requirements, we create a basic component and create multiple stories.
// src/component/task-list.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Task } from 'src/app/models/task';
@Component({
selector: 'app-task-card'.template: `
,})export class TaskCardComponent {
@Input() listName:string
@Input() tasks: Task[];
}
Copy the code
Similar to the task card component, then create the story, the length factor does not show the full code, mainly including
- Multiple task card information is passed in to show the basic rendering and the correct display of the completion status of the task list.
- Click on the bottom to quickly create a task. The output event contains information about the new task.
- Rendering of an empty list.
- Rendering under load.
- Scroll bar rendering beyond height.
.
Some things to watch out for are ✨ :
- To combine the task card component, you need to introduce it in the declaration.
decorators: [ moduleMetadata({ declarations: [TaskCardComponent] }), ... ] Copy the code
- Decorator allows you to wrap a list component around an outer div that limits the height of its external environment.
decorators: [ ... (storyFunc) => { const story = storyFunc(); return { ...story, template: `<div style="height: 800px; padding:20px">${story.template}</div>`}; }]Copy the code
After developing the component according to the description of the story, add “hundred million” details again to get:
After skipping the story configuration similar to the task card, as well as the IMPLEMENTATION of HTML and CSS, we can see that the final result has implemented various edge cases according to the design drawing. If necessary, more test cases can also be added, making the component much more reliable.
conclusion
This article took a brief look at the component-driven development process. In my daily UI development process, I usually build a basic page layout and routing structure first, then gradually refine the components of each part, write business logic for the background interface, and complete the page function. This top-down development process has several pain points:
- Early development work is not agile enough, before the actual development of the interface, need to write a good layout, routing, etc. It’s hard to see the final product quickly, and it’s hard to collaborate with the designers in a timely manner.
- Component development does not have a clear index, edge scenarios are not considered enough, it is easy to start coding before the scenario is clearly considered, and components lack reliability.
- Before interconnecting interfaces, it is often necessary to write fake data in components and pollute source code.
- Components lack presentation and explanation, and collaborative use of others’ components often requires source code, or repeated inquiries, resulting in high communication costs.
This article is based on sketches (task cards, task lists), and you can also build larger pages in that order. First, divide components according to the design drawing. In the case of zero routing and business pages, two components are gradually completed from bottom to top:
- Start by building a draft of the component, with a basic structure.
- Analyze the requirements for each scenario used by the component and identify the function points that need to be developed.
- Perfect components based on scenario-specific to achieve small unit agile feedback.
- Test and assemble components in a sandbox environment to keep the source code clean.
In addition to being developed as a component, Storybook can also use MDX (Markdown + JSX) to write documentation and test cases, creating a nice component library official website! So, see you next time! 😎