feat: toast component

This commit is contained in:
Alexandr
2022-07-26 19:13:30 +03:00
parent ba1a458f74
commit 4cca3e63cf
16 changed files with 382 additions and 38 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,
}
}

View File

@@ -1 +1,2 @@
export type ToastPreset = 'success' | 'warning' | 'danger' | 'empty'
export type ToastAlign = 'start' | 'center' | 'end'

View File

@@ -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))

View File

@@ -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 ''

View File

@@ -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 {