sellmate-design-system-react 0.1.10 → 0.2.0

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 (163) hide show
  1. package/dist/components/SActionModal/SActionModal.cjs +10 -4
  2. package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
  3. package/dist/components/SActionModal/SActionModal.js +10 -4
  4. package/dist/components/SActionModal/SActionModal.js.map +1 -1
  5. package/dist/components/SBarcodeInput/SBarcodeInput.cjs +22 -5
  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 +22 -5
  9. package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
  10. package/dist/components/SCalendar/SCalendar.cjs +9 -3
  11. package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
  12. package/dist/components/SCalendar/SCalendar.js +9 -3
  13. package/dist/components/SCalendar/SCalendar.js.map +1 -1
  14. package/dist/components/SChip/SChip.cjs +9 -3
  15. package/dist/components/SChip/SChip.cjs.map +1 -1
  16. package/dist/components/SChip/SChip.js +9 -3
  17. package/dist/components/SChip/SChip.js.map +1 -1
  18. package/dist/components/SChipInput/SChipInput.cjs +44 -13
  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 +44 -13
  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 +6 -2
  25. package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
  26. package/dist/components/SCircleProgress/SCircleProgress.js +6 -2
  27. package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
  28. package/dist/components/SConfirmModal/SConfirmModal.cjs +20 -13
  29. package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
  30. package/dist/components/SConfirmModal/SConfirmModal.d.ts +7 -4
  31. package/dist/components/SConfirmModal/SConfirmModal.js +20 -13
  32. package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
  33. package/dist/components/SDatePicker/SDatePicker.cjs +22 -5
  34. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  35. package/dist/components/SDatePicker/SDatePicker.d.ts +3 -1
  36. package/dist/components/SDatePicker/SDatePicker.js +22 -5
  37. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  38. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +22 -5
  39. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  40. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +3 -1
  41. package/dist/components/SDateRangePicker/SDateRangePicker.js +22 -5
  42. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  43. package/dist/components/SDropdownButton/SDropdownButton.cjs +3 -1
  44. package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
  45. package/dist/components/SDropdownButton/SDropdownButton.js +3 -1
  46. package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
  47. package/dist/components/SField/SField.cjs +20 -5
  48. package/dist/components/SField/SField.cjs.map +1 -1
  49. package/dist/components/SField/SField.d.ts +4 -1
  50. package/dist/components/SField/SField.js +20 -5
  51. package/dist/components/SField/SField.js.map +1 -1
  52. package/dist/components/SFilePicker/SFilePicker.cjs +23 -6
  53. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  54. package/dist/components/SFilePicker/SFilePicker.d.ts +3 -1
  55. package/dist/components/SFilePicker/SFilePicker.js +23 -6
  56. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  57. package/dist/components/SGhostButton/SGhostButton.cjs +9 -3
  58. package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
  59. package/dist/components/SGhostButton/SGhostButton.d.ts +5 -1
  60. package/dist/components/SGhostButton/SGhostButton.js +9 -3
  61. package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
  62. package/dist/components/SGnb/SGnb.cjs +3297 -0
  63. package/dist/components/SGnb/SGnb.cjs.map +1 -0
  64. package/dist/components/SGnb/SGnb.d.ts +35 -0
  65. package/dist/components/SGnb/SGnb.js +3295 -0
  66. package/dist/components/SGnb/SGnb.js.map +1 -0
  67. package/dist/components/SGnb/gnb.config.d.ts +63 -0
  68. package/dist/components/SGnb/index.d.ts +2 -0
  69. package/dist/components/SGuide/SGuide.cjs +9 -3
  70. package/dist/components/SGuide/SGuide.cjs.map +1 -1
  71. package/dist/components/SGuide/SGuide.js +9 -3
  72. package/dist/components/SGuide/SGuide.js.map +1 -1
  73. package/dist/components/SInput/SInput.cjs +22 -5
  74. package/dist/components/SInput/SInput.cjs.map +1 -1
  75. package/dist/components/SInput/SInput.d.ts +4 -1
  76. package/dist/components/SInput/SInput.js +22 -5
  77. package/dist/components/SInput/SInput.js.map +1 -1
  78. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +511 -209
  79. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  80. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +27 -6
  81. package/dist/components/SKeyValueTable/SKeyValueTable.js +510 -208
  82. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  83. package/dist/components/SLayout/SLayout.cjs +79 -0
  84. package/dist/components/SLayout/SLayout.cjs.map +1 -0
  85. package/dist/components/SLayout/SLayout.d.ts +41 -0
  86. package/dist/components/SLayout/SLayout.js +75 -0
  87. package/dist/components/SLayout/SLayout.js.map +1 -0
  88. package/dist/components/SLayout/index.d.ts +1 -0
  89. package/dist/components/SLoadingContainer/SLoadingContainer.cjs +6 -2
  90. package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
  91. package/dist/components/SLoadingContainer/SLoadingContainer.js +6 -2
  92. package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
  93. package/dist/components/SLoadingContainer/loading.cjs +6 -2
  94. package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
  95. package/dist/components/SLoadingContainer/loading.js +6 -2
  96. package/dist/components/SLoadingContainer/loading.js.map +1 -1
  97. package/dist/components/SLoadingModal/SLoadingModal.cjs +16 -6
  98. package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
  99. package/dist/components/SLoadingModal/SLoadingModal.js +16 -6
  100. package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
  101. package/dist/components/SModalContainer/SModalContainer.cjs +10 -4
  102. package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
  103. package/dist/components/SModalContainer/SModalContainer.d.ts +10 -0
  104. package/dist/components/SModalContainer/SModalContainer.js +10 -4
  105. package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
  106. package/dist/components/SNumberInput/SNumberInput.cjs +22 -5
  107. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  108. package/dist/components/SNumberInput/SNumberInput.d.ts +2 -0
  109. package/dist/components/SNumberInput/SNumberInput.js +22 -5
  110. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  111. package/dist/components/SPage/SPage.cjs +86 -0
  112. package/dist/components/SPage/SPage.cjs.map +1 -0
  113. package/dist/components/SPage/SPage.d.ts +13 -0
  114. package/dist/components/SPage/SPage.js +84 -0
  115. package/dist/components/SPage/SPage.js.map +1 -0
  116. package/dist/components/SPage/index.d.ts +1 -0
  117. package/dist/components/SPopover/SPopover.cjs +9 -3
  118. package/dist/components/SPopover/SPopover.cjs.map +1 -1
  119. package/dist/components/SPopover/SPopover.js +9 -3
  120. package/dist/components/SPopover/SPopover.js.map +1 -1
  121. package/dist/components/SPortal/SPortal.cjs +3 -1
  122. package/dist/components/SPortal/SPortal.cjs.map +1 -1
  123. package/dist/components/SPortal/SPortal.js +3 -1
  124. package/dist/components/SPortal/SPortal.js.map +1 -1
  125. package/dist/components/SSelect/SSelect.cjs +545 -265
  126. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  127. package/dist/components/SSelect/SSelect.d.ts +4 -2
  128. package/dist/components/SSelect/SSelect.js +546 -265
  129. package/dist/components/SSelect/SSelect.js.map +1 -1
  130. package/dist/components/STable/STable.cjs +552 -268
  131. package/dist/components/STable/STable.cjs.map +1 -1
  132. package/dist/components/STable/STable.js +553 -268
  133. package/dist/components/STable/STable.js.map +1 -1
  134. package/dist/components/STextLink/STextLink.cjs +2 -0
  135. package/dist/components/STextLink/STextLink.cjs.map +1 -1
  136. package/dist/components/STextLink/STextLink.js +2 -0
  137. package/dist/components/STextLink/STextLink.js.map +1 -1
  138. package/dist/components/STextarea/STextarea.cjs +22 -5
  139. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  140. package/dist/components/STextarea/STextarea.d.ts +2 -0
  141. package/dist/components/STextarea/STextarea.js +22 -5
  142. package/dist/components/STextarea/STextarea.js.map +1 -1
  143. package/dist/components/SToast/SToast.cjs +9 -3
  144. package/dist/components/SToast/SToast.cjs.map +1 -1
  145. package/dist/components/SToast/SToast.js +9 -3
  146. package/dist/components/SToast/SToast.js.map +1 -1
  147. package/dist/components/SToast/SToastContainer.cjs +9 -3
  148. package/dist/components/SToast/SToastContainer.cjs.map +1 -1
  149. package/dist/components/SToast/SToastContainer.js +9 -3
  150. package/dist/components/SToast/SToastContainer.js.map +1 -1
  151. package/dist/components/STooltip/STooltip.cjs +9 -3
  152. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  153. package/dist/components/STooltip/STooltip.js +9 -3
  154. package/dist/components/STooltip/STooltip.js.map +1 -1
  155. package/dist/index.cjs +840 -199
  156. package/dist/index.cjs.map +1 -1
  157. package/dist/index.d.ts +3 -1
  158. package/dist/index.js +832 -200
  159. package/dist/index.js.map +1 -1
  160. package/dist/lib/portal-container.d.ts +7 -0
  161. package/dist/styles.css +253 -63
  162. package/dist/theme.css +66 -13
  163. 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({
@@ -2978,8 +2976,12 @@ var SCircleProgress = react.forwardRef(
2978
2976
  "span",
2979
2977
  {
2980
2978
  className: cn(
2981
- "text-center text-[16px] font-bold leading-[26px]",
2982
- innerValue && "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
2979
+ "text-center text-[16px] font-bold",
2980
+ innerValue ? (
2981
+ // leading-[normal]: 26px leading 은 폰트 content(19px) 위아래로 홀수(7px)만큼 남아
2982
+ // 3px/4px 로 쪼개지고 글자가 링 중심보다 위로 뜬다. (leading-normal 은 1.5 라 동일 문제)
2983
+ "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 leading-[normal]"
2984
+ ) : "leading-[26px]"
2983
2985
  ),
2984
2986
  style: { color: percentColor },
2985
2987
  children: [
@@ -3135,10 +3137,110 @@ function SPagination({
3135
3137
  );
3136
3138
  }
3137
3139
  var PortalContainerContext = react.createContext(null);
3140
+ var InsideModalContext = react.createContext(false);
3138
3141
  PortalContainerContext.Provider;
3142
+ InsideModalContext.Provider;
3139
3143
  function usePortalContainer() {
3140
3144
  return react.useContext(PortalContainerContext) ?? void 0;
3141
3145
  }
3146
+ function useInsideModal() {
3147
+ return react.useContext(InsideModalContext);
3148
+ }
3149
+ function runRules(value, rules) {
3150
+ if (!rules || rules.length === 0) return "";
3151
+ for (const rule of rules) {
3152
+ const result = rule(value ?? "");
3153
+ if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
3154
+ }
3155
+ return "";
3156
+ }
3157
+ var FormContext = react.createContext(null);
3158
+ function useFormContext() {
3159
+ return react.useContext(FormContext);
3160
+ }
3161
+ function useFormField(field) {
3162
+ const ctx = useFormContext();
3163
+ const cbRef = react.useRef(field);
3164
+ cbRef.current = field;
3165
+ const name = field.name ?? "";
3166
+ react.useEffect(() => {
3167
+ if (!ctx) return;
3168
+ return ctx.register({
3169
+ name,
3170
+ validate: () => cbRef.current.validate(),
3171
+ resetValidation: () => cbRef.current.resetValidation()
3172
+ });
3173
+ }, [ctx, name]);
3174
+ }
3175
+
3176
+ // src/lib/sanitize-inline-html.ts
3177
+ var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
3178
+ var ALLOWED_ATTRS_BY_TAG = {
3179
+ A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
3180
+ };
3181
+ var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
3182
+ var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
3183
+ var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
3184
+ "SCRIPT",
3185
+ "STYLE",
3186
+ "IFRAME",
3187
+ "OBJECT",
3188
+ "EMBED",
3189
+ "META",
3190
+ "LINK",
3191
+ "BASE",
3192
+ "NOSCRIPT",
3193
+ // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
3194
+ // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
3195
+ "SVG",
3196
+ "MATH",
3197
+ "TEMPLATE"
3198
+ ]);
3199
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3200
+ var sanitizeNode = (node) => {
3201
+ if (node.nodeType === Node.COMMENT_NODE) {
3202
+ node.remove();
3203
+ return;
3204
+ }
3205
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
3206
+ const element = node;
3207
+ const tagName = element.tagName;
3208
+ if (DROP_CONTENT_TAGS.has(tagName)) {
3209
+ element.remove();
3210
+ return;
3211
+ }
3212
+ Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
3213
+ if (ALLOWED_INLINE_TAGS.has(tagName)) {
3214
+ if (element.hasAttribute("className")) {
3215
+ const val = element.getAttribute("className");
3216
+ element.removeAttribute("className");
3217
+ if (!element.hasAttribute("class")) element.setAttribute("class", val);
3218
+ }
3219
+ const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
3220
+ Array.from(element.attributes).forEach((attr) => {
3221
+ if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
3222
+ });
3223
+ if (tagName === "A") {
3224
+ const href = element.getAttribute("href");
3225
+ if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
3226
+ if (element.hasAttribute("target")) {
3227
+ element.setAttribute("rel", "noopener noreferrer");
3228
+ }
3229
+ }
3230
+ return;
3231
+ }
3232
+ const parent = element.parentNode;
3233
+ if (parent == null) return;
3234
+ while (element.firstChild) parent.insertBefore(element.firstChild, element);
3235
+ parent.removeChild(element);
3236
+ };
3237
+ var sanitizeInlineHtml = (value) => {
3238
+ if (typeof document === "undefined") return escapeHtml(value);
3239
+ const template = document.createElement("template");
3240
+ template.innerHTML = value;
3241
+ Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
3242
+ return template.innerHTML;
3243
+ };
3142
3244
 
3143
3245
  // src/lib/floating-anim.ts
3144
3246
  var FLOATING_SLIDE_ANIM = [
@@ -3256,6 +3358,8 @@ function SGhostButton({
3256
3358
  tooltipText,
3257
3359
  disabled = false,
3258
3360
  onClick,
3361
+ anchorClassName,
3362
+ anchorStyle,
3259
3363
  className,
3260
3364
  style
3261
3365
  }) {
@@ -3278,6 +3382,8 @@ function SGhostButton({
3278
3382
  };
3279
3383
  const s = SIZE[size];
3280
3384
  const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
3385
+ const text = (tooltipText ?? "").trim();
3386
+ const hasTooltip = text !== "";
3281
3387
  const btn = /* @__PURE__ */ jsxRuntime.jsxs(
3282
3388
  "button",
3283
3389
  {
@@ -3318,13 +3424,13 @@ function SGhostButton({
3318
3424
  ]
3319
3425
  }
3320
3426
  );
3321
- const text = (tooltipText ?? "").trim();
3322
- if (text === "") return btn;
3427
+ if (!hasTooltip) return btn;
3323
3428
  return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
3324
3429
  /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
3325
3430
  "span",
3326
3431
  {
3327
- className: "inline-flex",
3432
+ className: cn("inline-flex", anchorClassName),
3433
+ style: anchorStyle,
3328
3434
  onMouseEnter: openNow,
3329
3435
  onMouseLeave: startHide,
3330
3436
  onFocus: openNow,
@@ -3476,32 +3582,6 @@ var STooltip = react.forwardRef(function STooltip2({
3476
3582
  ] });
3477
3583
  });
3478
3584
  STooltip.displayName = "STooltip";
3479
- function runRules(value, rules) {
3480
- if (!rules || rules.length === 0) return "";
3481
- for (const rule of rules) {
3482
- const result = rule(value ?? "");
3483
- if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
3484
- }
3485
- return "";
3486
- }
3487
- var FormContext = react.createContext(null);
3488
- function useFormContext() {
3489
- return react.useContext(FormContext);
3490
- }
3491
- function useFormField(field) {
3492
- const ctx = useFormContext();
3493
- const cbRef = react.useRef(field);
3494
- cbRef.current = field;
3495
- const name = field.name ?? "";
3496
- react.useEffect(() => {
3497
- if (!ctx) return;
3498
- return ctx.register({
3499
- name,
3500
- validate: () => cbRef.current.validate(),
3501
- resetValidation: () => cbRef.current.resetValidation()
3502
- });
3503
- }, [ctx, name]);
3504
- }
3505
3585
  var FIELD_SIZE = {
3506
3586
  sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 16 },
3507
3587
  md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 16 }
@@ -3516,6 +3596,7 @@ var SField = react.forwardRef(function SField2({
3516
3596
  name,
3517
3597
  label = "",
3518
3598
  icon,
3599
+ iconColor,
3519
3600
  labelTooltip = "",
3520
3601
  labelTooltipProps,
3521
3602
  labelWidth,
@@ -3593,7 +3674,15 @@ var SField = react.forwardRef(function SField2({
3593
3674
  lineHeight: `${s.lineHeight}px`
3594
3675
  },
3595
3676
  children: [
3596
- icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3677
+ icon && /* @__PURE__ */ jsxRuntime.jsx(
3678
+ SIcon,
3679
+ {
3680
+ name: icon,
3681
+ size: s.icon,
3682
+ color: iconColor,
3683
+ style: { marginRight: s.labelGap }
3684
+ }
3685
+ ),
3597
3686
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3598
3687
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
3599
3688
  STooltip,
@@ -3643,7 +3732,7 @@ var SField = react.forwardRef(function SField2({
3643
3732
  borderBottomLeftRadius: s.radius - 1
3644
3733
  },
3645
3734
  children: [
3646
- icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon }),
3735
+ icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
3647
3736
  effectiveAddonLabel
3648
3737
  ]
3649
3738
  }
@@ -3658,6 +3747,77 @@ var SField = react.forwardRef(function SField2({
3658
3747
  }
3659
3748
  );
3660
3749
  });
3750
+ function SPortal({
3751
+ open,
3752
+ onClose,
3753
+ onOpenChange,
3754
+ anchorRef,
3755
+ placement = "bottom",
3756
+ align = "start",
3757
+ offset = 4,
3758
+ alignOffset = 0,
3759
+ fit = false,
3760
+ maxHeight,
3761
+ autoClose = false,
3762
+ persistent = false,
3763
+ viewportPadding = 0,
3764
+ onOpenAutoFocus,
3765
+ onCloseAutoFocus,
3766
+ children,
3767
+ className,
3768
+ style,
3769
+ ariaLabel
3770
+ }) {
3771
+ const portalContainer = usePortalContainer();
3772
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3773
+ RPopover2__namespace.Root,
3774
+ {
3775
+ open,
3776
+ onOpenChange: (next) => {
3777
+ onOpenChange?.(next);
3778
+ if (!next) onClose?.();
3779
+ },
3780
+ children: [
3781
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { virtualRef: anchorRef }),
3782
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
3783
+ RPopover2__namespace.Content,
3784
+ {
3785
+ side: placement,
3786
+ align,
3787
+ sideOffset: offset,
3788
+ alignOffset,
3789
+ collisionPadding: viewportPadding,
3790
+ "aria-label": ariaLabel,
3791
+ onPointerDownOutside: (e) => {
3792
+ if (persistent || anchorRef.current?.contains(e.target)) {
3793
+ e.preventDefault();
3794
+ }
3795
+ },
3796
+ onFocusOutside: (e) => {
3797
+ if (persistent || anchorRef.current?.contains(e.target)) {
3798
+ e.preventDefault();
3799
+ }
3800
+ },
3801
+ onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
3802
+ onOpenAutoFocus,
3803
+ onCloseAutoFocus,
3804
+ onClick: autoClose ? () => onClose?.() : void 0,
3805
+ className: cn(
3806
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm",
3807
+ maxHeight != null && "overflow-y-auto",
3808
+ fit && "min-w-(--radix-popover-trigger-width)",
3809
+ FLOATING_SLIDE_ANIM,
3810
+ className
3811
+ ),
3812
+ style: { maxHeight, ...style },
3813
+ children
3814
+ }
3815
+ ) })
3816
+ ]
3817
+ }
3818
+ );
3819
+ }
3820
+ SPortal.displayName = "SPortal";
3661
3821
 
3662
3822
  // src/components/SSelect/select.config.ts
3663
3823
  function flattenOptions(options, depth = 1) {
@@ -3692,75 +3852,6 @@ function flattenOptions(options, depth = 1) {
3692
3852
  var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
3693
3853
  var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
3694
3854
  var extractText = (html) => html.replace(/<[^>]*>/g, "");
3695
-
3696
- // src/lib/sanitize-inline-html.ts
3697
- var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
3698
- var ALLOWED_ATTRS_BY_TAG = {
3699
- A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
3700
- };
3701
- var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
3702
- var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
3703
- var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
3704
- "SCRIPT",
3705
- "STYLE",
3706
- "IFRAME",
3707
- "OBJECT",
3708
- "EMBED",
3709
- "META",
3710
- "LINK",
3711
- "BASE",
3712
- "NOSCRIPT",
3713
- // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
3714
- // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
3715
- "SVG",
3716
- "MATH",
3717
- "TEMPLATE"
3718
- ]);
3719
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3720
- var sanitizeNode = (node) => {
3721
- if (node.nodeType === Node.COMMENT_NODE) {
3722
- node.remove();
3723
- return;
3724
- }
3725
- if (node.nodeType !== Node.ELEMENT_NODE) return;
3726
- const element = node;
3727
- const tagName = element.tagName;
3728
- if (DROP_CONTENT_TAGS.has(tagName)) {
3729
- element.remove();
3730
- return;
3731
- }
3732
- Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
3733
- if (ALLOWED_INLINE_TAGS.has(tagName)) {
3734
- if (element.hasAttribute("className")) {
3735
- const val = element.getAttribute("className");
3736
- element.removeAttribute("className");
3737
- if (!element.hasAttribute("class")) element.setAttribute("class", val);
3738
- }
3739
- const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
3740
- Array.from(element.attributes).forEach((attr) => {
3741
- if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
3742
- });
3743
- if (tagName === "A") {
3744
- const href = element.getAttribute("href");
3745
- if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
3746
- if (element.hasAttribute("target")) {
3747
- element.setAttribute("rel", "noopener noreferrer");
3748
- }
3749
- }
3750
- return;
3751
- }
3752
- const parent = element.parentNode;
3753
- if (parent == null) return;
3754
- while (element.firstChild) parent.insertBefore(element.firstChild, element);
3755
- parent.removeChild(element);
3756
- };
3757
- var sanitizeInlineHtml = (value) => {
3758
- if (typeof document === "undefined") return escapeHtml(value);
3759
- const template = document.createElement("template");
3760
- template.innerHTML = value;
3761
- Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
3762
- return template.innerHTML;
3763
- };
3764
3855
  var SEARCH_THRESHOLD = 11;
3765
3856
  var SSelect = react.forwardRef(function SSelect2({
3766
3857
  value,
@@ -3784,6 +3875,7 @@ var SSelect = react.forwardRef(function SSelect2({
3784
3875
  label,
3785
3876
  labelWidth,
3786
3877
  icon,
3878
+ iconColor,
3787
3879
  labelTooltip,
3788
3880
  addonLabel,
3789
3881
  addonAlign,
@@ -3795,18 +3887,15 @@ var SSelect = react.forwardRef(function SSelect2({
3795
3887
  }, ref) {
3796
3888
  const multi = type === "multi" || type === "multi_depth";
3797
3889
  const portalContainer = usePortalContainer();
3890
+ const insideModal = useInsideModal();
3798
3891
  const [search, setSearch] = react.useState("");
3799
3892
  const [ruleError, setRuleError] = react.useState("");
3800
3893
  const [open, setOpen] = react.useState(false);
3894
+ const [pendingOpen, setPendingOpen] = react.useState(false);
3895
+ const [focusedIndex, setFocusedIndex] = react.useState(-1);
3801
3896
  const triggerRef = react.useRef(null);
3802
- react.useImperativeHandle(
3803
- ref,
3804
- () => ({
3805
- focus: () => triggerRef.current?.focus(),
3806
- open: () => setOpen(true)
3807
- }),
3808
- []
3809
- );
3897
+ const searchInputRef = react.useRef(null);
3898
+ const listRef = react.useRef(null);
3810
3899
  const flat = react.useMemo(() => flattenOptions(options), [options]);
3811
3900
  const selectable = react.useMemo(() => selectableOptions(flat), [flat]);
3812
3901
  const hasLeaf = react.useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -3824,6 +3913,46 @@ var SSelect = react.forwardRef(function SSelect2({
3824
3913
  const arr = Array.isArray(value) ? value : [value];
3825
3914
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
3826
3915
  }, [value]);
3916
+ const selectedRef = react.useRef(selected);
3917
+ selectedRef.current = selected;
3918
+ const emit = (next) => {
3919
+ if (!onValueChange) return;
3920
+ const mapped = next.map(
3921
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3922
+ );
3923
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3924
+ };
3925
+ const commitOpen = (next) => {
3926
+ setOpen(next);
3927
+ onOpenChange?.(next);
3928
+ if (!next) {
3929
+ setPendingOpen(false);
3930
+ setSearch("");
3931
+ if (rules && rules.length > 0) {
3932
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
3933
+ }
3934
+ }
3935
+ };
3936
+ const requestOpen = (next) => {
3937
+ if (next && insideModal && portalContainer == null) {
3938
+ setPendingOpen(true);
3939
+ return;
3940
+ }
3941
+ commitOpen(next);
3942
+ };
3943
+ react.useEffect(() => {
3944
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
3945
+ setPendingOpen(false);
3946
+ commitOpen(true);
3947
+ }, [insideModal, pendingOpen, portalContainer]);
3948
+ react.useImperativeHandle(
3949
+ ref,
3950
+ () => ({
3951
+ focus: () => triggerRef.current?.focus(),
3952
+ open: () => requestOpen(true)
3953
+ }),
3954
+ [insideModal, portalContainer]
3955
+ );
3827
3956
  useFormField({
3828
3957
  name,
3829
3958
  validate: () => {
@@ -3833,13 +3962,6 @@ var SSelect = react.forwardRef(function SSelect2({
3833
3962
  },
3834
3963
  resetValidation: () => setRuleError("")
3835
3964
  });
3836
- const emit = (next) => {
3837
- if (!onValueChange) return;
3838
- const mapped = next.map(
3839
- (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3840
- );
3841
- onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3842
- };
3843
3965
  const filtered = react.useMemo(() => {
3844
3966
  if (!showSearch || search.trim() === "") return flat;
3845
3967
  const q = search.trim().toLowerCase();
@@ -3850,6 +3972,16 @@ var SSelect = react.forwardRef(function SSelect2({
3850
3972
  const allValues = selectable.map((o) => o.value);
3851
3973
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
3852
3974
  const hasItems = filtered.some((o) => !o.isGroup);
3975
+ const navigableItems = react.useMemo(() => {
3976
+ const items = [];
3977
+ if (multi && useSelectAll && allValues.length > 0) {
3978
+ items.push({ value: "__select_all__", isSelectAll: true });
3979
+ }
3980
+ filtered.forEach((o) => {
3981
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
3982
+ });
3983
+ return items;
3984
+ }, [allValues.length, filtered, multi, useSelectAll]);
3853
3985
  const triggerText = (() => {
3854
3986
  if (selected.length === 0) return placeholder;
3855
3987
  if (multi) {
@@ -3859,26 +3991,119 @@ var SSelect = react.forwardRef(function SSelect2({
3859
3991
  return labelByValue.get(selected[0]) ?? selected[0];
3860
3992
  })();
3861
3993
  const isPlaceholder = selected.length === 0;
3862
- const selectStore = /* @__PURE__ */ jsxRuntime.jsxs(
3863
- Ariakit__namespace.SelectProvider,
3994
+ const handleSelect = (nextValue) => {
3995
+ if (multi) {
3996
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
3997
+ emit(next);
3998
+ return;
3999
+ }
4000
+ emit([nextValue]);
4001
+ commitOpen(false);
4002
+ };
4003
+ const handleSelectAll = () => {
4004
+ emit(allSelected ? [] : allValues);
4005
+ };
4006
+ react.useEffect(() => {
4007
+ if (!open) {
4008
+ setFocusedIndex(-1);
4009
+ return;
4010
+ }
4011
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4012
+ }, [open, navigableItems.length]);
4013
+ react.useEffect(() => {
4014
+ if (!open) return;
4015
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4016
+ }, [search, open, navigableItems.length]);
4017
+ react.useEffect(() => {
4018
+ if (!open || focusedIndex < 0) return;
4019
+ const list = listRef.current;
4020
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
4021
+ if (!list || !focusedEl) return;
4022
+ const listRect = list.getBoundingClientRect();
4023
+ const itemRect = focusedEl.getBoundingClientRect();
4024
+ if (itemRect.top < listRect.top) {
4025
+ list.scrollTop += itemRect.top - listRect.top;
4026
+ } else if (itemRect.bottom > listRect.bottom) {
4027
+ list.scrollTop += itemRect.bottom - listRect.bottom;
4028
+ }
4029
+ }, [focusedIndex, open]);
4030
+ const moveFocusedIndex = (direction) => {
4031
+ if (!open || navigableItems.length === 0) return;
4032
+ setFocusedIndex((current) => {
4033
+ if (direction === "next") {
4034
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
4035
+ }
4036
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
4037
+ });
4038
+ };
4039
+ const selectFocusedItem = () => {
4040
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
4041
+ const focused = navigableItems[focusedIndex];
4042
+ if (focused.isSelectAll) {
4043
+ handleSelectAll();
4044
+ return;
4045
+ }
4046
+ handleSelect(focused.value);
4047
+ };
4048
+ const handleKeyDown = (event) => {
4049
+ if (!open) {
4050
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
4051
+ event.preventDefault();
4052
+ requestOpen(true);
4053
+ }
4054
+ return;
4055
+ }
4056
+ if (event.key === "ArrowDown") {
4057
+ event.preventDefault();
4058
+ moveFocusedIndex("next");
4059
+ return;
4060
+ }
4061
+ if (event.key === "ArrowUp") {
4062
+ event.preventDefault();
4063
+ moveFocusedIndex("prev");
4064
+ return;
4065
+ }
4066
+ if (event.key === "Enter") {
4067
+ event.preventDefault();
4068
+ selectFocusedItem();
4069
+ return;
4070
+ }
4071
+ if (event.key === "Escape") {
4072
+ event.preventDefault();
4073
+ commitOpen(false);
4074
+ }
4075
+ };
4076
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4077
+ SField,
3864
4078
  {
3865
- animated: portalContainer == null ? 150 : false,
3866
- value: multi ? selected : selected[0] ?? "",
3867
- setValue: (v) => emit(Array.isArray(v) ? v : v === "" ? [] : [v]),
3868
- open,
3869
- setOpen: (o) => {
3870
- setOpen(o);
3871
- onOpenChange?.(o);
3872
- if (!o && rules && rules.length > 0) {
3873
- setRuleError(runRules(multi ? selected : selected[0] ?? "", rules));
3874
- }
3875
- },
4079
+ label,
4080
+ labelWidth,
4081
+ icon,
4082
+ iconColor,
4083
+ labelTooltip,
4084
+ labelTooltipProps,
4085
+ addonLabel,
4086
+ addonAlign,
4087
+ hint,
4088
+ error: error || ruleError !== "",
4089
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4090
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
4091
+ disabled: effectiveDisabled,
4092
+ width,
4093
+ className,
4094
+ style,
3876
4095
  children: [
4096
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
3877
4097
  /* @__PURE__ */ jsxRuntime.jsxs(
3878
- Ariakit__namespace.Select,
4098
+ "button",
3879
4099
  {
3880
4100
  ref: triggerRef,
4101
+ type: "button",
3881
4102
  disabled: effectiveDisabled,
4103
+ "aria-haspopup": "listbox",
4104
+ "aria-expanded": open,
4105
+ onClick: () => requestOpen(!open),
4106
+ onKeyDown: handleKeyDown,
3882
4107
  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",
3883
4108
  style: {
3884
4109
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -3903,178 +4128,237 @@ var SSelect = react.forwardRef(function SSelect2({
3903
4128
  ]
3904
4129
  }
3905
4130
  ),
3906
- /* @__PURE__ */ jsxRuntime.jsxs(
3907
- Ariakit__namespace.SelectPopover,
4131
+ /* @__PURE__ */ jsxRuntime.jsx(
4132
+ SPortal,
3908
4133
  {
3909
- portal: true,
3910
- portalElement: portalContainer,
3911
- gutter: 4,
3912
- sameWidth: !dropdownWidth,
3913
- 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",
4134
+ open,
4135
+ onClose: () => commitOpen(false),
4136
+ onOpenChange: (next) => {
4137
+ if (next) requestOpen(true);
4138
+ },
4139
+ anchorRef: triggerRef,
4140
+ placement: "bottom",
4141
+ align: "start",
4142
+ offset: 4,
4143
+ fit: !dropdownWidth,
4144
+ onOpenAutoFocus: (event) => {
4145
+ if (!showSearch) {
4146
+ event.preventDefault();
4147
+ return;
4148
+ }
4149
+ event.preventDefault();
4150
+ requestAnimationFrame(() => searchInputRef.current?.focus());
4151
+ },
4152
+ onCloseAutoFocus: (event) => {
4153
+ event.preventDefault();
4154
+ triggerRef.current?.focus();
4155
+ },
4156
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
3914
4157
  style: {
3915
- maxHeight: dropdownHeight,
3916
4158
  ...dropdownWidth ? { width: dropdownWidth } : {},
3917
4159
  maxWidth: maxDropdownWidth
3918
4160
  },
3919
- children: [
3920
- showSearch && // 원본 sd-select-list-item-search: sticky + 검색 아이콘 + 테두리 input + focus-within + clear
3921
- /* @__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: [
3922
- /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
3923
- /* @__PURE__ */ jsxRuntime.jsx(
3924
- Ariakit__namespace.Combobox,
3925
- {
3926
- autoSelect: true,
3927
- placeholder: "\uAC80\uC0C9",
3928
- 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)]"
3929
- }
3930
- ),
3931
- search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
3932
- SGhostButton,
3933
- {
3934
- icon: "close",
3935
- size: "xxs",
3936
- intent: "default",
3937
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
3938
- onClick: () => setSearch(""),
3939
- className: "shrink-0"
3940
- }
3941
- )
3942
- ] }) }),
3943
- multi && useSelectAll && /* @__PURE__ */ jsxRuntime.jsx(
3944
- "button",
3945
- {
3946
- type: "button",
3947
- 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)]",
3948
- onClick: () => emit(allSelected ? [] : allValues),
3949
- children: allSelectedLabel
3950
- }
3951
- ),
3952
- hasItems ? /* @__PURE__ */ jsxRuntime.jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
3953
- // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
3954
- /* @__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." })
3955
- )
3956
- ]
4161
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4162
+ "div",
4163
+ {
4164
+ ref: listRef,
4165
+ 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)]",
4166
+ style: { maxHeight: dropdownHeight },
4167
+ onMouseLeave: () => setFocusedIndex(-1),
4168
+ children: [
4169
+ 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: [
4170
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4171
+ /* @__PURE__ */ jsxRuntime.jsx(
4172
+ "input",
4173
+ {
4174
+ ref: searchInputRef,
4175
+ value: search,
4176
+ onChange: (e) => setSearch(e.target.value),
4177
+ onKeyDown: handleKeyDown,
4178
+ placeholder: "\uAC80\uC0C9",
4179
+ 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)]"
4180
+ }
4181
+ ),
4182
+ search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
4183
+ SGhostButton,
4184
+ {
4185
+ icon: "close",
4186
+ size: "xxs",
4187
+ intent: "default",
4188
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4189
+ onClick: () => setSearch(""),
4190
+ className: "shrink-0"
4191
+ }
4192
+ )
4193
+ ] }) }),
4194
+ hasItems ? /* @__PURE__ */ jsxRuntime.jsx(
4195
+ ItemList,
4196
+ {
4197
+ items: filtered,
4198
+ multi,
4199
+ selected,
4200
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
4201
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
4202
+ selectAllLabel: allSelectedLabel,
4203
+ selectAllSelected: allSelected,
4204
+ onSelectAll: handleSelectAll,
4205
+ onSelect: handleSelect,
4206
+ onHoverValue: (value2) => {
4207
+ if (value2 == null) {
4208
+ setFocusedIndex(-1);
4209
+ return;
4210
+ }
4211
+ const index = navigableItems.findIndex(
4212
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
4213
+ );
4214
+ if (index >= 0) setFocusedIndex(index);
4215
+ }
4216
+ }
4217
+ ) : /* @__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." })
4218
+ ]
4219
+ }
4220
+ )
3957
4221
  }
3958
4222
  )
3959
4223
  ]
3960
4224
  }
3961
4225
  );
3962
- const control = showSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
3963
- return /* @__PURE__ */ jsxRuntime.jsxs(
3964
- SField,
3965
- {
3966
- label,
3967
- labelWidth,
3968
- icon,
3969
- labelTooltip,
3970
- labelTooltipProps,
3971
- addonLabel,
3972
- addonAlign,
3973
- hint,
3974
- error: error || ruleError !== "",
3975
- status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
3976
- errorMessage: ruleError !== "" ? ruleError : errorMessage,
3977
- disabled: effectiveDisabled,
3978
- width,
3979
- className,
3980
- style,
3981
- children: [
3982
- name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
3983
- control
3984
- ]
3985
- }
3986
- );
3987
4226
  });
3988
4227
  SSelect.displayName = "SSelect";
3989
4228
  function ItemList({
3990
4229
  items,
3991
4230
  multi,
3992
4231
  selected,
3993
- useSearch
4232
+ focusedValue,
4233
+ showSelectAll,
4234
+ selectAllLabel,
4235
+ selectAllSelected,
4236
+ onSelectAll,
4237
+ onSelect,
4238
+ onHoverValue
3994
4239
  }) {
3995
4240
  const itemBase = cn(
3996
- // group: 체크박스 inverse(group-data-[active-item])를 위해 필요
3997
- "group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
4241
+ "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
3998
4242
  "text-[color:var(--cmp-listItem-content-default)]",
3999
- // Ariakit은 disabled 항목에 aria-disabled를 붙이므로 aria-disabled 변형으로 스타일 (data-disabled 아님)
4000
- "aria-disabled:cursor-not-allowed aria-disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4243
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4001
4244
  );
4002
- const activeClass = "data-[active-item]:bg-[var(--cmp-listItem-bg-hover)] data-[active-item]:text-[color:var(--cmp-listItem-content-hover)]";
4003
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: items.map((o) => {
4004
- const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4005
- if (o.isGroup) {
4006
- const isDepth1 = o.depth === 1;
4007
- return /* @__PURE__ */ jsxRuntime.jsx(
4008
- "div",
4009
- {
4010
- className: cn(
4011
- "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4012
- 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",
4013
- // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4014
- // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4015
- o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4016
- o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4017
- ),
4018
- style: { paddingLeft: padLeft },
4019
- children: o.label
4020
- },
4021
- o.value
4022
- );
4023
- }
4024
- const isSelected = selected.includes(o.value);
4025
- return /* @__PURE__ */ jsxRuntime.jsxs(
4026
- Ariakit__namespace.SelectItem,
4245
+ 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)]";
4246
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
4247
+ showSelectAll && /* @__PURE__ */ jsxRuntime.jsxs(
4248
+ "button",
4027
4249
  {
4028
- value: o.value,
4029
- disabled: o.disabled,
4030
- accessibleWhenDisabled: true,
4250
+ type: "button",
4251
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
4252
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
4031
4253
  className: cn(
4032
4254
  itemBase,
4033
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4034
- // Ariakit은 disabled 항목도 hover 시 active(data-active-item) 처리하므로,
4035
- // 비활성 항목에는 hover 하이라이트를 적용하지 않아 회색 비활성 스타일을 유지한다.
4036
- // (원본 Stencil의 `--focused...:not(--disabled)` 규칙과 동일한 의도)
4037
- !o.disabled && activeClass
4255
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
4038
4256
  ),
4039
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4040
- render: useSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxItem, {}) : void 0,
4257
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
4258
+ onMouseEnter: () => onHoverValue("__select_all__"),
4259
+ onMouseLeave: () => onHoverValue(null),
4260
+ onClick: onSelectAll,
4041
4261
  children: [
4042
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
4043
4262
  /* @__PURE__ */ jsxRuntime.jsx(
4044
4263
  "span",
4045
4264
  {
4046
4265
  className: cn(
4047
4266
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4048
- o.disabled ? (
4049
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4050
- "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4051
- ) : cn(
4052
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4053
- 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"
4054
- )
4267
+ 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)]"
4055
4268
  ),
4056
- children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4269
+ children: selectAllSelected && /* @__PURE__ */ jsxRuntime.jsx(
4057
4270
  SIcon,
4058
4271
  {
4059
4272
  name: "checkboxCheck",
4060
4273
  size: 12,
4061
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4274
+ color: "var(--cmp-checkbox-checked-icon-default)"
4062
4275
  }
4063
4276
  )
4064
4277
  }
4065
4278
  ),
4066
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4067
- "span",
4068
- {
4069
- className: "flex-1 truncate",
4070
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4071
- }
4072
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4279
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
4073
4280
  ]
4074
- },
4075
- o.value
4076
- );
4077
- }) });
4281
+ }
4282
+ ),
4283
+ items.map((o) => {
4284
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4285
+ if (o.isGroup) {
4286
+ const isDepth1 = o.depth === 1;
4287
+ return /* @__PURE__ */ jsxRuntime.jsx(
4288
+ "div",
4289
+ {
4290
+ className: cn(
4291
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4292
+ 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",
4293
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4294
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4295
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4296
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4297
+ ),
4298
+ style: { paddingLeft: padLeft },
4299
+ children: o.label
4300
+ },
4301
+ o.value
4302
+ );
4303
+ }
4304
+ const isSelected = selected.includes(o.value);
4305
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4306
+ "button",
4307
+ {
4308
+ type: "button",
4309
+ disabled: o.disabled,
4310
+ role: "option",
4311
+ "aria-selected": isSelected,
4312
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
4313
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
4314
+ className: cn(
4315
+ itemBase,
4316
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4317
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
4318
+ // 비활성 항목은 hover/focus 스타일 대상에서 제외한다.
4319
+ // (:hover 는 disabled 버튼에도 매칭되므로 클래스 자체를 붙이지 않아야 한다)
4320
+ !o.disabled && activeClass
4321
+ ),
4322
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4323
+ onMouseEnter: () => onHoverValue(o.value),
4324
+ onMouseLeave: () => onHoverValue(null),
4325
+ onClick: () => onSelect(o.value),
4326
+ children: [
4327
+ multi && /* @__PURE__ */ jsxRuntime.jsx(
4328
+ "span",
4329
+ {
4330
+ className: cn(
4331
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4332
+ o.disabled ? (
4333
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4334
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4335
+ ) : cn(
4336
+ 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)]"
4337
+ )
4338
+ ),
4339
+ children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4340
+ SIcon,
4341
+ {
4342
+ name: "checkboxCheck",
4343
+ size: 12,
4344
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4345
+ }
4346
+ )
4347
+ }
4348
+ ),
4349
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4350
+ "span",
4351
+ {
4352
+ className: "flex-1 truncate",
4353
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4354
+ }
4355
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4356
+ ]
4357
+ },
4358
+ o.value
4359
+ );
4360
+ })
4361
+ ] });
4078
4362
  }
4079
4363
  var DEFAULT_ROW_HEIGHT = 44;
4080
4364
  var DENSE_ROW_HEIGHT = 32;
@@ -4350,7 +4634,7 @@ var STable = react.forwardRef(function STable2({
4350
4634
  };
4351
4635
  const radiusClass = radius === "full" ? "rounded-none" : radius === "useTop" ? "rounded-b-[var(--cmp-table-radius)] rounded-t-none" : "rounded-[var(--cmp-table-radius)]";
4352
4636
  const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-body-paddingY-default)";
4353
- const cellPadX = dense ? "var(--cmp-table-body-paddingX-default)" : "var(--cmp-table-body-paddingX-wide)";
4637
+ const cellPadX = "var(--cmp-table-body-paddingX-wide)";
4354
4638
  const rowH = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-body-default-height)";
4355
4639
  const totalColSpan = visibleCols.length + (selectable ? 1 : 0);
4356
4640
  const stickyInfo = (i) => {
@@ -4558,10 +4842,10 @@ var STable = react.forwardRef(function STable2({
4558
4842
  "div",
4559
4843
  {
4560
4844
  "aria-hidden": true,
4561
- className: "pointer-events-none absolute left-0 right-0 top-0 z-[210] h-[var(--cmp-table-header-height)] bg-white/60"
4845
+ className: "pointer-events-none absolute left-0 right-0 top-0 z-[31] h-[var(--cmp-table-header-height)] bg-white/60"
4562
4846
  }
4563
4847
  ),
4564
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute bottom-0 left-0 right-0 top-[var(--cmp-table-header-height)] z-[200] flex items-center justify-center bg-white/60 text-[12px] text-[color:var(--color-grey-65)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[60px] w-full items-center justify-center", children: noDataLabel }) })
4848
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute bottom-0 left-0 right-0 top-[var(--cmp-table-header-height)] z-[30] flex items-center justify-center bg-white/60 text-[12px] text-[color:var(--color-grey-65)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[60px] w-full items-center justify-center", children: noDataLabel }) })
4565
4849
  ] }),
4566
4850
  isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-white/60", children: /* @__PURE__ */ jsxRuntime.jsx(SCircleProgress, { indeterminate: true }) })
4567
4851
  ]