Wire admin studio SSR and search infrastructure

This commit is contained in:
2026-05-01 11:46:06 +02:00
parent 257b0dbef6
commit 18cea8b0f0
329 changed files with 197465 additions and 2741 deletions

View File

@@ -26,6 +26,8 @@ import { createPortal } from 'react-dom'
* @prop {boolean} required - asterisk on label
* @prop {boolean} disabled
* @prop {function} renderOption - custom render fn: (option) => ReactNode
* @prop {function} renderValue - custom render fn for single-value trigger: (option) => ReactNode
* @prop {string} searchPlaceholder - placeholder shown in the dropdown search input
*/
export default function NovaSelect({
options = [],
@@ -41,8 +43,10 @@ export default function NovaSelect({
required = false,
disabled = false,
renderOption,
renderValue,
id,
className = '',
searchPlaceholder = 'Search…',
}) {
const [open, setOpen] = useState(false)
const [search, setSearch] = useState('')
@@ -211,9 +215,10 @@ export default function NovaSelect({
}, [open, filtered, highlighted, search, multi, selected, selectOption, closeDropdown, openDropdown, onChange])
// Build display label(s)
const optionMap = useMemo(() => Object.fromEntries(options.map((o) => [String(o.value), o])), [options])
const labelMap = useMemo(() => Object.fromEntries(options.map((o) => [String(o.value), o.label])), [options])
const hasValue = selected.length > 0
const selectedOption = !multi && hasValue ? optionMap[String(selected[0])] ?? null : null
// Trigger appearance
const triggerClass = [
@@ -273,7 +278,9 @@ export default function NovaSelect({
))}
{!multi && hasValue && (
<span className="truncate text-white">{labelMap[String(selected[0])] ?? selected[0]}</span>
renderValue && selectedOption
? renderValue(selectedOption)
: <span className="truncate text-white">{labelMap[String(selected[0])] ?? selected[0]}</span>
)}
{!hasValue && (
@@ -339,7 +346,7 @@ export default function NovaSelect({
value={search}
onChange={(e) => { setSearch(e.target.value); setHigh(0) }}
onKeyDown={handleKeyDown}
placeholder="Search…"
placeholder={searchPlaceholder}
className="w-full pl-3 pr-7 py-1.5 rounded-lg bg-white/5 border border-white/8 text-white text-xs placeholder:text-slate-500 focus:outline-none focus:ring-1 focus:ring-accent/50"
autoComplete="off"
/>