feat: cleanup button + add correct focus rings

This commit is contained in:
Aarnav Tale
2026-03-09 00:17:39 -04:00
parent 0ce411e92b
commit 37d0080cba
16 changed files with 94 additions and 81 deletions
+25 -13
View File
@@ -4,7 +4,7 @@ import { type AriaButtonOptions, useButton } from "react-aria";
import cn from "~/utils/cn";
export interface ButtonProps extends AriaButtonOptions<"button"> {
variant?: "heavy" | "light" | "danger";
variant?: "heavy" | "light" | "danger" | "ghost";
className?: string;
children?: React.ReactNode;
ref?: React.RefObject<HTMLButtonElement | null>;
@@ -20,22 +20,34 @@ export default function Button({ variant = "light", ...props }: ButtonProps) {
ref={ref}
{...buttonProps}
className={cn(
"w-fit text-sm rounded-md px-3 py-2",
"focus:outline-hidden focus:ring-3",
props.isDisabled && "opacity-60 cursor-not-allowed",
"w-fit rounded-lg px-3.5 py-2 text-sm leading-tight",
"transition-colors duration-100",
"focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
props.isDisabled && "pointer-events-none opacity-50",
...(variant === "heavy"
? [
"bg-mist-900 dark:bg-mist-50 font-semibold",
"hover:bg-mist-900/90 dark:hover:bg-mist-50/90",
"text-mist-200 dark:text-mist-800",
"bg-indigo-500 font-semibold text-white",
"hover:bg-indigo-500/90",
"dark:bg-indigo-500/90 dark:hover:bg-indigo-500/80",
]
: variant === "danger"
? ["bg-red-500 text-white font-semibold", "hover:bg-red-500/90"]
: [
"bg-mist-100 dark:bg-mist-800/50 font-medium",
"border border-mist-200 dark:border-mist-700",
"hover:bg-mist-200/90 dark:hover:bg-mist-700/50",
]),
? [
"bg-red-600 font-semibold text-white",
"hover:bg-red-600/90",
"dark:bg-red-500 dark:hover:bg-red-500/90",
]
: variant === "ghost"
? [
"font-medium text-indigo-600 dark:text-indigo-400",
"hover:bg-indigo-50 dark:hover:bg-indigo-500/10",
]
: [
"border border-mist-200 bg-white font-medium",
"hover:bg-mist-50",
"dark:border-mist-700 dark:bg-mist-800/50",
"dark:hover:bg-mist-700/50",
]),
props.className,
)}
>
+2 -1
View File
@@ -42,7 +42,8 @@ export default function Footer({ url, debug, healthy }: FooterProps) {
tabIndex={0} // Allows keyboard focus
className={cn(
"blur-sm hover:blur-none focus:blur-none transition",
"focus:outline-hidden focus:ring-2 rounded-xs",
"focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1 rounded-xs",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
)}
>
{url}
+4 -2
View File
@@ -42,7 +42,8 @@ function TabLink({ name, to, icon }: TabLinkProps) {
"after:absolute after:bottom-0 after:left-3 after:right-3",
"after:h-0.5 after:bg-mist-900 dark:after:bg-mist-200",
"hover:bg-mist-200 dark:hover:bg-mist-900",
"focus:outline-hidden focus:ring-3",
"focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
isActive ? "after:visible" : "after:invisible",
)
}
@@ -60,7 +61,8 @@ function Link({ href, text }: LinkProps) {
<a
className={cn(
"hidden sm:block hover:underline text-sm",
"focus:outline-hidden focus:ring-3 rounded-md",
"focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1 rounded-md",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
)}
href={href}
rel="noreferrer"
+9 -10
View File
@@ -22,19 +22,18 @@ export default function IconButton({ variant = "light", ...props }: IconButtonPr
{...buttonProps}
aria-label={props.label}
className={cn(
"rounded-full flex items-center justify-center p-1",
"focus:outline-hidden focus:ring-3",
props.isDisabled && "opacity-60 cursor-not-allowed",
"flex items-center justify-center rounded-full p-1",
"transition-colors duration-100",
"focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
props.isDisabled && "pointer-events-none opacity-50",
...(variant === "heavy"
? [
"bg-mist-900 dark:bg-mist-50 font-semibold",
"hover:bg-mist-900/90 dark:hover:bg-mist-50/90",
"text-mist-200 dark:text-mist-800",
"bg-indigo-500 font-semibold text-white",
"hover:bg-indigo-500/90",
"dark:bg-indigo-500/90 dark:hover:bg-indigo-500/80",
]
: [
"bg-mist-100 dark:bg-mist-700/30 font-medium",
"hover:bg-mist-200/90 dark:hover:bg-mist-800/30",
]),
: ["bg-mist-100 dark:bg-mist-700/30", "hover:bg-mist-200/90 dark:hover:bg-mist-800/30"]),
props.className,
)}
>
+2 -1
View File
@@ -54,7 +54,8 @@ export default function Input(props: InputProps) {
{...inputProps}
className={cn(
"rounded-md px-3 py-2",
"focus:outline-hidden focus:ring-3",
"focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
"bg-white dark:bg-mist-900",
"border border-mist-100 dark:border-mist-800",
className,
+27 -30
View File
@@ -1,35 +1,32 @@
import { ExternalLink } from 'lucide-react';
import cn from '~/utils/cn';
import { ExternalLink } from "lucide-react";
import cn from "~/utils/cn";
export interface LinkProps {
to: string;
name: string;
children: string;
className?: string;
to: string;
name: string;
children: string;
className?: string;
}
export default function Link({
to,
name: alt,
children,
className,
}: LinkProps) {
return (
<a
href={to}
aria-label={alt}
target="_blank"
rel="noreferrer"
className={cn(
'inline-flex items-center gap-x-0.5',
'text-blue-500 hover:text-blue-700',
'dark:text-blue-400 dark:hover:text-blue-300',
'focus:outline-hidden focus:ring-3 rounded-md',
className,
)}
>
{children}
<ExternalLink className="w-3.5" />
</a>
);
export default function Link({ to, name: alt, children, className }: LinkProps) {
return (
<a
href={to}
aria-label={alt}
target="_blank"
rel="noreferrer"
className={cn(
"inline-flex items-center gap-x-0.5",
"text-blue-500 hover:text-blue-700",
"dark:text-blue-400 dark:hover:text-blue-300",
"focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1 rounded-md",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
className,
)}
>
{children}
<ExternalLink className="w-3.5" />
</a>
);
}
+2 -1
View File
@@ -43,7 +43,8 @@ export default function NumberInput(props: InputProps) {
{...groupProps}
className={cn(
"flex items-center gap-1 rounded-md pr-1",
"focus-within:outline-hidden focus-within:ring-3",
"focus-within:outline-hidden focus-within:ring-2 focus-within:ring-indigo-500/40 focus-within:ring-offset-1",
"dark:focus-within:ring-indigo-400/40 dark:focus-within:ring-offset-mist-900",
"bg-white dark:bg-mist-900",
"border border-mist-100 dark:border-mist-800",
)}
+2 -1
View File
@@ -43,7 +43,8 @@ function Option({ item, state }: OptionsOptionProps) {
className={cn(
"pl-0.5 pr-2 py-0.5 rounded-md cursor-pointer",
"aria-selected:bg-mist-100 dark:aria-selected:bg-mist-950",
"focus:outline-hidden focus:ring-3 z-10",
"focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1 z-10",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
"border border-mist-100 dark:border-mist-800",
)}
>
+3 -1
View File
@@ -62,7 +62,9 @@ function Radio({ children, label, className, ...props }: RadioProps) {
className={cn(
"w-5 h-5 aspect-square rounded-full p-1 border-2",
"border border-mist-600 dark:border-mist-300",
isFocusVisible ? "ring-4" : "",
isFocusVisible
? "ring-2 ring-indigo-500/40 ring-offset-1 dark:ring-indigo-400/40 dark:ring-offset-mist-900"
: "",
isDisabled ? "opacity-50 cursor-not-allowed" : "",
isSelected ? "border-[6px] border-mist-900 dark:border-mist-100" : "",
className,
+2 -1
View File
@@ -58,7 +58,8 @@ function Select(props: SelectProps) {
</label>
<div
className={cn(
"flex rounded-md focus:outline-hidden focus-within:ring-3",
"flex rounded-md focus:outline-hidden focus-within:ring-2 focus-within:ring-indigo-500/40 focus-within:ring-offset-1",
"dark:focus-within:ring-indigo-400/40 dark:focus-within:ring-offset-mist-900",
"bg-white dark:bg-mist-900",
"border border-mist-100 dark:border-mist-800",
props.isInvalid && "ring-red-400",
+2 -1
View File
@@ -35,7 +35,8 @@ export default function Switch(props: SwitchProps) {
"bg-mist-300 dark:bg-mist-700",
"border border-transparent dark:border-mist-800",
state.isSelected && "bg-mist-900 dark:bg-mist-950",
isFocusVisible && "ring-2",
isFocusVisible &&
"ring-2 ring-indigo-500/40 ring-offset-1 dark:ring-indigo-400/40 dark:ring-offset-mist-900",
props.isDisabled && "opacity-50",
props.className,
)}
+2 -1
View File
@@ -50,7 +50,8 @@ function Tab({ item, state }: TabsTabProps) {
className={cn(
"pl-2 pr-3 py-2.5",
"aria-selected:bg-mist-100 dark:aria-selected:bg-mist-950",
"focus:outline-hidden focus:ring-3 z-10",
"focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1 z-10",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
"border-r border-mist-100 dark:border-mist-800",
"first:rounded-tl-lg last:rounded-tr-lg last:border-r-0",
)}
+2 -1
View File
@@ -34,7 +34,8 @@ function Tooltip(props: TooltipProps) {
{...triggerProps}
className={cn(
"flex items-center justify-center",
"focus:outline-hidden focus:ring-3 rounded-md",
"focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1 rounded-md",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
)}
>
{component}
+3 -8
View File
@@ -120,12 +120,11 @@ export default function MachineMenu({
) : (
<Button
className={cn(
"py-0.5 w-fit bg-transparent border-transparent",
"border group-hover:border-mist-200",
"dark:group-hover:border-mist-700",
"py-0.5",
"opacity-0 pointer-events-none group-hover:opacity-100",
"group-hover:pointer-events-auto",
)}
variant="ghost"
onPress={() => {
// We need to use JS to open the SSH URL
// in a new WINDOW since href can only
@@ -149,11 +148,7 @@ export default function MachineMenu({
</Menu.Button>
) : (
<Menu.IconButton
className={cn(
"py-0.5 w-10 bg-transparent border-transparent",
"border group-hover:border-mist-200",
"dark:group-hover:border-mist-700",
)}
className="w-10 bg-transparent py-0.5 hover:bg-mist-100 dark:hover:bg-mist-800"
label="Machine Options"
>
<Ellipsis className="h-5" />
+6 -3
View File
@@ -164,8 +164,9 @@ export default function Page({
)}
</div>
<Button
className={cn("px-1.5 py-0.5 rounded-md mt-1.5", "text-blue-500 dark:text-blue-400")}
className="mt-1.5 px-1.5 py-0.5"
onPress={() => setShowRouting(true)}
variant="ghost"
>
Edit
</Button>
@@ -193,8 +194,9 @@ export default function Page({
)}
</div>
<Button
className={cn("px-1.5 py-0.5 rounded-md mt-1.5", "text-blue-500 dark:text-blue-400")}
className="mt-1.5 px-1.5 py-0.5"
onPress={() => setShowRouting(true)}
variant="ghost"
>
Edit
</Button>
@@ -225,8 +227,9 @@ export default function Page({
)}
</div>
<Button
className={cn("px-1.5 py-0.5 rounded-md mt-1.5", "text-blue-500 dark:text-blue-400")}
className="mt-1.5 px-1.5 py-0.5"
onPress={() => setShowRouting(true)}
variant="ghost"
>
Edit
</Button>
+1 -6
View File
@@ -3,7 +3,6 @@ import { useState } from "react";
import Menu from "~/components/Menu";
import type { Machine, User } from "~/types";
import cn from "~/utils/cn";
import Delete from "../dialogs/delete-user";
import LinkUser from "../dialogs/link-user";
@@ -77,11 +76,7 @@ export default function UserMenu({ user, headscaleUsers, currentLink }: MenuProp
<Menu disabledKeys={disabledKeys}>
<Menu.IconButton
className={cn(
"w-10 border-transparent bg-transparent py-0.5",
"border group-hover:border-mist-200",
"dark:group-hover:border-mist-700",
)}
className="w-10 bg-transparent py-0.5 hover:bg-mist-100 dark:hover:bg-mist-800"
label="User Options"
>
<Ellipsis className="h-5" />