Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .cursor/skills/icon-usage/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
name: icon-usage
description: >-
Enforce centralized icon usage through the Icon component. Use when adding,
using, or referencing icons in any component. All icons must go through
src/components/Icon.tsx — never import react-icons directly in other files.
---

# Icon Usage Convention

## Rule

**All icons in this project MUST be used through the centralized `Icon` component at `src/components/Icon.tsx`.**

Never import icons directly from `react-icons/*` or any icon library in page/feature components.

## How to Use an Icon

```tsx
import Icon from '@/components/Icon';

<Icon icon="github" size={18} />
<Icon icon="heart" size={12} className="text-danger" />
```

## Adding a New Icon

When you need an icon that doesn't exist in the `Icon` component yet, update `src/components/Icon.tsx`:

1. Add the import from `react-icons/lu` (Lucide) or the appropriate library
2. Add the icon name to the `IconType` union type (keep alphabetical order)
3. Add the `case` in the `renderIcon` switch statement (keep alphabetical order)
4. Use `<Icon icon="your-new-icon" />` in your component

## Prohibited Pattern

```tsx
// WRONG — never do this in page/feature components
import { LuGithub, LuHeart } from 'react-icons/lu';

<LuGithub size={18} />
```

## Existing Icon Names

Check the `IconType` union in `src/components/Icon.tsx` for all available icons. The naming convention follows Lucide's kebab-case (e.g. `circle-check-big`, `external-link`, `file-text`).

## Icon Libraries

- Primary: `react-icons/lu` (Lucide icons)
- Secondary: `react-icons/md` (Material Design, for icons not in Lucide)
- AI providers: `@lobehub/icons-static-svg` (OpenAI, Anthropic, etc.)
15 changes: 14 additions & 1 deletion main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,7 @@ app.whenReady().then(() => {
// Create context menu for tray
const contextMenu = Menu.buildFromTemplate([
{
label: 'Settings ',
label: 'Settings... ',
click: () => {
showSettingsWindow();
},
Expand All @@ -717,6 +717,19 @@ app.whenReady().then(() => {
// Set the context menu
tray.setContextMenu(contextMenu);

// Set macOS Dock icon right-click menu
if (process.platform === 'darwin' && app.dock) {
const dockMenu = Menu.buildFromTemplate([
{
label: 'Settings...',
click: () => {
showSettingsWindow();
},
},
]);
app.dock.setMenu(dockMenu);
}

createSettingsWindow();
if (!openAtLoginService.isLoginLaunch()) {
showSettingsWindow();
Expand Down
34 changes: 33 additions & 1 deletion src/components/Icon.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import {
LuBot,
LuBookOpen,
LuBug,
LuEye,
LuEyeOff,
LuExternalLink,
LuGithub,
LuHeart,
LuTrash2,
LuSquarePen,
LuPlus,
Expand All @@ -13,6 +18,7 @@ import {
LuSquareDashed,
LuMessageCircle,
LuFileText,
LuScale,
LuSettings,
LuRotateCcw,
LuCloud,
Expand All @@ -24,6 +30,8 @@ import {
LuLightbulb,
LuLightbulbOff,
LuGlobe,
LuTag,
LuUser,
} from 'react-icons/lu';
import { MdOutlineCleaningServices } from 'react-icons/md';

Expand All @@ -36,17 +44,23 @@ import Qwen from '@lobehub/icons-static-svg/icons/qwen.svg?react';
import Ollama from '@lobehub/icons-static-svg/icons/ollama.svg?react';

type IconType =
| 'book-open'
| 'bot'
| 'bug'
| 'cog'
| 'eye'
| 'eye-off'
| 'external-link'
| 'flame'
| 'file-text'
| 'github'
| 'heart'
| 'trash-2'
| 'square-pen'
| 'square-dashed'
| 'message-circle'
| 'plus'
| 'scale'
| 'square'
| 'circle-check-big'
| 'circle-x'
Expand All @@ -68,7 +82,9 @@ type IconType =
| 'arrow-up'
| 'lightbulb'
| 'lightbulb-off'
| 'globe';
| 'globe'
| 'tag'
| 'user';

interface IconProps {
className?: string;
Expand All @@ -91,10 +107,20 @@ function Icon({
}: IconProps) {
const renderIcon = () => {
switch (icon) {
case 'book-open':
return <LuBookOpen className={svgClassName} color={color} size={size} />;
case 'bug':
return <LuBug className={svgClassName} color={color} size={size} />;
case 'eye':
return <LuEye className={svgClassName} color={color} size={size} />;
case 'eye-off':
return <LuEyeOff className={svgClassName} color={color} size={size} />;
case 'external-link':
return <LuExternalLink className={svgClassName} color={color} size={size} />;
case 'github':
return <LuGithub className={svgClassName} color={color} size={size} />;
case 'heart':
return <LuHeart className={svgClassName} color={color} size={size} />;
case 'trash-2':
return <LuTrash2 className={svgClassName} color={color} size={size} />;
case 'square-pen':
Expand All @@ -119,6 +145,8 @@ function Icon({
return <LuMessageCircle className={svgClassName} color={color} size={size} />;
case 'file-text':
return <LuFileText className={svgClassName} color={color} size={size} />;
case 'scale':
return <LuScale className={svgClassName} color={color} size={size} />;
case 'openai':
return <OpenAI className={svgClassName} fill={color} width={size} height={size} />;
case 'anthropic':
Expand Down Expand Up @@ -157,6 +185,10 @@ function Icon({
return <LuLightbulbOff className={svgClassName} color={color} size={size} />;
case 'globe':
return <LuGlobe className={svgClassName} color={color} size={size} />;
case 'tag':
return <LuTag className={svgClassName} color={color} size={size} />;
case 'user':
return <LuUser className={svgClassName} color={color} size={size} />;
default:
return null;
}
Expand Down
14 changes: 13 additions & 1 deletion src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,19 @@
},
"others": {
"title": "Others",
"showLogFiles": "Show Log Files"
"version": "Version",
"author": "Author",
"license": "License",
"links": "Links",
"githubRepo": "GitHub Repository",
"homepage": "Homepage",
"changelog": "Changelog & Releases",
"reportIssues": "Report Issues",
"discord": "Discord Community",
"diagnostics": "Diagnostics",
"diagnosticsDescription": "View application logs for troubleshooting.",
"showLogFiles": "Show Log Files",
"madeWith": "Made with"
}
},
"chat": {
Expand Down
14 changes: 13 additions & 1 deletion src/i18n/locales/zh-hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,19 @@
},
"others": {
"title": "其他",
"showLogFiles": "显示日志文件"
"version": "版本",
"author": "作者",
"license": "许可证",
"links": "链接",
"githubRepo": "GitHub 仓库",
"homepage": "官方网站",
"changelog": "更新日志",
"reportIssues": "反馈问题",
"discord": "Discord 社区",
"diagnostics": "诊断",
"diagnosticsDescription": "查看应用日志以排查问题。",
"showLogFiles": "显示日志文件",
"madeWith": "Made with"
}
},
"chat": {
Expand Down
14 changes: 13 additions & 1 deletion src/i18n/locales/zh-hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,19 @@
},
"others": {
"title": "其他",
"showLogFiles": "顯示日誌文件"
"version": "版本",
"author": "作者",
"license": "許可證",
"links": "連結",
"githubRepo": "GitHub 倉庫",
"homepage": "官方網站",
"changelog": "更新日誌",
"reportIssues": "反饋問題",
"discord": "Discord 社群",
"diagnostics": "診斷",
"diagnosticsDescription": "查看應用日誌以排查問題。",
"showLogFiles": "顯示日誌文件",
"madeWith": "Made with"
}
},
"chat": {
Expand Down
Loading
Loading