@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
text/typescript
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
}
}