ESLINT is holy crap
This commit is contained in:
43
src/primitive-components/ripple/hooks/useRippleEffect.ts
Normal file
43
src/primitive-components/ripple/hooks/useRippleEffect.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
interface RippleEventHandlers {
|
||||
onBlur: React.FocusEventHandler;
|
||||
onContextMenu: React.MouseEventHandler;
|
||||
onDragLeave: React.DragEventHandler;
|
||||
onMouseDown: React.MouseEventHandler;
|
||||
onMouseLeave: React.MouseEventHandler;
|
||||
onMouseUp: React.MouseEventHandler;
|
||||
onTouchEnd: React.TouchEventHandler;
|
||||
onTouchMove: React.TouchEventHandler;
|
||||
onTouchStart: React.TouchEventHandler;
|
||||
}
|
||||
|
||||
const UseRippleEffect = (ref, callback): undefined | RippleEventHandlers => {
|
||||
const [mounted, setMounted] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted) {
|
||||
setMounted(true);
|
||||
}
|
||||
});
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { start, stop } = ref.current;
|
||||
|
||||
return {
|
||||
onBlur: event => stop(event, callback),
|
||||
onContextMenu: event => start(event, callback),
|
||||
onDragLeave: event => stop(event, callback),
|
||||
onMouseDown: event => start(event, callback),
|
||||
onMouseLeave: event => stop(event, callback),
|
||||
onMouseUp: event => stop(event, callback),
|
||||
onTouchEnd: event => stop(event, callback),
|
||||
onTouchMove: event => stop(event, callback),
|
||||
onTouchStart: event => stop(event, callback),
|
||||
};
|
||||
};
|
||||
|
||||
export default UseRippleEffect;
|
||||
130
src/primitive-components/ripple/ripple-area.tsx
Normal file
130
src/primitive-components/ripple/ripple-area.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
'use client';
|
||||
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useId,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Ripple } from './ripple';
|
||||
import { Ripples } from './ripple';
|
||||
import { RippleAreaProps } from './ripple.types';
|
||||
|
||||
const TIMEOUT: number = 550;
|
||||
const rippleAreaContext = React.createContext(false);
|
||||
|
||||
const RippleArea = forwardRef(function RippleArea(
|
||||
{ central = false, callback, ...props }: RippleAreaProps,
|
||||
ref,
|
||||
) {
|
||||
const [ripples, setRipples] = useState<Array<JSX.Element>>([]),
|
||||
rippleDomain = useRef<any>(null),
|
||||
clicked = useRef<boolean>(false),
|
||||
uniqueKey = useRef<number>(0),
|
||||
uniqueId = useId();
|
||||
|
||||
const classes = props.className
|
||||
? `m3 m3-ripple-domain ${props.className}`.trimEnd()
|
||||
: 'm3 m3-ripple-domain';
|
||||
|
||||
const start = useCallback(
|
||||
(event: any, cb: (state: boolean) => void): void => {
|
||||
clicked.current = true;
|
||||
cb(clicked.current);
|
||||
|
||||
const rippleDomainChar = rippleDomain.current
|
||||
? rippleDomain.current.getBoundingClientRect()
|
||||
: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
left: 0,
|
||||
top: 0,
|
||||
};
|
||||
|
||||
const rippleX: number = !central
|
||||
? event.clientX - rippleDomainChar.left
|
||||
: rippleDomainChar.width / 2,
|
||||
rippleY: number = !central
|
||||
? event.clientY - rippleDomainChar.top
|
||||
: rippleDomainChar.height / 2,
|
||||
rippleSizeX: number =
|
||||
Math.max(
|
||||
Math.abs(rippleDomainChar.width - rippleX),
|
||||
rippleX,
|
||||
) *
|
||||
2 +
|
||||
2,
|
||||
rippleSizeY: number =
|
||||
Math.max(
|
||||
Math.abs(rippleDomainChar.height - rippleY),
|
||||
rippleY,
|
||||
) *
|
||||
2 +
|
||||
2,
|
||||
rippleS: number = (rippleSizeX ** 2 + rippleSizeY ** 2) ** 0.5;
|
||||
|
||||
setRipples((prevRipples: Array<JSX.Element>) => {
|
||||
if (prevRipples.length === 0) {
|
||||
return [
|
||||
<Ripple
|
||||
key={uniqueKey.current}
|
||||
lifetime={TIMEOUT}
|
||||
rippleS={rippleS}
|
||||
rippleX={rippleX}
|
||||
rippleY={rippleY}
|
||||
/>,
|
||||
];
|
||||
}
|
||||
const old = [...prevRipples];
|
||||
old.push(
|
||||
<Ripple
|
||||
key={uniqueKey.current}
|
||||
lifetime={TIMEOUT}
|
||||
rippleS={rippleS}
|
||||
rippleX={rippleX}
|
||||
rippleY={rippleY}
|
||||
/>,
|
||||
);
|
||||
return old;
|
||||
});
|
||||
|
||||
uniqueKey.current += 1;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const stop = useCallback((_event: any, cb: (state: boolean) => void) => {
|
||||
clicked.current = false;
|
||||
cb(clicked.current);
|
||||
|
||||
setRipples((prevRipples: Array<JSX.Element>) => {
|
||||
if (prevRipples.length > 0) {
|
||||
const old = [...prevRipples];
|
||||
old.shift();
|
||||
return old;
|
||||
}
|
||||
return prevRipples;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
start,
|
||||
stop,
|
||||
}),
|
||||
[start, stop],
|
||||
);
|
||||
|
||||
return (
|
||||
<span className={classes} id={uniqueId} ref={rippleDomain}>
|
||||
<rippleAreaContext.Provider value={clicked.current}>
|
||||
<Ripples>{ripples}</Ripples>
|
||||
</rippleAreaContext.Provider>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
export { rippleAreaContext, RippleArea };
|
||||
86
src/primitive-components/ripple/ripple.tsx
Normal file
86
src/primitive-components/ripple/ripple.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
'use client';
|
||||
|
||||
import isEmpty from './utils/utils';
|
||||
import { rippleProps } from './ripple.types';
|
||||
import { rippleAreaContext } from './ripple-area';
|
||||
import RippleEffectBuild from './utils/ripple-effect-builder';
|
||||
import React, {
|
||||
ForwardedRef,
|
||||
forwardRef,
|
||||
JSX,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
useTransition,
|
||||
} from 'react';
|
||||
|
||||
const Ripples = forwardRef(function Ripples(
|
||||
props: any,
|
||||
ref: ForwardedRef<any>,
|
||||
) {
|
||||
const [ripples, setRipples] = useState({});
|
||||
const firstRender = useRef<boolean>(true);
|
||||
const [pending, startTransition] = useTransition();
|
||||
|
||||
const LifetimeEnd = useCallback((child: JSX.Element) => {
|
||||
if (child.props.endLifetime) {
|
||||
child.props.endLifetime();
|
||||
}
|
||||
|
||||
setRipples(state => {
|
||||
const children = { ...state };
|
||||
delete children[child.key];
|
||||
return children;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.children.length > 0) {
|
||||
startTransition(() => {
|
||||
if (firstRender.current || isEmpty(ripples)) {
|
||||
setRipples(RippleEffectBuild(props.children, LifetimeEnd));
|
||||
firstRender.current = false;
|
||||
} else {
|
||||
setRipples(
|
||||
RippleEffectBuild(props.children, LifetimeEnd, ripples),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [props.children]);
|
||||
|
||||
return <>{Object.values(ripples)}</>;
|
||||
});
|
||||
|
||||
const Ripple = forwardRef(function Ripple(
|
||||
props: rippleProps,
|
||||
ref: ForwardedRef<any>,
|
||||
) {
|
||||
const { rippleX, rippleY, rippleS, endLifetime, lifetime } = props;
|
||||
|
||||
const clicked = useContext<boolean>(rippleAreaContext);
|
||||
const [classes, setClasses] = useState<string>('m3 ripple visible');
|
||||
|
||||
useEffect(() => {
|
||||
if (endLifetime !== null && !clicked) {
|
||||
setClasses('m3 ripple');
|
||||
setTimeout(endLifetime, lifetime);
|
||||
}
|
||||
}, [clicked, endLifetime]);
|
||||
|
||||
return (
|
||||
<span
|
||||
className={classes}
|
||||
style={{
|
||||
left: -(rippleS / 2) + rippleX,
|
||||
top: -(rippleS / 2) + rippleY,
|
||||
width: rippleS,
|
||||
aspectRatio: 1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export { Ripple, Ripples };
|
||||
21
src/primitive-components/ripple/ripple.types.ts
Normal file
21
src/primitive-components/ripple/ripple.types.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
export interface IRippleProps extends PropsWithChildren<any> {
|
||||
centralRipple?: boolean;
|
||||
}
|
||||
|
||||
export interface RippleAreaProps extends PropsWithChildren<any> {
|
||||
callback: Dispatch<SetStateAction<boolean>>;
|
||||
central?: boolean;
|
||||
}
|
||||
|
||||
export type rippleProps = {
|
||||
rippleX: number;
|
||||
rippleY: number;
|
||||
rippleS: number;
|
||||
endLifetime?: () => void;
|
||||
lifetime: number;
|
||||
key?: number;
|
||||
};
|
||||
|
||||
import { PropsWithChildren } from 'react';
|
||||
@@ -0,0 +1,15 @@
|
||||
import { cloneElement, ReactElement } from 'react';
|
||||
|
||||
export default function ArrayConvertToObj(
|
||||
obj: Object,
|
||||
nextChildren: ReactElement[],
|
||||
callback: (child: any) => void,
|
||||
): void {
|
||||
Object.values(nextChildren).forEach(
|
||||
(child: JSX.Element) =>
|
||||
(obj[child.key] = cloneElement(child, {
|
||||
...child.props,
|
||||
endLifetime: callback.bind(null, child),
|
||||
})),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import ArrayConvertToObj from './array-convert-to-obj';
|
||||
import { cloneElement, ReactElement } from 'react';
|
||||
import isEmpty from './utils';
|
||||
|
||||
export default function RippleEffectBuild(
|
||||
nextRipples: ReactElement[],
|
||||
callback: (child: any) => void,
|
||||
prevRipples?: any | null,
|
||||
) {
|
||||
const empty: boolean = isEmpty(prevRipples);
|
||||
const preparedRipples: object = empty ? {} : prevRipples;
|
||||
|
||||
switch (empty) {
|
||||
case true:
|
||||
ArrayConvertToObj(preparedRipples, nextRipples, callback);
|
||||
break;
|
||||
|
||||
case false:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const next: object = {};
|
||||
ArrayConvertToObj(next, nextRipples, callback);
|
||||
for (const rippleKey of Object.keys(next)) {
|
||||
if (preparedRipples[rippleKey] == undefined) {
|
||||
preparedRipples[rippleKey] = cloneElement(next[rippleKey], {
|
||||
...next[rippleKey].props,
|
||||
endLifetime: callback.bind(null, next[rippleKey]),
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return preparedRipples;
|
||||
}
|
||||
6
src/primitive-components/ripple/utils/utils.ts
Normal file
6
src/primitive-components/ripple/utils/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default function isEmpty(obj: Object): boolean {
|
||||
for (const _i in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user