diff --git a/eslint.config.mjs b/eslint.config.mjs index d35b300..4b63260 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -62,7 +62,7 @@ const eslintConfig = defineConfig([ files: ["src/**/*.{ts,tsx}"], rules: { "no-restricted-imports": [ - "warn", + "error", { patterns: [ { @@ -100,7 +100,7 @@ const eslintConfig = defineConfig([ message: "Pattern components must be pure presentation — no hooks imports.", }, { - group: ["@/lib/*", "@/lib"], + group: ["@/lib/*", "!@/lib/utils"], message: "Pattern components must be pure presentation — no lib imports. Use @/lib/utils for cn() only.", }, ], diff --git a/package-lock.json b/package-lock.json index f870cb2..ffbaf24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,6 @@ "cmdk": "^1.1.1", "discord.js": "^14.25.1", "electron-updater": "^6.8.3", - "lucide-react": "^0.563.0", "markdown-it": "^14.1.1", "motion": "^12.33.0", "nanoid": "^5.1.6", diff --git a/package.json b/package.json index 53250f0..3fb3623 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "cmdk": "^1.1.1", "discord.js": "^14.25.1", "electron-updater": "^6.8.3", - "lucide-react": "^0.563.0", "markdown-it": "^14.1.1", "motion": "^12.33.0", "nanoid": "^5.1.6", diff --git a/src/components/ai-elements/artifact.tsx b/src/components/ai-elements/artifact.tsx index cde4ff2..c053f96 100644 --- a/src/components/ai-elements/artifact.tsx +++ b/src/components/ai-elements/artifact.tsx @@ -1,6 +1,6 @@ "use client"; -import type { LucideIcon } from "lucide-react"; +import type { Icon as PhosphorIcon } from "@phosphor-icons/react"; import type { ComponentProps, HTMLAttributes } from "react"; import { Button } from "@/components/ui/button"; @@ -11,7 +11,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; -import { XIcon } from "lucide-react"; +import { X } from "@phosphor-icons/react"; export type ArtifactProps = HTMLAttributes; @@ -59,7 +59,7 @@ export const ArtifactClose = ({ variant={variant} {...props} > - {children ?? } + {children ?? } Close ); @@ -94,7 +94,7 @@ export const ArtifactActions = ({ export type ArtifactActionProps = ComponentProps & { tooltip?: string; label?: string; - icon?: LucideIcon; + icon?: PhosphorIcon; }; export const ArtifactAction = ({ diff --git a/src/components/ai-elements/chain-of-thought.tsx b/src/components/ai-elements/chain-of-thought.tsx index e5da02c..af06195 100644 --- a/src/components/ai-elements/chain-of-thought.tsx +++ b/src/components/ai-elements/chain-of-thought.tsx @@ -1,6 +1,6 @@ "use client"; -import type { LucideIcon } from "lucide-react"; +import type { Icon as PhosphorIcon } from "@phosphor-icons/react"; import type { ComponentProps, ReactNode } from "react"; import { useControllableState } from "@radix-ui/react-use-controllable-state"; @@ -11,7 +11,7 @@ import { CollapsibleTrigger, } from "@/components/ui/collapsible"; import { cn } from "@/lib/utils"; -import { BrainIcon, ChevronDownIcon, DotIcon } from "lucide-react"; +import { Brain, CaretDown, DotOutline } from "@phosphor-icons/react"; import { createContext, memo, useContext, useMemo } from "react"; interface ChainOfThoughtContextValue { @@ -86,11 +86,11 @@ export const ChainOfThoughtHeader = memo( )} {...props} > - + {children ?? "Chain of Thought"} - & { - icon?: LucideIcon; + icon?: PhosphorIcon; label: ReactNode; description?: ReactNode; status?: "complete" | "active" | "pending"; @@ -118,7 +118,7 @@ const stepStatusStyles = { export const ChainOfThoughtStep = memo( ({ className, - icon: Icon = DotIcon, + icon: Icon = DotOutline, label, description, status = "complete", diff --git a/src/components/ai-elements/code-block.tsx b/src/components/ai-elements/code-block.tsx index 25f526c..09cbda4 100644 --- a/src/components/ai-elements/code-block.tsx +++ b/src/components/ai-elements/code-block.tsx @@ -20,7 +20,7 @@ import { import { cn } from "@/lib/utils"; import { useThemeFamily } from "@/lib/theme/context"; import { resolveShikiTheme, resolveShikiThemes, SHIKI_DEFAULT_LIGHT, SHIKI_DEFAULT_DARK } from "@/lib/theme/code-themes"; -import { CheckIcon, CopyIcon } from "lucide-react"; +import { Check, Copy } from "@phosphor-icons/react"; import { createContext, memo, @@ -526,7 +526,7 @@ export const CodeBlockCopyButton = ({ [] ); - const Icon = isCopied ? CheckIcon : CopyIcon; + const Icon = isCopied ? Check : Copy; return ( ) ); @@ -161,7 +161,7 @@ export const ConversationDownload = ({ variant="outline" {...props} > - {children ?? } + {children ?? } ); }; diff --git a/src/components/ai-elements/message.tsx b/src/components/ai-elements/message.tsx index a486b53..b7ae76a 100644 --- a/src/components/ai-elements/message.tsx +++ b/src/components/ai-elements/message.tsx @@ -19,7 +19,7 @@ import { cjk } from "@streamdown/cjk"; import { createCodePlugin } from "@streamdown/code"; import { math } from "@streamdown/math"; import { mermaid } from "@streamdown/mermaid"; -import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; +import { CaretLeft, CaretRight } from "@phosphor-icons/react"; import { createContext, memo, @@ -271,7 +271,7 @@ export const MessageBranchPrevious = ({ variant="ghost" {...props} > - {children ?? } + {children ?? } ); }; @@ -294,7 +294,7 @@ export const MessageBranchNext = ({ variant="ghost" {...props} > - {children ?? } + {children ?? } ); }; diff --git a/src/components/ai-elements/prompt-input.tsx b/src/components/ai-elements/prompt-input.tsx index 599de7c..8acb21d 100644 --- a/src/components/ai-elements/prompt-input.tsx +++ b/src/components/ai-elements/prompt-input.tsx @@ -56,12 +56,12 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { - CornerDownLeftIcon, - ImageIcon, - PlusIcon, - SquareIcon, - XIcon, -} from "lucide-react"; + ArrowElbowDownLeft, + Image, + Plus, + Square, + X, +} from "@phosphor-icons/react"; import { nanoid } from "nanoid"; import { Children, @@ -353,7 +353,7 @@ export const PromptInputActionAddAttachments = ({ return ( - {label} + {label} ); }; @@ -1062,7 +1062,7 @@ export const PromptInputActionMenuTrigger = ({ }: PromptInputActionMenuTriggerProps) => ( - {children ?? } + {children ?? } ); @@ -1107,14 +1107,14 @@ export const PromptInputSubmit = ({ }: PromptInputSubmitProps) => { const isGenerating = status === "submitted" || status === "streaming"; - let Icon = ; + let Icon = ; if (status === "submitted") { Icon = ; } else if (status === "streaming") { - Icon = ; + Icon = ; } else if (status === "error") { - Icon = ; + Icon = ; } const handleClick = useCallback( diff --git a/src/components/ai-elements/reasoning.tsx b/src/components/ai-elements/reasoning.tsx index 5e89161..588a7e1 100644 --- a/src/components/ai-elements/reasoning.tsx +++ b/src/components/ai-elements/reasoning.tsx @@ -13,7 +13,7 @@ import { cjk } from "@streamdown/cjk"; import { code } from "@streamdown/code"; import { math } from "@streamdown/math"; import { mermaid } from "@streamdown/mermaid"; -import { BrainIcon, ChevronDownIcon } from "lucide-react"; +import { Brain, CaretDown } from "@phosphor-icons/react"; import { createContext, memo, @@ -184,9 +184,9 @@ export const ReasoningTrigger = memo( > {children ?? ( <> - + {getThinkingMessage(isStreaming, duration)} - ; @@ -36,7 +36,7 @@ export const SourcesTrigger = ({ {children ?? ( <>

Used {count} sources

- + )} @@ -70,7 +70,7 @@ export const Source = ({ href, title, children, ...props }: SourceProps) => ( > {children ?? ( <> - + {title} )} diff --git a/src/components/ai-elements/task.tsx b/src/components/ai-elements/task.tsx index fca8595..add8242 100644 --- a/src/components/ai-elements/task.tsx +++ b/src/components/ai-elements/task.tsx @@ -8,7 +8,7 @@ import { CollapsibleTrigger, } from "@/components/ui/collapsible"; import { cn } from "@/lib/utils"; -import { ChevronDownIcon, SearchIcon } from "lucide-react"; +import { CaretDown, MagnifyingGlass } from "@phosphor-icons/react"; export type TaskItemFileProps = ComponentProps<"div">; @@ -59,9 +59,9 @@ export const TaskTrigger = ({ {children ?? (
- +

{title}

- +
)}
diff --git a/src/components/ai-elements/terminal.tsx b/src/components/ai-elements/terminal.tsx index 14c8a97..cd4761e 100644 --- a/src/components/ai-elements/terminal.tsx +++ b/src/components/ai-elements/terminal.tsx @@ -5,7 +5,7 @@ import type { ComponentProps, HTMLAttributes } from "react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import Ansi from "ansi-to-react"; -import { CheckIcon, CopyIcon, TerminalIcon, Trash2Icon } from "lucide-react"; +import { Check, Copy, Terminal as TerminalIcon, Trash } from "@phosphor-icons/react"; import { createContext, useCallback, @@ -191,7 +191,7 @@ export const TerminalCopyButton = ({ [] ); - const Icon = isCopied ? CheckIcon : CopyIcon; + const Icon = isCopied ? Check : Copy; return ( ); }; diff --git a/src/components/ai-elements/tool.tsx b/src/components/ai-elements/tool.tsx index 0b2b7de..7a0c266 100644 --- a/src/components/ai-elements/tool.tsx +++ b/src/components/ai-elements/tool.tsx @@ -11,13 +11,13 @@ import { } from "@/components/ui/collapsible"; import { cn } from "@/lib/utils"; import { - CheckCircleIcon, - ChevronDownIcon, - CircleIcon, - ClockIcon, - WrenchIcon, - XCircleIcon, -} from "lucide-react"; + CheckCircle, + CaretDown, + Circle, + Clock, + Wrench, + XCircle, +} from "@phosphor-icons/react"; import { createContext, isValidElement, useContext, useState } from "react"; import { CodeBlock } from "./code-block"; @@ -73,13 +73,13 @@ const statusLabels: Record = { }; const statusIcons: Record = { - "approval-requested": , - "approval-responded": , - "input-available": , - "input-streaming": , - "output-available": , - "output-denied": , - "output-error": , + "approval-requested": , + "approval-responded": , + "input-available": , + "input-streaming": , + "output-available": , + "output-denied": , + "output-error": , }; export const getStatusBadge = (status: ToolPart["state"]) => ( @@ -109,11 +109,11 @@ export const ToolHeader = ({ {...props} >
- + {title ?? derivedName} {getStatusBadge(state)}
- + ); }; diff --git a/src/components/layout/ChatListPanel.tsx b/src/components/layout/ChatListPanel.tsx index 507ff66..ec2478e 100644 --- a/src/components/layout/ChatListPanel.tsx +++ b/src/components/layout/ChatListPanel.tsx @@ -14,8 +14,9 @@ import { Plus, FolderOpen, UserCircle, + Columns, + X, } from "@phosphor-icons/react"; -import { Columns2, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; @@ -453,7 +454,7 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) { {isSplitActive && (
- + {t('split.splitGroup')}
@@ -660,7 +661,7 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) { }); }} > - + )} {/* Streaming indicator: hidden when hover shows split icon */} diff --git a/src/components/layout/ErrorBoundary.tsx b/src/components/layout/ErrorBoundary.tsx index c280aeb..759b1e0 100644 --- a/src/components/layout/ErrorBoundary.tsx +++ b/src/components/layout/ErrorBoundary.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useState } from "react"; -import { CircleAlert } from "lucide-react"; +import { WarningCircle } from "@phosphor-icons/react"; import { useTranslation } from "@/hooks/useTranslation"; interface ErrorBoundaryProps { @@ -30,7 +30,7 @@ function ErrorFallback({
- +

diff --git a/src/components/layout/SplitColumn.tsx b/src/components/layout/SplitColumn.tsx index 32e6b08..f26944a 100644 --- a/src/components/layout/SplitColumn.tsx +++ b/src/components/layout/SplitColumn.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState, useCallback } from "react"; -import { X } from "lucide-react"; +import { X } from "@phosphor-icons/react"; import type { Message, MessagesResponse, ChatSession } from "@/types"; import { ChatView } from "@/components/chat/ChatView"; import { Button } from "@/components/ui/button"; diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx index 8cb4ca7..1012b0c 100644 --- a/src/components/ui/command.tsx +++ b/src/components/ui/command.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { Command as CommandPrimitive } from "cmdk" -import { SearchIcon } from "lucide-react" +import { MagnifyingGlass } from "@phosphor-icons/react" import { cn } from "@/lib/utils" import { @@ -69,7 +69,7 @@ function CommandInput({ data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3" > - + - + Close )} diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index bffc327..f36340d 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" +import { Check, CaretRight, Circle } from "@phosphor-icons/react" import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui" import { cn } from "@/lib/utils" @@ -100,7 +100,7 @@ function DropdownMenuCheckboxItem({ > - + {children} @@ -135,7 +135,7 @@ function DropdownMenuRadioItem({ > - + {children} @@ -217,7 +217,7 @@ function DropdownMenuSubTrigger({ {...props} > {children} - + ) } diff --git a/src/components/ui/icon.tsx b/src/components/ui/icon.tsx new file mode 100644 index 0000000..da42d9b --- /dev/null +++ b/src/components/ui/icon.tsx @@ -0,0 +1,24 @@ +// Unified icon re-exports + size constants for the project +export { + Check, + X, + CaretDown, + CaretUp, + CaretRight, + MagnifyingGlass, + SpinnerGap, + ArrowElbowDownLeft, + Image, + Brain, + Terminal, + Book, + DotOutline, + Circle, + Plus, + Copy, + Trash, +} from "@phosphor-icons/react"; + +export type { Icon, IconProps } from "@phosphor-icons/react"; + +export const ICON_SIZE = { sm: 14, md: 16, lg: 20, xl: 24 } as const; diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index fd01b74..3d4bb51 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" +import { Check, CaretDown, CaretUp } from "@phosphor-icons/react" import { Select as SelectPrimitive } from "radix-ui" import { cn } from "@/lib/utils" @@ -44,7 +44,7 @@ function SelectTrigger({ > {children} - + ) @@ -119,7 +119,7 @@ function SelectItem({ className="absolute right-2 flex size-3.5 items-center justify-center" > - + {children} @@ -153,7 +153,7 @@ function SelectScrollUpButton({ )} {...props} > - + ) } @@ -171,7 +171,7 @@ function SelectScrollDownButton({ )} {...props} > - + ) } diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx index 5963090..a0c5232 100644 --- a/src/components/ui/sheet.tsx +++ b/src/components/ui/sheet.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import { XIcon } from "lucide-react" +import { X } from "@phosphor-icons/react" import { Dialog as SheetPrimitive } from "radix-ui" import { cn } from "@/lib/utils" @@ -76,7 +76,7 @@ function SheetContent({ {children} {showCloseButton && ( - + Close )} diff --git a/src/components/ui/spinner.tsx b/src/components/ui/spinner.tsx index a70e713..5654b43 100644 --- a/src/components/ui/spinner.tsx +++ b/src/components/ui/spinner.tsx @@ -1,10 +1,10 @@ -import { Loader2Icon } from "lucide-react" +import { SpinnerGap } from "@phosphor-icons/react" import { cn } from "@/lib/utils" function Spinner({ className, ...props }: React.ComponentProps<"svg">) { return ( -