refactor: rename, reorder, etc.

This commit is contained in:
rusconn
2023-07-10 11:56:45 +09:00
parent e2a558f9e9
commit ce35eb910e
18 changed files with 211 additions and 280 deletions

View File

@@ -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]} />
); );

View File

@@ -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]} />;

View File

@@ -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]} />
); );

View File

@@ -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: "&gt; It&#39;s &quot;HTML escaping&quot;.", encoded: "&gt; It&#39;s &quot;HTML escaping&quot;.",
}); });
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]} />
); );

View File

@@ -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]} />

View File

@@ -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]} />
); );

View File

@@ -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]} />;

View File

@@ -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}>

View File

@@ -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]} />;

View File

@@ -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>
} }
/> />
); );

View File

@@ -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}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -0,0 +1,4 @@
export * from "./clear";
export * from "./copy";
export * from "./file";
export * from "./paste";

View File

@@ -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);

View File

@@ -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") {

View File

@@ -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;