mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 05:39:05 +08:00
refactor: restructure UI components and remove deprecated files
- Added new components including CodeEditor, CollapsibleSearchBar, and DraggableList. - Removed obsolete components such as Button, CustomCollapse, and StatusTag. - Updated migration status documentation and adjusted component exports. - Enhanced package.json dependencies for better compatibility.
This commit is contained in:
parent
57b9ca111a
commit
b9a947d2fd
@ -1,152 +1,151 @@
|
||||
# UI 组件库迁移状态
|
||||
# UI Component Library Migration Status
|
||||
|
||||
## 使用示例
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
// 从 @cherrystudio/ui 导入组件
|
||||
import { Spinner, DividerWithText, InfoTooltip, CustomTag } from '@cherrystudio/ui'
|
||||
// Import components from @cherrystudio/ui
|
||||
import { Spinner, DividerWithText, InfoTooltip } from '@cherrystudio/ui'
|
||||
|
||||
// 在组件中使用
|
||||
// Use in components
|
||||
function MyComponent() {
|
||||
return (
|
||||
<div>
|
||||
<Spinner size={24} />
|
||||
<DividerWithText text="分隔文本" />
|
||||
<InfoTooltip content="提示信息" />
|
||||
<CustomTag color="var(--color-primary)">标签</CustomTag>
|
||||
<DividerWithText text="Divider Text" />
|
||||
<InfoTooltip content="Tooltip message" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 目录结构说明
|
||||
## Directory Structure
|
||||
|
||||
```text
|
||||
@packages/ui/
|
||||
├── src/
|
||||
│ ├── components/ # 组件主目录
|
||||
│ │ ├── base/ # 基础组件(按钮、输入框、标签等)
|
||||
│ │ ├── display/ # 显示组件(卡片、列表、表格等)
|
||||
│ │ ├── layout/ # 布局组件(容器、网格、间距等)
|
||||
│ │ ├── icons/ # 图标组件
|
||||
│ │ ├── interactive/ # 交互组件(弹窗、提示、下拉等)
|
||||
│ │ └── composite/ # 复合组件(多个基础组件组合而成)
|
||||
│ ├── hooks/ # 自定义 React Hooks
|
||||
│ └── types/ # TypeScript 类型定义
|
||||
│ ├── components/ # Main components directory
|
||||
│ │ ├── base/ # Basic components (buttons, inputs, labels, etc.)
|
||||
│ │ ├── display/ # Display components (cards, lists, tables, etc.)
|
||||
│ │ ├── layout/ # Layout components (containers, grids, spacing, etc.)
|
||||
│ │ ├── icons/ # Icon components
|
||||
│ │ ├── interactive/ # Interactive components (modals, tooltips, dropdowns, etc.)
|
||||
│ │ └── composite/ # Composite components (made from multiple base components)
|
||||
│ ├── hooks/ # Custom React Hooks
|
||||
│ └── types/ # TypeScript type definitions
|
||||
```
|
||||
|
||||
### 组件分类指南
|
||||
### Component Classification Guide
|
||||
|
||||
提交 PR 时,请根据组件功能将其放入正确的目录:
|
||||
When submitting PRs, please place components in the correct directory based on their function:
|
||||
|
||||
- **base**: 最基础的 UI 元素,如按钮、输入框、开关、标签等
|
||||
- **display**: 用于展示内容的组件,如卡片、列表、表格、标签页等
|
||||
- **layout**: 用于页面布局的组件,如容器、网格系统、分隔符等
|
||||
- **icons**: 所有图标相关的组件
|
||||
- **interactive**: 需要用户交互的组件,如模态框、抽屉、提示框、下拉菜单等
|
||||
- **composite**: 复合组件,由多个基础组件组合而成
|
||||
- **base**: Most basic UI elements like buttons, inputs, switches, labels, etc.
|
||||
- **display**: Components for displaying content like cards, lists, tables, tabs, etc.
|
||||
- **layout**: Components for page layout like containers, grid systems, dividers, etc.
|
||||
- **icons**: All icon-related components
|
||||
- **interactive**: Components requiring user interaction like modals, drawers, tooltips, dropdowns, etc.
|
||||
- **composite**: Composite components made from multiple base components
|
||||
|
||||
## 迁移概览
|
||||
## Migration Overview
|
||||
|
||||
- **总组件数**: 236
|
||||
- **已迁移**: 34
|
||||
- **已重构**: 18
|
||||
- **待迁移**: 184
|
||||
- **Total Components**: 236
|
||||
- **Migrated**: 34
|
||||
- **Refactored**: 18
|
||||
- **Pending Migration**: 184
|
||||
|
||||
## 组件状态表
|
||||
## Component Status Table
|
||||
|
||||
| Category | Component Name | Migration Status | Refactoring Status | Description |
|
||||
| --------------- | ------------------------- | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **base** | | | | 基础组件 |
|
||||
| | CopyButton | ✅ | ✅ | 复制按钮 |
|
||||
| | CustomTag | ✅ | ✅ | 自定义标签 |
|
||||
| | DividerWithText | ✅ | ✅ | 带文本的分隔线 |
|
||||
| | EmojiIcon | ✅ | ✅ | 表情图标 |
|
||||
| | ErrorBoundary | ✅ | ✅ | 错误边界 (通过 props 解耦) |
|
||||
| | StatusTag | ✅ | ✅ | 统一状态标签(合并了 ErrorTag、SuccessTag、WarnTag、InfoTag) |
|
||||
| | IndicatorLight | ✅ | ✅ | 指示灯 |
|
||||
| | Spinner | ✅ | ✅ | 加载动画 |
|
||||
| | TextBadge | ✅ | ✅ | 文本徽标 |
|
||||
| | CustomCollapse | ✅ | ✅ | 自定义折叠面板 |
|
||||
| **display** | | | | 显示组件 |
|
||||
| | Ellipsis | ✅ | ✅ | 文本省略 |
|
||||
| | ExpandableText | ✅ | ✅ | 可展开文本 |
|
||||
| | ThinkingEffect | ✅ | ✅ | 思考效果动画 |
|
||||
| | EmojiAvatar | ✅ | ✅ | 表情头像 |
|
||||
| | ListItem | ✅ | ✅ | 列表项 |
|
||||
| | MaxContextCount | ✅ | ✅ | 最大上下文数显示 |
|
||||
| | ProviderAvatar | ✅ | ✅ | 提供者头像 |
|
||||
| | CodeViewer | ❌ | ❌ | 代码查看器 (外部依赖) |
|
||||
| | OGCard | ❌ | ❌ | OG 卡片 |
|
||||
| | MarkdownShadowDOMRenderer | ❌ | ❌ | Markdown 渲染器 |
|
||||
| | Preview/* | ❌ | ❌ | 预览组件 |
|
||||
| **layout** | | | | 布局组件 |
|
||||
| | HorizontalScrollContainer | ✅ | ❌ | 水平滚动容器 |
|
||||
| | Scrollbar | ✅ | ❌ | 滚动条 |
|
||||
| | Layout/* | ✅ | ✅ | 布局组件 |
|
||||
| | Tab/* | ❌ | ❌ | 标签页 (Redux 依赖) |
|
||||
| | TopView | ❌ | ❌ | 顶部视图 (window.api 依赖) |
|
||||
| **icons** | | | | 图标组件 |
|
||||
| | Icon | ✅ | ✅ | 图标工厂函数和预定义图标(合并了 CopyIcon、DeleteIcon、EditIcon、RefreshIcon、ResetIcon、ToolIcon、VisionIcon、WebSearchIcon、WrapIcon、UnWrapIcon、OcrIcon) |
|
||||
| | FileIcons | ✅ | ❌ | 文件图标 (FileSvgIcon、FilePngIcon) |
|
||||
| | ReasoningIcon | ✅ | ❌ | 推理图标 |
|
||||
| | SvgSpinners180Ring | ✅ | ❌ | 旋转加载图标 |
|
||||
| | ToolsCallingIcon | ✅ | ❌ | 工具调用图标 |
|
||||
| **interactive** | | | | 交互组件 |
|
||||
| | InfoTooltip | ✅ | ❌ | 信息提示 |
|
||||
| | HelpTooltip | ✅ | ❌ | 帮助提示 |
|
||||
| | WarnTooltip | ✅ | ❌ | 警告提示 |
|
||||
| | EditableNumber | ✅ | ❌ | 可编辑数字 |
|
||||
| | InfoPopover | ✅ | ❌ | 信息弹出框 |
|
||||
| | CollapsibleSearchBar | ✅ | ❌ | 可折叠搜索栏 |
|
||||
| | ImageToolButton | ✅ | ❌ | 图片工具按钮 |
|
||||
| | DraggableList | ✅ | ❌ | 可拖拽列表 |
|
||||
| | CodeEditor | ✅ | ❌ | 代码编辑器 |
|
||||
| | EmojiPicker | ❌ | ❌ | 表情选择器 (useTheme 依赖) |
|
||||
| | Selector | ✅ | ❌ | 选择器 (i18n 依赖) |
|
||||
| | ModelSelector | ❌ | ❌ | 模型选择器 (Redux 依赖) |
|
||||
| | LanguageSelect | ❌ | ❌ | 语言选择 |
|
||||
| | TranslateButton | ❌ | ❌ | 翻译按钮 (window.api 依赖) |
|
||||
| **composite** | | | | 复合组件 |
|
||||
| | - | - | - | 暂无复合组件 |
|
||||
| **未分类** | | | | 需要分类的组件 |
|
||||
| | Popups/* (16+ 文件) | ❌ | ❌ | 弹窗组件 (业务耦合) |
|
||||
| | RichEditor/* (30+ 文件) | ❌ | ❌ | 富文本编辑器 |
|
||||
| | MarkdownEditor/* | ❌ | ❌ | Markdown 编辑器 |
|
||||
| | MinApp/* | ❌ | ❌ | 迷你应用 (Redux 依赖) |
|
||||
| | Avatar/* | ❌ | ❌ | 头像组件 |
|
||||
| | ActionTools/* | ❌ | ❌ | 操作工具 |
|
||||
| | CodeBlockView/* | ❌ | ❌ | 代码块视图 (window.api 依赖) |
|
||||
| | ContextMenu | ❌ | ❌ | 右键菜单 (Electron API) |
|
||||
| | WindowControls | ❌ | ❌ | 窗口控制 (Electron API) |
|
||||
| | ErrorBoundary | ❌ | ❌ | 错误边界 (window.api 依赖) |
|
||||
| Category | Component Name | Migration Status | Refactoring Status | Description |
|
||||
| ----------------- | ------------------------- | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **base** | | | | Base components |
|
||||
| | CopyButton | ✅ | ✅ | Copy button |
|
||||
| | CustomTag | ✅ | ✅ | Custom tag |
|
||||
| | DividerWithText | ✅ | ✅ | Divider with text |
|
||||
| | EmojiIcon | ✅ | ✅ | Emoji icon |
|
||||
| | ErrorBoundary | ✅ | ✅ | Error boundary (decoupled via props) |
|
||||
| | StatusTag | ✅ | ✅ | Unified status tag (merged ErrorTag, SuccessTag, WarnTag, InfoTag) |
|
||||
| | IndicatorLight | ✅ | ✅ | Indicator light |
|
||||
| | Spinner | ✅ | ✅ | Loading spinner |
|
||||
| | TextBadge | ✅ | ✅ | Text badge |
|
||||
| | CustomCollapse | ✅ | ✅ | Custom collapse panel |
|
||||
| **display** | | | | Display components |
|
||||
| | Ellipsis | ✅ | ✅ | Text ellipsis |
|
||||
| | ExpandableText | ✅ | ✅ | Expandable text |
|
||||
| | ThinkingEffect | ✅ | ✅ | Thinking effect animation |
|
||||
| | EmojiAvatar | ✅ | ✅ | Emoji avatar |
|
||||
| | ListItem | ✅ | ✅ | List item |
|
||||
| | MaxContextCount | ✅ | ✅ | Max context count display |
|
||||
| | ProviderAvatar | ✅ | ✅ | Provider avatar |
|
||||
| | CodeViewer | ❌ | ❌ | Code viewer (external deps) |
|
||||
| | OGCard | ❌ | ❌ | OG card |
|
||||
| | MarkdownShadowDOMRenderer | ❌ | ❌ | Markdown renderer |
|
||||
| | Preview/* | ❌ | ❌ | Preview components |
|
||||
| **layout** | | | | Layout components |
|
||||
| | HorizontalScrollContainer | ✅ | ❌ | Horizontal scroll container |
|
||||
| | Scrollbar | ✅ | ❌ | Scrollbar |
|
||||
| | Layout/* | ✅ | ✅ | Layout components |
|
||||
| | Tab/* | ❌ | ❌ | Tab (Redux dependency) |
|
||||
| | TopView | ❌ | ❌ | Top view (window.api dependency) |
|
||||
| **icons** | | | | Icon components |
|
||||
| | Icon | ✅ | ✅ | Icon factory function and predefined icons (merged CopyIcon, DeleteIcon, EditIcon, RefreshIcon, ResetIcon, ToolIcon, VisionIcon, WebSearchIcon, WrapIcon, UnWrapIcon, OcrIcon) |
|
||||
| | FileIcons | ✅ | ❌ | File icons (FileSvgIcon, FilePngIcon) |
|
||||
| | ReasoningIcon | ✅ | ❌ | Reasoning icon |
|
||||
| | SvgSpinners180Ring | ✅ | ❌ | Spinner loading icon |
|
||||
| | ToolsCallingIcon | ✅ | ❌ | Tools calling icon |
|
||||
| **interactive** | | | | Interactive components |
|
||||
| | InfoTooltip | ✅ | ❌ | Info tooltip |
|
||||
| | HelpTooltip | ✅ | ❌ | Help tooltip |
|
||||
| | WarnTooltip | ✅ | ❌ | Warning tooltip |
|
||||
| | EditableNumber | ✅ | ❌ | Editable number |
|
||||
| | InfoPopover | ✅ | ❌ | Info popover |
|
||||
| | CollapsibleSearchBar | ✅ | ❌ | Collapsible search bar |
|
||||
| | ImageToolButton | ✅ | ❌ | Image tool button |
|
||||
| | DraggableList | ✅ | ❌ | Draggable list |
|
||||
| | CodeEditor | ✅ | ❌ | Code editor |
|
||||
| | EmojiPicker | ❌ | ❌ | Emoji picker (useTheme dependency) |
|
||||
| | Selector | ✅ | ❌ | Selector (i18n dependency) |
|
||||
| | ModelSelector | ❌ | ❌ | Model selector (Redux dependency) |
|
||||
| | LanguageSelect | ❌ | ❌ | Language select |
|
||||
| | TranslateButton | ❌ | ❌ | Translate button (window.api dependency) |
|
||||
| **composite** | | | | Composite components |
|
||||
| | - | - | - | No composite components yet |
|
||||
| **Uncategorized** | | | | Components needing categorization |
|
||||
| | Popups/* (16+ files) | ❌ | ❌ | Popup components (business coupled) |
|
||||
| | RichEditor/* (30+ files) | ❌ | ❌ | Rich text editor |
|
||||
| | MarkdownEditor/* | ❌ | ❌ | Markdown editor |
|
||||
| | MinApp/* | ❌ | ❌ | Mini app (Redux dependency) |
|
||||
| | Avatar/* | ❌ | ❌ | Avatar components |
|
||||
| | ActionTools/* | ❌ | ❌ | Action tools |
|
||||
| | CodeBlockView/* | ❌ | ❌ | Code block view (window.api dependency) |
|
||||
| | ContextMenu | ❌ | ❌ | Context menu (Electron API) |
|
||||
| | WindowControls | ❌ | ❌ | Window controls (Electron API) |
|
||||
| | ErrorBoundary | ❌ | ❌ | Error boundary (window.api dependency) |
|
||||
|
||||
## 迁移步骤
|
||||
## Migration Steps
|
||||
|
||||
### 第一阶段:复制迁移(当前阶段)
|
||||
### Phase 1: Copy Migration (Current Phase)
|
||||
|
||||
- 将组件原样复制到 @packages/ui
|
||||
- 保留原有依赖(antd、styled-components 等)
|
||||
- 在文件顶部添加原路径注释
|
||||
- Copy components as-is to @packages/ui
|
||||
- Retain original dependencies (antd, styled-components, etc.)
|
||||
- Add original path comment at file top
|
||||
|
||||
### 第二阶段:重构优化
|
||||
### Phase 2: Refactor and Optimize
|
||||
|
||||
- 移除 antd 依赖,替换为 HeroUI
|
||||
- 移除 styled-components,替换为 Tailwind CSS
|
||||
- 优化组件 API 和类型定义
|
||||
- Remove antd dependencies, replace with HeroUI
|
||||
- Remove styled-components, replace with Tailwind CSS
|
||||
- Optimize component APIs and type definitions
|
||||
|
||||
## 注意事项
|
||||
## Notes
|
||||
|
||||
1. **不迁移**包含以下依赖的组件(解耦后可迁移):
|
||||
- window.api 调用
|
||||
- Redux(useSelector、useDispatch 等)
|
||||
- 其他外部数据源
|
||||
1. **Do NOT migrate** components with these dependencies (can be migrated after decoupling):
|
||||
- window.api calls
|
||||
- Redux (useSelector, useDispatch, etc.)
|
||||
- Other external data sources
|
||||
|
||||
2. **可迁移**但需要后续解耦的组件:
|
||||
- 使用 i18n 的组件(将 i18n 改为 props 传入)
|
||||
- 使用 antd 的组件(后续替换为 HeroUI)
|
||||
2. **Can migrate** but need decoupling later:
|
||||
- Components using i18n (change i18n to props)
|
||||
- Components using antd (replace with HeroUI later)
|
||||
|
||||
3. **提交规范**:
|
||||
- 每次 PR 专注于一个类别的组件
|
||||
- 确保所有迁移的组件都有导出
|
||||
- 更新此文档的迁移状态
|
||||
3. **Submission Guidelines**:
|
||||
- Each PR should focus on one category of components
|
||||
- Ensure all migrated components are exported
|
||||
- Update migration status in this document
|
||||
|
||||
@ -1,151 +0,0 @@
|
||||
# UI Component Library Migration Status
|
||||
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
// Import components from @cherrystudio/ui
|
||||
import { Spinner, DividerWithText, InfoTooltip } from '@cherrystudio/ui'
|
||||
|
||||
// Use in components
|
||||
function MyComponent() {
|
||||
return (
|
||||
<div>
|
||||
<Spinner size={24} />
|
||||
<DividerWithText text="Divider Text" />
|
||||
<InfoTooltip content="Tooltip message" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```text
|
||||
@packages/ui/
|
||||
├── src/
|
||||
│ ├── components/ # Main components directory
|
||||
│ │ ├── base/ # Basic components (buttons, inputs, labels, etc.)
|
||||
│ │ ├── display/ # Display components (cards, lists, tables, etc.)
|
||||
│ │ ├── layout/ # Layout components (containers, grids, spacing, etc.)
|
||||
│ │ ├── icons/ # Icon components
|
||||
│ │ ├── interactive/ # Interactive components (modals, tooltips, dropdowns, etc.)
|
||||
│ │ └── composite/ # Composite components (made from multiple base components)
|
||||
│ ├── hooks/ # Custom React Hooks
|
||||
│ └── types/ # TypeScript type definitions
|
||||
```
|
||||
|
||||
### Component Classification Guide
|
||||
|
||||
When submitting PRs, please place components in the correct directory based on their function:
|
||||
|
||||
- **base**: Most basic UI elements like buttons, inputs, switches, labels, etc.
|
||||
- **display**: Components for displaying content like cards, lists, tables, tabs, etc.
|
||||
- **layout**: Components for page layout like containers, grid systems, dividers, etc.
|
||||
- **icons**: All icon-related components
|
||||
- **interactive**: Components requiring user interaction like modals, drawers, tooltips, dropdowns, etc.
|
||||
- **composite**: Composite components made from multiple base components
|
||||
|
||||
## Migration Overview
|
||||
|
||||
- **Total Components**: 236
|
||||
- **Migrated**: 34
|
||||
- **Refactored**: 18
|
||||
- **Pending Migration**: 184
|
||||
|
||||
## Component Status Table
|
||||
|
||||
| Category | Component Name | Migration Status | Refactoring Status | Description |
|
||||
| ----------------- | ------------------------- | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **base** | | | | Base components |
|
||||
| | CopyButton | ✅ | ✅ | Copy button |
|
||||
| | CustomTag | ✅ | ✅ | Custom tag |
|
||||
| | DividerWithText | ✅ | ✅ | Divider with text |
|
||||
| | EmojiIcon | ✅ | ✅ | Emoji icon |
|
||||
| | ErrorBoundary | ✅ | ✅ | Error boundary (decoupled via props) |
|
||||
| | StatusTag | ✅ | ✅ | Unified status tag (merged ErrorTag, SuccessTag, WarnTag, InfoTag) |
|
||||
| | IndicatorLight | ✅ | ✅ | Indicator light |
|
||||
| | Spinner | ✅ | ✅ | Loading spinner |
|
||||
| | TextBadge | ✅ | ✅ | Text badge |
|
||||
| | CustomCollapse | ✅ | ✅ | Custom collapse panel |
|
||||
| **display** | | | | Display components |
|
||||
| | Ellipsis | ✅ | ✅ | Text ellipsis |
|
||||
| | ExpandableText | ✅ | ✅ | Expandable text |
|
||||
| | ThinkingEffect | ✅ | ✅ | Thinking effect animation |
|
||||
| | EmojiAvatar | ✅ | ✅ | Emoji avatar |
|
||||
| | ListItem | ✅ | ✅ | List item |
|
||||
| | MaxContextCount | ✅ | ✅ | Max context count display |
|
||||
| | ProviderAvatar | ✅ | ✅ | Provider avatar |
|
||||
| | CodeViewer | ❌ | ❌ | Code viewer (external deps) |
|
||||
| | OGCard | ❌ | ❌ | OG card |
|
||||
| | MarkdownShadowDOMRenderer | ❌ | ❌ | Markdown renderer |
|
||||
| | Preview/* | ❌ | ❌ | Preview components |
|
||||
| **layout** | | | | Layout components |
|
||||
| | HorizontalScrollContainer | ✅ | ❌ | Horizontal scroll container |
|
||||
| | Scrollbar | ✅ | ❌ | Scrollbar |
|
||||
| | Layout/* | ✅ | ✅ | Layout components |
|
||||
| | Tab/* | ❌ | ❌ | Tab (Redux dependency) |
|
||||
| | TopView | ❌ | ❌ | Top view (window.api dependency) |
|
||||
| **icons** | | | | Icon components |
|
||||
| | Icon | ✅ | ✅ | Icon factory function and predefined icons (merged CopyIcon, DeleteIcon, EditIcon, RefreshIcon, ResetIcon, ToolIcon, VisionIcon, WebSearchIcon, WrapIcon, UnWrapIcon, OcrIcon) |
|
||||
| | FileIcons | ✅ | ❌ | File icons (FileSvgIcon, FilePngIcon) |
|
||||
| | ReasoningIcon | ✅ | ❌ | Reasoning icon |
|
||||
| | SvgSpinners180Ring | ✅ | ❌ | Spinner loading icon |
|
||||
| | ToolsCallingIcon | ✅ | ❌ | Tools calling icon |
|
||||
| **interactive** | | | | Interactive components |
|
||||
| | InfoTooltip | ✅ | ❌ | Info tooltip |
|
||||
| | HelpTooltip | ✅ | ❌ | Help tooltip |
|
||||
| | WarnTooltip | ✅ | ❌ | Warning tooltip |
|
||||
| | EditableNumber | ✅ | ❌ | Editable number |
|
||||
| | InfoPopover | ✅ | ❌ | Info popover |
|
||||
| | CollapsibleSearchBar | ✅ | ❌ | Collapsible search bar |
|
||||
| | ImageToolButton | ✅ | ❌ | Image tool button |
|
||||
| | DraggableList | ✅ | ❌ | Draggable list |
|
||||
| | CodeEditor | ✅ | ❌ | Code editor |
|
||||
| | EmojiPicker | ❌ | ❌ | Emoji picker (useTheme dependency) |
|
||||
| | Selector | ✅ | ❌ | Selector (i18n dependency) |
|
||||
| | ModelSelector | ❌ | ❌ | Model selector (Redux dependency) |
|
||||
| | LanguageSelect | ❌ | ❌ | Language select |
|
||||
| | TranslateButton | ❌ | ❌ | Translate button (window.api dependency) |
|
||||
| **composite** | | | | Composite components |
|
||||
| | - | - | - | No composite components yet |
|
||||
| **Uncategorized** | | | | Components needing categorization |
|
||||
| | Popups/* (16+ files) | ❌ | ❌ | Popup components (business coupled) |
|
||||
| | RichEditor/* (30+ files) | ❌ | ❌ | Rich text editor |
|
||||
| | MarkdownEditor/* | ❌ | ❌ | Markdown editor |
|
||||
| | MinApp/* | ❌ | ❌ | Mini app (Redux dependency) |
|
||||
| | Avatar/* | ❌ | ❌ | Avatar components |
|
||||
| | ActionTools/* | ❌ | ❌ | Action tools |
|
||||
| | CodeBlockView/* | ❌ | ❌ | Code block view (window.api dependency) |
|
||||
| | ContextMenu | ❌ | ❌ | Context menu (Electron API) |
|
||||
| | WindowControls | ❌ | ❌ | Window controls (Electron API) |
|
||||
| | ErrorBoundary | ❌ | ❌ | Error boundary (window.api dependency) |
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### Phase 1: Copy Migration (Current Phase)
|
||||
|
||||
- Copy components as-is to @packages/ui
|
||||
- Retain original dependencies (antd, styled-components, etc.)
|
||||
- Add original path comment at file top
|
||||
|
||||
### Phase 2: Refactor and Optimize
|
||||
|
||||
- Remove antd dependencies, replace with HeroUI
|
||||
- Remove styled-components, replace with Tailwind CSS
|
||||
- Optimize component APIs and type definitions
|
||||
|
||||
## Notes
|
||||
|
||||
1. **Do NOT migrate** components with these dependencies (can be migrated after decoupling):
|
||||
- window.api calls
|
||||
- Redux (useSelector, useDispatch, etc.)
|
||||
- Other external data sources
|
||||
|
||||
2. **Can migrate** but need decoupling later:
|
||||
- Components using i18n (change i18n to props)
|
||||
- Components using antd (replace with HeroUI later)
|
||||
|
||||
3. **Submission Guidelines**:
|
||||
- Each PR should focus on one category of components
|
||||
- Ensure all migrated components are exported
|
||||
- Update migration status in this document
|
||||
@ -4,7 +4,7 @@
|
||||
"components": "@cherrystudio/ui/components",
|
||||
"hooks": "@cherrystudio/ui/hooks",
|
||||
"lib": "@cherrystudio/ui/lib",
|
||||
"ui": "@cherrystudio/ui/components/ui",
|
||||
"ui": "@cherrystudio/ui/components/primitives",
|
||||
"utils": "@cherrystudio/ui/utils"
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import type { ButtonProps as HeroUIButtonProps } from '@heroui/react'
|
||||
import { Button as HeroUIButton } from '@heroui/react'
|
||||
|
||||
export interface ButtonProps extends HeroUIButtonProps {}
|
||||
|
||||
const Button = ({ ...props }: ButtonProps) => {
|
||||
return <HeroUIButton {...props} />
|
||||
}
|
||||
|
||||
Button.displayName = 'Button'
|
||||
|
||||
export default Button
|
||||
@ -1,46 +0,0 @@
|
||||
import { Accordion, AccordionItem, type AccordionItemProps, type AccordionProps } from '@heroui/react'
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
|
||||
// 重新导出 HeroUI 的组件,方便直接使用
|
||||
export { Accordion, AccordionItem } from '@heroui/react'
|
||||
|
||||
interface CustomCollapseProps {
|
||||
children: React.ReactNode
|
||||
accordionProps?: Omit<AccordionProps, 'children'>
|
||||
accordionItemProps?: Omit<AccordionItemProps, 'children'>
|
||||
}
|
||||
|
||||
const CustomCollapse: FC<CustomCollapseProps> = ({ children, accordionProps = {}, accordionItemProps = {} }) => {
|
||||
// 解构 Accordion 的 props
|
||||
const {
|
||||
defaultExpandedKeys = ['1'],
|
||||
variant = 'bordered',
|
||||
className = '',
|
||||
isDisabled = false,
|
||||
...restAccordionProps
|
||||
} = accordionProps
|
||||
|
||||
// 解构 AccordionItem 的 props
|
||||
const { title = 'Collapse Panel', ...restAccordionItemProps } = accordionItemProps
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
defaultExpandedKeys={defaultExpandedKeys}
|
||||
variant={variant}
|
||||
className={className}
|
||||
isDisabled={isDisabled}
|
||||
selectionMode="multiple"
|
||||
{...restAccordionProps}>
|
||||
<AccordionItem
|
||||
key="1"
|
||||
aria-label={typeof title === 'string' ? title : 'collapse-item'}
|
||||
title={title}
|
||||
{...restAccordionItemProps}>
|
||||
{children}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(CustomCollapse)
|
||||
@ -1,333 +0,0 @@
|
||||
# Selector 组件
|
||||
|
||||
基于 HeroUI Select 封装的下拉选择组件,简化了 Set 和 Selection 的转换逻辑。
|
||||
|
||||
## 核心特性
|
||||
|
||||
- ✅ **类型安全**: 单选和多选自动推断回调类型
|
||||
- ✅ **智能转换**: 自动处理 `Set<Key>` 和原始值的转换
|
||||
- ✅ **HeroUI 风格**: 保持与 HeroUI 生态一致的 API
|
||||
- ✅ **支持数字和字符串**: 泛型支持,自动识别值类型
|
||||
|
||||
## 基础用法
|
||||
|
||||
### 单选模式(默认)
|
||||
|
||||
```tsx
|
||||
import { Selector } from '@cherrystudio/ui'
|
||||
import { useState } from 'react'
|
||||
|
||||
function Example() {
|
||||
const [language, setLanguage] = useState('zh-CN')
|
||||
|
||||
const languageOptions = [
|
||||
{ label: '中文', value: 'zh-CN' },
|
||||
{ label: 'English', value: 'en-US' },
|
||||
{ label: '日本語', value: 'ja-JP' }
|
||||
]
|
||||
|
||||
return (
|
||||
<Selector
|
||||
selectedKeys={language}
|
||||
onSelectionChange={(value) => {
|
||||
// value 类型自动推断为 string
|
||||
setLanguage(value)
|
||||
}}
|
||||
items={languageOptions}
|
||||
placeholder="选择语言"
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 多选模式
|
||||
|
||||
```tsx
|
||||
import { Selector } from '@cherrystudio/ui'
|
||||
import { useState } from 'react'
|
||||
|
||||
function Example() {
|
||||
const [languages, setLanguages] = useState(['zh-CN', 'en-US'])
|
||||
|
||||
const languageOptions = [
|
||||
{ label: '中文', value: 'zh-CN' },
|
||||
{ label: 'English', value: 'en-US' },
|
||||
{ label: '日本語', value: 'ja-JP' },
|
||||
{ label: 'Français', value: 'fr-FR' }
|
||||
]
|
||||
|
||||
return (
|
||||
<Selector
|
||||
selectionMode="multiple"
|
||||
selectedKeys={languages}
|
||||
onSelectionChange={(values) => {
|
||||
// values 类型自动推断为 string[]
|
||||
setLanguages(values)
|
||||
}}
|
||||
items={languageOptions}
|
||||
placeholder="选择语言"
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 数字类型值
|
||||
|
||||
```tsx
|
||||
import { Selector } from '@cherrystudio/ui'
|
||||
|
||||
function Example() {
|
||||
const [priority, setPriority] = useState<number>(1)
|
||||
|
||||
const priorityOptions = [
|
||||
{ label: '低', value: 1 },
|
||||
{ label: '中', value: 2 },
|
||||
{ label: '高', value: 3 }
|
||||
]
|
||||
|
||||
return (
|
||||
<Selector<number>
|
||||
selectedKeys={priority}
|
||||
onSelectionChange={(value) => {
|
||||
// value 类型为 number
|
||||
setPriority(value)
|
||||
}}
|
||||
items={priorityOptions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 禁用选项
|
||||
|
||||
```tsx
|
||||
const options = [
|
||||
{ label: '选项 1', value: '1' },
|
||||
{ label: '选项 2 (禁用)', value: '2', disabled: true },
|
||||
{ label: '选项 3', value: '3' }
|
||||
]
|
||||
|
||||
<Selector
|
||||
selectedKeys="1"
|
||||
onSelectionChange={handleChange}
|
||||
items={options}
|
||||
/>
|
||||
```
|
||||
|
||||
### 自定义 Label
|
||||
|
||||
```tsx
|
||||
import { Flex } from '@cherrystudio/ui'
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: (
|
||||
<Flex className="items-center gap-2">
|
||||
<span>🇨🇳</span>
|
||||
<span>中文</span>
|
||||
</Flex>
|
||||
),
|
||||
value: 'zh-CN'
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Flex className="items-center gap-2">
|
||||
<span>🇺🇸</span>
|
||||
<span>English</span>
|
||||
</Flex>
|
||||
),
|
||||
value: 'en-US'
|
||||
}
|
||||
]
|
||||
|
||||
<Selector
|
||||
selectedKeys="zh-CN"
|
||||
onSelectionChange={handleChange}
|
||||
items={options}
|
||||
/>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### SelectorProps
|
||||
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `items` | `SelectorItem<V>[]` | - | 必填,选项列表 |
|
||||
| `selectedKeys` | `V` \| `V[]` | - | 受控的选中值(单选为单个值,多选为数组) |
|
||||
| `onSelectionChange` | `(key: V) => void` \| `(keys: V[]) => void` | - | 选择变化回调(类型根据 selectionMode 自动推断) |
|
||||
| `selectionMode` | `'single'` \| `'multiple'` | `'single'` | 选择模式 |
|
||||
| `placeholder` | `string` | - | 占位文本 |
|
||||
| `disabled` | `boolean` | `false` | 是否禁用 |
|
||||
| `isRequired` | `boolean` | `false` | 是否必填 |
|
||||
| `label` | `ReactNode` | - | 标签文本 |
|
||||
| `description` | `ReactNode` | - | 描述文本 |
|
||||
| `errorMessage` | `ReactNode` | - | 错误提示 |
|
||||
| ...rest | `SelectProps` | - | 其他 HeroUI Select 属性 |
|
||||
|
||||
### SelectorItem
|
||||
|
||||
```tsx
|
||||
interface SelectorItem<V = string | number> {
|
||||
label: string | ReactNode // 显示文本或自定义内容
|
||||
value: V // 选项值
|
||||
disabled?: boolean // 是否禁用
|
||||
[key: string]: any // 其他自定义属性
|
||||
}
|
||||
```
|
||||
|
||||
## 类型安全
|
||||
|
||||
组件使用 TypeScript 条件类型,根据 `selectionMode` 自动推断回调类型:
|
||||
|
||||
```tsx
|
||||
// 单选模式
|
||||
<Selector
|
||||
selectionMode="single" // 或省略(默认单选)
|
||||
selectedKeys={value} // 类型: V
|
||||
onSelectionChange={(v) => ...} // v 类型: V
|
||||
/>
|
||||
|
||||
// 多选模式
|
||||
<Selector
|
||||
selectionMode="multiple"
|
||||
selectedKeys={values} // 类型: V[]
|
||||
onSelectionChange={(vs) => ...} // vs 类型: V[]
|
||||
/>
|
||||
```
|
||||
|
||||
## 与 HeroUI Select 的区别
|
||||
|
||||
| 特性 | HeroUI Select | Selector (本组件) |
|
||||
|------|---------------|------------------|
|
||||
| `selectedKeys` | `Set<Key> \| 'all'` | `V` \| `V[]` (自动转换) |
|
||||
| `onSelectionChange` | `(keys: Selection) => void` | `(key: V) => void` \| `(keys: V[]) => void` |
|
||||
| 单选回调 | 返回 `Set` (需手动提取) | 直接返回单个值 |
|
||||
| 多选回调 | 返回 `Set` (需转数组) | 直接返回数组 |
|
||||
| 类型推断 | 无 | 根据 selectionMode 自动推断 |
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 显式声明 selectionMode
|
||||
|
||||
虽然单选是默认模式,但建议显式声明以提高代码可读性:
|
||||
|
||||
```tsx
|
||||
// ✅ 推荐
|
||||
<Selector selectionMode="single" ... />
|
||||
|
||||
// ⚠️ 可以但不够清晰
|
||||
<Selector ... />
|
||||
```
|
||||
|
||||
### 2. 使用泛型指定值类型
|
||||
|
||||
当值类型为数字或联合类型时,使用泛型获得更好的类型提示:
|
||||
|
||||
```tsx
|
||||
// ✅ 推荐
|
||||
<Selector<number> selectedKeys={priority} ... />
|
||||
|
||||
// ✅ 推荐(联合类型)
|
||||
type Status = 'pending' | 'approved' | 'rejected'
|
||||
<Selector<Status> selectedKeys={status} ... />
|
||||
```
|
||||
|
||||
### 3. 避免在渲染时创建 items
|
||||
|
||||
```tsx
|
||||
// ❌ 不推荐(每次渲染都创建新数组)
|
||||
<Selector items={[{ label: 'A', value: '1' }]} />
|
||||
|
||||
// ✅ 推荐(在组件外或使用 useMemo)
|
||||
const items = [{ label: 'A', value: '1' }]
|
||||
<Selector items={items} />
|
||||
```
|
||||
|
||||
## 迁移指南
|
||||
|
||||
### 从 antd Select 迁移
|
||||
|
||||
```tsx
|
||||
// antd Select
|
||||
import { Select } from 'antd'
|
||||
|
||||
<Select
|
||||
value={value}
|
||||
onChange={(value) => onChange(value)}
|
||||
options={[
|
||||
{ label: 'A', value: '1' },
|
||||
{ label: 'B', value: '2' }
|
||||
]}
|
||||
/>
|
||||
|
||||
// 迁移到 Selector
|
||||
import { Selector } from '@cherrystudio/ui'
|
||||
|
||||
<Selector
|
||||
selectedKeys={value} // value → selectedKeys
|
||||
onSelectionChange={(value) => onChange(value)} // onChange → onSelectionChange
|
||||
items={[ // options → items
|
||||
{ label: 'A', value: '1' },
|
||||
{ label: 'B', value: '2' }
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### 从旧版 Selector 迁移
|
||||
|
||||
```tsx
|
||||
// 旧版 Selector (返回数组)
|
||||
<Selector
|
||||
onSelectionChange={(values) => {
|
||||
const value = values[0] // 需要手动提取
|
||||
onChange(value)
|
||||
}}
|
||||
/>
|
||||
|
||||
// 新版 Selector (直接返回值)
|
||||
<Selector
|
||||
selectionMode="single"
|
||||
onSelectionChange={(value) => {
|
||||
onChange(value) // 直接使用
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 为什么单选模式下还需要 selectedKeys 而不是 selectedKey?
|
||||
|
||||
A: 为了保持与 HeroUI API 命名的一致性,同时简化组件实现。组件内部会自动处理单个值和 Set 的转换。
|
||||
|
||||
### Q: 如何清空选择?
|
||||
|
||||
```tsx
|
||||
// 单选模式
|
||||
<Selector
|
||||
selectedKeys={value}
|
||||
onSelectionChange={setValue}
|
||||
isClearable // 添加清空按钮
|
||||
/>
|
||||
|
||||
// 或手动设置为 undefined
|
||||
setValue(undefined)
|
||||
```
|
||||
|
||||
### Q: 支持异步加载选项吗?
|
||||
|
||||
支持,配合 `isLoading` 属性使用:
|
||||
|
||||
```tsx
|
||||
const [items, setItems] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
fetchItems().then(data => {
|
||||
setItems(data)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
<Selector items={items} isLoading={loading} />
|
||||
```
|
||||
@ -1,53 +0,0 @@
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
import { AlertTriangleIcon, CheckIcon, CircleXIcon, InfoIcon } from 'lucide-react'
|
||||
import React from 'react'
|
||||
|
||||
import CustomTag from '../CustomTag'
|
||||
|
||||
export type StatusType = 'success' | 'error' | 'warning' | 'info'
|
||||
|
||||
export interface StatusTagProps {
|
||||
type: StatusType
|
||||
message: string
|
||||
iconSize?: number
|
||||
icon?: React.ReactNode
|
||||
color?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const statusConfig: Record<StatusType, { Icon: LucideIcon; color: string }> = {
|
||||
success: { Icon: CheckIcon, color: '#10B981' }, // green-500
|
||||
error: { Icon: CircleXIcon, color: '#EF4444' }, // red-500
|
||||
warning: { Icon: AlertTriangleIcon, color: '#F59E0B' }, // amber-500
|
||||
info: { Icon: InfoIcon, color: '#3B82F6' } // blue-500
|
||||
}
|
||||
|
||||
export const StatusTag: React.FC<StatusTagProps> = ({ type, message, iconSize = 14, icon, color, className }) => {
|
||||
const config = statusConfig[type]
|
||||
const Icon = config.Icon
|
||||
const finalColor = color || config.color
|
||||
const finalIcon = icon || <Icon size={iconSize} color={finalColor} />
|
||||
|
||||
return (
|
||||
<CustomTag icon={finalIcon} color={finalColor} className={className}>
|
||||
{message}
|
||||
</CustomTag>
|
||||
)
|
||||
}
|
||||
|
||||
// 保留原有的导出以保持向后兼容
|
||||
export const SuccessTag = ({ iconSize, message }: { iconSize?: number; message: string }) => (
|
||||
<StatusTag type="success" iconSize={iconSize} message={message} />
|
||||
)
|
||||
|
||||
export const ErrorTag = ({ iconSize, message }: { iconSize?: number; message: string }) => (
|
||||
<StatusTag type="error" iconSize={iconSize} message={message} />
|
||||
)
|
||||
|
||||
export const WarnTag = ({ iconSize, message }: { iconSize?: number; message: string }) => (
|
||||
<StatusTag type="warning" iconSize={iconSize} message={message} />
|
||||
)
|
||||
|
||||
export const InfoTag = ({ iconSize, message }: { iconSize?: number; message: string }) => (
|
||||
<StatusTag type="info" iconSize={iconSize} message={message} />
|
||||
)
|
||||
@ -1,20 +0,0 @@
|
||||
// Original: src/renderer/src/components/TextBadge.tsx
|
||||
import type { FC } from 'react'
|
||||
|
||||
interface TextBadgeProps {
|
||||
text: string
|
||||
style?: React.CSSProperties
|
||||
className?: string
|
||||
}
|
||||
|
||||
const TextBadge: FC<TextBadgeProps> = ({ text, style, className = '' }) => {
|
||||
return (
|
||||
<span
|
||||
className={`text-xs text-blue-600 dark:text-blue-400 bg-blue-100 dark:bg-blue-900/30 px-1.5 py-0.5 rounded font-medium ${className}`}
|
||||
style={style}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default TextBadge
|
||||
@ -5,7 +5,7 @@ import { Search } from 'lucide-react'
|
||||
import { motion } from 'motion/react'
|
||||
import React, { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { Tooltip } from '../../base/Tooltip'
|
||||
import { Tooltip } from '../../primitives/tooltip'
|
||||
|
||||
interface CollapsibleSearchBarProps {
|
||||
onSearch: (text: string) => void
|
||||
@ -9,7 +9,7 @@ import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
|
||||
import { type ScrollToOptions, useVirtualizer, type VirtualItem } from '@tanstack/react-virtual'
|
||||
import { type Key, memo, useCallback, useImperativeHandle, useRef } from 'react'
|
||||
|
||||
import Scrollbar from '../../layout/Scrollbar'
|
||||
import Scrollbar from '../Scrollbar'
|
||||
import { droppableReorder } from './sort'
|
||||
|
||||
export interface DraggableVirtualListRef {
|
||||
@ -1,7 +1,7 @@
|
||||
// Original path: src/renderer/src/components/TooltipIcons/HelpTooltip.tsx
|
||||
import { HelpCircle } from 'lucide-react'
|
||||
|
||||
import { Tooltip } from '../../base/Tooltip'
|
||||
import { Tooltip } from '../../primitives/tooltip'
|
||||
import type { IconTooltipProps } from './types'
|
||||
|
||||
export const HelpTooltip = ({ iconProps, ...rest }: IconTooltipProps) => {
|
||||
@ -1,7 +1,7 @@
|
||||
// Original: src/renderer/src/components/TooltipIcons/InfoTooltip.tsx
|
||||
import { Info } from 'lucide-react'
|
||||
|
||||
import { Tooltip } from '../../base/Tooltip'
|
||||
import { Tooltip } from '../../primitives/tooltip'
|
||||
import type { IconTooltipProps } from './types'
|
||||
|
||||
export const InfoTooltip = ({ iconProps, ...rest }: IconTooltipProps) => {
|
||||
@ -1,7 +1,7 @@
|
||||
// Original path: src/renderer/src/components/TooltipIcons/WarnTooltip.tsx
|
||||
import { AlertTriangle } from 'lucide-react'
|
||||
|
||||
import { Tooltip } from '../../base/Tooltip'
|
||||
import { Tooltip } from '../../primitives/tooltip'
|
||||
import type { IconTooltipProps } from './types'
|
||||
|
||||
export const WarnTooltip = ({ iconProps, ...rest }: IconTooltipProps) => {
|
||||
@ -1,6 +1,6 @@
|
||||
import type { LucideProps } from 'lucide-react'
|
||||
|
||||
import type { TooltipProps } from '../../base/Tooltip'
|
||||
import type { TooltipProps } from '../../primitives/tooltip'
|
||||
|
||||
export interface IconTooltipProps extends TooltipProps {
|
||||
iconProps?: LucideProps
|
||||
@ -1,8 +1,8 @@
|
||||
// Original path: src/renderer/src/components/Preview/ImageToolButton.tsx
|
||||
import { memo } from 'react'
|
||||
|
||||
import Button from '../../base/Button'
|
||||
import { Tooltip } from '../../base/Tooltip'
|
||||
import { Button } from '../../primitives/button'
|
||||
import { Tooltip } from '../../primitives/tooltip'
|
||||
|
||||
interface ImageToolButtonProps {
|
||||
tooltip: string
|
||||
@ -13,7 +13,7 @@ interface ImageToolButtonProps {
|
||||
const ImageToolButton = ({ tooltip, icon, onPress }: ImageToolButtonProps) => {
|
||||
return (
|
||||
<Tooltip content={tooltip} delay={500} closeDelay={0}>
|
||||
<Button radius="full" isIconOnly onPress={onPress} aria-label={tooltip}>
|
||||
<Button size="icon" className="rounded-full" onClick={onPress} aria-label={tooltip}>
|
||||
{icon}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@ -1,33 +1,26 @@
|
||||
// Base Components
|
||||
export { Avatar, AvatarGroup, type AvatarProps, EmojiAvatar } from './base/Avatar'
|
||||
export { default as Button, type ButtonProps } from './base/Button'
|
||||
export { default as CopyButton } from './base/CopyButton'
|
||||
export { default as CustomCollapse } from './base/CustomCollapse'
|
||||
export { default as CustomTag } from './base/CustomTag'
|
||||
export { default as DividerWithText } from './base/DividerWithText'
|
||||
export { default as EmojiIcon } from './base/EmojiIcon'
|
||||
export type { CustomFallbackProps, ErrorBoundaryCustomizedProps } from './base/ErrorBoundary'
|
||||
export { ErrorBoundary } from './base/ErrorBoundary'
|
||||
export { default as IndicatorLight } from './base/IndicatorLight'
|
||||
export { default as Spinner } from './base/Spinner'
|
||||
export type { StatusTagProps, StatusType } from './base/StatusTag'
|
||||
export { ErrorTag, InfoTag, StatusTag, SuccessTag, WarnTag } from './base/StatusTag'
|
||||
export { DescriptionSwitch, Switch } from './base/Switch'
|
||||
export { default as TextBadge } from './base/TextBadge'
|
||||
export { getToastUtilities, type ToastUtilities } from './base/Toast'
|
||||
export { Tooltip, type TooltipProps } from './base/Tooltip'
|
||||
// Primitive Components
|
||||
export { Avatar, AvatarGroup, type AvatarProps, EmojiAvatar } from './primitives/Avatar'
|
||||
export { default as CopyButton } from './primitives/copyButton'
|
||||
export { default as CustomTag } from './primitives/customTag'
|
||||
export { default as DividerWithText } from './primitives/dividerWithText'
|
||||
export { default as EmojiIcon } from './primitives/emojiIcon'
|
||||
export type { CustomFallbackProps, ErrorBoundaryCustomizedProps } from './primitives/ErrorBoundary'
|
||||
export { ErrorBoundary } from './primitives/ErrorBoundary'
|
||||
export { default as IndicatorLight } from './primitives/indicatorLight'
|
||||
export { default as Spinner } from './primitives/spinner'
|
||||
export { DescriptionSwitch, Switch } from './primitives/switch'
|
||||
export { getToastUtilities, type ToastUtilities } from './primitives/toast'
|
||||
export { Tooltip, type TooltipProps } from './primitives/tooltip'
|
||||
|
||||
// Display Components
|
||||
export { default as Ellipsis } from './display/Ellipsis'
|
||||
export { default as ExpandableText } from './display/ExpandableText'
|
||||
export { default as ListItem } from './display/ListItem'
|
||||
export { default as MaxContextCount } from './display/MaxContextCount'
|
||||
export { default as ThinkingEffect } from './display/ThinkingEffect'
|
||||
|
||||
// Layout Components
|
||||
export { Box, Center, ColFlex, Flex, RowFlex, SpaceBetweenRowFlex } from './layout/Flex'
|
||||
export { default as HorizontalScrollContainer } from './layout/HorizontalScrollContainer'
|
||||
export { default as Scrollbar } from './layout/Scrollbar'
|
||||
// Composite Components
|
||||
export { default as Ellipsis } from './composites/Ellipsis'
|
||||
export { default as ExpandableText } from './composites/ExpandableText'
|
||||
export { Box, Center, ColFlex, Flex, RowFlex, SpaceBetweenRowFlex } from './composites/Flex'
|
||||
export { default as HorizontalScrollContainer } from './composites/HorizontalScrollContainer'
|
||||
export { default as ListItem } from './composites/ListItem'
|
||||
export { default as MaxContextCount } from './composites/MaxContextCount'
|
||||
export { default as Scrollbar } from './composites/Scrollbar'
|
||||
export { default as ThinkingEffect } from './composites/ThinkingEffect'
|
||||
|
||||
// Icon Components
|
||||
export { FilePngIcon, FileSvgIcon } from './icons/FileIcons'
|
||||
@ -49,11 +42,9 @@ export {
|
||||
export { default as SvgSpinners180Ring } from './icons/SvgSpinners180Ring'
|
||||
export { default as ToolsCallingIcon } from './icons/ToolsCallingIcon'
|
||||
|
||||
/* Interactive Components */
|
||||
|
||||
// Selector / SearchableSelector
|
||||
export { default as Selector } from './base/Selector'
|
||||
export { default as SearchableSelector } from './base/Selector/SearchableSelector'
|
||||
/* Selector Components */
|
||||
export { default as Selector } from './primitives/Selector'
|
||||
export { default as SearchableSelector } from './primitives/Selector/SearchableSelector'
|
||||
export type {
|
||||
MultipleSearchableSelectorProps,
|
||||
MultipleSelectorProps,
|
||||
@ -63,7 +54,9 @@ export type {
|
||||
SelectorProps,
|
||||
SingleSearchableSelectorProps,
|
||||
SingleSelectorProps
|
||||
} from './base/Selector/types'
|
||||
} from './primitives/Selector/types'
|
||||
|
||||
/* Additional Composite Components */
|
||||
// CodeEditor
|
||||
export {
|
||||
default as CodeEditor,
|
||||
@ -72,28 +65,25 @@ export {
|
||||
type CodeMirrorTheme,
|
||||
getCmThemeByName,
|
||||
getCmThemeNames
|
||||
} from './interactive/CodeEditor'
|
||||
} from './composites/CodeEditor'
|
||||
// CollapsibleSearchBar
|
||||
export { default as CollapsibleSearchBar } from './interactive/CollapsibleSearchBar'
|
||||
export { default as CollapsibleSearchBar } from './composites/CollapsibleSearchBar'
|
||||
// DraggableList
|
||||
export { DraggableList, useDraggableReorder } from './interactive/DraggableList'
|
||||
export { DraggableList, useDraggableReorder } from './composites/DraggableList'
|
||||
// EditableNumber
|
||||
export type { EditableNumberProps } from './interactive/EditableNumber'
|
||||
// EditableNumber
|
||||
export { default as EditableNumber } from './interactive/EditableNumber'
|
||||
export type { EditableNumberProps } from './composites/EditableNumber'
|
||||
export { default as EditableNumber } from './composites/EditableNumber'
|
||||
// Tooltip variants
|
||||
export { HelpTooltip, type IconTooltipProps, InfoTooltip, WarnTooltip } from './interactive/IconTooltips'
|
||||
export { HelpTooltip, type IconTooltipProps, InfoTooltip, WarnTooltip } from './composites/IconTooltips'
|
||||
// ImageToolButton
|
||||
export { default as ImageToolButton } from './interactive/ImageToolButton'
|
||||
export { default as ImageToolButton } from './composites/ImageToolButton'
|
||||
// Sortable
|
||||
export { Sortable } from './interactive/Sortable'
|
||||
export { Sortable } from './composites/Sortable'
|
||||
|
||||
// Composite Components (复合组件)
|
||||
// 暂无复合组件
|
||||
|
||||
// UI Components (shadcn)
|
||||
export * from './ui/button'
|
||||
export * from './ui/command'
|
||||
export * from './ui/dialog'
|
||||
export * from './ui/popover'
|
||||
export * from './ui/shadcn-io/dropzone'
|
||||
/* Shadcn Primitive Components */
|
||||
export * from './primitives/button'
|
||||
export * from './primitives/command'
|
||||
export * from './primitives/dialog'
|
||||
export * from './primitives/popover'
|
||||
export * from './primitives/radioGroup'
|
||||
export * from './primitives/shadcn-io/dropzone'
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { cn } from '@heroui/react'
|
||||
import React, { memo } from 'react'
|
||||
|
||||
import { cn } from '../../../utils'
|
||||
|
||||
interface EmojiAvatarProps {
|
||||
children: string
|
||||
size?: number
|
||||
@ -1,6 +1,7 @@
|
||||
import type { AvatarProps as HeroUIAvatarProps } from '@heroui/react'
|
||||
import { Avatar as HeroUIAvatar, AvatarGroup as HeroUIAvatarGroup, cn } from '@heroui/react'
|
||||
import { Avatar as HeroUIAvatar, AvatarGroup as HeroUIAvatarGroup } from '@heroui/react'
|
||||
|
||||
import { cn } from '../../../utils'
|
||||
import EmojiAvatar from './EmojiAvatar'
|
||||
|
||||
export interface AvatarProps extends Omit<HeroUIAvatarProps, 'size'> {
|
||||
@ -1,10 +1,10 @@
|
||||
// Original path: src/renderer/src/components/ErrorBoundary.tsx
|
||||
import { Button } from '@heroui/react'
|
||||
import { AlertTriangle } from 'lucide-react'
|
||||
import type { ComponentType, ReactNode } from 'react'
|
||||
import type { FallbackProps } from 'react-error-boundary'
|
||||
import { ErrorBoundary } from 'react-error-boundary'
|
||||
|
||||
import { Button } from '../button'
|
||||
import { formatErrorMessage } from './utils'
|
||||
|
||||
interface CustomFallbackProps extends FallbackProps {
|
||||
@ -35,12 +35,12 @@ const DefaultFallback: ComponentType<CustomFallbackProps> = (props: CustomFallba
|
||||
<p className="text-red-700 dark:text-red-300 text-sm mb-3">{formatErrorMessage(error)}</p>
|
||||
<div className="flex gap-2">
|
||||
{onDebugClick && (
|
||||
<Button size="sm" variant="flat" color="danger" onPress={onDebugClick}>
|
||||
<Button size="sm" variant="destructive" onClick={onDebugClick}>
|
||||
{debugButtonText}
|
||||
</Button>
|
||||
)}
|
||||
{onReloadClick && (
|
||||
<Button size="sm" variant="flat" color="danger" onPress={onReloadClick}>
|
||||
<Button size="sm" variant="destructive" onClick={onReloadClick}>
|
||||
{reloadButtonText}
|
||||
</Button>
|
||||
)}
|
||||
@ -4,7 +4,7 @@ import {
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@cherrystudio/ui/components/ui/dialog'
|
||||
} from '@cherrystudio/ui/components/primitives/dialog'
|
||||
import { cn } from '@cherrystudio/ui/utils'
|
||||
import { Command as CommandPrimitive } from 'cmdk'
|
||||
import { SearchIcon } from 'lucide-react'
|
||||
@ -1,8 +1,9 @@
|
||||
// Original path: src/renderer/src/components/CopyButton.tsx
|
||||
import { Tooltip } from '@heroui/react'
|
||||
import { Copy } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
|
||||
import { Tooltip } from './tooltip'
|
||||
|
||||
interface CopyButtonProps {
|
||||
tooltip?: string
|
||||
label?: string
|
||||
@ -1,9 +1,10 @@
|
||||
// Original path: src/renderer/src/components/Tags/CustomTag.tsx
|
||||
import { Tooltip } from '@heroui/react'
|
||||
import { X } from 'lucide-react'
|
||||
import type { CSSProperties, FC, MouseEventHandler } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
|
||||
import { Tooltip } from './tooltip'
|
||||
|
||||
export interface CustomTagProps {
|
||||
icon?: React.ReactNode
|
||||
children?: React.ReactNode | string
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { Button } from '@cherrystudio/ui/components/ui/button'
|
||||
import { Button } from '@cherrystudio/ui/components/primitives/button'
|
||||
import { cn } from '@cherrystudio/ui/utils/index'
|
||||
import { UploadIcon } from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { Button } from '../../../src/components'
|
||||
|
||||
const meta: Meta<typeof Button> = {
|
||||
title: 'Components/Base/Button',
|
||||
title: 'Components/Primitives/Button',
|
||||
component: Button,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
@ -12,30 +12,16 @@ const meta: Meta<typeof Button> = {
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: { type: 'select' },
|
||||
options: ['solid', 'bordered', 'light', 'flat', 'faded', 'shadow', 'ghost']
|
||||
},
|
||||
color: {
|
||||
control: { type: 'select' },
|
||||
options: ['default', 'primary', 'secondary', 'success', 'warning', 'danger']
|
||||
options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link']
|
||||
},
|
||||
size: {
|
||||
control: { type: 'select' },
|
||||
options: ['sm', 'md', 'lg']
|
||||
options: ['default', 'sm', 'lg', 'icon', 'icon-sm', 'icon-lg']
|
||||
},
|
||||
radius: {
|
||||
control: { type: 'select' },
|
||||
options: ['none', 'sm', 'md', 'lg', 'full']
|
||||
},
|
||||
isDisabled: {
|
||||
disabled: {
|
||||
control: { type: 'boolean' }
|
||||
},
|
||||
isLoading: {
|
||||
control: { type: 'boolean' }
|
||||
},
|
||||
fullWidth: {
|
||||
control: { type: 'boolean' }
|
||||
},
|
||||
isIconOnly: {
|
||||
asChild: {
|
||||
control: { type: 'boolean' }
|
||||
}
|
||||
}
|
||||
@ -55,27 +41,12 @@ export const Default: Story = {
|
||||
export const Variants: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button variant="solid">Solid</Button>
|
||||
<Button variant="bordered">Bordered</Button>
|
||||
<Button variant="light">Light</Button>
|
||||
<Button variant="flat">Flat</Button>
|
||||
<Button variant="faded">Faded</Button>
|
||||
<Button variant="shadow">Shadow</Button>
|
||||
<Button variant="default">Default</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="destructive">Destructive</Button>
|
||||
<Button variant="outline">Outline</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 不同颜色
|
||||
export const Colors: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button color="default">Default</Button>
|
||||
<Button color="primary">Primary</Button>
|
||||
<Button color="secondary">Secondary</Button>
|
||||
<Button color="success">Success</Button>
|
||||
<Button color="warning">Warning</Button>
|
||||
<Button color="danger">Danger</Button>
|
||||
<Button variant="link">Link</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -83,23 +54,21 @@ export const Colors: Story = {
|
||||
// 不同尺寸
|
||||
export const Sizes: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="flex gap-2 items-center flex-wrap">
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="md">Medium</Button>
|
||||
<Button size="default">Default</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 不同圆角
|
||||
export const Radius: Story = {
|
||||
// 图标按钮
|
||||
export const IconButtons: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button radius="none">None</Button>
|
||||
<Button radius="sm">Small</Button>
|
||||
<Button radius="md">Medium</Button>
|
||||
<Button radius="lg">Large</Button>
|
||||
<Button radius="full">Full</Button>
|
||||
<div className="flex gap-2 items-center flex-wrap">
|
||||
<Button size="icon-sm">🔍</Button>
|
||||
<Button size="icon">🔍</Button>
|
||||
<Button size="icon-lg">🔍</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -109,8 +78,7 @@ export const States: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button>Normal</Button>
|
||||
<Button isDisabled>Disabled</Button>
|
||||
<Button isLoading>Loading</Button>
|
||||
<Button disabled>Disabled</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -119,9 +87,15 @@ export const States: Story = {
|
||||
export const WithIcons: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button startContent={<span>📧</span>}>Email</Button>
|
||||
<Button endContent={<span>→</span>}>Next</Button>
|
||||
<Button isIconOnly>🔍</Button>
|
||||
<Button>
|
||||
<span className="mr-2">📧</span>
|
||||
Email
|
||||
</Button>
|
||||
<Button>
|
||||
Next
|
||||
<span className="ml-2">→</span>
|
||||
</Button>
|
||||
<Button size="icon">🔍</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -130,9 +104,7 @@ export const WithIcons: Story = {
|
||||
export const FullWidth: Story = {
|
||||
render: () => (
|
||||
<div className="w-96">
|
||||
<Button fullWidth color="primary">
|
||||
Full Width Button
|
||||
</Button>
|
||||
<Button className="w-full">Full Width Button</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -141,10 +113,45 @@ export const FullWidth: Story = {
|
||||
export const Interactive: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button onPress={() => alert('Button pressed!')}>Click Me</Button>
|
||||
<Button onPress={() => console.log('Primary action')} color="primary" variant="solid">
|
||||
<Button onClick={() => alert('Button clicked!')}>Click Me</Button>
|
||||
<Button onClick={() => console.log('Primary action')} variant="default">
|
||||
Primary Action
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 组合示例
|
||||
export const Combinations: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button variant="default" size="sm">
|
||||
Small Default
|
||||
</Button>
|
||||
<Button variant="destructive" size="sm">
|
||||
Small Destructive
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
Small Outline
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button variant="default">Default</Button>
|
||||
<Button variant="destructive">Destructive</Button>
|
||||
<Button variant="outline">Outline</Button>
|
||||
</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button variant="default" size="lg">
|
||||
Large Default
|
||||
</Button>
|
||||
<Button variant="destructive" size="lg">
|
||||
Large Destructive
|
||||
</Button>
|
||||
<Button variant="outline" size="lg">
|
||||
Large Outline
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import CopyButton from '../../../src/components/base/CopyButton'
|
||||
import CopyButton from '../../../src/components/primitives/copyButton'
|
||||
|
||||
const meta: Meta<typeof CopyButton> = {
|
||||
title: 'Base/CopyButton',
|
||||
|
||||
@ -1,459 +0,0 @@
|
||||
import { Button } from '@heroui/react'
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { AlertTriangle, CreditCard, Info, Monitor, Settings, Shield } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import CustomCollapse, { Accordion, AccordionItem } from '../../../src/components/base/CustomCollapse'
|
||||
|
||||
const meta: Meta<typeof CustomCollapse> = {
|
||||
title: 'Base/CustomCollapse',
|
||||
component: CustomCollapse,
|
||||
parameters: {
|
||||
layout: 'padded'
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
children: {
|
||||
control: false,
|
||||
description: '面板内容'
|
||||
},
|
||||
accordionProps: {
|
||||
control: false,
|
||||
description: 'Accordion 组件的属性'
|
||||
},
|
||||
accordionItemProps: {
|
||||
control: false,
|
||||
description: 'AccordionItem 组件的属性'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// 基础用法
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
accordionItemProps: {
|
||||
title: '默认折叠面板'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这是折叠面板的内容。</p>
|
||||
<p>可以包含任何内容,包括文本、图片、表单等。</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 带副标题
|
||||
export const WithSubtitle: Story = {
|
||||
args: {
|
||||
accordionProps: {
|
||||
defaultExpandedKeys: ['1']
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: '带副标题的折叠面板',
|
||||
subtitle: <span className="text-sm text-gray-500">这是副标题内容</span>
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>面板内容</p>
|
||||
<p>可以在 subtitle 属性中设置副标题</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// HeroUI 样式变体
|
||||
export const VariantLight: Story = {
|
||||
args: {
|
||||
accordionProps: {
|
||||
variant: 'light'
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: 'Light 变体'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这是 HeroUI 的 Light 变体样式。</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const VariantShadow: Story = {
|
||||
args: {
|
||||
accordionProps: {
|
||||
variant: 'shadow',
|
||||
className: 'p-2'
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: 'Shadow 变体',
|
||||
subtitle: '带阴影的面板样式'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这是 HeroUI 的 Shadow 变体样式。</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const VariantBordered: Story = {
|
||||
args: {
|
||||
accordionProps: {
|
||||
variant: 'bordered'
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: 'Bordered 变体(默认)'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这是 HeroUI 的 Bordered 变体样式。</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const VariantSplitted: Story = {
|
||||
args: {
|
||||
accordionProps: {
|
||||
variant: 'splitted'
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: 'Splitted 变体'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这是 HeroUI 的 Splitted 变体样式。</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 富内容标题
|
||||
export const RichLabel: Story = {
|
||||
args: {
|
||||
accordionItemProps: {
|
||||
title: (
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="text-default-500" size={20} />
|
||||
<span>设置面板</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span>通知设置</span>
|
||||
<Button size="sm" variant="flat">
|
||||
开启
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span>自动更新</span>
|
||||
<Button size="sm" variant="flat">
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 带警告提示
|
||||
export const WithWarning: Story = {
|
||||
args: {
|
||||
accordionItemProps: {
|
||||
title: (
|
||||
<div className="flex items-center gap-2">
|
||||
<Monitor className="text-primary" size={20} />
|
||||
<span>连接的设备</span>
|
||||
</div>
|
||||
),
|
||||
subtitle: (
|
||||
<p className="flex">
|
||||
2个问题需要<span className="text-primary ml-1">立即修复</span>
|
||||
</p>
|
||||
)
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p className="text-small">检测到以下设备连接异常:</p>
|
||||
<ul className="list-disc list-inside mt-2 text-small space-y-1">
|
||||
<li>外部显示器连接不稳定</li>
|
||||
<li>蓝牙键盘配对失败</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 禁用状态
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
accordionProps: {
|
||||
isDisabled: true,
|
||||
defaultExpandedKeys: ['1']
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: '禁用的折叠面板'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这个面板被禁用了,无法操作。</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 受控模式
|
||||
export const ControlledMode: Story = {
|
||||
render: function ControlledMode() {
|
||||
const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set(['1']))
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" onPress={() => setSelectedKeys(new Set(['1']))} color="primary">
|
||||
展开
|
||||
</Button>
|
||||
<Button size="sm" onPress={() => setSelectedKeys(new Set())} color="default">
|
||||
收起
|
||||
</Button>
|
||||
</div>
|
||||
<CustomCollapse
|
||||
accordionProps={{
|
||||
selectedKeys,
|
||||
onSelectionChange: (keys) => {
|
||||
if (keys !== 'all') {
|
||||
setSelectedKeys(keys as Set<string>)
|
||||
}
|
||||
}
|
||||
}}
|
||||
accordionItemProps={{
|
||||
title: '受控的折叠面板'
|
||||
}}>
|
||||
<div className="p-4">
|
||||
<p>这是一个受控的折叠面板</p>
|
||||
<p>通过按钮控制展开和收起状态</p>
|
||||
</div>
|
||||
</CustomCollapse>
|
||||
<div className="text-sm text-gray-600">当前状态:{selectedKeys.size > 0 ? '展开' : '收起'}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 多个单面板组合
|
||||
export const MultipleSinglePanels: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4">
|
||||
<CustomCollapse accordionProps={{ defaultExpandedKeys: ['1'] }} accordionItemProps={{ title: '第一个面板' }}>
|
||||
<div className="p-4">
|
||||
<p>第一个面板的内容</p>
|
||||
</div>
|
||||
</CustomCollapse>
|
||||
<CustomCollapse
|
||||
accordionItemProps={{
|
||||
title: '第二个面板',
|
||||
subtitle: '带副标题'
|
||||
}}>
|
||||
<div className="p-4">
|
||||
<p>第二个面板的内容</p>
|
||||
</div>
|
||||
</CustomCollapse>
|
||||
<CustomCollapse accordionProps={{ isDisabled: true }} accordionItemProps={{ title: '第三个面板(禁用)' }}>
|
||||
<div className="p-4">
|
||||
<p>这个面板被禁用了</p>
|
||||
</div>
|
||||
</CustomCollapse>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 使用原生 HeroUI Accordion 的多面板示例
|
||||
export const NativeAccordionMultiple: Story = {
|
||||
render: () => (
|
||||
<div className="max-w-lg">
|
||||
<h3 className="text-lg font-medium mb-4">原生 HeroUI Accordion 多面板</h3>
|
||||
<Accordion variant="shadow" className="p-2 flex flex-col gap-1" defaultExpandedKeys={['1']}>
|
||||
<AccordionItem
|
||||
key="1"
|
||||
title="连接的设备"
|
||||
startContent={<Monitor className="text-primary" size={20} />}
|
||||
subtitle={
|
||||
<p className="flex">
|
||||
2个问题需要<span className="text-primary ml-1">立即修复</span>
|
||||
</p>
|
||||
}>
|
||||
<div className="p-4">
|
||||
<p className="text-small">设备连接状态监控</p>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
<AccordionItem
|
||||
key="2"
|
||||
title="应用权限"
|
||||
startContent={<Shield className="text-default-500" size={20} />}
|
||||
subtitle="3个应用有读取权限">
|
||||
<div className="p-4">
|
||||
<p className="text-small">管理应用的系统权限设置</p>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
<AccordionItem
|
||||
key="3"
|
||||
title="待办任务"
|
||||
startContent={<Info className="text-warning" size={20} />}
|
||||
subtitle={<span className="text-warning">请完善您的个人资料</span>}>
|
||||
<div className="p-4">
|
||||
<p className="text-small">您还有一些信息需要完善</p>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
<AccordionItem
|
||||
key="4"
|
||||
title={
|
||||
<p className="flex gap-1 items-center">
|
||||
卡片已过期
|
||||
<span className="text-default-400 text-small">*4812</span>
|
||||
</p>
|
||||
}
|
||||
startContent={<CreditCard className="text-danger" size={20} />}
|
||||
subtitle={<span className="text-danger">请立即更新</span>}>
|
||||
<div className="p-4">
|
||||
<p className="text-small text-danger">您的信用卡已过期,请更新支付信息</p>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 富内容面板
|
||||
export const RichContent: Story = {
|
||||
args: {
|
||||
accordionItemProps: {
|
||||
title: (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<Info className="text-default-500" size={20} />
|
||||
<span>详细信息</span>
|
||||
</div>
|
||||
<div className="flex gap-2" onClick={(e) => e.stopPropagation()}>
|
||||
<Button size="sm" variant="flat" color="primary">
|
||||
保存
|
||||
</Button>
|
||||
<Button size="sm" variant="flat">
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
children: (
|
||||
<div className="p-4 space-y-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">基本信息</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">名称</label>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">类型</label>
|
||||
<select className="w-full px-3 py-2 border border-gray-300 rounded-md">
|
||||
<option>选择类型</option>
|
||||
<option>类型 A</option>
|
||||
<option>类型 B</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">描述</label>
|
||||
<textarea className="w-full px-3 py-2 border border-gray-300 rounded-md" rows={3} placeholder="请输入描述" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义样式
|
||||
export const CustomStyles: Story = {
|
||||
args: {
|
||||
accordionProps: {
|
||||
style: {
|
||||
backgroundColor: 'rgba(255, 193, 7, 0.1)',
|
||||
borderColor: 'var(--color-warning)'
|
||||
}
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: (
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertTriangle className="text-warning" size={16} />
|
||||
<span>警告面板</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
children: (
|
||||
<div className="p-4 bg-warning-50 dark:bg-warning-900/20">
|
||||
<p className="text-warning-800 dark:text-warning-200">这是一个带有自定义样式的警告面板。</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 原生 HeroUI Accordion 多面板受控模式
|
||||
export const NativeAccordionControlled: Story = {
|
||||
render: function NativeAccordionControlled() {
|
||||
const [activeKeys, setActiveKeys] = useState<Set<string>>(new Set(['1']))
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" onPress={() => setActiveKeys(new Set(['1', '2', '3']))} color="primary">
|
||||
全部展开
|
||||
</Button>
|
||||
<Button size="sm" onPress={() => setActiveKeys(new Set())} color="default">
|
||||
全部收起
|
||||
</Button>
|
||||
<Button size="sm" onPress={() => setActiveKeys(new Set(['2']))} color="default">
|
||||
只展开第二个
|
||||
</Button>
|
||||
</div>
|
||||
<Accordion
|
||||
selectedKeys={activeKeys}
|
||||
onSelectionChange={(keys) => {
|
||||
if (keys !== 'all') {
|
||||
setActiveKeys(keys as Set<string>)
|
||||
}
|
||||
}}>
|
||||
<AccordionItem key="1" title="受控面板 1">
|
||||
<div className="p-4">
|
||||
<p>第一个面板的内容</p>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" title="受控面板 2">
|
||||
<div className="p-4">
|
||||
<p>第二个面板的内容</p>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" title="受控面板 3">
|
||||
<div className="p-4">
|
||||
<p>第三个面板的内容</p>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<div className="text-sm text-gray-600">
|
||||
当前展开的面板:{activeKeys.size > 0 ? Array.from(activeKeys).join(', ') : '无'}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import { AlertTriangleIcon, StarIcon } from 'lucide-react'
|
||||
import { action } from 'storybook/actions'
|
||||
|
||||
import CustomTag from '../../../src/components/base/CustomTag'
|
||||
import CustomTag from '../../../src/components/primitives/customTag'
|
||||
|
||||
const meta: Meta<typeof CustomTag> = {
|
||||
title: 'Base/CustomTag',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import DividerWithText from '../../../src/components/base/DividerWithText'
|
||||
import DividerWithText from '../../../src/components/primitives/dividerWithText'
|
||||
|
||||
const meta: Meta<typeof DividerWithText> = {
|
||||
title: 'Base/DividerWithText',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import EmojiAvatar from '../../../src/components/base/Avatar/EmojiAvatar'
|
||||
import EmojiAvatar from '../../../src/components/primitives/Avatar/EmojiAvatar'
|
||||
|
||||
const meta: Meta<typeof EmojiAvatar> = {
|
||||
title: 'Display/EmojiAvatar',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import EmojiIcon from '../../../src/components/base/EmojiIcon'
|
||||
import EmojiIcon from '../../../src/components/primitives/emojiIcon'
|
||||
|
||||
const meta: Meta<typeof EmojiIcon> = {
|
||||
title: 'Base/EmojiIcon',
|
||||
|
||||
@ -2,8 +2,8 @@ import { Button } from '@heroui/react'
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import type { CustomFallbackProps } from '../../../src/components/base/ErrorBoundary'
|
||||
import { ErrorBoundary } from '../../../src/components/base/ErrorBoundary'
|
||||
import type { CustomFallbackProps } from '../../../src/components/primitives/ErrorBoundary'
|
||||
import { ErrorBoundary } from '../../../src/components/primitives/ErrorBoundary'
|
||||
|
||||
// 错误组件 - 用于触发错误
|
||||
const ThrowErrorComponent = ({ shouldThrow = false, errorMessage = '这是一个模拟错误' }) => {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import IndicatorLight from '../../../src/components/base/IndicatorLight'
|
||||
import IndicatorLight from '../../../src/components/primitives/indicatorLight'
|
||||
|
||||
const meta: Meta<typeof IndicatorLight> = {
|
||||
title: 'Base/IndicatorLight',
|
||||
|
||||
@ -2,7 +2,7 @@ import { Button } from '@heroui/react'
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import Spinner from '../../../src/components/base/Spinner'
|
||||
import Spinner from '../../../src/components/primitives/spinner'
|
||||
|
||||
const meta: Meta<typeof Spinner> = {
|
||||
title: 'Base/Spinner',
|
||||
|
||||
@ -1,176 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { ErrorTag, InfoTag, StatusTag, SuccessTag, WarnTag } from '../../../src/components/base/StatusTag'
|
||||
|
||||
const meta: Meta<typeof StatusTag> = {
|
||||
title: 'Base/StatusTag',
|
||||
component: StatusTag,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
type: {
|
||||
control: { type: 'select' },
|
||||
options: ['success', 'error', 'warning', 'info']
|
||||
},
|
||||
iconSize: { control: { type: 'range', min: 10, max: 24, step: 1 } },
|
||||
message: { control: 'text' },
|
||||
color: { control: 'color' }
|
||||
}
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Default
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
type: 'success',
|
||||
message: 'Success'
|
||||
}
|
||||
}
|
||||
|
||||
// All Types
|
||||
export const AllTypes: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-3">
|
||||
<StatusTag type="success" message="Success message" />
|
||||
<StatusTag type="error" message="Error message" />
|
||||
<StatusTag type="warning" message="Warning message" />
|
||||
<StatusTag type="info" message="Info message" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Convenience Components
|
||||
export const ConvenienceComponents: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-3">
|
||||
<SuccessTag message="Operation completed" />
|
||||
<ErrorTag message="Operation failed" />
|
||||
<WarnTag message="Please check this" />
|
||||
<InfoTag message="Additional information" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Different Icon Sizes
|
||||
export const IconSizes: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<StatusTag type="success" iconSize={10} message="Small icon" />
|
||||
<StatusTag type="success" iconSize={14} message="Default icon" />
|
||||
<StatusTag type="success" iconSize={18} message="Large icon" />
|
||||
<StatusTag type="success" iconSize={24} message="Extra large icon" />
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<ErrorTag iconSize={10} message="Small icon" />
|
||||
<ErrorTag iconSize={14} message="Default icon" />
|
||||
<ErrorTag iconSize={18} message="Large icon" />
|
||||
<ErrorTag iconSize={24} message="Extra large icon" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Custom Colors
|
||||
export const CustomColors: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-3">
|
||||
<StatusTag type="success" message="Custom purple" color="#8B5CF6" />
|
||||
<StatusTag type="error" message="Custom blue" color="#3B82F6" />
|
||||
<StatusTag type="warning" message="Custom green" color="#10B981" />
|
||||
<StatusTag type="info" message="Custom pink" color="#EC4899" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// In Context
|
||||
export const InContext: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border border-gray-200 p-4">
|
||||
<h3 className="mb-2 font-semibold">Form Submission</h3>
|
||||
<p className="mb-3 text-sm text-gray-600">Your form has been processed.</p>
|
||||
<SuccessTag message="Form submitted successfully" />
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-gray-200 p-4">
|
||||
<h3 className="mb-2 font-semibold">Validation Error</h3>
|
||||
<p className="mb-3 text-sm text-gray-600">Please fix the following issues:</p>
|
||||
<ErrorTag message="Invalid email format" />
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-gray-200 p-4">
|
||||
<h3 className="mb-2 font-semibold">System Status</h3>
|
||||
<div className="space-y-2">
|
||||
<SuccessTag message="Database connected" />
|
||||
<WarnTag message="High memory usage" />
|
||||
<ErrorTag message="Email service down" />
|
||||
<InfoTag message="Last backup: 2 hours ago" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Use Cases
|
||||
export const UseCases: Story = {
|
||||
render: () => (
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium">Success States</h4>
|
||||
<div className="space-y-2">
|
||||
<SuccessTag message="Saved" />
|
||||
<SuccessTag message="Published" />
|
||||
<SuccessTag message="Deployed" />
|
||||
<SuccessTag message="Verified" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium">Error States</h4>
|
||||
<div className="space-y-2">
|
||||
<ErrorTag message="Failed" />
|
||||
<ErrorTag message="Timeout" />
|
||||
<ErrorTag message="Not found" />
|
||||
<ErrorTag message="Access denied" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium">Warning States</h4>
|
||||
<div className="space-y-2">
|
||||
<WarnTag message="Deprecated" />
|
||||
<WarnTag message="Limited" />
|
||||
<WarnTag message="Expiring soon" />
|
||||
<WarnTag message="Low balance" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium">Info States</h4>
|
||||
<div className="space-y-2">
|
||||
<InfoTag message="New" />
|
||||
<InfoTag message="Beta" />
|
||||
<InfoTag message="Preview" />
|
||||
<InfoTag message="Optional" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Long Messages
|
||||
export const LongMessages: Story = {
|
||||
render: () => (
|
||||
<div className="max-w-md space-y-3">
|
||||
<SuccessTag message="Your request has been successfully processed and saved to the database" />
|
||||
<ErrorTag message="Unable to connect to the server. Please check your network connection and try again" />
|
||||
<WarnTag message="This feature will be deprecated in the next major version. Please migrate to the new API" />
|
||||
<InfoTag message="Additional information about this feature can be found in the documentation at docs.example.com" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,383 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import TextBadge from '../../../src/components/base/TextBadge'
|
||||
|
||||
const meta: Meta<typeof TextBadge> = {
|
||||
title: 'Base/TextBadge',
|
||||
component: TextBadge,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
text: {
|
||||
control: 'text',
|
||||
description: '徽章显示的文字'
|
||||
},
|
||||
style: {
|
||||
control: false,
|
||||
description: '自定义样式对象'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
text: '新'
|
||||
}
|
||||
}
|
||||
|
||||
export const ShortText: Story = {
|
||||
args: {
|
||||
text: 'V2'
|
||||
}
|
||||
}
|
||||
|
||||
export const LongText: Story = {
|
||||
args: {
|
||||
text: '热门推荐'
|
||||
}
|
||||
}
|
||||
|
||||
export const Numbers: Story = {
|
||||
args: {
|
||||
text: '99+'
|
||||
}
|
||||
}
|
||||
|
||||
export const Status: Story = {
|
||||
args: {
|
||||
text: '已完成'
|
||||
}
|
||||
}
|
||||
|
||||
export const Version: Story = {
|
||||
args: {
|
||||
text: 'v1.2.0'
|
||||
}
|
||||
}
|
||||
|
||||
export const CustomStyle: Story = {
|
||||
args: {
|
||||
text: '自定义',
|
||||
style: {
|
||||
backgroundColor: '#10b981',
|
||||
color: 'white',
|
||||
fontSize: '11px'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const CustomClassName: Story = {
|
||||
args: {
|
||||
text: '特殊样式',
|
||||
className:
|
||||
'bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 border border-purple-200 dark:border-purple-700'
|
||||
}
|
||||
}
|
||||
|
||||
export const ColorVariations: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-lg font-medium">颜色变化</h3>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<TextBadge text="默认蓝色" />
|
||||
<TextBadge text="绿色" className="bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400" />
|
||||
<TextBadge text="红色" className="bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400" />
|
||||
<TextBadge text="黄色" className="bg-yellow-100 dark:bg-yellow-900/30 text-yellow-600 dark:text-yellow-400" />
|
||||
<TextBadge text="紫色" className="bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400" />
|
||||
<TextBadge text="灰色" className="bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const StatusBadges: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-lg font-medium">状态徽章</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">任务状态</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<TextBadge text="待处理" className="bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400" />
|
||||
<TextBadge text="进行中" className="bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400" />
|
||||
<TextBadge text="已完成" className="bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400" />
|
||||
<TextBadge text="已取消" className="bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">优先级</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<TextBadge text="低" className="bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400" />
|
||||
<TextBadge text="中" />
|
||||
<TextBadge text="高" className="bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400" />
|
||||
<TextBadge text="紧急" className="bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">类型标签</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<TextBadge text="功能" />
|
||||
<TextBadge text="修复" className="bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400" />
|
||||
<TextBadge text="优化" className="bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400" />
|
||||
<TextBadge
|
||||
text="文档"
|
||||
className="bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const InUserInterface: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-lg font-medium">界面应用示例</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* 导航菜单 */}
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">导航菜单</h4>
|
||||
<nav className="flex gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>首页</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>产品</span>
|
||||
<TextBadge text="新" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>消息</span>
|
||||
<TextBadge text="5" className="bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>设置</span>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* 卡片列表 */}
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">文章列表</h4>
|
||||
<div className="space-y-3">
|
||||
<div className="p-4 border border-gray-200 dark:border-gray-700 rounded">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h5 className="font-medium">React 18 新特性介绍</h5>
|
||||
<p className="text-sm text-gray-500 mt-1">介绍 React 18 的并发特性...</p>
|
||||
</div>
|
||||
<div className="flex gap-2 ml-4">
|
||||
<TextBadge text="前端" />
|
||||
<TextBadge
|
||||
text="推荐"
|
||||
className="bg-yellow-100 dark:bg-yellow-900/30 text-yellow-600 dark:text-yellow-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 border border-gray-200 dark:border-gray-700 rounded">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h5 className="font-medium">Node.js 性能优化指南</h5>
|
||||
<p className="text-sm text-gray-500 mt-1">深入了解 Node.js 性能优化...</p>
|
||||
</div>
|
||||
<div className="flex gap-2 ml-4">
|
||||
<TextBadge text="后端" />
|
||||
<TextBadge text="热门" className="bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 border border-gray-200 dark:border-gray-700 rounded">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h5 className="font-medium">TypeScript 最佳实践</h5>
|
||||
<p className="text-sm text-gray-500 mt-1">TypeScript 开发的最佳实践...</p>
|
||||
</div>
|
||||
<div className="flex gap-2 ml-4">
|
||||
<TextBadge text="TypeScript" />
|
||||
<TextBadge text="新" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 用户列表 */}
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">团队成员</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between p-3 border border-gray-200 dark:border-gray-700 rounded">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-blue-500 text-white rounded-full flex items-center justify-center text-sm">
|
||||
张
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">张三</p>
|
||||
<p className="text-sm text-gray-500">前端开发</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<TextBadge
|
||||
text="管理员"
|
||||
className="bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400"
|
||||
/>
|
||||
<TextBadge
|
||||
text="在线"
|
||||
className="bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-3 border border-gray-200 dark:border-gray-700 rounded">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-green-500 text-white rounded-full flex items-center justify-center text-sm">
|
||||
李
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">李四</p>
|
||||
<p className="text-sm text-gray-500">后端开发</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<TextBadge text="成员" />
|
||||
<TextBadge text="离线" className="bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const VersionTags: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-lg font-medium">版本标签</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">软件版本</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<TextBadge text="v1.0.0" />
|
||||
<TextBadge text="v1.1.0" />
|
||||
<TextBadge text="v2.0.0-beta" />
|
||||
<TextBadge text="v2.1.0" className="bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400" />
|
||||
<TextBadge text="latest" className="bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">环境标签</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<TextBadge text="开发" className="bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400" />
|
||||
<TextBadge
|
||||
text="测试"
|
||||
className="bg-yellow-100 dark:bg-yellow-900/30 text-yellow-600 dark:text-yellow-400"
|
||||
/>
|
||||
<TextBadge
|
||||
text="预发布"
|
||||
className="bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400"
|
||||
/>
|
||||
<TextBadge text="生产" className="bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const NumberBadges: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-lg font-medium">数字徽章</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">通知数量</h4>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>消息</span>
|
||||
<TextBadge text="3" className="bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>任务</span>
|
||||
<TextBadge text="12" className="bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>评论</span>
|
||||
<TextBadge text="99+" className="bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">统计数据</h4>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>访问量</span>
|
||||
<TextBadge text="1.2K" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>下载</span>
|
||||
<TextBadge text="856" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>收藏</span>
|
||||
<TextBadge text="234" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const SizeVariations: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-lg font-medium">尺寸变化</h3>
|
||||
<div className="flex items-center gap-4">
|
||||
<TextBadge text="超小" style={{ fontSize: '10px', padding: '1px 4px' }} />
|
||||
<TextBadge text="小" style={{ fontSize: '11px', padding: '2px 6px' }} />
|
||||
<TextBadge text="默认" />
|
||||
<TextBadge text="大" style={{ fontSize: '14px', padding: '4px 8px' }} />
|
||||
<TextBadge text="超大" style={{ fontSize: '16px', padding: '6px 12px' }} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const OutlineBadges: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-lg font-medium">边框样式</h3>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<TextBadge
|
||||
text="边框"
|
||||
className="bg-transparent border border-blue-600 text-blue-600 dark:border-blue-400 dark:text-blue-400"
|
||||
/>
|
||||
<TextBadge
|
||||
text="绿色边框"
|
||||
className="bg-transparent border border-green-600 text-green-600 dark:border-green-400 dark:text-green-400"
|
||||
/>
|
||||
<TextBadge
|
||||
text="红色边框"
|
||||
className="bg-transparent border border-red-600 text-red-600 dark:border-red-400 dark:text-red-400"
|
||||
/>
|
||||
<TextBadge
|
||||
text="虚线边框"
|
||||
className="bg-transparent border border-dashed border-purple-600 text-purple-600 dark:border-purple-400 dark:text-purple-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import Ellipsis from '../../../src/components/display/Ellipsis'
|
||||
import Ellipsis from '../../../src/components/composites/Ellipsis'
|
||||
|
||||
const meta = {
|
||||
title: 'Display/Ellipsis',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import ExpandableText from '../../../src/components/display/ExpandableText'
|
||||
import ExpandableText from '../../../src/components/composites/ExpandableText'
|
||||
|
||||
const meta: Meta<typeof ExpandableText> = {
|
||||
title: 'Display/ExpandableText',
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { action } from 'storybook/actions'
|
||||
|
||||
import ListItem from '../../../src/components/display/ListItem'
|
||||
import ListItem from '../../../src/components/composites/ListItem'
|
||||
|
||||
const meta: Meta<typeof ListItem> = {
|
||||
title: 'Display/ListItem',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import MaxContextCount from '../../../src/components/display/MaxContextCount'
|
||||
import MaxContextCount from '../../../src/components/composites/MaxContextCount'
|
||||
|
||||
const meta: Meta<typeof MaxContextCount> = {
|
||||
title: 'Display/MaxContextCount',
|
||||
|
||||
@ -2,7 +2,7 @@ import { Button } from '@heroui/react'
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import ThinkingEffect from '../../../src/components/display/ThinkingEffect'
|
||||
import ThinkingEffect from '../../../src/components/composites/ThinkingEffect'
|
||||
|
||||
const meta: Meta<typeof ThinkingEffect> = {
|
||||
title: 'Display/ThinkingEffect',
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import { action } from 'storybook/actions'
|
||||
|
||||
import type { LanguageConfig } from '../../../src/components/interactive/CodeEditor'
|
||||
import CodeEditor, { getCmThemeByName, getCmThemeNames } from '../../../src/components/interactive/CodeEditor'
|
||||
import type { LanguageConfig } from '../../../src/components/composites/CodeEditor'
|
||||
import CodeEditor, { getCmThemeByName, getCmThemeNames } from '../../../src/components/composites/CodeEditor'
|
||||
|
||||
// 示例语言配置 - 为 Storybook 提供更丰富的语言支持演示
|
||||
const exampleLanguageConfig: LanguageConfig = {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Meta } from '@storybook/react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import Selector from '../../../src/components/base/Selector'
|
||||
import Selector from '../../../src/components/primitives/Selector'
|
||||
|
||||
const meta: Meta<typeof Selector> = {
|
||||
title: 'Interactive/Selector',
|
||||
|
||||
@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import clsx from 'clsx'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
import { Sortable } from '../../../src/components/interactive/Sortable'
|
||||
import { Sortable } from '../../../src/components/composites/Sortable'
|
||||
import { useDndReorder } from '../../../src/hooks'
|
||||
|
||||
type ExampleItem = { id: number; label: string }
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import { useState } from 'react'
|
||||
|
||||
import HorizontalScrollContainer from '../../../src/components/layout/HorizontalScrollContainer'
|
||||
import HorizontalScrollContainer from '../../../src/components/composites/HorizontalScrollContainer'
|
||||
|
||||
const meta: Meta<typeof HorizontalScrollContainer> = {
|
||||
title: 'Layout/HorizontalScrollContainer',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
|
||||
import Scrollbar from '../../../src/components/layout/Scrollbar'
|
||||
import Scrollbar from '../../../src/components/composites/Scrollbar'
|
||||
|
||||
const meta: Meta<typeof Scrollbar> = {
|
||||
title: 'Layout/Scrollbar',
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { cn } from '@cherrystudio/ui'
|
||||
import { Button, type ButtonProps } from '@cherrystudio/ui'
|
||||
import { Button, cn } from '@cherrystudio/ui'
|
||||
import React, { memo } from 'react'
|
||||
|
||||
interface ActionIconButtonProps extends Omit<ButtonProps, 'ref'> {
|
||||
interface ActionIconButtonProps extends Omit<React.ComponentProps<'button'>, 'ref'> {
|
||||
icon: React.ReactNode
|
||||
active?: boolean
|
||||
}
|
||||
@ -13,19 +12,17 @@ interface ActionIconButtonProps extends Omit<ButtonProps, 'ref'> {
|
||||
const ActionIconButton: React.FC<ActionIconButtonProps> = ({ icon, active = false, className, ...props }) => {
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
radius="full"
|
||||
isIconOnly
|
||||
startContent={icon}
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'flex cursor-pointer flex-row items-center justify-center border-none p-0 text-base transition-all duration-300 ease-in-out [&_.anticon]:text-icon [&_.icon-a-addchat]:mb-[-2px] [&_.icon-a-addchat]:text-lg [&_.icon]:text-icon [&_.iconfont]:text-icon [&_.lucide]:text-icon',
|
||||
'flex cursor-pointer flex-row items-center justify-center rounded-full border-none p-0 text-base transition-all duration-300 ease-in-out [&_.anticon]:text-icon [&_.icon-a-addchat]:mb-[-2px] [&_.icon-a-addchat]:text-lg [&_.icon]:text-icon [&_.iconfont]:text-icon [&_.lucide]:text-icon',
|
||||
active &&
|
||||
'[&_.anticon]:text-primary! [&_.icon]:text-primary! [&_.iconfont]:text-primary! [&_.lucide]:text-primary!',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
{...props}>
|
||||
{icon}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -89,32 +89,24 @@ const HtmlArtifactsCard: FC<Props> = ({ html, onSave, isStreaming = false }) =>
|
||||
</TerminalContent>
|
||||
</TerminalPreview>
|
||||
<ButtonContainer>
|
||||
<Button startContent={<CodeOutlined />} onPress={() => setIsPopupOpen(true)} color="primary">
|
||||
<Button onClick={() => setIsPopupOpen(true)}>
|
||||
<CodeOutlined />
|
||||
{t('chat.artifacts.button.preview')}
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</>
|
||||
) : (
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
startContent={<CodeOutlined />}
|
||||
onPress={() => setIsPopupOpen(true)}
|
||||
variant="light"
|
||||
isDisabled={!hasContent}>
|
||||
<Button onClick={() => setIsPopupOpen(true)} variant="ghost" disabled={!hasContent}>
|
||||
<CodeOutlined />
|
||||
{t('chat.artifacts.button.preview')}
|
||||
</Button>
|
||||
<Button
|
||||
startContent={<LinkIcon size={14} />}
|
||||
onPress={handleOpenExternal}
|
||||
variant="light"
|
||||
isDisabled={!hasContent}>
|
||||
<Button onClick={handleOpenExternal} variant="ghost" disabled={!hasContent}>
|
||||
<LinkIcon size={14} />
|
||||
{t('chat.artifacts.button.openExternal')}
|
||||
</Button>
|
||||
<Button
|
||||
startContent={<DownloadIcon size={14} />}
|
||||
onPress={handleDownload}
|
||||
variant="light"
|
||||
isDisabled={!hasContent}>
|
||||
<Button onClick={handleDownload} variant="ghost" disabled={!hasContent}>
|
||||
<DownloadIcon size={14} />
|
||||
{t('code_block.download.label')}
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
|
||||
@ -85,23 +85,23 @@ const HtmlArtifactsPopup: React.FC<HtmlArtifactsPopupProps> = ({ open, title, ht
|
||||
<ViewControls onDoubleClick={(e) => e.stopPropagation()}>
|
||||
<ViewButton
|
||||
size="sm"
|
||||
color={viewMode === 'split' ? 'primary' : 'default'}
|
||||
startContent={<SquareSplitHorizontal size={14} />}
|
||||
onPress={() => setViewMode('split')}>
|
||||
variant={viewMode === 'split' ? 'default' : 'secondary'}
|
||||
onClick={() => setViewMode('split')}>
|
||||
<SquareSplitHorizontal size={14} />
|
||||
{t('html_artifacts.split')}
|
||||
</ViewButton>
|
||||
<ViewButton
|
||||
size="sm"
|
||||
color={viewMode === 'code' ? 'primary' : 'default'}
|
||||
startContent={<Code size={14} />}
|
||||
onPress={() => setViewMode('code')}>
|
||||
variant={viewMode === 'code' ? 'default' : 'secondary'}
|
||||
onClick={() => setViewMode('code')}>
|
||||
<Code size={14} />
|
||||
{t('html_artifacts.code')}
|
||||
</ViewButton>
|
||||
<ViewButton
|
||||
size="sm"
|
||||
color={viewMode === 'preview' ? 'primary' : 'default'}
|
||||
startContent={<Eye size={14} />}
|
||||
onPress={() => setViewMode('preview')}>
|
||||
variant={viewMode === 'preview' ? 'default' : 'secondary'}
|
||||
onClick={() => setViewMode('preview')}>
|
||||
<Eye size={14} />
|
||||
{t('html_artifacts.preview')}
|
||||
</ViewButton>
|
||||
</ViewControls>
|
||||
@ -127,17 +127,17 @@ const HtmlArtifactsPopup: React.FC<HtmlArtifactsPopupProps> = ({ open, title, ht
|
||||
]
|
||||
}}>
|
||||
<Tooltip content={t('html_artifacts.capture.label')} closeDelay={0}>
|
||||
<Button variant="light" startContent={<Camera size={16} />} isIconOnly className="nodrag" />
|
||||
<Button variant="ghost" size="icon" className="nodrag">
|
||||
<Camera size={16} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Dropdown>
|
||||
<Button
|
||||
onPress={() => setIsFullscreen(!isFullscreen)}
|
||||
variant="light"
|
||||
startContent={isFullscreen ? <Minimize2 size={16} /> : <Maximize2 size={16} />}
|
||||
isIconOnly
|
||||
className="nodrag"
|
||||
/>
|
||||
<Button onPress={onClose} variant="light" startContent={<X size={16} />} isIconOnly className="nodrag" />
|
||||
<Button onClick={() => setIsFullscreen(!isFullscreen)} variant="ghost" size="icon" className="nodrag">
|
||||
{isFullscreen ? <Minimize2 size={16} /> : <Maximize2 size={16} />}
|
||||
</Button>
|
||||
<Button onClick={onClose} variant="ghost" size="icon" className="nodrag">
|
||||
<X size={16} />
|
||||
</Button>
|
||||
</HeaderRight>
|
||||
</ModalHeader>
|
||||
)
|
||||
@ -165,7 +165,7 @@ const HtmlArtifactsPopup: React.FC<HtmlArtifactsPopupProps> = ({ open, title, ht
|
||||
/>
|
||||
<ToolbarWrapper>
|
||||
<Tooltip content={t('code_block.edit.save.label')} closeDelay={0}>
|
||||
<ToolbarButton radius="full" size="lg" isIconOnly onPress={handleSave}>
|
||||
<ToolbarButton className="rounded-full" size="icon-lg" onClick={handleSave}>
|
||||
{saved ? (
|
||||
<Check size={16} color="var(--color-status-success)" />
|
||||
) : (
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Button } from '@heroui/react'
|
||||
import { Button } from '@cherrystudio/ui'
|
||||
import { CheckIcon, XIcon } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
@ -28,11 +28,13 @@ const ConfirmDialog: FC<Props> = ({ x, y, message, onConfirm, onCancel }) => {
|
||||
<div className="flex min-w-[160px] items-center rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] p-3 shadow-[0_4px_12px_rgba(0,0,0,0.15)]">
|
||||
<div className="mr-2 text-sm leading-[1.4]">{message}</div>
|
||||
<div className="flex justify-center gap-2">
|
||||
<Button onPress={onCancel} radius="full" className="h-6 w-6 min-w-0 p-1" color="danger">
|
||||
<XIcon className="text-danger-foreground" size={16} />
|
||||
<Button onClick={onCancel} className="h-6 w-6 min-w-0 rounded-full p-1" variant="destructive">
|
||||
<XIcon size={16} />
|
||||
</Button>
|
||||
<Button onPress={onConfirm} radius="full" className="h-6 w-6 min-w-0 p-1" color="success">
|
||||
<CheckIcon className="text-success-foreground" size={16} />
|
||||
<Button
|
||||
onClick={onConfirm}
|
||||
className="h-6 w-6 min-w-0 rounded-full bg-green-500 p-1 text-white hover:bg-green-600">
|
||||
<CheckIcon size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -365,8 +365,7 @@ export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
|
||||
{showUserToggle && (
|
||||
<Tooltip placement="bottom" content={t('button.includes_user_questions')} delay={800}>
|
||||
<ActionIconButton
|
||||
onPress={userOutlinedButtonOnClick}
|
||||
isIconOnly
|
||||
onClick={userOutlinedButtonOnClick}
|
||||
icon={
|
||||
<User size={18} style={{ color: includeUser ? 'var(--color-link)' : 'var(--color-icon)' }} />
|
||||
}
|
||||
@ -375,7 +374,7 @@ export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
|
||||
)}
|
||||
<Tooltip placement="bottom" content={t('button.case_sensitive')} delay={800}>
|
||||
<ActionIconButton
|
||||
onPress={caseSensitiveButtonOnClick}
|
||||
onClick={caseSensitiveButtonOnClick}
|
||||
icon={
|
||||
<CaseSensitive
|
||||
size={18}
|
||||
@ -386,7 +385,7 @@ export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
|
||||
</Tooltip>
|
||||
<Tooltip placement="bottom" content={t('button.whole_word')} delay={800}>
|
||||
<ActionIconButton
|
||||
onPress={wholeWordButtonOnClick}
|
||||
onClick={wholeWordButtonOnClick}
|
||||
icon={
|
||||
<WholeWord size={18} style={{ color: isWholeWord ? 'var(--color-link)' : 'var(--color-icon)' }} />
|
||||
}
|
||||
@ -408,16 +407,16 @@ export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
|
||||
</SearchResults>
|
||||
<ToolBar>
|
||||
<ActionIconButton
|
||||
onPress={prevButtonOnClick}
|
||||
isDisabled={allRanges.length === 0}
|
||||
onClick={prevButtonOnClick}
|
||||
disabled={allRanges.length === 0}
|
||||
icon={<ChevronUp size={18} />}
|
||||
/>
|
||||
<ActionIconButton
|
||||
onPress={nextButtonOnClick}
|
||||
isDisabled={allRanges.length === 0}
|
||||
onClick={nextButtonOnClick}
|
||||
disabled={allRanges.length === 0}
|
||||
icon={<ChevronDown size={18} />}
|
||||
/>
|
||||
<ActionIconButton onPress={closeButtonOnClick} icon={<X size={18} />} />
|
||||
<ActionIconButton onClick={closeButtonOnClick} icon={<X size={18} />} />
|
||||
</ToolBar>
|
||||
</SearchBarContainer>
|
||||
</NarrowLayout>
|
||||
|
||||
@ -24,10 +24,10 @@ const DefaultFallback: ComponentType<FallbackProps> = (props: FallbackProps): Re
|
||||
type="error"
|
||||
action={
|
||||
<Space>
|
||||
<Button size="sm" onPress={debug}>
|
||||
<Button size="sm" onClick={debug}>
|
||||
{t('error.boundary.default.devtools')}
|
||||
</Button>
|
||||
<Button size="sm" onPress={reload}>
|
||||
<Button size="sm" onClick={reload}>
|
||||
{t('error.boundary.default.reload')}
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
@ -22,7 +22,7 @@ const ExpandableText = ({
|
||||
|
||||
const button = useMemo(() => {
|
||||
return (
|
||||
<Button variant="light" onPress={toggleExpand} className="self-end">
|
||||
<Button variant="ghost" onClick={toggleExpand} className="self-end">
|
||||
{isExpanded ? t('common.collapse') : t('common.expand')}
|
||||
</Button>
|
||||
)
|
||||
|
||||
@ -78,12 +78,11 @@ const InputEmbeddingDimension = ({
|
||||
<Button
|
||||
role="button"
|
||||
aria-label="Get embedding dimension"
|
||||
isDisabled={disabled || loading}
|
||||
onPress={handleFetchDimension}
|
||||
size="sm"
|
||||
startContent={<RefreshIcon size={16} className={loading ? 'animation-rotate' : ''} />}
|
||||
isIconOnly
|
||||
/>
|
||||
disabled={disabled || loading}
|
||||
onClick={handleFetchDimension}
|
||||
size="icon-sm">
|
||||
<RefreshIcon size={16} className={loading ? 'animation-rotate' : ''} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
)
|
||||
|
||||
@ -196,18 +196,17 @@ export function LocalBackupManager({ visible, onClose, localBackupDir, restoreMe
|
||||
<Button
|
||||
className="inline-flex"
|
||||
size="sm"
|
||||
variant="light"
|
||||
onPress={() => handleRestore(record.fileName)}
|
||||
isDisabled={restoring || deleting}>
|
||||
variant="ghost"
|
||||
onClick={() => handleRestore(record.fileName)}
|
||||
disabled={restoring || deleting}>
|
||||
{t('settings.data.local.backup.manager.restore.text')}
|
||||
</Button>
|
||||
<Button
|
||||
className="inline-flex"
|
||||
size="sm"
|
||||
variant="light"
|
||||
color="danger"
|
||||
onPress={() => handleDeleteSingle(record.fileName)}
|
||||
isDisabled={deleting || restoring}>
|
||||
variant="ghost"
|
||||
onClick={() => handleDeleteSingle(record.fileName)}
|
||||
disabled={deleting || restoring}>
|
||||
{t('settings.data.local.backup.manager.delete.text')}
|
||||
</Button>
|
||||
</Flex>
|
||||
@ -232,19 +231,19 @@ export function LocalBackupManager({ visible, onClose, localBackupDir, restoreMe
|
||||
transitionName="animation-move-down"
|
||||
classNames={{ footer: 'flex justify-end gap-1' }}
|
||||
footer={[
|
||||
<Button key="refresh" startContent={<ReloadOutlined />} onPress={fetchBackupFiles} isDisabled={loading}>
|
||||
<Button key="refresh" onClick={fetchBackupFiles} disabled={loading}>
|
||||
<ReloadOutlined />
|
||||
{t('settings.data.local.backup.manager.refresh')}
|
||||
</Button>,
|
||||
<Button
|
||||
key="delete"
|
||||
color="danger"
|
||||
startContent={<DeleteOutlined />}
|
||||
onPress={handleDeleteSelected}
|
||||
isDisabled={selectedRowKeys.length === 0 || deleting}
|
||||
isLoading={deleting}>
|
||||
variant="destructive"
|
||||
onClick={handleDeleteSelected}
|
||||
disabled={selectedRowKeys.length === 0 || deleting}>
|
||||
<DeleteOutlined />
|
||||
{t('settings.data.local.backup.manager.delete.selected')} ({selectedRowKeys.length})
|
||||
</Button>,
|
||||
<Button key="close" onPress={onClose}>
|
||||
<Button key="close" onClick={onClose}>
|
||||
{t('common.close')}
|
||||
</Button>
|
||||
]}>
|
||||
|
||||
@ -35,10 +35,10 @@ export function LocalBackupModal({
|
||||
onCancel={handleCancel}
|
||||
classNames={{ footer: 'flex justify-end gap-1' }}
|
||||
footer={[
|
||||
<Button key="back" onPress={handleCancel}>
|
||||
<Button key="back" onClick={handleCancel}>
|
||||
{t('common.cancel')}
|
||||
</Button>,
|
||||
<Button key="submit" color="primary" isLoading={backuping} onPress={handleBackup}>
|
||||
<Button key="submit" disabled={backuping} onClick={handleBackup}>
|
||||
{t('common.confirm')}
|
||||
</Button>
|
||||
]}>
|
||||
|
||||
@ -130,7 +130,7 @@ const GoogleLoginTip = ({
|
||||
banner
|
||||
onClose={handleClose}
|
||||
action={
|
||||
<Button color="primary" size="sm" onPress={openGoogleMinApp}>
|
||||
<Button color="primary" size="sm" onClick={openGoogleMinApp}>
|
||||
{t('common.open')} Google
|
||||
</Button>
|
||||
}
|
||||
|
||||
@ -24,13 +24,9 @@ const ModelSelectButton = ({ model, onSelectModel, modelFilter, noTooltip, toolt
|
||||
|
||||
const button = useMemo(() => {
|
||||
return (
|
||||
<Button
|
||||
startContent={<ModelAvatar model={model} size={22} />}
|
||||
variant="light"
|
||||
radius="full"
|
||||
isIconOnly
|
||||
onPress={onClick}
|
||||
/>
|
||||
<Button variant="ghost" className="rounded-full" size="icon" onClick={onClick}>
|
||||
<ModelAvatar model={model} size={22} />
|
||||
</Button>
|
||||
)
|
||||
}, [model, onClick])
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user