| import { WritableAtom, useAtom } from 'jotai'; |
| import { RecoilState, useRecoilState } from 'recoil'; |
| import { Switch, InfoHoverCard, ESide } from '@librechat/client'; |
| import { useLocalize } from '~/hooks'; |
|
|
| type LocalizeFn = ReturnType<typeof useLocalize>; |
| type LocalizeKey = Parameters<LocalizeFn>[0]; |
|
|
| interface ToggleSwitchProps { |
| stateAtom: RecoilState<boolean> | WritableAtom<boolean, [boolean], void>; |
| localizationKey: LocalizeKey; |
| hoverCardText?: LocalizeKey; |
| switchId: string; |
| onCheckedChange?: (value: boolean) => void; |
| showSwitch?: boolean; |
| disabled?: boolean; |
| strongLabel?: boolean; |
| } |
|
|
| function isRecoilState<T>(atom: unknown): atom is RecoilState<T> { |
| return atom != null && typeof atom === 'object' && 'key' in atom; |
| } |
|
|
| const RecoilToggle: React.FC< |
| Omit<ToggleSwitchProps, 'stateAtom'> & { stateAtom: RecoilState<boolean> } |
| > = ({ |
| stateAtom, |
| localizationKey, |
| hoverCardText, |
| switchId, |
| onCheckedChange, |
| disabled = false, |
| strongLabel = false, |
| }) => { |
| const [switchState, setSwitchState] = useRecoilState(stateAtom); |
| const localize = useLocalize(); |
|
|
| const handleCheckedChange = (value: boolean) => { |
| setSwitchState(value); |
| onCheckedChange?.(value); |
| }; |
|
|
| const labelId = `${switchId}-label`; |
|
|
| return ( |
| <div className="flex items-center justify-between"> |
| <div className="flex items-center space-x-2"> |
| <div id={labelId}> |
| {strongLabel ? <strong>{localize(localizationKey)}</strong> : localize(localizationKey)} |
| </div> |
| {hoverCardText && <InfoHoverCard side={ESide.Bottom} text={localize(hoverCardText)} />} |
| </div> |
| <Switch |
| id={switchId} |
| checked={switchState} |
| onCheckedChange={handleCheckedChange} |
| disabled={disabled} |
| className="ml-4" |
| data-testid={switchId} |
| aria-labelledby={labelId} |
| /> |
| </div> |
| ); |
| }; |
|
|
| const JotaiToggle: React.FC< |
| Omit<ToggleSwitchProps, 'stateAtom'> & { stateAtom: WritableAtom<boolean, [boolean], void> } |
| > = ({ |
| stateAtom, |
| localizationKey, |
| hoverCardText, |
| switchId, |
| onCheckedChange, |
| disabled = false, |
| strongLabel = false, |
| }) => { |
| const [switchState, setSwitchState] = useAtom(stateAtom); |
| const localize = useLocalize(); |
|
|
| const handleCheckedChange = (value: boolean) => { |
| setSwitchState(value); |
| onCheckedChange?.(value); |
| }; |
|
|
| const labelId = `${switchId}-label`; |
|
|
| return ( |
| <div className="flex items-center justify-between"> |
| <div className="flex items-center space-x-2"> |
| <div id={labelId}> |
| {strongLabel ? <strong>{localize(localizationKey)}</strong> : localize(localizationKey)} |
| </div> |
| {hoverCardText && <InfoHoverCard side={ESide.Bottom} text={localize(hoverCardText)} />} |
| </div> |
| <Switch |
| id={switchId} |
| checked={switchState} |
| onCheckedChange={handleCheckedChange} |
| disabled={disabled} |
| className="ml-4" |
| data-testid={switchId} |
| aria-labelledby={labelId} |
| /> |
| </div> |
| ); |
| }; |
|
|
| const ToggleSwitch: React.FC<ToggleSwitchProps> = (props) => { |
| const { stateAtom, showSwitch = true } = props; |
|
|
| if (!showSwitch) { |
| return null; |
| } |
|
|
| const isRecoil = isRecoilState(stateAtom); |
|
|
| if (isRecoil) { |
| return <RecoilToggle {...props} stateAtom={stateAtom as RecoilState<boolean>} />; |
| } |
|
|
| return <JotaiToggle {...props} stateAtom={stateAtom as WritableAtom<boolean, [boolean], void>} />; |
| }; |
|
|
| export default ToggleSwitch; |
|
|