FIXED: Interfaces

This commit is contained in:
doryan04 2024-02-01 22:24:37 +04:00
parent ab4ae37015
commit 218b8f61d6
20 changed files with 163 additions and 137 deletions

View File

@ -1,3 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const prettierConfig = require('./.prettierrc.js'); const prettierConfig = require('./.prettierrc.js');
module.exports = { module.exports = {

View File

@ -1,6 +1,7 @@
<component name="InspectionProjectProfileManager"> <component name="InspectionProjectProfileManager">
<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="Eslint" enabled="true" level="WARNING" enabled_by_default="true" /> <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile> </profile>
</component> </component>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<option name="fix-on-save" value="true" />
</component>
</project>

View File

@ -6,8 +6,14 @@ export default function Badges() {
return ( return (
<div <div
className={'m3 m3-wrapper'} className={'m3 m3-wrapper'}
style={{ display: 'flex', flexDirection: 'row' }} style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
}}
> >
<h1> Badges </h1>
<div> <div>
<div <div
style={{ style={{

View File

@ -7,16 +7,14 @@ import useRippleEffect from '../ripple/hooks/useRippleEffect';
import React, { forwardRef, useId, useRef, useState } from 'react'; import React, { forwardRef, useId, useRef, useState } from 'react';
export const ButtonLayout = forwardRef<HTMLButtonElement, ButtonLayoutProps>( export const ButtonLayout = forwardRef<HTMLButtonElement, ButtonLayoutProps>(
function ButtonBase({ centralRipple = false, ...props }, ref) { ({ centralRipple = false, variant, ...props }, ref) => {
const [isActive, setIsActive] = useState<boolean>(false), const [isActive, setIsActive] = useState<boolean>(false),
ripplesRef = useRef(null), ripplesRef = useRef(null),
buttonId = useId(), buttonId = useId(),
events = useRippleEffect(ripplesRef, setIsActive); events = useRippleEffect(ripplesRef, setIsActive);
const { variant, disabled, className } = props; const classes = props.className
? `m3 ${props.className} ${variant}${isActive ? ' is-active' : ''}`
const classes = className
? `m3 ${className} ${variant}${isActive ? ' is-active' : ''}`
: `m3 ${variant}${isActive ? ' is-active' : ''}`; : `m3 ${variant}${isActive ? ' is-active' : ''}`;
return ( return (
@ -24,7 +22,7 @@ export const ButtonLayout = forwardRef<HTMLButtonElement, ButtonLayoutProps>(
{...props} {...props}
{...events} {...events}
className={classes} className={classes}
disabled={disabled} disabled={props.disabled}
id={buttonId} id={buttonId}
ref={ref} ref={ref}
> >
@ -40,6 +38,7 @@ export const ButtonLayout = forwardRef<HTMLButtonElement, ButtonLayoutProps>(
); );
ButtonLayout.propTypes = { ButtonLayout.propTypes = {
variant: string,
centralRipple: bool, centralRipple: bool,
children: string, children: string,
}; };

View File

@ -1,6 +1,6 @@
import { ButtonHTMLAttributes } from 'react'; import { ButtonHTMLAttributes } from 'react';
import { IRippleProps } from '../ripple/ripple.types'; import { IRippleProps } from '../ripple/ripple.types';
export interface ButtonLayoutProps export type ButtonLayoutProps = IRippleProps & ButtonHTMLAttributes<HTMLButtonElement> & {
extends IRippleProps, variant?: string;
ButtonHTMLAttributes<HTMLButtonElement> {} };

View File

@ -5,6 +5,7 @@ export interface ButtonMainProps
extends ButtonHTMLAttributes<HTMLButtonElement> { extends ButtonHTMLAttributes<HTMLButtonElement> {
disabled?: boolean; disabled?: boolean;
variant?: 'filled' | 'outlined' | 'elevated' | 'tonal' | 'text'; variant?: 'filled' | 'outlined' | 'elevated' | 'tonal' | 'text';
icon?: string;
} }
export interface ButtonProps extends IRippleProps, ButtonMainProps {} export type ButtonProps = IRippleProps & ButtonMainProps;

View File

@ -1,6 +1,7 @@
import { InputHTMLAttributes } from 'react'; import { InputHTMLAttributes } from 'react';
import { IRippleProps } from '../ripple/ripple.types'; import { IRippleProps } from '../ripple/ripple.types';
export interface CheckboxProps export type CheckboxProps = InputHTMLAttributes<HTMLInputElement> &
extends InputHTMLAttributes<HTMLInputElement>, IRippleProps & {
IRippleProps {} indeterminate?: boolean;
};

View File

@ -1,12 +1,14 @@
import React from 'react'; import { ButtonHTMLAttributes } from 'react';
import { IRippleProps } from '../ripple/ripple.types'; import { IRippleProps } from '../ripple/ripple.types';
export interface FABMainProps export interface FABMainProps {
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
icon: string; icon: string;
disabled?: boolean; disabled?: boolean;
elevated?: boolean;
size?: 'small' | 'default' | 'large' | 'extended'; size?: 'small' | 'default' | 'large' | 'extended';
variant?: 'surface' | 'primary' | 'secondary' | 'tertiary'; variant?: 'surface' | 'primary' | 'secondary' | 'tertiary';
} }
export interface FABProps extends FABMainProps, IRippleProps {} export type FABProps = FABMainProps &
IRippleProps &
ButtonHTMLAttributes<HTMLButtonElement>;

View File

@ -5,6 +5,7 @@ import { ButtonLayout } from '../button-layout/button-layout';
import { IconButtonProps, StateToggleIconType } from './icon-button.types'; import { IconButtonProps, StateToggleIconType } from './icon-button.types';
import { import {
forwardRef, forwardRef,
MouseEventHandler,
useCallback, useCallback,
useImperativeHandle, useImperativeHandle,
useRef, useRef,
@ -44,7 +45,7 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
const buttonRef = useRef<HTMLButtonElement>(null); const buttonRef = useRef<HTMLButtonElement>(null);
const callback = useCallback( const callback = useCallback(
(e: MouseEvent) => { (event: MouseEventHandler<HTMLButtonElement>) => {
if (toggled) { if (toggled) {
if (toggleIcon.state === 'selected') { if (toggleIcon.state === 'selected') {
toggle('', toggled.unselected ?? 'add_circle'); toggle('', toggled.unselected ?? 'add_circle');
@ -53,7 +54,7 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
} }
} }
if (props.onClick) { if (props.onClick) {
props.onClick.apply(null, e); props.onClick.apply(null, event);
} }
}, },
[toggleIcon], [toggleIcon],
@ -72,7 +73,7 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
variant={variant ? variant : 'default'} variant={variant ? variant : 'default'}
> >
<Icon <Icon
fill={toggleIcon.state === 'selected' ? 1 : 0} fillIcon={toggleIcon.state === 'selected' ? 1 : 0}
iconSize={28} iconSize={28}
svgSize={40} svgSize={40}
> >

View File

@ -1,6 +1,14 @@
import React from 'react'; import { ButtonHTMLAttributes } from 'react';
import { IRippleProps } from '../ripple/ripple.types'; import { IRippleProps } from '../ripple/ripple.types';
export interface IconButtonMainProps {
icon: string;
selected?: boolean;
disabled?: boolean;
toggled?: false | ToggleButtonType;
variant?: 'default' | 'filled' | 'tonal' | 'outlined';
}
export type StateToggleIconType = { export type StateToggleIconType = {
state: string; state: string;
icon: string; icon: string;
@ -11,12 +19,6 @@ export type ToggleButtonType = {
unselected: string; unselected: string;
}; };
export interface IconButtonMainProps export type IconButtonProps = IconButtonMainProps &
extends React.ButtonHTMLAttributes<HTMLButtonElement> { IRippleProps &
icon: string; ButtonHTMLAttributes<HTMLButtonElement>;
toggled?: false | ToggleButtonType;
disabled?: boolean;
variant?: 'default' | 'filled' | 'tonal' | 'outlined';
}
export interface IconButtonProps extends IconButtonMainProps, IRippleProps {}

View File

@ -1,8 +1,7 @@
import { InputHTMLAttributes } from 'react'; import { InputHTMLAttributes } from 'react';
import { IRippleProps } from '../ripple/ripple.types'; import { IRippleProps } from '../ripple/ripple.types';
export interface RadioProps export type RadioProps = InputHTMLAttributes<HTMLInputElement> &
extends InputHTMLAttributes<HTMLInputElement>, IRippleProps & {
IRippleProps { centralRipple?: boolean;
centralRipple?: boolean; };
}

View File

@ -1,17 +1,36 @@
import React, { useEffect, useState } from 'react'; import {
DragEventHandler,
FocusEventHandler,
MouseEventHandler,
TouchEventHandler,
useEffect,
useState,
} from 'react';
interface RippleEventHandlers { interface RippleEventHandlers {
onBlur: React.FocusEventHandler; onBlur: FocusEventHandler;
onContextMenu: React.MouseEventHandler; onContextMenu: MouseEventHandler;
onDragLeave: React.DragEventHandler; onDragLeave: DragEventHandler;
onMouseDown: React.MouseEventHandler; onMouseDown: MouseEventHandler;
onMouseLeave: React.MouseEventHandler; onMouseLeave: MouseEventHandler;
onMouseUp: React.MouseEventHandler; onMouseUp: MouseEventHandler;
onTouchEnd: React.TouchEventHandler; onTouchEnd: TouchEventHandler;
onTouchMove: React.TouchEventHandler; onTouchMove: TouchEventHandler;
onTouchStart: React.TouchEventHandler; onTouchStart: TouchEventHandler;
} }
export type InteractionEventsType = MouseEvent & TouchEvent & DragEvent & FocusEvent
export interface InteractionEvents<T>
extends MouseEvent,
TouchEvent,
DragEvent,
FocusEvent,
MouseEventHandler<T>,
DragEventHandler<T>,
FocusEventHandler<T>,
TouchEventHandler<T> {}
const UseRippleEffect = (ref, callback): undefined | RippleEventHandlers => { const UseRippleEffect = (ref, callback): undefined | RippleEventHandlers => {
const [mounted, setMounted] = useState<boolean>(false); const [mounted, setMounted] = useState<boolean>(false);

View File

@ -2,7 +2,7 @@
import React, { import React, {
forwardRef, forwardRef,
useCallback, ReactElement,
useId, useId,
useImperativeHandle, useImperativeHandle,
useRef, useRef,
@ -11,26 +11,27 @@ import React, {
import { Ripple } from './ripple'; import { Ripple } from './ripple';
import { Ripples } from './ripple'; import { Ripples } from './ripple';
import { RippleAreaProps } from './ripple.types'; import { RippleAreaProps } from './ripple.types';
import {InteractionEvents, InteractionEventsType} from './hooks/useRippleEffect';
const TIMEOUT: number = 550; const TIMEOUT: number = 550;
const rippleAreaContext = React.createContext(false); const rippleAreaContext = React.createContext(false);
const RippleArea = forwardRef(function RippleArea( const RippleArea = forwardRef(
{ central = false, callback, ...props }: RippleAreaProps, ({ central = false, ...props }: RippleAreaProps, ref) => {
ref, const [ripples, setRipples] = useState<Array<ReactElement>>([]),
) { rippleDomain = useRef(null),
const [ripples, setRipples] = useState<Array<JSX.Element>>([]), clicked = useRef<boolean>(false),
rippleDomain = useRef<any>(null), uniqueKey = useRef<number>(0),
clicked = useRef<boolean>(false), uniqueId = useId();
uniqueKey = useRef<number>(0),
uniqueId = useId();
const classes = props.className const classes = props.className
? `m3 m3-ripple-domain ${props.className}`.trimEnd() ? `m3 m3-ripple-domain ${props.className}`.trimEnd()
: 'm3 m3-ripple-domain'; : 'm3 m3-ripple-domain';
const start = useCallback( const start = (
(event: any, cb: (state: boolean) => void): void => { event: InteractionEventsType,
cb: (state: boolean) => void,
): void => {
clicked.current = true; clicked.current = true;
cb(clicked.current); cb(clicked.current);
@ -65,7 +66,7 @@ const RippleArea = forwardRef(function RippleArea(
2, 2,
rippleS: number = (rippleSizeX ** 2 + rippleSizeY ** 2) ** 0.5; rippleS: number = (rippleSizeX ** 2 + rippleSizeY ** 2) ** 0.5;
setRipples((prevRipples: Array<JSX.Element>) => { setRipples((prevRipples: Array<ReactElement>) => {
if (prevRipples.length === 0) { if (prevRipples.length === 0) {
return [ return [
<Ripple <Ripple
@ -91,40 +92,42 @@ const RippleArea = forwardRef(function RippleArea(
}); });
uniqueKey.current += 1; uniqueKey.current += 1;
}, };
[],
);
const stop = useCallback((_event: any, cb: (state: boolean) => void) => { const stop = (
clicked.current = false; _event: InteractionEventsType,
cb(clicked.current); cb: (state: boolean) => void,
) => {
clicked.current = false;
cb(clicked.current);
setRipples((prevRipples: Array<JSX.Element>) => { setRipples((prevRipples: Array<ReactElement>) => {
if (prevRipples.length > 0) { if (prevRipples.length > 0) {
const old = [...prevRipples]; const old = [...prevRipples];
old.shift(); old.shift();
return old; return old;
} }
return prevRipples; return prevRipples;
}); });
}, []); };
useImperativeHandle( useImperativeHandle(
ref, ref,
() => ({ () => ({
start, start,
stop, stop,
}), }),
[start, stop], [start, stop],
); );
return ( return (
<span className={classes} id={uniqueId} ref={rippleDomain}> <span className={classes} id={uniqueId} ref={rippleDomain}>
<rippleAreaContext.Provider value={clicked.current}> <rippleAreaContext.Provider value={clicked.current}>
<Ripples>{ripples}</Ripples> <Ripples>{ripples}</Ripples>
</rippleAreaContext.Provider> </rippleAreaContext.Provider>
</span> </span>
); );
}); },
);
export { rippleAreaContext, RippleArea }; export { rippleAreaContext, RippleArea };

View File

@ -1,14 +1,11 @@
'use client'; 'use client';
import isEmpty from './utils/utils'; import isEmpty from './utils/utils';
import { rippleProps } from './ripple.types'; import { RippleProps, RipplesProps } from './ripple.types';
import { rippleAreaContext } from './ripple-area'; import { rippleAreaContext } from './ripple-area';
import RippleEffectBuild from './utils/ripple-effect-builder'; import RippleEffectBuild from './utils/ripple-effect-builder';
import React, { import React, {
ForwardedRef, ReactElement,
forwardRef,
JSX,
useCallback,
useContext, useContext,
useEffect, useEffect,
useRef, useRef,
@ -16,15 +13,12 @@ import React, {
useTransition, useTransition,
} from 'react'; } from 'react';
const Ripples = forwardRef(function Ripples( const Ripples = (props: RipplesProps) => {
props: any,
ref: ForwardedRef<any>,
) {
const [ripples, setRipples] = useState({}); const [ripples, setRipples] = useState({});
const firstRender = useRef<boolean>(true); const firstRender = useRef<boolean>(true);
const [pending, startTransition] = useTransition(); const [pending, startTransition] = useTransition();
const LifetimeEnd = useCallback((child: JSX.Element) => { const LifetimeEnd = (child: ReactElement) => {
if (child.props.endLifetime) { if (child.props.endLifetime) {
child.props.endLifetime(); child.props.endLifetime();
} }
@ -34,10 +28,10 @@ const Ripples = forwardRef(function Ripples(
delete children[child.key]; delete children[child.key];
return children; return children;
}); });
}, []); };
useEffect(() => { useEffect(() => {
if (props.children.length > 0) { if (props.children.length > 0 && !pending) {
startTransition(() => { startTransition(() => {
if (firstRender.current || isEmpty(ripples)) { if (firstRender.current || isEmpty(ripples)) {
setRipples(RippleEffectBuild(props.children, LifetimeEnd)); setRipples(RippleEffectBuild(props.children, LifetimeEnd));
@ -52,14 +46,15 @@ const Ripples = forwardRef(function Ripples(
}, [props.children]); }, [props.children]);
return <>{Object.values(ripples)}</>; return <>{Object.values(ripples)}</>;
}); };
const Ripple = forwardRef(function Ripple(
props: rippleProps,
ref: ForwardedRef<any>,
) {
const { rippleX, rippleY, rippleS, endLifetime, lifetime } = props;
const Ripple = ({
rippleX,
rippleY,
rippleS,
endLifetime,
lifetime,
}: RippleProps) => {
const clicked = useContext<boolean>(rippleAreaContext); const clicked = useContext<boolean>(rippleAreaContext);
const [classes, setClasses] = useState<string>('m3 ripple visible'); const [classes, setClasses] = useState<string>('m3 ripple visible');
@ -81,6 +76,6 @@ const Ripple = forwardRef(function Ripple(
}} }}
/> />
); );
}); };
export { Ripple, Ripples }; export { Ripple, Ripples };

View File

@ -1,21 +1,23 @@
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, HTMLAttributes, ReactElement, SetStateAction } from 'react';
export interface IRippleProps extends PropsWithChildren<any> { export interface RipplesProps extends HTMLAttributes<HTMLElement> {
children?: ReactElement[];
}
export interface IRippleProps extends HTMLAttributes<HTMLElement> {
centralRipple?: boolean; centralRipple?: boolean;
} }
export interface RippleAreaProps extends PropsWithChildren<any> { export interface RippleAreaProps extends HTMLAttributes<HTMLElement> {
callback: Dispatch<SetStateAction<boolean>>; callback: Dispatch<SetStateAction<boolean>>;
central?: boolean; central?: boolean;
} }
export type rippleProps = { export interface RippleProps extends HTMLAttributes<HTMLElement> {
rippleX: number; rippleX: number;
rippleY: number; rippleY: number;
rippleS: number; rippleS: number;
endLifetime?: () => void; endLifetime?: () => void;
lifetime: number; lifetime: number;
key?: number; key?: number;
}; }
import { PropsWithChildren } from 'react';

View File

@ -1,12 +1,12 @@
import { cloneElement, ReactElement } from 'react'; import { cloneElement, ReactElement } from 'react';
export default function ArrayConvertToObj( export default function ArrayConvertToObj(
obj: Object, obj: object,
nextChildren: ReactElement[], nextChildren: ReactElement[],
callback: (child: any) => void, callback: (child: ReactElement) => void,
): void { ): void {
Object.values(nextChildren).forEach( Object.values(nextChildren).forEach(
(child: JSX.Element) => (child: ReactElement) =>
(obj[child.key] = cloneElement(child, { (obj[child.key] = cloneElement(child, {
...child.props, ...child.props,
endLifetime: callback.bind(null, child), endLifetime: callback.bind(null, child),

View File

@ -1,11 +1,11 @@
import isEmpty from './utils';
import ArrayConvertToObj from './array-convert-to-obj'; import ArrayConvertToObj from './array-convert-to-obj';
import { cloneElement, ReactElement } from 'react'; import { cloneElement, ReactElement } from 'react';
import isEmpty from './utils';
export default function RippleEffectBuild( export default function RippleEffectBuild(
nextRipples: ReactElement[], nextRipples: ReactElement[],
callback: (child: any) => void, callback: (child: ReactElement) => void,
prevRipples?: any | null, prevRipples?: object | null,
) { ) {
const empty: boolean = isEmpty(prevRipples); const empty: boolean = isEmpty(prevRipples);
const preparedRipples: object = empty ? {} : prevRipples; const preparedRipples: object = empty ? {} : prevRipples;

View File

@ -1,4 +1,4 @@
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;
} }

View File

@ -15,7 +15,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldInterface>(
}, },
ref, ref,
) => { ) => {
const [raised, setRaised] = useState<boolean>(!props.placeholder); const [raised, setRaised] = useState<boolean>(!!props.placeholder);
const callback = (e: FocusEvent<HTMLInputElement>): void => { const callback = (e: FocusEvent<HTMLInputElement>): void => {
if ( if (
@ -92,10 +92,10 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldInterface>(
TextField.propTypes = { TextField.propTypes = {
children: string, children: string,
withBeforeIcon: bool,
withAfterIcon: bool,
className: string, className: string,
variant: oneOf(['filled', 'outlined']),
placeholder: string, placeholder: string,
withAfterIcon: bool,
withBeforeIcon: bool,
supportingText: string, supportingText: string,
variant: oneOf(['filled', 'outlined']),
}; };