styling fixes
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
* 3. no-raw-select - Use <Select> from components/form/Select
|
||||
* 4. no-raw-textarea - Use <TextArea> from components/form/input/TextArea
|
||||
* 5. no-icon-children - Use startIcon/endIcon props instead of icon children in Button
|
||||
* 6. no-tailwind-default-colors - Disallow Tailwind default color utilities
|
||||
*
|
||||
* USAGE: Import in eslint.config.js and enable rules
|
||||
*/
|
||||
@@ -367,5 +368,101 @@ module.exports = {
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Disallow Tailwind default color utilities (blue-*, red-*, green-*, etc.)
|
||||
* Only allow brand-*, gray-*, success-*, warning-*, error-*, purple-*
|
||||
*/
|
||||
'no-tailwind-default-colors': {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'Disallow Tailwind default color utilities. Use brand/gray/success/warning/error/purple palettes only.',
|
||||
recommended: true,
|
||||
},
|
||||
messages: {
|
||||
noDefaultColors: 'Disallowed Tailwind color utility "{{className}}". Use brand/gray/success/warning/error/purple instead.',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
const allowedPalettes = new Set(['brand', 'gray', 'success', 'warning', 'error', 'purple']);
|
||||
const disallowedPalettes = new Set([
|
||||
'blue', 'red', 'green', 'emerald', 'amber', 'indigo', 'violet', 'info',
|
||||
'fuchsia', 'pink', 'rose', 'sky', 'teal', 'cyan', 'lime', 'yellow',
|
||||
'orange', 'slate', 'zinc', 'neutral', 'stone'
|
||||
]);
|
||||
|
||||
const allowedSpecial = new Set([
|
||||
'text-white', 'bg-white', 'border-white', 'fill-white', 'stroke-white',
|
||||
'text-black', 'bg-black', 'border-black', 'fill-black', 'stroke-black',
|
||||
'text-transparent', 'bg-transparent', 'border-transparent',
|
||||
]);
|
||||
|
||||
const classRegex = /^(?:bg|text|border|ring|stroke|fill|from|to|via|outline|decoration|placeholder|caret|divide)-([a-z]+)-(?:\d{2,3})$/;
|
||||
|
||||
function extractClassStrings(attrValue) {
|
||||
const results = [];
|
||||
if (!attrValue) return results;
|
||||
|
||||
if (attrValue.type === 'Literal' && typeof attrValue.value === 'string') {
|
||||
results.push(attrValue.value);
|
||||
}
|
||||
|
||||
if (attrValue.type === 'JSXExpressionContainer') {
|
||||
const expr = attrValue.expression;
|
||||
if (expr.type === 'Literal' && typeof expr.value === 'string') {
|
||||
results.push(expr.value);
|
||||
}
|
||||
if (expr.type === 'TemplateLiteral') {
|
||||
for (const quasi of expr.quasis) {
|
||||
if (typeof quasi.value.cooked === 'string') {
|
||||
results.push(quasi.value.cooked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function normalizeToken(token) {
|
||||
const base = token.split(':').pop() || token;
|
||||
return base.replace(/\/[0-9]+$/, '');
|
||||
}
|
||||
|
||||
return {
|
||||
JSXAttribute(node) {
|
||||
if (node.name.type !== 'JSXIdentifier' || node.name.name !== 'className') {
|
||||
return;
|
||||
}
|
||||
|
||||
const classStrings = extractClassStrings(node.value);
|
||||
if (!classStrings.length) return;
|
||||
|
||||
for (const classString of classStrings) {
|
||||
const tokens = classString.split(/\s+/).filter(Boolean);
|
||||
for (const token of tokens) {
|
||||
const normalized = normalizeToken(token);
|
||||
if (allowedSpecial.has(normalized)) continue;
|
||||
|
||||
const match = normalized.match(classRegex);
|
||||
if (!match) continue;
|
||||
|
||||
const palette = match[1];
|
||||
if (allowedPalettes.has(palette)) continue;
|
||||
if (disallowedPalettes.has(palette)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noDefaultColors',
|
||||
data: { className: token },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user