Skip to content

Commit 47215cd

Browse files
refactor: extract toggle into reusable component (#560)
1 parent 8081bb6 commit 47215cd

File tree

3 files changed

+66
-109
lines changed

3 files changed

+66
-109
lines changed

app/components/Toggle.client.vue

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
label?: string
4+
description?: string
5+
}>()
6+
7+
const checked = defineModel<boolean>({
8+
default: false,
9+
})
10+
</script>
11+
12+
<template>
13+
<button
14+
type="button"
15+
class="w-full flex items-center justify-between gap-4 group"
16+
role="switch"
17+
:aria-checked="checked"
18+
@click="checked = !checked"
19+
>
20+
<span v-if="label" class="text-sm text-fg font-medium text-start">
21+
{{ label }}
22+
</span>
23+
<span
24+
class="relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out motion-reduce:transition-none shadow-sm cursor-pointer"
25+
:class="checked ? 'bg-accent' : 'bg-bg border border-border'"
26+
aria-hidden="true"
27+
>
28+
<span
29+
class="pointer-events-none inline-block h-5 w-5 rounded-full shadow-sm ring-0 transition-transform duration-200 ease-in-out motion-reduce:transition-none"
30+
:class="checked ? 'bg-bg' : 'bg-fg-muted'"
31+
/>
32+
</span>
33+
</button>
34+
<p v-if="description" class="text-sm text-fg-muted">
35+
{{ description }}
36+
</p>
37+
</template>
38+
39+
<style scoped>
40+
button[aria-checked='false'] > span:last-of-type > span {
41+
translate: 0;
42+
}
43+
button[aria-checked='true'] > span:last-of-type > span {
44+
translate: calc(100%);
45+
}
46+
html[dir='rtl'] button[aria-checked='true'] > span:last-of-type > span {
47+
translate: calc(-100%);
48+
}
49+
</style>
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
<script setup lang="ts">
22
defineProps<{
3-
label: string
3+
label?: string
4+
description?: string
45
}>()
56
</script>
67

78
<template>
89
<div class="w-full flex items-center justify-between gap-4">
9-
<span class="text-sm text-fg font-medium text-start">
10+
<span v-if="label" class="text-sm text-fg font-medium text-start">
1011
{{ label }}
1112
</span>
1213
<span class="skeleton block h-6 w-11 shrink-0 rounded-full" />
1314
</div>
15+
<p v-if="description" class="text-sm text-fg-muted">
16+
{{ description }}
17+
</p>
1418
</template>

app/pages/settings.vue

Lines changed: 11 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -100,114 +100,30 @@ defineOgImageComponent('Default', {
100100
</h2>
101101
<div class="bg-bg-subtle border border-border rounded-lg p-4 sm:p-6 space-y-4">
102102
<!-- Relative dates toggle -->
103-
<div class="space-y-2">
104-
<ClientOnly>
105-
<button
106-
type="button"
107-
class="w-full flex items-center justify-between gap-4 group"
108-
role="switch"
109-
:aria-checked="settings.relativeDates"
110-
@click="settings.relativeDates = !settings.relativeDates"
111-
>
112-
<span class="text-sm text-fg font-medium text-start">
113-
{{ $t('settings.relative_dates') }}
114-
</span>
115-
<span
116-
class="relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out motion-reduce:transition-none shadow-sm cursor-pointer"
117-
:class="settings.relativeDates ? 'bg-accent' : 'bg-bg border border-border'"
118-
aria-hidden="true"
119-
>
120-
<span
121-
class="pointer-events-none inline-block h-5 w-5 rounded-full shadow-sm ring-0 transition-transform duration-200 ease-in-out motion-reduce:transition-none"
122-
:class="settings.relativeDates ? 'bg-bg' : 'bg-fg-muted'"
123-
/>
124-
</span>
125-
</button>
126-
<template #fallback>
127-
<ToggleSkeleton :label="$t('settings.relative_dates')" />
128-
</template>
129-
</ClientOnly>
130-
<p class="text-sm text-fg-muted">
131-
{{ $t('settings.relative_dates_description') }}
132-
</p>
133-
</div>
103+
<Toggle :label="$t('settings.relative_dates')" v-model="settings.relativeDates" />
134104

135105
<!-- Divider -->
136106
<div class="border-t border-border" />
137107

138108
<!-- Include @types in install toggle -->
139109
<div class="space-y-2">
140-
<ClientOnly>
141-
<button
142-
type="button"
143-
class="w-full flex items-center justify-between gap-4 group"
144-
role="switch"
145-
:aria-checked="settings.includeTypesInInstall"
146-
@click="settings.includeTypesInInstall = !settings.includeTypesInInstall"
147-
>
148-
<span class="text-sm text-fg font-medium text-start">
149-
{{ $t('settings.include_types') }}
150-
</span>
151-
<span
152-
class="relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out motion-reduce:transition-none shadow-sm cursor-pointer"
153-
:class="
154-
settings.includeTypesInInstall ? 'bg-accent' : 'bg-bg border border-border'
155-
"
156-
aria-hidden="true"
157-
>
158-
<span
159-
class="pointer-events-none inline-block h-5 w-5 rounded-full shadow-sm ring-0 transition-transform duration-200 ease-in-out motion-reduce:transition-none"
160-
:class="settings.includeTypesInInstall ? 'bg-bg' : 'bg-fg-muted'"
161-
/>
162-
</span>
163-
</button>
164-
<template #fallback>
165-
<ToggleSkeleton :label="$t('settings.include_types')" />
166-
</template>
167-
</ClientOnly>
168-
<p class="text-sm text-fg-muted">
169-
{{ $t('settings.include_types_description') }}
170-
</p>
110+
<Toggle
111+
:label="$t('settings.include_types')"
112+
:description="$t('settings.include_types_description')"
113+
v-model="settings.includeTypesInInstall"
114+
/>
171115
</div>
172116

173117
<!-- Divider -->
174118
<div class="border-t border-border" />
175119

176120
<!-- Hide platform-specific packages toggle -->
177121
<div class="space-y-2">
178-
<ClientOnly>
179-
<button
180-
type="button"
181-
class="w-full flex items-center justify-between gap-4 group"
182-
role="switch"
183-
:aria-checked="settings.hidePlatformPackages"
184-
@click="settings.hidePlatformPackages = !settings.hidePlatformPackages"
185-
>
186-
<span class="text-sm text-fg font-medium text-start">
187-
{{ $t('settings.hide_platform_packages') }}
188-
</span>
189-
<span
190-
class="relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out motion-reduce:transition-none shadow-sm cursor-pointer"
191-
:class="
192-
settings.hidePlatformPackages ? 'bg-accent' : 'bg-bg border border-border'
193-
"
194-
aria-hidden="true"
195-
>
196-
<span
197-
class="pointer-events-none inline-block h-5 w-5 rounded-full shadow-sm ring-0 transition-transform duration-200 ease-in-out motion-reduce:transition-none"
198-
:class="settings.hidePlatformPackages ? 'bg-bg' : 'bg-fg-muted'"
199-
/>
200-
</span>
201-
</button>
202-
<template #fallback>
203-
<p class="text-sm text-fg-muted">
204-
{{ $t('settings.hide_platform_packages') }}
205-
</p>
206-
</template>
207-
</ClientOnly>
208-
<p class="text-sm text-fg-muted">
209-
{{ $t('settings.hide_platform_packages_description') }}
210-
</p>
122+
<Toggle
123+
:label="$t('settings.hide_platform_packages')"
124+
:description="$t('settings.hide_platform_packages')"
125+
v-model="settings.hidePlatformPackages"
126+
/>
211127
</div>
212128
</div>
213129
</section>
@@ -271,15 +187,3 @@ defineOgImageComponent('Default', {
271187
</article>
272188
</main>
273189
</template>
274-
275-
<style scoped>
276-
button[aria-checked='false'] > span:last-of-type > span {
277-
translate: 0;
278-
}
279-
button[aria-checked='true'] > span:last-of-type > span {
280-
translate: calc(100%);
281-
}
282-
html[dir='rtl'] button[aria-checked='true'] > span:last-of-type > span {
283-
translate: calc(-100%);
284-
}
285-
</style>

0 commit comments

Comments
 (0)