|
| 1 | +'use client' |
| 2 | +import { Box, Input } from '@devup-ui/react' |
| 3 | +import { useState } from 'react' |
| 4 | + |
| 5 | +interface ToggleProps { |
| 6 | + defaultValue?: boolean | null |
| 7 | + value?: boolean | null |
| 8 | + onChange?: (value: boolean) => void |
| 9 | + disabled?: boolean |
| 10 | + variant?: 'default' | 'switch' |
| 11 | + className?: string |
| 12 | + style?: React.CSSProperties |
| 13 | + classNames?: { |
| 14 | + toggle?: string |
| 15 | + } |
| 16 | + styles?: { |
| 17 | + toggle?: React.CSSProperties |
| 18 | + } |
| 19 | + colors?: { |
| 20 | + primary?: string |
| 21 | + bg?: string |
| 22 | + hoverBg?: string |
| 23 | + primaryHoverBg?: string |
| 24 | + disabledBg?: string |
| 25 | + switchHoverOutline?: string |
| 26 | + switchShadow?: string |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +export function Toggle({ |
| 31 | + defaultValue = null, |
| 32 | + value = null, |
| 33 | + onChange, |
| 34 | + disabled, |
| 35 | + className, |
| 36 | + style, |
| 37 | + variant = 'default', |
| 38 | + colors, |
| 39 | + classNames, |
| 40 | + styles, |
| 41 | +}: ToggleProps) { |
| 42 | + const [innerValue, setInnerValue] = useState<boolean>( |
| 43 | + value ?? defaultValue ?? false, |
| 44 | + ) |
| 45 | + |
| 46 | + const resultValue = value ?? innerValue |
| 47 | + |
| 48 | + function handleToggle(value: boolean) { |
| 49 | + onChange?.(!value) |
| 50 | + setInnerValue((prev) => !prev) |
| 51 | + } |
| 52 | + |
| 53 | + const isDefault = variant === 'default' |
| 54 | + |
| 55 | + return ( |
| 56 | + <> |
| 57 | + <Box |
| 58 | + aria-disabled={disabled} |
| 59 | + bg={ |
| 60 | + resultValue |
| 61 | + ? 'var(--primary)' |
| 62 | + : 'var(--bg, light-dark(#E4E4E4, #383838))' |
| 63 | + } |
| 64 | + borderRadius="500px" |
| 65 | + boxSizing="border-box" |
| 66 | + className="toggleSwitch" |
| 67 | + cursor="pointer" |
| 68 | + h={isDefault ? '28px' : '8px'} |
| 69 | + justifyContent={resultValue ? 'flex-end' : undefined} |
| 70 | + onClick={() => !disabled && handleToggle(resultValue)} |
| 71 | + p={isDefault ? 1 : undefined} |
| 72 | + position="relative" |
| 73 | + selectors={{ |
| 74 | + '&[aria-disabled=true]': { |
| 75 | + cursor: 'not-allowed', |
| 76 | + bg: 'var(--disabledBg, light-dark(#D6D7DE, #373737))', |
| 77 | + }, |
| 78 | + '&:hover:not([aria-disabled=true]):not(:disabled)': { |
| 79 | + bg: resultValue |
| 80 | + ? `var(--primaryHoverBg, light-dark(color-mix(in srgb, var(--primary) 100%, #000 15%), color-mix(in srgb, var(--primary) 100%, #FFF 15%)))` |
| 81 | + : 'var(--hoverBg, light-dark(#C3C2C8, #696A6F))', |
| 82 | + }, |
| 83 | + }} |
| 84 | + style={style} |
| 85 | + styleVars={{ |
| 86 | + primary: colors?.primary, |
| 87 | + bg: colors?.bg, |
| 88 | + primaryHoverBg: colors?.primaryHoverBg, |
| 89 | + hoverBg: colors?.hoverBg, |
| 90 | + disabledBg: colors?.disabledBg, |
| 91 | + }} |
| 92 | + transition=".25s" |
| 93 | + w={isDefault ? '50px' : '40px'} |
| 94 | + > |
| 95 | + <Box |
| 96 | + backgroundColor="#fff" |
| 97 | + borderRadius="100%" |
| 98 | + boxSize="20px" |
| 99 | + className={classNames?.toggle} |
| 100 | + filter={ |
| 101 | + isDefault |
| 102 | + ? undefined |
| 103 | + : `drop-shadow(0px 0px 3px var(--switchShadow, rgba(0, 0, 0, 0.10)));` |
| 104 | + } |
| 105 | + outline="4px" |
| 106 | + pos="absolute" |
| 107 | + selectors={{ |
| 108 | + '.toggleSwitch:hover[aria-disabled=false] > &': { |
| 109 | + outline: '4px solid', |
| 110 | + outlineColor: `var(--switchHoverOutline, light-dark(color-mix(in srgb, var(--primary) 20%, transparent), color-mix(in srgb, var(--primary) 50%, transparent)))`, |
| 111 | + }, |
| 112 | + }} |
| 113 | + style={styles?.toggle} |
| 114 | + styleVars={{ |
| 115 | + primary: colors?.primary, |
| 116 | + primaryHoverBg: colors?.primaryHoverBg, |
| 117 | + switchShadow: colors?.switchShadow, |
| 118 | + switchHoverOutline: colors?.switchHoverOutline, |
| 119 | + }} |
| 120 | + top={isDefault ? undefined : '-6px'} |
| 121 | + transform={resultValue ? 'translateX(calc(100% + 2px))' : undefined} |
| 122 | + transition={isDefault ? '.25s' : 'transform .25s'} |
| 123 | + /> |
| 124 | + </Box> |
| 125 | + <Input |
| 126 | + defaultValue={String(defaultValue)} |
| 127 | + type="hidden" |
| 128 | + value={String(resultValue)} |
| 129 | + /> |
| 130 | + </> |
| 131 | + ) |
| 132 | +} |
0 commit comments