@lawkit/ui 0.1.40 → 0.1.41
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/CLAUDE.md +1174 -0
- package/package.json +7 -3
- package/scripts/postinstall.js +82 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,1174 @@
|
|
|
1
|
+
# @lawkit/ui 컴포넌트 레퍼런스
|
|
2
|
+
|
|
3
|
+
이 파일은 자동 생성됩니다. `pnpm --filter @lawkit/ui docs` 로 재생성하세요.
|
|
4
|
+
|
|
5
|
+
@lawkit/ui 컴포넌트 사용 시 아래 템플릿 코드를 참고하세요.
|
|
6
|
+
모든 컴포넌트는 `import { ComponentName } from "@lawkit/ui"` 로 사용합니다.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Alert
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Alert } from "@lds/ui-v3";
|
|
14
|
+
|
|
15
|
+
// 기본 사용
|
|
16
|
+
<Alert type="info" closable onClose={() => {}}>
|
|
17
|
+
중요! 이것은 기본 알림입니다. 확인해주세요!
|
|
18
|
+
</Alert>
|
|
19
|
+
|
|
20
|
+
// 제목 + 본문 (Expanded)
|
|
21
|
+
<Alert type="info" title="알림 제목" closable>
|
|
22
|
+
이것은 확장된 알림입니다. 제목과 본문이 분리됩니다.
|
|
23
|
+
</Alert>
|
|
24
|
+
|
|
25
|
+
// 텍스트 버튼 포함
|
|
26
|
+
<Alert type="info" textButton={{ label: "자세히 보기", onClick: handleClick }}>
|
|
27
|
+
알림 메시지
|
|
28
|
+
</Alert>
|
|
29
|
+
|
|
30
|
+
// 액션 버튼 포함 (승인/반려)
|
|
31
|
+
<Alert type="confirm" title="의견 검토 중" actions={[
|
|
32
|
+
{ label: "승인", intent: "primary", onClick: handleApprove },
|
|
33
|
+
{ label: "반려", intent: "warning", onClick: handleReject },
|
|
34
|
+
]}>
|
|
35
|
+
의견 검토 중 (검토자 : 김팀장)
|
|
36
|
+
</Alert>
|
|
37
|
+
|
|
38
|
+
// 작은 사이즈
|
|
39
|
+
<Alert type="info" size="small">
|
|
40
|
+
작은 사이즈 알림입니다.
|
|
41
|
+
</Alert>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## AutoComplete
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
const [value, setValue] = useState("");
|
|
48
|
+
|
|
49
|
+
const options = [
|
|
50
|
+
{ value: "jhson1", label: "손준호 (jhson1)" },
|
|
51
|
+
{ value: "kim01", label: "김민수 (kim01)" },
|
|
52
|
+
{ value: "park22", label: "박서연 (park22)" },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
<AutoComplete
|
|
56
|
+
options={options}
|
|
57
|
+
value={value}
|
|
58
|
+
onChange={(val) => setValue(val)}
|
|
59
|
+
placeholder="이름 또는 ID로 검색"
|
|
60
|
+
/>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Avatar
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { Avatar, AvatarGroup } from "@lds/ui-v3";
|
|
67
|
+
|
|
68
|
+
// Photo 아바타
|
|
69
|
+
<Avatar src="/photo.jpg" size="md" />
|
|
70
|
+
|
|
71
|
+
// System 아바타
|
|
72
|
+
<Avatar system />
|
|
73
|
+
|
|
74
|
+
// 이니셜 아바타
|
|
75
|
+
<Avatar initials="PI" color="success" />
|
|
76
|
+
|
|
77
|
+
// 상태 표시
|
|
78
|
+
<Avatar src="/photo.jpg" status="online" />
|
|
79
|
+
|
|
80
|
+
// 아바타 그룹
|
|
81
|
+
<AvatarGroup>
|
|
82
|
+
<Avatar src="/a.jpg" size="sm" />
|
|
83
|
+
<Avatar src="/b.jpg" size="sm" />
|
|
84
|
+
</AvatarGroup>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Button
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { Button } from "@lds/ui-v3";
|
|
91
|
+
|
|
92
|
+
// 기본 버튼
|
|
93
|
+
<Button>확인</Button>
|
|
94
|
+
|
|
95
|
+
// Outline 버튼
|
|
96
|
+
<Button variant="outline">취소</Button>
|
|
97
|
+
|
|
98
|
+
// 색상 변경
|
|
99
|
+
<Button color="danger">삭제</Button>
|
|
100
|
+
<Button variant="outline" color="secondary">보류</Button>
|
|
101
|
+
|
|
102
|
+
// 크기 변경
|
|
103
|
+
<Button size="small">작은 버튼</Button>
|
|
104
|
+
<Button size="large">큰 버튼</Button>
|
|
105
|
+
|
|
106
|
+
// 아이콘 포함
|
|
107
|
+
<Button iconLeft={<MyIcon />}>아이콘 버튼</Button>
|
|
108
|
+
<Button iconRight={<ArrowIcon />}>다음</Button>
|
|
109
|
+
|
|
110
|
+
// Pill 모양
|
|
111
|
+
<Button shape="round">라운드</Button>
|
|
112
|
+
|
|
113
|
+
// 비활성화
|
|
114
|
+
<Button disabled>비활성화</Button>
|
|
115
|
+
|
|
116
|
+
// 이벤트 핸들러
|
|
117
|
+
<Button onClick={handleSubmit}>제출</Button>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## ButtonGroup
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { useState } from "react";
|
|
124
|
+
import { ButtonGroup } from "@lds/ui-v3";
|
|
125
|
+
|
|
126
|
+
const [value, setValue] = useState("left");
|
|
127
|
+
|
|
128
|
+
// 기본 Fill 스타일
|
|
129
|
+
<ButtonGroup
|
|
130
|
+
items={[
|
|
131
|
+
{ value: "left", label: "Left" },
|
|
132
|
+
{ value: "center", label: "Center" },
|
|
133
|
+
{ value: "right", label: "Right" },
|
|
134
|
+
]}
|
|
135
|
+
value={value}
|
|
136
|
+
onChange={setValue}
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
// Outline 스타일
|
|
140
|
+
<ButtonGroup items={items} value={value} onChange={setValue} variant="outline" />
|
|
141
|
+
|
|
142
|
+
// Small 사이즈 + 아이콘
|
|
143
|
+
<ButtonGroup
|
|
144
|
+
items={[
|
|
145
|
+
{ value: "a", label: "옵션A", icon: <MyIcon /> },
|
|
146
|
+
{ value: "b", label: "옵션B", icon: <MyIcon /> },
|
|
147
|
+
]}
|
|
148
|
+
value={value}
|
|
149
|
+
onChange={setValue}
|
|
150
|
+
size="small"
|
|
151
|
+
/>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## ButtonTab
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
import { useState } from "react";
|
|
158
|
+
import { ButtonTab } from "@lds/ui-v3";
|
|
159
|
+
|
|
160
|
+
const [tab, setTab] = useState("tab1");
|
|
161
|
+
|
|
162
|
+
<ButtonTab
|
|
163
|
+
items={[
|
|
164
|
+
{ value: "tab1", label: "Tab 1" },
|
|
165
|
+
{ value: "tab2", label: "Tab 2" },
|
|
166
|
+
{ value: "tab3", label: "Tab 3", disabled: true },
|
|
167
|
+
{ value: "tab4", label: "Tab 4" },
|
|
168
|
+
]}
|
|
169
|
+
value={tab}
|
|
170
|
+
onChange={setTab}
|
|
171
|
+
/>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## CalendarPopover
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { CalendarPopover } from "@lds/ui-v3";
|
|
178
|
+
|
|
179
|
+
<CalendarPopover
|
|
180
|
+
badge="계약검토"
|
|
181
|
+
title="휴맥스이브이 법무시스템 Law.ai 공급 계약"
|
|
182
|
+
fields={[
|
|
183
|
+
{ label: "관리번호", value: "C20221108-0001" },
|
|
184
|
+
{ label: "상대계약자", value: "휴맥스이브이" },
|
|
185
|
+
{ label: "요청자", value: "박영업" },
|
|
186
|
+
{ label: "법무팀 담당자", value: "이법무" },
|
|
187
|
+
{ label: "수정일", value: "2023-06-13" },
|
|
188
|
+
]}
|
|
189
|
+
primaryText="일정 수정"
|
|
190
|
+
secondaryText="사건 바로가기"
|
|
191
|
+
placement="right"
|
|
192
|
+
>
|
|
193
|
+
<button>6/15</button>
|
|
194
|
+
</CalendarPopover>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Card
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import { Card, CardHeader, CardBody, CardFooter } from "@lds/ui-v3";
|
|
201
|
+
|
|
202
|
+
// 간편 API — 헤더만
|
|
203
|
+
<Card header="헤더" title="카드 타이틀">
|
|
204
|
+
<p>카드 본문 텍스트</p>
|
|
205
|
+
</Card>
|
|
206
|
+
|
|
207
|
+
// 간편 API — 헤더 + 푸터
|
|
208
|
+
<Card header="헤더" title="카드 타이틀" footer="푸터">
|
|
209
|
+
<p>카드 본문 텍스트</p>
|
|
210
|
+
</Card>
|
|
211
|
+
|
|
212
|
+
// Bordered 변형 + 헤더 액션
|
|
213
|
+
<Card
|
|
214
|
+
header="결재선"
|
|
215
|
+
bordered
|
|
216
|
+
headerActions={
|
|
217
|
+
<div style={{ display: "flex", gap: 8 }}>
|
|
218
|
+
<Button variant="outline" color="secondary" size="small">결재의견 추가</Button>
|
|
219
|
+
<Button size="small">결재하기</Button>
|
|
220
|
+
</div>
|
|
221
|
+
}
|
|
222
|
+
>
|
|
223
|
+
<p>결재선 콘텐츠</p>
|
|
224
|
+
</Card>
|
|
225
|
+
|
|
226
|
+
// Compound 패턴 — 자유 레이아웃
|
|
227
|
+
<Card bordered>
|
|
228
|
+
<CardHeader actions={<Button size="small">액션</Button>}>
|
|
229
|
+
커스텀 헤더
|
|
230
|
+
</CardHeader>
|
|
231
|
+
<CardBody>
|
|
232
|
+
<p>본문 콘텐츠</p>
|
|
233
|
+
</CardBody>
|
|
234
|
+
<CardFooter>커스텀 푸터</CardFooter>
|
|
235
|
+
</Card>
|
|
236
|
+
|
|
237
|
+
// Body만 (헤더/푸터 없음)
|
|
238
|
+
<Card>
|
|
239
|
+
<p>심플 카드</p>
|
|
240
|
+
</Card>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## ChartTooltip
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
import { ChartTooltip } from "@lds/ui-v3";
|
|
247
|
+
|
|
248
|
+
// Default (헤더 + 컬러 dot)
|
|
249
|
+
<ChartTooltip
|
|
250
|
+
header="12/12"
|
|
251
|
+
items={[{ label: "Label :", value: 90, color: "#2151EC" }]}
|
|
252
|
+
/>
|
|
253
|
+
|
|
254
|
+
// Pie (파란 배경)
|
|
255
|
+
<ChartTooltip
|
|
256
|
+
variant="pie"
|
|
257
|
+
items={[{ label: "Label :", value: 90 }]}
|
|
258
|
+
/>
|
|
259
|
+
|
|
260
|
+
// 여러 시리즈
|
|
261
|
+
<ChartTooltip
|
|
262
|
+
header="2023-06-15"
|
|
263
|
+
items={[
|
|
264
|
+
{ label: "계약건수 :", value: 120, color: "#2151EC" },
|
|
265
|
+
{ label: "소송건수 :", value: 45, color: "#F04438" },
|
|
266
|
+
{ label: "자문건수 :", value: 78, color: "#12B76A" },
|
|
267
|
+
]}
|
|
268
|
+
/>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Checkbox
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
import { useState } from "react";
|
|
275
|
+
import { Checkbox } from "@lds/ui-v3";
|
|
276
|
+
|
|
277
|
+
const [checked, setChecked] = useState(false);
|
|
278
|
+
|
|
279
|
+
<Checkbox label="동의합니다" checked={checked} onCheckedChange={setChecked} />
|
|
280
|
+
<Checkbox size="small" label="소형" />
|
|
281
|
+
<Checkbox size="large" label="대형" disabled />
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## ChipsNavigation
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
import { useState } from "react";
|
|
288
|
+
import { ChipsNavigation } from "@lds/ui-v3";
|
|
289
|
+
|
|
290
|
+
// 단일 선택
|
|
291
|
+
const [filter, setFilter] = useState<string | string[]>("");
|
|
292
|
+
|
|
293
|
+
<ChipsNavigation
|
|
294
|
+
items={[
|
|
295
|
+
{ value: "opt1", label: "Option 1" },
|
|
296
|
+
{ value: "opt2", label: "Option 2" },
|
|
297
|
+
{ value: "opt3", label: "Option 3" },
|
|
298
|
+
]}
|
|
299
|
+
value={filter}
|
|
300
|
+
onChange={setFilter}
|
|
301
|
+
/>
|
|
302
|
+
|
|
303
|
+
// 다중 선택
|
|
304
|
+
const [filters, setFilters] = useState<string | string[]>([]);
|
|
305
|
+
|
|
306
|
+
<ChipsNavigation
|
|
307
|
+
items={items}
|
|
308
|
+
value={filters}
|
|
309
|
+
onChange={setFilters}
|
|
310
|
+
multiple
|
|
311
|
+
/>
|
|
312
|
+
|
|
313
|
+
// "All" 텍스트 커스텀
|
|
314
|
+
<ChipsNavigation
|
|
315
|
+
items={items}
|
|
316
|
+
value={filter}
|
|
317
|
+
onChange={setFilter}
|
|
318
|
+
allLabel="전체"
|
|
319
|
+
/>
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Collapse
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
import { useState } from "react";
|
|
326
|
+
import { Collapse, CollapseGroup } from "@lds/ui-v3";
|
|
327
|
+
|
|
328
|
+
// 기본 (Uncontrolled)
|
|
329
|
+
<Collapse header="헤더">
|
|
330
|
+
<p>접기/펼치기 콘텐츠</p>
|
|
331
|
+
</Collapse>
|
|
332
|
+
|
|
333
|
+
// Controlled
|
|
334
|
+
const [open, setOpen] = useState(false);
|
|
335
|
+
<Collapse header="헤더" expanded={open} onToggle={setOpen}>
|
|
336
|
+
<p>콘텐츠</p>
|
|
337
|
+
</Collapse>
|
|
338
|
+
|
|
339
|
+
// 스타일 변형
|
|
340
|
+
<Collapse variant="shadow" header="Shadow">콘텐츠</Collapse>
|
|
341
|
+
<Collapse variant="border" header="Border">콘텐츠</Collapse>
|
|
342
|
+
<Collapse variant="margin" header="Margin">콘텐츠</Collapse>
|
|
343
|
+
|
|
344
|
+
// 그룹 (아코디언 배치)
|
|
345
|
+
<CollapseGroup variant="margin">
|
|
346
|
+
<Collapse variant="margin" header="패널 1">내용 1</Collapse>
|
|
347
|
+
<Collapse variant="margin" header="패널 2">내용 2</Collapse>
|
|
348
|
+
</CollapseGroup>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## DataTable
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
import { useState } from "react";
|
|
355
|
+
import { DataTable } from "@lds/ui-v3";
|
|
356
|
+
import type { ColumnDef, SortingState, RowSelectionState } from "@tanstack/react-table";
|
|
357
|
+
|
|
358
|
+
// 1. 데이터 타입 정의
|
|
359
|
+
interface User {
|
|
360
|
+
id: number;
|
|
361
|
+
name: string;
|
|
362
|
+
email: string;
|
|
363
|
+
role: string;
|
|
364
|
+
status: string;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 2. 컬럼 정의
|
|
368
|
+
const columns: ColumnDef<User>[] = [
|
|
369
|
+
{ accessorKey: "id", header: "번호", size: 80 },
|
|
370
|
+
{ accessorKey: "name", header: "이름" },
|
|
371
|
+
{ accessorKey: "email", header: "이메일" },
|
|
372
|
+
{ accessorKey: "role", header: "역할", size: 120 },
|
|
373
|
+
{ accessorKey: "status", header: "상태", size: 100 },
|
|
374
|
+
];
|
|
375
|
+
|
|
376
|
+
// 3. 기본 사용
|
|
377
|
+
<DataTable data={users} columns={columns} />
|
|
378
|
+
|
|
379
|
+
// 4. 정렬 + 선택 + bordered
|
|
380
|
+
function UserTable() {
|
|
381
|
+
const [sorting, setSorting] = useState<SortingState>([]);
|
|
382
|
+
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
|
383
|
+
|
|
384
|
+
return (
|
|
385
|
+
<DataTable
|
|
386
|
+
data={users}
|
|
387
|
+
columns={columns}
|
|
388
|
+
selectable
|
|
389
|
+
bordered
|
|
390
|
+
sorting={sorting}
|
|
391
|
+
onSortingChange={setSorting}
|
|
392
|
+
rowSelection={rowSelection}
|
|
393
|
+
onRowSelectionChange={setRowSelection}
|
|
394
|
+
getRowId={(row) => String(row.id)}
|
|
395
|
+
onRowClick={(row) => console.log(row)}
|
|
396
|
+
emptyText="조회된 데이터가 없습니다."
|
|
397
|
+
/>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## DatePicker
|
|
403
|
+
|
|
404
|
+
```tsx
|
|
405
|
+
import { useState } from "react";
|
|
406
|
+
import { DatePicker, DateRangePicker } from "@lds/ui-v3";
|
|
407
|
+
|
|
408
|
+
function MyPage() {
|
|
409
|
+
const [date, setDate] = useState<Date | null>(new Date());
|
|
410
|
+
const [start, setStart] = useState<Date | null>(null);
|
|
411
|
+
const [end, setEnd] = useState<Date | null>(null);
|
|
412
|
+
|
|
413
|
+
return (
|
|
414
|
+
<>
|
|
415
|
+
{/* 단일 날짜 선택 */}
|
|
416
|
+
<DatePicker value={date} onChange={setDate} />
|
|
417
|
+
|
|
418
|
+
{/* 시간 포함 */}
|
|
419
|
+
<DatePicker value={date} onChange={setDate} showTime />
|
|
420
|
+
|
|
421
|
+
{/* 날짜 범위 선택 */}
|
|
422
|
+
<DateRangePicker
|
|
423
|
+
startDate={start}
|
|
424
|
+
endDate={end}
|
|
425
|
+
onChange={({ start, end }) => { setStart(start); setEnd(end); }}
|
|
426
|
+
/>
|
|
427
|
+
</>
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Dropdown
|
|
433
|
+
|
|
434
|
+
```tsx
|
|
435
|
+
import { useState } from "react";
|
|
436
|
+
import { Dropdown } from "@lds/ui-v3";
|
|
437
|
+
|
|
438
|
+
// 기본 단일 선택
|
|
439
|
+
<Dropdown
|
|
440
|
+
options={[
|
|
441
|
+
{ value: "1y", label: "지난 1년" },
|
|
442
|
+
{ value: "3y", label: "지난 3년" },
|
|
443
|
+
]}
|
|
444
|
+
placeholder="기간 선택"
|
|
445
|
+
onChange={(val) => console.log(val)}
|
|
446
|
+
/>
|
|
447
|
+
|
|
448
|
+
// Multi Level (설명 포함)
|
|
449
|
+
<Dropdown
|
|
450
|
+
options={[
|
|
451
|
+
{ value: "add", label: "결재자 추가", description: "문서에 결재자를 추가합니다." },
|
|
452
|
+
]}
|
|
453
|
+
/>
|
|
454
|
+
|
|
455
|
+
// Multi Check (다중 선택)
|
|
456
|
+
<Dropdown
|
|
457
|
+
multiple
|
|
458
|
+
panelHeader="부서 선택"
|
|
459
|
+
options={[{ value: "sales", label: "영업팀" }, { value: "dev", label: "개발팀" }]}
|
|
460
|
+
onChange={(values) => console.log(values)}
|
|
461
|
+
/>
|
|
462
|
+
|
|
463
|
+
// Controlled
|
|
464
|
+
const [value, setValue] = useState("1y");
|
|
465
|
+
<Dropdown options={options} value={value} onChange={setValue} />
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## FileUpload
|
|
469
|
+
|
|
470
|
+
```tsx
|
|
471
|
+
import { useState } from "react";
|
|
472
|
+
import {
|
|
473
|
+
FileUploadArea,
|
|
474
|
+
FileThumbnail,
|
|
475
|
+
FileItem,
|
|
476
|
+
FileAttachBadge,
|
|
477
|
+
} from "@lds/ui-v3";
|
|
478
|
+
|
|
479
|
+
// 파일 업로드 영역 + 파일 목록
|
|
480
|
+
const [files, setFiles] = useState([]);
|
|
481
|
+
|
|
482
|
+
<FileUploadArea
|
|
483
|
+
onFilesAdded={(newFiles) =>
|
|
484
|
+
setFiles((prev) => [...prev, ...newFiles.map((f) => ({ name: f.name, size: f.size }))])
|
|
485
|
+
}
|
|
486
|
+
>
|
|
487
|
+
{files.map((f) => (
|
|
488
|
+
<FileItem
|
|
489
|
+
key={f.name}
|
|
490
|
+
filename={f.name}
|
|
491
|
+
fileMeta={\`\${(f.size / 1024 / 1024).toFixed(1)} MB\`}
|
|
492
|
+
onDelete={() => setFiles((prev) => prev.filter((x) => x.name !== f.name))}
|
|
493
|
+
/>
|
|
494
|
+
))}
|
|
495
|
+
</FileUploadArea>
|
|
496
|
+
|
|
497
|
+
// 이미지 썸네일
|
|
498
|
+
<FileThumbnail
|
|
499
|
+
src="/img.jpg"
|
|
500
|
+
filename="photo.jpg"
|
|
501
|
+
size="large"
|
|
502
|
+
layout="horizontal"
|
|
503
|
+
onDownload={() => {}}
|
|
504
|
+
onExpand={() => {}}
|
|
505
|
+
/>
|
|
506
|
+
|
|
507
|
+
// 파일 첨부 배지
|
|
508
|
+
<FileAttachBadge filename="report.pdf" onRemove={() => {}} />
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## IconButtonGroup
|
|
512
|
+
|
|
513
|
+
```tsx
|
|
514
|
+
import { useState } from "react";
|
|
515
|
+
import { IconButtonGroup } from "@lds/ui-v3";
|
|
516
|
+
|
|
517
|
+
const [view, setView] = useState("card");
|
|
518
|
+
|
|
519
|
+
<IconButtonGroup
|
|
520
|
+
items={[
|
|
521
|
+
{ value: "card", icon: <CardIcon />, "aria-label": "카드 보기" },
|
|
522
|
+
{ value: "grid", icon: <GridIcon />, "aria-label": "그리드 보기" },
|
|
523
|
+
{ value: "list", icon: <MenuIcon />, "aria-label": "리스트 보기" },
|
|
524
|
+
]}
|
|
525
|
+
value={view}
|
|
526
|
+
onChange={setView}
|
|
527
|
+
variant="fill"
|
|
528
|
+
/>
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## InfoPopover
|
|
532
|
+
|
|
533
|
+
```tsx
|
|
534
|
+
import { InfoPopover } from "@lds/ui-v3";
|
|
535
|
+
|
|
536
|
+
<InfoPopover
|
|
537
|
+
title="법무검토 중 외 3개"
|
|
538
|
+
steps={[
|
|
539
|
+
{ label: "법무 검토 중" },
|
|
540
|
+
{ label: "요청자 검토 중" },
|
|
541
|
+
{ label: "계약서 검토 완료" },
|
|
542
|
+
{ label: "체결 품의 중" },
|
|
543
|
+
]}
|
|
544
|
+
>
|
|
545
|
+
<span>법무검토 중 외 3개</span>
|
|
546
|
+
</InfoPopover>
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## Input
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
import { useState } from "react";
|
|
553
|
+
import { Input, InputGroup, MultiSelect } from "@lds/ui-v3";
|
|
554
|
+
import type { MultiSelectItem } from "@lds/ui-v3";
|
|
555
|
+
|
|
556
|
+
// 기본
|
|
557
|
+
<Input placeholder="이메일 입력" />
|
|
558
|
+
|
|
559
|
+
// 사이즈
|
|
560
|
+
<Input placeholder="Small" inputSize="small" />
|
|
561
|
+
<Input placeholder="Medium" inputSize="medium" />
|
|
562
|
+
<Input placeholder="Large" inputSize="large" />
|
|
563
|
+
|
|
564
|
+
// 왼쪽 아이콘 + 접미사
|
|
565
|
+
<Input placeholder="검색" leftIcon={<SearchIcon />} suffix={<span>Option ▾</span>} />
|
|
566
|
+
|
|
567
|
+
// InputGroup (라벨 + 도움말 + 상태)
|
|
568
|
+
<InputGroup label="Email" required helperText="유효한 이메일을 입력하세요" state="default">
|
|
569
|
+
<Input placeholder="email@example.com" />
|
|
570
|
+
</InputGroup>
|
|
571
|
+
|
|
572
|
+
// MultiSelect
|
|
573
|
+
const [items, setItems] = useState<MultiSelectItem[]>([
|
|
574
|
+
{ key: "1", label: "User Name1" },
|
|
575
|
+
]);
|
|
576
|
+
<MultiSelect
|
|
577
|
+
value={items}
|
|
578
|
+
onRemove={(key) => setItems((prev) => prev.filter((i) => i.key !== key))}
|
|
579
|
+
placeholder="검색..."
|
|
580
|
+
/>
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
## ListGroup
|
|
584
|
+
|
|
585
|
+
```tsx
|
|
586
|
+
import { ListGroup, ListGroupItem, BottomSheet } from "@lds/ui-v3";
|
|
587
|
+
|
|
588
|
+
// Basic (간편 API)
|
|
589
|
+
<ListGroup items={["항목 1", "항목 2", "항목 3"]} />
|
|
590
|
+
|
|
591
|
+
// Basic (Compound 패턴)
|
|
592
|
+
<ListGroup>
|
|
593
|
+
<ListGroupItem>항목 1</ListGroupItem>
|
|
594
|
+
<ListGroupItem>항목 2</ListGroupItem>
|
|
595
|
+
</ListGroup>
|
|
596
|
+
|
|
597
|
+
// With Icon
|
|
598
|
+
<ListGroup>
|
|
599
|
+
<ListGroupItem leading={<Icon />}>항목</ListGroupItem>
|
|
600
|
+
</ListGroup>
|
|
601
|
+
|
|
602
|
+
// Custom Content + active
|
|
603
|
+
<ListGroupItem active trailing={<span>3일 전</span>}>
|
|
604
|
+
<strong>제목</strong>
|
|
605
|
+
<p>내용</p>
|
|
606
|
+
</ListGroupItem>
|
|
607
|
+
|
|
608
|
+
// Bottom Sheet (MO)
|
|
609
|
+
<BottomSheet>
|
|
610
|
+
<ListGroup variant="flush">
|
|
611
|
+
<ListGroupItem leading={<span>🔏</span>}>인감 사용 신청</ListGroupItem>
|
|
612
|
+
<ListGroupItem leading={<span>✕</span>} danger>계약 중단</ListGroupItem>
|
|
613
|
+
</ListGroup>
|
|
614
|
+
</BottomSheet>
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
## Mention
|
|
618
|
+
|
|
619
|
+
```tsx
|
|
620
|
+
import { Mention } from "@lds/ui-v3";
|
|
621
|
+
|
|
622
|
+
// 기본 멘션
|
|
623
|
+
<Mention name="나담당" />
|
|
624
|
+
|
|
625
|
+
// 문장 내 사용
|
|
626
|
+
<p>검토자: <Mention name="나담당" /> 님이 확인 중입니다.</p>
|
|
627
|
+
|
|
628
|
+
// 여러 멘션
|
|
629
|
+
<div style={{ display: "flex", gap: 8 }}>
|
|
630
|
+
<Mention name="김철수" />
|
|
631
|
+
<Mention name="이영희" />
|
|
632
|
+
</div>
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
## Modal
|
|
636
|
+
|
|
637
|
+
```tsx
|
|
638
|
+
import { useState } from "react";
|
|
639
|
+
import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from "@lds/ui-v3";
|
|
640
|
+
|
|
641
|
+
function MyPage() {
|
|
642
|
+
const [open, setOpen] = useState(false);
|
|
643
|
+
return (
|
|
644
|
+
<>
|
|
645
|
+
<Button onClick={() => setOpen(true)}>모달 열기</Button>
|
|
646
|
+
|
|
647
|
+
{/* 간편 API */}
|
|
648
|
+
<Modal
|
|
649
|
+
open={open}
|
|
650
|
+
onClose={() => setOpen(false)}
|
|
651
|
+
size="medium"
|
|
652
|
+
title="모달 제목"
|
|
653
|
+
footer={
|
|
654
|
+
<>
|
|
655
|
+
<Button variant="outline" color="secondary">취소</Button>
|
|
656
|
+
<Button>저장</Button>
|
|
657
|
+
</>
|
|
658
|
+
}
|
|
659
|
+
>
|
|
660
|
+
<p>모달 본문</p>
|
|
661
|
+
</Modal>
|
|
662
|
+
|
|
663
|
+
{/* Compound 패턴 */}
|
|
664
|
+
<Modal open={open} onClose={() => setOpen(false)} size="xlarge">
|
|
665
|
+
<ModalHeader>커스텀 헤더</ModalHeader>
|
|
666
|
+
<ModalBody><p>본문</p></ModalBody>
|
|
667
|
+
<ModalFooter><Button>확인</Button></ModalFooter>
|
|
668
|
+
</Modal>
|
|
669
|
+
</>
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
## NavigationTab
|
|
675
|
+
|
|
676
|
+
```tsx
|
|
677
|
+
import { useState } from "react";
|
|
678
|
+
import { NavigationTab } from "@lds/ui-v3";
|
|
679
|
+
import type { NavigationTabItem } from "@lds/ui-v3";
|
|
680
|
+
|
|
681
|
+
const [tab, setTab] = useState("overview");
|
|
682
|
+
|
|
683
|
+
// 아이콘 + 라벨
|
|
684
|
+
<NavigationTab
|
|
685
|
+
items={[
|
|
686
|
+
{ value: "overview", label: "Overview", icon: <InfoIcon /> },
|
|
687
|
+
{ value: "details", label: "Details", icon: <DetailIcon /> },
|
|
688
|
+
{ value: "settings", label: "Settings", icon: <SettingsIcon /> },
|
|
689
|
+
]}
|
|
690
|
+
value={tab}
|
|
691
|
+
onChange={setTab}
|
|
692
|
+
/>
|
|
693
|
+
|
|
694
|
+
// 텍스트만
|
|
695
|
+
<NavigationTab
|
|
696
|
+
items={[
|
|
697
|
+
{ value: "a", label: "Overview" },
|
|
698
|
+
{ value: "b", label: "Details" },
|
|
699
|
+
]}
|
|
700
|
+
value={tab}
|
|
701
|
+
onChange={setTab}
|
|
702
|
+
/>
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
## NumberInput
|
|
706
|
+
|
|
707
|
+
```tsx
|
|
708
|
+
import { useState } from "react";
|
|
709
|
+
import { NumberInput } from "@lds/ui-v3";
|
|
710
|
+
|
|
711
|
+
const [count, setCount] = useState(50);
|
|
712
|
+
|
|
713
|
+
// 기본
|
|
714
|
+
<NumberInput value={count} onChange={setCount} />
|
|
715
|
+
|
|
716
|
+
// 사이즈
|
|
717
|
+
<NumberInput size="small" value={count} onChange={setCount} />
|
|
718
|
+
<NumberInput size="large" value={count} onChange={setCount} />
|
|
719
|
+
|
|
720
|
+
// Min / Max / Step
|
|
721
|
+
<NumberInput value={count} onChange={setCount} min={0} max={100} step={5} />
|
|
722
|
+
|
|
723
|
+
// 비활성화
|
|
724
|
+
<NumberInput value={50} disabled />
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
## Pagination
|
|
728
|
+
|
|
729
|
+
```tsx
|
|
730
|
+
import { useState } from "react";
|
|
731
|
+
import { Pagination, PaginationCount } from "@lds/ui-v3";
|
|
732
|
+
|
|
733
|
+
function MyPage() {
|
|
734
|
+
const [page, setPage] = useState(1);
|
|
735
|
+
return (
|
|
736
|
+
<>
|
|
737
|
+
{/* Basic */}
|
|
738
|
+
<Pagination page={page} totalPages={10} onPageChange={setPage} />
|
|
739
|
+
|
|
740
|
+
{/* With Count */}
|
|
741
|
+
<Pagination page={page} totalPages={100} onPageChange={setPage} totalCount={1144} />
|
|
742
|
+
|
|
743
|
+
{/* Count only */}
|
|
744
|
+
<PaginationCount totalCount={1144} />
|
|
745
|
+
</>
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
## Popover
|
|
751
|
+
|
|
752
|
+
```tsx
|
|
753
|
+
import { Popover } from "@lds/ui-v3";
|
|
754
|
+
|
|
755
|
+
// 기본 (간편 API)
|
|
756
|
+
<Popover
|
|
757
|
+
title="Popover Title"
|
|
758
|
+
content="This is a very beautiful popover, show some love."
|
|
759
|
+
confirmText="Read More"
|
|
760
|
+
cancelText="Skip"
|
|
761
|
+
placement="right"
|
|
762
|
+
>
|
|
763
|
+
<button>Click me</button>
|
|
764
|
+
</Popover>
|
|
765
|
+
|
|
766
|
+
// 커스텀 바디 (compound)
|
|
767
|
+
<Popover title="Custom" popoverBody={<MyCustomContent />} placement="bottom">
|
|
768
|
+
<button>Click</button>
|
|
769
|
+
</Popover>
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
## Progress
|
|
773
|
+
|
|
774
|
+
```tsx
|
|
775
|
+
import { ProgressBar, StepBar } from "@lds/ui-v3";
|
|
776
|
+
|
|
777
|
+
// Basic
|
|
778
|
+
<ProgressBar value={75} />
|
|
779
|
+
|
|
780
|
+
// With value label
|
|
781
|
+
<ProgressBar value={75} showValue />
|
|
782
|
+
|
|
783
|
+
// Striped + animated
|
|
784
|
+
<ProgressBar value={60} striped animated />
|
|
785
|
+
|
|
786
|
+
// Multiple bars
|
|
787
|
+
<ProgressBar segments={[
|
|
788
|
+
{ value: 30, color: "primary", label: "30%" },
|
|
789
|
+
{ value: 45, color: "warning", label: "45%" },
|
|
790
|
+
{ value: 15, color: "danger", label: "15%" },
|
|
791
|
+
]} />
|
|
792
|
+
|
|
793
|
+
// StepBar
|
|
794
|
+
<StepBar steps={[
|
|
795
|
+
{ label: "임시 저장", status: "completed" },
|
|
796
|
+
{ label: "법무검토 중", status: "active" },
|
|
797
|
+
{ label: "계약 종료", status: "scheduled" },
|
|
798
|
+
]} />
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
## Radio
|
|
802
|
+
|
|
803
|
+
```tsx
|
|
804
|
+
import { useState } from "react";
|
|
805
|
+
import { Radio, RadioGroup } from "@lds/ui-v3";
|
|
806
|
+
|
|
807
|
+
const [selected, setSelected] = useState("a");
|
|
808
|
+
|
|
809
|
+
// Basic
|
|
810
|
+
<RadioGroup value={selected} onChange={setSelected}>
|
|
811
|
+
<Radio value="a" label="Option A" />
|
|
812
|
+
<Radio value="b" label="Option B" />
|
|
813
|
+
<Radio value="c" label="Option C" />
|
|
814
|
+
</RadioGroup>
|
|
815
|
+
|
|
816
|
+
// Customized 변형 (테두리 스타일)
|
|
817
|
+
<RadioGroup variant="customized" value={selected} onChange={setSelected}>
|
|
818
|
+
<Radio value="a" label="Option A" />
|
|
819
|
+
<Radio value="b" label="Option B" />
|
|
820
|
+
</RadioGroup>
|
|
821
|
+
|
|
822
|
+
// 수직 정렬
|
|
823
|
+
<RadioGroup value={selected} onChange={setSelected} vertical>
|
|
824
|
+
<Radio value="a" label="Option A" />
|
|
825
|
+
<Radio value="b" label="Option B" />
|
|
826
|
+
</RadioGroup>
|
|
827
|
+
|
|
828
|
+
// 사이즈
|
|
829
|
+
<RadioGroup size="small" value={selected} onChange={setSelected}>
|
|
830
|
+
<Radio value="a" label="Small A" />
|
|
831
|
+
<Radio value="b" label="Small B" />
|
|
832
|
+
</RadioGroup>
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
## Skeleton
|
|
836
|
+
|
|
837
|
+
```tsx
|
|
838
|
+
import { Skeleton } from "@lds/ui-v3";
|
|
839
|
+
|
|
840
|
+
// 사각형
|
|
841
|
+
<Skeleton width={200} height={120} />
|
|
842
|
+
|
|
843
|
+
// 원형 아바타
|
|
844
|
+
<Skeleton variant="circle" width={40} height={40} />
|
|
845
|
+
|
|
846
|
+
// 텍스트 3줄
|
|
847
|
+
<Skeleton variant="text" lines={3} />
|
|
848
|
+
|
|
849
|
+
// Skeleton.Content — 로딩 ↔ 콘텐츠 자동 전환
|
|
850
|
+
<Skeleton.Content
|
|
851
|
+
loading={isLoading}
|
|
852
|
+
fallback={<Skeleton variant="text" lines={3} />}
|
|
853
|
+
>
|
|
854
|
+
<p>{data.content}</p>
|
|
855
|
+
</Skeleton.Content>
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
## Slider
|
|
859
|
+
|
|
860
|
+
```tsx
|
|
861
|
+
import { useState } from "react";
|
|
862
|
+
import { Slider, RangeSlider } from "@lds/ui-v3";
|
|
863
|
+
|
|
864
|
+
const [value, setValue] = useState(50);
|
|
865
|
+
const [range, setRange] = useState<[number, number]>([25, 75]);
|
|
866
|
+
|
|
867
|
+
// 기본 슬라이더
|
|
868
|
+
<Slider value={value} onChange={setValue} />
|
|
869
|
+
|
|
870
|
+
// 틱 + 라벨 + 값 배지
|
|
871
|
+
<Slider value={value} onChange={setValue} showTicks showLabels showValue />
|
|
872
|
+
|
|
873
|
+
// Step 설정
|
|
874
|
+
<Slider value={value} onChange={setValue} step={10} showTicks showLabels showValue />
|
|
875
|
+
|
|
876
|
+
// 범위 슬라이더
|
|
877
|
+
<RangeSlider value={range} onChange={setRange} showTicks />
|
|
878
|
+
|
|
879
|
+
// 범위 슬라이더 + 라벨 + 값 배지
|
|
880
|
+
<RangeSlider value={range} onChange={setRange} showTicks showLabels showValue />
|
|
881
|
+
|
|
882
|
+
// 비활성화
|
|
883
|
+
<Slider value={50} disabled showTicks showLabels />
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
## Spinner
|
|
887
|
+
|
|
888
|
+
```tsx
|
|
889
|
+
import { Spinner } from "@lds/ui-v3";
|
|
890
|
+
|
|
891
|
+
// 기본
|
|
892
|
+
<Spinner />
|
|
893
|
+
|
|
894
|
+
// 크기 변경
|
|
895
|
+
<Spinner size="lg" />
|
|
896
|
+
|
|
897
|
+
// 라벨 포함
|
|
898
|
+
<Spinner size="lg" label="로딩 중..." />
|
|
899
|
+
|
|
900
|
+
// 흰색 (어두운 배경용)
|
|
901
|
+
<Spinner color="white" />
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
## Stack
|
|
905
|
+
|
|
906
|
+
```tsx
|
|
907
|
+
import { HStack, VStack } from "@lds/ui-v3";
|
|
908
|
+
|
|
909
|
+
// 기본 — gap만
|
|
910
|
+
<HStack gap="x2">
|
|
911
|
+
<Button>취소</Button>
|
|
912
|
+
<Button color="primary">확인</Button>
|
|
913
|
+
</HStack>
|
|
914
|
+
|
|
915
|
+
// 헤더 양끝 배치
|
|
916
|
+
<HStack gap="x3" justify="between" align="center">
|
|
917
|
+
<h2>제목</h2>
|
|
918
|
+
<Button>추가</Button>
|
|
919
|
+
</HStack>
|
|
920
|
+
|
|
921
|
+
// 폼 세로 쌓기
|
|
922
|
+
<VStack gap="x4">
|
|
923
|
+
<Input label="이름" />
|
|
924
|
+
<Input label="이메일" />
|
|
925
|
+
<Button>제출</Button>
|
|
926
|
+
</VStack>
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
## SweetAlert
|
|
930
|
+
|
|
931
|
+
```tsx
|
|
932
|
+
import { useState } from "react";
|
|
933
|
+
import { SweetAlert, Button } from "@lds/ui-v3";
|
|
934
|
+
|
|
935
|
+
const [open, setOpen] = useState(false);
|
|
936
|
+
|
|
937
|
+
<Button onClick={() => setOpen(true)}>삭제</Button>
|
|
938
|
+
|
|
939
|
+
<SweetAlert
|
|
940
|
+
open={open}
|
|
941
|
+
onClose={() => setOpen(false)}
|
|
942
|
+
intent="warning"
|
|
943
|
+
title="정말 삭제하시겠습니까?"
|
|
944
|
+
confirmLabel="삭제"
|
|
945
|
+
onConfirm={handleDelete}
|
|
946
|
+
cancelLabel="취소"
|
|
947
|
+
onCancel={() => setOpen(false)}
|
|
948
|
+
>
|
|
949
|
+
<p>삭제된 데이터는 복구할 수 없습니다.</p>
|
|
950
|
+
</SweetAlert>
|
|
951
|
+
|
|
952
|
+
// Intent 종류: "warning" | "success" | "danger" | "info"
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
## Switch
|
|
956
|
+
|
|
957
|
+
```tsx
|
|
958
|
+
import { useState } from "react";
|
|
959
|
+
import { Switch } from "@lds/ui-v3";
|
|
960
|
+
|
|
961
|
+
const [on, setOn] = useState(false);
|
|
962
|
+
|
|
963
|
+
// 기본
|
|
964
|
+
<Switch label="알림 받기" checked={on} onCheckedChange={setOn} />
|
|
965
|
+
|
|
966
|
+
// Small
|
|
967
|
+
<Switch size="small" checked={on} onCheckedChange={setOn} />
|
|
968
|
+
|
|
969
|
+
// 라벨 없이
|
|
970
|
+
<Switch checked={on} onCheckedChange={setOn} />
|
|
971
|
+
|
|
972
|
+
// 비활성화
|
|
973
|
+
<Switch label="Disabled" disabled />
|
|
974
|
+
<Switch label="Disabled On" checked disabled />
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
## Tabs
|
|
978
|
+
|
|
979
|
+
```tsx
|
|
980
|
+
import { useState } from "react";
|
|
981
|
+
import { Tabs } from "@lds/ui-v3";
|
|
982
|
+
import type { TabItem } from "@lds/ui-v3";
|
|
983
|
+
|
|
984
|
+
const [tab, setTab] = useState("tab1");
|
|
985
|
+
|
|
986
|
+
// 기본 탭
|
|
987
|
+
<Tabs
|
|
988
|
+
items={[
|
|
989
|
+
{ value: "tab1", label: "Tab 1" },
|
|
990
|
+
{ value: "tab2", label: "Tab 2" },
|
|
991
|
+
{ value: "tab3", label: "Tab 3" },
|
|
992
|
+
]}
|
|
993
|
+
value={tab}
|
|
994
|
+
onChange={setTab}
|
|
995
|
+
/>
|
|
996
|
+
|
|
997
|
+
// Badge 탭
|
|
998
|
+
<Tabs
|
|
999
|
+
items={[
|
|
1000
|
+
{ value: "tab1", label: "Tab 1", badge: 12 },
|
|
1001
|
+
{ value: "tab2", label: "Tab 2", badge: 9 },
|
|
1002
|
+
{ value: "tab3", label: "Tab 3", badge: 3 },
|
|
1003
|
+
]}
|
|
1004
|
+
value={tab}
|
|
1005
|
+
onChange={setTab}
|
|
1006
|
+
size="medium"
|
|
1007
|
+
/>
|
|
1008
|
+
|
|
1009
|
+
// Action 버튼 포함
|
|
1010
|
+
<Tabs
|
|
1011
|
+
items={items}
|
|
1012
|
+
value={tab}
|
|
1013
|
+
onChange={setTab}
|
|
1014
|
+
action={{
|
|
1015
|
+
label: "Add Tab",
|
|
1016
|
+
icon: <PlusIcon />,
|
|
1017
|
+
onClick: handleAddTab,
|
|
1018
|
+
}}
|
|
1019
|
+
/>
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
## TagSelect
|
|
1023
|
+
|
|
1024
|
+
```tsx
|
|
1025
|
+
import { useState } from "react";
|
|
1026
|
+
import { TagSelect } from "@lds/ui-v3";
|
|
1027
|
+
|
|
1028
|
+
const [value, setValue] = useState<string[]>([]);
|
|
1029
|
+
|
|
1030
|
+
<TagSelect
|
|
1031
|
+
options={[
|
|
1032
|
+
{ value: "1", label: "Option A" },
|
|
1033
|
+
{ value: "2", label: "Option B" },
|
|
1034
|
+
{ value: "3", label: "Option C" },
|
|
1035
|
+
]}
|
|
1036
|
+
value={value}
|
|
1037
|
+
onChange={setValue}
|
|
1038
|
+
placeholder="Placeholder"
|
|
1039
|
+
/>
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
## Toast
|
|
1043
|
+
|
|
1044
|
+
```tsx
|
|
1045
|
+
import { Toast, ToastContainer } from "@lds/ui-v3";
|
|
1046
|
+
|
|
1047
|
+
// 1-row
|
|
1048
|
+
<Toast title="Toast Title" intent="info" onClose={() => {}} />
|
|
1049
|
+
|
|
1050
|
+
// 2-row + progress
|
|
1051
|
+
<Toast
|
|
1052
|
+
title="Toast Title"
|
|
1053
|
+
time="11 mins ago"
|
|
1054
|
+
description="상세 설명 텍스트"
|
|
1055
|
+
showProgress
|
|
1056
|
+
progress={67}
|
|
1057
|
+
intent="info"
|
|
1058
|
+
onClose={() => {}}
|
|
1059
|
+
/>
|
|
1060
|
+
|
|
1061
|
+
// 자동 닫기 (5초) + 호버 일시정지
|
|
1062
|
+
<Toast
|
|
1063
|
+
title="5초 후 자동 닫기"
|
|
1064
|
+
intent="info"
|
|
1065
|
+
duration={5000}
|
|
1066
|
+
pauseOnHover
|
|
1067
|
+
onClose={() => {}}
|
|
1068
|
+
/>
|
|
1069
|
+
|
|
1070
|
+
// Container로 위치 지정
|
|
1071
|
+
<ToastContainer position="top-right">
|
|
1072
|
+
<Toast title="알림" intent="success" onClose={() => {}} />
|
|
1073
|
+
</ToastContainer>
|
|
1074
|
+
|
|
1075
|
+
// Intent 종류: "info" | "success" | "warning" | "error"
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
## Tooltip
|
|
1079
|
+
|
|
1080
|
+
```tsx
|
|
1081
|
+
import { Tooltip } from "@lds/ui-v3";
|
|
1082
|
+
|
|
1083
|
+
// 1-row (기본)
|
|
1084
|
+
<Tooltip content="Tooltip right" placement="right">
|
|
1085
|
+
<button>Hover me</button>
|
|
1086
|
+
</Tooltip>
|
|
1087
|
+
|
|
1088
|
+
// 2-row (제목 + 내용)
|
|
1089
|
+
<Tooltip title="이법무(법무팀)" content={"bmlee3@humaxit.com\\n010-1234-5678"} placement="right">
|
|
1090
|
+
<span>이법무</span>
|
|
1091
|
+
</Tooltip>
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
## TreeView
|
|
1095
|
+
|
|
1096
|
+
```tsx
|
|
1097
|
+
import { TreeView } from "@lds/ui-v3";
|
|
1098
|
+
import type { TreeNode } from "@lds/ui-v3";
|
|
1099
|
+
|
|
1100
|
+
const nodes: TreeNode[] = [
|
|
1101
|
+
{
|
|
1102
|
+
id: "root",
|
|
1103
|
+
label: "상위 마스터",
|
|
1104
|
+
labelColor: "primary",
|
|
1105
|
+
columns: [
|
|
1106
|
+
{ text: "C20190301", type: "code" },
|
|
1107
|
+
{ text: "계약 제목", type: "title" },
|
|
1108
|
+
{ text: "2019-01-03", type: "date" },
|
|
1109
|
+
],
|
|
1110
|
+
children: [
|
|
1111
|
+
{
|
|
1112
|
+
id: "child-1",
|
|
1113
|
+
label: "하위",
|
|
1114
|
+
labelColor: "secondary",
|
|
1115
|
+
columns: [
|
|
1116
|
+
{ text: "C20190301-01", type: "code" },
|
|
1117
|
+
{ text: "하위 계약 제목", type: "title" },
|
|
1118
|
+
{ text: "2020-01-03", type: "date" },
|
|
1119
|
+
],
|
|
1120
|
+
},
|
|
1121
|
+
],
|
|
1122
|
+
},
|
|
1123
|
+
];
|
|
1124
|
+
|
|
1125
|
+
// 기본
|
|
1126
|
+
<TreeView nodes={nodes} defaultExpandedIds={["root"]} />
|
|
1127
|
+
|
|
1128
|
+
// 노드 선택
|
|
1129
|
+
<TreeView
|
|
1130
|
+
nodes={nodes}
|
|
1131
|
+
defaultExpandedIds={["root"]}
|
|
1132
|
+
selectedId={selectedId}
|
|
1133
|
+
onNodeSelect={(node) => setSelectedId(node.id)}
|
|
1134
|
+
/>
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
## Widget
|
|
1138
|
+
|
|
1139
|
+
```tsx
|
|
1140
|
+
import { Widget, StatCell, StatGrid, QuickMenuItem, ScheduleItem } from "@lds/ui-v3";
|
|
1141
|
+
|
|
1142
|
+
// 통계 위젯
|
|
1143
|
+
<Widget title="라이선스 현황">
|
|
1144
|
+
<StatGrid>
|
|
1145
|
+
<StatCell label="전체" value={120} valueColor="heading" />
|
|
1146
|
+
<StatCell label="사용중" value={98} valueColor="primary" active />
|
|
1147
|
+
<StatCell label="만료 예정" value={15} valueColor="warning" />
|
|
1148
|
+
<StatCell label="만료" value={7} valueColor="danger" />
|
|
1149
|
+
</StatGrid>
|
|
1150
|
+
</Widget>
|
|
1151
|
+
|
|
1152
|
+
// 접기/펼치기 + 뱃지
|
|
1153
|
+
<Widget title="공지사항" badge={3} collapsible>
|
|
1154
|
+
<p>내용</p>
|
|
1155
|
+
</Widget>
|
|
1156
|
+
|
|
1157
|
+
// 퀵 메뉴
|
|
1158
|
+
<Widget title="퀵 메뉴">
|
|
1159
|
+
<div style={{ display: "flex", gap: 8 }}>
|
|
1160
|
+
<QuickMenuItem icon={<FileIcon />} label="계약" />
|
|
1161
|
+
<QuickMenuItem icon={<FileIcon />} label="소송" />
|
|
1162
|
+
</div>
|
|
1163
|
+
</Widget>
|
|
1164
|
+
|
|
1165
|
+
// 일정 리스트
|
|
1166
|
+
<Widget title="최근 일정" badge={6} collapsible>
|
|
1167
|
+
<ScheduleItem date="2025.03.21" title="계약 검토 회의" body="A사 라이선스 계약 검토" />
|
|
1168
|
+
</Widget>
|
|
1169
|
+
|
|
1170
|
+
// Flush (테이블 스타일, 패딩 제거)
|
|
1171
|
+
<Widget title="최근 문서" badge={12} flush>
|
|
1172
|
+
{/* 테이블 컨텐츠 */}
|
|
1173
|
+
</Widget>
|
|
1174
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lawkit/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LDS Design System — React component library with design tokens",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
],
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
|
-
"README.md"
|
|
21
|
+
"README.md",
|
|
22
|
+
"CLAUDE.md",
|
|
23
|
+
"scripts"
|
|
22
24
|
],
|
|
23
25
|
"publishConfig": {
|
|
24
26
|
"access": "public"
|
|
@@ -69,6 +71,8 @@
|
|
|
69
71
|
"build": "vite build",
|
|
70
72
|
"lint": "echo \"No lint configured yet\"",
|
|
71
73
|
"check": "tsc --noEmit",
|
|
72
|
-
"test": "vitest run --config vitest.config.ts"
|
|
74
|
+
"test": "vitest run --config vitest.config.ts",
|
|
75
|
+
"docs": "node ../../scripts/generate-component-docs.mjs",
|
|
76
|
+
"postinstall": "node scripts/postinstall.js"
|
|
73
77
|
}
|
|
74
78
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
// 자기 자신 패키지 빌드 시에는 실행하지 않음
|
|
5
|
+
if (process.env.npm_package_name === '@lawkit/ui') process.exit(0);
|
|
6
|
+
|
|
7
|
+
// 소비 프로젝트 루트 (npm install 실행 위치)
|
|
8
|
+
const projectRoot = process.cwd();
|
|
9
|
+
|
|
10
|
+
const CLAUDE_MD_REF_LINE = '@node_modules/@lawkit/ui/CLAUDE.md';
|
|
11
|
+
const CURSOR_RULES_DIR = '.cursor/rules';
|
|
12
|
+
const CURSOR_RULES_FILE = '.cursor/rules/lds.mdc';
|
|
13
|
+
const COPILOT_INSTRUCTIONS_FILE = '.github/copilot-instructions.md';
|
|
14
|
+
const COPILOT_MARKER = '<!-- @lawkit/ui -->';
|
|
15
|
+
|
|
16
|
+
const CURSOR_MDC_CONTENT = `---
|
|
17
|
+
description: @lawkit/ui 컴포넌트 사용 규칙
|
|
18
|
+
globs: "**/*.tsx,**/*.ts"
|
|
19
|
+
alwaysApply: false
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
@lawkit/ui 컴포넌트를 사용할 때는 node_modules/@lawkit/ui/CLAUDE.md 파일을 참고하세요.
|
|
23
|
+
컴포넌트별 import 방법과 템플릿 코드가 정리되어 있습니다.
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const COPILOT_APPEND_CONTENT = `
|
|
27
|
+
<!-- @lawkit/ui -->
|
|
28
|
+
## @lawkit/ui 컴포넌트
|
|
29
|
+
컴포넌트 사용법은\`node_modules/@lawkit/ui/CLAUDE.md\` 를 참고하세요.
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
let anyActionTaken = false;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// 1. CLAUDE.md 처리 — 파일 끝에 추가 (기존 구조 보존)
|
|
36
|
+
const claudeMdPath = join(projectRoot, 'CLAUDE.md');
|
|
37
|
+
if (existsSync(claudeMdPath)) {
|
|
38
|
+
const content = readFileSync(claudeMdPath, 'utf-8');
|
|
39
|
+
if (!content.includes(CLAUDE_MD_REF_LINE)) {
|
|
40
|
+
const updated = content.trimEnd() + '\n' + CLAUDE_MD_REF_LINE + '\n';
|
|
41
|
+
writeFileSync(claudeMdPath, updated, 'utf-8');
|
|
42
|
+
console.log('[@lawkit/ui] CLAUDE.md 참조 추가됨');
|
|
43
|
+
} else {
|
|
44
|
+
console.log('[@lawkit/ui] CLAUDE.md 참조 이미 존재 — skip');
|
|
45
|
+
}
|
|
46
|
+
anyActionTaken = true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 2. .cursor/rules/ 디렉토리 처리
|
|
50
|
+
const cursorRulesDirPath = join(projectRoot, CURSOR_RULES_DIR);
|
|
51
|
+
if (existsSync(cursorRulesDirPath)) {
|
|
52
|
+
const ldsMdcPath = join(projectRoot, CURSOR_RULES_FILE);
|
|
53
|
+
if (!existsSync(ldsMdcPath)) {
|
|
54
|
+
writeFileSync(ldsMdcPath, CURSOR_MDC_CONTENT, 'utf-8');
|
|
55
|
+
console.log('[@lawkit/ui] .cursor/rules/lds.mdc 생성됨');
|
|
56
|
+
} else {
|
|
57
|
+
console.log('[@lawkit/ui] .cursor/rules/lds.mdc 이미 존재 — skip');
|
|
58
|
+
}
|
|
59
|
+
anyActionTaken = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 3. .github/copilot-instructions.md 처리
|
|
63
|
+
const copilotInstructionsPath = join(projectRoot, COPILOT_INSTRUCTIONS_FILE);
|
|
64
|
+
if (existsSync(copilotInstructionsPath)) {
|
|
65
|
+
const content = readFileSync(copilotInstructionsPath, 'utf-8');
|
|
66
|
+
if (!content.includes(COPILOT_MARKER)) {
|
|
67
|
+
const updated = content.trimEnd() + COPILOT_APPEND_CONTENT;
|
|
68
|
+
writeFileSync(copilotInstructionsPath, updated, 'utf-8');
|
|
69
|
+
console.log('[@lawkit/ui] copilot-instructions.md 참조 추가됨');
|
|
70
|
+
} else {
|
|
71
|
+
console.log('[@lawkit/ui] copilot-instructions.md 참조 이미 존재 — skip');
|
|
72
|
+
}
|
|
73
|
+
anyActionTaken = true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!anyActionTaken) {
|
|
77
|
+
console.log('[@lawkit/ui] AI 설정 파일을 찾지 못했습니다. 필요 시 수동으로 추가하세요.');
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
// postinstall 실패가 npm install 전체를 막지 않도록 경고만 출력
|
|
81
|
+
console.warn('[@lawkit/ui] AI 설정 파일 업데이트 중 오류 발생 (무시됨):', err.message);
|
|
82
|
+
}
|