684 lines
17 KiB
Markdown
684 lines
17 KiB
Markdown
# IGNY8 Frontend Component System
|
|
|
|
**Last Updated:** January 20, 2026
|
|
**Version:** 1.8.4
|
|
|
|
> **🔒 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` |
|
|
| Button Group | `ButtonGroup` | `components/ui/button-group/ButtonGroup` |
|
|
| 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` |
|
|
| Calendar Tooltip | `CalendarItemTooltip` | `components/ui/tooltip/CalendarItemTooltip` |
|
|
| 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. SITE-SPECIFIC COMPONENTS (v1.3.2)
|
|
|
|
### SiteInfoBar
|
|
|
|
Reusable site info header component for site-specific pages.
|
|
|
|
```tsx
|
|
import { SiteInfoBar } from '../../components/common/SiteInfoBar';
|
|
|
|
<SiteInfoBar
|
|
site={currentSite} // Site object with name, domain, etc.
|
|
onSiteChange={handleSiteChange} // Optional: Callback when site changes
|
|
showSelector={true} // Whether to show site selector dropdown
|
|
/>
|
|
```
|
|
|
|
### OnboardingWizard Components
|
|
|
|
Located in `components/onboarding/`:
|
|
|
|
```tsx
|
|
import { OnboardingWizard } from '../../components/onboarding/OnboardingWizard';
|
|
import {
|
|
Step1Welcome,
|
|
Step2AddSite,
|
|
Step3ConnectIntegration,
|
|
Step4AddKeywords,
|
|
Step5Complete,
|
|
} from '../../components/onboarding/steps';
|
|
```
|
|
|
|
### CalendarItemTooltip
|
|
|
|
Tooltip specifically designed for calendar items.
|
|
|
|
```tsx
|
|
import { CalendarItemTooltip } from '../../components/ui/tooltip';
|
|
|
|
<CalendarItemTooltip
|
|
item={contentItem} // Content object
|
|
onView={handleView} // Optional: View callback
|
|
onRemove={handleRemove} // Optional: Remove callback
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## 9. LIVE REFERENCE
|
|
|
|
View all components with live examples at: `/ui-elements`
|
|
|
|
This page shows every component with all prop variations.
|
|
|
|
---
|
|
|
|
## 10. 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. **Use semantic color tokens** - never hardcoded hex values or Tailwind defaults
|
|
|
|
### Color Rules
|
|
```tsx
|
|
// ✅ CORRECT - Use semantic tokens
|
|
className="bg-brand-500 text-gray-900 border-success-500"
|
|
|
|
// ❌ WRONG - Tailwind defaults (DISABLED)
|
|
className="bg-blue-500 text-slate-900 border-green-500"
|
|
|
|
// ❌ WRONG - Hardcoded hex
|
|
className="bg-[#0077B6]"
|
|
style={{ color: '#DC2626' }}
|
|
```
|
|
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.
|