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
|
||||
- **ListPage** — PageHeader + stat cards + list header with actions + scrollable item list
|
||||
- **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.
|
||||
|
||||
|
||||
23
DESIGN.md
23
DESIGN.md
@@ -1069,6 +1069,29 @@ import { FormPage } from '@/components/templates/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
|
||||
|
||||
@@ -4,14 +4,10 @@ import { AppShell } from './AppShell'
|
||||
import { TopBar } from '@/components/organisms/TopBar/TopBar'
|
||||
import { SideNav, SideNavItem, SideNavGroup, SideNavDivider } from '@/components/organisms/SideNav/SideNav'
|
||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||
import { IconButton } from '@/components/atoms/IconButton/IconButton'
|
||||
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'
|
||||
|
||||
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> = {
|
||||
title: 'Templates/AppShell',
|
||||
component: AppShell,
|
||||
@@ -29,44 +25,39 @@ export default meta
|
||||
|
||||
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 = {
|
||||
render: () => {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
return (
|
||||
<AppShell
|
||||
topBar={<SampleTopBar onMenuClick={() => setCollapsed(!collapsed)} />}
|
||||
sideNav={<SampleSideNav collapsed={collapsed} />}
|
||||
topBar={
|
||||
<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}
|
||||
>
|
||||
<PageHeader title="Dashboard" subtitle="Welcome back, Myra McKay" />
|
||||
<PageHeader title="Dashboard" subtitle="Welcome back" />
|
||||
<div className="p-6">
|
||||
<div className="rounded-lg border border-border bg-surface p-8 text-center text-text-secondary">
|
||||
Page content goes here
|
||||
@@ -80,8 +71,20 @@ export const Default: Story = {
|
||||
export const Collapsed: Story = {
|
||||
render: () => (
|
||||
<AppShell
|
||||
topBar={<SampleTopBar />}
|
||||
sideNav={<SampleSideNav collapsed />}
|
||||
topBar={
|
||||
<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
|
||||
>
|
||||
<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 { Badge } from '@/components/atoms/Badge/Badge'
|
||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||
import { IconButton } from '@/components/atoms/IconButton/IconButton'
|
||||
import { Menu, Bell, Home, FileText, LayoutGrid, Users, CheckCircle, Clock, Info } 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>
|
||||
)
|
||||
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||
import { Menu, Bell, Home, FileText, LayoutGrid, Users, CheckCircle, Clock, BarChart3 } from 'lucide-react'
|
||||
|
||||
const meta: Meta<typeof DashboardPage> = {
|
||||
title: 'Templates/DashboardPage',
|
||||
@@ -33,68 +29,87 @@ export default meta
|
||||
|
||||
type Story = StoryObj<typeof DashboardPage>
|
||||
|
||||
export const ProfessionalPathway: Story = {
|
||||
name: 'Professional Pathway Dashboard',
|
||||
export const WithAppShell: Story = {
|
||||
name: 'Full page',
|
||||
render: () => {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
return (
|
||||
<AppShell
|
||||
topBar={
|
||||
<TopBar
|
||||
title=""
|
||||
leading={<IconButton icon={<Menu />} aria-label="Menu" variant="tertiary" onClick={() => setCollapsed(!collapsed)} />}
|
||||
title="Project Hub"
|
||||
leading={<TopBarAction icon={<Menu />} label="Menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||
logo={<NswLogo />}
|
||||
>
|
||||
<IconButton icon={<Bell />} aria-label="Notifications" variant="tertiary" />
|
||||
<Avatar initials="MM" size="sm" />
|
||||
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||
<Avatar initials="AB" size="sm" />
|
||||
</TopBar>
|
||||
}
|
||||
sideNav={
|
||||
<SideNav collapsed={collapsed}>
|
||||
<SideNavItem icon={<Home />} active>My status</SideNavItem>
|
||||
<SideNavItem icon={<Users />}>My details</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>Resources</SideNavItem>
|
||||
<SideNavItem icon={<Home />} active>Overview</SideNavItem>
|
||||
<SideNavItem icon={<Users />}>Team</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Projects</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>Reports</SideNavItem>
|
||||
<SideNavDivider />
|
||||
<SideNavItem icon={<Users />}>Accreditation</SideNavItem>
|
||||
<SideNavItem icon={<BarChart3 />}>Analytics</SideNavItem>
|
||||
</SideNav>
|
||||
}
|
||||
sideNavCollapsed={collapsed}
|
||||
>
|
||||
<DashboardPage
|
||||
header={
|
||||
<PageHeader title="Myra McKay" subtitle="Accreditation Level: Proficient Teacher" theme="dark">
|
||||
<div className="mt-2 text-small text-white/80">
|
||||
Maroubra Junction Public School
|
||||
</div>
|
||||
</PageHeader>
|
||||
header={<PageHeader title="Overview" subtitle="Your workspace at a glance" theme="dark" />}
|
||||
stats={
|
||||
<>
|
||||
<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>
|
||||
</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">
|
||||
<CardHeader>
|
||||
<CardTitle>Steps to be taken</CardTitle>
|
||||
<CardTitle>Pending actions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="s1">
|
||||
<AccordionTrigger>Ensure you have completed the minimum requirements of your teaching degree as stated by NESA.</AccordionTrigger>
|
||||
<AccordionContent>Details about teaching degree requirements.</AccordionContent>
|
||||
<AccordionTrigger>Complete onboarding checklist</AccordionTrigger>
|
||||
<AccordionContent>Review and complete all required onboarding items.</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="s2">
|
||||
<AccordionTrigger>Apply for your Working With Children Check (WWCC).</AccordionTrigger>
|
||||
<AccordionContent>Information about WWCC application.</AccordionContent>
|
||||
<AccordionTrigger>Submit quarterly report</AccordionTrigger>
|
||||
<AccordionContent>Your Q2 report is due by end of month.</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="s3">
|
||||
<AccordionTrigger>Create an eTAMS account and submit required documentation to NESA.</AccordionTrigger>
|
||||
<AccordionContent>Steps for eTAMS registration.</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>
|
||||
<AccordionTrigger>Review team permissions</AccordionTrigger>
|
||||
<AccordionContent>Audit team member access levels.</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</CardContent>
|
||||
@@ -103,29 +118,30 @@ export const ProfessionalPathway: Story = {
|
||||
<div className="flex flex-col gap-6">
|
||||
<Card variant="surface">
|
||||
<CardHeader>
|
||||
<CardTitle>Mandatory Training Reminders</CardTitle>
|
||||
<CardTitle>Compliance status</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between border-b border-border pb-3">
|
||||
<span className="text-body font-medium">Aboriginal Cultural Education</span>
|
||||
<Badge variant="success" leftIcon={<CheckCircle size={14} />}>Certified</Badge>
|
||||
<span className="text-body font-medium">Security training</span>
|
||||
<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>
|
||||
<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>
|
||||
</Card>
|
||||
|
||||
<Card variant="elevated" className="bg-info/5">
|
||||
<CardContent className="flex gap-4 p-5">
|
||||
<Avatar initials="MK" size="lg" />
|
||||
<Avatar initials="HR" size="lg" />
|
||||
<div className="text-small">
|
||||
<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>
|
||||
<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">FAQ (questions I had)</a>
|
||||
<a href="#" className="text-info hover:underline">Getting started guide</a>
|
||||
<a href="#" className="text-info hover:underline">Frequently asked questions</a>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -138,7 +154,7 @@ export const ProfessionalPathway: Story = {
|
||||
}
|
||||
|
||||
export const Standalone: Story = {
|
||||
name: 'Without AppShell',
|
||||
name: 'Content only',
|
||||
render: () => (
|
||||
<DashboardPage
|
||||
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">
|
||||
<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 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">21h</p>
|
||||
<p className="text-small text-text-secondary">Total hours logged</p>
|
||||
<p className="text-caption text-text-secondary">Target 100h</p>
|
||||
<p className="text-small text-text-secondary">Hours logged</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">
|
||||
<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 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</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -183,10 +182,10 @@ export const Standalone: Story = {
|
||||
}
|
||||
>
|
||||
<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 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>
|
||||
</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 { Badge } from '@/components/atoms/Badge/Badge'
|
||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||
import { IconButton } from '@/components/atoms/IconButton/IconButton'
|
||||
import { Menu, Bell, Home, FileText, LayoutGrid, Users, Link, ArrowRight } 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>
|
||||
)
|
||||
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||
import { Menu, Bell, Home, FileText, LayoutGrid, Users, Link, ArrowRight, Settings } from 'lucide-react'
|
||||
|
||||
const meta: Meta<typeof FormPage> = {
|
||||
title: 'Templates/FormPage',
|
||||
@@ -36,35 +32,34 @@ export default meta
|
||||
|
||||
type Story = StoryObj<typeof FormPage>
|
||||
|
||||
export const PDPDetails: Story = {
|
||||
name: 'PDP Form',
|
||||
export const WithStepper: Story = {
|
||||
name: 'With stepper',
|
||||
render: () => {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
return (
|
||||
<AppShell
|
||||
topBar={
|
||||
<TopBar
|
||||
title="Performance and development plan"
|
||||
leading={<IconButton icon={<Menu />} aria-label="Menu" variant="tertiary" onClick={() => setCollapsed(!collapsed)} />}
|
||||
title="Application Portal"
|
||||
leading={<TopBarAction icon={<Menu />} label="Menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||
logo={<NswLogo />}
|
||||
>
|
||||
<IconButton icon={<Bell />} aria-label="Notifications" variant="tertiary" />
|
||||
<Avatar initials="DW" size="sm" />
|
||||
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||
<Avatar initials="SR" size="sm" />
|
||||
</TopBar>
|
||||
}
|
||||
sideNav={
|
||||
<SideNav collapsed={collapsed}>
|
||||
<SideNavItem icon={<Home />}>My status</SideNavItem>
|
||||
<SideNavItem icon={<Users />}>My details</SideNavItem>
|
||||
<SideNavItem icon={<Home />}>Home</SideNavItem>
|
||||
<SideNavItem icon={<Users />}>Profile</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||
<SideNavItem icon={<Link />}>Resources</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>My documents & links</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>Documents</SideNavItem>
|
||||
<SideNavDivider />
|
||||
<SideNavGroup icon={<FileText />} label="PDP" defaultOpen active>
|
||||
<SideNavItem active>My PDP</SideNavItem>
|
||||
<SideNavItem>PDP guide</SideNavItem>
|
||||
<SideNavItem>Management</SideNavItem>
|
||||
<SideNavItem>Useful links</SideNavItem>
|
||||
<SideNavGroup icon={<FileText />} label="Applications" defaultOpen active>
|
||||
<SideNavItem active>New application</SideNavItem>
|
||||
<SideNavItem>Guidelines</SideNavItem>
|
||||
<SideNavItem>History</SideNavItem>
|
||||
<SideNavItem>Support</SideNavItem>
|
||||
</SideNavGroup>
|
||||
</SideNav>
|
||||
@@ -73,10 +68,9 @@ export const PDPDetails: Story = {
|
||||
>
|
||||
<FormPage
|
||||
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">
|
||||
<Badge variant="warning">Plan - In progress</Badge>
|
||||
<span className="text-small text-white/80">Date commenced: dd-mm-yyyy</span>
|
||||
<Badge variant="warning">In progress</Badge>
|
||||
</div>
|
||||
</PageHeader>
|
||||
}
|
||||
@@ -85,77 +79,60 @@ export const PDPDetails: Story = {
|
||||
<Select
|
||||
label=""
|
||||
variant="stacked"
|
||||
options={[{ value: '2026', label: '2026 - PDP Siya Ram' }]}
|
||||
options={[{ value: '2026', label: '2026 — Application draft' }]}
|
||||
defaultValue="2026"
|
||||
/>
|
||||
<Button variant="secondary">More actions</Button>
|
||||
</>
|
||||
}
|
||||
steps={[
|
||||
{ label: 'Your PDP details', status: 'current' },
|
||||
{ label: 'Create your PDP', status: 'upcoming' },
|
||||
{ label: 'Notify your PDP supervisor', status: 'upcoming' },
|
||||
{ label: 'Your details', status: 'current' },
|
||||
{ label: 'Supporting documents', status: 'upcoming' },
|
||||
{ label: 'Review & submit', status: 'upcoming' },
|
||||
]}
|
||||
>
|
||||
<Card variant="surface">
|
||||
<CardContent className="space-y-6 p-6">
|
||||
<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">
|
||||
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>
|
||||
</div>
|
||||
|
||||
<Alert variant="info" title="Your PDP details">
|
||||
Fill in the details below to get started with your PDP.
|
||||
<Alert variant="info" title="Before you start">
|
||||
Make sure you have your identification documents and contact details ready.
|
||||
</Alert>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Select
|
||||
label="PDP year"
|
||||
options={[
|
||||
{ value: '2026', label: '2026' },
|
||||
{ value: '2025', label: '2025' },
|
||||
]}
|
||||
defaultValue="2026"
|
||||
/>
|
||||
<Input label="Full name" placeholder="Enter your full name" />
|
||||
<Input label="Email address" type="email" placeholder="you@example.com" />
|
||||
|
||||
<div>
|
||||
<p className="text-body font-semibold text-text">Middle leader role(s)</p>
|
||||
<p className="mb-2 text-small text-text-secondary">Some text about middle leader roles</p>
|
||||
<p className="text-body font-semibold text-text">Role information</p>
|
||||
<p className="mb-2 text-small text-text-secondary">Select the role that best describes your position.</p>
|
||||
<Select
|
||||
label="Middle leader role type (optional)"
|
||||
label="Role type"
|
||||
options={[
|
||||
{ value: 'deputy', label: 'Deputy Principal' },
|
||||
{ value: 'head', label: 'Head Teacher' },
|
||||
{ value: 'asst', label: 'Assistant Principal' },
|
||||
{ value: 'manager', label: 'Manager' },
|
||||
{ value: 'coordinator', label: 'Coordinator' },
|
||||
{ value: 'specialist', label: 'Specialist' },
|
||||
]}
|
||||
defaultValue="deputy"
|
||||
/>
|
||||
</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">
|
||||
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>
|
||||
<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="PDP Supervisor work location" error="PDP Supervisor work location" defaultValue="Work location goes here" />
|
||||
<Input label="Supervisor email" type="email" placeholder="supervisor@example.com" />
|
||||
<Input label="Supervisor location" placeholder="Office or site name" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<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>
|
||||
<Input label="Work location" placeholder="Your primary work location" />
|
||||
|
||||
<div className="flex justify-start pt-2">
|
||||
<Button rightIcon={<ArrowRight size={18} />}>Proceed</Button>
|
||||
@@ -170,7 +147,7 @@ export const PDPDetails: Story = {
|
||||
}
|
||||
|
||||
export const SimpleForm: Story = {
|
||||
name: 'Simple Form (no stepper)',
|
||||
name: 'Simple form (no stepper)',
|
||||
render: () => (
|
||||
<FormPage
|
||||
header={<PageHeader title="Create Account" subtitle="Set up your profile to get started" />}
|
||||
@@ -182,8 +159,8 @@ export const SimpleForm: Story = {
|
||||
<Select
|
||||
label="Role"
|
||||
options={[
|
||||
{ value: 'teacher', label: 'Teacher' },
|
||||
{ value: 'principal', label: 'Principal' },
|
||||
{ value: 'viewer', label: 'Viewer' },
|
||||
{ value: 'editor', label: 'Editor' },
|
||||
{ value: 'admin', label: 'Administrator' },
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -10,32 +10,24 @@ import { Badge } from '@/components/atoms/Badge/Badge'
|
||||
import { Tag } from '@/components/atoms/Tag/Tag'
|
||||
import { Button } from '@/components/atoms/Button/Button'
|
||||
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'
|
||||
|
||||
const NswLogo = () => (
|
||||
<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 }) => (
|
||||
const ListItem = ({ title, subtitle, status, date, tags }: { title: string; subtitle: string; status: string; date: string; tags: string[] }) => (
|
||||
<div className="flex flex-col gap-2 px-6 py-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<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">
|
||||
<span>{hours}</span>
|
||||
<span>{date}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Tag color="navy" variant="filled" size="sm">NSW DoE</Tag>
|
||||
<Badge variant="success" leftIcon={<Check size={14} />}>Registered</Badge>
|
||||
<Tag color="blue" variant="filled" size="sm">S1</Tag>
|
||||
<Tag color="orange" variant="filled" size="sm">s4</Tag>
|
||||
<Tag color="green" variant="filled" size="sm">S6</Tag>
|
||||
<Badge variant="success" leftIcon={<Check size={14} />}>{status}</Badge>
|
||||
{tags.map((tag) => (
|
||||
<Tag key={tag} color="blue" variant="filled" size="sm">{tag}</Tag>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-small text-text-secondary">
|
||||
Lorem dolor sit amet, consectetur adipiscing elit. Donec condimentum nulla gravida pretium libero. Proin in felis consectetur, laoreet est eu, consectetur mi.
|
||||
</p>
|
||||
<p className="text-small text-text-secondary">{subtitle}</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -56,74 +48,52 @@ export default meta
|
||||
|
||||
type Story = StoryObj<typeof ListPage>
|
||||
|
||||
export const PDLog: Story = {
|
||||
name: 'PD Log',
|
||||
export const WithAppShell: Story = {
|
||||
name: 'Full page',
|
||||
render: () => {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
return (
|
||||
<AppShell
|
||||
topBar={
|
||||
<TopBar
|
||||
title=""
|
||||
leading={<IconButton icon={<Menu />} aria-label="Menu" variant="tertiary" onClick={() => setCollapsed(!collapsed)} />}
|
||||
title="Activity Tracker"
|
||||
leading={<TopBarAction icon={<Menu />} label="Menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||
logo={<NswLogo />}
|
||||
>
|
||||
<IconButton icon={<Bell />} aria-label="Notifications" variant="tertiary" />
|
||||
<Avatar initials="JW" size="sm" />
|
||||
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||
<Avatar initials="JD" size="sm" />
|
||||
</TopBar>
|
||||
}
|
||||
sideNav={
|
||||
<SideNav collapsed={collapsed}>
|
||||
<SideNavItem icon={<Home />} active>My status</SideNavItem>
|
||||
<SideNavItem icon={<Home />} active>Home</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>My details</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>Reports</SideNavItem>
|
||||
<SideNavDivider />
|
||||
<SideNavItem icon={<Users />}>Accreditation</SideNavItem>
|
||||
<SideNavItem icon={<Users />}>Team</SideNavItem>
|
||||
</SideNav>
|
||||
}
|
||||
sideNavCollapsed={collapsed}
|
||||
>
|
||||
<ListPage
|
||||
header={
|
||||
<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>
|
||||
}
|
||||
header={<PageHeader title="Activity Log" subtitle="Track and manage your recorded activities" theme="dark" />}
|
||||
stats={
|
||||
<>
|
||||
<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 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">21h</p>
|
||||
<p className="text-small text-text-secondary">Total hours logged</p>
|
||||
<p className="text-h3 font-bold text-text">64h</p>
|
||||
<p className="text-small text-text-secondary">Total hours</p>
|
||||
<p className="text-caption text-text-secondary">Target 100h</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">
|
||||
<Clock size={20} />
|
||||
</div>
|
||||
<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">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">
|
||||
<BarChart3 size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-h3 font-bold text-text">5</p>
|
||||
<p className="text-h3 font-bold text-text">12</p>
|
||||
<p className="text-small text-text-secondary">Activities logged</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -133,17 +103,17 @@ export const PDLog: Story = {
|
||||
listHeader={
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-h4 font-bold text-text">My PD Log</h2>
|
||||
<p className="text-small text-text-secondary">Log every professional learning activity — NESA Registered and school-based.</p>
|
||||
<h2 className="text-h4 font-bold text-text">Recent activities</h2>
|
||||
<p className="text-small text-text-secondary">All recorded activities for the current period.</p>
|
||||
</div>
|
||||
<Button leftIcon={<Plus size={18} />}>Add Activity</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ActivityItem title="Trauma-informed practice" hours="8h" date="2024-02-20" />
|
||||
<ActivityItem title="Trauma-informed practice" hours="8h" date="2024-02-20" />
|
||||
<ActivityItem title="Trauma-informed practice" hours="8h" date="2024-02-20" />
|
||||
<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']} />
|
||||
<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']} />
|
||||
<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']} />
|
||||
<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>
|
||||
</AppShell>
|
||||
)
|
||||
@@ -151,10 +121,10 @@ export const PDLog: Story = {
|
||||
}
|
||||
|
||||
export const Standalone: Story = {
|
||||
name: 'Without AppShell',
|
||||
name: 'Content only',
|
||||
render: () => (
|
||||
<ListPage
|
||||
header={<PageHeader title="Activity Log" subtitle="Your professional development activities" />}
|
||||
header={<PageHeader title="Activity Log" subtitle="Your recent activities" />}
|
||||
listHeader={
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-h4 font-bold text-text">Activities</h2>
|
||||
@@ -162,8 +132,8 @@ export const Standalone: Story = {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ActivityItem title="Sample activity" hours="4h" date="2024-03-15" />
|
||||
<ActivityItem title="Another activity" hours="2h" date="2024-03-10" />
|
||||
<ListItem title="Sample activity" subtitle="A brief description of this activity." status="Complete" date="2024-03-15" tags={['Example']} />
|
||||
<ListItem title="Another activity" subtitle="Another brief description." status="Complete" date="2024-03-10" tags={['Example']} />
|
||||
</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