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.
- package/dist/components/SActionModal/SActionModal.cjs +10 -4
- package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
- package/dist/components/SActionModal/SActionModal.js +10 -4
- package/dist/components/SActionModal/SActionModal.js.map +1 -1
- package/dist/components/SBarcodeInput/SBarcodeInput.cjs +22 -5
- package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
- package/dist/components/SBarcodeInput/SBarcodeInput.d.ts +3 -1
- package/dist/components/SBarcodeInput/SBarcodeInput.js +22 -5
- package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
- package/dist/components/SCalendar/SCalendar.cjs +9 -3
- package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
- package/dist/components/SCalendar/SCalendar.js +9 -3
- package/dist/components/SCalendar/SCalendar.js.map +1 -1
- package/dist/components/SChip/SChip.cjs +9 -3
- package/dist/components/SChip/SChip.cjs.map +1 -1
- package/dist/components/SChip/SChip.js +9 -3
- package/dist/components/SChip/SChip.js.map +1 -1
- package/dist/components/SChipInput/SChipInput.cjs +44 -13
- package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
- package/dist/components/SChipInput/SChipInput.d.ts +5 -0
- package/dist/components/SChipInput/SChipInput.js +44 -13
- package/dist/components/SChipInput/SChipInput.js.map +1 -1
- package/dist/components/SChipInput/index.d.ts +1 -1
- package/dist/components/SCircleProgress/SCircleProgress.cjs +6 -2
- package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
- package/dist/components/SCircleProgress/SCircleProgress.js +6 -2
- package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
- package/dist/components/SConfirmModal/SConfirmModal.cjs +20 -13
- package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
- package/dist/components/SConfirmModal/SConfirmModal.d.ts +7 -4
- package/dist/components/SConfirmModal/SConfirmModal.js +20 -13
- package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
- package/dist/components/SDatePicker/SDatePicker.cjs +22 -5
- package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
- package/dist/components/SDatePicker/SDatePicker.d.ts +3 -1
- package/dist/components/SDatePicker/SDatePicker.js +22 -5
- package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
- package/dist/components/SDateRangePicker/SDateRangePicker.cjs +22 -5
- package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
- package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +3 -1
- package/dist/components/SDateRangePicker/SDateRangePicker.js +22 -5
- package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
- package/dist/components/SDropdownButton/SDropdownButton.cjs +3 -1
- package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
- package/dist/components/SDropdownButton/SDropdownButton.js +3 -1
- package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
- package/dist/components/SField/SField.cjs +20 -5
- package/dist/components/SField/SField.cjs.map +1 -1
- package/dist/components/SField/SField.d.ts +4 -1
- package/dist/components/SField/SField.js +20 -5
- package/dist/components/SField/SField.js.map +1 -1
- package/dist/components/SFilePicker/SFilePicker.cjs +23 -6
- package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
- package/dist/components/SFilePicker/SFilePicker.d.ts +3 -1
- package/dist/components/SFilePicker/SFilePicker.js +23 -6
- package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
- package/dist/components/SGhostButton/SGhostButton.cjs +9 -3
- package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
- package/dist/components/SGhostButton/SGhostButton.d.ts +5 -1
- package/dist/components/SGhostButton/SGhostButton.js +9 -3
- package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
- package/dist/components/SGnb/SGnb.cjs +3297 -0
- package/dist/components/SGnb/SGnb.cjs.map +1 -0
- package/dist/components/SGnb/SGnb.d.ts +35 -0
- package/dist/components/SGnb/SGnb.js +3295 -0
- package/dist/components/SGnb/SGnb.js.map +1 -0
- package/dist/components/SGnb/gnb.config.d.ts +63 -0
- package/dist/components/SGnb/index.d.ts +2 -0
- package/dist/components/SGuide/SGuide.cjs +9 -3
- package/dist/components/SGuide/SGuide.cjs.map +1 -1
- package/dist/components/SGuide/SGuide.js +9 -3
- package/dist/components/SGuide/SGuide.js.map +1 -1
- package/dist/components/SInput/SInput.cjs +22 -5
- package/dist/components/SInput/SInput.cjs.map +1 -1
- package/dist/components/SInput/SInput.d.ts +4 -1
- package/dist/components/SInput/SInput.js +22 -5
- package/dist/components/SInput/SInput.js.map +1 -1
- package/dist/components/SKeyValueTable/SKeyValueTable.cjs +511 -209
- package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
- package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +27 -6
- package/dist/components/SKeyValueTable/SKeyValueTable.js +510 -208
- package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
- package/dist/components/SLayout/SLayout.cjs +79 -0
- package/dist/components/SLayout/SLayout.cjs.map +1 -0
- package/dist/components/SLayout/SLayout.d.ts +41 -0
- package/dist/components/SLayout/SLayout.js +75 -0
- package/dist/components/SLayout/SLayout.js.map +1 -0
- package/dist/components/SLayout/index.d.ts +1 -0
- package/dist/components/SLoadingContainer/SLoadingContainer.cjs +6 -2
- package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
- package/dist/components/SLoadingContainer/SLoadingContainer.js +6 -2
- package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
- package/dist/components/SLoadingContainer/loading.cjs +6 -2
- package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
- package/dist/components/SLoadingContainer/loading.js +6 -2
- package/dist/components/SLoadingContainer/loading.js.map +1 -1
- package/dist/components/SLoadingModal/SLoadingModal.cjs +16 -6
- package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
- package/dist/components/SLoadingModal/SLoadingModal.js +16 -6
- package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
- package/dist/components/SModalContainer/SModalContainer.cjs +10 -4
- package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
- package/dist/components/SModalContainer/SModalContainer.d.ts +10 -0
- package/dist/components/SModalContainer/SModalContainer.js +10 -4
- package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
- package/dist/components/SNumberInput/SNumberInput.cjs +22 -5
- package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
- package/dist/components/SNumberInput/SNumberInput.d.ts +2 -0
- package/dist/components/SNumberInput/SNumberInput.js +22 -5
- package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
- package/dist/components/SPage/SPage.cjs +86 -0
- package/dist/components/SPage/SPage.cjs.map +1 -0
- package/dist/components/SPage/SPage.d.ts +13 -0
- package/dist/components/SPage/SPage.js +84 -0
- package/dist/components/SPage/SPage.js.map +1 -0
- package/dist/components/SPage/index.d.ts +1 -0
- package/dist/components/SPopover/SPopover.cjs +9 -3
- package/dist/components/SPopover/SPopover.cjs.map +1 -1
- package/dist/components/SPopover/SPopover.js +9 -3
- package/dist/components/SPopover/SPopover.js.map +1 -1
- package/dist/components/SPortal/SPortal.cjs +3 -1
- package/dist/components/SPortal/SPortal.cjs.map +1 -1
- package/dist/components/SPortal/SPortal.js +3 -1
- package/dist/components/SPortal/SPortal.js.map +1 -1
- package/dist/components/SSelect/SSelect.cjs +545 -265
- package/dist/components/SSelect/SSelect.cjs.map +1 -1
- package/dist/components/SSelect/SSelect.d.ts +4 -2
- package/dist/components/SSelect/SSelect.js +546 -265
- package/dist/components/SSelect/SSelect.js.map +1 -1
- package/dist/components/STable/STable.cjs +552 -268
- package/dist/components/STable/STable.cjs.map +1 -1
- package/dist/components/STable/STable.js +553 -268
- package/dist/components/STable/STable.js.map +1 -1
- package/dist/components/STextLink/STextLink.cjs +2 -0
- package/dist/components/STextLink/STextLink.cjs.map +1 -1
- package/dist/components/STextLink/STextLink.js +2 -0
- package/dist/components/STextLink/STextLink.js.map +1 -1
- package/dist/components/STextarea/STextarea.cjs +22 -5
- package/dist/components/STextarea/STextarea.cjs.map +1 -1
- package/dist/components/STextarea/STextarea.d.ts +2 -0
- package/dist/components/STextarea/STextarea.js +22 -5
- package/dist/components/STextarea/STextarea.js.map +1 -1
- package/dist/components/SToast/SToast.cjs +9 -3
- package/dist/components/SToast/SToast.cjs.map +1 -1
- package/dist/components/SToast/SToast.js +9 -3
- package/dist/components/SToast/SToast.js.map +1 -1
- package/dist/components/SToast/SToastContainer.cjs +9 -3
- package/dist/components/SToast/SToastContainer.cjs.map +1 -1
- package/dist/components/SToast/SToastContainer.js +9 -3
- package/dist/components/SToast/SToastContainer.js.map +1 -1
- package/dist/components/STooltip/STooltip.cjs +9 -3
- package/dist/components/STooltip/STooltip.cjs.map +1 -1
- package/dist/components/STooltip/STooltip.js +9 -3
- package/dist/components/STooltip/STooltip.js.map +1 -1
- package/dist/index.cjs +840 -199
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +832 -200
- package/dist/index.js.map +1 -1
- package/dist/lib/portal-container.d.ts +7 -0
- package/dist/styles.css +253 -63
- package/dist/theme.css +66 -13
- package/package.json +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { createContext, forwardRef, useState, useRef, useImperativeHandle, useMemo,
|
|
3
|
-
import * as Ariakit from '@ariakit/react';
|
|
2
|
+
import { createContext, forwardRef, useState, useRef, useImperativeHandle, useMemo, useEffect, useContext } from 'react';
|
|
4
3
|
import { clsx } from 'clsx';
|
|
5
4
|
import { extendTailwindMerge } from 'tailwind-merge';
|
|
6
5
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -17,10 +16,110 @@ function cn(...inputs) {
|
|
|
17
16
|
return twMerge(clsx(inputs));
|
|
18
17
|
}
|
|
19
18
|
var PortalContainerContext = createContext(null);
|
|
19
|
+
var InsideModalContext = createContext(false);
|
|
20
20
|
PortalContainerContext.Provider;
|
|
21
|
+
InsideModalContext.Provider;
|
|
21
22
|
function usePortalContainer() {
|
|
22
23
|
return useContext(PortalContainerContext) ?? void 0;
|
|
23
24
|
}
|
|
25
|
+
function useInsideModal() {
|
|
26
|
+
return useContext(InsideModalContext);
|
|
27
|
+
}
|
|
28
|
+
function runRules(value, rules) {
|
|
29
|
+
if (!rules || rules.length === 0) return "";
|
|
30
|
+
for (const rule of rules) {
|
|
31
|
+
const result = rule(value ?? "");
|
|
32
|
+
if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
|
|
33
|
+
}
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
var FormContext = createContext(null);
|
|
37
|
+
function useFormContext() {
|
|
38
|
+
return useContext(FormContext);
|
|
39
|
+
}
|
|
40
|
+
function useFormField(field) {
|
|
41
|
+
const ctx = useFormContext();
|
|
42
|
+
const cbRef = useRef(field);
|
|
43
|
+
cbRef.current = field;
|
|
44
|
+
const name = field.name ?? "";
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!ctx) return;
|
|
47
|
+
return ctx.register({
|
|
48
|
+
name,
|
|
49
|
+
validate: () => cbRef.current.validate(),
|
|
50
|
+
resetValidation: () => cbRef.current.resetValidation()
|
|
51
|
+
});
|
|
52
|
+
}, [ctx, name]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/lib/sanitize-inline-html.ts
|
|
56
|
+
var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
|
|
57
|
+
var ALLOWED_ATTRS_BY_TAG = {
|
|
58
|
+
A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
|
|
59
|
+
};
|
|
60
|
+
var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
|
|
61
|
+
var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
|
|
62
|
+
var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
|
|
63
|
+
"SCRIPT",
|
|
64
|
+
"STYLE",
|
|
65
|
+
"IFRAME",
|
|
66
|
+
"OBJECT",
|
|
67
|
+
"EMBED",
|
|
68
|
+
"META",
|
|
69
|
+
"LINK",
|
|
70
|
+
"BASE",
|
|
71
|
+
"NOSCRIPT",
|
|
72
|
+
// SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
|
|
73
|
+
// 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
|
|
74
|
+
"SVG",
|
|
75
|
+
"MATH",
|
|
76
|
+
"TEMPLATE"
|
|
77
|
+
]);
|
|
78
|
+
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
79
|
+
var sanitizeNode = (node) => {
|
|
80
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
81
|
+
node.remove();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
85
|
+
const element = node;
|
|
86
|
+
const tagName = element.tagName;
|
|
87
|
+
if (DROP_CONTENT_TAGS.has(tagName)) {
|
|
88
|
+
element.remove();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
|
|
92
|
+
if (ALLOWED_INLINE_TAGS.has(tagName)) {
|
|
93
|
+
if (element.hasAttribute("className")) {
|
|
94
|
+
const val = element.getAttribute("className");
|
|
95
|
+
element.removeAttribute("className");
|
|
96
|
+
if (!element.hasAttribute("class")) element.setAttribute("class", val);
|
|
97
|
+
}
|
|
98
|
+
const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
|
|
99
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
100
|
+
if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
|
|
101
|
+
});
|
|
102
|
+
if (tagName === "A") {
|
|
103
|
+
const href = element.getAttribute("href");
|
|
104
|
+
if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
|
|
105
|
+
if (element.hasAttribute("target")) {
|
|
106
|
+
element.setAttribute("rel", "noopener noreferrer");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const parent = element.parentNode;
|
|
112
|
+
if (parent == null) return;
|
|
113
|
+
while (element.firstChild) parent.insertBefore(element.firstChild, element);
|
|
114
|
+
parent.removeChild(element);
|
|
115
|
+
};
|
|
116
|
+
var sanitizeInlineHtml = (value) => {
|
|
117
|
+
if (typeof document === "undefined") return escapeHtml(value);
|
|
118
|
+
const template = document.createElement("template");
|
|
119
|
+
template.innerHTML = value;
|
|
120
|
+
Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
|
|
121
|
+
return template.innerHTML;
|
|
122
|
+
};
|
|
24
123
|
|
|
25
124
|
// src/lib/color.ts
|
|
26
125
|
var COLOR_KEYS = [
|
|
@@ -2973,6 +3072,8 @@ function SGhostButton({
|
|
|
2973
3072
|
tooltipText,
|
|
2974
3073
|
disabled = false,
|
|
2975
3074
|
onClick,
|
|
3075
|
+
anchorClassName,
|
|
3076
|
+
anchorStyle,
|
|
2976
3077
|
className,
|
|
2977
3078
|
style
|
|
2978
3079
|
}) {
|
|
@@ -2995,6 +3096,8 @@ function SGhostButton({
|
|
|
2995
3096
|
};
|
|
2996
3097
|
const s = SIZE[size];
|
|
2997
3098
|
const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
|
|
3099
|
+
const text = (tooltipText ?? "").trim();
|
|
3100
|
+
const hasTooltip = text !== "";
|
|
2998
3101
|
const btn = /* @__PURE__ */ jsxs(
|
|
2999
3102
|
"button",
|
|
3000
3103
|
{
|
|
@@ -3035,13 +3138,13 @@ function SGhostButton({
|
|
|
3035
3138
|
]
|
|
3036
3139
|
}
|
|
3037
3140
|
);
|
|
3038
|
-
|
|
3039
|
-
if (text === "") return btn;
|
|
3141
|
+
if (!hasTooltip) return btn;
|
|
3040
3142
|
return /* @__PURE__ */ jsxs(RPopover2.Root, { open, onOpenChange: setOpen, children: [
|
|
3041
3143
|
/* @__PURE__ */ jsx(RPopover2.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
3042
3144
|
"span",
|
|
3043
3145
|
{
|
|
3044
|
-
className: "inline-flex",
|
|
3146
|
+
className: cn("inline-flex", anchorClassName),
|
|
3147
|
+
style: anchorStyle,
|
|
3045
3148
|
onMouseEnter: openNow,
|
|
3046
3149
|
onMouseLeave: startHide,
|
|
3047
3150
|
onFocus: openNow,
|
|
@@ -3193,32 +3296,6 @@ var STooltip = forwardRef(function STooltip2({
|
|
|
3193
3296
|
] });
|
|
3194
3297
|
});
|
|
3195
3298
|
STooltip.displayName = "STooltip";
|
|
3196
|
-
function runRules(value, rules) {
|
|
3197
|
-
if (!rules || rules.length === 0) return "";
|
|
3198
|
-
for (const rule of rules) {
|
|
3199
|
-
const result = rule(value ?? "");
|
|
3200
|
-
if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
|
|
3201
|
-
}
|
|
3202
|
-
return "";
|
|
3203
|
-
}
|
|
3204
|
-
var FormContext = createContext(null);
|
|
3205
|
-
function useFormContext() {
|
|
3206
|
-
return useContext(FormContext);
|
|
3207
|
-
}
|
|
3208
|
-
function useFormField(field) {
|
|
3209
|
-
const ctx = useFormContext();
|
|
3210
|
-
const cbRef = useRef(field);
|
|
3211
|
-
cbRef.current = field;
|
|
3212
|
-
const name = field.name ?? "";
|
|
3213
|
-
useEffect(() => {
|
|
3214
|
-
if (!ctx) return;
|
|
3215
|
-
return ctx.register({
|
|
3216
|
-
name,
|
|
3217
|
-
validate: () => cbRef.current.validate(),
|
|
3218
|
-
resetValidation: () => cbRef.current.resetValidation()
|
|
3219
|
-
});
|
|
3220
|
-
}, [ctx, name]);
|
|
3221
|
-
}
|
|
3222
3299
|
var FIELD_SIZE = {
|
|
3223
3300
|
sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 16 },
|
|
3224
3301
|
md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 16 }
|
|
@@ -3233,6 +3310,7 @@ var SField = forwardRef(function SField2({
|
|
|
3233
3310
|
name,
|
|
3234
3311
|
label = "",
|
|
3235
3312
|
icon,
|
|
3313
|
+
iconColor,
|
|
3236
3314
|
labelTooltip = "",
|
|
3237
3315
|
labelTooltipProps,
|
|
3238
3316
|
labelWidth,
|
|
@@ -3310,7 +3388,15 @@ var SField = forwardRef(function SField2({
|
|
|
3310
3388
|
lineHeight: `${s.lineHeight}px`
|
|
3311
3389
|
},
|
|
3312
3390
|
children: [
|
|
3313
|
-
icon && /* @__PURE__ */ jsx(
|
|
3391
|
+
icon && /* @__PURE__ */ jsx(
|
|
3392
|
+
SIcon,
|
|
3393
|
+
{
|
|
3394
|
+
name: icon,
|
|
3395
|
+
size: s.icon,
|
|
3396
|
+
color: iconColor,
|
|
3397
|
+
style: { marginRight: s.labelGap }
|
|
3398
|
+
}
|
|
3399
|
+
),
|
|
3314
3400
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
|
|
3315
3401
|
(labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsx(
|
|
3316
3402
|
STooltip,
|
|
@@ -3360,7 +3446,7 @@ var SField = forwardRef(function SField2({
|
|
|
3360
3446
|
borderBottomLeftRadius: s.radius - 1
|
|
3361
3447
|
},
|
|
3362
3448
|
children: [
|
|
3363
|
-
icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon }),
|
|
3449
|
+
icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
|
|
3364
3450
|
effectiveAddonLabel
|
|
3365
3451
|
]
|
|
3366
3452
|
}
|
|
@@ -3375,6 +3461,77 @@ var SField = forwardRef(function SField2({
|
|
|
3375
3461
|
}
|
|
3376
3462
|
);
|
|
3377
3463
|
});
|
|
3464
|
+
function SPortal({
|
|
3465
|
+
open,
|
|
3466
|
+
onClose,
|
|
3467
|
+
onOpenChange,
|
|
3468
|
+
anchorRef,
|
|
3469
|
+
placement = "bottom",
|
|
3470
|
+
align = "start",
|
|
3471
|
+
offset = 4,
|
|
3472
|
+
alignOffset = 0,
|
|
3473
|
+
fit = false,
|
|
3474
|
+
maxHeight,
|
|
3475
|
+
autoClose = false,
|
|
3476
|
+
persistent = false,
|
|
3477
|
+
viewportPadding = 0,
|
|
3478
|
+
onOpenAutoFocus,
|
|
3479
|
+
onCloseAutoFocus,
|
|
3480
|
+
children,
|
|
3481
|
+
className,
|
|
3482
|
+
style,
|
|
3483
|
+
ariaLabel
|
|
3484
|
+
}) {
|
|
3485
|
+
const portalContainer = usePortalContainer();
|
|
3486
|
+
return /* @__PURE__ */ jsxs(
|
|
3487
|
+
RPopover2.Root,
|
|
3488
|
+
{
|
|
3489
|
+
open,
|
|
3490
|
+
onOpenChange: (next) => {
|
|
3491
|
+
onOpenChange?.(next);
|
|
3492
|
+
if (!next) onClose?.();
|
|
3493
|
+
},
|
|
3494
|
+
children: [
|
|
3495
|
+
/* @__PURE__ */ jsx(RPopover2.Anchor, { virtualRef: anchorRef }),
|
|
3496
|
+
/* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
|
|
3497
|
+
RPopover2.Content,
|
|
3498
|
+
{
|
|
3499
|
+
side: placement,
|
|
3500
|
+
align,
|
|
3501
|
+
sideOffset: offset,
|
|
3502
|
+
alignOffset,
|
|
3503
|
+
collisionPadding: viewportPadding,
|
|
3504
|
+
"aria-label": ariaLabel,
|
|
3505
|
+
onPointerDownOutside: (e) => {
|
|
3506
|
+
if (persistent || anchorRef.current?.contains(e.target)) {
|
|
3507
|
+
e.preventDefault();
|
|
3508
|
+
}
|
|
3509
|
+
},
|
|
3510
|
+
onFocusOutside: (e) => {
|
|
3511
|
+
if (persistent || anchorRef.current?.contains(e.target)) {
|
|
3512
|
+
e.preventDefault();
|
|
3513
|
+
}
|
|
3514
|
+
},
|
|
3515
|
+
onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
|
|
3516
|
+
onOpenAutoFocus,
|
|
3517
|
+
onCloseAutoFocus,
|
|
3518
|
+
onClick: autoClose ? () => onClose?.() : void 0,
|
|
3519
|
+
className: cn(
|
|
3520
|
+
"z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm",
|
|
3521
|
+
maxHeight != null && "overflow-y-auto",
|
|
3522
|
+
fit && "min-w-(--radix-popover-trigger-width)",
|
|
3523
|
+
FLOATING_SLIDE_ANIM,
|
|
3524
|
+
className
|
|
3525
|
+
),
|
|
3526
|
+
style: { maxHeight, ...style },
|
|
3527
|
+
children
|
|
3528
|
+
}
|
|
3529
|
+
) })
|
|
3530
|
+
]
|
|
3531
|
+
}
|
|
3532
|
+
);
|
|
3533
|
+
}
|
|
3534
|
+
SPortal.displayName = "SPortal";
|
|
3378
3535
|
|
|
3379
3536
|
// src/components/SSelect/select.config.ts
|
|
3380
3537
|
function flattenOptions(options, depth = 1) {
|
|
@@ -3409,75 +3566,6 @@ function flattenOptions(options, depth = 1) {
|
|
|
3409
3566
|
var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
|
|
3410
3567
|
var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
|
|
3411
3568
|
var extractText = (html) => html.replace(/<[^>]*>/g, "");
|
|
3412
|
-
|
|
3413
|
-
// src/lib/sanitize-inline-html.ts
|
|
3414
|
-
var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
|
|
3415
|
-
var ALLOWED_ATTRS_BY_TAG = {
|
|
3416
|
-
A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
|
|
3417
|
-
};
|
|
3418
|
-
var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
|
|
3419
|
-
var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
|
|
3420
|
-
var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
|
|
3421
|
-
"SCRIPT",
|
|
3422
|
-
"STYLE",
|
|
3423
|
-
"IFRAME",
|
|
3424
|
-
"OBJECT",
|
|
3425
|
-
"EMBED",
|
|
3426
|
-
"META",
|
|
3427
|
-
"LINK",
|
|
3428
|
-
"BASE",
|
|
3429
|
-
"NOSCRIPT",
|
|
3430
|
-
// SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
|
|
3431
|
-
// 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
|
|
3432
|
-
"SVG",
|
|
3433
|
-
"MATH",
|
|
3434
|
-
"TEMPLATE"
|
|
3435
|
-
]);
|
|
3436
|
-
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3437
|
-
var sanitizeNode = (node) => {
|
|
3438
|
-
if (node.nodeType === Node.COMMENT_NODE) {
|
|
3439
|
-
node.remove();
|
|
3440
|
-
return;
|
|
3441
|
-
}
|
|
3442
|
-
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
3443
|
-
const element = node;
|
|
3444
|
-
const tagName = element.tagName;
|
|
3445
|
-
if (DROP_CONTENT_TAGS.has(tagName)) {
|
|
3446
|
-
element.remove();
|
|
3447
|
-
return;
|
|
3448
|
-
}
|
|
3449
|
-
Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
|
|
3450
|
-
if (ALLOWED_INLINE_TAGS.has(tagName)) {
|
|
3451
|
-
if (element.hasAttribute("className")) {
|
|
3452
|
-
const val = element.getAttribute("className");
|
|
3453
|
-
element.removeAttribute("className");
|
|
3454
|
-
if (!element.hasAttribute("class")) element.setAttribute("class", val);
|
|
3455
|
-
}
|
|
3456
|
-
const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
|
|
3457
|
-
Array.from(element.attributes).forEach((attr) => {
|
|
3458
|
-
if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
|
|
3459
|
-
});
|
|
3460
|
-
if (tagName === "A") {
|
|
3461
|
-
const href = element.getAttribute("href");
|
|
3462
|
-
if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
|
|
3463
|
-
if (element.hasAttribute("target")) {
|
|
3464
|
-
element.setAttribute("rel", "noopener noreferrer");
|
|
3465
|
-
}
|
|
3466
|
-
}
|
|
3467
|
-
return;
|
|
3468
|
-
}
|
|
3469
|
-
const parent = element.parentNode;
|
|
3470
|
-
if (parent == null) return;
|
|
3471
|
-
while (element.firstChild) parent.insertBefore(element.firstChild, element);
|
|
3472
|
-
parent.removeChild(element);
|
|
3473
|
-
};
|
|
3474
|
-
var sanitizeInlineHtml = (value) => {
|
|
3475
|
-
if (typeof document === "undefined") return escapeHtml(value);
|
|
3476
|
-
const template = document.createElement("template");
|
|
3477
|
-
template.innerHTML = value;
|
|
3478
|
-
Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
|
|
3479
|
-
return template.innerHTML;
|
|
3480
|
-
};
|
|
3481
3569
|
var SEARCH_THRESHOLD = 11;
|
|
3482
3570
|
var SSelect = forwardRef(function SSelect2({
|
|
3483
3571
|
value,
|
|
@@ -3501,6 +3589,7 @@ var SSelect = forwardRef(function SSelect2({
|
|
|
3501
3589
|
label,
|
|
3502
3590
|
labelWidth,
|
|
3503
3591
|
icon,
|
|
3592
|
+
iconColor,
|
|
3504
3593
|
labelTooltip,
|
|
3505
3594
|
addonLabel,
|
|
3506
3595
|
addonAlign,
|
|
@@ -3512,18 +3601,15 @@ var SSelect = forwardRef(function SSelect2({
|
|
|
3512
3601
|
}, ref) {
|
|
3513
3602
|
const multi = type === "multi" || type === "multi_depth";
|
|
3514
3603
|
const portalContainer = usePortalContainer();
|
|
3604
|
+
const insideModal = useInsideModal();
|
|
3515
3605
|
const [search, setSearch] = useState("");
|
|
3516
3606
|
const [ruleError, setRuleError] = useState("");
|
|
3517
3607
|
const [open, setOpen] = useState(false);
|
|
3608
|
+
const [pendingOpen, setPendingOpen] = useState(false);
|
|
3609
|
+
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
3518
3610
|
const triggerRef = useRef(null);
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
() => ({
|
|
3522
|
-
focus: () => triggerRef.current?.focus(),
|
|
3523
|
-
open: () => setOpen(true)
|
|
3524
|
-
}),
|
|
3525
|
-
[]
|
|
3526
|
-
);
|
|
3611
|
+
const searchInputRef = useRef(null);
|
|
3612
|
+
const listRef = useRef(null);
|
|
3527
3613
|
const flat = useMemo(() => flattenOptions(options), [options]);
|
|
3528
3614
|
const selectable = useMemo(() => selectableOptions(flat), [flat]);
|
|
3529
3615
|
const hasLeaf = useMemo(() => flat.some((o) => !o.isGroup), [flat]);
|
|
@@ -3541,6 +3627,46 @@ var SSelect = forwardRef(function SSelect2({
|
|
|
3541
3627
|
const arr = Array.isArray(value) ? value : [value];
|
|
3542
3628
|
return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
|
|
3543
3629
|
}, [value]);
|
|
3630
|
+
const selectedRef = useRef(selected);
|
|
3631
|
+
selectedRef.current = selected;
|
|
3632
|
+
const emit = (next) => {
|
|
3633
|
+
if (!onValueChange) return;
|
|
3634
|
+
const mapped = next.map(
|
|
3635
|
+
(v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
|
|
3636
|
+
);
|
|
3637
|
+
onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
|
|
3638
|
+
};
|
|
3639
|
+
const commitOpen = (next) => {
|
|
3640
|
+
setOpen(next);
|
|
3641
|
+
onOpenChange?.(next);
|
|
3642
|
+
if (!next) {
|
|
3643
|
+
setPendingOpen(false);
|
|
3644
|
+
setSearch("");
|
|
3645
|
+
if (rules && rules.length > 0) {
|
|
3646
|
+
setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
};
|
|
3650
|
+
const requestOpen = (next) => {
|
|
3651
|
+
if (next && insideModal && portalContainer == null) {
|
|
3652
|
+
setPendingOpen(true);
|
|
3653
|
+
return;
|
|
3654
|
+
}
|
|
3655
|
+
commitOpen(next);
|
|
3656
|
+
};
|
|
3657
|
+
useEffect(() => {
|
|
3658
|
+
if (!insideModal || portalContainer == null || !pendingOpen) return;
|
|
3659
|
+
setPendingOpen(false);
|
|
3660
|
+
commitOpen(true);
|
|
3661
|
+
}, [insideModal, pendingOpen, portalContainer]);
|
|
3662
|
+
useImperativeHandle(
|
|
3663
|
+
ref,
|
|
3664
|
+
() => ({
|
|
3665
|
+
focus: () => triggerRef.current?.focus(),
|
|
3666
|
+
open: () => requestOpen(true)
|
|
3667
|
+
}),
|
|
3668
|
+
[insideModal, portalContainer]
|
|
3669
|
+
);
|
|
3544
3670
|
useFormField({
|
|
3545
3671
|
name,
|
|
3546
3672
|
validate: () => {
|
|
@@ -3550,13 +3676,6 @@ var SSelect = forwardRef(function SSelect2({
|
|
|
3550
3676
|
},
|
|
3551
3677
|
resetValidation: () => setRuleError("")
|
|
3552
3678
|
});
|
|
3553
|
-
const emit = (next) => {
|
|
3554
|
-
if (!onValueChange) return;
|
|
3555
|
-
const mapped = next.map(
|
|
3556
|
-
(v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
|
|
3557
|
-
);
|
|
3558
|
-
onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
|
|
3559
|
-
};
|
|
3560
3679
|
const filtered = useMemo(() => {
|
|
3561
3680
|
if (!showSearch || search.trim() === "") return flat;
|
|
3562
3681
|
const q = search.trim().toLowerCase();
|
|
@@ -3567,6 +3686,16 @@ var SSelect = forwardRef(function SSelect2({
|
|
|
3567
3686
|
const allValues = selectable.map((o) => o.value);
|
|
3568
3687
|
const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
|
|
3569
3688
|
const hasItems = filtered.some((o) => !o.isGroup);
|
|
3689
|
+
const navigableItems = useMemo(() => {
|
|
3690
|
+
const items = [];
|
|
3691
|
+
if (multi && useSelectAll && allValues.length > 0) {
|
|
3692
|
+
items.push({ value: "__select_all__", isSelectAll: true });
|
|
3693
|
+
}
|
|
3694
|
+
filtered.forEach((o) => {
|
|
3695
|
+
if (!o.isGroup && !o.disabled) items.push({ value: o.value });
|
|
3696
|
+
});
|
|
3697
|
+
return items;
|
|
3698
|
+
}, [allValues.length, filtered, multi, useSelectAll]);
|
|
3570
3699
|
const triggerText = (() => {
|
|
3571
3700
|
if (selected.length === 0) return placeholder;
|
|
3572
3701
|
if (multi) {
|
|
@@ -3576,26 +3705,119 @@ var SSelect = forwardRef(function SSelect2({
|
|
|
3576
3705
|
return labelByValue.get(selected[0]) ?? selected[0];
|
|
3577
3706
|
})();
|
|
3578
3707
|
const isPlaceholder = selected.length === 0;
|
|
3579
|
-
const
|
|
3580
|
-
|
|
3708
|
+
const handleSelect = (nextValue) => {
|
|
3709
|
+
if (multi) {
|
|
3710
|
+
const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
|
|
3711
|
+
emit(next);
|
|
3712
|
+
return;
|
|
3713
|
+
}
|
|
3714
|
+
emit([nextValue]);
|
|
3715
|
+
commitOpen(false);
|
|
3716
|
+
};
|
|
3717
|
+
const handleSelectAll = () => {
|
|
3718
|
+
emit(allSelected ? [] : allValues);
|
|
3719
|
+
};
|
|
3720
|
+
useEffect(() => {
|
|
3721
|
+
if (!open) {
|
|
3722
|
+
setFocusedIndex(-1);
|
|
3723
|
+
return;
|
|
3724
|
+
}
|
|
3725
|
+
setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
|
|
3726
|
+
}, [open, navigableItems.length]);
|
|
3727
|
+
useEffect(() => {
|
|
3728
|
+
if (!open) return;
|
|
3729
|
+
setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
|
|
3730
|
+
}, [search, open, navigableItems.length]);
|
|
3731
|
+
useEffect(() => {
|
|
3732
|
+
if (!open || focusedIndex < 0) return;
|
|
3733
|
+
const list = listRef.current;
|
|
3734
|
+
const focusedEl = list?.querySelector('[data-select-focused="true"]');
|
|
3735
|
+
if (!list || !focusedEl) return;
|
|
3736
|
+
const listRect = list.getBoundingClientRect();
|
|
3737
|
+
const itemRect = focusedEl.getBoundingClientRect();
|
|
3738
|
+
if (itemRect.top < listRect.top) {
|
|
3739
|
+
list.scrollTop += itemRect.top - listRect.top;
|
|
3740
|
+
} else if (itemRect.bottom > listRect.bottom) {
|
|
3741
|
+
list.scrollTop += itemRect.bottom - listRect.bottom;
|
|
3742
|
+
}
|
|
3743
|
+
}, [focusedIndex, open]);
|
|
3744
|
+
const moveFocusedIndex = (direction) => {
|
|
3745
|
+
if (!open || navigableItems.length === 0) return;
|
|
3746
|
+
setFocusedIndex((current) => {
|
|
3747
|
+
if (direction === "next") {
|
|
3748
|
+
return current < 0 ? 0 : (current + 1) % navigableItems.length;
|
|
3749
|
+
}
|
|
3750
|
+
return current <= 0 ? navigableItems.length - 1 : current - 1;
|
|
3751
|
+
});
|
|
3752
|
+
};
|
|
3753
|
+
const selectFocusedItem = () => {
|
|
3754
|
+
if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
|
|
3755
|
+
const focused = navigableItems[focusedIndex];
|
|
3756
|
+
if (focused.isSelectAll) {
|
|
3757
|
+
handleSelectAll();
|
|
3758
|
+
return;
|
|
3759
|
+
}
|
|
3760
|
+
handleSelect(focused.value);
|
|
3761
|
+
};
|
|
3762
|
+
const handleKeyDown = (event) => {
|
|
3763
|
+
if (!open) {
|
|
3764
|
+
if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
|
|
3765
|
+
event.preventDefault();
|
|
3766
|
+
requestOpen(true);
|
|
3767
|
+
}
|
|
3768
|
+
return;
|
|
3769
|
+
}
|
|
3770
|
+
if (event.key === "ArrowDown") {
|
|
3771
|
+
event.preventDefault();
|
|
3772
|
+
moveFocusedIndex("next");
|
|
3773
|
+
return;
|
|
3774
|
+
}
|
|
3775
|
+
if (event.key === "ArrowUp") {
|
|
3776
|
+
event.preventDefault();
|
|
3777
|
+
moveFocusedIndex("prev");
|
|
3778
|
+
return;
|
|
3779
|
+
}
|
|
3780
|
+
if (event.key === "Enter") {
|
|
3781
|
+
event.preventDefault();
|
|
3782
|
+
selectFocusedItem();
|
|
3783
|
+
return;
|
|
3784
|
+
}
|
|
3785
|
+
if (event.key === "Escape") {
|
|
3786
|
+
event.preventDefault();
|
|
3787
|
+
commitOpen(false);
|
|
3788
|
+
}
|
|
3789
|
+
};
|
|
3790
|
+
return /* @__PURE__ */ jsxs(
|
|
3791
|
+
SField,
|
|
3581
3792
|
{
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3793
|
+
label,
|
|
3794
|
+
labelWidth,
|
|
3795
|
+
icon,
|
|
3796
|
+
iconColor,
|
|
3797
|
+
labelTooltip,
|
|
3798
|
+
labelTooltipProps,
|
|
3799
|
+
addonLabel,
|
|
3800
|
+
addonAlign,
|
|
3801
|
+
hint,
|
|
3802
|
+
error: error || ruleError !== "",
|
|
3803
|
+
status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
|
|
3804
|
+
errorMessage: ruleError !== "" ? ruleError : errorMessage,
|
|
3805
|
+
disabled: effectiveDisabled,
|
|
3806
|
+
width,
|
|
3807
|
+
className,
|
|
3808
|
+
style,
|
|
3593
3809
|
children: [
|
|
3810
|
+
name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected.join(",") }),
|
|
3594
3811
|
/* @__PURE__ */ jsxs(
|
|
3595
|
-
|
|
3812
|
+
"button",
|
|
3596
3813
|
{
|
|
3597
3814
|
ref: triggerRef,
|
|
3815
|
+
type: "button",
|
|
3598
3816
|
disabled: effectiveDisabled,
|
|
3817
|
+
"aria-haspopup": "listbox",
|
|
3818
|
+
"aria-expanded": open,
|
|
3819
|
+
onClick: () => requestOpen(!open),
|
|
3820
|
+
onKeyDown: handleKeyDown,
|
|
3599
3821
|
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",
|
|
3600
3822
|
style: {
|
|
3601
3823
|
color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
|
|
@@ -3620,178 +3842,237 @@ var SSelect = forwardRef(function SSelect2({
|
|
|
3620
3842
|
]
|
|
3621
3843
|
}
|
|
3622
3844
|
),
|
|
3623
|
-
/* @__PURE__ */
|
|
3624
|
-
|
|
3845
|
+
/* @__PURE__ */ jsx(
|
|
3846
|
+
SPortal,
|
|
3625
3847
|
{
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3848
|
+
open,
|
|
3849
|
+
onClose: () => commitOpen(false),
|
|
3850
|
+
onOpenChange: (next) => {
|
|
3851
|
+
if (next) requestOpen(true);
|
|
3852
|
+
},
|
|
3853
|
+
anchorRef: triggerRef,
|
|
3854
|
+
placement: "bottom",
|
|
3855
|
+
align: "start",
|
|
3856
|
+
offset: 4,
|
|
3857
|
+
fit: !dropdownWidth,
|
|
3858
|
+
onOpenAutoFocus: (event) => {
|
|
3859
|
+
if (!showSearch) {
|
|
3860
|
+
event.preventDefault();
|
|
3861
|
+
return;
|
|
3862
|
+
}
|
|
3863
|
+
event.preventDefault();
|
|
3864
|
+
requestAnimationFrame(() => searchInputRef.current?.focus());
|
|
3865
|
+
},
|
|
3866
|
+
onCloseAutoFocus: (event) => {
|
|
3867
|
+
event.preventDefault();
|
|
3868
|
+
triggerRef.current?.focus();
|
|
3869
|
+
},
|
|
3870
|
+
className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
|
|
3631
3871
|
style: {
|
|
3632
|
-
maxHeight: dropdownHeight,
|
|
3633
3872
|
...dropdownWidth ? { width: dropdownWidth } : {},
|
|
3634
3873
|
maxWidth: maxDropdownWidth
|
|
3635
3874
|
},
|
|
3636
|
-
children:
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3875
|
+
children: /* @__PURE__ */ jsxs(
|
|
3876
|
+
"div",
|
|
3877
|
+
{
|
|
3878
|
+
ref: listRef,
|
|
3879
|
+
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)]",
|
|
3880
|
+
style: { maxHeight: dropdownHeight },
|
|
3881
|
+
onMouseLeave: () => setFocusedIndex(-1),
|
|
3882
|
+
children: [
|
|
3883
|
+
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: [
|
|
3884
|
+
/* @__PURE__ */ jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
|
|
3885
|
+
/* @__PURE__ */ jsx(
|
|
3886
|
+
"input",
|
|
3887
|
+
{
|
|
3888
|
+
ref: searchInputRef,
|
|
3889
|
+
value: search,
|
|
3890
|
+
onChange: (e) => setSearch(e.target.value),
|
|
3891
|
+
onKeyDown: handleKeyDown,
|
|
3892
|
+
placeholder: "\uAC80\uC0C9",
|
|
3893
|
+
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)]"
|
|
3894
|
+
}
|
|
3895
|
+
),
|
|
3896
|
+
search !== "" && /* @__PURE__ */ jsx(
|
|
3897
|
+
SGhostButton,
|
|
3898
|
+
{
|
|
3899
|
+
icon: "close",
|
|
3900
|
+
size: "xxs",
|
|
3901
|
+
intent: "default",
|
|
3902
|
+
ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
|
|
3903
|
+
onClick: () => setSearch(""),
|
|
3904
|
+
className: "shrink-0"
|
|
3905
|
+
}
|
|
3906
|
+
)
|
|
3907
|
+
] }) }),
|
|
3908
|
+
hasItems ? /* @__PURE__ */ jsx(
|
|
3909
|
+
ItemList,
|
|
3910
|
+
{
|
|
3911
|
+
items: filtered,
|
|
3912
|
+
multi,
|
|
3913
|
+
selected,
|
|
3914
|
+
focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
|
|
3915
|
+
showSelectAll: multi && useSelectAll && allValues.length > 0,
|
|
3916
|
+
selectAllLabel: allSelectedLabel,
|
|
3917
|
+
selectAllSelected: allSelected,
|
|
3918
|
+
onSelectAll: handleSelectAll,
|
|
3919
|
+
onSelect: handleSelect,
|
|
3920
|
+
onHoverValue: (value2) => {
|
|
3921
|
+
if (value2 == null) {
|
|
3922
|
+
setFocusedIndex(-1);
|
|
3923
|
+
return;
|
|
3924
|
+
}
|
|
3925
|
+
const index = navigableItems.findIndex(
|
|
3926
|
+
(item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
|
|
3927
|
+
);
|
|
3928
|
+
if (index >= 0) setFocusedIndex(index);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
) : /* @__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." })
|
|
3932
|
+
]
|
|
3933
|
+
}
|
|
3934
|
+
)
|
|
3674
3935
|
}
|
|
3675
3936
|
)
|
|
3676
3937
|
]
|
|
3677
3938
|
}
|
|
3678
3939
|
);
|
|
3679
|
-
const control = showSearch ? /* @__PURE__ */ jsx(Ariakit.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
|
|
3680
|
-
return /* @__PURE__ */ jsxs(
|
|
3681
|
-
SField,
|
|
3682
|
-
{
|
|
3683
|
-
label,
|
|
3684
|
-
labelWidth,
|
|
3685
|
-
icon,
|
|
3686
|
-
labelTooltip,
|
|
3687
|
-
labelTooltipProps,
|
|
3688
|
-
addonLabel,
|
|
3689
|
-
addonAlign,
|
|
3690
|
-
hint,
|
|
3691
|
-
error: error || ruleError !== "",
|
|
3692
|
-
status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
|
|
3693
|
-
errorMessage: ruleError !== "" ? ruleError : errorMessage,
|
|
3694
|
-
disabled: effectiveDisabled,
|
|
3695
|
-
width,
|
|
3696
|
-
className,
|
|
3697
|
-
style,
|
|
3698
|
-
children: [
|
|
3699
|
-
name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected.join(",") }),
|
|
3700
|
-
control
|
|
3701
|
-
]
|
|
3702
|
-
}
|
|
3703
|
-
);
|
|
3704
3940
|
});
|
|
3705
3941
|
SSelect.displayName = "SSelect";
|
|
3706
3942
|
function ItemList({
|
|
3707
3943
|
items,
|
|
3708
3944
|
multi,
|
|
3709
3945
|
selected,
|
|
3710
|
-
|
|
3946
|
+
focusedValue,
|
|
3947
|
+
showSelectAll,
|
|
3948
|
+
selectAllLabel,
|
|
3949
|
+
selectAllSelected,
|
|
3950
|
+
onSelectAll,
|
|
3951
|
+
onSelect,
|
|
3952
|
+
onHoverValue
|
|
3711
3953
|
}) {
|
|
3712
3954
|
const itemBase = cn(
|
|
3713
|
-
|
|
3714
|
-
"group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
|
|
3955
|
+
"group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
|
|
3715
3956
|
"text-[color:var(--cmp-listItem-content-default)]",
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
);
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
if (o.isGroup) {
|
|
3723
|
-
const isDepth1 = o.depth === 1;
|
|
3724
|
-
return /* @__PURE__ */ jsx(
|
|
3725
|
-
"div",
|
|
3726
|
-
{
|
|
3727
|
-
className: cn(
|
|
3728
|
-
"select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
|
|
3729
|
-
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",
|
|
3730
|
-
// 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
|
|
3731
|
-
// 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
|
|
3732
|
-
o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
|
|
3733
|
-
o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
|
|
3734
|
-
),
|
|
3735
|
-
style: { paddingLeft: padLeft },
|
|
3736
|
-
children: o.label
|
|
3737
|
-
},
|
|
3738
|
-
o.value
|
|
3739
|
-
);
|
|
3740
|
-
}
|
|
3741
|
-
const isSelected = selected.includes(o.value);
|
|
3742
|
-
return /* @__PURE__ */ jsxs(
|
|
3743
|
-
Ariakit.SelectItem,
|
|
3957
|
+
"disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
|
|
3958
|
+
);
|
|
3959
|
+
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)]";
|
|
3960
|
+
return /* @__PURE__ */ jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
|
|
3961
|
+
showSelectAll && /* @__PURE__ */ jsxs(
|
|
3962
|
+
"button",
|
|
3744
3963
|
{
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3964
|
+
type: "button",
|
|
3965
|
+
"data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
|
|
3966
|
+
"data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
|
|
3748
3967
|
className: cn(
|
|
3749
3968
|
itemBase,
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
|
|
3757
|
-
render: useSearch ? /* @__PURE__ */ jsx(Ariakit.ComboboxItem, {}) : void 0,
|
|
3969
|
+
focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
|
|
3970
|
+
),
|
|
3971
|
+
style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
|
|
3972
|
+
onMouseEnter: () => onHoverValue("__select_all__"),
|
|
3973
|
+
onMouseLeave: () => onHoverValue(null),
|
|
3974
|
+
onClick: onSelectAll,
|
|
3758
3975
|
children: [
|
|
3759
|
-
multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
|
|
3760
3976
|
/* @__PURE__ */ jsx(
|
|
3761
3977
|
"span",
|
|
3762
3978
|
{
|
|
3763
3979
|
className: cn(
|
|
3764
3980
|
"flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
|
|
3765
|
-
|
|
3766
|
-
// 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
|
|
3767
|
-
"cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
|
|
3768
|
-
) : cn(
|
|
3769
|
-
"group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
|
|
3770
|
-
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"
|
|
3771
|
-
)
|
|
3981
|
+
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)]"
|
|
3772
3982
|
),
|
|
3773
|
-
children:
|
|
3983
|
+
children: selectAllSelected && /* @__PURE__ */ jsx(
|
|
3774
3984
|
SIcon,
|
|
3775
3985
|
{
|
|
3776
3986
|
name: "checkboxCheck",
|
|
3777
3987
|
size: 12,
|
|
3778
|
-
color:
|
|
3988
|
+
color: "var(--cmp-checkbox-checked-icon-default)"
|
|
3779
3989
|
}
|
|
3780
3990
|
)
|
|
3781
3991
|
}
|
|
3782
3992
|
),
|
|
3783
|
-
|
|
3784
|
-
"span",
|
|
3785
|
-
{
|
|
3786
|
-
className: "flex-1 truncate",
|
|
3787
|
-
dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
|
|
3788
|
-
}
|
|
3789
|
-
) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.label })
|
|
3993
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
|
|
3790
3994
|
]
|
|
3791
|
-
}
|
|
3792
|
-
|
|
3793
|
-
)
|
|
3794
|
-
|
|
3995
|
+
}
|
|
3996
|
+
),
|
|
3997
|
+
items.map((o) => {
|
|
3998
|
+
const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
|
|
3999
|
+
if (o.isGroup) {
|
|
4000
|
+
const isDepth1 = o.depth === 1;
|
|
4001
|
+
return /* @__PURE__ */ jsx(
|
|
4002
|
+
"div",
|
|
4003
|
+
{
|
|
4004
|
+
className: cn(
|
|
4005
|
+
"select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
|
|
4006
|
+
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",
|
|
4007
|
+
// 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
|
|
4008
|
+
// 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
|
|
4009
|
+
o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
|
|
4010
|
+
o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
|
|
4011
|
+
),
|
|
4012
|
+
style: { paddingLeft: padLeft },
|
|
4013
|
+
children: o.label
|
|
4014
|
+
},
|
|
4015
|
+
o.value
|
|
4016
|
+
);
|
|
4017
|
+
}
|
|
4018
|
+
const isSelected = selected.includes(o.value);
|
|
4019
|
+
return /* @__PURE__ */ jsxs(
|
|
4020
|
+
"button",
|
|
4021
|
+
{
|
|
4022
|
+
type: "button",
|
|
4023
|
+
disabled: o.disabled,
|
|
4024
|
+
role: "option",
|
|
4025
|
+
"aria-selected": isSelected,
|
|
4026
|
+
"data-select-focused": focusedValue === o.value ? "true" : void 0,
|
|
4027
|
+
"data-select-active": focusedValue === o.value ? "true" : void 0,
|
|
4028
|
+
className: cn(
|
|
4029
|
+
itemBase,
|
|
4030
|
+
isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
|
|
4031
|
+
focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
|
|
4032
|
+
// 비활성 항목은 hover/focus 스타일 대상에서 제외한다.
|
|
4033
|
+
// (:hover 는 disabled 버튼에도 매칭되므로 클래스 자체를 붙이지 않아야 한다)
|
|
4034
|
+
!o.disabled && activeClass
|
|
4035
|
+
),
|
|
4036
|
+
style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
|
|
4037
|
+
onMouseEnter: () => onHoverValue(o.value),
|
|
4038
|
+
onMouseLeave: () => onHoverValue(null),
|
|
4039
|
+
onClick: () => onSelect(o.value),
|
|
4040
|
+
children: [
|
|
4041
|
+
multi && /* @__PURE__ */ jsx(
|
|
4042
|
+
"span",
|
|
4043
|
+
{
|
|
4044
|
+
className: cn(
|
|
4045
|
+
"flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
|
|
4046
|
+
o.disabled ? (
|
|
4047
|
+
// 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
|
|
4048
|
+
"cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
|
|
4049
|
+
) : cn(
|
|
4050
|
+
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)]"
|
|
4051
|
+
)
|
|
4052
|
+
),
|
|
4053
|
+
children: isSelected && /* @__PURE__ */ jsx(
|
|
4054
|
+
SIcon,
|
|
4055
|
+
{
|
|
4056
|
+
name: "checkboxCheck",
|
|
4057
|
+
size: 12,
|
|
4058
|
+
color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
|
|
4059
|
+
}
|
|
4060
|
+
)
|
|
4061
|
+
}
|
|
4062
|
+
),
|
|
4063
|
+
isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
|
|
4064
|
+
"span",
|
|
4065
|
+
{
|
|
4066
|
+
className: "flex-1 truncate",
|
|
4067
|
+
dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
|
|
4068
|
+
}
|
|
4069
|
+
) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.label })
|
|
4070
|
+
]
|
|
4071
|
+
},
|
|
4072
|
+
o.value
|
|
4073
|
+
);
|
|
4074
|
+
})
|
|
4075
|
+
] });
|
|
3795
4076
|
}
|
|
3796
4077
|
|
|
3797
4078
|
export { SSelect };
|