コンポーネントライブラリ

RoomCraft — v1.0.0 — 2026-03-19

Component Library: RoomCraft

作成日: 2026-03-19 ベース: shadcn/ui + Radix UI + Three.js (React Three Fiber) スタイリング: Tailwind CSS アニメーション: motion/react アイコン: Lucide React


1. 基本方針

1.1 コンポーネント設計原則

  • shadcn/ui をベースに、RoomCraft固有のデザイントークンを上書き
  • Radix UI プリミティブで堅牢なアクセシビリティを確保
  • motion/react によるアニメーションは transform / opacity プロパティのみ(200ms以下)
  • Three.js + React Three Fiber で3D要素を構築し、通常UIと明確に分離

1.2 ファイル構成

src/ ├── components/ │ ├── ui/ # shadcn/ui ベースコンポーネント │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── input.tsx │ │ ├── slider.tsx │ │ └── ... │ ├── furniture/ # 家具関連コンポーネント │ │ ├── FurnitureCard.tsx │ │ ├── FurnitureCarousel.tsx │ │ ├── FurnitureFilter.tsx │ │ └── FurnitureDetail.tsx │ ├── viewer/ # 3D/2Dビューア │ │ ├── RoomViewport.tsx │ │ ├── FloorPlanEditor.tsx │ │ ├── FurnitureModel.tsx │ │ └── ViewportControls.tsx │ ├── layout/ # レイアウト │ │ ├── Header.tsx │ │ ├── Sidebar.tsx │ │ ├── BottomSheet.tsx │ │ └── MakerLayout.tsx │ └── common/ # 共通コンポーネント │ ├── ColorPicker.tsx │ ├── LoadingState.tsx │ └── EmptyState.tsx

2. shadcn/ui コンポーネント設計

2.1 Button

// カラートークン上書き(tailwind.config.ts)
// primary: #2C3E2D, accent: #C8956C

// バリアント
<Button variant="default">    // primary #2C3E2D, white text
<Button variant="secondary">  // secondary #F5F0E8, charcoal text
<Button variant="outline">    // border #D4D0C8, transparent bg
<Button variant="ghost">      // no border, hover: #F5F0E8
<Button variant="cta">        // accent #C8956C, white text - 主要CTA
<Button variant="sage">       // sage green #5B8A72 - サブアクション

スタイル定義:

  • 角丸: rounded-md (8px)
  • シャドウ: shadow-[0_1px_2px_rgba(44,62,45,0.06)]
  • フォント: font-montserrat font-medium text-sm (Montserrat 500, 0.875rem)
  • 最小タッチターゲット: min-h-[44px] min-w-[44px]
  • アニメーション: transition-opacity duration-150 (opacity のみ)

2.2 Card

// 家具商品カード
<FurnitureCard
  product={product}
  onSelect={handleSelect}
  variant="grid"    // グリッド表示(正方形サムネイル)
  variant="list"    // リスト表示(横長)
/>

スタイル定義:

  • 角丸: rounded-xl (12px)
  • シャドウ: shadow-[0_4px_12px_rgba(44,62,45,0.06)]
  • ホバー: hover:shadow-[0_8px_24px_rgba(44,62,45,0.12)] hover:-translate-y-0.5
  • アニメーション: transition-[shadow,transform] duration-200
  • サムネイル: aspect-square (正方形統一)
  • 背景: bg-[#FAFAF7] (オフホワイト)

2.3 Input

<Input placeholder="家具を検索..." />
<Input type="search" icon={<Search />} />

スタイル定義:

  • 角丸: rounded-md (8px)
  • ボーダー: border border-[#D4D0C8]
  • シャドウ: なし
  • フォーカス: focus:border-[#2C3E2D] focus:ring-1 focus:ring-[#2C3E2D]
  • フォント: font-noto-sans-jp text-base

2.4 Slider(価格・サイズフィルタ)

<PriceRangeSlider
  min={0}
  max={1000000}
  step={1000}
  defaultValue={[0, 500000]}
  formatValue={(v) => `¥${v.toLocaleString()}`}
/>

スタイル定義:

  • トラック: bg-[#D4D0C8]
  • 塗り: bg-[#2C3E2D]
  • サム: bg-white border-2 border-[#2C3E2D] rounded-full w-5 h-5
  • 最小タッチターゲット: min-h-[44px]

3. 特殊コンポーネント設計

3.1 RoomViewport(3Dビューポート)

import { Canvas } from '@react-three/fiber'
import { OrbitControls, Environment, useGLTF } from '@react-three/drei'
import { motion } from 'motion/react'

export function RoomViewport({ roomConfig, selectedFurniture }) {
  return (
    <div className="relative w-full h-full rounded-none bg-[#FAFAF7]">
      {/* Three.js Canvas */}
      <Canvas
        camera={{ position: [0, 5, 10], fov: 60 }}
        gl={{ antialias: true, powerPreference: 'high-performance' }}
      >
        <Suspense fallback={<ModelLoadingFallback />}>
          <RoomScene roomConfig={roomConfig} />
          <FurnitureInstances furniture={selectedFurniture} />
          <Environment preset="apartment" />
          <OrbitControls
            enableDamping
            dampingFactor={0.05}
            minPolarAngle={0}
            maxPolarAngle={Math.PI / 2}
          />
        </Suspense>
      </Canvas>

      {/* UIオーバーレイ(半透明) */}
      <ViewportControls className="absolute bottom-4 right-4" />
      <FurnitureSelectionHUD className="absolute top-4 left-4" />
    </div>
  )
}

3D設計要件:

  • 背景: 部屋の雰囲気を再現(壁紙・照明反映)。#FAFAF7 ベース
  • UIオーバーレイ: bg-[rgba(26,29,26,0.7)] backdrop-blur-sm (半透明)
  • 家具選択ハイライト: アウトライン方式(THREE.LineSegments または outlinePass
  • グリッドヘルパー: デフォルトOFF、ツールバーからトグル
  • 寸法表示: 選択時のみ表示
  • フォーカス対応: キーボードで軌道操作可能(tabIndex={0}
  • 3Dモデルロード: Progressive Loading(<Suspense> + プログレスバー)

3.2 ModelLoadingFallback(ローディング)

export function ModelLoadingFallback() {
  return (
    <motion.div
      className="absolute inset-0 flex flex-col items-center justify-center bg-[#FAFAF7]"
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 0.15 }}
    >
      {/* シルエットプレースホルダー */}
      <div className="w-48 h-32 rounded-lg bg-[#D4D0C8] animate-pulse" />
      {/* プログレスバー */}
      <div className="mt-4 w-48 h-1 bg-[#D4D0C8] rounded-full overflow-hidden">
        <motion.div
          className="h-full bg-[#2C3E2D] rounded-full"
          initial={{ width: "0%" }}
          animate={{ width: "100%" }}
          transition={{ duration: 2, ease: "easeOut" }}
        />
      </div>
      <p className="mt-2 text-sm text-[#8A8A80] font-noto-sans-jp">
        家具モデルを読み込み中...
      </p>
    </motion.div>
  )
}

3.3 FloorPlanEditor(間取りエディタ)

import Konva from 'konva'
import { Stage, Layer, Rect, Line, Transformer } from 'react-konva'

export function FloorPlanEditor({ roomConfig, onRoomUpdate }) {
  // 2D間取り編集
  // - 壁・部屋形状の描画
  // - 家具の2D投影配置
  // - グリッドスナップ
  // - 寸法ラベル表示
}

3.4 FurnitureCarousel(商品カルーセル)

import { motion, AnimatePresence } from 'motion/react'

export function FurnitureCarousel({ items }) {
  return (
    <motion.div
      className="flex gap-4 overflow-x-auto scrollbar-hide px-4"
      drag="x"
      dragConstraints={{ right: 0, left: -totalWidth }}
      whileTap={{ cursor: "grabbing" }}
    >
      {items.map((item) => (
        <motion.div
          key={item.id}
          className="flex-none"
          whileHover={{ scale: 1.02 }}        // transform のみ
          transition={{ duration: 0.15 }}
        >
          <FurnitureCard product={item} />
        </motion.div>
      ))}
    </motion.div>
  )
}

3.5 BottomSheet(モバイル家具パネル)

import { motion, useDragControls } from 'motion/react'

export function BottomSheet({ isOpen, onClose, children }) {
  return (
    <motion.div
      className="fixed bottom-0 left-0 right-0 bg-white rounded-t-2xl shadow-modal z-50"
      style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}
      initial={{ y: "100%" }}
      animate={{ y: isOpen ? 0 : "100%" }}
      transition={{ type: "spring", damping: 30, stiffness: 300 }}
      drag="y"
      dragConstraints={{ top: 0 }}
      onDragEnd={handleDragEnd}
    >
      {/* ドラッグハンドル */}
      <div className="flex justify-center pt-3 pb-2">
        <div className="w-10 h-1 bg-[#D4D0C8] rounded-full" />
      </div>
      {children}
    </motion.div>
  )
}

3.6 EmptyState(空状態)

export function EmptyState({ type }: { type: 'room' | 'search' | 'error' }) {
  const configs = {
    room: {
      illustration: <RoomIllustration />,
      title: "家具を配置しましょう",
      description: "右のパネルから家具を選んで、ドラッグ&ドロップで配置できます",
      action: <Button variant="cta">家具を選ぶ</Button>,
    },
    search: {
      illustration: <SearchIllustration />,
      title: "検索結果がありません",
      description: "条件を変更してもう一度お試しください",
      action: <Button variant="outline" onClick={clearFilters}>条件をリセット</Button>,
    },
    error: {
      illustration: <ErrorIllustration />,
      title: "エラーが発生しました",
      description: "しばらく時間をおいてから再度お試しください",
      action: <Button variant="outline" onClick={retry}>再試行</Button>,
    },
  }
  // ...
}

4. 管理画面コンポーネント

4.1 DataTable(メーカー管理)

// 行ごとの微妙な背景色差で可読性向上
<DataTable
  data={products}
  columns={columns}
  rowClassName={(row, index) =>
    index % 2 === 0 ? "bg-white" : "bg-[#FAFAF7]"
  }
/>

4.2 SalesChart(売上グラフ)

// Earth tone系カラーで統一
const chartColors = {
  primary: "#2C3E2D",
  secondary: "#5B8A72",
  accent: "#C8956C",
  muted: "#D4D0C8",
}

4.3 ProductThumbnail(商品サムネイル)

// 正方形統一
<ProductThumbnail
  src={product.image}
  alt={product.name}
  className="aspect-square w-16 h-16 rounded-md object-cover"
/>

5. アクセシビリティ要件

コンポーネント要件
全インタラクティブ要素aria-label または可視ラベル必須
3Dビューポートrole="application" + aria-label="3D家具配置ビューア"
アイコンボタンaria-label 必須(<Button aria-label="家具を追加">
フォーム<label> または aria-labelledby 必須
ローディングaria-busy="true" + aria-live="polite"
エラーメッセージrole="alert"
フォーカスインジケーターfocus-visible:ring-2 focus-visible:ring-[#2C3E2D] 常時表示
キーボード操作全操作対応(3Dビュー含む。矢印キーで軌道回転)

6. 禁止パターン

カテゴリ禁止理由
フォントInter, Roboto, Arial, Space GroteskAI生成典型
カラー紫グラデーション on 白背景AI生成典型
アニメーションlayout プロパティ (width, height, top, left) のアニメーションCLS悪化
アニメーション200ms超のインタラクションアニメーション体感遅延
エフェクト過剰なドロップシャドウAI生成典型
エフェクトグロー・ネオン効果デザイン方針外
3D技術デモ的な暗いビューポート没入感の逆効果

Generated for RoomCraft - Phase 2: Design CCAGI SDK v3.14.4