カードコンポーネント
今回はポレゴンフーズで使用しているカードコンポーネントの作り方について綴っていこうと思う。
まずは前提事項だ
- Reactの基礎がわかる
- TypeScriptの基礎がわかる
- TailWindCSSインストール済み
npm install ~~~
みたいな環境構築は今回省くのでご了承ください。
それでは早速完成図から見ていこう
見本
descriptiondescriptiondescriptiondescriptiondescriptiondescriptiondescription
2021年2月14日 10時20分
少し歪な形をしているがマークダウンの仕様上、これが限界だった。
普段技術系のブログ一覧を見ていただければ見本はわかるだろう。
次の章から作り方を書いていこう。
ソースコード
では早速ソースコードを貼り付けよう
//blogCard.tsx
import Link from "next/link";
import { VFC } from "react";
import { AiOutlineFieldTime } from "react-icons/ai";
import { BiTimeFive } from "react-icons/bi";
import { format, formatDistanceToNow } from "date-fns";
import { ja } from "date-fns/locale";
import { cardIcon } from "../../../../public/icon";
import { Tooltip } from "@chakra-ui/react";
import { theme } from "../ChakraTheme/color";
type BlogInfo = {
key: string;
link: string;
imageUrl: string;
imageAlt: string;
createAt: string;
title: string;
text: string;
tag:
| "その他"
| "グルメ"
| "エッセイ"
| "日記"
| "フロントエンド"
| "バックエンド";
description: string;
};
export const DevelopCard: VFC<BlogInfo> = (props) => {
const target = new Date(props.createAt);
const date = format(new Date(props.createAt), "yyyy年MM月dd日");
return (
<Tooltip
theme={theme}
hasArrow
label={`文字数は${props.text.length}文字程です。`}
aria-label="A tooltip"
placement="auto"
bg="color.100"
color="color.1000"
>
<article
key={props.key}
className="relative transition duration-300 ease-in-out transform hover:-translate-y-2 max-w-xs rounded overflow-hidden shadow-lg my-4 bg-white dark:bg-gray-600 hover:bg-sushi hover:bg-opacity-10 dark:hover:bg-gray-500"
>
<span className="absolute right-0 top-0 inline-block bg-sushi dark:bg-darkSushi rounded text-xs sm:text-sm font-semibold p-1">
{props.text.length >= 2500 ? (
<>
読了推定5分以上
<BiTimeFive className="text-base sm:text-lg inline-block" />
</>
) : (
<>
読了推定5分以内
<AiOutlineFieldTime className="text-base sm:text-lg inline-block" />
</>
)}
</span>
<Link
href={{
pathname: "/develop/[developId]",
query: { developId: props.link },
}}
>
<a>
<picture className="w-full">
<img
className="w-full max-h-52 object-contain bg-gray-400"
src={props.imageUrl || cardIcon.devCardIcon}
alt={props.imageAlt}
/>
</picture>
</a>
</Link>
<div className="px-4 py-2">
<div className="font-bold text-base sm:text-xl mb-2 text-darkSushi dark:text-sushi">
<Link
href={{
pathname: "/develop/[developId]",
query: { developId: props.link },
}}
>
<a>{props.title}</a>
</Link>
</div>
<p className="text-grey-darker text-sm sm:text-base">
{props.description}
</p>
</div>
<div className="px-2">
<span className="inline-block bg-sushi dark:bg-darkSushi rounded-md text-xs sm:text-sm font-semibold py-1 px-2 mx-1">
{props.tag}
</span>
<span className="inline-block bg-sushi dark:bg-darkSushi rounded-md text-xs sm:text-sm font-semibold py-1 px-2 mx-1">
{formatDistanceToNow(target, { addSuffix: true, locale: ja })}
</span>
</div>
<div className="py-2">
<p className="text-xs sm:text-sm font-semibold py-1 px-2 mx-1 text-right text-gray-400">
{date}
</p>
</div>
</article>
</Tooltip>
);
};
必要なパッケージをimportする
上記で言うところの下記だ
import Link from "next/link";
import { VFC } from "react";
import { AiOutlineFieldTime } from "react-icons/ai";
import { BiTimeFive } from "react-icons/bi";
import { format, formatDistanceToNow } from "date-fns";
import { ja } from "date-fns/locale";
import { cardIcon } from "../../../../public/icon";
import { Tooltip } from "@chakra-ui/react";
import { theme } from "../ChakraTheme/color";
一つ一つ解説してると膨大な時間になるためポイントだけ解説しよう。
- {theme}はchakra-UIで個人的に設定した色をプロパティ化したものを持ってきている。
- {cardIcon}には画像の読み込みに失敗した時の代案画像が格納されている
型を定義
type BlogInfo = {
key: string;
link: string;
imageUrl: string;
imageAlt: string;
createAt: string;
title: string;
text: string;
tag:
| "その他"
| "グルメ"
| "エッセイ"
| "日記"
| "フロントエンド"
| "バックエンド";
description: string;
};
命名は雑だが全部stringで呼んでいる。 本当は
type BlogInfo = {
path: "develop" | "blog",
};
みたいに条件分岐してあげるのも良いかと思ったが、今回はdevelopカードとblogカードはそれぞれ別のコンポーネントで設定している。
ソースコード
export const DevelopCard: VFC<BlogInfo> = (props) => {
const target = new Date(props.createAt);
const date = format(new Date(props.createAt), "yyyy年MM月dd日");
return (
<Tooltip
theme={theme}
hasArrow
label={`文字数は${props.text.length}文字程です。`}
aria-label="A tooltip"
placement="auto"
bg="color.100"
color="color.1000"
>
<article
key={props.key}
className="relative transition duration-300 ease-in-out transform hover:-translate-y-2 max-w-xs rounded overflow-hidden shadow-lg my-4 bg-white dark:bg-gray-600 hover:bg-sushi hover:bg-opacity-10 dark:hover:bg-gray-500"
>
<span className="absolute right-0 top-0 inline-block bg-sushi dark:bg-darkSushi rounded text-xs sm:text-sm font-semibold p-1">
{props.text.length >= 2500 ? (
<>
読了推定5分以上
<BiTimeFive className="text-base sm:text-lg inline-block" />
</>
) : (
<>
読了推定5分以内
<AiOutlineFieldTime className="text-base sm:text-lg inline-block" />
</>
)}
</span>
<Link
href={{
pathname: "/develop/[developId]",
query: { developId: props.link },
}}
>
<a>
<picture className="w-full">
<img
className="w-full max-h-52 object-contain bg-gray-400"
src={props.imageUrl || cardIcon.devCardIcon}
alt={props.imageAlt}
/>
</picture>
</a>
</Link>
<div className="px-4 py-2">
<div className="font-bold text-base sm:text-xl mb-2 text-darkSushi dark:text-sushi">
<Link
href={{
pathname: "/develop/[developId]",
query: { developId: props.link },
}}
>
<a>{props.title}</a>
</Link>
</div>
<p className="text-grey-darker text-sm sm:text-base">
{props.description}
</p>
</div>
<div className="px-2">
<span className="inline-block bg-sushi dark:bg-darkSushi rounded-md text-xs sm:text-sm font-semibold py-1 px-2 mx-1">
{props.tag}
</span>
<span className="inline-block bg-sushi dark:bg-darkSushi rounded-md text-xs sm:text-sm font-semibold py-1 px-2 mx-1">
{formatDistanceToNow(target, { addSuffix: true, locale: ja })}
</span>
</div>
<div className="py-2">
<p className="text-xs sm:text-sm font-semibold py-1 px-2 mx-1 text-right text-gray-400">
{date}
</p>
</div>
</article>
</Tooltip>
);
};
これといって特徴はないが、強いて言うなら
<span className="absolute right-0 top-0 inline-block bg-sushi dark:bg-darkSushi rounded text-xs sm:text-sm font-semibold p-1">
{props.text.length >= 2500 ? (
<>
読了推定5分以上
<BiTimeFive className="text-base sm:text-lg inline-block" />
</>
) : (
<>
読了推定5分以内
<AiOutlineFieldTime className="text-base sm:text-lg inline-block" />
</>
)}
</span>
文字の多さによって推定読了時間を分けている。人間はおおよそ500文字読むのに1分かかるらしい。だからブレークポイントを2500文字にしているのだ。
実際のページコンポーネントで使ってみる
実際のページコンポーネントではこうやって記述する。
//index.tsx
import { Layout } from "../../components/layout";
import { DevelopCard } from "../../components/parts/Card/blogCard";
import { Tabs, TabList, TabPanels, Tab, TabPanel } from "@chakra-ui/react";
import { getAllDevelop } from "../../repositories/develop";
export async function getStaticProps() {
const develops = [];
await getAllDevelop(develops);
return {
props: {
develops,
},
revalidate: 100 * 100,
};
}
return(
<section className="flex flex-col items-center w-full text-gray-800 dark:text-white my-8 md:grid md:grid-cols-2 lg:grid-cols-3">
{develops.map((develop) => {
return (
<div
key={develop.id}
className="flex flex-col items-center w-full"
>
<DevelopCard
key={develop.id}
link={develop.id}
imageUrl={develop.imageUrl}
imageAlt={develop.title}
title={develop.title}
text={develop.text}
createAt={develop.createAt}
tag={develop.tag}
description={develop.description}
/>
</div>
);
})}
</section>
)
これでカード一覧が出てくるわけだ!
もっと詳しく知りたい!と思ったらコメントを残してくれ!
この記事書くの疲れちゃった!!
またね!!