mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-20 06:51:46 +01:00
feat(SelectMenu): add clearble
This commit is contained in:
@@ -1,23 +1,21 @@
|
||||
<template>
|
||||
<UContainer class="min-h-screen flex items-center">
|
||||
<UCard class="flex-1" :ui="{ background: 'bg-gray-50 dark:bg-gray-800/50', ring: 'ring-1 ring-gray-300 dark:ring-gray-700', divide: 'divide-y divide-gray-300 dark:divide-gray-700', header: { base: 'font-bold' } }">
|
||||
<template #header>
|
||||
Welcome to the playground!
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
Try your components here!
|
||||
</p>
|
||||
</UCard>
|
||||
</UContainer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const selected = ref()
|
||||
|
||||
const handleClose = () => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
@apply antialiased font-sans text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-900;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<UContainer>
|
||||
<USelectMenu
|
||||
v-model="selected"
|
||||
multiple
|
||||
clearable
|
||||
:options="people"
|
||||
placeholder="Select people"
|
||||
@clear="handleClose"
|
||||
/>
|
||||
</UContainer>
|
||||
</template>
|
||||
|
||||
@@ -41,8 +41,11 @@
|
||||
</slot>
|
||||
|
||||
<span v-if="(isTrailing && trailingIconName) || $slots.trailing" :class="trailingWrapperIconClass">
|
||||
<slot name="trailing" :selected="selected" :disabled="disabled" :loading="loading">
|
||||
<UIcon :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
|
||||
<slot
|
||||
name="trailing"
|
||||
v-bind="trailingSlotProps()"
|
||||
>
|
||||
<UIcon :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" @click="onClear" />
|
||||
</slot>
|
||||
</span>
|
||||
</button>
|
||||
@@ -332,9 +335,21 @@ export default defineComponent({
|
||||
uiMenu: {
|
||||
type: Object as PropType<DeepPartial<typeof configMenu> & { strategy?: Strategy }>,
|
||||
default: () => ({})
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
clearableIcon: {
|
||||
type: String,
|
||||
default: () => config.default.clerableIcon
|
||||
},
|
||||
closeOnClear: {
|
||||
type: Boolean,
|
||||
default: () => configMenu.default.closeOnClear
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'update:query', 'open', 'close', 'change'],
|
||||
emits: ['update:modelValue', 'update:query', 'open', 'close', 'change', 'clear'],
|
||||
setup(props, { emit, slots }) {
|
||||
if (import.meta.dev && props.multiple && !Array.isArray(props.modelValue)) {
|
||||
console.warn(`[@nuxt/ui] The USelectMenu components needs to have a modelValue of type Array when using the multiple prop. Got '${typeof props.modelValue}' instead.`, props.modelValue)
|
||||
@@ -431,7 +446,12 @@ export default defineComponent({
|
||||
return props.leadingIcon || props.icon
|
||||
})
|
||||
|
||||
const canClearValue = computed(() => props.clearable && (Array.isArray(selected.value) ? selected.value.length > 0 : !!selected.value))
|
||||
|
||||
const trailingIconName = computed(() => {
|
||||
if (canClearValue.value) {
|
||||
return props.clearableIcon
|
||||
}
|
||||
if (props.loading && !isLeading.value) {
|
||||
return props.loadingIcon
|
||||
}
|
||||
@@ -534,7 +554,7 @@ export default defineComponent({
|
||||
return ['string', 'number'].includes(typeof props.modelValue) ? query.value : { [props.optionAttribute]: query.value }
|
||||
})
|
||||
|
||||
function clearOnClose() {
|
||||
function handleClearSearchOnClose() {
|
||||
if (props.clearSearchOnClose) {
|
||||
query.value = ''
|
||||
}
|
||||
@@ -544,7 +564,7 @@ export default defineComponent({
|
||||
if (value) {
|
||||
emit('open')
|
||||
} else {
|
||||
clearOnClose()
|
||||
handleClearSearchOnClose()
|
||||
emit('close')
|
||||
emitFormBlur()
|
||||
}
|
||||
@@ -564,6 +584,31 @@ export default defineComponent({
|
||||
query.value = event.target.value
|
||||
}
|
||||
|
||||
function onClear(e: Event) {
|
||||
if (canClearValue.value) {
|
||||
if (!props.closeOnClear) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
emit('update:modelValue', props.multiple ? [] : null)
|
||||
emit('clear')
|
||||
emitFormChange()
|
||||
}
|
||||
}
|
||||
|
||||
function trailingSlotProps() {
|
||||
const slotProps: Record<string, any> = {
|
||||
selected: selected.value,
|
||||
loading: props.loading,
|
||||
disabled: props.disabled
|
||||
}
|
||||
|
||||
if (props.clearable) {
|
||||
slotProps.onClear = onClear
|
||||
}
|
||||
|
||||
return slotProps
|
||||
}
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
@@ -583,6 +628,7 @@ export default defineComponent({
|
||||
label,
|
||||
accessor,
|
||||
isLeading,
|
||||
onClear,
|
||||
isTrailing,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
selectClass,
|
||||
@@ -597,7 +643,8 @@ export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
query,
|
||||
onUpdate,
|
||||
onQueryChange
|
||||
onQueryChange,
|
||||
trailingSlotProps
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
color: 'white',
|
||||
variant: 'outline',
|
||||
loadingIcon: 'i-heroicons-arrow-path-20-solid',
|
||||
trailingIcon: 'i-heroicons-chevron-down-20-solid'
|
||||
trailingIcon: 'i-heroicons-chevron-down-20-solid',
|
||||
clerableIcon: 'i-heroicons-x-mark-20-solid'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export default {
|
||||
default: {
|
||||
selectedIcon: 'i-heroicons-check-20-solid',
|
||||
clearSearchOnClose: false,
|
||||
closeOnClear: true,
|
||||
showCreateOptionWhen: 'empty',
|
||||
searchablePlaceholder: {
|
||||
label: 'Search...'
|
||||
|
||||
Reference in New Issue
Block a user