parent
6ee10e0fa2
commit
3f027d9e51
|
@ -27,7 +27,7 @@ module.exports = {
|
||||||
plugins: ['react'],
|
plugins: ['react'],
|
||||||
rules: {
|
rules: {
|
||||||
// Possible errors
|
// Possible errors
|
||||||
'no-console': 'warn',
|
'no-console': 'off',
|
||||||
// Best practices
|
// Best practices
|
||||||
'dot-notation': 'error',
|
'dot-notation': 'error',
|
||||||
'no-else-return': 'error',
|
'no-else-return': 'error',
|
||||||
|
@ -36,9 +36,10 @@ module.exports = {
|
||||||
// Stylistic
|
// Stylistic
|
||||||
'array-bracket-spacing': 'error',
|
'array-bracket-spacing': 'error',
|
||||||
'computed-property-spacing': ['error', 'never'],
|
'computed-property-spacing': ['error', 'never'],
|
||||||
|
'@typescript-eslint/no-unused-vars': 'warn',
|
||||||
curly: 'error',
|
curly: 'error',
|
||||||
'no-lonely-if': 'error',
|
'no-lonely-if': 'error',
|
||||||
'no-unneeded-ternary': 'error',
|
'no-unneeded-ternary': 'warn',
|
||||||
'one-var-declaration-per-line': 'error',
|
'one-var-declaration-per-line': 'error',
|
||||||
quotes: [
|
quotes: [
|
||||||
'error',
|
'error',
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="ES6PreferShortImport" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="ES6PreferShortImport" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
|
@ -13,7 +13,7 @@ This repository is including and will be including components, enumerates in tab
|
||||||
- [X] Switches
|
- [X] Switches
|
||||||
- [ ] Chips
|
- [ ] Chips
|
||||||
- [x] Icon
|
- [x] Icon
|
||||||
- [x] Ripples Effect
|
- [x] Ripple Effect
|
||||||
- [ ] Dividers (WIP)
|
- [ ] Dividers (WIP)
|
||||||
- [x] Badges
|
- [x] Badges
|
||||||
- [ ] Select field
|
- [ ] Select field
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { bool, string } from 'prop-types';
|
import { bool, string } from 'prop-types';
|
||||||
import { RippleArea } from '../../ripple/ripple-area';
|
import { RippleEffect } from '../../ripple/ripple-effect';
|
||||||
import { ButtonLayoutProps } from './button-layout.types';
|
import { ButtonLayoutProps } from './button-layout.types';
|
||||||
import useRippleEffect from '../../ripple/hooks/useRippleEffect';
|
import useRippleEffect from '../../ripple/hooks/useRippleEffect';
|
||||||
import React, { forwardRef, useId, useRef, useState } from 'react';
|
import React, { forwardRef, useId, useRef, useState } from 'react';
|
||||||
|
@ -26,7 +26,7 @@ export const ButtonLayout = forwardRef<HTMLButtonElement, ButtonLayoutProps>(
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
<RippleArea
|
<RippleEffect
|
||||||
callback={setIsActive}
|
callback={setIsActive}
|
||||||
central={centralRipple}
|
central={centralRipple}
|
||||||
ref={ripplesRef}
|
ref={ripplesRef}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { RippleArea } from '../ripple/ripple-area';
|
import { RippleEffect } from '../ripple/ripple-effect';
|
||||||
import { CardActionAreaProps } from './card.types';
|
import { CardActionAreaProps } from './card.types';
|
||||||
import { forwardRef, useRef, useState } from 'react';
|
import { forwardRef, useRef, useState } from 'react';
|
||||||
import useRippleEffect from '../ripple/hooks/useRippleEffect';
|
import useRippleEffect from '../ripple/hooks/useRippleEffect';
|
||||||
|
@ -29,7 +29,7 @@ export const CardActionArea = forwardRef<HTMLDivElement, CardActionAreaProps>(
|
||||||
</div>
|
</div>
|
||||||
<span className={'m3 m3-card-state-layer'} />
|
<span className={'m3 m3-card-state-layer'} />
|
||||||
{ripples && (
|
{ripples && (
|
||||||
<RippleArea
|
<RippleEffect
|
||||||
callback={setIsActive}
|
callback={setIsActive}
|
||||||
central={centralRipple}
|
central={centralRipple}
|
||||||
ref={ripplesRef}
|
ref={ripplesRef}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
export { Card } from './card/card';
|
export { Card } from './card/card';
|
||||||
export { Icon } from './icon/icon';
|
export { Icon } from './icon/icon';
|
||||||
export { Badge } from './badge/badge';
|
export { Badge } from './badge/badge';
|
||||||
|
export { Ripple } from './ripple/ripple';
|
||||||
export { Divider } from './divider/divider';
|
export { Divider } from './divider/divider';
|
||||||
export { Container } from './container/container';
|
export { Container } from './container/container';
|
||||||
export { RippleArea } from './ripple/ripple-area';
|
export { RippleEffect } from './ripple/ripple-effect';
|
||||||
export { Ripples, Ripples } from './ripple/ripples';
|
|
||||||
export { FAB } from './button-components/fab/fab';
|
export { FAB } from './button-components/fab/fab';
|
||||||
export { Radio } from './input-components/radio/radio';
|
export { Radio } from './input-components/radio/radio';
|
||||||
export { Switch } from './input-components/switch/switch';
|
export { Switch } from './input-components/switch/switch';
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { bool } from 'prop-types';
|
import { bool } from 'prop-types';
|
||||||
import { CheckboxProps } from './checkbox.types';
|
import { CheckboxProps } from './checkbox.types';
|
||||||
import { RippleArea } from '../../ripple/ripple-area';
|
import { RippleEffect } from '../../ripple/ripple-effect';
|
||||||
import useRippleEffect from '../../ripple/hooks/useRippleEffect';
|
import useRippleEffect from '../../ripple/hooks/useRippleEffect';
|
||||||
import { CheckBoxLayout } from '../checkbox-layout/check-box-layout';
|
import { CheckBoxLayout } from '../checkbox-layout/check-box-layout';
|
||||||
import {
|
import {
|
||||||
|
@ -45,7 +45,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
||||||
/>
|
/>
|
||||||
<span className={'m3 m3-checkbox-state'} />
|
<span className={'m3 m3-checkbox-state'} />
|
||||||
<span className={'m3 m3-checkbox-state-layer'} />
|
<span className={'m3 m3-checkbox-state-layer'} />
|
||||||
<RippleArea
|
<RippleEffect
|
||||||
callback={setIsActive}
|
callback={setIsActive}
|
||||||
central={centralRipple}
|
central={centralRipple}
|
||||||
className={'m3-checkbox-ripple-layer'}
|
className={'m3-checkbox-ripple-layer'}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { bool, string } from 'prop-types';
|
import { bool, string } from 'prop-types';
|
||||||
import { RadioProps } from './radio.types';
|
import { RadioProps } from './radio.types';
|
||||||
import { RippleArea } from '../../ripple/ripple-area';
|
import { RippleEffect } from '../../ripple/ripple-effect';
|
||||||
import { forwardRef, useRef, useState } from 'react';
|
import { forwardRef, useRef, useState } from 'react';
|
||||||
import useRippleEffect from '../../ripple/hooks/useRippleEffect';
|
import useRippleEffect from '../../ripple/hooks/useRippleEffect';
|
||||||
import { CheckBoxLayout } from '../checkbox-layout/check-box-layout';
|
import { CheckBoxLayout } from '../checkbox-layout/check-box-layout';
|
||||||
|
@ -37,7 +37,7 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
|
||||||
cy={'50%'}
|
cy={'50%'}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<RippleArea
|
<RippleEffect
|
||||||
callback={setIsActive}
|
callback={setIsActive}
|
||||||
central={centralRipple}
|
central={centralRipple}
|
||||||
className={'m3-checkbox-ripple-layer'}
|
className={'m3-checkbox-ripple-layer'}
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
import React, {
|
||||||
|
ForwardedRef,
|
||||||
|
MutableRefObject,
|
||||||
|
useCallback,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
useTransition,
|
||||||
|
} from 'react';
|
||||||
|
import { Ripple } from '../ripple';
|
||||||
|
import { baseDOMRect, RippleContainer } from '../ripple.types';
|
||||||
|
import isEmpty, { rippleSizeCalculator } from '../utils/utils';
|
||||||
|
import { InteractionEventsType } from './useRippleEffect';
|
||||||
|
|
||||||
|
const RIPPLE_LIFETIME: number = 550;
|
||||||
|
const DEBOUNCE: number = 50;
|
||||||
|
|
||||||
|
const UseRippleBuilder = (
|
||||||
|
stateRef: MutableRefObject<boolean>,
|
||||||
|
rippleDomainRef: MutableRefObject<HTMLSpanElement>,
|
||||||
|
central: boolean,
|
||||||
|
forwardedRef: ForwardedRef<RippleContainer>,
|
||||||
|
) => {
|
||||||
|
const [ripples, setRipples] = useState({});
|
||||||
|
const [pending, transition] = useTransition();
|
||||||
|
const debounced = useRef<boolean>(false);
|
||||||
|
const uniqueKey = useRef<number>(0);
|
||||||
|
|
||||||
|
const endLifetimeRipple = (keyRipple: number) => {
|
||||||
|
if (!pending && !isEmpty(ripples)) {
|
||||||
|
transition(() => {
|
||||||
|
setRipples(prevRipples => {
|
||||||
|
delete prevRipples[keyRipple];
|
||||||
|
return prevRipples;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createRipple = useCallback(
|
||||||
|
(
|
||||||
|
event: InteractionEventsType,
|
||||||
|
changeStateCallback: (state: boolean) => void,
|
||||||
|
): void => {
|
||||||
|
if (stateRef.current || debounced.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
debounced.current = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
debounced.current = false;
|
||||||
|
}, DEBOUNCE);
|
||||||
|
|
||||||
|
stateRef.current = true;
|
||||||
|
changeStateCallback(stateRef.current);
|
||||||
|
|
||||||
|
const rippleDomain: DOMRect | baseDOMRect = rippleDomainRef.current
|
||||||
|
? rippleDomainRef.current.getBoundingClientRect()
|
||||||
|
: {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [rippleX, rippleY, rippleS]: Array<number> =
|
||||||
|
rippleSizeCalculator(event, rippleDomain, central);
|
||||||
|
|
||||||
|
const prevRipples = ripples;
|
||||||
|
|
||||||
|
prevRipples[uniqueKey.current] = (
|
||||||
|
<Ripple
|
||||||
|
endLifetime={endLifetimeRipple}
|
||||||
|
key={uniqueKey.current}
|
||||||
|
lifetime={RIPPLE_LIFETIME}
|
||||||
|
rippleKey={uniqueKey.current}
|
||||||
|
rippleS={rippleS}
|
||||||
|
rippleX={rippleX}
|
||||||
|
rippleY={rippleY}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
setRipples(prevRipples);
|
||||||
|
|
||||||
|
uniqueKey.current += 1;
|
||||||
|
},
|
||||||
|
[uniqueKey, debounced],
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeRipple = (
|
||||||
|
event: InteractionEventsType,
|
||||||
|
changeStateCallback: (state: boolean) => void,
|
||||||
|
) => {
|
||||||
|
if (!stateRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stateRef.current = false;
|
||||||
|
changeStateCallback(stateRef.current);
|
||||||
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(
|
||||||
|
forwardedRef,
|
||||||
|
() => ({
|
||||||
|
start: createRipple,
|
||||||
|
stop: removeRipple,
|
||||||
|
current: rippleDomainRef,
|
||||||
|
}),
|
||||||
|
[createRipple, removeRipple],
|
||||||
|
);
|
||||||
|
|
||||||
|
return ripples;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UseRippleBuilder;
|
|
@ -1,131 +0,0 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import React, {
|
|
||||||
forwardRef,
|
|
||||||
ReactElement,
|
|
||||||
useId,
|
|
||||||
useImperativeHandle,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { Ripples, Ripple } from './ripples';
|
|
||||||
import { RippleAreaProps } from './ripple.types';
|
|
||||||
import { InteractionEventsType } from './hooks/useRippleEffect';
|
|
||||||
|
|
||||||
const TIMEOUT: number = 550;
|
|
||||||
const rippleAreaContext = React.createContext(false);
|
|
||||||
|
|
||||||
const RippleArea = forwardRef(
|
|
||||||
({ central = false, ...props }: RippleAreaProps, ref) => {
|
|
||||||
const [ripples, setRipples] = useState<Array<ReactElement>>([]),
|
|
||||||
rippleDomain = useRef(null),
|
|
||||||
clicked = useRef<boolean>(false),
|
|
||||||
uniqueKey = useRef<number>(0),
|
|
||||||
uniqueId = useId();
|
|
||||||
|
|
||||||
const extraClassStyles =
|
|
||||||
`m3 m3-ripple-domain ${props.className ?? ''}`.trimEnd();
|
|
||||||
|
|
||||||
const createRipple = (
|
|
||||||
event: InteractionEventsType,
|
|
||||||
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<ReactElement>) => {
|
|
||||||
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 removeRipple = (
|
|
||||||
_event: InteractionEventsType,
|
|
||||||
cb: (state: boolean) => void,
|
|
||||||
) => {
|
|
||||||
clicked.current = false;
|
|
||||||
cb(clicked.current);
|
|
||||||
|
|
||||||
setRipples((prevRipples: Array<ReactElement>) => {
|
|
||||||
if (prevRipples.length > 0) {
|
|
||||||
const old = [...prevRipples];
|
|
||||||
old.shift();
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
return prevRipples;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useImperativeHandle(
|
|
||||||
ref,
|
|
||||||
() => ({
|
|
||||||
start: createRipple,
|
|
||||||
stop: removeRipple,
|
|
||||||
}),
|
|
||||||
[createRipple, removeRipple],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className={extraClassStyles} id={uniqueId} ref={rippleDomain}>
|
|
||||||
<rippleAreaContext.Provider value={clicked.current}>
|
|
||||||
<Ripples>{ripples}</Ripples>
|
|
||||||
</rippleAreaContext.Provider>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export { rippleAreaContext, RippleArea };
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import useRippleBuilder from './hooks/useRippleBuilder';
|
||||||
|
import React, { forwardRef, useId, useRef } from 'react';
|
||||||
|
import { RippleAreaProps, RippleContainer } from './ripple.types';
|
||||||
|
|
||||||
|
const rippleAreaContext = React.createContext(false);
|
||||||
|
|
||||||
|
const RippleEffect = forwardRef<RippleContainer, RippleAreaProps>(
|
||||||
|
({ central = false, ...props }, ref) => {
|
||||||
|
const uniqueId = useId(),
|
||||||
|
rippleDomain = useRef<HTMLSpanElement>(null),
|
||||||
|
clicked = useRef<boolean>(false),
|
||||||
|
ripples = useRippleBuilder(clicked, rippleDomain, central, ref);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`m3 m3-ripple-domain ${props.className ?? ''}`.trimEnd()}
|
||||||
|
id={uniqueId}
|
||||||
|
ref={rippleDomain}
|
||||||
|
>
|
||||||
|
<rippleAreaContext.Provider value={clicked.current}>
|
||||||
|
{Object.values(ripples)}
|
||||||
|
</rippleAreaContext.Provider>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export { rippleAreaContext, RippleEffect };
|
|
@ -0,0 +1,38 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { RippleProps } from './ripple.types';
|
||||||
|
import { rippleAreaContext } from './ripple-effect';
|
||||||
|
import React, { useContext, useLayoutEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const Ripple = ({
|
||||||
|
rippleX,
|
||||||
|
rippleY,
|
||||||
|
rippleS,
|
||||||
|
lifetime,
|
||||||
|
rippleKey,
|
||||||
|
endLifetime,
|
||||||
|
}: RippleProps) => {
|
||||||
|
const [classes, setClasses] = useState<string>('m3 ripple visible');
|
||||||
|
const rippleDomainContext = useContext(rippleAreaContext);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (endLifetime !== null && !rippleDomainContext) {
|
||||||
|
setClasses('m3 ripple');
|
||||||
|
setTimeout(() => endLifetime(rippleKey), lifetime);
|
||||||
|
}
|
||||||
|
}, [rippleDomainContext]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={classes}
|
||||||
|
style={{
|
||||||
|
left: -(rippleS / 2) + rippleX,
|
||||||
|
top: -(rippleS / 2) + rippleY,
|
||||||
|
width: rippleS,
|
||||||
|
aspectRatio: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Ripple };
|
|
@ -1,7 +1,28 @@
|
||||||
import { Dispatch, HTMLAttributes, ReactElement, SetStateAction } from 'react';
|
import {
|
||||||
|
Dispatch,
|
||||||
|
HTMLAttributes,
|
||||||
|
MutableRefObject,
|
||||||
|
SetStateAction,
|
||||||
|
} from 'react';
|
||||||
|
import { InteractionEventsType } from './hooks/useRippleEffect';
|
||||||
|
|
||||||
export interface RipplesProps extends HTMLAttributes<HTMLElement> {
|
export type baseDOMRect = {
|
||||||
children?: ReactElement[];
|
width: number;
|
||||||
|
height: number;
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface RippleContainer {
|
||||||
|
start: (
|
||||||
|
event: InteractionEventsType,
|
||||||
|
changeStateCallback: (state: boolean) => void,
|
||||||
|
) => void;
|
||||||
|
stop: (
|
||||||
|
event: InteractionEventsType,
|
||||||
|
changeStateCallback: (state: boolean) => void,
|
||||||
|
) => void;
|
||||||
|
current: MutableRefObject<HTMLSpanElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RipplePropsForComponents<T> extends HTMLAttributes<T> {
|
export interface RipplePropsForComponents<T> extends HTMLAttributes<T> {
|
||||||
|
@ -14,10 +35,10 @@ export interface RippleAreaProps extends HTMLAttributes<HTMLElement> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RippleProps extends HTMLAttributes<HTMLElement> {
|
export interface RippleProps extends HTMLAttributes<HTMLElement> {
|
||||||
|
rippleKey: number;
|
||||||
rippleX: number;
|
rippleX: number;
|
||||||
rippleY: number;
|
rippleY: number;
|
||||||
rippleS: number;
|
rippleS: number;
|
||||||
endLifetime?: () => void;
|
|
||||||
lifetime: number;
|
lifetime: number;
|
||||||
key?: number;
|
endLifetime?: (value: number) => void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import isEmpty from './utils/utils';
|
|
||||||
import { rippleAreaContext } from './ripple-area';
|
|
||||||
import { RippleProps, RipplesProps } from './ripple.types';
|
|
||||||
import RippleEffectBuild from './utils/ripple-effect-builder';
|
|
||||||
import React, {
|
|
||||||
ReactElement,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
useTransition,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
const Ripples = (props: RipplesProps) => {
|
|
||||||
const [ripples, setRipples] = useState({});
|
|
||||||
const firstRender = useRef<boolean>(true);
|
|
||||||
const [pending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const endLifetime = (child: ReactElement) => {
|
|
||||||
if (child.props.endLifetime) {
|
|
||||||
child.props.endLifetime();
|
|
||||||
}
|
|
||||||
|
|
||||||
setRipples(state => {
|
|
||||||
const children = { ...state };
|
|
||||||
delete children[child.key];
|
|
||||||
return children;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (props.children.length > 0 && !pending) {
|
|
||||||
startTransition(() => {
|
|
||||||
if (firstRender.current || isEmpty(ripples)) {
|
|
||||||
setRipples(RippleEffectBuild(props.children, endLifetime));
|
|
||||||
firstRender.current = false;
|
|
||||||
} else {
|
|
||||||
setRipples(
|
|
||||||
RippleEffectBuild(props.children, endLifetime, ripples),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [props.children]);
|
|
||||||
|
|
||||||
return <>{Object.values(ripples)}</>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Ripple = ({
|
|
||||||
rippleX,
|
|
||||||
rippleY,
|
|
||||||
rippleS,
|
|
||||||
endLifetime,
|
|
||||||
lifetime,
|
|
||||||
}: RippleProps) => {
|
|
||||||
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 { Ripples, Ripple };
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { cloneElement, ReactElement } from 'react';
|
|
||||||
|
|
||||||
export default function ArrayConvertToObj(
|
|
||||||
obj: object,
|
|
||||||
nextChildren: ReactElement[],
|
|
||||||
callback: (child: ReactElement) => void,
|
|
||||||
): void {
|
|
||||||
Object.values(nextChildren).forEach(
|
|
||||||
(child: ReactElement) =>
|
|
||||||
(obj[child.key] = cloneElement(child, {
|
|
||||||
...child.props,
|
|
||||||
endLifetime: callback.bind(null, child),
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import isEmpty from './utils';
|
|
||||||
import ArrayConvertToObj from './array-convert-to-obj';
|
|
||||||
import { cloneElement, ReactElement } from 'react';
|
|
||||||
|
|
||||||
export default function RippleEffectBuild(
|
|
||||||
nextRipples: ReactElement[],
|
|
||||||
callback: (child: ReactElement) => void,
|
|
||||||
prevRipples?: object | 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;
|
|
||||||
}
|
|
|
@ -1,6 +1,31 @@
|
||||||
|
import { InteractionEventsType } from '../hooks/useRippleEffect';
|
||||||
|
import { baseDOMRect } from '../ripple.types';
|
||||||
|
|
||||||
export default function isEmpty(obj: object): boolean {
|
export default function isEmpty(obj: object): boolean {
|
||||||
for (const _i in obj) {
|
for (const _i in obj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function maxSize(size: number, cordPos: number): number {
|
||||||
|
return Math.max(Math.abs(size - cordPos), cordPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rippleSizeCalculator(
|
||||||
|
event: InteractionEventsType,
|
||||||
|
rippleDomain: DOMRect | baseDOMRect,
|
||||||
|
centralRipple: boolean,
|
||||||
|
): Array<number> {
|
||||||
|
const rippleX: number = !centralRipple
|
||||||
|
? event.clientX - rippleDomain.left
|
||||||
|
: rippleDomain.width / 2,
|
||||||
|
rippleY: number = !centralRipple
|
||||||
|
? event.clientY - rippleDomain.top
|
||||||
|
: rippleDomain.height / 2,
|
||||||
|
rippleSizeX: number = maxSize(rippleDomain.width, rippleX) * 2,
|
||||||
|
rippleSizeY: number = maxSize(rippleDomain.height, rippleY) * 2,
|
||||||
|
rippleS: number = (rippleSizeX ** 2 + rippleSizeY ** 2) ** 0.5;
|
||||||
|
|
||||||
|
return [rippleX, rippleY, rippleS];
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue