# Base Design System

> An open-source React component library for enterprise software tools. Built with CSS custom property design tokens, dark/light theming, TypeScript, and FontAwesome icons.

## Package

- npm: `@josephavelez77/base-design-system`
- GitHub: `github:josephavelez77/basedesignsystem`
- Version: 0.2.1

## Critical rules — read before writing any code

1. **Always import the styles at your app entry point** — without this, nothing will be styled:
   ```tsx
   import '@josephavelez77/base-design-system/styles'
   ```

2. **Load the Google Fonts yourself** — the package does NOT bundle fonts. Add this to your HTML `<head>` or the fonts will fall back to the browser default serif:
   ```html
   <link rel="preconnect" href="https://fonts.googleapis.com" />
   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
   <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&family=DM+Serif+Display:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
   ```
   Font tokens: `--text-family-static-body` = DM Sans, `--text-family-static-headline` = DM Serif Display, `--text-family-static-mono` = JetBrains Mono.

2. **Never use raw HTML elements** — always use the DS component equivalents:
   | Instead of | Use |
   |---|---|
   | `<button>` | `<Button>` |
   | `<input type="text">` | `<TextField>` |
   | `<input type="checkbox">` | `<Checkbox>` |
   | `<input type="radio">` | `<RadioButton>` / `<RadioButtonGroup>` |
   | `<select>` | `<SelectField>` |
   | `<textarea>` | `<TextArea>` |
   | `<table>` | `<Table>` or `<DataGrid>` |
   | `<input type="password">` | `<PasswordField>` |
   | `<input type="number">` | `<NumberField>` |
   | `<hr>` | `<Divider>` |
   | `<dialog>` / modal | `<Dialog>` |

3. **Never hardcode colors, spacing, or font sizes** — always use CSS token variables:
   ```css
   /* ✅ correct */
   color: var(--text-color-themeable-primary);
   padding: var(--container-padding-static-primary);

   /* ❌ wrong */
   color: #fff;
   padding: 16px;
   ```

4. **All imports come from the root barrel**:
   ```tsx
   import { Button, TextField, Table } from '@josephavelez77/base-design-system'
   ```

---

## Install

```bash
npm install @josephavelez77/base-design-system
# peer deps
npm install react react-dom \
  @fortawesome/fontawesome-svg-core \
  @fortawesome/free-solid-svg-icons \
  @fortawesome/free-regular-svg-icons \
  @fortawesome/react-fontawesome
```

## App entry point setup

```tsx
// main.tsx or index.tsx — do this ONCE at the root
// BOTH imports are required — omitting /tokens leaves all var(--...) unresolved
import '@josephavelez77/base-design-system/tokens'  // CSS custom properties (fonts NOT included — load separately)
import '@josephavelez77/base-design-system/styles'  // component styles
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)
```

Default theme is dark. For light mode, add `data-theme="light"` to `<html>`:
```html
<html data-theme="light">
```

---

## Full page composition example

This is how a real page should be built using DS components. Use this as a template:

```tsx
import '@josephavelez77/base-design-system/styles'
import {
  PageHeader,
  Card,
  TextField,
  SelectField,
  Button,
  Table,
  StatusChip,
  Alert,
  Spinner,
} from '@josephavelez77/base-design-system'
import { faPlus, faDownload } from '@fortawesome/free-solid-svg-icons'
import styles from './MyPage.module.css'

export function MyPage() {
  return (
    <div className={styles.page}>
      <PageHeader
        title="Users"
        subtitle="Manage your organization's users"
        actions={[
          { label: 'Export', onClick: handleExport, leadingIcon: faDownload, variant: 'neutral' },
          { label: 'Add User', onClick: handleAdd, leadingIcon: faPlus },
        ]}
      />

      <Alert severity="info">You have 3 pending invitations.</Alert>

      <Card>
        <div className={styles.filters}>
          <TextField label="Search" placeholder="Search users..." />
          <SelectField
            label="Status"
            options={[
              { value: 'all', label: 'All' },
              { value: 'active', label: 'Active' },
              { value: 'inactive', label: 'Inactive' },
            ]}
          />
          <Button variant="neutral" emphasis="secondary">Clear</Button>
        </div>

        <Table
          columns={[
            { key: 'name', label: 'Name' },
            { key: 'email', label: 'Email' },
            { key: 'status', label: 'Status' },
          ]}
          data={users}
        />
      </Card>
    </div>
  )
}
```

```css
/* MyPage.module.css — use tokens for all values */
.page {
  display: flex;
  flex-direction: column;
  gap: var(--container-gap-static-large);
  padding: var(--container-padding-static-xl);
  background: var(--container-color-themeable-secondary);
  min-height: 100vh;
}

.filters {
  display: flex;
  gap: var(--container-gap-static-primary);
  align-items: flex-end;
  margin-bottom: var(--container-gap-static-primary);
}
```

---

## Common layout patterns

### Sidebar + content layout
```tsx
<div style={{ display: 'flex', height: '100vh' }}>
  <NavDrawer items={navItems} />
  <main style={{
    flex: 1,
    padding: 'var(--container-padding-static-xl)',
    background: 'var(--container-color-themeable-secondary)',
    overflow: 'auto'
  }}>
    {/* page content */}
  </main>
</div>
```

### Form layout
```tsx
<Card>
  <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--container-gap-static-primary)' }}>
    <TextField label="Full name" />
    <TextField label="Email" type="email" />
    <SelectField label="Role" options={roleOptions} />
    <CheckboxGroup items={permissionItems} orientation="vertical" />
    <div style={{ display: 'flex', gap: 'var(--container-gap-static-xs)', justifyContent: 'flex-end' }}>
      <Button variant="neutral" emphasis="secondary">Cancel</Button>
      <Button variant="brandPrimary">Save</Button>
    </div>
  </div>
</Card>
```

### Status display
```tsx
// Use StatusChip for row/item status — never colored text or badges
<StatusChip label="Active" statusType="positive" level={1} />
<StatusChip label="Failed" statusType="negative" level={1} />
<StatusChip label="Pending" statusType="neutral" level={2} />

// Use PriorityChip for priority fields
<PriorityChip priority="high" label="High" />
```

### Loading / empty states
```tsx
// Loading
<div style={{ display: 'flex', justifyContent: 'center', padding: 'var(--container-padding-static-2xl)' }}>
  <Spinner size="large" />
</div>

// Skeleton while content loads
<Skeleton shape="rect" height="200px" />
```

---

## Design tokens

All tokens are CSS custom properties. Use them directly in custom styles:

```css
.custom {
  background: var(--container-color-themeable-primary);
  color: var(--text-color-themeable-primary);
  border-radius: var(--border-radius-static-primary);
  padding: var(--container-padding-static-primary);
  gap: var(--container-gap-static-primary);
}
```

Token naming convention: `--{category}-{property}-{themeable|static}-{name}`
- `themeable` = changes between dark/light themes
- `static` = same in both themes

### Key token categories
- `--border-color-*`, `--border-radius-*`, `--border-width-*`
- `--container-color-*`, `--container-padding-*`, `--container-gap-*`, `--container-shadow-*`
- `--text-color-*`, `--text-size-*`, `--text-weight-*`, `--text-family-*`
- `--icon-color-*`, `--icon-width-*`, `--icon-height-*`
- `--focus-ring`, `--focus-ring-offset`

---

## This is NOT Tailwind or shadcn/ui — critical token mapping

This library does NOT use Tailwind CSS or shadcn/ui. Do not use Tailwind utility classes (`flex`, `gap-3`, `text-sm`, `bg-background`, etc.) or shadcn CSS variables. They will not work.

### CSS variable name mapping (shadcn → this library)

| shadcn / wrong | This library / correct |
|---|---|
| `var(--background)` | `var(--container-color-themeable-secondary)` |
| `var(--foreground)` | `var(--text-color-themeable-primary)` |
| `var(--card)` | `var(--container-color-themeable-primary)` |
| `var(--muted)` | `var(--container-color-themeable-tertiary)` |
| `var(--muted-foreground)` | `var(--text-color-themeable-secondary)` |
| `var(--border)` | `var(--border-color-themeable-primary)` |
| `var(--primary)` | `var(--container-color-static-brand-primary)` |
| `var(--destructive)` | `var(--container-color-static-state-error)` |
| `var(--radius)` or `var(--radius-lg)` | `var(--border-radius-static-primary)` |
| `var(--radius-sm)` | `var(--border-radius-static-small)` |
| `var(--text-sm)` | `var(--text-size-static-body2)` |
| `var(--text-xs)` | `var(--text-size-static-caption)` |

### Layout — use inline styles or CSS modules, NOT Tailwind classes

```tsx
// ❌ Wrong — Tailwind classes won't work
<div className="flex gap-4 p-6 bg-background rounded-lg">

// ✅ Correct — inline styles with tokens
<div style={{
  display: 'flex',
  gap: 'var(--container-gap-static-primary)',
  padding: 'var(--container-padding-static-large)',
  background: 'var(--container-color-themeable-primary)',
  borderRadius: 'var(--border-radius-static-primary)',
}}>
```

## Components

### Actions

**Button**
```tsx
<Button
  variant?: 'brandPrimary' | 'brandSecondary' | 'neutral' | 'statusError'  // default: brandPrimary
  emphasis?: 'primary' | 'secondary' | 'tertiary'  // default: primary
  leadingIcon?: React.ReactNode
  trailingIcon?: React.ReactNode
  fullWidth?: boolean
  loading?: boolean
  disabled?: boolean
>
  Label
</Button>
```

**IconButton**
```tsx
<IconButton
  icon: IconDefinition          // required — FontAwesome icon
  aria-label: string            // required for a11y
  variant?: 'neutral' | 'brandPrimary' | 'brandSecondary' | 'ghost'
  iconSize?: 'xs' | 'small' | 'medium' | 'large' | 'xl'
  disabled?: boolean
/>
```

**ButtonGroup**
```tsx
<ButtonGroup items: ButtonGroupItem[] />
// ButtonGroupItem: { label, onClick, disabled?, leadingIcon?, trailingIcon?, variant?, emphasis? }
```

**SplitButton**
```tsx
<SplitButton
  label: string
  onClick: () => void
  items: SplitButtonItem[]      // dropdown actions
  variant?: ButtonVariant
/>
```

### Feedback & Status

**Alert**
```tsx
<Alert
  severity?: 'error' | 'warning' | 'success' | 'info'  // default: info
  dismissible?: boolean
  onDismiss?: () => void
>
  Message text
</Alert>
```

**Toast**
```tsx
<Toast
  urgency?: 'error' | 'warning' | 'success' | 'information' | 'none'
  action?: string               // label for optional action button
  onAction?: () => void
  duration?: number             // ms before auto-dismiss; 0 = permanent
  onDismiss?: () => void
>
  Message
</Toast>
```

**Badge**
```tsx
<Badge
  variant?: 'numeric' | 'dot'
  value?: number
  max?: number                  // shows max+ when exceeded
/>
```

**StatusChip**
```tsx
<StatusChip
  label: string
  statusType: 'positive' | 'caution' | 'negative' | 'neutral'
  level: 1 | 2 | 3
/>
```

**PriorityChip**
```tsx
<PriorityChip
  priority: 'urgent' | 'high' | 'medium' | 'low'
  label?: string
/>
```

**ProgressBar**
```tsx
<ProgressBar value: number max?: number />
```

**Spinner**
```tsx
<Spinner size?: 'small' | 'medium' | 'large' color?: 'brand' | 'neutral' />
```

**Skeleton**
```tsx
<Skeleton shape?: 'line' | 'circle' | 'rect' width?: string height?: string />
```

### Overlays

**Dialog**
```tsx
<Dialog
  open: boolean
  onClose?: () => void
  title: string
  subtitle?: string
  dismissible?: boolean         // default: true
  content?: string
  primaryAction?: DialogAction  // { label, onClick, disabled?, variant?, leadingIcon? }
  secondaryAction?: DialogAction
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
>
  {/* optional custom body content */}
</Dialog>
```

**Drawer**
```tsx
<Drawer
  open: boolean
  onClose?: () => void
  title: string
  subtitle?: string
  dismissible?: boolean
  primaryAction?: DrawerAction  // { label, onClick, disabled? }
  secondaryAction?: DrawerAction
>
  {/* content */}
</Drawer>
```

**Tooltip**
```tsx
<Tooltip content: string placement?: 'top' | 'right'>
  <ChildElement />
</Tooltip>
```

**Popover**
```tsx
<Popover
  trigger: React.ReactElement
  placement?: 'top' | 'bottom' | 'left' | 'right'
>
  {/* content */}
</Popover>
```

**Menu / MenuItem**
```tsx
<Menu>
  <MenuItem
    label: string
    icon?: IconDefinition
    onClick?: () => void
    disabled?: boolean
    variant?: 'default' | 'danger'
  />
</Menu>
```

### Forms

**TextField**
```tsx
<TextField
  label?: string
  error?: string
  hint?: string
  leadingIcon?: IconDefinition
  trailingIcon?: IconDefinition
  disabled?: boolean
  // + all standard <input> props
/>
```

**TextArea**
```tsx
<TextArea label?: string error?: string hint?: string disabled?: boolean />
```

**NumberField**
```tsx
<NumberField label?: string error?: string hint?: string min?: number max?: number step?: number />
```

**PasswordField**
```tsx
<PasswordField label?: string error?: string hint?: string />
```

**SelectField**
```tsx
<SelectField
  label?: string
  error?: string
  hint?: string
  options: SelectOption[]       // { value, label, disabled? }
/>
```

**MultiSelectField**
```tsx
<MultiSelectField
  label?: string
  options: { value, label }[]
  value?: string[]
  onChange?: (values: string[]) => void
/>
```

**DateField / TimeField**
```tsx
<DateField label?: string error?: string hint?: string />
<TimeField label?: string error?: string hint?: string />
```

**DatePicker / TimePicker**
```tsx
<DatePicker label?: string value?: string onChange?: (date: string) => void />
<TimePicker label?: string value?: TimeValue onChange?: (value: TimeValue) => void />
```

**Checkbox / CheckboxGroup**
```tsx
<Checkbox label?: string checked?: boolean onChange?: () => void disabled?: boolean />
<CheckboxGroup
  items: { label, value, checked? }[]
  orientation?: 'horizontal' | 'vertical'
  onChange?: (values: string[]) => void
/>
```

**Switch / SwitchGroup**
```tsx
// Switch extends React.InputHTMLAttributes<HTMLInputElement>
// onChange receives a real React.ChangeEvent — use e.target.checked
<Switch
  checked?: boolean
  defaultChecked?: boolean
  onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
  disabled?: boolean
/>

// Example:
const [on, setOn] = useState(false)
<Switch checked={on} onChange={(e) => setOn(e.target.checked)} />
```

**RadioButton / RadioButtonGroup**
```tsx
// RadioButtonGroup wraps RadioButtonItem children — NOT an items prop
// name is REQUIRED for native radio group behavior
<RadioButtonGroup
  name="my-group"                          // required
  value={selected}
  onChange={(value) => setSelected(value)} // receives the string value directly
  orientation?: 'horizontal' | 'vertical'
>
  <RadioButtonItem value="csv"  label="CSV" />
  <RadioButtonItem value="xlsx" label="XLSX" />
  <RadioButtonItem value="pdf"  label="PDF" />
</RadioButtonGroup>
```

**FileUploader**
```tsx
<FileUploader
  accept?: string
  multiple?: boolean
  onUpload?: (files: File[]) => void
/>
```

### Navigation

**NavDrawer**
```tsx
<NavDrawer
  items: NavItemConfig[]        // { label, icon?, selected?, onClick?, children?: NavChildItem[] }
  collapsed?: boolean
  onCollapseToggle?: () => void
/>
```

**Breadcrumb**
```tsx
<Breadcrumb items: BreadcrumbItem[] />  // { label, href?, onClick? }
```

**TabGroup**
```tsx
<TabGroup
  tabs: { label, disabled? }[]
  activeIndex?: number
  onChange?: (index: number) => void
/>
```

**Pagination / SimplePagination**
```tsx
<Pagination
  totalItems: number
  pageSize: number
  currentPage: number
  onChange: (page: number) => void
/>
```

**Stepper**
```tsx
<Stepper
  steps: StepItem[]             // { label, description? }
  activeStep: number
/>
```

**GlobalToolbar**
```tsx
<GlobalToolbar
  leading?: React.ReactNode
  trailing?: React.ReactNode
/>
```

**PageHeader**
```tsx
<PageHeader
  title: string
  subtitle?: string
  actions?: PageAction[]        // { label, onClick, variant?, leadingIcon? }
  breadcrumb?: BreadcrumbItem[]
/>
```

### Display

**Avatar**
```tsx
<Avatar
  type?: 'initials' | 'icon' | 'image'  // default: initials
  size?: 'small' | 'default' | 'large'
  initials?: string             // for type="initials"
  icon?: IconDefinition         // for type="icon"
  src?: string                  // for type="image"
  alt?: string
/>
```

**Chip**
```tsx
<Chip
  label: string
  icon?: IconDefinition
  selected?: boolean
  defaultSelected?: boolean
  onChange?: (selected: boolean) => void
/>
```

**AttributeChip**
```tsx
<AttributeChip label: string value: string />
```

**Icon**
```tsx
<Icon
  icon: IconDefinition          // FontAwesome icon, e.g. faHouse
  size?: 'xs' | 'small' | 'medium' | 'large' | 'xl'
  color?: string
  aria-label?: string           // required if icon conveys meaning with no surrounding text
/>
```

**Divider**
```tsx
<Divider orientation?: 'horizontal' | 'vertical' />
```

**Logo**
```tsx
<Logo orientation?: 'horizontal' | 'stacked' size?: 'small' | 'medium' | 'large' />
```

**UserIdentificationTag**
```tsx
<UserIdentificationTag
  name: string
  subtitle?: string
  initials?: string
  avatarSrc?: string
/>
```

### Data

**Table**
```tsx
// Full example — copy this pattern exactly
<Table
  columns={[
    { key: 'name',   header: 'Name',   type: 'text',   accessor: (row) => row.name },
    { key: 'email',  header: 'Email',  type: 'text',   accessor: 'email' },  // keyof T shorthand
    { key: 'status', header: 'Status', type: 'status', accessor: (row) => ({
        label: row.status,
        statusType: 'positive',   // 'positive' | 'caution' | 'negative' | 'neutral'
        level: 1,                 // 1 | 2 | 3
      })
    },
    { key: 'role',     header: 'Role',    type: 'priority', accessor: (row) => row.priority },
    { key: 'actions',  header: '',        type: 'overflow',
      items: (row) => [{ label: 'Edit', onClick: () => handleEdit(row) }]
    },
  ]}
  data={rows}
  getRowId={(row) => row.id}       // required — unique string ID per row
  page={page}                       // required
  pageSize={pageSize}               // required
  totalItems={total}                // required
  onPageChange={setPage}            // required
  onPageSizeChange={setPageSize}    // required
/>

// TableColumn types: 'text' | 'user' | 'status' | 'priority' | 'checkbox' | 'overflow' | 'nav'
// 'user' accessor returns: { name, subtitle?, initials?, avatarSrc? }
// 'status' accessor returns: { label, statusType, level }
// 'overflow' uses items:(row) => { label, onClick, icon?, disabled? }[]
```

**DataGrid**
```tsx
<DataGrid
  columns: DataGridColumn[]
  data: Record<string, any>[]
  overflowItems?: DataGridOverflowItem[]
/>
```

**KpiCard**
```tsx
// Note: prop is "description" not "label"
<KpiCard
  value: string          // e.g. "1,240" or "94%"
  description: string    // e.g. "Estimated Rows" or "Uptime"
  trailingIcon?: IconDefinition
/>
```

**ListCard**
```tsx
// Combines a summary header with a scrollable list
<ListCard
  value: string          // large display number/text
  description: string    // label below the value
  items: { title: string, subtitle?: string }[]
/>
```

**ListGroup / ListItem**
```tsx
<ListGroup label?: string>
  <ListItem
    title: string
    subtitle?: React.ReactNode
    leading?: React.ReactNode
    trailing?: React.ReactNode
    onClick?: () => void
  />
</ListGroup>
```

**Card**
```tsx
<Card className?: string>{/* any content */}</Card>
```

**ChartCard**
```tsx
<ChartCard
  title: string
  data: BarChartDataPoint[]     // { label, value }
/>
```

**AccordionGroup / AccordionItem**
```tsx
<AccordionGroup>
  <AccordionItem title: string defaultOpen?: boolean>
    {/* content */}
  </AccordionItem>
</AccordionGroup>
```
