use Icon component

This commit is contained in:
Oliver Bryan
2026-01-17 22:12:29 +00:00
parent 20f755ef71
commit e2560b089b
21 changed files with 97 additions and 74 deletions

View File

@@ -1,8 +1,8 @@
import type { SprintRecord } from "@sprint/shared";
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import * as React from "react";
import { type DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
import { Button, buttonVariants } from "@/components/ui/button";
import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
function Calendar({
@@ -109,14 +109,16 @@ function Calendar({
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return <ChevronLeftIcon className={cn("size-4", className)} {...props} />;
return <Icon icon="chevronLeftIcon" className={cn("size-4", className)} {...props} />;
}
if (orientation === "right") {
return <ChevronRightIcon className={cn("size-4", className)} {...props} />;
return (
<Icon icon="chevronRightIcon" className={cn("size-4", className)} {...props} />
);
}
return <ChevronDownIcon className={cn("size-4", className)} {...props} />;
return <Icon icon="chevronDownIcon" className={cn("size-4", className)} {...props} />;
},
DayButton: (props) => <CalendarDayButton {...props} sprints={sprints} isEnd={isEnd} />,
WeekNumber: ({ children, ...props }) => {

View File

@@ -1,6 +1,6 @@
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";
import type * as React from "react";
import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
@@ -71,7 +71,7 @@ function DialogContent({
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
)}
>
<XIcon />
<Icon icon="x" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}

View File

@@ -1,6 +1,6 @@
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import type * as React from "react";
import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
@@ -136,7 +136,7 @@ function DropdownMenuCheckboxItem({
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
<Icon icon="checkIcon" className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
@@ -168,7 +168,7 @@ function DropdownMenuRadioItem({
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
<Icon icon="circleIcon" className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
@@ -242,7 +242,7 @@ function DropdownMenuSubTrigger({
{...props}
>
{children}
<ChevronRightIcon className="ml-auto size-4" />
<Icon icon="chevronRightIcon" className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
);
}

View File

@@ -56,6 +56,7 @@ import {
WarningIcon as PhosphorWarning,
XIcon as PhosphorX,
} from "@phosphor-icons/react";
import type { IconStyle } from "@sprint/shared";
import {
AlertTriangle,
Check,
@@ -92,6 +93,7 @@ import {
UserRound,
X,
} from "lucide-react";
import { useSessionSafe } from "@/components/session-provider";
const icons = {
alertTriangle: { lucide: AlertTriangle, pixel: PixelAlert, phosphor: PhosphorWarning },
@@ -141,11 +143,11 @@ const icons = {
export type IconName = keyof typeof icons;
export const iconNames = Object.keys(icons) as IconName[];
export const iconStyles = ["lucide", "pixel", "phosphor"] as const;
export type IconStyle = (typeof iconStyles)[number];
export type { IconStyle };
export default function Icon({
icon,
iconStyle = "lucide",
iconStyle,
size = 24,
...props
}: {
@@ -154,7 +156,9 @@ export default function Icon({
size?: number | string;
color?: string;
} & React.ComponentProps<"svg">) {
const IconComponent = icons[icon]?.[iconStyle];
const session = useSessionSafe();
const resolvedStyle = (iconStyle ?? session?.user?.iconPreference ?? "lucide") as IconStyle;
const IconComponent = icons[icon]?.[resolvedStyle];
if (!IconComponent) {
return null;
@@ -164,9 +168,9 @@ export default function Icon({
<IconComponent
size={size}
fill={
(iconStyle === "pixel" && icon === "moon") ||
(iconStyle === "pixel" && icon === "hash") ||
iconStyle === "phosphor"
(resolvedStyle === "pixel" && icon === "moon") ||
(resolvedStyle === "pixel" && icon === "hash") ||
resolvedStyle === "phosphor"
? "var(--foreground)"
: "transparent"
}

View File

@@ -1,5 +1,5 @@
import { Hash } from "lucide-react";
import type * as React from "react";
import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
function Input({
@@ -25,7 +25,7 @@ function Input({
>
{showHashPrefix && (
<span className="border-r px-1 py-1 text-muted-foreground">
<Hash className="size-3.5" strokeWidth={1.5} />
<Icon icon="hash" className="size-3.5" />
</span>
)}
<input

View File

@@ -1,8 +1,8 @@
"use client";
import { GripVerticalIcon } from "lucide-react";
import type * as React from "react";
import { Group, Panel, Separator } from "react-resizable-panels";
import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
@@ -49,7 +49,7 @@ function ResizableSeparator({
>
{withHandle && (
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
<GripVerticalIcon className="size-2.5" />
<Icon icon="gripVerticalIcon" className="size-2.5" />
</div>
)}
</Separator>

View File

@@ -1,6 +1,6 @@
import * as SelectPrimitive from "@radix-ui/react-select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import type * as React from "react";
import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
@@ -72,8 +72,9 @@ function SelectTrigger({
)}
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon
className={cn("size-4 opacity-50", chevronClassName)}
<Icon
icon="chevronDownIcon"
className={cn("size-4.5 opacity-50", chevronClassName)}
style={{ rotate: isOpen ? "180deg" : "0deg" }}
/>
</SelectPrimitive.Icon>
@@ -165,7 +166,7 @@ function SelectItem({
className="absolute right-2 flex size-3.5 items-center justify-center"
>
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
<Icon icon="checkIcon" className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
{textClassName ? (
@@ -197,7 +198,7 @@ function SelectScrollUpButton({
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronUpIcon className="size-4" />
<Icon icon="chevronUpIcon" className="size-4" />
</SelectPrimitive.ScrollUpButton>
);
}
@@ -212,7 +213,7 @@ function SelectScrollDownButton({
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronDownIcon className="size-4" />
<Icon icon="chevronDownIcon" className="size-4" />
</SelectPrimitive.ScrollDownButton>
);
}

View File

@@ -1,6 +1,6 @@
import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon } from "lucide-react";
import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";
import Icon from "@/components/ui/icon";
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme();
@@ -10,11 +10,11 @@ const Toaster = ({ ...props }: ToasterProps) => {
theme={theme as ToasterProps["theme"]}
className="toaster group"
icons={{
success: <CircleCheckIcon className="size-4" />,
info: <InfoIcon className="size-4" />,
warning: <TriangleAlertIcon className="size-4" />,
error: <OctagonXIcon className="size-4" />,
loading: <Loader2Icon className="size-4 animate-spin" />,
success: <Icon icon="circleCheckIcon" className="size-4" />,
info: <Icon icon="infoIcon" className="size-4" />,
warning: <Icon icon="triangleAlertIcon" className="size-4" />,
error: <Icon icon="octagonXIcon" className="size-4" />,
loading: <Icon icon="loader2Icon" className="size-4 animate-spin" />,
}}
style={
{

View File

@@ -1,10 +1,10 @@
import { Loader } from "lucide-react";
import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return (
<Loader
<Icon
icon="loader"
role="status"
aria-label="Loading"
className={cn("size-4 animate-spin", className)}