Genericise template stories, fix TopBar icons, and add DetailPage
Replace domain-specific (education/PDP) recipe stories with generic content. Fix TopBar action buttons using properly styled light-on-dark buttons instead of invisible IconButton tertiary. Use the real NSW waratah SVG logo. Add shared _story-helpers for TopBar actions and logo. Add DetailPage template for single-record/profile/document views with constrained width and tab support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,7 @@ Page-level layout components that define the shell and content structure. Templa
|
|||||||
- **DashboardPage** — PageHeader + stat cards row + responsive 2-column content grid
|
- **DashboardPage** — PageHeader + stat cards row + responsive 2-column content grid
|
||||||
- **ListPage** — PageHeader + stat cards + list header with actions + scrollable item list
|
- **ListPage** — PageHeader + stat cards + list header with actions + scrollable item list
|
||||||
- **FormPage** — PageHeader + optional action bar + optional vertical stepper + constrained-width form content
|
- **FormPage** — PageHeader + optional action bar + optional vertical stepper + constrained-width form content
|
||||||
|
- **DetailPage** — PageHeader + optional action bar (e.g. tabs) + single-column constrained content for viewing records/profiles/documents
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
23
DESIGN.md
23
DESIGN.md
@@ -1069,6 +1069,29 @@ import { FormPage } from '@/components/templates/FormPage'
|
|||||||
</FormPage>
|
</FormPage>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### DetailPage
|
||||||
|
|
||||||
|
Single-column detail view for records, profiles, or documents. Constrained width for readability.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `header` | `ReactNode` | — | PageHeader or custom header |
|
||||||
|
| `actions` | `ReactNode` | — | Action bar below header (e.g. tabs, buttons) |
|
||||||
|
| `maxWidth` | `'md' \| 'lg' \| 'xl' \| 'full'` | `'xl'` | Content area max width |
|
||||||
|
| `children` | `ReactNode` | — | **Required.** Page content |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DetailPage } from '@/components/templates/DetailPage'
|
||||||
|
|
||||||
|
<DetailPage
|
||||||
|
header={<PageHeader title="Alex Chen" subtitle="Senior Engineer" />}
|
||||||
|
actions={<Tabs value={tab} onChange={setTab}><TabList><Tab value="overview">Overview</Tab></TabList></Tabs>}
|
||||||
|
maxWidth="lg"
|
||||||
|
>
|
||||||
|
<Card><CardContent>Detail content</CardContent></Card>
|
||||||
|
</DetailPage>
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Do's and Don'ts
|
## Do's and Don'ts
|
||||||
|
|||||||
@@ -4,14 +4,10 @@ import { AppShell } from './AppShell'
|
|||||||
import { TopBar } from '@/components/organisms/TopBar/TopBar'
|
import { TopBar } from '@/components/organisms/TopBar/TopBar'
|
||||||
import { SideNav, SideNavItem, SideNavGroup, SideNavDivider } from '@/components/organisms/SideNav/SideNav'
|
import { SideNav, SideNavItem, SideNavGroup, SideNavDivider } from '@/components/organisms/SideNav/SideNav'
|
||||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||||
import { IconButton } from '@/components/atoms/IconButton/IconButton'
|
|
||||||
import { PageHeader } from '@/components/organisms/PageHeader/PageHeader'
|
import { PageHeader } from '@/components/organisms/PageHeader/PageHeader'
|
||||||
|
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||||
import { Menu, Search, Bell, Home, FileText, LayoutGrid, Settings, Users, Link } from 'lucide-react'
|
import { Menu, Search, Bell, Home, FileText, LayoutGrid, Settings, Users, Link } from 'lucide-react'
|
||||||
|
|
||||||
const NswLogo = () => (
|
|
||||||
<div className="flex size-7 items-center justify-center rounded bg-white/20 text-caption font-bold text-white">NSW</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const meta: Meta<typeof AppShell> = {
|
const meta: Meta<typeof AppShell> = {
|
||||||
title: 'Templates/AppShell',
|
title: 'Templates/AppShell',
|
||||||
component: AppShell,
|
component: AppShell,
|
||||||
@@ -29,44 +25,39 @@ export default meta
|
|||||||
|
|
||||||
type Story = StoryObj<typeof AppShell>
|
type Story = StoryObj<typeof AppShell>
|
||||||
|
|
||||||
const SampleTopBar = ({ onMenuClick }: { onMenuClick?: () => void }) => (
|
|
||||||
<TopBar
|
|
||||||
title="My Application"
|
|
||||||
leading={<IconButton icon={<Menu />} aria-label="Toggle menu" variant="tertiary" onClick={onMenuClick} />}
|
|
||||||
logo={<NswLogo />}
|
|
||||||
>
|
|
||||||
<IconButton icon={<Search />} aria-label="Search" variant="tertiary" />
|
|
||||||
<IconButton icon={<Bell />} aria-label="Notifications" variant="tertiary" />
|
|
||||||
<Avatar initials="MM" size="sm" />
|
|
||||||
</TopBar>
|
|
||||||
)
|
|
||||||
|
|
||||||
const SampleSideNav = ({ collapsed }: { collapsed: boolean }) => (
|
|
||||||
<SideNav collapsed={collapsed}>
|
|
||||||
<SideNavItem icon={<Home />} active>My status</SideNavItem>
|
|
||||||
<SideNavItem icon={<FileText />}>My details</SideNavItem>
|
|
||||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
|
||||||
<SideNavDivider />
|
|
||||||
<SideNavGroup icon={<Users />} label="PDP" defaultOpen>
|
|
||||||
<SideNavItem>My PDP</SideNavItem>
|
|
||||||
<SideNavItem>PDP guide</SideNavItem>
|
|
||||||
<SideNavItem>Management</SideNavItem>
|
|
||||||
</SideNavGroup>
|
|
||||||
<SideNavItem icon={<Link />}>Resources</SideNavItem>
|
|
||||||
<SideNavItem icon={<Settings />}>Settings</SideNavItem>
|
|
||||||
</SideNav>
|
|
||||||
)
|
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
render: () => {
|
render: () => {
|
||||||
const [collapsed, setCollapsed] = useState(false)
|
const [collapsed, setCollapsed] = useState(false)
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
topBar={<SampleTopBar onMenuClick={() => setCollapsed(!collapsed)} />}
|
topBar={
|
||||||
sideNav={<SampleSideNav collapsed={collapsed} />}
|
<TopBar
|
||||||
|
title="My Application"
|
||||||
|
leading={<TopBarAction icon={<Menu />} label="Toggle menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||||
|
logo={<NswLogo />}
|
||||||
|
>
|
||||||
|
<TopBarAction icon={<Search />} label="Search" />
|
||||||
|
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||||
|
<Avatar initials="AB" size="sm" />
|
||||||
|
</TopBar>
|
||||||
|
}
|
||||||
|
sideNav={
|
||||||
|
<SideNav collapsed={collapsed}>
|
||||||
|
<SideNavItem icon={<Home />} active>Home</SideNavItem>
|
||||||
|
<SideNavItem icon={<FileText />}>Documents</SideNavItem>
|
||||||
|
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||||
|
<SideNavDivider />
|
||||||
|
<SideNavGroup icon={<Users />} label="Team" defaultOpen>
|
||||||
|
<SideNavItem>Members</SideNavItem>
|
||||||
|
<SideNavItem>Roles</SideNavItem>
|
||||||
|
</SideNavGroup>
|
||||||
|
<SideNavItem icon={<Link />}>Resources</SideNavItem>
|
||||||
|
<SideNavItem icon={<Settings />}>Settings</SideNavItem>
|
||||||
|
</SideNav>
|
||||||
|
}
|
||||||
sideNavCollapsed={collapsed}
|
sideNavCollapsed={collapsed}
|
||||||
>
|
>
|
||||||
<PageHeader title="Dashboard" subtitle="Welcome back, Myra McKay" />
|
<PageHeader title="Dashboard" subtitle="Welcome back" />
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="rounded-lg border border-border bg-surface p-8 text-center text-text-secondary">
|
<div className="rounded-lg border border-border bg-surface p-8 text-center text-text-secondary">
|
||||||
Page content goes here
|
Page content goes here
|
||||||
@@ -80,8 +71,20 @@ export const Default: Story = {
|
|||||||
export const Collapsed: Story = {
|
export const Collapsed: Story = {
|
||||||
render: () => (
|
render: () => (
|
||||||
<AppShell
|
<AppShell
|
||||||
topBar={<SampleTopBar />}
|
topBar={
|
||||||
sideNav={<SampleSideNav collapsed />}
|
<TopBar title="My Application" leading={<TopBarAction icon={<Menu />} label="Menu" />} logo={<NswLogo />}>
|
||||||
|
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||||
|
<Avatar initials="AB" size="sm" />
|
||||||
|
</TopBar>
|
||||||
|
}
|
||||||
|
sideNav={
|
||||||
|
<SideNav collapsed>
|
||||||
|
<SideNavItem icon={<Home />} active>Home</SideNavItem>
|
||||||
|
<SideNavItem icon={<FileText />}>Documents</SideNavItem>
|
||||||
|
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||||
|
<SideNavItem icon={<Settings />}>Settings</SideNavItem>
|
||||||
|
</SideNav>
|
||||||
|
}
|
||||||
sideNavCollapsed
|
sideNavCollapsed
|
||||||
>
|
>
|
||||||
<PageHeader title="Dashboard" subtitle="SideNav collapsed to icon-only mode" />
|
<PageHeader title="Dashboard" subtitle="SideNav collapsed to icon-only mode" />
|
||||||
|
|||||||
@@ -9,12 +9,8 @@ import { Card, CardHeader, CardTitle, CardContent } from '@/components/molecules
|
|||||||
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/molecules/Accordion/Accordion'
|
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/molecules/Accordion/Accordion'
|
||||||
import { Badge } from '@/components/atoms/Badge/Badge'
|
import { Badge } from '@/components/atoms/Badge/Badge'
|
||||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||||
import { IconButton } from '@/components/atoms/IconButton/IconButton'
|
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||||
import { Menu, Bell, Home, FileText, LayoutGrid, Users, CheckCircle, Clock, Info } from 'lucide-react'
|
import { Menu, Bell, Home, FileText, LayoutGrid, Users, CheckCircle, Clock, BarChart3 } from 'lucide-react'
|
||||||
|
|
||||||
const NswLogo = () => (
|
|
||||||
<div className="flex size-7 items-center justify-center rounded bg-white/20 text-caption font-bold text-white">NSW</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const meta: Meta<typeof DashboardPage> = {
|
const meta: Meta<typeof DashboardPage> = {
|
||||||
title: 'Templates/DashboardPage',
|
title: 'Templates/DashboardPage',
|
||||||
@@ -33,68 +29,87 @@ export default meta
|
|||||||
|
|
||||||
type Story = StoryObj<typeof DashboardPage>
|
type Story = StoryObj<typeof DashboardPage>
|
||||||
|
|
||||||
export const ProfessionalPathway: Story = {
|
export const WithAppShell: Story = {
|
||||||
name: 'Professional Pathway Dashboard',
|
name: 'Full page',
|
||||||
render: () => {
|
render: () => {
|
||||||
const [collapsed, setCollapsed] = useState(false)
|
const [collapsed, setCollapsed] = useState(false)
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
topBar={
|
topBar={
|
||||||
<TopBar
|
<TopBar
|
||||||
title=""
|
title="Project Hub"
|
||||||
leading={<IconButton icon={<Menu />} aria-label="Menu" variant="tertiary" onClick={() => setCollapsed(!collapsed)} />}
|
leading={<TopBarAction icon={<Menu />} label="Menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||||
logo={<NswLogo />}
|
logo={<NswLogo />}
|
||||||
>
|
>
|
||||||
<IconButton icon={<Bell />} aria-label="Notifications" variant="tertiary" />
|
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||||
<Avatar initials="MM" size="sm" />
|
<Avatar initials="AB" size="sm" />
|
||||||
</TopBar>
|
</TopBar>
|
||||||
}
|
}
|
||||||
sideNav={
|
sideNav={
|
||||||
<SideNav collapsed={collapsed}>
|
<SideNav collapsed={collapsed}>
|
||||||
<SideNavItem icon={<Home />} active>My status</SideNavItem>
|
<SideNavItem icon={<Home />} active>Overview</SideNavItem>
|
||||||
<SideNavItem icon={<Users />}>My details</SideNavItem>
|
<SideNavItem icon={<Users />}>Team</SideNavItem>
|
||||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
<SideNavItem icon={<LayoutGrid />}>Projects</SideNavItem>
|
||||||
<SideNavItem icon={<FileText />}>Resources</SideNavItem>
|
<SideNavItem icon={<FileText />}>Reports</SideNavItem>
|
||||||
<SideNavDivider />
|
<SideNavDivider />
|
||||||
<SideNavItem icon={<Users />}>Accreditation</SideNavItem>
|
<SideNavItem icon={<BarChart3 />}>Analytics</SideNavItem>
|
||||||
</SideNav>
|
</SideNav>
|
||||||
}
|
}
|
||||||
sideNavCollapsed={collapsed}
|
sideNavCollapsed={collapsed}
|
||||||
>
|
>
|
||||||
<DashboardPage
|
<DashboardPage
|
||||||
header={
|
header={<PageHeader title="Overview" subtitle="Your workspace at a glance" theme="dark" />}
|
||||||
<PageHeader title="Myra McKay" subtitle="Accreditation Level: Proficient Teacher" theme="dark">
|
stats={
|
||||||
<div className="mt-2 text-small text-white/80">
|
<>
|
||||||
Maroubra Junction Public School
|
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||||
|
<CardContent className="flex items-center gap-4 p-5">
|
||||||
|
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><Clock size={20} /></div>
|
||||||
|
<div>
|
||||||
|
<p className="text-h3 font-bold text-text">142</p>
|
||||||
|
<p className="text-small text-text-secondary">Hours logged</p>
|
||||||
|
<p className="text-caption text-text-secondary">Target 200h</p>
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||||
|
<CardContent className="flex items-center gap-4 p-5">
|
||||||
|
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-success/10 text-success"><CheckCircle size={20} /></div>
|
||||||
|
<div>
|
||||||
|
<p className="text-h3 font-bold text-text">24</p>
|
||||||
|
<p className="text-small text-text-secondary">Tasks completed</p>
|
||||||
|
<p className="text-caption text-text-secondary">This month</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||||
|
<CardContent className="flex items-center gap-4 p-5">
|
||||||
|
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><BarChart3 size={20} /></div>
|
||||||
|
<div>
|
||||||
|
<p className="text-h3 font-bold text-text">8</p>
|
||||||
|
<p className="text-small text-text-secondary">Active projects</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Card variant="surface">
|
<Card variant="surface">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Steps to be taken</CardTitle>
|
<CardTitle>Pending actions</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
<Accordion type="single" collapsible>
|
<Accordion type="single" collapsible>
|
||||||
<AccordionItem value="s1">
|
<AccordionItem value="s1">
|
||||||
<AccordionTrigger>Ensure you have completed the minimum requirements of your teaching degree as stated by NESA.</AccordionTrigger>
|
<AccordionTrigger>Complete onboarding checklist</AccordionTrigger>
|
||||||
<AccordionContent>Details about teaching degree requirements.</AccordionContent>
|
<AccordionContent>Review and complete all required onboarding items.</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem value="s2">
|
<AccordionItem value="s2">
|
||||||
<AccordionTrigger>Apply for your Working With Children Check (WWCC).</AccordionTrigger>
|
<AccordionTrigger>Submit quarterly report</AccordionTrigger>
|
||||||
<AccordionContent>Information about WWCC application.</AccordionContent>
|
<AccordionContent>Your Q2 report is due by end of month.</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem value="s3">
|
<AccordionItem value="s3">
|
||||||
<AccordionTrigger>Create an eTAMS account and submit required documentation to NESA.</AccordionTrigger>
|
<AccordionTrigger>Review team permissions</AccordionTrigger>
|
||||||
<AccordionContent>Steps for eTAMS registration.</AccordionContent>
|
<AccordionContent>Audit team member access levels.</AccordionContent>
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem value="s4">
|
|
||||||
<AccordionTrigger>Pay your NESA fee.</AccordionTrigger>
|
|
||||||
<AccordionContent>Payment details.</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem value="s5">
|
|
||||||
<AccordionTrigger>Complete Mandatory Training.</AccordionTrigger>
|
|
||||||
<AccordionContent>Training module details.</AccordionContent>
|
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -103,29 +118,30 @@ export const ProfessionalPathway: Story = {
|
|||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<Card variant="surface">
|
<Card variant="surface">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Mandatory Training Reminders</CardTitle>
|
<CardTitle>Compliance status</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex items-center justify-between border-b border-border pb-3">
|
<div className="flex items-center justify-between border-b border-border pb-3">
|
||||||
<span className="text-body font-medium">Aboriginal Cultural Education</span>
|
<span className="text-body font-medium">Security training</span>
|
||||||
<Badge variant="success" leftIcon={<CheckCircle size={14} />}>Certified</Badge>
|
<Badge variant="success" leftIcon={<CheckCircle size={14} />}>Complete</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 flex items-center justify-between">
|
||||||
|
<span className="text-body font-medium">Data privacy certification</span>
|
||||||
|
<Badge variant="warning">Due soon</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-4 text-small text-text-secondary">
|
|
||||||
Please consult the Mandatory Training Hub for role specific training, or contact the MyPL Helpdesk for queries regarding training.
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card variant="elevated" className="bg-info/5">
|
<Card variant="elevated" className="bg-info/5">
|
||||||
<CardContent className="flex gap-4 p-5">
|
<CardContent className="flex gap-4 p-5">
|
||||||
<Avatar initials="MK" size="lg" />
|
<Avatar initials="HR" size="lg" />
|
||||||
<div className="text-small">
|
<div className="text-small">
|
||||||
<p className="font-medium text-text">
|
<p className="font-medium text-text">
|
||||||
Hi I am Martha. I got my conditional accreditation recently through NESA. These links really helped me through the process.
|
Welcome to the team! Here are some resources to help you get started.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-3 flex flex-col gap-1">
|
<div className="mt-3 flex flex-col gap-1">
|
||||||
<a href="#" className="text-info hover:underline">The resources that helped me</a>
|
<a href="#" className="text-info hover:underline">Getting started guide</a>
|
||||||
<a href="#" className="text-info hover:underline">FAQ (questions I had)</a>
|
<a href="#" className="text-info hover:underline">Frequently asked questions</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -138,7 +154,7 @@ export const ProfessionalPathway: Story = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Standalone: Story = {
|
export const Standalone: Story = {
|
||||||
name: 'Without AppShell',
|
name: 'Content only',
|
||||||
render: () => (
|
render: () => (
|
||||||
<DashboardPage
|
<DashboardPage
|
||||||
header={<PageHeader title="Dashboard" subtitle="Overview of your activity" />}
|
header={<PageHeader title="Dashboard" subtitle="Overview of your activity" />}
|
||||||
@@ -146,36 +162,19 @@ export const Standalone: Story = {
|
|||||||
<>
|
<>
|
||||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||||
<CardContent className="flex items-center gap-4 p-5">
|
<CardContent className="flex items-center gap-4 p-5">
|
||||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><Clock size={20} /></div>
|
||||||
<Clock size={20} />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-h3 font-bold text-text">21h</p>
|
<p className="text-h3 font-bold text-text">21h</p>
|
||||||
<p className="text-small text-text-secondary">Total hours logged</p>
|
<p className="text-small text-text-secondary">Hours logged</p>
|
||||||
<p className="text-caption text-text-secondary">Target 100h</p>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||||
<CardContent className="flex items-center gap-4 p-5">
|
<CardContent className="flex items-center gap-4 p-5">
|
||||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><BarChart3 size={20} /></div>
|
||||||
<Clock size={20} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-h3 font-bold text-text">18h</p>
|
|
||||||
<p className="text-small text-text-secondary">NESA Registered PD</p>
|
|
||||||
<p className="text-caption text-text-secondary">Target 60h</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
|
||||||
<CardContent className="flex items-center gap-4 p-5">
|
|
||||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
|
||||||
<Info size={20} />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-h3 font-bold text-text">5</p>
|
<p className="text-h3 font-bold text-text">5</p>
|
||||||
<p className="text-small text-text-secondary">Activities logged</p>
|
<p className="text-small text-text-secondary">Activities</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -183,10 +182,10 @@ export const Standalone: Story = {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Card variant="surface">
|
<Card variant="surface">
|
||||||
<CardContent className="p-8 text-center text-text-secondary">Left column content</CardContent>
|
<CardContent className="p-8 text-center text-text-secondary">Left column</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card variant="surface">
|
<Card variant="surface">
|
||||||
<CardContent className="p-8 text-center text-text-secondary">Right column content</CardContent>
|
<CardContent className="p-8 text-center text-text-secondary">Right column</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</DashboardPage>
|
</DashboardPage>
|
||||||
),
|
),
|
||||||
|
|||||||
163
src/components/templates/DetailPage/DetailPage.stories.tsx
Normal file
163
src/components/templates/DetailPage/DetailPage.stories.tsx
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { DetailPage } from './DetailPage'
|
||||||
|
import { AppShell } from '@/components/templates/AppShell/AppShell'
|
||||||
|
import { TopBar } from '@/components/organisms/TopBar/TopBar'
|
||||||
|
import { SideNav, SideNavItem, SideNavGroup, SideNavDivider } from '@/components/organisms/SideNav/SideNav'
|
||||||
|
import { PageHeader } from '@/components/organisms/PageHeader/PageHeader'
|
||||||
|
import { Card, CardHeader, CardTitle, CardContent } from '@/components/molecules/Card/Card'
|
||||||
|
import { Badge } from '@/components/atoms/Badge/Badge'
|
||||||
|
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||||
|
import { Button } from '@/components/atoms/Button/Button'
|
||||||
|
import { Tabs, TabList, Tab, TabPanel } from '@/components/atoms/Tabs/Tabs'
|
||||||
|
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||||
|
import { Menu, Bell, Home, FileText, LayoutGrid, Users, Settings, Mail, Phone, MapPin, Edit } from 'lucide-react'
|
||||||
|
|
||||||
|
const meta: Meta<typeof DetailPage> = {
|
||||||
|
title: 'Templates/DetailPage',
|
||||||
|
component: DetailPage,
|
||||||
|
tags: ['autodocs', 'template'],
|
||||||
|
parameters: {
|
||||||
|
layout: 'fullscreen',
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: 'Detail page template for viewing a single record, profile, or document. Single-column layout with constrained max-width for readability.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export default meta
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof DetailPage>
|
||||||
|
|
||||||
|
const InfoRow = ({ label, value, icon }: { label: string; value: string; icon: React.ReactNode }) => (
|
||||||
|
<div className="flex items-center gap-3 py-3">
|
||||||
|
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-info/10 text-info [&>svg]:size-4">{icon}</span>
|
||||||
|
<div>
|
||||||
|
<p className="text-caption text-text-secondary">{label}</p>
|
||||||
|
<p className="text-body text-text">{value}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ProfileView: Story = {
|
||||||
|
name: 'Profile detail',
|
||||||
|
render: () => {
|
||||||
|
const [collapsed, setCollapsed] = useState(false)
|
||||||
|
const [activeTab, setActiveTab] = useState('overview')
|
||||||
|
return (
|
||||||
|
<AppShell
|
||||||
|
topBar={
|
||||||
|
<TopBar
|
||||||
|
title="Team Directory"
|
||||||
|
leading={<TopBarAction icon={<Menu />} label="Menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||||
|
logo={<NswLogo />}
|
||||||
|
>
|
||||||
|
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||||
|
<Avatar initials="AB" size="sm" />
|
||||||
|
</TopBar>
|
||||||
|
}
|
||||||
|
sideNav={
|
||||||
|
<SideNav collapsed={collapsed}>
|
||||||
|
<SideNavItem icon={<Home />}>Home</SideNavItem>
|
||||||
|
<SideNavItem icon={<Users />} active>Team</SideNavItem>
|
||||||
|
<SideNavItem icon={<LayoutGrid />}>Projects</SideNavItem>
|
||||||
|
<SideNavItem icon={<FileText />}>Documents</SideNavItem>
|
||||||
|
<SideNavDivider />
|
||||||
|
<SideNavItem icon={<Settings />}>Settings</SideNavItem>
|
||||||
|
</SideNav>
|
||||||
|
}
|
||||||
|
sideNavCollapsed={collapsed}
|
||||||
|
>
|
||||||
|
<DetailPage
|
||||||
|
header={
|
||||||
|
<PageHeader title="Alex Chen" subtitle="Senior Engineer — Platform Team" theme="light">
|
||||||
|
<div className="mt-2 flex items-center gap-3">
|
||||||
|
<Badge variant="success">Active</Badge>
|
||||||
|
<Badge variant="info-light">Full-time</Badge>
|
||||||
|
</div>
|
||||||
|
</PageHeader>
|
||||||
|
}
|
||||||
|
actions={
|
||||||
|
<Tabs value={activeTab} onChange={setActiveTab}>
|
||||||
|
<TabList>
|
||||||
|
<Tab value="overview">Overview</Tab>
|
||||||
|
<Tab value="projects">Projects</Tab>
|
||||||
|
<Tab value="activity">Activity</Tab>
|
||||||
|
</TabList>
|
||||||
|
</Tabs>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Tabs value={activeTab} onChange={setActiveTab}>
|
||||||
|
<TabPanel value="overview">
|
||||||
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||||
|
<Card variant="surface" className="lg:col-span-2">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Contact information</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="divide-y divide-border">
|
||||||
|
<InfoRow icon={<Mail />} label="Email" value="alex.chen@example.com" />
|
||||||
|
<InfoRow icon={<Phone />} label="Phone" value="+61 2 9876 5432" />
|
||||||
|
<InfoRow icon={<MapPin />} label="Location" value="Sydney CBD, Level 12" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card variant="surface">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Quick actions</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-3">
|
||||||
|
<Button variant="secondary" leftIcon={<Mail size={18} />} className="w-full justify-start">Send email</Button>
|
||||||
|
<Button variant="secondary" leftIcon={<Edit size={18} />} className="w-full justify-start">Edit profile</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value="projects">
|
||||||
|
<Card variant="surface">
|
||||||
|
<CardContent className="p-8 text-center text-text-secondary">Project list content</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value="activity">
|
||||||
|
<Card variant="surface">
|
||||||
|
<CardContent className="p-8 text-center text-text-secondary">Activity feed content</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
</DetailPage>
|
||||||
|
</AppShell>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DocumentView: Story = {
|
||||||
|
name: 'Document detail',
|
||||||
|
render: () => (
|
||||||
|
<DetailPage
|
||||||
|
header={<PageHeader title="Project Brief" subtitle="Created 15 March 2024" noBackground />}
|
||||||
|
maxWidth="lg"
|
||||||
|
>
|
||||||
|
<Card variant="surface">
|
||||||
|
<CardContent className="space-y-4 p-6">
|
||||||
|
<h2 className="text-h3 font-bold text-text">Overview</h2>
|
||||||
|
<p className="text-body leading-relaxed text-text-secondary">
|
||||||
|
This document outlines the scope, objectives, and timeline for the upcoming platform migration project.
|
||||||
|
The project aims to consolidate three legacy systems into a single unified platform.
|
||||||
|
</p>
|
||||||
|
<h3 className="text-h4 font-bold text-text">Objectives</h3>
|
||||||
|
<ul className="list-disc space-y-2 pl-6 text-body text-text-secondary">
|
||||||
|
<li>Reduce operational costs by 40% through system consolidation</li>
|
||||||
|
<li>Improve data consistency across all business units</li>
|
||||||
|
<li>Provide a modern, accessible user interface</li>
|
||||||
|
<li>Enable real-time reporting and analytics</li>
|
||||||
|
</ul>
|
||||||
|
<h3 className="text-h4 font-bold text-text">Timeline</h3>
|
||||||
|
<p className="text-body leading-relaxed text-text-secondary">
|
||||||
|
The project is planned across three phases over 18 months, with the first phase targeting
|
||||||
|
core data migration and the second phase focusing on user-facing features.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</DetailPage>
|
||||||
|
),
|
||||||
|
}
|
||||||
43
src/components/templates/DetailPage/DetailPage.tsx
Normal file
43
src/components/templates/DetailPage/DetailPage.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { forwardRef, type HTMLAttributes, type ReactNode } from 'react'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
export interface DetailPageProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
|
/** PageHeader or custom header section */
|
||||||
|
header?: ReactNode
|
||||||
|
/** Action bar below the header (e.g. tabs, buttons) */
|
||||||
|
actions?: ReactNode
|
||||||
|
/** Single-column content area (max-w constrained for readability) */
|
||||||
|
children: ReactNode
|
||||||
|
/** Max width of the content area */
|
||||||
|
maxWidth?: 'md' | 'lg' | 'xl' | 'full'
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxWidthStyles = {
|
||||||
|
md: 'max-w-2xl',
|
||||||
|
lg: 'max-w-3xl',
|
||||||
|
xl: 'max-w-5xl',
|
||||||
|
full: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DetailPage = forwardRef<HTMLDivElement, DetailPageProps>(
|
||||||
|
({ header, actions, maxWidth = 'xl', className, children, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={cn('flex flex-col', className)} {...props}>
|
||||||
|
{header}
|
||||||
|
|
||||||
|
{actions && (
|
||||||
|
<div className="flex items-center gap-4 border-b border-border px-6 py-3">
|
||||||
|
{actions}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex-1 p-6">
|
||||||
|
<div className={cn(maxWidthStyles[maxWidth])}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DetailPage.displayName = 'DetailPage'
|
||||||
2
src/components/templates/DetailPage/index.ts
Normal file
2
src/components/templates/DetailPage/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { DetailPage } from './DetailPage'
|
||||||
|
export type { DetailPageProps } from './DetailPage'
|
||||||
@@ -12,12 +12,8 @@ import { Select } from '@/components/atoms/Select/Select'
|
|||||||
import { Button } from '@/components/atoms/Button/Button'
|
import { Button } from '@/components/atoms/Button/Button'
|
||||||
import { Badge } from '@/components/atoms/Badge/Badge'
|
import { Badge } from '@/components/atoms/Badge/Badge'
|
||||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||||
import { IconButton } from '@/components/atoms/IconButton/IconButton'
|
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||||
import { Menu, Bell, Home, FileText, LayoutGrid, Users, Link, ArrowRight } from 'lucide-react'
|
import { Menu, Bell, Home, FileText, LayoutGrid, Users, Link, ArrowRight, Settings } from 'lucide-react'
|
||||||
|
|
||||||
const NswLogo = () => (
|
|
||||||
<div className="flex size-7 items-center justify-center rounded bg-white/20 text-caption font-bold text-white">NSW</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const meta: Meta<typeof FormPage> = {
|
const meta: Meta<typeof FormPage> = {
|
||||||
title: 'Templates/FormPage',
|
title: 'Templates/FormPage',
|
||||||
@@ -36,35 +32,34 @@ export default meta
|
|||||||
|
|
||||||
type Story = StoryObj<typeof FormPage>
|
type Story = StoryObj<typeof FormPage>
|
||||||
|
|
||||||
export const PDPDetails: Story = {
|
export const WithStepper: Story = {
|
||||||
name: 'PDP Form',
|
name: 'With stepper',
|
||||||
render: () => {
|
render: () => {
|
||||||
const [collapsed, setCollapsed] = useState(false)
|
const [collapsed, setCollapsed] = useState(false)
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
topBar={
|
topBar={
|
||||||
<TopBar
|
<TopBar
|
||||||
title="Performance and development plan"
|
title="Application Portal"
|
||||||
leading={<IconButton icon={<Menu />} aria-label="Menu" variant="tertiary" onClick={() => setCollapsed(!collapsed)} />}
|
leading={<TopBarAction icon={<Menu />} label="Menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||||
logo={<NswLogo />}
|
logo={<NswLogo />}
|
||||||
>
|
>
|
||||||
<IconButton icon={<Bell />} aria-label="Notifications" variant="tertiary" />
|
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||||
<Avatar initials="DW" size="sm" />
|
<Avatar initials="SR" size="sm" />
|
||||||
</TopBar>
|
</TopBar>
|
||||||
}
|
}
|
||||||
sideNav={
|
sideNav={
|
||||||
<SideNav collapsed={collapsed}>
|
<SideNav collapsed={collapsed}>
|
||||||
<SideNavItem icon={<Home />}>My status</SideNavItem>
|
<SideNavItem icon={<Home />}>Home</SideNavItem>
|
||||||
<SideNavItem icon={<Users />}>My details</SideNavItem>
|
<SideNavItem icon={<Users />}>Profile</SideNavItem>
|
||||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||||
<SideNavItem icon={<Link />}>Resources</SideNavItem>
|
<SideNavItem icon={<Link />}>Resources</SideNavItem>
|
||||||
<SideNavItem icon={<FileText />}>My documents & links</SideNavItem>
|
<SideNavItem icon={<FileText />}>Documents</SideNavItem>
|
||||||
<SideNavDivider />
|
<SideNavDivider />
|
||||||
<SideNavGroup icon={<FileText />} label="PDP" defaultOpen active>
|
<SideNavGroup icon={<FileText />} label="Applications" defaultOpen active>
|
||||||
<SideNavItem active>My PDP</SideNavItem>
|
<SideNavItem active>New application</SideNavItem>
|
||||||
<SideNavItem>PDP guide</SideNavItem>
|
<SideNavItem>Guidelines</SideNavItem>
|
||||||
<SideNavItem>Management</SideNavItem>
|
<SideNavItem>History</SideNavItem>
|
||||||
<SideNavItem>Useful links</SideNavItem>
|
|
||||||
<SideNavItem>Support</SideNavItem>
|
<SideNavItem>Support</SideNavItem>
|
||||||
</SideNavGroup>
|
</SideNavGroup>
|
||||||
</SideNav>
|
</SideNav>
|
||||||
@@ -73,10 +68,9 @@ export const PDPDetails: Story = {
|
|||||||
>
|
>
|
||||||
<FormPage
|
<FormPage
|
||||||
header={
|
header={
|
||||||
<PageHeader title="Siya Ram" subtitle="Role title goes here" theme="dark">
|
<PageHeader title="New Application" subtitle="Submit your application for review" theme="dark">
|
||||||
<div className="mt-2 flex items-center gap-4">
|
<div className="mt-2 flex items-center gap-4">
|
||||||
<Badge variant="warning">Plan - In progress</Badge>
|
<Badge variant="warning">In progress</Badge>
|
||||||
<span className="text-small text-white/80">Date commenced: dd-mm-yyyy</span>
|
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
}
|
}
|
||||||
@@ -85,77 +79,60 @@ export const PDPDetails: Story = {
|
|||||||
<Select
|
<Select
|
||||||
label=""
|
label=""
|
||||||
variant="stacked"
|
variant="stacked"
|
||||||
options={[{ value: '2026', label: '2026 - PDP Siya Ram' }]}
|
options={[{ value: '2026', label: '2026 — Application draft' }]}
|
||||||
defaultValue="2026"
|
defaultValue="2026"
|
||||||
/>
|
/>
|
||||||
<Button variant="secondary">More actions</Button>
|
<Button variant="secondary">More actions</Button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
steps={[
|
steps={[
|
||||||
{ label: 'Your PDP details', status: 'current' },
|
{ label: 'Your details', status: 'current' },
|
||||||
{ label: 'Create your PDP', status: 'upcoming' },
|
{ label: 'Supporting documents', status: 'upcoming' },
|
||||||
{ label: 'Notify your PDP supervisor', status: 'upcoming' },
|
{ label: 'Review & submit', status: 'upcoming' },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Card variant="surface">
|
<Card variant="surface">
|
||||||
<CardContent className="space-y-6 p-6">
|
<CardContent className="space-y-6 p-6">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-h3 font-bold text-text">Welcome to your Performance and Development Plan (PDP)</h2>
|
<h2 className="text-h3 font-bold text-text">Your details</h2>
|
||||||
<p className="mt-2 text-body text-text-secondary">
|
<p className="mt-2 text-body text-text-secondary">
|
||||||
Once your goals are drafted and you're ready to share them, you can notify your PDP supervisor. Head to the Digital PDP page on the intranet to find key resources to help you complete your PDP.
|
Provide the information below to begin your application. You can save and return at any time.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Alert variant="info" title="Your PDP details">
|
<Alert variant="info" title="Before you start">
|
||||||
Fill in the details below to get started with your PDP.
|
Make sure you have your identification documents and contact details ready.
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Select
|
<Input label="Full name" placeholder="Enter your full name" />
|
||||||
label="PDP year"
|
<Input label="Email address" type="email" placeholder="you@example.com" />
|
||||||
options={[
|
|
||||||
{ value: '2026', label: '2026' },
|
|
||||||
{ value: '2025', label: '2025' },
|
|
||||||
]}
|
|
||||||
defaultValue="2026"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-body font-semibold text-text">Middle leader role(s)</p>
|
<p className="text-body font-semibold text-text">Role information</p>
|
||||||
<p className="mb-2 text-small text-text-secondary">Some text about middle leader roles</p>
|
<p className="mb-2 text-small text-text-secondary">Select the role that best describes your position.</p>
|
||||||
<Select
|
<Select
|
||||||
label="Middle leader role type (optional)"
|
label="Role type"
|
||||||
options={[
|
options={[
|
||||||
{ value: 'deputy', label: 'Deputy Principal' },
|
{ value: 'manager', label: 'Manager' },
|
||||||
{ value: 'head', label: 'Head Teacher' },
|
{ value: 'coordinator', label: 'Coordinator' },
|
||||||
{ value: 'asst', label: 'Assistant Principal' },
|
{ value: 'specialist', label: 'Specialist' },
|
||||||
]}
|
]}
|
||||||
defaultValue="deputy"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-body font-semibold text-text">Add your PDP supervisor's details here</p>
|
<p className="text-body font-semibold text-text">Supervisor details</p>
|
||||||
<p className="mb-2 text-small text-text-secondary">
|
<p className="mb-2 text-small text-text-secondary">
|
||||||
Note: if your supervisor's name does not appear when you search for them, ask them to access the Digital PDP using their credentials, then try again.
|
Enter your supervisor's contact details for verification.
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Input label="PDP Supervisor's email" error="PDP Supervisor's email" defaultValue="dhoni.mahi@det.nsw.edu.au" />
|
<Input label="Supervisor email" type="email" placeholder="supervisor@example.com" />
|
||||||
<Input label="PDP Supervisor work location" error="PDP Supervisor work location" defaultValue="Work location goes here" />
|
<Input label="Supervisor location" placeholder="Office or site name" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<Input label="Work location" placeholder="Your primary work location" />
|
||||||
<p className="text-body font-semibold text-text">Add your school or work location.</p>
|
|
||||||
<p className="mb-2 text-small text-text-secondary">
|
|
||||||
If you don't work in a school, add 'Education Office' as your work location.
|
|
||||||
</p>
|
|
||||||
<Input label="Your school or work location" error="Your school or work location" defaultValue="Work location goes here" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-small text-text-secondary">
|
|
||||||
<strong>Note:</strong> As the school leader, your principal can view all the POPs in the school.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex justify-start pt-2">
|
<div className="flex justify-start pt-2">
|
||||||
<Button rightIcon={<ArrowRight size={18} />}>Proceed</Button>
|
<Button rightIcon={<ArrowRight size={18} />}>Proceed</Button>
|
||||||
@@ -170,7 +147,7 @@ export const PDPDetails: Story = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SimpleForm: Story = {
|
export const SimpleForm: Story = {
|
||||||
name: 'Simple Form (no stepper)',
|
name: 'Simple form (no stepper)',
|
||||||
render: () => (
|
render: () => (
|
||||||
<FormPage
|
<FormPage
|
||||||
header={<PageHeader title="Create Account" subtitle="Set up your profile to get started" />}
|
header={<PageHeader title="Create Account" subtitle="Set up your profile to get started" />}
|
||||||
@@ -182,8 +159,8 @@ export const SimpleForm: Story = {
|
|||||||
<Select
|
<Select
|
||||||
label="Role"
|
label="Role"
|
||||||
options={[
|
options={[
|
||||||
{ value: 'teacher', label: 'Teacher' },
|
{ value: 'viewer', label: 'Viewer' },
|
||||||
{ value: 'principal', label: 'Principal' },
|
{ value: 'editor', label: 'Editor' },
|
||||||
{ value: 'admin', label: 'Administrator' },
|
{ value: 'admin', label: 'Administrator' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -10,32 +10,24 @@ import { Badge } from '@/components/atoms/Badge/Badge'
|
|||||||
import { Tag } from '@/components/atoms/Tag/Tag'
|
import { Tag } from '@/components/atoms/Tag/Tag'
|
||||||
import { Button } from '@/components/atoms/Button/Button'
|
import { Button } from '@/components/atoms/Button/Button'
|
||||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||||
import { IconButton } from '@/components/atoms/IconButton/IconButton'
|
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||||
import { Menu, Bell, Home, LayoutGrid, FileText, Users, Clock, BarChart3, Plus, Check } from 'lucide-react'
|
import { Menu, Bell, Home, LayoutGrid, FileText, Users, Clock, BarChart3, Plus, Check } from 'lucide-react'
|
||||||
|
|
||||||
const NswLogo = () => (
|
const ListItem = ({ title, subtitle, status, date, tags }: { title: string; subtitle: string; status: string; date: string; tags: string[] }) => (
|
||||||
<div className="flex size-7 items-center justify-center rounded bg-white/20 text-caption font-bold text-white">NSW</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const ActivityItem = ({ title, hours, date }: { title: string; hours: string; date: string }) => (
|
|
||||||
<div className="flex flex-col gap-2 px-6 py-4">
|
<div className="flex flex-col gap-2 px-6 py-4">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<a href="#" className="text-body font-semibold text-info hover:underline">{title}</a>
|
<a href="#" className="text-body font-semibold text-info hover:underline">{title}</a>
|
||||||
<div className="flex shrink-0 flex-col items-end gap-1 text-small text-text-secondary">
|
<div className="flex shrink-0 flex-col items-end gap-1 text-small text-text-secondary">
|
||||||
<span>{hours}</span>
|
|
||||||
<span>{date}</span>
|
<span>{date}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<Tag color="navy" variant="filled" size="sm">NSW DoE</Tag>
|
<Badge variant="success" leftIcon={<Check size={14} />}>{status}</Badge>
|
||||||
<Badge variant="success" leftIcon={<Check size={14} />}>Registered</Badge>
|
{tags.map((tag) => (
|
||||||
<Tag color="blue" variant="filled" size="sm">S1</Tag>
|
<Tag key={tag} color="blue" variant="filled" size="sm">{tag}</Tag>
|
||||||
<Tag color="orange" variant="filled" size="sm">s4</Tag>
|
))}
|
||||||
<Tag color="green" variant="filled" size="sm">S6</Tag>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-small text-text-secondary">
|
<p className="text-small text-text-secondary">{subtitle}</p>
|
||||||
Lorem dolor sit amet, consectetur adipiscing elit. Donec condimentum nulla gravida pretium libero. Proin in felis consectetur, laoreet est eu, consectetur mi.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,74 +48,52 @@ export default meta
|
|||||||
|
|
||||||
type Story = StoryObj<typeof ListPage>
|
type Story = StoryObj<typeof ListPage>
|
||||||
|
|
||||||
export const PDLog: Story = {
|
export const WithAppShell: Story = {
|
||||||
name: 'PD Log',
|
name: 'Full page',
|
||||||
render: () => {
|
render: () => {
|
||||||
const [collapsed, setCollapsed] = useState(false)
|
const [collapsed, setCollapsed] = useState(false)
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
topBar={
|
topBar={
|
||||||
<TopBar
|
<TopBar
|
||||||
title=""
|
title="Activity Tracker"
|
||||||
leading={<IconButton icon={<Menu />} aria-label="Menu" variant="tertiary" onClick={() => setCollapsed(!collapsed)} />}
|
leading={<TopBarAction icon={<Menu />} label="Menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||||
logo={<NswLogo />}
|
logo={<NswLogo />}
|
||||||
>
|
>
|
||||||
<IconButton icon={<Bell />} aria-label="Notifications" variant="tertiary" />
|
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||||
<Avatar initials="JW" size="sm" />
|
<Avatar initials="JD" size="sm" />
|
||||||
</TopBar>
|
</TopBar>
|
||||||
}
|
}
|
||||||
sideNav={
|
sideNav={
|
||||||
<SideNav collapsed={collapsed}>
|
<SideNav collapsed={collapsed}>
|
||||||
<SideNavItem icon={<Home />} active>My status</SideNavItem>
|
<SideNavItem icon={<Home />} active>Home</SideNavItem>
|
||||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||||
<SideNavItem icon={<FileText />}>My details</SideNavItem>
|
<SideNavItem icon={<FileText />}>Reports</SideNavItem>
|
||||||
<SideNavDivider />
|
<SideNavDivider />
|
||||||
<SideNavItem icon={<Users />}>Accreditation</SideNavItem>
|
<SideNavItem icon={<Users />}>Team</SideNavItem>
|
||||||
</SideNav>
|
</SideNav>
|
||||||
}
|
}
|
||||||
sideNavCollapsed={collapsed}
|
sideNavCollapsed={collapsed}
|
||||||
>
|
>
|
||||||
<ListPage
|
<ListPage
|
||||||
header={
|
header={<PageHeader title="Activity Log" subtitle="Track and manage your recorded activities" theme="dark" />}
|
||||||
<PageHeader title="Jane Williamson's Workspace" subtitle="Accreditation Level: Maintaining Proficient Teacher" theme="dark">
|
|
||||||
<div className="mt-2 text-small text-white/80">
|
|
||||||
Maroubra Junction Public School
|
|
||||||
</div>
|
|
||||||
</PageHeader>
|
|
||||||
}
|
|
||||||
stats={
|
stats={
|
||||||
<>
|
<>
|
||||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||||
<CardContent className="flex items-center gap-4 p-5">
|
<CardContent className="flex items-center gap-4 p-5">
|
||||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><Clock size={20} /></div>
|
||||||
<Clock size={20} />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-h3 font-bold text-text">21h</p>
|
<p className="text-h3 font-bold text-text">64h</p>
|
||||||
<p className="text-small text-text-secondary">Total hours logged</p>
|
<p className="text-small text-text-secondary">Total hours</p>
|
||||||
<p className="text-caption text-text-secondary">Target 100h</p>
|
<p className="text-caption text-text-secondary">Target 100h</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||||
<CardContent className="flex items-center gap-4 p-5">
|
<CardContent className="flex items-center gap-4 p-5">
|
||||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><BarChart3 size={20} /></div>
|
||||||
<Clock size={20} />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-h3 font-bold text-text">18h</p>
|
<p className="text-h3 font-bold text-text">12</p>
|
||||||
<p className="text-small text-text-secondary">NESA Registered PD</p>
|
|
||||||
<p className="text-caption text-text-secondary">Target 60h</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
|
||||||
<CardContent className="flex items-center gap-4 p-5">
|
|
||||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
|
||||||
<BarChart3 size={20} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-h3 font-bold text-text">5</p>
|
|
||||||
<p className="text-small text-text-secondary">Activities logged</p>
|
<p className="text-small text-text-secondary">Activities logged</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -133,17 +103,17 @@ export const PDLog: Story = {
|
|||||||
listHeader={
|
listHeader={
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-h4 font-bold text-text">My PD Log</h2>
|
<h2 className="text-h4 font-bold text-text">Recent activities</h2>
|
||||||
<p className="text-small text-text-secondary">Log every professional learning activity — NESA Registered and school-based.</p>
|
<p className="text-small text-text-secondary">All recorded activities for the current period.</p>
|
||||||
</div>
|
</div>
|
||||||
<Button leftIcon={<Plus size={18} />}>Add Activity</Button>
|
<Button leftIcon={<Plus size={18} />}>Add Activity</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ActivityItem title="Trauma-informed practice" hours="8h" date="2024-02-20" />
|
<ListItem title="Quarterly compliance review" subtitle="Completed annual review of team compliance requirements and documentation." status="Approved" date="2024-03-15" tags={['Compliance', 'Q1']} />
|
||||||
<ActivityItem title="Trauma-informed practice" hours="8h" date="2024-02-20" />
|
<ListItem title="Team workshop: Data governance" subtitle="Facilitated a half-day workshop on data governance best practices." status="Approved" date="2024-03-10" tags={['Training', 'Data']} />
|
||||||
<ActivityItem title="Trauma-informed practice" hours="8h" date="2024-02-20" />
|
<ListItem title="System migration planning" subtitle="Documented migration plan for the legacy CRM to the new platform." status="Approved" date="2024-02-28" tags={['Infrastructure', 'Planning']} />
|
||||||
<ActivityItem title="Trauma-informed practice" hours="8h" date="2024-02-20" />
|
<ListItem title="New starter onboarding session" subtitle="Conducted orientation for three new team members joining the project." status="Approved" date="2024-02-20" tags={['Onboarding']} />
|
||||||
</ListPage>
|
</ListPage>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
)
|
)
|
||||||
@@ -151,10 +121,10 @@ export const PDLog: Story = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Standalone: Story = {
|
export const Standalone: Story = {
|
||||||
name: 'Without AppShell',
|
name: 'Content only',
|
||||||
render: () => (
|
render: () => (
|
||||||
<ListPage
|
<ListPage
|
||||||
header={<PageHeader title="Activity Log" subtitle="Your professional development activities" />}
|
header={<PageHeader title="Activity Log" subtitle="Your recent activities" />}
|
||||||
listHeader={
|
listHeader={
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-h4 font-bold text-text">Activities</h2>
|
<h2 className="text-h4 font-bold text-text">Activities</h2>
|
||||||
@@ -162,8 +132,8 @@ export const Standalone: Story = {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ActivityItem title="Sample activity" hours="4h" date="2024-03-15" />
|
<ListItem title="Sample activity" subtitle="A brief description of this activity." status="Complete" date="2024-03-15" tags={['Example']} />
|
||||||
<ActivityItem title="Another activity" hours="2h" date="2024-03-10" />
|
<ListItem title="Another activity" subtitle="Another brief description." status="Complete" date="2024-03-10" tags={['Example']} />
|
||||||
</ListPage>
|
</ListPage>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/components/templates/_story-helpers.tsx
Normal file
33
src/components/templates/_story-helpers.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { type ReactNode } from 'react'
|
||||||
|
|
||||||
|
export const NswLogo = () => (
|
||||||
|
<svg viewBox="0 0 36 35" className="size-9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fillRule="evenodd" clipRule="evenodd" d="M20.234 29.091c-.204-.333-.512-.636-.916-.899a4.932 4.932 0 0 0-1.629-.597l-1.886-.391c-.57-.124-.963-.285-1.168-.476-.2-.187-.302-.435-.302-.738 0-.193.044-.369.13-.523.086-.154.211-.291.371-.406.161-.115.361-.206.594-.27a2.92 2.92 0 0 1 .788-.098c.378 0 .719.05 1.013.15.291.1.532.262.717.484.184.223.297.525.336.899l.005.052h2.341l-.001-.06a3.474 3.474 0 0 0-.531-1.734 3.453 3.453 0 0 0-1.5-1.23c-.653-.302-1.466-.454-2.419-.454-.8 0-1.533.14-2.18.418-.648.278-1.169.68-1.546 1.194-.38.516-.568 1.135-.563 1.838.016.859.275 1.555.77 2.072.493.514 1.169.856 2.01 1.016l1.902.39c.35.07.666.161.942.273.27.109.486.25.642.421.151.167.228.385.228.65 0 .293-.096.542-.284.736-.193.2-.456.35-.778.449a3.527 3.527 0 0 1-1.095.15c-.387 0-.744-.06-1.06-.18a1.956 1.956 0 0 1-.784-.514 1.714 1.714 0 0 1-.42-.822l-.01-.046h-2.386l.006.064c.057.562.23 1.06.516 1.482.286.418.651.771 1.086 1.05.434.277.921.488 1.448.627a6.44 6.44 0 0 0 1.613.209c.891 0 1.682-.142 2.351-.422.671-.281 1.202-.668 1.579-1.151.38-.485.572-1.04.572-1.647 0-.287-.032-.606-.096-.949a3.178 3.178 0 0 0-.405-.948ZM3.577 26.662l5.092 7.412h2.071V22.99H8.424v7.13L3.552 23.014l-.017-.025H1.268v11.085h2.31v-7.412Zm27.123 3.417 1.984-7.09h2.27l-3.253 11.085h-1.844l-2.007-7.017-2.022 7.017H24.014L20.77 22.99h2.276l1.984 7.088 1.985-7.088h1.678l2.007 7.089Z" fill="white" />
|
||||||
|
<path d="M16.868 20.24c-2.724-.338-5.37.542-10.027-.733-.475-.13-.65.375-.393.798 1.25 2.058 7.57.445 10.432.148.125-.013.113-.198-.012-.213Z" fill="#D7153A" />
|
||||||
|
<path d="M28.791 19.508c-4.656 1.274-7.302.395-10.026.731-.125.016-.138.201-.013.214 2.863.297 9.182 1.91 10.432-.148.258-.423.083-.928-.393-1.06v.263Z" fill="#D7153A" />
|
||||||
|
<path d="M8.26 16.564c-.947-1.399-1.72-2.963-2.32-4.684-1.807.531-3.657 1.3-5.54 2.303a.629.629 0 0 0-.4.65c-.007.276.13.527.366.669 3.646 2.197 7.21 3.454 10.611 3.744a7.133 7.133 0 0 1-2.718-2.682Z" fill="#D7153A" />
|
||||||
|
<path d="M3.18 11.89c.835-.337 1.661-.625 2.48-.867a31.64 31.64 0 0 1-.724-3.011 24.71 24.71 0 0 0-2.924-.228h-.026c-.264 0-.502.133-.641.362a.637.637 0 0 0-.017.762c.586 1.065 1.204 2.059 1.852 2.982Z" fill="#D7153A" />
|
||||||
|
<path d="M12.139 18.849c.565.251 1.143.392 1.704.421-1.373-.99-2.451-2.668-3.073-4.808-.8-2.754-1.068-5.793-.804-9.071a28.32 28.32 0 0 0-3.511-1.926.622.622 0 0 0-.746.06.636.636 0 0 0-.33.673c.294 4.757 1.511 8.747 3.617 11.86.884 1.305 1.97 2.27 3.143 2.791Z" fill="#D7153A" />
|
||||||
|
<path d="M14.826 3.278a28.02 28.02 0 0 0-2.104-2.342.605.605 0 0 0-.531-.229.588.588 0 0 0-.515.573c-.148.718-.43 2.181-.554 3.63a27.77 27.77 0 0 1 2.68 2.033c.317-1.217.73-2.451 1.024-3.665Z" fill="#D7153A" />
|
||||||
|
<path d="M35.23 14.183c-1.882-1.005-3.732-1.772-5.539-2.304a24.63 24.63 0 0 1-2.32 4.684 7.127 7.127 0 0 1-2.718 2.682c3.401-.29 6.966-1.548 10.611-3.744a.635.635 0 0 0 .366-.67.629.629 0 0 0-.4-.648Z" fill="#D7153A" />
|
||||||
|
<path d="M32.451 11.889a28.137 28.137 0 0 0 1.853-2.983.637.637 0 0 0-.017-.762.618.618 0 0 0-.641-.362h-.026c-1.008.034-1.983.11-2.924.228-.19 1.048-.431 2.053-.724 3.011.819.243 1.646.531 2.48.867Z" fill="#D7153A" />
|
||||||
|
<path d="M24.862 14.463c-.622-2.139-1.7-3.818-3.073-4.808.56.029 1.14.17 1.705.421 1.171.521 2.258 1.486 3.141 2.791 2.107 3.112 3.324 7.103 3.617 11.859a.636.636 0 0 0-.33-.673.622.622 0 0 0-.745-.06 28.382 28.382 0 0 0-3.511 1.926c.264-3.279-.003-6.318-.804-9.071v-.385Z" fill="#D7153A" />
|
||||||
|
<path d="M24.719 4.939c-.125-1.447-.406-2.911-.554-3.63a.588.588 0 0 0-.515-.572.605.605 0 0 0-.53.229 28.145 28.145 0 0 0-2.105 2.342c.504 1.243.917 2.476 1.234 3.694a27.58 27.58 0 0 1 2.47-2.063Z" fill="#D7153A" />
|
||||||
|
<path d="M17.813 12.128c.916-1.82 2.379-3.452 3.477-4.483a26.56 26.56 0 0 0-2.814-7.243.631.631 0 0 0-1.326 0 26.634 26.634 0 0 0-3.474 7.243c1.147 1.069 2.66 2.812 3.474 4.483h.663Z" fill="#D7153A" />
|
||||||
|
<path d="M21.178 18.587c.95-.672 1.9-1.843 2.502-3.472 1.167-3.153 1.287-6.666 1.12-9.152-2.114 1.42-5.471 4.387-6.617 7.558-.54 1.492-.791 3.525-.375 4.66.174.474.469.833.875 1.004.616.26 1.56.063 2.495-.598Z" fill="#D7153A" />
|
||||||
|
<path d="M17.34 13.245a11.07 11.07 0 0 0-1.066-1.973 22.052 22.052 0 0 0-5.445-5.309c-.023.369-.354 4.351.808 8.245.883 2.962 2.393 4.168 3.221 4.634.833.469 1.595.579 2.488.356-.906-1.242-.793-3.792-.006-5.954Z" fill="#D7153A" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
export function TopBarAction({ icon, label, onClick }: { icon: ReactNode; label: string; onClick?: () => void }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={label}
|
||||||
|
onClick={onClick}
|
||||||
|
className="flex size-12 items-center justify-center rounded-full text-white/80 transition-colors hover:bg-white/10 hover:text-white"
|
||||||
|
>
|
||||||
|
<span className="size-6 [&>svg]:size-full">{icon}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user