@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,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,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,204 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
import { within, userEvent, expect, fn } from "@storybook/test"
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
|
|
5
|
+
import { Checkbox } from "./Checkbox"
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Checkbox> = {
|
|
8
|
+
title: "Components/Primitives/Checkbox",
|
|
9
|
+
component: Checkbox,
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component: `
|
|
14
|
+
A styled checkbox component with label support.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
\`\`\`tsx
|
|
19
|
+
import { Checkbox } from "@buildcanada/components"
|
|
20
|
+
|
|
21
|
+
<Checkbox label="I agree to the terms" />
|
|
22
|
+
\`\`\`
|
|
23
|
+
|
|
24
|
+
## Controlled vs Uncontrolled
|
|
25
|
+
|
|
26
|
+
Use \`defaultChecked\` for uncontrolled or \`checked\` with \`onChange\` for controlled:
|
|
27
|
+
|
|
28
|
+
\`\`\`tsx
|
|
29
|
+
// Uncontrolled
|
|
30
|
+
<Checkbox label="Option" defaultChecked />
|
|
31
|
+
|
|
32
|
+
// Controlled
|
|
33
|
+
<Checkbox
|
|
34
|
+
label="Option"
|
|
35
|
+
checked={isChecked}
|
|
36
|
+
onChange={(e) => setIsChecked(e.target.checked)}
|
|
37
|
+
/>
|
|
38
|
+
\`\`\`
|
|
39
|
+
`,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
argTypes: {
|
|
44
|
+
label: { description: "The label text displayed next to the checkbox" },
|
|
45
|
+
disabled: { description: "Whether the checkbox is disabled" },
|
|
46
|
+
defaultChecked: { description: "Initial checked state (uncontrolled)" },
|
|
47
|
+
checked: { description: "Controlled checked state" },
|
|
48
|
+
onChange: { description: "Callback when checkbox state changes" },
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default meta
|
|
53
|
+
type Story = StoryObj<typeof Checkbox>
|
|
54
|
+
|
|
55
|
+
export const Default: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
label: "I agree to the terms and conditions",
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const Checked: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
label: "Subscribe to newsletter",
|
|
64
|
+
defaultChecked: true,
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const Unchecked: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
label: "Remember my preferences",
|
|
71
|
+
defaultChecked: false,
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const Disabled: Story = {
|
|
76
|
+
args: {
|
|
77
|
+
label: "This option is disabled",
|
|
78
|
+
disabled: true,
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const DisabledChecked: Story = {
|
|
83
|
+
args: {
|
|
84
|
+
label: "This option is checked and disabled",
|
|
85
|
+
defaultChecked: true,
|
|
86
|
+
disabled: true,
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const LongLabel: Story = {
|
|
91
|
+
args: {
|
|
92
|
+
label: "By checking this box, I confirm that I have read and understood the privacy policy, terms of service, and agree to receive communications from Build Canada.",
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Interactive test: Click to toggle
|
|
97
|
+
export const ClickTest: Story = {
|
|
98
|
+
args: {
|
|
99
|
+
label: "Click to toggle",
|
|
100
|
+
onChange: fn(),
|
|
101
|
+
},
|
|
102
|
+
play: async ({ canvasElement, args }) => {
|
|
103
|
+
const canvas = within(canvasElement)
|
|
104
|
+
const checkbox = canvas.getByRole("checkbox", { name: /click to toggle/i })
|
|
105
|
+
|
|
106
|
+
await expect(checkbox).not.toBeChecked()
|
|
107
|
+
await userEvent.click(checkbox)
|
|
108
|
+
await expect(checkbox).toBeChecked()
|
|
109
|
+
await expect(args.onChange).toHaveBeenCalled()
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Interactive test: Double click
|
|
114
|
+
export const DoubleClickTest: Story = {
|
|
115
|
+
args: {
|
|
116
|
+
label: "Double click test",
|
|
117
|
+
onChange: fn(),
|
|
118
|
+
},
|
|
119
|
+
play: async ({ canvasElement }) => {
|
|
120
|
+
const canvas = within(canvasElement)
|
|
121
|
+
const checkbox = canvas.getByRole("checkbox", { name: /double click/i })
|
|
122
|
+
|
|
123
|
+
await expect(checkbox).not.toBeChecked()
|
|
124
|
+
await userEvent.click(checkbox)
|
|
125
|
+
await expect(checkbox).toBeChecked()
|
|
126
|
+
await userEvent.click(checkbox)
|
|
127
|
+
await expect(checkbox).not.toBeChecked()
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Interactive test: Disabled should not toggle
|
|
132
|
+
export const DisabledClickTest: Story = {
|
|
133
|
+
args: {
|
|
134
|
+
label: "Disabled checkbox",
|
|
135
|
+
disabled: true,
|
|
136
|
+
onChange: fn(),
|
|
137
|
+
},
|
|
138
|
+
play: async ({ canvasElement, args }) => {
|
|
139
|
+
const canvas = within(canvasElement)
|
|
140
|
+
const checkbox = canvas.getByRole("checkbox", { name: /disabled checkbox/i })
|
|
141
|
+
|
|
142
|
+
await expect(checkbox).toBeDisabled()
|
|
143
|
+
await userEvent.click(checkbox)
|
|
144
|
+
await expect(args.onChange).not.toHaveBeenCalled()
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Interactive test: Keyboard interaction
|
|
149
|
+
export const KeyboardTest: Story = {
|
|
150
|
+
args: {
|
|
151
|
+
label: "Keyboard toggle",
|
|
152
|
+
onChange: fn(),
|
|
153
|
+
},
|
|
154
|
+
play: async ({ canvasElement }) => {
|
|
155
|
+
const canvas = within(canvasElement)
|
|
156
|
+
const checkbox = canvas.getByRole("checkbox", { name: /keyboard toggle/i })
|
|
157
|
+
|
|
158
|
+
checkbox.focus()
|
|
159
|
+
await expect(checkbox).toHaveFocus()
|
|
160
|
+
await userEvent.keyboard(" ")
|
|
161
|
+
await expect(checkbox).toBeChecked()
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const Controlled: Story = {
|
|
166
|
+
render: function ControlledCheckbox() {
|
|
167
|
+
const [checked, setChecked] = useState(false)
|
|
168
|
+
return (
|
|
169
|
+
<div>
|
|
170
|
+
<Checkbox
|
|
171
|
+
label={`Controlled checkbox (${checked ? "checked" : "unchecked"})`}
|
|
172
|
+
checked={checked}
|
|
173
|
+
onChange={(e) => setChecked(e.target.checked)}
|
|
174
|
+
/>
|
|
175
|
+
<p style={{ marginTop: "16px", fontFamily: "sans-serif", fontSize: "14px" }}>
|
|
176
|
+
The checkbox is currently: <strong>{checked ? "checked" : "unchecked"}</strong>
|
|
177
|
+
</p>
|
|
178
|
+
</div>
|
|
179
|
+
)
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const MultipleCheckboxes: Story = {
|
|
184
|
+
render: () => (
|
|
185
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
|
186
|
+
<Checkbox label="Option A" defaultChecked />
|
|
187
|
+
<Checkbox label="Option B" />
|
|
188
|
+
<Checkbox label="Option C" />
|
|
189
|
+
<Checkbox label="Option D (disabled)" disabled />
|
|
190
|
+
</div>
|
|
191
|
+
),
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export const FormExample: Story = {
|
|
195
|
+
render: () => (
|
|
196
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "16px", maxWidth: "500px" }}>
|
|
197
|
+
<h3 style={{ margin: 0, fontFamily: "sans-serif" }}>Communication Preferences</h3>
|
|
198
|
+
<Checkbox label="Email updates about new features" defaultChecked />
|
|
199
|
+
<Checkbox label="Weekly newsletter" defaultChecked />
|
|
200
|
+
<Checkbox label="Partner offers and promotions" />
|
|
201
|
+
<Checkbox label="Account activity alerts" defaultChecked />
|
|
202
|
+
</div>
|
|
203
|
+
),
|
|
204
|
+
}
|
|
@@ -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,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
|
+
}
|