-
Notifications
You must be signed in to change notification settings - Fork 0
/
Modal.svelte
104 lines (95 loc) · 3.16 KB
/
Modal.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<!--
@component
wrapper component for every modal used in the application
this component automatically handles multiple modals on top of each other and blurs all modals that aren't on top
content is aligned to center for `sm` screen and to bottom for smaller screens
@slot `default` - the main content of the modal
@prop `isOpen`
-->
<script lang="ts">
import { createEventDispatcher, onDestroy } from 'svelte'
import { fade, fly } from 'svelte/transition'
import { changePortalVisibility, portal, portalMap } from './actions/portal'
import cn from 'classnames'
import { useWobble } from './helpers/wobble-svelte'
import { shouldHideOverflowController$ } from './contexts/should-hide-overflow'
import _ from 'lodash'
/** @lends */
export let isOpen = false
/**
* @description accept exit when clicking outside the modal
* @default false
*/
export let acceptExit = false
/** @readonly */
export const toggle = (state?: boolean) => {
isOpen = _.isBoolean(state) ? state : !isOpen
}
/**
* @description wether the modal is animated from left or down (default)
* @default false
*/
export let animateWidth = false
export let className: { [key in 'bg']?: string } = {}
/**
* @description whether or not the wrapper should have the full screen width
* @default false
*/
export let neverFullWidth = false
const dispatch = createEventDispatcher<{ requestExit: true }>()
function dismiss() {
dispatch('requestExit', true)
if (acceptExit) {
isOpen = false
}
}
let node: HTMLElement
let index: number | null = null
const [shouldBlur, setShouldBlur] = useWobble({})
$: node && setShouldBlur($portalMap.some(x => (x.index ?? -1) > (index ?? -1)) ? 1 : 0)
$: index = changePortalVisibility(node, isOpen)
let notYetShown = !isOpen
$: isOpen && (notYetShown = false)
$: isOpen
? shouldHideOverflowController$.next({ Hide: true })
: !notYetShown && shouldHideOverflowController$.next({ Hide: false })
onDestroy(() => isOpen && !notYetShown && shouldHideOverflowController$.next({ Hide: false }))
</script>
<div use:portal bind:this={node}>
{#if isOpen}
<div
transition:fade
tabindex="0"
style="z-index: {(index ?? -100) + 50}; {$shouldBlur === 0
? ''
: `filter: blur(${$shouldBlur * 20}px); `}"
class={cn(
'overscroll-none touch-none',
'overscroll-contain-children',
'fixed inset-0',
'bg-black bg-opacity-40',
'overflow-hidden',
className.bg,
)}
on:keydown={e => e.key === 'Escape' && dismiss()}
on:click|self={dismiss}>
{#if isOpen}
<div
on:keydown={e => e.key === 'Escape' && dismiss()}
on:click|self={dismiss}
transition:fly={{ ...(animateWidth ? { x: -500 } : { y: 500 }), duration: 500 }}
class={cn(
neverFullWidth ? 'w-max' : 'w-full',
'h-full flex flex-col items-center justify-end sm:justify-center',
)}>
<slot {isOpen} />
</div>
{/if}
</div>
{/if}
</div>
<style lang="postcss">
.overscroll-contain-children :global(*) {
overscroll-behavior: contain;
}
</style>