@lark-apaas/coding-templates 0.1.5 → 0.1.7
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/package.json +1 -1
- package/template-vite-react/README.md +175 -0
- package/template-vite-react/client/index.html +2 -1
- package/template-vite-react/client/src/components/layout.tsx +0 -2
- package/template-vite-react/client/src/components/ui/README.md +134 -0
- package/template-vite-react/client/src/components/ui/accordion.tsx +22 -28
- package/template-vite-react/client/src/components/ui/alert-dialog.tsx +34 -64
- package/template-vite-react/client/src/components/ui/alert.tsx +10 -15
- package/template-vite-react/client/src/components/ui/aspect-ratio.tsx +5 -16
- package/template-vite-react/client/src/components/ui/avatar.tsx +11 -67
- package/template-vite-react/client/src/components/ui/badge.tsx +21 -31
- package/template-vite-react/client/src/components/ui/breadcrumb.tsx +23 -39
- package/template-vite-react/client/src/components/ui/button.tsx +36 -25
- package/template-vite-react/client/src/components/ui/calendar.tsx +37 -43
- package/template-vite-react/client/src/components/ui/card.tsx +73 -94
- package/template-vite-react/client/src/components/ui/carousel.tsx +7 -8
- package/template-vite-react/client/src/components/ui/chart.tsx +35 -49
- package/template-vite-react/client/src/components/ui/checkbox.tsx +10 -7
- package/template-vite-react/client/src/components/ui/collapsible.tsx +20 -6
- package/template-vite-react/client/src/components/ui/command.tsx +52 -40
- package/template-vite-react/client/src/components/ui/context-menu.tsx +170 -117
- package/template-vite-react/client/src/components/ui/dialog.tsx +37 -52
- package/template-vite-react/client/src/components/ui/drawer.tsx +12 -9
- package/template-vite-react/client/src/components/ui/dropdown-menu.tsx +194 -133
- package/template-vite-react/client/src/components/ui/hover-card.tsx +24 -29
- package/template-vite-react/client/src/components/ui/input-group.tsx +39 -29
- package/template-vite-react/client/src/components/ui/input-otp.tsx +7 -17
- package/template-vite-react/client/src/components/ui/input.tsx +4 -3
- package/template-vite-react/client/src/components/ui/label.tsx +9 -3
- package/template-vite-react/client/src/components/ui/menubar.tsx +160 -92
- package/template-vite-react/client/src/components/ui/navigation-menu.tsx +45 -45
- package/template-vite-react/client/src/components/ui/pagination.tsx +32 -35
- package/template-vite-react/client/src/components/ui/popover.tsx +20 -62
- package/template-vite-react/client/src/components/ui/progress.tsx +14 -64
- package/template-vite-react/client/src/components/ui/radio-group.tsx +20 -13
- package/template-vite-react/client/src/components/ui/resizable.tsx +18 -10
- package/template-vite-react/client/src/components/ui/scroll-area.tsx +13 -10
- package/template-vite-react/client/src/components/ui/select.tsx +122 -78
- package/template-vite-react/client/src/components/ui/separator.tsx +7 -4
- package/template-vite-react/client/src/components/ui/sheet.tsx +42 -41
- package/template-vite-react/client/src/components/ui/sidebar.tsx +162 -156
- package/template-vite-react/client/src/components/ui/skeleton.tsx +1 -1
- package/template-vite-react/client/src/components/ui/slider.tsx +52 -22
- package/template-vite-react/client/src/components/ui/sonner.tsx +44 -26
- package/template-vite-react/client/src/components/ui/switch.tsx +9 -8
- package/template-vite-react/client/src/components/ui/table.tsx +5 -5
- package/template-vite-react/client/src/components/ui/tabs.tsx +24 -38
- package/template-vite-react/client/src/components/ui/textarea.tsx +1 -1
- package/template-vite-react/client/src/components/ui/toggle-group.tsx +14 -20
- package/template-vite-react/client/src/components/ui/toggle.tsx +13 -10
- package/template-vite-react/client/src/components/ui/tooltip.tsx +30 -33
- package/template-vite-react/client/src/index.css +130 -0
- package/template-vite-react/client/src/main.tsx +1 -4
- package/template-vite-react/components.json +2 -6
- package/template-vite-react/eslint.config.js +11 -0
- package/template-vite-react/package.json +29 -2
- package/template-vite-react/client/src/components/header.tsx +0 -22
- package/template-vite-react/client/src/components/theme-provider.tsx +0 -45
- package/template-vite-react/client/src/components/ui/icons/file-ae-colorful-icon.tsx +0 -21
- package/template-vite-react/client/src/components/ui/icons/file-ai-colorful-icon.tsx +0 -36
- package/template-vite-react/client/src/components/ui/icons/file-android-colorful-icon.tsx +0 -33
- package/template-vite-react/client/src/components/ui/icons/file-audio-colorful-icon.tsx +0 -21
- package/template-vite-react/client/src/components/ui/icons/file-code-colorful-icon.tsx +0 -28
- package/template-vite-react/client/src/components/ui/icons/file-csv-colorful-icon.tsx +0 -21
- package/template-vite-react/client/src/components/ui/icons/file-eml-colorful-icon.tsx +0 -29
- package/template-vite-react/client/src/components/ui/icons/file-ios-colorful-icon.tsx +0 -25
- package/template-vite-react/client/src/components/ui/icons/file-keynote-colorful-icon.tsx +0 -29
- package/template-vite-react/client/src/components/ui/icons/file-pages-colorful-icon.tsx +0 -29
- package/template-vite-react/client/src/components/ui/icons/file-ps-colorful-icon.tsx +0 -21
- package/template-vite-react/client/src/components/ui/icons/file-sketch-colorful-icon.tsx +0 -21
- package/template-vite-react/client/src/components/ui/icons/file-slide-colorful-icon.tsx +0 -21
- package/template-vite-react/client/src/components/ui/icons/file-vcf-colorful-icon.tsx +0 -29
- package/template-vite-react/client/src/components/ui/icons/file-wiki-excel-colorful-icon.tsx +0 -23
- package/template-vite-react/client/src/components/ui/icons/file-wiki-image-colorful-icon.tsx +0 -27
- package/template-vite-react/client/src/components/ui/icons/file-wiki-pdf-colorful-icon.tsx +0 -20
- package/template-vite-react/client/src/components/ui/icons/file-wiki-ppt-colorful-icon.tsx +0 -21
- package/template-vite-react/client/src/components/ui/icons/file-wiki-text-colorful-icon.tsx +0 -12
- package/template-vite-react/client/src/components/ui/icons/file-wiki-unknown-colorful-icon.tsx +0 -14
- package/template-vite-react/client/src/components/ui/icons/file-wiki-video-colorful-icon.tsx +0 -23
- package/template-vite-react/client/src/components/ui/icons/file-wiki-word-colorful-icon.tsx +0 -38
- package/template-vite-react/client/src/components/ui/icons/file-wiki-zip-colorful-icon.tsx +0 -21
- package/template-vite-react/client/src/types/index.ts +0 -1
package/package.json
CHANGED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# 开发规范
|
|
2
|
+
|
|
3
|
+
## 技术栈
|
|
4
|
+
|
|
5
|
+
- 前端: React 19 + TypeScript
|
|
6
|
+
- 样式: Tailwind CSS v4
|
|
7
|
+
- UI 组件: shadcn/ui `import { Button } from "@/components/ui/button";`
|
|
8
|
+
- 图标: lucide-react `import { SearchIcon } from "lucide-react";`
|
|
9
|
+
- 图表: ReactECharts `import ReactECharts from "echarts-for-react";`
|
|
10
|
+
- 动画: framer-motion `import { motion } from "framer-motion";`
|
|
11
|
+
- 路由: react-router-dom `import { Link, useNavigate } from "react-router-dom";`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 项目结构
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
client/src/
|
|
19
|
+
├── app.tsx # 路由注册
|
|
20
|
+
├── index.css # 全局样式 + 主题变量
|
|
21
|
+
├── components/ # 全局共享组件
|
|
22
|
+
│ └── ui/ # shadcn/ui 内置组件(勿手动修改)
|
|
23
|
+
├── pages/ # 页面模块(每个页面一个目录)
|
|
24
|
+
│ └── home/
|
|
25
|
+
│ ├── index.tsx # 页面入口
|
|
26
|
+
│ └── components/ # 页面专属组件
|
|
27
|
+
├── hooks/ # 自定义 Hooks
|
|
28
|
+
└── lib/ # 工具函数(cn() 等)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 页面与组件规范
|
|
34
|
+
|
|
35
|
+
**页面文件只做骨架编排,不包含具体 UI 实现。**
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
// client/src/pages/dashboard/index.tsx
|
|
39
|
+
import { StatsSection } from "./components/stats-section";
|
|
40
|
+
import { DataTableSection } from "./components/data-table-section";
|
|
41
|
+
|
|
42
|
+
export default function DashboardPage() {
|
|
43
|
+
return (
|
|
44
|
+
<div className="space-y-8">
|
|
45
|
+
<StatsSection />
|
|
46
|
+
<DataTableSection />
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**规则:**
|
|
53
|
+
|
|
54
|
+
- 每个视觉上独立的区块拆为一个组件文件,即使只出现一次
|
|
55
|
+
- 单个组件文件不超过 **150 行**,超出时进一步拆分子组件
|
|
56
|
+
- 页面专属组件放在 `pages/<page>/components/`
|
|
57
|
+
- 跨页面复用的组件放在 `client/src/components/`
|
|
58
|
+
- 相同 UI 片段出现 **≥2 次**时,必须提取为可复用组件
|
|
59
|
+
- 文件名 kebab-case(`stat-card.tsx`),组件名 PascalCase(`StatCard`)
|
|
60
|
+
- 组件之间**禁止循环引用**
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 路由注册
|
|
65
|
+
|
|
66
|
+
新增页面在 `client/src/app.tsx` 中注册:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<Route element={<Layout />}>
|
|
70
|
+
<Route index element={<HomePage />} />
|
|
71
|
+
<Route path="dashboard" element={<DashboardPage />} />
|
|
72
|
+
<Route path="*" element={<NotFoundPage />} />
|
|
73
|
+
</Route>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**新增页面步骤:**
|
|
77
|
+
|
|
78
|
+
1. 在 `client/src/pages/` 下新建页面目录和 `index.tsx`
|
|
79
|
+
2. 在 `app.tsx` 的 `<Routes>` 内添加 `<Route>` 配置
|
|
80
|
+
|
|
81
|
+
**路由跳转必须使用 react-router-dom:**
|
|
82
|
+
|
|
83
|
+
- 组件内跳转:`<Link to="/dashboard">Dashboard</Link>`
|
|
84
|
+
- 编程式跳转:`const navigate = useNavigate(); navigate("/dashboard");`
|
|
85
|
+
- **禁止使用** `<a href="/">` 或 `window.location` 进行页面内跳转
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 样式与主题
|
|
90
|
+
|
|
91
|
+
### 主题变量
|
|
92
|
+
|
|
93
|
+
主题色定义在 `client/src/index.css` 中,通过 `:root` CSS 变量 + `@theme inline` 注册到 Tailwind。
|
|
94
|
+
|
|
95
|
+
**语义化颜色对照:**
|
|
96
|
+
|
|
97
|
+
| 用途 | Tailwind 类 | CSS 变量 |
|
|
98
|
+
|------|------------|----------|
|
|
99
|
+
| 页面背景 | `bg-background` | `--background` |
|
|
100
|
+
| 主文本 | `text-foreground` | `--foreground` |
|
|
101
|
+
| 卡片背景 | `bg-card` | `--card` |
|
|
102
|
+
| 次要文本 | `text-muted-foreground` | `--muted-foreground` |
|
|
103
|
+
| 主色 | `bg-primary` / `text-primary` | `--primary` |
|
|
104
|
+
| 强调色 | `bg-accent` | `--accent` |
|
|
105
|
+
| 边框 | `border-border` | `--border` |
|
|
106
|
+
| 危险色 | `text-destructive` | `--destructive` |
|
|
107
|
+
| 成功色 | `text-success-foreground` | `--success-foreground` |
|
|
108
|
+
| 警告色 | `text-warning-foreground` | `--warning-foreground` |
|
|
109
|
+
| 图表色 | `bg-chart-1` ~ `bg-chart-5` | `--chart-1` ~ `--chart-5` |
|
|
110
|
+
|
|
111
|
+
**颜色使用规则:**
|
|
112
|
+
|
|
113
|
+
- 主题色(背景、文本、主色、边框等)**必须使用语义化变量类**
|
|
114
|
+
- 灰阶辅助色(细节装饰、次要分隔线)可使用 Tailwind 原生色(如 `text-gray-500`)
|
|
115
|
+
- 类名合并使用 `cn()`:`import { cn } from "@/lib/utils"`
|
|
116
|
+
|
|
117
|
+
### 主题增量修改规范
|
|
118
|
+
|
|
119
|
+
修改主题时,**只覆盖需要变更的变量**:
|
|
120
|
+
|
|
121
|
+
```css
|
|
122
|
+
/* 正确:仅修改需要的变量 */
|
|
123
|
+
:root {
|
|
124
|
+
--primary: hsl(150, 60%, 40%);
|
|
125
|
+
--primary-foreground: hsl(0, 0%, 100%);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* 禁止:复制整个 :root 块后修改 */
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- 新增自定义颜色变量时,必须同时在 `:root` 和 `@theme inline` 中注册
|
|
132
|
+
- 禁止直接修改 `@theme inline` 中已有的 `--color-*` 映射关系
|
|
133
|
+
- 禁止删除已有的主题变量(可能被 shadcn/ui 组件依赖)
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 布局与交互
|
|
138
|
+
|
|
139
|
+
### 响应式布局
|
|
140
|
+
|
|
141
|
+
- 容器使用 `max-w-*` + `mx-auto` 居中,禁止内容在大屏贴边延伸
|
|
142
|
+
- 多列布局使用 `grid` + 断点类:`grid-cols-1 md:grid-cols-2 lg:grid-cols-3`
|
|
143
|
+
- flex 子元素设置 `min-w-0`,多元素横排时加 `flex-wrap`
|
|
144
|
+
- 禁止固定像素宽度作为主容器(如 `w-[720px]`)
|
|
145
|
+
|
|
146
|
+
### 内容自适应
|
|
147
|
+
|
|
148
|
+
- 区块高度由内容撑开,禁止固定 `h-` 值(图表容器除外)
|
|
149
|
+
- 图片:`max-w-full h-auto`
|
|
150
|
+
- 长文本:`break-words`
|
|
151
|
+
- 单行截断:`truncate`
|
|
152
|
+
- 表格/代码块:`overflow-x-auto`
|
|
153
|
+
|
|
154
|
+
### 交互规范
|
|
155
|
+
|
|
156
|
+
- 所有交互元素(按钮、链接、标签页等)必须有**实际交互逻辑**和**可见反馈**
|
|
157
|
+
- 禁止空函数(`onClick={() => {}}`)或仅 `console.log` 的响应
|
|
158
|
+
- 禁止 `href="#"` 占位链接、无内容切换的标签页、空下拉菜单
|
|
159
|
+
- 禁止"导出"、"分享"等无法真正执行的操作按钮
|
|
160
|
+
- 如功能暂未实现,**删除该入口**,不实现假按钮
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 自检清单
|
|
165
|
+
|
|
166
|
+
| 检查项 | 验收标准 |
|
|
167
|
+
|--------|---------|
|
|
168
|
+
| 页面拆分 | 页面文件只做骨架编排;每个区块为独立组件;单文件 ≤150 行 |
|
|
169
|
+
| 组件复用 | 相同片段 ≥2 次已提取为组件;文件名 kebab-case,组件名 PascalCase |
|
|
170
|
+
| 路由注册 | 新页面已在 `app.tsx` 注册;跳转使用 `<Link>` / `useNavigate()`,无 `<a href>` |
|
|
171
|
+
| 主题色 | 使用语义化变量类(`bg-background`、`text-primary` 等);未硬编码颜色值 |
|
|
172
|
+
| 主题修改 | 仅增量覆盖变更的变量;新增色同时注册 `:root` 和 `@theme inline` |
|
|
173
|
+
| 响应式 | 容器 `max-w-*` + `mx-auto`;多列布局窄屏退化单列;flex 有 `min-w-0` |
|
|
174
|
+
| 内容自适应 | 无固定 `h-`(图表除外);长文本 `break-words`;表格 `overflow-x-auto` |
|
|
175
|
+
| 交互完整性 | 所有按钮/链接有实际处理器和可见反馈;无空响应、无假按钮 |
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<title
|
|
7
|
+
<title>应用标题</title>
|
|
8
|
+
<link href="https://lf3-static.bytednsdoc.com/obj/eden-cn/ylcylz_fsph_ryhs/ljhwZthlaukjlkulzlp/feisuda/feisuda.svg" rel="shortcut icon"/>
|
|
8
9
|
</head>
|
|
9
10
|
<body>
|
|
10
11
|
<div id="root"></div>
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { Outlet } from "react-router-dom";
|
|
2
|
-
import { Header } from "@/components/header";
|
|
3
2
|
|
|
4
3
|
export function Layout() {
|
|
5
4
|
return (
|
|
6
5
|
<div className="min-h-screen bg-background text-foreground">
|
|
7
|
-
<Header />
|
|
8
6
|
<main className="container mx-auto px-4 py-8">
|
|
9
7
|
<Outlet />
|
|
10
8
|
</main>
|
|
@@ -0,0 +1,134 @@
|
|
|
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,23 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
|
5
|
+
import { ChevronDownIcon } from "lucide-react"
|
|
2
6
|
|
|
3
7
|
import { cn } from "@/lib/utils"
|
|
4
|
-
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
|
5
8
|
|
|
6
|
-
function Accordion({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
className={cn("flex w-full flex-col", className)}
|
|
11
|
-
{...props}
|
|
12
|
-
/>
|
|
13
|
-
)
|
|
9
|
+
function Accordion({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
|
12
|
+
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
function AccordionItem({
|
|
15
|
+
function AccordionItem({
|
|
16
|
+
className,
|
|
17
|
+
...props
|
|
18
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
17
19
|
return (
|
|
18
20
|
<AccordionPrimitive.Item
|
|
19
21
|
data-slot="accordion-item"
|
|
20
|
-
className={cn("
|
|
22
|
+
className={cn("border-b last:border-b-0", className)}
|
|
21
23
|
{...props}
|
|
22
24
|
/>
|
|
23
25
|
)
|
|
@@ -27,20 +29,19 @@ function AccordionTrigger({
|
|
|
27
29
|
className,
|
|
28
30
|
children,
|
|
29
31
|
...props
|
|
30
|
-
}: AccordionPrimitive.Trigger
|
|
32
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
|
31
33
|
return (
|
|
32
34
|
<AccordionPrimitive.Header className="flex">
|
|
33
35
|
<AccordionPrimitive.Trigger
|
|
34
36
|
data-slot="accordion-trigger"
|
|
35
37
|
className={cn(
|
|
36
|
-
"
|
|
38
|
+
"focus-visible:border-ring focus-visible:ring-ring/20 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none enabled:hover:underline focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
|
37
39
|
className
|
|
38
40
|
)}
|
|
39
41
|
{...props}
|
|
40
42
|
>
|
|
41
43
|
{children}
|
|
42
|
-
<ChevronDownIcon
|
|
43
|
-
<ChevronUpIcon data-slot="accordion-trigger-icon" className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline" />
|
|
44
|
+
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
|
44
45
|
</AccordionPrimitive.Trigger>
|
|
45
46
|
</AccordionPrimitive.Header>
|
|
46
47
|
)
|
|
@@ -50,22 +51,15 @@ function AccordionContent({
|
|
|
50
51
|
className,
|
|
51
52
|
children,
|
|
52
53
|
...props
|
|
53
|
-
}: AccordionPrimitive.
|
|
54
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
|
54
55
|
return (
|
|
55
|
-
<AccordionPrimitive.
|
|
56
|
+
<AccordionPrimitive.Content
|
|
56
57
|
data-slot="accordion-content"
|
|
57
|
-
className="
|
|
58
|
+
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
|
58
59
|
{...props}
|
|
59
60
|
>
|
|
60
|
-
<div
|
|
61
|
-
|
|
62
|
-
"h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
|
|
63
|
-
className
|
|
64
|
-
)}
|
|
65
|
-
>
|
|
66
|
-
{children}
|
|
67
|
-
</div>
|
|
68
|
-
</AccordionPrimitive.Panel>
|
|
61
|
+
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
|
62
|
+
</AccordionPrimitive.Content>
|
|
69
63
|
)
|
|
70
64
|
}
|
|
71
65
|
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
-
import
|
|
4
|
+
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
|
5
5
|
|
|
6
6
|
import { cn } from "@/lib/utils"
|
|
7
|
-
import {
|
|
7
|
+
import { buttonVariants } from "@/components/ui/button"
|
|
8
8
|
|
|
9
|
-
function AlertDialog({
|
|
9
|
+
function AlertDialog({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
|
10
12
|
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
function AlertDialogTrigger({
|
|
15
|
+
function AlertDialogTrigger({
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
|
14
18
|
return (
|
|
15
19
|
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
|
16
20
|
)
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
function AlertDialogPortal({
|
|
23
|
+
function AlertDialogPortal({
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
|
20
26
|
return (
|
|
21
27
|
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
|
22
28
|
)
|
|
@@ -25,12 +31,12 @@ function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
|
|
|
25
31
|
function AlertDialogOverlay({
|
|
26
32
|
className,
|
|
27
33
|
...props
|
|
28
|
-
}: AlertDialogPrimitive.
|
|
34
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
|
29
35
|
return (
|
|
30
|
-
<AlertDialogPrimitive.
|
|
36
|
+
<AlertDialogPrimitive.Overlay
|
|
31
37
|
data-slot="alert-dialog-overlay"
|
|
32
38
|
className={cn(
|
|
33
|
-
"
|
|
39
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
34
40
|
className
|
|
35
41
|
)}
|
|
36
42
|
{...props}
|
|
@@ -40,19 +46,15 @@ function AlertDialogOverlay({
|
|
|
40
46
|
|
|
41
47
|
function AlertDialogContent({
|
|
42
48
|
className,
|
|
43
|
-
size = "default",
|
|
44
49
|
...props
|
|
45
|
-
}: AlertDialogPrimitive.
|
|
46
|
-
size?: "default" | "sm"
|
|
47
|
-
}) {
|
|
50
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
|
|
48
51
|
return (
|
|
49
52
|
<AlertDialogPortal>
|
|
50
53
|
<AlertDialogOverlay />
|
|
51
|
-
<AlertDialogPrimitive.
|
|
54
|
+
<AlertDialogPrimitive.Content
|
|
52
55
|
data-slot="alert-dialog-content"
|
|
53
|
-
data-size={size}
|
|
54
56
|
className={cn(
|
|
55
|
-
"
|
|
57
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-sm:max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 max-w-lg",
|
|
56
58
|
className
|
|
57
59
|
)}
|
|
58
60
|
{...props}
|
|
@@ -68,10 +70,7 @@ function AlertDialogHeader({
|
|
|
68
70
|
return (
|
|
69
71
|
<div
|
|
70
72
|
data-slot="alert-dialog-header"
|
|
71
|
-
className={cn(
|
|
72
|
-
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
|
|
73
|
-
className
|
|
74
|
-
)}
|
|
73
|
+
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
|
75
74
|
{...props}
|
|
76
75
|
/>
|
|
77
76
|
)
|
|
@@ -85,23 +84,7 @@ function AlertDialogFooter({
|
|
|
85
84
|
<div
|
|
86
85
|
data-slot="alert-dialog-footer"
|
|
87
86
|
className={cn(
|
|
88
|
-
"
|
|
89
|
-
className
|
|
90
|
-
)}
|
|
91
|
-
{...props}
|
|
92
|
-
/>
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function AlertDialogMedia({
|
|
97
|
-
className,
|
|
98
|
-
...props
|
|
99
|
-
}: React.ComponentProps<"div">) {
|
|
100
|
-
return (
|
|
101
|
-
<div
|
|
102
|
-
data-slot="alert-dialog-media"
|
|
103
|
-
className={cn(
|
|
104
|
-
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
|
|
87
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
105
88
|
className
|
|
106
89
|
)}
|
|
107
90
|
{...props}
|
|
@@ -116,10 +99,7 @@ function AlertDialogTitle({
|
|
|
116
99
|
return (
|
|
117
100
|
<AlertDialogPrimitive.Title
|
|
118
101
|
data-slot="alert-dialog-title"
|
|
119
|
-
className={cn(
|
|
120
|
-
"text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
|
|
121
|
-
className
|
|
122
|
-
)}
|
|
102
|
+
className={cn("text-lg font-semibold", className)}
|
|
123
103
|
{...props}
|
|
124
104
|
/>
|
|
125
105
|
)
|
|
@@ -132,10 +112,7 @@ function AlertDialogDescription({
|
|
|
132
112
|
return (
|
|
133
113
|
<AlertDialogPrimitive.Description
|
|
134
114
|
data-slot="alert-dialog-description"
|
|
135
|
-
className={cn(
|
|
136
|
-
"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
|
137
|
-
className
|
|
138
|
-
)}
|
|
115
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
139
116
|
{...props}
|
|
140
117
|
/>
|
|
141
118
|
)
|
|
@@ -144,11 +121,10 @@ function AlertDialogDescription({
|
|
|
144
121
|
function AlertDialogAction({
|
|
145
122
|
className,
|
|
146
123
|
...props
|
|
147
|
-
}: React.ComponentProps<typeof
|
|
124
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
|
|
148
125
|
return (
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
className={cn(className)}
|
|
126
|
+
<AlertDialogPrimitive.Action
|
|
127
|
+
className={cn(buttonVariants(), className)}
|
|
152
128
|
{...props}
|
|
153
129
|
/>
|
|
154
130
|
)
|
|
@@ -156,16 +132,11 @@ function AlertDialogAction({
|
|
|
156
132
|
|
|
157
133
|
function AlertDialogCancel({
|
|
158
134
|
className,
|
|
159
|
-
variant = "outline",
|
|
160
|
-
size = "default",
|
|
161
135
|
...props
|
|
162
|
-
}: AlertDialogPrimitive.
|
|
163
|
-
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
|
136
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
|
|
164
137
|
return (
|
|
165
|
-
<AlertDialogPrimitive.
|
|
166
|
-
|
|
167
|
-
className={cn(className)}
|
|
168
|
-
render={<Button variant={variant} size={size} />}
|
|
138
|
+
<AlertDialogPrimitive.Cancel
|
|
139
|
+
className={cn(buttonVariants({ variant: "outline" }), className)}
|
|
169
140
|
{...props}
|
|
170
141
|
/>
|
|
171
142
|
)
|
|
@@ -173,15 +144,14 @@ function AlertDialogCancel({
|
|
|
173
144
|
|
|
174
145
|
export {
|
|
175
146
|
AlertDialog,
|
|
176
|
-
|
|
177
|
-
|
|
147
|
+
AlertDialogPortal,
|
|
148
|
+
AlertDialogOverlay,
|
|
149
|
+
AlertDialogTrigger,
|
|
178
150
|
AlertDialogContent,
|
|
179
|
-
AlertDialogDescription,
|
|
180
|
-
AlertDialogFooter,
|
|
181
151
|
AlertDialogHeader,
|
|
182
|
-
|
|
183
|
-
AlertDialogOverlay,
|
|
184
|
-
AlertDialogPortal,
|
|
152
|
+
AlertDialogFooter,
|
|
185
153
|
AlertDialogTitle,
|
|
186
|
-
|
|
154
|
+
AlertDialogDescription,
|
|
155
|
+
AlertDialogAction,
|
|
156
|
+
AlertDialogCancel,
|
|
187
157
|
}
|
|
@@ -4,13 +4,18 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|
|
4
4
|
import { cn } from "@/lib/utils"
|
|
5
5
|
|
|
6
6
|
const alertVariants = cva(
|
|
7
|
-
"
|
|
7
|
+
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
|
8
8
|
{
|
|
9
9
|
variants: {
|
|
10
10
|
variant: {
|
|
11
11
|
default: "bg-card text-card-foreground",
|
|
12
12
|
destructive:
|
|
13
|
-
"bg-card text-
|
|
13
|
+
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
|
14
|
+
// 新增语义化 success/warning variant
|
|
15
|
+
success:
|
|
16
|
+
"bg-success text-success-foreground border-success/50 [&>svg]:text-success-foreground *:data-[slot=alert-description]:text-success-foreground/90",
|
|
17
|
+
warning:
|
|
18
|
+
"bg-warning text-warning-foreground border-warning/50 [&>svg]:text-warning-foreground *:data-[slot=alert-description]:text-warning-foreground/90",
|
|
14
19
|
},
|
|
15
20
|
},
|
|
16
21
|
defaultVariants: {
|
|
@@ -39,7 +44,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
39
44
|
<div
|
|
40
45
|
data-slot="alert-title"
|
|
41
46
|
className={cn(
|
|
42
|
-
"
|
|
47
|
+
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
|
43
48
|
className
|
|
44
49
|
)}
|
|
45
50
|
{...props}
|
|
@@ -55,7 +60,7 @@ function AlertDescription({
|
|
|
55
60
|
<div
|
|
56
61
|
data-slot="alert-description"
|
|
57
62
|
className={cn(
|
|
58
|
-
"text-
|
|
63
|
+
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
|
59
64
|
className
|
|
60
65
|
)}
|
|
61
66
|
{...props}
|
|
@@ -63,14 +68,4 @@ function AlertDescription({
|
|
|
63
68
|
)
|
|
64
69
|
}
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<div
|
|
69
|
-
data-slot="alert-action"
|
|
70
|
-
className={cn("absolute top-2 right-2", className)}
|
|
71
|
-
{...props}
|
|
72
|
-
/>
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export { Alert, AlertTitle, AlertDescription, AlertAction }
|
|
71
|
+
export { Alert, AlertTitle, AlertDescription }
|