Skip to content

Commit

Permalink
Start work on light/dark mode switch
Browse files Browse the repository at this point in the history
  • Loading branch information
dogmar committed Sep 20, 2023
1 parent 26f296b commit 2761ac5
Showing 1 changed file with 140 additions and 0 deletions.
140 changes: 140 additions & 0 deletions src/components/LightDarkSwitch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { useRef } from 'react'
import { useToggleState } from 'react-stately'
import {
type AriaSwitchProps,
VisuallyHidden,
useFocusRing,
useSwitch,
} from 'react-aria'
import styled from 'styled-components'

const SwitchSC = styled.label<{
$checked: boolean
$disabled: boolean
$readOnly: boolean
}>(({ $checked, $disabled, $readOnly, theme }) => ({
display: 'flex',
columnGap: theme.spacing.xsmall,
alignItems: 'center',
...theme.partials.text.body2,
cursor: $disabled ? 'not-allowed' : $readOnly ? 'default' : 'pointer',
color: theme.colors['text-light'],
...($disabled || $readOnly
? {}
: {
'&:hover': {
color: theme.colors.text,
[SwitchToggleSC]: {
backgroundColor: $checked
? theme.colors['action-primary-hover']
: theme.colors['action-input-hover'],
borderColor: $checked
? theme.colors['action-primary-hover']
: theme.colors['action-input-hover'],
},
[SwitchHandleSC]: {
backgroundColor: theme.colors['action-link-active'],
},
},
}),
}))

const SwitchToggleSC = styled.div<{
$disabled: boolean
$focused: boolean
$checked: boolean
}>(({ $checked, $focused, $disabled, theme }) => ({
position: 'relative',
width: 42,
height: 24,
borderRadius: 12,
backgroundColor: $checked
? $disabled
? theme.colors['action-primary-disabled']
: theme.colors['action-primary']
: 'transparent',
outlineWidth: 1,
outlineStyle: 'solid',
outlineOffset: -1,
outlineColor:
$disabled && $checked
? theme.colors['action-primary-disabled']
: $disabled
? theme.colors['border-disabled']
: $focused
? theme.colors['border-outline-focused']
: $checked
? theme.colors['action-primary']
: theme.colors['border-input'],
transition: 'all 0.15s ease',
}))

const SwitchHandleSC = styled.div<{ $checked: boolean; $disabled: boolean }>(
({ $checked, $disabled, theme }) => ({
backgroundColor: $disabled
? theme.colors['text-primary-disabled']
: theme.colors['fill-primary'],
border: `1px solid ${$disabled ? '#C5C9D3' : '#3C42CC'}`,
position: 'absolute',
width: 16,
height: 16,
borderRadius: '50%',
top: 4,
left: 4,
transform: `translateX(${$checked ? `${42 - 4 * 2 - 16}px` : 0})`,
transition: 'transform 0.15s ease',
})
)

export function LightDarkSwitch({
children,
checked,
disabled,
readOnly,
...props
}: Omit<
AriaSwitchProps & Parameters<typeof useToggleState>[0],
'isDisabled' | 'isReadonly' | 'isSelected'
> & { checked?: boolean; disabled?: boolean; readOnly?: boolean }) {
const ariaProps: AriaSwitchProps = {
isSelected: checked,
isDisabled: disabled,
isReadOnly: readOnly,
...props,
}
const state = useToggleState(ariaProps)
const ref = useRef<HTMLInputElement>(null)
const { inputProps, isSelected, isDisabled, isReadOnly } = useSwitch(
{ ...ariaProps },
state,
ref
)
const { focusProps, isFocusVisible } = useFocusRing()

return (
<SwitchSC
$disabled={isDisabled}
$checked={isSelected}
$readOnly={isReadOnly}
>
<VisuallyHidden>
<input
{...inputProps}
{...focusProps}
ref={ref}
/>
</VisuallyHidden>
<SwitchToggleSC
$focused={isFocusVisible}
$disabled={isDisabled}
$checked={isSelected}
>
<SwitchHandleSC
$disabled={isDisabled}
$checked={isSelected}
/>
</SwitchToggleSC>
<div className="label">{children}</div>
</SwitchSC>
)
}

0 comments on commit 2761ac5

Please sign in to comment.