Skip to content

Instantly share code, notes, and snippets.

@dimfeld
Created July 6, 2020 23:55
Show Gist options
  • Save dimfeld/6d1d23792317b26baf96c1d2c146e4cc to your computer and use it in GitHub Desktop.
Save dimfeld/6d1d23792317b26baf96c1d2c146e4cc to your computer and use it in GitHub Desktop.
Dropdown Code
<script>
import tippy from 'tippy.js';
import 'tippy.js/animations/shift-away.css';
import 'tippy.js/dist/tippy.css';
import { createEventDispatcher, tick } from 'svelte';
import { scale } from 'svelte/transition';
import { cubicIn, cubicOut } from 'svelte/easing';
import Icon from './Icon.svelte';
import Button from './Button.svelte';
import { faChevronDown } from '@fortawesome/pro-solid-svg-icons/faChevronDown';
let classNames = '';
export { classNames as class };
export let dropdownAnimation = 'shift-away';
export let containerClass = '';
export let buttonClass = '';
export let buttonColor = 'normal';
export let buttonSize = 'md';
export let open = false;
export let disabled = false;
export let label;
export let closeOnClickInside = false;
export let closeOnClickOutside = true;
export let arrow = true;
export let offset = [0, 8];
export let bgColor = 'white';
export let placement = 'bottom';
export let popupArrow = true;
export let paddingClass = 'p-2';
export let theme = 'cv-dropdown';
export let maxWidth = 'none';
export let interactive = true;
export let hideOnEsc = true;
const dispatch = createEventDispatcher();
function toggleDropdown() {
let emit = true;
if (disabled) {
emit = open;
open = false;
} else {
open = !open;
}
if (emit) {
if (open) {
dispatch('open');
} else {
dispatch('close');
}
}
}
const hideOnEscPlugin = {
name: 'hideOnEsc',
defaultValue: true,
fn({ hide }) {
function onKeyDown(event) {
if (event.keyCode === 27) {
hide();
}
}
return {
onShow() {
document.addEventListener('keydown', onKeyDown);
},
onHide() {
document.removeEventListener('keydown', onKeyDown);
},
};
},
};
$: config = {
animation: dropdownAnimation,
hideOnClick: closeOnClickOutside ? true : 'toggle',
animation: dropdownAnimation,
interactive,
plugins: [hideOnEsc ? hideOnEscPlugin : null].filter(Boolean),
theme,
trigger: 'manual',
arrow: popupArrow,
maxWidth,
placement,
offset,
popperOptions: {
modifiers: [{ name: 'flip' }, { name: 'preventOverflow' }],
},
};
let showSlot = false;
let tippyInstance;
function createTippy(node, config) {
tippyInstance = tippy(node.querySelector('.dropdown-button'), {
content: node.querySelector('.dropdown-popup'),
onShow() {
// This is a hack to allow us to instantiate the slot before showing it.
// Necessary because otherwise the dropdown slides around, and if we render the slot too early
// then lifetime of components inside the dropdown becomes harder to manage.
let existingShowSlot = showSlot;
showSlot = true;
if (!existingShowSlot) {
setTimeout(async () => {
await tick();
tippyInstance.show();
});
return false;
}
},
onShown() {
open = true;
},
onHidden() {
open = false;
showSlot = false;
},
...config,
});
return {
update(config) {
tippyInstance.setProps(config);
},
destroy() {
tippyInstance.destroy();
tippyInstance = null;
},
};
}
$: if (tippyInstance) {
if (open) {
tippyInstance.show();
} else {
tippyInstance.hide();
}
}
function clickInside() {
if (closeOnClickInside) {
open = false;
}
}
</script>
<style global lang="postcss">
.tippy-box[data-theme~='cv-dropdown'] {
background-color: theme('colors.white');
background-color: var(--dropdown-bg-color, theme('colors.white'));
@apply rounded-lg shadow-xl text-black;
}
.tippy-box[data-theme~='cv-dropdown'][data-placement^='top']
> .tippy-arrow::before {
border-top-color: theme('colors.white');
border-top-color: var(--dropdown-bg-color, theme('colors.white'));
}
.tippy-box[data-theme~='cv-dropdown'][data-placement^='bottom']
> .tippy-arrow::before {
border-bottom-color: theme('colors.white');
border-bottom-color: var(--dropdown-bg-color, theme('colors.white'));
}
.tippy-box[data-theme~='cv-dropdown'][data-placement^='left']
> .tippy-arrow::before {
border-left-color: theme('colors.white');
border-left-color: var(--dropdown-bg-color, theme('colors.white'));
}
.tippy-box[data-theme~='cv-dropdown'][data-placement^='right']
> .tippy-arrow::before {
border-right-color: theme('colors.white');
border-right-color: var(--dropdown-bg-color, theme('colors.white'));
}
</style>
<div
class="inline-flex relative {containerClass}"
style="--dropdown-bg-color:{bgColor}"
use:createTippy={config}>
<div class="dropdown-button" on:click={toggleDropdown}>
<slot name="button" {disabled} {label}>
<Button
size={buttonSize}
color={buttonColor}
class={buttonClass}
{disabled}>
<span class="w-full text-center whitespace-no-wrap">
{label}
{#if arrow}
<span class="text-gray-800">
<Icon data={faChevronDown} />
</span>
{/if}
</span>
</Button>
</slot>
</div>
{#if process.browser}
<div
on:click={clickInside}
style="width:max-content"
class="dropdown-popup {classNames}
{paddingClass}">
{#if showSlot}
<slot hide={() => (open = false)} />
{/if}
</div>
{/if}
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment