sparkle-design-cli 1.3.8 → 1.3.10
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 +126 -17
- package/bin/sparkle-design.js +212 -53
- package/lib/anti-pattern-rules.js +901 -0
- package/lib/check.js +174 -0
- package/lib/setup.js +260 -0
- package/package.json +3 -2
|
@@ -0,0 +1,901 @@
|
|
|
1
|
+
const COMMON_INTRO =
|
|
2
|
+
'以下は頻繁に発生する誤用パターンです。コンポーネントが提供する専用 props・サブコンポーネントを使ってください。';
|
|
3
|
+
|
|
4
|
+
function lines(values) {
|
|
5
|
+
return values.join('\n');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function renderJSDocSection(section) {
|
|
9
|
+
const output = ['**アンチパターン / Anti-patterns**', ''];
|
|
10
|
+
|
|
11
|
+
for (const bullet of section.bullets) {
|
|
12
|
+
output.push(`- ${bullet.ja}`);
|
|
13
|
+
output.push(` en: ${bullet.en}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (section.example) {
|
|
17
|
+
output.push('');
|
|
18
|
+
output.push('```tsx');
|
|
19
|
+
output.push(...section.example.split('\n'));
|
|
20
|
+
output.push('```');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return output.map((line) => (line ? ` * ${line}` : ' *')).join('\n');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const MANUAL_REVIEW_REMINDERS = [
|
|
28
|
+
{
|
|
29
|
+
id: 'badge-tag-semantics',
|
|
30
|
+
message:
|
|
31
|
+
'Badge と Tag の使い分けが意味的に正しいか確認してください。数値情報は Badge、ラベリングやステータス表示は Tag を使います。',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'card-description-typography',
|
|
35
|
+
message:
|
|
36
|
+
'CardDescription に typography / color token が明示されているか確認してください。補足テキストには className で token を指定します。',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'dialog-modal-ux',
|
|
40
|
+
message:
|
|
41
|
+
'Dialog が確認用途、Modal が入力・詳細表示用途になっているか確認してください。文脈判断が必要なケースは機械検出されません。',
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const ANTI_PATTERN_GROUPS = [
|
|
46
|
+
{
|
|
47
|
+
id: 'card-description',
|
|
48
|
+
featureSection: lines([
|
|
49
|
+
'### CardTitle の補足テキストには CardDescription を使う',
|
|
50
|
+
'',
|
|
51
|
+
'```tsx',
|
|
52
|
+
'// ✅ Correct',
|
|
53
|
+
'<CardHeader>',
|
|
54
|
+
' <CardTitle>',
|
|
55
|
+
' プロジェクト一覧',
|
|
56
|
+
' <CardDescription className="character-3-regular-pro text-text-low">',
|
|
57
|
+
' 全 12 件',
|
|
58
|
+
' </CardDescription>',
|
|
59
|
+
' </CardTitle>',
|
|
60
|
+
'</CardHeader>',
|
|
61
|
+
'',
|
|
62
|
+
'// ❌ Wrong — CardTitle 内に span で補足を入れない',
|
|
63
|
+
'<CardHeader>',
|
|
64
|
+
' <CardTitle>',
|
|
65
|
+
' プロジェクト一覧',
|
|
66
|
+
' <span className="text-sm text-neutral-500">(12件)</span>',
|
|
67
|
+
' </CardTitle>',
|
|
68
|
+
'</CardHeader>',
|
|
69
|
+
'```',
|
|
70
|
+
'',
|
|
71
|
+
'CardTitle 内の補足情報や件数は CardDescription を使い、必要な typography / color token は className で明示する。長い説明文は CardContent に配置する。',
|
|
72
|
+
]),
|
|
73
|
+
jsdocTargets: [
|
|
74
|
+
{
|
|
75
|
+
file: 'src/components/ui/card/index.tsx',
|
|
76
|
+
targetName: 'CardDescription',
|
|
77
|
+
section: {
|
|
78
|
+
bullets: [
|
|
79
|
+
{
|
|
80
|
+
ja: '`CardDescription` は CardTitle 内の短い補足テキスト用です。長い説明文は `CardContent` に配置してください。',
|
|
81
|
+
en: '`CardDescription` is for short supporting text inside CardTitle. Put long descriptive copy inside `CardContent`.',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
ja: 'Typography や text color は用途に応じて className で明示してください。',
|
|
85
|
+
en: 'Specify typography and text color explicitly with className based on the use case.',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
example: lines([
|
|
89
|
+
'// ✅ Correct',
|
|
90
|
+
'<CardTitle>',
|
|
91
|
+
' プロジェクト一覧',
|
|
92
|
+
' <CardDescription className="character-3-regular-pro text-text-low">',
|
|
93
|
+
' 全 12 件',
|
|
94
|
+
' </CardDescription>',
|
|
95
|
+
'</CardTitle>',
|
|
96
|
+
'',
|
|
97
|
+
'// ❌ Wrong - 長い説明文を CardTitle 内に入れない',
|
|
98
|
+
'<CardTitle>',
|
|
99
|
+
' プロジェクト一覧',
|
|
100
|
+
' <CardDescription>',
|
|
101
|
+
' このカードはダッシュボードで重要な進捗と担当者の状態を表示します。',
|
|
102
|
+
' </CardDescription>',
|
|
103
|
+
'</CardTitle>',
|
|
104
|
+
]),
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'size-alignment',
|
|
111
|
+
featureSection: lines([
|
|
112
|
+
'### Input / Select と横並びの Button はサイズを揃える',
|
|
113
|
+
'',
|
|
114
|
+
'```tsx',
|
|
115
|
+
'// ✅ Correct — 同じサイズで統一',
|
|
116
|
+
'<div className="flex gap-2">',
|
|
117
|
+
' <Input placeholder="検索..." />',
|
|
118
|
+
' <Button size="md">検索</Button>',
|
|
119
|
+
'</div>',
|
|
120
|
+
'',
|
|
121
|
+
'// ❌ Wrong — サイズ不一致で高さが揃わない',
|
|
122
|
+
'<div className="flex gap-2">',
|
|
123
|
+
' <Input placeholder="検索..." />',
|
|
124
|
+
' <Button size="sm">検索</Button>',
|
|
125
|
+
'</div>',
|
|
126
|
+
'```',
|
|
127
|
+
'',
|
|
128
|
+
'Input / Select / Textarea のデフォルトサイズは md。横並びの Button も原則 md に合わせる。省スペース UI では Input と Button を揃えて sm にしてよい。テーブル内アクションなど独立した Button は sm でよい。',
|
|
129
|
+
]),
|
|
130
|
+
jsdocTargets: [
|
|
131
|
+
{
|
|
132
|
+
file: 'src/components/ui/input/index.tsx',
|
|
133
|
+
targetName: 'Input',
|
|
134
|
+
section: {
|
|
135
|
+
bullets: [
|
|
136
|
+
{
|
|
137
|
+
ja: 'Input の横にアイコンボタンを手動で配置しないでください。`isTrigger` / `triggerIcon` props を使用してください。',
|
|
138
|
+
en: 'Do not manually place an IconButton next to Input. Use `isTrigger` / `triggerIcon` props instead.',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
ja: 'Input と横並びの Button は原則同じサイズにしてください。デフォルトの Input には `Button size="md"`、省スペース UI では `Input size="sm"` と `Button size="sm"` を使ってください。',
|
|
142
|
+
en: 'Keep Button size aligned when placing it next to Input. Use `Button size="md"` with the default Input, or pair `Input size="sm"` with `Button size="sm"` in compact UIs.',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
ja: '無効状態は `isDisabled` を優先して使ってください。HTML 標準の `disabled` も互換のため受け付けますが、Sparkle Design のコードでは `isDisabled` に統一します。',
|
|
146
|
+
en: 'Prefer `isDisabled` for disabled state. The native `disabled` prop is still supported for compatibility, but Sparkle Design code should standardize on `isDisabled`.',
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
example: lines([
|
|
150
|
+
'// ✅ Correct',
|
|
151
|
+
'<>',
|
|
152
|
+
' <Input',
|
|
153
|
+
' isTrigger',
|
|
154
|
+
' triggerIcon="search"',
|
|
155
|
+
' triggerAriaLabel="検索"',
|
|
156
|
+
' onIconButtonClick={() => {}}',
|
|
157
|
+
' />',
|
|
158
|
+
' <div className="flex gap-2">',
|
|
159
|
+
' <Input placeholder="検索..." />',
|
|
160
|
+
' <Button size="md">検索</Button>',
|
|
161
|
+
' </div>',
|
|
162
|
+
' <div className="flex gap-2">',
|
|
163
|
+
' <Input size="sm" placeholder="メッセージ" />',
|
|
164
|
+
' <Button size="sm">送信</Button>',
|
|
165
|
+
' </div>',
|
|
166
|
+
' <Input isDisabled placeholder="無効状態" />',
|
|
167
|
+
'</>',
|
|
168
|
+
'',
|
|
169
|
+
'// ❌ Wrong - 手動配置',
|
|
170
|
+
'<div className="flex">',
|
|
171
|
+
' <Input />',
|
|
172
|
+
' <IconButton icon="search" aria-label="検索" />',
|
|
173
|
+
'</div>',
|
|
174
|
+
'',
|
|
175
|
+
'// ❌ Wrong - サイズ不一致',
|
|
176
|
+
'<div className="flex gap-2">',
|
|
177
|
+
' <Input placeholder="検索..." />',
|
|
178
|
+
' <Button size="sm">検索</Button>',
|
|
179
|
+
'</div>',
|
|
180
|
+
]),
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
file: 'src/components/ui/select/index.tsx',
|
|
185
|
+
targetName: 'Select',
|
|
186
|
+
section: {
|
|
187
|
+
bullets: [
|
|
188
|
+
{
|
|
189
|
+
ja: '`SelectTrigger` と横並びの Button は原則同じサイズにしてください。デフォルトの `SelectTrigger` に対しては `Button size="md"` を使ってください。',
|
|
190
|
+
en: 'Keep Button size aligned when placing it next to `SelectTrigger`. Use `Button size="md"` with the default `SelectTrigger` size.',
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
example: lines([
|
|
194
|
+
'// ✅ Correct',
|
|
195
|
+
'<div className="flex gap-2">',
|
|
196
|
+
' <Select>',
|
|
197
|
+
' <SelectTrigger size="md" className="w-60">',
|
|
198
|
+
' <SelectValue placeholder="選択してください" />',
|
|
199
|
+
' </SelectTrigger>',
|
|
200
|
+
' </Select>',
|
|
201
|
+
' <Button size="md">追加</Button>',
|
|
202
|
+
'</div>',
|
|
203
|
+
'',
|
|
204
|
+
'// ❌ Wrong - 高さが揃わないサイズ不一致',
|
|
205
|
+
'<div className="flex gap-2">',
|
|
206
|
+
' <Select>',
|
|
207
|
+
' <SelectTrigger size="md" className="w-60">',
|
|
208
|
+
' <SelectValue placeholder="選択してください" />',
|
|
209
|
+
' </SelectTrigger>',
|
|
210
|
+
' </Select>',
|
|
211
|
+
' <Button size="sm">追加</Button>',
|
|
212
|
+
'</div>',
|
|
213
|
+
]),
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: 'badge-tag',
|
|
220
|
+
featureSection: lines([
|
|
221
|
+
'### Badge と Tag を用途で使い分ける',
|
|
222
|
+
'',
|
|
223
|
+
'| コンポーネント | 用途 | 例 |',
|
|
224
|
+
'| --- | --- | --- |',
|
|
225
|
+
'| **Badge** | 特定の要素に**数値情報**を付与 | `<Badge>3</Badge>` |',
|
|
226
|
+
'| **Tag** | 情報の**ラベリング / ステータス付与** | `<Tag status="success">完了</Tag>` |',
|
|
227
|
+
'',
|
|
228
|
+
'```tsx',
|
|
229
|
+
'// ✅ Correct — ステータス表示には Tag を使う',
|
|
230
|
+
'<Tag variant="outline" status="warning">未紐付け</Tag>',
|
|
231
|
+
'',
|
|
232
|
+
'// ❌ Wrong — Badge をステータスラベルに使わない',
|
|
233
|
+
'<Badge>未紐付け</Badge>',
|
|
234
|
+
'```',
|
|
235
|
+
]),
|
|
236
|
+
jsdocTargets: [
|
|
237
|
+
{
|
|
238
|
+
file: 'src/components/ui/badge/index.tsx',
|
|
239
|
+
targetName: 'Badge',
|
|
240
|
+
section: {
|
|
241
|
+
bullets: [
|
|
242
|
+
{
|
|
243
|
+
ja: 'ステータス表示や分類ラベルには `Badge` を使わず、`Tag` を使ってください。',
|
|
244
|
+
en: 'Do not use `Badge` for status display or category labels. Use `Tag` instead.',
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
example: lines([
|
|
248
|
+
'// ✅ Correct',
|
|
249
|
+
'<>',
|
|
250
|
+
' <Badge>3</Badge>',
|
|
251
|
+
' <Tag status="warning" variant="outline">未紐付け</Tag>',
|
|
252
|
+
'</>',
|
|
253
|
+
'',
|
|
254
|
+
'// ❌ Wrong - ステータスラベルに Badge を使わない',
|
|
255
|
+
'<Badge>未紐付け</Badge>',
|
|
256
|
+
]),
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
file: 'src/components/ui/tag/index.tsx',
|
|
261
|
+
targetName: 'Tag',
|
|
262
|
+
section: {
|
|
263
|
+
bullets: [
|
|
264
|
+
{
|
|
265
|
+
ja: '通知数や件数などの数値情報には `Tag` を使わず、`Badge` を使ってください。',
|
|
266
|
+
en: 'Do not use `Tag` for numeric information such as notification counts. Use `Badge` instead.',
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
example: lines([
|
|
270
|
+
'// ✅ Correct',
|
|
271
|
+
'<>',
|
|
272
|
+
' <Tag status="success">完了</Tag>',
|
|
273
|
+
' <Badge>3</Badge>',
|
|
274
|
+
'</>',
|
|
275
|
+
'',
|
|
276
|
+
'// ❌ Wrong - 件数表示に Tag を使わない',
|
|
277
|
+
'<Tag status="info">3件</Tag>',
|
|
278
|
+
]),
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id: 'shadcn-token',
|
|
285
|
+
check: {
|
|
286
|
+
description: 'shadcn/ui 既定 token を Sparkle Design 内へ持ち込まない',
|
|
287
|
+
recommendation:
|
|
288
|
+
'text-muted-foreground / bg-background / border-border などは Sparkle Design token に置き換えてください。',
|
|
289
|
+
pattern: /\b(text-muted-foreground|bg-background|border-border)\b/g,
|
|
290
|
+
},
|
|
291
|
+
featureSection: lines([
|
|
292
|
+
'### shadcn/ui 由来の class / token をそのまま使わない',
|
|
293
|
+
'',
|
|
294
|
+
'```tsx',
|
|
295
|
+
'// ✅ Correct — Sparkle Design の typography / color token を使う',
|
|
296
|
+
'<CardDescription className="character-3-regular-pro text-text-low">',
|
|
297
|
+
' 全 12 件',
|
|
298
|
+
'</CardDescription>',
|
|
299
|
+
'',
|
|
300
|
+
'// ❌ Wrong — shadcn/ui の既定 token や素の font utility を持ち込まない',
|
|
301
|
+
'<CardDescription className="text-sm text-muted-foreground">',
|
|
302
|
+
' 全 12 件',
|
|
303
|
+
'</CardDescription>',
|
|
304
|
+
'<CardDescription className="font-medium text-slate-500">',
|
|
305
|
+
' 全 12 件',
|
|
306
|
+
'</CardDescription>',
|
|
307
|
+
'```',
|
|
308
|
+
'',
|
|
309
|
+
'shadcn/ui と混在するプロジェクトでも、Sparkle Design のコンポーネント内では `character-*` / `text-text-*` / Sparkle の color token を優先する。',
|
|
310
|
+
]),
|
|
311
|
+
jsdocTargets: [],
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
id: 'button-icons',
|
|
315
|
+
featureSection: lines([
|
|
316
|
+
'### Button: prefixIcon / suffixIcon を使う',
|
|
317
|
+
'',
|
|
318
|
+
'```tsx',
|
|
319
|
+
'// ✅ Correct',
|
|
320
|
+
'<Button prefixIcon="check">確定</Button>',
|
|
321
|
+
'<Button suffixIcon="arrow_forward">次へ</Button>',
|
|
322
|
+
'',
|
|
323
|
+
'// ❌ Wrong - Icon を children に入れない(レイアウト崩れ・ローディング時に非表示にならない)',
|
|
324
|
+
'<Button><Icon icon="check" /> 確定</Button>',
|
|
325
|
+
'',
|
|
326
|
+
'// ❌ Wrong - children なしの Button + prefixIcon は IconButton に置き換える',
|
|
327
|
+
'<Button prefixIcon="close" aria-label="閉じる" />',
|
|
328
|
+
'',
|
|
329
|
+
'// ❌ Wrong - asChild では prefixIcon / suffixIcon / isLoading は反映されない',
|
|
330
|
+
'<Button asChild prefixIcon="add">',
|
|
331
|
+
' <Link href="/items/new">新規作成</Link>',
|
|
332
|
+
'</Button>',
|
|
333
|
+
'',
|
|
334
|
+
'// ✅ Correct',
|
|
335
|
+
'<IconButton icon="edit" aria-label="編集" />',
|
|
336
|
+
'<Button asChild>',
|
|
337
|
+
' <Link href="/items/new">新規作成</Link>',
|
|
338
|
+
'</Button>',
|
|
339
|
+
'```',
|
|
340
|
+
'',
|
|
341
|
+
'> Button は size に応じてアイコンサイズを自動設定する(sm→4, md→5, lg→6)。isLoading 時にアイコンを自動で非表示にする。`asChild` 使用時はアイコンやローディング表現をスロット先で組み立てる。',
|
|
342
|
+
]),
|
|
343
|
+
jsdocTargets: [
|
|
344
|
+
{
|
|
345
|
+
file: 'src/components/ui/button/index.tsx',
|
|
346
|
+
targetName: 'Button',
|
|
347
|
+
section: {
|
|
348
|
+
bullets: [
|
|
349
|
+
{
|
|
350
|
+
ja: '`<Icon>` を children として渡さないでください。`prefixIcon` / `suffixIcon` props を使用してください。',
|
|
351
|
+
en: 'Do not pass `<Icon>` as children. Use `prefixIcon` / `suffixIcon` props instead.',
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
ja: 'アイコンのみのボタンには `IconButton` を使用してください。',
|
|
355
|
+
en: 'Use `IconButton` for icon-only buttons.',
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
ja: '`asChild` 使用時は `prefixIcon` / `suffixIcon` / `isLoading` が反映されません。必要ならスロット先でアイコンやローディング表現を構成してください。',
|
|
359
|
+
en: 'In `asChild` mode, `prefixIcon`, `suffixIcon`, and `isLoading` are ignored. Render icons and loading states inside the slotted child when needed.',
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
example: lines([
|
|
363
|
+
'// ✅ Correct',
|
|
364
|
+
'<Button prefixIcon="check">確定</Button>',
|
|
365
|
+
'',
|
|
366
|
+
'// ❌ Wrong - Icon を children に入れない',
|
|
367
|
+
'<Button><Icon icon="check" /> 確定</Button>',
|
|
368
|
+
'',
|
|
369
|
+
'// ❌ Wrong - children なしの Button + prefixIcon は IconButton に置き換える',
|
|
370
|
+
'<Button prefixIcon="close" aria-label="閉じる" />',
|
|
371
|
+
'',
|
|
372
|
+
'// ❌ Wrong - asChild では prefixIcon は反映されない',
|
|
373
|
+
'<Button asChild prefixIcon="add">',
|
|
374
|
+
' <Link href="/items/new">新規作成</Link>',
|
|
375
|
+
'</Button>',
|
|
376
|
+
'',
|
|
377
|
+
'// ✅ Correct',
|
|
378
|
+
'<IconButton icon="edit" aria-label="編集" />',
|
|
379
|
+
'<Button asChild>',
|
|
380
|
+
' <Link href="/items/new">新規作成</Link>',
|
|
381
|
+
'</Button>',
|
|
382
|
+
]),
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: 'card-control',
|
|
389
|
+
featureSection: lines([
|
|
390
|
+
'### CardHeader: CardControl を使う',
|
|
391
|
+
'',
|
|
392
|
+
'```tsx',
|
|
393
|
+
'// ✅ Correct',
|
|
394
|
+
'<CardHeader>',
|
|
395
|
+
' <CardTitle>タイトル</CardTitle>',
|
|
396
|
+
' <CardControl>',
|
|
397
|
+
' <Button theme="neutral" variant="outline">キャンセル</Button>',
|
|
398
|
+
' <Button>保存</Button>',
|
|
399
|
+
' </CardControl>',
|
|
400
|
+
'</CardHeader>',
|
|
401
|
+
'',
|
|
402
|
+
'// ❌ Wrong - 手動 flex を使わない(CardHeader は内部で flex justify-between を適用済み)',
|
|
403
|
+
'<CardHeader>',
|
|
404
|
+
' <div className="flex justify-between">',
|
|
405
|
+
' <CardTitle>タイトル</CardTitle>',
|
|
406
|
+
' <Button>アクション</Button>',
|
|
407
|
+
' </div>',
|
|
408
|
+
'</CardHeader>',
|
|
409
|
+
'```',
|
|
410
|
+
'',
|
|
411
|
+
'`CardControl` は既定で `flex items-center gap-2` を持つ。複数アクションでも追加の layout class は不要。',
|
|
412
|
+
]),
|
|
413
|
+
jsdocTargets: [
|
|
414
|
+
{
|
|
415
|
+
file: 'src/components/ui/card/index.tsx',
|
|
416
|
+
targetName: 'CardHeader',
|
|
417
|
+
section: {
|
|
418
|
+
bullets: [
|
|
419
|
+
{
|
|
420
|
+
ja: 'CardHeader 内で手動の flex レイアウト(`<div className="flex justify-between">`)を使わないでください。CardHeader は内部で flex レイアウトを適用済みです。アクションボタンは `CardControl` で囲んでください。',
|
|
421
|
+
en: 'Do not use manual flex layout inside CardHeader. CardHeader already applies flex layout internally. Wrap action buttons with `CardControl`.',
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
ja: 'CardTitle の補足情報や件数は、`span` などを直書きせず `CardDescription` を使ってください。',
|
|
425
|
+
en: 'For supporting text or counts inside CardTitle, do not inline a `span`; use `CardDescription`.',
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
ja: '`CardDescription` はタイトルの補足テキスト用です。長い説明文は `CardContent` に配置してください。',
|
|
429
|
+
en: 'Use `CardDescription` for short supporting text in CardTitle. Put long descriptive copy inside `CardContent`.',
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
example: lines([
|
|
433
|
+
'// ✅ Correct',
|
|
434
|
+
'<CardHeader>',
|
|
435
|
+
' <CardTitle>',
|
|
436
|
+
' タイトル',
|
|
437
|
+
' <CardDescription className="character-3-regular-pro text-text-low">',
|
|
438
|
+
' 全 12 件',
|
|
439
|
+
' </CardDescription>',
|
|
440
|
+
' </CardTitle>',
|
|
441
|
+
' <CardControl>',
|
|
442
|
+
' <Button theme="neutral" variant="outline">キャンセル</Button>',
|
|
443
|
+
' <Button>保存</Button>',
|
|
444
|
+
' </CardControl>',
|
|
445
|
+
'</CardHeader>',
|
|
446
|
+
'',
|
|
447
|
+
'// ❌ Wrong - 手動 flex を使わない',
|
|
448
|
+
'<CardHeader>',
|
|
449
|
+
' <div className="flex justify-between">',
|
|
450
|
+
' <CardTitle>タイトル</CardTitle>',
|
|
451
|
+
' <Button theme="neutral" variant="outline">キャンセル</Button>',
|
|
452
|
+
' <Button>保存</Button>',
|
|
453
|
+
' </div>',
|
|
454
|
+
'</CardHeader>',
|
|
455
|
+
'',
|
|
456
|
+
'// ❌ Wrong - CardTitle 内に補足を入れない',
|
|
457
|
+
'<CardHeader>',
|
|
458
|
+
' <CardTitle>',
|
|
459
|
+
' タイトル',
|
|
460
|
+
' <span className="text-sm text-neutral-500">全 12 件</span>',
|
|
461
|
+
' </CardTitle>',
|
|
462
|
+
'</CardHeader>',
|
|
463
|
+
]),
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
file: 'src/components/ui/card/index.tsx',
|
|
468
|
+
targetName: 'CardControl',
|
|
469
|
+
section: {
|
|
470
|
+
bullets: [
|
|
471
|
+
{
|
|
472
|
+
ja: 'CardHeader の右側アクションは `CardControl` に集約してください。複数ボタンでも追加の layout class は不要です。',
|
|
473
|
+
en: 'Place right-side actions in `CardControl`. No extra layout classes are needed even with multiple buttons.',
|
|
474
|
+
},
|
|
475
|
+
],
|
|
476
|
+
example: lines([
|
|
477
|
+
'// ✅ Correct',
|
|
478
|
+
'<CardControl>',
|
|
479
|
+
' <Button theme="neutral" variant="outline">キャンセル</Button>',
|
|
480
|
+
' <Button>保存</Button>',
|
|
481
|
+
'</CardControl>',
|
|
482
|
+
]),
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
id: 'icon-scale',
|
|
489
|
+
featureSection: lines([
|
|
490
|
+
'### Icon / Spinner: スケール値(1-12)を使う',
|
|
491
|
+
'',
|
|
492
|
+
'```tsx',
|
|
493
|
+
'// ✅ Correct - スケール値',
|
|
494
|
+
'<Icon icon="settings" size={6} /> // 24px 相当',
|
|
495
|
+
'',
|
|
496
|
+
'// ❌ Wrong - ピクセル値は受け付けない',
|
|
497
|
+
'<Icon icon="settings" size={24} />',
|
|
498
|
+
'```',
|
|
499
|
+
'',
|
|
500
|
+
'> スケール対応表は上記「Icon Size Scale」セクション参照。',
|
|
501
|
+
]),
|
|
502
|
+
jsdocTargets: [
|
|
503
|
+
{
|
|
504
|
+
file: 'src/components/ui/icon/index.tsx',
|
|
505
|
+
targetName: 'Icon',
|
|
506
|
+
section: {
|
|
507
|
+
bullets: [
|
|
508
|
+
{
|
|
509
|
+
ja: '`size` にピクセル値(24, 32 など)を渡さないでください。スケール値(1-12)を使用してください。',
|
|
510
|
+
en: 'Do not pass pixel values to `size`. Use scale values (1-12) instead.',
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
example: lines([
|
|
514
|
+
'// ✅ Correct - スケール値',
|
|
515
|
+
'<Icon icon="settings" size={6} /> // 24px 相当',
|
|
516
|
+
'',
|
|
517
|
+
'// ❌ Wrong - ピクセル値',
|
|
518
|
+
'<Icon icon="settings" size={24} />',
|
|
519
|
+
]),
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
file: 'src/components/ui/spinner/index.tsx',
|
|
524
|
+
targetName: 'Spinner',
|
|
525
|
+
section: {
|
|
526
|
+
bullets: [
|
|
527
|
+
{
|
|
528
|
+
ja: '`size` にピクセル値(24, 32 など)を渡さないでください。スケール値(1-12)を使用してください。',
|
|
529
|
+
en: 'Do not pass pixel values to `size`. Use scale values (1-12) instead.',
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
example: lines([
|
|
533
|
+
'// ✅ Correct - スケール値',
|
|
534
|
+
'<Spinner size={6} />',
|
|
535
|
+
'',
|
|
536
|
+
'// ❌ Wrong - ピクセル値',
|
|
537
|
+
'<Spinner size={24} />',
|
|
538
|
+
]),
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
id: 'button-icon-only',
|
|
545
|
+
check: {
|
|
546
|
+
description: 'children なしの Button に prefixIcon / suffixIcon を使わない',
|
|
547
|
+
recommendation:
|
|
548
|
+
'アイコンのみのアクションは IconButton を使ってください。Button に prefixIcon / suffixIcon だけを渡さないでください。',
|
|
549
|
+
pattern:
|
|
550
|
+
/<Button\b(?=[^>]*\b(?:prefixIcon|suffixIcon)=)[^>]*\/\s*>|<Button\b(?=[^>]*\b(?:prefixIcon|suffixIcon)=)[^>]*>\s*<\/Button>/g,
|
|
551
|
+
},
|
|
552
|
+
featureSection: lines([
|
|
553
|
+
'### children なしの Button + prefixIcon / suffixIcon を使わない',
|
|
554
|
+
'',
|
|
555
|
+
'```tsx',
|
|
556
|
+
'// ✅ Correct',
|
|
557
|
+
'<IconButton icon="close" aria-label="閉じる" />',
|
|
558
|
+
'',
|
|
559
|
+
'// ❌ Wrong - children なしの Button + prefixIcon',
|
|
560
|
+
'<Button prefixIcon="close" aria-label="閉じる" />',
|
|
561
|
+
'```',
|
|
562
|
+
]),
|
|
563
|
+
jsdocTargets: [],
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
id: 'material-symbols-direct',
|
|
567
|
+
check: {
|
|
568
|
+
description: 'Material Symbols を className 直書きで使わない',
|
|
569
|
+
recommendation:
|
|
570
|
+
'Material Symbols は Icon / IconButton コンポーネント経由で使ってください。`material-symbols-rounded` を直接書かないでください。',
|
|
571
|
+
pattern:
|
|
572
|
+
/<[^>]+\bclassName\s*=\s*(?:"[^"]*\bmaterial-symbols-rounded\b[^"]*"|'[^']*\bmaterial-symbols-rounded\b[^']*'|\{[^}]*\bmaterial-symbols-rounded\b[^}]*\})[^>]*>/g,
|
|
573
|
+
},
|
|
574
|
+
featureSection: lines([
|
|
575
|
+
'### Material Symbols を直書きしない',
|
|
576
|
+
'',
|
|
577
|
+
'```tsx',
|
|
578
|
+
'// ✅ Correct',
|
|
579
|
+
'<Icon icon="content_copy" size={4} />',
|
|
580
|
+
'<IconButton icon="content_copy" size="xs" aria-label="コピー" />',
|
|
581
|
+
'',
|
|
582
|
+
'// ❌ Wrong - material-symbols-rounded を直書きしない',
|
|
583
|
+
'<span className="material-symbols-rounded text-sm">content_copy</span>',
|
|
584
|
+
'```',
|
|
585
|
+
'',
|
|
586
|
+
'Material Symbols は `Icon` / `IconButton` 経由で使用する。直書きするとフォント読み込みタイミングでアイコン名テキストが見えることがある。',
|
|
587
|
+
]),
|
|
588
|
+
jsdocTargets: [
|
|
589
|
+
{
|
|
590
|
+
file: 'src/components/ui/icon/index.tsx',
|
|
591
|
+
targetName: 'Icon',
|
|
592
|
+
section: {
|
|
593
|
+
bullets: [
|
|
594
|
+
{
|
|
595
|
+
ja: '`material-symbols-rounded` を `span` に直書きせず、`Icon` を使ってください。',
|
|
596
|
+
en: 'Use `Icon` instead of writing `material-symbols-rounded` directly on a span.',
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
example: lines([
|
|
600
|
+
'// ✅ Correct',
|
|
601
|
+
'<Icon icon="content_copy" size={4} />',
|
|
602
|
+
'',
|
|
603
|
+
'// ❌ Wrong - className を直書きしない',
|
|
604
|
+
'<span className="material-symbols-rounded text-sm">content_copy</span>',
|
|
605
|
+
]),
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
file: 'src/components/ui/icon-button/index.tsx',
|
|
610
|
+
targetName: 'IconButton',
|
|
611
|
+
section: {
|
|
612
|
+
bullets: [
|
|
613
|
+
{
|
|
614
|
+
ja: 'アイコンだけのアクションでは `Button` + `prefixIcon` ではなく `IconButton` を使ってください。',
|
|
615
|
+
en: 'Use `IconButton` for icon-only actions instead of `Button` with `prefixIcon`.',
|
|
616
|
+
},
|
|
617
|
+
],
|
|
618
|
+
example: lines([
|
|
619
|
+
'// ✅ Correct',
|
|
620
|
+
'<IconButton icon="content_copy" aria-label="コピー" />',
|
|
621
|
+
'',
|
|
622
|
+
'// ❌ Wrong - Icon-only action を Button で表現しない',
|
|
623
|
+
'<Button prefixIcon="content_copy" aria-label="コピー" />',
|
|
624
|
+
]),
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
],
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
id: 'dialog-button-wrap',
|
|
631
|
+
check: {
|
|
632
|
+
description: 'DialogCancel/DialogAction を Button で二重ラップしない',
|
|
633
|
+
recommendation:
|
|
634
|
+
'DialogCancel と DialogAction は内部で Button を描画するので、children に Button を入れないでください。',
|
|
635
|
+
pattern: /<(DialogCancel|DialogAction)\b[^>]*>\s*<Button\b/g,
|
|
636
|
+
},
|
|
637
|
+
featureSection: lines([
|
|
638
|
+
'### DialogCancel / DialogAction: Button で二重ラップしない',
|
|
639
|
+
'',
|
|
640
|
+
'```tsx',
|
|
641
|
+
'// ✅ Correct - 文字列を渡すだけ(内部で Button を描画)',
|
|
642
|
+
'<DialogCancel>キャンセル</DialogCancel>',
|
|
643
|
+
'<DialogAction>確定</DialogAction>',
|
|
644
|
+
'',
|
|
645
|
+
'// ❌ Wrong - 二重ラップ',
|
|
646
|
+
'<DialogCancel><Button>キャンセル</Button></DialogCancel>',
|
|
647
|
+
'```',
|
|
648
|
+
]),
|
|
649
|
+
jsdocTargets: [
|
|
650
|
+
{
|
|
651
|
+
file: 'src/components/ui/dialog/index.tsx',
|
|
652
|
+
targetName: 'DialogCancel',
|
|
653
|
+
section: {
|
|
654
|
+
bullets: [
|
|
655
|
+
{
|
|
656
|
+
ja: '`DialogCancel` の children に `<Button>` を渡さないでください。内部で Button を描画します。',
|
|
657
|
+
en: 'Do not pass `<Button>` as children. DialogCancel renders a Button internally.',
|
|
658
|
+
},
|
|
659
|
+
],
|
|
660
|
+
example: lines([
|
|
661
|
+
'// ✅ Correct',
|
|
662
|
+
'<DialogCancel>キャンセル</DialogCancel>',
|
|
663
|
+
'',
|
|
664
|
+
'// ❌ Wrong - 二重ラップ',
|
|
665
|
+
'<DialogCancel><Button>キャンセル</Button></DialogCancel>',
|
|
666
|
+
]),
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
file: 'src/components/ui/dialog/index.tsx',
|
|
671
|
+
targetName: 'DialogAction',
|
|
672
|
+
section: {
|
|
673
|
+
bullets: [
|
|
674
|
+
{
|
|
675
|
+
ja: '`DialogAction` の children に `<Button>` を渡さないでください。内部で Button を描画します。',
|
|
676
|
+
en: 'Do not pass `<Button>` as children. DialogAction renders a Button internally.',
|
|
677
|
+
},
|
|
678
|
+
],
|
|
679
|
+
example: lines([
|
|
680
|
+
'// ✅ Correct',
|
|
681
|
+
'<DialogAction>確定</DialogAction>',
|
|
682
|
+
'',
|
|
683
|
+
'// ❌ Wrong - 二重ラップ',
|
|
684
|
+
'<DialogAction><Button>確定</Button></DialogAction>',
|
|
685
|
+
]),
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
],
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
id: 'dialog-form',
|
|
692
|
+
check: {
|
|
693
|
+
description: 'フォーム入力に Dialog を使わない',
|
|
694
|
+
recommendation: '確認ダイアログは Dialog、フォーム入力や詳細表示は Modal を使ってください。',
|
|
695
|
+
pattern:
|
|
696
|
+
/<Dialog(?:\s|>)[\s\S]*?<(Input|Select|Textarea|Checkbox|Radio|Switch|Form|FormField|FormItem)\b[\s\S]*?<\/Dialog>/g,
|
|
697
|
+
},
|
|
698
|
+
featureSection: lines([
|
|
699
|
+
'### Dialog / Modal: 用途を混ぜない',
|
|
700
|
+
'',
|
|
701
|
+
'| コンポーネント | ベース | 用途 | 例 |',
|
|
702
|
+
'| --- | --- | --- | --- |',
|
|
703
|
+
'| **Dialog** | `AlertDialog` | アクション確認 | 削除確認、再試行確認、保存前の警告 |',
|
|
704
|
+
'| **Modal** | `Dialog` | フォーム入力・情報表示 | 作成/編集フォーム、詳細表示 |',
|
|
705
|
+
'',
|
|
706
|
+
'```tsx',
|
|
707
|
+
'// ✅ Correct - フォーム入力には Modal を使う',
|
|
708
|
+
'<Modal>',
|
|
709
|
+
' <ModalContent size="md">',
|
|
710
|
+
' <ModalHeader>',
|
|
711
|
+
' <ModalTitle>ユーザー作成</ModalTitle>',
|
|
712
|
+
' <ModalClose />',
|
|
713
|
+
' </ModalHeader>',
|
|
714
|
+
' <ModalBody isSpace>',
|
|
715
|
+
' <Input />',
|
|
716
|
+
' <Select />',
|
|
717
|
+
' </ModalBody>',
|
|
718
|
+
' <ModalFooter>',
|
|
719
|
+
' <Button variant="ghost">キャンセル</Button>',
|
|
720
|
+
' <Button>作成</Button>',
|
|
721
|
+
' </ModalFooter>',
|
|
722
|
+
' </ModalContent>',
|
|
723
|
+
'</Modal>',
|
|
724
|
+
'',
|
|
725
|
+
'// ❌ Wrong - フォーム入力に Dialog を使わない',
|
|
726
|
+
'<Dialog>',
|
|
727
|
+
' <DialogContent>',
|
|
728
|
+
' <DialogHeader>',
|
|
729
|
+
' <DialogTitle>ユーザー作成</DialogTitle>',
|
|
730
|
+
' </DialogHeader>',
|
|
731
|
+
' <Input />',
|
|
732
|
+
' <Select />',
|
|
733
|
+
' <DialogFooter>',
|
|
734
|
+
' <DialogCancel>キャンセル</DialogCancel>',
|
|
735
|
+
' <DialogAction>作成</DialogAction>',
|
|
736
|
+
' </DialogFooter>',
|
|
737
|
+
' </DialogContent>',
|
|
738
|
+
'</Dialog>',
|
|
739
|
+
'```',
|
|
740
|
+
]),
|
|
741
|
+
jsdocTargets: [
|
|
742
|
+
{
|
|
743
|
+
file: 'src/components/ui/dialog/index.tsx',
|
|
744
|
+
targetName: 'Dialog',
|
|
745
|
+
section: {
|
|
746
|
+
bullets: [
|
|
747
|
+
{
|
|
748
|
+
ja: 'フォーム入力や詳細表示には使わず、削除確認や再試行確認などのアクション確認に使ってください。',
|
|
749
|
+
en: 'Do not use Dialog for forms or detail views. Use it for action confirmations such as delete and retry flows.',
|
|
750
|
+
},
|
|
751
|
+
],
|
|
752
|
+
example: lines([
|
|
753
|
+
'// ✅ Correct',
|
|
754
|
+
'<Dialog>',
|
|
755
|
+
' <DialogContent>',
|
|
756
|
+
' <DialogHeader>',
|
|
757
|
+
' <DialogTitle>削除確認</DialogTitle>',
|
|
758
|
+
' <DialogDescription>本当に削除しますか?</DialogDescription>',
|
|
759
|
+
' </DialogHeader>',
|
|
760
|
+
' <DialogFooter>',
|
|
761
|
+
' <DialogCancel>キャンセル</DialogCancel>',
|
|
762
|
+
' <DialogAction>削除</DialogAction>',
|
|
763
|
+
' </DialogFooter>',
|
|
764
|
+
' </DialogContent>',
|
|
765
|
+
'</Dialog>',
|
|
766
|
+
'',
|
|
767
|
+
'// ❌ Wrong - フォーム入力に Dialog を使わない',
|
|
768
|
+
'<Dialog>',
|
|
769
|
+
' <DialogContent>',
|
|
770
|
+
' <Input />',
|
|
771
|
+
' <Select />',
|
|
772
|
+
' </DialogContent>',
|
|
773
|
+
'</Dialog>',
|
|
774
|
+
]),
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
file: 'src/components/ui/modal/index.tsx',
|
|
779
|
+
targetName: 'Modal',
|
|
780
|
+
section: {
|
|
781
|
+
bullets: [
|
|
782
|
+
{
|
|
783
|
+
ja: '作成/編集フォームや詳細表示など、確認だけではない情報入力・閲覧に使ってください。',
|
|
784
|
+
en: 'Use Modal for forms and detail views such as create and edit flows, not for simple action confirmation.',
|
|
785
|
+
},
|
|
786
|
+
],
|
|
787
|
+
example: lines([
|
|
788
|
+
'// ✅ Correct',
|
|
789
|
+
'<Modal>',
|
|
790
|
+
' <ModalContent size="md">',
|
|
791
|
+
' <ModalHeader>',
|
|
792
|
+
' <ModalTitle>ユーザー作成</ModalTitle>',
|
|
793
|
+
' <ModalClose />',
|
|
794
|
+
' </ModalHeader>',
|
|
795
|
+
' <ModalBody isSpace>',
|
|
796
|
+
' <Input />',
|
|
797
|
+
' <Select />',
|
|
798
|
+
' </ModalBody>',
|
|
799
|
+
' <ModalFooter>',
|
|
800
|
+
' <Button>作成</Button>',
|
|
801
|
+
' </ModalFooter>',
|
|
802
|
+
' </ModalContent>',
|
|
803
|
+
'</Modal>',
|
|
804
|
+
]),
|
|
805
|
+
},
|
|
806
|
+
},
|
|
807
|
+
],
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
id: 'input-trigger',
|
|
811
|
+
featureSection: lines([
|
|
812
|
+
'### Input: 組み込みトリガーを使う',
|
|
813
|
+
'',
|
|
814
|
+
'```tsx',
|
|
815
|
+
'// ✅ Correct',
|
|
816
|
+
'<Input isTrigger triggerIcon="search" triggerAriaLabel="検索" onIconButtonClick={handleSearch} />',
|
|
817
|
+
'',
|
|
818
|
+
'// ❌ Wrong - 手動で IconButton を配置しない',
|
|
819
|
+
'<div className="flex">',
|
|
820
|
+
' <Input />',
|
|
821
|
+
' <IconButton icon="search" aria-label="検索" />',
|
|
822
|
+
'</div>',
|
|
823
|
+
'```',
|
|
824
|
+
]),
|
|
825
|
+
jsdocTargets: [],
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
id: 'link-open-in-new',
|
|
829
|
+
featureSection: lines([
|
|
830
|
+
'### Link: isOpenInNew を使う',
|
|
831
|
+
'',
|
|
832
|
+
'```tsx',
|
|
833
|
+
'// ✅ Correct - 自動で open_in_new アイコンが表示される',
|
|
834
|
+
'<Link href="https://example.com" isOpenInNew>外部リンク</Link>',
|
|
835
|
+
'',
|
|
836
|
+
'// ❌ Wrong - 手動でアイコンを追加しない',
|
|
837
|
+
'<Link href="https://example.com">外部リンク <Icon icon="open_in_new" /></Link>',
|
|
838
|
+
'```',
|
|
839
|
+
]),
|
|
840
|
+
jsdocTargets: [
|
|
841
|
+
{
|
|
842
|
+
file: 'src/components/ui/link/index.tsx',
|
|
843
|
+
targetName: 'Link',
|
|
844
|
+
section: {
|
|
845
|
+
bullets: [
|
|
846
|
+
{
|
|
847
|
+
ja: '外部リンクアイコンを手動で追加しないでください。`isOpenInNew` prop が自動で `open_in_new` アイコンを表示します。',
|
|
848
|
+
en: 'Do not manually add external link icons. The `isOpenInNew` prop automatically displays the `open_in_new` icon.',
|
|
849
|
+
},
|
|
850
|
+
],
|
|
851
|
+
example: lines([
|
|
852
|
+
'// ✅ Correct',
|
|
853
|
+
'<Link href="https://example.com" isOpenInNew>外部リンク</Link>',
|
|
854
|
+
'',
|
|
855
|
+
'// ❌ Wrong - 手動アイコン',
|
|
856
|
+
'<Link href="https://example.com">外部リンク <Icon icon="open_in_new" /></Link>',
|
|
857
|
+
]),
|
|
858
|
+
},
|
|
859
|
+
},
|
|
860
|
+
],
|
|
861
|
+
},
|
|
862
|
+
];
|
|
863
|
+
|
|
864
|
+
function getCheckRules() {
|
|
865
|
+
const order = ['dialog-form', 'dialog-button-wrap', 'button-icon-only', 'material-symbols-direct', 'shadcn-token'];
|
|
866
|
+
|
|
867
|
+
return ANTI_PATTERN_GROUPS.filter((group) => group.check)
|
|
868
|
+
.map((group) => ({
|
|
869
|
+
id: group.id,
|
|
870
|
+
...group.check,
|
|
871
|
+
}))
|
|
872
|
+
.sort((left, right) => {
|
|
873
|
+
const leftIndex = order.indexOf(left.id);
|
|
874
|
+
const rightIndex = order.indexOf(right.id);
|
|
875
|
+
const normalizedLeftIndex = leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex;
|
|
876
|
+
const normalizedRightIndex = rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex;
|
|
877
|
+
return normalizedLeftIndex - normalizedRightIndex;
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function getAllJSDocTargets() {
|
|
882
|
+
return ANTI_PATTERN_GROUPS.flatMap((group) => group.jsdocTargets);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function renderFeatureSections() {
|
|
886
|
+
return ANTI_PATTERN_GROUPS.map((group) => group.featureSection).join('\n\n');
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function getManualReviewReminders() {
|
|
890
|
+
return MANUAL_REVIEW_REMINDERS;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
export {
|
|
894
|
+
ANTI_PATTERN_GROUPS,
|
|
895
|
+
COMMON_INTRO,
|
|
896
|
+
getAllJSDocTargets,
|
|
897
|
+
getCheckRules,
|
|
898
|
+
getManualReviewReminders,
|
|
899
|
+
renderFeatureSections,
|
|
900
|
+
renderJSDocSection,
|
|
901
|
+
};
|