Skip to content

Commit

Permalink
fix: autocomplete auto array value set
Browse files Browse the repository at this point in the history
  • Loading branch information
aseerkt committed Feb 28, 2024
1 parent d83db8d commit 1f47b83
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 168 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AsyncAutocomplete } from '@/components/form';
import { AsyncAutocompleteField } from '@/components/form';
import { useSession } from 'next-auth/react';
import { useEffect, useState } from 'react';
import { Control } from 'react-hook-form';
Expand Down Expand Up @@ -52,7 +52,7 @@ export function AssigneeAutocomplete({
}, []);

return (
<AsyncAutocomplete
<AsyncAutocompleteField
name={name}
control={control}
options={assigneeOptions}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { AsyncAutocomplete, SelectField } from '@/components/form';
import { AsyncAutocompleteField, SelectField } from '@/components/form';
import { Button } from '@/components/ui/button';
import { Form } from '@/components/ui/form';
import { toast } from '@/components/ui/use-toast';
Expand Down Expand Up @@ -67,7 +67,7 @@ export default function InviteCollaborators({
return (
<Form {...form}>
<form onSubmit={handleInviteSubmit} className='flex gap-2'>
<AsyncAutocomplete
<AsyncAutocompleteField
name='collaborators'
control={form.control}
placeholder='Select collaborators'
Expand Down
163 changes: 0 additions & 163 deletions src/components/form/AsyncAutocomplete.tsx

This file was deleted.

144 changes: 144 additions & 0 deletions src/components/form/AsyncAutocompleteField/AsyncAutocomplete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { buttonVariants } from '@/components/ui/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
} from '@/components/ui/command';
import { FormControl } from '@/components/ui/form';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { CommandItem } from 'cmdk';
import { isEmpty } from 'lodash';
import { ChevronsUpDown, XCircleIcon } from 'lucide-react';
import { useState } from 'react';

export interface AsyncAutocompleteProps<Option, Value> {
value: Value | Value[];
onValueChange: (value?: Value | Value[]) => void;
options: Option[];
renderValue: (value: Value) => React.ReactNode;
getOptionValue: (option: Option) => Value;
getSearchValue: (option: Option) => string;
renderOption: (option: Option, index: number) => React.ReactNode;
onSearch: (term: string) => void;
optionKey: keyof Option;
valueKey: keyof Value;
placeholder?: React.ReactNode;
autocompletePlaceholder?: string;
noResultPlaceholder?: React.ReactNode;
multiple?: boolean;
disabled?: boolean;
}

export default function AsyncAutocomplete<Option, Value>({
value,
onValueChange,
options,
renderValue,
renderOption,
getSearchValue,
getOptionValue,
onSearch,
optionKey,
valueKey,
placeholder = 'Select option',
autocompletePlaceholder = 'Search option',
noResultPlaceholder = 'No results found',
multiple = false,
disabled = false,
}: AsyncAutocompleteProps<Option, Value>) {
const [open, setOpen] = useState(false);
const handleOpenChange = (open: boolean) => setOpen(open);

const renderedValue = isEmpty(value) ? (
placeholder
) : multiple ? (
(value as Value[]).map((v, index) => (
<div
className='text-sm flex items-center gap-2 border-1 rounded-sm bg-cyan-600 text-white px-2 py-1'
key={v[valueKey] as string}
>
{renderValue(v)}
<button
aria-label={`unselect value ${v[valueKey]}`}
onClick={() => {
onValueChange((value as Value[]).filter((_, idx) => index !== idx));
}}
>
<XCircleIcon height={14} width={14} />
</button>
</div>
))
) : (
<div className='flex justify-between gap-2 grow'>
{renderValue(value as Value)}
<button
aria-label='unselect value'
onClick={(e) => {
e.stopPropagation();
onValueChange(undefined);
}}
>
<XCircleIcon height={14} width={14} />
</button>
</div>
);

return (
<Popover open={open} onOpenChange={handleOpenChange}>
<PopoverTrigger disabled={disabled} asChild>
<FormControl>
<div
role='button'
className={cn(
buttonVariants({ variant: 'outline' }),
'w-full justify-between hover:bg-white'
)}
>
<div className='flex gap-1 grow cursor-default'>
{renderedValue}
</div>
<ChevronsUpDown className='ml-2 h-4 w-4 shrink-0 opacity-50' />
</div>
</FormControl>
</PopoverTrigger>
<PopoverContent
className={cn('w-[var(--radix-popover-trigger-width)] p-0')}
>
<Command
shouldFilter={false}
onChange={(event) => {
onSearch((event.target as HTMLInputElement).value);
}}
>
<CommandInput placeholder={autocompletePlaceholder} />
<CommandEmpty>{noResultPlaceholder}</CommandEmpty>
<CommandGroup>
{options.map((option, index) => (
<CommandItem
value={getSearchValue(option)}
key={option[optionKey] as string}
onSelect={() => {
const v = getOptionValue(option);
if (multiple) {
onValueChange((value as Value[]).concat(v));
} else {
onValueChange(v);
}
handleOpenChange(false);
}}
>
{renderOption(option, index)}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Control } from 'react-hook-form';
import AsyncAutocomplete, { AsyncAutocompleteProps } from './AsyncAutocomplete';

interface AsyncAutocompleteFieldProps<Option, Value>
extends Omit<
AsyncAutocompleteProps<Option, Value>,
'value' | 'onValueChange'
> {
name: string;
control: Control<any>;
label?: React.ReactNode;
helperText?: React.ReactNode;
}

export default function AsyncAutocompleteField<Option, Value>({
name,
control,
label,
helperText,
...props
}: AsyncAutocompleteFieldProps<Option, Value>) {
return (
<FormField
control={control}
name={name}
render={({ field }) => {
return (
<FormItem className='grow'>
{label && <FormLabel>{label}</FormLabel>}
<AsyncAutocomplete
value={field.value}
onValueChange={field.onChange}
{...props}
/>
{helperText && <FormDescription>{helperText}</FormDescription>}
<FormMessage />
</FormItem>
);
}}
/>
);
}
1 change: 1 addition & 0 deletions src/components/form/AsyncAutocompleteField/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as AsyncAutocompleteField } from './AsyncAutocompleteField';
2 changes: 1 addition & 1 deletion src/components/form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { default as AsyncAutocomplete } from './AsyncAutocomplete';
export * from './AsyncAutocompleteField';
export { default as InputField } from './InputField';
export * from './MdEditorField';
export { default as RadioGroupField } from './RadioGroupField';
Expand Down

0 comments on commit 1f47b83

Please sign in to comment.