ESLINT is holy crap

This commit is contained in:
2024-02-01 01:58:19 +04:00
parent 7cdc259fb3
commit 26584d5482
151 changed files with 26811 additions and 47 deletions

View 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;

View 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 };

View 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 };

View 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';

View File

@@ -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),
})),
);
}

View File

@@ -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;
}

View File

@@ -0,0 +1,6 @@
export default function isEmpty(obj: Object): boolean {
for (const _i in obj) {
return false;
}
return true;
}