@hivcd-printshop/design-system 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -176,32 +176,37 @@ var BackButton = ({ onClick, className }) => {
176
176
  // src/components/CTA/CTA.tsx
177
177
  var import_jsx_runtime3 = require("react/jsx-runtime");
178
178
  var CTA = ({
179
- primary = true,
180
- variable = "default",
179
+ primary = false,
180
+ alert = false,
181
+ disabled = false,
181
182
  label,
182
183
  onClick,
183
- className
184
+ className,
185
+ forModal = false,
186
+ ...props
184
187
  }) => {
185
188
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
186
189
  "button",
187
190
  {
188
191
  type: "button",
192
+ disabled,
189
193
  className: cn(
190
- "w-full max-w-360 h-12 p-2.5 text-center text-head3-medium",
191
- `${primary ? "border-0" : "border border-gray02"}`,
192
- /** Disabled */
193
- `${variable == "disabled" ? primary ? "bg-gray01 text-gray04 border border-gray01" : "bg-gray02 text-gray04" : ""}`,
194
- /** Default */
195
- `${variable == "default" ? primary ? "bg-black text-white" : "bg-white text-strong" : ""}`,
196
- /** Hover */
197
- `${variable != "disabled" ? primary ? "hover:bg-gray08 hover:text-white" : "hover:bg-gray01 hover:text-strong" : ""}`,
198
- /** Hover */
199
- `${variable != "disabled" && "hover:cursor-pointer"}`,
200
- /** Pressed */
201
- `${variable == "pressed" ? primary ? "bg-gray09 text-white" : "bg-gray02 text-strong" : ""}`,
194
+ forModal ? "h-14.5" : "h-12",
195
+ "w-full max-w-360 px-4",
196
+ "flex items-center justify-center",
197
+ "border-t box-border border-gray02",
198
+ primary && "border-none",
199
+ "text-head3-medium text-strong",
200
+ primary && "text-white",
201
+ alert && "text-danger",
202
+ disabled && "text-quaternary",
203
+ "bg-white hover:bg-gray00 active:bg-gray01 disabled:bg-gray02",
204
+ primary && "bg-black hover:bg-gray08 active:bg-gray09 disabled:bg-gray01",
205
+ "cursor-pointer disabled:cursor-default",
202
206
  className
203
207
  ),
204
208
  onClick,
209
+ ...props,
205
210
  children: label
206
211
  }
207
212
  );
@@ -386,7 +391,22 @@ var SearchBar = ({ value, onChange, className }) => {
386
391
  };
387
392
 
388
393
  // src/components/SideRadioAccordion/SideRadioAccordion.tsx
394
+ var import_react2 = require("react");
395
+
396
+ // src/lib/useControllableState.ts
389
397
  var import_react = require("react");
398
+ function useControllableState(controlled, defaultValue) {
399
+ const isControlled = controlled !== void 0;
400
+ const [uncontrolled, setUncontrolled] = (0, import_react.useState)(defaultValue);
401
+ const value = isControlled ? controlled : uncontrolled;
402
+ const setValue = (0, import_react.useCallback)(
403
+ (next) => {
404
+ if (!isControlled) setUncontrolled(next);
405
+ },
406
+ [isControlled]
407
+ );
408
+ return [value, setValue, isControlled];
409
+ }
390
410
 
391
411
  // src/components/SideRadioAccordion/Exclamation.svg
392
412
  var Exclamation_default = "./Exclamation-IUYZYYC4.svg";
@@ -411,71 +431,92 @@ var printColorBgClass = {
411
431
  "Seet Cutting": "bg-print-seet-cutting",
412
432
  "Laser Cutting": "bg-print-laser-cutting"
413
433
  };
414
- var SideRadioAccordion = ({
415
- step,
416
- title,
417
- radioToggleList,
418
- defaultSelected,
419
- onSelect,
420
- quantityLimitLabel,
421
- onClickQuantityLimit,
422
- className
423
- }) => {
424
- const [isOpen, setIsOpen] = (0, import_react.useState)(true);
425
- const [selected, setSelected] = (0, import_react.useState)(defaultSelected);
426
- const handleSelect = (itemTitle) => {
427
- setSelected(itemTitle);
428
- onSelect?.(itemTitle);
429
- };
430
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: cn("w-full", className), children: [
431
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
432
- Stepper,
433
- {
434
- step,
435
- title,
436
- isOpen,
437
- onClick: () => setIsOpen((prev) => !prev)
438
- }
439
- ),
440
- isOpen && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col", children: [
441
- radioToggleList.map((item) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
442
- RadioToggleItem,
443
- {
444
- ...item,
445
- open: selected === item.title,
446
- onSelect: () => handleSelect(item.title)
447
- },
448
- item.title
449
- )),
450
- quantityLimitLabel && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
451
- TextButton,
434
+ var itemKey = (item) => item.value ?? item.title;
435
+ var SideRadioAccordion = (0, import_react2.forwardRef)(
436
+ function SideRadioAccordion2({
437
+ step,
438
+ title,
439
+ radioToggleList,
440
+ selected,
441
+ defaultSelected,
442
+ onSelect,
443
+ stickyHeader = false,
444
+ collapsible = true,
445
+ defaultOpen = true,
446
+ open,
447
+ onOpenChange,
448
+ quantityLimitLabel,
449
+ onClickQuantityLimit,
450
+ className
451
+ }, ref) {
452
+ const [selectedValue, setSelectedValue] = useControllableState(
453
+ selected,
454
+ defaultSelected ?? null
455
+ );
456
+ const [isOpen, setIsOpen] = useControllableState(open, defaultOpen);
457
+ const expanded = collapsible ? isOpen : true;
458
+ const handleSelect = (value) => {
459
+ setSelectedValue(value);
460
+ onSelect?.(value);
461
+ };
462
+ const toggleOpen = () => {
463
+ if (!collapsible) return;
464
+ const next = !isOpen;
465
+ setIsOpen(next);
466
+ onOpenChange?.(next);
467
+ };
468
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref, className: cn("w-full", className), children: [
469
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
470
+ Stepper,
452
471
  {
453
- label: quantityLimitLabel,
454
- onClick: onClickQuantityLimit,
455
- className: "mt-2"
472
+ step,
473
+ title,
474
+ isOpen: expanded,
475
+ collapsible,
476
+ onClick: toggleOpen,
477
+ className: cn(stickyHeader && "sticky top-0 z-10")
456
478
  }
457
- )
458
- ] })
479
+ ),
480
+ expanded && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col", children: [
481
+ radioToggleList.map((item) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
482
+ RadioToggleItem,
483
+ {
484
+ ...item,
485
+ open: selectedValue === itemKey(item),
486
+ onSelect: () => handleSelect(itemKey(item))
487
+ },
488
+ itemKey(item)
489
+ )),
490
+ quantityLimitLabel && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
491
+ TextButton,
492
+ {
493
+ label: quantityLimitLabel,
494
+ onClick: onClickQuantityLimit,
495
+ className: "mt-2"
496
+ }
497
+ )
498
+ ] })
499
+ ] });
500
+ }
501
+ );
502
+ var Stepper = ({ step, title, isOpen, collapsible, onClick, className }) => {
503
+ const label = /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
504
+ step,
505
+ " | ",
506
+ title
459
507
  ] });
460
- };
461
- var Stepper = ({ step, title, isOpen, onClick, className }) => {
508
+ const base = "w-full flex items-center justify-between px-3 py-2.5 text-body1-medium text-strong text-left bg-white";
509
+ if (!collapsible) {
510
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: cn(base, className), children: label });
511
+ }
462
512
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
463
513
  "button",
464
514
  {
465
515
  type: "button",
466
516
  onClick,
467
- className: cn(
468
- "w-full flex items-center justify-between px-3 py-2.5",
469
- "text-body1-medium text-strong text-left cursor-pointer",
470
- "bg-white hover:bg-gray00 active:bg-gray01",
471
- className
472
- ),
517
+ className: cn(base, "cursor-pointer hover:bg-gray00 active:bg-gray01", className),
473
518
  children: [
474
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
475
- step,
476
- " | ",
477
- title
478
- ] }),
519
+ label,
479
520
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
480
521
  "img",
481
522
  {
@@ -493,22 +534,27 @@ var RadioToggleItem = ({
493
534
  content,
494
535
  badges,
495
536
  printColor,
537
+ color,
538
+ className,
496
539
  labelTextButton,
497
540
  onClickTextButton,
498
541
  open,
499
- onSelect,
500
- className
542
+ onSelect
501
543
  }) => {
502
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: cn("w-full", className), children: [
544
+ const rowBg = !open ? "bg-white hover:bg-gray00" : color ? void 0 : printColor ? printColorBgClass[printColor] : "bg-gray00";
545
+ const style = open && color ? { backgroundColor: color } : void 0;
546
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "w-full", children: [
503
547
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
504
548
  "button",
505
549
  {
506
550
  type: "button",
507
551
  onClick: onSelect,
552
+ style,
508
553
  className: cn(
509
554
  "w-full flex items-center gap-1.5 px-3 py-2.5",
510
555
  "text-body2-regular text-primary text-left cursor-pointer",
511
- open ? printColor ? printColorBgClass[printColor] : "bg-gray00" : "bg-white hover:bg-gray00"
556
+ rowBg,
557
+ className
512
558
  ),
513
559
  children: [
514
560
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Radio, { selected: open }),
@@ -596,7 +642,7 @@ var Tag = ({
596
642
  "box-border border border-gray02",
597
643
  "bg-white",
598
644
  // bgColor && `bg-[${bgColor}] border-none`,
599
- onClick && "cursor-pointer hover:bg-gray00 active:bg-gray01 cursor-pointer",
645
+ onClick && "cursor-pointer hover:bg-gray00 active:bg-gray01 cursor-pointer has-clickable",
600
646
  selected && "text-white bg-black border-black hover:bg-black active:bg-black",
601
647
  "disabled:text-gray04 disabled:bg-gray02 disabled:border-gray02 disabled:cursor-not-allowed",
602
648
  className
@@ -610,6 +656,12 @@ var Tag = ({
610
656
  );
611
657
  };
612
658
 
659
+ // src/components/Timetable/Timetable.tsx
660
+ var import_react4 = require("react");
661
+
662
+ // src/components/Timetable/WeekNavigation.tsx
663
+ var import_react3 = require("react");
664
+
613
665
  // src/components/Timetable/dateUtils.ts
614
666
  var KOREAN_WEEKDAYS = ["\uC77C", "\uC6D4", "\uD654", "\uC218", "\uBAA9", "\uAE08", "\uD1A0"];
615
667
  var parseISODate = (date) => {
@@ -647,38 +699,31 @@ var weekLabel = {
647
699
  nextWeek: "\uB2E4\uC74C \uC8FC",
648
700
  twoWeeksLater: "\uB2E4\uB2E4\uC74C \uC8FC"
649
701
  };
650
- var WeekNavigation = ({
651
- date,
652
- week,
653
- onPrevWeek,
654
- onNextWeek,
655
- className
656
- }) => {
657
- const showPrev = week !== "thisWeek";
658
- const showNext = week !== "twoWeeksLater";
659
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
660
- "div",
661
- {
662
- className: cn(
663
- "flex items-center justify-between",
664
- "w-full h-11 px-3",
665
- "bg-white border border-gray02",
666
- "text-body2-regular text-primary",
667
- className
668
- ),
669
- children: [
670
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ArrowButton, { direction: "left", visible: showPrev, onClick: onPrevWeek }),
671
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { children: [
672
- weekLabel[week],
673
- " (",
674
- formatWeekRange(date),
675
- ")"
676
- ] }),
677
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ArrowButton, { direction: "right", visible: showNext, onClick: onNextWeek })
678
- ]
679
- }
680
- );
681
- };
702
+ var WeekNavigation = (0, import_react3.forwardRef)(
703
+ function WeekNavigation2({ date, week, label, canPrev, canNext, onPrevWeek, onNextWeek, className }, ref) {
704
+ const showPrev = canPrev ?? (week ? week !== "thisWeek" : true);
705
+ const showNext = canNext ?? (week ? week !== "twoWeeksLater" : true);
706
+ const text = label ?? (week ? `${weekLabel[week]} (${formatWeekRange(date)})` : formatWeekRange(date));
707
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
708
+ "div",
709
+ {
710
+ ref,
711
+ className: cn(
712
+ "flex items-center justify-between",
713
+ "w-full h-11 px-3",
714
+ "bg-white border border-gray02",
715
+ "text-body2-regular text-primary",
716
+ className
717
+ ),
718
+ children: [
719
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ArrowButton, { direction: "left", visible: showPrev, onClick: onPrevWeek }),
720
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: text }),
721
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ArrowButton, { direction: "right", visible: showNext, onClick: onNextWeek })
722
+ ]
723
+ }
724
+ );
725
+ }
726
+ );
682
727
  var ArrowButton = ({ direction, visible, onClick }) => {
683
728
  if (!visible) {
684
729
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-6 h-6" });
@@ -715,38 +760,45 @@ var printColorHoverClass = {
715
760
  "Seet Cutting": "hover:bg-print-seet-cutting",
716
761
  "Laser Cutting": "hover:bg-print-laser-cutting"
717
762
  };
718
- var Timetable = ({
763
+ var Timetable = (0, import_react4.forwardRef)(function Timetable2({
719
764
  weekStart,
720
765
  week,
721
766
  days,
722
767
  onPrevWeek,
723
768
  onNextWeek,
724
769
  onSelectSlot,
770
+ fluid = false,
771
+ hideWeekNavigation = false,
772
+ canPrevWeek,
773
+ canNextWeek,
725
774
  className
726
- }) => {
727
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: cn(className), children: [
728
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
775
+ }, ref) {
776
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { ref, className: cn(fluid && "w-full", className), children: [
777
+ !hideWeekNavigation && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
729
778
  WeekNavigation,
730
779
  {
731
780
  date: weekStart,
732
781
  week,
782
+ canPrev: canPrevWeek,
783
+ canNext: canNextWeek,
733
784
  onPrevWeek,
734
785
  onNextWeek
735
786
  }
736
787
  ),
737
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex", children: days.map((day) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
788
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn("flex", fluid && "w-full"), children: days.map((day) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
738
789
  DailyTimetable,
739
790
  {
740
791
  date: day.date,
741
792
  slots: day.slots,
793
+ fluid,
742
794
  onSelectTime: onSelectSlot ? (time) => onSelectSlot(day.date, time) : void 0
743
795
  },
744
796
  day.date
745
797
  )) })
746
798
  ] });
747
- };
748
- var DailyTimetable = ({ date, slots, onSelectTime, className }) => {
749
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: cn("flex flex-col", className), children: [
799
+ });
800
+ var DailyTimetable = ({ date, slots, fluid, onSelectTime, className }) => {
801
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: cn("flex flex-col", fluid ? "flex-1 min-w-0" : "w-18", className), children: [
750
802
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "h-13 flex items-center justify-center text-body3-medium text-secondary", children: formatDayLabel(date) }),
751
803
  slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
752
804
  TimeItem,
@@ -754,27 +806,41 @@ var DailyTimetable = ({ date, slots, onSelectTime, className }) => {
754
806
  state: slot.state,
755
807
  label: slot.time,
756
808
  printColor: slot.printColor,
809
+ color: slot.color,
810
+ fluid,
811
+ className: slot.className,
757
812
  onClick: onSelectTime ? () => onSelectTime(slot.time) : void 0
758
813
  },
759
814
  slot.time
760
815
  ))
761
816
  ] });
762
817
  };
763
- var TimeItem = ({ state, label, printColor, onClick, className }) => {
818
+ var TimeItem = ({
819
+ state,
820
+ label,
821
+ printColor,
822
+ color,
823
+ fluid,
824
+ onClick,
825
+ className
826
+ }) => {
764
827
  const clickable = (state === "default" || state === "selected") && !!onClick;
828
+ const style = state === "default" && color ? { "--tt-hover": color } : void 0;
765
829
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
766
830
  "div",
767
831
  {
768
832
  onClick: clickable ? onClick : void 0,
833
+ style,
769
834
  className: cn(
770
- "w-18 h-18 flex items-center justify-center",
835
+ "h-18 flex items-center justify-center",
836
+ fluid ? "w-full" : "w-18",
771
837
  "text-body1-medium text-center bg-white text-primary",
772
838
  state === "none" && "bg-gray00 text-transparent",
773
839
  state === "selected" && "bg-black text-gray02 hover:cursor-pointer",
774
840
  state === "disabled" && "bg-gray00 text-gray03",
775
841
  state === "default" && [
776
842
  "hover:cursor-pointer",
777
- printColor ? printColorHoverClass[printColor] : "hover:bg-gray00"
843
+ color ? "hover:bg-(--tt-hover)" : printColor ? printColorHoverClass[printColor] : "hover:bg-gray00"
778
844
  ],
779
845
  className
780
846
  ),
@@ -784,81 +850,177 @@ var TimeItem = ({ state, label, printColor, onClick, className }) => {
784
850
  };
785
851
 
786
852
  // src/components/TNB/TNB.tsx
787
- var import_react2 = require("react");
853
+ var import_react5 = require("react");
788
854
  var import_jsx_runtime14 = require("react/jsx-runtime");
789
- var TNB = ({
855
+ function useControllableState2(controlled, defaultValue) {
856
+ const isControlled = controlled !== void 0;
857
+ const [uncontrolled, setUncontrolled] = (0, import_react5.useState)(defaultValue);
858
+ const value = isControlled ? controlled : uncontrolled;
859
+ const setValue = (0, import_react5.useCallback)(
860
+ (next) => {
861
+ if (!isControlled) setUncontrolled(next);
862
+ },
863
+ [isControlled]
864
+ );
865
+ return [value, setValue, isControlled];
866
+ }
867
+ var TNB = (0, import_react5.forwardRef)(function TNB2({
790
868
  logo,
791
869
  upperPartItemList,
792
870
  upperPartIconList,
871
+ selected,
793
872
  defaultSelected,
873
+ onSelectedChange,
874
+ selectedLower,
875
+ defaultSelectedLower,
876
+ onSelectedLowerChange,
794
877
  onSelectItem,
878
+ linkComponent,
879
+ "aria-label": ariaLabel = "\uC8FC\uC694 \uBA54\uB274",
795
880
  className
796
- }) => {
797
- const [selected, setSelected] = (0, import_react2.useState)(defaultSelected ?? upperPartItemList[0]?.label);
798
- const [selectedLower, setSelectedLower] = (0, import_react2.useState)(
799
- upperPartItemList.find((item) => item.label === selected)?.lowerPartItemList[0]?.label
881
+ }, ref) {
882
+ const [upperValue, setUpperValue] = useControllableState2(
883
+ selected,
884
+ defaultSelected ?? null
885
+ );
886
+ const [lowerValueRaw, setLowerValue, lowerIsControlled] = useControllableState2(
887
+ selectedLower,
888
+ defaultSelectedLower ?? null
800
889
  );
890
+ const currentUpper = upperPartItemList.find((item) => item.label === upperValue);
891
+ const currentLowerItemList = currentUpper?.lowerPartItemList ?? [];
892
+ const lowerValue = lowerIsControlled ? lowerValueRaw ?? void 0 : currentLowerItemList.some((item) => item.label === lowerValueRaw) ? lowerValueRaw : currentLowerItemList[0]?.label ?? void 0;
801
893
  const handleSelectUpper = (label) => {
802
- setSelected(label);
803
- setSelectedLower(
804
- upperPartItemList.find((item) => item.label === label)?.lowerPartItemList[0]?.label
805
- );
894
+ setUpperValue(label);
895
+ if (!lowerIsControlled) {
896
+ const nextLower = upperPartItemList.find((item) => item.label === label)?.lowerPartItemList[0]?.label ?? null;
897
+ setLowerValue(nextLower);
898
+ }
899
+ onSelectedChange?.(label);
806
900
  onSelectItem?.(label);
807
901
  };
808
- const currentLowerItemList = upperPartItemList.find((item) => item.label === selected)?.lowerPartItemList ?? [];
809
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("nav", { className: cn("w-full bg-white", className), children: [
902
+ const handleSelectLower = (label) => {
903
+ setLowerValue(label);
904
+ onSelectedLowerChange?.(label);
905
+ };
906
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("nav", { ref, "aria-label": ariaLabel, className: cn("w-full bg-white", className), children: [
810
907
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
811
908
  UpperPart,
812
909
  {
813
910
  logo,
814
911
  upperPartItemList,
815
912
  upperPartIconList,
816
- selected,
817
- onSelectItem: handleSelectUpper
913
+ selected: upperValue ?? void 0,
914
+ onSelectItem: handleSelectUpper,
915
+ linkComponent
818
916
  }
819
917
  ),
820
918
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
821
919
  LowerPart,
822
920
  {
823
921
  lowerPartItemList: currentLowerItemList,
824
- selected: selectedLower,
825
- onSelectItem: setSelectedLower
922
+ selected: lowerValue,
923
+ onSelectItem: handleSelectLower,
924
+ linkComponent
826
925
  }
827
926
  )
828
927
  ] });
829
- };
928
+ });
929
+ var NavClickable = (0, import_react5.forwardRef)(function NavClickable2({ href, onClick, linkComponent: Link, current, disabled, className, children }, ref) {
930
+ const ariaCurrent = current ? "page" : void 0;
931
+ if (href && !disabled) {
932
+ if (Link) {
933
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
934
+ Link,
935
+ {
936
+ href,
937
+ onClick,
938
+ "aria-current": ariaCurrent,
939
+ className,
940
+ ref,
941
+ children
942
+ }
943
+ );
944
+ }
945
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
946
+ "a",
947
+ {
948
+ href,
949
+ onClick,
950
+ "aria-current": ariaCurrent,
951
+ className,
952
+ ref,
953
+ children
954
+ }
955
+ );
956
+ }
957
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
958
+ "button",
959
+ {
960
+ type: "button",
961
+ onClick,
962
+ disabled,
963
+ "aria-current": ariaCurrent,
964
+ className,
965
+ ref,
966
+ children
967
+ }
968
+ );
969
+ });
830
970
  var UpperPart = ({
831
971
  logo,
832
972
  upperPartItemList,
833
973
  upperPartIconList,
834
974
  selected,
835
975
  onSelectItem,
976
+ linkComponent,
836
977
  className
837
978
  }) => {
838
979
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: cn("w-full h-13 px-4 flex items-center justify-between", className), children: [
839
980
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-6", children: [
840
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(NavIconButton, { ...logo }),
981
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(NavIconButton, { ...logo, linkComponent }),
841
982
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex items-center gap-4", children: upperPartItemList.map((item) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
842
983
  UpperPartTab,
843
984
  {
844
985
  label: item.label,
986
+ href: item.href,
845
987
  selected: item.label === selected,
846
- onClick: () => onSelectItem(item.label)
988
+ linkComponent,
989
+ onClick: () => {
990
+ item.onClick?.();
991
+ onSelectItem(item.label);
992
+ }
847
993
  },
848
- item.label
994
+ item.id ?? item.label
849
995
  )) })
850
996
  ] }),
851
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex items-center gap-2", children: upperPartIconList.map((icon) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(NavIconButton, { ...icon }, icon.src)) })
997
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex items-center gap-2", children: upperPartIconList.map((icon, index) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
998
+ NavIconButton,
999
+ {
1000
+ ...icon,
1001
+ linkComponent
1002
+ },
1003
+ icon.id ?? icon.src ?? icon.label ?? index
1004
+ )) })
852
1005
  ] });
853
1006
  };
854
- var UpperPartTab = ({ label, selected, onClick, className }) => {
1007
+ var UpperPartTab = ({
1008
+ label,
1009
+ href,
1010
+ selected,
1011
+ onClick,
1012
+ linkComponent,
1013
+ className
1014
+ }) => {
855
1015
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
856
- "button",
1016
+ NavClickable,
857
1017
  {
858
- type: "button",
1018
+ href,
859
1019
  onClick,
1020
+ linkComponent,
1021
+ current: selected,
860
1022
  className: cn(
861
- "px-1 py-1.5 text-body2-regular cursor-pointer",
1023
+ "inline-flex px-1 py-1.5 text-body2-regular cursor-pointer no-underline",
862
1024
  selected ? "text-strong font-medium underline underline-offset-4" : "text-quaternary hover:text-secondary",
863
1025
  className
864
1026
  ),
@@ -866,24 +1028,51 @@ var UpperPartTab = ({ label, selected, onClick, className }) => {
866
1028
  }
867
1029
  );
868
1030
  };
869
- var NavIconButton = ({ src, alt, onClick, selected, className }) => {
870
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
871
- "button",
872
- {
873
- type: "button",
874
- onClick,
875
- disabled: !onClick,
876
- className: cn(
877
- "w-8 h-8 flex items-center justify-center rounded-full",
878
- onClick && "cursor-pointer",
879
- selected ? "bg-black" : "bg-transparent hover:bg-gray00 active:bg-gray01",
880
- className
881
- ),
882
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("img", { src, alt: alt ?? "", className: "w-5 h-5" })
883
- }
884
- );
885
- };
886
- var LowerPart = ({ lowerPartItemList, selected, onSelectItem, className }) => {
1031
+ var NavIconButton = (0, import_react5.forwardRef)(
1032
+ function NavIconButton2({ src, icon, alt, label, href, onClick, selected, badge, className, linkComponent }, forwardedRef) {
1033
+ const interactive = Boolean(onClick || href);
1034
+ const showBadge = badge !== void 0 && badge !== null && badge !== false;
1035
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1036
+ NavClickable,
1037
+ {
1038
+ ref: forwardedRef,
1039
+ href,
1040
+ onClick,
1041
+ linkComponent,
1042
+ current: selected,
1043
+ disabled: !interactive,
1044
+ className: cn(
1045
+ "relative inline-flex h-8 min-w-8 items-center justify-center gap-1.5 rounded-full px-1.5",
1046
+ interactive && "cursor-pointer",
1047
+ selected ? "bg-black text-white" : "bg-transparent hover:bg-gray00 active:bg-gray01",
1048
+ className
1049
+ ),
1050
+ children: [
1051
+ icon ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "inline-flex h-5 w-5 items-center justify-center", children: icon }) : src ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("img", { src, alt: alt ?? label ?? "", className: "h-5 w-5" }) : null,
1052
+ label ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: cn("text-body2-regular", selected ? "text-white" : "text-secondary"), children: label }) : null,
1053
+ showBadge ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1054
+ "span",
1055
+ {
1056
+ "aria-hidden": badge === true,
1057
+ className: cn(
1058
+ "absolute -right-0.5 -top-0.5 flex items-center justify-center rounded-full bg-danger text-white",
1059
+ badge === true ? "h-2 w-2" : "h-4 min-w-4 px-1 text-caption-regular leading-none"
1060
+ ),
1061
+ children: badge === true ? null : badge
1062
+ }
1063
+ ) : null
1064
+ ]
1065
+ }
1066
+ );
1067
+ }
1068
+ );
1069
+ var LowerPart = ({
1070
+ lowerPartItemList,
1071
+ selected,
1072
+ onSelectItem,
1073
+ linkComponent,
1074
+ className
1075
+ }) => {
887
1076
  if (lowerPartItemList.length === 0) {
888
1077
  return null;
889
1078
  }
@@ -895,25 +1084,36 @@ var LowerPart = ({ lowerPartItemList, selected, onSelectItem, className }) => {
895
1084
  LowerPartItemButton,
896
1085
  {
897
1086
  label: item.label,
1087
+ href: item.href,
898
1088
  selected: item.label === selected,
1089
+ linkComponent,
899
1090
  onClick: () => {
900
1091
  item.onClick?.();
901
1092
  onSelectItem(item.label);
902
1093
  }
903
1094
  },
904
- item.label
1095
+ item.id ?? item.label
905
1096
  ))
906
1097
  }
907
1098
  );
908
1099
  };
909
- var LowerPartItemButton = ({ label, selected, onClick, className }) => {
1100
+ var LowerPartItemButton = ({
1101
+ label,
1102
+ href,
1103
+ selected,
1104
+ onClick,
1105
+ linkComponent,
1106
+ className
1107
+ }) => {
910
1108
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
911
- "button",
1109
+ NavClickable,
912
1110
  {
913
- type: "button",
1111
+ href,
914
1112
  onClick,
1113
+ linkComponent,
1114
+ current: selected,
915
1115
  className: cn(
916
- "px-1.5 py-1 text-caption-regular cursor-pointer",
1116
+ "inline-flex px-1.5 py-1 text-caption-regular cursor-pointer no-underline",
917
1117
  "hover:bg-gray00 active:bg-gray01",
918
1118
  selected ? "text-strong font-medium" : "text-quaternary",
919
1119
  className
@@ -924,21 +1124,59 @@ var LowerPartItemButton = ({ label, selected, onClick, className }) => {
924
1124
  };
925
1125
 
926
1126
  // src/components/Toast/Toast.tsx
1127
+ var import_react6 = require("react");
927
1128
  var import_jsx_runtime15 = require("react/jsx-runtime");
928
- var Toast = ({ message, className }) => {
929
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1129
+ var EXIT_MS = 300;
1130
+ var Toast = (0, import_react6.forwardRef)(function Toast2({ message, open, duration = 3e3, onClose, position = "none", className }, ref) {
1131
+ const controlled = open !== void 0;
1132
+ const [mounted, setMounted] = (0, import_react6.useState)(!controlled || open);
1133
+ const [visible, setVisible] = (0, import_react6.useState)(!controlled || open);
1134
+ (0, import_react6.useEffect)(() => {
1135
+ if (!controlled) return;
1136
+ if (open) {
1137
+ setMounted(true);
1138
+ const raf = requestAnimationFrame(() => setVisible(true));
1139
+ return () => cancelAnimationFrame(raf);
1140
+ }
1141
+ setVisible(false);
1142
+ const timer = setTimeout(() => setMounted(false), EXIT_MS);
1143
+ return () => clearTimeout(timer);
1144
+ }, [controlled, open]);
1145
+ (0, import_react6.useEffect)(() => {
1146
+ if (!controlled || !open || !duration) return;
1147
+ const timer = setTimeout(() => onClose?.(), duration);
1148
+ return () => clearTimeout(timer);
1149
+ }, [controlled, open, duration, onClose]);
1150
+ if (!mounted) return null;
1151
+ const banner = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
930
1152
  "div",
931
1153
  {
1154
+ ref,
1155
+ role: "status",
1156
+ "aria-live": "polite",
932
1157
  className: cn(
933
- `bg-gray06 w-dvw h-fit py-2.5`,
934
- `font-pretendard text-white text-center font-semibold text-[17px]`,
935
- `shadow-[0_2px_15px_rgba(0,0,0,0.18)]`,
1158
+ "w-full h-fit py-2.5 bg-gray06",
1159
+ "font-pretendard text-white text-center font-semibold text-[17px]",
1160
+ "shadow-[0_2px_15px_rgba(0,0,0,0.18)]",
1161
+ controlled && "transition-opacity duration-300",
1162
+ controlled && (visible ? "opacity-100" : "opacity-0"),
936
1163
  className
937
1164
  ),
938
1165
  children: message
939
1166
  }
940
1167
  );
941
- };
1168
+ if (position === "none") return banner;
1169
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1170
+ "div",
1171
+ {
1172
+ className: cn(
1173
+ "fixed inset-x-0 z-5000 pointer-events-none",
1174
+ position === "top" ? "top-0" : "bottom-0"
1175
+ ),
1176
+ children: banner
1177
+ }
1178
+ );
1179
+ });
942
1180
  // Annotate the CommonJS export names for ESM import in node:
943
1181
  0 && (module.exports = {
944
1182
  BackButton,