mirror of
https://github.com/ershisan99/DevToysWeb.git
synced 2025-12-16 12:32:48 +00:00
refactor: rename, reorder, etc.
This commit is contained in:
@@ -7,18 +7,8 @@ import { toolGroups } from "@/config/tools";
|
|||||||
import { safeJsonParse } from "@/lib/json";
|
import { safeJsonParse } from "@/lib/json";
|
||||||
import { safeYamlParse } from "@/lib/yaml";
|
import { safeYamlParse } from "@/lib/yaml";
|
||||||
import { Editor, EditorProps } from "@/components/ui/editor";
|
import { Editor, EditorProps } from "@/components/ui/editor";
|
||||||
import {
|
import * as Select from "@/components/ui/select";
|
||||||
Select,
|
import * as Button from "@/components/buttons";
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectProps,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { ClearButton } from "@/components/buttons/clear";
|
|
||||||
import { CopyButton } from "@/components/buttons/copy";
|
|
||||||
import { FileButton } from "@/components/buttons/file";
|
|
||||||
import { PasteButton } from "@/components/buttons/paste";
|
|
||||||
import { Configuration } from "@/components/configuration";
|
import { Configuration } from "@/components/configuration";
|
||||||
import { Configurations } from "@/components/configurations";
|
import { Configurations } from "@/components/configurations";
|
||||||
import { ControlMenu } from "@/components/control-menu";
|
import { ControlMenu } from "@/components/control-menu";
|
||||||
@@ -38,41 +28,44 @@ export default function Page() {
|
|||||||
yaml: "foo: bar",
|
yaml: "foo: bar",
|
||||||
});
|
});
|
||||||
|
|
||||||
const setJsonReactively = useCallback((text: string) => {
|
const setFormByJson = useCallback((text: string) => {
|
||||||
const parsed = safeJsonParse(text);
|
|
||||||
|
|
||||||
setForm(prev => ({
|
setForm(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
json: text,
|
json: text,
|
||||||
yaml: parsed.isErr()
|
yaml: safeJsonParse(text)
|
||||||
? ""
|
.map(x => yaml.dump(x, { indent: prev.indentation.length, quotingType: '"' }))
|
||||||
: yaml.dump(parsed.value, { indent: prev.indentation.length, quotingType: '"' }),
|
.unwrapOr(""),
|
||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setYamlReactively = useCallback((text: string) => {
|
const setFormByYaml = useCallback((text: string) => {
|
||||||
const parsed = safeYamlParse(text);
|
|
||||||
|
|
||||||
setForm(prev => ({
|
setForm(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
|
json: safeYamlParse(text)
|
||||||
|
.map(x => JSON.stringify(x, null, prev.indentation))
|
||||||
|
.unwrapOr(""),
|
||||||
yaml: text,
|
yaml: text,
|
||||||
json: parsed.isErr() ? "" : JSON.stringify(parsed.value, null, prev.indentation),
|
|
||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const clearBoth = useCallback(() => {
|
const clearBoth = useCallback(() => {
|
||||||
setForm(prev => ({ ...prev, json: "", yaml: "" }));
|
setForm(prev => ({
|
||||||
|
...prev,
|
||||||
|
json: "",
|
||||||
|
yaml: "",
|
||||||
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onIndentationChange: SelectProps["onValueChange"] = value => {
|
const onIndentationChange: Select.Props["onValueChange"] = value => {
|
||||||
const parsed = safeJsonParse(form.json);
|
const jsonYaml = safeJsonParse(form.json)
|
||||||
|
.map(x => ({
|
||||||
const jsonYaml = parsed.isErr()
|
json: JSON.stringify(x, null, value),
|
||||||
? { json: "", yaml: "" }
|
yaml: yaml.dump(x, { indent: value.length, quotingType: '"' }),
|
||||||
: {
|
}))
|
||||||
json: JSON.stringify(parsed.value, null, value),
|
.unwrapOr({
|
||||||
yaml: yaml.dump(parsed.value, { indent: value.length, quotingType: '"' }),
|
json: "",
|
||||||
};
|
yaml: "",
|
||||||
|
});
|
||||||
|
|
||||||
setForm({
|
setForm({
|
||||||
indentation: value,
|
indentation: value,
|
||||||
@@ -80,59 +73,55 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onJsonChange: EditorProps["onChange"] = value => setJsonReactively(value ?? "");
|
const onJsonChange: EditorProps["onChange"] = value => setFormByJson(value ?? "");
|
||||||
const onYamlChange: EditorProps["onChange"] = value => setYamlReactively(value ?? "");
|
const onYamlChange: EditorProps["onChange"] = value => setFormByYaml(value ?? "");
|
||||||
|
|
||||||
const indentationConfig = (
|
const indentationConfig = (
|
||||||
<Configuration
|
<Configuration
|
||||||
icon={<icons.Space size={24} className="-translate-y-1.5" />}
|
icon={<icons.Space size={24} className="-translate-y-1.5" />}
|
||||||
title="Indentation"
|
title="Indentation"
|
||||||
control={
|
control={
|
||||||
<Select value={form.indentation} onValueChange={onIndentationChange}>
|
<Select.Root value={form.indentation} onValueChange={onIndentationChange}>
|
||||||
<SelectTrigger
|
<Select.Trigger
|
||||||
className="w-28"
|
className="w-28"
|
||||||
aria-label="toggle open/close state of indentation selection"
|
aria-label="toggle open/close state of indentation selection"
|
||||||
>
|
>
|
||||||
<SelectValue placeholder={form.indentation} />
|
<Select.Value placeholder={form.indentation} />
|
||||||
</SelectTrigger>
|
</Select.Trigger>
|
||||||
<SelectContent>
|
<Select.Content>
|
||||||
<SelectItem value={indentations.two}>2 spaces</SelectItem>
|
<Select.Item value={indentations.two}>2 spaces</Select.Item>
|
||||||
<SelectItem value={indentations.four}>4 spaces</SelectItem>
|
<Select.Item value={indentations.four}>4 spaces</Select.Item>
|
||||||
</SelectContent>
|
</Select.Content>
|
||||||
</Select>
|
</Select.Root>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const jsonPasteButton = <PasteButton onClipboardRead={setJsonReactively} />;
|
const jsonPasteButton = <Button.Paste onClipboardRead={setFormByJson} />;
|
||||||
const yamlPasteButton = <PasteButton onClipboardRead={setYamlReactively} />;
|
const yamlPasteButton = <Button.Paste onClipboardRead={setFormByYaml} />;
|
||||||
|
|
||||||
const jsonFileButton = (
|
const jsonFileButton = (
|
||||||
<FileButton
|
<Button.File accept=".json" onFileRead={setFormByJson} iconOnly aria-label="load a json file" />
|
||||||
accept=".json"
|
|
||||||
onFileRead={setJsonReactively}
|
|
||||||
iconOnly
|
|
||||||
aria-label="load a json file"
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
const yamlFileButton = (
|
const yamlFileButton = (
|
||||||
<FileButton
|
<Button.File
|
||||||
accept=".yml,.yaml"
|
accept=".yml,.yaml"
|
||||||
onFileRead={setYamlReactively}
|
onFileRead={setFormByYaml}
|
||||||
iconOnly
|
iconOnly
|
||||||
aria-label="load a yaml file"
|
aria-label="load a yaml file"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const jsonCopyButton = <CopyButton text={form.json} />;
|
const jsonCopyButton = <Button.Copy text={form.json} />;
|
||||||
const yamlCopyButton = <CopyButton text={form.yaml} />;
|
const yamlCopyButton = <Button.Copy text={form.yaml} />;
|
||||||
|
|
||||||
const clearButton = <ClearButton onClick={clearBoth} iconOnly aria-label="clear json and yaml" />;
|
const clearButton = (
|
||||||
|
<Button.Clear onClick={clearBoth} iconOnly aria-label="clear json and yaml" />
|
||||||
|
);
|
||||||
|
|
||||||
const jsonControl = (
|
const jsonControl = (
|
||||||
<ControlMenu list={[jsonPasteButton, jsonFileButton, jsonCopyButton, clearButton]} />
|
<ControlMenu list={[jsonPasteButton, jsonFileButton, jsonCopyButton, clearButton]} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const yamlControl = (
|
const yamlControl = (
|
||||||
<ControlMenu list={[yamlPasteButton, yamlFileButton, yamlCopyButton, clearButton]} />
|
<ControlMenu list={[yamlPasteButton, yamlFileButton, yamlCopyButton, clearButton]} />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useCallback, useState } from "react";
|
|||||||
import { toolGroups } from "@/config/tools";
|
import { toolGroups } from "@/config/tools";
|
||||||
import * as baselib from "@/lib/base";
|
import * as baselib from "@/lib/base";
|
||||||
import { Input, InputProps } from "@/components/ui/input";
|
import { Input, InputProps } from "@/components/ui/input";
|
||||||
import { PasteButton } from "@/components/buttons/paste";
|
import * as Button from "@/components/buttons";
|
||||||
import { Configuration } from "@/components/configuration";
|
import { Configuration } from "@/components/configuration";
|
||||||
import { Configurations } from "@/components/configurations";
|
import { Configurations } from "@/components/configurations";
|
||||||
import { ControlMenu } from "@/components/control-menu";
|
import { ControlMenu } from "@/components/control-menu";
|
||||||
@@ -53,10 +53,10 @@ export default function Page() {
|
|||||||
const trySetOct = useCallback((value: string) => trySetInt(8)(value), []);
|
const trySetOct = useCallback((value: string) => trySetInt(8)(value), []);
|
||||||
const trySetBin = useCallback((value: string) => trySetInt(2)(value), []);
|
const trySetBin = useCallback((value: string) => trySetInt(2)(value), []);
|
||||||
|
|
||||||
const onDecChange: InputProps["onChange"] = ({ currentTarget: { value } }) => trySetDec(value);
|
const onDecChange: InputProps["onChange"] = e => trySetDec(e.currentTarget.value);
|
||||||
const onHexChange: InputProps["onChange"] = ({ currentTarget: { value } }) => trySetHex(value);
|
const onHexChange: InputProps["onChange"] = e => trySetHex(e.currentTarget.value);
|
||||||
const onOctChange: InputProps["onChange"] = ({ currentTarget: { value } }) => trySetOct(value);
|
const onOctChange: InputProps["onChange"] = e => trySetOct(e.currentTarget.value);
|
||||||
const onBinChange: InputProps["onChange"] = ({ currentTarget: { value } }) => trySetBin(value);
|
const onBinChange: InputProps["onChange"] = e => trySetBin(e.currentTarget.value);
|
||||||
|
|
||||||
const formatNumberConfig = (
|
const formatNumberConfig = (
|
||||||
<Configuration
|
<Configuration
|
||||||
@@ -73,10 +73,10 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const decPasteButton = <PasteButton onClipboardRead={trySetDec} />;
|
const decPasteButton = <Button.Paste onClipboardRead={trySetDec} />;
|
||||||
const hexPasteButton = <PasteButton onClipboardRead={trySetHex} />;
|
const hexPasteButton = <Button.Paste onClipboardRead={trySetHex} />;
|
||||||
const octPasteButton = <PasteButton onClipboardRead={trySetOct} />;
|
const octPasteButton = <Button.Paste onClipboardRead={trySetOct} />;
|
||||||
const binPasteButton = <PasteButton onClipboardRead={trySetBin} />;
|
const binPasteButton = <Button.Paste onClipboardRead={trySetBin} />;
|
||||||
|
|
||||||
const decControl = <ControlMenu list={[decPasteButton]} />;
|
const decControl = <ControlMenu list={[decPasteButton]} />;
|
||||||
const hexControl = <ControlMenu list={[hexPasteButton]} />;
|
const hexControl = <ControlMenu list={[hexPasteButton]} />;
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ import { decode, encode, isValid } from "js-base64";
|
|||||||
|
|
||||||
import { toolGroups } from "@/config/tools";
|
import { toolGroups } from "@/config/tools";
|
||||||
import { Textarea, TextareaProps } from "@/components/ui/textarea";
|
import { Textarea, TextareaProps } from "@/components/ui/textarea";
|
||||||
import { ClearButton } from "@/components/buttons/clear";
|
import * as Button from "@/components/buttons";
|
||||||
import { CopyButton } from "@/components/buttons/copy";
|
|
||||||
import { FileButton } from "@/components/buttons/file";
|
|
||||||
import { PasteButton } from "@/components/buttons/paste";
|
|
||||||
import { ControlMenu } from "@/components/control-menu";
|
import { ControlMenu } from "@/components/control-menu";
|
||||||
import { PageRootSection } from "@/components/page-root-section";
|
import { PageRootSection } from "@/components/page-root-section";
|
||||||
import { PageSection } from "@/components/page-section";
|
import { PageSection } from "@/components/page-section";
|
||||||
@@ -19,19 +16,19 @@ export default function Page() {
|
|||||||
encoded: "8J+YgPCfmILwn6Sj",
|
encoded: "8J+YgPCfmILwn6Sj",
|
||||||
});
|
});
|
||||||
|
|
||||||
const setDecodedReactively = useCallback((text: string) => {
|
const setFormByDecoded = useCallback((text: string) => {
|
||||||
setForm({
|
setForm({
|
||||||
decoded: text,
|
decoded: text,
|
||||||
encoded: encode(text),
|
encoded: encode(text),
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setEncodedReactively = useCallback((text: string) => {
|
const setFormByEncoded = useCallback((text: string) => {
|
||||||
const newDecoded = decode(text);
|
const newDecoded = decode(text);
|
||||||
|
|
||||||
setForm({
|
setForm({
|
||||||
encoded: text,
|
|
||||||
decoded: isValid(text) && !newDecoded.includes("<22>") ? newDecoded : "",
|
decoded: isValid(text) && !newDecoded.includes("<22>") ? newDecoded : "",
|
||||||
|
encoded: text,
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -42,33 +39,29 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onDecodedChange: TextareaProps["onChange"] = ({ currentTarget: { value } }) =>
|
const onDecodedChange: TextareaProps["onChange"] = e => setFormByDecoded(e.currentTarget.value);
|
||||||
setDecodedReactively(value);
|
const onEncodedChange: TextareaProps["onChange"] = e => setFormByEncoded(e.currentTarget.value);
|
||||||
|
|
||||||
const onEncodedChange: TextareaProps["onChange"] = ({ currentTarget: { value } }) =>
|
const decodedPasteButton = <Button.Paste onClipboardRead={setFormByDecoded} />;
|
||||||
setEncodedReactively(value);
|
const encodedPasteButton = <Button.Paste onClipboardRead={setFormByEncoded} />;
|
||||||
|
|
||||||
const decodedPasteButton = <PasteButton onClipboardRead={setDecodedReactively} />;
|
|
||||||
const encodedPasteButton = <PasteButton onClipboardRead={setEncodedReactively} />;
|
|
||||||
|
|
||||||
const decodedFileButton = (
|
const decodedFileButton = (
|
||||||
<FileButton onFileRead={setDecodedReactively} iconOnly aria-label="load a decoded file" />
|
<Button.File onFileRead={setFormByDecoded} iconOnly aria-label="load a decoded file" />
|
||||||
);
|
);
|
||||||
const encodedFileButton = (
|
const encodedFileButton = (
|
||||||
<FileButton onFileRead={setEncodedReactively} iconOnly aria-label="load a encoded file" />
|
<Button.File onFileRead={setFormByEncoded} iconOnly aria-label="load a encoded file" />
|
||||||
);
|
);
|
||||||
|
|
||||||
const decodedCopyButton = <CopyButton text={form.decoded} />;
|
const decodedCopyButton = <Button.Copy text={form.decoded} />;
|
||||||
const encodedCopyButton = <CopyButton text={form.encoded} />;
|
const encodedCopyButton = <Button.Copy text={form.encoded} />;
|
||||||
|
|
||||||
const clearButton = (
|
const clearButton = (
|
||||||
<ClearButton onClick={clearBoth} iconOnly aria-label="clear decoded and encoded" />
|
<Button.Clear onClick={clearBoth} iconOnly aria-label="clear decoded and encoded" />
|
||||||
);
|
);
|
||||||
|
|
||||||
const decodedControl = (
|
const decodedControl = (
|
||||||
<ControlMenu list={[decodedPasteButton, decodedFileButton, decodedCopyButton, clearButton]} />
|
<ControlMenu list={[decodedPasteButton, decodedFileButton, decodedCopyButton, clearButton]} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const encodedControl = (
|
const encodedControl = (
|
||||||
<ControlMenu list={[encodedPasteButton, encodedFileButton, encodedCopyButton, clearButton]} />
|
<ControlMenu list={[encodedPasteButton, encodedFileButton, encodedCopyButton, clearButton]} />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ import { escape, unescape } from "html-escaper";
|
|||||||
|
|
||||||
import { toolGroups } from "@/config/tools";
|
import { toolGroups } from "@/config/tools";
|
||||||
import { Textarea, TextareaProps } from "@/components/ui/textarea";
|
import { Textarea, TextareaProps } from "@/components/ui/textarea";
|
||||||
import { ClearButton } from "@/components/buttons/clear";
|
import * as Button from "@/components/buttons";
|
||||||
import { CopyButton } from "@/components/buttons/copy";
|
|
||||||
import { FileButton } from "@/components/buttons/file";
|
|
||||||
import { PasteButton } from "@/components/buttons/paste";
|
|
||||||
import { ControlMenu } from "@/components/control-menu";
|
import { ControlMenu } from "@/components/control-menu";
|
||||||
import { PageRootSection } from "@/components/page-root-section";
|
import { PageRootSection } from "@/components/page-root-section";
|
||||||
import { PageSection } from "@/components/page-section";
|
import { PageSection } from "@/components/page-section";
|
||||||
@@ -19,17 +16,17 @@ export default function Page() {
|
|||||||
encoded: "> It's "HTML escaping".",
|
encoded: "> It's "HTML escaping".",
|
||||||
});
|
});
|
||||||
|
|
||||||
const setDecodedReactively = useCallback((text: string) => {
|
const setFormByDecoded = useCallback((text: string) => {
|
||||||
setForm({
|
setForm({
|
||||||
decoded: text,
|
decoded: text,
|
||||||
encoded: escape(text),
|
encoded: escape(text),
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setEncodedReactively = useCallback((text: string) => {
|
const setFormByEncoded = useCallback((text: string) => {
|
||||||
setForm({
|
setForm({
|
||||||
encoded: text,
|
|
||||||
decoded: unescape(text),
|
decoded: unescape(text),
|
||||||
|
encoded: text,
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -40,33 +37,29 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onDecodedChange: TextareaProps["onChange"] = ({ currentTarget: { value } }) =>
|
const onDecodedChange: TextareaProps["onChange"] = e => setFormByDecoded(e.currentTarget.value);
|
||||||
setDecodedReactively(value);
|
const onEncodedChange: TextareaProps["onChange"] = e => setFormByEncoded(e.currentTarget.value);
|
||||||
|
|
||||||
const onEncodedChange: TextareaProps["onChange"] = ({ currentTarget: { value } }) =>
|
const decodedPasteButton = <Button.Paste onClipboardRead={setFormByDecoded} />;
|
||||||
setEncodedReactively(value);
|
const encodedPasteButton = <Button.Paste onClipboardRead={setFormByEncoded} />;
|
||||||
|
|
||||||
const decodedPasteButton = <PasteButton onClipboardRead={setDecodedReactively} />;
|
|
||||||
const encodedPasteButton = <PasteButton onClipboardRead={setEncodedReactively} />;
|
|
||||||
|
|
||||||
const decodedFileButton = (
|
const decodedFileButton = (
|
||||||
<FileButton onFileRead={setDecodedReactively} iconOnly aria-label="load a decoded file" />
|
<Button.File onFileRead={setFormByDecoded} iconOnly aria-label="load a decoded file" />
|
||||||
);
|
);
|
||||||
const encodedFileButton = (
|
const encodedFileButton = (
|
||||||
<FileButton onFileRead={setEncodedReactively} iconOnly aria-label="load a encoded file" />
|
<Button.File onFileRead={setFormByEncoded} iconOnly aria-label="load a encoded file" />
|
||||||
);
|
);
|
||||||
|
|
||||||
const decodedCopyButton = <CopyButton text={form.decoded} />;
|
const decodedCopyButton = <Button.Copy text={form.decoded} />;
|
||||||
const encodedCopyButton = <CopyButton text={form.encoded} />;
|
const encodedCopyButton = <Button.Copy text={form.encoded} />;
|
||||||
|
|
||||||
const clearButton = (
|
const clearButton = (
|
||||||
<ClearButton onClick={clearBoth} iconOnly aria-label="clear decoded and encoded" />
|
<Button.Clear onClick={clearBoth} iconOnly aria-label="clear decoded and encoded" />
|
||||||
);
|
);
|
||||||
|
|
||||||
const decodedControl = (
|
const decodedControl = (
|
||||||
<ControlMenu list={[decodedPasteButton, decodedFileButton, decodedCopyButton, clearButton]} />
|
<ControlMenu list={[decodedPasteButton, decodedFileButton, decodedCopyButton, clearButton]} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const encodedControl = (
|
const encodedControl = (
|
||||||
<ControlMenu list={[encodedPasteButton, encodedFileButton, encodedCopyButton, clearButton]} />
|
<ControlMenu list={[encodedPasteButton, encodedFileButton, encodedCopyButton, clearButton]} />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import { toolGroups } from "@/config/tools";
|
|||||||
import { decode } from "@/lib/jwt";
|
import { decode } from "@/lib/jwt";
|
||||||
import { Editor } from "@/components/ui/editor";
|
import { Editor } from "@/components/ui/editor";
|
||||||
import { Textarea, TextareaProps } from "@/components/ui/textarea";
|
import { Textarea, TextareaProps } from "@/components/ui/textarea";
|
||||||
import { ClearButton } from "@/components/buttons/clear";
|
import * as Button from "@/components/buttons";
|
||||||
import { CopyButton } from "@/components/buttons/copy";
|
|
||||||
import { FileButton } from "@/components/buttons/file";
|
|
||||||
import { PasteButton } from "@/components/buttons/paste";
|
|
||||||
import { ControlMenu } from "@/components/control-menu";
|
import { ControlMenu } from "@/components/control-menu";
|
||||||
import { PageRootSection } from "@/components/page-root-section";
|
import { PageRootSection } from "@/components/page-root-section";
|
||||||
import { PageSection } from "@/components/page-section";
|
import { PageSection } from "@/components/page-section";
|
||||||
@@ -20,23 +17,23 @@ export default function Page() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { header: h, payload: p } = decode(jwt);
|
const { header: h, payload: p } = decode(jwt);
|
||||||
const header = h.isErr() ? "" : JSON.stringify(h.value, null, 2);
|
const header = h.map(x => JSON.stringify(x, null, 2)).unwrapOr("");
|
||||||
const payload = p.isErr() ? "" : JSON.stringify(p.value, null, 2);
|
const payload = p.map(x => JSON.stringify(x, null, 2)).unwrapOr("");
|
||||||
|
|
||||||
const clearJwt = useCallback(() => setJwt(""), []);
|
const clearJwt = useCallback(() => setJwt(""), []);
|
||||||
|
|
||||||
const onJwtChange: TextareaProps["onChange"] = ({ currentTarget: { value } }) => setJwt(value);
|
const onJwtChange: TextareaProps["onChange"] = e => setJwt(e.currentTarget.value);
|
||||||
|
|
||||||
const jwtTokenPasteButton = <PasteButton onClipboardRead={setJwt} />;
|
const jwtTokenPasteButton = <Button.Paste onClipboardRead={setJwt} />;
|
||||||
|
|
||||||
const jwtTokenFileButton = (
|
const jwtTokenFileButton = (
|
||||||
<FileButton onFileRead={setJwt} iconOnly aria-label="load a token file" />
|
<Button.File onFileRead={setJwt} iconOnly aria-label="load a token file" />
|
||||||
);
|
);
|
||||||
|
|
||||||
const jwtTokenClearButton = <ClearButton onClick={clearJwt} iconOnly aria-label="clear token" />;
|
const jwtTokenClearButton = <Button.Clear onClick={clearJwt} iconOnly aria-label="clear token" />;
|
||||||
|
|
||||||
const heaederCopyButton = <CopyButton text={header} />;
|
const heaederCopyButton = <Button.Copy text={header} />;
|
||||||
const payloadCopyButton = <CopyButton text={payload} />;
|
const payloadCopyButton = <Button.Copy text={payload} />;
|
||||||
|
|
||||||
const jwtTokenControl = (
|
const jwtTokenControl = (
|
||||||
<ControlMenu list={[jwtTokenPasteButton, jwtTokenFileButton, jwtTokenClearButton]} />
|
<ControlMenu list={[jwtTokenPasteButton, jwtTokenFileButton, jwtTokenClearButton]} />
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ import { useCallback, useState } from "react";
|
|||||||
import { toolGroups } from "@/config/tools";
|
import { toolGroups } from "@/config/tools";
|
||||||
import { safeDecodeURIComponent, safeEncodeURIComponent } from "@/lib/uri";
|
import { safeDecodeURIComponent, safeEncodeURIComponent } from "@/lib/uri";
|
||||||
import { Textarea, TextareaProps } from "@/components/ui/textarea";
|
import { Textarea, TextareaProps } from "@/components/ui/textarea";
|
||||||
import { ClearButton } from "@/components/buttons/clear";
|
import * as Button from "@/components/buttons";
|
||||||
import { CopyButton } from "@/components/buttons/copy";
|
|
||||||
import { FileButton } from "@/components/buttons/file";
|
|
||||||
import { PasteButton } from "@/components/buttons/paste";
|
|
||||||
import { ControlMenu } from "@/components/control-menu";
|
import { ControlMenu } from "@/components/control-menu";
|
||||||
import { PageRootSection } from "@/components/page-root-section";
|
import { PageRootSection } from "@/components/page-root-section";
|
||||||
import { PageSection } from "@/components/page-section";
|
import { PageSection } from "@/components/page-section";
|
||||||
@@ -19,17 +16,17 @@ export default function Page() {
|
|||||||
encoded: "%3E%20It's%20%22URL%20encoding%22%3F",
|
encoded: "%3E%20It's%20%22URL%20encoding%22%3F",
|
||||||
});
|
});
|
||||||
|
|
||||||
const setDecodedReactively = useCallback((text: string) => {
|
const setFormByDecoded = useCallback((text: string) => {
|
||||||
setForm({
|
setForm({
|
||||||
decoded: text,
|
decoded: text,
|
||||||
encoded: safeEncodeURIComponent(text).unwrapOr(""),
|
encoded: safeEncodeURIComponent(text).unwrapOr(""),
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setEncodedReactively = useCallback((text: string) => {
|
const setFormByEncoded = useCallback((text: string) => {
|
||||||
setForm({
|
setForm({
|
||||||
encoded: text,
|
|
||||||
decoded: safeDecodeURIComponent(text).unwrapOr(""),
|
decoded: safeDecodeURIComponent(text).unwrapOr(""),
|
||||||
|
encoded: text,
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -40,33 +37,29 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onDecodedChange: TextareaProps["onChange"] = ({ currentTarget: { value } }) =>
|
const onDecodedChange: TextareaProps["onChange"] = e => setFormByDecoded(e.currentTarget.value);
|
||||||
setDecodedReactively(value);
|
const onEncodedChange: TextareaProps["onChange"] = e => setFormByEncoded(e.currentTarget.value);
|
||||||
|
|
||||||
const onEncodedChange: TextareaProps["onChange"] = ({ currentTarget: { value } }) =>
|
const decodedPasteButton = <Button.Paste onClipboardRead={setFormByDecoded} />;
|
||||||
setEncodedReactively(value);
|
const encodedPasteButton = <Button.Paste onClipboardRead={setFormByEncoded} />;
|
||||||
|
|
||||||
const decodedPasteButton = <PasteButton onClipboardRead={setDecodedReactively} />;
|
|
||||||
const encodedPasteButton = <PasteButton onClipboardRead={setEncodedReactively} />;
|
|
||||||
|
|
||||||
const decodedFileButton = (
|
const decodedFileButton = (
|
||||||
<FileButton onFileRead={setDecodedReactively} iconOnly aria-label="load a decoded file" />
|
<Button.File onFileRead={setFormByDecoded} iconOnly aria-label="load a decoded file" />
|
||||||
);
|
);
|
||||||
const encodedFileButton = (
|
const encodedFileButton = (
|
||||||
<FileButton onFileRead={setEncodedReactively} iconOnly aria-label="load a encoded file" />
|
<Button.File onFileRead={setFormByEncoded} iconOnly aria-label="load a encoded file" />
|
||||||
);
|
);
|
||||||
|
|
||||||
const decodedCopyButton = <CopyButton text={form.decoded} />;
|
const decodedCopyButton = <Button.Copy text={form.decoded} />;
|
||||||
const encodedCopyButton = <CopyButton text={form.encoded} />;
|
const encodedCopyButton = <Button.Copy text={form.encoded} />;
|
||||||
|
|
||||||
const clearButton = (
|
const clearButton = (
|
||||||
<ClearButton onClick={clearBoth} iconOnly aria-label="clear decoded and encoded" />
|
<Button.Clear onClick={clearBoth} iconOnly aria-label="clear decoded and encoded" />
|
||||||
);
|
);
|
||||||
|
|
||||||
const decodedControl = (
|
const decodedControl = (
|
||||||
<ControlMenu list={[decodedPasteButton, decodedFileButton, decodedCopyButton, clearButton]} />
|
<ControlMenu list={[decodedPasteButton, decodedFileButton, decodedCopyButton, clearButton]} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const encodedControl = (
|
const encodedControl = (
|
||||||
<ControlMenu list={[encodedPasteButton, encodedFileButton, encodedCopyButton, clearButton]} />
|
<ControlMenu list={[encodedPasteButton, encodedFileButton, encodedCopyButton, clearButton]} />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,17 +5,8 @@ import { useCallback, useState } from "react";
|
|||||||
import { toolGroups } from "@/config/tools";
|
import { toolGroups } from "@/config/tools";
|
||||||
import { safeJsonParse } from "@/lib/json";
|
import { safeJsonParse } from "@/lib/json";
|
||||||
import { Editor, EditorProps } from "@/components/ui/editor";
|
import { Editor, EditorProps } from "@/components/ui/editor";
|
||||||
import {
|
import * as Select from "@/components/ui/select";
|
||||||
Select,
|
import * as Button from "@/components/buttons";
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { ClearButton } from "@/components/buttons/clear";
|
|
||||||
import { CopyButton } from "@/components/buttons/copy";
|
|
||||||
import { FileButton } from "@/components/buttons/file";
|
|
||||||
import { PasteButton } from "@/components/buttons/paste";
|
|
||||||
import { Configuration } from "@/components/configuration";
|
import { Configuration } from "@/components/configuration";
|
||||||
import { Configurations } from "@/components/configurations";
|
import { Configurations } from "@/components/configurations";
|
||||||
import { ControlMenu } from "@/components/control-menu";
|
import { ControlMenu } from "@/components/control-menu";
|
||||||
@@ -35,7 +26,7 @@ export default function Page() {
|
|||||||
const [input, setInput] = useState('{\n"foo":"bar"\n}');
|
const [input, setInput] = useState('{\n"foo":"bar"\n}');
|
||||||
|
|
||||||
const parsed = safeJsonParse(input);
|
const parsed = safeJsonParse(input);
|
||||||
const output = parsed.isErr() ? "" : JSON.stringify(parsed.value, null, indentation);
|
const output = parsed.map(x => JSON.stringify(x, null, indentation)).unwrapOr("");
|
||||||
|
|
||||||
const clearInput = useCallback(() => setInput(""), []);
|
const clearInput = useCallback(() => setInput(""), []);
|
||||||
|
|
||||||
@@ -46,33 +37,33 @@ export default function Page() {
|
|||||||
icon={<icons.Space size={24} className="-translate-y-1.5" />}
|
icon={<icons.Space size={24} className="-translate-y-1.5" />}
|
||||||
title="Indentation"
|
title="Indentation"
|
||||||
control={
|
control={
|
||||||
<Select value={indentation} onValueChange={setIndentation}>
|
<Select.Root value={indentation} onValueChange={setIndentation}>
|
||||||
<SelectTrigger
|
<Select.Trigger
|
||||||
className="w-28"
|
className="w-28"
|
||||||
aria-label="toggle open/close state of indentation selection"
|
aria-label="toggle open/close state of indentation selection"
|
||||||
>
|
>
|
||||||
<SelectValue placeholder={indentation} />
|
<Select.Value placeholder={indentation} />
|
||||||
</SelectTrigger>
|
</Select.Trigger>
|
||||||
<SelectContent>
|
<Select.Content>
|
||||||
<SelectItem value={indentations.two}>2 spaces</SelectItem>
|
<Select.Item value={indentations.two}>2 spaces</Select.Item>
|
||||||
<SelectItem value={indentations.four}>4 spaces</SelectItem>
|
<Select.Item value={indentations.four}>4 spaces</Select.Item>
|
||||||
<SelectItem value={indentations.tab}>1 tab</SelectItem>
|
<Select.Item value={indentations.tab}>1 tab</Select.Item>
|
||||||
<SelectItem value={indentations.zero}>minified</SelectItem>
|
<Select.Item value={indentations.zero}>minified</Select.Item>
|
||||||
</SelectContent>
|
</Select.Content>
|
||||||
</Select>
|
</Select.Root>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const inputPasteButton = <PasteButton onClipboardRead={setInput} />;
|
const inputPasteButton = <Button.Paste onClipboardRead={setInput} />;
|
||||||
|
|
||||||
const inputFileButton = (
|
const inputFileButton = (
|
||||||
<FileButton accept=".json" onFileRead={setInput} iconOnly aria-label="load a json file" />
|
<Button.File accept=".json" onFileRead={setInput} iconOnly aria-label="load a json file" />
|
||||||
);
|
);
|
||||||
|
|
||||||
const inputClearButton = <ClearButton onClick={clearInput} iconOnly aria-label="clear json" />;
|
const inputClearButton = <Button.Clear onClick={clearInput} iconOnly aria-label="clear json" />;
|
||||||
|
|
||||||
const outputCopyButton = <CopyButton text={output} />;
|
const outputCopyButton = <Button.Copy text={output} />;
|
||||||
|
|
||||||
const inputControl = <ControlMenu list={[inputPasteButton, inputFileButton, inputClearButton]} />;
|
const inputControl = <ControlMenu list={[inputPasteButton, inputFileButton, inputClearButton]} />;
|
||||||
const outputControl = <ControlMenu list={[outputCopyButton]} />;
|
const outputControl = <ControlMenu list={[outputCopyButton]} />;
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import createHash from "create-hash";
|
|||||||
import { toolGroups } from "@/config/tools";
|
import { toolGroups } from "@/config/tools";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea, TextareaProps } from "@/components/ui/textarea";
|
import { Textarea, TextareaProps } from "@/components/ui/textarea";
|
||||||
import { ClearButton } from "@/components/buttons/clear";
|
import * as Button from "@/components/buttons";
|
||||||
import { CopyButton } from "@/components/buttons/copy";
|
|
||||||
import { FileButton } from "@/components/buttons/file";
|
|
||||||
import { PasteButton } from "@/components/buttons/paste";
|
|
||||||
import { Configuration } from "@/components/configuration";
|
import { Configuration } from "@/components/configuration";
|
||||||
import { Configurations } from "@/components/configurations";
|
import { Configurations } from "@/components/configurations";
|
||||||
import { ControlMenu } from "@/components/control-menu";
|
import { ControlMenu } from "@/components/control-menu";
|
||||||
@@ -34,8 +31,7 @@ export default function Page() {
|
|||||||
|
|
||||||
const clearInput = useCallback(() => setInput(""), []);
|
const clearInput = useCallback(() => setInput(""), []);
|
||||||
|
|
||||||
const onInputChange: TextareaProps["onChange"] = ({ currentTarget: { value } }) =>
|
const onInputChange: TextareaProps["onChange"] = e => setInput(e.currentTarget.value);
|
||||||
setInput(value);
|
|
||||||
|
|
||||||
const uppercaseConfig = (
|
const uppercaseConfig = (
|
||||||
<Configuration
|
<Configuration
|
||||||
@@ -53,16 +49,20 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const inputPasteButton = <PasteButton onClipboardRead={setInput} />;
|
const inputPasteButton = <Button.Paste onClipboardRead={setInput} />;
|
||||||
const inputFileButton = <FileButton onFileRead={setInput} iconOnly aria-label="load a file" />;
|
const inputFileButton = <Button.File onFileRead={setInput} iconOnly aria-label="load a file" />;
|
||||||
const inputClearButton = <ClearButton onClick={clearInput} iconOnly aria-label="clear input" />;
|
const inputClearButton = <Button.Clear onClick={clearInput} iconOnly aria-label="clear input" />;
|
||||||
|
|
||||||
const inputControl = <ControlMenu list={[inputPasteButton, inputFileButton, inputClearButton]} />;
|
const inputControl = <ControlMenu list={[inputPasteButton, inputFileButton, inputClearButton]} />;
|
||||||
|
|
||||||
const md5CopyButton = <CopyButton text={md5} iconOnly aria-label="copy generated md5" />;
|
const md5CopyButton = <Button.Copy text={md5} iconOnly aria-label="copy generated md5" />;
|
||||||
const sha1CopyButton = <CopyButton text={sha1} iconOnly aria-label="copy generated sha1" />;
|
const sha1CopyButton = <Button.Copy text={sha1} iconOnly aria-label="copy generated sha1" />;
|
||||||
const sha256CopyButton = <CopyButton text={sha256} iconOnly aria-label="copy generated sha256" />;
|
const sha256CopyButton = (
|
||||||
const sha512CopyButton = <CopyButton text={sha512} iconOnly aria-label="copy generated sha512" />;
|
<Button.Copy text={sha256} iconOnly aria-label="copy generated sha256" />
|
||||||
|
);
|
||||||
|
const sha512CopyButton = (
|
||||||
|
<Button.Copy text={sha512} iconOnly aria-label="copy generated sha512" />
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageRootSection title={toolGroups.generators.tools.hash.longTitle}>
|
<PageRootSection title={toolGroups.generators.tools.hash.longTitle}>
|
||||||
|
|||||||
@@ -9,17 +9,10 @@ import { uuid } from "@/lib/uuid";
|
|||||||
import { useScrollFollow } from "@/hooks/useScrollFollow";
|
import { useScrollFollow } from "@/hooks/useScrollFollow";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input, InputProps } from "@/components/ui/input";
|
import { Input, InputProps } from "@/components/ui/input";
|
||||||
import {
|
import * as Select from "@/components/ui/select";
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectProps,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { ClearButton } from "@/components/buttons/clear";
|
import { Clear } from "@/components/buttons/clear";
|
||||||
import { CopyButton } from "@/components/buttons/copy";
|
import { Copy } from "@/components/buttons/copy";
|
||||||
import { Configuration } from "@/components/configuration";
|
import { Configuration } from "@/components/configuration";
|
||||||
import { Configurations } from "@/components/configurations";
|
import { Configurations } from "@/components/configurations";
|
||||||
import { ControlMenu } from "@/components/control-menu";
|
import { ControlMenu } from "@/components/control-menu";
|
||||||
@@ -48,22 +41,19 @@ export default function Page() {
|
|||||||
|
|
||||||
const clearUuids = useCallback(() => setUuids([]), []);
|
const clearUuids = useCallback(() => setUuids([]), []);
|
||||||
|
|
||||||
const onUuidVersionChange: NonNullable<SelectProps["onValueChange"]> = useCallback(value => {
|
const onUuidVersionChange: NonNullable<Select.Props["onValueChange"]> = useCallback(value => {
|
||||||
if (uuidVersions.is(value)) {
|
if (uuidVersions.is(value)) {
|
||||||
setUuidVersion(value);
|
setUuidVersion(value);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onGeneratesChange: NonNullable<InputProps["onChange"]> = useCallback(
|
const onGeneratesChange: NonNullable<InputProps["onChange"]> = useCallback(e => {
|
||||||
({ currentTarget: { value } }) => {
|
const newGenerates = Number(e.currentTarget.value);
|
||||||
const newGenerates = Number(value);
|
|
||||||
|
|
||||||
if (newGenerates >= 1 && newGenerates <= 1000) {
|
if (newGenerates >= 1 && newGenerates <= 1000) {
|
||||||
setGenerates(newGenerates);
|
setGenerates(newGenerates);
|
||||||
}
|
}
|
||||||
},
|
}, []);
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onGenerateClick = () => {
|
const onGenerateClick = () => {
|
||||||
const newUuids = range(1, generates).map(_ => uuid(uuidVersion, hyphens, uppercase));
|
const newUuids = range(1, generates).map(_ => uuid(uuidVersion, hyphens, uppercase));
|
||||||
@@ -108,24 +98,24 @@ export default function Page() {
|
|||||||
title="UUID version"
|
title="UUID version"
|
||||||
description="Choose the version of UUID to generate"
|
description="Choose the version of UUID to generate"
|
||||||
control={
|
control={
|
||||||
<Select value={uuidVersion} onValueChange={onUuidVersionChange}>
|
<Select.Root value={uuidVersion} onValueChange={onUuidVersionChange}>
|
||||||
<SelectTrigger
|
<Select.Trigger
|
||||||
className="w-28"
|
className="w-28"
|
||||||
aria-label="toggle open/close state of uuid version selection"
|
aria-label="toggle open/close state of uuid version selection"
|
||||||
>
|
>
|
||||||
<SelectValue placeholder={uuidVersion} />
|
<Select.Value placeholder={uuidVersion} />
|
||||||
</SelectTrigger>
|
</Select.Trigger>
|
||||||
<SelectContent>
|
<Select.Content>
|
||||||
<SelectItem value={versions.v1}>1</SelectItem>
|
<Select.Item value={versions.v1}>1</Select.Item>
|
||||||
<SelectItem value={versions.v4}>4 (GUID)</SelectItem>
|
<Select.Item value={versions.v4}>4 (GUID)</Select.Item>
|
||||||
</SelectContent>
|
</Select.Content>
|
||||||
</Select>
|
</Select.Root>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const uuidsCopyButton = <CopyButton text={uuidsString} />;
|
const uuidsCopyButton = <Copy text={uuidsString} />;
|
||||||
const uuidsClearButton = <ClearButton onClick={clearUuids} iconOnly aria-label="clear uuids" />;
|
const uuidsClearButton = <Clear onClick={clearUuids} iconOnly aria-label="clear uuids" />;
|
||||||
|
|
||||||
const uuidsControl = <ControlMenu list={[uuidsCopyButton, uuidsClearButton]} />;
|
const uuidsControl = <ControlMenu list={[uuidsCopyButton, uuidsClearButton]} />;
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,7 @@
|
|||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
|
||||||
import { singleTools } from "@/config/tools";
|
import { singleTools } from "@/config/tools";
|
||||||
import {
|
import * as Select from "@/components/ui/select";
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Configuration } from "@/components/configuration";
|
import { Configuration } from "@/components/configuration";
|
||||||
import { Configurations } from "@/components/configurations";
|
import { Configurations } from "@/components/configurations";
|
||||||
import { icons } from "@/components/icons";
|
import { icons } from "@/components/icons";
|
||||||
@@ -25,19 +19,19 @@ export default function Page() {
|
|||||||
title="App theme"
|
title="App theme"
|
||||||
description="Select which app theme to display"
|
description="Select which app theme to display"
|
||||||
control={
|
control={
|
||||||
<Select value={theme} onValueChange={setTheme}>
|
<Select.Root value={theme} onValueChange={setTheme}>
|
||||||
<SelectTrigger
|
<Select.Trigger
|
||||||
className="w-28"
|
className="w-28"
|
||||||
aria-label="toggle open/close state of app theme selection"
|
aria-label="toggle open/close state of app theme selection"
|
||||||
>
|
>
|
||||||
<SelectValue placeholder={theme} />
|
<Select.Value placeholder={theme} />
|
||||||
</SelectTrigger>
|
</Select.Trigger>
|
||||||
<SelectContent>
|
<Select.Content>
|
||||||
<SelectItem value="light">Light</SelectItem>
|
<Select.Item value="light">Light</Select.Item>
|
||||||
<SelectItem value="dark">Dark</SelectItem>
|
<Select.Item value="dark">Dark</Select.Item>
|
||||||
<SelectItem value="system">System</SelectItem>
|
<Select.Item value="system">System</Select.Item>
|
||||||
</SelectContent>
|
</Select.Content>
|
||||||
</Select>
|
</Select.Root>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Button, ButtonProps } from "@/components/ui/button";
|
import { Button, ButtonProps } from "@/components/ui/button";
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
|
||||||
export type BaseButtonProps = ButtonProps & {
|
export type BaseProps = ButtonProps & {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
iconOnly?: true;
|
iconOnly?: true;
|
||||||
labelText: string;
|
labelText: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function BaseButton({ icon, iconOnly, labelText, ...props }: BaseButtonProps) {
|
export function Base({ icon, iconOnly, labelText, ...props }: BaseProps) {
|
||||||
const button = (
|
const button = (
|
||||||
<Button className="w-fit border" {...props}>
|
<Button className="w-fit border" {...props}>
|
||||||
{icon}
|
{icon}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import equal from "react-fast-compare";
|
|||||||
|
|
||||||
import { icons } from "@/components/icons";
|
import { icons } from "@/components/icons";
|
||||||
|
|
||||||
import { BaseButton, BaseButtonProps } from "./base";
|
import { Base, BaseProps } from "./base";
|
||||||
|
|
||||||
export type ClearButtonProps = Omit<BaseButtonProps, "icon" | "labelText">;
|
export type ClearProps = Omit<BaseProps, "icon" | "labelText">;
|
||||||
|
|
||||||
function RawClearButton({ iconOnly, ...props }: ClearButtonProps) {
|
function RawClear({ iconOnly, ...props }: ClearProps) {
|
||||||
return <BaseButton {...props} icon={<icons.X size={16} />} {...{ iconOnly }} labelText="Clear" />;
|
return <Base {...props} icon={<icons.X size={16} />} {...{ iconOnly }} labelText="Clear" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClearButton = memo(RawClearButton, equal);
|
export const Clear = memo(RawClear, equal);
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import equal from "react-fast-compare";
|
|||||||
|
|
||||||
import { icons } from "@/components/icons";
|
import { icons } from "@/components/icons";
|
||||||
|
|
||||||
import { BaseButton, BaseButtonProps } from "./base";
|
import { Base, BaseProps } from "./base";
|
||||||
|
|
||||||
export type CopyButtonProps = Omit<BaseButtonProps, "icon" | "labelText" | "onClick"> & {
|
export type CopyProps = Omit<BaseProps, "icon" | "labelText" | "onClick"> & {
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function RawCopyButton({ text, iconOnly, ...props }: CopyButtonProps) {
|
function RawButton({ text, iconOnly, ...props }: CopyProps) {
|
||||||
const onClick: BaseButtonProps["onClick"] = useCallback(() => {
|
const onClick: BaseProps["onClick"] = useCallback(() => {
|
||||||
navigator.clipboard.writeText(text).catch(e => {
|
navigator.clipboard.writeText(text).catch(e => {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
@@ -20,13 +20,8 @@ function RawCopyButton({ text, iconOnly, ...props }: CopyButtonProps) {
|
|||||||
}, [text]);
|
}, [text]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseButton
|
<Base {...props} icon={<icons.Copy size={16} />} {...{ iconOnly, onClick }} labelText="Copy" />
|
||||||
{...props}
|
|
||||||
icon={<icons.Copy size={16} />}
|
|
||||||
{...{ iconOnly, onClick }}
|
|
||||||
labelText="Copy"
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CopyButton = memo(RawCopyButton, equal);
|
export const Copy = memo(RawButton, equal);
|
||||||
|
|||||||
@@ -3,30 +3,24 @@ import equal from "react-fast-compare";
|
|||||||
|
|
||||||
import { icons } from "@/components/icons";
|
import { icons } from "@/components/icons";
|
||||||
|
|
||||||
import { BaseButton, BaseButtonProps } from "./base";
|
import { Base, BaseProps } from "./base";
|
||||||
|
|
||||||
type InputProps = React.ComponentProps<"input">;
|
type InputProps = React.ComponentProps<"input">;
|
||||||
|
|
||||||
export type FileButtonProps = Pick<InputProps, "accept"> &
|
export type FileProps = Pick<InputProps, "accept"> &
|
||||||
Omit<BaseButtonProps, "icon" | "labelText" | "onClick"> & {
|
Omit<BaseProps, "icon" | "labelText" | "onClick"> & {
|
||||||
maxFileSizeMb?: number;
|
maxFileSizeMb?: number;
|
||||||
onFileRead: (text: string) => void;
|
onFileRead: (text: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RawFileButton({
|
export function RawFile({ accept, iconOnly, maxFileSizeMb = 20, onFileRead, ...props }: FileProps) {
|
||||||
accept,
|
|
||||||
iconOnly,
|
|
||||||
maxFileSizeMb = 20,
|
|
||||||
onFileRead,
|
|
||||||
...props
|
|
||||||
}: FileButtonProps) {
|
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const onClick = () => ref.current?.click();
|
const onClick = () => ref.current?.click();
|
||||||
|
|
||||||
const onChange: NonNullable<InputProps["onChange"]> = useCallback(
|
const onChange: NonNullable<InputProps["onChange"]> = useCallback(
|
||||||
({ currentTarget }) => {
|
e => {
|
||||||
const file = Array.from(currentTarget.files ?? []).at(0);
|
const file = Array.from(e.currentTarget.files ?? []).at(0);
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return;
|
return;
|
||||||
@@ -51,14 +45,14 @@ export function RawFileButton({
|
|||||||
|
|
||||||
// clear selected file to accept the same file again
|
// clear selected file to accept the same file again
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
currentTarget.value = "";
|
e.currentTarget.value = "";
|
||||||
},
|
},
|
||||||
[maxFileSizeMb, onFileRead]
|
[maxFileSizeMb, onFileRead]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BaseButton
|
<Base
|
||||||
{...props}
|
{...props}
|
||||||
icon={<icons.File size={16} />}
|
icon={<icons.File size={16} />}
|
||||||
{...{ iconOnly, onClick }}
|
{...{ iconOnly, onClick }}
|
||||||
@@ -69,4 +63,4 @@ export function RawFileButton({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FileButton = memo(RawFileButton, equal);
|
export const File = memo(RawFile, equal);
|
||||||
|
|||||||
4
components/buttons/index.ts
Normal file
4
components/buttons/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./clear";
|
||||||
|
export * from "./copy";
|
||||||
|
export * from "./file";
|
||||||
|
export * from "./paste";
|
||||||
@@ -3,14 +3,14 @@ import equal from "react-fast-compare";
|
|||||||
|
|
||||||
import { icons } from "@/components/icons";
|
import { icons } from "@/components/icons";
|
||||||
|
|
||||||
import { BaseButton, BaseButtonProps } from "./base";
|
import { Base, BaseProps } from "./base";
|
||||||
|
|
||||||
export type PasteButtonProps = Omit<BaseButtonProps, "icon" | "labelText" | "onClick"> & {
|
export type PasteProps = Omit<BaseProps, "icon" | "labelText" | "onClick"> & {
|
||||||
onClipboardRead: (text: string) => void;
|
onClipboardRead: (text: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RawPasteButton({ iconOnly, onClipboardRead, ...props }: PasteButtonProps) {
|
export function RawPaste({ iconOnly, onClipboardRead, ...props }: PasteProps) {
|
||||||
const onClick: BaseButtonProps["onClick"] = useCallback(() => {
|
const onClick: BaseProps["onClick"] = useCallback(() => {
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.readText()
|
.readText()
|
||||||
.then(onClipboardRead)
|
.then(onClipboardRead)
|
||||||
@@ -23,7 +23,7 @@ export function RawPasteButton({ iconOnly, onClipboardRead, ...props }: PasteBut
|
|||||||
}, [onClipboardRead]);
|
}, [onClipboardRead]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseButton
|
<Base
|
||||||
{...props}
|
{...props}
|
||||||
icon={<icons.Clipboard size={16} />}
|
icon={<icons.Clipboard size={16} />}
|
||||||
{...{ iconOnly, onClick }}
|
{...{ iconOnly, onClick }}
|
||||||
@@ -32,4 +32,4 @@ export function RawPasteButton({ iconOnly, onClipboardRead, ...props }: PasteBut
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PasteButton = memo(RawPasteButton, equal);
|
export const Paste = memo(RawPaste, equal);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function SearchBar() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeText: InputProps["onChange"] = ({ currentTarget }) => setText(currentTarget.value);
|
const changeText: InputProps["onChange"] = e => setText(e.currentTarget.value);
|
||||||
|
|
||||||
const searchIfEnter: InputProps["onKeyDown"] = ({ code }) => {
|
const searchIfEnter: InputProps["onKeyDown"] = ({ code }) => {
|
||||||
if (code === "Enter") {
|
if (code === "Enter") {
|
||||||
|
|||||||
@@ -7,13 +7,11 @@ import { cn } from "@/lib/style";
|
|||||||
import { icons } from "@/components/icons";
|
import { icons } from "@/components/icons";
|
||||||
import { Indicator } from "@/components/indicator";
|
import { Indicator } from "@/components/indicator";
|
||||||
|
|
||||||
export type SelectProps = React.ComponentPropsWithoutRef<typeof Select>;
|
export type Props = React.ComponentPropsWithoutRef<typeof SelectPrimitive.Root>;
|
||||||
|
|
||||||
export const Select = SelectPrimitive.Root;
|
export const { Root, Group, Value } = SelectPrimitive;
|
||||||
export const SelectGroup = SelectPrimitive.Group;
|
|
||||||
export const SelectValue = SelectPrimitive.Value;
|
|
||||||
|
|
||||||
export const SelectTrigger = React.forwardRef<
|
export const Trigger = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, children, ...props }, ref) => (
|
||||||
@@ -34,9 +32,9 @@ export const SelectTrigger = React.forwardRef<
|
|||||||
</SelectPrimitive.Icon>
|
</SelectPrimitive.Icon>
|
||||||
</SelectPrimitive.Trigger>
|
</SelectPrimitive.Trigger>
|
||||||
));
|
));
|
||||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
Trigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
export const SelectContent = React.forwardRef<
|
export const Content = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||||
@@ -62,9 +60,9 @@ export const SelectContent = React.forwardRef<
|
|||||||
</SelectPrimitive.Content>
|
</SelectPrimitive.Content>
|
||||||
</SelectPrimitive.Portal>
|
</SelectPrimitive.Portal>
|
||||||
));
|
));
|
||||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
Content.displayName = SelectPrimitive.Content.displayName;
|
||||||
|
|
||||||
export const SelectLabel = React.forwardRef<
|
export const Label = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
@@ -74,9 +72,9 @@ export const SelectLabel = React.forwardRef<
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
Label.displayName = SelectPrimitive.Label.displayName;
|
||||||
|
|
||||||
export const SelectItem = React.forwardRef<
|
export const Item = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, children, ...props }, ref) => (
|
||||||
@@ -98,9 +96,9 @@ export const SelectItem = React.forwardRef<
|
|||||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
</SelectPrimitive.Item>
|
</SelectPrimitive.Item>
|
||||||
));
|
));
|
||||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
Item.displayName = SelectPrimitive.Item.displayName;
|
||||||
|
|
||||||
export const SelectSeparator = React.forwardRef<
|
export const Separator = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
@@ -110,4 +108,4 @@ export const SelectSeparator = React.forwardRef<
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
Separator.displayName = SelectPrimitive.Separator.displayName;
|
||||||
|
|||||||
Reference in New Issue
Block a user