componenets standardization 1
This commit is contained in:
617
docs/30-FRONTEND/COMPONENT-SYSTEM.md
Normal file
617
docs/30-FRONTEND/COMPONENT-SYSTEM.md
Normal file
@@ -0,0 +1,617 @@
|
||||
# IGNY8 Frontend Component System
|
||||
|
||||
> **🔒 ENFORCED BY ESLINT** - Violations will trigger warnings/errors during build.
|
||||
> This document is the single source of truth for all UI components.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
| Element | Component | Import Path |
|
||||
|---------|-----------|-------------|
|
||||
| Button | `Button` | `components/ui/button/Button` |
|
||||
| Icon Button | `IconButton` | `components/ui/button/IconButton` |
|
||||
| Text Input | `InputField` | `components/form/input/InputField` |
|
||||
| Checkbox | `Checkbox` | `components/form/input/Checkbox` |
|
||||
| Radio | `Radio` | `components/form/input/Radio` |
|
||||
| File Upload | `FileInput` | `components/form/input/FileInput` |
|
||||
| Textarea | `TextArea` | `components/form/input/TextArea` |
|
||||
| Dropdown | `Select` | `components/form/Select` |
|
||||
| Searchable Dropdown | `SelectDropdown` | `components/form/SelectDropdown` |
|
||||
| Multi-Select | `MultiSelect` | `components/form/MultiSelect` |
|
||||
| Toggle Switch | `Switch` | `components/form/switch/Switch` |
|
||||
| Badge | `Badge` | `components/ui/badge/Badge` |
|
||||
| Card | `Card` | `components/ui/card/Card` |
|
||||
| Modal | `Modal` | `components/ui/modal` |
|
||||
| Alert | `Alert` | `components/ui/alert/Alert` |
|
||||
| Spinner | `Spinner` | `components/ui/spinner/Spinner` |
|
||||
| Tabs | `Tabs` | `components/ui/tabs/Tabs` |
|
||||
| Tooltip | `Tooltip` | `components/ui/tooltip/Tooltip` |
|
||||
| Toast | `useToast` | `components/ui/toast/ToastContainer` |
|
||||
| Icons | `*Icon` | `icons` (e.g., `../../icons`) |
|
||||
|
||||
---
|
||||
|
||||
## 1. BUTTONS
|
||||
|
||||
### Button (Standard)
|
||||
|
||||
```tsx
|
||||
import Button from '../../components/ui/button/Button';
|
||||
|
||||
<Button
|
||||
variant="primary" // primary | secondary | outline | ghost | gradient
|
||||
tone="brand" // brand | success | warning | danger | neutral
|
||||
size="md" // xs | sm | md | lg | xl | 2xl
|
||||
shape="rounded" // rounded | pill
|
||||
startIcon={<Icon />} // Icon before text
|
||||
endIcon={<Icon />} // Icon after text
|
||||
onClick={handler}
|
||||
disabled={false}
|
||||
fullWidth={false}
|
||||
type="button" // button | submit | reset
|
||||
className="" // Additional classes
|
||||
>
|
||||
Button Text
|
||||
</Button>
|
||||
```
|
||||
|
||||
**Common Patterns:**
|
||||
```tsx
|
||||
// Primary action
|
||||
<Button variant="primary" tone="brand">Save</Button>
|
||||
|
||||
// Success/confirm
|
||||
<Button variant="primary" tone="success">Approve</Button>
|
||||
|
||||
// Danger/destructive
|
||||
<Button variant="primary" tone="danger">Delete</Button>
|
||||
|
||||
// Secondary/cancel
|
||||
<Button variant="outline" tone="neutral">Cancel</Button>
|
||||
|
||||
// With icon
|
||||
<Button startIcon={<PlusIcon className="w-4 h-4" />}>Add Item</Button>
|
||||
```
|
||||
|
||||
### IconButton (Icon-only)
|
||||
|
||||
```tsx
|
||||
import IconButton from '../../components/ui/button/IconButton';
|
||||
|
||||
<IconButton
|
||||
icon={<CloseIcon />} // Required: Icon component
|
||||
variant="ghost" // solid | outline | ghost
|
||||
tone="neutral" // brand | success | warning | danger | neutral
|
||||
size="sm" // xs | sm | md | lg
|
||||
shape="rounded" // rounded | circle
|
||||
onClick={handler}
|
||||
disabled={false}
|
||||
title="Close" // Required: Accessibility label
|
||||
aria-label="Close" // Optional: Override aria-label
|
||||
/>
|
||||
```
|
||||
|
||||
**Common Patterns:**
|
||||
```tsx
|
||||
// Close button
|
||||
<IconButton icon={<CloseIcon />} variant="ghost" tone="neutral" title="Close" />
|
||||
|
||||
// Delete button
|
||||
<IconButton icon={<TrashBinIcon />} variant="ghost" tone="danger" title="Delete" />
|
||||
|
||||
// Edit button
|
||||
<IconButton icon={<PencilIcon />} variant="ghost" tone="brand" title="Edit" />
|
||||
|
||||
// Add button (circular)
|
||||
<IconButton icon={<PlusIcon />} variant="solid" tone="brand" shape="circle" title="Add" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. FORM INPUTS
|
||||
|
||||
### InputField (Text/Number/Email/Password)
|
||||
|
||||
```tsx
|
||||
import InputField from '../../components/form/input/InputField';
|
||||
|
||||
<InputField
|
||||
type="text" // text | number | email | password | date | time
|
||||
label="Field Label"
|
||||
placeholder="Enter value..."
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
id="field-id"
|
||||
name="field-name"
|
||||
disabled={false}
|
||||
error={false} // Shows error styling
|
||||
success={false} // Shows success styling
|
||||
hint="Helper text" // Text below input
|
||||
min="0" // For number inputs
|
||||
max="100" // For number inputs
|
||||
step={1} // For number inputs
|
||||
/>
|
||||
```
|
||||
|
||||
### TextArea (Multi-line)
|
||||
|
||||
```tsx
|
||||
import TextArea from '../../components/form/input/TextArea';
|
||||
|
||||
<TextArea
|
||||
placeholder="Enter description..."
|
||||
rows={4}
|
||||
value={value}
|
||||
onChange={(value) => setValue(value)}
|
||||
disabled={false}
|
||||
error={false}
|
||||
hint="Helper text"
|
||||
/>
|
||||
```
|
||||
|
||||
### Checkbox
|
||||
|
||||
```tsx
|
||||
import Checkbox from '../../components/form/input/Checkbox';
|
||||
|
||||
<Checkbox
|
||||
label="Accept terms"
|
||||
checked={checked}
|
||||
onChange={(checked) => setChecked(checked)}
|
||||
id="checkbox-id"
|
||||
disabled={false}
|
||||
/>
|
||||
```
|
||||
|
||||
### Radio
|
||||
|
||||
```tsx
|
||||
import Radio from '../../components/form/input/Radio';
|
||||
|
||||
<Radio
|
||||
id="radio-option-1"
|
||||
name="radio-group"
|
||||
value="option1"
|
||||
label="Option 1"
|
||||
checked={selected === 'option1'}
|
||||
onChange={(value) => setSelected(value)}
|
||||
/>
|
||||
```
|
||||
|
||||
### Select (Dropdown)
|
||||
|
||||
```tsx
|
||||
import Select from '../../components/form/Select';
|
||||
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'opt1', label: 'Option 1' },
|
||||
{ value: 'opt2', label: 'Option 2' },
|
||||
]}
|
||||
placeholder="Select..."
|
||||
defaultValue=""
|
||||
onChange={(value) => setValue(value)}
|
||||
className=""
|
||||
/>
|
||||
```
|
||||
|
||||
### SelectDropdown (Searchable)
|
||||
|
||||
```tsx
|
||||
import SelectDropdown from '../../components/form/SelectDropdown';
|
||||
|
||||
<SelectDropdown
|
||||
label="Select Item"
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={(value) => setValue(value)}
|
||||
placeholder="Search..."
|
||||
searchable={true}
|
||||
/>
|
||||
```
|
||||
|
||||
### Switch (Toggle)
|
||||
|
||||
```tsx
|
||||
import Switch from '../../components/form/switch/Switch';
|
||||
|
||||
<Switch
|
||||
label="Enable feature"
|
||||
checked={enabled} // Controlled mode
|
||||
onChange={(checked) => setEnabled(checked)}
|
||||
disabled={false}
|
||||
color="blue" // blue | gray
|
||||
/>
|
||||
```
|
||||
|
||||
### FileInput
|
||||
|
||||
```tsx
|
||||
import FileInput from '../../components/form/input/FileInput';
|
||||
|
||||
<FileInput
|
||||
onChange={(files) => handleFiles(files)}
|
||||
accept=".csv,.json"
|
||||
multiple={false}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. DISPLAY COMPONENTS
|
||||
|
||||
### Badge
|
||||
|
||||
```tsx
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
|
||||
<Badge
|
||||
tone="success" // brand | success | warning | danger | info | neutral | purple | indigo | pink | teal | cyan | blue
|
||||
variant="soft" // solid | soft | outline | light
|
||||
size="sm" // xs | sm | md
|
||||
startIcon={<Icon />}
|
||||
endIcon={<Icon />}
|
||||
>
|
||||
Label
|
||||
</Badge>
|
||||
```
|
||||
|
||||
**Status Badge Patterns:**
|
||||
```tsx
|
||||
<Badge tone="success" variant="soft">Active</Badge>
|
||||
<Badge tone="warning" variant="soft">Pending</Badge>
|
||||
<Badge tone="danger" variant="soft">Failed</Badge>
|
||||
<Badge tone="info" variant="soft">Draft</Badge>
|
||||
<Badge tone="neutral" variant="soft">Archived</Badge>
|
||||
```
|
||||
|
||||
### Card
|
||||
|
||||
```tsx
|
||||
import { Card, CardTitle, CardContent, CardDescription, CardAction, CardIcon } from '../../components/ui/card/Card';
|
||||
|
||||
<Card
|
||||
variant="surface" // surface | panel | frosted | borderless | gradient
|
||||
padding="md" // none | sm | md | lg
|
||||
shadow="sm" // none | sm | md
|
||||
>
|
||||
<CardIcon><Icon /></CardIcon>
|
||||
<CardTitle>Title</CardTitle>
|
||||
<CardDescription>Description text</CardDescription>
|
||||
<CardContent>Main content</CardContent>
|
||||
<CardAction onClick={handler}>Action</CardAction>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### Alert
|
||||
|
||||
```tsx
|
||||
import Alert from '../../components/ui/alert/Alert';
|
||||
|
||||
<Alert
|
||||
variant="success" // success | error | warning | info
|
||||
title="Alert Title"
|
||||
message="Alert message text"
|
||||
showLink={false}
|
||||
linkHref="#"
|
||||
linkText="Learn more"
|
||||
/>
|
||||
```
|
||||
|
||||
### Modal
|
||||
|
||||
```tsx
|
||||
import { Modal } from '../../components/ui/modal';
|
||||
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
showCloseButton={true}
|
||||
isFullscreen={false}
|
||||
>
|
||||
<div className="p-6">
|
||||
Modal content
|
||||
</div>
|
||||
</Modal>
|
||||
```
|
||||
|
||||
### Spinner
|
||||
|
||||
```tsx
|
||||
import { Spinner } from '../../components/ui/spinner/Spinner';
|
||||
|
||||
<Spinner
|
||||
size="md" // sm | md | lg
|
||||
color="primary" // primary | success | error | warning | info
|
||||
/>
|
||||
```
|
||||
|
||||
### Tooltip
|
||||
|
||||
```tsx
|
||||
import { Tooltip } from '../../components/ui/tooltip/Tooltip';
|
||||
|
||||
<Tooltip text="Tooltip text" placement="top">
|
||||
<span>Hover target</span>
|
||||
</Tooltip>
|
||||
```
|
||||
|
||||
### Toast (Notifications)
|
||||
|
||||
```tsx
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
// Show notifications
|
||||
toast.success('Success', 'Operation completed');
|
||||
toast.error('Error', 'Something went wrong');
|
||||
toast.warning('Warning', 'Please review');
|
||||
toast.info('Info', 'Here is some information');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. ICONS
|
||||
|
||||
### Importing Icons
|
||||
|
||||
```tsx
|
||||
// Always import from central icons folder
|
||||
import { PlusIcon, CloseIcon, CheckCircleIcon } from '../../icons';
|
||||
|
||||
// Use consistent sizing
|
||||
<PlusIcon className="w-4 h-4" /> // Small (in buttons, badges)
|
||||
<PlusIcon className="w-5 h-5" /> // Medium (standalone)
|
||||
<PlusIcon className="w-6 h-6" /> // Large (headers, features)
|
||||
```
|
||||
|
||||
### Available Icons
|
||||
|
||||
**Core Icons:**
|
||||
- `PlusIcon`, `CloseIcon`, `CheckCircleIcon`, `AlertIcon`, `InfoIcon`, `ErrorIcon`
|
||||
- `BoltIcon`, `ArrowUpIcon`, `ArrowDownIcon`, `ArrowRightIcon`, `ArrowLeftIcon`
|
||||
- `PencilIcon`, `TrashBinIcon`, `DownloadIcon`, `CopyIcon`
|
||||
- `EyeIcon`, `EyeCloseIcon`, `LockIcon`, `UserIcon`
|
||||
- `FolderIcon`, `FileIcon`, `GridIcon`, `ListIcon`
|
||||
- `ChevronDownIcon`, `ChevronUpIcon`, `ChevronLeftIcon`, `ChevronRightIcon`
|
||||
- `AngleDownIcon`, `AngleUpIcon`, `AngleLeftIcon`, `AngleRightIcon`
|
||||
|
||||
**Module Icons:**
|
||||
- `TaskIcon`, `PageIcon`, `TableIcon`, `CalendarIcon`
|
||||
- `PlugInIcon`, `DocsIcon`, `MailIcon`, `ChatIcon`
|
||||
- `PieChartIcon`, `BoxCubeIcon`, `GroupIcon`
|
||||
- `ShootingStarIcon`, `DollarLineIcon`
|
||||
|
||||
**Aliases (for compatibility):**
|
||||
- `TrashIcon` → `TrashBinIcon`
|
||||
- `XIcon` / `XMarkIcon` → `CloseIcon`
|
||||
- `SearchIcon` → `GridIcon`
|
||||
- `SettingsIcon` → `BoxCubeIcon`
|
||||
- `FilterIcon` → `ListIcon`
|
||||
|
||||
### Adding New Icons
|
||||
|
||||
1. Add SVG file to `src/icons/`
|
||||
2. Export in `src/icons/index.ts`:
|
||||
```ts
|
||||
import { ReactComponent as NewIcon } from "./new-icon.svg?react";
|
||||
export { NewIcon };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. FOLDER STRUCTURE
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ ├── ui/ # UI Components (display/interaction)
|
||||
│ │ ├── accordion/
|
||||
│ │ ├── alert/
|
||||
│ │ │ ├── Alert.tsx
|
||||
│ │ │ └── AlertModal.tsx
|
||||
│ │ ├── avatar/
|
||||
│ │ ├── badge/
|
||||
│ │ │ └── Badge.tsx
|
||||
│ │ ├── breadcrumb/
|
||||
│ │ ├── button/
|
||||
│ │ │ ├── Button.tsx # ← Standard button
|
||||
│ │ │ ├── IconButton.tsx # ← Icon-only button
|
||||
│ │ │ └── ButtonWithTooltip.tsx
|
||||
│ │ ├── button-group/
|
||||
│ │ ├── card/
|
||||
│ │ ├── dataview/
|
||||
│ │ ├── dropdown/
|
||||
│ │ ├── list/
|
||||
│ │ ├── modal/
|
||||
│ │ ├── pagination/
|
||||
│ │ ├── progress/
|
||||
│ │ ├── ribbon/
|
||||
│ │ ├── spinner/
|
||||
│ │ ├── table/
|
||||
│ │ ├── tabs/
|
||||
│ │ ├── toast/
|
||||
│ │ ├── tooltip/
|
||||
│ │ └── videos/
|
||||
│ │
|
||||
│ └── form/ # Form Components (inputs)
|
||||
│ ├── input/
|
||||
│ │ ├── InputField.tsx # ← Text/number/email inputs
|
||||
│ │ ├── Checkbox.tsx # ← Checkbox
|
||||
│ │ ├── Radio.tsx # ← Radio button
|
||||
│ │ ├── RadioSm.tsx # ← Small radio button
|
||||
│ │ ├── FileInput.tsx # ← File upload
|
||||
│ │ └── TextArea.tsx # ← Multi-line text
|
||||
│ ├── switch/
|
||||
│ │ └── Switch.tsx # ← Toggle switch
|
||||
│ ├── Select.tsx # ← Dropdown select
|
||||
│ ├── SelectDropdown.tsx # ← Searchable dropdown
|
||||
│ ├── MultiSelect.tsx # ← Multi-select dropdown
|
||||
│ ├── Label.tsx # ← Form labels
|
||||
│ ├── Form.tsx # ← Form wrapper
|
||||
│ └── date-picker.tsx # ← Date picker
|
||||
│
|
||||
├── icons/ # All SVG icons
|
||||
│ ├── index.ts # ← Export all icons from here
|
||||
│ ├── plus.svg
|
||||
│ ├── close.svg
|
||||
│ └── ... (50+ icons)
|
||||
│
|
||||
└── styles/
|
||||
└── design-system.css # ← Global design tokens
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. ESLINT ENFORCEMENT
|
||||
|
||||
### Rules File Location
|
||||
`eslint-plugin-igny8-design-system.cjs` (project root)
|
||||
|
||||
### Active Rules
|
||||
|
||||
| Rule | Severity | Description |
|
||||
|------|----------|-------------|
|
||||
| `no-raw-button` | warn | Use `Button` or `IconButton` instead of `<button>` |
|
||||
| `no-raw-input` | warn | Use `InputField`, `Checkbox`, `Radio`, `FileInput` instead of `<input>` |
|
||||
| `no-raw-select` | warn | Use `Select` or `SelectDropdown` instead of `<select>` |
|
||||
| `no-raw-textarea` | warn | Use `TextArea` instead of `<textarea>` |
|
||||
| `no-restricted-imports` | error | Block imports from `@heroicons/*`, `lucide-react`, `@mui/icons-material` |
|
||||
|
||||
### Running Lint Check
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Viewing Violations
|
||||
```bash
|
||||
npm run lint 2>&1 | grep "igny8-design-system"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. MIGRATION EXAMPLES
|
||||
|
||||
### Raw Button → Button Component
|
||||
|
||||
```tsx
|
||||
// ❌ BEFORE
|
||||
<button
|
||||
className="px-4 py-2 bg-brand-500 text-white rounded hover:bg-brand-600"
|
||||
onClick={handleClick}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
||||
// ✅ AFTER
|
||||
<Button variant="primary" tone="brand" onClick={handleClick}>
|
||||
Save
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Raw Button → IconButton
|
||||
|
||||
```tsx
|
||||
// ❌ BEFORE
|
||||
<button
|
||||
className="p-1 hover:bg-gray-100 rounded"
|
||||
onClick={onClose}
|
||||
>
|
||||
<CloseIcon className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
// ✅ AFTER
|
||||
<IconButton
|
||||
icon={<CloseIcon />}
|
||||
variant="ghost"
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
title="Close"
|
||||
/>
|
||||
```
|
||||
|
||||
### Raw Input → InputField
|
||||
|
||||
```tsx
|
||||
// ❌ BEFORE
|
||||
<input
|
||||
type="text"
|
||||
className="border rounded px-3 py-2 focus:ring-2"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Enter name"
|
||||
/>
|
||||
|
||||
// ✅ AFTER
|
||||
<InputField
|
||||
type="text"
|
||||
label="Name"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Enter name"
|
||||
/>
|
||||
```
|
||||
|
||||
### Raw Select → Select Component
|
||||
|
||||
```tsx
|
||||
// ❌ BEFORE
|
||||
<select
|
||||
className="border rounded px-3 py-2"
|
||||
value={status}
|
||||
onChange={(e) => setStatus(e.target.value)}
|
||||
>
|
||||
<option value="">Select status</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
|
||||
// ✅ AFTER
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'active', label: 'Active' },
|
||||
{ value: 'inactive', label: 'Inactive' },
|
||||
]}
|
||||
placeholder="Select status"
|
||||
defaultValue={status}
|
||||
onChange={(value) => setStatus(value)}
|
||||
/>
|
||||
```
|
||||
|
||||
### External Icon → Internal Icon
|
||||
|
||||
```tsx
|
||||
// ❌ BEFORE
|
||||
import { XIcon } from '@heroicons/react/24/outline';
|
||||
<XIcon className="w-5 h-5" />
|
||||
|
||||
// ✅ AFTER
|
||||
import { CloseIcon } from '../../icons';
|
||||
<CloseIcon className="w-5 h-5" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. LIVE REFERENCE
|
||||
|
||||
View all components with live examples at: `/ui-elements`
|
||||
|
||||
This page shows every component with all prop variations.
|
||||
|
||||
---
|
||||
|
||||
## 9. AI AGENT INSTRUCTIONS
|
||||
|
||||
When working on this codebase, AI agents MUST:
|
||||
|
||||
1. **Never use raw HTML elements** (`<button>`, `<input>`, `<select>`, `<textarea>`)
|
||||
2. **Import icons only from `src/icons`** - never from external libraries
|
||||
3. **Follow the import paths** specified in this document
|
||||
4. **Check ESLint** after making changes: `npm run lint`
|
||||
5. **Reference this document** for correct component usage
|
||||
6. **Use consistent icon sizing**: `className="w-4 h-4"` for small, `w-5 h-5` for medium
|
||||
|
||||
If a component doesn't exist, create it in `components/ui/` or `components/form/` first.
|
||||
Reference in New Issue
Block a user