@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
@@ -0,0 +1,218 @@
1
+ @use "../../styles/tokens" as *;
2
+ @use "../../styles/typography" as *;
3
+
4
+ /*******************************************************************************
5
+ * Button Component
6
+ *
7
+ * Design notes:
8
+ * - Square corners (border-radius: 0) per brand motif "Huge Square Buttons"
9
+ * - Uppercase text with letter-spacing
10
+ * - Three sizes: sm (36px), md (44px), lg (56px)
11
+ ******************************************************************************/
12
+
13
+ .bc-btn {
14
+ @include button-text-md;
15
+ display: inline-flex;
16
+ align-items: center;
17
+ justify-content: center;
18
+ gap: $space-1;
19
+ border: 2px solid transparent;
20
+ border-radius: $radius-none;
21
+ cursor: pointer;
22
+ text-decoration: none;
23
+ transition: all $transition-fast;
24
+ white-space: nowrap;
25
+
26
+ &:focus-visible {
27
+ outline: 2px solid $auburn;
28
+ outline-offset: 2px;
29
+ }
30
+
31
+ /***************************************************************************
32
+ * Size Variants
33
+ ***************************************************************************/
34
+
35
+ &--sm {
36
+ @include button-text-sm;
37
+ padding: $space-1 $space-2;
38
+ min-height: 36px;
39
+ }
40
+
41
+ &--md {
42
+ @include button-text-md;
43
+ padding: calc($space-1 + 4px) $space-3;
44
+ min-height: 44px;
45
+ }
46
+
47
+ &--lg {
48
+ @include button-text-lg;
49
+ padding: $space-2 $space-4;
50
+ min-height: 56px;
51
+ }
52
+
53
+ /***************************************************************************
54
+ * Solid Variants
55
+ ***************************************************************************/
56
+
57
+ &--solid-auburn {
58
+ background-color: $auburn;
59
+ border-color: $auburn;
60
+ color: $white;
61
+
62
+ &:hover:not(:disabled) {
63
+ background-color: $auburn-600;
64
+ border-color: $auburn-600;
65
+ }
66
+
67
+ &:active:not(:disabled) {
68
+ background-color: $auburn-700;
69
+ border-color: $auburn-700;
70
+ }
71
+ }
72
+
73
+ &--solid-charcoal {
74
+ background-color: $charcoal;
75
+ border-color: $charcoal;
76
+ color: $white;
77
+
78
+ &:hover:not(:disabled) {
79
+ background-color: $gray-700;
80
+ border-color: $gray-700;
81
+ }
82
+
83
+ &:active:not(:disabled) {
84
+ background-color: $gray-600;
85
+ border-color: $gray-600;
86
+ }
87
+ }
88
+
89
+ &--solid-linen {
90
+ background-color: $linen;
91
+ border-color: $linen;
92
+ color: $charcoal;
93
+
94
+ &:hover:not(:disabled) {
95
+ background-color: darken($linen, 5%);
96
+ border-color: darken($linen, 5%);
97
+ }
98
+
99
+ &:active:not(:disabled) {
100
+ background-color: darken($linen, 8%);
101
+ border-color: darken($linen, 8%);
102
+ }
103
+ }
104
+
105
+ /***************************************************************************
106
+ * Outline Variants
107
+ ***************************************************************************/
108
+
109
+ &--outline-auburn {
110
+ background-color: transparent;
111
+ border-color: $auburn;
112
+ color: $auburn;
113
+
114
+ &:hover:not(:disabled) {
115
+ background-color: $auburn;
116
+ color: $white;
117
+ }
118
+
119
+ &:active:not(:disabled) {
120
+ background-color: $auburn-600;
121
+ border-color: $auburn-600;
122
+ color: $white;
123
+ }
124
+ }
125
+
126
+ &--outline-charcoal {
127
+ background-color: transparent;
128
+ border-color: $charcoal;
129
+ color: $charcoal;
130
+
131
+ &:hover:not(:disabled) {
132
+ background-color: $charcoal;
133
+ color: $white;
134
+ }
135
+
136
+ &:active:not(:disabled) {
137
+ background-color: $gray-700;
138
+ border-color: $gray-700;
139
+ color: $white;
140
+ }
141
+ }
142
+
143
+ &--outline-white {
144
+ background-color: transparent;
145
+ border-color: $white;
146
+ color: $white;
147
+
148
+ &:hover:not(:disabled) {
149
+ background-color: $white;
150
+ color: $charcoal;
151
+ }
152
+
153
+ &:active:not(:disabled) {
154
+ background-color: $gray-100;
155
+ border-color: $gray-100;
156
+ color: $charcoal;
157
+ }
158
+ }
159
+
160
+ /***************************************************************************
161
+ * State Modifiers
162
+ ***************************************************************************/
163
+
164
+ &:disabled {
165
+ opacity: 0.5;
166
+ cursor: not-allowed;
167
+ }
168
+
169
+ &--icon-only {
170
+ padding: $space-1;
171
+
172
+ &.bc-btn--sm {
173
+ padding: calc($space-1 - 2px);
174
+ min-width: 36px;
175
+ }
176
+
177
+ &.bc-btn--md {
178
+ padding: calc($space-1 + 2px);
179
+ min-width: 44px;
180
+ }
181
+
182
+ &.bc-btn--lg {
183
+ padding: $space-2;
184
+ min-width: 56px;
185
+ }
186
+ }
187
+
188
+ &--full-width {
189
+ width: 100%;
190
+ }
191
+
192
+ /***************************************************************************
193
+ * Icon Styles
194
+ ***************************************************************************/
195
+
196
+ &__icon {
197
+ font-size: 0.875em;
198
+ transition: transform $transition-fast;
199
+
200
+ &--left {
201
+ margin-right: $space-1;
202
+ }
203
+
204
+ &--right {
205
+ margin-left: $space-1;
206
+ }
207
+ }
208
+
209
+ // Animated arrow on hover (buildcanada.com style)
210
+ &:hover:not(:disabled) &__icon--right {
211
+ transform: translateX(4px);
212
+ }
213
+
214
+ &__text {
215
+ // Ensure text is vertically centered
216
+ line-height: 1;
217
+ }
218
+ }
@@ -0,0 +1,120 @@
1
+ import cx from "classnames"
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
3
+ import { IconDefinition, faArrowRight } from "@fortawesome/free-solid-svg-icons"
4
+
5
+ export type ButtonVariant =
6
+ | "solid-auburn"
7
+ | "solid-charcoal"
8
+ | "solid-linen"
9
+ | "outline-auburn"
10
+ | "outline-charcoal"
11
+ | "outline-white"
12
+
13
+ export type ButtonSize = "sm" | "md" | "lg"
14
+
15
+ type ButtonCommonProps = {
16
+ children?: React.ReactNode
17
+ text?: string
18
+ className?: string
19
+ style?: React.CSSProperties
20
+ variant?: ButtonVariant
21
+ size?: ButtonSize
22
+ icon?: IconDefinition | null
23
+ iconPosition?: "left" | "right"
24
+ fullWidth?: boolean
25
+ disabled?: boolean
26
+ ariaLabel?: string
27
+ dataTrackNote?: string
28
+ }
29
+
30
+ type WithHrefProps = {
31
+ href: string
32
+ onClick?: never
33
+ type?: never
34
+ }
35
+
36
+ type WithOnClickProps = {
37
+ onClick?: () => void
38
+ href?: never
39
+ type?: "button" | "submit"
40
+ }
41
+
42
+ export type ButtonProps =
43
+ | (ButtonCommonProps & WithHrefProps)
44
+ | (ButtonCommonProps & WithOnClickProps)
45
+
46
+ export function Button({
47
+ variant = "solid-auburn",
48
+ size = "md",
49
+ className,
50
+ style,
51
+ href,
52
+ onClick,
53
+ text,
54
+ children,
55
+ ariaLabel,
56
+ type = "button",
57
+ icon = faArrowRight,
58
+ iconPosition = "right",
59
+ fullWidth = false,
60
+ dataTrackNote,
61
+ disabled,
62
+ }: ButtonProps) {
63
+ const classes = cx(
64
+ "bc-btn",
65
+ `bc-btn--${variant}`,
66
+ `bc-btn--${size}`,
67
+ className,
68
+ {
69
+ "bc-btn--icon-only": icon && !text && !children,
70
+ "bc-btn--full-width": fullWidth,
71
+ }
72
+ )
73
+
74
+ const content = (
75
+ <>
76
+ {iconPosition === "left" && icon && (
77
+ <FontAwesomeIcon
78
+ className={cx("bc-btn__icon", { "bc-btn__icon--left": text || children })}
79
+ icon={icon}
80
+ />
81
+ )}
82
+ {text && <span className="bc-btn__text">{text}</span>}
83
+ {children}
84
+ {iconPosition !== "left" && icon && (
85
+ <FontAwesomeIcon
86
+ className={cx("bc-btn__icon", { "bc-btn__icon--right": text || children })}
87
+ icon={icon}
88
+ />
89
+ )}
90
+ </>
91
+ )
92
+
93
+ if (href) {
94
+ const aProps = {
95
+ href: disabled ? undefined : href,
96
+ className: classes,
97
+ style,
98
+ "data-track-note": dataTrackNote,
99
+ onClick: disabled
100
+ ? (e: React.MouseEvent) => e.preventDefault()
101
+ : undefined,
102
+ "aria-label": ariaLabel,
103
+ "aria-disabled": disabled,
104
+ }
105
+ return <a {...aProps}>{content}</a>
106
+ }
107
+
108
+ const buttonProps = {
109
+ type,
110
+ className: classes,
111
+ style,
112
+ onClick,
113
+ "aria-label": ariaLabel,
114
+ "data-track-note": dataTrackNote,
115
+ disabled,
116
+ }
117
+ return <button {...buttonProps}>{content}</button>
118
+ }
119
+
120
+ export default Button
@@ -0,0 +1,2 @@
1
+ export { Button, type ButtonProps, type ButtonVariant, type ButtonSize } from "./Button"
2
+ export { default } from "./Button"
@@ -0,0 +1,114 @@
1
+ @use "../../styles/tokens" as *;
2
+ @use "../../styles/typography" as *;
3
+
4
+ /*******************************************************************************
5
+ * Checkbox Component
6
+ ******************************************************************************/
7
+
8
+ .bc-checkbox {
9
+ display: inline-flex;
10
+
11
+ /***************************************************************************
12
+ * Hidden native input
13
+ ***************************************************************************/
14
+
15
+ &__input {
16
+ position: absolute;
17
+ width: 1px;
18
+ height: 1px;
19
+ padding: 0;
20
+ margin: -1px;
21
+ overflow: hidden;
22
+ clip: rect(0, 0, 0, 0);
23
+ white-space: nowrap;
24
+ border: 0;
25
+ }
26
+
27
+ /***************************************************************************
28
+ * Label wrapper
29
+ ***************************************************************************/
30
+
31
+ &__label {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ gap: $space-1;
35
+ cursor: pointer;
36
+ user-select: none;
37
+ }
38
+
39
+ /***************************************************************************
40
+ * Custom checkbox box
41
+ ***************************************************************************/
42
+
43
+ &__box {
44
+ display: inline-flex;
45
+ align-items: center;
46
+ justify-content: center;
47
+ width: 20px;
48
+ height: 20px;
49
+ border: 2px solid $border-default;
50
+ border-radius: $radius-none;
51
+ background-color: $white;
52
+ transition: all $transition-fast;
53
+ flex-shrink: 0;
54
+ }
55
+
56
+ &__check {
57
+ width: 14px;
58
+ height: 14px;
59
+ color: $white;
60
+ opacity: 0;
61
+ transform: scale(0.8);
62
+ transition: all $transition-fast;
63
+ }
64
+
65
+ &__text {
66
+ @include body-2;
67
+ color: $text-primary;
68
+ }
69
+
70
+ /***************************************************************************
71
+ * States
72
+ ***************************************************************************/
73
+
74
+ // Hover
75
+ &__label:hover &__box {
76
+ border-color: $gray-400;
77
+ }
78
+
79
+ // Focus
80
+ &__input:focus-visible + &__label &__box {
81
+ outline: 2px solid $auburn;
82
+ outline-offset: 2px;
83
+ }
84
+
85
+ // Checked
86
+ &__input:checked + &__label &__box {
87
+ background-color: $auburn;
88
+ border-color: $auburn;
89
+ }
90
+
91
+ &__input:checked + &__label &__check {
92
+ opacity: 1;
93
+ transform: scale(1);
94
+ }
95
+
96
+ // Disabled
97
+ &--disabled &__label {
98
+ cursor: not-allowed;
99
+ }
100
+
101
+ &--disabled &__box {
102
+ background-color: $gray-100;
103
+ border-color: $gray-300;
104
+ }
105
+
106
+ &--disabled &__text {
107
+ color: $text-muted;
108
+ }
109
+
110
+ &--disabled &__input:checked + &__label &__box {
111
+ background-color: $gray-400;
112
+ border-color: $gray-400;
113
+ }
114
+ }
@@ -0,0 +1,75 @@
1
+ import cx from "classnames"
2
+ import { forwardRef, useId } from "react"
3
+
4
+ export interface CheckboxProps {
5
+ label: string
6
+ checked?: boolean
7
+ defaultChecked?: boolean
8
+ name?: string
9
+ id?: string
10
+ className?: string
11
+ disabled?: boolean
12
+ onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
13
+ }
14
+
15
+ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
16
+ function Checkbox(
17
+ {
18
+ label,
19
+ checked,
20
+ defaultChecked,
21
+ name,
22
+ id: providedId,
23
+ className,
24
+ disabled = false,
25
+ onChange,
26
+ },
27
+ ref
28
+ ) {
29
+ const generatedId = useId()
30
+ const id = providedId || generatedId
31
+
32
+ const classes = cx(
33
+ "bc-checkbox",
34
+ { "bc-checkbox--disabled": disabled },
35
+ className
36
+ )
37
+
38
+ return (
39
+ <div className={classes}>
40
+ <input
41
+ ref={ref}
42
+ type="checkbox"
43
+ id={id}
44
+ name={name}
45
+ checked={checked}
46
+ defaultChecked={defaultChecked}
47
+ disabled={disabled}
48
+ className="bc-checkbox__input"
49
+ onChange={onChange}
50
+ />
51
+ <label htmlFor={id} className="bc-checkbox__label">
52
+ <span className="bc-checkbox__box">
53
+ <svg
54
+ className="bc-checkbox__check"
55
+ viewBox="0 0 14 14"
56
+ fill="none"
57
+ xmlns="http://www.w3.org/2000/svg"
58
+ >
59
+ <path
60
+ d="M11.5 4L5.5 10L2.5 7"
61
+ stroke="currentColor"
62
+ strokeWidth="2"
63
+ strokeLinecap="square"
64
+ strokeLinejoin="round"
65
+ />
66
+ </svg>
67
+ </span>
68
+ <span className="bc-checkbox__text">{label}</span>
69
+ </label>
70
+ </div>
71
+ )
72
+ }
73
+ )
74
+
75
+ export default Checkbox
@@ -0,0 +1,2 @@
1
+ export { Checkbox, type CheckboxProps } from "./Checkbox"
2
+ export { default } from "./Checkbox"
@@ -0,0 +1,93 @@
1
+ @use "../../styles/tokens" as *;
2
+ @use "../../styles/typography" as *;
3
+
4
+ /*******************************************************************************
5
+ * TextField Component
6
+ ******************************************************************************/
7
+
8
+ .bc-textfield {
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: calc($space-1 / 2);
12
+
13
+ /***************************************************************************
14
+ * Label
15
+ ***************************************************************************/
16
+
17
+ &__label {
18
+ @include label;
19
+ color: $text-primary;
20
+ }
21
+
22
+ &__required {
23
+ color: $auburn;
24
+ margin-left: 2px;
25
+ }
26
+
27
+ /***************************************************************************
28
+ * Input
29
+ ***************************************************************************/
30
+
31
+ &__input {
32
+ @include body-2;
33
+ width: 100%;
34
+ padding: calc($space-1 + 4px) $space-2;
35
+ border: 2px solid $border-default;
36
+ border-radius: $radius-none;
37
+ background-color: $white;
38
+ color: $text-primary;
39
+ transition: border-color $transition-fast, box-shadow $transition-fast;
40
+
41
+ &::placeholder {
42
+ color: $text-muted;
43
+ }
44
+
45
+ &:hover:not(:disabled) {
46
+ border-color: $gray-400;
47
+ }
48
+
49
+ &:focus {
50
+ outline: none;
51
+ border-color: $auburn;
52
+ box-shadow: 0 0 0 3px rgba($auburn, 0.1);
53
+ }
54
+
55
+ &:disabled {
56
+ background-color: $gray-100;
57
+ color: $text-muted;
58
+ cursor: not-allowed;
59
+ }
60
+ }
61
+
62
+ /***************************************************************************
63
+ * Hint & Error
64
+ ***************************************************************************/
65
+
66
+ &__hint {
67
+ @include body-3;
68
+ color: $text-secondary;
69
+ margin: 0;
70
+ }
71
+
72
+ &__error {
73
+ @include body-3;
74
+ color: $auburn;
75
+ margin: 0;
76
+ }
77
+
78
+ /***************************************************************************
79
+ * States
80
+ ***************************************************************************/
81
+
82
+ &--error &__input {
83
+ border-color: $auburn;
84
+
85
+ &:focus {
86
+ box-shadow: 0 0 0 3px rgba($auburn, 0.15);
87
+ }
88
+ }
89
+
90
+ &--disabled &__label {
91
+ color: $text-muted;
92
+ }
93
+ }