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`] = ` "