Skip to content

Commit dff2c03

Browse files
committed
add basic keyboard shortcuts popover
1 parent 5d279f7 commit dff2c03

File tree

4 files changed

+190
-3
lines changed

4 files changed

+190
-3
lines changed

app/components/AppFooter.vue

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
11
<script setup lang="ts">
2+
import { ref, onMounted, onBeforeUnmount } from 'vue'
3+
24
const route = useRoute()
35
const isHome = computed(() => route.name === 'index')
6+
7+
const triggerRef = ref<HTMLElement | null>(null)
8+
const popoverRef = ref<HTMLElement | null>(null)
9+
const showPopover = ref(false)
10+
11+
const togglePopover = (e?: Event) => {
12+
e?.stopPropagation()
13+
showPopover.value = !showPopover.value
14+
}
15+
16+
const onDocClick = (e: Event) => {
17+
const t = e.target as Node
18+
if (!popoverRef.value || !triggerRef.value) return
19+
if (!popoverRef.value.contains(t) && !triggerRef.value.contains(t)) {
20+
showPopover.value = false
21+
}
22+
}
23+
24+
const onKeydown = (e: KeyboardEvent) => {
25+
if (e.key === 'Escape') showPopover.value = false
26+
}
27+
28+
onMounted(() => {
29+
document.addEventListener('click', onDocClick)
30+
document.addEventListener('keydown', onKeydown)
31+
})
32+
onBeforeUnmount(() => {
33+
document.removeEventListener('click', onDocClick)
34+
document.removeEventListener('keydown', onKeydown)
35+
})
436
</script>
537

638
<template>
@@ -33,6 +65,116 @@ const isHome = computed(() => route.name === 'index')
3365
<LinkBase to="https://chat.npmx.dev">
3466
{{ $t('footer.chat') }}
3567
</LinkBase>
68+
69+
<button
70+
ref="triggerRef"
71+
type="button"
72+
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"
73+
@click.prevent="togglePopover"
74+
:aria-expanded="showPopover ? 'true' : 'false'"
75+
aria-haspopup="dialog"
76+
>
77+
{{ $t('footer.keyboard_shortcuts') }}
78+
</button>
79+
80+
<Teleport to="body">
81+
<Transition
82+
enter-active-class="transition-opacity duration-150 motion-reduce:transition-none"
83+
leave-active-class="transition-opacity duration-100 motion-reduce:transition-none"
84+
enter-from-class="opacity-0"
85+
leave-to-class="opacity-0"
86+
>
87+
<div v-if="showPopover">
88+
<div
89+
class="fixed inset-0 bg-bg-elevated/70 dark:bg-bg-elevated/90 z-40"
90+
@click="showPopover = false"
91+
aria-hidden="true"
92+
></div>
93+
94+
<div class="fixed inset-0 z-50 flex items-center justify-center">
95+
<div
96+
ref="popoverRef"
97+
class="mx-4 max-w-lg w-full p-6 bg-bg border border-border rounded-lg shadow-xl text-sm text-fg"
98+
role="dialog"
99+
:aria-label="$t('footer.keyboard_shortcuts')"
100+
>
101+
<div class="flex justify-between mb-8">
102+
<p class="m-0 font-mono text-fg-subtle">
103+
{{ $t('footer.keyboard_shortcuts') }}
104+
</p>
105+
<button
106+
class="text-xs text-link hover:underline flex items-center gap-2"
107+
@click="showPopover = false"
108+
>
109+
<span aria-hidden="true" class="size-5 i-lucide-x" />
110+
<span class="sr-only">{{ $t('close') }}</span>
111+
</button>
112+
</div>
113+
<p class="mb-2 font-mono text-fg-subtle">
114+
{{ $t('shortcuts.section.global') }}
115+
</p>
116+
<ul class="mb-8 flex flex-col gap-2">
117+
<li class="flex gap-2 items-center">
118+
<kbd
119+
class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2"
120+
>,</kbd
121+
><span>{{ $t('shortcuts.settings') }}</span>
122+
</li>
123+
<li class="flex gap-2 items-center">
124+
<kbd
125+
class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2"
126+
>c</kbd
127+
><span>{{ $t('shortcuts.compare') }}</span>
128+
</li>
129+
</ul>
130+
<p class="mb-2 font-mono text-fg-subtle">
131+
{{ $t('shortcuts.section.search') }}
132+
</p>
133+
<ul class="mb-8 flex flex-col gap-2">
134+
<li class="flex gap-2 items-center">
135+
<kbd
136+
class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2"
137+
>↑</kbd
138+
>/<kbd
139+
class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2"
140+
>↓</kbd
141+
><span>{{ $t('shortcuts.navigate_results') }}</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+
>Enter</kbd
147+
><span>{{ $t('shortcuts.go_to_result') }}</span>
148+
</li>
149+
</ul>
150+
<p class="mb-2 font-mono text-fg-subtle">
151+
{{ $t('shortcuts.section.package') }}
152+
</p>
153+
<ul class="mb-0 flex flex-col gap-2">
154+
<li class="flex gap-2 items-center">
155+
<kbd
156+
class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2"
157+
>.</kbd
158+
><span>{{ $t('shortcuts.open_code_view') }}</span>
159+
</li>
160+
<li class="flex gap-2 items-center">
161+
<kbd
162+
class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2"
163+
>d</kbd
164+
><span>{{ $t('shortcuts.open_docs') }}</span>
165+
</li>
166+
<li class="flex gap-2 items-center">
167+
<kbd
168+
class="items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2"
169+
>c</kbd
170+
><span>{{ $t('shortcuts.open_compare_prefilled') }}</span>
171+
</li>
172+
</ul>
173+
</div>
174+
</div>
175+
</div>
176+
</Transition>
177+
</Teleport>
36178
</div>
37179
</div>
38180
<p class="text-xs text-fg-muted text-center sm:text-start m-0">

i18n/locales/en.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,22 @@
1515
"docs": "docs",
1616
"source": "source",
1717
"social": "social",
18-
"chat": "chat"
18+
"chat": "chat",
19+
"keyboard_shortcuts": "keyboard shortcuts"
20+
},
21+
"shortcuts": {
22+
"section": {
23+
"global": "Global",
24+
"search": "Search",
25+
"package": "Package"
26+
},
27+
"settings": "Open settings",
28+
"compare": "Open compare",
29+
"navigate_results": "Navigate results",
30+
"go_to_result": "Go to result",
31+
"open_code_view": "Open code view",
32+
"open_docs": "Open docs",
33+
"open_compare_prefilled": "Open compare (prefilled with current package)"
1934
},
2035
"search": {
2136
"label": "Search npm packages",

lunaria/files/en-GB.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,22 @@
1515
"docs": "docs",
1616
"source": "source",
1717
"social": "social",
18-
"chat": "chat"
18+
"chat": "chat",
19+
"keyboard_shortcuts": "keyboard shortcuts"
20+
},
21+
"shortcuts": {
22+
"section": {
23+
"global": "Global",
24+
"search": "Search",
25+
"package": "Package"
26+
},
27+
"settings": "Open settings",
28+
"compare": "Open compare",
29+
"navigate_results": "Navigate results",
30+
"go_to_result": "Go to result",
31+
"open_code_view": "Open code view",
32+
"open_docs": "Open docs",
33+
"open_compare_prefilled": "Open compare (prefilled with current package)"
1934
},
2035
"search": {
2136
"label": "Search npm packages",

lunaria/files/en-US.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,22 @@
1515
"docs": "docs",
1616
"source": "source",
1717
"social": "social",
18-
"chat": "chat"
18+
"chat": "chat",
19+
"keyboard_shortcuts": "keyboard shortcuts"
20+
},
21+
"shortcuts": {
22+
"section": {
23+
"global": "Global",
24+
"search": "Search",
25+
"package": "Package"
26+
},
27+
"settings": "Open settings",
28+
"compare": "Open compare",
29+
"navigate_results": "Navigate results",
30+
"go_to_result": "Go to result",
31+
"open_code_view": "Open code view",
32+
"open_docs": "Open docs",
33+
"open_compare_prefilled": "Open compare (prefilled with current package)"
1934
},
2035
"search": {
2136
"label": "Search npm packages",

0 commit comments

Comments
 (0)