@buildcanada/components 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/assets/fonts/financier-text-regular.woff2 +0 -0
- package/src/assets/fonts/founders-grotesk-mono-regular.woff2 +0 -0
- package/src/assets/fonts/soehne-kraftig.woff2 +0 -0
- package/src/content/Card/Card.scss +281 -0
- package/src/content/Card/Card.stories.tsx +389 -0
- package/src/content/Card/Card.tsx +170 -0
- package/src/content/Card/index.ts +22 -0
- package/src/content/Hero/Hero.scss +150 -0
- package/src/content/Hero/Hero.stories.tsx +299 -0
- package/src/content/Hero/Hero.tsx +63 -0
- package/src/content/Hero/index.ts +13 -0
- package/src/content/StatBlock/StatBlock.scss +83 -0
- package/src/content/StatBlock/StatBlock.stories.tsx +331 -0
- package/src/content/StatBlock/StatBlock.tsx +52 -0
- package/src/content/StatBlock/index.ts +2 -0
- package/src/feedback/Dialog/Dialog.scss +158 -0
- package/src/feedback/Dialog/Dialog.stories.tsx +286 -0
- package/src/feedback/Dialog/Dialog.tsx +120 -0
- package/src/feedback/Dialog/index.ts +1 -0
- package/src/feedback/PopupForm/PopupForm.scss +34 -0
- package/src/feedback/PopupForm/PopupForm.stories.tsx +341 -0
- package/src/feedback/PopupForm/PopupForm.tsx +90 -0
- package/src/feedback/PopupForm/index.ts +1 -0
- package/src/index.ts +61 -0
- package/src/layout/Container/Container.scss +40 -0
- package/src/layout/Container/Container.stories.tsx +153 -0
- package/src/layout/Container/Container.tsx +29 -0
- package/src/layout/Container/index.ts +2 -0
- package/src/layout/Divider/Divider.scss +117 -0
- package/src/layout/Divider/Divider.stories.tsx +204 -0
- package/src/layout/Divider/Divider.tsx +32 -0
- package/src/layout/Divider/index.ts +2 -0
- package/src/layout/Grid/Grid.scss +81 -0
- package/src/layout/Grid/Grid.stories.tsx +263 -0
- package/src/layout/Grid/Grid.tsx +75 -0
- package/src/layout/Grid/index.ts +2 -0
- package/src/layout/Section/Section.scss +74 -0
- package/src/layout/Section/Section.stories.tsx +173 -0
- package/src/layout/Section/Section.tsx +37 -0
- package/src/layout/Section/index.ts +2 -0
- package/src/layout/Stack/Stack.scss +61 -0
- package/src/layout/Stack/Stack.stories.tsx +342 -0
- package/src/layout/Stack/Stack.tsx +48 -0
- package/src/layout/Stack/index.ts +9 -0
- package/src/navigation/Footer/Footer.scss +233 -0
- package/src/navigation/Footer/Footer.stories.tsx +351 -0
- package/src/navigation/Footer/Footer.tsx +174 -0
- package/src/navigation/Footer/index.ts +2 -0
- package/src/navigation/Header/Header.scss +325 -0
- package/src/navigation/Header/Header.stories.tsx +346 -0
- package/src/navigation/Header/Header.tsx +185 -0
- package/src/navigation/Header/index.ts +2 -0
- package/src/primitives/Button/Button.scss +218 -0
- package/src/primitives/Button/Button.stories.tsx +300 -0
- package/src/primitives/Button/Button.tsx +120 -0
- package/src/primitives/Button/index.ts +2 -0
- package/src/primitives/Checkbox/Checkbox.scss +114 -0
- package/src/primitives/Checkbox/Checkbox.stories.tsx +204 -0
- package/src/primitives/Checkbox/Checkbox.tsx +75 -0
- package/src/primitives/Checkbox/index.ts +2 -0
- package/src/primitives/TextField/TextField.scss +93 -0
- package/src/primitives/TextField/TextField.stories.tsx +265 -0
- package/src/primitives/TextField/TextField.tsx +105 -0
- package/src/primitives/TextField/index.ts +2 -0
- package/src/styles/fonts.scss +27 -0
- package/src/styles/main.scss +36 -0
- package/src/styles/tokens.scss +301 -0
- package/src/styles/typography.scss +232 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import cx from "classnames"
|
|
2
|
+
|
|
3
|
+
export type CardVariant = "default" | "project" | "memo" | "feature" | "stat" | "profile"
|
|
4
|
+
|
|
5
|
+
export interface CardProps {
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
className?: string
|
|
8
|
+
style?: React.CSSProperties
|
|
9
|
+
variant?: CardVariant
|
|
10
|
+
href?: string
|
|
11
|
+
onClick?: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function Card({
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
style,
|
|
18
|
+
variant = "default",
|
|
19
|
+
href,
|
|
20
|
+
onClick,
|
|
21
|
+
}: CardProps) {
|
|
22
|
+
const isInteractive = Boolean(href || onClick)
|
|
23
|
+
const isButton = Boolean(onClick)
|
|
24
|
+
|
|
25
|
+
const classes = cx(
|
|
26
|
+
"bc-card",
|
|
27
|
+
`bc-card--${variant}`,
|
|
28
|
+
{
|
|
29
|
+
"bc-card--interactive": isInteractive,
|
|
30
|
+
"bc-card--button": isButton,
|
|
31
|
+
},
|
|
32
|
+
className
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if (href) {
|
|
36
|
+
return (
|
|
37
|
+
<a href={href} className={classes} style={style}>
|
|
38
|
+
{children}
|
|
39
|
+
</a>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (onClick) {
|
|
44
|
+
return (
|
|
45
|
+
<button type="button" className={classes} style={style} onClick={onClick}>
|
|
46
|
+
{children}
|
|
47
|
+
</button>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className={classes} style={style}>
|
|
53
|
+
{children}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/*******************************************************************************
|
|
59
|
+
* Card Subcomponents
|
|
60
|
+
******************************************************************************/
|
|
61
|
+
|
|
62
|
+
export interface CardImageProps {
|
|
63
|
+
src: string
|
|
64
|
+
alt: string
|
|
65
|
+
className?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function CardImage({ src, alt, className }: CardImageProps) {
|
|
69
|
+
return (
|
|
70
|
+
<div className={cx("bc-card__image", className)}>
|
|
71
|
+
<img src={src} alt={alt} />
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface CardIconProps {
|
|
77
|
+
children: React.ReactNode
|
|
78
|
+
className?: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function CardIcon({ children, className }: CardIconProps) {
|
|
82
|
+
return <div className={cx("bc-card__icon", className)}>{children}</div>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface CardContentProps {
|
|
86
|
+
children: React.ReactNode
|
|
87
|
+
className?: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function CardContent({ children, className }: CardContentProps) {
|
|
91
|
+
return <div className={cx("bc-card__content", className)}>{children}</div>
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface CardTitleProps {
|
|
95
|
+
children: React.ReactNode
|
|
96
|
+
className?: string
|
|
97
|
+
as?: "h2" | "h3" | "h4" | "h5" | "h6"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function CardTitle({ children, className, as: Component = "h3" }: CardTitleProps) {
|
|
101
|
+
return <Component className={cx("bc-card__title", className)}>{children}</Component>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface CardDescriptionProps {
|
|
105
|
+
children: React.ReactNode
|
|
106
|
+
className?: string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function CardDescription({ children, className }: CardDescriptionProps) {
|
|
110
|
+
return <p className={cx("bc-card__description", className)}>{children}</p>
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface CardMetaProps {
|
|
114
|
+
children: React.ReactNode
|
|
115
|
+
className?: string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function CardMeta({ children, className }: CardMetaProps) {
|
|
119
|
+
return <div className={cx("bc-card__meta", className)}>{children}</div>
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface CardStatProps {
|
|
123
|
+
value: string | number
|
|
124
|
+
label: string
|
|
125
|
+
change?: string
|
|
126
|
+
changeDirection?: "up" | "down" | "neutral"
|
|
127
|
+
className?: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function CardStat({ value, label, change, changeDirection, className }: CardStatProps) {
|
|
131
|
+
return (
|
|
132
|
+
<div className={cx("bc-card__stat", className)}>
|
|
133
|
+
<span className="bc-card__stat-value">{value}</span>
|
|
134
|
+
<span className="bc-card__stat-label">{label}</span>
|
|
135
|
+
{change && (
|
|
136
|
+
<span
|
|
137
|
+
className={cx("bc-card__stat-change", {
|
|
138
|
+
"bc-card__stat-change--up": changeDirection === "up",
|
|
139
|
+
"bc-card__stat-change--down": changeDirection === "down",
|
|
140
|
+
})}
|
|
141
|
+
>
|
|
142
|
+
{change}
|
|
143
|
+
</span>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface CardAuthorProps {
|
|
150
|
+
name: string
|
|
151
|
+
role?: string
|
|
152
|
+
avatar?: string
|
|
153
|
+
className?: string
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function CardAuthor({ name, role, avatar, className }: CardAuthorProps) {
|
|
157
|
+
return (
|
|
158
|
+
<div className={cx("bc-card__author", className)}>
|
|
159
|
+
{avatar && (
|
|
160
|
+
<img src={avatar} alt={name} className="bc-card__author-avatar" />
|
|
161
|
+
)}
|
|
162
|
+
<div className="bc-card__author-info">
|
|
163
|
+
<span className="bc-card__author-name">{name}</span>
|
|
164
|
+
{role && <span className="bc-card__author-role">{role}</span>}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export default Card
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Card,
|
|
3
|
+
CardImage,
|
|
4
|
+
CardIcon,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardTitle,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardMeta,
|
|
9
|
+
CardStat,
|
|
10
|
+
CardAuthor,
|
|
11
|
+
type CardProps,
|
|
12
|
+
type CardVariant,
|
|
13
|
+
type CardImageProps,
|
|
14
|
+
type CardIconProps,
|
|
15
|
+
type CardContentProps,
|
|
16
|
+
type CardTitleProps,
|
|
17
|
+
type CardDescriptionProps,
|
|
18
|
+
type CardMetaProps,
|
|
19
|
+
type CardStatProps,
|
|
20
|
+
type CardAuthorProps,
|
|
21
|
+
} from "./Card.js"
|
|
22
|
+
export { default } from "./Card.js"
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
@use "../../styles/tokens" as *;
|
|
2
|
+
@use "../../styles/typography" as *;
|
|
3
|
+
|
|
4
|
+
/*******************************************************************************
|
|
5
|
+
* Hero Component
|
|
6
|
+
******************************************************************************/
|
|
7
|
+
|
|
8
|
+
.bc-hero {
|
|
9
|
+
width: 100%;
|
|
10
|
+
padding: $space-8 0;
|
|
11
|
+
|
|
12
|
+
@include md-up {
|
|
13
|
+
padding: $space-10 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/***************************************************************************
|
|
17
|
+
* Background Variants
|
|
18
|
+
***************************************************************************/
|
|
19
|
+
|
|
20
|
+
&--bg-white {
|
|
21
|
+
background-color: $white;
|
|
22
|
+
color: $text-primary;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&--bg-linen {
|
|
26
|
+
background-color: $linen;
|
|
27
|
+
color: $text-primary;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&--bg-charcoal {
|
|
31
|
+
background-color: $charcoal;
|
|
32
|
+
color: $text-inverse;
|
|
33
|
+
|
|
34
|
+
.bc-hero__subtitle {
|
|
35
|
+
color: rgba($white, 0.8);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/***************************************************************************
|
|
40
|
+
* Inner Container
|
|
41
|
+
***************************************************************************/
|
|
42
|
+
|
|
43
|
+
&__inner {
|
|
44
|
+
max-width: 1024px;
|
|
45
|
+
margin: 0 auto;
|
|
46
|
+
padding: 0 $space-3;
|
|
47
|
+
|
|
48
|
+
@include md-up {
|
|
49
|
+
padding: 0 $space-5;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/***************************************************************************
|
|
54
|
+
* Variant: Home (large, centered)
|
|
55
|
+
***************************************************************************/
|
|
56
|
+
|
|
57
|
+
&--home {
|
|
58
|
+
padding: $space-10 0;
|
|
59
|
+
|
|
60
|
+
@include md-up {
|
|
61
|
+
padding: $space-16 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.bc-hero__inner {
|
|
65
|
+
text-align: center;
|
|
66
|
+
max-width: 900px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.bc-hero__title {
|
|
70
|
+
@include display-1;
|
|
71
|
+
margin: 0 0 $space-3;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.bc-hero__subtitle {
|
|
75
|
+
@include body-lg;
|
|
76
|
+
max-width: 640px;
|
|
77
|
+
margin: 0 auto $space-4;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/***************************************************************************
|
|
82
|
+
* Variant: Page (standard page header)
|
|
83
|
+
***************************************************************************/
|
|
84
|
+
|
|
85
|
+
&--page {
|
|
86
|
+
padding: $space-6 0;
|
|
87
|
+
|
|
88
|
+
@include md-up {
|
|
89
|
+
padding: $space-8 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.bc-hero__title {
|
|
93
|
+
@include display-2;
|
|
94
|
+
margin: 0 0 $space-2;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.bc-hero__subtitle {
|
|
98
|
+
@include body-1;
|
|
99
|
+
color: $text-secondary;
|
|
100
|
+
margin: 0;
|
|
101
|
+
max-width: 640px;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/***************************************************************************
|
|
106
|
+
* Variant: Centered
|
|
107
|
+
***************************************************************************/
|
|
108
|
+
|
|
109
|
+
&--centered {
|
|
110
|
+
.bc-hero__inner {
|
|
111
|
+
text-align: center;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.bc-hero__title {
|
|
115
|
+
@include display-2;
|
|
116
|
+
margin: 0 0 $space-2;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.bc-hero__subtitle {
|
|
120
|
+
@include body-1;
|
|
121
|
+
color: $text-secondary;
|
|
122
|
+
margin: 0 auto;
|
|
123
|
+
max-width: 640px;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/***************************************************************************
|
|
128
|
+
* Subcomponents
|
|
129
|
+
***************************************************************************/
|
|
130
|
+
|
|
131
|
+
&__title {
|
|
132
|
+
color: inherit;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&__subtitle {
|
|
136
|
+
color: $text-secondary;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&__actions {
|
|
140
|
+
display: flex;
|
|
141
|
+
flex-wrap: wrap;
|
|
142
|
+
gap: $space-2;
|
|
143
|
+
margin-top: $space-4;
|
|
144
|
+
|
|
145
|
+
.bc-hero--home &,
|
|
146
|
+
.bc-hero--centered & {
|
|
147
|
+
justify-content: center;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
import { within, expect } from "@storybook/test"
|
|
3
|
+
|
|
4
|
+
import { Hero, HeroTitle, HeroSubtitle, HeroActions } from "./Hero"
|
|
5
|
+
import { Button } from "../../primitives/Button"
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Hero> = {
|
|
8
|
+
title: "Components/Content/Hero",
|
|
9
|
+
component: Hero,
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component: `
|
|
14
|
+
A hero section component for page headers with title, subtitle, and call-to-action buttons.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
\`\`\`tsx
|
|
19
|
+
import { Hero, HeroTitle, HeroSubtitle, HeroActions } from "@buildcanada/components"
|
|
20
|
+
import { Button } from "@buildcanada/components"
|
|
21
|
+
|
|
22
|
+
<Hero variant="home" background="linen">
|
|
23
|
+
<HeroTitle>Welcome to Build Canada</HeroTitle>
|
|
24
|
+
<HeroSubtitle>
|
|
25
|
+
Making government data accessible for all Canadians.
|
|
26
|
+
</HeroSubtitle>
|
|
27
|
+
<HeroActions>
|
|
28
|
+
<Button text="Get Started" variant="solid-auburn" />
|
|
29
|
+
<Button text="Learn More" variant="outline-charcoal" icon={null} />
|
|
30
|
+
</HeroActions>
|
|
31
|
+
</Hero>
|
|
32
|
+
\`\`\`
|
|
33
|
+
|
|
34
|
+
## Variants
|
|
35
|
+
|
|
36
|
+
- **home**: Large, impactful hero for homepages
|
|
37
|
+
- **page**: Standard page header with title and description
|
|
38
|
+
- **centered**: Center-aligned for special sections or CTAs
|
|
39
|
+
|
|
40
|
+
## Sub-components
|
|
41
|
+
|
|
42
|
+
- \`HeroTitle\`: Main heading (h1)
|
|
43
|
+
- \`HeroSubtitle\`: Supporting text
|
|
44
|
+
- \`HeroActions\`: Container for CTA buttons
|
|
45
|
+
`,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
argTypes: {
|
|
50
|
+
variant: {
|
|
51
|
+
control: "select",
|
|
52
|
+
options: ["home", "page", "centered"],
|
|
53
|
+
description: "Layout variant of the hero",
|
|
54
|
+
},
|
|
55
|
+
background: {
|
|
56
|
+
control: "select",
|
|
57
|
+
options: ["white", "linen", "charcoal"],
|
|
58
|
+
description: "Background color",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default meta
|
|
64
|
+
type Story = StoryObj<typeof Hero>
|
|
65
|
+
|
|
66
|
+
export const Default: Story = {
|
|
67
|
+
args: {
|
|
68
|
+
variant: "page",
|
|
69
|
+
background: "linen",
|
|
70
|
+
children: (
|
|
71
|
+
<>
|
|
72
|
+
<HeroTitle>Page Title</HeroTitle>
|
|
73
|
+
<HeroSubtitle>
|
|
74
|
+
A brief description of what this page is about and what
|
|
75
|
+
users can expect to find here.
|
|
76
|
+
</HeroSubtitle>
|
|
77
|
+
</>
|
|
78
|
+
),
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const HomeHero: Story = {
|
|
83
|
+
args: {
|
|
84
|
+
variant: "home",
|
|
85
|
+
background: "linen",
|
|
86
|
+
children: (
|
|
87
|
+
<>
|
|
88
|
+
<HeroTitle>Build Canada</HeroTitle>
|
|
89
|
+
<HeroSubtitle>
|
|
90
|
+
Making government data accessible and understandable for all Canadians.
|
|
91
|
+
Explore spending, analyze trends, and discover insights.
|
|
92
|
+
</HeroSubtitle>
|
|
93
|
+
<HeroActions>
|
|
94
|
+
<Button text="Explore Data" variant="solid-auburn" />
|
|
95
|
+
<Button text="Learn More" variant="outline-charcoal" icon={null} />
|
|
96
|
+
</HeroActions>
|
|
97
|
+
</>
|
|
98
|
+
),
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const PageHero: Story = {
|
|
103
|
+
args: {
|
|
104
|
+
variant: "page",
|
|
105
|
+
background: "linen",
|
|
106
|
+
children: (
|
|
107
|
+
<>
|
|
108
|
+
<HeroTitle>About Us</HeroTitle>
|
|
109
|
+
<HeroSubtitle>
|
|
110
|
+
Learn about our mission, team, and the work we do to make
|
|
111
|
+
government spending transparent.
|
|
112
|
+
</HeroSubtitle>
|
|
113
|
+
</>
|
|
114
|
+
),
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const CenteredHero: Story = {
|
|
119
|
+
args: {
|
|
120
|
+
variant: "centered",
|
|
121
|
+
background: "linen",
|
|
122
|
+
children: (
|
|
123
|
+
<>
|
|
124
|
+
<HeroTitle>Canada Spends</HeroTitle>
|
|
125
|
+
<HeroSubtitle>
|
|
126
|
+
Interactive visualizations of government spending across
|
|
127
|
+
all levels of government in Canada.
|
|
128
|
+
</HeroSubtitle>
|
|
129
|
+
<HeroActions>
|
|
130
|
+
<Button text="View Charts" variant="solid-auburn" />
|
|
131
|
+
</HeroActions>
|
|
132
|
+
</>
|
|
133
|
+
),
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const WhiteBackground: Story = {
|
|
138
|
+
args: {
|
|
139
|
+
variant: "page",
|
|
140
|
+
background: "white",
|
|
141
|
+
children: (
|
|
142
|
+
<>
|
|
143
|
+
<HeroTitle>Research & Reports</HeroTitle>
|
|
144
|
+
<HeroSubtitle>
|
|
145
|
+
In-depth analysis and reports on Canadian government policy
|
|
146
|
+
and spending patterns.
|
|
147
|
+
</HeroSubtitle>
|
|
148
|
+
</>
|
|
149
|
+
),
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const CharcoalBackground: Story = {
|
|
154
|
+
args: {
|
|
155
|
+
variant: "centered",
|
|
156
|
+
background: "charcoal",
|
|
157
|
+
children: (
|
|
158
|
+
<>
|
|
159
|
+
<HeroTitle>Join Our Newsletter</HeroTitle>
|
|
160
|
+
<HeroSubtitle>
|
|
161
|
+
Get the latest insights and analysis delivered directly to your inbox.
|
|
162
|
+
</HeroSubtitle>
|
|
163
|
+
<HeroActions>
|
|
164
|
+
<Button text="Subscribe" variant="solid-auburn" />
|
|
165
|
+
</HeroActions>
|
|
166
|
+
</>
|
|
167
|
+
),
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const WithMultipleActions: Story = {
|
|
172
|
+
args: {
|
|
173
|
+
variant: "home",
|
|
174
|
+
background: "linen",
|
|
175
|
+
children: (
|
|
176
|
+
<>
|
|
177
|
+
<HeroTitle>Open Government Data</HeroTitle>
|
|
178
|
+
<HeroSubtitle>
|
|
179
|
+
Access, analyze, and visualize government spending data
|
|
180
|
+
with our free and open-source tools.
|
|
181
|
+
</HeroSubtitle>
|
|
182
|
+
<HeroActions>
|
|
183
|
+
<Button text="Get Started" variant="solid-auburn" />
|
|
184
|
+
<Button text="View Demo" variant="outline-charcoal" icon={null} />
|
|
185
|
+
<Button text="Documentation" variant="outline-charcoal" icon={null} />
|
|
186
|
+
</HeroActions>
|
|
187
|
+
</>
|
|
188
|
+
),
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export const MinimalHero: Story = {
|
|
193
|
+
args: {
|
|
194
|
+
variant: "page",
|
|
195
|
+
background: "linen",
|
|
196
|
+
children: (
|
|
197
|
+
<HeroTitle>Contact</HeroTitle>
|
|
198
|
+
),
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const LongContent: Story = {
|
|
203
|
+
args: {
|
|
204
|
+
variant: "home",
|
|
205
|
+
background: "linen",
|
|
206
|
+
children: (
|
|
207
|
+
<>
|
|
208
|
+
<HeroTitle>Understanding Government Spending in Canada</HeroTitle>
|
|
209
|
+
<HeroSubtitle>
|
|
210
|
+
Our platform provides comprehensive data visualization and analysis tools
|
|
211
|
+
that help citizens, researchers, and policymakers understand how public
|
|
212
|
+
money is spent across federal, provincial, and municipal governments.
|
|
213
|
+
From healthcare to infrastructure, education to defense, explore the
|
|
214
|
+
full picture of Canadian government finances.
|
|
215
|
+
</HeroSubtitle>
|
|
216
|
+
<HeroActions>
|
|
217
|
+
<Button text="Explore the Data" variant="solid-auburn" />
|
|
218
|
+
<Button text="Read Our Methodology" variant="outline-charcoal" icon={null} />
|
|
219
|
+
</HeroActions>
|
|
220
|
+
</>
|
|
221
|
+
),
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const AllVariants: Story = {
|
|
226
|
+
render: () => (
|
|
227
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "0" }}>
|
|
228
|
+
<Hero variant="home" background="linen">
|
|
229
|
+
<HeroTitle>Home Variant</HeroTitle>
|
|
230
|
+
<HeroSubtitle>Large, impactful hero for homepages with prominent CTAs.</HeroSubtitle>
|
|
231
|
+
<HeroActions>
|
|
232
|
+
<Button text="Primary Action" variant="solid-auburn" />
|
|
233
|
+
<Button text="Secondary" variant="outline-charcoal" icon={null} />
|
|
234
|
+
</HeroActions>
|
|
235
|
+
</Hero>
|
|
236
|
+
<Hero variant="page" background="white">
|
|
237
|
+
<HeroTitle>Page Variant</HeroTitle>
|
|
238
|
+
<HeroSubtitle>Standard page header with title and optional description.</HeroSubtitle>
|
|
239
|
+
</Hero>
|
|
240
|
+
<Hero variant="centered" background="charcoal">
|
|
241
|
+
<HeroTitle>Centered Variant</HeroTitle>
|
|
242
|
+
<HeroSubtitle>Center-aligned content for special sections or CTAs.</HeroSubtitle>
|
|
243
|
+
<HeroActions>
|
|
244
|
+
<Button text="Take Action" variant="solid-auburn" />
|
|
245
|
+
</HeroActions>
|
|
246
|
+
</Hero>
|
|
247
|
+
</div>
|
|
248
|
+
),
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Interactive test: Title and subtitle render correctly
|
|
252
|
+
export const ContentTest: Story = {
|
|
253
|
+
args: {
|
|
254
|
+
variant: "page",
|
|
255
|
+
background: "linen",
|
|
256
|
+
children: (
|
|
257
|
+
<>
|
|
258
|
+
<HeroTitle>Test Title</HeroTitle>
|
|
259
|
+
<HeroSubtitle>Test subtitle text content.</HeroSubtitle>
|
|
260
|
+
</>
|
|
261
|
+
),
|
|
262
|
+
},
|
|
263
|
+
play: async ({ canvasElement }) => {
|
|
264
|
+
const canvas = within(canvasElement)
|
|
265
|
+
|
|
266
|
+
const title = canvas.getByText("Test Title")
|
|
267
|
+
const subtitle = canvas.getByText("Test subtitle text content.")
|
|
268
|
+
|
|
269
|
+
await expect(title).toBeInTheDocument()
|
|
270
|
+
await expect(subtitle).toBeInTheDocument()
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Interactive test: Action buttons are rendered
|
|
275
|
+
export const ActionsTest: Story = {
|
|
276
|
+
args: {
|
|
277
|
+
variant: "home",
|
|
278
|
+
background: "linen",
|
|
279
|
+
children: (
|
|
280
|
+
<>
|
|
281
|
+
<HeroTitle>Hero with Actions</HeroTitle>
|
|
282
|
+
<HeroSubtitle>Testing action buttons.</HeroSubtitle>
|
|
283
|
+
<HeroActions>
|
|
284
|
+
<Button text="Primary Button" variant="solid-auburn" />
|
|
285
|
+
<Button text="Secondary Button" variant="outline-charcoal" icon={null} />
|
|
286
|
+
</HeroActions>
|
|
287
|
+
</>
|
|
288
|
+
),
|
|
289
|
+
},
|
|
290
|
+
play: async ({ canvasElement }) => {
|
|
291
|
+
const canvas = within(canvasElement)
|
|
292
|
+
|
|
293
|
+
const primaryBtn = canvas.getByRole("button", { name: /primary button/i })
|
|
294
|
+
const secondaryBtn = canvas.getByRole("button", { name: /secondary button/i })
|
|
295
|
+
|
|
296
|
+
await expect(primaryBtn).toBeInTheDocument()
|
|
297
|
+
await expect(secondaryBtn).toBeInTheDocument()
|
|
298
|
+
},
|
|
299
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import cx from "classnames"
|
|
2
|
+
|
|
3
|
+
export type HeroVariant = "home" | "page" | "centered"
|
|
4
|
+
export type HeroBackground = "white" | "linen" | "charcoal"
|
|
5
|
+
|
|
6
|
+
export interface HeroProps {
|
|
7
|
+
children: React.ReactNode
|
|
8
|
+
className?: string
|
|
9
|
+
style?: React.CSSProperties
|
|
10
|
+
variant?: HeroVariant
|
|
11
|
+
background?: HeroBackground
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function Hero({
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
style,
|
|
18
|
+
variant = "page",
|
|
19
|
+
background = "linen",
|
|
20
|
+
}: HeroProps) {
|
|
21
|
+
const classes = cx(
|
|
22
|
+
"bc-hero",
|
|
23
|
+
`bc-hero--${variant}`,
|
|
24
|
+
`bc-hero--bg-${background}`,
|
|
25
|
+
className
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={classes} style={style}>
|
|
30
|
+
<div className="bc-hero__inner">{children}</div>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface HeroTitleProps {
|
|
36
|
+
children: React.ReactNode
|
|
37
|
+
className?: string
|
|
38
|
+
as?: "h1" | "h2"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function HeroTitle({ children, className, as: Component = "h1" }: HeroTitleProps) {
|
|
42
|
+
return <Component className={cx("bc-hero__title", className)}>{children}</Component>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface HeroSubtitleProps {
|
|
46
|
+
children: React.ReactNode
|
|
47
|
+
className?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function HeroSubtitle({ children, className }: HeroSubtitleProps) {
|
|
51
|
+
return <p className={cx("bc-hero__subtitle", className)}>{children}</p>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface HeroActionsProps {
|
|
55
|
+
children: React.ReactNode
|
|
56
|
+
className?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function HeroActions({ children, className }: HeroActionsProps) {
|
|
60
|
+
return <div className={cx("bc-hero__actions", className)}>{children}</div>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default Hero
|