chore(CommandPalette): handle loading state (#221)

This commit is contained in:
Benjamin Canac
2023-05-22 16:00:31 +02:00
committed by GitHub
parent e7eea067b2
commit bdaf2dbbd4
9 changed files with 71 additions and 10 deletions

View File

@@ -184,6 +184,7 @@ export default defineAppConfig({
commandPalette: { commandPalette: {
default: { default: {
icon: 'i-octicon-search-24', icon: 'i-octicon-search-24',
loadingIcon: 'i-octicon-sync-24',
selectedIcon: 'i-octicon-check-24', selectedIcon: 'i-octicon-check-24',
empty: { empty: {
icon: 'i-octicon-search-24' icon: 'i-octicon-search-24'

View File

@@ -29,7 +29,7 @@ baseProps:
### Chip ### Chip
Use the `chipColor`, `chipVariant` and `chipPosition` props to display a chip on the Avatar. Use the `chip-color`, `chip-variant` and `chip-position` props to display a chip on the Avatar.
::component-card ::component-card
--- ---

View File

@@ -113,7 +113,7 @@ Button
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`. Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
Use the `leading` and `trailing` props to set the icon position or the `leadingIcon` and `trailingIcon` props to set a different icon for each position. Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
::component-card ::component-card
--- ---
@@ -163,7 +163,7 @@ Button
Use the `loading` prop to show a loading icon and disable the Button. Use the `loading` prop to show a loading icon and disable the Button.
Use the `loadingIcon` prop to set a different icon or change it globally in `ui.button.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`. Use the `loading-icon` prop to set a different icon or change it globally in `ui.button.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
::component-card ::component-card
--- ---

View File

@@ -56,7 +56,7 @@ props:
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`. Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
Use the `leading` and `trailing` props to set the icon position or the `leadingIcon` and `trailingIcon` props to set a different icon for each position. Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
::component-card ::component-card
--- ---
@@ -94,7 +94,7 @@ excludedProps:
Use the `loading` prop to show a loading icon and disable the Input. Use the `loading` prop to show a loading icon and disable the Input.
Use the `loadingIcon` prop to set a different icon or change it globally in `ui.input.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`. Use the `loading-icon` prop to set a different icon or change it globally in `ui.input.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
::component-card ::component-card
--- ---

View File

@@ -78,7 +78,7 @@ props:
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`. Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
Use the `trailingIcon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`. Use the `trailing-icon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
::component-card ::component-card
--- ---

View File

@@ -136,9 +136,9 @@ const selected = ref(people[0])
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`. Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
Use the `trailingIcon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`. Use the `trailing-icon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
Use the `selectedIcon` prop to set a different icon or change it globally in `ui.selectMenu.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`. Use the `selected-icon` prop to set a different icon or change it globally in `ui.selectMenu.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
::component-card ::component-card
--- ---

View File

@@ -160,7 +160,7 @@ function onSelect (option) {
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`. Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
Use the `selectedIcon` prop to set a different icon or change it globally in `ui.commandPalette.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`. Use the `selected-icon` prop to set a different icon or change it globally in `ui.commandPalette.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
::component-card ::component-card
--- ---
@@ -174,6 +174,24 @@ excludedProps:
--- ---
:: ::
### Loading
Use the `loading` prop to show a loading icon.
Use the `loading-icon` prop to set a different icon or change it globally in `ui.commandPalette.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
::component-card
---
padding: false
baseProps:
empty: null
props:
loading: true
excludedProps:
- icon
---
::
### Placeholder ### Placeholder
Use the `placeholder` prop to change the input placeholder Use the `placeholder` prop to change the input placeholder
@@ -218,6 +236,8 @@ Use the `empty` prop to display a message when there are no results.
You can pass an `object` through the `empty` prop or globally through `ui.commandPalette.default.empty`. Here is the default: You can pass an `object` through the `empty` prop or globally through `ui.commandPalette.default.empty`. Here is the default:
You can also set it to `null` to hide the empty label.
::component-card ::component-card
--- ---
padding: false padding: false
@@ -299,6 +319,10 @@ const groups = computed(() => {
``` ```
:: ::
::alert{icon="i-heroicons-light-bulb"}
The `loading` state will automatically be enabled when a `search` function is loading. You can disable this behaviour by setting the `loading-icon` prop to `null` or globally in `ui.commandPalette.default.loadingIcon`.
::
## Themes ## Themes
Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do. Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do.

View File

@@ -546,6 +546,7 @@ const commandPalette = {
}, },
default: { default: {
icon: 'i-heroicons-magnifying-glass-20-solid', icon: 'i-heroicons-magnifying-glass-20-solid',
loadingIcon: 'i-heroicons-arrow-path-20-solid',
empty: { empty: {
icon: 'i-heroicons-magnifying-glass-20-solid', icon: 'i-heroicons-magnifying-glass-20-solid',
label: 'We couldn\'t find any items.', label: 'We couldn\'t find any items.',

View File

@@ -8,7 +8,7 @@
> >
<div :class="ui.wrapper"> <div :class="ui.wrapper">
<div v-show="searchable" :class="ui.input.wrapper"> <div v-show="searchable" :class="ui.input.wrapper">
<UIcon v-if="icon" :name="icon" :class="[ui.input.icon.base, ui.input.icon.size]" aria-hidden="true" /> <UIcon v-if="iconName" :name="iconName" :class="iconClass" aria-hidden="true" />
<ComboboxInput <ComboboxInput
ref="comboboxInput" ref="comboboxInput"
:value="query" :value="query"
@@ -74,6 +74,7 @@ import type { Group, Command } from '../../types/command-palette'
import UIcon from '../elements/Icon.vue' import UIcon from '../elements/Icon.vue'
import UButton from '../elements/Button.vue' import UButton from '../elements/Button.vue'
import type { Button as ButtonType } from '../../types/button' import type { Button as ButtonType } from '../../types/button'
import { classNames } from '../../utils'
import CommandPaletteGroup from './CommandPaletteGroup.vue' import CommandPaletteGroup from './CommandPaletteGroup.vue'
import { useAppConfig } from '#imports' import { useAppConfig } from '#imports'
// TODO: Remove // TODO: Remove
@@ -112,6 +113,10 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: true default: true
}, },
loading: {
type: Boolean,
default: false
},
groups: { groups: {
type: Array as PropType<Group[]>, type: Array as PropType<Group[]>,
default: () => [] default: () => []
@@ -120,6 +125,10 @@ export default defineComponent({
type: String, type: String,
default: () => appConfig.ui.commandPalette.default.icon default: () => appConfig.ui.commandPalette.default.icon
}, },
loadingIcon: {
type: String,
default: () => appConfig.ui.commandPalette.default.loadingIcon
},
selectedIcon: { selectedIcon: {
type: String, type: String,
default: () => appConfig.ui.commandPalette.default.selectedIcon default: () => appConfig.ui.commandPalette.default.selectedIcon
@@ -175,6 +184,7 @@ export default defineComponent({
const query = ref('') const query = ref('')
const comboboxInput = ref<ComponentPublicInstance<HTMLInputElement>>() const comboboxInput = ref<ComponentPublicInstance<HTMLInputElement>>()
const comboboxApi = ref(null) const comboboxApi = ref(null)
const isLoading = ref(false)
onMounted(() => { onMounted(() => {
if (props.autoselect) { if (props.autoselect) {
@@ -231,10 +241,17 @@ export default defineComponent({
const debouncedSearch = useDebounceFn(async () => { const debouncedSearch = useDebounceFn(async () => {
const searchableGroups = props.groups.filter(group => !!group.search) const searchableGroups = props.groups.filter(group => !!group.search)
if (!searchableGroups.length) {
return
}
isLoading.value = true
await Promise.all(searchableGroups.map(async (group) => { await Promise.all(searchableGroups.map(async (group) => {
searchResults.value[group.key] = await group.search(query.value) searchResults.value[group.key] = await group.search(query.value)
})) }))
isLoading.value = false
}, props.debounce) }, props.debounce)
watch(query, () => { watch(query, () => {
@@ -247,6 +264,22 @@ export default defineComponent({
}, 0) }, 0)
}) })
const iconName = computed(() => {
if ((props.loading || isLoading.value) && props.loadingIcon) {
return props.loadingIcon
}
return props.icon
})
const iconClass = computed(() => {
return classNames(
ui.value.input.icon.base,
ui.value.input.icon.size,
((props.loading || isLoading.value) && props.loadingIcon) && 'animate-spin'
)
})
// Methods // Methods
function activateFirstOption () { function activateFirstOption () {
@@ -292,6 +325,8 @@ export default defineComponent({
groups, groups,
comboboxInput, comboboxInput,
query, query,
iconName,
iconClass,
onSelect, onSelect,
onClear onClear
} }