-
Notifications
You must be signed in to change notification settings - Fork 467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature]: Make Select component compatible with React Hook Form #1110
Labels
Type: Accessibility
Indicates that the A11y is affected.
Type: Enhancement
Small enhancement to existing component or feature
Comments
severinlandolt
added
Type: Enhancement
Small enhancement to existing component or feature
Type: Accessibility
Indicates that the A11y is affected.
labels
Aug 13, 2024
I already integrated tremor with RHF, here you have the code in case you find it useful. The key is to use the Controller class: SelectInput.tsx 'use client'
import { Select, SelectItem, SelectProps, SelectItemProps } from '@tremor/react'
import {
BaseFormInputProps,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from 'components/Primitives/Form'
export interface SelectOptionProps extends SelectItemProps {
label: string
}
export { Select, SelectItem }
type BaseSelectProps = BaseFormInputProps & Omit<SelectProps, 'children'>
export interface SelectInputProps extends BaseSelectProps {
options: SelectOptionProps[]
}
export default function SelectInput(props: SelectInputProps) {
const { name, control, label, description, defaultValue, options, ...rest } =
props
return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem>
<FormLabel>{label}</FormLabel>
<FormControl>
<Select
onValueChange={field.onChange}
defaultValue={field.value?.toString() || ''}
{...rest}
>
{options.map((option) => (
<SelectItem
key={option.value}
value={option.value}
icon={option.icon}
>
{option.label}
</SelectItem>
))}
</Select>
</FormControl>
<FormDescription>{description}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
)
} SearchSelect.tsx 'use client'
import {
SearchSelect,
SearchSelectProps as TremorSearchSelectProps,
SearchSelectItem,
SearchSelectItemProps,
} from '@tremor/react'
import {
BaseFormInputProps,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from 'components/Primitives/Form'
import {
Avatar,
AvatarImage,
AvatarFallback,
} from 'components/Primitives/Avatar'
export { SearchSelect, SearchSelectItem }
export type {
TremorSearchSelectProps as SearchSelectProps,
SearchSelectItemProps,
}
interface SearchSelectOptionProps extends SearchSelectItemProps {
label: string
picture?: string | null
}
type SearchSelectProps = Omit<TremorSearchSelectProps, 'children'> &
BaseFormInputProps & { enableIconFallback?: boolean }
export interface SearchSelectInputProps extends SearchSelectProps {
options: SearchSelectOptionProps[]
}
const OptionIcon = ({ option }: { option: SearchSelectOptionProps }) => {
return (
<Avatar className="w-7 h-7 mr-4">
<AvatarImage src={option.picture as string} />
<AvatarFallback>{option.label}</AvatarFallback>
</Avatar>
)
}
export default function SearchSelectInput(props: SearchSelectInputProps) {
const {
name,
control,
label,
description,
placeholder,
enableIconFallback,
options,
onValueChange,
onSearchValueChange,
enableClear = true,
} = props
const onChange = (
newValue: string,
onFormChange: (...event: any[]) => void
) => {
onFormChange(newValue)
onValueChange?.(newValue)
}
return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem>
<FormLabel>{label}</FormLabel>
<FormControl>
<SearchSelect
placeholder={placeholder}
defaultValue={field.value}
onValueChange={(value) => onChange(value, field.onChange)}
onSearchValueChange={onSearchValueChange}
enableClear={enableClear}
>
{options.map((option) => (
<SearchSelectItem
key={option.value}
value={option.value}
icon={
option.picture || enableIconFallback
? () => <OptionIcon option={option} />
: undefined
}
>
{option.label}
</SearchSelectItem>
))}
</SearchSelect>
</FormControl>
<FormDescription>{description}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
)
} MultiSelect.tsx 'use client'
import { useState } from 'react'
import { useFormContext } from 'react-hook-form'
import {
MultiSelect,
MultiSelectItem,
MultiSelectProps as TremorMultiSelectProps,
MultiSelectItemProps,
} from '@tremor/react'
import {
BaseFormInputProps,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from 'components/Primitives/Form'
// This is used to control how the multiselect input behaves
export const multiSelectWithAllOptionChangeTransformer =
(allOptionsValue: string) => (prev: string[], newValues: string[]) => {
// If there is no selection, reset to all option
if (newValues.length === 0) return [allOptionsValue]
// If we have selected the all option while it wasnt selected before, reset to all
if (!prev.includes(allOptionsValue) && newValues.includes(allOptionsValue))
return [allOptionsValue]
// If we had all selected but now there is more selection, then unselect the all option
if (prev.includes(allOptionsValue) && newValues.length > 1) {
return newValues.filter((v: string) => v !== allOptionsValue)
}
return newValues
}
interface MultiSelectOptionProps extends MultiSelectItemProps {
label: string
}
type MultiSelectProps = Omit<TremorMultiSelectProps, 'children'> &
BaseFormInputProps
export interface MultiSelectInputProps extends MultiSelectProps {
options: MultiSelectOptionProps[]
allOptionsValue?: string
}
export default function MultiSelectInput(props: MultiSelectInputProps) {
const {
name,
control,
label,
description,
options,
allOptionsValue,
...rest
} = props
const { getValues } = useFormContext()
const [values, setValues] = useState<string[]>(getValues(name) || [])
const onSelectionChange = (
newValues: string[],
onChange: (...event: any[]) => void
) => {
const newSelection = allOptionsValue
? multiSelectWithAllOptionChangeTransformer(allOptionsValue)(
values,
newValues
)
: newValues
setValues(newSelection)
onChange(newSelection)
}
return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem>
<FormLabel>{label}</FormLabel>
<FormControl>
<MultiSelect
value={values}
defaultValue={values}
placeholderSearch="Buscar..."
onValueChange={(newValues) =>
onSelectionChange(newValues, field.onChange)
}
{...rest}
>
{options.map((option) => (
<MultiSelectItem key={option.value} value={option.value}>
{option.label}
</MultiSelectItem>
))}
</MultiSelect>
</FormControl>
<FormDescription>{description}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
)
} |
Hey @angelhodar thanks! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
Type: Accessibility
Indicates that the A11y is affected.
Type: Enhancement
Small enhancement to existing component or feature
What problem does this feature solve?
React Hook Form is a convenient library to handle forms and with native HTML input elements the integration is flawless, actually also with most of Tremor input components the integration is flawless, i.e. with the
TextInput
I can just do (which is what I would do for the<input>
element):Though the same method doesn't work for the
Select
component, this is the workaround I used:What does the proposed API look like?
It would be nice to make the
Select
component compatible with RHF so the integration would be:The text was updated successfully, but these errors were encountered: