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
@@ -4,8 +4,8 @@ import { clsx } from 'clsx';
4
4
  import { extendTailwindMerge } from 'tailwind-merge';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import * as RPopover2 from '@radix-ui/react-popover';
7
- import * as Ariakit from '@ariakit/react';
8
7
  import * as RCheckbox from '@radix-ui/react-checkbox';
8
+ import * as Ariakit from '@ariakit/react';
9
9
 
10
10
  var twMerge = extendTailwindMerge({
11
11
  extend: {
@@ -2873,10 +2873,15 @@ var FLOATING_SLIDE_ANIM = [
2873
2873
  "data-[state=closed]:data-[side=right]:animate-floating-out-right"
2874
2874
  ].join(" ");
2875
2875
  var PortalContainerContext = createContext(null);
2876
+ var InsideModalContext = createContext(false);
2876
2877
  PortalContainerContext.Provider;
2878
+ InsideModalContext.Provider;
2877
2879
  function usePortalContainer() {
2878
2880
  return useContext(PortalContainerContext) ?? void 0;
2879
2881
  }
2882
+ function useInsideModal() {
2883
+ return useContext(InsideModalContext);
2884
+ }
2880
2885
 
2881
2886
  // src/components/STag/tag.config.ts
2882
2887
  var TAG_SIZE_CONFIG = {
@@ -3128,7 +3133,7 @@ var STooltip = forwardRef(function STooltip2({
3128
3133
  if (disabled || body == null) return /* @__PURE__ */ jsx(Fragment, { children: triggerNode });
3129
3134
  const isHover = trigger === "hover";
3130
3135
  const contentClass = cn(
3131
- "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)]",
3136
+ "z-50 max-w-xs rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
3132
3137
  FLOATING_SLIDE_ANIM,
3133
3138
  className
3134
3139
  );
@@ -3400,6 +3405,7 @@ var SField = forwardRef(function SField2({
3400
3405
  name,
3401
3406
  label = "",
3402
3407
  icon,
3408
+ iconColor,
3403
3409
  labelTooltip = "",
3404
3410
  labelTooltipProps,
3405
3411
  labelWidth,
@@ -3477,7 +3483,15 @@ var SField = forwardRef(function SField2({
3477
3483
  lineHeight: `${s.lineHeight}px`
3478
3484
  },
3479
3485
  children: [
3480
- icon && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3486
+ icon && /* @__PURE__ */ jsx(
3487
+ SIcon,
3488
+ {
3489
+ name: icon,
3490
+ size: s.icon,
3491
+ color: iconColor,
3492
+ style: { marginRight: s.labelGap }
3493
+ }
3494
+ ),
3481
3495
  /* @__PURE__ */ jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3482
3496
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsx(
3483
3497
  STooltip,
@@ -3527,7 +3541,7 @@ var SField = forwardRef(function SField2({
3527
3541
  borderBottomLeftRadius: s.radius - 1
3528
3542
  },
3529
3543
  children: [
3530
- icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon }),
3544
+ icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
3531
3545
  effectiveAddonLabel
3532
3546
  ]
3533
3547
  }
@@ -3553,6 +3567,7 @@ var SInput = forwardRef(function SInput2({
3553
3567
  type = "text",
3554
3568
  size = "sm",
3555
3569
  icon,
3570
+ iconColor,
3556
3571
  prefix,
3557
3572
  suffix,
3558
3573
  clearable = false,
@@ -3606,6 +3621,7 @@ var SInput = forwardRef(function SInput2({
3606
3621
  labelTooltip,
3607
3622
  labelTooltipProps,
3608
3623
  icon,
3624
+ iconColor,
3609
3625
  size,
3610
3626
  hint,
3611
3627
  error: resolvedError,
@@ -3707,6 +3723,7 @@ var STextarea = forwardRef(function STextarea2({
3707
3723
  label,
3708
3724
  labelWidth,
3709
3725
  icon,
3726
+ iconColor,
3710
3727
  labelTooltip,
3711
3728
  labelTooltipProps,
3712
3729
  addonLabel,
@@ -3743,6 +3760,7 @@ var STextarea = forwardRef(function STextarea2({
3743
3760
  label,
3744
3761
  labelWidth,
3745
3762
  icon,
3763
+ iconColor,
3746
3764
  labelTooltip,
3747
3765
  labelTooltipProps,
3748
3766
  addonLabel,
@@ -3875,6 +3893,7 @@ var SNumberInput = forwardRef(function SNumberInput2({
3875
3893
  label,
3876
3894
  labelWidth,
3877
3895
  icon,
3896
+ iconColor,
3878
3897
  labelTooltip,
3879
3898
  labelTooltipProps,
3880
3899
  addonLabel,
@@ -4033,6 +4052,7 @@ var SNumberInput = forwardRef(function SNumberInput2({
4033
4052
  label,
4034
4053
  labelWidth,
4035
4054
  icon,
4055
+ iconColor,
4036
4056
  labelTooltip,
4037
4057
  labelTooltipProps,
4038
4058
  addonLabel,
@@ -4118,40 +4138,6 @@ var SNumberInput = forwardRef(function SNumberInput2({
4118
4138
  );
4119
4139
  });
4120
4140
 
4121
- // src/components/SSelect/select.config.ts
4122
- function flattenOptions(options, depth = 1) {
4123
- const out = [];
4124
- for (const o of options) {
4125
- const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4126
- if (hasChildren) {
4127
- const childFlat = flattenOptions(o.children, depth + 1);
4128
- const allChildrenDisabled = childFlat.every((c) => c.disabled);
4129
- out.push({
4130
- value: String(o.value),
4131
- label: o.label,
4132
- disabled: !!o.disabled || allChildrenDisabled,
4133
- depth,
4134
- isGroup: true,
4135
- raw: o.value
4136
- });
4137
- out.push(...childFlat);
4138
- } else {
4139
- out.push({
4140
- value: String(o.value),
4141
- label: o.label,
4142
- disabled: !!o.disabled,
4143
- depth,
4144
- isGroup: false,
4145
- raw: o.value
4146
- });
4147
- }
4148
- }
4149
- return out;
4150
- }
4151
- var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
4152
- var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
4153
- var extractText = (html) => html.replace(/<[^>]*>/g, "");
4154
-
4155
4141
  // src/lib/sanitize-inline-html.ts
4156
4142
  var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
4157
4143
  var ALLOWED_ATTRS_BY_TAG = {
@@ -4220,6 +4206,111 @@ var sanitizeInlineHtml = (value) => {
4220
4206
  Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
4221
4207
  return template.innerHTML;
4222
4208
  };
4209
+ function SPortal({
4210
+ open,
4211
+ onClose,
4212
+ onOpenChange,
4213
+ anchorRef,
4214
+ placement = "bottom",
4215
+ align = "start",
4216
+ offset = 4,
4217
+ alignOffset = 0,
4218
+ fit = false,
4219
+ maxHeight,
4220
+ autoClose = false,
4221
+ persistent = false,
4222
+ viewportPadding = 0,
4223
+ onOpenAutoFocus,
4224
+ onCloseAutoFocus,
4225
+ children,
4226
+ className,
4227
+ style,
4228
+ ariaLabel
4229
+ }) {
4230
+ const portalContainer = usePortalContainer();
4231
+ return /* @__PURE__ */ jsxs(
4232
+ RPopover2.Root,
4233
+ {
4234
+ open,
4235
+ onOpenChange: (next) => {
4236
+ onOpenChange?.(next);
4237
+ if (!next) onClose?.();
4238
+ },
4239
+ children: [
4240
+ /* @__PURE__ */ jsx(RPopover2.Anchor, { virtualRef: anchorRef }),
4241
+ /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
4242
+ RPopover2.Content,
4243
+ {
4244
+ side: placement,
4245
+ align,
4246
+ sideOffset: offset,
4247
+ alignOffset,
4248
+ collisionPadding: viewportPadding,
4249
+ "aria-label": ariaLabel,
4250
+ onPointerDownOutside: (e) => {
4251
+ if (persistent || anchorRef.current?.contains(e.target)) {
4252
+ e.preventDefault();
4253
+ }
4254
+ },
4255
+ onFocusOutside: (e) => {
4256
+ if (persistent || anchorRef.current?.contains(e.target)) {
4257
+ e.preventDefault();
4258
+ }
4259
+ },
4260
+ onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
4261
+ onOpenAutoFocus,
4262
+ onCloseAutoFocus,
4263
+ onClick: autoClose ? () => onClose?.() : void 0,
4264
+ className: cn(
4265
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm",
4266
+ maxHeight != null && "overflow-y-auto",
4267
+ fit && "min-w-(--radix-popover-trigger-width)",
4268
+ FLOATING_SLIDE_ANIM,
4269
+ className
4270
+ ),
4271
+ style: { maxHeight, ...style },
4272
+ children
4273
+ }
4274
+ ) })
4275
+ ]
4276
+ }
4277
+ );
4278
+ }
4279
+ SPortal.displayName = "SPortal";
4280
+
4281
+ // src/components/SSelect/select.config.ts
4282
+ function flattenOptions(options, depth = 1) {
4283
+ const out = [];
4284
+ for (const o of options) {
4285
+ const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4286
+ if (hasChildren) {
4287
+ const childFlat = flattenOptions(o.children, depth + 1);
4288
+ const allChildrenDisabled = childFlat.every((c) => c.disabled);
4289
+ out.push({
4290
+ value: String(o.value),
4291
+ label: o.label,
4292
+ disabled: !!o.disabled || allChildrenDisabled,
4293
+ depth,
4294
+ isGroup: true,
4295
+ raw: o.value
4296
+ });
4297
+ out.push(...childFlat);
4298
+ } else {
4299
+ out.push({
4300
+ value: String(o.value),
4301
+ label: o.label,
4302
+ disabled: !!o.disabled,
4303
+ depth,
4304
+ isGroup: false,
4305
+ raw: o.value
4306
+ });
4307
+ }
4308
+ }
4309
+ return out;
4310
+ }
4311
+ var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
4312
+ var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
4313
+ var extractText = (html) => html.replace(/<[^>]*>/g, "");
4223
4314
  var SEARCH_THRESHOLD = 11;
4224
4315
  var SSelect = forwardRef(function SSelect2({
4225
4316
  value,
@@ -4243,6 +4334,7 @@ var SSelect = forwardRef(function SSelect2({
4243
4334
  label,
4244
4335
  labelWidth,
4245
4336
  icon,
4337
+ iconColor,
4246
4338
  labelTooltip,
4247
4339
  addonLabel,
4248
4340
  addonAlign,
@@ -4254,18 +4346,15 @@ var SSelect = forwardRef(function SSelect2({
4254
4346
  }, ref) {
4255
4347
  const multi = type === "multi" || type === "multi_depth";
4256
4348
  const portalContainer = usePortalContainer();
4349
+ const insideModal = useInsideModal();
4257
4350
  const [search, setSearch] = useState("");
4258
4351
  const [ruleError, setRuleError] = useState("");
4259
4352
  const [open, setOpen] = useState(false);
4353
+ const [pendingOpen, setPendingOpen] = useState(false);
4354
+ const [focusedIndex, setFocusedIndex] = useState(-1);
4260
4355
  const triggerRef = useRef(null);
4261
- useImperativeHandle(
4262
- ref,
4263
- () => ({
4264
- focus: () => triggerRef.current?.focus(),
4265
- open: () => setOpen(true)
4266
- }),
4267
- []
4268
- );
4356
+ const searchInputRef = useRef(null);
4357
+ const listRef = useRef(null);
4269
4358
  const flat = useMemo(() => flattenOptions(options), [options]);
4270
4359
  const selectable = useMemo(() => selectableOptions(flat), [flat]);
4271
4360
  const hasLeaf = useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -4283,6 +4372,46 @@ var SSelect = forwardRef(function SSelect2({
4283
4372
  const arr = Array.isArray(value) ? value : [value];
4284
4373
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
4285
4374
  }, [value]);
4375
+ const selectedRef = useRef(selected);
4376
+ selectedRef.current = selected;
4377
+ const emit = (next) => {
4378
+ if (!onValueChange) return;
4379
+ const mapped = next.map(
4380
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
4381
+ );
4382
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
4383
+ };
4384
+ const commitOpen = (next) => {
4385
+ setOpen(next);
4386
+ onOpenChange?.(next);
4387
+ if (!next) {
4388
+ setPendingOpen(false);
4389
+ setSearch("");
4390
+ if (rules && rules.length > 0) {
4391
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
4392
+ }
4393
+ }
4394
+ };
4395
+ const requestOpen = (next) => {
4396
+ if (next && insideModal && portalContainer == null) {
4397
+ setPendingOpen(true);
4398
+ return;
4399
+ }
4400
+ commitOpen(next);
4401
+ };
4402
+ useEffect(() => {
4403
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
4404
+ setPendingOpen(false);
4405
+ commitOpen(true);
4406
+ }, [insideModal, pendingOpen, portalContainer]);
4407
+ useImperativeHandle(
4408
+ ref,
4409
+ () => ({
4410
+ focus: () => triggerRef.current?.focus(),
4411
+ open: () => requestOpen(true)
4412
+ }),
4413
+ [insideModal, portalContainer]
4414
+ );
4286
4415
  useFormField({
4287
4416
  name,
4288
4417
  validate: () => {
@@ -4292,13 +4421,6 @@ var SSelect = forwardRef(function SSelect2({
4292
4421
  },
4293
4422
  resetValidation: () => setRuleError("")
4294
4423
  });
4295
- const emit = (next) => {
4296
- if (!onValueChange) return;
4297
- const mapped = next.map(
4298
- (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
4299
- );
4300
- onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
4301
- };
4302
4424
  const filtered = useMemo(() => {
4303
4425
  if (!showSearch || search.trim() === "") return flat;
4304
4426
  const q = search.trim().toLowerCase();
@@ -4309,6 +4431,16 @@ var SSelect = forwardRef(function SSelect2({
4309
4431
  const allValues = selectable.map((o) => o.value);
4310
4432
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
4311
4433
  const hasItems = filtered.some((o) => !o.isGroup);
4434
+ const navigableItems = useMemo(() => {
4435
+ const items = [];
4436
+ if (multi && useSelectAll && allValues.length > 0) {
4437
+ items.push({ value: "__select_all__", isSelectAll: true });
4438
+ }
4439
+ filtered.forEach((o) => {
4440
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
4441
+ });
4442
+ return items;
4443
+ }, [allValues.length, filtered, multi, useSelectAll]);
4312
4444
  const triggerText = (() => {
4313
4445
  if (selected.length === 0) return placeholder;
4314
4446
  if (multi) {
@@ -4318,26 +4450,119 @@ var SSelect = forwardRef(function SSelect2({
4318
4450
  return labelByValue.get(selected[0]) ?? selected[0];
4319
4451
  })();
4320
4452
  const isPlaceholder = selected.length === 0;
4321
- const selectStore = /* @__PURE__ */ jsxs(
4322
- Ariakit.SelectProvider,
4453
+ const handleSelect = (nextValue) => {
4454
+ if (multi) {
4455
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
4456
+ emit(next);
4457
+ return;
4458
+ }
4459
+ emit([nextValue]);
4460
+ commitOpen(false);
4461
+ };
4462
+ const handleSelectAll = () => {
4463
+ emit(allSelected ? [] : allValues);
4464
+ };
4465
+ useEffect(() => {
4466
+ if (!open) {
4467
+ setFocusedIndex(-1);
4468
+ return;
4469
+ }
4470
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4471
+ }, [open, navigableItems.length]);
4472
+ useEffect(() => {
4473
+ if (!open) return;
4474
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4475
+ }, [search, open, navigableItems.length]);
4476
+ useEffect(() => {
4477
+ if (!open || focusedIndex < 0) return;
4478
+ const list = listRef.current;
4479
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
4480
+ if (!list || !focusedEl) return;
4481
+ const listRect = list.getBoundingClientRect();
4482
+ const itemRect = focusedEl.getBoundingClientRect();
4483
+ if (itemRect.top < listRect.top) {
4484
+ list.scrollTop += itemRect.top - listRect.top;
4485
+ } else if (itemRect.bottom > listRect.bottom) {
4486
+ list.scrollTop += itemRect.bottom - listRect.bottom;
4487
+ }
4488
+ }, [focusedIndex, open]);
4489
+ const moveFocusedIndex = (direction) => {
4490
+ if (!open || navigableItems.length === 0) return;
4491
+ setFocusedIndex((current) => {
4492
+ if (direction === "next") {
4493
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
4494
+ }
4495
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
4496
+ });
4497
+ };
4498
+ const selectFocusedItem = () => {
4499
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
4500
+ const focused = navigableItems[focusedIndex];
4501
+ if (focused.isSelectAll) {
4502
+ handleSelectAll();
4503
+ return;
4504
+ }
4505
+ handleSelect(focused.value);
4506
+ };
4507
+ const handleKeyDown = (event) => {
4508
+ if (!open) {
4509
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
4510
+ event.preventDefault();
4511
+ requestOpen(true);
4512
+ }
4513
+ return;
4514
+ }
4515
+ if (event.key === "ArrowDown") {
4516
+ event.preventDefault();
4517
+ moveFocusedIndex("next");
4518
+ return;
4519
+ }
4520
+ if (event.key === "ArrowUp") {
4521
+ event.preventDefault();
4522
+ moveFocusedIndex("prev");
4523
+ return;
4524
+ }
4525
+ if (event.key === "Enter") {
4526
+ event.preventDefault();
4527
+ selectFocusedItem();
4528
+ return;
4529
+ }
4530
+ if (event.key === "Escape") {
4531
+ event.preventDefault();
4532
+ commitOpen(false);
4533
+ }
4534
+ };
4535
+ return /* @__PURE__ */ jsxs(
4536
+ SField,
4323
4537
  {
4324
- animated: portalContainer == null ? 150 : false,
4325
- value: multi ? selected : selected[0] ?? "",
4326
- setValue: (v) => emit(Array.isArray(v) ? v : v === "" ? [] : [v]),
4327
- open,
4328
- setOpen: (o) => {
4329
- setOpen(o);
4330
- onOpenChange?.(o);
4331
- if (!o && rules && rules.length > 0) {
4332
- setRuleError(runRules(multi ? selected : selected[0] ?? "", rules));
4333
- }
4334
- },
4538
+ label,
4539
+ labelWidth,
4540
+ icon,
4541
+ iconColor,
4542
+ labelTooltip,
4543
+ labelTooltipProps,
4544
+ addonLabel,
4545
+ addonAlign,
4546
+ hint,
4547
+ error: error || ruleError !== "",
4548
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4549
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
4550
+ disabled: effectiveDisabled,
4551
+ width,
4552
+ className,
4553
+ style,
4335
4554
  children: [
4555
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected.join(",") }),
4336
4556
  /* @__PURE__ */ jsxs(
4337
- Ariakit.Select,
4557
+ "button",
4338
4558
  {
4339
4559
  ref: triggerRef,
4560
+ type: "button",
4340
4561
  disabled: effectiveDisabled,
4562
+ "aria-haspopup": "listbox",
4563
+ "aria-expanded": open,
4564
+ onClick: () => requestOpen(!open),
4565
+ onKeyDown: handleKeyDown,
4341
4566
  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",
4342
4567
  style: {
4343
4568
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -4362,174 +4587,229 @@ var SSelect = forwardRef(function SSelect2({
4362
4587
  ]
4363
4588
  }
4364
4589
  ),
4365
- /* @__PURE__ */ jsxs(
4366
- Ariakit.SelectPopover,
4590
+ /* @__PURE__ */ jsx(
4591
+ SPortal,
4367
4592
  {
4368
- portal: true,
4369
- portalElement: portalContainer,
4370
- gutter: 4,
4371
- sameWidth: !dropdownWidth,
4372
- 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",
4593
+ open,
4594
+ onClose: () => commitOpen(false),
4595
+ onOpenChange: (next) => {
4596
+ if (next) requestOpen(true);
4597
+ },
4598
+ anchorRef: triggerRef,
4599
+ placement: "bottom",
4600
+ align: "start",
4601
+ offset: 4,
4602
+ fit: !dropdownWidth,
4603
+ onOpenAutoFocus: (event) => {
4604
+ if (!showSearch) {
4605
+ event.preventDefault();
4606
+ return;
4607
+ }
4608
+ event.preventDefault();
4609
+ requestAnimationFrame(() => searchInputRef.current?.focus());
4610
+ },
4611
+ onCloseAutoFocus: (event) => {
4612
+ event.preventDefault();
4613
+ triggerRef.current?.focus();
4614
+ },
4615
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] py-[4px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
4373
4616
  style: {
4374
- maxHeight: dropdownHeight,
4375
4617
  ...dropdownWidth ? { width: dropdownWidth } : {},
4376
4618
  maxWidth: maxDropdownWidth
4377
4619
  },
4378
- children: [
4379
- showSearch && // 원본 sd-select-list-item-search: sticky + 검색 아이콘 + 테두리 input + focus-within + clear
4380
- /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ 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: [
4381
- /* @__PURE__ */ jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4382
- /* @__PURE__ */ jsx(
4383
- Ariakit.Combobox,
4384
- {
4385
- autoSelect: true,
4386
- placeholder: "\uAC80\uC0C9",
4387
- 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)]"
4388
- }
4389
- ),
4390
- search !== "" && /* @__PURE__ */ jsx(
4391
- SGhostButton,
4392
- {
4393
- icon: "close",
4394
- size: "xxs",
4395
- intent: "default",
4396
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4397
- onClick: () => setSearch(""),
4398
- className: "shrink-0"
4399
- }
4400
- )
4401
- ] }) }),
4402
- multi && useSelectAll && /* @__PURE__ */ jsx(
4403
- "button",
4404
- {
4405
- type: "button",
4406
- 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)]",
4407
- onClick: () => emit(allSelected ? [] : allValues),
4408
- children: allSelectedLabel
4409
- }
4410
- ),
4411
- hasItems ? /* @__PURE__ */ jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
4412
- // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
4413
- /* @__PURE__ */ 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." })
4414
- )
4415
- ]
4620
+ children: /* @__PURE__ */ jsxs(
4621
+ "div",
4622
+ {
4623
+ ref: listRef,
4624
+ 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)]",
4625
+ style: { maxHeight: dropdownHeight },
4626
+ onMouseLeave: () => setFocusedIndex(-1),
4627
+ children: [
4628
+ showSearch && /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ 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: [
4629
+ /* @__PURE__ */ jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4630
+ /* @__PURE__ */ jsx(
4631
+ "input",
4632
+ {
4633
+ ref: searchInputRef,
4634
+ value: search,
4635
+ onChange: (e) => setSearch(e.target.value),
4636
+ onKeyDown: handleKeyDown,
4637
+ placeholder: "\uAC80\uC0C9",
4638
+ 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)]"
4639
+ }
4640
+ ),
4641
+ search !== "" && /* @__PURE__ */ jsx(
4642
+ SGhostButton,
4643
+ {
4644
+ icon: "close",
4645
+ size: "xxs",
4646
+ intent: "default",
4647
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4648
+ onClick: () => setSearch(""),
4649
+ className: "shrink-0"
4650
+ }
4651
+ )
4652
+ ] }) }),
4653
+ hasItems ? /* @__PURE__ */ jsx(
4654
+ ItemList,
4655
+ {
4656
+ items: filtered,
4657
+ multi,
4658
+ selected,
4659
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
4660
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
4661
+ selectAllLabel: allSelectedLabel,
4662
+ selectAllSelected: allSelected,
4663
+ onSelectAll: handleSelectAll,
4664
+ onSelect: handleSelect,
4665
+ onHoverValue: (value2) => {
4666
+ const index = navigableItems.findIndex(
4667
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
4668
+ );
4669
+ if (index >= 0) setFocusedIndex(index);
4670
+ }
4671
+ }
4672
+ ) : /* @__PURE__ */ 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." })
4673
+ ]
4674
+ }
4675
+ )
4416
4676
  }
4417
4677
  )
4418
4678
  ]
4419
4679
  }
4420
4680
  );
4421
- const control = showSearch ? /* @__PURE__ */ jsx(Ariakit.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
4422
- return /* @__PURE__ */ jsxs(
4423
- SField,
4424
- {
4425
- label,
4426
- labelWidth,
4427
- icon,
4428
- labelTooltip,
4429
- labelTooltipProps,
4430
- addonLabel,
4431
- addonAlign,
4432
- hint,
4433
- error: error || ruleError !== "",
4434
- status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4435
- errorMessage: ruleError !== "" ? ruleError : errorMessage,
4436
- disabled: effectiveDisabled,
4437
- width,
4438
- className,
4439
- style,
4440
- children: [
4441
- name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected.join(",") }),
4442
- control
4443
- ]
4444
- }
4445
- );
4446
4681
  });
4447
4682
  SSelect.displayName = "SSelect";
4448
4683
  function ItemList({
4449
4684
  items,
4450
4685
  multi,
4451
4686
  selected,
4452
- useSearch
4687
+ focusedValue,
4688
+ showSelectAll,
4689
+ selectAllLabel,
4690
+ selectAllSelected,
4691
+ onSelectAll,
4692
+ onSelect,
4693
+ onHoverValue
4453
4694
  }) {
4454
4695
  const itemBase = cn(
4455
- // group: 체크박스 inverse(group-data-[active-item])를 위해 필요
4456
- "group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
4696
+ "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
4457
4697
  "text-[color:var(--cmp-listItem-content-default)]",
4458
- // Ariakit은 disabled 항목에 aria-disabled를 붙이므로 aria-disabled 변형으로 스타일 (data-disabled 아님)
4459
- "aria-disabled:cursor-not-allowed aria-disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4698
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4460
4699
  );
4461
- const activeClass = "data-[active-item]:bg-[var(--cmp-listItem-bg-hover)] data-[active-item]:text-[color:var(--cmp-listItem-content-hover)]";
4462
- return /* @__PURE__ */ jsx(Fragment, { children: items.map((o) => {
4463
- const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4464
- if (o.isGroup) {
4465
- const isDepth1 = o.depth === 1;
4466
- return /* @__PURE__ */ jsx(
4467
- "div",
4468
- {
4469
- className: cn(
4470
- "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4471
- 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",
4472
- // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4473
- // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4474
- o.disabled && "text-[color:var(--cmp-listItem-content-disabled)]",
4475
- o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4476
- ),
4477
- style: { paddingLeft: padLeft },
4478
- children: o.label
4479
- },
4480
- o.value
4481
- );
4482
- }
4483
- const isSelected = selected.includes(o.value);
4484
- return /* @__PURE__ */ jsxs(
4485
- Ariakit.SelectItem,
4700
+ 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)]";
4701
+ return /* @__PURE__ */ jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
4702
+ showSelectAll && /* @__PURE__ */ jsxs(
4703
+ "button",
4486
4704
  {
4487
- value: o.value,
4488
- disabled: o.disabled,
4705
+ type: "button",
4706
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
4707
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
4489
4708
  className: cn(
4490
4709
  itemBase,
4491
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4492
- activeClass
4710
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
4493
4711
  ),
4494
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4495
- render: useSearch ? /* @__PURE__ */ jsx(Ariakit.ComboboxItem, {}) : void 0,
4712
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
4713
+ onMouseEnter: () => onHoverValue("__select_all__"),
4714
+ onClick: onSelectAll,
4496
4715
  children: [
4497
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
4498
4716
  /* @__PURE__ */ jsx(
4499
4717
  "span",
4500
4718
  {
4501
4719
  className: cn(
4502
4720
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4503
- o.disabled ? (
4504
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4505
- "border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4506
- ) : cn(
4507
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4508
- 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"
4509
- )
4721
+ 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)]"
4510
4722
  ),
4511
- children: isSelected && /* @__PURE__ */ jsx(
4723
+ children: selectAllSelected && /* @__PURE__ */ jsx(
4512
4724
  SIcon,
4513
4725
  {
4514
4726
  name: "checkboxCheck",
4515
4727
  size: 12,
4516
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4728
+ color: "var(--cmp-checkbox-checked-icon-default)"
4517
4729
  }
4518
4730
  )
4519
4731
  }
4520
4732
  ),
4521
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
4522
- "span",
4523
- {
4524
- className: "flex-1 truncate",
4525
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4526
- }
4527
- ) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.label })
4733
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
4528
4734
  ]
4529
- },
4530
- o.value
4531
- );
4532
- }) });
4735
+ }
4736
+ ),
4737
+ items.map((o) => {
4738
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4739
+ if (o.isGroup) {
4740
+ const isDepth1 = o.depth === 1;
4741
+ return /* @__PURE__ */ jsx(
4742
+ "div",
4743
+ {
4744
+ className: cn(
4745
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4746
+ 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",
4747
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4748
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4749
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4750
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4751
+ ),
4752
+ style: { paddingLeft: padLeft },
4753
+ children: o.label
4754
+ },
4755
+ o.value
4756
+ );
4757
+ }
4758
+ const isSelected = selected.includes(o.value);
4759
+ return /* @__PURE__ */ jsxs(
4760
+ "button",
4761
+ {
4762
+ type: "button",
4763
+ disabled: o.disabled,
4764
+ role: "option",
4765
+ "aria-selected": isSelected,
4766
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
4767
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
4768
+ className: cn(
4769
+ itemBase,
4770
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4771
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
4772
+ activeClass
4773
+ ),
4774
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4775
+ onMouseEnter: () => onHoverValue(o.value),
4776
+ onClick: () => onSelect(o.value),
4777
+ children: [
4778
+ multi && /* @__PURE__ */ jsx(
4779
+ "span",
4780
+ {
4781
+ className: cn(
4782
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4783
+ o.disabled ? (
4784
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4785
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4786
+ ) : cn(
4787
+ 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)]"
4788
+ )
4789
+ ),
4790
+ children: isSelected && /* @__PURE__ */ jsx(
4791
+ SIcon,
4792
+ {
4793
+ name: "checkboxCheck",
4794
+ size: 12,
4795
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4796
+ }
4797
+ )
4798
+ }
4799
+ ),
4800
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
4801
+ "span",
4802
+ {
4803
+ className: "flex-1 truncate",
4804
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4805
+ }
4806
+ ) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.label })
4807
+ ]
4808
+ },
4809
+ o.value
4810
+ );
4811
+ })
4812
+ ] });
4533
4813
  }
4534
4814
  function SRadio({
4535
4815
  val,
@@ -4637,7 +4917,7 @@ function SRadioGroup({
4637
4917
  role: "radiogroup",
4638
4918
  className: cn(
4639
4919
  "inline-flex",
4640
- direction === "vertical" ? "flex-col gap-[8px]" : "flex-row gap-[20px]",
4920
+ direction === "vertical" ? "flex-col gap-[var(--cmp-radio-group-gap-vertical)]" : "flex-row gap-[var(--cmp-radio-group-gap-horizontal)]",
4641
4921
  className
4642
4922
  ),
4643
4923
  style,
@@ -4992,6 +5272,7 @@ function SDatePicker({
4992
5272
  label,
4993
5273
  labelWidth,
4994
5274
  icon,
5275
+ iconColor,
4995
5276
  labelTooltip,
4996
5277
  labelTooltipProps,
4997
5278
  addonLabel,
@@ -5030,6 +5311,7 @@ function SDatePicker({
5030
5311
  label,
5031
5312
  labelWidth,
5032
5313
  icon,
5314
+ iconColor,
5033
5315
  labelTooltip,
5034
5316
  labelTooltipProps,
5035
5317
  addonLabel,
@@ -5298,6 +5580,7 @@ function SDateRangePicker({
5298
5580
  label,
5299
5581
  labelWidth,
5300
5582
  icon,
5583
+ iconColor,
5301
5584
  labelTooltip,
5302
5585
  labelTooltipProps,
5303
5586
  addonLabel,
@@ -5340,6 +5623,7 @@ function SDateRangePicker({
5340
5623
  label,
5341
5624
  labelWidth,
5342
5625
  icon,
5626
+ iconColor,
5343
5627
  labelTooltip,
5344
5628
  labelTooltipProps,
5345
5629
  addonLabel,
@@ -5601,6 +5885,7 @@ function SFilePicker({
5601
5885
  label,
5602
5886
  labelWidth,
5603
5887
  icon,
5888
+ iconColor,
5604
5889
  labelTooltip,
5605
5890
  labelTooltipProps,
5606
5891
  addonLabel,
@@ -5615,7 +5900,10 @@ function SFilePicker({
5615
5900
  const autoId = useId();
5616
5901
  const resolvedName = name ?? autoId;
5617
5902
  const inputRef = useRef(null);
5903
+ const contentBoxRef = useRef(null);
5618
5904
  const [ruleError, setRuleError] = useState("");
5905
+ const [listOpen, setListOpen] = useState(false);
5906
+ const portalContainer = usePortalContainer();
5619
5907
  const files = multiple ? Array.isArray(value) ? value : [] : value && !Array.isArray(value) ? value : null;
5620
5908
  const hasFiles = multiple ? files.length > 0 : !!files;
5621
5909
  const displayText = !hasFiles ? placeholder : multiple ? files.map((f) => f.name).join(", ") : files.name;
@@ -5669,10 +5957,12 @@ function SFilePicker({
5669
5957
  e.stopPropagation();
5670
5958
  if (disabled) return;
5671
5959
  onValueChange?.(multiple ? [] : null);
5960
+ setListOpen(false);
5672
5961
  };
5673
5962
  const removeChip = (i) => {
5674
5963
  const next = files.filter((_, idx) => idx !== i);
5675
5964
  onValueChange?.(next);
5965
+ if (next.length === 0) setListOpen(false);
5676
5966
  };
5677
5967
  const open = () => {
5678
5968
  if (!disabled) inputRef.current?.click();
@@ -5683,9 +5973,12 @@ function SFilePicker({
5683
5973
  const iconSize = size === "md" ? 20 : 16;
5684
5974
  const gapCls = size === "md" ? "gap-[var(--cmp-filepicker-md-gap)]" : "gap-[var(--cmp-filepicker-sm-gap)]";
5685
5975
  const inlinePlaceholder = inline && !disabled && !hasFiles;
5686
- const content = /* @__PURE__ */ jsxs(
5976
+ const showListbox = multiple && useMultipleListBox;
5977
+ const filesArr = multiple ? files : [];
5978
+ const contentInner = /* @__PURE__ */ jsxs(
5687
5979
  "div",
5688
5980
  {
5981
+ ref: contentBoxRef,
5689
5982
  className: cn("group/filepicker flex h-full w-full items-center", gapCls),
5690
5983
  style: { padding: inline ? 0 : `0 ${padX}` },
5691
5984
  children: [
@@ -5740,25 +6033,34 @@ function SFilePicker({
5740
6033
  ]
5741
6034
  }
5742
6035
  ),
6036
+ showListbox && hasFiles && !disabled && /* @__PURE__ */ jsx(
6037
+ Ariakit.PopoverDisclosure,
6038
+ {
6039
+ render: /* @__PURE__ */ jsx(SGhostButton, { icon: "caretDown", size: "xxs", ariaLabel: "toggle file list" }),
6040
+ className: cn("shrink-0 transition-transform duration-150", listOpen && "rotate-180")
6041
+ }
6042
+ ),
5743
6043
  !disabled && hasFiles && /* @__PURE__ */ jsx(SGhostButton, { icon: "close", size: "xxs", ariaLabel: "clear", onClick: clear })
5744
6044
  ]
5745
6045
  }
5746
6046
  );
5747
- const chips = multiple && files.length > 0 && /* @__PURE__ */ jsx(
5748
- "div",
5749
- {
5750
- className: cn(
5751
- "mt-[8px] flex gap-[8px]",
5752
- 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"
5753
- ),
5754
- style: useMultipleListBox ? {
5755
- width: typeof width === "number" ? `${width}px` : width,
5756
- // label 있으면 SField 입력 박스 시작 위치(labelWidth + fieldGap) 맞춰 정렬
5757
- marginLeft: label && labelWidth != null ? `calc(${typeof labelWidth === "number" ? `${labelWidth}px` : labelWidth} + var(--sys-space-field-${size}-gap))` : void 0
5758
- } : void 0,
5759
- children: files.map((f, i) => /* @__PURE__ */ jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i))
5760
- }
5761
- );
6047
+ const content = showListbox ? /* @__PURE__ */ jsxs(Ariakit.PopoverProvider, { open: listOpen, setOpen: setListOpen, placement: "bottom-start", children: [
6048
+ contentInner,
6049
+ /* @__PURE__ */ jsx(
6050
+ Ariakit.Popover,
6051
+ {
6052
+ portal: true,
6053
+ portalElement: portalContainer,
6054
+ gutter: 4,
6055
+ unmountOnHide: true,
6056
+ getAnchorRect: () => contentBoxRef.current?.getBoundingClientRect() ?? null,
6057
+ style: { minWidth: contentBoxRef.current?.offsetWidth },
6058
+ 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)]",
6059
+ children: filesArr.map((f, i) => /* @__PURE__ */ jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i))
6060
+ }
6061
+ )
6062
+ ] }) : contentInner;
6063
+ const chips = multiple && !useMultipleListBox && filesArr.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-[8px] flex flex-wrap gap-[8px]", children: filesArr.map((f, i) => /* @__PURE__ */ jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i)) });
5762
6064
  return /* @__PURE__ */ jsxs("div", { className, style, children: [
5763
6065
  /* @__PURE__ */ jsx(
5764
6066
  SField,
@@ -5767,6 +6069,7 @@ function SFilePicker({
5767
6069
  label,
5768
6070
  labelWidth,
5769
6071
  icon,
6072
+ iconColor,
5770
6073
  labelTooltip,
5771
6074
  labelTooltipProps,
5772
6075
  addonLabel,
@@ -5789,21 +6092,26 @@ function SFilePicker({
5789
6092
  var cssLen2 = (v) => v == null ? void 0 : typeof v === "number" ? `${v}px` : v;
5790
6093
  function SKeyValueTable({
5791
6094
  fields = [],
6095
+ values = {},
5792
6096
  search = false,
5793
6097
  radius = "default",
5794
- dense = false,
5795
6098
  onChange,
5796
6099
  onSearch,
5797
6100
  className,
5798
6101
  style
5799
6102
  }) {
5800
6103
  if (fields.length === 0) return null;
5801
- const emit = (name, value) => onChange?.({ name, value });
5802
- const cellHeight = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-keyValue-height)";
5803
- const cellPadY = dense ? "var(--cmp-table-body-dense-paddingY)" : "var(--cmp-table-keyValue-body-paddingY)";
6104
+ const emit = (name, value) => onChange?.({
6105
+ name,
6106
+ value,
6107
+ values: { ...values, [name]: value }
6108
+ });
6109
+ const cellHeight = "var(--cmp-table-keyValue-height)";
6110
+ const cellPadY = "var(--cmp-table-keyValue-body-paddingY)";
5804
6111
  const renderField = (f) => {
5805
6112
  if (f.render) return f.render;
5806
- const opt = f.options ?? {};
6113
+ const opt = { ...f.options ?? {} };
6114
+ if (Object.prototype.hasOwnProperty.call(values, f.name)) opt.value = values[f.name];
5807
6115
  const up = (v) => emit(f.name, v);
5808
6116
  switch (f.type) {
5809
6117
  case "input":
@@ -5915,7 +6223,10 @@ function SKeyValueTable({
5915
6223
  rowSpan: f.tdRowSpan && f.tdRowSpan > 1 ? f.tdRowSpan : void 0,
5916
6224
  colSpan: f.tdColSpan && f.tdColSpan > 1 ? f.tdColSpan : void 0,
5917
6225
  className: cn(
5918
- "box-border bg-[var(--sys-color-bg-frame)] px-[var(--cmp-table-keyValue-body-paddingX-default)] align-middle",
6226
+ "box-border bg-[var(--sys-color-bg-frame)] align-middle",
6227
+ // text 타입(및 미지정 기본값)은 읽기 전용 콘텐츠라 넓은 paddingX(wide),
6228
+ // 폼 필드 셀은 default paddingX 유지
6229
+ f.type == null || f.type === "text" ? "px-[var(--cmp-table-keyValue-body-paddingX-wide)]" : "px-[var(--cmp-table-keyValue-body-paddingX-default)]",
5919
6230
  cellBorder,
5920
6231
  f.tdClass
5921
6232
  ),