From 8dc6291eabf409c82211b43a542d4b9b00920759 Mon Sep 17 00:00:00 2001 From: Oliver Bryan <04oliverbryan@gmail.com> Date: Sat, 17 Jan 2026 03:46:09 +0000 Subject: [PATCH] IconButton --- .../src/components/issue-detail-pane.tsx | 23 +++------- .../frontend/src/components/login-form.tsx | 8 ++-- .../src/components/multi-assignee-select.tsx | 12 ++--- .../src/components/organisations-dialog.tsx | 32 ++++++++------ .../server-configuration-dialog.tsx | 27 +++++------- .../frontend/src/components/theme-toggle.tsx | 10 ++--- .../src/components/ui/icon-button.tsx | 44 +++++++++++++++++++ 7 files changed, 92 insertions(+), 64 deletions(-) create mode 100644 packages/frontend/src/components/ui/icon-button.tsx diff --git a/packages/frontend/src/components/issue-detail-pane.tsx b/packages/frontend/src/components/issue-detail-pane.tsx index ab87269..7f06fa9 100644 --- a/packages/frontend/src/components/issue-detail-pane.tsx +++ b/packages/frontend/src/components/issue-detail-pane.tsx @@ -9,13 +9,13 @@ import { StatusSelect } from "@/components/status-select"; import StatusTag from "@/components/status-tag"; import { TimerDisplay } from "@/components/timer-display"; import { TimerModal } from "@/components/timer-modal"; -import { Button } from "@/components/ui/button"; import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { SelectTrigger } from "@/components/ui/select"; import { issue } from "@/lib/server"; import { issueID } from "@/lib/utils"; import SmallSprintDisplay from "./small-sprint-display"; import { SprintSelect } from "./sprint-select"; +import { IconButton } from "./ui/icon-button"; function assigneesToStringArray(assignees: UserRecord[]): string[] { if (assignees.length === 0) return ["unassigned"]; @@ -245,24 +245,15 @@ export function IssueDetailPane({

- - - +
diff --git a/packages/frontend/src/components/login-form.tsx b/packages/frontend/src/components/login-form.tsx index 882914b..dad2608 100644 --- a/packages/frontend/src/components/login-form.tsx +++ b/packages/frontend/src/components/login-form.tsx @@ -10,6 +10,7 @@ import { useSession } from "@/components/session-provider"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Field } from "@/components/ui/field"; +import { IconButton } from "@/components/ui/icon-button"; import { Label } from "@/components/ui/label"; import { UploadAvatar } from "@/components/upload-avatar"; import { capitalise, cn, getServerURL, setCsrfToken } from "@/lib/utils"; @@ -144,9 +145,8 @@ export default function LogInForm() { {/* under construction warning */} {showWarning && (
- +

diff --git a/packages/frontend/src/components/multi-assignee-select.tsx b/packages/frontend/src/components/multi-assignee-select.tsx index 16e6fef..e4ceff2 100644 --- a/packages/frontend/src/components/multi-assignee-select.tsx +++ b/packages/frontend/src/components/multi-assignee-select.tsx @@ -1,6 +1,6 @@ import type { UserRecord } from "@sprint/shared"; import { Plus } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { IconButton } from "@/components/ui/icon-button"; import { UserSelect } from "@/components/user-select"; export function MultiAssigneeSelect({ @@ -61,15 +61,9 @@ export function MultiAssigneeSelect({ />

{index === assigneeIds.length - 1 && canAddMore && ( - + )} ))} diff --git a/packages/frontend/src/components/organisations-dialog.tsx b/packages/frontend/src/components/organisations-dialog.tsx index f7c4629..80d107d 100644 --- a/packages/frontend/src/components/organisations-dialog.tsx +++ b/packages/frontend/src/components/organisations-dialog.tsx @@ -28,6 +28,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { IconButton } from "@/components/ui/icon-button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; @@ -552,9 +553,7 @@ function OrganisationsDialog({ member.OrganisationMember.role !== "owner" && member.User.id !== user.id && ( <> - - + + )}
@@ -802,9 +806,9 @@ function OrganisationsDialog({ asChild={false} className="w-9 h-9" /> - + {statusError && (

diff --git a/packages/frontend/src/components/server-configuration-dialog.tsx b/packages/frontend/src/components/server-configuration-dialog.tsx index f6a7b8d..b5f1f30 100644 --- a/packages/frontend/src/components/server-configuration-dialog.tsx +++ b/packages/frontend/src/components/server-configuration-dialog.tsx @@ -1,8 +1,8 @@ import { CheckIcon, ServerIcon, Undo2 } from "lucide-react"; import { type ReactNode, useState } from "react"; import { createPortal } from "react-dom"; -import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import { IconButton } from "@/components/ui/icon-button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { getServerURL } from "@/lib/utils"; @@ -113,14 +113,13 @@ export function ServerConfigurationDialog({ trigger }: { trigger?: ReactNode })

{trigger || ( - + )} @@ -147,25 +146,23 @@ export function ServerConfigurationDialog({ trigger }: { trigger?: ReactNode }) className={!isValid ? "border-destructive" : ""} spellCheck={false} /> - - + {!isValid && ( diff --git a/packages/frontend/src/components/theme-toggle.tsx b/packages/frontend/src/components/theme-toggle.tsx index c1a182d..b2b7417 100644 --- a/packages/frontend/src/components/theme-toggle.tsx +++ b/packages/frontend/src/components/theme-toggle.tsx @@ -1,6 +1,6 @@ import { Moon, Sun } from "lucide-react"; import { useTheme } from "@/components/theme-provider"; -import { Button } from "@/components/ui/button"; +import { IconButton } from "@/components/ui/icon-button"; import { cn } from "@/lib/utils"; function ThemeToggle({ className }: { className?: string }) { @@ -14,16 +14,14 @@ function ThemeToggle({ className }: { className?: string }) { const isDark = resolvedTheme === "dark"; return ( - + ); } diff --git a/packages/frontend/src/components/ui/icon-button.tsx b/packages/frontend/src/components/ui/icon-button.tsx new file mode 100644 index 0000000..6d61125 --- /dev/null +++ b/packages/frontend/src/components/ui/icon-button.tsx @@ -0,0 +1,44 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import type * as React from "react"; +import { cn } from "@/lib/utils"; + +const iconButtonVariants = cva( + "cursor-pointer inline-flex items-center justify-center [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none disabled:pointer-events-none disabled:opacity-50 focus-visible:ring-ring/50 focus-visible:ring-[3px]", + { + variants: { + variant: { + default: "hover:text-foreground/70", + destructive: "text-destructive hover:text-destructive/70", + yellow: "text-yellow-500 hover:text-yellow-500/70", + green: "text-green-500 hover:text-green-500/70", + ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + outline: "border bg-transparent dark:hover:bg-muted/40", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + primary: "bg-primary text-primary-foreground hover:bg-primary/90", + }, + size: { + default: "w-6 h-6", + sm: "w-5 h-5", + md: "w-9 h-9", + lg: "w-10 h-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +function IconButton({ + className, + variant, + size, + ...props +}: React.ComponentProps<"button"> & VariantProps) { + return ( +