sellmate-design-system-react 0.1.9 → 0.1.11

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 (148) hide show
  1. package/dist/components/SActionModal/SActionModal.cjs +3 -1
  2. package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
  3. package/dist/components/SActionModal/SActionModal.js +3 -1
  4. package/dist/components/SActionModal/SActionModal.js.map +1 -1
  5. package/dist/components/SBarcodeInput/SBarcodeInput.cjs +16 -3
  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 +16 -3
  9. package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
  10. package/dist/components/SCalendar/SCalendar.cjs +2 -0
  11. package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
  12. package/dist/components/SCalendar/SCalendar.js +2 -0
  13. package/dist/components/SCalendar/SCalendar.js.map +1 -1
  14. package/dist/components/SChip/SChip.cjs +2 -0
  15. package/dist/components/SChip/SChip.cjs.map +1 -1
  16. package/dist/components/SChip/SChip.js +2 -0
  17. package/dist/components/SChip/SChip.js.map +1 -1
  18. package/dist/components/SChipInput/SChipInput.cjs +22 -4
  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 +22 -4
  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 +69 -50
  25. package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
  26. package/dist/components/SCircleProgress/SCircleProgress.d.ts +2 -0
  27. package/dist/components/SCircleProgress/SCircleProgress.js +69 -50
  28. package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
  29. package/dist/components/SConfirmModal/SConfirmModal.cjs +13 -10
  30. package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
  31. package/dist/components/SConfirmModal/SConfirmModal.d.ts +7 -4
  32. package/dist/components/SConfirmModal/SConfirmModal.js +13 -10
  33. package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
  34. package/dist/components/SDatePicker/SDatePicker.cjs +16 -3
  35. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  36. package/dist/components/SDatePicker/SDatePicker.d.ts +3 -1
  37. package/dist/components/SDatePicker/SDatePicker.js +16 -3
  38. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  39. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +16 -3
  40. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  41. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +3 -1
  42. package/dist/components/SDateRangePicker/SDateRangePicker.js +16 -3
  43. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  44. package/dist/components/SDropdownButton/SDropdownButton.cjs +2 -0
  45. package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
  46. package/dist/components/SDropdownButton/SDropdownButton.js +2 -0
  47. package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
  48. package/dist/components/SField/SField.cjs +14 -3
  49. package/dist/components/SField/SField.cjs.map +1 -1
  50. package/dist/components/SField/SField.d.ts +4 -1
  51. package/dist/components/SField/SField.js +14 -3
  52. package/dist/components/SField/SField.js.map +1 -1
  53. package/dist/components/SFilePicker/SFilePicker.cjs +51 -19
  54. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  55. package/dist/components/SFilePicker/SFilePicker.d.ts +4 -2
  56. package/dist/components/SFilePicker/SFilePicker.js +50 -19
  57. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  58. package/dist/components/SGhostButton/SGhostButton.cjs +2 -0
  59. package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
  60. package/dist/components/SGhostButton/SGhostButton.js +2 -0
  61. package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
  62. package/dist/components/SGuide/SGuide.cjs +2 -0
  63. package/dist/components/SGuide/SGuide.cjs.map +1 -1
  64. package/dist/components/SGuide/SGuide.js +2 -0
  65. package/dist/components/SGuide/SGuide.js.map +1 -1
  66. package/dist/components/SInput/SInput.cjs +16 -3
  67. package/dist/components/SInput/SInput.cjs.map +1 -1
  68. package/dist/components/SInput/SInput.d.ts +4 -1
  69. package/dist/components/SInput/SInput.js +16 -3
  70. package/dist/components/SInput/SInput.js.map +1 -1
  71. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +530 -219
  72. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  73. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +27 -6
  74. package/dist/components/SKeyValueTable/SKeyValueTable.js +529 -218
  75. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  76. package/dist/components/SLoadingContainer/SLoadingContainer.cjs +69 -50
  77. package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
  78. package/dist/components/SLoadingContainer/SLoadingContainer.js +69 -50
  79. package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
  80. package/dist/components/SLoadingContainer/loading.cjs +69 -50
  81. package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
  82. package/dist/components/SLoadingContainer/loading.js +69 -50
  83. package/dist/components/SLoadingContainer/loading.js.map +1 -1
  84. package/dist/components/SLoadingModal/SLoadingModal.cjs +75 -51
  85. package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
  86. package/dist/components/SLoadingModal/SLoadingModal.d.ts +3 -1
  87. package/dist/components/SLoadingModal/SLoadingModal.js +75 -51
  88. package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
  89. package/dist/components/SModalContainer/SModalContainer.cjs +3 -1
  90. package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
  91. package/dist/components/SModalContainer/SModalContainer.js +3 -1
  92. package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
  93. package/dist/components/SNumberInput/SNumberInput.cjs +16 -3
  94. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  95. package/dist/components/SNumberInput/SNumberInput.d.ts +2 -0
  96. package/dist/components/SNumberInput/SNumberInput.js +16 -3
  97. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  98. package/dist/components/SPopover/SPopover.cjs +2 -0
  99. package/dist/components/SPopover/SPopover.cjs.map +1 -1
  100. package/dist/components/SPopover/SPopover.js +2 -0
  101. package/dist/components/SPopover/SPopover.js.map +1 -1
  102. package/dist/components/SPortal/SPortal.cjs +2 -0
  103. package/dist/components/SPortal/SPortal.cjs.map +1 -1
  104. package/dist/components/SPortal/SPortal.js +2 -0
  105. package/dist/components/SPortal/SPortal.js.map +1 -1
  106. package/dist/components/SRadio/SRadioGroup.cjs +1 -1
  107. package/dist/components/SRadio/SRadioGroup.cjs.map +1 -1
  108. package/dist/components/SRadio/SRadioGroup.js +1 -1
  109. package/dist/components/SRadio/SRadioGroup.js.map +1 -1
  110. package/dist/components/SSelect/SSelect.cjs +530 -258
  111. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  112. package/dist/components/SSelect/SSelect.d.ts +4 -2
  113. package/dist/components/SSelect/SSelect.js +531 -258
  114. package/dist/components/SSelect/SSelect.js.map +1 -1
  115. package/dist/components/STable/STable.cjs +603 -309
  116. package/dist/components/STable/STable.cjs.map +1 -1
  117. package/dist/components/STable/STable.js +604 -309
  118. package/dist/components/STable/STable.js.map +1 -1
  119. package/dist/components/STextLink/STextLink.cjs +2 -0
  120. package/dist/components/STextLink/STextLink.cjs.map +1 -1
  121. package/dist/components/STextLink/STextLink.js +2 -0
  122. package/dist/components/STextLink/STextLink.js.map +1 -1
  123. package/dist/components/STextarea/STextarea.cjs +16 -3
  124. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  125. package/dist/components/STextarea/STextarea.d.ts +2 -0
  126. package/dist/components/STextarea/STextarea.js +16 -3
  127. package/dist/components/STextarea/STextarea.js.map +1 -1
  128. package/dist/components/SToast/SToast.cjs +2 -0
  129. package/dist/components/SToast/SToast.cjs.map +1 -1
  130. package/dist/components/SToast/SToast.js +2 -0
  131. package/dist/components/SToast/SToast.js.map +1 -1
  132. package/dist/components/SToast/SToastContainer.cjs +2 -0
  133. package/dist/components/SToast/SToastContainer.cjs.map +1 -1
  134. package/dist/components/SToast/SToastContainer.js +2 -0
  135. package/dist/components/SToast/SToastContainer.js.map +1 -1
  136. package/dist/components/STooltip/STooltip.cjs +3 -1
  137. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  138. package/dist/components/STooltip/STooltip.js +3 -1
  139. package/dist/components/STooltip/STooltip.js.map +1 -1
  140. package/dist/index.cjs +522 -248
  141. package/dist/index.cjs.map +1 -1
  142. package/dist/index.d.ts +0 -1
  143. package/dist/index.js +523 -248
  144. package/dist/index.js.map +1 -1
  145. package/dist/lib/portal-container.d.ts +7 -0
  146. package/dist/styles.css +123 -58
  147. package/dist/theme.css +45 -21
  148. 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 = {
@@ -3152,7 +3157,7 @@ var STooltip = react.forwardRef(function STooltip2({
3152
3157
  if (disabled || body == null) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: triggerNode });
3153
3158
  const isHover = trigger === "hover";
3154
3159
  const contentClass = cn(
3155
- "z-50 max-w-xs rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
3160
+ "z-50 max-w-xs rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
3156
3161
  FLOATING_SLIDE_ANIM,
3157
3162
  className
3158
3163
  );
@@ -3424,6 +3429,7 @@ var SField = react.forwardRef(function SField2({
3424
3429
  name,
3425
3430
  label = "",
3426
3431
  icon,
3432
+ iconColor,
3427
3433
  labelTooltip = "",
3428
3434
  labelTooltipProps,
3429
3435
  labelWidth,
@@ -3501,7 +3507,15 @@ var SField = react.forwardRef(function SField2({
3501
3507
  lineHeight: `${s.lineHeight}px`
3502
3508
  },
3503
3509
  children: [
3504
- icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3510
+ icon && /* @__PURE__ */ jsxRuntime.jsx(
3511
+ SIcon,
3512
+ {
3513
+ name: icon,
3514
+ size: s.icon,
3515
+ color: iconColor,
3516
+ style: { marginRight: s.labelGap }
3517
+ }
3518
+ ),
3505
3519
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3506
3520
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
3507
3521
  STooltip,
@@ -3551,7 +3565,7 @@ var SField = react.forwardRef(function SField2({
3551
3565
  borderBottomLeftRadius: s.radius - 1
3552
3566
  },
3553
3567
  children: [
3554
- icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon }),
3568
+ icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
3555
3569
  effectiveAddonLabel
3556
3570
  ]
3557
3571
  }
@@ -3577,6 +3591,7 @@ var SInput = react.forwardRef(function SInput2({
3577
3591
  type = "text",
3578
3592
  size = "sm",
3579
3593
  icon,
3594
+ iconColor,
3580
3595
  prefix,
3581
3596
  suffix,
3582
3597
  clearable = false,
@@ -3630,6 +3645,7 @@ var SInput = react.forwardRef(function SInput2({
3630
3645
  labelTooltip,
3631
3646
  labelTooltipProps,
3632
3647
  icon,
3648
+ iconColor,
3633
3649
  size,
3634
3650
  hint,
3635
3651
  error: resolvedError,
@@ -3731,6 +3747,7 @@ var STextarea = react.forwardRef(function STextarea2({
3731
3747
  label,
3732
3748
  labelWidth,
3733
3749
  icon,
3750
+ iconColor,
3734
3751
  labelTooltip,
3735
3752
  labelTooltipProps,
3736
3753
  addonLabel,
@@ -3767,6 +3784,7 @@ var STextarea = react.forwardRef(function STextarea2({
3767
3784
  label,
3768
3785
  labelWidth,
3769
3786
  icon,
3787
+ iconColor,
3770
3788
  labelTooltip,
3771
3789
  labelTooltipProps,
3772
3790
  addonLabel,
@@ -3899,6 +3917,7 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
3899
3917
  label,
3900
3918
  labelWidth,
3901
3919
  icon,
3920
+ iconColor,
3902
3921
  labelTooltip,
3903
3922
  labelTooltipProps,
3904
3923
  addonLabel,
@@ -4057,6 +4076,7 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
4057
4076
  label,
4058
4077
  labelWidth,
4059
4078
  icon,
4079
+ iconColor,
4060
4080
  labelTooltip,
4061
4081
  labelTooltipProps,
4062
4082
  addonLabel,
@@ -4142,40 +4162,6 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
4142
4162
  );
4143
4163
  });
4144
4164
 
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
4165
  // src/lib/sanitize-inline-html.ts
4180
4166
  var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
4181
4167
  var ALLOWED_ATTRS_BY_TAG = {
@@ -4244,6 +4230,111 @@ var sanitizeInlineHtml = (value) => {
4244
4230
  Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
4245
4231
  return template.innerHTML;
4246
4232
  };
4233
+ function SPortal({
4234
+ open,
4235
+ onClose,
4236
+ onOpenChange,
4237
+ anchorRef,
4238
+ placement = "bottom",
4239
+ align = "start",
4240
+ offset = 4,
4241
+ alignOffset = 0,
4242
+ fit = false,
4243
+ maxHeight,
4244
+ autoClose = false,
4245
+ persistent = false,
4246
+ viewportPadding = 0,
4247
+ onOpenAutoFocus,
4248
+ onCloseAutoFocus,
4249
+ children,
4250
+ className,
4251
+ style,
4252
+ ariaLabel
4253
+ }) {
4254
+ const portalContainer = usePortalContainer();
4255
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4256
+ RPopover2__namespace.Root,
4257
+ {
4258
+ open,
4259
+ onOpenChange: (next) => {
4260
+ onOpenChange?.(next);
4261
+ if (!next) onClose?.();
4262
+ },
4263
+ children: [
4264
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { virtualRef: anchorRef }),
4265
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
4266
+ RPopover2__namespace.Content,
4267
+ {
4268
+ side: placement,
4269
+ align,
4270
+ sideOffset: offset,
4271
+ alignOffset,
4272
+ collisionPadding: viewportPadding,
4273
+ "aria-label": ariaLabel,
4274
+ onPointerDownOutside: (e) => {
4275
+ if (persistent || anchorRef.current?.contains(e.target)) {
4276
+ e.preventDefault();
4277
+ }
4278
+ },
4279
+ onFocusOutside: (e) => {
4280
+ if (persistent || anchorRef.current?.contains(e.target)) {
4281
+ e.preventDefault();
4282
+ }
4283
+ },
4284
+ onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
4285
+ onOpenAutoFocus,
4286
+ onCloseAutoFocus,
4287
+ onClick: autoClose ? () => onClose?.() : void 0,
4288
+ className: cn(
4289
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm",
4290
+ maxHeight != null && "overflow-y-auto",
4291
+ fit && "min-w-(--radix-popover-trigger-width)",
4292
+ FLOATING_SLIDE_ANIM,
4293
+ className
4294
+ ),
4295
+ style: { maxHeight, ...style },
4296
+ children
4297
+ }
4298
+ ) })
4299
+ ]
4300
+ }
4301
+ );
4302
+ }
4303
+ SPortal.displayName = "SPortal";
4304
+
4305
+ // src/components/SSelect/select.config.ts
4306
+ function flattenOptions(options, depth = 1) {
4307
+ const out = [];
4308
+ for (const o of options) {
4309
+ const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4310
+ if (hasChildren) {
4311
+ const childFlat = flattenOptions(o.children, depth + 1);
4312
+ const allChildrenDisabled = childFlat.every((c) => c.disabled);
4313
+ out.push({
4314
+ value: String(o.value),
4315
+ label: o.label,
4316
+ disabled: !!o.disabled || allChildrenDisabled,
4317
+ depth,
4318
+ isGroup: true,
4319
+ raw: o.value
4320
+ });
4321
+ out.push(...childFlat);
4322
+ } else {
4323
+ out.push({
4324
+ value: String(o.value),
4325
+ label: o.label,
4326
+ disabled: !!o.disabled,
4327
+ depth,
4328
+ isGroup: false,
4329
+ raw: o.value
4330
+ });
4331
+ }
4332
+ }
4333
+ return out;
4334
+ }
4335
+ var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
4336
+ var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
4337
+ var extractText = (html) => html.replace(/<[^>]*>/g, "");
4247
4338
  var SEARCH_THRESHOLD = 11;
4248
4339
  var SSelect = react.forwardRef(function SSelect2({
4249
4340
  value,
@@ -4267,6 +4358,7 @@ var SSelect = react.forwardRef(function SSelect2({
4267
4358
  label,
4268
4359
  labelWidth,
4269
4360
  icon,
4361
+ iconColor,
4270
4362
  labelTooltip,
4271
4363
  addonLabel,
4272
4364
  addonAlign,
@@ -4278,18 +4370,15 @@ var SSelect = react.forwardRef(function SSelect2({
4278
4370
  }, ref) {
4279
4371
  const multi = type === "multi" || type === "multi_depth";
4280
4372
  const portalContainer = usePortalContainer();
4373
+ const insideModal = useInsideModal();
4281
4374
  const [search, setSearch] = react.useState("");
4282
4375
  const [ruleError, setRuleError] = react.useState("");
4283
4376
  const [open, setOpen] = react.useState(false);
4377
+ const [pendingOpen, setPendingOpen] = react.useState(false);
4378
+ const [focusedIndex, setFocusedIndex] = react.useState(-1);
4284
4379
  const triggerRef = react.useRef(null);
4285
- react.useImperativeHandle(
4286
- ref,
4287
- () => ({
4288
- focus: () => triggerRef.current?.focus(),
4289
- open: () => setOpen(true)
4290
- }),
4291
- []
4292
- );
4380
+ const searchInputRef = react.useRef(null);
4381
+ const listRef = react.useRef(null);
4293
4382
  const flat = react.useMemo(() => flattenOptions(options), [options]);
4294
4383
  const selectable = react.useMemo(() => selectableOptions(flat), [flat]);
4295
4384
  const hasLeaf = react.useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -4307,6 +4396,46 @@ var SSelect = react.forwardRef(function SSelect2({
4307
4396
  const arr = Array.isArray(value) ? value : [value];
4308
4397
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
4309
4398
  }, [value]);
4399
+ const selectedRef = react.useRef(selected);
4400
+ selectedRef.current = selected;
4401
+ const emit = (next) => {
4402
+ if (!onValueChange) return;
4403
+ const mapped = next.map(
4404
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
4405
+ );
4406
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
4407
+ };
4408
+ const commitOpen = (next) => {
4409
+ setOpen(next);
4410
+ onOpenChange?.(next);
4411
+ if (!next) {
4412
+ setPendingOpen(false);
4413
+ setSearch("");
4414
+ if (rules && rules.length > 0) {
4415
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
4416
+ }
4417
+ }
4418
+ };
4419
+ const requestOpen = (next) => {
4420
+ if (next && insideModal && portalContainer == null) {
4421
+ setPendingOpen(true);
4422
+ return;
4423
+ }
4424
+ commitOpen(next);
4425
+ };
4426
+ react.useEffect(() => {
4427
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
4428
+ setPendingOpen(false);
4429
+ commitOpen(true);
4430
+ }, [insideModal, pendingOpen, portalContainer]);
4431
+ react.useImperativeHandle(
4432
+ ref,
4433
+ () => ({
4434
+ focus: () => triggerRef.current?.focus(),
4435
+ open: () => requestOpen(true)
4436
+ }),
4437
+ [insideModal, portalContainer]
4438
+ );
4310
4439
  useFormField({
4311
4440
  name,
4312
4441
  validate: () => {
@@ -4316,13 +4445,6 @@ var SSelect = react.forwardRef(function SSelect2({
4316
4445
  },
4317
4446
  resetValidation: () => setRuleError("")
4318
4447
  });
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
4448
  const filtered = react.useMemo(() => {
4327
4449
  if (!showSearch || search.trim() === "") return flat;
4328
4450
  const q = search.trim().toLowerCase();
@@ -4333,6 +4455,16 @@ var SSelect = react.forwardRef(function SSelect2({
4333
4455
  const allValues = selectable.map((o) => o.value);
4334
4456
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
4335
4457
  const hasItems = filtered.some((o) => !o.isGroup);
4458
+ const navigableItems = react.useMemo(() => {
4459
+ const items = [];
4460
+ if (multi && useSelectAll && allValues.length > 0) {
4461
+ items.push({ value: "__select_all__", isSelectAll: true });
4462
+ }
4463
+ filtered.forEach((o) => {
4464
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
4465
+ });
4466
+ return items;
4467
+ }, [allValues.length, filtered, multi, useSelectAll]);
4336
4468
  const triggerText = (() => {
4337
4469
  if (selected.length === 0) return placeholder;
4338
4470
  if (multi) {
@@ -4342,26 +4474,119 @@ var SSelect = react.forwardRef(function SSelect2({
4342
4474
  return labelByValue.get(selected[0]) ?? selected[0];
4343
4475
  })();
4344
4476
  const isPlaceholder = selected.length === 0;
4345
- const selectStore = /* @__PURE__ */ jsxRuntime.jsxs(
4346
- Ariakit__namespace.SelectProvider,
4477
+ const handleSelect = (nextValue) => {
4478
+ if (multi) {
4479
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
4480
+ emit(next);
4481
+ return;
4482
+ }
4483
+ emit([nextValue]);
4484
+ commitOpen(false);
4485
+ };
4486
+ const handleSelectAll = () => {
4487
+ emit(allSelected ? [] : allValues);
4488
+ };
4489
+ react.useEffect(() => {
4490
+ if (!open) {
4491
+ setFocusedIndex(-1);
4492
+ return;
4493
+ }
4494
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4495
+ }, [open, navigableItems.length]);
4496
+ react.useEffect(() => {
4497
+ if (!open) return;
4498
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4499
+ }, [search, open, navigableItems.length]);
4500
+ react.useEffect(() => {
4501
+ if (!open || focusedIndex < 0) return;
4502
+ const list = listRef.current;
4503
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
4504
+ if (!list || !focusedEl) return;
4505
+ const listRect = list.getBoundingClientRect();
4506
+ const itemRect = focusedEl.getBoundingClientRect();
4507
+ if (itemRect.top < listRect.top) {
4508
+ list.scrollTop += itemRect.top - listRect.top;
4509
+ } else if (itemRect.bottom > listRect.bottom) {
4510
+ list.scrollTop += itemRect.bottom - listRect.bottom;
4511
+ }
4512
+ }, [focusedIndex, open]);
4513
+ const moveFocusedIndex = (direction) => {
4514
+ if (!open || navigableItems.length === 0) return;
4515
+ setFocusedIndex((current) => {
4516
+ if (direction === "next") {
4517
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
4518
+ }
4519
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
4520
+ });
4521
+ };
4522
+ const selectFocusedItem = () => {
4523
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
4524
+ const focused = navigableItems[focusedIndex];
4525
+ if (focused.isSelectAll) {
4526
+ handleSelectAll();
4527
+ return;
4528
+ }
4529
+ handleSelect(focused.value);
4530
+ };
4531
+ const handleKeyDown = (event) => {
4532
+ if (!open) {
4533
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
4534
+ event.preventDefault();
4535
+ requestOpen(true);
4536
+ }
4537
+ return;
4538
+ }
4539
+ if (event.key === "ArrowDown") {
4540
+ event.preventDefault();
4541
+ moveFocusedIndex("next");
4542
+ return;
4543
+ }
4544
+ if (event.key === "ArrowUp") {
4545
+ event.preventDefault();
4546
+ moveFocusedIndex("prev");
4547
+ return;
4548
+ }
4549
+ if (event.key === "Enter") {
4550
+ event.preventDefault();
4551
+ selectFocusedItem();
4552
+ return;
4553
+ }
4554
+ if (event.key === "Escape") {
4555
+ event.preventDefault();
4556
+ commitOpen(false);
4557
+ }
4558
+ };
4559
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4560
+ SField,
4347
4561
  {
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
- },
4562
+ label,
4563
+ labelWidth,
4564
+ icon,
4565
+ iconColor,
4566
+ labelTooltip,
4567
+ labelTooltipProps,
4568
+ addonLabel,
4569
+ addonAlign,
4570
+ hint,
4571
+ error: error || ruleError !== "",
4572
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4573
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
4574
+ disabled: effectiveDisabled,
4575
+ width,
4576
+ className,
4577
+ style,
4359
4578
  children: [
4579
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
4360
4580
  /* @__PURE__ */ jsxRuntime.jsxs(
4361
- Ariakit__namespace.Select,
4581
+ "button",
4362
4582
  {
4363
4583
  ref: triggerRef,
4584
+ type: "button",
4364
4585
  disabled: effectiveDisabled,
4586
+ "aria-haspopup": "listbox",
4587
+ "aria-expanded": open,
4588
+ onClick: () => requestOpen(!open),
4589
+ onKeyDown: handleKeyDown,
4365
4590
  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
4591
  style: {
4367
4592
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -4386,174 +4611,229 @@ var SSelect = react.forwardRef(function SSelect2({
4386
4611
  ]
4387
4612
  }
4388
4613
  ),
4389
- /* @__PURE__ */ jsxRuntime.jsxs(
4390
- Ariakit__namespace.SelectPopover,
4614
+ /* @__PURE__ */ jsxRuntime.jsx(
4615
+ SPortal,
4391
4616
  {
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",
4617
+ open,
4618
+ onClose: () => commitOpen(false),
4619
+ onOpenChange: (next) => {
4620
+ if (next) requestOpen(true);
4621
+ },
4622
+ anchorRef: triggerRef,
4623
+ placement: "bottom",
4624
+ align: "start",
4625
+ offset: 4,
4626
+ fit: !dropdownWidth,
4627
+ onOpenAutoFocus: (event) => {
4628
+ if (!showSearch) {
4629
+ event.preventDefault();
4630
+ return;
4631
+ }
4632
+ event.preventDefault();
4633
+ requestAnimationFrame(() => searchInputRef.current?.focus());
4634
+ },
4635
+ onCloseAutoFocus: (event) => {
4636
+ event.preventDefault();
4637
+ triggerRef.current?.focus();
4638
+ },
4639
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] py-[4px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
4397
4640
  style: {
4398
- maxHeight: dropdownHeight,
4399
4641
  ...dropdownWidth ? { width: dropdownWidth } : {},
4400
4642
  maxWidth: maxDropdownWidth
4401
4643
  },
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
- ]
4644
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4645
+ "div",
4646
+ {
4647
+ ref: listRef,
4648
+ 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)]",
4649
+ style: { maxHeight: dropdownHeight },
4650
+ onMouseLeave: () => setFocusedIndex(-1),
4651
+ children: [
4652
+ 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: [
4653
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4654
+ /* @__PURE__ */ jsxRuntime.jsx(
4655
+ "input",
4656
+ {
4657
+ ref: searchInputRef,
4658
+ value: search,
4659
+ onChange: (e) => setSearch(e.target.value),
4660
+ onKeyDown: handleKeyDown,
4661
+ placeholder: "\uAC80\uC0C9",
4662
+ 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)]"
4663
+ }
4664
+ ),
4665
+ search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
4666
+ SGhostButton,
4667
+ {
4668
+ icon: "close",
4669
+ size: "xxs",
4670
+ intent: "default",
4671
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4672
+ onClick: () => setSearch(""),
4673
+ className: "shrink-0"
4674
+ }
4675
+ )
4676
+ ] }) }),
4677
+ hasItems ? /* @__PURE__ */ jsxRuntime.jsx(
4678
+ ItemList,
4679
+ {
4680
+ items: filtered,
4681
+ multi,
4682
+ selected,
4683
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
4684
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
4685
+ selectAllLabel: allSelectedLabel,
4686
+ selectAllSelected: allSelected,
4687
+ onSelectAll: handleSelectAll,
4688
+ onSelect: handleSelect,
4689
+ onHoverValue: (value2) => {
4690
+ const index = navigableItems.findIndex(
4691
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
4692
+ );
4693
+ if (index >= 0) setFocusedIndex(index);
4694
+ }
4695
+ }
4696
+ ) : /* @__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." })
4697
+ ]
4698
+ }
4699
+ )
4440
4700
  }
4441
4701
  )
4442
4702
  ]
4443
4703
  }
4444
4704
  );
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
4705
  });
4471
4706
  SSelect.displayName = "SSelect";
4472
4707
  function ItemList({
4473
4708
  items,
4474
4709
  multi,
4475
4710
  selected,
4476
- useSearch
4711
+ focusedValue,
4712
+ showSelectAll,
4713
+ selectAllLabel,
4714
+ selectAllSelected,
4715
+ onSelectAll,
4716
+ onSelect,
4717
+ onHoverValue
4477
4718
  }) {
4478
4719
  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",
4720
+ "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
4721
  "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)]"
4722
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4484
4723
  );
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 && "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,
4724
+ 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)]";
4725
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
4726
+ showSelectAll && /* @__PURE__ */ jsxRuntime.jsxs(
4727
+ "button",
4510
4728
  {
4511
- value: o.value,
4512
- disabled: o.disabled,
4729
+ type: "button",
4730
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
4731
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
4513
4732
  className: cn(
4514
4733
  itemBase,
4515
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4516
- activeClass
4734
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
4517
4735
  ),
4518
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4519
- render: useSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxItem, {}) : void 0,
4736
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
4737
+ onMouseEnter: () => onHoverValue("__select_all__"),
4738
+ onClick: onSelectAll,
4520
4739
  children: [
4521
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
4522
4740
  /* @__PURE__ */ jsxRuntime.jsx(
4523
4741
  "span",
4524
4742
  {
4525
4743
  className: cn(
4526
4744
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4527
- o.disabled ? (
4528
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4529
- "border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4530
- ) : cn(
4531
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4532
- 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"
4533
- )
4745
+ 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)]"
4534
4746
  ),
4535
- children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4747
+ children: selectAllSelected && /* @__PURE__ */ jsxRuntime.jsx(
4536
4748
  SIcon,
4537
4749
  {
4538
4750
  name: "checkboxCheck",
4539
4751
  size: 12,
4540
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4752
+ color: "var(--cmp-checkbox-checked-icon-default)"
4541
4753
  }
4542
4754
  )
4543
4755
  }
4544
4756
  ),
4545
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4546
- "span",
4547
- {
4548
- className: "flex-1 truncate",
4549
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4550
- }
4551
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4757
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
4552
4758
  ]
4553
- },
4554
- o.value
4555
- );
4556
- }) });
4759
+ }
4760
+ ),
4761
+ items.map((o) => {
4762
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4763
+ if (o.isGroup) {
4764
+ const isDepth1 = o.depth === 1;
4765
+ return /* @__PURE__ */ jsxRuntime.jsx(
4766
+ "div",
4767
+ {
4768
+ className: cn(
4769
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4770
+ 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",
4771
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4772
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4773
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4774
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4775
+ ),
4776
+ style: { paddingLeft: padLeft },
4777
+ children: o.label
4778
+ },
4779
+ o.value
4780
+ );
4781
+ }
4782
+ const isSelected = selected.includes(o.value);
4783
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4784
+ "button",
4785
+ {
4786
+ type: "button",
4787
+ disabled: o.disabled,
4788
+ role: "option",
4789
+ "aria-selected": isSelected,
4790
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
4791
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
4792
+ className: cn(
4793
+ itemBase,
4794
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4795
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
4796
+ activeClass
4797
+ ),
4798
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4799
+ onMouseEnter: () => onHoverValue(o.value),
4800
+ onClick: () => onSelect(o.value),
4801
+ children: [
4802
+ multi && /* @__PURE__ */ jsxRuntime.jsx(
4803
+ "span",
4804
+ {
4805
+ className: cn(
4806
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4807
+ o.disabled ? (
4808
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4809
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4810
+ ) : cn(
4811
+ 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)]"
4812
+ )
4813
+ ),
4814
+ children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4815
+ SIcon,
4816
+ {
4817
+ name: "checkboxCheck",
4818
+ size: 12,
4819
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4820
+ }
4821
+ )
4822
+ }
4823
+ ),
4824
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4825
+ "span",
4826
+ {
4827
+ className: "flex-1 truncate",
4828
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4829
+ }
4830
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4831
+ ]
4832
+ },
4833
+ o.value
4834
+ );
4835
+ })
4836
+ ] });
4557
4837
  }
4558
4838
  function SRadio({
4559
4839
  val,
@@ -4661,7 +4941,7 @@ function SRadioGroup({
4661
4941
  role: "radiogroup",
4662
4942
  className: cn(
4663
4943
  "inline-flex",
4664
- direction === "vertical" ? "flex-col gap-[8px]" : "flex-row gap-[20px]",
4944
+ direction === "vertical" ? "flex-col gap-[var(--cmp-radio-group-gap-vertical)]" : "flex-row gap-[var(--cmp-radio-group-gap-horizontal)]",
4665
4945
  className
4666
4946
  ),
4667
4947
  style,
@@ -5016,6 +5296,7 @@ function SDatePicker({
5016
5296
  label,
5017
5297
  labelWidth,
5018
5298
  icon,
5299
+ iconColor,
5019
5300
  labelTooltip,
5020
5301
  labelTooltipProps,
5021
5302
  addonLabel,
@@ -5054,6 +5335,7 @@ function SDatePicker({
5054
5335
  label,
5055
5336
  labelWidth,
5056
5337
  icon,
5338
+ iconColor,
5057
5339
  labelTooltip,
5058
5340
  labelTooltipProps,
5059
5341
  addonLabel,
@@ -5322,6 +5604,7 @@ function SDateRangePicker({
5322
5604
  label,
5323
5605
  labelWidth,
5324
5606
  icon,
5607
+ iconColor,
5325
5608
  labelTooltip,
5326
5609
  labelTooltipProps,
5327
5610
  addonLabel,
@@ -5364,6 +5647,7 @@ function SDateRangePicker({
5364
5647
  label,
5365
5648
  labelWidth,
5366
5649
  icon,
5650
+ iconColor,
5367
5651
  labelTooltip,
5368
5652
  labelTooltipProps,
5369
5653
  addonLabel,
@@ -5625,6 +5909,7 @@ function SFilePicker({
5625
5909
  label,
5626
5910
  labelWidth,
5627
5911
  icon,
5912
+ iconColor,
5628
5913
  labelTooltip,
5629
5914
  labelTooltipProps,
5630
5915
  addonLabel,
@@ -5639,7 +5924,10 @@ function SFilePicker({
5639
5924
  const autoId = react.useId();
5640
5925
  const resolvedName = name ?? autoId;
5641
5926
  const inputRef = react.useRef(null);
5927
+ const contentBoxRef = react.useRef(null);
5642
5928
  const [ruleError, setRuleError] = react.useState("");
5929
+ const [listOpen, setListOpen] = react.useState(false);
5930
+ const portalContainer = usePortalContainer();
5643
5931
  const files = multiple ? Array.isArray(value) ? value : [] : value && !Array.isArray(value) ? value : null;
5644
5932
  const hasFiles = multiple ? files.length > 0 : !!files;
5645
5933
  const displayText = !hasFiles ? placeholder : multiple ? files.map((f) => f.name).join(", ") : files.name;
@@ -5693,10 +5981,12 @@ function SFilePicker({
5693
5981
  e.stopPropagation();
5694
5982
  if (disabled) return;
5695
5983
  onValueChange?.(multiple ? [] : null);
5984
+ setListOpen(false);
5696
5985
  };
5697
5986
  const removeChip = (i) => {
5698
5987
  const next = files.filter((_, idx) => idx !== i);
5699
5988
  onValueChange?.(next);
5989
+ if (next.length === 0) setListOpen(false);
5700
5990
  };
5701
5991
  const open = () => {
5702
5992
  if (!disabled) inputRef.current?.click();
@@ -5707,9 +5997,12 @@ function SFilePicker({
5707
5997
  const iconSize = size === "md" ? 20 : 16;
5708
5998
  const gapCls = size === "md" ? "gap-[var(--cmp-filepicker-md-gap)]" : "gap-[var(--cmp-filepicker-sm-gap)]";
5709
5999
  const inlinePlaceholder = inline && !disabled && !hasFiles;
5710
- const content = /* @__PURE__ */ jsxRuntime.jsxs(
6000
+ const showListbox = multiple && useMultipleListBox;
6001
+ const filesArr = multiple ? files : [];
6002
+ const contentInner = /* @__PURE__ */ jsxRuntime.jsxs(
5711
6003
  "div",
5712
6004
  {
6005
+ ref: contentBoxRef,
5713
6006
  className: cn("group/filepicker flex h-full w-full items-center", gapCls),
5714
6007
  style: { padding: inline ? 0 : `0 ${padX}` },
5715
6008
  children: [
@@ -5764,25 +6057,34 @@ function SFilePicker({
5764
6057
  ]
5765
6058
  }
5766
6059
  ),
6060
+ showListbox && hasFiles && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
6061
+ Ariakit__namespace.PopoverDisclosure,
6062
+ {
6063
+ render: /* @__PURE__ */ jsxRuntime.jsx(SGhostButton, { icon: "caretDown", size: "xxs", ariaLabel: "toggle file list" }),
6064
+ className: cn("shrink-0 transition-transform duration-150", listOpen && "rotate-180")
6065
+ }
6066
+ ),
5767
6067
  !disabled && hasFiles && /* @__PURE__ */ jsxRuntime.jsx(SGhostButton, { icon: "close", size: "xxs", ariaLabel: "clear", onClick: clear })
5768
6068
  ]
5769
6069
  }
5770
6070
  );
5771
- const chips = multiple && files.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
5772
- "div",
5773
- {
5774
- className: cn(
5775
- "mt-[8px] flex gap-[8px]",
5776
- useMultipleListBox ? "box-border flex-col items-start rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] p-[8px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]" : "flex-wrap"
5777
- ),
5778
- style: useMultipleListBox ? {
5779
- width: typeof width === "number" ? `${width}px` : width,
5780
- // label 있으면 SField 입력 박스 시작 위치(labelWidth + fieldGap) 맞춰 정렬
5781
- marginLeft: label && labelWidth != null ? `calc(${typeof labelWidth === "number" ? `${labelWidth}px` : labelWidth} + var(--sys-space-field-${size}-gap))` : void 0
5782
- } : void 0,
5783
- children: files.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i))
5784
- }
5785
- );
6071
+ const content = showListbox ? /* @__PURE__ */ jsxRuntime.jsxs(Ariakit__namespace.PopoverProvider, { open: listOpen, setOpen: setListOpen, placement: "bottom-start", children: [
6072
+ contentInner,
6073
+ /* @__PURE__ */ jsxRuntime.jsx(
6074
+ Ariakit__namespace.Popover,
6075
+ {
6076
+ portal: true,
6077
+ portalElement: portalContainer,
6078
+ gutter: 4,
6079
+ unmountOnHide: true,
6080
+ getAnchorRect: () => contentBoxRef.current?.getBoundingClientRect() ?? null,
6081
+ style: { minWidth: contentBoxRef.current?.offsetWidth },
6082
+ 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)]",
6083
+ children: filesArr.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i))
6084
+ }
6085
+ )
6086
+ ] }) : contentInner;
6087
+ const chips = multiple && !useMultipleListBox && filesArr.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-[8px] flex flex-wrap gap-[8px]", children: filesArr.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i)) });
5786
6088
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style, children: [
5787
6089
  /* @__PURE__ */ jsxRuntime.jsx(
5788
6090
  SField,
@@ -5791,6 +6093,7 @@ function SFilePicker({
5791
6093
  label,
5792
6094
  labelWidth,
5793
6095
  icon,
6096
+ iconColor,
5794
6097
  labelTooltip,
5795
6098
  labelTooltipProps,
5796
6099
  addonLabel,
@@ -5813,21 +6116,26 @@ function SFilePicker({
5813
6116
  var cssLen2 = (v) => v == null ? void 0 : typeof v === "number" ? `${v}px` : v;
5814
6117
  function SKeyValueTable({
5815
6118
  fields = [],
6119
+ values = {},
5816
6120
  search = false,
5817
6121
  radius = "default",
5818
- dense = false,
5819
6122
  onChange,
5820
6123
  onSearch,
5821
6124
  className,
5822
6125
  style
5823
6126
  }) {
5824
6127
  if (fields.length === 0) return null;
5825
- const emit = (name, value) => onChange?.({ name, value });
5826
- const cellHeight = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-keyValue-height)";
5827
- const cellPadY = dense ? "var(--cmp-table-body-dense-paddingY)" : "var(--cmp-table-keyValue-body-paddingY)";
6128
+ const emit = (name, value) => onChange?.({
6129
+ name,
6130
+ value,
6131
+ values: { ...values, [name]: value }
6132
+ });
6133
+ const cellHeight = "var(--cmp-table-keyValue-height)";
6134
+ const cellPadY = "var(--cmp-table-keyValue-body-paddingY)";
5828
6135
  const renderField = (f) => {
5829
6136
  if (f.render) return f.render;
5830
- const opt = f.options ?? {};
6137
+ const opt = { ...f.options ?? {} };
6138
+ if (Object.prototype.hasOwnProperty.call(values, f.name)) opt.value = values[f.name];
5831
6139
  const up = (v) => emit(f.name, v);
5832
6140
  switch (f.type) {
5833
6141
  case "input":
@@ -5939,7 +6247,10 @@ function SKeyValueTable({
5939
6247
  rowSpan: f.tdRowSpan && f.tdRowSpan > 1 ? f.tdRowSpan : void 0,
5940
6248
  colSpan: f.tdColSpan && f.tdColSpan > 1 ? f.tdColSpan : void 0,
5941
6249
  className: cn(
5942
- "box-border bg-[var(--sys-color-bg-frame)] px-[var(--cmp-table-keyValue-body-paddingX-default)] align-middle",
6250
+ "box-border bg-[var(--sys-color-bg-frame)] align-middle",
6251
+ // text 타입(및 미지정 기본값)은 읽기 전용 콘텐츠라 넓은 paddingX(wide),
6252
+ // 폼 필드 셀은 default paddingX 유지
6253
+ f.type == null || f.type === "text" ? "px-[var(--cmp-table-keyValue-body-paddingX-wide)]" : "px-[var(--cmp-table-keyValue-body-paddingX-default)]",
5943
6254
  cellBorder,
5944
6255
  f.tdClass
5945
6256
  ),