@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,351 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
import { within, userEvent, expect, fn } from "@storybook/test"
|
|
3
|
+
|
|
4
|
+
import { Footer } from "./Footer"
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Footer> = {
|
|
7
|
+
title: "Components/Navigation/Footer",
|
|
8
|
+
component: Footer,
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: "fullscreen",
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component: `
|
|
14
|
+
A site footer component with logo, navigation links, social links, newsletter signup, and optional quote.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
\`\`\`tsx
|
|
19
|
+
import { Footer } from "@buildcanada/components"
|
|
20
|
+
|
|
21
|
+
<Footer
|
|
22
|
+
logo={<Logo />}
|
|
23
|
+
links={[
|
|
24
|
+
{ label: "Home", href: "/" },
|
|
25
|
+
{ label: "About", href: "/about" },
|
|
26
|
+
{ label: "Privacy", href: "/privacy" },
|
|
27
|
+
]}
|
|
28
|
+
socialLinks={[
|
|
29
|
+
{ platform: "twitter", href: "https://twitter.com/..." },
|
|
30
|
+
{ platform: "github", href: "https://github.com/..." },
|
|
31
|
+
]}
|
|
32
|
+
copyright="© 2024 Company Name"
|
|
33
|
+
/>
|
|
34
|
+
\`\`\`
|
|
35
|
+
|
|
36
|
+
## With Newsletter
|
|
37
|
+
|
|
38
|
+
\`\`\`tsx
|
|
39
|
+
<Footer
|
|
40
|
+
logo={<Logo />}
|
|
41
|
+
links={links}
|
|
42
|
+
newsletter={{
|
|
43
|
+
heading: "Stay Updated",
|
|
44
|
+
description: "Get the latest news delivered to your inbox.",
|
|
45
|
+
placeholder: "Enter your email",
|
|
46
|
+
buttonText: "Subscribe",
|
|
47
|
+
onSubmit: (email) => console.log(email),
|
|
48
|
+
}}
|
|
49
|
+
copyright="© 2024 Company Name"
|
|
50
|
+
/>
|
|
51
|
+
\`\`\`
|
|
52
|
+
|
|
53
|
+
## Social Platforms
|
|
54
|
+
|
|
55
|
+
Supported platforms: twitter, linkedin, github, email, bluesky
|
|
56
|
+
`,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default meta
|
|
63
|
+
type Story = StoryObj<typeof Footer>
|
|
64
|
+
|
|
65
|
+
const Logo = () => (
|
|
66
|
+
<span style={{
|
|
67
|
+
fontFamily: "'Soehne Kraftig', 'Helvetica Neue', Helvetica, Arial, sans-serif",
|
|
68
|
+
fontSize: "18px",
|
|
69
|
+
fontWeight: 500,
|
|
70
|
+
color: "#ffffff",
|
|
71
|
+
letterSpacing: "-0.01em",
|
|
72
|
+
}}>
|
|
73
|
+
Build Canada
|
|
74
|
+
</span>
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const basicLinks = [
|
|
78
|
+
{ label: "Home", href: "/" },
|
|
79
|
+
{ label: "About", href: "/about" },
|
|
80
|
+
{ label: "Projects", href: "/projects" },
|
|
81
|
+
{ label: "Contact", href: "/contact" },
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
const extendedLinks = [
|
|
85
|
+
{ label: "Home", href: "/" },
|
|
86
|
+
{ label: "About", href: "/about" },
|
|
87
|
+
{ label: "Projects", href: "/projects" },
|
|
88
|
+
{ label: "Research", href: "/research" },
|
|
89
|
+
{ label: "Data", href: "/data" },
|
|
90
|
+
{ label: "Blog", href: "/blog" },
|
|
91
|
+
{ label: "Privacy", href: "/privacy" },
|
|
92
|
+
{ label: "Terms", href: "/terms" },
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
const socialLinks = [
|
|
96
|
+
{ platform: "twitter" as const, href: "https://twitter.com/buildcanada" },
|
|
97
|
+
{ platform: "linkedin" as const, href: "https://linkedin.com/company/buildcanada" },
|
|
98
|
+
{ platform: "github" as const, href: "https://github.com/buildcanada" },
|
|
99
|
+
{ platform: "email" as const, href: "mailto:hello@buildcanada.com", label: "Email us" },
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
const socialLinksWithBluesky = [
|
|
103
|
+
{ platform: "twitter" as const, href: "https://twitter.com/buildcanada" },
|
|
104
|
+
{ platform: "bluesky" as const, href: "https://bsky.app/profile/buildcanada" },
|
|
105
|
+
{ platform: "linkedin" as const, href: "https://linkedin.com/company/buildcanada" },
|
|
106
|
+
{ platform: "github" as const, href: "https://github.com/buildcanada" },
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
export const Default: Story = {
|
|
110
|
+
args: {
|
|
111
|
+
logo: <Logo />,
|
|
112
|
+
links: basicLinks,
|
|
113
|
+
socialLinks: socialLinks,
|
|
114
|
+
copyright: "© 2024 Build Canada. All rights reserved.",
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const WithNewsletter: Story = {
|
|
119
|
+
args: {
|
|
120
|
+
logo: <Logo />,
|
|
121
|
+
links: basicLinks,
|
|
122
|
+
socialLinks: socialLinks,
|
|
123
|
+
newsletter: {
|
|
124
|
+
heading: "Stay Updated",
|
|
125
|
+
description: "Get the latest insights delivered to your inbox.",
|
|
126
|
+
placeholder: "Enter your email",
|
|
127
|
+
buttonText: "Subscribe",
|
|
128
|
+
onSubmit: (email) => console.log("Newsletter signup:", email),
|
|
129
|
+
},
|
|
130
|
+
copyright: "© 2024 Build Canada. All rights reserved.",
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const WithQuote: Story = {
|
|
135
|
+
args: {
|
|
136
|
+
logo: <Logo />,
|
|
137
|
+
links: basicLinks,
|
|
138
|
+
socialLinks: socialLinks,
|
|
139
|
+
quote: {
|
|
140
|
+
text: "The measure of a great democracy is how well it serves its citizens through transparent and accountable governance.",
|
|
141
|
+
attribution: "Build Canada Mission Statement",
|
|
142
|
+
},
|
|
143
|
+
copyright: "© 2024 Build Canada. All rights reserved.",
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const FullFeatured: Story = {
|
|
148
|
+
args: {
|
|
149
|
+
logo: <Logo />,
|
|
150
|
+
links: extendedLinks,
|
|
151
|
+
socialLinks: socialLinks,
|
|
152
|
+
newsletter: {
|
|
153
|
+
heading: "Join Our Newsletter",
|
|
154
|
+
description: "Weekly insights on Canadian government spending and policy.",
|
|
155
|
+
placeholder: "you@example.com",
|
|
156
|
+
buttonText: "Subscribe",
|
|
157
|
+
},
|
|
158
|
+
quote: {
|
|
159
|
+
text: "Democracy requires an informed citizenry. We make government data accessible to all.",
|
|
160
|
+
attribution: "Build Canada",
|
|
161
|
+
},
|
|
162
|
+
copyright: "© 2024 Build Canada. All rights reserved.",
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const MinimalFooter: Story = {
|
|
167
|
+
args: {
|
|
168
|
+
copyright: "© 2024 Build Canada",
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const WithLogoOnly: Story = {
|
|
173
|
+
args: {
|
|
174
|
+
logo: <Logo />,
|
|
175
|
+
copyright: "© 2024 Build Canada. All rights reserved.",
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const WithSocialOnly: Story = {
|
|
180
|
+
args: {
|
|
181
|
+
socialLinks: socialLinks,
|
|
182
|
+
copyright: "© 2024 Build Canada",
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const WithBluesky: Story = {
|
|
187
|
+
args: {
|
|
188
|
+
logo: <Logo />,
|
|
189
|
+
links: basicLinks,
|
|
190
|
+
socialLinks: socialLinksWithBluesky,
|
|
191
|
+
copyright: "© 2024 Build Canada. All rights reserved.",
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export const CanadaSpendsFooter: Story = {
|
|
196
|
+
args: {
|
|
197
|
+
logo: (
|
|
198
|
+
<span style={{ color: "#fff", fontWeight: 600, fontSize: "18px", fontFamily: "sans-serif" }}>
|
|
199
|
+
Canada Spends
|
|
200
|
+
</span>
|
|
201
|
+
),
|
|
202
|
+
links: [
|
|
203
|
+
{ label: "Charts", href: "/charts" },
|
|
204
|
+
{ label: "Data", href: "/data" },
|
|
205
|
+
{ label: "Topics", href: "/topics" },
|
|
206
|
+
{ label: "About", href: "/about" },
|
|
207
|
+
{ label: "Methodology", href: "/methodology" },
|
|
208
|
+
{ label: "API", href: "/api" },
|
|
209
|
+
],
|
|
210
|
+
socialLinks: [
|
|
211
|
+
{ platform: "twitter" as const, href: "https://twitter.com/canadaspends" },
|
|
212
|
+
{ platform: "github" as const, href: "https://github.com/buildcanada/canada-spends" },
|
|
213
|
+
],
|
|
214
|
+
newsletter: {
|
|
215
|
+
heading: "Data Updates",
|
|
216
|
+
description: "Get notified when new datasets are published.",
|
|
217
|
+
placeholder: "your@email.com",
|
|
218
|
+
buttonText: "Notify Me",
|
|
219
|
+
},
|
|
220
|
+
copyright: "© 2024 Canada Spends. A Build Canada Project.",
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const BuildCanadaFooter: Story = {
|
|
225
|
+
args: {
|
|
226
|
+
logo: (
|
|
227
|
+
<span style={{ color: "#fff", fontWeight: 600, fontSize: "18px", fontFamily: "sans-serif" }}>
|
|
228
|
+
Build Canada
|
|
229
|
+
</span>
|
|
230
|
+
),
|
|
231
|
+
links: [
|
|
232
|
+
{ label: "Home", href: "/" },
|
|
233
|
+
{ label: "About", href: "/about" },
|
|
234
|
+
{ label: "Projects", href: "/projects" },
|
|
235
|
+
{ label: "Memos", href: "/memos" },
|
|
236
|
+
{ label: "Team", href: "/team" },
|
|
237
|
+
{ label: "Contact", href: "/contact" },
|
|
238
|
+
],
|
|
239
|
+
socialLinks: socialLinks,
|
|
240
|
+
newsletter: {
|
|
241
|
+
heading: "Stay Informed",
|
|
242
|
+
description: "Join our mailing list for weekly updates on Canadian policy and data.",
|
|
243
|
+
placeholder: "Enter your email",
|
|
244
|
+
buttonText: "Subscribe",
|
|
245
|
+
},
|
|
246
|
+
quote: {
|
|
247
|
+
text: "We believe an informed citizenry is essential to a functioning democracy.",
|
|
248
|
+
attribution: "Build Canada",
|
|
249
|
+
},
|
|
250
|
+
copyright: "© 2024 Build Canada. All rights reserved.",
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export const LongLinkList: Story = {
|
|
255
|
+
args: {
|
|
256
|
+
logo: <Logo />,
|
|
257
|
+
links: [
|
|
258
|
+
{ label: "Home", href: "/" },
|
|
259
|
+
{ label: "About Us", href: "/about" },
|
|
260
|
+
{ label: "Our Mission", href: "/mission" },
|
|
261
|
+
{ label: "Projects", href: "/projects" },
|
|
262
|
+
{ label: "Canada Spends", href: "/canada-spends" },
|
|
263
|
+
{ label: "Research", href: "/research" },
|
|
264
|
+
{ label: "Reports", href: "/reports" },
|
|
265
|
+
{ label: "Data", href: "/data" },
|
|
266
|
+
{ label: "Blog", href: "/blog" },
|
|
267
|
+
{ label: "Careers", href: "/careers" },
|
|
268
|
+
{ label: "Press", href: "/press" },
|
|
269
|
+
{ label: "Contact", href: "/contact" },
|
|
270
|
+
{ label: "Privacy Policy", href: "/privacy" },
|
|
271
|
+
{ label: "Terms of Use", href: "/terms" },
|
|
272
|
+
],
|
|
273
|
+
socialLinks: socialLinks,
|
|
274
|
+
copyright: "© 2024 Build Canada. All rights reserved.",
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Interactive test: Footer links
|
|
279
|
+
export const LinksTest: Story = {
|
|
280
|
+
args: {
|
|
281
|
+
links: basicLinks,
|
|
282
|
+
copyright: "© 2024 Build Canada",
|
|
283
|
+
},
|
|
284
|
+
play: async ({ canvasElement }) => {
|
|
285
|
+
const canvas = within(canvasElement)
|
|
286
|
+
|
|
287
|
+
const homeLink = canvas.getByRole("link", { name: /home/i })
|
|
288
|
+
const aboutLink = canvas.getByRole("link", { name: /about/i })
|
|
289
|
+
|
|
290
|
+
await expect(homeLink).toBeInTheDocument()
|
|
291
|
+
await expect(aboutLink).toBeInTheDocument()
|
|
292
|
+
await expect(homeLink).toHaveAttribute("href", "/")
|
|
293
|
+
},
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Interactive test: Social links
|
|
297
|
+
export const SocialLinksTest: Story = {
|
|
298
|
+
args: {
|
|
299
|
+
socialLinks: socialLinks,
|
|
300
|
+
copyright: "© 2024 Build Canada",
|
|
301
|
+
},
|
|
302
|
+
play: async ({ canvasElement }) => {
|
|
303
|
+
const canvas = within(canvasElement)
|
|
304
|
+
|
|
305
|
+
// Check that social links are present (they should be links with icons)
|
|
306
|
+
const twitterLink = canvas.getByRole("link", { name: /twitter/i })
|
|
307
|
+
const githubLink = canvas.getByRole("link", { name: /github/i })
|
|
308
|
+
|
|
309
|
+
await expect(twitterLink).toBeInTheDocument()
|
|
310
|
+
await expect(githubLink).toBeInTheDocument()
|
|
311
|
+
},
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Interactive test: Newsletter form
|
|
315
|
+
export const NewsletterTest: Story = {
|
|
316
|
+
args: {
|
|
317
|
+
newsletter: {
|
|
318
|
+
heading: "Subscribe",
|
|
319
|
+
description: "Get updates",
|
|
320
|
+
placeholder: "your@email.com",
|
|
321
|
+
buttonText: "Subscribe",
|
|
322
|
+
onSubmit: fn(),
|
|
323
|
+
},
|
|
324
|
+
copyright: "© 2024 Build Canada",
|
|
325
|
+
},
|
|
326
|
+
play: async ({ canvasElement, args }) => {
|
|
327
|
+
const canvas = within(canvasElement)
|
|
328
|
+
|
|
329
|
+
const emailInput = canvas.getByPlaceholderText("your@email.com")
|
|
330
|
+
const submitButton = canvas.getByRole("button", { name: /subscribe/i })
|
|
331
|
+
|
|
332
|
+
await expect(emailInput).toBeInTheDocument()
|
|
333
|
+
await expect(submitButton).toBeInTheDocument()
|
|
334
|
+
|
|
335
|
+
await userEvent.type(emailInput, "test@example.com")
|
|
336
|
+
await expect(emailInput).toHaveValue("test@example.com")
|
|
337
|
+
},
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Interactive test: Copyright text
|
|
341
|
+
export const CopyrightTest: Story = {
|
|
342
|
+
args: {
|
|
343
|
+
copyright: "© 2024 Test Company. All rights reserved.",
|
|
344
|
+
},
|
|
345
|
+
play: async ({ canvasElement }) => {
|
|
346
|
+
const canvas = within(canvasElement)
|
|
347
|
+
|
|
348
|
+
const copyright = canvas.getByText(/2024 Test Company/i)
|
|
349
|
+
await expect(copyright).toBeInTheDocument()
|
|
350
|
+
},
|
|
351
|
+
}
|
|
@@ -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
|