@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 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 react-toastify
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
- {children}
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
- /** 토스트 컨테이너 식별자 (기본값: "default") */
100
- containerId?: string;
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: ({ containerId }: ToastProviderProps) => react_jsx_runtime.JSX.Element;
109
+ declare const ToastProvider: ({ children, maxCount }: ToastProviderProps) => react_jsx_runtime.JSX.Element;
109
110
 
110
- declare const useToast: (containerId?: string) => {
111
- success: (msg: string) => react_toastify.Id;
112
- error: (msg: string) => react_toastify.Id;
113
- warning: (msg: string) => react_toastify.Id;
114
- info: (msg: string) => react_toastify.Id;
115
- message: (msg: string) => react_toastify.Id;
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 React5 from 'react';
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 { ToastContainer, Slide, toast } from 'react-toastify';
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 = React5.useRef(null);
43
- React5.useEffect(() => {
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 ToastProvider = ({ containerId = "default" }) => {
264
- return /* @__PURE__ */ jsx(
265
- ToastContainer,
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
- containerId,
268
- position: "top-right",
269
- autoClose: 2e3,
270
- hideProgressBar: false,
271
- newestOnTop: false,
272
- closeOnClick: true,
273
- rtl: false,
274
- pauseOnFocusLoss: true,
275
- draggable: true,
276
- pauseOnHover: true,
277
- theme: "light",
278
- transition: Slide
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 useToast = (containerId = "default") => {
283
- const base = { containerId };
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
- success: (msg) => toast.success(msg, base),
286
- error: (msg) => toast.error(msg, base),
287
- warning: (msg) => toast.warning(msg, base),
288
- info: (msg) => toast.info(msg, base),
289
- message: (msg) => toast(msg, base)
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 = React5.forwardRef(
377
+ var Checkbox = React6.forwardRef(
312
378
  ({ label, size = "md", indeterminate, className, ...props }, ref) => {
313
- const inputRef = React5.useRef(null);
314
- React5.useImperativeHandle(ref, () => inputRef.current);
315
- React5.useEffect(() => {
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 = React5.useId();
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 = React5.forwardRef(
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 = React5.useId();
464
+ const internalId = React6.useId();
399
465
  const selectId = id ?? internalId;
400
466
  const isControlled = value !== void 0;
401
- const [internalValue, setInternalValue] = React5.useState(defaultValue);
467
+ const [internalValue, setInternalValue] = React6.useState(defaultValue);
402
468
  const currentValue = isControlled ? value ?? null : internalValue;
403
- const [isOpen, setIsOpen] = React5.useState(false);
404
- const [activeIndex, setActiveIndex] = React5.useState(-1);
405
- const [dropUp, setDropUp] = React5.useState(false);
406
- const wrapperRef = React5.useRef(null);
407
- const controlRef = React5.useRef(null);
408
- const currentOption = React5.useMemo(
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 = React5.useCallback(
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 = React5.useEffectEvent((e) => {
486
+ const handleOutsideClick = React6.useEffectEvent((e) => {
421
487
  if (!wrapperRef.current?.contains(e.target)) {
422
488
  setIsOpen(false);
423
489
  }
424
490
  });
425
- React5.useEffect(() => {
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
- React5.useEffect(() => {
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
- React5.useLayoutEffect(() => {
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 = React5.forwardRef(
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] = React5.useState(!!defaultChecked);
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 = React5.forwardRef(
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 ?? React5.useId();
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] = React5.useState(
710
+ const [innerValue, setInnerValue] = React6.useState(
644
711
  () => applyTransform(value ?? defaultValue ?? "")
645
712
  );
646
- const isComposingRef = React5.useRef(false);
647
- React5.useEffect(() => {
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 = React5.useMemo(
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
- it
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 = React5.useRef(null);
969
+ const panelRef = React6.useRef(null);
970
+ const titleId = React6.useId();
901
971
  useFocusTrap(panelRef, open);
902
- const handleEscape = React5.useEffectEvent((e) => {
972
+ const handleEscape = React6.useEffectEvent((e) => {
903
973
  if (e.key === "Escape") onClose?.();
904
974
  });
905
- React5.useEffect(() => {
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
- React5.useEffect(() => {
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 modalAriaLabel = ariaLabel ?? (typeof title === "string" ? title : "Dialog");
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-label": modalAriaLabel,
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__ */ jsx("div", { className: "sidebar_brand", children: /* @__PURE__ */ jsxs(Link, { href: brandHref, className: "sidebar_brand_link", children: [
55
- /* @__PURE__ */ jsx("div", {}),
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
- Image,
69
+ "button",
58
70
  {
59
- src: "/images/logo/bigtablet.png",
60
- alt: "Bigtablet",
61
- width: 96,
62
- height: 30,
63
- priority: true
64
- }
65
- ),
66
- /* @__PURE__ */ jsx(
67
- Image,
68
- {
69
- src: "/images/sidebar/arrow-close.svg",
70
- alt: "Close",
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
- Image,
171
+ "button",
162
172
  {
163
- src: "/images/sidebar/menu.svg",
164
- alt: "Open",
165
- width: 24,
166
- height: 24,
167
- onClick: () => toggleSidebar(true)
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.18.9",
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.1.11",
106
+ "storybook": "10.2.10",
109
107
  "tsup": "^8.5.0",
110
108
  "typescript": "^5",
111
109
  "vite": "^5",