|
1 | 1 | <script setup lang="ts"> |
2 | | -import { ref, watch, nextTick } from 'vue' |
3 | | -import { onClickOutside, onKeyDown } from '@vueuse/core' |
4 | | -import { useFocusTrap } from '@vueuse/integrations/useFocusTrap' |
| 2 | +import { ref, computed } from 'vue' |
| 3 | +import { onKeyDown } from '@vueuse/core' |
| 4 | +import Modal from './Modal.client.vue' |
5 | 5 |
|
6 | 6 | const route = useRoute() |
7 | 7 | const isHome = computed(() => route.name === 'index') |
8 | 8 |
|
9 | 9 | const triggerRef = ref<HTMLElement | null>(null) |
10 | | -const popoverRef = useTemplateRef<HTMLElement>('popoverRef') |
11 | | -const showPopover = ref(false) |
| 10 | +const modalRef = ref<any>(null) |
| 11 | +const modalOpen = ref(false) |
12 | 12 |
|
13 | 13 | const togglePopover = (e?: Event) => { |
14 | 14 | e?.stopPropagation() |
15 | | - showPopover.value = !showPopover.value |
| 15 | + if (!modalOpen.value) { |
| 16 | + modalRef.value?.showModal?.() |
| 17 | + modalOpen.value = true |
| 18 | + } else { |
| 19 | + modalRef.value?.close?.() |
| 20 | + modalOpen.value = false |
| 21 | + } |
16 | 22 | } |
17 | 23 |
|
18 | | -onClickOutside( |
19 | | - popoverRef, |
20 | | - () => { |
21 | | - showPopover.value = false |
22 | | - }, |
23 | | - { ignore: [triggerRef] }, |
24 | | -) |
25 | | -
|
26 | 24 | onKeyDown( |
27 | 25 | 'Escape', |
28 | 26 | (e: KeyboardEvent) => { |
29 | | - if (!showPopover.value) return |
| 27 | + if (!modalOpen.value) return |
30 | 28 | e.preventDefault() |
31 | 29 | e.stopImmediatePropagation() |
32 | | - showPopover.value = false |
| 30 | + modalRef.value?.close?.() |
| 31 | + modalOpen.value = false |
33 | 32 | }, |
34 | 33 | { dedupe: true }, |
35 | 34 | ) |
36 | 35 |
|
37 | | -const { activate, deactivate } = useFocusTrap(popoverRef, { allowOutsideClick: true }) |
38 | | -watch(showPopover, async open => { |
39 | | - if (open) { |
40 | | - await nextTick() |
41 | | - activate() |
42 | | - popoverRef.value?.focus?.() |
43 | | - } else { |
44 | | - deactivate() |
45 | | - triggerRef.value?.focus?.() |
46 | | - } |
47 | | -}) |
| 36 | +function onModalClosed() { |
| 37 | + modalOpen.value = false |
| 38 | + triggerRef.value?.focus?.() |
| 39 | +} |
48 | 40 | </script> |
49 | 41 |
|
50 | 42 | <template> |
@@ -83,112 +75,79 @@ watch(showPopover, async open => { |
83 | 75 | type="button" |
84 | 76 | class="group inline-flex gap-x-1 items-center justify-center underline-offset-[0.2rem] underline decoration-1 decoration-fg/30 font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200" |
85 | 77 | @click.prevent="togglePopover" |
86 | | - :aria-expanded="showPopover ? 'true' : 'false'" |
| 78 | + :aria-expanded="modalOpen ? 'true' : 'false'" |
87 | 79 | aria-haspopup="dialog" |
88 | 80 | > |
89 | 81 | {{ $t('footer.keyboard_shortcuts') }} |
90 | 82 | </button> |
91 | 83 |
|
92 | | - <Teleport to="body"> |
93 | | - <Transition |
94 | | - enter-active-class="transition-opacity duration-0 motion-reduce:transition-none" |
95 | | - leave-active-class="transition-opacity duration-0 motion-reduce:transition-none" |
96 | | - enter-from-class="opacity-0" |
97 | | - leave-to-class="opacity-0" |
98 | | - > |
99 | | - <div v-show="showPopover"> |
100 | | - <div |
101 | | - class="fixed inset-0 bg-bg-elevated/70 dark:bg-bg-elevated/90 z-[9998]" |
102 | | - @click="showPopover = false" |
103 | | - aria-hidden="true" |
104 | | - ></div> |
105 | | - |
106 | | - <div class="fixed inset-0 z-[9999] flex items-center justify-center"> |
107 | | - <div |
108 | | - ref="popoverRef" |
109 | | - tabindex="-1" |
110 | | - class="mx-4 max-w-lg w-full p-6 bg-bg border border-border rounded-lg shadow-xl text-sm text-fg" |
111 | | - role="dialog" |
112 | | - :aria-label="$t('footer.keyboard_shortcuts')" |
113 | | - > |
114 | | - <div class="flex justify-between mb-8"> |
115 | | - <p class="m-0 font-mono text-fg-subtle"> |
116 | | - {{ $t('footer.keyboard_shortcuts') }} |
117 | | - </p> |
118 | | - <button |
119 | | - class="text-xs text-link hover:underline flex items-center gap-2" |
120 | | - type="button" |
121 | | - @click="showPopover = false" |
122 | | - > |
123 | | - <span aria-hidden="true" class="size-5 i-lucide-x" /> |
124 | | - <span class="sr-only">{{ $t('common.close') }}</span> |
125 | | - </button> |
126 | | - </div> |
127 | | - <p class="mb-2 font-mono text-fg-subtle"> |
128 | | - {{ $t('shortcuts.section.global') }} |
129 | | - </p> |
130 | | - <ul class="mb-8 flex flex-col gap-2"> |
131 | | - <li class="flex gap-2 items-center"> |
132 | | - <kbd |
133 | | - class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
134 | | - >,</kbd |
135 | | - ><span>{{ $t('shortcuts.settings') }}</span> |
136 | | - </li> |
137 | | - <li class="flex gap-2 items-center"> |
138 | | - <kbd |
139 | | - class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
140 | | - >c</kbd |
141 | | - ><span>{{ $t('shortcuts.compare') }}</span> |
142 | | - </li> |
143 | | - </ul> |
144 | | - <p class="mb-2 font-mono text-fg-subtle"> |
145 | | - {{ $t('shortcuts.section.search') }} |
146 | | - </p> |
147 | | - <ul class="mb-8 flex flex-col gap-2"> |
148 | | - <li class="flex gap-2 items-center"> |
149 | | - <kbd |
150 | | - class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
151 | | - >↑</kbd |
152 | | - >/<kbd |
153 | | - class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
154 | | - >↓</kbd |
155 | | - ><span>{{ $t('shortcuts.navigate_results') }}</span> |
156 | | - </li> |
157 | | - <li class="flex gap-2 items-center"> |
158 | | - <kbd |
159 | | - class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
160 | | - >Enter</kbd |
161 | | - ><span>{{ $t('shortcuts.go_to_result') }}</span> |
162 | | - </li> |
163 | | - </ul> |
164 | | - <p class="mb-2 font-mono text-fg-subtle"> |
165 | | - {{ $t('shortcuts.section.package') }} |
166 | | - </p> |
167 | | - <ul class="mb-0 flex flex-col gap-2"> |
168 | | - <li class="flex gap-2 items-center"> |
169 | | - <kbd |
170 | | - class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
171 | | - >.</kbd |
172 | | - ><span>{{ $t('shortcuts.open_code_view') }}</span> |
173 | | - </li> |
174 | | - <li class="flex gap-2 items-center"> |
175 | | - <kbd |
176 | | - class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
177 | | - >d</kbd |
178 | | - ><span>{{ $t('shortcuts.open_docs') }}</span> |
179 | | - </li> |
180 | | - <li class="flex gap-2 items-center"> |
181 | | - <kbd |
182 | | - class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
183 | | - >c</kbd |
184 | | - ><span>{{ $t('shortcuts.open_compare_prefilled') }}</span> |
185 | | - </li> |
186 | | - </ul> |
187 | | - </div> |
188 | | - </div> |
189 | | - </div> |
190 | | - </Transition> |
191 | | - </Teleport> |
| 84 | + <Modal |
| 85 | + ref="modalRef" |
| 86 | + @close="onModalClosed" |
| 87 | + :modalTitle="$t('footer.keyboard_shortcuts')" |
| 88 | + class="w-auto max-w-lg" |
| 89 | + > |
| 90 | + <p class="mb-2 font-mono text-fg-subtle"> |
| 91 | + {{ $t('shortcuts.section.global') }} |
| 92 | + </p> |
| 93 | + <ul class="mb-6 flex flex-col gap-2"> |
| 94 | + <li class="flex gap-2 items-center"> |
| 95 | + <kbd |
| 96 | + class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
| 97 | + >,</kbd |
| 98 | + ><span>{{ $t('shortcuts.settings') }}</span> |
| 99 | + </li> |
| 100 | + <li class="flex gap-2 items-center"> |
| 101 | + <kbd |
| 102 | + class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
| 103 | + >c</kbd |
| 104 | + ><span>{{ $t('shortcuts.compare') }}</span> |
| 105 | + </li> |
| 106 | + </ul> |
| 107 | + <p class="mb-2 font-mono text-fg-subtle"> |
| 108 | + {{ $t('shortcuts.section.search') }} |
| 109 | + </p> |
| 110 | + <ul class="mb-6 flex flex-col gap-2"> |
| 111 | + <li class="flex gap-2 items-center"> |
| 112 | + <kbd |
| 113 | + class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
| 114 | + >↑</kbd |
| 115 | + >/<kbd |
| 116 | + class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
| 117 | + >↓</kbd |
| 118 | + ><span>{{ $t('shortcuts.navigate_results') }}</span> |
| 119 | + </li> |
| 120 | + <li class="flex gap-2 items-center"> |
| 121 | + <kbd |
| 122 | + class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
| 123 | + >Enter</kbd |
| 124 | + ><span>{{ $t('shortcuts.go_to_result') }}</span> |
| 125 | + </li> |
| 126 | + </ul> |
| 127 | + <p class="mb-2 font-mono text-fg-subtle"> |
| 128 | + {{ $t('shortcuts.section.package') }} |
| 129 | + </p> |
| 130 | + <ul class="mb-6 flex flex-col gap-2"> |
| 131 | + <li class="flex gap-2 items-center"> |
| 132 | + <kbd |
| 133 | + class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
| 134 | + >.</kbd |
| 135 | + ><span>{{ $t('shortcuts.open_code_view') }}</span> |
| 136 | + </li> |
| 137 | + <li class="flex gap-2 items-center"> |
| 138 | + <kbd |
| 139 | + class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
| 140 | + >d</kbd |
| 141 | + ><span>{{ $t('shortcuts.open_docs') }}</span> |
| 142 | + </li> |
| 143 | + <li class="flex gap-2 items-center"> |
| 144 | + <kbd |
| 145 | + class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2" |
| 146 | + >c</kbd |
| 147 | + ><span>{{ $t('shortcuts.open_compare_prefilled') }}</span> |
| 148 | + </li> |
| 149 | + </ul> |
| 150 | + </Modal> |
192 | 151 | </div> |
193 | 152 | </div> |
194 | 153 | <p class="text-xs text-fg-muted text-center sm:text-start m-0"> |
|
0 commit comments