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
@@ -5,7 +5,7 @@ var react = require('react');
5
5
  var clsx = require('clsx');
6
6
  var tailwindMerge = require('tailwind-merge');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
- var RPopover = require('@radix-ui/react-popover');
8
+ var RPopover2 = require('@radix-ui/react-popover');
9
9
  var Ariakit = require('@ariakit/react');
10
10
  var RCheckbox = require('@radix-ui/react-checkbox');
11
11
 
@@ -27,7 +27,7 @@ function _interopNamespace(e) {
27
27
  return Object.freeze(n);
28
28
  }
29
29
 
30
- var RPopover__namespace = /*#__PURE__*/_interopNamespace(RPopover);
30
+ var RPopover2__namespace = /*#__PURE__*/_interopNamespace(RPopover2);
31
31
  var Ariakit__namespace = /*#__PURE__*/_interopNamespace(Ariakit);
32
32
  var RCheckbox__namespace = /*#__PURE__*/_interopNamespace(RCheckbox);
33
33
 
@@ -2901,11 +2901,200 @@ PortalContainerContext.Provider;
2901
2901
  function usePortalContainer() {
2902
2902
  return react.useContext(PortalContainerContext) ?? void 0;
2903
2903
  }
2904
+
2905
+ // src/components/STag/tag.config.ts
2906
+ var TAG_SIZE_CONFIG = {
2907
+ xs: {
2908
+ height: 20,
2909
+ paddingX: 8,
2910
+ gap: 4,
2911
+ radius: 6,
2912
+ iconSize: 12,
2913
+ fontSize: 11,
2914
+ fontWeight: 500,
2915
+ lineHeight: 18
2916
+ },
2917
+ sm: {
2918
+ height: 24,
2919
+ paddingX: 8,
2920
+ gap: 4,
2921
+ radius: 6,
2922
+ iconSize: 16,
2923
+ fontSize: 12,
2924
+ fontWeight: 700,
2925
+ lineHeight: 20
2926
+ },
2927
+ md: {
2928
+ height: 28,
2929
+ paddingX: 12,
2930
+ gap: 6,
2931
+ radius: 8,
2932
+ iconSize: 16,
2933
+ fontSize: 14,
2934
+ fontWeight: 700,
2935
+ lineHeight: 24
2936
+ }
2937
+ };
2938
+ var STag = react.forwardRef(function STag2({
2939
+ shape = "square",
2940
+ size = "sm",
2941
+ color = "grey",
2942
+ label = "",
2943
+ icon,
2944
+ iconLeft = true,
2945
+ width,
2946
+ className,
2947
+ style,
2948
+ ...rest
2949
+ }, ref) {
2950
+ const s = TAG_SIZE_CONFIG[size];
2951
+ const contentColor = `var(--cmp-tag-${color}-content)`;
2952
+ const iconNode = icon ? /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.iconSize, color: contentColor }) : null;
2953
+ const resolvedWidth = typeof width === "number" ? `${width}px` : width;
2954
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2955
+ "span",
2956
+ {
2957
+ ref,
2958
+ "aria-label": label || "tag",
2959
+ className: cn(
2960
+ "box-border inline-flex w-fit select-none items-center justify-center whitespace-nowrap",
2961
+ className
2962
+ ),
2963
+ style: {
2964
+ minHeight: s.height,
2965
+ padding: `0 ${s.paddingX}px`,
2966
+ gap: s.gap,
2967
+ borderRadius: shape === "pill" ? 999 : s.radius,
2968
+ fontSize: s.fontSize,
2969
+ fontWeight: s.fontWeight,
2970
+ lineHeight: `${s.lineHeight}px`,
2971
+ background: `var(--cmp-tag-${color}-bg)`,
2972
+ color: contentColor,
2973
+ ...resolvedWidth != null ? { width: resolvedWidth } : {},
2974
+ ...style
2975
+ },
2976
+ ...rest,
2977
+ children: [
2978
+ icon && iconLeft && iconNode,
2979
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex min-w-0 items-center justify-center", children: label }),
2980
+ icon && !iconLeft && iconNode
2981
+ ]
2982
+ }
2983
+ );
2984
+ });
2985
+ var SIZE = {
2986
+ xxs: { icon: 12, radius: "sm" },
2987
+ xs: { icon: 16, radius: "sm" },
2988
+ sm: { icon: 20, radius: "sm" },
2989
+ md: { icon: 24, radius: "md" },
2990
+ lg: { icon: 32, radius: "md" }
2991
+ };
2992
+ var TOOLTIP_HIDE_DELAY_MS = 100;
2993
+ function SGhostButton({
2994
+ icon,
2995
+ size = "sm",
2996
+ intent = "default",
2997
+ ariaLabel = "",
2998
+ tooltipText,
2999
+ disabled = false,
3000
+ onClick,
3001
+ className,
3002
+ style
3003
+ }) {
3004
+ const portalContainer = usePortalContainer();
3005
+ const [open, setOpen] = react.useState(false);
3006
+ const hideTimer = react.useRef(void 0);
3007
+ const cancelHide = () => {
3008
+ if (hideTimer.current) {
3009
+ clearTimeout(hideTimer.current);
3010
+ hideTimer.current = void 0;
3011
+ }
3012
+ };
3013
+ const openNow = () => {
3014
+ cancelHide();
3015
+ setOpen(true);
3016
+ };
3017
+ const startHide = () => {
3018
+ cancelHide();
3019
+ hideTimer.current = setTimeout(() => setOpen(false), TOOLTIP_HIDE_DELAY_MS);
3020
+ };
3021
+ const s = SIZE[size];
3022
+ const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
3023
+ const btn = /* @__PURE__ */ jsxRuntime.jsxs(
3024
+ "button",
3025
+ {
3026
+ type: "button",
3027
+ disabled,
3028
+ "aria-disabled": disabled ? "true" : void 0,
3029
+ "aria-label": ariaLabel.trim() || void 0,
3030
+ tabIndex: disabled ? -1 : void 0,
3031
+ onClick: (e) => {
3032
+ if (disabled) {
3033
+ e.preventDefault();
3034
+ return;
3035
+ }
3036
+ onClick?.(e);
3037
+ },
3038
+ className: cn(
3039
+ "group relative box-border inline-flex items-center justify-center border-none bg-transparent p-0 transition-shadow",
3040
+ disabled ? "cursor-not-allowed" : "cursor-pointer",
3041
+ "focus-visible:outline-none focus-visible:shadow-[0_0_0_2px_var(--cmp-ghostButton-action-hover-bg)]",
3042
+ className
3043
+ ),
3044
+ style: {
3045
+ width: `var(--cmp-ghostButton-${size}-size)`,
3046
+ height: `var(--cmp-ghostButton-${size}-size)`,
3047
+ borderRadius: `var(--cmp-ghostButton-radius-${s.radius})`,
3048
+ ...style
3049
+ },
3050
+ children: [
3051
+ !disabled && /* @__PURE__ */ jsxRuntime.jsx(
3052
+ "span",
3053
+ {
3054
+ "aria-hidden": true,
3055
+ className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover:opacity-5",
3056
+ style: { background: `var(--cmp-ghostButton-${intent}-hover-bg)` }
3057
+ }
3058
+ ),
3059
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: contentColor })
3060
+ ]
3061
+ }
3062
+ );
3063
+ const text = (tooltipText ?? "").trim();
3064
+ if (text === "") return btn;
3065
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
3066
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
3067
+ "span",
3068
+ {
3069
+ className: "inline-flex",
3070
+ onMouseEnter: openNow,
3071
+ onMouseLeave: startHide,
3072
+ onFocus: openNow,
3073
+ onBlur: startHide,
3074
+ children: btn
3075
+ }
3076
+ ) }),
3077
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
3078
+ RPopover2__namespace.Content,
3079
+ {
3080
+ side: "top",
3081
+ sideOffset: 4,
3082
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
3083
+ "aria-hidden": true,
3084
+ onOpenAutoFocus: (e) => e.preventDefault(),
3085
+ onMouseEnter: cancelHide,
3086
+ onMouseLeave: startHide,
3087
+ children: /* @__PURE__ */ jsxRuntime.jsx(STag, { shape: "square", size: "xs", color: "grey", label: text })
3088
+ }
3089
+ ) })
3090
+ ] });
3091
+ }
2904
3092
  var HIDE_DELAY_MS = 100;
2905
3093
  function useBody(content, message = []) {
2906
3094
  if (content != null) return content;
2907
3095
  if (message.length === 0) return null;
2908
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: line }, i)) });
3096
+ const singleLine = message.length === 1;
3097
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: singleLine ? "leading-[24px]" : void 0, children: line }, i)) });
2909
3098
  }
2910
3099
  var STooltip = react.forwardRef(function STooltip2({
2911
3100
  content,
@@ -2973,10 +3162,10 @@ var STooltip = react.forwardRef(function STooltip2({
2973
3162
  fontWeight: tooltipType === "default" ? 500 : 700,
2974
3163
  ...style
2975
3164
  };
2976
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover__namespace.Root, { open, onOpenChange: setOpen, children: [
3165
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
2977
3166
  isHover ? (
2978
3167
  // hover: Anchor(위치 기준) + 직접 mouse 핸들러 — 클릭으로 토글되지 않도록 Trigger 미사용
2979
- /* @__PURE__ */ jsxRuntime.jsx(RPopover__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
3168
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
2980
3169
  "span",
2981
3170
  {
2982
3171
  className: "inline-flex",
@@ -2987,9 +3176,9 @@ var STooltip = react.forwardRef(function STooltip2({
2987
3176
  children: triggerNode
2988
3177
  }
2989
3178
  ) })
2990
- ) : /* @__PURE__ */ jsxRuntime.jsx(RPopover__namespace.Trigger, { asChild: true, children: triggerNode }),
2991
- /* @__PURE__ */ jsxRuntime.jsx(RPopover__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
2992
- RPopover__namespace.Content,
3179
+ ) : /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: triggerNode }),
3180
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
3181
+ RPopover2__namespace.Content,
2993
3182
  {
2994
3183
  side: placement,
2995
3184
  sideOffset: 8,
@@ -3008,15 +3197,21 @@ var STooltip = react.forwardRef(function STooltip2({
3008
3197
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-[12px]", children: [
3009
3198
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: body }),
3010
3199
  useClose && /* @__PURE__ */ jsxRuntime.jsx(
3011
- RPopover__namespace.Close,
3200
+ SGhostButton,
3012
3201
  {
3013
- "aria-label": "\uB2EB\uAE30",
3014
- className: "shrink-0 cursor-pointer opacity-70 hover:opacity-100",
3015
- children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "close", size: 16, color: fg })
3202
+ icon: "close",
3203
+ size: "xs",
3204
+ intent: tooltipType === "default" ? "inverse" : "default",
3205
+ ariaLabel: "\uB2EB\uAE30",
3206
+ className: "shrink-0",
3207
+ onClick: () => {
3208
+ cancelHide();
3209
+ setOpen(false);
3210
+ }
3016
3211
  }
3017
3212
  )
3018
3213
  ] }),
3019
- /* @__PURE__ */ jsxRuntime.jsx(RPopover__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
3214
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
3020
3215
  ]
3021
3216
  }
3022
3217
  ) })
@@ -3216,8 +3411,8 @@ function useFormField(field) {
3216
3411
  }, [ctx, name]);
3217
3412
  }
3218
3413
  var FIELD_SIZE = {
3219
- sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 12 },
3220
- md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 12 }
3414
+ sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 16 },
3415
+ md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 16 }
3221
3416
  };
3222
3417
  var ALIGN = {
3223
3418
  start: "flex-start",
@@ -3232,6 +3427,7 @@ var SField = react.forwardRef(function SField2({
3232
3427
  labelTooltip = "",
3233
3428
  labelTooltipProps,
3234
3429
  labelWidth,
3430
+ labelHeight,
3235
3431
  size = "sm",
3236
3432
  addonLabel = "",
3237
3433
  addonAlign = "start",
@@ -3246,6 +3442,7 @@ var SField = react.forwardRef(function SField2({
3246
3442
  readOnly = false,
3247
3443
  width,
3248
3444
  multiline = false,
3445
+ borderless = false,
3249
3446
  children,
3250
3447
  className,
3251
3448
  style,
@@ -3279,8 +3476,8 @@ var SField = react.forwardRef(function SField2({
3279
3476
  const resolvedSuccess = status === "pass";
3280
3477
  const showError = resolvedError && errorMessage !== "";
3281
3478
  const interactive = !disabled && !readOnly;
3282
- 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)";
3283
- const bgColor = disabled ? "var(--color-grey-30)" : readOnly ? "var(--sys-color-field-bg-readOnly)" : "var(--sys-color-field-bg-default)";
3479
+ 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)";
3480
+ const bgColor = borderless ? "transparent" : disabled ? "var(--color-grey-30)" : readOnly ? "var(--sys-color-field-bg-readOnly)" : "var(--sys-color-field-bg-default)";
3284
3481
  const textColor = disabled ? "var(--color-grey-65)" : readOnly ? "var(--sys-color-field-text-readOnly)" : "var(--sys-color-field-text-default)";
3285
3482
  return /* @__PURE__ */ jsxRuntime.jsx(
3286
3483
  "div",
@@ -3297,266 +3494,78 @@ var SField = react.forwardRef(function SField2({
3297
3494
  {
3298
3495
  className: "flex shrink-0 items-center font-bold text-(--sys-color-field-text-default)",
3299
3496
  style: {
3300
- height: s.height,
3497
+ height: cssLen(labelHeight) ?? s.height,
3301
3498
  marginRight: s.fieldGap,
3302
3499
  width: cssLen(labelWidth),
3303
- fontSize: s.fontSize,
3304
- lineHeight: `${s.lineHeight}px`
3305
- },
3306
- children: [
3307
- icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3308
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3309
- (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
3310
- STooltip,
3311
- {
3312
- message: labelTooltip !== "" ? [labelTooltip] : void 0,
3313
- iconSize: s.icon,
3314
- ...labelTooltipProps
3315
- }
3316
- ) })
3317
- ]
3318
- }
3319
- ),
3320
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
3321
- /* @__PURE__ */ jsxRuntime.jsxs(
3322
- "div",
3323
- {
3324
- className: cn(
3325
- "relative box-border flex min-w-0 items-stretch overflow-hidden border border-solid border-(--field-bc)",
3326
- 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]",
3327
- disabled && "cursor-not-allowed"
3328
- ),
3329
- style: {
3330
- // @ts-expect-error CSS 변수
3331
- "--field-bc": borderColor,
3332
- // focused/hovered prop이 명시된 경우 강조를 즉시 반영
3333
- ...interactive && focused ? { boxShadow: "0px 0px 4px 0px #0071ff66" } : {},
3334
- ...multiline ? { minHeight: s.height } : { height: s.height },
3335
- "width": cssLen(width),
3336
- "borderRadius": s.radius,
3337
- "background": bgColor,
3338
- "color": textColor
3339
- },
3340
- "data-focused": focused || void 0,
3341
- "data-hovered": hovered || void 0,
3342
- children: [
3343
- effectiveAddonLabel !== "" && /* @__PURE__ */ jsxRuntime.jsxs(
3344
- "div",
3345
- {
3346
- 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",
3347
- style: {
3348
- // 원본 sd-field: labelWidth가 addon 폭(--sd-field-addon-width)에도 적용됨
3349
- width: cssLen(labelWidth),
3350
- justifyContent: ALIGN[addonAlign],
3351
- background: "var(--cmp-field-addonLabel-bg)",
3352
- borderRightColor: disabled ? "var(--cmp-field-addonLabel-border-disabled)" : "var(--cmp-field-addonLabel-border-default)",
3353
- borderTopLeftRadius: s.radius - 1,
3354
- borderBottomLeftRadius: s.radius - 1
3355
- },
3356
- children: [
3357
- icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon }),
3358
- effectiveAddonLabel
3359
- ]
3360
- }
3361
- ),
3362
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 flex-1", children })
3363
- ]
3364
- }
3365
- ),
3366
- showError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-(--cmp-field-contents-gap) text-12 font-normal leading-20 text-(--cmp-field-errorMessage-color)", children: errorMessage }) : hint !== "" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-(--cmp-field-contents-gap) text-12 font-normal leading-20 text-(--cmp-field-hint-color)", children: hint }) : null
3367
- ] })
3368
- ] })
3369
- }
3370
- );
3371
- });
3372
-
3373
- // src/components/STag/tag.config.ts
3374
- var TAG_SIZE_CONFIG = {
3375
- xs: {
3376
- height: 20,
3377
- paddingX: 8,
3378
- gap: 4,
3379
- radius: 6,
3380
- iconSize: 12,
3381
- fontSize: 11,
3382
- fontWeight: 500,
3383
- lineHeight: 18
3384
- },
3385
- sm: {
3386
- height: 24,
3387
- paddingX: 8,
3388
- gap: 4,
3389
- radius: 6,
3390
- iconSize: 16,
3391
- fontSize: 12,
3392
- fontWeight: 700,
3393
- lineHeight: 20
3394
- },
3395
- md: {
3396
- height: 28,
3397
- paddingX: 12,
3398
- gap: 6,
3399
- radius: 8,
3400
- iconSize: 16,
3401
- fontSize: 14,
3402
- fontWeight: 700,
3403
- lineHeight: 24
3404
- }
3405
- };
3406
- var STag = react.forwardRef(function STag2({
3407
- shape = "square",
3408
- size = "sm",
3409
- color = "grey",
3410
- label = "",
3411
- icon,
3412
- iconLeft = true,
3413
- width,
3414
- className,
3415
- style,
3416
- ...rest
3417
- }, ref) {
3418
- const s = TAG_SIZE_CONFIG[size];
3419
- const contentColor = `var(--cmp-tag-${color}-content)`;
3420
- const iconNode = icon ? /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.iconSize, color: contentColor }) : null;
3421
- const resolvedWidth = typeof width === "number" ? `${width}px` : width;
3422
- return /* @__PURE__ */ jsxRuntime.jsxs(
3423
- "span",
3424
- {
3425
- ref,
3426
- "aria-label": label || "tag",
3427
- className: cn(
3428
- "box-border inline-flex w-fit select-none items-center justify-center whitespace-nowrap",
3429
- className
3430
- ),
3431
- style: {
3432
- minHeight: s.height,
3433
- padding: `0 ${s.paddingX}px`,
3434
- gap: s.gap,
3435
- borderRadius: shape === "pill" ? 999 : s.radius,
3436
- fontSize: s.fontSize,
3437
- fontWeight: s.fontWeight,
3438
- lineHeight: `${s.lineHeight}px`,
3439
- background: `var(--cmp-tag-${color}-bg)`,
3440
- color: contentColor,
3441
- ...resolvedWidth != null ? { width: resolvedWidth } : {},
3442
- ...style
3443
- },
3444
- ...rest,
3445
- children: [
3446
- icon && iconLeft && iconNode,
3447
- label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex min-w-0 items-center justify-center", children: label }),
3448
- icon && !iconLeft && iconNode
3449
- ]
3450
- }
3451
- );
3452
- });
3453
- var SIZE = {
3454
- xxs: { icon: 12, radius: "sm" },
3455
- xs: { icon: 16, radius: "sm" },
3456
- sm: { icon: 20, radius: "sm" },
3457
- md: { icon: 24, radius: "md" },
3458
- lg: { icon: 32, radius: "md" }
3459
- };
3460
- var TOOLTIP_HIDE_DELAY_MS = 100;
3461
- function SGhostButton({
3462
- icon,
3463
- size = "sm",
3464
- intent = "default",
3465
- ariaLabel = "",
3466
- tooltipText,
3467
- disabled = false,
3468
- onClick,
3469
- className,
3470
- style
3471
- }) {
3472
- const portalContainer = usePortalContainer();
3473
- const [open, setOpen] = react.useState(false);
3474
- const hideTimer = react.useRef(void 0);
3475
- const cancelHide = () => {
3476
- if (hideTimer.current) {
3477
- clearTimeout(hideTimer.current);
3478
- hideTimer.current = void 0;
3479
- }
3480
- };
3481
- const openNow = () => {
3482
- cancelHide();
3483
- setOpen(true);
3484
- };
3485
- const startHide = () => {
3486
- cancelHide();
3487
- hideTimer.current = setTimeout(() => setOpen(false), TOOLTIP_HIDE_DELAY_MS);
3488
- };
3489
- const s = SIZE[size];
3490
- const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
3491
- const btn = /* @__PURE__ */ jsxRuntime.jsxs(
3492
- "button",
3493
- {
3494
- type: "button",
3495
- disabled,
3496
- "aria-disabled": disabled ? "true" : void 0,
3497
- "aria-label": ariaLabel.trim() || void 0,
3498
- tabIndex: disabled ? -1 : void 0,
3499
- onClick: (e) => {
3500
- if (disabled) {
3501
- e.preventDefault();
3502
- return;
3503
- }
3504
- onClick?.(e);
3505
- },
3506
- className: cn(
3507
- "group relative box-border inline-flex items-center justify-center border-none bg-transparent p-0 transition-shadow",
3508
- disabled ? "cursor-not-allowed" : "cursor-pointer",
3509
- "focus-visible:outline-none focus-visible:shadow-[0_0_0_2px_var(--cmp-ghostButton-action-hover-bg)]",
3510
- className
3511
- ),
3512
- style: {
3513
- width: `var(--cmp-ghostButton-${size}-size)`,
3514
- height: `var(--cmp-ghostButton-${size}-size)`,
3515
- borderRadius: `var(--cmp-ghostButton-radius-${s.radius})`,
3516
- ...style
3517
- },
3518
- children: [
3519
- !disabled && /* @__PURE__ */ jsxRuntime.jsx(
3520
- "span",
3521
- {
3522
- "aria-hidden": true,
3523
- className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover:opacity-5",
3524
- style: { background: `var(--cmp-ghostButton-${intent}-hover-bg)` }
3500
+ fontSize: s.fontSize,
3501
+ lineHeight: `${s.lineHeight}px`
3502
+ },
3503
+ children: [
3504
+ icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3505
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3506
+ (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
3507
+ STooltip,
3508
+ {
3509
+ message: labelTooltip !== "" ? [labelTooltip] : void 0,
3510
+ iconSize: s.icon,
3511
+ ...labelTooltipProps
3512
+ }
3513
+ ) })
3514
+ ]
3525
3515
  }
3526
3516
  ),
3527
- /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: contentColor })
3528
- ]
3517
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
3518
+ /* @__PURE__ */ jsxRuntime.jsxs(
3519
+ "div",
3520
+ {
3521
+ className: cn(
3522
+ "relative box-border flex min-w-0 items-stretch overflow-hidden border border-solid border-(--field-bc)",
3523
+ 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]",
3524
+ disabled && "cursor-not-allowed"
3525
+ ),
3526
+ style: {
3527
+ // @ts-expect-error CSS 변수
3528
+ "--field-bc": borderColor,
3529
+ // focused/hovered prop이 명시된 경우 강조를 즉시 반영
3530
+ ...interactive && !borderless && focused ? { boxShadow: "0px 0px 4px 0px #0071ff66" } : {},
3531
+ ...multiline ? { minHeight: s.height } : { height: s.height },
3532
+ "width": cssLen(width),
3533
+ "borderRadius": s.radius,
3534
+ "background": bgColor,
3535
+ "color": textColor
3536
+ },
3537
+ "data-focused": focused || void 0,
3538
+ "data-hovered": hovered || void 0,
3539
+ children: [
3540
+ effectiveAddonLabel !== "" && /* @__PURE__ */ jsxRuntime.jsxs(
3541
+ "div",
3542
+ {
3543
+ 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",
3544
+ style: {
3545
+ // 원본 sd-field: labelWidth가 addon 폭(--sd-field-addon-width)에도 적용됨
3546
+ width: cssLen(labelWidth),
3547
+ justifyContent: ALIGN[addonAlign],
3548
+ background: "var(--cmp-field-addonLabel-bg)",
3549
+ borderRightColor: disabled ? "var(--cmp-field-addonLabel-border-disabled)" : "var(--cmp-field-addonLabel-border-default)",
3550
+ borderTopLeftRadius: s.radius - 1,
3551
+ borderBottomLeftRadius: s.radius - 1
3552
+ },
3553
+ children: [
3554
+ icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon }),
3555
+ effectiveAddonLabel
3556
+ ]
3557
+ }
3558
+ ),
3559
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 flex-1", children })
3560
+ ]
3561
+ }
3562
+ ),
3563
+ showError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-(--cmp-field-contents-gap) text-12 font-normal leading-20 text-(--cmp-field-errorMessage-color)", children: errorMessage }) : hint !== "" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-(--cmp-field-contents-gap) text-12 font-normal leading-20 text-(--cmp-field-hint-color)", children: hint }) : null
3564
+ ] })
3565
+ ] })
3529
3566
  }
3530
3567
  );
3531
- const text = (tooltipText ?? "").trim();
3532
- if (text === "") return btn;
3533
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover__namespace.Root, { open, onOpenChange: setOpen, children: [
3534
- /* @__PURE__ */ jsxRuntime.jsx(RPopover__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
3535
- "span",
3536
- {
3537
- className: "inline-flex",
3538
- onMouseEnter: openNow,
3539
- onMouseLeave: startHide,
3540
- onFocus: openNow,
3541
- onBlur: startHide,
3542
- children: btn
3543
- }
3544
- ) }),
3545
- /* @__PURE__ */ jsxRuntime.jsx(RPopover__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
3546
- RPopover__namespace.Content,
3547
- {
3548
- side: "top",
3549
- sideOffset: 4,
3550
- className: cn("z-50", FLOATING_SLIDE_ANIM),
3551
- "aria-hidden": true,
3552
- onOpenAutoFocus: (e) => e.preventDefault(),
3553
- onMouseEnter: cancelHide,
3554
- onMouseLeave: startHide,
3555
- children: /* @__PURE__ */ jsxRuntime.jsx(STag, { shape: "square", size: "xs", color: "grey", label: text })
3556
- }
3557
- ) })
3558
- ] });
3559
- }
3568
+ });
3560
3569
  var FONT = {
3561
3570
  sm: { fontSize: 12, lineHeight: 20, paddingX: "var(--cmp-textinput-sm-paddingX)" },
3562
3571
  md: { fontSize: 14, lineHeight: 24, paddingX: "var(--cmp-textinput-md-paddingX)" }
@@ -3811,8 +3820,8 @@ var STextarea = react.forwardRef(function STextarea2({
3811
3820
  );
3812
3821
  });
3813
3822
  var PAD2 = {
3814
- sm: { fontSize: 12, stepper: 20, icon: 12 },
3815
- md: { fontSize: 14, stepper: 24, icon: 14 }
3823
+ sm: { fontSize: 12, lineHeight: 20, stepper: 20, icon: 12 },
3824
+ md: { fontSize: 14, lineHeight: 24, stepper: 24, icon: 14 }
3816
3825
  };
3817
3826
  var toNumber = (val) => {
3818
3827
  if (val === null || val === void 0 || val === "") return null;
@@ -3830,6 +3839,38 @@ var formatWithCommas = (value) => {
3830
3839
  const result = isNeg ? "-" + formatted : formatted;
3831
3840
  return decPart !== void 0 && decPart !== "" ? result + "." + decPart : result;
3832
3841
  };
3842
+ var addCommas = (value) => value.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
3843
+ var formatInputWithCommas = (value) => {
3844
+ const isNeg = value.startsWith("-");
3845
+ const unsigned = isNeg ? value.slice(1) : value;
3846
+ const hasDecimal = unsigned.includes(".");
3847
+ const [intPart = "", decPart = ""] = unsigned.split(".");
3848
+ const formattedInt = addCommas(intPart === "" ? "0" : intPart);
3849
+ return `${isNeg ? "-" : ""}${formattedInt}${hasDecimal ? `.${decPart}` : ""}`;
3850
+ };
3851
+ var getDecimalLimit = (useDecimal, maxDecimalLength) => {
3852
+ if (!useDecimal || maxDecimalLength === void 0 || !Number.isFinite(maxDecimalLength)) {
3853
+ return void 0;
3854
+ }
3855
+ return Math.max(0, Math.floor(maxDecimalLength));
3856
+ };
3857
+ var applyDecimalLimit = (value, decimalLimit) => {
3858
+ if (value === null || decimalLimit === void 0) return value;
3859
+ return Number(value.toFixed(decimalLimit));
3860
+ };
3861
+ var getLogicalCaret = (value, caret) => {
3862
+ return value.slice(0, caret ?? value.length).replace(/,/g, "").length;
3863
+ };
3864
+ var getDisplayCaret = (value, logicalCaret) => {
3865
+ if (logicalCaret <= 0) return 0;
3866
+ let seen = 0;
3867
+ for (let i = 0; i < value.length; i += 1) {
3868
+ if (value[i] === ",") continue;
3869
+ seen += 1;
3870
+ if (seen >= logicalCaret) return i + 1;
3871
+ }
3872
+ return value.length;
3873
+ };
3833
3874
  var SNumberInput = react.forwardRef(function SNumberInput2({
3834
3875
  value,
3835
3876
  onValueChange,
@@ -3839,6 +3880,7 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
3839
3880
  step = 1,
3840
3881
  useButton = false,
3841
3882
  useDecimal = false,
3883
+ maxDecimalLength,
3842
3884
  inputPrefix = "",
3843
3885
  inputSuffix = "",
3844
3886
  inputClass,
@@ -3870,7 +3912,8 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
3870
3912
  }, ref) {
3871
3913
  const p = PAD2[size];
3872
3914
  const padX = `var(--cmp-numberInput-${size}-paddingX)`;
3873
- const numValue = toNumber(value);
3915
+ const decimalLimit = getDecimalLimit(useDecimal, maxDecimalLength);
3916
+ const numValue = applyDecimalLimit(toNumber(value), decimalLimit);
3874
3917
  const [display, setDisplay] = react.useState(() => formatWithCommas(numValue));
3875
3918
  const [ruleError, setRuleError] = react.useState("");
3876
3919
  const innerRef = react.useRef(null);
@@ -3880,8 +3923,9 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
3880
3923
  else if (ref) ref.current = el;
3881
3924
  };
3882
3925
  react.useEffect(() => {
3883
- setDisplay(formatWithCommas(toNumber(value)));
3884
- }, [value]);
3926
+ if (document.activeElement === innerRef.current) return;
3927
+ setDisplay(formatWithCommas(applyDecimalLimit(toNumber(value), decimalLimit)));
3928
+ }, [decimalLimit, value]);
3885
3929
  useFormField({
3886
3930
  name,
3887
3931
  validate: () => {
@@ -3893,48 +3937,87 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
3893
3937
  });
3894
3938
  const resolvedError = error || ruleError !== "";
3895
3939
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
3896
- const emit = (n) => {
3897
- setDisplay(formatWithCommas(n));
3898
- onValueChange?.(n);
3940
+ const restoreCaret = (nextDisplay, logicalCaret) => {
3941
+ const input = innerRef.current;
3942
+ if (logicalCaret === void 0 || !input || document.activeElement !== input) return;
3943
+ const nextCaret = getDisplayCaret(nextDisplay, logicalCaret);
3944
+ requestAnimationFrame(() => {
3945
+ if (document.activeElement !== input) return;
3946
+ input.setSelectionRange(nextCaret, nextCaret);
3947
+ });
3948
+ };
3949
+ const setDisplayWithCaret = (nextDisplay, logicalCaret) => {
3950
+ setDisplay(nextDisplay);
3951
+ restoreCaret(nextDisplay, logicalCaret);
3952
+ };
3953
+ const resetRejectedInput = (target, logicalCaret) => {
3954
+ target.value = display;
3955
+ restoreCaret(display, Math.min(logicalCaret, display.replace(/,/g, "").length));
3956
+ };
3957
+ const emit = (n, logicalCaret) => {
3958
+ const next = applyDecimalLimit(n, decimalLimit);
3959
+ const nextDisplay = formatWithCommas(next);
3960
+ setDisplayWithCaret(nextDisplay, logicalCaret);
3961
+ onValueChange?.(next);
3899
3962
  };
3900
3963
  const incDisabled = disabled || readOnly || numValue !== null && numValue >= max;
3901
3964
  const decDisabled = disabled || readOnly || numValue !== null && numValue <= min;
3902
3965
  const increment = () => {
3903
3966
  if (incDisabled) return;
3904
3967
  const cur = numValue ?? (min === Number.NEGATIVE_INFINITY ? 0 : min);
3905
- const next = Math.min(cur + step, max);
3968
+ const next = applyDecimalLimit(Math.min(cur + step, max), decimalLimit);
3906
3969
  if (next !== cur) emit(next);
3907
3970
  };
3908
3971
  const decrement = () => {
3909
3972
  if (decDisabled) return;
3910
3973
  const cur = numValue ?? (min === Number.NEGATIVE_INFINITY ? 0 : min);
3911
- const next = Math.max(cur - step, min);
3974
+ const next = applyDecimalLimit(Math.max(cur - step, min), decimalLimit);
3912
3975
  if (next !== cur) emit(next);
3913
3976
  };
3914
- const handleInput = (raw) => {
3977
+ const handleInput = (event) => {
3978
+ const target = event.currentTarget;
3979
+ const raw = target.value;
3980
+ const logicalCaret = getLogicalCaret(raw, target.selectionStart);
3915
3981
  if (raw === "") {
3916
- setDisplay("");
3982
+ setDisplayWithCaret("", 0);
3917
3983
  onValueChange?.(null);
3918
3984
  return;
3919
3985
  }
3920
3986
  const cleaned = raw.replace(/,/g, "");
3921
3987
  const decimals = (cleaned.match(/\./g) || []).length;
3922
- if (decimals > 1) return;
3988
+ if (decimals > 1) {
3989
+ resetRejectedInput(target, logicalCaret);
3990
+ return;
3991
+ }
3923
3992
  if (cleaned === "-" || cleaned === "." || cleaned === "-.") {
3924
- setDisplay(cleaned);
3993
+ setDisplayWithCaret(cleaned, logicalCaret);
3994
+ return;
3995
+ }
3996
+ if (!useDecimal && cleaned.includes(".")) {
3997
+ resetRejectedInput(target, logicalCaret);
3925
3998
  return;
3926
3999
  }
3927
- if (!useDecimal && cleaned.includes(".")) return;
4000
+ if (decimalLimit !== void 0 && cleaned.includes(".")) {
4001
+ const fraction = cleaned.split(".")[1] ?? "";
4002
+ if (fraction.length > decimalLimit) {
4003
+ resetRejectedInput(target, logicalCaret);
4004
+ return;
4005
+ }
4006
+ }
3928
4007
  if (cleaned.endsWith(".") && decimals === 1) {
3929
4008
  const parsed2 = toNumber(cleaned.slice(0, -1));
3930
4009
  if (parsed2 !== null) {
3931
- setDisplay(formatWithCommas(parsed2) + ".");
4010
+ setDisplayWithCaret(formatInputWithCommas(cleaned), logicalCaret);
3932
4011
  return;
3933
4012
  }
3934
4013
  }
3935
4014
  const parsed = toNumber(cleaned);
3936
- if (parsed === null) return;
3937
- emit(parsed);
4015
+ if (parsed === null) {
4016
+ resetRejectedInput(target, logicalCaret);
4017
+ return;
4018
+ }
4019
+ setDisplayWithCaret(formatInputWithCommas(cleaned), logicalCaret);
4020
+ onValueChange?.(applyDecimalLimit(parsed, decimalLimit));
3938
4021
  };
3939
4022
  const stepper = (dir) => {
3940
4023
  const off = dir === "inc" ? incDisabled : decDisabled;
@@ -3947,8 +4030,8 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
3947
4030
  "aria-label": dir === "inc" ? "\uC99D\uAC00" : "\uAC10\uC18C",
3948
4031
  onClick: dir === "inc" ? increment : decrement,
3949
4032
  className: cn(
3950
- "flex flex-shrink-0 items-center justify-center",
3951
- off ? "cursor-not-allowed" : "cursor-pointer"
4033
+ "flex flex-shrink-0 items-center justify-center border border-solid border-transparent transition-colors",
4034
+ off ? "cursor-not-allowed" : "cursor-pointer hover:border-[var(--color-border-accentLight)]"
3952
4035
  ),
3953
4036
  style: {
3954
4037
  width: `var(--cmp-numberInput-stepper-${size}-size)`,
@@ -3997,24 +4080,26 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
3997
4080
  style: {
3998
4081
  // 원본 sd-number-input: content gap = padding-x 토큰 (gap 토큰 아님)
3999
4082
  gap: `var(--cmp-numberInput-${size}-paddingX)`,
4000
- padding: `0 ${useButton ? padX : `calc(${padX} * 2)`}`
4083
+ padding: `0 ${useButton ? padX : `calc(${padX} * 2)`}`,
4084
+ fontSize: p.fontSize,
4085
+ lineHeight: `${p.lineHeight}px`
4001
4086
  },
4002
4087
  children: [
4003
4088
  useButton && stepper("dec"),
4004
- inputPrefix !== "" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", style: { fontSize: p.fontSize, color: affixColor }, children: inputPrefix }),
4089
+ inputPrefix !== "" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", style: { color: affixColor }, children: inputPrefix }),
4005
4090
  /* @__PURE__ */ jsxRuntime.jsx(
4006
4091
  "input",
4007
4092
  {
4008
4093
  ref: setRefs,
4009
4094
  name,
4010
4095
  type: "text",
4011
- inputMode: "numeric",
4096
+ inputMode: useDecimal ? "decimal" : "numeric",
4012
4097
  value: display,
4013
4098
  placeholder,
4014
4099
  disabled,
4015
4100
  readOnly,
4016
4101
  autoFocus,
4017
- onChange: (e) => handleInput(e.target.value),
4102
+ onChange: handleInput,
4018
4103
  onKeyDown: (e) => {
4019
4104
  if (e.key === "ArrowUp") {
4020
4105
  e.preventDefault();
@@ -4026,26 +4111,29 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
4026
4111
  },
4027
4112
  onFocus: (e) => onFocus?.(e),
4028
4113
  onBlur: (e) => {
4029
- const n = toNumber(display);
4114
+ const cleaned = display.replace(/,/g, "");
4115
+ const n = toNumber(cleaned);
4030
4116
  const clamped = n === null ? null : Math.min(Math.max(n, min), max);
4031
4117
  if (clamped !== n) emit(clamped);
4032
- else setDisplay(formatWithCommas(n));
4118
+ else setDisplay(n === null ? "" : formatInputWithCommas(cleaned));
4033
4119
  if (rules && rules.length > 0) setRuleError(runRules(clamped, rules));
4034
4120
  onBlur?.(e);
4035
4121
  },
4036
4122
  className: cn(
4037
- "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",
4123
+ // h-full 대신 라인박스 높이 그대로 두어 prefix/suffix 동일하게 flex 로 수직 센터링
4124
+ // (h-full 이면 input 내부 텍스트 센터링을 브라우저가 수행해 1px 어긋날 수 있음)
4125
+ "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",
4038
4126
  inputClass
4039
4127
  ),
4040
4128
  style: {
4041
- fontSize: p.fontSize,
4129
+ // fontSize/lineHeight 는 컨테이너에서 상속
4042
4130
  textAlign: useButton ? "center" : "right",
4043
4131
  color: disabled ? "var(--cmp-numberInput-text-disabled)" : "var(--cmp-numberInput-text-default)",
4044
4132
  ...inputStyle
4045
4133
  }
4046
4134
  }
4047
4135
  ),
4048
- inputSuffix !== "" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", style: { fontSize: p.fontSize, color: affixColor }, children: inputSuffix }),
4136
+ inputSuffix !== "" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", style: { color: affixColor }, children: inputSuffix }),
4049
4137
  useButton && stepper("inc")
4050
4138
  ]
4051
4139
  }
@@ -4059,15 +4147,28 @@ function flattenOptions(options, depth = 1) {
4059
4147
  const out = [];
4060
4148
  for (const o of options) {
4061
4149
  const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4062
- out.push({
4063
- value: String(o.value),
4064
- label: o.label,
4065
- disabled: !!o.disabled,
4066
- depth,
4067
- isGroup: hasChildren,
4068
- raw: o.value
4069
- });
4070
- if (hasChildren) out.push(...flattenOptions(o.children, depth + 1));
4150
+ if (hasChildren) {
4151
+ const childFlat = flattenOptions(o.children, depth + 1);
4152
+ const allChildrenDisabled = childFlat.every((c) => c.disabled);
4153
+ out.push({
4154
+ value: String(o.value),
4155
+ label: o.label,
4156
+ disabled: !!o.disabled || allChildrenDisabled,
4157
+ depth,
4158
+ isGroup: true,
4159
+ raw: o.value
4160
+ });
4161
+ out.push(...childFlat);
4162
+ } else {
4163
+ out.push({
4164
+ value: String(o.value),
4165
+ label: o.label,
4166
+ disabled: !!o.disabled,
4167
+ depth,
4168
+ isGroup: false,
4169
+ raw: o.value
4170
+ });
4171
+ }
4071
4172
  }
4072
4173
  return out;
4073
4174
  }
@@ -4191,6 +4292,9 @@ var SSelect = react.forwardRef(function SSelect2({
4191
4292
  );
4192
4293
  const flat = react.useMemo(() => flattenOptions(options), [options]);
4193
4294
  const selectable = react.useMemo(() => selectableOptions(flat), [flat]);
4295
+ const hasLeaf = react.useMemo(() => flat.some((o) => !o.isGroup), [flat]);
4296
+ const allOptionsDisabled = hasLeaf && selectable.length === 0;
4297
+ const effectiveDisabled = disabled || allOptionsDisabled;
4194
4298
  const showSearch = useSearch && selectable.length >= SEARCH_THRESHOLD;
4195
4299
  const labelByValue = react.useMemo(() => new Map(flat.map((o) => [o.value, o.label])), [flat]);
4196
4300
  const rawByValue = react.useMemo(() => new Map(flat.map((o) => [o.value, o.raw])), [flat]);
@@ -4257,10 +4361,10 @@ var SSelect = react.forwardRef(function SSelect2({
4257
4361
  Ariakit__namespace.Select,
4258
4362
  {
4259
4363
  ref: triggerRef,
4260
- disabled,
4364
+ disabled: effectiveDisabled,
4261
4365
  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",
4262
4366
  style: {
4263
- color: disabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
4367
+ color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
4264
4368
  },
4265
4369
  children: [
4266
4370
  isHtmlLabel(triggerText) ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -4275,7 +4379,7 @@ var SSelect = react.forwardRef(function SSelect2({
4275
4379
  {
4276
4380
  name: "chevronDown",
4277
4381
  size: 12,
4278
- color: disabled ? "var(--cmp-select-icon-disabled)" : "var(--cmp-select-icon-default)",
4382
+ color: effectiveDisabled ? "var(--cmp-select-icon-disabled)" : "var(--cmp-select-icon-default)",
4279
4383
  className: cn("shrink-0 transition-transform duration-200", open && "rotate-180")
4280
4384
  }
4281
4385
  )
@@ -4289,7 +4393,7 @@ var SSelect = react.forwardRef(function SSelect2({
4289
4393
  portalElement: portalContainer,
4290
4394
  gutter: 4,
4291
4395
  sameWidth: !dropdownWidth,
4292
- 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",
4396
+ className: "z-50 overflow-auto rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)] data-[enter]:animate-floating-in-bottom data-[leave]:animate-floating-out-bottom",
4293
4397
  style: {
4294
4398
  maxHeight: dropdownHeight,
4295
4399
  ...dropdownWidth ? { width: dropdownWidth } : {},
@@ -4330,7 +4434,7 @@ var SSelect = react.forwardRef(function SSelect2({
4330
4434
  ),
4331
4435
  hasItems ? /* @__PURE__ */ jsxRuntime.jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
4332
4436
  // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
4333
- /* @__PURE__ */ jsxRuntime.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." })
4437
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-[12px] py-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-disabled)]", children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
4334
4438
  )
4335
4439
  ]
4336
4440
  }
@@ -4353,7 +4457,7 @@ var SSelect = react.forwardRef(function SSelect2({
4353
4457
  error: error || ruleError !== "",
4354
4458
  status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4355
4459
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
4356
- disabled,
4460
+ disabled: effectiveDisabled,
4357
4461
  width,
4358
4462
  className,
4359
4463
  style,
@@ -4388,7 +4492,11 @@ function ItemList({
4388
4492
  {
4389
4493
  className: cn(
4390
4494
  "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4391
- 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"
4495
+ isDepth1 ? "border-t border-solid border-[color:var(--cmp-listItem-depth1-border-color)] bg-[var(--cmp-listItem-depth1-bg-default)] font-bold" : "bg-[var(--cmp-listItem-depth2-middle-bg)] font-medium",
4496
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4497
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4498
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4499
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4392
4500
  ),
4393
4501
  style: { paddingLeft: padLeft },
4394
4502
  children: o.label
@@ -4402,10 +4510,14 @@ function ItemList({
4402
4510
  {
4403
4511
  value: o.value,
4404
4512
  disabled: o.disabled,
4513
+ accessibleWhenDisabled: true,
4405
4514
  className: cn(
4406
4515
  itemBase,
4407
4516
  isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4408
- activeClass
4517
+ // Ariakit은 disabled 항목도 hover 시 active(data-active-item) 처리하므로,
4518
+ // 비활성 항목에는 hover 하이라이트를 적용하지 않아 회색 비활성 스타일을 유지한다.
4519
+ // (원본 Stencil의 `--focused...:not(--disabled)` 규칙과 동일한 의도)
4520
+ !o.disabled && activeClass
4409
4521
  ),
4410
4522
  style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4411
4523
  render: useSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxItem, {}) : void 0,
@@ -4415,15 +4527,21 @@ function ItemList({
4415
4527
  "span",
4416
4528
  {
4417
4529
  className: cn(
4418
- "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)]",
4419
- 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"
4530
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4531
+ o.disabled ? (
4532
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4533
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4534
+ ) : cn(
4535
+ "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4536
+ isSelected ? "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)] group-data-[active-item]:bg-transparent" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-transparent"
4537
+ )
4420
4538
  ),
4421
4539
  children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4422
4540
  SIcon,
4423
4541
  {
4424
4542
  name: "checkboxCheck",
4425
4543
  size: 12,
4426
- color: "var(--cmp-checkbox-checked-icon-default)"
4544
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4427
4545
  }
4428
4546
  )
4429
4547
  }
@@ -4547,7 +4665,7 @@ function SRadioGroup({
4547
4665
  role: "radiogroup",
4548
4666
  className: cn(
4549
4667
  "inline-flex",
4550
- direction === "vertical" ? "flex-col gap-[8px]" : "flex-row gap-[20px]",
4668
+ direction === "vertical" ? "flex-col gap-[var(--cmp-radio-group-gap-vertical)]" : "flex-row gap-[var(--cmp-radio-group-gap-horizontal)]",
4551
4669
  className
4552
4670
  ),
4553
4671
  style,
@@ -4829,7 +4947,7 @@ function SCalendar({
4829
4947
  "span",
4830
4948
  {
4831
4949
  className: cn(
4832
- "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]",
4950
+ "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)]",
4833
4951
  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)]",
4834
4952
  isToday && !disabled && "font-bold"
4835
4953
  ),
@@ -4954,8 +5072,8 @@ function SDatePicker({
4954
5072
  disabled,
4955
5073
  className,
4956
5074
  style,
4957
- children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover__namespace.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
4958
- /* @__PURE__ */ jsxRuntime.jsx(RPopover__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
5075
+ children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
5076
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
4959
5077
  "button",
4960
5078
  {
4961
5079
  type: "button",
@@ -4991,8 +5109,8 @@ function SDatePicker({
4991
5109
  ]
4992
5110
  }
4993
5111
  ) }),
4994
- /* @__PURE__ */ jsxRuntime.jsx(RPopover__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
4995
- RPopover__namespace.Content,
5112
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
5113
+ RPopover2__namespace.Content,
4996
5114
  {
4997
5115
  side: "bottom",
4998
5116
  align: "start",
@@ -5019,6 +5137,7 @@ function RangeCalendar({
5019
5137
  selectable,
5020
5138
  maxRange,
5021
5139
  onSelect,
5140
+ onPendingStartChange,
5022
5141
  onViewChange
5023
5142
  }) {
5024
5143
  const today = todayStr();
@@ -5081,9 +5200,11 @@ function RangeCalendar({
5081
5200
  setStart(d);
5082
5201
  setEnd(null);
5083
5202
  setHover(null);
5203
+ onPendingStartChange?.(d);
5084
5204
  return;
5085
5205
  }
5086
5206
  setEnd(d);
5207
+ onPendingStartChange?.(null);
5087
5208
  onSelect([start, d]);
5088
5209
  };
5089
5210
  const GRID_COLS = "grid grid-cols-[repeat(7,var(--cmp-datepicker-calendar-day-width))]";
@@ -5218,8 +5339,11 @@ function SDateRangePicker({
5218
5339
  const portalContainer = usePortalContainer();
5219
5340
  const [open, setOpen] = react.useState(false);
5220
5341
  const [ruleError, setRuleError] = react.useState("");
5342
+ const [pendingStart, setPendingStart] = react.useState(null);
5221
5343
  const hasValue = value != null && value[0] !== "" && value[1] !== "";
5222
- const display = hasValue ? `${value[0]} ~ ${value[1]}` : placeholder;
5344
+ const hasDisplay = pendingStart != null || hasValue;
5345
+ const startText = pendingStart ?? (hasValue ? value[0] : "");
5346
+ const endText = pendingStart ? "" : hasValue ? value[1] : "";
5223
5347
  useFormField({
5224
5348
  name,
5225
5349
  validate: () => {
@@ -5233,6 +5357,7 @@ function SDateRangePicker({
5233
5357
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
5234
5358
  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 };
5235
5359
  const select = (r) => {
5360
+ setPendingStart(null);
5236
5361
  onValueChange?.(r);
5237
5362
  if (rules && rules.length > 0) setRuleError(runRules(r, rules));
5238
5363
  setOpen(false);
@@ -5257,61 +5382,80 @@ function SDateRangePicker({
5257
5382
  disabled,
5258
5383
  className,
5259
5384
  style,
5260
- children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover__namespace.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
5261
- /* @__PURE__ */ jsxRuntime.jsx(RPopover__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
5262
- "button",
5263
- {
5264
- type: "button",
5265
- name,
5266
- disabled,
5267
- className: "flex h-full w-full items-center overflow-hidden bg-transparent outline-none disabled:cursor-not-allowed",
5268
- style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
5269
- children: [
5270
- /* @__PURE__ */ jsxRuntime.jsx(
5271
- SIcon,
5272
- {
5273
- name: "date",
5274
- size: dims.icon,
5275
- color: disabled ? "var(--cmp-datepicker-icon-disabled)" : "var(--cmp-datepicker-icon-default)",
5276
- className: "flex-shrink-0"
5277
- }
5278
- ),
5279
- /* @__PURE__ */ jsxRuntime.jsx(
5280
- "span",
5281
- {
5282
- className: "flex-1 truncate text-center",
5283
- style: {
5284
- minWidth: dims.minW,
5285
- fontSize: dims.fontSize,
5286
- lineHeight: `${dims.lineHeight}px`,
5287
- color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasValue ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
5288
- },
5289
- children: display
5290
- }
5291
- )
5292
- ]
5293
- }
5294
- ) }),
5295
- /* @__PURE__ */ jsxRuntime.jsx(RPopover__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
5296
- RPopover__namespace.Content,
5297
- {
5298
- side: "bottom",
5299
- align: "start",
5300
- sideOffset: 4,
5301
- className: cn("z-50", FLOATING_SLIDE_ANIM),
5302
- children: /* @__PURE__ */ jsxRuntime.jsx(
5303
- RangeCalendar,
5385
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
5386
+ RPopover2__namespace.Root,
5387
+ {
5388
+ open,
5389
+ onOpenChange: (o) => {
5390
+ if (disabled) return;
5391
+ setOpen(o);
5392
+ if (!o) setPendingStart(null);
5393
+ },
5394
+ children: [
5395
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
5396
+ "button",
5304
5397
  {
5305
- value: value ?? null,
5306
- selectable,
5307
- maxRange,
5308
- onSelect: select,
5309
- onViewChange
5398
+ type: "button",
5399
+ name,
5400
+ disabled,
5401
+ className: "flex h-full w-full items-center overflow-hidden bg-transparent outline-none disabled:cursor-not-allowed",
5402
+ style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
5403
+ children: [
5404
+ /* @__PURE__ */ jsxRuntime.jsx(
5405
+ SIcon,
5406
+ {
5407
+ name: "date",
5408
+ size: dims.icon,
5409
+ color: disabled ? "var(--cmp-datepicker-icon-disabled)" : "var(--cmp-datepicker-icon-default)",
5410
+ className: "flex-shrink-0"
5411
+ }
5412
+ ),
5413
+ /* @__PURE__ */ jsxRuntime.jsx(
5414
+ "span",
5415
+ {
5416
+ className: cn(
5417
+ "flex-1 items-center",
5418
+ hasDisplay ? "flex" : "block truncate text-center"
5419
+ ),
5420
+ style: {
5421
+ minWidth: dims.minW,
5422
+ fontSize: dims.fontSize,
5423
+ lineHeight: `${dims.lineHeight}px`,
5424
+ color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasDisplay ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
5425
+ },
5426
+ children: hasDisplay ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5427
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-right", children: startText }),
5428
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-[4px]", children: "~" }),
5429
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: endText })
5430
+ ] }) : placeholder
5431
+ }
5432
+ )
5433
+ ]
5310
5434
  }
5311
- )
5312
- }
5313
- ) })
5314
- ] })
5435
+ ) }),
5436
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
5437
+ RPopover2__namespace.Content,
5438
+ {
5439
+ side: "bottom",
5440
+ align: "start",
5441
+ sideOffset: 4,
5442
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
5443
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5444
+ RangeCalendar,
5445
+ {
5446
+ value: value ?? null,
5447
+ selectable,
5448
+ maxRange,
5449
+ onSelect: select,
5450
+ onPendingStartChange: setPendingStart,
5451
+ onViewChange
5452
+ }
5453
+ )
5454
+ }
5455
+ ) })
5456
+ ]
5457
+ }
5458
+ )
5315
5459
  }
5316
5460
  );
5317
5461
  }
@@ -5320,7 +5464,7 @@ var SChip = react.forwardRef(function SChip2({
5320
5464
  value = "",
5321
5465
  error: errorProp = false,
5322
5466
  disabled = false,
5323
- editable = true,
5467
+ editable = false,
5324
5468
  showRemove = true,
5325
5469
  placeholder = "",
5326
5470
  rules,
@@ -5473,6 +5617,7 @@ function SFilePicker({
5473
5617
  hovered = false,
5474
5618
  inline = false,
5475
5619
  multiple = false,
5620
+ useMultipleListBox = false,
5476
5621
  accept,
5477
5622
  maxFileSize,
5478
5623
  maxTotalSize,
@@ -5498,10 +5643,13 @@ function SFilePicker({
5498
5643
  const autoId = react.useId();
5499
5644
  const resolvedName = name ?? autoId;
5500
5645
  const inputRef = react.useRef(null);
5646
+ const contentBoxRef = react.useRef(null);
5501
5647
  const [ruleError, setRuleError] = react.useState("");
5648
+ const [listOpen, setListOpen] = react.useState(false);
5649
+ const portalContainer = usePortalContainer();
5502
5650
  const files = multiple ? Array.isArray(value) ? value : [] : value && !Array.isArray(value) ? value : null;
5503
- const hasSingle = !multiple && !!files;
5504
- const displayText = hasSingle ? files.name : placeholder;
5651
+ const hasFiles = multiple ? files.length > 0 : !!files;
5652
+ const displayText = !hasFiles ? placeholder : multiple ? files.map((f) => f.name).join(", ") : files.name;
5505
5653
  useFormField({
5506
5654
  name,
5507
5655
  validate: () => {
@@ -5552,10 +5700,12 @@ function SFilePicker({
5552
5700
  e.stopPropagation();
5553
5701
  if (disabled) return;
5554
5702
  onValueChange?.(multiple ? [] : null);
5703
+ setListOpen(false);
5555
5704
  };
5556
5705
  const removeChip = (i) => {
5557
5706
  const next = files.filter((_, idx) => idx !== i);
5558
5707
  onValueChange?.(next);
5708
+ if (next.length === 0) setListOpen(false);
5559
5709
  };
5560
5710
  const open = () => {
5561
5711
  if (!disabled) inputRef.current?.click();
@@ -5565,78 +5715,100 @@ function SFilePicker({
5565
5715
  const padX = size === "md" ? "var(--cmp-filepicker-md-paddingX)" : "var(--cmp-filepicker-sm-paddingX)";
5566
5716
  const iconSize = size === "md" ? 20 : 16;
5567
5717
  const gapCls = size === "md" ? "gap-[var(--cmp-filepicker-md-gap)]" : "gap-[var(--cmp-filepicker-sm-gap)]";
5568
- const content = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex h-full w-full items-center", gapCls), style: { padding: `0 ${padX}` }, children: [
5718
+ const inlinePlaceholder = inline && !disabled && !hasFiles;
5719
+ const showListbox = multiple && useMultipleListBox;
5720
+ const filesArr = multiple ? files : [];
5721
+ const contentInner = /* @__PURE__ */ jsxRuntime.jsxs(
5722
+ "div",
5723
+ {
5724
+ ref: contentBoxRef,
5725
+ className: cn("group/filepicker flex h-full w-full items-center", gapCls),
5726
+ style: { padding: inline ? 0 : `0 ${padX}` },
5727
+ children: [
5728
+ /* @__PURE__ */ jsxRuntime.jsx(
5729
+ "input",
5730
+ {
5731
+ ref: inputRef,
5732
+ type: "file",
5733
+ className: "hidden",
5734
+ disabled,
5735
+ multiple,
5736
+ accept,
5737
+ name: resolvedName,
5738
+ onChange,
5739
+ "aria-label": placeholder
5740
+ }
5741
+ ),
5742
+ /* @__PURE__ */ jsxRuntime.jsxs(
5743
+ "button",
5744
+ {
5745
+ type: "button",
5746
+ disabled,
5747
+ onClick: open,
5748
+ "aria-label": placeholder,
5749
+ className: cn(
5750
+ "flex min-w-0 flex-1 items-center bg-transparent text-left",
5751
+ gapCls,
5752
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
5753
+ ),
5754
+ children: [
5755
+ /* @__PURE__ */ jsxRuntime.jsx(
5756
+ SIcon,
5757
+ {
5758
+ name: "attachFile",
5759
+ size: iconSize,
5760
+ color: disabled ? "var(--cmp-filepicker-icon-disabled)" : "var(--color-grey-65)"
5761
+ }
5762
+ ),
5763
+ /* @__PURE__ */ jsxRuntime.jsx(
5764
+ "span",
5765
+ {
5766
+ className: cn(
5767
+ "min-w-0 flex-1 truncate text-[12px]",
5768
+ inlinePlaceholder && "text-[color:var(--cmp-filepicker-text-placeholder)] transition-colors group-hover/filepicker:text-[color:var(--cmp-filepicker-text-default)]"
5769
+ ),
5770
+ style: inlinePlaceholder ? void 0 : {
5771
+ color: disabled ? "var(--cmp-filepicker-text-disabled)" : hasFiles ? "var(--cmp-filepicker-text-default)" : "var(--cmp-filepicker-text-placeholder)"
5772
+ },
5773
+ children: displayText
5774
+ }
5775
+ )
5776
+ ]
5777
+ }
5778
+ ),
5779
+ showListbox && hasFiles && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
5780
+ Ariakit__namespace.PopoverDisclosure,
5781
+ {
5782
+ render: /* @__PURE__ */ jsxRuntime.jsx(SGhostButton, { icon: "caretDown", size: "xxs", ariaLabel: "toggle file list" }),
5783
+ className: cn("shrink-0 transition-transform duration-150", listOpen && "rotate-180")
5784
+ }
5785
+ ),
5786
+ !disabled && hasFiles && /* @__PURE__ */ jsxRuntime.jsx(SGhostButton, { icon: "close", size: "xxs", ariaLabel: "clear", onClick: clear })
5787
+ ]
5788
+ }
5789
+ );
5790
+ const content = showListbox ? /* @__PURE__ */ jsxRuntime.jsxs(Ariakit__namespace.PopoverProvider, { open: listOpen, setOpen: setListOpen, placement: "bottom-start", children: [
5791
+ contentInner,
5569
5792
  /* @__PURE__ */ jsxRuntime.jsx(
5570
- "input",
5571
- {
5572
- ref: inputRef,
5573
- type: "file",
5574
- className: "hidden",
5575
- disabled,
5576
- multiple,
5577
- accept,
5578
- name: resolvedName,
5579
- onChange,
5580
- "aria-label": placeholder
5581
- }
5582
- ),
5583
- /* @__PURE__ */ jsxRuntime.jsxs(
5584
- "button",
5793
+ Ariakit__namespace.Popover,
5585
5794
  {
5586
- type: "button",
5587
- disabled,
5588
- onClick: open,
5589
- "aria-label": placeholder,
5590
- className: cn(
5591
- "flex min-w-0 flex-1 items-center bg-transparent text-left",
5592
- gapCls,
5593
- disabled ? "cursor-not-allowed" : "cursor-pointer"
5594
- ),
5595
- children: [
5596
- /* @__PURE__ */ jsxRuntime.jsx(
5597
- SIcon,
5598
- {
5599
- name: "attachFile",
5600
- size: iconSize,
5601
- color: disabled ? "var(--cmp-filepicker-icon-disabled)" : "var(--color-grey-65)"
5602
- }
5603
- ),
5604
- /* @__PURE__ */ jsxRuntime.jsx(
5605
- "span",
5606
- {
5607
- className: "min-w-0 flex-1 truncate text-[12px]",
5608
- style: {
5609
- color: disabled ? "var(--cmp-filepicker-text-disabled)" : hasSingle ? "var(--cmp-filepicker-text-default)" : "var(--cmp-filepicker-text-placeholder)"
5610
- },
5611
- children: displayText
5612
- }
5613
- )
5614
- ]
5795
+ portal: true,
5796
+ portalElement: portalContainer,
5797
+ gutter: 4,
5798
+ unmountOnHide: true,
5799
+ getAnchorRect: () => contentBoxRef.current?.getBoundingClientRect() ?? null,
5800
+ style: { minWidth: contentBoxRef.current?.offsetWidth },
5801
+ className: "z-50 box-border flex flex-col items-stretch gap-[8px] rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] p-[8px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
5802
+ children: filesArr.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i))
5615
5803
  }
5616
- ),
5617
- !disabled && hasSingle && /* @__PURE__ */ jsxRuntime.jsx(SGhostButton, { icon: "close", size: "xxs", ariaLabel: "clear", onClick: clear })
5618
- ] });
5619
- const chips = multiple && files.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-[8px] flex flex-wrap gap-[8px]", children: files.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i)) });
5620
- if (inline) {
5621
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("inline-flex flex-col", className), style, children: [
5622
- /* @__PURE__ */ jsxRuntime.jsx(
5623
- "div",
5624
- {
5625
- 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)]",
5626
- style: {
5627
- height: "var(--cmp-filepicker-sm-height)",
5628
- width: typeof width === "number" ? `${width}px` : width
5629
- },
5630
- children: content
5631
- }
5632
- ),
5633
- chips
5634
- ] });
5635
- }
5804
+ )
5805
+ ] }) : contentInner;
5806
+ const chips = multiple && !useMultipleListBox && filesArr.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-[8px] flex flex-wrap gap-[8px]", children: filesArr.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i)) });
5636
5807
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style, children: [
5637
5808
  /* @__PURE__ */ jsxRuntime.jsx(
5638
5809
  SField,
5639
5810
  {
5811
+ borderless: inline,
5640
5812
  label,
5641
5813
  labelWidth,
5642
5814
  icon,
@@ -5664,6 +5836,7 @@ function SKeyValueTable({
5664
5836
  fields = [],
5665
5837
  search = false,
5666
5838
  radius = "default",
5839
+ dense = false,
5667
5840
  onChange,
5668
5841
  onSearch,
5669
5842
  className,
@@ -5671,6 +5844,8 @@ function SKeyValueTable({
5671
5844
  }) {
5672
5845
  if (fields.length === 0) return null;
5673
5846
  const emit = (name, value) => onChange?.({ name, value });
5847
+ const cellHeight = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-keyValue-height)";
5848
+ const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-keyValue-body-paddingY)";
5674
5849
  const renderField = (f) => {
5675
5850
  if (f.render) return f.render;
5676
5851
  const opt = f.options ?? {};
@@ -5751,10 +5926,15 @@ function SKeyValueTable({
5751
5926
  rowSpan: f.thRowSpan && f.thRowSpan > 1 ? f.thRowSpan : void 0,
5752
5927
  colSpan: f.thColSpan && f.thColSpan > 1 ? f.thColSpan : void 0,
5753
5928
  className: cn(
5754
- "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",
5929
+ "box-border bg-[var(--cmp-table-header-bg)] px-[var(--cmp-table-header-paddingX)] text-left align-middle",
5755
5930
  cellBorder,
5756
5931
  f.thClass
5757
5932
  ),
5933
+ style: {
5934
+ height: cellHeight,
5935
+ paddingTop: cellPadY,
5936
+ paddingBottom: cellPadY
5937
+ },
5758
5938
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full w-full items-center justify-between gap-[var(--cmp-table-header-gap)]", children: [
5759
5939
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center gap-[8px]", children: [
5760
5940
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[12px] font-bold leading-[20px] text-[color:var(--sys-color-fg-primary)]", children: f.label }),
@@ -5780,10 +5960,11 @@ function SKeyValueTable({
5780
5960
  rowSpan: f.tdRowSpan && f.tdRowSpan > 1 ? f.tdRowSpan : void 0,
5781
5961
  colSpan: f.tdColSpan && f.tdColSpan > 1 ? f.tdColSpan : void 0,
5782
5962
  className: cn(
5783
- "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",
5963
+ "box-border bg-[var(--sys-color-bg-frame)] px-[var(--cmp-table-keyValue-body-paddingX-default)] align-middle",
5784
5964
  cellBorder,
5785
5965
  f.tdClass
5786
5966
  ),
5967
+ style: { height: cellHeight, paddingTop: cellPadY, paddingBottom: cellPadY },
5787
5968
  children: renderField(f)
5788
5969
  },
5789
5970
  `td-${r}-${c}`