@usefui/components 1.5.1

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 (104) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/LICENSE +21 -0
  3. package/README.md +0 -0
  4. package/babel.config.js +12 -0
  5. package/dist/index.d.mts +1299 -0
  6. package/dist/index.d.ts +1299 -0
  7. package/dist/index.js +3701 -0
  8. package/dist/index.mjs +3586 -0
  9. package/package.json +44 -0
  10. package/src/__tests__/Accordion.test.tsx +106 -0
  11. package/src/__tests__/Avatar.test.tsx +89 -0
  12. package/src/__tests__/Badge.test.tsx +58 -0
  13. package/src/__tests__/Button.test.tsx +88 -0
  14. package/src/__tests__/Checkbox.test.tsx +106 -0
  15. package/src/__tests__/Collapsible.test.tsx +79 -0
  16. package/src/__tests__/Dialog.test.tsx +109 -0
  17. package/src/__tests__/Dropdown.test.tsx +159 -0
  18. package/src/__tests__/Field.test.tsx +100 -0
  19. package/src/__tests__/OTPField.test.tsx +199 -0
  20. package/src/__tests__/Overlay.test.tsx +70 -0
  21. package/src/__tests__/Page.test.tsx +98 -0
  22. package/src/__tests__/Portal.test.tsx +28 -0
  23. package/src/__tests__/Sheet.test.tsx +125 -0
  24. package/src/__tests__/Switch.test.tsx +90 -0
  25. package/src/__tests__/Tabs.test.tsx +129 -0
  26. package/src/__tests__/Toggle.test.tsx +67 -0
  27. package/src/__tests__/Toolbar.test.tsx +147 -0
  28. package/src/__tests__/Tooltip.test.tsx +88 -0
  29. package/src/accordion/Accordion.stories.tsx +89 -0
  30. package/src/accordion/hooks/index.tsx +39 -0
  31. package/src/accordion/index.tsx +170 -0
  32. package/src/avatar/Avatar.stories.tsx +62 -0
  33. package/src/avatar/index.tsx +90 -0
  34. package/src/avatar/styles/index.ts +79 -0
  35. package/src/badge/Badge.stories.tsx +60 -0
  36. package/src/badge/index.tsx +58 -0
  37. package/src/badge/styles/index.ts +109 -0
  38. package/src/button/Button.stories.tsx +47 -0
  39. package/src/button/index.tsx +79 -0
  40. package/src/button/styles/index.ts +180 -0
  41. package/src/checkbox/Checkbox.stories.tsx +100 -0
  42. package/src/checkbox/hooks/index.tsx +40 -0
  43. package/src/checkbox/index.tsx +147 -0
  44. package/src/checkbox/styles/index.ts +139 -0
  45. package/src/collapsible/Collapsible.stories.tsx +95 -0
  46. package/src/collapsible/hooks/index.tsx +50 -0
  47. package/src/collapsible/index.tsx +137 -0
  48. package/src/dialog/Dialog.stories.tsx +73 -0
  49. package/src/dialog/hooks/index.tsx +35 -0
  50. package/src/dialog/index.tsx +221 -0
  51. package/src/dialog/styles/index.ts +72 -0
  52. package/src/divider/index.ts +10 -0
  53. package/src/dropdown/Dropdown.stories.tsx +100 -0
  54. package/src/dropdown/hooks/index.tsx +64 -0
  55. package/src/dropdown/index.tsx +316 -0
  56. package/src/dropdown/styles/index.ts +90 -0
  57. package/src/field/Field.stories.tsx +146 -0
  58. package/src/field/hooks/index.tsx +28 -0
  59. package/src/field/index.tsx +183 -0
  60. package/src/field/styles/index.ts +166 -0
  61. package/src/index.ts +33 -0
  62. package/src/otp-field/OTPField.stories.tsx +50 -0
  63. package/src/otp-field/hooks/index.tsx +13 -0
  64. package/src/otp-field/index.tsx +234 -0
  65. package/src/otp-field/styles/index.ts +33 -0
  66. package/src/otp-field/types/index.ts +23 -0
  67. package/src/overlay/Overlay.stories.tsx +59 -0
  68. package/src/overlay/index.tsx +58 -0
  69. package/src/overlay/styles/index.ts +26 -0
  70. package/src/page/Page.stories.tsx +85 -0
  71. package/src/page/index.tsx +265 -0
  72. package/src/page/styles/index.ts +59 -0
  73. package/src/portal/Portal.stories.tsx +27 -0
  74. package/src/portal/index.tsx +36 -0
  75. package/src/scrollarea/Scrollarea.stories.tsx +99 -0
  76. package/src/scrollarea/index.tsx +27 -0
  77. package/src/scrollarea/styles/index.ts +71 -0
  78. package/src/sheet/Sheet.stories.tsx +86 -0
  79. package/src/sheet/hooks/index.tsx +47 -0
  80. package/src/sheet/index.tsx +190 -0
  81. package/src/sheet/styles/index.ts +69 -0
  82. package/src/switch/Switch.stories.tsx +96 -0
  83. package/src/switch/hooks/index.tsx +33 -0
  84. package/src/switch/index.tsx +122 -0
  85. package/src/switch/styles/index.ts +118 -0
  86. package/src/table/index.tsx +138 -0
  87. package/src/table/styles/index.ts +48 -0
  88. package/src/tabs/Tabs.stories.tsx +87 -0
  89. package/src/tabs/hooks/index.tsx +35 -0
  90. package/src/tabs/index.tsx +161 -0
  91. package/src/tabs/styles/index.ts +9 -0
  92. package/src/toggle/Toggle.stories.tsx +118 -0
  93. package/src/toggle/index.tsx +55 -0
  94. package/src/toggle/styles/index.ts +0 -0
  95. package/src/toolbar/Toolbar.stories.tsx +89 -0
  96. package/src/toolbar/hooks/index.tsx +35 -0
  97. package/src/toolbar/index.tsx +243 -0
  98. package/src/toolbar/styles/index.ts +129 -0
  99. package/src/tooltip/Tooltip.stories.tsx +60 -0
  100. package/src/tooltip/index.tsx +177 -0
  101. package/src/tooltip/styles/index.ts +38 -0
  102. package/src/utils/index.ts +2 -0
  103. package/tsconfig.json +18 -0
  104. package/vitest.config.ts +16 -0
@@ -0,0 +1,109 @@
1
+ import styled, { css } from "styled-components";
2
+
3
+ const BadgeBaseStyles = css`
4
+ display: inline-flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ gap: var(--measurement-medium-10);
8
+ min-width: var(--measurement-medium-60);
9
+ min-height: var(--measurement-medium-60);
10
+ width: fit-content;
11
+
12
+ border: var(--measurement-small-10) solid transparent;
13
+
14
+ font-size: var(--fontsize-small-60);
15
+ padding: var(--measurement-medium-10) var(--measurement-medium-30);
16
+ font-weight: 500;
17
+ transition: all ease-in-out 0.2s;
18
+ `;
19
+
20
+ const BadgeVariantStyles = css`
21
+ border: var(--measurement-small-10) solid transparent;
22
+
23
+ &[data-variant="primary"] {
24
+ background-color: var(--font-color);
25
+ color: var(--body-color);
26
+
27
+ &:hover,
28
+ &:focus {
29
+ border-color: var(--font-color-alpha-10);
30
+ }
31
+ }
32
+ &[data-variant="secondary"] {
33
+ background-color: var(--font-color-alpha-10);
34
+ color: var(--font-color-alpha-60);
35
+
36
+ &:hover,
37
+ &:focus {
38
+ color: var(--font-color);
39
+ }
40
+ }
41
+ &[data-variant="border"] {
42
+ background-color: transparent;
43
+ border-color: var(--font-color-alpha-10);
44
+ color: var(--font-color-alpha-60);
45
+
46
+ &:hover,
47
+ &:focus {
48
+ color: var(--font-color);
49
+ }
50
+ }
51
+ &[data-variant="error"] {
52
+ background-color: var(--alpha-red-10);
53
+ color: var(--alpha-red-80);
54
+
55
+ &:hover,
56
+ &:focus {
57
+ background-color: var(--alpha-red-10);
58
+ color: var(--color-red);
59
+ }
60
+ }
61
+ &[data-variant="warning"] {
62
+ background-color: var(--alpha-orange-10);
63
+ color: var(--alpha-orange-80);
64
+
65
+ &:hover,
66
+ &:focus {
67
+ background-color: var(--alpha-orange-10);
68
+ color: var(--color-orange);
69
+ }
70
+ }
71
+ &[data-variant="success"] {
72
+ background-color: var(--alpha-green-10);
73
+ color: var(--alpha-green-80);
74
+
75
+ &:hover,
76
+ &:focus {
77
+ background-color: var(--alpha-green-10);
78
+ color: var(--color-green);
79
+ }
80
+ }
81
+ &[data-variant="meta"] {
82
+ background-color: var(--alpha-blue-10);
83
+ color: var(--alpha-blue-80);
84
+
85
+ &:hover,
86
+ &:focus {
87
+ background-color: var(--alpha-blue-10);
88
+ color: var(--color-blue);
89
+ }
90
+ }
91
+ `;
92
+ const BadgeShapeStyles = css`
93
+ &[data-shape="square"] {
94
+ border-radius: 0;
95
+ }
96
+ &[data-shape="smooth"] {
97
+ border-radius: var(--measurement-medium-20);
98
+ }
99
+ &[data-shape="round"] {
100
+ border-radius: var(--measurement-large-90);
101
+ }
102
+ `;
103
+ export const BadgeWrapper = styled.div<any>`
104
+ &[data-raw="false"] {
105
+ ${BadgeBaseStyles}
106
+ ${BadgeVariantStyles}
107
+ ${BadgeShapeStyles}
108
+ }
109
+ `;
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { Button } from "..";
4
+
5
+ const meta = {
6
+ title: "Components/Button",
7
+ component: Button,
8
+ tags: ["autodocs"],
9
+ decorators: [
10
+ (Story) => (
11
+ <div className="m-medium-30">
12
+ <Story />
13
+ </div>
14
+ ),
15
+ ],
16
+ } satisfies Meta<typeof Button>;
17
+ export default meta;
18
+
19
+ type Story = StoryObj<typeof meta>;
20
+ export const Default: Story = {
21
+ render: ({ ...args }) => <Button {...args} />,
22
+ };
23
+ export const Sizes: Story = {
24
+ render: ({ ...args }) => {
25
+ return (
26
+ <div className="flex g-medium-30">
27
+ <Button sizing="large">Large</Button>
28
+ <Button sizing="medium">Medium</Button>
29
+ <Button sizing="small">Small</Button>
30
+ </div>
31
+ );
32
+ },
33
+ };
34
+ export const Variants: Story = {
35
+ render: ({ ...args }) => {
36
+ return (
37
+ <div className="flex g-medium-30">
38
+ <Button variant="primary">Primary</Button>
39
+ <Button variant="secondary">Secondary</Button>
40
+ <Button variant="tertiary">Tertiary</Button>
41
+ <Button variant="mono">Mono</Button>
42
+ <Button variant="border">Border</Button>
43
+ <Button variant="ghost">Ghost</Button>
44
+ </div>
45
+ );
46
+ },
47
+ };
@@ -0,0 +1,79 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { ButtonWrapper } from "./styles";
5
+ import {
6
+ IComponentStyling,
7
+ ComponentSizeEnum,
8
+ IComponentSize,
9
+ ComponentVariantEnum,
10
+ IComponentVariant,
11
+ } from "../../../../types";
12
+
13
+ export interface IButtonProperties
14
+ extends IComponentStyling,
15
+ IComponentSize,
16
+ IComponentVariant,
17
+ React.ComponentPropsWithRef<"button"> {
18
+ rawicon?: boolean;
19
+ }
20
+
21
+ /**
22
+ * Buttons are used to initialize an action.
23
+ *
24
+ * **Best practices:**
25
+ *
26
+ * - Define the hierarchy of buttons with different variants.
27
+ * - Button label must be short and understandable.
28
+ *
29
+ * @param {IButtonProperties} props - The props for the Button component.
30
+ * @param {boolean} props.raw - Define whether the component is styled or not.
31
+ * @param {boolean} props.rawicon - Define whether the component is styles its svg children.
32
+ * @param {ComponentSizeEnum} props.sizing - The size of the component. Defaults to `medium`.
33
+ * @param {string} props.variant - The style definition used by the component.
34
+ * @param {ReactNode} props.children - The content to be rendered inside the button.
35
+ * @returns {ReactElement} The Button component.
36
+ */
37
+ export const Button = React.forwardRef<HTMLButtonElement, IButtonProperties>(
38
+ (props, forwardedRef): React.ReactElement => {
39
+ const {
40
+ name,
41
+ variant = ComponentVariantEnum.Mono,
42
+ sizing = ComponentSizeEnum.Medium,
43
+ raw,
44
+ rawicon,
45
+ children,
46
+ ...restProps
47
+ } = props;
48
+
49
+ const defaultName = "button";
50
+ const ariaLabel = `${name ?? defaultName}-action`;
51
+ const disabledState = props.disabled ?? false;
52
+ const buttonType = props.type ?? "button";
53
+
54
+ const buttonDescription = `${ariaLabel}:${buttonType}`;
55
+ const buttonStateDescription = `disabled:${disabledState}`;
56
+ const ButtonFullDesc = `${buttonDescription}/${buttonStateDescription}`;
57
+
58
+ return (
59
+ <ButtonWrapper
60
+ ref={forwardedRef}
61
+ role="button"
62
+ type={buttonType}
63
+ name={name ?? defaultName}
64
+ aria-label={ariaLabel}
65
+ aria-description={ButtonFullDesc}
66
+ aria-disabled={disabledState}
67
+ data-variant={variant}
68
+ data-size={sizing}
69
+ data-raw={Boolean(raw)}
70
+ data-rawicon={Boolean(rawicon)}
71
+ tabIndex={0}
72
+ {...restProps}
73
+ >
74
+ {children}
75
+ </ButtonWrapper>
76
+ );
77
+ }
78
+ );
79
+ Button.displayName = "Button";
@@ -0,0 +1,180 @@
1
+ import styled, { css } from "styled-components";
2
+
3
+ const ButtonDefaultStyles = css`
4
+ cursor: pointer;
5
+ position: relative;
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: center;
9
+ gap: var(--measurement-medium-30);
10
+ font-size: var(--fontsize-medium-20);
11
+ height: fit-content;
12
+ font-weight: 500;
13
+ line-height: 1;
14
+ letter-spacing: calc(
15
+ var(--fontsize-small-10) - ((var(--fontsize-small-10) * 1.066))
16
+ );
17
+
18
+ border: var(--measurement-small-10) solid transparent;
19
+ backdrop-filter: blur(var(--measurement-small-10));
20
+
21
+ width: fit-content;
22
+ transition: all ease-in-out 0.2s;
23
+
24
+ span,
25
+ img {
26
+ opacity: 0.6;
27
+ }
28
+
29
+ &:hover,
30
+ &:focus,
31
+ &:active {
32
+ span,
33
+ img {
34
+ opacity: 1;
35
+ }
36
+ }
37
+
38
+ &:disabled {
39
+ cursor: not-allowed;
40
+ opacity: 0.6;
41
+ }
42
+ `;
43
+ const ButtonIconStyles = css`
44
+ svg {
45
+ width: var(--fontsize-medium-20);
46
+ height: var(--fontsize-medium-20);
47
+ fill: currentColor;
48
+ }
49
+
50
+ svg {
51
+ opacity: 0.6;
52
+ }
53
+
54
+ &:hover,
55
+ &:focus,
56
+ &:active {
57
+ svg {
58
+ opacity: 1;
59
+ }
60
+ }
61
+
62
+ &[data-variant="primary"] {
63
+ svg {
64
+ fill: var(--body-color);
65
+ }
66
+ }
67
+ `;
68
+ const ButtonVariantsStyles = css`
69
+ &[data-variant="primary"] {
70
+ color: var(--body-color) !important;
71
+ background-color: var(--font-color);
72
+
73
+ &:hover,
74
+ &:focus,
75
+ &:active {
76
+ color: var(--body-color);
77
+ }
78
+ }
79
+ &[data-variant="secondary"] {
80
+ color: var(--font-color-alpha-60);
81
+ background-color: var(--body-color);
82
+ border-color: var(--font-color-alpha-10);
83
+
84
+ &:hover,
85
+ &:focus,
86
+ &:active {
87
+ color: var(--font-color);
88
+ border-color: var(--font-color-alpha-30);
89
+ }
90
+ }
91
+ &[data-variant="tertiary"] {
92
+ color: var(--font-color-alpha-60);
93
+ border-color: var(--font-color-alpha-10);
94
+ background-color: transparent;
95
+
96
+ &:hover,
97
+ &:focus,
98
+ &:active {
99
+ color: var(--font-color);
100
+ background-color: var(--font-color-alpha-10);
101
+ border-color: transparent;
102
+ }
103
+ }
104
+ &[data-variant="border"] {
105
+ color: var(--font-color-alpha-60);
106
+ background-color: transparent;
107
+ border-color: var(--font-color-alpha-10);
108
+
109
+ &:hover,
110
+ &:focus,
111
+ &:active {
112
+ color: var(--font-color);
113
+ }
114
+ }
115
+ &[data-variant="mono"] {
116
+ color: var(--font-color-alpha-80);
117
+ background-color: var(--font-color-alpha-10);
118
+
119
+ &:hover,
120
+ &:focus,
121
+ &:active {
122
+ color: var(--font-color);
123
+ border-color: var(--font-color-alpha-10);
124
+ }
125
+ }
126
+ &[data-variant="ghost"] {
127
+ border: none;
128
+ padding: 0;
129
+ background-color: transparent;
130
+ min-width: fit-content;
131
+ min-height: var(--measurement-medium-60);
132
+ color: var(--font-color-alpha-60);
133
+
134
+ &:hover,
135
+ &:focus,
136
+ &:active {
137
+ color: var(--font-color);
138
+ }
139
+ }
140
+ `;
141
+ const ButtonSizeStyles = css`
142
+ &[data-size="small"] {
143
+ border-radius: var(--measurement-medium-20);
144
+ font-size: var(--fontsize-medium-10);
145
+
146
+ gap: var(--measurement-medium-10);
147
+ padding: var(--measurement-medium-10) var(--measurement-medium-30);
148
+ min-width: var(--measurement-medium-60);
149
+ min-height: var(--measurement-medium-60);
150
+
151
+ svg {
152
+ width: var(--fontsize-medium-10);
153
+ height: var(--fontsize-medium-10);
154
+ }
155
+ }
156
+ &[data-size="medium"] {
157
+ border-radius: var(--measurement-medium-30);
158
+ padding: var(--measurement-medium-10) var(--measurement-medium-60);
159
+ min-width: var(--measurement-medium-90);
160
+ min-height: var(--measurement-medium-80);
161
+ }
162
+ &[data-size="large"] {
163
+ border-radius: var(--measurement-medium-30);
164
+ padding: var(--measurement-medium-10) var(--measurement-medium-60);
165
+ min-width: var(--measurement-medium-90);
166
+ min-height: var(--measurement-medium-90);
167
+ }
168
+ `;
169
+
170
+ export const ButtonWrapper = styled.button`
171
+ &[data-raw="false"] {
172
+ ${ButtonDefaultStyles}
173
+ ${ButtonSizeStyles}
174
+ ${ButtonVariantsStyles}
175
+
176
+ &[data-rawIcon="false"] {
177
+ ${ButtonIconStyles}
178
+ }
179
+ }
180
+ `;
@@ -0,0 +1,100 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+
4
+ import { Checkbox } from "..";
5
+ import { ComponentVariantEnum, ComponentSizeEnum } from "../../../../types";
6
+
7
+ const meta = {
8
+ title: "Components/Checkbox",
9
+ component: Checkbox,
10
+ tags: ["autodocs"],
11
+ decorators: [
12
+ (Story) => (
13
+ <div className="m-medium-30">
14
+ <Story />
15
+ </div>
16
+ ),
17
+ ],
18
+ } satisfies Meta<typeof Checkbox>;
19
+ export default meta;
20
+
21
+ type Story = StoryObj<typeof meta>;
22
+ export const Default: Story = {
23
+ argTypes: {
24
+ variant: {
25
+ options: [
26
+ ComponentVariantEnum.Primary,
27
+ ComponentVariantEnum.Mono,
28
+ ComponentVariantEnum.Border,
29
+ ComponentVariantEnum.Ghost,
30
+ ],
31
+ control: { type: "radio" },
32
+ },
33
+ sizing: {
34
+ options: [
35
+ ComponentSizeEnum.Small,
36
+ ComponentSizeEnum.Medium,
37
+ ComponentSizeEnum.Large,
38
+ ],
39
+ control: { type: "radio" },
40
+ },
41
+ },
42
+ render: ({ ...args }) => (
43
+ <Checkbox.Root>
44
+ <Checkbox>
45
+ <Checkbox.Indicator />
46
+ </Checkbox>
47
+ </Checkbox.Root>
48
+ ),
49
+ };
50
+ export const DefaultChecked: Story = {
51
+ render: ({ ...args }) => (
52
+ <Checkbox.Root>
53
+ <Checkbox defaultChecked onChange={() => null}>
54
+ <Checkbox.Indicator />
55
+ </Checkbox>
56
+ </Checkbox.Root>
57
+ ),
58
+ };
59
+ export const Group: Story = {
60
+ render: ({ ...args }) => (
61
+ <div className="flex g-medium-30">
62
+ {[false, false, true].map((item: boolean, key: number) => (
63
+ <Checkbox.Root key={`${item}-${key}`}>
64
+ <Checkbox defaultChecked={item} onChange={() => null}>
65
+ <Checkbox.Indicator />
66
+ </Checkbox>
67
+ </Checkbox.Root>
68
+ ))}
69
+ </div>
70
+ ),
71
+ };
72
+ export const Sizes: Story = {
73
+ render: ({ ...args }) => (
74
+ <div className="flex g-medium-30">
75
+ {["large", "medium", "small"].map((item: string) => (
76
+ <Checkbox.Root key={item}>
77
+ <Checkbox sizing={item} onChange={() => null}>
78
+ <Checkbox.Indicator />
79
+ </Checkbox>
80
+ </Checkbox.Root>
81
+ ))}
82
+ </div>
83
+ ),
84
+ };
85
+ export const Variants: Story = {
86
+ render: ({ ...args }) => (
87
+ <div className="flex g-medium-30">
88
+ {["primary", "mono", "border", "ghost"].map((item: string) => (
89
+ <Checkbox.Root key={item}>
90
+ <Checkbox name={item} variant={item}>
91
+ <Checkbox.Indicator />
92
+ </Checkbox>
93
+ <label id={`${item}-label`} htmlFor={item}>
94
+ <small>{item}</small>
95
+ </label>
96
+ </Checkbox.Root>
97
+ ))}
98
+ </div>
99
+ ),
100
+ };
@@ -0,0 +1,40 @@
1
+ import React, { useState, createContext, useContext } from "react";
2
+ import {
3
+ IReactChildren,
4
+ IComponentAPI,
5
+ TComponentAPI,
6
+ } from "../../../../../types";
7
+
8
+ const defaultComponentAPI = {
9
+ id: "",
10
+ states: {},
11
+ methods: {},
12
+ };
13
+ const CheckboxContext = createContext<IComponentAPI>(defaultComponentAPI);
14
+ export const useCheckbox = () => useContext(CheckboxContext);
15
+
16
+ export const CheckboxProvider = ({ children }: IReactChildren): JSX.Element => {
17
+ const context = useCheckboxProvider();
18
+
19
+ return (
20
+ <CheckboxContext.Provider value={context}>
21
+ {children}
22
+ </CheckboxContext.Provider>
23
+ );
24
+ };
25
+
26
+ function useCheckboxProvider(): IComponentAPI {
27
+ const [checked, setChecked] = useState<boolean>(false);
28
+ const checkboxId = React.useId();
29
+
30
+ return {
31
+ id: checkboxId,
32
+ states: {
33
+ checked,
34
+ },
35
+ methods: {
36
+ toggleChecked: () => setChecked(!checked),
37
+ applyChecked: (state: TComponentAPI) => setChecked(Boolean(state)),
38
+ },
39
+ };
40
+ }
@@ -0,0 +1,147 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { CheckboxProvider, useCheckbox } from "./hooks";
5
+ import { CheckboxWrapper, NativeInput, Indicator } from "./styles";
6
+ import {
7
+ IReactChildren,
8
+ IComponentStyling,
9
+ ComponentSizeEnum,
10
+ IComponentSize,
11
+ ComponentVariantEnum,
12
+ IComponentVariant,
13
+ } from "../../../../types";
14
+
15
+ export interface ICheckboxComposition {
16
+ Root: typeof CheckboxRoot;
17
+ Indicator: typeof CheckboxIndicator;
18
+ }
19
+
20
+ export interface ICheckboxProperties
21
+ extends IComponentStyling,
22
+ IComponentSize,
23
+ IComponentVariant,
24
+ React.ComponentProps<"input"> {}
25
+
26
+ /**
27
+ * Checkbox are used to select one or more options from a set.
28
+ *
29
+ * **Best practices:**
30
+ *
31
+ * - Use a clear and descriptive label for each checkbox.
32
+ *
33
+ * @param {ICheckboxProperties} props - The props for the Checkbox component.
34
+ * @param {boolean} props.raw - Whether the checkbox is styled or not. Defaults to `false`.
35
+ * @param {ComponentSizeEnum} props.sizing - The size of the checkbox. Defaults to `medium`.
36
+ * @param {ComponentVariantEnum} props.variant - The styles used by the checkbox. Defaults to `tertiary`.
37
+ * @param {boolean} props.defaultChecked - The initial checked state of the checkbox..
38
+ * @param {ReactNode} props.children - The content to be rendered inside the checkbox.
39
+ * @returns {ReactElement} The Checkbox component.
40
+ */
41
+ const Checkbox = (props: ICheckboxProperties) => {
42
+ const { states, methods } = useCheckbox();
43
+ const { applyChecked, toggleChecked } = methods;
44
+ const {
45
+ raw,
46
+ sizing = ComponentSizeEnum.Medium,
47
+ variant = ComponentVariantEnum.Mono,
48
+ name,
49
+ disabled,
50
+ required,
51
+ defaultChecked,
52
+ value,
53
+ onClick,
54
+ children,
55
+ ...restProps
56
+ } = props;
57
+ const defaultValue = states.checked ? "checked" : "unchecked";
58
+
59
+ const handleClick = (event: React.MouseEvent<HTMLInputElement>) => {
60
+ if (toggleChecked) toggleChecked();
61
+ if (onClick) onClick(event);
62
+ };
63
+
64
+ React.useEffect(() => {
65
+ if (defaultChecked && applyChecked) applyChecked(defaultChecked);
66
+ // eslint-disable-next-line react-hooks/exhaustive-deps
67
+ }, []);
68
+
69
+ return (
70
+ <CheckboxWrapper
71
+ role="checkbox"
72
+ aria-hidden="true"
73
+ data-state={value ?? defaultValue}
74
+ data-disabled={disabled}
75
+ data-raw={Boolean(raw)}
76
+ data-size={sizing}
77
+ data-variant={variant}
78
+ aria-label={props["aria-label"] ?? `${name}-checkbox`}
79
+ >
80
+ <NativeInput
81
+ type="checkbox"
82
+ tabIndex={0}
83
+ name={name}
84
+ value={value ?? defaultValue}
85
+ defaultChecked={Boolean(states.checked)}
86
+ required={required}
87
+ disabled={disabled}
88
+ onInput={handleClick}
89
+ data-state={defaultValue}
90
+ data-raw={Boolean(raw)}
91
+ aria-disabled={Boolean(disabled)}
92
+ aria-required={Boolean(required)}
93
+ aria-checked={Boolean(states.checked)}
94
+ aria-label={props["aria-label"] ?? `${name}-native-checkbox`}
95
+ {...restProps}
96
+ />
97
+ {children}
98
+ </CheckboxWrapper>
99
+ );
100
+ };
101
+ Checkbox.displayName = "Checkbox";
102
+
103
+ const CheckboxRoot = (props: IReactChildren) => {
104
+ const { children, ...restProps } = props;
105
+ return <CheckboxProvider {...restProps}>{children}</CheckboxProvider>;
106
+ };
107
+ CheckboxRoot.displayName = "Checkbox.Root";
108
+
109
+ const CheckboxIndicator = (props: React.ComponentProps<"span">) => {
110
+ const { states } = useCheckbox();
111
+ const { children, ...restProps } = props;
112
+
113
+ return (
114
+ <>
115
+ {states.checked && (
116
+ <Indicator {...restProps}>
117
+ {children ?? (
118
+ <svg
119
+ tabIndex={-1}
120
+ aria-hidden="true"
121
+ aria-label="checked-icon"
122
+ focusable="false"
123
+ width="10"
124
+ height="10"
125
+ viewBox="0 0 10 10"
126
+ fill="none"
127
+ >
128
+ <title>Checked</title>
129
+ <path
130
+ d="M2 5.5L4.12132 7.62132L8.36396 3.37868"
131
+ strokeWidth="2"
132
+ strokeLinecap="round"
133
+ strokeLinejoin="round"
134
+ />
135
+ </svg>
136
+ )}
137
+ </Indicator>
138
+ )}
139
+ </>
140
+ );
141
+ };
142
+ CheckboxIndicator.displayName = "Checkbox.Indicator";
143
+
144
+ Checkbox.Root = CheckboxRoot;
145
+ Checkbox.Indicator = CheckboxIndicator;
146
+
147
+ export { Checkbox, CheckboxRoot, CheckboxIndicator };