componente · @solu30/ui-kit/input
Select
Selector de una opción que suma buscador a partir de cierta cantidad de opciones, con limpieza opcional.
Seguir leyendo — 3 párrafos más
Custom select component with integrated search, filtering, and clearable support. Uses CSS variables for theming: --ui-bg-raised, --ui-accent, --ui-border, --ui-content-primary, --ui-error.
NOTE (portal ancestry): The floating panel is portalled to document.body. It is NOT a DOM descendant of the trigger wrapper. CSS :focus-within on the wrapper, descendant selectors targeting the panel, and ancestor-based DOM queries (e.g. .closest()) will NOT reach the panel. Target the panel via [data-ui-overlay="select"] instead.
A11Y (WAI-ARIA APG, "select-only combobox" + managed focus): the trigger is role="combobox" — query it with getByRole('combobox'), NOT 'button'. It carries aria-expanded / aria-controls / aria-invalid / aria-describedby, and takes its accessible name from label (wired via aria-labelledby) or, when there is no visible label, from the ariaLabel prop. error renders with role="alert" so validation failures are announced when they appear.
325 sitios de import en 10 de 15 verticales — un archivo que la importa cuenta una vez, monte una instancia o veinte.
- TheCharterPanel
- carlosmartinpavon
- cmp-packages
- dietaly
- easycharter
- elliot
- gestorsalon
- luigi
- raquetea
- zentrya
El componente, montado
Cargando el espécimen…
Éste es el conjunto completo. Abajo, los mismos casos por separado, cada uno con su código.
Cómo se usa
import { Select } from '@solu30/ui-kit/input'<Select
label="Tipo de evento"
value={tipo}
options={[
{ id: "boda", name: "Boda" },
{ id: "cumple", name: "Cumpleaños" },
]}
onChange={(o) => setTipo(o.id)}
/>Props
| Prop | Requerida | Tipo | Qué hace |
|---|---|---|---|
| label | opcional | string | Label for the select |
| value | sí | T | Currently selected value |
| options | sí | SelectOption<T>[] | Available options |
| onChange | sí | (option: SelectOption<T>) => void | Callback when selection changes |
| placeholder | opcional | string | Placeholder when nothing is selected |
| disabled | opcional | boolean | Whether the select is disabled |
| className | opcional | string | Additional CSS class |
| error | opcional | string | Error message to display |
| helpText | opcional | string | Help text to display below the select |
| fullWidth | opcional | boolean | Whether the select should take full width |
| clearable | opcional | boolean | Whether the select allows clearing the selection |
| onClear | opcional | () => void | Callback when selection is cleared |
| searchThreshold | opcional | number | Minimum options count to show the search input (default: 10) |
| searchPlaceholder | opcional | string | Placeholder for the search input |
| noResultsText | opcional | string | Text shown when no options match the search |
| ariaLabel | opcional | string | Accessible name when there is no visible `label`. A11y: `role="combobox"` does NOT take its name from content, so a Select without `label` and without this prop is announced as an unnamed combobox. Pass one whenever the field's purpose lives in surrounding layout (toolbar filters, inline table editors) instead of a visible label. |
| clearLabel | opcional | string | Accessible name for the clear (×) affordance. Only rendered when `clearable`. |
Simple
Para elegir un valor de una lista corta y fija, cuando alcanza con label y placeholder — sin borrado propio ni error.
Cargando el espécimen…
export function SelectSimple() {
const [eventType, setEventType] = useState<EventTypeId>('boda')
return (
<>
<Select<EventTypeId>
label="Tipo de evento"
value={eventType}
options={EVENT_TYPE_OPTIONS}
onChange={(o) => setEventType(o.id)}
/>
</>
)
}De specimens/input.tsx · SelectSimple: el espécimen del Muestrario, no un ejemplo oficial del paquete. Muestra cómo se usa la pieza de verdad, montada contra los tipos instalados —eso es lo que vale— pero no es documentación del kit.
Con borrado
Cuando el campo puede quedar sin selección después de haber elegido algo — el filtro de un listado, no un campo obligatorio de un formulario.
Cargando el espécimen…
export function SelectClearable() {
const [dock, setDock] = useState('')
return (
<>
<Select
label="Muelle de zarpe"
value={dock}
options={DOCK_OPTIONS}
onChange={(o) => setDock(String(o.id))}
placeholder="Elegí un muelle"
clearable
onClear={() => setDock('')}
/>
</>
)
}De specimens/input.tsx · SelectClearable: el espécimen del Muestrario, no un ejemplo oficial del paquete. Muestra cómo se usa la pieza de verdad, montada contra los tipos instalados —eso es lo que vale— pero no es documentación del kit.
Deshabilitado
Cuando no hay opciones disponibles todavía y el campo tiene que mostrarlo sin dejar interactuar — el placeholder explica el motivo, no un label vacío.
Cargando el espécimen…
export function SelectDeshabilitado() {
return (
<>
<Select
label="Salón"
value=""
options={SALON_OPTIONS}
onChange={() => {}}
disabled
placeholder="Sin salones disponibles"
/>
</>
)
}De specimens/input.tsx · SelectDeshabilitado: el espécimen del Muestrario, no un ejemplo oficial del paquete. Muestra cómo se usa la pieza de verdad, montada contra los tipos instalados —eso es lo que vale— pero no es documentación del kit.
Con error
Para marcar que el campo quedó sin completar en una validación — el mensaje va pegado al campo, no en un aviso aparte.
Cargando el espécimen…
export function SelectError() {
return (
<>
<Select
label="Categoría del combo"
value=""
options={FLAVOR_CATEGORY_OPTIONS}
onChange={() => {}}
error="Elegí una categoría antes de publicar el combo"
/>
</>
)
}De specimens/input.tsx · SelectError: el espécimen del Muestrario, no un ejemplo oficial del paquete. Muestra cómo se usa la pieza de verdad, montada contra los tipos instalados —eso es lo que vale— pero no es documentación del kit.
Composición
Cuando el select necesita grupos con encabezado propio o ítems deshabilitados dentro de la lista — la API de composición estilo Radix. A diferencia de «Select», esta no tiene prop «error»: mismo concepto, dos vocabularios en la misma familia.
Cargando el espécimen…
export function SelectCompositionMatrix() {
const [flavor, setFlavor] = useState('dulce-de-leche')
return (
<Stage label="SelectRoot · composición, agrupada, sin prop de error" tall wide>
<div className="grid w-full grid-cols-1 gap-3 sm:grid-cols-2">
<SelectRoot value={flavor} onValueChange={setFlavor}>
<SelectTrigger>
<SelectValue placeholder="Elegí un sabor" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Clásicos</SelectLabel>
<SelectItem value="dulce-de-leche">Dulce de leche</SelectItem>
<SelectItem value="chocolate">Chocolate amargo</SelectItem>
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>De estación</SelectLabel>
<SelectItem value="frutilla">Frutilla</SelectItem>
<SelectItem value="mango" disabled>
Mango (sin stock)
</SelectItem>
</SelectGroup>
</SelectContent>
</SelectRoot>
<SelectRoot value="" onValueChange={() => {}} disabled>
<SelectTrigger>
<SelectValue placeholder="Franquicia cerrada temporalmente" />
</SelectTrigger>
<SelectContent>
<SelectItem value="a">—</SelectItem>
</SelectContent>
</SelectRoot>
</div>
</Stage>
)
}De specimens/input.tsx · SelectCompositionMatrix: el espécimen del Muestrario, no un ejemplo oficial del paquete. Muestra cómo se usa la pieza de verdad, montada contra los tipos instalados —eso es lo que vale— pero no es documentación del kit.
Ejemplo del paquete
<Select
label="Status"
value={status}
options={[
{ id: 'active', name: 'Active' },
{ id: 'inactive', name: 'Inactive' }
]}
onChange={handleChange}
/>
<Select<number>
label="Category"
value={categoryId}
options={categories}
onChange={handleCategoryChange}
error="Must select a category"
clearable
/>Del @example del JSDoc de @solu30/ui-kit, sin editar: son 65 de los 541 símbolos. La sangría y los renglones en blanco son los del paquete.