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,5 +1,5 @@
import { UserRound } from "lucide-react";
import { useSession } from "@/components/session-provider"; import { useSession } from "@/components/session-provider";
import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const FALLBACK_COLOURS = [ const FALLBACK_COLOURS = [
@@ -89,7 +89,7 @@ export default function Avatar({
) : name ? ( ) : name ? (
<span className={textClass}>{getInitials(name)}</span> <span className={textClass}>{getInitials(name)}</span>
) : ( ) : (
<UserRound className={"size-10"} /> <Icon icon="userRound" className={"size-10"} />
)} )}
</div> </div>
); );

View File

@@ -1,5 +1,4 @@
import type { IssueResponse, ProjectResponse, SprintRecord, UserRecord } from "@sprint/shared"; import type { IssueResponse, ProjectResponse, SprintRecord, UserRecord } from "@sprint/shared";
import { Check, Link, Trash, X } from "lucide-react";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { MultiAssigneeSelect } from "@/components/multi-assignee-select"; import { MultiAssigneeSelect } from "@/components/multi-assignee-select";
@@ -10,6 +9,7 @@ import StatusTag from "@/components/status-tag";
import { TimerDisplay } from "@/components/timer-display"; import { TimerDisplay } from "@/components/timer-display";
import { TimerModal } from "@/components/timer-modal"; import { TimerModal } from "@/components/timer-modal";
import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import Icon from "@/components/ui/icon";
import { SelectTrigger } from "@/components/ui/select"; import { SelectTrigger } from "@/components/ui/select";
import { issue } from "@/lib/server"; import { issue } from "@/lib/server";
import { issueID } from "@/lib/utils"; import { issueID } from "@/lib/utils";
@@ -246,13 +246,13 @@ export function IssueDetailPane({
</span> </span>
<div className="flex items-center"> <div className="flex items-center">
<IconButton onClick={handleCopyLink} title={linkCopied ? "Copied" : "Copy link"}> <IconButton onClick={handleCopyLink} title={linkCopied ? "Copied" : "Copy link"}>
{linkCopied ? <Check /> : <Link />} {linkCopied ? <Icon icon="check" /> : <Icon icon="link" />}
</IconButton> </IconButton>
<IconButton variant="destructive" onClick={handleDelete} title={"Delete issue"}> <IconButton variant="destructive" onClick={handleDelete} title={"Delete issue"}>
<Trash /> <Icon icon="trash" />
</IconButton> </IconButton>
<IconButton onClick={close} title={"Close"}> <IconButton onClick={close} title={"Close"}>
<X /> <Icon icon="x" />
</IconButton> </IconButton>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { LogOut } from "lucide-react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import Icon from "@/components/ui/icon";
import { clearAuth, cn, getCsrfToken, getServerURL } from "@/lib/utils"; import { clearAuth, cn, getCsrfToken, getServerURL } from "@/lib/utils";
export default function LogOutButton({ export default function LogOutButton({
@@ -37,7 +37,7 @@ export default function LogOutButton({
size={noStyle ? "none" : "default"} size={noStyle ? "none" : "default"}
> >
Log out Log out
<LogOut size={15} /> <Icon icon="logOut" size={15} />
</Button> </Button>
); );
} }

View File

@@ -1,7 +1,6 @@
/** biome-ignore-all lint/correctness/useExhaustiveDependencies: <> */ /** biome-ignore-all lint/correctness/useExhaustiveDependencies: <> */
import { USER_NAME_MAX_LENGTH, USER_USERNAME_MAX_LENGTH } from "@sprint/shared"; import { USER_NAME_MAX_LENGTH, USER_USERNAME_MAX_LENGTH } from "@sprint/shared";
import { AlertTriangle, X } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import Avatar from "@/components/avatar"; import Avatar from "@/components/avatar";
@@ -10,6 +9,7 @@ import { useSession } from "@/components/session-provider";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Field } from "@/components/ui/field"; import { Field } from "@/components/ui/field";
import Icon from "@/components/ui/icon";
import { IconButton } from "@/components/ui/icon-button"; import { IconButton } from "@/components/ui/icon-button";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { UploadAvatar } from "@/components/upload-avatar"; import { UploadAvatar } from "@/components/upload-avatar";
@@ -153,9 +153,9 @@ export default function LogInForm() {
setShowWarning(false); setShowWarning(false);
}} }}
> >
<X /> <Icon icon="x" />
</IconButton> </IconButton>
<AlertTriangle className="w-16 h-16 text-yellow-500" strokeWidth={1.5} /> <Icon icon="alertTriangle" className="w-16 h-16 text-yellow-500" />
<div className="text-center text-sm text-muted-foreground font-500"> <div className="text-center text-sm text-muted-foreground font-500">
<p> <p>
This application is currently under construction. Your data is very likely to be This application is currently under construction. Your data is very likely to be

View File

@@ -1,5 +1,5 @@
import type { UserRecord } from "@sprint/shared"; import type { UserRecord } from "@sprint/shared";
import { Plus } from "lucide-react"; import Icon from "@/components/ui/icon";
import { IconButton } from "@/components/ui/icon-button"; import { IconButton } from "@/components/ui/icon-button";
import { UserSelect } from "@/components/user-select"; import { UserSelect } from "@/components/user-select";
@@ -62,7 +62,7 @@ export function MultiAssigneeSelect({
</div> </div>
{index === assigneeIds.length - 1 && canAddMore && ( {index === assigneeIds.length - 1 && canAddMore && (
<IconButton onClick={handleAddAssignee} title={"Add assignee"} className="w-9 h-9"> <IconButton onClick={handleAddAssignee} title={"Add assignee"} className="w-9 h-9">
<Plus className="h-4 w-4" /> <Icon icon="plus" className="h-4 w-4" />
</IconButton> </IconButton>
)} )}
</> </>

View File

@@ -7,7 +7,6 @@ import {
type ProjectResponse, type ProjectResponse,
type SprintRecord, type SprintRecord,
} from "@sprint/shared"; } from "@sprint/shared";
import { ChevronDown, ChevronUp, EllipsisVertical, Plus, X } from "lucide-react";
import { type ReactNode, useCallback, useEffect, useState } from "react"; import { type ReactNode, useCallback, useEffect, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { AddMemberDialog } from "@/components/add-member-dialog"; import { AddMemberDialog } from "@/components/add-member-dialog";
@@ -28,6 +27,7 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import Icon from "@/components/ui/icon";
import { IconButton } from "@/components/ui/icon-button"; import { IconButton } from "@/components/ui/icon-button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@@ -571,9 +571,15 @@ function OrganisationsDialog({
> >
{member.OrganisationMember.role === {member.OrganisationMember.role ===
"admin" ? ( "admin" ? (
<ChevronDown className="size-5" /> <Icon
icon="chevronDown"
className="size-5"
/>
) : ( ) : (
<ChevronUp className="size-5" /> <Icon
icon="chevronUp"
className="size-5"
/>
)} )}
</IconButton> </IconButton>
<IconButton <IconButton
@@ -585,7 +591,7 @@ function OrganisationsDialog({
) )
} }
> >
<X className="size-5" /> <Icon icon="x" className="size-5" />
</IconButton> </IconButton>
</> </>
)} )}
@@ -609,7 +615,7 @@ function OrganisationsDialog({
}} }}
trigger={ trigger={
<Button variant="outline"> <Button variant="outline">
Add user <Plus className="size-4" /> Add user <Icon icon="plus" className="size-4" />
</Button> </Button>
} }
/> />
@@ -683,7 +689,10 @@ function OrganisationsDialog({
trigger={ trigger={
<Button variant="outline" size="sm"> <Button variant="outline" size="sm">
Create sprint{" "} Create sprint{" "}
<Plus className="size-4" /> <Icon
icon="plus"
className="size-4"
/>
</Button> </Button>
} }
sprints={sprints} sprints={sprints}
@@ -726,7 +735,10 @@ function OrganisationsDialog({
noStyle noStyle
className="hover:opacity-80 cursor-pointer" className="hover:opacity-80 cursor-pointer"
> >
<EllipsisVertical className="size-4 text-foreground" /> <Icon
icon="ellipsisVertical"
className="size-4 text-foreground"
/>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent <DropdownMenuContent
align="end" align="end"
@@ -740,7 +752,10 @@ function OrganisationsDialog({
} }
className="hover:bg-primary-foreground" className="hover:bg-primary-foreground"
> >
<ChevronUp className="size-4 text-muted-foreground" /> <Icon
icon="chevronUp"
className="size-4 text-muted-foreground"
/>
Move up Move up
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
@@ -753,7 +768,10 @@ function OrganisationsDialog({
} }
className="hover:bg-primary-foreground" className="hover:bg-primary-foreground"
> >
<ChevronDown className="size-4 text-muted-foreground" /> <Icon
icon="chevronDown"
className="size-4 text-muted-foreground"
/>
Move down Move down
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
@@ -766,7 +784,7 @@ function OrganisationsDialog({
} }
className="hover:bg-destructive/10" className="hover:bg-destructive/10"
> >
<X className="size-4" /> <Icon icon="x" className="size-4" />
Remove Remove
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
@@ -815,7 +833,7 @@ function OrganisationsDialog({
ISSUE_STATUS_MAX_LENGTH ISSUE_STATUS_MAX_LENGTH
} }
> >
<Plus className="size-4" /> <Icon icon="plus" className="size-4" />
</IconButton> </IconButton>
</div> </div>
{statusError && ( {statusError && (
@@ -833,7 +851,7 @@ function OrganisationsDialog({
}} }}
className="flex gap-2 w-full min-w-0" className="flex gap-2 w-full min-w-0"
> >
Create status <Plus className="size-4" /> Create status <Icon icon="plus" className="size-4" />
</Button> </Button>
))} ))}
</div> </div>

View File

@@ -1,7 +1,7 @@
import { CheckIcon, ServerIcon, Undo2 } from "lucide-react";
import { type ReactNode, useState } from "react"; import { type ReactNode, useState } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import Icon from "@/components/ui/icon";
import { IconButton } from "@/components/ui/icon-button"; import { IconButton } from "@/components/ui/icon-button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
@@ -118,7 +118,7 @@ export function ServerConfigurationDialog({ trigger }: { trigger?: ReactNode })
className="absolute top-2 right-2" className="absolute top-2 right-2"
title={"Server Configuration"} title={"Server Configuration"}
> >
<ServerIcon className="size-4" /> <Icon icon="serverIcon" className="size-4" />
</IconButton> </IconButton>
)} )}
</DialogTrigger> </DialogTrigger>
@@ -152,7 +152,7 @@ export function ServerConfigurationDialog({ trigger }: { trigger?: ReactNode })
disabled={!canSave || isCheckingHealth} disabled={!canSave || isCheckingHealth}
onClick={handleSave} onClick={handleSave}
> >
<CheckIcon className="size-4" /> <Icon icon="checkIcon" className="size-4" />
</IconButton> </IconButton>
<IconButton <IconButton
variant="secondary" variant="secondary"
@@ -161,7 +161,7 @@ export function ServerConfigurationDialog({ trigger }: { trigger?: ReactNode })
onClick={handleResetToDefault} onClick={handleResetToDefault}
title="Reset to default" title="Reset to default"
> >
<Undo2 className="size-4" /> <Icon icon="undo2" className="size-4" />
</IconButton> </IconButton>
</div> </div>
{!isValid && ( {!isValid && (

View File

@@ -1,5 +1,5 @@
import { Moon, Sun } from "lucide-react";
import { useTheme } from "@/components/theme-provider"; import { useTheme } from "@/components/theme-provider";
import Icon from "@/components/ui/icon";
import { IconButton } from "@/components/ui/icon-button"; import { IconButton } from "@/components/ui/icon-button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -20,7 +20,7 @@ function ThemeToggle({ className }: { className?: string }) {
onClick={() => setTheme(isDark ? "light" : "dark")} onClick={() => setTheme(isDark ? "light" : "dark")}
title={isDark ? "Switch to light mode" : "Switch to dark mode"} title={isDark ? "Switch to light mode" : "Switch to dark mode"}
> >
{isDark ? <Sun className="size-5" /> : <Moon className="size-5" />} {isDark ? <Icon icon="sun" className="size-5" /> : <Icon icon="moon" className="size-5" />}
</IconButton> </IconButton>
); );
} }

View File

@@ -1,8 +1,8 @@
import { Timer } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { IssueTimer } from "@/components/issue-timer"; import { IssueTimer } from "@/components/issue-timer";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import Icon from "@/components/ui/icon";
export function TimerModal({ issueId }: { issueId: number }) { export function TimerModal({ issueId }: { issueId: number }) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -11,7 +11,7 @@ export function TimerModal({ issueId }: { issueId: number }) {
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline" size="sm"> <Button variant="outline" size="sm">
<Timer className="size-4" /> <Icon icon="timer" className="size-4" />
Timer Timer
</Button> </Button>
</DialogTrigger> </DialogTrigger>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import { Edit } from "lucide-react";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import Avatar from "@/components/avatar"; import Avatar from "@/components/avatar";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import Icon from "@/components/ui/icon";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { parseError, user } from "@/lib/server"; import { parseError, user } from "@/lib/server";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -82,7 +82,7 @@ export function UploadAvatar({
{!uploading && showEdit && ( {!uploading && showEdit && (
<div className="absolute inset-0 flex items-center justify-center bg-black/40"> <div className="absolute inset-0 flex items-center justify-center bg-black/40">
<Edit className="size-6 text-white drop-shadow-md" /> <Icon icon="edit" className="size-6 text-white drop-shadow-md" />
</div> </div>
)} )}
</Button> </Button>

View File

@@ -1,9 +1,9 @@
import { CircleQuestionMark } from "lucide-react"; import Icon from "@/components/ui/icon";
export default function NotFound() { export default function NotFound() {
return ( return (
<div className={`w-full h-[100vh] flex flex-col items-center justify-center gap-4`}> <div className={`w-full h-[100vh] flex flex-col items-center justify-center gap-4`}>
<CircleQuestionMark size={72} /> <Icon icon="circleQuestionMark" size={72} />
<span className="text-7xl font-500">404</span> <span className="text-7xl font-500">404</span>
<span className="text-2xl font-400">Not Found</span> <span className="text-2xl font-400">Not Found</span>
</div> </div>

View File

@@ -1,7 +1,5 @@
# HIGH PRIORITY # HIGH PRIORITY
- icons
- respect user.iconPreference throughout the app
- projects menu - projects menu
- delete project - delete project
- sprints - sprints