@cakejp/design-system 0.19.0 → 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 (34) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/CLAUDE.md +1 -0
  3. package/bin/init.js +191 -28
  4. package/contracts/components.json +38 -7
  5. package/contracts/composition.json +1 -1
  6. package/contracts/rules.json +1 -1
  7. package/contracts/screen-patterns.json +1 -1
  8. package/dist/components/ec/order-summary.js +16 -15
  9. package/dist/components/ec/product-card.js +1 -1
  10. package/dist/components/foundations/alert-dialog.js +7 -7
  11. package/dist/components/foundations/data-table.js +489 -16
  12. package/dist/components/foundations/dialog.js +13 -13
  13. package/dist/components/foundations/drawer.js +5 -5
  14. package/dist/components/foundations/sheet.js +7 -7
  15. package/dist/components/primitives/admin-header.js +1 -1
  16. package/dist/components/primitives/admin-side-nav.js +195 -86
  17. package/dist/components/primitives/bottom-tab-bar.js +20 -20
  18. package/dist/components/primitives/header.js +43 -37
  19. package/dist/components/primitives/sticky-action-bar.js +2 -1
  20. package/dist/index.css +1 -1
  21. package/dist/index.js +791 -470
  22. package/dist/types/components/primitives/admin-side-nav.d.ts +3 -0
  23. package/dist/types/index.d.ts +3 -2
  24. package/dist/types/lib/link-component.d.ts +9 -0
  25. package/package.json +2 -2
  26. package/src/components/COMPONENT_LOOKUP.md +1 -1
  27. package/src/preset-standalone.css +2 -0
  28. package/src/preset.css +13 -3
  29. package/src/styles/semantic.css +10 -0
  30. package/templates/AGENTS.md +4 -2
  31. package/templates/AGENTS.stub.md +25 -0
  32. package/templates/CLAUDE.md +4 -2
  33. package/templates/CLAUDE.stub.md +25 -0
  34. package/tokens.json +9 -0
package/CHANGELOG.md CHANGED
@@ -9,6 +9,41 @@ 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
+
12
47
  ## [0.19.0] - 2026-08-20
13
48
 
14
49
  記事メディア系 consumer(cakejp-biz-mag)と仕入れサイト(cake-distributor)から上がっていた不具合・不足を一括で解消する回。**公開 API の破壊的変更はなし**(追加した prop はすべて既定値が従来の挙動)。ただし次の 4 点は既定の挙動・見た目が変わる。
package/CLAUDE.md CHANGED
@@ -102,6 +102,7 @@ PdM や社長が AI(Claude 等)で HTML モックを作る際、このパッ
102
102
  | テキスト(中) | `var(--color-gray-600)` |
103
103
  | テキスト(弱) | `var(--color-gray-400)` |
104
104
  | エラー | `var(--color-red-500)` |
105
+ | 浮遊レイヤーの地(メニュー・ポップオーバー・ダイアログ・シート) | `var(--Surface-Elevated)` |
105
106
  | 成功 | `var(--color-green-500)` |
106
107
 
107
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) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "name": "Cake.jp Design System — Component Contracts",
4
- "version": "0.19.0",
4
+ "version": "0.20.0",
5
5
  "description": "全コンポーネントの構造化定義。バリアント・アクセシビリティ要件・使用ルールを機械可読形式で管理。",
6
6
  "kind": "kind:\"shell\" は複数ページで再利用されるレイアウト枠(AdminShell / WorkspaceShell / MypageShell 等)。無印は部品コンポーネント。固有ページを組むときは該当 shell から始める。src/components/pages/ 配下のストーリーは特定ページ専用・非配布のため contracts の対象外。",
7
7
  "naming": "コンポーネント名は**用途ではなく機能**で付ける。用途名(QuantitySelector = カート数量)にすると、同じ機能を別の文脈で探した人が見つけられず二重実装が生まれる(実例: 用途名だったため NumberInput が別途作られ、意匠の違うステッパーが 2 つになった)。「〜Selector」「〜Picker」等の用途接尾辞を付ける前に、その部品が何をするものかで名前が付けられないかを先に検討する。既存名を機能名へ変える場合は deprecatedAliases に旧名を残す。",
@@ -214,6 +214,27 @@
214
214
  "name": "DataTable",
215
215
  "path": "src/components/foundations/data-table.tsx",
216
216
  "formBinding": "none",
217
+ "subcomponents": [
218
+ "DataTableTable",
219
+ "DataTableHeader",
220
+ "DataTableBody",
221
+ "DataTableRow",
222
+ "DataTableHead",
223
+ "DataTableCell",
224
+ "DataTableAvatarCell",
225
+ "DataTableImageCell",
226
+ "DataTableLinkCell",
227
+ "DataTableCheckboxCell",
228
+ "DataTableActionCell",
229
+ "DataTableInputCell",
230
+ "DataTableSelectCell",
231
+ "DataTableNumberCell",
232
+ "DataTableDragHandleCell",
233
+ "DataTableBulkActions",
234
+ "DataTableSectionRow",
235
+ "DataTableAddRow",
236
+ "DataTableEmptyState"
237
+ ],
217
238
  "rules": [
218
239
  "管理画面のテーブルはui/tableでなくこちらを使う。ソート・選択行・アクションメニュー対応",
219
240
  "ActionCellのメニュー操作は行のonClickへ伝播しない(本体でstopPropagation済み)。行クリック選択との併用時も利用側ガードは不要",
@@ -357,7 +378,8 @@
357
378
  "name": "Pagination",
358
379
  "path": "src/components/foundations/pagination.tsx",
359
380
  "rules": [
360
- "linkComponent でリンク実装を差し替えられる(next/link 等)。未指定なら素の <a>。この部品の項目に external フラグは無く、渡した href はすべて linkComponent で描かれる(外部 URL を出し分けたい場合は consumer 側でラップする)(#236)"
381
+ "linkComponent でリンク実装を差し替えられる(next/link 等)。未指定なら素の <a>。この部品の項目に external フラグは無く、渡した href はすべて linkComponent で描かれる(外部 URL を出し分けたい場合は consumer 側でラップする)(#236)",
382
+ "この部品は Server Component のまま使える。linkComponent を渡すためだけに、Next.js App Router の呼び出し側を \"use client\" にする必要はない(#264)"
361
383
  ]
362
384
  },
363
385
  {
@@ -597,7 +619,9 @@
597
619
  "サイドバーは 1 枚のパネルとして読ませる。ヘッダー行に border-bottom、フッター(ユーザーブロック・ヘルプ導線等)に border-top を引かない。内部を横線で区切ると分割されて見えるため、分離は余白で担う",
598
620
  "画面に見える線は①サイドバー右端の縦線②コンテンツヘッダー下の横線の 2 本に絞る。サイドバー内で横線を引くと 2 本が十字に交差して見える(正しくは T 字で突き当たる)",
599
621
  "折りたたみ(開閉)を持たせる場合、トリガーはサイドバー内ヘッダー行の右端に置く。コンテンツヘッダーの左端に置かない。動かす対象と操作する場所が離れると、そのボタンが何に属するか読めなくなる",
600
- "ナビ項目は項目高 44px(WCAG 2.5.5 の最小ターゲット)を確保する。メイン項目・サブメニュー項目とも min-h-11 で下限を固定する(行高の小さいテキストは縦 padding だけだと割り込む)"
622
+ "ナビ項目は項目高 44px(WCAG 2.5.5 の最小ターゲット)を確保する。メイン項目・サブメニュー項目とも min-h-11 で下限を固定する(行高の小さいテキストは縦 padding だけだと割り込む)",
623
+ "PC のサブメニューは DropdownMenu の Portal / collision handling を使い、side=right / align=start / collisionPadding=8 で viewport 内に収める。絶対配置の flyout を手組みしない",
624
+ "PC のサブメニューは hover とクリックに加え、Enter / Space / ArrowDown で開き Escape で閉じてトリガーへフォーカスを戻す。hover 起動時はメニューへフォーカスを移さず、トリガーと Portal 間は短い close delay で橋渡しする"
601
625
  ]
602
626
  },
603
627
  {
@@ -704,7 +728,8 @@
704
728
  "meta は 1 行に収める。複数行にすると一覧でカード高さが割れる",
705
729
  "画像は imageSlot に渡す(next/image・CMS・図版を吸収する)。縦横比とクリップはカード側が持つ",
706
730
  "Medium 型の縦リストは orientation=\"list\" + ContentList(区切り線つき)を使う",
707
- "カード全体がリンク。linkComponent で next/link 等を差し込める"
731
+ "カード全体がリンク。linkComponent で next/link 等を差し込める",
732
+ "この部品は Server Component のまま使える。linkComponent を渡すためだけに、Next.js App Router の呼び出し側を \"use client\" にする必要はない(#264)"
708
733
  ]
709
734
  },
710
735
  {
@@ -809,7 +834,8 @@
809
834
  "path": "src/components/primitives/footer.tsx",
810
835
  "rules": [
811
836
  "variant='brand-first' でロゴ・説明文を先頭カラムに置き、その右にリンク群を並べる。下部バーはコピーライト左・bottomRightSlot 右の左右振り分けになる。linkGroups が空でもロゴ・説明文は落ちず、逆に logo/description が無いときは nav を 2 カラムへ押し込まない。この構成をカスタムマークアップで自作しない(#246)",
812
- "linkComponent でリンク実装を差し替えられる(next/link 等)。未指定なら素の <a>。external:true のリンクは framework のリンク部品へ通す意味が無いため常に素の <a target=_blank> にフォールバックする(#236)"
837
+ "linkComponent でリンク実装を差し替えられる(next/link 等)。未指定なら素の <a>。external:true のリンクは framework のリンク部品へ通す意味が無いため常に素の <a target=_blank> にフォールバックする(#236)",
838
+ "この部品は Client Component のため、React Server Components では Server Component から linkComponent を渡せない(実行時に Functions cannot be passed directly to Client Components で落ちる)。Next.js App Router では呼び出し側のファイルを \"use client\" にする。エラーメッセージは \"use server\" を示唆するが正解は逆(#264)"
813
839
  ],
814
840
  "variants": [
815
841
  "default",
@@ -964,7 +990,8 @@
964
990
  "name": "MenuDrawer",
965
991
  "path": "src/components/primitives/menu-drawer.tsx",
966
992
  "rules": [
967
- "linkComponent でリンク実装を差し替えられる(next/link 等)。未指定なら素の <a>。この部品の項目に external フラグは無く、渡した href はすべて linkComponent で描かれる(外部 URL を出し分けたい場合は consumer 側でラップする)(#236)"
993
+ "linkComponent でリンク実装を差し替えられる(next/link 等)。未指定なら素の <a>。この部品の項目に external フラグは無く、渡した href はすべて linkComponent で描かれる(外部 URL を出し分けたい場合は consumer 側でラップする)(#236)",
994
+ "この部品は Client Component のため、React Server Components では Server Component から linkComponent を渡せない(実行時に Functions cannot be passed directly to Client Components で落ちる)。Next.js App Router では呼び出し側のファイルを \"use client\" にする。エラーメッセージは \"use server\" を示唆するが正解は逆(#264)"
968
995
  ]
969
996
  },
970
997
  {
@@ -1223,7 +1250,8 @@
1223
1250
  "rules": [
1224
1251
  "linkComponent でリンク実装を差し替えられる(next/link 等)。未指定なら素の <a>。この部品の項目に external フラグは無く、渡した href はすべて linkComponent で描かれる(外部 URL を出し分けたい場合は consumer 側でラップする)(#236)",
1225
1252
  "左右の余白は Container と同じ SP 16 / PC 24。利用側で px-* を足さない",
1226
- "本文を Container size=\"page\" で中央寄せしているサイトは containerSize=\"page\" を指定する。下線は全幅のまま中身だけ中央寄せされる(issue #234)"
1253
+ "本文を Container size=\"page\" で中央寄せしているサイトは containerSize=\"page\" を指定する。下線は全幅のまま中身だけ中央寄せされる(issue #234)",
1254
+ "この部品は Server Component のまま使える。linkComponent を渡すためだけに、Next.js App Router の呼び出し側を \"use client\" にする必要はない(#264)"
1227
1255
  ]
1228
1256
  },
1229
1257
  {
@@ -1374,6 +1402,9 @@
1374
1402
  {
1375
1403
  "name": "ProductCard",
1376
1404
  "path": "src/components/ec/product-card.tsx",
1405
+ "subcomponents": [
1406
+ "BookmarkRibbonIcon"
1407
+ ],
1377
1408
  "layouts": [
1378
1409
  "vertical",
1379
1410
  "horizontal"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "name": "Cake.jp Design System — Composition Contracts",
4
- "version": "0.19.0",
4
+ "version": "0.20.0",
5
5
  "description": "ページ骨格・視覚階層・余白リズムの機械可読な正本。画面パターンを選んだ後の「実際の組み方」を規定する。ページの外枠(モバイル/デスクトップ/管理)、商品グリッドの列数、余白の4段リズム、Surface のネスト階層、テキスト階層、CTA の優先順位を構造化する。人間向けの解説は guidelines/composition/layout.md・guidelines/composition/hierarchy.md・guidelines/overview.md。",
6
6
  "source": "guidelines/composition/layout.md, guidelines/composition/hierarchy.md, guidelines/overview.md, guidelines/components/button.md(CTA 密度ルール)を正本化",
7
7
  "roleSeparation": "screen-patterns.json はページ種別(Standard Page / Focused Workspace / ResponsiveDialog / Dialog)を選ぶための契約。composition.json は選んだ後、その骨格をどう組むか(外枠構造・余白リズム・階層)の契約。前者=何を使うか、後者=どう並べるか。",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "name": "Cake.jp Design System — Rules Contracts",
4
- "version": "0.19.0",
4
+ "version": "0.20.0",
5
5
  "description": "禁止パターン・アクセシビリティ要件・ライティングルールの構造化定義。lint-scratch.sh と check-drift.sh で自動検証。"
6
6
  },
7
7
  "prohibited": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "name": "Cake.jp Design System — Screen Patterns Contracts",
4
- "version": "0.19.0",
4
+ "version": "0.20.0",
5
5
  "description": "CRUD 機能でどの画面パターンを使うかの機械可読な判断基準。5パターンの定義・CRUD 対応・決定木・編集UIの観点比較を構造化する。人間向けの解説は guidelines/foundations/screen-patterns.md。",
6
6
  "source": "GitHub issue #37 CRUDの画面設計の型化と基本パターンの整備",
7
7
  "relatedContract": "本契約はページ種別の選択(何を使うか)を担う。選んだ後の骨格の組み方(外枠構造・余白リズム・視覚階層)は contracts/composition.json を参照する。",
@@ -1,14 +1,14 @@
1
- import { jsxs as a, jsx as e } from "react/jsx-runtime";
1
+ import { jsxs as r, jsx as e } from "react/jsx-runtime";
2
2
  import "react";
3
- import y from "iconsax-reactjs/dist/esm/ShieldTick.js";
3
+ import v from "iconsax-reactjs/dist/esm/ShieldTick.js";
4
4
  import { cn as l } from "../../lib/utils.js";
5
5
  import { Button as u } from "../foundations/button.js";
6
- import { Separator as v } from "../foundations/separator.js";
6
+ import { Separator as y } from "../foundations/separator.js";
7
7
  function T({
8
8
  className: t,
9
- lineItems: r,
10
- totalLabel: o = "合計(税込)",
11
- totalValue: d,
9
+ lineItems: a,
10
+ totalLabel: d = "合計(税込)",
11
+ totalValue: o,
12
12
  ctaLabel: c,
13
13
  onCTAClick: n,
14
14
  showSecurityBadge: p = !1,
@@ -17,8 +17,8 @@ function T({
17
17
  ctaDisabled: f = !1,
18
18
  ...i
19
19
  }) {
20
- const m = /* @__PURE__ */ a("div", { className: "space-y-2 px-4 py-3", children: [
21
- r && r.map((s) => /* @__PURE__ */ a(
20
+ const m = /* @__PURE__ */ r("div", { className: "space-y-2 px-4 py-3", children: [
21
+ a && a.map((s) => /* @__PURE__ */ r(
22
22
  "div",
23
23
  {
24
24
  className: "flex items-center justify-between typo-body-md",
@@ -29,10 +29,10 @@ function T({
29
29
  },
30
30
  s.label
31
31
  )),
32
- r && r.length > 0 && /* @__PURE__ */ e(v, {}),
33
- /* @__PURE__ */ a("div", { className: "flex items-center justify-between", children: [
34
- /* @__PURE__ */ e("span", { className: "typo-heading-md text-[var(--Text-High-Emphasis)]", children: o }),
35
- /* @__PURE__ */ e("span", { className: "typo-heading-lg text-[var(--Text-High-Emphasis)]", children: d })
32
+ a && a.length > 0 && /* @__PURE__ */ e(y, {}),
33
+ /* @__PURE__ */ r("div", { className: "flex items-center justify-between", children: [
34
+ /* @__PURE__ */ e("span", { className: "typo-heading-md text-[var(--Text-High-Emphasis)]", children: d }),
35
+ /* @__PURE__ */ e("span", { className: "typo-heading-lg text-[var(--Text-High-Emphasis)]", children: o })
36
36
  ] }),
37
37
  /* @__PURE__ */ e(
38
38
  u,
@@ -44,8 +44,8 @@ function T({
44
44
  children: c
45
45
  }
46
46
  ),
47
- p && /* @__PURE__ */ a("p", { className: "flex items-center justify-center gap-1 typo-body-sm text-[var(--Text-Low-Emphasis)]", children: [
48
- /* @__PURE__ */ e(y, { size: 12 }),
47
+ p && /* @__PURE__ */ r("p", { className: "flex items-center justify-center gap-1 typo-body-sm text-[var(--Text-Low-Emphasis)]", children: [
48
+ /* @__PURE__ */ e(v, { size: 12 }),
49
49
  h
50
50
  ] })
51
51
  ] });
@@ -65,7 +65,8 @@ function T({
65
65
  {
66
66
  "data-slot": "order-summary",
67
67
  className: l(
68
- "fixed inset-x-0 bottom-[52px] z-[var(--Z-Sticky)] mx-auto max-w-[430px] border-t border-[var(--Border-Low-Emphasis)] bg-[var(--Surface-Primary)]",
68
+ // fixed 表示はスクロール内容の上に重なるため地は Surface-Elevated(disableFixed 側はページ内フローなので Surface-Primary のまま)
69
+ "fixed inset-x-0 bottom-[52px] z-[var(--Z-Sticky)] mx-auto max-w-[430px] border-t border-[var(--Border-Low-Emphasis)] bg-[var(--Surface-Elevated)]",
69
70
  t
70
71
  ),
71
72
  ...i,
@@ -1,4 +1,4 @@
1
- import { jsxs as l, jsx as e } from "react/jsx-runtime";
1
+ import { jsx as e, jsxs as l } from "react/jsx-runtime";
2
2
  import "react";
3
3
  import { cn as m } from "../../lib/utils.js";
4
4
  import { PriceDisplay as k } from "../primitives/price-display.js";
@@ -8,7 +8,7 @@ function x({
8
8
  }) {
9
9
  return /* @__PURE__ */ e(o.Root, { "data-slot": "alert-dialog", ...a });
10
10
  }
11
- function D({
11
+ function v({
12
12
  ...a
13
13
  }) {
14
14
  return /* @__PURE__ */ e(o.Trigger, { "data-slot": "alert-dialog-trigger", ...a });
@@ -34,7 +34,7 @@ function g({
34
34
  }
35
35
  );
36
36
  }
37
- function h({
37
+ function D({
38
38
  className: a,
39
39
  size: t = "default",
40
40
  ...r
@@ -51,7 +51,7 @@ function h({
51
51
  // 画面端との余白は上下左右 40px(calc(100%-5rem))。
52
52
  // 幅上限は端末幅に応じて: SP は端末幅-80px に追従 / sm 以上は
53
53
  // default=480px(テキスト確認が間延びしない幅)・sm=320px
54
- "group/alert-dialog-content fixed top-[50%] left-[50%] z-[var(--Z-Modal)] grid w-full max-w-[calc(100%-5rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border border-[var(--Border-Medium-Emphasis)] bg-background p-6 shadow-lg duration-[var(--Motion-Duration-Base)] data-[size=sm]:max-w-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[size=default]:sm:max-w-[480px]",
54
+ "group/alert-dialog-content fixed top-[50%] left-[50%] z-[var(--Z-Modal)] grid w-full max-w-[calc(100%-5rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border border-[var(--Border-Medium-Emphasis)] bg-[var(--Surface-Elevated)] p-6 shadow-lg duration-[var(--Motion-Duration-Base)] data-[size=sm]:max-w-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[size=default]:sm:max-w-[480px]",
55
55
  a
56
56
  ),
57
57
  ...r
@@ -59,7 +59,7 @@ function h({
59
59
  )
60
60
  ] });
61
61
  }
62
- function v({
62
+ function h({
63
63
  className: a,
64
64
  ...t
65
65
  }) {
@@ -154,12 +154,12 @@ export {
154
154
  x as AlertDialog,
155
155
  w as AlertDialogAction,
156
156
  N as AlertDialogCancel,
157
- h as AlertDialogContent,
157
+ D as AlertDialogContent,
158
158
  y as AlertDialogDescription,
159
159
  A as AlertDialogFooter,
160
- v as AlertDialogHeader,
160
+ h as AlertDialogHeader,
161
161
  g as AlertDialogOverlay,
162
162
  n as AlertDialogPortal,
163
163
  z as AlertDialogTitle,
164
- D as AlertDialogTrigger
164
+ v as AlertDialogTrigger
165
165
  };