@buildcanada/components 0.1.0

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 (48) hide show
  1. package/package.json +67 -0
  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.tsx +170 -0
  7. package/src/content/Card/index.ts +22 -0
  8. package/src/content/Hero/Hero.scss +150 -0
  9. package/src/content/Hero/Hero.tsx +63 -0
  10. package/src/content/Hero/index.ts +13 -0
  11. package/src/content/StatBlock/StatBlock.scss +83 -0
  12. package/src/content/StatBlock/StatBlock.tsx +52 -0
  13. package/src/content/StatBlock/index.ts +2 -0
  14. package/src/index.ts +57 -0
  15. package/src/layout/Container/Container.scss +40 -0
  16. package/src/layout/Container/Container.tsx +29 -0
  17. package/src/layout/Container/index.ts +2 -0
  18. package/src/layout/Divider/Divider.scss +117 -0
  19. package/src/layout/Divider/Divider.tsx +32 -0
  20. package/src/layout/Divider/index.ts +2 -0
  21. package/src/layout/Grid/Grid.scss +81 -0
  22. package/src/layout/Grid/Grid.tsx +75 -0
  23. package/src/layout/Grid/index.ts +2 -0
  24. package/src/layout/Section/Section.scss +74 -0
  25. package/src/layout/Section/Section.tsx +37 -0
  26. package/src/layout/Section/index.ts +2 -0
  27. package/src/layout/Stack/Stack.scss +61 -0
  28. package/src/layout/Stack/Stack.tsx +48 -0
  29. package/src/layout/Stack/index.ts +9 -0
  30. package/src/navigation/Footer/Footer.scss +233 -0
  31. package/src/navigation/Footer/Footer.tsx +174 -0
  32. package/src/navigation/Footer/index.ts +2 -0
  33. package/src/navigation/Header/Header.scss +325 -0
  34. package/src/navigation/Header/Header.tsx +185 -0
  35. package/src/navigation/Header/index.ts +2 -0
  36. package/src/primitives/Button/Button.scss +218 -0
  37. package/src/primitives/Button/Button.tsx +120 -0
  38. package/src/primitives/Button/index.ts +2 -0
  39. package/src/primitives/Checkbox/Checkbox.scss +114 -0
  40. package/src/primitives/Checkbox/Checkbox.tsx +75 -0
  41. package/src/primitives/Checkbox/index.ts +2 -0
  42. package/src/primitives/TextField/TextField.scss +93 -0
  43. package/src/primitives/TextField/TextField.tsx +105 -0
  44. package/src/primitives/TextField/index.ts +2 -0
  45. package/src/styles/fonts.scss +27 -0
  46. package/src/styles/main.scss +34 -0
  47. package/src/styles/tokens.scss +301 -0
  48. package/src/styles/typography.scss +232 -0
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@buildcanada/components",
3
+ "version": "0.1.0",
4
+ "description": "Build Canada design system components",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.ts",
11
+ "import": "./src/index.ts"
12
+ },
13
+ "./styles.css": "./src/styles/main.scss",
14
+ "./src/styles/main.scss": "./src/styles/main.scss",
15
+ "./src/*": "./src/*"
16
+ },
17
+ "files": [
18
+ "src",
19
+ "!src/**/*.test.ts",
20
+ "!src/**/*.test.tsx",
21
+ "!src/**/*.stories.tsx"
22
+ ],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/BuildCanada/bcds.git",
26
+ "directory": "packages/components"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/BuildCanada/bcds/issues"
30
+ },
31
+ "homepage": "https://github.com/BuildCanada/bcds#readme",
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "vitest run",
38
+ "test:watch": "vitest"
39
+ },
40
+ "keywords": [
41
+ "components",
42
+ "design-system",
43
+ "react",
44
+ "build-canada"
45
+ ],
46
+ "author": "Build Canada",
47
+ "license": "MIT",
48
+ "peerDependencies": {
49
+ "react": "^19.0.0",
50
+ "react-dom": "^19.0.0"
51
+ },
52
+ "dependencies": {
53
+ "@fortawesome/fontawesome-svg-core": "^6.7.2",
54
+ "@fortawesome/free-solid-svg-icons": "^6.7.2",
55
+ "@fortawesome/react-fontawesome": "^0.2.2",
56
+ "classnames": "^2.5.1"
57
+ },
58
+ "devDependencies": {
59
+ "@testing-library/dom": "^10.4.1",
60
+ "@testing-library/jest-dom": "^6.9.1",
61
+ "@testing-library/react": "^16.3.0",
62
+ "eslint": "^9.37.0",
63
+ "eslint-plugin-react-hooks": "^5.2.0",
64
+ "happy-dom": "^20.1.0",
65
+ "vitest": "^4.0.15"
66
+ }
67
+ }
@@ -0,0 +1,281 @@
1
+ @use "../../styles/tokens" as *;
2
+ @use "../../styles/typography" as *;
3
+
4
+ /*******************************************************************************
5
+ * Card Component
6
+ ******************************************************************************/
7
+
8
+ .bc-card {
9
+ display: flex;
10
+ flex-direction: column;
11
+ background-color: $white;
12
+ border: 1px solid $border-muted;
13
+ border-radius: $radius-none;
14
+ overflow: hidden;
15
+ text-decoration: none;
16
+ color: inherit;
17
+ transition: box-shadow $transition-fast, transform $transition-fast;
18
+
19
+ /***************************************************************************
20
+ * Variant: Default Card
21
+ ***************************************************************************/
22
+
23
+ &--default {
24
+ .bc-card__content {
25
+ padding: $space-3;
26
+ }
27
+
28
+ .bc-card__title {
29
+ @include h3;
30
+ margin: 0 0 $space-1;
31
+ }
32
+
33
+ .bc-card__description {
34
+ @include body-2;
35
+ color: $text-secondary;
36
+ margin: 0;
37
+ }
38
+
39
+ .bc-card__meta {
40
+ @include micro;
41
+ color: $text-muted;
42
+ margin-top: $space-2;
43
+ }
44
+ }
45
+
46
+ // Interactive cards (when rendered as a or button)
47
+ &--interactive {
48
+ cursor: pointer;
49
+
50
+ &:hover {
51
+ box-shadow: $shadow-md;
52
+ transform: translateY(-2px);
53
+ }
54
+
55
+ &:focus-visible {
56
+ outline: 2px solid $auburn;
57
+ outline-offset: 2px;
58
+ }
59
+ }
60
+
61
+ // Button-specific styles
62
+ &--button {
63
+ border: 1px solid $border-muted;
64
+ text-align: left;
65
+ width: 100%;
66
+ }
67
+
68
+ /***************************************************************************
69
+ * Variant: Project Card
70
+ ***************************************************************************/
71
+
72
+ &--project {
73
+ .bc-card__image {
74
+ aspect-ratio: 16 / 9;
75
+ }
76
+
77
+ .bc-card__content {
78
+ padding: $space-3;
79
+ }
80
+
81
+ .bc-card__title {
82
+ @include h3;
83
+ margin: 0 0 $space-1;
84
+ }
85
+
86
+ .bc-card__description {
87
+ @include body-3;
88
+ color: $text-secondary;
89
+ margin: 0;
90
+ }
91
+ }
92
+
93
+ /***************************************************************************
94
+ * Variant: Memo Card
95
+ ***************************************************************************/
96
+
97
+ &--memo {
98
+ .bc-card__content {
99
+ padding: $space-3;
100
+ }
101
+
102
+ .bc-card__title {
103
+ @include h3;
104
+ margin: 0 0 $space-1;
105
+ }
106
+
107
+ .bc-card__description {
108
+ @include body-2;
109
+ color: $text-secondary;
110
+ margin: 0 0 $space-2;
111
+ display: -webkit-box;
112
+ -webkit-line-clamp: 3;
113
+ -webkit-box-orient: vertical;
114
+ overflow: hidden;
115
+ }
116
+ }
117
+
118
+ /***************************************************************************
119
+ * Variant: Feature Card
120
+ ***************************************************************************/
121
+
122
+ &--feature {
123
+ text-align: center;
124
+ padding: $space-4;
125
+
126
+ .bc-card__icon {
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ width: 64px;
131
+ height: 64px;
132
+ margin: 0 auto $space-2;
133
+ background-color: $linen;
134
+ border-radius: $radius-none;
135
+ color: $auburn;
136
+ font-size: 1.5rem;
137
+ }
138
+
139
+ .bc-card__title {
140
+ @include h4;
141
+ margin: 0 0 $space-1;
142
+ }
143
+
144
+ .bc-card__description {
145
+ @include body-3;
146
+ color: $text-secondary;
147
+ margin: 0;
148
+ }
149
+ }
150
+
151
+ /***************************************************************************
152
+ * Variant: Stat Card
153
+ ***************************************************************************/
154
+
155
+ &--stat {
156
+ padding: $space-3;
157
+ background-color: $linen;
158
+ border: none;
159
+
160
+ .bc-card__stat {
161
+ display: flex;
162
+ flex-direction: column;
163
+ gap: calc($space-1 / 2);
164
+ }
165
+
166
+ .bc-card__stat-value {
167
+ @include display-2;
168
+ color: $charcoal;
169
+ }
170
+
171
+ .bc-card__stat-label {
172
+ @include label;
173
+ color: $text-secondary;
174
+ }
175
+
176
+ .bc-card__stat-change {
177
+ @include mono-sm;
178
+ color: $text-secondary;
179
+
180
+ &--up {
181
+ color: $emerald-600;
182
+ }
183
+
184
+ &--down {
185
+ color: $auburn;
186
+ }
187
+ }
188
+ }
189
+
190
+ /***************************************************************************
191
+ * Variant: Profile Card
192
+ ***************************************************************************/
193
+
194
+ &--profile {
195
+ text-align: center;
196
+ padding: $space-4;
197
+
198
+ .bc-card__image {
199
+ width: 120px;
200
+ height: 120px;
201
+ margin: 0 auto $space-2;
202
+ border-radius: $radius-full;
203
+ overflow: hidden;
204
+
205
+ img {
206
+ width: 100%;
207
+ height: 100%;
208
+ object-fit: cover;
209
+ }
210
+ }
211
+
212
+ .bc-card__title {
213
+ @include h4;
214
+ margin: 0 0 calc($space-1 / 2);
215
+ }
216
+
217
+ .bc-card__description {
218
+ @include body-3;
219
+ color: $text-secondary;
220
+ margin: 0;
221
+ }
222
+
223
+ .bc-card__meta {
224
+ display: flex;
225
+ justify-content: center;
226
+ gap: $space-1;
227
+ margin-top: $space-2;
228
+ }
229
+ }
230
+
231
+ /***************************************************************************
232
+ * Subcomponents
233
+ ***************************************************************************/
234
+
235
+ &__image {
236
+ width: 100%;
237
+ overflow: hidden;
238
+
239
+ img {
240
+ width: 100%;
241
+ height: 100%;
242
+ object-fit: cover;
243
+ }
244
+ }
245
+
246
+ &__content {
247
+ flex: 1;
248
+ display: flex;
249
+ flex-direction: column;
250
+ }
251
+
252
+ &__author {
253
+ display: flex;
254
+ align-items: center;
255
+ gap: $space-1;
256
+ margin-top: auto;
257
+ padding-top: $space-2;
258
+ }
259
+
260
+ &__author-avatar {
261
+ width: 40px;
262
+ height: 40px;
263
+ border-radius: $radius-full;
264
+ object-fit: cover;
265
+ }
266
+
267
+ &__author-info {
268
+ display: flex;
269
+ flex-direction: column;
270
+ }
271
+
272
+ &__author-name {
273
+ @include label-sm;
274
+ color: $text-primary;
275
+ }
276
+
277
+ &__author-role {
278
+ @include body-3;
279
+ color: $text-secondary;
280
+ }
281
+ }
@@ -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"
22
+ export { default } from "./Card"
@@ -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
+ }