@aaas-pro/library 0.0.0-stage → 0.1.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/README.md +107 -2
- package/dist/components/address-picker.d.ts +18 -0
- package/dist/components/address-picker.js +46 -0
- package/dist/components/card-date-filter.d.ts +12 -0
- package/dist/components/card-date-filter.js +95 -0
- package/dist/components/date-time-picker.d.ts +12 -0
- package/dist/components/date-time-picker.js +96 -0
- package/dist/components/detail-dialog.d.ts +48 -0
- package/dist/components/detail-dialog.js +97 -0
- package/dist/components/document-links.d.ts +13 -0
- package/dist/components/document-links.js +19 -0
- package/dist/components/draft-row-actions.d.ts +14 -0
- package/dist/components/draft-row-actions.js +70 -0
- package/dist/components/error-boundary.d.ts +14 -0
- package/dist/components/error-boundary.js +35 -0
- package/dist/components/glow-card.d.ts +13 -0
- package/dist/components/glow-card.js +48 -0
- package/dist/components/org-picker.d.ts +17 -0
- package/dist/components/org-picker.js +56 -0
- package/dist/components/overflow-row-actions.d.ts +29 -0
- package/dist/components/overflow-row-actions.js +135 -0
- package/dist/components/page-header.d.ts +21 -0
- package/dist/components/page-header.js +21 -0
- package/dist/components/permission-button.d.ts +4 -0
- package/dist/components/permission-button.js +17 -0
- package/dist/components/permission-context.d.ts +12 -0
- package/dist/components/permission-context.js +17 -0
- package/dist/components/person-picker.d.ts +20 -0
- package/dist/components/person-picker.js +38 -0
- package/dist/components/process-flow-bar.d.ts +15 -0
- package/dist/components/process-flow-bar.js +19 -0
- package/dist/components/product-picker.d.ts +19 -0
- package/dist/components/product-picker.js +155 -0
- package/dist/components/ref-doc-picker.d.ts +24 -0
- package/dist/components/ref-doc-picker.js +139 -0
- package/dist/components/scroll-to-top.d.ts +1 -0
- package/dist/components/scroll-to-top.js +20 -0
- package/dist/components/state-view.d.ts +26 -0
- package/dist/components/state-view.js +75 -0
- package/dist/components/theme-switcher.d.ts +27 -0
- package/dist/components/theme-switcher.js +77 -0
- package/dist/components/time-filter.d.ts +7 -0
- package/dist/components/time-filter.js +65 -0
- package/dist/components/ui/accordion.d.ts +7 -0
- package/dist/components/ui/accordion.js +64 -0
- package/dist/components/ui/alert-dialog.d.ts +15 -0
- package/dist/components/ui/alert-dialog.js +160 -0
- package/dist/components/ui/alert.d.ts +12 -0
- package/dist/components/ui/alert.js +66 -0
- package/dist/components/ui/aspect-ratio.d.ts +3 -0
- package/dist/components/ui/aspect-ratio.js +10 -0
- package/dist/components/ui/avatar.d.ts +6 -0
- package/dist/components/ui/avatar.js +53 -0
- package/dist/components/ui/badge.d.ts +12 -0
- package/dist/components/ui/badge.js +44 -0
- package/dist/components/ui/breadcrumb.d.ts +11 -0
- package/dist/components/ui/breadcrumb.js +103 -0
- package/dist/components/ui/button-group.d.ts +14 -0
- package/dist/components/ui/button-group.js +76 -0
- package/dist/components/ui/button.d.ts +20 -0
- package/dist/components/ui/button.js +93 -0
- package/dist/components/ui/calendar.d.ts +73 -0
- package/dist/components/ui/calendar.js +103 -0
- package/dist/components/ui/card.d.ts +9 -0
- package/dist/components/ui/card.js +90 -0
- package/dist/components/ui/carousel.d.ts +14 -0
- package/dist/components/ui/carousel.js +200 -0
- package/dist/components/ui/chart.d.ts +43 -0
- package/dist/components/ui/chart.js +252 -0
- package/dist/components/ui/checkbox.d.ts +4 -0
- package/dist/components/ui/checkbox.js +31 -0
- package/dist/components/ui/collapsible.d.ts +5 -0
- package/dist/components/ui/collapsible.js +34 -0
- package/dist/components/ui/command.d.ts +18 -0
- package/dist/components/ui/command.js +170 -0
- package/dist/components/ui/context-menu.d.ts +26 -0
- package/dist/components/ui/context-menu.js +237 -0
- package/dist/components/ui/dialog.d.ts +17 -0
- package/dist/components/ui/dialog.js +139 -0
- package/dist/components/ui/drawer.d.ts +13 -0
- package/dist/components/ui/drawer.js +129 -0
- package/dist/components/ui/dropdown-menu.d.ts +26 -0
- package/dist/components/ui/dropdown-menu.js +245 -0
- package/dist/components/ui/empty.d.ts +14 -0
- package/dist/components/ui/empty.js +102 -0
- package/dist/components/ui/field.d.ts +27 -0
- package/dist/components/ui/field.js +229 -0
- package/dist/components/ui/form.d.ts +26 -0
- package/dist/components/ui/form.js +118 -0
- package/dist/components/ui/hover-card.d.ts +6 -0
- package/dist/components/ui/hover-card.js +39 -0
- package/dist/components/ui/input-group.d.ts +22 -0
- package/dist/components/ui/input-group.js +156 -0
- package/dist/components/ui/input-otp.d.ts +11 -0
- package/dist/components/ui/input-otp.js +66 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/input.js +21 -0
- package/dist/components/ui/item.d.ts +29 -0
- package/dist/components/ui/item.js +187 -0
- package/dist/components/ui/kbd.d.ts +3 -0
- package/dist/components/ui/kbd.js +31 -0
- package/dist/components/ui/label.d.ts +4 -0
- package/dist/components/ui/label.js +22 -0
- package/dist/components/ui/menubar.d.ts +26 -0
- package/dist/components/ui/menubar.js +252 -0
- package/dist/components/ui/navigation-menu.d.ts +17 -0
- package/dist/components/ui/navigation-menu.js +170 -0
- package/dist/components/ui/pagination.d.ts +13 -0
- package/dist/components/ui/pagination.js +118 -0
- package/dist/components/ui/popover.d.ts +8 -0
- package/dist/components/ui/popover.js +47 -0
- package/dist/components/ui/progress.d.ts +4 -0
- package/dist/components/ui/progress.js +34 -0
- package/dist/components/ui/radio-group.d.ts +5 -0
- package/dist/components/ui/radio-group.js +45 -0
- package/dist/components/ui/resizable.d.ts +7 -0
- package/dist/components/ui/resizable.js +49 -0
- package/dist/components/ui/scroll-area.d.ts +5 -0
- package/dist/components/ui/scroll-area.js +60 -0
- package/dist/components/ui/select.d.ts +17 -0
- package/dist/components/ui/select.js +215 -0
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/separator.js +26 -0
- package/dist/components/ui/sheet.d.ts +13 -0
- package/dist/components/ui/sheet.js +127 -0
- package/dist/components/ui/sidebar.d.ts +72 -0
- package/dist/components/ui/sidebar.js +656 -0
- package/dist/components/ui/skeleton.d.ts +2 -0
- package/dist/components/ui/skeleton.js +15 -0
- package/dist/components/ui/slider.d.ts +4 -0
- package/dist/components/ui/slider.js +63 -0
- package/dist/components/ui/sonner.d.ts +4 -0
- package/dist/components/ui/sonner.js +34 -0
- package/dist/components/ui/spinner.d.ts +2 -0
- package/dist/components/ui/spinner.js +17 -0
- package/dist/components/ui/switch.d.ts +4 -0
- package/dist/components/ui/switch.js +31 -0
- package/dist/components/ui/table.d.ts +10 -0
- package/dist/components/ui/table.js +114 -0
- package/dist/components/ui/tabs.d.ts +7 -0
- package/dist/components/ui/tabs.js +67 -0
- package/dist/components/ui/textarea.d.ts +3 -0
- package/dist/components/ui/textarea.js +18 -0
- package/dist/components/ui/toggle-group.d.ts +9 -0
- package/dist/components/ui/toggle-group.js +68 -0
- package/dist/components/ui/toggle.d.ts +12 -0
- package/dist/components/ui/toggle.js +43 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/components/ui/tooltip.js +56 -0
- package/dist/hooks/use-mobile.d.ts +1 -0
- package/dist/hooks/use-mobile.js +18 -0
- package/dist/icons/index.d.ts +57 -0
- package/dist/icons/index.js +115 -0
- package/dist/icons/paths.d.ts +556 -0
- package/dist/icons/paths.js +231 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +399 -0
- package/dist/internal/selection-base.d.ts +37 -0
- package/dist/internal/selection-base.js +40 -0
- package/dist/lib/class-names.d.ts +6 -0
- package/dist/lib/class-names.js +159 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.js +7 -0
- package/dist/lib/variants.d.ts +21 -0
- package/dist/lib/variants.js +21 -0
- package/dist/primitives/accordion.d.ts +29 -0
- package/dist/primitives/accordion.js +49 -0
- package/dist/primitives/alert-dialog.d.ts +10 -0
- package/dist/primitives/alert-dialog.js +22 -0
- package/dist/primitives/aspect-ratio.d.ts +4 -0
- package/dist/primitives/aspect-ratio.js +7 -0
- package/dist/primitives/avatar.d.ts +8 -0
- package/dist/primitives/avatar.js +45 -0
- package/dist/primitives/carousel.d.ts +22 -0
- package/dist/primitives/carousel.js +78 -0
- package/dist/primitives/chart.d.ts +71 -0
- package/dist/primitives/chart.js +125 -0
- package/dist/primitives/checkbox.d.ts +13 -0
- package/dist/primitives/checkbox.js +24 -0
- package/dist/primitives/collapsible.d.ts +14 -0
- package/dist/primitives/collapsible.js +29 -0
- package/dist/primitives/command.d.ts +43 -0
- package/dist/primitives/command.js +98 -0
- package/dist/primitives/context-menu.d.ts +24 -0
- package/dist/primitives/context-menu.js +19 -0
- package/dist/primitives/core.d.ts +25 -0
- package/dist/primitives/core.js +89 -0
- package/dist/primitives/dialog.d.ts +7 -0
- package/dist/primitives/dialog.js +15 -0
- package/dist/primitives/drawer.d.ts +30 -0
- package/dist/primitives/drawer.js +39 -0
- package/dist/primitives/dropdown-menu.d.ts +24 -0
- package/dist/primitives/dropdown-menu.js +19 -0
- package/dist/primitives/form.d.ts +154 -0
- package/dist/primitives/form.js +139 -0
- package/dist/primitives/hover-card.d.ts +4 -0
- package/dist/primitives/hover-card.js +8 -0
- package/dist/primitives/input-otp.d.ts +21 -0
- package/dist/primitives/input-otp.js +78 -0
- package/dist/primitives/label.d.ts +2 -0
- package/dist/primitives/label.js +7 -0
- package/dist/primitives/layer.d.ts +84 -0
- package/dist/primitives/layer.js +300 -0
- package/dist/primitives/menu.d.ts +56 -0
- package/dist/primitives/menu.js +126 -0
- package/dist/primitives/menubar.d.ts +91 -0
- package/dist/primitives/menubar.js +44 -0
- package/dist/primitives/navigation-menu.d.ts +32 -0
- package/dist/primitives/navigation-menu.js +121 -0
- package/dist/primitives/popover.d.ts +4 -0
- package/dist/primitives/popover.js +12 -0
- package/dist/primitives/progress.d.ts +7 -0
- package/dist/primitives/progress.js +13 -0
- package/dist/primitives/radio-group.d.ts +18 -0
- package/dist/primitives/radio-group.js +36 -0
- package/dist/primitives/resizable.d.ts +19 -0
- package/dist/primitives/resizable.js +91 -0
- package/dist/primitives/scroll-area.d.ts +12 -0
- package/dist/primitives/scroll-area.js +26 -0
- package/dist/primitives/select.d.ts +42 -0
- package/dist/primitives/select.js +167 -0
- package/dist/primitives/separator.d.ts +5 -0
- package/dist/primitives/separator.js +7 -0
- package/dist/primitives/slider.d.ts +25 -0
- package/dist/primitives/slider.js +98 -0
- package/dist/primitives/slot.d.ts +1 -0
- package/dist/primitives/slot.js +4 -0
- package/dist/primitives/switch.d.ts +11 -0
- package/dist/primitives/switch.js +22 -0
- package/dist/primitives/tabs.d.ts +23 -0
- package/dist/primitives/tabs.js +38 -0
- package/dist/primitives/toast.d.ts +55 -0
- package/dist/primitives/toast.js +86 -0
- package/dist/primitives/toggle-group.d.ts +24 -0
- package/dist/primitives/toggle-group.js +33 -0
- package/dist/primitives/toggle.d.ts +7 -0
- package/dist/primitives/toggle.js +13 -0
- package/dist/primitives/tooltip.d.ts +7 -0
- package/dist/primitives/tooltip.js +10 -0
- package/dist/style-entry.d.ts +1 -0
- package/dist/style-entry.js +1 -0
- package/dist/styles.css +7016 -0
- package/dist/styles.js +1 -0
- package/dist/theme/index.d.ts +110 -0
- package/dist/theme/index.js +199 -0
- package/licenses/Lucide.txt +15 -0
- package/package.json +67 -4
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useContext, useState } from "react";
|
|
3
|
+
import { PermissionContext } from "./permission-context.js";
|
|
4
|
+
import { Pencil, Trash2 } from "../icons/index.js";
|
|
5
|
+
import { Button } from "./ui/button.js";
|
|
6
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "./ui/dialog.js";
|
|
7
|
+
import { cn } from "../lib/utils.js";
|
|
8
|
+
function DraftRowActions({ status, docLabel, docNo, onEdit, onDelete, editableStatuses = ["草稿", "待确认"], align = "center", disabled = false }) {
|
|
9
|
+
const can = useContext(PermissionContext);
|
|
10
|
+
const [open, setOpen] = useState(false), [busy, setBusy] = useState(false), [error, setError] = useState("");
|
|
11
|
+
if (!editableStatuses.includes(status)) return null;
|
|
12
|
+
async function confirm() {
|
|
13
|
+
if (busy || disabled || !can("delete")) return;
|
|
14
|
+
setBusy(true);
|
|
15
|
+
setError("");
|
|
16
|
+
try {
|
|
17
|
+
const result = await onDelete();
|
|
18
|
+
if (typeof result === "string" && result) setError(result);
|
|
19
|
+
else setOpen(false);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
setError(e instanceof Error ? e.message : "删除失败,请重试");
|
|
22
|
+
} finally {
|
|
23
|
+
setBusy(false);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
27
|
+
/* @__PURE__ */ jsxs("div", { className: cn("flex gap-1", align === "center" ? "justify-center" : "justify-end"), children: [
|
|
28
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: disabled || busy, permissionAction: "update", onClick: () => {
|
|
29
|
+
if (can("update")) onEdit();
|
|
30
|
+
}, children: [
|
|
31
|
+
/* @__PURE__ */ jsx(Pencil, { className: "size-3.5" }),
|
|
32
|
+
"编辑"
|
|
33
|
+
] }),
|
|
34
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", color: "danger", size: "sm", disabled: disabled || busy, permissionAction: "delete", onClick: () => {
|
|
35
|
+
if (can("delete")) {
|
|
36
|
+
setError("");
|
|
37
|
+
setOpen(true);
|
|
38
|
+
}
|
|
39
|
+
}, children: [
|
|
40
|
+
/* @__PURE__ */ jsx(Trash2, { className: "size-3.5" }),
|
|
41
|
+
"删除"
|
|
42
|
+
] })
|
|
43
|
+
] }),
|
|
44
|
+
/* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => {
|
|
45
|
+
if (!busy) setOpen(next);
|
|
46
|
+
}, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
47
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
48
|
+
/* @__PURE__ */ jsxs(DialogTitle, { children: [
|
|
49
|
+
"删除",
|
|
50
|
+
docLabel
|
|
51
|
+
] }),
|
|
52
|
+
/* @__PURE__ */ jsxs(DialogDescription, { children: [
|
|
53
|
+
"确认删除",
|
|
54
|
+
docLabel,
|
|
55
|
+
" ",
|
|
56
|
+
docNo,
|
|
57
|
+
"?具体删除方式由业务应用处理。"
|
|
58
|
+
] })
|
|
59
|
+
] }),
|
|
60
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }),
|
|
61
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
62
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", disabled: busy, onClick: () => setOpen(false), children: "取消" }),
|
|
63
|
+
/* @__PURE__ */ jsx(Button, { type: "button", color: "danger", loading: busy, disabled, permissionAction: "delete", onClick: () => void confirm(), children: "确认删除" })
|
|
64
|
+
] })
|
|
65
|
+
] }) })
|
|
66
|
+
] });
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
DraftRowActions
|
|
70
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
2
|
+
interface Props {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
}
|
|
5
|
+
interface State {
|
|
6
|
+
error: Error | null;
|
|
7
|
+
}
|
|
8
|
+
export declare class ErrorBoundary extends Component<Props, State> {
|
|
9
|
+
state: State;
|
|
10
|
+
static getDerivedStateFromError(error: Error): State;
|
|
11
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
12
|
+
render(): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Component } from "react";
|
|
3
|
+
class ErrorBoundary extends Component {
|
|
4
|
+
state = { error: null };
|
|
5
|
+
static getDerivedStateFromError(error) {
|
|
6
|
+
return { error };
|
|
7
|
+
}
|
|
8
|
+
componentDidCatch(error, info) {
|
|
9
|
+
console.error("应用错误边界捕获:", error, info);
|
|
10
|
+
}
|
|
11
|
+
render() {
|
|
12
|
+
if (this.state.error) {
|
|
13
|
+
const msg = this.state.error?.message || String(this.state.error);
|
|
14
|
+
const stack = this.state.error?.stack || "";
|
|
15
|
+
return /* @__PURE__ */ jsx("div", { style: { minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", background: "#f0f4f3", padding: "24px" }, children: /* @__PURE__ */ jsxs("div", { style: { maxWidth: "640px", width: "100%", background: "#fff", border: "1px solid #e2e8f0", borderRadius: "12px", padding: "24px", boxShadow: "0 10px 30px rgba(0,0,0,0.08)" }, children: [
|
|
16
|
+
/* @__PURE__ */ jsx("h2", { style: { fontSize: "18px", fontWeight: 600, color: "#0f172a", margin: 0 }, children: "页面出错了" }),
|
|
17
|
+
/* @__PURE__ */ jsx("p", { style: { marginTop: "8px", fontSize: "14px", color: "#64748b", margin: 0 }, children: "应用遇到错误导致无法渲染,请把下方错误信息反馈给开发者。" }),
|
|
18
|
+
/* @__PURE__ */ jsx("pre", { style: { marginTop: "12px", maxHeight: "160px", overflow: "auto", background: "#0f172a", color: "#94a3b8", padding: "12px", borderRadius: "8px", fontSize: "12px", whiteSpace: "pre-wrap", wordBreak: "break-all", margin: "12px 0 0 0" }, children: msg }),
|
|
19
|
+
stack ? /* @__PURE__ */ jsx("pre", { style: { marginTop: "8px", maxHeight: "160px", overflow: "auto", background: "#f1f5f9", color: "#475569", padding: "12px", borderRadius: "8px", fontSize: "11px", whiteSpace: "pre-wrap", wordBreak: "break-all", margin: "8px 0 0 0" }, children: stack }) : null,
|
|
20
|
+
/* @__PURE__ */ jsx(
|
|
21
|
+
"button",
|
|
22
|
+
{
|
|
23
|
+
onClick: () => location.reload(),
|
|
24
|
+
style: { marginTop: "16px", background: "#1e3a5f", color: "#fff", border: "none", borderRadius: "8px", padding: "8px 20px", fontSize: "14px", cursor: "pointer" },
|
|
25
|
+
children: "刷新重试"
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
] }) });
|
|
29
|
+
}
|
|
30
|
+
return this.props.children;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
ErrorBoundary
|
|
35
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type VariantProps } from '../lib/variants';
|
|
2
|
+
declare const glowCardVariants: (props?: ({
|
|
3
|
+
glow?: "blue" | "orange" | "none" | "purple" | "red" | "default" | "neon" | null | undefined;
|
|
4
|
+
padding?: "none" | "default" | "sm" | "lg" | null | undefined;
|
|
5
|
+
} & {
|
|
6
|
+
class?: import("../lib/class-names").ClassValue;
|
|
7
|
+
className?: import("../lib/class-names").ClassValue;
|
|
8
|
+
}) | undefined) => string;
|
|
9
|
+
export interface GlowCardProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof glowCardVariants> {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
declare const GlowCard: import("react").ForwardRefExoticComponent<GlowCardProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
13
|
+
export { GlowCard, glowCardVariants };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { cva } from "../lib/variants.js";
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
const glowCardVariants = cva(
|
|
6
|
+
"relative rounded-2xl bg-card border border-border/50 overflow-hidden transition-all duration-300 ease-out",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
glow: {
|
|
10
|
+
default: "hover:shadow-[0_0_20px_color-mix(in_srgb,var(--primary)_15%,transparent)] hover:-translate-y-0.5 hover:border-[var(--primary)]/30",
|
|
11
|
+
purple: "hover:shadow-[0_0_20px_rgba(114,46,209,0.15)] hover:-translate-y-0.5 hover:border-[#722ed1]/30",
|
|
12
|
+
blue: "hover:shadow-[0_0_20px_rgba(14,165,233,0.15)] hover:-translate-y-0.5 hover:border-[#0ea5e9]/30",
|
|
13
|
+
orange: "hover:shadow-[0_0_20px_rgba(245,158,11,0.15)] hover:-translate-y-0.5 hover:border-[#f59e0b]/30",
|
|
14
|
+
red: "hover:shadow-[0_0_20px_rgba(239,68,68,0.15)] hover:-translate-y-0.5 hover:border-[#ef4444]/30",
|
|
15
|
+
neon: "hover:shadow-[0_0_25px_rgba(0,230,118,0.2)] hover:-translate-y-0.5 hover:border-[var(--primary-light)]/40",
|
|
16
|
+
none: ""
|
|
17
|
+
},
|
|
18
|
+
padding: {
|
|
19
|
+
default: "p-5",
|
|
20
|
+
sm: "p-4",
|
|
21
|
+
lg: "p-6",
|
|
22
|
+
none: "p-0"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
glow: "default",
|
|
27
|
+
padding: "default"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
const GlowCard = forwardRef(
|
|
32
|
+
({ className, glow, padding, children, ...props }, ref) => {
|
|
33
|
+
return /* @__PURE__ */ jsx(
|
|
34
|
+
"div",
|
|
35
|
+
{
|
|
36
|
+
ref,
|
|
37
|
+
className: cn(glowCardVariants({ glow, padding }), className),
|
|
38
|
+
...props,
|
|
39
|
+
children
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
GlowCard.displayName = "GlowCard";
|
|
45
|
+
export {
|
|
46
|
+
GlowCard,
|
|
47
|
+
glowCardVariants
|
|
48
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type SelectionStateProps } from '../internal/selection-base';
|
|
2
|
+
export interface OrganizationOption {
|
|
3
|
+
/** Unique organization identifier, including across branches. */
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
children?: OrganizationOption[];
|
|
8
|
+
}
|
|
9
|
+
export interface OrgPickerProps extends SelectionStateProps {
|
|
10
|
+
options: OrganizationOption[];
|
|
11
|
+
value: string | null;
|
|
12
|
+
onChange: (value: string | null, option: OrganizationOption | null) => void;
|
|
13
|
+
/** popover: input with dropdown; inline: embedded organization tree. */
|
|
14
|
+
mode?: 'popover' | 'inline';
|
|
15
|
+
}
|
|
16
|
+
/** Organization selection with host-provided tree data; no organization-level assumptions. */
|
|
17
|
+
export declare function OrgPicker({ options, value, onChange, mode, disabled, loading, error, placeholder, allowClear, className, 'aria-label': name }: OrgPickerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { ChevronRight, Building2 } from "../icons/index.js";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { SelectionSearch, SelectionMessage, SelectionFrame, SelectionCheck } from "../internal/selection-base.js";
|
|
6
|
+
function flatten(nodes) {
|
|
7
|
+
return nodes.flatMap((n) => [n, ...flatten(n.children ?? [])]);
|
|
8
|
+
}
|
|
9
|
+
function matches(node, query) {
|
|
10
|
+
return node.name.toLocaleLowerCase().includes(query) || !!node.children?.some((n) => matches(n, query));
|
|
11
|
+
}
|
|
12
|
+
function OrganizationRows({ nodes, value, onSelect, query, depth = 0 }) {
|
|
13
|
+
const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
|
|
14
|
+
return /* @__PURE__ */ jsx(Fragment, { children: nodes.filter((n) => matches(n, query)).map((node) => {
|
|
15
|
+
const expanded = !!query || !collapsed.has(node.id), hasChildren = !!node.children?.length;
|
|
16
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
17
|
+
/* @__PURE__ */ jsxs("div", { className: cn("flex items-center rounded-md", value === node.id ? "bg-primary/10" : "hover:bg-muted"), style: { paddingLeft: depth * 16 }, children: [
|
|
18
|
+
hasChildren ? /* @__PURE__ */ jsx("button", { type: "button", "aria-label": `${expanded ? "折叠" : "展开"}${node.name}`, "aria-expanded": expanded, onClick: () => setCollapsed((prev) => {
|
|
19
|
+
const next = new Set(prev);
|
|
20
|
+
if (next.has(node.id)) next.delete(node.id);
|
|
21
|
+
else next.add(node.id);
|
|
22
|
+
return next;
|
|
23
|
+
}), className: "rounded p-1.5 text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring", children: /* @__PURE__ */ jsx(ChevronRight, { className: cn("size-4 transition-transform", expanded && "rotate-90") }) }) : /* @__PURE__ */ jsx("span", { className: "w-7 shrink-0" }),
|
|
24
|
+
/* @__PURE__ */ jsxs("button", { type: "button", disabled: node.disabled, "aria-pressed": value === node.id, onClick: () => onSelect(node), className: "flex min-w-0 flex-1 items-center gap-2 rounded px-2 py-2 text-left text-sm disabled:cursor-not-allowed disabled:opacity-40 focus-visible:ring-2 focus-visible:ring-ring", children: [
|
|
25
|
+
/* @__PURE__ */ jsx(Building2, { className: "size-4 shrink-0 text-muted-foreground" }),
|
|
26
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: node.name }),
|
|
27
|
+
/* @__PURE__ */ jsx(SelectionCheck, { selected: value === node.id })
|
|
28
|
+
] })
|
|
29
|
+
] }),
|
|
30
|
+
hasChildren && expanded && /* @__PURE__ */ jsx(OrganizationRows, { nodes: node.children, value, onSelect, query, depth: depth + 1 })
|
|
31
|
+
] }, node.id);
|
|
32
|
+
}) });
|
|
33
|
+
}
|
|
34
|
+
function OrgPicker({ options, value, onChange, mode = "popover", disabled = false, loading = false, error, placeholder = "请选择组织", allowClear = true, className, "aria-label": name = "组织选择" }) {
|
|
35
|
+
const [open, setOpen] = useState(false), [query, setQuery] = useState("");
|
|
36
|
+
const normalized = query.trim().toLocaleLowerCase();
|
|
37
|
+
const label = flatten(options).find((n) => n.id === value)?.name ?? (value ?? "");
|
|
38
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
39
|
+
/* @__PURE__ */ jsx(SelectionSearch, { value: query, onChange: setQuery, label: "搜索组织名称" }),
|
|
40
|
+
/* @__PURE__ */ jsx(SelectionMessage, { loading, error, empty: !options.some((n) => matches(n, normalized)) }),
|
|
41
|
+
!loading && !error && /* @__PURE__ */ jsx("div", { className: "max-h-80 overflow-auto p-2", children: /* @__PURE__ */ jsx(OrganizationRows, { nodes: options, value, query: normalized, onSelect: (n) => {
|
|
42
|
+
if (!disabled) {
|
|
43
|
+
onChange(n.id, n);
|
|
44
|
+
setOpen(false);
|
|
45
|
+
}
|
|
46
|
+
} }) })
|
|
47
|
+
] });
|
|
48
|
+
if (mode === "inline") return /* @__PURE__ */ jsxs("fieldset", { disabled, "aria-label": name, className: cn("min-w-0 rounded-lg border border-border bg-card disabled:opacity-50", className), children: [
|
|
49
|
+
content,
|
|
50
|
+
allowClear && value !== null && /* @__PURE__ */ jsx("button", { type: "button", className: "w-full border-t border-border p-2 text-sm text-primary", onClick: () => onChange(null, null), children: "清空选择" })
|
|
51
|
+
] });
|
|
52
|
+
return /* @__PURE__ */ jsx(SelectionFrame, { label, placeholder, disabled, open, onOpenChange: setOpen, name, className, clear: allowClear && value !== null ? () => onChange(null, null) : void 0, children: content });
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
OrgPicker
|
|
56
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 行操作收纳渲染(列表操作列通用):
|
|
3
|
+
* 操作按钮总数不超过 3 个时全部平铺;超过 3 个时仅平铺主要业务动作(primary),
|
|
4
|
+
* 将编辑/删除等次级操作收进「更多」下拉菜单中选择,行为与平铺按钮完全一致
|
|
5
|
+
* (编辑打开同一表单回调、删除走同一确认流程)。
|
|
6
|
+
*/
|
|
7
|
+
import { ReactNode } from 'react';
|
|
8
|
+
export type RowActionTone = 'default' | 'danger' | 'warn' | 'ok' | 'amber' | 'blue';
|
|
9
|
+
export interface RowAction {
|
|
10
|
+
key: string;
|
|
11
|
+
label: string;
|
|
12
|
+
/** 按钮内图标(平铺与菜单一致展示) */
|
|
13
|
+
icon?: ReactNode;
|
|
14
|
+
tone?: RowActionTone;
|
|
15
|
+
title?: string;
|
|
16
|
+
/** 主操作(提交审批/发布竞价/转订单等业务动作):收纳时优先平铺;编辑/删除等次级操作收进「更多」 */
|
|
17
|
+
primary?: boolean;
|
|
18
|
+
onClick: () => void | Promise<unknown>;
|
|
19
|
+
/** 点击后先弹确认框再执行(与平铺按钮的删除确认一致) */
|
|
20
|
+
confirm?: {
|
|
21
|
+
title: string;
|
|
22
|
+
description: ReactNode;
|
|
23
|
+
confirmLabel?: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare function OverflowRowActions({ actions, align }: {
|
|
27
|
+
actions: RowAction[];
|
|
28
|
+
align?: 'center' | 'end';
|
|
29
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useContext, useState } from "react";
|
|
3
|
+
import { CommandPermissionContext } from "./permission-context.js";
|
|
4
|
+
import { Button } from "./ui/button.js";
|
|
5
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "./ui/dialog.js";
|
|
6
|
+
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from "./ui/dropdown-menu.js";
|
|
7
|
+
import { MoreHorizontal } from "../icons/index.js";
|
|
8
|
+
import { cn } from "../lib/utils.js";
|
|
9
|
+
import { toast } from "../primitives/toast.js";
|
|
10
|
+
const MAX_INLINE = 3;
|
|
11
|
+
const TONE_CLASS = {
|
|
12
|
+
default: "",
|
|
13
|
+
danger: "text-rose-600 border-rose-200 hover:bg-rose-50 hover:text-rose-700",
|
|
14
|
+
warn: "text-amber-600 border-amber-200 hover:bg-amber-50 hover:text-amber-700",
|
|
15
|
+
ok: "text-emerald-600 border-emerald-200 hover:text-emerald-700 hover:bg-emerald-50",
|
|
16
|
+
amber: "border-amber-200 text-amber-700 hover:bg-amber-50",
|
|
17
|
+
blue: "border-blue-200 text-blue-700 hover:bg-blue-50"
|
|
18
|
+
};
|
|
19
|
+
const TONE_MENU_ITEM = {
|
|
20
|
+
default: "",
|
|
21
|
+
danger: "text-rose-600 focus:text-rose-700",
|
|
22
|
+
warn: "text-amber-600 focus:text-amber-700",
|
|
23
|
+
ok: "text-emerald-600 focus:text-emerald-700",
|
|
24
|
+
amber: "text-amber-700 focus:text-amber-700",
|
|
25
|
+
blue: "text-blue-700 focus:text-blue-700"
|
|
26
|
+
};
|
|
27
|
+
function OverflowRowActions({ actions, align = "center" }) {
|
|
28
|
+
const canCommand = useContext(CommandPermissionContext);
|
|
29
|
+
actions = actions.filter((action) => canCommand(action.key));
|
|
30
|
+
const [confirming, setConfirming] = useState(null);
|
|
31
|
+
const [busy, setBusy] = useState(false);
|
|
32
|
+
const [error, setError] = useState("");
|
|
33
|
+
if (actions.length === 0) return null;
|
|
34
|
+
const overflow = actions.length > MAX_INLINE;
|
|
35
|
+
const primaries = actions.filter((a) => a.primary);
|
|
36
|
+
const inlineMax = MAX_INLINE - 1;
|
|
37
|
+
const inlineKeys = new Set(
|
|
38
|
+
overflow ? primaries.slice(0, Math.max(inlineMax, 0)).map((a) => a.key) : actions.map((a) => a.key)
|
|
39
|
+
);
|
|
40
|
+
const menuActions = overflow ? actions.filter((a) => !inlineKeys.has(a.key)) : [];
|
|
41
|
+
if (overflow && menuActions.length === 0) {
|
|
42
|
+
actions.slice(Math.max(inlineMax, 0)).forEach((a) => inlineKeys.delete(a.key));
|
|
43
|
+
}
|
|
44
|
+
async function run(a) {
|
|
45
|
+
if (!canCommand(a.key)) return;
|
|
46
|
+
if (a.confirm) {
|
|
47
|
+
setError("");
|
|
48
|
+
setConfirming(a);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
await a.onClick();
|
|
53
|
+
} catch (reason) {
|
|
54
|
+
toast.error(reason instanceof Error ? reason.message : "操作失败,请重试");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function confirmRun() {
|
|
58
|
+
if (!confirming || busy) return;
|
|
59
|
+
if (!canCommand(confirming.key)) {
|
|
60
|
+
setError("当前没有该操作权限");
|
|
61
|
+
toast.error("当前没有该操作权限");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
setBusy(true);
|
|
65
|
+
setError("");
|
|
66
|
+
try {
|
|
67
|
+
const r = await confirming.onClick();
|
|
68
|
+
if (typeof r === "string" && r) {
|
|
69
|
+
setError(r);
|
|
70
|
+
toast.error(r);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
setConfirming(null);
|
|
74
|
+
} catch (reason) {
|
|
75
|
+
const message = reason instanceof Error ? reason.message : "操作失败,请重试";
|
|
76
|
+
setError(message);
|
|
77
|
+
toast.error(message);
|
|
78
|
+
} finally {
|
|
79
|
+
setBusy(false);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const inlineActions = actions.filter((a) => inlineKeys.has(a.key));
|
|
83
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1.5 flex-wrap", align === "center" ? "justify-center" : "justify-end"), children: [
|
|
84
|
+
inlineActions.map((a) => /* @__PURE__ */ jsxs(
|
|
85
|
+
Button,
|
|
86
|
+
{
|
|
87
|
+
variant: "outline",
|
|
88
|
+
size: "sm",
|
|
89
|
+
className: cn("h-7 px-2 text-xs gap-1", TONE_CLASS[a.tone || "default"]),
|
|
90
|
+
title: a.title ?? a.label,
|
|
91
|
+
onClick: () => run(a),
|
|
92
|
+
children: [
|
|
93
|
+
a.icon,
|
|
94
|
+
a.label
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
a.key
|
|
98
|
+
)),
|
|
99
|
+
overflow && menuActions.length > 0 && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
100
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-7 px-2 text-xs gap-1", title: "更多操作(编辑/删除等)", children: [
|
|
101
|
+
/* @__PURE__ */ jsx(MoreHorizontal, { className: "w-3.5 h-3.5" }),
|
|
102
|
+
"更多"
|
|
103
|
+
] }) }),
|
|
104
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", className: "min-w-28", children: menuActions.map((a) => /* @__PURE__ */ jsxs(
|
|
105
|
+
DropdownMenuItem,
|
|
106
|
+
{
|
|
107
|
+
className: cn("text-xs gap-1.5 cursor-pointer", TONE_MENU_ITEM[a.tone || "default"]),
|
|
108
|
+
title: a.title ?? a.label,
|
|
109
|
+
onClick: () => run(a),
|
|
110
|
+
children: [
|
|
111
|
+
a.icon,
|
|
112
|
+
a.label
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
a.key
|
|
116
|
+
)) })
|
|
117
|
+
] }),
|
|
118
|
+
/* @__PURE__ */ jsx(Dialog, { open: !!confirming, onOpenChange: (v) => {
|
|
119
|
+
if (!v && !busy) setConfirming(null);
|
|
120
|
+
}, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-sm", showCloseButton: !busy, children: [
|
|
121
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
122
|
+
/* @__PURE__ */ jsx(DialogTitle, { className: "text-base", children: confirming?.confirm?.title }),
|
|
123
|
+
/* @__PURE__ */ jsx(DialogDescription, { className: "text-xs", children: confirming?.confirm?.description })
|
|
124
|
+
] }),
|
|
125
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }),
|
|
126
|
+
/* @__PURE__ */ jsxs(DialogFooter, { className: "gap-2", children: [
|
|
127
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", color: "default", size: "sm", disabled: busy, onClick: () => setConfirming(null), children: "取消" }),
|
|
128
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", color: "danger", loading: busy, permissionCommand: confirming?.key, onClick: () => void confirmRun(), children: confirming?.confirm?.confirmLabel ?? "确认删除" })
|
|
129
|
+
] })
|
|
130
|
+
] }) })
|
|
131
|
+
] });
|
|
132
|
+
}
|
|
133
|
+
export {
|
|
134
|
+
OverflowRowActions
|
|
135
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface PageHeaderProps {
|
|
3
|
+
/** 页面标题(h1)。 */
|
|
4
|
+
title: ReactNode;
|
|
5
|
+
/** 一句话说明页面用途或数据范围。 */
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
/** 标题前的图标,可使用库内 SVG 图标。 */
|
|
8
|
+
icon?: ReactNode;
|
|
9
|
+
/** 右侧主操作区(主按钮放最右)。 */
|
|
10
|
+
actions?: ReactNode;
|
|
11
|
+
/** 标题上方的面包屑或返回链接。 */
|
|
12
|
+
breadcrumb?: ReactNode;
|
|
13
|
+
/** 标题下方的附加内容,如页签或统计条。 */
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 页面标题区:统一页面级层次(面包屑 → 标题/说明 → 操作),窄屏时操作区换行到标题下方。
|
|
19
|
+
* 只负责布局与排版,不包含路由或权限逻辑。
|
|
20
|
+
*/
|
|
21
|
+
export declare function PageHeader({ title, description, icon, actions, breadcrumb, children, className }: PageHeaderProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
function PageHeader({ title, description, icon, actions, breadcrumb, children, className }) {
|
|
4
|
+
return /* @__PURE__ */ jsxs("header", { "data-slot": "page-header", className: cn("flex flex-col gap-3 pb-4", className), children: [
|
|
5
|
+
breadcrumb && /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: breadcrumb }),
|
|
6
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-x-6 gap-y-3", children: [
|
|
7
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [
|
|
8
|
+
icon && /* @__PURE__ */ jsx("div", { className: "mt-0.5 grid size-9 shrink-0 place-items-center rounded-lg bg-secondary text-primary [&_svg:not([class*=size-])]:size-5", children: icon }),
|
|
9
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
10
|
+
/* @__PURE__ */ jsx("h1", { className: "truncate text-xl leading-8 font-semibold text-foreground", children: title }),
|
|
11
|
+
description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: description })
|
|
12
|
+
] })
|
|
13
|
+
] }),
|
|
14
|
+
actions && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: actions })
|
|
15
|
+
] }),
|
|
16
|
+
children
|
|
17
|
+
] });
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
PageHeader
|
|
21
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ComponentProps } from 'react';
|
|
2
|
+
import { type PermissionProps } from './permission-context';
|
|
3
|
+
/** 保留原生按钮外观和事件;用于已有原生按钮的权限绑定。 */
|
|
4
|
+
export declare function PermissionButton({ permissionAction, permissionResource, permissionCommand, ...props }: ComponentProps<'button'> & PermissionProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { usePermissionCheck } from "./permission-context.js";
|
|
3
|
+
function PermissionButton({ permissionAction, permissionResource, permissionCommand, ...props }) {
|
|
4
|
+
const permitted = usePermissionCheck({ permissionAction, permissionResource, permissionCommand });
|
|
5
|
+
if (!permitted()) return null;
|
|
6
|
+
return /* @__PURE__ */ jsx("button", { ...props, onClickCapture: (event) => {
|
|
7
|
+
if (!permitted()) {
|
|
8
|
+
event.preventDefault();
|
|
9
|
+
event.stopPropagation();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
props.onClickCapture?.(event);
|
|
13
|
+
} });
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
PermissionButton
|
|
17
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** 技术门禁;业务资源与权限来源由使用方注入。默认维持既有组件行为。 */
|
|
2
|
+
export type PermissionAction = 'view' | 'create' | 'delete' | 'update' | 'audit' | 'unaudit' | 'export' | 'report' | 'grant';
|
|
3
|
+
export interface PermissionProps {
|
|
4
|
+
permissionAction?: PermissionAction;
|
|
5
|
+
permissionResource?: string;
|
|
6
|
+
permissionCommand?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const PermissionContext: import("react").Context<(action: PermissionAction, resource?: string) => boolean>;
|
|
9
|
+
/** 动态行操作码由业务方解释;组件库只执行注入的判定。 */
|
|
10
|
+
export declare const CommandPermissionContext: import("react").Context<(command: string) => boolean>;
|
|
11
|
+
export declare function usePermissionCheck({ permissionAction, permissionResource, permissionCommand }: PermissionProps): () => boolean;
|
|
12
|
+
export declare function useActionPermission(props: PermissionProps): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
const PermissionContext = createContext(() => true);
|
|
3
|
+
const CommandPermissionContext = createContext(() => true);
|
|
4
|
+
function usePermissionCheck({ permissionAction, permissionResource, permissionCommand }) {
|
|
5
|
+
const can = useContext(PermissionContext);
|
|
6
|
+
const canCommand = useContext(CommandPermissionContext);
|
|
7
|
+
return () => (permissionCommand === void 0 || canCommand(permissionCommand)) && (permissionAction === void 0 || can(permissionAction, permissionResource));
|
|
8
|
+
}
|
|
9
|
+
function useActionPermission(props) {
|
|
10
|
+
return usePermissionCheck(props)();
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
CommandPermissionContext,
|
|
14
|
+
PermissionContext,
|
|
15
|
+
useActionPermission,
|
|
16
|
+
usePermissionCheck
|
|
17
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type SelectionStateProps } from '../internal/selection-base';
|
|
2
|
+
export interface PersonOption {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
organization?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface PersonPickerProps extends SelectionStateProps {
|
|
10
|
+
options: PersonOption[];
|
|
11
|
+
/** Unique person IDs. Single mode accepts zero or one ID. */
|
|
12
|
+
value: string[];
|
|
13
|
+
onChange: (value: string[], selected: PersonOption[]) => void;
|
|
14
|
+
/** Default: true. false selects one person and closes the dropdown. */
|
|
15
|
+
multiple?: boolean;
|
|
16
|
+
/** Optional search notification; fetching and request cancellation belong to the host. */
|
|
17
|
+
onSearch?: (keyword: string) => void;
|
|
18
|
+
}
|
|
19
|
+
/** Controlled person selection. The host owns fetching, permissions and identity data. */
|
|
20
|
+
export declare function PersonPicker({ options, value, onChange, multiple, onSearch, disabled, loading, error, placeholder, allowClear, className, 'aria-label': name }: PersonPickerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { UserRound } from "../icons/index.js";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { SelectionFrame, SelectionSearch, SelectionMessage, SelectionCheck } from "../internal/selection-base.js";
|
|
6
|
+
function PersonPicker({ options, value, onChange, multiple = true, onSearch, disabled = false, loading = false, error, placeholder = "请选择人员", allowClear = true, className, "aria-label": name = "人员选择" }) {
|
|
7
|
+
const [open, setOpen] = useState(false), [query, setQuery] = useState("");
|
|
8
|
+
const keyword = query.trim().toLocaleLowerCase(), rows = options.filter((n) => `${n.name} ${n.organization ?? ""} ${n.description ?? ""}`.toLocaleLowerCase().includes(keyword));
|
|
9
|
+
const label = value.map((id) => options.find((n) => n.id === id)?.name ?? id).join("、");
|
|
10
|
+
const change = (ids) => onChange(ids, options.filter((n) => ids.includes(n.id)));
|
|
11
|
+
return /* @__PURE__ */ jsxs(SelectionFrame, { label, placeholder, disabled, open, onOpenChange: setOpen, name, className, clear: allowClear && value.length ? () => change([]) : void 0, children: [
|
|
12
|
+
/* @__PURE__ */ jsx(SelectionSearch, { value: query, onChange: (next) => {
|
|
13
|
+
setQuery(next);
|
|
14
|
+
onSearch?.(next);
|
|
15
|
+
}, label: "搜索姓名或组织" }),
|
|
16
|
+
/* @__PURE__ */ jsx(SelectionMessage, { loading, error, empty: !rows.length }),
|
|
17
|
+
!loading && !error && /* @__PURE__ */ jsx("div", { className: "max-h-80 overflow-auto p-2", children: rows.map((n) => /* @__PURE__ */ jsxs("button", { type: "button", disabled: n.disabled, "aria-pressed": value.includes(n.id), onClick: () => {
|
|
18
|
+
change(multiple ? value.includes(n.id) ? value.filter((id) => id !== n.id) : [...value, n.id] : [n.id]);
|
|
19
|
+
if (!multiple) setOpen(false);
|
|
20
|
+
}, className: cn("flex w-full items-center gap-3 rounded-md px-3 py-2.5 text-left disabled:cursor-not-allowed disabled:opacity-40 focus-visible:ring-2 focus-visible:ring-ring", value.includes(n.id) ? "bg-primary/10" : "hover:bg-muted"), children: [
|
|
21
|
+
/* @__PURE__ */ jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-full bg-secondary text-primary", children: /* @__PURE__ */ jsx(UserRound, { className: "size-4" }) }),
|
|
22
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
23
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-medium", children: n.name }),
|
|
24
|
+
(n.organization || n.description) && /* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-muted-foreground", children: [n.organization, n.description].filter(Boolean).join(" · ") })
|
|
25
|
+
] }),
|
|
26
|
+
/* @__PURE__ */ jsx(SelectionCheck, { selected: value.includes(n.id) })
|
|
27
|
+
] }, n.id)) }),
|
|
28
|
+
/* @__PURE__ */ jsxs("div", { className: "border-t border-border px-4 py-2 text-xs text-muted-foreground", children: [
|
|
29
|
+
multiple ? "可选择多名人员" : "请选择一名人员",
|
|
30
|
+
" · 已选 ",
|
|
31
|
+
value.length,
|
|
32
|
+
" 人"
|
|
33
|
+
] })
|
|
34
|
+
] });
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
PersonPicker
|
|
38
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface FlowStep {
|
|
2
|
+
id?: string;
|
|
3
|
+
label: string;
|
|
4
|
+
detail?: string;
|
|
5
|
+
status: 'completed' | 'current' | 'pending';
|
|
6
|
+
}
|
|
7
|
+
export interface ProcessFlowBarProps {
|
|
8
|
+
steps: FlowStep[];
|
|
9
|
+
onStepClick?: (step: FlowStep, index: number) => void;
|
|
10
|
+
variant?: 'default' | 'compact';
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
/** A workflow progress view. State transitions, routing and document dialogs belong to the host. */
|
|
15
|
+
export declare function ProcessFlowBar({ steps, onStepClick, variant, disabled, className }: ProcessFlowBarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronRight, Check } from "../icons/index.js";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
function ProcessFlowBar({ steps, onStepClick, variant = "default", disabled = false, className }) {
|
|
5
|
+
if (!steps.length) return /* @__PURE__ */ jsx("p", { role: "status", className: "p-4 text-sm text-muted-foreground", children: "暂无流程环节" });
|
|
6
|
+
return /* @__PURE__ */ jsx("ol", { "aria-label": "业务流程", className: cn("flex flex-wrap items-center gap-2 rounded-lg border border-border bg-card p-4", className), children: steps.map((step, index) => /* @__PURE__ */ jsxs("li", { className: "flex min-w-0 items-center gap-2", children: [
|
|
7
|
+
index > 0 && /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": true, className: "size-4 text-muted-foreground" }),
|
|
8
|
+
/* @__PURE__ */ jsxs("button", { type: "button", disabled: disabled || !onStepClick, "aria-current": step.status === "current" ? "step" : void 0, onClick: () => onStepClick?.(step, index), className: cn("flex items-center gap-2 rounded-md px-3 py-2 text-left focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default", step.status === "current" ? "bg-primary/10 text-primary" : step.status === "completed" ? "text-success" : "text-muted-foreground"), children: [
|
|
9
|
+
/* @__PURE__ */ jsx("span", { className: cn("grid size-7 shrink-0 place-items-center rounded-full border text-xs", step.status === "current" ? "border-primary bg-primary text-primary-foreground" : "border-current"), children: step.status === "completed" ? /* @__PURE__ */ jsx(Check, { className: "size-4" }) : index + 1 }),
|
|
10
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
11
|
+
/* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: step.label }),
|
|
12
|
+
variant === "default" && step.detail && /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: step.detail })
|
|
13
|
+
] })
|
|
14
|
+
] })
|
|
15
|
+
] }, step.id ?? index)) });
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
ProcessFlowBar
|
|
19
|
+
};
|