Skip to content

Commit 4bf2934

Browse files
committed
refactor: rewrite without extra element
1 parent ea8723f commit 4bf2934

File tree

2 files changed

+51
-89
lines changed

2 files changed

+51
-89
lines changed

app/components/Settings/Toggle.client.vue

Lines changed: 42 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -27,36 +27,39 @@ const id = useId()
2727
<template>
2828
<label
2929
:for="id"
30-
class="grid items-center gap-4 py-1 -my-1"
31-
:class="[
32-
justify === 'start' ? 'justify-start' : '',
33-
props.reverseOrder ? 'toggle-reverse' : 'toggle-default',
34-
]"
30+
class="grid items-center gap-4 py-1 -my-1 grid-cols-[auto_1fr_auto]"
31+
:class="[justify === 'start' ? 'justify-start' : '']"
32+
:style="
33+
props.reverseOrder
34+
? 'grid-template-areas: \'toggle . label-text\''
35+
: 'grid-template-areas: \'label-text . toggle\''
36+
"
3537
>
3638
<template v-if="props.reverseOrder">
3739
<input
3840
role="switch"
3941
type="checkbox"
4042
:id
41-
class="toggle-checkbox opacity-0"
42-
style="grid-row: 1; grid-column: 1; justify-self: start"
4343
v-model="checked"
44+
class="toggle appearance-none h-6 w-11 rounded-full border border-fg relative shrink-0 cursor-pointer bg-fg-subtle checked:bg-fg checked:border-fg focus-visible:(outline-2 outline-fg outline-offset-2) before:content-[''] before:absolute before:h-5 before:w-5 before:top-1px before:rounded-full before:bg-bg"
45+
style="grid-area: toggle"
4446
/>
45-
<span
46-
class="toggle-background h-6 w-11 rounded-full border border-fg relative flex shrink-0"
47-
></span>
4847
<TooltipApp
4948
v-if="tooltip && label"
5049
:text="tooltip"
5150
:position="tooltipPosition ?? 'top'"
5251
:to="tooltipTo"
5352
:offset="tooltipOffset"
5453
>
55-
<span class="text-sm text-fg font-medium text-start">
54+
<span class="text-sm text-fg font-medium text-start" style="grid-area: label-text">
5655
{{ label }}
5756
</span>
5857
</TooltipApp>
59-
<span v-else-if="label" class="text-sm text-fg font-medium text-start">
58+
<span
59+
v-else-if="label"
60+
class="text-sm text-fg font-medium text-start"
61+
style="grid-area: label-text"
62+
>
6063
{{ label }}
6164
</span>
6265
</template>
@@ -68,25 +71,25 @@ const id = useId()
6871
:to="tooltipTo"
6972
:offset="tooltipOffset"
7073
>
71-
<span class="text-sm text-fg font-medium text-start">
74+
<span class="text-sm text-fg font-medium text-start" style="grid-area: label-text">
7275
{{ label }}
7376
</span>
7477
</TooltipApp>
75-
<span v-else-if="label" class="text-sm text-fg font-medium text-start">
78+
<span
79+
v-else-if="label"
80+
class="text-sm text-fg font-medium text-start"
81+
style="grid-area: label-text"
82+
>
7683
{{ label }}
7784
</span>
7885
<input
7986
role="switch"
8087
type="checkbox"
8188
:id
82-
class="toggle-checkbox opacity-0"
83-
style="grid-row: 1; grid-column: 3; justify-self: end"
8489
v-model="checked"
90+
class="toggle appearance-none h-6 w-11 rounded-full border border-fg relative shrink-0 cursor-pointer bg-fg-subtle checked:bg-fg checked:border-fg focus-visible:(outline-2 outline-fg outline-offset-2) before:content-[''] before:absolute before:h-5 before:w-5 before:top-1px before:rounded-full before:bg-bg"
91+
style="grid-area: toggle; justify-self: end"
8592
/>
86-
<span
87-
class="toggle-background h-6 w-11 rounded-full border border-fg relative flex shrink-0"
88-
style="grid-area: toggle-background; justify-self: end"
89-
></span>
9093
</template>
9194
</label>
9295
<p v-if="description" class="text-sm text-fg-muted mt-2">
@@ -95,76 +98,45 @@ const id = useId()
9598
</template>
9699

97100
<style scoped>
98-
/* Layout: default order (label first, toggle last) */
99-
.toggle-default {
100-
grid-template-areas: 'label-text . toggle-background';
101-
grid-template-columns: auto 1fr auto;
102-
}
103-
104-
/* Layout: reverse order (toggle first, label last) */
105-
.toggle-reverse {
106-
grid-template-areas: 'toggle-background . label-text';
107-
grid-template-columns: auto 1fr auto;
101+
/* Thumb position: logical property for RTL support */
102+
.toggle::before {
103+
inset-inline-start: 1px;
108104
}
109105
110-
/* Track background */
111-
.toggle-background {
112-
background: var(--fg-subtle);
106+
/* Track transition */
107+
.toggle {
113108
transition:
114109
background-color 100ms ease-in,
115110
border-color 100ms ease-in;
116111
}
117112
118-
@media (prefers-reduced-motion: reduce) {
119-
.toggle-background {
120-
transition: none;
121-
}
122-
}
123-
124-
label:has(input:focus-visible) .toggle-background {
125-
outline: solid 2px var(--fg);
126-
outline-offset: 2px;
127-
}
128-
129-
label:has(input:checked) .toggle-background {
130-
background: var(--fg);
131-
border-color: var(--fg);
113+
.toggle::before {
114+
transition: translate 200ms ease-in-out;
132115
}
133116
134-
label:has(input:hover:not(:checked)) .toggle-background {
117+
/* Hover states */
118+
.toggle:hover:not(:checked) {
135119
background: var(--fg-muted);
136120
}
137121
138-
label:has(input:checked:hover) .toggle-background {
122+
.toggle:checked:hover {
139123
background: var(--fg-muted);
140124
border-color: var(--fg-muted);
141125
}
142126
143-
/* Circle that moves */
144-
.toggle-background::before {
145-
transition: translate 200ms ease-in-out;
146-
content: '';
147-
width: 20px;
148-
height: 20px;
149-
top: 1px;
150-
inset-inline-start: 1px;
151-
position: absolute;
152-
border-radius: 9999px;
153-
background: var(--bg);
127+
/* RTL-aware checked thumb position */
128+
:dir(ltr) .toggle:checked::before {
129+
translate: 20px;
130+
}
131+
132+
:dir(rtl) .toggle:checked::before {
133+
translate: -20px;
154134
}
155135
156136
@media (prefers-reduced-motion: reduce) {
157-
.toggle-background::before {
137+
.toggle,
138+
.toggle::before {
158139
transition: none;
159140
}
160141
}
161-
162-
/* Support rtl locales */
163-
:dir(ltr) input:checked + .toggle-background::before {
164-
translate: 20px;
165-
}
166-
167-
:dir(rtl) input:checked + .toggle-background::before {
168-
translate: -20px;
169-
}
170142
</style>

app/components/Settings/Toggle.server.vue

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@ const props = withDefaults(
1515

1616
<template>
1717
<div
18-
class="grid items-center gap-4 py-1 -my-1"
19-
:class="[
20-
justify === 'start' ? 'justify-start' : '',
21-
props.reverseOrder ? 'toggle-reverse' : 'toggle-default',
22-
]"
18+
class="grid items-center gap-4 py-1 -my-1 grid-cols-[auto_1fr_auto]"
19+
:class="[justify === 'start' ? 'justify-start' : '']"
20+
:style="
21+
props.reverseOrder
22+
? 'grid-template-areas: \'toggle . label-text\''
23+
: 'grid-template-areas: \'label-text . toggle\''
24+
"
2325
>
2426
<template v-if="props.reverseOrder">
25-
<SkeletonBlock class="h-6 w-11 shrink-0 rounded-full" style="grid-area: toggle-background" />
27+
<SkeletonBlock class="h-6 w-11 shrink-0 rounded-full" style="grid-area: toggle" />
2628
<span
2729
v-if="label"
2830
class="text-sm text-fg font-medium text-start"
@@ -41,23 +43,11 @@ const props = withDefaults(
4143
</span>
4244
<SkeletonBlock
4345
class="h-6 w-11 shrink-0 rounded-full"
44-
style="grid-area: toggle-background; justify-self: end"
46+
style="grid-area: toggle; justify-self: end"
4547
/>
4648
</template>
4749
</div>
4850
<p v-if="description" class="text-sm text-fg-muted mt-2">
4951
{{ description }}
5052
</p>
5153
</template>
52-
53-
<style scoped>
54-
.toggle-default {
55-
grid-template-areas: 'label-text . toggle-background';
56-
grid-template-columns: auto 1fr auto;
57-
}
58-
59-
.toggle-reverse {
60-
grid-template-areas: 'toggle-background . label-text';
61-
grid-template-columns: auto 1fr auto;
62-
}
63-
</style>

0 commit comments

Comments
 (0)