Skip to content

Commit

Permalink
fix: switched portal to fixed
Browse files Browse the repository at this point in the history
portal is unreliable with multiple portals
styling challenges since portals are moved out of the DOM tree.
  • Loading branch information
JohnCampionJr committed Oct 25, 2023
1 parent d7720f7 commit 2f1a93f
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 127 deletions.
42 changes: 26 additions & 16 deletions components/form/BaseAutocomplete.vue
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,26 @@ const props = withDefaults(
allowCustom?: boolean
/**
* Portal the dropdown to body
* Used a fixed strategy to float the component
*/
portal?: boolean
fixed?: boolean
/**
* The placement of the component via floating-ui.
*/
placement?:
| 'top'
| 'top-start'
| 'top-end'
| 'right'
| 'right-start'
| 'right-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
/**
* The properties to use for the value, label, sublabel, media, and icon of the options items.
Expand Down Expand Up @@ -233,8 +250,9 @@ const props = withDefaults(
},
classes: () => ({}),
allowCustom: false,
portal: false,
properties: undefined,
fixed: false,
placement: 'bottom-start',
properties: () => ({}),
},
)
Expand Down Expand Up @@ -403,8 +421,9 @@ function key(item: T) {
@hide="query = ''"
:flip="!props.multiple"
:offset="5"
:portal="props.portal"
:adaptive-width="props.portal"
:strategy="props.fixed ? 'fixed' : 'absolute'"
:placement="props.placement"
:adaptive-width="props.fixed"
:z-index="20"
>
<ComboboxLabel
Expand Down Expand Up @@ -516,22 +535,13 @@ function key(item: T) {
>
{{ props.error }}
</span>
<FloatContent
:class="[
!props.portal && 'w-full',
props.portal && 'nui-autocomplete',
props.portal && sizeStyle[props.size],
props.portal && contrastStyle[props.contrast],
props.portal && shape && shapeStyle[shape],
]"
>
<FloatContent :class="!props.fixed && 'w-full'">
<ComboboxOptions
as="div"
:class="{
'nui-autocomplete-results':
filteredItems.length > 0 || !allowCustom,
}"
:unmount="!portal"
>
<!-- Placeholder -->
<div
Expand Down
8 changes: 7 additions & 1 deletion components/form/BaseAutocompleteItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ const props = withDefaults(
mark: 'nui-mark',
selectedIcon: 'lucide:check',
item: undefined,
properties: undefined,
properties: () =>
({
label: 'label',
sublabel: 'sublabel',
media: 'media',
icon: 'icon',
}) as any,
},
)
Expand Down
234 changes: 124 additions & 110 deletions components/form/BaseListbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,28 @@ const props = withDefaults(
*/
contrast?: 'default' | 'default-contrast' | 'muted' | 'muted-contrast'
/**
* Used a fixed strategy to float the component
*/
fixed?: boolean
/**
* The placement of the component via floating-ui.
*/
placement?:
| 'top'
| 'top-start'
| 'top-end'
| 'right'
| 'right-start'
| 'right-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
/**
* The properties to use for the value, label, sublabel, media, and icon of the options items.
*/
Expand Down Expand Up @@ -129,10 +151,6 @@ const props = withDefaults(
*/
icon?: T extends object ? keyof T : string
}
/**
* Portal the dropdown to body
*/
portal?: boolean
}>(),
{
icon: '',
Expand Down Expand Up @@ -161,7 +179,8 @@ const props = withDefaults(
loading: false,
disabled: false,
properties: () => ({}),
portal: false,
fixed: false,
placement: 'bottom-start',
},
)
const emits = defineEmits<{
Expand Down Expand Up @@ -246,8 +265,9 @@ const value = computed(() => {
leave-to-class="opacity-0"
flip
:offset="5"
:portal="props.portal"
:adaptive-width="props.portal"
:strategy="props.fixed ? 'fixed' : 'absolute'"
:placement="props.placement"
:adaptive-width="props.fixed"
:z-index="20"
>
<ListboxLabel
Expand All @@ -262,120 +282,114 @@ const value = computed(() => {

<div class="nui-listbox-outer">
<FloatReference>
<ListboxButton
:disabled="props.disabled"
class="nui-listbox-button"
>
<slot name="listbox-button" :value="value" :open="open">
<div class="nui-listbox-button-inner">
<BaseIconBox
v-if="props.icon"
size="xs"
shape="rounded"
class="nui-icon-box"
>
<slot name="icon">
<Icon :name="props.icon" class="nui-icon-box-inner" />
</slot>
</BaseIconBox>

<template v-if="Array.isArray(value)">
<div
v-if="value.length === 0 && placeholder"
class="nui-listbox-placeholder"
:class="props.loading && 'text-transparent select-none'"
>
{{ placeholder }}
</div>
<div
class="block truncate text-left"
:class="[
props.loading && 'select-none text-transparent',
value.length === 0 &&
'text-muted-300 dark:text-muted-500',
]"
>
{{
typeof props.multipleLabel === 'function'
? props.multipleLabel(value, props.properties.label)
: props.multipleLabel
}}
</div>
</template>

<template v-else-if="value">
<BaseAvatar
v-if="
props.properties.media &&
(value as any)[props.properties.media]
"
:src="(value as any)[props.properties.media]"
size="xs"
class="-ms-2 me-2"
/>
<div>
<ListboxButton
:disabled="props.disabled"
class="nui-listbox-button"
>
<slot name="listbox-button" :value="value" :open="open">
<div class="nui-listbox-button-inner">
<BaseIconBox
v-else-if="
props.properties.icon &&
(value as any)[props.properties.icon]
"
v-if="props.icon"
size="xs"
shape="rounded"
class="-ms-2 me-2"
class="nui-icon-box"
>
<slot name="icon">
<Icon :name="props.icon" class="nui-icon-box-inner" />
</slot>
</BaseIconBox>

<template v-if="Array.isArray(value)">
<div
v-if="value.length === 0 && placeholder"
class="nui-listbox-placeholder"
:class="props.loading && 'select-none text-transparent'"
>
{{ placeholder }}
</div>
<div
class="block truncate text-left"
:class="[
props.loading && 'select-none text-transparent',
value.length === 0 &&
'text-muted-300 dark:text-muted-500',
]"
>
{{
typeof props.multipleLabel === 'function'
? props.multipleLabel(value, props.properties.label)
: props.multipleLabel
}}
</div>
</template>

<template v-else-if="value">
<BaseAvatar
v-if="
props.properties.media &&
(value as any)[props.properties.media]
"
:src="(value as any)[props.properties.media]"
size="xs"
class="-ms-2 me-2"
/>
<BaseIconBox
v-else-if="
props.properties.icon &&
(value as any)[props.properties.icon]
"
size="xs"
shape="rounded"
class="-ms-2 me-2"
>
<Icon
:name="(value as any)[props.properties.icon]"
class="h-4 w-4"
/>
</BaseIconBox>
<div
class="truncate text-left"
:class="props.loading && 'select-none text-transparent'"
>
{{
props.properties.label
? (value as any)[props.properties.label]
: props.properties.value
? (value as any)[props.properties.value]
: value
}}
</div>
</template>

<template v-else>
<div
class="nui-listbox-placeholder"
:class="props.loading && 'select-none text-transparent'"
>
{{ placeholder }}
</div>
</template>

<span class="nui-listbox-chevron">
<Icon
:name="(value as any)[props.properties.icon]"
class="h-4 w-4"
name="lucide:chevron-down"
class="nui-listbox-chevron-inner"
:class="[open && 'rotate-180']"
/>
</BaseIconBox>
<div
class="truncate text-left"
:class="props.loading && 'text-transparent select-none'"
>
{{
props.properties.label
? (value as any)[props.properties.label]
: props.properties.value
? (value as any)[props.properties.value]
: value
}}
</div>
</template>
</span>

<template v-else>
<div
class="nui-listbox-placeholder"
:class="props.loading && 'text-transparent select-none'"
>
{{ placeholder }}
<div v-if="props.loading" class="nui-listbox-placeload">
<BasePlaceload class="nui-placeload" />
</div>
</template>

<span class="nui-listbox-chevron">
<Icon
name="lucide:chevron-down"
class="nui-listbox-chevron-inner"
:class="[open && 'rotate-180']"
/>
</span>

<div v-if="props.loading" class="nui-listbox-placeload">
<BasePlaceload class="nui-placeload" />
</div>
</div>
</slot>
</ListboxButton>
</slot>
</ListboxButton>
</div>
</FloatReference>

<FloatContent
:class="[
!props.portal && 'w-full',
props.portal && 'nui-listbox',
props.portal && sizeStyle[props.size],
props.portal && contrastStyle[props.contrast],
props.portal && shape && shapeStyle[shape],
]"
>
<ListboxOptions class="nui-listbox-options" :unmount="!portal">
<FloatContent :class="!props.fixed && 'w-full'">
<ListboxOptions class="nui-listbox-options">
<ListboxOption
v-for="item in props.items"
v-slot="{ active, selected }"
Expand Down

0 comments on commit 2f1a93f

Please sign in to comment.