mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-28 19:27:41 +00:00
0cb5fae947
Install motion + animate-ui, overhaul design tokens with brand cyan accent, and replace CSS keyframe animations in Dialog, Tabs, Switch, and Tooltip with spring-physics and blur-fade transitions via animate-ui Radix primitives.
156 lines
3.5 KiB
TypeScript
156 lines
3.5 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { Switch as SwitchPrimitives } from 'radix-ui';
|
|
import {
|
|
motion,
|
|
type TargetAndTransition,
|
|
type VariantLabels,
|
|
type HTMLMotionProps,
|
|
type LegacyAnimationControls,
|
|
} from 'motion/react';
|
|
|
|
import { getStrictContext } from '@/lib/get-strict-context';
|
|
import { useControlledState } from '@/hooks/use-controlled-state';
|
|
|
|
type SwitchContextType = {
|
|
isChecked: boolean;
|
|
setIsChecked: (isChecked: boolean) => void;
|
|
isPressed: boolean;
|
|
setIsPressed: (isPressed: boolean) => void;
|
|
};
|
|
|
|
const [SwitchProvider, useSwitch] =
|
|
getStrictContext<SwitchContextType>('SwitchContext');
|
|
|
|
type SwitchProps = Omit<
|
|
React.ComponentProps<typeof SwitchPrimitives.Root>,
|
|
'asChild'
|
|
> &
|
|
HTMLMotionProps<'button'>;
|
|
|
|
function Switch(props: SwitchProps) {
|
|
// Destructure Radix-only props so they don't leak onto the motion.button DOM element
|
|
const {
|
|
checked,
|
|
defaultChecked,
|
|
onCheckedChange,
|
|
disabled,
|
|
required,
|
|
name,
|
|
value,
|
|
form,
|
|
...motionProps
|
|
} = props;
|
|
|
|
const [isPressed, setIsPressed] = React.useState(false);
|
|
const [isChecked, setIsChecked] = useControlledState({
|
|
value: checked,
|
|
defaultValue: defaultChecked,
|
|
onChange: onCheckedChange,
|
|
});
|
|
|
|
return (
|
|
<SwitchProvider
|
|
value={{ isChecked, setIsChecked, isPressed, setIsPressed }}
|
|
>
|
|
<SwitchPrimitives.Root
|
|
checked={checked}
|
|
defaultChecked={defaultChecked}
|
|
onCheckedChange={setIsChecked}
|
|
disabled={disabled}
|
|
required={required}
|
|
name={name}
|
|
value={value}
|
|
form={form}
|
|
asChild
|
|
>
|
|
<motion.button
|
|
data-slot="switch"
|
|
whileTap="tap"
|
|
initial={false}
|
|
onTapStart={() => setIsPressed(true)}
|
|
onTapCancel={() => setIsPressed(false)}
|
|
onTap={() => setIsPressed(false)}
|
|
{...motionProps}
|
|
/>
|
|
</SwitchPrimitives.Root>
|
|
</SwitchProvider>
|
|
);
|
|
}
|
|
|
|
type SwitchThumbProps = Omit<
|
|
React.ComponentProps<typeof SwitchPrimitives.Thumb>,
|
|
'asChild'
|
|
> &
|
|
HTMLMotionProps<'div'> & {
|
|
pressedAnimation?:
|
|
| TargetAndTransition
|
|
| VariantLabels
|
|
| boolean
|
|
| LegacyAnimationControls;
|
|
};
|
|
|
|
function SwitchThumb({
|
|
pressedAnimation,
|
|
transition = { type: 'spring', stiffness: 300, damping: 25 },
|
|
...props
|
|
}: SwitchThumbProps) {
|
|
const { isPressed } = useSwitch();
|
|
|
|
return (
|
|
<SwitchPrimitives.Thumb asChild>
|
|
<motion.div
|
|
data-slot="switch-thumb"
|
|
whileTap="tab"
|
|
layout
|
|
transition={transition}
|
|
animate={isPressed ? pressedAnimation : undefined}
|
|
{...props}
|
|
/>
|
|
</SwitchPrimitives.Thumb>
|
|
);
|
|
}
|
|
|
|
type SwitchIconPosition = 'left' | 'right' | 'thumb';
|
|
|
|
type SwitchIconProps = HTMLMotionProps<'div'> & {
|
|
position: SwitchIconPosition;
|
|
};
|
|
|
|
function SwitchIcon({
|
|
position,
|
|
transition = { type: 'spring', bounce: 0 },
|
|
...props
|
|
}: SwitchIconProps) {
|
|
const { isChecked } = useSwitch();
|
|
|
|
const isAnimated = React.useMemo(() => {
|
|
if (position === 'right') return !isChecked;
|
|
if (position === 'left') return isChecked;
|
|
if (position === 'thumb') return true;
|
|
return false;
|
|
}, [position, isChecked]);
|
|
|
|
return (
|
|
<motion.div
|
|
data-slot={`switch-${position}-icon`}
|
|
animate={isAnimated ? { scale: 1, opacity: 1 } : { scale: 0, opacity: 0 }}
|
|
transition={transition}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export {
|
|
Switch,
|
|
SwitchThumb,
|
|
SwitchIcon,
|
|
useSwitch,
|
|
type SwitchProps,
|
|
type SwitchThumbProps,
|
|
type SwitchIconProps,
|
|
type SwitchIconPosition,
|
|
type SwitchContextType,
|
|
};
|