sellmate-design-system-react 0.1.7 → 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 (146) hide show
  1. package/dist/components/SActionModal/SActionModal.cjs +11 -3
  2. package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
  3. package/dist/components/SActionModal/SActionModal.js +11 -3
  4. package/dist/components/SActionModal/SActionModal.js.map +1 -1
  5. package/dist/components/SBarcodeInput/SBarcodeInput.cjs +226 -209
  6. package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
  7. package/dist/components/SBarcodeInput/SBarcodeInput.js +226 -209
  8. package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
  9. package/dist/components/SCalendar/SCalendar.cjs +9 -1
  10. package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
  11. package/dist/components/SCalendar/SCalendar.js +9 -1
  12. package/dist/components/SCalendar/SCalendar.js.map +1 -1
  13. package/dist/components/SChip/SChip.cjs +9 -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 +9 -1
  17. package/dist/components/SChip/SChip.js.map +1 -1
  18. package/dist/components/SChipInput/SChipInput.cjs +260 -235
  19. package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
  20. package/dist/components/SChipInput/SChipInput.js +259 -235
  21. package/dist/components/SChipInput/SChipInput.js.map +1 -1
  22. package/dist/components/SCircleProgress/SCircleProgress.cjs +56 -58
  23. package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
  24. package/dist/components/SCircleProgress/SCircleProgress.js +56 -58
  25. package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
  26. package/dist/components/SConfirmModal/SConfirmModal.cjs +9 -1
  27. package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
  28. package/dist/components/SConfirmModal/SConfirmModal.js +9 -1
  29. package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
  30. package/dist/components/SDatePicker/SDatePicker.cjs +247 -221
  31. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  32. package/dist/components/SDatePicker/SDatePicker.js +247 -221
  33. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  34. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +308 -256
  35. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  36. package/dist/components/SDateRangePicker/SDateRangePicker.js +308 -256
  37. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  38. package/dist/components/SDropdownButton/SDropdownButton.cjs +51 -4
  39. package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
  40. package/dist/components/SDropdownButton/SDropdownButton.js +52 -5
  41. package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
  42. package/dist/components/SField/SField.cjs +226 -21
  43. package/dist/components/SField/SField.cjs.map +1 -1
  44. package/dist/components/SField/SField.d.ts +11 -0
  45. package/dist/components/SField/SField.js +226 -21
  46. package/dist/components/SField/SField.js.map +1 -1
  47. package/dist/components/SFilePicker/SFilePicker.cjs +306 -278
  48. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  49. package/dist/components/SFilePicker/SFilePicker.d.ts +4 -2
  50. package/dist/components/SFilePicker/SFilePicker.js +306 -278
  51. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  52. package/dist/components/SGhostButton/SGhostButton.cjs +8 -0
  53. package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
  54. package/dist/components/SGhostButton/SGhostButton.js +8 -0
  55. package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
  56. package/dist/components/SGuide/SGuide.cjs +12 -1
  57. package/dist/components/SGuide/SGuide.cjs.map +1 -1
  58. package/dist/components/SGuide/SGuide.js +12 -1
  59. package/dist/components/SGuide/SGuide.js.map +1 -1
  60. package/dist/components/SInput/SInput.cjs +226 -209
  61. package/dist/components/SInput/SInput.cjs.map +1 -1
  62. package/dist/components/SInput/SInput.js +226 -209
  63. package/dist/components/SInput/SInput.js.map +1 -1
  64. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +640 -453
  65. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  66. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +3 -1
  67. package/dist/components/SKeyValueTable/SKeyValueTable.js +640 -453
  68. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  69. package/dist/components/SLoadingContainer/SLoadingContainer.cjs +64 -58
  70. package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
  71. package/dist/components/SLoadingContainer/SLoadingContainer.js +64 -58
  72. package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
  73. package/dist/components/SLoadingContainer/loading.cjs +64 -58
  74. package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
  75. package/dist/components/SLoadingContainer/loading.js +64 -58
  76. package/dist/components/SLoadingContainer/loading.js.map +1 -1
  77. package/dist/components/SLoadingModal/SLoadingModal.cjs +68 -61
  78. package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
  79. package/dist/components/SLoadingModal/SLoadingModal.js +68 -61
  80. package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
  81. package/dist/components/SModalContainer/SModalContainer.cjs +9 -1
  82. package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
  83. package/dist/components/SModalContainer/SModalContainer.js +9 -1
  84. package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
  85. package/dist/components/SNumberInput/SNumberInput.cjs +334 -50
  86. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  87. package/dist/components/SNumberInput/SNumberInput.d.ts +2 -0
  88. package/dist/components/SNumberInput/SNumberInput.js +334 -50
  89. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  90. package/dist/components/SPagination/SPagination.cjs +1 -1
  91. package/dist/components/SPagination/SPagination.cjs.map +1 -1
  92. package/dist/components/SPagination/SPagination.js +1 -1
  93. package/dist/components/SPagination/SPagination.js.map +1 -1
  94. package/dist/components/SPopover/SPopover.cjs +20 -3
  95. package/dist/components/SPopover/SPopover.cjs.map +1 -1
  96. package/dist/components/SPopover/SPopover.js +20 -4
  97. package/dist/components/SPopover/SPopover.js.map +1 -1
  98. package/dist/components/SPopup/SPopup.cjs +2 -2
  99. package/dist/components/SPopup/SPopup.cjs.map +1 -1
  100. package/dist/components/SPopup/SPopup.js +2 -2
  101. package/dist/components/SPopup/SPopup.js.map +1 -1
  102. package/dist/components/SPortal/SPortal.cjs +8 -0
  103. package/dist/components/SPortal/SPortal.cjs.map +1 -1
  104. package/dist/components/SPortal/SPortal.js +8 -0
  105. package/dist/components/SPortal/SPortal.js.map +1 -1
  106. package/dist/components/SSelect/SSelect.cjs +76 -32
  107. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  108. package/dist/components/SSelect/SSelect.js +76 -32
  109. package/dist/components/SSelect/SSelect.js.map +1 -1
  110. package/dist/components/STable/STable.cjs +133 -91
  111. package/dist/components/STable/STable.cjs.map +1 -1
  112. package/dist/components/STable/STable.js +133 -91
  113. package/dist/components/STable/STable.js.map +1 -1
  114. package/dist/components/STextLink/STextLink.cjs +137 -3
  115. package/dist/components/STextLink/STextLink.cjs.map +1 -1
  116. package/dist/components/STextLink/STextLink.d.ts +1 -1
  117. package/dist/components/STextLink/STextLink.js +118 -4
  118. package/dist/components/STextLink/STextLink.js.map +1 -1
  119. package/dist/components/STextarea/STextarea.cjs +226 -21
  120. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  121. package/dist/components/STextarea/STextarea.js +226 -21
  122. package/dist/components/STextarea/STextarea.js.map +1 -1
  123. package/dist/components/SToast/SToast.cjs +8 -0
  124. package/dist/components/SToast/SToast.cjs.map +1 -1
  125. package/dist/components/SToast/SToast.js +8 -0
  126. package/dist/components/SToast/SToast.js.map +1 -1
  127. package/dist/components/SToast/SToastContainer.cjs +11 -3
  128. package/dist/components/SToast/SToastContainer.cjs.map +1 -1
  129. package/dist/components/SToast/SToastContainer.js +11 -3
  130. package/dist/components/SToast/SToastContainer.js.map +1 -1
  131. package/dist/components/STooltip/STooltip.cjs +217 -14
  132. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  133. package/dist/components/STooltip/STooltip.js +217 -14
  134. package/dist/components/STooltip/STooltip.js.map +1 -1
  135. package/dist/index.cjs +532 -300
  136. package/dist/index.cjs.map +1 -1
  137. package/dist/index.js +531 -300
  138. package/dist/index.js.map +1 -1
  139. package/dist/lib/floating-anim.cjs +8 -0
  140. package/dist/lib/floating-anim.cjs.map +1 -1
  141. package/dist/lib/floating-anim.d.ts +5 -1
  142. package/dist/lib/floating-anim.js +8 -0
  143. package/dist/lib/floating-anim.js.map +1 -1
  144. package/dist/styles.css +175 -66
  145. package/dist/theme.css +49 -22
  146. 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
 
@@ -2856,12 +2856,20 @@ var SIcon = forwardRef(function SIcon2({ name, size = 24, color, rotate, label,
2856
2856
  // src/lib/floating-anim.ts
2857
2857
  var FLOATING_SLIDE_ANIM = [
2858
2858
  "data-[state=open]:data-[side=top]:animate-floating-in-top",
2859
+ "data-[state=delayed-open]:data-[side=top]:animate-floating-in-top",
2860
+ "data-[state=instant-open]:data-[side=top]:animate-floating-in-top",
2859
2861
  "data-[state=closed]:data-[side=top]:animate-floating-out-top",
2860
2862
  "data-[state=open]:data-[side=bottom]:animate-floating-in-bottom",
2863
+ "data-[state=delayed-open]:data-[side=bottom]:animate-floating-in-bottom",
2864
+ "data-[state=instant-open]:data-[side=bottom]:animate-floating-in-bottom",
2861
2865
  "data-[state=closed]:data-[side=bottom]:animate-floating-out-bottom",
2862
2866
  "data-[state=open]:data-[side=left]:animate-floating-in-left",
2867
+ "data-[state=delayed-open]:data-[side=left]:animate-floating-in-left",
2868
+ "data-[state=instant-open]:data-[side=left]:animate-floating-in-left",
2863
2869
  "data-[state=closed]:data-[side=left]:animate-floating-out-left",
2864
2870
  "data-[state=open]:data-[side=right]:animate-floating-in-right",
2871
+ "data-[state=delayed-open]:data-[side=right]:animate-floating-in-right",
2872
+ "data-[state=instant-open]:data-[side=right]:animate-floating-in-right",
2865
2873
  "data-[state=closed]:data-[side=right]:animate-floating-out-right"
2866
2874
  ].join(" ");
2867
2875
  var PortalContainerContext = createContext(null);
@@ -2869,11 +2877,200 @@ PortalContainerContext.Provider;
2869
2877
  function usePortalContainer() {
2870
2878
  return useContext(PortalContainerContext) ?? void 0;
2871
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
+ }
2872
3068
  var HIDE_DELAY_MS = 100;
2873
3069
  function useBody(content, message = []) {
2874
3070
  if (content != null) return content;
2875
3071
  if (message.length === 0) return null;
2876
- 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)) });
2877
3074
  }
2878
3075
  var STooltip = forwardRef(function STooltip2({
2879
3076
  content,
@@ -2931,7 +3128,7 @@ var STooltip = forwardRef(function STooltip2({
2931
3128
  if (disabled || body == null) return /* @__PURE__ */ jsx(Fragment, { children: triggerNode });
2932
3129
  const isHover = trigger === "hover";
2933
3130
  const contentClass = cn(
2934
- "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)]",
2935
3132
  FLOATING_SLIDE_ANIM,
2936
3133
  className
2937
3134
  );
@@ -2941,10 +3138,10 @@ var STooltip = forwardRef(function STooltip2({
2941
3138
  fontWeight: tooltipType === "default" ? 500 : 700,
2942
3139
  ...style
2943
3140
  };
2944
- return /* @__PURE__ */ jsxs(RPopover.Root, { open, onOpenChange: setOpen, children: [
3141
+ return /* @__PURE__ */ jsxs(RPopover2.Root, { open, onOpenChange: setOpen, children: [
2945
3142
  isHover ? (
2946
3143
  // hover: Anchor(위치 기준) + 직접 mouse 핸들러 — 클릭으로 토글되지 않도록 Trigger 미사용
2947
- /* @__PURE__ */ jsx(RPopover.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
3144
+ /* @__PURE__ */ jsx(RPopover2.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
2948
3145
  "span",
2949
3146
  {
2950
3147
  className: "inline-flex",
@@ -2955,9 +3152,9 @@ var STooltip = forwardRef(function STooltip2({
2955
3152
  children: triggerNode
2956
3153
  }
2957
3154
  ) })
2958
- ) : /* @__PURE__ */ jsx(RPopover.Trigger, { asChild: true, children: triggerNode }),
2959
- /* @__PURE__ */ jsx(RPopover.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
2960
- RPopover.Content,
3155
+ ) : /* @__PURE__ */ jsx(RPopover2.Trigger, { asChild: true, children: triggerNode }),
3156
+ /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
3157
+ RPopover2.Content,
2961
3158
  {
2962
3159
  side: placement,
2963
3160
  sideOffset: 8,
@@ -2976,15 +3173,21 @@ var STooltip = forwardRef(function STooltip2({
2976
3173
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-[12px]", children: [
2977
3174
  /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: body }),
2978
3175
  useClose && /* @__PURE__ */ jsx(
2979
- RPopover.Close,
3176
+ SGhostButton,
2980
3177
  {
2981
- "aria-label": "\uB2EB\uAE30",
2982
- className: "shrink-0 cursor-pointer opacity-70 hover:opacity-100",
2983
- 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
+ }
2984
3187
  }
2985
3188
  )
2986
3189
  ] }),
2987
- /* @__PURE__ */ jsx(RPopover.Arrow, { width: 16, height: 12, style: { fill: bg } })
3190
+ /* @__PURE__ */ jsx(RPopover2.Arrow, { width: 16, height: 12, style: { fill: bg } })
2988
3191
  ]
2989
3192
  }
2990
3193
  ) })
@@ -3184,8 +3387,8 @@ function useFormField(field) {
3184
3387
  }, [ctx, name]);
3185
3388
  }
3186
3389
  var FIELD_SIZE = {
3187
- sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 12 },
3188
- 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 }
3189
3392
  };
3190
3393
  var ALIGN = {
3191
3394
  start: "flex-start",
@@ -3200,6 +3403,7 @@ var SField = forwardRef(function SField2({
3200
3403
  labelTooltip = "",
3201
3404
  labelTooltipProps,
3202
3405
  labelWidth,
3406
+ labelHeight,
3203
3407
  size = "sm",
3204
3408
  addonLabel = "",
3205
3409
  addonAlign = "start",
@@ -3214,6 +3418,7 @@ var SField = forwardRef(function SField2({
3214
3418
  readOnly = false,
3215
3419
  width,
3216
3420
  multiline = false,
3421
+ borderless = false,
3217
3422
  children,
3218
3423
  className,
3219
3424
  style,
@@ -3247,8 +3452,8 @@ var SField = forwardRef(function SField2({
3247
3452
  const resolvedSuccess = status === "pass";
3248
3453
  const showError = resolvedError && errorMessage !== "";
3249
3454
  const interactive = !disabled && !readOnly;
3250
- 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)";
3251
- 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)";
3252
3457
  const textColor = disabled ? "var(--color-grey-65)" : readOnly ? "var(--sys-color-field-text-readOnly)" : "var(--sys-color-field-text-default)";
3253
3458
  return /* @__PURE__ */ jsx(
3254
3459
  "div",
@@ -3264,267 +3469,79 @@ var SField = forwardRef(function SField2({
3264
3469
  "div",
3265
3470
  {
3266
3471
  className: "flex shrink-0 items-center font-bold text-(--sys-color-field-text-default)",
3267
- style: {
3268
- height: s.height,
3269
- marginRight: s.fieldGap,
3270
- width: cssLen(labelWidth),
3271
- fontSize: s.fontSize,
3272
- lineHeight: `${s.lineHeight}px`
3273
- },
3274
- children: [
3275
- icon && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3276
- /* @__PURE__ */ jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3277
- (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsx(
3278
- STooltip,
3279
- {
3280
- message: labelTooltip !== "" ? [labelTooltip] : void 0,
3281
- iconSize: s.icon,
3282
- ...labelTooltipProps
3283
- }
3284
- ) })
3285
- ]
3286
- }
3287
- ),
3288
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
3289
- /* @__PURE__ */ jsxs(
3290
- "div",
3291
- {
3292
- className: cn(
3293
- "relative box-border flex min-w-0 items-stretch overflow-hidden border border-solid border-(--field-bc)",
3294
- 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]",
3295
- disabled && "cursor-not-allowed"
3296
- ),
3297
- style: {
3298
- // @ts-expect-error CSS 변수
3299
- "--field-bc": borderColor,
3300
- // focused/hovered prop이 명시된 경우 강조를 즉시 반영
3301
- ...interactive && focused ? { boxShadow: "0px 0px 4px 0px #0071ff66" } : {},
3302
- ...multiline ? { minHeight: s.height } : { height: s.height },
3303
- "width": cssLen(width),
3304
- "borderRadius": s.radius,
3305
- "background": bgColor,
3306
- "color": textColor
3307
- },
3308
- "data-focused": focused || void 0,
3309
- "data-hovered": hovered || void 0,
3310
- children: [
3311
- effectiveAddonLabel !== "" && /* @__PURE__ */ jsxs(
3312
- "div",
3313
- {
3314
- 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",
3315
- style: {
3316
- // 원본 sd-field: labelWidth가 addon 폭(--sd-field-addon-width)에도 적용됨
3317
- width: cssLen(labelWidth),
3318
- justifyContent: ALIGN[addonAlign],
3319
- background: "var(--cmp-field-addonLabel-bg)",
3320
- borderRightColor: disabled ? "var(--cmp-field-addonLabel-border-disabled)" : "var(--cmp-field-addonLabel-border-default)",
3321
- borderTopLeftRadius: s.radius - 1,
3322
- borderBottomLeftRadius: s.radius - 1
3323
- },
3324
- children: [
3325
- icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon }),
3326
- effectiveAddonLabel
3327
- ]
3328
- }
3329
- ),
3330
- /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1", children })
3331
- ]
3332
- }
3333
- ),
3334
- 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
3335
- ] })
3336
- ] })
3337
- }
3338
- );
3339
- });
3340
-
3341
- // src/components/STag/tag.config.ts
3342
- var TAG_SIZE_CONFIG = {
3343
- xs: {
3344
- height: 20,
3345
- paddingX: 8,
3346
- gap: 4,
3347
- radius: 6,
3348
- iconSize: 12,
3349
- fontSize: 11,
3350
- fontWeight: 500,
3351
- lineHeight: 18
3352
- },
3353
- sm: {
3354
- height: 24,
3355
- paddingX: 8,
3356
- gap: 4,
3357
- radius: 6,
3358
- iconSize: 16,
3359
- fontSize: 12,
3360
- fontWeight: 700,
3361
- lineHeight: 20
3362
- },
3363
- md: {
3364
- height: 28,
3365
- paddingX: 12,
3366
- gap: 6,
3367
- radius: 8,
3368
- iconSize: 16,
3369
- fontSize: 14,
3370
- fontWeight: 700,
3371
- lineHeight: 24
3372
- }
3373
- };
3374
- var STag = forwardRef(function STag2({
3375
- shape = "square",
3376
- size = "sm",
3377
- color = "grey",
3378
- label = "",
3379
- icon,
3380
- iconLeft = true,
3381
- width,
3382
- className,
3383
- style,
3384
- ...rest
3385
- }, ref) {
3386
- const s = TAG_SIZE_CONFIG[size];
3387
- const contentColor = `var(--cmp-tag-${color}-content)`;
3388
- const iconNode = icon ? /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.iconSize, color: contentColor }) : null;
3389
- const resolvedWidth = typeof width === "number" ? `${width}px` : width;
3390
- return /* @__PURE__ */ jsxs(
3391
- "span",
3392
- {
3393
- ref,
3394
- "aria-label": label || "tag",
3395
- className: cn(
3396
- "box-border inline-flex w-fit select-none items-center justify-center whitespace-nowrap",
3397
- className
3398
- ),
3399
- style: {
3400
- minHeight: s.height,
3401
- padding: `0 ${s.paddingX}px`,
3402
- gap: s.gap,
3403
- borderRadius: shape === "pill" ? 999 : s.radius,
3404
- fontSize: s.fontSize,
3405
- fontWeight: s.fontWeight,
3406
- lineHeight: `${s.lineHeight}px`,
3407
- background: `var(--cmp-tag-${color}-bg)`,
3408
- color: contentColor,
3409
- ...resolvedWidth != null ? { width: resolvedWidth } : {},
3410
- ...style
3411
- },
3412
- ...rest,
3413
- children: [
3414
- icon && iconLeft && iconNode,
3415
- label && /* @__PURE__ */ jsx("span", { className: "inline-flex min-w-0 items-center justify-center", children: label }),
3416
- icon && !iconLeft && iconNode
3417
- ]
3418
- }
3419
- );
3420
- });
3421
- var SIZE = {
3422
- xxs: { icon: 12, radius: "sm" },
3423
- xs: { icon: 16, radius: "sm" },
3424
- sm: { icon: 20, radius: "sm" },
3425
- md: { icon: 24, radius: "md" },
3426
- lg: { icon: 32, radius: "md" }
3427
- };
3428
- var TOOLTIP_HIDE_DELAY_MS = 100;
3429
- function SGhostButton({
3430
- icon,
3431
- size = "sm",
3432
- intent = "default",
3433
- ariaLabel = "",
3434
- tooltipText,
3435
- disabled = false,
3436
- onClick,
3437
- className,
3438
- style
3439
- }) {
3440
- const portalContainer = usePortalContainer();
3441
- const [open, setOpen] = useState(false);
3442
- const hideTimer = useRef(void 0);
3443
- const cancelHide = () => {
3444
- if (hideTimer.current) {
3445
- clearTimeout(hideTimer.current);
3446
- hideTimer.current = void 0;
3447
- }
3448
- };
3449
- const openNow = () => {
3450
- cancelHide();
3451
- setOpen(true);
3452
- };
3453
- const startHide = () => {
3454
- cancelHide();
3455
- hideTimer.current = setTimeout(() => setOpen(false), TOOLTIP_HIDE_DELAY_MS);
3456
- };
3457
- const s = SIZE[size];
3458
- const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
3459
- const btn = /* @__PURE__ */ jsxs(
3460
- "button",
3461
- {
3462
- type: "button",
3463
- disabled,
3464
- "aria-disabled": disabled ? "true" : void 0,
3465
- "aria-label": ariaLabel.trim() || void 0,
3466
- tabIndex: disabled ? -1 : void 0,
3467
- onClick: (e) => {
3468
- if (disabled) {
3469
- e.preventDefault();
3470
- return;
3471
- }
3472
- onClick?.(e);
3473
- },
3474
- className: cn(
3475
- "group relative box-border inline-flex items-center justify-center border-none bg-transparent p-0 transition-shadow",
3476
- disabled ? "cursor-not-allowed" : "cursor-pointer",
3477
- "focus-visible:outline-none focus-visible:shadow-[0_0_0_2px_var(--cmp-ghostButton-action-hover-bg)]",
3478
- className
3479
- ),
3480
- style: {
3481
- width: `var(--cmp-ghostButton-${size}-size)`,
3482
- height: `var(--cmp-ghostButton-${size}-size)`,
3483
- borderRadius: `var(--cmp-ghostButton-radius-${s.radius})`,
3484
- ...style
3485
- },
3486
- children: [
3487
- !disabled && /* @__PURE__ */ jsx(
3488
- "span",
3489
- {
3490
- "aria-hidden": true,
3491
- className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover:opacity-5",
3492
- style: { background: `var(--cmp-ghostButton-${intent}-hover-bg)` }
3472
+ style: {
3473
+ height: cssLen(labelHeight) ?? s.height,
3474
+ marginRight: s.fieldGap,
3475
+ width: cssLen(labelWidth),
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
+ ]
3493
3491
  }
3494
3492
  ),
3495
- /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, color: contentColor })
3496
- ]
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
+ ] })
3497
3542
  }
3498
3543
  );
3499
- const text = (tooltipText ?? "").trim();
3500
- if (text === "") return btn;
3501
- return /* @__PURE__ */ jsxs(RPopover.Root, { open, onOpenChange: setOpen, children: [
3502
- /* @__PURE__ */ jsx(RPopover.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
3503
- "span",
3504
- {
3505
- className: "inline-flex",
3506
- onMouseEnter: openNow,
3507
- onMouseLeave: startHide,
3508
- onFocus: openNow,
3509
- onBlur: startHide,
3510
- children: btn
3511
- }
3512
- ) }),
3513
- /* @__PURE__ */ jsx(RPopover.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
3514
- RPopover.Content,
3515
- {
3516
- side: "top",
3517
- sideOffset: 4,
3518
- className: cn("z-50", FLOATING_SLIDE_ANIM),
3519
- "aria-hidden": true,
3520
- onOpenAutoFocus: (e) => e.preventDefault(),
3521
- onMouseEnter: cancelHide,
3522
- onMouseLeave: startHide,
3523
- children: /* @__PURE__ */ jsx(STag, { shape: "square", size: "xs", color: "grey", label: text })
3524
- }
3525
- ) })
3526
- ] });
3527
- }
3544
+ });
3528
3545
  var FONT = {
3529
3546
  sm: { fontSize: 12, lineHeight: 20, paddingX: "var(--cmp-textinput-sm-paddingX)" },
3530
3547
  md: { fontSize: 14, lineHeight: 24, paddingX: "var(--cmp-textinput-md-paddingX)" }
@@ -3779,8 +3796,8 @@ var STextarea = forwardRef(function STextarea2({
3779
3796
  );
3780
3797
  });
3781
3798
  var PAD2 = {
3782
- sm: { fontSize: 12, stepper: 20, icon: 12 },
3783
- 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 }
3784
3801
  };
3785
3802
  var toNumber = (val) => {
3786
3803
  if (val === null || val === void 0 || val === "") return null;
@@ -3798,6 +3815,38 @@ var formatWithCommas = (value) => {
3798
3815
  const result = isNeg ? "-" + formatted : formatted;
3799
3816
  return decPart !== void 0 && decPart !== "" ? result + "." + decPart : result;
3800
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
+ };
3801
3850
  var SNumberInput = forwardRef(function SNumberInput2({
3802
3851
  value,
3803
3852
  onValueChange,
@@ -3807,6 +3856,7 @@ var SNumberInput = forwardRef(function SNumberInput2({
3807
3856
  step = 1,
3808
3857
  useButton = false,
3809
3858
  useDecimal = false,
3859
+ maxDecimalLength,
3810
3860
  inputPrefix = "",
3811
3861
  inputSuffix = "",
3812
3862
  inputClass,
@@ -3838,7 +3888,8 @@ var SNumberInput = forwardRef(function SNumberInput2({
3838
3888
  }, ref) {
3839
3889
  const p = PAD2[size];
3840
3890
  const padX = `var(--cmp-numberInput-${size}-paddingX)`;
3841
- const numValue = toNumber(value);
3891
+ const decimalLimit = getDecimalLimit(useDecimal, maxDecimalLength);
3892
+ const numValue = applyDecimalLimit(toNumber(value), decimalLimit);
3842
3893
  const [display, setDisplay] = useState(() => formatWithCommas(numValue));
3843
3894
  const [ruleError, setRuleError] = useState("");
3844
3895
  const innerRef = useRef(null);
@@ -3848,8 +3899,9 @@ var SNumberInput = forwardRef(function SNumberInput2({
3848
3899
  else if (ref) ref.current = el;
3849
3900
  };
3850
3901
  useEffect(() => {
3851
- setDisplay(formatWithCommas(toNumber(value)));
3852
- }, [value]);
3902
+ if (document.activeElement === innerRef.current) return;
3903
+ setDisplay(formatWithCommas(applyDecimalLimit(toNumber(value), decimalLimit)));
3904
+ }, [decimalLimit, value]);
3853
3905
  useFormField({
3854
3906
  name,
3855
3907
  validate: () => {
@@ -3861,48 +3913,87 @@ var SNumberInput = forwardRef(function SNumberInput2({
3861
3913
  });
3862
3914
  const resolvedError = error || ruleError !== "";
3863
3915
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
3864
- const emit = (n) => {
3865
- setDisplay(formatWithCommas(n));
3866
- 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);
3867
3938
  };
3868
3939
  const incDisabled = disabled || readOnly || numValue !== null && numValue >= max;
3869
3940
  const decDisabled = disabled || readOnly || numValue !== null && numValue <= min;
3870
3941
  const increment = () => {
3871
3942
  if (incDisabled) return;
3872
3943
  const cur = numValue ?? (min === Number.NEGATIVE_INFINITY ? 0 : min);
3873
- const next = Math.min(cur + step, max);
3944
+ const next = applyDecimalLimit(Math.min(cur + step, max), decimalLimit);
3874
3945
  if (next !== cur) emit(next);
3875
3946
  };
3876
3947
  const decrement = () => {
3877
3948
  if (decDisabled) return;
3878
3949
  const cur = numValue ?? (min === Number.NEGATIVE_INFINITY ? 0 : min);
3879
- const next = Math.max(cur - step, min);
3950
+ const next = applyDecimalLimit(Math.max(cur - step, min), decimalLimit);
3880
3951
  if (next !== cur) emit(next);
3881
3952
  };
3882
- const handleInput = (raw) => {
3953
+ const handleInput = (event) => {
3954
+ const target = event.currentTarget;
3955
+ const raw = target.value;
3956
+ const logicalCaret = getLogicalCaret(raw, target.selectionStart);
3883
3957
  if (raw === "") {
3884
- setDisplay("");
3958
+ setDisplayWithCaret("", 0);
3885
3959
  onValueChange?.(null);
3886
3960
  return;
3887
3961
  }
3888
3962
  const cleaned = raw.replace(/,/g, "");
3889
3963
  const decimals = (cleaned.match(/\./g) || []).length;
3890
- if (decimals > 1) return;
3964
+ if (decimals > 1) {
3965
+ resetRejectedInput(target, logicalCaret);
3966
+ return;
3967
+ }
3891
3968
  if (cleaned === "-" || cleaned === "." || cleaned === "-.") {
3892
- setDisplay(cleaned);
3969
+ setDisplayWithCaret(cleaned, logicalCaret);
3893
3970
  return;
3894
3971
  }
3895
- if (!useDecimal && cleaned.includes(".")) return;
3972
+ if (!useDecimal && cleaned.includes(".")) {
3973
+ resetRejectedInput(target, logicalCaret);
3974
+ return;
3975
+ }
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
+ }
3896
3983
  if (cleaned.endsWith(".") && decimals === 1) {
3897
3984
  const parsed2 = toNumber(cleaned.slice(0, -1));
3898
3985
  if (parsed2 !== null) {
3899
- setDisplay(formatWithCommas(parsed2) + ".");
3986
+ setDisplayWithCaret(formatInputWithCommas(cleaned), logicalCaret);
3900
3987
  return;
3901
3988
  }
3902
3989
  }
3903
3990
  const parsed = toNumber(cleaned);
3904
- if (parsed === null) return;
3905
- emit(parsed);
3991
+ if (parsed === null) {
3992
+ resetRejectedInput(target, logicalCaret);
3993
+ return;
3994
+ }
3995
+ setDisplayWithCaret(formatInputWithCommas(cleaned), logicalCaret);
3996
+ onValueChange?.(applyDecimalLimit(parsed, decimalLimit));
3906
3997
  };
3907
3998
  const stepper = (dir) => {
3908
3999
  const off = dir === "inc" ? incDisabled : decDisabled;
@@ -3915,8 +4006,8 @@ var SNumberInput = forwardRef(function SNumberInput2({
3915
4006
  "aria-label": dir === "inc" ? "\uC99D\uAC00" : "\uAC10\uC18C",
3916
4007
  onClick: dir === "inc" ? increment : decrement,
3917
4008
  className: cn(
3918
- "flex flex-shrink-0 items-center justify-center",
3919
- 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)]"
3920
4011
  ),
3921
4012
  style: {
3922
4013
  width: `var(--cmp-numberInput-stepper-${size}-size)`,
@@ -3965,24 +4056,26 @@ var SNumberInput = forwardRef(function SNumberInput2({
3965
4056
  style: {
3966
4057
  // 원본 sd-number-input: content gap = padding-x 토큰 (gap 토큰 아님)
3967
4058
  gap: `var(--cmp-numberInput-${size}-paddingX)`,
3968
- padding: `0 ${useButton ? padX : `calc(${padX} * 2)`}`
4059
+ padding: `0 ${useButton ? padX : `calc(${padX} * 2)`}`,
4060
+ fontSize: p.fontSize,
4061
+ lineHeight: `${p.lineHeight}px`
3969
4062
  },
3970
4063
  children: [
3971
4064
  useButton && stepper("dec"),
3972
- 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 }),
3973
4066
  /* @__PURE__ */ jsx(
3974
4067
  "input",
3975
4068
  {
3976
4069
  ref: setRefs,
3977
4070
  name,
3978
4071
  type: "text",
3979
- inputMode: "numeric",
4072
+ inputMode: useDecimal ? "decimal" : "numeric",
3980
4073
  value: display,
3981
4074
  placeholder,
3982
4075
  disabled,
3983
4076
  readOnly,
3984
4077
  autoFocus,
3985
- onChange: (e) => handleInput(e.target.value),
4078
+ onChange: handleInput,
3986
4079
  onKeyDown: (e) => {
3987
4080
  if (e.key === "ArrowUp") {
3988
4081
  e.preventDefault();
@@ -3994,26 +4087,29 @@ var SNumberInput = forwardRef(function SNumberInput2({
3994
4087
  },
3995
4088
  onFocus: (e) => onFocus?.(e),
3996
4089
  onBlur: (e) => {
3997
- const n = toNumber(display);
4090
+ const cleaned = display.replace(/,/g, "");
4091
+ const n = toNumber(cleaned);
3998
4092
  const clamped = n === null ? null : Math.min(Math.max(n, min), max);
3999
4093
  if (clamped !== n) emit(clamped);
4000
- else setDisplay(formatWithCommas(n));
4094
+ else setDisplay(n === null ? "" : formatInputWithCommas(cleaned));
4001
4095
  if (rules && rules.length > 0) setRuleError(runRules(clamped, rules));
4002
4096
  onBlur?.(e);
4003
4097
  },
4004
4098
  className: cn(
4005
- "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",
4006
4102
  inputClass
4007
4103
  ),
4008
4104
  style: {
4009
- fontSize: p.fontSize,
4105
+ // fontSize/lineHeight 는 컨테이너에서 상속
4010
4106
  textAlign: useButton ? "center" : "right",
4011
4107
  color: disabled ? "var(--cmp-numberInput-text-disabled)" : "var(--cmp-numberInput-text-default)",
4012
4108
  ...inputStyle
4013
4109
  }
4014
4110
  }
4015
4111
  ),
4016
- 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 }),
4017
4113
  useButton && stepper("inc")
4018
4114
  ]
4019
4115
  }
@@ -4027,15 +4123,28 @@ function flattenOptions(options, depth = 1) {
4027
4123
  const out = [];
4028
4124
  for (const o of options) {
4029
4125
  const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4030
- out.push({
4031
- value: String(o.value),
4032
- label: o.label,
4033
- disabled: !!o.disabled,
4034
- depth,
4035
- isGroup: hasChildren,
4036
- raw: o.value
4037
- });
4038
- 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
+ }
4039
4148
  }
4040
4149
  return out;
4041
4150
  }
@@ -4159,6 +4268,9 @@ var SSelect = forwardRef(function SSelect2({
4159
4268
  );
4160
4269
  const flat = useMemo(() => flattenOptions(options), [options]);
4161
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;
4162
4274
  const showSearch = useSearch && selectable.length >= SEARCH_THRESHOLD;
4163
4275
  const labelByValue = useMemo(() => new Map(flat.map((o) => [o.value, o.label])), [flat]);
4164
4276
  const rawByValue = useMemo(() => new Map(flat.map((o) => [o.value, o.raw])), [flat]);
@@ -4209,6 +4321,7 @@ var SSelect = forwardRef(function SSelect2({
4209
4321
  const selectStore = /* @__PURE__ */ jsxs(
4210
4322
  Ariakit.SelectProvider,
4211
4323
  {
4324
+ animated: portalContainer == null ? 150 : false,
4212
4325
  value: multi ? selected : selected[0] ?? "",
4213
4326
  setValue: (v) => emit(Array.isArray(v) ? v : v === "" ? [] : [v]),
4214
4327
  open,
@@ -4224,10 +4337,10 @@ var SSelect = forwardRef(function SSelect2({
4224
4337
  Ariakit.Select,
4225
4338
  {
4226
4339
  ref: triggerRef,
4227
- disabled,
4340
+ disabled: effectiveDisabled,
4228
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",
4229
4342
  style: {
4230
- 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)"
4231
4344
  },
4232
4345
  children: [
4233
4346
  isHtmlLabel(triggerText) ? /* @__PURE__ */ jsx(
@@ -4242,7 +4355,7 @@ var SSelect = forwardRef(function SSelect2({
4242
4355
  {
4243
4356
  name: "chevronDown",
4244
4357
  size: 12,
4245
- 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)",
4246
4359
  className: cn("shrink-0 transition-transform duration-200", open && "rotate-180")
4247
4360
  }
4248
4361
  )
@@ -4256,7 +4369,7 @@ var SSelect = forwardRef(function SSelect2({
4256
4369
  portalElement: portalContainer,
4257
4370
  gutter: 4,
4258
4371
  sameWidth: !dropdownWidth,
4259
- 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)]",
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",
4260
4373
  style: {
4261
4374
  maxHeight: dropdownHeight,
4262
4375
  ...dropdownWidth ? { width: dropdownWidth } : {},
@@ -4297,7 +4410,7 @@ var SSelect = forwardRef(function SSelect2({
4297
4410
  ),
4298
4411
  hasItems ? /* @__PURE__ */ jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
4299
4412
  // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
4300
- /* @__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." })
4301
4414
  )
4302
4415
  ]
4303
4416
  }
@@ -4320,7 +4433,7 @@ var SSelect = forwardRef(function SSelect2({
4320
4433
  error: error || ruleError !== "",
4321
4434
  status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4322
4435
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
4323
- disabled,
4436
+ disabled: effectiveDisabled,
4324
4437
  width,
4325
4438
  className,
4326
4439
  style,
@@ -4355,7 +4468,11 @@ function ItemList({
4355
4468
  {
4356
4469
  className: cn(
4357
4470
  "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4358
- 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)]"
4359
4476
  ),
4360
4477
  style: { paddingLeft: padLeft },
4361
4478
  children: o.label
@@ -4382,15 +4499,21 @@ function ItemList({
4382
4499
  "span",
4383
4500
  {
4384
4501
  className: cn(
4385
- "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)]",
4386
- 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
+ )
4387
4510
  ),
4388
4511
  children: isSelected && /* @__PURE__ */ jsx(
4389
4512
  SIcon,
4390
4513
  {
4391
4514
  name: "checkboxCheck",
4392
4515
  size: 12,
4393
- color: "var(--cmp-checkbox-checked-icon-default)"
4516
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4394
4517
  }
4395
4518
  )
4396
4519
  }
@@ -4796,7 +4919,7 @@ function SCalendar({
4796
4919
  "span",
4797
4920
  {
4798
4921
  className: cn(
4799
- "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)]",
4800
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)]",
4801
4924
  isToday && !disabled && "font-bold"
4802
4925
  ),
@@ -4921,8 +5044,8 @@ function SDatePicker({
4921
5044
  disabled,
4922
5045
  className,
4923
5046
  style,
4924
- children: /* @__PURE__ */ jsxs(RPopover.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
4925
- /* @__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(
4926
5049
  "button",
4927
5050
  {
4928
5051
  type: "button",
@@ -4958,16 +5081,25 @@ function SDatePicker({
4958
5081
  ]
4959
5082
  }
4960
5083
  ) }),
4961
- /* @__PURE__ */ jsx(RPopover.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(RPopover.Content, { side: "bottom", align: "start", sideOffset: 4, className: "z-50", children: /* @__PURE__ */ jsx(
4962
- SCalendar,
4963
- {
4964
- value: value ?? void 0,
4965
- selectable,
4966
- elevated: true,
4967
- onValueChange: select,
4968
- onViewChange
5084
+ /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
5085
+ RPopover2.Content,
5086
+ {
5087
+ side: "bottom",
5088
+ align: "start",
5089
+ sideOffset: 4,
5090
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
5091
+ children: /* @__PURE__ */ jsx(
5092
+ SCalendar,
5093
+ {
5094
+ value: value ?? void 0,
5095
+ selectable,
5096
+ elevated: true,
5097
+ onValueChange: select,
5098
+ onViewChange
5099
+ }
5100
+ )
4969
5101
  }
4970
- ) }) })
5102
+ ) })
4971
5103
  ] })
4972
5104
  }
4973
5105
  );
@@ -4977,6 +5109,7 @@ function RangeCalendar({
4977
5109
  selectable,
4978
5110
  maxRange,
4979
5111
  onSelect,
5112
+ onPendingStartChange,
4980
5113
  onViewChange
4981
5114
  }) {
4982
5115
  const today = todayStr();
@@ -5039,9 +5172,11 @@ function RangeCalendar({
5039
5172
  setStart(d);
5040
5173
  setEnd(null);
5041
5174
  setHover(null);
5175
+ onPendingStartChange?.(d);
5042
5176
  return;
5043
5177
  }
5044
5178
  setEnd(d);
5179
+ onPendingStartChange?.(null);
5045
5180
  onSelect([start, d]);
5046
5181
  };
5047
5182
  const GRID_COLS = "grid grid-cols-[repeat(7,var(--cmp-datepicker-calendar-day-width))]";
@@ -5176,8 +5311,11 @@ function SDateRangePicker({
5176
5311
  const portalContainer = usePortalContainer();
5177
5312
  const [open, setOpen] = useState(false);
5178
5313
  const [ruleError, setRuleError] = useState("");
5314
+ const [pendingStart, setPendingStart] = useState(null);
5179
5315
  const hasValue = value != null && value[0] !== "" && value[1] !== "";
5180
- 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] : "";
5181
5319
  useFormField({
5182
5320
  name,
5183
5321
  validate: () => {
@@ -5191,6 +5329,7 @@ function SDateRangePicker({
5191
5329
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
5192
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 };
5193
5331
  const select = (r) => {
5332
+ setPendingStart(null);
5194
5333
  onValueChange?.(r);
5195
5334
  if (rules && rules.length > 0) setRuleError(runRules(r, rules));
5196
5335
  setOpen(false);
@@ -5215,52 +5354,80 @@ function SDateRangePicker({
5215
5354
  disabled,
5216
5355
  className,
5217
5356
  style,
5218
- children: /* @__PURE__ */ jsxs(RPopover.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
5219
- /* @__PURE__ */ jsx(RPopover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
5220
- "button",
5221
- {
5222
- type: "button",
5223
- name,
5224
- disabled,
5225
- className: "flex h-full w-full items-center overflow-hidden bg-transparent outline-none disabled:cursor-not-allowed",
5226
- style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
5227
- children: [
5228
- /* @__PURE__ */ jsx(
5229
- SIcon,
5230
- {
5231
- name: "date",
5232
- size: dims.icon,
5233
- color: disabled ? "var(--cmp-datepicker-icon-disabled)" : "var(--cmp-datepicker-icon-default)",
5234
- className: "flex-shrink-0"
5235
- }
5236
- ),
5237
- /* @__PURE__ */ jsx(
5238
- "span",
5239
- {
5240
- className: "flex-1 truncate text-center",
5241
- style: {
5242
- minWidth: dims.minW,
5243
- fontSize: dims.fontSize,
5244
- lineHeight: `${dims.lineHeight}px`,
5245
- color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasValue ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
5246
- },
5247
- children: display
5248
- }
5249
- )
5250
- ]
5251
- }
5252
- ) }),
5253
- /* @__PURE__ */ jsx(RPopover.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(RPopover.Content, { side: "bottom", align: "start", sideOffset: 4, className: "z-50", children: /* @__PURE__ */ jsx(
5254
- RangeCalendar,
5255
- {
5256
- value: value ?? null,
5257
- selectable,
5258
- maxRange,
5259
- onSelect: select,
5260
- onViewChange
5261
- }
5262
- ) }) })
5263
- ] })
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",
5369
+ {
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
+ ]
5406
+ }
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
+ )
5264
5431
  }
5265
5432
  );
5266
5433
  }
@@ -5269,7 +5436,7 @@ var SChip = forwardRef(function SChip2({
5269
5436
  value = "",
5270
5437
  error: errorProp = false,
5271
5438
  disabled = false,
5272
- editable = true,
5439
+ editable = false,
5273
5440
  showRemove = true,
5274
5441
  placeholder = "",
5275
5442
  rules,
@@ -5422,6 +5589,7 @@ function SFilePicker({
5422
5589
  hovered = false,
5423
5590
  inline = false,
5424
5591
  multiple = false,
5592
+ useMultipleListBox = false,
5425
5593
  accept,
5426
5594
  maxFileSize,
5427
5595
  maxTotalSize,
@@ -5449,8 +5617,8 @@ function SFilePicker({
5449
5617
  const inputRef = useRef(null);
5450
5618
  const [ruleError, setRuleError] = useState("");
5451
5619
  const files = multiple ? Array.isArray(value) ? value : [] : value && !Array.isArray(value) ? value : null;
5452
- const hasSingle = !multiple && !!files;
5453
- 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;
5454
5622
  useFormField({
5455
5623
  name,
5456
5624
  validate: () => {
@@ -5514,78 +5682,88 @@ function SFilePicker({
5514
5682
  const padX = size === "md" ? "var(--cmp-filepicker-md-paddingX)" : "var(--cmp-filepicker-sm-paddingX)";
5515
5683
  const iconSize = size === "md" ? 20 : 16;
5516
5684
  const gapCls = size === "md" ? "gap-[var(--cmp-filepicker-md-gap)]" : "gap-[var(--cmp-filepicker-sm-gap)]";
5517
- const content = /* @__PURE__ */ jsxs("div", { className: cn("flex h-full w-full items-center", gapCls), style: { padding: `0 ${padX}` }, children: [
5518
- /* @__PURE__ */ jsx(
5519
- "input",
5520
- {
5521
- ref: inputRef,
5522
- type: "file",
5523
- className: "hidden",
5524
- disabled,
5525
- multiple,
5526
- accept,
5527
- name: resolvedName,
5528
- onChange,
5529
- "aria-label": placeholder
5530
- }
5531
- ),
5532
- /* @__PURE__ */ jsxs(
5533
- "button",
5534
- {
5535
- type: "button",
5536
- disabled,
5537
- onClick: open,
5538
- "aria-label": placeholder,
5539
- className: cn(
5540
- "flex min-w-0 flex-1 items-center bg-transparent text-left",
5541
- gapCls,
5542
- 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
+ }
5543
5705
  ),
5544
- children: [
5545
- /* @__PURE__ */ jsx(
5546
- SIcon,
5547
- {
5548
- name: "attachFile",
5549
- size: iconSize,
5550
- color: disabled ? "var(--cmp-filepicker-icon-disabled)" : "var(--color-grey-65)"
5551
- }
5552
- ),
5553
- /* @__PURE__ */ jsx(
5554
- "span",
5555
- {
5556
- className: "min-w-0 flex-1 truncate text-[12px]",
5557
- style: {
5558
- color: disabled ? "var(--cmp-filepicker-text-disabled)" : hasSingle ? "var(--cmp-filepicker-text-default)" : "var(--cmp-filepicker-text-placeholder)"
5559
- },
5560
- children: displayText
5561
- }
5562
- )
5563
- ]
5564
- }
5565
- ),
5566
- !disabled && hasSingle && /* @__PURE__ */ jsx(SGhostButton, { icon: "close", size: "xxs", ariaLabel: "clear", onClick: clear })
5567
- ] });
5568
- 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)) });
5569
- if (inline) {
5570
- return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col", className), style, children: [
5571
- /* @__PURE__ */ jsx(
5572
- "div",
5573
- {
5574
- 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)]",
5575
- style: {
5576
- height: "var(--cmp-filepicker-sm-height)",
5577
- width: typeof width === "number" ? `${width}px` : width
5578
- },
5579
- children: content
5580
- }
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"
5581
5753
  ),
5582
- chips
5583
- ] });
5584
- }
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
+ );
5585
5762
  return /* @__PURE__ */ jsxs("div", { className, style, children: [
5586
5763
  /* @__PURE__ */ jsx(
5587
5764
  SField,
5588
5765
  {
5766
+ borderless: inline,
5589
5767
  label,
5590
5768
  labelWidth,
5591
5769
  icon,
@@ -5613,6 +5791,7 @@ function SKeyValueTable({
5613
5791
  fields = [],
5614
5792
  search = false,
5615
5793
  radius = "default",
5794
+ dense = false,
5616
5795
  onChange,
5617
5796
  onSearch,
5618
5797
  className,
@@ -5620,6 +5799,8 @@ function SKeyValueTable({
5620
5799
  }) {
5621
5800
  if (fields.length === 0) return null;
5622
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)";
5623
5804
  const renderField = (f) => {
5624
5805
  if (f.render) return f.render;
5625
5806
  const opt = f.options ?? {};
@@ -5700,10 +5881,15 @@ function SKeyValueTable({
5700
5881
  rowSpan: f.thRowSpan && f.thRowSpan > 1 ? f.thRowSpan : void 0,
5701
5882
  colSpan: f.thColSpan && f.thColSpan > 1 ? f.thColSpan : void 0,
5702
5883
  className: cn(
5703
- "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-padding)] text-left align-middle",
5884
+ "box-border bg-[var(--cmp-table-header-bg)] px-[var(--cmp-table-header-paddingX)] text-left align-middle",
5704
5885
  cellBorder,
5705
5886
  f.thClass
5706
5887
  ),
5888
+ style: {
5889
+ height: cellHeight,
5890
+ paddingTop: cellPadY,
5891
+ paddingBottom: cellPadY
5892
+ },
5707
5893
  children: /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full items-center justify-between gap-[var(--cmp-table-header-gap)]", children: [
5708
5894
  /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-[8px]", children: [
5709
5895
  /* @__PURE__ */ jsx("span", { className: "text-[12px] font-bold leading-[20px] text-[color:var(--sys-color-fg-primary)]", children: f.label }),
@@ -5729,10 +5915,11 @@ function SKeyValueTable({
5729
5915
  rowSpan: f.tdRowSpan && f.tdRowSpan > 1 ? f.tdRowSpan : void 0,
5730
5916
  colSpan: f.tdColSpan && f.tdColSpan > 1 ? f.tdColSpan : void 0,
5731
5917
  className: cn(
5732
- "h-[var(--cmp-table-keyValue-height)] box-border bg-[var(--sys-color-bg-frame)] p-[var(--cmp-table-keyValue-body-padding)] align-middle",
5918
+ "box-border bg-[var(--sys-color-bg-frame)] px-[var(--cmp-table-keyValue-body-paddingX-default)] align-middle",
5733
5919
  cellBorder,
5734
5920
  f.tdClass
5735
5921
  ),
5922
+ style: { height: cellHeight, paddingTop: cellPadY, paddingBottom: cellPadY },
5736
5923
  children: renderField(f)
5737
5924
  },
5738
5925
  `td-${r}-${c}`