Files
igny8/docs/30-FRONTEND/COMPONENT-SYSTEM.md
2026-01-01 21:42:04 +00:00

15 KiB

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)

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:

// 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)

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:

// 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)

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)

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

import Checkbox from '../../components/form/input/Checkbox';

<Checkbox
  label="Accept terms"
  checked={checked}
  onChange={(checked) => setChecked(checked)}
  id="checkbox-id"
  disabled={false}
/>

Radio

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)

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)

import SelectDropdown from '../../components/form/SelectDropdown';

<SelectDropdown
  label="Select Item"
  options={options}
  value={value}
  onChange={(value) => setValue(value)}
  placeholder="Search..."
  searchable={true}
/>

Switch (Toggle)

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

import FileInput from '../../components/form/input/FileInput';

<FileInput
  onChange={(files) => handleFiles(files)}
  accept=".csv,.json"
  multiple={false}
/>

3. DISPLAY COMPONENTS

Badge

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:

<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

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

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

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

import { Spinner } from '../../components/ui/spinner/Spinner';

<Spinner
  size="md"             // sm | md | lg
  color="primary"       // primary | success | error | warning | info
/>

Tooltip

import { Tooltip } from '../../components/ui/tooltip/Tooltip';

<Tooltip text="Tooltip text" placement="top">
  <span>Hover target</span>
</Tooltip>

Toast (Notifications)

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

// 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):

  • TrashIconTrashBinIcon
  • XIcon / XMarkIconCloseIcon
  • SearchIconGridIcon
  • SettingsIconBoxCubeIcon
  • FilterIconListIcon

Adding New Icons

  1. Add SVG file to src/icons/
  2. Export in src/icons/index.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

npm run lint

Viewing Violations

npm run lint 2>&1 | grep "igny8-design-system"

7. MIGRATION EXAMPLES

Raw Button → Button Component

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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.