@robert-stackflow/oneui 0.1.0 → 0.3.0
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 +28 -0
- package/README.md +58 -2
- package/components/AppShell.d.ts +11 -4
- package/components/AppShell.js +79 -54
- package/components/AppShell.module.css +16 -4
- package/components/AppearanceSettings.js +1 -1
- package/components/Button.d.ts +9 -2
- package/components/Button.js +30 -7
- package/components/Button.module.css +16 -7
- package/components/Calendar.d.ts +2 -1
- package/components/Calendar.js +4 -3
- package/components/Card.d.ts +2 -1
- package/components/Card.js +13 -3
- package/components/Card.module.css +19 -0
- package/components/Checkbox.module.css +3 -3
- package/components/Combobox.d.ts +4 -2
- package/components/Combobox.js +162 -76
- package/components/Combobox.module.css +5 -7
- package/components/Control.module.css +18 -5
- package/components/DatePicker.d.ts +3 -0
- package/components/DatePicker.js +41 -21
- package/components/DatePicker.module.css +3 -0
- package/components/Dialog.d.ts +6 -1
- package/components/Dialog.js +19 -6
- package/components/Dialog.module.css +17 -0
- package/components/DialogVariants.d.ts +27 -0
- package/components/DialogVariants.js +161 -0
- package/components/DialogVariants.module.css +29 -0
- package/components/HierarchyPicker.module.css +35 -0
- package/components/IconButton.module.css +5 -2
- package/components/IconPicker.d.ts +14 -4
- package/components/IconPicker.js +67 -25
- package/components/Indicator.module.css +1 -1
- package/components/Input.d.ts +2 -1
- package/components/Input.js +12 -4
- package/components/Input.module.css +18 -1
- package/components/LoadingState.d.ts +3 -1
- package/components/LoadingState.js +26 -15
- package/components/LoadingState.module.css +3 -0
- package/components/MenuItem.d.ts +2 -1
- package/components/MenuItem.js +2 -0
- package/components/MenuItem.module.css +9 -0
- package/components/MultiSelect.d.ts +2 -1
- package/components/MultiSelect.js +66 -2
- package/components/MultiSelect.module.css +38 -0
- package/components/NumberInput.js +1 -0
- package/components/OptionList.module.css +12 -0
- package/components/PasswordField.js +1 -0
- package/components/PinInput.js +1 -0
- package/components/Popover.d.ts +4 -1
- package/components/Popover.js +11 -5
- package/components/Popover.module.css +3 -0
- package/components/RadioGroup.module.css +2 -2
- package/components/SearchField.js +1 -0
- package/components/Select.js +1 -0
- package/components/Select.module.css +3 -0
- package/components/SelectionTag.d.ts +8 -0
- package/components/SelectionTag.js +39 -0
- package/components/SettingsLayout.d.ts +3 -1
- package/components/SettingsLayout.js +19 -3
- package/components/SettingsLayout.module.css +20 -0
- package/components/Switch.module.css +8 -5
- package/components/Table.d.ts +6 -2
- package/components/Table.js +14 -4
- package/components/TagInput.js +11 -23
- package/components/TagInput.module.css +1 -1
- package/components/ThemeProvider.js +17 -3
- package/components/ToastProvider.d.ts +4 -4
- package/components/ToastProvider.js +19 -22
- package/components/TreeGuides.d.ts +9 -0
- package/components/TreeGuides.js +39 -0
- package/components/TreeGuides.module.css +45 -0
- package/components/TreeSelect.d.ts +3 -0
- package/components/TreeSelect.js +49 -21
- package/components/TreeTable.d.ts +15 -0
- package/components/TreeTable.js +151 -0
- package/components/TreeTable.module.css +53 -0
- package/components/VirtualPickerList.d.ts +2 -0
- package/components/VirtualPickerList.js +1 -1
- package/components/VirtualPickerList.module.css +4 -0
- package/components/appearance.d.ts +1 -0
- package/components/appearance.js +15 -6
- package/components/calendarNow.d.ts +5 -0
- package/components/calendarNow.js +27 -0
- package/components/createToaster.d.ts +12 -0
- package/components/createToaster.js +40 -0
- package/components/hierarchy.d.ts +1 -0
- package/components/index.d.ts +6 -3
- package/components/index.js +13 -1
- package/components/usePopover.d.ts +2 -2
- package/package.json +36 -4
- package/styles/base.css +20 -3
- package/styles/blue.css +49 -35
- package/styles/minimal.css +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0 — 2026-10-28
|
|
4
|
+
|
|
5
|
+
- AppShell 新增 brandName、brandIcon、brand、homePath、basePath 和 showHeader;支持无顶部栏的移动导航,可省略设置入口及侧栏折叠回调。普通点击向宿主传递内部路径,链接和新标签页使用完整基础路径。
|
|
6
|
+
- 新增 createToaster 独立通知控制器,可由 React 组件外代码调用;ToastProvider 可接收 controller,保留原 useToast API 和单条通知替换行为。
|
|
7
|
+
- IconPicker 支持 valuePrefix、自定义图标目录与 React 图标内容,保留存储 ID;统一字段、只读、禁用和表单值行为。
|
|
8
|
+
- DatePicker 支持 timeZone、clearable、placeholder;指定时区影响今天/此刻及日历当天标记,输入的日历时间保持原值,不隐式转换为 UTC。
|
|
9
|
+
- React/React DOM peer 范围调整为 ^19.2.0,独立消费验证覆盖 React 19.3.0 + TypeScript 5.9.3 和现有 React 19.3.1。
|
|
10
|
+
- 两端示例增加前缀/自定义图标和指定时区日期示例;未迁移或修改 Moment 业务代码。
|
|
11
|
+
- 修复极简深色导航选中文字过暗的问题。
|
|
12
|
+
|
|
13
|
+
## 0.2.0 — 2026-10-28
|
|
14
|
+
|
|
15
|
+
- 新增 MessageDialog、ConfirmDialog、FormDialog 与统一 LoadingState,复用表单校验、异步提交、错误反馈和焦点管理。
|
|
16
|
+
- TreeSelect 新增 category 图标、数量、层级连线样式;保留 tree 风格,支持父子联动多选。
|
|
17
|
+
- 新增 TreeTable,复用 Table 列定义,支持受控展开、多级连线和键盘导航;列支持 align、width。
|
|
18
|
+
- Combobox 新增 mode="input" 触发框补全;保留弹层搜索,支持异步加载、取消、创建与失败重试。
|
|
19
|
+
- MultiSelect 新增 display="tags",与 TagInput 共用标签实现;保留摘要模式及选择限制。
|
|
20
|
+
- 蓝白主题采用 Moment 对应色板、输入 hover 混色和深色参数;修复默认色板迁移与选择器分类栏底色。
|
|
21
|
+
- 统一菜单项 1px 间距,修复快捷键右对齐、宿主 kbd 边框干扰、关闭的 Popover 被布局样式显示等问题。
|
|
22
|
+
- 树选择器底部清空/完成操作使用独立的 8px 按钮间距。
|
|
23
|
+
- 共享应用外壳与示例布局统一,修复图标尺寸、链接文字、动效示例、表单间距、弹层和移动布局。
|
|
24
|
+
- 发布构建使用干净输出并检查包内引用;增加独立 tarball 消费项目的类型、生产与交互验证。
|
|
25
|
+
|
|
26
|
+
## 0.1.0 — 2026-10-28
|
|
27
|
+
|
|
28
|
+
- 首次发布 React 组件、应用外壳、极简/蓝白主题与图标字符资源。
|
package/README.md
CHANGED
|
@@ -30,17 +30,73 @@ export function App() {
|
|
|
30
30
|
|
|
31
31
|
`ThemeProvider` 管理当前文档的主题,应用根部放置一个即可。颜色模式、动效、字体、密度、圆角和自定义配色均由共享层应用。
|
|
32
32
|
|
|
33
|
+
蓝白默认色板取自 Moment 的 `admin/src/styles.css` 最终变量及 `admin/src/main.tsx` 的 victoria 色阶:浅色背景 `#F5F7FB`、面板 `#FFFFFF`、正文 `#25324A`、次级文字 `#8592A8`、边框 `#E3E8F1`。导航/焦点蓝为 `#5067B8`,主按钮为 `#4059AA`,开关为 `#526EDB`;深色同样使用 Moment 的对应值。各颜色用途分开配置,不再共用一种近似蓝。
|
|
34
|
+
|
|
35
|
+
`normalizeAppearance` 使用 `paletteVersion: 2` 兼容旧蓝白默认值;非默认自定义配色保留,已带版本的显式颜色也不会被迁移。Desktop 与 Web 共用该规范化函数。参考值和源码摘要保存在 `tests/fixtures/moment-palette.json`,验证不依赖 Moment 或 Mantine 安装。
|
|
36
|
+
|
|
37
|
+
蓝白输入与选择控件的 hover 边框复用 Moment 的混色规则:强调色 35% 与基础边框色混合。聚焦或展开时使用完整强调色,错误状态保持错误色;禁用、只读控件不响应 hover。图标、Emoji 和符号选择器的分类栏使用透明背景与主题选中块,不继承普通块状标签页的底板和投影。
|
|
38
|
+
|
|
33
39
|
## 边界
|
|
34
40
|
|
|
35
41
|
- `AppShell` 通过导航数据和 `onNavigate` 接入任何路由或状态机制。
|
|
36
42
|
- `headerActions`、`sidebarFooter`、`windowControls` 为应用能力插槽;`draggable` 仅标记 header 的拖动区域。
|
|
37
43
|
- `UIProvider` 配置 Toast 位置、Tooltip 位置和必填标记;组件不读取宿主设置。
|
|
38
44
|
- 普通组件也支持子路径导入,例如 `@robert-stackflow/oneui/Input`。
|
|
45
|
+
- 页面直接复用 Form / FormField / useFormState 的校验与提交状态、Alert 的错误反馈和 ConfirmDialog 的确认流程;避免另写原生表单、按钮样式或错误提示。
|
|
46
|
+
- Button 支持 `href`,渲染标准链接并沿用按钮样式;禁用或加载时阻止导航。Card 传入 `onClick` 后渲染可通过键盘激活的按钮卡片。导航与业务回调由消费应用提供。
|
|
39
47
|
- CSS Modules 随组件分发,由消费项目的构建工具处理。图标/字符目录通过标准 `new URL(..., import.meta.url)` 加载随包资源。
|
|
40
|
-
- React 和 React DOM 为 peer dependencies
|
|
48
|
+
- React 和 React DOM 为 peer dependencies(^19.2.0),不打入组件包。独立消费验证覆盖 React 19.3.1,以及 React 19.3.0 + TypeScript 5.9.3;浏览器验证使用 Chromium。
|
|
49
|
+
|
|
50
|
+
## 弹窗与加载状态
|
|
51
|
+
|
|
52
|
+
`Dialog` 用于自定义内容,支持 small / medium / large。`MessageDialog` 提供纯文字消息与确认按钮,`ConfirmDialog` 提供普通、危险操作确认,`FormDialog` 提供有统一字段间距的表单及保存操作。三者均可从根入口或对应组件子路径导入。
|
|
53
|
+
|
|
54
|
+
确认与表单支持异步回调,等待期间禁止重复提交及关闭,失败时保留内容并显示错误;回调返回 `false` 可保留弹窗。`FormDialog` 接收共享 `useFormState` 的 controller,字段使用 `FormField`,`onSubmit` 接收校验后的 values,错误样式与普通 Form 一致。
|
|
55
|
+
|
|
56
|
+
`LoadingState` 复用 Spinner;`showLabel={false}` 仅显示动画,`fullPage` 用于页面内容区域。应用自行放置等待边界,保留路由外壳。
|
|
57
|
+
|
|
58
|
+
以上 API 从 0.2.0 起提供。
|
|
59
|
+
|
|
60
|
+
## 层级选择、补全与标签
|
|
61
|
+
|
|
62
|
+
- `TreeSelect variant="category"` 在原有树选择交互上增加图标底块、子项数量与层级连线;默认 `variant="tree"` 保留简洁样式。两种样式均支持搜索、父子联动、多选、禁用、异步加载及键盘操作。`HierarchyOption.count` 可单独提供数量。
|
|
63
|
+
- `TreeTable` 复用 `Table` 的列定义,接收 `rows`、`rowKey`、`getChildren`、`rowLabel`;通过 `expanded` / `onExpandedChange` 控制展开,或使用 `defaultExpanded` / `defaultExpandAll`。`treeColumn` 默认为第一列,支持多级连线、方向键导航及独立行操作。`Column.align`、`Column.width` 可用于对齐和列宽。
|
|
64
|
+
- `Combobox mode="input"` 直接在触发框输入并在下方显示候选,保留 `loadOptions`、`onCreate` 和键盘选择。输入中的文本只用于搜索;选择候选或创建成功才提交选项值,关闭时恢复已选标签。默认 `mode="panel"` 继续使用弹层内搜索。
|
|
65
|
+
- `MultiSelect display="tags"` 在触发框内展示可移除标签,复用 `TagInput` 的标签样式;默认 `display="summary"` 保留文字摘要。两种模式共享连续选择、搜索、数量限制和表单值。只读标签不可移除,禁用选项保持不可操作。
|
|
66
|
+
|
|
67
|
+
菜单项间距统一为 `--menu-item-spacing: 1px`,菜单快捷键为靠右的纯文字提示,不预留空的指示器位置。Desktop 与 Web 共用示例:`/pickers?view=hierarchy`、`/forms?view=advanced`、`/data?view=hierarchy`;层级表格中的增删改仅更新示例内存数据。以上扩展从 0.2.0 起提供。
|
|
68
|
+
|
|
69
|
+
## 接入现有应用(0.3.0)
|
|
70
|
+
|
|
71
|
+
`AppShell` 默认仍显示 OneUI 品牌和顶部栏。可通过 `brandName`、`brandIcon` 设置品牌,或用 `brand` 替换完整品牌内容;`homePath` 指定宿主内部首页,`basePath="/admin"` 为实际链接添加基础路径。`onNavigate` 收到未加基础路径的内部路径,适合 React Router basename;Ctrl/Cmd 点击和新标签页使用完整 href。`showHeader={false}` 保留移动端浮动导航按钮。`settingsItem` 和 `onCollapsedChange` 可省略。自定义完整品牌内容由宿主负责紧凑侧栏适配。
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
<AppShell brandName="Moment" brandIcon={<Brand />} homePath="/workbench"
|
|
75
|
+
basePath="/admin" showHeader={false} navigation={navigation}
|
|
76
|
+
active={active} title={title} onNavigate={navigate}>
|
|
77
|
+
{children}
|
|
78
|
+
</AppShell>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
组件外通知使用独立服务,并将同一实例交给 Provider;不需要在 API 模块调用 Hook。未挂载时保留最新消息;`notify` 返回该消息的关闭函数,旧消息的关闭函数不会关闭新消息。`dismiss` 触发关闭动画,`clear` 立即清除。默认仍只显示最新一条通知,多个控制器相互隔离;服务端渲染时应为每个应用/请求创建实例。
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// notifications.ts
|
|
85
|
+
export const notifications = createToaster();
|
|
86
|
+
export const notifyError = (error: Error) =>
|
|
87
|
+
notifications.notify(error.message, { tone: 'error' });
|
|
88
|
+
// application root
|
|
89
|
+
<ToastProvider controller={notifications}>{children}</ToastProvider>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`IconPicker valuePrefix="lucide:"` 将内置图标名转换为带前缀的存储值;`options` 可提供 `{ value, label, icon, keywords?, category? }` 自定义目录,选择后原样返回 value。自定义目录不加载内置图标资源,设置 options 时不应用 valuePrefix。`IconPickerProps`、`IconPickerOption` 可从根入口或组件子路径导入。
|
|
93
|
+
|
|
94
|
+
`DatePicker mode="datetime" timeZone="Asia/Shanghai" seconds clearable` 使用指定 IANA 时区的今天/此刻,并支持清空及 placeholder。value 始终是 `YYYY-MM-DDTHH:mm[:ss]` 日历字段字符串;时区参数不把输入值变成时间戳,也不校验夏令时缺口/重复时间。定时发布、UTC 换算和时区歧义规则由宿主业务负责。无 timeZone 时继续使用设备本地时间。
|
|
95
|
+
|
|
96
|
+
分类迁移时,如原应用父子分类独立选择,使用 `TreeSelect multiple checkStrictly`;图片上传的 EXIF/GPS、接口结果转换、串行策略(concurrency=1)与未保存状态属于宿主逻辑。
|
|
41
97
|
|
|
42
98
|
## 构建与打包
|
|
43
99
|
|
|
44
|
-
|
|
100
|
+
在仓库根目录运行 `npm run pack:ui`。源码工作区供两端即时开发使用;发布内容单独生成到本目录 `dist`,含 ESM、TypeScript 声明、CSS 和资源。构建使用干净的临时目录并检查所有包内引用,成功后替换 dist,避免旧文件混入。`dist/package.json` 只导出生成内容。
|
|
45
101
|
|
|
46
102
|
发布内容已通过独立 Vite 消费项目验证。包的许可为 UNLICENSED;资源目录内保留图标许可文件。
|
package/components/AppShell.d.ts
CHANGED
|
@@ -14,17 +14,24 @@ export declare function ShellHeader({ title, collapsed, onToggle, onMenu, action
|
|
|
14
14
|
windowControls?: ReactNode;
|
|
15
15
|
draggable?: boolean;
|
|
16
16
|
}): import("react").JSX.Element;
|
|
17
|
-
export
|
|
17
|
+
export interface AppShellProps {
|
|
18
18
|
navigation: readonly NavigationItem[];
|
|
19
|
-
settingsItem
|
|
19
|
+
settingsItem?: NavigationItem;
|
|
20
20
|
active: string;
|
|
21
21
|
title: string;
|
|
22
22
|
collapsed?: boolean;
|
|
23
|
-
onCollapsedChange(value: boolean): void;
|
|
23
|
+
onCollapsedChange?(value: boolean): void;
|
|
24
24
|
onNavigate(path: string): void;
|
|
25
25
|
children: ReactNode;
|
|
26
26
|
headerActions?: ReactNode;
|
|
27
27
|
sidebarFooter?: ReactNode;
|
|
28
28
|
windowControls?: ReactNode;
|
|
29
29
|
draggable?: boolean;
|
|
30
|
-
|
|
30
|
+
brandName?: string;
|
|
31
|
+
brandIcon?: ReactNode;
|
|
32
|
+
brand?: ReactNode;
|
|
33
|
+
homePath?: string;
|
|
34
|
+
basePath?: string;
|
|
35
|
+
showHeader?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare function AppShell({ navigation, settingsItem, active, title, collapsed, onCollapsedChange, onNavigate, children, headerActions, sidebarFooter, windowControls, draggable, brandName, brandIcon, brand, homePath, basePath, showHeader, }: AppShellProps): import("react").JSX.Element;
|
package/components/AppShell.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useState
|
|
6
|
+
} from "react";
|
|
3
7
|
import { Layers2, Menu, PanelLeftClose, PanelLeftOpen } from "lucide-react";
|
|
4
8
|
import { Drawer } from "./Drawer.js";
|
|
5
9
|
import { IconButton } from "./IconButton.js";
|
|
@@ -63,10 +67,24 @@ function AppShell({
|
|
|
63
67
|
headerActions,
|
|
64
68
|
sidebarFooter,
|
|
65
69
|
windowControls,
|
|
66
|
-
draggable = false
|
|
70
|
+
draggable = false,
|
|
71
|
+
brandName = "OneUI",
|
|
72
|
+
brandIcon = /* @__PURE__ */ jsx(Layers2, { size: 20 }),
|
|
73
|
+
brand,
|
|
74
|
+
homePath = "/",
|
|
75
|
+
basePath = "",
|
|
76
|
+
showHeader = true
|
|
67
77
|
}) {
|
|
68
78
|
const [mobile, setMobile] = useState(false);
|
|
69
79
|
const scroll = useRef(null);
|
|
80
|
+
const base = basePath.replace(/^\/+|\/+$/g, ""), href = (path) => (base ? "/" + base : "") + (path.startsWith("/") ? path : "/" + path);
|
|
81
|
+
const navigate = (event, path) => {
|
|
82
|
+
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
|
|
83
|
+
return;
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
onNavigate(path);
|
|
86
|
+
setMobile(false);
|
|
87
|
+
};
|
|
70
88
|
useEffect(() => {
|
|
71
89
|
setMobile(false);
|
|
72
90
|
scroll.current?.scrollTo({ top: 0 });
|
|
@@ -74,16 +92,10 @@ function AppShell({
|
|
|
74
92
|
const item = (n) => /* @__PURE__ */ jsxs(
|
|
75
93
|
"a",
|
|
76
94
|
{
|
|
77
|
-
href: n.path,
|
|
95
|
+
href: href(n.path),
|
|
78
96
|
"aria-current": active === n.id ? "page" : void 0,
|
|
79
97
|
title: collapsed ? n.label : void 0,
|
|
80
|
-
onClick: (
|
|
81
|
-
if (e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey) {
|
|
82
|
-
e.preventDefault();
|
|
83
|
-
onNavigate(n.path);
|
|
84
|
-
setMobile(false);
|
|
85
|
-
}
|
|
86
|
-
},
|
|
98
|
+
onClick: (event) => navigate(event, n.path),
|
|
87
99
|
children: [
|
|
88
100
|
n.icon,
|
|
89
101
|
/* @__PURE__ */ jsx("span", { children: n.label })
|
|
@@ -92,59 +104,72 @@ function AppShell({
|
|
|
92
104
|
n.id
|
|
93
105
|
);
|
|
94
106
|
const nav = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
95
|
-
/* @__PURE__ */
|
|
107
|
+
/* @__PURE__ */ jsx(
|
|
96
108
|
"a",
|
|
97
109
|
{
|
|
98
|
-
href:
|
|
110
|
+
href: href(homePath),
|
|
99
111
|
className: s.brand,
|
|
100
|
-
"aria-label": "
|
|
101
|
-
onClick: (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
children: [
|
|
107
|
-
/* @__PURE__ */ jsx("span", { className: s.mark, children: /* @__PURE__ */ jsx(Layers2, { size: 20 }) }),
|
|
108
|
-
/* @__PURE__ */ jsx("strong", { children: "OneUI" })
|
|
109
|
-
]
|
|
112
|
+
"aria-label": brandName + " \u9996\u9875",
|
|
113
|
+
onClick: (event) => navigate(event, homePath),
|
|
114
|
+
children: brand ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
115
|
+
/* @__PURE__ */ jsx("span", { className: s.mark, children: brandIcon }),
|
|
116
|
+
/* @__PURE__ */ jsx("strong", { children: brandName })
|
|
117
|
+
] })
|
|
110
118
|
}
|
|
111
119
|
),
|
|
112
120
|
/* @__PURE__ */ jsx("nav", { "aria-label": "\u4E3B\u5BFC\u822A", className: s.navigation, children: navigation.map(item) }),
|
|
113
|
-
/* @__PURE__ */ jsxs("div", { className: s.bottom, children: [
|
|
114
|
-
/* @__PURE__ */ jsx("nav", { "aria-label": "\u8BBE\u7F6E", className: s.navigation, children: item(settingsItem) }),
|
|
121
|
+
(settingsItem || sidebarFooter) && /* @__PURE__ */ jsxs("div", { className: s.bottom, children: [
|
|
122
|
+
settingsItem && /* @__PURE__ */ jsx("nav", { "aria-label": "\u8BBE\u7F6E", className: s.navigation, children: item(settingsItem) }),
|
|
115
123
|
sidebarFooter
|
|
116
124
|
] })
|
|
117
125
|
] });
|
|
118
|
-
return /* @__PURE__ */ jsxs(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
126
|
+
return /* @__PURE__ */ jsxs(
|
|
127
|
+
"div",
|
|
128
|
+
{
|
|
129
|
+
className: s.shell,
|
|
130
|
+
"data-collapsed": collapsed,
|
|
131
|
+
"data-header": showHeader,
|
|
132
|
+
children: [
|
|
133
|
+
/* @__PURE__ */ jsx("a", { className: s.skip, href: "#main", children: "\u8DF3\u8F6C\u5230\u5185\u5BB9" }),
|
|
134
|
+
/* @__PURE__ */ jsx("aside", { className: s.sidebar, children: nav }),
|
|
135
|
+
/* @__PURE__ */ jsx(
|
|
136
|
+
Drawer,
|
|
137
|
+
{
|
|
138
|
+
placement: "left",
|
|
139
|
+
size: 280,
|
|
140
|
+
open: mobile,
|
|
141
|
+
onClose: () => setMobile(false),
|
|
142
|
+
title: "\u5BFC\u822A",
|
|
143
|
+
children: /* @__PURE__ */ jsx("div", { className: s.mobileNav, children: nav })
|
|
144
|
+
}
|
|
145
|
+
),
|
|
146
|
+
/* @__PURE__ */ jsxs("div", { className: s.workspace, children: [
|
|
147
|
+
showHeader ? /* @__PURE__ */ jsx(
|
|
148
|
+
ShellHeader,
|
|
149
|
+
{
|
|
150
|
+
title,
|
|
151
|
+
collapsed,
|
|
152
|
+
onToggle: onCollapsedChange ? () => onCollapsedChange(!collapsed) : void 0,
|
|
153
|
+
onMenu: () => setMobile(true),
|
|
154
|
+
actions: headerActions,
|
|
155
|
+
windowControls,
|
|
156
|
+
draggable
|
|
157
|
+
}
|
|
158
|
+
) : /* @__PURE__ */ jsx(
|
|
159
|
+
IconButton,
|
|
160
|
+
{
|
|
161
|
+
className: `${s.mobileToggle} ${s.floatingNavigation}`,
|
|
162
|
+
label: "\u6253\u5F00\u5BFC\u822A",
|
|
163
|
+
variant: "ghost",
|
|
164
|
+
onClick: () => setMobile(true),
|
|
165
|
+
children: /* @__PURE__ */ jsx(Menu, { size: 18 })
|
|
166
|
+
}
|
|
167
|
+
),
|
|
168
|
+
/* @__PURE__ */ jsx("main", { ref: scroll, id: "main", tabIndex: -1, className: s.main, children: /* @__PURE__ */ jsx("div", { className: s.page, children }) })
|
|
169
|
+
] })
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
);
|
|
148
173
|
}
|
|
149
174
|
export {
|
|
150
175
|
AppShell,
|
|
@@ -78,12 +78,12 @@
|
|
|
78
78
|
}
|
|
79
79
|
.navigation a[aria-current="page"] {
|
|
80
80
|
background: var(--selected);
|
|
81
|
-
color: var(--accent);
|
|
81
|
+
color: var(--navigation-selected-color, var(--accent));
|
|
82
82
|
}
|
|
83
83
|
.bottom {
|
|
84
84
|
display: flex;
|
|
85
85
|
flex-direction: column;
|
|
86
|
-
gap:
|
|
86
|
+
gap: 8px;
|
|
87
87
|
margin-top: auto;
|
|
88
88
|
flex-shrink: 0;
|
|
89
89
|
}
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
align-items: center;
|
|
101
101
|
justify-content: space-between;
|
|
102
102
|
gap: 16px;
|
|
103
|
-
padding: 0
|
|
103
|
+
padding: 0 8px;
|
|
104
104
|
border-bottom: 1px solid var(--line);
|
|
105
105
|
background: var(--surface);
|
|
106
106
|
color: var(--fg);
|
|
@@ -172,6 +172,15 @@
|
|
|
172
172
|
.mobileToggle {
|
|
173
173
|
display: none;
|
|
174
174
|
}
|
|
175
|
+
.floatingNavigation {
|
|
176
|
+
position: fixed;
|
|
177
|
+
z-index: 50;
|
|
178
|
+
top: 12px;
|
|
179
|
+
left: 12px;
|
|
180
|
+
background: var(--surface);
|
|
181
|
+
border: 1px solid var(--line);
|
|
182
|
+
box-shadow: var(--shadow-sm);
|
|
183
|
+
}
|
|
175
184
|
.mobileNav {
|
|
176
185
|
display: flex;
|
|
177
186
|
flex-direction: column;
|
|
@@ -219,6 +228,9 @@
|
|
|
219
228
|
.page {
|
|
220
229
|
padding: 28px 24px 40px;
|
|
221
230
|
}
|
|
231
|
+
.shell[data-header="false"] .page {
|
|
232
|
+
padding-top: 64px;
|
|
233
|
+
}
|
|
222
234
|
}
|
|
223
235
|
@media (max-width: 540px) {
|
|
224
236
|
.page {
|
|
@@ -235,5 +247,5 @@
|
|
|
235
247
|
}
|
|
236
248
|
|
|
237
249
|
.header[data-window-controls="true"] {
|
|
238
|
-
padding-right:
|
|
250
|
+
padding-right: 8px;
|
|
239
251
|
}
|
|
@@ -55,7 +55,7 @@ function AppearanceSettings({
|
|
|
55
55
|
),
|
|
56
56
|
/* @__PURE__ */ jsxs(SettingsCard, { children: [
|
|
57
57
|
/* @__PURE__ */ jsx(SettingRow, { title: "\u5F3A\u8C03\u8272", children: /* @__PURE__ */ jsx("div", { className: s.swatches, children: [
|
|
58
|
-
settings.designTheme
|
|
58
|
+
appearanceDefaults(settings.designTheme).accent,
|
|
59
59
|
"#4479d8",
|
|
60
60
|
"#6d61bd",
|
|
61
61
|
settings.designTheme === "blue" ? "#397c67" : "#41886c",
|
package/components/Button.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { ComponentPropsWithRef } from "react";
|
|
2
|
-
|
|
2
|
+
type ButtonProps = {
|
|
3
3
|
variant?: "primary" | "secondary" | "ghost" | "danger";
|
|
4
4
|
size?: "normal" | "small";
|
|
5
5
|
loading?: boolean;
|
|
6
|
-
}
|
|
6
|
+
} & ((ComponentPropsWithRef<"button"> & {
|
|
7
|
+
href?: never;
|
|
8
|
+
}) | (ComponentPropsWithRef<"a"> & {
|
|
9
|
+
href: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}));
|
|
12
|
+
export declare function Button({ variant, size, loading, children, className, ...props }: ButtonProps): import("react").JSX.Element;
|
|
13
|
+
export {};
|
package/components/Button.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Spinner } from "./Spinner.js";
|
|
3
3
|
import s from "./Button.module.css";
|
|
4
4
|
function Button({
|
|
@@ -9,18 +9,41 @@ function Button({
|
|
|
9
9
|
className = "",
|
|
10
10
|
...props
|
|
11
11
|
}) {
|
|
12
|
-
|
|
12
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
13
|
+
/* @__PURE__ */ jsx("span", { className: s.content, style: { opacity: loading ? 0 : 1 }, children }),
|
|
14
|
+
loading && /* @__PURE__ */ jsx("span", { className: s.loader, children: /* @__PURE__ */ jsx(Spinner, { decorative: true }) })
|
|
15
|
+
] });
|
|
16
|
+
const classes = `${s.button} ${s[variant]} ${size === "small" ? s.small : ""} ${className}`;
|
|
17
|
+
if (typeof props.href === "string") {
|
|
18
|
+
const { disabled, onClick, ...link } = props;
|
|
19
|
+
const blocked = disabled || loading;
|
|
20
|
+
return /* @__PURE__ */ jsx(
|
|
21
|
+
"a",
|
|
22
|
+
{
|
|
23
|
+
...link,
|
|
24
|
+
className: classes,
|
|
25
|
+
"aria-disabled": blocked || void 0,
|
|
26
|
+
"aria-busy": loading || void 0,
|
|
27
|
+
tabIndex: blocked ? -1 : link.tabIndex,
|
|
28
|
+
onClick: (event) => {
|
|
29
|
+
if (blocked) {
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
} else onClick?.(event);
|
|
33
|
+
},
|
|
34
|
+
children: content
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return /* @__PURE__ */ jsx(
|
|
13
39
|
"button",
|
|
14
40
|
{
|
|
15
41
|
type: "button",
|
|
16
42
|
...props,
|
|
17
43
|
disabled: props.disabled || loading,
|
|
18
44
|
"aria-busy": loading || void 0,
|
|
19
|
-
className:
|
|
20
|
-
children:
|
|
21
|
-
/* @__PURE__ */ jsx("span", { className: s.content, style: { opacity: loading ? 0 : 1 }, children }),
|
|
22
|
-
loading && /* @__PURE__ */ jsx("span", { className: s.loader, children: /* @__PURE__ */ jsx(Spinner, { decorative: true }) })
|
|
23
|
-
]
|
|
45
|
+
className: classes,
|
|
46
|
+
children: content
|
|
24
47
|
}
|
|
25
48
|
);
|
|
26
49
|
}
|
|
@@ -14,21 +14,30 @@
|
|
|
14
14
|
transform var(--motion-hover),
|
|
15
15
|
border-color var(--motion-hover);
|
|
16
16
|
font-size: 12px;
|
|
17
|
+
text-decoration: none;
|
|
18
|
+
color: var(--fg);
|
|
17
19
|
}
|
|
18
|
-
.button
|
|
20
|
+
.button[aria-disabled="true"] {
|
|
21
|
+
opacity: 0.5;
|
|
22
|
+
cursor: not-allowed;
|
|
23
|
+
}
|
|
24
|
+
.button:hover:not(:disabled):not([aria-disabled="true"]) {
|
|
19
25
|
background: var(--hover);
|
|
20
26
|
border-color: color-mix(in srgb, var(--fg) 18%, var(--surface));
|
|
21
27
|
}
|
|
22
|
-
.button:active:not(:disabled) {
|
|
28
|
+
.button:active:not(:disabled):not([aria-disabled="true"]) {
|
|
23
29
|
transform: translateY(1px);
|
|
24
30
|
}
|
|
25
31
|
.primary {
|
|
26
|
-
background: var(--accent);
|
|
27
|
-
border-color: var(--accent);
|
|
28
|
-
color: var(--on-accent);
|
|
32
|
+
background: var(--accent-fill, var(--accent));
|
|
33
|
+
border-color: var(--accent-fill, var(--accent));
|
|
34
|
+
color: var(--on-accent-fill, var(--on-accent));
|
|
29
35
|
}
|
|
30
|
-
.primary:hover:not(:disabled) {
|
|
31
|
-
background:
|
|
36
|
+
.primary:hover:not(:disabled):not([aria-disabled="true"]) {
|
|
37
|
+
background: var(
|
|
38
|
+
--accent-fill-hover,
|
|
39
|
+
color-mix(in srgb, var(--accent) 86%, var(--surface))
|
|
40
|
+
);
|
|
32
41
|
border-color: transparent;
|
|
33
42
|
}
|
|
34
43
|
.secondary {
|
package/components/Calendar.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ export type CalendarProps = DateConstraints & {
|
|
|
6
6
|
range?: DateRange;
|
|
7
7
|
preview?: string;
|
|
8
8
|
onHover?(value: string): void;
|
|
9
|
+
today?: string;
|
|
9
10
|
};
|
|
10
|
-
export declare function Calendar({ value, onChange, range, preview, onHover, minDate, maxDate, disabledDate, }: CalendarProps): import("react").JSX.Element;
|
|
11
|
+
export declare function Calendar({ value, onChange, range, preview, onHover, minDate, maxDate, disabledDate, today, }: CalendarProps): import("react").JSX.Element;
|
package/components/Calendar.js
CHANGED
|
@@ -20,14 +20,15 @@ function Calendar({
|
|
|
20
20
|
onHover,
|
|
21
21
|
minDate,
|
|
22
22
|
maxDate,
|
|
23
|
-
disabledDate
|
|
23
|
+
disabledDate,
|
|
24
|
+
today = dateKey(/* @__PURE__ */ new Date())
|
|
24
25
|
}) {
|
|
25
26
|
const constraints = { minDate, maxDate, disabledDate };
|
|
26
27
|
const [month, setMonth] = useState(
|
|
27
|
-
() => readDate(boundedDate(value, constraints))
|
|
28
|
+
() => readDate(boundedDate(value || today, constraints))
|
|
28
29
|
), [view, setView] = useState("days"), [focused, setFocused] = useState(value);
|
|
29
30
|
const grid = useRef(null), choices = useRef(null), pending = useRef(null), pendingView = useRef(false);
|
|
30
|
-
const year = month.getFullYear(), index = month.getMonth(), start = (calendarDate(year, index).getDay() + 6) % 7, base = Math.floor(year / 12) * 12
|
|
31
|
+
const year = month.getFullYear(), index = month.getMonth(), start = (calendarDate(year, index).getDay() + 6) % 7, base = Math.floor(year / 12) * 12;
|
|
31
32
|
const allowed = (key) => isDateAllowed(key, constraints);
|
|
32
33
|
const monthAllowed = (y, m) => y >= 1 && y <= 9999 && !(minDate && parseDate(minDate) && dateKey(calendarDate(y, m + 1, 0)) < minDate) && !(maxDate && parseDate(maxDate) && dateKey(calendarDate(y, m)) > maxDate);
|
|
33
34
|
const periodAllowed = (amount) => {
|
package/components/Card.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
export declare function Card({ title, action, children, className, }: {
|
|
2
|
+
export declare function Card({ title, action, children, className, onClick, }: {
|
|
3
3
|
title?: string;
|
|
4
4
|
action?: ReactNode;
|
|
5
5
|
children: ReactNode;
|
|
6
6
|
className?: string;
|
|
7
|
+
onClick?(): void;
|
|
7
8
|
}): import("react").JSX.Element;
|
package/components/Card.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import s from "./Card.module.css";
|
|
3
3
|
function Card({
|
|
4
4
|
title,
|
|
5
5
|
action,
|
|
6
6
|
children,
|
|
7
|
-
className = ""
|
|
7
|
+
className = "",
|
|
8
|
+
onClick
|
|
8
9
|
}) {
|
|
9
|
-
|
|
10
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10
11
|
(title || action) && /* @__PURE__ */ jsxs("header", { className: s.header, children: [
|
|
11
12
|
/* @__PURE__ */ jsx("h2", { children: title }),
|
|
12
13
|
action
|
|
13
14
|
] }),
|
|
14
15
|
/* @__PURE__ */ jsx("div", { className: s.body, "data-card-body": true, children })
|
|
15
16
|
] });
|
|
17
|
+
return onClick ? /* @__PURE__ */ jsx(
|
|
18
|
+
"button",
|
|
19
|
+
{
|
|
20
|
+
type: "button",
|
|
21
|
+
className: `${s.card} ${s.interactive} ${className}`,
|
|
22
|
+
onClick,
|
|
23
|
+
children: content
|
|
24
|
+
}
|
|
25
|
+
) : /* @__PURE__ */ jsx("section", { className: s.card + " " + className, children: content });
|
|
16
26
|
}
|
|
17
27
|
export {
|
|
18
28
|
Card
|
|
@@ -5,6 +5,25 @@
|
|
|
5
5
|
min-width: 0;
|
|
6
6
|
box-shadow: var(--card-shadow, none);
|
|
7
7
|
}
|
|
8
|
+
.interactive {
|
|
9
|
+
display: block;
|
|
10
|
+
width: 100%;
|
|
11
|
+
padding: 0;
|
|
12
|
+
color: var(--fg);
|
|
13
|
+
font: inherit;
|
|
14
|
+
text-align: left;
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
transition:
|
|
17
|
+
box-shadow var(--motion-hover),
|
|
18
|
+
transform var(--motion-hover);
|
|
19
|
+
}
|
|
20
|
+
.interactive:hover {
|
|
21
|
+
box-shadow: var(--shadow);
|
|
22
|
+
transform: translateY(-2px);
|
|
23
|
+
}
|
|
24
|
+
.interactive:active {
|
|
25
|
+
transform: none;
|
|
26
|
+
}
|
|
8
27
|
.header {
|
|
9
28
|
display: flex;
|
|
10
29
|
align-items: center;
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
transform: scale(0.92);
|
|
55
55
|
}
|
|
56
56
|
.checkbox input:checked + .checkMark {
|
|
57
|
-
background: var(--accent);
|
|
58
|
-
color: var(--on-accent);
|
|
59
|
-
border-color: var(--accent);
|
|
57
|
+
background: var(--accent-fill, var(--accent));
|
|
58
|
+
color: var(--on-accent-fill, var(--on-accent));
|
|
59
|
+
border-color: var(--accent-fill, var(--accent));
|
|
60
60
|
}
|
|
61
61
|
.checkbox input:checked + .checkMark path {
|
|
62
62
|
stroke-dashoffset: 0;
|
package/components/Combobox.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FieldOptions } from "./field";
|
|
2
2
|
import type { SelectOption } from "./types";
|
|
3
|
-
export
|
|
3
|
+
export type ComboboxProps = FieldOptions & {
|
|
4
4
|
value: string;
|
|
5
5
|
onChange(value: string): void;
|
|
6
6
|
options?: SelectOption[];
|
|
@@ -8,4 +8,6 @@ export declare function Combobox({ value, onChange, options, loadOptions, onCrea
|
|
|
8
8
|
onCreate?(label: string): string | void | Promise<string | void>;
|
|
9
9
|
label?: string;
|
|
10
10
|
placeholder?: string;
|
|
11
|
-
|
|
11
|
+
mode?: "panel" | "input";
|
|
12
|
+
};
|
|
13
|
+
export declare function Combobox({ value, onChange, options, loadOptions, onCreate, label, placeholder, mode, ...props }: ComboboxProps): import("react").JSX.Element;
|