@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,74 @@
1
+ @use "../../styles/tokens" as *;
2
+
3
+ /*******************************************************************************
4
+ * Section Component
5
+ *
6
+ * Full-width section with background color variants
7
+ ******************************************************************************/
8
+
9
+ .bc-section {
10
+ width: 100%;
11
+
12
+ /***************************************************************************
13
+ * Background Variants
14
+ ***************************************************************************/
15
+
16
+ &--bg-white {
17
+ background-color: $white;
18
+ color: $text-primary;
19
+ }
20
+
21
+ &--bg-linen {
22
+ background-color: $linen;
23
+ color: $text-primary;
24
+ }
25
+
26
+ &--bg-charcoal {
27
+ background-color: $charcoal;
28
+ color: $text-inverse;
29
+ }
30
+
31
+ /***************************************************************************
32
+ * Spacing Variants
33
+ ***************************************************************************/
34
+
35
+ &--spacing-none {
36
+ padding-top: 0;
37
+ padding-bottom: 0;
38
+ }
39
+
40
+ &--spacing-sm {
41
+ padding-top: $space-4;
42
+ padding-bottom: $space-4;
43
+ }
44
+
45
+ &--spacing-md {
46
+ padding-top: $space-6;
47
+ padding-bottom: $space-6;
48
+
49
+ @include md-up {
50
+ padding-top: $space-8;
51
+ padding-bottom: $space-8;
52
+ }
53
+ }
54
+
55
+ &--spacing-lg {
56
+ padding-top: $space-8;
57
+ padding-bottom: $space-8;
58
+
59
+ @include md-up {
60
+ padding-top: $space-10;
61
+ padding-bottom: $space-10;
62
+ }
63
+ }
64
+
65
+ &--spacing-xl {
66
+ padding-top: $space-10;
67
+ padding-bottom: $space-10;
68
+
69
+ @include md-up {
70
+ padding-top: $space-12;
71
+ padding-bottom: $space-12;
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,37 @@
1
+ import cx from "classnames"
2
+
3
+ export type SectionBackground = "white" | "linen" | "charcoal"
4
+ export type SectionSpacing = "none" | "sm" | "md" | "lg" | "xl"
5
+
6
+ export interface SectionProps {
7
+ children: React.ReactNode
8
+ className?: string
9
+ style?: React.CSSProperties
10
+ background?: SectionBackground
11
+ spacing?: SectionSpacing
12
+ id?: string
13
+ }
14
+
15
+ export function Section({
16
+ children,
17
+ className,
18
+ style,
19
+ background = "white",
20
+ spacing = "lg",
21
+ id,
22
+ }: SectionProps) {
23
+ const classes = cx(
24
+ "bc-section",
25
+ `bc-section--bg-${background}`,
26
+ `bc-section--spacing-${spacing}`,
27
+ className
28
+ )
29
+
30
+ return (
31
+ <section className={classes} style={style} id={id}>
32
+ {children}
33
+ </section>
34
+ )
35
+ }
36
+
37
+ export default Section
@@ -0,0 +1,2 @@
1
+ export { Section, type SectionProps, type SectionBackground, type SectionSpacing } from "./Section"
2
+ export { default } from "./Section"
@@ -0,0 +1,61 @@
1
+ @use "../../styles/tokens" as *;
2
+
3
+ /*******************************************************************************
4
+ * Stack Component
5
+ *
6
+ * Flexbox-based layout for consistent spacing
7
+ ******************************************************************************/
8
+
9
+ .bc-stack {
10
+ display: flex;
11
+
12
+ /***************************************************************************
13
+ * Direction Variants
14
+ ***************************************************************************/
15
+
16
+ &--vertical {
17
+ flex-direction: column;
18
+ }
19
+
20
+ &--horizontal {
21
+ flex-direction: row;
22
+ }
23
+
24
+ /***************************************************************************
25
+ * Spacing Variants
26
+ ***************************************************************************/
27
+
28
+ &--spacing-none { gap: 0; }
29
+ &--spacing-xs { gap: calc($space-1 / 2); }
30
+ &--spacing-sm { gap: $space-1; }
31
+ &--spacing-md { gap: $space-2; }
32
+ &--spacing-lg { gap: $space-3; }
33
+ &--spacing-xl { gap: $space-4; }
34
+
35
+ /***************************************************************************
36
+ * Alignment Variants
37
+ ***************************************************************************/
38
+
39
+ &--align-start { align-items: flex-start; }
40
+ &--align-center { align-items: center; }
41
+ &--align-end { align-items: flex-end; }
42
+ &--align-stretch { align-items: stretch; }
43
+
44
+ /***************************************************************************
45
+ * Justify Variants
46
+ ***************************************************************************/
47
+
48
+ &--justify-start { justify-content: flex-start; }
49
+ &--justify-center { justify-content: center; }
50
+ &--justify-end { justify-content: flex-end; }
51
+ &--justify-between { justify-content: space-between; }
52
+ &--justify-around { justify-content: space-around; }
53
+
54
+ /***************************************************************************
55
+ * Wrap
56
+ ***************************************************************************/
57
+
58
+ &--wrap {
59
+ flex-wrap: wrap;
60
+ }
61
+ }
@@ -0,0 +1,48 @@
1
+ import cx from "classnames"
2
+
3
+ export type StackDirection = "vertical" | "horizontal"
4
+ export type StackSpacing = "none" | "xs" | "sm" | "md" | "lg" | "xl"
5
+ export type StackAlign = "start" | "center" | "end" | "stretch"
6
+ export type StackJustify = "start" | "center" | "end" | "between" | "around"
7
+
8
+ export interface StackProps {
9
+ children: React.ReactNode
10
+ className?: string
11
+ style?: React.CSSProperties
12
+ direction?: StackDirection
13
+ spacing?: StackSpacing
14
+ align?: StackAlign
15
+ justify?: StackJustify
16
+ wrap?: boolean
17
+ as?: "div" | "ul" | "ol" | "nav"
18
+ }
19
+
20
+ export function Stack({
21
+ children,
22
+ className,
23
+ style,
24
+ direction = "vertical",
25
+ spacing = "md",
26
+ align = "stretch",
27
+ justify = "start",
28
+ wrap = false,
29
+ as: Component = "div",
30
+ }: StackProps) {
31
+ const classes = cx(
32
+ "bc-stack",
33
+ `bc-stack--${direction}`,
34
+ `bc-stack--spacing-${spacing}`,
35
+ `bc-stack--align-${align}`,
36
+ `bc-stack--justify-${justify}`,
37
+ { "bc-stack--wrap": wrap },
38
+ className
39
+ )
40
+
41
+ return (
42
+ <Component className={classes} style={style}>
43
+ {children}
44
+ </Component>
45
+ )
46
+ }
47
+
48
+ export default Stack
@@ -0,0 +1,9 @@
1
+ export {
2
+ Stack,
3
+ type StackProps,
4
+ type StackDirection,
5
+ type StackSpacing,
6
+ type StackAlign,
7
+ type StackJustify,
8
+ } from "./Stack"
9
+ export { default } from "./Stack"
@@ -0,0 +1,233 @@
1
+ @use "../../styles/tokens" as *;
2
+ @use "../../styles/typography" as *;
3
+
4
+ /*******************************************************************************
5
+ * Footer Component
6
+ ******************************************************************************/
7
+
8
+ .bc-footer {
9
+ background-color: $charcoal;
10
+ color: $white;
11
+ padding: $space-8 0;
12
+
13
+ /***************************************************************************
14
+ * Inner Container
15
+ ***************************************************************************/
16
+
17
+ &__inner {
18
+ max-width: 1280px;
19
+ margin: 0 auto;
20
+ padding: 0 $space-3;
21
+
22
+ @include md-up {
23
+ padding: 0 $space-5;
24
+ }
25
+ }
26
+
27
+ /***************************************************************************
28
+ * Main Section
29
+ ***************************************************************************/
30
+
31
+ &__main {
32
+ display: grid;
33
+ gap: $space-6;
34
+
35
+ @include md-up {
36
+ grid-template-columns: auto 1fr auto;
37
+ align-items: start;
38
+ }
39
+ }
40
+
41
+ /***************************************************************************
42
+ * Logo
43
+ ***************************************************************************/
44
+
45
+ &__logo {
46
+ img {
47
+ height: 32px;
48
+ width: auto;
49
+ filter: brightness(0) invert(1);
50
+ }
51
+ }
52
+
53
+ /***************************************************************************
54
+ * Navigation
55
+ ***************************************************************************/
56
+
57
+ &__nav {
58
+ @include md-up {
59
+ justify-self: center;
60
+ }
61
+ }
62
+
63
+ &__nav-list {
64
+ display: flex;
65
+ flex-wrap: wrap;
66
+ gap: $space-1 $space-3;
67
+ list-style: none;
68
+ margin: 0;
69
+ padding: 0;
70
+ }
71
+
72
+ &__nav-link {
73
+ @include label;
74
+ color: rgba($white, 0.8);
75
+ text-decoration: none;
76
+ transition: color $transition-fast;
77
+
78
+ &:hover {
79
+ color: $white;
80
+ }
81
+ }
82
+
83
+ /***************************************************************************
84
+ * Newsletter
85
+ ***************************************************************************/
86
+
87
+ &__newsletter {
88
+ @include md-up {
89
+ text-align: right;
90
+ max-width: 320px;
91
+ }
92
+ }
93
+
94
+ &__newsletter-heading {
95
+ @include h4;
96
+ color: $white;
97
+ margin: 0 0 calc($space-1 / 2);
98
+ }
99
+
100
+ &__newsletter-description {
101
+ @include body-3;
102
+ color: rgba($white, 0.7);
103
+ margin: 0 0 $space-2;
104
+ }
105
+
106
+ &__newsletter-form {
107
+ display: flex;
108
+ gap: calc($space-1 / 2);
109
+ }
110
+
111
+ &__newsletter-input {
112
+ @include body-3;
113
+ flex: 1;
114
+ min-width: 0;
115
+ padding: $space-1 $space-2;
116
+ border: none;
117
+ border-radius: $radius-none;
118
+ background-color: rgba($white, 0.1);
119
+ color: $white;
120
+
121
+ &::placeholder {
122
+ color: rgba($white, 0.5);
123
+ }
124
+
125
+ &:focus {
126
+ outline: 2px solid $auburn;
127
+ outline-offset: -2px;
128
+ }
129
+ }
130
+
131
+ &__newsletter-button {
132
+ @include button-text-sm;
133
+ padding: $space-1 $space-2;
134
+ background-color: $auburn;
135
+ color: $white;
136
+ border: none;
137
+ border-radius: $radius-none;
138
+ cursor: pointer;
139
+ transition: background-color $transition-fast;
140
+
141
+ &:hover {
142
+ background-color: $auburn-600;
143
+ }
144
+ }
145
+
146
+ /***************************************************************************
147
+ * Bottom Section
148
+ ***************************************************************************/
149
+
150
+ &__bottom {
151
+ display: flex;
152
+ flex-wrap: wrap;
153
+ align-items: center;
154
+ justify-content: space-between;
155
+ gap: $space-2;
156
+ margin-top: $space-6;
157
+ padding-top: $space-4;
158
+ border-top: 1px solid rgba($white, 0.1);
159
+ }
160
+
161
+ /***************************************************************************
162
+ * Social Links
163
+ ***************************************************************************/
164
+
165
+ &__social {
166
+ display: flex;
167
+ gap: $space-2;
168
+ }
169
+
170
+ &__social-link {
171
+ display: flex;
172
+ align-items: center;
173
+ justify-content: center;
174
+ width: 36px;
175
+ height: 36px;
176
+ color: rgba($white, 0.7);
177
+ transition: color $transition-fast;
178
+
179
+ &:hover {
180
+ color: $white;
181
+ }
182
+
183
+ svg {
184
+ width: 20px;
185
+ height: 20px;
186
+ }
187
+ }
188
+
189
+ /***************************************************************************
190
+ * Copyright
191
+ ***************************************************************************/
192
+
193
+ &__copyright {
194
+ @include body-3;
195
+ color: rgba($white, 0.5);
196
+ margin: 0;
197
+ }
198
+
199
+ /***************************************************************************
200
+ * Quote
201
+ ***************************************************************************/
202
+
203
+ &__quote {
204
+ margin-top: $space-6;
205
+ padding-top: $space-4;
206
+ border-top: 1px solid rgba($white, 0.1);
207
+ text-align: center;
208
+ }
209
+
210
+ &__quote-text {
211
+ @include body-1;
212
+ font-style: italic;
213
+ color: rgba($white, 0.8);
214
+ margin: 0 0 $space-1;
215
+ max-width: 640px;
216
+ margin-left: auto;
217
+ margin-right: auto;
218
+
219
+ &::before {
220
+ content: '"';
221
+ }
222
+
223
+ &::after {
224
+ content: '"';
225
+ }
226
+ }
227
+
228
+ &__quote-attribution {
229
+ @include micro;
230
+ color: rgba($white, 0.5);
231
+ font-style: normal;
232
+ }
233
+ }
@@ -0,0 +1,174 @@
1
+ import cx from "classnames"
2
+
3
+ export interface FooterLink {
4
+ label: string
5
+ href: string
6
+ }
7
+
8
+ export interface SocialLink {
9
+ platform: "twitter" | "linkedin" | "instagram" | "github" | "email" | "bluesky"
10
+ href: string
11
+ label?: string
12
+ }
13
+
14
+ export interface FooterProps {
15
+ logo?: React.ReactNode
16
+ links?: FooterLink[]
17
+ socialLinks?: SocialLink[]
18
+ newsletter?: {
19
+ heading: string
20
+ description?: string
21
+ placeholder?: string
22
+ buttonText?: string
23
+ onSubmit?: (email: string) => void
24
+ }
25
+ quote?: {
26
+ text: string
27
+ attribution: string
28
+ }
29
+ copyright?: string
30
+ className?: string
31
+ style?: React.CSSProperties
32
+ }
33
+
34
+ const socialIcons: Record<SocialLink["platform"], React.ReactNode> = {
35
+ twitter: (
36
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
37
+ <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
38
+ </svg>
39
+ ),
40
+ linkedin: (
41
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
42
+ <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
43
+ </svg>
44
+ ),
45
+ instagram: (
46
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
47
+ <path d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z" />
48
+ </svg>
49
+ ),
50
+ github: (
51
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
52
+ <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
53
+ </svg>
54
+ ),
55
+ email: (
56
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
57
+ <path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
58
+ </svg>
59
+ ),
60
+ bluesky: (
61
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
62
+ <path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 01-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8z" />
63
+ </svg>
64
+ ),
65
+ }
66
+
67
+ export function Footer({
68
+ logo,
69
+ links = [],
70
+ socialLinks = [],
71
+ newsletter,
72
+ quote,
73
+ copyright,
74
+ className,
75
+ style,
76
+ }: FooterProps) {
77
+ const classes = cx("bc-footer", className)
78
+
79
+ const handleNewsletterSubmit = (e: React.FormEvent<HTMLFormElement>) => {
80
+ e.preventDefault()
81
+ const formData = new FormData(e.currentTarget)
82
+ const email = formData.get("email") as string
83
+ newsletter?.onSubmit?.(email)
84
+ }
85
+
86
+ return (
87
+ <footer className={classes} style={style}>
88
+ <div className="bc-footer__inner">
89
+ <div className="bc-footer__main">
90
+ {logo && <div className="bc-footer__logo">{logo}</div>}
91
+
92
+ {links.length > 0 && (
93
+ <nav className="bc-footer__nav" aria-label="Footer navigation">
94
+ <ul className="bc-footer__nav-list">
95
+ {links.map((link) => (
96
+ <li key={link.label}>
97
+ <a href={link.href} className="bc-footer__nav-link">
98
+ {link.label}
99
+ </a>
100
+ </li>
101
+ ))}
102
+ </ul>
103
+ </nav>
104
+ )}
105
+
106
+ {newsletter && (
107
+ <div className="bc-footer__newsletter">
108
+ <h4 className="bc-footer__newsletter-heading">
109
+ {newsletter.heading}
110
+ </h4>
111
+ {newsletter.description && (
112
+ <p className="bc-footer__newsletter-description">
113
+ {newsletter.description}
114
+ </p>
115
+ )}
116
+ <form
117
+ className="bc-footer__newsletter-form"
118
+ onSubmit={handleNewsletterSubmit}
119
+ >
120
+ <input
121
+ type="email"
122
+ name="email"
123
+ placeholder={newsletter.placeholder || "Enter your email"}
124
+ className="bc-footer__newsletter-input"
125
+ required
126
+ />
127
+ <button
128
+ type="submit"
129
+ className="bc-footer__newsletter-button"
130
+ >
131
+ {newsletter.buttonText || "Subscribe"}
132
+ </button>
133
+ </form>
134
+ </div>
135
+ )}
136
+ </div>
137
+
138
+ <div className="bc-footer__bottom">
139
+ {socialLinks.length > 0 && (
140
+ <div className="bc-footer__social">
141
+ {socialLinks.map((link) => (
142
+ <a
143
+ key={link.platform}
144
+ href={link.href}
145
+ className="bc-footer__social-link"
146
+ aria-label={link.label || link.platform}
147
+ >
148
+ {socialIcons[link.platform]}
149
+ </a>
150
+ ))}
151
+ </div>
152
+ )}
153
+
154
+ {copyright && (
155
+ <p className="bc-footer__copyright">{copyright}</p>
156
+ )}
157
+ </div>
158
+
159
+ {quote && (
160
+ <div className="bc-footer__quote">
161
+ <blockquote className="bc-footer__quote-text">
162
+ {quote.text}
163
+ </blockquote>
164
+ <cite className="bc-footer__quote-attribution">
165
+ — {quote.attribution}
166
+ </cite>
167
+ </div>
168
+ )}
169
+ </div>
170
+ </footer>
171
+ )
172
+ }
173
+
174
+ export default Footer
@@ -0,0 +1,2 @@
1
+ export { Footer, type FooterProps, type FooterLink, type SocialLink } from "./Footer"
2
+ export { default } from "./Footer"