mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 09:37:28 +00:00
* ♻️ refactor: migrate SkillStore and IntegrationDetailModal to imperative API - Refactor SkillStore to use createModal imperative API instead of declarative Modal - Refactor IntegrationDetailModal to use createModal with IntegrationDetailContent - Remove open/setOpen state management from all calling components - Add modal-imperative.mdc rule for modal best practices - Reduce code complexity and improve maintainability * 🐛 fix: keep modal open during OAuth flow until connection completes Close modal only after isConnected becomes true, not immediately after handleConnect returns. This ensures useSkillConnect listeners stay alive to detect OAuth completion via postMessage/polling. * 🔧 chore: update dependencies and refactor markdown handling - Updated "@lobehub/ui" to version "^4.27.4" in package.json. - Replaced "markdown-to-txt" with a local utility "markdownToTxt" for converting markdown to plain text across multiple components. - Refactored imports in various files to utilize the new markdownToTxt utility, improving code consistency and maintainability. Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
162 lines
4 KiB
Text
162 lines
4 KiB
Text
---
|
||
description: Modal 命令式调用指南
|
||
globs: "**/features/**/*.tsx"
|
||
alwaysApply: false
|
||
---
|
||
|
||
# Modal 命令式调用指南
|
||
|
||
当需要创建可命令式调用的 Modal 组件时,使用 `@lobehub/ui` 提供的 `createModal` API。
|
||
|
||
## 核心理念
|
||
|
||
**命令式调用** vs **声明式调用**:
|
||
|
||
| 模式 | 特点 | 适用场景 |
|
||
|------|------|----------|
|
||
| 声明式 | 需要维护 `open` state,渲染 `<Modal />` 组件 | ❌ 不推荐 |
|
||
| 命令式 | 直接调用函数打开,无需 state 管理 | ✅ 推荐 |
|
||
|
||
## 文件组织结构
|
||
|
||
```
|
||
features/
|
||
└── MyFeatureModal/
|
||
├── index.tsx # 导出 createXxxModal 函数
|
||
├── MyFeatureContent.tsx # Modal 内容组件
|
||
└── ...其他子组件
|
||
```
|
||
|
||
## createModal 用法(推荐)
|
||
|
||
### 1. 定义 Content 组件 (`MyFeatureContent.tsx`)
|
||
|
||
```tsx
|
||
'use client';
|
||
|
||
import { useModalContext } from '@lobehub/ui';
|
||
import { useTranslation } from 'react-i18next';
|
||
|
||
export const MyFeatureContent = () => {
|
||
const { t } = useTranslation('namespace');
|
||
const { close } = useModalContext(); // 可选:获取关闭方法
|
||
|
||
return (
|
||
<div>
|
||
{/* Modal 内容 */}
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
### 2. 导出 createModal 函数 (`index.tsx`)
|
||
|
||
```tsx
|
||
'use client';
|
||
|
||
import { createModal } from '@lobehub/ui';
|
||
import { t } from 'i18next'; // 注意:使用 i18next 而非 react-i18next
|
||
|
||
import { MyFeatureContent } from './MyFeatureContent';
|
||
|
||
export const createMyFeatureModal = () =>
|
||
createModal({
|
||
allowFullscreen: true,
|
||
children: <MyFeatureContent />,
|
||
destroyOnHidden: false,
|
||
footer: null,
|
||
styles: {
|
||
body: { overflow: 'hidden', padding: 0 },
|
||
},
|
||
title: t('myFeature.title', { ns: 'setting' }),
|
||
width: 'min(80%, 800px)',
|
||
});
|
||
```
|
||
|
||
### 3. 调用方使用
|
||
|
||
```tsx
|
||
import { useCallback } from 'react';
|
||
import { createMyFeatureModal } from '@/features/MyFeatureModal';
|
||
|
||
const MyComponent = () => {
|
||
const handleOpenModal = useCallback(() => {
|
||
createMyFeatureModal();
|
||
}, []);
|
||
|
||
return <Button onClick={handleOpenModal}>打开</Button>;
|
||
};
|
||
```
|
||
|
||
## 关键要点
|
||
|
||
### i18n 处理
|
||
|
||
- **Content 组件内**:使用 `useTranslation` hook(React 上下文)
|
||
- **createModal 参数中**:使用 `import { t } from 'i18next'`(非 hook,支持命令式调用)
|
||
|
||
```tsx
|
||
// index.tsx - 命令式上下文
|
||
import { t } from 'i18next';
|
||
title: t('key', { ns: 'namespace' })
|
||
|
||
// Content.tsx - React 组件上下文
|
||
import { useTranslation } from 'react-i18next';
|
||
const { t } = useTranslation('namespace');
|
||
```
|
||
|
||
### useModalContext Hook
|
||
|
||
在 Content 组件内可使用 `useModalContext` 获取 Modal 控制方法:
|
||
|
||
```tsx
|
||
const { close, setCanDismissByClickOutside } = useModalContext();
|
||
```
|
||
|
||
### ModalHost
|
||
|
||
`createModal` 依赖全局 `<ModalHost />` 组件。项目中已在 `src/layout/GlobalProvider/index.tsx` 配置,无需额外添加。
|
||
|
||
## 常用配置项
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `allowFullscreen` | `boolean` | 允许全屏模式 |
|
||
| `destroyOnHidden` | `boolean` | 关闭时是否销毁内容(`destroyOnClose` 已废弃) |
|
||
| `footer` | `ReactNode \| null` | 底部内容,`null` 表示无底部 |
|
||
| `width` | `string \| number` | Modal 宽度 |
|
||
| `styles.body` | `CSSProperties` | body 区域样式 |
|
||
|
||
## 迁移指南
|
||
|
||
### Before(声明式)
|
||
|
||
```tsx
|
||
// 调用方需要维护 state
|
||
const [open, setOpen] = useState(false);
|
||
|
||
return (
|
||
<>
|
||
<Button onClick={() => setOpen(true)}>打开</Button>
|
||
<MyModal open={open} setOpen={setOpen} />
|
||
</>
|
||
);
|
||
```
|
||
|
||
### After(命令式)
|
||
|
||
```tsx
|
||
// 调用方无需 state,直接调用函数
|
||
const handleOpen = useCallback(() => {
|
||
createMyModal();
|
||
}, []);
|
||
|
||
return <Button onClick={handleOpen}>打开</Button>;
|
||
```
|
||
|
||
## 示例参考
|
||
|
||
- `src/features/SkillStore/index.tsx` - createModal 标准用法
|
||
- `src/features/SkillStore/SkillStoreContent.tsx` - Content 组件示例
|
||
- `src/features/LibraryModal/CreateNew/index.tsx` - 带回调的 createModal 用法
|
||
- `src/features/Electron/updater/UpdateModal.tsx` - 复杂 Modal 控制示例
|