Skip to main content

GenericSelect

The abstract base for popup-driven selection controls: an Ant Design Select-styled trigger whose dropdown body is swapped for an arbitrary surface. It owns the styled trigger (selected-value display, size/status/variant/clear/disabled), the controlled open state, the in-trigger search box, and the popup container; a concrete component supplies only the popup body and how a value renders.

VEF-specific component. Not part of Ant Design. It is the foundation IconPicker is built on.

When to Use

  • Building a custom dropdown-style selection control — an icon grid, a data-table picker, a color-swatch picker — that needs Select's trigger affordances (value display, size/status/variant, clear button, search box) but a non-list popup surface.
  • Reach for Select directly when a flat option list is enough; reach for GenericSelect when the popup body is a custom widget.

Basic Usage

import { GenericSelect } from '@vef-framework-react/components';
import type { GenericSelectPopupApi } from '@vef-framework-react/components';
import { useState } from 'react';

function ColorPopup({ value, select, close }: GenericSelectPopupApi<string>) {
return (
<div style={{ display: 'flex', gap: 8, padding: 8 }}>
{['red', 'green', 'blue'].map((color) => (
<button
key={color}
style={{ background: color, width: 24, height: 24 }}
onClick={() => {
select(color);
close();
}}
/>
))}
</div>
);
}

export default function Demo() {
const [color, setColor] = useState<string | null>(null);

return (
<GenericSelect<string>
placeholder="Pick a color"
value={color}
renderLabel={(value) => value}
renderPopup={(api) => <ColorPopup {...api} />}
onChange={setColor}
/>
);
}

The Popup API

renderPopup receives a GenericSelectPopupApi object so the popup body never touches the underlying antd Select:

MemberTypeDescription
valueTValue | nullThe currently selected value, for highlighting the active option
keywordstringThe debounced search keyword typed into the trigger, "" when not searching
openbooleanWhether the popup is currently open — useful for deferring expensive work until first open
select(value: TValue) => voidCommit a value; forwards to onChange. Does not close the popup
close() => voidClose the popup without changing the value

Custom Label Rendering

<GenericSelect<string>
value={value}
renderLabel={(value) => <strong>{value}</strong>}
renderPopup={renderPopup}
onChange={setValue}
/>

Controlled Open State

const [isOpen, setIsOpen] = useState(false);

<GenericSelect<string>
open={isOpen}
renderPopup={renderPopup}
onOpenChange={setIsOpen}
/>

API

GenericSelectProps<TValue>

TValue is the selectable value type, forwarded to the underlying antd Select — it must be a string or number.

PropTypeDefaultDescription
valueTValue | nullThe selected value; null/undefined renders the placeholder
onChange(value: TValue | null) => voidFired when a value is committed from the popup, or null when cleared
onBlur() => voidFired when the trigger loses focus
openbooleanControlled open state of the popup
defaultOpenbooleanfalseInitial open state for uncontrolled mode; ignored when open is provided
onOpenChange(open: boolean) => voidFired when the popup opens or closes
renderPopup(api: GenericSelectPopupApi<TValue>) => ReactNoderequiredRender the popup body
renderLabel(value: TValue) => ReactNoderaw valueRender the selected value inside the trigger
searchablebooleantrueShow the search box in the trigger and forward the keyword to the popup
placeholderReactNodePlaceholder shown when no value is selected
size'small' | 'medium' | 'large''medium'Density preset
status'error' | 'warning'Validation status
variant'outlined' | 'filled' | 'borderless' | 'underlined''outlined'Visual variant of the trigger
disabledbooleanfalseDisable the control
allowClearbooleanfalseShow a clear button that resets the value to null
loadingbooleanfalseRender the trigger in a loading state
suffixIconReactNodeCustom suffix icon; overridden by a search icon while open and searchable
prefixReactNodeContent rendered before the value inside the trigger
getPopupContainer(triggerNode: HTMLElement) => HTMLElementdocument.bodyElement the popup renders into
popupMatchSelectWidthboolean | numberfalseWhether the popup width matches the trigger width
popupClassNamestringAdditional class on the popup root
classNamestringAdditional class on the trigger
styleCSSPropertiesInline style on the trigger

GenericSelectRef

MemberTypeDescription
focus() => voidMove focus into the trigger
blur() => voidRemove focus from the trigger

GenericSelectStatus / GenericSelectVariant

type GenericSelectStatus = "error" | "warning";
type GenericSelectVariant = "outlined" | "filled" | "borderless" | "underlined";