diff --git a/docs/content/3.components/select-menu.md b/docs/content/3.components/select-menu.md
index 82f711f575..f02b39f014 100644
--- a/docs/content/3.components/select-menu.md
+++ b/docs/content/3.components/select-menu.md
@@ -457,6 +457,71 @@ You can customize this icon globally in your `vite.config.ts` under `ui.icons.ch
:::
::
+### Clear :badge{label="Not released" class="align-text-top"}
+
+Use the `clear` prop to add a clear icon to reset the model value.
+
+::component-code
+---
+prettier: true
+ignore:
+ - items
+ - modelValue
+ - class
+external:
+ - items
+ - modelValue
+props:
+ modelValue: 'Backlog'
+ clear: true
+ items:
+ - Backlog
+ - Todo
+ - In Progress
+ - Done
+ class: 'w-48'
+---
+::
+
+### Clear Icon :badge{label="Not released" class="align-text-top"}
+
+Use the `clear-icon` prop to customize the clear icon. Defaults to `i-lucide-x`.
+
+::component-code
+---
+prettier: true
+ignore:
+ - items
+ - modelValue
+ - class
+external:
+ - items
+ - modelValue
+props:
+ modelValue: 'Backlog'
+ clear: true
+ clearIcon: 'i-lucide-trash'
+ items:
+ - Backlog
+ - Todo
+ - In Progress
+ - Done
+ class: 'w-48'
+---
+::
+
+::framework-only
+#nuxt
+:::tip{to="/getting-started/icons/nuxt#theme"}
+You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key.
+:::
+
+#vue
+:::tip{to="/getting-started/icons/vue#theme"}
+You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key.
+:::
+::
+
### Selected Icon
Use the `selected-icon` prop to customize the icon when an item is selected. Defaults to `i-lucide-check`.
diff --git a/playground/app/pages/components/select-menu.vue b/playground/app/pages/components/select-menu.vue
index b4797ce4a4..4b0d89bb5c 100644
--- a/playground/app/pages/components/select-menu.vue
+++ b/playground/app/pages/components/select-menu.vue
@@ -14,6 +14,7 @@ const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]] satisfies SelectMenuItem[][]
const selectedItems = ref([fruits[0]!, vegetables[0]!])
+const selectedItem = ref(fruits[0]!)
const statuses = [{
label: 'Backlog',
@@ -91,6 +92,8 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
+
+
@@ -100,6 +103,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
:items="items"
placeholder="Search..."
:size="size"
+ clear
class="w-48"
/>
diff --git a/src/runtime/components/SelectMenu.vue b/src/runtime/components/SelectMenu.vue
index 12426d5570..f0a3078189 100644
--- a/src/runtime/components/SelectMenu.vue
+++ b/src/runtime/components/SelectMenu.vue
@@ -51,6 +51,16 @@ export interface SelectMenuProps = Array
*/
size?: SelectMenu['variants']['size']
required?: boolean
+ /**
+ * Determines if user can clear the `modelValue` with icon click
+ * @defaultValue false
+ */
+ clear?: boolean
+ /**
+ * The icon displayed to clear the value.
+ * @defaultValue appConfig.ui.icons.close
+ */
+ clearIcon?: string
/**
* The icon displayed to open the menu.
* @defaultValue appConfig.ui.icons.chevronDown
@@ -185,6 +195,7 @@ defineOptions({ inheritAttrs: false })
const props = withDefaults(defineProps>(), {
portal: true,
searchInput: true,
+ clear: false,
labelKey: 'label' as never,
resetSearchTermOnBlur: true,
resetSearchTermOnSelect: true
@@ -236,6 +247,13 @@ function displayValue(value: GetItemValue | GetItemValue[]): strin
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
}
+const isEmpty = computed(() => {
+ if (Array.isArray(props.modelValue)) {
+ return props.modelValue.length === 0
+ }
+ return !(props.modelValue)
+})
+
const groups = computed(() =>
props.items?.length
? isArrayOfArray(props.items)
@@ -338,6 +356,11 @@ function onSelect(e: Event, item: SelectMenuItem) {
item.onSelect?.(e)
}
+function onClear() {
+ const newValue = props.multiple ? [] : null
+ emits('update:modelValue', newValue as GetModelValue)
+}
+
function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
return typeof item === 'object' && item !== null
}
@@ -394,7 +417,18 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
-
+
+
diff --git a/src/theme/select-menu.ts b/src/theme/select-menu.ts
index c8fa433186..0746be8f0a 100644
--- a/src/theme/select-menu.ts
+++ b/src/theme/select-menu.ts
@@ -7,6 +7,7 @@ export default (options: Required) => {
slots: {
input: 'border-b border-default',
focusScope: 'flex flex-col min-h-0',
+ clearIcon: ['hover:text-default', options.theme.transitions && 'transition-colors'],
content: (content: string) => [content, 'origin-(--reka-combobox-content-transform-origin) w-(--reka-combobox-trigger-width)']
}
}, select(options))
diff --git a/test/components/SelectMenu.spec.ts b/test/components/SelectMenu.spec.ts
index 8c3be877e4..ade58c7032 100644
--- a/test/components/SelectMenu.spec.ts
+++ b/test/components/SelectMenu.spec.ts
@@ -50,6 +50,7 @@ describe('SelectMenu', () => {
['without searchInput', { props: { ...props, searchInput: false } }],
['with searchInput placeholder', { props: { ...props, searchInput: { placeholder: 'Filter items...' } } }],
['with searchInput icon', { props: { ...props, searchInput: { icon: 'i-lucide-search' } } }],
+ ['with clear', { props: { clear: true } }],
['with disabled', { props: { ...props, disabled: true } }],
['with required', { props: { ...props, required: true } }],
['with icon', { props: { icon: 'i-lucide-search' } }],
diff --git a/test/components/__snapshots__/SelectMenu.spec.ts.snap b/test/components/__snapshots__/SelectMenu.spec.ts.snap
index 38b6750c0a..6c639794c7 100644
--- a/test/components/__snapshots__/SelectMenu.spec.ts.snap
+++ b/test/components/__snapshots__/SelectMenu.spec.ts.snap
@@ -164,6 +164,15 @@ exports[`SelectMenu > renders with class correctly 1`] = `
"
`;
+exports[`SelectMenu > renders with clear correctly 1`] = `
+"
+
+
+"
+`;
+
exports[`SelectMenu > renders with create-item-label slot correctly 1`] = `
"