Created
March 4, 2022 18:46
-
-
Save fabiogiolito/a75126d19f03dce3e08bde8a5ec72c5b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
import { fly } from "svelte/transition"; | |
import { teleport } from "$lib/teleport"; | |
export let isOpen = false; | |
// Usage | |
// <Dropdown up right> | |
// <button slot="trigger">Toggle</button> | |
// <div slot="menu">Content</div> | |
// </Dropdown> | |
// Direction | |
export let up = false; | |
export let down = !up; | |
export let left = false; | |
export let right = !left; | |
export let distance = 1; // Tailwind scale: 1 => 4px; | |
let triggerContainer; // Reference | |
let styles = ""; // Will calculate styles for position | |
let scrollableParent; // Will detect scrolling parents | |
// Calculate position | |
$: if (triggerContainer) getPosition(); | |
// Add scroll listeners | |
$: scrollableParent = getScrollableParent(isOpen, triggerContainer); | |
function toggleDropdown() { | |
getPosition(); | |
isOpen = !isOpen; | |
} | |
function getPosition() { | |
let container = triggerContainer.getBoundingClientRect(); | |
if (up) styles += `bottom: ${container.top - (distance * 4)}px;`; | |
if (down) styles += `top: ${container.bottom + (distance * 4)}px; `; | |
if (right) styles += `left: ${container.left}px; `; | |
if (left) styles += `right: ${window.innerWidth - container.right}px;`; | |
} | |
function closeDropdown() { | |
if (!isOpen) return; | |
isOpen = false; | |
} | |
function handleClick(e) { | |
if (!isOpen) return; | |
if (!triggerContainer.contains(e.srcElement)) { | |
closeDropdown(); | |
} | |
} | |
function getScrollableParent(state, node) { | |
if (isOpen) { | |
if (!node) return; | |
// Node scrolls | |
if (node.scrollHeight > node.clientHeight) { | |
node.addEventListener('scroll', closeDropdown); | |
return node; | |
// Try again with node's parent | |
} else { | |
return getScrollableParent(state, node.parentNode); | |
} | |
} else { | |
if (scrollableParent) { | |
node.removeEventListener('scroll', closeDropdown); | |
scrollableParent = null; | |
} | |
} | |
} | |
</script> | |
<svelte:window on:scroll={closeDropdown} on:resize={closeDropdown}></svelte:window> | |
<svelte:body on:click={handleClick}></svelte:body> | |
<div bind:this={triggerContainer} class="inline-block" on:click={toggleDropdown}> | |
<slot name="trigger" {isOpen}></slot> | |
</div> | |
{#if isOpen} | |
<div use:teleport | |
on:click | |
transition:fly={{ y: (distance * 4) * (down ? -1 : 1), duration: 200 }} | |
class="fixed z-50 m-0 {$$props.class || 'w-40 bg-white border border-opacity-50 p-2 rounded-lg shadow'}" | |
style={styles} | |
> | |
<slot name="menu"></slot> | |
</div> | |
{/if} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment