@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
package/README.md
CHANGED
|
@@ -1,3 +1,108 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AaasPro 公共组件库
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
包名:`@aaas-pro/library`。React、React DOM 和 React Router 由宿主提供。通用 UI 与业务通用控件从包入口或明确的组件子路径导入;不要求宿主配置 `@/` 别名,也不要求安装 Tailwind,样式已经预编译。
|
|
4
|
+
|
|
5
|
+
基础交互使用本库的 React 状态、上下文及浏览器 DOM 实现,源码在 `src/primitives`;不依赖 Radix UI,也不通过 cmdk、vaul 间接引入它。弹窗、菜单、选择、验证码、日历、轮播、面板调整、通知、表单校验和基础 SVG 图表由库统一维护;图标使用库内 SVG 渲染,矢量数据保留 Lucide/Feather 许可。组件变体、条件类名、样式覆盖和进退场动画同样由库内实现,源码位于 src/lib 和 src/motion.css;库没有第三方运行依赖,React/ReactDOM/Router 由宿主提供。Tailwind 及其插件仅用于开发阶段的样式编译,发布包自带 CSS。应用层的专项图表、表单、图标及动画依赖由业务应用自行管理。
|
|
6
|
+
|
|
7
|
+
历史实现范围见 [自主实现验收](docs/自主实现验收.md)。在前端工作区运行 `pnpm --filter @aaas-pro/library run check` 做类型检查,`pnpm --filter @aaas-pro/library run test` 运行组件测试,`pnpm run build:library` 构建发布包;文档开发与构建分别使用 `pnpm run dev:docs`、`pnpm run build:docs`。
|
|
8
|
+
|
|
9
|
+
`cn` 接受字符串、嵌套数组和条件字典,后传入的工具类按变体、重要性和方向覆盖前面的样式。覆盖规则包含库与原型使用的布局、间距、颜色、字体、边框、尺寸和动画等工具类;未匹配到工具类规则的自定义类保留原样,按自身 CSS 规则生效。变体与动画的原有组件参数保持不变。
|
|
10
|
+
|
|
11
|
+
## 最小接入
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { createRoot } from 'react-dom/client';
|
|
15
|
+
import { ConfigProvider, Button } from '@aaas-pro/library';
|
|
16
|
+
import '@aaas-pro/library/styles.css';
|
|
17
|
+
|
|
18
|
+
createRoot(document.getElementById('root')!).render(
|
|
19
|
+
<ConfigProvider theme="blue">
|
|
20
|
+
<Button color="primary" onClick={() => console.log('确认')}>确认</Button>
|
|
21
|
+
</ConfigProvider>
|
|
22
|
+
);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`Button.color` 取值为 `default | primary | success | warning | danger`。不传时,`destructive` 外观使用 `danger`,其余外观使用 `primary`。`loading` 默认为 `false`;为 `true` 时显示图标并阻止点击。`variant` 和 `size` 的完整范围见在线 API。
|
|
26
|
+
|
|
27
|
+
## 换肤
|
|
28
|
+
|
|
29
|
+
`theme` 可以是 `green`、`teal`、`cyan`(青空)、`lime`(青柠)、`gold`(鎏金)、`blue`、`indigo`、`violet`、`rose`、`orange`、`slate`、`dark`,默认 `green`,也可以只覆盖部分主题变量。文档主题选择同步展示全部 12 种预设:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
<ConfigProvider theme={{ preset: 'blue', token: {
|
|
33
|
+
colorPrimary: '#ad2468', colorSuccess: '#15803d', borderRadius: '8px'
|
|
34
|
+
}}} scope="document">
|
|
35
|
+
<App />
|
|
36
|
+
</ConfigProvider>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`scope` 默认 `local`,只影响当前容器及其库内浮层,适合多主题并排展示;`document` 同时设置页面根节点变量,适合整页应用及使用 body 浮层的第三方控件,卸载时恢复原值。
|
|
40
|
+
|
|
41
|
+
### 运行时切换主题
|
|
42
|
+
|
|
43
|
+
整页应用用 `ThemeProvider` 代替 `ConfigProvider`:它记住用户选择的色彩预设与导航栏外观(`localStorage`,默认键 `aaas-theme` / `aaas-theme:nav`,跨标签页同步),并允许应用级覆盖;`ThemeSwitcher` 提供现成的切换菜单(色样网格 + 导航栏浅色/深色),`useTheme()` 供自定义入口读取/切换。
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { ThemeProvider, useTheme } from '@aaas-pro/library/theme';
|
|
47
|
+
import { ThemeSwitcher } from '@aaas-pro/library/components/theme-switcher';
|
|
48
|
+
|
|
49
|
+
<ThemeProvider defaultTheme="green" defaultNavStyle="light" overrides={({ theme, navStyle }) => theme === 'green' && navStyle === 'dark' ? { colorNavBackground: '#14212e' } : undefined}>
|
|
50
|
+
<ThemeSwitcher showLabel />
|
|
51
|
+
<App />
|
|
52
|
+
</ThemeProvider>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`ThemeProvider` 属性:`defaultTheme`(默认 green)、`themes`(可切换的预设,默认全部)、`defaultNavStyle`(默认 light,深色预设固定 dark)、`overrides`(按预设的对象,或 `({ theme, navStyle }) => Partial<ThemeToken>`;在内置 `darkNavTokens` 派生之后应用)、`storageKey`(`null` 不持久化)、`scope`(默认 `document`)。`ThemeSwitcher` 可通过 `children` 传入自定义触发器(深色导航栏中建议这样做),`showLabel` 显示当前主题名。切换瞬间文档根节点带 `data-theme-switching` 属性 260ms,样式层据此加颜色过渡。
|
|
56
|
+
|
|
57
|
+
| Token | 类型 / 有效范围 | green 默认值 | 用途 |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| colorPrimary | 有效 CSS 颜色字符串 | #00875a | 品牌主色、焦点及主操作 |
|
|
60
|
+
| colorSuccess | 有效 CSS 颜色字符串 | #15803d | 成功状态 |
|
|
61
|
+
| colorWarning | 有效 CSS 颜色字符串 | #b45309 | 警告状态 |
|
|
62
|
+
| colorDanger | 有效 CSS 颜色字符串 | #dc2626 | 危险及错误状态 |
|
|
63
|
+
| colorInfo | 有效 CSS 颜色字符串 | #2563eb | 信息状态 |
|
|
64
|
+
| colorBackground | 有效 CSS 颜色字符串 | #f3f6f5 | 页面背景 |
|
|
65
|
+
| colorSurface | 有效 CSS 颜色字符串 | #ffffff | 卡片与浮层背景 |
|
|
66
|
+
| colorText | 有效 CSS 颜色字符串 | #17241f | 主要文字 |
|
|
67
|
+
| colorTextSecondary | 有效 CSS 颜色字符串 | #5b6b65 | 次要文字 |
|
|
68
|
+
| colorBorder | 有效 CSS 颜色字符串 | #e1e7e4 | 分隔线与卡片边框(输入框边框由其加深派生) |
|
|
69
|
+
| colorPrimaryText | 有效 CSS 颜色字符串 | #ffffff | 品牌色实心按钮文字 |
|
|
70
|
+
| borderRadius | 非负 CSS 长度 | 0.5rem | 基础圆角(控件 −2px,卡片/弹窗 +4px) |
|
|
71
|
+
| colorNavBackground | 有效 CSS 颜色字符串 | #ffffff | 导航栏背景 |
|
|
72
|
+
| colorNavText | 有效 CSS 颜色字符串 | #17241f | 导航栏文字与图标 |
|
|
73
|
+
| colorNavActive | 有效 CSS 颜色字符串 | #00875a | 导航栏选中强调色 |
|
|
74
|
+
|
|
75
|
+
`token` 所有字段均为可选,未传入的字段沿用预设。`ConfigProvider.children` 必填;`theme`、`scope` 和 `className` 可选。浮层优先挂入所在 Provider,显式指定 Portal.container 时由宿主保证目标容器的主题变量。
|
|
76
|
+
|
|
77
|
+
三套浅色预设同时改变主色、背景、文字与边框;选中底色和悬停色由主色计算。生态绿为 `#00875a / #f3f6f5`,科技蓝为 `#175cd3 / #f2f6fd`,暖橙为 `#c4510b / #fbf5f0`(主色 / 背景);深色为 `#3ddc97 / #0d151d`,表面 `#16222d`、边框 `#2a3845`。文档导航、品牌头像、主操作和焦点随主题变化;分类图标与成功、警告、错误状态保留各自含义。
|
|
78
|
+
|
|
79
|
+
## 业务通用控件
|
|
80
|
+
|
|
81
|
+
收录标准是独立的可复用交互单元、明确的数据与回调契约,并且适合多个业务。组织、人员、地址只是典型例子,不是数量限制。页面、工作台、领域 Store、具体单据编辑器和接口实现属于业务应用。
|
|
82
|
+
|
|
83
|
+
| 控件 | 可复用能力 | 宿主负责 |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| OrgPicker | 组织树选择、内嵌或下拉、搜索与清空 | 组织数据及有效范围 |
|
|
86
|
+
| PersonPicker | 人员单选/多选、搜索及状态反馈 | 通讯录、权限及远程请求 |
|
|
87
|
+
| AddressPicker | 任意层级地区路径选择 | 地区数据、编码版本及详细地址 |
|
|
88
|
+
| ProductPickerDialog | 商品搜索、分类、多选及禁选项 | 商品对象及业务规则 |
|
|
89
|
+
| RefDocPicker | 关联单据选择、移除及摘要 | 关联数据、摘要加载及导航 |
|
|
90
|
+
| DocumentLinks | 上下游单据导航 | 单据类型、加载及路由 |
|
|
91
|
+
| DraftRowActions | 可编辑记录操作、异步删除确认 | 可编辑状态、权限与实际删除 |
|
|
92
|
+
| ProcessFlowBar | 流程节点状态、紧凑显示及点击 | 状态机、审批、流转及编辑弹窗 |
|
|
93
|
+
|
|
94
|
+
以上是本次核查保留的控件,不是固定白名单。新增控件沿用同一职责标准。产品照片、视频、业务图标和模型仍由原型应用维护。
|
|
95
|
+
|
|
96
|
+
库源码只保留基础组件、通用控件、主题及少量 UI 工具。业务实现位于 `../aaaspro/src`;应用适配层将主数据、人员查询和表单字段转换为库的参数,不将业务依赖带入库。
|
|
97
|
+
|
|
98
|
+
## 文档维护
|
|
99
|
+
|
|
100
|
+
文档在 `docs/src`,接口由 TypeScript 源码解析;必填依据类型是否可选,默认值优先读取参数解构初始值,再读取库内交互实现的初始值。有限枚举在取值范围列展开,无源码边界的数值不虚构上限。`undefined(未声明初始值)` 表示组件没有设置该属性,必填属性使用 `—(必填,由调用方提供)`。
|
|
101
|
+
|
|
102
|
+
语义说明维护在 `docs/src/documentation-specs-data.json`。最小案例源文件位于 `docs/demos/<案例>/`,预览入口与源码展示位于 `docs/examples/`,静态文档位于 `docs/static/`;变更组件时同步维护相应案例和文档。`component-preview.html` 负责隔离文档交互。运行 `pnpm --filter @aaas-pro/library run docs` 从当前源码刷新 API、应用引用和真实案例资源;`pnpm run build:docs` 会先刷新文档,再由 `docs/vite.config.ts` 构建并复制静态页面到 `library/docs/dist/component-docs`。发布时部署完整 `library/docs/dist`。
|
|
103
|
+
|
|
104
|
+
## 规范与变更
|
|
105
|
+
|
|
106
|
+
- [组件库规范](../../../docs/02-前端/组件库规范.md):目录结构、公共 API、样式、类型、文档与测试约定。
|
|
107
|
+
- [设计规范](../../../docs/02-前端/设计规范.md):视觉方向、token 与组件状态约定。
|
|
108
|
+
- [CHANGELOG](CHANGELOG.md)。公共入口登记在 `api/entrypoints.json`,`pnpm --filter @aaas-pro/library run check` 执行类型检查,组件行为与公共出口由组件测试验证。
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type SelectionStateProps } from '../internal/selection-base';
|
|
2
|
+
export interface AddressOption {
|
|
3
|
+
/** Unique region code. Region data/version is provided by the host. */
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
children?: AddressOption[];
|
|
8
|
+
}
|
|
9
|
+
export interface AddressPickerProps extends SelectionStateProps {
|
|
10
|
+
options: AddressOption[];
|
|
11
|
+
/** Region ID path, from root to leaf. [] means no selection. */
|
|
12
|
+
value: string[];
|
|
13
|
+
onChange: (value: string[], selected: AddressOption[]) => void;
|
|
14
|
+
/** false requires a leaf; true also commits intermediate regions. Default: false. */
|
|
15
|
+
changeOnSelect?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Cascading region selection with any number of levels and no bundled administrative dataset. */
|
|
18
|
+
export declare function AddressPicker({ options, value, onChange, changeOnSelect, disabled, loading, error, placeholder, allowClear, className, 'aria-label': name }: AddressPickerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { ChevronRight } from "../icons/index.js";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { SelectionFrame, SelectionMessage, SelectionCheck } from "../internal/selection-base.js";
|
|
6
|
+
function pathNodes(options, ids) {
|
|
7
|
+
const nodes = [];
|
|
8
|
+
let rows = options;
|
|
9
|
+
for (const id of ids) {
|
|
10
|
+
const node = rows.find((n) => n.id === id);
|
|
11
|
+
if (!node) break;
|
|
12
|
+
nodes.push(node);
|
|
13
|
+
rows = node.children ?? [];
|
|
14
|
+
}
|
|
15
|
+
return nodes;
|
|
16
|
+
}
|
|
17
|
+
function AddressPicker({ options, value, onChange, changeOnSelect = false, disabled = false, loading = false, error, placeholder = "请选择地址", allowClear = true, className, "aria-label": name = "地址选择" }) {
|
|
18
|
+
const [open, setOpen] = useState(false), [draft, setDraft] = useState(value);
|
|
19
|
+
const nodes = pathNodes(options, draft), columns = [options, ...nodes.filter((n) => n.children?.length).map((n) => n.children)];
|
|
20
|
+
const label = pathNodes(options, value).map((n) => n.name).join(" / ") || value.join(" / ");
|
|
21
|
+
return /* @__PURE__ */ jsxs(SelectionFrame, { label, placeholder, disabled, open, onOpenChange: (next) => {
|
|
22
|
+
setOpen(next);
|
|
23
|
+
if (next) setDraft(value);
|
|
24
|
+
}, name, className, clear: allowClear && value.length ? () => onChange([], []) : void 0, children: [
|
|
25
|
+
/* @__PURE__ */ jsxs("div", { className: "border-b border-border px-4 py-3 text-sm font-medium", children: [
|
|
26
|
+
"选择地区",
|
|
27
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-2 text-xs font-normal text-muted-foreground", children: [
|
|
28
|
+
"逐级选择",
|
|
29
|
+
changeOnSelect ? ",允许选择上级地区" : "至末级地区"
|
|
30
|
+
] })
|
|
31
|
+
] }),
|
|
32
|
+
/* @__PURE__ */ jsx(SelectionMessage, { loading, error, empty: !options.length }),
|
|
33
|
+
!loading && !error && !!options.length && /* @__PURE__ */ jsx("div", { className: "flex overflow-x-auto", children: columns.map((rows, level) => /* @__PURE__ */ jsx("div", { className: "max-h-72 min-w-36 flex-1 overflow-y-auto border-r border-border p-2 last:border-r-0", "aria-label": `第 ${level + 1} 级地区`, children: rows.map((n) => /* @__PURE__ */ jsxs("button", { type: "button", disabled: n.disabled, "aria-pressed": draft[level] === n.id, onClick: () => {
|
|
34
|
+
const ids = [...draft.slice(0, level), n.id];
|
|
35
|
+
setDraft(ids);
|
|
36
|
+
if (changeOnSelect || !n.children?.length) onChange(ids, pathNodes(options, ids));
|
|
37
|
+
if (!n.children?.length) setOpen(false);
|
|
38
|
+
}, className: cn("flex w-full items-center gap-2 rounded-md px-2 py-2.5 text-left text-sm disabled:cursor-not-allowed disabled:opacity-40 focus-visible:ring-2 focus-visible:ring-ring", draft[level] === n.id ? "bg-primary/10 text-primary" : "hover:bg-muted"), children: [
|
|
39
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 whitespace-nowrap", children: n.name }),
|
|
40
|
+
n.children?.length ? /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5" }) : /* @__PURE__ */ jsx(SelectionCheck, { selected: draft[level] === n.id })
|
|
41
|
+
] }, n.id)) }, level)) })
|
|
42
|
+
] });
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
AddressPicker
|
|
46
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type CardDateKind = 'day' | 'week' | 'month' | 'year' | 'custom';
|
|
2
|
+
export type CardDateRange = {
|
|
3
|
+
kind: CardDateKind;
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function defaultCardDateRange(kind?: CardDateKind): CardDateRange;
|
|
8
|
+
export declare function CardDateFilter({ value, onChange, className }: {
|
|
9
|
+
value: CardDateRange;
|
|
10
|
+
onChange: (v: CardDateRange) => void;
|
|
11
|
+
className?: string;
|
|
12
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useMemo } from "react";
|
|
3
|
+
import { Calendar } from "./ui/calendar.js";
|
|
4
|
+
import { Popover, PopoverTrigger, PopoverContent } from "./ui/popover.js";
|
|
5
|
+
import { Button } from "./ui/button.js";
|
|
6
|
+
import { CalendarDays, ChevronDown } from "../icons/index.js";
|
|
7
|
+
import { cn } from "../lib/utils.js";
|
|
8
|
+
const KIND_LABELS = [
|
|
9
|
+
{ kind: "day", label: "本日" },
|
|
10
|
+
{ kind: "week", label: "本周" },
|
|
11
|
+
{ kind: "month", label: "本月" },
|
|
12
|
+
{ kind: "year", label: "本年" },
|
|
13
|
+
{ kind: "custom", label: "自定义" }
|
|
14
|
+
];
|
|
15
|
+
const fmt = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
16
|
+
function defaultCardDateRange(kind = "month") {
|
|
17
|
+
const now = /* @__PURE__ */ new Date();
|
|
18
|
+
const monday = new Date(now);
|
|
19
|
+
monday.setDate(now.getDate() - (now.getDay() + 6) % 7);
|
|
20
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
21
|
+
const yearStart = new Date(now.getFullYear(), 0, 1);
|
|
22
|
+
const weekAgo = new Date(now);
|
|
23
|
+
weekAgo.setDate(now.getDate() - 6);
|
|
24
|
+
switch (kind) {
|
|
25
|
+
case "day":
|
|
26
|
+
return { kind, from: fmt(now), to: fmt(now) };
|
|
27
|
+
case "week":
|
|
28
|
+
return { kind, from: fmt(monday), to: fmt(now) };
|
|
29
|
+
case "month":
|
|
30
|
+
return { kind, from: fmt(monthStart), to: fmt(now) };
|
|
31
|
+
case "year":
|
|
32
|
+
return { kind, from: fmt(yearStart), to: fmt(now) };
|
|
33
|
+
default:
|
|
34
|
+
return { kind: "custom", from: fmt(weekAgo), to: fmt(now) };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function CardDateFilter({ value, onChange, className }) {
|
|
38
|
+
const [open, setOpen] = useState(false);
|
|
39
|
+
const [range, setRange] = useState();
|
|
40
|
+
const selected = useMemo(() => ({
|
|
41
|
+
from: value.from ? new Date(value.from) : void 0,
|
|
42
|
+
to: value.to ? new Date(value.to) : void 0
|
|
43
|
+
}), [value.from, value.to]);
|
|
44
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-wrap items-center gap-1", className), children: [
|
|
45
|
+
/* @__PURE__ */ jsx(CalendarDays, { className: "w-3.5 h-3.5 text-muted-foreground" }),
|
|
46
|
+
KIND_LABELS.map((k) => /* @__PURE__ */ jsx(
|
|
47
|
+
"button",
|
|
48
|
+
{
|
|
49
|
+
type: "button",
|
|
50
|
+
"aria-pressed": value.kind === k.kind,
|
|
51
|
+
onClick: () => onChange(defaultCardDateRange(k.kind)),
|
|
52
|
+
className: cn(
|
|
53
|
+
"min-h-8 px-2 rounded-md text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring/15 transition-colors whitespace-nowrap",
|
|
54
|
+
value.kind === k.kind ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground hover:bg-muted/40"
|
|
55
|
+
),
|
|
56
|
+
children: k.label
|
|
57
|
+
},
|
|
58
|
+
k.kind
|
|
59
|
+
)),
|
|
60
|
+
/* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
61
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
62
|
+
Button,
|
|
63
|
+
{
|
|
64
|
+
variant: "outline",
|
|
65
|
+
color: "default",
|
|
66
|
+
size: "sm",
|
|
67
|
+
className: "h-8 ml-1 px-2 text-xs gap-1 whitespace-nowrap font-normal",
|
|
68
|
+
onClick: () => setRange(selected),
|
|
69
|
+
children: [
|
|
70
|
+
value.from,
|
|
71
|
+
" ~ ",
|
|
72
|
+
value.to,
|
|
73
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "w-3 h-3" })
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
) }),
|
|
77
|
+
/* @__PURE__ */ jsx(PopoverContent, { className: "w-auto max-w-[calc(100vw-32px)] p-0", align: "end", "aria-label": "选择日期范围", children: /* @__PURE__ */ jsx(
|
|
78
|
+
Calendar,
|
|
79
|
+
{
|
|
80
|
+
mode: "range",
|
|
81
|
+
selected: range,
|
|
82
|
+
onSelect: (r) => {
|
|
83
|
+
setRange(r);
|
|
84
|
+
if (r?.from && r?.to) onChange({ kind: "custom", from: fmt(r.from), to: fmt(r.to) });
|
|
85
|
+
},
|
|
86
|
+
numberOfMonths: 2
|
|
87
|
+
}
|
|
88
|
+
) })
|
|
89
|
+
] })
|
|
90
|
+
] });
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
CardDateFilter,
|
|
94
|
+
defaultCardDateRange
|
|
95
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Calendar } from './ui/calendar';
|
|
3
|
+
import { Input } from './ui/input';
|
|
4
|
+
export interface DateTimePickerProps extends Omit<React.ComponentProps<typeof Input>, 'value' | 'onChange' | 'type'> {
|
|
5
|
+
value: string;
|
|
6
|
+
onValueChange: (value: string) => void;
|
|
7
|
+
type?: 'date' | 'datetime';
|
|
8
|
+
/** 日历语言;例如 zh-CN。 */
|
|
9
|
+
locale?: React.ComponentProps<typeof Calendar>['locale'];
|
|
10
|
+
}
|
|
11
|
+
/** Local business date strings round-trip without UTC conversion or implicit defaults. */
|
|
12
|
+
export declare function DateTimePicker({ value, onValueChange, type, locale, disabled, readOnly, className, placeholder, min, max, ...props }: DateTimePickerProps): React.JSX.Element;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { CalendarDays } from "../icons/index.js";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { Button } from "./ui/button.js";
|
|
6
|
+
import { Calendar } from "./ui/calendar.js";
|
|
7
|
+
import { Input } from "./ui/input.js";
|
|
8
|
+
import { Label } from "./ui/label.js";
|
|
9
|
+
import { Popover, PopoverAnchor, PopoverTrigger, PopoverContent } from "./ui/popover.js";
|
|
10
|
+
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "./ui/select.js";
|
|
11
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
12
|
+
const dateText = (value) => `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
|
|
13
|
+
function calendarDate(value) {
|
|
14
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})(?:[ T]|$)/.exec(value);
|
|
15
|
+
if (!match) return;
|
|
16
|
+
const [year, month, day] = match.slice(1).map(Number);
|
|
17
|
+
const date = new Date(year, month - 1, day);
|
|
18
|
+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day ? date : void 0;
|
|
19
|
+
}
|
|
20
|
+
function DateTimePicker({ value, onValueChange, type = "date", locale, disabled, readOnly, className, placeholder, min, max, ...props }) {
|
|
21
|
+
const [open, setOpen] = React.useState(false);
|
|
22
|
+
const selected = calendarDate(value);
|
|
23
|
+
const minimum = calendarDate(String(min ?? "")), maximum = calendarDate(String(max ?? ""));
|
|
24
|
+
const time = /[ T](\d{2}):(\d{2})(?::(\d{2}))?/.exec(value)?.slice(1).map((part) => part || "00") ?? ["00", "00", "00"];
|
|
25
|
+
const label = props["aria-label"] || "日期";
|
|
26
|
+
const changeTime = (part, next) => {
|
|
27
|
+
if (!selected) return;
|
|
28
|
+
const parts = [...time];
|
|
29
|
+
parts[part] = next;
|
|
30
|
+
onValueChange(`${dateText(selected)} ${parts.join(":")}`);
|
|
31
|
+
};
|
|
32
|
+
return /* @__PURE__ */ jsxs(Popover, { open: open && !disabled && !readOnly, onOpenChange: setOpen, children: [
|
|
33
|
+
/* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: cn("relative min-w-0", className), children: [
|
|
34
|
+
/* @__PURE__ */ jsx(
|
|
35
|
+
Input,
|
|
36
|
+
{
|
|
37
|
+
...props,
|
|
38
|
+
type: "text",
|
|
39
|
+
value: value.replace("T", " "),
|
|
40
|
+
disabled,
|
|
41
|
+
readOnly,
|
|
42
|
+
className: "pr-10",
|
|
43
|
+
placeholder: placeholder ?? (type === "datetime" ? "年-月-日 时:分:秒" : "年-月-日"),
|
|
44
|
+
onChange: (event) => onValueChange(event.target.value.replace("T", " "))
|
|
45
|
+
}
|
|
46
|
+
),
|
|
47
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
48
|
+
Button,
|
|
49
|
+
{
|
|
50
|
+
type: "button",
|
|
51
|
+
variant: "ghost",
|
|
52
|
+
size: "icon-sm",
|
|
53
|
+
disabled: disabled || readOnly,
|
|
54
|
+
"aria-label": `选择${label}`,
|
|
55
|
+
className: "absolute inset-y-0 right-0.5 my-auto active:not-disabled:translate-y-0",
|
|
56
|
+
children: /* @__PURE__ */ jsx(CalendarDays, { className: "size-4" })
|
|
57
|
+
}
|
|
58
|
+
) })
|
|
59
|
+
] }) }),
|
|
60
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "w-auto p-0", "aria-label": `${label}选择器`, children: [
|
|
61
|
+
/* @__PURE__ */ jsx(
|
|
62
|
+
Calendar,
|
|
63
|
+
{
|
|
64
|
+
mode: "single",
|
|
65
|
+
locale,
|
|
66
|
+
selected,
|
|
67
|
+
defaultMonth: selected,
|
|
68
|
+
disabled: (date) => !!minimum && date < minimum || !!maximum && date > maximum,
|
|
69
|
+
onSelect: (date) => {
|
|
70
|
+
if (date) {
|
|
71
|
+
onValueChange(type === "datetime" ? `${dateText(date)} ${time.join(":")}` : dateText(date));
|
|
72
|
+
if (type === "date") setOpen(false);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
),
|
|
77
|
+
type === "datetime" && /* @__PURE__ */ jsx("div", { className: "flex gap-2 border-t px-3 py-2", children: ["时", "分", "秒"].map((unit, index) => /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
|
|
78
|
+
/* @__PURE__ */ jsx(Label, { className: "text-xs text-muted-foreground", children: unit }),
|
|
79
|
+
/* @__PURE__ */ jsxs(Select, { value: time[index], disabled: !selected, onValueChange: (next) => changeTime(index, next), children: [
|
|
80
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-full", "aria-label": `${label}${unit}`, children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
81
|
+
/* @__PURE__ */ jsx(SelectContent, { children: Array.from({ length: index === 0 ? 24 : 60 }, (_, n) => /* @__PURE__ */ jsx(SelectItem, { value: pad(n), children: pad(n) }, n)) })
|
|
82
|
+
] })
|
|
83
|
+
] }, unit)) }),
|
|
84
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between border-t px-3 py-2", children: [
|
|
85
|
+
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => {
|
|
86
|
+
onValueChange("");
|
|
87
|
+
setOpen(false);
|
|
88
|
+
}, children: "清空" }),
|
|
89
|
+
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: () => setOpen(false), children: "完成" })
|
|
90
|
+
] })
|
|
91
|
+
] })
|
|
92
|
+
] });
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
DateTimePicker
|
|
96
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface DetailField {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string | number;
|
|
4
|
+
type?: 'text' | 'badge' | 'status' | 'progress';
|
|
5
|
+
badgeVariant?: 'default' | 'destructive' | 'outline' | 'secondary';
|
|
6
|
+
color?: string;
|
|
7
|
+
suffix?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DetailAction {
|
|
10
|
+
label: string;
|
|
11
|
+
variant?: 'default' | 'destructive' | 'outline' | 'secondary';
|
|
12
|
+
onClick: () => void;
|
|
13
|
+
}
|
|
14
|
+
export interface CalculationStep {
|
|
15
|
+
step: number;
|
|
16
|
+
label: string;
|
|
17
|
+
formula?: string;
|
|
18
|
+
value?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ReportTable {
|
|
21
|
+
title: string;
|
|
22
|
+
headers: string[];
|
|
23
|
+
rows: (string | number)[][];
|
|
24
|
+
}
|
|
25
|
+
export interface ReportData {
|
|
26
|
+
calculation?: CalculationStep[];
|
|
27
|
+
source?: string;
|
|
28
|
+
period?: string;
|
|
29
|
+
table?: ReportTable[];
|
|
30
|
+
}
|
|
31
|
+
interface DetailDialogProps {
|
|
32
|
+
open: boolean;
|
|
33
|
+
onOpenChange: (open: boolean) => void;
|
|
34
|
+
title: string;
|
|
35
|
+
subtitle?: string;
|
|
36
|
+
icon?: React.ReactNode;
|
|
37
|
+
badge?: {
|
|
38
|
+
label: string;
|
|
39
|
+
variant?: 'default' | 'destructive' | 'outline' | 'secondary';
|
|
40
|
+
};
|
|
41
|
+
fields?: DetailField[];
|
|
42
|
+
actions?: DetailAction[];
|
|
43
|
+
children?: React.ReactNode;
|
|
44
|
+
width?: string;
|
|
45
|
+
report?: ReportData;
|
|
46
|
+
}
|
|
47
|
+
export declare function DetailDialog({ open, onOpenChange, title, subtitle, icon, badge, fields, actions, children, width, report, }: DetailDialogProps): import("react").JSX.Element;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { X, FileSpreadsheet, Calculator } from "../icons/index.js";
|
|
3
|
+
import { Dialog, DialogContent, DialogTitle } from "./ui/dialog.js";
|
|
4
|
+
import { ScrollArea } from "./ui/scroll-area.js";
|
|
5
|
+
import { Badge } from "./ui/badge.js";
|
|
6
|
+
import { Button } from "./ui/button.js";
|
|
7
|
+
function DetailDialog({
|
|
8
|
+
open,
|
|
9
|
+
onOpenChange,
|
|
10
|
+
title,
|
|
11
|
+
subtitle,
|
|
12
|
+
icon,
|
|
13
|
+
badge,
|
|
14
|
+
fields,
|
|
15
|
+
actions,
|
|
16
|
+
children,
|
|
17
|
+
width = "max-w-xl",
|
|
18
|
+
report
|
|
19
|
+
}) {
|
|
20
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { showCloseButton: false, className: `${width} p-0 gap-0 overflow-hidden`, children: [
|
|
21
|
+
/* @__PURE__ */ jsxs("div", { className: "px-6 py-4 border-b bg-[#f0f4f3]/80 flex items-center justify-between", children: [
|
|
22
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
23
|
+
icon && /* @__PURE__ */ jsx("div", { className: "w-9 h-9 rounded-lg bg-card border shadow-sm flex items-center justify-center text-[#475569]", children: icon }),
|
|
24
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
25
|
+
/* @__PURE__ */ jsx(DialogTitle, { className: "text-base font-semibold text-[#0f172a]", children: title }),
|
|
26
|
+
subtitle && /* @__PURE__ */ jsx("p", { className: "text-xs text-[#64748b] mt-0.5", children: subtitle })
|
|
27
|
+
] }),
|
|
28
|
+
badge && /* @__PURE__ */ jsx(Badge, { variant: badge.variant || "default", className: "ml-2 text-[10px]", children: badge.label })
|
|
29
|
+
] }),
|
|
30
|
+
/* @__PURE__ */ jsx("button", { onClick: () => onOpenChange(false), className: "w-7 h-7 rounded-md hover:bg-slate-200 flex items-center justify-center text-[#94a3b8]", children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" }) })
|
|
31
|
+
] }),
|
|
32
|
+
/* @__PURE__ */ jsx(ScrollArea, { className: "max-h-[65vh]", children: /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
|
|
33
|
+
fields && fields.length > 0 && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-x-6 gap-y-3 mb-5", children: fields.map((f, i) => /* @__PURE__ */ jsxs("div", { className: f.type === "progress" ? "col-span-2" : "", children: [
|
|
34
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-[#94a3b8] mb-0.5", children: f.label }),
|
|
35
|
+
f.type === "badge" ? /* @__PURE__ */ jsxs(Badge, { variant: f.badgeVariant || "default", className: "text-[10px]", children: [
|
|
36
|
+
f.value,
|
|
37
|
+
f.suffix
|
|
38
|
+
] }) : f.type === "status" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
39
|
+
/* @__PURE__ */ jsx("span", { className: `w-1.5 h-1.5 rounded-full ${f.color || "bg-slate-400"}` }),
|
|
40
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-[#334155]", children: f.value })
|
|
41
|
+
] }) : f.type === "progress" ? /* @__PURE__ */ jsxs("div", { children: [
|
|
42
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between mb-1", children: /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-[#334155]", children: [
|
|
43
|
+
f.value,
|
|
44
|
+
f.suffix
|
|
45
|
+
] }) }),
|
|
46
|
+
/* @__PURE__ */ jsx("div", { className: "w-full h-1.5 bg-[#e2e8e4] rounded-full overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "h-full rounded-full bg-[var(--primary)]", style: { width: `${Number(f.value)}%` } }) })
|
|
47
|
+
] }) : /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-[#334155]", children: [
|
|
48
|
+
f.value,
|
|
49
|
+
f.suffix && /* @__PURE__ */ jsx("span", { className: "text-[#94a3b8] font-normal ml-0.5", children: f.suffix })
|
|
50
|
+
] })
|
|
51
|
+
] }, i)) }),
|
|
52
|
+
!report && fields && fields.length > 0 && fields.some((f) => f.label === "数据来源") && /* @__PURE__ */ jsxs("div", { className: "bg-[var(--primary-soft)]/60 border border-[var(--primary)]/20 rounded-lg p-3", children: [
|
|
53
|
+
/* @__PURE__ */ jsxs("h4", { className: "text-sm font-semibold text-[var(--primary-hover)] mb-2 flex items-center gap-1.5", children: [
|
|
54
|
+
/* @__PURE__ */ jsx(FileSpreadsheet, { className: "w-3.5 h-3.5" }),
|
|
55
|
+
"数据明细"
|
|
56
|
+
] }),
|
|
57
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1 text-xs text-[#475569]", children: [
|
|
58
|
+
fields.filter((f) => f.label === "数据来源" || f.label === "统计周期" || f.label === "更新时间").map((f, i) => /* @__PURE__ */ jsxs("p", { children: [
|
|
59
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[#94a3b8]", children: [
|
|
60
|
+
f.label,
|
|
61
|
+
":"
|
|
62
|
+
] }),
|
|
63
|
+
f.value
|
|
64
|
+
] }, i)),
|
|
65
|
+
/* @__PURE__ */ jsx("p", { className: "pt-1 text-[#94a3b8] italic", children: "数据来源已验证,统计口径一致" })
|
|
66
|
+
] })
|
|
67
|
+
] }),
|
|
68
|
+
report && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
69
|
+
report.calculation && report.calculation.length > 0 && /* @__PURE__ */ jsxs("div", { className: "bg-amber-50/60 border border-amber-200 rounded-lg p-3", children: [
|
|
70
|
+
/* @__PURE__ */ jsxs("h4", { className: "text-sm font-semibold text-amber-800 mb-2 flex items-center gap-1.5", children: [
|
|
71
|
+
/* @__PURE__ */ jsx(Calculator, { className: "w-3.5 h-3.5" }),
|
|
72
|
+
"计算过程"
|
|
73
|
+
] }),
|
|
74
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: report.calculation.map((step, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xs", children: [
|
|
75
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 w-5 h-5 rounded-full bg-amber-200 text-amber-800 flex items-center justify-center font-bold text-[10px]", children: i + 1 }),
|
|
76
|
+
/* @__PURE__ */ jsx("span", { className: "text-[#334155] mt-0.5", children: typeof step === "string" ? step : `${step.label}${step.value ? ` = ${step.value}` : ""}${step.formula ? ` (${step.formula})` : ""}` })
|
|
77
|
+
] }, i)) })
|
|
78
|
+
] }),
|
|
79
|
+
report.table && report.table?.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-3", children: report.table.map((table, ti) => /* @__PURE__ */ jsxs("div", { children: [
|
|
80
|
+
/* @__PURE__ */ jsxs("h4", { className: "text-sm font-semibold text-[#0f172a] mb-1.5 flex items-center gap-1.5", children: [
|
|
81
|
+
/* @__PURE__ */ jsx(FileSpreadsheet, { className: "w-3.5 h-3.5 text-[var(--primary)]" }),
|
|
82
|
+
table.title
|
|
83
|
+
] }),
|
|
84
|
+
/* @__PURE__ */ jsx("div", { className: "border rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
|
|
85
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { className: "bg-[#e2e8e4]", children: table.headers.map((h, hi) => /* @__PURE__ */ jsx("th", { className: "px-2 py-1.5 text-left font-semibold text-[#334155]", children: h }, hi)) }) }),
|
|
86
|
+
/* @__PURE__ */ jsx("tbody", { children: table.rows.map((row, ri) => /* @__PURE__ */ jsx("tr", { className: ri % 2 === 0 ? "bg-card" : "bg-[#f0f4f3]/50", children: row.map((cell, ci) => /* @__PURE__ */ jsx("td", { className: "px-2 py-1.5 text-[#334155]", children: cell }, ci)) }, ri)) })
|
|
87
|
+
] }) })
|
|
88
|
+
] }, ti)) })
|
|
89
|
+
] }),
|
|
90
|
+
children
|
|
91
|
+
] }) }),
|
|
92
|
+
actions && actions.length > 0 && /* @__PURE__ */ jsx("div", { className: "px-6 py-3 border-t bg-[#f0f4f3]/50 flex items-center justify-end gap-2", children: actions.map((a, i) => /* @__PURE__ */ jsx(Button, { variant: a.variant || "outline", size: "sm", onClick: a.onClick, children: a.label }, i)) })
|
|
93
|
+
] }) });
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
DetailDialog
|
|
97
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface DocumentReference {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface DocumentLinksProps {
|
|
7
|
+
upstream?: DocumentReference[];
|
|
8
|
+
downstream?: DocumentReference[];
|
|
9
|
+
onOpen?: (document: DocumentReference, direction: 'upstream' | 'downstream') => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Relationship navigation for any document type. The host implements document loading and routing. */
|
|
13
|
+
export declare function DocumentLinks({ upstream, downstream, onOpen, className }: DocumentLinksProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowUpLeft, ArrowDownRight, FileText } from "../icons/index.js";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
function DocumentLinks({ upstream = [], downstream = [], onOpen, className }) {
|
|
5
|
+
if (!upstream.length && !downstream.length) return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "暂无关联单据" });
|
|
6
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap items-center gap-4", className), children: [["upstream", upstream], ["downstream", downstream]].filter(([, rows]) => rows.length).map(([direction, rows]) => /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
7
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
8
|
+
direction === "upstream" ? /* @__PURE__ */ jsx(ArrowUpLeft, { className: "size-3.5" }) : /* @__PURE__ */ jsx(ArrowDownRight, { className: "size-3.5" }),
|
|
9
|
+
direction === "upstream" ? "上游单据" : "下游单据"
|
|
10
|
+
] }),
|
|
11
|
+
rows.map((doc) => /* @__PURE__ */ jsxs("button", { type: "button", disabled: doc.disabled || !onOpen, onClick: () => onOpen?.(doc, direction), className: "inline-flex items-center gap-1.5 rounded-md border border-border bg-secondary px-3 py-1.5 text-sm text-primary hover:border-primary disabled:cursor-default disabled:opacity-60 focus-visible:ring-2 focus-visible:ring-ring", children: [
|
|
12
|
+
/* @__PURE__ */ jsx(FileText, { className: "size-3.5" }),
|
|
13
|
+
doc.label
|
|
14
|
+
] }, doc.id))
|
|
15
|
+
] }, direction)) });
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
DocumentLinks
|
|
19
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type DraftDeleteResult = string | null | void | Promise<string | null | void>;
|
|
2
|
+
export interface DraftRowActionsProps {
|
|
3
|
+
status: string;
|
|
4
|
+
docLabel: string;
|
|
5
|
+
docNo: string;
|
|
6
|
+
onEdit: () => void;
|
|
7
|
+
/** Return a nonempty message or reject to keep the dialog open; null/undefined means success. */
|
|
8
|
+
onDelete: () => DraftDeleteResult;
|
|
9
|
+
editableStatuses?: string[];
|
|
10
|
+
align?: 'center' | 'end';
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Reusable editable-record actions with deletion confirmation; no document store or deletion API. */
|
|
14
|
+
export declare function DraftRowActions({ status, docLabel, docNo, onEdit, onDelete, editableStatuses, align, disabled }: DraftRowActionsProps): import("react").JSX.Element | null;
|