@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,341 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
|
|
4
|
+
import { PopupForm } from "./PopupForm"
|
|
5
|
+
import { Button } from "../../primitives/Button"
|
|
6
|
+
import { TextField } from "../../primitives/TextField"
|
|
7
|
+
import { Checkbox } from "../../primitives/Checkbox"
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof PopupForm> = {
|
|
10
|
+
title: "Components/Feedback/PopupForm",
|
|
11
|
+
component: PopupForm,
|
|
12
|
+
parameters: {
|
|
13
|
+
layout: "fullscreen",
|
|
14
|
+
docs: {
|
|
15
|
+
description: {
|
|
16
|
+
component: `
|
|
17
|
+
A non-modal form panel that composes Dialog with form elements and action buttons.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
\`\`\`tsx
|
|
22
|
+
import { PopupForm, TextField, Checkbox } from "@buildcanada/components"
|
|
23
|
+
|
|
24
|
+
function MyComponent() {
|
|
25
|
+
const [open, setOpen] = useState(false)
|
|
26
|
+
|
|
27
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
28
|
+
console.log("Form submitted")
|
|
29
|
+
setOpen(false)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<Button text="Open Form" onClick={() => setOpen(true)} />
|
|
35
|
+
<PopupForm
|
|
36
|
+
open={open}
|
|
37
|
+
onClose={() => setOpen(false)}
|
|
38
|
+
onSubmit={handleSubmit}
|
|
39
|
+
title="Contact Us"
|
|
40
|
+
position="bottom-right"
|
|
41
|
+
>
|
|
42
|
+
<TextField label="Name" required />
|
|
43
|
+
<TextField label="Email" type="email" required />
|
|
44
|
+
<Checkbox label="Subscribe to newsletter" />
|
|
45
|
+
</PopupForm>
|
|
46
|
+
</>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
## Key Features
|
|
52
|
+
|
|
53
|
+
- **Non-blocking**: Users can still interact with the page
|
|
54
|
+
- **Corner positioning**: Place in any corner of the screen
|
|
55
|
+
- **Composable**: Works with TextField, Checkbox, and other form primitives
|
|
56
|
+
|
|
57
|
+
## Props
|
|
58
|
+
|
|
59
|
+
- **position**: Where to place the form (default: "bottom-right")
|
|
60
|
+
- **offset**: Distance from screen edges (default: 16px)
|
|
61
|
+
- **submitText** / **cancelText**: Button labels
|
|
62
|
+
- **isSubmitting**: Shows loading state on submit button
|
|
63
|
+
`,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
args: {
|
|
68
|
+
open: true,
|
|
69
|
+
title: "Form Title",
|
|
70
|
+
description: "",
|
|
71
|
+
position: "bottom-right",
|
|
72
|
+
offset: 16,
|
|
73
|
+
submitText: "Submit",
|
|
74
|
+
cancelText: "Cancel",
|
|
75
|
+
showCancel: true,
|
|
76
|
+
isSubmitting: false,
|
|
77
|
+
submitDisabled: false,
|
|
78
|
+
closeOnEscape: true,
|
|
79
|
+
},
|
|
80
|
+
argTypes: {
|
|
81
|
+
open: {
|
|
82
|
+
control: "boolean",
|
|
83
|
+
description: "Whether the form is open",
|
|
84
|
+
},
|
|
85
|
+
title: {
|
|
86
|
+
control: "text",
|
|
87
|
+
description: "Title displayed in the form header",
|
|
88
|
+
},
|
|
89
|
+
description: {
|
|
90
|
+
control: "text",
|
|
91
|
+
description: "Optional description below the title",
|
|
92
|
+
},
|
|
93
|
+
position: {
|
|
94
|
+
control: "select",
|
|
95
|
+
options: ["top-left", "top-right", "bottom-left", "bottom-right", "center"],
|
|
96
|
+
description: "Position of the popup form on screen",
|
|
97
|
+
},
|
|
98
|
+
offset: {
|
|
99
|
+
control: { type: "number", min: 0, max: 100 },
|
|
100
|
+
description: "Distance from screen edges in pixels",
|
|
101
|
+
},
|
|
102
|
+
submitText: {
|
|
103
|
+
control: "text",
|
|
104
|
+
description: "Text for the submit button",
|
|
105
|
+
},
|
|
106
|
+
cancelText: {
|
|
107
|
+
control: "text",
|
|
108
|
+
description: "Text for the cancel button",
|
|
109
|
+
},
|
|
110
|
+
showCancel: {
|
|
111
|
+
control: "boolean",
|
|
112
|
+
description: "Whether to show the cancel button",
|
|
113
|
+
},
|
|
114
|
+
isSubmitting: {
|
|
115
|
+
control: "boolean",
|
|
116
|
+
description: "Whether the form is currently submitting",
|
|
117
|
+
},
|
|
118
|
+
submitDisabled: {
|
|
119
|
+
control: "boolean",
|
|
120
|
+
description: "Whether the submit button is disabled",
|
|
121
|
+
},
|
|
122
|
+
closeOnEscape: {
|
|
123
|
+
control: "boolean",
|
|
124
|
+
description: "Whether pressing Escape closes the form",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
decorators: [
|
|
128
|
+
(Story) => (
|
|
129
|
+
<div style={{ minHeight: "400px" }}>
|
|
130
|
+
<PageContent />
|
|
131
|
+
<Story />
|
|
132
|
+
</div>
|
|
133
|
+
),
|
|
134
|
+
],
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default meta
|
|
138
|
+
type Story = StoryObj<typeof PopupForm>
|
|
139
|
+
|
|
140
|
+
// Page content to demonstrate non-blocking behavior
|
|
141
|
+
function PageContent() {
|
|
142
|
+
return (
|
|
143
|
+
<div style={{ padding: "24px", fontFamily: "sans-serif" }}>
|
|
144
|
+
<h1>Page Content</h1>
|
|
145
|
+
<p>This content remains interactive when the form is open.</p>
|
|
146
|
+
<p>
|
|
147
|
+
<button onClick={() => console.log("Button clicked!")} style={{ padding: "8px 16px" }}>
|
|
148
|
+
Clickable Button
|
|
149
|
+
</button>
|
|
150
|
+
</p>
|
|
151
|
+
<p>
|
|
152
|
+
<input
|
|
153
|
+
type="text"
|
|
154
|
+
placeholder="Type here while form is open..."
|
|
155
|
+
style={{ padding: "8px", width: "300px" }}
|
|
156
|
+
/>
|
|
157
|
+
</p>
|
|
158
|
+
</div>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Template that renders the form with args
|
|
163
|
+
function PopupFormTemplate(args: React.ComponentProps<typeof PopupForm> & { children?: React.ReactNode }) {
|
|
164
|
+
return (
|
|
165
|
+
<PopupForm
|
|
166
|
+
{...args}
|
|
167
|
+
onClose={() => console.log("Form closed")}
|
|
168
|
+
onSubmit={() => console.log("Form submitted")}
|
|
169
|
+
>
|
|
170
|
+
{args.children || (
|
|
171
|
+
<>
|
|
172
|
+
<TextField label="Name" placeholder="Your name" />
|
|
173
|
+
<TextField label="Email" type="email" placeholder="you@example.com" />
|
|
174
|
+
</>
|
|
175
|
+
)}
|
|
176
|
+
</PopupForm>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const Default: Story = {
|
|
181
|
+
render: (args) => <PopupFormTemplate {...args} />,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const WithDescription: Story = {
|
|
185
|
+
args: {
|
|
186
|
+
title: "Contact Us",
|
|
187
|
+
description: "We'd love to hear from you.",
|
|
188
|
+
},
|
|
189
|
+
render: (args) => <PopupFormTemplate {...args} />,
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export const NewsletterSignup: Story = {
|
|
193
|
+
args: {
|
|
194
|
+
title: "Subscribe",
|
|
195
|
+
description: "Get updates delivered to your inbox.",
|
|
196
|
+
submitText: "Subscribe",
|
|
197
|
+
},
|
|
198
|
+
render: (args) => (
|
|
199
|
+
<PopupFormTemplate {...args}>
|
|
200
|
+
<TextField label="Email Address" type="email" placeholder="you@example.com" />
|
|
201
|
+
<Checkbox label="I agree to receive emails" />
|
|
202
|
+
</PopupFormTemplate>
|
|
203
|
+
),
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export const TopLeftPosition: Story = {
|
|
207
|
+
args: {
|
|
208
|
+
title: "Quick Feedback",
|
|
209
|
+
position: "top-left",
|
|
210
|
+
submitText: "Send",
|
|
211
|
+
},
|
|
212
|
+
render: (args) => (
|
|
213
|
+
<PopupFormTemplate {...args}>
|
|
214
|
+
<TextField label="Your feedback" placeholder="Tell us what you think..." />
|
|
215
|
+
</PopupFormTemplate>
|
|
216
|
+
),
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export const CenteredPosition: Story = {
|
|
220
|
+
args: {
|
|
221
|
+
title: "Sign In",
|
|
222
|
+
position: "center",
|
|
223
|
+
submitText: "Sign In",
|
|
224
|
+
showCancel: false,
|
|
225
|
+
},
|
|
226
|
+
render: (args) => (
|
|
227
|
+
<PopupFormTemplate {...args}>
|
|
228
|
+
<TextField label="Email" type="email" placeholder="you@example.com" />
|
|
229
|
+
<TextField label="Password" type="password" placeholder="Enter password" />
|
|
230
|
+
<Checkbox label="Remember me" />
|
|
231
|
+
</PopupFormTemplate>
|
|
232
|
+
),
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export const NoCancel: Story = {
|
|
236
|
+
args: {
|
|
237
|
+
title: "Required Action",
|
|
238
|
+
description: "Please complete this form to continue.",
|
|
239
|
+
submitText: "Continue",
|
|
240
|
+
showCancel: false,
|
|
241
|
+
},
|
|
242
|
+
render: (args) => (
|
|
243
|
+
<PopupFormTemplate {...args}>
|
|
244
|
+
<Checkbox label="I accept the terms and conditions" />
|
|
245
|
+
</PopupFormTemplate>
|
|
246
|
+
),
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export const CustomOffset: Story = {
|
|
250
|
+
args: {
|
|
251
|
+
title: "Settings",
|
|
252
|
+
offset: 48,
|
|
253
|
+
submitText: "Save",
|
|
254
|
+
},
|
|
255
|
+
render: (args) => (
|
|
256
|
+
<PopupFormTemplate {...args}>
|
|
257
|
+
<TextField label="Display Name" placeholder="Enter name" />
|
|
258
|
+
<Checkbox label="Enable notifications" />
|
|
259
|
+
</PopupFormTemplate>
|
|
260
|
+
),
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export const SubmittingState: Story = {
|
|
264
|
+
args: {
|
|
265
|
+
title: "Save Changes",
|
|
266
|
+
submitText: "Save",
|
|
267
|
+
isSubmitting: true,
|
|
268
|
+
},
|
|
269
|
+
render: (args) => (
|
|
270
|
+
<PopupFormTemplate {...args}>
|
|
271
|
+
<TextField label="Name" defaultValue="John Doe" />
|
|
272
|
+
<TextField label="Email" type="email" defaultValue="john@example.com" />
|
|
273
|
+
</PopupFormTemplate>
|
|
274
|
+
),
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export const DisabledSubmit: Story = {
|
|
278
|
+
args: {
|
|
279
|
+
title: "Subscribe",
|
|
280
|
+
submitText: "Subscribe",
|
|
281
|
+
submitDisabled: true,
|
|
282
|
+
},
|
|
283
|
+
render: (args) => (
|
|
284
|
+
<PopupFormTemplate {...args}>
|
|
285
|
+
<TextField label="Email" type="email" placeholder="Enter email to enable submit" />
|
|
286
|
+
</PopupFormTemplate>
|
|
287
|
+
),
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export const AllPositions: Story = {
|
|
291
|
+
parameters: {
|
|
292
|
+
controls: { disable: true },
|
|
293
|
+
},
|
|
294
|
+
render: function AllPositionsDemo() {
|
|
295
|
+
const [openForm, setOpenForm] = useState<string | null>(null)
|
|
296
|
+
|
|
297
|
+
const positions = [
|
|
298
|
+
{ key: "top-left", label: "Top Left" },
|
|
299
|
+
{ key: "top-right", label: "Top Right" },
|
|
300
|
+
{ key: "bottom-left", label: "Bottom Left" },
|
|
301
|
+
{ key: "bottom-right", label: "Bottom Right" },
|
|
302
|
+
{ key: "center", label: "Center" },
|
|
303
|
+
] as const
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<>
|
|
307
|
+
<div style={{
|
|
308
|
+
position: "fixed",
|
|
309
|
+
top: "24px",
|
|
310
|
+
right: "24px",
|
|
311
|
+
display: "flex",
|
|
312
|
+
flexDirection: "column",
|
|
313
|
+
gap: "8px",
|
|
314
|
+
zIndex: 1
|
|
315
|
+
}}>
|
|
316
|
+
{positions.map(({ key, label }) => (
|
|
317
|
+
<Button
|
|
318
|
+
key={key}
|
|
319
|
+
text={label}
|
|
320
|
+
onClick={() => setOpenForm(openForm === key ? null : key)}
|
|
321
|
+
variant={openForm === key ? "solid-auburn" : "outline-charcoal"}
|
|
322
|
+
/>
|
|
323
|
+
))}
|
|
324
|
+
</div>
|
|
325
|
+
{positions.map(({ key, label }) => (
|
|
326
|
+
<PopupForm
|
|
327
|
+
key={key}
|
|
328
|
+
open={openForm === key}
|
|
329
|
+
onClose={() => setOpenForm(null)}
|
|
330
|
+
onSubmit={() => setOpenForm(null)}
|
|
331
|
+
title={label}
|
|
332
|
+
position={key}
|
|
333
|
+
submitText="Submit"
|
|
334
|
+
>
|
|
335
|
+
<TextField label="Field" placeholder="Enter text" />
|
|
336
|
+
</PopupForm>
|
|
337
|
+
))}
|
|
338
|
+
</>
|
|
339
|
+
)
|
|
340
|
+
},
|
|
341
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import cx from "classnames"
|
|
2
|
+
|
|
3
|
+
import { Dialog, type DialogPosition } from "../Dialog/index.js"
|
|
4
|
+
import { Button, type ButtonVariant } from "../../primitives/Button/index.js"
|
|
5
|
+
|
|
6
|
+
export interface PopupFormProps {
|
|
7
|
+
open: boolean
|
|
8
|
+
onClose: () => void
|
|
9
|
+
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void
|
|
10
|
+
children: React.ReactNode
|
|
11
|
+
title?: string
|
|
12
|
+
description?: string
|
|
13
|
+
position?: DialogPosition
|
|
14
|
+
offset?: number
|
|
15
|
+
className?: string
|
|
16
|
+
submitText?: string
|
|
17
|
+
submitVariant?: ButtonVariant
|
|
18
|
+
cancelText?: string
|
|
19
|
+
cancelVariant?: ButtonVariant
|
|
20
|
+
showCancel?: boolean
|
|
21
|
+
isSubmitting?: boolean
|
|
22
|
+
submitDisabled?: boolean
|
|
23
|
+
closeOnEscape?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function PopupForm({
|
|
27
|
+
open,
|
|
28
|
+
onClose,
|
|
29
|
+
onSubmit,
|
|
30
|
+
children,
|
|
31
|
+
title,
|
|
32
|
+
description,
|
|
33
|
+
position = "bottom-right",
|
|
34
|
+
offset,
|
|
35
|
+
className,
|
|
36
|
+
submitText = "Submit",
|
|
37
|
+
submitVariant = "solid-auburn",
|
|
38
|
+
cancelText = "Cancel",
|
|
39
|
+
cancelVariant = "outline-charcoal",
|
|
40
|
+
showCancel = true,
|
|
41
|
+
isSubmitting = false,
|
|
42
|
+
submitDisabled = false,
|
|
43
|
+
closeOnEscape = true,
|
|
44
|
+
}: PopupFormProps) {
|
|
45
|
+
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
46
|
+
e.preventDefault()
|
|
47
|
+
onSubmit(e)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const classes = cx("bc-popup-form", className)
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Dialog
|
|
54
|
+
open={open}
|
|
55
|
+
onClose={onClose}
|
|
56
|
+
title={title}
|
|
57
|
+
description={description}
|
|
58
|
+
position={position}
|
|
59
|
+
offset={offset}
|
|
60
|
+
closeOnEscape={closeOnEscape}
|
|
61
|
+
>
|
|
62
|
+
<form onSubmit={handleSubmit} className={classes}>
|
|
63
|
+
<div className="bc-popup-form__fields">
|
|
64
|
+
{children}
|
|
65
|
+
</div>
|
|
66
|
+
<div className="bc-popup-form__actions">
|
|
67
|
+
{showCancel && (
|
|
68
|
+
<Button
|
|
69
|
+
type="button"
|
|
70
|
+
text={cancelText}
|
|
71
|
+
variant={cancelVariant}
|
|
72
|
+
onClick={onClose}
|
|
73
|
+
disabled={isSubmitting}
|
|
74
|
+
icon={null}
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
<Button
|
|
78
|
+
type="submit"
|
|
79
|
+
text={isSubmitting ? "Submitting..." : submitText}
|
|
80
|
+
variant={submitVariant}
|
|
81
|
+
disabled={submitDisabled || isSubmitting}
|
|
82
|
+
icon={null}
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</form>
|
|
86
|
+
</Dialog>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default PopupForm
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PopupForm, type PopupFormProps } from "./PopupForm.js"
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
* @buildcanada/components
|
|
3
|
+
*
|
|
4
|
+
* Build Canada Design System Components
|
|
5
|
+
******************************************************************************/
|
|
6
|
+
|
|
7
|
+
// Primitives
|
|
8
|
+
export { Button, type ButtonProps, type ButtonVariant, type ButtonSize } from "./primitives/Button/index.js"
|
|
9
|
+
export { TextField, type TextFieldProps, type TextFieldType } from "./primitives/TextField/index.js"
|
|
10
|
+
export { Checkbox, type CheckboxProps } from "./primitives/Checkbox/index.js"
|
|
11
|
+
|
|
12
|
+
// Layout
|
|
13
|
+
export { Container, type ContainerProps, type ContainerSize } from "./layout/Container/index.js"
|
|
14
|
+
export { Section, type SectionProps, type SectionBackground, type SectionSpacing } from "./layout/Section/index.js"
|
|
15
|
+
export { Grid, GridItem, type GridProps, type GridItemProps, type GridColumns, type GridGap } from "./layout/Grid/index.js"
|
|
16
|
+
export { Stack, type StackProps, type StackDirection, type StackSpacing, type StackAlign, type StackJustify } from "./layout/Stack/index.js"
|
|
17
|
+
export { Divider, type DividerProps, type DividerOrientation, type DividerVariant } from "./layout/Divider/index.js"
|
|
18
|
+
|
|
19
|
+
// Content
|
|
20
|
+
export {
|
|
21
|
+
Card,
|
|
22
|
+
CardImage,
|
|
23
|
+
CardIcon,
|
|
24
|
+
CardContent,
|
|
25
|
+
CardTitle,
|
|
26
|
+
CardDescription,
|
|
27
|
+
CardMeta,
|
|
28
|
+
CardStat,
|
|
29
|
+
CardAuthor,
|
|
30
|
+
type CardProps,
|
|
31
|
+
type CardVariant,
|
|
32
|
+
type CardImageProps,
|
|
33
|
+
type CardIconProps,
|
|
34
|
+
type CardContentProps,
|
|
35
|
+
type CardTitleProps,
|
|
36
|
+
type CardDescriptionProps,
|
|
37
|
+
type CardMetaProps,
|
|
38
|
+
type CardStatProps,
|
|
39
|
+
type CardAuthorProps,
|
|
40
|
+
} from "./content/Card/index.js"
|
|
41
|
+
export {
|
|
42
|
+
Hero,
|
|
43
|
+
HeroTitle,
|
|
44
|
+
HeroSubtitle,
|
|
45
|
+
HeroActions,
|
|
46
|
+
type HeroProps,
|
|
47
|
+
type HeroVariant,
|
|
48
|
+
type HeroBackground,
|
|
49
|
+
type HeroTitleProps,
|
|
50
|
+
type HeroSubtitleProps,
|
|
51
|
+
type HeroActionsProps,
|
|
52
|
+
} from "./content/Hero/index.js"
|
|
53
|
+
export { StatBlock, type StatBlockProps, type StatBlockSize, type StatBlockTrend } from "./content/StatBlock/index.js"
|
|
54
|
+
|
|
55
|
+
// Navigation
|
|
56
|
+
export { Header, type HeaderProps, type NavItem } from "./navigation/Header/index.js"
|
|
57
|
+
export { Footer, type FooterProps, type FooterLink, type SocialLink } from "./navigation/Footer/index.js"
|
|
58
|
+
|
|
59
|
+
// Feedback
|
|
60
|
+
export { Dialog, type DialogProps, type DialogPosition } from "./feedback/Dialog/index.js"
|
|
61
|
+
export { PopupForm, type PopupFormProps } from "./feedback/PopupForm/index.js"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@use "../../styles/tokens" as *;
|
|
2
|
+
|
|
3
|
+
/*******************************************************************************
|
|
4
|
+
* Container Component
|
|
5
|
+
*
|
|
6
|
+
* Max-width container with responsive padding
|
|
7
|
+
******************************************************************************/
|
|
8
|
+
|
|
9
|
+
.bc-container {
|
|
10
|
+
width: 100%;
|
|
11
|
+
margin-left: auto;
|
|
12
|
+
margin-right: auto;
|
|
13
|
+
padding-left: $space-3;
|
|
14
|
+
padding-right: $space-3;
|
|
15
|
+
|
|
16
|
+
@include md-up {
|
|
17
|
+
padding-left: $space-5;
|
|
18
|
+
padding-right: $space-5;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&--sm {
|
|
22
|
+
max-width: 640px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&--md {
|
|
26
|
+
max-width: 768px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&--lg {
|
|
30
|
+
max-width: 1024px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&--xl {
|
|
34
|
+
max-width: 1280px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&--full {
|
|
38
|
+
max-width: none;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
|
|
3
|
+
import { Container } from "./Container"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Container> = {
|
|
6
|
+
title: "Components/Layout/Container",
|
|
7
|
+
component: Container,
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component: `
|
|
12
|
+
A centered container component with configurable max-width breakpoints.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
\`\`\`tsx
|
|
17
|
+
import { Container } from "@buildcanada/components"
|
|
18
|
+
|
|
19
|
+
<Container size="lg">
|
|
20
|
+
<p>Content is centered with max-width</p>
|
|
21
|
+
</Container>
|
|
22
|
+
\`\`\`
|
|
23
|
+
|
|
24
|
+
## Sizes
|
|
25
|
+
|
|
26
|
+
- **sm**: 640px max-width
|
|
27
|
+
- **md**: 768px max-width
|
|
28
|
+
- **lg**: 1024px max-width (default)
|
|
29
|
+
- **xl**: 1280px max-width
|
|
30
|
+
- **full**: No max-width
|
|
31
|
+
|
|
32
|
+
## Semantic HTML
|
|
33
|
+
|
|
34
|
+
Use the \`as\` prop for semantic HTML elements:
|
|
35
|
+
|
|
36
|
+
\`\`\`tsx
|
|
37
|
+
<Container as="main" size="lg">
|
|
38
|
+
<article>Page content</article>
|
|
39
|
+
</Container>
|
|
40
|
+
\`\`\`
|
|
41
|
+
`,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
argTypes: {
|
|
46
|
+
size: {
|
|
47
|
+
control: "select",
|
|
48
|
+
options: ["sm", "md", "lg", "xl", "full"],
|
|
49
|
+
description: "Maximum width of the container",
|
|
50
|
+
},
|
|
51
|
+
as: {
|
|
52
|
+
control: "select",
|
|
53
|
+
options: ["div", "main", "article", "section"],
|
|
54
|
+
description: "HTML element to render as",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default meta
|
|
60
|
+
type Story = StoryObj<typeof Container>
|
|
61
|
+
|
|
62
|
+
const PlaceholderContent = () => (
|
|
63
|
+
<div style={{
|
|
64
|
+
padding: "24px",
|
|
65
|
+
backgroundColor: "#F6ECE3",
|
|
66
|
+
border: "1px dashed #272727",
|
|
67
|
+
textAlign: "center",
|
|
68
|
+
fontFamily: "sans-serif"
|
|
69
|
+
}}>
|
|
70
|
+
Container Content
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
export const Default: Story = {
|
|
75
|
+
args: {
|
|
76
|
+
children: <PlaceholderContent />,
|
|
77
|
+
size: "lg",
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const Small: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
children: <PlaceholderContent />,
|
|
84
|
+
size: "sm",
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const Medium: Story = {
|
|
89
|
+
args: {
|
|
90
|
+
children: <PlaceholderContent />,
|
|
91
|
+
size: "md",
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const Large: Story = {
|
|
96
|
+
args: {
|
|
97
|
+
children: <PlaceholderContent />,
|
|
98
|
+
size: "lg",
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const ExtraLarge: Story = {
|
|
103
|
+
args: {
|
|
104
|
+
children: <PlaceholderContent />,
|
|
105
|
+
size: "xl",
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const FullWidth: Story = {
|
|
110
|
+
args: {
|
|
111
|
+
children: <PlaceholderContent />,
|
|
112
|
+
size: "full",
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const AsMain: Story = {
|
|
117
|
+
args: {
|
|
118
|
+
children: <PlaceholderContent />,
|
|
119
|
+
size: "lg",
|
|
120
|
+
as: "main",
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const AllSizes: Story = {
|
|
125
|
+
render: () => (
|
|
126
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
|
|
127
|
+
<div>
|
|
128
|
+
<p style={{ fontFamily: "sans-serif", marginBottom: "8px" }}>Small (640px)</p>
|
|
129
|
+
<Container size="sm">
|
|
130
|
+
<PlaceholderContent />
|
|
131
|
+
</Container>
|
|
132
|
+
</div>
|
|
133
|
+
<div>
|
|
134
|
+
<p style={{ fontFamily: "sans-serif", marginBottom: "8px" }}>Medium (768px)</p>
|
|
135
|
+
<Container size="md">
|
|
136
|
+
<PlaceholderContent />
|
|
137
|
+
</Container>
|
|
138
|
+
</div>
|
|
139
|
+
<div>
|
|
140
|
+
<p style={{ fontFamily: "sans-serif", marginBottom: "8px" }}>Large (1024px)</p>
|
|
141
|
+
<Container size="lg">
|
|
142
|
+
<PlaceholderContent />
|
|
143
|
+
</Container>
|
|
144
|
+
</div>
|
|
145
|
+
<div>
|
|
146
|
+
<p style={{ fontFamily: "sans-serif", marginBottom: "8px" }}>Extra Large (1280px)</p>
|
|
147
|
+
<Container size="xl">
|
|
148
|
+
<PlaceholderContent />
|
|
149
|
+
</Container>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
),
|
|
153
|
+
}
|