@ladder-ui/select 0.3.0 → 0.4.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/index.d.ts +3 -2
- package/dist/index.js +119 -271
- package/dist/index.mjs +119 -272
- package/dist/select.css +4 -19
- package/dist/select.d.ts +39 -96
- package/dist/select.vars.css +14 -14
- package/package.json +11 -28
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { Select,
|
|
2
|
-
export type { SelectProps,
|
|
1
|
+
export { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectLabel, SelectSeparator, } from "./select";
|
|
2
|
+
export type { SelectProps, SelectTriggerProps, SelectValueProps, SelectItemProps, } from "./select";
|
|
3
|
+
export { Select as default } from "./select";
|
package/dist/index.js
CHANGED
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
3
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
6
|
var react = require('react');
|
|
5
7
|
var reactDom = require('react-dom');
|
|
6
|
-
var
|
|
8
|
+
var core = require('@ladder-ui/core');
|
|
9
|
+
var primitives = require('@ladder-ui/primitives');
|
|
7
10
|
|
|
8
|
-
// useLayoutEffect runs synchronously after DOM mutations but before the browser
|
|
9
|
-
// paints. This eliminates the double-paint flash that useEffect causes for
|
|
10
|
-
// position-critical work like dropdown placement.
|
|
11
|
-
// Falls back to useEffect in SSR environments (no DOM = no layout to measure).
|
|
12
11
|
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
|
|
13
|
-
// ───
|
|
14
|
-
function ChevronDownIcon({ className }) {
|
|
15
|
-
return (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: className, children: jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" }) }));
|
|
16
|
-
}
|
|
17
|
-
function ChevronUpIcon({ className }) {
|
|
18
|
-
return (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: className, children: jsxRuntime.jsx("path", { d: "m18 15-6-6-6 6" }) }));
|
|
12
|
+
// ─── Icons ──────────────────────────────────────────────────────────────────
|
|
13
|
+
function ChevronDownIcon({ className, style }) {
|
|
14
|
+
return (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: className, style: style, children: jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" }) }));
|
|
19
15
|
}
|
|
20
16
|
function CheckIcon({ className }) {
|
|
21
17
|
return (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: className, children: jsxRuntime.jsx("path", { d: "M20 6 9 17l-5-5" }) }));
|
|
22
18
|
}
|
|
23
|
-
// ─── Label
|
|
24
|
-
//
|
|
25
|
-
// We scan the JSX children tree in Select root to build a value→label map.
|
|
26
|
-
// This works even when SelectContent renders null (closed state), because the
|
|
27
|
-
// JSX element tree passed as `children` always contains the full structure.
|
|
19
|
+
// ─── Helper: Label Extraction ────────────────────────────────────────────────
|
|
28
20
|
function extractTextContent(children) {
|
|
29
21
|
if (typeof children === "string")
|
|
30
22
|
return children;
|
|
@@ -32,303 +24,160 @@ function extractTextContent(children) {
|
|
|
32
24
|
return String(children);
|
|
33
25
|
if (Array.isArray(children))
|
|
34
26
|
return children.map(extractTextContent).join("");
|
|
35
|
-
if (react.isValidElement(children))
|
|
27
|
+
if (react.isValidElement(children))
|
|
36
28
|
return extractTextContent(children.props.children);
|
|
37
|
-
}
|
|
38
29
|
return "";
|
|
39
30
|
}
|
|
40
31
|
function scanItemLabels(children, registry) {
|
|
41
32
|
react.Children.forEach(children, (child) => {
|
|
42
33
|
if (!react.isValidElement(child))
|
|
43
34
|
return;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const dn =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
: undefined;
|
|
51
|
-
if (dn === "SelectItem" && (typeof props.value === "string" || props.value === null)) {
|
|
52
|
-
// null is the Shadcn-style "no selection" sentinel — stored as "" internally
|
|
53
|
-
const key = props.value ?? "";
|
|
54
|
-
const label = extractTextContent(props.children);
|
|
35
|
+
// Check for SelectItem based on type or name
|
|
36
|
+
const type = child.type;
|
|
37
|
+
const dn = type.displayName || type.name;
|
|
38
|
+
if (dn === "SelectItem") {
|
|
39
|
+
const key = child.props.value ?? "";
|
|
40
|
+
const label = extractTextContent(child.props.children);
|
|
55
41
|
if (label)
|
|
56
42
|
registry.set(key, label);
|
|
57
43
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
scanItemLabels(props.children, registry);
|
|
44
|
+
if (child.props.children) {
|
|
45
|
+
scanItemLabels(child.props.children, registry);
|
|
61
46
|
}
|
|
62
47
|
});
|
|
63
48
|
}
|
|
64
|
-
const SelectContext = react.createContext(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
49
|
+
const SelectContext = react.createContext(null);
|
|
50
|
+
// ─── Variants ────────────────────────────────────────────────────────────────
|
|
51
|
+
const selectTriggerVariants = core.cva({
|
|
52
|
+
base: "lui-select-trigger",
|
|
53
|
+
variants: {
|
|
54
|
+
size: { sm: "lui-select-trigger--sm", md: "lui-select-trigger--md" },
|
|
55
|
+
status: { default: "", error: "lui-select-trigger--error" },
|
|
56
|
+
fullWidth: { true: "lui-select-trigger--full-width" },
|
|
57
|
+
},
|
|
58
|
+
defaultVariants: { size: "md", status: "default" },
|
|
72
59
|
});
|
|
73
|
-
function Select({
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// JSX element tree always contains all SelectItem elements.
|
|
86
|
-
const labelsMap = new Map();
|
|
87
|
-
scanItemLabels(children, labelsMap);
|
|
88
|
-
// Merge items prop — supports null values and data-driven usage (.map()).
|
|
89
|
-
// Takes precedence over child-scanned labels for matching keys.
|
|
90
|
-
if (items) {
|
|
91
|
-
for (const item of items) {
|
|
92
|
-
labelsMap.set(item.value ?? "", item.label);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const displayLabel = (value !== undefined ? labelsMap.get(value) : undefined) ?? "";
|
|
96
|
-
const setOpen = react.useCallback((next) => {
|
|
97
|
-
if (!isOpenControlled)
|
|
98
|
-
setInternalOpen(next);
|
|
99
|
-
onOpenChange?.(next);
|
|
100
|
-
}, [isOpenControlled, onOpenChange]);
|
|
101
|
-
const handleValueChange = react.useCallback((newValue) => {
|
|
102
|
-
if (!isValueControlled)
|
|
103
|
-
setInternalValue(newValue);
|
|
104
|
-
onValueChange?.(newValue);
|
|
105
|
-
setOpen(false);
|
|
106
|
-
}, [isValueControlled, onValueChange, setOpen]);
|
|
107
|
-
return (jsxRuntime.jsx(SelectContext, { value: {
|
|
108
|
-
open,
|
|
109
|
-
setOpen,
|
|
110
|
-
value,
|
|
111
|
-
displayLabel,
|
|
112
|
-
onValueChange: handleValueChange,
|
|
113
|
-
disabled,
|
|
114
|
-
triggerId,
|
|
115
|
-
contentId,
|
|
116
|
-
triggerRef,
|
|
117
|
-
}, children: children }));
|
|
60
|
+
function Select({ children, items, ...props }) {
|
|
61
|
+
const select = primitives.useSelect(props);
|
|
62
|
+
const labelsMap = react.useMemo(() => {
|
|
63
|
+
const map = new Map();
|
|
64
|
+
// 1. Scan children (Static JSX)
|
|
65
|
+
scanItemLabels(children, map);
|
|
66
|
+
// 2. Add explicit items (Dynamic/SDUI)
|
|
67
|
+
items?.forEach(item => map.set(item.value, item.label));
|
|
68
|
+
return map;
|
|
69
|
+
}, [children, items]);
|
|
70
|
+
const displayLabel = (select.value !== undefined ? labelsMap.get(select.value) : undefined) ?? "";
|
|
71
|
+
return (jsxRuntime.jsx(SelectContext, { value: { ...select, displayLabel }, children: children }));
|
|
118
72
|
}
|
|
119
73
|
Select.displayName = "Select";
|
|
120
|
-
|
|
74
|
+
const SelectTrigger = react.forwardRef(({ className, size, status, fullWidth, children, ...props }, ref) => {
|
|
121
75
|
const ctx = react.use(SelectContext);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
76
|
+
if (!ctx)
|
|
77
|
+
return null;
|
|
78
|
+
const setRefs = (el) => {
|
|
125
79
|
ctx.triggerRef.current = el;
|
|
126
|
-
if (typeof ref === "function")
|
|
80
|
+
if (typeof ref === "function")
|
|
127
81
|
ref(el);
|
|
128
|
-
|
|
129
|
-
else if (ref) {
|
|
82
|
+
else if (ref)
|
|
130
83
|
ref.current = el;
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
const handleKeyDown = (e) => {
|
|
134
|
-
if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
|
|
135
|
-
e.preventDefault();
|
|
136
|
-
ctx.setOpen(true);
|
|
137
|
-
}
|
|
138
|
-
onKeyDown?.(e);
|
|
139
84
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
85
|
+
return (jsxRuntime.jsxs("button", { ref: setRefs, type: "button", role: "combobox", id: ctx.triggerId, "aria-expanded": ctx.open, "aria-haspopup": "listbox", "aria-controls": ctx.open ? ctx.contentId : undefined, disabled: ctx.disabled, className: selectTriggerVariants({ size, status, fullWidth, className }), onClick: (e) => {
|
|
86
|
+
ctx.setOpen(!ctx.open);
|
|
87
|
+
props.onClick?.(e);
|
|
88
|
+
}, onKeyDown: (e) => {
|
|
89
|
+
if (e.key === "Escape" && ctx.open) {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
ctx.setOpen(false);
|
|
92
|
+
}
|
|
93
|
+
if ((e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") && !ctx.open) {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
ctx.setOpen(true);
|
|
96
|
+
}
|
|
97
|
+
props.onKeyDown?.(e);
|
|
98
|
+
}, ...props, children: [children, jsxRuntime.jsx(ChevronDownIcon, { className: "lui-select-trigger__chevron" })] }));
|
|
99
|
+
});
|
|
143
100
|
SelectTrigger.displayName = "SelectTrigger";
|
|
144
|
-
|
|
101
|
+
const SelectValue = ({ placeholder, className }) => {
|
|
145
102
|
const ctx = react.use(SelectContext);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
103
|
+
return (jsxRuntime.jsx("span", { className: core.concatClassNames("lui-select-value", className), "data-slot": "select-value", children: ctx?.displayLabel || placeholder }));
|
|
104
|
+
};
|
|
149
105
|
SelectValue.displayName = "SelectValue";
|
|
150
|
-
|
|
106
|
+
const SelectContent = react.forwardRef(({ children, className, ...props }, ref) => {
|
|
151
107
|
const ctx = react.use(SelectContext);
|
|
152
108
|
const contentRef = react.useRef(null);
|
|
153
|
-
// Two-pass positioning:
|
|
154
|
-
// Pass 1 — render with visibility:hidden so browser can measure item offsets.
|
|
155
|
-
// Pass 2 — compute correct position, set isPositioned=true → visibility:visible.
|
|
156
|
-
const [isPositioned, setIsPositioned] = react.useState(false);
|
|
157
109
|
const [coords, setCoords] = react.useState({ top: 0, left: 0, width: 0 });
|
|
158
|
-
|
|
110
|
+
const [isPositioned, setIsPositioned] = react.useState(false);
|
|
111
|
+
const { addRef } = primitives.useOutsideClick(() => ctx?.setOpen(false), ctx?.open);
|
|
112
|
+
// Two-pass positioning strategy
|
|
159
113
|
useIsomorphicLayoutEffect(() => {
|
|
160
|
-
if (!ctx
|
|
114
|
+
if (!ctx?.open || !ctx.triggerRef.current || !contentRef.current) {
|
|
115
|
+
setIsPositioned(false);
|
|
161
116
|
return;
|
|
117
|
+
}
|
|
162
118
|
const triggerRect = ctx.triggerRef.current.getBoundingClientRect();
|
|
163
119
|
const content = contentRef.current;
|
|
164
120
|
const contentHeight = content.offsetHeight;
|
|
165
|
-
const contentWidth = content.offsetWidth;
|
|
166
121
|
const viewportHeight = window.innerHeight;
|
|
167
|
-
|
|
168
|
-
let top;
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const firstItem = content.querySelector('[role="option"]');
|
|
174
|
-
const targetItem = selectedItem ?? firstItem;
|
|
175
|
-
if (targetItem) {
|
|
176
|
-
// offsetTop is relative to the fixed content div (its offsetParent)
|
|
177
|
-
const itemTop = targetItem.offsetTop;
|
|
178
|
-
const itemHeight = targetItem.offsetHeight;
|
|
179
|
-
// Center item with trigger: move content up so item.center = trigger.center
|
|
180
|
-
top =
|
|
181
|
-
triggerRect.top -
|
|
182
|
-
itemTop +
|
|
183
|
-
(triggerRect.height - itemHeight) / 2;
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
top = triggerRect.top;
|
|
187
|
-
}
|
|
122
|
+
// Popper strategy: open below trigger
|
|
123
|
+
let top = triggerRect.bottom + window.scrollY + 4;
|
|
124
|
+
const left = triggerRect.left + window.scrollX;
|
|
125
|
+
// Flip if no space below
|
|
126
|
+
if (triggerRect.bottom + contentHeight + 8 > viewportHeight) {
|
|
127
|
+
top = triggerRect.top + window.scrollY - contentHeight - 4;
|
|
188
128
|
}
|
|
189
|
-
else {
|
|
190
|
-
// Popper: open below trigger, fall back to above if insufficient space
|
|
191
|
-
const spaceBelow = viewportHeight - triggerRect.bottom;
|
|
192
|
-
if (spaceBelow < contentHeight + 8 && triggerRect.top > contentHeight + 8) {
|
|
193
|
-
top = triggerRect.top - contentHeight - 4;
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
top = triggerRect.bottom + 4;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
// Clamp to viewport with 8 px margin
|
|
200
|
-
top = Math.max(8, Math.min(top, viewportHeight - contentHeight - 8));
|
|
201
|
-
const left = Math.max(8, Math.min(triggerRect.left, viewportWidth - contentWidth - 8));
|
|
202
129
|
setCoords({ top, left, width: triggerRect.width });
|
|
203
130
|
setIsPositioned(true);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
document.addEventListener("pointerdown", handlePointerDown);
|
|
229
|
-
return () => document.removeEventListener("pointerdown", handlePointerDown);
|
|
230
|
-
}, [ctx.open, ctx.setOpen, ctx.triggerRef]);
|
|
231
|
-
// Keyboard navigation within the listbox
|
|
232
|
-
const handleKeyDown = (e) => {
|
|
233
|
-
const items = contentRef.current?.querySelectorAll('[role="option"]:not([aria-disabled="true"])');
|
|
234
|
-
if (!items || items.length === 0)
|
|
235
|
-
return;
|
|
236
|
-
const active = document.activeElement;
|
|
237
|
-
const currentIndex = Array.from(items).indexOf(active);
|
|
238
|
-
switch (e.key) {
|
|
239
|
-
case "ArrowDown": {
|
|
240
|
-
e.preventDefault();
|
|
241
|
-
items[Math.min(currentIndex + 1, items.length - 1)]?.focus();
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
244
|
-
case "ArrowUp": {
|
|
245
|
-
e.preventDefault();
|
|
246
|
-
items[Math.max(currentIndex - 1, 0)]?.focus();
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
case "Home": {
|
|
250
|
-
e.preventDefault();
|
|
251
|
-
items[0]?.focus();
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
case "End": {
|
|
255
|
-
e.preventDefault();
|
|
256
|
-
items[items.length - 1]?.focus();
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
case "Escape":
|
|
260
|
-
case "Tab": {
|
|
131
|
+
}, [ctx?.open]);
|
|
132
|
+
if (!ctx?.open)
|
|
133
|
+
return null;
|
|
134
|
+
return reactDom.createPortal(jsxRuntime.jsx("div", { ref: (node) => {
|
|
135
|
+
contentRef.current = node;
|
|
136
|
+
if (typeof ref === "function")
|
|
137
|
+
ref(node);
|
|
138
|
+
else if (ref)
|
|
139
|
+
ref.current = node;
|
|
140
|
+
if (node)
|
|
141
|
+
addRef({ current: node });
|
|
142
|
+
addRef(ctx.triggerRef);
|
|
143
|
+
}, id: ctx.contentId, role: "listbox", "aria-labelledby": ctx.triggerId, "data-slot": "select-content", className: core.concatClassNames("lui-select-content", className), style: {
|
|
144
|
+
position: "absolute",
|
|
145
|
+
top: coords.top,
|
|
146
|
+
left: coords.left,
|
|
147
|
+
width: coords.width,
|
|
148
|
+
minWidth: coords.width,
|
|
149
|
+
zIndex: 9999,
|
|
150
|
+
visibility: isPositioned ? "visible" : "hidden",
|
|
151
|
+
}, onKeyDown: (e) => {
|
|
152
|
+
if (e.key === "Escape") {
|
|
261
153
|
ctx.setOpen(false);
|
|
262
154
|
ctx.triggerRef.current?.focus();
|
|
263
|
-
break;
|
|
264
155
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return null;
|
|
269
|
-
const style = {
|
|
270
|
-
position: "fixed",
|
|
271
|
-
top: coords.top,
|
|
272
|
-
left: coords.left,
|
|
273
|
-
width: coords.width || undefined,
|
|
274
|
-
minWidth: coords.width || undefined,
|
|
275
|
-
zIndex: 9999,
|
|
276
|
-
// Hidden during pass 1 to allow measurement without visual flash
|
|
277
|
-
visibility: isPositioned ? "visible" : "hidden",
|
|
278
|
-
};
|
|
279
|
-
const setRef = (el) => {
|
|
280
|
-
contentRef.current = el;
|
|
281
|
-
if (typeof ref === "function") {
|
|
282
|
-
ref(el);
|
|
283
|
-
}
|
|
284
|
-
else if (ref) {
|
|
285
|
-
ref.current = el;
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
const classes = concatClassNames("lui-select-content", className);
|
|
289
|
-
return reactDom.createPortal(jsxRuntime.jsx("div", { ref: setRef, id: ctx.contentId, role: "listbox", "aria-labelledby": ctx.triggerId, "data-slot": "select-content", "data-state": "open", className: classes, style: style, onKeyDown: handleKeyDown, children: jsxRuntime.jsx("div", { className: "lui-select-content__viewport", children: children }) }), document.body);
|
|
290
|
-
}
|
|
156
|
+
props.onKeyDown?.(e);
|
|
157
|
+
}, ...props, children: jsxRuntime.jsx("div", { className: "lui-select-content__viewport", children: children }) }), document.body);
|
|
158
|
+
});
|
|
291
159
|
SelectContent.displayName = "SelectContent";
|
|
292
|
-
|
|
293
|
-
return (jsxRuntime.jsx("div", { ref: ref, role: "group", "data-slot": "select-group", className: concatClassNames("lui-select-group", className), ...props, children: children }));
|
|
294
|
-
}
|
|
295
|
-
SelectGroup.displayName = "SelectGroup";
|
|
296
|
-
function SelectLabel({ ref, className, children, ...props }) {
|
|
297
|
-
return (jsxRuntime.jsx("div", { ref: ref, "data-slot": "select-label", className: concatClassNames("lui-select-label", className), ...props, children: children }));
|
|
298
|
-
}
|
|
299
|
-
SelectLabel.displayName = "SelectLabel";
|
|
300
|
-
function SelectItem({ ref, className, children, value, disabled, onClick, onKeyDown, ...props }) {
|
|
160
|
+
const SelectItem = react.forwardRef(({ value, children, disabled, className, ...props }, ref) => {
|
|
301
161
|
const ctx = react.use(SelectContext);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
312
|
-
e.preventDefault();
|
|
313
|
-
handleSelect();
|
|
314
|
-
}
|
|
315
|
-
onKeyDown?.(e);
|
|
316
|
-
};
|
|
317
|
-
const classes = concatClassNames("lui-select-item", isSelected ? "lui-select-item--selected" : undefined, disabled ? "lui-select-item--disabled" : undefined, className);
|
|
318
|
-
return (jsxRuntime.jsxs("div", { ref: ref, role: "option", "aria-selected": isSelected, "aria-disabled": disabled || undefined, tabIndex: disabled ? -1 : 0, "data-slot": "select-item", "data-value": normalizedValue, className: classes, onClick: handleSelect, onKeyDown: handleKeyDown, ...props, children: [jsxRuntime.jsx("span", { className: "lui-select-item__indicator", "aria-hidden": "true", children: isSelected && jsxRuntime.jsx(CheckIcon, { className: "lui-select-item__check" }) }), jsxRuntime.jsx("span", { className: "lui-select-item__text", children: children })] }));
|
|
319
|
-
}
|
|
162
|
+
const isSelected = ctx?.value === value;
|
|
163
|
+
return (jsxRuntime.jsxs("div", { ref: ref, role: "option", "aria-selected": isSelected, "aria-disabled": disabled, className: core.concatClassNames("lui-select-item", isSelected && "lui-select-item--selected", disabled && "lui-select-item--disabled", className), onClick: (e) => {
|
|
164
|
+
if (!disabled) {
|
|
165
|
+
ctx?.onValueChange(value === null ? "" : value);
|
|
166
|
+
ctx?.setOpen(false);
|
|
167
|
+
}
|
|
168
|
+
props.onClick?.(e);
|
|
169
|
+
}, ...props, children: [jsxRuntime.jsx("span", { className: "lui-select-item__indicator", "aria-hidden": "true", children: isSelected && jsxRuntime.jsx(CheckIcon, { className: "lui-select-item__check" }) }), jsxRuntime.jsx("span", { className: "lui-select-item__text", children: children })] }));
|
|
170
|
+
});
|
|
320
171
|
SelectItem.displayName = "SelectItem";
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
172
|
+
const SelectGroup = react.forwardRef(({ className, ...props }, ref) => (jsxRuntime.jsx("div", { ref: ref, role: "group", className: core.concatClassNames("lui-select-group", className), ...props })));
|
|
173
|
+
SelectGroup.displayName = "SelectGroup";
|
|
174
|
+
const SelectLabel = react.forwardRef(({ className, ...props }, ref) => (jsxRuntime.jsx("div", { ref: ref, className: core.concatClassNames("lui-select-label", className), ...props })));
|
|
175
|
+
SelectLabel.displayName = "SelectLabel";
|
|
176
|
+
const SelectSeparator = react.forwardRef(({ className, ...props }, ref) => (jsxRuntime.jsx("hr", { ref: ref, className: core.concatClassNames("lui-select-separator", className), ...props })));
|
|
324
177
|
SelectSeparator.displayName = "SelectSeparator";
|
|
325
|
-
|
|
326
|
-
return (jsxRuntime.jsx("div", { ref: ref, "data-slot": "select-scroll-up-button", className: concatClassNames("lui-select-scroll-button", className), ...props, children: jsxRuntime.jsx(ChevronUpIcon, { className: "lui-select-scroll-button__icon" }) }));
|
|
327
|
-
}
|
|
178
|
+
const SelectScrollUpButton = react.forwardRef(({ className, ...props }, ref) => (jsxRuntime.jsx("div", { ref: ref, className: core.concatClassNames("lui-select-scroll-button", className), "aria-hidden": "true", ...props, children: jsxRuntime.jsx(ChevronDownIcon, { className: "lui-select-scroll-button__icon", style: { transform: "rotate(180deg)" } }) })));
|
|
328
179
|
SelectScrollUpButton.displayName = "SelectScrollUpButton";
|
|
329
|
-
|
|
330
|
-
return (jsxRuntime.jsx("div", { ref: ref, "data-slot": "select-scroll-down-button", className: concatClassNames("lui-select-scroll-button", className), ...props, children: jsxRuntime.jsx(ChevronDownIcon, { className: "lui-select-scroll-button__icon" }) }));
|
|
331
|
-
}
|
|
180
|
+
const SelectScrollDownButton = react.forwardRef(({ className, ...props }, ref) => (jsxRuntime.jsx("div", { ref: ref, className: core.concatClassNames("lui-select-scroll-button", className), "aria-hidden": "true", ...props, children: jsxRuntime.jsx(ChevronDownIcon, { className: "lui-select-scroll-button__icon" }) })));
|
|
332
181
|
SelectScrollDownButton.displayName = "SelectScrollDownButton";
|
|
333
182
|
|
|
334
183
|
exports.Select = Select;
|
|
@@ -336,8 +185,7 @@ exports.SelectContent = SelectContent;
|
|
|
336
185
|
exports.SelectGroup = SelectGroup;
|
|
337
186
|
exports.SelectItem = SelectItem;
|
|
338
187
|
exports.SelectLabel = SelectLabel;
|
|
339
|
-
exports.SelectScrollDownButton = SelectScrollDownButton;
|
|
340
|
-
exports.SelectScrollUpButton = SelectScrollUpButton;
|
|
341
188
|
exports.SelectSeparator = SelectSeparator;
|
|
342
189
|
exports.SelectTrigger = SelectTrigger;
|
|
343
190
|
exports.SelectValue = SelectValue;
|
|
191
|
+
exports.default = Select;
|
package/dist/index.mjs
CHANGED
|
@@ -1,28 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createContext,
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { createContext, forwardRef, use, useRef, useState, useLayoutEffect, useEffect, useMemo, Children, isValidElement } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
|
-
import concatClassNames from '@ladder-ui/core
|
|
4
|
+
import { cva, concatClassNames } from '@ladder-ui/core';
|
|
5
|
+
import { useOutsideClick, useSelect } from '@ladder-ui/primitives';
|
|
5
6
|
|
|
6
|
-
// useLayoutEffect runs synchronously after DOM mutations but before the browser
|
|
7
|
-
// paints. This eliminates the double-paint flash that useEffect causes for
|
|
8
|
-
// position-critical work like dropdown placement.
|
|
9
|
-
// Falls back to useEffect in SSR environments (no DOM = no layout to measure).
|
|
10
7
|
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
11
|
-
// ───
|
|
12
|
-
function ChevronDownIcon({ className }) {
|
|
13
|
-
return (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: className, children: jsx("path", { d: "m6 9 6 6 6-6" }) }));
|
|
14
|
-
}
|
|
15
|
-
function ChevronUpIcon({ className }) {
|
|
16
|
-
return (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: className, children: jsx("path", { d: "m18 15-6-6-6 6" }) }));
|
|
8
|
+
// ─── Icons ──────────────────────────────────────────────────────────────────
|
|
9
|
+
function ChevronDownIcon({ className, style }) {
|
|
10
|
+
return (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: className, style: style, children: jsx("path", { d: "m6 9 6 6 6-6" }) }));
|
|
17
11
|
}
|
|
18
12
|
function CheckIcon({ className }) {
|
|
19
13
|
return (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: className, children: jsx("path", { d: "M20 6 9 17l-5-5" }) }));
|
|
20
14
|
}
|
|
21
|
-
// ─── Label
|
|
22
|
-
//
|
|
23
|
-
// We scan the JSX children tree in Select root to build a value→label map.
|
|
24
|
-
// This works even when SelectContent renders null (closed state), because the
|
|
25
|
-
// JSX element tree passed as `children` always contains the full structure.
|
|
15
|
+
// ─── Helper: Label Extraction ────────────────────────────────────────────────
|
|
26
16
|
function extractTextContent(children) {
|
|
27
17
|
if (typeof children === "string")
|
|
28
18
|
return children;
|
|
@@ -30,303 +20,160 @@ function extractTextContent(children) {
|
|
|
30
20
|
return String(children);
|
|
31
21
|
if (Array.isArray(children))
|
|
32
22
|
return children.map(extractTextContent).join("");
|
|
33
|
-
if (isValidElement(children))
|
|
23
|
+
if (isValidElement(children))
|
|
34
24
|
return extractTextContent(children.props.children);
|
|
35
|
-
}
|
|
36
25
|
return "";
|
|
37
26
|
}
|
|
38
27
|
function scanItemLabels(children, registry) {
|
|
39
28
|
Children.forEach(children, (child) => {
|
|
40
29
|
if (!isValidElement(child))
|
|
41
30
|
return;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const dn =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
: undefined;
|
|
49
|
-
if (dn === "SelectItem" && (typeof props.value === "string" || props.value === null)) {
|
|
50
|
-
// null is the Shadcn-style "no selection" sentinel — stored as "" internally
|
|
51
|
-
const key = props.value ?? "";
|
|
52
|
-
const label = extractTextContent(props.children);
|
|
31
|
+
// Check for SelectItem based on type or name
|
|
32
|
+
const type = child.type;
|
|
33
|
+
const dn = type.displayName || type.name;
|
|
34
|
+
if (dn === "SelectItem") {
|
|
35
|
+
const key = child.props.value ?? "";
|
|
36
|
+
const label = extractTextContent(child.props.children);
|
|
53
37
|
if (label)
|
|
54
38
|
registry.set(key, label);
|
|
55
39
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
scanItemLabels(props.children, registry);
|
|
40
|
+
if (child.props.children) {
|
|
41
|
+
scanItemLabels(child.props.children, registry);
|
|
59
42
|
}
|
|
60
43
|
});
|
|
61
44
|
}
|
|
62
|
-
const SelectContext = createContext(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
45
|
+
const SelectContext = createContext(null);
|
|
46
|
+
// ─── Variants ────────────────────────────────────────────────────────────────
|
|
47
|
+
const selectTriggerVariants = cva({
|
|
48
|
+
base: "lui-select-trigger",
|
|
49
|
+
variants: {
|
|
50
|
+
size: { sm: "lui-select-trigger--sm", md: "lui-select-trigger--md" },
|
|
51
|
+
status: { default: "", error: "lui-select-trigger--error" },
|
|
52
|
+
fullWidth: { true: "lui-select-trigger--full-width" },
|
|
53
|
+
},
|
|
54
|
+
defaultVariants: { size: "md", status: "default" },
|
|
70
55
|
});
|
|
71
|
-
function Select({
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// JSX element tree always contains all SelectItem elements.
|
|
84
|
-
const labelsMap = new Map();
|
|
85
|
-
scanItemLabels(children, labelsMap);
|
|
86
|
-
// Merge items prop — supports null values and data-driven usage (.map()).
|
|
87
|
-
// Takes precedence over child-scanned labels for matching keys.
|
|
88
|
-
if (items) {
|
|
89
|
-
for (const item of items) {
|
|
90
|
-
labelsMap.set(item.value ?? "", item.label);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const displayLabel = (value !== undefined ? labelsMap.get(value) : undefined) ?? "";
|
|
94
|
-
const setOpen = useCallback((next) => {
|
|
95
|
-
if (!isOpenControlled)
|
|
96
|
-
setInternalOpen(next);
|
|
97
|
-
onOpenChange?.(next);
|
|
98
|
-
}, [isOpenControlled, onOpenChange]);
|
|
99
|
-
const handleValueChange = useCallback((newValue) => {
|
|
100
|
-
if (!isValueControlled)
|
|
101
|
-
setInternalValue(newValue);
|
|
102
|
-
onValueChange?.(newValue);
|
|
103
|
-
setOpen(false);
|
|
104
|
-
}, [isValueControlled, onValueChange, setOpen]);
|
|
105
|
-
return (jsx(SelectContext, { value: {
|
|
106
|
-
open,
|
|
107
|
-
setOpen,
|
|
108
|
-
value,
|
|
109
|
-
displayLabel,
|
|
110
|
-
onValueChange: handleValueChange,
|
|
111
|
-
disabled,
|
|
112
|
-
triggerId,
|
|
113
|
-
contentId,
|
|
114
|
-
triggerRef,
|
|
115
|
-
}, children: children }));
|
|
56
|
+
function Select({ children, items, ...props }) {
|
|
57
|
+
const select = useSelect(props);
|
|
58
|
+
const labelsMap = useMemo(() => {
|
|
59
|
+
const map = new Map();
|
|
60
|
+
// 1. Scan children (Static JSX)
|
|
61
|
+
scanItemLabels(children, map);
|
|
62
|
+
// 2. Add explicit items (Dynamic/SDUI)
|
|
63
|
+
items?.forEach(item => map.set(item.value, item.label));
|
|
64
|
+
return map;
|
|
65
|
+
}, [children, items]);
|
|
66
|
+
const displayLabel = (select.value !== undefined ? labelsMap.get(select.value) : undefined) ?? "";
|
|
67
|
+
return (jsx(SelectContext, { value: { ...select, displayLabel }, children: children }));
|
|
116
68
|
}
|
|
117
69
|
Select.displayName = "Select";
|
|
118
|
-
|
|
70
|
+
const SelectTrigger = forwardRef(({ className, size, status, fullWidth, children, ...props }, ref) => {
|
|
119
71
|
const ctx = use(SelectContext);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
72
|
+
if (!ctx)
|
|
73
|
+
return null;
|
|
74
|
+
const setRefs = (el) => {
|
|
123
75
|
ctx.triggerRef.current = el;
|
|
124
|
-
if (typeof ref === "function")
|
|
76
|
+
if (typeof ref === "function")
|
|
125
77
|
ref(el);
|
|
126
|
-
|
|
127
|
-
else if (ref) {
|
|
78
|
+
else if (ref)
|
|
128
79
|
ref.current = el;
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
const handleKeyDown = (e) => {
|
|
132
|
-
if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
|
|
133
|
-
e.preventDefault();
|
|
134
|
-
ctx.setOpen(true);
|
|
135
|
-
}
|
|
136
|
-
onKeyDown?.(e);
|
|
137
80
|
};
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
81
|
+
return (jsxs("button", { ref: setRefs, type: "button", role: "combobox", id: ctx.triggerId, "aria-expanded": ctx.open, "aria-haspopup": "listbox", "aria-controls": ctx.open ? ctx.contentId : undefined, disabled: ctx.disabled, className: selectTriggerVariants({ size, status, fullWidth, className }), onClick: (e) => {
|
|
82
|
+
ctx.setOpen(!ctx.open);
|
|
83
|
+
props.onClick?.(e);
|
|
84
|
+
}, onKeyDown: (e) => {
|
|
85
|
+
if (e.key === "Escape" && ctx.open) {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
ctx.setOpen(false);
|
|
88
|
+
}
|
|
89
|
+
if ((e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") && !ctx.open) {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
ctx.setOpen(true);
|
|
92
|
+
}
|
|
93
|
+
props.onKeyDown?.(e);
|
|
94
|
+
}, ...props, children: [children, jsx(ChevronDownIcon, { className: "lui-select-trigger__chevron" })] }));
|
|
95
|
+
});
|
|
141
96
|
SelectTrigger.displayName = "SelectTrigger";
|
|
142
|
-
|
|
97
|
+
const SelectValue = ({ placeholder, className }) => {
|
|
143
98
|
const ctx = use(SelectContext);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
99
|
+
return (jsx("span", { className: concatClassNames("lui-select-value", className), "data-slot": "select-value", children: ctx?.displayLabel || placeholder }));
|
|
100
|
+
};
|
|
147
101
|
SelectValue.displayName = "SelectValue";
|
|
148
|
-
|
|
102
|
+
const SelectContent = forwardRef(({ children, className, ...props }, ref) => {
|
|
149
103
|
const ctx = use(SelectContext);
|
|
150
104
|
const contentRef = useRef(null);
|
|
151
|
-
// Two-pass positioning:
|
|
152
|
-
// Pass 1 — render with visibility:hidden so browser can measure item offsets.
|
|
153
|
-
// Pass 2 — compute correct position, set isPositioned=true → visibility:visible.
|
|
154
|
-
const [isPositioned, setIsPositioned] = useState(false);
|
|
155
105
|
const [coords, setCoords] = useState({ top: 0, left: 0, width: 0 });
|
|
156
|
-
|
|
106
|
+
const [isPositioned, setIsPositioned] = useState(false);
|
|
107
|
+
const { addRef } = useOutsideClick(() => ctx?.setOpen(false), ctx?.open);
|
|
108
|
+
// Two-pass positioning strategy
|
|
157
109
|
useIsomorphicLayoutEffect(() => {
|
|
158
|
-
if (!ctx
|
|
110
|
+
if (!ctx?.open || !ctx.triggerRef.current || !contentRef.current) {
|
|
111
|
+
setIsPositioned(false);
|
|
159
112
|
return;
|
|
113
|
+
}
|
|
160
114
|
const triggerRect = ctx.triggerRef.current.getBoundingClientRect();
|
|
161
115
|
const content = contentRef.current;
|
|
162
116
|
const contentHeight = content.offsetHeight;
|
|
163
|
-
const contentWidth = content.offsetWidth;
|
|
164
117
|
const viewportHeight = window.innerHeight;
|
|
165
|
-
|
|
166
|
-
let top;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const firstItem = content.querySelector('[role="option"]');
|
|
172
|
-
const targetItem = selectedItem ?? firstItem;
|
|
173
|
-
if (targetItem) {
|
|
174
|
-
// offsetTop is relative to the fixed content div (its offsetParent)
|
|
175
|
-
const itemTop = targetItem.offsetTop;
|
|
176
|
-
const itemHeight = targetItem.offsetHeight;
|
|
177
|
-
// Center item with trigger: move content up so item.center = trigger.center
|
|
178
|
-
top =
|
|
179
|
-
triggerRect.top -
|
|
180
|
-
itemTop +
|
|
181
|
-
(triggerRect.height - itemHeight) / 2;
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
top = triggerRect.top;
|
|
185
|
-
}
|
|
118
|
+
// Popper strategy: open below trigger
|
|
119
|
+
let top = triggerRect.bottom + window.scrollY + 4;
|
|
120
|
+
const left = triggerRect.left + window.scrollX;
|
|
121
|
+
// Flip if no space below
|
|
122
|
+
if (triggerRect.bottom + contentHeight + 8 > viewportHeight) {
|
|
123
|
+
top = triggerRect.top + window.scrollY - contentHeight - 4;
|
|
186
124
|
}
|
|
187
|
-
else {
|
|
188
|
-
// Popper: open below trigger, fall back to above if insufficient space
|
|
189
|
-
const spaceBelow = viewportHeight - triggerRect.bottom;
|
|
190
|
-
if (spaceBelow < contentHeight + 8 && triggerRect.top > contentHeight + 8) {
|
|
191
|
-
top = triggerRect.top - contentHeight - 4;
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
top = triggerRect.bottom + 4;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
// Clamp to viewport with 8 px margin
|
|
198
|
-
top = Math.max(8, Math.min(top, viewportHeight - contentHeight - 8));
|
|
199
|
-
const left = Math.max(8, Math.min(triggerRect.left, viewportWidth - contentWidth - 8));
|
|
200
125
|
setCoords({ top, left, width: triggerRect.width });
|
|
201
126
|
setIsPositioned(true);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
document.addEventListener("pointerdown", handlePointerDown);
|
|
227
|
-
return () => document.removeEventListener("pointerdown", handlePointerDown);
|
|
228
|
-
}, [ctx.open, ctx.setOpen, ctx.triggerRef]);
|
|
229
|
-
// Keyboard navigation within the listbox
|
|
230
|
-
const handleKeyDown = (e) => {
|
|
231
|
-
const items = contentRef.current?.querySelectorAll('[role="option"]:not([aria-disabled="true"])');
|
|
232
|
-
if (!items || items.length === 0)
|
|
233
|
-
return;
|
|
234
|
-
const active = document.activeElement;
|
|
235
|
-
const currentIndex = Array.from(items).indexOf(active);
|
|
236
|
-
switch (e.key) {
|
|
237
|
-
case "ArrowDown": {
|
|
238
|
-
e.preventDefault();
|
|
239
|
-
items[Math.min(currentIndex + 1, items.length - 1)]?.focus();
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
case "ArrowUp": {
|
|
243
|
-
e.preventDefault();
|
|
244
|
-
items[Math.max(currentIndex - 1, 0)]?.focus();
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
case "Home": {
|
|
248
|
-
e.preventDefault();
|
|
249
|
-
items[0]?.focus();
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
case "End": {
|
|
253
|
-
e.preventDefault();
|
|
254
|
-
items[items.length - 1]?.focus();
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
case "Escape":
|
|
258
|
-
case "Tab": {
|
|
127
|
+
}, [ctx?.open]);
|
|
128
|
+
if (!ctx?.open)
|
|
129
|
+
return null;
|
|
130
|
+
return createPortal(jsx("div", { ref: (node) => {
|
|
131
|
+
contentRef.current = node;
|
|
132
|
+
if (typeof ref === "function")
|
|
133
|
+
ref(node);
|
|
134
|
+
else if (ref)
|
|
135
|
+
ref.current = node;
|
|
136
|
+
if (node)
|
|
137
|
+
addRef({ current: node });
|
|
138
|
+
addRef(ctx.triggerRef);
|
|
139
|
+
}, id: ctx.contentId, role: "listbox", "aria-labelledby": ctx.triggerId, "data-slot": "select-content", className: concatClassNames("lui-select-content", className), style: {
|
|
140
|
+
position: "absolute",
|
|
141
|
+
top: coords.top,
|
|
142
|
+
left: coords.left,
|
|
143
|
+
width: coords.width,
|
|
144
|
+
minWidth: coords.width,
|
|
145
|
+
zIndex: 9999,
|
|
146
|
+
visibility: isPositioned ? "visible" : "hidden",
|
|
147
|
+
}, onKeyDown: (e) => {
|
|
148
|
+
if (e.key === "Escape") {
|
|
259
149
|
ctx.setOpen(false);
|
|
260
150
|
ctx.triggerRef.current?.focus();
|
|
261
|
-
break;
|
|
262
151
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return null;
|
|
267
|
-
const style = {
|
|
268
|
-
position: "fixed",
|
|
269
|
-
top: coords.top,
|
|
270
|
-
left: coords.left,
|
|
271
|
-
width: coords.width || undefined,
|
|
272
|
-
minWidth: coords.width || undefined,
|
|
273
|
-
zIndex: 9999,
|
|
274
|
-
// Hidden during pass 1 to allow measurement without visual flash
|
|
275
|
-
visibility: isPositioned ? "visible" : "hidden",
|
|
276
|
-
};
|
|
277
|
-
const setRef = (el) => {
|
|
278
|
-
contentRef.current = el;
|
|
279
|
-
if (typeof ref === "function") {
|
|
280
|
-
ref(el);
|
|
281
|
-
}
|
|
282
|
-
else if (ref) {
|
|
283
|
-
ref.current = el;
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
const classes = concatClassNames("lui-select-content", className);
|
|
287
|
-
return createPortal(jsx("div", { ref: setRef, id: ctx.contentId, role: "listbox", "aria-labelledby": ctx.triggerId, "data-slot": "select-content", "data-state": "open", className: classes, style: style, onKeyDown: handleKeyDown, children: jsx("div", { className: "lui-select-content__viewport", children: children }) }), document.body);
|
|
288
|
-
}
|
|
152
|
+
props.onKeyDown?.(e);
|
|
153
|
+
}, ...props, children: jsx("div", { className: "lui-select-content__viewport", children: children }) }), document.body);
|
|
154
|
+
});
|
|
289
155
|
SelectContent.displayName = "SelectContent";
|
|
290
|
-
|
|
291
|
-
return (jsx("div", { ref: ref, role: "group", "data-slot": "select-group", className: concatClassNames("lui-select-group", className), ...props, children: children }));
|
|
292
|
-
}
|
|
293
|
-
SelectGroup.displayName = "SelectGroup";
|
|
294
|
-
function SelectLabel({ ref, className, children, ...props }) {
|
|
295
|
-
return (jsx("div", { ref: ref, "data-slot": "select-label", className: concatClassNames("lui-select-label", className), ...props, children: children }));
|
|
296
|
-
}
|
|
297
|
-
SelectLabel.displayName = "SelectLabel";
|
|
298
|
-
function SelectItem({ ref, className, children, value, disabled, onClick, onKeyDown, ...props }) {
|
|
156
|
+
const SelectItem = forwardRef(({ value, children, disabled, className, ...props }, ref) => {
|
|
299
157
|
const ctx = use(SelectContext);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
310
|
-
e.preventDefault();
|
|
311
|
-
handleSelect();
|
|
312
|
-
}
|
|
313
|
-
onKeyDown?.(e);
|
|
314
|
-
};
|
|
315
|
-
const classes = concatClassNames("lui-select-item", isSelected ? "lui-select-item--selected" : undefined, disabled ? "lui-select-item--disabled" : undefined, className);
|
|
316
|
-
return (jsxs("div", { ref: ref, role: "option", "aria-selected": isSelected, "aria-disabled": disabled || undefined, tabIndex: disabled ? -1 : 0, "data-slot": "select-item", "data-value": normalizedValue, className: classes, onClick: handleSelect, onKeyDown: handleKeyDown, ...props, children: [jsx("span", { className: "lui-select-item__indicator", "aria-hidden": "true", children: isSelected && jsx(CheckIcon, { className: "lui-select-item__check" }) }), jsx("span", { className: "lui-select-item__text", children: children })] }));
|
|
317
|
-
}
|
|
158
|
+
const isSelected = ctx?.value === value;
|
|
159
|
+
return (jsxs("div", { ref: ref, role: "option", "aria-selected": isSelected, "aria-disabled": disabled, className: concatClassNames("lui-select-item", isSelected && "lui-select-item--selected", disabled && "lui-select-item--disabled", className), onClick: (e) => {
|
|
160
|
+
if (!disabled) {
|
|
161
|
+
ctx?.onValueChange(value === null ? "" : value);
|
|
162
|
+
ctx?.setOpen(false);
|
|
163
|
+
}
|
|
164
|
+
props.onClick?.(e);
|
|
165
|
+
}, ...props, children: [jsx("span", { className: "lui-select-item__indicator", "aria-hidden": "true", children: isSelected && jsx(CheckIcon, { className: "lui-select-item__check" }) }), jsx("span", { className: "lui-select-item__text", children: children })] }));
|
|
166
|
+
});
|
|
318
167
|
SelectItem.displayName = "SelectItem";
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
168
|
+
const SelectGroup = forwardRef(({ className, ...props }, ref) => (jsx("div", { ref: ref, role: "group", className: concatClassNames("lui-select-group", className), ...props })));
|
|
169
|
+
SelectGroup.displayName = "SelectGroup";
|
|
170
|
+
const SelectLabel = forwardRef(({ className, ...props }, ref) => (jsx("div", { ref: ref, className: concatClassNames("lui-select-label", className), ...props })));
|
|
171
|
+
SelectLabel.displayName = "SelectLabel";
|
|
172
|
+
const SelectSeparator = forwardRef(({ className, ...props }, ref) => (jsx("hr", { ref: ref, className: concatClassNames("lui-select-separator", className), ...props })));
|
|
322
173
|
SelectSeparator.displayName = "SelectSeparator";
|
|
323
|
-
|
|
324
|
-
return (jsx("div", { ref: ref, "data-slot": "select-scroll-up-button", className: concatClassNames("lui-select-scroll-button", className), ...props, children: jsx(ChevronUpIcon, { className: "lui-select-scroll-button__icon" }) }));
|
|
325
|
-
}
|
|
174
|
+
const SelectScrollUpButton = forwardRef(({ className, ...props }, ref) => (jsx("div", { ref: ref, className: concatClassNames("lui-select-scroll-button", className), "aria-hidden": "true", ...props, children: jsx(ChevronDownIcon, { className: "lui-select-scroll-button__icon", style: { transform: "rotate(180deg)" } }) })));
|
|
326
175
|
SelectScrollUpButton.displayName = "SelectScrollUpButton";
|
|
327
|
-
|
|
328
|
-
return (jsx("div", { ref: ref, "data-slot": "select-scroll-down-button", className: concatClassNames("lui-select-scroll-button", className), ...props, children: jsx(ChevronDownIcon, { className: "lui-select-scroll-button__icon" }) }));
|
|
329
|
-
}
|
|
176
|
+
const SelectScrollDownButton = forwardRef(({ className, ...props }, ref) => (jsx("div", { ref: ref, className: concatClassNames("lui-select-scroll-button", className), "aria-hidden": "true", ...props, children: jsx(ChevronDownIcon, { className: "lui-select-scroll-button__icon" }) })));
|
|
330
177
|
SelectScrollDownButton.displayName = "SelectScrollDownButton";
|
|
331
178
|
|
|
332
|
-
export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel,
|
|
179
|
+
export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Select as default };
|
package/dist/select.css
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
}
|
|
27
27
|
.lui-select-trigger:focus-visible {
|
|
28
28
|
border-color: var(--lui-select-focus-border);
|
|
29
|
-
box-shadow: 0 0 0 2px var(--lui-surface), 0 0 0 4px var(--lui-select-focus-ring-color);
|
|
29
|
+
box-shadow: 0 0 0 2px var(--lui-bg-surface), 0 0 0 4px var(--lui-select-focus-ring-color);
|
|
30
30
|
}
|
|
31
31
|
.lui-select-trigger:disabled, .lui-select-trigger[aria-disabled=true] {
|
|
32
32
|
opacity: var(--lui-disabled-opacity);
|
|
@@ -91,8 +91,6 @@
|
|
|
91
91
|
font-family: var(--lui-font-family);
|
|
92
92
|
}
|
|
93
93
|
.lui-select-item {
|
|
94
|
-
content-visibility: auto;
|
|
95
|
-
contain-intrinsic-size: auto 36px;
|
|
96
94
|
position: relative;
|
|
97
95
|
display: flex;
|
|
98
96
|
align-items: center;
|
|
@@ -109,12 +107,11 @@
|
|
|
109
107
|
font-family: var(--lui-font-family);
|
|
110
108
|
}
|
|
111
109
|
.lui-select-item:hover:not(.lui-select-item--disabled) {
|
|
112
|
-
background-color:
|
|
110
|
+
background-color: var(--lui-select-item-hover-bg);
|
|
113
111
|
color: var(--lui-select-item-hover-text);
|
|
114
112
|
}
|
|
115
|
-
.lui-select-item
|
|
116
|
-
|
|
117
|
-
color: var(--lui-select-item-hover-text);
|
|
113
|
+
.lui-select-item--selected {
|
|
114
|
+
color: var(--lui-select-item-selected-text);
|
|
118
115
|
}
|
|
119
116
|
.lui-select-item--disabled {
|
|
120
117
|
opacity: var(--lui-disabled-opacity);
|
|
@@ -147,16 +144,4 @@
|
|
|
147
144
|
border-top: 1px solid var(--lui-select-separator-color);
|
|
148
145
|
margin: 0.25rem -0.25rem;
|
|
149
146
|
}
|
|
150
|
-
.lui-select-scroll-button {
|
|
151
|
-
display: flex;
|
|
152
|
-
align-items: center;
|
|
153
|
-
justify-content: center;
|
|
154
|
-
padding: 0.25rem;
|
|
155
|
-
cursor: default;
|
|
156
|
-
color: var(--lui-select-label-text);
|
|
157
|
-
}
|
|
158
|
-
.lui-select-scroll-button__icon {
|
|
159
|
-
width: 1rem;
|
|
160
|
-
height: 1rem;
|
|
161
|
-
}
|
|
162
147
|
}
|
package/dist/select.d.ts
CHANGED
|
@@ -1,119 +1,62 @@
|
|
|
1
|
-
import type { Ref } from "react";
|
|
2
|
-
|
|
1
|
+
import type { Ref, HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
import { type VariantProps } from "@ladder-ui/core";
|
|
3
|
+
declare const selectTriggerVariants: (props?: (import("packages/core/dist/cva").ConfigVariants<{
|
|
4
|
+
size: {
|
|
5
|
+
sm: string;
|
|
6
|
+
md: string;
|
|
7
|
+
};
|
|
8
|
+
status: {
|
|
9
|
+
default: string;
|
|
10
|
+
error: string;
|
|
11
|
+
};
|
|
12
|
+
fullWidth: {
|
|
13
|
+
true: string;
|
|
14
|
+
};
|
|
15
|
+
}> & {
|
|
16
|
+
className?: string;
|
|
17
|
+
}) | undefined) => string;
|
|
3
18
|
export interface SelectItemData {
|
|
4
19
|
label: string;
|
|
5
|
-
|
|
6
|
-
value: string | null;
|
|
20
|
+
value: string;
|
|
7
21
|
}
|
|
8
22
|
export interface SelectProps {
|
|
9
|
-
/** Controlled selected value. Pair with `onValueChange`. */
|
|
10
23
|
value?: string;
|
|
11
|
-
/** Uncontrolled initial selected value. */
|
|
12
24
|
defaultValue?: string;
|
|
13
|
-
/** Called when the user selects a different item. */
|
|
14
25
|
onValueChange?: (value: string) => void;
|
|
15
|
-
/** Disables the entire select control. */
|
|
16
|
-
disabled?: boolean;
|
|
17
|
-
children?: React.ReactNode;
|
|
18
|
-
/** Controlled open state. Pair with `onOpenChange`. */
|
|
19
26
|
open?: boolean;
|
|
20
|
-
/** Called when open state changes. */
|
|
21
27
|
onOpenChange?: (open: boolean) => void;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* Useful when items are rendered via `.map()` and the label map
|
|
25
|
-
* cannot be built from static JSX children alone.
|
|
26
|
-
* Supports `value: null` for the "no selection" reset item.
|
|
27
|
-
*/
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
children?: ReactNode;
|
|
28
30
|
items?: SelectItemData[];
|
|
29
31
|
}
|
|
30
|
-
export declare function Select({
|
|
32
|
+
export declare function Select({ children, items, ...props }: SelectProps): import("react/jsx-runtime").JSX.Element;
|
|
31
33
|
export declare namespace Select {
|
|
32
34
|
var displayName: string;
|
|
33
35
|
}
|
|
34
|
-
export interface SelectTriggerProps extends
|
|
36
|
+
export interface SelectTriggerProps extends Omit<HTMLAttributes<HTMLButtonElement>, "size">, VariantProps<typeof selectTriggerVariants> {
|
|
35
37
|
ref?: Ref<HTMLButtonElement>;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/** When true, the trigger expands to fill its container width. */
|
|
38
|
+
size?: "sm" | "md";
|
|
39
|
+
status?: "default" | "error";
|
|
39
40
|
fullWidth?: boolean;
|
|
40
41
|
}
|
|
41
|
-
export declare
|
|
42
|
-
export declare namespace SelectTrigger {
|
|
43
|
-
var displayName: string;
|
|
44
|
-
}
|
|
42
|
+
export declare const SelectTrigger: import("react").ForwardRefExoticComponent<Omit<SelectTriggerProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
|
|
45
43
|
export interface SelectValueProps {
|
|
46
|
-
/** Text shown when no value is selected. */
|
|
47
44
|
placeholder?: string;
|
|
48
45
|
className?: string;
|
|
49
46
|
}
|
|
50
|
-
export declare
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
export
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
children?: React.ReactNode;
|
|
58
|
-
/**
|
|
59
|
-
* Positioning strategy.
|
|
60
|
-
* - `"item-aligned"` (default): the dropdown opens so the selected item
|
|
61
|
-
* is visually aligned with the trigger — matching Shadcn/Radix behaviour.
|
|
62
|
-
* - `"popper"`: the dropdown opens below (or above) the trigger.
|
|
63
|
-
*/
|
|
64
|
-
position?: "item-aligned" | "popper";
|
|
65
|
-
}
|
|
66
|
-
export declare function SelectContent({ ref, className, children, position, }: SelectContentProps): import("react").ReactPortal | null;
|
|
67
|
-
export declare namespace SelectContent {
|
|
68
|
-
var displayName: string;
|
|
69
|
-
}
|
|
70
|
-
export interface SelectGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
71
|
-
ref?: Ref<HTMLDivElement>;
|
|
72
|
-
}
|
|
73
|
-
export declare function SelectGroup({ ref, className, children, ...props }: SelectGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
74
|
-
export declare namespace SelectGroup {
|
|
75
|
-
var displayName: string;
|
|
76
|
-
}
|
|
77
|
-
export interface SelectLabelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
78
|
-
ref?: Ref<HTMLDivElement>;
|
|
79
|
-
}
|
|
80
|
-
export declare function SelectLabel({ ref, className, children, ...props }: SelectLabelProps): import("react/jsx-runtime").JSX.Element;
|
|
81
|
-
export declare namespace SelectLabel {
|
|
82
|
-
var displayName: string;
|
|
83
|
-
}
|
|
84
|
-
export interface SelectItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
85
|
-
ref?: Ref<HTMLDivElement>;
|
|
86
|
-
/**
|
|
87
|
-
* The value this item represents when selected. Required.
|
|
88
|
-
* Pass `null` for the "no selection" reset item (Shadcn-style).
|
|
89
|
-
* Internally treated as `""` — `onValueChange` is called with `""`.
|
|
90
|
-
*/
|
|
91
|
-
value: string | null;
|
|
92
|
-
/** Prevents selection of this item. */
|
|
47
|
+
export declare const SelectValue: {
|
|
48
|
+
({ placeholder, className }: SelectValueProps): import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
displayName: string;
|
|
50
|
+
};
|
|
51
|
+
export declare const SelectContent: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
|
|
52
|
+
export interface SelectItemProps extends Omit<HTMLAttributes<HTMLDivElement>, "value"> {
|
|
53
|
+
value: string;
|
|
93
54
|
disabled?: boolean;
|
|
94
55
|
}
|
|
95
|
-
export declare
|
|
96
|
-
export declare
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
export
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
export declare function SelectSeparator({ ref, className, ...props }: SelectSeparatorProps): import("react/jsx-runtime").JSX.Element;
|
|
103
|
-
export declare namespace SelectSeparator {
|
|
104
|
-
var displayName: string;
|
|
105
|
-
}
|
|
106
|
-
export interface SelectScrollUpButtonProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
107
|
-
ref?: Ref<HTMLDivElement>;
|
|
108
|
-
}
|
|
109
|
-
export declare function SelectScrollUpButton({ ref, className, ...props }: SelectScrollUpButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
110
|
-
export declare namespace SelectScrollUpButton {
|
|
111
|
-
var displayName: string;
|
|
112
|
-
}
|
|
113
|
-
export interface SelectScrollDownButtonProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
114
|
-
ref?: Ref<HTMLDivElement>;
|
|
115
|
-
}
|
|
116
|
-
export declare function SelectScrollDownButton({ ref, className, ...props }: SelectScrollDownButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
117
|
-
export declare namespace SelectScrollDownButton {
|
|
118
|
-
var displayName: string;
|
|
119
|
-
}
|
|
56
|
+
export declare const SelectItem: import("react").ForwardRefExoticComponent<SelectItemProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
57
|
+
export declare const SelectGroup: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
|
|
58
|
+
export declare const SelectLabel: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
|
|
59
|
+
export declare const SelectSeparator: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLHRElement> & import("react").RefAttributes<HTMLHRElement>>;
|
|
60
|
+
export declare const SelectScrollUpButton: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
|
|
61
|
+
export declare const SelectScrollDownButton: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
|
|
62
|
+
export {};
|
package/dist/select.vars.css
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
--lui-select-trigger-bg: var(--lui-surface);
|
|
3
|
-
--lui-select-trigger-text: var(--lui-
|
|
4
|
-
--lui-select-trigger-border: var(--lui-
|
|
2
|
+
--lui-select-trigger-bg: var(--lui-bg-surface);
|
|
3
|
+
--lui-select-trigger-text: var(--lui-text-primary);
|
|
4
|
+
--lui-select-trigger-border: var(--lui-border-default);
|
|
5
5
|
--lui-select-trigger-radius: var(--lui-radius-sm);
|
|
6
6
|
--lui-select-trigger-height: 2.25rem;
|
|
7
7
|
--lui-select-trigger-height-sm: 2rem;
|
|
8
8
|
--lui-select-trigger-px: 0.75rem;
|
|
9
9
|
--lui-select-trigger-py: 0.5rem;
|
|
10
|
-
--lui-select-focus-border: var(--lui-
|
|
11
|
-
--lui-select-focus-ring-color: color-mix(in srgb, var(--lui-
|
|
12
|
-
--lui-select-content-bg: var(--lui-surface);
|
|
13
|
-
--lui-select-content-border: var(--lui-
|
|
10
|
+
--lui-select-focus-border: var(--lui-bg-interactive);
|
|
11
|
+
--lui-select-focus-ring-color: color-mix(in srgb, var(--lui-bg-interactive) 40%, transparent);
|
|
12
|
+
--lui-select-content-bg: var(--lui-bg-surface);
|
|
13
|
+
--lui-select-content-border: var(--lui-border-default);
|
|
14
14
|
--lui-select-content-radius: var(--lui-radius-sm);
|
|
15
15
|
--lui-select-content-shadow: 0 4px 16px rgba(0, 0, 0, 0.12), 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
16
16
|
--lui-select-content-max-height: 20rem;
|
|
17
|
-
--lui-select-item-text: var(--lui-
|
|
18
|
-
--lui-select-item-hover-bg: color-mix(in srgb, var(--lui-
|
|
19
|
-
--lui-select-item-hover-text: var(--lui-
|
|
20
|
-
--lui-select-item-selected-text: var(--lui-
|
|
21
|
-
--lui-select-item-focus-bg: color-mix(in srgb, var(--lui-
|
|
17
|
+
--lui-select-item-text: var(--lui-text-primary);
|
|
18
|
+
--lui-select-item-hover-bg: color-mix(in srgb, var(--lui-bg-interactive) 10%, transparent);
|
|
19
|
+
--lui-select-item-hover-text: var(--lui-text-primary);
|
|
20
|
+
--lui-select-item-selected-text: var(--lui-bg-interactive);
|
|
21
|
+
--lui-select-item-focus-bg: color-mix(in srgb, var(--lui-bg-interactive) 10%, transparent);
|
|
22
22
|
--lui-select-item-radius: var(--lui-radius-sm);
|
|
23
23
|
--lui-select-item-px: 0.5rem;
|
|
24
24
|
--lui-select-item-py: 0.375rem;
|
|
25
25
|
--lui-select-item-indicator-size: 1rem;
|
|
26
|
-
--lui-select-label-text: color-mix(in srgb, var(--lui-
|
|
27
|
-
--lui-select-separator-color: var(--lui-
|
|
26
|
+
--lui-select-label-text: color-mix(in srgb, var(--lui-text-primary) 60%, transparent);
|
|
27
|
+
--lui-select-separator-color: var(--lui-border-default);
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ladder-ui/select",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -17,48 +17,31 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
|
-
"keywords": [
|
|
21
|
-
"nodejs",
|
|
22
|
-
"react",
|
|
23
|
-
"ui",
|
|
24
|
-
"components",
|
|
25
|
-
"library",
|
|
26
|
-
"select",
|
|
27
|
-
"dropdown",
|
|
28
|
-
"form"
|
|
29
|
-
],
|
|
30
|
-
"author": "Ivan Avila <ivelaval@gmail.com> - https://www.vennet.dev",
|
|
31
|
-
"license": "ISC",
|
|
32
|
-
"repository": {
|
|
33
|
-
"type": "git",
|
|
34
|
-
"url": "git+ssh://git@github.com/ivelaval/ladder-ui.git"
|
|
35
|
-
},
|
|
36
|
-
"bugs": {
|
|
37
|
-
"url": "https://github.com/ivelaval/ladder-ui/issues"
|
|
38
|
-
},
|
|
39
|
-
"homepage": "https://github.com/ivelaval/ladder-ui#readme",
|
|
40
|
-
"publishConfig": {
|
|
41
|
-
"access": "public"
|
|
42
|
-
},
|
|
43
20
|
"devDependencies": {
|
|
44
21
|
"@rollup/plugin-typescript": "^11.1.6",
|
|
45
22
|
"@types/react": "^19.0.0",
|
|
46
|
-
"@types/react-dom": "^19.
|
|
23
|
+
"@types/react-dom": "^19.2.3",
|
|
47
24
|
"rollup": "^4.59.0",
|
|
48
25
|
"rollup-plugin-postcss": "^4.0.2",
|
|
49
26
|
"sass": "^1.90.0",
|
|
50
27
|
"tslib": "^2.6.2",
|
|
51
28
|
"typescript": "^5.3.3",
|
|
52
|
-
"@ladder-ui/
|
|
29
|
+
"@ladder-ui/primitives": "0.4.0",
|
|
30
|
+
"@ladder-ui/layout": "0.4.0",
|
|
31
|
+
"@ladder-ui/core": "0.4.0"
|
|
53
32
|
},
|
|
54
33
|
"peerDependencies": {
|
|
55
34
|
"@ladder-ui/core": ">=0.0.0",
|
|
56
|
-
"react": ">=18.0.0"
|
|
57
|
-
"react-dom": ">=18.0.0"
|
|
35
|
+
"react": ">=18.0.0"
|
|
58
36
|
},
|
|
59
37
|
"sideEffects": [
|
|
60
38
|
"**/*.css"
|
|
61
39
|
],
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/ivelaval/ladder-ui.git",
|
|
43
|
+
"directory": "packages/select"
|
|
44
|
+
},
|
|
62
45
|
"scripts": {
|
|
63
46
|
"build": "pnpm clean && rollup -c",
|
|
64
47
|
"dev": "rollup -c -w",
|