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
@@ -6,8 +6,8 @@ var clsx = require('clsx');
6
6
  var tailwindMerge = require('tailwind-merge');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
8
  var RPopover2 = require('@radix-ui/react-popover');
9
- var Ariakit = require('@ariakit/react');
10
9
  var RCheckbox = require('@radix-ui/react-checkbox');
10
+ var Ariakit = require('@ariakit/react');
11
11
 
12
12
  function _interopNamespace(e) {
13
13
  if (e && e.__esModule) return e;
@@ -28,8 +28,8 @@ function _interopNamespace(e) {
28
28
  }
29
29
 
30
30
  var RPopover2__namespace = /*#__PURE__*/_interopNamespace(RPopover2);
31
- var Ariakit__namespace = /*#__PURE__*/_interopNamespace(Ariakit);
32
31
  var RCheckbox__namespace = /*#__PURE__*/_interopNamespace(RCheckbox);
32
+ var Ariakit__namespace = /*#__PURE__*/_interopNamespace(Ariakit);
33
33
 
34
34
  var twMerge = tailwindMerge.extendTailwindMerge({
35
35
  extend: {
@@ -2897,10 +2897,15 @@ var FLOATING_SLIDE_ANIM = [
2897
2897
  "data-[state=closed]:data-[side=right]:animate-floating-out-right"
2898
2898
  ].join(" ");
2899
2899
  var PortalContainerContext = react.createContext(null);
2900
+ var InsideModalContext = react.createContext(false);
2900
2901
  PortalContainerContext.Provider;
2902
+ InsideModalContext.Provider;
2901
2903
  function usePortalContainer() {
2902
2904
  return react.useContext(PortalContainerContext) ?? void 0;
2903
2905
  }
2906
+ function useInsideModal() {
2907
+ return react.useContext(InsideModalContext);
2908
+ }
2904
2909
 
2905
2910
  // src/components/STag/tag.config.ts
2906
2911
  var TAG_SIZE_CONFIG = {
@@ -2998,6 +3003,8 @@ function SGhostButton({
2998
3003
  tooltipText,
2999
3004
  disabled = false,
3000
3005
  onClick,
3006
+ anchorClassName,
3007
+ anchorStyle,
3001
3008
  className,
3002
3009
  style
3003
3010
  }) {
@@ -3020,6 +3027,8 @@ function SGhostButton({
3020
3027
  };
3021
3028
  const s = SIZE[size];
3022
3029
  const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
3030
+ const text = (tooltipText ?? "").trim();
3031
+ const hasTooltip = text !== "";
3023
3032
  const btn = /* @__PURE__ */ jsxRuntime.jsxs(
3024
3033
  "button",
3025
3034
  {
@@ -3060,13 +3069,13 @@ function SGhostButton({
3060
3069
  ]
3061
3070
  }
3062
3071
  );
3063
- const text = (tooltipText ?? "").trim();
3064
- if (text === "") return btn;
3072
+ if (!hasTooltip) return btn;
3065
3073
  return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
3066
3074
  /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
3067
3075
  "span",
3068
3076
  {
3069
- className: "inline-flex",
3077
+ className: cn("inline-flex", anchorClassName),
3078
+ style: anchorStyle,
3070
3079
  onMouseEnter: openNow,
3071
3080
  onMouseLeave: startHide,
3072
3081
  onFocus: openNow,
@@ -3424,6 +3433,7 @@ var SField = react.forwardRef(function SField2({
3424
3433
  name,
3425
3434
  label = "",
3426
3435
  icon,
3436
+ iconColor,
3427
3437
  labelTooltip = "",
3428
3438
  labelTooltipProps,
3429
3439
  labelWidth,
@@ -3501,7 +3511,15 @@ var SField = react.forwardRef(function SField2({
3501
3511
  lineHeight: `${s.lineHeight}px`
3502
3512
  },
3503
3513
  children: [
3504
- icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3514
+ icon && /* @__PURE__ */ jsxRuntime.jsx(
3515
+ SIcon,
3516
+ {
3517
+ name: icon,
3518
+ size: s.icon,
3519
+ color: iconColor,
3520
+ style: { marginRight: s.labelGap }
3521
+ }
3522
+ ),
3505
3523
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3506
3524
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
3507
3525
  STooltip,
@@ -3551,7 +3569,7 @@ var SField = react.forwardRef(function SField2({
3551
3569
  borderBottomLeftRadius: s.radius - 1
3552
3570
  },
3553
3571
  children: [
3554
- icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon }),
3572
+ icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
3555
3573
  effectiveAddonLabel
3556
3574
  ]
3557
3575
  }
@@ -3577,6 +3595,7 @@ var SInput = react.forwardRef(function SInput2({
3577
3595
  type = "text",
3578
3596
  size = "sm",
3579
3597
  icon,
3598
+ iconColor,
3580
3599
  prefix,
3581
3600
  suffix,
3582
3601
  clearable = false,
@@ -3630,6 +3649,7 @@ var SInput = react.forwardRef(function SInput2({
3630
3649
  labelTooltip,
3631
3650
  labelTooltipProps,
3632
3651
  icon,
3652
+ iconColor,
3633
3653
  size,
3634
3654
  hint,
3635
3655
  error: resolvedError,
@@ -3731,6 +3751,7 @@ var STextarea = react.forwardRef(function STextarea2({
3731
3751
  label,
3732
3752
  labelWidth,
3733
3753
  icon,
3754
+ iconColor,
3734
3755
  labelTooltip,
3735
3756
  labelTooltipProps,
3736
3757
  addonLabel,
@@ -3767,6 +3788,7 @@ var STextarea = react.forwardRef(function STextarea2({
3767
3788
  label,
3768
3789
  labelWidth,
3769
3790
  icon,
3791
+ iconColor,
3770
3792
  labelTooltip,
3771
3793
  labelTooltipProps,
3772
3794
  addonLabel,
@@ -3899,6 +3921,7 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
3899
3921
  label,
3900
3922
  labelWidth,
3901
3923
  icon,
3924
+ iconColor,
3902
3925
  labelTooltip,
3903
3926
  labelTooltipProps,
3904
3927
  addonLabel,
@@ -4057,6 +4080,7 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
4057
4080
  label,
4058
4081
  labelWidth,
4059
4082
  icon,
4083
+ iconColor,
4060
4084
  labelTooltip,
4061
4085
  labelTooltipProps,
4062
4086
  addonLabel,
@@ -4142,40 +4166,6 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
4142
4166
  );
4143
4167
  });
4144
4168
 
4145
- // src/components/SSelect/select.config.ts
4146
- function flattenOptions(options, depth = 1) {
4147
- const out = [];
4148
- for (const o of options) {
4149
- const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4150
- if (hasChildren) {
4151
- const childFlat = flattenOptions(o.children, depth + 1);
4152
- const allChildrenDisabled = childFlat.every((c) => c.disabled);
4153
- out.push({
4154
- value: String(o.value),
4155
- label: o.label,
4156
- disabled: !!o.disabled || allChildrenDisabled,
4157
- depth,
4158
- isGroup: true,
4159
- raw: o.value
4160
- });
4161
- out.push(...childFlat);
4162
- } else {
4163
- out.push({
4164
- value: String(o.value),
4165
- label: o.label,
4166
- disabled: !!o.disabled,
4167
- depth,
4168
- isGroup: false,
4169
- raw: o.value
4170
- });
4171
- }
4172
- }
4173
- return out;
4174
- }
4175
- var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
4176
- var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
4177
- var extractText = (html) => html.replace(/<[^>]*>/g, "");
4178
-
4179
4169
  // src/lib/sanitize-inline-html.ts
4180
4170
  var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
4181
4171
  var ALLOWED_ATTRS_BY_TAG = {
@@ -4244,6 +4234,111 @@ var sanitizeInlineHtml = (value) => {
4244
4234
  Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
4245
4235
  return template.innerHTML;
4246
4236
  };
4237
+ function SPortal({
4238
+ open,
4239
+ onClose,
4240
+ onOpenChange,
4241
+ anchorRef,
4242
+ placement = "bottom",
4243
+ align = "start",
4244
+ offset = 4,
4245
+ alignOffset = 0,
4246
+ fit = false,
4247
+ maxHeight,
4248
+ autoClose = false,
4249
+ persistent = false,
4250
+ viewportPadding = 0,
4251
+ onOpenAutoFocus,
4252
+ onCloseAutoFocus,
4253
+ children,
4254
+ className,
4255
+ style,
4256
+ ariaLabel
4257
+ }) {
4258
+ const portalContainer = usePortalContainer();
4259
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4260
+ RPopover2__namespace.Root,
4261
+ {
4262
+ open,
4263
+ onOpenChange: (next) => {
4264
+ onOpenChange?.(next);
4265
+ if (!next) onClose?.();
4266
+ },
4267
+ children: [
4268
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { virtualRef: anchorRef }),
4269
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
4270
+ RPopover2__namespace.Content,
4271
+ {
4272
+ side: placement,
4273
+ align,
4274
+ sideOffset: offset,
4275
+ alignOffset,
4276
+ collisionPadding: viewportPadding,
4277
+ "aria-label": ariaLabel,
4278
+ onPointerDownOutside: (e) => {
4279
+ if (persistent || anchorRef.current?.contains(e.target)) {
4280
+ e.preventDefault();
4281
+ }
4282
+ },
4283
+ onFocusOutside: (e) => {
4284
+ if (persistent || anchorRef.current?.contains(e.target)) {
4285
+ e.preventDefault();
4286
+ }
4287
+ },
4288
+ onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
4289
+ onOpenAutoFocus,
4290
+ onCloseAutoFocus,
4291
+ onClick: autoClose ? () => onClose?.() : void 0,
4292
+ className: cn(
4293
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm",
4294
+ maxHeight != null && "overflow-y-auto",
4295
+ fit && "min-w-(--radix-popover-trigger-width)",
4296
+ FLOATING_SLIDE_ANIM,
4297
+ className
4298
+ ),
4299
+ style: { maxHeight, ...style },
4300
+ children
4301
+ }
4302
+ ) })
4303
+ ]
4304
+ }
4305
+ );
4306
+ }
4307
+ SPortal.displayName = "SPortal";
4308
+
4309
+ // src/components/SSelect/select.config.ts
4310
+ function flattenOptions(options, depth = 1) {
4311
+ const out = [];
4312
+ for (const o of options) {
4313
+ const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4314
+ if (hasChildren) {
4315
+ const childFlat = flattenOptions(o.children, depth + 1);
4316
+ const allChildrenDisabled = childFlat.every((c) => c.disabled);
4317
+ out.push({
4318
+ value: String(o.value),
4319
+ label: o.label,
4320
+ disabled: !!o.disabled || allChildrenDisabled,
4321
+ depth,
4322
+ isGroup: true,
4323
+ raw: o.value
4324
+ });
4325
+ out.push(...childFlat);
4326
+ } else {
4327
+ out.push({
4328
+ value: String(o.value),
4329
+ label: o.label,
4330
+ disabled: !!o.disabled,
4331
+ depth,
4332
+ isGroup: false,
4333
+ raw: o.value
4334
+ });
4335
+ }
4336
+ }
4337
+ return out;
4338
+ }
4339
+ var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
4340
+ var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
4341
+ var extractText = (html) => html.replace(/<[^>]*>/g, "");
4247
4342
  var SEARCH_THRESHOLD = 11;
4248
4343
  var SSelect = react.forwardRef(function SSelect2({
4249
4344
  value,
@@ -4267,6 +4362,7 @@ var SSelect = react.forwardRef(function SSelect2({
4267
4362
  label,
4268
4363
  labelWidth,
4269
4364
  icon,
4365
+ iconColor,
4270
4366
  labelTooltip,
4271
4367
  addonLabel,
4272
4368
  addonAlign,
@@ -4278,18 +4374,15 @@ var SSelect = react.forwardRef(function SSelect2({
4278
4374
  }, ref) {
4279
4375
  const multi = type === "multi" || type === "multi_depth";
4280
4376
  const portalContainer = usePortalContainer();
4377
+ const insideModal = useInsideModal();
4281
4378
  const [search, setSearch] = react.useState("");
4282
4379
  const [ruleError, setRuleError] = react.useState("");
4283
4380
  const [open, setOpen] = react.useState(false);
4381
+ const [pendingOpen, setPendingOpen] = react.useState(false);
4382
+ const [focusedIndex, setFocusedIndex] = react.useState(-1);
4284
4383
  const triggerRef = react.useRef(null);
4285
- react.useImperativeHandle(
4286
- ref,
4287
- () => ({
4288
- focus: () => triggerRef.current?.focus(),
4289
- open: () => setOpen(true)
4290
- }),
4291
- []
4292
- );
4384
+ const searchInputRef = react.useRef(null);
4385
+ const listRef = react.useRef(null);
4293
4386
  const flat = react.useMemo(() => flattenOptions(options), [options]);
4294
4387
  const selectable = react.useMemo(() => selectableOptions(flat), [flat]);
4295
4388
  const hasLeaf = react.useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -4307,6 +4400,46 @@ var SSelect = react.forwardRef(function SSelect2({
4307
4400
  const arr = Array.isArray(value) ? value : [value];
4308
4401
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
4309
4402
  }, [value]);
4403
+ const selectedRef = react.useRef(selected);
4404
+ selectedRef.current = selected;
4405
+ const emit = (next) => {
4406
+ if (!onValueChange) return;
4407
+ const mapped = next.map(
4408
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
4409
+ );
4410
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
4411
+ };
4412
+ const commitOpen = (next) => {
4413
+ setOpen(next);
4414
+ onOpenChange?.(next);
4415
+ if (!next) {
4416
+ setPendingOpen(false);
4417
+ setSearch("");
4418
+ if (rules && rules.length > 0) {
4419
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
4420
+ }
4421
+ }
4422
+ };
4423
+ const requestOpen = (next) => {
4424
+ if (next && insideModal && portalContainer == null) {
4425
+ setPendingOpen(true);
4426
+ return;
4427
+ }
4428
+ commitOpen(next);
4429
+ };
4430
+ react.useEffect(() => {
4431
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
4432
+ setPendingOpen(false);
4433
+ commitOpen(true);
4434
+ }, [insideModal, pendingOpen, portalContainer]);
4435
+ react.useImperativeHandle(
4436
+ ref,
4437
+ () => ({
4438
+ focus: () => triggerRef.current?.focus(),
4439
+ open: () => requestOpen(true)
4440
+ }),
4441
+ [insideModal, portalContainer]
4442
+ );
4310
4443
  useFormField({
4311
4444
  name,
4312
4445
  validate: () => {
@@ -4316,13 +4449,6 @@ var SSelect = react.forwardRef(function SSelect2({
4316
4449
  },
4317
4450
  resetValidation: () => setRuleError("")
4318
4451
  });
4319
- const emit = (next) => {
4320
- if (!onValueChange) return;
4321
- const mapped = next.map(
4322
- (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
4323
- );
4324
- onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
4325
- };
4326
4452
  const filtered = react.useMemo(() => {
4327
4453
  if (!showSearch || search.trim() === "") return flat;
4328
4454
  const q = search.trim().toLowerCase();
@@ -4333,6 +4459,16 @@ var SSelect = react.forwardRef(function SSelect2({
4333
4459
  const allValues = selectable.map((o) => o.value);
4334
4460
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
4335
4461
  const hasItems = filtered.some((o) => !o.isGroup);
4462
+ const navigableItems = react.useMemo(() => {
4463
+ const items = [];
4464
+ if (multi && useSelectAll && allValues.length > 0) {
4465
+ items.push({ value: "__select_all__", isSelectAll: true });
4466
+ }
4467
+ filtered.forEach((o) => {
4468
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
4469
+ });
4470
+ return items;
4471
+ }, [allValues.length, filtered, multi, useSelectAll]);
4336
4472
  const triggerText = (() => {
4337
4473
  if (selected.length === 0) return placeholder;
4338
4474
  if (multi) {
@@ -4342,26 +4478,119 @@ var SSelect = react.forwardRef(function SSelect2({
4342
4478
  return labelByValue.get(selected[0]) ?? selected[0];
4343
4479
  })();
4344
4480
  const isPlaceholder = selected.length === 0;
4345
- const selectStore = /* @__PURE__ */ jsxRuntime.jsxs(
4346
- Ariakit__namespace.SelectProvider,
4481
+ const handleSelect = (nextValue) => {
4482
+ if (multi) {
4483
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
4484
+ emit(next);
4485
+ return;
4486
+ }
4487
+ emit([nextValue]);
4488
+ commitOpen(false);
4489
+ };
4490
+ const handleSelectAll = () => {
4491
+ emit(allSelected ? [] : allValues);
4492
+ };
4493
+ react.useEffect(() => {
4494
+ if (!open) {
4495
+ setFocusedIndex(-1);
4496
+ return;
4497
+ }
4498
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4499
+ }, [open, navigableItems.length]);
4500
+ react.useEffect(() => {
4501
+ if (!open) return;
4502
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4503
+ }, [search, open, navigableItems.length]);
4504
+ react.useEffect(() => {
4505
+ if (!open || focusedIndex < 0) return;
4506
+ const list = listRef.current;
4507
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
4508
+ if (!list || !focusedEl) return;
4509
+ const listRect = list.getBoundingClientRect();
4510
+ const itemRect = focusedEl.getBoundingClientRect();
4511
+ if (itemRect.top < listRect.top) {
4512
+ list.scrollTop += itemRect.top - listRect.top;
4513
+ } else if (itemRect.bottom > listRect.bottom) {
4514
+ list.scrollTop += itemRect.bottom - listRect.bottom;
4515
+ }
4516
+ }, [focusedIndex, open]);
4517
+ const moveFocusedIndex = (direction) => {
4518
+ if (!open || navigableItems.length === 0) return;
4519
+ setFocusedIndex((current) => {
4520
+ if (direction === "next") {
4521
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
4522
+ }
4523
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
4524
+ });
4525
+ };
4526
+ const selectFocusedItem = () => {
4527
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
4528
+ const focused = navigableItems[focusedIndex];
4529
+ if (focused.isSelectAll) {
4530
+ handleSelectAll();
4531
+ return;
4532
+ }
4533
+ handleSelect(focused.value);
4534
+ };
4535
+ const handleKeyDown = (event) => {
4536
+ if (!open) {
4537
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
4538
+ event.preventDefault();
4539
+ requestOpen(true);
4540
+ }
4541
+ return;
4542
+ }
4543
+ if (event.key === "ArrowDown") {
4544
+ event.preventDefault();
4545
+ moveFocusedIndex("next");
4546
+ return;
4547
+ }
4548
+ if (event.key === "ArrowUp") {
4549
+ event.preventDefault();
4550
+ moveFocusedIndex("prev");
4551
+ return;
4552
+ }
4553
+ if (event.key === "Enter") {
4554
+ event.preventDefault();
4555
+ selectFocusedItem();
4556
+ return;
4557
+ }
4558
+ if (event.key === "Escape") {
4559
+ event.preventDefault();
4560
+ commitOpen(false);
4561
+ }
4562
+ };
4563
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4564
+ SField,
4347
4565
  {
4348
- animated: portalContainer == null ? 150 : false,
4349
- value: multi ? selected : selected[0] ?? "",
4350
- setValue: (v) => emit(Array.isArray(v) ? v : v === "" ? [] : [v]),
4351
- open,
4352
- setOpen: (o) => {
4353
- setOpen(o);
4354
- onOpenChange?.(o);
4355
- if (!o && rules && rules.length > 0) {
4356
- setRuleError(runRules(multi ? selected : selected[0] ?? "", rules));
4357
- }
4358
- },
4566
+ label,
4567
+ labelWidth,
4568
+ icon,
4569
+ iconColor,
4570
+ labelTooltip,
4571
+ labelTooltipProps,
4572
+ addonLabel,
4573
+ addonAlign,
4574
+ hint,
4575
+ error: error || ruleError !== "",
4576
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4577
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
4578
+ disabled: effectiveDisabled,
4579
+ width,
4580
+ className,
4581
+ style,
4359
4582
  children: [
4583
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
4360
4584
  /* @__PURE__ */ jsxRuntime.jsxs(
4361
- Ariakit__namespace.Select,
4585
+ "button",
4362
4586
  {
4363
4587
  ref: triggerRef,
4588
+ type: "button",
4364
4589
  disabled: effectiveDisabled,
4590
+ "aria-haspopup": "listbox",
4591
+ "aria-expanded": open,
4592
+ onClick: () => requestOpen(!open),
4593
+ onKeyDown: handleKeyDown,
4365
4594
  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",
4366
4595
  style: {
4367
4596
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -4386,178 +4615,237 @@ var SSelect = react.forwardRef(function SSelect2({
4386
4615
  ]
4387
4616
  }
4388
4617
  ),
4389
- /* @__PURE__ */ jsxRuntime.jsxs(
4390
- Ariakit__namespace.SelectPopover,
4618
+ /* @__PURE__ */ jsxRuntime.jsx(
4619
+ SPortal,
4391
4620
  {
4392
- portal: true,
4393
- portalElement: portalContainer,
4394
- gutter: 4,
4395
- sameWidth: !dropdownWidth,
4396
- 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",
4621
+ open,
4622
+ onClose: () => commitOpen(false),
4623
+ onOpenChange: (next) => {
4624
+ if (next) requestOpen(true);
4625
+ },
4626
+ anchorRef: triggerRef,
4627
+ placement: "bottom",
4628
+ align: "start",
4629
+ offset: 4,
4630
+ fit: !dropdownWidth,
4631
+ onOpenAutoFocus: (event) => {
4632
+ if (!showSearch) {
4633
+ event.preventDefault();
4634
+ return;
4635
+ }
4636
+ event.preventDefault();
4637
+ requestAnimationFrame(() => searchInputRef.current?.focus());
4638
+ },
4639
+ onCloseAutoFocus: (event) => {
4640
+ event.preventDefault();
4641
+ triggerRef.current?.focus();
4642
+ },
4643
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
4397
4644
  style: {
4398
- maxHeight: dropdownHeight,
4399
4645
  ...dropdownWidth ? { width: dropdownWidth } : {},
4400
4646
  maxWidth: maxDropdownWidth
4401
4647
  },
4402
- children: [
4403
- showSearch && // 원본 sd-select-list-item-search: sticky + 검색 아이콘 + 테두리 input + focus-within + clear
4404
- /* @__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: [
4405
- /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4406
- /* @__PURE__ */ jsxRuntime.jsx(
4407
- Ariakit__namespace.Combobox,
4408
- {
4409
- autoSelect: true,
4410
- placeholder: "\uAC80\uC0C9",
4411
- 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)]"
4412
- }
4413
- ),
4414
- search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
4415
- SGhostButton,
4416
- {
4417
- icon: "close",
4418
- size: "xxs",
4419
- intent: "default",
4420
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4421
- onClick: () => setSearch(""),
4422
- className: "shrink-0"
4423
- }
4424
- )
4425
- ] }) }),
4426
- multi && useSelectAll && /* @__PURE__ */ jsxRuntime.jsx(
4427
- "button",
4428
- {
4429
- type: "button",
4430
- 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)]",
4431
- onClick: () => emit(allSelected ? [] : allValues),
4432
- children: allSelectedLabel
4433
- }
4434
- ),
4435
- hasItems ? /* @__PURE__ */ jsxRuntime.jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
4436
- // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
4437
- /* @__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." })
4438
- )
4439
- ]
4648
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4649
+ "div",
4650
+ {
4651
+ ref: listRef,
4652
+ 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)]",
4653
+ style: { maxHeight: dropdownHeight },
4654
+ onMouseLeave: () => setFocusedIndex(-1),
4655
+ children: [
4656
+ 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: [
4657
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4658
+ /* @__PURE__ */ jsxRuntime.jsx(
4659
+ "input",
4660
+ {
4661
+ ref: searchInputRef,
4662
+ value: search,
4663
+ onChange: (e) => setSearch(e.target.value),
4664
+ onKeyDown: handleKeyDown,
4665
+ placeholder: "\uAC80\uC0C9",
4666
+ 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)]"
4667
+ }
4668
+ ),
4669
+ search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
4670
+ SGhostButton,
4671
+ {
4672
+ icon: "close",
4673
+ size: "xxs",
4674
+ intent: "default",
4675
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4676
+ onClick: () => setSearch(""),
4677
+ className: "shrink-0"
4678
+ }
4679
+ )
4680
+ ] }) }),
4681
+ hasItems ? /* @__PURE__ */ jsxRuntime.jsx(
4682
+ ItemList,
4683
+ {
4684
+ items: filtered,
4685
+ multi,
4686
+ selected,
4687
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
4688
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
4689
+ selectAllLabel: allSelectedLabel,
4690
+ selectAllSelected: allSelected,
4691
+ onSelectAll: handleSelectAll,
4692
+ onSelect: handleSelect,
4693
+ onHoverValue: (value2) => {
4694
+ if (value2 == null) {
4695
+ setFocusedIndex(-1);
4696
+ return;
4697
+ }
4698
+ const index = navigableItems.findIndex(
4699
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
4700
+ );
4701
+ if (index >= 0) setFocusedIndex(index);
4702
+ }
4703
+ }
4704
+ ) : /* @__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." })
4705
+ ]
4706
+ }
4707
+ )
4440
4708
  }
4441
4709
  )
4442
4710
  ]
4443
4711
  }
4444
4712
  );
4445
- const control = showSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
4446
- return /* @__PURE__ */ jsxRuntime.jsxs(
4447
- SField,
4448
- {
4449
- label,
4450
- labelWidth,
4451
- icon,
4452
- labelTooltip,
4453
- labelTooltipProps,
4454
- addonLabel,
4455
- addonAlign,
4456
- hint,
4457
- error: error || ruleError !== "",
4458
- status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4459
- errorMessage: ruleError !== "" ? ruleError : errorMessage,
4460
- disabled: effectiveDisabled,
4461
- width,
4462
- className,
4463
- style,
4464
- children: [
4465
- name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
4466
- control
4467
- ]
4468
- }
4469
- );
4470
4713
  });
4471
4714
  SSelect.displayName = "SSelect";
4472
4715
  function ItemList({
4473
4716
  items,
4474
4717
  multi,
4475
4718
  selected,
4476
- useSearch
4719
+ focusedValue,
4720
+ showSelectAll,
4721
+ selectAllLabel,
4722
+ selectAllSelected,
4723
+ onSelectAll,
4724
+ onSelect,
4725
+ onHoverValue
4477
4726
  }) {
4478
4727
  const itemBase = cn(
4479
- // group: 체크박스 inverse(group-data-[active-item])를 위해 필요
4480
- "group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
4728
+ "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
4481
4729
  "text-[color:var(--cmp-listItem-content-default)]",
4482
- // Ariakit은 disabled 항목에 aria-disabled를 붙이므로 aria-disabled 변형으로 스타일 (data-disabled 아님)
4483
- "aria-disabled:cursor-not-allowed aria-disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4730
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4484
4731
  );
4485
- const activeClass = "data-[active-item]:bg-[var(--cmp-listItem-bg-hover)] data-[active-item]:text-[color:var(--cmp-listItem-content-hover)]";
4486
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: items.map((o) => {
4487
- const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4488
- if (o.isGroup) {
4489
- const isDepth1 = o.depth === 1;
4490
- return /* @__PURE__ */ jsxRuntime.jsx(
4491
- "div",
4492
- {
4493
- className: cn(
4494
- "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4495
- 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",
4496
- // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4497
- // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4498
- o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4499
- o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4500
- ),
4501
- style: { paddingLeft: padLeft },
4502
- children: o.label
4503
- },
4504
- o.value
4505
- );
4506
- }
4507
- const isSelected = selected.includes(o.value);
4508
- return /* @__PURE__ */ jsxRuntime.jsxs(
4509
- Ariakit__namespace.SelectItem,
4732
+ 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)]";
4733
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
4734
+ showSelectAll && /* @__PURE__ */ jsxRuntime.jsxs(
4735
+ "button",
4510
4736
  {
4511
- value: o.value,
4512
- disabled: o.disabled,
4513
- accessibleWhenDisabled: true,
4737
+ type: "button",
4738
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
4739
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
4514
4740
  className: cn(
4515
4741
  itemBase,
4516
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4517
- // Ariakit은 disabled 항목도 hover 시 active(data-active-item) 처리하므로,
4518
- // 비활성 항목에는 hover 하이라이트를 적용하지 않아 회색 비활성 스타일을 유지한다.
4519
- // (원본 Stencil의 `--focused...:not(--disabled)` 규칙과 동일한 의도)
4520
- !o.disabled && activeClass
4742
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
4521
4743
  ),
4522
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4523
- render: useSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxItem, {}) : void 0,
4744
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
4745
+ onMouseEnter: () => onHoverValue("__select_all__"),
4746
+ onMouseLeave: () => onHoverValue(null),
4747
+ onClick: onSelectAll,
4524
4748
  children: [
4525
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
4526
4749
  /* @__PURE__ */ jsxRuntime.jsx(
4527
4750
  "span",
4528
4751
  {
4529
4752
  className: cn(
4530
4753
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4531
- o.disabled ? (
4532
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4533
- "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4534
- ) : cn(
4535
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4536
- 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"
4537
- )
4754
+ 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)]"
4538
4755
  ),
4539
- children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4756
+ children: selectAllSelected && /* @__PURE__ */ jsxRuntime.jsx(
4540
4757
  SIcon,
4541
4758
  {
4542
4759
  name: "checkboxCheck",
4543
4760
  size: 12,
4544
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4761
+ color: "var(--cmp-checkbox-checked-icon-default)"
4545
4762
  }
4546
4763
  )
4547
4764
  }
4548
4765
  ),
4549
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4550
- "span",
4551
- {
4552
- className: "flex-1 truncate",
4553
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4554
- }
4555
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4766
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
4556
4767
  ]
4557
- },
4558
- o.value
4559
- );
4560
- }) });
4768
+ }
4769
+ ),
4770
+ items.map((o) => {
4771
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4772
+ if (o.isGroup) {
4773
+ const isDepth1 = o.depth === 1;
4774
+ return /* @__PURE__ */ jsxRuntime.jsx(
4775
+ "div",
4776
+ {
4777
+ className: cn(
4778
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4779
+ 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",
4780
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4781
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4782
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4783
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4784
+ ),
4785
+ style: { paddingLeft: padLeft },
4786
+ children: o.label
4787
+ },
4788
+ o.value
4789
+ );
4790
+ }
4791
+ const isSelected = selected.includes(o.value);
4792
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4793
+ "button",
4794
+ {
4795
+ type: "button",
4796
+ disabled: o.disabled,
4797
+ role: "option",
4798
+ "aria-selected": isSelected,
4799
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
4800
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
4801
+ className: cn(
4802
+ itemBase,
4803
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4804
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
4805
+ // 비활성 항목은 hover/focus 스타일 대상에서 제외한다.
4806
+ // (:hover 는 disabled 버튼에도 매칭되므로 클래스 자체를 붙이지 않아야 한다)
4807
+ !o.disabled && activeClass
4808
+ ),
4809
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4810
+ onMouseEnter: () => onHoverValue(o.value),
4811
+ onMouseLeave: () => onHoverValue(null),
4812
+ onClick: () => onSelect(o.value),
4813
+ children: [
4814
+ multi && /* @__PURE__ */ jsxRuntime.jsx(
4815
+ "span",
4816
+ {
4817
+ className: cn(
4818
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4819
+ o.disabled ? (
4820
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4821
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4822
+ ) : cn(
4823
+ 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)]"
4824
+ )
4825
+ ),
4826
+ children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4827
+ SIcon,
4828
+ {
4829
+ name: "checkboxCheck",
4830
+ size: 12,
4831
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4832
+ }
4833
+ )
4834
+ }
4835
+ ),
4836
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4837
+ "span",
4838
+ {
4839
+ className: "flex-1 truncate",
4840
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4841
+ }
4842
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4843
+ ]
4844
+ },
4845
+ o.value
4846
+ );
4847
+ })
4848
+ ] });
4561
4849
  }
4562
4850
  function SRadio({
4563
4851
  val,
@@ -5020,6 +5308,7 @@ function SDatePicker({
5020
5308
  label,
5021
5309
  labelWidth,
5022
5310
  icon,
5311
+ iconColor,
5023
5312
  labelTooltip,
5024
5313
  labelTooltipProps,
5025
5314
  addonLabel,
@@ -5058,6 +5347,7 @@ function SDatePicker({
5058
5347
  label,
5059
5348
  labelWidth,
5060
5349
  icon,
5350
+ iconColor,
5061
5351
  labelTooltip,
5062
5352
  labelTooltipProps,
5063
5353
  addonLabel,
@@ -5326,6 +5616,7 @@ function SDateRangePicker({
5326
5616
  label,
5327
5617
  labelWidth,
5328
5618
  icon,
5619
+ iconColor,
5329
5620
  labelTooltip,
5330
5621
  labelTooltipProps,
5331
5622
  addonLabel,
@@ -5368,6 +5659,7 @@ function SDateRangePicker({
5368
5659
  label,
5369
5660
  labelWidth,
5370
5661
  icon,
5662
+ iconColor,
5371
5663
  labelTooltip,
5372
5664
  labelTooltipProps,
5373
5665
  addonLabel,
@@ -5629,6 +5921,7 @@ function SFilePicker({
5629
5921
  label,
5630
5922
  labelWidth,
5631
5923
  icon,
5924
+ iconColor,
5632
5925
  labelTooltip,
5633
5926
  labelTooltipProps,
5634
5927
  addonLabel,
@@ -5798,7 +6091,7 @@ function SFilePicker({
5798
6091
  unmountOnHide: true,
5799
6092
  getAnchorRect: () => contentBoxRef.current?.getBoundingClientRect() ?? null,
5800
6093
  style: { minWidth: contentBoxRef.current?.offsetWidth },
5801
- 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)]",
6094
+ 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)]",
5802
6095
  children: filesArr.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i))
5803
6096
  }
5804
6097
  )
@@ -5812,6 +6105,7 @@ function SFilePicker({
5812
6105
  label,
5813
6106
  labelWidth,
5814
6107
  icon,
6108
+ iconColor,
5815
6109
  labelTooltip,
5816
6110
  labelTooltipProps,
5817
6111
  addonLabel,
@@ -5834,21 +6128,26 @@ function SFilePicker({
5834
6128
  var cssLen2 = (v) => v == null ? void 0 : typeof v === "number" ? `${v}px` : v;
5835
6129
  function SKeyValueTable({
5836
6130
  fields = [],
6131
+ values = {},
5837
6132
  search = false,
5838
6133
  radius = "default",
5839
- dense = false,
5840
6134
  onChange,
5841
6135
  onSearch,
5842
6136
  className,
5843
6137
  style
5844
6138
  }) {
5845
6139
  if (fields.length === 0) return null;
5846
- const emit = (name, value) => onChange?.({ name, value });
5847
- const cellHeight = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-keyValue-height)";
5848
- const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-keyValue-body-paddingY)";
6140
+ const emit = (name, value) => onChange?.({
6141
+ name,
6142
+ value,
6143
+ values: { ...values, [name]: value }
6144
+ });
6145
+ const cellHeight = "var(--cmp-table-keyValue-height)";
6146
+ const cellPadY = "var(--cmp-table-keyValue-body-paddingY)";
5849
6147
  const renderField = (f) => {
5850
6148
  if (f.render) return f.render;
5851
- const opt = f.options ?? {};
6149
+ const opt = { ...f.options ?? {} };
6150
+ if (Object.prototype.hasOwnProperty.call(values, f.name)) opt.value = values[f.name];
5852
6151
  const up = (v) => emit(f.name, v);
5853
6152
  switch (f.type) {
5854
6153
  case "input":
@@ -5960,7 +6259,10 @@ function SKeyValueTable({
5960
6259
  rowSpan: f.tdRowSpan && f.tdRowSpan > 1 ? f.tdRowSpan : void 0,
5961
6260
  colSpan: f.tdColSpan && f.tdColSpan > 1 ? f.tdColSpan : void 0,
5962
6261
  className: cn(
5963
- "box-border bg-[var(--sys-color-bg-frame)] px-[var(--cmp-table-keyValue-body-paddingX-default)] align-middle",
6262
+ "box-border bg-[var(--sys-color-bg-frame)] align-middle",
6263
+ // text 타입(및 미지정 기본값)은 읽기 전용 콘텐츠라 넓은 paddingX(wide),
6264
+ // 폼 필드 셀은 default paddingX 유지
6265
+ f.type == null || f.type === "text" ? "px-[var(--cmp-table-keyValue-body-paddingX-wide)]" : "px-[var(--cmp-table-keyValue-body-paddingX-default)]",
5964
6266
  cellBorder,
5965
6267
  f.tdClass
5966
6268
  ),