mirror of
https://github.com/ershisan99/DevToysWeb.git
synced 2025-12-16 04:59:24 +00:00
chore: upgrade to react 19 beta and next 14 canary
This commit is contained in:
@@ -11,8 +11,9 @@
|
||||
"next/core-web-vitals",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": ["tailwindcss"],
|
||||
"plugins": ["tailwindcss", "eslint-plugin-react-compiler"],
|
||||
"rules": {
|
||||
"react-compiler/react-compiler": "error",
|
||||
"@next/next/no-html-link-for-pages": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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: "> It's "HTML escaping".",
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { memo } from "react";
|
||||
import equal from "react-fast-compare";
|
||||
|
||||
import * as icons from "@/components/icons";
|
||||
|
||||
import { Base, BaseProps } from "./base";
|
||||
|
||||
export type ClearProps = Omit<BaseProps, "icon" | "labelText">;
|
||||
|
||||
function RawClear({ iconOnly, ...props }: ClearProps) {
|
||||
export function Clear({ iconOnly, ...props }: ClearProps) {
|
||||
return <Base {...props} icon={<icons.X size={16} />} {...{ iconOnly }} labelText="Clear" />;
|
||||
}
|
||||
|
||||
export const Clear = memo(RawClear, equal);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { memo, useCallback } from "react";
|
||||
import equal from "react-fast-compare";
|
||||
|
||||
import * as icons from "@/components/icons";
|
||||
|
||||
import { Base, BaseProps } from "./base";
|
||||
@@ -9,19 +6,17 @@ export type CopyProps = Omit<BaseProps, "icon" | "labelText" | "onClick"> & {
|
||||
text: string;
|
||||
};
|
||||
|
||||
function RawButton({ text, iconOnly, ...props }: CopyProps) {
|
||||
const onClick: BaseProps["onClick"] = useCallback(() => {
|
||||
export function Copy({ text, iconOnly, ...props }: CopyProps) {
|
||||
const onClick: BaseProps["onClick"] = () => {
|
||||
navigator.clipboard.writeText(text).catch(e => {
|
||||
if (e instanceof Error) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert(e.message);
|
||||
}
|
||||
});
|
||||
}, [text]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Base {...props} icon={<icons.Copy size={16} />} {...{ iconOnly, onClick }} labelText="Copy" />
|
||||
);
|
||||
}
|
||||
|
||||
export const Copy = memo(RawButton, equal);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { memo, useCallback, useRef } from "react";
|
||||
import equal from "react-fast-compare";
|
||||
import { useRef } from "react";
|
||||
|
||||
import * as icons from "@/components/icons";
|
||||
|
||||
@@ -13,42 +12,39 @@ export type FileProps = Pick<InputProps, "accept"> &
|
||||
onFileRead: (text: string) => void;
|
||||
};
|
||||
|
||||
export function RawFile({ accept, iconOnly, maxFileSizeMb = 20, onFileRead, ...props }: FileProps) {
|
||||
export function File({ accept, iconOnly, maxFileSizeMb = 20, onFileRead, ...props }: FileProps) {
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
|
||||
const onClick = () => ref.current?.click();
|
||||
|
||||
const onChange: NonNullable<InputProps["onChange"]> = useCallback(
|
||||
e => {
|
||||
const file = Array.from(e.currentTarget.files ?? []).at(0);
|
||||
const onChange: NonNullable<InputProps["onChange"]> = e => {
|
||||
const file = Array.from(e.currentTarget.files ?? []).at(0);
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: reject if the file is unsupported
|
||||
|
||||
if (file.size > maxFileSizeMb * 2 ** 20) {
|
||||
// eslint-disable-next-line no-alert
|
||||
return alert(`The file is too big. Up to ${maxFileSizeMb}MiB.`);
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = ({ target }) => {
|
||||
if (typeof target?.result === "string") {
|
||||
onFileRead(target?.result);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: reject if the file is unsupported
|
||||
reader.readAsText(file);
|
||||
|
||||
if (file.size > maxFileSizeMb * 2 ** 20) {
|
||||
// eslint-disable-next-line no-alert
|
||||
return alert(`The file is too big. Up to ${maxFileSizeMb}MiB.`);
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = ({ target }) => {
|
||||
if (typeof target?.result === "string") {
|
||||
onFileRead(target?.result);
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
|
||||
// clear selected file to accept the same file again
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
e.currentTarget.value = "";
|
||||
},
|
||||
[maxFileSizeMb, onFileRead]
|
||||
);
|
||||
// clear selected file to accept the same file again
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
e.currentTarget.value = "";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -62,5 +58,3 @@ export function RawFile({ accept, iconOnly, maxFileSizeMb = 20, onFileRead, ...p
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const File = memo(RawFile, equal);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { memo, useCallback } from "react";
|
||||
import equal from "react-fast-compare";
|
||||
|
||||
import * as icons from "@/components/icons";
|
||||
|
||||
import { Base, BaseProps } from "./base";
|
||||
@@ -9,8 +6,8 @@ export type PasteProps = Omit<BaseProps, "icon" | "labelText" | "onClick"> & {
|
||||
onClipboardRead: (text: string) => void;
|
||||
};
|
||||
|
||||
export function RawPaste({ iconOnly, onClipboardRead, ...props }: PasteProps) {
|
||||
const onClick: BaseProps["onClick"] = useCallback(() => {
|
||||
export function Paste({ iconOnly, onClipboardRead, ...props }: PasteProps) {
|
||||
const onClick: BaseProps["onClick"] = () => {
|
||||
navigator.clipboard
|
||||
.readText()
|
||||
.then(onClipboardRead)
|
||||
@@ -20,7 +17,7 @@ export function RawPaste({ iconOnly, onClipboardRead, ...props }: PasteProps) {
|
||||
alert(e.message);
|
||||
}
|
||||
});
|
||||
}, [onClipboardRead]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Base
|
||||
@@ -31,5 +28,3 @@ export function RawPaste({ iconOnly, onClipboardRead, ...props }: PasteProps) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const Paste = memo(RawPaste, equal);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { memo } from "react";
|
||||
import equal from "react-fast-compare";
|
||||
|
||||
import * as Icon from "@/components/icons";
|
||||
|
||||
import { Base, BaseProps } from "./base";
|
||||
@@ -9,7 +6,7 @@ export type ToggleFullSizeProps = Omit<BaseProps, "icon" | "labelText"> & {
|
||||
expanded: boolean;
|
||||
};
|
||||
|
||||
function RawToggleFullSize({ expanded, ...props }: ToggleFullSizeProps) {
|
||||
export function ToggleFullSize({ expanded, ...props }: ToggleFullSizeProps) {
|
||||
return (
|
||||
<Base
|
||||
{...props}
|
||||
@@ -18,5 +15,3 @@ function RawToggleFullSize({ expanded, ...props }: ToggleFullSizeProps) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const ToggleFullSize = memo(RawToggleFullSize, equal);
|
||||
|
||||
@@ -3,19 +3,17 @@
|
||||
import React, { PropsWithChildren } from "react";
|
||||
|
||||
import { cn } from "@/lib/style";
|
||||
import { SidebarStatus, useSidebarStatus } from "@/contexts/sidebar";
|
||||
import { useSidebar } from "@/hooks/use-sidebar";
|
||||
|
||||
export function ClientLayout({ children }: PropsWithChildren) {
|
||||
const sidebarStatus = useSidebarStatus();
|
||||
const isOpen = sidebarStatus === SidebarStatus.Open;
|
||||
const isClosed = sidebarStatus === SidebarStatus.Closed;
|
||||
const { isSidebarOpen, isLoading } = useSidebar();
|
||||
const isOpen = isSidebarOpen && !isLoading;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"grid h-full grid-rows-[3.5rem_1fr] transition-all",
|
||||
isOpen && "grid-cols-[18rem_1fr]",
|
||||
isClosed && "grid-cols-[0_1fr]"
|
||||
isOpen ? "grid-cols-[18rem_1fr]" : "grid-cols-[0_1fr]"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { memo } from "react";
|
||||
import equal from "react-fast-compare";
|
||||
|
||||
type Props = {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
@@ -8,7 +5,7 @@ type Props = {
|
||||
control: React.ReactNode;
|
||||
};
|
||||
|
||||
function RawConfiguration({ icon, title, description, control }: Props) {
|
||||
export function Configuration({ icon, title, description, control }: Props) {
|
||||
return (
|
||||
<div className="flex h-16 items-center gap-6 rounded border bg-configuration px-4">
|
||||
{icon}
|
||||
@@ -24,5 +21,3 @@ function RawConfiguration({ icon, title, description, control }: Props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Configuration = memo(RawConfiguration, equal);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { memo } from "react";
|
||||
import equal from "react-fast-compare";
|
||||
|
||||
type Props = {
|
||||
list: React.ReactNode[];
|
||||
};
|
||||
|
||||
function RawConfigurations({ list }: Props) {
|
||||
export function Configurations({ list }: Props) {
|
||||
return (
|
||||
<ul className="flex flex-col gap-1.5">
|
||||
{list.map((config, i) => (
|
||||
@@ -16,5 +13,3 @@ function RawConfigurations({ list }: Props) {
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export const Configurations = memo(RawConfigurations, equal);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { memo } from "react";
|
||||
import equal from "react-fast-compare";
|
||||
|
||||
type Props = {
|
||||
list: React.ReactNode[];
|
||||
};
|
||||
|
||||
function RawControlMenu({ list }: Props) {
|
||||
export function ControlMenu({ list }: Props) {
|
||||
return (
|
||||
<menu className="flex gap-2">
|
||||
{list.map((control, i) => (
|
||||
@@ -16,5 +13,3 @@ function RawControlMenu({ list }: Props) {
|
||||
</menu>
|
||||
);
|
||||
}
|
||||
|
||||
export const ControlMenu = memo(RawControlMenu, equal);
|
||||
|
||||
@@ -1,44 +1,42 @@
|
||||
import { memo, MemoExoticComponent } from "react";
|
||||
import * as icons from "lucide-react";
|
||||
import equal from "react-fast-compare";
|
||||
|
||||
export type Icon = icons.LucideIcon | MemoExoticComponent<icons.LucideIcon>;
|
||||
|
||||
export const AlignLeft = memo(icons.AlignLeft, equal);
|
||||
export const ArrowRightLeft = memo(icons.ArrowRightLeft, equal);
|
||||
export const Binary = memo(icons.Binary, equal);
|
||||
export const Braces = memo(icons.Braces, equal);
|
||||
export const CaseSensitive = memo(icons.CaseSensitive, equal);
|
||||
export const Check = memo(icons.Check, equal);
|
||||
export const ChevronDown = memo(icons.ChevronDown, equal);
|
||||
export const Clipboard = memo(icons.Clipboard, equal);
|
||||
export const Code = memo(icons.Code2, equal);
|
||||
export const Copy = memo(icons.Copy, equal);
|
||||
export const Diff = memo(icons.Diff, equal);
|
||||
export const Equal = memo(icons.Equal, equal);
|
||||
export const File = memo(icons.FileIcon, equal);
|
||||
export const Fingerprint = memo(icons.Fingerprint, equal);
|
||||
export const GripHorizontal = memo(icons.GripHorizontal, equal);
|
||||
export const GripVertical = memo(icons.GripVertical, equal);
|
||||
export const Hash = memo(icons.Hash, equal);
|
||||
export const Home = memo(icons.Home, equal);
|
||||
export const Key = memo(icons.Key, equal);
|
||||
export const Link = memo(icons.Link2, equal);
|
||||
export const Maximize = memo(icons.Maximize2, equal);
|
||||
export const Minimize = memo(icons.Minimize2, equal);
|
||||
export const Menu = memo(icons.Menu, equal);
|
||||
export const PackagePlus = memo(icons.PackagePlus, equal);
|
||||
export const Paintbrush = memo(icons.Paintbrush2, equal);
|
||||
export const Rows = memo(icons.Rows, equal);
|
||||
export const Search = memo(icons.Search, equal);
|
||||
export const Settings = memo(icons.Settings, equal);
|
||||
export const Settings2 = memo(icons.Settings2, equal);
|
||||
export const Space = memo(icons.Space, equal);
|
||||
export const Sun = memo(icons.SunMedium, equal);
|
||||
export const Type = memo(icons.Type, equal);
|
||||
export const Minus = memo(icons.Minus, equal);
|
||||
export const Moon = memo(icons.Moon, equal);
|
||||
export const X = memo(icons.X, equal);
|
||||
export type Icon = icons.LucideIcon;
|
||||
export const {
|
||||
AlignLeft,
|
||||
ArrowRightLeft,
|
||||
Binary,
|
||||
Braces,
|
||||
CaseSensitive,
|
||||
ChevronDown,
|
||||
Clipboard,
|
||||
Copy,
|
||||
Diff,
|
||||
Equal,
|
||||
Fingerprint,
|
||||
GripHorizontal,
|
||||
GripVertical,
|
||||
Hash,
|
||||
Home,
|
||||
Key,
|
||||
Menu,
|
||||
PackagePlus,
|
||||
Rows,
|
||||
Search,
|
||||
Settings,
|
||||
Settings2,
|
||||
Space,
|
||||
Type,
|
||||
Minus,
|
||||
Moon,
|
||||
X,
|
||||
} = icons;
|
||||
export const File = icons.FileIcon;
|
||||
export const Code = icons.Code2;
|
||||
export const Link = icons.Link2;
|
||||
export const Maximize = icons.Maximize2;
|
||||
export const Minimize = icons.Minimize2;
|
||||
export const Paintbrush = icons.Paintbrush2;
|
||||
export const Sun = icons.SunMedium;
|
||||
export const GitHub = (props: icons.LucideProps) => (
|
||||
<svg viewBox="0 0 438.549 438.549" {...props}>
|
||||
<path
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import { memo } from "react";
|
||||
|
||||
function RawIndicator() {
|
||||
export function Indicator() {
|
||||
return <span className="inline-block h-[18px] w-[3px] rounded bg-indicator" />;
|
||||
}
|
||||
|
||||
export const Indicator = memo(RawIndicator);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ComponentPropsWithoutRef, useMemo } from "react";
|
||||
import { ComponentPropsWithoutRef } from "react";
|
||||
import { PanelResizeHandle as PanelResizeHandlePrimitive } from "react-resizable-panels";
|
||||
|
||||
import { cn } from "@/lib/style";
|
||||
@@ -12,19 +12,15 @@ export const PanelResizeHandle = ({ direction = "vertical", className, ...props
|
||||
const isVertical = direction === "vertical";
|
||||
const isHorizontal = direction === "horizontal";
|
||||
|
||||
const classNames = useMemo(
|
||||
() =>
|
||||
cn(
|
||||
isVertical && "w-4",
|
||||
isHorizontal && "h-4",
|
||||
"flex items-center justify-center",
|
||||
"data-[resize-handle-state=drag]:bg-neutral-200",
|
||||
"dark:data-[resize-handle-state=drag]:bg-neutral-600",
|
||||
"data-[resize-handle-state=hover]:bg-neutral-300",
|
||||
"dark:data-[resize-handle-state=hover]:bg-neutral-700",
|
||||
className
|
||||
),
|
||||
[isVertical, isHorizontal, className]
|
||||
const classNames = cn(
|
||||
isVertical && "w-4",
|
||||
isHorizontal && "h-4",
|
||||
"flex items-center justify-center",
|
||||
"data-[resize-handle-state=drag]:bg-neutral-200",
|
||||
"dark:data-[resize-handle-state=drag]:bg-neutral-600",
|
||||
"data-[resize-handle-state=hover]:bg-neutral-300",
|
||||
"dark:data-[resize-handle-state=hover]:bg-neutral-700",
|
||||
className
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useRef } from "react";
|
||||
import { useRef } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import * as Accordion from "@radix-ui/react-accordion";
|
||||
|
||||
@@ -18,7 +18,7 @@ export function ToolGroup({ Icon, title, href, tools, isOpend }: Props) {
|
||||
const pathname = usePathname();
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const onClick = useCallback(() => triggerRef.current?.click(), []);
|
||||
const onClick = () => triggerRef.current?.click();
|
||||
|
||||
return (
|
||||
<Accordion.AccordionItem value={href}>
|
||||
@@ -40,13 +40,13 @@ export function ToolGroup({ Icon, title, href, tools, isOpend }: Props) {
|
||||
<Accordion.Trigger
|
||||
ref={triggerRef}
|
||||
className={cn(
|
||||
"absolute right-0 flex h-10 w-10 items-center justify-center rounded transition-all duration-0",
|
||||
"absolute right-0 flex size-10 items-center justify-center rounded transition-all duration-0",
|
||||
"hover:bg-accent",
|
||||
"[&[data-state=open]>svg]:rotate-180"
|
||||
)}
|
||||
aria-label="toggle open/close state of the tool group"
|
||||
>
|
||||
<icons.ChevronDown className="h-4 w-4 transition-transform duration-200" />
|
||||
<icons.ChevronDown className="size-4 transition-transform duration-200" />
|
||||
</Accordion.Trigger>
|
||||
</div>
|
||||
</Accordion.Header>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import Link, { LinkProps } from "next/link";
|
||||
|
||||
import { Tool } from "@/config/tools";
|
||||
@@ -14,8 +13,9 @@ type Props = Pick<Tool, "Icon" | "shortTitle"> &
|
||||
};
|
||||
|
||||
// FIXME: css outline messed up
|
||||
function RawToolLink({ Icon, shortTitle: title, href, onClick, highlight, grouped }: Props) {
|
||||
export function ToolLink({ Icon, shortTitle: title, href, onClick, highlight, grouped }: Props) {
|
||||
return (
|
||||
// @ts-expect-error react 19 beta error
|
||||
<Link
|
||||
className={cn(
|
||||
"flex h-10 items-center gap-3 whitespace-nowrap rounded",
|
||||
@@ -35,5 +35,3 @@ function RawToolLink({ Icon, shortTitle: title, href, onClick, highlight, groupe
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export const ToolLink = memo(RawToolLink);
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { siteConfig } from "@/config/site";
|
||||
import { cn } from "@/lib/style";
|
||||
import { useSidebar } from "@/hooks/use-sidebar";
|
||||
import * as icons from "@/components/icons";
|
||||
import { Menu } from "@/components/icons";
|
||||
import { ThemeToggle } from "@/components/theme-toggle";
|
||||
import { SidebarStatus, useSetSidebarStatus, useSidebarStatus } from "@/contexts/sidebar";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function SiteHeader({ className }: Props) {
|
||||
const setSidebarStatus = useSetSidebarStatus();
|
||||
const sidebarStatus = useSidebarStatus();
|
||||
const handleMenuToggle = useCallback(() => {
|
||||
setSidebarStatus(
|
||||
sidebarStatus === SidebarStatus.Open ? SidebarStatus.Closed : SidebarStatus.Open
|
||||
);
|
||||
}, [sidebarStatus, setSidebarStatus]);
|
||||
const { toggleSidebar } = useSidebar();
|
||||
|
||||
return (
|
||||
<header className={cn("flex items-center justify-between px-4", className)}>
|
||||
@@ -29,17 +22,18 @@ export function SiteHeader({ className }: Props) {
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center rounded p-1.5 hover:bg-accent"
|
||||
onClick={handleMenuToggle}
|
||||
onClick={toggleSidebar}
|
||||
>
|
||||
<Menu />
|
||||
</button>
|
||||
{/* @ts-expect-error react 19 beta error */}
|
||||
<Link className="text-lg" href="/">
|
||||
{siteConfig.name}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex gap-x-1">
|
||||
<a
|
||||
className="group h-10 w-10 rounded-md p-2"
|
||||
className="group size-10 rounded-md p-2"
|
||||
href={siteConfig.links.github}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
||||
@@ -2,7 +2,7 @@ export function TailwindIndicator() {
|
||||
if (process.env.NODE_ENV === "production") return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-1 left-1 z-50 flex h-6 w-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
|
||||
<div className="fixed bottom-1 left-1 z-50 flex size-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
|
||||
<div className="block sm:hidden">xs</div>
|
||||
<div className="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden">sm</div>
|
||||
<div className="hidden md:block lg:hidden xl:hidden 2xl:hidden">md</div>
|
||||
|
||||
@@ -15,8 +15,8 @@ export function ThemeToggle() {
|
||||
size="taller"
|
||||
onClick={() => setTheme(resolvedTheme === "light" ? "dark" : "light")}
|
||||
>
|
||||
<icons.Sun className="h-7 w-7 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<icons.Moon className="absolute h-7 w-7 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<icons.Sun className="size-7 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<icons.Moon className="absolute size-7 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ export type ToolCardProps = Pick<Tool, "Icon" | "longTitle" | "description" | "h
|
||||
|
||||
export function ToolCard({ Icon, longTitle, description, href }: ToolCardProps) {
|
||||
return (
|
||||
// @ts-expect-error react 19 beta error
|
||||
<Link className="rounded" {...{ href }}>
|
||||
<div className="group flex h-80 w-44 flex-col items-center gap-5 overflow-hidden rounded border bg-card p-5 text-card-foreground hover:bg-card-hover">
|
||||
<div className="flex flex-col p-5">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps } from "react";
|
||||
import { cva, VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/style";
|
||||
@@ -25,17 +25,8 @@ export const buttonVariants = cva(
|
||||
}
|
||||
);
|
||||
|
||||
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
|
||||
VariantProps<typeof buttonVariants>;
|
||||
export type ButtonProps = ComponentProps<"button"> & VariantProps<typeof buttonVariants>;
|
||||
|
||||
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, ...props }, ref) => (
|
||||
<button
|
||||
{...{ ref }}
|
||||
className={cn(buttonVariants({ variant, size }), className)}
|
||||
type="button"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
export const Button = ({ className, variant, size, ...props }: ButtonProps) => (
|
||||
<button className={cn(buttonVariants({ variant, size }), className)} type="button" {...props} />
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { ComponentPropsWithoutRef, forwardRef } from "react";
|
||||
import { ComponentProps } from "react";
|
||||
import { DiffEditor as MonacoDiffEditor } from "@monaco-editor/react";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
export type EditorProps = ComponentPropsWithoutRef<typeof MonacoDiffEditor>;
|
||||
// @ts-expect-error react 19 beta error
|
||||
export type DiffEditorProps = ComponentProps<typeof MonacoDiffEditor>;
|
||||
// @ts-expect-error react 19 beta error
|
||||
export const DiffEditor = ({ options, theme, ...props }: DiffEditorProps) => {
|
||||
const { theme: appTheme } = useTheme();
|
||||
const themeToUse = theme ?? (appTheme === "light" ? "light" : "vs-dark");
|
||||
|
||||
export const DiffEditor = forwardRef<HTMLTextAreaElement, EditorProps>(
|
||||
({ options, theme, ...props }, ref) => {
|
||||
const { theme: appTheme } = useTheme();
|
||||
const themeToUse = theme ?? (appTheme === "light" ? "light" : "vs-dark");
|
||||
|
||||
return (
|
||||
<MonacoDiffEditor
|
||||
{...{ ref }}
|
||||
theme={themeToUse}
|
||||
options={{
|
||||
tabFocusMode: true,
|
||||
automaticLayout: true,
|
||||
scrollBeyondLastLine: false,
|
||||
...options, // NOTE: merge shallowly
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
DiffEditor.displayName = "Editor";
|
||||
return (
|
||||
// @ts-expect-error react 19 beta error
|
||||
<MonacoDiffEditor
|
||||
// @ts-expect-error react 19 beta error
|
||||
theme={themeToUse}
|
||||
// @ts-expect-error react 19 beta error
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
options={{
|
||||
tabFocusMode: true,
|
||||
automaticLayout: true,
|
||||
scrollBeyondLastLine: false,
|
||||
// @ts-expect-error react 19 beta error
|
||||
...options, // NOTE: merge shallowly
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ComponentProps } from "react";
|
||||
import MonacoEditor from "@monaco-editor/react";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
export type EditorProps = React.ComponentPropsWithoutRef<typeof MonacoEditor>;
|
||||
// @ts-expect-error react 19 beta error
|
||||
export type EditorProps = ComponentProps<typeof MonacoEditor>;
|
||||
|
||||
/**
|
||||
* NOTE: This component maybe doesn't shrink according to the container component's width
|
||||
@@ -12,26 +13,28 @@ export type EditorProps = React.ComponentPropsWithoutRef<typeof MonacoEditor>;
|
||||
* @see https://github.com/suren-atoyan/monaco-react/issues/346
|
||||
*
|
||||
*/
|
||||
export const Editor = React.forwardRef<HTMLTextAreaElement, EditorProps>(
|
||||
({ options, theme, ...props }, ref) => {
|
||||
const { theme: appTheme } = useTheme();
|
||||
const themeToUse = theme ?? (appTheme === "light" ? "light" : "vs-dark");
|
||||
// @ts-expect-error react 19 beta error
|
||||
export const Editor = ({ options, theme, ...props }: EditorProps) => {
|
||||
const { theme: appTheme } = useTheme();
|
||||
const themeToUse = theme ?? (appTheme === "light" ? "light" : "vs-dark");
|
||||
|
||||
return (
|
||||
<MonacoEditor
|
||||
{...{ ref }}
|
||||
theme={themeToUse}
|
||||
options={{
|
||||
tabFocusMode: true,
|
||||
detectIndentation: false,
|
||||
minimap: { enabled: false },
|
||||
automaticLayout: true,
|
||||
scrollBeyondLastLine: false,
|
||||
...options, // NOTE: merge shallowly
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Editor.displayName = "Editor";
|
||||
return (
|
||||
// @ts-expect-error react 19 beta error
|
||||
<MonacoEditor
|
||||
// @ts-expect-error react 19 beta error
|
||||
theme={themeToUse}
|
||||
// @ts-expect-error react 19 beta error
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
options={{
|
||||
tabFocusMode: true,
|
||||
detectIndentation: false,
|
||||
minimap: { enabled: false },
|
||||
automaticLayout: true,
|
||||
scrollBeyondLastLine: false,
|
||||
// @ts-expect-error react 19 beta error
|
||||
...options, // NOTE: merge shallowly
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
import * as React from "react";
|
||||
import equal from "react-fast-compare";
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
import { cn } from "@/lib/style";
|
||||
|
||||
export type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
||||
export type InputProps = ComponentProps<"input"> & {
|
||||
fontMono?: true;
|
||||
};
|
||||
|
||||
const RawInput = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, fontMono, ...props }, ref) => (
|
||||
<input
|
||||
{...{ ref }}
|
||||
className={cn(
|
||||
"border-b-1 h-9 rounded border border-b-muted-foreground bg-input px-3 py-2 outline-none",
|
||||
"placeholder:text-muted-foreground",
|
||||
"hover:bg-input-hover",
|
||||
"focus:border-b-2 focus:border-b-indicator focus:bg-input-focus focus:pb-[7px]",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
fontMono && "font-mono",
|
||||
className
|
||||
)}
|
||||
spellCheck="false"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
export const Input = ({ className, fontMono, ...props }: InputProps) => (
|
||||
<input
|
||||
className={cn(
|
||||
"border-b-1 h-9 rounded border border-b-muted-foreground bg-input px-3 py-2 outline-none",
|
||||
"placeholder:text-muted-foreground",
|
||||
"hover:bg-input-hover",
|
||||
"focus:border-b-2 focus:border-b-indicator focus:bg-input-focus focus:pb-[7px]",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
fontMono && "font-mono",
|
||||
className
|
||||
)}
|
||||
spellCheck="false"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
RawInput.displayName = "RawInput";
|
||||
|
||||
export const Input = React.memo(RawInput, equal);
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ComponentProps } from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
|
||||
import { cn } from "@/lib/style";
|
||||
|
||||
export const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
export const Label = ({ className, ...props }: ComponentProps<typeof LabelPrimitive.Root>) => (
|
||||
<LabelPrimitive.Root
|
||||
{...{ ref }}
|
||||
className={cn(
|
||||
"leading-none",
|
||||
"peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
@@ -18,5 +14,4 @@ export const Label = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Label.displayName = LabelPrimitive.Root.displayName;
|
||||
);
|
||||
|
||||
23
components/ui/providers.tsx
Normal file
23
components/ui/providers.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||
import { ThemeProviderProps } from "next-themes/dist/types";
|
||||
|
||||
import { SidebarProvider } from "@/hooks/use-sidebar";
|
||||
import { TailwindIndicator } from "@/components/tailwind-indicator";
|
||||
import { SearchTextProvider } from "@/contexts/search-text";
|
||||
|
||||
import { ClientLayout } from "../client-layout";
|
||||
|
||||
export function Providers({ children, ...props }: ThemeProviderProps) {
|
||||
return (
|
||||
<NextThemesProvider {...props}>
|
||||
<SearchTextProvider>
|
||||
<SidebarProvider>
|
||||
<ClientLayout>{children}</ClientLayout>
|
||||
<TailwindIndicator />
|
||||
</SidebarProvider>
|
||||
</SearchTextProvider>
|
||||
</NextThemesProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ComponentProps } from "react";
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
|
||||
import { cn } from "@/lib/style";
|
||||
import * as icons from "@/components/icons";
|
||||
import { Indicator } from "@/components/indicator";
|
||||
|
||||
export type Props = React.ComponentPropsWithoutRef<typeof SelectPrimitive.Root>;
|
||||
export type Props = ComponentProps<typeof SelectPrimitive.Root>;
|
||||
|
||||
export const { Root, Group, Value } = SelectPrimitive;
|
||||
|
||||
export const Trigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
export const Trigger = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: ComponentProps<typeof SelectPrimitive.Trigger>) => (
|
||||
<SelectPrimitive.Trigger
|
||||
{...{ ref }}
|
||||
className={cn(
|
||||
"flex h-9 items-center justify-between rounded-md border bg-select px-2.5 py-1.5",
|
||||
"placeholder:text-muted-foreground",
|
||||
@@ -28,19 +28,20 @@ export const Trigger = React.forwardRef<
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<icons.ChevronDown className="h-4 w-4 opacity-50" />
|
||||
<icons.ChevronDown className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
));
|
||||
Trigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||
);
|
||||
|
||||
export const Content = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
export const Content = ({
|
||||
className,
|
||||
children,
|
||||
position = "popper",
|
||||
...props
|
||||
}: ComponentProps<typeof SelectPrimitive.Content>) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
{...{ ref, position }}
|
||||
position={position}
|
||||
className={cn(
|
||||
"relative z-50 overflow-hidden rounded-md border bg-select-content text-select-content-foreground shadow-md animate-in fade-in-80",
|
||||
position === "popper" && "translate-y-1",
|
||||
@@ -59,27 +60,18 @@ export const Content = React.forwardRef<
|
||||
</SelectPrimitive.Viewport>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
));
|
||||
Content.displayName = SelectPrimitive.Content.displayName;
|
||||
);
|
||||
|
||||
export const Label = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
{...{ ref }}
|
||||
className={cn("py-1.5 pl-8 pr-2 font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Label.displayName = SelectPrimitive.Label.displayName;
|
||||
export const Label = ({ className, ...props }: ComponentProps<typeof SelectPrimitive.Label>) => (
|
||||
<SelectPrimitive.Label className={cn("py-1.5 pl-8 pr-2 font-semibold", className)} {...props} />
|
||||
);
|
||||
|
||||
export const Item = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
export const Item = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: ComponentProps<typeof SelectPrimitive.Item>) => (
|
||||
<SelectPrimitive.Item
|
||||
{...{ ref }}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm px-2.5 py-1.5 outline-none",
|
||||
"hover:bg-select-item-hover",
|
||||
@@ -96,17 +88,11 @@ export const Item = React.forwardRef<
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
Item.displayName = SelectPrimitive.Item.displayName;
|
||||
);
|
||||
|
||||
export const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
{...{ ref }}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Separator.displayName = SelectPrimitive.Separator.displayName;
|
||||
export const Separator = ({
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<typeof SelectPrimitive.Separator>) => (
|
||||
<SelectPrimitive.Separator className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
|
||||
);
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ComponentProps } from "react";
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||
|
||||
import { cn } from "@/lib/style";
|
||||
|
||||
export const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
|
||||
export const Separator = ({
|
||||
className,
|
||||
orientation = "horizontal",
|
||||
decorative = true,
|
||||
...props
|
||||
}: ComponentProps<typeof SeparatorPrimitive.Root>) => (
|
||||
<SeparatorPrimitive.Root
|
||||
{...{ ref, decorative, orientation }}
|
||||
className={cn(
|
||||
"bg-separator",
|
||||
orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
|
||||
className
|
||||
)}
|
||||
{...{ decorative, orientation }}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||
);
|
||||
|
||||
@@ -1,38 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ComponentProps } from "react";
|
||||
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
||||
|
||||
import { cn } from "@/lib/style";
|
||||
|
||||
export type SwitchProps = React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>;
|
||||
export type SwitchProps = ComponentProps<typeof SwitchPrimitives.Root>;
|
||||
|
||||
export const Switch = React.forwardRef<React.ElementRef<typeof SwitchPrimitives.Root>, SwitchProps>(
|
||||
({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
{...{ ref }}
|
||||
export const Switch = ({ className, ...props }: SwitchProps) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
"group inline-flex h-5 w-10 shrink-0 cursor-pointer items-center rounded-full border border-muted-foreground bg-switch",
|
||||
"hover:bg-switch-hover",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"hover:disabled:bg-switch",
|
||||
"data-[state=checked]:border-transparent data-[state=checked]:bg-indicator",
|
||||
"data-[state=checked]:hover:bg-indicator-hover",
|
||||
"data-[state=checked]:disabled:hover:bg-indicator",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
"group inline-flex h-5 w-10 shrink-0 cursor-pointer items-center rounded-full border border-muted-foreground bg-switch",
|
||||
"hover:bg-switch-hover",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"hover:disabled:bg-switch",
|
||||
"data-[state=checked]:border-transparent data-[state=checked]:bg-indicator",
|
||||
"data-[state=checked]:hover:bg-indicator-hover",
|
||||
"data-[state=checked]:disabled:hover:bg-indicator",
|
||||
className
|
||||
"pointer-events-none block size-3.5 rounded-full bg-foreground/80 shadow-lg transition-transform",
|
||||
"group-hover:size-4",
|
||||
"group-disabled:size-3.5",
|
||||
"data-[state=checked]:translate-x-[22px] data-[state=checked]:bg-background",
|
||||
"data-[state=unchecked]:translate-x-0.5"
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
"pointer-events-none block h-3.5 w-3.5 rounded-full bg-foreground/80 shadow-lg transition-transform",
|
||||
"group-hover:h-4 group-hover:w-4",
|
||||
"group-disabled:h-3.5 group-disabled:w-3.5",
|
||||
"data-[state=checked]:translate-x-[22px] data-[state=checked]:bg-background",
|
||||
"data-[state=unchecked]:translate-x-0.5"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
)
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
);
|
||||
Switch.displayName = SwitchPrimitives.Root.displayName;
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
import * as React from "react";
|
||||
import equal from "react-fast-compare";
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
import { cn } from "@/lib/style";
|
||||
|
||||
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
||||
export type TextareaProps = ComponentProps<"textarea">;
|
||||
|
||||
export const RawTextarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => (
|
||||
<textarea
|
||||
{...{ ref }}
|
||||
className={cn(
|
||||
"border-b-1 resize-none rounded border border-b-muted-foreground bg-textarea px-3 py-2 font-mono outline-none",
|
||||
"placeholder:text-muted-foreground",
|
||||
"hover:bg-textarea-hover",
|
||||
"focus:border-b-2 focus:border-b-indicator focus:bg-textarea-focus focus:pb-[7px]",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
spellCheck="false"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
export const Textarea = ({ className, ...props }: TextareaProps) => (
|
||||
<textarea
|
||||
className={cn(
|
||||
"border-b-1 resize-none rounded border border-b-muted-foreground bg-textarea px-3 py-2 font-mono outline-none",
|
||||
"placeholder:text-muted-foreground",
|
||||
"hover:bg-textarea-hover",
|
||||
"focus:border-b-2 focus:border-b-indicator focus:bg-textarea-focus focus:pb-[7px]",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
spellCheck="false"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
RawTextarea.displayName = "RawTextarea";
|
||||
|
||||
export const Textarea = React.memo(RawTextarea, equal);
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ComponentProps } from "react";
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
||||
|
||||
import { cn } from "@/lib/style";
|
||||
|
||||
const ToggleGroup = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("flex items-center gap-2.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
const ToggleGroup = ({ className, ...props }: ComponentProps<typeof ToggleGroupPrimitive.Root>) => (
|
||||
<ToggleGroupPrimitive.Root className={cn("flex items-center gap-2.5", className)} {...props} />
|
||||
);
|
||||
|
||||
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
|
||||
|
||||
const ToggleGroupItem = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
const ToggleGroupItem = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: ComponentProps<typeof ToggleGroupPrimitive.Item>) => (
|
||||
<ToggleGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"focus-visible:ring-ring inline-flex h-10 items-center justify-center rounded-md bg-accent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-sky-600 data-[state=on]:text-white",
|
||||
className
|
||||
@@ -32,8 +25,6 @@ const ToggleGroupItem = React.forwardRef<
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
));
|
||||
|
||||
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
||||
);
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ComponentProps } from "react";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
|
||||
import { cn } from "@/lib/style";
|
||||
@@ -9,12 +9,13 @@ export const TooltipProvider = TooltipPrimitive.Provider;
|
||||
export const Tooltip = TooltipPrimitive.Root;
|
||||
export const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
|
||||
export const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
export const TooltipContent = ({
|
||||
className,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: ComponentProps<typeof TooltipPrimitive.Content>) => (
|
||||
<TooltipPrimitive.Content
|
||||
{...{ ref, sideOffset }}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 overflow-hidden rounded-md border bg-tooltip px-3 py-1.5 text-tooltip-foreground shadow-md animate-in fade-in-50",
|
||||
"data-[side=bottom]:slide-in-from-top-1",
|
||||
@@ -25,5 +26,4 @@ export const TooltipContent = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
);
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, PropsWithChildren, useCallback, useContext } from "react";
|
||||
import { useCookies } from "next-client-cookies";
|
||||
|
||||
const SIDEBAR_COOKIE_NAME = "sidebar";
|
||||
|
||||
export enum SidebarStatus {
|
||||
Open = "open",
|
||||
Closed = "closed",
|
||||
}
|
||||
|
||||
const SidebarContext = createContext(SidebarStatus.Closed);
|
||||
const SetSidebarContext = createContext<(newStatus: SidebarStatus) => void>(() => {});
|
||||
|
||||
export const useSidebarStatus = () => useContext(SidebarContext);
|
||||
export const useSetSidebarStatus = () => useContext(SetSidebarContext);
|
||||
|
||||
export const SidebarProvider = ({ children }: PropsWithChildren) => {
|
||||
const cookies = useCookies();
|
||||
|
||||
const sidebarStatus = (cookies.get(SIDEBAR_COOKIE_NAME) ?? SidebarStatus.Open) as SidebarStatus;
|
||||
const setSidebarStatus = useCallback(
|
||||
(newStatus: SidebarStatus) => {
|
||||
cookies.set(SIDEBAR_COOKIE_NAME, newStatus);
|
||||
},
|
||||
[cookies]
|
||||
);
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider value={sidebarStatus}>
|
||||
<SetSidebarContext.Provider value={setSidebarStatus}>{children}</SetSidebarContext.Provider>
|
||||
</SidebarContext.Provider>
|
||||
);
|
||||
};
|
||||
57
hooks/use-sidebar.tsx
Normal file
57
hooks/use-sidebar.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, ReactNode, useContext, useEffect, useState } from "react";
|
||||
|
||||
const LOCAL_STORAGE_KEY = "sidebar";
|
||||
|
||||
interface SidebarContext {
|
||||
isSidebarOpen: boolean;
|
||||
toggleSidebar: () => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
const SidebarContext = createContext<SidebarContext | undefined>(undefined);
|
||||
|
||||
export function useSidebar() {
|
||||
const context = useContext(SidebarContext);
|
||||
if (!context) {
|
||||
throw new Error("useSidebarContext must be used within a SidebarProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
interface SidebarProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function SidebarProvider({ children }: SidebarProviderProps) {
|
||||
const [isSidebarOpen, setSidebarOpen] = useState(true);
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const value = localStorage.getItem(LOCAL_STORAGE_KEY);
|
||||
if (value) {
|
||||
setSidebarOpen(JSON.parse(value) as boolean);
|
||||
}
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setSidebarOpen(value => {
|
||||
const newState = !value;
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newState));
|
||||
return newState;
|
||||
});
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider value={{ isSidebarOpen, toggleSidebar, isLoading }}>
|
||||
{children}
|
||||
</SidebarContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "export",
|
||||
reactStrictMode: true,
|
||||
experimental: {
|
||||
typedRoutes: true,
|
||||
reactCompiler: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
62
package.json
62
package.json
@@ -25,65 +25,67 @@
|
||||
"format": "prettier \"**/*.{js,jsx,ts,tsx,css,json,md}\" --cache"
|
||||
},
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "^4.5.1",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-select": "^1.2.2",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.6",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"change-case": "^5.4.4",
|
||||
"class-variance-authority": "^0.6.0",
|
||||
"clsx": "^1.2.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"create-hash": "^1.2.0",
|
||||
"fp-ts": "^2.16.0",
|
||||
"fuse.js": "^6.6.2",
|
||||
"html-escaper": "^3.0.3",
|
||||
"js-base64": "^3.7.5",
|
||||
"js-base64": "^3.7.7",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lucide-react": "^0.221.0",
|
||||
"lucide-react": "^0.378.0",
|
||||
"neverthrow": "^6.0.0",
|
||||
"next": "13.5.4",
|
||||
"next": "14.3.0-canary.70",
|
||||
"next-client-cookies": "^1.1.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "18.2.0",
|
||||
"react": "19.0.0-beta-26f2496093-20240514",
|
||||
"react-day-picker": "^8.7.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-dom": "19.0.0-beta-26f2496093-20240514",
|
||||
"react-fast-compare": "^3.2.2",
|
||||
"react-resizable-panels": "^2.0.19",
|
||||
"sharp": "^0.32.1",
|
||||
"tailwindcss-animate": "^1.0.5",
|
||||
"sharp": "^0.33.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"url-slug": "^4.0.1",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.0.1",
|
||||
"@next/bundle-analyzer": "13.5.4",
|
||||
"@playwright/test": "^1.39.0",
|
||||
"@types/create-hash": "^1.2.2",
|
||||
"@types/html-escaper": "^3.0.0",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/node": "~18.16.16",
|
||||
"@types/react": "~18.2.7",
|
||||
"@types/react-dom": "~18.2.4",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.2.1",
|
||||
"@next/bundle-analyzer": "14.2.3",
|
||||
"@playwright/test": "^1.44.0",
|
||||
"@types/create-hash": "^1.2.6",
|
||||
"@types/html-escaper": "^3.0.2",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "~20.12.12",
|
||||
"@types/react": "npm:types-react@19.0.0-beta.1",
|
||||
"@types/react-dom": "npm:types-react-dom@beta",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.7",
|
||||
"@typescript-eslint/parser": "^5.59.7",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"babel-plugin-react-compiler": "0.0.0-experimental-592953e-20240517",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-config-next": "~13.4.4",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-tailwindcss": "^3.12.0",
|
||||
"postcss": "^8.4.23",
|
||||
"eslint-config-next": "~14.2.3",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-compiler": "0.0.0-experimental-c8b3f72-20240517",
|
||||
"eslint-plugin-tailwindcss": "^3.15.2",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^2.8.8",
|
||||
"serve": "^14.2.1",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^5.0.4"
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
3336
pnpm-lock.yaml
generated
3336
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user