@bigtablet/design-system 1.17.4 → 1.18.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/dist/index.css +6 -3
- package/dist/index.d.ts +28 -8
- package/dist/index.js +283 -165
- package/package.json +9 -3
package/dist/index.css
CHANGED
|
@@ -310,6 +310,9 @@
|
|
|
310
310
|
.button_variant_danger:active:not(:disabled) {
|
|
311
311
|
transform: scale(0.98);
|
|
312
312
|
}
|
|
313
|
+
.button_full_width {
|
|
314
|
+
width: 100%;
|
|
315
|
+
}
|
|
313
316
|
|
|
314
317
|
/* src/ui/form/checkbox/style.scss */
|
|
315
318
|
.checkbox {
|
|
@@ -999,13 +1002,13 @@
|
|
|
999
1002
|
gap: 0.25rem;
|
|
1000
1003
|
}
|
|
1001
1004
|
}
|
|
1002
|
-
.
|
|
1005
|
+
.date_picker_full_width {
|
|
1003
1006
|
width: 100%;
|
|
1004
1007
|
}
|
|
1005
|
-
.
|
|
1008
|
+
.date_picker_full_width .date_picker_fields {
|
|
1006
1009
|
width: 100%;
|
|
1007
1010
|
}
|
|
1008
|
-
.
|
|
1011
|
+
.date_picker_full_width select {
|
|
1009
1012
|
flex: 1;
|
|
1010
1013
|
min-width: 0;
|
|
1011
1014
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -33,8 +33,10 @@ declare const AlertProvider: React.FC<{
|
|
|
33
33
|
|
|
34
34
|
interface SpinnerProps {
|
|
35
35
|
size?: number;
|
|
36
|
+
/** Accessible label for the spinner (default: "Loading") */
|
|
37
|
+
ariaLabel?: string;
|
|
36
38
|
}
|
|
37
|
-
declare const Spinner: ({ size }: SpinnerProps) => react_jsx_runtime.JSX.Element;
|
|
39
|
+
declare const Spinner: ({ size, ariaLabel }: SpinnerProps) => react_jsx_runtime.JSX.Element;
|
|
38
40
|
|
|
39
41
|
interface TopLoadingProps {
|
|
40
42
|
/** 진행률 (0-100). undefined면 indeterminate 모드 */
|
|
@@ -45,8 +47,10 @@ interface TopLoadingProps {
|
|
|
45
47
|
height?: number;
|
|
46
48
|
/** 표시 여부 */
|
|
47
49
|
isLoading?: boolean;
|
|
50
|
+
/** 프로그레스 바의 접근성 레이블 (기본값: "Page loading") */
|
|
51
|
+
ariaLabel?: string;
|
|
48
52
|
}
|
|
49
|
-
declare const TopLoading: ({ progress, color, height, isLoading, }: TopLoadingProps) => react_jsx_runtime.JSX.Element | null;
|
|
53
|
+
declare const TopLoading: ({ progress, color, height, isLoading, ariaLabel, }: TopLoadingProps) => react_jsx_runtime.JSX.Element | null;
|
|
50
54
|
|
|
51
55
|
interface ToastProviderProps {
|
|
52
56
|
containerId?: string;
|
|
@@ -64,16 +68,22 @@ declare const useToast: (containerId?: string) => {
|
|
|
64
68
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
65
69
|
variant?: "primary" | "secondary" | "ghost" | "danger";
|
|
66
70
|
size?: "sm" | "md" | "lg";
|
|
71
|
+
/** Whether the button should take the full width of its container */
|
|
72
|
+
fullWidth?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Custom width for the button
|
|
75
|
+
* @deprecated Use `fullWidth` prop or CSS instead
|
|
76
|
+
*/
|
|
67
77
|
width?: string;
|
|
68
78
|
}
|
|
69
|
-
declare const Button: ({ variant, size, width, className, ...props }: ButtonProps) => react_jsx_runtime.JSX.Element;
|
|
79
|
+
declare const Button: ({ variant, size, fullWidth, width, className, style, ...props }: ButtonProps) => react_jsx_runtime.JSX.Element;
|
|
70
80
|
|
|
71
81
|
interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
|
|
72
82
|
label?: React.ReactNode;
|
|
73
83
|
size?: "sm" | "md" | "lg";
|
|
74
84
|
indeterminate?: boolean;
|
|
75
85
|
}
|
|
76
|
-
declare const Checkbox:
|
|
86
|
+
declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLInputElement>>;
|
|
77
87
|
|
|
78
88
|
interface FileInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
79
89
|
label?: string;
|
|
@@ -85,7 +95,7 @@ interface RadioProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "
|
|
|
85
95
|
label?: React.ReactNode;
|
|
86
96
|
size?: "sm" | "md" | "lg";
|
|
87
97
|
}
|
|
88
|
-
declare const Radio:
|
|
98
|
+
declare const Radio: React.ForwardRefExoticComponent<RadioProps & React.RefAttributes<HTMLInputElement>>;
|
|
89
99
|
|
|
90
100
|
type SelectSize = "sm" | "md" | "lg";
|
|
91
101
|
type SelectVariant = "outline" | "filled" | "ghost";
|
|
@@ -117,8 +127,10 @@ interface SwitchProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>
|
|
|
117
127
|
onChange?: (checked: boolean) => void;
|
|
118
128
|
size?: "sm" | "md" | "lg";
|
|
119
129
|
disabled?: boolean;
|
|
130
|
+
/** Accessible label for the switch (for screen readers) */
|
|
131
|
+
ariaLabel: string;
|
|
120
132
|
}
|
|
121
|
-
declare const Switch:
|
|
133
|
+
declare const Switch: React.ForwardRefExoticComponent<SwitchProps & React.RefAttributes<HTMLButtonElement>>;
|
|
122
134
|
|
|
123
135
|
type TextFieldVariant = "outline" | "filled" | "ghost";
|
|
124
136
|
type TextFieldSize = "sm" | "md" | "lg";
|
|
@@ -151,9 +163,15 @@ interface DatePickerProps {
|
|
|
151
163
|
minDate?: string;
|
|
152
164
|
selectableRange?: SelectableRange;
|
|
153
165
|
disabled?: boolean;
|
|
166
|
+
/** Whether the date picker should take the full width of its container */
|
|
167
|
+
fullWidth?: boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Custom width for the date picker
|
|
170
|
+
* @deprecated Use `fullWidth` prop or CSS instead
|
|
171
|
+
*/
|
|
154
172
|
width?: number | string;
|
|
155
173
|
}
|
|
156
|
-
declare const DatePicker: ({ label, value, onChange, mode, startYear, endYear, minDate, selectableRange, disabled, width, }: DatePickerProps) => react_jsx_runtime.JSX.Element;
|
|
174
|
+
declare const DatePicker: ({ label, value, onChange, mode, startYear, endYear, minDate, selectableRange, disabled, fullWidth, width, }: DatePickerProps) => react_jsx_runtime.JSX.Element;
|
|
157
175
|
|
|
158
176
|
interface PaginationProps {
|
|
159
177
|
page: number;
|
|
@@ -168,7 +186,9 @@ interface ModalProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title">
|
|
|
168
186
|
closeOnOverlay?: boolean;
|
|
169
187
|
width?: number | string;
|
|
170
188
|
title?: React.ReactNode;
|
|
189
|
+
/** Accessible label for the modal (default uses title or "Dialog") */
|
|
190
|
+
ariaLabel?: string;
|
|
171
191
|
}
|
|
172
|
-
declare const Modal: ({ open, onClose, closeOnOverlay, width, title, children, className, ...props }: ModalProps) => react_jsx_runtime.JSX.Element | null;
|
|
192
|
+
declare const Modal: ({ open, onClose, closeOnOverlay, width, title, children, className, ariaLabel, ...props }: ModalProps) => react_jsx_runtime.JSX.Element | null;
|
|
173
193
|
|
|
174
194
|
export { AlertProvider, Button, Card, Checkbox, DatePicker, FileInput, Modal, Pagination, Radio, Select, type SelectOption, Spinner, Switch, TextField, ToastProvider, TopLoading, useAlert, useToast };
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,95 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import './index.css';
|
|
3
|
-
import
|
|
4
|
-
import * as React3 from 'react';
|
|
3
|
+
import * as React5 from 'react';
|
|
5
4
|
import { createContext, useContext, useState, useCallback } from 'react';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
6
|
import { createPortal } from 'react-dom';
|
|
7
7
|
import { ToastContainer, Slide, toast } from 'react-toastify';
|
|
8
8
|
import 'react-toastify/dist/ReactToastify.css';
|
|
9
9
|
import { ChevronDown, Check } from 'lucide-react';
|
|
10
10
|
|
|
11
|
-
// src/
|
|
11
|
+
// src/utils/cn.ts
|
|
12
|
+
var cn = (...classes) => {
|
|
13
|
+
const classNames = [];
|
|
14
|
+
for (const item of classes) {
|
|
15
|
+
if (!item) continue;
|
|
16
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
17
|
+
classNames.push(String(item));
|
|
18
|
+
} else if (Array.isArray(item)) {
|
|
19
|
+
const nested = cn(...item);
|
|
20
|
+
if (nested) {
|
|
21
|
+
classNames.push(nested);
|
|
22
|
+
}
|
|
23
|
+
} else if (typeof item === "object") {
|
|
24
|
+
for (const key in item) {
|
|
25
|
+
if (Object.prototype.hasOwnProperty.call(item, key) && item[key]) {
|
|
26
|
+
classNames.push(key);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return classNames.join(" ");
|
|
32
|
+
};
|
|
33
|
+
var FOCUSABLE_SELECTORS = [
|
|
34
|
+
"a[href]",
|
|
35
|
+
"button:not([disabled])",
|
|
36
|
+
"input:not([disabled])",
|
|
37
|
+
"select:not([disabled])",
|
|
38
|
+
"textarea:not([disabled])",
|
|
39
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
40
|
+
].join(", ");
|
|
41
|
+
function useFocusTrap(containerRef, isActive) {
|
|
42
|
+
const previousActiveElement = React5.useRef(null);
|
|
43
|
+
React5.useEffect(() => {
|
|
44
|
+
if (!isActive) return;
|
|
45
|
+
const container = containerRef.current;
|
|
46
|
+
if (!container) return;
|
|
47
|
+
previousActiveElement.current = document.activeElement;
|
|
48
|
+
const getFocusableElements = () => {
|
|
49
|
+
return container.querySelectorAll(FOCUSABLE_SELECTORS);
|
|
50
|
+
};
|
|
51
|
+
let wasTabIndexAdded = false;
|
|
52
|
+
const focusableElements = getFocusableElements();
|
|
53
|
+
if (focusableElements.length > 0) {
|
|
54
|
+
focusableElements[0].focus();
|
|
55
|
+
} else {
|
|
56
|
+
container.setAttribute("tabindex", "-1");
|
|
57
|
+
wasTabIndexAdded = true;
|
|
58
|
+
container.focus();
|
|
59
|
+
}
|
|
60
|
+
const handleKeyDown = (e) => {
|
|
61
|
+
if (e.key !== "Tab") return;
|
|
62
|
+
const focusableElements2 = getFocusableElements();
|
|
63
|
+
if (focusableElements2.length === 0) {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const firstElement = focusableElements2[0];
|
|
68
|
+
const lastElement = focusableElements2[focusableElements2.length - 1];
|
|
69
|
+
if (e.shiftKey) {
|
|
70
|
+
if (document.activeElement === firstElement) {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
lastElement.focus();
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
if (document.activeElement === lastElement) {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
firstElement.focus();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
82
|
+
return () => {
|
|
83
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
84
|
+
if (wasTabIndexAdded) {
|
|
85
|
+
container.removeAttribute("tabindex");
|
|
86
|
+
}
|
|
87
|
+
if (previousActiveElement.current && previousActiveElement.current.focus) {
|
|
88
|
+
previousActiveElement.current.focus();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}, [isActive, containerRef]);
|
|
92
|
+
}
|
|
12
93
|
var Card = ({
|
|
13
94
|
heading,
|
|
14
95
|
shadow = "sm",
|
|
@@ -18,14 +99,14 @@ var Card = ({
|
|
|
18
99
|
children,
|
|
19
100
|
...props
|
|
20
101
|
}) => {
|
|
21
|
-
const
|
|
102
|
+
const cardClassName = cn(
|
|
22
103
|
"card",
|
|
23
104
|
`card_shadow_${shadow}`,
|
|
24
105
|
`card_p_${padding}`,
|
|
25
|
-
bordered
|
|
26
|
-
className
|
|
27
|
-
|
|
28
|
-
return /* @__PURE__ */ jsxs("div", { className:
|
|
106
|
+
{ card_bordered: bordered },
|
|
107
|
+
className
|
|
108
|
+
);
|
|
109
|
+
return /* @__PURE__ */ jsxs("div", { className: cardClassName, ...props, children: [
|
|
29
110
|
heading ? /* @__PURE__ */ jsx("div", { className: "card_title", children: heading }) : null,
|
|
30
111
|
/* @__PURE__ */ jsx("div", { className: "card_body", children })
|
|
31
112
|
] });
|
|
@@ -133,14 +214,14 @@ var AlertModal = ({
|
|
|
133
214
|
}
|
|
134
215
|
) });
|
|
135
216
|
};
|
|
136
|
-
var Spinner = ({ size = 24 }) => {
|
|
217
|
+
var Spinner = ({ size = 24, ariaLabel = "Loading" }) => {
|
|
137
218
|
return /* @__PURE__ */ jsx(
|
|
138
219
|
"span",
|
|
139
220
|
{
|
|
140
221
|
className: "spinner",
|
|
141
222
|
style: { width: size, height: size },
|
|
142
223
|
role: "status",
|
|
143
|
-
"aria-label":
|
|
224
|
+
"aria-label": ariaLabel
|
|
144
225
|
}
|
|
145
226
|
);
|
|
146
227
|
};
|
|
@@ -148,7 +229,8 @@ var TopLoading = ({
|
|
|
148
229
|
progress,
|
|
149
230
|
color,
|
|
150
231
|
height = 3,
|
|
151
|
-
isLoading = true
|
|
232
|
+
isLoading = true,
|
|
233
|
+
ariaLabel = "Page loading"
|
|
152
234
|
}) => {
|
|
153
235
|
if (!isLoading) return null;
|
|
154
236
|
const isIndeterminate = progress === void 0;
|
|
@@ -161,7 +243,7 @@ var TopLoading = ({
|
|
|
161
243
|
"aria-valuemin": 0,
|
|
162
244
|
"aria-valuemax": 100,
|
|
163
245
|
"aria-valuenow": isIndeterminate ? void 0 : progress,
|
|
164
|
-
"aria-label":
|
|
246
|
+
"aria-label": ariaLabel,
|
|
165
247
|
children: /* @__PURE__ */ jsx(
|
|
166
248
|
"div",
|
|
167
249
|
{
|
|
@@ -210,49 +292,51 @@ var useToast = (containerId = "default") => {
|
|
|
210
292
|
var Button = ({
|
|
211
293
|
variant = "primary",
|
|
212
294
|
size = "md",
|
|
213
|
-
|
|
295
|
+
fullWidth = true,
|
|
296
|
+
width,
|
|
214
297
|
className,
|
|
298
|
+
style,
|
|
215
299
|
...props
|
|
216
300
|
}) => {
|
|
217
|
-
const buttonClassName =
|
|
301
|
+
const buttonClassName = cn(
|
|
218
302
|
"button",
|
|
219
303
|
`button_variant_${variant}`,
|
|
220
304
|
`button_size_${size}`,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
label,
|
|
227
|
-
size = "md",
|
|
228
|
-
indeterminate,
|
|
229
|
-
className,
|
|
230
|
-
...props
|
|
231
|
-
}) => {
|
|
232
|
-
const inputRef = React3.useRef(null);
|
|
233
|
-
React3.useEffect(() => {
|
|
234
|
-
if (!inputRef.current) return;
|
|
235
|
-
inputRef.current.indeterminate = Boolean(indeterminate);
|
|
236
|
-
}, [indeterminate]);
|
|
237
|
-
const rootClassName = [
|
|
238
|
-
"checkbox",
|
|
239
|
-
`checkbox_size_${size}`,
|
|
240
|
-
className ?? ""
|
|
241
|
-
].filter(Boolean).join(" ");
|
|
242
|
-
return /* @__PURE__ */ jsxs("label", { className: rootClassName, children: [
|
|
243
|
-
/* @__PURE__ */ jsx(
|
|
244
|
-
"input",
|
|
245
|
-
{
|
|
246
|
-
ref: inputRef,
|
|
247
|
-
type: "checkbox",
|
|
248
|
-
className: "checkbox_input",
|
|
249
|
-
...props
|
|
250
|
-
}
|
|
251
|
-
),
|
|
252
|
-
/* @__PURE__ */ jsx("span", { className: "checkbox_box", "aria-hidden": "true" }),
|
|
253
|
-
label ? /* @__PURE__ */ jsx("span", { className: "checkbox_label", children: label }) : null
|
|
254
|
-
] });
|
|
305
|
+
fullWidth && !width && "button_full_width",
|
|
306
|
+
className
|
|
307
|
+
);
|
|
308
|
+
const buttonStyle = width ? { ...style, width } : style;
|
|
309
|
+
return /* @__PURE__ */ jsx("button", { className: buttonClassName, style: buttonStyle, ...props });
|
|
255
310
|
};
|
|
311
|
+
var Checkbox = React5.forwardRef(
|
|
312
|
+
({ label, size = "md", indeterminate, className, ...props }, ref) => {
|
|
313
|
+
const inputRef = React5.useRef(null);
|
|
314
|
+
React5.useImperativeHandle(ref, () => inputRef.current);
|
|
315
|
+
React5.useEffect(() => {
|
|
316
|
+
if (!inputRef.current) return;
|
|
317
|
+
inputRef.current.indeterminate = Boolean(indeterminate);
|
|
318
|
+
}, [indeterminate]);
|
|
319
|
+
const rootClassName = cn(
|
|
320
|
+
"checkbox",
|
|
321
|
+
`checkbox_size_${size}`,
|
|
322
|
+
className
|
|
323
|
+
);
|
|
324
|
+
return /* @__PURE__ */ jsxs("label", { className: rootClassName, children: [
|
|
325
|
+
/* @__PURE__ */ jsx(
|
|
326
|
+
"input",
|
|
327
|
+
{
|
|
328
|
+
ref: inputRef,
|
|
329
|
+
type: "checkbox",
|
|
330
|
+
className: "checkbox_input",
|
|
331
|
+
...props
|
|
332
|
+
}
|
|
333
|
+
),
|
|
334
|
+
/* @__PURE__ */ jsx("span", { className: "checkbox_box", "aria-hidden": "true" }),
|
|
335
|
+
label ? /* @__PURE__ */ jsx("span", { className: "checkbox_label", children: label }) : null
|
|
336
|
+
] });
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
Checkbox.displayName = "Checkbox";
|
|
256
340
|
var FileInput = ({
|
|
257
341
|
label = "\uD30C\uC77C \uC120\uD0DD",
|
|
258
342
|
onFiles,
|
|
@@ -260,7 +344,7 @@ var FileInput = ({
|
|
|
260
344
|
disabled,
|
|
261
345
|
...props
|
|
262
346
|
}) => {
|
|
263
|
-
const inputId =
|
|
347
|
+
const inputId = React5.useId();
|
|
264
348
|
const rootClassName = [
|
|
265
349
|
"file_input",
|
|
266
350
|
disabled && "file_input_disabled",
|
|
@@ -281,18 +365,21 @@ var FileInput = ({
|
|
|
281
365
|
/* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "file_input_label", children: label })
|
|
282
366
|
] });
|
|
283
367
|
};
|
|
284
|
-
var Radio = (
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
/* @__PURE__ */
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
};
|
|
368
|
+
var Radio = React5.forwardRef(
|
|
369
|
+
({ label, size = "md", className, ...props }, ref) => {
|
|
370
|
+
const rootClassName = cn(
|
|
371
|
+
"radio",
|
|
372
|
+
`radio_size_${size}`,
|
|
373
|
+
className
|
|
374
|
+
);
|
|
375
|
+
return /* @__PURE__ */ jsxs("label", { className: rootClassName, children: [
|
|
376
|
+
/* @__PURE__ */ jsx("input", { ref, type: "radio", className: "radio_input", ...props }),
|
|
377
|
+
/* @__PURE__ */ jsx("span", { className: "radio_dot", "aria-hidden": "true" }),
|
|
378
|
+
label ? /* @__PURE__ */ jsx("span", { className: "radio_label", children: label }) : null
|
|
379
|
+
] });
|
|
380
|
+
}
|
|
381
|
+
);
|
|
382
|
+
Radio.displayName = "Radio";
|
|
296
383
|
var Select = ({
|
|
297
384
|
id,
|
|
298
385
|
label,
|
|
@@ -308,21 +395,21 @@ var Select = ({
|
|
|
308
395
|
className,
|
|
309
396
|
textAlign = "left"
|
|
310
397
|
}) => {
|
|
311
|
-
const internalId =
|
|
398
|
+
const internalId = React5.useId();
|
|
312
399
|
const selectId = id ?? internalId;
|
|
313
400
|
const isControlled = value !== void 0;
|
|
314
|
-
const [internalValue, setInternalValue] =
|
|
401
|
+
const [internalValue, setInternalValue] = React5.useState(defaultValue);
|
|
315
402
|
const currentValue = isControlled ? value ?? null : internalValue;
|
|
316
|
-
const [isOpen, setIsOpen] =
|
|
317
|
-
const [activeIndex, setActiveIndex] =
|
|
318
|
-
const [dropUp, setDropUp] =
|
|
319
|
-
const wrapperRef =
|
|
320
|
-
const controlRef =
|
|
321
|
-
const currentOption =
|
|
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(
|
|
322
409
|
() => options.find((o) => o.value === currentValue) ?? null,
|
|
323
410
|
[options, currentValue]
|
|
324
411
|
);
|
|
325
|
-
const setValue =
|
|
412
|
+
const setValue = React5.useCallback(
|
|
326
413
|
(next) => {
|
|
327
414
|
const option = options.find((o) => o.value === next) ?? null;
|
|
328
415
|
if (!isControlled) setInternalValue(next);
|
|
@@ -330,15 +417,14 @@ var Select = ({
|
|
|
330
417
|
},
|
|
331
418
|
[isControlled, onChange, options]
|
|
332
419
|
);
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
document.
|
|
341
|
-
return () => document.removeEventListener("mousedown", onDocClick);
|
|
420
|
+
const handleOutsideClick = React5.useEffectEvent((e) => {
|
|
421
|
+
if (!wrapperRef.current?.contains(e.target)) {
|
|
422
|
+
setIsOpen(false);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
React5.useEffect(() => {
|
|
426
|
+
document.addEventListener("mousedown", handleOutsideClick);
|
|
427
|
+
return () => document.removeEventListener("mousedown", handleOutsideClick);
|
|
342
428
|
}, []);
|
|
343
429
|
const moveActive = (dir) => {
|
|
344
430
|
if (!isOpen) {
|
|
@@ -401,12 +487,12 @@ var Select = ({
|
|
|
401
487
|
break;
|
|
402
488
|
}
|
|
403
489
|
};
|
|
404
|
-
|
|
490
|
+
React5.useEffect(() => {
|
|
405
491
|
if (!isOpen) return;
|
|
406
492
|
const idx = options.findIndex((o) => o.value === currentValue && !o.disabled);
|
|
407
493
|
setActiveIndex(idx >= 0 ? idx : Math.max(0, options.findIndex((o) => !o.disabled)));
|
|
408
494
|
}, [isOpen, options, currentValue]);
|
|
409
|
-
|
|
495
|
+
React5.useLayoutEffect(() => {
|
|
410
496
|
if (!isOpen || !controlRef.current) return;
|
|
411
497
|
const rect = controlRef.current.getBoundingClientRect();
|
|
412
498
|
const listHeight = Math.min(options.length * 40, 288);
|
|
@@ -414,18 +500,14 @@ var Select = ({
|
|
|
414
500
|
const spaceAbove = rect.top;
|
|
415
501
|
setDropUp(spaceBelow < listHeight && spaceAbove > spaceBelow);
|
|
416
502
|
}, [isOpen, options.length]);
|
|
417
|
-
const rootClassName =
|
|
418
|
-
const controlClassName =
|
|
503
|
+
const rootClassName = cn("select", className);
|
|
504
|
+
const controlClassName = cn(
|
|
419
505
|
"select_control",
|
|
420
506
|
`select_variant_${variant}`,
|
|
421
507
|
`select_size_${size}`,
|
|
422
|
-
isOpen
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const listClassName = [
|
|
426
|
-
"select_list",
|
|
427
|
-
dropUp && "select_list_up"
|
|
428
|
-
].filter(Boolean).join(" ");
|
|
508
|
+
{ is_open: isOpen, is_disabled: disabled }
|
|
509
|
+
);
|
|
510
|
+
const listClassName = cn("select_list", { select_list_up: dropUp });
|
|
429
511
|
return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: rootClassName, style: fullWidth ? { width: "100%" } : void 0, children: [
|
|
430
512
|
label && /* @__PURE__ */ jsx("label", { htmlFor: selectId, className: "select_label", children: label }),
|
|
431
513
|
/* @__PURE__ */ jsxs(
|
|
@@ -463,12 +545,10 @@ var Select = ({
|
|
|
463
545
|
children: options.map((opt, i) => {
|
|
464
546
|
const selected = currentValue === opt.value;
|
|
465
547
|
const active = i === activeIndex;
|
|
466
|
-
const optionClassName =
|
|
548
|
+
const optionClassName = cn(
|
|
467
549
|
"select_option",
|
|
468
|
-
selected
|
|
469
|
-
|
|
470
|
-
opt.disabled && "is_disabled"
|
|
471
|
-
].filter(Boolean).join(" ");
|
|
550
|
+
{ is_selected: selected, is_active: active, is_disabled: opt.disabled }
|
|
551
|
+
);
|
|
472
552
|
return /* @__PURE__ */ jsxs(
|
|
473
553
|
"li",
|
|
474
554
|
{
|
|
@@ -493,46 +573,51 @@ var Select = ({
|
|
|
493
573
|
)
|
|
494
574
|
] });
|
|
495
575
|
};
|
|
496
|
-
var Switch = (
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
535
|
-
|
|
576
|
+
var Switch = React5.forwardRef(
|
|
577
|
+
({
|
|
578
|
+
checked,
|
|
579
|
+
defaultChecked,
|
|
580
|
+
onChange,
|
|
581
|
+
size = "md",
|
|
582
|
+
disabled,
|
|
583
|
+
className,
|
|
584
|
+
ariaLabel,
|
|
585
|
+
...props
|
|
586
|
+
}, ref) => {
|
|
587
|
+
const isControlled = checked !== void 0;
|
|
588
|
+
const [innerChecked, setInnerChecked] = React5.useState(!!defaultChecked);
|
|
589
|
+
const isOn = isControlled ? !!checked : innerChecked;
|
|
590
|
+
const handleToggle = () => {
|
|
591
|
+
if (disabled) return;
|
|
592
|
+
const next = !isOn;
|
|
593
|
+
if (!isControlled) setInnerChecked(next);
|
|
594
|
+
onChange?.(next);
|
|
595
|
+
};
|
|
596
|
+
const rootClassName = cn(
|
|
597
|
+
"switch",
|
|
598
|
+
`switch_size_${size}`,
|
|
599
|
+
{ switch_on: isOn, switch_disabled: disabled },
|
|
600
|
+
className
|
|
601
|
+
);
|
|
602
|
+
return /* @__PURE__ */ jsx(
|
|
603
|
+
"button",
|
|
604
|
+
{
|
|
605
|
+
ref,
|
|
606
|
+
type: "button",
|
|
607
|
+
role: "switch",
|
|
608
|
+
"aria-checked": isOn,
|
|
609
|
+
"aria-label": ariaLabel,
|
|
610
|
+
disabled,
|
|
611
|
+
onClick: handleToggle,
|
|
612
|
+
className: rootClassName,
|
|
613
|
+
...props,
|
|
614
|
+
children: /* @__PURE__ */ jsx("span", { className: "switch_thumb" })
|
|
615
|
+
}
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
);
|
|
619
|
+
Switch.displayName = "Switch";
|
|
620
|
+
var TextField = React5.forwardRef(
|
|
536
621
|
({
|
|
537
622
|
id,
|
|
538
623
|
label,
|
|
@@ -551,37 +636,41 @@ var TextField = React3.forwardRef(
|
|
|
551
636
|
transformValue,
|
|
552
637
|
...props
|
|
553
638
|
}, ref) => {
|
|
554
|
-
const inputId = id ??
|
|
639
|
+
const inputId = id ?? React5.useId();
|
|
555
640
|
const helperId = helperText ? `${inputId}-help` : void 0;
|
|
556
641
|
const isControlled = value !== void 0;
|
|
557
642
|
const applyTransform = (nextValue) => transformValue ? transformValue(nextValue) : nextValue;
|
|
558
|
-
const [innerValue, setInnerValue] =
|
|
643
|
+
const [innerValue, setInnerValue] = React5.useState(
|
|
559
644
|
() => applyTransform(value ?? defaultValue ?? "")
|
|
560
645
|
);
|
|
561
|
-
const isComposingRef =
|
|
562
|
-
|
|
646
|
+
const isComposingRef = React5.useRef(false);
|
|
647
|
+
React5.useEffect(() => {
|
|
563
648
|
if (!isControlled) return;
|
|
564
649
|
setInnerValue(applyTransform(value ?? ""));
|
|
565
650
|
}, [isControlled, value, transformValue]);
|
|
566
|
-
const rootClassName =
|
|
651
|
+
const rootClassName = cn(
|
|
567
652
|
"text_field",
|
|
568
|
-
fullWidth
|
|
569
|
-
className
|
|
570
|
-
|
|
571
|
-
const inputClassName =
|
|
653
|
+
{ text_field_full_width: fullWidth },
|
|
654
|
+
className
|
|
655
|
+
);
|
|
656
|
+
const inputClassName = cn(
|
|
572
657
|
"text_field_input",
|
|
573
658
|
`text_field_variant_${variant}`,
|
|
574
659
|
`text_field_size_${size}`,
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
660
|
+
{
|
|
661
|
+
text_field_with_left: !!leftIcon,
|
|
662
|
+
text_field_with_right: !!rightIcon,
|
|
663
|
+
text_field_error: !!error,
|
|
664
|
+
text_field_success: !!success
|
|
665
|
+
}
|
|
666
|
+
);
|
|
667
|
+
const helperClassName = cn(
|
|
581
668
|
"text_field_helper",
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
669
|
+
{
|
|
670
|
+
text_field_helper_error: error,
|
|
671
|
+
text_field_helper_success: success
|
|
672
|
+
}
|
|
673
|
+
);
|
|
585
674
|
return /* @__PURE__ */ jsxs("div", { className: rootClassName, children: [
|
|
586
675
|
label ? /* @__PURE__ */ jsx("label", { className: "text_field_label", htmlFor: inputId, children: label }) : null,
|
|
587
676
|
/* @__PURE__ */ jsxs("div", { className: "text_field_wrap", children: [
|
|
@@ -638,6 +727,7 @@ var DatePicker = ({
|
|
|
638
727
|
minDate,
|
|
639
728
|
selectableRange = "all",
|
|
640
729
|
disabled,
|
|
730
|
+
fullWidth = true,
|
|
641
731
|
width
|
|
642
732
|
}) => {
|
|
643
733
|
const today = /* @__PURE__ */ new Date();
|
|
@@ -665,7 +755,8 @@ var DatePicker = ({
|
|
|
665
755
|
onChange(`${yy}-${pad(mm)}-${pad(safeDay)}`);
|
|
666
756
|
};
|
|
667
757
|
const containerStyle = width ? { width: normalizeWidth(width) } : void 0;
|
|
668
|
-
|
|
758
|
+
const rootClassName = cn("date_picker", { date_picker_full_width: fullWidth && !width });
|
|
759
|
+
return /* @__PURE__ */ jsxs("div", { className: rootClassName, style: containerStyle, children: [
|
|
669
760
|
label && /* @__PURE__ */ jsx("label", { className: "date_picker_label", children: label }),
|
|
670
761
|
/* @__PURE__ */ jsxs("div", { className: "date_picker_fields", children: [
|
|
671
762
|
/* @__PURE__ */ jsxs(
|
|
@@ -747,7 +838,7 @@ var getPaginationItems = (page, totalPages) => {
|
|
|
747
838
|
var Pagination = ({ page, totalPages, onChange }) => {
|
|
748
839
|
const prevDisabled = page <= 1;
|
|
749
840
|
const nextDisabled = page >= totalPages;
|
|
750
|
-
const items =
|
|
841
|
+
const items = React5.useMemo(
|
|
751
842
|
() => getPaginationItems(page, totalPages),
|
|
752
843
|
[page, totalPages]
|
|
753
844
|
);
|
|
@@ -767,10 +858,10 @@ var Pagination = ({ page, totalPages, onChange }) => {
|
|
|
767
858
|
return /* @__PURE__ */ jsx("span", { className: "pagination_ellipsis", "aria-hidden": "true", children: "\u2026" }, `e-${idx}`);
|
|
768
859
|
}
|
|
769
860
|
const isActive = it === page;
|
|
770
|
-
const buttonClassName =
|
|
861
|
+
const buttonClassName = cn(
|
|
771
862
|
"pagination_page_button",
|
|
772
|
-
isActive
|
|
773
|
-
|
|
863
|
+
{ pagination_active: isActive }
|
|
864
|
+
);
|
|
774
865
|
return /* @__PURE__ */ jsx(
|
|
775
866
|
"button",
|
|
776
867
|
{
|
|
@@ -803,28 +894,55 @@ var Modal = ({
|
|
|
803
894
|
title,
|
|
804
895
|
children,
|
|
805
896
|
className,
|
|
897
|
+
ariaLabel,
|
|
806
898
|
...props
|
|
807
899
|
}) => {
|
|
808
|
-
|
|
900
|
+
const panelRef = React5.useRef(null);
|
|
901
|
+
useFocusTrap(panelRef, open);
|
|
902
|
+
const handleEscape = React5.useEffectEvent((e) => {
|
|
903
|
+
if (e.key === "Escape") onClose?.();
|
|
904
|
+
});
|
|
905
|
+
React5.useEffect(() => {
|
|
809
906
|
if (!open) return;
|
|
810
|
-
|
|
811
|
-
|
|
907
|
+
document.addEventListener("keydown", handleEscape);
|
|
908
|
+
return () => document.removeEventListener("keydown", handleEscape);
|
|
909
|
+
}, [open]);
|
|
910
|
+
React5.useEffect(() => {
|
|
911
|
+
if (!open) return;
|
|
912
|
+
const body = document.body;
|
|
913
|
+
const openModals = parseInt(body.dataset.openModals || "0", 10);
|
|
914
|
+
if (openModals === 0) {
|
|
915
|
+
body.dataset.originalOverflow = window.getComputedStyle(body).overflow;
|
|
916
|
+
body.style.overflow = "hidden";
|
|
917
|
+
}
|
|
918
|
+
body.dataset.openModals = String(openModals + 1);
|
|
919
|
+
return () => {
|
|
920
|
+
const currentOpenModals = parseInt(body.dataset.openModals || "1", 10);
|
|
921
|
+
const nextOpenModals = currentOpenModals - 1;
|
|
922
|
+
if (nextOpenModals === 0) {
|
|
923
|
+
body.style.overflow = body.dataset.originalOverflow || "";
|
|
924
|
+
delete body.dataset.openModals;
|
|
925
|
+
delete body.dataset.originalOverflow;
|
|
926
|
+
} else {
|
|
927
|
+
body.dataset.openModals = String(nextOpenModals);
|
|
928
|
+
}
|
|
812
929
|
};
|
|
813
|
-
|
|
814
|
-
return () => document.removeEventListener("keydown", onKeyDown);
|
|
815
|
-
}, [open, onClose]);
|
|
930
|
+
}, [open]);
|
|
816
931
|
if (!open) return null;
|
|
817
|
-
const panelClassName =
|
|
932
|
+
const panelClassName = cn("modal_panel", className);
|
|
933
|
+
const modalAriaLabel = ariaLabel ?? (typeof title === "string" ? title : "Dialog");
|
|
818
934
|
return /* @__PURE__ */ jsx(
|
|
819
935
|
"div",
|
|
820
936
|
{
|
|
821
937
|
className: "modal",
|
|
822
938
|
role: "dialog",
|
|
823
939
|
"aria-modal": "true",
|
|
940
|
+
"aria-label": modalAriaLabel,
|
|
824
941
|
onClick: () => closeOnOverlay && onClose?.(),
|
|
825
942
|
children: /* @__PURE__ */ jsxs(
|
|
826
943
|
"div",
|
|
827
944
|
{
|
|
945
|
+
ref: panelRef,
|
|
828
946
|
className: panelClassName,
|
|
829
947
|
style: { width },
|
|
830
948
|
onClick: (e) => e.stopPropagation(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bigtablet/design-system",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "Bigtablet Design System UI Components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
"dev": "tsup --watch",
|
|
48
48
|
"storybook": "storybook dev -p 6006",
|
|
49
49
|
"build:sb": "storybook build",
|
|
50
|
-
"test": "
|
|
50
|
+
"test": "vitest run --project unit",
|
|
51
|
+
"test:watch": "vitest --project unit",
|
|
52
|
+
"test:coverage": "vitest run --project unit --coverage",
|
|
51
53
|
"chromatic": "npx chromatic --project-token=chpt_2f758912f0dde5c --build-script-name=build:sb"
|
|
52
54
|
},
|
|
53
55
|
"keywords": [
|
|
@@ -83,6 +85,9 @@
|
|
|
83
85
|
"@storybook/addon-vitest": "10.1.11",
|
|
84
86
|
"@storybook/react": "10.1.11",
|
|
85
87
|
"@storybook/react-vite": "10.1.11",
|
|
88
|
+
"@testing-library/dom": "^10.4.1",
|
|
89
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
90
|
+
"@testing-library/react": "^16.3.2",
|
|
86
91
|
"@types/node": "^24",
|
|
87
92
|
"@types/react": "^19",
|
|
88
93
|
"@types/react-dom": "^19",
|
|
@@ -91,8 +96,9 @@
|
|
|
91
96
|
"chromatic": "^13.3.3",
|
|
92
97
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
93
98
|
"esbuild-sass-plugin": "^3",
|
|
99
|
+
"jsdom": "^28.0.0",
|
|
94
100
|
"lucide-react": "^0.552.0",
|
|
95
|
-
"next": "16.
|
|
101
|
+
"next": "16.1.5",
|
|
96
102
|
"playwright": "^1.57.0",
|
|
97
103
|
"react": "19.2.0",
|
|
98
104
|
"react-dom": "19.2.0",
|