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