feat: toast component
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<flowbite-themable-child tag="button" :apply="['background', 'hover', 'focus']" :class="wrapperClasses" :disabled="disabled">
|
||||
<flowbite-themable-child tag="button" :apply="appliableTheme" :class="wrapperClasses" :disabled="disabled">
|
||||
<div v-if="!isOutlineGradient && ($slots.prefix || loadingPrefix)" class="mr-2"> <!--automatically add mr class if slot provided or loading -->
|
||||
<spinner :color="spinnerColor" :size="spinnerSize" v-if="loadingPrefix" />
|
||||
<slot name="prefix" v-else />
|
||||
@@ -85,4 +85,9 @@ const loadingSuffix = computed(() => props.loading && props.loadingPosition ===
|
||||
const { wrapperClasses, spanClasses } = useButtonClasses(toRefs(props))
|
||||
const { color: spinnerColor, size: spinnerSize } = useButtonSpinner(toRefs(props))
|
||||
|
||||
const appliableTheme = computed(() => {
|
||||
if(['alternative', 'light'].includes(props.color)) return []
|
||||
return ['background', 'hover', 'focus']
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,32 +1,63 @@
|
||||
<template>
|
||||
<div id="toast-default" class="flex items-center w-full max-w-xs p-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800" role="alert">
|
||||
<div v-if="type !== 'empty'" :class="['inline-flex flex-shrink-0 justify-center items-center w-8 h-8 rounded-lg', typeClasses]">
|
||||
<svg v-if="type === 'success'" aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>
|
||||
<div v-if="visible" id="toast-default" :class="wrapperClasses" role="alert" >
|
||||
<flowbite-themable-child
|
||||
v-if="type !== 'empty' || $slots.icon"
|
||||
:class="[
|
||||
'inline-flex flex-shrink-0 justify-center items-center w-8 h-8 rounded-lg',
|
||||
typeClasses
|
||||
]"
|
||||
:apply="['background', 'text']"
|
||||
>
|
||||
<slot v-if="$slots.icon" :class="{ 'ml-3': type !== 'empty' }" name="icon" />
|
||||
<svg v-else-if="type === 'success'" aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>
|
||||
<svg v-else-if="type === 'danger'" aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
|
||||
<svg v-else-if="type === 'warning'" aria-hidden="true" class="text-orange-500 bg-orange-100 dark:bg-orange-700 dark:text-orange-200 w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>
|
||||
</div>
|
||||
<div class="ml-3 text-sm font-normal">
|
||||
<svg v-else-if="type === 'warning'" aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>
|
||||
</flowbite-themable-child>
|
||||
<div :class="[contentClasses, { 'ml-3': $slots.icon || type !== 'empty' }]">
|
||||
<slot />
|
||||
</div>
|
||||
<button type="button" class="ml-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700" data-dismiss-target="#toast-default" aria-label="Close">
|
||||
<button @click="onClose" v-if="closable" type="button" class="border-none ml-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700" aria-label="Close">
|
||||
<span class="sr-only">Close</span>
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { ToastPreset } from '@/components/Toast/types'
|
||||
import type { ToastAlign, ToastPreset } from '@/components/Toast/types'
|
||||
import type { PropType } from 'vue'
|
||||
import { useToastClasses } from './composables/useToastClasses'
|
||||
import { toRefs } from 'vue'
|
||||
import { ref, toRefs } from 'vue'
|
||||
import FlowbiteThemableChild
|
||||
from '@/components/utils/FlowbiteThemable/components/FlowbiteThemableChild/FlowbiteThemableChild.vue'
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String as PropType<ToastPreset>,
|
||||
default: 'empty',
|
||||
},
|
||||
alignment: {
|
||||
type: String as PropType<ToastAlign>,
|
||||
default: 'center',
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
divide: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const { typeClasses } = useToastClasses(toRefs(props))
|
||||
const visible = ref(true)
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const { typeClasses, wrapperClasses, contentClasses } = useToastClasses(toRefs(props))
|
||||
|
||||
const onClose = () => {
|
||||
emit('close')
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import type { ToastPreset } from '@/components/Toast/types'
|
||||
import { simplifyTailwindClasses } from '@/utils/simplifyTailwindClasses'
|
||||
import type { ToastAlign } from '@/components/Toast/types'
|
||||
|
||||
type UseToastClassesReturns = {
|
||||
typeClasses: Ref<string>
|
||||
wrapperClasses: Ref<string>
|
||||
contentClasses: Ref<string>
|
||||
}
|
||||
|
||||
type UseToastClassesProps = {
|
||||
type: Ref<ToastPreset>
|
||||
divide: Ref<boolean>
|
||||
alignment: Ref<ToastAlign>
|
||||
}
|
||||
|
||||
const typeClassesMap: Record<ToastPreset, string> = {
|
||||
@@ -17,13 +23,36 @@ const typeClassesMap: Record<ToastPreset, string> = {
|
||||
warning: 'text-orange-500 bg-orange-100 dark:bg-orange-700 dark:text-orange-200',
|
||||
}
|
||||
|
||||
|
||||
const wrapperAlignmentClasses: Record<ToastAlign, string> = {
|
||||
center: 'items-center',
|
||||
end: 'items-end',
|
||||
start: 'items-start',
|
||||
}
|
||||
const defaultWrapperClasses = 'flex w-full max-w-xs p-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800'
|
||||
|
||||
const defaultContentClasses = 'text-sm font-normal'
|
||||
|
||||
export function useToastClasses(props: UseToastClassesProps): UseToastClassesReturns {
|
||||
|
||||
const typeClasses = computed(() => {
|
||||
return typeClassesMap[props.type.value]
|
||||
})
|
||||
|
||||
const wrapperClasses = computed(() => {
|
||||
const alignmentClass = wrapperAlignmentClasses[props.alignment.value]
|
||||
if(props.divide.value) return simplifyTailwindClasses(defaultWrapperClasses, 'dark:divide-gray-700 divide-x divide-gray-200', alignmentClass)
|
||||
return simplifyTailwindClasses(defaultWrapperClasses, alignmentClass)
|
||||
})
|
||||
|
||||
const contentClasses = computed(() => {
|
||||
if(props.type.value !== 'empty' && props.divide.value) return simplifyTailwindClasses(defaultContentClasses, 'pl-3')
|
||||
return defaultContentClasses
|
||||
})
|
||||
|
||||
return {
|
||||
typeClasses,
|
||||
wrapperClasses,
|
||||
contentClasses,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export type ToastPreset = 'success' | 'warning' | 'danger' | 'empty'
|
||||
export type ToastAlign = 'start' | 'center' | 'end'
|
||||
|
||||
@@ -12,7 +12,8 @@ import type {
|
||||
ThemableChildrenApply,
|
||||
} from '@/components/utils/FlowbiteThemable/components/FlowbiteThemableChild/types'
|
||||
import { toRefs } from 'vue'
|
||||
import { simplifyTailwindClasses } from '../../../../../utils/simplifyTailwindClasses'
|
||||
import { simplifyTailwindClasses } from '@/utils/simplifyTailwindClasses'
|
||||
import type { FlowbiteTheme } from '@/components/utils/FlowbiteThemable/types'
|
||||
|
||||
const props = defineProps({
|
||||
apply: {
|
||||
@@ -23,6 +24,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
theme: {
|
||||
type: String as PropType<FlowbiteTheme>,
|
||||
default: undefined,
|
||||
},
|
||||
})
|
||||
|
||||
const { classes } = useFlowbiteThemableChildClasses(toRefs(props))
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
ThemableChildrenApply,
|
||||
} from '../types'
|
||||
import { useFlowbiteThemable } from '../../../composables/useFlowbiteThemable'
|
||||
import type { FlowbiteTheme } from '@/components/utils/FlowbiteThemable/types'
|
||||
|
||||
type UseFlowbiteThemableChildReturns = {
|
||||
classes: Ref<string>
|
||||
@@ -11,11 +12,12 @@ type UseFlowbiteThemableChildReturns = {
|
||||
|
||||
type UseFlowbiteThemableChildProps = {
|
||||
apply: Ref<ThemableChildrenApply[]>
|
||||
theme?: Ref<FlowbiteTheme | undefined>
|
||||
}
|
||||
|
||||
export function useFlowbiteThemableChildClasses(props: UseFlowbiteThemableChildProps): UseFlowbiteThemableChildReturns {
|
||||
|
||||
const { textClasses, borderClasses, backgroundClasses, hoverClasses, disabledClasses, focusClasses, isActive } = useFlowbiteThemable()
|
||||
const { textClasses, borderClasses, backgroundClasses, hoverClasses, disabledClasses, focusClasses, isActive } = useFlowbiteThemable(props.theme?.value)
|
||||
|
||||
const classes = computed(() => {
|
||||
if(!isActive.value) return ''
|
||||
|
||||
@@ -63,41 +63,45 @@ const flowbiteThemeClasses: FlowbiteThemes<FlowbiteTheme> = {
|
||||
|
||||
}
|
||||
|
||||
export function useFlowbiteThemable(): UseFlowbiteThemableReturns {
|
||||
export function useFlowbiteThemable(_theme?: FlowbiteTheme): UseFlowbiteThemableReturns {
|
||||
|
||||
const theme = inject<Ref<FlowbiteTheme | null>>(FLOWBITE_THEMABLE_INJECTION_KEY, ref(null))
|
||||
|
||||
const isActive = computed(() => !!theme?.value)
|
||||
const color = computed(() => theme?.value || undefined)
|
||||
|
||||
const themeName = computed(() => {
|
||||
return _theme || theme.value
|
||||
})
|
||||
|
||||
const backgroundClasses = computed(() => {
|
||||
if(!theme.value) return ''
|
||||
return flowbiteThemeClasses[theme.value].background
|
||||
if(!themeName.value) return ''
|
||||
return flowbiteThemeClasses[themeName.value].background
|
||||
})
|
||||
|
||||
const disabledClasses = computed(() => {
|
||||
if(!theme.value) return ''
|
||||
return flowbiteThemeClasses[theme.value].disabled
|
||||
if(!themeName.value) return ''
|
||||
return flowbiteThemeClasses[themeName.value].disabled
|
||||
})
|
||||
|
||||
const hoverClasses = computed(() => {
|
||||
if(!theme.value) return ''
|
||||
return flowbiteThemeClasses[theme.value].hover
|
||||
if(!themeName.value) return ''
|
||||
return flowbiteThemeClasses[themeName.value].hover
|
||||
})
|
||||
|
||||
const textClasses = computed(() => {
|
||||
if(!theme.value) return ''
|
||||
return flowbiteThemeClasses[theme.value].text
|
||||
if(!themeName.value) return ''
|
||||
return flowbiteThemeClasses[themeName.value].text
|
||||
})
|
||||
|
||||
const borderClasses = computed(() => {
|
||||
if(!theme.value) return ''
|
||||
return flowbiteThemeClasses[theme.value].border
|
||||
if(!themeName.value) return ''
|
||||
return flowbiteThemeClasses[themeName.value].border
|
||||
})
|
||||
|
||||
const focusClasses = computed(() => {
|
||||
if(!theme.value) return ''
|
||||
return flowbiteThemeClasses[theme.value].focus
|
||||
if(!themeName.value) return ''
|
||||
return flowbiteThemeClasses[themeName.value].focus
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user