@spaceone/design-system
Version:
SpaceONE Design System
855 lines (823 loc) • 23.2 kB
text/mdx
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" />