Skip to content

Commit

Permalink
feat: use Floating UI for dropdown (#94)
Browse files Browse the repository at this point in the history
* feat: add floating UI to dropdowns

Allows for positioning elements wherever you want

* feat: added floating-ui to buttondropdown

* feat(BaseDropdown): add floating-ui placement property, deprecate orientation

---------

Co-authored-by: Sacha STAFYNIAK <sacha@digisquad.io>
  • Loading branch information
JohnCampionJr and stafyniaksacha committed Oct 3, 2023
1 parent 68eb81d commit abd1523
Show file tree
Hide file tree
Showing 5 changed files with 568 additions and 444 deletions.
150 changes: 95 additions & 55 deletions components/base/BaseDropdown.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { Menu, MenuButton, MenuItems } from '@headlessui/vue'
import { Float } from '@headlessui-float/vue'
const props = withDefaults(
defineProps<{
Expand Down Expand Up @@ -44,9 +45,28 @@ const props = withDefaults(
/**
* The orientation of the dropdown.
*
* @deprecated use placement instead
*/
orientation?: 'start' | 'end'
/**
* The placement of the dropdown 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 size of the dropdown.
*/
Expand All @@ -67,21 +87,19 @@ const props = withDefaults(
buttonColor: 'default',
shape: undefined,
orientation: 'start',
placement: undefined,
size: 'md',
color: 'white',
label: '',
headerLabel: undefined,
},
)
const appConfig = useAppConfig()
const shape = computed(
() => props.shape ?? appConfig.nui.defaultShapes?.dropdown,
)
const orientationStyle = {
start: 'nui-dropdown-start',
end: 'nui-dropdown-end',
}
const sizeStyle = {
md: 'nui-menu-md',
lg: 'nui-menu-lg',
Expand All @@ -105,71 +123,86 @@ const colorStyle = {
danger: 'nui-menu-danger',
none: '',
}
/**
* fallback placement with old orientation value
* @todo remove this on next major version
*/
const placementValue = computed(() => {
if (props.placement) {
return props.placement
}
return props.orientation === 'end' ? 'bottom-end' : 'bottom-start'
})
</script>

<template>
<div class="nui-dropdown" :class="[orientationStyle[props.orientation]]">
<div class="nui-dropdown">
<Menu
v-slot="{ open, close }: { open: boolean; close: () => void }"
as="div"
class="nui-menu"
>
<MenuButton as="template">
<slot name="button" v-bind="{ open, close }">
<BaseButton
v-if="props.flavor === 'button'"
:color="props.buttonColor"
:shape="shape"
class="!pe-3 !ps-4"
>
<slot name="label" v-bind="{ open, close }">
<span>{{ props.label }}</span>
</slot>
<Icon
name="lucide:chevron-down"
class="nui-chevron"
:class="open && 'rotate-180'"
/>
</BaseButton>
<button
v-else-if="props.flavor === 'context'"
type="button"
class="nui-context-button"
>
<span class="nui-context-button-inner">
<Icon
name="lucide:more-horizontal"
class="nui-context-icon"
:class="open && 'rotate-90'"
/>
</span>
</button>
<button
v-else-if="props.flavor === 'text'"
type="button"
class="nui-text-button"
>
<slot name="label" v-bind="{ open, close }">
<span class="nui-text-button-inner">{{ props.label }}</span>
</slot>

<Icon
name="lucide:chevron-down"
class="nui-chevron"
:class="open && 'rotate-180'"
/>
</button>
</slot>
</MenuButton>

<Transition
<Float
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-in"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
flip
:offset="props.flavor === 'context' ? 6 : 4"
:placement="placementValue"
>
<MenuButton as="template">
<slot name="button" v-bind="{ open, close }">
<BaseButton
v-if="props.flavor === 'button'"
:color="props.buttonColor"
:shape="shape"
class="!pe-3 !ps-4"
>
<slot name="label" v-bind="{ open, close }">
<span>{{ props.label }}</span>
</slot>
<Icon
name="lucide:chevron-down"
class="nui-chevron"
:class="open && 'rotate-180'"
/>
</BaseButton>
<button
v-else-if="props.flavor === 'context'"
type="button"
class="nui-context-button"
>
<span class="nui-context-button-inner">
<Icon
name="lucide:more-horizontal"
class="nui-context-icon"
:class="open && 'rotate-90'"
/>
</span>
</button>
<button
v-else-if="props.flavor === 'text'"
type="button"
class="nui-text-button"
>
<slot name="label" v-bind="{ open, close }">
<span class="nui-text-button-inner">{{ props.label }}</span>
</slot>

<Icon
name="lucide:chevron-down"
class="nui-chevron"
:class="open && 'rotate-180'"
/>
</button>
</slot>
</MenuButton>

<MenuItems
class="nui-dropdown-menu"
:class="[
Expand All @@ -189,7 +222,14 @@ const colorStyle = {
<slot v-bind="{ open, close }"></slot>
</div>
</MenuItems>
</Transition>
</Float>
</Menu>
</div>
</template>

<style scoped>
.nui-dropdown .nui-dropdown-menu {
position: unset;
margin-top: unset;
}
</style>
Loading

0 comments on commit abd1523

Please sign in to comment.