# Component Conventions

Core components built from `02-design-tokens.md`, applying `01-design-principles.md`. This is not an exhaustive component library — it's the small set of primitives most of the product is built from, plus the two components most specific to CommerceDex's identity (the automation suggestion card and the status badge).

## Buttons

Three variants, per `01-design-principles.md` rule 6 (one primary action per screen):

| Variant | Style | Use |
|---|---|---|
| Primary | `color.accent.500` fill, white text, `radius.sm` | The one primary action on a screen |
| Secondary | 1px `color.neutral.400` border, `color.neutral.900` text, transparent fill | Secondary actions alongside a primary button |
| Destructive | `color.danger.500` text or fill (fill only after the confirmation step, per `../ux/03-ux-patterns-and-conventions.md` rule 3) | Refund, delete, cancel actions |

All buttons: `type.body` weight Medium, `space.2` vertical padding, `space.4` horizontal padding, `radius.sm`. No shadow (per token rule). Hover/active/disabled states are opacity and background shifts only — no size or shape change, so nothing shifts layout on interaction.

## Inputs

Text inputs, selects, and textareas share one visual treatment: 1px `color.neutral.400` border (the token specifically chosen to clear the 3:1 non-text contrast minimum for interactive component boundaries — see `02-design-tokens.md`'s audit), `radius.sm`, `space.3` internal padding, `type.body`. Focus state is a `color.accent.500` border plus a subtle accent-tinted ring — the same accent used everywhere else, not a separate "focus color." Error state replaces the border color with `color.danger.500` and shows helper text in `type.small` beneath the field, never as a popup or tooltip that requires a hover to see.

## Cards

The default container for grouped content (a product's edit form, a report summary, a settings section). `color.neutral.0` background, 1px `color.neutral.100` border, `radius.md`, `space.6` internal padding, `shadow.none` — per `01-design-principles.md` rule 2, cards are separated by border, not shadow, because they're static content, not floating elements.

## Tables / lists

The primary way order, product, and customer lists are presented (`../ux/01-information-architecture.md`'s list views). Row separation is a 1px `color.neutral.100` bottom border, not alternating row shading (alternating shading reads as visual noise at the density these lists run at). Row hover is a `color.neutral.50` background — subtle, not a border or shadow change. Column headers use `type.subheading` in `color.neutral.500` (muted, since the data is the point, not the header). Density is the one place `01-design-principles.md` rule 3 allows tighter spacing (`space.3` row padding instead of `space.4`+), because a merchant scanning fifty orders needs more rows visible, not more air.

## Status badges

The visual expression of `../ux/03-ux-patterns-and-conventions.md` rule 7 ("status language is consistent across sections"). A small pill: `radius.sm`, `space.1` vertical / `space.2` horizontal padding, `type.caption`, tinted background at the relevant semantic `*.100` token with text in the corresponding `*.500` token (e.g., "Delivered" = `color.success.100` background, `color.success.500` text). The same badge component renders order status, campaign status, and product status identically in shape — only the semantic color and label text change.

## The automation suggestion card

The visual form of `../ux/03-ux-patterns-and-conventions.md` rule 1's three-part pattern (detected fact → why it matters, when non-obvious → specific action). A card (per the Cards spec above) with:

- A small `color.warning.500` or `color.accent.500` indicator (dot or icon) signaling "this needs a look," never `color.danger.500` — a suggestion is an opportunity, not an error, even when it's about a problem.
- The detected fact in `type.body`, `color.neutral.900`.
- Optional "why it matters" in `type.small`, `color.neutral.500`, omitted when not needed (per the UX rule).
- A primary button (the specific action) and a quiet dismiss affordance (text-only, `color.neutral.500`) side by side — accepting is visually invited, dismissing is available but not competing for attention.

Dismissing a suggestion animates it out per `05-motion-and-microinteractions.md`, rather than disappearing instantly — the motion itself communicates "this was handled," reinforcing `01-design-principles.md` rule 8's confidence-through-clarity goal.

## Navigation shell

The container for the seven sections in `../ux/01-information-architecture.md`. Desktop: a persistent left sidebar, `color.neutral.0` background, 1px right border (`color.neutral.100`), each nav item in `type.body` Medium with `color.neutral.700`. The active item is signaled by color alone — `color.accent.500` text/icon, `type.body` Semibold, no background fill — against the same plain sidebar background every other item sits on, not a filled pill; a merchant reads "where am I" from the accent color the same way primary buttons use it, not from a separate highlighted surface. Some sections (e.g. Products) expand into a small set of sub-items via a minimal chevron (`ChevronDown`/`ChevronRight`, no button chrome beyond a subtle hover background) — the sub-item list auto-expands when a merchant is already on one of its routes, and otherwise stays collapsed. The Store Health glimpse (per `../ux/01-information-architecture.md`'s "global elements") sits at the top of the sidebar, above the nav list, so it's the first thing seen on every screen without needing its own top-level nav slot. Full responsive behavior, including the mobile adaptation, is specified in `04-layout-and-responsive.md`.

## Tree view (`components/tree/`)

A VS Code/Finder-style collapsible tree + details panel, for hierarchical data a flat table stops scaling for (categories, four levels deep and growing) — a genuinely different pattern from the nav shell above, not an inconsistency with it:

- **Selection = background fill** (`color.accent.100`), not color-only. The nav shell's "color alone, no fill" rule is specifically about *route* highlighting against a sidebar a merchant sees on every screen; a tree's selected row is closer to a file explorer's selection, where a background highlight is the expected, conventional affordance. Hover is a separate, subtler `color.neutral.50`.
- **Disclosure arrow** reuses the nav shell's exact `ChevronDown`/`ChevronRight` + `Set<string>` expanded-ids pattern, just keyed by node id instead of nav path. A leaf (no children) shows a plain bullet (`•`) instead of an arrow — not decorative, it's how a user tells "this can't be expanded" from "this happens to be collapsed."
- **Rows carry no per-row action buttons** — edit/move/delete/etc. live in the details panel once something is selected, keeping the row itself to just the disclosure control and the name (no thumbnails, no counts, no icon clutter in the tree itself).
- **Data**: fetched once as a flat, already-sorted list (`sort_order`, not alphabetical) and built into a parent→children map client-side; only *expanded* branches are mounted into the DOM. This is a deliberate trade-off against literally animating collapse (there's no mounted element to animate the height of) — expansion instead plays a brief fade+slide on mount (`treeBranchIn`, 180ms), and collapse is instant. Acceptable since VS Code's own tree behaves similarly in practice.
- **Drag-and-drop** (`@dnd-kit/core`) offers two distinct, separately-highlighted drop targets per row: dropping directly on a row's body reparents the dragged item as that row's child; a thin strip below each row (and one above the very first item in a branch) reparents/reorders it as a sibling at that exact position. A dragged node's own drop targets, and every one of its descendants', are disabled client-side during that drag (not just rejected after the fact by the backend's cycle guard) — dropping a folder into its own child was never a valid gesture to begin with, so the UI doesn't offer it as one.
