1- import React , { PropsWithChildren , useState } from 'react' ;
1+ import React , { PropsWithChildren , useState , useMemo } from 'react' ;
22import {
33 Popover ,
44 PopoverContent ,
@@ -24,13 +24,51 @@ interface DropdownSelectProps<T> extends PropsWithChildren {
2424 onSelect : ( name : string , item : T ) => void ;
2525 onSelectEmpty ?: ( ) => void ;
2626 renderItem ?: ( item : T ) => React . ReactNode ;
27+ filterText ?: string ; // Optional filter text for internal filtering
28+ allowCustomInput ?: boolean ;
2729}
2830export const DropdownSelect = < T extends BasicListItem > (
2931 props : DropdownSelectProps < T >
3032) => {
3133 const [ isOpen , setIsOpen ] = useState ( props . defaultIsOpen ?? false ) ;
3234 const { t } = useTranslation ( ) ;
3335 const dropdownSize = props . dropdownSize ?? 'default' ;
36+ const allowCustomInput = props . allowCustomInput ?? true ;
37+
38+ // Filter list based on filterText if provided
39+ const filteredList = useMemo ( ( ) => {
40+ if ( ! props . filterText ) {
41+ return props . list ;
42+ }
43+ return props . list . filter (
44+ ( item ) =>
45+ item . name . toLowerCase ( ) . includes ( props . filterText ! . toLowerCase ( ) ) ||
46+ ( item . label &&
47+ item . label . toLowerCase ( ) . includes ( props . filterText ! . toLowerCase ( ) ) )
48+ ) ;
49+ } , [ props . list , props . filterText ] ) ;
50+
51+ // Check if we should show the specify option
52+ const shouldShowSpecify = useMemo ( ( ) => {
53+ return (
54+ allowCustomInput &&
55+ props . filterText &&
56+ props . filterText . trim ( ) . length > 0 &&
57+ filteredList . length === 0
58+ ) ;
59+ } , [ allowCustomInput , props . filterText , filteredList . length ] ) ;
60+
61+ const handleSpecifySelect = ( ) => {
62+ if ( shouldShowSpecify ) {
63+ // Create a custom item with the filterText as name
64+ const customItem = {
65+ name : props . filterText ! . trim ( ) ,
66+ label : props . filterText ! . trim ( ) ,
67+ } as T ;
68+ props . onSelect ( props . filterText ! . trim ( ) , customItem ) ;
69+ setIsOpen ( false ) ;
70+ }
71+ } ;
3472
3573 return (
3674 < Popover
@@ -62,35 +100,46 @@ export const DropdownSelect = <T extends BasicListItem>(
62100 < div > { props . dropdownHeader } </ div >
63101
64102 < ScrollArea className = "flex-1" >
65- { props . list . length === 0 && (
103+ { shouldShowSpecify ? (
104+ < div
105+ className = "hover:bg-muted flex cursor-pointer items-center gap-2 rounded px-2 py-1 text-sm transition-all"
106+ onClick = { handleSpecifySelect }
107+ >
108+ < span className = "text-primary" >
109+ { t ( 'Specify' ) } : < strong > { props . filterText ! . trim ( ) } </ strong >
110+ </ span >
111+ </ div >
112+ ) : filteredList . length === 0 ? (
66113 < div className = "mt-4 text-center opacity-80" >
67- { t ( 'No any item availabled.' ) }
114+ { props . filterText
115+ ? t ( 'No items match your search.' )
116+ : t ( 'No any item availabled.' ) }
117+ </ div >
118+ ) : (
119+ < div className = "flex flex-col gap-0.5" >
120+ { filteredList . map ( ( item , i ) => {
121+ return (
122+ < div
123+ key = { i }
124+ className = { cn (
125+ 'hover:bg-muted flex cursor-pointer items-center gap-2 rounded px-2 py-1 text-sm transition-all' ,
126+ props . value === item . name && 'bg-muted'
127+ ) }
128+ onClick = { ( ) => {
129+ props . onSelect ( item . name , item ) ;
130+ setIsOpen ( false ) ;
131+ } }
132+ >
133+ { props . renderItem ? (
134+ props . renderItem ( item )
135+ ) : (
136+ < span > { item . label ?? item . name } </ span >
137+ ) }
138+ </ div >
139+ ) ;
140+ } ) }
68141 </ div >
69142 ) }
70-
71- < div className = "flex flex-col gap-0.5" >
72- { props . list . map ( ( item , i ) => {
73- return (
74- < div
75- key = { i }
76- className = { cn (
77- 'hover:bg-muted flex cursor-pointer items-center gap-2 rounded px-2 py-1 text-sm transition-all' ,
78- props . value === item . name && 'bg-muted'
79- ) }
80- onClick = { ( ) => {
81- props . onSelect ( item . name , item ) ;
82- setIsOpen ( false ) ;
83- } }
84- >
85- { props . renderItem ? (
86- props . renderItem ( item )
87- ) : (
88- < span > { item . label ?? item . name } </ span >
89- ) }
90- </ div >
91- ) ;
92- } ) }
93- </ div >
94143 </ ScrollArea >
95144 </ div >
96145 </ PopoverContent >
0 commit comments