@linhey/rhine-design 0.8.0 → 0.9.1
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/CHANGELOG.md +15 -0
- package/README.md +3 -1
- package/components/AdvancedForms.tsx +40 -10
- package/components/ListFilterBar.tsx +104 -0
- package/components/OrbitBackdrop.tsx +9 -3
- package/components/README.md +3 -2
- package/components/SPACING.md +6 -0
- package/components/components.tsx +5 -2
- package/components/extensions.css +10 -0
- package/components/index.ts +2 -1
- package/components/list-filter-bar.css +33 -0
- package/components/orbit.css +21 -0
- package/components/styles.css +1 -0
- package/components/timeline.css +2 -1
- package/docs/APPLICATION_COMPONENTS.md +4 -0
- package/docs/BACKGROUND_FILTER_VERIFICATION.md +12 -0
- package/docs/COMPACT_ACCOUNT_PATTERNS.md +3 -1
- package/docs/COMPACT_LAYOUT_VERIFICATION.md +53 -0
- package/docs/DESIGN_SYSTEM.md +6 -0
- package/docs/INTERACTION_PATTERNS.md +11 -1
- package/docs/LIST_FILTER_BAR.md +76 -0
- package/docs/ORBIT_BACKDROP.md +25 -0
- package/docs/assets/compact-layouts/account.png +0 -0
- package/docs/assets/compact-layouts/date-arknights.png +0 -0
- package/docs/assets/compact-layouts/date-neo-brutalism.png +0 -0
- package/docs/assets/compact-layouts/date-rhine.png +0 -0
- package/docs/assets/compact-layouts/table.png +0 -0
- package/package.json +1 -1
- package/preview/BackgroundFiltersFixture.tsx +28 -0
- package/preview/CompactAccountGallery.tsx +18 -18
- package/preview/DateRangeFilterGallery.tsx +18 -1
- package/preview/GalleryShell.tsx +1 -1
- package/preview/ListFilterBarGallery.tsx +170 -0
- package/preview/OrbitGallery.tsx +14 -0
- package/preview/SpacingGallery.tsx +15 -1
- package/preview/main.tsx +4 -1
- package/preview/preview.css +24 -2
- package/scripts/check-background-filters.js +58 -0
- package/scripts/check-background-motion.js +169 -0
- package/scripts/check-compact-layouts.js +136 -0
- package/scripts/check-filter-native-date.js +47 -0
- package/skills/rhine-design/SKILL.md +3 -3
- package/skills/rhine-design/references/API_MAP.md +1 -1
- package/skills/rhine-design/references/integration.md +1 -1
- package/tests/CompactAccountGallery.test.tsx +2 -1
- package/tests/DateNativeInput.test.tsx +147 -0
- package/tests/DesignSystem.test.tsx +12 -1
- package/tests/ListFilterBar.test.tsx +198 -0
- package/tests/OrbitBackdrop.test.tsx +28 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# 列表筛选栏
|
|
2
|
+
|
|
3
|
+
`ListFilterBar` 把列表顶部限制为搜索、最多两个常显条件、更多筛选、结果反馈、刷新与一个业务操作。它统一信息层级与布局,不承接查询、权限、分页或业务筛选状态。需求来源为 [#19](https://github.com/linhay/rhine-design/issues/19),真实展台为 `/#list-filter-bar`。
|
|
4
|
+
|
|
5
|
+
**版本边界:本组件在 0.9.0 新增。** 0.8.0 及更早版本不提供该组件;消费时必须核对实际导出及固定源码/安装快照。图库跟随源码,不能代替 npm 发布证据。
|
|
6
|
+
|
|
7
|
+
## 先决定常显内容
|
|
8
|
+
|
|
9
|
+
- 搜索在用户确实需要查找记录时提供;统计范围等场景无需填充一个无用搜索框。
|
|
10
|
+
- 搜索 label 持续可见,不以 placeholder 代替。#22 的 `f0f8495` 已移除早期候选隐藏搜索 label 的 CSS;消费旧安装副本时须核对,不能以源码文档描述代替实际安装行为。
|
|
11
|
+
- `primary` 只常显前两项,超出的项目自动进入“更多筛选”。选择高频且互相独立的条件,例如状态和排序;不要为了展开所有控件增加第二个筛选栏。
|
|
12
|
+
- 其他条件进入同一个 Drawer。字段自己的 label 持续可见,组件不重复加可见标题,也不提供任意 `children` 让页面插回一排控件。
|
|
13
|
+
- 非默认条件用短摘要说明当前限制;单项清除与整体重置都由宿主定义。默认排序、默认统计日期等是否需要摘要由宿主的 `activeLabel` 决定,不把所有非空值都视为筛选。
|
|
14
|
+
- 结果数量、更新中与错误反馈在栏内呈现;Drawer 打开时同样显示反馈。失败不能藏在遮罩后的页面上,也不能把旧数量当作新条件已经成功。
|
|
15
|
+
- 刷新之外最多一个业务操作。多记录的选择、批量操作属于结果区,录制启停等对象设置也不塞进筛选栏。
|
|
16
|
+
- 平级工作区已有侧栏身份时不重复大标题。组件的 `label` 提供可访问分组名称,宿主仍负责页面标题、主内容名称及路由焦点。
|
|
17
|
+
|
|
18
|
+
布局按组件实际容器宽度重排,窄嵌入区域和 200% 内容缩放保持完整文字、可达操作和 44px 目标;不以省略日期、缩小文字或横向撑破页面换取一行。三套主题使用同一组件结构与 tokens。
|
|
19
|
+
|
|
20
|
+
## 公共契约
|
|
21
|
+
|
|
22
|
+
| 属性 | 输入与责任 |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `label` | 必填分组名称,也是更多筛选 Drawer 的标题 |
|
|
25
|
+
| `search` | 可选原生 input 属性加必填 `label`;支持受控 value、change、composition、disabled;内部为 TextField |
|
|
26
|
+
| `primary` | `ListFilterField[]`;最多前两项常显,其他顺序移入 Drawer |
|
|
27
|
+
| `filters` | `ListFilterField[]`;按顺序追加到 Drawer |
|
|
28
|
+
| `onReset` | 宿主清除搜索与恢复各条件默认值;有搜索或 activeLabel 才显示重置入口 |
|
|
29
|
+
| `refresh` | `{onClick,busy?,disabled?}`;原生共享 Button,不自动执行请求 |
|
|
30
|
+
| `action` | 可选单个 ReactElement,代表一个业务操作;消费方不能用 Fragment 或自建按钮组绕过数量限制 |
|
|
31
|
+
| `result` | 宿主提供的结果摘要;数量未知时明确显示未知,不用已加载条数冒充总数 |
|
|
32
|
+
| `pending` / `error` | 正在更新 / 失败说明;在栏与已展开 Drawer 中保持一致 |
|
|
33
|
+
| `className` | 只用于外层组合布局,不覆盖组件内部视觉或状态 |
|
|
34
|
+
|
|
35
|
+
字段包含 `{id,label,control,activeLabel?,onClear?}`。`id` 在当前栏内唯一,用于稳定标识;它不是输入框 DOM id。`control` 提供真实受控组件及自己的可见/可访问标签;字段包装只添加分组语义。`activeLabel` 是宿主判断后的非默认条件摘要;`onClear` 缺省时摘要只读。不同实例的输入 DOM id 仍须唯一,优先使用组件自动生成的 id。
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<ListFilterBar
|
|
39
|
+
label="会话筛选"
|
|
40
|
+
search={{ label: '搜索会话', value: query, onChange: event => setQuery(event.target.value) }}
|
|
41
|
+
primary={[
|
|
42
|
+
{
|
|
43
|
+
id: 'state', label: '会话状态',
|
|
44
|
+
control: <Select label="会话状态" value={state} onChange={event => setState(event.target.value)}>
|
|
45
|
+
<option value="all">全部状态</option><option value="done">已完成</option>
|
|
46
|
+
</Select>,
|
|
47
|
+
activeLabel: state === 'all' ? undefined : '已完成',
|
|
48
|
+
onClear: () => setState('all'),
|
|
49
|
+
},
|
|
50
|
+
]}
|
|
51
|
+
filters={additionalFields}
|
|
52
|
+
onReset={resetFilters}
|
|
53
|
+
refresh={{ onClick: reload, busy: loading }}
|
|
54
|
+
result={total === null ? '总数待确认' : `${total} 条结果`}
|
|
55
|
+
pending={loading}
|
|
56
|
+
error={error}
|
|
57
|
+
/>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
示例中的状态、函数与额外字段由宿主提供。使用受控 `search.value` 才能准确决定搜索是否需要重置;组件不解析其他控件的值。
|
|
61
|
+
|
|
62
|
+
## 状态与查询边界
|
|
63
|
+
|
|
64
|
+
更多筛选是同一组状态的展开视图,不建立另一套草稿/应用流程。普通选择即时回传宿主;关闭、点击外部与 Escape 只收起抽屉,不撤销已经接受的条件。业务本来要求完整范围显式应用的日期控件可以保留其局部草稿,但应在 Drawer 内内联展示,不再嵌套日期 Drawer。
|
|
65
|
+
|
|
66
|
+
服务端搜索、权限裁剪、默认值、URL 与偏好、请求防抖、IME、取消和过期响应保护都留在宿主。输入中的值不等于最后接受的结果;失败保留还是回滚条件按接口契约处理,并把结果/错误传入组件。不要为了接入组件改成拉取全量数据在浏览器过滤。
|
|
67
|
+
|
|
68
|
+
Drawer 复用已有模态、Escape 与焦点归还;移除当前聚焦摘要时焦点回到有效控件。忙碌与禁用不同:busy 保留焦点,禁用字段由宿主明确传入;不因一次后台刷新禁用所有条件。只读身份应从传入字段中移除其无权使用的条件,不能只改变颜色或 CSS 隐藏。
|
|
69
|
+
|
|
70
|
+
## 真实展台与验收
|
|
71
|
+
|
|
72
|
+
`preview/ListFilterBarGallery.tsx` 提供独立会话列表、420px 嵌入列表、无搜索统计范围三种组合。独立例子刻意传入第三项 primary,以验证运行时移入 Drawer;全局状态选择使用真实控件演示正常、加载、无结果、错误与禁用。记录与操作均为合成示例,无业务请求或文件下载。
|
|
73
|
+
|
|
74
|
+
验收至少覆盖:三主题与真实窄容器;400/520/880px、200% CSS zoom;主条件上限;输入法、键盘和焦点;多个活跃条件的完整摘要、单项/全量重置;Drawer 内加载与失败;筛选后总数、分页与迟到响应。图库证明共享组件组合,产品的服务端权限、导出和查询契约仍分别验收。
|
|
75
|
+
|
|
76
|
+
Drawer 中的原生日期还须检查实际文本编辑区:完整值、最后日期片段与日历图标均可见并可操作;input/document 的 scrollWidth 不溢出不代表原生编辑区没有裁字。重复分组内距的共享修复由 [#25](https://github.com/linhay/rhine-design/issues/25) 跟踪;不在业务页复制日期内部 CSS。
|
package/docs/ORBIT_BACKDROP.md
CHANGED
|
@@ -71,3 +71,28 @@ CSS zoom 不能替代浏览器原生缩放;`document.hidden` 事件模拟不
|
|
|
71
71
|

|
|
72
72
|
|
|
73
73
|

|
|
74
|
+
|
|
75
|
+
## 入口文字与全页背景(0.9.0 新增)
|
|
76
|
+
|
|
77
|
+
| 输入 | 契约 |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `controls?: boolean`(0.9.0) | 默认 `true`;`false` 不渲染局部暂停按钮,并移除为控件预留的额外底部内边距。它不解除已经暂停的状态,也不覆盖系统、prop 或祖先减少动态 |
|
|
80
|
+
| `composition?: 'full' \| 'corner'`(0.9.0) | 默认 `full` 保留现有构图;`corner` 将实际绘制区域和静态降级艺术一起放至右下角,宽度最多 480px、高度为容器的 44% 且最多 360px |
|
|
81
|
+
| `framed?: boolean`(0.9.0) | 默认 `true` 保留主题面板边框、圆角与阴影;`false` 移除这三项装饰,用于宿主定位的全页背景,背景艺术与内容内边距仍保留 |
|
|
82
|
+
|
|
83
|
+
[Issue #17](https://github.com/linhay/rhine-design/issues/17) 补充入口页面的公共构图能力。需要保留上方文字阅读空间时使用 `composition="corner"`,让完整艺术在右下方较小的绘制区域内重绘;Canvas2D、可选 WebGL 和 Canvas 不可用时的静态 SVG 使用相同几何,不裁去艺术的一部分来伪造位置变化。宿主将标题和正文组合在上方;`corner` 不替宿主判断任意内容高度,长文案仍需真实布局验证。
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<OrbitBackdrop composition="corner" controls={false} style={{ minHeight: 640 }}>
|
|
87
|
+
<Stack gap={4}>
|
|
88
|
+
<Text as="h1" variant="title">继续创造</Text>
|
|
89
|
+
<Text>从这里进入工作空间。</Text>
|
|
90
|
+
</Stack>
|
|
91
|
+
</OrbitBackdrop>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
宽度不足以保留独立艺术栏时,宿主可将同一组件定位为整个页面的背景,使用 `framed={false}` 去掉面板装饰;前景表单使用 `Surface` 保障可读性。响应式断点、业务内容和页面高度归宿主布局负责,禁止通过业务选择器覆盖内部 Canvas 或控件。
|
|
95
|
+
|
|
96
|
+
`controls={false}` 只移除局部控制界面,不关闭动画、不禁用系统或手动减少动态,也不创建额外帧循环。默认用法继续提供暂停入口;用户明确采用无局部按钮的装饰背景时,可由宿主统一动效偏好控制。重新显示控件保留其原暂停状态;改变构图和面板边框不重挂载画布或重置子元素状态。该组合在 0.9.0 新增,旧版本不提供这些选项。
|
|
97
|
+
|
|
98
|
+
展台中的 `OrbitEntryExample` 按实际容器 760px 重排:宽容器独立艺术栏,窄容器扩展为整页背景,表单保留实色 Surface。布局变更不重挂载画布或输入;`?fixture=orbit-entry` 提供独立验收入口。
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Button, Checkbox, OrbitBackdrop, Select, Stack, Surface, Text, TextField } from '../components';
|
|
3
|
+
import { ThemeProvider, themes, type ThemeName } from '../components/theme';
|
|
4
|
+
import { ListFilterBarGallery } from './ListFilterBarGallery';
|
|
5
|
+
|
|
6
|
+
export function OrbitEntryExample({ reduced = false, appearance = 'theme' }: { reduced?: boolean; appearance?: 'theme' | 'original' }) {
|
|
7
|
+
return <div className="ds-orbit-entry-container"><div className="ds-orbit-entry">
|
|
8
|
+
<OrbitBackdrop className="ds-orbit-entry-background" appearance={appearance} composition="corner" controls={false} framed={false} reducedMotion={reduced} aria-hidden="true" />
|
|
9
|
+
<Stack gap={4} className="ds-orbit-entry-intro"><Text variant="overline">RHINE STUDIO</Text><Text as="h3" variant="title">从这里,继续创造。</Text><Text>让内容拥有清晰的阅读空间。</Text></Stack>
|
|
10
|
+
<Surface className="ds-orbit-entry-form"><Stack gap={4}><Text as="h3" variant="heading">欢迎回来</Text><TextField label="示例昵称" placeholder="输入你的昵称" /><Button>进入工作空间</Button></Stack></Surface>
|
|
11
|
+
</div></div>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function BackgroundFiltersFixture() {
|
|
15
|
+
const params = new URLSearchParams(location.search);
|
|
16
|
+
const zoom = Number(params.get('zoom')) || 1;
|
|
17
|
+
const [theme, setTheme] = useState<ThemeName>((params.get('theme') || 'rhine') as ThemeName);
|
|
18
|
+
const [reduced, setReduced] = useState(false), [mounted, setMounted] = useState(true);
|
|
19
|
+
useEffect(() => { document.documentElement.style.zoom = String(zoom); return () => { document.documentElement.style.zoom = ''; }; }, [zoom]);
|
|
20
|
+
return <ThemeProvider theme={theme} onThemeChange={setTheme}><div style={{ padding: 12 }}>
|
|
21
|
+
{params.get('fixture') === 'list-filters' ? <ListFilterBarGallery /> : <Stack gap={4}>
|
|
22
|
+
<Select label="验收主题" value={theme} onChange={event => setTheme(event.target.value as ThemeName)}>{themes.map(item => <option key={item.name} value={item.name}>{item.label}</option>)}</Select>
|
|
23
|
+
<Checkbox label="减少入口动态" checked={reduced} onChange={event => setReduced(event.target.checked)} />
|
|
24
|
+
<Checkbox label="挂载入口" checked={mounted} onChange={event => setMounted(event.target.checked)} />
|
|
25
|
+
{mounted && <OrbitEntryExample reduced={reduced} />}
|
|
26
|
+
</Stack>}
|
|
27
|
+
</div></ThemeProvider>;
|
|
28
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import {
|
|
3
|
-
Badge, Button, ButtonLink, Checkbox, Disclosure, Divider,
|
|
3
|
+
Badge, Button, ButtonLink, Checkbox, Disclosure, Divider, Grid, Inline,
|
|
4
4
|
Notice, Progress, Select, Skeleton, Stack, StatusTimelineList, Surface, Text,
|
|
5
5
|
TextField, TimelineLegend, Tooltip, type TimelineSegment, type TimelineStatus,
|
|
6
6
|
} from '../components';
|
|
@@ -45,29 +45,29 @@ export function CompactAccountExample({ state = 'normal' }: { state?: AccountSta
|
|
|
45
45
|
</Stack>
|
|
46
46
|
<Stack gap={1}>
|
|
47
47
|
<StatusTimelineList label="资源用量与异常记录" items={[{
|
|
48
|
-
id: 'resource-a', title: '用量与异常记录',
|
|
49
|
-
note: unknown ? '历史记录不完整,仅展示已确认的时段。' :
|
|
48
|
+
id: 'resource-a', title: '用量与异常记录', summary: '24小时窗口 · 15分钟',
|
|
49
|
+
note: `10/23 00:00 – 10/24 00:00 · 上海${unknown ? ' · 历史记录不完整,仅展示已确认的时段。' : ''}`,
|
|
50
50
|
timeline: { startLabel: '10/23 00:00 · 上海', endLabel: '10/24 00:00 · 上海', segments: unknown ? segments.slice(0, 48) : segments },
|
|
51
51
|
}]} />
|
|
52
52
|
<TimelineLegend compact label="历史状态图例" items={compactAccountLegend} />
|
|
53
53
|
</Stack>
|
|
54
|
-
<Stack gap={
|
|
55
|
-
<
|
|
54
|
+
<Stack gap={3} className="compact-gallery-facts" data-testid="compact-account-facts">
|
|
55
|
+
<Grid gap={2} className="compact-gallery-facts-meta">
|
|
56
56
|
<Tooltip content="数值越小,调度优先级越高;这不是健康评分。"><Text as="span" variant="caption" tabIndex={0}>优先级 <strong>0</strong></Text></Tooltip>
|
|
57
57
|
<Text as="span" variant="caption">API-Key <strong>{unknown ? '—' : zero ? '0' : '3'}</strong></Text>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
</
|
|
58
|
+
<Text as="span" variant="caption" className="compact-gallery-facts-expiry">到期 <strong>{long ? '2027-10-24 23:59 · 上海(运营设置的账号期限)' : '未设置'}</strong></Text>
|
|
59
|
+
{long && <Text as="span" variant="caption" className="compact-gallery-facts-subscription">订阅到期 <strong>2026-11-24 08:30 · 上海(当前服务套餐期限)</strong></Text>}
|
|
60
|
+
</Grid>
|
|
61
|
+
<Stack gap={2}>
|
|
62
|
+
<Grid gap={2} className="compact-gallery-fact-pair">
|
|
63
|
+
<Text as="span" variant="caption">今日消费 <strong>{unknown ? '—' : zero ? '$0.00' : long ? '$12,345.67 / $24,691.34' : '$2.40'}</strong></Text>
|
|
64
|
+
<Text as="span" variant="caption">本周期 <strong>{unknown ? '—' : zero ? '$0.00' : long ? '$123,456.78 / $246,913.56' : '$18.50'}</strong></Text>
|
|
65
|
+
</Grid>
|
|
66
|
+
<Grid gap={2} className="compact-gallery-fact-pair">
|
|
67
|
+
<Text as="span" variant="caption">今日生图 <strong>{unknown ? '—' : zero ? '0' : '8'}</strong></Text>
|
|
68
|
+
<Text as="span" variant="caption">本周期生图 <strong>{unknown ? '—' : zero ? '0' : '24'}</strong></Text>
|
|
69
|
+
</Grid>
|
|
70
|
+
</Stack>
|
|
71
71
|
</Stack>
|
|
72
72
|
</Stack></Surface>;
|
|
73
73
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import { Button, DateRangePicker, Divider, Icon, Inline, Popover, SegmentedControl, Stack, Surface, Text, type DateRange } from '../components';
|
|
2
|
+
import { Button, DateField, DateRangePicker, Divider, Icon, Inline, Popover, SegmentedControl, Select, Stack, Surface, Text, type DateRange } from '../components';
|
|
3
3
|
|
|
4
4
|
const quickRanges = [
|
|
5
5
|
{ value: 'today', label: '今天', range: { start: '2026-10-24', end: '2026-10-24' } },
|
|
@@ -78,6 +78,10 @@ export function DateRangeFilterExample({ label = '时间范围', initialValue =
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
export function DateRangeFilterGallery() {
|
|
81
|
+
const [narrowWidth, setNarrowWidth] = useState('150');
|
|
82
|
+
const [narrowRange, setNarrowRange] = useState<DateRange>({ start: '2026-10-24', end: '2026-10-25' });
|
|
83
|
+
const [singleDate, setSingleDate] = useState('2026-10-24');
|
|
84
|
+
const [resetRevision, setResetRevision] = useState(0);
|
|
81
85
|
return <section id="date-range-filter">
|
|
82
86
|
<div className="ds-section-title"><span>RANGE</span><h2>日期筛选 · 快捷选择与自定义</h2></div>
|
|
83
87
|
<p className="ds-caption">常用日期是一组互斥选项;具体日期持续可见。更多预设即选即用,自定义日期在确认后生效。</p>
|
|
@@ -103,6 +107,19 @@ export function DateRangeFilterGallery() {
|
|
|
103
107
|
<Text variant="caption" tone="muted">归档结果的统计范围固定,保持日期可读。</Text>
|
|
104
108
|
</Stack>
|
|
105
109
|
</Surface>
|
|
110
|
+
<Stack gap={3}>
|
|
111
|
+
<Text as="h3" variant="heading">窄容器中的完整日期</Text>
|
|
112
|
+
<Text variant="caption" tone="muted">只改变实际容器宽度,不缩小字号。请聚焦日期并编辑最后一段,确认完整年月日与日历入口仍可见;尺寸适配自 0.9.1 提供。</Text>
|
|
113
|
+
<Select label="日期容器宽度" value={narrowWidth} onChange={event => setNarrowWidth(event.target.value)}>
|
|
114
|
+
<option value="150">150 CSS px</option><option value="200">200 CSS px</option><option value="280">280 CSS px</option>
|
|
115
|
+
</Select>
|
|
116
|
+
<Stack gap={3} style={{ width: Number(narrowWidth), maxWidth: '100%' }}>
|
|
117
|
+
<DateRangePicker key={resetRevision} label="局部日期" value={narrowRange} onValueChange={setNarrowRange} />
|
|
118
|
+
<Inline><DateField key={resetRevision} label="独立日期" value={singleDate} onValueChange={setSingleDate} /></Inline>
|
|
119
|
+
<Button variant="secondary" onClick={() => { setNarrowRange({ start: '2026-10-24', end: '2026-10-25' }); setSingleDate('2026-10-24'); setResetRevision(value => value + 1); }}>重置示例日期</Button>
|
|
120
|
+
</Stack>
|
|
121
|
+
<Text variant="caption" role="status">已接受日期:{narrowRange.start || '不限开始'} — {narrowRange.end || '不限结束'}</Text>
|
|
122
|
+
</Stack>
|
|
106
123
|
<Text variant="caption" tone="muted">方向键切换快捷项;Escape 或取消退出草稿并归还焦点,点击外部控件按原生焦点行为继续。所有主题使用同一组公开组件。</Text>
|
|
107
124
|
</Stack>
|
|
108
125
|
</section>;
|
package/preview/GalleryShell.tsx
CHANGED
|
@@ -10,7 +10,7 @@ export const galleryGroups = [
|
|
|
10
10
|
{ label: '应用能力', items: [['app-shell', '背景与多侧栏'], ['advanced-forms', '复杂表单'], ['advanced-data', '受控数据表'], ['media', '媒体卡片与查看器'], ['monitoring', '健康详情与图表']] },
|
|
11
11
|
{ label: '设计语言', items: [['foundation', '视觉基础'], ['backgrounds', '场景背景'], ['spacing', '间距与组合'], ['typography', '字体与文字'], ['brand', '品牌标志']] },
|
|
12
12
|
{ label: '通用组件', items: [['actions', '按钮与输入'], ['selection', '选择与切换'], ['feedback', '状态与加载'], ['disclosure', '展开与组合'], ['data', '数据与分页'], ['forms', '表单与选择'], ['utilities', '操作与布局'], ['overlays', '浮层与反馈'], ['overlay-bounds', '浮层缩放与边界'], ['settings-dialog', '设置导航弹窗'], ['theme-picker', '主题预览与选择'], ['navigation', '导航与步骤'], ['form-workflow', '表单流程']] },
|
|
13
|
-
{ label: '交互与动效', items: [['patterns', '场景与交互指南'], ['
|
|
13
|
+
{ label: '交互与动效', items: [['patterns', '场景与交互指南'], ['compact-account', '紧凑账号与凭证'], ['list-filter-bar', '列表筛选栏'], ['date-range-filter', '日期筛选组合'], ['motion', '动效实验室'], ['startup', '启动动画'], ['orbit', '轨道星空'], ['neutron-star', '中子星']] },
|
|
14
14
|
] as const;
|
|
15
15
|
export type GallerySection = typeof galleryGroups[number]['items'][number][0];
|
|
16
16
|
const entries = galleryGroups.flatMap(group => [...group.items]);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Badge, Button, Checkbox, DateRangePicker, EmptyState, Inline, ListFilterBar,
|
|
4
|
+
Select, Skeleton, Stack, Surface, Text, type DateRange,
|
|
5
|
+
} from '../components';
|
|
6
|
+
|
|
7
|
+
type PreviewState = 'normal' | 'loading' | 'empty' | 'error' | 'disabled';
|
|
8
|
+
const records = [
|
|
9
|
+
{ id: 'design-review', title: '核对页面设计与窄屏布局', person: '示例成员', state: 'done', reviewed: true, date: '2026-10-24' },
|
|
10
|
+
{ id: 'component-review', title: '整理共享组件与交互规则', person: '组件维护者', state: 'running', reviewed: false, date: '2026-10-23' },
|
|
11
|
+
];
|
|
12
|
+
const dateLabel = ({ start, end }: DateRange) => start || end ? `${start || '不限开始'} — ${end || '不限结束'}` : undefined;
|
|
13
|
+
|
|
14
|
+
function usePreviewRefresh() {
|
|
15
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
16
|
+
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
17
|
+
useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);
|
|
18
|
+
return {
|
|
19
|
+
refreshing,
|
|
20
|
+
refresh: () => {
|
|
21
|
+
if (timer.current) clearTimeout(timer.current);
|
|
22
|
+
setRefreshing(true);
|
|
23
|
+
timer.current = setTimeout(() => setRefreshing(false), 600);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function SessionListExample({ mode, embedded = false }: { mode: PreviewState; embedded?: boolean }) {
|
|
29
|
+
const [query, setQuery] = useState('');
|
|
30
|
+
const [state, setState] = useState('all');
|
|
31
|
+
const [sort, setSort] = useState('newest');
|
|
32
|
+
const [person, setPerson] = useState('all');
|
|
33
|
+
const [reviewed, setReviewed] = useState(false);
|
|
34
|
+
const [range, setRange] = useState<DateRange>({ start: '', end: '' });
|
|
35
|
+
const [dateResetKey, setDateResetKey] = useState(0);
|
|
36
|
+
const [exportCount, setExportCount] = useState(0);
|
|
37
|
+
const { refreshing, refresh } = usePreviewRefresh();
|
|
38
|
+
const disabled = mode === 'disabled';
|
|
39
|
+
const pending = mode === 'loading' || refreshing;
|
|
40
|
+
const label = embedded ? '人员会话筛选' : '录制会话筛选';
|
|
41
|
+
const reset = () => { setQuery(''); setState('all'); setSort('newest'); setPerson('all'); setReviewed(false); setRange({ start: '', end: '' }); setDateResetKey(key => key + 1); };
|
|
42
|
+
// Gallery records are synthetic; production hosts send these values to their own query service.
|
|
43
|
+
const rows = mode === 'empty' ? [] : records.filter(row =>
|
|
44
|
+
(state === 'all' || row.state === state) &&
|
|
45
|
+
(person === 'all' || row.person === person) &&
|
|
46
|
+
(!reviewed || row.reviewed) &&
|
|
47
|
+
(!range.start || row.date >= range.start) && (!range.end || row.date <= range.end) &&
|
|
48
|
+
`${row.title} ${row.person} ${row.id}`.includes(query.trim()),
|
|
49
|
+
).slice().sort((a, b) => sort === 'oldest' ? a.id.localeCompare(b.id) : b.id.localeCompare(a.id));
|
|
50
|
+
const common = [
|
|
51
|
+
{
|
|
52
|
+
id: 'status', label: '会话状态', activeLabel: state === 'all' ? undefined : state === 'done' ? '已完成' : '进行中',
|
|
53
|
+
onClear: disabled ? undefined : () => setState('all'),
|
|
54
|
+
control: <Select label="会话状态" value={state} disabled={disabled} onChange={event => setState(event.target.value)}>
|
|
55
|
+
<option value="all">全部状态</option><option value="running">进行中</option><option value="done">已完成</option>
|
|
56
|
+
</Select>,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'sort', label: '排序', activeLabel: sort === 'newest' ? undefined : '最早事件优先',
|
|
60
|
+
onClear: disabled ? undefined : () => setSort('newest'),
|
|
61
|
+
control: <Select label="排序" value={sort} disabled={disabled} onChange={event => setSort(event.target.value)}>
|
|
62
|
+
<option value="newest">最后事件最新</option><option value="oldest">最早事件优先</option>
|
|
63
|
+
</Select>,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
const reviewField = {
|
|
67
|
+
id: 'reviewed', label: '审查', activeLabel: reviewed ? '只看已审查' : undefined,
|
|
68
|
+
onClear: disabled ? undefined : () => setReviewed(false),
|
|
69
|
+
control: <Checkbox label="只看已审查" checked={reviewed} disabled={disabled} onChange={event => setReviewed(event.target.checked)} />,
|
|
70
|
+
};
|
|
71
|
+
return <Stack gap={4} style={embedded ? { maxWidth: 420, width: '100%' } : undefined}>
|
|
72
|
+
<ListFilterBar label={label}
|
|
73
|
+
search={{ label: '搜索会话', placeholder: '搜索消息或会话 ID', value: query, disabled, onChange: event => setQuery(event.target.value) }}
|
|
74
|
+
primary={embedded ? common : [...common, reviewField]}
|
|
75
|
+
filters={[
|
|
76
|
+
...(embedded ? [reviewField] : []),
|
|
77
|
+
{
|
|
78
|
+
id: 'person', label: '人员', activeLabel: person === 'all' ? undefined : person,
|
|
79
|
+
onClear: disabled ? undefined : () => setPerson('all'),
|
|
80
|
+
control: <Select label="人员" value={person} disabled={disabled} onChange={event => setPerson(event.target.value)}>
|
|
81
|
+
<option value="all">全部人员</option><option>示例成员</option><option>组件维护者</option>
|
|
82
|
+
</Select>,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'date', label: '最后事件日期', activeLabel: dateLabel(range),
|
|
86
|
+
onClear: disabled ? undefined : () => setRange({ start: '', end: '' }),
|
|
87
|
+
control: <DateRangePicker key={dateResetKey} label="最后事件日期" value={range} onValueChange={setRange} disabled={disabled}
|
|
88
|
+
presets={[{ label: '今天', value: { start: '2026-10-24', end: '2026-10-24' } }]} />,
|
|
89
|
+
},
|
|
90
|
+
]}
|
|
91
|
+
onReset={disabled ? undefined : reset}
|
|
92
|
+
refresh={{ onClick: refresh, busy: pending, disabled }}
|
|
93
|
+
action={embedded ? undefined : <Button variant="secondary" disabled={disabled || pending} onClick={() => setExportCount(count => count + 1)}>导出示例</Button>}
|
|
94
|
+
result={pending ? undefined : `${rows.length} 条结果`}
|
|
95
|
+
pending={pending}
|
|
96
|
+
error={mode === 'error' ? '读取失败,当前保留上次结果。请刷新重试。' : undefined}
|
|
97
|
+
/>
|
|
98
|
+
{pending ? <Skeleton label="正在读取会话" lines={3} /> : rows.length ? rows.map(row => <Surface key={row.id}>
|
|
99
|
+
<Stack gap={2}>
|
|
100
|
+
<Inline gap={2}><Text as="h4" variant="label">{row.person}</Text><Badge>{row.state === 'done' ? '已完成' : '进行中'}</Badge></Inline>
|
|
101
|
+
<Text>{row.title}</Text><Text variant="caption" tone="muted">{row.id}</Text>
|
|
102
|
+
</Stack>
|
|
103
|
+
</Surface>) : <EmptyState title="没有匹配的会话" description="试试其他条件。" action={!disabled ? <Button variant="secondary" onClick={reset}>清除筛选</Button> : undefined} />}
|
|
104
|
+
{!!exportCount && <Text role="status" variant="caption" tone="muted">已触发 {exportCount} 次示例操作;展台不生成或下载文件。</Text>}
|
|
105
|
+
</Stack>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function StatisticsExample({ mode }: { mode: PreviewState }) {
|
|
109
|
+
const [scope, setScope] = useState('all');
|
|
110
|
+
const [platform, setPlatform] = useState('all');
|
|
111
|
+
const [metric, setMetric] = useState('cost');
|
|
112
|
+
const [range, setRange] = useState<DateRange>({ start: '2026-10-24', end: '2026-10-24' });
|
|
113
|
+
const [dateResetKey, setDateResetKey] = useState(0);
|
|
114
|
+
const { refreshing, refresh } = usePreviewRefresh();
|
|
115
|
+
const disabled = mode === 'disabled';
|
|
116
|
+
const pending = mode === 'loading' || refreshing;
|
|
117
|
+
const reset = () => { setScope('all'); setPlatform('all'); setMetric('cost'); setRange({ start: '2026-10-24', end: '2026-10-24' }); setDateResetKey(key => key + 1); };
|
|
118
|
+
return <Stack gap={4}>
|
|
119
|
+
<ListFilterBar label="部门用量筛选"
|
|
120
|
+
primary={[
|
|
121
|
+
{ id: 'platform', label: '平台', activeLabel: platform === 'all' ? undefined : platform, onClear: disabled ? undefined : () => setPlatform('all'),
|
|
122
|
+
control: <Select label="平台" value={platform} disabled={disabled} onChange={event => setPlatform(event.target.value)}><option value="all">全部平台</option><option>OpenAI</option></Select> },
|
|
123
|
+
{ id: 'metric', label: '排行依据', activeLabel: metric === 'cost' ? undefined : 'Token 排行', onClear: disabled ? undefined : () => setMetric('cost'),
|
|
124
|
+
control: <Select label="排行依据" value={metric} disabled={disabled} onChange={event => setMetric(event.target.value)}><option value="cost">消费排行</option><option value="tokens">Token 排行</option></Select> },
|
|
125
|
+
]}
|
|
126
|
+
filters={[
|
|
127
|
+
{ id: 'scope', label: '统计范围', activeLabel: scope === 'all' ? undefined : scope, onClear: disabled ? undefined : () => setScope('all'),
|
|
128
|
+
control: <Select label="统计范围" value={scope} disabled={disabled} onChange={event => setScope(event.target.value)}><option value="all">所有可见部门</option><option>示例团队 / 跨部门体验与可访问性协作小组</option></Select> },
|
|
129
|
+
{ id: 'range', label: '统计日期', activeLabel: range.start === '2026-10-24' && range.end === '2026-10-24' ? undefined : dateLabel(range),
|
|
130
|
+
onClear: disabled ? undefined : () => setRange({ start: '2026-10-24', end: '2026-10-24' }),
|
|
131
|
+
control: <DateRangePicker key={dateResetKey} label="统计日期" value={range} onValueChange={setRange} disabled={disabled} clearable={false} /> },
|
|
132
|
+
]}
|
|
133
|
+
onReset={disabled ? undefined : reset} refresh={{ onClick: refresh, busy: pending, disabled }} pending={pending}
|
|
134
|
+
result={pending ? undefined : mode === 'empty' ? '当前范围无用量' : `${dateLabel(range)} · ${scope === 'all' ? '所有可见部门' : scope}`}
|
|
135
|
+
error={mode === 'error' ? '统计读取失败,暂不能确认当前范围结果。' : undefined}
|
|
136
|
+
/>
|
|
137
|
+
{pending ? <Skeleton label="正在读取用量" lines={2} /> : mode === 'empty' ? <EmptyState title="当前范围没有用量" description="可更改范围或日期后重试。" /> : <Surface><Stack gap={2}>
|
|
138
|
+
<Text as="h4" variant="label">示例统计结果</Text><Text variant="caption" tone="muted">统计范围、日期与排行方式由宿主控制;此处仅展示合成数据。</Text>
|
|
139
|
+
<Text numeric>{metric === 'cost' ? '$ 8.42' : '1,400,000 Token'}</Text>
|
|
140
|
+
</Stack></Surface>}
|
|
141
|
+
</Stack>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function ListFilterBarGallery() {
|
|
145
|
+
const [mode, setMode] = useState<PreviewState>('normal');
|
|
146
|
+
return <section id="list-filter-bar">
|
|
147
|
+
<div className="ds-section-title"><span>FILTERS</span><h2>列表筛选 · 限制常显复杂度</h2></div>
|
|
148
|
+
<p className="ds-caption">搜索、最多两个常显条件和一个业务操作;其他条件按需展开。对应 Rhine #19,查询与权限由宿主管理。</p>
|
|
149
|
+
<Stack gap={6}>
|
|
150
|
+
<Select label="示例状态" value={mode} onChange={event => setMode(event.target.value as PreviewState)}>
|
|
151
|
+
<option value="normal">正常 / 有数据</option><option value="loading">加载中</option><option value="empty">无结果</option><option value="error">读取失败</option><option value="disabled">条件不可修改</option>
|
|
152
|
+
</Select>
|
|
153
|
+
<Stack gap={3}>
|
|
154
|
+
<Text as="h3" variant="heading">独立列表</Text>
|
|
155
|
+
<Text variant="caption" tone="muted">传入的第三个常显条件“审查”自动归入更多筛选;不会继续增加顶部行数。</Text>
|
|
156
|
+
<SessionListExample mode={mode} />
|
|
157
|
+
</Stack>
|
|
158
|
+
<Stack gap={3}>
|
|
159
|
+
<Text as="h3" variant="heading">嵌入人员详情 · 420px 容器</Text>
|
|
160
|
+
<Text variant="caption" tone="muted">同一组件随实际容器重排,窄区域仍保留结果、刷新和退出筛选的入口。</Text>
|
|
161
|
+
<SessionListExample mode={mode} embedded />
|
|
162
|
+
</Stack>
|
|
163
|
+
<Stack gap={3}>
|
|
164
|
+
<Text as="h3" variant="heading">统计范围 · 无搜索框</Text>
|
|
165
|
+
<StatisticsExample mode={mode} />
|
|
166
|
+
</Stack>
|
|
167
|
+
<Text variant="caption" tone="muted">筛选即时改变宿主状态,关闭抽屉不撤销条件。加载、错误和结果在抽屉内同样可见;用 Escape 关闭并返回入口。</Text>
|
|
168
|
+
</Stack>
|
|
169
|
+
</section>;
|
|
170
|
+
}
|
package/preview/OrbitGallery.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
+
import { OrbitEntryExample } from './BackgroundFiltersFixture';
|
|
2
3
|
import { Badge, Button, Disclosure, Inline, OrbitBackdrop, SegmentedControl, Stack, Surface, Switch, Text, TextField } from '../components';
|
|
3
4
|
|
|
4
5
|
export function OrbitGallery() {
|
|
@@ -46,5 +47,18 @@ export function OrbitGallery() {
|
|
|
46
47
|
</Stack></div>
|
|
47
48
|
</OrbitBackdrop>
|
|
48
49
|
</Disclosure>
|
|
50
|
+
<Disclosure title="查看入口与全页背景组合">
|
|
51
|
+
<Text tone="muted">文字保留在上方,艺术位于右下角;无框背景下的表单使用 Surface 保持清晰。</Text>
|
|
52
|
+
<div className="ds-orbit-layout-container"><div className="ds-orbit-layout-examples ds-gap-top">
|
|
53
|
+
<OrbitBackdrop className="ds-orbit-corner-example" appearance={appearance} reducedMotion={reduced} composition="corner" controls={false} role="region" aria-label="右下角艺术与文字">
|
|
54
|
+
<Stack gap={4}>
|
|
55
|
+
<Text variant="overline">RHINE STUDIO</Text>
|
|
56
|
+
<Text as="h3" variant="title">从这里,<br />继续创造。</Text>
|
|
57
|
+
<Text tone="muted">让内容拥有清晰的阅读空间。</Text>
|
|
58
|
+
</Stack>
|
|
59
|
+
</OrbitBackdrop>
|
|
60
|
+
<OrbitEntryExample appearance={appearance} reduced={reduced} />
|
|
61
|
+
</div></div>
|
|
62
|
+
</Disclosure>
|
|
49
63
|
</section>;
|
|
50
64
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import { Button, Grid, Inline, Notice, Select, Stack, Surface, Text, type Spacing } from '../components';
|
|
2
|
+
import { Button, DataTable, Grid, Inline, Notice, Select, Stack, Surface, Text, type Spacing } from '../components';
|
|
3
3
|
import './spacing.css';
|
|
4
4
|
|
|
5
5
|
const scale: Spacing[] = [1, 2, 3, 4, 6, 8, 12];
|
|
6
6
|
export function SpacingGallery() {
|
|
7
7
|
const [state, setState] = useState('normal');
|
|
8
|
+
const [selected, setSelected] = useState<string[]>(['research']);
|
|
8
9
|
return <section id="spacing">
|
|
9
10
|
<div className="ds-section-title"><span>SPACE</span><h2>间距与组合 · 让关系可见</h2></div>
|
|
10
11
|
<Stack gap={6}>
|
|
@@ -27,6 +28,19 @@ export function SpacingGallery() {
|
|
|
27
28
|
<Text variant="caption" tone="muted">Surface只负责边界与内边距。避免父gap与子margin重复累加;不固定卡高,不为空白增加动效。</Text>
|
|
28
29
|
</Stack></Surface>
|
|
29
30
|
</Grid>
|
|
31
|
+
<Stack gap={3}>
|
|
32
|
+
<Text as="h3" variant="heading">纯表格 · 取消容器外围内距</Text>
|
|
33
|
+
<Text variant="caption" tone="muted">Surface padding="none" 自 0.9.1 提供;表头和单元格保留原有间距。切换主题并勾选行,检查边界和文字。</Text>
|
|
34
|
+
<Surface padding="none">
|
|
35
|
+
<DataTable caption="示例部门用量" rows={[
|
|
36
|
+
{ id: 'research', name: '研发协作组', requests: 128 },
|
|
37
|
+
{ id: 'accessibility', name: '跨部门体验与可访问性协作小组', requests: 42 },
|
|
38
|
+
]} columns={[
|
|
39
|
+
{ key: 'name', header: '部门', render: row => row.name, rowHeader: true },
|
|
40
|
+
{ key: 'requests', header: '请求', render: row => row.requests, align: 'right' },
|
|
41
|
+
]} rowKey={row => row.id} rowLabel={row => row.name} selection={{ keys: selected, onChange: setSelected }} />
|
|
42
|
+
</Surface>
|
|
43
|
+
</Stack>
|
|
30
44
|
</Stack>
|
|
31
45
|
</section>;
|
|
32
46
|
}
|
package/preview/main.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BackgroundFiltersFixture } from './BackgroundFiltersFixture';
|
|
1
2
|
import { StrictMode, useEffect, useRef, useState } from 'react';
|
|
2
3
|
import { createRoot } from 'react-dom/client';
|
|
3
4
|
import { Badge, Button, Checkbox, Disclosure, EmptyState, Logo, Notice, Progress, SegmentedControl, Select, SelectionCard, Skeleton, Surface, Tabs, Text, TextField, Toolbar, type TextVariant } from '../components/index';
|
|
@@ -19,6 +20,7 @@ import { NeutronStarGallery } from './NeutronStarGallery';
|
|
|
19
20
|
import { AdvancedDataGallery, AdvancedFormsGallery, MediaGallery, ShellGallery } from './ExtensionsGallery';
|
|
20
21
|
import { MonitoringGallery } from './MonitoringGallery';
|
|
21
22
|
import { DateRangeFilterGallery } from './DateRangeFilterGallery';
|
|
23
|
+
import { ListFilterBarGallery } from './ListFilterBarGallery';
|
|
22
24
|
import { CompactAccountGallery } from './CompactAccountGallery';
|
|
23
25
|
|
|
24
26
|
function Gallery() {
|
|
@@ -44,6 +46,7 @@ function Gallery() {
|
|
|
44
46
|
{section === 'overlay-bounds' && <OverlayBoundsGallery />}
|
|
45
47
|
{section === 'monitoring' && <MonitoringGallery />}
|
|
46
48
|
{section === 'date-range-filter' && <DateRangeFilterGallery />}
|
|
49
|
+
{section === 'list-filter-bar' && <ListFilterBarGallery />}
|
|
47
50
|
{section === 'compact-account' && <CompactAccountGallery />}
|
|
48
51
|
{section === 'backgrounds' && <BackgroundGallery />}
|
|
49
52
|
{section === 'app-shell' && <ShellGallery />}
|
|
@@ -135,4 +138,4 @@ function Gallery() {
|
|
|
135
138
|
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
createRoot(document.getElementById('root')!).render(<StrictMode>{new URLSearchParams(location.search).get('fixture') === 'overlay-bounds' ? <OverlayBoundsFixture /> : new URLSearchParams(location.search).get('fixture') === 'settings-dialog' ? <SettingsDialogFixture /> : new URLSearchParams(location.search).get('fixture') === 'theme-picker' ? <ThemePickerFixture /> : <Gallery />}</StrictMode>);
|
|
141
|
+
createRoot(document.getElementById('root')!).render(<StrictMode>{['orbit-entry', 'list-filters'].includes(new URLSearchParams(location.search).get('fixture') || '') ? <BackgroundFiltersFixture /> : new URLSearchParams(location.search).get('fixture') === 'overlay-bounds' ? <OverlayBoundsFixture /> : new URLSearchParams(location.search).get('fixture') === 'settings-dialog' ? <SettingsDialogFixture /> : new URLSearchParams(location.search).get('fixture') === 'theme-picker' ? <ThemePickerFixture /> : <Gallery />}</StrictMode>);
|
package/preview/preview.css
CHANGED
|
@@ -3,8 +3,15 @@ body { margin:0; }
|
|
|
3
3
|
.compact-gallery-stage { max-width:100%; min-width:0; }
|
|
4
4
|
.compact-gallery-examples { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr)); gap:var(--rhine-space-6); align-items:start; }
|
|
5
5
|
.compact-gallery-examples>* { min-width:0; }
|
|
6
|
-
.compact-gallery-
|
|
7
|
-
.compact-gallery-
|
|
6
|
+
.compact-gallery-facts { container-type:inline-size; }
|
|
7
|
+
.compact-gallery-facts-meta { grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,2fr); align-items:baseline; }
|
|
8
|
+
.compact-gallery-facts-subscription { grid-column:1 / -1; }
|
|
9
|
+
.compact-gallery-fact-pair { grid-template-columns:repeat(2,minmax(0,1fr)); align-items:baseline; }
|
|
10
|
+
.compact-gallery-facts-meta>*,.compact-gallery-fact-pair>* { min-width:0; overflow-wrap:anywhere; }
|
|
11
|
+
@container (max-width:360px) {
|
|
12
|
+
.compact-gallery-facts-meta { grid-template-columns:repeat(2,minmax(0,1fr)); }
|
|
13
|
+
.compact-gallery-facts-expiry { grid-column:1 / -1; }
|
|
14
|
+
}
|
|
8
15
|
.ds-type-row { display:grid; grid-template-columns:180px minmax(0,1fr); align-items:baseline; gap:12px 24px; padding:16px 0; border-bottom:1px solid var(--rhine-line); }
|
|
9
16
|
.ds-type-row:last-child { border:0; }
|
|
10
17
|
.ds-type-copy { display:grid; gap:12px; margin-top:16px; max-width:60ch; }
|
|
@@ -26,4 +33,19 @@ body { margin:0; }
|
|
|
26
33
|
.ds-footer { border-top:1px solid var(--rhine-ink); display:flex; justify-content:space-between; gap:16px; flex-wrap:wrap; padding:24px 0; color:var(--rhine-muted); font-size:12px; }
|
|
27
34
|
.ds-orbit-artwork { min-height:clamp(360px, 52vw, 560px); }
|
|
28
35
|
.ds-orbit-example-content { width:min(100%, 560px); }
|
|
36
|
+
.ds-orbit-layout-container { container-type:inline-size; }
|
|
37
|
+
.ds-orbit-layout-examples { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--rhine-space-6); }
|
|
38
|
+
.ds-orbit-corner-example { min-height:620px; }
|
|
39
|
+
@container (max-width:760px) { .ds-orbit-layout-examples { grid-template-columns:1fr; } }
|
|
29
40
|
@media(max-width:760px) { .ds-hero,.ds-grid { grid-template-columns:1fr; gap:24px; }.ds-hero { padding-top:32px; }.ds-hero h1 { font-size:32px; letter-spacing:-1px; }.ds-palette { grid-template-columns:repeat(auto-fit,minmax(min(100%,90px),1fr)); }.ds-palette>div>div { height:64px; }.ds-section-title h2 { font-size:19px; } }
|
|
41
|
+
|
|
42
|
+
.ds-orbit-entry-container { container-type:inline-size; min-width:0; }
|
|
43
|
+
.ds-orbit-entry { position:relative; isolation:isolate; display:grid; grid-template-columns:1fr 1fr; gap:var(--rhine-space-6); min-height:620px; padding:var(--rhine-space-6); }
|
|
44
|
+
.ds-orbit-entry-background { position:absolute; inset:0 auto 0 0; width:50%; z-index:-1; pointer-events:none; }
|
|
45
|
+
.ds-orbit-entry-intro, .ds-orbit-entry-form { min-width:0; align-self:start; }
|
|
46
|
+
.ds-orbit-entry-form { background:var(--rhine-surface); }
|
|
47
|
+
.ds-orbit-entry:has([data-rhine-orbit-appearance="original"]) .ds-orbit-entry-intro { color:#f2f7ff; }
|
|
48
|
+
@container (max-width:760px) {
|
|
49
|
+
.ds-orbit-entry { grid-template-columns:1fr; padding-bottom:300px; }
|
|
50
|
+
.ds-orbit-entry-background { width:100%; }
|
|
51
|
+
}
|