はじめに
この記事の概要
こんにちは、株式会社TOKOSのスギタです!
今回はtailwindcssを用いてUIライブラリMantineを使いMultiSelectコンポーネントを作成する方法を説明していきたいと思います。
対象読者
- Reactを用いたWEB制作、WEB開発を行っている方
- tailwindcssを使用しながらUIライブラリを使用したい方
MantineUIについて
UIライブラリといえば思いつくのが、Material-UI、BootStrap、AntDesign、Chakra UI辺りだと思います。
UIライブラリを用いることで、スピーディーなフロントエンド開発、インタラクティブなUIを実現させることが容易になります。
多くのフロントエンド開発何かしらのUIライブラリを用いているのでは無いでしょうか?
UIライブラリのメリットは先程挙げた事になりますが、デメリットももちろんあります。
思いつくデメリットとしては、スタイルの変更が容易では無いことやUIライブラリの制約が強いことではないでしょうか?
場合によっては、tailwindcssと親和性が低いというところもあると思います。
だったらヘッドレスUIを使えばとなると思いますが、ヘッドレスUIの代表的ライブラリのHeadlessUIやRadixではUIの種類が少ないというのもあるのでは無いでしょうか。
そこで紹介したいのがMantineです。
MantineUIについて
皆さんMantineは知っていますか?
聞いたこともない人も多いと思うので軽く説明したいと思います!
MantineはReact専用のUIライブラリになります。
私が思うMantineの強みを5つあげたいと思います。
- 100種類以上のコンポーネントや50種類以上の便利のHooks
- Next.jsはもちろんモダンなフレームワークをサポート
- TypeScriptベース
- tailwindcssを用いてスタイルの上書き可能、ヘッドレスUIのような使用方法が可能
- 公式ドキュメントの可読性の良さ
上記5つの中でもスタイルの上書きできることが、かなり良いなと感じます。
開発環境
開発環境は下記になります。
- React 18.0.28
- Next.js13.2.3
- TypeScript 4.9.5
- Mantaine 6.0.13
下記参照内でフレームワークを選択してください。
今回はNext.jsで行っていきたいと思います!
また使用したいパッケージを選択してインストールしてください。
今回はmantine/core内にあるコンポーネントを使用するので、@mantine/coreをインストールしています。
tailwindcssとの整合性をあわせる
問題点
tailwindcss×Mantineの場合問題なことが2点あります。
- ブレークポイントの整合性
- リセットCSSの整合性
この上記2つを解消しないとスタイルの崩れや、意図しない挙動になってしまいます。
ブレークポイントの統一
前提としてtailwindとMantineでお互いにブレークポインを持ってます。
下記はtailwindのブレークポイント(初期値)です。
- sm : 640px
- md : 768px
- lg : 1024px
- xl : 1280px
- 2xl : 1536px
次はMantaineのブレークポイントです。(公式では1em=16pxでem表記になってます)
- xs : 576px
- sm : 768px
- md : 992px
- lg : 1200px
- xl : 1400px
上記でも分かる通り、名称と値がそれぞれ違います。これを統一したほうが、意図しない挙動を避けるためには必要です。
特に問題がなければMantaine側に合わせるのがbetterだと思います。
module.exports = {
theme: {
extend: {
// Mantineのbreak-pointに合わせています
screens: {
lg: "1200px",
md: "992px",
sm: "768px",
xl: "1400px",
xs: "576px",
},
},
},
}
これでブレークポイントの問題は解消です。
リセットCSSの統合
次にリセットCSSの競合解消です。
様々解消の方法はあると思いますが、個人的には、Mantine側のリセットCSSを削除する方法が一番良いかなと感じております。
Mantaine側のリセットCSSを採用したところ、破壊的なスタイルが多かったためです。
逆にtailwind側のリセットCSSを採用した場合わかる範囲で問題となった箇所はButton関係のコンポーネントを使用する際に背景が消える点です。
リセットCSSで背景が消えてもCSSでスタイルを上書きができるので、問題がないと思っています!
またMantineのButtonコンポーネントを使うよりボタンに関しては自作コンポーネントを使うほうが多いかなと思っています!
ではMantineのリセットCSSの削除方法ですが、pages/_app.tsxのMantineProvider内の記述を削除すれば良いです。
import { AppProps } from 'next/app';
import Head from 'next/head';
import { MantineProvider } from '@mantine/core';
export default function App(props: AppProps) {
const { Component, pageProps } = props;
return (
<>
<Head>
<title>Page title</title>
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
</Head>
<MantineProvider
withGlobalStyles
withNormalizeCSS
theme={{
/** Put your mantine theme override here */
colorScheme: 'light',
}}
>
<Component {...pageProps} />
</MantineProvider>
</>
);
}
ハイライトされている2行を削除します。
今回はGrobalStylesも不要なので削除しています。
これで準備完了です。
コンポーネントの作成例
今回は例としてMantineのCoreパッケージの中からMultiSelectを使ってみたいと思います。
完成予定
今回の完成予定は下記になります。
MultiSelectの実装
まずは公式ドキュメンと内のサイドバーのMANTINE COREのinputs内のMultiSelectを押してください。
ドキュメント内から要件によって近しいものを選択肢コピペすればOKになります。
今回は機能的要件はあまり求めていないので一番上にある「A bare minimum example」の必要な箇所をコピペします。
import { MultiSelect } from '@mantine/core';
const data = [
{ value: 'react', label: 'React' },
{ value: 'ng', label: 'Angular' },
{ value: 'svelte', label: 'Svelte' },
{ value: 'vue', label: 'Vue' },
{ value: 'riot', label: 'Riot' },
{ value: 'next', label: 'Next.js' },
{ value: 'blitz', label: 'Blitz.js' },
];
const ExamplePage: NextPageWithLayout = () => {
return (
<MultiSelect
data={data}
label="Your favorite frameworks/libraries"
placeholder="Pick all that you like"
/>
);
}
ここから今回の完成に向け必要な箇所、不必要な箇所を修正します。
import { MultiSelect } from '@mantine/core';
const data = [
{ label: "ユーザー1", value: "userId1" },
{ label: "ユーザー2", value: "userId2" },
{ label: "ユーザー3", value: "userId3" },
{ label: "ユーザー4", value: "userId4" },
{ label: "ユーザー5", value: "userId5" },
{ label: "ユーザー6", value: "userId6" },
{ label: "ユーザー7", value: "userId7" },
];
const ExamplePage: NextPageWithLayout = () => {
return (
<MultiSelect
data={data}
placeholder="ユーザーを選択してください"
/>
);
}
これでドキュメントのように表示できていると思います。
現状と完成予定と比べると下記と相違があります。
- 全体のスタイリング+(左のユーザーアイコン)
- 閉じる開くに連動する矢印アイコン
- 選択肢がなくなった場合の文字の「選択肢がありません」の表示
はじめにスタイルの調整から行います。
コンポーネントに対しPropsでsize等のPropsを渡すことも可能ですが、細かく調整したい場合はコンポーネント内の要素に個別プロパティを当てることも可能です。その場合はドキュメント内タブメニューの「Styles API」で確認します。
ドキュメントをに書いてある通り、関わってくる箇所(Name)としては「root」「input」「value」「values」辺りでしょうか!
かなりドキュメントも読みやすいのではないでしょうか!
確認後tailwindでスタイリングしていきます。”classNames”で複数形なので注意してください!
import { MultiSelect } from '@mantine/core';
const data = [
{ label: "ユーザー1", value: "userId1" },
{ label: "ユーザー2", value: "userId2" },
{ label: "ユーザー3", value: "userId3" },
{ label: "ユーザー4", value: "userId4" },
{ label: "ユーザー5", value: "userId5" },
{ label: "ユーザー6", value: "userId6" },
{ label: "ユーザー7", value: "userId7" },
];
const ExamplePage: NextPageWithLayout = () => {
return (
<div className="max-w-[480px] flex border border-gray-200 shadow rounded-full py-3 px-6">
//ユーザーアイコンの追加
<OutLinePersonIcon width={32} height={32} className="fill-blue-500" />
<MultiSelect
data={data}
placeholder="ユーザーを選択してください"
//下記でスタイリング、classNamesなので注意
classNames={{
input: "border-0",
root: "w-full",
value: "h-8 bg-tint-blue-200",
values: "overflow-y-scroll h-8",
}}
/>
</div>
);
}
上記のように要素に対し個別でスタイルを与えることができます。
どこがどこだかわからない場合はDevツールで確認するのも良いと思います!
必要な機能はドキュメント内タブメニューの「Component props」で確認します。
閉じる開くに連動する矢印アイコンは「rightSection」でアイコンを追加し、選択欄が開くとき発火するコールバック関数「onDropdownOpen」、選択欄が閉じるとき発火するコールバック関数「onDropdownClose」を使って矢印の制御をしていきます。
選択欄が開いている状態閉じる状態をbooleanで状態管理したいので、useStateで定義していきます。
三項演算子を用いてアイコンが回転するスタイルを追記していきます。
import { MultiSelect } from '@mantine/core';
const data = [
{ label: "ユーザー1", value: "userId1" },
{ label: "ユーザー2", value: "userId2" },
{ label: "ユーザー3", value: "userId3" },
{ label: "ユーザー4", value: "userId4" },
{ label: "ユーザー5", value: "userId5" },
{ label: "ユーザー6", value: "userId6" },
{ label: "ユーザー7", value: "userId7" },
];
const ExamplePage: NextPageWithLayout = () => {
const [isOpen, setIsOpen] = useState(false)
return (
<div className="max-w-[480px] flex border border-gray-200 shadow rounded-full py-3 px-6">
//ユーザーアイコンの追加
<OutLinePersonIcon width={32} height={32} className="fill-blue-500" />
<MultiSelect
data={data}
placeholder="ユーザーを選択してください"
//下記でスタイリング、classNamesなので注意
classNames={{
input: "border-0",
root: "w-full",
value: "h-8 bg-tint-blue-200",
values: "overflow-y-scroll h-8",
//アイコンの記述
rightSection: `pointer-events-none fill-gray-500 ${
isOpen
? "rotate-180 transition duration-200"
: "rotate-0 transition duration-200"
}`,
}}
rightSection={
<ChevronBottomIcon width={24} height={24} fill="#636B7E" />
}
onDropdownOpen={() => setIsOpen((prev) => !prev)}
onDropdownClose={() => setIsOpen((prev) => !prev)}
//選択肢が無いときの記述
nothingFound="選択肢がありません"
/>
</div>
);
}
これで完成になります。
他にも必要なpropsを選択肢自分でカスタムしてみてください!
さいごに
今回はReact専用のUIライブラリのMantineを紹介しました。
他のライブラリと比べかなり自由度が高いと感じました!
またtailwindを使ってスタイリングできるのは他に無いところではないでしょうか!
今回はMultiSelectを使用しましたが、他にも便利なコンポーネントやHooksが用意されているので、みなさんもぜひ使ってみてください!