forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1<script setup lang="ts">
2import TooltipApp from '~/components/Tooltip/App.vue'
3
4const props = withDefaults(
5 defineProps<{
6 label: string
7 description?: string
8 justify?: 'between' | 'start'
9 tooltip?: string
10 tooltipPosition?: 'top' | 'bottom' | 'left' | 'right'
11 tooltipTo?: string
12 tooltipOffset?: number
13 reverseOrder?: boolean
14 }>(),
15 {
16 justify: 'between',
17 reverseOrder: false,
18 },
19)
20
21const checked = defineModel<boolean>({
22 required: true,
23})
24const id = useId()
25</script>
26
27<template>
28 <label
29 :for="id"
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 "
37 >
38 <template v-if="props.reverseOrder">
39 <input
40 role="switch"
41 type="checkbox"
42 :id
43 v-model="checked"
44 class="toggle appearance-none h-6 w-11 rounded-full border border-fg relative shrink-0 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"
46 />
47 <TooltipApp
48 v-if="tooltip && label"
49 :text="tooltip"
50 :position="tooltipPosition ?? 'top'"
51 :to="tooltipTo"
52 :offset="tooltipOffset"
53 >
54 <span class="text-sm text-fg font-medium text-start" style="grid-area: label-text">
55 {{ label }}
56 </span>
57 </TooltipApp>
58 <span
59 v-else-if="label"
60 class="text-sm text-fg font-medium text-start"
61 style="grid-area: label-text"
62 >
63 {{ label }}
64 </span>
65 </template>
66 <template v-else>
67 <TooltipApp
68 v-if="tooltip && label"
69 :text="tooltip"
70 :position="tooltipPosition ?? 'top'"
71 :to="tooltipTo"
72 :offset="tooltipOffset"
73 >
74 <span class="text-sm text-fg font-medium text-start" style="grid-area: label-text">
75 {{ label }}
76 </span>
77 </TooltipApp>
78 <span
79 v-else-if="label"
80 class="text-sm text-fg font-medium text-start"
81 style="grid-area: label-text"
82 >
83 {{ label }}
84 </span>
85 <input
86 role="switch"
87 type="checkbox"
88 :id
89 v-model="checked"
90 class="toggle appearance-none h-6 w-11 rounded-full border border-fg relative shrink-0 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"
92 />
93 </template>
94 </label>
95 <p v-if="description" class="text-sm text-fg-muted mt-2">
96 {{ description }}
97 </p>
98</template>
99
100<style scoped>
101/* Thumb position: logical property for RTL support */
102.toggle::before {
103 inset-inline-start: 1px;
104}
105
106/* Track transition */
107.toggle {
108 transition:
109 background-color 200ms ease-in-out,
110 border-color 100ms ease-in-out;
111}
112
113.toggle::before {
114 transition:
115 background-color 200ms ease-in-out,
116 translate 200ms ease-in-out;
117}
118
119/* Hover states */
120.toggle:hover:not(:checked) {
121 background: var(--fg-muted);
122}
123
124.toggle:checked:hover {
125 background: var(--fg-muted);
126 border-color: var(--fg-muted);
127}
128
129/* RTL-aware checked thumb position */
130:dir(ltr) .toggle:checked::before {
131 translate: 20px;
132}
133
134:dir(rtl) .toggle:checked::before {
135 translate: -20px;
136}
137
138@media (prefers-reduced-motion: reduce) {
139 .toggle,
140 .toggle::before {
141 transition: none;
142 }
143}
144
145/* Support forced colors */
146@media (forced-colors: active) {
147 label > span {
148 background: Canvas;
149 color: Highlight;
150 forced-color-adjust: none;
151 }
152
153 label:has(.toggle:checked) > span {
154 background: Highlight;
155 color: Canvas;
156 }
157
158 .toggle::before {
159 forced-color-adjust: none;
160 background-color: Highlight;
161 }
162
163 .toggle,
164 .toggle:hover {
165 background: Canvas;
166 border-color: CanvasText;
167 }
168
169 .toggle:checked,
170 .toggle:checked:hover {
171 background: Highlight;
172 border-color: CanvasText;
173 }
174
175 .toggle:checked::before {
176 background: Canvas;
177 }
178}
179</style>