diff --git a/packages/ui/src/components/button/Button.vue b/packages/ui/src/components/button/Button.vue index 42b0c5e2f..019cdbd31 100644 --- a/packages/ui/src/components/button/Button.vue +++ b/packages/ui/src/components/button/Button.vue @@ -1,5 +1,12 @@ diff --git a/packages/ui/src/components/wave/index.ts b/packages/ui/src/components/wave/index.ts new file mode 100644 index 000000000..881d72675 --- /dev/null +++ b/packages/ui/src/components/wave/index.ts @@ -0,0 +1,12 @@ +import { App, Plugin } from 'vue' +import Wave from './Wave.vue' +import './style/index.css' + +export { default as Wave } from './Wave.vue' + +/* istanbul ignore next */ +Wave.install = function (app: App) { + app.component('AWave', Wave) + return app +} +export default Wave as typeof Wave & Plugin diff --git a/packages/ui/src/components/wave/style/index.css b/packages/ui/src/components/wave/style/index.css new file mode 100644 index 000000000..401918467 --- /dev/null +++ b/packages/ui/src/components/wave/style/index.css @@ -0,0 +1,21 @@ +@reference '../../../style/tailwind.css'; + +.ant-wave-motion { + @apply absolute; + @apply bg-transparent; + @apply pointer-events-none; + @apply box-border; + @apply text-accent; + @apply opacity-20; + box-shadow: 0 0 0 0 currentcolor; + + &:where(.ant-wave-motion-appear) { + transition: + box-shadow 0.4s cubic-bezier(0.08, 0.82, 0.17, 1), + opacity 2s cubic-bezier(0.08, 0.82, 0.17, 1); + &:where(.ant-wave-motion-appear-active) { + @apply opacity-0; + box-shadow: 0 0 0 6px currentcolor; + } + } +} diff --git a/packages/ui/src/components/wave/util.ts b/packages/ui/src/components/wave/util.ts new file mode 100644 index 000000000..cd5bf6377 --- /dev/null +++ b/packages/ui/src/components/wave/util.ts @@ -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; +} diff --git a/packages/ui/src/utils/isVisible.ts b/packages/ui/src/utils/isVisible.ts new file mode 100644 index 000000000..9dfc2b754 --- /dev/null +++ b/packages/ui/src/utils/isVisible.ts @@ -0,0 +1,25 @@ +export default (element: HTMLElement | SVGGraphicsElement): boolean => { + if (!element) { + return false; + } + + if ((element as HTMLElement).offsetParent) { + return true; + } + + if ((element as SVGGraphicsElement).getBBox) { + const box = (element as SVGGraphicsElement).getBBox(); + if (box.width || box.height) { + return true; + } + } + + if ((element as HTMLElement).getBoundingClientRect) { + const box = (element as HTMLElement).getBoundingClientRect(); + if (box.width || box.height) { + return true; + } + } + + return false; +}; diff --git a/packages/ui/src/utils/raf.ts b/packages/ui/src/utils/raf.ts new file mode 100644 index 000000000..160c73c80 --- /dev/null +++ b/packages/ui/src/utils/raf.ts @@ -0,0 +1,47 @@ +let raf = (callback: FrameRequestCallback) => setTimeout(callback, 16) as any; +let caf = (num: number) => clearTimeout(num); + +if (typeof window !== 'undefined' && 'requestAnimationFrame' in window) { + raf = (callback: FrameRequestCallback) => window.requestAnimationFrame(callback); + caf = (handle: number) => window.cancelAnimationFrame(handle); +} + +let rafUUID = 0; +const rafIds = new Map(); + +function cleanup(id: number) { + rafIds.delete(id); +} + +export default function wrapperRaf(callback: () => void, times = 1): number { + rafUUID += 1; + const id = rafUUID; + + function callRef(leftTimes: number) { + if (leftTimes === 0) { + // Clean up + cleanup(id); + + // Trigger + callback(); + } else { + // Next raf + const realId = raf(() => { + callRef(leftTimes - 1); + }); + + // Bind real raf id + rafIds.set(id, realId); + } + } + + callRef(times); + + return id; +} + +wrapperRaf.cancel = (id: number) => { + const realId = rafIds.get(id); + cleanup(realId); + return caf(realId); +};