@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.
Files changed (69) hide show
  1. package/package.json +3 -2
  2. package/src/assets/fonts/financier-text-regular.woff2 +0 -0
  3. package/src/assets/fonts/founders-grotesk-mono-regular.woff2 +0 -0
  4. package/src/assets/fonts/soehne-kraftig.woff2 +0 -0
  5. package/src/content/Card/Card.scss +281 -0
  6. package/src/content/Card/Card.stories.tsx +389 -0
  7. package/src/content/Card/Card.tsx +170 -0
  8. package/src/content/Card/index.ts +22 -0
  9. package/src/content/Hero/Hero.scss +150 -0
  10. package/src/content/Hero/Hero.stories.tsx +299 -0
  11. package/src/content/Hero/Hero.tsx +63 -0
  12. package/src/content/Hero/index.ts +13 -0
  13. package/src/content/StatBlock/StatBlock.scss +83 -0
  14. package/src/content/StatBlock/StatBlock.stories.tsx +331 -0
  15. package/src/content/StatBlock/StatBlock.tsx +52 -0
  16. package/src/content/StatBlock/index.ts +2 -0
  17. package/src/feedback/Dialog/Dialog.scss +158 -0
  18. package/src/feedback/Dialog/Dialog.stories.tsx +286 -0
  19. package/src/feedback/Dialog/Dialog.tsx +120 -0
  20. package/src/feedback/Dialog/index.ts +1 -0
  21. package/src/feedback/PopupForm/PopupForm.scss +34 -0
  22. package/src/feedback/PopupForm/PopupForm.stories.tsx +341 -0
  23. package/src/feedback/PopupForm/PopupForm.tsx +90 -0
  24. package/src/feedback/PopupForm/index.ts +1 -0
  25. package/src/index.ts +61 -0
  26. package/src/layout/Container/Container.scss +40 -0
  27. package/src/layout/Container/Container.stories.tsx +153 -0
  28. package/src/layout/Container/Container.tsx +29 -0
  29. package/src/layout/Container/index.ts +2 -0
  30. package/src/layout/Divider/Divider.scss +117 -0
  31. package/src/layout/Divider/Divider.stories.tsx +204 -0
  32. package/src/layout/Divider/Divider.tsx +32 -0
  33. package/src/layout/Divider/index.ts +2 -0
  34. package/src/layout/Grid/Grid.scss +81 -0
  35. package/src/layout/Grid/Grid.stories.tsx +263 -0
  36. package/src/layout/Grid/Grid.tsx +75 -0
  37. package/src/layout/Grid/index.ts +2 -0
  38. package/src/layout/Section/Section.scss +74 -0
  39. package/src/layout/Section/Section.stories.tsx +173 -0
  40. package/src/layout/Section/Section.tsx +37 -0
  41. package/src/layout/Section/index.ts +2 -0
  42. package/src/layout/Stack/Stack.scss +61 -0
  43. package/src/layout/Stack/Stack.stories.tsx +342 -0
  44. package/src/layout/Stack/Stack.tsx +48 -0
  45. package/src/layout/Stack/index.ts +9 -0
  46. package/src/navigation/Footer/Footer.scss +233 -0
  47. package/src/navigation/Footer/Footer.stories.tsx +351 -0
  48. package/src/navigation/Footer/Footer.tsx +174 -0
  49. package/src/navigation/Footer/index.ts +2 -0
  50. package/src/navigation/Header/Header.scss +325 -0
  51. package/src/navigation/Header/Header.stories.tsx +346 -0
  52. package/src/navigation/Header/Header.tsx +185 -0
  53. package/src/navigation/Header/index.ts +2 -0
  54. package/src/primitives/Button/Button.scss +218 -0
  55. package/src/primitives/Button/Button.stories.tsx +300 -0
  56. package/src/primitives/Button/Button.tsx +120 -0
  57. package/src/primitives/Button/index.ts +2 -0
  58. package/src/primitives/Checkbox/Checkbox.scss +114 -0
  59. package/src/primitives/Checkbox/Checkbox.stories.tsx +204 -0
  60. package/src/primitives/Checkbox/Checkbox.tsx +75 -0
  61. package/src/primitives/Checkbox/index.ts +2 -0
  62. package/src/primitives/TextField/TextField.scss +93 -0
  63. package/src/primitives/TextField/TextField.stories.tsx +265 -0
  64. package/src/primitives/TextField/TextField.tsx +105 -0
  65. package/src/primitives/TextField/index.ts +2 -0
  66. package/src/styles/fonts.scss +27 -0
  67. package/src/styles/main.scss +36 -0
  68. package/src/styles/tokens.scss +301 -0
  69. package/src/styles/typography.scss +232 -0
@@ -0,0 +1,265 @@
1
+ import type { Meta, StoryObj } from "@storybook/react"
2
+ import { within, userEvent, expect, fn } from "@storybook/test"
3
+
4
+ import { TextField } from "./TextField"
5
+
6
+ const meta: Meta<typeof TextField> = {
7
+ title: "Components/Primitives/TextField",
8
+ component: TextField,
9
+ parameters: {
10
+ docs: {
11
+ description: {
12
+ component: `
13
+ A form text input component with label, hint, and error states.
14
+
15
+ ## Usage
16
+
17
+ \`\`\`tsx
18
+ import { TextField } from "@buildcanada/components"
19
+
20
+ <TextField
21
+ label="Email Address"
22
+ type="email"
23
+ placeholder="you@example.com"
24
+ />
25
+ \`\`\`
26
+
27
+ ## Input Types
28
+
29
+ Supports standard HTML input types: text, email, password, number, tel, url.
30
+
31
+ ## Validation States
32
+
33
+ \`\`\`tsx
34
+ // With hint text
35
+ <TextField label="Password" hint="Must be 8+ characters" />
36
+
37
+ // With error
38
+ <TextField label="Email" error="Invalid email format" />
39
+
40
+ // Required field
41
+ <TextField label="Name" required />
42
+ \`\`\`
43
+ `,
44
+ },
45
+ },
46
+ },
47
+ argTypes: {
48
+ type: {
49
+ control: "select",
50
+ options: ["text", "email", "password", "number", "tel", "url"],
51
+ description: "HTML input type",
52
+ },
53
+ label: { description: "Label text above the input" },
54
+ placeholder: { description: "Placeholder text" },
55
+ hint: { description: "Helper text below the input" },
56
+ error: { description: "Error message (shows error state when set)" },
57
+ required: { description: "Whether the field is required" },
58
+ disabled: { description: "Whether the input is disabled" },
59
+ },
60
+ }
61
+
62
+ export default meta
63
+ type Story = StoryObj<typeof TextField>
64
+
65
+ export const Default: Story = {
66
+ args: {
67
+ label: "Full Name",
68
+ placeholder: "Enter your full name",
69
+ },
70
+ }
71
+
72
+ export const WithValue: Story = {
73
+ args: {
74
+ label: "Email Address",
75
+ type: "email",
76
+ value: "contact@buildcanada.com",
77
+ },
78
+ }
79
+
80
+ export const WithPlaceholder: Story = {
81
+ args: {
82
+ label: "Company",
83
+ placeholder: "Your company name",
84
+ },
85
+ }
86
+
87
+ export const WithHint: Story = {
88
+ args: {
89
+ label: "Password",
90
+ type: "password",
91
+ placeholder: "Enter your password",
92
+ hint: "Must be at least 8 characters long",
93
+ },
94
+ }
95
+
96
+ export const WithError: Story = {
97
+ args: {
98
+ label: "Email Address",
99
+ type: "email",
100
+ value: "invalid-email",
101
+ error: "Please enter a valid email address",
102
+ },
103
+ }
104
+
105
+ export const Required: Story = {
106
+ args: {
107
+ label: "Email Address",
108
+ type: "email",
109
+ placeholder: "you@example.com",
110
+ required: true,
111
+ },
112
+ }
113
+
114
+ export const Disabled: Story = {
115
+ args: {
116
+ label: "Organization",
117
+ value: "Build Canada",
118
+ disabled: true,
119
+ },
120
+ }
121
+
122
+ export const EmailType: Story = {
123
+ args: {
124
+ label: "Email",
125
+ type: "email",
126
+ placeholder: "you@example.com",
127
+ },
128
+ }
129
+
130
+ export const PasswordType: Story = {
131
+ args: {
132
+ label: "Password",
133
+ type: "password",
134
+ placeholder: "Enter your password",
135
+ },
136
+ }
137
+
138
+ export const NumberType: Story = {
139
+ args: {
140
+ label: "Amount (CAD)",
141
+ type: "number",
142
+ placeholder: "0.00",
143
+ },
144
+ }
145
+
146
+ export const TelType: Story = {
147
+ args: {
148
+ label: "Phone Number",
149
+ type: "tel",
150
+ placeholder: "+1 (555) 123-4567",
151
+ },
152
+ }
153
+
154
+ export const NoLabel: Story = {
155
+ args: {
156
+ placeholder: "Search...",
157
+ type: "text",
158
+ },
159
+ }
160
+
161
+ // Interactive test: Type in the field
162
+ export const TypeTest: Story = {
163
+ args: {
164
+ label: "Username",
165
+ placeholder: "Enter username",
166
+ onChange: fn(),
167
+ },
168
+ play: async ({ canvasElement, args }) => {
169
+ const canvas = within(canvasElement)
170
+ const input = canvas.getByRole("textbox", { name: /username/i })
171
+
172
+ await expect(input).toBeInTheDocument()
173
+ await userEvent.type(input, "testuser")
174
+ await expect(input).toHaveValue("testuser")
175
+ await expect(args.onChange).toHaveBeenCalled()
176
+ },
177
+ }
178
+
179
+ // Interactive test: Focus and blur
180
+ export const FocusBlurTest: Story = {
181
+ args: {
182
+ label: "Email",
183
+ placeholder: "Enter email",
184
+ onFocus: fn(),
185
+ onBlur: fn(),
186
+ },
187
+ play: async ({ canvasElement, args }) => {
188
+ const canvas = within(canvasElement)
189
+ const input = canvas.getByRole("textbox", { name: /email/i })
190
+
191
+ await userEvent.click(input)
192
+ await expect(args.onFocus).toHaveBeenCalled()
193
+
194
+ await userEvent.tab()
195
+ await expect(args.onBlur).toHaveBeenCalled()
196
+ },
197
+ }
198
+
199
+ // Interactive test: Disabled field
200
+ export const DisabledTest: Story = {
201
+ args: {
202
+ label: "Locked Field",
203
+ value: "Cannot edit this",
204
+ disabled: true,
205
+ },
206
+ play: async ({ canvasElement }) => {
207
+ const canvas = within(canvasElement)
208
+ const input = canvas.getByRole("textbox", { name: /locked field/i })
209
+
210
+ await expect(input).toBeDisabled()
211
+ await expect(input).toHaveValue("Cannot edit this")
212
+ },
213
+ }
214
+
215
+ // Interactive test: Error state
216
+ export const ErrorStateTest: Story = {
217
+ args: {
218
+ label: "Email",
219
+ value: "invalid",
220
+ error: "Invalid email format",
221
+ },
222
+ play: async ({ canvasElement }) => {
223
+ const canvas = within(canvasElement)
224
+ const input = canvas.getByRole("textbox", { name: /email/i })
225
+ const errorMessage = canvas.getByText("Invalid email format")
226
+
227
+ await expect(input).toHaveAttribute("aria-invalid", "true")
228
+ await expect(errorMessage).toBeInTheDocument()
229
+ },
230
+ }
231
+
232
+ export const AllStates: Story = {
233
+ render: () => (
234
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px", maxWidth: "400px" }}>
235
+ <TextField
236
+ label="Default"
237
+ placeholder="Enter text..."
238
+ />
239
+ <TextField
240
+ label="With Value"
241
+ value="Some content"
242
+ />
243
+ <TextField
244
+ label="With Hint"
245
+ placeholder="Enter text..."
246
+ hint="This is a helpful hint"
247
+ />
248
+ <TextField
249
+ label="With Error"
250
+ value="Invalid input"
251
+ error="This field has an error"
252
+ />
253
+ <TextField
254
+ label="Required Field"
255
+ placeholder="This field is required"
256
+ required
257
+ />
258
+ <TextField
259
+ label="Disabled"
260
+ value="Cannot edit"
261
+ disabled
262
+ />
263
+ </div>
264
+ ),
265
+ }
@@ -0,0 +1,105 @@
1
+ import cx from "classnames"
2
+ import { forwardRef, useId } from "react"
3
+
4
+ export type TextFieldType = "text" | "email" | "password" | "number" | "tel" | "url"
5
+
6
+ export interface TextFieldProps {
7
+ label?: string
8
+ placeholder?: string
9
+ value?: string
10
+ defaultValue?: string
11
+ type?: TextFieldType
12
+ name?: string
13
+ id?: string
14
+ className?: string
15
+ error?: string
16
+ hint?: string
17
+ disabled?: boolean
18
+ required?: boolean
19
+ autoComplete?: string
20
+ onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
21
+ onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void
22
+ onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void
23
+ }
24
+
25
+ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
26
+ function TextField(
27
+ {
28
+ label,
29
+ placeholder,
30
+ value,
31
+ defaultValue,
32
+ type = "text",
33
+ name,
34
+ id: providedId,
35
+ className,
36
+ error,
37
+ hint,
38
+ disabled = false,
39
+ required = false,
40
+ autoComplete,
41
+ onChange,
42
+ onBlur,
43
+ onFocus,
44
+ },
45
+ ref
46
+ ) {
47
+ const generatedId = useId()
48
+ const id = providedId || generatedId
49
+ const errorId = `${id}-error`
50
+ const hintId = `${id}-hint`
51
+
52
+ const hasError = Boolean(error)
53
+
54
+ const classes = cx(
55
+ "bc-textfield",
56
+ { "bc-textfield--error": hasError },
57
+ { "bc-textfield--disabled": disabled },
58
+ className
59
+ )
60
+
61
+ return (
62
+ <div className={classes}>
63
+ {label && (
64
+ <label htmlFor={id} className="bc-textfield__label">
65
+ {label}
66
+ {required && <span className="bc-textfield__required">*</span>}
67
+ </label>
68
+ )}
69
+ <input
70
+ ref={ref}
71
+ type={type}
72
+ id={id}
73
+ name={name}
74
+ value={value}
75
+ defaultValue={defaultValue}
76
+ placeholder={placeholder}
77
+ disabled={disabled}
78
+ required={required}
79
+ autoComplete={autoComplete}
80
+ className="bc-textfield__input"
81
+ aria-invalid={hasError}
82
+ aria-describedby={
83
+ [error && errorId, hint && hintId].filter(Boolean).join(" ") ||
84
+ undefined
85
+ }
86
+ onChange={onChange}
87
+ onBlur={onBlur}
88
+ onFocus={onFocus}
89
+ />
90
+ {hint && !error && (
91
+ <p id={hintId} className="bc-textfield__hint">
92
+ {hint}
93
+ </p>
94
+ )}
95
+ {error && (
96
+ <p id={errorId} className="bc-textfield__error" role="alert">
97
+ {error}
98
+ </p>
99
+ )}
100
+ </div>
101
+ )
102
+ }
103
+ )
104
+
105
+ export default TextField
@@ -0,0 +1,2 @@
1
+ export { TextField, type TextFieldProps, type TextFieldType } from "./TextField.js"
2
+ export { default } from "./TextField.js"
@@ -0,0 +1,27 @@
1
+ /*******************************************************************************
2
+ * Build Canada Font Definitions
3
+ ******************************************************************************/
4
+
5
+ @font-face {
6
+ font-family: 'Soehne Kraftig';
7
+ src: url('/assets/fonts/soehne-kraftig.woff2') format('woff2');
8
+ font-weight: 500;
9
+ font-style: normal;
10
+ font-display: swap;
11
+ }
12
+
13
+ @font-face {
14
+ font-family: 'Financier Text';
15
+ src: url('/assets/fonts/financier-text-regular.woff2') format('woff2');
16
+ font-weight: 400;
17
+ font-style: normal;
18
+ font-display: swap;
19
+ }
20
+
21
+ @font-face {
22
+ font-family: 'Founders Grotesk Mono';
23
+ src: url('/assets/fonts/founders-grotesk-mono-regular.woff2') format('woff2');
24
+ font-weight: 400;
25
+ font-style: normal;
26
+ font-display: swap;
27
+ }
@@ -0,0 +1,36 @@
1
+ /*******************************************************************************
2
+ * Build Canada Components - Main Stylesheet
3
+ *
4
+ * All @use statements must come first before any other rules
5
+ ******************************************************************************/
6
+
7
+ @use "fonts";
8
+ @use "tokens";
9
+ @use "typography";
10
+
11
+ // Component stylesheets (using @use to include their CSS output)
12
+ @use "../primitives/Button/Button.scss";
13
+ @use "../primitives/TextField/TextField.scss";
14
+ @use "../primitives/Checkbox/Checkbox.scss";
15
+ @use "../layout/Container/Container.scss";
16
+ @use "../layout/Section/Section.scss";
17
+ @use "../layout/Grid/Grid.scss";
18
+ @use "../layout/Stack/Stack.scss";
19
+ @use "../layout/Divider/Divider.scss";
20
+ @use "../content/Card/Card.scss";
21
+ @use "../content/Hero/Hero.scss";
22
+ @use "../content/StatBlock/StatBlock.scss";
23
+ @use "../navigation/Header/Header.scss";
24
+ @use "../navigation/Footer/Footer.scss";
25
+ @use "../feedback/Dialog/Dialog.scss";
26
+ @use "../feedback/PopupForm/PopupForm.scss";
27
+
28
+ /*******************************************************************************
29
+ * Base Styles
30
+ ******************************************************************************/
31
+
32
+ *,
33
+ *::before,
34
+ *::after {
35
+ box-sizing: border-box;
36
+ }