ksk-design-system 1.66.2 → 1.67.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/DESIGN.md CHANGED
@@ -165,6 +165,10 @@ KSK の必須正本・publish 依存にせず、KSK 固有の multi-theme / nati
165
165
  - **タッチターゲット**(モバイル): WCAG 2.5.5 / Apple HIG に従い主要操作(ボタン/アイコンボタン/入力/ナビ)の
166
166
  **min は 44px** 以上、推奨 48px。44 未満が避けられない **チップ(min 32px)は hitSlop**(不可視の拡張タップ領域)で
167
167
  実効 44px を確保する。値の正本は `tokens.json` の `touchTargets`。
168
+ - **タップ領域はコンポーネント所有**。見た目の寸法が 44px 未満のコントロール(Chip / pill TabsTrigger /
169
+ RadioGroupItem の 20px 円など)は、透明な `::before` 擬似要素で当たり判定だけを 44px に広げる。
170
+ 消費側で `[role="radio"] { min-height: 44px }` のようなロール単位のグローバル上書きを足してはいけない
171
+ (円が縦長に潰れ、ChipSelector など同じロールを使う他コントロールも巻き添えで壊れる。issue #470)。
168
172
 
169
173
  ## Elevation & Depth
170
174
 
package/MIGRATION.md CHANGED
@@ -53,6 +53,78 @@ npx ksk-ds check-migration ./src
53
53
 
54
54
  ## v1 系内の minor 変更(参考)
55
55
 
56
+ ### 次のリリース — `ResponsiveOverlayFrame` 追加(`BottomSheetFrame` + 消費側デスクトップ CSS からの移行 / issue #472)
57
+
58
+ `BottomSheetFrame` はモバイルの preset(`mobile-full` / `mobile-page` /
59
+ `mobile-form`)を持つ一方でデスクトップでは中央モーダルにならず、消費側は
60
+ global CSS で `position` / `transform` / `width` / `max-height` / `radius` を
61
+ `!important` 上書きしてシートをデスクトップモーダルへ変換していた
62
+ (belle-todo で 25 箇所 + 変換ルール1本)。この上書きは DS 内部の `data-slot` /
63
+ `data-side` / class 名の substring に依存するため、Sheet の内部変更で静かに壊れる。
64
+
65
+ `ResponsiveOverlayFrame` は同じ preset をモバイルで保ったまま、デスクトップでは
66
+ `DialogContent`(中央モーダル)として描画する。上書き用の CSS は不要になる。
67
+
68
+ **before**(`Sheet` + `BottomSheetFrame` + 消費側 global CSS)
69
+
70
+ ```tsx
71
+ <Sheet open={open} onOpenChange={setOpen}>
72
+ <BottomSheetFrame preset="mobile-form">
73
+ <DetailSheetScaffold header={…} footer={<KeyboardAwareSheetFooter>…</KeyboardAwareSheetFooter>}>
74
+ …
75
+ </DetailSheetScaffold>
76
+ </BottomSheetFrame>
77
+ </Sheet>
78
+ ```
79
+
80
+ ```css
81
+ /* 消費側 global CSS — 削除する */
82
+ @media (min-width: 768px) {
83
+ [data-slot="sheet-content"][data-side="bottom"]:not([data-snap]) {
84
+ position: fixed !important;
85
+ left: 50% !important;
86
+ top: 50% !important;
87
+ transform: translate(-50%, -50%) !important;
88
+ width: min(32rem, calc(100% - 3rem)) !important;
89
+ max-height: 85dvh !important;
90
+ border-radius: var(--Radius-Modal) !important;
91
+ }
92
+ }
93
+ ```
94
+
95
+ **after**
96
+
97
+ ```tsx
98
+ <ResponsiveDialog open={open} onOpenChange={setOpen} breakpoint="md">
99
+ <ResponsiveOverlayFrame preset="mobile-form" description="タスクを編集します">
100
+ <DetailSheetScaffold header={…} footer={<ResponsiveOverlayFooter>…</ResponsiveOverlayFooter>}>
101
+ …
102
+ </DetailSheetScaffold>
103
+ </ResponsiveOverlayFrame>
104
+ </ResponsiveDialog>
105
+ ```
106
+
107
+ 移行の手順:
108
+
109
+ 1. `<Sheet>` を `<ResponsiveDialog>` に、`<BottomSheetFrame>` を
110
+ `<ResponsiveOverlayFrame>` に置き換える(`preset` / `surface` はそのまま)。
111
+ 2. `<KeyboardAwareSheetFooter>` を `<ResponsiveOverlayFooter>` に置き換える
112
+ (モバイルでは従来どおりソフトキーボードに追従し、デスクトップでは静的フッタになる)。
113
+ 3. 消費側 global CSS のデスクトップ変換ルールを削除する。
114
+ 4. 切り替え境界が 768px でよければ指定不要。変えたい場合は
115
+ `breakpoint="lg"` などを指定するか、`breakpoint="product-theme"` にして
116
+ `:root { --Overlay-Desktop-Breakpoint: 1024px }` を product theme 側に置く。
117
+ 5. `<Sheet snapPoints={…}>` の snap シートは**変換しない**。`ResponsiveDialog` に
118
+ `snapPoints` を渡した場合は境界を越えてもシートのまま描画される。
119
+
120
+ `description` / `autoFocus` / `restoreFocusOnClose` / `closeOnEsc` /
121
+ `bodyScrollLock` / `zIndex` は両分岐へそのまま渡る。`container` /
122
+ `overlayClassName` / `glassOverlay` / `swipeToClose` はシート固有なので
123
+ デスクトップでは無視される。
124
+
125
+ 既存の `BottomSheetFrame` / `ResponsiveDialogContent` は非推奨ではない
126
+ (モバイル専用面・preset 不要の面では引き続きそのまま使う)。
127
+
56
128
  ### 次のリリース — `typescript` が必須依存から optional peer dependency になった(issue #409)
57
129
 
58
130
  CLI の1ルール(P046)のためだけに typescript(24MB)が全 consumer の
package/README.md CHANGED
@@ -245,6 +245,10 @@ P029 の対象外です。CLAUDE.md の実装前セルフチェックにある
245
245
  識別子・関数呼び出し・メンバーアクセスの補間(`` `bg-${color}` `` 等)や、テンプレートリテラルの
246
246
  入れ子は静的抽出できないため引き続き検出します。`cn()` / `clsx()` / CVA を使ってください。
247
247
 
248
+ `&&` は左辺が条件で出力されないため右辺だけを見ますが、`||` は左辺が truthy ならその値が
249
+ そのまま出力されるので両辺を見ます(issue #468)。したがって
250
+ `` `base ${props.className || ""}` `` は `` `base ${props.className ?? ""}` `` と同じく検出対象です。
251
+
248
252
  ### Jest(CommonJS)でコンポーネントをテストする
249
253
 
250
254
  このパッケージは **ESM-only** です。CJS との dual build は配布せず、Jest
package/bin/lint.js CHANGED
@@ -1202,7 +1202,11 @@ function splitTopLevelLogical(expr) {
1202
1202
  (candidate) => candidate.type === "&&" || candidate.type === "||",
1203
1203
  )
1204
1204
  if (!token) return null
1205
- return { rhs: expr.slice(token.index + token.type.length) }
1205
+ return {
1206
+ type: token.type,
1207
+ lhs: expr.slice(0, token.index),
1208
+ rhs: expr.slice(token.index + token.type.length),
1209
+ }
1206
1210
  }
1207
1211
 
1208
1212
  function isStringLiteralExpr(expr) {
@@ -1237,7 +1241,14 @@ export function isLiteralOnlyInterpolation(expr) {
1237
1241
  return isLiteralOnlyInterpolation(ternary.whenTrue) && isLiteralOnlyInterpolation(ternary.whenFalse)
1238
1242
  }
1239
1243
  const logical = splitTopLevelLogical(trimmed)
1240
- if (logical) return isLiteralOnlyInterpolation(logical.rhs)
1244
+ if (logical) {
1245
+ // `&&` は左辺が条件で出力されないので右辺だけ見る。
1246
+ // `||` は左辺が truthy ならそのまま出力されるため両辺を見る(issue #468)。
1247
+ // `${props.className || ""}` は右辺だけならリテラルに見えてしまい、`??` 形と
1248
+ // 判定が食い違っていた。
1249
+ if (logical.type === "&&") return isLiteralOnlyInterpolation(logical.rhs)
1250
+ return isLiteralOnlyInterpolation(logical.lhs) && isLiteralOnlyInterpolation(logical.rhs)
1251
+ }
1241
1252
  return false
1242
1253
  }
1243
1254
 
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "meta": {
3
3
  "name": "KSK Design System — Component Contracts",
4
- "version": "1.66.2",
4
+ "version": "1.67.0",
5
5
  "description": "全コンポーネントの構造化定義。バリアント・アクセシビリティ要件・使用ルールを機械可読形式で管理。",
6
6
  "counts": {
7
7
  "ui": 68,
8
- "patterns": 60,
8
+ "patterns": 61,
9
9
  "commerce": 12,
10
10
  "admin": 8,
11
11
  "shells": 3,
12
- "total": 151
12
+ "total": 152
13
13
  },
14
14
  "exportNaming": {
15
15
  "_doc": "contracts の name が `import { name } from \"ksk-design-system\"` で解決できるかを示す。__tests__/contracts-export-integrity.test.ts が src/index.ts の実 export と突き合わせて固定する。",
@@ -286,7 +286,8 @@
286
286
  "description": "ラジオボタングループ。単一選択に使用。各RadioGroupItemにLabelが必要。",
287
287
  "variants": [],
288
288
  "accessibility": [
289
- "Must pair with <Label> for each item"
289
+ "Must pair with <Label> for each item",
290
+ "RadioGroupItem は見た目 20px の円のまま ::before で 44x44px の当たり判定を内蔵する。消費側で [role=\"radio\"] のグローバル寸法上書きを追加しない(issue #470)"
290
291
  ]
291
292
  },
292
293
  {
@@ -566,7 +567,7 @@
566
567
  {
567
568
  "name": "ResponsiveDialog",
568
569
  "path": "src/components/ui/responsive-dialog.tsx",
569
- "description": "レスポンシブダイアログ。PCではDialog、モバイルではSheetに自動切り替え。useMediaQueryフック使用。",
570
+ "description": "レスポンシブダイアログ。PCではDialog、モバイルではSheetに自動切り替え。切り替え境界は breakpoint(\"sm\"|\"md\"(既定・768px)|\"lg\"|\"xl\"|\"product-theme\") または breakpointQuery(生のメディアクエリ)で指定する(issue #472)。判定結果は context で全サブコンポーネントに共有されるため、単一の境界で揃う。snapPoints を渡した場合は境界を越えてもSheetのまま。BottomSheetFrame の preset をモバイルで保ちたい場合は ResponsiveOverlayFrame を content として使う。",
570
571
  "subcomponents": [
571
572
  "ResponsiveDialogTrigger",
572
573
  "ResponsiveDialogContent",
@@ -577,7 +578,11 @@
577
578
  "ResponsiveDialogClose"
578
579
  ],
579
580
  "rules": [
580
- "Dialog on desktop, Sheet on mobile. Use useMediaQuery hook."
581
+ "Dialog on desktop, Sheet on mobile. Use useMediaQuery hook.",
582
+ "境界を 768px 以外にしたい場合は breakpoint / breakpointQuery を使う(消費側で自前の useMediaQuery 分岐を書かない)",
583
+ "breakpoint=\"product-theme\" は :root の --Overlay-Desktop-Breakpoint を読む(product theme の公開変数)",
584
+ "snap point 付きシートは中央モーダルへ変換しない(snap は掴んで高さを変える操作そのものが機能)",
585
+ "モバイルで BottomSheetFrame の preset(mobile-full / mobile-page / mobile-form)を保ちたい場合は ResponsiveDialogContent ではなく ResponsiveOverlayFrame を使う"
581
586
  ]
582
587
  },
583
588
  {
@@ -2213,6 +2218,31 @@
2213
2218
  "native (src/native/components/BottomSheetFrame.tsx): height=\"preset\"(既定・従来の固定高)|\"fitContent\"(min/max を外して内容量に追従)。maxHeight で上限だけ明示できる。safeArea(既定 false) で bottom inset 分の内側余白、handle(既定 false) で上端のつまみを表示(issue #448)"
2214
2219
  ]
2215
2220
  },
2221
+ {
2222
+ "name": "ResponsiveOverlayFrame",
2223
+ "path": "src/components/patterns/responsive-overlay-frame.tsx",
2224
+ "description": "モバイルでは BottomSheetFrame の preset をそのまま保ち、デスクトップでは DialogContent(中央モーダル)として出すレスポンシブ overlay frame(issue #472)。消費側が global CSS で position / transform / width / max-height / radius を !important 上書きしてシートをデスクトップモーダルへ変換する回避策を不要にする。切り替え境界は親の <ResponsiveDialog breakpoint> が決め、snapPoints 付きのシートは境界を越えてもシートのまま。",
2225
+ "variants": [
2226
+ "preset: mobile-full / mobile-page / mobile-form / desktop-floating",
2227
+ "surface: default / glass",
2228
+ "desktopPosition: center / top / fullscreen"
2229
+ ],
2230
+ "features": [
2231
+ "preset ごとのデスクトップ寸法写像(幅・max-height・角丸)",
2232
+ "description / autoFocus / restoreFocusOnClose / closeOnEsc / zIndex を両分岐へ透過",
2233
+ "snap sheet はデスクトップでもシートのまま",
2234
+ "ResponsiveOverlayFooter がモバイルのみキーボード追従になる"
2235
+ ],
2236
+ "rules": [
2237
+ "必ず <ResponsiveDialog> の子として使う(境界の判定は ResponsiveDialog の context が持つ)",
2238
+ "consumer 側の global CSS でシートをデスクトップモーダルへ変換しない — この frame に置き換える",
2239
+ "中身は DetailSheetScaffold + ResponsiveOverlayFooter で組む",
2240
+ "デスクトップ側にだけ効かせたい className は desktopClassName、モバイル側は mobileClassName を使う"
2241
+ ],
2242
+ "subcomponents": [
2243
+ "ResponsiveOverlayFooter"
2244
+ ]
2245
+ },
2216
2246
  {
2217
2247
  "name": "SideDrawerFrame",
2218
2248
  "path": "src/components/patterns/side-drawer-frame.tsx",
@@ -2479,7 +2509,8 @@
2479
2509
  "sliding-selection-platter",
2480
2510
  "scrollEdge",
2481
2511
  "native Expo Router tabBar adapter",
2482
- "native hidden routes"
2512
+ "native hidden routes",
2513
+ "product theme customization (--Nav-*)"
2483
2514
  ],
2484
2515
  "rules": [
2485
2516
  "Mobile only. Use safe-area-inset-bottom.",
@@ -2491,11 +2522,32 @@
2491
2522
  "Use pillPosition='absolute' only inside controlled shells or Storybook demos; fixed is the app default.",
2492
2523
  "Set maxWidth to the mobile web shell width when the app is centered in a desktop viewport.",
2493
2524
  "Use floatingPosition='left'|'right' (variant='pill' only) when a FAB is placed on the opposite side; default 'center' keeps the existing centered float.",
2494
- "Native Expo Router / React Navigation consumers should use createExpoRouterTabBar instead of local floating tab bars."
2525
+ "Native Expo Router / React Navigation consumers should use createExpoRouterTabBar instead of local floating tab bars.",
2526
+ "寸法・面のプロダクト差分は公開 CSS 変数 --Nav-*(--Nav-Pill-* / --Nav-Item-* / --Nav-Center-Action-* / --Nav-Selected-*)で表現する。contracts/product-theme-overrides.json の nav グループが正本(issue #471)。",
2527
+ "消費側は DS の内部マークアップ([data-slot=\"bottom-nav-pill\"] > :is(a, button)、span:first-child / span:last-child、[data-global-nav-add-icon])を !important や :has() で叩かない。DOM を狙う場合も公開 data-slot だけを使う。",
2528
+ "--Nav-Item-Min-Height / --Nav-Center-Action-Size は max(44px, var(...)) で参照するため、44px(HIG のタップ領域下限)より縮まない。",
2529
+ "--Nav-Center-Action-Surface / -Border / -Shadow と --Nav-Selected-Surface / -Shadow は宣言したときだけ DS 既定(.glass-accent / ガラスの選択プラッター)を置き換える。未宣言なら tone / dark の出し分けは DS 側が持つ。",
2530
+ "--Nav-Pill-* の寸法が効くのは prominent レイアウト(showLabels か centerAction がある pill)。アイコンのみの compact pill は契約外で固定。"
2495
2531
  ],
2496
2532
  "props": {
2497
2533
  "navLabel": "string (既定 \"メインナビゲーション\") — nav 要素の aria-label(issue #428)"
2498
- }
2534
+ },
2535
+ "slots": [
2536
+ "bottom-nav-pill",
2537
+ "bottom-nav-item",
2538
+ "bottom-nav-item-icon",
2539
+ "bottom-nav-item-label",
2540
+ "bottom-nav-center-action",
2541
+ "bottom-nav-center-action-icon",
2542
+ "bottom-nav-center-action-label",
2543
+ "bottom-nav-selected-surface"
2544
+ ],
2545
+ "productTheme": [
2546
+ "--Nav-Pill-*",
2547
+ "--Nav-Item-*",
2548
+ "--Nav-Center-Action-*",
2549
+ "--Nav-Selected-*"
2550
+ ]
2499
2551
  },
2500
2552
  {
2501
2553
  "name": "MobileTabBar",
@@ -2511,7 +2563,15 @@
2511
2563
  "rules": [
2512
2564
  "tabs は BottomTabBarItem を手組みせず MobileTabItem<T>[](key/label/Icon)で渡す",
2513
2565
  "addAction 指定時はブランド色丸背景 + Add アイコンが自動で centerAction になる",
2514
- "floatingPosition='left'|'right' を使う場合は FAB を反対側に併置する"
2566
+ "floatingPosition='left'|'right' を使う場合は FAB を反対側に併置する",
2567
+ "寸法・面の上書きは BottomTabBar と同じ --Nav-* を使う(MobileTabBar は薄いラッパーなので変数はそのまま効く。issue #471)",
2568
+ "[data-global-nav-add-icon] は belle-todo 互換の後方互換フックで公開契約ではない。中央アクションのアイコンは data-slot=\"bottom-nav-center-action-icon\" で狙う(issue #471)"
2569
+ ],
2570
+ "productTheme": [
2571
+ "--Nav-Pill-*",
2572
+ "--Nav-Item-*",
2573
+ "--Nav-Center-Action-*",
2574
+ "--Nav-Selected-*"
2515
2575
  ]
2516
2576
  },
2517
2577
  {
@@ -95,7 +95,8 @@
95
95
  "viewport の sm:/md:/lg: はレイアウト切替段として使う",
96
96
  "viewport xl=1280px はコンテンツ幅の上限段。ここで新しいレイアウト変形を追加しない",
97
97
  "@md: など @ prefix は container query であり、同名の viewport breakpoint と値も用途も異なる",
98
- "container query の値を変える場合は tokens.json と preset の --container-* を同時に更新する"
98
+ "container query の値を変える場合は tokens.json と preset の --container-* を同時に更新する",
99
+ "overlay(Sheet↔Dialog)の切替段は ResponsiveDialog の breakpoint prop で宣言する(既定 md=768px)。消費側 CSS のメディアクエリでシートを中央モーダルへ変換しない(issue #472)"
99
100
  ]
100
101
  },
101
102
  "formHierarchy": {
@@ -22,7 +22,8 @@
22
22
  "lintRule": "P049(npx ksk-ds lint が消費側 CSS を検査する)",
23
23
  "statusPaletteNote": "statusPalette は「消費側が DS の primitive を自分の Tailwind alias(--color-sun-800 等)の実体として使っている」ケース向けの公開段(issue #384)。@theme inline の alias は生成ユーティリティに var(--Primitive-*) が直接展開されるため、alias 側を dark で差し替えても効かず、primitive を上書きする以外の経路が無い。公開しているのは status の tint 段(-50 × 4色)と、依頼のあったアクセント段(Red-600 / Green-700)だけ。-50 は DS の .dark ブロックが一切参照していない(dark の Surface-Caution/Success/Warning/Info は -900 と実値)ので、dark 内での上書きは DS コンポーネントに対して無害。light では --Surface-Caution / --Surface-Success / --Surface-Warning / --Surface-Info を駆動するので上書きは効く。アクセント段は light / dark の両方で DS semantic を駆動するため、パレット全段ではなく依頼ベースで1段ずつ開ける。",
24
24
  "statusPaletteSideEffects": "--Primitive-Red-600 は --Caution-Base(light / dark 共通)と、dark の --Active-Destructive-Button を駆動する。dark で Red-600 を Red-500(#EF4444) より明るい値に差し替えると、destructive ボタンの active が hover より明るくなり押下の階調が反転する。差し替えるなら --Hover-Destructive-Button との明度関係を実測すること。--Primitive-Green-700 は light の --Text-Success / --Success-Base のみを駆動する(dark は Green-400 なので影響なし)。",
25
- "contrastResponsibility": "neutralPalette(--Primitive-Gray-* / White / Black)を差し替えたら、本文×背景のコントラストは消費プロダクト側の責任になる。Gray は --Text-High-Emphasis(本文色)・--Text-Medium-Emphasis・--Surface-Secondary・--Border-Low-Emphasis など semantic.css の 33 箇所を駆動しているため、差し替え後に本文テキスト×各 Surface が WCAG AA(4.5:1、大きい文字は 3:1)を満たすか必ず実測すること。DS 側の scripts/check-contrast.mjs は DS 既定パレット向けの検査で、消費側の差し替えは見ていない。light / dark の両方を確認する。"
25
+ "contrastResponsibility": "neutralPalette(--Primitive-Gray-* / White / Black)を差し替えたら、本文×背景のコントラストは消費プロダクト側の責任になる。Gray は --Text-High-Emphasis(本文色)・--Text-Medium-Emphasis・--Surface-Secondary・--Border-Low-Emphasis など semantic.css の 33 箇所を駆動しているため、差し替え後に本文テキスト×各 Surface が WCAG AA(4.5:1、大きい文字は 3:1)を満たすか必ず実測すること。DS 側の scripts/check-contrast.mjs は DS 既定パレット向けの検査で、消費側の差し替えは見ていない。light / dark の両方を確認する。",
26
+ "navSurfaceNote": "--Nav-Center-Action-Surface / -Border / -Shadow と --Nav-Selected-Surface / -Shadow は例外的に :root へ既定値を置いていない(issue #471)。DS 既定は tone=\"inverse\" / dark で出し分ける素材(.glass-accent・ガラスの選択プラッター)で、単一の :root 値に畳めないため。src/styles/bottom-nav.css が var(--Nav-*, <DS 既定>) の形で持ち、消費側が宣言したときだけ上書きされる。宣言しなければレンダリングは 1px も変わらない。"
26
27
  },
27
28
  "allowedVariables": {
28
29
  "brandColor": [
@@ -158,6 +159,24 @@
158
159
  "--Motion-Duration-Base",
159
160
  "--Motion-Duration-Slow",
160
161
  "--Motion-Duration-Slower"
162
+ ],
163
+ "nav": [
164
+ "--Nav-Pill-Min-Height",
165
+ "--Nav-Pill-Padding-X",
166
+ "--Nav-Pill-Padding-Y",
167
+ "--Nav-Pill-Gap",
168
+ "--Nav-Item-Min-Height",
169
+ "--Nav-Item-Gap",
170
+ "--Nav-Center-Action-Size",
171
+ "--Nav-Center-Action-Radius",
172
+ "--Nav-Center-Action-Surface",
173
+ "--Nav-Center-Action-Border",
174
+ "--Nav-Center-Action-Shadow",
175
+ "--Nav-Selected-Surface",
176
+ "--Nav-Selected-Shadow"
177
+ ],
178
+ "overlay": [
179
+ "--Overlay-Desktop-Breakpoint"
161
180
  ]
162
181
  },
163
182
  "dsVariableNamespaces": [
@@ -186,7 +205,8 @@
186
205
  "--Product-",
187
206
  "--Chip-",
188
207
  "--glass-",
189
- "--card-surface"
208
+ "--card-surface",
209
+ "--Nav-"
190
210
  ],
191
211
  "namespaceNote": "P049 が「DS の変数を上書きしている」と判定する接頭辞。ここに該当し、かつ DS 自身の CSS(src/preset.css / src/styles/*.css / src/themes/*.css)に実在し、allowedVariables に無い変数を消費側 CSS が宣言していたら契約違反。接頭辞は一致するが DS に実在しない名前(消費側が DS の命名に寄せて作った独自変数。例: --Surface-Inverse-Hover)は上書きではないので対象外(issue #377)。DS の CSS が読めない環境では接頭辞一致だけで判定する。shadcn 互換の小文字ブリッジ(--primary / --border 等)は消費側アプリが同名変数を持つことがあるため対象外。",
192
212
  "wiredComponents": {
@@ -196,15 +216,28 @@
196
216
  "--Control-Gap",
197
217
  "--Control-Radius"
198
218
  ],
199
- "Input": ["--Field-Height-Md", "--Field-Padding-X-Md", "--Field-Radius"],
219
+ "Input": [
220
+ "--Field-Height-Md",
221
+ "--Field-Padding-X-Md",
222
+ "--Field-Radius"
223
+ ],
200
224
  "Textarea": [
201
225
  "--Field-Min-Height",
202
226
  "--Field-Padding-X-Md",
203
227
  "--Field-Padding-Y",
204
228
  "--Field-Radius"
205
229
  ],
206
- "SelectTrigger": ["--Field-Height-*", "--Field-Padding-X-*", "--Field-Radius"],
207
- "Card": ["--Product-Card-Padding", "--Product-Card-Gap", "--Radius-Surface", "--card-surface"],
230
+ "SelectTrigger": [
231
+ "--Field-Height-*",
232
+ "--Field-Padding-X-*",
233
+ "--Field-Radius"
234
+ ],
235
+ "Card": [
236
+ "--Product-Card-Padding",
237
+ "--Product-Card-Gap",
238
+ "--Radius-Surface",
239
+ "--card-surface"
240
+ ],
208
241
  "Tabs": [
209
242
  "--Control-Height-Md",
210
243
  "--Control-Padding-X-Sm",
@@ -212,8 +245,26 @@
212
245
  "--Control-Radius",
213
246
  "--Field-Radius"
214
247
  ],
215
- "Chip": ["--Chip-Radius"],
216
- "AdminShell": ["--Product-Page-Padding-Y"]
248
+ "Chip": [
249
+ "--Chip-Radius"
250
+ ],
251
+ "AdminShell": [
252
+ "--Product-Page-Padding-Y"
253
+ ],
254
+ "BottomTabBar": [
255
+ "--Nav-Pill-*",
256
+ "--Nav-Item-*",
257
+ "--Nav-Center-Action-*",
258
+ "--Nav-Selected-*"
259
+ ],
260
+ "MobileTabBar": [
261
+ "--Nav-Pill-*",
262
+ "--Nav-Item-*",
263
+ "--Nav-Center-Action-*",
264
+ "--Nav-Selected-*"
265
+ ],
266
+ "ResponsiveDialog": ["--Overlay-Desktop-Breakpoint"],
267
+ "ResponsiveOverlayFrame": ["--Overlay-Desktop-Breakpoint"]
217
268
  },
218
269
  "aiGuidance": [
219
270
  "プロダクト差分は、まずこの契約の CSS 変数上書きで表現する。className での個別上書きを何十箇所にも散らさない。",
@@ -227,6 +278,8 @@
227
278
  "Checkbox / RadioGroup / Switch(20px の size-5 トグル)は意図的にこの契約の対象外。「コントロールの高さ」という概念が当てはまらない固定サイズのため、Control 変数を当てると意味が壊れる。",
228
279
  "Chip は角丸(--Chip-Radius)だけを配線し、高さ・横 padding は配線しない。sm/md/lg の縦 margin(my-*)は「44px タッチターゲット - 本体高さ」ちょうどの手計算値で、height を変数化すると当たり判定の math が壊れる。",
229
280
  "AppShell / MarketingShell / MobileAppShell はページ本文の padding をシェル自身が持たない(Container の gutter か呼び出し側の contentClassName に委ねている)ため product theme の対象外。AdminShell の <main> だけが --Product-Page-Padding-Y を持つ。",
230
- "--Product-Type-Scale は typo-* の font-size に一律で掛かる乗数(既定 1)。文字倍率を上げたら Control(Button)/ Field(Input 等)の高さ・padding も --Control-Height-* / --Field-Height-* 等で別途上げること。この変数はコントロールの寸法には自動連動しない(文字だけ大きくして枠が追従しないと窮屈になるため、意図的に分離してある)。Web のみが対象で React Native(native/scales.ts)には効かない。"
281
+ "--Product-Type-Scale は typo-* の font-size に一律で掛かる乗数(既定 1)。文字倍率を上げたら Control(Button)/ Field(Input 等)の高さ・padding も --Control-Height-* / --Field-Height-* 等で別途上げること。この変数はコントロールの寸法には自動連動しない(文字だけ大きくして枠が追従しないと窮屈になるため、意図的に分離してある)。Web のみが対象で React Native(native/scales.ts)には効かない。",
282
+ "BottomTabBar / MobileTabBar の寸法・面は --Nav-* で変える。DS の内部マークアップ([data-slot=\"bottom-nav-pill\"] > :is(a, button) や span:first-child / span:last-child のような子孫順、[data-global-nav-add-icon])を !important で叩かない。DOM を狙う必要が残る場合も、公開している data-slot(bottom-nav-item / -item-icon / -item-label / bottom-nav-center-action / -icon / -label / bottom-nav-selected-surface)だけを使う(issue #471)。",
283
+ "--Nav-Item-Min-Height / --Nav-Center-Action-Size は max(44px, var(...)) で参照しているため、44px(HIG のタップ領域下限)より小さくはならない。ナビを詰めたい場合も当たり判定は 44px を保つ。"
231
284
  ]
232
285
  }
@@ -470,9 +470,9 @@
470
470
  "pattern": "SheetContent[^>]*className=[^>]*(max-h-\\[(88|90|100)dvh\\]|rounded-t|safe-area-inset-bottom)|max-h-\\[(88|90|100)dvh\\][^\"']*SheetContent",
471
471
  "excludePaths": [".stories."],
472
472
  "excludeDsPaths": ["components/patterns/", "src/native/components/"],
473
- "excludeLines": ["BottomSheetFrame", "ksk-ds-allow-custom-ui"],
473
+ "excludeLines": ["BottomSheetFrame", "ResponsiveOverlayFrame", "ksk-ds-allow-custom-ui"],
474
474
  "message": "Bottom sheet outer frame class recipe のローカル再実装禁止",
475
- "fix": "<BottomSheetFrame preset=\"mobile-full|mobile-form|desktop-floating\"> を使用"
475
+ "fix": "<BottomSheetFrame preset=\"mobile-full|mobile-page|mobile-form|desktop-floating\"> を使用。PC で中央モーダルにしたい場合は <ResponsiveDialog> + <ResponsiveOverlayFrame preset=...>(消費側 CSS で position / transform / width / radius を !important 上書きしない / issue #472)"
476
476
  },
477
477
  {
478
478
  "id": "P043",
@@ -101,8 +101,8 @@
101
101
  "name": "レスポンシブダイアログ",
102
102
  "group": "overlay",
103
103
  "purpose": "現在の画面を維持したまま前面に重ねる軽量な作成/編集/詳細確認 UI。PC/モバイルでコンポーネントを出し分ける。",
104
- "dsComponents": ["ResponsiveDialog", "Dialog", "Sheet", "BottomSheetForm", "DetailSheetScaffold", "FormField"],
105
- "dsComponentsNote": "PC/モバイル自動切替が必要な場合は ResponsiveDialog を第一候補にする(PC=Dialog相当・モバイル=Sheet相当を内部で出し分ける)。フォーム系のボトムシートは BottomSheetForm、閲覧+編集の detail sheet は DetailSheetScaffold を使う。ResponsiveDialog を使わず自前で Dialog と Sheet を viewport 分岐する場合は、両方に同じ内容を実装し片方だけ更新して差分が出ないよう注意する。",
104
+ "dsComponents": ["ResponsiveDialog", "ResponsiveOverlayFrame", "Dialog", "Sheet", "BottomSheetFrame", "BottomSheetForm", "DetailSheetScaffold", "FormField"],
105
+ "dsComponentsNote": "PC/モバイル自動切替が必要な場合は ResponsiveDialog を第一候補にする(PC=Dialog相当・モバイル=Sheet相当を内部で出し分ける)。モバイル側で BottomSheetFrame の preset(mobile-full / mobile-page / mobile-form)を保ったまま PC で中央モーダルにしたい場合は、content に ResponsiveOverlayFrame を使う(issue #472。消費側 CSS で position / transform / width / radius を !important 上書きしない)。切り替え境界は ResponsiveDialog の breakpoint(既定 md=768px / lg / product-theme)で指定する。snapPoints 付きの snap シートは境界を越えてもシートのまま。フォーム系のボトムシートは BottomSheetForm、閲覧+編集の detail sheet は DetailSheetScaffold を使う。ResponsiveDialog を使わず自前で Dialog と Sheet を viewport 分岐する場合は、両方に同じ内容を実装し片方だけ更新して差分が出ないよう注意する。",
106
106
  "navigation": { "globalNav": false, "breadcrumb": false, "header": false },
107
107
  "crud": ["create", "read", "update"],
108
108
  "whenToUse": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "name": "KSK Design System — Semantic Token Hex Cache",
4
- "version": "1.66.2",
4
+ "version": "1.67.0",
5
5
  "description": "semantic / semanticDark トークン(var(--Primitive-*) 参照)を実 hex に解決したサイドカー生成物。hex はデフォルト(Blue)テーマでの解決値であり、Brand 系(meta.themeDependentKeys に列挙)はテーマ差し替え(orange/green/violet 等)で実色が変わる。テーマ別の完全解決値は `ksk-design-system/native` エクスポート(バンドル済み native トークンモジュール)の themes を参照。tokens.json 本体のスキーマは変更せず、AI がこのファイルだけで実色を把握できるようにし、primitive 値の変更による semantic 実色のドリフトを --check で機械検出する。",
6
6
  "generatedBy": "scripts/generate-token-hex-cache.mjs",
7
7
  "theme": "default",