99 SelectTrigger ,
1010 SelectValue ,
1111} from '@/components/ui/select'
12+ import { FIELD_TYPE_METADATA , validateFieldValue } from '@/lib/knowledge/consts'
1213import { cn } from '@/lib/utils'
1314
1415interface TypedTagInputProps {
@@ -28,157 +29,59 @@ export function TypedTagInput({
2829 onChange,
2930 placeholder,
3031 disabled = false ,
31- className = '' ,
32+ className,
3233 showInlineError = false ,
3334 onValidityChange,
3435} : TypedTagInputProps ) {
35- const [ inputValue , setInputValue ] = useState ( value || '' )
36- const [ isValid , setIsValid ] = useState ( true )
37- const [ error , setError ] = useState < string | undefined > ( )
36+ const [ error , setError ] = useState < string > ( '' )
3837
38+ // Validate on value or fieldType change
3939 useEffect ( ( ) => {
40- setInputValue ( value || '' )
41- } , [ value ] )
42-
43- useEffect ( ( ) => {
44- setIsValid ( true )
45- setError ( undefined )
46- if ( inputValue . trim ( ) ) {
47- validateAndUpdate ( inputValue )
40+ if ( ! value . trim ( ) ) {
41+ setError ( '' )
42+ onValidityChange ?.( true )
43+ return
4844 }
49- } , [ fieldType ] )
50-
51- const validateAndUpdate = ( newValue : string ) => {
52- setInputValue ( newValue )
5345
54- let valid = true
55- let errorMessage : string | undefined
56-
57- if ( newValue . trim ( ) ) {
58- switch ( fieldType ) {
59- case 'number' : {
60- const num = Number ( newValue . trim ( ) )
61- if ( Number . isNaN ( num ) || ! Number . isFinite ( num ) ) {
62- valid = false
63- errorMessage = 'Must be a valid number'
64- }
65- break
66- }
67- case 'date' : {
68- const date = new Date ( newValue . trim ( ) )
69- if ( Number . isNaN ( date . getTime ( ) ) ) {
70- valid = false
71- errorMessage = 'Must be a valid date'
72- }
73- break
74- }
75- case 'boolean' : {
76- const lower = newValue . trim ( ) . toLowerCase ( )
77- if ( ! [ 'true' , 'false' , '1' , '0' , 'yes' , 'no' ] . includes ( lower ) ) {
78- valid = false
79- errorMessage = 'Must be true, false, yes, no, 1, or 0'
80- }
81- break
82- }
83- }
84- }
46+ const validation = validateFieldValue ( fieldType , value )
47+ setError ( validation . isValid ? '' : validation . error || '' )
48+ onValidityChange ?.( validation . isValid )
49+ // eslint-disable-next-line react-hooks/exhaustive-deps
50+ } , [ value , fieldType ] )
8551
86- setIsValid ( valid )
87- setError ( errorMessage )
88- if ( onValidityChange ) onValidityChange ( valid )
52+ const metadata =
53+ FIELD_TYPE_METADATA [ fieldType as keyof typeof FIELD_TYPE_METADATA ] || FIELD_TYPE_METADATA . text
8954
90- if ( valid ) {
91- onChange ( newValue )
92- }
55+ // Boolean type uses Select
56+ if ( fieldType === 'boolean' ) {
57+ return (
58+ < div className = 'space-y-1' >
59+ < Select value = { value || 'true' } onValueChange = { onChange } disabled = { disabled } >
60+ < SelectTrigger className = { cn ( 'w-full' , className ) } >
61+ < SelectValue placeholder = { placeholder || metadata . placeholder } />
62+ </ SelectTrigger >
63+ < SelectContent >
64+ < SelectItem value = 'true' > True</ SelectItem >
65+ < SelectItem value = 'false' > False</ SelectItem >
66+ </ SelectContent >
67+ </ Select >
68+ </ div >
69+ )
9370 }
9471
95- switch ( fieldType ) {
96- case 'boolean' :
97- return (
98- < div className = { className } >
99- < Select
100- value = { inputValue || undefined }
101- onValueChange = { validateAndUpdate }
102- disabled = { disabled }
103- >
104- < SelectTrigger
105- className = { cn (
106- 'h-8 w-full justify-between rounded-[10px] border-[#E5E5E5] bg-[#FFFFFF] text-sm dark:border-[#414141] dark:bg-[var(--surface-elevated)]' ,
107- ! isValid && 'border-red-300 focus:border-red-500 focus:ring-0'
108- ) }
109- >
110- < SelectValue placeholder = 'Select' />
111- </ SelectTrigger >
112- < SelectContent className = 'rounded-lg border-[#E5E5E5] bg-[#FFFFFF] dark:border-[#414141] dark:bg-[var(--surface-elevated)]' >
113- < SelectItem value = 'true' > True</ SelectItem >
114- < SelectItem value = 'false' > False</ SelectItem >
115- </ SelectContent >
116- </ Select >
117- { showInlineError && ! isValid && error && (
118- < p className = 'mt-1 text-red-600 text-xs' > { error } </ p >
119- ) }
120- </ div >
121- )
122-
123- case 'date' :
124- return (
125- < div className = { className } >
126- < Input
127- type = 'text'
128- value = { inputValue }
129- onChange = { ( e ) => validateAndUpdate ( e . target . value ) }
130- placeholder = { placeholder || 'mm/dd/yyyy' }
131- disabled = { disabled }
132- className = { cn (
133- 'h-8 rounded-[10px] border-[#E5E5E5] bg-[#FFFFFF] text-sm dark:border-[#414141] dark:bg-[var(--surface-elevated)]' ,
134- ! isValid && 'border-red-300 focus:border-red-500 focus:ring-0'
135- ) }
136- />
137- { showInlineError && ! isValid && error && (
138- < p className = 'mt-1 text-red-600 text-xs' > { error } </ p >
139- ) }
140- </ div >
141- )
142-
143- case 'number' :
144- return (
145- < div className = { className } >
146- < Input
147- type = 'text'
148- inputMode = 'numeric'
149- value = { inputValue }
150- onChange = { ( e ) => validateAndUpdate ( e . target . value ) }
151- placeholder = { placeholder || 'Number' }
152- disabled = { disabled }
153- className = { cn (
154- 'h-8 rounded-[10px] border-[#E5E5E5] bg-[#FFFFFF] text-sm dark:border-[#414141] dark:bg-[var(--surface-elevated)]' ,
155- ! isValid && 'border-red-300 focus:border-red-500 focus:ring-0'
156- ) }
157- />
158- { showInlineError && ! isValid && error && (
159- < p className = 'mt-1 text-red-600 text-xs' > { error } </ p >
160- ) }
161- </ div >
162- )
163-
164- default :
165- return (
166- < div className = { className } >
167- < Input
168- type = 'text'
169- value = { inputValue }
170- onChange = { ( e ) => validateAndUpdate ( e . target . value ) }
171- placeholder = { placeholder || 'Value' }
172- disabled = { disabled }
173- className = { cn (
174- 'h-8 rounded-[10px] border-[#E5E5E5] bg-[#FFFFFF] text-sm dark:border-[#414141] dark:bg-[var(--surface-elevated)]' ,
175- ! isValid && 'border-red-300 focus:border-red-500 focus:ring-0'
176- ) }
177- />
178- { showInlineError && ! isValid && error && (
179- < p className = 'mt-1 text-red-600 text-xs' > { error } </ p >
180- ) }
181- </ div >
182- )
183- }
72+ // All other types use Input
73+ return (
74+ < div className = 'space-y-1' >
75+ < Input
76+ type = { fieldType === 'number' ? 'text' : 'text' }
77+ inputMode = { fieldType === 'number' ? 'numeric' : undefined }
78+ value = { value }
79+ onChange = { ( e ) => onChange ( e . target . value ) }
80+ placeholder = { placeholder || metadata . placeholder }
81+ disabled = { disabled }
82+ className = { cn ( error && showInlineError && 'border-red-500' , className ) }
83+ />
84+ { showInlineError && error && < p className = 'text-red-600 text-xs' > { error } </ p > }
85+ </ div >
86+ )
18487}
0 commit comments