---
name: ui-components
description: |
  Generates standardized SmartStack UI components.
  Use this skill when:
  - Creating React page (.tsx) in src/pages/
  - Creating React component in src/components/
  - User asks to create cards, grids, tables, or Kanban
  - Claude detects creation of an entity list with display
  - User mentions "card", "grid", "table", "list", "kanban"
  - User asks for tooltips or infobubbles
  - Creating a page with entity display
  - Managing disabled states with explanatory messages
  Scope: Pages, Components, Cards, Tables, Grids, Kanban boards, Tooltips
---

# Skill UI Components SmartStack

**Reference:** [_shared.md](../_shared.md) for architecture, i18n

## WHEN THIS SKILL ACTIVATES

| Trigger | Example |
|---------|---------|
| React page/component creation | File in `src/pages/**/*.tsx` or `src/components/**/*.tsx` |
| List creation | "Display products in cards" |
| Keywords | "card", "grid", "table", "kanban", "tooltip" |
| Disabled states | "Disable button with explanatory message" |

## MANDATORY COMPONENT: EntityCard

**ALWAYS use `EntityCard` for entity cards. NEVER use custom cards with divs.**

```typescript
import { EntityCard, ProviderCard, TemplateCard } from '@/components/ui/EntityCard';
```

### EntityCard Usage
```tsx
<EntityCard
  avatar={{ letter: 'O', color: '#10a37f' }}
  title="OpenAI" subtitle="openai"
  description="OpenAI GPT models"
  stats="15 model(s)"
  badge={{ icon: Shield, tooltip: 'Admin API supported' }}
  links={[{ icon: ExternalLink, label: 'Website', href: 'https://...' }]}
  actions={[{ label: 'Config', onClick: () => {}, variant: 'primary' }]}
/>
```

### ProviderCard Usage (AI Providers)
```tsx
<ProviderCard name="OpenAI" code="openai" description="..." modelCount={15} color="#10a37f"
  websiteUrl="..." docsUrl="..." apiKeyUrl="..." hasAdminKey />
```

### TemplateCard Usage (Templates)
```tsx
<TemplateCard name="Welcome" code="welcome" category="Transactional" isActive isSystem
  icon={Mail} translationsCount={3} onClick={() => {}} onEdit={() => {}} onDelete={() => {}} />
```

### EntityCard Props
| Prop | Type |
|------|------|
| `avatar` | `{ letter, color, imageUrl? }` |
| `title`, `subtitle`, `description` | `string` |
| `stats` | `string` |
| `badge` | `{ icon?, tooltip?, color? }` |
| `links` | `Array<{ icon, label, href?, onClick? }>` |
| `actions` | `Array<{ label, href?, onClick?, variant, icon?, disabled? }>` |

### Action Variants
| Variant | Style |
|---------|-------|
| `primary` | `bg-accent-500 text-white` |
| `secondary` | `bg-accent-700 text-white` |
| `ghost` | `border bg-transparent` |

## RESPONSIVE GRID

```tsx
// Standard
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
  {items.map(item => <EntityCard key={item.id} {...mapToCardProps(item)} />)}
</div>

// With empty state
{items.length === 0 ? (
  <div className="text-center py-12"><p>No items</p></div>
) : (
  <div className="grid ...">...</div>
)}
```

## CUSTOM CARDS (Status, Dashboard)

Button alignment pattern at bottom (mandatory for custom cards):

```tsx
// WARNING: h-full flex flex-col + flex-1 + mt-auto
<div className="h-full flex flex-col rounded-[var(--radius-card)] border ...">
  {/* Header */}
  <div className="px-4 py-3 bg-gradient-to-r from-[var(--color-accent-500)]/10 ...">
    {/* ... */}
  </div>
  {/* Content with flex-1 */}
  <div className="flex-1 flex flex-col p-4">
    {/* Variable content */}
    {/* WARNING: mt-auto for button at bottom */}
    <button className="mt-auto w-full ...">Action</button>
  </div>
</div>
```

## COMPONENT: DataTable

```tsx
import { DataTable } from '@/components/ui/DataTable';

<DataTable
  data={users}
  columns={[
    { key: 'name', label: 'Name', sortable: true },
    { key: 'role', label: 'Role', render: (user) => <Badge>{user.role}</Badge> }
  ]}
  pagination={{ pageSize: 10 }}
  searchable
  onRowClick={(user) => navigate(`/users/${user.id}`)}
/>
```

## COMPONENT: Tooltip

```tsx
import { Tooltip } from '@/components/ui/Tooltip';

// Variants: default, error, warning, success, info
// Positions: top, bottom, left, right

<Tooltip content="Permission required" variant="error">
  <button disabled>Protected action</button>
</Tooltip>
```

### Disabled Button with Explanation Pattern
```tsx
const canExecute = hasPermission('module.action.execute');
<Tooltip content={!canExecute ? t('errors.noPermission') : undefined} variant="error" disabled={canExecute}>
  <button disabled={!canExecute}>Action</button>
</Tooltip>
```

## WHEN TO USE EntityCard vs Custom

| EntityCard for | Custom for |
|----------------|------------|
| Homogeneous entity lists | Dashboard stats cards |
| Catalogs | Cards with complex interactive states |
| Clickable grids | Cards with integrated forms |

## ABSOLUTE RULES

| DO | DON'T |
|----|-------|
| EntityCard for entities | Custom cards with manual divs |
| Distinct colored header | rounded-full for avatar (that's table) |
| Responsive grid 1→2→3→4 | Fixed non-responsive grid |
| h-full + flex-1 + mt-auto | Unaligned buttons in grid |
| Empty and loading states | Native HTML tooltip |
