@nucleux/select 0.5.0 → 0.7.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/README.md CHANGED
@@ -1,9 +1,20 @@
1
1
  # @nucleux/select
2
2
 
3
- Accessible native-backed select with a chevron — Nucleux agentic UI for React.
3
+ Select — a custom listbox select with a trigger, groups and keyboard navigation — Nucleux agentic UI for React.
4
4
 
5
5
  Part of **[Nucleux](https://github.com/intelligaia-open-labs/nucleux)** — agentic UI components for React. Install a single component, or the full kit via [`@nucleux/react`](https://www.npmjs.com/package/@nucleux/react).
6
6
 
7
+
8
+ ## Exports
9
+
10
+ - **`Select`** — root; `value`/`defaultValue`/`onValueChange`, `open`/`onOpenChange`, `disabled`, `required`, `name` (submits with forms)
11
+ - **`SelectTrigger`** (`size`: default/sm), **`SelectValue`** (`placeholder`)
12
+ - **`SelectContent`** — the listbox (`position`, `align`); flips above the trigger when there is no room below
13
+ - **`SelectItem`** (`value`, `disabled`, `textValue`), **`SelectGroup`**, **`SelectLabel`**, **`SelectSeparator`**
14
+ - **`SelectScrollUpButton`**, **`SelectScrollDownButton`** — kept for shadcn API parity
15
+
16
+ For a plain native `<select>`, use `@nucleux/native-select`.
17
+
7
18
  ## Install
8
19
 
9
20
  ```bash
@@ -15,7 +26,15 @@ pnpm add @nucleux/select @nucleux/tokens
15
26
  ## Usage
16
27
 
17
28
  ```tsx
18
- import { Select } from "@nucleux/select";
29
+ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@nucleux/select";
30
+
31
+ <Select value={status} onValueChange={setStatus}>
32
+ <SelectTrigger aria-label="Status"><SelectValue placeholder="Status" /></SelectTrigger>
33
+ <SelectContent>
34
+ <SelectItem value="open">Open</SelectItem>
35
+ <SelectItem value="done">Done</SelectItem>
36
+ </SelectContent>
37
+ </Select>
19
38
  ```
20
39
 
21
40
  Requires the theme once in your app: `import "@nucleux/tokens/styles.css";`
package/dist/index.cjs CHANGED
@@ -27,25 +27,277 @@ function _interopNamespace(e) {
27
27
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
28
28
 
29
29
  // src/index.tsx
30
- var Select = React__namespace.forwardRef(
31
- ({ className, containerClassName, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("relative inline-flex w-full", containerClassName), children: [
32
- /* @__PURE__ */ jsxRuntime.jsx(
33
- "select",
30
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React__namespace.useLayoutEffect : React__namespace.useEffect;
31
+ var SelectContext = React__namespace.createContext(null);
32
+ function useSelect(component) {
33
+ const ctx = React__namespace.useContext(SelectContext);
34
+ if (!ctx) throw new Error(`<${component}> must be used inside <Select>`);
35
+ return ctx;
36
+ }
37
+ function Select({
38
+ value,
39
+ defaultValue,
40
+ onValueChange,
41
+ open,
42
+ defaultOpen = false,
43
+ onOpenChange,
44
+ disabled = false,
45
+ required = false,
46
+ name,
47
+ children
48
+ }) {
49
+ const [internalValue, setInternalValue] = React__namespace.useState(defaultValue);
50
+ const [internalOpen, setInternalOpen] = React__namespace.useState(defaultOpen);
51
+ const currentValue = value !== void 0 ? value : internalValue;
52
+ const isOpen = open !== void 0 ? open : internalOpen;
53
+ const triggerRef = React__namespace.useRef(null);
54
+ const containerRef = React__namespace.useRef(null);
55
+ const contentId = React__namespace.useId();
56
+ const [labels, setLabels] = React__namespace.useState(() => /* @__PURE__ */ new Map());
57
+ const setOpen = React__namespace.useCallback(
58
+ (next) => {
59
+ if (open === void 0) setInternalOpen(next);
60
+ onOpenChange?.(next);
61
+ },
62
+ [open, onOpenChange]
63
+ );
64
+ const select = React__namespace.useCallback(
65
+ (next) => {
66
+ if (value === void 0) setInternalValue(next);
67
+ onValueChange?.(next);
68
+ },
69
+ [value, onValueChange]
70
+ );
71
+ const registerLabel = React__namespace.useCallback((v, label) => {
72
+ setLabels((prev) => prev.get(v) === label ? prev : new Map(prev).set(v, label));
73
+ }, []);
74
+ React__namespace.useEffect(() => {
75
+ if (!isOpen) return;
76
+ const onDown = (e) => {
77
+ if (!containerRef.current?.contains(e.target)) setOpen(false);
78
+ };
79
+ document.addEventListener("mousedown", onDown);
80
+ return () => document.removeEventListener("mousedown", onDown);
81
+ }, [isOpen, setOpen]);
82
+ return /* @__PURE__ */ jsxRuntime.jsx(
83
+ SelectContext.Provider,
84
+ {
85
+ value: { value: currentValue, select, open: isOpen, setOpen, disabled, required, triggerRef, contentId, labels, registerLabel },
86
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative inline-block w-full max-w-full", "data-slot": "select", children: [
87
+ children,
88
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: currentValue ?? "" })
89
+ ] })
90
+ }
91
+ );
92
+ }
93
+ var SelectTrigger = React__namespace.forwardRef(
94
+ ({ size = "default", className, children, onClick, onKeyDown, ...props }, ref) => {
95
+ const { open, setOpen, disabled, required, triggerRef, contentId, value } = useSelect("SelectTrigger");
96
+ return /* @__PURE__ */ jsxRuntime.jsxs(
97
+ "button",
34
98
  {
35
- ref,
99
+ ref: (node) => {
100
+ triggerRef.current = node;
101
+ if (typeof ref === "function") ref(node);
102
+ else if (ref) ref.current = node;
103
+ },
104
+ type: "button",
105
+ role: "combobox",
106
+ "aria-expanded": open,
107
+ "aria-haspopup": "listbox",
108
+ "aria-controls": contentId,
109
+ "aria-required": required || void 0,
110
+ disabled,
111
+ "data-slot": "select-trigger",
112
+ "data-size": size,
113
+ "data-placeholder": value === void 0 ? "" : void 0,
114
+ onClick: (e) => {
115
+ onClick?.(e);
116
+ if (!e.defaultPrevented) setOpen(!open);
117
+ },
118
+ onKeyDown: (e) => {
119
+ onKeyDown?.(e);
120
+ if (e.defaultPrevented || open) return;
121
+ if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
122
+ e.preventDefault();
123
+ setOpen(true);
124
+ }
125
+ },
126
+ className: utils.cn(
127
+ "flex w-full items-center justify-between gap-2 whitespace-nowrap rounded-lg border border-input bg-background px-3 text-sm text-foreground shadow-sm outline-none transition-colors",
128
+ "focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
129
+ "aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/20",
130
+ "data-[placeholder]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
131
+ "*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
132
+ size === "sm" ? "h-8" : "h-9",
133
+ className
134
+ ),
135
+ ...props,
136
+ children: [
137
+ children,
138
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { "aria-hidden": true, className: "size-4 opacity-50" })
139
+ ]
140
+ }
141
+ );
142
+ }
143
+ );
144
+ SelectTrigger.displayName = "SelectTrigger";
145
+ var SelectValue = React__namespace.forwardRef(
146
+ ({ placeholder, className, ...props }, ref) => {
147
+ const { value, labels } = useSelect("SelectValue");
148
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { ref, "data-slot": "select-value", className: utils.cn("truncate", className), ...props, children: value !== void 0 ? labels.get(value) ?? value : placeholder });
149
+ }
150
+ );
151
+ SelectValue.displayName = "SelectValue";
152
+ var optionsOf = (list) => Array.from(list.querySelectorAll('[role="option"]:not([aria-disabled="true"])'));
153
+ var SelectContent = React__namespace.forwardRef(
154
+ ({ position = "popper", align = "start", className, children, onKeyDown, ...props }, ref) => {
155
+ const { open, setOpen, value, triggerRef, contentId } = useSelect("SelectContent");
156
+ const listRef = React__namespace.useRef(null);
157
+ const typeahead = React__namespace.useRef({ text: "", timer: 0 });
158
+ const [up, setUp] = React__namespace.useState(false);
159
+ useIsomorphicLayoutEffect(() => {
160
+ if (!open || !listRef.current || !triggerRef.current) return;
161
+ const trigger = triggerRef.current.getBoundingClientRect();
162
+ const { height } = listRef.current.getBoundingClientRect();
163
+ setUp(trigger.bottom + 4 + height > window.innerHeight && trigger.top - 4 - height >= 0);
164
+ const options = optionsOf(listRef.current);
165
+ const selected = options.find((o) => o.dataset.value === value);
166
+ (selected ?? options[0])?.focus();
167
+ }, [open, value, triggerRef]);
168
+ const close = () => {
169
+ setOpen(false);
170
+ triggerRef.current?.focus();
171
+ };
172
+ return /* @__PURE__ */ jsxRuntime.jsx(
173
+ "div",
174
+ {
175
+ ref: (node) => {
176
+ listRef.current = node;
177
+ if (typeof ref === "function") ref(node);
178
+ else if (ref) ref.current = node;
179
+ },
180
+ id: contentId,
181
+ role: "listbox",
182
+ tabIndex: -1,
183
+ hidden: !open,
184
+ "data-slot": "select-content",
185
+ "data-state": open ? "open" : "closed",
186
+ "data-position": position,
187
+ onKeyDown: (e) => {
188
+ onKeyDown?.(e);
189
+ if (e.defaultPrevented || !listRef.current) return;
190
+ const options = optionsOf(listRef.current);
191
+ const i = options.indexOf(document.activeElement);
192
+ const focus = (n) => options[Math.max(0, Math.min(options.length - 1, n))]?.focus();
193
+ if (e.key === "ArrowDown") e.preventDefault(), focus(i + 1);
194
+ else if (e.key === "ArrowUp") e.preventDefault(), focus(i - 1);
195
+ else if (e.key === "Home") e.preventDefault(), focus(0);
196
+ else if (e.key === "End") e.preventDefault(), focus(options.length - 1);
197
+ else if (e.key === "Escape") e.preventDefault(), close();
198
+ else if (e.key === "Tab") setOpen(false);
199
+ else if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && e.key !== " ") {
200
+ const t = typeahead.current;
201
+ clearTimeout(t.timer);
202
+ t.text += e.key.toLowerCase();
203
+ t.timer = setTimeout(() => t.text = "", 500);
204
+ const start = i + (t.text.length === 1 ? 1 : 0);
205
+ const ordered = [...options.slice(start), ...options.slice(0, start)];
206
+ ordered.find((o) => (o.textContent ?? "").trim().toLowerCase().startsWith(t.text))?.focus();
207
+ }
208
+ },
36
209
  className: utils.cn(
37
- "h-9 w-full appearance-none rounded-lg border border-input bg-background pl-3 pr-8 text-sm text-foreground shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
210
+ "absolute z-50 max-h-72 min-w-[8rem] w-full overflow-y-auto overflow-x-hidden rounded-lg border border-border bg-background p-1 text-foreground shadow-md outline-none animate-nx-fade-in",
211
+ up ? "bottom-full mb-1" : "top-full mt-1",
212
+ align === "end" ? "right-0" : align === "center" ? "left-1/2 -translate-x-1/2" : "left-0",
38
213
  className
39
214
  ),
40
215
  ...props,
41
216
  children
42
217
  }
43
- ),
44
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "pointer-events-none absolute right-2 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" })
45
- ] })
218
+ );
219
+ }
220
+ );
221
+ SelectContent.displayName = "SelectContent";
222
+ var SelectItem = React__namespace.forwardRef(
223
+ ({ value, disabled = false, textValue, className, children, onClick, onKeyDown, ...props }, ref) => {
224
+ const { value: selected, select, setOpen, triggerRef, registerLabel } = useSelect("SelectItem");
225
+ const isSelected = selected === value;
226
+ React__namespace.useEffect(() => registerLabel(value, textValue ?? children), [value, textValue, children, registerLabel]);
227
+ const choose = () => {
228
+ if (disabled) return;
229
+ select(value);
230
+ setOpen(false);
231
+ triggerRef.current?.focus();
232
+ };
233
+ return /* @__PURE__ */ jsxRuntime.jsxs(
234
+ "div",
235
+ {
236
+ ref,
237
+ role: "option",
238
+ tabIndex: -1,
239
+ "aria-selected": isSelected,
240
+ "aria-disabled": disabled || void 0,
241
+ "data-slot": "select-item",
242
+ "data-value": value,
243
+ "data-state": isSelected ? "checked" : "unchecked",
244
+ "data-disabled": disabled ? "" : void 0,
245
+ onClick: (e) => {
246
+ onClick?.(e);
247
+ if (!e.defaultPrevented) choose();
248
+ },
249
+ onKeyDown: (e) => {
250
+ onKeyDown?.(e);
251
+ if (!e.defaultPrevented && (e.key === "Enter" || e.key === " ")) {
252
+ e.preventDefault();
253
+ choose();
254
+ }
255
+ },
256
+ onMouseMove: (e) => {
257
+ if (!disabled && document.activeElement !== e.currentTarget) e.currentTarget.focus();
258
+ },
259
+ className: utils.cn(
260
+ "relative flex w-full cursor-default select-none items-center gap-2 rounded-md py-1.5 pl-2 pr-8 text-sm outline-none",
261
+ "focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
262
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
263
+ className
264
+ ),
265
+ ...props,
266
+ children: [
267
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", "aria-hidden": true, children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "size-4" }) }),
268
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex items-center gap-2", children })
269
+ ]
270
+ }
271
+ );
272
+ }
273
+ );
274
+ SelectItem.displayName = "SelectItem";
275
+ var SelectGroup = React__namespace.forwardRef(
276
+ (props, ref) => /* @__PURE__ */ jsxRuntime.jsx("div", { ref, role: "group", "data-slot": "select-group", ...props })
277
+ );
278
+ SelectGroup.displayName = "SelectGroup";
279
+ var SelectLabel = React__namespace.forwardRef(
280
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx("div", { ref, "data-slot": "select-label", className: utils.cn("px-2 py-1.5 text-xs text-muted-foreground", className), ...props })
281
+ );
282
+ SelectLabel.displayName = "SelectLabel";
283
+ var SelectSeparator = React__namespace.forwardRef(
284
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx("div", { ref, "aria-hidden": true, "data-slot": "select-separator", className: utils.cn("pointer-events-none -mx-1 my-1 h-px bg-border", className), ...props })
46
285
  );
47
- Select.displayName = "Select";
286
+ SelectSeparator.displayName = "SelectSeparator";
287
+ var SelectScrollUpButton = ({ className, children, ...props }) => children ? /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "select-scroll-up-button", className: utils.cn("flex items-center justify-center py-1", className), ...props, children }) : null;
288
+ SelectScrollUpButton.displayName = "SelectScrollUpButton";
289
+ var SelectScrollDownButton = ({ className, children, ...props }) => children ? /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "select-scroll-down-button", className: utils.cn("flex items-center justify-center py-1", className), ...props, children }) : null;
290
+ SelectScrollDownButton.displayName = "SelectScrollDownButton";
48
291
 
49
292
  exports.Select = Select;
293
+ exports.SelectContent = SelectContent;
294
+ exports.SelectGroup = SelectGroup;
295
+ exports.SelectItem = SelectItem;
296
+ exports.SelectLabel = SelectLabel;
297
+ exports.SelectScrollDownButton = SelectScrollDownButton;
298
+ exports.SelectScrollUpButton = SelectScrollUpButton;
299
+ exports.SelectSeparator = SelectSeparator;
300
+ exports.SelectTrigger = SelectTrigger;
301
+ exports.SelectValue = SelectValue;
50
302
  //# sourceMappingURL=index.cjs.map
51
303
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx"],"names":["React","cn","jsx","ChevronDown"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcO,IAAM,MAAA,GAAeA,gBAAA,CAAA,UAAA;AAAA,EAC1B,CAAC,EAAE,SAAA,EAAW,kBAAA,EAAoB,UAAU,GAAG,KAAA,EAAM,EAAG,GAAA,qCACrD,KAAA,EAAA,EAAI,SAAA,EAAWC,QAAA,CAAG,6BAAA,EAA+B,kBAAkB,CAAA,EAClE,QAAA,EAAA;AAAA,oBAAAC,cAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAWD,QAAA;AAAA,UACT,+PAAA;AAAA,UACA;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEH;AAAA;AAAA,KACH;AAAA,oBACAC,cAAA,CAACC,uBAAA,EAAA,EAAY,SAAA,EAAU,4FAAA,EAA6F;AAAA,GAAA,EACtH;AAEJ;AACA,MAAA,CAAO,WAAA,GAAc,QAAA","file":"index.cjs","sourcesContent":["import * as React from \"react\";\nimport { ChevronDown } from \"lucide-react\";\nimport { cn } from \"@nucleux/utils\";\n\nexport interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {\n /** Wrapper className (the chevron is positioned within it). */\n containerClassName?: string;\n}\n\n/**\n * An accessible select built on the native `<select>` element (full keyboard +\n * screen-reader support), styled to match the design system with a chevron.\n * Pass `<option>`s as children.\n */\nexport const Select = React.forwardRef<HTMLSelectElement, SelectProps>(\n ({ className, containerClassName, children, ...props }, ref) => (\n <div className={cn(\"relative inline-flex w-full\", containerClassName)}>\n <select\n ref={ref}\n className={cn(\n \"h-9 w-full appearance-none rounded-lg border border-input bg-background pl-3 pr-8 text-sm text-foreground shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n {children}\n </select>\n <ChevronDown className=\"pointer-events-none absolute right-2 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n </div>\n ),\n);\nSelect.displayName = \"Select\";\n"]}
1
+ {"version":3,"sources":["../src/index.tsx"],"names":["React","jsx","jsxs","cn","ChevronDown","Check"],"mappings":"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","file":"index.cjs","sourcesContent":["import * as React from \"react\";\nimport { Check, ChevronDown } from \"lucide-react\";\nimport { cn } from \"@nucleux/utils\";\n\n// useLayoutEffect warns when rendered on the server; fall back to useEffect there.\nconst useIsomorphicLayoutEffect = typeof window !== \"undefined\" ? React.useLayoutEffect : React.useEffect;\n\ninterface SelectContextValue {\n value: string | undefined;\n select: (value: string) => void;\n open: boolean;\n setOpen: (open: boolean) => void;\n disabled: boolean;\n required: boolean;\n triggerRef: React.RefObject<HTMLButtonElement>;\n contentId: string;\n labels: Map<string, React.ReactNode>;\n registerLabel: (value: string, label: React.ReactNode) => void;\n}\nconst SelectContext = React.createContext<SelectContextValue | null>(null);\n\nfunction useSelect(component: string) {\n const ctx = React.useContext(SelectContext);\n if (!ctx) throw new Error(`<${component}> must be used inside <Select>`);\n return ctx;\n}\n\nexport interface SelectProps {\n /** Selected value (controlled). */\n value?: string;\n /** Initially selected value (uncontrolled). */\n defaultValue?: string;\n /** Called with the newly selected value. */\n onValueChange?: (value: string) => void;\n /** Whether the list is open (controlled). */\n open?: boolean;\n /** Initial open state (uncontrolled). */\n defaultOpen?: boolean;\n /** Called when the list opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Disable the whole select. */\n disabled?: boolean;\n /** Mark as required in forms. */\n required?: boolean;\n /** Form field name; when set, the value is submitted with the form. */\n name?: string;\n children: React.ReactNode;\n}\n\n/**\n * A custom select: a trigger button showing the chosen option and a listbox of\n * SelectItems. Compose SelectTrigger (with SelectValue) and SelectContent.\n * Keyboard: Arrow keys, Home/End and typing a letter move between options;\n * Enter or Space chooses; Escape closes and returns focus to the trigger.\n * For a plain native `<select>`, use NativeSelect.\n */\nexport function Select({\n value,\n defaultValue,\n onValueChange,\n open,\n defaultOpen = false,\n onOpenChange,\n disabled = false,\n required = false,\n name,\n children,\n}: SelectProps) {\n const [internalValue, setInternalValue] = React.useState(defaultValue);\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen);\n const currentValue = value !== undefined ? value : internalValue;\n const isOpen = open !== undefined ? open : internalOpen;\n const triggerRef = React.useRef<HTMLButtonElement>(null);\n const containerRef = React.useRef<HTMLDivElement>(null);\n const contentId = React.useId();\n const [labels, setLabels] = React.useState<Map<string, React.ReactNode>>(() => new Map());\n\n const setOpen = React.useCallback(\n (next: boolean) => {\n if (open === undefined) setInternalOpen(next);\n onOpenChange?.(next);\n },\n [open, onOpenChange],\n );\n const select = React.useCallback(\n (next: string) => {\n if (value === undefined) setInternalValue(next);\n onValueChange?.(next);\n },\n [value, onValueChange],\n );\n const registerLabel = React.useCallback((v: string, label: React.ReactNode) => {\n setLabels((prev) => (prev.get(v) === label ? prev : new Map(prev).set(v, label)));\n }, []);\n\n React.useEffect(() => {\n if (!isOpen) return;\n const onDown = (e: MouseEvent) => {\n if (!containerRef.current?.contains(e.target as Node)) setOpen(false);\n };\n document.addEventListener(\"mousedown\", onDown);\n return () => document.removeEventListener(\"mousedown\", onDown);\n }, [isOpen, setOpen]);\n\n return (\n <SelectContext.Provider\n value={{ value: currentValue, select, open: isOpen, setOpen, disabled, required, triggerRef, contentId, labels, registerLabel }}\n >\n <div ref={containerRef} className=\"relative inline-block w-full max-w-full\" data-slot=\"select\">\n {children}\n {name && <input type=\"hidden\" name={name} value={currentValue ?? \"\"} />}\n </div>\n </SelectContext.Provider>\n );\n}\n\nexport interface SelectTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** \"default\" (h-9) or \"sm\" (h-8). */\n size?: \"default\" | \"sm\";\n}\n\n/** The button that shows the current value and opens the list. Put a SelectValue inside. */\nexport const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(\n ({ size = \"default\", className, children, onClick, onKeyDown, ...props }, ref) => {\n const { open, setOpen, disabled, required, triggerRef, contentId, value } = useSelect(\"SelectTrigger\");\n return (\n <button\n ref={(node) => {\n (triggerRef as React.MutableRefObject<HTMLButtonElement | null>).current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n }}\n type=\"button\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-controls={contentId}\n aria-required={required || undefined}\n disabled={disabled}\n data-slot=\"select-trigger\"\n data-size={size}\n data-placeholder={value === undefined ? \"\" : undefined}\n onClick={(e) => {\n onClick?.(e);\n if (!e.defaultPrevented) setOpen(!open);\n }}\n onKeyDown={(e) => {\n onKeyDown?.(e);\n if (e.defaultPrevented || open) return;\n if ([\"ArrowDown\", \"ArrowUp\", \"Enter\", \" \"].includes(e.key)) {\n e.preventDefault();\n setOpen(true);\n }\n }}\n className={cn(\n \"flex w-full items-center justify-between gap-2 whitespace-nowrap rounded-lg border border-input bg-background px-3 text-sm text-foreground shadow-sm outline-none transition-colors\",\n \"focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\",\n \"aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/20\",\n \"data-[placeholder]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n \"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2\",\n size === \"sm\" ? \"h-8\" : \"h-9\",\n className,\n )}\n {...props}\n >\n {children}\n <ChevronDown aria-hidden className=\"size-4 opacity-50\" />\n </button>\n );\n },\n);\nSelectTrigger.displayName = \"SelectTrigger\";\n\nexport interface SelectValueProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Shown while nothing is selected. */\n placeholder?: React.ReactNode;\n}\n\n/** Shows the selected option's label (or the placeholder) inside SelectTrigger. */\nexport const SelectValue = React.forwardRef<HTMLSpanElement, SelectValueProps>(\n ({ placeholder, className, ...props }, ref) => {\n const { value, labels } = useSelect(\"SelectValue\");\n return (\n <span ref={ref} data-slot=\"select-value\" className={cn(\"truncate\", className)} {...props}>\n {value !== undefined ? (labels.get(value) ?? value) : placeholder}\n </span>\n );\n },\n);\nSelectValue.displayName = \"SelectValue\";\n\nconst optionsOf = (list: HTMLElement) =>\n Array.from(list.querySelectorAll<HTMLElement>('[role=\"option\"]:not([aria-disabled=\"true\"])'));\n\nexport interface SelectContentProps extends React.HTMLAttributes<HTMLDivElement> {\n /** \"popper\" (default) places the list below the trigger, flipping above when there is no room. */\n position?: \"popper\" | \"item-aligned\";\n /** Horizontal alignment relative to the trigger. */\n align?: \"start\" | \"center\" | \"end\";\n}\n\n/**\n * The listbox of SelectItems. It stays mounted (hidden) while closed so the\n * trigger can show the chosen item's label.\n */\nexport const SelectContent = React.forwardRef<HTMLDivElement, SelectContentProps>(\n ({ position = \"popper\", align = \"start\", className, children, onKeyDown, ...props }, ref) => {\n const { open, setOpen, value, triggerRef, contentId } = useSelect(\"SelectContent\");\n const listRef = React.useRef<HTMLDivElement | null>(null);\n const typeahead = React.useRef({ text: \"\", timer: 0 as unknown as ReturnType<typeof setTimeout> });\n const [up, setUp] = React.useState(false);\n\n useIsomorphicLayoutEffect(() => {\n if (!open || !listRef.current || !triggerRef.current) return;\n const trigger = triggerRef.current.getBoundingClientRect();\n const { height } = listRef.current.getBoundingClientRect();\n setUp(trigger.bottom + 4 + height > window.innerHeight && trigger.top - 4 - height >= 0);\n const options = optionsOf(listRef.current);\n const selected = options.find((o) => o.dataset.value === value);\n (selected ?? options[0])?.focus();\n }, [open, value, triggerRef]);\n\n const close = () => {\n setOpen(false);\n triggerRef.current?.focus();\n };\n\n return (\n <div\n ref={(node) => {\n listRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n }}\n id={contentId}\n role=\"listbox\"\n tabIndex={-1}\n hidden={!open}\n data-slot=\"select-content\"\n data-state={open ? \"open\" : \"closed\"}\n data-position={position}\n onKeyDown={(e) => {\n onKeyDown?.(e);\n if (e.defaultPrevented || !listRef.current) return;\n const options = optionsOf(listRef.current);\n const i = options.indexOf(document.activeElement as HTMLElement);\n const focus = (n: number) => options[Math.max(0, Math.min(options.length - 1, n))]?.focus();\n if (e.key === \"ArrowDown\") (e.preventDefault(), focus(i + 1));\n else if (e.key === \"ArrowUp\") (e.preventDefault(), focus(i - 1));\n else if (e.key === \"Home\") (e.preventDefault(), focus(0));\n else if (e.key === \"End\") (e.preventDefault(), focus(options.length - 1));\n else if (e.key === \"Escape\") (e.preventDefault(), close());\n else if (e.key === \"Tab\") setOpen(false);\n else if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && e.key !== \" \") {\n const t = typeahead.current;\n clearTimeout(t.timer);\n t.text += e.key.toLowerCase();\n t.timer = setTimeout(() => (t.text = \"\"), 500);\n const start = i + (t.text.length === 1 ? 1 : 0);\n const ordered = [...options.slice(start), ...options.slice(0, start)];\n ordered.find((o) => (o.textContent ?? \"\").trim().toLowerCase().startsWith(t.text))?.focus();\n }\n }}\n className={cn(\n \"absolute z-50 max-h-72 min-w-[8rem] w-full overflow-y-auto overflow-x-hidden rounded-lg border border-border bg-background p-1 text-foreground shadow-md outline-none animate-nx-fade-in\",\n up ? \"bottom-full mb-1\" : \"top-full mt-1\",\n align === \"end\" ? \"right-0\" : align === \"center\" ? \"left-1/2 -translate-x-1/2\" : \"left-0\",\n className,\n )}\n {...props}\n >\n {children}\n </div>\n );\n },\n);\nSelectContent.displayName = \"SelectContent\";\n\nexport interface SelectItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** The value this option sets. */\n value: string;\n /** Not selectable. */\n disabled?: boolean;\n /** Plain-text label used in the trigger when the children are rich. */\n textValue?: string;\n}\n\n/** One option. Shows a check mark when selected. */\nexport const SelectItem = React.forwardRef<HTMLDivElement, SelectItemProps>(\n ({ value, disabled = false, textValue, className, children, onClick, onKeyDown, ...props }, ref) => {\n const { value: selected, select, setOpen, triggerRef, registerLabel } = useSelect(\"SelectItem\");\n const isSelected = selected === value;\n React.useEffect(() => registerLabel(value, textValue ?? children), [value, textValue, children, registerLabel]);\n const choose = () => {\n if (disabled) return;\n select(value);\n setOpen(false);\n triggerRef.current?.focus();\n };\n return (\n <div\n ref={ref}\n role=\"option\"\n tabIndex={-1}\n aria-selected={isSelected}\n aria-disabled={disabled || undefined}\n data-slot=\"select-item\"\n data-value={value}\n data-state={isSelected ? \"checked\" : \"unchecked\"}\n data-disabled={disabled ? \"\" : undefined}\n onClick={(e) => {\n onClick?.(e);\n if (!e.defaultPrevented) choose();\n }}\n onKeyDown={(e) => {\n onKeyDown?.(e);\n if (!e.defaultPrevented && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n choose();\n }\n }}\n onMouseMove={(e) => {\n if (!disabled && document.activeElement !== e.currentTarget) e.currentTarget.focus();\n }}\n className={cn(\n \"relative flex w-full cursor-default select-none items-center gap-2 rounded-md py-1.5 pl-2 pr-8 text-sm outline-none\",\n \"focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className,\n )}\n {...props}\n >\n <span className=\"absolute right-2 flex size-3.5 items-center justify-center\" aria-hidden>\n {isSelected && <Check className=\"size-4\" />}\n </span>\n <span className=\"flex items-center gap-2\">{children}</span>\n </div>\n );\n },\n);\nSelectItem.displayName = \"SelectItem\";\n\n/** Groups related options; pair with SelectLabel. */\nexport const SelectGroup = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n (props, ref) => <div ref={ref} role=\"group\" data-slot=\"select-group\" {...props} />,\n);\nSelectGroup.displayName = \"SelectGroup\";\n\n/** Non-selectable heading for a SelectGroup. */\nexport const SelectLabel = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div ref={ref} data-slot=\"select-label\" className={cn(\"px-2 py-1.5 text-xs text-muted-foreground\", className)} {...props} />\n ),\n);\nSelectLabel.displayName = \"SelectLabel\";\n\n/** Hairline between groups (decorative: a listbox may only contain options and groups). */\nexport const SelectSeparator = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div ref={ref} aria-hidden data-slot=\"select-separator\" className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)} {...props} />\n ),\n);\nSelectSeparator.displayName = \"SelectSeparator\";\n\n/**\n * Kept for shadcn API parity. Nucleux's SelectContent scrolls natively, so\n * these render a small static affordance only when given children.\n */\nexport const SelectScrollUpButton = ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) =>\n children ? (\n <div data-slot=\"select-scroll-up-button\" className={cn(\"flex items-center justify-center py-1\", className)} {...props}>\n {children}\n </div>\n ) : null;\nSelectScrollUpButton.displayName = \"SelectScrollUpButton\";\n\n/** See SelectScrollUpButton. */\nexport const SelectScrollDownButton = ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) =>\n children ? (\n <div data-slot=\"select-scroll-down-button\" className={cn(\"flex items-center justify-center py-1\", className)} {...props}>\n {children}\n </div>\n ) : null;\nSelectScrollDownButton.displayName = \"SelectScrollDownButton\";\n"]}
package/dist/index.d.cts CHANGED
@@ -1,14 +1,85 @@
1
1
  import * as React from 'react';
2
2
 
3
- interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
4
- /** Wrapper className (the chevron is positioned within it). */
5
- containerClassName?: string;
3
+ interface SelectProps {
4
+ /** Selected value (controlled). */
5
+ value?: string;
6
+ /** Initially selected value (uncontrolled). */
7
+ defaultValue?: string;
8
+ /** Called with the newly selected value. */
9
+ onValueChange?: (value: string) => void;
10
+ /** Whether the list is open (controlled). */
11
+ open?: boolean;
12
+ /** Initial open state (uncontrolled). */
13
+ defaultOpen?: boolean;
14
+ /** Called when the list opens or closes. */
15
+ onOpenChange?: (open: boolean) => void;
16
+ /** Disable the whole select. */
17
+ disabled?: boolean;
18
+ /** Mark as required in forms. */
19
+ required?: boolean;
20
+ /** Form field name; when set, the value is submitted with the form. */
21
+ name?: string;
22
+ children: React.ReactNode;
6
23
  }
7
24
  /**
8
- * An accessible select built on the native `<select>` element (full keyboard +
9
- * screen-reader support), styled to match the design system with a chevron.
10
- * Pass `<option>`s as children.
25
+ * A custom select: a trigger button showing the chosen option and a listbox of
26
+ * SelectItems. Compose SelectTrigger (with SelectValue) and SelectContent.
27
+ * Keyboard: Arrow keys, Home/End and typing a letter move between options;
28
+ * Enter or Space chooses; Escape closes and returns focus to the trigger.
29
+ * For a plain native `<select>`, use NativeSelect.
11
30
  */
12
- declare const Select: React.ForwardRefExoticComponent<SelectProps & React.RefAttributes<HTMLSelectElement>>;
31
+ declare function Select({ value, defaultValue, onValueChange, open, defaultOpen, onOpenChange, disabled, required, name, children, }: SelectProps): React.JSX.Element;
32
+ interface SelectTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
33
+ /** "default" (h-9) or "sm" (h-8). */
34
+ size?: "default" | "sm";
35
+ }
36
+ /** The button that shows the current value and opens the list. Put a SelectValue inside. */
37
+ declare const SelectTrigger: React.ForwardRefExoticComponent<SelectTriggerProps & React.RefAttributes<HTMLButtonElement>>;
38
+ interface SelectValueProps extends React.HTMLAttributes<HTMLSpanElement> {
39
+ /** Shown while nothing is selected. */
40
+ placeholder?: React.ReactNode;
41
+ }
42
+ /** Shows the selected option's label (or the placeholder) inside SelectTrigger. */
43
+ declare const SelectValue: React.ForwardRefExoticComponent<SelectValueProps & React.RefAttributes<HTMLSpanElement>>;
44
+ interface SelectContentProps extends React.HTMLAttributes<HTMLDivElement> {
45
+ /** "popper" (default) places the list below the trigger, flipping above when there is no room. */
46
+ position?: "popper" | "item-aligned";
47
+ /** Horizontal alignment relative to the trigger. */
48
+ align?: "start" | "center" | "end";
49
+ }
50
+ /**
51
+ * The listbox of SelectItems. It stays mounted (hidden) while closed so the
52
+ * trigger can show the chosen item's label.
53
+ */
54
+ declare const SelectContent: React.ForwardRefExoticComponent<SelectContentProps & React.RefAttributes<HTMLDivElement>>;
55
+ interface SelectItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onSelect"> {
56
+ /** The value this option sets. */
57
+ value: string;
58
+ /** Not selectable. */
59
+ disabled?: boolean;
60
+ /** Plain-text label used in the trigger when the children are rich. */
61
+ textValue?: string;
62
+ }
63
+ /** One option. Shows a check mark when selected. */
64
+ declare const SelectItem: React.ForwardRefExoticComponent<SelectItemProps & React.RefAttributes<HTMLDivElement>>;
65
+ /** Groups related options; pair with SelectLabel. */
66
+ declare const SelectGroup: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
67
+ /** Non-selectable heading for a SelectGroup. */
68
+ declare const SelectLabel: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
69
+ /** Hairline between groups (decorative: a listbox may only contain options and groups). */
70
+ declare const SelectSeparator: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
71
+ /**
72
+ * Kept for shadcn API parity. Nucleux's SelectContent scrolls natively, so
73
+ * these render a small static affordance only when given children.
74
+ */
75
+ declare const SelectScrollUpButton: {
76
+ ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element | null;
77
+ displayName: string;
78
+ };
79
+ /** See SelectScrollUpButton. */
80
+ declare const SelectScrollDownButton: {
81
+ ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element | null;
82
+ displayName: string;
83
+ };
13
84
 
14
- export { Select, type SelectProps };
85
+ export { Select, SelectContent, type SelectContentProps, SelectGroup, SelectItem, type SelectItemProps, SelectLabel, type SelectProps, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, type SelectTriggerProps, SelectValue, type SelectValueProps };
package/dist/index.d.ts CHANGED
@@ -1,14 +1,85 @@
1
1
  import * as React from 'react';
2
2
 
3
- interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
4
- /** Wrapper className (the chevron is positioned within it). */
5
- containerClassName?: string;
3
+ interface SelectProps {
4
+ /** Selected value (controlled). */
5
+ value?: string;
6
+ /** Initially selected value (uncontrolled). */
7
+ defaultValue?: string;
8
+ /** Called with the newly selected value. */
9
+ onValueChange?: (value: string) => void;
10
+ /** Whether the list is open (controlled). */
11
+ open?: boolean;
12
+ /** Initial open state (uncontrolled). */
13
+ defaultOpen?: boolean;
14
+ /** Called when the list opens or closes. */
15
+ onOpenChange?: (open: boolean) => void;
16
+ /** Disable the whole select. */
17
+ disabled?: boolean;
18
+ /** Mark as required in forms. */
19
+ required?: boolean;
20
+ /** Form field name; when set, the value is submitted with the form. */
21
+ name?: string;
22
+ children: React.ReactNode;
6
23
  }
7
24
  /**
8
- * An accessible select built on the native `<select>` element (full keyboard +
9
- * screen-reader support), styled to match the design system with a chevron.
10
- * Pass `<option>`s as children.
25
+ * A custom select: a trigger button showing the chosen option and a listbox of
26
+ * SelectItems. Compose SelectTrigger (with SelectValue) and SelectContent.
27
+ * Keyboard: Arrow keys, Home/End and typing a letter move between options;
28
+ * Enter or Space chooses; Escape closes and returns focus to the trigger.
29
+ * For a plain native `<select>`, use NativeSelect.
11
30
  */
12
- declare const Select: React.ForwardRefExoticComponent<SelectProps & React.RefAttributes<HTMLSelectElement>>;
31
+ declare function Select({ value, defaultValue, onValueChange, open, defaultOpen, onOpenChange, disabled, required, name, children, }: SelectProps): React.JSX.Element;
32
+ interface SelectTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
33
+ /** "default" (h-9) or "sm" (h-8). */
34
+ size?: "default" | "sm";
35
+ }
36
+ /** The button that shows the current value and opens the list. Put a SelectValue inside. */
37
+ declare const SelectTrigger: React.ForwardRefExoticComponent<SelectTriggerProps & React.RefAttributes<HTMLButtonElement>>;
38
+ interface SelectValueProps extends React.HTMLAttributes<HTMLSpanElement> {
39
+ /** Shown while nothing is selected. */
40
+ placeholder?: React.ReactNode;
41
+ }
42
+ /** Shows the selected option's label (or the placeholder) inside SelectTrigger. */
43
+ declare const SelectValue: React.ForwardRefExoticComponent<SelectValueProps & React.RefAttributes<HTMLSpanElement>>;
44
+ interface SelectContentProps extends React.HTMLAttributes<HTMLDivElement> {
45
+ /** "popper" (default) places the list below the trigger, flipping above when there is no room. */
46
+ position?: "popper" | "item-aligned";
47
+ /** Horizontal alignment relative to the trigger. */
48
+ align?: "start" | "center" | "end";
49
+ }
50
+ /**
51
+ * The listbox of SelectItems. It stays mounted (hidden) while closed so the
52
+ * trigger can show the chosen item's label.
53
+ */
54
+ declare const SelectContent: React.ForwardRefExoticComponent<SelectContentProps & React.RefAttributes<HTMLDivElement>>;
55
+ interface SelectItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onSelect"> {
56
+ /** The value this option sets. */
57
+ value: string;
58
+ /** Not selectable. */
59
+ disabled?: boolean;
60
+ /** Plain-text label used in the trigger when the children are rich. */
61
+ textValue?: string;
62
+ }
63
+ /** One option. Shows a check mark when selected. */
64
+ declare const SelectItem: React.ForwardRefExoticComponent<SelectItemProps & React.RefAttributes<HTMLDivElement>>;
65
+ /** Groups related options; pair with SelectLabel. */
66
+ declare const SelectGroup: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
67
+ /** Non-selectable heading for a SelectGroup. */
68
+ declare const SelectLabel: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
69
+ /** Hairline between groups (decorative: a listbox may only contain options and groups). */
70
+ declare const SelectSeparator: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
71
+ /**
72
+ * Kept for shadcn API parity. Nucleux's SelectContent scrolls natively, so
73
+ * these render a small static affordance only when given children.
74
+ */
75
+ declare const SelectScrollUpButton: {
76
+ ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element | null;
77
+ displayName: string;
78
+ };
79
+ /** See SelectScrollUpButton. */
80
+ declare const SelectScrollDownButton: {
81
+ ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element | null;
82
+ displayName: string;
83
+ };
13
84
 
14
- export { Select, type SelectProps };
85
+ export { Select, SelectContent, type SelectContentProps, SelectGroup, SelectItem, type SelectItemProps, SelectLabel, type SelectProps, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, type SelectTriggerProps, SelectValue, type SelectValueProps };
package/dist/index.js CHANGED
@@ -1,29 +1,272 @@
1
1
  "use client";
2
2
  import * as React from 'react';
3
- import { ChevronDown } from 'lucide-react';
3
+ import { ChevronDown, Check } from 'lucide-react';
4
4
  import { cn } from '@nucleux/utils';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
6
6
 
7
7
  // src/index.tsx
8
- var Select = React.forwardRef(
9
- ({ className, containerClassName, children, ...props }, ref) => /* @__PURE__ */ jsxs("div", { className: cn("relative inline-flex w-full", containerClassName), children: [
10
- /* @__PURE__ */ jsx(
11
- "select",
8
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
9
+ var SelectContext = React.createContext(null);
10
+ function useSelect(component) {
11
+ const ctx = React.useContext(SelectContext);
12
+ if (!ctx) throw new Error(`<${component}> must be used inside <Select>`);
13
+ return ctx;
14
+ }
15
+ function Select({
16
+ value,
17
+ defaultValue,
18
+ onValueChange,
19
+ open,
20
+ defaultOpen = false,
21
+ onOpenChange,
22
+ disabled = false,
23
+ required = false,
24
+ name,
25
+ children
26
+ }) {
27
+ const [internalValue, setInternalValue] = React.useState(defaultValue);
28
+ const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
29
+ const currentValue = value !== void 0 ? value : internalValue;
30
+ const isOpen = open !== void 0 ? open : internalOpen;
31
+ const triggerRef = React.useRef(null);
32
+ const containerRef = React.useRef(null);
33
+ const contentId = React.useId();
34
+ const [labels, setLabels] = React.useState(() => /* @__PURE__ */ new Map());
35
+ const setOpen = React.useCallback(
36
+ (next) => {
37
+ if (open === void 0) setInternalOpen(next);
38
+ onOpenChange?.(next);
39
+ },
40
+ [open, onOpenChange]
41
+ );
42
+ const select = React.useCallback(
43
+ (next) => {
44
+ if (value === void 0) setInternalValue(next);
45
+ onValueChange?.(next);
46
+ },
47
+ [value, onValueChange]
48
+ );
49
+ const registerLabel = React.useCallback((v, label) => {
50
+ setLabels((prev) => prev.get(v) === label ? prev : new Map(prev).set(v, label));
51
+ }, []);
52
+ React.useEffect(() => {
53
+ if (!isOpen) return;
54
+ const onDown = (e) => {
55
+ if (!containerRef.current?.contains(e.target)) setOpen(false);
56
+ };
57
+ document.addEventListener("mousedown", onDown);
58
+ return () => document.removeEventListener("mousedown", onDown);
59
+ }, [isOpen, setOpen]);
60
+ return /* @__PURE__ */ jsx(
61
+ SelectContext.Provider,
62
+ {
63
+ value: { value: currentValue, select, open: isOpen, setOpen, disabled, required, triggerRef, contentId, labels, registerLabel },
64
+ children: /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative inline-block w-full max-w-full", "data-slot": "select", children: [
65
+ children,
66
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: currentValue ?? "" })
67
+ ] })
68
+ }
69
+ );
70
+ }
71
+ var SelectTrigger = React.forwardRef(
72
+ ({ size = "default", className, children, onClick, onKeyDown, ...props }, ref) => {
73
+ const { open, setOpen, disabled, required, triggerRef, contentId, value } = useSelect("SelectTrigger");
74
+ return /* @__PURE__ */ jsxs(
75
+ "button",
12
76
  {
13
- ref,
77
+ ref: (node) => {
78
+ triggerRef.current = node;
79
+ if (typeof ref === "function") ref(node);
80
+ else if (ref) ref.current = node;
81
+ },
82
+ type: "button",
83
+ role: "combobox",
84
+ "aria-expanded": open,
85
+ "aria-haspopup": "listbox",
86
+ "aria-controls": contentId,
87
+ "aria-required": required || void 0,
88
+ disabled,
89
+ "data-slot": "select-trigger",
90
+ "data-size": size,
91
+ "data-placeholder": value === void 0 ? "" : void 0,
92
+ onClick: (e) => {
93
+ onClick?.(e);
94
+ if (!e.defaultPrevented) setOpen(!open);
95
+ },
96
+ onKeyDown: (e) => {
97
+ onKeyDown?.(e);
98
+ if (e.defaultPrevented || open) return;
99
+ if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
100
+ e.preventDefault();
101
+ setOpen(true);
102
+ }
103
+ },
104
+ className: cn(
105
+ "flex w-full items-center justify-between gap-2 whitespace-nowrap rounded-lg border border-input bg-background px-3 text-sm text-foreground shadow-sm outline-none transition-colors",
106
+ "focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
107
+ "aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/20",
108
+ "data-[placeholder]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
109
+ "*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
110
+ size === "sm" ? "h-8" : "h-9",
111
+ className
112
+ ),
113
+ ...props,
114
+ children: [
115
+ children,
116
+ /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": true, className: "size-4 opacity-50" })
117
+ ]
118
+ }
119
+ );
120
+ }
121
+ );
122
+ SelectTrigger.displayName = "SelectTrigger";
123
+ var SelectValue = React.forwardRef(
124
+ ({ placeholder, className, ...props }, ref) => {
125
+ const { value, labels } = useSelect("SelectValue");
126
+ return /* @__PURE__ */ jsx("span", { ref, "data-slot": "select-value", className: cn("truncate", className), ...props, children: value !== void 0 ? labels.get(value) ?? value : placeholder });
127
+ }
128
+ );
129
+ SelectValue.displayName = "SelectValue";
130
+ var optionsOf = (list) => Array.from(list.querySelectorAll('[role="option"]:not([aria-disabled="true"])'));
131
+ var SelectContent = React.forwardRef(
132
+ ({ position = "popper", align = "start", className, children, onKeyDown, ...props }, ref) => {
133
+ const { open, setOpen, value, triggerRef, contentId } = useSelect("SelectContent");
134
+ const listRef = React.useRef(null);
135
+ const typeahead = React.useRef({ text: "", timer: 0 });
136
+ const [up, setUp] = React.useState(false);
137
+ useIsomorphicLayoutEffect(() => {
138
+ if (!open || !listRef.current || !triggerRef.current) return;
139
+ const trigger = triggerRef.current.getBoundingClientRect();
140
+ const { height } = listRef.current.getBoundingClientRect();
141
+ setUp(trigger.bottom + 4 + height > window.innerHeight && trigger.top - 4 - height >= 0);
142
+ const options = optionsOf(listRef.current);
143
+ const selected = options.find((o) => o.dataset.value === value);
144
+ (selected ?? options[0])?.focus();
145
+ }, [open, value, triggerRef]);
146
+ const close = () => {
147
+ setOpen(false);
148
+ triggerRef.current?.focus();
149
+ };
150
+ return /* @__PURE__ */ jsx(
151
+ "div",
152
+ {
153
+ ref: (node) => {
154
+ listRef.current = node;
155
+ if (typeof ref === "function") ref(node);
156
+ else if (ref) ref.current = node;
157
+ },
158
+ id: contentId,
159
+ role: "listbox",
160
+ tabIndex: -1,
161
+ hidden: !open,
162
+ "data-slot": "select-content",
163
+ "data-state": open ? "open" : "closed",
164
+ "data-position": position,
165
+ onKeyDown: (e) => {
166
+ onKeyDown?.(e);
167
+ if (e.defaultPrevented || !listRef.current) return;
168
+ const options = optionsOf(listRef.current);
169
+ const i = options.indexOf(document.activeElement);
170
+ const focus = (n) => options[Math.max(0, Math.min(options.length - 1, n))]?.focus();
171
+ if (e.key === "ArrowDown") e.preventDefault(), focus(i + 1);
172
+ else if (e.key === "ArrowUp") e.preventDefault(), focus(i - 1);
173
+ else if (e.key === "Home") e.preventDefault(), focus(0);
174
+ else if (e.key === "End") e.preventDefault(), focus(options.length - 1);
175
+ else if (e.key === "Escape") e.preventDefault(), close();
176
+ else if (e.key === "Tab") setOpen(false);
177
+ else if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && e.key !== " ") {
178
+ const t = typeahead.current;
179
+ clearTimeout(t.timer);
180
+ t.text += e.key.toLowerCase();
181
+ t.timer = setTimeout(() => t.text = "", 500);
182
+ const start = i + (t.text.length === 1 ? 1 : 0);
183
+ const ordered = [...options.slice(start), ...options.slice(0, start)];
184
+ ordered.find((o) => (o.textContent ?? "").trim().toLowerCase().startsWith(t.text))?.focus();
185
+ }
186
+ },
14
187
  className: cn(
15
- "h-9 w-full appearance-none rounded-lg border border-input bg-background pl-3 pr-8 text-sm text-foreground shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
188
+ "absolute z-50 max-h-72 min-w-[8rem] w-full overflow-y-auto overflow-x-hidden rounded-lg border border-border bg-background p-1 text-foreground shadow-md outline-none animate-nx-fade-in",
189
+ up ? "bottom-full mb-1" : "top-full mt-1",
190
+ align === "end" ? "right-0" : align === "center" ? "left-1/2 -translate-x-1/2" : "left-0",
16
191
  className
17
192
  ),
18
193
  ...props,
19
194
  children
20
195
  }
21
- ),
22
- /* @__PURE__ */ jsx(ChevronDown, { className: "pointer-events-none absolute right-2 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" })
23
- ] })
196
+ );
197
+ }
198
+ );
199
+ SelectContent.displayName = "SelectContent";
200
+ var SelectItem = React.forwardRef(
201
+ ({ value, disabled = false, textValue, className, children, onClick, onKeyDown, ...props }, ref) => {
202
+ const { value: selected, select, setOpen, triggerRef, registerLabel } = useSelect("SelectItem");
203
+ const isSelected = selected === value;
204
+ React.useEffect(() => registerLabel(value, textValue ?? children), [value, textValue, children, registerLabel]);
205
+ const choose = () => {
206
+ if (disabled) return;
207
+ select(value);
208
+ setOpen(false);
209
+ triggerRef.current?.focus();
210
+ };
211
+ return /* @__PURE__ */ jsxs(
212
+ "div",
213
+ {
214
+ ref,
215
+ role: "option",
216
+ tabIndex: -1,
217
+ "aria-selected": isSelected,
218
+ "aria-disabled": disabled || void 0,
219
+ "data-slot": "select-item",
220
+ "data-value": value,
221
+ "data-state": isSelected ? "checked" : "unchecked",
222
+ "data-disabled": disabled ? "" : void 0,
223
+ onClick: (e) => {
224
+ onClick?.(e);
225
+ if (!e.defaultPrevented) choose();
226
+ },
227
+ onKeyDown: (e) => {
228
+ onKeyDown?.(e);
229
+ if (!e.defaultPrevented && (e.key === "Enter" || e.key === " ")) {
230
+ e.preventDefault();
231
+ choose();
232
+ }
233
+ },
234
+ onMouseMove: (e) => {
235
+ if (!disabled && document.activeElement !== e.currentTarget) e.currentTarget.focus();
236
+ },
237
+ className: cn(
238
+ "relative flex w-full cursor-default select-none items-center gap-2 rounded-md py-1.5 pl-2 pr-8 text-sm outline-none",
239
+ "focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
240
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
241
+ className
242
+ ),
243
+ ...props,
244
+ children: [
245
+ /* @__PURE__ */ jsx("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", "aria-hidden": true, children: isSelected && /* @__PURE__ */ jsx(Check, { className: "size-4" }) }),
246
+ /* @__PURE__ */ jsx("span", { className: "flex items-center gap-2", children })
247
+ ]
248
+ }
249
+ );
250
+ }
251
+ );
252
+ SelectItem.displayName = "SelectItem";
253
+ var SelectGroup = React.forwardRef(
254
+ (props, ref) => /* @__PURE__ */ jsx("div", { ref, role: "group", "data-slot": "select-group", ...props })
255
+ );
256
+ SelectGroup.displayName = "SelectGroup";
257
+ var SelectLabel = React.forwardRef(
258
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, "data-slot": "select-label", className: cn("px-2 py-1.5 text-xs text-muted-foreground", className), ...props })
259
+ );
260
+ SelectLabel.displayName = "SelectLabel";
261
+ var SelectSeparator = React.forwardRef(
262
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, "aria-hidden": true, "data-slot": "select-separator", className: cn("pointer-events-none -mx-1 my-1 h-px bg-border", className), ...props })
24
263
  );
25
- Select.displayName = "Select";
264
+ SelectSeparator.displayName = "SelectSeparator";
265
+ var SelectScrollUpButton = ({ className, children, ...props }) => children ? /* @__PURE__ */ jsx("div", { "data-slot": "select-scroll-up-button", className: cn("flex items-center justify-center py-1", className), ...props, children }) : null;
266
+ SelectScrollUpButton.displayName = "SelectScrollUpButton";
267
+ var SelectScrollDownButton = ({ className, children, ...props }) => children ? /* @__PURE__ */ jsx("div", { "data-slot": "select-scroll-down-button", className: cn("flex items-center justify-center py-1", className), ...props, children }) : null;
268
+ SelectScrollDownButton.displayName = "SelectScrollDownButton";
26
269
 
27
- export { Select };
270
+ export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue };
28
271
  //# sourceMappingURL=index.js.map
29
272
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":";;;;;;AAcO,IAAM,MAAA,GAAe,KAAA,CAAA,UAAA;AAAA,EAC1B,CAAC,EAAE,SAAA,EAAW,kBAAA,EAAoB,UAAU,GAAG,KAAA,EAAM,EAAG,GAAA,0BACrD,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,6BAAA,EAA+B,kBAAkB,CAAA,EAClE,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,+PAAA;AAAA,UACA;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEH;AAAA;AAAA,KACH;AAAA,oBACA,GAAA,CAAC,WAAA,EAAA,EAAY,SAAA,EAAU,4FAAA,EAA6F;AAAA,GAAA,EACtH;AAEJ;AACA,MAAA,CAAO,WAAA,GAAc,QAAA","file":"index.js","sourcesContent":["import * as React from \"react\";\nimport { ChevronDown } from \"lucide-react\";\nimport { cn } from \"@nucleux/utils\";\n\nexport interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {\n /** Wrapper className (the chevron is positioned within it). */\n containerClassName?: string;\n}\n\n/**\n * An accessible select built on the native `<select>` element (full keyboard +\n * screen-reader support), styled to match the design system with a chevron.\n * Pass `<option>`s as children.\n */\nexport const Select = React.forwardRef<HTMLSelectElement, SelectProps>(\n ({ className, containerClassName, children, ...props }, ref) => (\n <div className={cn(\"relative inline-flex w-full\", containerClassName)}>\n <select\n ref={ref}\n className={cn(\n \"h-9 w-full appearance-none rounded-lg border border-input bg-background pl-3 pr-8 text-sm text-foreground shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n {children}\n </select>\n <ChevronDown className=\"pointer-events-none absolute right-2 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n </div>\n ),\n);\nSelect.displayName = \"Select\";\n"]}
1
+ {"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import * as React from \"react\";\nimport { Check, ChevronDown } from \"lucide-react\";\nimport { cn } from \"@nucleux/utils\";\n\n// useLayoutEffect warns when rendered on the server; fall back to useEffect there.\nconst useIsomorphicLayoutEffect = typeof window !== \"undefined\" ? React.useLayoutEffect : React.useEffect;\n\ninterface SelectContextValue {\n value: string | undefined;\n select: (value: string) => void;\n open: boolean;\n setOpen: (open: boolean) => void;\n disabled: boolean;\n required: boolean;\n triggerRef: React.RefObject<HTMLButtonElement>;\n contentId: string;\n labels: Map<string, React.ReactNode>;\n registerLabel: (value: string, label: React.ReactNode) => void;\n}\nconst SelectContext = React.createContext<SelectContextValue | null>(null);\n\nfunction useSelect(component: string) {\n const ctx = React.useContext(SelectContext);\n if (!ctx) throw new Error(`<${component}> must be used inside <Select>`);\n return ctx;\n}\n\nexport interface SelectProps {\n /** Selected value (controlled). */\n value?: string;\n /** Initially selected value (uncontrolled). */\n defaultValue?: string;\n /** Called with the newly selected value. */\n onValueChange?: (value: string) => void;\n /** Whether the list is open (controlled). */\n open?: boolean;\n /** Initial open state (uncontrolled). */\n defaultOpen?: boolean;\n /** Called when the list opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Disable the whole select. */\n disabled?: boolean;\n /** Mark as required in forms. */\n required?: boolean;\n /** Form field name; when set, the value is submitted with the form. */\n name?: string;\n children: React.ReactNode;\n}\n\n/**\n * A custom select: a trigger button showing the chosen option and a listbox of\n * SelectItems. Compose SelectTrigger (with SelectValue) and SelectContent.\n * Keyboard: Arrow keys, Home/End and typing a letter move between options;\n * Enter or Space chooses; Escape closes and returns focus to the trigger.\n * For a plain native `<select>`, use NativeSelect.\n */\nexport function Select({\n value,\n defaultValue,\n onValueChange,\n open,\n defaultOpen = false,\n onOpenChange,\n disabled = false,\n required = false,\n name,\n children,\n}: SelectProps) {\n const [internalValue, setInternalValue] = React.useState(defaultValue);\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen);\n const currentValue = value !== undefined ? value : internalValue;\n const isOpen = open !== undefined ? open : internalOpen;\n const triggerRef = React.useRef<HTMLButtonElement>(null);\n const containerRef = React.useRef<HTMLDivElement>(null);\n const contentId = React.useId();\n const [labels, setLabels] = React.useState<Map<string, React.ReactNode>>(() => new Map());\n\n const setOpen = React.useCallback(\n (next: boolean) => {\n if (open === undefined) setInternalOpen(next);\n onOpenChange?.(next);\n },\n [open, onOpenChange],\n );\n const select = React.useCallback(\n (next: string) => {\n if (value === undefined) setInternalValue(next);\n onValueChange?.(next);\n },\n [value, onValueChange],\n );\n const registerLabel = React.useCallback((v: string, label: React.ReactNode) => {\n setLabels((prev) => (prev.get(v) === label ? prev : new Map(prev).set(v, label)));\n }, []);\n\n React.useEffect(() => {\n if (!isOpen) return;\n const onDown = (e: MouseEvent) => {\n if (!containerRef.current?.contains(e.target as Node)) setOpen(false);\n };\n document.addEventListener(\"mousedown\", onDown);\n return () => document.removeEventListener(\"mousedown\", onDown);\n }, [isOpen, setOpen]);\n\n return (\n <SelectContext.Provider\n value={{ value: currentValue, select, open: isOpen, setOpen, disabled, required, triggerRef, contentId, labels, registerLabel }}\n >\n <div ref={containerRef} className=\"relative inline-block w-full max-w-full\" data-slot=\"select\">\n {children}\n {name && <input type=\"hidden\" name={name} value={currentValue ?? \"\"} />}\n </div>\n </SelectContext.Provider>\n );\n}\n\nexport interface SelectTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** \"default\" (h-9) or \"sm\" (h-8). */\n size?: \"default\" | \"sm\";\n}\n\n/** The button that shows the current value and opens the list. Put a SelectValue inside. */\nexport const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(\n ({ size = \"default\", className, children, onClick, onKeyDown, ...props }, ref) => {\n const { open, setOpen, disabled, required, triggerRef, contentId, value } = useSelect(\"SelectTrigger\");\n return (\n <button\n ref={(node) => {\n (triggerRef as React.MutableRefObject<HTMLButtonElement | null>).current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n }}\n type=\"button\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-controls={contentId}\n aria-required={required || undefined}\n disabled={disabled}\n data-slot=\"select-trigger\"\n data-size={size}\n data-placeholder={value === undefined ? \"\" : undefined}\n onClick={(e) => {\n onClick?.(e);\n if (!e.defaultPrevented) setOpen(!open);\n }}\n onKeyDown={(e) => {\n onKeyDown?.(e);\n if (e.defaultPrevented || open) return;\n if ([\"ArrowDown\", \"ArrowUp\", \"Enter\", \" \"].includes(e.key)) {\n e.preventDefault();\n setOpen(true);\n }\n }}\n className={cn(\n \"flex w-full items-center justify-between gap-2 whitespace-nowrap rounded-lg border border-input bg-background px-3 text-sm text-foreground shadow-sm outline-none transition-colors\",\n \"focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\",\n \"aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/20\",\n \"data-[placeholder]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n \"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2\",\n size === \"sm\" ? \"h-8\" : \"h-9\",\n className,\n )}\n {...props}\n >\n {children}\n <ChevronDown aria-hidden className=\"size-4 opacity-50\" />\n </button>\n );\n },\n);\nSelectTrigger.displayName = \"SelectTrigger\";\n\nexport interface SelectValueProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Shown while nothing is selected. */\n placeholder?: React.ReactNode;\n}\n\n/** Shows the selected option's label (or the placeholder) inside SelectTrigger. */\nexport const SelectValue = React.forwardRef<HTMLSpanElement, SelectValueProps>(\n ({ placeholder, className, ...props }, ref) => {\n const { value, labels } = useSelect(\"SelectValue\");\n return (\n <span ref={ref} data-slot=\"select-value\" className={cn(\"truncate\", className)} {...props}>\n {value !== undefined ? (labels.get(value) ?? value) : placeholder}\n </span>\n );\n },\n);\nSelectValue.displayName = \"SelectValue\";\n\nconst optionsOf = (list: HTMLElement) =>\n Array.from(list.querySelectorAll<HTMLElement>('[role=\"option\"]:not([aria-disabled=\"true\"])'));\n\nexport interface SelectContentProps extends React.HTMLAttributes<HTMLDivElement> {\n /** \"popper\" (default) places the list below the trigger, flipping above when there is no room. */\n position?: \"popper\" | \"item-aligned\";\n /** Horizontal alignment relative to the trigger. */\n align?: \"start\" | \"center\" | \"end\";\n}\n\n/**\n * The listbox of SelectItems. It stays mounted (hidden) while closed so the\n * trigger can show the chosen item's label.\n */\nexport const SelectContent = React.forwardRef<HTMLDivElement, SelectContentProps>(\n ({ position = \"popper\", align = \"start\", className, children, onKeyDown, ...props }, ref) => {\n const { open, setOpen, value, triggerRef, contentId } = useSelect(\"SelectContent\");\n const listRef = React.useRef<HTMLDivElement | null>(null);\n const typeahead = React.useRef({ text: \"\", timer: 0 as unknown as ReturnType<typeof setTimeout> });\n const [up, setUp] = React.useState(false);\n\n useIsomorphicLayoutEffect(() => {\n if (!open || !listRef.current || !triggerRef.current) return;\n const trigger = triggerRef.current.getBoundingClientRect();\n const { height } = listRef.current.getBoundingClientRect();\n setUp(trigger.bottom + 4 + height > window.innerHeight && trigger.top - 4 - height >= 0);\n const options = optionsOf(listRef.current);\n const selected = options.find((o) => o.dataset.value === value);\n (selected ?? options[0])?.focus();\n }, [open, value, triggerRef]);\n\n const close = () => {\n setOpen(false);\n triggerRef.current?.focus();\n };\n\n return (\n <div\n ref={(node) => {\n listRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n }}\n id={contentId}\n role=\"listbox\"\n tabIndex={-1}\n hidden={!open}\n data-slot=\"select-content\"\n data-state={open ? \"open\" : \"closed\"}\n data-position={position}\n onKeyDown={(e) => {\n onKeyDown?.(e);\n if (e.defaultPrevented || !listRef.current) return;\n const options = optionsOf(listRef.current);\n const i = options.indexOf(document.activeElement as HTMLElement);\n const focus = (n: number) => options[Math.max(0, Math.min(options.length - 1, n))]?.focus();\n if (e.key === \"ArrowDown\") (e.preventDefault(), focus(i + 1));\n else if (e.key === \"ArrowUp\") (e.preventDefault(), focus(i - 1));\n else if (e.key === \"Home\") (e.preventDefault(), focus(0));\n else if (e.key === \"End\") (e.preventDefault(), focus(options.length - 1));\n else if (e.key === \"Escape\") (e.preventDefault(), close());\n else if (e.key === \"Tab\") setOpen(false);\n else if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && e.key !== \" \") {\n const t = typeahead.current;\n clearTimeout(t.timer);\n t.text += e.key.toLowerCase();\n t.timer = setTimeout(() => (t.text = \"\"), 500);\n const start = i + (t.text.length === 1 ? 1 : 0);\n const ordered = [...options.slice(start), ...options.slice(0, start)];\n ordered.find((o) => (o.textContent ?? \"\").trim().toLowerCase().startsWith(t.text))?.focus();\n }\n }}\n className={cn(\n \"absolute z-50 max-h-72 min-w-[8rem] w-full overflow-y-auto overflow-x-hidden rounded-lg border border-border bg-background p-1 text-foreground shadow-md outline-none animate-nx-fade-in\",\n up ? \"bottom-full mb-1\" : \"top-full mt-1\",\n align === \"end\" ? \"right-0\" : align === \"center\" ? \"left-1/2 -translate-x-1/2\" : \"left-0\",\n className,\n )}\n {...props}\n >\n {children}\n </div>\n );\n },\n);\nSelectContent.displayName = \"SelectContent\";\n\nexport interface SelectItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** The value this option sets. */\n value: string;\n /** Not selectable. */\n disabled?: boolean;\n /** Plain-text label used in the trigger when the children are rich. */\n textValue?: string;\n}\n\n/** One option. Shows a check mark when selected. */\nexport const SelectItem = React.forwardRef<HTMLDivElement, SelectItemProps>(\n ({ value, disabled = false, textValue, className, children, onClick, onKeyDown, ...props }, ref) => {\n const { value: selected, select, setOpen, triggerRef, registerLabel } = useSelect(\"SelectItem\");\n const isSelected = selected === value;\n React.useEffect(() => registerLabel(value, textValue ?? children), [value, textValue, children, registerLabel]);\n const choose = () => {\n if (disabled) return;\n select(value);\n setOpen(false);\n triggerRef.current?.focus();\n };\n return (\n <div\n ref={ref}\n role=\"option\"\n tabIndex={-1}\n aria-selected={isSelected}\n aria-disabled={disabled || undefined}\n data-slot=\"select-item\"\n data-value={value}\n data-state={isSelected ? \"checked\" : \"unchecked\"}\n data-disabled={disabled ? \"\" : undefined}\n onClick={(e) => {\n onClick?.(e);\n if (!e.defaultPrevented) choose();\n }}\n onKeyDown={(e) => {\n onKeyDown?.(e);\n if (!e.defaultPrevented && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n choose();\n }\n }}\n onMouseMove={(e) => {\n if (!disabled && document.activeElement !== e.currentTarget) e.currentTarget.focus();\n }}\n className={cn(\n \"relative flex w-full cursor-default select-none items-center gap-2 rounded-md py-1.5 pl-2 pr-8 text-sm outline-none\",\n \"focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className,\n )}\n {...props}\n >\n <span className=\"absolute right-2 flex size-3.5 items-center justify-center\" aria-hidden>\n {isSelected && <Check className=\"size-4\" />}\n </span>\n <span className=\"flex items-center gap-2\">{children}</span>\n </div>\n );\n },\n);\nSelectItem.displayName = \"SelectItem\";\n\n/** Groups related options; pair with SelectLabel. */\nexport const SelectGroup = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n (props, ref) => <div ref={ref} role=\"group\" data-slot=\"select-group\" {...props} />,\n);\nSelectGroup.displayName = \"SelectGroup\";\n\n/** Non-selectable heading for a SelectGroup. */\nexport const SelectLabel = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div ref={ref} data-slot=\"select-label\" className={cn(\"px-2 py-1.5 text-xs text-muted-foreground\", className)} {...props} />\n ),\n);\nSelectLabel.displayName = \"SelectLabel\";\n\n/** Hairline between groups (decorative: a listbox may only contain options and groups). */\nexport const SelectSeparator = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div ref={ref} aria-hidden data-slot=\"select-separator\" className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)} {...props} />\n ),\n);\nSelectSeparator.displayName = \"SelectSeparator\";\n\n/**\n * Kept for shadcn API parity. Nucleux's SelectContent scrolls natively, so\n * these render a small static affordance only when given children.\n */\nexport const SelectScrollUpButton = ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) =>\n children ? (\n <div data-slot=\"select-scroll-up-button\" className={cn(\"flex items-center justify-center py-1\", className)} {...props}>\n {children}\n </div>\n ) : null;\nSelectScrollUpButton.displayName = \"SelectScrollUpButton\";\n\n/** See SelectScrollUpButton. */\nexport const SelectScrollDownButton = ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) =>\n children ? (\n <div data-slot=\"select-scroll-down-button\" className={cn(\"flex items-center justify-center py-1\", className)} {...props}>\n {children}\n </div>\n ) : null;\nSelectScrollDownButton.displayName = \"SelectScrollDownButton\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nucleux/select",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.cjs",
@@ -22,7 +22,7 @@
22
22
  "react-dom": ">=18"
23
23
  },
24
24
  "dependencies": {
25
- "@nucleux/utils": "0.5.0",
25
+ "@nucleux/utils": "0.7.0",
26
26
  "lucide-react": "^0.469.0"
27
27
  },
28
28
  "devDependencies": {
@@ -41,7 +41,7 @@
41
41
  "directory": "packages/select"
42
42
  },
43
43
  "homepage": "https://github.com/intelligaia-open-labs/nucleux#readme",
44
- "description": "Accessible native-backed select with a chevron — Nucleux agentic UI for React.",
44
+ "description": "Select — a custom listbox select with a trigger, groups and keyboard navigation — Nucleux agentic UI for React.",
45
45
  "keywords": [
46
46
  "nucleux",
47
47
  "react",