Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save sayandedotcom/cfd39848edd55a2955b2a5efea99dc8e to your computer and use it in GitHub Desktop.
Save sayandedotcom/cfd39848edd55a2955b2a5efea99dc8e to your computer and use it in GitHub Desktop.
React Select Library with shadcnui's design systems and forms !
1. Select Component ---------------------------------------------------------------------------------
"use client";
import clsx from "clsx";
import { ChevronDownIcon, X } from "lucide-react";
import Select, {
ClearIndicatorProps,
DropdownIndicatorProps,
MultiValueRemoveProps,
components,
} from "react-select";
import makeAnimated from "react-select/animated";
import CreatableSelect from "react-select/creatable";
const DropdownIndicator = (props: DropdownIndicatorProps) => {
return (
<components.DropdownIndicator {...props}>
<ChevronDownIcon />
</components.DropdownIndicator>
);
};
const ClearIndicator = (props: ClearIndicatorProps) => {
return (
<components.ClearIndicator {...props}>
<X />
</components.ClearIndicator>
);
};
const MultiValueRemove = (props: MultiValueRemoveProps) => {
return (
<components.MultiValueRemove {...props}>
<X />
</components.MultiValueRemove>
);
};
const controlStyles = {
base: "border border-border rounded-lg bg-background hover:cursor-pointer hover:bg-secondary",
focus: "border-border ring-ring ring-primary-500",
nonFocus: "border-border",
};
const placeholderStyles = "text-muted-foreground text-sm ml-1";
const selectInputStyles = "text-foreground text-sm ml-1";
const valueContainerStyles = "text-foreground text-sm";
const singleValueStyles = "ml-1";
const multiValueStyles =
"ml-1 bg-background border border-border rounded items-center py-0.5 pl-2 pr-1 gap-1.5";
const multiValueLabelStyles = "leading-6 py-0.5";
const multiValueRemoveStyles =
"border border-gray-200 bg-white hover:bg-red-50 hover:text-red-800 text-gray-500 hover:border-red-300 rounded-md bg-background";
const indicatorsContainerStyles = "p-1 gap-1 bg-background rounded-lg";
const clearIndicatorStyles = "text-gray-500 p-1 rounded-md hover:text-red-800";
const indicatorSeparatorStyles = "bg-mutated";
const dropdownIndicatorStyles = "p-1 hover:text-foreground text-gray-500";
const menuStyles = "mt-2 p-2 border border-border bg-background text-sm rounded-lg";
const optionsStyle = "bg-background p-2 border-0 text-base hover:bg-secondary hover:cursor-pointer";
const groupHeadingStyles = "ml-3 mt-2 mb-1 text-gray-500 text-sm bg-background";
const noOptionsMessageStyles = "text-muted-foreground bg-background";
type SelectComponentProps = {
options: any[];
value?: any;
onChange?: (value: any) => void;
isMulti?: boolean;
isDisabled?: boolean;
isLoading?: boolean;
createAble: boolean;
placeholder?: string;
};
export const SelectComponent = ({
options,
value,
onChange,
isMulti,
isDisabled,
isLoading,
createAble,
placeholder,
...props
}: SelectComponentProps) => {
const animatedComponents = makeAnimated();
const Comp = createAble ? CreatableSelect : Select;
return (
<>
<Comp
unstyled
isClearable
isSearchable
value={value}
isDisabled={isDisabled}
isMulti={isMulti}
isLoading={isLoading}
placeholder={placeholder}
components={animatedComponents}
// defaultInputValue={defaultValue}
defaultValue={value}
options={options}
noOptionsMessage={() => "No options found !!"}
onChange={onChange}
classNames={{
control: ({ isFocused }) =>
clsx(isFocused ? controlStyles.focus : controlStyles.nonFocus, controlStyles.base),
placeholder: () => placeholderStyles,
input: () => selectInputStyles,
option: () => optionsStyle,
menu: () => menuStyles,
valueContainer: () => valueContainerStyles,
singleValue: () => singleValueStyles,
multiValue: () => multiValueStyles,
multiValueLabel: () => multiValueLabelStyles,
multiValueRemove: () => multiValueRemoveStyles,
indicatorsContainer: () => indicatorsContainerStyles,
clearIndicator: () => clearIndicatorStyles,
indicatorSeparator: () => indicatorSeparatorStyles,
dropdownIndicator: () => dropdownIndicatorStyles,
groupHeading: () => groupHeadingStyles,
noOptionsMessage: () => noOptionsMessageStyles,
}}
{...props}
/>
</>
);
};
2. Single Select Component with form ---------------------------------------------------------------------------------
const jobTypeList = [
{ value: "Full Time", label: "Full Time" },
{ value: "Part Time", label: "Part Time" },
{ value: "Intern", label: "Intern" },
{ value: "Temporary", label: "Temporary" },
{ value: "Contractor", label: "Contractor" },
{ value: "Volunteer", label: "Volunteer" },
{ value: "Freelance", label: "Freelance" },
];
<FormField
control={form.control}
name="jobType"
render={({ field }) => (
<FormItem>
<FormLabel>Joy Type</FormLabel>
<FormDescription>Select the job type.</FormDescription>
<SelectComponent
createAble={true}
isMulti={false}
value={field.value}
options={jobTypeList}
onChange={field.onChange}
placeholder="Select Job Type"
{...field}
/>
<FormMessage />
</FormItem>
)}
/>
3. Multi Select Component with form ---------------------------------------------------------------------------------
const jobTypeList = [
{ value: "Full Time", label: "Full Time" },
{ value: "Part Time", label: "Part Time" },
{ value: "Intern", label: "Intern" },
{ value: "Temporary", label: "Temporary" },
{ value: "Contractor", label: "Contractor" },
{ value: "Volunteer", label: "Volunteer" },
{ value: "Freelance", label: "Freelance" },
];
<FormField
control={form.control}
name="skills"
render={({ field }) => (
<FormItem>
<FormLabel>Skills</FormLabel>
<FormDescription>Select the Skills Required.</FormDescription>
<SelectComponent
createAble={true}
isMulti={true}
value={field.value}
options={jobTypeList}
onChange={field.onChange}
placeholder="Select Skills"
{...field}
/>
<FormMessage />
</FormItem>
)}
/>
@emredevsalot
Copy link

Great implementation. Thank you for that.

I got an error using the options, so I tried to take the type of the original react-select element options.
From
options: any[];
to
options: | OptionsOrGroups< { value: string; label: string }, GroupBase<{ value: string; label: string }> > | undefined;

That seems to fix the issue, also that got me wondering, is it possible to take all the prop types from the original component rather than creating in SelectComponentProps ?

@alvarezmauro
Copy link

@emredevsalot yes, you can import the react-select prop types

import Select, { type Props as SelectProps } from "react-select";

@ak274
Copy link

ak274 commented Sep 20, 2023

using this inside a react hook form gives

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of SlotClone.

@iamcarlo042
Copy link

@ak274
Currently experiencing the same. Did you manage to do a fix?

@ak274
Copy link

ak274 commented Oct 17, 2023

https://gist.github.com/ak274/3f38bbdd7810489e6fd77882e46b2387

Use forwardRef to pass ref to your react hook form controller.

@amosmachora
Copy link

Hey i dont know if i missed something but the dropdocn icon is kind of different and also on the txt file some components are not used. Like

`const DropdownIndicator = (props: DropdownIndicatorProps) => {
return (
<components.DropdownIndicator {...props}>

</components.DropdownIndicator>
);
};

const ClearIndicator = (props: ClearIndicatorProps) => {
return (
<components.ClearIndicator {...props}>

</components.ClearIndicator>
);
};

const MultiValueRemove = (props: MultiValueRemoveProps) => {
return (
<components.MultiValueRemove {...props}>

</components.MultiValueRemove>
);
};
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment