chore: upgrade to react 19 beta and next 14 canary

This commit is contained in:
2024-05-19 14:53:00 +02:00
parent fe429295ef
commit 63e0be09e6
65 changed files with 2626 additions and 1898 deletions

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -12,6 +13,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useState } from "react";
import { useState } from "react";
import yaml from "js-yaml";
import { toolGroups } from "@/config/tools";
@@ -28,7 +28,7 @@ export default function Page() {
yaml: "foo: bar",
});
const setFormByJson = useCallback((text: string) => {
const setFormByJson = (text: string) => {
setForm(prev => ({
...prev,
json: text,
@@ -36,9 +36,9 @@ export default function Page() {
.map(x => yaml.dump(x, { indent: prev.indentation.length, quotingType: '"' }))
.unwrapOr(""),
}));
}, []);
};
const setFormByYaml = useCallback((text: string) => {
const setFormByYaml = (text: string) => {
setForm(prev => ({
...prev,
json: safeYamlParse(text)
@@ -46,15 +46,15 @@ export default function Page() {
.unwrapOr(""),
yaml: text,
}));
}, []);
};
const clearBoth = useCallback(() => {
const clearBoth = () => {
setForm(prev => ({
...prev,
json: "",
yaml: "",
}));
}, []);
};
const onIndentationChange: Select.Props["onValueChange"] = value => {
const jsonYaml = safeJsonParse(form.json)
@@ -73,7 +73,11 @@ export default function Page() {
});
};
// @ts-expect-error react 19 beta error
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const onJsonChange: EditorProps["onChange"] = value => setFormByJson(value ?? "");
// @ts-expect-error react 19 beta error
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const onYamlChange: EditorProps["onChange"] = value => setFormByYaml(value ?? "");
const indentationConfig = (
@@ -133,9 +137,11 @@ export default function Page() {
</PageSection>
<div className="flex flex-1 flex-col gap-x-4 gap-y-5 lg:flex-row">
<PageSection className="min-h-[200px] flex-1" title="Json" control={jsonControl}>
{/* @ts-expect-error react 19 beta error */}
<Editor language="json" value={form.json} onChange={onJsonChange} />
</PageSection>
<PageSection className="min-h-[200px] flex-1" title="Yaml" control={yamlControl}>
{/* @ts-expect-error react 19 beta error */}
<Editor language="yaml" value={form.yaml} onChange={onYamlChange} />
</PageSection>
</div>

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -11,6 +12,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -12,6 +13,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useState } from "react";
import { useState } from "react";
import { toolGroups } from "@/config/tools";
import * as baselib from "@/lib/base";
@@ -48,10 +48,10 @@ export default function Page() {
}
};
const trySetDec = useCallback((value: string) => trySetInt(10)(value), []);
const trySetHex = useCallback((value: string) => trySetInt(16)(value), []);
const trySetOct = useCallback((value: string) => trySetInt(8)(value), []);
const trySetBin = useCallback((value: string) => trySetInt(2)(value), []);
const trySetDec = (value: string) => trySetInt(10)(value);
const trySetHex = (value: string) => trySetInt(16)(value);
const trySetOct = (value: string) => trySetInt(8)(value);
const trySetBin = (value: string) => trySetInt(2)(value);
const onDecChange: InputProps["onChange"] = e => trySetDec(e.currentTarget.value);
const onHexChange: InputProps["onChange"] = e => trySetHex(e.currentTarget.value);

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -12,6 +13,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useState } from "react";
import { useState } from "react";
import { decode, encode, isValid } from "js-base64";
import { toolGroups } from "@/config/tools";
@@ -16,28 +16,28 @@ export default function Page() {
encoded: "8J+YgPCfmILwn6Sj",
});
const setFormByDecoded = useCallback((text: string) => {
const setFormByDecoded = (text: string) => {
setForm({
decoded: text,
encoded: encode(text),
});
}, []);
};
const setFormByEncoded = useCallback((text: string) => {
const setFormByEncoded = (text: string) => {
const newDecoded = decode(text);
setForm({
decoded: isValid(text) && !newDecoded.includes("<22>") ? newDecoded : "",
encoded: text,
});
}, []);
};
const clearBoth = useCallback(() => {
const clearBoth = () => {
setForm({
decoded: "",
encoded: "",
});
}, []);
};
const onDecodedChange: TextareaProps["onChange"] = e => setFormByDecoded(e.currentTarget.value);
const onEncodedChange: TextareaProps["onChange"] = e => setFormByEncoded(e.currentTarget.value);

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -12,6 +13,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useState } from "react";
import { useState } from "react";
import { escape, unescape } from "html-escaper";
import { toolGroups } from "@/config/tools";
@@ -16,26 +16,26 @@ export default function Page() {
encoded: "&gt; It&#39;s &quot;HTML escaping&quot;.",
});
const setFormByDecoded = useCallback((text: string) => {
const setFormByDecoded = (text: string) => {
setForm({
decoded: text,
encoded: escape(text),
});
}, []);
};
const setFormByEncoded = useCallback((text: string) => {
const setFormByEncoded = (text: string) => {
setForm({
decoded: unescape(text),
encoded: text,
});
}, []);
};
const clearBoth = useCallback(() => {
const clearBoth = () => {
setForm({
decoded: "",
encoded: "",
});
}, []);
};
const onDecodedChange: TextareaProps["onChange"] = e => setFormByDecoded(e.currentTarget.value);
const onEncodedChange: TextareaProps["onChange"] = e => setFormByEncoded(e.currentTarget.value);

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -12,6 +13,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useState } from "react";
import { useState } from "react";
import { toolGroups } from "@/config/tools";
import { decode } from "@/lib/jwt";
@@ -20,7 +20,7 @@ export default function Page() {
const header = h.map(x => JSON.stringify(x, null, 2)).unwrapOr("");
const payload = p.map(x => JSON.stringify(x, null, 2)).unwrapOr("");
const clearJwt = useCallback(() => setJwt(""), []);
const clearJwt = () => setJwt("");
const onJwtChange: TextareaProps["onChange"] = e => setJwt(e.currentTarget.value);
@@ -49,9 +49,11 @@ export default function Page() {
</PageSection>
<div className="flex flex-col gap-3">
<PageSection title="Header" control={heaederControl}>
{/* @ts-expect-error react 19 beta error */}
<Editor height={180} language="json" value={header} options={{ readOnly: true }} />
</PageSection>
<PageSection title="Payload" control={payloadControl}>
{/* @ts-expect-error react 19 beta error */}
<Editor height={180} language="json" value={payload} options={{ readOnly: true }} />
</PageSection>
</div>

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -11,6 +12,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -12,6 +13,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useState } from "react";
import { useState } from "react";
import { toolGroups } from "@/config/tools";
import { safeDecodeURIComponent, safeEncodeURIComponent } from "@/lib/uri";
@@ -16,26 +16,26 @@ export default function Page() {
encoded: "%3E%20It's%20%22URL%20encoding%22%3F",
});
const setFormByDecoded = useCallback((text: string) => {
const setFormByDecoded = (text: string) => {
setForm({
decoded: text,
encoded: safeEncodeURIComponent(text).unwrapOr(""),
});
}, []);
};
const setFormByEncoded = useCallback((text: string) => {
const setFormByEncoded = (text: string) => {
setForm({
decoded: safeDecodeURIComponent(text).unwrapOr(""),
encoded: text,
});
}, []);
};
const clearBoth = useCallback(() => {
const clearBoth = () => {
setForm({
decoded: "",
encoded: "",
});
}, []);
};
const onDecodedChange: TextareaProps["onChange"] = e => setFormByDecoded(e.currentTarget.value);
const onEncodedChange: TextareaProps["onChange"] = e => setFormByEncoded(e.currentTarget.value);

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -12,6 +13,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useState } from "react";
import { useState } from "react";
import { toolGroups } from "@/config/tools";
import { safeJsonParse } from "@/lib/json";
@@ -21,15 +21,26 @@ const indentations = {
tab: "\t",
};
const INDENTATION = {
TWO_SPACES: "two",
FOUR_SPACES: "four",
TAB: "tab",
ZERO: "zero",
} as const;
type Indentation = (typeof INDENTATION)[keyof typeof INDENTATION];
export default function Page() {
const [indentation, setIndentation] = useState(indentations.two);
const [indentation, setIndentation] = useState<Indentation>(INDENTATION.TWO_SPACES);
const [input, setInput] = useState('{\n"foo":"bar"\n}');
const parsed = safeJsonParse(input);
const output = parsed.map(x => JSON.stringify(x, null, indentation)).unwrapOr("");
const output = parsed.map(x => JSON.stringify(x, null, indentations[indentation])).unwrapOr("");
const clearInput = useCallback(() => setInput(""), []);
const clearInput = () => setInput("");
// @ts-expect-error react 19 beta error
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const onJsonChange: EditorProps["onChange"] = value => setInput(value ?? "");
const indentationConfig = (
@@ -37,7 +48,10 @@ export default function Page() {
icon={<icons.Space size={24} className="-translate-y-1.5" />}
title="Indentation"
control={
<Select.Root value={indentation} onValueChange={setIndentation}>
<Select.Root
value={indentation}
onValueChange={value => setIndentation(value as Indentation)}
>
<Select.Trigger
className="w-28"
aria-label="toggle open/close state of indentation selection"
@@ -45,10 +59,10 @@ export default function Page() {
<Select.Value placeholder={indentation} />
</Select.Trigger>
<Select.Content>
<Select.Item value={indentations.two}>2 spaces</Select.Item>
<Select.Item value={indentations.four}>4 spaces</Select.Item>
<Select.Item value={indentations.tab}>1 tab</Select.Item>
<Select.Item value={indentations.zero}>minified</Select.Item>
<Select.Item value={INDENTATION.TWO_SPACES}>2 spaces</Select.Item>
<Select.Item value={INDENTATION.FOUR_SPACES}>4 spaces</Select.Item>
<Select.Item value={INDENTATION.TAB}>1 tab</Select.Item>
<Select.Item value={INDENTATION.ZERO}>minified</Select.Item>
</Select.Content>
</Select.Root>
}
@@ -75,9 +89,11 @@ export default function Page() {
</PageSection>
<div className="flex flex-1 flex-col gap-x-4 gap-y-5 lg:flex-row">
<PageSection className="min-h-[200px] flex-1" title="Input" control={inputControl}>
{/* @ts-expect-error react 19 beta error */}
<Editor language="json" value={input} onChange={onJsonChange} />
</PageSection>
<PageSection className="min-h-[200px] flex-1" title="Output" control={outputControl}>
{/* @ts-expect-error react 19 beta error */}
<Editor language="json" value={output} options={{ readOnly: true }} />
</PageSection>
</div>

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -11,6 +12,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -12,6 +13,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useState } from "react";
import { useState } from "react";
import createHash from "create-hash";
import { toolGroups } from "@/config/tools";
@@ -29,7 +29,7 @@ export default function Page() {
const sha256 = uppercase ? newSha256.toUpperCase() : newSha256;
const sha512 = uppercase ? newSha512.toUpperCase() : newSha512;
const clearInput = useCallback(() => setInput(""), []);
const clearInput = () => setInput("");
const onInputChange: TextareaProps["onChange"] = e => setInput(e.currentTarget.value);

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -11,6 +12,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -12,6 +13,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useState } from "react";
import { useState } from "react";
import { range } from "fp-ts/NonEmptyArray";
import { toolGroups } from "@/config/tools";
@@ -41,21 +41,21 @@ export default function Page() {
const uuidsString = uuids.join("\n");
const clearUuids = useCallback(() => setUuids([]), []);
const clearUuids = () => setUuids([]);
const onUuidVersionChange: NonNullable<Select.Props["onValueChange"]> = useCallback(value => {
const onUuidVersionChange: NonNullable<Select.Props["onValueChange"]> = value => {
if (isUuidVersion(value)) {
setUuidVersion(value);
}
}, []);
};
const onGeneratesChange: NonNullable<InputProps["onChange"]> = useCallback(e => {
const onGeneratesChange: NonNullable<InputProps["onChange"]> = e => {
const newGenerates = Number(e.currentTarget.value);
if (newGenerates >= 1 && newGenerates <= 1000) {
setGenerates(newGenerates);
}
}, []);
};
const onGenerateClick = () => {
const newUuids = range(1, generates).map(_ => uuid(uuidVersion, hyphens, uppercase));
@@ -136,7 +136,7 @@ export default function Page() {
</div>
</PageSection>
<PageSection className="-mt-3" title="UUID(s)" control={uuidsControl}>
<Textarea {...{ ref }} value={uuidsString} rows={10} readOnly />
<Textarea ref={ref} value={uuidsString} rows={10} readOnly />
</PageSection>
</PageRootSection>
);

View File

@@ -1,20 +1,13 @@
import "@/styles/globals.css";
import { Metadata } from "next";
import { CookiesProvider } from "next-client-cookies/server";
import { siteConfig } from "@/config/site";
import { fontMono, fontSans } from "@/lib/fonts";
import { cn } from "@/lib/style";
import { ClientLayout } from "@/components/client-layout";
import { Providers } from "@/components/ui/providers";
import { Sidebar } from "@/components/sidebar";
import { SiteHeader } from "@/components/site-header";
import { TailwindIndicator } from "@/components/tailwind-indicator";
import { ThemeProvider } from "@/components/theme-provider";
import { SearchTextProvider } from "@/contexts/search-text";
import { SidebarProvider } from "@/contexts/sidebar";
export const dynamic = "force-dynamic";
export const metadata: Metadata = {
metadataBase: new URL(siteConfig.url),
@@ -59,22 +52,11 @@ export default function RootLayout({ children }: RootLayoutProps) {
fontMono.variable
)}
>
<CookiesProvider>
<ThemeProvider attribute="class" defaultTheme="system" disableTransitionOnChange>
<SearchTextProvider>
<SidebarProvider>
<ClientLayout>
<SiteHeader className="col-span-full" />
<Sidebar />
<main className="overflow-y-auto rounded-tl-md border bg-page p-12">
{children}
</main>
</ClientLayout>
<TailwindIndicator />
</SidebarProvider>
</SearchTextProvider>
</ThemeProvider>
</CookiesProvider>
<Providers attribute="class" defaultTheme="system" disableTransitionOnChange>
<SiteHeader className="col-span-full" />
<Sidebar />
<main className="overflow-y-auto rounded-tl-md border bg-page p-12">{children}</main>
</Providers>
</body>
</html>
);

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
// TODO: use query param
@@ -10,6 +11,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { singleTools } from "@/config/tools";
@@ -11,6 +12,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useMemo, useState } from "react";
import { useState } from "react";
import { Panel, PanelGroup } from "react-resizable-panels";
import { PERSISTENCE_KEY } from "@/config/persistence-keys";
@@ -27,71 +27,56 @@ export default function Page() {
const [input2, setInput2] = useState<string | undefined>("Hello, World!");
const [diffFullHeight, setDiffFullHeight] = useState(false);
const [inlineMode, setInlineMode] = useState(false);
const diffPanelMaxSize = useMemo(
() => (diffFullHeight ? PANEL_FULL_SIZE : VERTICAL_PANEL_MAX_SIZE),
[diffFullHeight]
const diffPanelMaxSize = diffFullHeight ? PANEL_FULL_SIZE : VERTICAL_PANEL_MAX_SIZE;
const clearInput1 = () => setInput1("");
const clearInput2 = () => setInput2("");
const toggleFullHeight = () => setDiffFullHeight(prev => !prev);
const inlineModeConfig = (
<Configuration
icon={<icons.Rows size={24} />}
title="Inline mode"
control={
<LabeledSwitch
id="uppercase-switch"
label={inlineMode ? "On" : "Off"}
checked={inlineMode}
onCheckedChange={setInlineMode}
aria-label="toggle whether to show diff in inline mode"
/>
}
/>
);
const input1Control = (
<ControlMenu
list={[
<Button.Paste onClipboardRead={setInput1} />,
<Button.File onFileRead={setInput1} iconOnly aria-label="load a file with old text" />,
<Button.Clear onClick={clearInput1} iconOnly aria-label="clear old text input" />,
]}
/>
);
const clearInput1 = useCallback(() => setInput1(""), [setInput1]);
const clearInput2 = useCallback(() => setInput2(""), [setInput2]);
const toggleFullHeight = useCallback(() => setDiffFullHeight(prev => !prev), [setDiffFullHeight]);
const inlineModeConfig = useMemo(
() => (
<Configuration
icon={<icons.Rows size={24} />}
title="Inline mode"
control={
<LabeledSwitch
id="uppercase-switch"
label={inlineMode ? "On" : "Off"}
checked={inlineMode}
onCheckedChange={setInlineMode}
aria-label="toggle whether to show diff in inline mode"
/>
}
/>
),
[inlineMode, setInlineMode]
);
const input1Control = useMemo(
() => (
<ControlMenu
list={[
<Button.Paste onClipboardRead={setInput1} />,
<Button.File onFileRead={setInput1} iconOnly aria-label="load a file with old text" />,
<Button.Clear onClick={clearInput1} iconOnly aria-label="clear old text input" />,
]}
/>
),
[setInput1, clearInput1]
const input2Control = (
<ControlMenu
list={[
<Button.Paste onClipboardRead={setInput2} />,
<Button.File onFileRead={setInput2} iconOnly aria-label="load a file with new text" />,
<Button.Clear onClick={clearInput2} iconOnly aria-label="clear new text input" />,
]}
/>
);
const input2Control = useMemo(
() => (
<ControlMenu
list={[
<Button.Paste onClipboardRead={setInput2} />,
<Button.File onFileRead={setInput2} iconOnly aria-label="load a file with new text" />,
<Button.Clear onClick={clearInput2} iconOnly aria-label="clear new text input" />,
]}
/>
),
[setInput2, clearInput2]
const diffControl = (
<ControlMenu
list={[
<Button.ToggleFullSize iconOnly onClick={toggleFullHeight} expanded={diffFullHeight} />,
]}
/>
);
const diffControl = useMemo(
() => (
<ControlMenu
list={[
<Button.ToggleFullSize iconOnly onClick={toggleFullHeight} expanded={diffFullHeight} />,
]}
/>
),
[diffFullHeight, toggleFullHeight]
);
const hiddenInFullHeightMode = useMemo(() => (diffFullHeight ? "hidden" : ""), [diffFullHeight]);
const hiddenInFullHeightMode = diffFullHeight ? "hidden" : "";
return (
<PageRootSection className="h-full" title={toolGroups.text.tools.diff.longTitle}>
@@ -106,6 +91,7 @@ export default function Page() {
>
<Panel maxSize={HORIZONTAL_PANEL_MAX_SIZE}>
<PageSection className="h-full" title="Old text" control={input1Control}>
{/* @ts-expect-error react 19 beta error */}
<Editor value={input1} onChange={setInput1} />
</PageSection>
</Panel>
@@ -113,6 +99,7 @@ export default function Page() {
<Panel maxSize={HORIZONTAL_PANEL_MAX_SIZE}>
<PageSection className="h-full" title="New text" control={input2Control}>
{/* @ts-expect-error react 19 beta error */}
<Editor value={input2} onChange={setInput2} />
</PageSection>
</Panel>
@@ -121,9 +108,14 @@ export default function Page() {
<PanelResizeHandle direction="horizontal" className={hiddenInFullHeightMode} />
<Panel maxSize={diffPanelMaxSize}>
<PageSection className="h-full" title="Difference" control={diffControl}>
{/* @ts-expect-error react 19 beta error */}
<DiffEditor
// @ts-expect-error react 19 beta error
original={input1}
// @ts-expect-error react 19 beta error
modified={input2}
// @ts-expect-error react 19 beta error
options={{
readOnly: true,
renderSideBySide: !inlineMode,

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -12,6 +13,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useMemo, useState } from "react";
import { useState } from "react";
import { toolGroups } from "@/config/tools";
import { noOp } from "@/lib/base";
@@ -27,17 +27,14 @@ export default function Page() {
const output = transformText(input, mode);
const stats = useMemo(
() => ({
characters: countCharacters(input),
words: countWords(input),
lines: countLines(input),
bytes: countBytes(input),
}),
[input]
);
const stats = {
characters: countCharacters(input),
words: countWords(input),
lines: countLines(input),
bytes: countBytes(input),
};
const clearInput = useCallback(() => setInput(""), []);
const clearInput = () => setInput("");
const onInputChange: TextareaProps["onChange"] = e => setInput(e.currentTarget.value);
const onModeChange = (value: string) => {

View File

@@ -1,3 +1,4 @@
import { PropsWithChildren } from "react";
import { Metadata } from "next";
import { toolGroups } from "@/config/tools";
@@ -11,6 +12,6 @@ export const metadata: Metadata = {
},
};
export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return children;
}