Skip to content

Commit 7e0db32

Browse files
committed
add demo site and docs simlink
1 parent 2cb65a7 commit 7e0db32

File tree

19 files changed

+250
-60
lines changed

19 files changed

+250
-60
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# Typer Diff
22

3+
![Typer Diff](assets/demo.png)

apps/docs/app/components/DiffText.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"use client";
2+
import { cn } from "@/lib/utils";
3+
import { useAtom } from "jotai";
4+
import { useRef } from "react";
5+
import { useClickInside, useClickOutside } from "@/app/hooks/hooks";
6+
import { diffText, isTyping } from "@/data/state";
7+
8+
export default function DiffText() {
9+
const [diff] = useAtom(diffText);
10+
const [typing, setTyping] = useAtom(isTyping);
11+
const ref = useRef<HTMLDivElement>(null);
12+
useClickInside(ref, () => {
13+
setTyping(true);
14+
});
15+
useClickOutside(ref, () => {
16+
setTyping(false);
17+
});
18+
return (
19+
<div
20+
className="text-3xl text-white/50 font-mono leading-relaxed tracking-wide overflow-x-hidden scroll mx-auto max-w-screen-lg"
21+
ref={ref}>
22+
{diff.diff.map((item, index) => (
23+
<span
24+
key={index}
25+
className={cn(
26+
item.type === "correct" && "text-white",
27+
item.type === "extra" &&
28+
"text-red-500/50 underline decoration-red-500/50 decoration-wavy",
29+
item.type === "missing" &&
30+
"text-white/50 underline decoration-red-500 decoration-wavy",
31+
item.type === "wrong" && "text-red-500"
32+
)}>
33+
{item.value}
34+
</span>
35+
))}
36+
</div>
37+
);
38+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use client";
2+
import { allowModifications, textAtom, isTyping } from "@/data/state";
3+
import { useAtom } from "jotai";
4+
import { useEffect, useRef } from "react";
5+
6+
export default function HiddenInput() {
7+
let inputRef = useRef<HTMLInputElement>(null);
8+
let [text, setText] = useAtom(textAtom);
9+
const [allow] = useAtom(allowModifications);
10+
const [typing] = useAtom(isTyping);
11+
12+
const handleKeyDown = () => {
13+
if (!inputRef || !inputRef.current || !typing) {
14+
return;
15+
}
16+
inputRef.current.focus();
17+
let val = inputRef.current.value;
18+
inputRef.current.value = "";
19+
inputRef.current.value = val;
20+
};
21+
22+
useEffect(() => {
23+
document.addEventListener("keydown", handleKeyDown);
24+
25+
return () => {
26+
document.removeEventListener("keydown", handleKeyDown);
27+
};
28+
});
29+
30+
return (
31+
<input
32+
ref={inputRef}
33+
type="text"
34+
className="fixed -top-95 opacity-0"
35+
value={text.text}
36+
onChange={(e) => {
37+
if (allow) {
38+
setText((text) => {
39+
return { ...text, text: e.target.value };
40+
});
41+
}
42+
}}
43+
/>
44+
);
45+
}

apps/docs/app/hooks/hooks.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from "react";
2+
3+
export const useClickInside = (
4+
ref: React.RefObject<HTMLElement>,
5+
callback: () => void
6+
) => {
7+
const handleClick = (e: MouseEvent) => {
8+
if (ref.current && ref.current.contains(e.target as Node)) {
9+
callback();
10+
}
11+
};
12+
React.useEffect(() => {
13+
document.addEventListener("click", handleClick);
14+
return () => {
15+
document.removeEventListener("click", handleClick);
16+
};
17+
});
18+
};
19+
20+
export const useClickOutside = (
21+
ref: React.RefObject<HTMLElement>,
22+
callback: () => void
23+
) => {
24+
const handleClick = (e: MouseEvent) => {
25+
if (ref.current && !ref.current.contains(e.target as Node)) {
26+
callback();
27+
}
28+
};
29+
React.useEffect(() => {
30+
document.addEventListener("click", handleClick);
31+
return () => {
32+
document.removeEventListener("click", handleClick);
33+
};
34+
});
35+
};

apps/docs/app/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export default function RootLayout({
1616
}>) {
1717
return (
1818
<html lang="en">
19-
<body className={`bg-black text-white font-mono container`}>
19+
<body
20+
className={`bg-black text-white font-mono h-screen overscroll-none`}>
2021
{children}
2122
</body>
2223
</html>

apps/docs/app/page.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import DiffText from "./components/DiffText";
2+
import HiddenInput from "./components/HiddenInput";
3+
14
export default function Home() {
25
return (
3-
<main className="flex min-h-screen flex-col items-center justify-between p-24">
4-
Hello World
5-
</main>
6+
<div className="h-full leading-relaxed flex flex-col justify-center items-center">
7+
<DiffText></DiffText>
8+
<HiddenInput></HiddenInput>
9+
</div>
610
);
711
}

apps/docs/data/state.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { atom } from "jotai";
2+
import { diff } from "typer-diff";
3+
4+
let text =
5+
"typer-diff is a library to diff two strings, useful for showing for typing games (like monkeytype) or for showing differences between two strings. It is a simple library that is easy to use and has no dependencies. It is also very fast and lightweight.";
6+
export let textAtom = atom({
7+
original: text,
8+
text: "",
9+
});
10+
11+
export const diffText = atom((get) => {
12+
const text = get(textAtom);
13+
return diff(text.original, text.text);
14+
});
15+
16+
export const allowModifications = atom((get) => {
17+
const diff = get(diffText);
18+
return !diff.end;
19+
});
20+
21+
export const isTyping = atom(true);

apps/docs/docs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
./apps/docs

apps/docs/lib/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { type ClassValue, clsx } from "clsx";
2+
import { twMerge } from "tailwind-merge";
3+
4+
export function cn(...inputs: ClassValue[]) {
5+
return twMerge(clsx(inputs));
6+
}

apps/docs/next.config.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/** @type {import('next').NextConfig} */
2-
const nextConfig = {};
2+
const nextConfig = {
3+
output: "export",
4+
};
35

46
export default nextConfig;

0 commit comments

Comments
 (0)