@bigtablet/design-system 0.1.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.cjs ADDED
@@ -0,0 +1,523 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var lucideReact = require('lucide-react');
5
+ var reactToastify = require('react-toastify');
6
+ require('react-toastify/dist/ReactToastify.css');
7
+ var React5 = require('react');
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var React5__namespace = /*#__PURE__*/_interopNamespace(React5);
28
+
29
+ // src/ui/display/card/index.tsx
30
+ var Card = ({ shadow = "sm", padding = "md", bordered, className, ...props }) => {
31
+ const cls = ["card", `card--shadow-${shadow}`, `card--p-${padding}`, bordered && "card--bordered", className].filter(Boolean).join(" ");
32
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls, ...props });
33
+ };
34
+ var Alert = ({
35
+ variant = "info",
36
+ title,
37
+ icon,
38
+ closable,
39
+ onClose,
40
+ className,
41
+ children,
42
+ ...props
43
+ }) => {
44
+ return /* @__PURE__ */ jsxRuntime.jsxs(
45
+ "div",
46
+ {
47
+ className: ["alert", `alert--${variant}`, className].filter(Boolean).join(" "),
48
+ role: "alert",
49
+ "aria-live": "polite",
50
+ ...props,
51
+ children: [
52
+ icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "alert__icon", "aria-hidden": "true", children: icon }),
53
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "alert__content", children: [
54
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "alert__title", children: title }),
55
+ children && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "alert__desc", children })
56
+ ] }),
57
+ closable && /* @__PURE__ */ jsxRuntime.jsx(
58
+ "button",
59
+ {
60
+ type: "button",
61
+ className: "alert__close",
62
+ "aria-label": "\uB2EB\uAE30",
63
+ onClick: onClose,
64
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16, "aria-hidden": "true", focusable: "false" })
65
+ }
66
+ )
67
+ ]
68
+ }
69
+ );
70
+ };
71
+ var Loading = ({ size = 24 }) => {
72
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "loading", style: { width: size, height: size }, "aria-label": "Loading" });
73
+ };
74
+ var ToastProvider = () => /* @__PURE__ */ jsxRuntime.jsx(
75
+ reactToastify.ToastContainer,
76
+ {
77
+ position: "bottom-right",
78
+ autoClose: 2e3,
79
+ hideProgressBar: false,
80
+ newestOnTop: false,
81
+ closeOnClick: true,
82
+ rtl: false,
83
+ pauseOnFocusLoss: true,
84
+ draggable: true,
85
+ pauseOnHover: true,
86
+ theme: "light",
87
+ transition: reactToastify.Slide
88
+ }
89
+ );
90
+ var useToast = () => {
91
+ return {
92
+ success: (msg) => reactToastify.toast.success(msg),
93
+ error: (msg) => reactToastify.toast.error(msg),
94
+ warning: (msg) => reactToastify.toast.warning(msg),
95
+ info: (msg) => reactToastify.toast.info(msg),
96
+ message: (msg) => reactToastify.toast(msg)
97
+ };
98
+ };
99
+ var Checkbox = ({ label, size = "md", indeterminate, className, ...props }) => {
100
+ const ref = React5__namespace.useRef(null);
101
+ React5__namespace.useEffect(() => {
102
+ if (ref.current) ref.current.indeterminate = !!indeterminate;
103
+ }, [indeterminate]);
104
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: ["checkbox", `checkbox--${size}`, className].filter(Boolean).join(" "), children: [
105
+ /* @__PURE__ */ jsxRuntime.jsx("input", { ref, type: "checkbox", className: "checkbox__input", ...props }),
106
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "checkbox__box", "aria-hidden": true }),
107
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "checkbox__label", children: label })
108
+ ] });
109
+ };
110
+ var FileInput = ({ label = "Choose file", onFiles, className, ...props }) => {
111
+ const id = React5__namespace.useId();
112
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ["file", className].filter(Boolean).join(" "), children: [
113
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id, type: "file", className: "file__input", onChange: (e) => onFiles?.(e.currentTarget.files), ...props }),
114
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, className: "file__label", children: label })
115
+ ] });
116
+ };
117
+ var Radio = ({ label, size = "md", className, ...props }) => {
118
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: ["radio", `radio--${size}`, className].filter(Boolean).join(" "), children: [
119
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "radio", className: "radio__input", ...props }),
120
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "radio__dot", "aria-hidden": true }),
121
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "radio__label", children: label })
122
+ ] });
123
+ };
124
+ var Switch = ({ checked, defaultChecked, onChange, size = "md", disabled, className, ...props }) => {
125
+ const controlled = checked !== void 0;
126
+ const [inner, setInner] = React5__namespace.useState(!!defaultChecked);
127
+ const on = controlled ? !!checked : inner;
128
+ const toggle = () => {
129
+ if (disabled) return;
130
+ const next = !on;
131
+ if (!controlled) setInner(next);
132
+ onChange?.(next);
133
+ };
134
+ return /* @__PURE__ */ jsxRuntime.jsx(
135
+ "button",
136
+ {
137
+ type: "button",
138
+ role: "switch",
139
+ "aria-checked": on,
140
+ disabled,
141
+ onClick: toggle,
142
+ className: ["switch", `switch--${size}`, on && "is-on", disabled && "is-disabled", className].filter(Boolean).join(" "),
143
+ ...props,
144
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "switch__thumb" })
145
+ }
146
+ );
147
+ };
148
+ var TextField = React5__namespace.forwardRef(
149
+ ({
150
+ id,
151
+ label,
152
+ helperText,
153
+ error,
154
+ success,
155
+ variant = "outline",
156
+ size = "md",
157
+ leftIcon,
158
+ rightIcon,
159
+ fullWidth,
160
+ className,
161
+ ...props
162
+ }, ref) => {
163
+ const inputId = id ?? React5__namespace.useId();
164
+ const helperId = helperText ? `${inputId}-help` : void 0;
165
+ const classNames = [
166
+ "tf__input",
167
+ `tf__input--${variant}`,
168
+ `tf__input--${size}`,
169
+ leftIcon && "tf__input--with-left",
170
+ rightIcon && "tf__input--with-right",
171
+ error && "tf__input--error",
172
+ success && "tf__input--success",
173
+ className
174
+ ].filter(Boolean).join(" ");
175
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tf", style: fullWidth ? { width: "100%" } : void 0, children: [
176
+ label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "tf__label", htmlFor: inputId, children: label }),
177
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tf__wrapper", children: [
178
+ leftIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tf__icon tf__icon--left", children: leftIcon }),
179
+ /* @__PURE__ */ jsxRuntime.jsx(
180
+ "input",
181
+ {
182
+ id: inputId,
183
+ ref,
184
+ className: classNames,
185
+ "aria-invalid": !!error,
186
+ "aria-describedby": helperId,
187
+ ...props
188
+ }
189
+ ),
190
+ rightIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tf__icon tf__icon--right", children: rightIcon })
191
+ ] }),
192
+ helperText && /* @__PURE__ */ jsxRuntime.jsx(
193
+ "div",
194
+ {
195
+ id: helperId,
196
+ className: `tf__helper ${error ? "tf__helper--error" : ""}`,
197
+ children: helperText
198
+ }
199
+ )
200
+ ] });
201
+ }
202
+ );
203
+ TextField.displayName = "TextField";
204
+ var Button = ({
205
+ variant = "primary",
206
+ size = "md",
207
+ className,
208
+ ...props
209
+ }) => {
210
+ const classes = ["btn", `btn--${variant}`, `btn--${size}`, className].filter(Boolean).join(" ");
211
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { className: classes, ...props });
212
+ };
213
+ function Select({
214
+ id,
215
+ label,
216
+ placeholder = "Select\u2026",
217
+ options,
218
+ value,
219
+ onChange,
220
+ defaultValue = null,
221
+ disabled,
222
+ size = "md",
223
+ variant = "outline",
224
+ fullWidth,
225
+ className
226
+ }) {
227
+ const internalId = React5__namespace.useId();
228
+ const selectId = id ?? internalId;
229
+ const isControlled = value !== void 0;
230
+ const [internal, setInternal] = React5__namespace.useState(defaultValue);
231
+ const currentValue = isControlled ? value ?? null : internal;
232
+ const [open, setOpen] = React5__namespace.useState(false);
233
+ const [activeIndex, setActiveIndex] = React5__namespace.useState(-1);
234
+ const wrapperRef = React5__namespace.useRef(null);
235
+ const listRef = React5__namespace.useRef(null);
236
+ const currentOption = React5__namespace.useMemo(
237
+ () => options.find((o) => o.value === currentValue) ?? null,
238
+ [options, currentValue]
239
+ );
240
+ const setValue = React5__namespace.useCallback(
241
+ (next) => {
242
+ const opt = options.find((o) => o.value === next) ?? null;
243
+ if (!isControlled) setInternal(next);
244
+ onChange?.(next, opt);
245
+ },
246
+ [isControlled, onChange, options]
247
+ );
248
+ React5__namespace.useEffect(() => {
249
+ const onDocClick = (e) => {
250
+ if (!wrapperRef.current) return;
251
+ if (!wrapperRef.current.contains(e.target)) setOpen(false);
252
+ };
253
+ document.addEventListener("mousedown", onDocClick);
254
+ return () => document.removeEventListener("mousedown", onDocClick);
255
+ }, []);
256
+ const moveActive = (dir) => {
257
+ if (!open) {
258
+ setOpen(true);
259
+ return;
260
+ }
261
+ let i = activeIndex;
262
+ const len = options.length;
263
+ for (let step = 0; step < len; step++) {
264
+ i = (i + dir + len) % len;
265
+ if (!options[i].disabled) {
266
+ setActiveIndex(i);
267
+ break;
268
+ }
269
+ }
270
+ };
271
+ const commitActive = () => {
272
+ if (activeIndex < 0 || activeIndex >= options.length) return;
273
+ const opt = options[activeIndex];
274
+ if (!opt.disabled) {
275
+ setValue(opt.value);
276
+ setOpen(false);
277
+ }
278
+ };
279
+ const onKeyDown = (e) => {
280
+ if (disabled) return;
281
+ switch (e.key) {
282
+ case " ":
283
+ case "Enter":
284
+ e.preventDefault();
285
+ if (!open) setOpen(true);
286
+ else commitActive();
287
+ break;
288
+ case "ArrowDown":
289
+ e.preventDefault();
290
+ moveActive(1);
291
+ break;
292
+ case "ArrowUp":
293
+ e.preventDefault();
294
+ moveActive(-1);
295
+ break;
296
+ case "Home":
297
+ e.preventDefault();
298
+ setOpen(true);
299
+ setActiveIndex(options.findIndex((o) => !o.disabled));
300
+ break;
301
+ case "End":
302
+ e.preventDefault();
303
+ setOpen(true);
304
+ for (let i = options.length - 1; i >= 0; i--) {
305
+ if (!options[i].disabled) {
306
+ setActiveIndex(i);
307
+ break;
308
+ }
309
+ }
310
+ break;
311
+ case "Escape":
312
+ e.preventDefault();
313
+ setOpen(false);
314
+ break;
315
+ }
316
+ };
317
+ React5__namespace.useEffect(() => {
318
+ if (open) {
319
+ const idx = Math.max(0, options.findIndex((o) => o.value === currentValue && !o.disabled));
320
+ setActiveIndex(idx === -1 ? 0 : idx);
321
+ }
322
+ }, [open, options, currentValue]);
323
+ return /* @__PURE__ */ jsxRuntime.jsxs(
324
+ "div",
325
+ {
326
+ ref: wrapperRef,
327
+ className: `select${className ? ` ${className}` : ""}`,
328
+ style: fullWidth ? { width: "100%" } : void 0,
329
+ children: [
330
+ label && /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: selectId, className: "select__label", children: label }),
331
+ /* @__PURE__ */ jsxRuntime.jsxs(
332
+ "button",
333
+ {
334
+ id: selectId,
335
+ type: "button",
336
+ className: [
337
+ "select__control",
338
+ `select__control--${variant}`,
339
+ `select__control--${size}`,
340
+ open && "is-open",
341
+ disabled && "is-disabled"
342
+ ].filter(Boolean).join(" "),
343
+ "aria-haspopup": "listbox",
344
+ "aria-expanded": open,
345
+ "aria-controls": `${selectId}-listbox`,
346
+ onClick: () => !disabled && setOpen((o) => !o),
347
+ onKeyDown,
348
+ disabled,
349
+ children: [
350
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: currentOption ? "select__value" : "select__placeholder", children: currentOption ? currentOption.label : placeholder }),
351
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "select__icon", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: 16 }) })
352
+ ]
353
+ }
354
+ ),
355
+ open && /* @__PURE__ */ jsxRuntime.jsx(
356
+ "ul",
357
+ {
358
+ ref: listRef,
359
+ id: `${selectId}-listbox`,
360
+ role: "listbox",
361
+ className: "select__list",
362
+ children: options.map((opt, i) => {
363
+ const selected = currentValue === opt.value;
364
+ const active = i === activeIndex;
365
+ return /* @__PURE__ */ jsxRuntime.jsxs(
366
+ "li",
367
+ {
368
+ role: "option",
369
+ "aria-selected": selected,
370
+ className: [
371
+ "select__option",
372
+ selected && "is-selected",
373
+ active && "is-active",
374
+ opt.disabled && "is-disabled"
375
+ ].filter(Boolean).join(" "),
376
+ onMouseEnter: () => !opt.disabled && setActiveIndex(i),
377
+ onClick: () => {
378
+ if (opt.disabled) return;
379
+ setValue(opt.value);
380
+ setOpen(false);
381
+ },
382
+ children: [
383
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: opt.label }),
384
+ selected && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 16, "aria-hidden": true })
385
+ ]
386
+ },
387
+ opt.value
388
+ );
389
+ })
390
+ }
391
+ )
392
+ ]
393
+ }
394
+ );
395
+ }
396
+ var Pagination = ({ page, total, onChange, siblingCount = 1 }) => {
397
+ const clamp = (n) => Math.max(1, Math.min(total, n));
398
+ const range = (s, e) => Array.from({ length: e - s + 1 }, (_, i) => s + i);
399
+ const start = Math.max(2, page - siblingCount);
400
+ const end = Math.min(total - 1, page + siblingCount);
401
+ const mid = range(start, end);
402
+ const pages = [
403
+ 1,
404
+ ...start > 2 ? ["\u2026"] : [],
405
+ ...mid,
406
+ ...end < total - 1 ? ["\u2026"] : [],
407
+ total
408
+ ].filter((x, i, arr) => typeof x === "number" ? arr.indexOf(x) === i : true);
409
+ return /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "pagination", "aria-label": "Pagination", children: [
410
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "pagination__item", onClick: () => onChange(clamp(page - 1)), disabled: page <= 1, children: "Prev" }),
411
+ pages.map(
412
+ (p, i) => typeof p === "number" ? /* @__PURE__ */ jsxRuntime.jsx(
413
+ "button",
414
+ {
415
+ className: ["pagination__item", p === page && "is-active"].filter(Boolean).join(" "),
416
+ onClick: () => onChange(p),
417
+ children: p
418
+ },
419
+ i
420
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pagination__ellipsis", children: "\u2026" }, i)
421
+ ),
422
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "pagination__item", onClick: () => onChange(clamp(page + 1)), disabled: page >= total, children: "Next" })
423
+ ] });
424
+ };
425
+ var Sidebar = ({
426
+ items = [],
427
+ activeKey,
428
+ onItemSelect,
429
+ width = 240,
430
+ collapsible = true,
431
+ defaultCollapsed,
432
+ className,
433
+ style
434
+ }) => {
435
+ const [collapsed, setCollapsed] = React5__namespace.useState(!!defaultCollapsed);
436
+ const list = Array.isArray(items) ? items : [];
437
+ return /* @__PURE__ */ jsxRuntime.jsxs(
438
+ "aside",
439
+ {
440
+ className: ["sidebar", collapsed && "is-collapsed", className].filter(Boolean).join(" "),
441
+ style: { width: collapsed ? 64 : width, ...style },
442
+ children: [
443
+ collapsible && /* @__PURE__ */ jsxRuntime.jsx(
444
+ "button",
445
+ {
446
+ type: "button",
447
+ className: "sidebar__toggle",
448
+ onClick: () => setCollapsed((c) => !c),
449
+ "aria-label": "Toggle sidebar"
450
+ }
451
+ ),
452
+ /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "sidebar__nav", children: list.map((it) => /* @__PURE__ */ jsxRuntime.jsxs(
453
+ "button",
454
+ {
455
+ type: "button",
456
+ className: ["sidebar__item", activeKey === it.key && "is-active"].filter(Boolean).join(" "),
457
+ onClick: () => onItemSelect?.(it.key),
458
+ title: typeof it.label === "string" ? it.label : void 0,
459
+ children: [
460
+ it.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sidebar__icon", children: React5__namespace.createElement(it.icon, { size: 16 }) }),
461
+ !collapsed && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sidebar__label", children: it.label })
462
+ ]
463
+ },
464
+ it.key
465
+ )) })
466
+ ]
467
+ }
468
+ );
469
+ };
470
+ var Modal = ({
471
+ open,
472
+ onClose,
473
+ closeOnOverlay = true,
474
+ width = 520,
475
+ title,
476
+ children,
477
+ ...props
478
+ }) => {
479
+ React5__namespace.useEffect(() => {
480
+ const onKey = (e) => e.key === "Escape" && onClose?.();
481
+ if (open) document.addEventListener("keydown", onKey);
482
+ return () => document.removeEventListener("keydown", onKey);
483
+ }, [open, onClose]);
484
+ if (!open) return null;
485
+ return /* @__PURE__ */ jsxRuntime.jsx(
486
+ "div",
487
+ {
488
+ className: "modal",
489
+ role: "dialog",
490
+ "aria-modal": "true",
491
+ onClick: () => closeOnOverlay && onClose?.(),
492
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
493
+ "div",
494
+ {
495
+ className: "modal__panel",
496
+ style: { width },
497
+ onClick: (e) => e.stopPropagation(),
498
+ ...props,
499
+ children: [
500
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "modal__header", children: title }),
501
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "modal__body", children })
502
+ ]
503
+ }
504
+ )
505
+ }
506
+ );
507
+ };
508
+
509
+ exports.Alert = Alert;
510
+ exports.Button = Button;
511
+ exports.Card = Card;
512
+ exports.Checkbox = Checkbox;
513
+ exports.FileInput = FileInput;
514
+ exports.Loading = Loading;
515
+ exports.Modal = Modal;
516
+ exports.Pagination = Pagination;
517
+ exports.Radio = Radio;
518
+ exports.Select = Select;
519
+ exports.Sidebar = Sidebar;
520
+ exports.Switch = Switch;
521
+ exports.TextField = TextField;
522
+ exports.ToastProvider = ToastProvider;
523
+ exports.useToast = useToast;