UNPKG

@datametria/vue-components

Version:

DATAMETRIA Vue.js 3 Component Library with Multi-Brand Theming - 51 components + 10 composables with theming support, WCAG 2.2 AA, dark mode, responsive system

219 lines (181 loc) 5.16 kB
import { ref, onMounted, onUnmounted, watch } from 'vue' interface RippleOptions { color?: string duration?: number disabled?: boolean } interface RippleEffect { x: number y: number size: number id: string } /** * Composable para efeito ripple em elementos clicáveis * Implementa Material Design ripple effect com acessibilidade */ export function useRipple(options: RippleOptions = {}) { const { color = 'rgba(255, 255, 255, 0.3)', duration = 600, disabled = false } = options const elementRef = ref<HTMLElement>() const ripples = ref<RippleEffect[]>([]) // Criar elemento ripple DOM const createRipple = (event: MouseEvent | TouchEvent) => { if (disabled || !elementRef.value) return const element = elementRef.value const rect = element.getBoundingClientRect() // Calcular posição do clique let clientX: number, clientY: number if (event instanceof MouseEvent) { clientX = event.clientX clientY = event.clientY } else { const touch = event.touches[0] || event.changedTouches[0] clientX = touch.clientX clientY = touch.clientY } const x = clientX - rect.left const y = clientY - rect.top // Calcular tamanho do ripple (maior dimensão) const size = Math.max(rect.width, rect.height) // Criar elemento DOM do ripple const rippleElement = document.createElement('div') rippleElement.className = 'datametria-ripple' rippleElement.style.cssText = ` position: absolute; left: ${x - size / 2}px; top: ${y - size / 2}px; width: ${size}px; height: ${size}px; border-radius: 50%; background: ${color}; transform: scale(0); animation: ripple-animation ${duration / 1000}s ease-out; pointer-events: none; z-index: 1; ` element.appendChild(rippleElement) // Remover ripple após animação setTimeout(() => { if (rippleElement.parentNode) { rippleElement.parentNode.removeChild(rippleElement) } }, duration) } // Event handlers const handleClick = (event: MouseEvent) => { createRipple(event) } // Aplicar ripple ao elemento const applyRipple = (element: HTMLElement) => { if (!element) return elementRef.value = element // Garantir posição relativa para ripples const computedStyle = window.getComputedStyle(element) if (computedStyle.position === 'static') { element.style.position = 'relative' } // Garantir overflow hidden element.style.overflow = 'hidden' // Adicionar event listeners element.addEventListener('click', handleClick) return () => { element.removeEventListener('click', handleClick) } } // Remover ripple específico const removeRipple = (id: string) => { const index = ripples.value.findIndex(r => r.id === id) if (index > -1) { ripples.value.splice(index, 1) } } // Limpar todos os ripples const clearRipples = () => { ripples.value = [] } // Cleanup onUnmounted(() => { clearRipples() }) // Computed styles para ripples const getRippleStyle = (ripple: RippleEffect) => ({ position: 'absolute' as const, left: `${ripple.x}px`, top: `${ripple.y}px`, width: `${ripple.size}px`, height: `${ripple.size}px`, borderRadius: '50%', backgroundColor: color, transform: 'scale(0)', animation: `ripple-animation ${duration}ms ease-out`, pointerEvents: 'none' as const, zIndex: 1 }) // CSS para animação (deve ser injetado no head) const injectRippleCSS = () => { const styleId = 'ripple-keyframes' if (document.getElementById(styleId)) return const style = document.createElement('style') style.id = styleId style.textContent = ` @keyframes ripple-animation { 0% { transform: scale(0); opacity: 1; } 100% { transform: scale(2); opacity: 0; } } @media (prefers-reduced-motion: reduce) { @keyframes ripple-animation { 0%, 100% { transform: scale(0); opacity: 0; } } } ` document.head.appendChild(style) } // Método para adicionar efeito ripple ao elemento const addRippleEffect = () => { if (!elementRef.value) return return applyRipple(elementRef.value) } // Método para remover efeito ripple do elemento const removeRippleEffect = () => { if (!elementRef.value) return elementRef.value.removeEventListener('click', handleClick) } // Injetar CSS na montagem onMounted(() => { injectRippleCSS() // Auto-aplicar se elemento já existe if (elementRef.value) { addRippleEffect() } }) // Watch para aplicar ripple quando elemento for definido watch(elementRef, (newElement) => { if (newElement) { addRippleEffect() } }) return { rippleRef: elementRef, ripples, applyRipple, removeRipple, clearRipples, getRippleStyle, createRipple, addRippleEffect, removeRippleEffect } }