Skip to content

Commit 7034e74

Browse files
committed
Toggle switch icon
Can't just rely on color b/c a11y
1 parent fafca1f commit 7034e74

File tree

3 files changed

+40
-7
lines changed

3 files changed

+40
-7
lines changed
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
import { createHandler } from '~/shared/utility/callback-attrs/events';
22

3+
/** Data attribute for identifying which icon to show */
4+
export const TOGGLE_ICON_ATTR = 'data-toggle-icon';
5+
36
/**
47
* Synchronize the toggle switch's `aria-checked` attribute with the `checked` property.
58
* Kinda unclear whether this is necessary if underlying element *is* a checkbox
69
* but https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/switch_role
710
* says `aria-checked` is required.
11+
*
12+
* Also changes the icon
813
*/
914
export const toggleSwitchChange = createHandler('change', '$c-toggle-switch__change', (event) => {
10-
const target = event.currentTarget as HTMLElement;
11-
const checked = (target as HTMLInputElement).checked;
15+
const target = event.target as HTMLInputElement;
16+
const checked = target.checked;
1217
target.setAttribute('aria-checked', checked.toString());
18+
19+
// Assume this handler is on the container
20+
event.currentTarget
21+
?.querySelector<HTMLElement>(`[${TOGGLE_ICON_ATTR}='on']`)
22+
?.classList.toggle('t-hidden', !checked);
23+
event.currentTarget
24+
?.querySelector<HTMLElement>(`[${TOGGLE_ICON_ATTR}='off']`)
25+
?.classList.toggle('t-hidden', checked);
1326
});

src/shared/components/toggle-switch.css

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,25 @@
3535
}
3636

3737
.c-toggle-switch__thumb {
38+
@mixin v-colors-tooltip;
39+
3840
position: absolute;
3941
margin-inline: calc(0.5 * var(--v-space-sm) - var(--v-border-width));
4042
height: calc(var(--v-input-height) - var(--v-space-sm));
4143
width: calc(var(--v-input-height) - var(--v-space-sm));
4244
border-radius: 9999em;
43-
background-color: var(--v-input-fg);
45+
color: color-mix(in hsl, var(--v-fg), transparent);
46+
display: flex;
47+
align-items: center;
48+
justify-content: center;
4449

4550
@mixin t-transition transform;
4651
}
4752

4853
.c-toggle-switch:has(input:checked) .c-toggle-switch__thumb {
4954
transform: translateX(calc(0.75 * var(--v-input-height)));
5055
background-color: var(--v-primary-fg);
56+
color: var(--v-primary-bg);
5157
}
5258

5359
.c-toggle-switch:has([aria-invalid='true']) {

src/shared/components/toggle-switch.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import cx from 'classix';
2+
import { CheckIcon, CircleIcon } from 'lucide-solid';
23
import { splitProps } from 'solid-js';
34

45
import { checkboxClick, checkboxEnter } from '~/shared/components/callbacks/checkbox';
5-
import { toggleSwitchChange } from '~/shared/components/callbacks/toggle-switch';
6+
import { TOGGLE_ICON_ATTR, toggleSwitchChange } from '~/shared/components/callbacks/toggle-switch';
67
import {
78
type FormElementProps,
89
mergeFormElementProps,
@@ -34,16 +35,29 @@ export function ToggleSwitch(props: ToggleSwitchProps) {
3435
const formProps = mergeFormElementProps<'input'>(rest);
3536

3637
return (
37-
<div class={cx('c-toggle-switch', local.class)} {...callbackAttrs(checkboxClick)}>
38+
<div
39+
class={cx('c-toggle-switch', local.class)}
40+
{...callbackAttrs(checkboxClick, toggleSwitchChange)}
41+
>
3842
<input
3943
type="checkbox"
4044
role="switch"
4145
aria-checked={props.checked}
4246
aria-readonly={props.readOnly}
4347
{...formProps}
44-
{...callbackAttrs(props, checkboxEnter, toggleSwitchChange)}
48+
{...callbackAttrs(props, checkboxEnter)}
4549
/>
46-
<div class="c-toggle-switch__thumb" />
50+
<div class="c-toggle-switch__thumb">
51+
<CheckIcon
52+
{...{ [TOGGLE_ICON_ATTR]: 'on' }}
53+
class={cx(!props.checked && 't-hidden')}
54+
/>
55+
56+
<CircleIcon
57+
{...{ [TOGGLE_ICON_ATTR]: 'off' }}
58+
class={cx(props.checked && 't-hidden')}
59+
/>
60+
</div>
4761
</div>
4862
);
4963
}

0 commit comments

Comments
 (0)