|
| 1 | +import { AriaAttributes, useRef } from 'react' |
| 2 | +import { AnimatePresence, motion } from 'framer-motion' |
| 3 | + |
| 4 | +import { Tooltip } from '@Common/Tooltip' |
| 5 | +import { ComponentSizeType } from '@Shared/constants' |
| 6 | +import { getUniqueId } from '@Shared/Helpers' |
| 7 | + |
| 8 | +import { Icon } from '../Icon' |
| 9 | +import { INDETERMINATE_ICON_WIDTH_MAP, LOADING_COLOR_MAP } from './constants' |
| 10 | +import { DTSwitchProps } from './types' |
| 11 | +import { |
| 12 | + getSwitchContainerClass, |
| 13 | + getSwitchIconColor, |
| 14 | + getSwitchThumbClass, |
| 15 | + getSwitchTrackColor, |
| 16 | + getSwitchTrackHoverColor, |
| 17 | + getThumbPadding, |
| 18 | + getThumbPosition, |
| 19 | +} from './utils' |
| 20 | + |
| 21 | +import './switch.scss' |
| 22 | + |
| 23 | +const Switch = ({ |
| 24 | + ariaLabel, |
| 25 | + dataTestId, |
| 26 | + isDisabled, |
| 27 | + isLoading, |
| 28 | + isChecked, |
| 29 | + tooltipContent, |
| 30 | + shape = 'rounded', |
| 31 | + variant = 'positive', |
| 32 | + iconColor, |
| 33 | + iconName, |
| 34 | + indeterminate = false, |
| 35 | + size = ComponentSizeType.medium, |
| 36 | + name, |
| 37 | + onChange, |
| 38 | +}: DTSwitchProps) => { |
| 39 | + const inputId = useRef(getUniqueId()) |
| 40 | + |
| 41 | + const getAriaCheckedValue = (): AriaAttributes['aria-checked'] => { |
| 42 | + if (!isChecked) { |
| 43 | + return false |
| 44 | + } |
| 45 | + |
| 46 | + return indeterminate ? 'mixed' : true |
| 47 | + } |
| 48 | + |
| 49 | + const ariaCheckedValue = getAriaCheckedValue() |
| 50 | + |
| 51 | + const showIndeterminateIcon = ariaCheckedValue === 'mixed' |
| 52 | + |
| 53 | + const renderContent = () => ( |
| 54 | + <motion.span |
| 55 | + className={`flex flex-grow-1 ${getThumbPadding({ shape, isLoading })} ${getThumbPosition({ isChecked, isLoading })}`} |
| 56 | + layout |
| 57 | + transition={{ ease: 'easeInOut', duration: 0.2 }} |
| 58 | + > |
| 59 | + {isLoading ? ( |
| 60 | + <motion.span |
| 61 | + transition={{ ease: 'easeInOut', duration: 0.2 }} |
| 62 | + layoutId={`${name}-loader`} |
| 63 | + className="flex-grow-1 h-100 dc__fill-available-space dc__no-shrink" |
| 64 | + > |
| 65 | + <Icon name="ic-circle-loader" color={LOADING_COLOR_MAP[variant]} size={null} /> |
| 66 | + </motion.span> |
| 67 | + ) : ( |
| 68 | + <motion.span |
| 69 | + layoutId={`${name}-thumb`} |
| 70 | + className={getSwitchThumbClass({ shape, size, showIndeterminateIcon })} |
| 71 | + layout |
| 72 | + transition={{ ease: 'easeInOut', duration: 0.2 }} |
| 73 | + > |
| 74 | + <AnimatePresence> |
| 75 | + {showIndeterminateIcon ? ( |
| 76 | + <motion.span |
| 77 | + className={`${INDETERMINATE_ICON_WIDTH_MAP[size]} h-2 br-4 dc__no-shrink bg__white`} |
| 78 | + initial={{ scale: 0, opacity: 0 }} |
| 79 | + animate={{ scale: 1, opacity: 1 }} |
| 80 | + exit={{ scale: 0, opacity: 0 }} |
| 81 | + /> |
| 82 | + ) : ( |
| 83 | + iconName && ( |
| 84 | + <motion.span |
| 85 | + className="icon-dim-12 flex dc__fill-available-space dc__no-shrink" |
| 86 | + initial={{ scale: 0.8, opacity: 0 }} |
| 87 | + animate={{ scale: 1, opacity: 1 }} |
| 88 | + exit={{ scale: 0.8, opacity: 0 }} |
| 89 | + > |
| 90 | + <Icon |
| 91 | + name={iconName} |
| 92 | + color={getSwitchIconColor({ |
| 93 | + isChecked, |
| 94 | + iconColor, |
| 95 | + variant, |
| 96 | + })} |
| 97 | + size={null} |
| 98 | + /> |
| 99 | + </motion.span> |
| 100 | + ) |
| 101 | + )} |
| 102 | + </AnimatePresence> |
| 103 | + </motion.span> |
| 104 | + )} |
| 105 | + </motion.span> |
| 106 | + ) |
| 107 | + |
| 108 | + return ( |
| 109 | + <Tooltip alwaysShowTippyOnHover={!!tooltipContent} content={tooltipContent}> |
| 110 | + <label |
| 111 | + htmlFor={inputId.current} |
| 112 | + className={`${getSwitchContainerClass({ shape, size })} flex dc__no-shrink py-2 m-0`} |
| 113 | + > |
| 114 | + <input |
| 115 | + type="checkbox" |
| 116 | + id={inputId.current} |
| 117 | + name={name} |
| 118 | + checked={isChecked} |
| 119 | + disabled={isDisabled} |
| 120 | + readOnly |
| 121 | + hidden |
| 122 | + /> |
| 123 | + |
| 124 | + <button |
| 125 | + type="button" |
| 126 | + role="checkbox" |
| 127 | + aria-checked={ariaCheckedValue} |
| 128 | + aria-labelledby={inputId.current} |
| 129 | + aria-label={isLoading ? 'Loading...' : ariaLabel} |
| 130 | + data-testid={dataTestId} |
| 131 | + disabled={isDisabled || isLoading} |
| 132 | + aria-disabled={isDisabled} |
| 133 | + className={`p-0-imp h-100 flex flex-grow-1 dc__no-border dt-switch__track ${shape === 'rounded' ? 'br-12' : 'br-4'} ${getSwitchTrackColor({ shape, variant, isChecked, isLoading })} ${isDisabled ? 'dc__disabled' : ''} dc__fill-available-space`} |
| 134 | + onClick={onChange} |
| 135 | + style={{ |
| 136 | + // Adding hover styles directly to the button |
| 137 | + ['--switch-track-hover-color' as string]: getSwitchTrackHoverColor({ |
| 138 | + shape, |
| 139 | + variant, |
| 140 | + isChecked, |
| 141 | + }), |
| 142 | + }} |
| 143 | + > |
| 144 | + {renderContent()} |
| 145 | + </button> |
| 146 | + </label> |
| 147 | + </Tooltip> |
| 148 | + ) |
| 149 | +} |
| 150 | + |
| 151 | +export default Switch |
0 commit comments