iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 12
1
Modern Web

玩轉 Storybook系列 第 12

玩轉 Storybook: Day 12 Composite component - Vue

接下來就來把Task元件組裝成TaskList吧

TaskList

可以把TaskList規劃成四種狀態

  • Default - 當 Task 都是 defualt 狀態的時候
  • Pinned - 當有 default task 也有 Pinned task 時,Pinned Task 要擺在 TaskList 的最上面
  • Empty - 當 Task 全數完成清空的時候
  • Loading - 當 Task 正在被載入的時候

Get Setup

接下來就可以來建立 TaskList.vue 及 TaskList.stories.vue

TaskList.vue

<!--src/components/TaskList.vue-->
<template>
  <div>
    <div class="list-items" v-if="loading">loading</div>
    <div class="list-items" v-if="noTasks && !this.loading">empty</div>
    <div class="list-items" v-if="showTasks">
      <task
        v-for="(task, index) in tasks"
        :key="index"
        :task="task"
        @archiveTask="$emit('archive-task', $event)"
        @pinTask="$emit('pin-task', $event)"
      />
    </div>
  </div>
</template>

<script>
import Task from "./Task";
export default {
  name: "task-list",
  props: {
    loading: {
      type: Boolean,
      default: false
    },
    tasks: {
      type: Array,
      default: () => []
    }
  },
  components: {
    Task
  },
  computed: {
    noTasks() {
      return this.tasks.length === 0;
    },
    showTasks() {
      return !this.loading && !this.noTasks;
    }
  }
};
</script>

TaskList.stories.js

//src/components/TaskList.stories.js
import TaskList from "./TaskList";
import { taskData, actionsData } from "./Task.stories";

const paddedList = () => {
  return {
    template: '<div style="padding: 3rem;"><story/></div>'
  };
};

export default {
  title: "TaskList",
  excludeStories: /.*Data$/,
  decorators: [paddedList]
};

export const defaultTasksData = [
  { ...taskData, id: "1", title: "Task 1" },
  { ...taskData, id: "2", title: "Task 2" },
  { ...taskData, id: "3", title: "Task 3" },
  { ...taskData, id: "4", title: "Task 4" },
  { ...taskData, id: "5", title: "Task 5" },
  { ...taskData, id: "6", title: "Task 6" }
];
export const withPinnedTasksData = [
  ...defaultTasksData.slice(0, 5),
  { id: "6", title: "Task 6 (pinned)", state: "TASK_PINNED" }
];

// default TaskList state
export const Default = () => ({
  components: { TaskList },
  template: `<task-list :tasks="tasks" @archiveTask="onArchiveTask" @pinTask="onPinTask"/>`,
  props: {
    tasks: {
      default: () => defaultTasksData
    }
  },
  methods: actionsData
});

// tasklist with pinned tasks
export const WithPinnedTasks = () => ({
  components: { TaskList },
  template: `<task-list :tasks="tasks" @archiveTask="onArchiveTask" @pinTask="onPinTask"/>`,
  props: {
    tasks: {
      default: () => withPinnedTasksData
    }
  },
  methods: actionsData
});

// tasklist in loading state
export const Loading = () => ({
  components: { TaskList },
  template: `<task-list loading @archiveTask="onArchiveTask" @pinTask="onPinTask"/>`,
  methods: actionsData
});

// tasklist no tasks
export const Empty = () => ({
  components: { TaskList },
  template: `<task-list @archiveTask="onArchiveTask" @pinTask="onPinTask"/>`,
  methods: actionsData
});

在以上範例有一個decorators的設定style="padding: 3rem;",在Decorators的單元有介紹到,可以使用Decorators為要描述的Stories製作元件以外的版型設定。

程式碼設置完成後,目前運行的畫面如下圖

Default Task 的部分是完整呈現了

但 WithPinnedTasks 的 Pinned Task 沒有出現在最上面

而 Loading 及 Empty 則還需要加上樣式調整

完善 TaskList

<template>
  <div>
    <div v-if="loading">
      <div class="loading-item" v-for="(n, index) in 5" :key="index">
        <span class="glow-checkbox" />
        <span class="glow-text">
          <span>Loading</span> <span>cool</span> <span>state</span>
        </span>
      </div>
    </div>
    <div class="list-items" v-if="noTasks && !this.loading">
      <div class="wrapper-message">
        <span class="icon-check" />
        <div class="title-message">You have no tasks</div>
        <div class="subtitle-message">Sit back and relax</div>
      </div>
    </div>
    <div class="list-items" v-if="showTasks">
      <task
        v-for="(task, index) in tasksInOrder"
        :key="index"
        :task="task"
        @archiveTask="$emit('archive-task', $event)"
        @pinTask="$emit('pin-task', $event)"
      />
    </div>
  </div>
</template>

<script>
import Task from "./Task";
export default {
  name: "task-list",
  props: {
    loading: {
      type: Boolean,
      default: false
    },
    tasks: {
      type: Array,
      default: () => []
    }
  },
  components: {
    Task
  },
  computed: {
    noTasks() {
      return this.tasks.length === 0;
    },
    showTasks() {
      return !this.loading && !this.noTasks;
    },
    tasksInOrder() {
      return [
        ...this.tasks.filter(t => t.state === "TASK_PINNED"),
        ...this.tasks.filter(t => t.state !== "TASK_PINNED")
      ];
    }
  }
};
</script>

從差異修改的地方可以看到 Loading 及 Empty 已加上樣式調整

而 tasks 改用 tasksInorder 計算屬性

用來整理資料,把 Pinned Task 放在資料的最前頭

小結

目前運行畫面:

整個元件建置的過程,也沒有啟動專案本身的運行環境,僅僅只有啟動Storybook!

程式碼下載

tag: composite-component https://git.io/JURQp

Reference

Composite Component:Vue


上一篇
玩轉 Storybook: Day 11 Simple component - Vue
下一篇
玩轉 Storybook: Day 13 Simple and Composite - Angular
系列文
玩轉 Storybook30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言