@@ -17,9 +17,9 @@ import {
1717 useId ,
1818 useRef ,
1919} from 'react' ;
20- export interface SelectOption {
20+ export interface SelectOption < T extends React . Key = string > {
2121 label : string ;
22- value : string ;
22+ value : T ;
2323}
2424
2525export interface SelectUIComponents {
@@ -34,10 +34,11 @@ export interface SelectUIComponents {
3434
3535export type SelectContentProps = Pick < ComponentProps < typeof PopoverPrimitive . Content > , 'align' | 'side' | 'sideOffset' > ;
3636
37- export interface SelectProps extends Omit < React . ButtonHTMLAttributes < HTMLButtonElement > , 'value' | 'onChange' > {
38- options : SelectOption [ ] ;
39- value ?: string ;
40- onValueChange ?: ( value : string ) => void ;
37+ export interface SelectProps < T extends React . Key = string >
38+ extends Omit < React . ButtonHTMLAttributes < HTMLButtonElement > , 'value' | 'onChange' > {
39+ options : SelectOption < T > [ ] ;
40+ value ?: T ;
41+ onValueChange ?: ( value : T ) => void ;
4142 placeholder ?: string ;
4243 disabled ?: boolean ;
4344 className ?: string ;
@@ -50,7 +51,7 @@ export interface SelectProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonE
5051 searchInputProps ?: React . ComponentPropsWithoutRef < typeof CommandInput > ;
5152 // Creatable behavior
5253 creatable ?: boolean ;
53- onCreateOption ?: ( input : string ) => SelectOption | Promise < SelectOption > ;
54+ onCreateOption ?: ( input : string ) => SelectOption < T > | Promise < SelectOption < T > > ;
5455 createOptionLabel ?: ( input : string ) => string ;
5556}
5657
@@ -60,7 +61,7 @@ const DefaultSearchInput = forwardRef<HTMLInputElement, React.ComponentPropsWith
6061) ;
6162DefaultSearchInput . displayName = 'SelectSearchInput' ;
6263
63- export function Select ( {
64+ export function Select < T extends React . Key = string > ( {
6465 options,
6566 value,
6667 onValueChange,
@@ -77,7 +78,7 @@ export function Select({
7778 onCreateOption,
7879 createOptionLabel,
7980 ...buttonProps
80- } : SelectProps ) {
81+ } : SelectProps < T > ) {
8182 const popoverState = useOverlayTriggerState ( { } ) ;
8283 const listboxId = useId ( ) ;
8384 const triggerRef = useRef < HTMLButtonElement > ( null ) ;
@@ -132,7 +133,7 @@ export function Select({
132133 aria-controls = { listboxId }
133134 { ...buttonProps }
134135 >
135- { value !== '' ? ( selectedOption ?. label ?? value ) : placeholder }
136+ { value != null ? ( selectedOption ?. label ?? String ( value ) ) : placeholder }
136137 < ChevronIcon className = "w-4 h-4 opacity-50" />
137138 </ Trigger >
138139 </ PopoverTrigger >
@@ -175,8 +176,8 @@ export function Select({
175176 const isSelected = option . value === value ;
176177 const commonProps = {
177178 'data-selected' : isSelected ? 'true' : 'false' ,
178- 'data-value' : option . value ,
179- 'data-testid' : `select-option-${ option . value } ` ,
179+ 'data-value' : String ( option . value ) ,
180+ 'data-testid' : `select-option-${ String ( option . value ) } ` ,
180181 } as const ;
181182
182183 // When a custom Item is provided, use asChild to let it render as the actual item element
@@ -240,7 +241,7 @@ export function Select({
240241 const lower = q . toLowerCase ( ) ;
241242 const hasExactMatch =
242243 q . length > 0 &&
243- options . some ( ( o ) => o . label . toLowerCase ( ) === lower || o . value . toLowerCase ( ) === lower ) ;
244+ options . some ( ( o ) => o . label . toLowerCase ( ) === lower || String ( o . value ) . toLowerCase ( ) === lower ) ;
244245 if ( ! creatable || ! q || hasExactMatch ) return null ;
245246 const label = createOptionLabel ?.( q ) ?? `Select "${ q } "` ;
246247 return (
@@ -252,7 +253,7 @@ export function Select({
252253 onSelect = { async ( ) => {
253254 if ( ! onCreateOption ) return ;
254255 const created = await onCreateOption ( q ) ;
255- if ( created ?. value ) onValueChange ?.( created . value ) ;
256+ onValueChange ?.( created . value ) ;
256257 popoverState . close ( ) ;
257258 } }
258259 className = { cn (
0 commit comments