catalinmiron/react-typical

Make compatible with TypeScript

acferM opened this issue ยท 2 comments

I was recently making a site using TSX an i wanted to use react-typical but when tried to install @types/react-typical it was not finding that module, so i guess they didn't made it yet.

The source code of the project is quite small, and it doesn't "hurt" that much to do // @ts-ignore before importing the module.

And first it'd be needed to type https://github.com/camwiegert/typical, the library this component uses.

@acferM I just used this project locally as a TS module. Here's what I did ๐Ÿ‘‡

As @davidomarf rightfully said, https://github.com/camwiegert/typical isn't typed.

So I made a declaration.d.ts at the root of the project.

I am using Next.js with src/ folder as my root so I created src/declaration.d.ts. I wrote types of @camwiegert/typical in this file.

src/declaration.d.ts

declare module '@camwiegert/typical' {
	export function type(node: HTMLElement, ...args: any[]): Promise<void>

	declare function edit(node: HTMLElement, text: string): Promise<void>

	declare function wait(ms: number): Promise<void>

	declare function perform(node: HTMLElement, edits: any, speed?: number): Promise<void>

	export function editor(edits: any): Generator<(node: any) => number, void, unknown>

	export function writer(
		[...text]: Iterable<any>,
		startIndex?: number,
		endIndex?: number
	): Generator<string, void, unknown>

	export function deleter(
		[...text]: Iterable<any>,
		startIndex?: number,
		endIndex?: number
	): Generator<string, void, unknown>

	export function getOverlap(start: any, [...end]: Iterable<any>): number
}

Then I created a lib/react-typical folder. In that folder, I created 2 files: index.ts & styles.module.css. I needed to create styles.module.css because I couldn't import .css files like import styles from "./styles.css because in Next.js or rather in React, we do it like import "./styles.css".

Not sure, how this module does it. It might be some rollup magic Idk.

But in Next.js, we can import styles from "./styles.module.css" if & only if you have the file extension .module.css.

It was also giving TS error if I did import styles from "./styles.css" so I used .module.css.

I also merged PR #8 into my code & exported the function as a named export rather than default export. I faced a weird error while using <Wrapper /> so I had to use React.createElement instead. You can learn more about the errors here. But it's all solved so you can copy-paste this thing into your project & it should work :)

index.ts

import React from 'react'
import { type, type as loopedType } from '@camwiegert/typical'
import styles from './styles.module.css'

type Props = {
	steps: Array<any>
	loop: number
	className?: string
	wrapper?: keyof JSX.IntrinsicElements
} & React.HTMLAttributes<HTMLOrSVGElement>

const Typical = ({ steps, loop, className, wrapper: Wrapper = 'p' }: Props) => {
	const typicalRef: React.RefObject<HTMLElement> = React.useRef<HTMLElement>(null)
	const classNames: string[] = [styles.typicalWrapper]

	if (className) {
		classNames.unshift(className)
	}

	const typicalStyles: string = classNames.join(' ')

	React.useEffect(() => {
		if (loop === Infinity) {
			type(typicalRef.current as HTMLElement, ...steps, loopedType)
		} else if (typeof loop === 'number') {
			type(typicalRef.current as HTMLElement, ...Array(loop).fill(steps).flat())
		} else {
			type(typicalRef.current as HTMLElement, ...steps)
		}
	}, [typicalRef])

	return React.createElement(Wrapper, {
		ref: typicalRef,
		className: typicalStyles,
	})
}

export const ReactTypical = React.memo(Typical)

styles.module.css

.typicalWrapper::after {
	content: '|';
	animation: blink 1s infinite step-start;
}

@keyframes blink {
	50% {
		opacity: 0;
	}
}

That's it. The usage is like:

class Index extends React.Component {
    state = {
	  hypeText: ['Creator', 'Entrepreneur', 'Doer', 'Lone Wolf'],
	}
    
    render() {
      return <ReactTypical steps={hypeText.flatMap((hype) => [hype, 5000])} loop={Infinity} />
    }
}