Skip to content

Commit 170ebed

Browse files
committed
feat: checkbox styled layer
1 parent ed55cf5 commit 170ebed

File tree

14 files changed

+373
-229
lines changed

14 files changed

+373
-229
lines changed

docs/registry/ui/checkbox.tsx

Lines changed: 13 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,25 @@
11
"use client";
22

3-
import {
4-
type UseCheckboxProps,
5-
useCheckbox,
6-
} from "@seed-design/react-checkbox";
7-
import {
8-
type CheckboxVariantProps,
9-
checkbox,
10-
} from "@seed-design/recipe/checkbox";
11-
import clsx from "clsx";
3+
import IconCheckmarkFatFill from "@daangn/react-monochrome-icon/IconCheckmarkFatFill";
4+
import { Checkbox as SeedCheckbox } from "@seed-design/react";
125
import * as React from "react";
13-
import type { CSSProperties } from "react";
14-
import {
15-
IconCheckmarkFatFill,
16-
IconMinusFatFill,
17-
} from "@daangn/react-monochrome-icon";
186

197
import "@seed-design/stylesheet/checkbox.css";
208

21-
type Assign<T, U> = Omit<T, keyof U> & U;
22-
23-
const visuallyHidden: CSSProperties = {
24-
border: 0,
25-
clip: "rect(0 0 0 0)",
26-
height: "1px",
27-
margin: "-1px",
28-
overflow: "hidden",
29-
padding: 0,
30-
position: "absolute",
31-
whiteSpace: "nowrap",
32-
width: "1px",
33-
};
34-
35-
export interface CheckboxProps extends CheckboxVariantProps {
36-
label: React.ReactNode;
9+
export interface CheckboxProps extends SeedCheckbox.RootProps {
10+
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
11+
rootRef?: React.Ref<HTMLLabelElement>;
3712
}
3813

39-
interface ReactCheckboxProps
40-
extends Omit<
41-
Assign<React.InputHTMLAttributes<HTMLInputElement>, UseCheckboxProps>,
42-
"size"
43-
>,
44-
CheckboxProps {}
45-
46-
export const Checkbox = React.forwardRef<HTMLInputElement, ReactCheckboxProps>(
47-
(
48-
{
49-
className,
50-
size = "medium",
51-
label,
52-
bold = false,
53-
indeterminate = false,
54-
variant = "square",
55-
...otherProps
56-
},
57-
ref,
58-
) => {
59-
const { stateProps, restProps, controlProps, hiddenInputProps, rootProps } =
60-
useCheckbox(otherProps);
61-
62-
const classNames = checkbox({ size, bold, indeterminate, variant });
63-
14+
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
15+
({ inputProps, rootRef, ...otherProps }, ref) => {
6416
return (
65-
<label className={clsx(classNames.root, className)} {...rootProps}>
66-
<div {...controlProps} className={classNames.control}>
67-
{!indeterminate ? (
68-
<IconCheckmarkFatFill
69-
aria-hidden
70-
{...stateProps}
71-
className={classNames.icon}
72-
/>
73-
) : (
74-
<IconMinusFatFill
75-
aria-hidden
76-
{...stateProps}
77-
className={classNames.icon}
78-
/>
79-
)}
80-
</div>
81-
<input
82-
ref={ref}
83-
{...hiddenInputProps}
84-
{...restProps}
85-
style={visuallyHidden}
86-
/>
87-
<span {...stateProps} className={classNames.label}>
88-
{label}
89-
</span>
90-
</label>
17+
<SeedCheckbox.Root ref={rootRef} {...otherProps}>
18+
<SeedCheckbox.Control>
19+
<SeedCheckbox.CheckedIcon svg={<IconCheckmarkFatFill />} />
20+
</SeedCheckbox.Control>
21+
<SeedCheckbox.HiddenInput ref={ref} {...inputProps} />
22+
</SeedCheckbox.Root>
9123
);
9224
},
9325
);

packages/react-headless/checkbox/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
},
2828
"dependencies": {
2929
"@radix-ui/react-use-controllable-state": "1.0.1",
30-
"@seed-design/dom-utils": "0.0.0-alpha-20241030023710"
30+
"@seed-design/dom-utils": "0.0.0-alpha-20241030023710",
31+
"@seed-design/react-primitive": "0.0.0"
3132
},
3233
"devDependencies": {
3334
"nanobundle": "^1.6.0"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export {
2+
CheckboxRoot as Root,
3+
CheckboxControl as Control,
4+
CheckboxHiddenInput as HiddenInput,
5+
type CheckboxRootProps as RootProps,
6+
type CheckboxControlProps as ControlProps,
7+
type CheckboxHiddenInputProps as HiddenInputProps,
8+
} from "./Checkbox";
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { mergeProps } from "@seed-design/dom-utils";
2+
import { Primitive, type PrimitiveProps } from "@seed-design/react-primitive";
3+
import type * as React from "react";
4+
import { forwardRef, useMemo } from "react";
5+
import { useCheckbox, type UseCheckboxProps } from "./useCheckbox";
6+
import { CheckboxProvider, useCheckboxContext } from "./useCheckboxContext";
7+
8+
export interface CheckboxRootProps
9+
extends UseCheckboxProps,
10+
PrimitiveProps,
11+
React.HTMLAttributes<HTMLLabelElement> {}
12+
13+
export const CheckboxRoot = forwardRef<HTMLLabelElement, CheckboxRootProps>((props, ref) => {
14+
const { checked, defaultChecked, disabled, invalid, onCheckedChange, required, ...otherProps } =
15+
props;
16+
17+
const api = useMemo(
18+
() =>
19+
useCheckbox({
20+
checked,
21+
defaultChecked,
22+
disabled,
23+
invalid,
24+
onCheckedChange,
25+
required,
26+
}),
27+
[checked, defaultChecked, disabled, invalid, required],
28+
);
29+
const mergedProps = mergeProps(api.rootProps, otherProps);
30+
31+
return (
32+
<CheckboxProvider value={api}>
33+
<Primitive.label ref={ref} {...mergedProps} />
34+
</CheckboxProvider>
35+
);
36+
});
37+
CheckboxRoot.displayName = "CheckboxRoot";
38+
39+
export interface CheckboxControlProps
40+
extends PrimitiveProps,
41+
React.HTMLAttributes<HTMLDivElement> {}
42+
43+
export const CheckboxControl = forwardRef<HTMLDivElement, CheckboxControlProps>((props, ref) => {
44+
const { controlProps } = useCheckboxContext();
45+
const mergedProps = mergeProps(controlProps, props);
46+
return <Primitive.div ref={ref} {...mergedProps} />;
47+
});
48+
CheckboxControl.displayName = "CheckboxControl";
49+
50+
export interface CheckboxHiddenInputProps
51+
extends PrimitiveProps,
52+
React.InputHTMLAttributes<HTMLInputElement> {}
53+
54+
export const CheckboxHiddenInput = forwardRef<HTMLInputElement, CheckboxHiddenInputProps>(
55+
(props, ref) => {
56+
const { hiddenInputProps } = useCheckboxContext();
57+
const mergedProps = mergeProps(hiddenInputProps, props);
58+
return <Primitive.input ref={ref} {...mergedProps} />;
59+
},
60+
);
61+
CheckboxHiddenInput.displayName = "CheckboxHiddenInput";
Lines changed: 12 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,12 @@
1-
import { useControllableState } from "@radix-ui/react-use-controllable-state";
2-
import { useState } from "react";
3-
4-
import { dataAttr, elementProps, inputProps, labelProps } from "@seed-design/dom-utils";
5-
6-
export interface UseCheckboxStateProps {
7-
checked?: boolean;
8-
9-
defaultChecked?: boolean;
10-
11-
onCheckedChange?: (checked: boolean) => void;
12-
}
13-
14-
export function useCheckboxState(props: UseCheckboxStateProps) {
15-
const [isChecked, setIsChecked] = useControllableState({
16-
prop: props.checked,
17-
defaultProp: props.defaultChecked,
18-
onChange: props.onCheckedChange,
19-
});
20-
const [isHovered, setIsHovered] = useState(false);
21-
const [isActive, setIsActive] = useState(false);
22-
const [isFocused, setIsFocused] = useState(false);
23-
const [isFocusVisible, setIsFocusVisible] = useState(false);
24-
25-
return {
26-
isChecked,
27-
setIsChecked,
28-
isHovered,
29-
setIsHovered,
30-
isActive,
31-
setIsActive,
32-
isFocused,
33-
setIsFocused,
34-
isFocusVisible,
35-
setIsFocusVisible,
36-
};
37-
}
38-
39-
export interface UseCheckboxProps extends UseCheckboxStateProps {
40-
disabled?: boolean;
41-
42-
invalid?: boolean;
43-
}
44-
45-
export function useCheckbox(props: UseCheckboxProps) {
46-
const { checked, defaultChecked, disabled, invalid, onCheckedChange, ...restProps } = props;
47-
48-
const {
49-
setIsChecked,
50-
isChecked,
51-
setIsHovered,
52-
isHovered,
53-
setIsActive,
54-
isActive,
55-
setIsFocused,
56-
isFocused,
57-
setIsFocusVisible,
58-
isFocusVisible,
59-
} = useCheckboxState(props);
60-
61-
const stateProps = {
62-
"data-checked": dataAttr(isChecked),
63-
"data-hover": dataAttr(isHovered),
64-
"data-active": dataAttr(isActive),
65-
"data-focus": dataAttr(isFocused),
66-
"data-focus-visible": dataAttr(isFocusVisible),
67-
"data-disabled": dataAttr(props.disabled),
68-
disabled,
69-
};
70-
71-
const isControlled = checked != null;
72-
73-
return {
74-
isChecked,
75-
setIsChecked,
76-
isFocused,
77-
setIsFocused,
78-
setIsFocusVisible,
79-
80-
restProps,
81-
stateProps,
82-
rootProps: labelProps({
83-
...stateProps,
84-
onPointerMove() {
85-
setIsHovered(true);
86-
},
87-
onPointerDown() {
88-
setIsActive(true);
89-
},
90-
onPointerUp() {
91-
setIsActive(false);
92-
},
93-
onPointerLeave() {
94-
setIsHovered(false);
95-
setIsActive(false);
96-
},
97-
}),
98-
99-
controlProps: elementProps({
100-
...stateProps,
101-
"aria-hidden": true,
102-
}),
103-
104-
hiddenInputProps: inputProps({
105-
type: "checkbox",
106-
defaultChecked,
107-
checked: isControlled ? isChecked : undefined,
108-
"aria-invalid": invalid,
109-
...stateProps,
110-
onChange(event) {
111-
setIsChecked(event.currentTarget.checked);
112-
setIsFocusVisible(event.target.matches(":focus-visible"));
113-
},
114-
onFocus(event) {
115-
setIsFocused(true);
116-
setIsFocusVisible(event.target.matches(":focus-visible"));
117-
},
118-
onBlur() {
119-
setIsFocused(false);
120-
setIsFocusVisible(false);
121-
},
122-
onKeyDown(event) {
123-
if (event.key === " ") {
124-
setIsActive(true);
125-
}
126-
},
127-
onKeyUp(event) {
128-
if (event.key === " ") {
129-
setIsActive(false);
130-
}
131-
},
132-
}),
133-
};
134-
}
1+
export {
2+
CheckboxRoot,
3+
CheckboxControl,
4+
CheckboxHiddenInput,
5+
type CheckboxRootProps,
6+
type CheckboxControlProps,
7+
type CheckboxHiddenInputProps,
8+
} from "./Checkbox";
9+
10+
export { useCheckboxContext, type UseCheckboxContext } from "./useCheckboxContext";
11+
12+
export * as Checkbox from "./Checkbox.namespace";

packages/react-headless/checkbox/src/useCheckbox.test.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ import { cleanup, render } from "@testing-library/react";
33
import userEvent from "@testing-library/user-event";
44
import { afterEach, describe, expect, it, vi } from "vitest";
55

6+
import type * as React from "react";
67
import type { ReactElement } from "react";
7-
import * as React from "react";
88

9-
import { useCheckbox, type UseCheckboxProps } from "./index";
9+
import {
10+
CheckboxControl,
11+
CheckboxHiddenInput,
12+
CheckboxRoot,
13+
type CheckboxRootProps,
14+
} from "./Checkbox";
1015

1116
afterEach(cleanup);
1217

@@ -17,16 +22,12 @@ function setUp(jsx: ReactElement) {
1722
};
1823
}
1924

20-
function Checkbox(props: UseCheckboxProps) {
21-
const { controlProps, hiddenInputProps, restProps, rootProps, stateProps } = useCheckbox(props);
25+
function Checkbox(props: CheckboxRootProps) {
2226
return (
23-
<label {...rootProps}>
24-
<div {...controlProps}>
25-
<svg {...stateProps} />
26-
</div>
27-
<input {...hiddenInputProps} {...restProps} />
28-
<span {...stateProps} />
29-
</label>
27+
<CheckboxRoot {...props}>
28+
<CheckboxControl />
29+
<CheckboxHiddenInput />
30+
</CheckboxRoot>
3031
);
3132
}
3233

0 commit comments

Comments
 (0)