@hivcd-printshop/design-system 1.1.0 → 1.2.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.mjs CHANGED
@@ -54,6 +54,20 @@ var IconChevronDownXS = ({
54
54
  }
55
55
  );
56
56
  };
57
+ var IconCheck = ({ className, stroke }) => {
58
+ return /* @__PURE__ */ jsx(
59
+ "svg",
60
+ {
61
+ className,
62
+ width: "13",
63
+ height: "13",
64
+ viewBox: "0 0 13 13",
65
+ fill: "none",
66
+ xmlns: "http://www.w3.org/2000/svg",
67
+ children: /* @__PURE__ */ jsx("path", { d: "M2.05566 7.0555L4.83344 9.8333L10.9446 3.72217", stroke: stroke || "#121212" })
68
+ }
69
+ );
70
+ };
57
71
  var IconSearch = ({ className, stroke }) => {
58
72
  return /* @__PURE__ */ jsxs(
59
73
  "svg",
@@ -88,6 +102,32 @@ var IconSearch = ({ className, stroke }) => {
88
102
  }
89
103
  );
90
104
  };
105
+ var IconInfo = ({ className, stroke }) => {
106
+ return /* @__PURE__ */ jsxs(
107
+ "svg",
108
+ {
109
+ className,
110
+ width: "16",
111
+ height: "16",
112
+ viewBox: "0 0 16 16",
113
+ fill: "none",
114
+ xmlns: "http://www.w3.org/2000/svg",
115
+ children: [
116
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6.25", stroke: stroke || "#8b94a1", strokeWidth: "1.5" }),
117
+ /* @__PURE__ */ jsx(
118
+ "path",
119
+ {
120
+ d: "M8 7.25V11.25",
121
+ stroke: stroke || "#8b94a1",
122
+ strokeWidth: "1.5",
123
+ strokeLinecap: "round"
124
+ }
125
+ ),
126
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "5", r: "0.9", fill: stroke || "#8b94a1" })
127
+ ]
128
+ }
129
+ );
130
+ };
91
131
  var IconGoBack = ({ className, stroke }) => {
92
132
  return /* @__PURE__ */ jsxs(
93
133
  "svg",
@@ -138,46 +178,732 @@ var BackButton = ({ onClick, className }) => {
138
178
  // src/components/CTA/CTA.tsx
139
179
  import { jsx as jsx3 } from "react/jsx-runtime";
140
180
  var CTA = ({
141
- primary = true,
142
- variable = "default",
181
+ primary = false,
182
+ alert = false,
183
+ disabled = false,
143
184
  label,
144
185
  onClick,
145
- className
186
+ className,
187
+ forModal = false,
188
+ ...props
146
189
  }) => {
147
190
  return /* @__PURE__ */ jsx3(
148
191
  "button",
149
192
  {
150
193
  type: "button",
194
+ disabled,
151
195
  className: cn(
152
- "w-full max-w-360 h-12 p-2.5 text-center text-head3-medium",
153
- `${primary ? "border-0" : "border border-gray02"}`,
154
- /** Disabled */
155
- `${variable == "disabled" ? primary ? "bg-gray01 text-gray04 border border-gray01" : "bg-gray02 text-gray04" : ""}`,
156
- /** Default */
157
- `${variable == "default" ? primary ? "bg-black text-white" : "bg-white text-strong" : ""}`,
158
- /** Hover */
159
- `${variable != "disabled" ? primary ? "hover:bg-gray08 hover:text-white" : "hover:bg-gray01 hover:text-strong" : ""}`,
160
- /** Hover */
161
- `${variable != "disabled" && "hover:cursor-pointer"}`,
162
- /** Pressed */
163
- `${variable == "pressed" ? primary ? "bg-gray09 text-white" : "bg-gray02 text-strong" : ""}`,
196
+ forModal ? "h-14.5" : "h-12",
197
+ "w-full max-w-360 px-4",
198
+ "flex items-center justify-center",
199
+ "border-t box-border border-gray02",
200
+ primary && "border-none",
201
+ "text-head3-medium text-strong",
202
+ primary && "text-white",
203
+ alert && "text-danger",
204
+ disabled && "text-quaternary",
205
+ "bg-white hover:bg-gray00 active:bg-gray01 disabled:bg-gray02",
206
+ primary && "bg-black hover:bg-gray08 active:bg-gray09 disabled:bg-gray01",
207
+ "cursor-pointer disabled:cursor-default",
164
208
  className
165
209
  ),
166
210
  onClick,
211
+ ...props,
167
212
  children: label
168
213
  }
169
214
  );
170
215
  };
171
216
 
172
- // src/components/createPost/CategorySegment.tsx
217
+ // src/components/Column/MultiColumn.tsx
173
218
  import { jsx as jsx4 } from "react/jsx-runtime";
219
+ var MultiColumnMain = ({ children, className }) => {
220
+ return /* @__PURE__ */ jsx4("div", { className: cn("flex flex-col w-full h-full", className), children });
221
+ };
222
+ var Columns = ({ children, className }) => {
223
+ return (
224
+ // flex 래퍼를 사용하고 하위 Column들이 flex-1을 가지게 하거나,
225
+ // grid를 사용하여 열을 나눌 수 있어. 여기서는 flex를 활용한 균등 분할 예시야.
226
+ /* @__PURE__ */ jsx4("div", { className: cn("flex w-full h-full divide-x divide-gray02", className), children })
227
+ );
228
+ };
229
+ var CTAs = ({ children, className }) => {
230
+ if (!children) return null;
231
+ return (
232
+ // CTA 버튼들을 중앙에 배치하는 예시 (우측 정렬이 필요하면 justify-end 사용)
233
+ /* @__PURE__ */ jsx4("div", { className: cn("flex flex-col w-full h-fit", className), children })
234
+ );
235
+ };
236
+ var MultiColumn = Object.assign(MultiColumnMain, {
237
+ Columns,
238
+ CTAs
239
+ });
240
+
241
+ // src/components/Column/Column.tsx
242
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
243
+ var Column = ({ className, children, header }) => {
244
+ return /* @__PURE__ */ jsxs2("div", { className: cn("flex flex-col gap-5", "h-full w-full px-5 pt-3 pb-8", className), children: [
245
+ header && /* @__PURE__ */ jsx5("h1", { className: cn("text-head3-medium text-primary font-bold"), children: header }),
246
+ children
247
+ ] });
248
+ };
249
+
250
+ // src/components/Column/ColumnContentContainer.tsx
251
+ import { jsx as jsx6 } from "react/jsx-runtime";
252
+ var ColumnContentContainer = ({ className, children }) => {
253
+ return /* @__PURE__ */ jsx6("div", { className: cn("flex flex-col gap-1.5 w-full h-fit", className), children });
254
+ };
255
+
256
+ // src/components/Column/ColumnLabel.tsx
257
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
258
+ var ColumnLabel = ({ className, children, subLabel, htmlFor }) => {
259
+ return /* @__PURE__ */ jsxs3("label", { htmlFor, className: cn("flex gap-1.5 items-end", className), children: [
260
+ /* @__PURE__ */ jsx7("span", { className: cn("text-body2-regular text-tertiary"), children }),
261
+ subLabel && /* @__PURE__ */ jsx7("span", { className: cn("text-caption-book text-quaternary"), children: subLabel })
262
+ ] });
263
+ };
264
+
265
+ // src/components/Column/ColumnText.tsx
266
+ import { jsx as jsx8 } from "react/jsx-runtime";
267
+ var ColumnTextStyle = "text-body3-book text-strong";
268
+ var ModalTextStyle = "text-body1-regular text-primary";
269
+ var ColumnText = ({ className, alert = false, children, onClick }) => {
270
+ return /* @__PURE__ */ jsx8("div", { className: cn(ColumnTextStyle, alert && "text-danger", className), onClick, children: children ? children : "-" });
271
+ };
272
+
273
+ // src/components/Column/ColumnSubTitle.tsx
274
+ import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
275
+ var ColumnSubTitle = ({
276
+ className,
277
+ children,
278
+ action,
279
+ as: Tag2 = "h2"
280
+ }) => {
281
+ return /* @__PURE__ */ jsxs4(Tag2, { className: cn("flex items-center justify-between gap-1.5", "w-full", className), children: [
282
+ /* @__PURE__ */ jsx9("span", { className: cn("text-head3-medium text-strong"), children }),
283
+ action && /* @__PURE__ */ jsx9("span", { className: "flex shrink-0 items-center", children: action })
284
+ ] });
285
+ };
286
+
287
+ // src/components/Form/BasicFormField.tsx
288
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
289
+ var BasicFormField = ({
290
+ children,
291
+ isDanger,
292
+ dangerText,
293
+ className
294
+ }) => {
295
+ return /* @__PURE__ */ jsxs5("div", { className: cn("flex flex-col gap-1.5", className), children: [
296
+ children,
297
+ isDanger && dangerText && /* @__PURE__ */ jsx10("span", { className: "text-caption-book text-danger", children: dangerText })
298
+ ] });
299
+ };
300
+ var getInputStyles = (isDanger, additionalClass) => {
301
+ return cn(
302
+ "w-full h-10 px-3",
303
+ "box-border border border-gray02 focus:outline-none focus:border-black",
304
+ "bg-white hover:bg-gray00 transition-colors focus:hover:bg-white",
305
+ ColumnTextStyle,
306
+ "text-primary placeholder:text-quaternary",
307
+ isDanger && "border-danger",
308
+ // 에러 시 빨간 테두리
309
+ additionalClass
310
+ // 각 인풋별 추가 스타일 (예: textarea의 h-30)
311
+ );
312
+ };
313
+
314
+ // src/components/Form/FormText.tsx
315
+ import { jsx as jsx11 } from "react/jsx-runtime";
316
+ var FormText = ({ className, isDanger, dangerText, ...props }) => {
317
+ return /* @__PURE__ */ jsx11(BasicFormField, { isDanger, dangerText, children: /* @__PURE__ */ jsx11("input", { type: "text", className: getInputStyles(isDanger, className), ...props }) });
318
+ };
319
+
320
+ // src/components/Form/FormTextArea.tsx
321
+ import { jsx as jsx12 } from "react/jsx-runtime";
322
+ var FormTextArea = ({ className, isDanger, dangerText, ...props }) => {
323
+ return /* @__PURE__ */ jsx12(BasicFormField, { isDanger, dangerText, children: /* @__PURE__ */ jsx12(
324
+ "textarea",
325
+ {
326
+ className: getInputStyles(isDanger, "resize-none h-30 py-2.5 " + (className || "")),
327
+ ...props
328
+ }
329
+ ) });
330
+ };
331
+
332
+ // src/components/Form/FormNumber.tsx
333
+ import { jsx as jsx13 } from "react/jsx-runtime";
334
+ var FormNumber = ({ className, isDanger, dangerText, ...props }) => {
335
+ return /* @__PURE__ */ jsx13(BasicFormField, { isDanger, dangerText, children: /* @__PURE__ */ jsx13("input", { type: "number", className: getInputStyles(isDanger, className), ...props }) });
336
+ };
337
+
338
+ // src/components/Form/FormPassword.tsx
339
+ import { jsx as jsx14 } from "react/jsx-runtime";
340
+ var FormPassword = ({ className, isDanger, dangerText, ...props }) => {
341
+ return /* @__PURE__ */ jsx14(BasicFormField, { isDanger, dangerText, children: /* @__PURE__ */ jsx14("input", { type: "password", className: getInputStyles(isDanger, className), ...props }) });
342
+ };
343
+
344
+ // src/components/Form/FormTel.tsx
345
+ import { jsx as jsx15 } from "react/jsx-runtime";
346
+ var FormTel = ({ className, isDanger, dangerText, onChange, ...props }) => {
347
+ const handlePhoneChange = (e) => {
348
+ const rawValue = e.target.value.replace(/[^0-9]/g, "");
349
+ let formattedValue = "";
350
+ if (rawValue.length < 4) formattedValue = rawValue;
351
+ else if (rawValue.length < 8) formattedValue = `${rawValue.slice(0, 3)}-${rawValue.slice(3)}`;
352
+ else
353
+ formattedValue = `${rawValue.slice(0, 3)}-${rawValue.slice(3, 7)}-${rawValue.slice(7, 11)}`;
354
+ e.target.value = formattedValue;
355
+ if (onChange) onChange(e);
356
+ };
357
+ return /* @__PURE__ */ jsx15(BasicFormField, { isDanger, dangerText, children: /* @__PURE__ */ jsx15(
358
+ "input",
359
+ {
360
+ type: "tel",
361
+ className: getInputStyles(isDanger, className),
362
+ onChange: handlePhoneChange,
363
+ maxLength: 13,
364
+ ...props
365
+ }
366
+ ) });
367
+ };
368
+
369
+ // src/components/Form/FormColor.tsx
370
+ import { useState } from "react";
371
+ import { jsx as jsx16, jsxs as jsxs6 } from "react/jsx-runtime";
372
+ var FormColor = ({
373
+ className,
374
+ isDanger,
375
+ dangerText,
376
+ value,
377
+ defaultValue = "#000000",
378
+ onChange,
379
+ ...props
380
+ }) => {
381
+ const [internalColor, setInternalColor] = useState(
382
+ value || defaultValue || "#000000"
383
+ );
384
+ const color = value !== void 0 ? value : internalColor;
385
+ const handleColorChange = (e) => {
386
+ setInternalColor(e.target.value);
387
+ onChange?.(e);
388
+ };
389
+ return /* @__PURE__ */ jsx16(BasicFormField, { isDanger, dangerText, children: /* @__PURE__ */ jsxs6(
390
+ "label",
391
+ {
392
+ className: getInputStyles(
393
+ isDanger,
394
+ // 💡 focus-within을 통해 내부 <input>이 포커스될 때 부모(label)의 테두리와 배경 변경!
395
+ "flex items-center gap-2 relative focus-within:border-black focus-within:hover:bg-white" + (className || "")
396
+ ),
397
+ children: [
398
+ /* @__PURE__ */ jsx16("div", { className: "w-6 h-6 shrink-0 overflow-hidden", style: { backgroundColor: color }, children: /* @__PURE__ */ jsx16(
399
+ "input",
400
+ {
401
+ type: "color",
402
+ value: color,
403
+ onChange: handleColorChange,
404
+ className: "absolute inset-0 w-full h-full opacity-0",
405
+ ...props
406
+ }
407
+ ) }),
408
+ /* @__PURE__ */ jsx16("span", { className: "text-primary text-body3-regular uppercase flex-1 select-none", children: color })
409
+ ]
410
+ }
411
+ ) });
412
+ };
413
+
414
+ // src/components/Form/FormFile.tsx
415
+ import { useState as useState2 } from "react";
416
+ import { jsx as jsx17, jsxs as jsxs7 } from "react/jsx-runtime";
417
+ var FormFile = ({
418
+ className,
419
+ isDanger,
420
+ dangerText,
421
+ placeholder = "\uD30C\uC77C\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694",
422
+ fileUrl,
423
+ filename,
424
+ onChange,
425
+ ...props
426
+ }) => {
427
+ const [selectedFile, setSelectedFile] = useState2(null);
428
+ const handleFileChange = (e) => {
429
+ const originalFile = e.target.files?.[0];
430
+ if (!originalFile) {
431
+ setSelectedFile(null);
432
+ onChange?.(e);
433
+ return;
434
+ }
435
+ let finalFile = originalFile;
436
+ if (filename) {
437
+ const lastDotIndex = originalFile.name.lastIndexOf(".");
438
+ const extension = lastDotIndex !== -1 ? originalFile.name.slice(lastDotIndex) : "";
439
+ const newFileName = filename.endsWith(extension) ? filename : `${filename}${extension}`;
440
+ finalFile = new File([originalFile], newFileName, {
441
+ type: originalFile.type,
442
+ lastModified: originalFile.lastModified
443
+ });
444
+ try {
445
+ const dataTransfer = new DataTransfer();
446
+ dataTransfer.items.add(finalFile);
447
+ e.target.files = dataTransfer.files;
448
+ } catch (err) {
449
+ console.warn("DataTransfer\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBE0C\uB77C\uC6B0\uC800\uC785\uB2C8\uB2E4.", err);
450
+ }
451
+ }
452
+ setSelectedFile(finalFile);
453
+ onChange?.(e);
454
+ };
455
+ const handleDownload = (e) => {
456
+ e.preventDefault();
457
+ e.stopPropagation();
458
+ if (selectedFile) {
459
+ const url = URL.createObjectURL(selectedFile);
460
+ const a = document.createElement("a");
461
+ a.href = url;
462
+ a.download = selectedFile.name;
463
+ document.body.appendChild(a);
464
+ a.click();
465
+ document.body.removeChild(a);
466
+ URL.revokeObjectURL(url);
467
+ } else if (fileUrl) {
468
+ const a = document.createElement("a");
469
+ a.href = fileUrl;
470
+ a.download = fileUrl.split("/").pop() || "download";
471
+ a.target = "_blank";
472
+ document.body.appendChild(a);
473
+ a.click();
474
+ document.body.removeChild(a);
475
+ }
476
+ };
477
+ const displayFileName = selectedFile?.name || (fileUrl ? fileUrl.split("/").pop() : null);
478
+ return /* @__PURE__ */ jsx17(BasicFormField, { isDanger, dangerText, children: /* @__PURE__ */ jsxs7(
479
+ "div",
480
+ {
481
+ className: getInputStyles(
482
+ isDanger,
483
+ "active:bg-gray00 flex items-center gap-2 justify-between " + (className || "")
484
+ ),
485
+ children: [
486
+ /* @__PURE__ */ jsx17(
487
+ "div",
488
+ {
489
+ className: `truncate flex-1 max-w-50 w-full ${displayFileName ? "text-primary" : "text-quaternary"}`,
490
+ children: displayFileName ? /* @__PURE__ */ jsx17(
491
+ "span",
492
+ {
493
+ onClick: handleDownload,
494
+ className: "w-full underline text-primary cursor-pointer hover:text-black transition-colors",
495
+ title: `${displayFileName} \uB2E4\uC6B4\uB85C\uB4DC`,
496
+ children: displayFileName
497
+ }
498
+ ) : /* @__PURE__ */ jsx17("span", { className: "w-full", children: placeholder })
499
+ }
500
+ ),
501
+ /* @__PURE__ */ jsxs7("label", { className: "px-1 -mx-1 cursor-pointer", children: [
502
+ /* @__PURE__ */ jsx17("div", { className: "text-primary text-caption-regular underline", children: "\uD30C\uC77C \uCCA8\uBD80" }),
503
+ /* @__PURE__ */ jsx17("input", { type: "file", className: "hidden", onChange: handleFileChange, ...props })
504
+ ] })
505
+ ]
506
+ }
507
+ ) });
508
+ };
509
+
510
+ // src/components/Form/FormCheckBox.tsx
511
+ import { jsx as jsx18, jsxs as jsxs8 } from "react/jsx-runtime";
512
+ var FormCheckBox = ({
513
+ isChecked,
514
+ setIsChecked,
515
+ children,
516
+ className,
517
+ textStyle,
518
+ ...props
519
+ }) => {
520
+ return /* @__PURE__ */ jsxs8(
521
+ "label",
522
+ {
523
+ className: cn(
524
+ "w-fit h-fit pr-1",
525
+ "flex items-center gap-1",
526
+ "hover:bg-gray00 active:bg-gray01 transition-colors",
527
+ "cursor-pointer select-none",
528
+ // 💡 1. 텍스트 드래그 방지 추가
529
+ className
530
+ ),
531
+ children: [
532
+ /* @__PURE__ */ jsx18(
533
+ "input",
534
+ {
535
+ type: "checkbox",
536
+ checked: isChecked,
537
+ onChange: (e) => setIsChecked(e.target.checked),
538
+ className: "sr-only peer",
539
+ ...props
540
+ }
541
+ ),
542
+ /* @__PURE__ */ jsx18("div", { className: "flex items-center justify-center w-5 h-5 shrink-0", children: /* @__PURE__ */ jsx18(
543
+ "div",
544
+ {
545
+ className: cn(
546
+ "w-[15px] h-[15px]",
547
+ "box-border border",
548
+ "flex items-center justify-center transition-colors duration-200",
549
+ // 💡 3. 키보드 Tab 이동 시 접근성 향상 (포커스 링 표시)
550
+ "peer-focus-visible:ring-2 peer-focus-visible:ring-black peer-focus-visible:ring-offset-1"
551
+ ),
552
+ children: isChecked && /* @__PURE__ */ jsx18(IconCheck, {})
553
+ }
554
+ ) }),
555
+ children && /* @__PURE__ */ jsx18("span", { className: cn(ColumnTextStyle, textStyle), children })
556
+ ]
557
+ }
558
+ );
559
+ };
560
+
561
+ // src/components/Form/FormSelectBox.tsx
562
+ import { useEffect, useRef, useState as useState3 } from "react";
563
+
564
+ // src/components/Form/SelectBoxOption.tsx
565
+ import { jsx as jsx19 } from "react/jsx-runtime";
566
+ var SelectBoxOption = ({
567
+ label,
568
+ onClick,
569
+ // selectedLabel,
570
+ className
571
+ }) => {
572
+ return /* @__PURE__ */ jsx19(
573
+ "li",
574
+ {
575
+ onClick,
576
+ className: cn(
577
+ "w-full h-11 px-3 shrink-0",
578
+ "flex items-center justify-start",
579
+ // "box-border border-b border-gray02",
580
+ "text-body3-book text-primary",
581
+ "bg-white hover:bg-gray00 active:bg-gray01",
582
+ // isSelected
583
+ // ? "bg-black text-white"
584
+ // : "",
585
+ className
586
+ ),
587
+ children: label
588
+ }
589
+ );
590
+ };
591
+
592
+ // src/components/Form/FormSelectBox.tsx
593
+ import { jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
594
+ var FormSelectBox = ({
595
+ options,
596
+ value,
597
+ onChange,
598
+ placeholder = "\uC120\uD0DD\uD574\uC8FC\uC138\uC694",
599
+ isDanger,
600
+ dangerText,
601
+ className
602
+ }) => {
603
+ const [isOpen, setIsOpen] = useState3(false);
604
+ const containerRef = useRef(null);
605
+ useEffect(() => {
606
+ const handleClickOutside = (event) => {
607
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
608
+ setIsOpen(false);
609
+ }
610
+ };
611
+ document.addEventListener("mousedown", handleClickOutside);
612
+ return () => {
613
+ document.removeEventListener("mousedown", handleClickOutside);
614
+ };
615
+ }, []);
616
+ return /* @__PURE__ */ jsx20(BasicFormField, { isDanger, dangerText, children: /* @__PURE__ */ jsxs9("div", { ref: containerRef, className: "relative w-full", children: [
617
+ /* @__PURE__ */ jsxs9(
618
+ "div",
619
+ {
620
+ tabIndex: 0,
621
+ className: getInputStyles(
622
+ isDanger,
623
+ cn(
624
+ "flex items-center justify-between cursor-pointer select-none",
625
+ isOpen && "border-black",
626
+ // 💡 열려있을 때는 테두리를 검은색으로 고정 (포커스 효과)
627
+ className
628
+ )
629
+ ),
630
+ onClick: (e) => {
631
+ if (isOpen) {
632
+ e.currentTarget.blur();
633
+ }
634
+ setIsOpen((prev) => !prev);
635
+ },
636
+ children: [
637
+ /* @__PURE__ */ jsx20("span", { className: value ? "text-primary" : "text-quaternary", children: value || placeholder }),
638
+ /* @__PURE__ */ jsx20(
639
+ IconChevronDownXS,
640
+ {
641
+ className: cn("text-gray06 transition-transform", isOpen && "rotate-180"),
642
+ stroke: "currentColor"
643
+ }
644
+ )
645
+ ]
646
+ }
647
+ ),
648
+ isOpen && /* @__PURE__ */ jsx20(
649
+ "ul",
650
+ {
651
+ className: cn(
652
+ "absolute top-full left-0 right-0 z-10",
653
+ // 💡 Input 바로 아래에 띄움
654
+ "box-border border border-gray02 bg-white flex flex-col",
655
+ "max-h-50 overflow-y-auto shadow-sm"
656
+ // 💡 항목이 너무 많으면 스크롤 생성
657
+ ),
658
+ children: options.map((option) => /* @__PURE__ */ jsx20(
659
+ SelectBoxOption,
660
+ {
661
+ label: option,
662
+ onClick: () => {
663
+ onChange(option);
664
+ setIsOpen(false);
665
+ }
666
+ },
667
+ option
668
+ ))
669
+ }
670
+ )
671
+ ] }) });
672
+ };
673
+
674
+ // src/components/TextButton/TextButton.tsx
675
+ import { Fragment, jsx as jsx21, jsxs as jsxs10 } from "react/jsx-runtime";
676
+ var TextButton = ({
677
+ children,
678
+ icon,
679
+ iconPosition = "left",
680
+ alert = false,
681
+ underline = true,
682
+ className,
683
+ ...props
684
+ }) => {
685
+ const content = /* @__PURE__ */ jsxs10(Fragment, { children: [
686
+ icon && iconPosition === "left" && /* @__PURE__ */ jsx21("span", { className: "flex shrink-0 items-center", children: icon }),
687
+ /* @__PURE__ */ jsx21("span", { className: cn(underline && "underline underline-offset-2"), children }),
688
+ icon && iconPosition === "right" && /* @__PURE__ */ jsx21("span", { className: "flex shrink-0 items-center", children: icon })
689
+ ] });
690
+ const classes = cn(
691
+ "inline-flex w-fit items-center gap-1 px-1 -mx-1",
692
+ "text-caption-book",
693
+ alert ? "text-danger" : "text-tertiary",
694
+ "hover:bg-gray00 active:bg-gray01 transition-colors",
695
+ alert ? "hover:text-danger" : "hover:text-primary",
696
+ "cursor-pointer select-none",
697
+ "disabled:cursor-default disabled:text-quaternary disabled:hover:bg-transparent",
698
+ "aria-disabled:cursor-default aria-disabled:text-quaternary aria-disabled:hover:bg-transparent",
699
+ className
700
+ );
701
+ if (props.href !== void 0) {
702
+ const { href, ...rest } = props;
703
+ return /* @__PURE__ */ jsx21("a", { href, className: classes, ...rest, children: content });
704
+ }
705
+ const buttonProps = props;
706
+ return /* @__PURE__ */ jsx21("button", { type: "button", className: classes, ...buttonProps, children: content });
707
+ };
708
+
709
+ // src/components/Dropdown/Dropdown.tsx
710
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState4 } from "react";
711
+ import { jsx as jsx22, jsxs as jsxs11 } from "react/jsx-runtime";
712
+ var Dropdown = ({ selectedLabel, setSelectedLabel, labels, className }) => {
713
+ const [isOpen, setIsOpen] = useState4(false);
714
+ const containerRef = useRef2(null);
715
+ useEffect2(() => {
716
+ const handleClickOutside = (event) => {
717
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
718
+ setIsOpen(false);
719
+ }
720
+ };
721
+ document.addEventListener("mousedown", handleClickOutside);
722
+ return () => {
723
+ document.removeEventListener("mousedown", handleClickOutside);
724
+ };
725
+ }, []);
726
+ return /* @__PURE__ */ jsxs11("div", { ref: containerRef, className: "relative", children: [
727
+ /* @__PURE__ */ jsxs11(
728
+ "div",
729
+ {
730
+ className: cn(
731
+ "w-fit h-fit px-2 py-1",
732
+ "flex items-center justify-center gap-1.5",
733
+ "text-body3-book text-strong",
734
+ "bg-white hover:bg-gray00 active:bg-gray01",
735
+ "cursor-pointer",
736
+ className
737
+ ),
738
+ onClick: () => setIsOpen((prev) => !prev),
739
+ children: [
740
+ /* @__PURE__ */ jsx22("p", { children: selectedLabel }),
741
+ /* @__PURE__ */ jsx22(
742
+ IconChevronDownXS,
743
+ {
744
+ className: cn("text-gray06 transition-transform", isOpen && "rotate-180"),
745
+ stroke: "currentColor"
746
+ }
747
+ )
748
+ ]
749
+ }
750
+ ),
751
+ isOpen && /* @__PURE__ */ jsx22("div", { className: cn("absolute top-full right-0 z-10", "box-border border border-gray02"), children: labels.map((label) => /* @__PURE__ */ jsx22(
752
+ SelectBoxOption,
753
+ {
754
+ label,
755
+ onClick: () => {
756
+ setSelectedLabel(label);
757
+ setIsOpen(false);
758
+ },
759
+ className: "w-44 h-11 justify-center"
760
+ },
761
+ label
762
+ )) })
763
+ ] });
764
+ };
765
+
766
+ // src/components/ListView/List.tsx
767
+ import { jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
768
+ function List({
769
+ className,
770
+ label1,
771
+ label2,
772
+ label2OnClick,
773
+ label3,
774
+ disabled = false,
775
+ selected = false,
776
+ onClick,
777
+ children,
778
+ ...props
779
+ }) {
780
+ return /* @__PURE__ */ jsxs12(
781
+ "li",
782
+ {
783
+ className: cn(
784
+ "w-full min-w-fit h-13 pl-4 pr-6",
785
+ "grid grid-cols-[4fr_1fr_1fr] items-center gap-4",
786
+ "bg-white hover:bg-gray00 hover:has-[.has-clickable:hover]:bg-white active:bg-gray01",
787
+ "text-primary select-none [&_*]:cursor-pointer cursor-pointer",
788
+ disabled && "opacity-40 [&_*]:cursor-pointer cursor-pointer",
789
+ selected && "bg-black text-white hover:bg-black active:bg-black hover:has-[.has-clickable:hover]:bg-black",
790
+ className
791
+ ),
792
+ ...props,
793
+ onClick,
794
+ children: [
795
+ /* @__PURE__ */ jsxs12("section", { className: cn("flex items-center gap-4"), children: [
796
+ /* @__PURE__ */ jsx23("label", { className: cn("text-body2-regular"), children: label1 ? label1 : "-" }),
797
+ /* @__PURE__ */ jsx23("div", { className: cn("flex items-center gap-2"), children })
798
+ ] }),
799
+ /* @__PURE__ */ jsx23(
800
+ "label",
801
+ {
802
+ className: cn(
803
+ "text-body3-book truncate p-1 w-fit",
804
+ label2OnClick && "underline has-clickable",
805
+ label2OnClick && !selected && "hover:bg-gray00"
806
+ ),
807
+ onClick: label2OnClick,
808
+ children: label2 ? label2 : "-"
809
+ }
810
+ ),
811
+ /* @__PURE__ */ jsx23("label", { className: cn("text-body3-regular text-right"), children: label3 ? label3 : "-" })
812
+ ]
813
+ }
814
+ );
815
+ }
816
+
817
+ // src/components/Modal/Modal.tsx
818
+ import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
819
+ var ModalBase = ({ isVisible, header, children, className }) => {
820
+ if (!isVisible) return null;
821
+ return /* @__PURE__ */ jsx24("div", { className: cn("fixed inset-0 z-50 pointer-events-auto"), children: /* @__PURE__ */ jsx24(
822
+ "div",
823
+ {
824
+ className: cn("fixed inset-0 z-1001 flex items-center justify-center pointer-events-auto"),
825
+ children: /* @__PURE__ */ jsxs13(
826
+ "main",
827
+ {
828
+ className: cn(
829
+ "w-110 pt-8 h-fit",
830
+ "flex flex-col items-center",
831
+ "border border-gray02",
832
+ "bg-white shadow-[0 0 30px 0 rgba(0, 0, 0, 0.08);]",
833
+ className
834
+ ),
835
+ children: [
836
+ /* @__PURE__ */ jsx24("h1", { className: cn("text-strong font-[500] text-[24px] leading-[32px]"), children: header }),
837
+ children
838
+ ]
839
+ }
840
+ )
841
+ }
842
+ ) });
843
+ };
844
+ var Contents = ({ children, className }) => {
845
+ return /* @__PURE__ */ jsx24(
846
+ "div",
847
+ {
848
+ className: cn(
849
+ "flex flex-col items-center gap-8",
850
+ "w-full h-fit",
851
+ "px-14 pt-8 pb-9",
852
+ className
853
+ ),
854
+ children
855
+ }
856
+ );
857
+ };
858
+ var CTAs2 = ({ children, className, row = false }) => {
859
+ if (!children) return null;
860
+ return (
861
+ // CTA 버튼들을 중앙에 배치하는 예시 (우측 정렬이 필요하면 justify-end 사용)
862
+ /* @__PURE__ */ jsx24(
863
+ "div",
864
+ {
865
+ className: cn(
866
+ "flex flex-col w-full h-fit",
867
+ row && "flex-row divide-x divide-gray02",
868
+ className
869
+ ),
870
+ children
871
+ }
872
+ )
873
+ );
874
+ };
875
+ var Modal = Object.assign(ModalBase, {
876
+ Contents,
877
+ CTAs: CTAs2
878
+ });
879
+
880
+ // src/components/Modal/ModalText.tsx
881
+ import { jsx as jsx25 } from "react/jsx-runtime";
882
+ var ModalText = ({ children, className }) => {
883
+ return /* @__PURE__ */ jsx25(
884
+ "div",
885
+ {
886
+ className: cn(
887
+ "flex flex-col items-center",
888
+ "mt-[-20px] mb-[-4px]",
889
+ ModalTextStyle,
890
+ "text-tertiary text-center",
891
+ className
892
+ ),
893
+ children
894
+ }
895
+ );
896
+ };
897
+
898
+ // src/components/createPost/CategorySegment.tsx
899
+ import { jsx as jsx26 } from "react/jsx-runtime";
174
900
  var CategorySegment = ({
175
901
  selected = false,
176
902
  label,
177
903
  className,
178
904
  onClick
179
905
  }) => {
180
- return /* @__PURE__ */ jsx4(
906
+ return /* @__PURE__ */ jsx26(
181
907
  "div",
182
908
  {
183
909
  className: cn(
@@ -197,7 +923,7 @@ var CategorySegment = ({
197
923
  };
198
924
 
199
925
  // src/components/createPost/CategorySegmentedControl.tsx
200
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
926
+ import { jsx as jsx27, jsxs as jsxs14 } from "react/jsx-runtime";
201
927
  var categorySegments = {
202
928
  info: ["\uACF5\uC6A9 \uBB3C\uD488", "\uC77C\uBC18 \uAE30\uC790\uC7AC", "\uD2B9\uC218 \uAE30\uC790\uC7AC"],
203
929
  archive: ["\uD504\uB9B0\uD2B8\uC2E4", "\uD64D\uC775\uC2DC\uB514"],
@@ -211,8 +937,8 @@ var CategorySegmentedControl = ({
211
937
  className
212
938
  }) => {
213
939
  const segments = categorySegments[variable];
214
- return /* @__PURE__ */ jsxs2("div", { className: cn("w-full h-fit flex items-center justify-start", className), children: [
215
- /* @__PURE__ */ jsx5(
940
+ return /* @__PURE__ */ jsxs14("div", { className: cn("w-full h-fit flex items-center justify-start", className), children: [
941
+ /* @__PURE__ */ jsx27(
216
942
  "div",
217
943
  {
218
944
  className: cn(
@@ -225,7 +951,7 @@ var CategorySegmentedControl = ({
225
951
  children: variable === "infoTag" ? "\uD0DC\uADF8 \uC120\uD0DD" : "\uCE74\uD14C\uACE0\uB9AC \uC120\uD0DD"
226
952
  }
227
953
  ),
228
- segments.map((label) => /* @__PURE__ */ jsx5(
954
+ segments.map((label) => /* @__PURE__ */ jsx27(
229
955
  CategorySegment,
230
956
  {
231
957
  label,
@@ -238,9 +964,9 @@ var CategorySegmentedControl = ({
238
964
  };
239
965
 
240
966
  // src/components/createPost/TitleInput.tsx
241
- import { jsx as jsx6 } from "react/jsx-runtime";
967
+ import { jsx as jsx28 } from "react/jsx-runtime";
242
968
  var TitleInput = ({ title, onChange, className }) => {
243
- return /* @__PURE__ */ jsx6(
969
+ return /* @__PURE__ */ jsx28(
244
970
  "input",
245
971
  {
246
972
  type: "text",
@@ -260,9 +986,9 @@ var TitleInput = ({ title, onChange, className }) => {
260
986
  };
261
987
 
262
988
  // src/components/notice/NoticeBadge.tsx
263
- import { jsx as jsx7 } from "react/jsx-runtime";
989
+ import { jsx as jsx29 } from "react/jsx-runtime";
264
990
  var NoticeBadge = ({ variable, className, onClick }) => {
265
- return /* @__PURE__ */ jsx7(
991
+ return /* @__PURE__ */ jsx29(
266
992
  "button",
267
993
  {
268
994
  type: "button",
@@ -283,7 +1009,7 @@ var NoticeBadge = ({ variable, className, onClick }) => {
283
1009
  };
284
1010
 
285
1011
  // src/components/notice/NoticeBar.tsx
286
- import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
1012
+ import { jsx as jsx30, jsxs as jsxs15 } from "react/jsx-runtime";
287
1013
  var NoticeBar = ({
288
1014
  variable = "default",
289
1015
  badgeVariable,
@@ -292,7 +1018,7 @@ var NoticeBar = ({
292
1018
  className,
293
1019
  onClick
294
1020
  }) => {
295
- return /* @__PURE__ */ jsxs3(
1021
+ return /* @__PURE__ */ jsxs15(
296
1022
  "div",
297
1023
  {
298
1024
  className: cn(
@@ -304,20 +1030,20 @@ var NoticeBar = ({
304
1030
  ),
305
1031
  onClick,
306
1032
  children: [
307
- /* @__PURE__ */ jsxs3("section", { className: "flex items-center gap-3", children: [
308
- /* @__PURE__ */ jsx8(NoticeBadge, { variable: badgeVariable }),
309
- /* @__PURE__ */ jsx8("h1", { className: "text-head1-medium text-strong", children: title })
1033
+ /* @__PURE__ */ jsxs15("section", { className: "flex items-center gap-3", children: [
1034
+ /* @__PURE__ */ jsx30(NoticeBadge, { variable: badgeVariable }),
1035
+ /* @__PURE__ */ jsx30("h1", { className: "text-head1-medium text-strong", children: title })
310
1036
  ] }),
311
- /* @__PURE__ */ jsx8("p", { className: "text-body3-book text-tertiary", children: date })
1037
+ /* @__PURE__ */ jsx30("p", { className: "text-body3-book text-tertiary", children: date })
312
1038
  ]
313
1039
  }
314
1040
  );
315
1041
  };
316
1042
 
317
1043
  // src/components/SearchBar/SearchBar.tsx
318
- import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
1044
+ import { jsx as jsx31, jsxs as jsxs16 } from "react/jsx-runtime";
319
1045
  var SearchBar = ({ value, onChange, className }) => {
320
- return /* @__PURE__ */ jsxs4(
1046
+ return /* @__PURE__ */ jsxs16(
321
1047
  "label",
322
1048
  {
323
1049
  className: cn(
@@ -328,8 +1054,8 @@ var SearchBar = ({ value, onChange, className }) => {
328
1054
  className
329
1055
  ),
330
1056
  children: [
331
- /* @__PURE__ */ jsx9(IconSearch, {}),
332
- /* @__PURE__ */ jsx9(
1057
+ /* @__PURE__ */ jsx31(IconSearch, {}),
1058
+ /* @__PURE__ */ jsx31(
333
1059
  "input",
334
1060
  {
335
1061
  type: "text",
@@ -348,7 +1074,22 @@ var SearchBar = ({ value, onChange, className }) => {
348
1074
  };
349
1075
 
350
1076
  // src/components/SideRadioAccordion/SideRadioAccordion.tsx
351
- import { useState } from "react";
1077
+ import { forwardRef } from "react";
1078
+
1079
+ // src/lib/useControllableState.ts
1080
+ import { useCallback, useState as useState5 } from "react";
1081
+ function useControllableState(controlled, defaultValue) {
1082
+ const isControlled = controlled !== void 0;
1083
+ const [uncontrolled, setUncontrolled] = useState5(defaultValue);
1084
+ const value = isControlled ? controlled : uncontrolled;
1085
+ const setValue = useCallback(
1086
+ (next) => {
1087
+ if (!isControlled) setUncontrolled(next);
1088
+ },
1089
+ [isControlled]
1090
+ );
1091
+ return [value, setValue, isControlled];
1092
+ }
352
1093
 
353
1094
  // src/components/SideRadioAccordion/Exclamation.svg
354
1095
  var Exclamation_default = "./Exclamation-IUYZYYC4.svg";
@@ -363,7 +1104,7 @@ var RadioSelected_default = "./RadioSelected-DOLVV7ZM.svg";
363
1104
  var RadioUnselected_default = "./RadioUnselected-QN74EU46.svg";
364
1105
 
365
1106
  // src/components/SideRadioAccordion/SideRadioAccordion.tsx
366
- import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
1107
+ import { jsx as jsx32, jsxs as jsxs17 } from "react/jsx-runtime";
367
1108
  var printColorBgClass = {
368
1109
  Plotter: "bg-print-plotter",
369
1110
  Printer: "bg-print-printer",
@@ -373,72 +1114,93 @@ var printColorBgClass = {
373
1114
  "Seet Cutting": "bg-print-seet-cutting",
374
1115
  "Laser Cutting": "bg-print-laser-cutting"
375
1116
  };
376
- var SideRadioAccordion = ({
377
- step,
378
- title,
379
- radioToggleList,
380
- defaultSelected,
381
- onSelect,
382
- quantityLimitLabel,
383
- onClickQuantityLimit,
384
- className
385
- }) => {
386
- const [isOpen, setIsOpen] = useState(true);
387
- const [selected, setSelected] = useState(defaultSelected);
388
- const handleSelect = (itemTitle) => {
389
- setSelected(itemTitle);
390
- onSelect?.(itemTitle);
391
- };
392
- return /* @__PURE__ */ jsxs5("div", { className: cn("w-full", className), children: [
393
- /* @__PURE__ */ jsx10(
394
- Stepper,
395
- {
396
- step,
397
- title,
398
- isOpen,
399
- onClick: () => setIsOpen((prev) => !prev)
400
- }
401
- ),
402
- isOpen && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col", children: [
403
- radioToggleList.map((item) => /* @__PURE__ */ jsx10(
404
- RadioToggleItem,
405
- {
406
- ...item,
407
- open: selected === item.title,
408
- onSelect: () => handleSelect(item.title)
409
- },
410
- item.title
411
- )),
412
- quantityLimitLabel && /* @__PURE__ */ jsx10(
413
- TextButton,
1117
+ var itemKey = (item) => item.value ?? item.title;
1118
+ var SideRadioAccordion = forwardRef(
1119
+ function SideRadioAccordion2({
1120
+ step,
1121
+ title,
1122
+ radioToggleList,
1123
+ selected,
1124
+ defaultSelected,
1125
+ onSelect,
1126
+ stickyHeader = false,
1127
+ collapsible = true,
1128
+ defaultOpen = true,
1129
+ open,
1130
+ onOpenChange,
1131
+ quantityLimitLabel,
1132
+ onClickQuantityLimit,
1133
+ className
1134
+ }, ref) {
1135
+ const [selectedValue, setSelectedValue] = useControllableState(
1136
+ selected,
1137
+ defaultSelected ?? null
1138
+ );
1139
+ const [isOpen, setIsOpen] = useControllableState(open, defaultOpen);
1140
+ const expanded = collapsible ? isOpen : true;
1141
+ const handleSelect = (value) => {
1142
+ setSelectedValue(value);
1143
+ onSelect?.(value);
1144
+ };
1145
+ const toggleOpen = () => {
1146
+ if (!collapsible) return;
1147
+ const next = !isOpen;
1148
+ setIsOpen(next);
1149
+ onOpenChange?.(next);
1150
+ };
1151
+ return /* @__PURE__ */ jsxs17("div", { ref, className: cn("w-full", className), children: [
1152
+ /* @__PURE__ */ jsx32(
1153
+ Stepper,
414
1154
  {
415
- label: quantityLimitLabel,
416
- onClick: onClickQuantityLimit,
417
- className: "mt-2"
1155
+ step,
1156
+ title,
1157
+ isOpen: expanded,
1158
+ collapsible,
1159
+ onClick: toggleOpen,
1160
+ className: cn(stickyHeader && "sticky top-0 z-10")
418
1161
  }
419
- )
420
- ] })
1162
+ ),
1163
+ expanded && /* @__PURE__ */ jsxs17("div", { className: "flex flex-col", children: [
1164
+ radioToggleList.map((item) => /* @__PURE__ */ jsx32(
1165
+ RadioToggleItem,
1166
+ {
1167
+ ...item,
1168
+ open: selectedValue === itemKey(item),
1169
+ onSelect: () => handleSelect(itemKey(item))
1170
+ },
1171
+ itemKey(item)
1172
+ )),
1173
+ quantityLimitLabel && /* @__PURE__ */ jsx32(
1174
+ TextButton2,
1175
+ {
1176
+ label: quantityLimitLabel,
1177
+ onClick: onClickQuantityLimit,
1178
+ className: "mt-2"
1179
+ }
1180
+ )
1181
+ ] })
1182
+ ] });
1183
+ }
1184
+ );
1185
+ var Stepper = ({ step, title, isOpen, collapsible, onClick, className }) => {
1186
+ const label = /* @__PURE__ */ jsxs17("span", { children: [
1187
+ step,
1188
+ " | ",
1189
+ title
421
1190
  ] });
422
- };
423
- var Stepper = ({ step, title, isOpen, onClick, className }) => {
424
- return /* @__PURE__ */ jsxs5(
1191
+ const base = "w-full flex items-center justify-between px-3 py-2.5 text-body1-medium text-strong text-left bg-white";
1192
+ if (!collapsible) {
1193
+ return /* @__PURE__ */ jsx32("div", { className: cn(base, className), children: label });
1194
+ }
1195
+ return /* @__PURE__ */ jsxs17(
425
1196
  "button",
426
1197
  {
427
1198
  type: "button",
428
1199
  onClick,
429
- className: cn(
430
- "w-full flex items-center justify-between px-3 py-2.5",
431
- "text-body1-medium text-strong text-left cursor-pointer",
432
- "bg-white hover:bg-gray00 active:bg-gray01",
433
- className
434
- ),
1200
+ className: cn(base, "cursor-pointer hover:bg-gray00 active:bg-gray01", className),
435
1201
  children: [
436
- /* @__PURE__ */ jsxs5("span", { children: [
437
- step,
438
- " | ",
439
- title
440
- ] }),
441
- /* @__PURE__ */ jsx10(
1202
+ label,
1203
+ /* @__PURE__ */ jsx32(
442
1204
  "img",
443
1205
  {
444
1206
  src: ChevronDown_default,
@@ -455,34 +1217,39 @@ var RadioToggleItem = ({
455
1217
  content,
456
1218
  badges,
457
1219
  printColor,
1220
+ color,
1221
+ className,
458
1222
  labelTextButton,
459
1223
  onClickTextButton,
460
1224
  open,
461
- onSelect,
462
- className
1225
+ onSelect
463
1226
  }) => {
464
- return /* @__PURE__ */ jsxs5("div", { className: cn("w-full", className), children: [
465
- /* @__PURE__ */ jsxs5(
1227
+ const rowBg = !open ? "bg-white hover:bg-gray00" : color ? void 0 : printColor ? printColorBgClass[printColor] : "bg-gray00";
1228
+ const style = open && color ? { backgroundColor: color } : void 0;
1229
+ return /* @__PURE__ */ jsxs17("div", { className: "w-full", children: [
1230
+ /* @__PURE__ */ jsxs17(
466
1231
  "button",
467
1232
  {
468
1233
  type: "button",
469
1234
  onClick: onSelect,
1235
+ style,
470
1236
  className: cn(
471
1237
  "w-full flex items-center gap-1.5 px-3 py-2.5",
472
1238
  "text-body2-regular text-primary text-left cursor-pointer",
473
- open ? printColor ? printColorBgClass[printColor] : "bg-gray00" : "bg-white hover:bg-gray00"
1239
+ rowBg,
1240
+ className
474
1241
  ),
475
1242
  children: [
476
- /* @__PURE__ */ jsx10(Radio, { selected: open }),
477
- /* @__PURE__ */ jsx10("span", { children: title })
1243
+ /* @__PURE__ */ jsx32(Radio, { selected: open }),
1244
+ /* @__PURE__ */ jsx32("span", { children: title })
478
1245
  ]
479
1246
  }
480
1247
  ),
481
- open && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2 px-3 py-3 bg-white", children: [
482
- /* @__PURE__ */ jsx10("p", { className: "text-caption-regular text-secondary", children: content }),
483
- badges && badges.length > 0 && /* @__PURE__ */ jsx10("div", { className: "flex gap-1", children: badges.map((badge) => /* @__PURE__ */ jsx10(Badge, { label: badge }, badge)) }),
484
- labelTextButton && /* @__PURE__ */ jsx10(
485
- TextButton,
1248
+ open && /* @__PURE__ */ jsxs17("div", { className: "flex flex-col gap-2 px-3 py-3 bg-white", children: [
1249
+ /* @__PURE__ */ jsx32("p", { className: "text-caption-regular text-secondary", children: content }),
1250
+ badges && badges.length > 0 && /* @__PURE__ */ jsx32("div", { className: "flex gap-1", children: badges.map((badge) => /* @__PURE__ */ jsx32(Badge, { label: badge }, badge)) }),
1251
+ labelTextButton && /* @__PURE__ */ jsx32(
1252
+ TextButton2,
486
1253
  {
487
1254
  label: labelTextButton,
488
1255
  onClick: onClickTextButton,
@@ -494,7 +1261,7 @@ var RadioToggleItem = ({
494
1261
  ] });
495
1262
  };
496
1263
  var Radio = ({ selected = false, className }) => {
497
- return /* @__PURE__ */ jsx10(
1264
+ return /* @__PURE__ */ jsx32(
498
1265
  "img",
499
1266
  {
500
1267
  src: selected ? RadioSelected_default : RadioUnselected_default,
@@ -503,8 +1270,8 @@ var Radio = ({ selected = false, className }) => {
503
1270
  }
504
1271
  );
505
1272
  };
506
- var TextButton = ({ label, onClick, showIcon = false, className }) => {
507
- return /* @__PURE__ */ jsxs5(
1273
+ var TextButton2 = ({ label, onClick, showIcon = false, className }) => {
1274
+ return /* @__PURE__ */ jsxs17(
508
1275
  "button",
509
1276
  {
510
1277
  type: "button",
@@ -517,14 +1284,14 @@ var TextButton = ({ label, onClick, showIcon = false, className }) => {
517
1284
  className
518
1285
  ),
519
1286
  children: [
520
- showIcon && /* @__PURE__ */ jsx10("img", { src: Exclamation_default, alt: "" }),
1287
+ showIcon && /* @__PURE__ */ jsx32("img", { src: Exclamation_default, alt: "" }),
521
1288
  label
522
1289
  ]
523
1290
  }
524
1291
  );
525
1292
  };
526
1293
  var Badge = ({ label, className }) => {
527
- return /* @__PURE__ */ jsx10(
1294
+ return /* @__PURE__ */ jsx32(
528
1295
  "div",
529
1296
  {
530
1297
  className: cn(
@@ -538,7 +1305,7 @@ var Badge = ({ label, className }) => {
538
1305
  };
539
1306
 
540
1307
  // src/components/Tag/Tag.tsx
541
- import { jsx as jsx11 } from "react/jsx-runtime";
1308
+ import { jsx as jsx33 } from "react/jsx-runtime";
542
1309
  var Tag = ({
543
1310
  className,
544
1311
  children,
@@ -548,7 +1315,7 @@ var Tag = ({
548
1315
  onClick,
549
1316
  ...props
550
1317
  }) => {
551
- return /* @__PURE__ */ jsx11(
1318
+ return /* @__PURE__ */ jsx33(
552
1319
  "button",
553
1320
  {
554
1321
  className: cn(
@@ -558,7 +1325,7 @@ var Tag = ({
558
1325
  "box-border border border-gray02",
559
1326
  "bg-white",
560
1327
  // bgColor && `bg-[${bgColor}] border-none`,
561
- onClick && "cursor-pointer hover:bg-gray00 active:bg-gray01 cursor-pointer",
1328
+ onClick && "cursor-pointer hover:bg-gray00 active:bg-gray01 cursor-pointer has-clickable",
562
1329
  selected && "text-white bg-black border-black hover:bg-black active:bg-black",
563
1330
  "disabled:text-gray04 disabled:bg-gray02 disabled:border-gray02 disabled:cursor-not-allowed",
564
1331
  className
@@ -567,11 +1334,17 @@ var Tag = ({
567
1334
  disabled,
568
1335
  style: bgColor ? { backgroundColor: bgColor, border: "none" } : void 0,
569
1336
  ...props,
570
- children: /* @__PURE__ */ jsx11("span", { children })
1337
+ children: /* @__PURE__ */ jsx33("span", { children })
571
1338
  }
572
1339
  );
573
1340
  };
574
1341
 
1342
+ // src/components/Timetable/Timetable.tsx
1343
+ import { forwardRef as forwardRef3 } from "react";
1344
+
1345
+ // src/components/Timetable/WeekNavigation.tsx
1346
+ import { forwardRef as forwardRef2 } from "react";
1347
+
575
1348
  // src/components/Timetable/dateUtils.ts
576
1349
  var KOREAN_WEEKDAYS = ["\uC77C", "\uC6D4", "\uD654", "\uC218", "\uBAA9", "\uAE08", "\uD1A0"];
577
1350
  var parseISODate = (date) => {
@@ -603,49 +1376,42 @@ var formatWeekRange = (weekStart) => {
603
1376
  };
604
1377
 
605
1378
  // src/components/Timetable/WeekNavigation.tsx
606
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
1379
+ import { jsx as jsx34, jsxs as jsxs18 } from "react/jsx-runtime";
607
1380
  var weekLabel = {
608
1381
  thisWeek: "\uC774\uBC88 \uC8FC",
609
1382
  nextWeek: "\uB2E4\uC74C \uC8FC",
610
1383
  twoWeeksLater: "\uB2E4\uB2E4\uC74C \uC8FC"
611
1384
  };
612
- var WeekNavigation = ({
613
- date,
614
- week,
615
- onPrevWeek,
616
- onNextWeek,
617
- className
618
- }) => {
619
- const showPrev = week !== "thisWeek";
620
- const showNext = week !== "twoWeeksLater";
621
- return /* @__PURE__ */ jsxs6(
622
- "div",
623
- {
624
- className: cn(
625
- "flex items-center justify-between",
626
- "w-full h-11 px-3",
627
- "bg-white border border-gray02",
628
- "text-body2-regular text-primary",
629
- className
630
- ),
631
- children: [
632
- /* @__PURE__ */ jsx12(ArrowButton, { direction: "left", visible: showPrev, onClick: onPrevWeek }),
633
- /* @__PURE__ */ jsxs6("span", { children: [
634
- weekLabel[week],
635
- " (",
636
- formatWeekRange(date),
637
- ")"
638
- ] }),
639
- /* @__PURE__ */ jsx12(ArrowButton, { direction: "right", visible: showNext, onClick: onNextWeek })
640
- ]
641
- }
642
- );
643
- };
1385
+ var WeekNavigation = forwardRef2(
1386
+ function WeekNavigation2({ date, week, label, canPrev, canNext, onPrevWeek, onNextWeek, className }, ref) {
1387
+ const showPrev = canPrev ?? (week ? week !== "thisWeek" : true);
1388
+ const showNext = canNext ?? (week ? week !== "twoWeeksLater" : true);
1389
+ const text = label ?? (week ? `${weekLabel[week]} (${formatWeekRange(date)})` : formatWeekRange(date));
1390
+ return /* @__PURE__ */ jsxs18(
1391
+ "div",
1392
+ {
1393
+ ref,
1394
+ className: cn(
1395
+ "flex items-center justify-between",
1396
+ "w-full h-11 px-3",
1397
+ "bg-white border border-gray02",
1398
+ "text-body2-regular text-primary",
1399
+ className
1400
+ ),
1401
+ children: [
1402
+ /* @__PURE__ */ jsx34(ArrowButton, { direction: "left", visible: showPrev, onClick: onPrevWeek }),
1403
+ /* @__PURE__ */ jsx34("span", { children: text }),
1404
+ /* @__PURE__ */ jsx34(ArrowButton, { direction: "right", visible: showNext, onClick: onNextWeek })
1405
+ ]
1406
+ }
1407
+ );
1408
+ }
1409
+ );
644
1410
  var ArrowButton = ({ direction, visible, onClick }) => {
645
1411
  if (!visible) {
646
- return /* @__PURE__ */ jsx12("span", { className: "w-6 h-6" });
1412
+ return /* @__PURE__ */ jsx34("span", { className: "w-6 h-6" });
647
1413
  }
648
- return /* @__PURE__ */ jsx12(
1414
+ return /* @__PURE__ */ jsx34(
649
1415
  "button",
650
1416
  {
651
1417
  type: "button",
@@ -655,7 +1421,7 @@ var ArrowButton = ({ direction, visible, onClick }) => {
655
1421
  "text-gray06 cursor-pointer",
656
1422
  "hover:bg-gray00 active:bg-gray01"
657
1423
  ),
658
- children: /* @__PURE__ */ jsx12(
1424
+ children: /* @__PURE__ */ jsx34(
659
1425
  IconChevronDownXS,
660
1426
  {
661
1427
  className: direction === "left" ? "rotate-90" : "-rotate-90",
@@ -667,7 +1433,7 @@ var ArrowButton = ({ direction, visible, onClick }) => {
667
1433
  };
668
1434
 
669
1435
  // src/components/Timetable/Timetable.tsx
670
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1436
+ import { jsx as jsx35, jsxs as jsxs19 } from "react/jsx-runtime";
671
1437
  var printColorHoverClass = {
672
1438
  Plotter: "hover:bg-print-plotter",
673
1439
  Printer: "hover:bg-print-printer",
@@ -677,66 +1443,87 @@ var printColorHoverClass = {
677
1443
  "Seet Cutting": "hover:bg-print-seet-cutting",
678
1444
  "Laser Cutting": "hover:bg-print-laser-cutting"
679
1445
  };
680
- var Timetable = ({
1446
+ var Timetable = forwardRef3(function Timetable2({
681
1447
  weekStart,
682
1448
  week,
683
1449
  days,
684
1450
  onPrevWeek,
685
1451
  onNextWeek,
686
1452
  onSelectSlot,
1453
+ fluid = false,
1454
+ hideWeekNavigation = false,
1455
+ canPrevWeek,
1456
+ canNextWeek,
687
1457
  className
688
- }) => {
689
- return /* @__PURE__ */ jsxs7("div", { className: cn(className), children: [
690
- /* @__PURE__ */ jsx13(
1458
+ }, ref) {
1459
+ return /* @__PURE__ */ jsxs19("div", { ref, className: cn(fluid && "w-full", className), children: [
1460
+ !hideWeekNavigation && /* @__PURE__ */ jsx35(
691
1461
  WeekNavigation,
692
1462
  {
693
1463
  date: weekStart,
694
1464
  week,
1465
+ canPrev: canPrevWeek,
1466
+ canNext: canNextWeek,
695
1467
  onPrevWeek,
696
1468
  onNextWeek
697
1469
  }
698
1470
  ),
699
- /* @__PURE__ */ jsx13("div", { className: "flex", children: days.map((day) => /* @__PURE__ */ jsx13(
1471
+ /* @__PURE__ */ jsx35("div", { className: cn("flex", fluid && "w-full"), children: days.map((day) => /* @__PURE__ */ jsx35(
700
1472
  DailyTimetable,
701
1473
  {
702
1474
  date: day.date,
703
1475
  slots: day.slots,
1476
+ fluid,
704
1477
  onSelectTime: onSelectSlot ? (time) => onSelectSlot(day.date, time) : void 0
705
1478
  },
706
1479
  day.date
707
1480
  )) })
708
1481
  ] });
709
- };
710
- var DailyTimetable = ({ date, slots, onSelectTime, className }) => {
711
- return /* @__PURE__ */ jsxs7("div", { className: cn("flex flex-col", className), children: [
712
- /* @__PURE__ */ jsx13("div", { className: "h-13 flex items-center justify-center text-body3-medium text-secondary", children: formatDayLabel(date) }),
713
- slots.map((slot) => /* @__PURE__ */ jsx13(
1482
+ });
1483
+ var DailyTimetable = ({ date, slots, fluid, onSelectTime, className }) => {
1484
+ return /* @__PURE__ */ jsxs19("div", { className: cn("flex flex-col", fluid ? "flex-1 min-w-0" : "w-18", className), children: [
1485
+ /* @__PURE__ */ jsx35("div", { className: "h-13 flex items-center justify-center text-body3-medium text-secondary", children: formatDayLabel(date) }),
1486
+ slots.map((slot) => /* @__PURE__ */ jsx35(
714
1487
  TimeItem,
715
1488
  {
716
1489
  state: slot.state,
717
1490
  label: slot.time,
718
1491
  printColor: slot.printColor,
1492
+ color: slot.color,
1493
+ fluid,
1494
+ className: slot.className,
719
1495
  onClick: onSelectTime ? () => onSelectTime(slot.time) : void 0
720
1496
  },
721
1497
  slot.time
722
1498
  ))
723
1499
  ] });
724
1500
  };
725
- var TimeItem = ({ state, label, printColor, onClick, className }) => {
1501
+ var TimeItem = ({
1502
+ state,
1503
+ label,
1504
+ printColor,
1505
+ color,
1506
+ fluid,
1507
+ onClick,
1508
+ className
1509
+ }) => {
726
1510
  const clickable = (state === "default" || state === "selected") && !!onClick;
727
- return /* @__PURE__ */ jsx13(
1511
+ const style = state === "default" && color ? { "--tt-hover": color } : void 0;
1512
+ return /* @__PURE__ */ jsx35(
728
1513
  "div",
729
1514
  {
730
1515
  onClick: clickable ? onClick : void 0,
1516
+ style,
731
1517
  className: cn(
732
- "w-18 h-18 flex items-center justify-center",
1518
+ "h-18 flex items-center justify-center",
1519
+ fluid ? "w-full" : "w-18",
733
1520
  "text-body1-medium text-center bg-white text-primary",
734
1521
  state === "none" && "bg-gray00 text-transparent",
735
1522
  state === "selected" && "bg-black text-gray02 hover:cursor-pointer",
736
1523
  state === "disabled" && "bg-gray00 text-gray03",
737
1524
  state === "default" && [
738
1525
  "hover:cursor-pointer",
739
- printColor ? printColorHoverClass[printColor] : "hover:bg-gray00"
1526
+ color ? "hover:bg-(--tt-hover)" : printColor ? printColorHoverClass[printColor] : "hover:bg-gray00"
740
1527
  ],
741
1528
  className
742
1529
  ),
@@ -746,81 +1533,181 @@ var TimeItem = ({ state, label, printColor, onClick, className }) => {
746
1533
  };
747
1534
 
748
1535
  // src/components/TNB/TNB.tsx
749
- import { useState as useState2 } from "react";
750
- import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
751
- var TNB = ({
1536
+ import {
1537
+ forwardRef as forwardRef4,
1538
+ useCallback as useCallback2,
1539
+ useState as useState6
1540
+ } from "react";
1541
+ import { jsx as jsx36, jsxs as jsxs20 } from "react/jsx-runtime";
1542
+ function useControllableState2(controlled, defaultValue) {
1543
+ const isControlled = controlled !== void 0;
1544
+ const [uncontrolled, setUncontrolled] = useState6(defaultValue);
1545
+ const value = isControlled ? controlled : uncontrolled;
1546
+ const setValue = useCallback2(
1547
+ (next) => {
1548
+ if (!isControlled) setUncontrolled(next);
1549
+ },
1550
+ [isControlled]
1551
+ );
1552
+ return [value, setValue, isControlled];
1553
+ }
1554
+ var TNB = forwardRef4(function TNB2({
752
1555
  logo,
753
1556
  upperPartItemList,
754
1557
  upperPartIconList,
1558
+ selected,
755
1559
  defaultSelected,
1560
+ onSelectedChange,
1561
+ selectedLower,
1562
+ defaultSelectedLower,
1563
+ onSelectedLowerChange,
756
1564
  onSelectItem,
1565
+ linkComponent,
1566
+ "aria-label": ariaLabel = "\uC8FC\uC694 \uBA54\uB274",
757
1567
  className
758
- }) => {
759
- const [selected, setSelected] = useState2(defaultSelected ?? upperPartItemList[0]?.label);
760
- const [selectedLower, setSelectedLower] = useState2(
761
- upperPartItemList.find((item) => item.label === selected)?.lowerPartItemList[0]?.label
1568
+ }, ref) {
1569
+ const [upperValue, setUpperValue] = useControllableState2(
1570
+ selected,
1571
+ defaultSelected ?? null
762
1572
  );
1573
+ const [lowerValueRaw, setLowerValue, lowerIsControlled] = useControllableState2(
1574
+ selectedLower,
1575
+ defaultSelectedLower ?? null
1576
+ );
1577
+ const currentUpper = upperPartItemList.find((item) => item.label === upperValue);
1578
+ const currentLowerItemList = currentUpper?.lowerPartItemList ?? [];
1579
+ const lowerValue = lowerIsControlled ? lowerValueRaw ?? void 0 : currentLowerItemList.some((item) => item.label === lowerValueRaw) ? lowerValueRaw : currentLowerItemList[0]?.label ?? void 0;
763
1580
  const handleSelectUpper = (label) => {
764
- setSelected(label);
765
- setSelectedLower(
766
- upperPartItemList.find((item) => item.label === label)?.lowerPartItemList[0]?.label
767
- );
1581
+ setUpperValue(label);
1582
+ if (!lowerIsControlled) {
1583
+ const nextLower = upperPartItemList.find((item) => item.label === label)?.lowerPartItemList[0]?.label ?? null;
1584
+ setLowerValue(nextLower);
1585
+ }
1586
+ onSelectedChange?.(label);
768
1587
  onSelectItem?.(label);
769
1588
  };
770
- const currentLowerItemList = upperPartItemList.find((item) => item.label === selected)?.lowerPartItemList ?? [];
771
- return /* @__PURE__ */ jsxs8("nav", { className: cn("w-full bg-white", className), children: [
772
- /* @__PURE__ */ jsx14(
1589
+ const handleSelectLower = (label) => {
1590
+ setLowerValue(label);
1591
+ onSelectedLowerChange?.(label);
1592
+ };
1593
+ return /* @__PURE__ */ jsxs20("nav", { ref, "aria-label": ariaLabel, className: cn("w-full bg-white", className), children: [
1594
+ /* @__PURE__ */ jsx36(
773
1595
  UpperPart,
774
1596
  {
775
1597
  logo,
776
1598
  upperPartItemList,
777
1599
  upperPartIconList,
778
- selected,
779
- onSelectItem: handleSelectUpper
1600
+ selected: upperValue ?? void 0,
1601
+ onSelectItem: handleSelectUpper,
1602
+ linkComponent
780
1603
  }
781
1604
  ),
782
- /* @__PURE__ */ jsx14(
1605
+ /* @__PURE__ */ jsx36(
783
1606
  LowerPart,
784
1607
  {
785
1608
  lowerPartItemList: currentLowerItemList,
786
- selected: selectedLower,
787
- onSelectItem: setSelectedLower
1609
+ selected: lowerValue,
1610
+ onSelectItem: handleSelectLower,
1611
+ linkComponent
788
1612
  }
789
1613
  )
790
1614
  ] });
791
- };
1615
+ });
1616
+ var NavClickable = forwardRef4(function NavClickable2({ href, onClick, linkComponent: Link, current, disabled, className, children }, ref) {
1617
+ const ariaCurrent = current ? "page" : void 0;
1618
+ if (href && !disabled) {
1619
+ if (Link) {
1620
+ return /* @__PURE__ */ jsx36(
1621
+ Link,
1622
+ {
1623
+ href,
1624
+ onClick,
1625
+ "aria-current": ariaCurrent,
1626
+ className,
1627
+ ref,
1628
+ children
1629
+ }
1630
+ );
1631
+ }
1632
+ return /* @__PURE__ */ jsx36(
1633
+ "a",
1634
+ {
1635
+ href,
1636
+ onClick,
1637
+ "aria-current": ariaCurrent,
1638
+ className,
1639
+ ref,
1640
+ children
1641
+ }
1642
+ );
1643
+ }
1644
+ return /* @__PURE__ */ jsx36(
1645
+ "button",
1646
+ {
1647
+ type: "button",
1648
+ onClick,
1649
+ disabled,
1650
+ "aria-current": ariaCurrent,
1651
+ className,
1652
+ ref,
1653
+ children
1654
+ }
1655
+ );
1656
+ });
792
1657
  var UpperPart = ({
793
1658
  logo,
794
1659
  upperPartItemList,
795
1660
  upperPartIconList,
796
1661
  selected,
797
1662
  onSelectItem,
1663
+ linkComponent,
798
1664
  className
799
1665
  }) => {
800
- return /* @__PURE__ */ jsxs8("div", { className: cn("w-full h-13 px-4 flex items-center justify-between", className), children: [
801
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-6", children: [
802
- /* @__PURE__ */ jsx14(NavIconButton, { ...logo }),
803
- /* @__PURE__ */ jsx14("div", { className: "flex items-center gap-4", children: upperPartItemList.map((item) => /* @__PURE__ */ jsx14(
1666
+ return /* @__PURE__ */ jsxs20("div", { className: cn("w-full h-13 px-4 flex items-center justify-between", className), children: [
1667
+ /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-6", children: [
1668
+ /* @__PURE__ */ jsx36(NavIconButton, { ...logo, linkComponent }),
1669
+ /* @__PURE__ */ jsx36("div", { className: "flex items-center gap-4", children: upperPartItemList.map((item) => /* @__PURE__ */ jsx36(
804
1670
  UpperPartTab,
805
1671
  {
806
1672
  label: item.label,
1673
+ href: item.href,
807
1674
  selected: item.label === selected,
808
- onClick: () => onSelectItem(item.label)
1675
+ linkComponent,
1676
+ onClick: () => {
1677
+ item.onClick?.();
1678
+ onSelectItem(item.label);
1679
+ }
809
1680
  },
810
- item.label
1681
+ item.id ?? item.label
811
1682
  )) })
812
1683
  ] }),
813
- /* @__PURE__ */ jsx14("div", { className: "flex items-center gap-2", children: upperPartIconList.map((icon) => /* @__PURE__ */ jsx14(NavIconButton, { ...icon }, icon.src)) })
1684
+ /* @__PURE__ */ jsx36("div", { className: "flex items-center gap-2", children: upperPartIconList.map((icon, index) => /* @__PURE__ */ jsx36(
1685
+ NavIconButton,
1686
+ {
1687
+ ...icon,
1688
+ linkComponent
1689
+ },
1690
+ icon.id ?? icon.src ?? icon.label ?? index
1691
+ )) })
814
1692
  ] });
815
1693
  };
816
- var UpperPartTab = ({ label, selected, onClick, className }) => {
817
- return /* @__PURE__ */ jsx14(
818
- "button",
1694
+ var UpperPartTab = ({
1695
+ label,
1696
+ href,
1697
+ selected,
1698
+ onClick,
1699
+ linkComponent,
1700
+ className
1701
+ }) => {
1702
+ return /* @__PURE__ */ jsx36(
1703
+ NavClickable,
819
1704
  {
820
- type: "button",
1705
+ href,
821
1706
  onClick,
1707
+ linkComponent,
1708
+ current: selected,
822
1709
  className: cn(
823
- "px-1 py-1.5 text-body2-regular cursor-pointer",
1710
+ "inline-flex px-1 py-1.5 text-body2-regular cursor-pointer no-underline",
824
1711
  selected ? "text-strong font-medium underline underline-offset-4" : "text-quaternary hover:text-secondary",
825
1712
  className
826
1713
  ),
@@ -828,54 +1715,92 @@ var UpperPartTab = ({ label, selected, onClick, className }) => {
828
1715
  }
829
1716
  );
830
1717
  };
831
- var NavIconButton = ({ src, alt, onClick, selected, className }) => {
832
- return /* @__PURE__ */ jsx14(
833
- "button",
834
- {
835
- type: "button",
836
- onClick,
837
- disabled: !onClick,
838
- className: cn(
839
- "w-8 h-8 flex items-center justify-center rounded-full",
840
- onClick && "cursor-pointer",
841
- selected ? "bg-black" : "bg-transparent hover:bg-gray00 active:bg-gray01",
842
- className
843
- ),
844
- children: /* @__PURE__ */ jsx14("img", { src, alt: alt ?? "", className: "w-5 h-5" })
845
- }
846
- );
847
- };
848
- var LowerPart = ({ lowerPartItemList, selected, onSelectItem, className }) => {
1718
+ var NavIconButton = forwardRef4(
1719
+ function NavIconButton2({ src, icon, alt, label, href, onClick, selected, badge, className, linkComponent }, forwardedRef) {
1720
+ const interactive = Boolean(onClick || href);
1721
+ const showBadge = badge !== void 0 && badge !== null && badge !== false;
1722
+ return /* @__PURE__ */ jsxs20(
1723
+ NavClickable,
1724
+ {
1725
+ ref: forwardedRef,
1726
+ href,
1727
+ onClick,
1728
+ linkComponent,
1729
+ current: selected,
1730
+ disabled: !interactive,
1731
+ className: cn(
1732
+ "relative inline-flex h-8 min-w-8 items-center justify-center gap-1.5 rounded-full px-1.5",
1733
+ interactive && "cursor-pointer",
1734
+ selected ? "bg-black text-white" : "bg-transparent hover:bg-gray00 active:bg-gray01",
1735
+ className
1736
+ ),
1737
+ children: [
1738
+ icon ? /* @__PURE__ */ jsx36("span", { className: "inline-flex h-5 w-5 items-center justify-center", children: icon }) : src ? /* @__PURE__ */ jsx36("img", { src, alt: alt ?? label ?? "", className: "h-5 w-5" }) : null,
1739
+ label ? /* @__PURE__ */ jsx36("span", { className: cn("text-body2-regular", selected ? "text-white" : "text-secondary"), children: label }) : null,
1740
+ showBadge ? /* @__PURE__ */ jsx36(
1741
+ "span",
1742
+ {
1743
+ "aria-hidden": badge === true,
1744
+ className: cn(
1745
+ "absolute -right-0.5 -top-0.5 flex items-center justify-center rounded-full bg-danger text-white",
1746
+ badge === true ? "h-2 w-2" : "h-4 min-w-4 px-1 text-caption-regular leading-none"
1747
+ ),
1748
+ children: badge === true ? null : badge
1749
+ }
1750
+ ) : null
1751
+ ]
1752
+ }
1753
+ );
1754
+ }
1755
+ );
1756
+ var LowerPart = ({
1757
+ lowerPartItemList,
1758
+ selected,
1759
+ onSelectItem,
1760
+ linkComponent,
1761
+ className
1762
+ }) => {
849
1763
  if (lowerPartItemList.length === 0) {
850
1764
  return null;
851
1765
  }
852
- return /* @__PURE__ */ jsx14(
1766
+ return /* @__PURE__ */ jsx36(
853
1767
  "div",
854
1768
  {
855
1769
  className: cn("w-full h-9 px-4 flex items-center gap-4 border-t border-gray02", className),
856
- children: lowerPartItemList.map((item) => /* @__PURE__ */ jsx14(
1770
+ children: lowerPartItemList.map((item) => /* @__PURE__ */ jsx36(
857
1771
  LowerPartItemButton,
858
1772
  {
859
1773
  label: item.label,
1774
+ href: item.href,
860
1775
  selected: item.label === selected,
1776
+ linkComponent,
861
1777
  onClick: () => {
862
1778
  item.onClick?.();
863
1779
  onSelectItem(item.label);
864
1780
  }
865
1781
  },
866
- item.label
1782
+ item.id ?? item.label
867
1783
  ))
868
1784
  }
869
1785
  );
870
1786
  };
871
- var LowerPartItemButton = ({ label, selected, onClick, className }) => {
872
- return /* @__PURE__ */ jsx14(
873
- "button",
1787
+ var LowerPartItemButton = ({
1788
+ label,
1789
+ href,
1790
+ selected,
1791
+ onClick,
1792
+ linkComponent,
1793
+ className
1794
+ }) => {
1795
+ return /* @__PURE__ */ jsx36(
1796
+ NavClickable,
874
1797
  {
875
- type: "button",
1798
+ href,
876
1799
  onClick,
1800
+ linkComponent,
1801
+ current: selected,
877
1802
  className: cn(
878
- "px-1.5 py-1 text-caption-regular cursor-pointer",
1803
+ "inline-flex px-1.5 py-1 text-caption-regular cursor-pointer no-underline",
879
1804
  "hover:bg-gray00 active:bg-gray01",
880
1805
  selected ? "text-strong font-medium" : "text-quaternary",
881
1806
  className
@@ -886,35 +1811,101 @@ var LowerPartItemButton = ({ label, selected, onClick, className }) => {
886
1811
  };
887
1812
 
888
1813
  // src/components/Toast/Toast.tsx
889
- import { jsx as jsx15 } from "react/jsx-runtime";
890
- var Toast = ({ message, className }) => {
891
- return /* @__PURE__ */ jsx15(
1814
+ import { forwardRef as forwardRef5, useEffect as useEffect3, useState as useState7 } from "react";
1815
+ import { jsx as jsx37 } from "react/jsx-runtime";
1816
+ var EXIT_MS = 300;
1817
+ var Toast = forwardRef5(function Toast2({ message, open, duration = 3e3, onClose, position = "none", className }, ref) {
1818
+ const controlled = open !== void 0;
1819
+ const [mounted, setMounted] = useState7(!controlled || open);
1820
+ const [visible, setVisible] = useState7(!controlled || open);
1821
+ useEffect3(() => {
1822
+ if (!controlled) return;
1823
+ if (open) {
1824
+ setMounted(true);
1825
+ const raf = requestAnimationFrame(() => setVisible(true));
1826
+ return () => cancelAnimationFrame(raf);
1827
+ }
1828
+ setVisible(false);
1829
+ const timer = setTimeout(() => setMounted(false), EXIT_MS);
1830
+ return () => clearTimeout(timer);
1831
+ }, [controlled, open]);
1832
+ useEffect3(() => {
1833
+ if (!controlled || !open || !duration) return;
1834
+ const timer = setTimeout(() => onClose?.(), duration);
1835
+ return () => clearTimeout(timer);
1836
+ }, [controlled, open, duration, onClose]);
1837
+ if (!mounted) return null;
1838
+ const banner = /* @__PURE__ */ jsx37(
892
1839
  "div",
893
1840
  {
1841
+ ref,
1842
+ role: "status",
1843
+ "aria-live": "polite",
894
1844
  className: cn(
895
- `bg-gray06 w-dvw h-fit py-2.5`,
896
- `font-pretendard text-white text-center font-semibold text-[17px]`,
897
- `shadow-[0_2px_15px_rgba(0,0,0,0.18)]`,
1845
+ "w-full h-fit py-2.5 bg-gray06",
1846
+ "font-pretendard text-white text-center font-semibold text-[17px]",
1847
+ "shadow-[0_2px_15px_rgba(0,0,0,0.18)]",
1848
+ controlled && "transition-opacity duration-300",
1849
+ controlled && (visible ? "opacity-100" : "opacity-0"),
898
1850
  className
899
1851
  ),
900
1852
  children: message
901
1853
  }
902
1854
  );
903
- };
1855
+ if (position === "none") return banner;
1856
+ return /* @__PURE__ */ jsx37(
1857
+ "div",
1858
+ {
1859
+ className: cn(
1860
+ "fixed inset-x-0 z-5000 pointer-events-none",
1861
+ position === "top" ? "top-0" : "bottom-0"
1862
+ ),
1863
+ children: banner
1864
+ }
1865
+ );
1866
+ });
904
1867
  export {
905
1868
  BackButton,
1869
+ BasicFormField,
906
1870
  CTA,
907
1871
  CategorySegment,
908
1872
  CategorySegmentedControl,
1873
+ Column,
1874
+ ColumnContentContainer,
1875
+ ColumnLabel,
1876
+ ColumnSubTitle,
1877
+ ColumnText,
1878
+ ColumnTextStyle,
1879
+ Dropdown,
1880
+ FormCheckBox,
1881
+ FormColor,
1882
+ FormFile,
1883
+ FormNumber,
1884
+ FormPassword,
1885
+ FormSelectBox,
1886
+ FormTel,
1887
+ FormText,
1888
+ FormTextArea,
1889
+ IconCheck,
1890
+ IconChevronDownXS,
1891
+ IconInfo,
1892
+ List,
1893
+ Modal,
1894
+ ModalText,
1895
+ ModalTextStyle,
1896
+ MultiColumn,
909
1897
  NoticeBadge,
910
1898
  NoticeBar,
911
1899
  SearchBar,
1900
+ SelectBoxOption,
912
1901
  SideRadioAccordion,
913
1902
  TNB,
914
1903
  Tag,
1904
+ TextButton,
915
1905
  Timetable,
916
1906
  TitleInput,
917
1907
  Toast,
918
- WeekNavigation
1908
+ WeekNavigation,
1909
+ getInputStyles
919
1910
  };
920
1911
  //# sourceMappingURL=index.mjs.map