@mindlogic-ai/logician-ui 3.2.0-alpha.0 → 3.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Tree/Tree.d.ts +20 -0
- package/dist/components/Tree/Tree.d.ts.map +1 -0
- package/dist/components/Tree/Tree.js +43 -0
- package/dist/components/Tree/Tree.js.map +1 -0
- package/dist/components/Tree/Tree.mjs +41 -0
- package/dist/components/Tree/Tree.mjs.map +1 -0
- package/dist/components/Tree/Tree.types.d.ts +21 -0
- package/dist/components/Tree/Tree.types.d.ts.map +1 -0
- package/dist/components/Tree/TreeBranch.d.ts +3 -0
- package/dist/components/Tree/TreeBranch.d.ts.map +1 -0
- package/dist/components/Tree/TreeBranch.js +14 -0
- package/dist/components/Tree/TreeBranch.js.map +1 -0
- package/dist/components/Tree/TreeBranch.mjs +12 -0
- package/dist/components/Tree/TreeBranch.mjs.map +1 -0
- package/dist/components/Tree/TreeBranchContent.d.ts +3 -0
- package/dist/components/Tree/TreeBranchContent.d.ts.map +1 -0
- package/dist/components/Tree/TreeBranchContent.js +14 -0
- package/dist/components/Tree/TreeBranchContent.js.map +1 -0
- package/dist/components/Tree/TreeBranchContent.mjs +12 -0
- package/dist/components/Tree/TreeBranchContent.mjs.map +1 -0
- package/dist/components/Tree/TreeBranchControl.d.ts +3 -0
- package/dist/components/Tree/TreeBranchControl.d.ts.map +1 -0
- package/dist/components/Tree/TreeBranchControl.js +19 -0
- package/dist/components/Tree/TreeBranchControl.js.map +1 -0
- package/dist/components/Tree/TreeBranchControl.mjs +17 -0
- package/dist/components/Tree/TreeBranchControl.mjs.map +1 -0
- package/dist/components/Tree/TreeBranchIndentGuide.d.ts +3 -0
- package/dist/components/Tree/TreeBranchIndentGuide.d.ts.map +1 -0
- package/dist/components/Tree/TreeBranchIndentGuide.js +20 -0
- package/dist/components/Tree/TreeBranchIndentGuide.js.map +1 -0
- package/dist/components/Tree/TreeBranchIndentGuide.mjs +18 -0
- package/dist/components/Tree/TreeBranchIndentGuide.mjs.map +1 -0
- package/dist/components/Tree/TreeBranchIndicator.d.ts +3 -0
- package/dist/components/Tree/TreeBranchIndicator.d.ts.map +1 -0
- package/dist/components/Tree/TreeBranchIndicator.js +15 -0
- package/dist/components/Tree/TreeBranchIndicator.js.map +1 -0
- package/dist/components/Tree/TreeBranchIndicator.mjs +13 -0
- package/dist/components/Tree/TreeBranchIndicator.mjs.map +1 -0
- package/dist/components/Tree/TreeBranchText.d.ts +3 -0
- package/dist/components/Tree/TreeBranchText.d.ts.map +1 -0
- package/dist/components/Tree/TreeBranchText.js +14 -0
- package/dist/components/Tree/TreeBranchText.js.map +1 -0
- package/dist/components/Tree/TreeBranchText.mjs +12 -0
- package/dist/components/Tree/TreeBranchText.mjs.map +1 -0
- package/dist/components/Tree/TreeBranchTrigger.d.ts +3 -0
- package/dist/components/Tree/TreeBranchTrigger.d.ts.map +1 -0
- package/dist/components/Tree/TreeBranchTrigger.js +15 -0
- package/dist/components/Tree/TreeBranchTrigger.js.map +1 -0
- package/dist/components/Tree/TreeBranchTrigger.mjs +13 -0
- package/dist/components/Tree/TreeBranchTrigger.mjs.map +1 -0
- package/dist/components/Tree/TreeItem.d.ts +3 -0
- package/dist/components/Tree/TreeItem.d.ts.map +1 -0
- package/dist/components/Tree/TreeItem.js +25 -0
- package/dist/components/Tree/TreeItem.js.map +1 -0
- package/dist/components/Tree/TreeItem.mjs +23 -0
- package/dist/components/Tree/TreeItem.mjs.map +1 -0
- package/dist/components/Tree/TreeItemIndicator.d.ts +3 -0
- package/dist/components/Tree/TreeItemIndicator.d.ts.map +1 -0
- package/dist/components/Tree/TreeItemIndicator.js +14 -0
- package/dist/components/Tree/TreeItemIndicator.js.map +1 -0
- package/dist/components/Tree/TreeItemIndicator.mjs +12 -0
- package/dist/components/Tree/TreeItemIndicator.mjs.map +1 -0
- package/dist/components/Tree/TreeItemText.d.ts +3 -0
- package/dist/components/Tree/TreeItemText.d.ts.map +1 -0
- package/dist/components/Tree/TreeItemText.js +14 -0
- package/dist/components/Tree/TreeItemText.js.map +1 -0
- package/dist/components/Tree/TreeItemText.mjs +12 -0
- package/dist/components/Tree/TreeItemText.mjs.map +1 -0
- package/dist/components/Tree/TreeLabel.d.ts +3 -0
- package/dist/components/Tree/TreeLabel.d.ts.map +1 -0
- package/dist/components/Tree/TreeLabel.js +14 -0
- package/dist/components/Tree/TreeLabel.js.map +1 -0
- package/dist/components/Tree/TreeLabel.mjs +12 -0
- package/dist/components/Tree/TreeLabel.mjs.map +1 -0
- package/dist/components/Tree/TreeNode.d.ts +5 -0
- package/dist/components/Tree/TreeNode.d.ts.map +1 -0
- package/dist/components/Tree/TreeNode.js +9 -0
- package/dist/components/Tree/TreeNode.js.map +1 -0
- package/dist/components/Tree/TreeNode.mjs +7 -0
- package/dist/components/Tree/TreeNode.mjs.map +1 -0
- package/dist/components/Tree/TreeNodeCheckbox.d.ts +3 -0
- package/dist/components/Tree/TreeNodeCheckbox.d.ts.map +1 -0
- package/dist/components/Tree/TreeNodeCheckbox.js +14 -0
- package/dist/components/Tree/TreeNodeCheckbox.js.map +1 -0
- package/dist/components/Tree/TreeNodeCheckbox.mjs +12 -0
- package/dist/components/Tree/TreeNodeCheckbox.mjs.map +1 -0
- package/dist/components/Tree/TreeNodeContext.d.ts +2 -0
- package/dist/components/Tree/TreeNodeContext.d.ts.map +1 -0
- package/dist/components/Tree/TreeNodeContext.js +9 -0
- package/dist/components/Tree/TreeNodeContext.js.map +1 -0
- package/dist/components/Tree/TreeNodeContext.mjs +7 -0
- package/dist/components/Tree/TreeNodeContext.mjs.map +1 -0
- package/dist/components/Tree/TreeNodeProvider.d.ts +5 -0
- package/dist/components/Tree/TreeNodeProvider.d.ts.map +1 -0
- package/dist/components/Tree/TreeNodeProvider.js +9 -0
- package/dist/components/Tree/TreeNodeProvider.js.map +1 -0
- package/dist/components/Tree/TreeNodeProvider.mjs +7 -0
- package/dist/components/Tree/TreeNodeProvider.mjs.map +1 -0
- package/dist/components/Tree/TreeRoot.d.ts +3 -0
- package/dist/components/Tree/TreeRoot.d.ts.map +1 -0
- package/dist/components/Tree/TreeRoot.js +14 -0
- package/dist/components/Tree/TreeRoot.js.map +1 -0
- package/dist/components/Tree/TreeRoot.mjs +12 -0
- package/dist/components/Tree/TreeRoot.mjs.map +1 -0
- package/dist/components/Tree/TreeTree.d.ts +3 -0
- package/dist/components/Tree/TreeTree.d.ts.map +1 -0
- package/dist/components/Tree/TreeTree.js +14 -0
- package/dist/components/Tree/TreeTree.js.map +1 -0
- package/dist/components/Tree/TreeTree.mjs +12 -0
- package/dist/components/Tree/TreeTree.mjs.map +1 -0
- package/dist/components/Tree/index.d.ts +23 -0
- package/dist/components/Tree/index.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +19 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Tree/Tree.stories.tsx +564 -0
- package/src/components/Tree/Tree.tsx +37 -0
- package/src/components/Tree/Tree.types.ts +51 -0
- package/src/components/Tree/TreeBranch.tsx +11 -0
- package/src/components/Tree/TreeBranchContent.tsx +21 -0
- package/src/components/Tree/TreeBranchControl.tsx +28 -0
- package/src/components/Tree/TreeBranchIndentGuide.tsx +20 -0
- package/src/components/Tree/TreeBranchIndicator.tsx +27 -0
- package/src/components/Tree/TreeBranchText.tsx +19 -0
- package/src/components/Tree/TreeBranchTrigger.tsx +25 -0
- package/src/components/Tree/TreeItem.tsx +33 -0
- package/src/components/Tree/TreeItemIndicator.tsx +20 -0
- package/src/components/Tree/TreeItemText.tsx +19 -0
- package/src/components/Tree/TreeLabel.tsx +19 -0
- package/src/components/Tree/TreeNode.tsx +6 -0
- package/src/components/Tree/TreeNodeCheckbox.tsx +12 -0
- package/src/components/Tree/TreeNodeContext.tsx +3 -0
- package/src/components/Tree/TreeNodeProvider.tsx +6 -0
- package/src/components/Tree/TreeRoot.tsx +11 -0
- package/src/components/Tree/TreeTree.tsx +11 -0
- package/src/components/Tree/index.ts +42 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Box, HStack, Stack, Text } from '@chakra-ui/react';
|
|
3
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { IoFolder, IoFolderOpen } from 'react-icons/io5';
|
|
5
|
+
|
|
6
|
+
import { Tree } from './Tree';
|
|
7
|
+
import {
|
|
8
|
+
createTreeCollection,
|
|
9
|
+
type TreeExpandedChangeDetails,
|
|
10
|
+
type TreeSelectionChangeDetails,
|
|
11
|
+
} from './index';
|
|
12
|
+
|
|
13
|
+
interface Node {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
children?: Node[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Realistic 5-level deep org chart. Exercises deep nesting, mixed
|
|
21
|
+
* branch/leaf siblings, and long-ish Korean labels (회사 → 본부 →
|
|
22
|
+
* 그룹 → 팀 → 파트 → 멤버).
|
|
23
|
+
*/
|
|
24
|
+
const orgSample: Node = {
|
|
25
|
+
id: 'ROOT',
|
|
26
|
+
name: '',
|
|
27
|
+
children: [
|
|
28
|
+
{
|
|
29
|
+
id: 'hq',
|
|
30
|
+
name: '본사',
|
|
31
|
+
children: [
|
|
32
|
+
{
|
|
33
|
+
id: 'hq-mgmt',
|
|
34
|
+
name: '경영지원본부',
|
|
35
|
+
children: [
|
|
36
|
+
{
|
|
37
|
+
id: 'hq-mgmt-hr',
|
|
38
|
+
name: '인사팀',
|
|
39
|
+
children: [
|
|
40
|
+
{
|
|
41
|
+
id: 'hq-mgmt-hr-recruit',
|
|
42
|
+
name: '채용파트',
|
|
43
|
+
children: [
|
|
44
|
+
{ id: 'hq-mgmt-hr-recruit-kim', name: '김채용' },
|
|
45
|
+
{ id: 'hq-mgmt-hr-recruit-lee', name: '이지원' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{ id: 'hq-mgmt-hr-eval', name: '평가파트' },
|
|
49
|
+
{ id: 'hq-mgmt-hr-edu', name: '교육파트' },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'hq-mgmt-fin',
|
|
54
|
+
name: '재무팀',
|
|
55
|
+
children: [
|
|
56
|
+
{ id: 'hq-mgmt-fin-acct', name: '회계파트' },
|
|
57
|
+
{ id: 'hq-mgmt-fin-tax', name: '세무파트' },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{ id: 'hq-mgmt-legal', name: '법무팀' },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'hq-dev',
|
|
65
|
+
name: '개발본부',
|
|
66
|
+
children: [
|
|
67
|
+
{
|
|
68
|
+
id: 'hq-dev-backend',
|
|
69
|
+
name: '백엔드그룹',
|
|
70
|
+
children: [
|
|
71
|
+
{
|
|
72
|
+
id: 'hq-dev-backend-api',
|
|
73
|
+
name: 'API팀',
|
|
74
|
+
children: [
|
|
75
|
+
{ id: 'hq-dev-backend-api-cs', name: '김철수' },
|
|
76
|
+
{ id: 'hq-dev-backend-api-yh', name: '이영희' },
|
|
77
|
+
{ id: 'hq-dev-backend-api-jm', name: '박지민' },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'hq-dev-backend-db',
|
|
82
|
+
name: 'DB팀',
|
|
83
|
+
children: [{ id: 'hq-dev-backend-db-sh', name: '최성호' }],
|
|
84
|
+
},
|
|
85
|
+
{ id: 'hq-dev-backend-platform', name: '플랫폼팀' },
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'hq-dev-frontend',
|
|
90
|
+
name: '프론트엔드그룹',
|
|
91
|
+
children: [
|
|
92
|
+
{ id: 'hq-dev-frontend-web', name: '웹팀' },
|
|
93
|
+
{ id: 'hq-dev-frontend-mobile', name: '모바일팀' },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'hq-dev-ai',
|
|
98
|
+
name: 'AI그룹',
|
|
99
|
+
children: [
|
|
100
|
+
{
|
|
101
|
+
id: 'hq-dev-ai-research',
|
|
102
|
+
name: '연구파트',
|
|
103
|
+
children: [
|
|
104
|
+
{ id: 'hq-dev-ai-research-senior', name: '시니어 연구원' },
|
|
105
|
+
{ id: 'hq-dev-ai-research-junior', name: '주니어 연구원' },
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
{ id: 'hq-dev-ai-eng', name: '엔지니어링파트' },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'hq-sales',
|
|
115
|
+
name: '영업본부',
|
|
116
|
+
children: [
|
|
117
|
+
{ id: 'hq-sales-domestic', name: '국내영업팀' },
|
|
118
|
+
{
|
|
119
|
+
id: 'hq-sales-global',
|
|
120
|
+
name: '해외영업팀',
|
|
121
|
+
children: [
|
|
122
|
+
{
|
|
123
|
+
id: 'hq-sales-global-amer',
|
|
124
|
+
name: '미주파트',
|
|
125
|
+
children: [
|
|
126
|
+
{ id: 'hq-sales-global-amer-us', name: '미국' },
|
|
127
|
+
{ id: 'hq-sales-global-amer-ca', name: '캐나다' },
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
{ id: 'hq-sales-global-eu', name: '유럽파트' },
|
|
131
|
+
{
|
|
132
|
+
id: 'hq-sales-global-asia',
|
|
133
|
+
name: '아시아파트',
|
|
134
|
+
children: [
|
|
135
|
+
{ id: 'hq-sales-global-asia-jp', name: '일본' },
|
|
136
|
+
{ id: 'hq-sales-global-asia-cn', name: '중국' },
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'rnd',
|
|
147
|
+
name: 'R&D센터',
|
|
148
|
+
children: [
|
|
149
|
+
{ id: 'rnd-ml', name: '머신러닝팀' },
|
|
150
|
+
{ id: 'rnd-data', name: '데이터팀' },
|
|
151
|
+
{ id: 'rnd-infra', name: '리서치인프라팀' },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{ id: 'subsidiary', name: '미국지사' },
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const orgCollection = createTreeCollection<Node>({
|
|
159
|
+
rootNode: orgSample,
|
|
160
|
+
nodeToValue: (node) => node.id,
|
|
161
|
+
nodeToString: (node) => node.name,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Convenience: all branch ids — for "expand all" demos.
|
|
165
|
+
const allBranchIds = (function collect(n: Node): string[] {
|
|
166
|
+
if (!n.children) return [];
|
|
167
|
+
return n.children.flatMap(c =>
|
|
168
|
+
c.children ? [c.id, ...collect(c)] : [],
|
|
169
|
+
);
|
|
170
|
+
})(orgSample);
|
|
171
|
+
|
|
172
|
+
const renderNode = ({
|
|
173
|
+
node,
|
|
174
|
+
nodeState,
|
|
175
|
+
}: {
|
|
176
|
+
node: Node;
|
|
177
|
+
nodeState: { isBranch: boolean };
|
|
178
|
+
}) =>
|
|
179
|
+
nodeState.isBranch ? (
|
|
180
|
+
<Tree.BranchControl>
|
|
181
|
+
<Tree.BranchIndicator />
|
|
182
|
+
<Tree.BranchText>{node.name}</Tree.BranchText>
|
|
183
|
+
</Tree.BranchControl>
|
|
184
|
+
) : (
|
|
185
|
+
<Tree.Item>
|
|
186
|
+
<Tree.ItemText>{node.name}</Tree.ItemText>
|
|
187
|
+
</Tree.Item>
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const meta = {
|
|
191
|
+
title: 'Components/Tree',
|
|
192
|
+
component: Tree.Root,
|
|
193
|
+
parameters: { layout: 'padded' },
|
|
194
|
+
} satisfies Meta<typeof Tree.Root>;
|
|
195
|
+
|
|
196
|
+
export default meta;
|
|
197
|
+
|
|
198
|
+
type Story = StoryObj<typeof meta>;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* All branches collapsed — the simplest entry point. Click a branch
|
|
202
|
+
* (or the chevron) to expand. Single selection mode by default.
|
|
203
|
+
*/
|
|
204
|
+
export const Default: Story = {
|
|
205
|
+
args: { collection: orgCollection, 'aria-label': '조직' },
|
|
206
|
+
render: (args) => (
|
|
207
|
+
<Box maxWidth="360px">
|
|
208
|
+
<Tree.Root {...args}>
|
|
209
|
+
<Tree.Tree>
|
|
210
|
+
<Tree.Node
|
|
211
|
+
indentGuide={<Tree.BranchIndentGuide />}
|
|
212
|
+
render={renderNode}
|
|
213
|
+
/>
|
|
214
|
+
</Tree.Tree>
|
|
215
|
+
</Tree.Root>
|
|
216
|
+
</Box>
|
|
217
|
+
),
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Some branches pre-expanded so the depth-based indent and the
|
|
222
|
+
* vertical indent guides are visible without interaction.
|
|
223
|
+
*/
|
|
224
|
+
export const DefaultExpanded: Story = {
|
|
225
|
+
args: {
|
|
226
|
+
collection: orgCollection,
|
|
227
|
+
'aria-label': '조직',
|
|
228
|
+
defaultExpandedValue: [
|
|
229
|
+
'hq',
|
|
230
|
+
'hq-dev',
|
|
231
|
+
'hq-dev-backend',
|
|
232
|
+
'hq-dev-backend-api',
|
|
233
|
+
],
|
|
234
|
+
defaultSelectedValue: ['hq-dev-backend-api-yh'],
|
|
235
|
+
},
|
|
236
|
+
render: (args) => (
|
|
237
|
+
<Box maxWidth="360px">
|
|
238
|
+
<Tree.Root {...args}>
|
|
239
|
+
<Tree.Tree>
|
|
240
|
+
<Tree.Node
|
|
241
|
+
indentGuide={<Tree.BranchIndentGuide />}
|
|
242
|
+
render={renderNode}
|
|
243
|
+
/>
|
|
244
|
+
</Tree.Tree>
|
|
245
|
+
</Tree.Root>
|
|
246
|
+
</Box>
|
|
247
|
+
),
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Every branch expanded. Use to verify that depth-based padding and
|
|
252
|
+
* the stacked vertical indent guides line up correctly at 4-5 levels
|
|
253
|
+
* deep (본사 → 개발본부 → 백엔드그룹 → API팀 → 김철수).
|
|
254
|
+
*/
|
|
255
|
+
export const DeeplyNested: Story = {
|
|
256
|
+
args: {
|
|
257
|
+
collection: orgCollection,
|
|
258
|
+
'aria-label': '조직',
|
|
259
|
+
defaultExpandedValue: allBranchIds,
|
|
260
|
+
},
|
|
261
|
+
render: (args) => (
|
|
262
|
+
<Box maxWidth="420px">
|
|
263
|
+
<Tree.Root {...args}>
|
|
264
|
+
<Tree.Tree>
|
|
265
|
+
<Tree.Node
|
|
266
|
+
indentGuide={<Tree.BranchIndentGuide />}
|
|
267
|
+
render={renderNode}
|
|
268
|
+
/>
|
|
269
|
+
</Tree.Tree>
|
|
270
|
+
</Tree.Root>
|
|
271
|
+
</Box>
|
|
272
|
+
),
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Side-by-side comparison: default style (chevron + depth padding only)
|
|
277
|
+
* vs. factchat 그룹 관리 style (∟ leaf prefix using the `Tree.Item`
|
|
278
|
+
* children slot). Demonstrates that any custom content can be inlined
|
|
279
|
+
* before `Tree.ItemText`.
|
|
280
|
+
*/
|
|
281
|
+
export const LeafIndicatorComparison: Story = {
|
|
282
|
+
args: {
|
|
283
|
+
collection: orgCollection,
|
|
284
|
+
'aria-label': '조직',
|
|
285
|
+
defaultExpandedValue: ['hq', 'hq-dev', 'hq-dev-backend', 'hq-dev-backend-api'],
|
|
286
|
+
},
|
|
287
|
+
render: (args) => {
|
|
288
|
+
const renderWithLeafPrefix = ({
|
|
289
|
+
node,
|
|
290
|
+
nodeState,
|
|
291
|
+
}: {
|
|
292
|
+
node: Node;
|
|
293
|
+
nodeState: { isBranch: boolean };
|
|
294
|
+
}) =>
|
|
295
|
+
nodeState.isBranch ? (
|
|
296
|
+
<Tree.BranchControl>
|
|
297
|
+
<Tree.BranchIndicator />
|
|
298
|
+
<Tree.BranchText>{node.name}</Tree.BranchText>
|
|
299
|
+
</Tree.BranchControl>
|
|
300
|
+
) : (
|
|
301
|
+
<Tree.Item>
|
|
302
|
+
<Text as="span" color="fg.muted" fontSize="sm" lineHeight="1">
|
|
303
|
+
∟
|
|
304
|
+
</Text>
|
|
305
|
+
<Tree.ItemText>{node.name}</Tree.ItemText>
|
|
306
|
+
</Tree.Item>
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<HStack align="flex-start" gap={8}>
|
|
311
|
+
<Stack gap={2} width="360px">
|
|
312
|
+
<Text fontSize="sm" fontWeight="semibold" color="fg.muted">
|
|
313
|
+
기본
|
|
314
|
+
</Text>
|
|
315
|
+
<Tree.Root {...args}>
|
|
316
|
+
<Tree.Tree>
|
|
317
|
+
<Tree.Node
|
|
318
|
+
indentGuide={<Tree.BranchIndentGuide />}
|
|
319
|
+
render={renderNode}
|
|
320
|
+
/>
|
|
321
|
+
</Tree.Tree>
|
|
322
|
+
</Tree.Root>
|
|
323
|
+
</Stack>
|
|
324
|
+
<Stack gap={2} width="360px">
|
|
325
|
+
<Text fontSize="sm" fontWeight="semibold" color="fg.muted">
|
|
326
|
+
∟ leaf 표시 (factchat 그룹 관리 스타일)
|
|
327
|
+
</Text>
|
|
328
|
+
<Tree.Root {...args}>
|
|
329
|
+
<Tree.Tree>
|
|
330
|
+
<Tree.Node
|
|
331
|
+
indentGuide={<Tree.BranchIndentGuide />}
|
|
332
|
+
render={renderWithLeafPrefix}
|
|
333
|
+
/>
|
|
334
|
+
</Tree.Tree>
|
|
335
|
+
</Tree.Root>
|
|
336
|
+
</Stack>
|
|
337
|
+
</HStack>
|
|
338
|
+
);
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Replace the default chevron with a folder icon that swaps between
|
|
344
|
+
* `IoFolder` (closed) and `IoFolderOpen` (expanded). Uses the
|
|
345
|
+
* `nodeState.expanded` boolean exposed by Ark's render prop, and
|
|
346
|
+
* sets `transform="none"` on the indicator to suppress the recipe's
|
|
347
|
+
* default `_open: rotate(90deg)` (which fits a chevron, not a folder).
|
|
348
|
+
*/
|
|
349
|
+
export const CustomBranchIcon: Story = {
|
|
350
|
+
args: {
|
|
351
|
+
collection: orgCollection,
|
|
352
|
+
'aria-label': '조직',
|
|
353
|
+
defaultExpandedValue: ['hq', 'hq-mgmt', 'hq-mgmt-hr'],
|
|
354
|
+
},
|
|
355
|
+
render: (args) => {
|
|
356
|
+
const renderWithFolder = ({
|
|
357
|
+
node,
|
|
358
|
+
nodeState,
|
|
359
|
+
}: {
|
|
360
|
+
node: Node;
|
|
361
|
+
nodeState: { isBranch: boolean; expanded: boolean };
|
|
362
|
+
}) =>
|
|
363
|
+
nodeState.isBranch ? (
|
|
364
|
+
<Tree.BranchControl>
|
|
365
|
+
<Tree.BranchIndicator
|
|
366
|
+
transform="none"
|
|
367
|
+
_open={{ transform: 'none' }}
|
|
368
|
+
>
|
|
369
|
+
{nodeState.expanded ? <IoFolderOpen /> : <IoFolder />}
|
|
370
|
+
</Tree.BranchIndicator>
|
|
371
|
+
<Tree.BranchText>{node.name}</Tree.BranchText>
|
|
372
|
+
</Tree.BranchControl>
|
|
373
|
+
) : (
|
|
374
|
+
<Tree.Item>
|
|
375
|
+
<Tree.ItemText>{node.name}</Tree.ItemText>
|
|
376
|
+
</Tree.Item>
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
return (
|
|
380
|
+
<Box maxWidth="360px">
|
|
381
|
+
<Tree.Root {...args}>
|
|
382
|
+
<Tree.Tree>
|
|
383
|
+
<Tree.Node
|
|
384
|
+
indentGuide={<Tree.BranchIndentGuide />}
|
|
385
|
+
render={renderWithFolder}
|
|
386
|
+
/>
|
|
387
|
+
</Tree.Tree>
|
|
388
|
+
</Tree.Root>
|
|
389
|
+
</Box>
|
|
390
|
+
);
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The recipe ships three size variants (`xs` / `sm` / `md`). They
|
|
396
|
+
* scale row text size, padding-block, indentation step and icon size
|
|
397
|
+
* proportionally — useful for dense sidebars (xs) vs. main content (md).
|
|
398
|
+
*/
|
|
399
|
+
export const SizeVariants: Story = {
|
|
400
|
+
args: {
|
|
401
|
+
collection: orgCollection,
|
|
402
|
+
'aria-label': '조직',
|
|
403
|
+
defaultExpandedValue: ['hq', 'hq-dev', 'hq-dev-backend'],
|
|
404
|
+
},
|
|
405
|
+
render: (args) => (
|
|
406
|
+
<HStack align="flex-start" gap={6}>
|
|
407
|
+
{(['xs', 'sm', 'md'] as const).map(size => (
|
|
408
|
+
<Stack key={size} gap={2} width="300px">
|
|
409
|
+
<Text fontSize="sm" fontWeight="semibold" color="fg.muted">
|
|
410
|
+
size = {size}
|
|
411
|
+
</Text>
|
|
412
|
+
<Tree.Root {...args} size={size}>
|
|
413
|
+
<Tree.Tree>
|
|
414
|
+
<Tree.Node
|
|
415
|
+
indentGuide={<Tree.BranchIndentGuide />}
|
|
416
|
+
render={renderNode}
|
|
417
|
+
/>
|
|
418
|
+
</Tree.Tree>
|
|
419
|
+
</Tree.Root>
|
|
420
|
+
</Stack>
|
|
421
|
+
))}
|
|
422
|
+
</HStack>
|
|
423
|
+
),
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Long labels that exceed the container width — verifies the leaf
|
|
428
|
+
* rows wrap (or, in tight columns, truncate) without breaking the
|
|
429
|
+
* depth-based start padding alignment.
|
|
430
|
+
*/
|
|
431
|
+
const longLabelSample: Node = {
|
|
432
|
+
id: 'ROOT',
|
|
433
|
+
name: '',
|
|
434
|
+
children: [
|
|
435
|
+
{
|
|
436
|
+
id: 'long-root',
|
|
437
|
+
name: '아주 긴 한글 그룹명 — 사이드바 폭을 시험하기 위한 항목',
|
|
438
|
+
children: [
|
|
439
|
+
{
|
|
440
|
+
id: 'long-child-1',
|
|
441
|
+
name: '여러 줄로 줄바꿈이 일어날 수 있는 충분히 긴 자식 노드의 이름',
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
id: 'long-child-2',
|
|
445
|
+
name: '비교적 짧은 이름',
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
id: 'long-branch',
|
|
449
|
+
name: '하위 그룹도 있는, 마찬가지로 길어서 줄바꿈 테스트가 가능한 브랜치 노드',
|
|
450
|
+
children: [
|
|
451
|
+
{
|
|
452
|
+
id: 'long-leaf',
|
|
453
|
+
name: '깊게 들여쓰여진 상태에서의 매우 긴 잎 노드 — 가로 폭이 좁아질수록 줄바꿈이 잘 일어나야 합니다',
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
export const LongLabels: Story = {
|
|
463
|
+
args: {
|
|
464
|
+
collection: createTreeCollection<Node>({
|
|
465
|
+
rootNode: longLabelSample,
|
|
466
|
+
nodeToValue: (node) => node.id,
|
|
467
|
+
nodeToString: (node) => node.name,
|
|
468
|
+
}),
|
|
469
|
+
'aria-label': '긴 라벨 트리',
|
|
470
|
+
defaultExpandedValue: ['long-root', 'long-branch'],
|
|
471
|
+
},
|
|
472
|
+
render: (args) => (
|
|
473
|
+
<Box maxWidth="280px">
|
|
474
|
+
<Tree.Root {...args}>
|
|
475
|
+
<Tree.Tree>
|
|
476
|
+
<Tree.Node
|
|
477
|
+
indentGuide={<Tree.BranchIndentGuide />}
|
|
478
|
+
render={renderNode}
|
|
479
|
+
/>
|
|
480
|
+
</Tree.Tree>
|
|
481
|
+
</Tree.Root>
|
|
482
|
+
</Box>
|
|
483
|
+
),
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const ControlledTree = () => {
|
|
487
|
+
const [expandedValue, setExpandedValue] = useState<string[]>([
|
|
488
|
+
'hq',
|
|
489
|
+
'hq-mgmt',
|
|
490
|
+
'hq-mgmt-hr',
|
|
491
|
+
]);
|
|
492
|
+
const [selectedValue, setSelectedValue] = useState<string[]>([
|
|
493
|
+
'hq-mgmt-hr-eval',
|
|
494
|
+
]);
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<Box maxWidth="360px">
|
|
498
|
+
<Tree.Root
|
|
499
|
+
collection={orgCollection}
|
|
500
|
+
aria-label="조직"
|
|
501
|
+
expandedValue={expandedValue}
|
|
502
|
+
onExpandedChange={(details: TreeExpandedChangeDetails) =>
|
|
503
|
+
setExpandedValue(details.expandedValue)
|
|
504
|
+
}
|
|
505
|
+
selectedValue={selectedValue}
|
|
506
|
+
onSelectionChange={(details: TreeSelectionChangeDetails) =>
|
|
507
|
+
setSelectedValue(details.selectedValue)
|
|
508
|
+
}
|
|
509
|
+
>
|
|
510
|
+
<Tree.Tree>
|
|
511
|
+
<Tree.Node
|
|
512
|
+
indentGuide={<Tree.BranchIndentGuide />}
|
|
513
|
+
render={renderNode}
|
|
514
|
+
/>
|
|
515
|
+
</Tree.Tree>
|
|
516
|
+
</Tree.Root>
|
|
517
|
+
<Text mt={4} fontSize="xs" color="fg.muted">
|
|
518
|
+
expanded: {expandedValue.join(', ') || '(none)'}
|
|
519
|
+
<br />
|
|
520
|
+
selected: {selectedValue.join(', ') || '(none)'}
|
|
521
|
+
</Text>
|
|
522
|
+
</Box>
|
|
523
|
+
);
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Fully controlled — `expandedValue` and `selectedValue` are driven
|
|
528
|
+
* by React state. Useful when parent state needs to react to user
|
|
529
|
+
* navigation (e.g., sync with URL).
|
|
530
|
+
*/
|
|
531
|
+
export const Controlled: Story = {
|
|
532
|
+
args: { collection: orgCollection },
|
|
533
|
+
render: () => <ControlledTree />,
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Multiple selection — Ctrl/Cmd-click (or Shift-click for range) to
|
|
538
|
+
* select more than one node at a time.
|
|
539
|
+
*/
|
|
540
|
+
export const MultipleSelection: Story = {
|
|
541
|
+
args: {
|
|
542
|
+
collection: orgCollection,
|
|
543
|
+
'aria-label': '조직',
|
|
544
|
+
selectionMode: 'multiple',
|
|
545
|
+
defaultExpandedValue: ['hq', 'hq-dev', 'hq-dev-backend'],
|
|
546
|
+
defaultSelectedValue: [
|
|
547
|
+
'hq-dev-backend-api',
|
|
548
|
+
'hq-dev-backend-db',
|
|
549
|
+
'hq-dev-frontend',
|
|
550
|
+
],
|
|
551
|
+
},
|
|
552
|
+
render: (args) => (
|
|
553
|
+
<Box maxWidth="360px">
|
|
554
|
+
<Tree.Root {...args}>
|
|
555
|
+
<Tree.Tree>
|
|
556
|
+
<Tree.Node
|
|
557
|
+
indentGuide={<Tree.BranchIndentGuide />}
|
|
558
|
+
render={renderNode}
|
|
559
|
+
/>
|
|
560
|
+
</Tree.Tree>
|
|
561
|
+
</Tree.Root>
|
|
562
|
+
</Box>
|
|
563
|
+
),
|
|
564
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { TreeBranch } from './TreeBranch';
|
|
2
|
+
import { TreeBranchContent } from './TreeBranchContent';
|
|
3
|
+
import { TreeBranchControl } from './TreeBranchControl';
|
|
4
|
+
import { TreeBranchIndentGuide } from './TreeBranchIndentGuide';
|
|
5
|
+
import { TreeBranchIndicator } from './TreeBranchIndicator';
|
|
6
|
+
import { TreeBranchText } from './TreeBranchText';
|
|
7
|
+
import { TreeBranchTrigger } from './TreeBranchTrigger';
|
|
8
|
+
import { TreeItem } from './TreeItem';
|
|
9
|
+
import { TreeItemIndicator } from './TreeItemIndicator';
|
|
10
|
+
import { TreeItemText } from './TreeItemText';
|
|
11
|
+
import { TreeLabel } from './TreeLabel';
|
|
12
|
+
import { TreeNode } from './TreeNode';
|
|
13
|
+
import { TreeNodeCheckbox } from './TreeNodeCheckbox';
|
|
14
|
+
import { TreeNodeContext } from './TreeNodeContext';
|
|
15
|
+
import { TreeNodeProvider } from './TreeNodeProvider';
|
|
16
|
+
import { TreeRoot } from './TreeRoot';
|
|
17
|
+
import { TreeTree } from './TreeTree';
|
|
18
|
+
|
|
19
|
+
export const Tree = {
|
|
20
|
+
Root: TreeRoot,
|
|
21
|
+
Tree: TreeTree,
|
|
22
|
+
Branch: TreeBranch,
|
|
23
|
+
BranchControl: TreeBranchControl,
|
|
24
|
+
BranchTrigger: TreeBranchTrigger,
|
|
25
|
+
BranchIndicator: TreeBranchIndicator,
|
|
26
|
+
BranchText: TreeBranchText,
|
|
27
|
+
BranchContent: TreeBranchContent,
|
|
28
|
+
BranchIndentGuide: TreeBranchIndentGuide,
|
|
29
|
+
Item: TreeItem,
|
|
30
|
+
ItemText: TreeItemText,
|
|
31
|
+
ItemIndicator: TreeItemIndicator,
|
|
32
|
+
Node: TreeNode,
|
|
33
|
+
NodeCheckbox: TreeNodeCheckbox,
|
|
34
|
+
NodeContext: TreeNodeContext,
|
|
35
|
+
NodeProvider: TreeNodeProvider,
|
|
36
|
+
Label: TreeLabel,
|
|
37
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TreeViewBranchContentProps,
|
|
3
|
+
TreeViewBranchControlProps,
|
|
4
|
+
TreeViewBranchIndentGuideProps,
|
|
5
|
+
TreeViewBranchIndicatorProps,
|
|
6
|
+
TreeViewBranchProps,
|
|
7
|
+
TreeViewBranchTextProps,
|
|
8
|
+
TreeViewBranchTriggerProps,
|
|
9
|
+
TreeViewItemIndicatorProps,
|
|
10
|
+
TreeViewItemProps,
|
|
11
|
+
TreeViewItemTextProps,
|
|
12
|
+
TreeViewLabelProps,
|
|
13
|
+
TreeViewNodeCheckboxProps,
|
|
14
|
+
TreeViewRootProps,
|
|
15
|
+
TreeViewTreeProps,
|
|
16
|
+
} from '@chakra-ui/react';
|
|
17
|
+
|
|
18
|
+
export type TreeRootProps = TreeViewRootProps;
|
|
19
|
+
export type TreeTreeProps = TreeViewTreeProps;
|
|
20
|
+
export type TreeBranchProps = TreeViewBranchProps;
|
|
21
|
+
export type TreeBranchControlProps = TreeViewBranchControlProps;
|
|
22
|
+
export type TreeBranchTriggerProps = TreeViewBranchTriggerProps;
|
|
23
|
+
export type TreeBranchIndicatorProps = TreeViewBranchIndicatorProps;
|
|
24
|
+
export type TreeBranchTextProps = TreeViewBranchTextProps;
|
|
25
|
+
export type TreeBranchContentProps = TreeViewBranchContentProps;
|
|
26
|
+
export type TreeBranchIndentGuideProps = TreeViewBranchIndentGuideProps;
|
|
27
|
+
export type TreeItemProps = TreeViewItemProps;
|
|
28
|
+
export type TreeItemTextProps = TreeViewItemTextProps;
|
|
29
|
+
export type TreeItemIndicatorProps = TreeViewItemIndicatorProps;
|
|
30
|
+
export type TreeNodeCheckboxProps = TreeViewNodeCheckboxProps;
|
|
31
|
+
export type TreeLabelProps = TreeViewLabelProps;
|
|
32
|
+
|
|
33
|
+
// Detail payloads aren't re-exported from `@chakra-ui/react`'s top-level entry,
|
|
34
|
+
// so derive them from the event handler signatures the Root component exposes.
|
|
35
|
+
export type TreeExpandedChangeDetails = Parameters<
|
|
36
|
+
NonNullable<TreeViewRootProps['onExpandedChange']>
|
|
37
|
+
>[0];
|
|
38
|
+
|
|
39
|
+
export type TreeSelectionChangeDetails = Parameters<
|
|
40
|
+
NonNullable<TreeViewRootProps['onSelectionChange']>
|
|
41
|
+
>[0];
|
|
42
|
+
|
|
43
|
+
export type TreeCheckedChangeDetails = Parameters<
|
|
44
|
+
NonNullable<TreeViewRootProps['onCheckedChange']>
|
|
45
|
+
>[0];
|
|
46
|
+
|
|
47
|
+
export type TreeFocusChangeDetails = Parameters<
|
|
48
|
+
NonNullable<TreeViewRootProps['onFocusChange']>
|
|
49
|
+
>[0];
|
|
50
|
+
|
|
51
|
+
export type { TreeCollection } from '@chakra-ui/react';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { TreeView as ChakraTreeView } from '@chakra-ui/react';
|
|
3
|
+
|
|
4
|
+
import { TreeBranchProps } from './Tree.types';
|
|
5
|
+
|
|
6
|
+
export const TreeBranch = forwardRef<HTMLDivElement, TreeBranchProps>(
|
|
7
|
+
(props, ref) => {
|
|
8
|
+
return <ChakraTreeView.Branch ref={ref} {...props} />;
|
|
9
|
+
}
|
|
10
|
+
);
|
|
11
|
+
TreeBranch.displayName = 'TreeBranch';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { TreeView as ChakraTreeView } from '@chakra-ui/react';
|
|
3
|
+
|
|
4
|
+
import { TreeBranchContentProps } from './Tree.types';
|
|
5
|
+
|
|
6
|
+
export const TreeBranchContent = forwardRef<
|
|
7
|
+
HTMLDivElement,
|
|
8
|
+
TreeBranchContentProps
|
|
9
|
+
>((props, ref) => {
|
|
10
|
+
return (
|
|
11
|
+
<ChakraTreeView.BranchContent
|
|
12
|
+
ref={ref}
|
|
13
|
+
position="relative"
|
|
14
|
+
display="flex"
|
|
15
|
+
flexDirection="column"
|
|
16
|
+
gap={0.5}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
TreeBranchContent.displayName = 'TreeBranchContent';
|