refactor: memoize icons on export

This commit is contained in:
rusconn
2023-06-24 09:26:37 +09:00
parent 257080b00d
commit 240fba0e74
14 changed files with 74 additions and 101 deletions

View File

@@ -83,11 +83,9 @@ export default function Page() {
const onJsonChange: EditorProps["onChange"] = value => setJsonReactively(value ?? ""); const onJsonChange: EditorProps["onChange"] = value => setJsonReactively(value ?? "");
const onYamlChange: EditorProps["onChange"] = value => setYamlReactively(value ?? ""); const onYamlChange: EditorProps["onChange"] = value => setYamlReactively(value ?? "");
const indentationIcon = useMemo(() => <icons.Space size={24} className="-translate-y-1.5" />, []);
const indentationConfig = ( const indentationConfig = (
<Configuration <Configuration
icon={indentationIcon} icon={<icons.Space size={24} className="-translate-y-1.5" />}
title="Indentation" title="Indentation"
control={ control={
<Select value={form.indentation} onValueChange={onIndentationChange}> <Select value={form.indentation} onValueChange={onIndentationChange}>

View File

@@ -58,12 +58,10 @@ export default function Page() {
const onOctChange: InputProps["onChange"] = ({ currentTarget: { value } }) => trySetOct(value); const onOctChange: InputProps["onChange"] = ({ currentTarget: { value } }) => trySetOct(value);
const onBinChange: InputProps["onChange"] = ({ currentTarget: { value } }) => trySetBin(value); const onBinChange: InputProps["onChange"] = ({ currentTarget: { value } }) => trySetBin(value);
const formatNumberIcon = useMemo(() => <icons.CaseSensitive size={24} />, []);
const formatNumberConfig = useMemo( const formatNumberConfig = useMemo(
() => ( () => (
<Configuration <Configuration
icon={formatNumberIcon} icon={<icons.CaseSensitive size={24} />}
title="Format number" title="Format number"
control={ control={
<LabeledSwitch <LabeledSwitch
@@ -76,7 +74,7 @@ export default function Page() {
} }
/> />
), ),
[format, formatNumberIcon] [format]
); );
const decPasteButton = useMemo(() => <PasteButton onClipboardRead={trySetDec} />, [trySetDec]); const decPasteButton = useMemo(() => <PasteButton onClipboardRead={trySetDec} />, [trySetDec]);

View File

@@ -41,12 +41,10 @@ export default function Page() {
const onJsonChange: EditorProps["onChange"] = value => setInput(value ?? ""); const onJsonChange: EditorProps["onChange"] = value => setInput(value ?? "");
const indentationIcon = useMemo(() => <icons.Space size={24} className="-translate-y-1.5" />, []);
const indentationConfig = useMemo( const indentationConfig = useMemo(
() => ( () => (
<Configuration <Configuration
icon={indentationIcon} icon={<icons.Space size={24} className="-translate-y-1.5" />}
title="Indentation" title="Indentation"
control={ control={
<Select value={indentation} onValueChange={setIndentation}> <Select value={indentation} onValueChange={setIndentation}>
@@ -66,7 +64,7 @@ export default function Page() {
} }
/> />
), ),
[indentation, indentationIcon] [indentation]
); );
const inputPasteButton = useMemo(() => <PasteButton onClipboardRead={setInput} />, []); const inputPasteButton = useMemo(() => <PasteButton onClipboardRead={setInput} />, []);

View File

@@ -37,12 +37,10 @@ export default function Page() {
const onInputChange: TextareaProps["onChange"] = ({ currentTarget: { value } }) => const onInputChange: TextareaProps["onChange"] = ({ currentTarget: { value } }) =>
setInput(value); setInput(value);
const uppercaseIcon = useMemo(() => <icons.CaseSensitive size={24} />, []);
const uppercaseConfig = useMemo( const uppercaseConfig = useMemo(
() => ( () => (
<Configuration <Configuration
icon={uppercaseIcon} icon={<icons.CaseSensitive size={24} />}
title="Uppercase" title="Uppercase"
control={ control={
<LabeledSwitch <LabeledSwitch
@@ -55,7 +53,7 @@ export default function Page() {
} }
/> />
), ),
[uppercase, uppercaseIcon] [uppercase]
); );
const inputPasteButton = useMemo(() => <PasteButton onClipboardRead={setInput} />, []); const inputPasteButton = useMemo(() => <PasteButton onClipboardRead={setInput} />, []);

View File

@@ -20,12 +20,10 @@ import { PageSection } from "@/components/page-section";
export default function Page() { export default function Page() {
const { theme = "system", setTheme } = useTheme(); const { theme = "system", setTheme } = useTheme();
const appThemeIcon = useMemo(() => <icons.Paintbrush size={24} />, []);
const appThemeConfig = useMemo( const appThemeConfig = useMemo(
() => ( () => (
<Configuration <Configuration
icon={appThemeIcon} icon={<icons.Paintbrush size={24} />}
title="App theme" title="App theme"
description="Select which app theme to display" description="Select which app theme to display"
control={ control={
@@ -45,7 +43,7 @@ export default function Page() {
} }
/> />
), ),
[appThemeIcon, setTheme, theme] [setTheme, theme]
); );
return ( return (

View File

@@ -1,5 +1,3 @@
import { useMemo } from "react";
import { icons } from "@/components/icons"; import { icons } from "@/components/icons";
import { BaseButton, BaseButtonProps } from "./base"; import { BaseButton, BaseButtonProps } from "./base";
@@ -7,7 +5,5 @@ import { BaseButton, BaseButtonProps } from "./base";
export type ClearButtonProps = Omit<BaseButtonProps, "icon" | "labelText">; export type ClearButtonProps = Omit<BaseButtonProps, "icon" | "labelText">;
export function ClearButton({ iconOnly, ...props }: ClearButtonProps) { export function ClearButton({ iconOnly, ...props }: ClearButtonProps) {
const icon = useMemo(() => <icons.X size={16} />, []); return <BaseButton {...props} icon={<icons.X size={16} />} {...{ iconOnly }} labelText="Clear" />;
return <BaseButton {...props} {...{ icon, iconOnly }} labelText="Clear" />;
} }

View File

@@ -1,4 +1,4 @@
import { useCallback, useMemo } from "react"; import { useCallback } from "react";
import { icons } from "@/components/icons"; import { icons } from "@/components/icons";
@@ -18,7 +18,12 @@ export function CopyButton({ text, iconOnly, ...props }: CopyButtonProps) {
}); });
}, [text]); }, [text]);
const icon = useMemo(() => <icons.Copy size={16} />, []); return (
<BaseButton
return <BaseButton {...props} {...{ icon, iconOnly, onClick }} labelText="Copy" />; {...props}
icon={<icons.Copy size={16} />}
{...{ iconOnly, onClick }}
labelText="Copy"
/>
);
} }

View File

@@ -1,4 +1,4 @@
import { useCallback, useMemo, useRef } from "react"; import { useCallback, useRef } from "react";
import { icons } from "@/components/icons"; import { icons } from "@/components/icons";
@@ -55,11 +55,14 @@ export function FileButton({
[maxFileSizeMb, onFileRead] [maxFileSizeMb, onFileRead]
); );
const icon = useMemo(() => <icons.File size={16} />, []);
return ( return (
<> <>
<BaseButton {...props} {...{ icon, iconOnly, onClick }} labelText="Load a file" /> <BaseButton
{...props}
icon={<icons.File size={16} />}
{...{ iconOnly, onClick }}
labelText="Load a file"
/>
<input hidden type="file" {...{ ref, accept, onChange }} /> <input hidden type="file" {...{ ref, accept, onChange }} />
</> </>
); );

View File

@@ -1,4 +1,4 @@
import { useCallback, useMemo } from "react"; import { useCallback } from "react";
import { icons } from "@/components/icons"; import { icons } from "@/components/icons";
@@ -21,7 +21,12 @@ export function PasteButton({ iconOnly, onClipboardRead, ...props }: PasteButton
}); });
}, [onClipboardRead]); }, [onClipboardRead]);
const icon = useMemo(() => <icons.Clipboard size={16} />, []); return (
<BaseButton
return <BaseButton {...props} {...{ icon, iconOnly, onClick }} labelText="Paste" />; {...props}
icon={<icons.Clipboard size={16} />}
{...{ iconOnly, onClick }}
labelText="Paste"
/>
);
} }

View File

@@ -1,3 +1,4 @@
import { memo, MemoExoticComponent } from "react";
import { import {
AlignLeft, AlignLeft,
ArrowRightLeft, ArrowRightLeft,
@@ -29,37 +30,38 @@ import {
X, X,
type Icon as LucideIcon, type Icon as LucideIcon,
} from "lucide-react"; } from "lucide-react";
import equal from "react-fast-compare";
export type Icon = LucideIcon; export type Icon = LucideIcon | MemoExoticComponent<LucideIcon>;
export const icons = { export const icons = {
AlignLeft, AlignLeft: memo(AlignLeft, equal),
ArrowRightLeft, ArrowRightLeft: memo(ArrowRightLeft, equal),
Binary, Binary: memo(Binary, equal),
Braces, Braces: memo(Braces, equal),
CaseSensitive, CaseSensitive: memo(CaseSensitive, equal),
Check, Check: memo(Check, equal),
ChevronDown, ChevronDown: memo(ChevronDown, equal),
Clipboard, Clipboard: memo(Clipboard, equal),
Code: Code2, Code: memo(Code2, equal),
Copy, Copy: memo(Copy, equal),
Equal, Equal: memo(Equal, equal),
File: FileIcon, File: memo(FileIcon, equal),
Fingerprint, Fingerprint: memo(Fingerprint, equal),
Hash, Hash: memo(Hash, equal),
Home, Home: memo(Home, equal),
Key, Key: memo(Key, equal),
Link: Link2, Link: memo(Link2, equal),
PackagePlus, PackagePlus: memo(PackagePlus, equal),
Paintbrush: Paintbrush2, Paintbrush: memo(Paintbrush2, equal),
Search, Search: memo(Search, equal),
Settings, Settings: memo(Settings, equal),
Settings2, Settings2: memo(Settings2, equal),
Space, Space: memo(Space, equal),
Sun: SunMedium, Sun: memo(SunMedium, equal),
Minus, Minus: memo(Minus, equal),
Moon, Moon: memo(Moon, equal),
X, X: memo(X, equal),
GitHub: (props: LucideProps) => ( GitHub: (props: LucideProps) => (
<svg viewBox="0 0 438.549 438.549" {...props}> <svg viewBox="0 0 438.549 438.549" {...props}>
<path <path

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useSetSearchText } from "@/contexts/search-text"; import { useSetSearchText } from "@/contexts/search-text";
@@ -40,13 +40,6 @@ export function SearchBar() {
inputRef.current?.focus(); inputRef.current?.focus();
}; };
const clearIcon = useMemo(() => <icons.X className="p-1 text-muted-foreground" />, []);
const searchIcon = useMemo(
() => <icons.Search className="-scale-x-100 p-1 text-muted-foreground" />,
[]
);
return ( return (
<div className="relative flex w-full items-center"> <div className="relative flex w-full items-center">
<Input <Input
@@ -59,11 +52,11 @@ export function SearchBar() {
/> />
<div className="absolute right-1 flex gap-1"> <div className="absolute right-1 flex gap-1">
<Button className={cn("h-6 p-0", !text && "hidden")} variant="ghost" onClick={clearText}> <Button className={cn("h-6 p-0", !text && "hidden")} variant="ghost" onClick={clearText}>
{clearIcon} <icons.X className="p-1 text-muted-foreground" />
<span className="sr-only">Clear search text</span> <span className="sr-only">Clear search text</span>
</Button> </Button>
<Button className="h-6 p-0" variant="ghost" onClick={search} aria-label="search"> <Button className="h-6 p-0" variant="ghost" onClick={search} aria-label="search">
{searchIcon} <icons.Search className="-scale-x-100 p-1 text-muted-foreground" />
<span className="sr-only">Search tools</span> <span className="sr-only">Search tools</span>
</Button> </Button>
</div> </div>

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useCallback, useMemo, useRef } from "react"; import { useCallback, useRef } from "react";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import * as Accordion from "@radix-ui/react-accordion"; import * as Accordion from "@radix-ui/react-accordion";
@@ -20,11 +20,6 @@ export function ToolGroup({ Icon, title, href, tools, isOpend }: Props) {
const onClick = useCallback(() => triggerRef.current?.click(), []); const onClick = useCallback(() => triggerRef.current?.click(), []);
const chevronIcon = useMemo(
() => <icons.ChevronDown className="h-4 w-4 transition-transform duration-200" />,
[]
);
return ( return (
<Accordion.AccordionItem value={href}> <Accordion.AccordionItem value={href}>
<Accordion.Header asChild> <Accordion.Header asChild>
@@ -46,7 +41,7 @@ export function ToolGroup({ Icon, title, href, tools, isOpend }: Props) {
className="absolute right-0 flex h-10 w-10 items-center justify-center rounded transition-all duration-0 hover:bg-accent [&[data-state=open]>svg]:rotate-180" className="absolute right-0 flex h-10 w-10 items-center justify-center rounded transition-all duration-0 hover:bg-accent [&[data-state=open]>svg]:rotate-180"
aria-label="toggle open/close state of the tool group" aria-label="toggle open/close state of the tool group"
> >
{chevronIcon} <icons.ChevronDown className="h-4 w-4 transition-transform duration-200" />
</Accordion.Trigger> </Accordion.Trigger>
</div> </div>
</Accordion.Header> </Accordion.Header>

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { memo, useMemo } from "react"; import { memo } from "react";
import Link, { LinkProps } from "next/link"; import Link, { LinkProps } from "next/link";
import { Tool } from "@/config/tools"; import { Tool } from "@/config/tools";
@@ -13,8 +13,6 @@ type Props = Pick<Tool, "Icon" | "shortTitle"> &
}; };
function RawToolLink({ Icon, shortTitle: title, href, onClick, className, highlight }: Props) { function RawToolLink({ Icon, shortTitle: title, href, onClick, className, highlight }: Props) {
const icon = useMemo(() => <Icon size={16} />, [Icon]);
return ( return (
<Link <Link
className={cn( className={cn(
@@ -28,7 +26,7 @@ function RawToolLink({ Icon, shortTitle: title, href, onClick, className, highli
<Indicator /> <Indicator />
</span> </span>
<span className="flex select-none items-center"> <span className="flex select-none items-center">
{icon} <Icon size={16} />
<span className="ml-4">{title}</span> <span className="ml-4">{title}</span>
</span> </span>
</Link> </Link>

View File

@@ -1,6 +1,5 @@
"use client"; "use client";
import { useMemo } from "react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -9,27 +8,14 @@ import { icons } from "@/components/icons";
export function ThemeToggle() { export function ThemeToggle() {
const { resolvedTheme, setTheme } = useTheme(); const { resolvedTheme, setTheme } = useTheme();
const sunIcon = useMemo(
() => (
<icons.Sun className="h-7 w-7 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
),
[]
);
const moonIcon = useMemo(
() => (
<icons.Moon className="absolute h-7 w-7 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
),
[]
);
return ( return (
<Button <Button
className="h-10 w-10 p-0" className="h-10 w-10 p-0"
variant="ghost" variant="ghost"
onClick={() => setTheme(resolvedTheme === "light" ? "dark" : "light")} onClick={() => setTheme(resolvedTheme === "light" ? "dark" : "light")}
> >
{sunIcon} <icons.Sun className="h-7 w-7 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
{moonIcon} <icons.Moon className="absolute h-7 w-7 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span> <span className="sr-only">Toggle theme</span>
</Button> </Button>
); );