Files
community-rule/app/components/controls/Switch/Switch.container.tsx
T
2026-04-29 07:20:16 -06:00

170 lines
3.8 KiB
TypeScript

"use client";
import { memo, useCallback, useId, forwardRef } from "react";
import { SwitchView } from "./Switch.view";
import type { SwitchProps } from "./Switch.types";
/**
* Figma: "Control / Switch". Animated on/off toggle switch,
* optionally paired with a trailing text label.
*/
const SwitchContainer = memo(
forwardRef<HTMLButtonElement, SwitchProps>((props, ref) => {
const {
propSwitch = false,
onChange,
onFocus,
onBlur,
state: stateProp = "default",
text,
className = "",
...rest
} = props;
const state = stateProp;
const switchId = useId();
const handleClick = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
if (onChange) {
onChange(e);
}
},
[onChange],
);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLButtonElement>) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
if (onChange) {
onChange(e);
}
}
},
[onChange],
);
const handleFocus = useCallback(
(e: React.FocusEvent<HTMLButtonElement>) => {
if (onFocus) {
onFocus(e);
}
},
[onFocus],
);
const handleBlur = useCallback(
(e: React.FocusEvent<HTMLButtonElement>) => {
if (onBlur) {
onBlur(e);
}
},
[onBlur],
);
// Switch track styles based on propSwitch state
const getTrackStyles = useCallback(() => {
return propSwitch
? "bg-[var(--color-surface-inverse-tertiary)]"
: "bg-[var(--color-surface-default-tertiary)]";
}, [propSwitch]);
// Switch thumb styles based on propSwitch state
const getThumbStyles = useCallback(() => {
return "bg-[var(--color-gray-000)]";
}, []);
// Focus styles
const getFocusStyles = useCallback(() => {
if (state === "focus") {
return "shadow-[0_0_5px_3px_#3281F8] rounded-full";
}
return "";
}, [state]);
const trackStyles = getTrackStyles();
const thumbStyles = getThumbStyles();
const focusStyles = getFocusStyles();
const switchClasses = `
relative
inline-flex
items-center
cursor-pointer
transition-all
duration-200
focus:outline-none
focus-visible:shadow-[0_0_5px_3px_#3281F8]
focus-visible:rounded-full
${focusStyles}
${className}
`
.trim()
.replace(/\s+/g, " ");
const trackClasses = `
${trackStyles}
w-[40px]
h-[24px]
rounded-full
transition-all
duration-200
flex
items-center
${propSwitch ? "justify-end" : "justify-start"}
p-[2px]
`
.trim()
.replace(/\s+/g, " ");
const thumbClasses = `
${thumbStyles}
w-[var(--measures-sizing-020)]
h-[var(--measures-sizing-020)]
rounded-[var(--measures-radius-xlarge)]
transition-all
duration-200
shadow-sm
`
.trim()
.replace(/\s+/g, " ");
const labelClasses = `
ml-[var(--measures-spacing-008)]
font-inter
font-normal
text-[14px]
leading-[20px]
text-[var(--color-content-default-primary)]
`
.trim()
.replace(/\s+/g, " ");
return (
<SwitchView
ref={ref}
switchId={switchId}
propSwitch={propSwitch}
state={state}
text={text}
className={className}
switchClasses={switchClasses}
trackClasses={trackClasses}
thumbClasses={thumbClasses}
labelClasses={labelClasses}
onClick={handleClick}
onKeyDown={handleKeyDown}
onFocus={handleFocus}
onBlur={handleBlur}
{...rest}
/>
);
}),
);
SwitchContainer.displayName = "Switch";
export default SwitchContainer;