Open3
tremor紹介
Tremorとは
TremorはTailwindCSS製のReaectコンポーネントライブラリです。
ダッシュボードに適したコンポーネントやおしゃれなチャートが豊富に提供されています。
環境
- Next.js v15
- pnpm v9.8.0
環境構築
依存関係のインストール
コア依存関係をインストールするには次のコマンドを実行する。
必要に応じて各コンポーネントの依存関係をインストールしていく。
ターミナル
pnpm add tailwind-variants cx tailwind-merge @remixicon/react clsx
package | 概要 |
---|---|
tailwind-variants | TailwindCSS用のファーストクラスのバリアントAPIライブラリ |
cx | データ指向のWebアプリケーションを構築するためのフレームワーク |
tailwind-merge | Tailwind CSSのスタイルの競合をなくし、マージする |
@remixicon/react | ReactのRemixアイコン |
clsx | 条件に応じて className 文字列を構築する。後述のユーティリティ作成時に使用する。 |
本記事で必要な依存関係
ターミナル
pnpm add recharts
package | 依存するコンポーネント |
---|---|
recharts | area-chart |
ダークモードをの背景を追加する
src/app/layout.tsx
src/app/layout.tsx
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
- className={`${geistSans.variable} ${geistMono.variable} antialiased`}
+ className={`${geistSans.variable} ${geistMono.variable} antialiased dark:bg-gray-950`}
>
{children}
</body>
</html>
);
}
@tailwindcss/formsプラグインのインストール
ターミナル
pnpm add -D @tailwindcss/forms
tailwind.config.jsの編集
アニメーションを正しく動作させるためにtailwind.config.jsを編集します。
合わせて、contentに"src/lib"配下を追加します。
また、plubinsに"@tailwindcss/forms"を追加する
tailwind.config.js
tailwind.config.js
import type { Config } from "tailwindcss"
const config: Config = {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/lib/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
+ keyframes: {
+ hide: {
+ from: { opacity: "1" },
+ to: { opacity: "0" },
+ },
+ slideDownAndFade: {
+ from: { opacity: "0", transform: "translateY(-6px)" },
+ to: { opacity: "1", transform: "translateY(0)" },
+ },
+ slideLeftAndFade: {
+ from: { opacity: "0", transform: "translateX(6px)" },
+ to: { opacity: "1", transform: "translateX(0)" },
+ },
+ slideUpAndFade: {
+ from: { opacity: "0", transform: "translateY(6px)" },
+ to: { opacity: "1", transform: "translateY(0)" },
+ },
+ slideRightAndFade: {
+ from: { opacity: "0", transform: "translateX(-6px)" },
+ to: { opacity: "1", transform: "translateX(0)" },
+ },
+ accordionOpen: {
+ from: { height: "0px" },
+ to: { height: "var(--radix-accordion-content-height)" },
+ },
+ accordionClose: {
+ from: {
+ height: "var(--radix-accordion-content-height)",
+ },
+ to: { height: "0px" },
+ },
+ dialogOverlayShow: {
+ from: { opacity: "0" },
+ to: { opacity: "1" },
+ },
+ dialogContentShow: {
+ from: {
+ opacity: "0",
+ transform: "translate(-50%, -45%) scale(0.95)",
+ },
+ to: { opacity: "1", transform: "translate(-50%, -50%) scale(1)" },
+ },
+ drawerSlideLeftAndFade: {
+ from: { opacity: "0", transform: "translateX(100%)" },
+ to: { opacity: "1", transform: "translateX(0)" },
+ },
+ drawerSlideRightAndFade: {
+ from: { opacity: "1", transform: "translateX(0)" },
+ to: { opacity: "0", transform: "translateX(100%)" },
+ },
+ },
+ },
+ animation: {
+ hide: "hide 150ms cubic-bezier(0.16, 1, 0.3, 1)",
+ slideDownAndFade: "slideDownAndFade 150ms cubic-bezier(0.16, 1, 0.3, 1)",
+ slideLeftAndFade: "slideLeftAndFade 150ms cubic-bezier(0.16, 1, 0.3, 1)",
+ slideUpAndFade: "slideUpAndFade 150ms cubic-bezier(0.16, 1, 0.3, 1)",
+ slideRightAndFade:
+ "slideRightAndFade 150ms cubic-bezier(0.16, 1, 0.3, 1)",
+ // Accordion
+ accordionOpen: "accordionOpen 150ms cubic-bezier(0.87, 0, 0.13, 1)",
+ accordionClose: "accordionClose 150ms cubic-bezier(0.87, 0, 0.13, 1)",
+ // Dialog
+ dialogOverlayShow:
+ "dialogOverlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1)",
+ dialogContentShow:
+ "dialogContentShow 150ms cubic-bezier(0.16, 1, 0.3, 1)",
+ // Drawer
+ drawerSlideLeftAndFade:
+ "drawerSlideLeftAndFade 150ms cubic-bezier(0.16, 1, 0.3, 1)",
+ drawerSlideRightAndFade: "drawerSlideRightAndFade 150ms ease-in",
+ },
},
+ plugins: [require("@tailwindcss/forms")],
}
export default config
ユーティリティとヘルパーを新規追加
Tremorのコンポーネントが依存しているユーティリティを作成する。
src/lib/utils.ts
src/lib/utils.ts
// Tremor Raw cx [v0.0.0]
import clsx, { type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cx(...args: ClassValue[]) {
return twMerge(clsx(...args))
}
// Tremor Raw focusInput [v0.0.1]
export const focusInput = [
// base
"focus:ring-2",
// ring color
"focus:ring-blue-200 focus:dark:ring-blue-700/30",
// border color
"focus:border-blue-500 focus:dark:border-blue-700",
]
// Tremor Raw focusRing [v0.0.1]
export const focusRing = [
// base
"outline outline-offset-2 outline-0 focus-visible:outline-2",
// outline color
"outline-blue-500 dark:outline-blue-500",
]
// Tremor Raw hasErrorInput [v0.0.1]
export const hasErrorInput = [
// base
"ring-2",
// border color
"border-red-500 dark:border-red-700",
// ring color
"ring-red-200 dark:ring-red-700/30",
]
src/lib/chartUtils.ts
src/lib/chartUtils.ts
// Tremor Raw chartColors [v0.1.0]
export type ColorUtility = "bg" | "stroke" | "fill" | "text"
export const chartColors = {
blue: {
bg: "bg-blue-500",
stroke: "stroke-blue-500",
fill: "fill-blue-500",
text: "text-blue-500",
},
emerald: {
bg: "bg-emerald-500",
stroke: "stroke-emerald-500",
fill: "fill-emerald-500",
text: "text-emerald-500",
},
violet: {
bg: "bg-violet-500",
stroke: "stroke-violet-500",
fill: "fill-violet-500",
text: "text-violet-500",
},
amber: {
bg: "bg-amber-500",
stroke: "stroke-amber-500",
fill: "fill-amber-500",
text: "text-amber-500",
},
gray: {
bg: "bg-gray-500",
stroke: "stroke-gray-500",
fill: "fill-gray-500",
text: "text-gray-500",
},
cyan: {
bg: "bg-cyan-500",
stroke: "stroke-cyan-500",
fill: "fill-cyan-500",
text: "text-cyan-500",
},
pink: {
bg: "bg-pink-500",
stroke: "stroke-pink-500",
fill: "fill-pink-500",
text: "text-pink-500",
},
lime: {
bg: "bg-lime-500",
stroke: "stroke-lime-500",
fill: "fill-lime-500",
text: "text-lime-500",
},
fuchsia: {
bg: "bg-fuchsia-500",
stroke: "stroke-fuchsia-500",
fill: "fill-fuchsia-500",
text: "text-fuchsia-500",
},
} as const satisfies {
[color: string]: {
[key in ColorUtility]: string
}
}
export type AvailableChartColorsKeys = keyof typeof chartColors
export const AvailableChartColors: AvailableChartColorsKeys[] = Object.keys(
chartColors,
) as Array<AvailableChartColorsKeys>
export const constructCategoryColors = (
categories: string[],
colors: AvailableChartColorsKeys[],
): Map<string, AvailableChartColorsKeys> => {
const categoryColors = new Map<string, AvailableChartColorsKeys>()
categories.forEach((category, index) => {
categoryColors.set(category, colors[index % colors.length])
})
return categoryColors
}
export const getColorClassName = (
color: AvailableChartColorsKeys,
type: ColorUtility,
): string => {
const fallbackColor = {
bg: "bg-gray-500",
stroke: "stroke-gray-500",
fill: "fill-gray-500",
text: "text-gray-500",
}
return chartColors[color]?.[type] ?? fallbackColor[type]
}
// Tremor Raw getYAxisDomain [v0.0.0]
export const getYAxisDomain = (
autoMinValue: boolean,
minValue: number | undefined,
maxValue: number | undefined,
) => {
const minDomain = autoMinValue ? "auto" : minValue ?? 0
const maxDomain = maxValue ?? "auto"
return [minDomain, maxDomain]
}
// Tremor Raw hasOnlyOneValueForKey [v0.1.0]
export function hasOnlyOneValueForKey(
array: any[],
keyToCheck: string,
): boolean {
const val: any[] = []
for (const obj of array) {
if (Object.prototype.hasOwnProperty.call(obj, keyToCheck)) {
val.push(obj[keyToCheck])
if (val.length > 1) {
return false
}
}
}
return true
}
Area Chart
Area Chart(面グラフ)の使い方
⚠️更新中