@xyhp915/slack-base-ui
v0.1.4
Published
> 一个基于 [@base-ui/react](https://base-ui.com/) 的 Slack 风格 UI 组件库和演示项目
Readme
Slack Base UI
一个基于 @base-ui/react 的 Slack 风格 UI 组件库和演示项目
📖 项目简介
这是一个完整的 UI 组件库项目,使用 Base UI React 无样式组件库作为底层,实现了 Slack 的设计风格。项目包含:
- 可重用的组件库:Button、Avatar、Badge、Input、IconButton、Tooltip 等
- 完整的 Slack 克隆演示:展示如何使用这些组件构建真实应用
- 响应式设计:支持亮色/暗色主题切换
- 现代技术栈:React 19 + TypeScript + Vite + TailwindCSS
✨ 特性
- 🎨 Slack 设计风格:完全复刻 Slack 的视觉设计和交互体验
- 🧩 无样式组件基础:使用 Base UI 提供的可访问性和行为,完全自定义样式
- 🌓 主题支持:内置亮色/暗色主题,支持 CSS 变量动态切换
- ♿️ 无障碍访问:继承 Base UI 的完整 ARIA 支持
- 🚀 开箱即用:组件即装即用,支持按需引入
- 📦 TypeScript:完整的类型定义和代码提示
- 🎯 可扩展:易于定制和扩展的架构设计
🏗️ 项目结构
slack-base-ui/
├── src/
│ ├── components/ # 可重用的 UI 组件
│ │ ├── Button.tsx # 按钮组件
│ │ ├── Avatar.tsx # 头像组件
│ │ ├── Badge.tsx # 徽章组件
│ │ ├── Input.tsx # 输入框组件
│ │ ├── IconButton.tsx # 图标按钮组件
│ │ └── Tooltip.tsx # 工具提示组件
│ ├── pages/ # 页面
│ │ ├── Dashboard.tsx # 首页
│ │ └── ComponentShowcase.tsx # 组件展示页
│ ├── examples/ # 示例应用
│ │ └── slack-clone/ # Slack 克隆完整演示
│ │ ├── SlackApp.tsx
│ │ ├── components/ # 消息、编辑器等组件
│ │ └── layout/ # 侧边栏、布局组件
│ ├── context/ # React Context
│ │ └── ThemeContext.tsx # 主题切换上下文
│ ├── App.tsx # 根组件和路由
│ ├── main.tsx # 入口文件
│ └── index.css # 全局样式和 CSS 变量
├── public/ # 静态资源
├── index.html # HTML 模板
└── package.json # 项目配置🚀 快速开始
安装依赖
npm install启动开发服务器
npm run dev访问 http://localhost:5173 查看项目。
构建生产版本
npm run build预览生产构建
npm run preview📦 核心依赖
- @base-ui/react ^1.1.0 - 无样式组件库基础
- React ^19.2.0 - UI 框架
- react-router-dom ^7.13.0 - 路由管理
- TailwindCSS ^4.1.17 - CSS 工具类
- lucide-react ^0.563.0 - 图标库
- TypeScript ~5.9.3 - 类型系统
- Vite ^7.2.4 - 构建工具
🧩 组件列表
Button - 按钮
- 支持
primary、secondary、danger、ghost四种变体 - 三种尺寸:
sm、md、lg - 支持禁用状态和全宽模式
- 基于 Base UI 的
Button组件
Avatar - 头像
- 五种尺寸:
xs、sm、md、lg、xl - 状态指示器:
online、away、dnd、offline - 支持图片或文字回退
- 可选圆角或圆形
Badge - 徽章
- 数字徽章和圆点徽章
neutral和danger两种风格- 用于未读消息计数等场景
Input - 输入框
- 支持标签和错误提示
- 全宽和标准宽度模式
- 完整的表单控制支持
IconButton - 图标按钮
- 紧凑的图标操作按钮
- 支持激活状态
- 适用于工具栏
Dialog - 对话框
- 基于 Base UI 的
Dialog组件 - 四种尺寸:
sm、md、lg、xl - 支持标题、描述和自定义内容
- 带有遮罩层和优雅的进入/退出动画
- 支持子组件:
DialogBody、DialogFooter、DialogClose - 完整的键盘导航和焦点管理
- 命令式调用:
DialogProvider+useDialog()hook,支持show/confirm/alert三种模式
AlertDialog - 警告对话框
- 基于 Base UI 的
Dialog组件 - 四种语义化变体:
info、success、warning、danger - 每种变体配有对应的图标和配色
- 支持异步确认操作(带加载状态)
- 可自定义确认/取消按钮文本
- 专注于重要的确认和警告场景
Form - 表单系统
- 完整的表单状态管理(values、errors、touched)
- 内置验证系统
- 支持多种表单控件:
FormInput- 文本输入框FormTextarea- 多行文本框FormSelect- 下拉选择框FormCheckbox- 复选框FormField- 自定义表单字段(render props)FormActions- 表单操作按钮容器
- 自动错误提示和验证反馈
- 支持必填字段标记
Tooltip - 工具提示
- 基于 Base UI 的
Tooltip组件 - 支持四个方向:
top、bottom、left、right - 悬停显示上下文信息
Popover - 浮层
- 基于 Base UI 的
Popover组件 - 用于显示临时内容和交互
- 支持四个方向和对齐方式配置
- 提供便捷组件:
PopoverHeader、PopoverBody、PopoverFooter - 支持受控和非受控模式
- 命令式调用:
ImperativePopoverProvider+useImperativePopover()hook,可将 Popover 锚定到任意元素 - 详细文档
Menu - 下拉菜单
- 基于 Base UI 的
Menu组件 - 支持多种菜单项类型:普通项、复选框、单选组
- 支持子菜单和菜单分组
- 带图标和快捷键显示的
MenuItemWithIcon组件 - 支持危险操作标记(红色高亮)
- 完整的键盘导航支持
- 详细文档
ContextMenu - 右键菜单
- 基于 Base UI 的
Menu组件(右键触发) - API 与 Menu 一致,支持所有菜单项类型
- 自动右键触发,无需手动处理事件
- 适用于上下文操作场景
- 详细文档
Toast - 通知条
- 基于 Base UI 的
Toast组件 - 五种语义类型:
default、info、success、warning、error - 六个显示位置:
top-left、top-center、top-right、bottom-left、bottom-center、bottom-right - 支持可选操作按钮(action)和自定义超时时长
- 通过
<ToastProvider position="…">设置默认位置 - 运行时通过
useToast()返回的setPosition()动态切换位置 - 使用
useToast()命令式调用,无需管理状态
🎨 主题系统
项目使用 CSS 变量实现主题切换,所有颜色都定义在 src/index.css 中:
:root {
--slack-aubergine: #3F0E40;
--slack-blue: #1164A3;
--slack-green: #007a5a;
--bg-primary: #ffffff;
--text-primary: #1d1c1d;
/* ... 更多变量 */
}
:root.dark {
--bg-primary: #1A1D21;
--text-primary: #D1D2D3;
/* ... 暗色主题变量 */
}主题切换通过 ThemeContext 管理,在根元素添加/移除 .dark 类实现。
🎯 使用示例
基础按钮
import { Button } from './components/Button';
function MyComponent() {
return (
<div>
<Button variant="primary">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
<Button variant="danger" size="lg">Delete</Button>
</div>
);
}带状态的头像
import { Avatar } from './components/Avatar';
function UserList() {
return (
<div className="flex gap-2">
<Avatar
src="https://i.pravatar.cc/150?u=1"
status="online"
size="md"
/>
<Avatar fallback="JD" status="away" />
</div>
);
}工具提示
import { Tooltip } from './components/Tooltip';
import { Button } from './components/Button';
function Toolbar() {
return (
<Tooltip content="Click to send message">
<Button variant="primary">Send</Button>
</Tooltip>
);
}对话框
import { useState } from 'react';
import { Dialog, DialogBody, DialogFooter } from './components/Dialog';
import { Button } from './components/Button';
function ConfirmDialog() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>
Delete Channel
</Button>
<Dialog
open={open}
onOpenChange={setOpen}
title="Delete channel?"
description="This action cannot be undone."
size="sm"
>
<DialogBody>
<p>Are you sure you want to delete this channel?</p>
</DialogBody>
<DialogFooter>
<Button variant="secondary" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button variant="danger" onClick={() => setOpen(false)}>
Delete
</Button>
</DialogFooter>
</Dialog>
</>
);
}Toast 通知
import { useToast } from './components/Toast';
function NotifyButton() {
const { toast } = useToast();
return (
<Button onClick={() =>
toast({
type: 'success',
title: 'Message sent!',
description: 'Your message was delivered to #general.',
})
}>
Send Message
</Button>
);
}Toast 自定义位置
// 1. 在 Provider 上设置默认位置(main.tsx)
<ToastProvider position="top-right">
<App />
</ToastProvider>
// 2. 在任意组件中运行时切换位置
function ToastPositionDemo() {
const { toast, setPosition } = useToast();
return (
<button onClick={() => {
setPosition('top-center');
toast({ type: 'info', title: '已切换到顶部居中' });
}}>
切换到 top-center
</button>
);
}支持的 6 个位置:
top-left·top-center·top-rightbottom-left·bottom-center·bottom-right(默认)
🚀 命令式 API
Dialog 和 Popover 均支持不依赖声明式 JSX、直接用代码调用的命令式 API,使用方式与 useToast() 完全一致。
1. 注册 Provider
在应用根节点(main.tsx)包裹对应的 Provider:
// main.tsx
import { DialogProvider } from './components/Dialog'
import { ImperativePopoverProvider } from './components/Popover'
createRoot(document.getElementById('root')!).render(
<ThemeProvider>
<ToastProvider>
<DialogProvider>
<ImperativePopoverProvider>
<App />
</ImperativePopoverProvider>
</DialogProvider>
</ToastProvider>
</ThemeProvider>
)2. useDialog — 命令式对话框
在任意子组件中调用 useDialog(),获得三个返回 Promise 的方法:
| 方法 | 返回值 | 说明 |
|------|--------|------|
| show(options) | Promise<void> | 通用弹窗,关闭后 resolve |
| confirm(options) | Promise<boolean> | 确认弹窗,确认 → true,取消/关闭 → false |
| alert(options) | Promise<void> | 仅有 OK 按钮的提示弹窗,点击后 resolve |
通用弹窗 show
import { useDialog } from './components/Dialog'
function MyComponent() {
const { show } = useDialog()
const handleOpen = async () => {
await show({
title: '快捷设置',
content: <SettingsForm />,
size: 'md',
})
console.log('弹窗已关闭')
}
return <Button onClick={handleOpen}>打开设置</Button>
}确认弹窗 confirm
import { useDialog } from './components/Dialog'
function DeleteButton({ onDelete }: { onDelete: () => void }) {
const { confirm } = useDialog()
const handleClick = async () => {
const ok = await confirm({
title: '确认删除频道?',
description: '此操作不可撤销,频道内所有消息将永久删除。',
confirmLabel: '删除',
cancelLabel: '取消',
confirmVariant: 'danger',
})
if (ok) onDelete()
}
return <Button variant="danger" onClick={handleClick}>删除频道</Button>
}提示弹窗 alert
import { useDialog } from './components/Dialog'
function SubmitForm() {
const { alert } = useDialog()
const handleSubmit = async () => {
try {
await submitData()
} catch {
await alert({
title: '提交失败',
description: '服务器出现错误,请稍后再试。',
})
}
}
return <Button onClick={handleSubmit}>提交</Button>
}show 完整选项
interface ShowDialogOptions {
title?: string // 对话框标题
description?: string // 标题下方的副文本
content?: React.ReactNode // 自定义正文内容
size?: 'sm' | 'md' | 'lg' | 'xl'
showCloseButton?: boolean // 是否显示右上角关闭按钮,默认 true
className?: string
}confirm 完整选项
interface ConfirmDialogOptions {
title?: string
description?: string
content?: React.ReactNode
size?: 'sm' | 'md' | 'lg' | 'xl'
confirmLabel?: string // 默认 'Confirm'
cancelLabel?: string // 默认 'Cancel'
confirmVariant?: 'primary' | 'danger' | 'secondary' // 默认 'primary'
}alert 完整选项
interface AlertDialogOptions {
title?: string
description?: string
content?: React.ReactNode
size?: 'sm' | 'md' | 'lg' | 'xl'
confirmLabel?: string // 默认 'OK'
}3. useImperativePopover — 命令式浮层
在任意子组件中调用 useImperativePopover(),可以将 Popover 锚定到任意 DOM 元素上弹出,无需在 JSX 中手动组合 <Popover> + <PopoverTrigger>。
全局单例模式:同一时刻只显示一个命令式 Popover。
| 方法 / 属性 | 说明 |
|-------------|------|
| show(anchor, options) | 在指定元素旁弹出 Popover |
| hide() | 关闭当前 Popover |
| toggle(anchor, options) | 同一锚点再次调用时关闭,否则打开 |
| isOpen | 当前是否处于打开状态 |
基础用法
import { useImperativePopover } from './components/Popover'
function UserCard({ user }: { user: User }) {
const popover = useImperativePopover()
return (
<button
onClick={e =>
popover.show(e.currentTarget, {
content: <ProfilePanel user={user} />,
side: 'bottom',
align: 'start',
})
}
>
{user.name}
</button>
)
}Toggle 用法(点击再次关闭)
function SettingsButton() {
const popover = useImperativePopover()
return (
<IconButton
onClick={e =>
popover.toggle(e.currentTarget, {
content: <SettingsPanel onClose={popover.hide} />,
side: 'bottom',
align: 'end',
sideOffset: 4,
})
}
active={popover.isOpen}
>
<Settings size={16} />
</IconButton>
)
}show / toggle 完整选项
interface ImperativePopoverOptions {
content: React.ReactNode // 弹出内容(必填)
side?: 'top' | 'right' | 'bottom' | 'left' // 默认 'bottom'
align?: 'start' | 'center' | 'end' // 默认 'center'
sideOffset?: number // 与锚点的间距,默认 8
alignOffset?: number // 对齐方向偏移,默认 0
className?: string // 自定义弹出层样式
}🎬 演示页面
项目包含三个主要页面:
- Dashboard (
/) - 项目首页,展示项目介绍和快速导航 - Component Showcase (
/components) - 组件展示页,演示所有组件的用法 - Slack Clone (
/examples/slack) - 完整的 Slack 应用克隆,包括:- 侧边栏导航
- 频道列表
- 消息列表
- 富文本编辑器
- 用户状态
🛠️ 开发建议
添加新组件
- 在
src/components/创建新组件文件 - 使用 Base UI 的对应组件作为基础(如果有)
- 使用 TailwindCSS 和 CSS 变量实现 Slack 风格
- 在
ComponentShowcase.tsx中添加展示示例 - 确保组件支持暗色主题
自定义主题
修改 src/index.css 中的 CSS 变量即可:
:root {
--slack-blue: #1164A3; /* 修改为你的品牌色 */
--slack-green: #007a5a;
}扩展组件
所有组件都使用 React.forwardRef,支持传递 ref 和额外的 props:
<Button
ref={buttonRef}
onClick={handleClick}
className="custom-class"
data-testid="my-button"
>
Click me
</Button>📚 学习资源
🤝 贡献
欢迎提交 Issue 和 Pull Request!
📄 许可证
MIT
注意:这是一个教育和演示项目,不隶属于 Slack Technologies, LLC。Slack 是 Slack Technologies, LLC 的注册商标。
