@robert-stackflow/oneui 0.2.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 +10 -0
- package/README.md +30 -1
- package/components/AppShell.d.ts +11 -4
- package/components/AppShell.js +79 -54
- package/components/AppShell.module.css +13 -1
- package/components/Calendar.d.ts +2 -1
- package/components/Calendar.js +4 -3
- package/components/DatePicker.d.ts +3 -0
- package/components/DatePicker.js +41 -21
- package/components/DatePicker.module.css +3 -0
- package/components/IconPicker.d.ts +14 -4
- package/components/IconPicker.js +67 -25
- package/components/ToastProvider.d.ts +4 -4
- package/components/ToastProvider.js +19 -22
- package/components/VirtualPickerList.d.ts +2 -0
- package/components/VirtualPickerList.js +1 -1
- 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/index.d.ts +3 -2
- package/components/index.js +7 -1
- package/package.json +9 -4
- package/styles/minimal.css +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
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
|
+
|
|
3
13
|
## 0.2.0 — 2026-10-28
|
|
4
14
|
|
|
5
15
|
- 新增 MessageDialog、ConfirmDialog、FormDialog 与统一 LoadingState,复用表单校验、异步提交、错误反馈和焦点管理。
|
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ export function App() {
|
|
|
45
45
|
- 页面直接复用 Form / FormField / useFormState 的校验与提交状态、Alert 的错误反馈和 ConfirmDialog 的确认流程;避免另写原生表单、按钮样式或错误提示。
|
|
46
46
|
- Button 支持 `href`,渲染标准链接并沿用按钮样式;禁用或加载时阻止导航。Card 传入 `onClick` 后渲染可通过键盘激活的按钮卡片。导航与业务回调由消费应用提供。
|
|
47
47
|
- CSS Modules 随组件分发,由消费项目的构建工具处理。图标/字符目录通过标准 `new URL(..., import.meta.url)` 加载随包资源。
|
|
48
|
-
- 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
49
|
|
|
50
50
|
## 弹窗与加载状态
|
|
51
51
|
|
|
@@ -66,6 +66,35 @@ export function App() {
|
|
|
66
66
|
|
|
67
67
|
菜单项间距统一为 `--menu-item-spacing: 1px`,菜单快捷键为靠右的纯文字提示,不预留空的指示器位置。Desktop 与 Web 共用示例:`/pickers?view=hierarchy`、`/forms?view=advanced`、`/data?view=hierarchy`;层级表格中的增删改仅更新示例内存数据。以上扩展从 0.2.0 起提供。
|
|
68
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)与未保存状态属于宿主逻辑。
|
|
97
|
+
|
|
69
98
|
## 构建与打包
|
|
70
99
|
|
|
71
100
|
在仓库根目录运行 `npm run pack:ui`。源码工作区供两端即时开发使用;发布内容单独生成到本目录 `dist`,含 ESM、TypeScript 声明、CSS 和资源。构建使用干净的临时目录并检查所有包内引用,成功后替换 dist,避免旧文件混入。`dist/package.json` 只导出生成内容。
|
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,7 +78,7 @@
|
|
|
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;
|
|
@@ -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 {
|
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) => {
|
|
@@ -8,6 +8,9 @@ export type SingleDatePickerProps = FieldOptions & DateConstraints & {
|
|
|
8
8
|
label: string;
|
|
9
9
|
mode?: Exclude<DatePickerMode, "range">;
|
|
10
10
|
seconds?: boolean;
|
|
11
|
+
timeZone?: string;
|
|
12
|
+
clearable?: boolean;
|
|
13
|
+
placeholder?: string;
|
|
11
14
|
};
|
|
12
15
|
export type DatePickerProps = SingleDatePickerProps | (DateRangePickerProps & {
|
|
13
16
|
mode: "range";
|
package/components/DatePicker.js
CHANGED
|
@@ -5,7 +5,7 @@ import { useId, useState } from "react";
|
|
|
5
5
|
import { CalendarDays, Clock3 } from "lucide-react";
|
|
6
6
|
import { Popover } from "./Popover.js";
|
|
7
7
|
import { usePopover } from "./usePopover.js";
|
|
8
|
-
import { Calendar
|
|
8
|
+
import { Calendar } from "./Calendar.js";
|
|
9
9
|
import { TimeColumns } from "./TimeColumns.js";
|
|
10
10
|
import { Button } from "./Button.js";
|
|
11
11
|
import s from "./DatePicker.module.css";
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
isDateAllowed
|
|
15
15
|
} from "./dateConstraints.js";
|
|
16
16
|
import { DateRangePicker } from "./DateRangePicker.js";
|
|
17
|
+
import { calendarNow } from "./calendarNow.js";
|
|
17
18
|
function DatePicker(props) {
|
|
18
19
|
return props.mode === "range" ? /* @__PURE__ */ jsx(DateRangePicker, { ...props }) : /* @__PURE__ */ jsx(SingleDatePicker, { ...props });
|
|
19
20
|
}
|
|
@@ -23,6 +24,9 @@ function SingleDatePicker({
|
|
|
23
24
|
label,
|
|
24
25
|
mode = "date",
|
|
25
26
|
seconds = false,
|
|
27
|
+
timeZone,
|
|
28
|
+
clearable = false,
|
|
29
|
+
placeholder = "\u8BF7\u9009\u62E9",
|
|
26
30
|
disabled,
|
|
27
31
|
readOnly,
|
|
28
32
|
name,
|
|
@@ -47,7 +51,8 @@ function SingleDatePicker({
|
|
|
47
51
|
hint,
|
|
48
52
|
...aria
|
|
49
53
|
});
|
|
50
|
-
const
|
|
54
|
+
const now = () => calendarNow(timeZone), today = now().date;
|
|
55
|
+
const pop = usePopover(field.disabled || field.readOnly), id = useId(), [date, setDate] = useState(value.slice(0, 10) || today), [time, setTime] = useState("00:00");
|
|
51
56
|
const close = pop.closeAndFocus;
|
|
52
57
|
const show = () => {
|
|
53
58
|
if (field.disabled || field.readOnly) return;
|
|
@@ -56,7 +61,7 @@ function SingleDatePicker({
|
|
|
56
61
|
return;
|
|
57
62
|
}
|
|
58
63
|
setDate(
|
|
59
|
-
mode === "time" ?
|
|
64
|
+
mode === "time" ? now().date : boundedDate(value.slice(0, 10) || now().date, constraints)
|
|
60
65
|
);
|
|
61
66
|
setTime(
|
|
62
67
|
(mode === "time" ? value : value.split(/[T ]/)[1]) || (seconds ? "00:00:00" : "00:00")
|
|
@@ -82,6 +87,7 @@ function SingleDatePicker({
|
|
|
82
87
|
"aria-expanded": pop.open,
|
|
83
88
|
"aria-haspopup": "dialog",
|
|
84
89
|
"aria-controls": id,
|
|
90
|
+
"data-placeholder": !value || void 0,
|
|
85
91
|
onClick: show,
|
|
86
92
|
onKeyDown: (e) => {
|
|
87
93
|
if (e.key === "ArrowDown" && !pop.open) {
|
|
@@ -90,7 +96,7 @@ function SingleDatePicker({
|
|
|
90
96
|
}
|
|
91
97
|
},
|
|
92
98
|
children: [
|
|
93
|
-
/* @__PURE__ */ jsx("span", { children: value.replace("T", " ") ||
|
|
99
|
+
/* @__PURE__ */ jsx("span", { children: value.replace("T", " ") || placeholder }),
|
|
94
100
|
mode === "time" ? /* @__PURE__ */ jsx(Clock3, {}) : /* @__PURE__ */ jsx(CalendarDays, {})
|
|
95
101
|
]
|
|
96
102
|
}
|
|
@@ -119,6 +125,7 @@ function SingleDatePicker({
|
|
|
119
125
|
Calendar,
|
|
120
126
|
{
|
|
121
127
|
...constraints,
|
|
128
|
+
today,
|
|
122
129
|
key: pop.open ? "open" : "closed",
|
|
123
130
|
value: date,
|
|
124
131
|
onChange: (next) => {
|
|
@@ -143,16 +150,14 @@ function SingleDatePicker({
|
|
|
143
150
|
] }),
|
|
144
151
|
/* @__PURE__ */ jsxs("footer", { className: s.footer, children: [
|
|
145
152
|
/* @__PURE__ */ jsx(
|
|
146
|
-
|
|
153
|
+
Button,
|
|
147
154
|
{
|
|
155
|
+
variant: "ghost",
|
|
156
|
+
size: "small",
|
|
148
157
|
type: "button",
|
|
149
|
-
disabled: mode !== "time" && !isDateAllowed(
|
|
158
|
+
disabled: mode !== "time" && !isDateAllowed(today, constraints),
|
|
150
159
|
onClick: () => {
|
|
151
|
-
const
|
|
152
|
-
now.getHours(),
|
|
153
|
-
now.getMinutes(),
|
|
154
|
-
...seconds ? [now.getSeconds()] : []
|
|
155
|
-
].map((v) => String(v).padStart(2, "0")).join(":");
|
|
160
|
+
const current = now(), nextDate = current.date, nextTime = current.time.slice(0, seconds ? 8 : 5);
|
|
156
161
|
if (mode !== "time" && !isDateAllowed(nextDate, constraints))
|
|
157
162
|
return;
|
|
158
163
|
setDate(nextDate);
|
|
@@ -165,16 +170,31 @@ function SingleDatePicker({
|
|
|
165
170
|
children: mode === "date" ? "\u4ECA\u5929" : "\u6B64\u523B"
|
|
166
171
|
}
|
|
167
172
|
),
|
|
168
|
-
mode !== "date" && /* @__PURE__ */
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
173
|
+
(clearable || mode !== "date") && /* @__PURE__ */ jsxs("div", { className: s.rangeActions, children: [
|
|
174
|
+
clearable && /* @__PURE__ */ jsx(
|
|
175
|
+
Button,
|
|
176
|
+
{
|
|
177
|
+
size: "small",
|
|
178
|
+
variant: "ghost",
|
|
179
|
+
disabled: !value,
|
|
180
|
+
onClick: () => {
|
|
181
|
+
onChange("");
|
|
182
|
+
close();
|
|
183
|
+
},
|
|
184
|
+
children: "\u6E05\u7A7A"
|
|
185
|
+
}
|
|
186
|
+
),
|
|
187
|
+
mode !== "date" && /* @__PURE__ */ jsx(
|
|
188
|
+
Button,
|
|
189
|
+
{
|
|
190
|
+
variant: "primary",
|
|
191
|
+
size: "small",
|
|
192
|
+
onClick: commit,
|
|
193
|
+
disabled: mode !== "time" && !isDateAllowed(date, constraints),
|
|
194
|
+
children: "\u786E\u5B9A"
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
] })
|
|
178
198
|
] })
|
|
179
199
|
]
|
|
180
200
|
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type FieldOptions } from "./field";
|
|
3
|
+
export type IconPickerOption = {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
icon: ReactNode;
|
|
7
|
+
keywords?: string[];
|
|
8
|
+
category?: string;
|
|
9
|
+
};
|
|
10
|
+
export type IconPickerProps = FieldOptions & {
|
|
2
11
|
value: string;
|
|
3
12
|
onChange(value: string): void;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
13
|
+
options?: IconPickerOption[];
|
|
14
|
+
valuePrefix?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function IconPicker({ value, onChange, label, options, valuePrefix, ...props }: IconPickerProps): import("react").JSX.Element;
|
package/components/IconPicker.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useMemo,
|
|
2
|
+
import { useCallback, useMemo, useState } from "react";
|
|
3
3
|
import { Popover } from "./Popover.js";
|
|
4
4
|
import { SearchField } from "./SearchField.js";
|
|
5
5
|
import { CatalogIcon } from "./CatalogIcon.js";
|
|
@@ -10,16 +10,48 @@ import {
|
|
|
10
10
|
VirtualPickerList
|
|
11
11
|
} from "./VirtualPickerList.js";
|
|
12
12
|
import { useIconCatalog, iconCategories } from "./iconCatalog.js";
|
|
13
|
+
import { usePopover } from "./usePopover.js";
|
|
14
|
+
import { useFieldControl, fieldTriggerProps } from "./field.js";
|
|
13
15
|
import s from "./Picker.module.css";
|
|
14
16
|
function IconPicker({
|
|
15
17
|
value,
|
|
16
18
|
onChange,
|
|
17
|
-
label
|
|
18
|
-
|
|
19
|
+
label,
|
|
20
|
+
options,
|
|
21
|
+
valuePrefix = "",
|
|
22
|
+
...props
|
|
19
23
|
}) {
|
|
20
|
-
const
|
|
21
|
-
const
|
|
24
|
+
const field = useFieldControl({ ...props, label }), pickerLabel = field.label || "\u56FE\u6807", blocked = field.disabled || field.readOnly, pop = usePopover(blocked), [query, setQuery] = useState("");
|
|
25
|
+
const name = valuePrefix && value.startsWith(valuePrefix) ? value.slice(valuePrefix.length) : value;
|
|
26
|
+
const selected = options?.find((option) => option.value === value);
|
|
27
|
+
const { items, error, retry } = useIconCatalog(
|
|
28
|
+
pop.open && options === void 0
|
|
29
|
+
);
|
|
22
30
|
const groups = useMemo(() => {
|
|
31
|
+
if (options) {
|
|
32
|
+
const categories = /* @__PURE__ */ new Map();
|
|
33
|
+
for (const option of options) {
|
|
34
|
+
const category = option.category || "\u5168\u90E8";
|
|
35
|
+
if (!categories.has(category))
|
|
36
|
+
categories.set(category, {
|
|
37
|
+
id: category,
|
|
38
|
+
label: category,
|
|
39
|
+
items: []
|
|
40
|
+
});
|
|
41
|
+
categories.get(category).items.push({
|
|
42
|
+
id: option.value,
|
|
43
|
+
label: option.label,
|
|
44
|
+
content: option.icon,
|
|
45
|
+
search: [
|
|
46
|
+
option.value,
|
|
47
|
+
option.label,
|
|
48
|
+
category,
|
|
49
|
+
...option.keywords || []
|
|
50
|
+
].join(" ").toLowerCase()
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return [...categories.values()];
|
|
54
|
+
}
|
|
23
55
|
if (!items) return [];
|
|
24
56
|
const indexed = items.map((icon) => ({
|
|
25
57
|
id: icon.name,
|
|
@@ -40,43 +72,53 @@ function IconPicker({
|
|
|
40
72
|
)
|
|
41
73
|
})
|
|
42
74
|
).filter((group) => group.items.length);
|
|
43
|
-
}, [items]);
|
|
75
|
+
}, [items, options]);
|
|
44
76
|
const choose = useCallback(
|
|
45
77
|
(item) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
78
|
+
if (blocked) return;
|
|
79
|
+
onChange(options ? item.id : valuePrefix + item.id);
|
|
80
|
+
pop.closeAndFocus();
|
|
49
81
|
},
|
|
50
|
-
[onChange]
|
|
82
|
+
[onChange, blocked, options, valuePrefix, pop.closeAndFocus]
|
|
51
83
|
);
|
|
52
84
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
53
85
|
/* @__PURE__ */ jsx(
|
|
54
86
|
SelectTrigger,
|
|
55
87
|
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
88
|
+
...fieldTriggerProps(field),
|
|
89
|
+
"aria-label": pickerLabel,
|
|
90
|
+
ref: pop.anchor,
|
|
91
|
+
expanded: pop.open,
|
|
92
|
+
"data-readonly": field.readOnly || void 0,
|
|
60
93
|
"aria-haspopup": "dialog",
|
|
61
|
-
onClick:
|
|
94
|
+
onClick: pop.toggle,
|
|
62
95
|
onKeyDown: (e) => {
|
|
63
96
|
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
64
97
|
e.preventDefault();
|
|
65
|
-
|
|
98
|
+
pop.show();
|
|
66
99
|
}
|
|
67
100
|
},
|
|
68
|
-
icon: /* @__PURE__ */ jsx(CatalogIcon, { name
|
|
69
|
-
children: /* @__PURE__ */ jsx("span", { children:
|
|
101
|
+
icon: options ? selected?.icon : /* @__PURE__ */ jsx(CatalogIcon, { name }),
|
|
102
|
+
children: /* @__PURE__ */ jsx("span", { children: selected?.label || name || "\u9009\u62E9\u56FE\u6807" })
|
|
103
|
+
}
|
|
104
|
+
),
|
|
105
|
+
field.name && /* @__PURE__ */ jsx(
|
|
106
|
+
"input",
|
|
107
|
+
{
|
|
108
|
+
type: "hidden",
|
|
109
|
+
name: field.name,
|
|
110
|
+
value,
|
|
111
|
+
disabled: field.disabled
|
|
70
112
|
}
|
|
71
113
|
),
|
|
72
114
|
/* @__PURE__ */ jsx(
|
|
73
115
|
Popover,
|
|
74
116
|
{
|
|
75
|
-
open,
|
|
76
|
-
onClose:
|
|
77
|
-
anchor,
|
|
117
|
+
open: pop.open,
|
|
118
|
+
onClose: pop.close,
|
|
119
|
+
anchor: pop.anchor,
|
|
78
120
|
width: 470,
|
|
79
|
-
label:
|
|
121
|
+
label: pickerLabel + "\u9009\u62E9\u5668",
|
|
80
122
|
children: /* @__PURE__ */ jsxs("div", { className: s.panel, children: [
|
|
81
123
|
/* @__PURE__ */ jsx(
|
|
82
124
|
SearchField,
|
|
@@ -87,18 +129,18 @@ function IconPicker({
|
|
|
87
129
|
onChange: setQuery
|
|
88
130
|
}
|
|
89
131
|
),
|
|
90
|
-
error ? /* @__PURE__ */ jsx(Button, { onClick: retry, children: "\u91CD\u65B0\u52A0\u8F7D" }) : !items ? /* @__PURE__ */ jsx("div", { className: s.loading, children: /* @__PURE__ */ jsx(Spinner, { label: "\u52A0\u8F7D\u56FE\u6807" }) }) : /* @__PURE__ */ jsx(
|
|
132
|
+
options === void 0 && error ? /* @__PURE__ */ jsx(Button, { onClick: retry, children: "\u91CD\u65B0\u52A0\u8F7D" }) : options === void 0 && !items ? /* @__PURE__ */ jsx("div", { className: s.loading, children: /* @__PURE__ */ jsx(Spinner, { label: "\u52A0\u8F7D\u56FE\u6807" }) }) : /* @__PURE__ */ jsx(
|
|
91
133
|
VirtualPickerList,
|
|
92
134
|
{
|
|
93
135
|
groups,
|
|
94
136
|
query,
|
|
95
|
-
value,
|
|
137
|
+
value: options ? value : name,
|
|
96
138
|
label: "\u56FE\u6807",
|
|
97
139
|
onChoose: choose
|
|
98
140
|
}
|
|
99
141
|
),
|
|
100
142
|
/* @__PURE__ */ jsx("div", { className: s.footer, children: /* @__PURE__ */ jsxs("span", { children: [
|
|
101
|
-
items?.length
|
|
143
|
+
options?.length ?? items?.length ?? 0,
|
|
102
144
|
" \u4E2A\u56FE\u6807"
|
|
103
145
|
] }) })
|
|
104
146
|
] })
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
+
import { type ToastController, type ToastNotify } from "./createToaster";
|
|
2
3
|
export type ToastPosition = "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
|
3
4
|
export type ToastTone = "neutral" | "success" | "info" | "warning" | "error";
|
|
4
5
|
export type ToastAction = {
|
|
@@ -27,9 +28,8 @@ export type ToastMessage = {
|
|
|
27
28
|
options?: ToastOptions;
|
|
28
29
|
closing?: boolean;
|
|
29
30
|
};
|
|
30
|
-
|
|
31
|
-
export declare function ToastProvider({ children }: {
|
|
31
|
+
export declare function ToastProvider({ children, controller, }: {
|
|
32
32
|
children: ReactNode;
|
|
33
|
+
controller?: ToastController;
|
|
33
34
|
}): import("react").JSX.Element;
|
|
34
|
-
export declare const useToast: () =>
|
|
35
|
-
export {};
|
|
35
|
+
export declare const useToast: () => ToastNotify;
|
|
@@ -1,42 +1,39 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import {
|
|
3
3
|
createContext,
|
|
4
|
-
useCallback,
|
|
5
4
|
useContext,
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
useState,
|
|
6
|
+
useSyncExternalStore
|
|
8
7
|
} from "react";
|
|
9
8
|
import { useUIConfig } from "./UIProvider.js";
|
|
10
9
|
import { Toast } from "./Toast.js";
|
|
10
|
+
import {
|
|
11
|
+
createToaster
|
|
12
|
+
} from "./createToaster.js";
|
|
11
13
|
const Context = createContext(() => () => {
|
|
12
14
|
});
|
|
13
|
-
|
|
15
|
+
const serverSnapshot = () => null;
|
|
16
|
+
function ToastProvider({
|
|
17
|
+
children,
|
|
18
|
+
controller
|
|
19
|
+
}) {
|
|
14
20
|
const config = useUIConfig();
|
|
15
|
-
const [
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
id,
|
|
21
|
-
options: typeof options === "string" ? { tone: options } : options
|
|
22
|
-
});
|
|
23
|
-
return () => setToast(
|
|
24
|
-
(current) => current?.id === id ? { ...current, closing: true } : current
|
|
25
|
-
);
|
|
26
|
-
}, []);
|
|
27
|
-
const clear = useCallback(
|
|
28
|
-
(id) => setToast((current) => current?.id === id ? null : current),
|
|
29
|
-
[]
|
|
21
|
+
const [local] = useState(createToaster), source = controller || local;
|
|
22
|
+
const toast = useSyncExternalStore(
|
|
23
|
+
source.subscribe,
|
|
24
|
+
source.getSnapshot,
|
|
25
|
+
serverSnapshot
|
|
30
26
|
);
|
|
31
|
-
return /* @__PURE__ */ jsxs(Context, { value: notify, children: [
|
|
27
|
+
return /* @__PURE__ */ jsxs(Context, { value: source.notify, children: [
|
|
32
28
|
children,
|
|
33
29
|
/* @__PURE__ */ jsx(
|
|
34
30
|
Toast,
|
|
35
31
|
{
|
|
36
32
|
toast,
|
|
37
33
|
position: toast?.options?.position || config.toastPosition,
|
|
38
|
-
onDismiss: clear
|
|
39
|
-
}
|
|
34
|
+
onDismiss: source.clear
|
|
35
|
+
},
|
|
36
|
+
source.id
|
|
40
37
|
)
|
|
41
38
|
] });
|
|
42
39
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
import { type LucideIconData } from "lucide-react";
|
|
2
3
|
export type PickerCatalogItem = {
|
|
3
4
|
id: string;
|
|
@@ -5,6 +6,7 @@ export type PickerCatalogItem = {
|
|
|
5
6
|
search: string;
|
|
6
7
|
glyph?: string;
|
|
7
8
|
icon?: LucideIconData;
|
|
9
|
+
content?: ReactNode;
|
|
8
10
|
variants?: {
|
|
9
11
|
glyph: string;
|
|
10
12
|
label: string;
|
|
@@ -44,7 +44,7 @@ const Cell = memo(function Cell2({
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
children: [
|
|
47
|
-
item.icon ? /* @__PURE__ */ jsx(Icon, { icon: item.icon }) : item.glyph,
|
|
47
|
+
item.content !== void 0 ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: item.content }) : item.icon ? /* @__PURE__ */ jsx(Icon, { icon: item.icon }) : item.glyph,
|
|
48
48
|
item.variants && item.variants.length > 1 && /* @__PURE__ */ jsx("span", { className: s.variantMark, "aria-hidden": "true" })
|
|
49
49
|
]
|
|
50
50
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { dateKey } from "./dateConstraints.js";
|
|
2
|
+
function calendarNow(timeZone, instant = /* @__PURE__ */ new Date()) {
|
|
3
|
+
if (!timeZone)
|
|
4
|
+
return {
|
|
5
|
+
date: dateKey(instant),
|
|
6
|
+
time: [instant.getHours(), instant.getMinutes(), instant.getSeconds()].map((part2) => String(part2).padStart(2, "0")).join(":")
|
|
7
|
+
};
|
|
8
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
9
|
+
timeZone,
|
|
10
|
+
numberingSystem: "latn",
|
|
11
|
+
year: "numeric",
|
|
12
|
+
month: "2-digit",
|
|
13
|
+
day: "2-digit",
|
|
14
|
+
hour: "2-digit",
|
|
15
|
+
minute: "2-digit",
|
|
16
|
+
second: "2-digit",
|
|
17
|
+
hourCycle: "h23"
|
|
18
|
+
}).formatToParts(instant);
|
|
19
|
+
const part = (type) => parts.find((item) => item.type === type).value;
|
|
20
|
+
return {
|
|
21
|
+
date: part("year").padStart(4, "0") + "-" + part("month") + "-" + part("day"),
|
|
22
|
+
time: part("hour") + ":" + part("minute") + ":" + part("second")
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
calendarNow
|
|
27
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ToastMessage, ToastOptions, ToastTone } from "./ToastProvider";
|
|
2
|
+
export type ToastNotify = (message: string, options?: ToastOptions | ToastTone) => () => void;
|
|
3
|
+
export interface ToastController {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
notify: ToastNotify;
|
|
6
|
+
dismiss(id?: number): void;
|
|
7
|
+
clear(id?: number): void;
|
|
8
|
+
subscribe(listener: () => void): () => void;
|
|
9
|
+
getSnapshot(): ToastMessage | null;
|
|
10
|
+
}
|
|
11
|
+
/** An isolated notification service; no React or document access is required. */
|
|
12
|
+
export declare function createToaster(): ToastController;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
let nextController = 0;
|
|
2
|
+
function createToaster() {
|
|
3
|
+
let current = null, counter = 0;
|
|
4
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
const publish = (message) => {
|
|
6
|
+
if (current === message) return;
|
|
7
|
+
current = message;
|
|
8
|
+
listeners.forEach((listener) => listener());
|
|
9
|
+
};
|
|
10
|
+
const dismiss = (id = current?.id) => {
|
|
11
|
+
if (current && current.id === id && !current.closing)
|
|
12
|
+
publish({ ...current, closing: true });
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
id: "oneui-toast-" + ++nextController,
|
|
16
|
+
notify(message, options) {
|
|
17
|
+
const id = ++counter;
|
|
18
|
+
publish({
|
|
19
|
+
message,
|
|
20
|
+
id,
|
|
21
|
+
options: typeof options === "string" ? { tone: options } : options
|
|
22
|
+
});
|
|
23
|
+
return () => dismiss(id);
|
|
24
|
+
},
|
|
25
|
+
dismiss,
|
|
26
|
+
clear(id = current?.id) {
|
|
27
|
+
if (current?.id === id) publish(null);
|
|
28
|
+
},
|
|
29
|
+
subscribe(listener) {
|
|
30
|
+
listeners.add(listener);
|
|
31
|
+
return () => {
|
|
32
|
+
listeners.delete(listener);
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
getSnapshot: () => current
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
createToaster
|
|
40
|
+
};
|
package/components/index.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export { CharacterPicker } from "./CharacterPicker";
|
|
|
47
47
|
export { DataTable } from "./DataTable";
|
|
48
48
|
export { EmojiPicker } from "./EmojiPicker";
|
|
49
49
|
export { FileTree } from "./FileTree";
|
|
50
|
-
export { IconPicker } from "./IconPicker";
|
|
50
|
+
export { IconPicker, type IconPickerProps, type IconPickerOption, } from "./IconPicker";
|
|
51
51
|
export { LoadingOverlay } from "./LoadingOverlay";
|
|
52
52
|
export { LoadingState } from "./LoadingState";
|
|
53
53
|
export { Pagination } from "./Pagination";
|
|
@@ -143,10 +143,11 @@ export { ThemeProvider } from "./ThemeProvider";
|
|
|
143
143
|
export type { UITheme, ColorMode } from "./ThemeProvider";
|
|
144
144
|
export { ThemeStylePicker } from "./ThemeStylePicker";
|
|
145
145
|
export { AppShell, ShellHeader } from "./AppShell";
|
|
146
|
-
export type { NavigationItem } from "./AppShell";
|
|
146
|
+
export type { NavigationItem, AppShellProps } from "./AppShell";
|
|
147
147
|
export { SettingsLayout, SettingsCard } from "./SettingsLayout";
|
|
148
148
|
export { AppearanceSettings } from "./AppearanceSettings";
|
|
149
149
|
export { appearanceDefaults, normalizeAppearance } from "./appearance";
|
|
150
150
|
export type { AppearancePreferences } from "./appearance";
|
|
151
151
|
export { MessageDialog, ConfirmDialog, FormDialog } from "./DialogVariants";
|
|
152
152
|
export { TreeTable, type TreeTableProps } from "./TreeTable";
|
|
153
|
+
export { createToaster, type ToastController, type ToastNotify, } from "./createToaster";
|
package/components/index.js
CHANGED
|
@@ -43,7 +43,9 @@ import { CharacterPicker } from "./CharacterPicker.js";
|
|
|
43
43
|
import { DataTable } from "./DataTable.js";
|
|
44
44
|
import { EmojiPicker } from "./EmojiPicker.js";
|
|
45
45
|
import { FileTree } from "./FileTree.js";
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
IconPicker
|
|
48
|
+
} from "./IconPicker.js";
|
|
47
49
|
import { LoadingOverlay } from "./LoadingOverlay.js";
|
|
48
50
|
import { LoadingState } from "./LoadingState.js";
|
|
49
51
|
import { Pagination } from "./Pagination.js";
|
|
@@ -106,6 +108,9 @@ import { AppearanceSettings } from "./AppearanceSettings.js";
|
|
|
106
108
|
import { appearanceDefaults, normalizeAppearance } from "./appearance.js";
|
|
107
109
|
import { MessageDialog, ConfirmDialog, FormDialog } from "./DialogVariants.js";
|
|
108
110
|
import { TreeTable } from "./TreeTable.js";
|
|
111
|
+
import {
|
|
112
|
+
createToaster
|
|
113
|
+
} from "./createToaster.js";
|
|
109
114
|
export {
|
|
110
115
|
Accordion,
|
|
111
116
|
Alert,
|
|
@@ -216,6 +221,7 @@ export {
|
|
|
216
221
|
UploadList,
|
|
217
222
|
VirtualPickerList,
|
|
218
223
|
appearanceDefaults,
|
|
224
|
+
createToaster,
|
|
219
225
|
formatFileSize,
|
|
220
226
|
normalizeAppearance,
|
|
221
227
|
useFormState,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robert-stackflow/oneui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "OneUI React components, application shell and portable visual themes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -789,7 +789,12 @@
|
|
|
789
789
|
"import": "./components/TreeTable.js",
|
|
790
790
|
"default": "./components/TreeTable.js"
|
|
791
791
|
},
|
|
792
|
-
"./TreeTable.module.css": "./components/TreeTable.module.css"
|
|
792
|
+
"./TreeTable.module.css": "./components/TreeTable.module.css",
|
|
793
|
+
"./createToaster": {
|
|
794
|
+
"types": "./components/createToaster.d.ts",
|
|
795
|
+
"import": "./components/createToaster.js",
|
|
796
|
+
"default": "./components/createToaster.js"
|
|
797
|
+
}
|
|
793
798
|
},
|
|
794
799
|
"files": [
|
|
795
800
|
"components",
|
|
@@ -799,8 +804,8 @@
|
|
|
799
804
|
"CHANGELOG.md"
|
|
800
805
|
],
|
|
801
806
|
"peerDependencies": {
|
|
802
|
-
"react": "19.
|
|
803
|
-
"react-dom": "19.
|
|
807
|
+
"react": "^19.2.0",
|
|
808
|
+
"react-dom": "^19.2.0"
|
|
804
809
|
},
|
|
805
810
|
"dependencies": {
|
|
806
811
|
"lucide-react": "1.59.0"
|