@bigtablet/design-system 1.18.9 → 1.19.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.md +27 -4
- package/dist/index.css +135 -0
- package/dist/index.d.ts +25 -14
- package/dist/index.js +143 -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,105 @@ 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 [isMounted, setIsMounted] = React6.useState(false);
|
|
316
|
+
React6.useEffect(() => {
|
|
317
|
+
setIsMounted(true);
|
|
318
|
+
}, []);
|
|
319
|
+
const addToast = React6.useCallback(
|
|
320
|
+
(message, variant, duration = 3e3) => {
|
|
321
|
+
const id = crypto.randomUUID();
|
|
322
|
+
setToasts((prev) => [{ id, message, variant, duration }, ...prev].slice(0, maxCount));
|
|
323
|
+
},
|
|
324
|
+
[maxCount]
|
|
325
|
+
);
|
|
326
|
+
const removeToast = React6.useCallback((id) => {
|
|
327
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
328
|
+
}, []);
|
|
329
|
+
return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: { addToast }, children: [
|
|
330
|
+
children,
|
|
331
|
+
isMounted && createPortal(
|
|
332
|
+
/* @__PURE__ */ jsx("div", { className: "toast_container", children: toasts.map((item) => /* @__PURE__ */ jsx(
|
|
333
|
+
ToastItemComponent,
|
|
334
|
+
{
|
|
335
|
+
item,
|
|
336
|
+
onRemove: removeToast
|
|
337
|
+
},
|
|
338
|
+
item.id
|
|
339
|
+
)) }),
|
|
340
|
+
document.body
|
|
341
|
+
)
|
|
342
|
+
] });
|
|
343
|
+
};
|
|
344
|
+
var useToast = () => {
|
|
345
|
+
const ctx = useContext(ToastContext);
|
|
346
|
+
if (!ctx) {
|
|
347
|
+
throw new Error("useToast must be used within ToastProvider");
|
|
348
|
+
}
|
|
284
349
|
return {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
350
|
+
/** 성공 메시지를 표시한다 */
|
|
351
|
+
success: (message, duration) => ctx.addToast(message, "success", duration),
|
|
352
|
+
/** 오류 메시지를 표시한다 */
|
|
353
|
+
error: (message, duration) => ctx.addToast(message, "error", duration),
|
|
354
|
+
/** 경고 메시지를 표시한다 */
|
|
355
|
+
warning: (message, duration) => ctx.addToast(message, "warning", duration),
|
|
356
|
+
/** 정보 메시지를 표시한다 */
|
|
357
|
+
info: (message, duration) => ctx.addToast(message, "info", duration),
|
|
358
|
+
/** 기본 메시지를 표시한다 */
|
|
359
|
+
message: (message, duration) => ctx.addToast(message, "default", duration)
|
|
290
360
|
};
|
|
291
361
|
};
|
|
292
362
|
var Button = ({
|
|
@@ -308,11 +378,11 @@ var Button = ({
|
|
|
308
378
|
const buttonStyle = width ? { ...style, width } : style;
|
|
309
379
|
return /* @__PURE__ */ jsx("button", { className: buttonClassName, style: buttonStyle, ...props });
|
|
310
380
|
};
|
|
311
|
-
var Checkbox =
|
|
381
|
+
var Checkbox = React6.forwardRef(
|
|
312
382
|
({ label, size = "md", indeterminate, className, ...props }, ref) => {
|
|
313
|
-
const inputRef =
|
|
314
|
-
|
|
315
|
-
|
|
383
|
+
const inputRef = React6.useRef(null);
|
|
384
|
+
React6.useImperativeHandle(ref, () => inputRef.current);
|
|
385
|
+
React6.useEffect(() => {
|
|
316
386
|
if (!inputRef.current) return;
|
|
317
387
|
inputRef.current.indeterminate = Boolean(indeterminate);
|
|
318
388
|
}, [indeterminate]);
|
|
@@ -344,7 +414,7 @@ var FileInput = ({
|
|
|
344
414
|
disabled,
|
|
345
415
|
...props
|
|
346
416
|
}) => {
|
|
347
|
-
const inputId =
|
|
417
|
+
const inputId = React6.useId();
|
|
348
418
|
const rootClassName = [
|
|
349
419
|
"file_input",
|
|
350
420
|
disabled && "file_input_disabled",
|
|
@@ -365,7 +435,7 @@ var FileInput = ({
|
|
|
365
435
|
/* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "file_input_label", children: label })
|
|
366
436
|
] });
|
|
367
437
|
};
|
|
368
|
-
var Radio =
|
|
438
|
+
var Radio = React6.forwardRef(
|
|
369
439
|
({ label, size = "md", className, ...props }, ref) => {
|
|
370
440
|
const rootClassName = cn(
|
|
371
441
|
"radio",
|
|
@@ -395,21 +465,21 @@ var Select = ({
|
|
|
395
465
|
className,
|
|
396
466
|
textAlign = "left"
|
|
397
467
|
}) => {
|
|
398
|
-
const internalId =
|
|
468
|
+
const internalId = React6.useId();
|
|
399
469
|
const selectId = id ?? internalId;
|
|
400
470
|
const isControlled = value !== void 0;
|
|
401
|
-
const [internalValue, setInternalValue] =
|
|
471
|
+
const [internalValue, setInternalValue] = React6.useState(defaultValue);
|
|
402
472
|
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 =
|
|
473
|
+
const [isOpen, setIsOpen] = React6.useState(false);
|
|
474
|
+
const [activeIndex, setActiveIndex] = React6.useState(-1);
|
|
475
|
+
const [dropUp, setDropUp] = React6.useState(false);
|
|
476
|
+
const wrapperRef = React6.useRef(null);
|
|
477
|
+
const controlRef = React6.useRef(null);
|
|
478
|
+
const currentOption = React6.useMemo(
|
|
409
479
|
() => options.find((o) => o.value === currentValue) ?? null,
|
|
410
480
|
[options, currentValue]
|
|
411
481
|
);
|
|
412
|
-
const setValue =
|
|
482
|
+
const setValue = React6.useCallback(
|
|
413
483
|
(next) => {
|
|
414
484
|
const option = options.find((o) => o.value === next) ?? null;
|
|
415
485
|
if (!isControlled) setInternalValue(next);
|
|
@@ -417,12 +487,12 @@ var Select = ({
|
|
|
417
487
|
},
|
|
418
488
|
[isControlled, onChange, options]
|
|
419
489
|
);
|
|
420
|
-
const handleOutsideClick =
|
|
490
|
+
const handleOutsideClick = React6.useEffectEvent((e) => {
|
|
421
491
|
if (!wrapperRef.current?.contains(e.target)) {
|
|
422
492
|
setIsOpen(false);
|
|
423
493
|
}
|
|
424
494
|
});
|
|
425
|
-
|
|
495
|
+
React6.useEffect(() => {
|
|
426
496
|
document.addEventListener("mousedown", handleOutsideClick);
|
|
427
497
|
return () => document.removeEventListener("mousedown", handleOutsideClick);
|
|
428
498
|
}, []);
|
|
@@ -487,12 +557,12 @@ var Select = ({
|
|
|
487
557
|
break;
|
|
488
558
|
}
|
|
489
559
|
};
|
|
490
|
-
|
|
560
|
+
React6.useEffect(() => {
|
|
491
561
|
if (!isOpen) return;
|
|
492
562
|
const idx = options.findIndex((o) => o.value === currentValue && !o.disabled);
|
|
493
563
|
setActiveIndex(idx >= 0 ? idx : Math.max(0, options.findIndex((o) => !o.disabled)));
|
|
494
564
|
}, [isOpen, options, currentValue]);
|
|
495
|
-
|
|
565
|
+
React6.useLayoutEffect(() => {
|
|
496
566
|
if (!isOpen || !controlRef.current) return;
|
|
497
567
|
const rect = controlRef.current.getBoundingClientRect();
|
|
498
568
|
const listHeight = Math.min(options.length * 40, 288);
|
|
@@ -554,6 +624,7 @@ var Select = ({
|
|
|
554
624
|
{
|
|
555
625
|
role: "option",
|
|
556
626
|
"aria-selected": selected,
|
|
627
|
+
"aria-disabled": opt.disabled ? true : void 0,
|
|
557
628
|
className: optionClassName,
|
|
558
629
|
onMouseEnter: () => !opt.disabled && setActiveIndex(i),
|
|
559
630
|
onClick: () => {
|
|
@@ -573,7 +644,7 @@ var Select = ({
|
|
|
573
644
|
)
|
|
574
645
|
] });
|
|
575
646
|
};
|
|
576
|
-
var Switch =
|
|
647
|
+
var Switch = React6.forwardRef(
|
|
577
648
|
({
|
|
578
649
|
checked,
|
|
579
650
|
defaultChecked,
|
|
@@ -585,7 +656,7 @@ var Switch = React5.forwardRef(
|
|
|
585
656
|
...props
|
|
586
657
|
}, ref) => {
|
|
587
658
|
const isControlled = checked !== void 0;
|
|
588
|
-
const [innerChecked, setInnerChecked] =
|
|
659
|
+
const [innerChecked, setInnerChecked] = React6.useState(!!defaultChecked);
|
|
589
660
|
const isOn = isControlled ? !!checked : innerChecked;
|
|
590
661
|
const handleToggle = () => {
|
|
591
662
|
if (disabled) return;
|
|
@@ -617,7 +688,7 @@ var Switch = React5.forwardRef(
|
|
|
617
688
|
}
|
|
618
689
|
);
|
|
619
690
|
Switch.displayName = "Switch";
|
|
620
|
-
var TextField =
|
|
691
|
+
var TextField = React6.forwardRef(
|
|
621
692
|
({
|
|
622
693
|
id,
|
|
623
694
|
label,
|
|
@@ -636,15 +707,15 @@ var TextField = React5.forwardRef(
|
|
|
636
707
|
transformValue,
|
|
637
708
|
...props
|
|
638
709
|
}, ref) => {
|
|
639
|
-
const inputId = id ??
|
|
710
|
+
const inputId = id ?? React6.useId();
|
|
640
711
|
const helperId = helperText ? `${inputId}-help` : void 0;
|
|
641
712
|
const isControlled = value !== void 0;
|
|
642
713
|
const applyTransform = (nextValue) => transformValue ? transformValue(nextValue) : nextValue;
|
|
643
|
-
const [innerValue, setInnerValue] =
|
|
714
|
+
const [innerValue, setInnerValue] = React6.useState(
|
|
644
715
|
() => applyTransform(value ?? defaultValue ?? "")
|
|
645
716
|
);
|
|
646
|
-
const isComposingRef =
|
|
647
|
-
|
|
717
|
+
const isComposingRef = React6.useRef(false);
|
|
718
|
+
React6.useEffect(() => {
|
|
648
719
|
if (!isControlled) return;
|
|
649
720
|
setInnerValue(applyTransform(value ?? ""));
|
|
650
721
|
}, [isControlled, value, transformValue]);
|
|
@@ -674,7 +745,7 @@ var TextField = React5.forwardRef(
|
|
|
674
745
|
return /* @__PURE__ */ jsxs("div", { className: rootClassName, children: [
|
|
675
746
|
label ? /* @__PURE__ */ jsx("label", { className: "text_field_label", htmlFor: inputId, children: label }) : null,
|
|
676
747
|
/* @__PURE__ */ jsxs("div", { className: "text_field_wrap", children: [
|
|
677
|
-
leftIcon ? /* @__PURE__ */ jsx("span", { className: "text_field_icon text_field_icon_left", children: leftIcon }) : null,
|
|
748
|
+
leftIcon ? /* @__PURE__ */ jsx("span", { className: "text_field_icon text_field_icon_left", "aria-hidden": "true", children: leftIcon }) : null,
|
|
678
749
|
/* @__PURE__ */ jsx(
|
|
679
750
|
"input",
|
|
680
751
|
{
|
|
@@ -707,7 +778,7 @@ var TextField = React5.forwardRef(
|
|
|
707
778
|
}
|
|
708
779
|
}
|
|
709
780
|
),
|
|
710
|
-
rightIcon ? /* @__PURE__ */ jsx("span", { className: "text_field_icon text_field_icon_right", children: rightIcon }) : null
|
|
781
|
+
rightIcon ? /* @__PURE__ */ jsx("span", { className: "text_field_icon text_field_icon_right", "aria-hidden": "true", children: rightIcon }) : null
|
|
711
782
|
] }),
|
|
712
783
|
helperText ? /* @__PURE__ */ jsx("div", { id: helperId, className: helperClassName, children: helperText }) : null
|
|
713
784
|
] });
|
|
@@ -762,6 +833,7 @@ var DatePicker = ({
|
|
|
762
833
|
/* @__PURE__ */ jsxs(
|
|
763
834
|
"select",
|
|
764
835
|
{
|
|
836
|
+
"aria-label": "\uC5F0\uB3C4",
|
|
765
837
|
value: year,
|
|
766
838
|
disabled,
|
|
767
839
|
onChange: (e) => emit(Number(e.target.value), month || minMonth, day || minDay),
|
|
@@ -777,6 +849,7 @@ var DatePicker = ({
|
|
|
777
849
|
/* @__PURE__ */ jsxs(
|
|
778
850
|
"select",
|
|
779
851
|
{
|
|
852
|
+
"aria-label": "\uC6D4",
|
|
780
853
|
value: month,
|
|
781
854
|
disabled: disabled || !year,
|
|
782
855
|
onChange: (e) => emit(year, Number(e.target.value), day || minDay),
|
|
@@ -791,6 +864,7 @@ var DatePicker = ({
|
|
|
791
864
|
mode === "year-month-day" && /* @__PURE__ */ jsxs(
|
|
792
865
|
"select",
|
|
793
866
|
{
|
|
867
|
+
"aria-label": "\uC77C",
|
|
794
868
|
value: day,
|
|
795
869
|
disabled: disabled || !month,
|
|
796
870
|
onChange: (e) => emit(year, month, Number(e.target.value)),
|
|
@@ -838,7 +912,7 @@ var getPaginationItems = (page, totalPages) => {
|
|
|
838
912
|
var Pagination = ({ page, totalPages, onChange }) => {
|
|
839
913
|
const prevDisabled = page <= 1;
|
|
840
914
|
const nextDisabled = page >= totalPages;
|
|
841
|
-
const items =
|
|
915
|
+
const items = React6.useMemo(
|
|
842
916
|
() => getPaginationItems(page, totalPages),
|
|
843
917
|
[page, totalPages]
|
|
844
918
|
);
|
|
@@ -862,7 +936,7 @@ var Pagination = ({ page, totalPages, onChange }) => {
|
|
|
862
936
|
"pagination_page_button",
|
|
863
937
|
{ pagination_active: isActive }
|
|
864
938
|
);
|
|
865
|
-
return /* @__PURE__ */ jsx(
|
|
939
|
+
return /* @__PURE__ */ jsx("span", { role: "listitem", children: /* @__PURE__ */ jsx(
|
|
866
940
|
"button",
|
|
867
941
|
{
|
|
868
942
|
type: "button",
|
|
@@ -870,9 +944,8 @@ var Pagination = ({ page, totalPages, onChange }) => {
|
|
|
870
944
|
onClick: () => onChange(it),
|
|
871
945
|
"aria-current": isActive ? "page" : void 0,
|
|
872
946
|
children: it
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
);
|
|
947
|
+
}
|
|
948
|
+
) }, it);
|
|
876
949
|
}) }),
|
|
877
950
|
/* @__PURE__ */ jsx(
|
|
878
951
|
"button",
|
|
@@ -897,17 +970,18 @@ var Modal = ({
|
|
|
897
970
|
ariaLabel,
|
|
898
971
|
...props
|
|
899
972
|
}) => {
|
|
900
|
-
const panelRef =
|
|
973
|
+
const panelRef = React6.useRef(null);
|
|
974
|
+
const titleId = React6.useId();
|
|
901
975
|
useFocusTrap(panelRef, open);
|
|
902
|
-
const handleEscape =
|
|
976
|
+
const handleEscape = React6.useEffectEvent((e) => {
|
|
903
977
|
if (e.key === "Escape") onClose?.();
|
|
904
978
|
});
|
|
905
|
-
|
|
979
|
+
React6.useEffect(() => {
|
|
906
980
|
if (!open) return;
|
|
907
981
|
document.addEventListener("keydown", handleEscape);
|
|
908
982
|
return () => document.removeEventListener("keydown", handleEscape);
|
|
909
983
|
}, [open]);
|
|
910
|
-
|
|
984
|
+
React6.useEffect(() => {
|
|
911
985
|
if (!open) return;
|
|
912
986
|
const body = document.body;
|
|
913
987
|
const openModals = parseInt(body.dataset.openModals || "0", 10);
|
|
@@ -930,14 +1004,15 @@ var Modal = ({
|
|
|
930
1004
|
}, [open]);
|
|
931
1005
|
if (!open) return null;
|
|
932
1006
|
const panelClassName = cn("modal_panel", className);
|
|
933
|
-
const
|
|
1007
|
+
const hasTitle = !!title;
|
|
934
1008
|
return /* @__PURE__ */ jsx(
|
|
935
1009
|
"div",
|
|
936
1010
|
{
|
|
937
1011
|
className: "modal",
|
|
938
1012
|
role: "dialog",
|
|
939
1013
|
"aria-modal": "true",
|
|
940
|
-
"aria-
|
|
1014
|
+
"aria-labelledby": hasTitle && !ariaLabel ? titleId : void 0,
|
|
1015
|
+
"aria-label": !hasTitle ? ariaLabel ?? "Dialog" : ariaLabel,
|
|
941
1016
|
onClick: () => closeOnOverlay && onClose?.(),
|
|
942
1017
|
children: /* @__PURE__ */ jsxs(
|
|
943
1018
|
"div",
|
|
@@ -948,7 +1023,7 @@ var Modal = ({
|
|
|
948
1023
|
onClick: (e) => e.stopPropagation(),
|
|
949
1024
|
...props,
|
|
950
1025
|
children: [
|
|
951
|
-
title && /* @__PURE__ */ jsx("div", { className: "modal_header", children: title }),
|
|
1026
|
+
title && /* @__PURE__ */ jsx("div", { id: titleId, className: "modal_header", children: title }),
|
|
952
1027
|
/* @__PURE__ */ jsx("div", { className: "modal_body", children })
|
|
953
1028
|
]
|
|
954
1029
|
}
|
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.1",
|
|
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",
|