sellmate-design-system-react 0.1.8 → 0.1.9

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 (104) 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 +218 -209
  6. package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
  7. package/dist/components/SBarcodeInput/SBarcodeInput.js +218 -209
  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 +235 -221
  19. package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
  20. package/dist/components/SChipInput/SChipInput.js +235 -221
  21. package/dist/components/SChipInput/SChipInput.js.map +1 -1
  22. package/dist/components/SCircleProgress/SCircleProgress.cjs +53 -55
  23. package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
  24. package/dist/components/SCircleProgress/SCircleProgress.js +53 -55
  25. package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
  26. package/dist/components/SDatePicker/SDatePicker.cjs +223 -214
  27. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  28. package/dist/components/SDatePicker/SDatePicker.js +223 -214
  29. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  30. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +298 -263
  31. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  32. package/dist/components/SDateRangePicker/SDateRangePicker.js +298 -263
  33. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  34. package/dist/components/SDropdownButton/SDropdownButton.cjs +4 -4
  35. package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
  36. package/dist/components/SDropdownButton/SDropdownButton.js +4 -4
  37. package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
  38. package/dist/components/SField/SField.cjs +218 -21
  39. package/dist/components/SField/SField.cjs.map +1 -1
  40. package/dist/components/SField/SField.d.ts +11 -0
  41. package/dist/components/SField/SField.js +218 -21
  42. package/dist/components/SField/SField.js.map +1 -1
  43. package/dist/components/SFilePicker/SFilePicker.cjs +298 -278
  44. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  45. package/dist/components/SFilePicker/SFilePicker.d.ts +4 -2
  46. package/dist/components/SFilePicker/SFilePicker.js +298 -278
  47. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  48. package/dist/components/SInput/SInput.cjs +218 -209
  49. package/dist/components/SInput/SInput.cjs.map +1 -1
  50. package/dist/components/SInput/SInput.js +218 -209
  51. package/dist/components/SInput/SInput.js.map +1 -1
  52. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +610 -450
  53. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  54. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +3 -1
  55. package/dist/components/SKeyValueTable/SKeyValueTable.js +610 -450
  56. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  57. package/dist/components/SLoadingContainer/SLoadingContainer.cjs +61 -55
  58. package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
  59. package/dist/components/SLoadingContainer/SLoadingContainer.js +61 -55
  60. package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
  61. package/dist/components/SLoadingContainer/loading.cjs +61 -55
  62. package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
  63. package/dist/components/SLoadingContainer/loading.js +61 -55
  64. package/dist/components/SLoadingContainer/loading.js.map +1 -1
  65. package/dist/components/SLoadingModal/SLoadingModal.cjs +56 -57
  66. package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
  67. package/dist/components/SLoadingModal/SLoadingModal.js +56 -57
  68. package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
  69. package/dist/components/SNumberInput/SNumberInput.cjs +326 -50
  70. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  71. package/dist/components/SNumberInput/SNumberInput.d.ts +2 -0
  72. package/dist/components/SNumberInput/SNumberInput.js +326 -50
  73. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  74. package/dist/components/SPagination/SPagination.cjs +1 -1
  75. package/dist/components/SPagination/SPagination.cjs.map +1 -1
  76. package/dist/components/SPagination/SPagination.js +1 -1
  77. package/dist/components/SPagination/SPagination.js.map +1 -1
  78. package/dist/components/SSelect/SSelect.cjs +67 -32
  79. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  80. package/dist/components/SSelect/SSelect.js +67 -32
  81. package/dist/components/SSelect/SSelect.js.map +1 -1
  82. package/dist/components/STable/STable.cjs +121 -88
  83. package/dist/components/STable/STable.cjs.map +1 -1
  84. package/dist/components/STable/STable.js +121 -88
  85. package/dist/components/STable/STable.js.map +1 -1
  86. package/dist/components/STextarea/STextarea.cjs +218 -21
  87. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  88. package/dist/components/STextarea/STextarea.js +218 -21
  89. package/dist/components/STextarea/STextarea.js.map +1 -1
  90. package/dist/components/SToast/SToastContainer.cjs +3 -3
  91. package/dist/components/SToast/SToastContainer.cjs.map +1 -1
  92. package/dist/components/SToast/SToastContainer.js +3 -3
  93. package/dist/components/SToast/SToastContainer.js.map +1 -1
  94. package/dist/components/STooltip/STooltip.cjs +209 -14
  95. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  96. package/dist/components/STooltip/STooltip.js +209 -14
  97. package/dist/components/STooltip/STooltip.js.map +1 -1
  98. package/dist/index.cjs +432 -260
  99. package/dist/index.cjs.map +1 -1
  100. package/dist/index.js +432 -260
  101. package/dist/index.js.map +1 -1
  102. package/dist/styles.css +51 -27
  103. package/dist/theme.css +4 -2
  104. 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,
@@ -2963,7 +3152,7 @@ var STooltip = react.forwardRef(function STooltip2({
2963
3152
  if (disabled || body == null) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: triggerNode });
2964
3153
  const isHover = trigger === "hover";
2965
3154
  const contentClass = cn(
2966
- "z-50 max-w-xs rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
3155
+ "z-50 max-w-xs rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
2967
3156
  FLOATING_SLIDE_ANIM,
2968
3157
  className
2969
3158
  );
@@ -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 && "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
@@ -4415,15 +4523,21 @@ function ItemList({
4415
4523
  "span",
4416
4524
  {
4417
4525
  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"
4526
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4527
+ o.disabled ? (
4528
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4529
+ "border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4530
+ ) : cn(
4531
+ "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4532
+ isSelected ? "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)] group-data-[active-item]:bg-transparent" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-transparent"
4533
+ )
4420
4534
  ),
4421
4535
  children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4422
4536
  SIcon,
4423
4537
  {
4424
4538
  name: "checkboxCheck",
4425
4539
  size: 12,
4426
- color: "var(--cmp-checkbox-checked-icon-default)"
4540
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4427
4541
  }
4428
4542
  )
4429
4543
  }
@@ -4829,7 +4943,7 @@ function SCalendar({
4829
4943
  "span",
4830
4944
  {
4831
4945
  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]",
4946
+ "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
4947
  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
4948
  isToday && !disabled && "font-bold"
4835
4949
  ),
@@ -4954,8 +5068,8 @@ function SDatePicker({
4954
5068
  disabled,
4955
5069
  className,
4956
5070
  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(
5071
+ children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
5072
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
4959
5073
  "button",
4960
5074
  {
4961
5075
  type: "button",
@@ -4991,8 +5105,8 @@ function SDatePicker({
4991
5105
  ]
4992
5106
  }
4993
5107
  ) }),
4994
- /* @__PURE__ */ jsxRuntime.jsx(RPopover__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
4995
- RPopover__namespace.Content,
5108
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
5109
+ RPopover2__namespace.Content,
4996
5110
  {
4997
5111
  side: "bottom",
4998
5112
  align: "start",
@@ -5019,6 +5133,7 @@ function RangeCalendar({
5019
5133
  selectable,
5020
5134
  maxRange,
5021
5135
  onSelect,
5136
+ onPendingStartChange,
5022
5137
  onViewChange
5023
5138
  }) {
5024
5139
  const today = todayStr();
@@ -5081,9 +5196,11 @@ function RangeCalendar({
5081
5196
  setStart(d);
5082
5197
  setEnd(null);
5083
5198
  setHover(null);
5199
+ onPendingStartChange?.(d);
5084
5200
  return;
5085
5201
  }
5086
5202
  setEnd(d);
5203
+ onPendingStartChange?.(null);
5087
5204
  onSelect([start, d]);
5088
5205
  };
5089
5206
  const GRID_COLS = "grid grid-cols-[repeat(7,var(--cmp-datepicker-calendar-day-width))]";
@@ -5218,8 +5335,11 @@ function SDateRangePicker({
5218
5335
  const portalContainer = usePortalContainer();
5219
5336
  const [open, setOpen] = react.useState(false);
5220
5337
  const [ruleError, setRuleError] = react.useState("");
5338
+ const [pendingStart, setPendingStart] = react.useState(null);
5221
5339
  const hasValue = value != null && value[0] !== "" && value[1] !== "";
5222
- const display = hasValue ? `${value[0]} ~ ${value[1]}` : placeholder;
5340
+ const hasDisplay = pendingStart != null || hasValue;
5341
+ const startText = pendingStart ?? (hasValue ? value[0] : "");
5342
+ const endText = pendingStart ? "" : hasValue ? value[1] : "";
5223
5343
  useFormField({
5224
5344
  name,
5225
5345
  validate: () => {
@@ -5233,6 +5353,7 @@ function SDateRangePicker({
5233
5353
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
5234
5354
  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
5355
  const select = (r) => {
5356
+ setPendingStart(null);
5236
5357
  onValueChange?.(r);
5237
5358
  if (rules && rules.length > 0) setRuleError(runRules(r, rules));
5238
5359
  setOpen(false);
@@ -5257,61 +5378,80 @@ function SDateRangePicker({
5257
5378
  disabled,
5258
5379
  className,
5259
5380
  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,
5381
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
5382
+ RPopover2__namespace.Root,
5383
+ {
5384
+ open,
5385
+ onOpenChange: (o) => {
5386
+ if (disabled) return;
5387
+ setOpen(o);
5388
+ if (!o) setPendingStart(null);
5389
+ },
5390
+ children: [
5391
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
5392
+ "button",
5304
5393
  {
5305
- value: value ?? null,
5306
- selectable,
5307
- maxRange,
5308
- onSelect: select,
5309
- onViewChange
5394
+ type: "button",
5395
+ name,
5396
+ disabled,
5397
+ className: "flex h-full w-full items-center overflow-hidden bg-transparent outline-none disabled:cursor-not-allowed",
5398
+ style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
5399
+ children: [
5400
+ /* @__PURE__ */ jsxRuntime.jsx(
5401
+ SIcon,
5402
+ {
5403
+ name: "date",
5404
+ size: dims.icon,
5405
+ color: disabled ? "var(--cmp-datepicker-icon-disabled)" : "var(--cmp-datepicker-icon-default)",
5406
+ className: "flex-shrink-0"
5407
+ }
5408
+ ),
5409
+ /* @__PURE__ */ jsxRuntime.jsx(
5410
+ "span",
5411
+ {
5412
+ className: cn(
5413
+ "flex-1 items-center",
5414
+ hasDisplay ? "flex" : "block truncate text-center"
5415
+ ),
5416
+ style: {
5417
+ minWidth: dims.minW,
5418
+ fontSize: dims.fontSize,
5419
+ lineHeight: `${dims.lineHeight}px`,
5420
+ color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasDisplay ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
5421
+ },
5422
+ children: hasDisplay ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5423
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-right", children: startText }),
5424
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-[4px]", children: "~" }),
5425
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: endText })
5426
+ ] }) : placeholder
5427
+ }
5428
+ )
5429
+ ]
5310
5430
  }
5311
- )
5312
- }
5313
- ) })
5314
- ] })
5431
+ ) }),
5432
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
5433
+ RPopover2__namespace.Content,
5434
+ {
5435
+ side: "bottom",
5436
+ align: "start",
5437
+ sideOffset: 4,
5438
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
5439
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5440
+ RangeCalendar,
5441
+ {
5442
+ value: value ?? null,
5443
+ selectable,
5444
+ maxRange,
5445
+ onSelect: select,
5446
+ onPendingStartChange: setPendingStart,
5447
+ onViewChange
5448
+ }
5449
+ )
5450
+ }
5451
+ ) })
5452
+ ]
5453
+ }
5454
+ )
5315
5455
  }
5316
5456
  );
5317
5457
  }
@@ -5320,7 +5460,7 @@ var SChip = react.forwardRef(function SChip2({
5320
5460
  value = "",
5321
5461
  error: errorProp = false,
5322
5462
  disabled = false,
5323
- editable = true,
5463
+ editable = false,
5324
5464
  showRemove = true,
5325
5465
  placeholder = "",
5326
5466
  rules,
@@ -5473,6 +5613,7 @@ function SFilePicker({
5473
5613
  hovered = false,
5474
5614
  inline = false,
5475
5615
  multiple = false,
5616
+ useMultipleListBox = false,
5476
5617
  accept,
5477
5618
  maxFileSize,
5478
5619
  maxTotalSize,
@@ -5500,8 +5641,8 @@ function SFilePicker({
5500
5641
  const inputRef = react.useRef(null);
5501
5642
  const [ruleError, setRuleError] = react.useState("");
5502
5643
  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;
5644
+ const hasFiles = multiple ? files.length > 0 : !!files;
5645
+ const displayText = !hasFiles ? placeholder : multiple ? files.map((f) => f.name).join(", ") : files.name;
5505
5646
  useFormField({
5506
5647
  name,
5507
5648
  validate: () => {
@@ -5565,78 +5706,88 @@ function SFilePicker({
5565
5706
  const padX = size === "md" ? "var(--cmp-filepicker-md-paddingX)" : "var(--cmp-filepicker-sm-paddingX)";
5566
5707
  const iconSize = size === "md" ? 20 : 16;
5567
5708
  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: [
5569
- /* @__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",
5585
- {
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"
5709
+ const inlinePlaceholder = inline && !disabled && !hasFiles;
5710
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(
5711
+ "div",
5712
+ {
5713
+ className: cn("group/filepicker flex h-full w-full items-center", gapCls),
5714
+ style: { padding: inline ? 0 : `0 ${padX}` },
5715
+ children: [
5716
+ /* @__PURE__ */ jsxRuntime.jsx(
5717
+ "input",
5718
+ {
5719
+ ref: inputRef,
5720
+ type: "file",
5721
+ className: "hidden",
5722
+ disabled,
5723
+ multiple,
5724
+ accept,
5725
+ name: resolvedName,
5726
+ onChange,
5727
+ "aria-label": placeholder
5728
+ }
5594
5729
  ),
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
- ]
5615
- }
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
- }
5730
+ /* @__PURE__ */ jsxRuntime.jsxs(
5731
+ "button",
5732
+ {
5733
+ type: "button",
5734
+ disabled,
5735
+ onClick: open,
5736
+ "aria-label": placeholder,
5737
+ className: cn(
5738
+ "flex min-w-0 flex-1 items-center bg-transparent text-left",
5739
+ gapCls,
5740
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
5741
+ ),
5742
+ children: [
5743
+ /* @__PURE__ */ jsxRuntime.jsx(
5744
+ SIcon,
5745
+ {
5746
+ name: "attachFile",
5747
+ size: iconSize,
5748
+ color: disabled ? "var(--cmp-filepicker-icon-disabled)" : "var(--color-grey-65)"
5749
+ }
5750
+ ),
5751
+ /* @__PURE__ */ jsxRuntime.jsx(
5752
+ "span",
5753
+ {
5754
+ className: cn(
5755
+ "min-w-0 flex-1 truncate text-[12px]",
5756
+ inlinePlaceholder && "text-[color:var(--cmp-filepicker-text-placeholder)] transition-colors group-hover/filepicker:text-[color:var(--cmp-filepicker-text-default)]"
5757
+ ),
5758
+ style: inlinePlaceholder ? void 0 : {
5759
+ color: disabled ? "var(--cmp-filepicker-text-disabled)" : hasFiles ? "var(--cmp-filepicker-text-default)" : "var(--cmp-filepicker-text-placeholder)"
5760
+ },
5761
+ children: displayText
5762
+ }
5763
+ )
5764
+ ]
5765
+ }
5766
+ ),
5767
+ !disabled && hasFiles && /* @__PURE__ */ jsxRuntime.jsx(SGhostButton, { icon: "close", size: "xxs", ariaLabel: "clear", onClick: clear })
5768
+ ]
5769
+ }
5770
+ );
5771
+ const chips = multiple && files.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
5772
+ "div",
5773
+ {
5774
+ className: cn(
5775
+ "mt-[8px] flex gap-[8px]",
5776
+ useMultipleListBox ? "box-border flex-col items-start rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] p-[8px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]" : "flex-wrap"
5632
5777
  ),
5633
- chips
5634
- ] });
5635
- }
5778
+ style: useMultipleListBox ? {
5779
+ width: typeof width === "number" ? `${width}px` : width,
5780
+ // label 이 있으면 SField 입력 박스 시작 위치(labelWidth + fieldGap)에 맞춰 정렬
5781
+ marginLeft: label && labelWidth != null ? `calc(${typeof labelWidth === "number" ? `${labelWidth}px` : labelWidth} + var(--sys-space-field-${size}-gap))` : void 0
5782
+ } : void 0,
5783
+ children: files.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i))
5784
+ }
5785
+ );
5636
5786
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style, children: [
5637
5787
  /* @__PURE__ */ jsxRuntime.jsx(
5638
5788
  SField,
5639
5789
  {
5790
+ borderless: inline,
5640
5791
  label,
5641
5792
  labelWidth,
5642
5793
  icon,
@@ -5664,6 +5815,7 @@ function SKeyValueTable({
5664
5815
  fields = [],
5665
5816
  search = false,
5666
5817
  radius = "default",
5818
+ dense = false,
5667
5819
  onChange,
5668
5820
  onSearch,
5669
5821
  className,
@@ -5671,6 +5823,8 @@ function SKeyValueTable({
5671
5823
  }) {
5672
5824
  if (fields.length === 0) return null;
5673
5825
  const emit = (name, value) => onChange?.({ name, value });
5826
+ const cellHeight = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-keyValue-height)";
5827
+ const cellPadY = dense ? "var(--cmp-table-body-dense-paddingY)" : "var(--cmp-table-keyValue-body-paddingY)";
5674
5828
  const renderField = (f) => {
5675
5829
  if (f.render) return f.render;
5676
5830
  const opt = f.options ?? {};
@@ -5751,10 +5905,15 @@ function SKeyValueTable({
5751
5905
  rowSpan: f.thRowSpan && f.thRowSpan > 1 ? f.thRowSpan : void 0,
5752
5906
  colSpan: f.thColSpan && f.thColSpan > 1 ? f.thColSpan : void 0,
5753
5907
  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",
5908
+ "box-border bg-[var(--cmp-table-header-bg)] px-[var(--cmp-table-header-paddingX)] text-left align-middle",
5755
5909
  cellBorder,
5756
5910
  f.thClass
5757
5911
  ),
5912
+ style: {
5913
+ height: cellHeight,
5914
+ paddingTop: cellPadY,
5915
+ paddingBottom: cellPadY
5916
+ },
5758
5917
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full w-full items-center justify-between gap-[var(--cmp-table-header-gap)]", children: [
5759
5918
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center gap-[8px]", children: [
5760
5919
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[12px] font-bold leading-[20px] text-[color:var(--sys-color-fg-primary)]", children: f.label }),
@@ -5780,10 +5939,11 @@ function SKeyValueTable({
5780
5939
  rowSpan: f.tdRowSpan && f.tdRowSpan > 1 ? f.tdRowSpan : void 0,
5781
5940
  colSpan: f.tdColSpan && f.tdColSpan > 1 ? f.tdColSpan : void 0,
5782
5941
  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",
5942
+ "box-border bg-[var(--sys-color-bg-frame)] px-[var(--cmp-table-keyValue-body-paddingX-default)] align-middle",
5784
5943
  cellBorder,
5785
5944
  f.tdClass
5786
5945
  ),
5946
+ style: { height: cellHeight, paddingTop: cellPadY, paddingBottom: cellPadY },
5787
5947
  children: renderField(f)
5788
5948
  },
5789
5949
  `td-${r}-${c}`