mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-21 23:40:39 +01:00
feat: rewrite to use app config and rework docs (#143)
Co-authored-by: Daniel Roe <daniel@roe.dev> Co-authored-by: Sébastien Chopin <seb@nuxt.com>
This commit is contained in:
@@ -1,91 +1,79 @@
|
||||
<template>
|
||||
<div v-if="isOpen" ref="container" :class="[containerClass, widthClass]">
|
||||
<transition appear v-bind="transitionClass">
|
||||
<div :class="[baseClass, ringClass, roundedClass, shadowClass, backgroundClass]">
|
||||
<div v-if="isOpen" ref="container" :class="[ui.container, ui.width]">
|
||||
<transition appear v-bind="ui.transition">
|
||||
<div :class="[ui.base, ui.ring, ui.rounded, ui.shadow, ui.background]">
|
||||
<slot />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType, Ref } from 'vue'
|
||||
import { computed, toRef } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import type { VirtualElement } from '@popperjs/core'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { PopperOptions } from '../../types'
|
||||
import $ui from '#build/ui'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
virtualElement: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
popper: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.contextMenu>>,
|
||||
default: () => appConfig.ui.contextMenu
|
||||
}
|
||||
},
|
||||
virtualElement: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.wrapper
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.container
|
||||
},
|
||||
widthClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.width
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.background
|
||||
},
|
||||
shadowClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.shadow
|
||||
},
|
||||
roundedClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.rounded
|
||||
},
|
||||
ringClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.ring
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.base
|
||||
},
|
||||
transitionClass: {
|
||||
type: Object,
|
||||
default: () => $ui.contextMenu.transition
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({})
|
||||
emits: ['update:modelValue', 'close'],
|
||||
setup (props, { emit }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.contextMenu>>(() => defu({}, props.ui, appConfig.ui.contextMenu))
|
||||
|
||||
const popper = computed<PopperOptions>(() => defu({}, props.popper, ui.value.popper as PopperOptions))
|
||||
|
||||
const isOpen = computed({
|
||||
get () {
|
||||
return props.modelValue
|
||||
},
|
||||
set (value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const virtualElement = toRef(props, 'virtualElement') as Ref<VirtualElement>
|
||||
|
||||
const [, container] = usePopper(popper.value, virtualElement)
|
||||
|
||||
onClickOutside(container, () => {
|
||||
isOpen.value = false
|
||||
})
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
isOpen,
|
||||
container
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'close'])
|
||||
|
||||
const isOpen = computed({
|
||||
get () {
|
||||
return props.modelValue
|
||||
},
|
||||
set (value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const virtualElement = toRef(props, 'virtualElement') as Ref<VirtualElement>
|
||||
|
||||
const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions, $ui.contextMenu.popperOptions))
|
||||
|
||||
const [, container] = usePopper(popperOptions.value, virtualElement)
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'UContextMenu' }
|
||||
</script>
|
||||
|
||||
@@ -1,39 +1,15 @@
|
||||
<template>
|
||||
<TransitionRoot :appear="appear" :show="isOpen" as="template">
|
||||
<Dialog :class="wrapperClass" @close="close">
|
||||
<TransitionChild
|
||||
v-if="overlay"
|
||||
as="template"
|
||||
:appear="appear"
|
||||
v-bind="overlayTransition"
|
||||
>
|
||||
<div class="fixed inset-0 transition-opacity" :class="overlayBackgroundClass" />
|
||||
<Dialog :class="ui.wrapper" @close="close">
|
||||
<TransitionChild v-if="overlay" as="template" :appear="appear" v-bind="ui.overlay.transition">
|
||||
<div :class="[ui.overlay.base, ui.overlay.background]" />
|
||||
</TransitionChild>
|
||||
|
||||
<div :class="innerClass" :style="innerStyle">
|
||||
<div :class="containerClass">
|
||||
<TransitionChild
|
||||
as="template"
|
||||
:appear="appear"
|
||||
v-bind="modalTransition"
|
||||
>
|
||||
<DialogPanel :class="modalClass">
|
||||
<Card
|
||||
base-class=""
|
||||
background-class=""
|
||||
shadow-class=""
|
||||
ring-class=""
|
||||
rounded-class=""
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template v-if="$slots.header" #header>
|
||||
<slot name="header" />
|
||||
</template>
|
||||
<slot />
|
||||
<template v-if="$slots.footer" #footer>
|
||||
<slot name="footer" />
|
||||
</template>
|
||||
</Card>
|
||||
<div :class="ui.inner">
|
||||
<div :class="[ui.container, ui.spacing]">
|
||||
<TransitionChild as="template" :appear="appear" v-bind="ui.transition">
|
||||
<DialogPanel :class="[ui.base, ui.width, ui.height, ui.background, ui.ring, ui.rounded, ui.shadow]">
|
||||
<slot />
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
@@ -42,131 +18,75 @@
|
||||
</TransitionRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Dialog, DialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
|
||||
import { classNames } from '../../utils'
|
||||
import Card from '../layout/Card.vue'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
appear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: () => $ui.modal.wrapper
|
||||
},
|
||||
innerClass: {
|
||||
type: String,
|
||||
default: () => $ui.modal.inner
|
||||
},
|
||||
innerStyle: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
default: () => $ui.modal.container
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: () => $ui.modal.base
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: () => $ui.modal.background
|
||||
},
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
overlayBackgroundClass: {
|
||||
type: String,
|
||||
default: () => $ui.modal.overlay.background
|
||||
},
|
||||
overlayTransitionClass: {
|
||||
type: Object,
|
||||
default: () => $ui.modal.overlay.transition
|
||||
},
|
||||
shadowClass: {
|
||||
type: String,
|
||||
default: () => $ui.modal.shadow
|
||||
},
|
||||
ringClass: {
|
||||
type: String,
|
||||
default: () => $ui.modal.ring
|
||||
},
|
||||
roundedClass: {
|
||||
type: String,
|
||||
default: () => $ui.modal.rounded
|
||||
},
|
||||
widthClass: {
|
||||
type: String,
|
||||
default: () => $ui.modal.width
|
||||
},
|
||||
transition: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
transitionClass: {
|
||||
type: Object,
|
||||
default: () => $ui.modal.transition
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'close'])
|
||||
|
||||
const isOpen = computed({
|
||||
get () {
|
||||
return props.modelValue
|
||||
},
|
||||
set (value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const modalClass = computed(() => {
|
||||
return classNames(
|
||||
props.baseClass,
|
||||
props.widthClass,
|
||||
props.backgroundClass,
|
||||
props.shadowClass,
|
||||
props.ringClass,
|
||||
props.roundedClass
|
||||
)
|
||||
})
|
||||
|
||||
const overlayTransition = computed(() => {
|
||||
if (!props.transition) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return props.overlayTransitionClass
|
||||
})
|
||||
|
||||
const modalTransition = computed(() => {
|
||||
if (!props.transition) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return props.transitionClass
|
||||
})
|
||||
|
||||
function close (value: boolean) {
|
||||
isOpen.value = value
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'UModal',
|
||||
inheritAttrs: false
|
||||
}
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { Dialog, DialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
TransitionRoot,
|
||||
TransitionChild
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
appear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
transition: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.modal>>,
|
||||
default: () => appConfig.ui.modal
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'close'],
|
||||
setup (props, { emit }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.modal>>(() => defu({}, props.ui, appConfig.ui.modal))
|
||||
|
||||
const isOpen = computed({
|
||||
get () {
|
||||
return props.modelValue
|
||||
},
|
||||
set (value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
function close (value: boolean) {
|
||||
isOpen.value = value
|
||||
emit('close')
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
isOpen,
|
||||
close
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,211 +1,191 @@
|
||||
<template>
|
||||
<transition appear v-bind="transitionClass">
|
||||
<transition appear v-bind="ui.transition">
|
||||
<div
|
||||
:class="['z-50 w-full pointer-events-auto', backgroundClass, roundedClass, shadowClass]"
|
||||
:class="[ui.wrapper, ui.background, ui.rounded, ui.shadow]"
|
||||
@mouseover="onMouseover"
|
||||
@mouseleave="onMouseleave"
|
||||
>
|
||||
<div :class="['relative overflow-hidden', roundedClass, ringClass]">
|
||||
<div :class="[ui.container, ui.rounded, ui.ring]">
|
||||
<div class="p-4">
|
||||
<div class="flex gap-3" :class="{ 'items-start': description, 'items-center': !description }">
|
||||
<div v-if="iconName" class="flex-shrink-0">
|
||||
<Icon :name="iconName" :class="iconClass" />
|
||||
</div>
|
||||
<Icon v-if="icon" :name="icon" :class="ui.icon" />
|
||||
<Avatar v-if="avatar" v-bind="avatar" :class="ui.avatar" />
|
||||
|
||||
<div class="w-0 flex-1">
|
||||
<p class="text-sm font-medium u-text-gray-900">
|
||||
<p :class="ui.title">
|
||||
{{ title }}
|
||||
</p>
|
||||
<p v-if="description" class="mt-1 text-sm leading-5 u-text-gray-500">
|
||||
<p v-if="description" :class="ui.description">
|
||||
{{ description }}
|
||||
</p>
|
||||
|
||||
<div v-if="description && actions.length" class="mt-3 flex items-center gap-6">
|
||||
<button v-for="(action, index) of actions" :key="index" type="button" class="text-sm font-medium focus:outline-none text-primary-500 dark:text-primary-400 hover:text-primary-400 dark:hover:text-primary-500" @click.stop="onAction(action)">
|
||||
{{ action.label }}
|
||||
</button>
|
||||
<div v-if="description && actions.length" class="mt-3 flex items-center gap-2">
|
||||
<Button v-for="(action, index) of actions" :key="index" v-bind="{ ...ui.default.action, ...action }" @click.stop="onAction(action)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-shrink-0 flex items-center gap-3">
|
||||
<div v-if="!description && actions.length" class="flex items-center gap-2">
|
||||
<button v-for="(action, index) of actions" :key="index" type="button" class="text-sm font-medium focus:outline-none text-primary-500 dark:text-primary-400 hover:text-primary-400 dark:hover:text-primary-500" @click.stop="onAction(action)">
|
||||
{{ action.label }}
|
||||
</button>
|
||||
<Button v-for="(action, index) of actions" :key="index" v-bind="{ ...ui.default.action, ...action }" @click.stop="onAction(action)" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="inline-flex transition duration-150 ease-in-out u-text-gray-400 focus:outline-none hover:u-text-gray-500 focus:u-text-gray-500"
|
||||
@click.stop="onClose"
|
||||
>
|
||||
<span class="sr-only">Close</span>
|
||||
<Icon :name="$ui.notification.close.icon.name" class="w-5 h-5" />
|
||||
</button>
|
||||
<Button v-if="close" v-bind="{ ...ui.default.close, ...close }" @click.stop="onClose" />
|
||||
</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 v-if="timeout" :class="ui.progress" :style="progressBarStyle" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watchEffect } from 'vue'
|
||||
<script lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watchEffect, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import Avatar from '../elements/Avatar.vue'
|
||||
import Button from '../elements/Button.vue'
|
||||
import { useTimer } from '../../composables/useTimer'
|
||||
import { classNames } from '../../utils'
|
||||
import type { ToastNotificationAction } from '../../types'
|
||||
import $ui from '#build/ui'
|
||||
import type { Avatar as AvatarType } from '../../types/avatar'
|
||||
import type { Button as ButtonType } from '../../types/button'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Icon,
|
||||
Avatar,
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
Button
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: null,
|
||||
validator (value: string) {
|
||||
return Object.keys($ui.notification.type).includes(value)
|
||||
props: {
|
||||
id: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
avatar: {
|
||||
type: Object as PropType<Partial<AvatarType>>,
|
||||
default: null
|
||||
},
|
||||
close: {
|
||||
type: Object as PropType<Partial<ButtonType>>,
|
||||
default: () => appConfig.ui.notification.default.close
|
||||
},
|
||||
timeout: {
|
||||
type: Number,
|
||||
default: 5000
|
||||
},
|
||||
actions: {
|
||||
type: Array as PropType<ToastNotificationAction[]>,
|
||||
default: () => []
|
||||
},
|
||||
callback: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.notification>>,
|
||||
default: () => appConfig.ui.notification
|
||||
}
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: () => $ui.notification.background
|
||||
},
|
||||
shadowClass: {
|
||||
type: String,
|
||||
default: () => $ui.notification.shadow
|
||||
},
|
||||
ringClass: {
|
||||
type: String,
|
||||
default: () => $ui.notification.ring
|
||||
},
|
||||
roundedClass: {
|
||||
type: String,
|
||||
default: () => $ui.notification.rounded
|
||||
},
|
||||
transitionClass: {
|
||||
type: Object,
|
||||
default: () => $ui.notification.transition
|
||||
},
|
||||
customClass: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
iconBaseClass: {
|
||||
type: String,
|
||||
default: () => $ui.notification.icon.base
|
||||
},
|
||||
timeout: {
|
||||
type: Number,
|
||||
default: 5000
|
||||
},
|
||||
actions: {
|
||||
type: Array as PropType<{
|
||||
label: string,
|
||||
click: Function
|
||||
}[]>,
|
||||
default: () => []
|
||||
},
|
||||
callback: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
emits: ['close'],
|
||||
setup (props, { emit }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const ui = computed<Partial<typeof appConfig.ui.notification>>(() => defu({}, props.ui, appConfig.ui.notification))
|
||||
|
||||
let timer: any = null
|
||||
const remaining = ref(props.timeout)
|
||||
let timer: any = null
|
||||
const remaining = ref(props.timeout)
|
||||
|
||||
const iconName = computed(() => {
|
||||
return props.icon || $ui.notification.type[props.type]
|
||||
})
|
||||
const progressBarStyle = computed(() => {
|
||||
const remainingPercent = remaining.value / props.timeout * 100
|
||||
|
||||
const iconClass = computed(() => {
|
||||
return classNames(
|
||||
props.iconBaseClass,
|
||||
$ui.notification.icon.color[props.type] || 'u-text-gray-400'
|
||||
)
|
||||
})
|
||||
return { width: `${remainingPercent || 0}%` }
|
||||
})
|
||||
|
||||
const progressBarStyle = computed(() => {
|
||||
const remainingPercent = remaining.value / props.timeout * 100
|
||||
return { width: `${remainingPercent || 0}%` }
|
||||
})
|
||||
function onMouseover () {
|
||||
if (timer) {
|
||||
timer.pause()
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseover () {
|
||||
if (timer) {
|
||||
timer.pause()
|
||||
}
|
||||
}
|
||||
function onMouseleave () {
|
||||
if (timer) {
|
||||
timer.resume()
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseleave () {
|
||||
if (timer) {
|
||||
timer.resume()
|
||||
}
|
||||
}
|
||||
function onClose () {
|
||||
if (timer) {
|
||||
timer.stop()
|
||||
}
|
||||
|
||||
function onClose () {
|
||||
if (timer) {
|
||||
timer.stop()
|
||||
}
|
||||
if (props.callback) {
|
||||
props.callback()
|
||||
}
|
||||
|
||||
if (props.callback) {
|
||||
props.callback()
|
||||
}
|
||||
emit('close')
|
||||
}
|
||||
|
||||
emit('close')
|
||||
}
|
||||
function onAction (action: ToastNotificationAction) {
|
||||
if (timer) {
|
||||
timer.stop()
|
||||
}
|
||||
|
||||
function onAction (action: ToastNotificationAction) {
|
||||
if (timer) {
|
||||
timer.stop()
|
||||
}
|
||||
if (action.click) {
|
||||
action.click()
|
||||
}
|
||||
|
||||
if (action.click) {
|
||||
action.click()
|
||||
}
|
||||
emit('close')
|
||||
}
|
||||
|
||||
emit('close')
|
||||
}
|
||||
onMounted(() => {
|
||||
if (!props.timeout) {
|
||||
return
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!props.timeout) {
|
||||
return
|
||||
}
|
||||
timer = useTimer(() => {
|
||||
onClose()
|
||||
}, props.timeout)
|
||||
|
||||
timer = useTimer(() => {
|
||||
onClose()
|
||||
}, props.timeout)
|
||||
watchEffect(() => {
|
||||
remaining.value = timer.remaining.value
|
||||
})
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
remaining.value = timer.remaining.value
|
||||
})
|
||||
})
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
timer.stop()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
timer.stop()
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
progressBarStyle,
|
||||
onMouseover,
|
||||
onMouseleave,
|
||||
onClose,
|
||||
onAction
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'UNotification' }
|
||||
</script>
|
||||
|
||||
@@ -1,31 +1,57 @@
|
||||
<template>
|
||||
<div class="fixed bottom-0 right-0 flex flex-col justify-end w-full z-[55] sm:w-96">
|
||||
<div v-if="notifications.length" class="px-4 py-6 space-y-3 overflow-y-auto sm:px-6">
|
||||
<div
|
||||
v-for="notification of notifications"
|
||||
:key="notification.id"
|
||||
>
|
||||
<div :class="ui.wrapper">
|
||||
<div v-if="notifications.length" :class="ui.container">
|
||||
<div v-for="notification of notifications" :key="notification.id">
|
||||
<Notification
|
||||
v-bind="notification"
|
||||
:class="notification.click && 'cursor-pointer'"
|
||||
@click="notification.click && notification.click(notification)"
|
||||
@close="toast.removeNotification(notification.id)"
|
||||
@close="toast.remove(notification.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import type { ToastNotification } from '../../types'
|
||||
import { useToast } from '../../composables/useToast'
|
||||
import Notification from './Notification.vue'
|
||||
import { useState } from '#imports'
|
||||
import { useState, useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
const toast = useToast()
|
||||
const notifications = useState<ToastNotification[]>('notifications', () => [])
|
||||
</script>
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'UNotifications' }
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Notification
|
||||
},
|
||||
props: {
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.notifications>>,
|
||||
default: () => appConfig.ui.notifications
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.notifications>>(() => defu({}, props.ui, appConfig.ui.notifications))
|
||||
|
||||
const toast = useToast()
|
||||
const notifications = useState<ToastNotification[]>('notifications', () => [])
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
toast,
|
||||
notifications
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Popover v-slot="{ open, close }" :class="wrapperClass" @mouseleave="onMouseLeave">
|
||||
<Popover v-slot="{ open, close }" :class="ui.wrapper" @mouseleave="onMouseLeave">
|
||||
<PopoverButton
|
||||
ref="trigger"
|
||||
as="div"
|
||||
@@ -15,9 +15,9 @@
|
||||
</slot>
|
||||
</PopoverButton>
|
||||
|
||||
<div v-if="open" ref="container" :class="[containerClass, widthClass]" @mouseover="onMouseOver">
|
||||
<transition appear v-bind="transitionClass">
|
||||
<PopoverPanel :class="[baseClass, ringClass, roundedClass, shadowClass, backgroundClass]" static>
|
||||
<div v-if="open" ref="container" :class="[ui.container, ui.width]" @mouseover="onMouseOver">
|
||||
<transition appear v-bind="ui.transition">
|
||||
<PopoverPanel :class="[ui.base, ui.background, ui.ring, ui.rounded, ui.shadow]" static>
|
||||
<slot name="panel" :open="open" :close="close" />
|
||||
</PopoverPanel>
|
||||
</transition>
|
||||
@@ -25,140 +25,131 @@
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
<script lang="ts">
|
||||
import { computed, ref, onMounted, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { PopperOptions } from '../../types'
|
||||
import $ui from '#build/ui'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'click',
|
||||
validator: (value: string) => {
|
||||
return ['click', 'hover'].includes(value)
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Popover,
|
||||
PopoverButton,
|
||||
PopoverPanel
|
||||
},
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'click',
|
||||
validator: (value: string) => {
|
||||
return ['click', 'hover'].includes(value)
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
openDelay: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
closeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
popper: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.popover>>,
|
||||
default: () => appConfig.ui.popover
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: () => $ui.popover.wrapper
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
default: () => $ui.popover.container
|
||||
},
|
||||
widthClass: {
|
||||
type: String,
|
||||
default: () => $ui.popover.width
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: () => $ui.popover.base
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: () => $ui.popover.background
|
||||
},
|
||||
shadowClass: {
|
||||
type: String,
|
||||
default: () => $ui.popover.shadow
|
||||
},
|
||||
roundedClass: {
|
||||
type: String,
|
||||
default: () => $ui.popover.rounded
|
||||
},
|
||||
ringClass: {
|
||||
type: String,
|
||||
default: () => $ui.popover.ring
|
||||
},
|
||||
transitionClass: {
|
||||
type: Object,
|
||||
default: () => $ui.popover.transition
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
openDelay: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
closeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
setup (props) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions, $ui.popover.popperOptions))
|
||||
const ui = computed<Partial<typeof appConfig.ui.popover>>(() => defu({}, props.ui, appConfig.ui.popover))
|
||||
|
||||
const [trigger, container] = usePopper(popperOptions.value)
|
||||
const popper = computed<PopperOptions>(() => defu({}, props.popper, ui.value.popper as PopperOptions))
|
||||
|
||||
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/popover/popover.ts#L151
|
||||
const popoverApi = ref<any>(null)
|
||||
const [trigger, container] = usePopper(popper.value)
|
||||
|
||||
let openTimeout: NodeJS.Timeout | null = null
|
||||
let closeTimeout: NodeJS.Timeout | null = null
|
||||
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/popover/popover.ts#L151
|
||||
const popoverApi = ref<any>(null)
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
// @ts-expect-error internals
|
||||
const popoverProvides = trigger.value?.$.provides
|
||||
if (!popoverProvides) {
|
||||
return
|
||||
let openTimeout: NodeJS.Timeout | null = null
|
||||
let closeTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
// @ts-expect-error internals
|
||||
const popoverProvides = trigger.value?.$.provides
|
||||
if (!popoverProvides) {
|
||||
return
|
||||
}
|
||||
const popoverProvidesSymbols = Object.getOwnPropertySymbols(popoverProvides)
|
||||
popoverApi.value = popoverProvidesSymbols.length && popoverProvides[popoverProvidesSymbols[0]]
|
||||
}, 200)
|
||||
})
|
||||
|
||||
function onMouseOver () {
|
||||
if (props.mode !== 'hover' || !popoverApi.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// cancel programmed closing
|
||||
if (closeTimeout) {
|
||||
clearTimeout(closeTimeout)
|
||||
closeTimeout = null
|
||||
}
|
||||
// dropdown already open
|
||||
if (popoverApi.value.popoverState === 0) {
|
||||
return
|
||||
}
|
||||
openTimeout = openTimeout || setTimeout(() => {
|
||||
popoverApi.value.togglePopover && popoverApi.value.togglePopover()
|
||||
openTimeout = null
|
||||
}, props.openDelay)
|
||||
}
|
||||
const popoverProvidesSymbols = Object.getOwnPropertySymbols(popoverProvides)
|
||||
popoverApi.value = popoverProvidesSymbols.length && popoverProvides[popoverProvidesSymbols[0]]
|
||||
}, 200)
|
||||
|
||||
function onMouseLeave () {
|
||||
if (props.mode !== 'hover' || !popoverApi.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// cancel programmed opening
|
||||
if (openTimeout) {
|
||||
clearTimeout(openTimeout)
|
||||
openTimeout = null
|
||||
}
|
||||
// dropdown already closed
|
||||
if (popoverApi.value.popoverState === 1) {
|
||||
return
|
||||
}
|
||||
closeTimeout = closeTimeout || setTimeout(() => {
|
||||
popoverApi.value.closePopover && popoverApi.value.closePopover()
|
||||
closeTimeout = null
|
||||
}, props.closeDelay)
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
trigger,
|
||||
container,
|
||||
onMouseOver,
|
||||
onMouseLeave
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function onMouseOver () {
|
||||
if (props.mode !== 'hover' || !popoverApi.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// cancel programmed closing
|
||||
if (closeTimeout) {
|
||||
clearTimeout(closeTimeout)
|
||||
closeTimeout = null
|
||||
}
|
||||
// dropdown already open
|
||||
if (popoverApi.value.popoverState === 0) {
|
||||
return
|
||||
}
|
||||
openTimeout = openTimeout || setTimeout(() => {
|
||||
popoverApi.value.togglePopover && popoverApi.value.togglePopover()
|
||||
openTimeout = null
|
||||
}, props.openDelay)
|
||||
}
|
||||
|
||||
function onMouseLeave () {
|
||||
if (props.mode !== 'hover' || !popoverApi.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// cancel programmed opening
|
||||
if (openTimeout) {
|
||||
clearTimeout(openTimeout)
|
||||
openTimeout = null
|
||||
}
|
||||
// dropdown already closed
|
||||
if (popoverApi.value.popoverState === 1) {
|
||||
return
|
||||
}
|
||||
closeTimeout = closeTimeout || setTimeout(() => {
|
||||
popoverApi.value.closePopover && popoverApi.value.closePopover()
|
||||
closeTimeout = null
|
||||
}, props.closeDelay)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'UPopover' }
|
||||
</script>
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
<template>
|
||||
<TransitionRoot as="template" :appear="appear" :show="isOpen">
|
||||
<Dialog :class="[wrapperClass, { 'justify-end': side === 'right' }]" @close="close">
|
||||
<TransitionChild
|
||||
v-if="overlay"
|
||||
as="template"
|
||||
:appear="appear"
|
||||
v-bind="overlayTransition"
|
||||
>
|
||||
<div class="fixed inset-0 transition-opacity" :class="overlayBackgroundClass" />
|
||||
<Dialog :class="[ui.wrapper, { 'justify-end': side === 'right' }]" @close="close">
|
||||
<TransitionChild v-if="overlay" as="template" :appear="appear" v-bind="ui.overlay.transition">
|
||||
<div :class="[ui.overlay.base, ui.overlay.background]" />
|
||||
</TransitionChild>
|
||||
|
||||
<TransitionChild
|
||||
as="template"
|
||||
:appear="appear"
|
||||
v-bind="slideoverTransition"
|
||||
>
|
||||
<DialogPanel :class="slideoverClass">
|
||||
<div v-if="$slots.header" :class="headerClass">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<TransitionChild as="template" :appear="appear" v-bind="transitionClass">
|
||||
<DialogPanel :class="[ui.base, ui.width, ui.background, ui.ring]">
|
||||
<slot />
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
@@ -26,116 +14,95 @@
|
||||
</TransitionRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { WritableComputedRef, PropType } from 'vue'
|
||||
import { Dialog, DialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
|
||||
import { classNames } from '../../utils'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
appear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
side: {
|
||||
type: String,
|
||||
default: 'left',
|
||||
validator: (value: string) => ['left', 'right'].includes(value)
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: () => $ui.slideover.wrapper
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: () => $ui.slideover.base
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: () => $ui.slideover.background
|
||||
},
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
overlayBackgroundClass: {
|
||||
type: String,
|
||||
default: () => $ui.slideover.overlay.background
|
||||
},
|
||||
overlayTransitionClass: {
|
||||
type: Object,
|
||||
default: () => $ui.slideover.overlay.transition
|
||||
},
|
||||
widthClass: {
|
||||
type: String,
|
||||
default: () => $ui.slideover.width
|
||||
},
|
||||
headerClass: {
|
||||
type: String,
|
||||
default: () => $ui.slideover.header
|
||||
},
|
||||
transition: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
transitionClass: {
|
||||
type: Object,
|
||||
default: () => $ui.slideover.transition
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'close'])
|
||||
|
||||
const isOpen: WritableComputedRef<boolean> = computed({
|
||||
get () {
|
||||
return props.modelValue
|
||||
},
|
||||
set (value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const slideoverClass = computed(() => {
|
||||
return classNames(
|
||||
props.baseClass,
|
||||
props.widthClass,
|
||||
props.backgroundClass
|
||||
)
|
||||
})
|
||||
|
||||
const overlayTransition = computed(() => {
|
||||
if (!props.transition) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return props.overlayTransitionClass
|
||||
})
|
||||
|
||||
const slideoverTransition = computed(() => {
|
||||
if (!props.transition) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
enterFrom: props.side === 'left' ? '-translate-x-full' : 'translate-x-full',
|
||||
enterTo: 'translate-x-0',
|
||||
leaveFrom: 'translate-x-0',
|
||||
leaveTo: props.side === 'left' ? '-translate-x-full' : 'translate-x-full',
|
||||
...props.transitionClass
|
||||
}
|
||||
})
|
||||
|
||||
function close (value: boolean) {
|
||||
isOpen.value = value
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'USlideover' }
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import type { WritableComputedRef, PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { Dialog, DialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
TransitionRoot,
|
||||
TransitionChild
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
appear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
side: {
|
||||
type: String,
|
||||
default: 'left',
|
||||
validator: (value: string) => ['left', 'right'].includes(value)
|
||||
},
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
transition: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.slideover>>,
|
||||
default: () => appConfig.ui.slideover
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'close'],
|
||||
setup (props, { emit }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.slideover>>(() => defu({}, props.ui, appConfig.ui.slideover))
|
||||
|
||||
const isOpen: WritableComputedRef<boolean> = computed({
|
||||
get () {
|
||||
return props.modelValue
|
||||
},
|
||||
set (value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const transitionClass = computed(() => {
|
||||
if (!props.transition) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
...ui.value.transition,
|
||||
enterFrom: props.side === 'left' ? '-translate-x-full' : 'translate-x-full',
|
||||
enterTo: 'translate-x-0',
|
||||
leaveFrom: 'translate-x-0',
|
||||
leaveTo: props.side === 'left' ? '-translate-x-full' : 'translate-x-full'
|
||||
}
|
||||
})
|
||||
|
||||
function close (value: boolean) {
|
||||
isOpen.value = value
|
||||
emit('close')
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
isOpen,
|
||||
transitionClass,
|
||||
close
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<div ref="trigger" :class="wrapperClass" @mouseover="onMouseOver" @mouseleave="onMouseLeave">
|
||||
<div ref="trigger" :class="ui.wrapper" @mouseover="onMouseOver" @mouseleave="onMouseLeave">
|
||||
<slot :open="open">
|
||||
Hover me
|
||||
hover
|
||||
</slot>
|
||||
|
||||
<div v-if="open && !prevent" ref="container" :class="[containerClass, widthClass]">
|
||||
<transition appear v-bind="transitionClass">
|
||||
<div :class="[baseClass, backgroundClass, roundedClass, shadowClass, ringClass]">
|
||||
<div v-if="open && !prevent" ref="container" :class="[ui.container, ui.width]">
|
||||
<transition appear v-bind="ui.transition">
|
||||
<div :class="[ui.base, ui.background, ui.rounded, ui.shadow, ui.ring]">
|
||||
<slot name="text">
|
||||
{{ text }}
|
||||
</slot>
|
||||
|
||||
<span v-if="shortcuts?.length" :class="shortcutsClass">
|
||||
<span class="mr-1 u-text-gray-700">·</span>
|
||||
<kbd v-for="shortcut of shortcuts" :key="shortcut" class="flex items-center justify-center font-sans px-1 h-4 min-w-[16px] text-[10px] u-bg-gray-100 rounded u-text-gray-900">
|
||||
<span v-if="shortcuts?.length" :class="ui.shortcuts">
|
||||
<span class="mr-1 text-gray-700 dark:text-gray-200">·</span>
|
||||
<kbd v-for="shortcut of shortcuts" :key="shortcut" class="flex items-center justify-center font-sans px-1 h-4 min-w-[16px] text-[10px] bg-gray-100 dark:bg-gray-800 rounded text-gray-900 dark:text-white">
|
||||
{{ shortcut }}
|
||||
</kbd>
|
||||
</span>
|
||||
@@ -23,125 +23,108 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script lang="ts">
|
||||
import { computed, ref, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { PopperOptions } from '../../types'
|
||||
import $ui from '#build/ui'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
default: null
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
prevent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
shortcuts: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
openDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
closeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
popper: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.tooltip>>,
|
||||
default: () => appConfig.ui.tooltip
|
||||
}
|
||||
},
|
||||
prevent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
shortcuts: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.wrapper
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.container
|
||||
},
|
||||
widthClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.width
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.background
|
||||
},
|
||||
shadowClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.shadow
|
||||
},
|
||||
ringClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.ring
|
||||
},
|
||||
roundedClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.rounded
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.base
|
||||
},
|
||||
transitionClass: {
|
||||
type: Object,
|
||||
default: () => $ui.tooltip.transition
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
shortcutsClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.shortcuts
|
||||
},
|
||||
openDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
closeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
setup (props) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.tooltip>>(() => defu({}, props.ui, appConfig.ui.tooltip))
|
||||
|
||||
const popper = computed<PopperOptions>(() => defu({}, props.popper, ui.value.popper as PopperOptions))
|
||||
|
||||
const [trigger, container] = usePopper(popper.value)
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
let openTimeout: NodeJS.Timeout | null = null
|
||||
let closeTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
// Methods
|
||||
|
||||
function onMouseOver () {
|
||||
// cancel programmed closing
|
||||
if (closeTimeout) {
|
||||
clearTimeout(closeTimeout)
|
||||
closeTimeout = null
|
||||
}
|
||||
// dropdown already open
|
||||
if (open.value) {
|
||||
return
|
||||
}
|
||||
openTimeout = openTimeout || setTimeout(() => {
|
||||
open.value = true
|
||||
openTimeout = null
|
||||
}, props.openDelay)
|
||||
}
|
||||
|
||||
function onMouseLeave () {
|
||||
// cancel programmed opening
|
||||
if (openTimeout) {
|
||||
clearTimeout(openTimeout)
|
||||
openTimeout = null
|
||||
}
|
||||
// dropdown already closed
|
||||
if (!open.value) {
|
||||
return
|
||||
}
|
||||
closeTimeout = closeTimeout || setTimeout(() => {
|
||||
open.value = false
|
||||
closeTimeout = null
|
||||
}, props.closeDelay)
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
trigger,
|
||||
container,
|
||||
open,
|
||||
onMouseOver,
|
||||
onMouseLeave
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions, $ui.tooltip.popperOptions))
|
||||
|
||||
const [trigger, container] = usePopper(popperOptions.value)
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
let openTimeout: NodeJS.Timeout | null = null
|
||||
let closeTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
// Methods
|
||||
|
||||
function onMouseOver () {
|
||||
// cancel programmed closing
|
||||
if (closeTimeout) {
|
||||
clearTimeout(closeTimeout)
|
||||
closeTimeout = null
|
||||
}
|
||||
// dropdown already open
|
||||
if (open.value) {
|
||||
return
|
||||
}
|
||||
openTimeout = openTimeout || setTimeout(() => {
|
||||
open.value = true
|
||||
openTimeout = null
|
||||
}, props.openDelay)
|
||||
}
|
||||
|
||||
function onMouseLeave () {
|
||||
// cancel programmed opening
|
||||
if (openTimeout) {
|
||||
clearTimeout(openTimeout)
|
||||
openTimeout = null
|
||||
}
|
||||
// dropdown already closed
|
||||
if (!open.value) {
|
||||
return
|
||||
}
|
||||
closeTimeout = closeTimeout || setTimeout(() => {
|
||||
open.value = false
|
||||
closeTimeout = null
|
||||
}, props.closeDelay)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'UTooltip' }
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user