@jho951/ui-components 0.1.0 → 0.1.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.
@@ -0,0 +1,1036 @@
1
+ import {
2
+ SVG_ASSETS
3
+ } from "./chunk-KLEL7SOM.js";
4
+ import {
5
+ useScrollLock
6
+ } from "./chunk-VRIA2QTM.js";
7
+ import {
8
+ cn,
9
+ ensurePortalRoot,
10
+ generateId
11
+ } from "./chunk-HW6ZBRLQ.js";
12
+
13
+ // ui/icon/Icon.tsx
14
+ import React2 from "react";
15
+
16
+ // ui/icon/Icon.module.css
17
+ var Icon_default = {};
18
+
19
+ // ui/icon/Icon.util.ts
20
+ import React from "react";
21
+ var svgCache = /* @__PURE__ */ new Map();
22
+ function getRegistryIcon(name, registry) {
23
+ if (!registry) return void 0;
24
+ return registry[name];
25
+ }
26
+ function isExternalSvgPath(p) {
27
+ return /^https?:\/\//.test(p) || p.startsWith("/");
28
+ }
29
+ function resolveIconSrc(name, src, basePath = "/assert/icons", ext = "svg") {
30
+ if (src) return src;
31
+ if (isExternalSvgPath(name)) return name;
32
+ return `${basePath}/${name}.${ext}`;
33
+ }
34
+ function getAriaProps(title) {
35
+ return title ? { role: "img", "aria-label": title } : { "aria-hidden": true };
36
+ }
37
+ function useInlineSvg(src) {
38
+ const [svgText, setSvgText] = React.useState(null);
39
+ React.useEffect(() => {
40
+ let alive = true;
41
+ if (!src) {
42
+ setSvgText(null);
43
+ return;
44
+ }
45
+ const cached = svgCache.get(src);
46
+ if (cached) {
47
+ setSvgText(cached);
48
+ return;
49
+ }
50
+ fetch(src).then((r) => r.ok ? r.text() : Promise.reject()).then((txt) => {
51
+ if (!alive) return;
52
+ svgCache.set(src, txt);
53
+ setSvgText(txt);
54
+ }).catch(() => {
55
+ if (!alive) return;
56
+ setSvgText(null);
57
+ });
58
+ return () => {
59
+ alive = false;
60
+ };
61
+ }, [src]);
62
+ return svgText;
63
+ }
64
+ function extractSvgInner(svgText) {
65
+ const m = svgText.match(/<svg[^>]*>([\s\S]*?)<\/svg>/i);
66
+ return m ? m[1] : svgText;
67
+ }
68
+ function extractViewBox(svgText, fallback = "0 0 24 24") {
69
+ const m = svgText.match(/viewBox="([^"]+)"/i);
70
+ return m ? m[1] : fallback;
71
+ }
72
+
73
+ // ui/icon/Icon.tsx
74
+ import { jsx, jsxs } from "react/jsx-runtime";
75
+ var DEFAULT_ICONS = Object.entries(SVG_ASSETS).reduce(
76
+ (acc, [name, content]) => {
77
+ const isString = typeof content === "string";
78
+ const isSvgContent = isString && content.includes("<svg");
79
+ acc[name] = {
80
+ vb: isSvgContent ? extractViewBox(content) : "0 0 24 24",
81
+ raw: isSvgContent ? extractSvgInner(content) : void 0,
82
+ src: !isSvgContent ? content : void 0
83
+ // 문자열이 아니면 경로로 간주
84
+ };
85
+ return acc;
86
+ },
87
+ {}
88
+ );
89
+ var Icon = ({
90
+ name,
91
+ size = 24,
92
+ title,
93
+ color,
94
+ source = "auto",
95
+ src,
96
+ basePath = "/assert/svg",
97
+ ext = "svg",
98
+ icons,
99
+ className,
100
+ style,
101
+ ...rest
102
+ }) => {
103
+ const registry = icons ?? DEFAULT_ICONS;
104
+ const regData = getRegistryIcon(String(name), registry);
105
+ const shouldUseRegistry = source === "registry" || source === "auto" && !!regData;
106
+ const ariaProps = getAriaProps(title);
107
+ const svgCommonProps = {
108
+ width: size,
109
+ height: size,
110
+ focusable: "false",
111
+ style: { color, ...style },
112
+ ...ariaProps,
113
+ ...rest
114
+ };
115
+ if (shouldUseRegistry && regData && (regData.raw || regData.g)) {
116
+ return /* @__PURE__ */ jsxs(
117
+ "svg",
118
+ {
119
+ viewBox: regData.vb,
120
+ className: cn(Icon_default.icon, Icon_default.registry, className),
121
+ ...svgCommonProps,
122
+ children: [
123
+ regData.g?.map(
124
+ ({ el, ...attrs }, i) => React2.createElement(el, { key: i, ...attrs })
125
+ ),
126
+ regData.raw && /* @__PURE__ */ jsx("g", { dangerouslySetInnerHTML: { __html: regData.raw } })
127
+ ]
128
+ }
129
+ );
130
+ }
131
+ const finalSrc = regData?.src || resolveIconSrc(String(name), src, basePath, ext);
132
+ const svgText = useInlineSvg(finalSrc);
133
+ if (!svgText) {
134
+ return /* @__PURE__ */ jsx(
135
+ "span",
136
+ {
137
+ className: cn(Icon_default.icon, Icon_default.placeholder, className),
138
+ style: { width: size, height: size, ...style },
139
+ "aria-hidden": true
140
+ }
141
+ );
142
+ }
143
+ return /* @__PURE__ */ jsx(
144
+ "svg",
145
+ {
146
+ viewBox: extractViewBox(svgText),
147
+ className: cn(Icon_default.icon, Icon_default.remote, className),
148
+ ...svgCommonProps,
149
+ dangerouslySetInnerHTML: { __html: extractSvgInner(svgText) }
150
+ }
151
+ );
152
+ };
153
+
154
+ // ui/arrow/Arrow.constant.ts
155
+ var DIRECTION_MAP = {
156
+ down: "0deg",
157
+ left: "90deg",
158
+ up: "180deg",
159
+ right: "-90deg"
160
+ };
161
+
162
+ // ui/arrow/Arrow.module.css
163
+ var Arrow_default = {};
164
+
165
+ // ui/arrow/Arrow.tsx
166
+ import { jsx as jsx2 } from "react/jsx-runtime";
167
+ var Arrow = ({ size = 24, direction = "down", className }) => {
168
+ return /* @__PURE__ */ jsx2("span", { className: cn(Arrow_default.container, className), style: { "--arrow-rotation": DIRECTION_MAP[direction] }, children: /* @__PURE__ */ jsx2(Icon, { name: "arrow", size }) });
169
+ };
170
+
171
+ // ui/input/Input.tsx
172
+ import { forwardRef, useId } from "react";
173
+
174
+ // ui/input/Input.module.css
175
+ var Input_default = {};
176
+
177
+ // ui/input/Input.tsx
178
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
179
+ var Input = forwardRef(
180
+ ({
181
+ label,
182
+ helperText,
183
+ error,
184
+ startIcon,
185
+ endIcon,
186
+ className,
187
+ fullWidth = false,
188
+ size = "m",
189
+ id,
190
+ disabled,
191
+ ...rest
192
+ }, ref) => {
193
+ const autoId = useId();
194
+ const inputId = id ?? autoId;
195
+ const helperId = helperText ? `${inputId}-helper` : void 0;
196
+ const errorId = error ? `${inputId}-error` : void 0;
197
+ const describedBy = [helperId, errorId].filter(Boolean).join(" ") || void 0;
198
+ return /* @__PURE__ */ jsxs2("div", { className: cn(Input_default.root, fullWidth && Input_default.fullWidth, className), children: [
199
+ label && /* @__PURE__ */ jsx3("label", { className: Input_default.label, htmlFor: inputId, children: label }),
200
+ /* @__PURE__ */ jsxs2("div", { className: cn(Input_default.control, Input_default[size], disabled && Input_default.disabled, error && Input_default.invalid), children: [
201
+ startIcon && /* @__PURE__ */ jsx3("span", { className: Input_default.icon, children: startIcon }),
202
+ /* @__PURE__ */ jsx3(
203
+ "input",
204
+ {
205
+ ref,
206
+ id: inputId,
207
+ className: Input_default.input,
208
+ "aria-invalid": Boolean(error) || void 0,
209
+ "aria-describedby": describedBy,
210
+ disabled,
211
+ ...rest
212
+ }
213
+ ),
214
+ endIcon && /* @__PURE__ */ jsx3("span", { className: Input_default.icon, children: endIcon })
215
+ ] }),
216
+ helperText && !error && /* @__PURE__ */ jsx3("span", { id: helperId, className: Input_default.helper, children: helperText }),
217
+ error && /* @__PURE__ */ jsx3("span", { id: errorId, className: Input_default.error, role: "alert", children: error })
218
+ ] });
219
+ }
220
+ );
221
+ Input.displayName = "Input";
222
+
223
+ // ui/select/Select.tsx
224
+ import { forwardRef as forwardRef2, useId as useId2 } from "react";
225
+
226
+ // ui/select/Select.module.css
227
+ var Select_default = {};
228
+
229
+ // ui/select/Select.tsx
230
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
231
+ var Select = forwardRef2(
232
+ ({
233
+ label,
234
+ helperText,
235
+ error,
236
+ options,
237
+ placeholder,
238
+ className,
239
+ fullWidth = false,
240
+ size = "m",
241
+ id,
242
+ disabled,
243
+ children,
244
+ ...rest
245
+ }, ref) => {
246
+ const autoId = useId2();
247
+ const selectId = id ?? autoId;
248
+ const helperId = helperText ? `${selectId}-helper` : void 0;
249
+ const errorId = error ? `${selectId}-error` : void 0;
250
+ const describedBy = [helperId, errorId].filter(Boolean).join(" ") || void 0;
251
+ return /* @__PURE__ */ jsxs3("div", { className: cn(Select_default.root, fullWidth && Select_default.fullWidth, className), children: [
252
+ label && /* @__PURE__ */ jsx4("label", { className: Select_default.label, htmlFor: selectId, children: label }),
253
+ /* @__PURE__ */ jsxs3("div", { className: cn(Select_default.control, Select_default[size], disabled && Select_default.disabled, error && Select_default.invalid), children: [
254
+ /* @__PURE__ */ jsxs3(
255
+ "select",
256
+ {
257
+ ref,
258
+ id: selectId,
259
+ className: Select_default.select,
260
+ "aria-invalid": Boolean(error) || void 0,
261
+ "aria-describedby": describedBy,
262
+ disabled,
263
+ ...rest,
264
+ children: [
265
+ placeholder && /* @__PURE__ */ jsx4("option", { value: "", disabled: true, children: placeholder }),
266
+ options ? options.map((option) => /* @__PURE__ */ jsx4("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value)) : children
267
+ ]
268
+ }
269
+ ),
270
+ /* @__PURE__ */ jsx4("span", { className: Select_default.caret, "aria-hidden": true, children: "\u25BE" })
271
+ ] }),
272
+ helperText && !error && /* @__PURE__ */ jsx4("span", { id: helperId, className: Select_default.helper, children: helperText }),
273
+ error && /* @__PURE__ */ jsx4("span", { id: errorId, className: Select_default.error, role: "alert", children: error })
274
+ ] });
275
+ }
276
+ );
277
+ Select.displayName = "Select";
278
+
279
+ // ui/dropdown/Dropdown.tsx
280
+ import { useEffect, useId as useId3, useRef, useState } from "react";
281
+
282
+ // ui/dropdown/Dropdown.module.css
283
+ var Dropdown_default = {};
284
+
285
+ // ui/dropdown/Dropdown.tsx
286
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
287
+ var Dropdown = ({
288
+ label,
289
+ items,
290
+ value,
291
+ placeholder = "Select",
292
+ size = "m",
293
+ align = "start",
294
+ disabled = false,
295
+ onSelect
296
+ }) => {
297
+ const [open, setOpen] = useState(false);
298
+ const [internalValue, setInternalValue] = useState(value);
299
+ const triggerRef = useRef(null);
300
+ const menuRef = useRef(null);
301
+ const baseId = useId3();
302
+ const currentValue = value ?? internalValue;
303
+ const selectedItem = items.find((item) => item.value === currentValue);
304
+ useEffect(() => {
305
+ if (value !== void 0) {
306
+ setInternalValue(value);
307
+ }
308
+ }, [value]);
309
+ useEffect(() => {
310
+ if (!open) return;
311
+ const handleClick = (event) => {
312
+ const target = event.target;
313
+ if (!menuRef.current?.contains(target) && !triggerRef.current?.contains(target)) {
314
+ setOpen(false);
315
+ }
316
+ };
317
+ const handleKey = (event) => {
318
+ if (event.key === "Escape") {
319
+ setOpen(false);
320
+ triggerRef.current?.focus();
321
+ }
322
+ };
323
+ document.addEventListener("mousedown", handleClick);
324
+ document.addEventListener("keydown", handleKey);
325
+ return () => {
326
+ document.removeEventListener("mousedown", handleClick);
327
+ document.removeEventListener("keydown", handleKey);
328
+ };
329
+ }, [open]);
330
+ const handleSelect = (nextValue) => {
331
+ if (disabled) return;
332
+ setInternalValue(nextValue);
333
+ onSelect?.(nextValue);
334
+ setOpen(false);
335
+ triggerRef.current?.focus();
336
+ };
337
+ return /* @__PURE__ */ jsxs4("div", { className: Dropdown_default.root, children: [
338
+ label && /* @__PURE__ */ jsx5("span", { className: Dropdown_default.label, children: label }),
339
+ /* @__PURE__ */ jsxs4(
340
+ "button",
341
+ {
342
+ type: "button",
343
+ ref: triggerRef,
344
+ className: cn(Dropdown_default.trigger, Dropdown_default[size], Dropdown_default[align], disabled && Dropdown_default.disabled),
345
+ "aria-haspopup": "menu",
346
+ "aria-expanded": open,
347
+ "aria-controls": `${baseId}-menu`,
348
+ onClick: () => !disabled && setOpen((prev) => !prev),
349
+ children: [
350
+ /* @__PURE__ */ jsx5("span", { className: Dropdown_default.value, children: selectedItem ? selectedItem.label : placeholder }),
351
+ /* @__PURE__ */ jsx5("span", { className: Dropdown_default.caret, "aria-hidden": true, children: "\u25BE" })
352
+ ]
353
+ }
354
+ ),
355
+ open && /* @__PURE__ */ jsx5(
356
+ "ul",
357
+ {
358
+ id: `${baseId}-menu`,
359
+ role: "menu",
360
+ ref: menuRef,
361
+ className: cn(Dropdown_default.menu, Dropdown_default[align]),
362
+ children: items.map((item) => /* @__PURE__ */ jsx5("li", { role: "none", children: /* @__PURE__ */ jsx5(
363
+ "button",
364
+ {
365
+ type: "button",
366
+ role: "menuitem",
367
+ className: cn(Dropdown_default.item, item.disabled && Dropdown_default.itemDisabled),
368
+ onClick: () => !item.disabled && handleSelect(item.value),
369
+ disabled: item.disabled,
370
+ children: item.label
371
+ }
372
+ ) }, item.value))
373
+ }
374
+ )
375
+ ] });
376
+ };
377
+
378
+ // ui/tooltip/Tooltip.tsx
379
+ import { useId as useId4 } from "react";
380
+
381
+ // ui/tooltip/Tooltip.module.css
382
+ var Tooltip_default = {};
383
+
384
+ // ui/tooltip/Tooltip.tsx
385
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
386
+ var Tooltip = ({ content, children, position = "top" }) => {
387
+ const tooltipId = useId4();
388
+ return /* @__PURE__ */ jsxs5("span", { className: Tooltip_default.wrapper, "aria-describedby": tooltipId, children: [
389
+ /* @__PURE__ */ jsx6("span", { className: Tooltip_default.target, tabIndex: 0, children }),
390
+ /* @__PURE__ */ jsx6("span", { id: tooltipId, role: "tooltip", className: cn(Tooltip_default.tooltip, Tooltip_default[position]), children: content })
391
+ ] });
392
+ };
393
+
394
+ // ui/toast/Toast.tsx
395
+ import { useEffect as useEffect2 } from "react";
396
+
397
+ // ui/toast/Toast.module.css
398
+ var Toast_default = {};
399
+
400
+ // ui/toast/Toast.tsx
401
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
402
+ var Toast = ({
403
+ open,
404
+ title,
405
+ message,
406
+ variant = "info",
407
+ duration = 3e3,
408
+ onClose
409
+ }) => {
410
+ useEffect2(() => {
411
+ if (!open || !duration) return;
412
+ const timer = window.setTimeout(() => onClose?.(), duration);
413
+ return () => window.clearTimeout(timer);
414
+ }, [open, duration, onClose]);
415
+ if (!open) return null;
416
+ const role = variant === "error" ? "alert" : "status";
417
+ return /* @__PURE__ */ jsxs6("div", { className: cn(Toast_default.toast, Toast_default[variant]), role, "aria-live": variant === "error" ? "assertive" : "polite", children: [
418
+ /* @__PURE__ */ jsxs6("div", { className: Toast_default.body, children: [
419
+ title && /* @__PURE__ */ jsx7("strong", { className: Toast_default.title, children: title }),
420
+ message && /* @__PURE__ */ jsx7("div", { className: Toast_default.message, children: message })
421
+ ] }),
422
+ onClose && /* @__PURE__ */ jsx7("button", { type: "button", className: Toast_default.close, onClick: onClose, "aria-label": "Close", children: "\xD7" })
423
+ ] });
424
+ };
425
+
426
+ // ui/tabs/Tabs.tsx
427
+ import { useEffect as useEffect3, useId as useId5, useMemo, useRef as useRef2, useState as useState2 } from "react";
428
+
429
+ // ui/tabs/Tabs.module.css
430
+ var Tabs_default = {};
431
+
432
+ // ui/tabs/Tabs.tsx
433
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
434
+ var Tabs = ({ items, value, defaultValue, onChange }) => {
435
+ const baseId = useId5();
436
+ const [internalValue, setInternalValue] = useState2(() => defaultValue ?? items[0]?.value ?? "");
437
+ const currentValue = value ?? internalValue;
438
+ const tabRefs = useRef2([]);
439
+ useEffect3(() => {
440
+ if (value !== void 0) {
441
+ setInternalValue(value);
442
+ }
443
+ }, [value]);
444
+ const activeIndex = useMemo(() => items.findIndex((item) => item.value === currentValue), [items, currentValue]);
445
+ const setValue = (nextValue) => {
446
+ setInternalValue(nextValue);
447
+ onChange?.(nextValue);
448
+ };
449
+ const handleKeyDown = (event) => {
450
+ if (!items.length) return;
451
+ const lastIndex = items.length - 1;
452
+ let nextIndex = activeIndex;
453
+ if (event.key === "ArrowRight") nextIndex = activeIndex === lastIndex ? 0 : activeIndex + 1;
454
+ if (event.key === "ArrowLeft") nextIndex = activeIndex <= 0 ? lastIndex : activeIndex - 1;
455
+ if (event.key === "Home") nextIndex = 0;
456
+ if (event.key === "End") nextIndex = lastIndex;
457
+ if (nextIndex !== activeIndex) {
458
+ event.preventDefault();
459
+ const nextItem = items[nextIndex];
460
+ if (!nextItem.disabled) {
461
+ setValue(nextItem.value);
462
+ tabRefs.current[nextIndex]?.focus();
463
+ }
464
+ }
465
+ };
466
+ return /* @__PURE__ */ jsxs7("div", { className: Tabs_default.root, children: [
467
+ /* @__PURE__ */ jsx8("div", { className: Tabs_default.tablist, role: "tablist", "aria-orientation": "horizontal", onKeyDown: handleKeyDown, children: items.map((item, index) => {
468
+ const selected = item.value === currentValue;
469
+ return /* @__PURE__ */ jsx8(
470
+ "button",
471
+ {
472
+ ref: (el) => tabRefs.current[index] = el,
473
+ role: "tab",
474
+ type: "button",
475
+ "aria-selected": selected,
476
+ "aria-controls": `${baseId}-panel-${item.value}`,
477
+ id: `${baseId}-tab-${item.value}`,
478
+ tabIndex: selected ? 0 : -1,
479
+ className: cn(Tabs_default.tab, selected && Tabs_default.active, item.disabled && Tabs_default.disabled),
480
+ disabled: item.disabled,
481
+ onClick: () => !item.disabled && setValue(item.value),
482
+ children: item.label
483
+ },
484
+ item.value
485
+ );
486
+ }) }),
487
+ items.map((item) => /* @__PURE__ */ jsx8(
488
+ "div",
489
+ {
490
+ role: "tabpanel",
491
+ id: `${baseId}-panel-${item.value}`,
492
+ "aria-labelledby": `${baseId}-tab-${item.value}`,
493
+ hidden: item.value !== currentValue,
494
+ className: Tabs_default.panel,
495
+ children: item.content
496
+ },
497
+ item.value
498
+ ))
499
+ ] });
500
+ };
501
+
502
+ // ui/accordion/Accordion.tsx
503
+ import { useMemo as useMemo2, useState as useState3 } from "react";
504
+
505
+ // ui/accordion/Accordion.module.css
506
+ var Accordion_default = {};
507
+
508
+ // ui/accordion/Accordion.tsx
509
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
510
+ var Accordion = ({ items, allowMultiple = false, defaultOpenIds = [] }) => {
511
+ const [openIds, setOpenIds] = useState3(defaultOpenIds);
512
+ const isOpen = (id) => openIds.includes(id);
513
+ const toggle = (id) => {
514
+ setOpenIds((prev) => {
515
+ if (prev.includes(id)) return prev.filter((openId) => openId !== id);
516
+ if (allowMultiple) return [...prev, id];
517
+ return [id];
518
+ });
519
+ };
520
+ const ids = useMemo2(() => new Set(items.map((item) => item.id)), [items]);
521
+ return /* @__PURE__ */ jsx9("div", { className: Accordion_default.root, children: items.map((item) => {
522
+ const open = isOpen(item.id);
523
+ return /* @__PURE__ */ jsxs8("div", { className: cn(Accordion_default.item, open && Accordion_default.open, item.disabled && Accordion_default.disabled), children: [
524
+ /* @__PURE__ */ jsxs8(
525
+ "button",
526
+ {
527
+ type: "button",
528
+ className: Accordion_default.trigger,
529
+ "aria-expanded": open,
530
+ "aria-controls": `${item.id}-panel`,
531
+ id: `${item.id}-header`,
532
+ disabled: item.disabled || !ids.has(item.id),
533
+ onClick: () => toggle(item.id),
534
+ children: [
535
+ /* @__PURE__ */ jsx9("span", { children: item.title }),
536
+ /* @__PURE__ */ jsx9("span", { className: Accordion_default.icon, "aria-hidden": true, children: open ? "\u2212" : "+" })
537
+ ]
538
+ }
539
+ ),
540
+ /* @__PURE__ */ jsx9(
541
+ "div",
542
+ {
543
+ id: `${item.id}-panel`,
544
+ role: "region",
545
+ "aria-labelledby": `${item.id}-header`,
546
+ className: Accordion_default.panel,
547
+ hidden: !open,
548
+ children: item.content
549
+ }
550
+ )
551
+ ] }, item.id);
552
+ }) });
553
+ };
554
+
555
+ // ui/table/Table.module.css
556
+ var Table_default = {};
557
+
558
+ // ui/table/Table.tsx
559
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
560
+ var Table = ({ columns, data, caption, striped = false, compact = false }) => {
561
+ return /* @__PURE__ */ jsx10("div", { className: Table_default.wrapper, children: /* @__PURE__ */ jsxs9("table", { className: cn(Table_default.table, striped && Table_default.striped, compact && Table_default.compact), children: [
562
+ caption && /* @__PURE__ */ jsx10("caption", { className: Table_default.caption, children: caption }),
563
+ /* @__PURE__ */ jsx10("thead", { children: /* @__PURE__ */ jsx10("tr", { children: columns.map((col) => /* @__PURE__ */ jsx10("th", { style: { textAlign: col.align, width: col.width }, children: col.header }, col.key)) }) }),
564
+ /* @__PURE__ */ jsx10("tbody", { children: data.map((row, idx) => /* @__PURE__ */ jsx10("tr", { children: columns.map((col) => /* @__PURE__ */ jsx10("td", { style: { textAlign: col.align }, children: row[col.key] }, col.key)) }, idx)) })
565
+ ] }) });
566
+ };
567
+
568
+ // ui/pagination/Pagination.module.css
569
+ var Pagination_default = {};
570
+
571
+ // ui/pagination/Pagination.tsx
572
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
573
+ var getRange = (current, total, siblingCount) => {
574
+ const totalNumbers = siblingCount * 2 + 3;
575
+ const totalBlocks = totalNumbers + 2;
576
+ if (total <= totalBlocks) {
577
+ return Array.from({ length: total }, (_, i) => i + 1);
578
+ }
579
+ const leftSibling = Math.max(current - siblingCount, 1);
580
+ const rightSibling = Math.min(current + siblingCount, total);
581
+ const showLeftEllipsis = leftSibling > 2;
582
+ const showRightEllipsis = rightSibling < total - 1;
583
+ if (!showLeftEllipsis && showRightEllipsis) {
584
+ const leftRange = Array.from({ length: totalNumbers }, (_, i) => i + 1);
585
+ return [...leftRange, "...", total];
586
+ }
587
+ if (showLeftEllipsis && !showRightEllipsis) {
588
+ const rightRange = Array.from({ length: totalNumbers }, (_, i) => total - totalNumbers + 1 + i);
589
+ return [1, "...", ...rightRange];
590
+ }
591
+ return [1, "...", ...Array.from({ length: rightSibling - leftSibling + 1 }, (_, i) => leftSibling + i), "...", total];
592
+ };
593
+ var Pagination = ({ currentPage, totalPages, onPageChange, siblingCount = 1 }) => {
594
+ const pages = getRange(currentPage, totalPages, siblingCount);
595
+ return /* @__PURE__ */ jsxs10("nav", { className: Pagination_default.nav, "aria-label": "Pagination", children: [
596
+ /* @__PURE__ */ jsx11(
597
+ "button",
598
+ {
599
+ type: "button",
600
+ className: Pagination_default.arrow,
601
+ onClick: () => onPageChange(Math.max(1, currentPage - 1)),
602
+ disabled: currentPage === 1,
603
+ children: "Prev"
604
+ }
605
+ ),
606
+ /* @__PURE__ */ jsx11("ul", { className: Pagination_default.list, children: pages.map((page, index) => /* @__PURE__ */ jsx11("li", { children: page === "..." ? /* @__PURE__ */ jsx11("span", { className: Pagination_default.ellipsis, children: "\u2026" }) : /* @__PURE__ */ jsx11(
607
+ "button",
608
+ {
609
+ type: "button",
610
+ className: cn(Pagination_default.page, page === currentPage && Pagination_default.active),
611
+ onClick: () => onPageChange(page),
612
+ "aria-current": page === currentPage ? "page" : void 0,
613
+ children: page
614
+ }
615
+ ) }, `${page}-${index}`)) }),
616
+ /* @__PURE__ */ jsx11(
617
+ "button",
618
+ {
619
+ type: "button",
620
+ className: Pagination_default.arrow,
621
+ onClick: () => onPageChange(Math.min(totalPages, currentPage + 1)),
622
+ disabled: currentPage === totalPages,
623
+ children: "Next"
624
+ }
625
+ )
626
+ ] });
627
+ };
628
+
629
+ // ui/breadcrumb/Breadcrumb.module.css
630
+ var Breadcrumb_default = {};
631
+
632
+ // ui/breadcrumb/Breadcrumb.tsx
633
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
634
+ var Breadcrumb = ({ items }) => {
635
+ return /* @__PURE__ */ jsx12("nav", { "aria-label": "Breadcrumb", className: Breadcrumb_default.nav, children: /* @__PURE__ */ jsx12("ol", { className: Breadcrumb_default.list, children: items.map((item, index) => {
636
+ const isLast = index === items.length - 1;
637
+ const content = item.href ? /* @__PURE__ */ jsx12("a", { href: item.href, onClick: item.onClick, className: Breadcrumb_default.link, children: item.label }) : /* @__PURE__ */ jsx12("span", { className: cn(Breadcrumb_default.link, item.current && Breadcrumb_default.current), children: item.label });
638
+ return /* @__PURE__ */ jsxs11("li", { className: Breadcrumb_default.item, children: [
639
+ content,
640
+ !isLast && /* @__PURE__ */ jsx12("span", { className: Breadcrumb_default.separator, children: "/" })
641
+ ] }, `${item.label}-${index}`);
642
+ }) }) });
643
+ };
644
+
645
+ // ui/avatar/Avatar.tsx
646
+ import { useMemo as useMemo3 } from "react";
647
+
648
+ // ui/avatar/Avatar.module.css
649
+ var Avatar_default = {};
650
+
651
+ // ui/avatar/Avatar.tsx
652
+ import { jsx as jsx13 } from "react/jsx-runtime";
653
+ var Avatar = ({ src, alt, name, size = "m", shape = "circle", className, ...rest }) => {
654
+ const initials = useMemo3(() => {
655
+ if (!name) return "";
656
+ return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase()).join("");
657
+ }, [name]);
658
+ const sizeStyle = typeof size === "number" ? { width: size, height: size } : void 0;
659
+ return /* @__PURE__ */ jsx13(
660
+ "div",
661
+ {
662
+ className: cn(Avatar_default.avatar, Avatar_default[shape], typeof size === "string" && Avatar_default[size], className),
663
+ style: sizeStyle,
664
+ ...rest,
665
+ children: src ? /* @__PURE__ */ jsx13("img", { src, alt: alt ?? name ?? "avatar", className: Avatar_default.image }) : /* @__PURE__ */ jsx13("span", { className: Avatar_default.fallback, children: initials || "?" })
666
+ }
667
+ );
668
+ };
669
+
670
+ // ui/badge/Badge.module.css
671
+ var Badge_default = {};
672
+
673
+ // ui/badge/Badge.tsx
674
+ import { jsx as jsx14 } from "react/jsx-runtime";
675
+ var Badge = ({ children, variant = "default", size = "m", className, ...rest }) => {
676
+ return /* @__PURE__ */ jsx14("span", { className: cn(Badge_default.badge, Badge_default[variant], Badge_default[size], className), ...rest, children });
677
+ };
678
+
679
+ // ui/alert/Alert.module.css
680
+ var Alert_default = {};
681
+
682
+ // ui/alert/Alert.tsx
683
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
684
+ var Alert = ({ title, children, variant = "info", onClose }) => {
685
+ const role = variant === "error" ? "alert" : "status";
686
+ return /* @__PURE__ */ jsxs12("div", { className: cn(Alert_default.alert, Alert_default[variant]), role, "aria-live": variant === "error" ? "assertive" : "polite", children: [
687
+ /* @__PURE__ */ jsxs12("div", { className: Alert_default.body, children: [
688
+ title && /* @__PURE__ */ jsx15("div", { className: Alert_default.title, children: title }),
689
+ /* @__PURE__ */ jsx15("div", { className: Alert_default.content, children })
690
+ ] }),
691
+ onClose && /* @__PURE__ */ jsx15("button", { type: "button", className: Alert_default.close, onClick: onClose, "aria-label": "Close", children: "\xD7" })
692
+ ] });
693
+ };
694
+
695
+ // ui/skeleton/Skeleton.module.css
696
+ var Skeleton_default = {};
697
+
698
+ // ui/skeleton/Skeleton.tsx
699
+ import { jsx as jsx16 } from "react/jsx-runtime";
700
+ var Skeleton = ({ width = "100%", height = "1rem", circle = false, className, style, ...rest }) => {
701
+ return /* @__PURE__ */ jsx16(
702
+ "div",
703
+ {
704
+ className: cn(Skeleton_default.skeleton, circle && Skeleton_default.circle, className),
705
+ style: { width, height, ...style },
706
+ "aria-hidden": "true",
707
+ ...rest
708
+ }
709
+ );
710
+ };
711
+
712
+ // ui/backdrop/BackDrop.tsx
713
+ import { useEffect as useEffect4, useState as useState4 } from "react";
714
+
715
+ // ui/backdrop/BackDrop.module.css
716
+ var BackDrop_default = {};
717
+
718
+ // ui/backdrop/BackDrop.tsx
719
+ import { jsx as jsx17 } from "react/jsx-runtime";
720
+ var BackDrop = ({ visible = false, onClick, className, variant = "blur" }) => {
721
+ const [shouldRender, setRender] = useState4(visible);
722
+ useEffect4(() => {
723
+ if (visible) {
724
+ setRender(true);
725
+ document.body.style.overflow = "hidden";
726
+ } else {
727
+ const timer = setTimeout(() => {
728
+ setRender(false);
729
+ document.body.style.overflow = "auto";
730
+ }, 300);
731
+ return () => {
732
+ clearTimeout(timer);
733
+ document.body.style.overflow = "auto";
734
+ };
735
+ }
736
+ }, [visible]);
737
+ if (!shouldRender) return null;
738
+ return /* @__PURE__ */ jsx17(
739
+ "div",
740
+ {
741
+ className: cn(BackDrop_default.backdrop, BackDrop_default[variant], visible && BackDrop_default.isActive, className),
742
+ onClick,
743
+ "aria-hidden": !visible
744
+ }
745
+ );
746
+ };
747
+
748
+ // ui/button/Button.tsx
749
+ import { forwardRef as forwardRef3 } from "react";
750
+
751
+ // ui/spinner/Spinner.module.css
752
+ var Spinner_default = {};
753
+
754
+ // ui/spinner/Spinner.tsx
755
+ import { jsx as jsx18 } from "react/jsx-runtime";
756
+ var Spinner = ({ size = 24, className, label = "Loading" }) => {
757
+ return /* @__PURE__ */ jsx18("span", { className: cn(Spinner_default.spinner, className), role: "status", "aria-label": label, "aria-live": "polite", children: /* @__PURE__ */ jsx18(Icon, { name: "spinner", size }) });
758
+ };
759
+
760
+ // ui/button/Button.module.css
761
+ var Button_default = {};
762
+
763
+ // ui/button/Button.tsx
764
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
765
+ var BaseButton = forwardRef3(
766
+ ({ className, children, variant = "primary", size = "m", disabled = false, ...rest }, ref) => {
767
+ return /* @__PURE__ */ jsx19(
768
+ "button",
769
+ {
770
+ className: cn(Button_default.button, Button_default[variant], Button_default[size], disabled && Button_default.disabled, className),
771
+ disabled,
772
+ ...rest,
773
+ ref,
774
+ children: /* @__PURE__ */ jsx19("span", { className: Button_default.label, children })
775
+ }
776
+ );
777
+ }
778
+ );
779
+ BaseButton.displayName = "BaseButton";
780
+ var Button = forwardRef3(
781
+ ({ children, isLoading, leftIcon, rightIcon, ...rest }, ref) => {
782
+ if (isLoading) return /* @__PURE__ */ jsx19(Spinner, {});
783
+ return /* @__PURE__ */ jsxs13(BaseButton, { ref, ...rest, children: [
784
+ leftIcon && /* @__PURE__ */ jsx19("span", { className: Button_default.icon, children: leftIcon }),
785
+ children,
786
+ rightIcon && /* @__PURE__ */ jsx19("span", { className: Button_default.icon, children: rightIcon })
787
+ ] });
788
+ }
789
+ );
790
+ Button.displayName = "Button";
791
+
792
+ // ui/button/Button.constant.ts
793
+ var BUTTON_SIZES = ["s", "m", "l"];
794
+ var BUTTON_VARIANTS = ["primary", "secondary", "ghost", "text"];
795
+
796
+ // ui/card/Card.module.css
797
+ var Card_default = {};
798
+
799
+ // ui/card/Card.utils.tsx
800
+ import { jsx as jsx20 } from "react/jsx-runtime";
801
+ var createCardSection = (name) => {
802
+ const Section = ({ className, children, ...rest }) => {
803
+ return /* @__PURE__ */ jsx20("div", { className: cn(Card_default[name.toLowerCase()], className), ...rest, children });
804
+ };
805
+ Section.displayName = `Card.${name}`;
806
+ return Section;
807
+ };
808
+
809
+ // ui/card/Card.tsx
810
+ import { forwardRef as forwardRef4 } from "react";
811
+ import { jsx as jsx21 } from "react/jsx-runtime";
812
+ var CardRoot = forwardRef4(
813
+ ({ children, className, ...rest }, ref) => {
814
+ return /* @__PURE__ */ jsx21("section", { className: cn(Card_default.card, className), ...rest, ref, children });
815
+ }
816
+ );
817
+ var Card = CardRoot;
818
+ Card.Header = createCardSection("Header");
819
+ Card.Body = createCardSection("Body");
820
+ Card.Footer = createCardSection("Footer");
821
+ CardRoot.displayName = "Card";
822
+
823
+ // ui/checkbox/CheckBox.tsx
824
+ import { forwardRef as forwardRef5, useEffect as useEffect5, useImperativeHandle, useRef as useRef3 } from "react";
825
+
826
+ // ui/label/Label.module.css
827
+ var Label_default = {};
828
+
829
+ // ui/label/Label.tsx
830
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
831
+ var Label = ({ className, children, htmlFor, required = false, variant = "default", ...rest }) => {
832
+ return /* @__PURE__ */ jsxs14("label", { htmlFor, className: cn(Label_default.label, Label_default[variant], className), ...rest, children: [
833
+ children,
834
+ required && /* @__PURE__ */ jsx22(Icon, { className: Label_default.required, name: "required" })
835
+ ] });
836
+ };
837
+
838
+ // ui/checkbox/CheckBox.module.css
839
+ var CheckBox_default = {};
840
+
841
+ // ui/checkbox/CheckBox.tsx
842
+ import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
843
+ var Checkbox = forwardRef5(
844
+ ({ label, error, indeterminate, className, ...rest }, ref) => {
845
+ const id = rest.id || generateId();
846
+ const inputRef = useRef3(null);
847
+ useImperativeHandle(ref, () => inputRef.current);
848
+ useEffect5(() => {
849
+ if (inputRef.current) inputRef.current.indeterminate = !!indeterminate;
850
+ }, [indeterminate]);
851
+ return /* @__PURE__ */ jsxs15(Label, { htmlFor: id, className: cn(CheckBox_default.container, rest.disabled && CheckBox_default.disabled, className), children: [
852
+ /* @__PURE__ */ jsxs15("div", { className: CheckBox_default.wrapper, children: [
853
+ /* @__PURE__ */ jsx23("input", { id, className: "sr-only", type: "checkbox", ref: inputRef, ...rest }),
854
+ /* @__PURE__ */ jsx23("div", { className: cn(CheckBox_default.styledBox, error && CheckBox_default.error, rest.checked && CheckBox_default.checked, indeterminate && CheckBox_default.indeterminate), children: (rest.checked || indeterminate) && /* @__PURE__ */ jsx23(Icon, { name: indeterminate ? "indeterminate" : "check" }) })
855
+ ] }),
856
+ label && /* @__PURE__ */ jsx23("span", { className: CheckBox_default.labelText, children: label })
857
+ ] });
858
+ }
859
+ );
860
+ Checkbox.displayName = "Checkbox";
861
+
862
+ // ui/divider/Divider.module.css
863
+ var Divider_default = {};
864
+
865
+ // ui/divider/Divider.tsx
866
+ import { jsx as jsx24 } from "react/jsx-runtime";
867
+ var Divider = () => {
868
+ return /* @__PURE__ */ jsx24("section", { className: Divider_default.divider });
869
+ };
870
+
871
+ // ui/form/Form.module.css
872
+ var Form_default = {};
873
+
874
+ // ui/form/Form.tsx
875
+ import { jsx as jsx25 } from "react/jsx-runtime";
876
+ var Form = ({ children, onSubmit, className, ...rest }) => {
877
+ return /* @__PURE__ */ jsx25("form", { onSubmit, className: cn(Form_default.container, className), noValidate: true, ...rest, children });
878
+ };
879
+
880
+ // ui/modal/Modal.tsx
881
+ import { useCallback, useEffect as useEffect6, useRef as useRef4, useState as useState5 } from "react";
882
+ import { createPortal } from "react-dom";
883
+
884
+ // ui/modal/Modal.module.css
885
+ var Modal_default = {};
886
+
887
+ // ui/modal/Modal.tsx
888
+ import { jsx as jsx26, jsxs as jsxs16 } from "react/jsx-runtime";
889
+ var Modal = ({ isOpen, content, onClose, title = "\uBBF8\uB9AC\uBCF4\uAE30", size = "medium" }) => {
890
+ const [mounted, setMounted] = useState5(false);
891
+ const [visible, setVisible] = useState5(false);
892
+ const [portalElement, setPortalElement] = useState5(null);
893
+ const modalRef = useRef4(null);
894
+ useEffect6(() => {
895
+ if (!isOpen) {
896
+ setVisible(false);
897
+ const t = window.setTimeout(() => {
898
+ setMounted(false);
899
+ }, 300);
900
+ return () => window.clearTimeout(t);
901
+ }
902
+ setMounted(true);
903
+ setPortalElement(ensurePortalRoot("modal-root"));
904
+ const raf = requestAnimationFrame(() => setVisible(true));
905
+ return () => cancelAnimationFrame(raf);
906
+ }, [isOpen]);
907
+ useScrollLock(mounted);
908
+ const handleClose = useCallback(() => {
909
+ setVisible(false);
910
+ setTimeout(onClose, 300);
911
+ }, [onClose]);
912
+ useEffect6(() => {
913
+ if (!visible) return;
914
+ const handleKeyDown = (e) => {
915
+ if (e.key === "Escape") handleClose();
916
+ };
917
+ window.addEventListener("keydown", handleKeyDown);
918
+ modalRef.current?.focus();
919
+ return () => window.removeEventListener("keydown", handleKeyDown);
920
+ }, [visible, handleClose]);
921
+ if (!mounted || !portalElement) return null;
922
+ return createPortal(
923
+ /* @__PURE__ */ jsxs16("div", { className: Modal_default.container, role: "presentation", children: [
924
+ /* @__PURE__ */ jsx26(BackDrop, { className: Modal_default.overlay, visible, onClick: handleClose }),
925
+ /* @__PURE__ */ jsxs16(
926
+ "div",
927
+ {
928
+ className: cn(Modal_default.modal, Modal_default[size], visible && Modal_default.show),
929
+ ref: modalRef,
930
+ tabIndex: -1,
931
+ role: "dialog",
932
+ "aria-modal": "true",
933
+ "aria-labelledby": "modal-title",
934
+ onClick: (e) => e.stopPropagation(),
935
+ children: [
936
+ /* @__PURE__ */ jsxs16("section", { className: Modal_default.header, children: [
937
+ /* @__PURE__ */ jsx26("h2", { id: "modal-title", className: Modal_default.title, children: title }),
938
+ /* @__PURE__ */ jsx26(
939
+ "button",
940
+ {
941
+ onClick: handleClose,
942
+ className: Modal_default.closeBtn,
943
+ "aria-label": "\uBAA8\uB2EC \uB2EB\uAE30",
944
+ children: /* @__PURE__ */ jsx26("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx26("path", { d: "M18 6L6 18M6 6l12 12" }) })
945
+ }
946
+ )
947
+ ] }),
948
+ /* @__PURE__ */ jsx26("article", { className: Modal_default.content, children: /* @__PURE__ */ jsx26("div", { dangerouslySetInnerHTML: { __html: content } }) })
949
+ ]
950
+ }
951
+ )
952
+ ] }),
953
+ portalElement
954
+ );
955
+ };
956
+
957
+ // ui/tag/Tag.module.css
958
+ var Tag_default = {};
959
+
960
+ // ui/tag/Tag.tsx
961
+ import { jsx as jsx27 } from "react/jsx-runtime";
962
+ var Tag = ({ active, color = "default", children }) => {
963
+ return /* @__PURE__ */ jsx27("span", { className: cn(Tag_default.tag, Tag_default[color], active && Tag_default.active), children });
964
+ };
965
+
966
+ // ui/textarea/Textarea.tsx
967
+ import { forwardRef as forwardRef6, useEffect as useEffect7, useImperativeHandle as useImperativeHandle2, useRef as useRef5 } from "react";
968
+
969
+ // ui/textarea/Textarea.module.css
970
+ var Textarea_default = {};
971
+
972
+ // ui/textarea/Textarea.tsx
973
+ import { jsx as jsx28 } from "react/jsx-runtime";
974
+ var Textarea = forwardRef6(
975
+ ({ autoResize = true, className, value, ...rest }, ref) => {
976
+ const innerRef = useRef5(null);
977
+ useImperativeHandle2(ref, () => innerRef.current, []);
978
+ useEffect7(() => {
979
+ if (!autoResize || !innerRef.current) return;
980
+ const el = innerRef.current;
981
+ el.style.height = "auto";
982
+ el.style.height = `${el.scrollHeight}px`;
983
+ }, [value, autoResize]);
984
+ return /* @__PURE__ */ jsx28(
985
+ "textarea",
986
+ {
987
+ className: cn(Textarea_default.textarea, autoResize && Textarea_default.autoResize, className),
988
+ ref: innerRef,
989
+ value,
990
+ ...rest
991
+ }
992
+ );
993
+ }
994
+ );
995
+ Textarea.displayName = "Textarea";
996
+
997
+ export {
998
+ getRegistryIcon,
999
+ isExternalSvgPath,
1000
+ resolveIconSrc,
1001
+ getAriaProps,
1002
+ useInlineSvg,
1003
+ extractSvgInner,
1004
+ extractViewBox,
1005
+ Icon,
1006
+ DIRECTION_MAP,
1007
+ Arrow,
1008
+ Input,
1009
+ Select,
1010
+ Dropdown,
1011
+ Tooltip,
1012
+ Toast,
1013
+ Tabs,
1014
+ Accordion,
1015
+ Table,
1016
+ Pagination,
1017
+ Breadcrumb,
1018
+ Avatar,
1019
+ Badge,
1020
+ Alert,
1021
+ Skeleton,
1022
+ BackDrop,
1023
+ Spinner,
1024
+ Button,
1025
+ BUTTON_SIZES,
1026
+ BUTTON_VARIANTS,
1027
+ createCardSection,
1028
+ Card,
1029
+ Label,
1030
+ Checkbox,
1031
+ Divider,
1032
+ Form,
1033
+ Modal,
1034
+ Tag,
1035
+ Textarea
1036
+ };