@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 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,105 @@ 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 [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
- 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)
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 = React5.forwardRef(
381
+ var Checkbox = React6.forwardRef(
312
382
  ({ label, size = "md", indeterminate, className, ...props }, ref) => {
313
- const inputRef = React5.useRef(null);
314
- React5.useImperativeHandle(ref, () => inputRef.current);
315
- React5.useEffect(() => {
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 = React5.useId();
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 = React5.forwardRef(
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 = React5.useId();
468
+ const internalId = React6.useId();
399
469
  const selectId = id ?? internalId;
400
470
  const isControlled = value !== void 0;
401
- const [internalValue, setInternalValue] = React5.useState(defaultValue);
471
+ const [internalValue, setInternalValue] = React6.useState(defaultValue);
402
472
  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(
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 = React5.useCallback(
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 = React5.useEffectEvent((e) => {
490
+ const handleOutsideClick = React6.useEffectEvent((e) => {
421
491
  if (!wrapperRef.current?.contains(e.target)) {
422
492
  setIsOpen(false);
423
493
  }
424
494
  });
425
- React5.useEffect(() => {
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
- React5.useEffect(() => {
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
- React5.useLayoutEffect(() => {
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 = React5.forwardRef(
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] = React5.useState(!!defaultChecked);
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 = React5.forwardRef(
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 ?? React5.useId();
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] = React5.useState(
714
+ const [innerValue, setInnerValue] = React6.useState(
644
715
  () => applyTransform(value ?? defaultValue ?? "")
645
716
  );
646
- const isComposingRef = React5.useRef(false);
647
- React5.useEffect(() => {
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 = React5.useMemo(
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
- it
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 = React5.useRef(null);
973
+ const panelRef = React6.useRef(null);
974
+ const titleId = React6.useId();
901
975
  useFocusTrap(panelRef, open);
902
- const handleEscape = React5.useEffectEvent((e) => {
976
+ const handleEscape = React6.useEffectEvent((e) => {
903
977
  if (e.key === "Escape") onClose?.();
904
978
  });
905
- React5.useEffect(() => {
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
- React5.useEffect(() => {
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 modalAriaLabel = ariaLabel ?? (typeof title === "string" ? title : "Dialog");
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-label": modalAriaLabel,
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__ */ 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.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.1.11",
106
+ "storybook": "10.2.10",
109
107
  "tsup": "^8.5.0",
110
108
  "typescript": "^5",
111
109
  "vite": "^5",