mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 10:40:07 +08:00
Merge branch 'v2' of github.com:CherryHQ/cherry-studio into refactor/ocr
This commit is contained in:
commit
35ff0c63f4
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -1,4 +1,5 @@
|
||||
/src/renderer/src/store/ @0xfullex
|
||||
/src/renderer/src/databases/ @0xfullex
|
||||
/src/main/services/ConfigManager.ts @0xfullex
|
||||
/packages/shared/IpcChannel.ts @0xfullex
|
||||
/src/main/ipc.ts @0xfullex
|
||||
@ -9,3 +10,4 @@
|
||||
/src/renderer/src/data/ @0xfullex
|
||||
|
||||
/packages/ui/ @MyPrototypeWhat
|
||||
|
||||
|
||||
12
.github/pull_request_template.md
vendored
12
.github/pull_request_template.md
vendored
@ -3,6 +3,18 @@
|
||||
1. Consider creating this PR as draft: https://github.com/CherryHQ/cherry-studio/blob/main/CONTRIBUTING.md
|
||||
-->
|
||||
|
||||
<!--
|
||||
|
||||
⚠️ Important: Redux/IndexedDB Data-Changing Feature PRs Temporarily On Hold ⚠️
|
||||
|
||||
Please note: For our current development cycle, we are not accepting feature Pull Requests that introduce changes to Redux data models or IndexedDB schemas.
|
||||
|
||||
While we value your contributions, PRs of this nature will be blocked without merge. We welcome all other contributions (bug fixes, perf enhancements, docs, etc.). Thank you!
|
||||
|
||||
Once version 2.0.0 is released, we will resume reviewing feature PRs.
|
||||
|
||||
-->
|
||||
|
||||
### What this PR does
|
||||
|
||||
Before this PR:
|
||||
|
||||
10
.github/workflows/auto-i18n.yml
vendored
10
.github/workflows/auto-i18n.yml
vendored
@ -1,9 +1,10 @@
|
||||
name: Auto I18N
|
||||
|
||||
env:
|
||||
API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
|
||||
MODEL: ${{ vars.AUTO_I18N_MODEL || 'deepseek/deepseek-v3.1'}}
|
||||
BASE_URL: ${{ vars.AUTO_I18N_BASE_URL || 'https://api.ppinfra.com/openai'}}
|
||||
TRANSLATION_API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
|
||||
TRANSLATION_MODEL: ${{ vars.AUTO_I18N_MODEL || 'deepseek/deepseek-v3.1'}}
|
||||
TRANSLATION_BASE_URL: ${{ vars.AUTO_I18N_BASE_URL || 'https://api.ppinfra.com/openai'}}
|
||||
TRANSLATION_BASE_LOCALE: ${{ vars.AUTO_I18N_BASE_LOCALE || 'en-us'}}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@ -29,6 +30,7 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
package-manager-cache: false
|
||||
|
||||
- name: 📦 Install dependencies in isolated directory
|
||||
run: |
|
||||
@ -42,7 +44,7 @@ jobs:
|
||||
echo "NODE_PATH=/tmp/translation-deps/node_modules" >> $GITHUB_ENV
|
||||
|
||||
- name: 🏃♀️ Translate
|
||||
run: npx tsx scripts/auto-translate-i18n.ts
|
||||
run: npx tsx scripts/sync-i18n.ts && npx tsx scripts/auto-translate-i18n.ts
|
||||
|
||||
- name: 🔍 Format
|
||||
run: cd /tmp/translation-deps && npx biome format --config-path /home/runner/work/cherry-studio/cherry-studio/biome.jsonc --write /home/runner/work/cherry-studio/cherry-studio/src/renderer/src/i18n/
|
||||
|
||||
131
.yarn/patches/@ai-sdk-huggingface-npm-0.0.4-8080836bc1.patch
vendored
Normal file
131
.yarn/patches/@ai-sdk-huggingface-npm-0.0.4-8080836bc1.patch
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index b3f018730a93639aad7c203f15fb1aeb766c73f4..ade2a43d66e9184799d072153df61ef7be4ea110 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -296,7 +296,14 @@ var HuggingFaceResponsesLanguageModel = class {
|
||||
metadata: huggingfaceOptions == null ? void 0 : huggingfaceOptions.metadata,
|
||||
instructions: huggingfaceOptions == null ? void 0 : huggingfaceOptions.instructions,
|
||||
...preparedTools && { tools: preparedTools },
|
||||
- ...preparedToolChoice && { tool_choice: preparedToolChoice }
|
||||
+ ...preparedToolChoice && { tool_choice: preparedToolChoice },
|
||||
+ ...(huggingfaceOptions?.reasoningEffort != null && {
|
||||
+ reasoning: {
|
||||
+ ...(huggingfaceOptions?.reasoningEffort != null && {
|
||||
+ effort: huggingfaceOptions.reasoningEffort,
|
||||
+ }),
|
||||
+ },
|
||||
+ }),
|
||||
};
|
||||
return { args: baseArgs, warnings };
|
||||
}
|
||||
@@ -365,6 +372,20 @@ var HuggingFaceResponsesLanguageModel = class {
|
||||
}
|
||||
break;
|
||||
}
|
||||
+ case 'reasoning': {
|
||||
+ for (const contentPart of part.content) {
|
||||
+ content.push({
|
||||
+ type: 'reasoning',
|
||||
+ text: contentPart.text,
|
||||
+ providerMetadata: {
|
||||
+ huggingface: {
|
||||
+ itemId: part.id,
|
||||
+ },
|
||||
+ },
|
||||
+ });
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
case "mcp_call": {
|
||||
content.push({
|
||||
type: "tool-call",
|
||||
@@ -519,6 +540,11 @@ var HuggingFaceResponsesLanguageModel = class {
|
||||
id: value.item.call_id,
|
||||
toolName: value.item.name
|
||||
});
|
||||
+ } else if (value.item.type === 'reasoning') {
|
||||
+ controller.enqueue({
|
||||
+ type: 'reasoning-start',
|
||||
+ id: value.item.id,
|
||||
+ });
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -570,6 +596,22 @@ var HuggingFaceResponsesLanguageModel = class {
|
||||
});
|
||||
return;
|
||||
}
|
||||
+ if (isReasoningDeltaChunk(value)) {
|
||||
+ controller.enqueue({
|
||||
+ type: 'reasoning-delta',
|
||||
+ id: value.item_id,
|
||||
+ delta: value.delta,
|
||||
+ });
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (isReasoningEndChunk(value)) {
|
||||
+ controller.enqueue({
|
||||
+ type: 'reasoning-end',
|
||||
+ id: value.item_id,
|
||||
+ });
|
||||
+ return;
|
||||
+ }
|
||||
},
|
||||
flush(controller) {
|
||||
controller.enqueue({
|
||||
@@ -593,7 +635,8 @@ var HuggingFaceResponsesLanguageModel = class {
|
||||
var huggingfaceResponsesProviderOptionsSchema = z2.object({
|
||||
metadata: z2.record(z2.string(), z2.string()).optional(),
|
||||
instructions: z2.string().optional(),
|
||||
- strictJsonSchema: z2.boolean().optional()
|
||||
+ strictJsonSchema: z2.boolean().optional(),
|
||||
+ reasoningEffort: z2.string().optional(),
|
||||
});
|
||||
var huggingfaceResponsesResponseSchema = z2.object({
|
||||
id: z2.string(),
|
||||
@@ -727,12 +770,31 @@ var responseCreatedChunkSchema = z2.object({
|
||||
model: z2.string()
|
||||
})
|
||||
});
|
||||
+var reasoningTextDeltaChunkSchema = z2.object({
|
||||
+ type: z2.literal('response.reasoning_text.delta'),
|
||||
+ item_id: z2.string(),
|
||||
+ output_index: z2.number(),
|
||||
+ content_index: z2.number(),
|
||||
+ delta: z2.string(),
|
||||
+ sequence_number: z2.number(),
|
||||
+});
|
||||
+
|
||||
+var reasoningTextEndChunkSchema = z2.object({
|
||||
+ type: z2.literal('response.reasoning_text.done'),
|
||||
+ item_id: z2.string(),
|
||||
+ output_index: z2.number(),
|
||||
+ content_index: z2.number(),
|
||||
+ text: z2.string(),
|
||||
+ sequence_number: z2.number(),
|
||||
+});
|
||||
var huggingfaceResponsesChunkSchema = z2.union([
|
||||
responseOutputItemAddedSchema,
|
||||
responseOutputItemDoneSchema,
|
||||
textDeltaChunkSchema,
|
||||
responseCompletedChunkSchema,
|
||||
responseCreatedChunkSchema,
|
||||
+ reasoningTextDeltaChunkSchema,
|
||||
+ reasoningTextEndChunkSchema,
|
||||
z2.object({ type: z2.string() }).loose()
|
||||
// fallback for unknown chunks
|
||||
]);
|
||||
@@ -751,6 +813,12 @@ function isResponseCompletedChunk(chunk) {
|
||||
function isResponseCreatedChunk(chunk) {
|
||||
return chunk.type === "response.created";
|
||||
}
|
||||
+function isReasoningDeltaChunk(chunk) {
|
||||
+ return chunk.type === 'response.reasoning_text.delta';
|
||||
+}
|
||||
+function isReasoningEndChunk(chunk) {
|
||||
+ return chunk.type === 'response.reasoning_text.done';
|
||||
+}
|
||||
|
||||
// src/huggingface-provider.ts
|
||||
function createHuggingFace(options = {}) {
|
||||
@ -65,7 +65,28 @@ The Test Plan aims to provide users with a more stable application experience an
|
||||
### Other Suggestions
|
||||
|
||||
- **Contact Developers**: Before submitting a PR, you can contact the developers first to discuss or get help.
|
||||
- **Become a Core Developer**: If you contribute to the project consistently, congratulations, you can become a core developer and gain project membership status. Please check our [Membership Guide](https://github.com/CherryHQ/community/blob/main/docs/membership.en.md).
|
||||
|
||||
## Important Contribution Guidelines & Focus Areas
|
||||
|
||||
Please review the following critical information before submitting your Pull Request:
|
||||
|
||||
### Temporary Restriction on Data-Changing Feature PRs 🚫
|
||||
|
||||
**Currently, we are NOT accepting feature Pull Requests that introduce changes to our Redux data models or IndexedDB schemas.**
|
||||
|
||||
Our core team is currently focused on significant architectural updates that involve these data structures. To ensure stability and focus during this period, contributions of this nature will be temporarily managed internally.
|
||||
|
||||
* **PRs that require changes to Redux state shape or IndexedDB schemas will be closed.**
|
||||
* **This restriction is temporary and will be lifted with the release of `v2.0.0`.** You can track the progress of `v2.0.0` and its related discussions on issue [#10162](https://github.com/YOUR_ORG/YOUR_REPO/issues/10162) (please replace with your actual repo link).
|
||||
|
||||
We highly encourage contributions for:
|
||||
* Bug fixes 🐞
|
||||
* Performance improvements 🚀
|
||||
* Documentation updates 📚
|
||||
* Features that **do not** alter Redux data models or IndexedDB schemas (e.g., UI enhancements, new components, minor refactors). ✨
|
||||
|
||||
We appreciate your understanding and continued support during this important development phase. Thank you!
|
||||
|
||||
|
||||
## Contact Us
|
||||
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
<p align="center">English | <a href="./docs/README.zh.md">中文</a> | <a href="https://cherry-ai.com">Official Site</a> | <a href="https://docs.cherry-ai.com/cherry-studio-wen-dang/en-us">Documents</a> | <a href="./docs/dev.md">Development</a> | <a href="https://github.com/CherryHQ/cherry-studio/issues">Feedback</a><br></p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
[![][deepwiki-shield]][deepwiki-link]
|
||||
[![][twitter-shield]][twitter-link]
|
||||
[![][discord-shield]][discord-link]
|
||||
@ -45,7 +45,7 @@
|
||||
|
||||
</div>
|
||||
<div align="center">
|
||||
|
||||
|
||||
[![][github-release-shield]][github-release-link]
|
||||
[![][github-nightly-shield]][github-nightly-link]
|
||||
[![][github-contributors-shield]][github-contributors-link]
|
||||
@ -248,10 +248,10 @@ The Enterprise Edition addresses core challenges in team collaboration by centra
|
||||
|
||||
| Feature | Community Edition | Enterprise Edition |
|
||||
| :---------------- | :----------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Open Source** | ✅ Yes | ⭕️ Partially released to customers |
|
||||
| **Open Source** | ✅ Yes | ⭕️ Partially released to customers |
|
||||
| **Cost** | Free for Personal Use / Commercial License | Buyout / Subscription Fee |
|
||||
| **Admin Backend** | — | ● Centralized **Model** Access<br>● **Employee** Management<br>● Shared **Knowledge Base**<br>● **Access** Control<br>● **Data** Backup |
|
||||
| **Server** | — | ✅ Dedicated Private Deployment |
|
||||
| **Server** | — | ✅ Dedicated Private Deployment |
|
||||
|
||||
## Get the Enterprise Edition
|
||||
|
||||
|
||||
@ -69,7 +69,28 @@ git commit --signoff -m "Your commit message"
|
||||
### 其他建议
|
||||
|
||||
- **联系开发者**:在提交 PR 之前,您可以先和开发者进行联系,共同探讨或者获取帮助。
|
||||
- **成为核心开发者**:如果您能够稳定为项目贡献,恭喜您可以成为项目核心开发者,获取到项目成员身份。请查看我们的[成员指南](https://github.com/CherryHQ/community/blob/main/membership.md)
|
||||
|
||||
## 重要贡献指南与关注点
|
||||
|
||||
在提交 Pull Request 之前,请务必阅读以下关键信息:
|
||||
|
||||
### 🚫 暂时限制涉及数据更改的功能性 PR
|
||||
|
||||
**目前,我们不接受涉及 Redux 数据模型或 IndexedDB schema 变更的功能性 Pull Request。**
|
||||
|
||||
我们的核心团队目前正专注于涉及这些数据结构的关键架构更新和基础工作。为确保在此期间的稳定性与专注,此类贡献将暂时由内部进行管理。
|
||||
|
||||
* **需要更改 Redux 状态结构或 IndexedDB schema 的 PR 将会被关闭。**
|
||||
* **此限制是临时性的,并将在 `v2.0.0` 版本发布后解除。** 您可以通过 Issue [#10162](https://github.com/YOUR_ORG/YOUR_REPO/issues/10162) (请替换为您的实际仓库链接) 跟踪 `v2.0.0` 的进展及相关讨论。
|
||||
|
||||
我们非常鼓励以下类型的贡献:
|
||||
* 错误修复 🐞
|
||||
* 性能改进 🚀
|
||||
* 文档更新 📚
|
||||
* 不改变 Redux 数据模型或 IndexedDB schema 的功能(例如,UI 增强、新组件、小型重构)。✨
|
||||
|
||||
感谢您在此重要开发阶段的理解与持续支持。谢谢!
|
||||
|
||||
|
||||
## 联系我们
|
||||
|
||||
|
||||
@ -107,6 +107,7 @@
|
||||
"@agentic/tavily": "^7.3.3",
|
||||
"@ai-sdk/amazon-bedrock": "^3.0.35",
|
||||
"@ai-sdk/google-vertex": "^3.0.40",
|
||||
"@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.4#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.4-8080836bc1.patch",
|
||||
"@ai-sdk/mistral": "^2.0.19",
|
||||
"@ai-sdk/perplexity": "^2.0.13",
|
||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||
@ -130,8 +131,8 @@
|
||||
"@cherrystudio/embedjs-ollama": "^0.1.31",
|
||||
"@cherrystudio/embedjs-openai": "^0.1.31",
|
||||
"@cherrystudio/extension-table-plus": "workspace:^",
|
||||
"@cherrystudio/ui": "workspace:*",
|
||||
"@cherrystudio/openai": "^6.5.0",
|
||||
"@cherrystudio/ui": "workspace:*",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
@ -152,7 +153,7 @@
|
||||
"@modelcontextprotocol/sdk": "^1.17.5",
|
||||
"@mozilla/readability": "^0.6.0",
|
||||
"@notionhq/client": "^2.2.15",
|
||||
"@openrouter/ai-sdk-provider": "^1.1.2",
|
||||
"@openrouter/ai-sdk-provider": "^1.2.0",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/core": "2.0.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
|
||||
@ -395,7 +396,8 @@
|
||||
"@img/sharp-linux-arm": "0.34.4",
|
||||
"@img/sharp-linux-arm64": "0.34.4",
|
||||
"@img/sharp-linux-x64": "0.34.4",
|
||||
"@img/sharp-win32-x64": "0.34.4"
|
||||
"@img/sharp-win32-x64": "0.34.4",
|
||||
"openai@npm:5.12.2": "npm:@cherrystudio/openai@6.5.0"
|
||||
},
|
||||
"packageManager": "yarn@4.9.1",
|
||||
"lint-staged": {
|
||||
|
||||
@ -7,6 +7,7 @@ import { createAzure } from '@ai-sdk/azure'
|
||||
import { type AzureOpenAIProviderSettings } from '@ai-sdk/azure'
|
||||
import { createDeepSeek } from '@ai-sdk/deepseek'
|
||||
import { createGoogleGenerativeAI } from '@ai-sdk/google'
|
||||
import { createHuggingFace } from '@ai-sdk/huggingface'
|
||||
import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai'
|
||||
import { createOpenAICompatible } from '@ai-sdk/openai-compatible'
|
||||
import type { LanguageModelV2 } from '@ai-sdk/provider'
|
||||
@ -29,7 +30,8 @@ export const baseProviderIds = [
|
||||
'azure',
|
||||
'azure-responses',
|
||||
'deepseek',
|
||||
'openrouter'
|
||||
'openrouter',
|
||||
'huggingface'
|
||||
] as const
|
||||
|
||||
/**
|
||||
@ -133,6 +135,12 @@ export const baseProviders = [
|
||||
name: 'OpenRouter',
|
||||
creator: createOpenRouter,
|
||||
supportsImageGeneration: true
|
||||
},
|
||||
{
|
||||
id: 'huggingface',
|
||||
name: 'HuggingFace',
|
||||
creator: createHuggingFace,
|
||||
supportsImageGeneration: true
|
||||
}
|
||||
] as const satisfies BaseProvider[]
|
||||
|
||||
|
||||
@ -138,6 +138,7 @@ export enum IpcChannel {
|
||||
Windows_Close = 'window:close',
|
||||
Windows_IsMaximized = 'window:is-maximized',
|
||||
Windows_MaximizedChanged = 'window:maximized-changed',
|
||||
Windows_NavigateToAbout = 'window:navigate-to-about',
|
||||
|
||||
KnowledgeBase_Create = 'knowledge-base:create',
|
||||
KnowledgeBase_Reset = 'knowledge-base:reset',
|
||||
|
||||
@ -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,31 +65,27 @@ 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'
|
||||
// May group them in shadcn-io/index.ts ?
|
||||
export * from './ui/shadcn-io/dropzone'
|
||||
export * from './ui/shadcn-io/skeleton'
|
||||
export * from './ui/shadcn-io/tabs'
|
||||
/* 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'
|
||||
export * from './primitives/shadcn-io/skeleton'
|
||||
export * from './primitives/shadcn-io/tabs'
|
||||
|
||||
@ -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',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user