diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 1fec93a..c43dc73 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -77,9 +77,17 @@ Small compositions of atoms into reusable units. May combine icons, text, button ### `src/components/organisms/` — Organisms Larger compositions that carry domain semantics or define page-level regions. Built from atoms and molecules. - TopBar, SideNav, PageHeader -- *(planned)* AppShell (header + sidebar + content area) - *(planned)* DatePicker +### `src/components/templates/` — Templates +Page-level layout components that define the shell and content structure. Templates accept typed slot props (ReactNode) for their sections, making them composable by AI agents and developers. They do not own content — they define where content goes. +- **AppShell** — TopBar + SideNav + scrollable content area. All pages render inside this. +- **DashboardPage** — PageHeader + stat cards row + responsive 2-column content grid +- **ListPage** — PageHeader + stat cards + list header with actions + scrollable item list +- **FormPage** — PageHeader + optional action bar + optional vertical stepper + constrained-width form content + +Templates have Storybook stories tagged `['autodocs', 'template']` that show realistic "recipe" compositions — full pages built from real components with sample data. These serve as reference implementations for AI coding agents. + ### Which Tier Does a Component Belong To? | Question | If yes → | @@ -88,6 +96,7 @@ Larger compositions that carry domain semantics or define page-level regions. Bu | Does it compose 2+ atoms into a reusable unit (e.g., Alert = icon + text + close button)? | **molecules/** | | Does it carry domain-specific naming or logic (e.g., ThemeCard, ParticipantRow)? | **organisms/** | | Does it define a page-level region or shell (header, sidebar, content area)? | **organisms/** | +| Does it define the layout structure of a full page (slot-based, no owned content)? | **templates/** | When in doubt: start in `atoms/`. Promote to `molecules/` when a component begins importing other atoms. @@ -121,7 +130,8 @@ src/ ├── components/ │ ├── atoms/ # Single-purpose elements │ ├── molecules/ # Small compositions of atoms -│ └── organisms/ # Domain-aware / page-level components +│ ├── organisms/ # Domain-aware / page-level components +│ └── templates/ # Page-level layout components (slot-based) ├── tokens/ │ └── tokens.css # Design tokens (@theme block) ├── styles/ diff --git a/DESIGN.md b/DESIGN.md index 8755b66..e8ff973 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -6,39 +6,78 @@ colors: # Palette (do not reference directly in components) blue-01: "#002664" blue-02: "#146CFD" - blue-03: "#69B3E7" + blue-03: "#8CE0FF" blue-04: "#CBEDFD" - blue-05: "#EBF5FF" - red-01: "#3E0014" + red-01: "#630019" red-02: "#D7153A" - red-03: "#F5C5D0" - red-04: "#FDDDE5" - red-05: "#FFF5F8" - orange-01: "#7A3300" - orange-02: "#EC6608" - orange-03: "#F5B98A" - orange-04: "#FEF0E4" - green-01: "#005C35" - green-02: "#00A651" - green-03: "#89E5B3" - green-04: "#E0F8EA" + red-03: "#FFB8C1" + red-04: "#FFE6EA" + orange-01: "#941B00" + orange-02: "#F3631B" + orange-03: "#FFCE99" + orange-04: "#FDEDDF" + green-01: "#004000" + green-02: "#00AA45" + green-03: "#A8EDB3" + green-04: "#DBFADF" + teal-01: "#0B3F47" + teal-02: "#2E808E" + teal-03: "#8CDBE5" + teal-04: "#D1EEEA" + brown-01: "#523719" + brown-02: "#B68D5D" + brown-03: "#E8D0B5" + brown-04: "#EDE3D7" + purple-01: "#441170" + purple-02: "#8055F1" + purple-03: "#CEBFFF" + purple-04: "#E6E1FD" + fuchsia-01: "#65004D" + fuchsia-02: "#D912AE" + fuchsia-03: "#F4B5E6" + fuchsia-04: "#FDDEF2" + yellow-01: "#694800" + yellow-02: "#FAAF05" + yellow-03: "#FDE79A" + yellow-04: "#FFF4CF" grey-01: "#22272B" - grey-02: "#6D7278" - grey-03: "#C0C0C0" - grey-04: "#E0E0E0" - off-white: "#F4F4F4" + grey-02: "#495054" + grey-03: "#CDD3D6" + grey-04: "#EBEBEB" + grey-05: "#F2F2F2" white: "#FFFFFF" # Semantic (use these in components) - primary: "{colors.blue-02}" - primary-dark: "{colors.blue-01}" + primary: "{colors.blue-01}" + info: "{colors.blue-02}" + secondary: "{colors.blue-04}" error: "{colors.red-02}" success: "{colors.green-02}" warning: "{colors.orange-02}" text: "{colors.grey-01}" text-secondary: "{colors.grey-02}" border: "{colors.grey-04}" - bg: "{colors.off-white}" + bg: "{colors.grey-05}" surface: "{colors.white}" + # Form controls + control-border: "{colors.grey-03}" + control-border-hover: "{colors.grey-01}" + control-checked: "{colors.blue-01}" + control-checked-hover: "{colors.blue-02}" + control-focus-ring: "{colors.blue-04}" + control-label: "{colors.blue-01}" + control-description: "{colors.grey-02}" + control-error: "{colors.red-02}" + control-bg: "{colors.white}" + control-bg-readonly: "{colors.grey-05}" + # Button + button-default: "{colors.blue-01}" + button-danger: "{colors.red-02}" + button-neutral: "{colors.grey-01}" + button-subtle-bg: "{colors.blue-04}" + button-subtle-text: "{colors.blue-01}" + # Switch + switch-on: "{colors.green-02}" + switch-on-hover: "{colors.green-01}" typography: h1: fontFamily: Public Sans Variable @@ -92,59 +131,39 @@ typography: lineHeight: 1.5 rounded: sm: 4px - DEFAULT: 6px - lg: 10px - xl: 16px + DEFAULT: 8px + lg: 16px + xl: 24px full: 9999px spacing: unit: 4px scale: "Tailwind default (4px base: 1=4px, 2=8px, 3=12px, 4=16px, 6=24px, 8=32px)" -components: - button-primary-default: - backgroundColor: "{colors.primary-dark}" - textColor: "{colors.white}" - typography: "{typography.body}" - rounded: "{rounded.DEFAULT}" - height: 48px - button-primary-danger: - backgroundColor: "{colors.red-02}" - textColor: "{colors.white}" - button-secondary-default: - backgroundColor: transparent - textColor: "{colors.primary-dark}" - input: - backgroundColor: "{colors.white}" - textColor: "{colors.text}" - typography: "{typography.body}" - rounded: "{rounded.DEFAULT}" - height: 48px - card-surface: - backgroundColor: "{colors.surface}" - rounded: "{rounded.xl}" - badge-info: - backgroundColor: "{colors.blue-02}" - textColor: "{colors.white}" - rounded: "{rounded.full}" - alert-info: - backgroundColor: "{colors.blue-05}" - rounded: "{rounded.DEFAULT}" --- ## Overview -ADS 3.0 (Adaptive Design System) is a React component library based on the NSW Department of Education's digital design language. It uses Public Sans as its typeface, a structured blue/grey palette with semantic colour aliases, and consistent border radii and spacing. Components are built with React 19, TypeScript (strict), and Tailwind CSS v4. +ADS 3.0 (Adaptive Design System) is a React component library based on the NSW Department of Education's digital design language. It uses Public Sans as its typeface, a structured colour palette with semantic aliases, and consistent border radii and spacing. Components are built with React 19, TypeScript (strict), and Tailwind CSS v4. All visual values come from design tokens defined in `src/tokens/tokens.css`. Components never use raw colour values — they reference semantic or domain-specific token classes via Tailwind utilities. ## Colors -The palette is organised in three layers: +The palette is organised in layers, from raw to consumable: -1. **Palette** — Raw values (`blue-01` through `blue-05`, `grey-01` through `grey-04`, etc.). Never use these directly in component code. -2. **Semantic** — Purpose-based aliases (`primary`, `error`, `success`, `warning`, `text`, `surface`, `bg`, `border`). Use these for general UI. -3. **Domain** — Component-specific tokens (`button-default`, `control-border`, `badge-info`, `alert-error-bg`, `tag-navy`, etc.). Use these within their respective components. +1. **Palette** — Raw values across 10 colour families (blue, red, orange, green, teal, brown, purple, fuchsia, yellow, grey) with 4 shades each (`-01` darkest to `-04` lightest), plus grey-05 and white. Never use these directly in component code. +2. **Semantic** — Purpose-based aliases (`primary`, `info`, `secondary`, `error`, `success`, `warning`, `text`, `text-secondary`, `surface`, `bg`, `border`). Use these for general UI. +3. **Form control** — Shared interactive-state tokens for all form components: `control-border`, `control-border-hover`, `control-checked`, `control-checked-hover`, `control-focus-ring`, `control-label`, `control-description`, `control-error`, `control-bg`, `control-bg-readonly`. +4. **Button** — Intent tokens: `button-default` (navy), `button-danger` (red), `button-neutral` (dark grey), `button-subtle-bg`, `button-subtle-text`. +5. **Switch** — On-state tokens: `switch-on` (success green), `switch-on-hover`. +6. **Badge** — Status colour tokens: `badge-navy`, `badge-info`, `badge-info-light`, `badge-success`, `badge-success-light`, `badge-error`, `badge-error-light`, `badge-warning`, `badge-warning-light`, `badge-neutral`, plus contrast text tokens (`badge-on-success-light`, `badge-on-error-light`, `badge-on-warning-light`). +7. **Chip** — Border/fill state tokens: `chip-border`, `chip-text`, `chip-bg`, `chip-selected-bg`, `chip-selected-text`. +8. **Tag** — 11-colour system, each with a `-light` variant: navy, blue, green, red, orange, grey, teal, brown, purple, fuchsia, yellow. +9. **Alert** — Background, border, and icon tokens for 5 variants: `alert-{variant}-bg`, `alert-{variant}-border`, `alert-{variant}-icon` (info, warning, error, success, neutral). +10. **Avatar** — `avatar` (background), `avatar-text`. +11. **TopBar** — `topbar` (background, navy). +12. **SideNav** — `nav-bg`, `nav-text`, `nav-icon`, `nav-active`, `nav-divider`. -The primary brand colour is `blue-01` (#002664, dark navy) for interactive elements like buttons and links. `blue-02` (#146CFD) is the brighter accent used for info states, focus rings, and highlights. +The primary brand colour is `blue-01` (#002664, dark navy) used for buttons, links, and navigation. `blue-02` (#146CFD) is the brighter accent for info states, focus rings, and highlights. ## Typography @@ -159,7 +178,7 @@ Spacing follows Tailwind's default 4px base scale. Common values: - `gap-2` (8px), `gap-3` (12px), `gap-4` (16px) for flex/grid gaps - `space-y-4` (16px) for stacked content -No custom spacing tokens are defined — the Tailwind defaults are sufficient. This may change in future. +No custom spacing tokens are defined — the Tailwind defaults are sufficient. ## Elevation & Depth @@ -171,14 +190,61 @@ Two shadow levels: Border radii use the custom scale: - `rounded-sm` (4px) — small elements, tags -- `rounded-default` (6px) — buttons, inputs, alerts -- `rounded-lg` (10px) — cards, dialogs -- `rounded-xl` (16px) — large containers -- `rounded-full` (9999px) — badges, circular buttons +- `rounded-default` (8px) — buttons, inputs, alerts, chips +- `rounded-lg` (16px) — cards, dialogs +- `rounded-xl` (24px) — large containers, page headers +- `rounded-full` (9999px) — badges, avatars, circular buttons + +## Page Layout + +Pages are composed using the **AppShell** template, which provides the fixed TopBar, collapsible SideNav, and scrollable content area. Content within the main area follows these conventions: + +### Shell structure +``` +┌─────────────────────────────────────────┐ +│ TopBar (h-16, fixed top, full width) │ +├────────┬────────────────────────────────┤ +│SideNav │ Content area (scrollable) │ +│(w-[360]│ │ +│ or │ PageHeader (optional) │ +│ w-20 │ │ +│collapsed│ Main content │ +│) │ │ +└────────┴────────────────────────────────┘ +``` + +### Content patterns + +**Dashboard** — Use a responsive grid for content cards: +- 2-column grid at `lg` breakpoint: `grid grid-cols-1 lg:grid-cols-2 gap-6` +- Stat cards in a horizontal row: `flex gap-4` or `grid grid-cols-3 gap-4` +- Content padding: `p-6` or `p-8` +- Section spacing: `space-y-6` + +**List page** — Stat summary row above a scrollable list: +- Stat cards row: `flex gap-4` with Card components +- List section: Card wrapping a vertical list with `divide-y divide-border` +- Each list item: `px-6 py-4` with flex layout for metadata + +**Form page** — Stepped form with vertical stepper: +- Content max-width: `max-w-3xl` for readable line length +- Form sections: `space-y-6` between groups +- Stepper: vertical list on left, form content fills remaining space +- Submit actions: right-aligned with `flex justify-end gap-3` + +### Spacing conventions +- Page content padding: `p-6` (24px) minimum +- Section gap: `gap-6` (24px) between major sections +- Card internal padding: `p-5` or `p-6` +- Form field spacing: `space-y-4` (16px) between fields, `space-y-6` (24px) between groups + +--- ## Components -### Button +### Atoms + +#### Button Interactive button with variant/intent/size matrix. @@ -199,7 +265,7 @@ import { Button } from '@/components/atoms/Button' ``` -### IconButton +#### IconButton Icon-only button with required `aria-label`. @@ -220,7 +286,7 @@ import { X, Settings } from 'lucide-react' } aria-label="Settings" shape="square" size="compact" /> ``` -### Input +#### Input Text input with label, description, hint, error, and icon slots. @@ -244,7 +310,7 @@ import { Search, Mail } from 'lucide-react' ``` -### Textarea +#### Textarea Multi-line text input with optional auto-resize. @@ -265,7 +331,7 @@ import { Textarea } from '@/components/atoms/Textarea'