feat: add command palette

This commit is contained in:
Ahmed 2023-09-18 17:40:51 +01:00
parent 9a325676d0
commit 98217db270
4 changed files with 151 additions and 13 deletions

View File

@ -0,0 +1,37 @@
<script setup lang="ts">
import { cva } from 'class-variance-authority'
import { computed } from 'vue'
interface KbdProps {
as?: string
size?: 'xs' | 'sm' | 'md'
}
const props = withDefaults(defineProps<KbdProps>(), {
as: 'kbd',
size: 'sm',
})
const kbdClass = computed(() => {
return cva(
'inline-flex items-center pointer-events-none h-5 select-none items-center gap-1 rounded border border-border bg-muted font-sans font-medium',
{
variants: {
size: {
xs: 'min-h-[16px] text-[10px] h-4 px-1',
sm: 'min-h-[20px] text-[11px] h-5 px-1',
md: 'min-h-[24px] text-[12px] h-6 px-1.5',
},
},
},
)({
size: props.size,
})
})
</script>
<template>
<component :is="props.as" :class="kbdClass">
<slot />
</component>
</template>

View File

@ -1,30 +1,38 @@
<script setup lang="ts"> <script setup lang="ts">
import { useToggle } from '@vueuse/core' import { useToggle } from '@vueuse/core'
import { Content, useData, useRoute } from 'vitepress' import { Content, useData, useRoute, useRouter } from 'vitepress'
import { onMounted } from 'vue' import { onMounted, onUnmounted, ref } from 'vue'
import { SearchIcon } from 'lucide-vue-next'
import { docsConfig } from '../config/docs' import { docsConfig } from '../config/docs'
import Logo from '../components/Logo.vue' import Logo from '../components/Logo.vue'
import MobileNav from '../components/MobileNav.vue' import MobileNav from '../components/MobileNav.vue'
// import { Button } from '@/lib/registry/default/ui/button' import Kbd from '../components/Kbd.vue'
// import { Kbd } from '@/lib/registry/default/ui/kbd' import { Command, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/lib/registry/default/ui/command'
// import LucideSearch from '~icons/lucide/search'
import { Button } from '@/lib/registry/default/ui/button'
import RadixIconsGithubLogo from '~icons/radix-icons/github-logo' import RadixIconsGithubLogo from '~icons/radix-icons/github-logo'
import TablerBrandX from '~icons/tabler/brand-x' import TablerBrandX from '~icons/tabler/brand-x'
import RadixIconsMoon from '~icons/radix-icons/moon' import RadixIconsMoon from '~icons/radix-icons/moon'
import RadixIconsSun from '~icons/radix-icons/sun' import RadixIconsSun from '~icons/radix-icons/sun'
import { useConfigStore } from '@/stores/config' import { useConfigStore } from '@/stores/config'
import { Dialog, DialogContent } from '@/lib/registry/default/ui/dialog'
import File from '~icons/radix-icons/file'
import Circle from '~icons/radix-icons/circle'
const { radius, theme } = useConfigStore() const { radius, theme } = useConfigStore()
// Whenever the component is mounted, update the document class list // Whenever the component is mounted, update the document class list
onMounted(() => { onMounted(() => {
document.documentElement.style.setProperty('--radius', `${radius.value}rem`) document.documentElement.style.setProperty('--radius', `${radius.value}rem`)
document.documentElement.classList.add(`theme-${theme.value}`) document.documentElement.classList.add(`theme-${theme.value}`)
window.addEventListener('keydown', onKeyDown)
}) })
const { frontmatter, isDark } = useData() const { frontmatter, isDark } = useData()
const $route = useRoute() const $route = useRoute()
const $router = useRouter()
const toggleDark = useToggle(isDark) const toggleDark = useToggle(isDark)
const links = [ const links = [
@ -39,6 +47,27 @@ const links = [
icon: TablerBrandX, icon: TablerBrandX,
}, },
] ]
const isOpen = ref<boolean>(false)
function onKeyDown(event: KeyboardEvent) {
if (isOpen.value)
return
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
event.preventDefault()
isOpen.value = true
}
}
onUnmounted(() => {
window.removeEventListener('keydown', onKeyDown)
})
function openInNewTab(url: string | undefined) {
const win = window.open(url, '_blank')
win?.focus()
}
</script> </script>
<template> <template>
@ -71,19 +100,19 @@ const links = [
</div> </div>
<div class=" flex items-center justify-end space-x-4 "> <div class=" flex items-center justify-end space-x-4 ">
<!-- <Button <Button
variant="outline" variant="outline"
class="w-72 h-8 px-3 hidden lg:flex lg:justify-between lg:items-center" class="w-72 h-8 px-3 hidden lg:flex lg:justify-between lg:items-center"
@click="isOpen = true"
> >
<div class="flex items-center"> <div class="flex items-center">
<LucideSearch class="w-4 h-4 mr-2 text-muted-foreground" /> <SearchIcon class="w-4 h-4 mr-2 text-muted-foreground" />
<span class="text-muted-foreground"> Search for anything... </span> <span class="text-muted-foreground"> Search for anything... </span>
</div> </div>
<div class="flex items-center gap-x-1"> <div class="flex items-center gap-x-1">
<Kbd></Kbd> <Kbd> <span></span>K </Kbd>
<Kbd>K</Kbd>
</div> </div>
</Button> --> </Button>
<div <div
v-for="link in links" v-for="link in links"
@ -162,5 +191,77 @@ const links = [
</div> </div>
</div> </div>
</footer> </footer>
<Dialog v-model:open="isOpen">
<DialogContent class="p-0">
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandGroup heading="Links">
<CommandItem
v-for="item in docsConfig.mainNav"
:key="item.title"
:heading="item.title"
:value="item.title"
class="py-3"
@select="() => {
if (item.external) {
openInNewTab(item.href);
}
else {
$router.go(`${item.href}.html`);
}
isOpen = false;
}"
>
<File class="mr-2 h-5 w-5" />
<span>{{ item.title }}</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup v-for="item in docsConfig.sidebarNav" :key="item.title" :heading="item.title">
<CommandItem
v-for="subItem in item.items" :key="subItem.title" :heading="subItem.title" :value="subItem.title" class="py-3" @select="() => {
$router.go(`${subItem.href}.html`)
isOpen = false
}"
>
<Circle class="mr-2 h-5 w-5" />
<span>{{ subItem.title }}</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Theme">
<CommandItem
value="light-theme"
class="py-3"
@select="
() => {
isDark = false;
isOpen = false;
}
"
>
<RadixIconsSun class="mr-2 h-5 w-5" />
<span>Light Theme</span>
</CommandItem>
<CommandItem
value="dark-theme"
class="py-3"
@select="
() => {
isDark = true;
isOpen = false;
}
"
>
<RadixIconsMoon class="mr-2 h-5 w-5" />
<span>Dark Theme</span>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</DialogContent>
</Dialog>
</div> </div>
</template> </template>

View File

@ -11,7 +11,7 @@ const props = defineProps<ComboboxGroupProps & {
<template> <template>
<ComboboxGroup <ComboboxGroup
v-bind="props" v-bind="props"
:class="cn('overflow-hidden p-1 text-foreground', $attrs.class ?? '')" :class="cn('p-1 text-foreground', $attrs.class ?? '')"
> >
<ComboboxLabel v-if="heading" class="px-2 py-1.5 text-xs font-medium text-muted-foreground"> <ComboboxLabel v-if="heading" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
{{ heading }} {{ heading }}

View File

@ -33,9 +33,9 @@ const emitsAsProps = useEmitAsProps(emits)
<slot /> <slot />
<DialogClose <DialogClose
class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary" class="absolute top-3 right-3 p-0.5 transition-colors rounded-md hover:bg-secondary"
> >
<X class="w-4 h-4" /> <X class="w-4 h-4 text-muted-foreground" />
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
</DialogClose> </DialogClose>
</DialogContent> </DialogContent>