-
Notifications
You must be signed in to change notification settings - Fork 45
/
Checkbox.tsx
180 lines (161 loc) · 5.79 KB
/
Checkbox.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Slot } from "@radix-ui/react-slot";
import { cn, isReactElement } from "../../helpers/utils";
import { useCheckboxGroupContext } from "../CheckboxGroup/CheckboxGroup";
import { Label, type LabelProps } from "../Label";
import { type LabelHelperProps } from "../types";
/* ---------------------------------- Types --------------------------------- */
export type CheckboxElement = React.ElementRef<typeof CheckboxPrimitive.Root>;
export type CheckboxElementProps = React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> &
LabelProps &
LabelHelperProps & {
isIndeterminate?: boolean;
};
type CheckboxRootProps = React.HTMLAttributes<HTMLDivElement> & { asChild?: boolean };
/* ------------------------------- Components ------------------------------- */
const CheckboxRoot = React.forwardRef<HTMLDivElement, CheckboxRootProps>(
({ className, children, asChild, ...otherProps }, ref) => {
const useAsChild = asChild && isReactElement(children);
const Component = useAsChild ? Slot : "div";
return (
<Component ref={ref} className={cn("wg-checkbox flex gap-2", className)} {...otherProps}>
{children}
</Component>
);
}
);
const CheckboxWedges = React.forwardRef<CheckboxElement, CheckboxElementProps>(
(
{
checked,
children,
className,
description,
disabled,
helperText,
id,
label,
required,
tooltip,
...otherProps
},
ref
) => {
const isDisabled = disabled;
const ariaInvalid = otherProps["aria-invalid"];
const isIndeterminate = checked === "indeterminate";
const generatedId = React.useId();
const elId = id ?? generatedId;
const isInGroup = useCheckboxGroupContext(true) ? true : false;
const indeterminateIcon = (
<svg className="scale-100" fill="none" height="24" viewBox="0 0 24 24" width="24">
<path
clipRule="evenodd"
d="M8 4C5.79086 4 4 5.79086 4 8V16C4 18.2091 5.79086 20 8 20H16C18.2091 20 20 18.2091 20 16V8C20 5.79086 18.2091 4 16 4H8ZM9 11C8.44772 11 8 11.4477 8 12C8 12.5523 8.44772 13 9 13H15C15.5523 13 16 12.5523 16 12C16 11.4477 15.5523 11 15 11H9Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
const renderEmptyBox = isIndeterminate ? (
indeterminateIcon
) : (
<svg
className={cn(
"wg-unchecked aspect-square w-full scale-100",
isDisabled && "text-surface-100 dark:text-surface-50"
)}
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
>
<rect
fill="none"
height="14.5"
rx="3"
ry="3"
stroke="currentColor"
strokeWidth="1.5"
width="14.5"
x="4.75"
y="4.75"
/>
</svg>
);
const renderCheckedBox = isIndeterminate ? (
indeterminateIcon
) : (
<svg
className={cn(
"aspect-square w-full scale-100",
isDisabled && "text-surface-200 dark:text-surface-100"
)}
fill="none"
height="24"
stroke="none"
viewBox="0 0 24 24"
width="24"
>
<path
clipRule="evenodd"
d="M8 4C5.79086 4 4 5.79086 4 8V16C4 18.2091 5.79086 20 8 20H16C18.2091 20 20 18.2091 20 16V8C20 5.79086 18.2091 4 16 4H8ZM15.7902 10.2702C16.0776 9.97186 16.0686 9.49708 15.7702 9.20976C15.4719 8.92244 14.9971 8.9314 14.7098 9.22977L10.9458 13.1385L9.31677 11.2588C9.04549 10.9458 8.57182 10.912 8.2588 11.1832C7.94579 11.4545 7.91195 11.9282 8.18323 12.2412L10.3499 14.7412C10.4879 14.9004 10.6864 14.9942 10.897 14.9997C11.1076 15.0053 11.3108 14.922 11.4569 14.7702L15.7902 10.2702Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
const renderLabel =
label ?? description ?? tooltip ?? helperText ? (
<div className="inline-flex flex-col">
<Label
className={cn(isInGroup && "font-normal")}
description={description}
disabled={isDisabled}
htmlFor={elId}
id={`${elId}__label`}
required={required}
tooltip={tooltip}
>
{label}
</Label>
<Label.Helper aria-invalid={ariaInvalid} disabled={isDisabled} id={`${elId}__describer`}>
{helperText}
</Label.Helper>
</div>
) : null;
return (
<CheckboxRoot>
<CheckboxPrimitive.Root
ref={ref}
aria-labelledby={label ? `${elId}__label` : undefined}
checked={checked}
className={cn(
"group relative flex size-6 items-center justify-center rounded-lg text-surface-200 outline-primary transition-colors duration-100 focus:outline-0 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 [&:has([data-state=checked])_.wg-unchecked]:hidden",
isDisabled && "pointer-events-none text-surface-200 dark:text-surface-100",
!isDisabled && !isIndeterminate && "hover:text-surface-300",
!isDisabled && isIndeterminate && "text-primary",
className
)}
disabled={isDisabled}
id={elId}
{...otherProps}
>
{renderEmptyBox}
<CheckboxPrimitive.Indicator asChild className="absolute text-primary">
{renderCheckedBox}
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
{!children ? renderLabel : children}
</CheckboxRoot>
);
}
);
CheckboxWedges.displayName = "CheckboxWedges";
CheckboxRoot.displayName = "CheckboxRoot";
const Checkbox = Object.assign(CheckboxWedges, {
Root: CheckboxRoot,
Item: CheckboxWedges,
});
export default Checkbox;