Skip to content

Commit

Permalink
fix(Combobox): better support for input within content
Browse files Browse the repository at this point in the history
  • Loading branch information
zernonia committed Nov 28, 2024
1 parent cf512a7 commit eb600c2
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 4 deletions.
19 changes: 18 additions & 1 deletion packages/core/src/Combobox/ComboboxContentImpl.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const [injectComboboxContentContext, provideComboboxContentContext]
</script>

<script setup lang="ts">
import { computed, toRefs } from 'vue'
import { computed, onMounted, onUnmounted, ref, toRefs } from 'vue'
import { injectComboboxRootContext } from './ComboboxRoot.vue'
import { DismissableLayer } from '@/DismissableLayer'
import { PopperContent } from '@/Popper'
Expand Down Expand Up @@ -73,6 +73,23 @@ const popperStyle = {
}
provideComboboxContentContext({ position })
// Handle case where input position within the content
const isInputWithinContent = ref(false)
onMounted(() => {
if (rootContext.inputElement.value) {
isInputWithinContent.value = currentElement.value.contains(rootContext.inputElement.value)
if (isInputWithinContent.value) {
rootContext.inputElement.value.focus()
}
}
})
onUnmounted(() => {
if (isInputWithinContent.value) {
rootContext.triggerElement.value?.focus()
}
})
</script>

<template>
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/Combobox/ComboboxRoot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type ComboboxRootContext<T> = {
contentId: string
inputElement: Ref<HTMLInputElement | undefined>
onInputElementChange: (el: HTMLInputElement) => void
triggerElement: Ref<HTMLElement | undefined>
onTriggerElementChange: (el: HTMLElement) => void
highlightedElement: Ref<HTMLElement | undefined>
parentElement: Ref<HTMLElement | undefined>
onResetSearchTerm: EventHookOn
Expand Down Expand Up @@ -118,6 +120,7 @@ const resetSearchTerm = createEventHook()
const isUserInputted = ref(false)
const isVirtual = ref(false)
const inputElement = ref<HTMLInputElement>()
const triggerElement = ref<HTMLElement>()
const highlightedElement = computed(() => primitiveElement.value?.highlightedElement ?? undefined)
Expand Down Expand Up @@ -182,7 +185,7 @@ watch(() => open.value, () => {
}, { flush: 'post' })
defineExpose({
filterState,
filtered: computed(() => filterState.filtered),
highlightedElement,
highlightItem: primitiveElement.value?.highlightItem,
highlightFirstItem: primitiveElement.value?.highlightFirstItem,
Expand All @@ -201,6 +204,8 @@ provideComboboxRootContext({
inputElement,
highlightedElement,
onInputElementChange: val => inputElement.value = val,
triggerElement,
onTriggerElementChange: val => triggerElement.value = val,
parentElement,
onResetSearchTerm: resetSearchTerm.on,
allItems,
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/Combobox/ComboboxTrigger.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,27 @@ export interface ComboboxTriggerProps extends PrimitiveProps {
<script setup lang="ts">
import { Primitive } from '@/Primitive'
import { useForwardExpose } from '@/shared'
import { computed } from 'vue'
import { computed, onMounted } from 'vue'
import { injectComboboxRootContext } from './ComboboxRoot.vue'
const props = withDefaults(defineProps<ComboboxTriggerProps>(), {
as: 'button',
})
useForwardExpose()
const { forwardRef, currentElement } = useForwardExpose()
const rootContext = injectComboboxRootContext()
const disabled = computed(() => props.disabled || rootContext.disabled.value || false)
onMounted(() => {
if (currentElement.value)
rootContext.onTriggerElementChange(currentElement.value)
})
</script>

<template>
<Primitive
v-bind="props"
:ref="forwardRef"
:type="as === 'button' ? 'button' : undefined"
tabindex="-1"
aria-label="Show popup"
Expand Down
100 changes: 100 additions & 0 deletions packages/core/src/Combobox/story/ComboboxSelectMenu.story.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ComboboxAnchor, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxLabel, ComboboxRoot, ComboboxSeparator, ComboboxTrigger, ComboboxViewport } from '..'
import { Icon } from '@iconify/vue'
import type { GenericComponentInstance } from '@/shared/types'
const options = [
{ name: 'Fruit', children: [
{ name: 'Apple' },
{ name: 'Banana' },
{ name: 'Orange' },
{ name: 'Honeydew' },
{ name: 'Grapes' },
{ name: 'Watermelon' },
{ name: 'Cantaloupe' },
{ name: 'Pear' },
] },
{ name: 'Vegetable', children: [
{ name: 'Cabbage' },
{ name: 'Broccoli' },
{ name: 'Carrots' },
{ name: 'Lettuce' },
{ name: 'Spinach' },
{ name: 'Bok Choy' },
{ name: 'Cauliflower' },
{ name: 'Potatoes' },
] },
]
const comboboxRef = ref<GenericComponentInstance<typeof ComboboxRoot>>()
</script>

<template>
<Story
title="Combobox/SelectMenu"
:layout="{ type: 'single', iframe: false }"
>
<Variant title="default">
<ComboboxRoot
v-slot="{ modelValue }"
ref="comboboxRef"
>
<ComboboxAnchor
as-child
class="min-w-[160px] inline-flex items-center justify-between rounded px-[15px] text-[13px] leading-none h-[35px] gap-[5px] bg-white text-grass11 shadow-[0_2px_10px] shadow-black/10 hover:bg-mauve3 focus:shadow-[0_0_0_2px] focus:shadow-black data-[placeholder]:text-grass9 outline-none"
>
<ComboboxTrigger tabindex="0">
<span>{{ modelValue }}</span>
<Icon
icon="radix-icons:chevron-down"
class="h-4 w-4 text-grass11"
/>
</ComboboxTrigger>
</ComboboxAnchor>
<ComboboxContent class="mt-2 min-w-[160px] bg-white overflow-hidden rounded shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade">
<ComboboxViewport class="p-[5px]">
<ComboboxInput
class="bg-transparent outline-none text-grass11 placeholder-gray-400"
placeholder="Test"
/>

<ComboboxEmpty class="text-mauve8 text-xs font-medium text-center py-2" />

<template
v-for="(group, index) in options"
:key="group.name"
>
<ComboboxGroup>
<ComboboxSeparator
v-if="index !== 0"
class="h-[1px] bg-grass6 m-[5px]"
/>

<ComboboxLabel class="px-[25px] text-xs leading-[25px] text-mauve11">
{{ group.name }}
</ComboboxLabel>

<ComboboxItem
v-for="option in group.children"
:key="option.name"
:value="option.name"
class="text-[13px] leading-none text-grass11 rounded-[3px] flex items-center h-[25px] pr-[35px] pl-[25px] relative select-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-grass9 data-[highlighted]:text-grass1"
>
<ComboboxItemIndicator
class="absolute left-0 w-[25px] inline-flex items-center justify-center"
>
<Icon icon="radix-icons:check" />
</ComboboxItemIndicator>
<span>
{{ option.name }}
</span>
</ComboboxItem>
</ComboboxGroup>
</template>
</ComboboxViewport>
</ComboboxContent>
</ComboboxRoot>
</Variant>
</Story>
</template>

0 comments on commit eb600c2

Please # to comment.