@lark-apaas/coding-templates 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -7,47 +7,36 @@ OpenClaw 项目模板包,供 mclaw CLI 使用。
|
|
|
7
7
|
| 模板 | 目录 | 说明 |
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| html | `template-html/` | 纯 HTML,零依赖,原型验证 |
|
|
10
|
-
|
|
|
11
|
-
| nextjs-fullstack | `template-nextjs-fullstack/` | Next.js App Router + Drizzle + PostgreSQL,全栈 |
|
|
10
|
+
| vite-react | `template-vite-react/` | Vite + React 19 + Tailwind CSS + shadcn/ui |
|
|
12
11
|
|
|
13
12
|
## 包结构
|
|
14
13
|
|
|
15
14
|
```
|
|
16
|
-
|
|
15
|
+
coding-templates/
|
|
17
16
|
├── package.json # npm 包定义(@lark-apaas/coding-templates)
|
|
18
17
|
├── meta.json # 模板元数据(业务侧生成,名称/描述/特性标签)
|
|
19
18
|
├── template-html/
|
|
20
19
|
│ ├── _gitignore
|
|
21
|
-
│ ├── package.json # mclaw.
|
|
20
|
+
│ ├── package.json # mclaw.stack: "html"
|
|
22
21
|
│ └── index.html
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
│ ├── eslint.config.js
|
|
28
|
-
│ ├── postcss.config.js
|
|
29
|
-
│ ├── components.json
|
|
30
|
-
│ └── src/
|
|
31
|
-
│ ├── pages/ # Pages Router 路由
|
|
32
|
-
│ ├── components/ui/ # shadcn/ui 58 个组件
|
|
33
|
-
│ ├── hooks/
|
|
34
|
-
│ ├── lib/
|
|
35
|
-
│ └── styles/globals.css
|
|
36
|
-
└── template-nextjs-fullstack/
|
|
37
|
-
├── _gitignore
|
|
38
|
-
├── _env.local.example # CLI 复制时重命名为 .env.local.example
|
|
39
|
-
├── package.json # mclaw.type: "nextjs-fullstack",name 为 {{projectName}}
|
|
40
|
-
├── drizzle.config.ts
|
|
41
|
-
├── tailwind.config.ts
|
|
22
|
+
└── template-vite-react/
|
|
23
|
+
├── _gitignore # CLI 复制时重命名为 .gitignore
|
|
24
|
+
├── package.json # mclaw.stack: "vite-react",name 为 {{projectName}}
|
|
25
|
+
├── vite.config.ts
|
|
42
26
|
├── eslint.config.js
|
|
43
|
-
├── postcss.config.js
|
|
44
27
|
├── components.json
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
28
|
+
├── client/
|
|
29
|
+
│ ├── index.html
|
|
30
|
+
│ ├── public/
|
|
31
|
+
│ └── src/
|
|
32
|
+
│ ├── app.tsx # 路由注册
|
|
33
|
+
│ ├── index.css # 全局样式 + 主题变量
|
|
34
|
+
│ ├── components/ui/ # shadcn/ui 组件(new-york 风格)
|
|
35
|
+
│ ├── pages/
|
|
36
|
+
│ ├── hooks/
|
|
37
|
+
│ └── lib/
|
|
38
|
+
└── shared/ # 前后端共享代码
|
|
39
|
+
└── types.ts
|
|
51
40
|
```
|
|
52
41
|
|
|
53
42
|
## 特殊文件命名
|
|
@@ -57,72 +46,35 @@ npm publish 会忽略 `.gitignore` 和 `.env*`,因此模板中使用 `_` 前
|
|
|
57
46
|
| 模板中的文件名 | CLI 复制后的文件名 |
|
|
58
47
|
|---|---|
|
|
59
48
|
| `_gitignore` | `.gitignore` |
|
|
60
|
-
| `_env.local
|
|
49
|
+
| `_env.local` | `.env.local` |
|
|
61
50
|
|
|
62
51
|
## 模板类型标识
|
|
63
52
|
|
|
64
|
-
每个模板的 `package.json` 包含 `mclaw.
|
|
53
|
+
每个模板的 `package.json` 包含 `mclaw.stack` 字段,标识模板类型:
|
|
65
54
|
|
|
66
55
|
```json
|
|
67
56
|
{
|
|
68
57
|
"mclaw": {
|
|
69
|
-
"
|
|
58
|
+
"stack": "vite-react",
|
|
59
|
+
"stackVersion": "0.1.0"
|
|
70
60
|
}
|
|
71
61
|
}
|
|
72
62
|
```
|
|
73
63
|
|
|
74
|
-
`meta.json` 由业务侧生成和维护。若 `meta.json` 丢失,CLI 可扫描各模板目录的 `package.json` 中的 `mclaw.type` 恢复。
|
|
75
|
-
|
|
76
|
-
## mclaw CLI 集成方式
|
|
77
|
-
|
|
78
|
-
```javascript
|
|
79
|
-
const path = require('path');
|
|
80
|
-
const fs = require('fs-extra');
|
|
81
|
-
|
|
82
|
-
// 1. 读取模板元数据
|
|
83
|
-
const meta = require('@lark-apaas/coding-templates/meta.json');
|
|
84
|
-
|
|
85
|
-
// 2. 定位模板目录
|
|
86
|
-
const templateDir = path.dirname(require.resolve('@lark-apaas/coding-templates/meta.json'));
|
|
87
|
-
const srcDir = path.join(templateDir, selectedTemplate.dir);
|
|
88
|
-
|
|
89
|
-
// 3. 复制到目标目录
|
|
90
|
-
await fs.copy(srcDir, targetDir);
|
|
91
|
-
|
|
92
|
-
// 4. 重命名特殊文件
|
|
93
|
-
const renames = { '_gitignore': '.gitignore', '_env.local.example': '.env.local.example' };
|
|
94
|
-
for (const [from, to] of Object.entries(renames)) {
|
|
95
|
-
const filePath = path.join(targetDir, from);
|
|
96
|
-
if (fs.existsSync(filePath)) {
|
|
97
|
-
await fs.rename(filePath, path.join(targetDir, to));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 5. 替换 package.json 中的占位符
|
|
102
|
-
const pkgPath = path.join(targetDir, 'package.json');
|
|
103
|
-
if (fs.existsSync(pkgPath)) {
|
|
104
|
-
let content = await fs.readFile(pkgPath, 'utf-8');
|
|
105
|
-
content = content.replace('{{projectName}}', projectName);
|
|
106
|
-
await fs.writeFile(pkgPath, content);
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
64
|
## 技术栈
|
|
111
65
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
- **Next.js 16** + React 19 + TypeScript 5
|
|
115
|
-
- **Tailwind CSS 4**(`@tailwindcss/postcss`,CSS-first 配置)
|
|
116
|
-
- **shadcn/ui**(base-nova 风格,lucide 图标,58 个组件预装)
|
|
117
|
-
- **ESLint 9** flat config(`@eslint/js` + `typescript-eslint`,无 `eslint-config-next`)
|
|
118
|
-
- **next-themes**(暗色模式)
|
|
66
|
+
vite-react 模板技术选型:
|
|
119
67
|
|
|
120
|
-
|
|
68
|
+
- **Vite 8** + React 19 + TypeScript 5
|
|
69
|
+
- **Tailwind CSS 4**(`@tailwindcss/vite`,CSS-first 配置)
|
|
70
|
+
- **shadcn/ui**(new-york 风格,lucide 图标,radix-ui 基础组件)
|
|
71
|
+
- **ESLint 9** flat config
|
|
72
|
+
- **react-router-dom** 客户端路由
|
|
121
73
|
|
|
122
74
|
## 发版
|
|
123
75
|
|
|
124
76
|
```bash
|
|
125
|
-
cd
|
|
77
|
+
cd packages/openclaw/coding-templates
|
|
126
78
|
npm version patch
|
|
127
79
|
npm publish
|
|
128
80
|
```
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
- 样式: Tailwind CSS v4
|
|
7
7
|
- UI 组件: shadcn/ui `import { Button } from "@/components/ui/button";`
|
|
8
8
|
- 图标: lucide-react `import { SearchIcon } from "lucide-react";`
|
|
9
|
-
- 图表:
|
|
9
|
+
- 图表: recharts `import { LineChart } from "recharts";`
|
|
10
10
|
- 动画: framer-motion `import { motion } from "framer-motion";`
|
|
11
11
|
- 路由: react-router-dom `import { Link, useNavigate } from "react-router-dom";`
|
|
12
12
|
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
"@radix-ui/react-toggle-group": "^1.1.10",
|
|
44
44
|
"@radix-ui/react-tooltip": "^1.1.18",
|
|
45
45
|
"@tailwindcss/vite": "^4.2.2",
|
|
46
|
+
"@formkit/auto-animate": "^0.9.0",
|
|
47
|
+
"framer-motion": "^12.38.0",
|
|
46
48
|
"class-variance-authority": "^0.7.1",
|
|
47
49
|
"clsx": "^2.1.1",
|
|
48
50
|
"cmdk": "^1.1.1",
|
|
@@ -59,8 +61,7 @@
|
|
|
59
61
|
"react-router-dom": "^7.13.2",
|
|
60
62
|
"recharts": "^3.8.0",
|
|
61
63
|
"sonner": "^2.0.7",
|
|
62
|
-
|
|
63
|
-
"tailwind-merge": "^3.5.0",
|
|
64
|
+
"tailwind-merge": "^3.5.0",
|
|
64
65
|
"tailwindcss": "^4.2.2",
|
|
65
66
|
"tw-animate-css": "^1.2.0",
|
|
66
67
|
"vaul": "^1.1.2",
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
# shadcn/ui 组件开发指南
|
|
2
|
-
|
|
3
|
-
## 核心原则
|
|
4
|
-
|
|
5
|
-
- 组件位置:`/client/src/components/ui/`
|
|
6
|
-
- 图标库:必须使用 `lucide-react`,禁用 Emoji
|
|
7
|
-
- 查阅源码实现:直接读取组件源码了解最新组件实现
|
|
8
|
-
- 交互系统:Button、Badge 使用 elevate 遮罩系统处理 hover/active 状态(通过 `::after` 伪元素叠加 `--elevate-1` / `--elevate-2`)
|
|
9
|
-
|
|
10
|
-
## 关键组件规范
|
|
11
|
-
|
|
12
|
-
#### 1. Button 组件
|
|
13
|
-
|
|
14
|
-
**导入路径**:`import { Button } from '@/components/ui/button'`
|
|
15
|
-
|
|
16
|
-
**Props**:
|
|
17
|
-
- `variant`: `"link" | "default" | "destructive" | "outline" | "secondary" | "ghost"` - 按钮样式变体
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
// ✅ 使用变体
|
|
21
|
-
<Button variant="secondary">标准按钮</Button>
|
|
22
|
-
|
|
23
|
-
// ✅ 自定义颜色时必须配对前景色
|
|
24
|
-
<Button className="bg-primary text-primary-foreground">自定义按钮</Button>
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
#### 2. Badge 组件
|
|
28
|
-
|
|
29
|
-
**导入路径**:`import { Badge } from '@/components/ui/badge'`
|
|
30
|
-
|
|
31
|
-
**Props**:
|
|
32
|
-
- `variant`: `"default" | "destructive" | "outline" | "secondary"` - 样式变体
|
|
33
|
-
|
|
34
|
-
```tsx
|
|
35
|
-
// ✅ 使用变体
|
|
36
|
-
<Badge>默认</Badge>
|
|
37
|
-
<Badge variant="secondary">次要</Badge>
|
|
38
|
-
<Badge variant="outline">边框</Badge>
|
|
39
|
-
<Badge variant="destructive">危险</Badge>
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
#### 3. Alert 组件
|
|
43
|
-
|
|
44
|
-
**导入路径**:`import { Alert, AlertTitle, AlertDescription, AlertAction } from '@/components/ui/alert'`
|
|
45
|
-
|
|
46
|
-
**Props**:
|
|
47
|
-
- `variant`: `"default" | "destructive" | "success" | "warning"` - 样式变体
|
|
48
|
-
|
|
49
|
-
```tsx
|
|
50
|
-
// ✅ default / destructive / success / warning 四种变体
|
|
51
|
-
<Alert variant="success">
|
|
52
|
-
<CheckCircle className="size-4" />
|
|
53
|
-
<AlertTitle>操作成功</AlertTitle>
|
|
54
|
-
<AlertDescription>您的更改已保存</AlertDescription>
|
|
55
|
-
</Alert>
|
|
56
|
-
|
|
57
|
-
<Alert variant="warning">
|
|
58
|
-
<AlertTriangle className="size-4" />
|
|
59
|
-
<AlertTitle>警告</AlertTitle>
|
|
60
|
-
<AlertDescription>请注意检查</AlertDescription>
|
|
61
|
-
</Alert>
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
#### 4. Empty 组件
|
|
66
|
-
|
|
67
|
-
**导入路径**:`import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent } from '@/components/ui/empty'`
|
|
68
|
-
|
|
69
|
-
**子组件 Props**:
|
|
70
|
-
- `EmptyMedia` 组件的 `variant`: `"default" | "icon"` - 媒体展示方式
|
|
71
|
-
|
|
72
|
-
```tsx
|
|
73
|
-
// 标准结构:EmptyHeader 包含 EmptyMedia + EmptyTitle + EmptyDescription
|
|
74
|
-
<Empty>
|
|
75
|
-
<EmptyHeader>
|
|
76
|
-
<EmptyMedia variant="icon">
|
|
77
|
-
<SearchIcon className="size-6" />
|
|
78
|
-
</EmptyMedia>
|
|
79
|
-
<EmptyTitle>暂无数据</EmptyTitle>
|
|
80
|
-
<EmptyDescription>当前没有找到相关内容</EmptyDescription>
|
|
81
|
-
</EmptyHeader>
|
|
82
|
-
<EmptyContent>
|
|
83
|
-
<Button>添加数据</Button>
|
|
84
|
-
</EmptyContent>
|
|
85
|
-
</Empty>
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
#### 5. Card Padding 系统
|
|
89
|
-
|
|
90
|
-
**导入路径**:`import { CardHeader, CardContent, CardFooter } from '@/components/ui/card'`
|
|
91
|
-
|
|
92
|
-
- `CardHeader`: `p-6` (24px 全方向)
|
|
93
|
-
- `CardContent`: `p-6 pt-0` (与 header 无缝衔接)
|
|
94
|
-
- `CardFooter`: `p-6 pt-0` (与 content 无缝衔接)
|
|
95
|
-
|
|
96
|
-
#### 6. Dialog 组件
|
|
97
|
-
|
|
98
|
-
**导入路径**:`import { Dialog, DialogContent } from '@/components/ui/dialog'`
|
|
99
|
-
|
|
100
|
-
Dialog 默认提供了右上角的close能力,同时也提供了自定义关闭按钮的能力,即通过设置showCloseButton为false来关闭默认的关闭按钮。所以当默认存在close时,禁止在内容区域提供自定义的关闭按钮。
|
|
101
|
-
|
|
102
|
-
#### 7. Image 组件
|
|
103
|
-
|
|
104
|
-
**导入路径**:`import { Image } from '@/components/ui/image'`
|
|
105
|
-
|
|
106
|
-
**Props**:支持原生 `<img>` 标签所有属性。
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement>
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
**使用规范**:
|
|
113
|
-
|
|
114
|
-
1. **响应式图片场景**:当图片尺寸需要根据视口宽度变化时,必须设置 `sizes` 属性
|
|
115
|
-
2. **固定尺寸图片场景**:当图片有固定尺寸时,必须设置 `width` 属性(number 类型)
|
|
116
|
-
3. **必须提供有意义的 `alt` 属性**
|
|
117
|
-
|
|
118
|
-
```tsx
|
|
119
|
-
// ✅ 响应式图片
|
|
120
|
-
<Image
|
|
121
|
-
src="/path/to/image.jpg"
|
|
122
|
-
alt="描述文字"
|
|
123
|
-
sizes="(max-width: 768px) 100vw, 50vw"
|
|
124
|
-
/>
|
|
125
|
-
|
|
126
|
-
// ✅ 固定尺寸图片(width/height 使用 number)
|
|
127
|
-
<Image
|
|
128
|
-
src="/path/to/image.jpg"
|
|
129
|
-
alt="描述文字"
|
|
130
|
-
width={300}
|
|
131
|
-
height={200}
|
|
132
|
-
/>
|
|
133
|
-
|
|
134
|
-
```
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { Streamdown as StreamdownPrimitive } from 'streamdown';
|
|
5
|
-
|
|
6
|
-
import { cn } from '@/lib/utils';
|
|
7
|
-
import { omit } from 'es-toolkit';
|
|
8
|
-
|
|
9
|
-
const defaultComponents: NonNullable<
|
|
10
|
-
React.ComponentProps<typeof StreamdownPrimitive>['components']
|
|
11
|
-
> = {
|
|
12
|
-
ol: ({ className, ...props }) => (
|
|
13
|
-
<ol
|
|
14
|
-
className={cn(className)}
|
|
15
|
-
data-streamdown="ordered-list"
|
|
16
|
-
{...omit(props, ['node'])}
|
|
17
|
-
/>
|
|
18
|
-
),
|
|
19
|
-
li: ({ className, ...props }) => (
|
|
20
|
-
<li
|
|
21
|
-
className={cn(className)}
|
|
22
|
-
data-streamdown="list-item"
|
|
23
|
-
{...omit(props, ['node'])}
|
|
24
|
-
/>
|
|
25
|
-
),
|
|
26
|
-
ul: ({ className, ...props }) => (
|
|
27
|
-
<ul
|
|
28
|
-
className={cn(className)}
|
|
29
|
-
data-streamdown="unordered-list"
|
|
30
|
-
{...omit(props, ['node'])}
|
|
31
|
-
/>
|
|
32
|
-
),
|
|
33
|
-
hr: ({ className, ...props }) => (
|
|
34
|
-
<hr
|
|
35
|
-
className={cn(className)}
|
|
36
|
-
data-streamdown="horizontal-rule"
|
|
37
|
-
{...omit(props, ['node'])}
|
|
38
|
-
/>
|
|
39
|
-
),
|
|
40
|
-
strong: ({ className, ...props }) => (
|
|
41
|
-
<strong
|
|
42
|
-
className={cn(className)}
|
|
43
|
-
data-streamdown="strong"
|
|
44
|
-
{...omit(props, ['node'])}
|
|
45
|
-
/>
|
|
46
|
-
),
|
|
47
|
-
a: ({ className, href, ...props }) => {
|
|
48
|
-
const isIncomplete = href === 'streamdown:incomplete-link';
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<a
|
|
52
|
-
className={cn(className)}
|
|
53
|
-
data-incomplete={isIncomplete}
|
|
54
|
-
data-streamdown="link"
|
|
55
|
-
href={href}
|
|
56
|
-
rel={props.rel ?? 'noreferrer'}
|
|
57
|
-
target={props.target ?? '_blank'}
|
|
58
|
-
{...omit(props, ['node'])}
|
|
59
|
-
/>
|
|
60
|
-
);
|
|
61
|
-
},
|
|
62
|
-
h1: ({ className, ...props }) => (
|
|
63
|
-
<h1
|
|
64
|
-
className={cn(className)}
|
|
65
|
-
data-streamdown="heading-1"
|
|
66
|
-
{...omit(props, ['node'])}
|
|
67
|
-
/>
|
|
68
|
-
),
|
|
69
|
-
h2: ({ className, ...props }) => (
|
|
70
|
-
<h2
|
|
71
|
-
className={cn(className)}
|
|
72
|
-
data-streamdown="heading-2"
|
|
73
|
-
{...omit(props, ['node'])}
|
|
74
|
-
/>
|
|
75
|
-
),
|
|
76
|
-
h3: ({ className, ...props }) => (
|
|
77
|
-
<h3
|
|
78
|
-
className={cn(className)}
|
|
79
|
-
data-streamdown="heading-3"
|
|
80
|
-
{...omit(props, ['node'])}
|
|
81
|
-
/>
|
|
82
|
-
),
|
|
83
|
-
h4: ({ className, ...props }) => (
|
|
84
|
-
<h4
|
|
85
|
-
className={cn(className)}
|
|
86
|
-
data-streamdown="heading-4"
|
|
87
|
-
{...omit(props, ['node'])}
|
|
88
|
-
/>
|
|
89
|
-
),
|
|
90
|
-
h5: ({ className, ...props }) => (
|
|
91
|
-
<h5
|
|
92
|
-
className={cn(className)}
|
|
93
|
-
data-streamdown="heading-5"
|
|
94
|
-
{...omit(props, ['node'])}
|
|
95
|
-
/>
|
|
96
|
-
),
|
|
97
|
-
h6: ({ className, ...props }) => (
|
|
98
|
-
<h6
|
|
99
|
-
className={cn(className)}
|
|
100
|
-
data-streamdown="heading-6"
|
|
101
|
-
{...omit(props, ['node'])}
|
|
102
|
-
/>
|
|
103
|
-
),
|
|
104
|
-
table: ({ className, ...props }) => (
|
|
105
|
-
<table
|
|
106
|
-
className={cn(className)}
|
|
107
|
-
data-streamdown="table-wrapper"
|
|
108
|
-
{...omit(props, ['node'])}
|
|
109
|
-
/>
|
|
110
|
-
),
|
|
111
|
-
thead: ({ className, ...props }) => (
|
|
112
|
-
<thead
|
|
113
|
-
className={cn(className)}
|
|
114
|
-
data-streamdown="table-header"
|
|
115
|
-
{...omit(props, ['node'])}
|
|
116
|
-
/>
|
|
117
|
-
),
|
|
118
|
-
tbody: ({ className, ...props }) => (
|
|
119
|
-
<tbody
|
|
120
|
-
className={cn(className)}
|
|
121
|
-
data-streamdown="table-body"
|
|
122
|
-
{...omit(props, ['node'])}
|
|
123
|
-
/>
|
|
124
|
-
),
|
|
125
|
-
tr: ({ className, ...props }) => (
|
|
126
|
-
<tr
|
|
127
|
-
className={cn(className)}
|
|
128
|
-
data-streamdown="table-row"
|
|
129
|
-
{...omit(props, ['node'])}
|
|
130
|
-
/>
|
|
131
|
-
),
|
|
132
|
-
th: ({ className, ...props }) => (
|
|
133
|
-
<th
|
|
134
|
-
className={cn(className)}
|
|
135
|
-
data-streamdown="table-header-cell"
|
|
136
|
-
{...omit(props, ['node'])}
|
|
137
|
-
/>
|
|
138
|
-
),
|
|
139
|
-
td: ({ className, ...props }) => (
|
|
140
|
-
<td
|
|
141
|
-
className={cn(className)}
|
|
142
|
-
data-streamdown="table-cell"
|
|
143
|
-
{...omit(props, ['node'])}
|
|
144
|
-
/>
|
|
145
|
-
),
|
|
146
|
-
blockquote: ({ className, ...props }) => (
|
|
147
|
-
<blockquote
|
|
148
|
-
className={cn(className)}
|
|
149
|
-
data-streamdown="blockquote"
|
|
150
|
-
{...omit(props, ['node'])}
|
|
151
|
-
/>
|
|
152
|
-
),
|
|
153
|
-
sup: ({ className, ...props }) => (
|
|
154
|
-
<sup className={cn(className)} {...omit(props, ['node'])} />
|
|
155
|
-
),
|
|
156
|
-
sub: ({ className, ...props }) => (
|
|
157
|
-
<sub className={cn(className)} {...omit(props, ['node'])} />
|
|
158
|
-
),
|
|
159
|
-
p: ({ className, ...props }) => (
|
|
160
|
-
<p className={cn(className)} {...omit(props, ['node'])} />
|
|
161
|
-
),
|
|
162
|
-
section: ({ className, ...props }) => (
|
|
163
|
-
<section className={cn(className)} {...omit(props, ['node'])} />
|
|
164
|
-
),
|
|
165
|
-
img: ({ className, alt, ...props }) => (
|
|
166
|
-
<img className={cn(className)} alt={alt ?? ''} {...omit(props, ['node'])} />
|
|
167
|
-
),
|
|
168
|
-
pre: ({ children }) => <div className="not-prose">{children}</div>,
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
export function Streamdown({
|
|
172
|
-
className,
|
|
173
|
-
components,
|
|
174
|
-
...props
|
|
175
|
-
}: React.ComponentProps<typeof StreamdownPrimitive>) {
|
|
176
|
-
return (
|
|
177
|
-
<StreamdownPrimitive
|
|
178
|
-
className={cn(
|
|
179
|
-
'prose max-w-none dark:prose-invert',
|
|
180
|
-
className,
|
|
181
|
-
)}
|
|
182
|
-
components={{ ...defaultComponents, ...components }}
|
|
183
|
-
{...props}
|
|
184
|
-
/>
|
|
185
|
-
);
|
|
186
|
-
}
|