feat(command-dialog-fuse): add new Demo component for custom filter-search with useFuse

This commit is contained in:
hrynevych.romann 2024-01-17 20:45:59 +02:00
parent a906ca1883
commit 070a51647e
6 changed files with 289 additions and 6 deletions

View File

@ -31,6 +31,7 @@
"embla-carousel": "8.0.0-rc19", "embla-carousel": "8.0.0-rc19",
"embla-carousel-autoplay": "8.0.0-rc19", "embla-carousel-autoplay": "8.0.0-rc19",
"embla-carousel-vue": "8.0.0-rc19", "embla-carousel-vue": "8.0.0-rc19",
"fuse.js": "^7.0.0",
"lucide-vue-next": "^0.276.0", "lucide-vue-next": "^0.276.0",
"radix-vue": "^1.3.0", "radix-vue": "^1.3.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",

View File

@ -137,7 +137,106 @@ watch(CmdJ, (v) => {
</div> </div>
</template> </template>
``` ```
### Dialog with custom filter (Fuse.js)
<ComponentPreview name="CommandDialogUseFuseDemo" />
For this Demo we will use the [useFuse](https://vueuse.org/integrations/useFuse/) integration. Don't forget to install `fuse.js` and `@vueuse/integrations`:
```bash
pnpm install fuse.js @vueuse/integrations
```
Code Example:
```vue
<script setup lang="ts">
import { useMagicKeys } from '@vueuse/core'
import { ref, watch } from 'vue'
import { useFuse } from '@vueuse/integrations/useFuse'
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from '@/lib/registry/default/ui/command'
const open = ref(false)
const { Meta_J, Ctrl_J } = useMagicKeys({
passive: false,
onEventFired(e) {
if (e.key === 'j' && (e.metaKey || e.ctrlKey))
e.preventDefault()
},
})
watch([Meta_J, Ctrl_J], (v) => {
if (v[0] || v[1])
handleOpenChange()
})
function handleOpenChange() {
open.value = !open.value
}
function customFiltering(val: string[], term: string) {
const { results } = useFuse(term, val, {
matchAllWhenSearchEmpty: true,
fuseOptions: {
shouldSort: false,
},
})
return results.value?.map(v => v.item)
}
</script>
<template>
<div>
<p class="text-sm text-muted-foreground">
Press
<kbd
class="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100"
>
<span class="text-xs"></span>J
</kbd>
</p>
<CommandDialog v-model:open="open" :filter-function="customFiltering">
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem value="calendar">
Calendar
</CommandItem>
<CommandItem value="search-emoji">
Search Emoji
</CommandItem>
<CommandItem value="calculator">
Calculator
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem value="profile">
Profile
</CommandItem>
<CommandItem value="billing">
Billing
</CommandItem>
<CommandItem value="settings">
Settings
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
</div>
</template>
```
### Combobox ### Combobox
You can use the `<Command />` component as a combobox. See the [Combobox](/docs/components/combobox) page for more information. You can use the `<Command />` component as a combobox. See the [Combobox](/docs/components/combobox) page for more information.

View File

@ -0,0 +1,87 @@
<script setup lang="ts">
import { useMagicKeys } from '@vueuse/core'
import { ref, watch } from 'vue'
import { useFuse } from '@vueuse/integrations/useFuse'
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from '@/lib/registry/default/ui/command'
const open = ref(false)
const { Meta_M, Ctrl_M } = useMagicKeys({
passive: false,
onEventFired(e) {
if (e.key === 'm' && (e.metaKey || e.ctrlKey))
e.preventDefault()
},
})
watch([Meta_M, Ctrl_M], (v) => {
if (v[0] || v[1])
handleOpenChange()
})
function handleOpenChange() {
open.value = !open.value
}
function customFiltering(val: string[], term: string) {
const { results } = useFuse(term, val, {
matchAllWhenSearchEmpty: true,
fuseOptions: {
shouldSort: false,
},
})
return results.value?.map(v => v.item)
}
</script>
<template>
<div>
<p class="text-sm text-muted-foreground">
Press
<kbd
class="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100"
>
<span class="text-xs"></span>M
</kbd>
</p>
<CommandDialog v-model:open="open" :filter-function="customFiltering">
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem value="calendar">
Calendar
</CommandItem>
<CommandItem value="search-emoji">
Search Emoji
</CommandItem>
<CommandItem value="calculator">
Calculator
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem value="profile">
Profile
</CommandItem>
<CommandItem value="billing">
Billing
</CommandItem>
<CommandItem value="settings">
Settings
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
</div>
</template>

View File

@ -1,4 +1,5 @@
<script setup lang="ts" generic="T"> <script setup lang="ts" generic="T">
import { reactiveOmit } from '@vueuse/core'
import { useEmitAsProps } from 'radix-vue' import { useEmitAsProps } from 'radix-vue'
import type { DialogRootEmits, DialogRootProps } from 'radix-vue' import type { DialogRootEmits, DialogRootProps } from 'radix-vue'
import Command from './Command.vue' import Command from './Command.vue'
@ -16,7 +17,7 @@ const emitsAsProps = useEmitAsProps(emits)
</script> </script>
<template> <template>
<Dialog v-bind="{ ...props, ...emitsAsProps }"> <Dialog v-bind="{ ...reactiveOmit(props, 'filterFunction'), ...emitsAsProps }">
<DialogContent class="p-0 overflow-hidden shadow-lg"> <DialogContent class="p-0 overflow-hidden shadow-lg">
<Command :filter-function="props.filterFunction" class="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"> <Command :filter-function="props.filterFunction" class="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
<slot /> <slot />

View File

@ -0,0 +1,87 @@
<script setup lang="ts">
import { useMagicKeys } from '@vueuse/core'
import { ref, watch } from 'vue'
import { useFuse } from '@vueuse/integrations/useFuse'
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from '@/lib/registry/default/ui/command'
const open = ref(false)
const { Meta_M, Ctrl_M } = useMagicKeys({
passive: false,
onEventFired(e) {
if (e.key === 'm' && (e.metaKey || e.ctrlKey))
e.preventDefault()
},
})
watch([Meta_M, Ctrl_M], (v) => {
if (v[0] || v[1])
handleOpenChange()
})
function handleOpenChange() {
open.value = !open.value
}
function customFiltering(val: string[], term: string) {
const { results } = useFuse(term, val, {
matchAllWhenSearchEmpty: true,
fuseOptions: {
shouldSort: false,
},
})
return results.value?.map(v => v.item)
}
</script>
<template>
<div>
<p class="text-sm text-muted-foreground">
Press
<kbd
class="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100"
>
<span class="text-xs"></span>M
</kbd>
</p>
<CommandDialog v-model:open="open" :filter-function="customFiltering">
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem value="calendar">
Calendar
</CommandItem>
<CommandItem value="search-emoji">
Search Emoji
</CommandItem>
<CommandItem value="calculator">
Calculator
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem value="profile">
Profile
</CommandItem>
<CommandItem value="billing">
Billing
</CommandItem>
<CommandItem value="settings">
Settings
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
</div>
</template>

View File

@ -92,6 +92,9 @@ importers:
embla-carousel-vue: embla-carousel-vue:
specifier: 8.0.0-rc19 specifier: 8.0.0-rc19
version: 8.0.0-rc19(vue@3.4.8) version: 8.0.0-rc19(vue@3.4.8)
fuse.js:
specifier: ^7.0.0
version: 7.0.0
lucide-vue-next: lucide-vue-next:
specifier: ^0.276.0 specifier: ^0.276.0
version: 0.276.0(vue@3.4.8) version: 0.276.0(vue@3.4.8)
@ -182,7 +185,7 @@ importers:
version: 4.5.0(@types/node@20.8.10) version: 4.5.0(@types/node@20.8.10)
vitepress: vitepress:
specifier: ^1.0.0-rc.24 specifier: ^1.0.0-rc.24
version: 1.0.0-rc.24(@algolia/client-search@4.22.0)(@types/node@20.8.10)(postcss@8.4.33)(search-insights@2.13.0)(typescript@5.2.2) version: 1.0.0-rc.24(@algolia/client-search@4.22.0)(@types/node@20.8.10)(fuse.js@7.0.0)(postcss@8.4.33)(search-insights@2.13.0)(typescript@5.2.2)
vue-tsc: vue-tsc:
specifier: ^1.8.27 specifier: ^1.8.27
version: 1.8.27(typescript@5.2.2) version: 1.8.27(typescript@5.2.2)
@ -5193,7 +5196,7 @@ packages:
- '@vue/composition-api' - '@vue/composition-api'
- vue - vue
/@vueuse/integrations@10.5.0(focus-trap@7.5.4)(vue@3.4.8): /@vueuse/integrations@10.5.0(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.4.8):
resolution: {integrity: sha512-fm5sXLCK0Ww3rRnzqnCQRmfjDURaI4xMsx+T+cec0ngQqHx/JgUtm8G0vRjwtonIeTBsH1Q8L3SucE+7K7upJQ==} resolution: {integrity: sha512-fm5sXLCK0Ww3rRnzqnCQRmfjDURaI4xMsx+T+cec0ngQqHx/JgUtm8G0vRjwtonIeTBsH1Q8L3SucE+7K7upJQ==}
peerDependencies: peerDependencies:
async-validator: '*' async-validator: '*'
@ -5237,6 +5240,7 @@ packages:
'@vueuse/core': 10.5.0(vue@3.4.8) '@vueuse/core': 10.5.0(vue@3.4.8)
'@vueuse/shared': 10.5.0(vue@3.4.8) '@vueuse/shared': 10.5.0(vue@3.4.8)
focus-trap: 7.5.4 focus-trap: 7.5.4
fuse.js: 7.0.0
vue-demi: 0.14.6(vue@3.4.8) vue-demi: 0.14.6(vue@3.4.8)
transitivePeerDependencies: transitivePeerDependencies:
- '@vue/composition-api' - '@vue/composition-api'
@ -8423,6 +8427,10 @@ packages:
/function-bind@1.1.2: /function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
/fuse.js@7.0.0:
resolution: {integrity: sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==}
engines: {node: '>=10'}
/gauge@3.0.2: /gauge@3.0.2:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -14301,7 +14309,7 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true dev: true
/vitepress@1.0.0-rc.24(@algolia/client-search@4.22.0)(@types/node@20.8.10)(postcss@8.4.33)(search-insights@2.13.0)(typescript@5.2.2): /vitepress@1.0.0-rc.24(@algolia/client-search@4.22.0)(@types/node@20.8.10)(fuse.js@7.0.0)(postcss@8.4.33)(search-insights@2.13.0)(typescript@5.2.2):
resolution: {integrity: sha512-RpnL8cnOGwiRlBbrYQUm9sYkJbtyOt/wYXk2diTcokY4yvks/5lq9LuSt+MURWB6ZqwpSNHvTmxgaSfLoG0/OA==} resolution: {integrity: sha512-RpnL8cnOGwiRlBbrYQUm9sYkJbtyOt/wYXk2diTcokY4yvks/5lq9LuSt+MURWB6ZqwpSNHvTmxgaSfLoG0/OA==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -14319,7 +14327,7 @@ packages:
'@vitejs/plugin-vue': 4.3.1(vite@4.5.0)(vue@3.4.8) '@vitejs/plugin-vue': 4.3.1(vite@4.5.0)(vue@3.4.8)
'@vue/devtools-api': 6.5.1 '@vue/devtools-api': 6.5.1
'@vueuse/core': 10.5.0(vue@3.4.8) '@vueuse/core': 10.5.0(vue@3.4.8)
'@vueuse/integrations': 10.5.0(focus-trap@7.5.4)(vue@3.4.8) '@vueuse/integrations': 10.5.0(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.4.8)
focus-trap: 7.5.4 focus-trap: 7.5.4
mark.js: 8.11.1 mark.js: 8.11.1
minisearch: 6.1.0 minisearch: 6.1.0