Initial commit: FA 2.0 Design System foundation
Token pipeline (Style Dictionary v4, DTCG format): - Primitive tokens: colour palettes (brand, sage, neutral, feedback), typography (3 font families, 21-variant type scale), spacing (4px grid), border radius, shadows, opacity - Semantic tokens: text, surface, border, interactive, feedback colours; typography roles; layout spacing - Component tokens: Button (4 sizes), Input (2 sizes) - Generated outputs: CSS custom properties, JS ES6 module, flat JSON Atoms (3 components): - Button: contained/soft/outlined/text × primary/secondary, 4 sizes, loading state, underline for text variant - Typography: 21 variants across display/heading/body/label/caption/overline, maxLines truncation - Input: external label, helper text, error/success validation, start/end icons, required indicator, 2 sizes, multiline support Infrastructure: - MUI v5 theme with full token mapping - Storybook 8 with autodocs - Claude Code agents and skills for token/component workflows - Design system documentation and cross-session memory Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
345
src/components/atoms/Typography/Typography.stories.tsx
Normal file
345
src/components/atoms/Typography/Typography.stories.tsx
Normal file
@@ -0,0 +1,345 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Typography } from './Typography';
|
||||
|
||||
const meta: Meta<typeof Typography> = {
|
||||
title: 'Atoms/Typography',
|
||||
component: Typography,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
design: {
|
||||
type: 'figma',
|
||||
url: 'https://www.figma.com/design/3t6fpT5inh7zzjxQdW8U5p/Design-System---Template?node-id=23-30',
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'displayHero', 'display1', 'display2', 'display3', 'displaySm',
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
'bodyLg', 'body1', 'body2', 'bodyXs',
|
||||
'labelLg', 'label', 'labelSm',
|
||||
'caption', 'captionSm',
|
||||
'overline', 'overlineSm',
|
||||
],
|
||||
description: 'Typography variant — 21 variants across 6 categories',
|
||||
table: { defaultValue: { summary: 'body1' } },
|
||||
},
|
||||
color: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'textPrimary', 'textSecondary', 'textDisabled',
|
||||
'primary', 'secondary', 'error',
|
||||
],
|
||||
},
|
||||
maxLines: { control: 'number' },
|
||||
gutterBottom: { control: 'boolean' },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Typography>;
|
||||
|
||||
const SAMPLE = 'Discover, Explore, and Plan Funerals in Minutes, Not Hours';
|
||||
|
||||
// ─── Default ────────────────────────────────────────────────────────────────
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: 'Funeral Arranger helps families find transparent, affordable funeral services across Australia.',
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Display (Noto Serif SC, Regular) ───────────────────────────────────────
|
||||
|
||||
/** 5 display levels — Noto Serif SC Regular. For hero/marketing text. All scale down on mobile. */
|
||||
export const Display: Story = {
|
||||
name: 'Display (Serif)',
|
||||
render: () => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">displayHero — 80px</Typography>
|
||||
<Typography variant="displayHero">{SAMPLE}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">display1 — 64px</Typography>
|
||||
<Typography variant="display1">{SAMPLE}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">display2 — 52px</Typography>
|
||||
<Typography variant="display2">{SAMPLE}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">display3 — 40px</Typography>
|
||||
<Typography variant="display3">{SAMPLE}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">displaySm — 32px</Typography>
|
||||
<Typography variant="displaySm">{SAMPLE}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Headings (Montserrat, Bold) ────────────────────────────────────────────
|
||||
|
||||
/** 6 heading levels — Montserrat Bold. For content structure. All scale down on mobile. */
|
||||
export const Headings: Story = {
|
||||
name: 'Headings (Sans-serif)',
|
||||
render: () => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">h1 — 36px</Typography>
|
||||
<Typography variant="h1">{SAMPLE}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">h2 — 30px</Typography>
|
||||
<Typography variant="h2">{SAMPLE}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">h3 — 24px</Typography>
|
||||
<Typography variant="h3">{SAMPLE}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">h4 — 20px</Typography>
|
||||
<Typography variant="h4">{SAMPLE}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">h5 — 18px</Typography>
|
||||
<Typography variant="h5">{SAMPLE}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">h6 — 16px</Typography>
|
||||
<Typography variant="h6">{SAMPLE}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Body (Montserrat, Medium) ──────────────────────────────────────────────
|
||||
|
||||
/** 4 body sizes — Montserrat Medium (500). For content text. */
|
||||
export const Body: Story = {
|
||||
name: 'Body Text',
|
||||
render: () => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 640 }}>
|
||||
<div>
|
||||
<Typography variant="overline" gutterBottom>bodyLg — 18px</Typography>
|
||||
<Typography variant="bodyLg">
|
||||
Planning a funeral is one of the most difficult tasks a family faces. Funeral Arranger
|
||||
is here to help you navigate this process with care and transparency.
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="overline" gutterBottom>body1 (default) — 16px</Typography>
|
||||
<Typography variant="body1">
|
||||
Compare funeral directors in your area, view transparent pricing, and make informed
|
||||
decisions at your own pace. Every family deserves clarity during this time.
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="overline" gutterBottom>body2 (small) — 14px</Typography>
|
||||
<Typography variant="body2">
|
||||
Prices shown are indicative and may vary based on your specific requirements.
|
||||
Contact the funeral director directly for a detailed quote.
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="overline" gutterBottom>bodyXs — 12px</Typography>
|
||||
<Typography variant="bodyXs">
|
||||
Terms and conditions apply. Funeral Arranger is a comparison service and does not
|
||||
directly provide funeral services. ABN 12 345 678 901.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Label, Caption, Overline ───────────────────────────────────────────────
|
||||
|
||||
/** UI text variants — labels (medium 500), captions (regular 400), overlines (semibold 600 uppercase) */
|
||||
export const UIText: Story = {
|
||||
name: 'UI Text (Label / Caption / Overline)',
|
||||
render: () => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">labelLg — 16px medium</Typography>
|
||||
<Typography variant="labelLg" display="block">Form label or section label</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">label — 14px medium</Typography>
|
||||
<Typography variant="label" display="block">Default form label</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">labelSm — 12px medium</Typography>
|
||||
<Typography variant="labelSm" display="block">Compact label or tag text</Typography>
|
||||
</div>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Typography variant="captionSm" color="textSecondary">caption — 12px regular</Typography>
|
||||
<Typography variant="caption" display="block">Fine print, timestamps, metadata</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">captionSm — 11px regular</Typography>
|
||||
<Typography variant="captionSm" display="block">Compact metadata, footnotes</Typography>
|
||||
</div>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Typography variant="captionSm" color="textSecondary">overline — 12px semibold uppercase</Typography>
|
||||
<Typography variant="overline" display="block">Section overline</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="captionSm" color="textSecondary">overlineSm — 11px semibold uppercase</Typography>
|
||||
<Typography variant="overlineSm" display="block">Compact overline</Typography>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Colours ────────────────────────────────────────────────────────────────
|
||||
|
||||
export const Colours: Story = {
|
||||
name: 'Colours',
|
||||
render: () => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<Typography color="textPrimary">Text Primary — main body text (neutral.800)</Typography>
|
||||
<Typography color="textSecondary">Text Secondary — helper text (neutral.600)</Typography>
|
||||
<Typography color="textDisabled">Text Disabled — inactive (neutral.400)</Typography>
|
||||
<Typography color="primary">Primary — brand emphasis (brand.600)</Typography>
|
||||
<Typography color="secondary">Secondary — neutral emphasis (neutral.600)</Typography>
|
||||
<Typography color="error">Error — validation errors (red.600)</Typography>
|
||||
<Typography color="warning.main">Warning — cautionary (amber.600)</Typography>
|
||||
<Typography color="success.main">Success — confirmations (green.600)</Typography>
|
||||
<Typography color="info.main">Info — helpful tips (blue.600)</Typography>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Font Families ──────────────────────────────────────────────────────────
|
||||
|
||||
/** The two font families: serif for display, sans-serif for everything else */
|
||||
export const FontFamilies: Story = {
|
||||
name: 'Font Families',
|
||||
render: () => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
|
||||
<div>
|
||||
<Typography variant="overline" gutterBottom>Display font — Noto Serif SC (Regular 400)</Typography>
|
||||
<Typography variant="display3">
|
||||
Warm, trustworthy, and professional
|
||||
</Typography>
|
||||
<Typography variant="caption" color="textSecondary" sx={{ mt: 1 }}>
|
||||
Used exclusively for display variants (hero through sm). Regular weight — serif carries inherent visual weight at large sizes.
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="overline" gutterBottom>Body font — Montserrat</Typography>
|
||||
<Typography variant="h3" gutterBottom>Clean, modern, and highly readable</Typography>
|
||||
<Typography>
|
||||
Used for all headings (h1–h6), body text, labels, captions, and UI elements.
|
||||
Headings use Bold (700), body uses Medium (500), captions use Regular (400).
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Max Lines ──────────────────────────────────────────────────────────────
|
||||
|
||||
export const MaxLines: Story = {
|
||||
name: 'Max Lines (Truncation)',
|
||||
render: () => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 24, maxWidth: 400 }}>
|
||||
<div>
|
||||
<Typography variant="label" gutterBottom>maxLines=1</Typography>
|
||||
<Typography maxLines={1}>
|
||||
H. Parsons Funeral Directors — trusted by Australian families for over 30 years,
|
||||
providing compassionate and transparent funeral services.
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="label" gutterBottom>maxLines=2</Typography>
|
||||
<Typography maxLines={2}>
|
||||
H. Parsons Funeral Directors — trusted by Australian families for over 30 years,
|
||||
providing compassionate and transparent funeral services across metropolitan
|
||||
and regional areas.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Realistic Content ──────────────────────────────────────────────────────
|
||||
|
||||
export const RealisticContent: Story = {
|
||||
name: 'Realistic Content',
|
||||
render: () => (
|
||||
<div style={{ maxWidth: 640, display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<Typography variant="overline">Funeral planning</Typography>
|
||||
<Typography variant="display3">Compare funeral services in your area</Typography>
|
||||
<Typography variant="bodyLg" color="textSecondary">
|
||||
Transparent pricing and service comparison to help you make informed
|
||||
decisions during a difficult time.
|
||||
</Typography>
|
||||
<Typography variant="h2" sx={{ mt: 2 }}>How it works</Typography>
|
||||
<Typography>
|
||||
Enter your suburb or postcode to find funeral directors near you. Each
|
||||
listing includes a full price breakdown, service inclusions, and reviews
|
||||
from families who have used their services.
|
||||
</Typography>
|
||||
<Typography variant="h3" sx={{ mt: 1 }}>Step 1: Browse packages</Typography>
|
||||
<Typography>
|
||||
Compare packages side by side. Each package clearly shows what is and
|
||||
isn't included, so there are no surprises.
|
||||
</Typography>
|
||||
<Typography variant="caption" color="textSecondary" sx={{ mt: 2 }}>
|
||||
Prices are indicative and current as of March 2026. Contact the funeral
|
||||
director for a binding quote.
|
||||
</Typography>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Complete Scale ─────────────────────────────────────────────────────────
|
||||
|
||||
/** All 21 variants in a single view — matches the Figma "Fonts - Desktop" layout */
|
||||
export const CompleteScale: Story = {
|
||||
name: 'Complete Scale (All 21 Variants)',
|
||||
render: () => {
|
||||
const variants = [
|
||||
{ variant: 'displayHero', label: 'display/hero — 80px' },
|
||||
{ variant: 'display1', label: 'display/1 — 64px' },
|
||||
{ variant: 'display2', label: 'display/2 — 52px' },
|
||||
{ variant: 'display3', label: 'display/3 — 40px' },
|
||||
{ variant: 'displaySm', label: 'display/sm — 32px' },
|
||||
{ variant: 'h1', label: 'heading/1 — 36px' },
|
||||
{ variant: 'h2', label: 'heading/2 — 30px' },
|
||||
{ variant: 'h3', label: 'heading/3 — 24px' },
|
||||
{ variant: 'h4', label: 'heading/4 — 20px' },
|
||||
{ variant: 'h5', label: 'heading/5 — 18px' },
|
||||
{ variant: 'h6', label: 'heading/6 — 16px' },
|
||||
{ variant: 'bodyLg', label: 'body/lg — 18px' },
|
||||
{ variant: 'body1', label: 'body/md — 16px' },
|
||||
{ variant: 'body2', label: 'body/sm — 14px' },
|
||||
{ variant: 'bodyXs', label: 'body/xs — 12px' },
|
||||
{ variant: 'labelLg', label: 'label/lg — 16px' },
|
||||
{ variant: 'label', label: 'label/md — 14px' },
|
||||
{ variant: 'labelSm', label: 'label/sm — 12px' },
|
||||
{ variant: 'caption', label: 'caption/md — 12px' },
|
||||
{ variant: 'captionSm', label: 'caption/sm — 11px' },
|
||||
{ variant: 'overline', label: 'overline/md — 12px' },
|
||||
{ variant: 'overlineSm', label: 'overline/sm — 11px' },
|
||||
] as const;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{variants.map(({ variant, label }) => (
|
||||
<div key={variant} style={{ display: 'flex', alignItems: 'baseline', gap: 16 }}>
|
||||
<Typography variant="captionSm" color="textSecondary" sx={{ width: 160, flexShrink: 0, textAlign: 'right' }}>
|
||||
{label}
|
||||
</Typography>
|
||||
<Typography variant={variant}>{SAMPLE}</Typography>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
63
src/components/atoms/Typography/Typography.tsx
Normal file
63
src/components/atoms/Typography/Typography.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import MuiTypography from '@mui/material/Typography';
|
||||
import type { TypographyProps as MuiTypographyProps } from '@mui/material/Typography';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Props for the FA Typography component */
|
||||
export interface TypographyProps extends MuiTypographyProps {
|
||||
/** Truncate text with ellipsis after this many lines (CSS line-clamp) */
|
||||
maxLines?: number;
|
||||
}
|
||||
|
||||
// ─── Component ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Text display component for the FA design system.
|
||||
*
|
||||
* Wraps MUI Typography with FA brand fonts and type scale. All variant
|
||||
* styles (sizes, weights, line heights) come from the MUI theme which
|
||||
* maps to our design tokens.
|
||||
*
|
||||
* Variant guide (21 variants across 6 categories):
|
||||
*
|
||||
* Display (Noto Serif SC, Regular 400):
|
||||
* - `displayHero` 80px, `display1` 64px, `display2` 52px, `display3` 40px, `displaySm` 32px
|
||||
*
|
||||
* Headings (Montserrat, Bold 700):
|
||||
* - `h1` 36px, `h2` 30px, `h3` 24px, `h4` 20px, `h5` 18px, `h6` 16px
|
||||
*
|
||||
* Body (Montserrat, Medium 500):
|
||||
* - `bodyLg` 18px, `body1` 16px, `body2` 14px, `bodyXs` 12px
|
||||
*
|
||||
* Label (Montserrat, Medium 500):
|
||||
* - `labelLg` 16px, `label` 14px, `labelSm` 12px
|
||||
*
|
||||
* Caption (Montserrat, Regular 400):
|
||||
* - `caption` 12px, `captionSm` 11px
|
||||
*
|
||||
* Overline (Montserrat, SemiBold 600, uppercase):
|
||||
* - `overline` 12px, `overlineSm` 11px
|
||||
*/
|
||||
export const Typography = React.forwardRef<HTMLElement, TypographyProps>(
|
||||
({ maxLines, sx, ...props }, ref) => {
|
||||
return (
|
||||
<MuiTypography
|
||||
ref={ref}
|
||||
sx={[
|
||||
maxLines != null && {
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: maxLines,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
...(Array.isArray(sx) ? sx : [sx]),
|
||||
]}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Typography.displayName = 'Typography';
|
||||
export default Typography;
|
||||
3
src/components/atoms/Typography/index.ts
Normal file
3
src/components/atoms/Typography/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default } from './Typography';
|
||||
export { Typography } from './Typography';
|
||||
export type { TypographyProps } from './Typography';
|
||||
Reference in New Issue
Block a user