mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-29 19:30:37 +01:00
@@ -2,7 +2,7 @@ export default defineAppConfig({
|
|||||||
toaster: {
|
toaster: {
|
||||||
position: 'bottom-right' as const,
|
position: 'bottom-right' as const,
|
||||||
expand: true,
|
expand: true,
|
||||||
duration: 60000
|
duration: 5000
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
primary: 'sky',
|
primary: 'sky',
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ function removeToast () {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col items-center gap-8">
|
<div class="flex flex-col items-center gap-8">
|
||||||
<div>
|
<div class="flex flex-col gap-2">
|
||||||
<URadioGroup v-model="appConfig.toaster.position" :options="positions" />
|
<URadioGroup v-model="appConfig.toaster.position" :options="positions" />
|
||||||
<UCheckbox v-model="appConfig.toaster.expand" label="Expand" class="mt-1" />
|
<UCheckbox v-model="appConfig.toaster.expand" label="Expand" class="mt-1" />
|
||||||
<UInput v-model="appConfig.toaster.duration" label="Duration" type="number" class="mt-1" />
|
<UInput v-model="appConfig.toaster.duration" label="Duration" type="number" class="mt-1" />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { ToastRootProps, ToastRootEmits } from 'radix-vue'
|
|||||||
import type { AppConfig } from '@nuxt/schema'
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
import _appConfig from '#build/app.config'
|
import _appConfig from '#build/app.config'
|
||||||
import theme from '#build/ui/toast'
|
import theme from '#build/ui/toast'
|
||||||
import type { AvatarProps, ButtonProps, IconProps } from '#ui/types'
|
import type { AvatarProps, ButtonProps, IconProps, ToasterContext } from '#ui/types'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { toast: Partial<typeof theme> } }
|
const appConfig = _appConfig as AppConfig & { ui: { toast: Partial<typeof theme> } }
|
||||||
|
|
||||||
@@ -19,17 +19,17 @@ export interface ToastProps extends Omit<ToastRootProps, 'asChild' | 'forceMount
|
|||||||
icon?: IconProps['name']
|
icon?: IconProps['name']
|
||||||
avatar?: AvatarProps
|
avatar?: AvatarProps
|
||||||
color?: ToastVariants['color']
|
color?: ToastVariants['color']
|
||||||
actions?: (ButtonProps & { click?: () => void })[]
|
actions?: ButtonProps[]
|
||||||
close?: ButtonProps | null
|
close?: ButtonProps | null
|
||||||
class?: any
|
class?: any
|
||||||
ui?: Partial<typeof toast.slots>
|
ui?: Partial<typeof toast.slots>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToastEmits extends ToastRootEmits { }
|
export interface ToastEmits extends ToastRootEmits {}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, inject, onMounted } from 'vue'
|
||||||
import { ToastRoot, ToastTitle, ToastDescription, ToastAction, ToastClose, useForwardPropsEmits } from 'radix-vue'
|
import { ToastRoot, ToastTitle, ToastDescription, ToastAction, ToastClose, useForwardPropsEmits } from 'radix-vue'
|
||||||
import { reactivePick } from '@vueuse/core'
|
import { reactivePick } from '@vueuse/core'
|
||||||
import { useAppConfig } from '#imports'
|
import { useAppConfig } from '#imports'
|
||||||
@@ -38,14 +38,15 @@ import { UIcon, UAvatar } from '#components'
|
|||||||
const props = defineProps<ToastProps>()
|
const props = defineProps<ToastProps>()
|
||||||
const emits = defineEmits<ToastEmits>()
|
const emits = defineEmits<ToastEmits>()
|
||||||
|
|
||||||
|
const toaster = inject<ToasterContext>('Toaster')
|
||||||
|
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'duration', 'open', 'type'), emits)
|
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'duration', 'open', 'type'), emits)
|
||||||
|
|
||||||
const multiline = computed(() => !!props.title && !!props.description)
|
const multiline = computed(() => !!props.title && !!props.description)
|
||||||
|
const duration = computed(() => props.duration || toaster?.value.duration)
|
||||||
|
|
||||||
const ui = computed(() => tv({ extend: toast, slots: props.ui })({
|
const ui = computed(() => tv({ extend: toast, slots: props.ui })({ color: props.color }))
|
||||||
color: props.color
|
|
||||||
}))
|
|
||||||
|
|
||||||
const el = ref()
|
const el = ref()
|
||||||
const height = ref(0)
|
const height = ref(0)
|
||||||
@@ -56,7 +57,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
height.value = el.value.$el.getBoundingClientRect().height
|
height.value = el.value.$el.getBoundingClientRect()?.height
|
||||||
}, 0)
|
}, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -66,7 +67,13 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ToastRoot ref="el" v-bind="rootProps" :class="ui.root({ class: props.class, multiline })" :style="{ '--height': height }">
|
<ToastRoot
|
||||||
|
ref="el"
|
||||||
|
v-slot="{ remaining }"
|
||||||
|
v-bind="rootProps"
|
||||||
|
:class="ui.root({ class: props.class, multiline })"
|
||||||
|
:style="{ '--height': height }"
|
||||||
|
>
|
||||||
<UAvatar v-if="avatar" size="2xl" v-bind="avatar" :class="ui.avatar()" />
|
<UAvatar v-if="avatar" size="2xl" v-bind="avatar" :class="ui.avatar()" />
|
||||||
<UIcon v-else-if="icon" :name="icon" :class="ui.icon()" />
|
<UIcon v-else-if="icon" :name="icon" :class="ui.icon()" />
|
||||||
|
|
||||||
@@ -110,7 +117,6 @@ defineExpose({
|
|||||||
</ToastClose>
|
</ToastClose>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="ui.progress()" />
|
<div v-if="remaining && duration" :class="ui.progress()" :style="{ width: `${remaining / duration * 100}%` }" />
|
||||||
<div :class="ui.mask()" />
|
|
||||||
</ToastRoot>
|
</ToastRoot>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { ComputedRef } from 'vue'
|
||||||
import { tv, type VariantProps } from 'tailwind-variants'
|
import { tv, type VariantProps } from 'tailwind-variants'
|
||||||
import type { ToastProviderProps } from 'radix-vue'
|
import type { ToastProviderProps } from 'radix-vue'
|
||||||
import type { AppConfig } from '@nuxt/schema'
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
@@ -17,17 +18,21 @@ export interface ToasterProps extends Omit<ToastProviderProps, 'swipeDirection'>
|
|||||||
class?: any
|
class?: any
|
||||||
ui?: Partial<typeof toaster.slots>
|
ui?: Partial<typeof toaster.slots>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ToasterContext = ComputedRef<{
|
||||||
|
duration: number
|
||||||
|
}>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, provide } from 'vue'
|
||||||
import { ToastProvider, ToastViewport, useForwardProps } from 'radix-vue'
|
import { ToastProvider, ToastViewport, useForwardProps } from 'radix-vue'
|
||||||
import { reactivePick } from '@vueuse/core'
|
import { reactivePick } from '@vueuse/core'
|
||||||
import { useToast } from '#imports'
|
import { useToast } from '#imports'
|
||||||
import { UToast } from '#components'
|
import { UToast } from '#components'
|
||||||
import { omit } from '#ui/utils'
|
import { omit } from '#ui/utils'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ToasterProps>(), { expand: true })
|
const props = withDefaults(defineProps<ToasterProps>(), { expand: true, duration: 5000 })
|
||||||
|
|
||||||
const providerProps = useForwardProps(reactivePick(props, 'duration', 'label', 'swipeThreshold'))
|
const providerProps = useForwardProps(reactivePick(props, 'duration', 'label', 'swipeThreshold'))
|
||||||
|
|
||||||
@@ -73,6 +78,8 @@ const frontHeight = computed(() => refs.value[refs.value.length - 1]?.height ||
|
|||||||
function getOffset (index: number) {
|
function getOffset (index: number) {
|
||||||
return refs.value.slice(index + 1).reduce((acc, { height }) => acc + height + 16, 0)
|
return refs.value.slice(index + 1).reduce((acc, { height }) => acc + height + 16, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provide<ToasterContext>('Toaster', providerProps)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ export default (config: { colors: string[] }) => ({
|
|||||||
icon: 'shrink-0 size-5',
|
icon: 'shrink-0 size-5',
|
||||||
avatar: 'shrink-0',
|
avatar: 'shrink-0',
|
||||||
actions: 'flex gap-1.5 shrink-0',
|
actions: 'flex gap-1.5 shrink-0',
|
||||||
progress: 'absolute inset-0 rounded-lg border-b-2 z-[-1]',
|
progress: 'absolute inset-0 border-b-2 z-[-1]',
|
||||||
mask: 'absolute top-0 inset-0 bottom-[2px] bg-white dark:bg-gray-900 z-[-1]',
|
|
||||||
close: 'p-1'
|
close: 'p-1'
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
|
|||||||
Reference in New Issue
Block a user