@purpurds/checkbox 3.0.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.
@@ -0,0 +1 @@
1
+ ._purpur-checkbox_10fl8_1{all:unset;padding:calc((var(--purpur-spacing-400) + var(--purpur-spacing-150) - var(--purpur-spacing-300)) / 2);margin:calc(-1 * (var(--purpur-spacing-400) + var(--purpur-spacing-150) - var(--purpur-spacing-300)) / 2);cursor:pointer}._purpur-checkbox_10fl8_1:active>._purpur-checkbox__box_10fl8_7{box-shadow:var(--purpur-color-border-interactive-subtle-hover) 0 0 0 var(--purpur-border-width-xs) inset}._purpur-checkbox_10fl8_1:disabled{cursor:default}._purpur-checkbox_10fl8_1:disabled>._purpur-checkbox__box_10fl8_7{box-shadow:var(--purpur-color-border-weak) 0 0 0 var(--purpur-border-width-xs) inset}._purpur-checkbox--error_10fl8_16>._purpur-checkbox__box_10fl8_7{box-shadow:var(--purpur-color-border-status-error) 0 0 0 var(--purpur-border-width-xs) inset}._purpur-checkbox--error_10fl8_16:not(:disabled)~label *{color:var(--purpur-color-text-status-error-strong)}._purpur-checkbox_10fl8_1:not(:disabled):not(:active):hover>._purpur-checkbox__box_10fl8_7{box-shadow:var(--purpur-color-border-interactive-subtle-hover) 0 0 0 var(--purpur-border-width-sm) inset}._purpur-checkbox_10fl8_1:focus-visible:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border-radius:var(--purpur-border-radius-xs);outline:var(--purpur-border-width-sm) solid var(--purpur-color-border-interactive-focus);outline-offset:var(--purpur-spacing-25);pointer-events:none}._purpur-checkbox_10fl8_1[data-state=checked]>._purpur-checkbox__box_10fl8_7,._purpur-checkbox_10fl8_1[data-state=indeterminate]>._purpur-checkbox__box_10fl8_7{box-shadow:none;background-color:var(--purpur-color-background-interactive-primary)}._purpur-checkbox_10fl8_1[data-state=checked]:active>._purpur-checkbox__box_10fl8_7,._purpur-checkbox_10fl8_1[data-state=indeterminate]:active>._purpur-checkbox__box_10fl8_7{background-color:var(--purpur-color-background-interactive-primary-active)}._purpur-checkbox_10fl8_1[data-state=checked]:disabled>._purpur-checkbox__box_10fl8_7,._purpur-checkbox_10fl8_1[data-state=indeterminate]:disabled>._purpur-checkbox__box_10fl8_7{box-shadow:var(--purpur-color-border-weak) 0 0 0 var(--purpur-border-width-xs) inset;background-color:var(--purpur-color-background-interactive-disabled)}._purpur-checkbox_10fl8_1[data-state=checked]:not(:disabled):not(:active):hover>._purpur-checkbox__box_10fl8_7,._purpur-checkbox_10fl8_1[data-state=indeterminate]:not(:disabled):not(:active):hover>._purpur-checkbox__box_10fl8_7{box-shadow:none;background-color:var(--purpur-color-background-interactive-primary-hover)}._purpur-checkbox__box_10fl8_7{display:block;height:var(--purpur-spacing-300);width:var(--purpur-spacing-300);background-color:var(--purpur-color-background-primary);border-radius:var(--purpur-border-radius-xs);box-shadow:var(--purpur-color-border-interactive-subtle) 0 0 0 var(--purpur-border-width-xs) inset;transition:box-shadow var(--purpur-motion-duration-150) ease,background-color var(--purpur-motion-duration-150) ease;box-sizing:border-box}._purpur-checkbox__indicator_10fl8_59{width:100%;height:100%;display:flex;align-items:center;justify-content:center;color:var(--purpur-color-text-interactive-on-primary)}._purpur-checkbox__indicator_10fl8_59[data-disabled]{color:var(--purpur-color-text-weak)}._purpur-checkbox__label_10fl8_70{padding-left:var(--purpur-spacing-150)}._purpur-checkbox__wrapper_10fl8_73{display:flex;flex-direction:column;justify-content:center;width:fit-content}._purpur-checkbox__container_10fl8_79{position:relative;display:flex;align-items:flex-start;width:fit-content;padding:calc((var(--purpur-spacing-400) + var(--purpur-spacing-150) - var(--purpur-spacing-300)) / 2) 0}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@purpurds/checkbox",
3
+ "version": "3.0.0",
4
+ "license": "AGPL-3.0-only",
5
+ "main": "./dist/checkbox.cjs.js",
6
+ "types": "./dist/checkbox.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "require": "./dist/checkbox.cjs.js",
10
+ "systemjs": "./dist/checkbox.system.js",
11
+ "types": "./dist/checkbox.d.ts",
12
+ "default": "./dist/checkbox.es.js"
13
+ },
14
+ "./styles": "./dist/styles.css"
15
+ },
16
+ "source": "src/checkbox.tsx",
17
+ "dependencies": {
18
+ "classnames": "~2.5.0",
19
+ "@radix-ui/react-checkbox": "~1.0.4",
20
+ "@storybook/client-api": "~7.6.0",
21
+ "@purpurds/tokens": "3.0.0",
22
+ "@purpurds/icon": "3.0.0",
23
+ "@purpurds/paragraph": "3.0.0",
24
+ "@purpurds/label": "3.0.0",
25
+ "@purpurds/field-error-text": "3.0.0"
26
+ },
27
+ "devDependencies": {
28
+ "@rushstack/eslint-patch": "~1.7.0",
29
+ "@storybook/blocks": "~7.6.0",
30
+ "@storybook/react": "~7.6.0",
31
+ "@telia/base-rig": "~8.2.0",
32
+ "@telia/react-rig": "~3.2.0",
33
+ "@testing-library/dom": "~9.3.3",
34
+ "@testing-library/jest-dom": "~6.3.0",
35
+ "@testing-library/react": "~14.1.2",
36
+ "@types/node": "18",
37
+ "@types/react-dom": "~18.2.17",
38
+ "@types/react": "~18.2.42",
39
+ "eslint-plugin-testing-library": "~6.2.0",
40
+ "eslint": "~8.56.0",
41
+ "jsdom": "~22.1.0",
42
+ "lint-staged": "~10.5.3",
43
+ "prettier": "~2.8.8",
44
+ "react-dom": "~18.2.0",
45
+ "react": "~18.2.0",
46
+ "typescript": "~5.2.2",
47
+ "vite": "~5.0.6",
48
+ "vitest": "~1.2.0",
49
+ "@purpurds/component-rig": "1.0.0"
50
+ },
51
+ "scripts": {
52
+ "build:dev": "vite",
53
+ "build:watch": "vite build --watch",
54
+ "build": "vite build",
55
+ "ci:build": "rushx build",
56
+ "coverage": "vitest run --coverage",
57
+ "lint:fix": "eslint . --fix",
58
+ "lint": "lint-staged --no-stash 2>&1",
59
+ "sbdev": "rush sbdev",
60
+ "test:unit": "vitest run --passWithNoTests",
61
+ "test:watch": "vitest --watch",
62
+ "test": "rushx test:unit",
63
+ "typecheck": "tsc -p ./tsconfig.json"
64
+ }
65
+ }
package/readme.mdx ADDED
@@ -0,0 +1,168 @@
1
+ import { Meta, ArgTypes, Primary, Subtitle } from "@storybook/blocks";
2
+
3
+ import * as CheckboxStories from "./src/checkbox.stories";
4
+ import packageInfo from "./package.json";
5
+
6
+ <Meta name="Docs" title="Components/Checkbox" of={CheckboxStories} />
7
+
8
+ # Checkbox
9
+
10
+ <Subtitle>Version {packageInfo.version}</Subtitle>
11
+
12
+ ### Showcase
13
+
14
+ <Primary />
15
+
16
+ ### Properties
17
+
18
+ <ArgTypes />
19
+
20
+ ### Installation
21
+
22
+ #### Via NPM
23
+
24
+ Add the dependency to your consumer app like `"@purpurds/checkbox": "x.y.z"`
25
+
26
+ #### From outside the monorepo (build-time)
27
+
28
+ To install this package, you need to setup access to the artifactory. [Click here to go to the guide on how to do that](https://github.com/telia-company/jfrog-documentation/blob/main/doc/JFrog/JFrog_Onboarding.md#getting-access-to-artifactory-and-other-jfrog-applications).
29
+
30
+ ---
31
+
32
+ In MyApp.tsx
33
+
34
+ ```tsx
35
+ import "@purpurds/tokens/index.css";
36
+ ```
37
+
38
+ and
39
+
40
+ ```tsx
41
+ import "@purpurds/checkbox/styles";
42
+ ```
43
+
44
+ ### Examples
45
+
46
+ In MyComponent.tsx
47
+
48
+ #### Controlled.
49
+
50
+ ---
51
+
52
+ For when you have to controll and use the state of the checkbox.
53
+
54
+ ```tsx
55
+ import { Checkbox } from "@purpurds/checkbox";
56
+
57
+ export const MyComponent = () => {
58
+ const [checked, setChecked] = useState(false);
59
+ return (
60
+ <div>
61
+ <Checkbox
62
+ id="my-checkbox"
63
+ checked={checked}
64
+ onChange={setChecked}
65
+ label="My checkbox"
66
+ labelPosition="right"
67
+ />
68
+ </div>
69
+ );
70
+ };
71
+ ```
72
+
73
+ #### Indeterminate.
74
+
75
+ ---
76
+
77
+ When using the Indeterminate state, the toggle must be controlled.
78
+
79
+ ```tsx
80
+ import { Checkbox } from "@purpurds/checkbox";
81
+
82
+ export const MyComponent = () => {
83
+ const [checked, setChecked] = useState(false);
84
+
85
+ return (
86
+ <div>
87
+ <Checkbox
88
+ id="my-checkbox"
89
+ checked={checked}
90
+ onChange={setChecked}
91
+ label="My checkbox"
92
+ labelPosition="right"
93
+ />
94
+ <Button
95
+ variant="primary"
96
+ onClick={() =>
97
+ setChecked((prevChecked) => (prevChecked === "indeterminate" ? false : "indeterminate"))
98
+ }
99
+ >
100
+ Toggle indeterminate
101
+ </Button>
102
+ </div>
103
+ );
104
+ };
105
+ ```
106
+
107
+ #### Uncontrolled
108
+
109
+ ---
110
+
111
+ For when you don't have to controll state of the checkbox, e.g. when in a form.
112
+
113
+ ```tsx
114
+ import { Checkbox } from "@purpurds/checkbox";
115
+
116
+ export const MyComponent = () => {
117
+ /**
118
+ * The checkbox will render checked, and handle it's state itself.
119
+ *
120
+ * Since it is rendered in a form, it will render a checkbox input under the hood
121
+ * that reflects its value and state.
122
+ */
123
+ return (
124
+ <form>
125
+ <Checkbox id="my-checkbox" defaultChecked label="My uncontrolled checkbox" />
126
+ </form>
127
+ );
128
+ };
129
+ ```
130
+
131
+ #### With custom label (not recommended).
132
+
133
+ ---
134
+
135
+ Use the `aria-labelledby` property and pass the id of the label.
136
+
137
+ ```tsx
138
+ import { Checkbox } from "@purpurds/checkbox";
139
+
140
+ export const MyComponent = () => {
141
+ return (
142
+ <div>
143
+ <label id="my-custom-label" htmlFor="my-checkbox">
144
+ Custom label
145
+ </label>
146
+ <Checkbox aria-labeledby="my-custom-label" id="my-checkbox" {...otherProps} />
147
+ </div>
148
+ );
149
+ };
150
+ ```
151
+
152
+ #### Without label (not recommended).
153
+
154
+ ---
155
+
156
+ If there should be no label at all, use the `aria-label` to label the checkbox for screen readers.
157
+
158
+ ```tsx
159
+ import { Checkbox } from "@purpurds/checkbox";
160
+
161
+ export const MyComponent = () => {
162
+ return (
163
+ <div>
164
+ <Checkbox aria-label="checkbox some awesome stuff!" id="my-checkbox" {...otherProps} />
165
+ </div>
166
+ );
167
+ };
168
+ ```
@@ -0,0 +1,125 @@
1
+ $target-area-size: calc(var(--purpur-spacing-400) + var(--purpur-spacing-150));
2
+ $checkbox-size: var(--purpur-spacing-300);
3
+ $checkbox-target-area-padding: calc(calc($target-area-size - $checkbox-size) / 2);
4
+
5
+ @mixin border($color, $width) {
6
+ box-shadow: $color 0px 0px 0px $width inset;
7
+ }
8
+
9
+ .purpur-checkbox {
10
+ $root: &;
11
+
12
+ all: unset;
13
+ padding: $checkbox-target-area-padding;
14
+ margin: calc(-1 * $checkbox-target-area-padding);
15
+ cursor: pointer;
16
+
17
+ &:active > #{$root}__box {
18
+ @include border(
19
+ var(--purpur-color-border-interactive-subtle-hover),
20
+ var(--purpur-border-width-xs)
21
+ );
22
+ }
23
+
24
+ &:disabled {
25
+ cursor: default;
26
+
27
+ & > #{$root}__box {
28
+ @include border(var(--purpur-color-border-weak), var(--purpur-border-width-xs));
29
+ }
30
+ }
31
+
32
+ &--error > #{$root}__box {
33
+ @include border(var(--purpur-color-border-status-error), var(--purpur-border-width-xs));
34
+ }
35
+
36
+ &--error:not(:disabled) ~ label * {
37
+ color: var(--purpur-color-text-status-error-strong);
38
+ }
39
+
40
+ &:not(:disabled):not(:active):hover > #{$root}__box {
41
+ @include border(
42
+ var(--purpur-color-border-interactive-subtle-hover),
43
+ var(--purpur-border-width-sm)
44
+ );
45
+ }
46
+
47
+ &:focus-visible {
48
+ &::after {
49
+ content: "";
50
+ position: absolute;
51
+ inset: 0;
52
+ border-radius: var(--purpur-border-radius-xs);
53
+ outline: var(--purpur-border-width-sm) solid var(--purpur-color-border-interactive-focus);
54
+ outline-offset: var(--purpur-spacing-25);
55
+ pointer-events: none;
56
+ }
57
+ }
58
+
59
+ &[data-state="checked"],
60
+ &[data-state="indeterminate"] {
61
+ & > #{$root}__box {
62
+ box-shadow: none;
63
+ background-color: var(--purpur-color-background-interactive-primary);
64
+ }
65
+
66
+ &:active > #{$root}__box {
67
+ background-color: var(--purpur-color-background-interactive-primary-active);
68
+ }
69
+
70
+ &:disabled > #{$root}__box {
71
+ @include border(var(--purpur-color-border-weak), var(--purpur-border-width-xs));
72
+ background-color: var(--purpur-color-background-interactive-disabled);
73
+ }
74
+
75
+ &:not(:disabled):not(:active):hover > #{$root}__box {
76
+ box-shadow: none;
77
+ background-color: var(--purpur-color-background-interactive-primary-hover);
78
+ }
79
+ }
80
+
81
+ &__box {
82
+ display: block;
83
+ height: $checkbox-size;
84
+ width: $checkbox-size;
85
+ background-color: var(--purpur-color-background-primary);
86
+ border-radius: var(--purpur-border-radius-xs);
87
+ @include border(var(--purpur-color-border-interactive-subtle), var(--purpur-border-width-xs));
88
+ transition:
89
+ box-shadow var(--purpur-motion-duration-150) ease,
90
+ background-color var(--purpur-motion-duration-150) ease;
91
+ box-sizing: border-box;
92
+ }
93
+
94
+ &__indicator {
95
+ width: 100%;
96
+ height: 100%;
97
+ display: flex;
98
+ align-items: center;
99
+ justify-content: center;
100
+ color: var(--purpur-color-text-interactive-on-primary);
101
+
102
+ &[data-disabled] {
103
+ color: var(--purpur-color-text-weak);
104
+ }
105
+ }
106
+
107
+ &__label {
108
+ padding-left: var(--purpur-spacing-150);
109
+ }
110
+
111
+ &__wrapper {
112
+ display: flex;
113
+ flex-direction: column;
114
+ justify-content: center;
115
+ width: fit-content;
116
+ }
117
+
118
+ &__container {
119
+ position: relative;
120
+ display: flex;
121
+ align-items: flex-start;
122
+ width: fit-content;
123
+ padding: $checkbox-target-area-padding 0;
124
+ }
125
+ }
@@ -0,0 +1,93 @@
1
+ import { useArgs } from "@storybook/client-api";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import React from "react";
4
+ import { Checkbox, CheckedState } from "./checkbox";
5
+
6
+ import "@purpurds/icon/styles";
7
+ import "@purpurds/field-error-text/styles";
8
+ import "@purpurds/label/styles";
9
+ import "@purpurds/paragraph/styles";
10
+
11
+ const meta: Meta<typeof Checkbox> = {
12
+ title: "Inputs/Checkbox",
13
+ component: Checkbox,
14
+ };
15
+
16
+ export default meta;
17
+ type Story = StoryObj<typeof Checkbox>;
18
+
19
+ export const Controlled: Story = {
20
+ argTypes: {
21
+ checked: {
22
+ options: [true, false, "indeterminate"],
23
+ },
24
+ },
25
+ args: {
26
+ id: "checkbox-showcase",
27
+ label: "Controlled checkbox",
28
+ },
29
+ parameters: {
30
+ design: [
31
+ {
32
+ name: "Checkbox",
33
+ type: "figma",
34
+ url: "https://www.figma.com/file/XEaIIFskrrxIBHMZDkIuIg/Purpur-DS---Component-library-%26-guidelines?type=design&node-id=1274-614&mode=dev",
35
+ },
36
+ ],
37
+ },
38
+ render: ({ ...args }) => {
39
+ const [{ checked }, updateArgs] = useArgs(); // eslint-disable-line react-hooks/rules-of-hooks
40
+ const setChecked = (value: CheckedState) => {
41
+ args.onChange?.(value);
42
+ updateArgs({ checked: value });
43
+ };
44
+
45
+ return <Checkbox {...args} onChange={setChecked} checked={checked} />;
46
+ },
47
+ };
48
+
49
+ export const Uncontrolled: Story = {
50
+ argTypes: {
51
+ checked: { table: { disable: true } },
52
+ onChange: { table: { disable: true } },
53
+ },
54
+ args: {
55
+ id: "checkbox-showcase",
56
+ label: "Uncontrolled checkbox",
57
+ },
58
+ parameters: {
59
+ design: [
60
+ {
61
+ name: "Checkbox",
62
+ type: "figma",
63
+ url: "https://www.figma.com/file/XEaIIFskrrxIBHMZDkIuIg/Purpur-DS---Component-library-%26-guidelines?type=design&node-id=1274-614&mode=dev",
64
+ },
65
+ ],
66
+ },
67
+ decorators: [
68
+ (Story) => {
69
+ const codeStyle = {
70
+ background: "var(--purpur-color-transparent-black-50)",
71
+ padding: "var(--purpur-spacing-10)",
72
+ borderRadius: "var(--purpur-border-radius-xs)",
73
+ border: "var(--purpur-border-width-xs) solid var(--purpur-color-transparent-black-100)",
74
+ };
75
+
76
+ return (
77
+ <form>
78
+ <Story />
79
+ <hr />
80
+ <p>
81
+ In this case <code style={codeStyle}>checked</code> and&nbsp;
82
+ <code style={codeStyle}>onChange</code> are not passed to the component.
83
+ </p>
84
+ <p>
85
+ Also, it is wrapped in a form which makes it render a checkbox input under the hood that
86
+ reflects its value and state.
87
+ </p>
88
+ </form>
89
+ );
90
+ },
91
+ ],
92
+ render: ({ onChange: _onChange, checked: _checked, ...args }) => <Checkbox {...args} />,
93
+ };
@@ -0,0 +1,78 @@
1
+ import React from "react";
2
+ import * as matchers from "@testing-library/jest-dom/matchers";
3
+ import { cleanup, fireEvent, render, screen } from "@testing-library/react";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+
6
+ import { Checkbox } from "./checkbox";
7
+
8
+ expect.extend(matchers);
9
+
10
+ describe("Checkbox", () => {
11
+ afterEach(cleanup);
12
+
13
+ it("should render uncontrolled default checked", () => {
14
+ render(<Checkbox id="test" defaultChecked />);
15
+ expect(screen.getByRole("checkbox")).toHaveAttribute("data-state", "checked");
16
+ fireEvent.click(screen.getByRole("checkbox"));
17
+ expect(screen.getByRole("checkbox")).toHaveAttribute("data-state", "unchecked");
18
+ });
19
+
20
+ it("should render uncontrolled default unchecked checked", () => {
21
+ render(<Checkbox id="test" defaultChecked={false} />);
22
+ expect(screen.getByRole("checkbox")).toHaveAttribute("data-state", "unchecked");
23
+ fireEvent.click(screen.getByRole("checkbox"));
24
+ expect(screen.getByRole("checkbox")).toHaveAttribute("data-state", "checked");
25
+ });
26
+
27
+ it("should render controlled checked", () => {
28
+ render(<Checkbox id="test" checked />);
29
+ expect(screen.getByRole("checkbox")).toHaveAttribute("data-state", "checked");
30
+ fireEvent.click(screen.getByRole("checkbox"));
31
+ expect(screen.getByRole("checkbox")).toHaveAttribute("data-state", "checked");
32
+ });
33
+
34
+ it("should render controlled unchecked", () => {
35
+ render(<Checkbox id="test" checked={false} />);
36
+ expect(screen.getByRole("checkbox")).toHaveAttribute("data-state", "unchecked");
37
+ fireEvent.click(screen.getByRole("checkbox"));
38
+ expect(screen.getByRole("checkbox")).toHaveAttribute("data-state", "unchecked");
39
+ });
40
+
41
+ it("should render disabled", () => {
42
+ render(<Checkbox data-testid="test" id="test" disabled />);
43
+ expect(screen.getByRole("checkbox")).toHaveAttribute("disabled");
44
+ });
45
+
46
+ it("should render label", () => {
47
+ render(<Checkbox data-testid="test" id="test" label="Test label" />);
48
+ expect(screen.getByTestId("test-label")).toHaveTextContent("Test label");
49
+ });
50
+
51
+ it("should render label required", () => {
52
+ render(<Checkbox data-testid="test" id="test" label="Test label" required />);
53
+ expect(screen.getByTestId("test-label")).toHaveTextContent("* Test label");
54
+ });
55
+
56
+ it("should not render label given no label", () => {
57
+ render(<Checkbox data-testid="test" id="test" />);
58
+ expect(screen.queryByTestId("test-label")).not.toBeInTheDocument();
59
+ });
60
+
61
+ it("should render errorText when given", () => {
62
+ render(<Checkbox data-testid="test" id="test" errorText="Error text" />);
63
+ expect(screen.getByTestId("test-error-text")).toHaveTextContent("Error text");
64
+ });
65
+
66
+ it("should not render errorText when not given", () => {
67
+ render(<Checkbox data-testid="test" id="test" />);
68
+ expect(screen.queryByTestId("test-error-text")).not.toBeInTheDocument();
69
+ });
70
+
71
+ it("should emit onChange", () => {
72
+ const onChangeMock = vi.fn();
73
+ render(<Checkbox data-testid="test" id="test" label="Test label" onChange={onChangeMock} />);
74
+ fireEvent.click(screen.getByTestId("test-label"));
75
+ fireEvent.click(screen.getByRole("checkbox"));
76
+ expect(onChangeMock).toHaveBeenCalledTimes(2);
77
+ });
78
+ });
@@ -0,0 +1,135 @@
1
+ import React, { ForwardedRef, forwardRef } from "react";
2
+ import { FieldErrorText } from "@purpurds/field-error-text";
3
+ import { checkmarkBold, Icon, minusBold } from "@purpurds/icon";
4
+ import { Label } from "@purpurds/label";
5
+ import { Paragraph } from "@purpurds/paragraph";
6
+ import * as RadixCheckbox from "@radix-ui/react-checkbox";
7
+ import c from "classnames";
8
+
9
+ import styles from "./checkbox.module.scss";
10
+
11
+ export type CheckedState = boolean | "indeterminate";
12
+ export type CheckboxProps = {
13
+ /**
14
+ * To use when no label is given (not recommended).
15
+ * */
16
+ ["aria-label"]?: string;
17
+ /**
18
+ * To use with custom label (not recommended).
19
+ * */
20
+ ["aria-labelledby"]?: string;
21
+ ["data-testid"]?: string;
22
+ checked?: CheckedState;
23
+ /**
24
+ * The checked state of the checkbox when it is initially rendered. Use when you do not need to control its checked state.
25
+ */
26
+ className?: string;
27
+ /**
28
+ * The checked state of the checkbox when it is initially rendered. Use when you do not need to control its checked state.
29
+ */
30
+ defaultChecked?: boolean;
31
+ /**
32
+ * When `true`, prevents the user from interacting with the checkbox.
33
+ */
34
+ disabled?: boolean;
35
+ /**
36
+ * Use to render error message below the checkbox. The checkbox renders with error appearance.
37
+ * */
38
+ errorText?: string;
39
+ /**
40
+ * ID of the checkbox.
41
+ * */
42
+ id: string;
43
+ /**
44
+ * The label of the checkbox.
45
+ * */
46
+ label?: string;
47
+ /**
48
+ * The name of the checkbox. Submitted with its owning form as part of a name/value pair.
49
+ * */
50
+ name?: string;
51
+ /**
52
+ * Event handler called when the checked state of the checkbox changes.
53
+ * */
54
+ onChange?: (value: CheckedState) => void;
55
+ /**
56
+ * When `true`, indicates that the user must check the checkbox before the owning form can be submitted.
57
+ * */
58
+ required?: boolean;
59
+ /**
60
+ * The value given as data when submitted with a name.
61
+ * */
62
+ value?: string;
63
+ };
64
+
65
+ const rootClassName = "purpur-checkbox";
66
+
67
+ const CheckboxComponent = (
68
+ {
69
+ ["data-testid"]: dataTestId,
70
+ checked,
71
+ className,
72
+ errorText,
73
+ label,
74
+ onChange,
75
+ ...props
76
+ }: CheckboxProps,
77
+ ref: ForwardedRef<HTMLButtonElement>
78
+ ) => {
79
+ const isError = errorText && !checked;
80
+
81
+ return (
82
+ <div className={c([className, styles[`${rootClassName}__wrapper`]])}>
83
+ <div className={c([className, styles[`${rootClassName}__container`]])}>
84
+ <RadixCheckbox.Root
85
+ {...props}
86
+ checked={checked}
87
+ className={c(styles[rootClassName], {
88
+ [styles[`${rootClassName}--error`]]: isError,
89
+ })}
90
+ data-testid={dataTestId}
91
+ onCheckedChange={onChange}
92
+ ref={ref}
93
+ >
94
+ <span className={styles[`${rootClassName}__box`]}>
95
+ <RadixCheckbox.Indicator className={styles[`${rootClassName}__indicator`]}>
96
+ {checked === "indeterminate" ? (
97
+ <Icon
98
+ data-testid={dataTestId && `${dataTestId}-checked-icon`}
99
+ size="xs"
100
+ svg={minusBold}
101
+ />
102
+ ) : (
103
+ <Icon
104
+ data-testid={dataTestId && `${dataTestId}-indeterminate-icon`}
105
+ size="xs"
106
+ svg={checkmarkBold}
107
+ />
108
+ )}
109
+ </RadixCheckbox.Indicator>
110
+ </span>
111
+ </RadixCheckbox.Root>
112
+ {label && (
113
+ <Label
114
+ htmlFor={props.id}
115
+ data-testid={dataTestId && `${dataTestId}-label`}
116
+ disabled={props.disabled}
117
+ className={styles[`${rootClassName}__label`]}
118
+ >
119
+ <Paragraph variant="paragraph-100" disabled={props.disabled}>
120
+ {`${props.required ? "* " : ""}${label}`}
121
+ </Paragraph>
122
+ </Label>
123
+ )}
124
+ </div>
125
+ {isError && (
126
+ <FieldErrorText data-testid={dataTestId && `${dataTestId}-error-text`}>
127
+ {errorText}
128
+ </FieldErrorText>
129
+ )}
130
+ </div>
131
+ );
132
+ };
133
+
134
+ export const Checkbox = forwardRef(CheckboxComponent);
135
+ Checkbox.displayName = "Checkbox";
@@ -0,0 +1,4 @@
1
+ declare module "*.scss" {
2
+ const styles: { [className: string]: string };
3
+ export default styles;
4
+ }