sellmate-design-system-react 0.1.10 → 0.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.
Files changed (163) hide show
  1. package/dist/components/SActionModal/SActionModal.cjs +10 -4
  2. package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
  3. package/dist/components/SActionModal/SActionModal.js +10 -4
  4. package/dist/components/SActionModal/SActionModal.js.map +1 -1
  5. package/dist/components/SBarcodeInput/SBarcodeInput.cjs +22 -5
  6. package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
  7. package/dist/components/SBarcodeInput/SBarcodeInput.d.ts +3 -1
  8. package/dist/components/SBarcodeInput/SBarcodeInput.js +22 -5
  9. package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
  10. package/dist/components/SCalendar/SCalendar.cjs +9 -3
  11. package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
  12. package/dist/components/SCalendar/SCalendar.js +9 -3
  13. package/dist/components/SCalendar/SCalendar.js.map +1 -1
  14. package/dist/components/SChip/SChip.cjs +9 -3
  15. package/dist/components/SChip/SChip.cjs.map +1 -1
  16. package/dist/components/SChip/SChip.js +9 -3
  17. package/dist/components/SChip/SChip.js.map +1 -1
  18. package/dist/components/SChipInput/SChipInput.cjs +44 -13
  19. package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
  20. package/dist/components/SChipInput/SChipInput.d.ts +5 -0
  21. package/dist/components/SChipInput/SChipInput.js +44 -13
  22. package/dist/components/SChipInput/SChipInput.js.map +1 -1
  23. package/dist/components/SChipInput/index.d.ts +1 -1
  24. package/dist/components/SCircleProgress/SCircleProgress.cjs +6 -2
  25. package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
  26. package/dist/components/SCircleProgress/SCircleProgress.js +6 -2
  27. package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
  28. package/dist/components/SConfirmModal/SConfirmModal.cjs +20 -13
  29. package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
  30. package/dist/components/SConfirmModal/SConfirmModal.d.ts +7 -4
  31. package/dist/components/SConfirmModal/SConfirmModal.js +20 -13
  32. package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
  33. package/dist/components/SDatePicker/SDatePicker.cjs +22 -5
  34. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  35. package/dist/components/SDatePicker/SDatePicker.d.ts +3 -1
  36. package/dist/components/SDatePicker/SDatePicker.js +22 -5
  37. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  38. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +22 -5
  39. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  40. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +3 -1
  41. package/dist/components/SDateRangePicker/SDateRangePicker.js +22 -5
  42. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  43. package/dist/components/SDropdownButton/SDropdownButton.cjs +3 -1
  44. package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
  45. package/dist/components/SDropdownButton/SDropdownButton.js +3 -1
  46. package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
  47. package/dist/components/SField/SField.cjs +20 -5
  48. package/dist/components/SField/SField.cjs.map +1 -1
  49. package/dist/components/SField/SField.d.ts +4 -1
  50. package/dist/components/SField/SField.js +20 -5
  51. package/dist/components/SField/SField.js.map +1 -1
  52. package/dist/components/SFilePicker/SFilePicker.cjs +23 -6
  53. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  54. package/dist/components/SFilePicker/SFilePicker.d.ts +3 -1
  55. package/dist/components/SFilePicker/SFilePicker.js +23 -6
  56. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  57. package/dist/components/SGhostButton/SGhostButton.cjs +9 -3
  58. package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
  59. package/dist/components/SGhostButton/SGhostButton.d.ts +5 -1
  60. package/dist/components/SGhostButton/SGhostButton.js +9 -3
  61. package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
  62. package/dist/components/SGnb/SGnb.cjs +3297 -0
  63. package/dist/components/SGnb/SGnb.cjs.map +1 -0
  64. package/dist/components/SGnb/SGnb.d.ts +35 -0
  65. package/dist/components/SGnb/SGnb.js +3295 -0
  66. package/dist/components/SGnb/SGnb.js.map +1 -0
  67. package/dist/components/SGnb/gnb.config.d.ts +63 -0
  68. package/dist/components/SGnb/index.d.ts +2 -0
  69. package/dist/components/SGuide/SGuide.cjs +9 -3
  70. package/dist/components/SGuide/SGuide.cjs.map +1 -1
  71. package/dist/components/SGuide/SGuide.js +9 -3
  72. package/dist/components/SGuide/SGuide.js.map +1 -1
  73. package/dist/components/SInput/SInput.cjs +22 -5
  74. package/dist/components/SInput/SInput.cjs.map +1 -1
  75. package/dist/components/SInput/SInput.d.ts +4 -1
  76. package/dist/components/SInput/SInput.js +22 -5
  77. package/dist/components/SInput/SInput.js.map +1 -1
  78. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +511 -209
  79. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  80. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +27 -6
  81. package/dist/components/SKeyValueTable/SKeyValueTable.js +510 -208
  82. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  83. package/dist/components/SLayout/SLayout.cjs +79 -0
  84. package/dist/components/SLayout/SLayout.cjs.map +1 -0
  85. package/dist/components/SLayout/SLayout.d.ts +41 -0
  86. package/dist/components/SLayout/SLayout.js +75 -0
  87. package/dist/components/SLayout/SLayout.js.map +1 -0
  88. package/dist/components/SLayout/index.d.ts +1 -0
  89. package/dist/components/SLoadingContainer/SLoadingContainer.cjs +6 -2
  90. package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
  91. package/dist/components/SLoadingContainer/SLoadingContainer.js +6 -2
  92. package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
  93. package/dist/components/SLoadingContainer/loading.cjs +6 -2
  94. package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
  95. package/dist/components/SLoadingContainer/loading.js +6 -2
  96. package/dist/components/SLoadingContainer/loading.js.map +1 -1
  97. package/dist/components/SLoadingModal/SLoadingModal.cjs +16 -6
  98. package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
  99. package/dist/components/SLoadingModal/SLoadingModal.js +16 -6
  100. package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
  101. package/dist/components/SModalContainer/SModalContainer.cjs +10 -4
  102. package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
  103. package/dist/components/SModalContainer/SModalContainer.d.ts +10 -0
  104. package/dist/components/SModalContainer/SModalContainer.js +10 -4
  105. package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
  106. package/dist/components/SNumberInput/SNumberInput.cjs +22 -5
  107. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  108. package/dist/components/SNumberInput/SNumberInput.d.ts +2 -0
  109. package/dist/components/SNumberInput/SNumberInput.js +22 -5
  110. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  111. package/dist/components/SPage/SPage.cjs +86 -0
  112. package/dist/components/SPage/SPage.cjs.map +1 -0
  113. package/dist/components/SPage/SPage.d.ts +13 -0
  114. package/dist/components/SPage/SPage.js +84 -0
  115. package/dist/components/SPage/SPage.js.map +1 -0
  116. package/dist/components/SPage/index.d.ts +1 -0
  117. package/dist/components/SPopover/SPopover.cjs +9 -3
  118. package/dist/components/SPopover/SPopover.cjs.map +1 -1
  119. package/dist/components/SPopover/SPopover.js +9 -3
  120. package/dist/components/SPopover/SPopover.js.map +1 -1
  121. package/dist/components/SPortal/SPortal.cjs +3 -1
  122. package/dist/components/SPortal/SPortal.cjs.map +1 -1
  123. package/dist/components/SPortal/SPortal.js +3 -1
  124. package/dist/components/SPortal/SPortal.js.map +1 -1
  125. package/dist/components/SSelect/SSelect.cjs +545 -265
  126. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  127. package/dist/components/SSelect/SSelect.d.ts +4 -2
  128. package/dist/components/SSelect/SSelect.js +546 -265
  129. package/dist/components/SSelect/SSelect.js.map +1 -1
  130. package/dist/components/STable/STable.cjs +552 -268
  131. package/dist/components/STable/STable.cjs.map +1 -1
  132. package/dist/components/STable/STable.js +553 -268
  133. package/dist/components/STable/STable.js.map +1 -1
  134. package/dist/components/STextLink/STextLink.cjs +2 -0
  135. package/dist/components/STextLink/STextLink.cjs.map +1 -1
  136. package/dist/components/STextLink/STextLink.js +2 -0
  137. package/dist/components/STextLink/STextLink.js.map +1 -1
  138. package/dist/components/STextarea/STextarea.cjs +22 -5
  139. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  140. package/dist/components/STextarea/STextarea.d.ts +2 -0
  141. package/dist/components/STextarea/STextarea.js +22 -5
  142. package/dist/components/STextarea/STextarea.js.map +1 -1
  143. package/dist/components/SToast/SToast.cjs +9 -3
  144. package/dist/components/SToast/SToast.cjs.map +1 -1
  145. package/dist/components/SToast/SToast.js +9 -3
  146. package/dist/components/SToast/SToast.js.map +1 -1
  147. package/dist/components/SToast/SToastContainer.cjs +9 -3
  148. package/dist/components/SToast/SToastContainer.cjs.map +1 -1
  149. package/dist/components/SToast/SToastContainer.js +9 -3
  150. package/dist/components/SToast/SToastContainer.js.map +1 -1
  151. package/dist/components/STooltip/STooltip.cjs +9 -3
  152. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  153. package/dist/components/STooltip/STooltip.js +9 -3
  154. package/dist/components/STooltip/STooltip.js.map +1 -1
  155. package/dist/index.cjs +840 -199
  156. package/dist/index.cjs.map +1 -1
  157. package/dist/index.d.ts +3 -1
  158. package/dist/index.js +832 -200
  159. package/dist/index.js.map +1 -1
  160. package/dist/lib/portal-container.d.ts +7 -0
  161. package/dist/styles.css +253 -63
  162. package/dist/theme.css +66 -13
  163. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -3347,8 +3347,12 @@ var SCircleProgress = react.forwardRef(
3347
3347
  "span",
3348
3348
  {
3349
3349
  className: cn(
3350
- "text-center text-[16px] font-bold leading-[26px]",
3351
- innerValue && "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
3350
+ "text-center text-[16px] font-bold",
3351
+ innerValue ? (
3352
+ // leading-[normal]: 26px leading 은 폰트 content(19px) 위아래로 홀수(7px)만큼 남아
3353
+ // 3px/4px 로 쪼개지고 글자가 링 중심보다 위로 뜬다. (leading-normal 은 1.5 라 동일 문제)
3354
+ "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 leading-[normal]"
3355
+ ) : "leading-[26px]"
3352
3356
  ),
3353
3357
  style: { color: percentColor },
3354
3358
  children: [
@@ -3533,10 +3537,15 @@ var FLOATING_SLIDE_ANIM = [
3533
3537
  "data-[state=closed]:data-[side=right]:animate-floating-out-right"
3534
3538
  ].join(" ");
3535
3539
  var PortalContainerContext = react.createContext(null);
3540
+ var InsideModalContext = react.createContext(false);
3536
3541
  var PortalContainerProvider = PortalContainerContext.Provider;
3542
+ var InsideModalProvider = InsideModalContext.Provider;
3537
3543
  function usePortalContainer() {
3538
3544
  return react.useContext(PortalContainerContext) ?? void 0;
3539
3545
  }
3546
+ function useInsideModal() {
3547
+ return react.useContext(InsideModalContext);
3548
+ }
3540
3549
  var SIZE_CONFIG = {
3541
3550
  sm: {
3542
3551
  gap: 4,
@@ -3909,7 +3918,7 @@ var SDropdownButton = react.forwardRef(
3909
3918
  ...style
3910
3919
  },
3911
3920
  children: [
3912
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: label }),
3921
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate text-center", children: label }),
3913
3922
  caret
3914
3923
  ]
3915
3924
  }
@@ -3937,6 +3946,8 @@ function SGhostButton({
3937
3946
  tooltipText,
3938
3947
  disabled = false,
3939
3948
  onClick,
3949
+ anchorClassName,
3950
+ anchorStyle,
3940
3951
  className,
3941
3952
  style
3942
3953
  }) {
@@ -3959,6 +3970,8 @@ function SGhostButton({
3959
3970
  };
3960
3971
  const s = SIZE2[size];
3961
3972
  const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
3973
+ const text = (tooltipText ?? "").trim();
3974
+ const hasTooltip = text !== "";
3962
3975
  const btn = /* @__PURE__ */ jsxRuntime.jsxs(
3963
3976
  "button",
3964
3977
  {
@@ -3999,13 +4012,13 @@ function SGhostButton({
3999
4012
  ]
4000
4013
  }
4001
4014
  );
4002
- const text = (tooltipText ?? "").trim();
4003
- if (text === "") return btn;
4015
+ if (!hasTooltip) return btn;
4004
4016
  return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
4005
4017
  /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4006
4018
  "span",
4007
4019
  {
4008
- className: "inline-flex",
4020
+ className: cn("inline-flex", anchorClassName),
4021
+ style: anchorStyle,
4009
4022
  onMouseEnter: openNow,
4010
4023
  onMouseLeave: startHide,
4011
4024
  onFocus: openNow,
@@ -4406,7 +4419,7 @@ function SPortal({
4406
4419
  onCloseAutoFocus,
4407
4420
  onClick: autoClose ? () => onClose?.() : void 0,
4408
4421
  className: cn(
4409
- "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm",
4422
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm",
4410
4423
  maxHeight != null && "overflow-y-auto",
4411
4424
  fit && "min-w-(--radix-popover-trigger-width)",
4412
4425
  FLOATING_SLIDE_ANIM,
@@ -4647,7 +4660,7 @@ function SModalContainer({
4647
4660
  className: "absolute right-[16px] top-[16px] z-10"
4648
4661
  }
4649
4662
  ),
4650
- /* @__PURE__ */ jsxRuntime.jsx(PortalContainerProvider, { value: portalContainer, children })
4663
+ /* @__PURE__ */ jsxRuntime.jsx(InsideModalProvider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(PortalContainerProvider, { value: portalContainer, children }) })
4651
4664
  ]
4652
4665
  }
4653
4666
  )
@@ -4693,7 +4706,8 @@ function SConfirmModal({
4693
4706
  tagSize = "sm",
4694
4707
  tagColor = "grey",
4695
4708
  slotLabel,
4696
- children,
4709
+ tagSlot,
4710
+ optionSlot,
4697
4711
  mainButtonLabel = "\uD655\uC778",
4698
4712
  mainButtonName,
4699
4713
  subButtonLabel,
@@ -4703,6 +4717,9 @@ function SConfirmModal({
4703
4717
  }) {
4704
4718
  const iconName = TYPE_ICON[type];
4705
4719
  const mainStyle = MAIN_BUTTON_STYLE[mainButtonName ?? DEFAULT_MAIN_BUTTON[type]];
4720
+ const tagNode = tagSlot ?? (tagLabel ? /* @__PURE__ */ jsxRuntime.jsx(STag, { shape: tagShape, size: tagSize, color: tagColor, label: tagLabel }) : null);
4721
+ const optionNode = optionSlot ?? (slotLabel ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: slotLabel }) : null);
4722
+ const hasBody = topMessage.length > 0 || bottomMessage.length > 0 || tagNode != null || optionNode != null;
4706
4723
  return /* @__PURE__ */ jsxRuntime.jsx(
4707
4724
  SModalContainer,
4708
4725
  {
@@ -4728,15 +4745,12 @@ function SConfirmModal({
4728
4745
  }
4729
4746
  )
4730
4747
  ] }),
4731
- (topMessage.length > 0 || bottomMessage.length > 0 || tagLabel || slotLabel || children) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-[20px] text-center text-[12px] font-normal leading-[20px] text-[color:var(--cmp-modal-confirm-message-color)]", children: [
4748
+ hasBody && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-[20px] text-center text-[12px] font-normal leading-[20px] text-[color:var(--cmp-modal-confirm-message-color)] w-full", children: [
4732
4749
  topMessage.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-[2px]", children: topMessage.map((m, i) => /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0", children: m }, i)) }),
4733
- (tagLabel || slotLabel || children) && // content-box: 흰 박스 + 1px 테두리 + 패딩 + 14/700 (원본 sd-confirm-modal__content-box)
4734
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-[8px] rounded-[8px] border border-solid border-[color:var(--color-grey-30)] bg-white px-[16px] py-[12px] text-[14px] font-bold leading-[22px]", children: [
4735
- tagLabel && /* @__PURE__ */ jsxRuntime.jsx(STag, { shape: tagShape, size: tagSize, color: tagColor, label: tagLabel }),
4736
- slotLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { children: slotLabel }),
4737
- children
4738
- ] }),
4739
- bottomMessage.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-[2px]", children: bottomMessage.map((m, i) => /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0", children: m }, i)) })
4750
+ tagNode != null && // tag 위치 content-box: 흰 박스 + 1px 테두리 + 패딩 + 14/700 (원본 sd-confirm-modal__content-box)
4751
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center gap-[8px] rounded-[8px] border border-solid border-[color:var(--color-grey-30)] bg-white px-[16px] py-[12px] text-[14px] font-bold leading-[22px] w-full", children: tagNode }),
4752
+ bottomMessage.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-[2px]", children: bottomMessage.map((m, i) => /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0", children: m }, i)) }),
4753
+ optionNode != null && optionNode
4740
4754
  ] })
4741
4755
  ] }),
4742
4756
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-[8px]", children: [
@@ -4887,6 +4901,384 @@ function SLoadingModal({
4887
4901
  );
4888
4902
  }
4889
4903
 
4904
+ // src/components/SGnb/gnb.config.ts
4905
+ var belt = (p) => `var(--cmp-gnbBelt-${p})`;
4906
+ var box = (p) => `var(--cmp-gnbBox-${p})`;
4907
+ var light = (p) => `var(--cmp-light-${p})`;
4908
+ var dark = (p) => `var(--cmp-dark-${p})`;
4909
+ var GNB_LAYOUT = {
4910
+ // 사용자 명명: type="box" → gnbBox 토큰(둥금), type="belt" → gnbBelt 토큰(각짐)
4911
+ box: {
4912
+ topPaddingX: box("paddingX"),
4913
+ topGap: box("gap"),
4914
+ // 상단바 gap (버튼 간 세부 gap 은 --cmp-gnbBox-button-gap)
4915
+ bodyPadding: box("menu-body-padding"),
4916
+ itemPaddingRight: box("item-paddingRight"),
4917
+ itemGap: box("item-gap"),
4918
+ itemRadius: box("item-radius"),
4919
+ itemArrow: box("item-arrow"),
4920
+ depth1PaddingLeft: box("item-depth1-paddingLeft"),
4921
+ depth2PaddingLeft: box("item-depth2-paddingLeft"),
4922
+ depth3PaddingLeft: box("item-depth3-paddingLeft"),
4923
+ depth1Icon: box("item-depth1-icon"),
4924
+ depth1FontSize: 14,
4925
+ // token: gnbBox.item.typography.bold.fontSize
4926
+ depth1LineHeight: 24,
4927
+ // token: gnbBox.item.typography.bold.lineHeight
4928
+ depth1FontWeight: 700,
4929
+ itemFontSize: 14,
4930
+ // token: gnbBox.item.typography.default.fontSize
4931
+ itemLineHeight: 24
4932
+ // token: gnbBox.item.typography.default.lineHeight
4933
+ },
4934
+ belt: {
4935
+ topPaddingX: belt("top-paddingX"),
4936
+ topGap: belt("top-gap"),
4937
+ bodyPadding: belt("body-paddingY"),
4938
+ itemPaddingRight: belt("item-paddingRight"),
4939
+ itemGap: belt("item-gap"),
4940
+ itemRadius: "0px",
4941
+ // belt 토큰에 radius 없음 → 풀폭 행
4942
+ itemArrow: belt("item-icon"),
4943
+ depth1PaddingLeft: belt("item-depth1-paddingLeft"),
4944
+ depth2PaddingLeft: belt("item-depth2-paddingLeft"),
4945
+ depth3PaddingLeft: belt("item-depth3-paddingLeft"),
4946
+ depth1Icon: box("item-depth1-icon"),
4947
+ // belt 토큰에 depth1-icon 없음 → box 값(20) 공유
4948
+ depth1FontSize: 13,
4949
+ // token: gnbBelt.item.depth1.typography.fontSize
4950
+ depth1LineHeight: 22,
4951
+ // token: gnbBelt.item.depth1.typography.lineHeight
4952
+ depth1FontWeight: 700,
4953
+ itemFontSize: 12,
4954
+ // token: gnbBelt.item.typography.default.fontSize
4955
+ itemLineHeight: 20
4956
+ // token: gnbBelt.item.typography.default.lineHeight
4957
+ }
4958
+ };
4959
+ var GNB_COLORS = {
4960
+ light: {
4961
+ panelBg: light("bg"),
4962
+ topBorder: light("divider"),
4963
+ itemBgDefault: light("item-bg-default"),
4964
+ itemBgHover: light("item-bg-hover"),
4965
+ itemBgSelected: light("item-bg-selected"),
4966
+ itemColorDefault: light("item-fg-default"),
4967
+ itemColorActive: light("item-fg-selected")
4968
+ },
4969
+ dark: {
4970
+ panelBg: dark("bg"),
4971
+ topBorder: dark("divider"),
4972
+ itemBgDefault: dark("item-bg-default"),
4973
+ itemBgHover: dark("item-bg-hover"),
4974
+ itemBgSelected: dark("item-bg-selected"),
4975
+ itemColorDefault: dark("item-fg-default"),
4976
+ itemColorActive: dark("item-fg-selected")
4977
+ }
4978
+ };
4979
+ var GNB_COMMON = {
4980
+ topHeight: box("top-height"),
4981
+ // 56 (box=belt 동일)
4982
+ logoHeight: box("logo-height"),
4983
+ // 32
4984
+ itemPaddingY: box("item-paddingY"),
4985
+ // 6
4986
+ foldWidth: belt("fold-width")
4987
+ // 48 (belt 토큰 공유)
4988
+ };
4989
+ var GNB_MENU_WIDTH = 240;
4990
+ function findGnbAncestors(items, value) {
4991
+ const walk = (list, trail) => {
4992
+ for (const item of list) {
4993
+ if (item.value === value) return trail;
4994
+ if (item.children) {
4995
+ const found = walk(item.children, [...trail, item.value]);
4996
+ if (found) return found;
4997
+ }
4998
+ }
4999
+ return null;
5000
+ };
5001
+ return walk(items, []) ?? [];
5002
+ }
5003
+ var SLayoutContext = react.createContext(null);
5004
+ function useSLayout() {
5005
+ const ctx = react.useContext(SLayoutContext);
5006
+ if (!ctx) throw new Error("useSLayout \uC740 <SLayout> \uC548\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
5007
+ return ctx;
5008
+ }
5009
+ var SLayout = react.forwardRef(function SLayout2({
5010
+ type = "box",
5011
+ folded,
5012
+ defaultFolded = false,
5013
+ onFoldedChange,
5014
+ className,
5015
+ style,
5016
+ children,
5017
+ ...rest
5018
+ }, ref) {
5019
+ const [uncontrolledFolded, setUncontrolledFolded] = react.useState(defaultFolded);
5020
+ const isControlled = folded !== void 0;
5021
+ const currentFolded = isControlled ? folded : uncontrolledFolded;
5022
+ const setFolded = react.useCallback(
5023
+ (next) => {
5024
+ if (!isControlled) setUncontrolledFolded(next);
5025
+ onFoldedChange?.(next);
5026
+ },
5027
+ [isControlled, onFoldedChange]
5028
+ );
5029
+ const ctx = react.useMemo(
5030
+ () => ({ type, folded: currentFolded, setFolded }),
5031
+ [type, currentFolded, setFolded]
5032
+ );
5033
+ const isBox = type === "box";
5034
+ const frameStyle = isBox ? {
5035
+ gridTemplateColumns: `${currentFolded ? 0 : GNB_MENU_WIDTH}px 1fr`,
5036
+ gridTemplateRows: `${GNB_COMMON.topHeight} 1fr`
5037
+ } : void 0;
5038
+ return /* @__PURE__ */ jsxRuntime.jsx(SLayoutContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx(
5039
+ "div",
5040
+ {
5041
+ ref,
5042
+ "data-type": type,
5043
+ className: cn(
5044
+ "box-border h-full overflow-hidden",
5045
+ isBox ? "grid" : "flex flex-row",
5046
+ className
5047
+ ),
5048
+ style: { ...frameStyle, ...style },
5049
+ ...rest,
5050
+ children
5051
+ }
5052
+ ) });
5053
+ });
5054
+ var DEPTH_PADDING_LEFT = (L, depth) => depth === 1 ? L.depth1PaddingLeft : depth === 2 ? L.depth2PaddingLeft : L.depth3PaddingLeft;
5055
+ var SGnb = react.forwardRef(function SGnb2({
5056
+ type: typeProp,
5057
+ color = "light",
5058
+ items,
5059
+ value = "",
5060
+ onValueChange,
5061
+ folded: foldedProp,
5062
+ onFoldChange,
5063
+ onLauncherClick,
5064
+ logo,
5065
+ search,
5066
+ className,
5067
+ style
5068
+ }, ref) {
5069
+ const layout = react.useContext(SLayoutContext);
5070
+ const type = typeProp ?? layout?.type ?? "box";
5071
+ const folded = foldedProp ?? layout?.folded ?? false;
5072
+ const L = GNB_LAYOUT[type];
5073
+ const C = GNB_COLORS[color];
5074
+ const [expandedKeys, setExpandedKeys] = react.useState(
5075
+ () => new Set(findGnbAncestors(items, value))
5076
+ );
5077
+ const [hoveredKey, setHoveredKey] = react.useState(null);
5078
+ react.useEffect(() => {
5079
+ const ancestors = findGnbAncestors(items, value);
5080
+ if (ancestors.length === 0) return;
5081
+ setExpandedKeys((prev) => {
5082
+ const next = new Set(prev);
5083
+ ancestors.forEach((a) => next.add(a));
5084
+ return next;
5085
+ });
5086
+ }, [items, value]);
5087
+ const toggleExpand = (key) => {
5088
+ setExpandedKeys((prev) => {
5089
+ const next = new Set(prev);
5090
+ if (next.has(key)) next.delete(key);
5091
+ else next.add(key);
5092
+ return next;
5093
+ });
5094
+ };
5095
+ const handleItemClick = (item) => {
5096
+ if (item.disabled) return;
5097
+ if (item.children && item.children.length > 0) {
5098
+ toggleExpand(item.value);
5099
+ return;
5100
+ }
5101
+ onValueChange?.(item.value);
5102
+ };
5103
+ const handleFoldToggle = () => {
5104
+ const next = !folded;
5105
+ onFoldChange?.(next);
5106
+ layout?.setFolded(next);
5107
+ };
5108
+ const iconBtnClass = "inline-flex shrink-0 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-1.5";
5109
+ const renderItem = (item, depth) => {
5110
+ const hasChildren = !!(item.children && item.children.length > 0);
5111
+ const isSelected = !hasChildren && item.value === value;
5112
+ const isExpanded = expandedKeys.has(item.value);
5113
+ const isHovered = hoveredKey === item.value;
5114
+ const bg = isSelected ? C.itemBgSelected : isHovered && !item.disabled ? C.itemBgHover : C.itemBgDefault;
5115
+ const itemStyle = {
5116
+ gap: L.itemGap,
5117
+ paddingTop: GNB_COMMON.itemPaddingY,
5118
+ paddingBottom: GNB_COMMON.itemPaddingY,
5119
+ paddingRight: L.itemPaddingRight,
5120
+ paddingLeft: DEPTH_PADDING_LEFT(L, depth),
5121
+ marginLeft: L.bodyPadding,
5122
+ marginRight: L.bodyPadding,
5123
+ borderRadius: L.itemRadius,
5124
+ backgroundColor: bg,
5125
+ color: isSelected ? C.itemColorActive : C.itemColorDefault,
5126
+ fontSize: depth === 1 ? L.depth1FontSize : L.itemFontSize,
5127
+ lineHeight: `${depth === 1 ? L.depth1LineHeight : L.itemLineHeight}px`,
5128
+ fontWeight: depth === 1 || isSelected ? L.depth1FontWeight : 400,
5129
+ opacity: item.disabled ? 0.4 : 1,
5130
+ cursor: item.disabled ? "not-allowed" : "pointer"
5131
+ };
5132
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5133
+ /* @__PURE__ */ jsxRuntime.jsxs(
5134
+ "div",
5135
+ {
5136
+ role: hasChildren ? "button" : "menuitem",
5137
+ tabIndex: item.disabled ? -1 : 0,
5138
+ "aria-disabled": item.disabled || void 0,
5139
+ "aria-expanded": hasChildren ? isExpanded : void 0,
5140
+ "aria-current": isSelected ? "page" : void 0,
5141
+ className: "box-border flex select-none items-center transition-colors duration-150",
5142
+ style: itemStyle,
5143
+ onClick: () => handleItemClick(item),
5144
+ onMouseEnter: () => setHoveredKey(item.value),
5145
+ onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
5146
+ onKeyDown: (e) => {
5147
+ if (e.key === "Enter" || e.key === " ") {
5148
+ e.preventDefault();
5149
+ handleItemClick(item);
5150
+ }
5151
+ },
5152
+ children: [
5153
+ depth === 1 && item.icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: item.icon, size: L.depth1Icon, className: "shrink-0" }),
5154
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap", children: item.label }),
5155
+ item.tag !== void 0 && item.tag !== "" && /* @__PURE__ */ jsxRuntime.jsx(
5156
+ STag,
5157
+ {
5158
+ shape: "square",
5159
+ size: "xs",
5160
+ color: isSelected ? "blue" : "grey",
5161
+ label: item.tag,
5162
+ className: "shrink-0"
5163
+ }
5164
+ ),
5165
+ hasChildren && /* @__PURE__ */ jsxRuntime.jsx(
5166
+ SIcon,
5167
+ {
5168
+ name: "chevronDown",
5169
+ size: L.itemArrow,
5170
+ rotate: isExpanded ? 180 : 0,
5171
+ className: "shrink-0 transition-transform duration-150"
5172
+ }
5173
+ )
5174
+ ]
5175
+ }
5176
+ ),
5177
+ hasChildren && isExpanded && item.children.map((child) => renderItem(child, depth + 1))
5178
+ ] }, item.value);
5179
+ };
5180
+ const railFolded = folded && type === "belt";
5181
+ const rootWidth = type === "box" ? "100%" : folded ? GNB_COMMON.foldWidth : GNB_MENU_WIDTH;
5182
+ const inBoxGrid = !!layout && type === "box";
5183
+ const rootStyle = react.useMemo(
5184
+ () => ({
5185
+ width: rootWidth,
5186
+ ...inBoxGrid ? { gridArea: "1 / 1 / -1 / -1" } : null,
5187
+ ...style
5188
+ }),
5189
+ [rootWidth, inBoxGrid, style]
5190
+ );
5191
+ const iconColor = C.itemColorDefault;
5192
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5193
+ "div",
5194
+ {
5195
+ ref,
5196
+ className: cn(
5197
+ "box-border flex flex-col",
5198
+ type === "box" && !inBoxGrid ? "h-auto" : "h-full",
5199
+ className
5200
+ ),
5201
+ style: rootStyle,
5202
+ "data-type": type,
5203
+ "data-color": color,
5204
+ children: [
5205
+ /* @__PURE__ */ jsxRuntime.jsxs(
5206
+ "div",
5207
+ {
5208
+ className: cn("box-border flex items-center", railFolded && "flex-col"),
5209
+ style: {
5210
+ gap: railFolded ? 8 : L.topGap,
5211
+ height: railFolded ? "auto" : GNB_COMMON.topHeight,
5212
+ padding: railFolded ? "8px 0" : `0 ${L.topPaddingX}`,
5213
+ borderBottom: `1px solid ${C.topBorder}`,
5214
+ backgroundColor: C.panelBg,
5215
+ color: iconColor
5216
+ },
5217
+ children: [
5218
+ /* @__PURE__ */ jsxRuntime.jsx(
5219
+ "button",
5220
+ {
5221
+ type: "button",
5222
+ "aria-label": "app launcher",
5223
+ className: iconBtnClass,
5224
+ onClick: onLauncherClick,
5225
+ children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "launcher", size: 20 })
5226
+ }
5227
+ ),
5228
+ !railFolded && logo && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", style: { height: GNB_COMMON.logoHeight }, children: logo }),
5229
+ !railFolded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: search }),
5230
+ /* @__PURE__ */ jsxRuntime.jsx(
5231
+ "button",
5232
+ {
5233
+ type: "button",
5234
+ "aria-label": "toggle navigation",
5235
+ "aria-pressed": folded,
5236
+ className: cn(iconBtnClass, !railFolded && "ml-auto"),
5237
+ onClick: handleFoldToggle,
5238
+ children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "sidebar", size: 20 })
5239
+ }
5240
+ )
5241
+ ]
5242
+ }
5243
+ ),
5244
+ !folded && /* @__PURE__ */ jsxRuntime.jsx(
5245
+ "div",
5246
+ {
5247
+ role: "menu",
5248
+ className: "flex flex-col overflow-y-auto",
5249
+ style: {
5250
+ width: GNB_MENU_WIDTH,
5251
+ alignSelf: "flex-start",
5252
+ flexGrow: 1,
5253
+ flexShrink: 1,
5254
+ flexBasis: "auto",
5255
+ minHeight: 0,
5256
+ paddingTop: L.bodyPadding,
5257
+ paddingBottom: L.bodyPadding,
5258
+ backgroundColor: C.panelBg
5259
+ },
5260
+ children: items.map((item) => renderItem(item, 1))
5261
+ }
5262
+ )
5263
+ ]
5264
+ }
5265
+ );
5266
+ });
5267
+ var SPage = react.forwardRef(function SPage2({ className, style, children, ...rest }, ref) {
5268
+ const layout = react.useContext(SLayoutContext);
5269
+ const gridStyle = layout?.type === "box" ? { gridColumn: 2, gridRow: 2 } : void 0;
5270
+ return /* @__PURE__ */ jsxRuntime.jsx(
5271
+ "main",
5272
+ {
5273
+ ref,
5274
+ className: cn("box-border min-h-0 min-w-0 flex-1 overflow-auto p-24", className),
5275
+ style: { ...gridStyle, ...style },
5276
+ ...rest,
5277
+ children
5278
+ }
5279
+ );
5280
+ });
5281
+
4890
5282
  // src/components/STabs/tabs.config.ts
4891
5283
  var TAB_MAIN_SIZE = {
4892
5284
  md: { height: 36, paddingX: 24 },
@@ -5237,6 +5629,7 @@ var SField = react.forwardRef(function SField2({
5237
5629
  name,
5238
5630
  label = "",
5239
5631
  icon,
5632
+ iconColor,
5240
5633
  labelTooltip = "",
5241
5634
  labelTooltipProps,
5242
5635
  labelWidth,
@@ -5314,7 +5707,15 @@ var SField = react.forwardRef(function SField2({
5314
5707
  lineHeight: `${s.lineHeight}px`
5315
5708
  },
5316
5709
  children: [
5317
- icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
5710
+ icon && /* @__PURE__ */ jsxRuntime.jsx(
5711
+ SIcon,
5712
+ {
5713
+ name: icon,
5714
+ size: s.icon,
5715
+ color: iconColor,
5716
+ style: { marginRight: s.labelGap }
5717
+ }
5718
+ ),
5318
5719
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
5319
5720
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
5320
5721
  STooltip,
@@ -5364,7 +5765,7 @@ var SField = react.forwardRef(function SField2({
5364
5765
  borderBottomLeftRadius: s.radius - 1
5365
5766
  },
5366
5767
  children: [
5367
- icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon }),
5768
+ icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
5368
5769
  effectiveAddonLabel
5369
5770
  ]
5370
5771
  }
@@ -5436,6 +5837,7 @@ var SSelect = react.forwardRef(function SSelect2({
5436
5837
  label,
5437
5838
  labelWidth,
5438
5839
  icon,
5840
+ iconColor,
5439
5841
  labelTooltip,
5440
5842
  addonLabel,
5441
5843
  addonAlign,
@@ -5447,18 +5849,15 @@ var SSelect = react.forwardRef(function SSelect2({
5447
5849
  }, ref) {
5448
5850
  const multi = type === "multi" || type === "multi_depth";
5449
5851
  const portalContainer = usePortalContainer();
5852
+ const insideModal = useInsideModal();
5450
5853
  const [search, setSearch] = react.useState("");
5451
5854
  const [ruleError, setRuleError] = react.useState("");
5452
5855
  const [open, setOpen] = react.useState(false);
5856
+ const [pendingOpen, setPendingOpen] = react.useState(false);
5857
+ const [focusedIndex, setFocusedIndex] = react.useState(-1);
5453
5858
  const triggerRef = react.useRef(null);
5454
- react.useImperativeHandle(
5455
- ref,
5456
- () => ({
5457
- focus: () => triggerRef.current?.focus(),
5458
- open: () => setOpen(true)
5459
- }),
5460
- []
5461
- );
5859
+ const searchInputRef = react.useRef(null);
5860
+ const listRef = react.useRef(null);
5462
5861
  const flat = react.useMemo(() => flattenOptions(options), [options]);
5463
5862
  const selectable = react.useMemo(() => selectableOptions(flat), [flat]);
5464
5863
  const hasLeaf = react.useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -5476,6 +5875,46 @@ var SSelect = react.forwardRef(function SSelect2({
5476
5875
  const arr = Array.isArray(value) ? value : [value];
5477
5876
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
5478
5877
  }, [value]);
5878
+ const selectedRef = react.useRef(selected);
5879
+ selectedRef.current = selected;
5880
+ const emit2 = (next) => {
5881
+ if (!onValueChange) return;
5882
+ const mapped = next.map(
5883
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
5884
+ );
5885
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
5886
+ };
5887
+ const commitOpen = (next) => {
5888
+ setOpen(next);
5889
+ onOpenChange?.(next);
5890
+ if (!next) {
5891
+ setPendingOpen(false);
5892
+ setSearch("");
5893
+ if (rules && rules.length > 0) {
5894
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
5895
+ }
5896
+ }
5897
+ };
5898
+ const requestOpen = (next) => {
5899
+ if (next && insideModal && portalContainer == null) {
5900
+ setPendingOpen(true);
5901
+ return;
5902
+ }
5903
+ commitOpen(next);
5904
+ };
5905
+ react.useEffect(() => {
5906
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
5907
+ setPendingOpen(false);
5908
+ commitOpen(true);
5909
+ }, [insideModal, pendingOpen, portalContainer]);
5910
+ react.useImperativeHandle(
5911
+ ref,
5912
+ () => ({
5913
+ focus: () => triggerRef.current?.focus(),
5914
+ open: () => requestOpen(true)
5915
+ }),
5916
+ [insideModal, portalContainer]
5917
+ );
5479
5918
  useFormField({
5480
5919
  name,
5481
5920
  validate: () => {
@@ -5485,13 +5924,6 @@ var SSelect = react.forwardRef(function SSelect2({
5485
5924
  },
5486
5925
  resetValidation: () => setRuleError("")
5487
5926
  });
5488
- const emit2 = (next) => {
5489
- if (!onValueChange) return;
5490
- const mapped = next.map(
5491
- (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
5492
- );
5493
- onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
5494
- };
5495
5927
  const filtered = react.useMemo(() => {
5496
5928
  if (!showSearch || search.trim() === "") return flat;
5497
5929
  const q = search.trim().toLowerCase();
@@ -5502,6 +5934,16 @@ var SSelect = react.forwardRef(function SSelect2({
5502
5934
  const allValues = selectable.map((o) => o.value);
5503
5935
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
5504
5936
  const hasItems = filtered.some((o) => !o.isGroup);
5937
+ const navigableItems = react.useMemo(() => {
5938
+ const items = [];
5939
+ if (multi && useSelectAll && allValues.length > 0) {
5940
+ items.push({ value: "__select_all__", isSelectAll: true });
5941
+ }
5942
+ filtered.forEach((o) => {
5943
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
5944
+ });
5945
+ return items;
5946
+ }, [allValues.length, filtered, multi, useSelectAll]);
5505
5947
  const triggerText = (() => {
5506
5948
  if (selected.length === 0) return placeholder;
5507
5949
  if (multi) {
@@ -5511,26 +5953,119 @@ var SSelect = react.forwardRef(function SSelect2({
5511
5953
  return labelByValue.get(selected[0]) ?? selected[0];
5512
5954
  })();
5513
5955
  const isPlaceholder = selected.length === 0;
5514
- const selectStore = /* @__PURE__ */ jsxRuntime.jsxs(
5515
- Ariakit__namespace.SelectProvider,
5956
+ const handleSelect = (nextValue) => {
5957
+ if (multi) {
5958
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
5959
+ emit2(next);
5960
+ return;
5961
+ }
5962
+ emit2([nextValue]);
5963
+ commitOpen(false);
5964
+ };
5965
+ const handleSelectAll = () => {
5966
+ emit2(allSelected ? [] : allValues);
5967
+ };
5968
+ react.useEffect(() => {
5969
+ if (!open) {
5970
+ setFocusedIndex(-1);
5971
+ return;
5972
+ }
5973
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
5974
+ }, [open, navigableItems.length]);
5975
+ react.useEffect(() => {
5976
+ if (!open) return;
5977
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
5978
+ }, [search, open, navigableItems.length]);
5979
+ react.useEffect(() => {
5980
+ if (!open || focusedIndex < 0) return;
5981
+ const list = listRef.current;
5982
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
5983
+ if (!list || !focusedEl) return;
5984
+ const listRect = list.getBoundingClientRect();
5985
+ const itemRect = focusedEl.getBoundingClientRect();
5986
+ if (itemRect.top < listRect.top) {
5987
+ list.scrollTop += itemRect.top - listRect.top;
5988
+ } else if (itemRect.bottom > listRect.bottom) {
5989
+ list.scrollTop += itemRect.bottom - listRect.bottom;
5990
+ }
5991
+ }, [focusedIndex, open]);
5992
+ const moveFocusedIndex = (direction) => {
5993
+ if (!open || navigableItems.length === 0) return;
5994
+ setFocusedIndex((current) => {
5995
+ if (direction === "next") {
5996
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
5997
+ }
5998
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
5999
+ });
6000
+ };
6001
+ const selectFocusedItem = () => {
6002
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
6003
+ const focused = navigableItems[focusedIndex];
6004
+ if (focused.isSelectAll) {
6005
+ handleSelectAll();
6006
+ return;
6007
+ }
6008
+ handleSelect(focused.value);
6009
+ };
6010
+ const handleKeyDown = (event) => {
6011
+ if (!open) {
6012
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
6013
+ event.preventDefault();
6014
+ requestOpen(true);
6015
+ }
6016
+ return;
6017
+ }
6018
+ if (event.key === "ArrowDown") {
6019
+ event.preventDefault();
6020
+ moveFocusedIndex("next");
6021
+ return;
6022
+ }
6023
+ if (event.key === "ArrowUp") {
6024
+ event.preventDefault();
6025
+ moveFocusedIndex("prev");
6026
+ return;
6027
+ }
6028
+ if (event.key === "Enter") {
6029
+ event.preventDefault();
6030
+ selectFocusedItem();
6031
+ return;
6032
+ }
6033
+ if (event.key === "Escape") {
6034
+ event.preventDefault();
6035
+ commitOpen(false);
6036
+ }
6037
+ };
6038
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6039
+ SField,
5516
6040
  {
5517
- animated: portalContainer == null ? 150 : false,
5518
- value: multi ? selected : selected[0] ?? "",
5519
- setValue: (v) => emit2(Array.isArray(v) ? v : v === "" ? [] : [v]),
5520
- open,
5521
- setOpen: (o) => {
5522
- setOpen(o);
5523
- onOpenChange?.(o);
5524
- if (!o && rules && rules.length > 0) {
5525
- setRuleError(runRules(multi ? selected : selected[0] ?? "", rules));
5526
- }
5527
- },
6041
+ label,
6042
+ labelWidth,
6043
+ icon,
6044
+ iconColor,
6045
+ labelTooltip,
6046
+ labelTooltipProps,
6047
+ addonLabel,
6048
+ addonAlign,
6049
+ hint,
6050
+ error: error || ruleError !== "",
6051
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
6052
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
6053
+ disabled: effectiveDisabled,
6054
+ width,
6055
+ className,
6056
+ style,
5528
6057
  children: [
6058
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
5529
6059
  /* @__PURE__ */ jsxRuntime.jsxs(
5530
- Ariakit__namespace.Select,
6060
+ "button",
5531
6061
  {
5532
6062
  ref: triggerRef,
6063
+ type: "button",
5533
6064
  disabled: effectiveDisabled,
6065
+ "aria-haspopup": "listbox",
6066
+ "aria-expanded": open,
6067
+ onClick: () => requestOpen(!open),
6068
+ onKeyDown: handleKeyDown,
5534
6069
  className: "flex h-full w-full cursor-pointer items-center justify-between gap-[8px] bg-transparent px-[12px] text-left text-[12px] leading-[20px] outline-none disabled:cursor-not-allowed",
5535
6070
  style: {
5536
6071
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -5555,178 +6090,237 @@ var SSelect = react.forwardRef(function SSelect2({
5555
6090
  ]
5556
6091
  }
5557
6092
  ),
5558
- /* @__PURE__ */ jsxRuntime.jsxs(
5559
- Ariakit__namespace.SelectPopover,
6093
+ /* @__PURE__ */ jsxRuntime.jsx(
6094
+ SPortal,
5560
6095
  {
5561
- portal: true,
5562
- portalElement: portalContainer,
5563
- gutter: 4,
5564
- sameWidth: !dropdownWidth,
5565
- className: "z-50 overflow-auto rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)] data-[enter]:animate-floating-in-bottom data-[leave]:animate-floating-out-bottom",
6096
+ open,
6097
+ onClose: () => commitOpen(false),
6098
+ onOpenChange: (next) => {
6099
+ if (next) requestOpen(true);
6100
+ },
6101
+ anchorRef: triggerRef,
6102
+ placement: "bottom",
6103
+ align: "start",
6104
+ offset: 4,
6105
+ fit: !dropdownWidth,
6106
+ onOpenAutoFocus: (event) => {
6107
+ if (!showSearch) {
6108
+ event.preventDefault();
6109
+ return;
6110
+ }
6111
+ event.preventDefault();
6112
+ requestAnimationFrame(() => searchInputRef.current?.focus());
6113
+ },
6114
+ onCloseAutoFocus: (event) => {
6115
+ event.preventDefault();
6116
+ triggerRef.current?.focus();
6117
+ },
6118
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
5566
6119
  style: {
5567
- maxHeight: dropdownHeight,
5568
6120
  ...dropdownWidth ? { width: dropdownWidth } : {},
5569
6121
  maxWidth: maxDropdownWidth
5570
6122
  },
5571
- children: [
5572
- showSearch && // 원본 sd-select-list-item-search: sticky + 검색 아이콘 + 테두리 input + focus-within + clear
5573
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-[6px] rounded-[4px] border border-solid border-[color:var(--color-grey-45)] bg-white px-[8px] py-[4px] focus-within:border-[color:var(--sys-color-field-border-focus)]", children: [
5574
- /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
5575
- /* @__PURE__ */ jsxRuntime.jsx(
5576
- Ariakit__namespace.Combobox,
5577
- {
5578
- autoSelect: true,
5579
- placeholder: "\uAC80\uC0C9",
5580
- className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--cmp-select-text-default)] outline-none placeholder:text-[color:var(--color-grey-55)]"
5581
- }
5582
- ),
5583
- search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
5584
- SGhostButton,
5585
- {
5586
- icon: "close",
5587
- size: "xxs",
5588
- intent: "default",
5589
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
5590
- onClick: () => setSearch(""),
5591
- className: "shrink-0"
5592
- }
5593
- )
5594
- ] }) }),
5595
- multi && useSelectAll && /* @__PURE__ */ jsxRuntime.jsx(
5596
- "button",
5597
- {
5598
- type: "button",
5599
- className: "flex w-full items-center px-[12px] py-[4px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)] hover:bg-[var(--cmp-listItem-bg-hover)] hover:text-[color:var(--cmp-listItem-content-hover)]",
5600
- onClick: () => emit2(allSelected ? [] : allValues),
5601
- children: allSelectedLabel
5602
- }
5603
- ),
5604
- hasItems ? /* @__PURE__ */ jsxRuntime.jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
5605
- // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
5606
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-[12px] py-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-disabled)]", children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
5607
- )
5608
- ]
6123
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
6124
+ "div",
6125
+ {
6126
+ ref: listRef,
6127
+ className: "overflow-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-[8px] [&::-webkit-scrollbar]:bg-[var(--color-grey-25)] [&::-webkit-scrollbar:horizontal]:h-[8px] [&::-webkit-scrollbar-thumb]:rounded-[4px] [&::-webkit-scrollbar-thumb]:bg-[var(--color-grey-45)] [&::-webkit-scrollbar-track]:bg-[var(--color-grey-25)]",
6128
+ style: { maxHeight: dropdownHeight },
6129
+ onMouseLeave: () => setFocusedIndex(-1),
6130
+ children: [
6131
+ showSearch && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-[6px] rounded-[4px] border border-solid border-[color:var(--color-grey-45)] bg-white px-[8px] py-[4px] focus-within:border-[color:var(--sys-color-field-border-focus)]", children: [
6132
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
6133
+ /* @__PURE__ */ jsxRuntime.jsx(
6134
+ "input",
6135
+ {
6136
+ ref: searchInputRef,
6137
+ value: search,
6138
+ onChange: (e) => setSearch(e.target.value),
6139
+ onKeyDown: handleKeyDown,
6140
+ placeholder: "\uAC80\uC0C9",
6141
+ className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--cmp-select-text-default)] outline-none placeholder:text-[color:var(--color-grey-55)]"
6142
+ }
6143
+ ),
6144
+ search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
6145
+ SGhostButton,
6146
+ {
6147
+ icon: "close",
6148
+ size: "xxs",
6149
+ intent: "default",
6150
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
6151
+ onClick: () => setSearch(""),
6152
+ className: "shrink-0"
6153
+ }
6154
+ )
6155
+ ] }) }),
6156
+ hasItems ? /* @__PURE__ */ jsxRuntime.jsx(
6157
+ ItemList,
6158
+ {
6159
+ items: filtered,
6160
+ multi,
6161
+ selected,
6162
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
6163
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
6164
+ selectAllLabel: allSelectedLabel,
6165
+ selectAllSelected: allSelected,
6166
+ onSelectAll: handleSelectAll,
6167
+ onSelect: handleSelect,
6168
+ onHoverValue: (value2) => {
6169
+ if (value2 == null) {
6170
+ setFocusedIndex(-1);
6171
+ return;
6172
+ }
6173
+ const index = navigableItems.findIndex(
6174
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
6175
+ );
6176
+ if (index >= 0) setFocusedIndex(index);
6177
+ }
6178
+ }
6179
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-[12px] py-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-disabled)]", children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
6180
+ ]
6181
+ }
6182
+ )
5609
6183
  }
5610
6184
  )
5611
6185
  ]
5612
6186
  }
5613
6187
  );
5614
- const control = showSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
5615
- return /* @__PURE__ */ jsxRuntime.jsxs(
5616
- SField,
5617
- {
5618
- label,
5619
- labelWidth,
5620
- icon,
5621
- labelTooltip,
5622
- labelTooltipProps,
5623
- addonLabel,
5624
- addonAlign,
5625
- hint,
5626
- error: error || ruleError !== "",
5627
- status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
5628
- errorMessage: ruleError !== "" ? ruleError : errorMessage,
5629
- disabled: effectiveDisabled,
5630
- width,
5631
- className,
5632
- style,
5633
- children: [
5634
- name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
5635
- control
5636
- ]
5637
- }
5638
- );
5639
6188
  });
5640
6189
  SSelect.displayName = "SSelect";
5641
6190
  function ItemList({
5642
6191
  items,
5643
6192
  multi,
5644
6193
  selected,
5645
- useSearch
6194
+ focusedValue,
6195
+ showSelectAll,
6196
+ selectAllLabel,
6197
+ selectAllSelected,
6198
+ onSelectAll,
6199
+ onSelect,
6200
+ onHoverValue
5646
6201
  }) {
5647
6202
  const itemBase = cn(
5648
- // group: 체크박스 inverse(group-data-[active-item])를 위해 필요
5649
- "group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
6203
+ "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
5650
6204
  "text-[color:var(--cmp-listItem-content-default)]",
5651
- // Ariakit은 disabled 항목에 aria-disabled를 붙이므로 aria-disabled 변형으로 스타일 (data-disabled 아님)
5652
- "aria-disabled:cursor-not-allowed aria-disabled:text-[color:var(--cmp-listItem-content-disabled)]"
5653
- );
5654
- const activeClass = "data-[active-item]:bg-[var(--cmp-listItem-bg-hover)] data-[active-item]:text-[color:var(--cmp-listItem-content-hover)]";
5655
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: items.map((o) => {
5656
- const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
5657
- if (o.isGroup) {
5658
- const isDepth1 = o.depth === 1;
5659
- return /* @__PURE__ */ jsxRuntime.jsx(
5660
- "div",
5661
- {
5662
- className: cn(
5663
- "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
5664
- isDepth1 ? "border-t border-solid border-[color:var(--cmp-listItem-depth1-border-color)] bg-[var(--cmp-listItem-depth1-bg-default)] font-bold" : "bg-[var(--cmp-listItem-depth2-middle-bg)] font-medium",
5665
- // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
5666
- // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
5667
- o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
5668
- o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
5669
- ),
5670
- style: { paddingLeft: padLeft },
5671
- children: o.label
5672
- },
5673
- o.value
5674
- );
5675
- }
5676
- const isSelected = selected.includes(o.value);
5677
- return /* @__PURE__ */ jsxRuntime.jsxs(
5678
- Ariakit__namespace.SelectItem,
6205
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
6206
+ );
6207
+ const activeClass = "hover:bg-[var(--cmp-listItem-bg-hover)] hover:text-[color:var(--cmp-listItem-content-hover)] focus:bg-[var(--cmp-listItem-bg-hover)] focus:text-[color:var(--cmp-listItem-content-hover)]";
6208
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
6209
+ showSelectAll && /* @__PURE__ */ jsxRuntime.jsxs(
6210
+ "button",
5679
6211
  {
5680
- value: o.value,
5681
- disabled: o.disabled,
5682
- accessibleWhenDisabled: true,
6212
+ type: "button",
6213
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
6214
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
5683
6215
  className: cn(
5684
6216
  itemBase,
5685
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
5686
- // Ariakit은 disabled 항목도 hover 시 active(data-active-item) 처리하므로,
5687
- // 비활성 항목에는 hover 하이라이트를 적용하지 않아 회색 비활성 스타일을 유지한다.
5688
- // (원본 Stencil의 `--focused...:not(--disabled)` 규칙과 동일한 의도)
5689
- !o.disabled && activeClass
6217
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
5690
6218
  ),
5691
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
5692
- render: useSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxItem, {}) : void 0,
6219
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
6220
+ onMouseEnter: () => onHoverValue("__select_all__"),
6221
+ onMouseLeave: () => onHoverValue(null),
6222
+ onClick: onSelectAll,
5693
6223
  children: [
5694
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
5695
6224
  /* @__PURE__ */ jsxRuntime.jsx(
5696
6225
  "span",
5697
6226
  {
5698
6227
  className: cn(
5699
6228
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
5700
- o.disabled ? (
5701
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
5702
- "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
5703
- ) : cn(
5704
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
5705
- isSelected ? "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)] group-data-[active-item]:bg-transparent" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-transparent"
5706
- )
6229
+ selectAllSelected ? focusedValue === "__select_all__" ? "border-[color:var(--cmp-checkbox-checked-border-inverse)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-[var(--cmp-checkbox-unchecked-bg-default)]"
5707
6230
  ),
5708
- children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
6231
+ children: selectAllSelected && /* @__PURE__ */ jsxRuntime.jsx(
5709
6232
  SIcon,
5710
6233
  {
5711
6234
  name: "checkboxCheck",
5712
6235
  size: 12,
5713
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
6236
+ color: "var(--cmp-checkbox-checked-icon-default)"
5714
6237
  }
5715
6238
  )
5716
6239
  }
5717
6240
  ),
5718
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
5719
- "span",
5720
- {
5721
- className: "flex-1 truncate",
5722
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
5723
- }
5724
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
6241
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
5725
6242
  ]
5726
- },
5727
- o.value
5728
- );
5729
- }) });
6243
+ }
6244
+ ),
6245
+ items.map((o) => {
6246
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
6247
+ if (o.isGroup) {
6248
+ const isDepth1 = o.depth === 1;
6249
+ return /* @__PURE__ */ jsxRuntime.jsx(
6250
+ "div",
6251
+ {
6252
+ className: cn(
6253
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
6254
+ isDepth1 ? "border-t border-solid border-[color:var(--cmp-listItem-depth1-border-color)] bg-[var(--cmp-listItem-depth1-bg-default)] font-bold" : "bg-[var(--cmp-listItem-depth2-middle-bg)] font-medium",
6255
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
6256
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
6257
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
6258
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
6259
+ ),
6260
+ style: { paddingLeft: padLeft },
6261
+ children: o.label
6262
+ },
6263
+ o.value
6264
+ );
6265
+ }
6266
+ const isSelected = selected.includes(o.value);
6267
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6268
+ "button",
6269
+ {
6270
+ type: "button",
6271
+ disabled: o.disabled,
6272
+ role: "option",
6273
+ "aria-selected": isSelected,
6274
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
6275
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
6276
+ className: cn(
6277
+ itemBase,
6278
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
6279
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
6280
+ // 비활성 항목은 hover/focus 스타일 대상에서 제외한다.
6281
+ // (:hover 는 disabled 버튼에도 매칭되므로 클래스 자체를 붙이지 않아야 한다)
6282
+ !o.disabled && activeClass
6283
+ ),
6284
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
6285
+ onMouseEnter: () => onHoverValue(o.value),
6286
+ onMouseLeave: () => onHoverValue(null),
6287
+ onClick: () => onSelect(o.value),
6288
+ children: [
6289
+ multi && /* @__PURE__ */ jsxRuntime.jsx(
6290
+ "span",
6291
+ {
6292
+ className: cn(
6293
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
6294
+ o.disabled ? (
6295
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
6296
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
6297
+ ) : cn(
6298
+ isSelected ? focusedValue === o.value ? "border-[color:var(--cmp-checkbox-checked-border-inverse)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-[var(--cmp-checkbox-unchecked-bg-default)]"
6299
+ )
6300
+ ),
6301
+ children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
6302
+ SIcon,
6303
+ {
6304
+ name: "checkboxCheck",
6305
+ size: 12,
6306
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
6307
+ }
6308
+ )
6309
+ }
6310
+ ),
6311
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
6312
+ "span",
6313
+ {
6314
+ className: "flex-1 truncate",
6315
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
6316
+ }
6317
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
6318
+ ]
6319
+ },
6320
+ o.value
6321
+ );
6322
+ })
6323
+ ] });
5730
6324
  }
5731
6325
  var DEFAULT_ROW_HEIGHT = 44;
5732
6326
  var DENSE_ROW_HEIGHT = 32;
@@ -6002,7 +6596,7 @@ var STable = react.forwardRef(function STable2({
6002
6596
  };
6003
6597
  const radiusClass = radius === "full" ? "rounded-none" : radius === "useTop" ? "rounded-b-[var(--cmp-table-radius)] rounded-t-none" : "rounded-[var(--cmp-table-radius)]";
6004
6598
  const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-body-paddingY-default)";
6005
- const cellPadX = dense ? "var(--cmp-table-body-paddingX-default)" : "var(--cmp-table-body-paddingX-wide)";
6599
+ const cellPadX = "var(--cmp-table-body-paddingX-wide)";
6006
6600
  const rowH = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-body-default-height)";
6007
6601
  const totalColSpan = visibleCols.length + (selectable ? 1 : 0);
6008
6602
  const stickyInfo = (i) => {
@@ -6210,10 +6804,10 @@ var STable = react.forwardRef(function STable2({
6210
6804
  "div",
6211
6805
  {
6212
6806
  "aria-hidden": true,
6213
- className: "pointer-events-none absolute left-0 right-0 top-0 z-[210] h-[var(--cmp-table-header-height)] bg-white/60"
6807
+ className: "pointer-events-none absolute left-0 right-0 top-0 z-[31] h-[var(--cmp-table-header-height)] bg-white/60"
6214
6808
  }
6215
6809
  ),
6216
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute bottom-0 left-0 right-0 top-[var(--cmp-table-header-height)] z-[200] flex items-center justify-center bg-white/60 text-[12px] text-[color:var(--color-grey-65)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[60px] w-full items-center justify-center", children: noDataLabel }) })
6810
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute bottom-0 left-0 right-0 top-[var(--cmp-table-header-height)] z-[30] flex items-center justify-center bg-white/60 text-[12px] text-[color:var(--color-grey-65)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[60px] w-full items-center justify-center", children: noDataLabel }) })
6217
6811
  ] }),
6218
6812
  isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-white/60", children: /* @__PURE__ */ jsxRuntime.jsx(SCircleProgress, { indeterminate: true }) })
6219
6813
  ]
@@ -6249,6 +6843,7 @@ var SInput = react.forwardRef(function SInput2({
6249
6843
  type = "text",
6250
6844
  size = "sm",
6251
6845
  icon,
6846
+ iconColor,
6252
6847
  prefix,
6253
6848
  suffix,
6254
6849
  clearable = false,
@@ -6302,6 +6897,7 @@ var SInput = react.forwardRef(function SInput2({
6302
6897
  labelTooltip,
6303
6898
  labelTooltipProps,
6304
6899
  icon,
6900
+ iconColor,
6305
6901
  size,
6306
6902
  hint,
6307
6903
  error: resolvedError,
@@ -6403,6 +6999,7 @@ var STextarea = react.forwardRef(function STextarea2({
6403
6999
  label,
6404
7000
  labelWidth,
6405
7001
  icon,
7002
+ iconColor,
6406
7003
  labelTooltip,
6407
7004
  labelTooltipProps,
6408
7005
  addonLabel,
@@ -6439,6 +7036,7 @@ var STextarea = react.forwardRef(function STextarea2({
6439
7036
  label,
6440
7037
  labelWidth,
6441
7038
  icon,
7039
+ iconColor,
6442
7040
  labelTooltip,
6443
7041
  labelTooltipProps,
6444
7042
  addonLabel,
@@ -6571,6 +7169,7 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
6571
7169
  label,
6572
7170
  labelWidth,
6573
7171
  icon,
7172
+ iconColor,
6574
7173
  labelTooltip,
6575
7174
  labelTooltipProps,
6576
7175
  addonLabel,
@@ -6729,6 +7328,7 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
6729
7328
  label,
6730
7329
  labelWidth,
6731
7330
  icon,
7331
+ iconColor,
6732
7332
  labelTooltip,
6733
7333
  labelTooltipProps,
6734
7334
  addonLabel,
@@ -7204,6 +7804,7 @@ function SDatePicker({
7204
7804
  label,
7205
7805
  labelWidth,
7206
7806
  icon,
7807
+ iconColor,
7207
7808
  labelTooltip,
7208
7809
  labelTooltipProps,
7209
7810
  addonLabel,
@@ -7242,6 +7843,7 @@ function SDatePicker({
7242
7843
  label,
7243
7844
  labelWidth,
7244
7845
  icon,
7846
+ iconColor,
7245
7847
  labelTooltip,
7246
7848
  labelTooltipProps,
7247
7849
  addonLabel,
@@ -7510,6 +8112,7 @@ function SDateRangePicker({
7510
8112
  label,
7511
8113
  labelWidth,
7512
8114
  icon,
8115
+ iconColor,
7513
8116
  labelTooltip,
7514
8117
  labelTooltipProps,
7515
8118
  addonLabel,
@@ -7552,6 +8155,7 @@ function SDateRangePicker({
7552
8155
  label,
7553
8156
  labelWidth,
7554
8157
  icon,
8158
+ iconColor,
7555
8159
  labelTooltip,
7556
8160
  labelTooltipProps,
7557
8161
  addonLabel,
@@ -7813,6 +8417,7 @@ function SFilePicker({
7813
8417
  label,
7814
8418
  labelWidth,
7815
8419
  icon,
8420
+ iconColor,
7816
8421
  labelTooltip,
7817
8422
  labelTooltipProps,
7818
8423
  addonLabel,
@@ -7982,7 +8587,7 @@ function SFilePicker({
7982
8587
  unmountOnHide: true,
7983
8588
  getAnchorRect: () => contentBoxRef.current?.getBoundingClientRect() ?? null,
7984
8589
  style: { minWidth: contentBoxRef.current?.offsetWidth },
7985
- className: "z-50 box-border flex flex-col items-stretch gap-[8px] rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] p-[8px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
8590
+ className: "z-50 box-border flex flex-col items-start gap-[8px] rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] p-[8px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
7986
8591
  children: filesArr.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i))
7987
8592
  }
7988
8593
  )
@@ -7996,6 +8601,7 @@ function SFilePicker({
7996
8601
  label,
7997
8602
  labelWidth,
7998
8603
  icon,
8604
+ iconColor,
7999
8605
  labelTooltip,
8000
8606
  labelTooltipProps,
8001
8607
  addonLabel,
@@ -8018,21 +8624,26 @@ function SFilePicker({
8018
8624
  var cssLen2 = (v) => v == null ? void 0 : typeof v === "number" ? `${v}px` : v;
8019
8625
  function SKeyValueTable({
8020
8626
  fields = [],
8627
+ values = {},
8021
8628
  search = false,
8022
8629
  radius = "default",
8023
- dense = false,
8024
8630
  onChange,
8025
8631
  onSearch,
8026
8632
  className,
8027
8633
  style
8028
8634
  }) {
8029
8635
  if (fields.length === 0) return null;
8030
- const emit2 = (name, value) => onChange?.({ name, value });
8031
- const cellHeight = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-keyValue-height)";
8032
- const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-keyValue-body-paddingY)";
8636
+ const emit2 = (name, value) => onChange?.({
8637
+ name,
8638
+ value,
8639
+ values: { ...values, [name]: value }
8640
+ });
8641
+ const cellHeight = "var(--cmp-table-keyValue-height)";
8642
+ const cellPadY = "var(--cmp-table-keyValue-body-paddingY)";
8033
8643
  const renderField = (f) => {
8034
8644
  if (f.render) return f.render;
8035
- const opt = f.options ?? {};
8645
+ const opt = { ...f.options ?? {} };
8646
+ if (Object.prototype.hasOwnProperty.call(values, f.name)) opt.value = values[f.name];
8036
8647
  const up = (v) => emit2(f.name, v);
8037
8648
  switch (f.type) {
8038
8649
  case "input":
@@ -8144,7 +8755,10 @@ function SKeyValueTable({
8144
8755
  rowSpan: f.tdRowSpan && f.tdRowSpan > 1 ? f.tdRowSpan : void 0,
8145
8756
  colSpan: f.tdColSpan && f.tdColSpan > 1 ? f.tdColSpan : void 0,
8146
8757
  className: cn(
8147
- "box-border bg-[var(--sys-color-bg-frame)] px-[var(--cmp-table-keyValue-body-paddingX-default)] align-middle",
8758
+ "box-border bg-[var(--sys-color-bg-frame)] align-middle",
8759
+ // text 타입(및 미지정 기본값)은 읽기 전용 콘텐츠라 넓은 paddingX(wide),
8760
+ // 폼 필드 셀은 default paddingX 유지
8761
+ f.type == null || f.type === "text" ? "px-[var(--cmp-table-keyValue-body-paddingX-wide)]" : "px-[var(--cmp-table-keyValue-body-paddingX-default)]",
8148
8762
  cellBorder,
8149
8763
  f.tdClass
8150
8764
  ),
@@ -8637,6 +9251,7 @@ var SChipInput = react.forwardRef(function SChipInput2({
8637
9251
  error,
8638
9252
  useReset = false,
8639
9253
  maxCount,
9254
+ metaPlacement = "end",
8640
9255
  duplicateLabel,
8641
9256
  suggestions = [],
8642
9257
  loadingSuggestions = false,
@@ -8650,6 +9265,7 @@ var SChipInput = react.forwardRef(function SChipInput2({
8650
9265
  width,
8651
9266
  status,
8652
9267
  icon,
9268
+ iconColor,
8653
9269
  labelTooltip,
8654
9270
  labelTooltipProps,
8655
9271
  className,
@@ -8784,6 +9400,7 @@ var SChipInput = react.forwardRef(function SChipInput2({
8784
9400
  labelWidth,
8785
9401
  labelHeight: `calc(var(--cmp-chipInput-${size}-minHeight) + 2px)`,
8786
9402
  icon,
9403
+ iconColor,
8787
9404
  labelTooltip,
8788
9405
  labelTooltipProps,
8789
9406
  size,
@@ -8931,7 +9548,11 @@ var SChipInput = react.forwardRef(function SChipInput2({
8931
9548
  showMetaInfo && /* @__PURE__ */ jsxRuntime.jsxs(
8932
9549
  "div",
8933
9550
  {
8934
- className: "s-chip-input__meta ml-auto flex items-center gap-8",
9551
+ className: cn(
9552
+ "s-chip-input__meta flex items-center gap-8",
9553
+ metaPlacement === "end" && "ml-auto"
9554
+ ),
9555
+ "data-placement": metaPlacement,
8935
9556
  onMouseDown: (e) => e.preventDefault(),
8936
9557
  children: [
8937
9558
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -8941,11 +9562,20 @@ var SChipInput = react.forwardRef(function SChipInput2({
8941
9562
  style: { width: 1, height: 12, backgroundColor: "#E1E1E1" }
8942
9563
  }
8943
9564
  ),
8944
- showMaxCount && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "s-chip-input__max-count text-12 text-(--cmp-chipInput-text-placeholder)", children: [
8945
- "\uCD5C\uB300 ",
8946
- maxCount,
8947
- "\uAC1C"
8948
- ] }),
9565
+ showMaxCount && /* @__PURE__ */ jsxRuntime.jsxs(
9566
+ "span",
9567
+ {
9568
+ className: cn(
9569
+ "s-chip-input__max-count text-12",
9570
+ isMax ? "text-(--sys-color-fg-warning)" : "text-(--cmp-chipInput-text-placeholder)"
9571
+ ),
9572
+ children: [
9573
+ "\uCD5C\uB300 ",
9574
+ maxCount,
9575
+ "\uAC1C"
9576
+ ]
9577
+ }
9578
+ ),
8949
9579
  showResetLink && /* @__PURE__ */ jsxRuntime.jsx(
8950
9580
  STextLink,
8951
9581
  {
@@ -8982,7 +9612,7 @@ var SChipInput = react.forwardRef(function SChipInput2({
8982
9612
  onMouseDown: (e) => e.preventDefault(),
8983
9613
  children: [
8984
9614
  loading2 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "s-chip-input__suggestions-empty p-12 text-center text-12 leading-20 text-(--color-grey-65)", children: "\uBD88\uB7EC\uC624\uB294 \uC911..." }),
8985
- !effectiveAutocomplete ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-8 p-12", children: items.map((item, i) => {
9615
+ !effectiveAutocomplete ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-8 px-12 py-6", children: items.map((item, i) => {
8986
9616
  const selected = values.includes(item);
8987
9617
  const active = i === activeIndex && !selected;
8988
9618
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -9071,6 +9701,7 @@ var SBarcodeInput = react.forwardRef(
9071
9701
  label,
9072
9702
  labelWidth,
9073
9703
  icon,
9704
+ iconColor,
9074
9705
  labelTooltip,
9075
9706
  labelTooltipProps,
9076
9707
  addonLabel,
@@ -9102,6 +9733,7 @@ var SBarcodeInput = react.forwardRef(
9102
9733
  label,
9103
9734
  labelWidth,
9104
9735
  icon,
9736
+ iconColor,
9105
9737
  labelTooltip,
9106
9738
  labelTooltipProps,
9107
9739
  addonLabel,
@@ -9184,6 +9816,10 @@ exports.BADGE_COLORS = BADGE_COLORS;
9184
9816
  exports.BUTTON_COLORS = BUTTON_COLORS;
9185
9817
  exports.BUTTON_SIZES = BUTTON_SIZES;
9186
9818
  exports.COLOR_KEYS = COLOR_KEYS;
9819
+ exports.GNB_COLORS = GNB_COLORS;
9820
+ exports.GNB_COMMON = GNB_COMMON;
9821
+ exports.GNB_LAYOUT = GNB_LAYOUT;
9822
+ exports.GNB_MENU_WIDTH = GNB_MENU_WIDTH;
9187
9823
  exports.ICONS = ICONS;
9188
9824
  exports.SActionModal = SActionModal;
9189
9825
  exports.SBadge = SBadge;
@@ -9205,16 +9841,19 @@ exports.SField = SField;
9205
9841
  exports.SFilePicker = SFilePicker;
9206
9842
  exports.SForm = SForm;
9207
9843
  exports.SGhostButton = SGhostButton;
9844
+ exports.SGnb = SGnb;
9208
9845
  exports.SGuide = SGuide;
9209
9846
  exports.SIcon = SIcon;
9210
9847
  exports.SInput = SInput;
9211
9848
  exports.SKeyValueTable = SKeyValueTable;
9849
+ exports.SLayout = SLayout;
9850
+ exports.SLayoutContext = SLayoutContext;
9212
9851
  exports.SLinearProgress = SLinearProgress;
9213
9852
  exports.SLoadingContainer = SLoadingContainer;
9214
9853
  exports.SLoadingModal = SLoadingModal;
9215
9854
  exports.SLoadingViewport = SLoadingViewport;
9216
- exports.SModalContainer = SModalContainer;
9217
9855
  exports.SNumberInput = SNumberInput;
9856
+ exports.SPage = SPage;
9218
9857
  exports.SPagination = SPagination;
9219
9858
  exports.SPopover = SPopover;
9220
9859
  exports.SPopup = SPopup;
@@ -9238,8 +9877,10 @@ exports.TAG_SHAPES = TAG_SHAPES;
9238
9877
  exports.TAG_SIZES = TAG_SIZES;
9239
9878
  exports.buttonPreset = buttonPreset;
9240
9879
  exports.cn = cn;
9880
+ exports.findGnbAncestors = findGnbAncestors;
9241
9881
  exports.isColorKey = isColorKey;
9242
9882
  exports.loading = loading;
9243
9883
  exports.resolveColor = resolveColor;
9884
+ exports.useSLayout = useSLayout;
9244
9885
  //# sourceMappingURL=index.cjs.map
9245
9886
  //# sourceMappingURL=index.cjs.map