ksk-design-system 1.63.1 → 1.65.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/README.md +40 -4
- package/bin/check-duplicates.js +127 -4
- package/bin/lint.js +99 -18
- package/bin/product-theme-override.js +55 -0
- package/contracts/components.json +23 -9
- package/contracts/rules.json +132 -55
- package/contracts/token-hex-cache.json +1 -1
- package/dist/index.js +443 -431
- package/dist/native/ui.js +1167 -1126
- package/dist/types/components/patterns/mobile-floating-action-button.d.ts +15 -1
- package/dist/types/components/patterns/tag.d.ts +4 -25
- package/dist/types/components/ui/date-field.d.ts +7 -1
- package/dist/types/components/ui/label.d.ts +18 -1
- package/dist/types/components/ui/multi-select.d.ts +6 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lib/categorical.d.ts +84 -0
- package/dist/types/native/components/Avatar.d.ts +28 -5
- package/dist/types/native/components/BottomSheetFrame.d.ts +31 -1
- package/dist/types/native/components/Button.d.ts +7 -1
- package/dist/types/native/components/DateField.d.ts +6 -1
- package/dist/types/native/components/DatePicker.d.ts +6 -1
- package/dist/types/native/components/Input.d.ts +14 -1
- package/dist/types/native/components/MobileFloatingActionButton.d.ts +14 -1
- package/dist/types/native/components/MultiSelect.d.ts +6 -1
- package/dist/types/native/components/Sheet.d.ts +7 -0
- package/dist/types/native/components/Tag.d.ts +25 -1
- package/package.json +1 -1
- package/src/components/COMPONENT_LOOKUP.md +5 -5
- package/src/styles/source-safelist.css +4 -1
package/README.md
CHANGED
|
@@ -119,9 +119,11 @@ import { Button, Card, Input, FormField } from "ksk-design-system"
|
|
|
119
119
|
| `Button` | `type` 既定 `"button"` / disabled・aria-disabled クリックの抑止 / `haptic` / `asChild` | `inline-flex` `items-center` `justify-center` `gap-*` `whitespace-nowrap` `typo-*` `cursor-pointer` と variant・size・layout の全クラス |
|
|
120
120
|
| `Input` | `showCount`(IME 追従)/ adornment の配置土台 | `h-*` `w-full` `border-*` `bg-*` `px-*` `typo-*` `placeholder:*` |
|
|
121
121
|
| `Textarea` | `autoGrow` / `showCount` | `min-h-*` `w-full` `border-*` `bg-*` `px-*` `py-*` `typo-*` `placeholder:*` |
|
|
122
|
+
| `Label` | Radix Label の挙動(クリックで対象コントロールへフォーカス移動) | `typo-label-md` `text-[var(--Text-High-Emphasis)]` `peer-disabled:cursor-not-allowed` `peer-disabled:opacity-50` |
|
|
122
123
|
|
|
123
|
-
キーボード操作時の `focus-visible` リングだけは a11y
|
|
124
|
-
|
|
124
|
+
キーボード操作時の `focus-visible` リングだけは a11y のため既定で残ります(`Label` は
|
|
125
|
+
フォーカス可能要素ではないため対象外)。手書き CSS が自前のフォーカス表現を持つ場合は
|
|
126
|
+
`className="focus-visible:ring-0"` で消せます。
|
|
125
127
|
|
|
126
128
|
移行の順番は次のとおりです。
|
|
127
129
|
|
|
@@ -198,8 +200,34 @@ const BRAND = "#06C755"
|
|
|
198
200
|
- 先頭 12 行に自動生成マーカー(`AUTO-GENERATED` / `DO NOT EDIT` / `自動生成` 等)を持つ
|
|
199
201
|
ファイルは全ルールを skip します。直す先は生成物ではなく生成元です。
|
|
200
202
|
- DS 自身のトークン定義 CSS(`src/styles/*.css` / `src/themes/*.css` / `src/preset.css` と、
|
|
201
|
-
それを再梱包したベンダリングコピー)は P049 の対象外です。トークンを**定義**している側で
|
|
202
|
-
あって、consumer
|
|
203
|
+
それを再梱包したベンダリングコピー)は P049 / P050 の対象外です。トークンを**定義**している側で
|
|
204
|
+
あって、consumer による上書き・並行実装ではないためです。
|
|
205
|
+
|
|
206
|
+
#### P050: DS を参照しない並行パレットの検出(issue #393)
|
|
207
|
+
|
|
208
|
+
P049 は「DS の名前空間の変数(`--Primitive-Brand-500` 等)に触れている CSS」しか見ないため、DS を
|
|
209
|
+
一切使わない独自パレット(`--bg` / `--accent` / `--surface` 等)を持つ consumer には
|
|
210
|
+
無言でした。P050 は `:root` / `.dark` / `[data-theme=...]` のようなルート的セレクタの中で、
|
|
211
|
+
DS 名前空間に属さないカスタムプロパティへ色値(`#hex` / `rgb()` / `hsl()` / `oklch()`)を
|
|
212
|
+
<!-- docs-drift-ignore: --Primitive- -->
|
|
213
|
+
5 個以上定義していたら「並行パレットの疑い」を warn します。値が `var(--Primitive-...)` の
|
|
214
|
+
ように DS トークンを参照しているだけのものはカウントしません。意図的な独立パレット
|
|
215
|
+
(LINE 風チャット画面のライトなど)は `// ksk-ds-lint-ignore P050 -- 理由` で抑制できます。
|
|
216
|
+
|
|
217
|
+
#### ルールごとの除外(rules.json の excludes 系)
|
|
218
|
+
|
|
219
|
+
`contracts/rules.json` の除外指定は、当てる対象ごとに 3 つに分かれています。
|
|
220
|
+
|
|
221
|
+
| フィールド | 当てる対象 | 効く範囲 |
|
|
222
|
+
|---|---|---|
|
|
223
|
+
| `excludePaths` | ファイルパス | 全リポジトリ(`.stories.` 等) |
|
|
224
|
+
| `excludeDsPaths` | ファイルパス | **DS パッケージ配下のファイルだけ**(`components/ui/` 等) |
|
|
225
|
+
| `excludeLines` | 行の内容 | 全リポジトリ(`data-slot` / `asChild` 等) |
|
|
226
|
+
|
|
227
|
+
`excludeDsPaths` は「DS 自身の実装だから生タグを書いてよい」という意味の除外なので、
|
|
228
|
+
consumer が shadcn 既定の `src/components/ui/` に部品を置いても中核ルールは無効化されません
|
|
229
|
+
(v1.63.x までは単一の `excludes` をパスと行内容の両方に OR で当てていたため、
|
|
230
|
+
このディレクトリ名だけで 13 ルールが同時に黙っていました)。
|
|
203
231
|
|
|
204
232
|
### Jest(CommonJS)でコンポーネントをテストする
|
|
205
233
|
|
|
@@ -275,6 +303,14 @@ npx ksk-ds check-duplicates ./src --strict
|
|
|
275
303
|
|
|
276
304
|
既定は助言モードで終了コード 0、`--strict` は重複候補があると終了コード 1 です。正本は同梱の `contracts/components.json` であり、consumer 側に別の「昇格候補台帳」を作らないでください。
|
|
277
305
|
|
|
306
|
+
DS を import して委譲するだけの薄いラッパー(段階移行の型)は、DS と同名でも重複報告から除外され、`ℹ ラッパー(DS 委譲済み)として除外` として件数のみ表示されます。
|
|
307
|
+
|
|
308
|
+
<!-- docs-drift-ignore: PrimaryButton AppTextInput BottomSheet CandidateOverflowMenu CrisisBanner AvatarView -->
|
|
309
|
+
さらに、`contracts/components.json` と完全一致しない名前でも、DS 部品を連想させる命名パターン(`PrimaryButton` → `Button` / `AppTextInput` → `Input` / `BottomSheet` → `Sheet` / `CandidateOverflowMenu` → `DropdownMenu` / `CrisisBanner` → `Banner` / `AvatarView` → `Avatar` 等)は「重複の疑い」として `⚠` 付きで warn 表示されます。これは名前一致のみによる推測(AST は使いません)のため確度は下がり、`--strict` でも exit code には影響しません。誤検知の場合は無視して構いませんが、恒常的に抑制したい場合はそのファイル内のコメントに `ksk-ds-local-fallback` を含めてください。
|
|
310
|
+
|
|
311
|
+
<!-- docs-drift-ignore: CandidateOverflowMenu -->
|
|
312
|
+
ただし、ファイルが `ksk-design-system`(`/native` 等のサブパス含む)から named import し、その import した識別子を JSX またはコールとして実際に使っている場合は、名前が DS と異なっていても「DS 委譲ラッパー」とみなし疑い検出の対象から除外します(例: `CandidateOverflowMenu` が `DropdownMenu` を import して `<DropdownMenu>` として使うケース)。DS を import しているだけで実際には使っていない場合はこの除外の対象外です。
|
|
313
|
+
|
|
278
314
|
### Media overlay utilities
|
|
279
315
|
|
|
280
316
|
動画・写真の上に文字や操作を置く場合は、`--Text-on-Media` と `.text-on-media` / `.text-on-media-secondary`、上下の `.media-scrim-top` / `.media-scrim-bottom` を使います。TikTok / Reels 型の操作群は `MediaActionCluster` が glass ボタン、ラベル、safe-area anchor、idle auto-hide をまとめて扱います。
|
package/bin/check-duplicates.js
CHANGED
|
@@ -43,8 +43,13 @@ export function runCheckDuplicatesCli(
|
|
|
43
43
|
const wrapperExclusions = results
|
|
44
44
|
.flatMap((result) => result.wrapperExclusions)
|
|
45
45
|
.sort((a, b) => a.file.localeCompare(b.file) || a.name.localeCompare(b.name))
|
|
46
|
+
const suspicions = results
|
|
47
|
+
.flatMap((result) => result.suspicions)
|
|
48
|
+
.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.name.localeCompare(b.name))
|
|
46
49
|
|
|
47
|
-
printFindings(findings, wrapperExclusions, files.length, options.strict)
|
|
50
|
+
printFindings(findings, wrapperExclusions, suspicions, files.length, options.strict)
|
|
51
|
+
// suspicions は名前ヒューリスティックによる warn 止まりの「疑い」であり、
|
|
52
|
+
// --strict でも exit code には影響させない(issue #392 取りこぼし側)。
|
|
48
53
|
return options.strict && findings.length > 0 ? 1 : 0
|
|
49
54
|
}
|
|
50
55
|
|
|
@@ -169,6 +174,34 @@ function collectDsImportSourceNames(source) {
|
|
|
169
174
|
return names
|
|
170
175
|
}
|
|
171
176
|
|
|
177
|
+
// このファイルが ksk-design-system から named import している要素の
|
|
178
|
+
// ローカル名(alias 込み)の集合。DS 委譲ラッパー判定(実使用チェック)に使う。
|
|
179
|
+
function collectDsImportLocalNames(source) {
|
|
180
|
+
const names = new Set()
|
|
181
|
+
const importPattern = /import\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/g
|
|
182
|
+
|
|
183
|
+
for (const match of source.matchAll(importPattern)) {
|
|
184
|
+
const [, specifierList, moduleSpecifier] = match
|
|
185
|
+
if (!isDsModuleSpecifier(moduleSpecifier)) continue
|
|
186
|
+
for (const { localName } of parseSpecifierList(specifierList)) {
|
|
187
|
+
names.add(localName)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return names
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// import した DS のローカル名が、JSX(`<Name`)または関数呼び出し(`Name(`)として
|
|
195
|
+
// 実際に使われているか。import しているだけで使っていないケース(不要 import 等)は
|
|
196
|
+
// 「実際に委譲している」とは言えないため対象外にする。
|
|
197
|
+
function usesAnyIdentifierAsJsxOrCall(source, localNames) {
|
|
198
|
+
for (const localName of localNames) {
|
|
199
|
+
const usagePattern = new RegExp(`<${localName}[\\s/>]|\\b${localName}\\s*\\(`)
|
|
200
|
+
if (usagePattern.test(source)) return true
|
|
201
|
+
}
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
|
|
172
205
|
// `export { X as Y } from 'ksk-design-system/...'` のような re-export 形式。
|
|
173
206
|
// これは常に DS の実装をそのまま右から左へ渡すだけなので、localName が
|
|
174
207
|
// registry と一致する場合は無条件でラッパー(除外対象)として扱う。
|
|
@@ -187,16 +220,68 @@ function collectDsReExports(source) {
|
|
|
187
220
|
return reExports
|
|
188
221
|
}
|
|
189
222
|
|
|
223
|
+
// issue #392 取りこぼし側: 名前が違う真の重複(改名された DS 相当の自前実装)を
|
|
224
|
+
// AST を使わず名前ヒューリスティックで拾う。確度が下がるため error にはせず warn
|
|
225
|
+
// (「疑い」)止まりとし、--strict でも exit code は変えない。
|
|
226
|
+
//
|
|
227
|
+
// キーワードは接尾辞(PrimaryButton・AppTextInput 等の前置修飾パターン)と
|
|
228
|
+
// 接頭辞(AvatarView 等、DS 部品名 + 汎用語のパターン)の両方で判定する。
|
|
229
|
+
const NAME_HEURISTIC_MAP = {
|
|
230
|
+
Button: "Button",
|
|
231
|
+
TextInput: "Input",
|
|
232
|
+
Input: "Input",
|
|
233
|
+
Sheet: "Sheet",
|
|
234
|
+
Modal: "Dialog",
|
|
235
|
+
Menu: "DropdownMenu",
|
|
236
|
+
Banner: "Banner",
|
|
237
|
+
Alert: "Alert",
|
|
238
|
+
Avatar: "Avatar",
|
|
239
|
+
// Card は除外: ドメインカード(PropertyCard 等)が正当に多く、ノイズ源になる
|
|
240
|
+
Chip: "Chip",
|
|
241
|
+
Badge: "Badge",
|
|
242
|
+
Toast: "Toast",
|
|
243
|
+
Tooltip: "Tooltip",
|
|
244
|
+
Dialog: "Dialog",
|
|
245
|
+
}
|
|
246
|
+
const NAME_HEURISTIC_KEYWORDS = Object.keys(NAME_HEURISTIC_MAP).sort((a, b) => b.length - a.length)
|
|
247
|
+
// 接頭辞パターンで許容する汎用語(残り部分がこれらに完全一致する場合のみ疑いとする)。
|
|
248
|
+
// 無制限に許可すると「AvatarUploadFlow」のような無関係な名前まで拾ってしまうため絞る。
|
|
249
|
+
const GENERIC_NAME_SUFFIXES = new Set(["View", "Component", "Element", "Widget", "Item", "Base", "Wrapper"])
|
|
250
|
+
// 抑制マーカー: このコメントを含むファイルは「疑い」報告の対象から除外する
|
|
251
|
+
// (contracts/rules.json 等の既存の一時実装ルールに合わせた運用)。
|
|
252
|
+
const SUPPRESSION_MARKER = "ksk-ds-local-fallback"
|
|
253
|
+
|
|
254
|
+
function suggestDsNameForLocalName(name) {
|
|
255
|
+
for (const keyword of NAME_HEURISTIC_KEYWORDS) {
|
|
256
|
+
if (name.length > keyword.length && name.endsWith(keyword)) {
|
|
257
|
+
return NAME_HEURISTIC_MAP[keyword]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
for (const keyword of NAME_HEURISTIC_KEYWORDS) {
|
|
261
|
+
if (name.length > keyword.length && name.startsWith(keyword)) {
|
|
262
|
+
const remainder = name.slice(keyword.length)
|
|
263
|
+
if (GENERIC_NAME_SUFFIXES.has(remainder)) return NAME_HEURISTIC_MAP[keyword]
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return null
|
|
267
|
+
}
|
|
268
|
+
|
|
190
269
|
function findDuplicateExports(file, cwd, registry) {
|
|
191
270
|
const source = readFileSync(file, "utf8")
|
|
192
271
|
const lines = source.split(/\r?\n/)
|
|
193
272
|
const findings = []
|
|
194
273
|
const wrapperExclusions = []
|
|
274
|
+
const suspicions = []
|
|
195
275
|
const relFile = normalize(relative(cwd, file))
|
|
276
|
+
const suppressSuspicions = source.includes(SUPPRESSION_MARKER)
|
|
196
277
|
|
|
197
278
|
const codeForImportScan = stripComments(source)
|
|
198
279
|
const dsImportSourceNames = collectDsImportSourceNames(codeForImportScan)
|
|
280
|
+
const dsImportLocalNames = collectDsImportLocalNames(codeForImportScan)
|
|
199
281
|
const dsReExportLocalNames = collectDsReExports(codeForImportScan)
|
|
282
|
+
// issue #451: 名前が DS と違っても、DS を import してそれを JSX/関数呼び出しで
|
|
283
|
+
// 実際に使っていれば「DS 委譲ラッパー」とみなし、名前ヒューリスティックの疑い対象から除外する。
|
|
284
|
+
const isDsDelegatingWrapper = usesAnyIdentifierAsJsxOrCall(codeForImportScan, dsImportLocalNames)
|
|
200
285
|
|
|
201
286
|
const declarationPattern =
|
|
202
287
|
/^\s*export\s+(?:default\s+)?(?:(?:async|declare)\s+)?(?:function|const)\s+([A-Z][A-Za-z0-9]*)\b/
|
|
@@ -206,7 +291,24 @@ function findDuplicateExports(file, cwd, registry) {
|
|
|
206
291
|
if (!match) continue
|
|
207
292
|
const name = match[1]
|
|
208
293
|
const components = registry.get(name)
|
|
209
|
-
|
|
294
|
+
|
|
295
|
+
if (!components) {
|
|
296
|
+
// 完全一致は無いが、名前が DS 部品を連想させるパターン(issue #392 取りこぼし側)。
|
|
297
|
+
// DS を import しているファイル(何らかの形で DS を使っている=別部品のラッパー等)
|
|
298
|
+
// でも疑いは出す。抑制は SUPPRESSION_MARKER コメントのみ。
|
|
299
|
+
if (suppressSuspicions) continue
|
|
300
|
+
if (isDsDelegatingWrapper) continue
|
|
301
|
+
const suggested = suggestDsNameForLocalName(name)
|
|
302
|
+
if (!suggested) continue
|
|
303
|
+
suspicions.push({
|
|
304
|
+
file: relFile,
|
|
305
|
+
line: index + 1,
|
|
306
|
+
name,
|
|
307
|
+
suggested,
|
|
308
|
+
suggestedComponents: registry.get(suggested) ?? null,
|
|
309
|
+
})
|
|
310
|
+
continue
|
|
311
|
+
}
|
|
210
312
|
|
|
211
313
|
// 同名のローカル宣言があっても、同ファイルが ksk-design-system から同名(元の
|
|
212
314
|
// export 名基準。ローカル alias は問わない)を import して使っているなら、
|
|
@@ -231,10 +333,10 @@ function findDuplicateExports(file, cwd, registry) {
|
|
|
231
333
|
wrapperExclusions.push({ file: relFile, name: localName })
|
|
232
334
|
}
|
|
233
335
|
|
|
234
|
-
return { findings, wrapperExclusions }
|
|
336
|
+
return { findings, wrapperExclusions, suspicions }
|
|
235
337
|
}
|
|
236
338
|
|
|
237
|
-
function printFindings(findings, wrapperExclusions, scannedFiles, strict) {
|
|
339
|
+
function printFindings(findings, wrapperExclusions, suspicions, scannedFiles, strict) {
|
|
238
340
|
if (findings.length === 0) {
|
|
239
341
|
console.log(`ksk-ds check-duplicates: 重複候補はありません(${scannedFiles} files)`)
|
|
240
342
|
} else {
|
|
@@ -260,6 +362,21 @@ function printFindings(findings, wrapperExclusions, scannedFiles, strict) {
|
|
|
260
362
|
console.log(` ℹ ラッパー(DS 委譲済み)として除外: ${wrapper.name} (${wrapper.file})`)
|
|
261
363
|
}
|
|
262
364
|
}
|
|
365
|
+
|
|
366
|
+
if (suspicions.length > 0) {
|
|
367
|
+
console.log(`\n⚠ 重複の疑い(名前ヒューリスティック・要確認): ${suspicions.length} 件`)
|
|
368
|
+
for (const suspicion of suspicions) {
|
|
369
|
+
const dsPathHint = suspicion.suggestedComponents
|
|
370
|
+
? ` (DS: ${suspicion.suggestedComponents.map((component) => component.path).join(", ")})`
|
|
371
|
+
: ""
|
|
372
|
+
console.log(
|
|
373
|
+
` ⚠ ${suspicion.file}:${suspicion.line} ${suspicion.name} は DS の ${suspicion.suggested} で置換できる可能性があります${dsPathHint}`,
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
console.log(
|
|
377
|
+
" ※ 名前一致のみによる推測(warn)です。誤検知の場合は無視して問題ありません。抑制するには実装コメントに `ksk-ds-local-fallback` を含めてください。",
|
|
378
|
+
)
|
|
379
|
+
}
|
|
263
380
|
}
|
|
264
381
|
|
|
265
382
|
function printHelp() {
|
|
@@ -278,6 +395,12 @@ DIR の既定値:
|
|
|
278
395
|
ただし、同ファイルが ksk-design-system から同名(alias 可)を import/re-export して
|
|
279
396
|
使っているものは DS 委譲ラッパーとみなし、重複報告から除外します(info 行で件数報告)。
|
|
280
397
|
既定は助言モード(常に exit 0)、--strict は検出時 exit 1 です。
|
|
398
|
+
|
|
399
|
+
完全一致しない名前でも、DS 部品を連想させる命名パターン
|
|
400
|
+
(PrimaryButton → Button・AppTextInput → Input・BottomSheet → Sheet・
|
|
401
|
+
CandidateOverflowMenu → DropdownMenu・CrisisBanner → Banner・AvatarView → Avatar 等)は
|
|
402
|
+
「重複の疑い」として warn 表示します(--strict でも exit code には影響しません)。
|
|
403
|
+
抑制するにはファイル内のコメントに \`ksk-ds-local-fallback\` を含めてください。
|
|
281
404
|
`)
|
|
282
405
|
}
|
|
283
406
|
|
package/bin/lint.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"
|
|
2
|
-
import { basename, extname, join, relative, resolve } from "node:path"
|
|
2
|
+
import { basename, extname, isAbsolute, join, relative, resolve } from "node:path"
|
|
3
3
|
import { spawnSync } from "node:child_process"
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
inspectParallelPaletteCandidates,
|
|
6
|
+
inspectProductThemeOverrides,
|
|
7
|
+
loadProductThemeContract,
|
|
8
|
+
} from "./product-theme-override.js"
|
|
5
9
|
|
|
6
10
|
// card-child-spacing.js は `import ts from "typescript"` している(P046 専用エンジン)。
|
|
7
11
|
// typescript は 24MB 前後あり、59ルール中 P046 の1つにしか使わないため、
|
|
@@ -354,9 +358,13 @@ export async function runLintCli(argv, { cwd = process.cwd(), pkgRoot = resolve(
|
|
|
354
358
|
return 1
|
|
355
359
|
}
|
|
356
360
|
|
|
361
|
+
// CSS 専用エンジン一覧。P049(product-theme-override)と P050(parallel-palette、
|
|
362
|
+
// issue #393)はどちらも .css だけを対象にした独自エンジンで、TSX 向けの
|
|
363
|
+
// 正規表現エンジンとは別の走査経路(lintCssFile)を通す。
|
|
364
|
+
const CSS_ENGINES = new Set(["product-theme-override", "parallel-palette"])
|
|
357
365
|
const rules = loadRules(rulesPath)
|
|
358
|
-
const cssRules = rules.filter((rule) => rule.engine
|
|
359
|
-
let sourceRules = rules.filter((rule) => rule.engine
|
|
366
|
+
const cssRules = rules.filter((rule) => CSS_ENGINES.has(rule.engine))
|
|
367
|
+
let sourceRules = rules.filter((rule) => !CSS_ENGINES.has(rule.engine))
|
|
360
368
|
const productThemeContract = cssRules.length > 0 ? readProductThemeContract(pkgRoot) : null
|
|
361
369
|
// DS 自身 / ベンダリングされた DS の CSS を P049 の対象から外すための材料(issue #407)
|
|
362
370
|
const dsCssIdentity = cssRules.length > 0 ? collectDsCssIdentity(pkgRoot) : null
|
|
@@ -393,12 +401,19 @@ export async function runLintCli(argv, { cwd = process.cwd(), pkgRoot = resolve(
|
|
|
393
401
|
continue
|
|
394
402
|
}
|
|
395
403
|
if (CSS_EXTENSIONS.has(extname(file))) {
|
|
396
|
-
|
|
397
|
-
|
|
404
|
+
// productThemeContract が読めない環境(contracts/product-theme-overrides.json
|
|
405
|
+
// が無い)でも P050 は動かせるので、CSS ルールが 1 件でもあれば走査する
|
|
406
|
+
// (P049 側は contract が null なら lintCssFile 内で個別に skip する)。
|
|
407
|
+
if (cssRules.length > 0) {
|
|
408
|
+
findings.push(
|
|
409
|
+
...lintCssFile(file, cwd, cssRules, productThemeContract, dsCssIdentity, pkgRoot),
|
|
410
|
+
)
|
|
398
411
|
}
|
|
399
412
|
continue
|
|
400
413
|
}
|
|
401
|
-
findings.push(
|
|
414
|
+
findings.push(
|
|
415
|
+
...lintFile(file, cwd, sourceRules, { ...options, inspectCardChildSpacing, pkgRoot }),
|
|
416
|
+
)
|
|
402
417
|
}
|
|
403
418
|
|
|
404
419
|
const summary = summarize(findings)
|
|
@@ -645,11 +660,12 @@ function readProductThemeContract(pkgRoot) {
|
|
|
645
660
|
}
|
|
646
661
|
|
|
647
662
|
/**
|
|
648
|
-
* CSS ファイルには product theme
|
|
649
|
-
* TSX 向けの正規表現ルールを CSS に流すと誤検知しかしない。
|
|
663
|
+
* CSS ファイルには P049(product theme の許可リスト検査)と P050(並行パレット検出、
|
|
664
|
+
* issue #393)だけを当てる。TSX 向けの正規表現ルールを CSS に流すと誤検知しかしない。
|
|
650
665
|
*/
|
|
651
|
-
function lintCssFile(file, cwd, cssRules, contract, dsCssIdentity = null) {
|
|
666
|
+
function lintCssFile(file, cwd, cssRules, contract, dsCssIdentity = null, pkgRoot = null) {
|
|
652
667
|
const rel = normalize(relative(cwd, file))
|
|
668
|
+
const isDsFile = isDsPackageFile(file, pkgRoot)
|
|
653
669
|
const source = readFileSync(file, "utf8")
|
|
654
670
|
// 自動生成 CSS(source-safelist.css 等)は生成元を直すべきなので全ルール skip(issue #408)
|
|
655
671
|
if (isAutoGeneratedSource(source)) return []
|
|
@@ -663,10 +679,36 @@ function lintCssFile(file, cwd, cssRules, contract, dsCssIdentity = null) {
|
|
|
663
679
|
findings.push(...ignores.problems)
|
|
664
680
|
for (const rule of cssRules) {
|
|
665
681
|
// CSS は web のみに存在する(RN に .css は無い)。glob 指定の appliesTo は
|
|
666
|
-
// capability タグを含まないので、この判定でも従来どおり P049 が当たる。
|
|
682
|
+
// capability タグを含まないので、この判定でも従来どおり P049/P050 が当たる。
|
|
667
683
|
if (!ruleAppliesTo(rule, { capabilities: CSS_CAPABILITIES, filePath: rel })) continue
|
|
684
|
+
|
|
685
|
+
if (rule.engine === "parallel-palette") {
|
|
686
|
+
// contract が読めない(DS の名前空間一覧が無い)と何が DS 名前空間かを
|
|
687
|
+
// 判定できず false positive しか出せないので、その場合は skip する。
|
|
688
|
+
if (!contract) continue
|
|
689
|
+
const threshold = typeof rule.minColorVars === "number" ? rule.minColorVars : 5
|
|
690
|
+
const candidates = inspectParallelPaletteCandidates(source, { namespaces: contract.namespaces })
|
|
691
|
+
if (candidates.length < threshold) continue
|
|
692
|
+
const first = candidates[0]
|
|
693
|
+
if (matchesRuleExclude(rule, { file: rel, line: first.name, isDsFile })) continue
|
|
694
|
+
if (ignores.suppresses(rule.id ?? "UNKNOWN", first.line)) continue
|
|
695
|
+
const names = candidates.slice(0, 5).map((c) => c.name).join(", ")
|
|
696
|
+
const suffix = candidates.length > 5 ? ` ほか計${candidates.length}個` : `(計${candidates.length}個)`
|
|
697
|
+
findings.push({
|
|
698
|
+
...toFinding(rule, rel, first.line, "web"),
|
|
699
|
+
message: `${rule.message ?? "DS を参照しない並行パレットの疑い"}: ${names}${suffix}`,
|
|
700
|
+
})
|
|
701
|
+
continue
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// contract が読めない環境(contracts/product-theme-overrides.json が無い)では
|
|
705
|
+
// P049 の許可リスト判定ができないので skip する(従来は呼び出し元で丸ごと
|
|
706
|
+
// skip していたのと同じ挙動を、CSS ルール単位に落とし込んだだけ)。
|
|
707
|
+
if (!contract) continue
|
|
668
708
|
for (const violation of inspectProductThemeOverrides(source, contract)) {
|
|
669
|
-
|
|
709
|
+
// violation.name は CSS 変数名(=内容)なので line 側に渡す。
|
|
710
|
+
// パス除外を変数名に当ててはいけない(issue #404 の提案 c)。
|
|
711
|
+
if (matchesRuleExclude(rule, { file: rel, line: violation.name, isDsFile })) continue
|
|
670
712
|
if (ignores.suppresses(rule.id ?? "UNKNOWN", violation.line)) continue
|
|
671
713
|
findings.push({
|
|
672
714
|
...toFinding(rule, rel, violation.line, "web"),
|
|
@@ -679,6 +721,7 @@ function lintCssFile(file, cwd, cssRules, contract, dsCssIdentity = null) {
|
|
|
679
721
|
|
|
680
722
|
function lintFile(file, cwd, rules, options = {}) {
|
|
681
723
|
const rel = normalize(relative(cwd, file))
|
|
724
|
+
const isDsFile = isDsPackageFile(file, options.pkgRoot)
|
|
682
725
|
const source = readFileSync(file, "utf8")
|
|
683
726
|
// 自動生成物(src/tokens/native/themes.ts 等)は hex をそこにしか書けない。
|
|
684
727
|
// 直す先は生成物ではなく生成元なので、全ルールを skip する(issue #408)。
|
|
@@ -710,7 +753,7 @@ function lintFile(file, cwd, rules, options = {}) {
|
|
|
710
753
|
for (const finding of options.inspectCardChildSpacing(source, file)) {
|
|
711
754
|
const line = lines[finding.line - 1] ?? ""
|
|
712
755
|
if (ignores.suppresses(rule.id ?? "UNKNOWN", finding.line)) continue
|
|
713
|
-
if (!matchesRuleExclude(rule, rel, line)) {
|
|
756
|
+
if (!matchesRuleExclude(rule, { file: rel, line, isDsFile })) {
|
|
714
757
|
findings.push(toFinding(rule, rel, finding.line, platform))
|
|
715
758
|
}
|
|
716
759
|
}
|
|
@@ -735,7 +778,7 @@ function lintFile(file, cwd, rules, options = {}) {
|
|
|
735
778
|
// excludes(ksk-ds-allow-* 等)はコメントに書かれることが多いため、
|
|
736
779
|
// 判定は常にマスク前の生の行に対して行う。
|
|
737
780
|
const rawLine = lines[lineNumber - 1] ?? ""
|
|
738
|
-
if (matchesRuleExclude(rule, rel, rawLine)) continue
|
|
781
|
+
if (matchesRuleExclude(rule, { file: rel, line: rawLine, isDsFile })) continue
|
|
739
782
|
if (ignores.suppresses(rule.id ?? "UNKNOWN", lineNumber)) continue
|
|
740
783
|
findings.push(toFinding(rule, rel, lineNumber, platform))
|
|
741
784
|
}
|
|
@@ -743,7 +786,7 @@ function lintFile(file, cwd, rules, options = {}) {
|
|
|
743
786
|
}
|
|
744
787
|
for (let index = 0; index < lines.length; index++) {
|
|
745
788
|
const rawLine = lines[index]
|
|
746
|
-
if (matchesRuleExclude(rule, rel, rawLine)) continue
|
|
789
|
+
if (matchesRuleExclude(rule, { file: rel, line: rawLine, isDsFile })) continue
|
|
747
790
|
const maskedLine = maskedLines[index] ?? ""
|
|
748
791
|
if (!regex.test(maskedLine)) continue
|
|
749
792
|
if (ignores.suppresses(rule.id ?? "UNKNOWN", index + 1)) continue
|
|
@@ -1076,9 +1119,47 @@ function maskTemplateLiterals(source) {
|
|
|
1076
1119
|
return maskStrings(source, TEMPLATE_DELIMITER_ONLY)
|
|
1077
1120
|
}
|
|
1078
1121
|
|
|
1079
|
-
function
|
|
1080
|
-
|
|
1081
|
-
|
|
1122
|
+
function excludeList(value) {
|
|
1123
|
+
return Array.isArray(value) ? value : []
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* このファイルは DS パッケージ本体の中にあるか(issue #404)。
|
|
1128
|
+
*
|
|
1129
|
+
* `excludeDsPaths`(`components/ui/` 等)は「DS 自身の実装なので生タグを書いてよい」
|
|
1130
|
+
* という意味の除外なので、consumer が同名ディレクトリ(shadcn 既定の
|
|
1131
|
+
* `src/components/ui/`)を持っているだけで効いてはいけない。judgement は
|
|
1132
|
+
* 走査対象の実ファイルが DS パッケージ(pkgRoot)配下かどうかで行う。
|
|
1133
|
+
*/
|
|
1134
|
+
export function isDsPackageFile(absFile, pkgRoot) {
|
|
1135
|
+
if (!pkgRoot || !absFile) return false
|
|
1136
|
+
const rel = relative(resolve(pkgRoot), resolve(absFile))
|
|
1137
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel)
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* ルール単位の除外判定(issue #404)。
|
|
1142
|
+
*
|
|
1143
|
+
* 除外語彙は用途ごとに 3 つに分かれている:
|
|
1144
|
+
* - `excludePaths` … ファイルパスに当てる。どのリポジトリでも効く(`.stories.` 等)
|
|
1145
|
+
* - `excludeDsPaths` … ファイルパスに当てるが、**DS パッケージ配下のファイルにだけ**効く
|
|
1146
|
+
* (`components/ui/` 等。consumer の同名ディレクトリでは効かない)
|
|
1147
|
+
* - `excludeLines` … 行の内容に当てる(`data-slot` / `asChild` / CSS 変数名 等)
|
|
1148
|
+
*
|
|
1149
|
+
* v1.62.x までは全部が単一の `excludes` に混在し `file.includes || line.includes` の
|
|
1150
|
+
* OR で判定していたため、(1) 行内にパス文字列が出るだけでルールが黙り、
|
|
1151
|
+
* (2) consumer が `src/components/ui/` に部品を置くと中核 13 ルールが丸ごと無効に
|
|
1152
|
+
* なっていた。旧スキーマの `excludes` は後方互換のため従来の OR 判定で引き続き読む。
|
|
1153
|
+
*/
|
|
1154
|
+
function matchesRuleExclude(rule, { file, line = "", isDsFile = false }) {
|
|
1155
|
+
if (excludeList(rule.excludePaths).some((exclude) => file.includes(exclude))) return true
|
|
1156
|
+
if (isDsFile && excludeList(rule.excludeDsPaths).some((exclude) => file.includes(exclude))) {
|
|
1157
|
+
return true
|
|
1158
|
+
}
|
|
1159
|
+
if (excludeList(rule.excludeLines).some((exclude) => line.includes(exclude))) return true
|
|
1160
|
+
return excludeList(rule.excludes).some(
|
|
1161
|
+
(exclude) => file.includes(exclude) || line.includes(exclude),
|
|
1162
|
+
)
|
|
1082
1163
|
}
|
|
1083
1164
|
|
|
1084
1165
|
function shouldIgnorePath(relPath, options) {
|
|
@@ -102,6 +102,61 @@ export function inspectProductThemeOverrides(source, contract) {
|
|
|
102
102
|
return findings
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* P050 — DS を参照しない並行パレットの検出(issue #393)
|
|
107
|
+
*
|
|
108
|
+
* P049 は「DS の名前空間に触れている CSS」しか見ないため、DS 名前空間を
|
|
109
|
+
* 一切使わない独自パレット(`--bg` / `--accent` / `--surface` 等)を持つ
|
|
110
|
+
* consumer には無言だった。`:root` / `.dark` / `[data-theme=...]` のような
|
|
111
|
+
* ルート的セレクタの中で、DS 名前空間に属さないカスタムプロパティへ
|
|
112
|
+
* 色値(hex / rgb / hsl / oklch)を敷いている数を数え、閾値以上なら
|
|
113
|
+
* 「並行パレットの疑い」として warn する。
|
|
114
|
+
*
|
|
115
|
+
* 値が `var(--Primitive-...)` のように DS トークンを参照しているだけの
|
|
116
|
+
* ものはカウントしない(それは並行パレットではなく DS への委譲)。
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/** ルート的セレクタ(ここで宣言された変数だけがテーマの「パレット」候補) */
|
|
120
|
+
const ROOT_LIKE_BLOCK =
|
|
121
|
+
/(?:^|[};])\s*(:root|\.dark|\[data-theme(?:=[^\]]*)?\])\s*\{([^{}]*)\}/g
|
|
122
|
+
|
|
123
|
+
/** ブロック内のカスタムプロパティ宣言(値も一緒に取る) */
|
|
124
|
+
const DECLARATION_WITH_VALUE = /(^|[;{}\s])(--[A-Za-z0-9_-]+)\s*:\s*([^;]+);/g
|
|
125
|
+
|
|
126
|
+
/** 色値を含む値(hex / rgb() / rgba() / hsl() / hsla() / oklch()) */
|
|
127
|
+
const COLOR_VALUE_RE = /#[0-9a-fA-F]{3,8}\b|\brgba?\(|\bhsla?\(|\boklch\(/i
|
|
128
|
+
|
|
129
|
+
/** 値が `var(...)` の参照だけで完結しているか(DS トークンへの委譲はカウントしない) */
|
|
130
|
+
const VAR_ONLY_RE = /^var\(/i
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {string} source CSS ソース
|
|
134
|
+
* @param {{ namespaces?: string[] }} options DS 名前空間の一覧
|
|
135
|
+
* @returns {Array<{ line: number, name: string }>} DS 名前空間に属さず色値を持つ宣言
|
|
136
|
+
*/
|
|
137
|
+
export function inspectParallelPaletteCandidates(source, { namespaces = [] } = {}) {
|
|
138
|
+
const stripped = stripCssComments(source)
|
|
139
|
+
const candidates = []
|
|
140
|
+
|
|
141
|
+
for (const block of stripped.matchAll(ROOT_LIKE_BLOCK)) {
|
|
142
|
+
const body = block[2]
|
|
143
|
+
const bodyStart = block.index + block[0].indexOf(body)
|
|
144
|
+
for (const decl of body.matchAll(DECLARATION_WITH_VALUE)) {
|
|
145
|
+
const name = decl[2]
|
|
146
|
+
const value = decl[3].trim()
|
|
147
|
+
if (namespaces.some((prefix) => name.startsWith(prefix))) continue
|
|
148
|
+
if (VAR_ONLY_RE.test(value)) continue
|
|
149
|
+
if (!COLOR_VALUE_RE.test(value)) continue
|
|
150
|
+
const nameIndex = bodyStart + decl.index + decl[0].indexOf(name)
|
|
151
|
+
candidates.push({
|
|
152
|
+
line: stripped.slice(0, nameIndex).split(/\r?\n/).length,
|
|
153
|
+
name,
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return candidates
|
|
158
|
+
}
|
|
159
|
+
|
|
105
160
|
/**
|
|
106
161
|
* 契約 JSON から判定に使う形へ畳む。
|
|
107
162
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"name": "KSK Design System — Component Contracts",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.65.0",
|
|
5
5
|
"description": "全コンポーネントの構造化定義。バリアント・アクセシビリティ要件・使用ルールを機械可読形式で管理。",
|
|
6
6
|
"counts": {
|
|
7
7
|
"ui": 68,
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"リンクを Button として表示する場合は <Button asChild><a href=... /></Button> を使う",
|
|
78
78
|
"Icon-only buttons require aria-label",
|
|
79
79
|
"Native Button supports loading/loadingLabel and leadingIcon/trailingIcon; do not wrap ActivityIndicator in Text",
|
|
80
|
+
"Native Button supports spinnerColor to override the loading ActivityIndicator color; default unchanged (falls back to variant fg color, issue #450)",
|
|
80
81
|
"variant=glass は背景がグラデーション/写真の場合のみ使用",
|
|
81
82
|
"variant=glass-inverse は暗い写真・動画・濃色面上の Liquid Glass CTA 専用",
|
|
82
83
|
"variant=inverse / ghost-inverse は暗背景・ヒーローセクション専用",
|
|
@@ -103,7 +104,8 @@
|
|
|
103
104
|
"Must use <Input> not <input>",
|
|
104
105
|
"showCount + maxLength で controlled / uncontrolled / IME 対応の文字数カウンタを表示",
|
|
105
106
|
"showCount 未指定時は wrapper / counter DOM を追加しない",
|
|
106
|
-
"既存の手書き CSS を持つ画面の段階移行では unstyled を使う(showCount / adornment / IME 追従は維持される)"
|
|
107
|
+
"既存の手書き CSS を持つ画面の段階移行では unstyled を使う(showCount / adornment / IME 追従は維持される)",
|
|
108
|
+
"Native Input supports style (outer container View) and inputStyle (inner TextInput) as RN-idiomatic style overrides, merged after DS defaults (issue #447)"
|
|
107
109
|
]
|
|
108
110
|
},
|
|
109
111
|
{
|
|
@@ -211,7 +213,10 @@
|
|
|
211
213
|
"rules": [
|
|
212
214
|
"value/onChange は常に \"YYYY-MM-DD\" 文字列。Date オブジェクトを直接渡さない",
|
|
213
215
|
"strToDate/dateToStr は new Date(y, m-1, d) のローカルタイム方式(TZ ずれ回避)"
|
|
214
|
-
]
|
|
216
|
+
],
|
|
217
|
+
"props": {
|
|
218
|
+
"triggerLabel": "string — trigger のスクリーンリーダー向けラベル。未指定時は選択済みの日付/placeholder が読み上げられる(issue #426。React Native 版は accessibilityLabel)"
|
|
219
|
+
}
|
|
215
220
|
},
|
|
216
221
|
{
|
|
217
222
|
"name": "Textarea",
|
|
@@ -240,6 +245,9 @@
|
|
|
240
245
|
"path": "src/components/ui/label.tsx",
|
|
241
246
|
"description": "フォームラベル。すべての入力コントロールに対して必須。htmlForで入力と紐付ける。",
|
|
242
247
|
"variants": [],
|
|
248
|
+
"props": {
|
|
249
|
+
"unstyled": "boolean — typo-label-md・文字色・peer-disabled 系クラスを出さず Radix Label の挙動だけ提供(段階移行用 / issue #443, #420の続き)"
|
|
250
|
+
},
|
|
243
251
|
"rules": [
|
|
244
252
|
"Always pair with form control via htmlFor"
|
|
245
253
|
]
|
|
@@ -338,7 +346,8 @@
|
|
|
338
346
|
],
|
|
339
347
|
"rules": [
|
|
340
348
|
"rounded-full only",
|
|
341
|
-
"Use DiceBear shapes for placeholder, not pravatar.cc"
|
|
349
|
+
"Use DiceBear shapes for placeholder, not pravatar.cc",
|
|
350
|
+
"native (src/native/components/Avatar.tsx): size はプリセット(sm/md/lg/xl)に加えて pt の数値も可。shape=\"circle\"(既定)|\"rounded\"、rounded の角丸は radius(lg/xl/2xl・既定 xl)。fallback は文字列に加えて ReactNode を受け、独自のフォールバック表示(決定論グラデ+絵文字等)を差し込める。source はリモート URL 文字列も受け取り {uri} に正規化する(issue #449)"
|
|
342
351
|
]
|
|
343
352
|
},
|
|
344
353
|
{
|
|
@@ -402,7 +411,8 @@
|
|
|
402
411
|
"autoFocus は first-input / title / ref / false を指定可能",
|
|
403
412
|
"description prop はRadix管理IDの sr-only description を自動生成する",
|
|
404
413
|
"SheetTitle は level=\"page\"|\"section\"|\"card\" で見出しの役割を指定する(値は contracts/composition.json の textHierarchy と1対1)。未指定時は BottomSheetFrame preset=\"mobile-full\"|\"mobile-page\" 配下で page 相当、それ以外は従来の typo-heading-lg",
|
|
405
|
-
"native (src/native/components/Sheet.tsx): safeArea(既定 true) で side=\"top\" の上端 inset 退避と、画面いっぱいに近い side=\"bottom\" の食い込み分の退避を行う。実測 inset は SafeAreaInsetsProvider から供給し、未供給なら回避量 0(従来どおり)"
|
|
414
|
+
"native (src/native/components/Sheet.tsx): safeArea(既定 true) で side=\"top\" の上端 inset 退避と、画面いっぱいに近い side=\"bottom\" の食い込み分の退避を行う。実測 inset は SafeAreaInsetsProvider から供給し、未供給なら回避量 0(従来どおり)",
|
|
415
|
+
"native (src/native/components/Sheet.tsx): surfaceColor でパネル(および snap モードの footer)の面色を上書きできる。未指定なら theme.surface.primary(従来どおり)。scrim の色は変えない(issue #448)"
|
|
406
416
|
],
|
|
407
417
|
"variants": {
|
|
408
418
|
"titleLevel": [
|
|
@@ -1037,7 +1047,8 @@
|
|
|
1037
1047
|
"3 件以上選択する用途に。少数なら CheckboxGroup を優先"
|
|
1038
1048
|
],
|
|
1039
1049
|
"props": {
|
|
1040
|
-
"clearLabel": "string (既定 \"選択をクリア\") — クリアボタンの aria-label(issue #428)"
|
|
1050
|
+
"clearLabel": "string (既定 \"選択をクリア\") — クリアボタンの aria-label(issue #428)",
|
|
1051
|
+
"triggerLabel": "string — trigger のスクリーンリーダー向けラベル。未指定時は未選択なら placeholder、選択済みならチップの表示テキストが読み上げられる(issue #426。React Native 版は accessibilityLabel)"
|
|
1041
1052
|
}
|
|
1042
1053
|
},
|
|
1043
1054
|
{
|
|
@@ -1592,7 +1603,8 @@
|
|
|
1592
1603
|
"placement: start / end / center",
|
|
1593
1604
|
"bottomOffset: none / bottom-nav / bottom-nav-pill / bottom-nav-pill-inline",
|
|
1594
1605
|
"keyboardBehavior: hide / lift / stay",
|
|
1595
|
-
"fullWidth: true / false"
|
|
1606
|
+
"fullWidth: true / false",
|
|
1607
|
+
"bottomOffsetExtra: CSS length(web)/ number pt(native)。bottomOffset プリセット値へ加算する任意の追加オフセット"
|
|
1596
1608
|
],
|
|
1597
1609
|
"accessibility": [
|
|
1598
1610
|
"label prop is required and used as aria-label"
|
|
@@ -1604,7 +1616,8 @@
|
|
|
1604
1616
|
"pill BottomTabBar の上に重ねる場合は bottomOffset=bottom-nav-pill、横並びに置く場合は bottomOffset=bottom-nav-pill-inline を使う",
|
|
1605
1617
|
"keyboard 表示中に入力を邪魔する場合は keyboardBehavior=hide を使う",
|
|
1606
1618
|
"variant=glass に shadow-* を重ねない(glass-accent が inset 3層 + drop shadow を内包するため二重になる)",
|
|
1607
|
-
"下部固定の全幅 CTA は fullWidth を使い、fixed inset-x + Button を手組みしない。fullWidth 時は placement が無視されラベルが常に表示される"
|
|
1619
|
+
"下部固定の全幅 CTA は fullWidth を使い、fixed inset-x + Button を手組みしない。fullWidth 時は placement が無視されラベルが常に表示される",
|
|
1620
|
+
"広告バナー等で動的に持ち上げる必要があるときは、内部変数 --ksk-fab-bottom-offset を直接上書きせず bottomOffsetExtra を使う(issue #453)"
|
|
1608
1621
|
]
|
|
1609
1622
|
},
|
|
1610
1623
|
{
|
|
@@ -2196,7 +2209,8 @@
|
|
|
2196
2209
|
],
|
|
2197
2210
|
"rules": [
|
|
2198
2211
|
"長い SheetContent className recipe を consumer 側に重複させず BottomSheetFrame preset を使う",
|
|
2199
|
-
"フォーム/詳細の中身は DetailSheetScaffold と KeyboardAwareSheetFooter を使う"
|
|
2212
|
+
"フォーム/詳細の中身は DetailSheetScaffold と KeyboardAwareSheetFooter を使う",
|
|
2213
|
+
"native (src/native/components/BottomSheetFrame.tsx): height=\"preset\"(既定・従来の固定高)|\"fitContent\"(min/max を外して内容量に追従)。maxHeight で上限だけ明示できる。safeArea(既定 false) で bottom inset 分の内側余白、handle(既定 false) で上端のつまみを表示(issue #448)"
|
|
2200
2214
|
]
|
|
2201
2215
|
},
|
|
2202
2216
|
{
|