@cakejp/design-system 0.17.1 → 0.20.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 (101) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/CLAUDE.md +16 -0
  3. package/bin/init.js +191 -28
  4. package/contracts/components.json +176 -17
  5. package/contracts/composition.json +1 -1
  6. package/contracts/rules.json +5 -1
  7. package/contracts/screen-patterns.json +1 -1
  8. package/dist/components/ec/admin-shell.js +7 -6
  9. package/dist/components/ec/order-summary.js +16 -15
  10. package/dist/components/ec/product-card.js +107 -102
  11. package/dist/components/ec/product-option.js +1 -0
  12. package/dist/components/ec/review-card.js +22 -20
  13. package/dist/components/ec/review-form.js +1 -0
  14. package/dist/components/foundations/accordion.js +23 -22
  15. package/dist/components/foundations/alert-dialog.js +7 -7
  16. package/dist/components/foundations/alert.js +1 -0
  17. package/dist/components/foundations/aspect-ratio.js +3 -2
  18. package/dist/components/foundations/avatar.js +1 -0
  19. package/dist/components/foundations/button.js +7 -1
  20. package/dist/components/foundations/calendar.js +1 -0
  21. package/dist/components/foundations/checkbox-group.js +1 -0
  22. package/dist/components/foundations/checkbox.js +1 -0
  23. package/dist/components/foundations/command.js +1 -0
  24. package/dist/components/foundations/context-menu.js +1 -0
  25. package/dist/components/foundations/data-table.js +489 -16
  26. package/dist/components/foundations/dialog.js +13 -13
  27. package/dist/components/foundations/drawer.js +6 -5
  28. package/dist/components/foundations/file-input.js +1 -0
  29. package/dist/components/foundations/form.js +26 -25
  30. package/dist/components/foundations/pagination.js +49 -46
  31. package/dist/components/foundations/popover.js +9 -8
  32. package/dist/components/foundations/radio-group.js +3 -2
  33. package/dist/components/foundations/responsive-dialog.js +1 -0
  34. package/dist/components/foundations/separator.js +7 -6
  35. package/dist/components/foundations/sheet.js +41 -34
  36. package/dist/components/foundations/slider.js +7 -6
  37. package/dist/components/foundations/social-icon.js +30 -10
  38. package/dist/components/foundations/sonner.js +5 -4
  39. package/dist/components/foundations/tabs.js +45 -25
  40. package/dist/components/foundations/toggle-group.js +6 -5
  41. package/dist/components/primitives/admin-header.js +1 -1
  42. package/dist/components/primitives/admin-side-nav.js +196 -86
  43. package/dist/components/primitives/bottom-tab-bar.js +20 -20
  44. package/dist/components/primitives/chat-composer.js +1 -0
  45. package/dist/components/primitives/combobox.js +1 -0
  46. package/dist/components/primitives/content-card.js +111 -0
  47. package/dist/components/primitives/content-carousel.js +9 -8
  48. package/dist/components/primitives/date-picker.js +9 -8
  49. package/dist/components/primitives/footer.js +169 -100
  50. package/dist/components/primitives/form-field.js +1 -0
  51. package/dist/components/primitives/header.js +226 -176
  52. package/dist/components/primitives/image-carousel.js +8 -7
  53. package/dist/components/primitives/image-gallery.js +15 -14
  54. package/dist/components/primitives/image-input.js +9 -8
  55. package/dist/components/primitives/list-item.js +9 -8
  56. package/dist/components/primitives/menu-drawer.js +60 -56
  57. package/dist/components/primitives/multi-select.js +7 -6
  58. package/dist/components/primitives/number-input.js +1 -0
  59. package/dist/components/primitives/prose.js +52 -0
  60. package/dist/components/primitives/search-bar.js +81 -66
  61. package/dist/components/primitives/section-header.js +58 -55
  62. package/dist/components/primitives/share-buttons.js +93 -85
  63. package/dist/components/primitives/status-count-tabs.js +8 -7
  64. package/dist/components/primitives/sticky-action-bar.js +2 -1
  65. package/dist/components/primitives/sub-nav.js +52 -52
  66. package/dist/index.css +1 -1
  67. package/dist/index.js +2027 -752
  68. package/dist/lib/field-labelling.js +7 -6
  69. package/dist/lib/link-component.js +6 -0
  70. package/dist/lib/use-value-length.js +1 -0
  71. package/dist/types/components/ec/product-card.d.ts +12 -19
  72. package/dist/types/components/ec/review-card.d.ts +6 -1
  73. package/dist/types/components/foundations/button.d.ts +1 -1
  74. package/dist/types/components/foundations/chip-variants.d.ts +1 -1
  75. package/dist/types/components/foundations/data-table.d.ts +9 -1
  76. package/dist/types/components/foundations/pagination.d.ts +7 -1
  77. package/dist/types/components/foundations/sheet.d.ts +6 -1
  78. package/dist/types/components/foundations/social-icon.d.ts +1 -1
  79. package/dist/types/components/foundations/tabs.d.ts +2 -1
  80. package/dist/types/components/primitives/admin-side-nav.d.ts +3 -0
  81. package/dist/types/components/primitives/content-card.d.ts +67 -0
  82. package/dist/types/components/primitives/footer.d.ts +21 -1
  83. package/dist/types/components/primitives/header.d.ts +30 -2
  84. package/dist/types/components/primitives/menu-drawer.d.ts +7 -1
  85. package/dist/types/components/primitives/prose.d.ts +35 -0
  86. package/dist/types/components/primitives/search-bar.d.ts +14 -1
  87. package/dist/types/components/primitives/section-header.d.ts +2 -1
  88. package/dist/types/components/primitives/share-buttons.d.ts +26 -4
  89. package/dist/types/components/primitives/sub-nav.d.ts +16 -1
  90. package/dist/types/index.d.ts +7 -2
  91. package/dist/types/lib/link-component.d.ts +35 -0
  92. package/package.json +5 -4
  93. package/src/components/COMPONENT_LOOKUP.md +8 -6
  94. package/src/preset-standalone.css +3 -0
  95. package/src/preset.css +21 -3
  96. package/src/styles/semantic.css +11 -0
  97. package/templates/AGENTS.md +4 -2
  98. package/templates/AGENTS.stub.md +25 -0
  99. package/templates/CLAUDE.md +4 -2
  100. package/templates/CLAUDE.stub.md +25 -0
  101. package/tokens.json +17 -1
package/CHANGELOG.md CHANGED
@@ -9,6 +9,91 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.20.0] - 2026-08-20
13
+
14
+ ダークモードで浮遊レイヤー(メニュー・ポップオーバー・ダイアログ・シート)の地が半透明で、後ろのコンテンツが透けていた問題の修正を中心に、開閉アニメーションの配布漏れ・`AdminSideNav` サブメニューの標準化・`DataTable` 系の root export をまとめた回。**公開 API の破壊的変更はなし。** ただし次の 3 点は既定の見た目・挙動が変わる。
15
+
16
+ - ダークモードで浮遊レイヤーの地が半透明の黒 → 不透明のグレーになる(ライトモードは変更なし)
17
+ - 同じくダークモードで、エッジ固定バー(`Header` / `StickyActionBar` / `BottomTabBar` / `AdminHeader` / `AdminSideNav` のレール / `OrderSummary` の fixed 表示)の地も不透明のグレーになる(ライトモードは変更なし)
18
+ - tw-animate-css を自分で入れていなかった consumer では、メニュー・ダイアログ等の**開閉アニメーションが新たに効き始める**(`prefers-reduced-motion` では従来どおり停止)
19
+ - `AdminSideNav` の PC サブメニューが標準 overlay(`DropdownMenu` 合成)になる
20
+
21
+ ### ✨ Added
22
+
23
+ - **`--Surface-Elevated` トークンを追加**: 浮遊レイヤー専用の**不透明**サーフェス。light は `#FFFFFF`(`Surface-Primary` と同値)、dark は `#3A3736`(`Primitive-Gray-800`)。ページ地として設計された `Surface-Primary` は dark でアルファ付き(`#000000E5`)のため、任意のコンテンツの上に重なる浮遊レイヤーには使えない。`preset-standalone.css` / `tokens.json` にも配布する
24
+ - **`DataTable` の実装済み subcomponent と `SortDirection` 型を root export**: `DataTableTable` / `DataTableHeader` / `DataTableBody` / `DataTableRow` / `DataTableHead` / `DataTableCell` と、セル・一括操作・空状態など全19部品を `@cakejp/design-system` から直接 import できる。consumer は配布対象外の内部パスや互換 wrapper に依存せず、標準の表現を組み合わせられる([#270](https://github.com/flashpark/cake-design-system/issues/270))
25
+ - **`BookmarkRibbonIcon` を root export**: `size` / `filled` を指定して、商品カードと同じブックマーク意匠を consumer から直接利用できる。互換 wrapper で同じアイコンを再実装する必要がなくなる([#271](https://github.com/flashpark/cake-design-system/issues/271))
26
+ - **AI 向けルールを「コピー」から「参照」へ変更**: `init` / postinstall が consumer ルートへ置くのはルール本文ではなく、package 内の正本を指す**ポインタ**(`templates/*.stub.md`)になった。DS 側のルール更新が `npm install` だけで既存 consumer にも届く。ルール本文の正本は `node_modules/@cakejp/design-system/templates/AGENTS.md` / `CLAUDE.md`([#263](https://github.com/flashpark/cake-design-system/issues/263))
27
+
28
+ **既存 consumer の移行**(初回のみ手作業。以後は不要):
29
+ 1. 自分の `AGENTS.md` / `CLAUDE.md` から、DS 共通ルールを写した部分を削除する
30
+ 2. 代わりに次の 1 行を置く: `DS 共通ルールは node_modules/@cakejp/design-system/templates/AGENTS.md(Claude は CLAUDE.md)を読む`
31
+ 3. プロジェクト固有のルールはそのまま残す
32
+
33
+ 移行しない場合も動作は変わらないが、DS 側のルール更新は届かないままになる。
34
+
35
+ ### 🐛 Fixed
36
+
37
+ - **ダークモードで `DropdownMenu` / `Popover` / `Select` / `HoverCard` / `Command` / `ContextMenu` / `Menubar` のメニュー面が半透明で、後ろのコンテンツが透けていた問題を修正**: `--popover` が `--Surface-Primary`(dark = `#000000E5` = 10% 透過)を指していた。`--Surface-Elevated` に向け直して不透明にする([#262](https://github.com/flashpark/cake-design-system/issues/262))
38
+ - **同じ理由で `Sheet` / `Dialog` / `AlertDialog` / `Drawer` の地も半透明だった問題を修正**: `bg-background`(= `--Surface-Primary`)から `bg-[var(--Surface-Elevated)]` へ。`Dialog` だけは以前から `dark:bg-[var(--Primitive-Gray-800)]` の個別対処が入っていたため**見た目は変わらない**(トークン経由に置き換えただけ)。他の 3 つは dark で地が変わる
39
+ - **preset がアニメーションユーティリティ(tw-animate-css)を配っておらず、consumer で開閉アニメーションが無言で効いていなかった問題を修正**: foundations の 12 部品(Dialog / Sheet / Popover / DropdownMenu / Select / Tooltip / Drawer / AlertDialog / HoverCard / ContextMenu / Menubar / NavigationMenu)が `animate-in` / `zoom-in-95` / `slide-in-from-*` を使うが、これらは Tailwind 本体ではなく tw-animate-css のユーティリティで、DS の devDependency のままだった。たまたま自分でも tw-animate-css を入れていた consumer(cake-api / kakeyasan-jp)だけアニメーションが動き、入れていない consumer(cakejp-biz-mag)では動かなかった。`tw-animate-css` を dependency に昇格し、`preset` が `@import` して配る。**consumer 側の設定変更は不要**(既に自分で import している場合は重複しても無害。自前の import と依存は削除してよい)
40
+ - **ダークモードでエッジ固定バーの地が半透明で、スクロールした内容が透けていた問題を修正**: `Header`(sticky) / `StickyActionBar` / `BottomTabBar` / `AdminHeader` / `AdminSideNav` のレール / `OrderSummary`(fixed 表示)が `bg-[var(--Surface-Primary)]`(dark = 10% 透過)を地にしていた。浮遊レイヤーと同じ `--Surface-Elevated` へ変更。**ライトモードは同値(`#FFFFFF`)のため変化なし**。ページ内フローに置かれる面(`OrderSummary disableFixed` / カード類)は重なりが発生しないため `Surface-Primary` のまま
41
+ - **`AdminSideNav` のPCサブメニューを標準overlayへ統一**: 自作の絶対配置flyoutを `DropdownMenu` 合成へ置き換え、下端寄り・12件以上でもviewport内へ収まるようにした。hover時はフォーカスを奪わず、クリック・Enter・Space・ArrowDown・Escapeでも操作できる。ダークモードでは不透明な `Surface-Elevated`、標準枠・影・重なり順を使う([#272](https://github.com/flashpark/cake-design-system/issues/272))
42
+
43
+ ### 📝 Docs
44
+
45
+ - **`linkComponent` の React Server Components 境界を部品別に明記**: Client Component の `Footer` / `MenuDrawer` へ Server Component から渡すと `Functions cannot be passed directly to Client Components` で落ちるため、Next.js App Router では呼び出し側を `"use client"` にする。一方、`Pagination` / `SubNav` / `ContentCard` は Server Component のまま利用でき、この prop のためだけに `"use client"` を付ける必要はない。契約・JSDoc に追記([#264](https://github.com/flashpark/cake-design-system/issues/264))
46
+
47
+ ## [0.19.0] - 2026-08-20
48
+
49
+ 記事メディア系 consumer(cakejp-biz-mag)と仕入れサイト(cake-distributor)から上がっていた不具合・不足を一括で解消する回。**公開 API の破壊的変更はなし**(追加した prop はすべて既定値が従来の挙動)。ただし次の 4 点は既定の挙動・見た目が変わる。
50
+
51
+ - `Sheet` を素で使っている画面は、開閉時にフォーカスが自動で移動するようになる(アクセシビリティの修正)
52
+ - 利用側の `lg:` が Tailwind 既定の 1024px ではなく DS の 960px で切り替わるようになる
53
+ - `SubNav` の PC 表示で左右の余白が 16px → 24px になる
54
+ - `Header variant="back-search"` の戻るボタンが 32px → 44px になる
55
+
56
+ > **0.18.0 は npm に公開されていません**(バージョン確定まで済んでいたが publish が走っていなかった)。npm 上の直前の版は 0.17.1 なので、0.17.x から上げると **0.18.0 の変更も同時に入ります**。0.18.0 の内容は下のセクションを参照してください。
57
+
58
+ ### ✨ Added
59
+
60
+ - **`Prose` を追加**(primitives): CMS(microCMS 等)のリッチテキスト HTML に、トークンで揃えた体裁を当てる部品。見出し(h2〜h5)・段落・リスト・引用・リンク・画像・図版キャプション・表・コード・区切り線の余白とサイズと線を 1 箇所に集約し、記事メディアごとに CSS Modules を書き分けて見た目がばらつくのを防ぐ。`html`(**利用側でサニタイズ済み**の HTML)か `children` のどちらでも使える。`as` で `article` / `section` に変えられる。幅は持たないので `Container size="narrow"` と組み合わせる。幅の広い表は表自身が縮む・横スクロールするのでページは横に伸びない([#239](https://github.com/flashpark/cake-design-system/issues/239))
61
+ - **`ContentCard` / `ContentList` を追加**(primitives): 記事・特集・お知らせなど「画像 + タイトル + メタ」のカード。`ProductCard`(商品)/ `ShopCard`(店舗)の兄弟で、記事メディア向け。汎用の `Card` に載せて組むと枠線・タイトル行数・カード高さが揃わない(実測で高さ 268 と 312 に割れる事例)ため部品化した。タイトルは 2 行で省略し**常に 2 行ぶんの高さ**を取るので、タイトルの長短が混ざっても一覧で下端が揃う。`orientation="vertical" | "horizontal" | "list"`、`aspect="16/10" | "16/9" | "1/1"`、画像は `imageSlot`(`next/image` / CMS / 図版を吸収)、`linkComponent` 対応。`ContentList` は区切り線つきの Medium 型リスト([#241](https://github.com/flashpark/cake-design-system/issues/241))
62
+ - **`ShareButtons` に `services` を追加**(共有先の選択と並び替え): 組み込みは `line` / `x` / `facebook` / `hatena`(はてなブックマーク)/ `copy`。既定は従来どおり `["line", "x", "copy"]`。組み込みに無い共有先は `{ key, label, href, icon }` を混ぜて足せる(`href` の `{url}` / `{text}` が URL エンコード済みの値に置換される)。事業者向けメディアで Facebook・はてなブックマークを出すために利用側が `Button asChild` で同意匠を組み直す必要がなくなる([#238](https://github.com/flashpark/cake-design-system/issues/238))
63
+ - **`SocialIcon` に `platform="hatena"` を追加**: はてなブックマークの「B!」マーク。あわせて `--Third-Party-Hatena`(#00A4DE)を配布トークンに追加した
64
+ - **`SearchBar` に `action` を追加**(`<form method="get">` モード): 指定すると素のフォーム送信で遷移するため、**JavaScript 無効の環境やクローラでも検索が成立する**。従来は送信時に `window.location` を書き換える実装しか無く、SEO を重視する記事メディアでは採用できず `Input` + `Button` で `<form>` を手組みすることになっていた。クエリのキーは `name`(既定 `"q"`)。`role="search"` も付く。未指定なら従来どおり([#237](https://github.com/flashpark/cake-design-system/issues/237))
65
+ - **`Header` / `SubNav` に `containerSize` を追加**(`page` / `wide` / `fluid`。`Container` と同じ段): 指定すると**背景・border・sticky は全幅のまま、中身だけ**を中央寄せする。本文を `Container size="page"` で組んでいるサイトで、ロゴ・ナビの左端が本文と揃う(1440px 幅での実測: 指定前 ロゴ 24 / サブナビ 16 / 本文 104 → 指定後すべて **104**)。従来は利用側が `className="mx-auto max-w-[1280px]"` を当てると背景と線まで中央幅になって両脇が抜けるため、sticky と装飾をラッパーに持ち直す必要があった。未指定時は DOM 構造も従来どおり(内側ラッパーを足さない)([#234](https://github.com/flashpark/cake-design-system/issues/234))
66
+ - **`TabsList` に `size="sm"` を追加**: セグメント(ピル型)タブの 1 項目あたりの最小幅(100px)を外し、左右の padding を詰める。既定の `size="md"` は従来どおり。サイドバー幅 266px に 3 択のセグメントを置くと右にはみ出していた(実測 `scrollWidth 308 > clientWidth 266`)のが収まる([#235](https://github.com/flashpark/cake-design-system/issues/235))
67
+ - **`Button` に `size="icon-xl"` を追加**(44px / `size-11`): `size="default"` と同じ高さなので、入力行(`Input` / `SearchBar` / `SelectTrigger`)の横にアイコンボタンを並置しても揃う。既存の icon サイズ(24 / 32 / 36 / 40px)はどれも 44px の入力コントロールと揃わず、`tokens.json` の `touchTarget["text-input"].min` = 44px も満たしていなかった
68
+
69
+ ### 🐛 Fixed
70
+
71
+ - **`Header variant="back-search"` の戻るボタンと `centerSlot` の高さが揃っていなかった問題を修正**: 戻るボタンが `size="icon-sm"`(32px)固定で、DS が推奨する組み合わせ(`centerSlot={<SearchBar />}` = 44px)と **12px ずれていた**。`size="icon-xl"`(44px)にして揃えた。あわせて 44px のタップターゲット下限も満たすようになる
72
+ - **`SubNav` の左右余白を `Container` と揃えた**: `px-4` 固定だったため、PC で `Container` の gutter(24px)と 8px ずれていた。SP 16 / PC 24 になる。**PC(768px 以上)で左右の余白が 16px → 24px に変わる**([#234](https://github.com/flashpark/cake-design-system/issues/234))
73
+ - **`SectionHeader`(`align="start"`)で `subtitle` と右側 `action` を併用するとキャプションが早い位置で折り返される問題を修正**: タイトルとキャプションが `action` と同じ Flex 行の中に入っていたため、キャプションまで `action` の幅ぶん狭まっていた。タイトル行とキャプションを別の行に分け、キャプションはヘッダー全幅(右側アクションの下も含む)で折り返す。`moreHref` 併用時にリンクになるのは**タイトル行だけ**になり、`subtitle` はリンクの外に出る(読み上げ対象のリンク数は従来と同じ 1 つ)。`align="center"` は変更なし([#232](https://github.com/flashpark/cake-design-system/issues/232))
74
+ - **`Tabs`(underbar)のトリガーに左右 padding が無く、狭幅で隣のラベルと文字が接触する問題を修正**: 既定で左右 8px を持たせた。サイドバー幅 266px の実測で余白が `1px / 1px` → `8px / 8px` になる。`scrollable` の underbar は `gap-4` で間隔を取っているため従来どおり padding 0([#235](https://github.com/flashpark/cake-design-system/issues/235))
75
+ - **`SheetContent` が開閉時のフォーカス自動移動を無条件に止めていた問題を修正**(a11y / WCAG 2.4.3): `onOpenAutoFocus` / `onCloseAutoFocus` を常に `preventDefault` していたため、素で使うと**開いてもキーボードで中に入れず、閉じてもトリガーにフォーカスが戻らない**状態だった。既定を Radix のまま(開く=中へ / 閉じる=トリガーへ戻る)に直し、止めたい用途向けに `autoFocus={false}` を追加。あわせて利用側が渡した `onOpenAutoFocus` / `onCloseAutoFocus` が DS の既定を消さずに追加で呼ばれるようにした(従来は利用側のハンドラが DS の指定ごと置き換わっていた)。`Sheet` を素で使っている画面はフォーカス挙動が変わる([#233](https://github.com/flashpark/cake-design-system/issues/233))
76
+ - **`preset` がビューポートのブレイクポイント(`--breakpoint-md/lg/xl`)を配っておらず、利用側の `lg:` が Tailwind 既定の 1024px で効いていた問題を修正**: DS の切替段は `md`=768 / `lg`=**960** で、`preset.css` はコンテナクエリ用の `--container-*` しか定義していなかった。そのため `@theme` で自分で書き直したプロダクトだけが正しい位置で切り替わり、書いていないプロダクトは PC 中段が 64px ずれていた(実例: cakejp-biz-mag のトップが 1000px 幅で 1 カラムに落ちる)。`preset` を読み込むだけで `md:` / `lg:` / `xl:` が DS の段に揃う。**利用側の `globals.css` に同じ `@theme` を書いている場合は削除してよい**(値が同じなら実害はない)。`lg:` を Tailwind 既定の 1024px 前提で書いていた画面は切替位置が 960px に変わる([#242](https://github.com/flashpark/cake-design-system/issues/242))
77
+
78
+ ## [0.18.0] - 2026-08-19
79
+
80
+ 利用側が DS 内部 DOM を `nth-child` / `:has()` で上書きして回避していたケースを公開 props と不具合修正で解消し、あわせてリンクを描く部品に `linkComponent` を追加してフレームワークのリンク部品を差し込めるようにする回。**公開 API の破壊的変更はなし**(追加した prop はすべて既定値が従来の挙動)。ただし `navBreakpoint="lg"` を使っている場合のみ、PC 行の開始が 1024px → 960px に変わる(tokens.json の切替段に合わせた修正)。
81
+
82
+ ### 🐛 Fixed
83
+
84
+ - **`Header` の `navBreakpoint="lg"` が 1024px で切り替わっていたのを、tokens.json の切替段 `lg`=960px に修正**: DS の切替段は `md`=768 / `lg`=960 の 2 つで、1024 は存在しない段だった。`navBreakpoint="lg"` を使っている場合、PC 行の開始が 1024px → 960px に変わる
85
+
86
+ - **`Header` の navItems 自動退避で、退避した項目がページのスクロール領域に残り横スクロールが起きる問題を修正**: 計測用の複製は `absolute` + `invisible` で通常フローには影響しないが、絶対配置要素も祖先のスクロール可能領域には寄与する。利用側の実測では viewport 769px に対しページが 932px を要求していた([#243](https://github.com/flashpark/cake-design-system/issues/243))。0 幅 0 高 + `overflow:hidden` の器で包んで寄与を断つ。クリップは描画時のみでレイアウトは保たれるため、退避件数の計算結果は変わらない
87
+
88
+ ### ✨ Added
89
+
90
+ - **`Footer` / `MenuDrawer` / `SubNav` / `PaginationLink` に `linkComponent` を追加**: リンクの描画に使うコンポーネントを差し替えられる(`next/link` 等)。これらの部品は素の `<a>` しか描けず、Next.js の consumer ではこの導線だけフルページ遷移になっていた。未指定なら従来どおり `<a>`。`Footer` は `external: true` のリンクだけ常に素の `<a target="_blank">` にフォールバックする(他 3 部品の項目に `external` フラグは無く、渡した href はすべて `linkComponent` で描かれる)([#236](https://github.com/flashpark/cake-design-system/issues/236))
91
+ - **`Header` に `searchBreakpoint` を追加**(`md`=768px / `lg`=960px): `searchSlot` の器を出し始めるコンテナ幅を選べる。中身だけを利用側の CSS で隠しても器は `flex-1` の幅を確保し続けてインラインナビを潰すため、狭い帯で検索を出さない場合はこの prop を使う。値は tokens.json の切替段のみで、コンテンツ上限段の `xl`(1280) は受け付けない([#243](https://github.com/flashpark/cake-design-system/issues/243))
92
+ - **`HeaderAction` に `showLabel` を追加**: アイコンの右に可視ラベルを出すピル型(高さ 40px・枠線)で描画する。「ログイン」等アイコンだけでは意味が伝わらない導線向け。可視テキストがあるときは `aria-label` を付けず二重読み上げを避ける([#245](https://github.com/flashpark/cake-design-system/issues/245))
93
+ - **`ProductCard` に `hoverEffect` を追加**(`zoom` / `fade` / `none`): ホバー時の画像演出を選べる。`fade` は拡大せずわずかに明るくする本番 EC 準拠の挙動で、暗テーマでも明るくなるよう opacity ではなく brightness を使う。未指定時の既定は従来の見た目を保つため orientation ごと(`vertical` = `zoom` / `horizontal` = `none`)で、明示指定すれば `horizontal` でも効く([#244](https://github.com/flashpark/cake-design-system/issues/244))
94
+ - **`ReviewCard` に `bordered` を追加**(既定 `true`): 商品詳細などページ全体をフラットに統一する画面で枠線を外せる([#247](https://github.com/flashpark/cake-design-system/issues/247))
95
+ - **`Footer` に `variant="brand-first"` と `bottomRightSlot` を追加**: ロゴ・説明文を先頭カラムに置き、その右にリンク群を並べる構成。下部バーはコピーライト左・`bottomRightSlot`(`Powered by` 等)右の左右振り分けになる。求人ケーキ屋さん.jp / 仕入れケーキ屋さん.JP のフッターをカスタムマークアップなしで組める([#246](https://github.com/flashpark/cake-design-system/issues/246))
96
+
12
97
  ## [0.17.1] - 2026-08-06
13
98
 
14
99
  **0.17.0 の型定義が壊れていたため、0.17.0 は使わず 0.17.1 へ上げてください。**
package/CLAUDE.md CHANGED
@@ -67,6 +67,21 @@ PdM や社長が AI(Claude 等)で HTML モックを作る際、このパッ
67
67
 
68
68
  ---
69
69
 
70
+ ## 見た目のフィードバックを受けたときのゲート(DS 起因か consumer 起因かを先に切り分ける)
71
+
72
+ 「崩れている」「詰まっている」「揃っていない」等の指摘を受けたら、**直す前に**起因を切り分ける。DS 起因の崩れを consumer 側の `className` だけで握りつぶすと、同じ崩れが他プロダクトで再発し、DS には何も残らない。
73
+
74
+ 1. **実測する**: computed style / `getBoundingClientRect` で数値を取る(padding・幅・左端座標)。見た目の印象で判断しない。
75
+ 2. **DS 部品のソースを読む**: `node_modules/@cakejp/design-system/dist/index.js` か DS リポジトリの `src/components/**` で該当部品の className を確認し、その挙動が
76
+ - **DS の設計上の仕様/欠落**(実例: `TabsTrigger` の underbar が padding を持たない / `Header` `SubNav` が内側コンテナを持たず全幅に描く / `SubNav` の gutter が `Container` と 8px 違う / segment タブの `min-w-[100px]` / `SheetContent` がフォーカス制御を無条件に `preventDefault`)なのか
77
+ - **consumer の使い方**(variant の選択ミス、`className` の当て方、ラッパーの有無、`Container` を通していない)なのか
78
+ を判定する。
79
+ 3. **DS 起因なら**: consumer 側は最小の回避(`className` 追加・ラッパー)に留め、コードコメントに「DS 起因。DS 側で X が直ったら削除」と書く。**同時に flashpark/cake-design-system に issue を立てる**(再現条件・実測値・期待挙動・consumer 側の回避コード)。
80
+ 4. **consumer 起因なら**: 素直に直す。同じ誤用が他に無いか grep する。
81
+ 5. **報告には「起因: DS / consumer」と根拠(読んだ DS のソース箇所・実測値)を必ず添える。** 起因を書かない「直しました」報告は不完全。
82
+
83
+ ---
84
+
70
85
  ## 技術スタック
71
86
 
72
87
  - React 19 + TypeScript
@@ -87,6 +102,7 @@ PdM や社長が AI(Claude 等)で HTML モックを作る際、このパッ
87
102
  | テキスト(中) | `var(--color-gray-600)` |
88
103
  | テキスト(弱) | `var(--color-gray-400)` |
89
104
  | エラー | `var(--color-red-500)` |
105
+ | 浮遊レイヤーの地(メニュー・ポップオーバー・ダイアログ・シート) | `var(--Surface-Elevated)` |
90
106
  | 成功 | `var(--color-green-500)` |
91
107
 
92
108
  詳細は `tokens.json` を参照。
package/bin/init.js CHANGED
@@ -11,8 +11,8 @@
11
11
  // npx cakejp-ds init --force # 既存ファイルを上書き
12
12
  // npx cakejp-ds postinstall # npm postinstall から呼ばれる silent モード
13
13
 
14
- import { copyFileSync, existsSync, readdirSync } from "node:fs"
15
- import { dirname, join, resolve } from "node:path"
14
+ import { copyFileSync, existsSync, readFileSync, realpathSync, readdirSync } from "node:fs"
15
+ import { dirname, join, resolve, sep } from "node:path"
16
16
  import { fileURLToPath } from "node:url"
17
17
 
18
18
  const __dirname = dirname(fileURLToPath(import.meta.url))
@@ -94,25 +94,186 @@ if (cmd !== "init" && cmd !== "postinstall") {
94
94
  }
95
95
 
96
96
  // ─── postinstall モードのガード ─────────────────────────────
97
- // INIT_CWD は npm install が実行されたディレクトリ(consumer のルート)。
97
+ function findDirectPackageInstallRoot(packageRoot) {
98
+ const parts = resolve(packageRoot).split(sep)
99
+ for (let i = parts.length - 3; i >= 0; i--) {
100
+ if (parts[i] !== "node_modules" || parts[i + 1] !== "@cakejp" || parts[i + 2] !== "design-system") {
101
+ continue
102
+ }
103
+
104
+ const candidate = parts.slice(0, i).join(sep) || sep
105
+ if (existsSync(join(candidate, "package.json"))) {
106
+ return candidate
107
+ }
108
+ }
109
+
110
+ return undefined
111
+ }
112
+
113
+ function readWorkspacePatterns(root) {
114
+ const patterns = []
115
+
116
+ try {
117
+ const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"))
118
+ if (Array.isArray(pkg.workspaces)) patterns.push(...pkg.workspaces)
119
+ if (Array.isArray(pkg.workspaces?.packages)) patterns.push(...pkg.workspaces.packages)
120
+ } catch {
121
+ // package.json が無い・壊れている場合は pnpm-workspace.yaml の確認へ進む。
122
+ }
123
+
124
+ try {
125
+ patterns.push(...readPnpmWorkspacePatterns(join(root, "pnpm-workspace.yaml")))
126
+ } catch {
127
+ // pnpm-workspace.yaml が無い・壊れている場合は通常の単一 package として扱う。
128
+ }
129
+
130
+ return [...new Set(patterns)]
131
+ }
132
+
133
+ function readPnpmWorkspacePatterns(file) {
134
+ if (!existsSync(file)) return []
135
+
136
+ const patterns = []
137
+ let inPackages = false
138
+ for (const rawLine of readFileSync(file, "utf8").split(/\r?\n/)) {
139
+ const withoutComment = rawLine.replace(/\s+#.*$/, "")
140
+ const line = withoutComment.trim()
141
+ if (!line) continue
142
+
143
+ if (!inPackages) {
144
+ const inlineMatch = line.match(/^packages:\s*\[(.*)\]$/)
145
+ if (inlineMatch) {
146
+ patterns.push(...parseYamlInlineSequence(inlineMatch[1]))
147
+ continue
148
+ }
149
+ inPackages = line === "packages:"
150
+ continue
151
+ }
152
+
153
+ if (/^\S[^:]*:/.test(withoutComment)) break
154
+
155
+ const match = line.match(/^-\s+['"]?([^'"]+)['"]?$/)
156
+ if (match) patterns.push(match[1])
157
+ }
158
+
159
+ return patterns
160
+ }
161
+
162
+ function parseYamlInlineSequence(value) {
163
+ return value
164
+ .split(",")
165
+ .map((item) => item.trim().replace(/^['"]|['"]$/g, ""))
166
+ .filter(Boolean)
167
+ }
168
+
169
+ function hasWorkspaceConfig(root) {
170
+ try {
171
+ const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"))
172
+ if (Array.isArray(pkg.workspaces) || Array.isArray(pkg.workspaces?.packages)) return true
173
+ } catch {
174
+ // package.json が無い・壊れている場合は pnpm-workspace.yaml の有無だけを見る。
175
+ }
176
+
177
+ return existsSync(join(root, "pnpm-workspace.yaml"))
178
+ }
179
+
180
+ function expandWorkspaceRoots(root) {
181
+ const roots = new Set([root])
182
+ const workspacePatterns = readWorkspacePatterns(root)
183
+
184
+ for (const pattern of workspacePatterns) {
185
+ if (typeof pattern !== "string") continue
186
+
187
+ if (!pattern.includes("*")) {
188
+ roots.add(resolve(root, pattern))
189
+ continue
190
+ }
191
+
192
+ if (pattern.endsWith("/**")) {
193
+ collectPackageRoots(resolve(root, pattern.slice(0, -3)), roots)
194
+ continue
195
+ }
196
+
197
+ // npm/pnpm workspaces で一般的な apps/* / packages/* を展開する。
198
+ // それ以外の複雑な glob は安全に無視する。
199
+ if (!pattern.endsWith("/*") || pattern.includes("**")) continue
200
+
201
+ const parent = resolve(root, pattern.slice(0, -2))
202
+ if (!existsSync(parent)) continue
203
+
204
+ for (const entry of readdirSync(parent, { withFileTypes: true })) {
205
+ if (entry.isDirectory()) roots.add(join(parent, entry.name))
206
+ }
207
+ }
208
+
209
+ return [...roots].filter((candidate) => existsSync(join(candidate, "package.json")))
210
+ }
211
+
212
+ function collectPackageRoots(dir, roots) {
213
+ if (!existsSync(dir)) return
214
+
215
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
216
+ if (!entry.isDirectory()) continue
217
+ if (["node_modules", ".git", ".hg", ".svn", ".pnpm"].includes(entry.name)) continue
218
+
219
+ const full = join(dir, entry.name)
220
+ if (existsSync(join(full, "package.json"))) {
221
+ roots.add(full)
222
+ }
223
+ collectPackageRoots(full, roots)
224
+ }
225
+ }
226
+
227
+ function findLinkedConsumerRoots(packageRoot, requestedRoot) {
228
+ const realPackageRoot = realpathSync(packageRoot)
229
+ return expandWorkspaceRoots(requestedRoot).filter((workspaceRoot) => {
230
+ const installedPackage = join(workspaceRoot, "node_modules/@cakejp/design-system")
231
+ if (!existsSync(installedPackage)) return false
232
+ try {
233
+ return realpathSync(installedPackage) === realPackageRoot
234
+ } catch {
235
+ return false
236
+ }
237
+ })
238
+ }
239
+
240
+ function findConsumerRoots(packageRoot, requestedRoot) {
241
+ const roots = new Set()
242
+ const directRoot = findDirectPackageInstallRoot(packageRoot)
243
+ if (directRoot) roots.add(directRoot)
244
+ for (const linkedRoot of findLinkedConsumerRoots(packageRoot, requestedRoot)) {
245
+ roots.add(linkedRoot)
246
+ }
247
+ if (roots.size === 0 && !hasWorkspaceConfig(requestedRoot)) roots.add(requestedRoot)
248
+ return [...roots].filter((candidate) => existsSync(join(candidate, "package.json")))
249
+ }
250
+
251
+ // INIT_CWD は npm install が実行されたディレクトリ。
252
+ // monorepo では root になり、実際の consumer package は frontend/node_modules 等にある
253
+ // ため、postinstall では package の配置場所から consumer package root を優先して決める。
98
254
  // 未設定、または pkgRoot と同じ場合は DS 自身のインストールコンテキストなのでスキップ。
99
255
  const isPostinstall = cmd === "postinstall"
100
- const consumerRoot = process.env.INIT_CWD || process.cwd()
256
+ const requestedRoot = resolve(process.env.INIT_CWD || process.cwd())
257
+ const consumerRoots = isPostinstall ? findConsumerRoots(pkgRoot, requestedRoot) : [requestedRoot]
101
258
 
102
259
  if (isPostinstall) {
103
260
  // DS 自身を開発している場合(INIT_CWD が pkgRoot)はスキップ
104
- if (!process.env.INIT_CWD || resolve(consumerRoot) === pkgRoot) {
261
+ if (!process.env.INIT_CWD || requestedRoot === pkgRoot) {
105
262
  process.exit(0)
106
263
  }
107
264
  // consumer のルートに package.json が無ければスキップ(安全のため)
108
- if (!existsSync(join(consumerRoot, "package.json"))) {
265
+ if (consumerRoots.length === 0) {
109
266
  process.exit(0)
110
267
  }
111
268
  }
112
269
 
270
+ // consumer へ設置するのは「正本を参照するポインタ」だけ(issue #87 / #263)。
271
+ // ルール本文(templates/AGENTS.md / CLAUDE.md)は package 内に置いたまま読ませる。
272
+ // 本文をコピーすると、DS 側の更新が既存 consumer へ二度と届かない
273
+ // (postinstall は既存ファイルを上書きしないため)。
113
274
  const files = [
114
- { src: "templates/AGENTS.md", dest: "AGENTS.md", label: "Codex 用" },
115
- { src: "templates/CLAUDE.md", dest: "CLAUDE.md", label: "Claude Code 用" },
275
+ { src: "templates/AGENTS.stub.md", dest: "AGENTS.md", label: "Codex 用" },
276
+ { src: "templates/CLAUDE.stub.md", dest: "CLAUDE.md", label: "Claude Code 用" },
116
277
  ]
117
278
 
118
279
  if (!isPostinstall) {
@@ -123,32 +284,34 @@ if (!isPostinstall) {
123
284
  let created = 0
124
285
  let skipped = 0
125
286
 
126
- for (const { src, dest, label } of files) {
127
- const srcPath = join(pkgRoot, src)
128
- const destPath = join(consumerRoot, dest)
287
+ for (const consumerRoot of consumerRoots) {
288
+ for (const { src, dest, label } of files) {
289
+ const srcPath = join(pkgRoot, src)
290
+ const destPath = join(consumerRoot, dest)
129
291
 
130
- if (!existsSync(srcPath)) {
131
- if (!isPostinstall) {
132
- console.error(` ✗ テンプレートが見つかりません: ${srcPath}`)
292
+ if (!existsSync(srcPath)) {
293
+ if (!isPostinstall) {
294
+ console.error(` ✗ テンプレートが見つかりません: ${srcPath}`)
295
+ }
296
+ process.exit(isPostinstall ? 0 : 1)
133
297
  }
134
- process.exit(isPostinstall ? 0 : 1)
135
- }
136
298
 
137
- if (existsSync(destPath) && !force) {
138
- if (!isPostinstall) {
139
- console.log(` ⏭ ${dest} は既に存在するためスキップ(--force で上書き)`)
299
+ if (existsSync(destPath) && !force) {
300
+ if (!isPostinstall) {
301
+ console.log(` ⏭ ${dest} は既に存在するためスキップ(--force で上書き)`)
302
+ }
303
+ skipped++
304
+ continue
140
305
  }
141
- skipped++
142
- continue
143
- }
144
306
 
145
- copyFileSync(srcPath, destPath)
146
- if (isPostinstall) {
147
- console.log(`[@cakejp/design-system] ${dest} をプロジェクトルートに設置しました`)
148
- } else {
149
- console.log(` ✓ ${dest} を設置しました(${label})`)
307
+ copyFileSync(srcPath, destPath)
308
+ if (isPostinstall) {
309
+ console.log(`[@cakejp/design-system] ${dest} をプロジェクトルートに設置しました`)
310
+ } else {
311
+ console.log(` ✓ ${dest} を設置しました(${label})`)
312
+ }
313
+ created++
150
314
  }
151
- created++
152
315
  }
153
316
 
154
317
  if (isPostinstall) {