Open7
shadcn/uiでアイコン付きInputコンポーネントを作成する
Create Next Appを実行する。
$ npx create-next-app@latest --experimental-app
✔ What is your project named? … my-app
✔ Would you like to use TypeScript with this project? … No / Yes
✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use Tailwind CSS with this project? … No / Yes
✔ Would you like to use `src/` directory with this project? … No / Yes
✔ What import alias would you like configured? … @/*
shadcn/uiをインストールする。
npx shadcn-ui init
Inputコンポーネントをインストールする。
npx shadcn-ui add input
Mantine Hooksをインストールする。
npm i @mantine/hooks
Heroiconsをインストールする。
npm install @heroicons/react
/components/ui/input.tsx
を編集する。
/components/ui/input.tsx
"use client";
import { cn } from "@/lib/utils";
import { useMergedRef } from "@mantine/hooks";
import {
ElementType,
InputHTMLAttributes,
forwardRef,
useReducer,
useRef,
} from "react";
type InputProps = InputHTMLAttributes<HTMLInputElement> & {
icon?: ElementType;
};
const Input = forwardRef<HTMLInputElement, InputProps>(
({ className, type, icon: Icon, ...props }, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
const [isFocused, handleFocusChange] = useReducer(
(_: boolean, isFocused: boolean) => {
const { current } = inputRef;
if (!current) return isFocused;
if (isFocused === false) {
current.blur();
} else {
current.focus();
}
return isFocused;
},
false
);
const mergedRef = useMergedRef(ref, inputRef);
return (
<div
className={cn(
"relative w-full flex items-center min-w-[10rem] focus:outline-none focus:ring-2 bg-white hover:bg-gray-50 text-gray-500 border-gray-300 focus:ring-blue-200 rounded-md border shadow-sm",
isFocused && "ring-2",
className
)}
onFocus={() => {
handleFocusChange(true);
}}
onBlur={() => {
handleFocusChange(false);
}}
>
{Icon ? (
<Icon
className="shrink-0 h-5 w-5 text-gray-400 ml-3.5"
aria-hidden="true"
/>
) : null}
<input
className="w-full focus:outline-none focus:ring-0 bg-transparent pl-4 pr-4 py-2 font-medium border-0 placeholder:text-gray-500"
type={type}
ref={mergedRef}
{...props}
/>
</div>
);
}
);
Input.displayName = "Input";
export { Input };
以下を作成する。
/components/Search.tsx
"use client";
import { MagnifyingGlassIcon } from "@heroicons/react/24/solid";
import { Input } from "@/components/ui/input";
export const Search = () => {
return (
<Input type="search" icon={MagnifyingGlassIcon} placeholder="Search..." />
);
};
ページでインポートする。
/app/page.tsx
import { Search } from "@/components/Search";
export default function Home() {
return (
<main className="p-2 bg-neutral-100 h-screen">
<Search />
</main>
);
}
背景
shadcn/uiのInput要素はアイコン表示に対応していなかったので、TremorのTextInputコンポーネントを採用しようとした。className
で変更できなかった。className
で変更できるのは、コンポーネントの一番外側のroot
スタイルのみで、内側の子コンポーネントのスタイルを変更できない。そのため、上記のようなコンポーネントを作成した。