@cakejp/design-system 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/CLAUDE.md +18 -1
  3. package/README.md +48 -0
  4. package/UPDATING.md +9 -0
  5. package/bin/check-duplicates.mjs +132 -0
  6. package/bin/init.js +8 -0
  7. package/contracts/components.json +175 -21
  8. package/contracts/rules.json +17 -1
  9. package/dist/index.css +1 -1
  10. package/dist/index.js +5266 -3354
  11. package/dist/types/components/ec/admin-shell.d.ts +10 -1
  12. package/dist/types/components/ec/mypage-shell.d.ts +10 -1
  13. package/dist/types/components/ec/product-card.d.ts +3 -1
  14. package/dist/types/components/ec/product-list-item.d.ts +8 -1
  15. package/dist/types/components/foundations/alert.d.ts +5 -1
  16. package/dist/types/components/foundations/button.d.ts +1 -1
  17. package/dist/types/components/foundations/card.d.ts +12 -2
  18. package/dist/types/components/foundations/chip-variants.d.ts +17 -0
  19. package/dist/types/components/foundations/field.d.ts +58 -0
  20. package/dist/types/components/foundations/file-input.d.ts +75 -0
  21. package/dist/types/components/foundations/icon-badge.d.ts +2 -2
  22. package/dist/types/components/foundations/input.d.ts +9 -1
  23. package/dist/types/components/foundations/radio-group.d.ts +12 -2
  24. package/dist/types/components/foundations/skip-link.d.ts +39 -0
  25. package/dist/types/components/foundations/textarea.d.ts +9 -1
  26. package/dist/types/components/primitives/chip.d.ts +2 -7
  27. package/dist/types/components/primitives/content-carousel.d.ts +52 -0
  28. package/dist/types/components/primitives/date-picker.d.ts +32 -0
  29. package/dist/types/components/primitives/error-state.d.ts +43 -16
  30. package/dist/types/components/primitives/funnel-shell.d.ts +10 -1
  31. package/dist/types/components/primitives/header.d.ts +15 -1
  32. package/dist/types/components/primitives/section-nav.d.ts +28 -2
  33. package/dist/types/components/primitives/share-buttons.d.ts +8 -2
  34. package/dist/types/components/primitives/standard-page-shell.d.ts +11 -1
  35. package/dist/types/components/primitives/workspace-shell.d.ts +19 -1
  36. package/dist/types/index.d.ts +66 -2
  37. package/package.json +1 -1
  38. package/src/preset-standalone.css +306 -4
  39. package/src/styles/semantic.css +7 -0
  40. package/src/styles/typography.css +8 -1
  41. package/templates/AGENTS.md +9 -0
  42. package/templates/CLAUDE.md +9 -0
  43. package/tokens.json +10 -1
package/CHANGELOG.md CHANGED
@@ -7,7 +7,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
- ## [Unreleased]
10
+ ## [0.12.0] - 2026-07-23
11
+
12
+ consumer(cake-distributor 等)の DS 採用時に、内部 DOM を `[&>div]` 等で上書きして凌いでいた3点の粗の解消と、手組みカルーセル排除のための新コンポーネントを収録する回。
13
+
14
+ ### ✨ Added
15
+
16
+ - **`ContentCarousel`(primitives)を新設 — ReactNode スライド対応のカルーセル**: 既存の `ImageCarousel`(`images: string[]`)と `BannerCarousel`(`imageUrl` 必須)はいずれも**画像URL前提**で、eyebrow / タイトル / 本文 / ポイント列 / 価格 / バッジで構成する**テキスト主体のヒーローバナー**を組めなかった。そのため consumer(cake-distributor)はカルーセルを手組みすることになり、autoplay / 矢印 / ドットの実装が分散して**意匠ドリフト**が発生していた(手組みの矢印が DS の白黒円形ボタンから外れ `Button variant="secondary"` のオレンジで実装されレビュー指摘 → 修正済みの実績あり)。`slides: ReactNode[]` を受け取り、autoplay / 左右矢印 / ドット / スワイプ(scroll-snap)を DS が提供する `ContentCarousel` を追加。矢印・ドットの markup は `ImageCarousel` と同一のものを流用し**意匠の一致を担保**(SP はドット + スワイプ、PC はホバーで矢印表示)。a11y は `role="region"` + `aria-roledescription="carousel"` と各スライドの `role="group"` を付与。既存の `ImageCarousel` / `BannerCarousel` の API・挙動は不変([#97](https://github.com/flashpark/cake-design-system/issues/97))
17
+ - **`SkipLink`(foundations)を新設し、全シェルが標準内蔵 — a11y スキップナビ一式**: consumer(cake-distributor)は全ページに「コンテンツへ移動」スキップリンクを置くが DS に部品が無く、`sr-only` + focus 可視化のパターンを**全ページで手組みコピペ**していた。加えて `StandardPageShell` / `FunnelShell` が描く `<main>` に **id を渡す口が無く**、スキップ先 `#main-content` を内側 Container に付けるワークアラウンドをしていた。①`SkipLink`(`href` 既定 `#main-content` / `label` 既定「コンテンツへ移動」。通常 `sr-only`・Tab フォーカス時のみ左上に可視化)を追加、②全シェル(`StandardPageShell` / `FunnelShell` / `WorkspaceShell` / `AdminShell` / `MypageShell`)に `mainId?`(既定 `main-content`)を追加して `<main id>` を制御可能にし、さらに **DOM 先頭に `SkipLink` を標準内蔵**(`skipLink={false}` で無効化)。`sr-only` なので視覚は不変で、シェル採用ページの a11y 定型がゼロコンフィグになる。既定値は現行のままで**後方互換**([#100](https://github.com/flashpark/cake-design-system/issues/100))
18
+ - **`Header` variant="logo" に `searchSlot` を追加 — PC で常設検索をナビ行と共存**: EC の PC ヘッダーは「ロゴ + グローバルナビ + 検索 + アクション」が標準構成だが、`Header` に検索の置き場が無く、consumer は `bottomSlot`(本来 ProductDetailBar 用の全幅下段)検索を SP 限定にし PC は検索アイコンで代替していた。`searchSlot?: ReactNode` を追加し、PC 行(@[768px]+、ロゴ+navItems+actions の行)で navItems と actions の間に inline 配置(`flex-1 max-w-[400px]` で伸縮・既存要素を潰さない)。SP 行には出さない(SP の常設検索は従来どおり `bottomSlot`)。navItems 無し + searchSlot ありでも PC 行を描画するよう条件を調整。既定は現行のままで**後方互換**([#102](https://github.com/flashpark/cake-design-system/issues/102))
19
+ - **`SectionNav` に横型アンダーバー variant(`orientation="horizontal"`)を追加 — ページ内ジャンプナビ**: ガイド・FAQ のような長い静的ページで「セクションへジャンプする sticky ナビ」が必要になり、consumer は Tabs underbar 風のナビを**2ページで手組み共有**していた。`AnchorNav` を新設せず、既存 `SectionNav` に `orientation?: "vertical" | "horizontal"`(既定 `"vertical"`=現行)を追加。horizontal は横並び・`overflow-x-auto`・アンダーバー意匠(`typo-label-md`、アクティブ=`border-b-2` + Text-High、非アクティブ=Text-Low + hover で High)。**`sticky` は内蔵せず**利用側が `className="sticky top-14 …"` で付与し、ジャンプ先には `scroll-mt-*` を付ける方針を docstring に明記。`IntersectionObserver` は内包せず `activeKey` は利用側制御(既存設計を踏襲)。`aria-current="location"` の a11y 流儀は維持。既定は現行のままで**後方互換**([#105](https://github.com/flashpark/cake-design-system/issues/105))
20
+ - **`HeaderAction` に `href` を追加 — カート・マイページ等の本来リンクである導線を `<a>` で描画可能に**: `Header` の `actions`(`HeaderAction`)は `onClick` のみで、本来リンクであるべき導線(カート・マイページ・ログイン等)が全て button 化され、中クリック/Cmd+クリックでの新規タブ・リンクコピー・hover プレビューが効かなかった(cake-distributor 移行時のレビューで既知の後退として記録済み)。`href?: string` を追加し、指定時は `Button asChild` + `<a href>` で描画。`onClick` と併用可(両方付与される)。href 未指定時の既存挙動は不変([#101](https://github.com/flashpark/cake-design-system/issues/101))
21
+ - **バレルから props 型(interface)・アイテム型と `toast` 関数を re-export**: consumer が型を使おうとしてもバレルに無く、`TagProps` / `HeaderAction` / `HeaderNavItem` / `MenuSection` / `FooterLinkGroup` / `SideNavGroup` 等の interface を `@cakejp/design-system` から import できず `ComponentProps<typeof X>["variant"]` 等の回避が必要だった(cake-distributor で複数箇所)。公開コンポーネントの props / アイテム型を網羅的に `export type` した。また `Toaster` は export 済みだが `toast` 関数が re-export されておらず consumer が `sonner` を直接依存に持つ必要があったため、`export { toast } from "sonner"` を追加。既存 export の改名・削除なし([#103](https://github.com/flashpark/cake-design-system/issues/103))
22
+ - **`ProductCard` / `ProductListItem` に規格・仕様キャプション(`caption`)を追加**: 「1kg / 冷蔵」等の商品の規格・仕様を表す短い自由記述を出す受け皿が無く、cake-distributor ではカタログカードは外側に手組みキャプションを追記し、お気に入り一覧では表示自体を落としていた(情報喪失)。タグ(配送方法等の enum variant)とは性質が異なるため `tags` では代替できない。両コンポーネントに `caption?: string` を追加し、価格表示の直下に `typo-body-xs` / `Text-Medium-Emphasis` で表示([#104](https://github.com/flashpark/cake-design-system/issues/104))
23
+ - **`ProductListItem`(cart variant)に商品ごとの小計(`subtotal`)を追加**: カート明細で「単価 × 数量 = 小計」を商品ごとに出す受け皿が無く、cake-distributor では `bottomSlot` にトークン手組みで実装していた(コード内に DS ギャップとしてコメント済み)。BtoB/法人 EC のロット購入では小計の明示が特に重要。`subtotal?: number` と `subtotalLabel?: string`(既定「小計」)を追加し、cart variant のときのみ数量セレクタ行の右側(収まらなければ折り返して下)に `¥{カンマ区切り}` で表示。cart variant 以外・未指定時は無視([#106](https://github.com/flashpark/cake-design-system/issues/106))
24
+ - **`Alert` の inline variant(`inline-info` / `inline-caution` / `inline-warning`)を prop-based API 化**: これまで inline variant は CSS 上の意匠は存在したが、`title` / `description` を渡すだけの宣言的な組み方ができず、20px 情報アイコン + `AlertTitle` + `AlertDescription` を毎回 children として手組みする必要があった(cake-api のスタッフ登録画面等)。既存の bordered variant(success/info/error/warning)と同じ `title` / `description` / `icon` / `action` prop で組めるようにした。アイコンは variant に応じて 20px で自動選択(`InfoCircle` / `Warning2`)、`icon` prop で上書き可。children を渡した場合は従来どおり composable に使える([#109](https://github.com/flashpark/cake-design-system/issues/109))
25
+ - **`WorkspaceShell` に `headerLayout="start" | "center"` を追加 — ヘッダータイトルの中央寄せを公開 API 化**: cake-api の管理画面編集では「左=戻る / 中央=タイトル / 右=primaryAction」の3カラム中央寄せヘッダーが必要になり、consumer 側で `[&>[data-slot=header]]:!grid ...` のようなセレクタで DS 内部の DOM 構造を上書きしていた(DS の「component 内部スタイルの consumer class 上書き禁止」ルールと衝突するグレーな回避)。`headerLayout?: "start" | "center"`(既定 `"start"` = 現行不変)を追加し、`"center"` では `Header` の `className` prop(twMerge で解決される正規の公開 API)経由で `grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]` を適用してタイトルを真の中央に固定する。既定値は現行のままで**後方互換**([#112](https://github.com/flashpark/cake-design-system/issues/112))
26
+ - **`DatePicker` / `DateTimePicker`(primitives)を新設 — Popover+Calendar 組合せ済みの日付・日時入力**: contracts は「`<input type="date">` を使わず Popover と Calendar を組む」と定めていたが、組み合わせ済みの入力 primitive が無く、consumer(cake-api の管理画面)は毎回手組みで DatePicker を実装していた(trigger の border を DS 移行期に consumer 側で当て直す手当ても発生)。`DatePicker`(`value` / `onChange` / `placeholder` / `min` / `max` / `disabledDates` / `disabled` / `id`)と、時・分 Select を足した `DateTimePicker`(`minuteStep` 既定5分)を追加。trigger は既存ページの実例(admin-shipping-period 等)の意匠を踏襲し、選択値は rules.json の日付表記ルールに従い `M月D日(曜日)`(例: `8月1日(木)`)で表示、カレンダー内ラベルは `react-day-picker/locale` の `ja` で日本語化(**date-fns 非依存** — DS 内の DeliveryCalendar / Calendar の流儀に合わせ optional peer に依存しない)。`Date` をそのまま扱い TZ 変換はしない(**JST 運用前提**、docstring に明記)。`FormField` の children に置き `id` を `htmlFor` と揃えれば label + エラー表示と接続できる。合わせて rules.json R49 / CLAUDE.md の「input type='date' 禁止」の誘導先を「実装済みの正解形 `DatePicker` / `DateTimePicker` を使う」に更新([#107](https://github.com/flashpark/cake-design-system/issues/107))
27
+ - **`Field` 群(foundations)を新設 + `Input` / `Textarea` に文字数カウンタ(`showCount`)を追加 — フォームレイアウトの標準化**: DS のフォーム系は `FormField` と `Label` / `Separator` までで、フォーム全体の見出し・グループ・説明・エラーの並べ方が consumer 実装依存になり画面ごとに余白・階層がブレていた(cake-api は `ui/field.tsx` と `CharCounter` をローカル実装)。shadcn の field パターンを DS のトークンと composition.json の縦リズムに合わせて `FieldSet`(`<fieldset>`, gap-6)/ `FieldLegend`(`<legend>`)/ `FieldGroup`(`<div role="group">`, gap-4)/ `FieldDescription` / `FieldError`(`role="alert"`)/ `FieldSeparator`(children で中央ラベル)を追加(実利用の薄い `FieldContent` 等は見送り)。加えて `Input` / `Textarea` に `showCount?: boolean` を追加し、`maxLength` とセットで右下に `{現在}/{max}` を `typo-body-xs` で表示(上限で Text-Caution 色)。制御(`value`)/ 非制御(`defaultValue`)どちらでも追従する。`showCount` 未指定時の DOM・挙動は不変で**後方互換**([#108](https://github.com/flashpark/cake-design-system/issues/108))
28
+ - **`typo-heading-4xl`(32px / bold)を追加 — ヒーロー・ページ大見出し用の最大タイポスケール**: これまで見出しの最大は `typo-heading-3xl`(28px)で、ランディングやヒーローの大見出しに一段大きいスケールが無かった。`typo-heading-4xl`(`font-size: 32px` / `line-height: 1.4` / `letter-spacing: 0.04em` / `font-weight: 700`)を既存 heading スケールと同じ定義形式で `styles/typography.css`(`@utility`)・`preset-standalone.css`(素のクラス)・`tokens.json`・`contracts/rules.json` に追加。既存スケールは不変([#119](https://github.com/flashpark/cake-design-system/issues/119))
29
+ - **`ShareButtons` にコピー成功/失敗フィードバックと `onCopy` を追加**: コピーボタンは `navigator.clipboard.writeText` を投げっぱなしで押しても無反応だった。コピー成功時はラベルを `copiedLabel`(既定「コピーしました」)へ約2秒切り替え、失敗時(例外・`navigator.clipboard` 不在)は `copyErrorLabel`(既定「コピーできませんでした」)へ切り替える。ラベル部分に `aria-live="polite"` を付与し読み上げでも通知、タイマーはアンマウント時にクリア。加えて完了結果を受け取る `onCopy?: (result: "success" \| "error") => void` を追加。既存 API は不変で**後方互換**([#120](https://github.com/flashpark/cake-design-system/issues/120))
30
+ - **`IconBadge` に中間サイズ `size="ml"`(48px / アイコン24px)を追加**: これまで `md`(44px / icon20)と `lg`(72px / icon38)の2段しかなく、その中間の受け皿が無かった。`ml`(`size-12` / `[&>svg]:size-6`)を追加(既存 md/lg の px は不変)。`contracts/components.json` の variants も更新([#121](https://github.com/flashpark/cake-design-system/issues/121))
31
+ - **`Card` に `surface="default" | "primary"` variant(CVA)を追加 — グレー地でのコントラスト確保**: グレー Section(Surface-Tertiary)の地の上では既定の `bg-card`(Surface-Secondary)はコントラストが不足していた。`surface="primary"` で `Surface-Primary`(白)背景に切り替えられるようにした(既定は現行 `bg-card` のままで**後方互換**)。`contracts/components.json` の Card 定義に variant と「Surface-Tertiary 系の地の上では `surface="primary"` を使う」ルールを追記。`cardVariants` をバレルから export([#127](https://github.com/flashpark/cake-design-system/issues/127))
32
+ - **`ErrorState` に `kind="notFound"` variant と `NotFoundIllustration` を追加 — 404 状態パターンとブランドイラスト**: DS は `EmptyState` / `ErrorState` はあったが 404 向けの illustration と状態パターンが無く、cake-api の EC は 404 用 SVG をローカル実装していた。新コンポーネントは作らず、既存 `ErrorState` に `kind?: "error" | "notFound"`(既定 `"error"` = 現行不変)を追加。`kind="notFound"` はケーキモチーフの抽象イラスト + 既定見出し(「ページが見つかりません」)+ 既定説明 + `action` slot(トップへ戻る等)の定番構成で、`action` だけ渡せば成立する。イラストは `NotFoundIllustration` として単体 export し、consumer が独自レイアウトでも流用可能(色は `var(--*)` トークンと `currentColor` のみで HEX 直書きなし)。既存 API は全て additive で**後方互換**([#110](https://github.com/flashpark/cake-design-system/issues/110))
33
+ - **半透過サーフェストークン(`Surface-Overlay-Light` / `Surface-Overlay-Dark`)と `Button` の `variant="overlay"` を追加 — 画像・メディア上の操作ボタン**: 画像の上に載せる操作ボタン(カルーセル矢印・保存/お気に入り等)を組む際、consumer は `rgba(255,255,255,0.8)` 等の半透過白下地を手組みしていた。既存の半透過白(`Surface-Glass`)とは用途を分けて `Surface-Overlay-Light`(rgba(255,255,255,0.8))/ `Surface-Overlay-Dark`(rgba(0,0,0,0.4))を `tokens.json` と preset CSS 両方(`styles/semantic.css` / `preset-standalone.css`)に追加(メディア上での可読性のため light/dark で切替えない)。`Button` に `variant="overlay"`(半透過白下地 + `shadow-md` + `backdrop-blur-sm`、`ImageCarousel` 矢印と同系の意匠。`size="icon"` + `rounded-full` で円形化)を追加。既存 variant は不変で**後方互換**([#126](https://github.com/flashpark/cake-design-system/issues/126))
34
+ - **`RadioGroup` に `variant="chip"`(choice chip)を追加 — ラベル内包のチップ型排他選択**: タグ選択・絞り込みのように横並びでコンパクトに1つ選ばせたい場面で、consumer は Chip を手組みして排他選択のキーボード操作・a11y を自前実装していた。新コンポーネント(ChipGroup)は作らず、既存 `RadioGroup` に `variant?: "tile" | "chip"`(既定 `"tile"` = 現行不変)を追加。`chip` は `RadioGroupItem` の children をラベルとして、Chip 本体と同じ `chipVariants`(CVA)を再利用して描画する(unselected=filled / selected=accent。静止=薄オレンジ + オレンジ文字、hover=ベタ塗りオレンジ。Chip 意匠変更に自動追従)。ラジオ丸・description・expandedContent は持たない。Radix RadioGroup のキーボード操作・focus-visible・`aria-invalid` はそのまま維持し、`FormField` / `FieldError` と接続できる。既定は現行のままで**後方互換**([#123](https://github.com/flashpark/cake-design-system/issues/123))
35
+ - **`FileInput`(foundations)を新設 — 汎用ファイル添付の1行フォーム部品**: DS には画像プレビュー管理の `ImageUploader` はあったが、PDF・CSV 等も含む汎用の添付入力が無く、consumer は `<input type="file">` を手組みしていた(ボタン意匠・ファイル名表示・a11y がバラバラ)。「ファイルを選択」ボタン(`Button` secondary 意匠を再利用)+ 選択済みファイル名 + クリアボタンの1行部品を追加。`accept` / `multiple`(複数時は「n件選択」で集約)/ `maxSize`(超過は受理せず `onError`)/ `disabled` / `placeholder` / controlled `value` + `onFilesChange` に対応。ネイティブ `<input type="file">` を visually-hidden で内包しキーボード・SR 操作可能、`id` を Label と揃え `aria-describedby` で `FieldError` と接続できる。`ImageUploader`(プレビュー付き画像)との使い分けを rules.json に明記。バレル export・contracts・単体テスト付き([#124](https://github.com/flashpark/cake-design-system/issues/124))
36
+ - **`npx cakejp-ds check-duplicates [DIR]` を新設 — consumer の二重実装を機械検出**: consumer(cake-api)が「DS 0.10.0 に既にある `ImageUploader` / `ResponsiveDialog` / `DataTable` 等」を、手元の昇格候補台帳が古いせいで「無い」と誤認しローカル保持し続けていた。原因は ①consumer が DS 正典と別に手元台帳を二重管理して drift ②DS への追加が push で届かない ③機械検出が無い、の3点。DS 同梱の `contracts/components.json`(3 tier 全コンポーネント名)を唯一の正典として、対象 DIR(既定 `./src`)の `.tsx/.ts` から `export function`/`export const` の PascalCase 宣言を抽出し、**名前の完全一致**で DS 既存部品との重複を `ファイル:行` + DS 側 tier/パス付きで報告する。既定は助言モード(常に exit 0)、`--strict` で検出時 exit 1(consumer CI 組込み用)。依存追加なし(node 標準 API のみ)。あわせて「DS に無いと思ったらローカル台帳で管理せず issue を立てる」ゲートを consumer 向けドキュメント(`templates/CLAUDE.md` / `templates/AGENTS.md` / ルート `CLAUDE.md`)に、更新時の削除候補チェック手順を `UPDATING.md` に追記。**templates を更新したため、既存 consumer は `npx cakejp-ds init --force` で `AGENTS.md` / `CLAUDE.md` を再設置すること**
37
+
38
+ ### 🐛 Fixed
39
+
40
+ - **`Footer` のロゴコンテナが固定幅 `w-[100px]` で横長ロゴが崩れる不具合を修正**: フッター下部のロゴを囲む `<div className="mx-auto w-[100px]">` が固定幅で、既定ロゴより横長のロゴを渡すとはみ出し・切れが起きた。`mx-auto w-fit max-w-[160px]` に変更して内容幅に追従させつつ上限を設けた。Cake.jp 既定ロゴ(`CakeLogo`)の見た目は変わらない([#125](https://github.com/flashpark/cake-design-system/issues/125))
41
+ - **`SectionHeader` の subtitle + action 併用時に、action がタイトル+subtitle の2行分の縦中央へ落ちる不具合を修正**: docstring は「タイトル行: タイトル(左)+ もっと見る(右)を `items-baseline` で配置」と記していたが、実装はタイトル行が `items-center` になっており、subtitle を併用すると右側の action(もっと見る / ボタン / カルーセルナビ)がタイトル行のベースラインではなく2行の縦中央へずれていた。実装を docstring 通り `items-baseline` に揃え、subtitle の有無で action の縦位置が動かないようにした。合わせて subtitle + action 併用ストーリー(`WithSubtitleAndAction`)を追加。公開 API は不変([#94](https://github.com/flashpark/cake-design-system/issues/94))
42
+ - **`MenuDrawer` 先頭のヘッダー行が、中身が無くても空の余白帯として描画される不具合を修正**: ヘッダー行(`px-4 py-3`)はユーザー名(`isLoggedIn` 時のみ)と閉じるボタン(`onClose` 時のみ)を収めるが、そのどちらも無い場合(未ログイン かつ `onClose` 未指定=ガイドサイドバー等)でも無条件で描画され、ドロワー先頭に空白帯が残っていた。両方が描画されないときはヘッダー行ごと省略する条件レンダリングに変更。ログイン時・`onClose` あり等の既存ストーリーの見た目は不変([#95](https://github.com/flashpark/cake-design-system/issues/95))
43
+ - **`preset-standalone` で semantic color token が CSS 変数として展開されない不具合を修正**: 正本 `src/styles/semantic.css` は `@theme inline` 構文(Tailwind CSS v4 のビルド時変数解決に依存)のため、Tailwind を通さない consumer では `@import` するだけではブロックごと無視され、CSS 変数として一切展開されないことを実機 Chromium(`getComputedStyle`)で確認した(Storybook manager UI 等で consumer が同値の fallback を重複定義してしのいでいた)。`preset-standalone.css` に semantic トークン一式(`Surface-*` / `Text-*` / `Object-*` / `Hover-*` / `Active-*` / `Border-*` / `Brand-*` / `Overlay-*` / `Caution-*` / `Focus-High-Emphasis` / `Third-Party-*`、light/dark 各76トークン)を tokens.json のhexキャッシュ(未収録グループは primitive 実値)まで解決した素の `:root` / `.dark` として完全展開。ガードテスト(`__tests__/preset-standalone-coverage.test.ts`)を新設し、tokens.json との hex 整合と semantic.css 全トークンの網羅をドリフト検出する([#113](https://github.com/flashpark/cake-design-system/issues/113))
44
+
45
+ ### ♻️ Changed
46
+
47
+ - **`Header` variant="logo" の PC グローバルナビ項目の文字サイズを `typo-body-sm`(12px)→ `typo-body-md`(14px)に引き上げ**(**視覚変更あり**): PC ヘッダーにインライン表示するグローバルナビ(`navItems`)が 12px 固定で、サイト全体の一次ナビゲーションとしては小さく可読性・タップ性に欠けていた。14px に引き上げた。新しい prop は追加せず、`navItems` を渡している全 consumer で PC ナビの文字が一段大きくなる(SP 表示は非対象)([#96](https://github.com/flashpark/cake-design-system/issues/96))
48
+
49
+ ### 📝 Docs
50
+
51
+ - **README に「テスト環境(Jest)での取り込み」節を追加**: DS は ESM-only 配布のため consumer の Jest(CJS)がそのまま解釈できず、component 追加のたびに手動 mock を保守する運用コストが発生していた(cake-api で `ErrorState` / `Card` 系等で繰り返し発生)。CJS dual 配布は行わない方針とし、代わりに `transformIgnorePatterns` + CSS の `moduleNameMapper` スタブ + babel-jest(TS/JSX transform)の構成を、実際に `npm pack` した tgz を Jest から import してコンポーネントを render するテストで動作確認のうえ記載。Vitest(ESM ネイティブ)へ移行できれば同設定は不要になる旨も明記し、UPDATING.md のトラブル時セクションからリンク([#111](https://github.com/flashpark/cake-design-system/issues/111))
11
52
 
12
53
  ## [0.11.0] - 2026-07-22
13
54
 
package/CLAUDE.md CHANGED
@@ -41,6 +41,7 @@ PdM や社長が AI(Claude 等)で HTML モックを作る際、このパッ
41
41
  | カスタムフォントサイズ・ウェイト | `tokens.json` の `typography.*` を使う |
42
42
  | 新しい `<div>` ラッパーでレイアウト構造を作る | Card, Section, Stack 等のコンポーネントに置き換えられないか確認 |
43
43
  | 生の `<button>` `<input>` `<select>` `<textarea>` `<table>` | DS の Button / Input / Select / Textarea / DataTable を使う |
44
+ | 生の `<input type="file">`(ファイル添付・アップロード) | 汎用の添付(PDF/CSV/画像混在)は `FileInput`(foundations)。プレビュー・並び替え付きの画像管理は `ImageUploader`(primitives) |
44
45
 
45
46
  **確認手順:**
46
47
  1. `contracts/components.json` で該当コンポーネントを検索
@@ -48,6 +49,17 @@ PdM や社長が AI(Claude 等)で HTML モックを作る際、このパッ
48
49
 
49
50
  ---
50
51
 
52
+ ## 「DS に無い」と思ったときのゲート(ローカル台帳禁止・issue 直行)
53
+
54
+ 部品が DS に無いと思っても、**手元の台帳・TODO リストで「昇格候補」を管理してはいけない**。台帳は必ず drift する(実例: cake-api の昇格候補台帳が DS 0.10.0 で追加された ImageUploader / ResponsiveDialog / DataTable 等を反映せず、既に DS にある部品を「無い」と誤認してローカル実装を保持し続けた)。次の順で確認する:
55
+
56
+ 1. `contracts/components.json` を名前で検索する(正典はここ 1 箇所)。
57
+ 2. `npx cakejp-ds check-duplicates` を実行し、ローカルに DS 同名の実装が無いか機械検出する(`--strict` で検出時 exit 1、consumer CI に組み込める)。
58
+ 3. それでも無ければ、手元に台帳を作らず **flashpark/cake-design-system に issue を立てる**。
59
+ 4. やむを得ずローカル実装する場合は、「DS に X が追加されたら削除」の条件コメントを必ず添える。
60
+
61
+ ---
62
+
51
63
  ## 技術スタック
52
64
 
53
65
  - React 19 + TypeScript
@@ -81,6 +93,11 @@ PdM や社長が AI(Claude 等)で HTML モックを作る際、このパッ
81
93
  - **価格表記**: `¥{数字,区切り}(税込)` — `円` `税込み` `(税込)` は禁止
82
94
  - **日付表記**: `M月D日(曜日)` — `YYYY/MM/DD` や英語表記禁止
83
95
  - **aria-label**: 日本語で記述 — 英語 aria-label 禁止
84
- - **input type="date"**: 使用禁止 — Popover と Calendar を組み合わせる
96
+ - **input type="date"**: 使用禁止 — 実装済みの正解形 `DatePicker` / `DateTimePicker`(primitives)を使う(内部で Popover と Calendar を DS 意匠で組み済み)。特殊要件のときのみ Popover と Calendar を手組みする
97
+ - **input type="file"**: 使用禁止 — 汎用の添付(PDF/CSV/画像混在)は `FileInput`(foundations)、プレビュー付きの画像管理(並び替え・複数サムネイル)は `ImageUploader`(primitives)を使う
85
98
  - **Tag に onClick**: 禁止 — 押せるものは Chip を使う
99
+ - **選択チップ(単一選択)**: `Chip` に onClick で排他選択を自作しない — 単一選択は `RadioGroup variant="chip"` を使う(Radix のキーボード操作・a11y を維持したまま Chip 意匠で選ばせる)
100
+ - **グレー Section 上の Card**: 背景に沈む場合は `Card surface="primary"`(`Surface-Primary` 下地)で境界を明示する
101
+ - **ヒーロー見出し**: ランディング/ガイド系の最大見出しは `typo-heading-4xl`(32px)を使う
102
+ - **画像・メディア上の操作ボタン**: `rgba(...)` の半透過下地を手組みしない — `--Surface-Overlay-Light`/`--Surface-Overlay-Dark` + `Button variant="overlay"` を使う
86
103
  - **SectionHeader が担う「パターン」の手書き**: 禁止 — 次の2パターンは `SectionHeader` を使う。①「タイトル + もっと見る/すべて見るアクション」の並び、②中央寄せの大見出し(ランディング/ガイド系。`align="center"`、eyebrow に Badge 等)。**通常の文書見出し(`<h1>`〜`<h6>` 相当の素の semantic heading)まで SectionHeader に置き換える意味ではない** — 見出し単体なら `typo-heading-*` を付けた素の heading 要素でよい。正典は `contracts/components.json` の SectionHeader `rules`
package/README.md CHANGED
@@ -49,6 +49,54 @@ import "@cakejp/design-system/dist/index.css"
49
49
  > なる等の不具合が出る(import 順の入れ替えでは解決しない)。Tailwind 併用時は上の
50
50
  > `@source` 方式を使う。詳細は [#83](https://github.com/flashpark/cake-design-system/issues/83)。
51
51
 
52
+ ### テスト環境(Jest)での取り込み
53
+
54
+ DS は **ESM-only 配布**(`package.json` の `exports` に `require` 条件を持たない)。
55
+ CJS ベースの Jest でそのまま `import`/`require` すると構文エラーになるため、
56
+ transform 対象に含める設定が必要。以下は実際に
57
+ `npm pack` → 生成物 tgz を Jest(CJS プロジェクト)から import → コンポーネントを
58
+ render するテストで動作確認済みの構成(`transformIgnorePatterns` を外すと落ちることも確認済み):
59
+
60
+ ```js
61
+ // jest.config.cjs(例)
62
+ module.exports = {
63
+ testEnvironment: "jsdom",
64
+ transform: {
65
+ "^.+\\.(t|j)sx?$": "babel-jest",
66
+ },
67
+ // DS は ESM-only。node_modules 除外の例外にして transform 対象に含める
68
+ transformIgnorePatterns: [
69
+ "node_modules/(?!(@cakejp/design-system)/)",
70
+ ],
71
+ // dist/index.css 等を setup ファイルで import する場合、生CSSをJestが解釈できずエラーになるためスタブ化
72
+ moduleNameMapper: {
73
+ "\\.(css|less|scss|sass)$": "identity-obj-proxy",
74
+ },
75
+ }
76
+ ```
77
+
78
+ ```js
79
+ // babel.config.cjs(例。TS/JSX を Jest で解釈するための最小構成)
80
+ module.exports = {
81
+ presets: [
82
+ ["@babel/preset-env", { targets: { node: "current" } }],
83
+ ["@babel/preset-react", { runtime: "automatic" }],
84
+ "@babel/preset-typescript",
85
+ ],
86
+ }
87
+ ```
88
+
89
+ 必要な devDependencies: `jest` `jest-environment-jsdom` `babel-jest` `@babel/core`
90
+ `@babel/preset-env` `@babel/preset-react` `@babel/preset-typescript` `identity-obj-proxy`
91
+ (CSS を import する場合のみ)。SWC ベース(`@swc/jest`)でも同じ役割(TS/JSX を
92
+ CommonJS へ変換)を果たせば代替可。
93
+
94
+ > 上記構成なら consumer 独自の手動 mock(`ErrorState` / `Card` 等の素通し export を
95
+ > component 追加のたびに保守する運用)は不要になる。詳細背景は
96
+ > [#111](https://github.com/flashpark/cake-design-system/issues/111)。
97
+ >
98
+ > **Vitest(ESM ネイティブ)へ移行できるなら、上記の transform 設定は丸ごと不要になり最も摩擦が少ない。**
99
+
52
100
  ## 構造
53
101
 
54
102
  3-tier アーキテクチャ:
package/UPDATING.md CHANGED
@@ -153,6 +153,9 @@ npm run test # あれば
153
153
  - 開発サーバーで **DS コンポーネントを使っている主要画面を目視確認**
154
154
  - マイナーアップデートで視覚変更が告知されている場合は、対象画面のビフォー/アフターのスクリーンショットを PR に貼る
155
155
  - dev console に `[@cakejp/design-system] ... is deprecated` 警告が出ていないか確認
156
+ - **マイナー更新では CHANGELOG の「✨ Added」を読み、ローカルに持っている同等実装の削除候補を確認する**。
157
+ DS に新しく追加された部品(例: 0.10.0 の `ImageUploader` / `ResponsiveDialog` / `DataTable`)と同名のローカル実装が残っていないか、`npx cakejp-ds check-duplicates ./src` を流すと機械検出できる(`--strict` で検出時 exit 1)。
158
+ 検出されたローカル実装は DS の import に置き換え、不要になったファイルを削除する。
156
159
 
157
160
  ### 5. PR 作成・マージ
158
161
 
@@ -241,6 +244,12 @@ publish 直後は npm キャッシュ反映に **30〜90 秒**かかる。少し
241
244
  DS の peerDependencies(react ^18 || ^19、radix-ui 等)と自リポジトリの依存がズレている。
242
245
  `npm ls <パッケージ名>` で衝突を確認し、CHANGELOG に peer 変更の記載がないか見る。
243
246
 
247
+ ### Jest でテストしたら ESM が解釈できずエラーになる
248
+
249
+ DS は ESM-only 配布のため、CJS ベースの Jest では `transformIgnorePatterns` 等の追加設定が要る。
250
+ [README.md「テスト環境(Jest)での取り込み」](./README.md#テスト環境jestでの取り込み)に
251
+ 動作確認済みの設定を掲載している。
252
+
244
253
  ---
245
254
 
246
255
  ## 関連ドキュメント
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Duplicate-component checker(consumer の二重実装検出)
4
+ *
5
+ * consumer プロジェクトの中に、DS(@cakejp/design-system)に**既にある**
6
+ * コンポーネントと同名のローカル実装が無いかを走査して知らせる。
7
+ *
8
+ * 背景: consumer が DS 正典(contracts/components.json)とは別に「昇格候補台帳」
9
+ * のようなローカルの一覧を手元で二重管理すると、DS へ追加された分(例: 0.10.0 で
10
+ * 追加された ImageUploader / ResponsiveDialog / DataTable 等)が反映されず、
11
+ * 「DS には無い」と誤認してローカル実装を保持し続ける drift が起きる。
12
+ * このツールは DS 同梱の contracts/components.json を唯一の正典として、
13
+ * consumer のローカル export と**名前の完全一致**で機械検出する。
14
+ *
15
+ * 使い方:
16
+ * npx cakejp-ds check-duplicates [DIR] # 既定 DIR=./src、助言モード(常に exit 0)
17
+ * npx cakejp-ds check-duplicates ./src --strict # 検出時 exit 1(consumer の CI 組込み用)
18
+ *
19
+ * 依存追加なし(node 標準 API のみ)。
20
+ */
21
+
22
+ import fs from "node:fs"
23
+ import path from "node:path"
24
+ import { fileURLToPath } from "node:url"
25
+
26
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
27
+ const pkgRoot = path.resolve(__dirname, "..")
28
+
29
+ const args = process.argv.slice(2)
30
+ const strict = args.includes("--strict")
31
+ const targetDir = args.find((a) => !a.startsWith("-")) || "./src"
32
+
33
+ // ── DS 正典(contracts/components.json)から全コンポーネント名を収集 ──────────
34
+ const contractsPath = path.join(pkgRoot, "contracts/components.json")
35
+ if (!fs.existsSync(contractsPath)) {
36
+ console.error(`✗ contracts/components.json が見つかりません: ${contractsPath}`)
37
+ process.exit(strict ? 1 : 0)
38
+ }
39
+
40
+ /** name -> { tier, path, sub? } */
41
+ const dsComponents = new Map()
42
+ {
43
+ const contracts = JSON.parse(fs.readFileSync(contractsPath, "utf-8"))
44
+ for (const tier of ["foundations", "primitives", "ec"]) {
45
+ for (const entry of contracts[tier] || []) {
46
+ if (entry.name && !dsComponents.has(entry.name)) {
47
+ dsComponents.set(entry.name, { tier, path: entry.path })
48
+ }
49
+ for (const sub of entry.subcomponents || []) {
50
+ if (!dsComponents.has(sub)) {
51
+ dsComponents.set(sub, { tier, path: entry.path, sub: entry.name })
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ // ── 対象 DIR の走査 ──────────────────────────────────────────────
59
+ const IGNORED_DIRS = new Set(["node_modules", "dist", ".next", "build"])
60
+
61
+ if (!fs.existsSync(targetDir)) {
62
+ console.error(`✗ ディレクトリが見つかりません: ${path.resolve(targetDir)}`)
63
+ process.exit(strict ? 1 : 0)
64
+ }
65
+
66
+ function findFiles(dir) {
67
+ const results = []
68
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
69
+ const full = path.join(dir, entry.name)
70
+ if (entry.isDirectory()) {
71
+ if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith(".")) continue
72
+ results.push(...findFiles(full))
73
+ } else if (/\.(ts|tsx)$/.test(entry.name)) {
74
+ results.push(full)
75
+ }
76
+ }
77
+ return results
78
+ }
79
+
80
+ // PascalCase の export 宣言を抽出(関数コンポーネント / const コンポーネント)
81
+ const FN_RE = /export\s+function\s+([A-Z][A-Za-z0-9]*)/
82
+ const CONST_RE = /export\s+const\s+([A-Z][A-Za-z0-9]*)\s*(?::[^=]+)?=/
83
+
84
+ console.log(`\n🔍 二重実装チェック`)
85
+ console.log(` 対象: ${path.resolve(targetDir)}`)
86
+ console.log(` DS 正典: contracts/components.json(${dsComponents.size} 名)\n`)
87
+
88
+ const findings = []
89
+
90
+ for (const file of findFiles(targetDir)) {
91
+ const lines = fs.readFileSync(file, "utf-8").split(/\r?\n/)
92
+ for (let i = 0; i < lines.length; i++) {
93
+ const line = lines[i]
94
+ const m = line.match(FN_RE) || line.match(CONST_RE)
95
+ if (!m) continue
96
+ const name = m[1]
97
+ const ds = dsComponents.get(name)
98
+ if (ds) {
99
+ findings.push({ name, file, line: i + 1, ds })
100
+ }
101
+ }
102
+ }
103
+
104
+ if (findings.length === 0) {
105
+ console.log("✓ DS と同名のローカル実装は見つかりませんでした。二重実装なし 🎉\n")
106
+ process.exit(0)
107
+ }
108
+
109
+ console.log(`⚠ DS に既に存在する名前のローカル実装: ${findings.length} 件\n`)
110
+
111
+ for (const f of findings) {
112
+ const rel = path.relative(process.cwd(), f.file)
113
+ const tierLabel = f.ds.sub
114
+ ? `${f.ds.tier}(${f.ds.sub} のサブコンポーネント)`
115
+ : f.ds.tier
116
+ console.log(` ● ${f.name}`)
117
+ console.log(` ローカル: ${rel}:${f.line}`)
118
+ console.log(` DS 側: ${tierLabel} / ${f.ds.path}`)
119
+ console.log(
120
+ ` → DS の import(@cakejp/design-system)に置き換え検討。` +
121
+ `意図的なローカル実装なら理由コメントを残すこと。`
122
+ )
123
+ console.log("")
124
+ }
125
+
126
+ console.log(
127
+ `💡 DS に部品があるか迷ったら、まず contracts/components.json を検索し、` +
128
+ `無ければ手元の台帳ではなく flashpark/cake-design-system に issue を立てること` +
129
+ `(台帳は必ず drift します)。\n`
130
+ )
131
+
132
+ process.exit(strict ? 1 : 0)
package/bin/init.js CHANGED
@@ -30,6 +30,7 @@ if (cmd === "help" || cmd === "--help" || cmd === "-h") {
30
30
  npx cakejp-ds init --force 既存ファイルを上書き
31
31
  npx cakejp-ds codemod v0.2-to-v0.3 [DIR] 旧 API を新 API に自動移行
32
32
  npx cakejp-ds check-migration [DIR] 旧 API 利用箇所を検出
33
+ npx cakejp-ds check-duplicates [DIR] DS に既にある部品の二重実装を検出(--strict で検出時 exit 1)
33
34
  `)
34
35
  process.exit(0)
35
36
  }
@@ -53,6 +54,13 @@ if (cmd === "check-migration") {
53
54
  process.exit(0)
54
55
  }
55
56
 
57
+ if (cmd === "check-duplicates") {
58
+ // process.argv の調整(check-duplicates.mjs 側は process.argv.slice(2) を見る)
59
+ process.argv = [process.argv[0], process.argv[1], ...args.slice(1)]
60
+ await import(join(pkgRoot, "bin/check-duplicates.mjs"))
61
+ process.exit(0)
62
+ }
63
+
56
64
  if (cmd !== "init" && cmd !== "postinstall") {
57
65
  console.error(`未知のコマンド: ${cmd}`)
58
66
  console.error(`npx cakejp-ds help を参照してください`)