mirror of
				https://github.com/antd-tiny-vue/antd-tiny-vue.git
				synced 2025-11-04 10:31:46 +08:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			21c3a13f5a
			...
			9243370f1c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9243370f1c | |||
| 69388270af | |||
| 691bd2b965 | 
							
								
								
									
										60
									
								
								components/_util/wave/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								components/_util/wave/index.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
import type { VNode } from 'vue'
 | 
			
		||||
import { cloneVNode, computed, defineComponent, shallowRef } from 'vue'
 | 
			
		||||
import { booleanType, classNames, filterEmpty, isVisible } from '@v-c/utils'
 | 
			
		||||
import { useEventListener } from '@vueuse/core'
 | 
			
		||||
import { useProviderConfigState } from '../../config-provider/context'
 | 
			
		||||
import useStyle from './style'
 | 
			
		||||
import useWave from './use-wave'
 | 
			
		||||
 | 
			
		||||
const Wave = defineComponent({
 | 
			
		||||
  name: 'Wave',
 | 
			
		||||
  props: {
 | 
			
		||||
    disabled: booleanType()
 | 
			
		||||
  },
 | 
			
		||||
  setup(props, { slots }) {
 | 
			
		||||
    const { getPrefixCls } = useProviderConfigState()
 | 
			
		||||
    const containerRef = shallowRef()
 | 
			
		||||
 | 
			
		||||
    // ============================== Style ===============================
 | 
			
		||||
    const prefixCls = computed(() => getPrefixCls('wave'))
 | 
			
		||||
    const [, hashId] = useStyle(prefixCls)
 | 
			
		||||
    const showWave = useWave(
 | 
			
		||||
      containerRef,
 | 
			
		||||
      computed(() => classNames(prefixCls.value, hashId.value))
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    const onClick = (e: MouseEvent) => {
 | 
			
		||||
      const node = containerRef.value
 | 
			
		||||
      const { disabled } = props
 | 
			
		||||
      if (!node || node.nodeType !== 1 || disabled) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      // Fix radio button click twice
 | 
			
		||||
      if (
 | 
			
		||||
        (e.target as HTMLElement).tagName === 'INPUT' ||
 | 
			
		||||
        !isVisible(e.target as HTMLElement) ||
 | 
			
		||||
        // No need wave
 | 
			
		||||
        !node.getAttribute ||
 | 
			
		||||
        node.getAttribute('disabled') ||
 | 
			
		||||
        (node as HTMLInputElement).disabled ||
 | 
			
		||||
        node.className.includes('disabled') ||
 | 
			
		||||
        node.className.includes('-leave')
 | 
			
		||||
      ) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      showWave()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    useEventListener(containerRef, 'click', onClick, true)
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      const children = slots.default?.()
 | 
			
		||||
      const child = filterEmpty(children)[0]
 | 
			
		||||
      if (!child) return null
 | 
			
		||||
      return cloneVNode(child as VNode, { ref: containerRef })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export default Wave
 | 
			
		||||
							
								
								
									
										34
									
								
								components/_util/wave/style.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								components/_util/wave/style.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
import { genComponentStyleHook } from '../../theme/internal'
 | 
			
		||||
import type { FullToken, GenerateStyle } from '../../theme/internal'
 | 
			
		||||
 | 
			
		||||
export interface ComponentToken {}
 | 
			
		||||
 | 
			
		||||
export interface WaveToken extends FullToken<'Wave'> {}
 | 
			
		||||
 | 
			
		||||
const genWaveStyle: GenerateStyle<WaveToken> = token => {
 | 
			
		||||
  const { componentCls, colorPrimary } = token
 | 
			
		||||
  return {
 | 
			
		||||
    [componentCls]: {
 | 
			
		||||
      position: 'absolute',
 | 
			
		||||
      background: 'transparent',
 | 
			
		||||
      pointerEvents: 'none',
 | 
			
		||||
      boxSizing: 'border-box',
 | 
			
		||||
      color: `var(--wave-color, ${colorPrimary})`,
 | 
			
		||||
 | 
			
		||||
      boxShadow: `0 0 0 0 currentcolor`,
 | 
			
		||||
      opacity: 0.2,
 | 
			
		||||
 | 
			
		||||
      // =================== Motion ===================
 | 
			
		||||
      '&.wave-motion-appear': {
 | 
			
		||||
        transition: [`box-shadow 0.4s ${token.motionEaseOutCirc}`, `opacity 2s ${token.motionEaseOutCirc}`].join(','),
 | 
			
		||||
 | 
			
		||||
        '&-active': {
 | 
			
		||||
          boxShadow: `0 0 0 6px currentcolor`,
 | 
			
		||||
          opacity: 0
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default genComponentStyleHook('Wave', token => [genWaveStyle(token)])
 | 
			
		||||
							
								
								
									
										11
									
								
								components/_util/wave/use-wave.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								components/_util/wave/use-wave.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
import type { ComputedRef, Ref } from 'vue'
 | 
			
		||||
import showWaveEffect from './wave-effect'
 | 
			
		||||
 | 
			
		||||
export default function useWave(nodeRef: Ref<HTMLElement>, className: ComputedRef<string>): VoidFunction {
 | 
			
		||||
  function showWave() {
 | 
			
		||||
    // const node = nodeRef.va!
 | 
			
		||||
    showWaveEffect(nodeRef, className)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return showWave
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								components/_util/wave/util.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								components/_util/wave/util.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
export function isNotGrey(color: string) {
 | 
			
		||||
  // eslint-disable-next-line no-useless-escape
 | 
			
		||||
  const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\d.]*)?\)/)
 | 
			
		||||
  if (match && match[1] && match[2] && match[3]) {
 | 
			
		||||
    return !(match[1] === match[2] && match[2] === match[3])
 | 
			
		||||
  }
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isValidWaveColor(color: string) {
 | 
			
		||||
  return (
 | 
			
		||||
    color &&
 | 
			
		||||
    color !== '#fff' &&
 | 
			
		||||
    color !== '#ffffff' &&
 | 
			
		||||
    color !== 'rgb(255, 255, 255)' &&
 | 
			
		||||
    color !== 'rgba(255, 255, 255, 1)' &&
 | 
			
		||||
    isNotGrey(color) &&
 | 
			
		||||
    !/rgba\((?:\d*, ){3}0\)/.test(color) && // any transparent rgba color
 | 
			
		||||
    color !== 'transparent'
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getTargetWaveColor(node: HTMLElement) {
 | 
			
		||||
  const { borderTopColor, borderColor, backgroundColor } = getComputedStyle(node)
 | 
			
		||||
  if (isValidWaveColor(borderTopColor)) {
 | 
			
		||||
    return borderTopColor
 | 
			
		||||
  }
 | 
			
		||||
  if (isValidWaveColor(borderColor)) {
 | 
			
		||||
    return borderColor
 | 
			
		||||
  }
 | 
			
		||||
  if (isValidWaveColor(backgroundColor)) {
 | 
			
		||||
    return backgroundColor
 | 
			
		||||
  }
 | 
			
		||||
  return null
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										108
									
								
								components/_util/wave/wave-effect.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								components/_util/wave/wave-effect.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
import type { ComputedRef, Ref } from 'vue'
 | 
			
		||||
import { unrefElement, useResizeObserver } from '@vueuse/core'
 | 
			
		||||
import { computed, defineComponent, onMounted, render, shallowRef, toRef } from 'vue'
 | 
			
		||||
import { classNames, delayTimer, objectType, safeNextick, useState } from '@v-c/utils'
 | 
			
		||||
import { getTargetWaveColor } from './util'
 | 
			
		||||
function validateNum(value: number) {
 | 
			
		||||
  return Number.isNaN(value) ? 0 : value
 | 
			
		||||
}
 | 
			
		||||
export const WaveEffect = defineComponent({
 | 
			
		||||
  name: 'WaveEffect',
 | 
			
		||||
  props: {
 | 
			
		||||
    target: objectType<HTMLElement>()
 | 
			
		||||
  },
 | 
			
		||||
  setup(props, { attrs }) {
 | 
			
		||||
    const divRef = shallowRef<HTMLDivElement | undefined>(undefined)
 | 
			
		||||
 | 
			
		||||
    const [color, setWaveColor] = useState<string | null>(null)
 | 
			
		||||
    const [borderRadius, setBorderRadius] = useState<number[]>([])
 | 
			
		||||
    const [left, setLeft] = useState(0)
 | 
			
		||||
    const [top, setTop] = useState(0)
 | 
			
		||||
    const [width, setWidth] = useState(0)
 | 
			
		||||
    const [height, setHeight] = useState(0)
 | 
			
		||||
    const [enabled, setEnabled] = useState(false)
 | 
			
		||||
    const [active, setActive] = useState(false)
 | 
			
		||||
    const waveStyle = computed(() => {
 | 
			
		||||
      const style: Record<string, any> = {
 | 
			
		||||
        left: `${left.value}px`,
 | 
			
		||||
        top: `${top.value}px`,
 | 
			
		||||
        width: `${width.value}px`,
 | 
			
		||||
        height: `${height.value}px`,
 | 
			
		||||
        borderRadius: borderRadius.value.map(radius => `${radius}px`).join(' ')
 | 
			
		||||
      }
 | 
			
		||||
      if (color.value) {
 | 
			
		||||
        style['--wave-color'] = color.value
 | 
			
		||||
      }
 | 
			
		||||
      return style
 | 
			
		||||
    })
 | 
			
		||||
    function syncPos() {
 | 
			
		||||
      const { target } = props
 | 
			
		||||
      const nodeStyle = getComputedStyle(target)
 | 
			
		||||
 | 
			
		||||
      // Get wave color from target
 | 
			
		||||
      setWaveColor(getTargetWaveColor(target))
 | 
			
		||||
 | 
			
		||||
      const isStatic = nodeStyle.position === 'static'
 | 
			
		||||
 | 
			
		||||
      // Rect
 | 
			
		||||
      const { borderLeftWidth, borderTopWidth } = nodeStyle
 | 
			
		||||
      setLeft(isStatic ? target.offsetLeft : validateNum(-parseFloat(borderLeftWidth)))
 | 
			
		||||
      setTop(isStatic ? target.offsetTop : validateNum(-parseFloat(borderTopWidth)))
 | 
			
		||||
      setWidth(target.offsetWidth)
 | 
			
		||||
      setHeight(target.offsetHeight)
 | 
			
		||||
 | 
			
		||||
      // Get border radius
 | 
			
		||||
      const { borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius } = nodeStyle
 | 
			
		||||
 | 
			
		||||
      setBorderRadius([borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius].map(radius => validateNum(parseFloat(radius))))
 | 
			
		||||
    }
 | 
			
		||||
    onMounted(async () => {
 | 
			
		||||
      syncPos()
 | 
			
		||||
      setEnabled(true)
 | 
			
		||||
      await safeNextick()
 | 
			
		||||
      setActive(true)
 | 
			
		||||
      await delayTimer(5000)
 | 
			
		||||
      const holder = divRef.value?.parentElement
 | 
			
		||||
      holder!.parentElement?.removeChild(holder!)
 | 
			
		||||
    })
 | 
			
		||||
    const motionClassName = computed(() =>
 | 
			
		||||
      classNames({
 | 
			
		||||
        'wave-motion-appear': enabled.value,
 | 
			
		||||
        'wave-motion': true
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    const motionClassNameActive = computed(() => ({
 | 
			
		||||
      'wave-motion-appear-active': active.value
 | 
			
		||||
    }))
 | 
			
		||||
    useResizeObserver(toRef(props, 'target'), syncPos)
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      if (!enabled.value) return null
 | 
			
		||||
      return (
 | 
			
		||||
        <div
 | 
			
		||||
          ref={divRef}
 | 
			
		||||
          class={[attrs.class, motionClassName.value, motionClassNameActive.value]}
 | 
			
		||||
          style={waveStyle.value}
 | 
			
		||||
        />
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export default function showWaveEffect(nodeRef: Ref<HTMLElement>, className: ComputedRef<string>) {
 | 
			
		||||
  const node = unrefElement(nodeRef)
 | 
			
		||||
  // Create holder
 | 
			
		||||
  const holder = document.createElement('div')
 | 
			
		||||
  holder.style.position = 'absolute'
 | 
			
		||||
  holder.style.left = `0px`
 | 
			
		||||
  holder.style.top = `0px`
 | 
			
		||||
  node?.insertBefore(holder, node?.firstChild)
 | 
			
		||||
 | 
			
		||||
  render(
 | 
			
		||||
    <WaveEffect
 | 
			
		||||
      target={node}
 | 
			
		||||
      class={className.value}
 | 
			
		||||
    />,
 | 
			
		||||
    holder
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { computed, defineComponent } from 'vue'
 | 
			
		||||
import { useProviderConfigState } from '../config-provider/context'
 | 
			
		||||
import Wave from '../_util/wave'
 | 
			
		||||
import useStyle from './style'
 | 
			
		||||
 | 
			
		||||
const Button = defineComponent({
 | 
			
		||||
@@ -29,12 +30,14 @@ const Button = defineComponent({
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      return wrapSSR(
 | 
			
		||||
        <Wave>
 | 
			
		||||
          <button
 | 
			
		||||
            {...attrs}
 | 
			
		||||
            class={[cls.value, attrs.class]}
 | 
			
		||||
          >
 | 
			
		||||
            {slots.default?.()}
 | 
			
		||||
          </button>
 | 
			
		||||
        </Wave>
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ import type { ComponentToken as ButtonComponentToken } from '../../button/style'
 | 
			
		||||
// import type { ComponentToken as TourComponentToken } from '../../tour/style'
 | 
			
		||||
// import type { ComponentToken as QRCodeComponentToken } from '../../qrcode/style'
 | 
			
		||||
// import type { ComponentToken as AppComponentToken } from '../../app/style'
 | 
			
		||||
// import type { ComponentToken as WaveToken } from '../../_util/wave/style'
 | 
			
		||||
import type { ComponentToken as WaveToken } from '../../_util/wave/style'
 | 
			
		||||
 | 
			
		||||
export interface ComponentTokenMap {
 | 
			
		||||
  Affix?: {}
 | 
			
		||||
@@ -115,5 +115,5 @@ export interface ComponentTokenMap {
 | 
			
		||||
  // App?: AppComponentToken
 | 
			
		||||
  //
 | 
			
		||||
  // /** @private Internal TS definition. Do not use. */
 | 
			
		||||
  // Wave?: WaveToken
 | 
			
		||||
  Wave?: WaveToken
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
    "@ant-design/colors": "^7.0.0",
 | 
			
		||||
    "@antd-tiny-vue/cssinjs": "^0.0.4",
 | 
			
		||||
    "@ctrl/tinycolor": "^3.6.0",
 | 
			
		||||
    "@v-c/utils": "^0.0.5",
 | 
			
		||||
    "@v-c/utils": "^0.0.12",
 | 
			
		||||
    "@vueuse/core": "^9.13.0",
 | 
			
		||||
    "vue": "^3.2.0"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@@ -10,7 +10,7 @@ specifiers:
 | 
			
		||||
  '@mistjs/tsconfig': ^1.0.0
 | 
			
		||||
  '@mistjs/tsconfig-vue': ^0.0.3
 | 
			
		||||
  '@types/node': ^18.13.0
 | 
			
		||||
  '@v-c/utils': ^0.0.5
 | 
			
		||||
  '@v-c/utils': ^0.0.12
 | 
			
		||||
  '@vitejs/plugin-vue-jsx': ^3.0.0
 | 
			
		||||
  '@vueuse/core': ^9.13.0
 | 
			
		||||
  eslint: ^8.34.0
 | 
			
		||||
@@ -28,7 +28,7 @@ dependencies:
 | 
			
		||||
  '@ant-design/colors': 7.0.0
 | 
			
		||||
  '@antd-tiny-vue/cssinjs': 0.0.4_vue@3.2.47
 | 
			
		||||
  '@ctrl/tinycolor': 3.6.0
 | 
			
		||||
  '@v-c/utils': 0.0.5
 | 
			
		||||
  '@v-c/utils': 0.0.12
 | 
			
		||||
  '@vueuse/core': 9.13.0_vue@3.2.47
 | 
			
		||||
  vue: 3.2.47
 | 
			
		||||
 | 
			
		||||
@@ -1280,8 +1280,8 @@ packages:
 | 
			
		||||
      eslint-visitor-keys: 3.3.0
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /@v-c/utils/0.0.5:
 | 
			
		||||
    resolution: {integrity: sha512-HiK9iupJ3YIl4AO8VxvQMVh5G7pkTYo7wMhWdsWr6XOPw86p5MgWdQRLhQNX1WbDjA9BsbpjuO7I5PyEhGYoFw==}
 | 
			
		||||
  /@v-c/utils/0.0.12:
 | 
			
		||||
    resolution: {integrity: sha512-onwjLSQpH6SG78WJnKiw8XgJfCgYtq59zFW19yiT22ik7ncaLkjojjCU0cAZDA6j6zY6li5U0VuZk91KbOrw2A==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      lodash.clonedeep: 4.5.0
 | 
			
		||||
      vue: 3.2.47
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ title: 基础按钮
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <a-button>这是按钮</a-button>
 | 
			
		||||
    <div style="height: 10px"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user