workspace-ui-kit/.claude/skills/designing-workspace-ui/references/coding-rules.md
snc777 f6a781c47e initial commit
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 14:24:17 +09:00

271 lines
7.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# コーディングルール詳細
このプロジェクト固有の Incorrect/Correct ペアを収録する。shadcn スキルの `rules/` にある汎用例とは異なり、workspace-ui-kit の実コードに基づく固有パターンを扱う。
## 目次
- baseBase UI固有のパターン
- スペーシング
- タイポグラフィ
- セマンティックカラー
- アイコン
- コンポジション
- フォーム
- components/ui/ 編集の具体例
---
## baseBase UI固有のパターン
このプロジェクトは baseBase UIを使用している`components.json` の `style` フィールドで確認できる。AI は radix の API をデフォルトで生成しやすいので、特に注意する。
**Incorrect:**
```tsx
<DialogTrigger asChild>
<Button variant="outline">設定を開く</Button>
</DialogTrigger>
```
**Correct:**
```tsx
<DialogTrigger render={<Button variant="outline" />}>設定を開く</DialogTrigger>
```
ポイント: base では `asChild` ではなく `render` を使う。`render` は JSX 要素を受け取り、そのまま描画する。
**Incorrect:**
```tsx
<SelectTrigger asChild>
<Button variant="outline">{selectedValue}</Button>
</SelectTrigger>
```
**Correct:**
```tsx
<SelectTrigger>
<SelectValue placeholder="選択してください" />
</SelectTrigger>
```
ポイント: base の Select は `items` prop でオプションを渡す方式もある。`components.json` の `base` フィールドを確認して API を選ぶ。
---
## スペーシング
**Incorrect — Pane4 のセクション内で子要素に余白を持たせている:**
```tsx
<section>
<h2 className="...">プロフィール</h2>
<div className="space-y-3">
<Row label="氏名">...</Row>
<Row label="生年月日">...</Row>
{showSource && <Row label="応募経路">...</Row>}
</div>
</section>
```
**Correct — 入れ物が子同士の隙間を管理:**
```tsx
<section>
<h2 className="...">プロフィール</h2>
<div className="flex flex-col gap-3">
<Row label="氏名">...</Row>
<Row label="生年月日">...</Row>
{showSource && <Row label="応募経路">...</Row>}
</div>
</section>
```
`showSource``false` になったとき、`space-y-3` だと最後の `Row` に余計な `margin-top` が残ることがある。`gap-3` なら親が管理するので子が何個でも崩れない。
---
## タイポグラフィ
**Incorrect — 呼び出し側で見た目を毎回打ち消す(コピペ蔓延の原因):**
```tsx
<CardTitle className="text-sm font-semibold text-foreground">
評価サマリ
</CardTitle>
```
**Correct — 部品側にサイズ展開を用意して、呼び出し側はシンプルに:**
```tsx
<CardTitle data-size="sm">評価サマリ</CardTitle>
```
`components/ui/card.tsx``data-size="sm"` の variant を追加するcomponents/ui/ 編集の具体例を参照)。
**Incorrect — Pane4 セクション見出しを h2 + className で毎回書く:**
```tsx
<h2 className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">
基本情報
</h2>
```
この h2 + className の組み合わせが 11 セクション分コピペされているのが前回の監査で発見された。
**Correct — 見出し用のプリミティブを抽出:**
```tsx
<SectionLabel>基本情報</SectionLabel>
```
共通の見出しプリミティブ `<SectionLabel>``components/primitives/` に作り、内部で h2 + スタイルを持たせる。
---
## セマンティックカラー
**Incorrect — 生の色クラスで状態を表す:**
```tsx
<span className="text-emerald-600">通過</span>
<span className="text-red-600">不合格</span>
```
**Correct — semantic token または Badge variant:**
```tsx
<Badge variant="secondary">通過</Badge>
<span className="text-destructive">不合格</span>
```
このプロジェクトのサーフェス階層は `openspec/decision/` の配色 ADR で定義されている。`app/globals.css` の `@theme` セクションと ADR を読み、既存トークンで表現できるか確認する。
---
## アイコン
**Incorrect — Button 内のアイコンにサイジングクラスを付ける:**
```tsx
<Button variant="ghost" size="icon">
<Settings className="h-4 w-4" />
</Button>
```
**Correct — 部品が CSS でアイコンサイズを制御:**
```tsx
<Button variant="ghost" size="icon">
<Settings />
</Button>
```
shadcn の Button は内部でアイコンのサイズを制御している。サイジングクラスを付けると二重制御になり、サイズ変更時に壊れる。
正方形のアイコンコンテナには `size-*` を使う:
```tsx
<span className="flex size-8 items-center justify-center rounded-md bg-muted">
<Mail />
</span>
```
---
## コンポジション
**Card は Header / Title / Content のフル構成で使う。** 中身を全部 CardContent に詰めない:
```tsx
<Card>
<CardHeader>
<CardTitle>評価サマリ</CardTitle>
<CardDescription>最新の面接評価</CardDescription>
</CardHeader>
<CardContent>{/* 本文 */}</CardContent>
</Card>
```
**Dialog / Sheet / Drawer には Title が必須。** 視覚的に不要でも `sr-only` で付ける:
```tsx
<Dialog>
<DialogContent>
<DialogTitle className="sr-only">候補者を追加</DialogTitle>
{/* 本文 */}
</DialogContent>
</Dialog>
```
**Avatar には AvatarFallback が必須。** 画像が読み込めなかった場合の代替表示:
```tsx
<Avatar className="size-10">
<AvatarImage src={candidate.avatarUrl} alt={candidate.name} />
<AvatarFallback>{candidate.name.charAt(0)}</AvatarFallback>
</Avatar>
```
---
## フォーム
shadcn の Forms ルールに従い、`FieldGroup` + `Field` + `FieldLabel` で構成する。生の `div` + `Label` で組まない。
```tsx
<FieldGroup>
<Field>
<FieldLabel htmlFor="name">氏名</FieldLabel>
<Input id="name" />
</Field>
<Field>
<FieldLabel htmlFor="email">メールアドレス</FieldLabel>
<Input id="email" type="email" />
</Field>
</FieldGroup>
```
2〜7 択の選択肢には `ToggleGroup` を使う。`Button` をループして独自の active state を管理しない。
---
## components/ui/ 編集の具体例
### variant を追加する手順
例: `CardTitle``data-size="sm"` variant を追加する。
`components/ui/card.tsx` を開き、`CardTitle` の定義に data 属性によるスタイル切替を追加:
```tsx
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn(
"text-base leading-none font-medium",
"data-[size=sm]:text-sm data-[size=sm]:font-semibold",
className,
)}
{...props}
/>
);
}
```
呼び出し側:
```tsx
<CardTitle data-size="sm">評価サマリ</CardTitle>
```
### upstream の更新を取り込む手順
1. `npx shadcn@latest add card --dry-run` で影響範囲を確認
2. `npx shadcn@latest add card --diff card.tsx` で自分の変更と公式の変更を比較
3. 差分を見て、自分の variant 追加を保持しつつ公式の修正を取り込む
4. `--overwrite` はユーザーの明示的な承認なしに使わない