@plus-experience/design-system-carbon 0.2.2
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/DESIGN_SYSTEM.md +62 -0
- package/VIBECODING.md +70 -0
- package/app/carbon.scss +2 -0
- package/app/globals.css +152 -0
- package/bin/setup.js +271 -0
- package/dist/chunk-OELI3IRW.mjs +30 -0
- package/dist/chunk-PWVNM57C.mjs +8 -0
- package/dist/components/ui/accordion.d.mts +13 -0
- package/dist/components/ui/accordion.mjs +27 -0
- package/dist/components/ui/alert.d.mts +17 -0
- package/dist/components/ui/alert.mjs +37 -0
- package/dist/components/ui/badge.d.mts +8 -0
- package/dist/components/ui/badge.mjs +17 -0
- package/dist/components/ui/breadcrumb.d.mts +13 -0
- package/dist/components/ui/breadcrumb.mjs +27 -0
- package/dist/components/ui/button.d.mts +9 -0
- package/dist/components/ui/button.mjs +21 -0
- package/dist/components/ui/card.d.mts +16 -0
- package/dist/components/ui/card.mjs +42 -0
- package/dist/components/ui/checkbox.d.mts +9 -0
- package/dist/components/ui/checkbox.mjs +19 -0
- package/dist/components/ui/code-snippet.d.mts +9 -0
- package/dist/components/ui/code-snippet.mjs +17 -0
- package/dist/components/ui/date-picker.d.mts +13 -0
- package/dist/components/ui/date-picker.mjs +29 -0
- package/dist/components/ui/dialog.d.mts +22 -0
- package/dist/components/ui/dialog.mjs +48 -0
- package/dist/components/ui/dropdown-menu.d.mts +13 -0
- package/dist/components/ui/dropdown-menu.mjs +27 -0
- package/dist/components/ui/grid.d.mts +1 -0
- package/dist/components/ui/grid.mjs +2 -0
- package/dist/components/ui/input.d.mts +9 -0
- package/dist/components/ui/input.mjs +19 -0
- package/dist/components/ui/label.d.mts +5 -0
- package/dist/components/ui/label.mjs +23 -0
- package/dist/components/ui/loading.d.mts +13 -0
- package/dist/components/ui/loading.mjs +27 -0
- package/dist/components/ui/number-input.d.mts +9 -0
- package/dist/components/ui/number-input.mjs +19 -0
- package/dist/components/ui/pagination.d.mts +22 -0
- package/dist/components/ui/pagination.mjs +27 -0
- package/dist/components/ui/popover.d.mts +13 -0
- package/dist/components/ui/popover.mjs +27 -0
- package/dist/components/ui/progress.d.mts +10 -0
- package/dist/components/ui/progress.mjs +18 -0
- package/dist/components/ui/radio-group.d.mts +13 -0
- package/dist/components/ui/radio-group.mjs +29 -0
- package/dist/components/ui/search.d.mts +9 -0
- package/dist/components/ui/search.mjs +19 -0
- package/dist/components/ui/select.d.mts +13 -0
- package/dist/components/ui/select.mjs +22 -0
- package/dist/components/ui/separator.d.mts +7 -0
- package/dist/components/ui/separator.mjs +28 -0
- package/dist/components/ui/skeleton.d.mts +14 -0
- package/dist/components/ui/skeleton.mjs +28 -0
- package/dist/components/ui/slider.d.mts +9 -0
- package/dist/components/ui/slider.mjs +19 -0
- package/dist/components/ui/structured-list.d.mts +1 -0
- package/dist/components/ui/structured-list.mjs +2 -0
- package/dist/components/ui/switch.d.mts +9 -0
- package/dist/components/ui/switch.mjs +19 -0
- package/dist/components/ui/table.d.mts +1 -0
- package/dist/components/ui/table.mjs +2 -0
- package/dist/components/ui/tabs.d.mts +1 -0
- package/dist/components/ui/tabs.mjs +2 -0
- package/dist/components/ui/textarea.d.mts +9 -0
- package/dist/components/ui/textarea.mjs +19 -0
- package/dist/components/ui/tooltip.d.mts +14 -0
- package/dist/components/ui/tooltip.mjs +28 -0
- package/dist/design-system-carbon.css +2 -0
- package/dist/hooks/use-mobile.d.mts +3 -0
- package/dist/hooks/use-mobile.mjs +19 -0
- package/dist/lib/utils.d.mts +5 -0
- package/dist/lib/utils.mjs +2 -0
- package/package.json +67 -0
package/DESIGN_SYSTEM.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# CLAUDE.md — design-system v2 (Carbon) 프로젝트 룰
|
|
2
|
+
|
|
3
|
+
## 디자인 시스템
|
|
4
|
+
|
|
5
|
+
이 프로젝트는 **design-system v2** (IBM Carbon Design System v11 기반)를 사용합니다.
|
|
6
|
+
|
|
7
|
+
### 필수 규칙
|
|
8
|
+
|
|
9
|
+
1. **네이티브 HTML 금지**: `<button>`, `<input>`, `<select>`, `<textarea>` 대신 항상 `@/components/ui/` 컴포넌트 사용
|
|
10
|
+
2. **하드코딩 색상 금지**: `bg-[#161616]` 대신 `bg-background`, `bg-card`, `text-interactive` 등 CSS 변수 토큰 사용
|
|
11
|
+
3. **접근성 유지**: Carbon 컴포넌트의 aria 속성, labelText 등을 유지
|
|
12
|
+
4. **Carbon 스페이싱**: 8px, 16px, 24px, 32px, 48px 단위 사용
|
|
13
|
+
|
|
14
|
+
### 컴포넌트 Import
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// Button
|
|
18
|
+
import { Button } from "@/components/ui/button"
|
|
19
|
+
|
|
20
|
+
// Input
|
|
21
|
+
import { Input } from "@/components/ui/input"
|
|
22
|
+
|
|
23
|
+
// Card
|
|
24
|
+
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
|
25
|
+
|
|
26
|
+
// Badge (Carbon Tag 래퍼)
|
|
27
|
+
import { Badge } from "@/components/ui/badge"
|
|
28
|
+
|
|
29
|
+
// Dialog (Carbon ComposedModal 래퍼)
|
|
30
|
+
import { Dialog, DialogHeader, DialogContent, DialogFooter } from "@/components/ui/dialog"
|
|
31
|
+
|
|
32
|
+
// Table
|
|
33
|
+
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "@/components/ui/table"
|
|
34
|
+
|
|
35
|
+
// Accordion
|
|
36
|
+
import { Accordion, AccordionItem } from "@/components/ui/accordion"
|
|
37
|
+
|
|
38
|
+
// Tabs
|
|
39
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent, TabsContentGroup } from "@/components/ui/tabs"
|
|
40
|
+
|
|
41
|
+
// Alert (Carbon InlineNotification 래퍼)
|
|
42
|
+
import { Alert } from "@/components/ui/alert"
|
|
43
|
+
|
|
44
|
+
// Loading
|
|
45
|
+
import { Loading, Spinner } from "@/components/ui/loading"
|
|
46
|
+
|
|
47
|
+
// Icons (Carbon Icons)
|
|
48
|
+
import { Add, Edit, TrashCan, Search, ChevronRight } from "@carbon/icons-react"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 테마
|
|
52
|
+
|
|
53
|
+
- Dark 모드 기본 (`className="dark"` on html)
|
|
54
|
+
- Primary: #0f62fe (Blue 60)
|
|
55
|
+
- Interactive: #78a9ff (dark) / #0f62fe (light)
|
|
56
|
+
- Font: IBM Plex Sans
|
|
57
|
+
|
|
58
|
+
### 스타일
|
|
59
|
+
|
|
60
|
+
- Carbon SCSS: `app/carbon.scss` (자동 import)
|
|
61
|
+
- Tailwind 유틸리티: 레이아웃에 사용 (flex, grid, gap, padding 등)
|
|
62
|
+
- CSS 변수 토큰: `globals.css`에 정의
|
package/VIBECODING.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# design-system v2 — Carbon Design System 바이브코딩 룰
|
|
2
|
+
|
|
3
|
+
## 핵심 원칙
|
|
4
|
+
|
|
5
|
+
1. **네이티브 HTML 금지** — `<button>`, `<input>`, `<select>` 대신 항상 `components/ui/` 래퍼를 사용하세요.
|
|
6
|
+
2. **Carbon 토큰 사용** — 하드코딩 색상 금지. `bg-primary`, `text-interactive`, `bg-card` 같은 CSS 변수 사용.
|
|
7
|
+
3. **접근성 우선** — Carbon 컴포넌트는 WCAG 2.1 AA 기준을 충족합니다. aria 속성을 유지하세요.
|
|
8
|
+
4. **2px Grid 시스템** — Carbon의 2px 기반 스페이싱: 8px, 16px, 24px, 32px, 48px 단위 사용.
|
|
9
|
+
|
|
10
|
+
## 디자인 시스템
|
|
11
|
+
|
|
12
|
+
- **Base**: IBM Carbon Design System v11
|
|
13
|
+
- **Components**: `@carbon/react` + 커스텀 래퍼
|
|
14
|
+
- **Icons**: `@carbon/icons-react`
|
|
15
|
+
- **Font**: IBM Plex Sans (Sans), IBM Plex Mono (Mono)
|
|
16
|
+
- **Theme**: g90 (dark default) / White (light)
|
|
17
|
+
- **Primary Color**: Blue 60 (#0f62fe)
|
|
18
|
+
- **Interactive**: Blue 60 (light) / Blue 40 (#78a9ff, dark)
|
|
19
|
+
- **Destructive**: Red 60 (#da1e28, light) / Red 50 (#fa4d56, dark)
|
|
20
|
+
- **Border Radius**: 0px (Carbon 기본 — 직각 모서리)
|
|
21
|
+
|
|
22
|
+
## 컬러 토큰 매핑
|
|
23
|
+
|
|
24
|
+
| Tailwind 토큰 | Light | Dark | 용도 |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| `bg-background` | #ffffff | #161616 | 페이지 배경 |
|
|
27
|
+
| `text-foreground` | #161616 | #f4f4f4 | 기본 텍스트 |
|
|
28
|
+
| `bg-primary` | #0f62fe | #0f62fe | 주요 액션 |
|
|
29
|
+
| `text-interactive` | #0f62fe | #78a9ff | 인터랙티브 요소 |
|
|
30
|
+
| `bg-card` | #f4f4f4 | #262626 | 카드/타일 배경 |
|
|
31
|
+
| `bg-muted` | #f4f4f4 | #262626 | 뮤트 배경 |
|
|
32
|
+
| `text-muted-foreground` | #6f6f6f | #a8a8a8 | 보조 텍스트 |
|
|
33
|
+
| `bg-destructive` | #da1e28 | #fa4d56 | 위험/삭제 |
|
|
34
|
+
| `border-border` | #e0e0e0 | #393939 | 테두리 |
|
|
35
|
+
|
|
36
|
+
## Carbon → design-system 컴포넌트 매핑
|
|
37
|
+
|
|
38
|
+
| design-system v2 | Carbon 원본 | 비고 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `Button` | `Button` | variant → kind 매핑 |
|
|
41
|
+
| `Input` | `TextInput` | label, helperText 지원 |
|
|
42
|
+
| `Textarea` | `TextArea` | |
|
|
43
|
+
| `Card` | `Tile / ClickableTile` | onClick 시 ClickableTile |
|
|
44
|
+
| `Checkbox` | `Checkbox` | |
|
|
45
|
+
| `RadioGroup` | `RadioButtonGroup` | |
|
|
46
|
+
| `Select` | `Select / SelectItem` | |
|
|
47
|
+
| `Switch` | `Toggle` | |
|
|
48
|
+
| `Badge` | `Tag` | variant → type 매핑 |
|
|
49
|
+
| `Dialog` | `ComposedModal` | |
|
|
50
|
+
| `Tabs` | `Tabs / TabList / Tab` | |
|
|
51
|
+
| `Alert` | `InlineNotification` | |
|
|
52
|
+
| `Tooltip` | `Tooltip / Toggletip` | |
|
|
53
|
+
| `Breadcrumb` | `Breadcrumb` | |
|
|
54
|
+
| `Pagination` | `Pagination` | |
|
|
55
|
+
| `Progress` | `ProgressBar` | |
|
|
56
|
+
| `Skeleton` | `SkeletonText` | |
|
|
57
|
+
| `Loading` | `Loading / InlineLoading` | |
|
|
58
|
+
| `DropdownMenu` | `OverflowMenu` | |
|
|
59
|
+
| `CodeSnippet` | `CodeSnippet` | |
|
|
60
|
+
| `Search` | `Search` | |
|
|
61
|
+
| `Grid` | `Grid / Column` | |
|
|
62
|
+
|
|
63
|
+
## 룰
|
|
64
|
+
|
|
65
|
+
- Dark 모드가 기본 (`className="dark"`)
|
|
66
|
+
- 버튼 variant: default(primary), outline(tertiary), secondary, ghost, destructive(danger)
|
|
67
|
+
- 인라인 스타일 금지
|
|
68
|
+
- 넉넉한 패딩: 카드 내부 p-6 이상, 섹션 gap-6 이상
|
|
69
|
+
- footer에 `border-t bg-muted` 패턴 금지
|
|
70
|
+
- Carbon의 모션 가이드라인 준수 (productive motion)
|
package/app/carbon.scss
ADDED
package/app/globals.css
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
@import "tailwindcss/theme" theme(inline);
|
|
2
|
+
@import "tailwindcss/utilities";
|
|
3
|
+
|
|
4
|
+
@custom-variant dark (&:is(.dark *));
|
|
5
|
+
|
|
6
|
+
@theme inline {
|
|
7
|
+
--color-background: var(--background);
|
|
8
|
+
--color-foreground: var(--foreground);
|
|
9
|
+
--font-sans: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
10
|
+
--font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, monospace;
|
|
11
|
+
--font-heading: var(--font-sans);
|
|
12
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
13
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
14
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
15
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
16
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
17
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
18
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
19
|
+
--color-sidebar: var(--sidebar);
|
|
20
|
+
--color-chart-5: var(--chart-5);
|
|
21
|
+
--color-chart-4: var(--chart-4);
|
|
22
|
+
--color-chart-3: var(--chart-3);
|
|
23
|
+
--color-chart-2: var(--chart-2);
|
|
24
|
+
--color-chart-1: var(--chart-1);
|
|
25
|
+
--color-ring: var(--ring);
|
|
26
|
+
--color-input: var(--input);
|
|
27
|
+
--color-border: var(--border);
|
|
28
|
+
--color-destructive: var(--destructive);
|
|
29
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
30
|
+
--color-accent: var(--accent);
|
|
31
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
32
|
+
--color-muted: var(--muted);
|
|
33
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
34
|
+
--color-secondary: var(--secondary);
|
|
35
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
36
|
+
--color-primary: var(--primary);
|
|
37
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
38
|
+
--color-popover: var(--popover);
|
|
39
|
+
--color-card-foreground: var(--card-foreground);
|
|
40
|
+
--color-card: var(--card);
|
|
41
|
+
--color-interactive: var(--interactive);
|
|
42
|
+
--color-interactive-hover: var(--interactive-hover);
|
|
43
|
+
--radius-sm: calc(var(--radius) * 0.5);
|
|
44
|
+
--radius-md: calc(var(--radius) * 0.75);
|
|
45
|
+
--radius-lg: var(--radius);
|
|
46
|
+
--radius-xl: calc(var(--radius) * 1.5);
|
|
47
|
+
--radius-2xl: calc(var(--radius) * 2);
|
|
48
|
+
--radius-3xl: calc(var(--radius) * 2.5);
|
|
49
|
+
--radius-4xl: calc(var(--radius) * 3);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* ── Light theme (Carbon White / g10) ── */
|
|
53
|
+
:root {
|
|
54
|
+
--background: #ffffff;
|
|
55
|
+
--foreground: #161616;
|
|
56
|
+
--card: #f4f4f4;
|
|
57
|
+
--card-foreground: #161616;
|
|
58
|
+
--popover: #ffffff;
|
|
59
|
+
--popover-foreground: #161616;
|
|
60
|
+
--primary: #0f62fe;
|
|
61
|
+
--primary-foreground: #ffffff;
|
|
62
|
+
--secondary: #e0e0e0;
|
|
63
|
+
--secondary-foreground: #161616;
|
|
64
|
+
--muted: #f4f4f4;
|
|
65
|
+
--muted-foreground: #6f6f6f;
|
|
66
|
+
--accent: #e0e0e0;
|
|
67
|
+
--accent-foreground: #161616;
|
|
68
|
+
--destructive: #da1e28;
|
|
69
|
+
--border: #e0e0e0;
|
|
70
|
+
--input: #e0e0e0;
|
|
71
|
+
--ring: #0f62fe;
|
|
72
|
+
--interactive: #0f62fe;
|
|
73
|
+
--interactive-hover: #0043ce;
|
|
74
|
+
--chart-1: #0f62fe;
|
|
75
|
+
--chart-2: #6929c4;
|
|
76
|
+
--chart-3: #009d9a;
|
|
77
|
+
--chart-4: #ee538b;
|
|
78
|
+
--chart-5: #fa4d56;
|
|
79
|
+
--radius: 0px;
|
|
80
|
+
--sidebar: #f4f4f4;
|
|
81
|
+
--sidebar-foreground: #161616;
|
|
82
|
+
--sidebar-primary: #0f62fe;
|
|
83
|
+
--sidebar-primary-foreground: #ffffff;
|
|
84
|
+
--sidebar-accent: #e0e0e0;
|
|
85
|
+
--sidebar-accent-foreground: #161616;
|
|
86
|
+
--sidebar-border: #e0e0e0;
|
|
87
|
+
--sidebar-ring: #0f62fe;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* ── Dark theme (Carbon g90 / g100) ── */
|
|
91
|
+
.dark {
|
|
92
|
+
--background: #161616;
|
|
93
|
+
--foreground: #f4f4f4;
|
|
94
|
+
--card: #262626;
|
|
95
|
+
--card-foreground: #f4f4f4;
|
|
96
|
+
--popover: #262626;
|
|
97
|
+
--popover-foreground: #f4f4f4;
|
|
98
|
+
--primary: #0f62fe;
|
|
99
|
+
--primary-foreground: #ffffff;
|
|
100
|
+
--secondary: #393939;
|
|
101
|
+
--secondary-foreground: #f4f4f4;
|
|
102
|
+
--muted: #262626;
|
|
103
|
+
--muted-foreground: #a8a8a8;
|
|
104
|
+
--accent: #393939;
|
|
105
|
+
--accent-foreground: #f4f4f4;
|
|
106
|
+
--destructive: #fa4d56;
|
|
107
|
+
--border: #393939;
|
|
108
|
+
--input: #393939;
|
|
109
|
+
--ring: #0f62fe;
|
|
110
|
+
--interactive: #78a9ff;
|
|
111
|
+
--interactive-hover: #a6c8ff;
|
|
112
|
+
--chart-1: #78a9ff;
|
|
113
|
+
--chart-2: #be95ff;
|
|
114
|
+
--chart-3: #08bdba;
|
|
115
|
+
--chart-4: #ff7eb6;
|
|
116
|
+
--chart-5: #fa4d56;
|
|
117
|
+
--sidebar: #1c1c1c;
|
|
118
|
+
--sidebar-foreground: #f4f4f4;
|
|
119
|
+
--sidebar-primary: #78a9ff;
|
|
120
|
+
--sidebar-primary-foreground: #161616;
|
|
121
|
+
--sidebar-accent: #393939;
|
|
122
|
+
--sidebar-accent-foreground: #f4f4f4;
|
|
123
|
+
--sidebar-border: #393939;
|
|
124
|
+
--sidebar-ring: #78a9ff;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@layer base {
|
|
128
|
+
* {
|
|
129
|
+
border-color: var(--border);
|
|
130
|
+
}
|
|
131
|
+
body {
|
|
132
|
+
background-color: var(--background);
|
|
133
|
+
color: var(--foreground);
|
|
134
|
+
}
|
|
135
|
+
html {
|
|
136
|
+
font-family: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Carbon UI Shell adjustments for docs */
|
|
141
|
+
.docs-sidenav.cds--side-nav {
|
|
142
|
+
position: fixed;
|
|
143
|
+
top: 3rem;
|
|
144
|
+
bottom: 0;
|
|
145
|
+
width: 16rem;
|
|
146
|
+
z-index: 40;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.cds--content {
|
|
150
|
+
margin-left: 0;
|
|
151
|
+
}
|
|
152
|
+
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const cwd = process.cwd();
|
|
8
|
+
|
|
9
|
+
function run(cmd) {
|
|
10
|
+
console.log(` $ ${cmd}`);
|
|
11
|
+
execSync(cmd, { cwd, stdio: "inherit" });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function log(msg) {
|
|
15
|
+
console.log(`✓ ${msg}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log("\ndesign-system v2 (Carbon) 세팅 시작...\n");
|
|
19
|
+
|
|
20
|
+
// 1. design-system-carbon 설치
|
|
21
|
+
run("npm install @plus-experience/design-system-carbon");
|
|
22
|
+
log("@plus-experience/design-system-carbon 설치");
|
|
23
|
+
|
|
24
|
+
// 3. 핵심 의존성
|
|
25
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
26
|
+
if (!pkg.dependencies?.next) {
|
|
27
|
+
run("npm install next react react-dom");
|
|
28
|
+
log("next, react, react-dom 설치");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 4. UI 의존성 (Carbon + utilities)
|
|
32
|
+
run("npm install @carbon/react @carbon/icons-react clsx tailwind-merge next-themes date-fns recharts");
|
|
33
|
+
log("Carbon + UI 의존성 설치");
|
|
34
|
+
|
|
35
|
+
// 5. dev 의존성 (sass)
|
|
36
|
+
run("npm install -D sass");
|
|
37
|
+
log("sass 설치");
|
|
38
|
+
|
|
39
|
+
// 6. globals.css 복사
|
|
40
|
+
const appDir = path.join(cwd, "app");
|
|
41
|
+
if (!fs.existsSync(appDir)) fs.mkdirSync(appDir, { recursive: true });
|
|
42
|
+
|
|
43
|
+
const srcCss = path.join(cwd, "node_modules/@plus-experience/design-system-carbon/app/globals.css");
|
|
44
|
+
if (fs.existsSync(srcCss)) {
|
|
45
|
+
let cssContent = fs.readFileSync(srcCss, "utf8");
|
|
46
|
+
const styleImport = '@import "@plus-experience/design-system-carbon/styles.css";';
|
|
47
|
+
if (!cssContent.includes(styleImport)) {
|
|
48
|
+
cssContent = cssContent.replace(
|
|
49
|
+
'@import "tailwindcss/theme"',
|
|
50
|
+
'@import "tailwindcss/theme"' + '\n' + styleImport
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
fs.writeFileSync(path.join(appDir, "globals.css"), cssContent);
|
|
54
|
+
log("globals.css 복사");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 7. carbon.scss 복사
|
|
58
|
+
const srcScss = path.join(cwd, "node_modules/@plus-experience/design-system-carbon/app/carbon.scss");
|
|
59
|
+
if (fs.existsSync(srcScss)) {
|
|
60
|
+
fs.copyFileSync(srcScss, path.join(appDir, "carbon.scss"));
|
|
61
|
+
log("carbon.scss 복사");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 8. DESIGN_SYSTEM.md + CLAUDE.md + AGENTS.md 설정
|
|
65
|
+
const dsSourcePath = path.join(cwd, "node_modules/@plus-experience/design-system-carbon/DESIGN_SYSTEM.md");
|
|
66
|
+
if (fs.existsSync(dsSourcePath)) {
|
|
67
|
+
const dsContent = fs.readFileSync(dsSourcePath, "utf8");
|
|
68
|
+
|
|
69
|
+
// DESIGN_SYSTEM.md — 항상 덮어쓰기 (우리 파일)
|
|
70
|
+
fs.writeFileSync(path.join(cwd, "DESIGN_SYSTEM.md"), dsContent);
|
|
71
|
+
log("DESIGN_SYSTEM.md 생성");
|
|
72
|
+
|
|
73
|
+
// CLAUDE.md — @DESIGN_SYSTEM.md 참조 추가 (기존 내용 유지)
|
|
74
|
+
const claudePath = path.join(cwd, "CLAUDE.md");
|
|
75
|
+
const claudeRef = "@DESIGN_SYSTEM.md";
|
|
76
|
+
if (fs.existsSync(claudePath)) {
|
|
77
|
+
const claudeContent = fs.readFileSync(claudePath, "utf8");
|
|
78
|
+
if (!claudeContent.includes(claudeRef)) {
|
|
79
|
+
fs.appendFileSync(claudePath, "\n" + claudeRef + "\n");
|
|
80
|
+
log("CLAUDE.md — @DESIGN_SYSTEM.md 참조 추가");
|
|
81
|
+
} else {
|
|
82
|
+
log("CLAUDE.md — 이미 참조됨 (스킵)");
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
fs.writeFileSync(claudePath, claudeRef + "\n");
|
|
86
|
+
log("CLAUDE.md 생성 (@DESIGN_SYSTEM.md 참조)");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// AGENTS.md — BEGIN/END 블록으로 내용 직접 삽입 (Codex 등 대응)
|
|
90
|
+
const agentsPath = path.join(cwd, "AGENTS.md");
|
|
91
|
+
const beginTag = "<!-- BEGIN:design-system-rules -->";
|
|
92
|
+
const endTag = "<!-- END:design-system-rules -->";
|
|
93
|
+
const dsBlock = beginTag + "\n" + dsContent + "\n" + endTag;
|
|
94
|
+
|
|
95
|
+
if (fs.existsSync(agentsPath)) {
|
|
96
|
+
let agentsContent = fs.readFileSync(agentsPath, "utf8");
|
|
97
|
+
const blockRegex = /<!-- BEGIN:design-system-rules -->[\s\S]*?<!-- END:design-system-rules -->/;
|
|
98
|
+
if (blockRegex.test(agentsContent)) {
|
|
99
|
+
agentsContent = agentsContent.replace(blockRegex, dsBlock);
|
|
100
|
+
fs.writeFileSync(agentsPath, agentsContent);
|
|
101
|
+
log("AGENTS.md — design-system 룰 업데이트");
|
|
102
|
+
} else {
|
|
103
|
+
fs.appendFileSync(agentsPath, "\n" + dsBlock + "\n");
|
|
104
|
+
log("AGENTS.md — design-system 룰 추가");
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
fs.writeFileSync(agentsPath, dsBlock + "\n");
|
|
108
|
+
log("AGENTS.md 생성 (design-system 룰 포함)");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 9. next.config 수정
|
|
113
|
+
patchNextConfig();
|
|
114
|
+
|
|
115
|
+
// 10. layout.tsx 수정
|
|
116
|
+
patchLayout();
|
|
117
|
+
|
|
118
|
+
// 11. IBM Plex 폰트 추가
|
|
119
|
+
patchLayoutFont();
|
|
120
|
+
|
|
121
|
+
console.log("\n✅ design-system v2 (Carbon) 세팅 완료!");
|
|
122
|
+
console.log(' import { Button } from "@plus-experience/design-system-carbon/ui/button" 로 사용하세요.\n');
|
|
123
|
+
|
|
124
|
+
// --- helpers ---
|
|
125
|
+
|
|
126
|
+
function patchNextConfig() {
|
|
127
|
+
const candidates = ["next.config.ts", "next.config.mjs", "next.config.js"];
|
|
128
|
+
let configPath = null;
|
|
129
|
+
for (const name of candidates) {
|
|
130
|
+
const p = path.join(cwd, name);
|
|
131
|
+
if (fs.existsSync(p)) { configPath = p; break; }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!configPath) {
|
|
135
|
+
configPath = path.join(cwd, "next.config.ts");
|
|
136
|
+
fs.writeFileSync(configPath, [
|
|
137
|
+
'import type { NextConfig } from "next";',
|
|
138
|
+
'',
|
|
139
|
+
'const nextConfig: NextConfig = {',
|
|
140
|
+
' transpilePackages: ["@plus-experience/design-system-carbon"],',
|
|
141
|
+
' sassOptions: {',
|
|
142
|
+
' silenceDeprecations: ["mixed-decls", "color-functions", "global-builtin", "import"],',
|
|
143
|
+
' },',
|
|
144
|
+
'};',
|
|
145
|
+
'',
|
|
146
|
+
'export default nextConfig;',
|
|
147
|
+
'',
|
|
148
|
+
].join("\n"));
|
|
149
|
+
log("next.config.ts 생성");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let content = fs.readFileSync(configPath, "utf8");
|
|
154
|
+
|
|
155
|
+
if (content.includes("@plus-experience/design-system-carbon")) {
|
|
156
|
+
log("next.config — 이미 설정됨 (스킵)");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (content.includes("transpilePackages")) {
|
|
161
|
+
content = content.replace(
|
|
162
|
+
/transpilePackages\s*:\s*\[/,
|
|
163
|
+
'transpilePackages: ["@plus-experience/design-system-carbon", '
|
|
164
|
+
);
|
|
165
|
+
fs.writeFileSync(configPath, content);
|
|
166
|
+
log(`${path.basename(configPath)} — transpilePackages에 추가`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const patterns = [
|
|
171
|
+
/(:.*NextConfig\s*=\s*\{)/,
|
|
172
|
+
/(const\s+\w+\s*=\s*\{)/,
|
|
173
|
+
/(export\s+default\s*\{)/,
|
|
174
|
+
/(module\.exports\s*=\s*\{)/,
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
for (const pattern of patterns) {
|
|
178
|
+
if (pattern.test(content)) {
|
|
179
|
+
content = content.replace(pattern, '$1\n transpilePackages: ["@plus-experience/design-system-carbon"],\n sassOptions: { silenceDeprecations: ["mixed-decls", "color-functions", "global-builtin", "import"] },');
|
|
180
|
+
fs.writeFileSync(configPath, content);
|
|
181
|
+
log(`${path.basename(configPath)} — transpilePackages + sassOptions 추가`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log(`⚠️ ${path.basename(configPath)} 자동 수정 실패 — 수동으로 설정해주세요.`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function patchLayoutFont() {
|
|
190
|
+
const layoutCandidates = ["app/layout.tsx", "app/layout.jsx", "app/layout.js"];
|
|
191
|
+
let layoutPath = null;
|
|
192
|
+
for (const name of layoutCandidates) {
|
|
193
|
+
const p = path.join(cwd, name);
|
|
194
|
+
if (fs.existsSync(p)) { layoutPath = p; break; }
|
|
195
|
+
}
|
|
196
|
+
if (!layoutPath) return;
|
|
197
|
+
|
|
198
|
+
let content = fs.readFileSync(layoutPath, "utf8");
|
|
199
|
+
const fontUrl = "https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap";
|
|
200
|
+
|
|
201
|
+
if (content.includes("IBM+Plex")) {
|
|
202
|
+
log("IBM Plex 폰트 — 이미 설정됨 (스킵)");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (content.includes("<head>")) {
|
|
207
|
+
content = content.replace(
|
|
208
|
+
"<head>",
|
|
209
|
+
`<head>\n <link rel="stylesheet" href="${fontUrl}" />`
|
|
210
|
+
);
|
|
211
|
+
} else if (/<html\b[^>]*>/.test(content)) {
|
|
212
|
+
content = content.replace(
|
|
213
|
+
/(<html\b[^>]*>)/,
|
|
214
|
+
`$1\n <head>\n <link rel="stylesheet" href="${fontUrl}" />\n </head>`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fs.writeFileSync(layoutPath, content);
|
|
219
|
+
log("IBM Plex 폰트 CDN 추가");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function patchLayout() {
|
|
223
|
+
const layoutCandidates = ["app/layout.tsx", "app/layout.jsx", "app/layout.js"];
|
|
224
|
+
let layoutPath = null;
|
|
225
|
+
for (const name of layoutCandidates) {
|
|
226
|
+
const p = path.join(cwd, name);
|
|
227
|
+
if (fs.existsSync(p)) { layoutPath = p; break; }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!layoutPath) return;
|
|
231
|
+
|
|
232
|
+
let content = fs.readFileSync(layoutPath, "utf8");
|
|
233
|
+
let modified = false;
|
|
234
|
+
|
|
235
|
+
if (!content.includes('"dark"') && !content.includes("'dark'")) {
|
|
236
|
+
if (/<html[^>]*className\s*=\s*"([^"]*)"/.test(content)) {
|
|
237
|
+
content = content.replace(
|
|
238
|
+
/(<html[^>]*className\s*=\s*")([^"]*")/,
|
|
239
|
+
'$1dark $2'
|
|
240
|
+
);
|
|
241
|
+
modified = true;
|
|
242
|
+
} else if (/<html\b/.test(content) && !/<html[^>]*className/.test(content)) {
|
|
243
|
+
content = content.replace(/(<html\b)/, '$1 className="dark"');
|
|
244
|
+
modified = true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!content.includes("suppressHydrationWarning")) {
|
|
249
|
+
content = content.replace(/(<html\b[^>]*)(>)/, '$1 suppressHydrationWarning$2');
|
|
250
|
+
modified = true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Add carbon.scss import if not present
|
|
254
|
+
if (!content.includes("carbon.scss")) {
|
|
255
|
+
const importLine = 'import "./carbon.scss";';
|
|
256
|
+
if (content.includes("import")) {
|
|
257
|
+
// Add after last import
|
|
258
|
+
const lastImportIdx = content.lastIndexOf("import ");
|
|
259
|
+
const endOfLine = content.indexOf("\n", lastImportIdx);
|
|
260
|
+
content = content.slice(0, endOfLine + 1) + importLine + "\n" + content.slice(endOfLine + 1);
|
|
261
|
+
modified = true;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (modified) {
|
|
266
|
+
fs.writeFileSync(layoutPath, content);
|
|
267
|
+
log("app/layout — dark 클래스 + carbon.scss import 추가");
|
|
268
|
+
} else {
|
|
269
|
+
log("app/layout — 이미 설정됨 (스킵)");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
var __objRest = (source, exclude) => {
|
|
18
|
+
var target = {};
|
|
19
|
+
for (var prop in source)
|
|
20
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
21
|
+
target[prop] = source[prop];
|
|
22
|
+
if (source != null && __getOwnPropSymbols)
|
|
23
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
24
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
25
|
+
target[prop] = source[prop];
|
|
26
|
+
}
|
|
27
|
+
return target;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { __objRest, __spreadValues };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Accordion as Accordion$1, AccordionItem as AccordionItem$1 } from '@carbon/react';
|
|
3
|
+
|
|
4
|
+
type CarbonAccordionProps = React.ComponentProps<typeof Accordion$1>;
|
|
5
|
+
type CarbonAccordionItemProps = React.ComponentProps<typeof AccordionItem$1>;
|
|
6
|
+
interface AccordionProps extends CarbonAccordionProps {
|
|
7
|
+
}
|
|
8
|
+
interface AccordionItemProps extends CarbonAccordionItemProps {
|
|
9
|
+
}
|
|
10
|
+
declare function Accordion({ className, ...props }: AccordionProps): React.JSX.Element;
|
|
11
|
+
declare function AccordionItem({ className, ...props }: AccordionItemProps): React.JSX.Element;
|
|
12
|
+
|
|
13
|
+
export { Accordion, AccordionItem, type AccordionItemProps, type AccordionProps };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cn } from '../../chunk-PWVNM57C.mjs';
|
|
2
|
+
import { __objRest, __spreadValues } from '../../chunk-OELI3IRW.mjs';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Accordion as Accordion$1, AccordionItem as AccordionItem$1 } from '@carbon/react';
|
|
5
|
+
|
|
6
|
+
function Accordion(_a) {
|
|
7
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
8
|
+
return /* @__PURE__ */ React.createElement(
|
|
9
|
+
Accordion$1,
|
|
10
|
+
__spreadValues({
|
|
11
|
+
"data-slot": "accordion",
|
|
12
|
+
className: cn(className)
|
|
13
|
+
}, props)
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
function AccordionItem(_a) {
|
|
17
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
18
|
+
return /* @__PURE__ */ React.createElement(
|
|
19
|
+
AccordionItem$1,
|
|
20
|
+
__spreadValues({
|
|
21
|
+
"data-slot": "accordion-item",
|
|
22
|
+
className: cn(className)
|
|
23
|
+
}, props)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { Accordion, AccordionItem };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ActionableNotification, InlineNotification, ToastNotification } from '@carbon/react';
|
|
3
|
+
|
|
4
|
+
type CarbonInlineNotificationProps = React.ComponentProps<typeof InlineNotification>;
|
|
5
|
+
type CarbonToastNotificationProps = React.ComponentProps<typeof ToastNotification>;
|
|
6
|
+
type CarbonActionableNotificationProps = React.ComponentProps<typeof ActionableNotification>;
|
|
7
|
+
interface AlertProps extends CarbonInlineNotificationProps {
|
|
8
|
+
}
|
|
9
|
+
interface ToastProps extends CarbonToastNotificationProps {
|
|
10
|
+
}
|
|
11
|
+
interface ActionableAlertProps extends CarbonActionableNotificationProps {
|
|
12
|
+
}
|
|
13
|
+
declare function Alert({ className, ...props }: AlertProps): React.JSX.Element;
|
|
14
|
+
declare function Toast({ className, ...props }: ToastProps): React.JSX.Element;
|
|
15
|
+
declare function ActionableAlert({ className, ...props }: ActionableAlertProps): React.JSX.Element;
|
|
16
|
+
|
|
17
|
+
export { ActionableAlert, type ActionableAlertProps, Alert, type AlertProps, Toast, type ToastProps };
|