feat: migrate icon library from Hugeicons to Phosphor + UI polish

Icon library migration:
- Replace @hugeicons/react with @phosphor-icons/react across 60+ components
  (Phosphor supports 6 weight variants: thin/light/regular/bold/fill/duotone)
- Update apps/site (marketing) to use Phosphor icons
- Remove leftover svg.hugeicons CSS rules from globals.css

UI improvements:
- NavRail: selected tab icons use filled weight, unselected use regular
- User chat bubble: dark bg + white text in light mode, light bg + dark text
  in dark mode (new --user-bubble / --user-bubble-foreground CSS variables)
- Fix expand/collapse icons: use CaretRight (triangle) instead of ArrowRight
  in file-tree.tsx and tool-actions-group.tsx

Lint fixes:
- Fix eslint react-hooks/static-components in CodeBlock, ToolCallBlock,
  tool-actions-group — use createElement() instead of PascalCase variable
- Fix react-hooks/rules-of-hooks in MessageItem — move early return after hooks
- Fix react-hooks/set-state-in-effect in GalleryDetail — use ref pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-03-11 11:12:12 +08:00
1 parent 5adeb3548e
commit e5a48ca6af
61 files changed
+606 -752

No files matched your search

+13 -14
View File
@@ -8,14 +8,13 @@ import {
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Folder01Icon,
FolderOpenIcon,
File01Icon,
ArrowRight01Icon,
PlusSignIcon,
} from "@hugeicons/core-free-icons";
Folder,
FolderOpen,
File,
CaretRight,
Plus,
} from "@phosphor-icons/react";
import {
createContext,
useCallback,
@@ -155,10 +154,10 @@ export const FileTreeFolder = ({
className="shrink-0 rounded p-0.5 hover:bg-muted"
onClick={(e) => e.stopPropagation()}
>
<HugeiconsIcon
icon={ArrowRight01Icon}
<CaretRight
size={16}
className={cn(
"size-4 text-muted-foreground transition-transform",
"text-muted-foreground transition-transform",
isExpanded && "rotate-90"
)}
/>
@@ -166,9 +165,9 @@ export const FileTreeFolder = ({
</CollapsibleTrigger>
<FileTreeIcon>
{isExpanded ? (
<HugeiconsIcon icon={FolderOpenIcon} className="size-4 text-muted-foreground" />
<FolderOpen size={16} className="text-muted-foreground" />
) : (
<HugeiconsIcon icon={Folder01Icon} className="size-4 text-muted-foreground" />
<Folder size={16} className="text-muted-foreground" />
)}
</FileTreeIcon>
<FileTreeName>{name}</FileTreeName>
@@ -249,7 +248,7 @@ export const FileTreeFile = ({
{children ?? (
<>
<FileTreeIcon>
{icon ?? <HugeiconsIcon icon={File01Icon} className="size-4 text-muted-foreground" />}
{icon ?? <File size={16} className="text-muted-foreground" />}
</FileTreeIcon>
<FileTreeName>{name}</FileTreeName>
{onAdd && (
@@ -259,7 +258,7 @@ export const FileTreeFile = ({
onClick={handleAdd}
title="Add to chat"
>
<HugeiconsIcon icon={PlusSignIcon} className="size-3 text-muted-foreground" />
<Plus size={12} className="text-muted-foreground" />
</button>
)}
</>
+1 -1
View File
@@ -56,7 +56,7 @@ export const MessageContent = ({
<div
className={cn(
"is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
"group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
"group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-[var(--user-bubble)] group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-[var(--user-bubble-foreground)]",
"group-[.is-assistant]:w-full group-[.is-assistant]:text-foreground",
className
)}
@@ -1,20 +1,19 @@
'use client';
import { useState } from 'react';
import { useState, createElement } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { HugeiconsIcon } from "@hugeicons/react";
import type { IconSvgElement } from "@hugeicons/react";
import type { Icon } from "@phosphor-icons/react";
import {
File01Icon,
FileEditIcon,
CommandLineIcon,
Search01Icon,
Wrench01Icon,
Loading02Icon,
CheckmarkCircle02Icon,
CancelCircleIcon,
ArrowRight01Icon,
} from "@hugeicons/core-free-icons";
File,
NotePencil,
Terminal,
MagnifyingGlass,
Wrench,
SpinnerGap,
CheckCircle,
XCircle,
CaretRight,
} from "@phosphor-icons/react";
import { cn } from '@/lib/utils';
// ---------------------------------------------------------------------------
@@ -61,13 +60,13 @@ function getToolCategory(name: string): ToolCategory {
return 'other';
}
function getToolIcon(category: ToolCategory): IconSvgElement {
function getToolIcon(category: ToolCategory): Icon {
switch (category) {
case 'read': return File01Icon;
case 'write': return FileEditIcon;
case 'bash': return CommandLineIcon;
case 'search': return Search01Icon;
case 'other': return Wrench01Icon;
case 'read': return File;
case 'write': return NotePencil;
case 'bash': return Terminal;
case 'search': return MagnifyingGlass;
case 'other': return Wrench;
}
}
@@ -130,12 +129,12 @@ function StatusDot({ status }: { status: ToolStatus }) {
switch (status) {
case 'running':
return (
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 shrink-0 animate-spin text-muted-foreground/50" />
<SpinnerGap size={14} className="shrink-0 animate-spin text-muted-foreground/50" />
);
case 'success':
return <HugeiconsIcon icon={CheckmarkCircle02Icon} className="h-3.5 w-3.5 shrink-0 text-green-500" />;
return <CheckCircle size={14} className="shrink-0 text-green-500" />;
case 'error':
return <HugeiconsIcon icon={CancelCircleIcon} className="h-3.5 w-3.5 shrink-0 text-red-500" />;
return <XCircle size={14} className="shrink-0 text-red-500" />;
}
}
@@ -145,7 +144,7 @@ function StatusDot({ status }: { status: ToolStatus }) {
function ToolActionRow({ tool }: { tool: ToolAction }) {
const category = getToolCategory(tool.name);
const icon = getToolIcon(category);
const toolIcon = getToolIcon(category);
const summary = getToolSummary(tool.name, tool.input, category);
const filePath = getFilePath(tool.input);
const status = getStatus(tool);
@@ -154,7 +153,7 @@ function ToolActionRow({ tool }: { tool: ToolAction }) {
return (
<div className="flex items-center gap-2 px-2 py-1 min-h-[28px] text-xs hover:bg-muted/30 rounded-sm transition-colors">
<HugeiconsIcon icon={icon} className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
{createElement(toolIcon, { size: 14, className: "shrink-0 text-muted-foreground" })}
{label && (
<span className="font-medium text-muted-foreground shrink-0">{label}</span>
@@ -229,10 +228,10 @@ export function ToolActionsGroup({
onClick={handleToggle}
className="flex w-full items-center gap-2 py-1 text-xs rounded-sm hover:bg-muted/30 transition-colors"
>
<HugeiconsIcon
icon={ArrowRight01Icon}
<CaretRight
size={12}
className={cn(
"h-3 w-3 shrink-0 text-muted-foreground/60 transition-transform duration-200",
"shrink-0 text-muted-foreground/60 transition-transform duration-200",
expanded && "rotate-90"
)}
/>