componenets standardization 1
This commit is contained in:
230
frontend/eslint/eslint-plugin-igny8-design-system.cjs
Normal file
230
frontend/eslint/eslint-plugin-igny8-design-system.cjs
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* IGNY8 Design System ESLint Plugin
|
||||
*
|
||||
* This plugin enforces consistent component usage across the application.
|
||||
* It prevents raw HTML elements and ensures all UI elements come from
|
||||
* the central component library.
|
||||
*
|
||||
* DOCUMENTATION:
|
||||
* - Full component reference: docs/30-FRONTEND/COMPONENT-SYSTEM.md
|
||||
* - Design guide: DESIGN-GUIDE.md (root)
|
||||
* - Live demo: /ui-elements route
|
||||
*
|
||||
* RULES:
|
||||
* 1. no-raw-button - Use <Button> from components/ui/button/Button
|
||||
* 2. no-raw-input - Use <InputField> from components/form/input/InputField
|
||||
* 3. no-raw-select - Use <Select> from components/form/Select
|
||||
* 4. no-raw-textarea - Use <TextArea> from components/form/input/TextArea
|
||||
*
|
||||
* USAGE: Import in eslint.config.js and enable rules
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
name: 'eslint-plugin-igny8-design-system',
|
||||
version: '1.0.0',
|
||||
},
|
||||
rules: {
|
||||
/**
|
||||
* Disallow raw <button> elements
|
||||
* Must use <Button> component from components/ui/button/Button
|
||||
*/
|
||||
'no-raw-button': {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Disallow raw <button> elements. Use <Button> from components/ui/button/Button instead.',
|
||||
recommended: true,
|
||||
},
|
||||
messages: {
|
||||
useButtonComponent: 'Use <Button> from components/ui/button/Button instead of raw <button>. For icon-only buttons, use <IconButton> from components/ui/button/IconButton.',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
JSXOpeningElement(node) {
|
||||
if (node.name.type === 'JSXIdentifier' && node.name.name === 'button') {
|
||||
// Allow in specific files that need raw buttons (like the Button component itself)
|
||||
const filename = context.getFilename();
|
||||
const allowedFiles = [
|
||||
'Button.tsx',
|
||||
'IconButton.tsx',
|
||||
'ButtonGroup.tsx',
|
||||
'Components.tsx', // Demo page
|
||||
'UIElements.tsx', // Demo page
|
||||
// Low-level UI components that need raw buttons
|
||||
'Pagination.tsx',
|
||||
'CompactPagination.tsx',
|
||||
'AlertModal.tsx',
|
||||
'Accordion.tsx',
|
||||
'AccordionItem.tsx',
|
||||
'Tabs.tsx',
|
||||
'Tab.tsx',
|
||||
'DropdownItem.tsx',
|
||||
'SelectDropdown.tsx',
|
||||
'MultiSelect.tsx',
|
||||
'List.tsx',
|
||||
'ListRadioItem.tsx',
|
||||
'Dropdown.tsx',
|
||||
// Form components
|
||||
'PhoneInput.tsx',
|
||||
'SearchModal.tsx',
|
||||
];
|
||||
|
||||
if (allowedFiles.some(f => filename.endsWith(f))) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'useButtonComponent',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Disallow raw <input> elements
|
||||
* Must use <InputField>, <Checkbox>, <Radio>, or <FileInput>
|
||||
*/
|
||||
'no-raw-input': {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Disallow raw <input> elements. Use InputField, Checkbox, Radio, or FileInput from components/form/input.',
|
||||
recommended: true,
|
||||
},
|
||||
messages: {
|
||||
useInputComponent: 'Use InputField, Checkbox, Radio, or FileInput from components/form/input instead of raw <input>.',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
JSXOpeningElement(node) {
|
||||
if (node.name.type === 'JSXIdentifier' && node.name.name === 'input') {
|
||||
const filename = context.getFilename();
|
||||
const allowedFiles = [
|
||||
'InputField.tsx',
|
||||
'Checkbox.tsx',
|
||||
'Radio.tsx',
|
||||
'RadioSm.tsx',
|
||||
'FileInput.tsx',
|
||||
'Switch.tsx',
|
||||
'Components.tsx',
|
||||
'UIElements.tsx',
|
||||
'date-picker.tsx',
|
||||
// Low-level UI components
|
||||
'Pagination.tsx',
|
||||
'CompactPagination.tsx',
|
||||
'ListRadioItem.tsx',
|
||||
'SearchModal.tsx',
|
||||
'PhoneInput.tsx',
|
||||
'SelectDropdown.tsx',
|
||||
'MultiSelect.tsx',
|
||||
'ConfigModal.tsx', // Complex automation config
|
||||
];
|
||||
|
||||
if (allowedFiles.some(f => filename.endsWith(f))) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'useInputComponent',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Disallow raw <select> elements
|
||||
* Must use <Select> or <SelectDropdown> from components/form/
|
||||
*/
|
||||
'no-raw-select': {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Disallow raw <select> elements. Use Select or SelectDropdown from components/form.',
|
||||
recommended: true,
|
||||
},
|
||||
messages: {
|
||||
useSelectComponent: 'Use Select or SelectDropdown from components/form instead of raw <select>.',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
JSXOpeningElement(node) {
|
||||
if (node.name.type === 'JSXIdentifier' && node.name.name === 'select') {
|
||||
const filename = context.getFilename();
|
||||
const allowedFiles = [
|
||||
'Select.tsx',
|
||||
'SelectDropdown.tsx',
|
||||
'MultiSelect.tsx',
|
||||
'Components.tsx',
|
||||
'UIElements.tsx',
|
||||
'CompactPagination.tsx', // Has custom styling needs
|
||||
];
|
||||
|
||||
if (allowedFiles.some(f => filename.endsWith(f))) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'useSelectComponent',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Disallow raw <textarea> elements
|
||||
* Must use <TextArea> from components/form/input/TextArea
|
||||
*/
|
||||
'no-raw-textarea': {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Disallow raw <textarea> elements. Use TextArea from components/form/input/TextArea.',
|
||||
recommended: true,
|
||||
},
|
||||
messages: {
|
||||
useTextAreaComponent: 'Use TextArea from components/form/input/TextArea instead of raw <textarea>.',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
JSXOpeningElement(node) {
|
||||
if (node.name.type === 'JSXIdentifier' && node.name.name === 'textarea') {
|
||||
const filename = context.getFilename();
|
||||
const allowedFiles = [
|
||||
'TextArea.tsx',
|
||||
'Components.tsx',
|
||||
'UIElements.tsx',
|
||||
];
|
||||
|
||||
if (allowedFiles.some(f => filename.endsWith(f))) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'useTextAreaComponent',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user