sellmate-design-system-react 0.1.9 → 0.1.11

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 (148) hide show
  1. package/dist/components/SActionModal/SActionModal.cjs +3 -1
  2. package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
  3. package/dist/components/SActionModal/SActionModal.js +3 -1
  4. package/dist/components/SActionModal/SActionModal.js.map +1 -1
  5. package/dist/components/SBarcodeInput/SBarcodeInput.cjs +16 -3
  6. package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
  7. package/dist/components/SBarcodeInput/SBarcodeInput.d.ts +3 -1
  8. package/dist/components/SBarcodeInput/SBarcodeInput.js +16 -3
  9. package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
  10. package/dist/components/SCalendar/SCalendar.cjs +2 -0
  11. package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
  12. package/dist/components/SCalendar/SCalendar.js +2 -0
  13. package/dist/components/SCalendar/SCalendar.js.map +1 -1
  14. package/dist/components/SChip/SChip.cjs +2 -0
  15. package/dist/components/SChip/SChip.cjs.map +1 -1
  16. package/dist/components/SChip/SChip.js +2 -0
  17. package/dist/components/SChip/SChip.js.map +1 -1
  18. package/dist/components/SChipInput/SChipInput.cjs +22 -4
  19. package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
  20. package/dist/components/SChipInput/SChipInput.d.ts +5 -0
  21. package/dist/components/SChipInput/SChipInput.js +22 -4
  22. package/dist/components/SChipInput/SChipInput.js.map +1 -1
  23. package/dist/components/SChipInput/index.d.ts +1 -1
  24. package/dist/components/SCircleProgress/SCircleProgress.cjs +69 -50
  25. package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
  26. package/dist/components/SCircleProgress/SCircleProgress.d.ts +2 -0
  27. package/dist/components/SCircleProgress/SCircleProgress.js +69 -50
  28. package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
  29. package/dist/components/SConfirmModal/SConfirmModal.cjs +13 -10
  30. package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
  31. package/dist/components/SConfirmModal/SConfirmModal.d.ts +7 -4
  32. package/dist/components/SConfirmModal/SConfirmModal.js +13 -10
  33. package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
  34. package/dist/components/SDatePicker/SDatePicker.cjs +16 -3
  35. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  36. package/dist/components/SDatePicker/SDatePicker.d.ts +3 -1
  37. package/dist/components/SDatePicker/SDatePicker.js +16 -3
  38. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  39. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +16 -3
  40. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  41. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +3 -1
  42. package/dist/components/SDateRangePicker/SDateRangePicker.js +16 -3
  43. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  44. package/dist/components/SDropdownButton/SDropdownButton.cjs +2 -0
  45. package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
  46. package/dist/components/SDropdownButton/SDropdownButton.js +2 -0
  47. package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
  48. package/dist/components/SField/SField.cjs +14 -3
  49. package/dist/components/SField/SField.cjs.map +1 -1
  50. package/dist/components/SField/SField.d.ts +4 -1
  51. package/dist/components/SField/SField.js +14 -3
  52. package/dist/components/SField/SField.js.map +1 -1
  53. package/dist/components/SFilePicker/SFilePicker.cjs +51 -19
  54. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  55. package/dist/components/SFilePicker/SFilePicker.d.ts +4 -2
  56. package/dist/components/SFilePicker/SFilePicker.js +50 -19
  57. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  58. package/dist/components/SGhostButton/SGhostButton.cjs +2 -0
  59. package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
  60. package/dist/components/SGhostButton/SGhostButton.js +2 -0
  61. package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
  62. package/dist/components/SGuide/SGuide.cjs +2 -0
  63. package/dist/components/SGuide/SGuide.cjs.map +1 -1
  64. package/dist/components/SGuide/SGuide.js +2 -0
  65. package/dist/components/SGuide/SGuide.js.map +1 -1
  66. package/dist/components/SInput/SInput.cjs +16 -3
  67. package/dist/components/SInput/SInput.cjs.map +1 -1
  68. package/dist/components/SInput/SInput.d.ts +4 -1
  69. package/dist/components/SInput/SInput.js +16 -3
  70. package/dist/components/SInput/SInput.js.map +1 -1
  71. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +530 -219
  72. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  73. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +27 -6
  74. package/dist/components/SKeyValueTable/SKeyValueTable.js +529 -218
  75. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  76. package/dist/components/SLoadingContainer/SLoadingContainer.cjs +69 -50
  77. package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
  78. package/dist/components/SLoadingContainer/SLoadingContainer.js +69 -50
  79. package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
  80. package/dist/components/SLoadingContainer/loading.cjs +69 -50
  81. package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
  82. package/dist/components/SLoadingContainer/loading.js +69 -50
  83. package/dist/components/SLoadingContainer/loading.js.map +1 -1
  84. package/dist/components/SLoadingModal/SLoadingModal.cjs +75 -51
  85. package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
  86. package/dist/components/SLoadingModal/SLoadingModal.d.ts +3 -1
  87. package/dist/components/SLoadingModal/SLoadingModal.js +75 -51
  88. package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
  89. package/dist/components/SModalContainer/SModalContainer.cjs +3 -1
  90. package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
  91. package/dist/components/SModalContainer/SModalContainer.js +3 -1
  92. package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
  93. package/dist/components/SNumberInput/SNumberInput.cjs +16 -3
  94. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  95. package/dist/components/SNumberInput/SNumberInput.d.ts +2 -0
  96. package/dist/components/SNumberInput/SNumberInput.js +16 -3
  97. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  98. package/dist/components/SPopover/SPopover.cjs +2 -0
  99. package/dist/components/SPopover/SPopover.cjs.map +1 -1
  100. package/dist/components/SPopover/SPopover.js +2 -0
  101. package/dist/components/SPopover/SPopover.js.map +1 -1
  102. package/dist/components/SPortal/SPortal.cjs +2 -0
  103. package/dist/components/SPortal/SPortal.cjs.map +1 -1
  104. package/dist/components/SPortal/SPortal.js +2 -0
  105. package/dist/components/SPortal/SPortal.js.map +1 -1
  106. package/dist/components/SRadio/SRadioGroup.cjs +1 -1
  107. package/dist/components/SRadio/SRadioGroup.cjs.map +1 -1
  108. package/dist/components/SRadio/SRadioGroup.js +1 -1
  109. package/dist/components/SRadio/SRadioGroup.js.map +1 -1
  110. package/dist/components/SSelect/SSelect.cjs +530 -258
  111. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  112. package/dist/components/SSelect/SSelect.d.ts +4 -2
  113. package/dist/components/SSelect/SSelect.js +531 -258
  114. package/dist/components/SSelect/SSelect.js.map +1 -1
  115. package/dist/components/STable/STable.cjs +603 -309
  116. package/dist/components/STable/STable.cjs.map +1 -1
  117. package/dist/components/STable/STable.js +604 -309
  118. package/dist/components/STable/STable.js.map +1 -1
  119. package/dist/components/STextLink/STextLink.cjs +2 -0
  120. package/dist/components/STextLink/STextLink.cjs.map +1 -1
  121. package/dist/components/STextLink/STextLink.js +2 -0
  122. package/dist/components/STextLink/STextLink.js.map +1 -1
  123. package/dist/components/STextarea/STextarea.cjs +16 -3
  124. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  125. package/dist/components/STextarea/STextarea.d.ts +2 -0
  126. package/dist/components/STextarea/STextarea.js +16 -3
  127. package/dist/components/STextarea/STextarea.js.map +1 -1
  128. package/dist/components/SToast/SToast.cjs +2 -0
  129. package/dist/components/SToast/SToast.cjs.map +1 -1
  130. package/dist/components/SToast/SToast.js +2 -0
  131. package/dist/components/SToast/SToast.js.map +1 -1
  132. package/dist/components/SToast/SToastContainer.cjs +2 -0
  133. package/dist/components/SToast/SToastContainer.cjs.map +1 -1
  134. package/dist/components/SToast/SToastContainer.js +2 -0
  135. package/dist/components/SToast/SToastContainer.js.map +1 -1
  136. package/dist/components/STooltip/STooltip.cjs +3 -1
  137. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  138. package/dist/components/STooltip/STooltip.js +3 -1
  139. package/dist/components/STooltip/STooltip.js.map +1 -1
  140. package/dist/index.cjs +522 -248
  141. package/dist/index.cjs.map +1 -1
  142. package/dist/index.d.ts +0 -1
  143. package/dist/index.js +523 -248
  144. package/dist/index.js.map +1 -1
  145. package/dist/lib/portal-container.d.ts +7 -0
  146. package/dist/styles.css +123 -58
  147. package/dist/theme.css +45 -21
  148. package/package.json +1 -1
@@ -6,7 +6,6 @@ var clsx = require('clsx');
6
6
  var tailwindMerge = require('tailwind-merge');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
8
  var RCheckbox = require('@radix-ui/react-checkbox');
9
- var Ariakit = require('@ariakit/react');
10
9
  var RPopover2 = require('@radix-ui/react-popover');
11
10
 
12
11
  function _interopNamespace(e) {
@@ -28,7 +27,6 @@ function _interopNamespace(e) {
28
27
  }
29
28
 
30
29
  var RCheckbox__namespace = /*#__PURE__*/_interopNamespace(RCheckbox);
31
- var Ariakit__namespace = /*#__PURE__*/_interopNamespace(Ariakit);
32
30
  var RPopover2__namespace = /*#__PURE__*/_interopNamespace(RPopover2);
33
31
 
34
32
  var twMerge = tailwindMerge.extendTailwindMerge({
@@ -2959,12 +2957,35 @@ var COLOR_MAP = {
2959
2957
  };
2960
2958
  var clamp = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
2961
2959
  var SCircleProgress = react.forwardRef(
2962
- function SCircleProgress2({ value = 0, type = "primary", indeterminate = false, label, className, style }, ref) {
2960
+ function SCircleProgress2({
2961
+ value = 0,
2962
+ type = "primary",
2963
+ indeterminate = false,
2964
+ label,
2965
+ innerValue = false,
2966
+ className,
2967
+ style
2968
+ }, ref) {
2963
2969
  const v = clamp(value);
2964
2970
  const colors = COLOR_MAP[type];
2965
2971
  const c = VIEWBOX / 2;
2966
2972
  const percentColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : colors.arc;
2967
2973
  const filled = v / 100 * CIRCUMFERENCE;
2974
+ const showPercent = !indeterminate;
2975
+ const percentNode = /* @__PURE__ */ jsxRuntime.jsxs(
2976
+ "span",
2977
+ {
2978
+ className: cn(
2979
+ "text-center text-[16px] font-bold leading-[26px]",
2980
+ innerValue && "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
2981
+ ),
2982
+ style: { color: percentColor },
2983
+ children: [
2984
+ Math.round(v),
2985
+ "%"
2986
+ ]
2987
+ }
2988
+ );
2968
2989
  return /* @__PURE__ */ jsxRuntime.jsxs(
2969
2990
  "div",
2970
2991
  {
@@ -2975,57 +2996,53 @@ var SCircleProgress = react.forwardRef(
2975
2996
  ),
2976
2997
  style,
2977
2998
  children: [
2978
- /* @__PURE__ */ jsxRuntime.jsxs(
2979
- "svg",
2980
- {
2981
- viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`,
2982
- xmlns: "http://www.w3.org/2000/svg",
2983
- className: cn("h-[80px] w-[80px]", indeterminate && "origin-center animate-circle-spin"),
2984
- children: [
2985
- /* @__PURE__ */ jsxRuntime.jsx(
2986
- "circle",
2987
- {
2988
- cx: c,
2989
- cy: c,
2990
- r: RADIUS,
2991
- fill: "none",
2992
- strokeWidth: STROKE,
2993
- style: { stroke: colors.track }
2994
- }
2999
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(innerValue && "relative"), children: [
3000
+ /* @__PURE__ */ jsxRuntime.jsxs(
3001
+ "svg",
3002
+ {
3003
+ viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`,
3004
+ xmlns: "http://www.w3.org/2000/svg",
3005
+ className: cn(
3006
+ "h-[80px] w-[80px]",
3007
+ indeterminate && "origin-center animate-circle-spin"
2995
3008
  ),
2996
- /* @__PURE__ */ jsxRuntime.jsx(
2997
- "circle",
2998
- {
2999
- cx: c,
3000
- cy: c,
3001
- r: RADIUS,
3002
- fill: "none",
3003
- strokeWidth: STROKE,
3004
- strokeLinecap: "round",
3005
- className: cn(
3006
- "origin-center",
3007
- indeterminate ? "animate-circle-arc" : "-rotate-90 transition-[stroke-dasharray] duration-[400ms] ease-linear"
3008
- ),
3009
- style: {
3010
- stroke: colors.arc,
3011
- ...indeterminate ? {} : { strokeDasharray: `${filled} ${CIRCUMFERENCE}`, strokeDashoffset: 0 }
3009
+ children: [
3010
+ /* @__PURE__ */ jsxRuntime.jsx(
3011
+ "circle",
3012
+ {
3013
+ cx: c,
3014
+ cy: c,
3015
+ r: RADIUS,
3016
+ fill: "none",
3017
+ strokeWidth: STROKE,
3018
+ style: { stroke: colors.track }
3012
3019
  }
3013
- }
3014
- )
3015
- ]
3016
- }
3017
- ),
3018
- !indeterminate && /* @__PURE__ */ jsxRuntime.jsxs(
3019
- "span",
3020
- {
3021
- className: "text-center text-[16px] font-bold leading-[26px]",
3022
- style: { color: percentColor },
3023
- children: [
3024
- Math.round(v),
3025
- "%"
3026
- ]
3027
- }
3028
- ),
3020
+ ),
3021
+ /* @__PURE__ */ jsxRuntime.jsx(
3022
+ "circle",
3023
+ {
3024
+ cx: c,
3025
+ cy: c,
3026
+ r: RADIUS,
3027
+ fill: "none",
3028
+ strokeWidth: STROKE,
3029
+ strokeLinecap: "round",
3030
+ className: cn(
3031
+ "origin-center",
3032
+ indeterminate ? "animate-circle-arc" : "-rotate-90 transition-[stroke-dasharray] duration-[400ms] ease-linear"
3033
+ ),
3034
+ style: {
3035
+ stroke: colors.arc,
3036
+ ...indeterminate ? {} : { strokeDasharray: `${filled} ${CIRCUMFERENCE}`, strokeDashoffset: 0 }
3037
+ }
3038
+ }
3039
+ )
3040
+ ]
3041
+ }
3042
+ ),
3043
+ showPercent && innerValue && percentNode
3044
+ ] }),
3045
+ showPercent && !innerValue && percentNode,
3029
3046
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-center text-[12px] font-normal leading-[20px] text-[color:var(--cmp-progress-color-label)]", children: label })
3030
3047
  ]
3031
3048
  }
@@ -3116,10 +3133,110 @@ function SPagination({
3116
3133
  );
3117
3134
  }
3118
3135
  var PortalContainerContext = react.createContext(null);
3136
+ var InsideModalContext = react.createContext(false);
3119
3137
  PortalContainerContext.Provider;
3138
+ InsideModalContext.Provider;
3120
3139
  function usePortalContainer() {
3121
3140
  return react.useContext(PortalContainerContext) ?? void 0;
3122
3141
  }
3142
+ function useInsideModal() {
3143
+ return react.useContext(InsideModalContext);
3144
+ }
3145
+ function runRules(value, rules) {
3146
+ if (!rules || rules.length === 0) return "";
3147
+ for (const rule of rules) {
3148
+ const result = rule(value ?? "");
3149
+ if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
3150
+ }
3151
+ return "";
3152
+ }
3153
+ var FormContext = react.createContext(null);
3154
+ function useFormContext() {
3155
+ return react.useContext(FormContext);
3156
+ }
3157
+ function useFormField(field) {
3158
+ const ctx = useFormContext();
3159
+ const cbRef = react.useRef(field);
3160
+ cbRef.current = field;
3161
+ const name = field.name ?? "";
3162
+ react.useEffect(() => {
3163
+ if (!ctx) return;
3164
+ return ctx.register({
3165
+ name,
3166
+ validate: () => cbRef.current.validate(),
3167
+ resetValidation: () => cbRef.current.resetValidation()
3168
+ });
3169
+ }, [ctx, name]);
3170
+ }
3171
+
3172
+ // src/lib/sanitize-inline-html.ts
3173
+ var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
3174
+ var ALLOWED_ATTRS_BY_TAG = {
3175
+ A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
3176
+ };
3177
+ var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
3178
+ var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
3179
+ var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
3180
+ "SCRIPT",
3181
+ "STYLE",
3182
+ "IFRAME",
3183
+ "OBJECT",
3184
+ "EMBED",
3185
+ "META",
3186
+ "LINK",
3187
+ "BASE",
3188
+ "NOSCRIPT",
3189
+ // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
3190
+ // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
3191
+ "SVG",
3192
+ "MATH",
3193
+ "TEMPLATE"
3194
+ ]);
3195
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3196
+ var sanitizeNode = (node) => {
3197
+ if (node.nodeType === Node.COMMENT_NODE) {
3198
+ node.remove();
3199
+ return;
3200
+ }
3201
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
3202
+ const element = node;
3203
+ const tagName = element.tagName;
3204
+ if (DROP_CONTENT_TAGS.has(tagName)) {
3205
+ element.remove();
3206
+ return;
3207
+ }
3208
+ Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
3209
+ if (ALLOWED_INLINE_TAGS.has(tagName)) {
3210
+ if (element.hasAttribute("className")) {
3211
+ const val = element.getAttribute("className");
3212
+ element.removeAttribute("className");
3213
+ if (!element.hasAttribute("class")) element.setAttribute("class", val);
3214
+ }
3215
+ const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
3216
+ Array.from(element.attributes).forEach((attr) => {
3217
+ if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
3218
+ });
3219
+ if (tagName === "A") {
3220
+ const href = element.getAttribute("href");
3221
+ if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
3222
+ if (element.hasAttribute("target")) {
3223
+ element.setAttribute("rel", "noopener noreferrer");
3224
+ }
3225
+ }
3226
+ return;
3227
+ }
3228
+ const parent = element.parentNode;
3229
+ if (parent == null) return;
3230
+ while (element.firstChild) parent.insertBefore(element.firstChild, element);
3231
+ parent.removeChild(element);
3232
+ };
3233
+ var sanitizeInlineHtml = (value) => {
3234
+ if (typeof document === "undefined") return escapeHtml(value);
3235
+ const template = document.createElement("template");
3236
+ template.innerHTML = value;
3237
+ Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
3238
+ return template.innerHTML;
3239
+ };
3123
3240
 
3124
3241
  // src/lib/floating-anim.ts
3125
3242
  var FLOATING_SLIDE_ANIM = [
@@ -3391,7 +3508,7 @@ var STooltip = react.forwardRef(function STooltip2({
3391
3508
  if (disabled || body == null) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: triggerNode });
3392
3509
  const isHover = trigger === "hover";
3393
3510
  const contentClass = cn(
3394
- "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)]",
3511
+ "z-50 max-w-xs rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
3395
3512
  FLOATING_SLIDE_ANIM,
3396
3513
  className
3397
3514
  );
@@ -3457,32 +3574,6 @@ var STooltip = react.forwardRef(function STooltip2({
3457
3574
  ] });
3458
3575
  });
3459
3576
  STooltip.displayName = "STooltip";
3460
- function runRules(value, rules) {
3461
- if (!rules || rules.length === 0) return "";
3462
- for (const rule of rules) {
3463
- const result = rule(value ?? "");
3464
- if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
3465
- }
3466
- return "";
3467
- }
3468
- var FormContext = react.createContext(null);
3469
- function useFormContext() {
3470
- return react.useContext(FormContext);
3471
- }
3472
- function useFormField(field) {
3473
- const ctx = useFormContext();
3474
- const cbRef = react.useRef(field);
3475
- cbRef.current = field;
3476
- const name = field.name ?? "";
3477
- react.useEffect(() => {
3478
- if (!ctx) return;
3479
- return ctx.register({
3480
- name,
3481
- validate: () => cbRef.current.validate(),
3482
- resetValidation: () => cbRef.current.resetValidation()
3483
- });
3484
- }, [ctx, name]);
3485
- }
3486
3577
  var FIELD_SIZE = {
3487
3578
  sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 16 },
3488
3579
  md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 16 }
@@ -3497,6 +3588,7 @@ var SField = react.forwardRef(function SField2({
3497
3588
  name,
3498
3589
  label = "",
3499
3590
  icon,
3591
+ iconColor,
3500
3592
  labelTooltip = "",
3501
3593
  labelTooltipProps,
3502
3594
  labelWidth,
@@ -3574,7 +3666,15 @@ var SField = react.forwardRef(function SField2({
3574
3666
  lineHeight: `${s.lineHeight}px`
3575
3667
  },
3576
3668
  children: [
3577
- icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3669
+ icon && /* @__PURE__ */ jsxRuntime.jsx(
3670
+ SIcon,
3671
+ {
3672
+ name: icon,
3673
+ size: s.icon,
3674
+ color: iconColor,
3675
+ style: { marginRight: s.labelGap }
3676
+ }
3677
+ ),
3578
3678
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3579
3679
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
3580
3680
  STooltip,
@@ -3624,7 +3724,7 @@ var SField = react.forwardRef(function SField2({
3624
3724
  borderBottomLeftRadius: s.radius - 1
3625
3725
  },
3626
3726
  children: [
3627
- icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon }),
3727
+ icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
3628
3728
  effectiveAddonLabel
3629
3729
  ]
3630
3730
  }
@@ -3639,6 +3739,77 @@ var SField = react.forwardRef(function SField2({
3639
3739
  }
3640
3740
  );
3641
3741
  });
3742
+ function SPortal({
3743
+ open,
3744
+ onClose,
3745
+ onOpenChange,
3746
+ anchorRef,
3747
+ placement = "bottom",
3748
+ align = "start",
3749
+ offset = 4,
3750
+ alignOffset = 0,
3751
+ fit = false,
3752
+ maxHeight,
3753
+ autoClose = false,
3754
+ persistent = false,
3755
+ viewportPadding = 0,
3756
+ onOpenAutoFocus,
3757
+ onCloseAutoFocus,
3758
+ children,
3759
+ className,
3760
+ style,
3761
+ ariaLabel
3762
+ }) {
3763
+ const portalContainer = usePortalContainer();
3764
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3765
+ RPopover2__namespace.Root,
3766
+ {
3767
+ open,
3768
+ onOpenChange: (next) => {
3769
+ onOpenChange?.(next);
3770
+ if (!next) onClose?.();
3771
+ },
3772
+ children: [
3773
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { virtualRef: anchorRef }),
3774
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
3775
+ RPopover2__namespace.Content,
3776
+ {
3777
+ side: placement,
3778
+ align,
3779
+ sideOffset: offset,
3780
+ alignOffset,
3781
+ collisionPadding: viewportPadding,
3782
+ "aria-label": ariaLabel,
3783
+ onPointerDownOutside: (e) => {
3784
+ if (persistent || anchorRef.current?.contains(e.target)) {
3785
+ e.preventDefault();
3786
+ }
3787
+ },
3788
+ onFocusOutside: (e) => {
3789
+ if (persistent || anchorRef.current?.contains(e.target)) {
3790
+ e.preventDefault();
3791
+ }
3792
+ },
3793
+ onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
3794
+ onOpenAutoFocus,
3795
+ onCloseAutoFocus,
3796
+ onClick: autoClose ? () => onClose?.() : void 0,
3797
+ className: cn(
3798
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm",
3799
+ maxHeight != null && "overflow-y-auto",
3800
+ fit && "min-w-(--radix-popover-trigger-width)",
3801
+ FLOATING_SLIDE_ANIM,
3802
+ className
3803
+ ),
3804
+ style: { maxHeight, ...style },
3805
+ children
3806
+ }
3807
+ ) })
3808
+ ]
3809
+ }
3810
+ );
3811
+ }
3812
+ SPortal.displayName = "SPortal";
3642
3813
 
3643
3814
  // src/components/SSelect/select.config.ts
3644
3815
  function flattenOptions(options, depth = 1) {
@@ -3673,75 +3844,6 @@ function flattenOptions(options, depth = 1) {
3673
3844
  var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
3674
3845
  var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
3675
3846
  var extractText = (html) => html.replace(/<[^>]*>/g, "");
3676
-
3677
- // src/lib/sanitize-inline-html.ts
3678
- var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
3679
- var ALLOWED_ATTRS_BY_TAG = {
3680
- A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
3681
- };
3682
- var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
3683
- var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
3684
- var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
3685
- "SCRIPT",
3686
- "STYLE",
3687
- "IFRAME",
3688
- "OBJECT",
3689
- "EMBED",
3690
- "META",
3691
- "LINK",
3692
- "BASE",
3693
- "NOSCRIPT",
3694
- // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
3695
- // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
3696
- "SVG",
3697
- "MATH",
3698
- "TEMPLATE"
3699
- ]);
3700
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3701
- var sanitizeNode = (node) => {
3702
- if (node.nodeType === Node.COMMENT_NODE) {
3703
- node.remove();
3704
- return;
3705
- }
3706
- if (node.nodeType !== Node.ELEMENT_NODE) return;
3707
- const element = node;
3708
- const tagName = element.tagName;
3709
- if (DROP_CONTENT_TAGS.has(tagName)) {
3710
- element.remove();
3711
- return;
3712
- }
3713
- Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
3714
- if (ALLOWED_INLINE_TAGS.has(tagName)) {
3715
- if (element.hasAttribute("className")) {
3716
- const val = element.getAttribute("className");
3717
- element.removeAttribute("className");
3718
- if (!element.hasAttribute("class")) element.setAttribute("class", val);
3719
- }
3720
- const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
3721
- Array.from(element.attributes).forEach((attr) => {
3722
- if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
3723
- });
3724
- if (tagName === "A") {
3725
- const href = element.getAttribute("href");
3726
- if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
3727
- if (element.hasAttribute("target")) {
3728
- element.setAttribute("rel", "noopener noreferrer");
3729
- }
3730
- }
3731
- return;
3732
- }
3733
- const parent = element.parentNode;
3734
- if (parent == null) return;
3735
- while (element.firstChild) parent.insertBefore(element.firstChild, element);
3736
- parent.removeChild(element);
3737
- };
3738
- var sanitizeInlineHtml = (value) => {
3739
- if (typeof document === "undefined") return escapeHtml(value);
3740
- const template = document.createElement("template");
3741
- template.innerHTML = value;
3742
- Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
3743
- return template.innerHTML;
3744
- };
3745
3847
  var SEARCH_THRESHOLD = 11;
3746
3848
  var SSelect = react.forwardRef(function SSelect2({
3747
3849
  value,
@@ -3765,6 +3867,7 @@ var SSelect = react.forwardRef(function SSelect2({
3765
3867
  label,
3766
3868
  labelWidth,
3767
3869
  icon,
3870
+ iconColor,
3768
3871
  labelTooltip,
3769
3872
  addonLabel,
3770
3873
  addonAlign,
@@ -3776,18 +3879,15 @@ var SSelect = react.forwardRef(function SSelect2({
3776
3879
  }, ref) {
3777
3880
  const multi = type === "multi" || type === "multi_depth";
3778
3881
  const portalContainer = usePortalContainer();
3882
+ const insideModal = useInsideModal();
3779
3883
  const [search, setSearch] = react.useState("");
3780
3884
  const [ruleError, setRuleError] = react.useState("");
3781
3885
  const [open, setOpen] = react.useState(false);
3886
+ const [pendingOpen, setPendingOpen] = react.useState(false);
3887
+ const [focusedIndex, setFocusedIndex] = react.useState(-1);
3782
3888
  const triggerRef = react.useRef(null);
3783
- react.useImperativeHandle(
3784
- ref,
3785
- () => ({
3786
- focus: () => triggerRef.current?.focus(),
3787
- open: () => setOpen(true)
3788
- }),
3789
- []
3790
- );
3889
+ const searchInputRef = react.useRef(null);
3890
+ const listRef = react.useRef(null);
3791
3891
  const flat = react.useMemo(() => flattenOptions(options), [options]);
3792
3892
  const selectable = react.useMemo(() => selectableOptions(flat), [flat]);
3793
3893
  const hasLeaf = react.useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -3805,6 +3905,46 @@ var SSelect = react.forwardRef(function SSelect2({
3805
3905
  const arr = Array.isArray(value) ? value : [value];
3806
3906
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
3807
3907
  }, [value]);
3908
+ const selectedRef = react.useRef(selected);
3909
+ selectedRef.current = selected;
3910
+ const emit = (next) => {
3911
+ if (!onValueChange) return;
3912
+ const mapped = next.map(
3913
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3914
+ );
3915
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3916
+ };
3917
+ const commitOpen = (next) => {
3918
+ setOpen(next);
3919
+ onOpenChange?.(next);
3920
+ if (!next) {
3921
+ setPendingOpen(false);
3922
+ setSearch("");
3923
+ if (rules && rules.length > 0) {
3924
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
3925
+ }
3926
+ }
3927
+ };
3928
+ const requestOpen = (next) => {
3929
+ if (next && insideModal && portalContainer == null) {
3930
+ setPendingOpen(true);
3931
+ return;
3932
+ }
3933
+ commitOpen(next);
3934
+ };
3935
+ react.useEffect(() => {
3936
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
3937
+ setPendingOpen(false);
3938
+ commitOpen(true);
3939
+ }, [insideModal, pendingOpen, portalContainer]);
3940
+ react.useImperativeHandle(
3941
+ ref,
3942
+ () => ({
3943
+ focus: () => triggerRef.current?.focus(),
3944
+ open: () => requestOpen(true)
3945
+ }),
3946
+ [insideModal, portalContainer]
3947
+ );
3808
3948
  useFormField({
3809
3949
  name,
3810
3950
  validate: () => {
@@ -3814,13 +3954,6 @@ var SSelect = react.forwardRef(function SSelect2({
3814
3954
  },
3815
3955
  resetValidation: () => setRuleError("")
3816
3956
  });
3817
- const emit = (next) => {
3818
- if (!onValueChange) return;
3819
- const mapped = next.map(
3820
- (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3821
- );
3822
- onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3823
- };
3824
3957
  const filtered = react.useMemo(() => {
3825
3958
  if (!showSearch || search.trim() === "") return flat;
3826
3959
  const q = search.trim().toLowerCase();
@@ -3831,6 +3964,16 @@ var SSelect = react.forwardRef(function SSelect2({
3831
3964
  const allValues = selectable.map((o) => o.value);
3832
3965
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
3833
3966
  const hasItems = filtered.some((o) => !o.isGroup);
3967
+ const navigableItems = react.useMemo(() => {
3968
+ const items = [];
3969
+ if (multi && useSelectAll && allValues.length > 0) {
3970
+ items.push({ value: "__select_all__", isSelectAll: true });
3971
+ }
3972
+ filtered.forEach((o) => {
3973
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
3974
+ });
3975
+ return items;
3976
+ }, [allValues.length, filtered, multi, useSelectAll]);
3834
3977
  const triggerText = (() => {
3835
3978
  if (selected.length === 0) return placeholder;
3836
3979
  if (multi) {
@@ -3840,26 +3983,119 @@ var SSelect = react.forwardRef(function SSelect2({
3840
3983
  return labelByValue.get(selected[0]) ?? selected[0];
3841
3984
  })();
3842
3985
  const isPlaceholder = selected.length === 0;
3843
- const selectStore = /* @__PURE__ */ jsxRuntime.jsxs(
3844
- Ariakit__namespace.SelectProvider,
3986
+ const handleSelect = (nextValue) => {
3987
+ if (multi) {
3988
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
3989
+ emit(next);
3990
+ return;
3991
+ }
3992
+ emit([nextValue]);
3993
+ commitOpen(false);
3994
+ };
3995
+ const handleSelectAll = () => {
3996
+ emit(allSelected ? [] : allValues);
3997
+ };
3998
+ react.useEffect(() => {
3999
+ if (!open) {
4000
+ setFocusedIndex(-1);
4001
+ return;
4002
+ }
4003
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4004
+ }, [open, navigableItems.length]);
4005
+ react.useEffect(() => {
4006
+ if (!open) return;
4007
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4008
+ }, [search, open, navigableItems.length]);
4009
+ react.useEffect(() => {
4010
+ if (!open || focusedIndex < 0) return;
4011
+ const list = listRef.current;
4012
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
4013
+ if (!list || !focusedEl) return;
4014
+ const listRect = list.getBoundingClientRect();
4015
+ const itemRect = focusedEl.getBoundingClientRect();
4016
+ if (itemRect.top < listRect.top) {
4017
+ list.scrollTop += itemRect.top - listRect.top;
4018
+ } else if (itemRect.bottom > listRect.bottom) {
4019
+ list.scrollTop += itemRect.bottom - listRect.bottom;
4020
+ }
4021
+ }, [focusedIndex, open]);
4022
+ const moveFocusedIndex = (direction) => {
4023
+ if (!open || navigableItems.length === 0) return;
4024
+ setFocusedIndex((current) => {
4025
+ if (direction === "next") {
4026
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
4027
+ }
4028
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
4029
+ });
4030
+ };
4031
+ const selectFocusedItem = () => {
4032
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
4033
+ const focused = navigableItems[focusedIndex];
4034
+ if (focused.isSelectAll) {
4035
+ handleSelectAll();
4036
+ return;
4037
+ }
4038
+ handleSelect(focused.value);
4039
+ };
4040
+ const handleKeyDown = (event) => {
4041
+ if (!open) {
4042
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
4043
+ event.preventDefault();
4044
+ requestOpen(true);
4045
+ }
4046
+ return;
4047
+ }
4048
+ if (event.key === "ArrowDown") {
4049
+ event.preventDefault();
4050
+ moveFocusedIndex("next");
4051
+ return;
4052
+ }
4053
+ if (event.key === "ArrowUp") {
4054
+ event.preventDefault();
4055
+ moveFocusedIndex("prev");
4056
+ return;
4057
+ }
4058
+ if (event.key === "Enter") {
4059
+ event.preventDefault();
4060
+ selectFocusedItem();
4061
+ return;
4062
+ }
4063
+ if (event.key === "Escape") {
4064
+ event.preventDefault();
4065
+ commitOpen(false);
4066
+ }
4067
+ };
4068
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4069
+ SField,
3845
4070
  {
3846
- animated: portalContainer == null ? 150 : false,
3847
- value: multi ? selected : selected[0] ?? "",
3848
- setValue: (v) => emit(Array.isArray(v) ? v : v === "" ? [] : [v]),
3849
- open,
3850
- setOpen: (o) => {
3851
- setOpen(o);
3852
- onOpenChange?.(o);
3853
- if (!o && rules && rules.length > 0) {
3854
- setRuleError(runRules(multi ? selected : selected[0] ?? "", rules));
3855
- }
3856
- },
4071
+ label,
4072
+ labelWidth,
4073
+ icon,
4074
+ iconColor,
4075
+ labelTooltip,
4076
+ labelTooltipProps,
4077
+ addonLabel,
4078
+ addonAlign,
4079
+ hint,
4080
+ error: error || ruleError !== "",
4081
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4082
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
4083
+ disabled: effectiveDisabled,
4084
+ width,
4085
+ className,
4086
+ style,
3857
4087
  children: [
4088
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
3858
4089
  /* @__PURE__ */ jsxRuntime.jsxs(
3859
- Ariakit__namespace.Select,
4090
+ "button",
3860
4091
  {
3861
4092
  ref: triggerRef,
4093
+ type: "button",
3862
4094
  disabled: effectiveDisabled,
4095
+ "aria-haspopup": "listbox",
4096
+ "aria-expanded": open,
4097
+ onClick: () => requestOpen(!open),
4098
+ onKeyDown: handleKeyDown,
3863
4099
  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",
3864
4100
  style: {
3865
4101
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -3884,174 +4120,229 @@ var SSelect = react.forwardRef(function SSelect2({
3884
4120
  ]
3885
4121
  }
3886
4122
  ),
3887
- /* @__PURE__ */ jsxRuntime.jsxs(
3888
- Ariakit__namespace.SelectPopover,
4123
+ /* @__PURE__ */ jsxRuntime.jsx(
4124
+ SPortal,
3889
4125
  {
3890
- portal: true,
3891
- portalElement: portalContainer,
3892
- gutter: 4,
3893
- sameWidth: !dropdownWidth,
3894
- 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",
4126
+ open,
4127
+ onClose: () => commitOpen(false),
4128
+ onOpenChange: (next) => {
4129
+ if (next) requestOpen(true);
4130
+ },
4131
+ anchorRef: triggerRef,
4132
+ placement: "bottom",
4133
+ align: "start",
4134
+ offset: 4,
4135
+ fit: !dropdownWidth,
4136
+ onOpenAutoFocus: (event) => {
4137
+ if (!showSearch) {
4138
+ event.preventDefault();
4139
+ return;
4140
+ }
4141
+ event.preventDefault();
4142
+ requestAnimationFrame(() => searchInputRef.current?.focus());
4143
+ },
4144
+ onCloseAutoFocus: (event) => {
4145
+ event.preventDefault();
4146
+ triggerRef.current?.focus();
4147
+ },
4148
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] py-[4px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
3895
4149
  style: {
3896
- maxHeight: dropdownHeight,
3897
4150
  ...dropdownWidth ? { width: dropdownWidth } : {},
3898
4151
  maxWidth: maxDropdownWidth
3899
4152
  },
3900
- children: [
3901
- showSearch && // 원본 sd-select-list-item-search: sticky + 검색 아이콘 + 테두리 input + focus-within + clear
3902
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-[6px] rounded-[4px] border border-solid border-[color:var(--color-grey-45)] bg-white px-[8px] py-[4px] focus-within:border-[color:var(--sys-color-field-border-focus)]", children: [
3903
- /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
3904
- /* @__PURE__ */ jsxRuntime.jsx(
3905
- Ariakit__namespace.Combobox,
3906
- {
3907
- autoSelect: true,
3908
- placeholder: "\uAC80\uC0C9",
3909
- className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--cmp-select-text-default)] outline-none placeholder:text-[color:var(--color-grey-55)]"
3910
- }
3911
- ),
3912
- search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
3913
- SGhostButton,
3914
- {
3915
- icon: "close",
3916
- size: "xxs",
3917
- intent: "default",
3918
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
3919
- onClick: () => setSearch(""),
3920
- className: "shrink-0"
3921
- }
3922
- )
3923
- ] }) }),
3924
- multi && useSelectAll && /* @__PURE__ */ jsxRuntime.jsx(
3925
- "button",
3926
- {
3927
- type: "button",
3928
- className: "flex w-full items-center px-[12px] py-[4px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)] hover:bg-[var(--cmp-listItem-bg-hover)] hover:text-[color:var(--cmp-listItem-content-hover)]",
3929
- onClick: () => emit(allSelected ? [] : allValues),
3930
- children: allSelectedLabel
3931
- }
3932
- ),
3933
- hasItems ? /* @__PURE__ */ jsxRuntime.jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
3934
- // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
3935
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-[12px] py-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-disabled)]", children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
3936
- )
3937
- ]
4153
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4154
+ "div",
4155
+ {
4156
+ ref: listRef,
4157
+ className: "overflow-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-[8px] [&::-webkit-scrollbar]:bg-[var(--color-grey-25)] [&::-webkit-scrollbar:horizontal]:h-[8px] [&::-webkit-scrollbar-thumb]:rounded-[4px] [&::-webkit-scrollbar-thumb]:bg-[var(--color-grey-45)] [&::-webkit-scrollbar-track]:bg-[var(--color-grey-25)]",
4158
+ style: { maxHeight: dropdownHeight },
4159
+ onMouseLeave: () => setFocusedIndex(-1),
4160
+ children: [
4161
+ showSearch && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-[6px] rounded-[4px] border border-solid border-[color:var(--color-grey-45)] bg-white px-[8px] py-[4px] focus-within:border-[color:var(--sys-color-field-border-focus)]", children: [
4162
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4163
+ /* @__PURE__ */ jsxRuntime.jsx(
4164
+ "input",
4165
+ {
4166
+ ref: searchInputRef,
4167
+ value: search,
4168
+ onChange: (e) => setSearch(e.target.value),
4169
+ onKeyDown: handleKeyDown,
4170
+ placeholder: "\uAC80\uC0C9",
4171
+ className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--cmp-select-text-default)] outline-none placeholder:text-[color:var(--color-grey-55)]"
4172
+ }
4173
+ ),
4174
+ search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
4175
+ SGhostButton,
4176
+ {
4177
+ icon: "close",
4178
+ size: "xxs",
4179
+ intent: "default",
4180
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4181
+ onClick: () => setSearch(""),
4182
+ className: "shrink-0"
4183
+ }
4184
+ )
4185
+ ] }) }),
4186
+ hasItems ? /* @__PURE__ */ jsxRuntime.jsx(
4187
+ ItemList,
4188
+ {
4189
+ items: filtered,
4190
+ multi,
4191
+ selected,
4192
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
4193
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
4194
+ selectAllLabel: allSelectedLabel,
4195
+ selectAllSelected: allSelected,
4196
+ onSelectAll: handleSelectAll,
4197
+ onSelect: handleSelect,
4198
+ onHoverValue: (value2) => {
4199
+ const index = navigableItems.findIndex(
4200
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
4201
+ );
4202
+ if (index >= 0) setFocusedIndex(index);
4203
+ }
4204
+ }
4205
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-[12px] py-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-disabled)]", children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
4206
+ ]
4207
+ }
4208
+ )
3938
4209
  }
3939
4210
  )
3940
4211
  ]
3941
4212
  }
3942
4213
  );
3943
- const control = showSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
3944
- return /* @__PURE__ */ jsxRuntime.jsxs(
3945
- SField,
3946
- {
3947
- label,
3948
- labelWidth,
3949
- icon,
3950
- labelTooltip,
3951
- labelTooltipProps,
3952
- addonLabel,
3953
- addonAlign,
3954
- hint,
3955
- error: error || ruleError !== "",
3956
- status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
3957
- errorMessage: ruleError !== "" ? ruleError : errorMessage,
3958
- disabled: effectiveDisabled,
3959
- width,
3960
- className,
3961
- style,
3962
- children: [
3963
- name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
3964
- control
3965
- ]
3966
- }
3967
- );
3968
4214
  });
3969
4215
  SSelect.displayName = "SSelect";
3970
4216
  function ItemList({
3971
4217
  items,
3972
4218
  multi,
3973
4219
  selected,
3974
- useSearch
4220
+ focusedValue,
4221
+ showSelectAll,
4222
+ selectAllLabel,
4223
+ selectAllSelected,
4224
+ onSelectAll,
4225
+ onSelect,
4226
+ onHoverValue
3975
4227
  }) {
3976
4228
  const itemBase = cn(
3977
- // group: 체크박스 inverse(group-data-[active-item])를 위해 필요
3978
- "group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
4229
+ "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
3979
4230
  "text-[color:var(--cmp-listItem-content-default)]",
3980
- // Ariakit은 disabled 항목에 aria-disabled를 붙이므로 aria-disabled 변형으로 스타일 (data-disabled 아님)
3981
- "aria-disabled:cursor-not-allowed aria-disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4231
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
3982
4232
  );
3983
- const activeClass = "data-[active-item]:bg-[var(--cmp-listItem-bg-hover)] data-[active-item]:text-[color:var(--cmp-listItem-content-hover)]";
3984
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: items.map((o) => {
3985
- const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
3986
- if (o.isGroup) {
3987
- const isDepth1 = o.depth === 1;
3988
- return /* @__PURE__ */ jsxRuntime.jsx(
3989
- "div",
3990
- {
3991
- className: cn(
3992
- "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
3993
- 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",
3994
- // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
3995
- // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
3996
- o.disabled && "text-[color:var(--cmp-listItem-content-disabled)]",
3997
- o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
3998
- ),
3999
- style: { paddingLeft: padLeft },
4000
- children: o.label
4001
- },
4002
- o.value
4003
- );
4004
- }
4005
- const isSelected = selected.includes(o.value);
4006
- return /* @__PURE__ */ jsxRuntime.jsxs(
4007
- Ariakit__namespace.SelectItem,
4233
+ const activeClass = "hover:bg-[var(--cmp-listItem-bg-hover)] hover:text-[color:var(--cmp-listItem-content-hover)] focus:bg-[var(--cmp-listItem-bg-hover)] focus:text-[color:var(--cmp-listItem-content-hover)]";
4234
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
4235
+ showSelectAll && /* @__PURE__ */ jsxRuntime.jsxs(
4236
+ "button",
4008
4237
  {
4009
- value: o.value,
4010
- disabled: o.disabled,
4238
+ type: "button",
4239
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
4240
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
4011
4241
  className: cn(
4012
4242
  itemBase,
4013
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4014
- activeClass
4243
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
4015
4244
  ),
4016
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4017
- render: useSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxItem, {}) : void 0,
4245
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
4246
+ onMouseEnter: () => onHoverValue("__select_all__"),
4247
+ onClick: onSelectAll,
4018
4248
  children: [
4019
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
4020
4249
  /* @__PURE__ */ jsxRuntime.jsx(
4021
4250
  "span",
4022
4251
  {
4023
4252
  className: cn(
4024
4253
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4025
- o.disabled ? (
4026
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4027
- "border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4028
- ) : cn(
4029
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4030
- 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"
4031
- )
4254
+ selectAllSelected ? focusedValue === "__select_all__" ? "border-[color:var(--cmp-checkbox-checked-border-inverse)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-[var(--cmp-checkbox-unchecked-bg-default)]"
4032
4255
  ),
4033
- children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4256
+ children: selectAllSelected && /* @__PURE__ */ jsxRuntime.jsx(
4034
4257
  SIcon,
4035
4258
  {
4036
4259
  name: "checkboxCheck",
4037
4260
  size: 12,
4038
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4261
+ color: "var(--cmp-checkbox-checked-icon-default)"
4039
4262
  }
4040
4263
  )
4041
4264
  }
4042
4265
  ),
4043
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4044
- "span",
4045
- {
4046
- className: "flex-1 truncate",
4047
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4048
- }
4049
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4266
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
4050
4267
  ]
4051
- },
4052
- o.value
4053
- );
4054
- }) });
4268
+ }
4269
+ ),
4270
+ items.map((o) => {
4271
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4272
+ if (o.isGroup) {
4273
+ const isDepth1 = o.depth === 1;
4274
+ return /* @__PURE__ */ jsxRuntime.jsx(
4275
+ "div",
4276
+ {
4277
+ className: cn(
4278
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4279
+ 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",
4280
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4281
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4282
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4283
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4284
+ ),
4285
+ style: { paddingLeft: padLeft },
4286
+ children: o.label
4287
+ },
4288
+ o.value
4289
+ );
4290
+ }
4291
+ const isSelected = selected.includes(o.value);
4292
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4293
+ "button",
4294
+ {
4295
+ type: "button",
4296
+ disabled: o.disabled,
4297
+ role: "option",
4298
+ "aria-selected": isSelected,
4299
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
4300
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
4301
+ className: cn(
4302
+ itemBase,
4303
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4304
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
4305
+ activeClass
4306
+ ),
4307
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4308
+ onMouseEnter: () => onHoverValue(o.value),
4309
+ onClick: () => onSelect(o.value),
4310
+ children: [
4311
+ multi && /* @__PURE__ */ jsxRuntime.jsx(
4312
+ "span",
4313
+ {
4314
+ className: cn(
4315
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4316
+ o.disabled ? (
4317
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4318
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4319
+ ) : cn(
4320
+ isSelected ? focusedValue === o.value ? "border-[color:var(--cmp-checkbox-checked-border-inverse)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-[var(--cmp-checkbox-unchecked-bg-default)]"
4321
+ )
4322
+ ),
4323
+ children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4324
+ SIcon,
4325
+ {
4326
+ name: "checkboxCheck",
4327
+ size: 12,
4328
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4329
+ }
4330
+ )
4331
+ }
4332
+ ),
4333
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4334
+ "span",
4335
+ {
4336
+ className: "flex-1 truncate",
4337
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4338
+ }
4339
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4340
+ ]
4341
+ },
4342
+ o.value
4343
+ );
4344
+ })
4345
+ ] });
4055
4346
  }
4056
4347
  var DEFAULT_ROW_HEIGHT = 44;
4057
4348
  var DENSE_ROW_HEIGHT = 32;
@@ -4326,7 +4617,8 @@ var STable = react.forwardRef(function STable2({
4326
4617
  else onSelectedChange?.([...selected, row]);
4327
4618
  };
4328
4619
  const radiusClass = radius === "full" ? "rounded-none" : radius === "useTop" ? "rounded-b-[var(--cmp-table-radius)] rounded-t-none" : "rounded-[var(--cmp-table-radius)]";
4329
- const cellPadY = dense ? "var(--cmp-table-body-dense-paddingY)" : "var(--cmp-table-body-default-paddingY)";
4620
+ const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-body-paddingY-default)";
4621
+ const cellPadX = "var(--cmp-table-body-paddingX-wide)";
4330
4622
  const rowH = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-body-default-height)";
4331
4623
  const totalColSpan = visibleCols.length + (selectable ? 1 : 0);
4332
4624
  const stickyInfo = (i) => {
@@ -4501,13 +4793,15 @@ var STable = react.forwardRef(function STable2({
4501
4793
  className: cn(
4502
4794
  // 원본 .td: 컬럼 폭에 맞춰 내용 말줄임(overflow hidden + ellipsis) + 행 구분선(border-b)
4503
4795
  // 셀 배경은 흰색으로 채워 뒤 배경이 비치지 않게 하고, hover 시 행 배경(grey_05)을 따른다.
4504
- "overflow-hidden text-ellipsis whitespace-nowrap border-b border-solid border-[color:var(--cmp-table-border-color)] px-[16px] text-[12px] leading-[20px] text-[color:var(--sys-color-fg-primary)]",
4796
+ "overflow-hidden text-ellipsis whitespace-nowrap border-b border-solid border-[color:var(--cmp-table-border-color)] text-[12px] leading-[20px] text-[color:var(--sys-color-fg-primary)]",
4505
4797
  rowBg,
4506
4798
  "group-hover:bg-[var(--color-grey-05)]",
4507
4799
  stickyShadowClass(i),
4508
4800
  col.tdClass
4509
4801
  ),
4510
4802
  style: {
4803
+ paddingLeft: cellPadX,
4804
+ paddingRight: cellPadX,
4511
4805
  paddingTop: cellPadY,
4512
4806
  paddingBottom: cellPadY,
4513
4807
  textAlign: col.align ?? "left",