# Card Patterns

## Overview

Pre-composed card blocks for dashboards, project management, and feature pages. Each component is built exclusively from `ui/` primitives — no external dependencies. They live in `components/blocks/card-patterns/` and are available from the root `xertica-ui` import.

---

## When to Use

- You need a recurring dashboard pattern (activity feed, KPI, project status, notifications) and don't want to re-compose it from scratch in every feature.
- The pattern is stable and reused across multiple pages.

## When NOT to Use

- One-off layouts specific to a single page — compose directly from `Card` and `ui/` primitives instead.
- When the pattern has significant data-fetching logic — keep the block as a pure presentational component and handle state in the feature layer.

---

## Import

```tsx
// from root barrel
import { ActivityCard, ProjectCard, FeatureCard } from 'xertica-ui';

// or from blocks subpath (when available)
import { ActivityCard, ProjectCard, FeatureCard } from 'xertica-ui/blocks';
```

---

## Components

### FeatureCard

Icon with a colored background, title, optional badge, description, and an action button. Matches the card pattern used on the Home page.

```tsx
import { FeatureCard } from 'xertica-ui';
import { FileText } from 'lucide-react';

<FeatureCard
  title="Template CLI"
  description="Página de template pronta para uso com todos os componentes configurados."
  icon={<FileText />}
  color="chart-2"
  badge="Novo"
  actionLabel="Visualizar"
  onAction={() => navigate('/template')}
/>;
```

**Props:**

| Prop            | Type               | Default     | Description                                  |
| --------------- | ------------------ | ----------- | -------------------------------------------- |
| `title`         | `string`           | —           | Card title                                   |
| `description`   | `string`           | —           | Body text                                    |
| `icon`          | `ReactNode`        | —           | Icon element (size is controlled internally) |
| `color`         | `FeatureCardColor` | `'primary'` | Icon background color token                  |
| `badge`         | `string`           | —           | Optional badge label                         |
| `badgeVariant`  | `BadgeVariant`     | `'default'` | Badge color variant                          |
| `actionLabel`   | `string`           | —           | Button label (omit to hide button)           |
| `actionVariant` | `ButtonVariant`    | `'outline'` | Button variant                               |
| `onAction`      | `() => void`       | —           | Button click handler                         |

**`FeatureCardColor` values:** `primary` · `chart-1` · `chart-2` · `chart-3` · `chart-4` · `chart-5` · `success` · `info` · `warning` · `destructive`

---

### ActivityCard

Chronological feed of user actions with avatar, action description, timestamp, and type badge.

```tsx
import { ActivityCard } from 'xertica-ui';

<ActivityCard
  title="Atividade Recente"
  items={[
    {
      id: '1',
      user: { name: 'Ana Souza', initials: 'AS' },
      action: 'criou o projeto',
      target: 'Xertica Dashboard',
      time: 'há 2 min',
      type: 'create',
    },
  ]}
  action={
    <Button variant="ghost" size="sm">
      Ver tudo
    </Button>
  }
  maxItems={5}
/>;
```

**Props:**

| Prop       | Type             | Default               | Description                 |
| ---------- | ---------------- | --------------------- | --------------------------- |
| `title`    | `string`         | `'Atividade Recente'` | Card title                  |
| `items`    | `ActivityItem[]` | —                     | Feed entries                |
| `action`   | `ReactNode`      | —                     | Right-aligned header action |
| `maxItems` | `number`         | `5`                   | Maximum visible items       |

**`ActivityItem` shape:**

```ts
{
  id: string
  user: { name: string; initials: string; avatar?: string }
  action: string        // verb: "criou", "atualizou", etc.
  target: string        // object of the action
  time: string          // relative time string
  type?: 'create' | 'update' | 'delete' | 'comment' | 'deploy'
}
```

---

### ProfileCard

User or team member card with avatar, status badge, stat row, and primary/secondary actions.

```tsx
import { ProfileCard } from 'xertica-ui';

<ProfileCard
  name="Ana Souza"
  role="Senior Frontend Engineer"
  department="Design System"
  initials="AS"
  status="online"
  stats={[
    { label: 'Projetos', value: 12 },
    { label: 'Commits', value: 348 },
    { label: 'Reviews', value: 57 },
  ]}
  primaryAction={{ label: 'Ver Perfil' }}
  secondaryAction={{ label: 'Mensagem' }}
/>;
```

**Props:**

| Prop              | Type                                           | Default | Description                      |
| ----------------- | ---------------------------------------------- | ------- | -------------------------------- |
| `name`            | `string`                                       | —       | Full name                        |
| `role`            | `string`                                       | —       | Job title                        |
| `department`      | `string`                                       | —       | Department or team               |
| `initials`        | `string`                                       | —       | Avatar fallback (2 chars)        |
| `avatar`          | `string`                                       | —       | Avatar image URL                 |
| `status`          | `'online' \| 'offline' \| 'away' \| 'busy'`    | —       | Presence badge                   |
| `stats`           | `{ label: string; value: string \| number }[]` | —       | Up to 3 stats in the divider row |
| `primaryAction`   | `{ label: string; onClick?: () => void }`      | —       | Primary button                   |
| `secondaryAction` | `{ label: string; onClick?: () => void }`      | —       | Secondary (outline) button       |

---

### ProjectCard

Project status widget with title, description, status badge, progress bar, member avatar stack, and due date.

```tsx
import { ProjectCard } from 'xertica-ui';

<ProjectCard
  title="Xertica Dashboard v3"
  description="Redesign completo do painel."
  status="active"
  progress={68}
  dueDate="Vence em 12 dias"
  members={[
    { name: 'Ana', initials: 'AS' },
    { name: 'Bruno', initials: 'BL' },
  ]}
/>;
```

**Props:**

| Prop          | Type              | Default | Description                               |
| ------------- | ----------------- | ------- | ----------------------------------------- |
| `title`       | `string`          | —       | Project name                              |
| `description` | `string`          | —       | Short description                         |
| `status`      | `ProjectStatus`   | —       | Controls badge color and progress color   |
| `progress`    | `number`          | —       | 0–100 completion percentage               |
| `dueDate`     | `string`          | —       | Due date label string                     |
| `members`     | `ProjectMember[]` | `[]`    | Avatar stack                              |
| `maxMembers`  | `number`          | `4`     | Max visible avatars before overflow count |
| `action`      | `ReactNode`       | —       | Extra action appended to the status badge |

**`ProjectStatus` values:** `active` · `review` · `done` · `paused` · `at-risk`

---

### QuickActionCard

Action tile with icon in colored box, title, description, optional badge, and a full-width action button.

```tsx
import { QuickActionCard } from 'xertica-ui';
import { FileText } from 'lucide-react';

<QuickActionCard
  title="Novo Relatório"
  description="Gere relatórios personalizados de métricas."
  icon={<FileText className="size-5" />}
  actionLabel="Criar Relatório"
  badge="Pro"
  badgeVariant="warning"
  onAction={() => openReportModal()}
/>;
```

**Props:**

| Prop            | Type            | Default       | Description              |
| --------------- | --------------- | ------------- | ------------------------ |
| `title`         | `string`        | —             | Tile title               |
| `description`   | `string`        | —             | Supporting text          |
| `icon`          | `ReactNode`     | —             | Icon in the colored box  |
| `badge`         | `string`        | —             | Optional badge           |
| `badgeVariant`  | `BadgeVariant`  | `'secondary'` | Badge color variant      |
| `actionLabel`   | `string`        | —             | Button label             |
| `actionVariant` | `ButtonVariant` | `'default'`   | Button variant           |
| `onAction`      | `() => void`    | —             | Button click handler     |
| `disabled`      | `boolean`       | `false`       | Disables the entire card |

---

### NotificationCard

Notification list with unread indicator dots, type badges, timestamps, and bulk mark-as-read action.

```tsx
import { NotificationCard } from 'xertica-ui';

<NotificationCard
  title="Notificações"
  items={[
    {
      id: '1',
      title: 'Deploy concluído',
      message: 'v2.1.0 publicado com sucesso.',
      time: 'há 2 min',
      read: false,
      type: 'success',
    },
  ]}
  onMarkAllRead={() => markAllRead()}
  onViewAll={() => navigate('/notifications')}
  maxItems={4}
/>;
```

**Props:**

| Prop            | Type                 | Default          | Description                                     |
| --------------- | -------------------- | ---------------- | ----------------------------------------------- |
| `title`         | `string`             | `'Notificações'` | Card title                                      |
| `items`         | `NotificationItem[]` | —                | Notification entries                            |
| `unreadCount`   | `number`             | auto-counted     | Override unread count badge                     |
| `onMarkAllRead` | `() => void`         | —                | Shows "Marcar todas como lidas" button when set |
| `onViewAll`     | `() => void`         | —                | Shows "Ver todas" footer button when set        |
| `maxItems`      | `number`             | `4`              | Maximum visible items                           |

**`NotificationItem` shape:**

```ts
{
  id: string
  title: string
  message: string
  time: string
  read?: boolean
  type?: 'info' | 'warning' | 'success' | 'error' | 'default'
  user?: { name: string; initials: string; avatar?: string }
}
```

---

## Dashboard Layout Example

```tsx
import {
  ActivityCard,
  ChartCard,
  NotificationCard,
  ProfileCard,
  ProjectCard,
  QuickActionCard,
} from 'xertica-ui';
import { DashboardLineChart } from 'xertica-ui/ui';
import { Briefcase, Users, BarChart2 } from 'lucide-react';

<div className="grid gap-4">
  {/* Quick actions row */}
  <div className="grid gap-4 md:grid-cols-3">
    <QuickActionCard title="Novo Projeto" icon={<Briefcase />} actionLabel="Criar" />
    <QuickActionCard
      title="Convidar Equipe"
      icon={<Users />}
      actionLabel="Convidar"
      actionVariant="outline"
    />
    <QuickActionCard
      title="Relatórios"
      icon={<BarChart2 />}
      actionLabel="Abrir"
      badge="Novo"
      badgeVariant="success"
    />
  </div>

  {/* Chart + Notifications */}
  <div className="grid gap-4 md:grid-cols-3">
    <div className="md:col-span-2">
      <ChartCard title="Usuários Ativos" description="Esta semana" contentClassName="h-[220px]">
        <DashboardLineChart
          data={lineData}
          config={lineConfig}
          series={[{ key: 'usuarios', label: 'Usuários' }]}
          indexKey="dia"
          className="h-full w-full"
        />
      </ChartCard>
    </div>
    <NotificationCard title="Alertas" items={notifications} onViewAll={() => {}} />
  </div>

  {/* Activity + Profile + Project */}
  <div className="grid gap-4 md:grid-cols-2">
    <ActivityCard items={activityItems} />
    <div className="grid gap-4">
      <ProfileCard
        name="Ana Souza"
        role="Designer"
        initials="AS"
        status="online"
        stats={[{ label: 'Projetos', value: 12 }]}
      />
      <ProjectCard
        title="Dashboard v3"
        status="active"
        progress={68}
        dueDate="12 dias"
        members={members}
      />
    </div>
  </div>
</div>;
```

---

## Loading States — Skeleton Components

Every card pattern ships with a matching `*Skeleton` companion that mirrors its layout with pulsing placeholders. Use them while data is loading to avoid layout shift and provide visual feedback.

| Card               | Skeleton companion         | Configurable props                  |
| ------------------ | -------------------------- | ----------------------------------- |
| `ActivityCard`     | `ActivityCardSkeleton`     | `rows` (default `5`)                |
| `ProfileCard`      | `ProfileCardSkeleton`      | `showStats`, `showActions`          |
| `ProjectCard`      | `ProjectCardSkeleton`      | `memberCount` (default `3`)         |
| `NotificationCard` | `NotificationCardSkeleton` | `rows` (default `4`), `showViewAll` |
| `QuickActionCard`  | `QuickActionCardSkeleton`  | —                                   |
| `FeatureCard`      | `FeatureCardSkeleton`      | `showAction`                        |

> A matching `StatsCardSkeleton` is also available from `xertica-ui` (lives in `ui/stats-card/`, with props `showIcon` and `showTrend`).

### Usage pattern

```tsx
import { useQuery } from '@tanstack/react-query';
import { ActivityCard, ActivityCardSkeleton } from 'xertica-ui';

function ActivityFeed() {
  const { data: items, isLoading } = useActivityItems();

  if (isLoading) return <ActivityCardSkeleton rows={5} />;
  return <ActivityCard items={items!} />;
}
```

For grids, render one skeleton per expected card:

```tsx
{
  isLoading ? (
    <>
      <FeatureCardSkeleton showAction />
      <FeatureCardSkeleton showAction />
      <FeatureCardSkeleton showAction />
    </>
  ) : (
    data.map(item => <FeatureCard key={item.id} {...item} />)
  );
}
```

### Examples

```tsx
// ActivityCard with 4 row placeholders
<ActivityCardSkeleton rows={4} />

// ProfileCard without stats grid and without action buttons
<ProfileCardSkeleton showStats={false} showActions={false} />

// ProjectCard with 5 member-avatar placeholders
<ProjectCardSkeleton memberCount={5} />

// NotificationCard with footer placeholder
<NotificationCardSkeleton rows={3} showViewAll />

// FeatureCard without footer action button
<FeatureCardSkeleton showAction={false} />
```

> All skeletons use the `Skeleton` primitive from `ui/skeleton`, inheriting the `bg-accent animate-pulse` styling. They render plain `<div>` placeholders — no real Card/Header/Footer subcomponents — so they're cheap to render at scale.

---

## AI Rules

- Import block components from `xertica-ui` — they are included in the root barrel.
- Pass all data as props — never embed fetch calls inside block components.
- Keep `maxItems` at a sensible limit (4–6) to avoid overflow in fixed-height containers.
- For chart panels, use `ChartCard` from `xertica-ui/ui` (not a block component) with any dashboard-ready chart as `children`.
- `FeatureCard` badge wraps to a new line when title + badge don't fit — this is intentional; do not constrain the container width to force inline layout.
- **Always pair each card with its matching `*Skeleton` for loading states** — do not render a spinner or empty container while data fetches. The skeleton variants mirror the final layout closely, preventing layout shift when data arrives.
- **Skeleton row counts should match the expected data count when possible** — pass `rows={maxItems}` to `ActivityCardSkeleton`/`NotificationCardSkeleton` so the placeholder height matches the loaded state.

---

## Related

- [`Card`](./card.md) — Primitive used internally by all block components
- [`StatsCard`](./stats-card.md) — KPI card (lives in `ui/`, not `blocks/`) — also has `StatsCardSkeleton`
- [`Skeleton`](./skeleton.md) — Low-level pulsing placeholder primitive used by every `*Skeleton` here
- [`Chart`](./chart.md) — `ChartCard` and dashboard-ready chart wrappers
- [Dashboard Pattern](../patterns/dashboard.md)
