@turk.net/mui 3.0.7 → 3.0.8
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/.opencode/rules/design-compliance.md +119 -0
- package/.opencode/skills/onehub-design-verification/SKILL.md +315 -0
- package/.opencode/skills/turknet-onehub-component-usage/SKILL.md +16 -16
- package/COMPONENT_GLOSSARY.md +500 -0
- package/COMPONENT_MAP.md +567 -0
- package/DESIGN_RULES.md +399 -0
- package/DESIGN_SOURCES.md +198 -0
- package/DESIGN_TOKENS_MAP.md +271 -0
- package/ESLINT_DESIGN_RULES.md +392 -0
- package/LLM_EXAMPLES.md +183 -169
- package/LLM_GUIDELINES.md +192 -192
- package/PAGE_PATTERNS.md +509 -0
- package/SKILL_SETUP.md +237 -200
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/dist/theme/index.js +1 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +1 -0
- package/dist/theme/index.mjs.map +1 -1
- package/package.json +20 -3
- package/scripts/verify-design.js +351 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# OneHub Design Tokens Map
|
|
2
|
+
|
|
3
|
+
> **Purpose:** Complete mapping of Figma design tokens to MUI theme paths and CSS custom properties.
|
|
4
|
+
>
|
|
5
|
+
> **Critical Rule:** ALL colors MUST be referenced via CSS custom properties (preferred) or `theme.vars.palette.*` paths. NEVER use raw hex values or RGB values directly in component code.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Color System Architecture
|
|
10
|
+
|
|
11
|
+
OneHub uses a **3-tier color architecture**:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Layer 1: Raw color tokens (colors.ts) → gray-2, primary-10, red-origin, ...
|
|
15
|
+
Layer 2: Semantic palettes (palettes.ts) → neutral.background[1], brand.foreground[2], ...
|
|
16
|
+
Layer 3: CSS custom props (colorSchemes.ts) → --neutral-background-1-rest, --brand-foreground-2-rest, ...
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### State Suffixes
|
|
20
|
+
Every color token has **4 interaction states**:
|
|
21
|
+
|
|
22
|
+
| State | Suffix | Usage |
|
|
23
|
+
|-------|--------|-------|
|
|
24
|
+
| **rest** | `-rest` | Default/normal state |
|
|
25
|
+
| **hover** | `-hover` | Mouse hover |
|
|
26
|
+
| **pressed** | `-pressed` | Active/clicked |
|
|
27
|
+
| **selected** | `-selected` | Currently selected |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Palette Categories
|
|
32
|
+
|
|
33
|
+
### 1. Neutral Palette (`neutral`)
|
|
34
|
+
|
|
35
|
+
Default UI elements: backgrounds, text, borders.
|
|
36
|
+
|
|
37
|
+
#### Background (`neutral.background`)
|
|
38
|
+
|
|
39
|
+
| Figma CSS Var | MUI Theme Path | Typical Color (Light) | Usage |
|
|
40
|
+
|---------------|---------------|----------------------|-------|
|
|
41
|
+
| `--neutral-background-1-rest` | `theme.vars.palette.neutral.background[1].rest` | `#ffffff` (white) | Primary surface: page bg, card bg, table row |
|
|
42
|
+
| `--neutral-background-2-rest` | `theme.vars.palette.neutral.background[2].rest` | `#fafafa` | Secondary surface: table header, sidebar bg |
|
|
43
|
+
| `--neutral-background-3-rest` | `theme.vars.palette.neutral.background[3].rest` | — | Tertiary surface |
|
|
44
|
+
| `--neutral-background-4-rest` | `theme.vars.palette.neutral.background[4].rest` | — | Quaternary surface |
|
|
45
|
+
| `--neutral-background-5-rest` | `theme.vars.palette.neutral.background[5].rest` | — | 5th level surface |
|
|
46
|
+
| `--neutral-background-6-rest` | `theme.vars.palette.neutral.background[6].rest` | — | 6th level surface |
|
|
47
|
+
| `--neutral-background-disabled-rest` | `theme.vars.palette.neutral.background.disabled.rest` | — | Disabled element bg |
|
|
48
|
+
| `--neutral-background-inverted-rest` | `theme.vars.palette.neutral.background.inverted.rest` | `#000000` | Inverted background (dark bg on light) |
|
|
49
|
+
| `--neutral-background-overlay-rest` | `theme.vars.palette.neutral.background.overlay.rest` | `rgba(0,0,0,0.40)` | Modal/dialog overlay |
|
|
50
|
+
| `--neutral-background-static-rest` | `theme.vars.palette.neutral.background.static.rest` | — | Static background (doesn't invert) |
|
|
51
|
+
| `--neutral-background-stencil-rest` | `theme.vars.palette.neutral.background.stencil.rest` | — | Skeleton/placeholder bg |
|
|
52
|
+
| `--neutral-background-subtle-rest` | `theme.vars.palette.neutral.background.subtle.rest` | `rgba(255,255,255,0)` | Transparent bg (icon buttons) |
|
|
53
|
+
|
|
54
|
+
#### Foreground / Text (`neutral.foreground`)
|
|
55
|
+
|
|
56
|
+
| Figma CSS Var | MUI Theme Path | Typical Color (Light) | Usage |
|
|
57
|
+
|---------------|---------------|----------------------|-------|
|
|
58
|
+
| `--neutral-foreground-1-rest` | `theme.vars.palette.neutral.foreground[1].rest` | `#242424` | Primary text |
|
|
59
|
+
| `--neutral-foreground-2-rest` | `theme.vars.palette.neutral.foreground[2].rest` | `#424242` | Secondary text, form labels |
|
|
60
|
+
| `--neutral-foreground-3-rest` | `theme.vars.palette.neutral.foreground[3].rest` | — | Tertiary text |
|
|
61
|
+
| `--neutral-foreground-4-rest` | `theme.vars.palette.neutral.foreground[4].rest` | `#707070` | Placeholder, disabled text |
|
|
62
|
+
| `--neutral-foreground-disabled-rest` | `theme.vars.palette.neutral.foreground.disabled.rest` | — | Fully disabled text |
|
|
63
|
+
| `--neutral-foreground-on-brand-rest` | `theme.vars.palette.neutral.foreground.on.brand.rest` | `#ffffff` | Text on brand-colored bg |
|
|
64
|
+
|
|
65
|
+
#### Stroke / Border (`neutral.stroke`)
|
|
66
|
+
|
|
67
|
+
| Figma CSS Var | MUI Theme Path | Typical Color (Light) | Usage |
|
|
68
|
+
|---------------|---------------|----------------------|-------|
|
|
69
|
+
| `--neutral-stroke-1-rest` | `theme.vars.palette.neutral.stroke[1].rest` | `#d1d1d1` | Primary border (inputs, outlined buttons) |
|
|
70
|
+
| `--neutral-stroke-2-rest` | `theme.vars.palette.neutral.stroke[2].rest` | — | Secondary border |
|
|
71
|
+
| `--neutral-stroke-3-rest` | `theme.vars.palette.neutral.stroke[3].rest` | `#f0f0f0` | Subtle divider (table borders) |
|
|
72
|
+
| `--neutral-stroke-accessible-rest` | `theme.vars.palette.neutral.stroke.accessible.rest` | `#616161` | Accessible border (checkboxes, focus rings) |
|
|
73
|
+
| `--neutral-stroke-disabled-rest` | `theme.vars.palette.neutral.stroke.disabled.rest` | — | Disabled element border |
|
|
74
|
+
| `--neutral-stroke-focus-rest` | `theme.vars.palette.neutral.stroke.focus.rest` | — | Focus ring color |
|
|
75
|
+
| `--neutral-stroke-subtle-rest` | `theme.vars.palette.neutral.stroke.subtle.rest` | — | Very subtle border |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
### 2. Brand Palette (`brand`)
|
|
80
|
+
|
|
81
|
+
Primary brand color (blue). Used for primary actions, selected states, brand elements.
|
|
82
|
+
|
|
83
|
+
| Figma CSS Var | MUI Theme Path | Typical Color (Light) | Usage |
|
|
84
|
+
|---------------|---------------|----------------------|-------|
|
|
85
|
+
| `--brand-background-1-rest` | `theme.vars.palette.brand.background[1].rest` | `#2970ff` | Primary button bg, selected state |
|
|
86
|
+
| `--brand-background-2-rest` | `theme.vars.palette.brand.background[2].rest` | `#e7efff` | Chip/Badge bg, filter tag bg |
|
|
87
|
+
| `--brand-background-compound-rest` | `theme.vars.palette.brand.background.compound.rest` | `#2970ff` | Compound bg (checkbox fill, radio fill) |
|
|
88
|
+
| `--brand-foreground-1-rest` | `theme.vars.palette.brand.foreground[1].rest` | — | Brand-colored text |
|
|
89
|
+
| `--brand-foreground-2-rest` | `theme.vars.palette.brand.foreground[2].rest` | `#2362df` | Chip/Badge text on brand bg |
|
|
90
|
+
| `--brand-foreground-compound-rest` | `theme.vars.palette.brand.foreground.compound.rest` | — | Compound foreground |
|
|
91
|
+
| `--brand-stroke-1-rest` | `theme.vars.palette.brand.stroke[1].rest` | — | Brand border |
|
|
92
|
+
| `--brand-stroke-2-rest` | `theme.vars.palette.brand.stroke[2].rest` | `#b7cfff` | Chip/Badge border |
|
|
93
|
+
| `--brand-stroke-compound-rest` | `theme.vars.palette.brand.stroke.compound.rest` | — | Compound stroke |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### 3. Danger Palette (`danger`)
|
|
98
|
+
|
|
99
|
+
Error, destructive actions, critical states. Uses red tones.
|
|
100
|
+
|
|
101
|
+
| Figma CSS Var | MUI Theme Path | Usage |
|
|
102
|
+
|---------------|---------------|-------|
|
|
103
|
+
| `--danger-background-1-rest` | `theme.vars.palette.danger.background[1].rest` | Error button bg |
|
|
104
|
+
| `--danger-background-2-rest` | `theme.vars.palette.danger.background[2].rest` | Error chip/badge bg |
|
|
105
|
+
| `--danger-foreground-1-rest` | `theme.vars.palette.danger.foreground[1].rest` | Error text |
|
|
106
|
+
| `--danger-foreground-2-rest` | `theme.vars.palette.danger.foreground[2].rest` | Error chip/badge text |
|
|
107
|
+
| `--danger-stroke-1-rest` | `theme.vars.palette.danger.stroke[1].rest` | Error border |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### 4. Shared Palette (`shared`)
|
|
112
|
+
|
|
113
|
+
Common semantic colors available as sub-palettes.
|
|
114
|
+
|
|
115
|
+
| Sub-palette | Typical Use |
|
|
116
|
+
|-------------|-------------|
|
|
117
|
+
| `shared.red` | Error, destructive |
|
|
118
|
+
| `shared.green` | Success, positive |
|
|
119
|
+
| `shared.orange` | Severe warning |
|
|
120
|
+
| `shared.yellow` | Warning |
|
|
121
|
+
| `shared.purple` | Informational, special |
|
|
122
|
+
| `shared.pink` | Accent |
|
|
123
|
+
| `shared.cyan` | Info, link |
|
|
124
|
+
| `shared.lime` | Success alternative |
|
|
125
|
+
|
|
126
|
+
Each has `background`, `foreground`, and `stroke` properties with 4 states.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Typography Token Map
|
|
131
|
+
|
|
132
|
+
### Untitled UI System → MUI Variant → Figma Equivalent
|
|
133
|
+
|
|
134
|
+
OneHub uses custom typography variants. MUI default `h1`-`h6`, `body1`, `body2`, `subtitle1`, `subtitle2`, `caption`, `button` are **DISABLED** and MUST NOT be used.
|
|
135
|
+
|
|
136
|
+
#### Display Scale
|
|
137
|
+
|
|
138
|
+
| MUI Variant | Font Size | Line Height | Font Weight(s) | Figma Class Equivalent |
|
|
139
|
+
|-------------|-----------|-------------|----------------|----------------------|
|
|
140
|
+
| `display.xl2.bold` | 72px | 90px | 700 | — |
|
|
141
|
+
| `display.xl.bold` | 60px | 72px | 700 | — |
|
|
142
|
+
| `display.lg.bold` | 48px | 60px | 700 | — |
|
|
143
|
+
| `display.lg.semibold` | 48px | 60px | 600 | — |
|
|
144
|
+
| `display.md.bold` | 36px | 38px | 700 | — |
|
|
145
|
+
| `display.md.semibold` | 36px | 38px | 600 | — |
|
|
146
|
+
| `display.sm.bold` | 30px | 38px | 700 | — |
|
|
147
|
+
| `display.xs.bold` | 24px | 32px | 700 | — |
|
|
148
|
+
| `display.xs.semibold` | 24px | 32px | 600 | — |
|
|
149
|
+
|
|
150
|
+
#### Text Scale
|
|
151
|
+
|
|
152
|
+
| MUI Variant | Font Size | Line Height | Font Weight(s) | Figma Equivalent |
|
|
153
|
+
|-------------|-----------|-------------|----------------|-----------------|
|
|
154
|
+
| `text.xl.medium` | 20px | 30px | 500 | `text-[20px] leading-[30px] font-medium` |
|
|
155
|
+
| `text.xl.regular` | 20px | 30px | 400 | `text-[20px] leading-[30px] font-normal` |
|
|
156
|
+
| `text.lg.medium` | 18px | 28px | 500 | `text-[18px] leading-[28px] font-medium` |
|
|
157
|
+
| `text.lg.regular` | 18px | 28px | 400 | `text-[18px] leading-[28px] font-normal` |
|
|
158
|
+
| `text.md.medium` | 16px | 28px | 500 | `text-[16px] leading-[28px] font-medium` |
|
|
159
|
+
| `text.md.regular` | 16px | 28px | 400 | `text-[16px] leading-[28px] font-normal` |
|
|
160
|
+
| `text.sm.semibold` | 14px | 20px | 600 | `text-[14px] leading-[20px] font-semibold` |
|
|
161
|
+
| `text.sm.medium` | 14px | 20px | 500 | `text-[14px] leading-[20px] font-medium` |
|
|
162
|
+
| `text.sm.regular` | 14px | 20px | 400 | `text-[14px] leading-[20px] font-normal` |
|
|
163
|
+
| `text.xs.medium` | 12px | 18px | 500 | `text-[12px] leading-[18px] font-medium` |
|
|
164
|
+
| `text.xs.regular` | 12px | 18px | 400 | `text-[12px] leading-[18px] font-normal` |
|
|
165
|
+
|
|
166
|
+
### Usage Rules
|
|
167
|
+
|
|
168
|
+
- **Page titles** MUST use `display.*` variants
|
|
169
|
+
- **Section headers** MUST use `text.xl.*` or `text.lg.*`
|
|
170
|
+
- **Body text** MUST use `text.md.*` or `text.sm.*`
|
|
171
|
+
- **Table headers** MUST use `text.xs.medium`
|
|
172
|
+
- **Labels, captions** MUST use `text.xs.*`
|
|
173
|
+
- **NEVER use `variant="h1"`, `variant="body1"`, etc.** — these are disabled
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Spacing System
|
|
178
|
+
|
|
179
|
+
### Base Grid: 4px
|
|
180
|
+
|
|
181
|
+
MU theme uses **`spacing: 4`** instead of MUI default 8. All spacing values are multiples of 4.
|
|
182
|
+
|
|
183
|
+
| Figma Value | MUI equivalent | Usage Pattern |
|
|
184
|
+
|-------------|---------------|---------------|
|
|
185
|
+
| 4px | `theme.spacing(1)` | Icon gaps, tight padding |
|
|
186
|
+
| 8px | `theme.spacing(2)` | Standard gap between elements |
|
|
187
|
+
| 12px | `theme.spacing(3)` | Button horizontal padding |
|
|
188
|
+
| 16px | `theme.spacing(4)` | Card padding, section gap |
|
|
189
|
+
| 24px | `theme.spacing(6)` | Large section gap |
|
|
190
|
+
| 32px | `theme.spacing(8)` | Table cell horizontal padding |
|
|
191
|
+
| 40px | `theme.spacing(10)` | — |
|
|
192
|
+
| 48px | `theme.spacing(12)` | — |
|
|
193
|
+
| 64px | `theme.spacing(16)` | Page padding |
|
|
194
|
+
|
|
195
|
+
### Common Figma Spacing Patterns
|
|
196
|
+
|
|
197
|
+
| Figma Pattern | MUI Code Equivalent |
|
|
198
|
+
|---------------|-------------------|
|
|
199
|
+
| `gap-[8px]` | `gap: theme.spacing(2)` or `sx={{ gap: 2 }}` |
|
|
200
|
+
| `gap-[4px]` | `gap: theme.spacing(1)` or `sx={{ gap: 1 }}` |
|
|
201
|
+
| `px-[32px]` (table cell) | `paddingLeft: theme.spacing(8), paddingRight: theme.spacing(8)` |
|
|
202
|
+
| `py-[16px]` (section) | `paddingTop: theme.spacing(4), paddingBottom: theme.spacing(4)` |
|
|
203
|
+
| `px-[8px]` (button icon) | `paddingLeft: theme.spacing(2), paddingRight: theme.spacing(2)` |
|
|
204
|
+
| `px-[12px]` (button text) | `paddingLeft: theme.spacing(3), paddingRight: theme.spacing(3)` |
|
|
205
|
+
|
|
206
|
+
### Magic Number Rule
|
|
207
|
+
|
|
208
|
+
> **MUST NOT use raw pixel values.** Every spacing value MUST pass through `theme.spacing(n)`.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// ✅ CORRECT
|
|
212
|
+
sx={{ padding: theme.spacing(2) }}
|
|
213
|
+
|
|
214
|
+
// ❌ WRONG — Magic numbers
|
|
215
|
+
sx={{ padding: '8px' }}
|
|
216
|
+
sx={{ padding: 8 }}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Border Radius
|
|
222
|
+
|
|
223
|
+
| Figma Value | MUI Equivalent | Usage |
|
|
224
|
+
|-------------|---------------|-------|
|
|
225
|
+
| `rounded-[4px]` | `theme.shape.borderRadius / 2` (4px) | Checkbox, small elements |
|
|
226
|
+
| `rounded-[8px]` | `theme.shape.borderRadius` (8px) | Buttons, inputs, cards, radio buttons |
|
|
227
|
+
| `rounded-[16px]` | `theme.shape.borderRadius * 2` (16px) | Chips, badges, filter tags |
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Shadow System
|
|
232
|
+
|
|
233
|
+
25-level shadow system. Access via `theme.shadows[n]`.
|
|
234
|
+
|
|
235
|
+
| Level | Figma Pattern | Usage |
|
|
236
|
+
|-------|--------------|-------|
|
|
237
|
+
| `shadows[0]` | `none` | Flat elements |
|
|
238
|
+
| `shadows[1]` | — | MUI default (minimal shadow) |
|
|
239
|
+
| `shadows[2]` | `0 0 2px rgba(0,0,0,0.12)` | Very subtle elevation |
|
|
240
|
+
| `shadows[3]` | `0 0 4px rgba(0,0,0,0.12)` | Slight elevation |
|
|
241
|
+
| `shadows[4]` | `0 0 8px rgba(0,0,0,0.12)` | Dropdowns, tooltips |
|
|
242
|
+
| `shadows[5]` | `0 0 16px rgba(0,0,0,0.12)` | Dialogs |
|
|
243
|
+
| `shadows[6]` | `0 0 28px rgba(0,0,0,0.12)` | Modals |
|
|
244
|
+
| `shadows[7]` | `0 0 64px rgba(0,0,0,0.12)` | High elevation modals |
|
|
245
|
+
| `shadows[8-13]` | Brand shadows (`rgba(0,0,0,0.3)` overlay) | Branded elevated elements |
|
|
246
|
+
| `shadows[14-24]` | MUI defaults | — |
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Sizing System
|
|
251
|
+
|
|
252
|
+
### Component Height Table (from Figma)
|
|
253
|
+
|
|
254
|
+
| Component | Height | Notes |
|
|
255
|
+
|-----------|--------|-------|
|
|
256
|
+
| Button (medium) | `32px` | Standard action button |
|
|
257
|
+
| Table Header | `40px` | — |
|
|
258
|
+
| Table Row | `56px` | — |
|
|
259
|
+
| Filter Button | `32px` | Same as button |
|
|
260
|
+
| Checkbox/Radio | `16px` | Square (width = height) |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Font Family
|
|
265
|
+
|
|
266
|
+
| Property | Value |
|
|
267
|
+
|----------|-------|
|
|
268
|
+
| **Family** | Inter (`var(--font-inter)`) |
|
|
269
|
+
| **Weights** | 400 (Regular), 500 (Medium), 600 (Semibold), 700 (Bold) |
|
|
270
|
+
| **Host Project Responsibility** | MUST define `@font-face` and set `--font-inter` CSS variable |
|
|
271
|
+
| **Package** | Font files NOT distributed by `@turknet/onehub`. Host project must self-host. |
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# ESLint Design Rules — Specification
|
|
2
|
+
|
|
3
|
+
> **Purpose:** Specification for a custom ESLint plugin that automates OneHub design rule enforcement.
|
|
4
|
+
>
|
|
5
|
+
> **Status:** Specification / future implementation. Currently enforced via `scripts/verify-design.js`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
This document defines ESLint rules that can automatically detect design violations in OneHub projects. Rules are divided into:
|
|
12
|
+
|
|
13
|
+
1. **Auto-detectable** — Can be reliably detected by static analysis (ESLint)
|
|
14
|
+
2. **Manual review required** — Needs human/AI judgment, cannot be fully automated
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Auto-Detectable Rules (ESLint)
|
|
19
|
+
|
|
20
|
+
### `onehub/no-mui-default-typography`
|
|
21
|
+
|
|
22
|
+
Detects usage of MUI default typography variants that are disabled in the theme.
|
|
23
|
+
|
|
24
|
+
**Rule:** Disallow `variant="h1"` through `variant="h6"`, `variant="body1"`, `variant="body2"`, `variant="subtitle1"`, `variant="subtitle2"`, `variant="caption"`, `variant="button"`, `variant="overline"`.
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"onehub/no-mui-default-typography": "error"
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Detects:**
|
|
33
|
+
```tsx
|
|
34
|
+
// ❌ Error
|
|
35
|
+
<Typography variant="h1">Title</Typography>
|
|
36
|
+
<Typography variant="body1">Text</Typography>
|
|
37
|
+
<Typography variant="caption">Caption</Typography>
|
|
38
|
+
|
|
39
|
+
// ✅ OK
|
|
40
|
+
<Typography variant="display.lg.bold">Title</Typography>
|
|
41
|
+
<Typography variant="text.md.regular">Text</Typography>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### `onehub/no-inline-style`
|
|
47
|
+
|
|
48
|
+
Detects inline `style={{}}` props on JSX elements.
|
|
49
|
+
|
|
50
|
+
**Rule:** Disallow `style={{}` prop usage. Must use `sx` or `styled()`.
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"onehub/no-inline-style": "error"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Detects:**
|
|
59
|
+
```tsx
|
|
60
|
+
// ❌ Error
|
|
61
|
+
<Button style={{ color: 'red' }}>Click</Button>
|
|
62
|
+
<div style={{ padding: 20 }}>Text</div>
|
|
63
|
+
|
|
64
|
+
// ✅ OK
|
|
65
|
+
<Button sx={{ color: theme.vars.palette.danger.foreground[1].rest }}>
|
|
66
|
+
<Box sx={{ padding: theme.spacing(5) }}>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### `onehub/no-raw-hex-colors`
|
|
72
|
+
|
|
73
|
+
Detects raw hex color values in JSX attributes and template literals.
|
|
74
|
+
|
|
75
|
+
**Rule:** Disallow `#`-prefixed hex colors in `sx`, `css`, `styled` (unless inside `theme.vars.*` or `var(--*)` context).
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"onehub/no-raw-hex-colors": "error"
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Detects:**
|
|
84
|
+
```tsx
|
|
85
|
+
// ❌ Error
|
|
86
|
+
sx={{ color: '#242424' }}
|
|
87
|
+
sx={{ backgroundColor: '#2970ff' }}
|
|
88
|
+
|
|
89
|
+
// ✅ OK
|
|
90
|
+
sx={{ color: 'var(--neutral-foreground-1-rest)' }}
|
|
91
|
+
sx={{ bgcolor: theme.vars.palette.brand.background[1].rest }}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Edge cases to handle:**
|
|
95
|
+
- SVG fill values may legitimately use hex colors → exclude SVG elements
|
|
96
|
+
- Storybook stories may use hex for demo purposes → allow in `.stories.tsx` files
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### `onehub/no-magic-spacing`
|
|
101
|
+
|
|
102
|
+
Detects raw pixel values and numeric spacing that should use `theme.spacing()`.
|
|
103
|
+
|
|
104
|
+
**Rule:** Disallow string px values (`'8px'`) and raw number spacing values in `sx` and `css` props.
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"onehub/no-magic-spacing": ["error", { "allowedZero": true }]
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Detects:**
|
|
113
|
+
```tsx
|
|
114
|
+
// ❌ Error
|
|
115
|
+
sx={{ padding: '8px' }}
|
|
116
|
+
sx={{ margin: '16px' }}
|
|
117
|
+
sx={{ gap: '4px' }}
|
|
118
|
+
|
|
119
|
+
// ✅ OK
|
|
120
|
+
sx={{ padding: theme.spacing(2) }}
|
|
121
|
+
sx={{ margin: theme.spacing(4) }}
|
|
122
|
+
sx={{ gap: theme.spacing(1) }}
|
|
123
|
+
sx={{ padding: 0 }}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### `onehub/no-invalid-mui-color-prop`
|
|
129
|
+
|
|
130
|
+
Detects MUI `color` prop values that are not valid for the specific component.
|
|
131
|
+
|
|
132
|
+
**Rule:** Enforce valid `color` prop values per component based on theme definitions.
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"onehub/no-invalid-mui-color-prop": "error"
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Detects:**
|
|
141
|
+
```tsx
|
|
142
|
+
// ❌ Error
|
|
143
|
+
<Button color="success">Save</Button> // Valid: primary, secondary, error
|
|
144
|
+
<TextField color="primary" /> // Valid: neutral, brand, danger
|
|
145
|
+
<Chip color="success" /> // Valid: primary, secondary, error
|
|
146
|
+
<Badge color="primary" /> // Valid: default, brand, success, danger, ...warning, important
|
|
147
|
+
|
|
148
|
+
// ✅ OK
|
|
149
|
+
<Button color="primary" />
|
|
150
|
+
<TextField color="neutral" />
|
|
151
|
+
<Chip color="primary" />
|
|
152
|
+
<Badge color="success" />
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### `onehub/no-invalid-mui-variant-prop`
|
|
158
|
+
|
|
159
|
+
Detects MUI `variant` prop values that are not valid for the specific component.
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"onehub/no-invalid-mui-variant-prop": "error"
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Detects:**
|
|
168
|
+
```tsx
|
|
169
|
+
// ❌ Error
|
|
170
|
+
<Button variant="gradient">Click</Button> // Valid: contained, outlined, text
|
|
171
|
+
<Chip variant="soft" /> // Valid: filled, tint, outline, ghost
|
|
172
|
+
|
|
173
|
+
// ✅ OK
|
|
174
|
+
<Button variant="contained" />
|
|
175
|
+
<Chip variant="tint" />
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### `onehub/no-full-mui-import`
|
|
181
|
+
|
|
182
|
+
Detects full library imports from `@mui/material`.
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"onehub/no-full-mui-import": "error"
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Detects:**
|
|
191
|
+
```tsx
|
|
192
|
+
// ❌ Error
|
|
193
|
+
import * as Mui from '@mui/material';
|
|
194
|
+
|
|
195
|
+
// ✅ OK
|
|
196
|
+
import { Button, Typography, Box } from '@mui/material';
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
### `onehub/no-checkbox-radio-size-large`
|
|
202
|
+
|
|
203
|
+
Detects `size="large"` on Checkbox and Radio components.
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"onehub/no-checkbox-radio-size-large": "error"
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Detects:**
|
|
212
|
+
```tsx
|
|
213
|
+
// ❌ Error
|
|
214
|
+
<Checkbox size="large" />
|
|
215
|
+
<Radio size="large" />
|
|
216
|
+
|
|
217
|
+
// ✅ OK
|
|
218
|
+
<Checkbox size="small" />
|
|
219
|
+
<Radio size="medium" />
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### `onehub/no-non-vars-palette`
|
|
225
|
+
|
|
226
|
+
Detects usage of `theme.palette.*` (non-vars palette) instead of `theme.vars.palette.*`.
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"onehub/no-non-vars-palette": "error"
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Detects:**
|
|
235
|
+
```tsx
|
|
236
|
+
// ❌ Error
|
|
237
|
+
sx={{ color: theme.palette.primary.main }}
|
|
238
|
+
sx={{ bgcolor: theme.palette.error.light }}
|
|
239
|
+
|
|
240
|
+
// ✅ OK
|
|
241
|
+
sx={{ color: theme.vars.palette.brand.foreground[1].rest }}
|
|
242
|
+
sx={{ bgcolor: theme.vars.palette.danger.background[2].rest }}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Manual Review Required (Cannot be fully automated)
|
|
248
|
+
|
|
249
|
+
These rules require human or AI judgment because context matters.
|
|
250
|
+
|
|
251
|
+
| Rule | Why Manual |
|
|
252
|
+
|------|-----------|
|
|
253
|
+
| Typography variant matches content hierarchy | Page title vs section header — depends on intent |
|
|
254
|
+
| Correct MUI component selected for Figma pattern | Requires visual comparison |
|
|
255
|
+
| Form uses Formik + Yup | Can check imports but can't verify correct usage |
|
|
256
|
+
| Data fetching uses React Query | Can check imports but can't verify correct pattern |
|
|
257
|
+
| Icons from correct source | `@turknet/onehub/icons` vs `@mui/icons-material` can be checked by import source |
|
|
258
|
+
| Accessibility (labels, keyboard nav) | Needs behavioral/visual verification |
|
|
259
|
+
| Border radius matches element type | Context-dependent |
|
|
260
|
+
| Shadow/elevation level appropriate | Context-dependent |
|
|
261
|
+
| Layout component used correctly | Needs understanding of page structure |
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Configuration Format
|
|
266
|
+
|
|
267
|
+
### `.eslintrc.json` Example
|
|
268
|
+
|
|
269
|
+
```json
|
|
270
|
+
{
|
|
271
|
+
"plugins": ["onehub"],
|
|
272
|
+
"rules": {
|
|
273
|
+
"onehub/no-mui-default-typography": "error",
|
|
274
|
+
"onehub/no-inline-style": "error",
|
|
275
|
+
"onehub/no-raw-hex-colors": "error",
|
|
276
|
+
"onehub/no-magic-spacing": "error",
|
|
277
|
+
"onehub/no-invalid-mui-color-prop": "error",
|
|
278
|
+
"onehub/no-invalid-mui-variant-prop": "error",
|
|
279
|
+
"onehub/no-full-mui-import": "error",
|
|
280
|
+
"onehub/no-checkbox-radio-size-large": "error",
|
|
281
|
+
"onehub/no-non-vars-palette": "error"
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### `eslint.config.mjs` (Flat Config) Example
|
|
287
|
+
|
|
288
|
+
```js
|
|
289
|
+
import onehub from 'eslint-plugin-onehub';
|
|
290
|
+
|
|
291
|
+
export default [
|
|
292
|
+
{
|
|
293
|
+
plugins: { onehub },
|
|
294
|
+
rules: {
|
|
295
|
+
'onehub/no-mui-default-typography': 'error',
|
|
296
|
+
'onehub/no-inline-style': 'error',
|
|
297
|
+
'onehub/no-raw-hex-colors': 'error',
|
|
298
|
+
'onehub/no-magic-spacing': 'error',
|
|
299
|
+
'onehub/no-invalid-mui-color-prop': 'error',
|
|
300
|
+
'onehub/no-invalid-mui-variant-prop': 'error',
|
|
301
|
+
|
|
302
|
+
'onehub/no-full-mui-import': 'error',
|
|
303
|
+
'onehub/no-checkbox-radio-size-large': 'error',
|
|
304
|
+
'onehub/no-non-vars-palette': 'error',
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Integration Steps
|
|
313
|
+
|
|
314
|
+
### 1. Install the ESLint plugin
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
yarn add -D eslint-plugin-onehub
|
|
318
|
+
# or
|
|
319
|
+
npm install --save-dev eslint-plugin-onehub
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### 2. Add to ESLint config
|
|
323
|
+
|
|
324
|
+
Add `"onehub"` to the `plugins` array and configure rules as shown above.
|
|
325
|
+
|
|
326
|
+
### 3. Add lint script
|
|
327
|
+
|
|
328
|
+
```json
|
|
329
|
+
{
|
|
330
|
+
"scripts": {
|
|
331
|
+
"lint:design": "eslint --rule 'onehub/*: error' src/"
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### 4. CI Integration
|
|
337
|
+
|
|
338
|
+
```yaml
|
|
339
|
+
# bitbucket-pipelines.yml example
|
|
340
|
+
design-lint:
|
|
341
|
+
- step:
|
|
342
|
+
name: Design Compliance Check
|
|
343
|
+
script:
|
|
344
|
+
- yarn lint:design
|
|
345
|
+
- node node_modules/@turknet/onehub/scripts/verify-design.js src/
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Rule Priority & Adoption Order
|
|
351
|
+
|
|
352
|
+
When implementing the ESLint plugin, prioritize rules in this order:
|
|
353
|
+
|
|
354
|
+
| Priority | Rule | Reason |
|
|
355
|
+
|----------|------|--------|
|
|
356
|
+
| **P0** | `no-mui-default-typography` | High impact, easy to detect |
|
|
357
|
+
| **P0** | `no-inline-style` | Common antipattern |
|
|
358
|
+
| **P1** | `no-raw-hex-colors` | High impact, some edge cases |
|
|
359
|
+
| **P1** | `no-magic-spacing` | High impact, some edge cases |
|
|
360
|
+
| **P2** | `no-invalid-mui-color-prop` | Per-component validation |
|
|
361
|
+
| **P2** | `no-invalid-mui-variant-prop` | Per-component validation |
|
|
362
|
+
| **P2** | `no-full-mui-import` | Bundle size |
|
|
363
|
+
| **P3** | `no-checkbox-radio-size-large` | Edge case |
|
|
364
|
+
| **P3** | `no-non-vars-palette` | Edge case, may have legit uses |
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Current Enforcement (Until ESLint Plugin Ready)
|
|
369
|
+
|
|
370
|
+
Until the custom ESLint plugin is implemented, use:
|
|
371
|
+
|
|
372
|
+
1. **`scripts/verify-design.js`** — Runs in CI and pre-commit
|
|
373
|
+
```bash
|
|
374
|
+
node scripts/verify-design.js src/
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
2. **`.opencode/rules/design-compliance.md`** — Always-apply rule for AI agents
|
|
378
|
+
|
|
379
|
+
3. **`.opencode/skills/onehub-design-verification/SKILL.md`** — Manual verification checklist
|
|
380
|
+
|
|
381
|
+
4. **Manual code review** — Check against DESIGN_RULES.md
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## References
|
|
386
|
+
|
|
387
|
+
- [ESLint Plugin Development Guide](https://eslint.org/docs/latest/extend/plugins)
|
|
388
|
+
- [ESTree AST Spec](https://github.com/estree/estree) — For writing AST-based rules
|
|
389
|
+
- [@typescript-eslint](https://typescript-eslint.io/) — TypeScript-aware linting
|
|
390
|
+
- `DESIGN_RULES.md` — Complete design rule reference
|
|
391
|
+
- `COMPONENT_MAP.md` — Valid prop combinations for all components
|
|
392
|
+
- `COMPONENT_GLOSSARY.md` — Exhaustive component catalog
|