feat: add command palette (#59)
* feat: add command palette * fix: add command empty --------- Co-authored-by: zernonia <59365435+zernonia@users.noreply.github.com>
This commit is contained in:
parent
d5bb3a8a7a
commit
cc49dd59bf
37
apps/www/.vitepress/theme/components/Kbd.vue
Normal file
37
apps/www/.vitepress/theme/components/Kbd.vue
Normal 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>
|
||||||
|
|
@ -1,30 +1,39 @@
|
||||||
<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 { onMounted, watch } from 'vue'
|
import { onMounted, watch } from 'vue'
|
||||||
|
import { Content, useData, useRoute, useRouter } from 'vitepress'
|
||||||
|
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, CommandEmpty, 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 = [
|
||||||
|
|
@ -40,6 +49,27 @@ const links = [
|
||||||
// },
|
// },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
watch(() => $route.path, (n) => {
|
watch(() => $route.path, (n) => {
|
||||||
// @ts-expect-error View Transition API not supported by all the browser yet
|
// @ts-expect-error View Transition API not supported by all the browser yet
|
||||||
if (document.startViewTransition) {
|
if (document.startViewTransition) {
|
||||||
|
|
@ -81,19 +111,19 @@ watch(() => $route.path, (n) => {
|
||||||
</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"
|
||||||
|
|
@ -180,5 +210,80 @@ watch(() => $route.path, (n) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<Dialog v-model:open="isOpen">
|
||||||
|
<DialogContent class="p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Type a command or search..." />
|
||||||
|
<CommandEmpty>
|
||||||
|
No results found.
|
||||||
|
</CommandEmpty>
|
||||||
|
<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>
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user