diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c43dc73..509d831 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -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. diff --git a/DESIGN.md b/DESIGN.md index e8ff973..a3ad4b4 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1069,6 +1069,29 @@ import { FormPage } from '@/components/templates/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' + +} + actions={Overview} + maxWidth="lg" +> + Detail content + +``` + --- ## Do's and Don'ts diff --git a/src/components/templates/AppShell/AppShell.stories.tsx b/src/components/templates/AppShell/AppShell.stories.tsx index 9668cc2..b7b1a78 100644 --- a/src/components/templates/AppShell/AppShell.stories.tsx +++ b/src/components/templates/AppShell/AppShell.stories.tsx @@ -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 = () => ( -
NSW
-) - const meta: Meta = { title: 'Templates/AppShell', component: AppShell, @@ -29,44 +25,39 @@ export default meta type Story = StoryObj -const SampleTopBar = ({ onMenuClick }: { onMenuClick?: () => void }) => ( - } aria-label="Toggle menu" variant="tertiary" onClick={onMenuClick} />} - logo={} - > - } aria-label="Search" variant="tertiary" /> - } aria-label="Notifications" variant="tertiary" /> - - -) - -const SampleSideNav = ({ collapsed }: { collapsed: boolean }) => ( - - } active>My status - }>My details - }>Workspace - - } label="PDP" defaultOpen> - My PDP - PDP guide - Management - - }>Resources - }>Settings - -) - export const Default: Story = { render: () => { const [collapsed, setCollapsed] = useState(false) return ( setCollapsed(!collapsed)} />} - sideNav={} + topBar={ + } label="Toggle menu" onClick={() => setCollapsed(!collapsed)} />} + logo={} + > + } label="Search" /> + } label="Notifications" /> + + + } + sideNav={ + + } active>Home + }>Documents + }>Workspace + + } label="Team" defaultOpen> + Members + Roles + + }>Resources + }>Settings + + } sideNavCollapsed={collapsed} > - +
Page content goes here @@ -80,8 +71,20 @@ export const Default: Story = { export const Collapsed: Story = { render: () => ( } - sideNav={} + topBar={ + } label="Menu" />} logo={}> + } label="Notifications" /> + + + } + sideNav={ + + } active>Home + }>Documents + }>Workspace + }>Settings + + } sideNavCollapsed > diff --git a/src/components/templates/DashboardPage/DashboardPage.stories.tsx b/src/components/templates/DashboardPage/DashboardPage.stories.tsx index 28bd8bd..0567ab8 100644 --- a/src/components/templates/DashboardPage/DashboardPage.stories.tsx +++ b/src/components/templates/DashboardPage/DashboardPage.stories.tsx @@ -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 = () => ( -
NSW
-) +import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers' +import { Menu, Bell, Home, FileText, LayoutGrid, Users, CheckCircle, Clock, BarChart3 } from 'lucide-react' const meta: Meta = { title: 'Templates/DashboardPage', @@ -33,68 +29,87 @@ export default meta type Story = StoryObj -export const ProfessionalPathway: Story = { - name: 'Professional Pathway Dashboard', +export const WithAppShell: Story = { + name: 'Full page', render: () => { const [collapsed, setCollapsed] = useState(false) return ( } aria-label="Menu" variant="tertiary" onClick={() => setCollapsed(!collapsed)} />} + title="Project Hub" + leading={} label="Menu" onClick={() => setCollapsed(!collapsed)} />} logo={} > - } aria-label="Notifications" variant="tertiary" /> - + } label="Notifications" /> + } sideNav={ - } active>My status - }>My details - }>Workspace - }>Resources + } active>Overview + }>Team + }>Projects + }>Reports - }>Accreditation + }>Analytics } sideNavCollapsed={collapsed} > -
- Maroubra Junction Public School -
-
+ header={} + stats={ + <> + + +
+
+

142

+

Hours logged

+

Target 200h

+
+
+
+ + +
+
+

24

+

Tasks completed

+

This month

+
+
+
+ + +
+
+

8

+

Active projects

+
+
+
+ } > - Steps to be taken + Pending actions - Ensure you have completed the minimum requirements of your teaching degree as stated by NESA. - Details about teaching degree requirements. + Complete onboarding checklist + Review and complete all required onboarding items. - Apply for your Working With Children Check (WWCC). - Information about WWCC application. + Submit quarterly report + Your Q2 report is due by end of month. - Create an eTAMS account and submit required documentation to NESA. - Steps for eTAMS registration. - - - Pay your NESA fee. - Payment details. - - - Complete Mandatory Training. - Training module details. + Review team permissions + Audit team member access levels. @@ -103,29 +118,30 @@ export const ProfessionalPathway: Story = {
- Mandatory Training Reminders + Compliance status
- Aboriginal Cultural Education - }>Certified + Security training + }>Complete +
+
+ Data privacy certification + Due soon
-

- Please consult the Mandatory Training Hub for role specific training, or contact the MyPL Helpdesk for queries regarding training. -

- +

- 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.

@@ -138,7 +154,7 @@ export const ProfessionalPathway: Story = { } export const Standalone: Story = { - name: 'Without AppShell', + name: 'Content only', render: () => ( } @@ -146,36 +162,19 @@ export const Standalone: Story = { <> -
- -
+

21h

-

Total hours logged

-

Target 100h

+

Hours logged

-
- -
-
-

18h

-

NESA Registered PD

-

Target 60h

-
-
-
- - -
- -
+

5

-

Activities logged

+

Activities

@@ -183,10 +182,10 @@ export const Standalone: Story = { } > - Left column content + Left column - Right column content + Right column
), diff --git a/src/components/templates/DetailPage/DetailPage.stories.tsx b/src/components/templates/DetailPage/DetailPage.stories.tsx new file mode 100644 index 0000000..de4b10a --- /dev/null +++ b/src/components/templates/DetailPage/DetailPage.stories.tsx @@ -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 = { + 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 + +const InfoRow = ({ label, value, icon }: { label: string; value: string; icon: React.ReactNode }) => ( +
+ {icon} +
+

{label}

+

{value}

+
+
+) + +export const ProfileView: Story = { + name: 'Profile detail', + render: () => { + const [collapsed, setCollapsed] = useState(false) + const [activeTab, setActiveTab] = useState('overview') + return ( + } label="Menu" onClick={() => setCollapsed(!collapsed)} />} + logo={} + > + } label="Notifications" /> + + + } + sideNav={ + + }>Home + } active>Team + }>Projects + }>Documents + + }>Settings + + } + sideNavCollapsed={collapsed} + > + +
+ Active + Full-time +
+ + } + actions={ + + + Overview + Projects + Activity + + + } + > + + +
+ + + Contact information + + + } label="Email" value="alex.chen@example.com" /> + } label="Phone" value="+61 2 9876 5432" /> + } label="Location" value="Sydney CBD, Level 12" /> + + + + + + Quick actions + + + + + + +
+
+ + + Project list content + + + + + Activity feed content + + +
+
+
+ ) + }, +} + +export const DocumentView: Story = { + name: 'Document detail', + render: () => ( + } + maxWidth="lg" + > + + +

Overview

+

+ 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. +

+

Objectives

+
    +
  • Reduce operational costs by 40% through system consolidation
  • +
  • Improve data consistency across all business units
  • +
  • Provide a modern, accessible user interface
  • +
  • Enable real-time reporting and analytics
  • +
+

Timeline

+

+ 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. +

+
+
+
+ ), +} diff --git a/src/components/templates/DetailPage/DetailPage.tsx b/src/components/templates/DetailPage/DetailPage.tsx new file mode 100644 index 0000000..be94312 --- /dev/null +++ b/src/components/templates/DetailPage/DetailPage.tsx @@ -0,0 +1,43 @@ +import { forwardRef, type HTMLAttributes, type ReactNode } from 'react' +import { cn } from '@/lib/utils' + +export interface DetailPageProps extends HTMLAttributes { + /** 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( + ({ header, actions, maxWidth = 'xl', className, children, ...props }, ref) => { + return ( +
+ {header} + + {actions && ( +
+ {actions} +
+ )} + +
+
+ {children} +
+
+
+ ) + }, +) +DetailPage.displayName = 'DetailPage' diff --git a/src/components/templates/DetailPage/index.ts b/src/components/templates/DetailPage/index.ts new file mode 100644 index 0000000..7a62f80 --- /dev/null +++ b/src/components/templates/DetailPage/index.ts @@ -0,0 +1,2 @@ +export { DetailPage } from './DetailPage' +export type { DetailPageProps } from './DetailPage' diff --git a/src/components/templates/FormPage/FormPage.stories.tsx b/src/components/templates/FormPage/FormPage.stories.tsx index 076c5df..5223d90 100644 --- a/src/components/templates/FormPage/FormPage.stories.tsx +++ b/src/components/templates/FormPage/FormPage.stories.tsx @@ -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 = () => ( -
NSW
-) +import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers' +import { Menu, Bell, Home, FileText, LayoutGrid, Users, Link, ArrowRight, Settings } from 'lucide-react' const meta: Meta = { title: 'Templates/FormPage', @@ -36,35 +32,34 @@ export default meta type Story = StoryObj -export const PDPDetails: Story = { - name: 'PDP Form', +export const WithStepper: Story = { + name: 'With stepper', render: () => { const [collapsed, setCollapsed] = useState(false) return ( } aria-label="Menu" variant="tertiary" onClick={() => setCollapsed(!collapsed)} />} + title="Application Portal" + leading={} label="Menu" onClick={() => setCollapsed(!collapsed)} />} logo={} > - } aria-label="Notifications" variant="tertiary" /> - + } label="Notifications" /> + } sideNav={ - }>My status - }>My details + }>Home + }>Profile }>Workspace }>Resources - }>My documents & links + }>Documents - } label="PDP" defaultOpen active> - My PDP - PDP guide - Management - Useful links + } label="Applications" defaultOpen active> + New application + Guidelines + History Support @@ -73,10 +68,9 @@ export const PDPDetails: Story = { > +
- Plan - In progress - Date commenced: dd-mm-yyyy + In progress
} @@ -85,77 +79,60 @@ export const PDPDetails: Story = { + +
-

Middle leader role(s)

-

Some text about middle leader roles

+

Role information

+

Select the role that best describes your position.

- + +
-
-

Add your school or work location.

-

- If you don't work in a school, add 'Education Office' as your work location. -

- -
- -

- Note: As the school leader, your principal can view all the POPs in the school. -

+
@@ -170,7 +147,7 @@ export const PDPDetails: Story = { } export const SimpleForm: Story = { - name: 'Simple Form (no stepper)', + name: 'Simple form (no stepper)', render: () => ( } @@ -182,8 +159,8 @@ export const SimpleForm: Story = {