Created
July 6, 2020 23:55
-
-
Save dimfeld/6d1d23792317b26baf96c1d2c146e4cc to your computer and use it in GitHub Desktop.
Dropdown Code
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 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