@cakejp/design-system 0.11.0 → 0.13.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.
- package/CHANGELOG.md +85 -1
- package/CLAUDE.md +22 -1
- package/README.md +48 -0
- package/UPDATING.md +9 -0
- package/bin/check-duplicates.mjs +256 -0
- package/bin/exports-parser.mjs +54 -0
- package/bin/init.js +11 -0
- package/contracts/components.json +395 -37
- package/contracts/composition.json +10 -7
- package/contracts/rules.json +21 -3
- package/dist/index.css +1 -1
- package/dist/index.js +6134 -3475
- package/dist/types/components/ec/admin-shell.d.ts +39 -2
- package/dist/types/components/ec/mypage-shell.d.ts +10 -1
- package/dist/types/components/ec/product-card.d.ts +3 -1
- package/dist/types/components/ec/product-list-item.d.ts +8 -1
- package/dist/types/components/foundations/alert.d.ts +5 -1
- package/dist/types/components/foundations/button.d.ts +1 -1
- package/dist/types/components/foundations/card.d.ts +36 -2
- package/dist/types/components/foundations/chip-variants.d.ts +17 -0
- package/dist/types/components/foundations/field.d.ts +81 -0
- package/dist/types/components/foundations/file-input.d.ts +75 -0
- package/dist/types/components/foundations/icon-badge.d.ts +2 -2
- package/dist/types/components/foundations/input.d.ts +11 -1
- package/dist/types/components/foundations/radio-group.d.ts +12 -2
- package/dist/types/components/foundations/skip-link.d.ts +39 -0
- package/dist/types/components/foundations/textarea.d.ts +11 -1
- package/dist/types/components/primitives/chat-composer.d.ts +87 -0
- package/dist/types/components/primitives/chip.d.ts +2 -7
- package/dist/types/components/primitives/content-carousel.d.ts +58 -0
- package/dist/types/components/primitives/date-picker.d.ts +44 -0
- package/dist/types/components/primitives/error-state.d.ts +43 -16
- package/dist/types/components/primitives/funnel-shell.d.ts +12 -1
- package/dist/types/components/primitives/header.d.ts +15 -1
- package/dist/types/components/primitives/image-input.d.ts +119 -0
- package/dist/types/components/primitives/section-header.d.ts +9 -2
- package/dist/types/components/primitives/section-nav.d.ts +28 -2
- package/dist/types/components/primitives/section.d.ts +35 -2
- package/dist/types/components/primitives/share-buttons.d.ts +8 -2
- package/dist/types/components/primitives/standard-page-shell.d.ts +11 -1
- package/dist/types/components/primitives/timeline.d.ts +23 -0
- package/dist/types/components/primitives/workspace-shell.d.ts +21 -1
- package/dist/types/index.d.ts +80 -4
- package/dist/types/lib/pagination-range.d.ts +16 -0
- package/dist/types/lib/use-value-length.d.ts +31 -0
- package/package.json +2 -1
- package/src/preset-standalone.css +322 -4
- package/src/preset.css +17 -0
- package/src/styles/semantic.css +7 -0
- package/src/styles/typography.css +15 -1
- package/templates/AGENTS.md +11 -0
- package/templates/CLAUDE.md +11 -0
- package/tokens.json +18 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,91 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
## [
|
|
10
|
+
## [0.13.0] - 2026-07-23
|
|
11
|
+
|
|
12
|
+
consumer(cake-api / cake-distributor / cake-prototypes)からのフィードバック対応。実装の誤用を誘発していた contracts の記述修正と、任意項目・他ブランド採用で足りなかった口の追加。
|
|
13
|
+
|
|
14
|
+
### ✨ Added
|
|
15
|
+
|
|
16
|
+
- **Surface-Secondary 地のシェル(`AdminShell` / `WorkspaceShell` / `FunnelShell`)も配下 `Card` へサーフェス文脈を伝播**(**視覚変更あり**): #150 で `Section` に入れた仕組みをシェルにも広げた。これらのシェルは地が `Surface-Secondary`(#FBF9F8)で、`Card` の既定 `bg-card` も同じ `Surface-Secondary` のため、**カードが地と完全に同色で罫線だけの区切りになっていた**(実測: 管理ダッシュボードの地 `rgb(251,249,248)` に対しカードも `rgb(251,249,248)`、商品編集ワークスペースはカード5枚すべて同色)。各シェルのルート要素に `--card-surface: var(--Surface-Primary)`(dark は `Surface-Secondary` に据え置き)を宣言し、**配下の `Card` は `surface` 未指定のまま白で地から浮く**ようにした。`MypageShell` は本文が白い `main` の中に入る構造なので**あえて宣言していない**(宣言すると白地に白カードで階層差が消える)。`Section` と同じく伝播は CSS カスタムプロパティのみで React context は使わず、`surface="primary"` の明示指定・`className="bg-card"` での上書きも従来どおり。**影響**: light で上記3シェルを使う画面の `surface` 未指定 `Card` が Surface-Secondary → Surface-Primary(白)に変わる(DS 同梱の管理画面ストーリー多数が該当)。dark は変化なし。API 変更なし([#150](https://github.com/flashpark/cake-design-system/issues/150))
|
|
17
|
+
- **`Timeline`(primitives)を新設**: 応募フロー・オンボーディング・注文履歴向けの本文付き縦タイムライン。連番または任意 marker、見出し、ReactNode 本文を受け、コネクター線を DS 側で管理するため consumer の `::before` 手組みを不要にした([#157](https://github.com/flashpark/cake-design-system/issues/157))
|
|
18
|
+
- **`getPaginationRange()` を追加**: 現在ページ・総ページ数・前後表示数から、先頭/末尾を維持したページ番号と `"ellipsis"` の窓を返す。既存 `Pagination*` primitives の描画 API は変更せず、全ページ列挙を避ける判断ロジックだけを共通化した([#156](https://github.com/flashpark/cake-design-system/issues/156))
|
|
19
|
+
- **`ContentCarousel itemsPerView` を追加**: PC で2〜4枚の ReactNode カードを同時表示できるようにし、`ProductCarousel` に依存せず求人・記事等の多列カルーセルを組めるようにした。SP は従来どおり1枚表示([#160](https://github.com/flashpark/cake-design-system/issues/160))
|
|
20
|
+
- **`Section backgroundImage / scrim` を追加**: `dark` / `light` / `bottom-dark` の画像上オーバーレイを `Surface-Overlay-*` トークンで DS 管理し、consumer ごとの gradient 手組みを不要にした([#159](https://github.com/flashpark/cake-design-system/issues/159))
|
|
21
|
+
- **`.tap-target` CSS utility を preset / preset-standalone に追加**: 見た目を変えず透明な操作領域を44×44px以上へ広げ、小型ボタン・Chip 等の SP 操作性を consumer 側の個別疑似要素なしで確保できるようにした([#158](https://github.com/flashpark/cake-design-system/issues/158))
|
|
22
|
+
- **React Hook Form の `register()` / `reset()` 統合 story を追加**: consumer の主要な非制御 ref 経路を Storybook 上で再現し、Input / Textarea の初期値・外部リセット・文字数表示を目視できるようにした([#152](https://github.com/flashpark/cake-design-system/issues/152))
|
|
23
|
+
- **`Section` から配下 `Card` へサーフェス文脈を伝播 — 色地の上のカードが `surface` 未指定でも白になる**(**視覚変更あり**): 0.12.0 で入れた `Card surface="primary"`([#127](https://github.com/flashpark/cake-design-system/issues/127))は「グレー地でカードが沈む」を prop で解決できたが、保証が**カード1枚ごとの明示指定**になったため、consumer(cake-jobboard)は移行で3ファイル7箇所を個別に触る必要があり、さらに**この先カードを足したときの付け忘れが型でもレビューでも検知できない**(症状が地味なコントラスト不足なので見逃される)という指摘があった。`Section background="subtle"|"accent-subtle"` が配下に `--card-surface: var(--Surface-Primary)` を流し、`Card` の既定サーフェスを `bg-[var(--card-surface,var(--card))]` に変更することで、**色地 Section の中の Card は `surface` 未指定のまま自動で白**になる(=移行前の `.section-gray [data-slot="card"] { background: ... }` と同じ「構造による保証」を DS 側で持つ)。伝播は **CSS カスタムプロパティのカスケードのみで React context は使わない** — Card / Section は Server Component のまま使え、DS の `Card` 以外(consumer 自前の `article` 等)には一切影響しない。DS の `Section` を使わない自前の色地でも、親要素に `className="[--card-surface:var(--Surface-Primary)]"` を宣言すれば同じ文脈を作れる。`Card surface="primary"` の明示指定は従来どおり有効で文脈より優先。例外的に1枚だけ地に馴染ませたいときは `className="bg-card"` で上書きする(tailwind-merge で後勝ち)。`background="none"` は文脈をリセットしない(色地 Section に入れ子にした `none` は背景が透過して外側の色地が見えるため、配下 Card も白のままが正しい)。**dark では伝播値を `Surface-Secondary` に据え置く**(dark の Surface-Secondary は半透明白なので色地に重なって一段明るく浮き、そもそも沈まない。ここで Surface-Primary(ほぼ不透明の黒)を強制すると地より暗い「穴」になり elevation が反転する。※ `surface="primary"` の明示指定は light/dark とも Surface-Primary で固定=従来どおり)。**影響**: light では `Section background="subtle"|"accent-subtle"` の中で `surface` 未指定の `Card` を使っている画面のカード背景が Surface-Secondary(#FBF9F8)から Surface-Primary(#FFFFFF)に変わる(= contracts が元々求めていた見た目に揃う)。dark は変化なし。`Card` は `data-surface` 属性も出すようになった([#150](https://github.com/flashpark/cake-design-system/issues/150))
|
|
24
|
+
- **`typo-heading-5xl`(40px)とLPヒーロー向けレスポンシブ見出しを追加**: LP・マーケティング系ヒーローのPC見出し用に `typo-heading-5xl` を追加し、`SectionHeader` でも `size="3xl"` + `desktopSize="4xl"` / `"5xl"` としてSP 28pxからPC 32px/40pxへ型安全に拡大できるようにした。用途境界は「3xl=通常ページの主見出しにも使用、4xl/5xl=LP・マーケティング系ヒーローのPC限定」に統一。MCPのタイポグラフィ一覧は `tokens.json` から生成し、今後のスケール追加が外部AIツールから欠落しないようにした([#144](https://github.com/flashpark/cake-design-system/issues/144))
|
|
25
|
+
- **`ImageInput`(primitives)を新設 — 画像1枚の入力(プレビュー + 形式/サイズ検証 + 値のバインド)**: 既存の `ImageUploader` は名前に反して**複数枚の表示グリッド**で、`images` を並べるだけ・ファイル選択は `onAdd` で consumer に丸投げという実体だった。そのため cake-api(管理画面のカテゴリLP編集・5箇所)は**同名で責務の違う「1枚の画像入力」を 387 行ローカル実装**して保持し続けており、`npx cakejp-ds check-duplicates` が恒常的に名前衝突を検出していた。「N枚の管理グリッド」と「1枚の入力+検証」は props がほぼ交わらないため `ImageUploader` に mode を足す拡張は採らず、**責務ごとに別部品**として `ImageInput` を新設した(`FileInput`=汎用添付 / `ImageInput`=画像1枚の入力 / `ImageUploader`=複数枚のグリッド管理、の三者の住み分けを contracts・rules・各 docstring に明記)。値は `value: File | string | null`(`File`=新規選択 / `string`=保存済み画像の URL)+ `onChange(file)` の制御コンポーネントで、`id` / `name` / `onBlur` / `error` を持ち `FormField` や React Hook Form に直結できる。ファイル選択の口は既存 `FileInput` を内包して再利用し(生の `<input type="file">` は書かない)、`accept`(既定 `image/png,image/jpeg`)の MIME 検査と `maxSizeMb`(既定 2)の上限検査を行い、**エラーは日本語でインライン表示**(`FieldError` = `role="alert"`、内包 input に `aria-invalid` / `aria-describedby` を接続)。モーダルは入力の流れを止めフィールドと紐付かないため既定にせず、Dialog やトーストで出したい consumer 向けに `onValidationError` を用意した。プレビューは `File` の `URL.createObjectURL` を **値の差し替え時・アンマウント時に必ず `revokeObjectURL`**(メモリリーク防止)、`string` はそのまま描画するため SSR 初回から表示される。表示サイズは `aspect`(`square` / `landscape`=1200:630)× `size`(`sm`=156px / `md`=380px / `lg`=568px)と `objectFit`(`cover` / `contain`)の CVA バリアント。クリアボタンは `clearable={false}` で抑制でき、`disabled` にも対応。非同期検証(画像寸法チェック等)は DS には持たせず、`onChange` 後に consumer 側で検証して `error` で戻す方針を明記した。**新規追加のみで `ImageUploader` / `FileInput` の API・挙動は不変**([#138](https://github.com/flashpark/cake-design-system/issues/138))
|
|
26
|
+
- **`ChatComposer`(primitives)を新設 — メッセージ入力 + 添付 + 送信の一体型コンポーザ**: DS にはチャット/メッセージング系の入力 primitive が無く、consumer(cake-jobboard の応募者⇔店舗チャット)は `Textarea` + `Button` + 生の `<input type="file">` で**コンポーザを丸ごと自前実装**していた(実装コメントに「汎用 UI として DS への昇格候補」と明記済み)。送信キー(Enter か ⌘Enter か)・添付の見せ方・送信中の扱いが consumer ごとにドリフトし、とくに**IME 変換確定の Enter で誤送信**する事故は各実装で個別に手当てするしかなかった。`value` / `onChange` / `onSubmit(value, files)` を持つ制御コンポーネントとして追加し、中身は既存部品の組み合わせ(`Textarea` / `FileInput` / `Chip` / `Button`)で新規 DOM を最小に抑えている。送信キーは Enter 送信・Shift+Enter 改行が既定で、長文が主な用途は `submitKey="mod-enter"`(⌘/Ctrl+Enter 送信・Enter は改行)に切替可。**どちらのモードでも IME 変換中の Enter では送信しない**(`isComposing` と `compositionstart`/`compositionend` で二重ガード)。添付は内蔵の `FileInput`(`accept` / `maxFiles` / `maxSizeMb`、超過は `onAttachmentError`)で選択し、選択済みは押せる・消せる `Chip` の removable で表示(表示専用の `Tag` は使わない)。`disabled` / `isSubmitting`(送信ボタン無効化で**二重送信防止**)/ `placeholder` / `maxLength`(`Textarea` の `showCount` と連動して文字数カウンタ表示)に対応。`<form>` を内包しないため consumer のフォーム内に置いても入れ子にならない。**新規追加のみで既存 API の変更なし**([#122](https://github.com/flashpark/cake-design-system/issues/122))
|
|
27
|
+
- **`DatePicker` / `DateTimePicker` に `clearable` を追加 — 選択済みの値を明示的に消せるように**: これまでクリア手段は「カレンダーを開いて選択済みの日付を再クリックする」(react-day-picker の標準トグル)だけで、trigger 上に導線が無く**画面から発見できなかった**。cake-api の「広告掲載開始日時/終了日時」は任意項目(`nullable`)で「一度設定した期間を空に戻す」操作が実運用で必要だが辿れず、0.12.0 移行時にローカル実装が持っていた × ボタンが失われる退行が発生していた。`clearable?: boolean`(既定 `false`=後方互換)を追加し、`value` があり `disabled` でないとき trigger 右端に × を描画、押下で `onChange(undefined)` を呼ぶ。`aria-label` は `clearLabel`(`DatePicker` 既定「日付をクリア」/ `DateTimePicker` 既定「日時をクリア」)。**クリアボタンは trigger の兄弟として絶対配置**しており(`Chip` removable と同じ解決)、button の入れ子にならない。※ これに伴い `className` の付与先が trigger から外側ラッパーへ変わる(幅・レイアウト指定はそのまま効く)([#136](https://github.com/flashpark/cake-design-system/issues/136))
|
|
28
|
+
- **`AdminShell` に `brand` / `sidebarHeader` を追加 — 他ブランドの管理画面で骨格を再利用できるように**: ブランド表示が `shopName`(文字列)から `{shopName}管理` を合成する形に固定されており、バッジ+見出し+説明のようなブランドブロックを渡す口が無かった(cake-distributor がローカル AdminShell を保持していた理由の一つ)。`brand?: ReactNode`(ヘッダー左側。既定=`{shopName}管理`)と `sidebarHeader?: ReactNode`(SP ドロワー上部見出し。既定=`shopName`)を追加。内部の `AdminHeader.brand` / `AdminSideNav.drawerTitle` は元々 ReactNode を受けられたため、シェルが塞いでいた口を開けた形。既定は現行のままで**後方互換**。※ **PC の左サイドナビは 72px のアイコンレール**でブランドブロックを置く領域が無く、幅広サイドバー前提の画面はこの骨格では置き換えられない旨を docstring / contracts に明記した([#135](https://github.com/flashpark/cake-design-system/issues/135))
|
|
29
|
+
|
|
30
|
+
### 🐛 Fixed
|
|
31
|
+
|
|
32
|
+
- **`StatusCountTabs` がマウント後の `activeIndex` 変更へ追従しない不具合を修正**: URL クエリ復元・外部フィルタ・非同期データ確定など、クリック以外の経路で親が `activeIndex` を変更しても内部 state が初期値のまま残っていた。`activeIndex` の変更を内部選択へ同期し、従来の非制御クリック操作も維持した。外部変更 story と回帰テストを追加([#165](https://github.com/flashpark/cake-design-system/issues/165))
|
|
33
|
+
- **`Input` / `Textarea` の `showCount` が非制御(react-hook-form の `register()`)で初期値を数えず `0/n` のままだった不具合を修正**: カウントの実装が非制御時に内部 state(`useState` + `onChange` でのみ更新)だけを見ていたため、`const count = isControlled ? String(value ?? "").length : uncontrolledValue.length` の非制御側が常に 0 起点だった。react-hook-form の `register()` は `name` / `onChange` / `onBlur` / `ref` のみを渡す非制御パターンで、初期値は `defaultValues` / `reset()` により **`ref` 経由で DOM の `value` に直接書き込まれる**(`onChange` は発火しない)。そのため入力欄に文字が見えているのにカウンタは `0/40` のまま、ユーザーが1文字打つと正しくなる、という挙動になっていた(cake-api のカテゴリLP編集画面で発見)。非制御時は内部 state ではなく**実際の DOM の `value` を毎 render 後の layout effect で読み直す**方式に変更し、`ref` 経由の初期値・`reset()` / `setValue()` による後からの外部書き換えにも追従するようにした。consumer が渡す `ref` は内部 ref と合成して従来どおり届く。制御(`value` 指定)・`defaultValue` のみの非制御・`showCount` 未指定時の挙動と DOM は不変で**後方互換**
|
|
34
|
+
- **`AdminSideNav` の5文字超ラベルが不自然に1文字だけ折り返される問題を修正**: PC レールのラベルを `typo-label-xs`・中央揃え・`max-w-12` に統一し、consumer の `data-slot` 内部上書きを不要にした([#155](https://github.com/flashpark/cake-design-system/issues/155))
|
|
35
|
+
- **フォーム入力コントロールと `Button` の高さを 44px に統一し、`SelectTrigger` の高さを `className` で上書きできるようにした**(**視覚変更あり**): 0.12.0 では `Input`(`h-12`=48px)と `SelectTrigger`(`data-[size=default]:h-10`=40px)の高さが揃わず、検索 + 絞り込みセレクトのような**並置が定番の組み合わせで 8px ずれて**いた。さらに `SelectTrigger` の高さが **data 属性セレクタ**(`data-[size=default]:h-10`)で指定されていたため詳細度で勝てず、`className="h-12"` を渡しても効かない(consumer は DS の内部記法を知って `data-[size=default]:h-12` と書く必要があった)。①`SelectTrigger` の高さ指定を**素のクラス**(`size==="sm" ? "h-9" : "h-11"`)に変更して `className` で素直に上書きできるようにし、②入力コントロールの既定を **44px(`h-11`)に統一**した。44px は `tokens.json` / `contracts/rules.json` の touchTarget『Text input』の **min = 44px を満たす最小サイズ**で、管理画面の情報密度とアクセシビリティのバランスが最良(iOS HIG のタッチターゲット 44pt とも一致)。48px は管理画面のような密度の高い画面で間延びするため採らなかった(48px まで上げたい画面は consumer が `className="h-12"` で上書きできる)。③あわせて **`Button` の `size="default"` も 40px → 44px** に変更した。入力コントロールだけを 48px に揃えても `Button` の既定が 40px のままではフォーム内で **48 vs 40 のズレが残る**ため、今回は**入力行に並ぶ部品を Button 込みで本当に全部 44px に揃えた**。`Button` の他サイズ(`xs`=24px / `sm`=32px / `lg`=48px / `xl`=56px / `hero`=min 56px)と `SelectTrigger size="sm"`(36px)は変更なし。**0.12.0 からの差分**: `Input` 48px → **44px**(低くなる) / `SelectTrigger` default 40px → **44px** / `DatePicker`・`DateTimePicker` trigger 40px → **44px** / `Button` default 40px → **44px**。`Input` を使っている画面は行の高さが 4px 詰まり、`Select` / `DatePicker` / `Button`(default)を使っている画面は 4px 高くなる。「入力コントロールと Button default は 44px」を `contracts/components.json` の Input / Select / Button に明記([#142](https://github.com/flashpark/cake-design-system/issues/142)。0.13.0 は未 publish のため、48px 統一(commit `6b21352`)を受け取った consumer は存在しない)
|
|
36
|
+
- **`DateTimePicker` で日付を変えると設定済みの時刻が 00:00 に落ちる不具合を修正**: カレンダーが返す `Date` は「その日の 00:00:00」だが、`DateTimePicker` がそれをそのまま `onChange` へ流しており、時刻(例 10:30)を設定済みの状態で日付だけ変更すると時刻が失われていた。日付部分のみ差し替え、時/分/秒/ミリ秒は直前の値から引き継ぐよう修正(クリア=`undefined` はそのまま透過)。cake-api では adapter 側で引き継ぎを実装して回避していた([#136](https://github.com/flashpark/cake-design-system/issues/136))
|
|
37
|
+
- **`check-duplicates` が「DS に無い名前」を二重実装として誤検出していたのを修正 — 判定を実 export に限定**: 0.12.0 で新設した `npx cakejp-ds check-duplicates` は `contracts/components.json` の名前とだけ照合していたが、contracts には `CheckboxCard` のような**実装ファイル名・グループ名**(それ自体は export されず、実 export は `CheckboxCardGroup` / `CheckboxCardItem`)が含まれるため、DS から import できない名前でも「DS の import に置き換え検討」と出ていた(cake-api の実行結果 5 件のうち `Field` がこれに該当していた。`Field` 自体は #139 で実 export になった)。判定対象を package 同梱の `dist/types/index.d.ts`(再 export だけの静的テキスト。peer 依存の解決もモジュール評価も不要)から抽出した**実 export 名**との積集合に変更。dist が無い環境(DS リポ自体での実行等)では contracts にフォールバックし、その旨を出力に明示する([#140](https://github.com/flashpark/cake-design-system/issues/140))
|
|
38
|
+
- **`CheckboxGroup` / `CheckboxGroupItem` / `PopoverAnchor` がバレルから export されておらず npm 経由で使えなかったのを修正**: いずれも実装・ストーリー・contracts には存在するのに `src/index.ts` に載っておらず、consumer は `import { CheckboxGroup } from "@cakejp/design-system"` でエラーになっていた。export を追加(追加のみで既存 API の変更なし)([#140](https://github.com/flashpark/cake-design-system/issues/140))
|
|
39
|
+
|
|
40
|
+
### 🛠 Tooling
|
|
41
|
+
|
|
42
|
+
- **react-hook-form の実利用パターンを DS 側で検証できるようにした(テスト・story・contracts・受け入れ条件)**: 0.12.0 の `showCount` 不具合([#151](https://github.com/flashpark/cake-design-system/issues/151))は、CHANGELOG に「制御/非制御どちらでも追従する」と書きながら、**RHF の `register()` / `reset()` 経路がテストにも story にも一度も登場していなかった**ため、consumer(cake-api 管理画面の 13 ファイル / 22 コールサイト)の全カウンタが `0/n` になるまで誰も気付けなかった。同じ穴を塞ぐため、①`__tests__/react-hook-form-integration.test.tsx` を新設し、全フォーム部品について **`defaultValues` での初期表示 / `reset()` への追従 / 操作での値更新**を固定(24 ケース)、②`contracts/components.json` に **`formBinding`**(`register` = `{...register()}` を直接 spread できる / `controller` = `Controller` が必要 / `layout` / `none`)を機械可読で追加し、consumer と AI が繋ぎ方で迷わないようにした、③`__tests__/form-binding-contract.test.ts` が「フォーム部品なら `formBinding` 宣言必須」「`register`/`controller` なら RHF テストに登場必須」を強制する、④`form.stories.tsx` に **`defaultValues` に実データが入った状態**の RHF story(register 経路 / Controller 経路)を追加して視覚回帰にも載せた、⑤`CLAUDE_DEV.md` / `AGENTS.md` に「フォーム系コンポーネントの受け入れ条件」を明記(利用パターンのサポートを CHANGELOG に書くならテストを同 PR に含める)。**実装の変更は無し**([#152](https://github.com/flashpark/cake-design-system/issues/152))
|
|
43
|
+
|
|
44
|
+
- **`check-duplicates` に意図的残置の抑制機構を追加(理由必須)**: 対象 export の直前のコメント行(連続コメントブロック内のどこでも可)または行末コメントに `// cakejp-ds-allow-duplicate: <理由>` を書くと、その 1 件だけ検出から外れる。**理由が空なら抑制しない**(形骸化防止)。抑制した件数と理由はサマリに出力し、理由が空のマーカーは警告する。これにより consumer は `--strict` を CI に常設したまま「新規の意図しない二重実装だけを弾く」運用ができる。書式は `npx cakejp-ds check-duplicates --help` と consumer 向けテンプレ(`templates/CLAUDE.md` / `templates/AGENTS.md`)に明記([#140](https://github.com/flashpark/cake-design-system/issues/140))
|
|
45
|
+
|
|
46
|
+
### 📝 Docs
|
|
47
|
+
|
|
48
|
+
- **contracts と実 export の drift を解消し、対応関係を機械可読にした**: `Sonner`→`Toaster` / `PaymentMethod`→`PaymentMethodDisplay` へエントリ名を実 export に合わせ、`CheckboxCard` のように name がグループ名のものは `"exported": false` + 実 export を `subcomponents` に列挙、`FormField`(primitives)のように別名で export されるものは `"exportedAs": "CakeFormField"` を付与。contracts から辿れなかった実体(`CheckboxCardGroup` / `CheckboxCardItem` / `CheckboxGroupItem` / `RadioGroupItem` / `CreditCardIcon` / `SparkleIcon` / `ReviewItem` / `OptionQuantity` / `OptionSelect` / `OptionText` / `OptionImageUpload` / `MissingComponent` / `Form*`)を `subcomponents` に追加し、旧名 export(`AdminLayout` / `WorkspaceLayout` / `EcPageShell`)は `deprecatedAliases` として記録した(新規利用は不可のまま)。規約は `meta.exportNaming` に明記。再発防止として `__tests__/contracts-export-integrity.test.ts` を新設し、contracts 上の「import できるはずの名前」がすべて `src/index.ts` から export されていること(および `exported:false` の name が export されて**いない**こと)をビルド不要で固定した([#140](https://github.com/flashpark/cake-design-system/issues/140))
|
|
49
|
+
- **Button の `secondary` / `tertiary` の使い分けを contracts から読み取れるように修正**: `composition.json` の `ctaHierarchy.priority` が variant を**重要度の序数だけ**で定義していたため(`secondary`="Secondary action")、「主 CTA の隣=2番目」を素直に読むと「戻る」に `secondary` が選ばれていた。実際は `secondary`=ブランドアクセント(薄オレンジ)/ `tertiary`=ニュートラルグレーで、Material・Bootstrap・shadcn の慣習(secondary=グレー)と**意匠が逆**なため、LLM 実装では特に誤用が繰り返される。各項目に **`appearance`(実際の色)** を追加し、「序列ではなく役割で選ぶ」旨を `description` に明記。「戻る/キャンセル/閉じる/スキップ/あとで=`tertiary`」「`secondary` は別方向の“前進”副 CTA」を `composition.json` / `components.json` 双方の `rules` に追加。意匠・API は不変([#133](https://github.com/flashpark/cake-design-system/issues/133))
|
|
50
|
+
- **`RadioGroup` の記述が枠の二重化を誘発していたのを修正**: `accessibility` の「各RadioGroupItemにLabel必須」が「`<Label>` を併置しろ」と読め、consumer が `RadioGroupItem` を独自の枠 div で囲み `<Label htmlFor>` を兄弟に置いた結果、**選択肢が二重の枠で描画される**崩れが起きていた。実際は `children` がラベルとして描画され内部で `<label>` 相当の関連付けまで行う。文言を「ラベルは children として内側に書く。外側に `<Label htmlFor>` を併置しない」に修正し、「タイルの枠は `RadioGroupItem` 自身が描くので consumer 側で囲まない」を `rules` に追加。`.d.ts` を開かないと分からなかった `description` / `expandedContent` prop も `props` として明記。実装は不変([#134](https://github.com/flashpark/cake-design-system/issues/134))
|
|
51
|
+
- `CLAUDE.md` の「AI 生成コードの典型的な誤り」に上記2件(Button の後退アクション=tertiary、RadioGroupItem を div で囲まない・Label 併置しない)を追加(常時ロードされる導線のため)
|
|
52
|
+
|
|
53
|
+
## [0.12.0] - 2026-07-23
|
|
54
|
+
|
|
55
|
+
consumer(cake-distributor 等)の DS 採用時に、内部 DOM を `[&>div]` 等で上書きして凌いでいた3点の粗の解消と、手組みカルーセル排除のための新コンポーネントを収録する回。
|
|
56
|
+
|
|
57
|
+
### ✨ Added
|
|
58
|
+
|
|
59
|
+
- **`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))
|
|
60
|
+
- **`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))
|
|
61
|
+
- **`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))
|
|
62
|
+
- **`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))
|
|
63
|
+
- **`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))
|
|
64
|
+
- **バレルから 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))
|
|
65
|
+
- **`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))
|
|
66
|
+
- **`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))
|
|
67
|
+
- **`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))
|
|
68
|
+
- **`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))
|
|
69
|
+
- **`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))
|
|
70
|
+
- **`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))
|
|
71
|
+
- **`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))
|
|
72
|
+
- **`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))
|
|
73
|
+
- **`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))
|
|
74
|
+
- **`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))
|
|
75
|
+
- **`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))
|
|
76
|
+
- **半透過サーフェストークン(`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))
|
|
77
|
+
- **`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))
|
|
78
|
+
- **`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))
|
|
79
|
+
- **`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` を再設置すること**
|
|
80
|
+
|
|
81
|
+
### 🐛 Fixed
|
|
82
|
+
|
|
83
|
+
- **`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))
|
|
84
|
+
- **`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))
|
|
85
|
+
- **`MenuDrawer` 先頭のヘッダー行が、中身が無くても空の余白帯として描画される不具合を修正**: ヘッダー行(`px-4 py-3`)はユーザー名(`isLoggedIn` 時のみ)と閉じるボタン(`onClose` 時のみ)を収めるが、そのどちらも無い場合(未ログイン かつ `onClose` 未指定=ガイドサイドバー等)でも無条件で描画され、ドロワー先頭に空白帯が残っていた。両方が描画されないときはヘッダー行ごと省略する条件レンダリングに変更。ログイン時・`onClose` あり等の既存ストーリーの見た目は不変([#95](https://github.com/flashpark/cake-design-system/issues/95))
|
|
86
|
+
- **`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))
|
|
87
|
+
|
|
88
|
+
### ♻️ Changed
|
|
89
|
+
|
|
90
|
+
- **`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))
|
|
91
|
+
|
|
92
|
+
### 📝 Docs
|
|
93
|
+
|
|
94
|
+
- **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
95
|
|
|
12
96
|
## [0.11.0] - 2026-07-22
|
|
13
97
|
|
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,19 @@ 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
|
+
**意図的にローカル実装を残すとき(check-duplicates の抑制)**: 対象 export の直前のコメント行(または行末コメント)に `// cakejp-ds-allow-duplicate: <理由>` を書くと、その 1 件だけ検出から外れる。**理由が空なら抑制されない**(形骸化防止)。抑制した件数と理由はサマリに出る。
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
51
65
|
## 技術スタック
|
|
52
66
|
|
|
53
67
|
- React 19 + TypeScript
|
|
@@ -81,6 +95,13 @@ PdM や社長が AI(Claude 等)で HTML モックを作る際、このパッ
|
|
|
81
95
|
- **価格表記**: `¥{数字,区切り}(税込)` — `円` `税込み` `(税込)` は禁止
|
|
82
96
|
- **日付表記**: `M月D日(曜日)` — `YYYY/MM/DD` や英語表記禁止
|
|
83
97
|
- **aria-label**: 日本語で記述 — 英語 aria-label 禁止
|
|
84
|
-
- **input type="date"**: 使用禁止 — Popover と Calendar
|
|
98
|
+
- **input type="date"**: 使用禁止 — 実装済みの正解形 `DatePicker` / `DateTimePicker`(primitives)を使う(内部で Popover と Calendar を DS 意匠で組み済み)。特殊要件のときのみ Popover と Calendar を手組みする
|
|
99
|
+
- **input type="file"**: 使用禁止 — 汎用の添付(PDF/CSV/画像混在)は `FileInput`(foundations)、プレビュー付きの画像管理(並び替え・複数サムネイル)は `ImageUploader`(primitives)を使う
|
|
100
|
+
- **Button の「戻る・キャンセル・閉じる」**: `variant="secondary"` は誤り — **`variant="tertiary"`(グレー)を使う**。Cake DS の `secondary` は Material/Bootstrap/shadcn と違い**ブランドアクセント(薄オレンジ地+オレンジ文字)**。「主 CTA の隣=2番目だから secondary」と序列で選ばず、役割(前進か後退か)で選ぶ。secondary は別方向の“前進”副 CTA 用(例: 主「購入する」/ 副「サンプルを見る」)
|
|
101
|
+
- **RadioGroupItem を div で囲む / Label を併置する**: 禁止 — **ラベルは `children` として内側に書く**(`<RadioGroupItem value="x">ラベル</RadioGroupItem>`)。タイルの枠は `RadioGroupItem` 自身が描くので外側で囲むと**枠が二重**になる。`<Label htmlFor>` を兄弟に置くのもラベル重複で誤り。補足説明は `description`、展開内容は `expandedContent` prop を使う
|
|
85
102
|
- **Tag に onClick**: 禁止 — 押せるものは Chip を使う
|
|
103
|
+
- **選択チップ(単一選択)**: `Chip` に onClick で排他選択を自作しない — 単一選択は `RadioGroup variant="chip"` を使う(Radix のキーボード操作・a11y を維持したまま Chip 意匠で選ばせる)
|
|
104
|
+
- **グレー地の上の Card**: `Section background="subtle"`/`"accent-subtle"` と、地が Surface-Secondary のシェル(`AdminShell` / `WorkspaceShell` / `FunnelShell`)の配下は `--card-surface` が伝播するので、**Card は `surface` 未指定のままで自動的に白**になる(付け忘れ事故が起きない)。DS の `Section` を使わない自前の色地では、親要素に `className="[--card-surface:var(--Surface-Primary)]"` を宣言する(dark にも配慮するなら `dark:[--card-surface:var(--Surface-Secondary)]` を併記)。`Card surface="primary"` の明示指定も従来どおり有効・優先
|
|
105
|
+
- **大見出し**: 通常ページの主見出しは `typo-heading-3xl`(28px)を上限にする。`typo-heading-4xl`(32px)/ `typo-heading-5xl`(40px)は**LP・マーケティング系ヒーローの PC 表示限定**で、SP は `typo-heading-3xl` から段階を上げる。`SectionHeader` では `size="3xl"` に `desktopSize="4xl"` または `desktopSize="5xl"` を加える。手書きの `text-[32px]` / `text-[40px]` 等は禁止
|
|
106
|
+
- **画像・メディア上の操作ボタン**: `rgba(...)` の半透過下地を手組みしない — `--Surface-Overlay-Light`/`--Surface-Overlay-Dark` + `Button variant="overlay"` を使う
|
|
86
107
|
- **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,256 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Duplicate-component checker(consumer の二重実装検出)
|
|
4
|
+
*
|
|
5
|
+
* consumer プロジェクトの中に、DS(@cakejp/design-system)から**実際に import できる**
|
|
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
|
+
* ただし contracts の name には「実装ファイル名・グループ名」(例: CheckboxCard)が
|
|
16
|
+
* 含まれ、それ自体は export されていない。名前だけで照合すると **import できない名前でも
|
|
17
|
+
* 検出される**(issue #140)ため、判定対象は `dist/types/index.d.ts` の実 export と
|
|
18
|
+
* 突き合わせて絞り込む。dist が無い環境(DS リポ自体など)では contracts のみで判定し、
|
|
19
|
+
* その旨を出力に明示する。
|
|
20
|
+
*
|
|
21
|
+
* 使い方:
|
|
22
|
+
* npx cakejp-ds check-duplicates [DIR] # 既定 DIR=./src、助言モード(常に exit 0)
|
|
23
|
+
* npx cakejp-ds check-duplicates ./src --strict # 検出時 exit 1(consumer の CI 組込み用)
|
|
24
|
+
* npx cakejp-ds check-duplicates --help
|
|
25
|
+
*
|
|
26
|
+
* 意図的にローカル実装を残す場合(抑制):
|
|
27
|
+
* 対象 export の**直前のコメント行**(連続コメントブロック内のどこでも可)か
|
|
28
|
+
* **同じ行の行末コメント**に、理由付きで次を書く。
|
|
29
|
+
*
|
|
30
|
+
* // cakejp-ds-allow-duplicate: DS 版は用途が違うため独自実装を残す
|
|
31
|
+
* export function ImageUploader() { ... }
|
|
32
|
+
*
|
|
33
|
+
* 理由が空(`// cakejp-ds-allow-duplicate:` だけ)の場合は抑制しない。
|
|
34
|
+
*
|
|
35
|
+
* 依存追加なし(node 標準 API のみ)。
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import fs from "node:fs"
|
|
39
|
+
import path from "node:path"
|
|
40
|
+
import { fileURLToPath } from "node:url"
|
|
41
|
+
import { parseValueExportNames } from "./exports-parser.mjs"
|
|
42
|
+
|
|
43
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
44
|
+
const pkgRoot = path.resolve(__dirname, "..")
|
|
45
|
+
|
|
46
|
+
const args = process.argv.slice(2)
|
|
47
|
+
const strict = args.includes("--strict")
|
|
48
|
+
const targetDir = args.find((a) => !a.startsWith("-")) || "./src"
|
|
49
|
+
|
|
50
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
51
|
+
console.log(`cakejp-ds check-duplicates — DS に既にある部品の二重実装を検出
|
|
52
|
+
|
|
53
|
+
使い方:
|
|
54
|
+
npx cakejp-ds check-duplicates [DIR] 既定 DIR=./src。助言モード(常に exit 0)
|
|
55
|
+
npx cakejp-ds check-duplicates ./src --strict 検出時 exit 1(CI 組込み用)
|
|
56
|
+
|
|
57
|
+
判定対象:
|
|
58
|
+
contracts/components.json の名前のうち、DS のバレル(dist/types/index.d.ts)から
|
|
59
|
+
実際に import できるものだけ。import できない名前(実装ファイル名・グループ名)は検出しない。
|
|
60
|
+
|
|
61
|
+
意図的なローカル実装の抑制(理由必須):
|
|
62
|
+
対象 export の直前のコメント行、または同じ行の行末コメントに次を書く。
|
|
63
|
+
|
|
64
|
+
// cakejp-ds-allow-duplicate: <残す理由>
|
|
65
|
+
export function ImageUploader() { ... }
|
|
66
|
+
|
|
67
|
+
理由が空の場合は抑制されない(形骸化防止)。抑制した件数はサマリに出る。
|
|
68
|
+
`)
|
|
69
|
+
process.exit(0)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── DS 正典(contracts/components.json)から全コンポーネント名を収集 ──────────
|
|
73
|
+
const contractsPath = path.join(pkgRoot, "contracts/components.json")
|
|
74
|
+
if (!fs.existsSync(contractsPath)) {
|
|
75
|
+
console.error(`✗ contracts/components.json が見つかりません: ${contractsPath}`)
|
|
76
|
+
process.exit(strict ? 1 : 0)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** name -> { tier, path, sub? } */
|
|
80
|
+
const dsComponents = new Map()
|
|
81
|
+
{
|
|
82
|
+
const contracts = JSON.parse(fs.readFileSync(contractsPath, "utf-8"))
|
|
83
|
+
for (const tier of ["foundations", "primitives", "ec"]) {
|
|
84
|
+
for (const entry of contracts[tier] || []) {
|
|
85
|
+
// exported: false = contracts 上のグループ名(実装ファイル名)で、import できない
|
|
86
|
+
const exportedName = entry.exportedAs || entry.name
|
|
87
|
+
if (entry.exported !== false && exportedName && !dsComponents.has(exportedName)) {
|
|
88
|
+
dsComponents.set(exportedName, { tier, path: entry.path })
|
|
89
|
+
}
|
|
90
|
+
for (const sub of entry.subcomponents || []) {
|
|
91
|
+
if (!dsComponents.has(sub)) {
|
|
92
|
+
dsComponents.set(sub, { tier, path: entry.path, sub: entry.name })
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── 実 export(dist/types/index.d.ts)で絞り込む ────────────────────────
|
|
100
|
+
// dist/index.js の動的 import は peer 依存の解決とモジュール評価が必要で consumer 環境で壊れやすい。
|
|
101
|
+
// .d.ts は再 export だけの静的テキストなので、依存も副作用も無く確実に読める。
|
|
102
|
+
const dtsPath = path.join(pkgRoot, "dist/types/index.d.ts")
|
|
103
|
+
let exportSource = null
|
|
104
|
+
if (fs.existsSync(dtsPath)) {
|
|
105
|
+
const realExports = parseValueExportNames(fs.readFileSync(dtsPath, "utf-8"))
|
|
106
|
+
if (realExports.size > 0) {
|
|
107
|
+
for (const name of [...dsComponents.keys()]) {
|
|
108
|
+
if (!realExports.has(name)) dsComponents.delete(name)
|
|
109
|
+
}
|
|
110
|
+
exportSource = "dist/types/index.d.ts"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── 対象 DIR の走査 ──────────────────────────────────────────────
|
|
115
|
+
const IGNORED_DIRS = new Set(["node_modules", "dist", ".next", "build"])
|
|
116
|
+
|
|
117
|
+
if (!fs.existsSync(targetDir)) {
|
|
118
|
+
console.error(`✗ ディレクトリが見つかりません: ${path.resolve(targetDir)}`)
|
|
119
|
+
process.exit(strict ? 1 : 0)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function findFiles(dir) {
|
|
123
|
+
const results = []
|
|
124
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
125
|
+
const full = path.join(dir, entry.name)
|
|
126
|
+
if (entry.isDirectory()) {
|
|
127
|
+
if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith(".")) continue
|
|
128
|
+
results.push(...findFiles(full))
|
|
129
|
+
} else if (/\.(ts|tsx)$/.test(entry.name)) {
|
|
130
|
+
results.push(full)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return results
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// PascalCase の export 宣言を抽出(関数コンポーネント / const コンポーネント)
|
|
137
|
+
const FN_RE = /export\s+function\s+([A-Z][A-Za-z0-9]*)/
|
|
138
|
+
const CONST_RE = /export\s+const\s+([A-Z][A-Za-z0-9]*)\s*(?::[^=]+)?=/
|
|
139
|
+
|
|
140
|
+
// 意図的残置の抑制マーカー(理由必須)
|
|
141
|
+
const ALLOW_RE = /cakejp-ds-allow-duplicate\s*:(.*)$/
|
|
142
|
+
const COMMENT_LINE_RE = /^\s*(\/\/|\/\*|\*)/
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 宣言行の直前コメントブロック(または同じ行の行末コメント)から抑制理由を探す。
|
|
146
|
+
* @returns {{ reason: string } | { reason: "" } | null} マーカー無し=null / 理由空=reason:""
|
|
147
|
+
*/
|
|
148
|
+
function findAllowMarker(lines, declIndex) {
|
|
149
|
+
const scan = (line) => {
|
|
150
|
+
const m = line.match(ALLOW_RE)
|
|
151
|
+
if (!m) return null
|
|
152
|
+
const reason = m[1].replace(/\*\/\s*$/, "").trim()
|
|
153
|
+
return { reason }
|
|
154
|
+
}
|
|
155
|
+
// 同じ行の行末コメント
|
|
156
|
+
const own = scan(lines[declIndex])
|
|
157
|
+
if (own) return own
|
|
158
|
+
// 直前の連続コメントブロック
|
|
159
|
+
for (let j = declIndex - 1; j >= 0; j--) {
|
|
160
|
+
const line = lines[j]
|
|
161
|
+
if (!COMMENT_LINE_RE.test(line)) break
|
|
162
|
+
const hit = scan(line)
|
|
163
|
+
if (hit) return hit
|
|
164
|
+
}
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log(`\n🔍 二重実装チェック`)
|
|
169
|
+
console.log(` 対象: ${path.resolve(targetDir)}`)
|
|
170
|
+
if (exportSource) {
|
|
171
|
+
console.log(
|
|
172
|
+
` DS 正典: contracts/components.json × 実 export(${exportSource})で照合(${dsComponents.size} 名)\n`
|
|
173
|
+
)
|
|
174
|
+
} else {
|
|
175
|
+
console.log(` DS 正典: contracts/components.json(${dsComponents.size} 名)`)
|
|
176
|
+
console.log(
|
|
177
|
+
` ⚠ dist/types/index.d.ts が無いため実 export と照合できません` +
|
|
178
|
+
`(contracts のみで判定 — import できない名前を誤検出する可能性があります)\n`
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const findings = []
|
|
183
|
+
const suppressed = []
|
|
184
|
+
const missingReason = []
|
|
185
|
+
|
|
186
|
+
for (const file of findFiles(targetDir)) {
|
|
187
|
+
const lines = fs.readFileSync(file, "utf-8").split(/\r?\n/)
|
|
188
|
+
for (let i = 0; i < lines.length; i++) {
|
|
189
|
+
const line = lines[i]
|
|
190
|
+
const m = line.match(FN_RE) || line.match(CONST_RE)
|
|
191
|
+
if (!m) continue
|
|
192
|
+
const name = m[1]
|
|
193
|
+
const ds = dsComponents.get(name)
|
|
194
|
+
if (!ds) continue
|
|
195
|
+
const allow = findAllowMarker(lines, i)
|
|
196
|
+
if (allow && allow.reason) {
|
|
197
|
+
suppressed.push({ name, file, line: i + 1, reason: allow.reason })
|
|
198
|
+
continue
|
|
199
|
+
}
|
|
200
|
+
if (allow) missingReason.push({ name, file, line: i + 1 })
|
|
201
|
+
findings.push({ name, file, line: i + 1, ds })
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const rel = (f) => path.relative(process.cwd(), f)
|
|
206
|
+
|
|
207
|
+
function printSuppressionSummary() {
|
|
208
|
+
if (suppressed.length > 0) {
|
|
209
|
+
console.log(`ℹ ${suppressed.length} 件を抑制(理由コメントあり)`)
|
|
210
|
+
for (const s of suppressed) {
|
|
211
|
+
console.log(` ・${s.name}(${rel(s.file)}:${s.line})— ${s.reason}`)
|
|
212
|
+
}
|
|
213
|
+
console.log("")
|
|
214
|
+
}
|
|
215
|
+
if (missingReason.length > 0) {
|
|
216
|
+
console.log(
|
|
217
|
+
`⚠ 理由が空の抑制コメントが ${missingReason.length} 件あります(理由を書かないと抑制されません)`
|
|
218
|
+
)
|
|
219
|
+
for (const s of missingReason) {
|
|
220
|
+
console.log(` ・${s.name}(${rel(s.file)}:${s.line})`)
|
|
221
|
+
}
|
|
222
|
+
console.log("")
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (findings.length === 0) {
|
|
227
|
+
printSuppressionSummary()
|
|
228
|
+
console.log("✓ DS と同名のローカル実装は見つかりませんでした。二重実装なし 🎉\n")
|
|
229
|
+
process.exit(0)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log(`⚠ DS に既に存在する名前のローカル実装: ${findings.length} 件\n`)
|
|
233
|
+
|
|
234
|
+
for (const f of findings) {
|
|
235
|
+
const tierLabel = f.ds.sub
|
|
236
|
+
? `${f.ds.tier}(${f.ds.sub} のサブコンポーネント)`
|
|
237
|
+
: f.ds.tier
|
|
238
|
+
console.log(` ● ${f.name}`)
|
|
239
|
+
console.log(` ローカル: ${rel(f.file)}:${f.line}`)
|
|
240
|
+
console.log(` DS 側: ${tierLabel} / ${f.ds.path}`)
|
|
241
|
+
console.log(
|
|
242
|
+
` → DS の import(@cakejp/design-system)に置き換え検討。` +
|
|
243
|
+
`意図的なローカル実装なら直前行に「// cakejp-ds-allow-duplicate: 理由」を書くこと。`
|
|
244
|
+
)
|
|
245
|
+
console.log("")
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
printSuppressionSummary()
|
|
249
|
+
|
|
250
|
+
console.log(
|
|
251
|
+
`💡 DS に部品があるか迷ったら、まず contracts/components.json を検索し、` +
|
|
252
|
+
`無ければ手元の台帳ではなく flashpark/cake-design-system に issue を立てること` +
|
|
253
|
+
`(台帳は必ず drift します)。\n`
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
process.exit(strict ? 1 : 0)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* バレル(`src/index.ts` / `dist/types/index.d.ts`)から
|
|
3
|
+
* 「値として import できる export 名」を抽出する小さなパーサ。
|
|
4
|
+
*
|
|
5
|
+
* check-duplicates(bin/check-duplicates.mjs)と
|
|
6
|
+
* contracts 整合テスト(__tests__/contracts-export-integrity.test.ts)で共用する。
|
|
7
|
+
*
|
|
8
|
+
* 実 export の取得に**型定義バレルのテキスト解析**を使う理由:
|
|
9
|
+
* - `dist/index.js` の動的 import は react / radix-ui / sonner 等の peer 依存が
|
|
10
|
+
* DS のパス基準で解決できる必要があり、モジュール評価も走るため consumer 環境で壊れやすい
|
|
11
|
+
* - `.d.ts` は再 export だけの静的テキストで、依存も副作用も無い
|
|
12
|
+
*
|
|
13
|
+
* 抽出対象:
|
|
14
|
+
* - `export { A, B as C } from "..."` / `export { A }`(`as` があれば公開名 = 別名側)
|
|
15
|
+
* - `export declare const|function|class X`(バレル直書き宣言)
|
|
16
|
+
* 除外:
|
|
17
|
+
* - `export type { ... }` と specifier 単位の `type X`(値ではない)
|
|
18
|
+
* - `import ...`(`import "./index.css"` 等)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** ソースからコメントを除去(文字列中の // は barrel には出てこない前提の簡易版) */
|
|
22
|
+
function stripComments(source) {
|
|
23
|
+
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/[^\n]*/g, "$1")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} source バレルのソース文字列
|
|
28
|
+
* @returns {Set<string>} 値として import できる export 名
|
|
29
|
+
*/
|
|
30
|
+
export function parseValueExportNames(source) {
|
|
31
|
+
const text = stripComments(source)
|
|
32
|
+
const names = new Set()
|
|
33
|
+
|
|
34
|
+
// export { ... } / export type { ... }(複数行可)
|
|
35
|
+
const braceRe = /export\s+(type\s+)?\{([^}]*)\}/g
|
|
36
|
+
let m
|
|
37
|
+
while ((m = braceRe.exec(text)) !== null) {
|
|
38
|
+
if (m[1]) continue // export type { ... } は値ではない
|
|
39
|
+
for (const raw of m[2].split(",")) {
|
|
40
|
+
const spec = raw.trim()
|
|
41
|
+
if (!spec) continue
|
|
42
|
+
if (/^type\s/.test(spec)) continue // { type Foo } は値ではない
|
|
43
|
+
const parts = spec.split(/\s+as\s+/)
|
|
44
|
+
const exported = (parts[1] || parts[0]).trim()
|
|
45
|
+
if (exported && exported !== "default") names.add(exported)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// export (declare) const|function|class X
|
|
50
|
+
const declRe = /export\s+(?:declare\s+)?(?:const|let|var|function|class)\s+([A-Za-z_$][\w$]*)/g
|
|
51
|
+
while ((m = declRe.exec(text)) !== null) names.add(m[1])
|
|
52
|
+
|
|
53
|
+
return names
|
|
54
|
+
}
|