1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI駆動フロントエンド開発 ~ Day 5 シンプルなコンポーネント実装編 ~

1
Posted at

はじめに

Day 4 でトークンの登録が完了しました。今回はカードやボタンなどのコンポーネントを実装していきます。

前回はこちら。

日程 テーマ 内容
Day 1 環境構築 Next.js + TypeScript セットアップ
Day 2 Cursor × Figma MCP 連携設定 Cursor で Figma デザインを読み込めるようにする
Day 3 デザイン分析・精査 AI がトークン/コンポーネント提案 → 人間が精査
Day 4 Tokens Studio登録 AI の提案をトークンとして登録
☆ Day 5 シンプルなコンポーネント実装 Button, Input, Card など基本部品を実装
Day 6 ダッシュボード完成 全コンポーネント統合・完成
Day 7 完結・まとめ 6日間の体験総括

実装するコンポーネント

- NavigationItem
- Button
- KpiCard
- ChartCard
- ActivityTableRow
- Status

ファイル構成

以下のような構成でコンポーネントを作ります。

src/
├── components/
│   └── ui/
│       ├── Button.tsx
│       ├── Input.tsx
│       ├── Card.tsx
│       ├── Badge.tsx
│       ├── Alert.tsx
│       ├── Select.tsx
│       ├── Checkbox.tsx
│       └── index.ts // 全コンポーネントをエクスポート

Cursor に実装指示

最初に Figma に存在しているコンポーネントを読み取らせます。

Figmaに定義されているコンポーネントを読み取ってください。
以下の要件でコンポーネントを実装してください:

## 要件

- TypeScript で厳密に型定義
- Tailwind CSS で全てスタイリング
- トークン(primary, accent など)を使用
- React 18 + の機能を使用
- arrow function で実装
- default export ではなく named export

## ファイル保存先

各コンポーネントを `src/components/ui/[ComponentName].tsx` に保存してください。

その後、`src/components/ui/index.ts` で全コンポーネントをまとめてエクスポートしてください。

AI の実装結果

実装したものが以下になります。ファイル数が多いため一部抜粋しています。
上記プロンプトでは一発で完全に実装できなかったため、何回か修正を入れています。

次回の画面実装時にまた微修正は入れていきます。

Button.tsx

"use client";

import { type FC, type ReactNode } from "react";
import { cn } from "@/lib/utils";

export type ButtonSize = "medium";
export type ButtonState = "Default" | "Hover" | "Active" | "Disabled";

export interface ButtonProps {
  size?: ButtonSize;
  state?: ButtonState;
  children: ReactNode;
  onClick?: () => void;
  disabled?: boolean;
  className?: string;
  type?: "button" | "submit" | "reset";
}

export const Button: FC<ButtonProps> = ({ size = "medium", state = "Default", children, onClick, disabled = false, className, type = "button" }) => {
  const isDisabled = disabled || state === "Disabled";

  return (
    <button
      type={type}
      onClick={onClick}
      disabled={isDisabled}
      className={cn(
        "inline-flex items-center justify-center rounded-button font-bold text-text-white transition-colors",
        size === "medium" && "px-6 py-4 text-3xl",
        state === "Default" && "bg-accent-indigoDark hover:bg-accent-indigoDark/90",
        state === "Hover" && "bg-accent-indigoDark/90",
        state === "Active" && "bg-accent-indigoDark/80",
        state === "Disabled" && "bg-accent-indigoDark/50 cursor-not-allowed",
        isDisabled && "cursor-not-allowed opacity-50",
        className,
      )}
    >
      {children}
    </button>
  );
};

KpiCard.tsx

import { type FC, type ReactNode } from "react";
import { cn } from "@/lib/utils";
import { tokens } from "@/lib/tokens";

export type KpiCardVariant = "indigo" | "orange" | "green" | "red";

export interface KpiCardProps {
  variant: KpiCardVariant;
  title: string;
  value: string;
  delta: string;
  deltaType: "increase" | "decrease";
  icon?: ReactNode;
  className?: string;
}

export const KpiCard: FC<KpiCardProps> = ({ variant, title, value, delta, deltaType, icon, className }) => {
  const borderColor = tokens.colors.accent?.[variant]?.value;
  const iconBgColor = tokens.colors.icon?.background?.[variant]?.value;
  const deltaColor = deltaType === "increase" ? "text-status-success-text" : "text-status-error-text";

  return (
    <div className={cn("flex flex-col gap-4 p-6 rounded-card bg-background-primary border-l-[6px]", className)} style={{ borderLeftColor: borderColor }}>
      <div className="flex items-start justify-between">
        <h3 className="text-3xl font-bold text-text-secondary">{title}</h3>
        {icon && (
          <div className="flex items-center justify-center w-24 h-24 rounded-iconArea" style={{ backgroundColor: iconBgColor }}>
            <span className="text-5xl">{icon}</span>
          </div>
        )}
      </div>

      <div className="text-6xl font-bold text-text-primary">{value}</div>

      <div className={cn("text-2xl font-normal", deltaColor)}>{delta}</div>
    </div>
  );
};

index.ts

export { Status, type StatusProps, type StatusVariant } from "./Status";
export { NavigationItem, type NavigationItemProps, type NavigationItemVariant } from "./NavigationItem";
export { Button, type ButtonProps, type ButtonSize, type ButtonState } from "./Button";
export { KpiCard, type KpiCardProps, type KpiCardVariant } from "./KpiCard";
export { ChartCard, type ChartCardProps } from "./ChartCard";
export { ActivityTableRow, type ActivityTableRowProps, type ActivityTableRowStatus } from "./ActivityTableRow";

コンポーネントの検証

src/app/page.tsx にコンポーネントを呼び出し、動作確認をします。

ブラウザで pnpm dev を実行してください。

スクリーンショット 2025-12-21 20.00.05.png

最後に

次回はダッシュボードを完成させていきます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?