sellmate-design-system-react 0.1.8 → 0.1.10

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 (110) hide show
  1. package/dist/components/SActionModal/SActionModal.cjs +1 -1
  2. package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
  3. package/dist/components/SActionModal/SActionModal.js +1 -1
  4. package/dist/components/SActionModal/SActionModal.js.map +1 -1
  5. package/dist/components/SBarcodeInput/SBarcodeInput.cjs +217 -208
  6. package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
  7. package/dist/components/SBarcodeInput/SBarcodeInput.js +217 -208
  8. package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
  9. package/dist/components/SCalendar/SCalendar.cjs +1 -1
  10. package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
  11. package/dist/components/SCalendar/SCalendar.js +1 -1
  12. package/dist/components/SCalendar/SCalendar.js.map +1 -1
  13. package/dist/components/SChip/SChip.cjs +1 -1
  14. package/dist/components/SChip/SChip.cjs.map +1 -1
  15. package/dist/components/SChip/SChip.d.ts +1 -1
  16. package/dist/components/SChip/SChip.js +1 -1
  17. package/dist/components/SChip/SChip.js.map +1 -1
  18. package/dist/components/SChipInput/SChipInput.cjs +234 -220
  19. package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
  20. package/dist/components/SChipInput/SChipInput.js +234 -220
  21. package/dist/components/SChipInput/SChipInput.js.map +1 -1
  22. package/dist/components/SCircleProgress/SCircleProgress.cjs +31 -14
  23. package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
  24. package/dist/components/SCircleProgress/SCircleProgress.d.ts +2 -0
  25. package/dist/components/SCircleProgress/SCircleProgress.js +31 -14
  26. package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
  27. package/dist/components/SDatePicker/SDatePicker.cjs +222 -213
  28. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  29. package/dist/components/SDatePicker/SDatePicker.js +222 -213
  30. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  31. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +297 -262
  32. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  33. package/dist/components/SDateRangePicker/SDateRangePicker.js +297 -262
  34. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  35. package/dist/components/SDropdownButton/SDropdownButton.cjs +4 -4
  36. package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
  37. package/dist/components/SDropdownButton/SDropdownButton.js +4 -4
  38. package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
  39. package/dist/components/SField/SField.cjs +217 -20
  40. package/dist/components/SField/SField.cjs.map +1 -1
  41. package/dist/components/SField/SField.d.ts +11 -0
  42. package/dist/components/SField/SField.js +217 -20
  43. package/dist/components/SField/SField.js.map +1 -1
  44. package/dist/components/SFilePicker/SFilePicker.cjs +315 -276
  45. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  46. package/dist/components/SFilePicker/SFilePicker.d.ts +4 -2
  47. package/dist/components/SFilePicker/SFilePicker.js +314 -276
  48. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  49. package/dist/components/SInput/SInput.cjs +217 -208
  50. package/dist/components/SInput/SInput.cjs.map +1 -1
  51. package/dist/components/SInput/SInput.js +217 -208
  52. package/dist/components/SInput/SInput.js.map +1 -1
  53. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +631 -450
  54. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  55. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +3 -1
  56. package/dist/components/SKeyValueTable/SKeyValueTable.js +631 -450
  57. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  58. package/dist/components/SLoadingContainer/SLoadingContainer.cjs +39 -14
  59. package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
  60. package/dist/components/SLoadingContainer/SLoadingContainer.js +39 -14
  61. package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
  62. package/dist/components/SLoadingContainer/loading.cjs +39 -14
  63. package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
  64. package/dist/components/SLoadingContainer/loading.js +39 -14
  65. package/dist/components/SLoadingContainer/loading.js.map +1 -1
  66. package/dist/components/SLoadingModal/SLoadingModal.cjs +37 -16
  67. package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
  68. package/dist/components/SLoadingModal/SLoadingModal.d.ts +3 -1
  69. package/dist/components/SLoadingModal/SLoadingModal.js +37 -16
  70. package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
  71. package/dist/components/SNumberInput/SNumberInput.cjs +325 -49
  72. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  73. package/dist/components/SNumberInput/SNumberInput.d.ts +2 -0
  74. package/dist/components/SNumberInput/SNumberInput.js +325 -49
  75. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  76. package/dist/components/SPagination/SPagination.cjs +1 -1
  77. package/dist/components/SPagination/SPagination.cjs.map +1 -1
  78. package/dist/components/SPagination/SPagination.js +1 -1
  79. package/dist/components/SPagination/SPagination.js.map +1 -1
  80. package/dist/components/SRadio/SRadioGroup.cjs +1 -1
  81. package/dist/components/SRadio/SRadioGroup.cjs.map +1 -1
  82. package/dist/components/SRadio/SRadioGroup.js +1 -1
  83. package/dist/components/SRadio/SRadioGroup.js.map +1 -1
  84. package/dist/components/SSelect/SSelect.cjs +71 -32
  85. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  86. package/dist/components/SSelect/SSelect.js +71 -32
  87. package/dist/components/SSelect/SSelect.js.map +1 -1
  88. package/dist/components/STable/STable.cjs +108 -49
  89. package/dist/components/STable/STable.cjs.map +1 -1
  90. package/dist/components/STable/STable.js +108 -49
  91. package/dist/components/STable/STable.js.map +1 -1
  92. package/dist/components/STextarea/STextarea.cjs +217 -20
  93. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  94. package/dist/components/STextarea/STextarea.js +217 -20
  95. package/dist/components/STextarea/STextarea.js.map +1 -1
  96. package/dist/components/SToast/SToastContainer.cjs +3 -3
  97. package/dist/components/SToast/SToastContainer.cjs.map +1 -1
  98. package/dist/components/SToast/SToastContainer.js +3 -3
  99. package/dist/components/SToast/SToastContainer.js.map +1 -1
  100. package/dist/components/STooltip/STooltip.cjs +208 -13
  101. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  102. package/dist/components/STooltip/STooltip.js +208 -13
  103. package/dist/components/STooltip/STooltip.js.map +1 -1
  104. package/dist/index.cjs +439 -221
  105. package/dist/index.cjs.map +1 -1
  106. package/dist/index.js +439 -221
  107. package/dist/index.js.map +1 -1
  108. package/dist/styles.css +113 -48
  109. package/dist/theme.css +49 -23
  110. package/package.json +1 -1
@@ -2,8 +2,8 @@
2
2
  import { forwardRef, createContext, useState, useRef, useImperativeHandle, useEffect, useMemo, useContext, Fragment as Fragment$1, useId } from 'react';
3
3
  import { clsx } from 'clsx';
4
4
  import { extendTailwindMerge } from 'tailwind-merge';
5
- import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
6
- import * as RPopover from '@radix-ui/react-popover';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+ import * as RPopover2 from '@radix-ui/react-popover';
7
7
  import * as Ariakit from '@ariakit/react';
8
8
  import * as RCheckbox from '@radix-ui/react-checkbox';
9
9
 
@@ -2877,11 +2877,200 @@ PortalContainerContext.Provider;
2877
2877
  function usePortalContainer() {
2878
2878
  return useContext(PortalContainerContext) ?? void 0;
2879
2879
  }
2880
+
2881
+ // src/components/STag/tag.config.ts
2882
+ var TAG_SIZE_CONFIG = {
2883
+ xs: {
2884
+ height: 20,
2885
+ paddingX: 8,
2886
+ gap: 4,
2887
+ radius: 6,
2888
+ iconSize: 12,
2889
+ fontSize: 11,
2890
+ fontWeight: 500,
2891
+ lineHeight: 18
2892
+ },
2893
+ sm: {
2894
+ height: 24,
2895
+ paddingX: 8,
2896
+ gap: 4,
2897
+ radius: 6,
2898
+ iconSize: 16,
2899
+ fontSize: 12,
2900
+ fontWeight: 700,
2901
+ lineHeight: 20
2902
+ },
2903
+ md: {
2904
+ height: 28,
2905
+ paddingX: 12,
2906
+ gap: 6,
2907
+ radius: 8,
2908
+ iconSize: 16,
2909
+ fontSize: 14,
2910
+ fontWeight: 700,
2911
+ lineHeight: 24
2912
+ }
2913
+ };
2914
+ var STag = forwardRef(function STag2({
2915
+ shape = "square",
2916
+ size = "sm",
2917
+ color = "grey",
2918
+ label = "",
2919
+ icon,
2920
+ iconLeft = true,
2921
+ width,
2922
+ className,
2923
+ style,
2924
+ ...rest
2925
+ }, ref) {
2926
+ const s = TAG_SIZE_CONFIG[size];
2927
+ const contentColor = `var(--cmp-tag-${color}-content)`;
2928
+ const iconNode = icon ? /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.iconSize, color: contentColor }) : null;
2929
+ const resolvedWidth = typeof width === "number" ? `${width}px` : width;
2930
+ return /* @__PURE__ */ jsxs(
2931
+ "span",
2932
+ {
2933
+ ref,
2934
+ "aria-label": label || "tag",
2935
+ className: cn(
2936
+ "box-border inline-flex w-fit select-none items-center justify-center whitespace-nowrap",
2937
+ className
2938
+ ),
2939
+ style: {
2940
+ minHeight: s.height,
2941
+ padding: `0 ${s.paddingX}px`,
2942
+ gap: s.gap,
2943
+ borderRadius: shape === "pill" ? 999 : s.radius,
2944
+ fontSize: s.fontSize,
2945
+ fontWeight: s.fontWeight,
2946
+ lineHeight: `${s.lineHeight}px`,
2947
+ background: `var(--cmp-tag-${color}-bg)`,
2948
+ color: contentColor,
2949
+ ...resolvedWidth != null ? { width: resolvedWidth } : {},
2950
+ ...style
2951
+ },
2952
+ ...rest,
2953
+ children: [
2954
+ icon && iconLeft && iconNode,
2955
+ label && /* @__PURE__ */ jsx("span", { className: "inline-flex min-w-0 items-center justify-center", children: label }),
2956
+ icon && !iconLeft && iconNode
2957
+ ]
2958
+ }
2959
+ );
2960
+ });
2961
+ var SIZE = {
2962
+ xxs: { icon: 12, radius: "sm" },
2963
+ xs: { icon: 16, radius: "sm" },
2964
+ sm: { icon: 20, radius: "sm" },
2965
+ md: { icon: 24, radius: "md" },
2966
+ lg: { icon: 32, radius: "md" }
2967
+ };
2968
+ var TOOLTIP_HIDE_DELAY_MS = 100;
2969
+ function SGhostButton({
2970
+ icon,
2971
+ size = "sm",
2972
+ intent = "default",
2973
+ ariaLabel = "",
2974
+ tooltipText,
2975
+ disabled = false,
2976
+ onClick,
2977
+ className,
2978
+ style
2979
+ }) {
2980
+ const portalContainer = usePortalContainer();
2981
+ const [open, setOpen] = useState(false);
2982
+ const hideTimer = useRef(void 0);
2983
+ const cancelHide = () => {
2984
+ if (hideTimer.current) {
2985
+ clearTimeout(hideTimer.current);
2986
+ hideTimer.current = void 0;
2987
+ }
2988
+ };
2989
+ const openNow = () => {
2990
+ cancelHide();
2991
+ setOpen(true);
2992
+ };
2993
+ const startHide = () => {
2994
+ cancelHide();
2995
+ hideTimer.current = setTimeout(() => setOpen(false), TOOLTIP_HIDE_DELAY_MS);
2996
+ };
2997
+ const s = SIZE[size];
2998
+ const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
2999
+ const btn = /* @__PURE__ */ jsxs(
3000
+ "button",
3001
+ {
3002
+ type: "button",
3003
+ disabled,
3004
+ "aria-disabled": disabled ? "true" : void 0,
3005
+ "aria-label": ariaLabel.trim() || void 0,
3006
+ tabIndex: disabled ? -1 : void 0,
3007
+ onClick: (e) => {
3008
+ if (disabled) {
3009
+ e.preventDefault();
3010
+ return;
3011
+ }
3012
+ onClick?.(e);
3013
+ },
3014
+ className: cn(
3015
+ "group relative box-border inline-flex items-center justify-center border-none bg-transparent p-0 transition-shadow",
3016
+ disabled ? "cursor-not-allowed" : "cursor-pointer",
3017
+ "focus-visible:outline-none focus-visible:shadow-[0_0_0_2px_var(--cmp-ghostButton-action-hover-bg)]",
3018
+ className
3019
+ ),
3020
+ style: {
3021
+ width: `var(--cmp-ghostButton-${size}-size)`,
3022
+ height: `var(--cmp-ghostButton-${size}-size)`,
3023
+ borderRadius: `var(--cmp-ghostButton-radius-${s.radius})`,
3024
+ ...style
3025
+ },
3026
+ children: [
3027
+ !disabled && /* @__PURE__ */ jsx(
3028
+ "span",
3029
+ {
3030
+ "aria-hidden": true,
3031
+ className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover:opacity-5",
3032
+ style: { background: `var(--cmp-ghostButton-${intent}-hover-bg)` }
3033
+ }
3034
+ ),
3035
+ /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, color: contentColor })
3036
+ ]
3037
+ }
3038
+ );
3039
+ const text = (tooltipText ?? "").trim();
3040
+ if (text === "") return btn;
3041
+ return /* @__PURE__ */ jsxs(RPopover2.Root, { open, onOpenChange: setOpen, children: [
3042
+ /* @__PURE__ */ jsx(RPopover2.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
3043
+ "span",
3044
+ {
3045
+ className: "inline-flex",
3046
+ onMouseEnter: openNow,
3047
+ onMouseLeave: startHide,
3048
+ onFocus: openNow,
3049
+ onBlur: startHide,
3050
+ children: btn
3051
+ }
3052
+ ) }),
3053
+ /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
3054
+ RPopover2.Content,
3055
+ {
3056
+ side: "top",
3057
+ sideOffset: 4,
3058
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
3059
+ "aria-hidden": true,
3060
+ onOpenAutoFocus: (e) => e.preventDefault(),
3061
+ onMouseEnter: cancelHide,
3062
+ onMouseLeave: startHide,
3063
+ children: /* @__PURE__ */ jsx(STag, { shape: "square", size: "xs", color: "grey", label: text })
3064
+ }
3065
+ ) })
3066
+ ] });
3067
+ }
2880
3068
  var HIDE_DELAY_MS = 100;
2881
3069
  function useBody(content, message = []) {
2882
3070
  if (content != null) return content;
2883
3071
  if (message.length === 0) return null;
2884
- return /* @__PURE__ */ jsx(Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsx("div", { children: line }, i)) });
3072
+ const singleLine = message.length === 1;
3073
+ return /* @__PURE__ */ jsx(Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsx("div", { className: singleLine ? "leading-[24px]" : void 0, children: line }, i)) });
2885
3074
  }
2886
3075
  var STooltip = forwardRef(function STooltip2({
2887
3076
  content,
@@ -2949,10 +3138,10 @@ var STooltip = forwardRef(function STooltip2({
2949
3138
  fontWeight: tooltipType === "default" ? 500 : 700,
2950
3139
  ...style
2951
3140
  };
2952
- return /* @__PURE__ */ jsxs(RPopover.Root, { open, onOpenChange: setOpen, children: [
3141
+ return /* @__PURE__ */ jsxs(RPopover2.Root, { open, onOpenChange: setOpen, children: [
2953
3142
  isHover ? (
2954
3143
  // hover: Anchor(위치 기준) + 직접 mouse 핸들러 — 클릭으로 토글되지 않도록 Trigger 미사용
2955
- /* @__PURE__ */ jsx(RPopover.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
3144
+ /* @__PURE__ */ jsx(RPopover2.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
2956
3145
  "span",
2957
3146
  {
2958
3147
  className: "inline-flex",
@@ -2963,9 +3152,9 @@ var STooltip = forwardRef(function STooltip2({
2963
3152
  children: triggerNode
2964
3153
  }
2965
3154
  ) })
2966
- ) : /* @__PURE__ */ jsx(RPopover.Trigger, { asChild: true, children: triggerNode }),
2967
- /* @__PURE__ */ jsx(RPopover.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
2968
- RPopover.Content,
3155
+ ) : /* @__PURE__ */ jsx(RPopover2.Trigger, { asChild: true, children: triggerNode }),
3156
+ /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
3157
+ RPopover2.Content,
2969
3158
  {
2970
3159
  side: placement,
2971
3160
  sideOffset: 8,
@@ -2984,15 +3173,21 @@ var STooltip = forwardRef(function STooltip2({
2984
3173
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-[12px]", children: [
2985
3174
  /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: body }),
2986
3175
  useClose && /* @__PURE__ */ jsx(
2987
- RPopover.Close,
3176
+ SGhostButton,
2988
3177
  {
2989
- "aria-label": "\uB2EB\uAE30",
2990
- className: "shrink-0 cursor-pointer opacity-70 hover:opacity-100",
2991
- children: /* @__PURE__ */ jsx(SIcon, { name: "close", size: 16, color: fg })
3178
+ icon: "close",
3179
+ size: "xs",
3180
+ intent: tooltipType === "default" ? "inverse" : "default",
3181
+ ariaLabel: "\uB2EB\uAE30",
3182
+ className: "shrink-0",
3183
+ onClick: () => {
3184
+ cancelHide();
3185
+ setOpen(false);
3186
+ }
2992
3187
  }
2993
3188
  )
2994
3189
  ] }),
2995
- /* @__PURE__ */ jsx(RPopover.Arrow, { width: 16, height: 12, style: { fill: bg } })
3190
+ /* @__PURE__ */ jsx(RPopover2.Arrow, { width: 16, height: 12, style: { fill: bg } })
2996
3191
  ]
2997
3192
  }
2998
3193
  ) })
@@ -3192,8 +3387,8 @@ function useFormField(field) {
3192
3387
  }, [ctx, name]);
3193
3388
  }
3194
3389
  var FIELD_SIZE = {
3195
- sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 12 },
3196
- md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 12 }
3390
+ sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 16 },
3391
+ md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 16 }
3197
3392
  };
3198
3393
  var ALIGN = {
3199
3394
  start: "flex-start",
@@ -3208,6 +3403,7 @@ var SField = forwardRef(function SField2({
3208
3403
  labelTooltip = "",
3209
3404
  labelTooltipProps,
3210
3405
  labelWidth,
3406
+ labelHeight,
3211
3407
  size = "sm",
3212
3408
  addonLabel = "",
3213
3409
  addonAlign = "start",
@@ -3222,6 +3418,7 @@ var SField = forwardRef(function SField2({
3222
3418
  readOnly = false,
3223
3419
  width,
3224
3420
  multiline = false,
3421
+ borderless = false,
3225
3422
  children,
3226
3423
  className,
3227
3424
  style,
@@ -3255,8 +3452,8 @@ var SField = forwardRef(function SField2({
3255
3452
  const resolvedSuccess = status === "pass";
3256
3453
  const showError = resolvedError && errorMessage !== "";
3257
3454
  const interactive = !disabled && !readOnly;
3258
- const borderColor = disabled ? "var(--color-grey-45)" : interactive && focused ? "var(--sys-color-field-border-focus)" : resolvedSuccess ? "var(--sys-color-field-border-success)" : resolvedError ? "var(--sys-color-field-border-danger)" : "var(--sys-color-field-border-default)";
3259
- const bgColor = disabled ? "var(--color-grey-30)" : readOnly ? "var(--sys-color-field-bg-readOnly)" : "var(--sys-color-field-bg-default)";
3455
+ const borderColor = borderless ? "transparent" : disabled ? "var(--color-grey-45)" : interactive && focused ? "var(--sys-color-field-border-focus)" : resolvedSuccess ? "var(--sys-color-field-border-success)" : resolvedError ? "var(--sys-color-field-border-danger)" : "var(--sys-color-field-border-default)";
3456
+ const bgColor = borderless ? "transparent" : disabled ? "var(--color-grey-30)" : readOnly ? "var(--sys-color-field-bg-readOnly)" : "var(--sys-color-field-bg-default)";
3260
3457
  const textColor = disabled ? "var(--color-grey-65)" : readOnly ? "var(--sys-color-field-text-readOnly)" : "var(--sys-color-field-text-default)";
3261
3458
  return /* @__PURE__ */ jsx(
3262
3459
  "div",
@@ -3273,266 +3470,78 @@ var SField = forwardRef(function SField2({
3273
3470
  {
3274
3471
  className: "flex shrink-0 items-center font-bold text-(--sys-color-field-text-default)",
3275
3472
  style: {
3276
- height: s.height,
3473
+ height: cssLen(labelHeight) ?? s.height,
3277
3474
  marginRight: s.fieldGap,
3278
3475
  width: cssLen(labelWidth),
3279
- fontSize: s.fontSize,
3280
- lineHeight: `${s.lineHeight}px`
3281
- },
3282
- children: [
3283
- icon && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3284
- /* @__PURE__ */ jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3285
- (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsx(
3286
- STooltip,
3287
- {
3288
- message: labelTooltip !== "" ? [labelTooltip] : void 0,
3289
- iconSize: s.icon,
3290
- ...labelTooltipProps
3291
- }
3292
- ) })
3293
- ]
3294
- }
3295
- ),
3296
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
3297
- /* @__PURE__ */ jsxs(
3298
- "div",
3299
- {
3300
- className: cn(
3301
- "relative box-border flex min-w-0 items-stretch overflow-hidden border border-solid border-(--field-bc)",
3302
- interactive && "hover:border-(--sys-color-field-border-focus) hover:shadow-[0px_0px_4px_0px_#0071ff66] focus-within:border-(--sys-color-field-border-focus) focus-within:shadow-[0px_0px_4px_0px_#0071ff66]",
3303
- disabled && "cursor-not-allowed"
3304
- ),
3305
- style: {
3306
- // @ts-expect-error CSS 변수
3307
- "--field-bc": borderColor,
3308
- // focused/hovered prop이 명시된 경우 강조를 즉시 반영
3309
- ...interactive && focused ? { boxShadow: "0px 0px 4px 0px #0071ff66" } : {},
3310
- ...multiline ? { minHeight: s.height } : { height: s.height },
3311
- "width": cssLen(width),
3312
- "borderRadius": s.radius,
3313
- "background": bgColor,
3314
- "color": textColor
3315
- },
3316
- "data-focused": focused || void 0,
3317
- "data-hovered": hovered || void 0,
3318
- children: [
3319
- effectiveAddonLabel !== "" && /* @__PURE__ */ jsxs(
3320
- "div",
3321
- {
3322
- className: "flex shrink-0 items-center gap-(--cmp-field-addonLabel-gap) whitespace-nowrap border-r border-solid px-(--cmp-field-addonLabel-paddingX) text-12 font-medium leading-20",
3323
- style: {
3324
- // 원본 sd-field: labelWidth가 addon 폭(--sd-field-addon-width)에도 적용됨
3325
- width: cssLen(labelWidth),
3326
- justifyContent: ALIGN[addonAlign],
3327
- background: "var(--cmp-field-addonLabel-bg)",
3328
- borderRightColor: disabled ? "var(--cmp-field-addonLabel-border-disabled)" : "var(--cmp-field-addonLabel-border-default)",
3329
- borderTopLeftRadius: s.radius - 1,
3330
- borderBottomLeftRadius: s.radius - 1
3331
- },
3332
- children: [
3333
- icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon }),
3334
- effectiveAddonLabel
3335
- ]
3336
- }
3337
- ),
3338
- /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1", children })
3339
- ]
3340
- }
3341
- ),
3342
- showError ? /* @__PURE__ */ jsx("div", { className: "mt-(--cmp-field-contents-gap) text-12 font-normal leading-20 text-(--cmp-field-errorMessage-color)", children: errorMessage }) : hint !== "" ? /* @__PURE__ */ jsx("div", { className: "mt-(--cmp-field-contents-gap) text-12 font-normal leading-20 text-(--cmp-field-hint-color)", children: hint }) : null
3343
- ] })
3344
- ] })
3345
- }
3346
- );
3347
- });
3348
-
3349
- // src/components/STag/tag.config.ts
3350
- var TAG_SIZE_CONFIG = {
3351
- xs: {
3352
- height: 20,
3353
- paddingX: 8,
3354
- gap: 4,
3355
- radius: 6,
3356
- iconSize: 12,
3357
- fontSize: 11,
3358
- fontWeight: 500,
3359
- lineHeight: 18
3360
- },
3361
- sm: {
3362
- height: 24,
3363
- paddingX: 8,
3364
- gap: 4,
3365
- radius: 6,
3366
- iconSize: 16,
3367
- fontSize: 12,
3368
- fontWeight: 700,
3369
- lineHeight: 20
3370
- },
3371
- md: {
3372
- height: 28,
3373
- paddingX: 12,
3374
- gap: 6,
3375
- radius: 8,
3376
- iconSize: 16,
3377
- fontSize: 14,
3378
- fontWeight: 700,
3379
- lineHeight: 24
3380
- }
3381
- };
3382
- var STag = forwardRef(function STag2({
3383
- shape = "square",
3384
- size = "sm",
3385
- color = "grey",
3386
- label = "",
3387
- icon,
3388
- iconLeft = true,
3389
- width,
3390
- className,
3391
- style,
3392
- ...rest
3393
- }, ref) {
3394
- const s = TAG_SIZE_CONFIG[size];
3395
- const contentColor = `var(--cmp-tag-${color}-content)`;
3396
- const iconNode = icon ? /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.iconSize, color: contentColor }) : null;
3397
- const resolvedWidth = typeof width === "number" ? `${width}px` : width;
3398
- return /* @__PURE__ */ jsxs(
3399
- "span",
3400
- {
3401
- ref,
3402
- "aria-label": label || "tag",
3403
- className: cn(
3404
- "box-border inline-flex w-fit select-none items-center justify-center whitespace-nowrap",
3405
- className
3406
- ),
3407
- style: {
3408
- minHeight: s.height,
3409
- padding: `0 ${s.paddingX}px`,
3410
- gap: s.gap,
3411
- borderRadius: shape === "pill" ? 999 : s.radius,
3412
- fontSize: s.fontSize,
3413
- fontWeight: s.fontWeight,
3414
- lineHeight: `${s.lineHeight}px`,
3415
- background: `var(--cmp-tag-${color}-bg)`,
3416
- color: contentColor,
3417
- ...resolvedWidth != null ? { width: resolvedWidth } : {},
3418
- ...style
3419
- },
3420
- ...rest,
3421
- children: [
3422
- icon && iconLeft && iconNode,
3423
- label && /* @__PURE__ */ jsx("span", { className: "inline-flex min-w-0 items-center justify-center", children: label }),
3424
- icon && !iconLeft && iconNode
3425
- ]
3426
- }
3427
- );
3428
- });
3429
- var SIZE = {
3430
- xxs: { icon: 12, radius: "sm" },
3431
- xs: { icon: 16, radius: "sm" },
3432
- sm: { icon: 20, radius: "sm" },
3433
- md: { icon: 24, radius: "md" },
3434
- lg: { icon: 32, radius: "md" }
3435
- };
3436
- var TOOLTIP_HIDE_DELAY_MS = 100;
3437
- function SGhostButton({
3438
- icon,
3439
- size = "sm",
3440
- intent = "default",
3441
- ariaLabel = "",
3442
- tooltipText,
3443
- disabled = false,
3444
- onClick,
3445
- className,
3446
- style
3447
- }) {
3448
- const portalContainer = usePortalContainer();
3449
- const [open, setOpen] = useState(false);
3450
- const hideTimer = useRef(void 0);
3451
- const cancelHide = () => {
3452
- if (hideTimer.current) {
3453
- clearTimeout(hideTimer.current);
3454
- hideTimer.current = void 0;
3455
- }
3456
- };
3457
- const openNow = () => {
3458
- cancelHide();
3459
- setOpen(true);
3460
- };
3461
- const startHide = () => {
3462
- cancelHide();
3463
- hideTimer.current = setTimeout(() => setOpen(false), TOOLTIP_HIDE_DELAY_MS);
3464
- };
3465
- const s = SIZE[size];
3466
- const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
3467
- const btn = /* @__PURE__ */ jsxs(
3468
- "button",
3469
- {
3470
- type: "button",
3471
- disabled,
3472
- "aria-disabled": disabled ? "true" : void 0,
3473
- "aria-label": ariaLabel.trim() || void 0,
3474
- tabIndex: disabled ? -1 : void 0,
3475
- onClick: (e) => {
3476
- if (disabled) {
3477
- e.preventDefault();
3478
- return;
3479
- }
3480
- onClick?.(e);
3481
- },
3482
- className: cn(
3483
- "group relative box-border inline-flex items-center justify-center border-none bg-transparent p-0 transition-shadow",
3484
- disabled ? "cursor-not-allowed" : "cursor-pointer",
3485
- "focus-visible:outline-none focus-visible:shadow-[0_0_0_2px_var(--cmp-ghostButton-action-hover-bg)]",
3486
- className
3487
- ),
3488
- style: {
3489
- width: `var(--cmp-ghostButton-${size}-size)`,
3490
- height: `var(--cmp-ghostButton-${size}-size)`,
3491
- borderRadius: `var(--cmp-ghostButton-radius-${s.radius})`,
3492
- ...style
3493
- },
3494
- children: [
3495
- !disabled && /* @__PURE__ */ jsx(
3496
- "span",
3497
- {
3498
- "aria-hidden": true,
3499
- className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover:opacity-5",
3500
- style: { background: `var(--cmp-ghostButton-${intent}-hover-bg)` }
3476
+ fontSize: s.fontSize,
3477
+ lineHeight: `${s.lineHeight}px`
3478
+ },
3479
+ children: [
3480
+ icon && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3481
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3482
+ (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsx(
3483
+ STooltip,
3484
+ {
3485
+ message: labelTooltip !== "" ? [labelTooltip] : void 0,
3486
+ iconSize: s.icon,
3487
+ ...labelTooltipProps
3488
+ }
3489
+ ) })
3490
+ ]
3501
3491
  }
3502
3492
  ),
3503
- /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, color: contentColor })
3504
- ]
3493
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
3494
+ /* @__PURE__ */ jsxs(
3495
+ "div",
3496
+ {
3497
+ className: cn(
3498
+ "relative box-border flex min-w-0 items-stretch overflow-hidden border border-solid border-(--field-bc)",
3499
+ interactive && !borderless && "hover:border-(--sys-color-field-border-focus) hover:shadow-[0px_0px_4px_0px_#0071ff66] focus-within:border-(--sys-color-field-border-focus) focus-within:shadow-[0px_0px_4px_0px_#0071ff66]",
3500
+ disabled && "cursor-not-allowed"
3501
+ ),
3502
+ style: {
3503
+ // @ts-expect-error CSS 변수
3504
+ "--field-bc": borderColor,
3505
+ // focused/hovered prop이 명시된 경우 강조를 즉시 반영
3506
+ ...interactive && !borderless && focused ? { boxShadow: "0px 0px 4px 0px #0071ff66" } : {},
3507
+ ...multiline ? { minHeight: s.height } : { height: s.height },
3508
+ "width": cssLen(width),
3509
+ "borderRadius": s.radius,
3510
+ "background": bgColor,
3511
+ "color": textColor
3512
+ },
3513
+ "data-focused": focused || void 0,
3514
+ "data-hovered": hovered || void 0,
3515
+ children: [
3516
+ effectiveAddonLabel !== "" && /* @__PURE__ */ jsxs(
3517
+ "div",
3518
+ {
3519
+ className: "flex shrink-0 items-center gap-(--cmp-field-addonLabel-gap) whitespace-nowrap border-r border-solid px-(--cmp-field-addonLabel-paddingX) text-12 font-medium leading-20",
3520
+ style: {
3521
+ // 원본 sd-field: labelWidth가 addon 폭(--sd-field-addon-width)에도 적용됨
3522
+ width: cssLen(labelWidth),
3523
+ justifyContent: ALIGN[addonAlign],
3524
+ background: "var(--cmp-field-addonLabel-bg)",
3525
+ borderRightColor: disabled ? "var(--cmp-field-addonLabel-border-disabled)" : "var(--cmp-field-addonLabel-border-default)",
3526
+ borderTopLeftRadius: s.radius - 1,
3527
+ borderBottomLeftRadius: s.radius - 1
3528
+ },
3529
+ children: [
3530
+ icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon }),
3531
+ effectiveAddonLabel
3532
+ ]
3533
+ }
3534
+ ),
3535
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1", children })
3536
+ ]
3537
+ }
3538
+ ),
3539
+ showError ? /* @__PURE__ */ jsx("div", { className: "mt-(--cmp-field-contents-gap) text-12 font-normal leading-20 text-(--cmp-field-errorMessage-color)", children: errorMessage }) : hint !== "" ? /* @__PURE__ */ jsx("div", { className: "mt-(--cmp-field-contents-gap) text-12 font-normal leading-20 text-(--cmp-field-hint-color)", children: hint }) : null
3540
+ ] })
3541
+ ] })
3505
3542
  }
3506
3543
  );
3507
- const text = (tooltipText ?? "").trim();
3508
- if (text === "") return btn;
3509
- return /* @__PURE__ */ jsxs(RPopover.Root, { open, onOpenChange: setOpen, children: [
3510
- /* @__PURE__ */ jsx(RPopover.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
3511
- "span",
3512
- {
3513
- className: "inline-flex",
3514
- onMouseEnter: openNow,
3515
- onMouseLeave: startHide,
3516
- onFocus: openNow,
3517
- onBlur: startHide,
3518
- children: btn
3519
- }
3520
- ) }),
3521
- /* @__PURE__ */ jsx(RPopover.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
3522
- RPopover.Content,
3523
- {
3524
- side: "top",
3525
- sideOffset: 4,
3526
- className: cn("z-50", FLOATING_SLIDE_ANIM),
3527
- "aria-hidden": true,
3528
- onOpenAutoFocus: (e) => e.preventDefault(),
3529
- onMouseEnter: cancelHide,
3530
- onMouseLeave: startHide,
3531
- children: /* @__PURE__ */ jsx(STag, { shape: "square", size: "xs", color: "grey", label: text })
3532
- }
3533
- ) })
3534
- ] });
3535
- }
3544
+ });
3536
3545
  var FONT = {
3537
3546
  sm: { fontSize: 12, lineHeight: 20, paddingX: "var(--cmp-textinput-sm-paddingX)" },
3538
3547
  md: { fontSize: 14, lineHeight: 24, paddingX: "var(--cmp-textinput-md-paddingX)" }
@@ -3787,8 +3796,8 @@ var STextarea = forwardRef(function STextarea2({
3787
3796
  );
3788
3797
  });
3789
3798
  var PAD2 = {
3790
- sm: { fontSize: 12, stepper: 20, icon: 12 },
3791
- md: { fontSize: 14, stepper: 24, icon: 14 }
3799
+ sm: { fontSize: 12, lineHeight: 20, stepper: 20, icon: 12 },
3800
+ md: { fontSize: 14, lineHeight: 24, stepper: 24, icon: 14 }
3792
3801
  };
3793
3802
  var toNumber = (val) => {
3794
3803
  if (val === null || val === void 0 || val === "") return null;
@@ -3806,6 +3815,38 @@ var formatWithCommas = (value) => {
3806
3815
  const result = isNeg ? "-" + formatted : formatted;
3807
3816
  return decPart !== void 0 && decPart !== "" ? result + "." + decPart : result;
3808
3817
  };
3818
+ var addCommas = (value) => value.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
3819
+ var formatInputWithCommas = (value) => {
3820
+ const isNeg = value.startsWith("-");
3821
+ const unsigned = isNeg ? value.slice(1) : value;
3822
+ const hasDecimal = unsigned.includes(".");
3823
+ const [intPart = "", decPart = ""] = unsigned.split(".");
3824
+ const formattedInt = addCommas(intPart === "" ? "0" : intPart);
3825
+ return `${isNeg ? "-" : ""}${formattedInt}${hasDecimal ? `.${decPart}` : ""}`;
3826
+ };
3827
+ var getDecimalLimit = (useDecimal, maxDecimalLength) => {
3828
+ if (!useDecimal || maxDecimalLength === void 0 || !Number.isFinite(maxDecimalLength)) {
3829
+ return void 0;
3830
+ }
3831
+ return Math.max(0, Math.floor(maxDecimalLength));
3832
+ };
3833
+ var applyDecimalLimit = (value, decimalLimit) => {
3834
+ if (value === null || decimalLimit === void 0) return value;
3835
+ return Number(value.toFixed(decimalLimit));
3836
+ };
3837
+ var getLogicalCaret = (value, caret) => {
3838
+ return value.slice(0, caret ?? value.length).replace(/,/g, "").length;
3839
+ };
3840
+ var getDisplayCaret = (value, logicalCaret) => {
3841
+ if (logicalCaret <= 0) return 0;
3842
+ let seen = 0;
3843
+ for (let i = 0; i < value.length; i += 1) {
3844
+ if (value[i] === ",") continue;
3845
+ seen += 1;
3846
+ if (seen >= logicalCaret) return i + 1;
3847
+ }
3848
+ return value.length;
3849
+ };
3809
3850
  var SNumberInput = forwardRef(function SNumberInput2({
3810
3851
  value,
3811
3852
  onValueChange,
@@ -3815,6 +3856,7 @@ var SNumberInput = forwardRef(function SNumberInput2({
3815
3856
  step = 1,
3816
3857
  useButton = false,
3817
3858
  useDecimal = false,
3859
+ maxDecimalLength,
3818
3860
  inputPrefix = "",
3819
3861
  inputSuffix = "",
3820
3862
  inputClass,
@@ -3846,7 +3888,8 @@ var SNumberInput = forwardRef(function SNumberInput2({
3846
3888
  }, ref) {
3847
3889
  const p = PAD2[size];
3848
3890
  const padX = `var(--cmp-numberInput-${size}-paddingX)`;
3849
- const numValue = toNumber(value);
3891
+ const decimalLimit = getDecimalLimit(useDecimal, maxDecimalLength);
3892
+ const numValue = applyDecimalLimit(toNumber(value), decimalLimit);
3850
3893
  const [display, setDisplay] = useState(() => formatWithCommas(numValue));
3851
3894
  const [ruleError, setRuleError] = useState("");
3852
3895
  const innerRef = useRef(null);
@@ -3856,8 +3899,9 @@ var SNumberInput = forwardRef(function SNumberInput2({
3856
3899
  else if (ref) ref.current = el;
3857
3900
  };
3858
3901
  useEffect(() => {
3859
- setDisplay(formatWithCommas(toNumber(value)));
3860
- }, [value]);
3902
+ if (document.activeElement === innerRef.current) return;
3903
+ setDisplay(formatWithCommas(applyDecimalLimit(toNumber(value), decimalLimit)));
3904
+ }, [decimalLimit, value]);
3861
3905
  useFormField({
3862
3906
  name,
3863
3907
  validate: () => {
@@ -3869,48 +3913,87 @@ var SNumberInput = forwardRef(function SNumberInput2({
3869
3913
  });
3870
3914
  const resolvedError = error || ruleError !== "";
3871
3915
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
3872
- const emit = (n) => {
3873
- setDisplay(formatWithCommas(n));
3874
- onValueChange?.(n);
3916
+ const restoreCaret = (nextDisplay, logicalCaret) => {
3917
+ const input = innerRef.current;
3918
+ if (logicalCaret === void 0 || !input || document.activeElement !== input) return;
3919
+ const nextCaret = getDisplayCaret(nextDisplay, logicalCaret);
3920
+ requestAnimationFrame(() => {
3921
+ if (document.activeElement !== input) return;
3922
+ input.setSelectionRange(nextCaret, nextCaret);
3923
+ });
3924
+ };
3925
+ const setDisplayWithCaret = (nextDisplay, logicalCaret) => {
3926
+ setDisplay(nextDisplay);
3927
+ restoreCaret(nextDisplay, logicalCaret);
3928
+ };
3929
+ const resetRejectedInput = (target, logicalCaret) => {
3930
+ target.value = display;
3931
+ restoreCaret(display, Math.min(logicalCaret, display.replace(/,/g, "").length));
3932
+ };
3933
+ const emit = (n, logicalCaret) => {
3934
+ const next = applyDecimalLimit(n, decimalLimit);
3935
+ const nextDisplay = formatWithCommas(next);
3936
+ setDisplayWithCaret(nextDisplay, logicalCaret);
3937
+ onValueChange?.(next);
3875
3938
  };
3876
3939
  const incDisabled = disabled || readOnly || numValue !== null && numValue >= max;
3877
3940
  const decDisabled = disabled || readOnly || numValue !== null && numValue <= min;
3878
3941
  const increment = () => {
3879
3942
  if (incDisabled) return;
3880
3943
  const cur = numValue ?? (min === Number.NEGATIVE_INFINITY ? 0 : min);
3881
- const next = Math.min(cur + step, max);
3944
+ const next = applyDecimalLimit(Math.min(cur + step, max), decimalLimit);
3882
3945
  if (next !== cur) emit(next);
3883
3946
  };
3884
3947
  const decrement = () => {
3885
3948
  if (decDisabled) return;
3886
3949
  const cur = numValue ?? (min === Number.NEGATIVE_INFINITY ? 0 : min);
3887
- const next = Math.max(cur - step, min);
3950
+ const next = applyDecimalLimit(Math.max(cur - step, min), decimalLimit);
3888
3951
  if (next !== cur) emit(next);
3889
3952
  };
3890
- const handleInput = (raw) => {
3953
+ const handleInput = (event) => {
3954
+ const target = event.currentTarget;
3955
+ const raw = target.value;
3956
+ const logicalCaret = getLogicalCaret(raw, target.selectionStart);
3891
3957
  if (raw === "") {
3892
- setDisplay("");
3958
+ setDisplayWithCaret("", 0);
3893
3959
  onValueChange?.(null);
3894
3960
  return;
3895
3961
  }
3896
3962
  const cleaned = raw.replace(/,/g, "");
3897
3963
  const decimals = (cleaned.match(/\./g) || []).length;
3898
- if (decimals > 1) return;
3964
+ if (decimals > 1) {
3965
+ resetRejectedInput(target, logicalCaret);
3966
+ return;
3967
+ }
3899
3968
  if (cleaned === "-" || cleaned === "." || cleaned === "-.") {
3900
- setDisplay(cleaned);
3969
+ setDisplayWithCaret(cleaned, logicalCaret);
3970
+ return;
3971
+ }
3972
+ if (!useDecimal && cleaned.includes(".")) {
3973
+ resetRejectedInput(target, logicalCaret);
3901
3974
  return;
3902
3975
  }
3903
- if (!useDecimal && cleaned.includes(".")) return;
3976
+ if (decimalLimit !== void 0 && cleaned.includes(".")) {
3977
+ const fraction = cleaned.split(".")[1] ?? "";
3978
+ if (fraction.length > decimalLimit) {
3979
+ resetRejectedInput(target, logicalCaret);
3980
+ return;
3981
+ }
3982
+ }
3904
3983
  if (cleaned.endsWith(".") && decimals === 1) {
3905
3984
  const parsed2 = toNumber(cleaned.slice(0, -1));
3906
3985
  if (parsed2 !== null) {
3907
- setDisplay(formatWithCommas(parsed2) + ".");
3986
+ setDisplayWithCaret(formatInputWithCommas(cleaned), logicalCaret);
3908
3987
  return;
3909
3988
  }
3910
3989
  }
3911
3990
  const parsed = toNumber(cleaned);
3912
- if (parsed === null) return;
3913
- emit(parsed);
3991
+ if (parsed === null) {
3992
+ resetRejectedInput(target, logicalCaret);
3993
+ return;
3994
+ }
3995
+ setDisplayWithCaret(formatInputWithCommas(cleaned), logicalCaret);
3996
+ onValueChange?.(applyDecimalLimit(parsed, decimalLimit));
3914
3997
  };
3915
3998
  const stepper = (dir) => {
3916
3999
  const off = dir === "inc" ? incDisabled : decDisabled;
@@ -3923,8 +4006,8 @@ var SNumberInput = forwardRef(function SNumberInput2({
3923
4006
  "aria-label": dir === "inc" ? "\uC99D\uAC00" : "\uAC10\uC18C",
3924
4007
  onClick: dir === "inc" ? increment : decrement,
3925
4008
  className: cn(
3926
- "flex flex-shrink-0 items-center justify-center",
3927
- off ? "cursor-not-allowed" : "cursor-pointer"
4009
+ "flex flex-shrink-0 items-center justify-center border border-solid border-transparent transition-colors",
4010
+ off ? "cursor-not-allowed" : "cursor-pointer hover:border-[var(--color-border-accentLight)]"
3928
4011
  ),
3929
4012
  style: {
3930
4013
  width: `var(--cmp-numberInput-stepper-${size}-size)`,
@@ -3973,24 +4056,26 @@ var SNumberInput = forwardRef(function SNumberInput2({
3973
4056
  style: {
3974
4057
  // 원본 sd-number-input: content gap = padding-x 토큰 (gap 토큰 아님)
3975
4058
  gap: `var(--cmp-numberInput-${size}-paddingX)`,
3976
- padding: `0 ${useButton ? padX : `calc(${padX} * 2)`}`
4059
+ padding: `0 ${useButton ? padX : `calc(${padX} * 2)`}`,
4060
+ fontSize: p.fontSize,
4061
+ lineHeight: `${p.lineHeight}px`
3977
4062
  },
3978
4063
  children: [
3979
4064
  useButton && stepper("dec"),
3980
- inputPrefix !== "" && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", style: { fontSize: p.fontSize, color: affixColor }, children: inputPrefix }),
4065
+ inputPrefix !== "" && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", style: { color: affixColor }, children: inputPrefix }),
3981
4066
  /* @__PURE__ */ jsx(
3982
4067
  "input",
3983
4068
  {
3984
4069
  ref: setRefs,
3985
4070
  name,
3986
4071
  type: "text",
3987
- inputMode: "numeric",
4072
+ inputMode: useDecimal ? "decimal" : "numeric",
3988
4073
  value: display,
3989
4074
  placeholder,
3990
4075
  disabled,
3991
4076
  readOnly,
3992
4077
  autoFocus,
3993
- onChange: (e) => handleInput(e.target.value),
4078
+ onChange: handleInput,
3994
4079
  onKeyDown: (e) => {
3995
4080
  if (e.key === "ArrowUp") {
3996
4081
  e.preventDefault();
@@ -4002,26 +4087,29 @@ var SNumberInput = forwardRef(function SNumberInput2({
4002
4087
  },
4003
4088
  onFocus: (e) => onFocus?.(e),
4004
4089
  onBlur: (e) => {
4005
- const n = toNumber(display);
4090
+ const cleaned = display.replace(/,/g, "");
4091
+ const n = toNumber(cleaned);
4006
4092
  const clamped = n === null ? null : Math.min(Math.max(n, min), max);
4007
4093
  if (clamped !== n) emit(clamped);
4008
- else setDisplay(formatWithCommas(n));
4094
+ else setDisplay(n === null ? "" : formatInputWithCommas(cleaned));
4009
4095
  if (rules && rules.length > 0) setRuleError(runRules(clamped, rules));
4010
4096
  onBlur?.(e);
4011
4097
  },
4012
4098
  className: cn(
4013
- "h-full min-w-0 flex-1 border-0 bg-transparent p-0 outline-none placeholder:text-[color:var(--cmp-numberInput-text-placeholder)] disabled:cursor-not-allowed",
4099
+ // h-full 대신 라인박스 높이 그대로 두어 prefix/suffix 와 동일하게 flex 로 수직 센터링
4100
+ // (h-full 이면 input 내부 텍스트 센터링을 브라우저가 수행해 1px 어긋날 수 있음)
4101
+ "min-w-0 flex-1 border-0 bg-transparent p-0 outline-none placeholder:text-[color:var(--cmp-numberInput-text-placeholder)] disabled:cursor-not-allowed",
4014
4102
  inputClass
4015
4103
  ),
4016
4104
  style: {
4017
- fontSize: p.fontSize,
4105
+ // fontSize/lineHeight 는 컨테이너에서 상속
4018
4106
  textAlign: useButton ? "center" : "right",
4019
4107
  color: disabled ? "var(--cmp-numberInput-text-disabled)" : "var(--cmp-numberInput-text-default)",
4020
4108
  ...inputStyle
4021
4109
  }
4022
4110
  }
4023
4111
  ),
4024
- inputSuffix !== "" && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", style: { fontSize: p.fontSize, color: affixColor }, children: inputSuffix }),
4112
+ inputSuffix !== "" && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", style: { color: affixColor }, children: inputSuffix }),
4025
4113
  useButton && stepper("inc")
4026
4114
  ]
4027
4115
  }
@@ -4035,15 +4123,28 @@ function flattenOptions(options, depth = 1) {
4035
4123
  const out = [];
4036
4124
  for (const o of options) {
4037
4125
  const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4038
- out.push({
4039
- value: String(o.value),
4040
- label: o.label,
4041
- disabled: !!o.disabled,
4042
- depth,
4043
- isGroup: hasChildren,
4044
- raw: o.value
4045
- });
4046
- if (hasChildren) out.push(...flattenOptions(o.children, depth + 1));
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
+ }
4047
4148
  }
4048
4149
  return out;
4049
4150
  }
@@ -4167,6 +4268,9 @@ var SSelect = forwardRef(function SSelect2({
4167
4268
  );
4168
4269
  const flat = useMemo(() => flattenOptions(options), [options]);
4169
4270
  const selectable = useMemo(() => selectableOptions(flat), [flat]);
4271
+ const hasLeaf = useMemo(() => flat.some((o) => !o.isGroup), [flat]);
4272
+ const allOptionsDisabled = hasLeaf && selectable.length === 0;
4273
+ const effectiveDisabled = disabled || allOptionsDisabled;
4170
4274
  const showSearch = useSearch && selectable.length >= SEARCH_THRESHOLD;
4171
4275
  const labelByValue = useMemo(() => new Map(flat.map((o) => [o.value, o.label])), [flat]);
4172
4276
  const rawByValue = useMemo(() => new Map(flat.map((o) => [o.value, o.raw])), [flat]);
@@ -4233,10 +4337,10 @@ var SSelect = forwardRef(function SSelect2({
4233
4337
  Ariakit.Select,
4234
4338
  {
4235
4339
  ref: triggerRef,
4236
- disabled,
4340
+ disabled: effectiveDisabled,
4237
4341
  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",
4238
4342
  style: {
4239
- color: disabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
4343
+ color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
4240
4344
  },
4241
4345
  children: [
4242
4346
  isHtmlLabel(triggerText) ? /* @__PURE__ */ jsx(
@@ -4251,7 +4355,7 @@ var SSelect = forwardRef(function SSelect2({
4251
4355
  {
4252
4356
  name: "chevronDown",
4253
4357
  size: 12,
4254
- color: disabled ? "var(--cmp-select-icon-disabled)" : "var(--cmp-select-icon-default)",
4358
+ color: effectiveDisabled ? "var(--cmp-select-icon-disabled)" : "var(--cmp-select-icon-default)",
4255
4359
  className: cn("shrink-0 transition-transform duration-200", open && "rotate-180")
4256
4360
  }
4257
4361
  )
@@ -4265,7 +4369,7 @@ var SSelect = forwardRef(function SSelect2({
4265
4369
  portalElement: portalContainer,
4266
4370
  gutter: 4,
4267
4371
  sameWidth: !dropdownWidth,
4268
- className: "z-50 overflow-auto rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] py-[4px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)] data-[enter]:animate-floating-in-bottom data-[leave]:animate-floating-out-bottom",
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",
4269
4373
  style: {
4270
4374
  maxHeight: dropdownHeight,
4271
4375
  ...dropdownWidth ? { width: dropdownWidth } : {},
@@ -4306,7 +4410,7 @@ var SSelect = forwardRef(function SSelect2({
4306
4410
  ),
4307
4411
  hasItems ? /* @__PURE__ */ jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
4308
4412
  // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
4309
- /* @__PURE__ */ jsx("div", { className: "px-[12px] py-[12px] text-center text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-disabled)]", children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
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." })
4310
4414
  )
4311
4415
  ]
4312
4416
  }
@@ -4329,7 +4433,7 @@ var SSelect = forwardRef(function SSelect2({
4329
4433
  error: error || ruleError !== "",
4330
4434
  status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4331
4435
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
4332
- disabled,
4436
+ disabled: effectiveDisabled,
4333
4437
  width,
4334
4438
  className,
4335
4439
  style,
@@ -4364,7 +4468,11 @@ function ItemList({
4364
4468
  {
4365
4469
  className: cn(
4366
4470
  "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4367
- 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"
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 && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4475
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4368
4476
  ),
4369
4477
  style: { paddingLeft: padLeft },
4370
4478
  children: o.label
@@ -4378,10 +4486,14 @@ function ItemList({
4378
4486
  {
4379
4487
  value: o.value,
4380
4488
  disabled: o.disabled,
4489
+ accessibleWhenDisabled: true,
4381
4490
  className: cn(
4382
4491
  itemBase,
4383
4492
  isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4384
- activeClass
4493
+ // Ariakit은 disabled 항목도 hover 시 active(data-active-item) 처리하므로,
4494
+ // 비활성 항목에는 hover 하이라이트를 적용하지 않아 회색 비활성 스타일을 유지한다.
4495
+ // (원본 Stencil의 `--focused...:not(--disabled)` 규칙과 동일한 의도)
4496
+ !o.disabled && activeClass
4385
4497
  ),
4386
4498
  style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4387
4499
  render: useSearch ? /* @__PURE__ */ jsx(Ariakit.ComboboxItem, {}) : void 0,
@@ -4391,15 +4503,21 @@ function ItemList({
4391
4503
  "span",
4392
4504
  {
4393
4505
  className: cn(
4394
- "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4395
- 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"
4506
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4507
+ o.disabled ? (
4508
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4509
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4510
+ ) : cn(
4511
+ "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4512
+ 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"
4513
+ )
4396
4514
  ),
4397
4515
  children: isSelected && /* @__PURE__ */ jsx(
4398
4516
  SIcon,
4399
4517
  {
4400
4518
  name: "checkboxCheck",
4401
4519
  size: 12,
4402
- color: "var(--cmp-checkbox-checked-icon-default)"
4520
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4403
4521
  }
4404
4522
  )
4405
4523
  }
@@ -4523,7 +4641,7 @@ function SRadioGroup({
4523
4641
  role: "radiogroup",
4524
4642
  className: cn(
4525
4643
  "inline-flex",
4526
- direction === "vertical" ? "flex-col gap-[8px]" : "flex-row gap-[20px]",
4644
+ direction === "vertical" ? "flex-col gap-[var(--cmp-radio-group-gap-vertical)]" : "flex-row gap-[var(--cmp-radio-group-gap-horizontal)]",
4527
4645
  className
4528
4646
  ),
4529
4647
  style,
@@ -4805,7 +4923,7 @@ function SCalendar({
4805
4923
  "span",
4806
4924
  {
4807
4925
  className: cn(
4808
- "flex h-[var(--cmp-datepicker-calendar-day-circle-size)] w-[var(--cmp-datepicker-calendar-day-circle-size)] items-center justify-center rounded-[var(--cmp-datepicker-calendar-day-circle-radius)] border border-solid border-transparent text-[14px]",
4926
+ "flex h-[var(--cmp-datepicker-calendar-day-circle-size)] w-[var(--cmp-datepicker-calendar-day-circle-size)] items-center justify-center rounded-[var(--cmp-datepicker-calendar-day-circle-radius)] border border-solid border-transparent text-[14px] leading-[var(--cmp-datepicker-calendar-day-circle-size)]",
4809
4927
  selected ? "bg-[var(--cmp-datepicker-calendar-day-select-bg)] text-[color:var(--cmp-datepicker-calendar-day-select-text)]" : disabled ? "text-[color:var(--cmp-datepicker-calendar-day-disabled-text)]" : "text-[color:var(--cmp-datepicker-calendar-day-default-text)] hover:border-[color:var(--cmp-datepicker-calendar-day-hover-border)]",
4810
4928
  isToday && !disabled && "font-bold"
4811
4929
  ),
@@ -4930,8 +5048,8 @@ function SDatePicker({
4930
5048
  disabled,
4931
5049
  className,
4932
5050
  style,
4933
- children: /* @__PURE__ */ jsxs(RPopover.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
4934
- /* @__PURE__ */ jsx(RPopover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
5051
+ children: /* @__PURE__ */ jsxs(RPopover2.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
5052
+ /* @__PURE__ */ jsx(RPopover2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
4935
5053
  "button",
4936
5054
  {
4937
5055
  type: "button",
@@ -4967,8 +5085,8 @@ function SDatePicker({
4967
5085
  ]
4968
5086
  }
4969
5087
  ) }),
4970
- /* @__PURE__ */ jsx(RPopover.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
4971
- RPopover.Content,
5088
+ /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
5089
+ RPopover2.Content,
4972
5090
  {
4973
5091
  side: "bottom",
4974
5092
  align: "start",
@@ -4995,6 +5113,7 @@ function RangeCalendar({
4995
5113
  selectable,
4996
5114
  maxRange,
4997
5115
  onSelect,
5116
+ onPendingStartChange,
4998
5117
  onViewChange
4999
5118
  }) {
5000
5119
  const today = todayStr();
@@ -5057,9 +5176,11 @@ function RangeCalendar({
5057
5176
  setStart(d);
5058
5177
  setEnd(null);
5059
5178
  setHover(null);
5179
+ onPendingStartChange?.(d);
5060
5180
  return;
5061
5181
  }
5062
5182
  setEnd(d);
5183
+ onPendingStartChange?.(null);
5063
5184
  onSelect([start, d]);
5064
5185
  };
5065
5186
  const GRID_COLS = "grid grid-cols-[repeat(7,var(--cmp-datepicker-calendar-day-width))]";
@@ -5194,8 +5315,11 @@ function SDateRangePicker({
5194
5315
  const portalContainer = usePortalContainer();
5195
5316
  const [open, setOpen] = useState(false);
5196
5317
  const [ruleError, setRuleError] = useState("");
5318
+ const [pendingStart, setPendingStart] = useState(null);
5197
5319
  const hasValue = value != null && value[0] !== "" && value[1] !== "";
5198
- const display = hasValue ? `${value[0]} ~ ${value[1]}` : placeholder;
5320
+ const hasDisplay = pendingStart != null || hasValue;
5321
+ const startText = pendingStart ?? (hasValue ? value[0] : "");
5322
+ const endText = pendingStart ? "" : hasValue ? value[1] : "";
5199
5323
  useFormField({
5200
5324
  name,
5201
5325
  validate: () => {
@@ -5209,6 +5333,7 @@ function SDateRangePicker({
5209
5333
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
5210
5334
  const dims = size === "md" ? { padX: 16, gap: 12, icon: 20, minW: 104, fontSize: 14, lineHeight: 24, radius: 6 } : { padX: 12, gap: 8, icon: 16, minW: 80, fontSize: 12, lineHeight: 20, radius: 4 };
5211
5335
  const select = (r) => {
5336
+ setPendingStart(null);
5212
5337
  onValueChange?.(r);
5213
5338
  if (rules && rules.length > 0) setRuleError(runRules(r, rules));
5214
5339
  setOpen(false);
@@ -5233,61 +5358,80 @@ function SDateRangePicker({
5233
5358
  disabled,
5234
5359
  className,
5235
5360
  style,
5236
- children: /* @__PURE__ */ jsxs(RPopover.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
5237
- /* @__PURE__ */ jsx(RPopover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
5238
- "button",
5239
- {
5240
- type: "button",
5241
- name,
5242
- disabled,
5243
- className: "flex h-full w-full items-center overflow-hidden bg-transparent outline-none disabled:cursor-not-allowed",
5244
- style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
5245
- children: [
5246
- /* @__PURE__ */ jsx(
5247
- SIcon,
5248
- {
5249
- name: "date",
5250
- size: dims.icon,
5251
- color: disabled ? "var(--cmp-datepicker-icon-disabled)" : "var(--cmp-datepicker-icon-default)",
5252
- className: "flex-shrink-0"
5253
- }
5254
- ),
5255
- /* @__PURE__ */ jsx(
5256
- "span",
5257
- {
5258
- className: "flex-1 truncate text-center",
5259
- style: {
5260
- minWidth: dims.minW,
5261
- fontSize: dims.fontSize,
5262
- lineHeight: `${dims.lineHeight}px`,
5263
- color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasValue ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
5264
- },
5265
- children: display
5266
- }
5267
- )
5268
- ]
5269
- }
5270
- ) }),
5271
- /* @__PURE__ */ jsx(RPopover.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
5272
- RPopover.Content,
5273
- {
5274
- side: "bottom",
5275
- align: "start",
5276
- sideOffset: 4,
5277
- className: cn("z-50", FLOATING_SLIDE_ANIM),
5278
- children: /* @__PURE__ */ jsx(
5279
- RangeCalendar,
5361
+ children: /* @__PURE__ */ jsxs(
5362
+ RPopover2.Root,
5363
+ {
5364
+ open,
5365
+ onOpenChange: (o) => {
5366
+ if (disabled) return;
5367
+ setOpen(o);
5368
+ if (!o) setPendingStart(null);
5369
+ },
5370
+ children: [
5371
+ /* @__PURE__ */ jsx(RPopover2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
5372
+ "button",
5280
5373
  {
5281
- value: value ?? null,
5282
- selectable,
5283
- maxRange,
5284
- onSelect: select,
5285
- onViewChange
5374
+ type: "button",
5375
+ name,
5376
+ disabled,
5377
+ className: "flex h-full w-full items-center overflow-hidden bg-transparent outline-none disabled:cursor-not-allowed",
5378
+ style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
5379
+ children: [
5380
+ /* @__PURE__ */ jsx(
5381
+ SIcon,
5382
+ {
5383
+ name: "date",
5384
+ size: dims.icon,
5385
+ color: disabled ? "var(--cmp-datepicker-icon-disabled)" : "var(--cmp-datepicker-icon-default)",
5386
+ className: "flex-shrink-0"
5387
+ }
5388
+ ),
5389
+ /* @__PURE__ */ jsx(
5390
+ "span",
5391
+ {
5392
+ className: cn(
5393
+ "flex-1 items-center",
5394
+ hasDisplay ? "flex" : "block truncate text-center"
5395
+ ),
5396
+ style: {
5397
+ minWidth: dims.minW,
5398
+ fontSize: dims.fontSize,
5399
+ lineHeight: `${dims.lineHeight}px`,
5400
+ color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasDisplay ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
5401
+ },
5402
+ children: hasDisplay ? /* @__PURE__ */ jsxs(Fragment, { children: [
5403
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-right", children: startText }),
5404
+ /* @__PURE__ */ jsx("span", { className: "px-[4px]", children: "~" }),
5405
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: endText })
5406
+ ] }) : placeholder
5407
+ }
5408
+ )
5409
+ ]
5286
5410
  }
5287
- )
5288
- }
5289
- ) })
5290
- ] })
5411
+ ) }),
5412
+ /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
5413
+ RPopover2.Content,
5414
+ {
5415
+ side: "bottom",
5416
+ align: "start",
5417
+ sideOffset: 4,
5418
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
5419
+ children: /* @__PURE__ */ jsx(
5420
+ RangeCalendar,
5421
+ {
5422
+ value: value ?? null,
5423
+ selectable,
5424
+ maxRange,
5425
+ onSelect: select,
5426
+ onPendingStartChange: setPendingStart,
5427
+ onViewChange
5428
+ }
5429
+ )
5430
+ }
5431
+ ) })
5432
+ ]
5433
+ }
5434
+ )
5291
5435
  }
5292
5436
  );
5293
5437
  }
@@ -5296,7 +5440,7 @@ var SChip = forwardRef(function SChip2({
5296
5440
  value = "",
5297
5441
  error: errorProp = false,
5298
5442
  disabled = false,
5299
- editable = true,
5443
+ editable = false,
5300
5444
  showRemove = true,
5301
5445
  placeholder = "",
5302
5446
  rules,
@@ -5449,6 +5593,7 @@ function SFilePicker({
5449
5593
  hovered = false,
5450
5594
  inline = false,
5451
5595
  multiple = false,
5596
+ useMultipleListBox = false,
5452
5597
  accept,
5453
5598
  maxFileSize,
5454
5599
  maxTotalSize,
@@ -5474,10 +5619,13 @@ function SFilePicker({
5474
5619
  const autoId = useId();
5475
5620
  const resolvedName = name ?? autoId;
5476
5621
  const inputRef = useRef(null);
5622
+ const contentBoxRef = useRef(null);
5477
5623
  const [ruleError, setRuleError] = useState("");
5624
+ const [listOpen, setListOpen] = useState(false);
5625
+ const portalContainer = usePortalContainer();
5478
5626
  const files = multiple ? Array.isArray(value) ? value : [] : value && !Array.isArray(value) ? value : null;
5479
- const hasSingle = !multiple && !!files;
5480
- const displayText = hasSingle ? files.name : placeholder;
5627
+ const hasFiles = multiple ? files.length > 0 : !!files;
5628
+ const displayText = !hasFiles ? placeholder : multiple ? files.map((f) => f.name).join(", ") : files.name;
5481
5629
  useFormField({
5482
5630
  name,
5483
5631
  validate: () => {
@@ -5528,10 +5676,12 @@ function SFilePicker({
5528
5676
  e.stopPropagation();
5529
5677
  if (disabled) return;
5530
5678
  onValueChange?.(multiple ? [] : null);
5679
+ setListOpen(false);
5531
5680
  };
5532
5681
  const removeChip = (i) => {
5533
5682
  const next = files.filter((_, idx) => idx !== i);
5534
5683
  onValueChange?.(next);
5684
+ if (next.length === 0) setListOpen(false);
5535
5685
  };
5536
5686
  const open = () => {
5537
5687
  if (!disabled) inputRef.current?.click();
@@ -5541,78 +5691,100 @@ function SFilePicker({
5541
5691
  const padX = size === "md" ? "var(--cmp-filepicker-md-paddingX)" : "var(--cmp-filepicker-sm-paddingX)";
5542
5692
  const iconSize = size === "md" ? 20 : 16;
5543
5693
  const gapCls = size === "md" ? "gap-[var(--cmp-filepicker-md-gap)]" : "gap-[var(--cmp-filepicker-sm-gap)]";
5544
- const content = /* @__PURE__ */ jsxs("div", { className: cn("flex h-full w-full items-center", gapCls), style: { padding: `0 ${padX}` }, children: [
5694
+ const inlinePlaceholder = inline && !disabled && !hasFiles;
5695
+ const showListbox = multiple && useMultipleListBox;
5696
+ const filesArr = multiple ? files : [];
5697
+ const contentInner = /* @__PURE__ */ jsxs(
5698
+ "div",
5699
+ {
5700
+ ref: contentBoxRef,
5701
+ className: cn("group/filepicker flex h-full w-full items-center", gapCls),
5702
+ style: { padding: inline ? 0 : `0 ${padX}` },
5703
+ children: [
5704
+ /* @__PURE__ */ jsx(
5705
+ "input",
5706
+ {
5707
+ ref: inputRef,
5708
+ type: "file",
5709
+ className: "hidden",
5710
+ disabled,
5711
+ multiple,
5712
+ accept,
5713
+ name: resolvedName,
5714
+ onChange,
5715
+ "aria-label": placeholder
5716
+ }
5717
+ ),
5718
+ /* @__PURE__ */ jsxs(
5719
+ "button",
5720
+ {
5721
+ type: "button",
5722
+ disabled,
5723
+ onClick: open,
5724
+ "aria-label": placeholder,
5725
+ className: cn(
5726
+ "flex min-w-0 flex-1 items-center bg-transparent text-left",
5727
+ gapCls,
5728
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
5729
+ ),
5730
+ children: [
5731
+ /* @__PURE__ */ jsx(
5732
+ SIcon,
5733
+ {
5734
+ name: "attachFile",
5735
+ size: iconSize,
5736
+ color: disabled ? "var(--cmp-filepicker-icon-disabled)" : "var(--color-grey-65)"
5737
+ }
5738
+ ),
5739
+ /* @__PURE__ */ jsx(
5740
+ "span",
5741
+ {
5742
+ className: cn(
5743
+ "min-w-0 flex-1 truncate text-[12px]",
5744
+ inlinePlaceholder && "text-[color:var(--cmp-filepicker-text-placeholder)] transition-colors group-hover/filepicker:text-[color:var(--cmp-filepicker-text-default)]"
5745
+ ),
5746
+ style: inlinePlaceholder ? void 0 : {
5747
+ color: disabled ? "var(--cmp-filepicker-text-disabled)" : hasFiles ? "var(--cmp-filepicker-text-default)" : "var(--cmp-filepicker-text-placeholder)"
5748
+ },
5749
+ children: displayText
5750
+ }
5751
+ )
5752
+ ]
5753
+ }
5754
+ ),
5755
+ showListbox && hasFiles && !disabled && /* @__PURE__ */ jsx(
5756
+ Ariakit.PopoverDisclosure,
5757
+ {
5758
+ render: /* @__PURE__ */ jsx(SGhostButton, { icon: "caretDown", size: "xxs", ariaLabel: "toggle file list" }),
5759
+ className: cn("shrink-0 transition-transform duration-150", listOpen && "rotate-180")
5760
+ }
5761
+ ),
5762
+ !disabled && hasFiles && /* @__PURE__ */ jsx(SGhostButton, { icon: "close", size: "xxs", ariaLabel: "clear", onClick: clear })
5763
+ ]
5764
+ }
5765
+ );
5766
+ const content = showListbox ? /* @__PURE__ */ jsxs(Ariakit.PopoverProvider, { open: listOpen, setOpen: setListOpen, placement: "bottom-start", children: [
5767
+ contentInner,
5545
5768
  /* @__PURE__ */ jsx(
5546
- "input",
5547
- {
5548
- ref: inputRef,
5549
- type: "file",
5550
- className: "hidden",
5551
- disabled,
5552
- multiple,
5553
- accept,
5554
- name: resolvedName,
5555
- onChange,
5556
- "aria-label": placeholder
5557
- }
5558
- ),
5559
- /* @__PURE__ */ jsxs(
5560
- "button",
5769
+ Ariakit.Popover,
5561
5770
  {
5562
- type: "button",
5563
- disabled,
5564
- onClick: open,
5565
- "aria-label": placeholder,
5566
- className: cn(
5567
- "flex min-w-0 flex-1 items-center bg-transparent text-left",
5568
- gapCls,
5569
- disabled ? "cursor-not-allowed" : "cursor-pointer"
5570
- ),
5571
- children: [
5572
- /* @__PURE__ */ jsx(
5573
- SIcon,
5574
- {
5575
- name: "attachFile",
5576
- size: iconSize,
5577
- color: disabled ? "var(--cmp-filepicker-icon-disabled)" : "var(--color-grey-65)"
5578
- }
5579
- ),
5580
- /* @__PURE__ */ jsx(
5581
- "span",
5582
- {
5583
- className: "min-w-0 flex-1 truncate text-[12px]",
5584
- style: {
5585
- color: disabled ? "var(--cmp-filepicker-text-disabled)" : hasSingle ? "var(--cmp-filepicker-text-default)" : "var(--cmp-filepicker-text-placeholder)"
5586
- },
5587
- children: displayText
5588
- }
5589
- )
5590
- ]
5771
+ portal: true,
5772
+ portalElement: portalContainer,
5773
+ gutter: 4,
5774
+ unmountOnHide: true,
5775
+ getAnchorRect: () => contentBoxRef.current?.getBoundingClientRect() ?? null,
5776
+ style: { minWidth: contentBoxRef.current?.offsetWidth },
5777
+ className: "z-50 box-border flex flex-col items-stretch gap-[8px] rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] p-[8px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
5778
+ children: filesArr.map((f, i) => /* @__PURE__ */ jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i))
5591
5779
  }
5592
- ),
5593
- !disabled && hasSingle && /* @__PURE__ */ jsx(SGhostButton, { icon: "close", size: "xxs", ariaLabel: "clear", onClick: clear })
5594
- ] });
5595
- const chips = multiple && files.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-[8px] flex flex-wrap gap-[8px]", children: files.map((f, i) => /* @__PURE__ */ jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i)) });
5596
- if (inline) {
5597
- return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col", className), style, children: [
5598
- /* @__PURE__ */ jsx(
5599
- "div",
5600
- {
5601
- className: "box-border flex items-center rounded-[var(--cmp-filepicker-sm-radius)] border border-solid border-[color:var(--cmp-filepicker-border-default)] bg-[var(--cmp-filepicker-bg-default)]",
5602
- style: {
5603
- height: "var(--cmp-filepicker-sm-height)",
5604
- width: typeof width === "number" ? `${width}px` : width
5605
- },
5606
- children: content
5607
- }
5608
- ),
5609
- chips
5610
- ] });
5611
- }
5780
+ )
5781
+ ] }) : contentInner;
5782
+ 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)) });
5612
5783
  return /* @__PURE__ */ jsxs("div", { className, style, children: [
5613
5784
  /* @__PURE__ */ jsx(
5614
5785
  SField,
5615
5786
  {
5787
+ borderless: inline,
5616
5788
  label,
5617
5789
  labelWidth,
5618
5790
  icon,
@@ -5640,6 +5812,7 @@ function SKeyValueTable({
5640
5812
  fields = [],
5641
5813
  search = false,
5642
5814
  radius = "default",
5815
+ dense = false,
5643
5816
  onChange,
5644
5817
  onSearch,
5645
5818
  className,
@@ -5647,6 +5820,8 @@ function SKeyValueTable({
5647
5820
  }) {
5648
5821
  if (fields.length === 0) return null;
5649
5822
  const emit = (name, value) => onChange?.({ name, value });
5823
+ const cellHeight = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-keyValue-height)";
5824
+ const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-keyValue-body-paddingY)";
5650
5825
  const renderField = (f) => {
5651
5826
  if (f.render) return f.render;
5652
5827
  const opt = f.options ?? {};
@@ -5727,10 +5902,15 @@ function SKeyValueTable({
5727
5902
  rowSpan: f.thRowSpan && f.thRowSpan > 1 ? f.thRowSpan : void 0,
5728
5903
  colSpan: f.thColSpan && f.thColSpan > 1 ? f.thColSpan : void 0,
5729
5904
  className: cn(
5730
- "h-[var(--cmp-table-keyValue-height)] box-border bg-[var(--cmp-table-header-bg)] px-[var(--cmp-table-header-paddingX)] py-[var(--cmp-table-keyValue-body-paddingY)] text-left align-middle",
5905
+ "box-border bg-[var(--cmp-table-header-bg)] px-[var(--cmp-table-header-paddingX)] text-left align-middle",
5731
5906
  cellBorder,
5732
5907
  f.thClass
5733
5908
  ),
5909
+ style: {
5910
+ height: cellHeight,
5911
+ paddingTop: cellPadY,
5912
+ paddingBottom: cellPadY
5913
+ },
5734
5914
  children: /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full items-center justify-between gap-[var(--cmp-table-header-gap)]", children: [
5735
5915
  /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-[8px]", children: [
5736
5916
  /* @__PURE__ */ jsx("span", { className: "text-[12px] font-bold leading-[20px] text-[color:var(--sys-color-fg-primary)]", children: f.label }),
@@ -5756,10 +5936,11 @@ function SKeyValueTable({
5756
5936
  rowSpan: f.tdRowSpan && f.tdRowSpan > 1 ? f.tdRowSpan : void 0,
5757
5937
  colSpan: f.tdColSpan && f.tdColSpan > 1 ? f.tdColSpan : void 0,
5758
5938
  className: cn(
5759
- "h-[var(--cmp-table-keyValue-height)] box-border bg-[var(--sys-color-bg-frame)] px-[var(--cmp-table-keyValue-body-paddingX-default)] py-[var(--cmp-table-keyValue-body-paddingY)] align-middle",
5939
+ "box-border bg-[var(--sys-color-bg-frame)] px-[var(--cmp-table-keyValue-body-paddingX-default)] align-middle",
5760
5940
  cellBorder,
5761
5941
  f.tdClass
5762
5942
  ),
5943
+ style: { height: cellHeight, paddingTop: cellPadY, paddingBottom: cellPadY },
5763
5944
  children: renderField(f)
5764
5945
  },
5765
5946
  `td-${r}-${c}`