UNPKG

@spaceone/design-system

Version:
855 lines (823 loc) • 23.2 kB
import { Meta, Canvas, Story, ArgsTable } from '@storybook/addon-docs/blocks'; import PTreeNode from './PTreeNode.vue'; import { reactive, toRefs } from '@vue/composition-api'; <Meta title='Data Display/Tree/Tree Node' parameters={{ design: { type: 'figma', url: 'https://www.figma.com/file/wq4wSowBcADBuUrMEZLz6i/SpaceONE-Console-Design?node-id=5443%3A141753', }, }} argTypes={{ data: { name: 'data', type: {name: 'any', required: true}, description: 'data to display', defaultValue: 'Data', table: { type: { summary: 'any' }, defaultValue: { summary: '' }, category: 'props' }, control: { type: 'object' } }, level: { name: 'level', type: {name: 'number'}, description: `Depth of the node. <br/> If 0 is given, the node is not displayed, so a value of 1 or more must be given.`, defaultValue: 1, table: { type: { summary: 'number' }, defaultValue: { summary: 1 }, category: 'props' }, control: { type: 'number' } }, index: { name: 'index', type: {name: 'number'}, description: 'Index of the node', defaultValue: 0, table: { type: { summary: 'number' }, defaultValue: { summary: 0 }, category: 'props' }, control: { type: 'number' } }, padSize: { name: 'padSize', type: {name: 'string'}, description: 'Padding size of the node to display depth', defaultValue: '1rem', table: { type: { summary: 'string' }, defaultValue: { summary: '`1rem`' }, category: 'props' }, control: { type: 'text' } }, disableToggle: { name: 'disableToggle', type: {name: 'boolean'}, description: 'Value that decides disable toggle or not', defaultValue: false, table: { type: { summary: 'boolean' }, defaultValue: { summary: false }, category: 'props' }, control: { type: 'boolean' } }, selectOptions: { name: 'selectOptions', type: {name: `object`}, description: `Options for node selection.`, defaultValue: {}, table: { type: { summary: 'object' }, defaultValue: { summary: '{}' }, category: 'props' }, control: { type: 'object' } }, editOptions: { name: 'editOptions', type: {name: `object`}, description: `Options for inline edit.`, defaultValue: {}, table: { type: { summary: 'object' }, defaultValue: { summary: '{}' }, category: 'props' }, control: { type: 'object' } }, dragOptions: { name: 'dragOptions', type: {name: `object`}, description: `Options for drag & drop.`, defaultValue: {}, table: { type: { summary: 'object' }, defaultValue: { summary: '{}' }, category: 'props' }, control: { type: 'object' } }, getClassNames: { name: 'getClassNames', type: {name: `func`}, description: `Function that returns class names. <br/> \` (item: TreeItem<T>) => string|string[]|object \` `, defaultValue: () => ({}), table: { type: { summary: 'func' }, defaultValue: { summary: '() => ({})' }, category: 'props' }, control: { type: 'object' } }, getDefaultNode: { name: 'getDefaultNode', type: {name: `func`}, description: `Function that returns default node. <br/> \` (data: T) => TreeNode<T> \` `, defaultValue: data => ({ _id: Math.floor(Math.random() * Date.now()), data, children: false, expanded: false, selected: false, loading: false, }), table: { type: { summary: 'func' }, defaultValue: { summary: ` data => ({ _id: Math.floor(Math.random() * Date.now()), data, children: false, expanded: false, selected: false, loading: false, })` }, category: 'props' }, control: { type: 'object' } }, parent: { name: 'parent', type: {name: `object`}, description: `The parent node`, defaultValue: null, table: { type: { summary: 'object' }, defaultValue: { summary: `null` }, category: 'props' }, control: { type: 'object' } }, children: { name: 'children', type: {name: ['array', 'boolean']}, description: `The children nodes or an indicator if the node has children.<br/> Sync props`, defaultValue: true, table: { type: { summary: ['array', 'boolean'] }, defaultValue: { summary: `false` }, category: 'props' }, control: { type: 'object' } }, expanded: { name: 'expanded', type: {name: 'boolean'}, description: `Value indicating if the toggle is open. <br/> Sync props`, defaultValue: false, table: { type: { summary: 'boolean' }, defaultValue: { summary: `false` }, category: 'props' }, control: { type: 'boolean' } }, selected: { name: 'selected', type: {name: 'boolean'}, description: `Value indicating if the node is selected. <br/> Sync props`, defaultValue: false, table: { type: { summary: 'boolean' }, defaultValue: { summary: `false` }, category: 'props' }, control: { type: 'boolean' } }, loading: { name: 'loading', type: {name: 'boolean'}, description: `A value indicating if it is loading. <br/> If loading, it shows loading icon instead of toggle. <br/> Sync props`, defaultValue: false, table: { type: { summary: 'boolean' }, defaultValue: { summary: `false` }, category: 'props' }, control: { type: 'boolean' } }, dataSlot: { name: 'data', description: 'Use it to replace data area.', table: { category: 'slots' }, control: { type: 'text' } }, rowSlot: { name: 'row', description: 'Use it to replace row area.', table: { category: 'slots' }, control: { type: 'text' } }, nodeSlot: { name: 'node', description: 'Use it to replace node area.', table: { category: 'slots' }, control: { type: 'text' } }, leftExtraSlot: { name: 'left-extra', description: 'Use it to insert something into left area of the node.', table: { category: 'slots' }, control: { type: 'text' } }, toggleSlot: { name: 'toggle', description: 'Use it to replace toggle.', table: { category: 'slots' }, control: { type: 'text' } }, toggleRightSlot: { name: 'toggle-right', description: 'Use it to insert something into sight side of toggle.', table: { category: 'slots' }, control: { type: 'text' } }, iconSlot: { name: 'icon', description: 'Use it to insert icon.', table: { category: 'slots' }, control: { type: 'text' } }, rightExtraSlot: { name: 'right-extra', description: 'Use it to insert something into right side of the node.', table: { category: 'slots' }, control: { type: 'text' } }, getChildrenNodes: { name: 'getChildrenNodes', description: 'Returns children array with `TreeItem` format.', table: { type: { summary: '() => TreeItem<T>[]' }, category: 'functions' }, }, deleteNode: { name: 'deleteNode', description: 'Only an `delete` event is triggered to delete the node from the parent node.', table: { type: { summary: '() => void' }, category: 'functions' }, }, addChild: { name: 'addChild', description: 'Asynchronously returns the node reflecting the added data.', table: { type: { summary: '(data: T) => Promise<TreeItem<T>>' }, category: 'functions' }, }, startEdit: { name: 'startEdit', description: 'Start inline editing.', table: { type: { summary: '() => void' }, category: 'functions' }, }, finishEdit: { name: 'finishEdit', description: 'Finish inline editing. <br/> Fires a `finish-edit` event.', table: { type: { summary: '() => void' }, category: 'functions' }, }, findChildNode: { name: 'setExpanded', description: `Find child node by node id.`, table: { type: { summary: '(id: string|number) => TreeItem<T>|null' }, category: 'functions' }, }, setData: { name: 'setData', description: 'Fires a `update-data` event.', table: { type: { summary: '(data: T) => void' }, category: 'functions' }, }, setChildren: { name: 'setChildren', description: 'Set children and return the children reflected asynchronously.', table: { type: { summary: '(children: T[] | boolean) => Promise<TreeItem<T>[]>' }, category: 'functions' }, }, addChildren: { name: 'addChildren', description: 'Add children and return the children reflected asynchronously.', table: { type: { summary: '(children: T[]) => Promise<TreeItem<T>[]>' }, category: 'functions' }, }, setSelected: { name: 'setSelected', description: `Set selected and fire a \`check-select\` event<br/> If the second parameter(\`force\`) is set to \`true\`, \`check-select\` event is not fired and select validation is not executed.`, table: { type: { summary: '(selected: boolean, force?: boolean) => void' }, category: 'functions' }, }, setLoading: { name: 'setLoading', description: `Set loading`, table: { type: { summary: '(loading: boolean) => void' }, category: 'functions' }, }, setExpanded: { name: 'setExpanded', description: `Set expanded`, table: { type: { summary: '(expanded: boolean) => void' }, category: 'functions' }, }, onInit: { name: 'init', description: `Event emitted when the node is ready`, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } }, onDelete: { name: 'delete', description: `Event emitted when call \`node.deleteNode()\``, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } }, onCheckSelect: { name: 'check-select', description: `Event emitted when call \`node.setSelected()\` and select validation is succeed`, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } }, onClickNode: { name: 'click-node', description: `Event emitted when the node is clicked`, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } }, onToggle: { name: 'toggle', description: `Event emitted when the toggle is clicked and expanded`, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } }, onFold: { name: 'fold', description: `Event emitted when the toggle is clicked and folded`, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } }, onStartDrag: { name: 'start-drag', description: `Event emitted when started dragging the descendant node`, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } }, onEndDrag: { name: 'end-drag', description: `Event emitted when the descendant node is dropped`, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } }, onAddDrag: { name: 'add-drag', description: `Event emitted when the descendant node's parent is changed by drag & drop`, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } }, onUpdateDrag: { name: 'update-drag', description: `Event emitted when the descendant node moved under the same parent by drag & drop`, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } }, onFinishEdit: { name: 'finish-edit', description: `Event emitted when finish inline edit`, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } }, onUpdateData: { name: 'update-data', description: `Event emitted when data is changed by \`setData()\``, defaultValue: null, table: { type: { summary: null, }, defaultValue: { summary: null, }, category: 'events' } } }} /> export const Template = (args, { argTypes }) => ({ components: { PTreeNode }, template: ` <div class="w-4/5"> <p-tree-node class="w-full" :level="1" :index="0" :data.sync="node.data" :children.sync="node.children" :expanded.sync="node.expanded" :loading.sync="node.loading" :selected.sync="node.selected" @toggle="onToggle" @fold="onFold" @click-node="onClickNode" > </p-tree-node> </div> `, setup() { const state = reactive({ node: { data: 'Data', children: [ { _id: 1, data: 'Child 1' }, { _id: 2, data: 'Child 2', children: [ { _id: 3, data: 'Child 2 - Child 3' }, { _id: 4, data: 'Child 2 - Child 4' } ] } ], expanded: true, loading: false, selected: false } }) const onToggle = (item) => { item.setExpanded(true) } const onFold = (item) => { item.setExpanded(false) } const onClickNode = (item) => { item.setSelected(!item.selected, true) } return { ...toRefs((state)), onToggle, onFold, onClickNode, } } }); export const PlaygroundTemplate = (args, { argTypes }) => ({ props: Object.keys(argTypes), components: { PTreeNode }, template: ` <div class="w-4/5"> <p-tree-node class="w-full" :level="level" :index="index" :pad-size="padSize" :disable-toggle="disableToggle" :select-options="selectOptions" :edit-options="editOptions" :drag-options="dragOptions" :get-default-node="getDefaultNode" :get-class-names="getClassNames" :data="data" :children="children" :expanded="expanded" :loading="loading" :selected="selected" @init="onInit" @delete="onDelete" @toggle="onToggle" @fold="onFold" @click-node="onClickNode" @check-select="onCheckSelect" @start-drag="onStartDrag" @add-drag="onAddDrag" @end-drag="onEndDrag" @update-drag="onUpdateDrag" @finish-edit="onFinishEdit" @update-data="onUpdateData" > <template v-if="dataSlot" #data> <div v-html="dataSlot"/> </template> <template v-if="rowSlot" #row> <div v-html="rowSlot"/> </template> <template v-if="nodeSlot" #node> <div v-html="nodeSlot"/> </template> <template v-if="leftExtraSlot" #left-extra> <div v-html="leftExtraSlot"/> </template> <template v-if="toggleSlot" #toggle> <div v-html="toggleSlot"/> </template> <template v-if="toggleRightSlot" #toggle-right> <div v-html="toggleRightSlot"/> </template> <template v-if="iconSlot" #icon> <div v-html="iconSlot"/> </template> <template v-if="rightExtraSlot" #right-extra> <div v-html="rightExtraSlot"/> </template> </p-tree-node> </div> `, setup() { return {} } }); # TreeNode <br/> <br/> ## Basic <Canvas> <Story name="basic"> {Template.bind({})} </Story> </Canvas> <br/> ### How to inject children and customize data In order to have children nodes, child data must be injected in children props in array with format `TreeNode<T>` below. ```typescript interface TreeNode<T> { _id: string|number; data: T; children?: TreeNode<T>[] | boolean; expanded?: boolean; selected?: boolean; loading?: boolean; } ``` If you want to customize the default values of children nodes based on `data` props, give `getDefaultNode` props that receives `data` and return `TreeNode`. ### Tree Item Tree node component creates an object for internal manipulation, `TreeItem`, and returns it through the `init` event after creation. ```typescript interface TreeItem<T=any> extends TreeNode { index: number; level: number; parent: TreeItem<T>|null; el?: HTMLElement; getChildrenNodes: () => TreeItem<T>[]; deleteNode: () => void; addChild: (data: T) => Promise<TreeItem<T>>; startEdit: () => void; finishEdit: () => void; findChildNode: (id: string|number) => TreeItem<T>|null; setData: (data: T) => void; setChildren: (children: T[] | boolean) => Promise<TreeItem<T>[]>; addChildren: (children: T[]) => Promise<TreeItem<T>[]>; setSelected: (selected: boolean, force?: boolean) => void; setLoading: (loading: boolean) => void; setExpanded: (expanded: boolean) => void; } ``` ### Slot Props All slots have the same slot props. <br/> - depth: Padding value. e.g. `1rem` - node: Current node `TreeItem`. ```typescript interface TreeNodeSlotProps<T=any> { depth: string; node: TreeItem<T>; } ``` ### TreeNode Props Type ```typescript interface TreeNodeProps<T=any> { index?: number; level?: number; padSize?: string; disableToggle?: boolean; selectOptions?: SelectOptions<T>; editOptions?: EditOptions<T>; dragOptions?: DragOptions<T>; parent?: TreeItem<T>; data?: T; children?: TreeNodeProps<T>[] | boolean; expanded?: boolean; selected?: boolean; loading?: boolean; getDefaultNode?: (data: T) => TreeNode<T>; getClassNames?: (item: TreeItem<T>) => string|string[]|object; } ``` ## Playground <Canvas> <Story name="playground"> {PlaygroundTemplate.bind({})} </Story> </Canvas> <ArgsTable story="playground" />