@turk.net/mui 3.0.7 → 3.0.9
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 +128 -0
- package/.opencode/skills/onehub-design-verification/SKILL.md +315 -0
- package/.opencode/skills/turknet-onehub-component-usage/SKILL.md +65 -16
- package/COMPONENT_GLOSSARY.md +500 -0
- package/COMPONENT_MAP.md +659 -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 +246 -192
- package/PAGE_PATTERNS.md +571 -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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@turk.net/mui",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.9",
|
|
4
4
|
"description": "A theme library customized for Material UI (v9).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mui",
|
|
@@ -116,14 +116,31 @@
|
|
|
116
116
|
"./LLM_GUIDELINES.md": "./LLM_GUIDELINES.md",
|
|
117
117
|
"./LLM_EXAMPLES.md": "./LLM_EXAMPLES.md",
|
|
118
118
|
"./SKILL_SETUP.md": "./SKILL_SETUP.md",
|
|
119
|
-
"./
|
|
119
|
+
"./DESIGN_RULES.md": "./DESIGN_RULES.md",
|
|
120
|
+
"./COMPONENT_MAP.md": "./COMPONENT_MAP.md",
|
|
121
|
+
"./COMPONENT_GLOSSARY.md": "./COMPONENT_GLOSSARY.md",
|
|
122
|
+
"./DESIGN_SOURCES.md": "./DESIGN_SOURCES.md",
|
|
123
|
+
"./DESIGN_TOKENS_MAP.md": "./DESIGN_TOKENS_MAP.md",
|
|
124
|
+
"./PAGE_PATTERNS.md": "./PAGE_PATTERNS.md",
|
|
125
|
+
"./ESLINT_DESIGN_RULES.md": "./ESLINT_DESIGN_RULES.md",
|
|
126
|
+
"./skills/*": "./.opencode/skills/*",
|
|
127
|
+
"./skills/design-verification": "./.opencode/skills/onehub-design-verification/SKILL.md"
|
|
120
128
|
},
|
|
121
129
|
"files": [
|
|
122
130
|
"dist",
|
|
123
131
|
"LLM_GUIDELINES.md",
|
|
124
132
|
"LLM_EXAMPLES.md",
|
|
125
133
|
"SKILL_SETUP.md",
|
|
126
|
-
".
|
|
134
|
+
"DESIGN_RULES.md",
|
|
135
|
+
"COMPONENT_MAP.md",
|
|
136
|
+
"COMPONENT_GLOSSARY.md",
|
|
137
|
+
"DESIGN_SOURCES.md",
|
|
138
|
+
"DESIGN_TOKENS_MAP.md",
|
|
139
|
+
"PAGE_PATTERNS.md",
|
|
140
|
+
"ESLINT_DESIGN_RULES.md",
|
|
141
|
+
"scripts/verify-design.js",
|
|
142
|
+
".opencode/skills",
|
|
143
|
+
".opencode/rules"
|
|
127
144
|
],
|
|
128
145
|
"sideEffects": [
|
|
129
146
|
"**/*.css",
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// OneHub Design Verification Script
|
|
4
|
+
// Tarama: .tsx dosyalarını tarar, tasarım kurallarına aykırılıkları raporlar.
|
|
5
|
+
// Çıkış kodu: İhlal varsa 1, yoksa 0 (CI uyumlu).
|
|
6
|
+
//
|
|
7
|
+
// VALID_VARIANTS ve VALID_COLORS değerleri doğrudan tema paketinin
|
|
8
|
+
// component-augmentations.ts dosyasındaki type override'larından alınır.
|
|
9
|
+
// SADECE augmentation'da true/false ile belirtilen değerler kontrol edilir.
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
// ============================================================
|
|
15
|
+
// Tanımlar — Tema component-augmentations.ts baz alınarak
|
|
16
|
+
// ============================================================
|
|
17
|
+
|
|
18
|
+
// Variant kontrolü: augmentation'da variant değerleri true/false ile
|
|
19
|
+
// kısıtlanmış bileşenler
|
|
20
|
+
const VALID_VARIANTS = {
|
|
21
|
+
Button: ['contained', 'outlined', 'text'],
|
|
22
|
+
ButtonGroup: ['contained', 'outlined', 'text'],
|
|
23
|
+
Chip: ['filled', 'tint', 'outline', 'ghost'],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Color kontrolü: augmentation'da color değerleri true/false ile
|
|
27
|
+
// kısıtlanmış bileşenler (false = kullanılamaz)
|
|
28
|
+
const VALID_COLORS = {
|
|
29
|
+
Button: ['primary', 'secondary', 'error'],
|
|
30
|
+
ButtonGroup: ['primary', 'secondary', 'error'],
|
|
31
|
+
TextField: ['neutral', 'brand', 'danger'],
|
|
32
|
+
Chip: ['informative', 'brand', 'danger', 'success', 'severewarning', 'important', 'warning'],
|
|
33
|
+
Badge: ['default', 'brand', 'success', 'danger', 'severe-warning', 'warning', 'important', 'white'],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Checkbox için augmentation'da size.large = false
|
|
37
|
+
// Radio ve Switch için augmentation'da color/size kısıtlaması yok
|
|
38
|
+
|
|
39
|
+
// Yasaklı typography variant'ları (MUI default)
|
|
40
|
+
// Augmentation'da h1-h6, body1, body2, subtitle1, subtitle2, caption, button = false
|
|
41
|
+
const FORBIDDEN_TYPOGRAPHY_VARIANTS = [
|
|
42
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
43
|
+
'body1', 'body2',
|
|
44
|
+
'subtitle1', 'subtitle2',
|
|
45
|
+
'caption',
|
|
46
|
+
'button',
|
|
47
|
+
'overline',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// ============================================================
|
|
51
|
+
// Yardımcı fonksiyonlar
|
|
52
|
+
// ============================================================
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Bir dizindeki tüm .tsx dosyalarını recursive olarak bulur
|
|
56
|
+
*/
|
|
57
|
+
function findTsxFiles(dir) {
|
|
58
|
+
const results = [];
|
|
59
|
+
if (!fs.existsSync(dir)) {
|
|
60
|
+
console.error(`Hata: Dizin bulunamadı: ${dir}`);
|
|
61
|
+
process.exit(2);
|
|
62
|
+
}
|
|
63
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
// node_modules ve .next dizinlerini atla
|
|
66
|
+
if (entry.name === 'node_modules' || entry.name === '.next' || entry.name === '.git') {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const fullPath = path.join(dir, entry.name);
|
|
70
|
+
if (entry.isDirectory()) {
|
|
71
|
+
results.push(...findTsxFiles(fullPath));
|
|
72
|
+
} else if (entry.name.endsWith('.tsx') || entry.name.endsWith('.jsx')) {
|
|
73
|
+
results.push(fullPath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Dosyadaki bir satırın ihlal raporu
|
|
81
|
+
*/
|
|
82
|
+
function violation(filePath, lineNumber, rule, message) {
|
|
83
|
+
return { file: filePath, line: lineNumber, rule, message };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================================
|
|
87
|
+
// Denetim (check) fonksiyonları
|
|
88
|
+
// ============================================================
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check 1: Geçersiz variant prop'ları
|
|
92
|
+
* Tema augmentation'ında kısıtlanmış variant değerleri kontrol edilir
|
|
93
|
+
*/
|
|
94
|
+
function checkInvalidVariants(filePath, lines) {
|
|
95
|
+
const violations = [];
|
|
96
|
+
const variantRegex = /<(\w+)[^>]*?\bvariant\s*=\s*["'{]([^"'}]+)["'}][^>]*?\/?>/gi;
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < lines.length; i++) {
|
|
99
|
+
const line = lines[i];
|
|
100
|
+
let match;
|
|
101
|
+
while ((match = variantRegex.exec(line)) !== null) {
|
|
102
|
+
const componentName = match[1];
|
|
103
|
+
const variantValue = match[2];
|
|
104
|
+
const valid = VALID_VARIANTS[componentName];
|
|
105
|
+
if (valid && !valid.includes(variantValue)) {
|
|
106
|
+
violations.push(violation(
|
|
107
|
+
filePath, i + 1, 'invalid-variant',
|
|
108
|
+
`<${componentName}> bileşeninde geçersiz variant="${variantValue}". Geçerli: [${valid.join(', ')}]`
|
|
109
|
+
));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return violations;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check 2: Geçersiz color prop'ları
|
|
118
|
+
* Tema augmentation'ında kısıtlanmış color değerleri kontrol edilir
|
|
119
|
+
*/
|
|
120
|
+
function checkInvalidColors(filePath, lines) {
|
|
121
|
+
const violations = [];
|
|
122
|
+
const colorRegex = /<(\w+)[^>]*?\bcolor\s*=\s*["'{]([^"'}]+)["'}][^>]*?\/?>/gi;
|
|
123
|
+
|
|
124
|
+
for (let i = 0; i < lines.length; i++) {
|
|
125
|
+
const line = lines[i];
|
|
126
|
+
let match;
|
|
127
|
+
while ((match = colorRegex.exec(line)) !== null) {
|
|
128
|
+
const componentName = match[1];
|
|
129
|
+
const colorValue = match[2];
|
|
130
|
+
const valid = VALID_COLORS[componentName];
|
|
131
|
+
if (valid && !valid.includes(colorValue)) {
|
|
132
|
+
violations.push(violation(
|
|
133
|
+
filePath, i + 1, 'invalid-color',
|
|
134
|
+
`<${componentName}> bileşeninde geçersiz color="${colorValue}". Geçerli: [${valid.join(', ')}]`
|
|
135
|
+
));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return violations;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check 3: Yasaklı typography variant'ları
|
|
144
|
+
* Tema augmentation'ında false olan MUI default variant'lar
|
|
145
|
+
*/
|
|
146
|
+
function checkForbiddenTypographyVariants(filePath, lines) {
|
|
147
|
+
const violations = [];
|
|
148
|
+
const variantRegex = /<Typography[^>]*?\bvariant\s*=\s*["'{]([^"'}]+)["'}]/gi;
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < lines.length; i++) {
|
|
151
|
+
const line = lines[i];
|
|
152
|
+
let match;
|
|
153
|
+
while ((match = variantRegex.exec(line)) !== null) {
|
|
154
|
+
const variantValue = match[1];
|
|
155
|
+
if (FORBIDDEN_TYPOGRAPHY_VARIANTS.includes(variantValue)) {
|
|
156
|
+
violations.push(violation(
|
|
157
|
+
filePath, i + 1, 'forbidden-typography-variant',
|
|
158
|
+
`<Typography variant="${variantValue}"> kullanılmış. MUI default typography variant'ları DEVRE DIŞI. Untitled UI variant'larını (display.*, text.*) kullanın.`
|
|
159
|
+
));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return violations;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check 4: Inline style={{}} kullanımı
|
|
168
|
+
*/
|
|
169
|
+
function checkInlineStyles(filePath, lines) {
|
|
170
|
+
const violations = [];
|
|
171
|
+
const inlineStyleRegex = /\bstyle\s*=\s*\{\{/;
|
|
172
|
+
|
|
173
|
+
for (let i = 0; i < lines.length; i++) {
|
|
174
|
+
if (inlineStyleRegex.test(lines[i])) {
|
|
175
|
+
violations.push(violation(
|
|
176
|
+
filePath, i + 1, 'inline-style',
|
|
177
|
+
'Inline style={{}} kullanımı yasak. Bunun yerine sx prop veya styled() kullanın.'
|
|
178
|
+
));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return violations;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Check 5: Magic number spacing — sx içinde raw px değerleri
|
|
186
|
+
* padding: '8px', margin: '16px' gibi
|
|
187
|
+
*/
|
|
188
|
+
function checkMagicNumberSpacing(filePath, lines) {
|
|
189
|
+
const violations = [];
|
|
190
|
+
|
|
191
|
+
for (let i = 0; i < lines.length; i++) {
|
|
192
|
+
const line = lines[i];
|
|
193
|
+
|
|
194
|
+
// String px değerleri: '8px', "8px"
|
|
195
|
+
const pxStringRegex = /(?:padding|margin|gap|top|bottom|left|right|px|py|pt|pb|pl|pr|mx|my|mt|mb|ml|mr|spacing)\s*:\s*['"](\d+)px['"]/gi;
|
|
196
|
+
let match;
|
|
197
|
+
while ((match = pxStringRegex.exec(line)) !== null) {
|
|
198
|
+
const pxValue = match[1];
|
|
199
|
+
if (!/theme\.spacing/.test(line)) {
|
|
200
|
+
violations.push(violation(
|
|
201
|
+
filePath, i + 1, 'magic-number-spacing',
|
|
202
|
+
`Raw piksel değeri kullanılmış: '${pxValue}px'. theme.spacing(${Math.round(pxValue / 4)}) kullanın (${pxValue}px = theme.spacing(${Math.round(pxValue / 4)})).`
|
|
203
|
+
));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return violations;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Check 6: Raw hex renk değerleri
|
|
212
|
+
* sx={{ color: '#242424' }} gibi. CSS var() / theme.vars kullanmayanlar.
|
|
213
|
+
* TEMA DOSYALARI HARİÇ (src/theme/ altındakiler kontrol edilmez —
|
|
214
|
+
* tema override dosyalarında hex kullanımı normaldir).
|
|
215
|
+
*/
|
|
216
|
+
function checkRawHexColors(filePath, lines) {
|
|
217
|
+
const violations = [];
|
|
218
|
+
|
|
219
|
+
// Tema dosyalarını atla — override tanımlarında hex normaldir
|
|
220
|
+
if (filePath.includes('/theme/') || filePath.includes('\\theme\\')) {
|
|
221
|
+
return violations;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const hexRegex = /['"](#[0-9a-fA-F]{3,8})['"]/g;
|
|
225
|
+
|
|
226
|
+
for (let i = 0; i < lines.length; i++) {
|
|
227
|
+
const line = lines[i];
|
|
228
|
+
let match;
|
|
229
|
+
while ((match = hexRegex.exec(line)) !== null) {
|
|
230
|
+
const hexValue = match[1];
|
|
231
|
+
// theme.vars veya CSS var() kullanıyorsa atla
|
|
232
|
+
if (!/theme\.vars/.test(line) && !/var\(--/.test(line)) {
|
|
233
|
+
violations.push(violation(
|
|
234
|
+
filePath, i + 1, 'raw-hex-color',
|
|
235
|
+
`Raw hex rengi kullanılmış: ${hexValue}. CSS custom property veya theme.vars.palette.* kullanın.`
|
|
236
|
+
));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return violations;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Check 7: Checkbox için size="large" kullanımı
|
|
245
|
+
* Augmentation'da CheckboxPropsSizeOverrides.large = false
|
|
246
|
+
*/
|
|
247
|
+
function checkInvalidSizes(filePath, lines) {
|
|
248
|
+
const violations = [];
|
|
249
|
+
const sizeCheckRegex = /<(Checkbox)[^>]*?\bsize\s*=\s*["'{](large)["'}]/gi;
|
|
250
|
+
|
|
251
|
+
for (let i = 0; i < lines.length; i++) {
|
|
252
|
+
const line = lines[i];
|
|
253
|
+
let match;
|
|
254
|
+
while ((match = sizeCheckRegex.exec(line)) !== null) {
|
|
255
|
+
violations.push(violation(
|
|
256
|
+
filePath, i + 1, 'invalid-size',
|
|
257
|
+
`<${match[1]}> bileşeninde size="large" kullanımı DEVRE DIŞI (augmentation: large=false). Sadece "small" ve "medium" geçerlidir.`
|
|
258
|
+
));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return violations;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ============================================================
|
|
265
|
+
// Ana tarama fonksiyonu
|
|
266
|
+
// ============================================================
|
|
267
|
+
|
|
268
|
+
function scanFile(filePath) {
|
|
269
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
270
|
+
const lines = content.split('\n');
|
|
271
|
+
|
|
272
|
+
return [
|
|
273
|
+
...checkInvalidVariants(filePath, lines),
|
|
274
|
+
...checkInvalidColors(filePath, lines),
|
|
275
|
+
...checkForbiddenTypographyVariants(filePath, lines),
|
|
276
|
+
...checkInlineStyles(filePath, lines),
|
|
277
|
+
...checkMagicNumberSpacing(filePath, lines),
|
|
278
|
+
...checkRawHexColors(filePath, lines),
|
|
279
|
+
...checkInvalidSizes(filePath, lines),
|
|
280
|
+
];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function main() {
|
|
284
|
+
const args = process.argv.slice(2);
|
|
285
|
+
const targetDir = args[0] || 'src/';
|
|
286
|
+
const absTargetDir = path.resolve(process.cwd(), targetDir);
|
|
287
|
+
|
|
288
|
+
console.log(`\n🔍 OneHub Design Verification`);
|
|
289
|
+
console.log(` Tarama dizini: ${absTargetDir}\n`);
|
|
290
|
+
|
|
291
|
+
const startTime = Date.now();
|
|
292
|
+
const files = findTsxFiles(absTargetDir);
|
|
293
|
+
|
|
294
|
+
if (files.length === 0) {
|
|
295
|
+
console.log(` ⚠️ Hiç .tsx/.jsx dosyası bulunamadı: ${absTargetDir}`);
|
|
296
|
+
console.log(` ✅ İhlal yok.\n`);
|
|
297
|
+
process.exit(0);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log(` ${files.length} dosya taranıyor...\n`);
|
|
301
|
+
|
|
302
|
+
let allViolations = [];
|
|
303
|
+
for (const file of files) {
|
|
304
|
+
allViolations = allViolations.concat(scanFile(file));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const ruleCounts = {};
|
|
308
|
+
for (const v of allViolations) {
|
|
309
|
+
ruleCounts[v.rule] = (ruleCounts[v.rule] || 0) + 1;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
313
|
+
|
|
314
|
+
if (allViolations.length === 0) {
|
|
315
|
+
console.log(` ✅ Tasarım ihlali bulunamadı! (${files.length} dosya, ${elapsed}s)\n`);
|
|
316
|
+
process.exit(0);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
console.log(` ❌ ${allViolations.length} ihlal bulundu (${files.length} dosya, ${elapsed}s):\n`);
|
|
320
|
+
console.log(` Kategori Dağılımı:`);
|
|
321
|
+
for (const [rule, count] of Object.entries(ruleCounts).sort()) {
|
|
322
|
+
console.log(` ${rule}: ${count}`);
|
|
323
|
+
}
|
|
324
|
+
console.log();
|
|
325
|
+
|
|
326
|
+
console.log(` İhlal Detayları:`);
|
|
327
|
+
console.log(`${'─'.repeat(80)}`);
|
|
328
|
+
|
|
329
|
+
const byFile = {};
|
|
330
|
+
for (const v of allViolations) {
|
|
331
|
+
if (!byFile[v.file]) byFile[v.file] = [];
|
|
332
|
+
byFile[v.file].push(v);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
for (const [file, fileViolations] of Object.entries(byFile).sort()) {
|
|
336
|
+
const relativeFile = path.relative(process.cwd(), file);
|
|
337
|
+
console.log(`\n 📄 ${relativeFile}`);
|
|
338
|
+
for (const v of fileViolations) {
|
|
339
|
+
console.log(` Satır ${v.line}: [${v.rule}] ${v.message}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
console.log(`\n${'─'.repeat(80)}`);
|
|
344
|
+
console.log(` Toplam: ${allViolations.length} ihlal, ${Object.keys(byFile).length} dosya`);
|
|
345
|
+
console.log(` Tasarım kuralları için: DESIGN_RULES.md`);
|
|
346
|
+
console.log(` Bileşen kullanımı için: COMPONENT_MAP.md, COMPONENT_GLOSSARY.md\n`);
|
|
347
|
+
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
main();
|