feat(ui): 新增 TextButton / ToggleRow,Table 支持紧凑档
- TextButton:文字按钮(tone 主色/危险、可选图标、disabled), 用于表格行内操作,hover 只加下划线 - ToggleRow:左标签 + 右开关的开关行,支持 hint 与 switchLabel - Table:新增 size='md' | 'sm',sm 把行内边距收到 space-2 - 展示页:按钮补「文字按钮」、开关补「开关行」、表格补「紧凑档」; COMPONENTS.md 契约 3.46 / 3.47 与 README 清单同步
This commit is contained in:
@@ -95,6 +95,7 @@ stash-ui/
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `Button` | 操作按钮(圆角 / 胶囊,支持描边) | `variant` `size` `shape` `block` `loading` `icon` |
|
| `Button` | 操作按钮(圆角 / 胶囊,支持描边) | `variant` `size` `shape` `block` `loading` `icon` |
|
||||||
| `IconButton` | 圆形图标按钮(28/36/44) | `label`(必填) `icon` `variant` `size` `spinning` |
|
| `IconButton` | 圆形图标按钮(28/36/44) | `label`(必填) `icon` `variant` `size` `spinning` |
|
||||||
|
| `TextButton` | 文字按钮(行内操作,无外框) | `icon` `tone` `disabled` `title` `label` |
|
||||||
| `Card` | 白色容器(极浅描边) | `as` `padding` `hoverable` |
|
| `Card` | 白色容器(极浅描边) | `as` `padding` `hoverable` |
|
||||||
| `Cover` | 比例封面卡(2:3 / 4:3 / 16:9) | `ratio` `src` `caption` `lift` `onClick` |
|
| `Cover` | 比例封面卡(2:3 / 4:3 / 16:9) | `ratio` `src` `caption` `lift` `onClick` |
|
||||||
| `Image` | 带比例 / 圆角 / 失败降级的图片 | `ratio` `fit` `radius` `fallback` |
|
| `Image` | 带比例 / 圆角 / 失败降级的图片 | `ratio` `fit` `radius` `fallback` |
|
||||||
@@ -103,6 +104,7 @@ stash-ui/
|
|||||||
| `Skeleton` | 骨架屏占位 | `variant` `lines` `width` `height` |
|
| `Skeleton` | 骨架屏占位 | `variant` `lines` `width` `height` |
|
||||||
| `Spinner` | 加载指示 | `size` `label` |
|
| `Spinner` | 加载指示 | `size` `label` |
|
||||||
| `Switch` | 即时开关 | `checked` `onChange` `size` `label` |
|
| `Switch` | 即时开关 | `checked` `onChange` `size` `label` |
|
||||||
|
| `ToggleRow` | 开关行(左标签 + 右开关) | `label` `checked` `onChange` `hint` `switchLabel` |
|
||||||
| `Checkbox` | 复选框 | `checked` `onChange` `label` |
|
| `Checkbox` | 复选框 | `checked` `onChange` `label` |
|
||||||
| `RadioGroup` | 互斥单选组 | `options` `value` `onChange` `direction` |
|
| `RadioGroup` | 互斥单选组 | `options` `value` `onChange` `direction` |
|
||||||
| `Select` | 受控下拉选择 | `options` `value` `onChange` `placeholder` |
|
| `Select` | 受控下拉选择 | `options` `value` `onChange` `placeholder` |
|
||||||
@@ -110,7 +112,7 @@ stash-ui/
|
|||||||
| `Tabs` | 页内分区切换 | `items` `activeKey` `onChange` `variant` |
|
| `Tabs` | 页内分区切换 | `items` `activeKey` `onChange` `variant` |
|
||||||
| `Menu` | 侧栏两级菜单 | `items` `activeKey` `onSelect` |
|
| `Menu` | 侧栏两级菜单 | `items` `activeKey` `onSelect` |
|
||||||
| `Breadcrumb` | 路径导航 | `items` `separator` |
|
| `Breadcrumb` | 路径导航 | `items` `separator` |
|
||||||
| `Table` | 只读数据表格 | `columns` `rows` `rowKey` `emptyText` |
|
| `Table` | 只读数据表格 | `columns` `rows` `rowKey` `size` `emptyText` |
|
||||||
| `Tag` | 分类 / 标签 / 状态(浅底 / 实底) | `tone` `variant` `shape` `icon` `dot` |
|
| `Tag` | 分类 / 标签 / 状态(浅底 / 实底) | `tone` `variant` `shape` `icon` `dot` |
|
||||||
| `ItemBadge` | 状态角标:置顶 / 付费 / HOT | `tone` |
|
| `ItemBadge` | 状态角标:置顶 / 付费 / HOT | `tone` |
|
||||||
| `Badge` | 数字角标 | `count` `tone` `max` |
|
| `Badge` | 数字角标 | `count` `tone` `max` |
|
||||||
|
|||||||
+24
-3
@@ -19,10 +19,12 @@
|
|||||||
| `Typography` | L1 | ✅ | 标题 / 文字 / 段落(Title / Text / Paragraph) | `level` `tone` `size` `lines` |
|
| `Typography` | L1 | ✅ | 标题 / 文字 / 段落(Title / Text / Paragraph) | `level` `tone` `size` `lines` |
|
||||||
| `Button` | L2 | ✅ | 操作按钮(圆角 / 胶囊) | `variant` `size` `shape` `block` `loading` `icon` |
|
| `Button` | L2 | ✅ | 操作按钮(圆角 / 胶囊) | `variant` `size` `shape` `block` `loading` `icon` |
|
||||||
| `IconButton` | L2 | ✅ | 圆形图标按钮(28 / 36 / 44) | `label`(必填) `icon` `variant` `size` `spinning` |
|
| `IconButton` | L2 | ✅ | 圆形图标按钮(28 / 36 / 44) | `label`(必填) `icon` `variant` `size` `spinning` |
|
||||||
|
| `TextButton` | L2 | ✅ | 文字按钮(行内操作,无外框) | `icon` `tone` `disabled` `title` `label` |
|
||||||
| `Input` | L2 | ✅ | 组合式输入框 | `icon` `clearable` `onClear` `shortcut` `inputRef` |
|
| `Input` | L2 | ✅ | 组合式输入框 | `icon` `clearable` `onClear` `shortcut` `inputRef` |
|
||||||
| `Textarea` | L2 | ✅ | 多行文本(Markdown / 简介) | `rows` `resize` `invalid` |
|
| `Textarea` | L2 | ✅ | 多行文本(Markdown / 简介) | `rows` `resize` `invalid` |
|
||||||
| `PasswordInput` | L2 | ✅ | 密码输入(锁图标 + 显隐切换) | `icon` `size` `toggle` |
|
| `PasswordInput` | L2 | ✅ | 密码输入(锁图标 + 显隐切换) | `icon` `size` `toggle` |
|
||||||
| `Switch` | L2 | ✅ | 即时生效的开关 | `checked` `onChange` `size` `label` |
|
| `Switch` | L2 | ✅ | 即时生效的开关 | `checked` `onChange` `size` `label` |
|
||||||
|
| `ToggleRow` | L2 | ✅ | 开关行:左标签 + 右开关 | `label` `checked` `onChange` `hint` `switchLabel` |
|
||||||
| `Checkbox` | L2 | ✅ | 复选框(多选 / 确认) | `checked` `onChange` `label` |
|
| `Checkbox` | L2 | ✅ | 复选框(多选 / 确认) | `checked` `onChange` `label` |
|
||||||
| `RadioGroup` | L2 | ✅ | 互斥单选组 | `options` `value` `onChange` `direction` |
|
| `RadioGroup` | L2 | ✅ | 互斥单选组 | `options` `value` `onChange` `direction` |
|
||||||
| `Select` | L2 | ✅ | 受控下拉选择 | `options` `value` `onChange` `placeholder` |
|
| `Select` | L2 | ✅ | 受控下拉选择 | `options` `value` `onChange` `placeholder` |
|
||||||
@@ -42,7 +44,7 @@
|
|||||||
| `FormField` | L3 | ✅ | 表单字段排版(标签 + 控件 + 提示/错误) | `label` `hint` `error` `required` |
|
| `FormField` | L3 | ✅ | 表单字段排版(标签 + 控件 + 提示/错误) | `label` `hint` `error` `required` |
|
||||||
| `Grid` | L3 | ✅ | 卡片列表网格 | `columns` `tabletColumns` `mobileColumns` `gap` `as` |
|
| `Grid` | L3 | ✅ | 卡片列表网格 | `columns` `tabletColumns` `mobileColumns` `gap` `as` |
|
||||||
| `Space` | L3 | ✅ | 一维间距容器 | `direction` `gap` `wrap` `align` `justify` |
|
| `Space` | L3 | ✅ | 一维间距容器 | `direction` `gap` `wrap` `align` `justify` |
|
||||||
| `Table` | L3 | ✅ | 只读数据表格 | `columns` `rows` `rowKey` `emptyText` |
|
| `Table` | L3 | ✅ | 只读数据表格 | `columns` `rows` `rowKey` `size` `emptyText` |
|
||||||
| `Notice` | L4 | ✅ | 说明 / 错误提示条 | `tone` `title` `action` |
|
| `Notice` | L4 | ✅ | 说明 / 错误提示条 | `tone` `title` `action` |
|
||||||
| `Empty` | L4 | ✅ | 空态 | `text` `action` |
|
| `Empty` | L4 | ✅ | 空态 | `text` `action` |
|
||||||
| `Result` | L4 | ✅ | 整块结果反馈 | `status` `title` `description` `actions` |
|
| `Result` | L4 | ✅ | 整块结果反馈 | `status` `title` `description` `actions` |
|
||||||
@@ -448,8 +450,8 @@
|
|||||||
### 3.30 Table ✅
|
### 3.30 Table ✅
|
||||||
|
|
||||||
- **用途**:数据表格(只读)。
|
- **用途**:数据表格(只读)。
|
||||||
- **Props**:`columns: { key, title, width?, align? }[]`、`rows: Record<string, ReactNode>[]`、`rowKey`(默认 `id`)、`emptyText`。
|
- **Props**:`columns: { key, title, width?, align? }[]`、`rows: Record<string, ReactNode>[]`、`rowKey`(默认 `id`)、`size: 'md' | 'sm'`(行高密度,默认 `md`)、`emptyText`。
|
||||||
- **规则**:排序 / 筛选 / 分页由页面组合,组件不内置交互;空数据渲染 `emptyText`。
|
- **规则**:排序 / 筛选 / 分页由页面组合,组件不内置交互;空数据渲染 `emptyText`;单元格是任意 `ReactNode`,行内操作直接用 `TextButton` / `IconButton`。
|
||||||
- **展示页**:`#table`
|
- **展示页**:`#table`
|
||||||
|
|
||||||
### 3.31 Result ✅
|
### 3.31 Result ✅
|
||||||
@@ -666,6 +668,24 @@
|
|||||||
- **规则**:`onChange` 给字符串而不是事件——这是它和 `Input` 唯一的语义差别,别在组件里做过滤/请求。
|
- **规则**:`onChange` 给字符串而不是事件——这是它和 `Input` 唯一的语义差别,别在组件里做过滤/请求。
|
||||||
- **展示页**:`#site-chrome`
|
- **展示页**:`#site-chrome`
|
||||||
|
|
||||||
|
### 3.46 TextButton ✅
|
||||||
|
|
||||||
|
- **用途**:文字按钮——看起来像链接、行为像按钮,用于表格行内操作(编辑 / 删除 / 新建子项)。
|
||||||
|
- **不该用在哪**:需要底色、尺寸或独立成行的地方(用 `Button`);只有图标的地方(用 `IconButton`)。
|
||||||
|
- **解剖**:`<button type="button">` + 可选前置图标;正文 `font-size-sm`,`tone` 决定主色 / 危险色。
|
||||||
|
- **Props**:`children`、`icon`、`tone: 'primary' | 'danger'`(默认 `primary`)、`disabled`、`onClick`、`title`、`label`。
|
||||||
|
- **规则**:hover 只加下划线,不加底色;`disabled` 用 `--su-text-disabled`;`focus-visible` 保留焦点环。
|
||||||
|
- **展示页**:`#buttons`
|
||||||
|
|
||||||
|
### 3.47 ToggleRow ✅
|
||||||
|
|
||||||
|
- **用途**:设置面板里「左标签 + 右开关」的一行。
|
||||||
|
- **不该用在哪**:需要用户点「保存」才生效的场景(用 `Checkbox` + 按钮)。
|
||||||
|
- **解剖**:`div` 两端对齐,左侧 `Text`(可加一行 `hint`),右侧 `Switch`。
|
||||||
|
- **Props**:`label`、`checked`、`onChange`、`switchLabel`(默认取 label 文本)、`hint`、`disabled`。
|
||||||
|
- **规则**:开关的无障碍名称必须有值,label 不是字符串时请显式传 `switchLabel`。
|
||||||
|
- **展示页**:`#switch`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 组合模式(不进库,但必须照这个拼)
|
## 4. 组合模式(不进库,但必须照这个拼)
|
||||||
@@ -771,3 +791,4 @@
|
|||||||
| 2026-11-01 | 新增三个「站点外壳」组件,把应用里各自手写的一套收进库:`Brand`(logo / 首字标记 + 名称,`href` 可选)、`Ticker`(无限滚动信息条,按实测宽度位移,循环不跳帧)、`SearchField`(语义版搜索框,`variant` 切普通 / 悬浮条)。同时新增展示级令牌 `--su-font-size-2xl` / `--su-line-height-2xl`(32 / 40)。兼容性:纯新增,无破坏 |
|
| 2026-11-01 | 新增三个「站点外壳」组件,把应用里各自手写的一套收进库:`Brand`(logo / 首字标记 + 名称,`href` 可选)、`Ticker`(无限滚动信息条,按实测宽度位移,循环不跳帧)、`SearchField`(语义版搜索框,`variant` 切普通 / 悬浮条)。同时新增展示级令牌 `--su-font-size-2xl` / `--su-line-height-2xl`(32 / 40)。兼容性:纯新增,无破坏 |
|
||||||
| 2026-11-01 | 新增 `EntryRow` / `EntryTile`:把应用里两套手写内容卡(`.app-card` / `.ces-card`)收进库。两者共用同一套点击语义(无修饰键接管、按住修饰键保留默认)与悬停反馈;`EntryTile` 由 `Card padding="none" hoverable` + `Cover` 拼成。兼容性:纯新增 |
|
| 2026-11-01 | 新增 `EntryRow` / `EntryTile`:把应用里两套手写内容卡(`.app-card` / `.ces-card`)收进库。两者共用同一套点击语义(无修饰键接管、按住修饰键保留默认)与悬停反馈;`EntryTile` 由 `Card padding="none" hoverable` + `Cover` 拼成。兼容性:纯新增 |
|
||||||
| 2026-11-01 | `Select` 的下拉箭头从 `arrow_down_line` 换成和 `Menu` 一致的 CSS 折角(6px、收起朝下 / 展开朝上)。影响面:所有 `Select` 的外观,交互不变 |
|
| 2026-11-01 | `Select` 的下拉箭头从 `arrow_down_line` 换成和 `Menu` 一致的 CSS 折角(6px、收起朝下 / 展开朝上)。影响面:所有 `Select` 的外观,交互不变 |
|
||||||
|
| 2026-11-01 | 新增 `TextButton`(文字按钮:`tone` 主色 / 危险色、可选图标)与 `ToggleRow`(左标签 + 右开关的开关行,支持 `hint`);`Table` 新增 `size: 'md' \| 'sm'` 紧凑档。目的是把后台配置页里手写的 `.group-action` / `.sidebar-toggle-row` / `<table>` 收进库。兼容性:纯新增,`Table` 默认行为不变 |
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Button, Icon, IconButton, Space } from '../../src'
|
import { Button, Icon, IconButton, Space, TextButton } from '../../src'
|
||||||
import { Demo } from '../components/Demo'
|
import { Demo } from '../components/Demo'
|
||||||
import { PropsTable } from '../components/PropsTable'
|
import { PropsTable } from '../components/PropsTable'
|
||||||
import { Section } from '../components/Section'
|
import { Section } from '../components/Section'
|
||||||
@@ -126,6 +126,27 @@ export function Buttons() {
|
|||||||
</Space>
|
</Space>
|
||||||
</Demo>
|
</Demo>
|
||||||
|
|
||||||
|
<Demo
|
||||||
|
title="文字按钮"
|
||||||
|
desc="看起来像链接、行为像按钮,用在表格行内操作这类不需要外框的地方;需要底色或独立操作时仍用 Button。"
|
||||||
|
layout="column"
|
||||||
|
code={`<TextButton icon={<Icon name="add_line" />}>新建子分组</TextButton>
|
||||||
|
<TextButton icon={<Icon name="edit_4_line" />}>编辑</TextButton>
|
||||||
|
<TextButton tone="danger" icon={<Icon name="delete_2_line" />}>删除</TextButton>
|
||||||
|
<TextButton disabled>禁用</TextButton>`}
|
||||||
|
>
|
||||||
|
<Space gap="lg" wrap>
|
||||||
|
<TextButton icon={<Icon name="add_line" />}>新建子分组</TextButton>
|
||||||
|
<TextButton icon={<Icon name="edit_4_line" />}>编辑</TextButton>
|
||||||
|
<TextButton tone="danger" icon={<Icon name="delete_2_line" />}>
|
||||||
|
删除
|
||||||
|
</TextButton>
|
||||||
|
<TextButton disabled icon={<Icon name="edit_4_line" />}>
|
||||||
|
禁用
|
||||||
|
</TextButton>
|
||||||
|
</Space>
|
||||||
|
</Demo>
|
||||||
|
|
||||||
<PropsTable
|
<PropsTable
|
||||||
rows={[
|
rows={[
|
||||||
{ name: 'variant', type: "'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'", defaultValue: "'primary'", desc: '语义变体' },
|
{ name: 'variant', type: "'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'", defaultValue: "'primary'", desc: '语义变体' },
|
||||||
@@ -148,6 +169,16 @@ export function Buttons() {
|
|||||||
{ name: 'spinning', type: 'boolean', defaultValue: 'false', desc: '图标旋转' },
|
{ name: 'spinning', type: 'boolean', defaultValue: 'false', desc: '图标旋转' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PropsTable
|
||||||
|
rows={[
|
||||||
|
{ name: 'icon', type: 'ReactNode', desc: '前置图标' },
|
||||||
|
{ name: 'tone', type: "'primary' | 'danger'", defaultValue: "'primary'", desc: '颜色语义' },
|
||||||
|
{ name: 'disabled', type: 'boolean', defaultValue: 'false', desc: '禁用态' },
|
||||||
|
{ name: 'onClick', type: '() => void', desc: '点击回调' },
|
||||||
|
{ name: 'title / label', type: 'string', desc: '悬停提示 / 无障碍名称' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Space, Switch, Text } from '../../src'
|
import { Space, Switch, Text, ToggleRow } from '../../src'
|
||||||
import { Demo } from '../components/Demo'
|
import { Demo } from '../components/Demo'
|
||||||
import { PropsTable } from '../components/PropsTable'
|
import { PropsTable } from '../components/PropsTable'
|
||||||
import { Section } from '../components/Section'
|
import { Section } from '../components/Section'
|
||||||
@@ -58,6 +58,24 @@ export function Switches() {
|
|||||||
</Space>
|
</Space>
|
||||||
</Demo>
|
</Demo>
|
||||||
|
|
||||||
|
<Demo
|
||||||
|
title="开关行"
|
||||||
|
desc="设置面板里每行一个开关时用 ToggleRow:左边标签(可带一行说明),右边开关,两端对齐,不用自己拼 Space。"
|
||||||
|
layout="column"
|
||||||
|
code={`<ToggleRow label="明暗切换" checked={on} onChange={setOn} />
|
||||||
|
<ToggleRow label="显示封面图" hint="列表里用封面代替首字" checked={showCover} onChange={setShowCover} />`}
|
||||||
|
>
|
||||||
|
<div style={{ width: 360 }}>
|
||||||
|
<ToggleRow label="明暗切换" checked={autoSync} onChange={setAutoSync} />
|
||||||
|
<ToggleRow
|
||||||
|
label="显示封面图"
|
||||||
|
hint="列表里用封面代替首字"
|
||||||
|
checked={showCover}
|
||||||
|
onChange={setShowCover}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Demo>
|
||||||
|
|
||||||
<PropsTable
|
<PropsTable
|
||||||
rows={[
|
rows={[
|
||||||
{ name: 'checked', type: 'boolean', required: true, desc: '受控状态' },
|
{ name: 'checked', type: 'boolean', required: true, desc: '受控状态' },
|
||||||
@@ -67,6 +85,17 @@ export function Switches() {
|
|||||||
{ name: 'label', type: 'string', desc: '无障碍名称:旁边没有文字时必填' },
|
{ name: 'label', type: 'string', desc: '无障碍名称:旁边没有文字时必填' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PropsTable
|
||||||
|
rows={[
|
||||||
|
{ name: 'label', type: 'ReactNode', required: true, desc: '左侧标签' },
|
||||||
|
{ name: 'checked', type: 'boolean', required: true, desc: '受控状态' },
|
||||||
|
{ name: 'onChange', type: '(checked: boolean) => void', required: true, desc: '切换回调' },
|
||||||
|
{ name: 'switchLabel', type: 'string', desc: '读屏用的开关名称,默认取 label' },
|
||||||
|
{ name: 'hint', type: 'ReactNode', desc: '标签下方的一行说明' },
|
||||||
|
{ name: 'disabled', type: 'boolean', defaultValue: 'false', desc: '禁用' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,15 @@ export function Tables() {
|
|||||||
<Table columns={COLUMNS} rows={ROWS} rowKey="id" />
|
<Table columns={COLUMNS} rows={ROWS} rowKey="id" />
|
||||||
</Demo>
|
</Demo>
|
||||||
|
|
||||||
|
<Demo
|
||||||
|
title="紧凑档"
|
||||||
|
desc="size='sm' 把行高收一档,配置面板那种列多、面板窄的场景用它。"
|
||||||
|
layout="column"
|
||||||
|
code={`<Table columns={COLUMNS} rows={ROWS} size="sm" />`}
|
||||||
|
>
|
||||||
|
<Table columns={COLUMNS} rows={ROWS} size="sm" />
|
||||||
|
</Demo>
|
||||||
|
|
||||||
<Demo
|
<Demo
|
||||||
title="空态"
|
title="空态"
|
||||||
desc="空数据时渲染默认文案;也可以传 emptyText 换一句更具体的说明。"
|
desc="空数据时渲染默认文案;也可以传 emptyText 换一句更具体的说明。"
|
||||||
@@ -70,6 +79,7 @@ export function Tables() {
|
|||||||
{ name: 'columns', type: 'TableColumn[]', required: true, desc: '{ key, title, width?, align? }' },
|
{ name: 'columns', type: 'TableColumn[]', required: true, desc: '{ key, title, width?, align? }' },
|
||||||
{ name: 'rows', type: 'Record<string, ReactNode>[]', required: true, desc: '行数据,键对应列的 key' },
|
{ name: 'rows', type: 'Record<string, ReactNode>[]', required: true, desc: '行数据,键对应列的 key' },
|
||||||
{ name: 'rowKey', type: 'string', defaultValue: "'id'", desc: '行的唯一键字段' },
|
{ name: 'rowKey', type: 'string', defaultValue: "'id'", desc: '行的唯一键字段' },
|
||||||
|
{ name: 'size', type: "'md' | 'sm'", defaultValue: "'md'", desc: '行高密度' },
|
||||||
{ name: 'emptyText', type: 'ReactNode', defaultValue: "'暂无数据'", desc: '空态文案' },
|
{ name: 'emptyText', type: 'ReactNode', defaultValue: "'暂无数据'", desc: '空态文案' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -41,3 +41,9 @@
|
|||||||
color: var(--su-text-muted);
|
color: var(--su-text-muted);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 紧凑档:行高收一档,窄面板里五列也能放下 */
|
||||||
|
.sm th,
|
||||||
|
.sm .cell {
|
||||||
|
padding: var(--su-space-2) var(--su-space-4);
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export type TableProps = {
|
|||||||
rows: Record<string, ReactNode>[]
|
rows: Record<string, ReactNode>[]
|
||||||
/** 行的唯一键字段,默认 `id` */
|
/** 行的唯一键字段,默认 `id` */
|
||||||
rowKey?: string
|
rowKey?: string
|
||||||
|
/** 行高密度:md 常规、sm 紧凑(后台配置表用) */
|
||||||
|
size?: 'md' | 'sm'
|
||||||
emptyText?: ReactNode
|
emptyText?: ReactNode
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
@@ -28,11 +30,12 @@ export function Table({
|
|||||||
columns,
|
columns,
|
||||||
rows,
|
rows,
|
||||||
rowKey = 'id',
|
rowKey = 'id',
|
||||||
|
size = 'md',
|
||||||
emptyText = '暂无数据',
|
emptyText = '暂无数据',
|
||||||
className,
|
className,
|
||||||
}: TableProps) {
|
}: TableProps) {
|
||||||
return (
|
return (
|
||||||
<div className={[styles.wrap, className].filter(Boolean).join(' ')}>
|
<div className={[styles.wrap, styles[size], className].filter(Boolean).join(' ')}>
|
||||||
<table className={styles.table}>
|
<table className={styles.table}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
.root {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--su-space-1);
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
font-size: var(--su-font-size-sm);
|
||||||
|
line-height: var(--su-line-height-sm);
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity var(--su-duration-fast) var(--su-ease-out);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
color: var(--su-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
color: var(--su-color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.root:hover:not(:disabled) {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root:focus-visible {
|
||||||
|
border-radius: var(--su-radius-xs);
|
||||||
|
outline: var(--su-focus-ring);
|
||||||
|
outline-offset: var(--su-focus-ring-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.root:disabled {
|
||||||
|
color: var(--su-text-disabled);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: var(--su-font-size-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.root {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import styles from './TextButton.module.css'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文字按钮:看起来像链接,行为像按钮。
|
||||||
|
*
|
||||||
|
* 用于表格行内操作(编辑 / 删除 / 新建子项)这类「不需要按钮外框」的地方。
|
||||||
|
* 需要底色或图标的独立操作请用 `Button` / `IconButton`,不要拿它当主按钮。
|
||||||
|
*/
|
||||||
|
export type TextButtonTone = 'primary' | 'danger'
|
||||||
|
|
||||||
|
export type TextButtonProps = {
|
||||||
|
children: ReactNode
|
||||||
|
/** 前置图标,一般传 `<Icon name="edit_4_line" />` */
|
||||||
|
icon?: ReactNode
|
||||||
|
/** 主色(默认)/ 危险色 */
|
||||||
|
tone?: TextButtonTone
|
||||||
|
disabled?: boolean
|
||||||
|
onClick?: () => void
|
||||||
|
/** 悬停提示;行内只有图标时用它补充说明 */
|
||||||
|
title?: string
|
||||||
|
/** 无障碍名称,文字已经说清楚时可省略 */
|
||||||
|
label?: string
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TextButton({
|
||||||
|
children,
|
||||||
|
icon,
|
||||||
|
tone = 'primary',
|
||||||
|
disabled = false,
|
||||||
|
onClick,
|
||||||
|
title,
|
||||||
|
label,
|
||||||
|
className,
|
||||||
|
}: TextButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={[styles.root, styles[tone], className].filter(Boolean).join(' ')}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={onClick}
|
||||||
|
title={title}
|
||||||
|
aria-label={label}
|
||||||
|
>
|
||||||
|
{icon !== undefined && (
|
||||||
|
<span className={styles.icon} aria-hidden="true">
|
||||||
|
{icon}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--su-space-3);
|
||||||
|
min-height: var(--su-control-height-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--su-space-1);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { Switch } from './Switch'
|
||||||
|
import { Text } from './Typography'
|
||||||
|
import styles from './ToggleRow.module.css'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开关行:左边一行说明文字,右边一个开关,两端对齐。
|
||||||
|
*
|
||||||
|
* 设置面板里「每行一个开关」的场景用它,比自己拼 `Space justify="between"` 更整齐。
|
||||||
|
* 开关旁边没有文字标签时,`switchLabel` 必填(会进 aria-label)。
|
||||||
|
*/
|
||||||
|
export type ToggleRowProps = {
|
||||||
|
/** 显示的标签 */
|
||||||
|
label: ReactNode
|
||||||
|
checked: boolean
|
||||||
|
onChange: (checked: boolean) => void
|
||||||
|
/** 屏幕阅读器读到的开关名称,默认取 label 的文本 */
|
||||||
|
switchLabel?: string
|
||||||
|
disabled?: boolean
|
||||||
|
/** 标签下方的补充说明 */
|
||||||
|
hint?: ReactNode
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ToggleRow({
|
||||||
|
label,
|
||||||
|
checked,
|
||||||
|
onChange,
|
||||||
|
switchLabel,
|
||||||
|
disabled = false,
|
||||||
|
hint,
|
||||||
|
className,
|
||||||
|
}: ToggleRowProps) {
|
||||||
|
const accessibleLabel =
|
||||||
|
switchLabel ?? (typeof label === 'string' ? label : '开关')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={[styles.root, className].filter(Boolean).join(' ')}>
|
||||||
|
<span className={styles.text}>
|
||||||
|
<Text>{label}</Text>
|
||||||
|
{hint !== undefined && (
|
||||||
|
<Text size="xs" tone="muted">
|
||||||
|
{hint}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<Switch
|
||||||
|
checked={checked}
|
||||||
|
onChange={onChange}
|
||||||
|
disabled={disabled}
|
||||||
|
label={accessibleLabel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -43,6 +43,12 @@ export type { EntryRowProps } from './components/EntryRow'
|
|||||||
export { EntryTile } from './components/EntryTile'
|
export { EntryTile } from './components/EntryTile'
|
||||||
export type { EntryTileProps } from './components/EntryTile'
|
export type { EntryTileProps } from './components/EntryTile'
|
||||||
|
|
||||||
|
export { TextButton } from './components/TextButton'
|
||||||
|
export type { TextButtonProps, TextButtonTone } from './components/TextButton'
|
||||||
|
|
||||||
|
export { ToggleRow } from './components/ToggleRow'
|
||||||
|
export type { ToggleRowProps } from './components/ToggleRow'
|
||||||
|
|
||||||
export { Cover } from './components/Cover'
|
export { Cover } from './components/Cover'
|
||||||
export type { CoverProps, CoverRatio } from './components/Cover'
|
export type { CoverProps, CoverRatio } from './components/Cover'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user