mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-22 16:00:39 +01:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
948f4b89b1 | ||
|
|
e6d0dd5898 | ||
|
|
4702a4f103 | ||
|
|
efa9674815 |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -2,6 +2,18 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.1.0](https://github.com/nuxtlabs/ui/compare/v1.0.0...v1.1.0) (2023-02-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **CommandPalette:** handle async search for specific groups ([efa9674](https://github.com/nuxtlabs/ui/commit/efa9674815ab4de756079690da0a381c3703d564))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **CommandPalette:** types ([4702a4f](https://github.com/nuxtlabs/ui/commit/4702a4f10379201c167cc52099519778756a5780))
|
||||||
|
|
||||||
## [1.0.0](https://github.com/nuxtlabs/ui/compare/v0.2.1...v1.0.0) (2023-02-17)
|
## [1.0.0](https://github.com/nuxtlabs/ui/compare/v0.2.1...v1.0.0) (2023-02-17)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -149,18 +149,13 @@
|
|||||||
|
|
||||||
<UCard body-class="">
|
<UCard body-class="">
|
||||||
<UCommandPalette
|
<UCommandPalette
|
||||||
v-model="form.persons"
|
|
||||||
multiple
|
|
||||||
:placeholder="false"
|
:placeholder="false"
|
||||||
:options="{
|
:options="{
|
||||||
fuseOptions: {
|
fuseOptions: {
|
||||||
includeMatches: true
|
includeMatches: true
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
:groups="[{
|
:groups="commandPaletteGroups"
|
||||||
key: 'persons',
|
|
||||||
commands: people
|
|
||||||
}]"
|
|
||||||
command-attribute="name"
|
command-attribute="name"
|
||||||
/>
|
/>
|
||||||
</UCard>
|
</UCard>
|
||||||
@@ -263,6 +258,18 @@ const y = ref(0)
|
|||||||
const isContextMenuOpen = ref(false)
|
const isContextMenuOpen = ref(false)
|
||||||
const contextMenuRef = ref(null)
|
const contextMenuRef = ref(null)
|
||||||
|
|
||||||
|
const commandPaletteGroups = computed(() => ([{
|
||||||
|
key: 'people',
|
||||||
|
commands: people.value
|
||||||
|
}, {
|
||||||
|
key: 'search',
|
||||||
|
label: q => q && `Search results for "${q}"...`,
|
||||||
|
search: async (q) => {
|
||||||
|
if (!q) { return [] }
|
||||||
|
return await $fetch(`https://jsonplaceholder.typicode.com/users?q=${q}`)
|
||||||
|
}
|
||||||
|
}]))
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('mousemove', ({ clientX, clientY }) => {
|
document.addEventListener('mousemove', ({ clientX, clientY }) => {
|
||||||
x.value = clientX
|
x.value = clientX
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nuxthq/ui",
|
"name": "@nuxthq/ui",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"repository": "https://github.com/nuxtlabs/ui",
|
"repository": "https://github.com/nuxtlabs/ui",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -38,7 +38,14 @@
|
|||||||
aria-label="Commands"
|
aria-label="Commands"
|
||||||
class="relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2"
|
class="relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2"
|
||||||
>
|
>
|
||||||
<CommandPaletteGroup v-for="group of groups" :key="group.key" :group="group" :group-attribute="groupAttribute" :command-attribute="commandAttribute">
|
<CommandPaletteGroup
|
||||||
|
v-for="group of groups"
|
||||||
|
:key="group.key"
|
||||||
|
:query="query"
|
||||||
|
:group="group"
|
||||||
|
:group-attribute="groupAttribute"
|
||||||
|
:command-attribute="commandAttribute"
|
||||||
|
>
|
||||||
<template v-for="(_, name) in $slots" #[name]="slotData">
|
<template v-for="(_, name) in $slots" #[name]="slotData">
|
||||||
<slot :name="name" v-bind="slotData" />
|
<slot :name="name" v-bind="slotData" />
|
||||||
</template>
|
</template>
|
||||||
@@ -59,6 +66,7 @@
|
|||||||
import { ref, computed, watch, onMounted } from 'vue'
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
import { Combobox, ComboboxInput, ComboboxOptions } from '@headlessui/vue'
|
import { Combobox, ComboboxInput, ComboboxOptions } from '@headlessui/vue'
|
||||||
import type { ComputedRef, PropType, ComponentPublicInstance } from 'vue'
|
import type { ComputedRef, PropType, ComponentPublicInstance } from 'vue'
|
||||||
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import { useFuse } from '@vueuse/integrations/useFuse'
|
import { useFuse } from '@vueuse/integrations/useFuse'
|
||||||
import { groupBy, map } from 'lodash-es'
|
import { groupBy, map } from 'lodash-es'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
@@ -133,6 +141,10 @@ const props = defineProps({
|
|||||||
placeholder: {
|
placeholder: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
debounce: {
|
||||||
|
type: Number,
|
||||||
|
default: 200
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -168,29 +180,44 @@ const options: ComputedRef<Partial<UseFuseOptions<Command>>> = computed(() => de
|
|||||||
matchAllWhenSearchEmpty: true
|
matchAllWhenSearchEmpty: true
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const commands = computed(() => props.groups.reduce((acc, group) => {
|
const commands = computed(() => props.groups.filter(group => !group.search).reduce((acc, group) => {
|
||||||
return acc.concat(group.commands.map(command => ({ ...command, group: group.key })))
|
return acc.concat(group.commands.map(command => ({ ...command, group: group.key })))
|
||||||
}, [] as Command[]))
|
}, [] as Command[]))
|
||||||
|
|
||||||
|
const searchResults = ref<{ [key: string]: any }>({})
|
||||||
|
|
||||||
const { results } = useFuse(query, commands, options)
|
const { results } = useFuse(query, commands, options)
|
||||||
|
|
||||||
const groups = computed(() => map(groupBy(results.value, command => command.item.group), (results, key) => {
|
const groups = computed(() => ([
|
||||||
const commands = results.map((result) => {
|
...map(groupBy(results.value, command => command.item.group), (results, key) => {
|
||||||
const { item, ...data } = result
|
const commands = results.map((result) => {
|
||||||
|
const { item, ...data } = result
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...props.groups.find(group => group.key === key),
|
||||||
...data
|
commands: commands.slice(0, options.value.resultLimit)
|
||||||
}
|
} as Group
|
||||||
})
|
}),
|
||||||
|
...props.groups.filter(group => !!group.search).map(group => ({ ...group, commands: (searchResults.value[group.key] || []).slice(0, options.value.resultLimit) })).filter(group => group.commands.length)
|
||||||
|
]))
|
||||||
|
|
||||||
return {
|
const debouncedSearch = useDebounceFn(async () => {
|
||||||
...props.groups.find(group => group.key === key),
|
const searchableGroups = props.groups.filter(group => !!group.search)
|
||||||
commands: commands.slice(0, options.value.resultLimit)
|
|
||||||
} as Group
|
await Promise.all(searchableGroups.map(async (group) => {
|
||||||
}))
|
searchResults.value[group.key] = await group.search(query.value)
|
||||||
|
}))
|
||||||
|
}, props.debounce)
|
||||||
|
|
||||||
watch(query, () => {
|
watch(query, () => {
|
||||||
|
debouncedSearch()
|
||||||
|
|
||||||
// Select first item on search changes
|
// Select first item on search changes
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// https://github.com/tailwindlabs/headlessui/blob/6fa6074cd5d3a96f78a2d965392aa44101f5eede/packages/%40headlessui-vue/src/components/combobox/combobox.ts#L804
|
// https://github.com/tailwindlabs/headlessui/blob/6fa6074cd5d3a96f78a2d965392aa44101f5eede/packages/%40headlessui-vue/src/components/combobox/combobox.ts#L804
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-2" role="option">
|
<div class="p-2" role="option">
|
||||||
<h2 v-if="group[groupAttribute]" class="px-3 my-2 text-xs font-semibold u-text-gray-900">
|
<h2 v-if="label" class="px-3 my-2 text-xs font-semibold u-text-gray-900">
|
||||||
{{ group[groupAttribute] }}
|
{{ label }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="text-sm u-text-gray-700" role="listbox" :aria-label="group[groupAttribute]">
|
<div class="text-sm u-text-gray-700" role="listbox" :aria-label="group[groupAttribute]">
|
||||||
@@ -52,6 +52,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { ComboboxOption } from '@headlessui/vue'
|
import { ComboboxOption } from '@headlessui/vue'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import Icon from '../elements/Icon.vue'
|
import Icon from '../elements/Icon.vue'
|
||||||
@@ -59,11 +60,15 @@ import Avatar from '../elements/Avatar.vue'
|
|||||||
import type { Group } from '../../types/command-palette'
|
import type { Group } from '../../types/command-palette'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
group: {
|
group: {
|
||||||
type: Object as PropType<Group>,
|
type: Object as PropType<Group>,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
query: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
groupAttribute: {
|
groupAttribute: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
@@ -74,6 +79,12 @@ defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const label = computed(() => {
|
||||||
|
const label = props.group[props.groupAttribute]
|
||||||
|
|
||||||
|
return typeof label === 'function' ? label(props.query) : label
|
||||||
|
})
|
||||||
|
|
||||||
function highlight ({ indices, value }: { indices: number[][], value:string }, i = 1): string {
|
function highlight ({ indices, value }: { indices: number[][], value:string }, i = 1): string {
|
||||||
const pair = indices[indices.length - i]
|
const pair = indices[indices.length - i]
|
||||||
if (!pair) {
|
if (!pair) {
|
||||||
|
|||||||
Reference in New Issue
Block a user