ksk-design-system 2.8.0 → 2.9.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/AGENTS.md +33 -0
- package/CLAUDE.md +33 -0
- package/DESIGN.md +1 -1
- package/PUBLISHING.md +3 -1
- package/README.md +14 -0
- package/bin/lint.js +45 -24
- package/bin/overlay-footer-padding.js +129 -0
- package/contracts/components.json +42 -6
- package/contracts/consumer-requests.json +5 -0
- package/contracts/product-theme-overrides.json +5 -0
- package/contracts/rules.json +16 -4
- package/contracts/token-hex-cache.json +15 -3
- package/dist/class-names.js +1 -1
- package/dist/index.js +1965 -1890
- package/dist/native/ui.js +60 -28
- package/dist/{native-_H0V_r0X.js → native-BHDWkOgd.js} +90 -18
- package/dist/native.js +1 -1
- package/dist/{server-variants-DTpkG8A7.js → server-variants-C7GNdOYi.js} +28 -3
- package/dist/types/components/patterns/admin/data-table.d.ts +1 -1
- package/dist/types/components/patterns/chip.d.ts +1 -1
- package/dist/types/components/patterns/commerce/bottom-tab-bar.d.ts +4 -1
- package/dist/types/components/patterns/commerce/price-display.d.ts +1 -1
- package/dist/types/components/patterns/responsive-overlay-frame.d.ts +11 -1
- package/dist/types/components/patterns/sidebar-nav.d.ts +59 -0
- package/dist/types/components/patterns/tag.d.ts +1 -1
- package/dist/types/components/ui/accordion.d.ts +12 -3
- package/dist/types/components/ui/badge.d.ts +1 -1
- package/dist/types/components/ui/button.d.ts +7 -0
- package/dist/types/components/ui/card.d.ts +1 -1
- package/dist/types/components/ui/container.d.ts +1 -1
- package/dist/types/components/ui/section.d.ts +1 -1
- package/dist/types/components/ui/select.d.ts +1 -1
- package/dist/types/components/ui/toast.d.ts +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lib/server-variants/button-variants.d.ts +2 -20
- package/dist/types/lib/server-variants/unstyled.d.ts +1 -1
- package/dist/types/native/components/Alert.d.ts +13 -1
- package/dist/types/native/components/PillToggle.d.ts +7 -0
- package/dist/types/native/components/ResponsiveDialog.d.ts +6 -1
- package/dist/types/tokens/native/themes.d.ts +84 -12
- package/package.json +2 -1
- package/src/components/COMPONENT_LOOKUP.md +4 -3
- package/src/styles/semantic.css +22 -2
- package/src/styles/source-safelist.css +13 -5
- package/tokens.json +15 -3
package/AGENTS.md
CHANGED
|
@@ -179,6 +179,19 @@ afterEach フックが全ストーリー(tags フィルタなし)に対し
|
|
|
179
179
|
(AA 未達トークンの darken と opacity 減衰の廃止により全ストーリー通過。
|
|
180
180
|
トークンペアの正本チェックは `scripts/check-contrast.mjs`)。CI では常時実行される。
|
|
181
181
|
|
|
182
|
+
**Button など、フォーカス表示の `outline-none` / `outline-hidden` 系クラスを触ったら `npm run test:forced-colors` も実行:**
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npm run test:forced-colors
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
強制配色(forced-colors / Windows ハイコントラスト等)モードでのフォーカス表示を
|
|
189
|
+
chromium 実機で検証する(issue #620)。axe-core はフォーカスリングの視認性を
|
|
190
|
+
検証できないため、CDP で forced-colors を有効化し、`outline-hidden` を使う
|
|
191
|
+
コンポーネント(対象例: Button)がキーボードフォーカス時に outline を出すことを
|
|
192
|
+
`__tests__/browser/` の専用テストで確認する。設定は `vitest.forced-colors.config.ts`。
|
|
193
|
+
CI では常時実行される。
|
|
194
|
+
|
|
182
195
|
エラーが出た場合は修正してから次に進むこと。
|
|
183
196
|
|
|
184
197
|
---
|
|
@@ -309,6 +322,9 @@ npm run test:a11y
|
|
|
309
322
|
|
|
310
323
|
# 旧 Radix 固定の回帰テスト(consumer が古い Radix を掴む条件を再現。issue #516)
|
|
311
324
|
npm run test:legacy-radix
|
|
325
|
+
|
|
326
|
+
# 強制配色モードのフォーカス表示検証(chromium の CDP で forced-colors を再現。issue #620)
|
|
327
|
+
npm run test:forced-colors
|
|
312
328
|
```
|
|
313
329
|
|
|
314
330
|
**`Closes #N` 誤リンク監査について(issue #486 が由来):**
|
|
@@ -355,6 +371,23 @@ npm run test:legacy-radix
|
|
|
355
371
|
`eslint/icon-button-aria-label.js`(`ksk-a11y/icon-button-aria-label`)で lint 時に検出する
|
|
356
372
|
(`.stories.tsx` は対象外)。
|
|
357
373
|
|
|
374
|
+
**forced-colors(強制配色)テストについて(issue #620):**
|
|
375
|
+
|
|
376
|
+
- axe-core はフォーカスリングが実際に見えるかまでは検証しない。DS のフォーカス表示は
|
|
377
|
+
`focus-visible:ring-*`(box-shadow)だが、box-shadow は forced-colors モードで
|
|
378
|
+
描画されないため、`focus-visible:outline-hidden` を併用し、forced-colors 時だけ
|
|
379
|
+
`outline: 2px solid transparent` を出して OS 側の枠に委ねる(`outline-none` では
|
|
380
|
+
このメディアクエリ自体が生成されず枠が消えたままになる)。
|
|
381
|
+
- `__tests__/browser/` に置いた専用テストが CDP (`Emulation.setEmulatedMedia`) で
|
|
382
|
+
forced-colors を有効化し、Tab フォーカス後の `outlineStyle` が `none` でないことを
|
|
383
|
+
chromium 実機で確認する。設定は `vitest.forced-colors.config.ts`(`vitest.config.ts`
|
|
384
|
+
の既定テストからは `__tests__/browser/**` を除外し、`npm run test:forced-colors` で
|
|
385
|
+
個別に実行する)。
|
|
386
|
+
- DS コンポーネント本体で `outline-none`(前置バリアント付き含む)の新規追加が
|
|
387
|
+
無いことは `__tests__/focus-ring-outline-pairing.test.ts` の機械検証(`npm test`)で
|
|
388
|
+
検出する。consumer 向けの `outline-none` 検出は `contracts/rules.json` P023(`bin/lint.js`)
|
|
389
|
+
と `scripts/lint-scratch.sh` W7 が担う。
|
|
390
|
+
|
|
358
391
|
**旧 Radix 回帰テストについて(issue #516):**
|
|
359
392
|
|
|
360
393
|
- DS の node_modules は ref churn 修正済みの Radix を解決するため、`npm test` では
|
package/CLAUDE.md
CHANGED
|
@@ -159,6 +159,19 @@ afterEach フックが全ストーリー(tags フィルタなし)に対し
|
|
|
159
159
|
(AA 未達トークンの darken と opacity 減衰の廃止により全ストーリー通過。
|
|
160
160
|
トークンペアの正本チェックは `scripts/check-contrast.mjs`)。CI では常時実行される。
|
|
161
161
|
|
|
162
|
+
**Button など、フォーカス表示の `outline-none` / `outline-hidden` 系クラスを触ったら `npm run test:forced-colors` も実行:**
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npm run test:forced-colors
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
強制配色(forced-colors / Windows ハイコントラスト等)モードでのフォーカス表示を
|
|
169
|
+
chromium 実機で検証する(issue #620)。axe-core はフォーカスリングの視認性を
|
|
170
|
+
検証できないため、CDP で forced-colors を有効化し、`outline-hidden` を使う
|
|
171
|
+
コンポーネント(対象例: Button)がキーボードフォーカス時に outline を出すことを
|
|
172
|
+
`__tests__/browser/` の専用テストで確認する。設定は `vitest.forced-colors.config.ts`。
|
|
173
|
+
CI では常時実行される。
|
|
174
|
+
|
|
162
175
|
エラーが出た場合は修正してから次に進むこと。
|
|
163
176
|
|
|
164
177
|
---
|
|
@@ -309,6 +322,9 @@ npm run test:a11y
|
|
|
309
322
|
|
|
310
323
|
# 旧 Radix 固定の回帰テスト(consumer が古い Radix を掴む条件を再現。issue #516)
|
|
311
324
|
npm run test:legacy-radix
|
|
325
|
+
|
|
326
|
+
# 強制配色モードのフォーカス表示検証(chromium の CDP で forced-colors を再現。issue #620)
|
|
327
|
+
npm run test:forced-colors
|
|
312
328
|
```
|
|
313
329
|
|
|
314
330
|
**`Closes #N` 誤リンク監査について(issue #486 が由来):**
|
|
@@ -355,6 +371,23 @@ npm run test:legacy-radix
|
|
|
355
371
|
`eslint/icon-button-aria-label.js`(`ksk-a11y/icon-button-aria-label`)で lint 時に検出する
|
|
356
372
|
(`.stories.tsx` は対象外)。
|
|
357
373
|
|
|
374
|
+
**forced-colors(強制配色)テストについて(issue #620):**
|
|
375
|
+
|
|
376
|
+
- axe-core はフォーカスリングが実際に見えるかまでは検証しない。DS のフォーカス表示は
|
|
377
|
+
`focus-visible:ring-*`(box-shadow)だが、box-shadow は forced-colors モードで
|
|
378
|
+
描画されないため、`focus-visible:outline-hidden` を併用し、forced-colors 時だけ
|
|
379
|
+
`outline: 2px solid transparent` を出して OS 側の枠に委ねる(`outline-none` では
|
|
380
|
+
このメディアクエリ自体が生成されず枠が消えたままになる)。
|
|
381
|
+
- `__tests__/browser/` に置いた専用テストが CDP (`Emulation.setEmulatedMedia`) で
|
|
382
|
+
forced-colors を有効化し、Tab フォーカス後の `outlineStyle` が `none` でないことを
|
|
383
|
+
chromium 実機で確認する。設定は `vitest.forced-colors.config.ts`(`vitest.config.ts`
|
|
384
|
+
の既定テストからは `__tests__/browser/**` を除外し、`npm run test:forced-colors` で
|
|
385
|
+
個別に実行する)。
|
|
386
|
+
- DS コンポーネント本体で `outline-none`(前置バリアント付き含む)の新規追加が
|
|
387
|
+
無いことは `__tests__/focus-ring-outline-pairing.test.ts` の機械検証(`npm test`)で
|
|
388
|
+
検出する。consumer 向けの `outline-none` 検出は `contracts/rules.json` P023(`bin/lint.js`)
|
|
389
|
+
と `scripts/lint-scratch.sh` W7 が担う。
|
|
390
|
+
|
|
358
391
|
**旧 Radix 回帰テストについて(issue #516):**
|
|
359
392
|
|
|
360
393
|
- DS の node_modules は ref churn 修正済みの Radix を解決するため、`npm test` では
|
package/DESIGN.md
CHANGED
|
@@ -335,7 +335,7 @@ Portal に載る要素(Dialog / Sheet / Popover / Toast 等)は DOM 上の
|
|
|
335
335
|
|
|
336
336
|
代表例(トークン参照で構成)。
|
|
337
337
|
|
|
338
|
-
- **Button**: bg `{colors.primary}` / text 白 / `rounded-full` / 高さ 48px。variant: default·secondary·tertiary·ghost·destructive·link
|
|
338
|
+
- **Button**: bg `{colors.primary}` / text 白 / `rounded-full` / 高さ 48px。variant: default·secondary·tertiary·ghost·destructive·info·warning·success·link(info・warning・success は状態色。不可逆操作は destructive)。
|
|
339
339
|
- **Card**: bg `{colors.surface}` + 1px `{colors.border}` + `{elevation.md}` + `{rounded.surface}`。
|
|
340
340
|
- **Dialog(中央モーダル)**= タスク面(フォーム/絞り込み)。`role="dialog"`、`{rounded.modal}`。
|
|
341
341
|
- **AlertDialog / ConfirmDialog(割り込み確認)**= 削除等の"止める"操作。`role="alertdialog"`、背景タップで閉じない・中央表示。
|
package/PUBLISHING.md
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
> 一本化しており、`main` への version 変更 push が公開の唯一のトリガー。
|
|
8
8
|
> 各消費リポジトリの `package.json` は `ksk-design-system@X.Y.Z` を参照する。
|
|
9
9
|
> 対象の消費リポ一覧は `scripts/update-consumers.sh` の `DEFAULT_REPOS` が正本
|
|
10
|
-
> (単体リポ・monorepo
|
|
10
|
+
> (単体リポ・monorepo が混在する。ExamKit 本体と資格アプリは含めず、ExamKit で
|
|
11
|
+
> `npm i ksk-design-system@X.Y.Z --save-exact` → `kit:bump` → `kit:rollout` の順で届ける。
|
|
12
|
+
> 資格アプリは DS を ExamKit と同じ版に完全固定しているため、`^X.Y.Z` を書く一括 bump を流すと版がずれる)。
|
|
11
13
|
|
|
12
14
|
## 前提
|
|
13
15
|
|
package/README.md
CHANGED
|
@@ -285,6 +285,20 @@ DS 名前空間に属さないカスタムプロパティへ色値(`#hex` / `r
|
|
|
285
285
|
ように DS トークンを参照しているだけのものはカウントしません。意図的な独立パレット
|
|
286
286
|
(LINE 風チャット画面のライトなど)は `// ksk-ds-lint-ignore P050 -- 理由` で抑制できます。
|
|
287
287
|
|
|
288
|
+
#### P051: ResponsiveOverlayFrame + Footer の `padding={false}` 付け忘れ(issue #619)
|
|
289
|
+
|
|
290
|
+
`preset="plain"` / `side="float"` / `side="float-glass"` の `ResponsiveOverlayFrame` は、
|
|
291
|
+
既定で `p-6` と safe-area 込みの下余白を持ちます。`ResponsiveOverlayFooter` も自前で
|
|
292
|
+
safe-area 込みの下余白を持つため、併用するときは Frame に `padding={false}` が必要です。
|
|
293
|
+
付け忘れると下の余白が二重になるので、P051 が warn します。
|
|
294
|
+
|
|
295
|
+
誤検知を出さない方を優先し、`side` / `preset` が文字列リテラルで確定していて、Frame に
|
|
296
|
+
spread props(`{...props}`)が無く、`padding` が未指定・値なし・`{true}` で、Footer が
|
|
297
|
+
Frame の JSX の中に直接書かれているときだけ報告します。`padding={cond}` のような式、
|
|
298
|
+
Footer を別コンポーネントに切り出した構成、preset 経路(`mobile-form` 等。余白を持たない)は
|
|
299
|
+
対象外です。P046 と同じく TypeScript の構文解析を使うため、`typescript` が無いプロジェクトでは
|
|
300
|
+
P051 だけ skip して案内を1行出します。
|
|
301
|
+
|
|
288
302
|
#### ルールごとの除外(rules.json の excludes 系)
|
|
289
303
|
|
|
290
304
|
`contracts/rules.json` の除外指定は、当てる対象ごとに 3 つに分かれています。
|
package/bin/lint.js
CHANGED
|
@@ -7,19 +7,39 @@ import {
|
|
|
7
7
|
loadProductThemeContract,
|
|
8
8
|
} from "./product-theme-override.js"
|
|
9
9
|
|
|
10
|
-
// card-child-spacing.js は `import ts from "typescript"`
|
|
11
|
-
// typescript は 24MB 前後あり、
|
|
12
|
-
//
|
|
10
|
+
// card-child-spacing.js / overlay-footer-padding.js は `import ts from "typescript"` している
|
|
11
|
+
// (P046 / P051 専用エンジン)。typescript は 24MB 前後あり、AST が要るのはこの2ルールだけなので、
|
|
12
|
+
// 該当ルールが実際に有効なとき(rules.json に該当ルールがあり、対象ファイルも存在するとき)
|
|
13
13
|
// だけ動的 import する(issue #409)。typescript が未インストールの consumer では
|
|
14
|
-
// ERR_MODULE_NOT_FOUND
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (!
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
// ERR_MODULE_NOT_FOUND で落とさず、該当ルールだけを skip して案内を1行出す。
|
|
15
|
+
const tsEngineModulePromises = new Map()
|
|
16
|
+
function loadTsEngine(specifier) {
|
|
17
|
+
if (!tsEngineModulePromises.has(specifier)) {
|
|
18
|
+
tsEngineModulePromises.set(
|
|
19
|
+
specifier,
|
|
20
|
+
import(specifier).catch((error) => {
|
|
21
|
+
return { __unavailable: true, error }
|
|
22
|
+
}),
|
|
23
|
+
)
|
|
21
24
|
}
|
|
22
|
-
return
|
|
25
|
+
return tsEngineModulePromises.get(specifier)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* TypeScript AST を使うエンジン。engine 名 → { module, export, label }。
|
|
30
|
+
* label は typescript 不在で skip するときの案内文に使う。
|
|
31
|
+
*/
|
|
32
|
+
const TS_ENGINES = {
|
|
33
|
+
"card-direct-child-spacing": {
|
|
34
|
+
module: "./card-child-spacing.js",
|
|
35
|
+
exportName: "inspectCardChildSpacing",
|
|
36
|
+
label: "P046(Card 直下の子要素スペーシング)",
|
|
37
|
+
},
|
|
38
|
+
"overlay-footer-padding": {
|
|
39
|
+
module: "./overlay-footer-padding.js",
|
|
40
|
+
exportName: "inspectOverlayFooterPadding",
|
|
41
|
+
label: "P051(ResponsiveOverlayFrame + Footer の padding={false} 付け忘れ)",
|
|
42
|
+
},
|
|
23
43
|
}
|
|
24
44
|
|
|
25
45
|
const DEFAULT_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx"])
|
|
@@ -369,20 +389,20 @@ export async function runLintCli(argv, { cwd = process.cwd(), pkgRoot = resolve(
|
|
|
369
389
|
// DS 自身 / ベンダリングされた DS の CSS を P049 の対象から外すための材料(issue #407)
|
|
370
390
|
const dsCssIdentity = cssRules.length > 0 ? collectDsCssIdentity(pkgRoot) : null
|
|
371
391
|
|
|
372
|
-
// P046
|
|
373
|
-
// 未インストール環境では ERR_MODULE_NOT_FOUND
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
const mod = await
|
|
392
|
+
// TypeScript AST エンジン(P046 / P051)は有効なときだけ typescript を動的 import する。
|
|
393
|
+
// 未インストール環境では ERR_MODULE_NOT_FOUND で落とさず、該当ルールだけ skip して1行案内する(issue #409)。
|
|
394
|
+
const tsInspectors = {}
|
|
395
|
+
for (const [engine, spec] of Object.entries(TS_ENGINES)) {
|
|
396
|
+
if (!sourceRules.some((rule) => rule.engine === engine)) continue
|
|
397
|
+
const mod = await loadTsEngine(spec.module)
|
|
378
398
|
if (mod.__unavailable) {
|
|
379
399
|
console.log(
|
|
380
|
-
`[ksk-ds lint] typescript が見つからないため
|
|
400
|
+
`[ksk-ds lint] typescript が見つからないため ${spec.label} を skip します。` +
|
|
381
401
|
`devDependencies に typescript を追加すると検査対象になります。`,
|
|
382
402
|
)
|
|
383
|
-
sourceRules = sourceRules.filter((rule) => rule.engine !==
|
|
403
|
+
sourceRules = sourceRules.filter((rule) => rule.engine !== engine)
|
|
384
404
|
} else {
|
|
385
|
-
|
|
405
|
+
tsInspectors[engine] = mod[spec.exportName]
|
|
386
406
|
}
|
|
387
407
|
}
|
|
388
408
|
|
|
@@ -422,7 +442,7 @@ export async function runLintCli(argv, { cwd = process.cwd(), pkgRoot = resolve(
|
|
|
422
442
|
continue
|
|
423
443
|
}
|
|
424
444
|
findings.push(
|
|
425
|
-
...lintFile(file, cwd, sourceRules, { ...options,
|
|
445
|
+
...lintFile(file, cwd, sourceRules, { ...options, tsInspectors, pkgRoot }),
|
|
426
446
|
)
|
|
427
447
|
} catch (error) {
|
|
428
448
|
if (error && error.code === "ENOENT") {
|
|
@@ -781,9 +801,10 @@ function lintFile(file, cwd, rules, options = {}) {
|
|
|
781
801
|
|
|
782
802
|
for (const rule of rules) {
|
|
783
803
|
if (!ruleAppliesTo(rule, { capabilities, filePath: rel })) continue
|
|
784
|
-
if (rule.engine
|
|
785
|
-
|
|
786
|
-
|
|
804
|
+
if (rule.engine && Object.hasOwn(TS_ENGINES, rule.engine)) {
|
|
805
|
+
const inspect = options.tsInspectors?.[rule.engine]
|
|
806
|
+
if (!inspect) continue
|
|
807
|
+
for (const finding of inspect(source, file)) {
|
|
787
808
|
const line = lines[finding.line - 1] ?? ""
|
|
788
809
|
if (ignores.suppresses(rule.id ?? "UNKNOWN", finding.line)) continue
|
|
789
810
|
if (!matchesRuleExclude(rule, { file: rel, line, isDsFile })) {
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import ts from "typescript"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* P051: ResponsiveOverlayFrame + ResponsiveOverlayFooter の padding={false} 付け忘れ(issue #619)
|
|
5
|
+
*
|
|
6
|
+
* `preset="plain"` / `side="float" | "float-glass"` の ResponsiveOverlayFrame は
|
|
7
|
+
* 既定で `p-6` と safe-area 込みの下余白を持つ(#599 / #613)。
|
|
8
|
+
* ResponsiveOverlayFooter も自前で `pb-[max(1rem,env(safe-area-inset-bottom))]` を
|
|
9
|
+
* 持つため、Frame 側を `padding={false}` にしないと下余白が二重になる。
|
|
10
|
+
*
|
|
11
|
+
* 誤検知を出さない方を優先し、静的に確定できるときだけ報告する:
|
|
12
|
+
* - Frame の `side` / `preset` が文字列リテラルで、plain / float 系だと確定している
|
|
13
|
+
* - Frame に spread props(`{...props}`)が無い(padding が来うるため)
|
|
14
|
+
* - `padding` が未指定・値なし(`padding`)・`{true}` のいずれか(`{cond}` 等の式は対象外)
|
|
15
|
+
* - Footer が Frame の JSX 子孫に直接書かれている(別コンポーネントへの切り出しは追えない)
|
|
16
|
+
*
|
|
17
|
+
* preset 経路(mobile-form 等 / BottomSheetFrame 経由)は padding を受け付けず、
|
|
18
|
+
* 余白も持たないので対象外。
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const FRAME = "ResponsiveOverlayFrame"
|
|
22
|
+
const FOOTER = "ResponsiveOverlayFooter"
|
|
23
|
+
const FLOAT_SIDES = new Set(["float", "float-glass"])
|
|
24
|
+
|
|
25
|
+
/** `Foo` / `ns.Foo` の末尾の名前 */
|
|
26
|
+
function tagBaseName(tagName) {
|
|
27
|
+
if (ts.isIdentifier(tagName)) return tagName.text
|
|
28
|
+
if (ts.isPropertyAccessExpression(tagName)) return tagName.name.text
|
|
29
|
+
return ""
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function openingOf(node) {
|
|
33
|
+
if (ts.isJsxElement(node)) return node.openingElement
|
|
34
|
+
if (ts.isJsxSelfClosingElement(node)) return node
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const UNKNOWN = Symbol("unknown")
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 属性値を静的に読む。
|
|
42
|
+
* - 属性なし → undefined
|
|
43
|
+
* - 値なし(`padding`)→ true
|
|
44
|
+
* - 文字列 / true / false リテラル → その値
|
|
45
|
+
* - それ以外の式 → UNKNOWN
|
|
46
|
+
*/
|
|
47
|
+
function readAttribute(opening, name) {
|
|
48
|
+
const attribute = opening.attributes.properties.find(
|
|
49
|
+
(property) => ts.isJsxAttribute(property) && property.name.getText() === name,
|
|
50
|
+
)
|
|
51
|
+
if (!attribute) return undefined
|
|
52
|
+
const init = attribute.initializer
|
|
53
|
+
if (!init) return true
|
|
54
|
+
if (ts.isStringLiteral(init)) return init.text
|
|
55
|
+
if (ts.isJsxExpression(init) && init.expression) {
|
|
56
|
+
const expression = init.expression
|
|
57
|
+
if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) {
|
|
58
|
+
return expression.text
|
|
59
|
+
}
|
|
60
|
+
if (expression.kind === ts.SyntaxKind.TrueKeyword) return true
|
|
61
|
+
if (expression.kind === ts.SyntaxKind.FalseKeyword) return false
|
|
62
|
+
}
|
|
63
|
+
return UNKNOWN
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** padding を持つ経路(plain / float 系)だと静的に確定しているか */
|
|
67
|
+
function isPaddedVariant(opening) {
|
|
68
|
+
const side = readAttribute(opening, "side")
|
|
69
|
+
if (side === UNKNOWN) return false
|
|
70
|
+
if (typeof side === "string" && FLOAT_SIDES.has(side)) return true
|
|
71
|
+
if (side !== undefined && side !== "bottom") return false
|
|
72
|
+
return readAttribute(opening, "preset") === "plain"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function hasSpread(opening) {
|
|
76
|
+
return opening.attributes.properties.some((property) => ts.isJsxSpreadAttribute(property))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Frame の子孫に Footer が書かれているか(入れ子の Frame の中は数えない) */
|
|
80
|
+
function containsFooter(frame) {
|
|
81
|
+
let found = false
|
|
82
|
+
function visit(node) {
|
|
83
|
+
if (found) return
|
|
84
|
+
const opening = openingOf(node)
|
|
85
|
+
if (opening) {
|
|
86
|
+
const name = tagBaseName(opening.tagName)
|
|
87
|
+
if (name === FOOTER) {
|
|
88
|
+
found = true
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
// 入れ子の Frame は自分自身として別途判定される
|
|
92
|
+
if (name === FRAME) return
|
|
93
|
+
}
|
|
94
|
+
ts.forEachChild(node, visit)
|
|
95
|
+
}
|
|
96
|
+
for (const child of frame.children) visit(child)
|
|
97
|
+
return found
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function inspectOverlayFooterPadding(source, filePath = "source.tsx") {
|
|
101
|
+
const sourceFile = ts.createSourceFile(
|
|
102
|
+
filePath,
|
|
103
|
+
source,
|
|
104
|
+
ts.ScriptTarget.Latest,
|
|
105
|
+
true,
|
|
106
|
+
ts.ScriptKind.TSX,
|
|
107
|
+
)
|
|
108
|
+
const findings = []
|
|
109
|
+
|
|
110
|
+
function walk(node) {
|
|
111
|
+
if (ts.isJsxElement(node) && tagBaseName(node.openingElement.tagName) === FRAME) {
|
|
112
|
+
const opening = node.openingElement
|
|
113
|
+
const padding = readAttribute(opening, "padding")
|
|
114
|
+
if (
|
|
115
|
+
isPaddedVariant(opening) &&
|
|
116
|
+
!hasSpread(opening) &&
|
|
117
|
+
(padding === undefined || padding === true) &&
|
|
118
|
+
containsFooter(node)
|
|
119
|
+
) {
|
|
120
|
+
const position = sourceFile.getLineAndCharacterOfPosition(opening.getStart(sourceFile))
|
|
121
|
+
findings.push({ line: position.line + 1, tag: FRAME })
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
ts.forEachChild(node, walk)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
walk(sourceFile)
|
|
128
|
+
return findings
|
|
129
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"name": "KSK Design System — Component Contracts",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.9.0",
|
|
5
5
|
"description": "全コンポーネントの構造化定義。バリアント・アクセシビリティ要件・使用ルールを機械可読形式で管理。",
|
|
6
6
|
"counts": {
|
|
7
7
|
"ui": 69,
|
|
8
|
-
"patterns":
|
|
8
|
+
"patterns": 62,
|
|
9
9
|
"commerce": 12,
|
|
10
10
|
"admin": 8,
|
|
11
11
|
"shells": 3,
|
|
12
|
-
"total":
|
|
12
|
+
"total": 154
|
|
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 と突き合わせて固定する。",
|
|
@@ -42,6 +42,9 @@
|
|
|
42
42
|
"ghost",
|
|
43
43
|
"link",
|
|
44
44
|
"destructive",
|
|
45
|
+
"info",
|
|
46
|
+
"warning",
|
|
47
|
+
"success",
|
|
45
48
|
"glass",
|
|
46
49
|
"glass-inverse",
|
|
47
50
|
"glass-accent",
|
|
@@ -78,10 +81,12 @@
|
|
|
78
81
|
"Icon-only buttons require aria-label",
|
|
79
82
|
"Native Button supports loading/loadingLabel and leadingIcon/trailingIcon; do not wrap ActivityIndicator in Text",
|
|
80
83
|
"Native Button supports spinnerColor to override the loading ActivityIndicator color; default unchanged (falls back to variant fg color, issue #450)",
|
|
84
|
+
"variant=info / warning / success は状態色で種類や意味を伝えるボタン(issue #598)。背景色を className で上書きせず variant を使う。不可逆操作には warning ではなく destructive を使う",
|
|
81
85
|
"variant=glass は背景がグラデーション/写真の場合のみ使用",
|
|
82
86
|
"variant=glass-inverse は暗い写真・動画・濃色面上の Liquid Glass CTA 専用",
|
|
83
87
|
"variant=inverse / ghost-inverse は暗背景・ヒーローセクション専用",
|
|
84
88
|
"size=hero は hero / final-CTA 専用(ピル型 56px)",
|
|
89
|
+
"size=xs / sm / icon-sm は透明な before 擬似要素で当たり判定を 44×44px に広げる(見た目の寸法は不変・issue #601)。隣と重ねないには見た目の端同士を sm / icon-sm で gap-3、xs で gap-5 以上空ける。glass 系 variant と overflow-hidden を足したボタンでは拡張されない",
|
|
85
90
|
"既存の手書き CSS を持つ画面の段階移行では unstyled を使う(variant / size は無視され、focus ring 以外の視覚クラスを出さない)"
|
|
86
91
|
]
|
|
87
92
|
},
|
|
@@ -494,12 +499,19 @@
|
|
|
494
499
|
{
|
|
495
500
|
"name": "Accordion",
|
|
496
501
|
"path": "src/components/ui/accordion.tsx",
|
|
497
|
-
"description": "アコーディオン。FAQ・設定項目など折りたたみコンテンツに使用。",
|
|
502
|
+
"description": "アコーディオン。FAQ・設定項目など折りたたみコンテンツに使用。size=\"lg\" で LP の「よくある質問」のような大きめの見せ方にできる(issue #600)。",
|
|
498
503
|
"subcomponents": [
|
|
499
504
|
"AccordionItem",
|
|
500
505
|
"AccordionTrigger",
|
|
501
506
|
"AccordionContent"
|
|
502
|
-
]
|
|
507
|
+
],
|
|
508
|
+
"sizes": [
|
|
509
|
+
"default",
|
|
510
|
+
"lg"
|
|
511
|
+
],
|
|
512
|
+
"props": {
|
|
513
|
+
"size": "\"default\" | \"lg\" — Accordion ルートで指定すると context 経由で Trigger/Content へ伝わる(既定 \"default\")。Trigger/Content に直接渡すと個別上書きできる。lg は Trigger: typo-label-lg py-6 / Content: typo-body-lg"
|
|
514
|
+
}
|
|
503
515
|
},
|
|
504
516
|
{
|
|
505
517
|
"name": "Separator",
|
|
@@ -2312,6 +2324,8 @@
|
|
|
2312
2324
|
"preset ごとのデスクトップ寸法写像(幅・max-height・角丸)",
|
|
2313
2325
|
"side=float / float-glass のデスクトップ写像(32rem / min(85dvh,46rem))。float 系は preset ではなく padding が効く",
|
|
2314
2326
|
"preset=\"plain\" は BottomSheetFrame を通さない素の bottom シート(全幅・下端固定・p-6・max-h-90dvh のまま)で、デスクトップだけ中央モーダル(32rem / min(90dvh,46rem))になる。padding が効く(issue #486)",
|
|
2327
|
+
"モバイル分岐の下余白に home indicator の safe-area を含める(issue #599)。plain は pb = 1.5rem + env(safe-area-inset-bottom)、float 系は面が下端から 12px 浮いているので pb = max(1.5rem, env(safe-area-inset-bottom))。padding={false} / safeArea={false} / キーボード表示中は足さない。デスクトップ分岐と preset 経路は変えない",
|
|
2328
|
+
"モバイルの side=float / float-glass は既定で max-height: min(85dvh, 100dvh - 1.5rem - 上部safe-area)(lg 以上は min(85dvh,46rem))を持ち、超えた分は面の中でスクロールする(issue #599)。キーボード表示中は #337 の inline style が優先",
|
|
2315
2329
|
"description / autoFocus / restoreFocusOnClose / closeOnEsc / zIndex を両分岐へ透過",
|
|
2316
2330
|
"snap sheet はデスクトップでもシートのまま",
|
|
2317
2331
|
"ResponsiveOverlayFooter がモバイルのみキーボード追従になる",
|
|
@@ -2326,7 +2340,8 @@
|
|
|
2326
2340
|
"side / preset ごとに効く prop が判別ユニオンで分かれている: preset 経路は preset / surface / desktopPosition、float 系と preset=\"plain\" は padding。効かない組み合わせは黙って無視されずコンパイルエラーになる(型テスト test/types/responsive-overlay-frame.tsx)",
|
|
2327
2341
|
"デスクトップ分岐のキーボード補正は max-height だけで lift(bottom)は当てない。DialogContent は top:50% + translate-y:-50% で位置決めしているため bottom を足すと両拘束になり高さが縦一杯へ伸びる。中央寄せのキャップは 100dvh - 2*kb(100dvh - kb では下端がキーボードへ潜り込む。desktopPosition=\"top\" / \"fullscreen\" は上端固定なのでキーボード 1 回分 / #487)",
|
|
2328
2342
|
"3 つのデスクトップ分岐(preset / plain / float 系)はすべて data-frame=\"responsive-overlay-frame\" + data-side + data-position を出す。consumer が独自のキーボード補正を当てる場合もこの属性で絞る(#487)",
|
|
2329
|
-
"float-glass の実スクロールは src/styles/sheet-keyboard.css の :is(sheet-content, dialog-content)[data-side=\"float-glass\"] が担う。.glass-specular の overflow:hidden が非レイヤー CSS で className を踏み潰すため、className だけでは効かない(#337 / #479)"
|
|
2343
|
+
"float-glass の実スクロールは src/styles/sheet-keyboard.css の :is(sheet-content, dialog-content)[data-side=\"float-glass\"] が担う。.glass-specular の overflow:hidden が非レイヤー CSS で className を踏み潰すため、className だけでは効かない(#337 / #479)",
|
|
2344
|
+
"モバイルの下余白(safe-area)や float の最大高さを consumer 側で pb-8 / max-h-[85dvh] overflow-y-auto と書き足さない(#599 で既定化)。値を変えたいときだけ className / mobileClassName の pb-* / max-h-* で上書きする(twMerge で既定と置き換わり二重にならない)。ResponsiveOverlayFooter を使う面は padding={false} にする(フッタ自身が safe-area を持つ。付け忘れは npx ksk-ds lint の P051 が warn)"
|
|
2330
2345
|
],
|
|
2331
2346
|
"subcomponents": [
|
|
2332
2347
|
"ResponsiveOverlayFooter"
|
|
@@ -2408,6 +2423,27 @@
|
|
|
2408
2423
|
"既に fixed な nav を渡す場合は bottomNavMode=external を使う"
|
|
2409
2424
|
]
|
|
2410
2425
|
},
|
|
2426
|
+
{
|
|
2427
|
+
"name": "SidebarNav",
|
|
2428
|
+
"path": "src/components/patterns/sidebar-nav.tsx",
|
|
2429
|
+
"description": "PC 表示の左サイドバーに縦に並べるナビ項目。行高 44px・角丸・選択中の面/文字色・アイコン位置・件数バッジを DS が持つ。items は BottomTabBarItem と同じ形で、モバイルの BottomTabBar と同じ配列を渡せる。collapsed でアイコンだけのレール表示。",
|
|
2430
|
+
"variants": [
|
|
2431
|
+
"collapsed: false / true"
|
|
2432
|
+
],
|
|
2433
|
+
"accessibility": [
|
|
2434
|
+
"nav に aria-label(既定 \"サイドナビゲーション\")を付与",
|
|
2435
|
+
"isActive の項目に aria-current=\"page\" を付与",
|
|
2436
|
+
"role=tab / aria-selected は使わない",
|
|
2437
|
+
"collapsed 時はラベルを aria-label と title に移す",
|
|
2438
|
+
"行の高さは 44px 以上"
|
|
2439
|
+
],
|
|
2440
|
+
"rules": [
|
|
2441
|
+
"MobileAppShell の desktopSidebar / AdminShell の sidebar slot に置く。Button を行の形へ上書きして代用しない",
|
|
2442
|
+
"モバイルの BottomTabBar と同じ items 配列を渡す",
|
|
2443
|
+
"ページ内アンカーの目次は SectionNav、ビュー状態の切替は SubNav を使う",
|
|
2444
|
+
"ロゴ行・フッターは部品に含めず slot 側で並べる"
|
|
2445
|
+
]
|
|
2446
|
+
},
|
|
2411
2447
|
{
|
|
2412
2448
|
"name": "CompactFilePicker / ImageAttachmentPicker",
|
|
2413
2449
|
"exported": false,
|
package/contracts/rules.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"meta": {
|
|
3
3
|
"name": "KSK Design System — Rules Contracts",
|
|
4
4
|
"version": "1.2.0",
|
|
5
|
-
"description": "禁止パターン・アクセシビリティ要件・ライティングルールの構造化定義。lint-scratch.sh と check-drift.sh で自動検証。禁止パターン
|
|
5
|
+
"description": "禁止パターン・アクセシビリティ要件・ライティングルールの構造化定義。lint-scratch.sh と check-drift.sh で自動検証。禁止パターン51件・AIアンチパターン10件。"
|
|
6
6
|
},
|
|
7
7
|
"prohibited": [
|
|
8
8
|
{
|
|
@@ -260,9 +260,9 @@
|
|
|
260
260
|
"pattern": "outline-none\\b",
|
|
261
261
|
"excludePaths": [".stories."],
|
|
262
262
|
"excludeDsPaths": ["components/ui/", "components/patterns/"],
|
|
263
|
-
"
|
|
264
|
-
"
|
|
265
|
-
"
|
|
263
|
+
"message": "outline-none 禁止(フォーカスリング削除はアクセシビリティ違反。強制配色モードでは outline-hidden でないと枠が消える)",
|
|
264
|
+
"fix": "focus-visible:outline-hidden focus-visible:ring-[3px] focus-visible:ring-[var(--Focus-High-Emphasis)]/50",
|
|
265
|
+
"note": "issue #620。Tailwind v4 で outline-none は常に outline-style: none(強制配色モードでも非表示のまま)。outline-hidden は通常時 none だが forced-colors 時だけ outline: 2px solid transparent になり OS 色へ置換されるため、box-shadow の ring が描画されない強制配色モードでも枠が残る。以前は excludeLines: [\"focus-visible\"] で `focus-visible:outline-none` を含む行を丸ごと素通りさせていたため、まさに forced-colors で問題になるパターン(outline-none + ring の組み合わせ)を検出できていなかった(レビュー指摘)。前置バリアント(`md:` / `data-[...]:` / `group-focus-visible:` / `!` 等)付きでも `outline-none` 部分文字列があれば検出する。DS コンポーネント本体(components/ui・components/patterns)はこのルールの対象外で、__tests__/focus-ring-outline-pairing.test.ts が outline-hidden 必須を機械検証する"
|
|
266
266
|
},
|
|
267
267
|
{
|
|
268
268
|
"id": "P024",
|
|
@@ -568,6 +568,18 @@
|
|
|
568
568
|
"message": "DS を参照しない並行パレットの疑い(DS 名前空間に属さないカスタムプロパティに色値を5個以上定義)",
|
|
569
569
|
"fix": "ksk-design-system/preset + テーマの導入、または既存変数の値を DS トークン参照へ置換",
|
|
570
570
|
"note": "P049 は DS 名前空間(--Primitive- 等)に触れる CSS だけを違反にするため、DS を一切使わない独自パレット(--bg / --accent / --surface 等)を敷いた CSS には無言だった(issue #393)。:root / .dark / [data-theme=...] のようなルート的セレクタの中で、DS 名前空間(contracts/product-theme-overrides.json の dsVariableNamespaces)に属さないカスタムプロパティへ色値(hex / rgb() / hsl() / oklch())を minColorVars 個以上定義していたら warn する。値が var(--Primitive-...) のように DS トークンを参照しているだけのものはカウントしない。意図的な独立パレット(チャット画面の LINE 風ライト等)は // ksk-ds-lint-ignore P050 -- 理由 で抑制できる。DS 自身 / ベンダリングされた DS の CSS は P049 と同じ判定(issue #407)で対象外"
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
"id": "P051",
|
|
574
|
+
"severity": "warning",
|
|
575
|
+
"category": "spacing",
|
|
576
|
+
"appliesTo": ["dom"],
|
|
577
|
+
"pattern": "overlay-footer-padding",
|
|
578
|
+
"engine": "overlay-footer-padding",
|
|
579
|
+
"excludeLines": [],
|
|
580
|
+
"message": "ResponsiveOverlayFrame(preset=\"plain\" / side=\"float\" 系)の中に ResponsiveOverlayFooter を置くのに padding={false} が無い(下余白が二重になる)",
|
|
581
|
+
"fix": "Frame に padding={false} を付ける。ResponsiveOverlayFooter が safe-area 込みの下余白を自前で持つため、Frame 側の p-6 と safe-area の下余白は不要",
|
|
582
|
+
"note": "issue #619。#599 / #613 で plain / float 系の Frame が既定で p-6 + safe-area 込みの下余白を持つようになったため、Footer と併用すると二重になる。公開 CLI と lint-scratch.sh は共通の TypeScript AST 検査(bin/overlay-footer-padding.js)。誤検知を出さない方を優先し、side / preset が文字列リテラルで plain / float 系と確定し、Frame に spread props が無く、padding が未指定・値なし・{true} で、Footer が Frame の JSX 子孫に直接書かれているときだけ報告する。padding={cond} のような式・{...props}・Footer を別コンポーネントに切り出した構成・preset 経路(mobile-form 等。padding を受け付けず余白も持たない)は対象外"
|
|
571
583
|
}
|
|
572
584
|
],
|
|
573
585
|
"aiPatterns": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"name": "KSK Design System — Semantic Token Hex Cache",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.9.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",
|
|
@@ -346,8 +346,14 @@
|
|
|
346
346
|
"hover.tertiary-button": "#F4F4F5",
|
|
347
347
|
"hover.ghost-button": "#F4F4F5",
|
|
348
348
|
"hover.destructive-button": "#B91C1C",
|
|
349
|
+
"hover.info-button": "#1D4ED8",
|
|
350
|
+
"hover.warning-button": "#9A3412",
|
|
351
|
+
"hover.success-button": "#166534",
|
|
349
352
|
"active.primary-button": "#1E40AF",
|
|
350
353
|
"active.destructive-button": "#991B1B",
|
|
354
|
+
"active.info-button": "#1E40AF",
|
|
355
|
+
"active.warning-button": "#7C2D12",
|
|
356
|
+
"active.success-button": "#14532D",
|
|
351
357
|
"active.secondary-button": "#DBEAFE",
|
|
352
358
|
"active.tertiary-button": "#E4E4E7",
|
|
353
359
|
"focus.high-emphasis": "#60A5FA",
|
|
@@ -452,9 +458,15 @@
|
|
|
452
458
|
"brand.on-apple": "#000000",
|
|
453
459
|
"hover.primary-button": "#3B82F6",
|
|
454
460
|
"hover.secondary-button": "#1E3A8A",
|
|
455
|
-
"hover.destructive-button": "#
|
|
461
|
+
"hover.destructive-button": "#FCA5A5",
|
|
462
|
+
"hover.info-button": "#93C5FD",
|
|
463
|
+
"hover.warning-button": "#FDBA74",
|
|
464
|
+
"hover.success-button": "#86EFAC",
|
|
456
465
|
"active.primary-button": "#2563EB",
|
|
457
|
-
"active.destructive-button": "#
|
|
466
|
+
"active.destructive-button": "#FECACA",
|
|
467
|
+
"active.info-button": "#BFDBFE",
|
|
468
|
+
"active.warning-button": "#FED7AA",
|
|
469
|
+
"active.success-button": "#BBF7D0",
|
|
458
470
|
"active.secondary-button": "#1E40AF",
|
|
459
471
|
"focus.high-emphasis": "#60A5FA",
|
|
460
472
|
"caution.base": "#F87171",
|
package/dist/class-names.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as e } from "./server-variants-
|
|
1
|
+
import { n as e } from "./server-variants-C7GNdOYi.js";
|
|
2
2
|
export { e as buttonVariants };
|