Skip to content

Commit 034c64a

Browse files
committed
feat: switch styled layer
1 parent a8a2688 commit 034c64a

File tree

17 files changed

+373
-182
lines changed

17 files changed

+373
-182
lines changed

docs/registry/ui/switch.tsx

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,22 @@
1-
import { type UseSwitchProps, useSwitch } from "@seed-design/react-switch";
2-
import {
3-
type SwitchVariantProps,
4-
switchStyle,
5-
} from "@seed-design/recipe/switch";
6-
import clsx from "clsx";
71
import * as React from "react";
8-
9-
import type { Assign } from "../util/types";
10-
import { visuallyHidden } from "../util/visuallyHidden";
2+
import { Switch as SeedSwitch } from "@seed-design/react";
113

124
import "@seed-design/stylesheet/switch.css";
135

14-
export interface SwitchProps extends SwitchVariantProps {}
15-
16-
interface ReactSwitchProps
17-
extends Assign<React.HTMLAttributes<HTMLInputElement>, UseSwitchProps>,
18-
SwitchProps {}
19-
20-
export const Switch = React.forwardRef<HTMLInputElement, ReactSwitchProps>(
21-
({ className, size = "medium", ...otherProps }, ref) => {
22-
const { restProps, controlProps, hiddenInputProps, rootProps, thumbProps } =
23-
useSwitch(otherProps);
24-
const classNames = switchStyle({ size });
6+
export interface SwitchProps extends SeedSwitch.RootProps {
7+
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
8+
rootRef?: React.Ref<HTMLLabelElement>;
9+
}
2510

11+
export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
12+
({ inputProps, rootRef, ...otherProps }, ref) => {
2613
return (
27-
<label className={clsx(classNames.root, className)} {...rootProps}>
28-
<div {...controlProps} className={classNames.control}>
29-
<div {...thumbProps} className={classNames.thumb} />
30-
</div>
31-
<input
32-
ref={ref}
33-
{...hiddenInputProps}
34-
{...restProps}
35-
style={visuallyHidden}
36-
/>
37-
</label>
14+
<SeedSwitch.Root ref={rootRef} {...otherProps}>
15+
<SeedSwitch.Control>
16+
<SeedSwitch.Thumb />
17+
</SeedSwitch.Control>
18+
<SeedSwitch.HiddenInput ref={ref} {...inputProps} />
19+
</SeedSwitch.Root>
3820
);
3921
},
4022
);

packages/react-headless/switch/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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export {
2+
SwitchRoot as Root,
3+
SwitchControl as Control,
4+
SwitchThumb as Thumb,
5+
SwitchHiddenInput as HiddenInput,
6+
type SwitchRootProps as RootProps,
7+
type SwitchControlProps as ControlProps,
8+
type SwitchThumbProps as ThumbProps,
9+
type SwitchHiddenInputProps as HiddenInputProps,
10+
} from "./Switch";
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Primitive, type PrimitiveProps } from "@seed-design/react-primitive";
2+
import type * as React from "react";
3+
import { forwardRef } from "react";
4+
import { useSwitch, type UseSwitchProps } from "./useSwitch";
5+
import { SwitchProvider, useSwitchContext } from "./useSwitchContext";
6+
7+
export interface SwitchRootProps
8+
extends UseSwitchProps,
9+
PrimitiveProps,
10+
React.HTMLAttributes<HTMLLabelElement> {}
11+
12+
export const SwitchRoot = forwardRef<HTMLLabelElement, SwitchRootProps>((props, ref) => {
13+
const api = useSwitch(props);
14+
return (
15+
<SwitchProvider value={api}>
16+
<Primitive.label ref={ref} {...api.rootProps} {...api.restProps} />
17+
</SwitchProvider>
18+
);
19+
});
20+
SwitchRoot.displayName = "SwitchRoot";
21+
22+
export interface SwitchControlProps extends PrimitiveProps, React.HTMLAttributes<HTMLDivElement> {}
23+
24+
export const SwitchControl = forwardRef<HTMLDivElement, SwitchControlProps>((props, ref) => {
25+
const { controlProps } = useSwitchContext();
26+
return <Primitive.div ref={ref} {...controlProps} {...props} />;
27+
});
28+
SwitchControl.displayName = "SwitchControl";
29+
30+
export interface SwitchThumbProps extends PrimitiveProps, React.HTMLAttributes<HTMLDivElement> {}
31+
32+
export const SwitchThumb = forwardRef<HTMLDivElement, SwitchThumbProps>((props, ref) => {
33+
const { thumbProps } = useSwitchContext();
34+
return <Primitive.div ref={ref} {...thumbProps} {...props} />;
35+
});
36+
SwitchThumb.displayName = "SwitchThumb";
37+
38+
export interface SwitchHiddenInputProps
39+
extends PrimitiveProps,
40+
React.InputHTMLAttributes<HTMLInputElement> {}
41+
42+
export const SwitchHiddenInput = forwardRef<HTMLInputElement, SwitchHiddenInputProps>(
43+
({ onBlur, onChange, onFocus, onKeyDown, onKeyUp, ...otherProps }, ref) => {
44+
const { getHiddenInputProps } = useSwitchContext();
45+
return (
46+
<Primitive.input
47+
ref={ref}
48+
{...getHiddenInputProps({ onBlur, onChange, onFocus, onKeyDown, onKeyUp })}
49+
{...otherProps}
50+
/>
51+
);
52+
},
53+
);
54+
SwitchHiddenInput.displayName = "SwitchHiddenInput";
Lines changed: 14 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,14 @@
1-
import { useControllableState } from "@radix-ui/react-use-controllable-state";
2-
import { useState } from "react";
3-
4-
import { dataAttr, elementProps, inputProps } from "@seed-design/dom-utils";
5-
6-
export interface UseSwitchStateProps {
7-
checked?: boolean;
8-
9-
defaultChecked?: boolean;
10-
11-
onCheckedChange?: (checked: boolean) => void;
12-
}
13-
14-
export function useSwitchState(props: UseSwitchStateProps) {
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 UseSwitchProps extends UseSwitchStateProps {
40-
disabled?: boolean;
41-
42-
invalid?: boolean;
43-
}
44-
45-
export function useSwitch(props: UseSwitchProps) {
46-
const { checked, defaultChecked, disabled, invalid, onCheckedChange, ...restProps } = props;
47-
const {
48-
setIsChecked,
49-
isChecked,
50-
setIsHovered,
51-
isHovered,
52-
setIsActive,
53-
isActive,
54-
setIsFocused,
55-
isFocused,
56-
setIsFocusVisible,
57-
isFocusVisible,
58-
} = useSwitchState(props);
59-
60-
const stateProps = {
61-
"data-checked": dataAttr(isChecked),
62-
"data-hover": dataAttr(isHovered),
63-
"data-active": dataAttr(isActive),
64-
"data-focus": dataAttr(isFocused),
65-
"data-focus-visible": dataAttr(isFocusVisible),
66-
"data-disabled": dataAttr(props.disabled),
67-
};
68-
69-
const isControlled = checked != null;
70-
71-
return {
72-
isChecked,
73-
setIsChecked,
74-
isFocused,
75-
setIsFocused,
76-
setIsFocusVisible,
77-
78-
restProps,
79-
stateProps,
80-
rootProps: elementProps({
81-
...stateProps,
82-
onPointerMove() {
83-
setIsHovered(true);
84-
},
85-
onPointerDown() {
86-
setIsActive(true);
87-
},
88-
onPointerUp() {
89-
setIsActive(false);
90-
},
91-
onPointerLeave() {
92-
setIsHovered(false);
93-
setIsActive(false);
94-
},
95-
}),
96-
97-
controlProps: elementProps({
98-
...stateProps,
99-
"aria-hidden": true,
100-
}),
101-
102-
thumbProps: elementProps({
103-
...stateProps,
104-
"aria-hidden": true,
105-
}),
106-
107-
hiddenInputProps: inputProps({
108-
type: "checkbox",
109-
role: "switch",
110-
checked: isControlled ? isChecked : undefined,
111-
disabled: props.disabled,
112-
"aria-invalid": props.invalid,
113-
...stateProps,
114-
onChange(event) {
115-
setIsChecked(event.currentTarget.checked);
116-
setIsFocusVisible(event.target.matches(":focus-visible"));
117-
},
118-
onFocus(event) {
119-
setIsFocused(true);
120-
setIsFocusVisible(event.target.matches(":focus-visible"));
121-
},
122-
onBlur() {
123-
setIsFocused(false);
124-
setIsFocusVisible(false);
125-
},
126-
onKeyDown(event) {
127-
if (event.key === " ") {
128-
setIsActive(true);
129-
}
130-
},
131-
onKeyUp(event) {
132-
if (event.key === " ") {
133-
setIsActive(false);
134-
}
135-
},
136-
}),
137-
};
138-
}
1+
export {
2+
SwitchRoot,
3+
SwitchControl,
4+
SwitchThumb,
5+
SwitchHiddenInput,
6+
type SwitchRootProps,
7+
type SwitchControlProps,
8+
type SwitchThumbProps,
9+
type SwitchHiddenInputProps,
10+
} from "./Switch";
11+
12+
export { useSwitchContext, type UseSwitchContext } from "./useSwitchContext";
13+
14+
export * as Switch from "./Switch.namespace";

packages/react-headless/switch/src/useSwitch.test.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ import { afterEach, describe, expect, it, vi } from "vitest";
66
import type { ReactElement } from "react";
77
import * as React from "react";
88

9-
import { useSwitch, type UseSwitchProps } from "./index";
9+
import {
10+
SwitchControl,
11+
SwitchHiddenInput,
12+
SwitchRoot,
13+
SwitchThumb,
14+
type SwitchRootProps,
15+
} from "./Switch";
1016

1117
afterEach(cleanup);
1218

@@ -17,18 +23,15 @@ function setUp(jsx: ReactElement) {
1723
};
1824
}
1925

20-
function Switch(props: UseSwitchProps) {
21-
const { stateProps, restProps, controlProps, hiddenInputProps, rootProps, thumbProps } =
22-
useSwitch(props);
26+
const Switch = React.forwardRef<HTMLInputElement, SwitchRootProps>((props, ref) => {
2327
return (
24-
<label {...rootProps}>
25-
<div {...controlProps}>
26-
<div {...stateProps} {...thumbProps} />
27-
</div>
28-
<input {...hiddenInputProps} {...restProps} />
29-
</label>
28+
<SwitchRoot {...props}>
29+
<SwitchControl />
30+
<SwitchThumb />
31+
<SwitchHiddenInput ref={ref} />
32+
</SwitchRoot>
3033
);
31-
}
34+
});
3235

3336
describe("useSwitch", () => {
3437
it("should render the switch correctly", () => {

0 commit comments

Comments
 (0)