@bigtablet/design-system 1.18.9 → 1.19.0
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.md +27 -4
- package/dist/index.css +135 -0
- package/dist/index.d.ts +25 -14
- package/dist/index.js +139 -68
- package/dist/next.js +46 -29
- package/package.json +3 -5
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ pnpm add @bigtablet/design-system
|
|
|
49
49
|
**Peer Dependencies**
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
npm install react react-dom lucide-react
|
|
52
|
+
npm install react react-dom lucide-react
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
> Recommended for use with **React 18+** and **Next.js 13+**.
|
|
@@ -129,8 +129,9 @@ export default function RootLayout({ children }) {
|
|
|
129
129
|
<html>
|
|
130
130
|
<body>
|
|
131
131
|
<AlertProvider>
|
|
132
|
-
<ToastProvider
|
|
133
|
-
|
|
132
|
+
<ToastProvider>
|
|
133
|
+
{children}
|
|
134
|
+
</ToastProvider>
|
|
134
135
|
</AlertProvider>
|
|
135
136
|
</body>
|
|
136
137
|
</html>
|
|
@@ -138,8 +139,9 @@ export default function RootLayout({ children }) {
|
|
|
138
139
|
}
|
|
139
140
|
```
|
|
140
141
|
|
|
142
|
+
**Alert usage**
|
|
143
|
+
|
|
141
144
|
```tsx
|
|
142
|
-
// Usage example
|
|
143
145
|
import { useAlert } from '@bigtablet/design-system';
|
|
144
146
|
|
|
145
147
|
function MyComponent() {
|
|
@@ -162,6 +164,27 @@ function MyComponent() {
|
|
|
162
164
|
}
|
|
163
165
|
```
|
|
164
166
|
|
|
167
|
+
**Toast usage**
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { useToast } from '@bigtablet/design-system';
|
|
171
|
+
|
|
172
|
+
function MyComponent() {
|
|
173
|
+
const toast = useToast();
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<div>
|
|
177
|
+
<Button onClick={() => toast.success('Saved successfully!')}>Save</Button>
|
|
178
|
+
<Button onClick={() => toast.error('An error occurred.')}>Error</Button>
|
|
179
|
+
<Button onClick={() => toast.warning('Session expiring soon.')}>Warning</Button>
|
|
180
|
+
<Button onClick={() => toast.info('New version available.')}>Info</Button>
|
|
181
|
+
{/* Custom duration (ms) as second argument */}
|
|
182
|
+
<Button onClick={() => toast.success('Saved!', 5000)}>Save (5s)</Button>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
165
188
|
### Vanilla JS (HTML/CSS/JS)
|
|
166
189
|
|
|
167
190
|
For non-React environments (Thymeleaf, JSP, PHP, etc.), use directly via CDN.
|
package/dist/index.css
CHANGED
|
@@ -234,6 +234,141 @@
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
/* src/ui/feedback/toast/style.scss */
|
|
238
|
+
@keyframes toast_slide_in {
|
|
239
|
+
from {
|
|
240
|
+
opacity: 0;
|
|
241
|
+
transform: translateX(calc(100% + 16px));
|
|
242
|
+
}
|
|
243
|
+
to {
|
|
244
|
+
opacity: 1;
|
|
245
|
+
transform: translateX(0);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
@keyframes toast_slide_out {
|
|
249
|
+
from {
|
|
250
|
+
opacity: 1;
|
|
251
|
+
transform: translateX(0);
|
|
252
|
+
}
|
|
253
|
+
to {
|
|
254
|
+
opacity: 0;
|
|
255
|
+
transform: translateX(calc(100% + 16px));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
@keyframes toast_progress {
|
|
259
|
+
from {
|
|
260
|
+
width: 100%;
|
|
261
|
+
}
|
|
262
|
+
to {
|
|
263
|
+
width: 0%;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
.toast_container {
|
|
267
|
+
position: fixed;
|
|
268
|
+
top: 1rem;
|
|
269
|
+
right: 1rem;
|
|
270
|
+
z-index: 10001;
|
|
271
|
+
display: flex;
|
|
272
|
+
flex-direction: column;
|
|
273
|
+
gap: 0.5rem;
|
|
274
|
+
pointer-events: none;
|
|
275
|
+
width: 360px;
|
|
276
|
+
max-width: calc(100vw - 32px);
|
|
277
|
+
}
|
|
278
|
+
.toast_item {
|
|
279
|
+
pointer-events: auto;
|
|
280
|
+
position: relative;
|
|
281
|
+
overflow: hidden;
|
|
282
|
+
display: flex;
|
|
283
|
+
align-items: flex-start;
|
|
284
|
+
gap: 0.5rem;
|
|
285
|
+
padding: 0.75rem 0.75rem 1rem;
|
|
286
|
+
background: #ffffff;
|
|
287
|
+
border: 1px solid #e5e5e5;
|
|
288
|
+
border-radius: 12px;
|
|
289
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04);
|
|
290
|
+
font-family: "Pretendard", sans-serif;
|
|
291
|
+
font-size: 0.875rem;
|
|
292
|
+
font-weight: 500;
|
|
293
|
+
color: #1a1a1a;
|
|
294
|
+
line-height: 1.5;
|
|
295
|
+
animation: toast_slide_in 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
296
|
+
}
|
|
297
|
+
.toast_item_exiting {
|
|
298
|
+
animation: toast_slide_out 0.26s ease-in forwards;
|
|
299
|
+
}
|
|
300
|
+
.toast_icon {
|
|
301
|
+
flex-shrink: 0;
|
|
302
|
+
margin-top: 1px;
|
|
303
|
+
display: flex;
|
|
304
|
+
}
|
|
305
|
+
.toast_icon_success {
|
|
306
|
+
color: #10b981;
|
|
307
|
+
}
|
|
308
|
+
.toast_icon_error {
|
|
309
|
+
color: #ef4444;
|
|
310
|
+
}
|
|
311
|
+
.toast_icon_warning {
|
|
312
|
+
color: #f59e0b;
|
|
313
|
+
}
|
|
314
|
+
.toast_icon_info {
|
|
315
|
+
color: #3b82f6;
|
|
316
|
+
}
|
|
317
|
+
.toast_icon_default {
|
|
318
|
+
color: #666666;
|
|
319
|
+
}
|
|
320
|
+
.toast_message {
|
|
321
|
+
flex: 1;
|
|
322
|
+
word-break: break-word;
|
|
323
|
+
}
|
|
324
|
+
.toast_close {
|
|
325
|
+
flex-shrink: 0;
|
|
326
|
+
display: flex;
|
|
327
|
+
align-items: center;
|
|
328
|
+
justify-content: center;
|
|
329
|
+
width: 20px;
|
|
330
|
+
height: 20px;
|
|
331
|
+
margin-top: 1px;
|
|
332
|
+
padding: 0;
|
|
333
|
+
border: none;
|
|
334
|
+
background: none;
|
|
335
|
+
cursor: pointer;
|
|
336
|
+
color: #999999;
|
|
337
|
+
border-radius: 6px;
|
|
338
|
+
transition: color 0.1s ease-in-out, background-color 0.1s ease-in-out;
|
|
339
|
+
}
|
|
340
|
+
.toast_close:hover {
|
|
341
|
+
color: #1a1a1a;
|
|
342
|
+
background-color: #fafafa;
|
|
343
|
+
}
|
|
344
|
+
.toast_close:focus-visible {
|
|
345
|
+
outline: none;
|
|
346
|
+
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.15);
|
|
347
|
+
}
|
|
348
|
+
.toast_progress {
|
|
349
|
+
position: absolute;
|
|
350
|
+
bottom: 0;
|
|
351
|
+
left: 0;
|
|
352
|
+
height: 3px;
|
|
353
|
+
border-radius: 0 0 0 12px;
|
|
354
|
+
animation: toast_progress var(--toast-duration, 3000ms) linear forwards;
|
|
355
|
+
}
|
|
356
|
+
.toast_progress_success {
|
|
357
|
+
background: #10b981;
|
|
358
|
+
}
|
|
359
|
+
.toast_progress_error {
|
|
360
|
+
background: #ef4444;
|
|
361
|
+
}
|
|
362
|
+
.toast_progress_warning {
|
|
363
|
+
background: #f59e0b;
|
|
364
|
+
}
|
|
365
|
+
.toast_progress_info {
|
|
366
|
+
background: #3b82f6;
|
|
367
|
+
}
|
|
368
|
+
.toast_progress_default {
|
|
369
|
+
background: #666666;
|
|
370
|
+
}
|
|
371
|
+
|
|
237
372
|
/* src/ui/general/button/style.scss */
|
|
238
373
|
.button {
|
|
239
374
|
display: inline-flex;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import * as react_toastify from 'react-toastify';
|
|
4
3
|
|
|
5
4
|
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
5
|
/** 카드 상단에 표시할 제목 */
|
|
@@ -96,23 +95,35 @@ interface TopLoadingProps {
|
|
|
96
95
|
declare const TopLoading: ({ progress, color, height, isLoading, ariaLabel, }: TopLoadingProps) => react_jsx_runtime.JSX.Element | null;
|
|
97
96
|
|
|
98
97
|
interface ToastProviderProps {
|
|
99
|
-
/**
|
|
100
|
-
|
|
98
|
+
/** 앱 루트에서 감싸는 자식 요소 */
|
|
99
|
+
children: React.ReactNode;
|
|
100
|
+
/** 최대 동시 표시 토스트 수 (기본값: 5) */
|
|
101
|
+
maxCount?: number;
|
|
101
102
|
}
|
|
102
103
|
/**
|
|
103
|
-
* 토스트
|
|
104
|
-
*
|
|
105
|
-
* @param props
|
|
106
|
-
* @returns 렌더링된 토스트 컨테이너
|
|
104
|
+
* 토스트 컨텍스트를 제공하는 Provider를 렌더링한다.
|
|
105
|
+
* 앱 최상단에서 children을 감싸야 useToast 훅을 사용할 수 있다.
|
|
106
|
+
* @param props Provider 속성
|
|
107
|
+
* @returns 렌더링된 Provider와 토스트 컨테이너
|
|
107
108
|
*/
|
|
108
|
-
declare const ToastProvider: ({
|
|
109
|
+
declare const ToastProvider: ({ children, maxCount }: ToastProviderProps) => react_jsx_runtime.JSX.Element;
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
/**
|
|
112
|
+
* 토스트 메시지를 표시하는 훅.
|
|
113
|
+
* ToastProvider 내부에서만 사용할 수 있다.
|
|
114
|
+
* @returns 토스트 메시지 표시 함수 객체
|
|
115
|
+
*/
|
|
116
|
+
declare const useToast: () => {
|
|
117
|
+
/** 성공 메시지를 표시한다 */
|
|
118
|
+
success: (message: string, duration?: number) => void;
|
|
119
|
+
/** 오류 메시지를 표시한다 */
|
|
120
|
+
error: (message: string, duration?: number) => void;
|
|
121
|
+
/** 경고 메시지를 표시한다 */
|
|
122
|
+
warning: (message: string, duration?: number) => void;
|
|
123
|
+
/** 정보 메시지를 표시한다 */
|
|
124
|
+
info: (message: string, duration?: number) => void;
|
|
125
|
+
/** 기본 메시지를 표시한다 */
|
|
126
|
+
message: (message: string, duration?: number) => void;
|
|
116
127
|
};
|
|
117
128
|
|
|
118
129
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import './index.css';
|
|
3
|
-
import * as
|
|
3
|
+
import * as React6 from 'react';
|
|
4
4
|
import { createContext, useContext, useState, useCallback } from 'react';
|
|
5
5
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
6
|
import { createPortal } from 'react-dom';
|
|
7
|
-
import {
|
|
8
|
-
import 'react-toastify/dist/ReactToastify.css';
|
|
9
|
-
import { ChevronDown, Check } from 'lucide-react';
|
|
7
|
+
import { ChevronDown, Check, X, Bell, Info, AlertTriangle, XCircle, CheckCircle2 } from 'lucide-react';
|
|
10
8
|
|
|
11
9
|
// src/utils/cn.ts
|
|
12
10
|
var cn = (...classes) => {
|
|
@@ -39,8 +37,8 @@ var FOCUSABLE_SELECTORS = [
|
|
|
39
37
|
'[tabindex]:not([tabindex="-1"])'
|
|
40
38
|
].join(", ");
|
|
41
39
|
function useFocusTrap(containerRef, isActive) {
|
|
42
|
-
const previousActiveElement =
|
|
43
|
-
|
|
40
|
+
const previousActiveElement = React6.useRef(null);
|
|
41
|
+
React6.useEffect(() => {
|
|
44
42
|
if (!isActive) return;
|
|
45
43
|
const container = containerRef.current;
|
|
46
44
|
if (!container) return;
|
|
@@ -260,33 +258,101 @@ var TopLoading = ({
|
|
|
260
258
|
}
|
|
261
259
|
);
|
|
262
260
|
};
|
|
263
|
-
var
|
|
264
|
-
|
|
265
|
-
|
|
261
|
+
var ToastContext = React6.createContext(null);
|
|
262
|
+
var VARIANT_ICONS = {
|
|
263
|
+
success: /* @__PURE__ */ jsx(CheckCircle2, { size: 18 }),
|
|
264
|
+
error: /* @__PURE__ */ jsx(XCircle, { size: 18 }),
|
|
265
|
+
warning: /* @__PURE__ */ jsx(AlertTriangle, { size: 18 }),
|
|
266
|
+
info: /* @__PURE__ */ jsx(Info, { size: 18 }),
|
|
267
|
+
default: /* @__PURE__ */ jsx(Bell, { size: 18 })
|
|
268
|
+
};
|
|
269
|
+
var ToastItemComponent = ({ item, onRemove }) => {
|
|
270
|
+
const [exiting, setExiting] = React6.useState(false);
|
|
271
|
+
const closingRef = React6.useRef(false);
|
|
272
|
+
const close = React6.useCallback(() => {
|
|
273
|
+
if (closingRef.current) return;
|
|
274
|
+
closingRef.current = true;
|
|
275
|
+
setExiting(true);
|
|
276
|
+
setTimeout(() => onRemove(item.id), 260);
|
|
277
|
+
}, [item.id, onRemove]);
|
|
278
|
+
const itemClassName = [
|
|
279
|
+
"toast_item",
|
|
280
|
+
exiting && "toast_item_exiting"
|
|
281
|
+
].filter(Boolean).join(" ");
|
|
282
|
+
return /* @__PURE__ */ jsxs(
|
|
283
|
+
"div",
|
|
266
284
|
{
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
285
|
+
className: itemClassName,
|
|
286
|
+
role: "alert",
|
|
287
|
+
children: [
|
|
288
|
+
/* @__PURE__ */ jsx("span", { className: `toast_icon toast_icon_${item.variant}`, "aria-hidden": "true", children: VARIANT_ICONS[item.variant] }),
|
|
289
|
+
/* @__PURE__ */ jsx("span", { className: "toast_message", children: item.message }),
|
|
290
|
+
/* @__PURE__ */ jsx(
|
|
291
|
+
"button",
|
|
292
|
+
{
|
|
293
|
+
type: "button",
|
|
294
|
+
className: "toast_close",
|
|
295
|
+
onClick: close,
|
|
296
|
+
"aria-label": "\uB2EB\uAE30",
|
|
297
|
+
children: /* @__PURE__ */ jsx(X, { size: 14 })
|
|
298
|
+
}
|
|
299
|
+
),
|
|
300
|
+
/* @__PURE__ */ jsx(
|
|
301
|
+
"div",
|
|
302
|
+
{
|
|
303
|
+
className: `toast_progress toast_progress_${item.variant}`,
|
|
304
|
+
style: { "--toast-duration": `${item.duration}ms` },
|
|
305
|
+
onAnimationEnd: close,
|
|
306
|
+
"aria-hidden": "true"
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
]
|
|
279
310
|
}
|
|
280
311
|
);
|
|
281
312
|
};
|
|
282
|
-
var
|
|
283
|
-
const
|
|
313
|
+
var ToastProvider = ({ children, maxCount = 5 }) => {
|
|
314
|
+
const [toasts, setToasts] = React6.useState([]);
|
|
315
|
+
const addToast = React6.useCallback(
|
|
316
|
+
(message, variant, duration = 3e3) => {
|
|
317
|
+
const id = crypto.randomUUID();
|
|
318
|
+
setToasts((prev) => [{ id, message, variant, duration }, ...prev].slice(0, maxCount));
|
|
319
|
+
},
|
|
320
|
+
[maxCount]
|
|
321
|
+
);
|
|
322
|
+
const removeToast = React6.useCallback((id) => {
|
|
323
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
324
|
+
}, []);
|
|
325
|
+
return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: { addToast }, children: [
|
|
326
|
+
children,
|
|
327
|
+
typeof document !== "undefined" && createPortal(
|
|
328
|
+
/* @__PURE__ */ jsx("div", { className: "toast_container", children: toasts.map((item) => /* @__PURE__ */ jsx(
|
|
329
|
+
ToastItemComponent,
|
|
330
|
+
{
|
|
331
|
+
item,
|
|
332
|
+
onRemove: removeToast
|
|
333
|
+
},
|
|
334
|
+
item.id
|
|
335
|
+
)) }),
|
|
336
|
+
document.body
|
|
337
|
+
)
|
|
338
|
+
] });
|
|
339
|
+
};
|
|
340
|
+
var useToast = () => {
|
|
341
|
+
const ctx = useContext(ToastContext);
|
|
342
|
+
if (!ctx) {
|
|
343
|
+
throw new Error("useToast must be used within ToastProvider");
|
|
344
|
+
}
|
|
284
345
|
return {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
346
|
+
/** 성공 메시지를 표시한다 */
|
|
347
|
+
success: (message, duration) => ctx.addToast(message, "success", duration),
|
|
348
|
+
/** 오류 메시지를 표시한다 */
|
|
349
|
+
error: (message, duration) => ctx.addToast(message, "error", duration),
|
|
350
|
+
/** 경고 메시지를 표시한다 */
|
|
351
|
+
warning: (message, duration) => ctx.addToast(message, "warning", duration),
|
|
352
|
+
/** 정보 메시지를 표시한다 */
|
|
353
|
+
info: (message, duration) => ctx.addToast(message, "info", duration),
|
|
354
|
+
/** 기본 메시지를 표시한다 */
|
|
355
|
+
message: (message, duration) => ctx.addToast(message, "default", duration)
|
|
290
356
|
};
|
|
291
357
|
};
|
|
292
358
|
var Button = ({
|
|
@@ -308,11 +374,11 @@ var Button = ({
|
|
|
308
374
|
const buttonStyle = width ? { ...style, width } : style;
|
|
309
375
|
return /* @__PURE__ */ jsx("button", { className: buttonClassName, style: buttonStyle, ...props });
|
|
310
376
|
};
|
|
311
|
-
var Checkbox =
|
|
377
|
+
var Checkbox = React6.forwardRef(
|
|
312
378
|
({ label, size = "md", indeterminate, className, ...props }, ref) => {
|
|
313
|
-
const inputRef =
|
|
314
|
-
|
|
315
|
-
|
|
379
|
+
const inputRef = React6.useRef(null);
|
|
380
|
+
React6.useImperativeHandle(ref, () => inputRef.current);
|
|
381
|
+
React6.useEffect(() => {
|
|
316
382
|
if (!inputRef.current) return;
|
|
317
383
|
inputRef.current.indeterminate = Boolean(indeterminate);
|
|
318
384
|
}, [indeterminate]);
|
|
@@ -344,7 +410,7 @@ var FileInput = ({
|
|
|
344
410
|
disabled,
|
|
345
411
|
...props
|
|
346
412
|
}) => {
|
|
347
|
-
const inputId =
|
|
413
|
+
const inputId = React6.useId();
|
|
348
414
|
const rootClassName = [
|
|
349
415
|
"file_input",
|
|
350
416
|
disabled && "file_input_disabled",
|
|
@@ -365,7 +431,7 @@ var FileInput = ({
|
|
|
365
431
|
/* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "file_input_label", children: label })
|
|
366
432
|
] });
|
|
367
433
|
};
|
|
368
|
-
var Radio =
|
|
434
|
+
var Radio = React6.forwardRef(
|
|
369
435
|
({ label, size = "md", className, ...props }, ref) => {
|
|
370
436
|
const rootClassName = cn(
|
|
371
437
|
"radio",
|
|
@@ -395,21 +461,21 @@ var Select = ({
|
|
|
395
461
|
className,
|
|
396
462
|
textAlign = "left"
|
|
397
463
|
}) => {
|
|
398
|
-
const internalId =
|
|
464
|
+
const internalId = React6.useId();
|
|
399
465
|
const selectId = id ?? internalId;
|
|
400
466
|
const isControlled = value !== void 0;
|
|
401
|
-
const [internalValue, setInternalValue] =
|
|
467
|
+
const [internalValue, setInternalValue] = React6.useState(defaultValue);
|
|
402
468
|
const currentValue = isControlled ? value ?? null : internalValue;
|
|
403
|
-
const [isOpen, setIsOpen] =
|
|
404
|
-
const [activeIndex, setActiveIndex] =
|
|
405
|
-
const [dropUp, setDropUp] =
|
|
406
|
-
const wrapperRef =
|
|
407
|
-
const controlRef =
|
|
408
|
-
const currentOption =
|
|
469
|
+
const [isOpen, setIsOpen] = React6.useState(false);
|
|
470
|
+
const [activeIndex, setActiveIndex] = React6.useState(-1);
|
|
471
|
+
const [dropUp, setDropUp] = React6.useState(false);
|
|
472
|
+
const wrapperRef = React6.useRef(null);
|
|
473
|
+
const controlRef = React6.useRef(null);
|
|
474
|
+
const currentOption = React6.useMemo(
|
|
409
475
|
() => options.find((o) => o.value === currentValue) ?? null,
|
|
410
476
|
[options, currentValue]
|
|
411
477
|
);
|
|
412
|
-
const setValue =
|
|
478
|
+
const setValue = React6.useCallback(
|
|
413
479
|
(next) => {
|
|
414
480
|
const option = options.find((o) => o.value === next) ?? null;
|
|
415
481
|
if (!isControlled) setInternalValue(next);
|
|
@@ -417,12 +483,12 @@ var Select = ({
|
|
|
417
483
|
},
|
|
418
484
|
[isControlled, onChange, options]
|
|
419
485
|
);
|
|
420
|
-
const handleOutsideClick =
|
|
486
|
+
const handleOutsideClick = React6.useEffectEvent((e) => {
|
|
421
487
|
if (!wrapperRef.current?.contains(e.target)) {
|
|
422
488
|
setIsOpen(false);
|
|
423
489
|
}
|
|
424
490
|
});
|
|
425
|
-
|
|
491
|
+
React6.useEffect(() => {
|
|
426
492
|
document.addEventListener("mousedown", handleOutsideClick);
|
|
427
493
|
return () => document.removeEventListener("mousedown", handleOutsideClick);
|
|
428
494
|
}, []);
|
|
@@ -487,12 +553,12 @@ var Select = ({
|
|
|
487
553
|
break;
|
|
488
554
|
}
|
|
489
555
|
};
|
|
490
|
-
|
|
556
|
+
React6.useEffect(() => {
|
|
491
557
|
if (!isOpen) return;
|
|
492
558
|
const idx = options.findIndex((o) => o.value === currentValue && !o.disabled);
|
|
493
559
|
setActiveIndex(idx >= 0 ? idx : Math.max(0, options.findIndex((o) => !o.disabled)));
|
|
494
560
|
}, [isOpen, options, currentValue]);
|
|
495
|
-
|
|
561
|
+
React6.useLayoutEffect(() => {
|
|
496
562
|
if (!isOpen || !controlRef.current) return;
|
|
497
563
|
const rect = controlRef.current.getBoundingClientRect();
|
|
498
564
|
const listHeight = Math.min(options.length * 40, 288);
|
|
@@ -554,6 +620,7 @@ var Select = ({
|
|
|
554
620
|
{
|
|
555
621
|
role: "option",
|
|
556
622
|
"aria-selected": selected,
|
|
623
|
+
"aria-disabled": opt.disabled ? true : void 0,
|
|
557
624
|
className: optionClassName,
|
|
558
625
|
onMouseEnter: () => !opt.disabled && setActiveIndex(i),
|
|
559
626
|
onClick: () => {
|
|
@@ -573,7 +640,7 @@ var Select = ({
|
|
|
573
640
|
)
|
|
574
641
|
] });
|
|
575
642
|
};
|
|
576
|
-
var Switch =
|
|
643
|
+
var Switch = React6.forwardRef(
|
|
577
644
|
({
|
|
578
645
|
checked,
|
|
579
646
|
defaultChecked,
|
|
@@ -585,7 +652,7 @@ var Switch = React5.forwardRef(
|
|
|
585
652
|
...props
|
|
586
653
|
}, ref) => {
|
|
587
654
|
const isControlled = checked !== void 0;
|
|
588
|
-
const [innerChecked, setInnerChecked] =
|
|
655
|
+
const [innerChecked, setInnerChecked] = React6.useState(!!defaultChecked);
|
|
589
656
|
const isOn = isControlled ? !!checked : innerChecked;
|
|
590
657
|
const handleToggle = () => {
|
|
591
658
|
if (disabled) return;
|
|
@@ -617,7 +684,7 @@ var Switch = React5.forwardRef(
|
|
|
617
684
|
}
|
|
618
685
|
);
|
|
619
686
|
Switch.displayName = "Switch";
|
|
620
|
-
var TextField =
|
|
687
|
+
var TextField = React6.forwardRef(
|
|
621
688
|
({
|
|
622
689
|
id,
|
|
623
690
|
label,
|
|
@@ -636,15 +703,15 @@ var TextField = React5.forwardRef(
|
|
|
636
703
|
transformValue,
|
|
637
704
|
...props
|
|
638
705
|
}, ref) => {
|
|
639
|
-
const inputId = id ??
|
|
706
|
+
const inputId = id ?? React6.useId();
|
|
640
707
|
const helperId = helperText ? `${inputId}-help` : void 0;
|
|
641
708
|
const isControlled = value !== void 0;
|
|
642
709
|
const applyTransform = (nextValue) => transformValue ? transformValue(nextValue) : nextValue;
|
|
643
|
-
const [innerValue, setInnerValue] =
|
|
710
|
+
const [innerValue, setInnerValue] = React6.useState(
|
|
644
711
|
() => applyTransform(value ?? defaultValue ?? "")
|
|
645
712
|
);
|
|
646
|
-
const isComposingRef =
|
|
647
|
-
|
|
713
|
+
const isComposingRef = React6.useRef(false);
|
|
714
|
+
React6.useEffect(() => {
|
|
648
715
|
if (!isControlled) return;
|
|
649
716
|
setInnerValue(applyTransform(value ?? ""));
|
|
650
717
|
}, [isControlled, value, transformValue]);
|
|
@@ -674,7 +741,7 @@ var TextField = React5.forwardRef(
|
|
|
674
741
|
return /* @__PURE__ */ jsxs("div", { className: rootClassName, children: [
|
|
675
742
|
label ? /* @__PURE__ */ jsx("label", { className: "text_field_label", htmlFor: inputId, children: label }) : null,
|
|
676
743
|
/* @__PURE__ */ jsxs("div", { className: "text_field_wrap", children: [
|
|
677
|
-
leftIcon ? /* @__PURE__ */ jsx("span", { className: "text_field_icon text_field_icon_left", children: leftIcon }) : null,
|
|
744
|
+
leftIcon ? /* @__PURE__ */ jsx("span", { className: "text_field_icon text_field_icon_left", "aria-hidden": "true", children: leftIcon }) : null,
|
|
678
745
|
/* @__PURE__ */ jsx(
|
|
679
746
|
"input",
|
|
680
747
|
{
|
|
@@ -707,7 +774,7 @@ var TextField = React5.forwardRef(
|
|
|
707
774
|
}
|
|
708
775
|
}
|
|
709
776
|
),
|
|
710
|
-
rightIcon ? /* @__PURE__ */ jsx("span", { className: "text_field_icon text_field_icon_right", children: rightIcon }) : null
|
|
777
|
+
rightIcon ? /* @__PURE__ */ jsx("span", { className: "text_field_icon text_field_icon_right", "aria-hidden": "true", children: rightIcon }) : null
|
|
711
778
|
] }),
|
|
712
779
|
helperText ? /* @__PURE__ */ jsx("div", { id: helperId, className: helperClassName, children: helperText }) : null
|
|
713
780
|
] });
|
|
@@ -762,6 +829,7 @@ var DatePicker = ({
|
|
|
762
829
|
/* @__PURE__ */ jsxs(
|
|
763
830
|
"select",
|
|
764
831
|
{
|
|
832
|
+
"aria-label": "\uC5F0\uB3C4",
|
|
765
833
|
value: year,
|
|
766
834
|
disabled,
|
|
767
835
|
onChange: (e) => emit(Number(e.target.value), month || minMonth, day || minDay),
|
|
@@ -777,6 +845,7 @@ var DatePicker = ({
|
|
|
777
845
|
/* @__PURE__ */ jsxs(
|
|
778
846
|
"select",
|
|
779
847
|
{
|
|
848
|
+
"aria-label": "\uC6D4",
|
|
780
849
|
value: month,
|
|
781
850
|
disabled: disabled || !year,
|
|
782
851
|
onChange: (e) => emit(year, Number(e.target.value), day || minDay),
|
|
@@ -791,6 +860,7 @@ var DatePicker = ({
|
|
|
791
860
|
mode === "year-month-day" && /* @__PURE__ */ jsxs(
|
|
792
861
|
"select",
|
|
793
862
|
{
|
|
863
|
+
"aria-label": "\uC77C",
|
|
794
864
|
value: day,
|
|
795
865
|
disabled: disabled || !month,
|
|
796
866
|
onChange: (e) => emit(year, month, Number(e.target.value)),
|
|
@@ -838,7 +908,7 @@ var getPaginationItems = (page, totalPages) => {
|
|
|
838
908
|
var Pagination = ({ page, totalPages, onChange }) => {
|
|
839
909
|
const prevDisabled = page <= 1;
|
|
840
910
|
const nextDisabled = page >= totalPages;
|
|
841
|
-
const items =
|
|
911
|
+
const items = React6.useMemo(
|
|
842
912
|
() => getPaginationItems(page, totalPages),
|
|
843
913
|
[page, totalPages]
|
|
844
914
|
);
|
|
@@ -862,7 +932,7 @@ var Pagination = ({ page, totalPages, onChange }) => {
|
|
|
862
932
|
"pagination_page_button",
|
|
863
933
|
{ pagination_active: isActive }
|
|
864
934
|
);
|
|
865
|
-
return /* @__PURE__ */ jsx(
|
|
935
|
+
return /* @__PURE__ */ jsx("span", { role: "listitem", children: /* @__PURE__ */ jsx(
|
|
866
936
|
"button",
|
|
867
937
|
{
|
|
868
938
|
type: "button",
|
|
@@ -870,9 +940,8 @@ var Pagination = ({ page, totalPages, onChange }) => {
|
|
|
870
940
|
onClick: () => onChange(it),
|
|
871
941
|
"aria-current": isActive ? "page" : void 0,
|
|
872
942
|
children: it
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
);
|
|
943
|
+
}
|
|
944
|
+
) }, it);
|
|
876
945
|
}) }),
|
|
877
946
|
/* @__PURE__ */ jsx(
|
|
878
947
|
"button",
|
|
@@ -897,17 +966,18 @@ var Modal = ({
|
|
|
897
966
|
ariaLabel,
|
|
898
967
|
...props
|
|
899
968
|
}) => {
|
|
900
|
-
const panelRef =
|
|
969
|
+
const panelRef = React6.useRef(null);
|
|
970
|
+
const titleId = React6.useId();
|
|
901
971
|
useFocusTrap(panelRef, open);
|
|
902
|
-
const handleEscape =
|
|
972
|
+
const handleEscape = React6.useEffectEvent((e) => {
|
|
903
973
|
if (e.key === "Escape") onClose?.();
|
|
904
974
|
});
|
|
905
|
-
|
|
975
|
+
React6.useEffect(() => {
|
|
906
976
|
if (!open) return;
|
|
907
977
|
document.addEventListener("keydown", handleEscape);
|
|
908
978
|
return () => document.removeEventListener("keydown", handleEscape);
|
|
909
979
|
}, [open]);
|
|
910
|
-
|
|
980
|
+
React6.useEffect(() => {
|
|
911
981
|
if (!open) return;
|
|
912
982
|
const body = document.body;
|
|
913
983
|
const openModals = parseInt(body.dataset.openModals || "0", 10);
|
|
@@ -930,14 +1000,15 @@ var Modal = ({
|
|
|
930
1000
|
}, [open]);
|
|
931
1001
|
if (!open) return null;
|
|
932
1002
|
const panelClassName = cn("modal_panel", className);
|
|
933
|
-
const
|
|
1003
|
+
const hasTitle = !!title;
|
|
934
1004
|
return /* @__PURE__ */ jsx(
|
|
935
1005
|
"div",
|
|
936
1006
|
{
|
|
937
1007
|
className: "modal",
|
|
938
1008
|
role: "dialog",
|
|
939
1009
|
"aria-modal": "true",
|
|
940
|
-
"aria-
|
|
1010
|
+
"aria-labelledby": hasTitle && !ariaLabel ? titleId : void 0,
|
|
1011
|
+
"aria-label": !hasTitle ? ariaLabel ?? "Dialog" : ariaLabel,
|
|
941
1012
|
onClick: () => closeOnOverlay && onClose?.(),
|
|
942
1013
|
children: /* @__PURE__ */ jsxs(
|
|
943
1014
|
"div",
|
|
@@ -948,7 +1019,7 @@ var Modal = ({
|
|
|
948
1019
|
onClick: (e) => e.stopPropagation(),
|
|
949
1020
|
...props,
|
|
950
1021
|
children: [
|
|
951
|
-
title && /* @__PURE__ */ jsx("div", { className: "modal_header", children: title }),
|
|
1022
|
+
title && /* @__PURE__ */ jsx("div", { id: titleId, className: "modal_header", children: title }),
|
|
952
1023
|
/* @__PURE__ */ jsx("div", { className: "modal_body", children })
|
|
953
1024
|
]
|
|
954
1025
|
}
|
package/dist/next.js
CHANGED
|
@@ -51,29 +51,38 @@ var Sidebar = ({
|
|
|
51
51
|
className: sidebarClassName,
|
|
52
52
|
style,
|
|
53
53
|
children: isOpen ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
54
|
-
/* @__PURE__ */
|
|
55
|
-
/* @__PURE__ */
|
|
54
|
+
/* @__PURE__ */ jsxs("div", { className: "sidebar_brand", children: [
|
|
55
|
+
/* @__PURE__ */ jsxs(Link, { href: brandHref, className: "sidebar_brand_link", children: [
|
|
56
|
+
/* @__PURE__ */ jsx("div", {}),
|
|
57
|
+
/* @__PURE__ */ jsx(
|
|
58
|
+
Image,
|
|
59
|
+
{
|
|
60
|
+
src: "/images/logo/bigtablet.png",
|
|
61
|
+
alt: "Bigtablet",
|
|
62
|
+
width: 96,
|
|
63
|
+
height: 30,
|
|
64
|
+
priority: true
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
] }),
|
|
56
68
|
/* @__PURE__ */ jsx(
|
|
57
|
-
|
|
69
|
+
"button",
|
|
58
70
|
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
width: 24,
|
|
72
|
-
height: 24,
|
|
73
|
-
onClick: () => toggleSidebar(false)
|
|
71
|
+
type: "button",
|
|
72
|
+
className: "sidebar_close_btn",
|
|
73
|
+
onClick: () => toggleSidebar(false),
|
|
74
|
+
children: /* @__PURE__ */ jsx(
|
|
75
|
+
Image,
|
|
76
|
+
{
|
|
77
|
+
src: "/images/sidebar/arrow-close.svg",
|
|
78
|
+
alt: "Close",
|
|
79
|
+
width: 24,
|
|
80
|
+
height: 24
|
|
81
|
+
}
|
|
82
|
+
)
|
|
74
83
|
}
|
|
75
84
|
)
|
|
76
|
-
] })
|
|
85
|
+
] }),
|
|
77
86
|
/* @__PURE__ */ jsx("nav", { className: "sidebar_nav", children: items.map((item) => {
|
|
78
87
|
if (item.type === "group") {
|
|
79
88
|
const open = openGroups.includes(item.id);
|
|
@@ -91,13 +100,14 @@ var Sidebar = ({
|
|
|
91
100
|
{
|
|
92
101
|
type: "button",
|
|
93
102
|
className: "sidebar_item",
|
|
103
|
+
"aria-expanded": open,
|
|
94
104
|
onClick: () => toggleGroup(item.id),
|
|
95
105
|
children: [
|
|
96
106
|
/* @__PURE__ */ jsxs("div", { className: "sidebar_item_left", children: [
|
|
97
|
-
item.icon && /* @__PURE__ */ jsx("span", { className: "sidebar_icon", children: /* @__PURE__ */ jsx(item.icon, { size: 16 }) }),
|
|
107
|
+
item.icon && /* @__PURE__ */ jsx("span", { className: "sidebar_icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(item.icon, { size: 16 }) }),
|
|
98
108
|
/* @__PURE__ */ jsx("span", { className: "sidebar_label", children: item.label })
|
|
99
109
|
] }),
|
|
100
|
-
/* @__PURE__ */ jsx("span", { className: "sidebar_item_right", children: /* @__PURE__ */ jsx(
|
|
110
|
+
/* @__PURE__ */ jsx("span", { className: "sidebar_item_right", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
101
111
|
ChevronDown,
|
|
102
112
|
{
|
|
103
113
|
size: 16,
|
|
@@ -124,7 +134,7 @@ var Sidebar = ({
|
|
|
124
134
|
child.href
|
|
125
135
|
),
|
|
126
136
|
children: [
|
|
127
|
-
/* @__PURE__ */ jsx("span", { className: "sidebar_sub_icon", children: /* @__PURE__ */ jsx(
|
|
137
|
+
/* @__PURE__ */ jsx("span", { className: "sidebar_sub_icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
128
138
|
CornerDownRight,
|
|
129
139
|
{
|
|
130
140
|
size: 14
|
|
@@ -150,7 +160,7 @@ var Sidebar = ({
|
|
|
150
160
|
className: itemClassName,
|
|
151
161
|
onClick: () => onItemSelect?.(item.href),
|
|
152
162
|
children: /* @__PURE__ */ jsxs("div", { className: "sidebar_item_left", children: [
|
|
153
|
-
item.icon && /* @__PURE__ */ jsx("span", { className: "sidebar_icon", children: /* @__PURE__ */ jsx(item.icon, { size: 16 }) }),
|
|
163
|
+
item.icon && /* @__PURE__ */ jsx("span", { className: "sidebar_icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(item.icon, { size: 16 }) }),
|
|
154
164
|
/* @__PURE__ */ jsx("span", { className: "sidebar_label", children: item.label })
|
|
155
165
|
] })
|
|
156
166
|
},
|
|
@@ -158,13 +168,20 @@ var Sidebar = ({
|
|
|
158
168
|
);
|
|
159
169
|
}) })
|
|
160
170
|
] }) : /* @__PURE__ */ jsx(
|
|
161
|
-
|
|
171
|
+
"button",
|
|
162
172
|
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
type: "button",
|
|
174
|
+
className: "sidebar_open_btn",
|
|
175
|
+
onClick: () => toggleSidebar(true),
|
|
176
|
+
children: /* @__PURE__ */ jsx(
|
|
177
|
+
Image,
|
|
178
|
+
{
|
|
179
|
+
src: "/images/sidebar/menu.svg",
|
|
180
|
+
alt: "Open",
|
|
181
|
+
width: 24,
|
|
182
|
+
height: 24
|
|
183
|
+
}
|
|
184
|
+
)
|
|
168
185
|
}
|
|
169
186
|
)
|
|
170
187
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bigtablet/design-system",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "Bigtablet Design System UI Components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -68,8 +68,7 @@
|
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"lucide-react": ">=0.552.0",
|
|
70
70
|
"react": "^19",
|
|
71
|
-
"react-dom": "^19"
|
|
72
|
-
"react-toastify": ">=11.0.5"
|
|
71
|
+
"react-dom": "^19"
|
|
73
72
|
},
|
|
74
73
|
"peerDependenciesMeta": {
|
|
75
74
|
"next": {
|
|
@@ -102,10 +101,9 @@
|
|
|
102
101
|
"playwright": "^1.57.0",
|
|
103
102
|
"react": "19.2.0",
|
|
104
103
|
"react-dom": "19.2.0",
|
|
105
|
-
"react-toastify": "^11.0.5",
|
|
106
104
|
"sass-embedded": "^1.93.3",
|
|
107
105
|
"semantic-release": "^25.0.1",
|
|
108
|
-
"storybook": "10.
|
|
106
|
+
"storybook": "10.2.10",
|
|
109
107
|
"tsup": "^8.5.0",
|
|
110
108
|
"typescript": "^5",
|
|
111
109
|
"vite": "^5",
|