@lawkit/ui 0.1.39 → 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 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.39",
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
+ }