@atlashub/smartstack-cli 1.37.0 → 2.0.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/config/mcp-defaults.json +62 -0
- package/dist/index.js +57 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +16984 -0
- package/dist/mcp-entry.mjs.map +1 -0
- package/package.json +14 -5
- package/templates/agents/gitflow/start.md +5 -4
- package/templates/agents/mcp-healthcheck.md +15 -13
- package/templates/mcp-scaffolding/component.tsx.hbs +298 -0
- package/templates/mcp-scaffolding/controller.cs.hbs +184 -0
- package/templates/mcp-scaffolding/entity-extension.cs.hbs +231 -0
- package/templates/mcp-scaffolding/frontend/api-client.ts.hbs +116 -0
- package/templates/mcp-scaffolding/frontend/nav-routes.ts.hbs +133 -0
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +134 -0
- package/templates/mcp-scaffolding/migrations/seed-roles.cs.hbs +261 -0
- package/templates/mcp-scaffolding/service-extension.cs.hbs +53 -0
- package/templates/mcp-scaffolding/tests/controller.test.cs.hbs +413 -0
- package/templates/mcp-scaffolding/tests/entity.test.cs.hbs +239 -0
- package/templates/mcp-scaffolding/tests/repository.test.cs.hbs +441 -0
- package/templates/mcp-scaffolding/tests/security.test.cs.hbs +442 -0
- package/templates/mcp-scaffolding/tests/service.test.cs.hbs +390 -0
- package/templates/mcp-scaffolding/tests/validator.test.cs.hbs +428 -0
- package/templates/ralph/README.md +3 -3
- package/templates/ralph/ralph.config.yaml +2 -2
- package/templates/skills/admin/SKILL.md +42 -0
- package/templates/skills/business-analyse/_shared.md +24 -1
- package/templates/skills/business-analyse/questionnaire/01-context.md +4 -4
- package/templates/skills/business-analyse/questionnaire/02-stakeholders.md +3 -3
- package/templates/skills/business-analyse/questionnaire/03-scope.md +4 -4
- package/templates/skills/business-analyse/questionnaire/04-data.md +7 -7
- package/templates/skills/business-analyse/questionnaire/05-integrations.md +1 -1
- package/templates/skills/business-analyse/questionnaire/06-security.md +3 -3
- package/templates/skills/business-analyse/questionnaire/07-ui.md +1 -1
- package/templates/skills/business-analyse/questionnaire/08-performance.md +3 -3
- package/templates/skills/business-analyse/questionnaire/09-constraints.md +4 -4
- package/templates/skills/business-analyse/questionnaire/10-documentation.md +2 -2
- package/templates/skills/business-analyse/questionnaire/11-data-lifecycle.md +2 -2
- package/templates/skills/business-analyse/questionnaire/12-migration.md +1 -1
- package/templates/skills/business-analyse/questionnaire/13-cross-module.md +2 -2
- package/templates/skills/business-analyse/steps/step-01-discover.md +50 -25
- package/templates/skills/business-analyse/steps/step-05-handoff.md +133 -34
- package/templates/skills/cc-agent/SKILL.md +129 -0
- package/templates/skills/cc-agent/references/agent-frontmatter.md +213 -0
- package/templates/skills/cc-agent/references/permission-modes.md +102 -0
- package/templates/skills/cc-agent/references/tools-reference.md +144 -0
- package/templates/skills/cc-agent/steps/step-00-init.md +134 -0
- package/templates/skills/cc-agent/steps/step-01-design.md +186 -0
- package/templates/skills/cc-agent/steps/step-02-generate.md +204 -0
- package/templates/skills/cc-agent/steps/step-03-validate.md +130 -0
- package/templates/skills/cc-agent/templates/agent-categorized.md +67 -0
- package/templates/skills/cc-agent/templates/agent-standalone.md +56 -0
- package/templates/skills/cc-agent/templates/agent-with-skills.md +94 -0
- package/templates/skills/cc-audit/SKILL.md +108 -0
- package/templates/skills/cc-audit/references/agent-checklist.md +91 -0
- package/templates/skills/cc-audit/references/hook-checklist.md +110 -0
- package/templates/skills/cc-audit/references/skill-checklist.md +70 -0
- package/templates/skills/cc-audit/steps/step-00-init.md +98 -0
- package/templates/skills/cc-audit/steps/step-01-scan.md +142 -0
- package/templates/skills/cc-audit/steps/step-02-analyze.md +158 -0
- package/templates/skills/cc-audit/steps/step-03-report.md +142 -0
- package/templates/skills/cc-skill/SKILL.md +134 -0
- package/templates/skills/cc-skill/references/best-practices.md +167 -0
- package/templates/skills/cc-skill/references/frontmatter-reference.md +182 -0
- package/templates/skills/cc-skill/references/skill-patterns.md +199 -0
- package/templates/skills/cc-skill/steps/step-00-init.md +119 -0
- package/templates/skills/cc-skill/steps/step-01-design.md +199 -0
- package/templates/skills/cc-skill/steps/step-02-generate.md +145 -0
- package/templates/skills/cc-skill/steps/step-03-steps.md +151 -0
- package/templates/skills/cc-skill/steps/step-04-validate.md +124 -0
- package/templates/skills/cc-skill/templates/skill-forked.md +85 -0
- package/templates/skills/cc-skill/templates/skill-progressive.md +102 -0
- package/templates/skills/cc-skill/templates/skill-simple.md +75 -0
- package/templates/skills/cc-skill/templates/step-template.md +82 -0
- package/templates/skills/check-version/SKILL.md +6 -0
- package/templates/skills/debug/SKILL.md +4 -0
- package/templates/skills/documentation/SKILL.md +1 -0
- package/templates/skills/efcore/SKILL.md +5 -0
- package/templates/skills/efcore/steps/db/step-deploy.md +26 -5
- package/templates/skills/efcore/steps/shared/step-00-init.md +21 -7
- package/templates/skills/explore/SKILL.md +28 -32
- package/templates/skills/feature-full/SKILL.md +1 -0
- package/templates/skills/gitflow/SKILL.md +8 -0
- package/templates/skills/gitflow/steps/step-start.md +45 -10
- package/templates/skills/mcp/SKILL.md +38 -18
- package/templates/skills/quick-search/SKILL.md +8 -1
- package/templates/skills/ralph-loop/SKILL.md +1 -1
- package/templates/skills/ralph-loop/steps/step-00-init.md +8 -68
- package/templates/skills/ralph-loop/steps/step-04-check.md +1 -1
- package/templates/skills/refactor/SKILL.md +1 -0
- package/templates/skills/review-code/SKILL.md +7 -1
- package/templates/skills/ui-components/SKILL.md +31 -438
- package/templates/skills/ui-components/accessibility.md +170 -0
- package/templates/skills/ui-components/patterns/data-table.md +39 -0
- package/templates/skills/ui-components/patterns/entity-card.md +77 -0
- package/templates/skills/ui-components/patterns/grid-layout.md +91 -0
- package/templates/skills/ui-components/patterns/kanban.md +43 -0
- package/templates/skills/ui-components/style-guide.md +86 -0
- package/templates/skills/utils/SKILL.md +1 -0
- package/templates/skills/validate/SKILL.md +1 -0
|
@@ -27,465 +27,59 @@ description: |
|
|
|
27
27
|
| Keywords | "card", "grid", "table", "kanban", "tooltip" |
|
|
28
28
|
| Disabled states | "Disable button with explanatory message" |
|
|
29
29
|
|
|
30
|
-
##
|
|
30
|
+
## Detailed patterns
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
- For EntityCard (mandatory): [patterns/entity-card.md](patterns/entity-card.md)
|
|
33
|
+
- For DataTable: [patterns/data-table.md](patterns/data-table.md)
|
|
34
|
+
- For Grid layouts: [patterns/grid-layout.md](patterns/grid-layout.md)
|
|
35
|
+
- For Kanban boards: [patterns/kanban.md](patterns/kanban.md)
|
|
36
|
+
- For styling rules: [style-guide.md](style-guide.md)
|
|
37
|
+
- For accessibility: [accessibility.md](accessibility.md)
|
|
38
|
+
|
|
39
|
+
## CORE RULES (always apply)
|
|
33
40
|
|
|
41
|
+
### EntityCard is Mandatory
|
|
42
|
+
**ALWAYS use `EntityCard` for entity cards. NEVER use custom cards with divs.**
|
|
34
43
|
```typescript
|
|
35
44
|
import { EntityCard, ProviderCard, TemplateCard } from '@/components/ui/EntityCard';
|
|
36
45
|
```
|
|
37
46
|
|
|
38
|
-
###
|
|
47
|
+
### CSS Variables (NEVER hardcode colors)
|
|
48
|
+
**NEVER use hardcoded Tailwind colors.** ALWAYS use CSS variables for theme compliance.
|
|
39
49
|
```tsx
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
title="OpenAI" subtitle="openai"
|
|
43
|
-
description="OpenAI GPT models"
|
|
44
|
-
stats="15 model(s)"
|
|
45
|
-
badge={{ icon: Shield, tooltip: 'Admin API supported' }}
|
|
46
|
-
links={[{ icon: ExternalLink, label: 'Website', href: 'https://...' }]}
|
|
47
|
-
actions={[{ label: 'Config', onClick: () => {}, variant: 'primary' }]}
|
|
48
|
-
/>
|
|
49
|
-
```
|
|
50
|
+
// CORRECT
|
|
51
|
+
<span className="bg-[var(--info-bg)] text-[var(--info-text)]">Badge</span>
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<ProviderCard name="OpenAI" code="openai" description="..." modelCount={15} color="#10a37f"
|
|
54
|
-
websiteUrl="..." docsUrl="..." apiKeyUrl="..." hasAdminKey />
|
|
53
|
+
// WRONG
|
|
54
|
+
<span className="bg-blue-500/10 text-blue-500">Badge</span>
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
###
|
|
57
|
+
### Responsive Grid
|
|
58
58
|
```tsx
|
|
59
|
-
<TemplateCard name="Welcome" code="welcome" category="Transactional" isActive isSystem
|
|
60
|
-
icon={Mail} translationsCount={3} onClick={() => {}} onEdit={() => {}} onDelete={() => {}} />
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### EntityCard Props
|
|
64
|
-
| Prop | Type |
|
|
65
|
-
|------|------|
|
|
66
|
-
| `avatar` | `{ letter, color, imageUrl? }` |
|
|
67
|
-
| `title`, `subtitle`, `description` | `string` |
|
|
68
|
-
| `stats` | `string` |
|
|
69
|
-
| `badge` | `{ icon?, tooltip?, color? }` |
|
|
70
|
-
| `links` | `Array<{ icon, label, href?, onClick? }>` |
|
|
71
|
-
| `actions` | `Array<{ label, href?, onClick?, variant, icon?, disabled? }>` |
|
|
72
|
-
|
|
73
|
-
### Action Variants (EntityCard)
|
|
74
|
-
| Variant | Style |
|
|
75
|
-
|---------|-------|
|
|
76
|
-
| `primary` | `bg-[var(--color-accent-600)] text-white` |
|
|
77
|
-
| `secondary` | `bg-[var(--bg-secondary)] text-[var(--text-secondary)]` |
|
|
78
|
-
| `ghost` | `text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]` (NO border!) |
|
|
79
|
-
|
|
80
|
-
## RESPONSIVE GRID
|
|
81
|
-
|
|
82
|
-
```tsx
|
|
83
|
-
// Standard
|
|
84
59
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
|
85
60
|
{items.map(item => <EntityCard key={item.id} {...mapToCardProps(item)} />)}
|
|
86
61
|
</div>
|
|
87
|
-
|
|
88
|
-
// With empty state
|
|
89
|
-
{items.length === 0 ? (
|
|
90
|
-
<div className="text-center py-12"><p>No items</p></div>
|
|
91
|
-
) : (
|
|
92
|
-
<div className="grid ...">...</div>
|
|
93
|
-
)}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## CUSTOM CARDS (Status, Dashboard)
|
|
97
|
-
|
|
98
|
-
Button alignment pattern at bottom (mandatory for custom cards):
|
|
99
|
-
|
|
100
|
-
```tsx
|
|
101
|
-
// WARNING: h-full flex flex-col + flex-1 + mt-auto
|
|
102
|
-
<div className="h-full flex flex-col rounded-[var(--radius-card)] border ...">
|
|
103
|
-
{/* Header */}
|
|
104
|
-
<div className="px-4 py-3 bg-gradient-to-r from-[var(--color-accent-500)]/10 ...">
|
|
105
|
-
{/* ... */}
|
|
106
|
-
</div>
|
|
107
|
-
{/* Content with flex-1 */}
|
|
108
|
-
<div className="flex-1 flex flex-col p-4">
|
|
109
|
-
{/* Variable content */}
|
|
110
|
-
{/* WARNING: mt-auto for button at bottom */}
|
|
111
|
-
<button className="mt-auto w-full ...">Action</button>
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## COMPONENT: DataTable
|
|
117
|
-
|
|
118
|
-
```tsx
|
|
119
|
-
import { DataTable } from '@/components/ui/DataTable';
|
|
120
|
-
|
|
121
|
-
<DataTable
|
|
122
|
-
data={users}
|
|
123
|
-
columns={[
|
|
124
|
-
{ key: 'name', label: 'Name', sortable: true },
|
|
125
|
-
{ key: 'role', label: 'Role', render: (user) => <Badge>{user.role}</Badge> }
|
|
126
|
-
]}
|
|
127
|
-
pagination={{ pageSize: 10 }}
|
|
128
|
-
searchable
|
|
129
|
-
onRowClick={(user) => navigate(`/users/${user.id}`)}
|
|
130
|
-
/>
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## COMPONENT: Tooltip
|
|
134
|
-
|
|
135
|
-
```tsx
|
|
136
|
-
import { Tooltip } from '@/components/ui/Tooltip';
|
|
137
|
-
|
|
138
|
-
// Variants: default, error, warning, success, info
|
|
139
|
-
// Positions: top, bottom, left, right
|
|
140
|
-
|
|
141
|
-
<Tooltip content="Permission required" variant="error">
|
|
142
|
-
<button disabled>Protected action</button>
|
|
143
|
-
</Tooltip>
|
|
144
62
|
```
|
|
145
63
|
|
|
146
|
-
###
|
|
64
|
+
### Ghost Buttons: NO BORDER
|
|
147
65
|
```tsx
|
|
148
|
-
const canExecute = hasPermission('module.action.execute');
|
|
149
|
-
<Tooltip content={!canExecute ? t('errors.noPermission') : undefined} variant="error" disabled={canExecute}>
|
|
150
|
-
<button disabled={!canExecute}>Action</button>
|
|
151
|
-
</Tooltip>
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## WHEN TO USE EntityCard vs Custom
|
|
155
|
-
|
|
156
|
-
| EntityCard for | Custom for |
|
|
157
|
-
|----------------|------------|
|
|
158
|
-
| Homogeneous entity lists | Dashboard stats cards |
|
|
159
|
-
| Catalogs | Cards with complex interactive states |
|
|
160
|
-
| Clickable grids | Cards with integrated forms |
|
|
161
|
-
|
|
162
|
-
## CRITICAL: CSS VARIABLES (NEVER HARDCODE COLORS)
|
|
163
|
-
|
|
164
|
-
**NEVER use hardcoded Tailwind colors.** ALWAYS use CSS variables for theme compliance.
|
|
165
|
-
|
|
166
|
-
### Status Colors
|
|
167
|
-
| Status | Background | Text | Border | Dot (solid) |
|
|
168
|
-
|--------|------------|------|--------|-------------|
|
|
169
|
-
| **Info** (Pending, Business) | `--info-bg` | `--info-text` | `--info-border` | `--info-dot` |
|
|
170
|
-
| **Success** (Active) | `--success-bg` | `--success-text` | `--success-border` | `--success-dot` |
|
|
171
|
-
| **Warning** (Suspended, Owner) | `--warning-bg` | `--warning-text` | `--warning-border` | `--warning-dot` |
|
|
172
|
-
| **Error** (Danger, Archive) | `--error-bg` | `--error-text` | `--error-border` | `--error-dot` |
|
|
173
|
-
| **Accent** (Personal, Primary) | `--accent-bg` | `--accent-text` | `--accent-border` | `--color-accent-500` |
|
|
174
|
-
|
|
175
|
-
### Badge Pattern
|
|
176
|
-
```tsx
|
|
177
|
-
// ✅ CORRECT - Uses CSS variables
|
|
178
|
-
<span className="px-2 py-0.5 text-xs rounded-[var(--radius-badge)] bg-[var(--info-bg)] text-[var(--info-text)]">
|
|
179
|
-
Info Badge
|
|
180
|
-
</span>
|
|
181
|
-
|
|
182
|
-
// ❌ WRONG - Hardcoded Tailwind colors
|
|
183
|
-
<span className="bg-blue-500/10 text-blue-500">Info Badge</span>
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Status Config Pattern
|
|
187
|
-
```tsx
|
|
188
|
-
// ✅ CORRECT
|
|
189
|
-
const STATUS_CONFIG = {
|
|
190
|
-
Pending: { bgColor: 'bg-[var(--info-bg)]', color: 'text-[var(--info-text)]', borderColor: 'border-[var(--info-border)]' },
|
|
191
|
-
Active: { bgColor: 'bg-[var(--success-bg)]', color: 'text-[var(--success-text)]', borderColor: 'border-[var(--success-border)]' },
|
|
192
|
-
Suspended: { bgColor: 'bg-[var(--warning-bg)]', color: 'text-[var(--warning-text)]', borderColor: 'border-[var(--warning-border)]' },
|
|
193
|
-
Archived: { bgColor: 'bg-[var(--bg-tertiary)]', color: 'text-[var(--text-tertiary)]', borderColor: 'border-[var(--border-color)]' }
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
// ❌ WRONG - dark: variants are obsolete
|
|
197
|
-
const STATUS_CONFIG = {
|
|
198
|
-
Active: { bgColor: 'bg-emerald-50 dark:bg-emerald-900/20', ... }
|
|
199
|
-
};
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## BUTTON VARIANTS
|
|
203
|
-
|
|
204
|
-
### Action Buttons (Status changes)
|
|
205
|
-
```tsx
|
|
206
|
-
// Success action (Activate)
|
|
207
|
-
<button className="px-4 py-2 rounded-[var(--radius-button)] bg-[var(--success-dot)] text-white hover:opacity-90">
|
|
208
|
-
Activate
|
|
209
|
-
</button>
|
|
210
|
-
|
|
211
|
-
// Warning action (Suspend)
|
|
212
|
-
<button className="px-4 py-2 rounded-[var(--radius-button)] bg-[var(--warning-dot)] text-white hover:opacity-90">
|
|
213
|
-
Suspend
|
|
214
|
-
</button>
|
|
215
|
-
|
|
216
|
-
// Danger action (Archive/Delete)
|
|
217
|
-
<button className="px-4 py-2 rounded-[var(--radius-button)] bg-[var(--error-dot)] text-white hover:opacity-90">
|
|
218
|
-
Archive
|
|
219
|
-
</button>
|
|
220
|
-
|
|
221
|
-
// Primary action
|
|
222
|
-
<button className="px-4 py-2 rounded-[var(--radius-button)] bg-[var(--color-accent-600)] text-white hover:bg-[var(--color-accent-700)]">
|
|
223
|
-
Save
|
|
224
|
-
</button>
|
|
225
|
-
|
|
226
66
|
// Ghost action (NO BORDER!)
|
|
227
|
-
<button className="px-4 py-2 rounded-[var(--radius-button)] text-[var(--text-secondary)] hover:
|
|
228
|
-
|
|
229
|
-
</button>
|
|
230
|
-
|
|
231
|
-
// Secondary action
|
|
232
|
-
<button className="px-4 py-2 rounded-[var(--radius-button)] bg-[var(--bg-secondary)] text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]">
|
|
233
|
-
Cancel
|
|
234
|
-
</button>
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### Button Rules
|
|
238
|
-
| Variant | Background | Text | Border | Hover |
|
|
239
|
-
|---------|------------|------|--------|-------|
|
|
240
|
-
| **Primary** | `--color-accent-600` | `white` | none | `--color-accent-700` |
|
|
241
|
-
| **Success** | `--success-dot` | `white` | none | `opacity-90` |
|
|
242
|
-
| **Warning** | `--warning-dot` | `white` | none | `opacity-90` |
|
|
243
|
-
| **Danger** | `--error-dot` | `white` | none | `opacity-90` |
|
|
244
|
-
| **Secondary** | `--bg-secondary` | `--text-secondary` | none | `--bg-hover` |
|
|
245
|
-
| **Ghost** | transparent | `--text-secondary` | **NONE** | `--bg-hover` |
|
|
246
|
-
|
|
247
|
-
## DETAIL PAGE TEMPLATE
|
|
248
|
-
|
|
249
|
-
### Structure
|
|
250
|
-
```tsx
|
|
251
|
-
<div className="container mx-auto px-4 py-6 max-w-7xl space-y-6">
|
|
252
|
-
{/* Header */}
|
|
253
|
-
<div className="flex items-center gap-4">
|
|
254
|
-
<button onClick={() => navigate(-1)} className="p-2 rounded-[var(--radius-button)] hover:bg-[var(--bg-hover)]">
|
|
255
|
-
<ArrowLeft className="w-5 h-5 text-[var(--text-secondary)]" />
|
|
256
|
-
</button>
|
|
257
|
-
<div className="flex-1">
|
|
258
|
-
<h1 className="text-xl sm:text-2xl font-bold text-[var(--text-primary)]">{title}</h1>
|
|
259
|
-
<p className="text-sm text-[var(--text-secondary)]">{subtitle}</p>
|
|
260
|
-
</div>
|
|
261
|
-
<div className="flex items-center gap-2">
|
|
262
|
-
{/* Status badges */}
|
|
263
|
-
</div>
|
|
264
|
-
</div>
|
|
265
|
-
|
|
266
|
-
{/* Pill Tabs */}
|
|
267
|
-
<div className="flex gap-1 p-1 bg-[var(--bg-secondary)] rounded-[var(--radius-card)] border border-[var(--border-color)]">
|
|
268
|
-
{tabs.map(tab => (
|
|
269
|
-
<button
|
|
270
|
-
key={tab.id}
|
|
271
|
-
className={`px-4 py-2 rounded-[var(--radius-button)] font-medium ${
|
|
272
|
-
activeTab === tab.id
|
|
273
|
-
? 'bg-[var(--color-accent-600)] text-white'
|
|
274
|
-
: 'text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]'
|
|
275
|
-
}`}
|
|
276
|
-
>
|
|
277
|
-
{tab.label}
|
|
278
|
-
</button>
|
|
279
|
-
))}
|
|
280
|
-
</div>
|
|
281
|
-
|
|
282
|
-
{/* Tab Content */}
|
|
283
|
-
<div className="min-h-[400px]">{/* ... */}</div>
|
|
284
|
-
</div>
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### Info Card Pattern
|
|
288
|
-
```tsx
|
|
289
|
-
<div className="p-4 rounded-[var(--radius-card)] bg-[var(--bg-primary)] border border-[var(--border-color)]">
|
|
290
|
-
<div className="flex items-center justify-between mb-4">
|
|
291
|
-
<h3 className="text-lg font-semibold text-[var(--text-primary)] flex items-center gap-2">
|
|
292
|
-
<Icon className="w-5 h-5" />
|
|
293
|
-
{title}
|
|
294
|
-
</h3>
|
|
295
|
-
<button className="flex items-center gap-1 px-3 py-1.5 rounded-[var(--radius-button)] bg-[var(--bg-secondary)] text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]">
|
|
296
|
-
<Edit3 className="w-4 h-4" />
|
|
297
|
-
{t('common:edit')}
|
|
298
|
-
</button>
|
|
299
|
-
</div>
|
|
300
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
301
|
-
<div className="p-3 rounded-lg bg-[var(--bg-secondary)]">
|
|
302
|
-
<p className="text-xs text-[var(--text-tertiary)] uppercase tracking-wider mb-1">{label}</p>
|
|
303
|
-
<p className="text-sm text-[var(--text-primary)] font-medium">{value}</p>
|
|
304
|
-
</div>
|
|
305
|
-
</div>
|
|
306
|
-
</div>
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
### Error Display Pattern
|
|
310
|
-
```tsx
|
|
311
|
-
{error && (
|
|
312
|
-
<div className="p-4 rounded-[var(--radius-card)] bg-[var(--error-bg)] border border-[var(--error-border)]">
|
|
313
|
-
<div className="flex items-center gap-2 text-[var(--error-text)]">
|
|
314
|
-
<AlertTriangle className="w-5 h-5" />
|
|
315
|
-
<span>{error}</span>
|
|
316
|
-
</div>
|
|
317
|
-
</div>
|
|
318
|
-
)}
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
## TYPESCRIPT INTERFACES (MANDATORY)
|
|
322
|
-
|
|
323
|
-
**ALWAYS define explicit TypeScript interfaces for component props.**
|
|
324
|
-
|
|
325
|
-
```typescript
|
|
326
|
-
// ✅ CORRECT - Explicit interface with all props typed
|
|
327
|
-
interface UserCardProps {
|
|
328
|
-
user: User;
|
|
329
|
-
onSelect?: (user: User) => void;
|
|
330
|
-
isSelected?: boolean;
|
|
331
|
-
variant?: 'default' | 'compact';
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
export const UserCard: React.FC<UserCardProps> = ({
|
|
335
|
-
user,
|
|
336
|
-
onSelect,
|
|
337
|
-
isSelected = false,
|
|
338
|
-
variant = 'default'
|
|
339
|
-
}) => {
|
|
340
|
-
// ...
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
// ❌ WRONG - Missing interface, inline types, or any
|
|
344
|
-
export const UserCard = ({ user, onSelect }: any) => { ... }
|
|
345
|
-
export const UserCard = (props: { user: any }) => { ... }
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### State Typing
|
|
349
|
-
```typescript
|
|
350
|
-
// ✅ CORRECT - Explicit state types
|
|
351
|
-
const [users, setUsers] = useState<User[]>([]);
|
|
352
|
-
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
353
|
-
const [status, setStatus] = useState<'idle' | 'loading' | 'error'>('idle');
|
|
354
|
-
|
|
355
|
-
// ❌ WRONG - Implicit or missing types
|
|
356
|
-
const [users, setUsers] = useState([]);
|
|
357
|
-
const [data, setData] = useState();
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
## SEMANTIC HTML (ACCESSIBILITY)
|
|
361
|
-
|
|
362
|
-
**ALWAYS use semantic HTML elements instead of divs with roles.**
|
|
363
|
-
|
|
364
|
-
```tsx
|
|
365
|
-
// ✅ CORRECT - Native button element
|
|
366
|
-
<button type="button" onClick={handleClick} disabled={isDisabled}>
|
|
367
|
-
Click me
|
|
67
|
+
<button className="px-4 py-2 rounded-[var(--radius-button)] text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]">
|
|
68
|
+
Action
|
|
368
69
|
</button>
|
|
369
|
-
|
|
370
|
-
// ❌ WRONG - div with role (S6819)
|
|
371
|
-
<div role="button" onClick={handleClick} tabIndex={0}>
|
|
372
|
-
Click me
|
|
373
|
-
</div>
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
### Interactive Elements Mapping
|
|
377
|
-
|
|
378
|
-
| Use Case | Correct Element | Wrong Pattern |
|
|
379
|
-
|----------|----------------|---------------|
|
|
380
|
-
| Clickable action | `<button type="button">` | `<div role="button">` |
|
|
381
|
-
| Navigation link | `<a href="...">` or `<Link>` | `<div onClick={navigate}>` |
|
|
382
|
-
| Form submission | `<button type="submit">` | `<div onClick={submit}>` |
|
|
383
|
-
| Expandable section | `<details><summary>` | `<div onClick={toggle}>` |
|
|
384
|
-
| List of items | `<ul><li>` with `<button>` inside | `<div role="listitem">` |
|
|
385
|
-
|
|
386
|
-
### Clickable Cards Pattern
|
|
387
|
-
```tsx
|
|
388
|
-
// ✅ CORRECT - Card with button inside
|
|
389
|
-
<div className="rounded-[var(--radius-card)] border ...">
|
|
390
|
-
<div className="p-4">
|
|
391
|
-
<h3>{title}</h3>
|
|
392
|
-
<p>{description}</p>
|
|
393
|
-
</div>
|
|
394
|
-
<button
|
|
395
|
-
type="button"
|
|
396
|
-
onClick={() => onSelect(item)}
|
|
397
|
-
className="w-full px-4 py-2 border-t ..."
|
|
398
|
-
>
|
|
399
|
-
Select
|
|
400
|
-
</button>
|
|
401
|
-
</div>
|
|
402
|
-
|
|
403
|
-
// ✅ CORRECT - Entire card as link
|
|
404
|
-
<Link to={`/items/${item.id}`} className="block rounded-[var(--radius-card)] ...">
|
|
405
|
-
<div className="p-4">...</div>
|
|
406
|
-
</Link>
|
|
407
|
-
|
|
408
|
-
// ❌ WRONG - div with click handler
|
|
409
|
-
<div onClick={() => onSelect(item)} className="cursor-pointer ...">
|
|
410
|
-
...
|
|
411
|
-
</div>
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
## PERFORMANCE OPTIMIZATION
|
|
415
|
-
|
|
416
|
-
### React.memo for Pure Components
|
|
417
|
-
```tsx
|
|
418
|
-
import { memo, useCallback, useMemo } from 'react';
|
|
419
|
-
|
|
420
|
-
// ✅ CORRECT - Memoized component for lists
|
|
421
|
-
export const UserCard = memo(({ user, onSelect }: UserCardProps) => {
|
|
422
|
-
return (
|
|
423
|
-
<div className="...">
|
|
424
|
-
<h3>{user.name}</h3>
|
|
425
|
-
<button onClick={() => onSelect(user)}>Select</button>
|
|
426
|
-
</div>
|
|
427
|
-
);
|
|
428
|
-
});
|
|
429
|
-
UserCard.displayName = 'UserCard';
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
### useCallback for Event Handlers
|
|
433
|
-
```tsx
|
|
434
|
-
// ✅ CORRECT - Stable callback reference
|
|
435
|
-
const handleSelect = useCallback((item: Item) => {
|
|
436
|
-
onSelect?.(item);
|
|
437
|
-
setSelectedId(item.id);
|
|
438
|
-
}, [onSelect]);
|
|
439
|
-
|
|
440
|
-
// ❌ WRONG - New function on every render
|
|
441
|
-
const handleSelect = (item: Item) => {
|
|
442
|
-
onSelect?.(item);
|
|
443
|
-
};
|
|
444
70
|
```
|
|
445
71
|
|
|
446
|
-
###
|
|
447
|
-
|
|
448
|
-
// ✅ CORRECT - Memoized filtered list
|
|
449
|
-
const filteredItems = useMemo(() =>
|
|
450
|
-
items.filter(item => item.status === filter),
|
|
451
|
-
[items, filter]
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
// ❌ WRONG - Recomputed on every render
|
|
455
|
-
const filteredItems = items.filter(item => item.status === filter);
|
|
456
|
-
```
|
|
72
|
+
### TypeScript Interfaces
|
|
73
|
+
**ALWAYS define explicit TypeScript interfaces for component props.** Never use `any`.
|
|
457
74
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
**NEVER use array index as React key. ALWAYS use unique identifiers.**
|
|
461
|
-
|
|
462
|
-
```tsx
|
|
463
|
-
// ✅ CORRECT - Unique ID from data
|
|
464
|
-
{items.map(item => (
|
|
465
|
-
<EntityCard key={item.id} {...mapToCardProps(item)} />
|
|
466
|
-
))}
|
|
467
|
-
|
|
468
|
-
// ✅ CORRECT - Composite key when needed
|
|
469
|
-
{items.map(item => (
|
|
470
|
-
<Row key={`${item.tenantId}-${item.id}`} data={item} />
|
|
471
|
-
))}
|
|
472
|
-
|
|
473
|
-
// ❌ WRONG - Array index as key (S6479)
|
|
474
|
-
{items.map((item, index) => (
|
|
475
|
-
<EntityCard key={index} {...mapToCardProps(item)} />
|
|
476
|
-
))}
|
|
477
|
-
|
|
478
|
-
// ❌ WRONG - Random key
|
|
479
|
-
{items.map(item => (
|
|
480
|
-
<EntityCard key={Math.random()} {...mapToCardProps(item)} />
|
|
481
|
-
))}
|
|
482
|
-
```
|
|
75
|
+
### Semantic HTML
|
|
76
|
+
**ALWAYS use `<button>` for click actions, `<a>` or `<Link>` for navigation.** Never use `<div role="button">`.
|
|
483
77
|
|
|
484
|
-
###
|
|
485
|
-
-
|
|
486
|
-
-
|
|
487
|
-
-
|
|
488
|
-
-
|
|
78
|
+
### Performance
|
|
79
|
+
- `memo()` for list item components
|
|
80
|
+
- `useCallback` for event handlers passed as props
|
|
81
|
+
- `useMemo` for expensive computations
|
|
82
|
+
- Unique `id` for list keys (NEVER array index)
|
|
489
83
|
|
|
490
84
|
## ABSOLUTE RULES
|
|
491
85
|
|
|
@@ -493,10 +87,9 @@ const filteredItems = items.filter(item => item.status === filter);
|
|
|
493
87
|
|----|-------|
|
|
494
88
|
| CSS variables for ALL colors | Hardcoded Tailwind colors (`bg-blue-500`) |
|
|
495
89
|
| `--success-dot`, `--warning-dot`, `--error-dot` for action buttons | `bg-emerald-500`, `bg-amber-500`, `bg-red-500` |
|
|
496
|
-
| `--info-bg`, `--success-bg`, etc. for badges | `bg-blue-500/10 dark:bg-blue-900/20` |
|
|
497
90
|
| Ghost buttons WITHOUT border | `border border-[var(--border-color)]` on ghost |
|
|
498
91
|
| EntityCard for entities | Custom cards with manual divs |
|
|
499
|
-
| Responsive grid 1
|
|
92
|
+
| Responsive grid 1->2->3->4 | Fixed non-responsive grid |
|
|
500
93
|
| h-full + flex-1 + mt-auto | Unaligned buttons in grid |
|
|
501
94
|
| Empty and loading states | Native HTML tooltip |
|
|
502
95
|
| Explicit TypeScript interfaces | `any` or implicit types |
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Accessibility & Best Practices
|
|
2
|
+
|
|
3
|
+
## TYPESCRIPT INTERFACES (MANDATORY)
|
|
4
|
+
|
|
5
|
+
**ALWAYS define explicit TypeScript interfaces for component props.**
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// CORRECT - Explicit interface with all props typed
|
|
9
|
+
interface UserCardProps {
|
|
10
|
+
user: User;
|
|
11
|
+
onSelect?: (user: User) => void;
|
|
12
|
+
isSelected?: boolean;
|
|
13
|
+
variant?: 'default' | 'compact';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const UserCard: React.FC<UserCardProps> = ({
|
|
17
|
+
user,
|
|
18
|
+
onSelect,
|
|
19
|
+
isSelected = false,
|
|
20
|
+
variant = 'default'
|
|
21
|
+
}) => {
|
|
22
|
+
// ...
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// WRONG - Missing interface, inline types, or any
|
|
26
|
+
export const UserCard = ({ user, onSelect }: any) => { ... }
|
|
27
|
+
export const UserCard = (props: { user: any }) => { ... }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### State Typing
|
|
31
|
+
```typescript
|
|
32
|
+
// CORRECT - Explicit state types
|
|
33
|
+
const [users, setUsers] = useState<User[]>([]);
|
|
34
|
+
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
35
|
+
const [status, setStatus] = useState<'idle' | 'loading' | 'error'>('idle');
|
|
36
|
+
|
|
37
|
+
// WRONG - Implicit or missing types
|
|
38
|
+
const [users, setUsers] = useState([]);
|
|
39
|
+
const [data, setData] = useState();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## SEMANTIC HTML (ACCESSIBILITY)
|
|
43
|
+
|
|
44
|
+
**ALWAYS use semantic HTML elements instead of divs with roles.**
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
// CORRECT - Native button element
|
|
48
|
+
<button type="button" onClick={handleClick} disabled={isDisabled}>
|
|
49
|
+
Click me
|
|
50
|
+
</button>
|
|
51
|
+
|
|
52
|
+
// WRONG - div with role (S6819)
|
|
53
|
+
<div role="button" onClick={handleClick} tabIndex={0}>
|
|
54
|
+
Click me
|
|
55
|
+
</div>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Interactive Elements Mapping
|
|
59
|
+
|
|
60
|
+
| Use Case | Correct Element | Wrong Pattern |
|
|
61
|
+
|----------|----------------|---------------|
|
|
62
|
+
| Clickable action | `<button type="button">` | `<div role="button">` |
|
|
63
|
+
| Navigation link | `<a href="...">` or `<Link>` | `<div onClick={navigate}>` |
|
|
64
|
+
| Form submission | `<button type="submit">` | `<div onClick={submit}>` |
|
|
65
|
+
| Expandable section | `<details><summary>` | `<div onClick={toggle}>` |
|
|
66
|
+
| List of items | `<ul><li>` with `<button>` inside | `<div role="listitem">` |
|
|
67
|
+
|
|
68
|
+
### Clickable Cards Pattern
|
|
69
|
+
```tsx
|
|
70
|
+
// CORRECT - Card with button inside
|
|
71
|
+
<div className="rounded-[var(--radius-card)] border ...">
|
|
72
|
+
<div className="p-4">
|
|
73
|
+
<h3>{title}</h3>
|
|
74
|
+
<p>{description}</p>
|
|
75
|
+
</div>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
onClick={() => onSelect(item)}
|
|
79
|
+
className="w-full px-4 py-2 border-t ..."
|
|
80
|
+
>
|
|
81
|
+
Select
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
// CORRECT - Entire card as link
|
|
86
|
+
<Link to={`/items/${item.id}`} className="block rounded-[var(--radius-card)] ...">
|
|
87
|
+
<div className="p-4">...</div>
|
|
88
|
+
</Link>
|
|
89
|
+
|
|
90
|
+
// WRONG - div with click handler
|
|
91
|
+
<div onClick={() => onSelect(item)} className="cursor-pointer ...">
|
|
92
|
+
...
|
|
93
|
+
</div>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## PERFORMANCE OPTIMIZATION
|
|
97
|
+
|
|
98
|
+
### React.memo for Pure Components
|
|
99
|
+
```tsx
|
|
100
|
+
import { memo, useCallback, useMemo } from 'react';
|
|
101
|
+
|
|
102
|
+
// CORRECT - Memoized component for lists
|
|
103
|
+
export const UserCard = memo(({ user, onSelect }: UserCardProps) => {
|
|
104
|
+
return (
|
|
105
|
+
<div className="...">
|
|
106
|
+
<h3>{user.name}</h3>
|
|
107
|
+
<button onClick={() => onSelect(user)}>Select</button>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
UserCard.displayName = 'UserCard';
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### useCallback for Event Handlers
|
|
115
|
+
```tsx
|
|
116
|
+
// CORRECT - Stable callback reference
|
|
117
|
+
const handleSelect = useCallback((item: Item) => {
|
|
118
|
+
onSelect?.(item);
|
|
119
|
+
setSelectedId(item.id);
|
|
120
|
+
}, [onSelect]);
|
|
121
|
+
|
|
122
|
+
// WRONG - New function on every render
|
|
123
|
+
const handleSelect = (item: Item) => {
|
|
124
|
+
onSelect?.(item);
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### useMemo for Expensive Computations
|
|
129
|
+
```tsx
|
|
130
|
+
// CORRECT - Memoized filtered list
|
|
131
|
+
const filteredItems = useMemo(() =>
|
|
132
|
+
items.filter(item => item.status === filter),
|
|
133
|
+
[items, filter]
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// WRONG - Recomputed on every render
|
|
137
|
+
const filteredItems = items.filter(item => item.status === filter);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## LIST RENDERING (UNIQUE KEYS)
|
|
141
|
+
|
|
142
|
+
**NEVER use array index as React key. ALWAYS use unique identifiers.**
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
// CORRECT - Unique ID from data
|
|
146
|
+
{items.map(item => (
|
|
147
|
+
<EntityCard key={item.id} {...mapToCardProps(item)} />
|
|
148
|
+
))}
|
|
149
|
+
|
|
150
|
+
// CORRECT - Composite key when needed
|
|
151
|
+
{items.map(item => (
|
|
152
|
+
<Row key={`${item.tenantId}-${item.id}`} data={item} />
|
|
153
|
+
))}
|
|
154
|
+
|
|
155
|
+
// WRONG - Array index as key (S6479)
|
|
156
|
+
{items.map((item, index) => (
|
|
157
|
+
<EntityCard key={index} {...mapToCardProps(item)} />
|
|
158
|
+
))}
|
|
159
|
+
|
|
160
|
+
// WRONG - Random key
|
|
161
|
+
{items.map(item => (
|
|
162
|
+
<EntityCard key={Math.random()} {...mapToCardProps(item)} />
|
|
163
|
+
))}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Why Index Keys Are Problematic
|
|
167
|
+
- Causes unnecessary re-renders when list order changes
|
|
168
|
+
- Breaks component state (inputs, animations)
|
|
169
|
+
- Causes visual glitches with drag-and-drop
|
|
170
|
+
- Makes reconciliation inefficient
|