@highpixel-co/palda-design-system 0.4.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,976 @@
1
+ import {
2
+ CloseIcon
3
+ } from "./chunk-TWYJ2UTC.js";
4
+
5
+ // ../../components/src/Alert/Alert.tsx
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ var TITLE_COLOR_CLASS = {
8
+ neutral: "palda-alert--neutral",
9
+ accent: "palda-alert--accent",
10
+ warning: "palda-alert--warning",
11
+ danger: "palda-alert--danger"
12
+ };
13
+ function Alert({
14
+ children,
15
+ className = "",
16
+ title,
17
+ icon,
18
+ action,
19
+ variant = "neutral",
20
+ ...props
21
+ }) {
22
+ const classes = ["palda-alert", TITLE_COLOR_CLASS[variant], className].filter(Boolean).join(" ");
23
+ const role = variant === "danger" ? "alert" : "status";
24
+ return /* @__PURE__ */ jsxs("div", { ...props, className: classes, role, children: [
25
+ icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "palda-alert__icon", children: icon }) : null,
26
+ /* @__PURE__ */ jsxs("div", { className: "palda-alert__content", children: [
27
+ title ? /* @__PURE__ */ jsx("p", { className: "palda-alert__title palda-text-body2-reading-semibold", children: title }) : null,
28
+ /* @__PURE__ */ jsx("div", { className: "palda-alert__body palda-text-body2-reading-medium", children }),
29
+ action ? /* @__PURE__ */ jsx("div", { className: "palda-alert__action", children: action }) : null
30
+ ] })
31
+ ] });
32
+ }
33
+
34
+ // ../../components/src/Badge/Badge.tsx
35
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
36
+ var TYPOGRAPHY_CLASS = {
37
+ md: "palda-text-label-regular-semibold",
38
+ sm: "palda-text-caption-regular-semibold"
39
+ };
40
+ function Badge({
41
+ children,
42
+ className = "",
43
+ variant = "primary",
44
+ icon,
45
+ size = "md",
46
+ ...props
47
+ }) {
48
+ const classes = [
49
+ "palda-badge",
50
+ `palda-badge--${variant}`,
51
+ `palda-badge--${size}`,
52
+ TYPOGRAPHY_CLASS[size],
53
+ className
54
+ ].filter(Boolean).join(" ");
55
+ return /* @__PURE__ */ jsxs2("span", { ...props, className: classes, children: [
56
+ icon ? /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", className: "palda-badge__icon", children: icon }) : null,
57
+ /* @__PURE__ */ jsx2("span", { className: "palda-badge__label", children })
58
+ ] });
59
+ }
60
+
61
+ // ../../components/src/Button/Button.tsx
62
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
63
+ var TYPOGRAPHY_CLASS2 = {
64
+ md: "palda-text-body2-regular-semibold",
65
+ sm: "palda-text-label-regular-semibold"
66
+ };
67
+ function Button({
68
+ children,
69
+ className = "",
70
+ leadingIcon,
71
+ size = "md",
72
+ trailingIcon,
73
+ type = "button",
74
+ variant = "primary",
75
+ ...props
76
+ }) {
77
+ const classes = [
78
+ "palda-button",
79
+ `palda-button--${variant}`,
80
+ `palda-button--${size}`,
81
+ TYPOGRAPHY_CLASS2[size],
82
+ className
83
+ ].filter(Boolean).join(" ");
84
+ return /* @__PURE__ */ jsxs3("button", { ...props, className: classes, type, children: [
85
+ leadingIcon ? /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", className: "palda-button__icon", children: leadingIcon }) : null,
86
+ /* @__PURE__ */ jsx3("span", { className: "palda-button__content", children }),
87
+ trailingIcon ? /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", className: "palda-button__icon", children: trailingIcon }) : null
88
+ ] });
89
+ }
90
+
91
+ // ../../components/src/Checkbox/Checkbox.tsx
92
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
93
+ var LABEL_TYPOGRAPHY_CLASS = "palda-text-body2-regular-medium";
94
+ function Checkbox({ children, className = "", disabled, ...props }) {
95
+ const classes = ["palda-checkbox", disabled ? "palda-checkbox--disabled" : "", className].filter(Boolean).join(" ");
96
+ return /* @__PURE__ */ jsxs4("label", { className: classes, children: [
97
+ /* @__PURE__ */ jsx4("input", { ...props, className: "palda-checkbox__input", disabled, type: "checkbox" }),
98
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", className: "palda-checkbox__control", children: /* @__PURE__ */ jsx4(
99
+ "svg",
100
+ {
101
+ className: "palda-checkbox__check",
102
+ fill: "none",
103
+ stroke: "currentColor",
104
+ strokeLinecap: "round",
105
+ strokeLinejoin: "round",
106
+ strokeWidth: "1.5",
107
+ viewBox: "0 0 20 20",
108
+ children: /* @__PURE__ */ jsx4("path", { d: "M16 5 7.75 14 4 9.90909" })
109
+ }
110
+ ) }),
111
+ children === void 0 || children === null || children === "" ? null : /* @__PURE__ */ jsx4("span", { className: `palda-checkbox__label ${LABEL_TYPOGRAPHY_CLASS}`, children })
112
+ ] });
113
+ }
114
+
115
+ // ../../components/src/Chip/Chip.tsx
116
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
117
+ var TYPOGRAPHY_CLASS3 = {
118
+ md: "palda-text-body2-regular-medium",
119
+ sm: "palda-text-label-regular-semibold"
120
+ };
121
+ function Chip({
122
+ children,
123
+ className = "",
124
+ icon,
125
+ selected = false,
126
+ size = "md",
127
+ variant = "primary",
128
+ ...props
129
+ }) {
130
+ const classes = [
131
+ "palda-chip",
132
+ `palda-chip--${variant}`,
133
+ `palda-chip--${size}`,
134
+ selected ? "palda-chip--selected" : "",
135
+ TYPOGRAPHY_CLASS3[size],
136
+ className
137
+ ].filter(Boolean).join(" ");
138
+ return /* @__PURE__ */ jsxs5("button", { ...props, "aria-pressed": selected, className: classes, type: "button", children: [
139
+ /* @__PURE__ */ jsx5("span", { className: "palda-chip__content", children }),
140
+ icon ? /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", className: "palda-chip__icon", children: icon }) : null
141
+ ] });
142
+ }
143
+
144
+ // ../../components/src/DropdownButton/DropdownButton.tsx
145
+ import { useEffect, useId, useRef, useState } from "react";
146
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
147
+ var TYPOGRAPHY_CLASS4 = "palda-text-body2-regular-medium";
148
+ function DropdownButton({
149
+ align = "start",
150
+ children,
151
+ className = "",
152
+ items,
153
+ onClick,
154
+ onKeyDown,
155
+ onSelect,
156
+ trailingIcon,
157
+ variant = "outline",
158
+ ...props
159
+ }) {
160
+ const [open, setOpen] = useState(false);
161
+ const [activeIndex, setActiveIndex] = useState(-1);
162
+ const rootRef = useRef(null);
163
+ const triggerRef = useRef(null);
164
+ const itemRefs = useRef([]);
165
+ const menuId = useId();
166
+ const enabled = items.map((item, index) => item.disabled ? -1 : index).filter((i) => i >= 0);
167
+ const first = enabled.length > 0 ? enabled[0] : -1;
168
+ const last = enabled.length > 0 ? enabled[enabled.length - 1] : -1;
169
+ useEffect(() => {
170
+ if (open) {
171
+ itemRefs.current[activeIndex]?.focus();
172
+ }
173
+ }, [activeIndex, open]);
174
+ useEffect(() => {
175
+ if (!open) {
176
+ return;
177
+ }
178
+ const handlePointerDown = (event) => {
179
+ if (!rootRef.current?.contains(event.target)) {
180
+ setOpen(false);
181
+ }
182
+ };
183
+ document.addEventListener("pointerdown", handlePointerDown);
184
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
185
+ }, [open]);
186
+ const openAt = (index) => {
187
+ setActiveIndex(index);
188
+ setOpen(true);
189
+ };
190
+ const close = () => {
191
+ setOpen(false);
192
+ setActiveIndex(-1);
193
+ };
194
+ const closeAndFocusTrigger = () => {
195
+ close();
196
+ triggerRef.current?.focus();
197
+ };
198
+ const handleTriggerClick = (event) => {
199
+ onClick?.(event);
200
+ if (open) {
201
+ close();
202
+ } else {
203
+ openAt(first);
204
+ }
205
+ };
206
+ const handleTriggerKeyDown = (event) => {
207
+ onKeyDown?.(event);
208
+ if (event.key === "ArrowDown") {
209
+ event.preventDefault();
210
+ openAt(first);
211
+ } else if (event.key === "ArrowUp") {
212
+ event.preventDefault();
213
+ openAt(last);
214
+ } else if (event.key === "Escape" && open) {
215
+ event.preventDefault();
216
+ close();
217
+ }
218
+ };
219
+ const handleMenuKeyDown = (event) => {
220
+ if (enabled.length === 0) {
221
+ if (event.key === "Escape") {
222
+ event.preventDefault();
223
+ closeAndFocusTrigger();
224
+ }
225
+ return;
226
+ }
227
+ const position = enabled.indexOf(activeIndex);
228
+ if (event.key === "ArrowDown") {
229
+ event.preventDefault();
230
+ setActiveIndex(enabled[(position + 1) % enabled.length]);
231
+ } else if (event.key === "ArrowUp") {
232
+ event.preventDefault();
233
+ setActiveIndex(enabled[(position - 1 + enabled.length) % enabled.length]);
234
+ } else if (event.key === "Home") {
235
+ event.preventDefault();
236
+ setActiveIndex(first);
237
+ } else if (event.key === "End") {
238
+ event.preventDefault();
239
+ setActiveIndex(last);
240
+ } else if (event.key === "Escape") {
241
+ event.preventDefault();
242
+ closeAndFocusTrigger();
243
+ }
244
+ };
245
+ const handleBlur = (event) => {
246
+ if (!event.currentTarget.contains(event.relatedTarget)) {
247
+ close();
248
+ }
249
+ };
250
+ const handleItemClick = (item) => {
251
+ onSelect?.(item.value);
252
+ closeAndFocusTrigger();
253
+ };
254
+ const classes = [
255
+ "palda-dropdown-button",
256
+ `palda-dropdown-button--${variant}`,
257
+ `palda-dropdown-button--${align}`,
258
+ open ? "palda-dropdown-button--open" : "",
259
+ className
260
+ ].filter(Boolean).join(" ");
261
+ return /* @__PURE__ */ jsxs6("div", { className: classes, onBlur: handleBlur, ref: rootRef, children: [
262
+ /* @__PURE__ */ jsxs6(
263
+ "button",
264
+ {
265
+ ...props,
266
+ "aria-controls": open ? menuId : void 0,
267
+ "aria-expanded": open,
268
+ "aria-haspopup": "menu",
269
+ className: `palda-dropdown-button__trigger ${TYPOGRAPHY_CLASS4}`,
270
+ onClick: handleTriggerClick,
271
+ onKeyDown: handleTriggerKeyDown,
272
+ ref: triggerRef,
273
+ type: "button",
274
+ children: [
275
+ /* @__PURE__ */ jsx6("span", { className: "palda-dropdown-button__label", children }),
276
+ trailingIcon ? /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", className: "palda-dropdown-button__icon", children: trailingIcon }) : null
277
+ ]
278
+ }
279
+ ),
280
+ open ? /* @__PURE__ */ jsx6(
281
+ "div",
282
+ {
283
+ className: "palda-dropdown-button__menu",
284
+ id: menuId,
285
+ onKeyDown: handleMenuKeyDown,
286
+ role: "menu",
287
+ children: items.map((item, index) => /* @__PURE__ */ jsxs6(
288
+ "button",
289
+ {
290
+ className: `palda-dropdown-button__item ${TYPOGRAPHY_CLASS4}`,
291
+ disabled: item.disabled,
292
+ onClick: () => handleItemClick(item),
293
+ ref: (node) => {
294
+ itemRefs.current[index] = node;
295
+ },
296
+ role: "menuitem",
297
+ tabIndex: index === activeIndex ? 0 : -1,
298
+ type: "button",
299
+ children: [
300
+ item.icon ? /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", className: "palda-dropdown-button__item-icon", children: item.icon }) : null,
301
+ /* @__PURE__ */ jsx6("span", { className: "palda-dropdown-button__item-label", children: item.label })
302
+ ]
303
+ },
304
+ item.value
305
+ ))
306
+ }
307
+ ) : null
308
+ ] });
309
+ }
310
+
311
+ // ../../components/src/IconButton/IconButton.tsx
312
+ import { jsx as jsx7 } from "react/jsx-runtime";
313
+ function IconButton({
314
+ className = "",
315
+ icon,
316
+ label,
317
+ size = "md",
318
+ type = "button",
319
+ variant = "primary",
320
+ ...props
321
+ }) {
322
+ const classes = [
323
+ "palda-icon-button",
324
+ `palda-icon-button--${variant}`,
325
+ `palda-icon-button--${size}`,
326
+ className
327
+ ].filter(Boolean).join(" ");
328
+ return /* @__PURE__ */ jsx7("button", { ...props, "aria-label": label, className: classes, type, children: /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", className: "palda-icon-button__icon", children: icon }) });
329
+ }
330
+
331
+ // ../../components/src/Link/Link.tsx
332
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
333
+ var TYPOGRAPHY_CLASS5 = {
334
+ md: "palda-text-body2-reading-medium",
335
+ sm: "palda-text-label-reading-medium"
336
+ };
337
+ function Link({
338
+ children,
339
+ className = "",
340
+ external = false,
341
+ href,
342
+ rel,
343
+ size = "md",
344
+ target,
345
+ variant = "primary",
346
+ ...props
347
+ }) {
348
+ const classes = [
349
+ "palda-link",
350
+ `palda-link--${variant}`,
351
+ `palda-link--${size}`,
352
+ TYPOGRAPHY_CLASS5[size],
353
+ className
354
+ ].filter(Boolean).join(" ");
355
+ return /* @__PURE__ */ jsxs7(
356
+ "a",
357
+ {
358
+ ...props,
359
+ className: classes,
360
+ href,
361
+ rel: external ? rel ?? "noreferrer" : rel,
362
+ target: external ? target ?? "_blank" : target,
363
+ children: [
364
+ children,
365
+ external ? /* @__PURE__ */ jsx8("span", { className: "palda-link__new-window", children: "(\uC0C8 \uCC3D)" }) : null
366
+ ]
367
+ }
368
+ );
369
+ }
370
+
371
+ // ../../components/src/Modal/Modal.tsx
372
+ import { useEffect as useEffect2, useId as useId2, useRef as useRef2 } from "react";
373
+ import { createPortal } from "react-dom";
374
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
375
+ var TITLE_TYPOGRAPHY_CLASS = "palda-text-body1-regular-semibold";
376
+ var FOCUSABLE_SELECTOR = [
377
+ "a[href]",
378
+ "button:not(:disabled)",
379
+ "input:not(:disabled)",
380
+ "select:not(:disabled)",
381
+ "textarea:not(:disabled)",
382
+ '[tabindex]:not([tabindex="-1"])'
383
+ ].join(", ");
384
+ function ModalActions({ children, className = "" }) {
385
+ return /* @__PURE__ */ jsx9("div", { className: ["palda-modal__actions", className].filter(Boolean).join(" "), children });
386
+ }
387
+ function Modal({
388
+ children,
389
+ className = "",
390
+ closeLabel = "\uB2EB\uAE30",
391
+ footer,
392
+ onClose,
393
+ open,
394
+ showCloseButton = true,
395
+ title
396
+ }) {
397
+ const dialogRef = useRef2(null);
398
+ const titleId = useId2();
399
+ useEffect2(() => {
400
+ if (!open) {
401
+ return;
402
+ }
403
+ const previous = document.activeElement;
404
+ const dialog = dialogRef.current;
405
+ const first = dialog?.querySelector(FOCUSABLE_SELECTOR);
406
+ (first ?? dialog)?.focus();
407
+ return () => previous?.focus();
408
+ }, [open]);
409
+ useEffect2(() => {
410
+ if (!open) {
411
+ return;
412
+ }
413
+ const previous = document.body.style.overflow;
414
+ document.body.style.overflow = "hidden";
415
+ return () => {
416
+ document.body.style.overflow = previous;
417
+ };
418
+ }, [open]);
419
+ if (!open) {
420
+ return null;
421
+ }
422
+ const handleKeyDown = (event) => {
423
+ if (event.key === "Escape") {
424
+ event.preventDefault();
425
+ onClose();
426
+ return;
427
+ }
428
+ if (event.key !== "Tab") {
429
+ return;
430
+ }
431
+ const items = Array.from(
432
+ dialogRef.current?.querySelectorAll(FOCUSABLE_SELECTOR) ?? []
433
+ );
434
+ if (items.length === 0) {
435
+ event.preventDefault();
436
+ return;
437
+ }
438
+ const first = items[0];
439
+ const last = items[items.length - 1];
440
+ if (event.shiftKey && document.activeElement === first) {
441
+ event.preventDefault();
442
+ last.focus();
443
+ } else if (!event.shiftKey && document.activeElement === last) {
444
+ event.preventDefault();
445
+ first.focus();
446
+ }
447
+ };
448
+ const handleScrimPointerDown = (event) => {
449
+ if (event.target === event.currentTarget) {
450
+ onClose();
451
+ }
452
+ };
453
+ return createPortal(
454
+ /* @__PURE__ */ jsx9("div", { className: "palda-modal__scrim", onPointerDown: handleScrimPointerDown, children: /* @__PURE__ */ jsxs8(
455
+ "div",
456
+ {
457
+ "aria-labelledby": titleId,
458
+ "aria-modal": "true",
459
+ className: ["palda-modal", className].filter(Boolean).join(" "),
460
+ onKeyDown: handleKeyDown,
461
+ ref: dialogRef,
462
+ role: "dialog",
463
+ tabIndex: -1,
464
+ children: [
465
+ /* @__PURE__ */ jsxs8("div", { className: "palda-modal__header", children: [
466
+ /* @__PURE__ */ jsx9("h2", { className: `palda-modal__title ${TITLE_TYPOGRAPHY_CLASS}`, id: titleId, children: title }),
467
+ showCloseButton ? /* @__PURE__ */ jsx9(
468
+ "button",
469
+ {
470
+ "aria-label": closeLabel,
471
+ className: "palda-modal__close",
472
+ onClick: onClose,
473
+ type: "button",
474
+ children: /* @__PURE__ */ jsx9(CloseIcon, {})
475
+ }
476
+ ) : null
477
+ ] }),
478
+ /* @__PURE__ */ jsx9("div", { className: "palda-modal__body", children }),
479
+ footer ? /* @__PURE__ */ jsx9("div", { className: "palda-modal__footer", children: footer }) : null
480
+ ]
481
+ }
482
+ ) }),
483
+ document.body
484
+ );
485
+ }
486
+
487
+ // ../../components/src/NavigationButton/NavigationButton.tsx
488
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
489
+ function NavigationButton({
490
+ children,
491
+ className = "",
492
+ icon,
493
+ selected = false,
494
+ ...props
495
+ }) {
496
+ const classes = [
497
+ "palda-navigation-button",
498
+ selected ? "palda-navigation-button--selected" : "",
499
+ "palda-text-body2-regular-semibold",
500
+ className
501
+ ].filter(Boolean).join(" ");
502
+ return /* @__PURE__ */ jsxs9(
503
+ "button",
504
+ {
505
+ ...props,
506
+ "aria-current": selected ? "page" : void 0,
507
+ className: classes,
508
+ type: "button",
509
+ children: [
510
+ icon ? /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", className: "palda-navigation-button__icon", children: icon }) : null,
511
+ /* @__PURE__ */ jsx10("span", { className: "palda-navigation-button__label", children })
512
+ ]
513
+ }
514
+ );
515
+ }
516
+
517
+ // ../../components/src/ProgressBar/ProgressBar.tsx
518
+ import { jsx as jsx11 } from "react/jsx-runtime";
519
+ function ProgressBar({ value, label, className = "", ...props }) {
520
+ const clamped = Math.min(100, Math.max(0, Math.round(value)));
521
+ const classes = ["palda-progress-bar", className].filter(Boolean).join(" ");
522
+ return /* @__PURE__ */ jsx11(
523
+ "div",
524
+ {
525
+ ...props,
526
+ "aria-label": label,
527
+ "aria-valuemax": 100,
528
+ "aria-valuemin": 0,
529
+ "aria-valuenow": clamped,
530
+ className: classes,
531
+ role: "progressbar",
532
+ children: /* @__PURE__ */ jsx11("div", { className: "palda-progress-bar__fill", style: { inlineSize: `${clamped}%` } })
533
+ }
534
+ );
535
+ }
536
+
537
+ // ../../components/src/Radio/Radio.tsx
538
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
539
+ var LABEL_TYPOGRAPHY_CLASS2 = "palda-text-body2-regular-medium";
540
+ function Radio({ children, className = "", disabled, ...props }) {
541
+ const classes = ["palda-radio", disabled ? "palda-radio--disabled" : "", className].filter(Boolean).join(" ");
542
+ return /* @__PURE__ */ jsxs10("label", { className: classes, children: [
543
+ /* @__PURE__ */ jsx12("input", { ...props, className: "palda-radio__input", disabled, type: "radio" }),
544
+ /* @__PURE__ */ jsx12("span", { "aria-hidden": "true", className: "palda-radio__control" }),
545
+ children === void 0 || children === null || children === "" ? null : /* @__PURE__ */ jsx12("span", { className: `palda-radio__label ${LABEL_TYPOGRAPHY_CLASS2}`, children })
546
+ ] });
547
+ }
548
+
549
+ // ../../components/src/Switch/Switch.tsx
550
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
551
+ function Switch({ className = "", disabled, size = "md", ...props }) {
552
+ const classes = [
553
+ "palda-switch",
554
+ `palda-switch--${size}`,
555
+ disabled ? "palda-switch--disabled" : "",
556
+ className
557
+ ].filter(Boolean).join(" ");
558
+ return /* @__PURE__ */ jsxs11("label", { className: classes, children: [
559
+ /* @__PURE__ */ jsx13(
560
+ "input",
561
+ {
562
+ ...props,
563
+ className: "palda-switch__input",
564
+ disabled,
565
+ role: "switch",
566
+ type: "checkbox"
567
+ }
568
+ ),
569
+ /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", className: "palda-switch__track", children: /* @__PURE__ */ jsx13("span", { className: "palda-switch__knob" }) })
570
+ ] });
571
+ }
572
+
573
+ // ../../components/src/Tab/Tab.tsx
574
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
575
+ function Tab({ children, className = "", icon, selected = false, ...props }) {
576
+ const classes = [
577
+ "palda-tab",
578
+ selected ? "palda-tab--selected" : "",
579
+ "palda-text-body2-regular-semibold",
580
+ className
581
+ ].filter(Boolean).join(" ");
582
+ return /* @__PURE__ */ jsxs12("button", { ...props, "aria-selected": selected, className: classes, role: "tab", type: "button", children: [
583
+ icon ? /* @__PURE__ */ jsx14("span", { "aria-hidden": "true", className: "palda-tab__icon", children: icon }) : null,
584
+ /* @__PURE__ */ jsx14("span", { className: "palda-tab__label", children })
585
+ ] });
586
+ }
587
+
588
+ // ../../components/src/TextField/TextField.tsx
589
+ import { useId as useId3, useState as useState2 } from "react";
590
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
591
+ var LABEL_TYPOGRAPHY_CLASS3 = "palda-text-body2-regular-semibold";
592
+ var FOOTER_TYPOGRAPHY_CLASS = "palda-text-label-regular-semibold";
593
+ var CONTROL_TYPOGRAPHY_CLASS = {
594
+ md: "palda-text-body2-reading-medium",
595
+ sm: "palda-text-body2-regular-medium"
596
+ };
597
+ function TextField({
598
+ className = "",
599
+ defaultValue,
600
+ description,
601
+ disabled,
602
+ error = false,
603
+ icon,
604
+ id,
605
+ label,
606
+ maxLength,
607
+ onChange,
608
+ required,
609
+ size = "sm",
610
+ value,
611
+ variant = "outline",
612
+ ...props
613
+ }) {
614
+ const generatedId = useId3();
615
+ const controlId = id ?? generatedId;
616
+ const descriptionId = `${controlId}-description`;
617
+ const countId = `${controlId}-count`;
618
+ const showCount = maxLength !== void 0;
619
+ const [uncontrolledLength, setUncontrolledLength] = useState2(
620
+ () => String(defaultValue ?? "").length
621
+ );
622
+ const length = value === void 0 ? uncontrolledLength : String(value).length;
623
+ const handleChange = (event) => {
624
+ setUncontrolledLength(event.target.value.length);
625
+ onChange?.(event);
626
+ };
627
+ const describedBy = [description ? descriptionId : "", showCount ? countId : ""].filter(Boolean).join(" ");
628
+ const controlProps = {
629
+ ...props,
630
+ "aria-describedby": describedBy || void 0,
631
+ "aria-invalid": error || void 0,
632
+ className: `palda-text-field__control ${CONTROL_TYPOGRAPHY_CLASS[size]}`,
633
+ defaultValue,
634
+ disabled,
635
+ id: controlId,
636
+ maxLength,
637
+ onChange: handleChange,
638
+ required,
639
+ value
640
+ };
641
+ const classes = [
642
+ "palda-text-field",
643
+ `palda-text-field--${variant}`,
644
+ `palda-text-field--${size}`,
645
+ error ? "palda-text-field--error" : "",
646
+ disabled ? "palda-text-field--disabled" : "",
647
+ className
648
+ ].filter(Boolean).join(" ");
649
+ return /* @__PURE__ */ jsxs13("div", { className: classes, children: [
650
+ label === void 0 || label === null || label === "" ? null : /* @__PURE__ */ jsxs13("label", { className: `palda-text-field__label ${LABEL_TYPOGRAPHY_CLASS3}`, htmlFor: controlId, children: [
651
+ label,
652
+ required ? /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", className: "palda-text-field__required", children: "*" }) : null
653
+ ] }),
654
+ /* @__PURE__ */ jsxs13("div", { className: "palda-text-field__field", children: [
655
+ size === "md" ? (
656
+ // input과 textarea는 같은 prop을 받지만 이벤트 타입만 다르다.
657
+ /* @__PURE__ */ jsx15("textarea", { ...controlProps })
658
+ ) : /* @__PURE__ */ jsx15("input", { ...controlProps }),
659
+ icon ? /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", className: "palda-text-field__icon", children: icon }) : null
660
+ ] }),
661
+ description || showCount ? /* @__PURE__ */ jsxs13("div", { className: `palda-text-field__footer ${FOOTER_TYPOGRAPHY_CLASS}`, children: [
662
+ /* @__PURE__ */ jsx15("span", { className: "palda-text-field__description", id: descriptionId, children: description }),
663
+ showCount ? /* @__PURE__ */ jsxs13("span", { className: "palda-text-field__count", id: countId, children: [
664
+ length,
665
+ "/",
666
+ maxLength
667
+ ] }) : null
668
+ ] }) : null
669
+ ] });
670
+ }
671
+
672
+ // ../../components/src/Toast/toastStore.ts
673
+ var queue = [];
674
+ var lastId = 0;
675
+ var listeners = /* @__PURE__ */ new Set();
676
+ function publish(next) {
677
+ queue = next;
678
+ for (const listener of listeners) {
679
+ listener();
680
+ }
681
+ }
682
+ function subscribeToToasts(listener) {
683
+ listeners.add(listener);
684
+ return () => {
685
+ listeners.delete(listener);
686
+ };
687
+ }
688
+ function getToasts() {
689
+ return queue;
690
+ }
691
+ function toast(message, options = {}) {
692
+ lastId += 1;
693
+ const id = `palda-toast-${lastId}`;
694
+ publish([...queue, { ...options, id, message }]);
695
+ return id;
696
+ }
697
+ toast.dismiss = (id) => {
698
+ publish(id === void 0 ? [] : queue.filter((item) => item.id !== id));
699
+ };
700
+
701
+ // ../../components/src/Toast/Toaster.tsx
702
+ import { useSyncExternalStore } from "react";
703
+
704
+ // ../../components/src/Toast/Toast.tsx
705
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
706
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
707
+ var TYPOGRAPHY_CLASS6 = "palda-text-body2-reading-medium";
708
+ var DEFAULT_DURATION_MS = 5e3;
709
+ function Toast({
710
+ actionLabel,
711
+ children,
712
+ className = "",
713
+ closeLabel = "\uB2EB\uAE30",
714
+ duration = DEFAULT_DURATION_MS,
715
+ icon,
716
+ onAction,
717
+ onClose,
718
+ variant = "primary"
719
+ }) {
720
+ const [paused, setPaused] = useState3(false);
721
+ const remainingRef = useRef3(duration);
722
+ useEffect3(() => {
723
+ remainingRef.current = duration;
724
+ }, [duration]);
725
+ useEffect3(() => {
726
+ if (!onClose || duration <= 0 || paused) {
727
+ return;
728
+ }
729
+ const startedAt = Date.now();
730
+ const timer = setTimeout(onClose, remainingRef.current);
731
+ return () => {
732
+ clearTimeout(timer);
733
+ remainingRef.current = Math.max(remainingRef.current - (Date.now() - startedAt), 0);
734
+ };
735
+ }, [duration, onClose, paused]);
736
+ const classes = ["palda-toast", `palda-toast--${variant}`, TYPOGRAPHY_CLASS6, className].filter(Boolean).join(" ");
737
+ return /* @__PURE__ */ jsxs14(
738
+ "div",
739
+ {
740
+ className: classes,
741
+ onBlur: () => setPaused(false),
742
+ onFocus: () => setPaused(true),
743
+ onMouseEnter: () => setPaused(true),
744
+ onMouseLeave: () => setPaused(false),
745
+ role: variant === "danger" ? "alert" : "status",
746
+ children: [
747
+ icon ? /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", className: "palda-toast__icon", children: icon }) : null,
748
+ /* @__PURE__ */ jsx16("span", { className: "palda-toast__content", children }),
749
+ actionLabel ? /* @__PURE__ */ jsx16("button", { className: "palda-toast__action", onClick: onAction, type: "button", children: actionLabel }) : null,
750
+ onClose ? /* @__PURE__ */ jsx16(
751
+ "button",
752
+ {
753
+ "aria-label": closeLabel,
754
+ className: "palda-toast__close",
755
+ onClick: onClose,
756
+ type: "button",
757
+ children: /* @__PURE__ */ jsx16(CloseIcon, {})
758
+ }
759
+ ) : null
760
+ ]
761
+ }
762
+ );
763
+ }
764
+
765
+ // ../../components/src/Toast/Toaster.tsx
766
+ import { jsx as jsx17 } from "react/jsx-runtime";
767
+ import { createElement } from "react";
768
+ function Toaster({ className = "", placement = "bottom-center" }) {
769
+ const toasts = useSyncExternalStore(subscribeToToasts, getToasts, getToasts);
770
+ const classes = ["palda-toaster", `palda-toaster--${placement}`, className].filter(Boolean).join(" ");
771
+ return /* @__PURE__ */ jsx17("div", { className: classes, children: toasts.map(({ id, message, onAction, ...options }) => /* @__PURE__ */ createElement(
772
+ Toast,
773
+ {
774
+ ...options,
775
+ key: id,
776
+ onAction: onAction ? () => {
777
+ onAction();
778
+ toast.dismiss(id);
779
+ } : void 0,
780
+ onClose: () => toast.dismiss(id)
781
+ },
782
+ message
783
+ )) });
784
+ }
785
+
786
+ // ../../components/src/Tooltip/Tooltip.tsx
787
+ import {
788
+ cloneElement,
789
+ isValidElement,
790
+ useCallback,
791
+ useEffect as useEffect4,
792
+ useId as useId4,
793
+ useLayoutEffect,
794
+ useRef as useRef4,
795
+ useState as useState4
796
+ } from "react";
797
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
798
+ var OPPOSITE = {
799
+ bottom: "top",
800
+ left: "right",
801
+ right: "left",
802
+ top: "bottom"
803
+ };
804
+ var OPEN_DELAY_MS = 200;
805
+ var CLOSE_GRACE_MS = 100;
806
+ var GAP_FALLBACK_PX = 4;
807
+ var ARROW_INSET_PX = 10;
808
+ var clamp = (value, min, max) => Math.max(min, Math.min(value, max));
809
+ function readGap(element) {
810
+ const raw = getComputedStyle(element).getPropertyValue("--palda-tooltip-gap");
811
+ const parsed = Number.parseFloat(raw);
812
+ return Number.isNaN(parsed) ? GAP_FALLBACK_PX : parsed;
813
+ }
814
+ function Tooltip({ children, className = "", label, placement = "top" }) {
815
+ const [open, setOpen] = useState4(false);
816
+ const [position, setPosition] = useState4(null);
817
+ const anchorRef = useRef4(null);
818
+ const bubbleRef = useRef4(null);
819
+ const timerRef = useRef4(void 0);
820
+ const tooltipId = useId4();
821
+ const clearTimer = () => clearTimeout(timerRef.current);
822
+ useEffect4(() => clearTimer, []);
823
+ const place = useCallback(() => {
824
+ const anchor = anchorRef.current;
825
+ const bubble = bubbleRef.current;
826
+ if (!anchor || !bubble) {
827
+ return;
828
+ }
829
+ const trigger2 = anchor.getBoundingClientRect();
830
+ const { height, width } = bubble.getBoundingClientRect();
831
+ const gap = readGap(bubble);
832
+ const viewportWidth = window.innerWidth;
833
+ const viewportHeight = window.innerHeight;
834
+ const room = {
835
+ bottom: viewportHeight - trigger2.bottom,
836
+ left: trigger2.left,
837
+ right: viewportWidth - trigger2.right,
838
+ top: trigger2.top
839
+ };
840
+ const needed = (side) => (side === "top" || side === "bottom" ? height : width) + gap * 2;
841
+ const flipped = OPPOSITE[placement];
842
+ const next = room[placement] < needed(placement) && room[flipped] >= needed(flipped) ? flipped : placement;
843
+ const vertical = next === "top" || next === "bottom";
844
+ let left = vertical ? trigger2.left + trigger2.width / 2 - width / 2 : next === "left" ? trigger2.left - width - gap : trigger2.right + gap;
845
+ let top = vertical ? next === "top" ? trigger2.top - height - gap : trigger2.bottom + gap : trigger2.top + trigger2.height / 2 - height / 2;
846
+ if (vertical) {
847
+ left = clamp(left, gap, viewportWidth - width - gap);
848
+ } else {
849
+ top = clamp(top, gap, viewportHeight - height - gap);
850
+ }
851
+ const center = vertical ? trigger2.left + trigger2.width / 2 - left : trigger2.top + trigger2.height / 2 - top;
852
+ setPosition({
853
+ arrowOffset: clamp(
854
+ center,
855
+ ARROW_INSET_PX,
856
+ Math.max((vertical ? width : height) - ARROW_INSET_PX, ARROW_INSET_PX)
857
+ ),
858
+ left,
859
+ placement: next,
860
+ top
861
+ });
862
+ }, [placement]);
863
+ useLayoutEffect(() => {
864
+ if (open) {
865
+ place();
866
+ } else {
867
+ setPosition(null);
868
+ }
869
+ }, [open, place]);
870
+ useEffect4(() => {
871
+ if (!open) {
872
+ return;
873
+ }
874
+ const handleScroll = () => place();
875
+ window.addEventListener("resize", handleScroll);
876
+ window.addEventListener("scroll", handleScroll, true);
877
+ return () => {
878
+ window.removeEventListener("resize", handleScroll);
879
+ window.removeEventListener("scroll", handleScroll, true);
880
+ };
881
+ }, [open, place]);
882
+ useEffect4(() => {
883
+ if (!open) {
884
+ return;
885
+ }
886
+ const handleKeyDown = (event) => {
887
+ if (event.key === "Escape") {
888
+ setOpen(false);
889
+ }
890
+ };
891
+ document.addEventListener("keydown", handleKeyDown);
892
+ return () => document.removeEventListener("keydown", handleKeyDown);
893
+ }, [open]);
894
+ const handleMouseEnter = () => {
895
+ clearTimer();
896
+ timerRef.current = setTimeout(() => setOpen(true), OPEN_DELAY_MS);
897
+ };
898
+ const handleMouseLeave = () => {
899
+ clearTimer();
900
+ timerRef.current = setTimeout(() => setOpen(false), CLOSE_GRACE_MS);
901
+ };
902
+ const handleFocus = () => {
903
+ clearTimer();
904
+ setOpen(true);
905
+ };
906
+ const handleBlur = () => {
907
+ clearTimer();
908
+ setOpen(false);
909
+ };
910
+ const bubbleClasses = [
911
+ "palda-tooltip__bubble",
912
+ `palda-tooltip__bubble--${position?.placement ?? placement}`,
913
+ "palda-text-label-reading-medium"
914
+ ].join(" ");
915
+ const trigger = isValidElement(children) ? cloneElement(children, {
916
+ "aria-describedby": open ? tooltipId : children.props["aria-describedby"]
917
+ }) : children;
918
+ return /* @__PURE__ */ jsxs15(
919
+ "span",
920
+ {
921
+ className: ["palda-tooltip", className].filter(Boolean).join(" "),
922
+ onBlur: handleBlur,
923
+ onFocus: handleFocus,
924
+ onMouseEnter: handleMouseEnter,
925
+ onMouseLeave: handleMouseLeave,
926
+ ref: anchorRef,
927
+ children: [
928
+ trigger,
929
+ open ? /* @__PURE__ */ jsxs15(
930
+ "span",
931
+ {
932
+ className: bubbleClasses,
933
+ id: tooltipId,
934
+ ref: bubbleRef,
935
+ role: "tooltip",
936
+ style: position ? {
937
+ "--palda-tooltip-arrow-offset": `${position.arrowOffset}px`,
938
+ // 논리 속성이 아니라 물리 좌표다(Tooltip.css의 화살표 주석 참고).
939
+ left: `${position.left}px`,
940
+ top: `${position.top}px`
941
+ } : (
942
+ // 재기 전에는 자리를 모르니 감춰 둔다. 첫 프레임에 엉뚱한 곳에 보이지 않게 한다.
943
+ { visibility: "hidden" }
944
+ ),
945
+ children: [
946
+ label,
947
+ /* @__PURE__ */ jsx18("span", { "aria-hidden": "true", className: "palda-tooltip__arrow" })
948
+ ]
949
+ }
950
+ ) : null
951
+ ]
952
+ }
953
+ );
954
+ }
955
+
956
+ export {
957
+ Alert,
958
+ Badge,
959
+ Button,
960
+ Checkbox,
961
+ Chip,
962
+ DropdownButton,
963
+ IconButton,
964
+ Link,
965
+ ModalActions,
966
+ Modal,
967
+ NavigationButton,
968
+ ProgressBar,
969
+ Radio,
970
+ Switch,
971
+ Tab,
972
+ TextField,
973
+ toast,
974
+ Toaster,
975
+ Tooltip
976
+ };