2023/08/27
react-selectの見た目をTailwindCSSで整える
RadixUIとかheadlessUIとかいろいろありますが、selectboxに関してはreact-selectが一番使い勝手がいい気がします。特にMultiple SelectとかCreatable Selectを作りたい時はほぼ一択ですよね。
ただやっぱりCSSのカスタマイズ方法がちょっとややこしいので、TailwindCSSで見た目を整える方法をここにまとめておきます。
Selectコンポーネント
早速ですが、完成したSelectコンポーネントはこちらです。
components/ui/select.tsx
import SelectComponent, {
components,
DropdownIndicatorProps,
MultiValueRemoveProps,
ClearIndicatorProps
} from "react-select";
import Creatable from "react-select/creatable";
import { ChevronDown, X } from "lucide-react";
import { clsx } from "clsx";
const DropdownIndicator = (props: DropdownIndicatorProps) => {
return (
<components.DropdownIndicator {...props}>
<ChevronDown size={18} strokeWidth={1.4} />
</components.DropdownIndicator>
);
};
const ClearIndicator = (props: ClearIndicatorProps) => {
return (
<components.ClearIndicator {...props}>
<X size={18} strokeWidth={1.4} />
</components.ClearIndicator>
);
};
const MultiValueRemove = (props: MultiValueRemoveProps) => {
return (
<components.MultiValueRemove {...props}>
<X size={12} strokeWidth={3} />
</components.MultiValueRemove>
);
};
const selectStyles = {
control: {
base: "flex border rounded-md bg-input hover:cursor-pointer min-w-[100px] !min-h-[40px] w-[100%] h-[100%] text-foreground text-foreground",
focus: "border border-primary shadow-[0_0_0_2px_rgba(var(--primary),0.3)]",
nonFocus: "border-border"
},
option: {
base: "hover:cursor-pointer px-3 py-2 rounded flex gap-2 items-center hover:bg-muted !text-sm",
focus: "",
selected: "bg-[rgba(var(--primary),0.15)] hover:bg-[rgba(var(--primary),0.15)] text-primary font-bold"
}
};
const classNames = {
control: ({ isFocused }: any) =>
clsx(
isFocused ? selectStyles.control.focus : selectStyles.control.nonFocus,
selectStyles.control.base
),
option: ({ isSelected, isFocused }: any) =>
clsx(
selectStyles.option.base,
isSelected ? selectStyles.option.selected : "",
isFocused ? selectStyles.option.focus : ""
),
menu: () => "p-1 mt-1 bg-card rounded-md shadow-md",
placeholder: () => "text-placeholder pl-1",
input: () => "text-foreground pl-1",
valueContainer: () => "p-1 gap-1",
singleValue: () => "pl-1",
multiValue: () => "flex bg-[rgba(var(--primary),0.15)] p-0 pl-2 rounded-sm text-xs overflow-hidden",
multiValueLabel: () => "py-1",
multiValueRemove: () => "bg-[rgba(var(--primary),0.1)] hover:bg-[rgba(var(--primary),0.2)] text-primary px-1 ml-2",
indicatorsContainer: () => "p-1 gap-1",
clearIndicator: () => "text-muted-foreground px-1 hover:text-error",
indicatorSeparator: () => "bg-border",
dropdownIndicator: () => "p-1 hover:bg-muted text-muted-foreground rounded-md hover:text-foreground",
groupHeading: () => "ml-3 mt-2 mb-1 text-muted-foreground text-sm",
noOptionsMessage: () => "text-placeholder py-3"
};
type Option = { [string: string]: any }
type SelectProps = {
options: Array<Option>;
className?: string;
clearValue?: () => void;
creatable?: boolean;
isMulti?: boolean;
value?: any;
onChange?: () => void;
};
export const Select = ({
options,
className,
creatable = false,
isMulti = false,
value,
onChange
}: SelectProps) => {
if (creatable) {
return (
<Creatable
unstyled
options={options}
isMulti={isMulti}
value={value}
onChange={onChange}
classNames={{ ...classNames }}
className={clsx(className)}
components={{ DropdownIndicator, ClearIndicator, MultiValueRemove }}
/>
);
}
return (
<SelectComponent
unstyled
options={options}
isMulti={isMulti}
value={value}
onChange={onChange}
classNames={{ ...classNames }}
className={clsx(className)}
components={{ DropdownIndicator, ClearIndicator, MultiValueRemove }}
/>
);
};
unstyled
を指定してデフォルトのスタイルを消してから、classNames
でそれぞれのパーツにTailwindのクラス名を当てています。
私はTailwindCSSを使う時、globals.css
とtailwind.config.js
から使える色の種類を増やしてしているので(primary, foreground, placeholderなど)このままだと動きませんが、それぞれ自分が使いたい色に書き換えると動くと思います。
clsx
を使ってクラス名の動的な付け替えを行なっていますが、こちらの記事にあるcn
関数を使っても構いません。
components
から各種アイコンもカスタマイズしています。
使い方
作成したセレクトコンポーネントを使いたいときはこういう感じで使います。
const options = [
{
label: "Mammal",
options: [
{ value: "Dolphin", label: "🐬 Dolphin" },
{ value: "Giraffe", label: "🦒 Giraffe" }
]
},
{
label: "Carnivore",
options: [
{ value: "Tiger", label: "🐅 Tiger" },
{ value: "Lion", label: "🦁 Lion" }
]
},
];
const options2 = [
{ value: "Butterfly", label: "🦋 Butterfly" },
{ value: "Fox", label: "🦊 Fox" },
{ value: "Cat", label: "🐱 Cat" }
];
<Select
options={options}
className="sm:w-[300px] w-[100%]"
/>
<Select
options={options2}
className="w-[300px]"
creatable
isMulti
/>
セレクトボックスのサイズはclassName
に設定すると適用されます。
creatable
、isMulti
も設定可能です🙆♀️