diff --git a/.changeset/shiny-ghosts-argue.md b/.changeset/shiny-ghosts-argue.md new file mode 100644 index 0000000..d9194a3 --- /dev/null +++ b/.changeset/shiny-ghosts-argue.md @@ -0,0 +1,5 @@ +--- +"bako-ui": patch +--- + +rhf-combobox can work only with lowercase value diff --git a/packages/ui/src/components/RhfCombobox/rhf-combobox.test.tsx b/packages/ui/src/components/RhfCombobox/rhf-combobox.test.tsx index 8cf7dc4..ec0523e 100644 --- a/packages/ui/src/components/RhfCombobox/rhf-combobox.test.tsx +++ b/packages/ui/src/components/RhfCombobox/rhf-combobox.test.tsx @@ -253,4 +253,87 @@ describe('RhfCombobox', () => { ) as HTMLInputElement; expect(hiddenInput?.value).toBe('banana'); }); + + it('converts input to lowercase when onlyLowercase is true', async () => { + const onSubmit = vi.fn(); + const { user } = renderSingleCombobox( + { onlyLowercase: true }, + { onSubmit } + ); + + const input = screen.getByRole('combobox'); + await user.click(input); + await user.type(input, 'BaNaNa'); + + // Wait for the value to be normalized to lowercase letters + await waitFor(() => { + const hiddenInput = document.querySelector( + 'input[type="hidden"]' + ) as HTMLInputElement; + expect(hiddenInput?.value).toBe('banana'); + }); + + await user.click(screen.getByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ single: 'banana' }), + expect.anything() + ); + }); + }); + + it('converts selected option to lowercase when onlyLowercase is true', async () => { + const onSubmit = vi.fn(); + const { user } = renderSingleCombobox( + { + onlyLowercase: true, + options: [ + { label: 'Apple', value: 'APPLE' }, + { label: 'Banana', value: 'BANANA' }, + { label: 'Cherry', value: 'CHERRY' }, + ], + }, + { onSubmit } + ); + + await user.click(screen.getByRole('combobox')); + await user.click(await screen.findByText('Banana')); + + await waitFor(() => { + const hiddenInput = document.querySelector( + 'input[type="hidden"]' + ) as HTMLInputElement; + expect(hiddenInput?.value).toBe('banana'); + }); + + await user.click(screen.getByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ single: 'banana' }), + expect.anything() + ); + }); + }); + + it('converts defaultValue to lowercase when onlyLowercase is true', async () => { + renderSingleCombobox({ + defaultValue: 'APPLE', + onlyLowercase: true, + }); + + const input = screen.getByRole('combobox'); + + // The input should display “apple” (converted to lowercase) + await waitFor(() => { + expect(input).toHaveValue('apple'); + }); + + // The hidden input must also have a lowercase value + const hiddenInput = document.querySelector( + 'input[type="hidden"]' + ) as HTMLInputElement; + expect(hiddenInput?.value).toBe('apple'); + }); }); diff --git a/packages/ui/src/components/RhfCombobox/rhf-combobox.tsx b/packages/ui/src/components/RhfCombobox/rhf-combobox.tsx index 070d4f7..3fbf542 100644 --- a/packages/ui/src/components/RhfCombobox/rhf-combobox.tsx +++ b/packages/ui/src/components/RhfCombobox/rhf-combobox.tsx @@ -69,6 +69,7 @@ export function RhfCombobox< clearTriggerIcon, showTrigger = false, allowCustomValue = true, + onlyLowercase = false, onInputValueChange, }: RhfComboboxProps) { const { @@ -80,46 +81,57 @@ export function RhfCombobox< filter: contains, }); - const [inputValue, setInputValue] = useState(value || ''); + const normalizeValue = useCallback( + (val: string) => { + if (!val) return val; + return onlyLowercase ? val.toLowerCase() : val; + }, + [onlyLowercase] + ); + + const [inputValue, setInputValue] = useState(normalizeValue(value || '')); const [isTyping, setIsTyping] = useState(false); useEffect(() => { if (isTyping) return; - if (value !== inputValue) { - setInputValue(value || ''); + const normalizedValue = normalizeValue(value || ''); + if (normalizedValue !== inputValue) { + setInputValue(normalizedValue || ''); } - }, [value, inputValue, isTyping]); + }, [value, inputValue, isTyping, normalizeValue]); const handleValueChange = useCallback( (details: ComboboxValueChangeDetails) => { const newValue = details.value[0] || ''; + const normalizedValue = normalizeValue(newValue); setIsTyping(false); - onChange(newValue); - setInputValue(newValue); - onInputValueChange?.(newValue); + onChange(normalizedValue); + setInputValue(normalizedValue); + onInputValueChange?.(normalizedValue); }, - [onChange, onInputValueChange] + [onChange, onInputValueChange, normalizeValue] ); const handleInputValueChange = useCallback( (details: ComboboxInputValueChangeDetails) => { setIsTyping(true); - setInputValue(details.inputValue); + const normalizedInputValue = normalizeValue(details.inputValue); + setInputValue(normalizedInputValue); flushSync(() => { - filter(details.inputValue); + filter(normalizedInputValue); }); - onInputValueChange?.(details.inputValue); + onInputValueChange?.(normalizedInputValue); if (allowCustomValue) { - onChange(details.inputValue); + onChange(normalizedInputValue); } setTimeout(() => setIsTyping(false), 100); }, - [filter, allowCustomValue, onChange, onInputValueChange] + [filter, allowCustomValue, onChange, onInputValueChange, normalizeValue] ); const handleOpenChange = useCallback( @@ -176,7 +188,7 @@ export function RhfCombobox< width="full" variant={variant} inputValue={inputValue} - value={value ? [value] : []} + value={value ? [normalizeValue(value)] : []} borderRadius="lg" onValueChange={handleValueChange} onOpenChange={handleOpenChange} @@ -187,7 +199,7 @@ export function RhfCombobox< invalid={!!error} allowCustomValue={allowCustomValue} selectionBehavior="preserve" - defaultValue={[defaultValue || '']} + defaultValue={[normalizeValue(defaultValue || '')]} placeholder={placeholder} {...slotProps?.root} > @@ -210,7 +222,10 @@ export function RhfCombobox< - + {helperText && {helperText}} {error?.message && {error.message}} diff --git a/packages/ui/src/components/RhfCombobox/rhf-combobox.types.tsx b/packages/ui/src/components/RhfCombobox/rhf-combobox.types.tsx index a0251d4..098e1b2 100644 --- a/packages/ui/src/components/RhfCombobox/rhf-combobox.types.tsx +++ b/packages/ui/src/components/RhfCombobox/rhf-combobox.types.tsx @@ -35,4 +35,5 @@ export type RhfComboboxProps< label?: FieldLabelProps; input?: Omit; }; + onlyLowercase?: boolean; };