Refactor proxy codebase

This commit is contained in:
Gregory Schier
2026-03-12 08:31:05 -07:00
parent 4968237ece
commit 5e3ef70d93
21 changed files with 437 additions and 408 deletions

View File

@@ -0,0 +1,95 @@
import classNames from "classnames";
import type { MouseEvent } from "react";
import { forwardRef, useCallback } from "react";
import { useTimedBoolean } from "../hooks/useTimedBoolean";
import type { ButtonProps } from "./Button";
import { Button } from "./Button";
import type { IconProps } from "./Icon";
import { Icon } from "./Icon";
import { LoadingIcon } from "./LoadingIcon";
export type IconButtonProps = IconProps &
ButtonProps & {
showConfirm?: boolean;
iconClassName?: string;
iconSize?: IconProps["size"];
iconColor?: IconProps["color"];
title: string;
showBadge?: boolean;
};
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(
{
showConfirm,
icon,
color = "default",
spin,
onClick,
className,
iconClassName,
tabIndex,
size = "md",
iconSize,
showBadge,
iconColor,
isLoading,
type = "button",
...props
}: IconButtonProps,
ref,
) {
const [confirmed, setConfirmed] = useTimedBoolean();
const handleClick = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
if (showConfirm) setConfirmed();
onClick?.(e);
},
[onClick, setConfirmed, showConfirm],
);
return (
<Button
ref={ref}
aria-hidden={icon === "empty"}
disabled={icon === "empty"}
tabIndex={(tabIndex ?? icon === "empty") ? -1 : undefined}
onClick={handleClick}
innerClassName="flex items-center justify-center"
size={size}
color={color}
type={type}
className={classNames(
className,
"group/button relative flex-shrink-0",
"!px-0",
size === "md" && "w-md",
size === "sm" && "w-sm",
size === "xs" && "w-xs",
size === "2xs" && "w-5",
)}
{...props}
>
{showBadge && (
<div className="absolute top-0 right-0 w-1/2 h-1/2 flex items-center justify-center">
<div className="w-2.5 h-2.5 bg-pink-500 rounded-full" />
</div>
)}
{isLoading ? (
<LoadingIcon size={iconSize} className={iconClassName} />
) : (
<Icon
size={iconSize}
icon={confirmed ? "check" : icon}
spin={spin}
color={iconColor}
className={classNames(
iconClassName,
"group-hover/button:text-text",
confirmed && "!text-success",
props.disabled && "opacity-70",
)}
/>
)}
</Button>
);
});

View File

@@ -0,0 +1,19 @@
import { useRef, useState } from 'react';
import { useUnmount } from 'react-use';
/** Returns a boolean that is true for a given number of milliseconds. */
export function useTimedBoolean(millis = 1500): [boolean, () => void] {
const [value, setValue] = useState(false);
const timeout = useRef<NodeJS.Timeout | null>(null);
const reset = () => timeout.current && clearTimeout(timeout.current);
useUnmount(reset);
const setToTrue = () => {
setValue(true);
reset();
timeout.current = setTimeout(() => setValue(false), millis);
};
return [value, setToTrue];
}

View File

@@ -3,7 +3,10 @@ export type { ButtonProps } from "./components/Button";
export { HeaderSize } from "./components/HeaderSize";
export { Icon } from "./components/Icon";
export type { IconProps } from "./components/Icon";
export { IconButton } from "./components/IconButton";
export type { IconButtonProps } from "./components/IconButton";
export { LoadingIcon } from "./components/LoadingIcon";
export { useTimedBoolean } from "./hooks/useTimedBoolean";
export { WindowControls } from "./components/WindowControls";
export { useIsFullscreen } from "./hooks/useIsFullscreen";
export { useDebouncedValue } from "./hooks/useDebouncedValue";