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
@@ -1,10 +1,9 @@
1
1
  "use client";
2
- import { forwardRef, createContext, useState, useRef, useImperativeHandle, useMemo, useId, useEffect, useCallback, useContext } from 'react';
2
+ import { forwardRef, createContext, useState, useRef, useImperativeHandle, useMemo, useEffect, useId, useCallback, useContext } from 'react';
3
3
  import { clsx } from 'clsx';
4
4
  import { extendTailwindMerge } from 'tailwind-merge';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import * as RCheckbox from '@radix-ui/react-checkbox';
7
- import * as Ariakit from '@ariakit/react';
8
7
  import * as RPopover2 from '@radix-ui/react-popover';
9
8
 
10
9
  var twMerge = extendTailwindMerge({
@@ -2935,12 +2934,35 @@ var COLOR_MAP = {
2935
2934
  };
2936
2935
  var clamp = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
2937
2936
  var SCircleProgress = forwardRef(
2938
- function SCircleProgress2({ value = 0, type = "primary", indeterminate = false, label, className, style }, ref) {
2937
+ function SCircleProgress2({
2938
+ value = 0,
2939
+ type = "primary",
2940
+ indeterminate = false,
2941
+ label,
2942
+ innerValue = false,
2943
+ className,
2944
+ style
2945
+ }, ref) {
2939
2946
  const v = clamp(value);
2940
2947
  const colors = COLOR_MAP[type];
2941
2948
  const c = VIEWBOX / 2;
2942
2949
  const percentColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : colors.arc;
2943
2950
  const filled = v / 100 * CIRCUMFERENCE;
2951
+ const showPercent = !indeterminate;
2952
+ const percentNode = /* @__PURE__ */ jsxs(
2953
+ "span",
2954
+ {
2955
+ className: cn(
2956
+ "text-center text-[16px] font-bold leading-[26px]",
2957
+ innerValue && "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
2958
+ ),
2959
+ style: { color: percentColor },
2960
+ children: [
2961
+ Math.round(v),
2962
+ "%"
2963
+ ]
2964
+ }
2965
+ );
2944
2966
  return /* @__PURE__ */ jsxs(
2945
2967
  "div",
2946
2968
  {
@@ -2951,57 +2973,53 @@ var SCircleProgress = forwardRef(
2951
2973
  ),
2952
2974
  style,
2953
2975
  children: [
2954
- /* @__PURE__ */ jsxs(
2955
- "svg",
2956
- {
2957
- viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`,
2958
- xmlns: "http://www.w3.org/2000/svg",
2959
- className: cn("h-[80px] w-[80px]", indeterminate && "origin-center animate-circle-spin"),
2960
- children: [
2961
- /* @__PURE__ */ jsx(
2962
- "circle",
2963
- {
2964
- cx: c,
2965
- cy: c,
2966
- r: RADIUS,
2967
- fill: "none",
2968
- strokeWidth: STROKE,
2969
- style: { stroke: colors.track }
2970
- }
2976
+ /* @__PURE__ */ jsxs("div", { className: cn(innerValue && "relative"), children: [
2977
+ /* @__PURE__ */ jsxs(
2978
+ "svg",
2979
+ {
2980
+ viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`,
2981
+ xmlns: "http://www.w3.org/2000/svg",
2982
+ className: cn(
2983
+ "h-[80px] w-[80px]",
2984
+ indeterminate && "origin-center animate-circle-spin"
2971
2985
  ),
2972
- /* @__PURE__ */ jsx(
2973
- "circle",
2974
- {
2975
- cx: c,
2976
- cy: c,
2977
- r: RADIUS,
2978
- fill: "none",
2979
- strokeWidth: STROKE,
2980
- strokeLinecap: "round",
2981
- className: cn(
2982
- "origin-center",
2983
- indeterminate ? "animate-circle-arc" : "-rotate-90 transition-[stroke-dasharray] duration-[400ms] ease-linear"
2984
- ),
2985
- style: {
2986
- stroke: colors.arc,
2987
- ...indeterminate ? {} : { strokeDasharray: `${filled} ${CIRCUMFERENCE}`, strokeDashoffset: 0 }
2986
+ children: [
2987
+ /* @__PURE__ */ jsx(
2988
+ "circle",
2989
+ {
2990
+ cx: c,
2991
+ cy: c,
2992
+ r: RADIUS,
2993
+ fill: "none",
2994
+ strokeWidth: STROKE,
2995
+ style: { stroke: colors.track }
2988
2996
  }
2989
- }
2990
- )
2991
- ]
2992
- }
2993
- ),
2994
- !indeterminate && /* @__PURE__ */ jsxs(
2995
- "span",
2996
- {
2997
- className: "text-center text-[16px] font-bold leading-[26px]",
2998
- style: { color: percentColor },
2999
- children: [
3000
- Math.round(v),
3001
- "%"
3002
- ]
3003
- }
3004
- ),
2997
+ ),
2998
+ /* @__PURE__ */ jsx(
2999
+ "circle",
3000
+ {
3001
+ cx: c,
3002
+ cy: c,
3003
+ r: RADIUS,
3004
+ fill: "none",
3005
+ strokeWidth: STROKE,
3006
+ strokeLinecap: "round",
3007
+ className: cn(
3008
+ "origin-center",
3009
+ indeterminate ? "animate-circle-arc" : "-rotate-90 transition-[stroke-dasharray] duration-[400ms] ease-linear"
3010
+ ),
3011
+ style: {
3012
+ stroke: colors.arc,
3013
+ ...indeterminate ? {} : { strokeDasharray: `${filled} ${CIRCUMFERENCE}`, strokeDashoffset: 0 }
3014
+ }
3015
+ }
3016
+ )
3017
+ ]
3018
+ }
3019
+ ),
3020
+ showPercent && innerValue && percentNode
3021
+ ] }),
3022
+ showPercent && !innerValue && percentNode,
3005
3023
  label && /* @__PURE__ */ jsx("span", { className: "text-center text-[12px] font-normal leading-[20px] text-[color:var(--cmp-progress-color-label)]", children: label })
3006
3024
  ]
3007
3025
  }
@@ -3092,10 +3110,110 @@ function SPagination({
3092
3110
  );
3093
3111
  }
3094
3112
  var PortalContainerContext = createContext(null);
3113
+ var InsideModalContext = createContext(false);
3095
3114
  PortalContainerContext.Provider;
3115
+ InsideModalContext.Provider;
3096
3116
  function usePortalContainer() {
3097
3117
  return useContext(PortalContainerContext) ?? void 0;
3098
3118
  }
3119
+ function useInsideModal() {
3120
+ return useContext(InsideModalContext);
3121
+ }
3122
+ function runRules(value, rules) {
3123
+ if (!rules || rules.length === 0) return "";
3124
+ for (const rule of rules) {
3125
+ const result = rule(value ?? "");
3126
+ if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
3127
+ }
3128
+ return "";
3129
+ }
3130
+ var FormContext = createContext(null);
3131
+ function useFormContext() {
3132
+ return useContext(FormContext);
3133
+ }
3134
+ function useFormField(field) {
3135
+ const ctx = useFormContext();
3136
+ const cbRef = useRef(field);
3137
+ cbRef.current = field;
3138
+ const name = field.name ?? "";
3139
+ useEffect(() => {
3140
+ if (!ctx) return;
3141
+ return ctx.register({
3142
+ name,
3143
+ validate: () => cbRef.current.validate(),
3144
+ resetValidation: () => cbRef.current.resetValidation()
3145
+ });
3146
+ }, [ctx, name]);
3147
+ }
3148
+
3149
+ // src/lib/sanitize-inline-html.ts
3150
+ var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
3151
+ var ALLOWED_ATTRS_BY_TAG = {
3152
+ A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
3153
+ };
3154
+ var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
3155
+ var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
3156
+ var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
3157
+ "SCRIPT",
3158
+ "STYLE",
3159
+ "IFRAME",
3160
+ "OBJECT",
3161
+ "EMBED",
3162
+ "META",
3163
+ "LINK",
3164
+ "BASE",
3165
+ "NOSCRIPT",
3166
+ // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
3167
+ // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
3168
+ "SVG",
3169
+ "MATH",
3170
+ "TEMPLATE"
3171
+ ]);
3172
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3173
+ var sanitizeNode = (node) => {
3174
+ if (node.nodeType === Node.COMMENT_NODE) {
3175
+ node.remove();
3176
+ return;
3177
+ }
3178
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
3179
+ const element = node;
3180
+ const tagName = element.tagName;
3181
+ if (DROP_CONTENT_TAGS.has(tagName)) {
3182
+ element.remove();
3183
+ return;
3184
+ }
3185
+ Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
3186
+ if (ALLOWED_INLINE_TAGS.has(tagName)) {
3187
+ if (element.hasAttribute("className")) {
3188
+ const val = element.getAttribute("className");
3189
+ element.removeAttribute("className");
3190
+ if (!element.hasAttribute("class")) element.setAttribute("class", val);
3191
+ }
3192
+ const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
3193
+ Array.from(element.attributes).forEach((attr) => {
3194
+ if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
3195
+ });
3196
+ if (tagName === "A") {
3197
+ const href = element.getAttribute("href");
3198
+ if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
3199
+ if (element.hasAttribute("target")) {
3200
+ element.setAttribute("rel", "noopener noreferrer");
3201
+ }
3202
+ }
3203
+ return;
3204
+ }
3205
+ const parent = element.parentNode;
3206
+ if (parent == null) return;
3207
+ while (element.firstChild) parent.insertBefore(element.firstChild, element);
3208
+ parent.removeChild(element);
3209
+ };
3210
+ var sanitizeInlineHtml = (value) => {
3211
+ if (typeof document === "undefined") return escapeHtml(value);
3212
+ const template = document.createElement("template");
3213
+ template.innerHTML = value;
3214
+ Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
3215
+ return template.innerHTML;
3216
+ };
3099
3217
 
3100
3218
  // src/lib/floating-anim.ts
3101
3219
  var FLOATING_SLIDE_ANIM = [
@@ -3367,7 +3485,7 @@ var STooltip = forwardRef(function STooltip2({
3367
3485
  if (disabled || body == null) return /* @__PURE__ */ jsx(Fragment, { children: triggerNode });
3368
3486
  const isHover = trigger === "hover";
3369
3487
  const contentClass = cn(
3370
- "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)]",
3488
+ "z-50 max-w-xs rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
3371
3489
  FLOATING_SLIDE_ANIM,
3372
3490
  className
3373
3491
  );
@@ -3433,32 +3551,6 @@ var STooltip = forwardRef(function STooltip2({
3433
3551
  ] });
3434
3552
  });
3435
3553
  STooltip.displayName = "STooltip";
3436
- function runRules(value, rules) {
3437
- if (!rules || rules.length === 0) return "";
3438
- for (const rule of rules) {
3439
- const result = rule(value ?? "");
3440
- if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
3441
- }
3442
- return "";
3443
- }
3444
- var FormContext = createContext(null);
3445
- function useFormContext() {
3446
- return useContext(FormContext);
3447
- }
3448
- function useFormField(field) {
3449
- const ctx = useFormContext();
3450
- const cbRef = useRef(field);
3451
- cbRef.current = field;
3452
- const name = field.name ?? "";
3453
- useEffect(() => {
3454
- if (!ctx) return;
3455
- return ctx.register({
3456
- name,
3457
- validate: () => cbRef.current.validate(),
3458
- resetValidation: () => cbRef.current.resetValidation()
3459
- });
3460
- }, [ctx, name]);
3461
- }
3462
3554
  var FIELD_SIZE = {
3463
3555
  sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 16 },
3464
3556
  md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 16 }
@@ -3473,6 +3565,7 @@ var SField = forwardRef(function SField2({
3473
3565
  name,
3474
3566
  label = "",
3475
3567
  icon,
3568
+ iconColor,
3476
3569
  labelTooltip = "",
3477
3570
  labelTooltipProps,
3478
3571
  labelWidth,
@@ -3550,7 +3643,15 @@ var SField = forwardRef(function SField2({
3550
3643
  lineHeight: `${s.lineHeight}px`
3551
3644
  },
3552
3645
  children: [
3553
- icon && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3646
+ icon && /* @__PURE__ */ jsx(
3647
+ SIcon,
3648
+ {
3649
+ name: icon,
3650
+ size: s.icon,
3651
+ color: iconColor,
3652
+ style: { marginRight: s.labelGap }
3653
+ }
3654
+ ),
3554
3655
  /* @__PURE__ */ jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3555
3656
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsx(
3556
3657
  STooltip,
@@ -3600,7 +3701,7 @@ var SField = forwardRef(function SField2({
3600
3701
  borderBottomLeftRadius: s.radius - 1
3601
3702
  },
3602
3703
  children: [
3603
- icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon }),
3704
+ icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
3604
3705
  effectiveAddonLabel
3605
3706
  ]
3606
3707
  }
@@ -3615,6 +3716,77 @@ var SField = forwardRef(function SField2({
3615
3716
  }
3616
3717
  );
3617
3718
  });
3719
+ function SPortal({
3720
+ open,
3721
+ onClose,
3722
+ onOpenChange,
3723
+ anchorRef,
3724
+ placement = "bottom",
3725
+ align = "start",
3726
+ offset = 4,
3727
+ alignOffset = 0,
3728
+ fit = false,
3729
+ maxHeight,
3730
+ autoClose = false,
3731
+ persistent = false,
3732
+ viewportPadding = 0,
3733
+ onOpenAutoFocus,
3734
+ onCloseAutoFocus,
3735
+ children,
3736
+ className,
3737
+ style,
3738
+ ariaLabel
3739
+ }) {
3740
+ const portalContainer = usePortalContainer();
3741
+ return /* @__PURE__ */ jsxs(
3742
+ RPopover2.Root,
3743
+ {
3744
+ open,
3745
+ onOpenChange: (next) => {
3746
+ onOpenChange?.(next);
3747
+ if (!next) onClose?.();
3748
+ },
3749
+ children: [
3750
+ /* @__PURE__ */ jsx(RPopover2.Anchor, { virtualRef: anchorRef }),
3751
+ /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
3752
+ RPopover2.Content,
3753
+ {
3754
+ side: placement,
3755
+ align,
3756
+ sideOffset: offset,
3757
+ alignOffset,
3758
+ collisionPadding: viewportPadding,
3759
+ "aria-label": ariaLabel,
3760
+ onPointerDownOutside: (e) => {
3761
+ if (persistent || anchorRef.current?.contains(e.target)) {
3762
+ e.preventDefault();
3763
+ }
3764
+ },
3765
+ onFocusOutside: (e) => {
3766
+ if (persistent || anchorRef.current?.contains(e.target)) {
3767
+ e.preventDefault();
3768
+ }
3769
+ },
3770
+ onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
3771
+ onOpenAutoFocus,
3772
+ onCloseAutoFocus,
3773
+ onClick: autoClose ? () => onClose?.() : void 0,
3774
+ className: cn(
3775
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm",
3776
+ maxHeight != null && "overflow-y-auto",
3777
+ fit && "min-w-(--radix-popover-trigger-width)",
3778
+ FLOATING_SLIDE_ANIM,
3779
+ className
3780
+ ),
3781
+ style: { maxHeight, ...style },
3782
+ children
3783
+ }
3784
+ ) })
3785
+ ]
3786
+ }
3787
+ );
3788
+ }
3789
+ SPortal.displayName = "SPortal";
3618
3790
 
3619
3791
  // src/components/SSelect/select.config.ts
3620
3792
  function flattenOptions(options, depth = 1) {
@@ -3649,75 +3821,6 @@ function flattenOptions(options, depth = 1) {
3649
3821
  var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
3650
3822
  var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
3651
3823
  var extractText = (html) => html.replace(/<[^>]*>/g, "");
3652
-
3653
- // src/lib/sanitize-inline-html.ts
3654
- var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
3655
- var ALLOWED_ATTRS_BY_TAG = {
3656
- A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
3657
- };
3658
- var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
3659
- var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
3660
- var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
3661
- "SCRIPT",
3662
- "STYLE",
3663
- "IFRAME",
3664
- "OBJECT",
3665
- "EMBED",
3666
- "META",
3667
- "LINK",
3668
- "BASE",
3669
- "NOSCRIPT",
3670
- // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
3671
- // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
3672
- "SVG",
3673
- "MATH",
3674
- "TEMPLATE"
3675
- ]);
3676
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3677
- var sanitizeNode = (node) => {
3678
- if (node.nodeType === Node.COMMENT_NODE) {
3679
- node.remove();
3680
- return;
3681
- }
3682
- if (node.nodeType !== Node.ELEMENT_NODE) return;
3683
- const element = node;
3684
- const tagName = element.tagName;
3685
- if (DROP_CONTENT_TAGS.has(tagName)) {
3686
- element.remove();
3687
- return;
3688
- }
3689
- Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
3690
- if (ALLOWED_INLINE_TAGS.has(tagName)) {
3691
- if (element.hasAttribute("className")) {
3692
- const val = element.getAttribute("className");
3693
- element.removeAttribute("className");
3694
- if (!element.hasAttribute("class")) element.setAttribute("class", val);
3695
- }
3696
- const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
3697
- Array.from(element.attributes).forEach((attr) => {
3698
- if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
3699
- });
3700
- if (tagName === "A") {
3701
- const href = element.getAttribute("href");
3702
- if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
3703
- if (element.hasAttribute("target")) {
3704
- element.setAttribute("rel", "noopener noreferrer");
3705
- }
3706
- }
3707
- return;
3708
- }
3709
- const parent = element.parentNode;
3710
- if (parent == null) return;
3711
- while (element.firstChild) parent.insertBefore(element.firstChild, element);
3712
- parent.removeChild(element);
3713
- };
3714
- var sanitizeInlineHtml = (value) => {
3715
- if (typeof document === "undefined") return escapeHtml(value);
3716
- const template = document.createElement("template");
3717
- template.innerHTML = value;
3718
- Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
3719
- return template.innerHTML;
3720
- };
3721
3824
  var SEARCH_THRESHOLD = 11;
3722
3825
  var SSelect = forwardRef(function SSelect2({
3723
3826
  value,
@@ -3741,6 +3844,7 @@ var SSelect = forwardRef(function SSelect2({
3741
3844
  label,
3742
3845
  labelWidth,
3743
3846
  icon,
3847
+ iconColor,
3744
3848
  labelTooltip,
3745
3849
  addonLabel,
3746
3850
  addonAlign,
@@ -3752,18 +3856,15 @@ var SSelect = forwardRef(function SSelect2({
3752
3856
  }, ref) {
3753
3857
  const multi = type === "multi" || type === "multi_depth";
3754
3858
  const portalContainer = usePortalContainer();
3859
+ const insideModal = useInsideModal();
3755
3860
  const [search, setSearch] = useState("");
3756
3861
  const [ruleError, setRuleError] = useState("");
3757
3862
  const [open, setOpen] = useState(false);
3863
+ const [pendingOpen, setPendingOpen] = useState(false);
3864
+ const [focusedIndex, setFocusedIndex] = useState(-1);
3758
3865
  const triggerRef = useRef(null);
3759
- useImperativeHandle(
3760
- ref,
3761
- () => ({
3762
- focus: () => triggerRef.current?.focus(),
3763
- open: () => setOpen(true)
3764
- }),
3765
- []
3766
- );
3866
+ const searchInputRef = useRef(null);
3867
+ const listRef = useRef(null);
3767
3868
  const flat = useMemo(() => flattenOptions(options), [options]);
3768
3869
  const selectable = useMemo(() => selectableOptions(flat), [flat]);
3769
3870
  const hasLeaf = useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -3781,6 +3882,46 @@ var SSelect = forwardRef(function SSelect2({
3781
3882
  const arr = Array.isArray(value) ? value : [value];
3782
3883
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
3783
3884
  }, [value]);
3885
+ const selectedRef = useRef(selected);
3886
+ selectedRef.current = selected;
3887
+ const emit = (next) => {
3888
+ if (!onValueChange) return;
3889
+ const mapped = next.map(
3890
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3891
+ );
3892
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3893
+ };
3894
+ const commitOpen = (next) => {
3895
+ setOpen(next);
3896
+ onOpenChange?.(next);
3897
+ if (!next) {
3898
+ setPendingOpen(false);
3899
+ setSearch("");
3900
+ if (rules && rules.length > 0) {
3901
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
3902
+ }
3903
+ }
3904
+ };
3905
+ const requestOpen = (next) => {
3906
+ if (next && insideModal && portalContainer == null) {
3907
+ setPendingOpen(true);
3908
+ return;
3909
+ }
3910
+ commitOpen(next);
3911
+ };
3912
+ useEffect(() => {
3913
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
3914
+ setPendingOpen(false);
3915
+ commitOpen(true);
3916
+ }, [insideModal, pendingOpen, portalContainer]);
3917
+ useImperativeHandle(
3918
+ ref,
3919
+ () => ({
3920
+ focus: () => triggerRef.current?.focus(),
3921
+ open: () => requestOpen(true)
3922
+ }),
3923
+ [insideModal, portalContainer]
3924
+ );
3784
3925
  useFormField({
3785
3926
  name,
3786
3927
  validate: () => {
@@ -3790,13 +3931,6 @@ var SSelect = forwardRef(function SSelect2({
3790
3931
  },
3791
3932
  resetValidation: () => setRuleError("")
3792
3933
  });
3793
- const emit = (next) => {
3794
- if (!onValueChange) return;
3795
- const mapped = next.map(
3796
- (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3797
- );
3798
- onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3799
- };
3800
3934
  const filtered = useMemo(() => {
3801
3935
  if (!showSearch || search.trim() === "") return flat;
3802
3936
  const q = search.trim().toLowerCase();
@@ -3807,6 +3941,16 @@ var SSelect = forwardRef(function SSelect2({
3807
3941
  const allValues = selectable.map((o) => o.value);
3808
3942
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
3809
3943
  const hasItems = filtered.some((o) => !o.isGroup);
3944
+ const navigableItems = useMemo(() => {
3945
+ const items = [];
3946
+ if (multi && useSelectAll && allValues.length > 0) {
3947
+ items.push({ value: "__select_all__", isSelectAll: true });
3948
+ }
3949
+ filtered.forEach((o) => {
3950
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
3951
+ });
3952
+ return items;
3953
+ }, [allValues.length, filtered, multi, useSelectAll]);
3810
3954
  const triggerText = (() => {
3811
3955
  if (selected.length === 0) return placeholder;
3812
3956
  if (multi) {
@@ -3816,26 +3960,119 @@ var SSelect = forwardRef(function SSelect2({
3816
3960
  return labelByValue.get(selected[0]) ?? selected[0];
3817
3961
  })();
3818
3962
  const isPlaceholder = selected.length === 0;
3819
- const selectStore = /* @__PURE__ */ jsxs(
3820
- Ariakit.SelectProvider,
3963
+ const handleSelect = (nextValue) => {
3964
+ if (multi) {
3965
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
3966
+ emit(next);
3967
+ return;
3968
+ }
3969
+ emit([nextValue]);
3970
+ commitOpen(false);
3971
+ };
3972
+ const handleSelectAll = () => {
3973
+ emit(allSelected ? [] : allValues);
3974
+ };
3975
+ useEffect(() => {
3976
+ if (!open) {
3977
+ setFocusedIndex(-1);
3978
+ return;
3979
+ }
3980
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
3981
+ }, [open, navigableItems.length]);
3982
+ useEffect(() => {
3983
+ if (!open) return;
3984
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
3985
+ }, [search, open, navigableItems.length]);
3986
+ useEffect(() => {
3987
+ if (!open || focusedIndex < 0) return;
3988
+ const list = listRef.current;
3989
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
3990
+ if (!list || !focusedEl) return;
3991
+ const listRect = list.getBoundingClientRect();
3992
+ const itemRect = focusedEl.getBoundingClientRect();
3993
+ if (itemRect.top < listRect.top) {
3994
+ list.scrollTop += itemRect.top - listRect.top;
3995
+ } else if (itemRect.bottom > listRect.bottom) {
3996
+ list.scrollTop += itemRect.bottom - listRect.bottom;
3997
+ }
3998
+ }, [focusedIndex, open]);
3999
+ const moveFocusedIndex = (direction) => {
4000
+ if (!open || navigableItems.length === 0) return;
4001
+ setFocusedIndex((current) => {
4002
+ if (direction === "next") {
4003
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
4004
+ }
4005
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
4006
+ });
4007
+ };
4008
+ const selectFocusedItem = () => {
4009
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
4010
+ const focused = navigableItems[focusedIndex];
4011
+ if (focused.isSelectAll) {
4012
+ handleSelectAll();
4013
+ return;
4014
+ }
4015
+ handleSelect(focused.value);
4016
+ };
4017
+ const handleKeyDown = (event) => {
4018
+ if (!open) {
4019
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
4020
+ event.preventDefault();
4021
+ requestOpen(true);
4022
+ }
4023
+ return;
4024
+ }
4025
+ if (event.key === "ArrowDown") {
4026
+ event.preventDefault();
4027
+ moveFocusedIndex("next");
4028
+ return;
4029
+ }
4030
+ if (event.key === "ArrowUp") {
4031
+ event.preventDefault();
4032
+ moveFocusedIndex("prev");
4033
+ return;
4034
+ }
4035
+ if (event.key === "Enter") {
4036
+ event.preventDefault();
4037
+ selectFocusedItem();
4038
+ return;
4039
+ }
4040
+ if (event.key === "Escape") {
4041
+ event.preventDefault();
4042
+ commitOpen(false);
4043
+ }
4044
+ };
4045
+ return /* @__PURE__ */ jsxs(
4046
+ SField,
3821
4047
  {
3822
- animated: portalContainer == null ? 150 : false,
3823
- value: multi ? selected : selected[0] ?? "",
3824
- setValue: (v) => emit(Array.isArray(v) ? v : v === "" ? [] : [v]),
3825
- open,
3826
- setOpen: (o) => {
3827
- setOpen(o);
3828
- onOpenChange?.(o);
3829
- if (!o && rules && rules.length > 0) {
3830
- setRuleError(runRules(multi ? selected : selected[0] ?? "", rules));
3831
- }
3832
- },
4048
+ label,
4049
+ labelWidth,
4050
+ icon,
4051
+ iconColor,
4052
+ labelTooltip,
4053
+ labelTooltipProps,
4054
+ addonLabel,
4055
+ addonAlign,
4056
+ hint,
4057
+ error: error || ruleError !== "",
4058
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4059
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
4060
+ disabled: effectiveDisabled,
4061
+ width,
4062
+ className,
4063
+ style,
3833
4064
  children: [
4065
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected.join(",") }),
3834
4066
  /* @__PURE__ */ jsxs(
3835
- Ariakit.Select,
4067
+ "button",
3836
4068
  {
3837
4069
  ref: triggerRef,
4070
+ type: "button",
3838
4071
  disabled: effectiveDisabled,
4072
+ "aria-haspopup": "listbox",
4073
+ "aria-expanded": open,
4074
+ onClick: () => requestOpen(!open),
4075
+ onKeyDown: handleKeyDown,
3839
4076
  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",
3840
4077
  style: {
3841
4078
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -3860,174 +4097,229 @@ var SSelect = forwardRef(function SSelect2({
3860
4097
  ]
3861
4098
  }
3862
4099
  ),
3863
- /* @__PURE__ */ jsxs(
3864
- Ariakit.SelectPopover,
4100
+ /* @__PURE__ */ jsx(
4101
+ SPortal,
3865
4102
  {
3866
- portal: true,
3867
- portalElement: portalContainer,
3868
- gutter: 4,
3869
- sameWidth: !dropdownWidth,
3870
- 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",
4103
+ open,
4104
+ onClose: () => commitOpen(false),
4105
+ onOpenChange: (next) => {
4106
+ if (next) requestOpen(true);
4107
+ },
4108
+ anchorRef: triggerRef,
4109
+ placement: "bottom",
4110
+ align: "start",
4111
+ offset: 4,
4112
+ fit: !dropdownWidth,
4113
+ onOpenAutoFocus: (event) => {
4114
+ if (!showSearch) {
4115
+ event.preventDefault();
4116
+ return;
4117
+ }
4118
+ event.preventDefault();
4119
+ requestAnimationFrame(() => searchInputRef.current?.focus());
4120
+ },
4121
+ onCloseAutoFocus: (event) => {
4122
+ event.preventDefault();
4123
+ triggerRef.current?.focus();
4124
+ },
4125
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] py-[4px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
3871
4126
  style: {
3872
- maxHeight: dropdownHeight,
3873
4127
  ...dropdownWidth ? { width: dropdownWidth } : {},
3874
4128
  maxWidth: maxDropdownWidth
3875
4129
  },
3876
- children: [
3877
- showSearch && // 원본 sd-select-list-item-search: sticky + 검색 아이콘 + 테두리 input + focus-within + clear
3878
- /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ 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: [
3879
- /* @__PURE__ */ jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
3880
- /* @__PURE__ */ jsx(
3881
- Ariakit.Combobox,
3882
- {
3883
- autoSelect: true,
3884
- placeholder: "\uAC80\uC0C9",
3885
- 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)]"
3886
- }
3887
- ),
3888
- search !== "" && /* @__PURE__ */ jsx(
3889
- SGhostButton,
3890
- {
3891
- icon: "close",
3892
- size: "xxs",
3893
- intent: "default",
3894
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
3895
- onClick: () => setSearch(""),
3896
- className: "shrink-0"
3897
- }
3898
- )
3899
- ] }) }),
3900
- multi && useSelectAll && /* @__PURE__ */ jsx(
3901
- "button",
3902
- {
3903
- type: "button",
3904
- 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)]",
3905
- onClick: () => emit(allSelected ? [] : allValues),
3906
- children: allSelectedLabel
3907
- }
3908
- ),
3909
- hasItems ? /* @__PURE__ */ jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
3910
- // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
3911
- /* @__PURE__ */ jsx("div", { className: "px-[12px] py-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-disabled)]", children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
3912
- )
3913
- ]
4130
+ children: /* @__PURE__ */ jsxs(
4131
+ "div",
4132
+ {
4133
+ ref: listRef,
4134
+ 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)]",
4135
+ style: { maxHeight: dropdownHeight },
4136
+ onMouseLeave: () => setFocusedIndex(-1),
4137
+ children: [
4138
+ showSearch && /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ 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: [
4139
+ /* @__PURE__ */ jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4140
+ /* @__PURE__ */ jsx(
4141
+ "input",
4142
+ {
4143
+ ref: searchInputRef,
4144
+ value: search,
4145
+ onChange: (e) => setSearch(e.target.value),
4146
+ onKeyDown: handleKeyDown,
4147
+ placeholder: "\uAC80\uC0C9",
4148
+ 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)]"
4149
+ }
4150
+ ),
4151
+ search !== "" && /* @__PURE__ */ jsx(
4152
+ SGhostButton,
4153
+ {
4154
+ icon: "close",
4155
+ size: "xxs",
4156
+ intent: "default",
4157
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4158
+ onClick: () => setSearch(""),
4159
+ className: "shrink-0"
4160
+ }
4161
+ )
4162
+ ] }) }),
4163
+ hasItems ? /* @__PURE__ */ jsx(
4164
+ ItemList,
4165
+ {
4166
+ items: filtered,
4167
+ multi,
4168
+ selected,
4169
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
4170
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
4171
+ selectAllLabel: allSelectedLabel,
4172
+ selectAllSelected: allSelected,
4173
+ onSelectAll: handleSelectAll,
4174
+ onSelect: handleSelect,
4175
+ onHoverValue: (value2) => {
4176
+ const index = navigableItems.findIndex(
4177
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
4178
+ );
4179
+ if (index >= 0) setFocusedIndex(index);
4180
+ }
4181
+ }
4182
+ ) : /* @__PURE__ */ jsx("div", { className: "px-[12px] py-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-disabled)]", children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
4183
+ ]
4184
+ }
4185
+ )
3914
4186
  }
3915
4187
  )
3916
4188
  ]
3917
4189
  }
3918
4190
  );
3919
- const control = showSearch ? /* @__PURE__ */ jsx(Ariakit.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
3920
- return /* @__PURE__ */ jsxs(
3921
- SField,
3922
- {
3923
- label,
3924
- labelWidth,
3925
- icon,
3926
- labelTooltip,
3927
- labelTooltipProps,
3928
- addonLabel,
3929
- addonAlign,
3930
- hint,
3931
- error: error || ruleError !== "",
3932
- status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
3933
- errorMessage: ruleError !== "" ? ruleError : errorMessage,
3934
- disabled: effectiveDisabled,
3935
- width,
3936
- className,
3937
- style,
3938
- children: [
3939
- name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected.join(",") }),
3940
- control
3941
- ]
3942
- }
3943
- );
3944
4191
  });
3945
4192
  SSelect.displayName = "SSelect";
3946
4193
  function ItemList({
3947
4194
  items,
3948
4195
  multi,
3949
4196
  selected,
3950
- useSearch
4197
+ focusedValue,
4198
+ showSelectAll,
4199
+ selectAllLabel,
4200
+ selectAllSelected,
4201
+ onSelectAll,
4202
+ onSelect,
4203
+ onHoverValue
3951
4204
  }) {
3952
4205
  const itemBase = cn(
3953
- // group: 체크박스 inverse(group-data-[active-item])를 위해 필요
3954
- "group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
4206
+ "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
3955
4207
  "text-[color:var(--cmp-listItem-content-default)]",
3956
- // Ariakit은 disabled 항목에 aria-disabled를 붙이므로 aria-disabled 변형으로 스타일 (data-disabled 아님)
3957
- "aria-disabled:cursor-not-allowed aria-disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4208
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
3958
4209
  );
3959
- const activeClass = "data-[active-item]:bg-[var(--cmp-listItem-bg-hover)] data-[active-item]:text-[color:var(--cmp-listItem-content-hover)]";
3960
- return /* @__PURE__ */ jsx(Fragment, { children: items.map((o) => {
3961
- const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
3962
- if (o.isGroup) {
3963
- const isDepth1 = o.depth === 1;
3964
- return /* @__PURE__ */ jsx(
3965
- "div",
3966
- {
3967
- className: cn(
3968
- "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
3969
- 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",
3970
- // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
3971
- // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
3972
- o.disabled && "text-[color:var(--cmp-listItem-content-disabled)]",
3973
- o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
3974
- ),
3975
- style: { paddingLeft: padLeft },
3976
- children: o.label
3977
- },
3978
- o.value
3979
- );
3980
- }
3981
- const isSelected = selected.includes(o.value);
3982
- return /* @__PURE__ */ jsxs(
3983
- Ariakit.SelectItem,
4210
+ 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)]";
4211
+ return /* @__PURE__ */ jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
4212
+ showSelectAll && /* @__PURE__ */ jsxs(
4213
+ "button",
3984
4214
  {
3985
- value: o.value,
3986
- disabled: o.disabled,
4215
+ type: "button",
4216
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
4217
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
3987
4218
  className: cn(
3988
4219
  itemBase,
3989
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
3990
- activeClass
4220
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
3991
4221
  ),
3992
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
3993
- render: useSearch ? /* @__PURE__ */ jsx(Ariakit.ComboboxItem, {}) : void 0,
4222
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
4223
+ onMouseEnter: () => onHoverValue("__select_all__"),
4224
+ onClick: onSelectAll,
3994
4225
  children: [
3995
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
3996
4226
  /* @__PURE__ */ jsx(
3997
4227
  "span",
3998
4228
  {
3999
4229
  className: cn(
4000
4230
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4001
- o.disabled ? (
4002
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4003
- "border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4004
- ) : cn(
4005
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4006
- 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"
4007
- )
4231
+ 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)]"
4008
4232
  ),
4009
- children: isSelected && /* @__PURE__ */ jsx(
4233
+ children: selectAllSelected && /* @__PURE__ */ jsx(
4010
4234
  SIcon,
4011
4235
  {
4012
4236
  name: "checkboxCheck",
4013
4237
  size: 12,
4014
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4238
+ color: "var(--cmp-checkbox-checked-icon-default)"
4015
4239
  }
4016
4240
  )
4017
4241
  }
4018
4242
  ),
4019
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
4020
- "span",
4021
- {
4022
- className: "flex-1 truncate",
4023
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4024
- }
4025
- ) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.label })
4243
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
4026
4244
  ]
4027
- },
4028
- o.value
4029
- );
4030
- }) });
4245
+ }
4246
+ ),
4247
+ items.map((o) => {
4248
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4249
+ if (o.isGroup) {
4250
+ const isDepth1 = o.depth === 1;
4251
+ return /* @__PURE__ */ jsx(
4252
+ "div",
4253
+ {
4254
+ className: cn(
4255
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4256
+ 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",
4257
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4258
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4259
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4260
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4261
+ ),
4262
+ style: { paddingLeft: padLeft },
4263
+ children: o.label
4264
+ },
4265
+ o.value
4266
+ );
4267
+ }
4268
+ const isSelected = selected.includes(o.value);
4269
+ return /* @__PURE__ */ jsxs(
4270
+ "button",
4271
+ {
4272
+ type: "button",
4273
+ disabled: o.disabled,
4274
+ role: "option",
4275
+ "aria-selected": isSelected,
4276
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
4277
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
4278
+ className: cn(
4279
+ itemBase,
4280
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4281
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
4282
+ activeClass
4283
+ ),
4284
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4285
+ onMouseEnter: () => onHoverValue(o.value),
4286
+ onClick: () => onSelect(o.value),
4287
+ children: [
4288
+ multi && /* @__PURE__ */ jsx(
4289
+ "span",
4290
+ {
4291
+ className: cn(
4292
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4293
+ o.disabled ? (
4294
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4295
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4296
+ ) : cn(
4297
+ 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)]"
4298
+ )
4299
+ ),
4300
+ children: isSelected && /* @__PURE__ */ jsx(
4301
+ SIcon,
4302
+ {
4303
+ name: "checkboxCheck",
4304
+ size: 12,
4305
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4306
+ }
4307
+ )
4308
+ }
4309
+ ),
4310
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
4311
+ "span",
4312
+ {
4313
+ className: "flex-1 truncate",
4314
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4315
+ }
4316
+ ) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.label })
4317
+ ]
4318
+ },
4319
+ o.value
4320
+ );
4321
+ })
4322
+ ] });
4031
4323
  }
4032
4324
  var DEFAULT_ROW_HEIGHT = 44;
4033
4325
  var DENSE_ROW_HEIGHT = 32;
@@ -4302,7 +4594,8 @@ var STable = forwardRef(function STable2({
4302
4594
  else onSelectedChange?.([...selected, row]);
4303
4595
  };
4304
4596
  const radiusClass = radius === "full" ? "rounded-none" : radius === "useTop" ? "rounded-b-[var(--cmp-table-radius)] rounded-t-none" : "rounded-[var(--cmp-table-radius)]";
4305
- const cellPadY = dense ? "var(--cmp-table-body-dense-paddingY)" : "var(--cmp-table-body-default-paddingY)";
4597
+ const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-body-paddingY-default)";
4598
+ const cellPadX = "var(--cmp-table-body-paddingX-wide)";
4306
4599
  const rowH = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-body-default-height)";
4307
4600
  const totalColSpan = visibleCols.length + (selectable ? 1 : 0);
4308
4601
  const stickyInfo = (i) => {
@@ -4477,13 +4770,15 @@ var STable = forwardRef(function STable2({
4477
4770
  className: cn(
4478
4771
  // 원본 .td: 컬럼 폭에 맞춰 내용 말줄임(overflow hidden + ellipsis) + 행 구분선(border-b)
4479
4772
  // 셀 배경은 흰색으로 채워 뒤 배경이 비치지 않게 하고, hover 시 행 배경(grey_05)을 따른다.
4480
- "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)]",
4773
+ "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)]",
4481
4774
  rowBg,
4482
4775
  "group-hover:bg-[var(--color-grey-05)]",
4483
4776
  stickyShadowClass(i),
4484
4777
  col.tdClass
4485
4778
  ),
4486
4779
  style: {
4780
+ paddingLeft: cellPadX,
4781
+ paddingRight: cellPadX,
4487
4782
  paddingTop: cellPadY,
4488
4783
  paddingBottom: cellPadY,
4489
4784
  textAlign: col.align ?? "left",