@peppone.choi/ui-kit 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +230 -84
- package/README.md +233 -86
- package/package.json +27 -17
package/README.ko.md
CHANGED
|
@@ -1,99 +1,148 @@
|
|
|
1
1
|
# Peppone UI Kit
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@peppone.choi/ui-kit)
|
|
3
4
|
[](#라이선스)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
버튼·입력·다이얼로그·달력·내비게이션 등 **바로 쓰는 컴포넌트 70개 이상**을 담은
|
|
7
|
+
친절한 React 컴포넌트 라이브러리입니다.
|
|
8
|
+
[Wanted Design System (Montage)](https://github.com/wanteddev/montage-web) 기반이고,
|
|
9
|
+
**어떤 React 앱에서도** 동작하며, **TypeScript 타입 완비**, **다크 모드 기본 제공**.
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
처음이세요? 아래 **[60초 설정](#60초-설정)**부터 보세요 — 설치하고, CSS 한 줄
|
|
12
|
+
import하고, 컴포넌트 하나 넣으면 끝입니다.
|
|
13
|
+
|
|
14
|
+
> 🇺🇸 English docs: **[README.md](https://github.com/peppone-choi/Peppone-UI-KIT/blob/main/README.md)**
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 목차
|
|
19
|
+
|
|
20
|
+
- [왜 Peppone UI Kit인가](#왜-peppone-ui-kit인가)
|
|
21
|
+
- [60초 설정](#60초-설정)
|
|
22
|
+
- [예제](#예제)
|
|
23
|
+
- [다크 모드](#다크-모드)
|
|
24
|
+
- [Next.js와 함께 쓰기](#nextjs와-함께-쓰기)
|
|
25
|
+
- [모든 컴포넌트 둘러보기](#모든-컴포넌트-둘러보기)
|
|
26
|
+
- [문제 해결](#문제-해결)
|
|
27
|
+
- [전체 컴포넌트](#전체-컴포넌트)
|
|
28
|
+
- [기여 / 로컬 개발](#기여--로컬-개발)
|
|
29
|
+
- [라이선스](#라이선스)
|
|
10
30
|
|
|
11
31
|
---
|
|
12
32
|
|
|
13
|
-
##
|
|
33
|
+
## 왜 Peppone UI Kit인가
|
|
14
34
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
-
|
|
35
|
+
- 🧩 **컴포넌트 70개 이상** — 액션, 콘텐츠, 피드백, 내비게이션, 오버레이, 폼.
|
|
36
|
+
- ♿ **기본으로 접근성** — 다이얼로그·팝오버·메뉴·바텀시트가 [Base UI](https://base-ui.com)
|
|
37
|
+
프리미티브 기반이라 포커스 트랩, 스크롤 락, `Escape`/바깥클릭 닫기, ARIA가 자동.
|
|
38
|
+
- 🎨 **테마 + 다크 모드** — Tailwind CSS v4 토큰을 CSS 변수로. 클래스 하나로 전체
|
|
39
|
+
다크 전환.
|
|
40
|
+
- 🟦 **TypeScript 우선** — 모든 컴포넌트·prop에 타입 제공.
|
|
41
|
+
- ⚛️ **어디서나 동작** — 프레임워크 비종속 코어 + `next/link`를 미리 연결한 선택적
|
|
42
|
+
Next.js 빌드.
|
|
43
|
+
- 📦 **현대적 패키징** — ESM + CJS, 트리셰이킹, RSC용 `"use client"` 보존.
|
|
21
44
|
|
|
22
45
|
---
|
|
23
46
|
|
|
24
|
-
##
|
|
47
|
+
## 60초 설정
|
|
48
|
+
|
|
49
|
+
**1. 설치**
|
|
25
50
|
|
|
26
51
|
```bash
|
|
27
|
-
|
|
28
|
-
# 또는:
|
|
52
|
+
npm install @peppone.choi/ui-kit
|
|
53
|
+
# 또는: pnpm add @peppone.choi/ui-kit
|
|
29
54
|
# 또는: yarn add @peppone.choi/ui-kit
|
|
30
55
|
```
|
|
31
56
|
|
|
32
|
-
|
|
57
|
+
> `react`/`react-dom`(v19+)은 peer 의존성입니다. **npm 7+ 와 pnpm은 자동 설치**하니
|
|
58
|
+
> 따로 안 해도 됩니다. (Yarn Classic만 `yarn add react react-dom` 필요.) 그 외 킷이
|
|
59
|
+
> 쓰는 패키지는 일반 의존성으로 함께 설치됩니다.
|
|
33
60
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
사본이 중복돼 "Invalid hook call" 오류가 날 수 있습니다.
|
|
61
|
+
**2. 스타일시트를 한 번만 import** — 앱 진입점에서. 다들 빠뜨리는 단계니 먼저
|
|
62
|
+
하세요:
|
|
37
63
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
```ts
|
|
65
|
+
// Next.js: app/layout.tsx · Vite: src/main.tsx · CRA: src/index.tsx
|
|
66
|
+
import "@peppone.choi/ui-kit/styles.css";
|
|
67
|
+
```
|
|
41
68
|
|
|
42
|
-
|
|
43
|
-
|
|
69
|
+
> ⚠️ 스타일시트 없으면 컴포넌트가 스타일 없이 나옵니다. 깨져 보이면 이 import부터
|
|
70
|
+
> 확인하세요.
|
|
44
71
|
|
|
45
|
-
|
|
72
|
+
**3. 컴포넌트 사용**
|
|
46
73
|
|
|
47
|
-
|
|
74
|
+
```tsx
|
|
75
|
+
import { Button } from "@peppone.choi/ui-kit";
|
|
48
76
|
|
|
49
|
-
|
|
50
|
-
|
|
77
|
+
export default function App() {
|
|
78
|
+
return <Button onClick={() => alert("동작!")}>눌러보세요</Button>;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
51
81
|
|
|
52
|
-
|
|
82
|
+
이게 설정 전부입니다. 완전한 Next.js 진입점은 이렇게 생겼습니다:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
// app/layout.tsx
|
|
53
86
|
import "@peppone.choi/ui-kit/styles.css";
|
|
54
|
-
```
|
|
55
87
|
|
|
56
|
-
|
|
88
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
89
|
+
return (
|
|
90
|
+
<html lang="ko">
|
|
91
|
+
<body>{children}</body>
|
|
92
|
+
</html>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
57
96
|
|
|
58
97
|
---
|
|
59
98
|
|
|
60
|
-
##
|
|
99
|
+
## 예제
|
|
100
|
+
|
|
101
|
+
### 카드 + 버튼
|
|
61
102
|
|
|
62
103
|
```tsx
|
|
63
|
-
import {
|
|
64
|
-
import "@peppone.choi/ui-kit/styles.css";
|
|
104
|
+
import { Card, CardHeader, CardTitle, CardContent, Button } from "@peppone.choi/ui-kit";
|
|
65
105
|
|
|
66
|
-
export function
|
|
106
|
+
export function WelcomeCard() {
|
|
67
107
|
return (
|
|
68
108
|
<Card>
|
|
69
109
|
<CardHeader>
|
|
70
|
-
<CardTitle
|
|
110
|
+
<CardTitle>환영합니다 👋</CardTitle>
|
|
71
111
|
</CardHeader>
|
|
72
|
-
<CardContent>
|
|
73
|
-
<
|
|
112
|
+
<CardContent className="flex flex-col gap-3">
|
|
113
|
+
<p>몇 초면 시작할 수 있어요.</p>
|
|
114
|
+
<Button>시작하기</Button>
|
|
74
115
|
</CardContent>
|
|
75
116
|
</Card>
|
|
76
117
|
);
|
|
77
118
|
}
|
|
78
119
|
```
|
|
79
120
|
|
|
80
|
-
### 다이얼로그 (
|
|
121
|
+
### 확인 다이얼로그 (접근성 자동)
|
|
81
122
|
|
|
82
123
|
```tsx
|
|
83
124
|
import { useState } from "react";
|
|
84
125
|
import { Popup, Button } from "@peppone.choi/ui-kit";
|
|
85
126
|
|
|
86
|
-
export function
|
|
127
|
+
export function DeleteButton() {
|
|
87
128
|
const [open, setOpen] = useState(false);
|
|
129
|
+
|
|
88
130
|
return (
|
|
89
131
|
<>
|
|
90
|
-
<Button onClick={() => setOpen(true)}
|
|
132
|
+
<Button variant="destructive" onClick={() => setOpen(true)}>
|
|
133
|
+
삭제
|
|
134
|
+
</Button>
|
|
135
|
+
|
|
91
136
|
<Popup
|
|
92
137
|
open={open}
|
|
93
138
|
onClose={() => setOpen(false)}
|
|
94
|
-
title="항목을 삭제할까요?"
|
|
139
|
+
title="이 항목을 삭제할까요?"
|
|
95
140
|
description="이 작업은 되돌릴 수 없습니다."
|
|
96
|
-
primaryAction={{
|
|
141
|
+
primaryAction={{
|
|
142
|
+
label: "삭제",
|
|
143
|
+
variant: "destructive",
|
|
144
|
+
onClick: () => setOpen(false),
|
|
145
|
+
}}
|
|
97
146
|
secondaryAction={{ label: "취소", onClick: () => setOpen(false) }}
|
|
98
147
|
/>
|
|
99
148
|
</>
|
|
@@ -101,55 +150,80 @@ export function ConfirmDialog() {
|
|
|
101
150
|
}
|
|
102
151
|
```
|
|
103
152
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
## 두 개의 진입점
|
|
110
|
-
|
|
111
|
-
| import 경로 | 사용 시점 |
|
|
112
|
-
|---|---|
|
|
113
|
-
| `@peppone.choi/ui-kit` | 모든 React 앱(Vite, CRA, Remix, Astro, Next.js 클라이언트 컴포넌트). 프레임워크 비종속. |
|
|
114
|
-
| `@peppone.choi/ui-kit/next` | `next/link` 기반 클라이언트 라우팅을 원하는 Next.js 앱. 코어 전체를 재노출하고 Next 결합 컴포넌트만 덮어씀. |
|
|
115
|
-
|
|
116
|
-
### 코어 (프레임워크 비종속)
|
|
153
|
+
포커스 트랩, 스크롤 락, `Escape` 처리, ARIA 라벨을 직접 안 짜도 다이얼로그가 다
|
|
154
|
+
해줍니다.
|
|
117
155
|
|
|
118
|
-
|
|
119
|
-
어떤 라우터든 주입할 수 있습니다:
|
|
156
|
+
### 라디오 그룹
|
|
120
157
|
|
|
121
158
|
```tsx
|
|
122
|
-
import {
|
|
123
|
-
import { Link } from "react-router-dom";
|
|
159
|
+
import { RadioGroup, RadioGroupItem } from "@peppone.choi/ui-kit";
|
|
124
160
|
|
|
125
|
-
|
|
161
|
+
export function ShippingOptions() {
|
|
162
|
+
return (
|
|
163
|
+
<RadioGroup defaultValue="standard">
|
|
164
|
+
<RadioGroupItem value="standard" label="일반 (무료)" />
|
|
165
|
+
<RadioGroupItem value="express" label="빠른 배송 (+5,000원)" />
|
|
166
|
+
<RadioGroupItem value="overnight" label="당일 (+15,000원)" />
|
|
167
|
+
</RadioGroup>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
126
170
|
```
|
|
127
171
|
|
|
128
|
-
###
|
|
172
|
+
### 바텀시트 (모바일에 좋음)
|
|
129
173
|
|
|
130
174
|
```tsx
|
|
131
|
-
|
|
132
|
-
import {
|
|
175
|
+
import { useState } from "react";
|
|
176
|
+
import { BottomSheet, Button } from "@peppone.choi/ui-kit";
|
|
133
177
|
|
|
134
|
-
|
|
178
|
+
export function FilterSheet() {
|
|
179
|
+
const [open, setOpen] = useState(false);
|
|
180
|
+
return (
|
|
181
|
+
<>
|
|
182
|
+
<Button variant="outline" onClick={() => setOpen(true)}>필터</Button>
|
|
183
|
+
<BottomSheet open={open} onClose={() => setOpen(false)} title="필터">
|
|
184
|
+
<p>여기에 필터 컨트롤을 넣으세요.</p>
|
|
185
|
+
</BottomSheet>
|
|
186
|
+
</>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
135
189
|
```
|
|
136
190
|
|
|
137
191
|
---
|
|
138
192
|
|
|
139
|
-
##
|
|
193
|
+
## 다크 모드
|
|
140
194
|
|
|
141
|
-
킷은
|
|
142
|
-
|
|
195
|
+
킷은 색을 CSS 변수에서 읽고, 상위 요소(보통 `<html>`)에 `dark` 클래스가 있으면
|
|
196
|
+
전환됩니다:
|
|
143
197
|
|
|
144
198
|
```html
|
|
145
199
|
<html class="dark">
|
|
146
200
|
```
|
|
147
201
|
|
|
148
|
-
토글 방식은
|
|
149
|
-
|
|
150
|
-
|
|
202
|
+
토글 방식은 자유입니다. [`next-themes`](https://github.com/pacocoursey/next-themes) 예:
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
// app/providers.tsx
|
|
206
|
+
"use client";
|
|
207
|
+
import { ThemeProvider } from "next-themes";
|
|
208
|
+
|
|
209
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
210
|
+
return (
|
|
211
|
+
<ThemeProvider attribute="class" defaultTheme="system">
|
|
212
|
+
{children}
|
|
213
|
+
</ThemeProvider>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
라이브러리 없이 한 줄로:
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
<button onClick={() => document.documentElement.classList.toggle("dark")}>
|
|
222
|
+
테마 전환
|
|
223
|
+
</button>
|
|
224
|
+
```
|
|
151
225
|
|
|
152
|
-
|
|
226
|
+
JS에서 테마 값(토큰, WDS `sx` 헬퍼)이 필요하면 킷의 프로바이더로 감싸세요:
|
|
153
227
|
|
|
154
228
|
```tsx
|
|
155
229
|
import { ThemeProvider, useTheme } from "@peppone.choi/ui-kit";
|
|
@@ -161,9 +235,76 @@ import { ThemeProvider, useTheme } from "@peppone.choi/ui-kit";
|
|
|
161
235
|
|
|
162
236
|
---
|
|
163
237
|
|
|
164
|
-
##
|
|
238
|
+
## Next.js와 함께 쓰기
|
|
239
|
+
|
|
240
|
+
import 경로가 둘 있습니다. 대부분은 첫 번째만 쓰면 됩니다.
|
|
241
|
+
|
|
242
|
+
| import 경로 | 사용 시점 |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `@peppone.choi/ui-kit` | **기본.** 모든 React 앱 — Vite, CRA, Remix, Astro, Next.js. |
|
|
245
|
+
| `@peppone.choi/ui-kit/next` | `next/link` 기반 클라이언트 라우팅을 원하는 Next.js 앱. 코어 전체를 재노출하고 Next 전용 부분만 교체. |
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
// 순수 React (Next에서도 동작):
|
|
249
|
+
import { Button, ActionArea } from "@peppone.choi/ui-kit";
|
|
250
|
+
|
|
251
|
+
// next/link 라우팅을 원하면 /next에서:
|
|
252
|
+
import { ActionArea } from "@peppone.choi/ui-kit/next";
|
|
253
|
+
|
|
254
|
+
<ActionArea href="/profile">프로필로</ActionArea>;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
다른 라우터(React Router, TanStack Router)를 쓰면 코어 `ActionArea`의
|
|
258
|
+
`linkComponent` prop을 쓰세요:
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
import { ActionArea } from "@peppone.choi/ui-kit";
|
|
262
|
+
import { Link } from "react-router-dom";
|
|
263
|
+
|
|
264
|
+
<ActionArea href="/profile" linkComponent={Link}>프로필</ActionArea>;
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 모든 컴포넌트 둘러보기
|
|
270
|
+
|
|
271
|
+
props와 라이트/다크 미리보기까지 실제로 보고 싶다면 로컬에서 Storybook을 실행하세요:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
git clone https://github.com/peppone-choi/Peppone-UI-KIT
|
|
275
|
+
cd Peppone-UI-KIT
|
|
276
|
+
pnpm install
|
|
277
|
+
pnpm storybook # http://localhost:6006 열림
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 문제 해결
|
|
283
|
+
|
|
284
|
+
**컴포넌트에 스타일이 없거나 깨져 보여요.**
|
|
285
|
+
스타일시트를 빠뜨린 경우가 대부분입니다. 앱 진입점에
|
|
286
|
+
`import "@peppone.choi/ui-kit/styles.css";`를 한 번 추가하세요.
|
|
165
287
|
|
|
166
|
-
|
|
288
|
+
**다크 모드가 안 바뀌어요.**
|
|
289
|
+
상위 요소(보통 `<html>`)에 실제로 `dark` 클래스가 붙는지 확인하세요. `next-themes`는
|
|
290
|
+
`attribute="class"`로 설정.
|
|
291
|
+
|
|
292
|
+
**"Invalid hook call" / React 사본 2개.**
|
|
293
|
+
앱에 `react`/`react-dom`이 하나만 있도록 하세요. 중복을 막으려고 `react`를 일부러
|
|
294
|
+
peer 의존성으로 둡니다.
|
|
295
|
+
|
|
296
|
+
**서버 컴포넌트 에러: "use client 필요".**
|
|
297
|
+
킷 컴포넌트는 클라이언트 컴포넌트입니다(이미 `"use client"` 표시). 본인 클라이언트
|
|
298
|
+
컴포넌트에서 import하거나 그 안에서 렌더링하세요 — 서버 컴포넌트에서 훅을 직접
|
|
299
|
+
호출하지 마세요.
|
|
300
|
+
|
|
301
|
+
**Next.js `<Link>` 이동이 전체 새로고침돼요.**
|
|
302
|
+
컴포넌트를 `@peppone.choi/ui-kit/next`에서 import하거나 `linkComponent`를 넘겨
|
|
303
|
+
`<a>` 대신 `next/link`를 쓰게 하세요.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 전체 컴포넌트
|
|
167
308
|
|
|
168
309
|
| 카테고리 | 컴포넌트 |
|
|
169
310
|
|---|---|
|
|
@@ -171,32 +312,37 @@ import { ThemeProvider, useTheme } from "@peppone.choi/ui-kit";
|
|
|
171
312
|
| **콘텐츠** | Accordion, Avatar, AvatarGroup, AvatarButton, Card, CardList, ContentBadge, List, ListCard, ListCell, PlayBadge, SectionHeader, Table, Thumbnail, Typography |
|
|
172
313
|
| **피드백** | Alert, FallbackView, PushBadge, SectionMessage, Snackbar, Toast, Loading, Skeleton |
|
|
173
314
|
| **내비게이션** | BottomNavigation, Category, PageCounter, Pagination, PaginationDots, ProgressIndicator, ProgressTracker, ProgressStepIndicator, Tab, TopNavigation |
|
|
174
|
-
|
|
|
315
|
+
| **오버레이** | Autocomplete, BottomSheet, Menu, Popover, Popup, ScrollArea, Tooltip |
|
|
175
316
|
| **선택 & 입력** | Checkbox, Radio, RadioGroup, RoundCheckbox, CheckMark, DatePicker, DateRangePicker, DateCalendar, DateRangeCalendar, TimePicker, TimeView, FilterButton, FramedStyle, Label, SearchField, SegmentedControl, Select, SelectMultiple, Slider, Stepper, Switch, TextArea, Input, Form, PickerActionArea |
|
|
176
317
|
| **레이아웃 & 유틸리티** | Box, FlexBox, Grid, GridItem, Divider |
|
|
177
318
|
|
|
178
|
-
|
|
179
|
-
|
|
319
|
+
모두 패키지 루트에서 export되며 타입이 완비돼 있습니다. **Montage WDS 아이콘 350개**,
|
|
320
|
+
디자인 토큰, Lottie 로딩 애니메이션도 함께 포함됩니다.
|
|
180
321
|
|
|
181
322
|
---
|
|
182
323
|
|
|
183
|
-
## 개발
|
|
324
|
+
## 기여 / 로컬 개발
|
|
184
325
|
|
|
185
326
|
```bash
|
|
186
327
|
pnpm install # 의존성 설치
|
|
187
328
|
pnpm dev # 쇼케이스 앱 실행 (Next.js)
|
|
188
|
-
pnpm
|
|
189
|
-
pnpm test # 단위 테스트
|
|
329
|
+
pnpm storybook # localhost:6006에서 컴포넌트 둘러보기
|
|
330
|
+
pnpm test # 단위 테스트 (Vitest)
|
|
190
331
|
pnpm lint # ESLint
|
|
191
|
-
pnpm
|
|
332
|
+
pnpm build # 라이브러리 빌드 (JS + .d.ts + CSS) → dist/
|
|
192
333
|
```
|
|
193
334
|
|
|
194
|
-
|
|
195
|
-
|
|
335
|
+
이 저장소는 [Changesets](https://github.com/changesets/changesets)를 씁니다. 릴리스에
|
|
336
|
+
포함할 변경이면 추가하세요:
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
pnpm changeset
|
|
340
|
+
```
|
|
196
341
|
|
|
197
342
|
---
|
|
198
343
|
|
|
199
344
|
## 라이선스
|
|
200
345
|
|
|
201
|
-
MIT
|
|
202
|
-
[Wanted Design System (Montage)](https://github.com/wanteddev/montage-web) 기반으로
|
|
346
|
+
MIT — 개인·상업 프로젝트 모두 자유롭게 사용 가능. 동일하게 MIT인
|
|
347
|
+
[Wanted Design System (Montage)](https://github.com/wanteddev/montage-web) 기반으로
|
|
348
|
+
제작됐습니다.
|
package/README.md
CHANGED
|
@@ -1,101 +1,151 @@
|
|
|
1
1
|
# Peppone UI Kit
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@peppone.choi/ui-kit)
|
|
3
4
|
[](#license)
|
|
4
5
|
|
|
5
|
-
A React component library with **70+ components
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
A friendly React component library with **70+ ready-to-use components** — buttons,
|
|
7
|
+
inputs, dialogs, calendars, navigation, and more. It's built on the
|
|
8
|
+
[Wanted Design System (Montage)](https://github.com/wanteddev/montage-web), works
|
|
9
|
+
in **any React app**, has **first-class TypeScript types**, and **dark mode** out
|
|
10
|
+
of the box.
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
New here? Start with **[60-second setup](#60-second-setup)** below — install, import
|
|
13
|
+
one stylesheet, drop in a component. That's it.
|
|
14
|
+
|
|
15
|
+
> 🇰🇷 한국어 문서: **[README.ko.md](https://github.com/peppone-choi/Peppone-UI-KIT/blob/main/README.ko.md)**
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Contents
|
|
20
|
+
|
|
21
|
+
- [Why Peppone UI Kit](#why-peppone-ui-kit)
|
|
22
|
+
- [60-second setup](#60-second-setup)
|
|
23
|
+
- [Examples](#examples)
|
|
24
|
+
- [Dark mode](#dark-mode)
|
|
25
|
+
- [Using it with Next.js](#using-it-with-nextjs)
|
|
26
|
+
- [Browse every component](#browse-every-component)
|
|
27
|
+
- [Troubleshooting](#troubleshooting)
|
|
28
|
+
- [All components](#all-components)
|
|
29
|
+
- [Contributing / local development](#contributing--local-development)
|
|
30
|
+
- [License](#license)
|
|
11
31
|
|
|
12
32
|
---
|
|
13
33
|
|
|
14
|
-
##
|
|
34
|
+
## Why Peppone UI Kit
|
|
15
35
|
|
|
16
|
-
- **70+ components**
|
|
17
|
-
- **
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- **
|
|
21
|
-
|
|
36
|
+
- 🧩 **70+ components** — actions, content, feedback, navigation, overlays, forms.
|
|
37
|
+
- ♿ **Accessible by default** — dialogs, popovers, menus, and the bottom sheet are
|
|
38
|
+
built on [Base UI](https://base-ui.com) primitives, so you get focus trapping,
|
|
39
|
+
scroll locking, `Escape`/outside-click dismissal, and ARIA wiring for free.
|
|
40
|
+
- 🎨 **Theming + dark mode** — Tailwind CSS v4 tokens via CSS variables. Flip one
|
|
41
|
+
class and the whole kit switches to dark.
|
|
42
|
+
- 🟦 **TypeScript first** — every component and prop is typed.
|
|
43
|
+
- ⚛️ **Works anywhere** — a framework-agnostic core plus an optional Next.js build
|
|
44
|
+
that pre-wires `next/link`.
|
|
45
|
+
- 📦 **Modern packaging** — ESM + CJS, tree-shakeable, `"use client"` preserved for
|
|
46
|
+
React Server Components.
|
|
22
47
|
|
|
23
48
|
---
|
|
24
49
|
|
|
25
|
-
##
|
|
50
|
+
## 60-second setup
|
|
51
|
+
|
|
52
|
+
**1. Install**
|
|
26
53
|
|
|
27
54
|
```bash
|
|
28
|
-
|
|
29
|
-
# or:
|
|
55
|
+
npm install @peppone.choi/ui-kit
|
|
56
|
+
# or: pnpm add @peppone.choi/ui-kit
|
|
30
57
|
# or: yarn add @peppone.choi/ui-kit
|
|
31
58
|
```
|
|
32
59
|
|
|
33
|
-
|
|
60
|
+
> `react` and `react-dom` (v19+) are peer dependencies. **npm 7+ and pnpm install
|
|
61
|
+
> them automatically** — you don't need to do anything. (Only Yarn Classic needs
|
|
62
|
+
> `yarn add react react-dom`.) Everything else the kit needs is bundled as a normal
|
|
63
|
+
> dependency.
|
|
34
64
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
would risk a duplicate copy and the dreaded "Invalid hook call" error.
|
|
65
|
+
**2. Import the stylesheet once** — at your app's entry point. This is the step
|
|
66
|
+
people forget, so do it first:
|
|
38
67
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
68
|
+
```ts
|
|
69
|
+
// Next.js: app/layout.tsx · Vite: src/main.tsx · CRA: src/index.tsx
|
|
70
|
+
import "@peppone.choi/ui-kit/styles.css";
|
|
71
|
+
```
|
|
42
72
|
|
|
43
|
-
|
|
44
|
-
ships as a regular dependency and is installed for you.
|
|
73
|
+
> ⚠️ No stylesheet = unstyled components. If things look broken, check this import.
|
|
45
74
|
|
|
46
|
-
|
|
47
|
-
`@peppone.choi/ui-kit/next`.
|
|
75
|
+
**3. Use a component**
|
|
48
76
|
|
|
49
|
-
|
|
77
|
+
```tsx
|
|
78
|
+
import { Button } from "@peppone.choi/ui-kit";
|
|
50
79
|
|
|
51
|
-
|
|
52
|
-
(
|
|
80
|
+
export default function App() {
|
|
81
|
+
return <Button onClick={() => alert("It works!")}>Click me</Button>;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
53
84
|
|
|
54
|
-
|
|
85
|
+
That's the whole setup. A complete Next.js entry looks like this:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
// app/layout.tsx
|
|
55
89
|
import "@peppone.choi/ui-kit/styles.css";
|
|
56
|
-
```
|
|
57
90
|
|
|
58
|
-
|
|
91
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
92
|
+
return (
|
|
93
|
+
<html lang="en">
|
|
94
|
+
<body>{children}</body>
|
|
95
|
+
</html>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
59
99
|
|
|
60
100
|
---
|
|
61
101
|
|
|
62
|
-
##
|
|
102
|
+
## Examples
|
|
103
|
+
|
|
104
|
+
### Card + Button
|
|
63
105
|
|
|
64
106
|
```tsx
|
|
65
|
-
import {
|
|
66
|
-
import "@peppone.choi/ui-kit/styles.css";
|
|
107
|
+
import { Card, CardHeader, CardTitle, CardContent, Button } from "@peppone.choi/ui-kit";
|
|
67
108
|
|
|
68
|
-
export function
|
|
109
|
+
export function WelcomeCard() {
|
|
69
110
|
return (
|
|
70
111
|
<Card>
|
|
71
112
|
<CardHeader>
|
|
72
|
-
<CardTitle>
|
|
113
|
+
<CardTitle>Welcome 👋</CardTitle>
|
|
73
114
|
</CardHeader>
|
|
74
|
-
<CardContent>
|
|
75
|
-
<
|
|
115
|
+
<CardContent className="flex flex-col gap-3">
|
|
116
|
+
<p>Get started in seconds.</p>
|
|
117
|
+
<Button>Let's go</Button>
|
|
76
118
|
</CardContent>
|
|
77
119
|
</Card>
|
|
78
120
|
);
|
|
79
121
|
}
|
|
80
122
|
```
|
|
81
123
|
|
|
82
|
-
###
|
|
124
|
+
### A confirm dialog (accessible automatically)
|
|
83
125
|
|
|
84
126
|
```tsx
|
|
85
127
|
import { useState } from "react";
|
|
86
128
|
import { Popup, Button } from "@peppone.choi/ui-kit";
|
|
87
129
|
|
|
88
|
-
export function
|
|
130
|
+
export function DeleteButton() {
|
|
89
131
|
const [open, setOpen] = useState(false);
|
|
132
|
+
|
|
90
133
|
return (
|
|
91
134
|
<>
|
|
92
|
-
<Button onClick={() => setOpen(true)}>
|
|
135
|
+
<Button variant="destructive" onClick={() => setOpen(true)}>
|
|
136
|
+
Delete
|
|
137
|
+
</Button>
|
|
138
|
+
|
|
93
139
|
<Popup
|
|
94
140
|
open={open}
|
|
95
141
|
onClose={() => setOpen(false)}
|
|
96
|
-
title="Delete item?"
|
|
142
|
+
title="Delete this item?"
|
|
97
143
|
description="This action cannot be undone."
|
|
98
|
-
primaryAction={{
|
|
144
|
+
primaryAction={{
|
|
145
|
+
label: "Delete",
|
|
146
|
+
variant: "destructive",
|
|
147
|
+
onClick: () => setOpen(false),
|
|
148
|
+
}}
|
|
99
149
|
secondaryAction={{ label: "Cancel", onClick: () => setOpen(false) }}
|
|
100
150
|
/>
|
|
101
151
|
</>
|
|
@@ -103,55 +153,80 @@ export function ConfirmDialog() {
|
|
|
103
153
|
}
|
|
104
154
|
```
|
|
105
155
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
|
-
## Two entry points
|
|
112
|
-
|
|
113
|
-
| Import path | Use when |
|
|
114
|
-
|---|---|
|
|
115
|
-
| `@peppone.choi/ui-kit` | Any React app (Vite, CRA, Remix, Astro, Next.js client components). Framework-agnostic. |
|
|
116
|
-
| `@peppone.choi/ui-kit/next` | Next.js apps that want components pre-wired to `next/link` (client-side navigation). Re-exports the entire core, overriding only the Next-coupled components. |
|
|
156
|
+
You don't wire up focus traps, scroll locking, `Escape` handling, or ARIA labels —
|
|
157
|
+
the dialog does all of that for you.
|
|
117
158
|
|
|
118
|
-
###
|
|
119
|
-
|
|
120
|
-
The core `ActionArea` renders a native `<a>` for links, or accepts a custom
|
|
121
|
-
`linkComponent` so it works with any router:
|
|
159
|
+
### A radio group
|
|
122
160
|
|
|
123
161
|
```tsx
|
|
124
|
-
import {
|
|
125
|
-
import { Link } from "react-router-dom";
|
|
162
|
+
import { RadioGroup, RadioGroupItem } from "@peppone.choi/ui-kit";
|
|
126
163
|
|
|
127
|
-
|
|
164
|
+
export function ShippingOptions() {
|
|
165
|
+
return (
|
|
166
|
+
<RadioGroup defaultValue="standard">
|
|
167
|
+
<RadioGroupItem value="standard" label="Standard (free)" />
|
|
168
|
+
<RadioGroupItem value="express" label="Express (+$5)" />
|
|
169
|
+
<RadioGroupItem value="overnight" label="Overnight (+$15)" />
|
|
170
|
+
</RadioGroup>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
128
173
|
```
|
|
129
174
|
|
|
130
|
-
###
|
|
175
|
+
### A bottom sheet (great on mobile)
|
|
131
176
|
|
|
132
177
|
```tsx
|
|
133
|
-
|
|
134
|
-
import {
|
|
178
|
+
import { useState } from "react";
|
|
179
|
+
import { BottomSheet, Button } from "@peppone.choi/ui-kit";
|
|
135
180
|
|
|
136
|
-
|
|
181
|
+
export function FilterSheet() {
|
|
182
|
+
const [open, setOpen] = useState(false);
|
|
183
|
+
return (
|
|
184
|
+
<>
|
|
185
|
+
<Button variant="outline" onClick={() => setOpen(true)}>Filters</Button>
|
|
186
|
+
<BottomSheet open={open} onClose={() => setOpen(false)} title="Filters">
|
|
187
|
+
<p>Your filter controls go here.</p>
|
|
188
|
+
</BottomSheet>
|
|
189
|
+
</>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
137
192
|
```
|
|
138
193
|
|
|
139
194
|
---
|
|
140
195
|
|
|
141
|
-
##
|
|
196
|
+
## Dark mode
|
|
142
197
|
|
|
143
|
-
The kit
|
|
144
|
-
|
|
198
|
+
The kit reads its colors from CSS variables and switches whenever an ancestor has
|
|
199
|
+
the `dark` class — usually on `<html>`:
|
|
145
200
|
|
|
146
201
|
```html
|
|
147
202
|
<html class="dark">
|
|
148
203
|
```
|
|
149
204
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
205
|
+
Use any toggle you like. With [`next-themes`](https://github.com/pacocoursey/next-themes):
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
// app/providers.tsx
|
|
209
|
+
"use client";
|
|
210
|
+
import { ThemeProvider } from "next-themes";
|
|
211
|
+
|
|
212
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
213
|
+
return (
|
|
214
|
+
<ThemeProvider attribute="class" defaultTheme="system">
|
|
215
|
+
{children}
|
|
216
|
+
</ThemeProvider>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Or a one-liner without any library:
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
<button onClick={() => document.documentElement.classList.toggle("dark")}>
|
|
225
|
+
Toggle theme
|
|
226
|
+
</button>
|
|
227
|
+
```
|
|
153
228
|
|
|
154
|
-
|
|
229
|
+
Need theme values in JS (tokens, the WDS `sx` helper)? Wrap your tree in the kit's
|
|
155
230
|
provider:
|
|
156
231
|
|
|
157
232
|
```tsx
|
|
@@ -164,9 +239,77 @@ import { ThemeProvider, useTheme } from "@peppone.choi/ui-kit";
|
|
|
164
239
|
|
|
165
240
|
---
|
|
166
241
|
|
|
167
|
-
##
|
|
242
|
+
## Using it with Next.js
|
|
243
|
+
|
|
244
|
+
There are two import paths. Most apps only need the first one.
|
|
168
245
|
|
|
169
|
-
|
|
246
|
+
| Import from | When |
|
|
247
|
+
|---|---|
|
|
248
|
+
| `@peppone.choi/ui-kit` | **Default.** Any React app — Vite, CRA, Remix, Astro, or Next.js. |
|
|
249
|
+
| `@peppone.choi/ui-kit/next` | Next.js apps that want components pre-wired to `next/link` for client-side navigation. It re-exports everything from the core and only swaps the Next-specific pieces. |
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
// Plain React (works in Next too):
|
|
253
|
+
import { Button, ActionArea } from "@peppone.choi/ui-kit";
|
|
254
|
+
|
|
255
|
+
// Want next/link navigation baked in? Import those from /next:
|
|
256
|
+
import { ActionArea } from "@peppone.choi/ui-kit/next";
|
|
257
|
+
|
|
258
|
+
<ActionArea href="/profile">Go to profile</ActionArea>;
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Using another router (React Router, TanStack Router)? The core `ActionArea` takes a
|
|
262
|
+
`linkComponent` prop:
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
import { ActionArea } from "@peppone.choi/ui-kit";
|
|
266
|
+
import { Link } from "react-router-dom";
|
|
267
|
+
|
|
268
|
+
<ActionArea href="/profile" linkComponent={Link}>Profile</ActionArea>;
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Browse every component
|
|
274
|
+
|
|
275
|
+
Want to see everything live, with props and light/dark previews? Run Storybook
|
|
276
|
+
locally:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
git clone https://github.com/peppone-choi/Peppone-UI-KIT
|
|
280
|
+
cd Peppone-UI-KIT
|
|
281
|
+
pnpm install
|
|
282
|
+
pnpm storybook # opens http://localhost:6006
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Troubleshooting
|
|
288
|
+
|
|
289
|
+
**Components have no styling / look broken.**
|
|
290
|
+
You probably forgot the stylesheet. Add `import "@peppone.choi/ui-kit/styles.css";`
|
|
291
|
+
once at your app entry.
|
|
292
|
+
|
|
293
|
+
**Dark mode isn't switching.**
|
|
294
|
+
Make sure a parent element (usually `<html>`) actually gets the `dark` class. With
|
|
295
|
+
`next-themes`, set `attribute="class"`.
|
|
296
|
+
|
|
297
|
+
**"Invalid hook call" / two copies of React.**
|
|
298
|
+
Make sure there's a single `react` / `react-dom` in your app. `react` is a *peer*
|
|
299
|
+
dependency on purpose so it isn't duplicated.
|
|
300
|
+
|
|
301
|
+
**Server Component error: "needs `use client`".**
|
|
302
|
+
The kit's components are client components (already marked `"use client"`). Import
|
|
303
|
+
them into your own client component, or render them inside one — don't call their
|
|
304
|
+
hooks from a Server Component.
|
|
305
|
+
|
|
306
|
+
**Next.js `<Link>` navigation does a full reload.**
|
|
307
|
+
Import the component from `@peppone.choi/ui-kit/next` (or pass `linkComponent`), so
|
|
308
|
+
it uses `next/link` instead of a plain `<a>`.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## All components
|
|
170
313
|
|
|
171
314
|
| Category | Components |
|
|
172
315
|
|---|---|
|
|
@@ -174,32 +317,36 @@ import { ThemeProvider, useTheme } from "@peppone.choi/ui-kit";
|
|
|
174
317
|
| **Content** | Accordion, Avatar, AvatarGroup, AvatarButton, Card, CardList, ContentBadge, List, ListCard, ListCell, PlayBadge, SectionHeader, Table, Thumbnail, Typography |
|
|
175
318
|
| **Feedback** | Alert, FallbackView, PushBadge, SectionMessage, Snackbar, Toast, Loading, Skeleton |
|
|
176
319
|
| **Navigation** | BottomNavigation, Category, PageCounter, Pagination, PaginationDots, ProgressIndicator, ProgressTracker, ProgressStepIndicator, Tab, TopNavigation |
|
|
177
|
-
| **
|
|
320
|
+
| **Overlays** | Autocomplete, BottomSheet, Menu, Popover, Popup, ScrollArea, Tooltip |
|
|
178
321
|
| **Selection & input** | Checkbox, Radio, RadioGroup, RoundCheckbox, CheckMark, DatePicker, DateRangePicker, DateCalendar, DateRangeCalendar, TimePicker, TimeView, FilterButton, FramedStyle, Label, SearchField, SegmentedControl, Select, SelectMultiple, Slider, Stepper, Switch, TextArea, Input, Form, PickerActionArea |
|
|
179
322
|
| **Layout & utilities** | Box, FlexBox, Grid, GridItem, Divider |
|
|
180
323
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
animation.
|
|
324
|
+
Everything is exported from the package root and fully typed. The kit also bundles
|
|
325
|
+
**350 Montage WDS icons**, design tokens, and a Lottie loading animation.
|
|
184
326
|
|
|
185
327
|
---
|
|
186
328
|
|
|
187
|
-
##
|
|
329
|
+
## Contributing / local development
|
|
188
330
|
|
|
189
331
|
```bash
|
|
190
332
|
pnpm install # install dependencies
|
|
191
333
|
pnpm dev # run the showcase app (Next.js)
|
|
192
|
-
pnpm
|
|
193
|
-
pnpm test # run the unit
|
|
334
|
+
pnpm storybook # browse components at localhost:6006
|
|
335
|
+
pnpm test # run the unit tests (Vitest)
|
|
194
336
|
pnpm lint # ESLint
|
|
195
|
-
pnpm
|
|
337
|
+
pnpm build # build the library (JS + .d.ts + CSS) into dist/
|
|
196
338
|
```
|
|
197
339
|
|
|
198
|
-
|
|
199
|
-
|
|
340
|
+
This repo uses [Changesets](https://github.com/changesets/changesets). If your
|
|
341
|
+
change should ship in a release, add one:
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
pnpm changeset
|
|
345
|
+
```
|
|
200
346
|
|
|
201
347
|
---
|
|
202
348
|
|
|
203
349
|
## License
|
|
204
350
|
|
|
205
|
-
MIT
|
|
351
|
+
MIT — free to use in personal and commercial projects. Built on the
|
|
352
|
+
[Wanted Design System (Montage)](https://github.com/wanteddev/montage-web), also MIT.
|
package/package.json
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peppone.choi/ui-kit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Peppone UI Kit — a React component library (framework-agnostic core + optional Next.js adapter) built on the Wanted Design System (Montage).",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/peppone-choi/Peppone-UI-KIT.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/peppone-choi/Peppone-UI-KIT#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/peppone-choi/Peppone-UI-KIT/issues"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
7
18
|
"sideEffects": [
|
|
8
19
|
"**/*.css"
|
|
9
20
|
],
|
|
@@ -30,21 +41,6 @@
|
|
|
30
41
|
"publishConfig": {
|
|
31
42
|
"access": "public"
|
|
32
43
|
},
|
|
33
|
-
"scripts": {
|
|
34
|
-
"dev": "next dev",
|
|
35
|
-
"build:app": "next build",
|
|
36
|
-
"start": "next start",
|
|
37
|
-
"build": "pnpm build:lib && pnpm build:css",
|
|
38
|
-
"build:lib": "tsup",
|
|
39
|
-
"build:css": "tailwindcss -i ./src/styles.css -o ./dist/peppone-ui.css",
|
|
40
|
-
"typecheck": "tsc --noEmit",
|
|
41
|
-
"lint": "eslint",
|
|
42
|
-
"test": "vitest run",
|
|
43
|
-
"test:watch": "vitest",
|
|
44
|
-
"storybook": "storybook dev -p 6006",
|
|
45
|
-
"build-storybook": "storybook build",
|
|
46
|
-
"prepublishOnly": "pnpm build"
|
|
47
|
-
},
|
|
48
44
|
"peerDependencies": {
|
|
49
45
|
"next": ">=15",
|
|
50
46
|
"react": ">=19",
|
|
@@ -97,5 +93,19 @@
|
|
|
97
93
|
"vitest": "^3",
|
|
98
94
|
"vitest-axe": "^0.1.0",
|
|
99
95
|
"vitest-canvas-mock": "^1.1.4"
|
|
96
|
+
},
|
|
97
|
+
"scripts": {
|
|
98
|
+
"dev": "next dev",
|
|
99
|
+
"build:app": "next build",
|
|
100
|
+
"start": "next start",
|
|
101
|
+
"build": "pnpm build:lib && pnpm build:css",
|
|
102
|
+
"build:lib": "tsup",
|
|
103
|
+
"build:css": "tailwindcss -i ./src/styles.css -o ./dist/peppone-ui.css",
|
|
104
|
+
"typecheck": "tsc --noEmit",
|
|
105
|
+
"lint": "eslint",
|
|
106
|
+
"test": "vitest run",
|
|
107
|
+
"test:watch": "vitest",
|
|
108
|
+
"storybook": "storybook dev -p 6006",
|
|
109
|
+
"build-storybook": "storybook build"
|
|
100
110
|
}
|
|
101
|
-
}
|
|
111
|
+
}
|