diff --git a/.eslintrc.js b/.eslintrc.js
index 77bfc4e..e2138aa 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -27,7 +27,7 @@ module.exports = {
plugins: ['react'],
rules: {
// Possible errors
- 'no-console': 'warn',
+ 'no-console': 'off',
// Best practices
'dot-notation': 'error',
'no-else-return': 'error',
@@ -36,9 +36,10 @@ module.exports = {
// Stylistic
'array-bracket-spacing': 'error',
'computed-property-spacing': ['error', 'never'],
+ '@typescript-eslint/no-unused-vars': 'warn',
curly: 'error',
'no-lonely-if': 'error',
- 'no-unneeded-ternary': 'error',
+ 'no-unneeded-ternary': 'warn',
'one-var-declaration-per-line': 'error',
quotes: [
'error',
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index e6591d4..f21e1f1 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -2,5 +2,6 @@
+
\ No newline at end of file
diff --git a/README.md b/README.md
index db9f067..c18e587 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ This repository is including and will be including components, enumerates in tab
- [X] Switches
- [ ] Chips
- [x] Icon
-- [x] Ripples Effect
+- [x] Ripple Effect
- [ ] Dividers (WIP)
- [x] Badges
- [ ] Select field
diff --git a/src/primitive-components/button-components/button-layout/button-layout.tsx b/src/primitive-components/button-components/button-layout/button-layout.tsx
index cb4253b..5d976df 100644
--- a/src/primitive-components/button-components/button-layout/button-layout.tsx
+++ b/src/primitive-components/button-components/button-layout/button-layout.tsx
@@ -1,7 +1,7 @@
'use client';
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 useRippleEffect from '../../ripple/hooks/useRippleEffect';
import React, { forwardRef, useId, useRef, useState } from 'react';
@@ -26,7 +26,7 @@ export const ButtonLayout = forwardRef(
ref={ref}
>
{props.children}
- (
{ripples && (
- (
/>
- (
cy={'50%'}
/>
- ,
+ rippleDomainRef: MutableRefObject,
+ central: boolean,
+ forwardedRef: ForwardedRef,
+) => {
+ const [ripples, setRipples] = useState({});
+ const [pending, transition] = useTransition();
+ const debounced = useRef(false);
+ const uniqueKey = useRef(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 =
+ rippleSizeCalculator(event, rippleDomain, central);
+
+ const prevRipples = ripples;
+
+ prevRipples[uniqueKey.current] = (
+
+ );
+
+ 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;
diff --git a/src/primitive-components/ripple/ripple-area.tsx b/src/primitive-components/ripple/ripple-area.tsx
deleted file mode 100644
index 08da78e..0000000
--- a/src/primitive-components/ripple/ripple-area.tsx
+++ /dev/null
@@ -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>([]),
- rippleDomain = useRef(null),
- clicked = useRef(false),
- uniqueKey = useRef(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) => {
- if (prevRipples.length === 0) {
- return [
- ,
- ];
- }
- const old = [...prevRipples];
- old.push(
- ,
- );
- return old;
- });
-
- uniqueKey.current += 1;
- };
-
- const removeRipple = (
- _event: InteractionEventsType,
- cb: (state: boolean) => void,
- ) => {
- clicked.current = false;
- cb(clicked.current);
-
- setRipples((prevRipples: Array) => {
- if (prevRipples.length > 0) {
- const old = [...prevRipples];
- old.shift();
- return old;
- }
- return prevRipples;
- });
- };
-
- useImperativeHandle(
- ref,
- () => ({
- start: createRipple,
- stop: removeRipple,
- }),
- [createRipple, removeRipple],
- );
-
- return (
-
-
- {ripples}
-
-
- );
- },
-);
-
-export { rippleAreaContext, RippleArea };
diff --git a/src/primitive-components/ripple/ripple-effect.tsx b/src/primitive-components/ripple/ripple-effect.tsx
new file mode 100644
index 0000000..ab134c4
--- /dev/null
+++ b/src/primitive-components/ripple/ripple-effect.tsx
@@ -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(
+ ({ central = false, ...props }, ref) => {
+ const uniqueId = useId(),
+ rippleDomain = useRef(null),
+ clicked = useRef(false),
+ ripples = useRippleBuilder(clicked, rippleDomain, central, ref);
+
+ return (
+
+
+ {Object.values(ripples)}
+
+
+ );
+ },
+);
+
+export { rippleAreaContext, RippleEffect };
diff --git a/src/primitive-components/ripple/ripple.tsx b/src/primitive-components/ripple/ripple.tsx
new file mode 100644
index 0000000..f886872
--- /dev/null
+++ b/src/primitive-components/ripple/ripple.tsx
@@ -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('m3 ripple visible');
+ const rippleDomainContext = useContext(rippleAreaContext);
+
+ useLayoutEffect(() => {
+ if (endLifetime !== null && !rippleDomainContext) {
+ setClasses('m3 ripple');
+ setTimeout(() => endLifetime(rippleKey), lifetime);
+ }
+ }, [rippleDomainContext]);
+
+ return (
+
+ );
+};
+
+export { Ripple };
diff --git a/src/primitive-components/ripple/ripple.types.ts b/src/primitive-components/ripple/ripple.types.ts
index 8fa9b6e..c27d50d 100644
--- a/src/primitive-components/ripple/ripple.types.ts
+++ b/src/primitive-components/ripple/ripple.types.ts
@@ -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 {
- children?: ReactElement[];
+export type baseDOMRect = {
+ 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;
}
export interface RipplePropsForComponents extends HTMLAttributes {
@@ -14,10 +35,10 @@ export interface RippleAreaProps extends HTMLAttributes {
}
export interface RippleProps extends HTMLAttributes {
+ rippleKey: number;
rippleX: number;
rippleY: number;
rippleS: number;
- endLifetime?: () => void;
lifetime: number;
- key?: number;
+ endLifetime?: (value: number) => void;
}
diff --git a/src/primitive-components/ripple/ripples.tsx b/src/primitive-components/ripple/ripples.tsx
deleted file mode 100644
index 9cf81b9..0000000
--- a/src/primitive-components/ripple/ripples.tsx
+++ /dev/null
@@ -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(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(rippleAreaContext);
- const [classes, setClasses] = useState('m3 ripple visible');
-
- useEffect(() => {
- if (endLifetime !== null && !clicked) {
- setClasses('m3 ripple');
- setTimeout(endLifetime, lifetime);
- }
- }, [clicked, endLifetime]);
-
- return (
-
- );
-};
-
-export { Ripples, Ripple };
diff --git a/src/primitive-components/ripple/utils/array-convert-to-obj.ts b/src/primitive-components/ripple/utils/array-convert-to-obj.ts
deleted file mode 100644
index c1166ee..0000000
--- a/src/primitive-components/ripple/utils/array-convert-to-obj.ts
+++ /dev/null
@@ -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),
- })),
- );
-}
diff --git a/src/primitive-components/ripple/utils/ripple-effect-builder.ts b/src/primitive-components/ripple/utils/ripple-effect-builder.ts
deleted file mode 100644
index 0e9655c..0000000
--- a/src/primitive-components/ripple/utils/ripple-effect-builder.ts
+++ /dev/null
@@ -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;
-}
diff --git a/src/primitive-components/ripple/utils/utils.ts b/src/primitive-components/ripple/utils/utils.ts
index 7d663b9..e9f2a7f 100644
--- a/src/primitive-components/ripple/utils/utils.ts
+++ b/src/primitive-components/ripple/utils/utils.ts
@@ -1,6 +1,31 @@
+import { InteractionEventsType } from '../hooks/useRippleEffect';
+import { baseDOMRect } from '../ripple.types';
+
export default function isEmpty(obj: object): boolean {
for (const _i in obj) {
return false;
}
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 {
+ 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];
+}