Rebrand to ADS 3.0 Design System and add DESIGN.md component reference
Convert from Research Synthesiser-specific project to general-purpose ADS 3.0 design system intended to be forked for downstream applications. Add DESIGN.md following Google Labs spec as machine-readable reference for AI coding agents. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
586
DESIGN.md
Normal file
586
DESIGN.md
Normal file
@@ -0,0 +1,586 @@
|
|||||||
|
---
|
||||||
|
name: ADS 3.0 Design System
|
||||||
|
description: React component library implementing the NSW Department of Education's ADS 3.0 design language
|
||||||
|
version: alpha
|
||||||
|
colors:
|
||||||
|
# Palette (do not reference directly in components)
|
||||||
|
blue-01: "#002664"
|
||||||
|
blue-02: "#146CFD"
|
||||||
|
blue-03: "#69B3E7"
|
||||||
|
blue-04: "#CBEDFD"
|
||||||
|
blue-05: "#EBF5FF"
|
||||||
|
red-01: "#3E0014"
|
||||||
|
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"
|
||||||
|
grey-01: "#22272B"
|
||||||
|
grey-02: "#6D7278"
|
||||||
|
grey-03: "#C0C0C0"
|
||||||
|
grey-04: "#E0E0E0"
|
||||||
|
off-white: "#F4F4F4"
|
||||||
|
white: "#FFFFFF"
|
||||||
|
# Semantic (use these in components)
|
||||||
|
primary: "{colors.blue-02}"
|
||||||
|
primary-dark: "{colors.blue-01}"
|
||||||
|
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}"
|
||||||
|
surface: "{colors.white}"
|
||||||
|
typography:
|
||||||
|
h1:
|
||||||
|
fontFamily: Public Sans Variable
|
||||||
|
fontSize: 48px
|
||||||
|
fontWeight: "700"
|
||||||
|
lineHeight: 1.25
|
||||||
|
h2:
|
||||||
|
fontFamily: Public Sans Variable
|
||||||
|
fontSize: 32px
|
||||||
|
fontWeight: "700"
|
||||||
|
lineHeight: 1.25
|
||||||
|
h3:
|
||||||
|
fontFamily: Public Sans Variable
|
||||||
|
fontSize: 24px
|
||||||
|
fontWeight: "600"
|
||||||
|
lineHeight: 1.333
|
||||||
|
h4:
|
||||||
|
fontFamily: Public Sans Variable
|
||||||
|
fontSize: 20px
|
||||||
|
fontWeight: "600"
|
||||||
|
lineHeight: 1.4
|
||||||
|
h5:
|
||||||
|
fontFamily: Public Sans Variable
|
||||||
|
fontSize: 16px
|
||||||
|
fontWeight: "600"
|
||||||
|
lineHeight: 1.5
|
||||||
|
h6:
|
||||||
|
fontFamily: Public Sans Variable
|
||||||
|
fontSize: 14px
|
||||||
|
fontWeight: "600"
|
||||||
|
lineHeight: 1.43
|
||||||
|
intro:
|
||||||
|
fontFamily: Public Sans Variable
|
||||||
|
fontSize: 20px
|
||||||
|
fontWeight: "400"
|
||||||
|
lineHeight: 1.4
|
||||||
|
body:
|
||||||
|
fontFamily: Public Sans Variable
|
||||||
|
fontSize: 16px
|
||||||
|
fontWeight: "400"
|
||||||
|
lineHeight: 1.5
|
||||||
|
small:
|
||||||
|
fontFamily: Public Sans Variable
|
||||||
|
fontSize: 14px
|
||||||
|
fontWeight: "400"
|
||||||
|
lineHeight: 1.357
|
||||||
|
caption:
|
||||||
|
fontFamily: Public Sans Variable
|
||||||
|
fontSize: 12px
|
||||||
|
fontWeight: "400"
|
||||||
|
lineHeight: 1.5
|
||||||
|
rounded:
|
||||||
|
sm: 4px
|
||||||
|
DEFAULT: 6px
|
||||||
|
lg: 10px
|
||||||
|
xl: 16px
|
||||||
|
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.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
The system uses **Public Sans Variable** (loaded via `@fontsource-variable/public-sans`). The type scale runs from `caption` (12px) to `h1` (48px). Headings use weight 600–700; body text uses 400.
|
||||||
|
|
||||||
|
Use Tailwind's `text-{scale}` utilities: `text-h1`, `text-h2`, `text-h3`, `text-h4`, `text-h5`, `text-h6`, `text-intro`, `text-body`, `text-small`, `text-caption`.
|
||||||
|
|
||||||
|
## Layout & Spacing
|
||||||
|
|
||||||
|
Spacing follows Tailwind's default 4px base scale. Common values:
|
||||||
|
- `p-2` (8px), `p-3` (12px), `p-4` (16px), `p-6` (24px) for padding
|
||||||
|
- `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.
|
||||||
|
|
||||||
|
## Elevation & Depth
|
||||||
|
|
||||||
|
Two shadow levels:
|
||||||
|
- `shadow-default` — subtle lift for cards and surfaces (`0 1px 3px rgba(0,0,0,0.08)`)
|
||||||
|
- `shadow-md` — elevated elements like dropdowns and dialogs (`0 4px 6px -1px rgba(0,0,0,0.1)`)
|
||||||
|
|
||||||
|
## Shapes
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### Button
|
||||||
|
|
||||||
|
Interactive button with variant/intent/size matrix.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `variant` | `'primary' \| 'secondary' \| 'tertiary'` | `'primary'` | Visual weight |
|
||||||
|
| `intent` | `'default' \| 'danger' \| 'subtle' \| 'neutral'` | `'default'` | Semantic purpose |
|
||||||
|
| `size` | `'default' \| 'comfortable' \| 'compact'` | `'default'` | Height and padding |
|
||||||
|
| `loading` | `boolean` | `false` | Shows spinner, disables interaction |
|
||||||
|
| `leftIcon` | `ReactNode` | — | Icon before label |
|
||||||
|
| `rightIcon` | `ReactNode` | — | Icon after label |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Button } from '@/components/atoms/Button'
|
||||||
|
|
||||||
|
<Button variant="primary" intent="default">Save</Button>
|
||||||
|
<Button variant="secondary" intent="danger" leftIcon={<Trash2 />}>Delete</Button>
|
||||||
|
<Button variant="tertiary" size="compact">Cancel</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### IconButton
|
||||||
|
|
||||||
|
Icon-only button with required `aria-label`.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `variant` | `'primary' \| 'secondary' \| 'tertiary'` | `'primary'` | Visual weight |
|
||||||
|
| `intent` | `'default' \| 'danger' \| 'neutral'` | `'default'` | Semantic purpose |
|
||||||
|
| `size` | `'default' \| 'large' \| 'compact' \| 'small' \| 'xsmall'` | `'default'` | Button dimensions |
|
||||||
|
| `shape` | `'circle' \| 'square'` | `'circle'` | Border radius |
|
||||||
|
| `icon` | `ReactNode` | — | **Required.** The icon element |
|
||||||
|
| `aria-label` | `string` | — | **Required.** Accessible label |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { IconButton } from '@/components/atoms/IconButton'
|
||||||
|
import { X, Settings } from 'lucide-react'
|
||||||
|
|
||||||
|
<IconButton icon={<X />} aria-label="Close" variant="tertiary" />
|
||||||
|
<IconButton icon={<Settings />} aria-label="Settings" shape="square" size="compact" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Input
|
||||||
|
|
||||||
|
Text input with label, description, hint, error, and icon slots.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `label` | `string` | — | **Required.** Visible label |
|
||||||
|
| `description` | `string` | — | Help text below label |
|
||||||
|
| `hint` | `string` | — | Inline hint (right-aligned) |
|
||||||
|
| `error` | `string` | — | Error message (replaces description) |
|
||||||
|
| `variant` | `'outlined' \| 'stacked'` | `'outlined'` | Label position |
|
||||||
|
| `size` | `'default' \| 'compact'` | `'default'` | Input height |
|
||||||
|
| `leftIcon` | `ReactNode` | — | Icon inside input (left) |
|
||||||
|
| `rightIcon` | `ReactNode` | — | Icon inside input (right) |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Input } from '@/components/atoms/Input'
|
||||||
|
import { Search, Mail } from 'lucide-react'
|
||||||
|
|
||||||
|
<Input label="Email" type="email" placeholder="you@example.com" leftIcon={<Mail />} />
|
||||||
|
<Input label="Search" variant="stacked" size="compact" leftIcon={<Search />} />
|
||||||
|
<Input label="Name" error="Name is required" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Textarea
|
||||||
|
|
||||||
|
Multi-line text input with optional auto-resize.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `label` | `string` | — | **Required.** Visible label |
|
||||||
|
| `description` | `string` | — | Help text below label |
|
||||||
|
| `hint` | `string` | — | Inline hint |
|
||||||
|
| `error` | `string` | — | Error message |
|
||||||
|
| `variant` | `'outlined' \| 'stacked'` | `'outlined'` | Label position |
|
||||||
|
| `resize` | `'vertical' \| 'horizontal' \| 'both' \| 'none'` | `'vertical'` | Resize handle |
|
||||||
|
| `autoResize` | `boolean` | `false` | Auto-grow with content |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Textarea } from '@/components/atoms/Textarea'
|
||||||
|
|
||||||
|
<Textarea label="Notes" placeholder="Add your notes..." rows={4} />
|
||||||
|
<Textarea label="Description" autoResize error="Too short" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Select
|
||||||
|
|
||||||
|
Custom dropdown select with keyboard navigation.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `label` | `string` | — | **Required.** Visible label |
|
||||||
|
| `description` | `string` | — | Help text |
|
||||||
|
| `hint` | `string` | — | Inline hint |
|
||||||
|
| `error` | `string` | — | Error message |
|
||||||
|
| `variant` | `'outlined' \| 'stacked'` | `'outlined'` | Label position |
|
||||||
|
| `placeholder` | `string` | — | Placeholder text |
|
||||||
|
| `options` | `SelectOption[]` | — | **Required.** `{ value, label, disabled? }` |
|
||||||
|
| `value` | `string` | — | Controlled value |
|
||||||
|
| `defaultValue` | `string` | — | Uncontrolled default |
|
||||||
|
| `onChange` | `(value: string) => void` | — | Change handler |
|
||||||
|
| `disabled` | `boolean` | `false` | Disables the select |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Select } from '@/components/atoms/Select'
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label="Status"
|
||||||
|
options={[
|
||||||
|
{ value: 'active', label: 'Active' },
|
||||||
|
{ value: 'archived', label: 'Archived' },
|
||||||
|
{ value: 'draft', label: 'Draft' },
|
||||||
|
]}
|
||||||
|
onChange={(val) => console.log(val)}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Checkbox
|
||||||
|
|
||||||
|
Checkbox with label, description, error, and indeterminate state.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `label` | `string` | — | Visible label |
|
||||||
|
| `description` | `string` | — | Help text below label |
|
||||||
|
| `error` | `string` | — | Error message |
|
||||||
|
| `indeterminate` | `boolean` | `false` | Shows minus icon |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Checkbox } from '@/components/atoms/Checkbox'
|
||||||
|
|
||||||
|
<Checkbox label="I agree to the terms" />
|
||||||
|
<Checkbox label="Select all" indeterminate description="Some items are selected" />
|
||||||
|
<Checkbox label="Required" error="You must agree" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Radio / RadioGroup
|
||||||
|
|
||||||
|
Radio buttons grouped with shared state.
|
||||||
|
|
||||||
|
| Prop (RadioGroup) | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `label` | `string` | — | Group label |
|
||||||
|
| `description` | `string` | — | Group help text |
|
||||||
|
| `error` | `string` | — | Error message |
|
||||||
|
| `value` | `string` | — | Controlled value |
|
||||||
|
| `defaultValue` | `string` | — | Uncontrolled default |
|
||||||
|
| `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Layout direction |
|
||||||
|
| `onChange` | `(value: string) => void` | — | Change handler |
|
||||||
|
|
||||||
|
| Prop (Radio) | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `label` | `string` | — | Radio label |
|
||||||
|
| `value` | `string` | — | **Required.** Radio value |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { RadioGroup, Radio } from '@/components/atoms/Radio'
|
||||||
|
|
||||||
|
<RadioGroup label="Priority" orientation="horizontal" onChange={setPriority}>
|
||||||
|
<Radio value="low" label="Low" />
|
||||||
|
<Radio value="medium" label="Medium" />
|
||||||
|
<Radio value="high" label="High" />
|
||||||
|
</RadioGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Switch
|
||||||
|
|
||||||
|
Toggle switch with label and description.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `label` | `string` | — | Visible label |
|
||||||
|
| `description` | `string` | — | Help text |
|
||||||
|
| `checked` | `boolean` | `false` | Controlled state |
|
||||||
|
| `onChange` | `(checked: boolean) => void` | — | Change handler |
|
||||||
|
| `disabled` | `boolean` | `false` | Disables the switch |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Switch } from '@/components/atoms/Switch'
|
||||||
|
|
||||||
|
<Switch label="Dark mode" checked={dark} onChange={setDark} />
|
||||||
|
<Switch label="Notifications" description="Receive email alerts" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Badge
|
||||||
|
|
||||||
|
Small status indicator with colour variants.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `variant` | `'navy' \| 'info' \| 'info-light' \| 'success' \| 'success-light' \| 'error' \| 'error-light' \| 'warning' \| 'warning-light' \| 'neutral' \| 'white'` | `'navy'` | Colour variant |
|
||||||
|
| `leftIcon` | `ReactNode` | — | Icon before text |
|
||||||
|
| `rightIcon` | `ReactNode` | — | Icon after text |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Badge } from '@/components/atoms/Badge'
|
||||||
|
|
||||||
|
<Badge variant="success">Active</Badge>
|
||||||
|
<Badge variant="error-light">Overdue</Badge>
|
||||||
|
<Badge variant="info" leftIcon={<Clock />}>Pending</Badge>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Chip
|
||||||
|
|
||||||
|
Selectable/dismissible filter chip.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `children` | `ReactNode` | — | **Required.** Chip label |
|
||||||
|
| `selected` | `boolean` | `false` | Selected state |
|
||||||
|
| `onDismiss` | `() => void` | — | Shows dismiss icon, makes removable |
|
||||||
|
| `rightIcon` | `ReactNode` | — | Custom right icon |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Chip } from '@/components/atoms/Chip'
|
||||||
|
|
||||||
|
<Chip selected={isActive} onClick={toggle}>Qualitative</Chip>
|
||||||
|
<Chip onDismiss={() => removeFilter('date')}>2024</Chip>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tag
|
||||||
|
|
||||||
|
Coloured label for categorisation.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `variant` | `'outline' \| 'filled' \| 'light'` | `'outline'` | Visual style |
|
||||||
|
| `color` | `'navy' \| 'blue' \| 'green' \| 'red' \| 'orange' \| 'grey'` | `'navy'` | Colour |
|
||||||
|
| `size` | `'default' \| 'sm'` | `'default'` | Tag size |
|
||||||
|
| `icon` | `ReactNode` | — | Leading icon |
|
||||||
|
| `onRemove` | `() => void` | — | Shows remove button |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Tag } from '@/components/atoms/Tag'
|
||||||
|
|
||||||
|
<Tag color="blue" variant="light">Research</Tag>
|
||||||
|
<Tag color="green" variant="filled" onRemove={handleRemove}>Complete</Tag>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tooltip
|
||||||
|
|
||||||
|
Floating tooltip on hover/focus with arrow.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `content` | `ReactNode` | — | **Required.** Tooltip content |
|
||||||
|
| `placement` | Floating UI `Placement` | `'top'` | Position relative to trigger |
|
||||||
|
| `delay` | `number \| { open?, close? }` | `{ open: 400, close: 0 }` | Show/hide delay (ms) |
|
||||||
|
| `children` | `ReactElement` | — | **Required.** Trigger element |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Tooltip } from '@/components/atoms/Tooltip'
|
||||||
|
|
||||||
|
<Tooltip content="Save your changes">
|
||||||
|
<Button>Save</Button>
|
||||||
|
</Tooltip>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Card
|
||||||
|
|
||||||
|
Container with variant styles. Composed of `Card`, `CardHeader`, `CardContent`, `CardFooter`.
|
||||||
|
|
||||||
|
| Prop (Card) | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `variant` | `'surface' \| 'outlined' \| 'elevated' \| 'filled'` | `'surface'` | Visual style |
|
||||||
|
|
||||||
|
| Prop (CardHeader) | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `action` | `ReactNode` | — | Action element (top-right) |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Card, CardHeader, CardContent, CardFooter } from '@/components/molecules/Card'
|
||||||
|
|
||||||
|
<Card variant="elevated">
|
||||||
|
<CardHeader action={<IconButton icon={<MoreHorizontal />} aria-label="Options" variant="tertiary" />}>
|
||||||
|
<h3>Card Title</h3>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p>Card body content goes here.</p>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button variant="tertiary" size="compact">View details</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accordion
|
||||||
|
|
||||||
|
Collapsible content sections. Composed of `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent`.
|
||||||
|
|
||||||
|
| Prop (Accordion) | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `type` | `'single' \| 'multiple'` | `'single'` | One or many open at once |
|
||||||
|
| `collapsible` | `boolean` | `false` | Allow closing all (single mode) |
|
||||||
|
| `defaultValue` | `string \| string[]` | — | Initially open item(s) |
|
||||||
|
| `value` | `string \| string[]` | — | Controlled open state |
|
||||||
|
| `onValueChange` | `(value) => void` | — | Change handler |
|
||||||
|
|
||||||
|
| Prop (AccordionItem) | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `value` | `string` | — | **Required.** Unique item identifier |
|
||||||
|
| `disabled` | `boolean` | `false` | Prevents opening |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/molecules/Accordion'
|
||||||
|
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value="section-1">
|
||||||
|
<AccordionTrigger>What is this?</AccordionTrigger>
|
||||||
|
<AccordionContent>Explanation here.</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem value="section-2">
|
||||||
|
<AccordionTrigger>How does it work?</AccordionTrigger>
|
||||||
|
<AccordionContent>Details here.</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alert
|
||||||
|
|
||||||
|
Contextual message with icon, title, close button, and action slot.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `variant` | `'info' \| 'warning' \| 'error' \| 'success' \| 'neutral'` | `'info'` | Alert type and colour |
|
||||||
|
| `title` | `string` | — | Bold heading |
|
||||||
|
| `onClose` | `() => void` | — | Shows close button |
|
||||||
|
| `action` | `ReactNode` | — | Action element (e.g. link or button) |
|
||||||
|
| `icon` | `ReactNode` | — | Custom icon (default based on variant) |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Alert } from '@/components/molecules/Alert'
|
||||||
|
|
||||||
|
<Alert variant="success" title="Saved" onClose={dismiss}>
|
||||||
|
Your changes have been saved successfully.
|
||||||
|
</Alert>
|
||||||
|
<Alert variant="error" title="Error">Something went wrong.</Alert>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dialog
|
||||||
|
|
||||||
|
Modal dialog with backdrop, size options, and composed sections.
|
||||||
|
|
||||||
|
| Prop (Dialog) | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `open` | `boolean` | — | **Required.** Controls visibility |
|
||||||
|
| `onClose` | `() => void` | — | **Required.** Close handler |
|
||||||
|
| `size` | `'sm' \| 'default' \| 'lg' \| 'full'` | `'default'` | Max width |
|
||||||
|
| `closeOnBackdrop` | `boolean` | `true` | Close when clicking backdrop |
|
||||||
|
|
||||||
|
Composed with `DialogHeader`, `DialogContent`, `DialogFooter`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Dialog, DialogHeader, DialogContent, DialogFooter } from '@/components/molecules/Dialog'
|
||||||
|
|
||||||
|
<Dialog open={isOpen} onClose={() => setIsOpen(false)} size="sm">
|
||||||
|
<DialogHeader onClose={() => setIsOpen(false)}>Confirm</DialogHeader>
|
||||||
|
<DialogContent>Are you sure you want to delete this?</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="tertiary" onClick={() => setIsOpen(false)}>Cancel</Button>
|
||||||
|
<Button intent="danger">Delete</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Popover
|
||||||
|
|
||||||
|
Floating content panel triggered by a child element. Composed of `Popover`, `PopoverTrigger`, `PopoverContent`.
|
||||||
|
|
||||||
|
| Prop (Popover) | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `placement` | Floating UI `Placement` | `'bottom'` | Position relative to trigger |
|
||||||
|
| `open` | `boolean` | — | Controlled open state |
|
||||||
|
| `onOpenChange` | `(open: boolean) => void` | — | Open state handler |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Popover, PopoverTrigger, PopoverContent } from '@/components/molecules/Popover'
|
||||||
|
|
||||||
|
<Popover placement="bottom-start">
|
||||||
|
<PopoverTrigger>
|
||||||
|
<Button variant="secondary">Options</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<p>Popover content here.</p>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Do's and Don'ts
|
||||||
|
|
||||||
|
**Do:**
|
||||||
|
- Use semantic token classes (`bg-primary`, `text-error`, `border-control-border`), never palette tokens (`bg-blue-01`)
|
||||||
|
- Use the `cn()` utility from `@/lib/utils` for conditional classes
|
||||||
|
- Use `forwardRef` for components wrapping native elements
|
||||||
|
- Use semantic HTML (`<button>`, `<input>`, `<dialog>`) — not `<div onClick>`
|
||||||
|
- Include `aria-label` on icon-only buttons
|
||||||
|
- Use `lucide-react` for icons (already a dev dependency)
|
||||||
|
|
||||||
|
**Don't:**
|
||||||
|
- Hardcode colour hex values in component code
|
||||||
|
- Use inline styles (except for truly dynamic values like calculated positions)
|
||||||
|
- Use CSS modules or styled-components
|
||||||
|
- Skip the `label` prop on form controls — all inputs must have visible labels
|
||||||
|
- Nest interactive elements (button inside button, link inside button)
|
||||||
91
README.md
91
README.md
@@ -1,73 +1,32 @@
|
|||||||
# React + TypeScript + Vite
|
# ADS 3.0 Design System
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
A React component library implementing the ADS 3.0 (Adaptive Design System) design language. Built with React 19, TypeScript, Tailwind CSS v4, and Storybook 10.
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
## Getting Started
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
```bash
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
npm install
|
||||||
|
npm run storybook # Component development at localhost:6006
|
||||||
## React Compiler
|
npm run dev # Vite dev server
|
||||||
|
|
||||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
|
||||||
|
|
||||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
|
|
||||||
// Remove tseslint.configs.recommended and replace with this
|
|
||||||
tseslint.configs.recommendedTypeChecked,
|
|
||||||
// Alternatively, use this for stricter rules
|
|
||||||
tseslint.configs.strictTypeChecked,
|
|
||||||
// Optionally, add this for stylistic rules
|
|
||||||
tseslint.configs.stylisticTypeChecked,
|
|
||||||
|
|
||||||
// Other configs...
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
## Architecture
|
||||||
|
|
||||||
```js
|
- **Tokens** — Design tokens in `src/tokens/tokens.css` as a Tailwind v4 `@theme` block
|
||||||
// eslint.config.js
|
- **Atoms** — Single-purpose elements (Button, Input, Badge, etc.)
|
||||||
import reactX from 'eslint-plugin-react-x'
|
- **Molecules** — Small compositions (Alert, Dialog, Card, Accordion)
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
- **Organisms** — Page-level regions (AppShell, TabBar)
|
||||||
|
|
||||||
export default defineConfig([
|
## Usage as a Base
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
This repo is designed to be forked for specific applications. Fork it, then build your application screens and domain logic on top of the shared component set.
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
## Tech Stack
|
||||||
// Other configs...
|
|
||||||
// Enable lint rules for React
|
| Tool | Purpose |
|
||||||
reactX.configs['recommended-typescript'],
|
|------|---------|
|
||||||
// Enable lint rules for React DOM
|
| React 19 | UI framework |
|
||||||
reactDom.configs.recommended,
|
| TypeScript (strict) | Type safety |
|
||||||
],
|
| Tailwind CSS v4 | Utility-first styling via CSS-first config |
|
||||||
languageOptions: {
|
| Storybook 10 | Component development and documentation |
|
||||||
parserOptions: {
|
| Vite | Build tooling |
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>sdc-frontend</title>
|
<title>ADS 3.0 Design System</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "sdc-frontend",
|
"name": "ads3-design-system",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-bg text-text">
|
<div className="min-h-screen bg-bg text-text">
|
||||||
<header className="bg-surface border-b border-border px-6 py-3">
|
<header className="bg-surface border-b border-border px-6 py-3">
|
||||||
<h1 className="text-lg font-semibold">SDC Design System</h1>
|
<h1 className="text-lg font-semibold">ADS 3.0 Design System</h1>
|
||||||
</header>
|
</header>
|
||||||
<main className="p-6">
|
<main className="p-6">
|
||||||
<p className="text-text-secondary">
|
<p className="text-text-secondary">
|
||||||
Component library for the Research Synthesiser.
|
React component library implementing the ADS 3.0 design language.
|
||||||
</p>
|
</p>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ export const Default: Story = {
|
|||||||
<AccordionItem value="item-1">
|
<AccordionItem value="item-1">
|
||||||
<AccordionTrigger>What is this design system?</AccordionTrigger>
|
<AccordionTrigger>What is this design system?</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
A React component library built for the Research Synthesiser, following NSW Design System
|
A React component library implementing the ADS 3.0 design language with custom tokens
|
||||||
patterns with custom tokens and Tailwind CSS v4.
|
and Tailwind CSS v4.
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem value="item-2">
|
<AccordionItem value="item-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user