dify/packages/dify-ui/src/switch/index.tsx

132 lines
4.3 KiB
TypeScript

'use client'
import type { Switch as BaseSwitchNS } from '@base-ui/react/switch'
import type { VariantProps } from 'class-variance-authority'
import { Switch as BaseSwitch } from '@base-ui/react/switch'
import { cva } from 'class-variance-authority'
import * as React from 'react'
import { cn } from '../cn'
const switchRootStateClassName =
'bg-components-toggle-bg-unchecked hover:bg-components-toggle-bg-unchecked-hover data-checked:bg-components-toggle-bg data-checked:hover:bg-components-toggle-bg-hover data-disabled:cursor-not-allowed data-disabled:bg-components-toggle-bg-unchecked-disabled data-disabled:hover:bg-components-toggle-bg-unchecked-disabled data-disabled:data-checked:bg-components-toggle-bg-disabled data-disabled:data-checked:hover:bg-components-toggle-bg-disabled'
const switchRootVariants = cva(
`group relative inline-flex shrink-0 cursor-pointer touch-manipulation items-center outline-hidden transition-colors duration-200 ease-in-out focus-visible:ring-2 focus-visible:ring-state-accent-solid motion-reduce:transition-none ${switchRootStateClassName}`,
{
variants: {
size: {
xs: 'h-2.5 w-3.5 rounded-xs p-0.5',
sm: 'h-3 w-5 rounded-[3.5px] p-0.5',
md: 'h-4 w-7 rounded-[5px] p-0.5',
lg: 'h-5 w-9 rounded-md p-0.75',
},
},
defaultVariants: {
size: 'md',
},
},
)
const switchThumbVariants = cva(
'block bg-components-toggle-knob shadow-sm transition-transform duration-200 ease-in-out group-hover:bg-components-toggle-knob-hover group-hover:shadow-md group-data-disabled:bg-components-toggle-knob-disabled group-data-disabled:shadow-none motion-reduce:transition-none',
{
variants: {
size: {
xs: 'h-1.5 w-1 rounded-[1px] data-checked:translate-x-1.5',
sm: 'h-2 w-1.75 rounded-xs data-checked:translate-x-2.25',
md: 'h-3 w-2.5 rounded-[3px] data-checked:translate-x-3.5',
lg: 'size-3.5 rounded-sm data-checked:translate-x-4',
},
},
defaultVariants: {
size: 'md',
},
},
)
const switchSpinnerVariants = cva('absolute top-1/2 -translate-x-1/2 -translate-y-1/2', {
variants: {
size: {
md: 'left-[calc(50%+6px)] size-2 group-data-checked:left-[calc(50%-6px)]',
lg: 'left-[calc(50%+8px)] size-2.5 group-data-checked:left-[calc(50%-8px)]',
},
},
})
type ControlledSwitchProps = {
checked: boolean
defaultChecked?: never
}
type UncontrolledSwitchProps = {
checked?: never
defaultChecked?: boolean
}
type SwitchControlProps = ControlledSwitchProps | UncontrolledSwitchProps
type SwitchProps = Omit<
BaseSwitchNS.Root.Props,
'checked' | 'defaultChecked' | 'className' | 'size' | 'onCheckedChange'
> &
VariantProps<typeof switchRootVariants> &
SwitchControlProps & {
onCheckedChange?: (checked: boolean) => void
loading?: boolean
className?: string
}
function Switch({
checked,
size = 'md',
disabled,
loading = false,
className,
onCheckedChange,
...props
}: SwitchProps) {
const isDisabled = disabled || loading
return (
<BaseSwitch.Root
checked={checked}
disabled={isDisabled}
aria-busy={loading || undefined}
className={cn(switchRootVariants({ size }), className)}
onCheckedChange={(value) => onCheckedChange?.(value)}
{...props}
>
<BaseSwitch.Thumb className={switchThumbVariants({ size })} />
{loading && (size === 'md' || size === 'lg') ? (
<span className={switchSpinnerVariants({ size })} aria-hidden="true">
<i className="i-ri-loader-2-line size-full animate-spin text-text-tertiary motion-reduce:animate-none" />
</span>
) : null}
</BaseSwitch.Root>
)
}
const switchSkeletonVariants = cva('bg-text-quaternary opacity-20', {
variants: {
size: {
xs: 'h-2.5 w-3.5 rounded-xs',
sm: 'h-3 w-5 rounded-[3.5px]',
md: 'h-4 w-7 rounded-[5px]',
lg: 'h-5 w-9 rounded-md',
},
},
defaultVariants: {
size: 'md',
},
})
type SwitchSkeletonProps = React.ComponentProps<'div'> & VariantProps<typeof switchSkeletonVariants>
function SwitchSkeleton({ size = 'md', className, ...props }: SwitchSkeletonProps) {
return <div className={cn(switchSkeletonVariants({ size }), className)} {...props} />
}
export { Switch, SwitchSkeleton }
export type { SwitchProps, SwitchSkeletonProps }