import { makeComponentProps } from '@/composables/component' import { makeTagProps } from '@/composables/tag' import { genericComponent, propsFactory, useRender } from '@/utils' import { ExtractPropTypes } from 'vue' import { computed, ref, watch, onMounted } from 'vue' import { createPopper, Instance, Placement } from '@popperjs/core' export const makeUTooltipProps = propsFactory( { theme: { type: String, default: 'dark', required: false, }, position: { type: String, default: 'none bottom', required: false, }, always: { type: Boolean, default: false, required: false, }, ...makeComponentProps(), ...makeTagProps(), }, 'UTooltip' ) export type UTooltipProps = ExtractPropTypes export type UTooltipSlots = { tooltipToggle: never text: never supportingText: never } export const UTooltip = genericComponent()({ name: 'UTooltip', props: makeUTooltipProps(), emits: { click: (e: MouseEvent) => true, }, setup(props, { emit, slots }) { const tooltipIsVisible = ref(false) const tooltipToggle = ref(null) const tooltipComponent = ref(null) const popperInstance = ref(null) const alwaysVisible = computed(() => props.always) const popperCreated = ref(false) watch( () => props.position, () => { destroyTooltip() props.always === true ? createTooltip() : '' } ) watch(alwaysVisible, (newValue) => { if (newValue === true) { destroyTooltip() createTooltip() } else { destroyTooltip() } }) function createTooltip() { if (tooltipToggle.value && tooltipComponent.value) { tooltipIsVisible.value = true tooltipComponent.value.removeAttribute('data-popper-reference-hidden') let placement = props.position as Placement let offset = [0, 12] if (props.position === 'none top' || props.position === 'none bottom') { offset = [0, 6] } else if ( props.position === 'top-end' || props.position === 'bottom-end' ) { offset = [13, 12] } else if ( props.position === 'top-start' || props.position === 'bottom-start' ) { offset = [-14, 12] } if (props.position === 'none top') { placement = 'top' } else if (props.position === 'none bottom') { placement = 'bottom' } setTimeout(() => { popperInstance.value = createPopper( tooltipToggle.value as HTMLElement, tooltipComponent.value as HTMLElement, { placement: placement, modifiers: [ { name: 'offset', options: { offset: offset, }, }, ], } ) popperCreated.value = true }, 5) } } function destroyTooltip() { tooltipIsVisible.value = false popperCreated.value = false if (popperInstance.value) { popperInstance.value.destroy() popperInstance.value = null } } onMounted(() => { if (alwaysVisible.value === true) { createTooltip() } }) const tooltipTheme = computed(() => { let tooltipTheme = { color: 'white', backgroundColor: 'gray-900', } switch (props.theme) { case 'dark': tooltipTheme = { color: 'white', backgroundColor: 'gray-900', } break case 'light': tooltipTheme = { color: 'gray-700', backgroundColor: 'white', } break default: break } return tooltipTheme }) const GeneralClasses = `absolute flex flex-col font-medium max-w-sm py-2 px-3 text-text-xs rounded-lg w-fit` const ThemeDarkClasses = computed( () => `bg-${tooltipTheme.value.backgroundColor} text-${tooltipTheme.value.color} shadow shadow-lg shadow-${tooltipTheme.value.backgroundColor}` ) const ThemeLightClasses = computed( () => `bg-${tooltipTheme.value.backgroundColor} text-${tooltipTheme.value.color} shadow shadow-lg shadow-gray-100` ) const PseudoElementClasses = computed( () => `before:block before:absolute before:bg-${tooltipTheme.value.backgroundColor} before:w-3 before:h-xs before:rotate-45` ) const PseudoElementBottomCenterClasses = computed( () => `before:-bottom-1.5 before:left-1/2 before:transform before:-translate-x-1/2` ) const PseudoElementBottomLeftClasses = computed( () => `before:-bottom-1.5 before:inset-x-3.5` ) const PseudoElementBottomRightClasses = computed( () => `before:-bottom-1.5 before:right-3.5` ) const PseudoElementTopCenterClasses = computed( () => `before:-top-1.5 before:left-1/2 before:transform before:-translate-x-1/2` ) const PseudoElementTopLeftClasses = computed( () => `before:-top-1.5 before:inset-x-3.5` ) const PseudoElementTopRightClasses = computed( () => `before:-top-1.5 before:right-3.5` ) const PseudoElementLeftClasses = computed( () => `before:top-1/2 before:transform before:-translate-y-1/2 before:-inset-1.5` ) const PseudoElementRightClasses = computed( () => `before:top-1/2 before:transform before:-translate-y-1/2 before:-right-1.5` ) const classes = computed(() => ({ [GeneralClasses]: true, [ThemeLightClasses.value]: props.theme === 'light', [ThemeDarkClasses.value]: props.theme === 'dark', [PseudoElementClasses.value]: props.position !== 'none top' && props.position !== 'none bottom', [PseudoElementBottomCenterClasses.value]: props.position === 'top', [PseudoElementBottomLeftClasses.value]: props.position === 'top-start', [PseudoElementBottomRightClasses.value]: props.position === 'top-end', [PseudoElementTopCenterClasses.value]: props.position === 'bottom', [PseudoElementTopLeftClasses.value]: props.position === 'bottom-start', [PseudoElementTopRightClasses.value]: props.position === 'bottom-end', [PseudoElementLeftClasses.value]: props.position === 'right', [PseudoElementRightClasses.value]: props.position === 'left', })) useRender(() => (
emit('click', e)} onMouseenter={() => !props.always && createTooltip()} onMouseleave={() => !props.always && destroyTooltip()} onTouchstart={() => !props.always && createTooltip()} onTouchend={() => !props.always && destroyTooltip()} > {slots.tooltipToggle?.()}
{slots.text?.()}
{slots.supportingText?.()}
)) return { tooltipIsVisible, } }, }) export type UTooltip = InstanceType