chore: move to tsup

This commit is contained in:
Benjamin Canac
2021-11-24 16:07:18 +01:00
parent 7a31f1be4f
commit 65a6aa5fda
35 changed files with 132 additions and 16 deletions

View File

@@ -0,0 +1,95 @@
<template>
<TransitionRoot :show="isOpen" as="template">
<Dialog @close="setIsOpen">
<div class="fixed z-10 inset-0 overflow-y-auto">
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<TransitionChild
as="template"
enter="ease-out duration-300"
enter-from="opacity-0"
enter-to="opacity-100"
leave="ease-in duration-200"
leave-from="opacity-100"
leave-to="opacity-0"
>
<DialogOverlay class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</TransitionChild>
<!-- This element is to trick the browser into centering the modal contents. -->
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<TransitionChild
as="template"
enter="ease-out duration-300"
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enter-to="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leave-from="opacity-100 translate-y-0 sm:scale-100"
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Card
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
v-bind="$attrs"
ring-class="sm:ring-1 sm:ring-transparent dark:ring-gray-700"
>
<template v-if="$slots.header" #header>
<slot name="header" />
</template>
<slot />
<template v-if="$slots.footer" #footer>
<slot name="footer" />
</template>
</Card>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import { Dialog, DialogOverlay, TransitionRoot, TransitionChild } from '@headlessui/vue'
import Card from '../layout/Card'
export default {
components: {
Dialog,
DialogOverlay,
TransitionRoot,
TransitionChild,
Card
},
props: {
modelValue: {
type: Boolean,
default: false
},
title: {
type: String,
default: null
}
},
emits: ['update:modelValue'],
setup (props, { emit }) {
const isOpen = computed({
get () {
return props.modelValue
},
set (value) {
emit('update:modelValue', value)
}
})
return {
isOpen,
setIsOpen (value) {
isOpen.value = value
},
toggleIsOpen () {
isOpen.value = !isOpen.value
}
}
}
}
</script>

View File

@@ -0,0 +1,191 @@
<template>
<transition
appear
enter-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enter-active-class="transition duration-300 ease-out transform"
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
leave-class="opacity-100"
leave-active-class="transition duration-100 ease-in"
leave-to-class="opacity-0"
>
<div
class="z-50 w-full bg-white rounded-lg shadow-lg pointer-events-auto dark:bg-gray-800"
@mouseover="onMouseover"
@mouseout="onMouseout"
>
<div class="relative overflow-hidden rounded-lg ring-1 ring-gray-200 dark:ring-gray-700">
<div class="p-4">
<div class="flex">
<div class="flex-shrink-0">
<Icon :name="iconName" class="w-6 h-6" :class="iconClass" />
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium leading-5 text-tw-gray-900">
{{ title }}
</p>
<p v-if="description" class="mt-1 text-sm leading-5 text-tw-gray-500">
{{ description }}
</p>
<Button
v-if="undo"
variant="white"
size="xs"
class="mt-2"
@click.native.stop="cancel"
>
Undo
<div class="inline-flex items-center rounded bg-tw-gray-200 ml-1.5">
<span class="w-full px-1 text-center text-tw-gray-600 text-xxs">
Z
</span>
</div>
</Button>
</div>
<div class="flex-shrink-0 ml-4">
<button
class="transition duration-150 ease-in-out text-tw-gray-400 focus:outline-none hover:text-tw-gray-500 focus:text-tw-gray-500"
@click.stop="close"
>
<span class="sr-only">Close</span>
<Icon name="outline/x" class="w-5 h-5" />
</button>
</div>
</div>
</div>
<div v-if="timeout" class="absolute bottom-0 left-0 right-0 h-1">
<div class="h-1 bg-primary-500" :style="progressBarStyle" />
</div>
</div>
</div>
</transition>
</template>
<script>
import Icon from '../elements/Icon'
import Button from '../elements/Button'
export default {
components: {
Icon,
Button
},
props: {
id: {
type: String,
required: true
},
type: {
type: String,
required: true,
default: 'info',
validator (value) {
return ['info', 'success', 'error', 'warning'].includes(value)
}
},
title: {
type: String,
required: true
},
description: {
type: String,
default: null
},
icon: {
type: String,
default: null
},
timeout: {
type: Number,
default: 5000
},
undo: {
type: Function,
default: null
},
callback: {
type: Function,
default: null
}
},
data () {
return {
timer: null,
ticker: null,
remainingTime: this.timeout
}
},
computed: {
iconName () {
return this.icon || ({
warning: 'solid/exclamation-circle',
info: 'solid/information-circle',
success: 'solid/check-circle',
error: 'solid/x-circle'
})[this.type]
},
iconClass () {
return ({
warning: 'text-orange-400',
info: 'text-blue-400',
success: 'text-green-400',
error: 'text-red-400'
})[this.type] || 'text-tw-gray-400'
},
progressBarStyle () {
const remainingPercent = this.remainingTime / this.timeout * 100
return { width: `${remainingPercent}%` }
}
},
mounted () {
if (!this.$timer) {
return
}
if (this.timeout) {
this.timer = new this.$timer(() => {
this.close()
this.ticker?.stop()
}, this.timeout)
this.ticker = new this.$ticker(() => {
this.remainingTime -= 10
}, 10)
}
},
beforeDestroy () {
if (this.timer) {
this.timer.stop()
this.ticker.stop()
}
},
methods: {
onMouseover () {
if (this.timer) {
this.timer.pause()
this.ticker.stop()
}
},
onMouseout () {
if (this.timer) {
this.timer.resume()
this.ticker.start()
}
},
cancel () {
if (this.timer) {
this.timer.stop()
this.ticker.stop()
}
if (this.undo) {
this.undo()
}
this.$emit('close')
},
close () {
if (this.callback) {
this.callback()
}
this.$emit('close')
}
}
}
</script>

View File

@@ -0,0 +1,91 @@
<template>
<Popover v-slot="{ open }" :class="wrapperClass">
<PopoverButton ref="trigger" as="div">
<slot :open="open">
Open
</slot>
</PopoverButton>
<div v-if="open" ref="container" :class="containerClass">
<transition
appear
enter-active-class="transition ease-out duration-200"
enter-from-class="opacity-0 translate-y-1"
enter-to-class="opacity-100 translate-y-0"
leave-active-class="transition ease-in duration-150"
leave-from-class="opacity-100 translate-y-0"
leave-to-class="opacity-0 translate-y-1"
>
<PopoverPanel :class="panelClass" static>
<slot name="panel" />
</PopoverPanel>
</transition>
</div>
</Popover>
</template>
<script>
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
import { usePopper } from '../../utils'
export default {
components: {
Popover,
PopoverButton,
PopoverPanel
},
props: {
placement: {
type: String,
default: 'bottom'
},
strategy: {
type: String,
default: 'fixed'
},
wrapperClass: {
type: String,
default: 'relative'
},
containerClass: {
type: String,
default: 'z-10'
},
panelClass: {
type: String,
default: 'transform'
}
},
setup (props) {
const [trigger, container] = usePopper({
placement: props.placement,
strategy: props.strategy,
modifiers: [{
name: 'offset',
options: {
offset: [0, 8]
}
},
{
name: 'computeStyles',
options: {
gpuAcceleration: false,
adaptive: false
}
},
{
name: 'preventOverflow',
options: {
padding: 8
}
}]
})
return {
trigger,
container
}
}
}
</script>

View File

@@ -0,0 +1,210 @@
<template>
<div v-if="showSlideover" ref="container" class="fixed inset-0 z-50 overflow-hidden slideover">
<div class="absolute inset-0 overflow-hidden">
<transition
appear
enter-class="opacity-0"
enter-active-class="duration-300 ease-out"
enter-to-class="opacity-100"
leave-class="opacity-100"
leave-active-class="duration-200 ease-in"
leave-to-class="opacity-0"
@before-leave="backdropLeaving = true"
@after-leave="backdropLeaving = false"
>
<div v-if="showBackdrop" class="fixed inset-0 transition-opacity bg-gray-800 sm:bg-opacity-75" @click="open = false" />
</transition>
<section class="absolute inset-y-0 right-0 flex max-w-full sm:pl-16">
<transition
appear
v-bind="transitionProps"
@before-leave="contentLeaving = true"
@after-leave="contentLeaving = false"
>
<Card
v-if="showContent"
v-bind="$attrs"
role="dialog"
aria-modal="true"
class="z-50 flex flex-col w-screen h-screen transition transform shadow-xl sm:max-w-md "
body-class="flex-1 overflow-y-auto"
ring-class="sm:ring-1 ring-transparent dark:ring-gray-700"
variant="white"
:rounded="false"
>
<template v-if="$slots.header" #header>
<slot name="header" />
</template>
<template v-else-if="title" #header>
<div class="flex items-center justify-between">
<h2 class="font-medium sm:leading-6 sm:text-lg text-tw-gray-900">
{{ title }}
</h2>
<div class="flex items-center">
<button
type="button"
aria-label="Close panel"
class="rounded-md text-tw-gray-400 hover:text-tw-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500"
@click="open = false"
>
<Icon name="outline/x" class="w-6 h-6" />
</button>
</div>
</div>
</template>
<slot />
<template v-if="$slots.footer" #footer>
<slot name="footer" />
</template>
</Card>
</transition>
</section>
</div>
</div>
</template>
<script>
// import focusLock from 'dom-focus-lock'
import Icon from '../elements/Icon'
import Card from '../layout/Card'
export default {
components: {
Icon,
Card
},
props: {
value: {
type: Boolean,
default: false
},
title: {
type: String,
default: null
}
},
data () {
return {
showSlideover: false,
showBackdrop: false,
showContent: false,
backdropLeaving: false,
contentLeaving: false,
lock: false
}
},
head () {
if (this.open) {
return {
bodyAttrs: {
class: ['overflow-hidden']
},
htmlAttrs: {
style: ['touch-action: none;']
}
}
}
return undefined
},
computed: {
transitionProps () {
// Same transition than Modal but only on mobile
if (this.$mq === 'xs') {
return {
enterClass: 'translate-y-4 opacity-0 sm:translate-y-0 sm:scale-95',
enterActiveClass: 'duration-300 ease-out',
enterToClass: 'translate-y-0 opacity-100 sm:scale-100',
leaveClass: 'translate-y-0 opacity-100 sm:scale-100',
leaveActiveClass: 'duration-200 ease-in',
leaveToClass: 'translate-y-4 opacity-0 sm:translate-y-0 sm:scale-95'
}
} else {
return {
enterClass: 'translate-x-full',
enterActiveClass: 'transition duration-500 ease-in-out transform sm:duration-700',
enterToClass: 'translate-x-0',
leaveClass: 'translate-x-0',
leaveActiveClass: 'transition duration-500 ease-in-out transform sm:duration-700',
leaveToClass: 'translate-x-full'
}
}
},
leaving () {
return this.backdropLeaving || this.contentLeaving
},
open: {
get () {
return this.value
},
set (open) {
this.$emit('input', open)
}
}
},
watch: {
open: {
handler (newValue) {
if (newValue) {
this.show()
} else {
this.close()
}
},
immediate: true
},
leaving (newValue) {
if (newValue === false) {
this.showSlideover = false
this.open = false
}
}
},
beforeDestroy () {
if (this.lock) {
// focusLock.off(this.$refs.container)
this.lock = false
}
},
shortcuts: {
disabled () {
return !this.open
},
esc: 'esc'
},
methods: {
show () {
this.showSlideover = true
this.showBackdrop = true
this.showContent = true
// Remove current focus if any, avoiding the close button to autofocus and break opening animation.
document.activeElement?.blur()
this.$nextTick(() => {
if (this.$refs.container && !this.lock) {
// focusLock.on(this.$refs.container)
this.lock = true
}
})
},
close () {
this.showBackdrop = false
this.showContent = false
if (this.lock) {
// focusLock.off(this.$refs.container)
this.lock = false
}
},
esc () {
this.$listeners.close ? this.$listeners.close() : this.close()
}
}
}
</script>
<style scoped>
.slideover {
margin: env(safe-area-inset-top) 0 0 env(safe-area-inset-left);
}
</style>

View File

@@ -0,0 +1,105 @@
<template>
<div ref="container" @mouseover="mouseover" @mouseleave="mouseleave">
<slot />
<transition
enter-class="transform scale-95 opacity-0"
enter-active-class="transition duration-100 ease-out"
enter-to-class="transform scale-100 opacity-100"
leave-class="opacity-100"
leave-active-class="duration-100 ease-in"
leave-to-class="opacity-0"
>
<div
v-show="show && (text || shortcuts.length || $slots.text)"
ref="tooltip"
class="fixed z-30 flex bg-gray-800 items-center justify-center invisible w-auto h-6 max-w-xs px-2 space-x-1 truncate rounded shadow lg:visible"
>
<span v-if="text || $slots.text" class="truncate text-gray-50 text-xxs">
<slot name="text">{{ text }}</slot>
</span>
<div v-if="shortcuts && shortcuts.length">
<div class="flex items-center flex-shrink-0 h-6 space-x-1">
<span v-if="text" class="mb-2 text-center text-gray-500">.</span>
<div
v-for="(shortcut, index) of shortcuts"
:key="index"
class="flex items-center px-1 bg-gray-600 rounded"
>
<span
class="font-light text-center text-gray-200 text-xxs"
>{{ shortcut }}</span>
</div>
</div>
</div>
</div>
</transition>
</div>
</template>
<script>
// import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
import { usePopper } from '../../utils'
export default {
// components: {
// Popover,
// PopoverButton,
// PopoverPanel
// },
props: {
placement: {
type: String,
default: 'bottom'
},
strategy: {
type: String,
default: 'absolute'
},
wrapperClass: {
type: String,
default: 'relative'
},
tooltipClass: {
type: String,
default: 'z-10'
},
label: {
type: String,
default: ''
}
},
setup (props) {
const [trigger, container] = usePopper({
placement: props.placement,
strategy: props.strategy,
modifiers: [{
name: 'offset',
options: {
offset: [0, 8]
}
},
{
name: 'computeStyles',
options: {
gpuAcceleration: false,
adaptive: false
}
},
{
name: 'preventOverflow',
options: {
padding: 8
}
}]
})
return {
trigger,
container
}
}
}
</script>