Files
windmill/frontend/src/lib/components/meltComponents/Menu.svelte
T

122 lines
2.8 KiB
Svelte

<script lang="ts">
import { untrack } from 'svelte'
import { createBubbler } from 'svelte/legacy'
const bubble = createBubbler()
import { melt, createSync } from '@melt-ui/svelte'
import type { MenubarBuilders } from '@melt-ui/svelte'
import type { Placement } from '@floating-ui/core'
import { pointerDownOutside } from '$lib/utils'
import { twMerge } from 'tailwind-merge'
import ResolveOpen from '$lib/components/common/menu/ResolveOpen.svelte'
import { watch } from 'runed'
interface Props {
placement?: Placement
justifyEnd?: boolean
lightMode?: boolean
maxHeight?: number
disabled?: boolean
createMenu: MenubarBuilders['createMenu']
invisible?: boolean
usePointerDownOutside?: boolean
menuClass?: string
open?: boolean
renderContent?: boolean
classNames?: string
triggr?: import('svelte').Snippet<[any]>
children?: import('svelte').Snippet<[any]>
class?: string
}
let {
placement = 'right-start',
justifyEnd = false,
lightMode = false,
maxHeight = 900,
disabled = false,
createMenu,
invisible = false,
usePointerDownOutside = false,
menuClass = '',
open = $bindable(false),
renderContent = false,
class: classNames = '',
triggr,
children
}: Props = $props()
// Use the passed createMenu function
const menu = untrack(() => createMenu)({
positioning: {
placement: untrack(() => placement),
fitViewport: true,
strategy: 'fixed'
},
loop: true
})
//Melt
const {
elements: { trigger, menu: menuElement, item },
states
} = menu
const sync = createSync(states)
watch(
() => open,
() => sync.open(open, (v) => (open = Boolean(v)))
)
export function close() {
open = false
}
async function getMenuElements(): Promise<HTMLElement[]> {
return Array.from(document.querySelectorAll('[data-menu]')) as HTMLElement[]
}
</script>
<div class={twMerge('w-full h-8', classNames)}>
<ResolveOpen {open} on:open on:close />
<button
class={twMerge('w-full h-full', justifyEnd ? 'flex justify-end' : '')}
{disabled}
use:pointerDownOutside={{
capture: true,
stopPropagation: false,
exclude: getMenuElements,
customEventName: 'pointerdown_menu',
onClickOutside: () => {
if (usePointerDownOutside) {
close()
}
}
}}
data-menu
>
{@render triggr?.({ trigger })}
</button>
<!--svelte-ignore a11y_no_static_element_interactions-->
{#if open || renderContent}
<div
use:melt={$menuElement}
data-menu
class={twMerge(
'z-[6000] border w-56 origin-top-right rounded-md shadow-md focus:outline-none overflow-y-auto',
lightMode ? 'bg-surface-inverse' : 'bg-surface',
invisible ? 'opacity-0' : '',
menuClass
)}
onclick={bubble('click')}
>
<div class="py-1" style="max-height: {maxHeight}px; ">
{@render children?.({ item, open })}
</div>
</div>
{/if}
</div>