This commit is contained in:
Nadar
2026-03-17 13:24:22 +03:00
commit 82e5ac9d81
554 changed files with 29637 additions and 0 deletions

View File

@@ -0,0 +1,187 @@
<template>
<div
ref="rootEl"
:class="[cn.b(), cn.is('active', active), cn.is('focused', focused)]"
>
<UiRenderless ref="triggerEl">
<slot v-bind="{ isActive: active }" />
</UiRenderless>
<Teleport
to="body"
:disabled="!teleport"
>
<div
v-if="withOverlay"
:class="[cn.e('overlay'), cn.is('active', active)]"
/>
<Transition
name="dropdown"
@after-leave="scrollbarInstance()?.destroy()"
>
<div
v-if="active"
ref="popperEl"
:class="[cn.e('popper')]"
:style="floatingStyles"
>
<ul
ref="scrollParentEl"
role="listbox"
:class="[cn.e('dropdown'), dropdownClass]"
>
<slot name="dropdown">
<span>Content</span>
</slot>
</ul>
</div>
</Transition>
</Teleport>
</div>
</template>
<script setup lang="ts">
import { provide, ref, toRef, watch } from 'vue'
import {
autoUpdate,
flip as floatingFlip,
offset as floatingOffset,
useFloating,
} from '@floating-ui/vue'
import {
onClickOutside,
useElementHover,
useEventListener,
} from '@vueuse/core'
import type { OffsetOptions, Placement } from '@floating-ui/vue'
import { useOverlayScrollbars } from 'overlayscrollbars-vue'
import useClassname from '../../composables/use-classname.js'
import UiRenderless from '../renderless/index.vue'
import { dropdownContextKey } from './constants'
export interface Props {
trigger?: 'hover' | 'click'
disabled?: false
offset?: OffsetOptions
placement?: Placement
hideOnClick?: boolean
withOverlay?: boolean
dropdownClass?: string
transitionName?: string
teleport?: boolean
}
defineOptions({
name: 'UiDropdown',
})
const props = withDefaults(defineProps<Props>(), {
trigger: 'click',
disabled: false,
placement: 'bottom-start',
offset: 4,
hideOnClick: true,
withOverlay: false,
transitionName: 'ui-dropdown',
teleport: false,
})
defineEmits<{
'update:modelValue': []
focus: []
blur: []
}>()
const rootEl = ref<HTMLElement | null>()
const triggerEl = ref()
const popperEl = ref()
const scrollParentEl = ref()
const active = ref(false)
const focused = ref(false)
const cn = useClassname('ui-dropdown')
if (props.trigger === 'click') {
useEventListener(triggerEl, 'click', () => {
toggle()
})
onClickOutside(
popperEl,
() => {
close()
},
{ ignore: [triggerEl] },
)
}
else {
const isHoveredTrigger = useElementHover(triggerEl, { delayLeave: 300 })
const isHoveredPopper = useElementHover(popperEl, { delayLeave: 10 })
watch(isHoveredTrigger, (value: boolean) => {
if (value)
open()
else if (!isHoveredPopper.value)
close()
})
watch(isHoveredPopper, async (value: boolean) => {
if (!value && !isHoveredTrigger.value)
close()
})
}
const { floatingStyles } = useFloating(triggerEl, popperEl, {
placement: toRef(props, 'placement'),
middleware: [floatingOffset(props.offset), floatingFlip()],
whileElementsMounted: autoUpdate,
})
const [initScrollbar, scrollbarInstance] = useOverlayScrollbars({
options: {
scrollbars: {
theme: 'os-small-theme',
},
},
})
function setActive(state: boolean) {
if (props.disabled)
return
active.value = state
}
function toggle() {
setActive(!active.value)
}
function open() {
setActive(true)
}
function close() {
setActive(false)
}
function handleItemClick() {
if (props.hideOnClick)
close()
}
// watch(scrollParentEl, (value) => {
// if (value)
// initScrollbar(value)
// })
provide(dropdownContextKey, {
handleItemClick,
})
</script>
<style lang="scss">
@use 'styles';
</style>