Skip to content

Instantly share code, notes, and snippets.

@fabiogiolito
Created March 4, 2022 18:46
Show Gist options
  • Save fabiogiolito/a75126d19f03dce3e08bde8a5ec72c5b to your computer and use it in GitHub Desktop.
Save fabiogiolito/a75126d19f03dce3e08bde8a5ec72c5b to your computer and use it in GitHub Desktop.
<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