paper-ink 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +149 -2
- package/dist/alert/index.d.ts +44 -0
- package/dist/alert/index.js +32 -0
- package/dist/alert/style.css +10 -0
- package/dist/app/index.d.ts +39 -0
- package/dist/app/index.js +117 -0
- package/dist/app/style.css +130 -0
- package/dist/auto-complete/index.d.ts +22 -0
- package/dist/auto-complete/index.js +54 -0
- package/dist/auto-complete/style.css +114 -0
- package/dist/avatar/index.d.ts +21 -0
- package/dist/avatar/index.js +25 -0
- package/dist/avatar/style.css +6 -0
- package/dist/badge/index.d.ts +14 -0
- package/dist/badge/index.js +13 -0
- package/dist/badge/style.css +11 -0
- package/dist/breadcrumb/index.d.ts +17 -0
- package/dist/breadcrumb/index.js +8 -0
- package/dist/breadcrumb/style.css +3 -0
- package/dist/button/button.d.ts +2 -0
- package/dist/button/button.js +41 -0
- package/dist/button/index.d.ts +2 -0
- package/dist/button/index.js +1 -0
- package/dist/button/style.css +53 -0
- package/dist/button/types.d.ts +32 -0
- package/dist/button/types.js +1 -0
- package/dist/card/index.d.ts +27 -0
- package/dist/card/index.js +15 -0
- package/dist/card/style.css +19 -0
- package/dist/checkbox/index.d.ts +41 -0
- package/dist/checkbox/index.js +82 -0
- package/dist/checkbox/style.css +21 -0
- package/dist/collapse/index.d.ts +30 -0
- package/dist/collapse/index.js +21 -0
- package/dist/collapse/style.css +3 -0
- package/dist/color-picker/color-picker.d.ts +2 -0
- package/dist/color-picker/color-picker.js +187 -0
- package/dist/color-picker/color.d.ts +36 -0
- package/dist/color-picker/color.js +56 -0
- package/dist/color-picker/index.d.ts +4 -0
- package/dist/color-picker/index.js +2 -0
- package/dist/color-picker/style.css +200 -0
- package/dist/color-picker/types.d.ts +31 -0
- package/dist/color-picker/types.js +1 -0
- package/dist/config-provider/index.d.ts +8 -0
- package/dist/config-provider/index.js +11 -0
- package/dist/date-picker/index.d.ts +7 -0
- package/dist/date-picker/index.js +9 -0
- package/dist/date-picker/style.css +65 -0
- package/dist/date-picker/types.d.ts +1 -0
- package/dist/date-picker/types.js +1 -0
- package/dist/date-time/style.css +63 -0
- package/dist/descriptions/index.d.ts +29 -0
- package/dist/descriptions/index.js +34 -0
- package/dist/descriptions/style.css +11 -0
- package/dist/divider/index.d.ts +13 -0
- package/dist/divider/index.js +10 -0
- package/dist/divider/style.css +10 -0
- package/dist/drawer/index.d.ts +24 -0
- package/dist/drawer/index.js +24 -0
- package/dist/drawer/style.css +103 -0
- package/dist/dropdown/index.d.ts +46 -0
- package/dist/dropdown/index.js +86 -0
- package/dist/dropdown/style.css +23 -0
- package/dist/empty/index.d.ts +14 -0
- package/dist/empty/index.js +12 -0
- package/dist/empty/style.css +7 -0
- package/dist/form/index.d.ts +18 -0
- package/dist/form/index.js +180 -0
- package/dist/form/store.d.ts +53 -0
- package/dist/form/store.js +247 -0
- package/dist/form/style.css +14 -0
- package/dist/form/types.d.ts +99 -0
- package/dist/form/types.js +1 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/use-localstorage-state.d.ts +3 -0
- package/dist/hooks/use-localstorage-state.js +42 -0
- package/dist/hooks/use-ref-state.d.ts +6 -0
- package/dist/hooks/use-ref-state.js +13 -0
- package/dist/hooks/useUpload.d.ts +31 -0
- package/dist/hooks/useUpload.js +173 -0
- package/dist/icons/index.d.ts +2 -0
- package/dist/icons/index.js +2 -0
- package/dist/image/image.d.ts +6 -0
- package/dist/image/image.js +76 -0
- package/dist/image/index.d.ts +2 -0
- package/dist/image/index.js +1 -0
- package/dist/image/style.css +98 -0
- package/dist/image/types.d.ts +30 -0
- package/dist/image/types.js +1 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +58 -0
- package/dist/inline/index.d.ts +5 -0
- package/dist/inline/index.js +6 -0
- package/dist/inline/style.css +3 -0
- package/dist/input/index.d.ts +10 -0
- package/dist/input/index.js +6 -0
- package/dist/input/input.d.ts +2 -0
- package/dist/input/input.js +33 -0
- package/dist/input/password.d.ts +2 -0
- package/dist/input/password.js +16 -0
- package/dist/input/search.d.ts +2 -0
- package/dist/input/search.js +42 -0
- package/dist/input/shared.d.ts +15 -0
- package/dist/input/shared.js +53 -0
- package/dist/input/style.css +105 -0
- package/dist/input/text-area.d.ts +2 -0
- package/dist/input/text-area.js +70 -0
- package/dist/input/types.d.ts +69 -0
- package/dist/input/types.js +1 -0
- package/dist/input-number/index.d.ts +44 -0
- package/dist/input-number/index.js +127 -0
- package/dist/input-number/style.css +129 -0
- package/dist/internal/date-time-control.d.ts +7 -0
- package/dist/internal/date-time-control.js +299 -0
- package/dist/internal/date-time-panels.d.ts +19 -0
- package/dist/internal/date-time-panels.js +92 -0
- package/dist/internal/date-time-types.d.ts +84 -0
- package/dist/internal/date-time-types.js +1 -0
- package/dist/internal/date-time.d.ts +65 -0
- package/dist/internal/date-time.js +20 -0
- package/dist/internal/field-binding.d.ts +14 -0
- package/dist/internal/field-binding.js +17 -0
- package/dist/internal/icons.d.ts +23 -0
- package/dist/internal/icons.js +53 -0
- package/dist/internal/inert.d.ts +2 -0
- package/dist/internal/inert.js +6 -0
- package/dist/internal/motion.d.ts +3 -0
- package/dist/internal/motion.js +22 -0
- package/dist/layout/index.d.ts +23 -0
- package/dist/layout/index.js +68 -0
- package/dist/layout/shared.d.ts +8 -0
- package/dist/layout/shared.js +4 -0
- package/dist/layout/style.css +18 -0
- package/dist/menu/index.d.ts +2 -0
- package/dist/menu/index.js +1 -0
- package/dist/menu/menu.d.ts +2 -0
- package/dist/menu/menu.js +322 -0
- package/dist/menu/style.css +45 -0
- package/dist/menu/types.d.ts +64 -0
- package/dist/menu/types.js +1 -0
- package/dist/message/index.d.ts +2 -0
- package/dist/message/index.js +1 -0
- package/dist/message/message.d.ts +7 -0
- package/dist/message/message.js +109 -0
- package/dist/message/style.css +23 -0
- package/dist/message/types.d.ts +31 -0
- package/dist/message/types.js +1 -0
- package/dist/modal/index.d.ts +2 -0
- package/dist/modal/index.js +1 -0
- package/dist/modal/modal.d.ts +2 -0
- package/dist/modal/modal.js +137 -0
- package/dist/modal/style.css +79 -0
- package/dist/modal/types.d.ts +29 -0
- package/dist/modal/types.js +1 -0
- package/dist/notification/index.d.ts +2 -0
- package/dist/notification/index.js +1 -0
- package/dist/notification/notification.d.ts +7 -0
- package/dist/notification/notification.js +100 -0
- package/dist/notification/style.css +19 -0
- package/dist/notification/types.d.ts +36 -0
- package/dist/notification/types.js +1 -0
- package/dist/pagination/index.d.ts +27 -0
- package/dist/pagination/index.js +40 -0
- package/dist/pagination/style.css +68 -0
- package/dist/popconfirm/index.d.ts +19 -0
- package/dist/popconfirm/index.js +39 -0
- package/dist/popconfirm/style.css +71 -0
- package/dist/popover/index.d.ts +2 -0
- package/dist/popover/index.js +1 -0
- package/dist/popover/popover.d.ts +2 -0
- package/dist/popover/popover.js +122 -0
- package/dist/popover/style.css +12 -0
- package/dist/popover/types.d.ts +23 -0
- package/dist/popover/types.js +1 -0
- package/dist/progress/index.d.ts +21 -0
- package/dist/progress/index.js +26 -0
- package/dist/progress/style.css +3 -0
- package/dist/radio/index.d.ts +45 -0
- package/dist/radio/index.js +93 -0
- package/dist/radio/style.css +32 -0
- package/dist/rate/index.d.ts +23 -0
- package/dist/rate/index.js +40 -0
- package/dist/rate/style.css +14 -0
- package/dist/result/index.d.ts +14 -0
- package/dist/result/index.js +14 -0
- package/dist/result/style.css +8 -0
- package/dist/segmented/index.d.ts +22 -0
- package/dist/segmented/index.js +22 -0
- package/dist/segmented/style.css +8 -0
- package/dist/select/index.d.ts +2 -0
- package/dist/select/index.js +1 -0
- package/dist/select/select.d.ts +3 -0
- package/dist/select/select.js +213 -0
- package/dist/select/style.css +54 -0
- package/dist/select/types.d.ts +47 -0
- package/dist/select/types.js +1 -0
- package/dist/simple-grid/index.d.ts +23 -0
- package/dist/simple-grid/index.js +30 -0
- package/dist/simple-grid/style.css +4 -0
- package/dist/skeleton/index.d.ts +37 -0
- package/dist/skeleton/index.js +10 -0
- package/dist/skeleton/style.css +3 -0
- package/dist/slider/index.d.ts +43 -0
- package/dist/slider/index.js +67 -0
- package/dist/slider/style.css +31 -0
- package/dist/spin/index.d.ts +15 -0
- package/dist/spin/index.js +32 -0
- package/dist/spin/style.css +15 -0
- package/dist/splitter/index.d.ts +22 -0
- package/dist/splitter/index.js +124 -0
- package/dist/splitter/style.css +26 -0
- package/dist/stack/index.d.ts +4 -0
- package/dist/stack/index.js +6 -0
- package/dist/stack/style.css +3 -0
- package/dist/statistic/index.d.ts +18 -0
- package/dist/statistic/index.js +21 -0
- package/dist/statistic/style.css +9 -0
- package/dist/steps/index.d.ts +22 -0
- package/dist/steps/index.js +14 -0
- package/dist/steps/style.css +30 -0
- package/dist/style.css +941 -0
- package/dist/styles/theme.css +1 -0
- package/dist/switch/index.d.ts +14 -0
- package/dist/switch/index.js +21 -0
- package/dist/switch/style.css +18 -0
- package/dist/table/index.d.ts +3 -0
- package/dist/table/index.js +309 -0
- package/dist/table/spans.d.ts +12 -0
- package/dist/table/spans.js +53 -0
- package/dist/table/style.css +192 -0
- package/dist/table/types.d.ts +123 -0
- package/dist/table/types.js +1 -0
- package/dist/table-hooks/index.d.ts +89 -0
- package/dist/table-hooks/index.js +238 -0
- package/dist/tabs/index.d.ts +25 -0
- package/dist/tabs/index.js +57 -0
- package/dist/tabs/style.css +20 -0
- package/dist/tag/index.d.ts +19 -0
- package/dist/tag/index.js +30 -0
- package/dist/tag/style.css +11 -0
- package/dist/theme/defaults.d.ts +2 -0
- package/dist/theme/defaults.js +18 -0
- package/dist/theme/resolve.d.ts +4 -0
- package/dist/theme/resolve.js +20 -0
- package/dist/theme/types.d.ts +92 -0
- package/dist/theme/types.js +1 -0
- package/dist/theme/variables.d.ts +3 -0
- package/dist/theme/variables.js +23 -0
- package/dist/time-picker/index.d.ts +4 -0
- package/dist/time-picker/index.js +6 -0
- package/dist/time-picker/style.css +65 -0
- package/dist/timeline/index.d.ts +23 -0
- package/dist/timeline/index.js +28 -0
- package/dist/timeline/style.css +23 -0
- package/dist/tooltip/index.d.ts +2 -0
- package/dist/tooltip/index.js +1 -0
- package/dist/tooltip/style.css +11 -0
- package/dist/tooltip/tooltip.d.ts +2 -0
- package/dist/tooltip/tooltip.js +115 -0
- package/dist/tooltip/types.d.ts +19 -0
- package/dist/tooltip/types.js +1 -0
- package/dist/typography/index.d.ts +39 -0
- package/dist/typography/index.js +46 -0
- package/dist/typography/style.css +13 -0
- package/dist/upload/index.d.ts +2 -0
- package/dist/upload/index.js +1 -0
- package/dist/upload/style.css +116 -0
- package/dist/upload/types.d.ts +55 -0
- package/dist/upload/types.js +1 -0
- package/dist/upload/upload.d.ts +9 -0
- package/dist/upload/upload.js +186 -0
- package/dist/use-localstorage-state/index.d.ts +1 -0
- package/dist/use-localstorage-state/index.js +1 -0
- package/dist/use-ref-state/index.d.ts +1 -0
- package/dist/use-ref-state/index.js +2 -0
- package/dist/use-upload/index.d.ts +1 -0
- package/dist/use-upload/index.js +1 -0
- package/dist/util/index.d.ts +11 -0
- package/dist/util/index.js +1 -0
- package/package.json +353 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Paper Ink contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,150 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Paper Ink
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**以黑白为底、语义为色的轻量 React 组件库。**
|
|
4
|
+
|
|
5
|
+
Paper Ink 追求清晰、克制的水墨风格:足够对比度的黑白、淡色边界,以及简洁的交互。常用组件 API 参考 Ant Design,提供组件包和 CLI 源码安装两种使用方式,既能直接使用,也能将源码带回项目自由修改。
|
|
6
|
+
|
|
7
|
+
[](https://tingxi8087.github.io/paper-ink/#/showcase)
|
|
8
|
+
|
|
9
|
+
[在线文档](https://tingxi8087.github.io/paper-ink/) · [组件画廊](https://tingxi8087.github.io/paper-ink/#/showcase) · [快速开始](https://tingxi8087.github.io/paper-ink/#/guide) · [主题实验室](https://tingxi8087.github.io/paper-ink/#/theme)
|
|
10
|
+
|
|
11
|
+
> 当前版本为 **0.2.0**。面向 React 18+、ReactDOM 和 TypeScript 项目。
|
|
12
|
+
|
|
13
|
+
## 为什么选择 Paper Ink
|
|
14
|
+
|
|
15
|
+
- **聚焦常用能力。** 精简 API 的重复配置,支持组件 JS 与 CSS 按需引入;React 和 ReactDOM 为 peer dependencies,不打包进组件库。
|
|
16
|
+
- **追求更少的包装层。** 使用简洁的 DOM 与 CSS,减少不必要的节点嵌套,便于理解结构、覆盖样式和调试。这是设计原则,不代表所有组件都比其他库节点更少。
|
|
17
|
+
- **包与源码两种分发。** 包引入方便统一升级;CLI 按需安装组件源码、共享模块和所需依赖,适合二次开发。
|
|
18
|
+
- **极简的默认风格。** 黑白主色、清晰对比度、统一 SVG 图标与克制动效,链接和状态保留必要的语义色。
|
|
19
|
+
- **熟悉的 API,明确的范围。** 常用属性参考 Ant Design;复杂的 Form、Table、布局和 Upload 按轻量思路独立设计,各组件文档列出支持的 API 与边界。
|
|
20
|
+
- **清晰的主题结构。** 按视觉用途组织主题变量,支持 ConfigProvider、嵌套继承和组件覆盖;主题实验室可以直接试验与导出配置。
|
|
21
|
+
- **可以实际体验的文档。** 交互案例、在线 JSX 编辑预览、组件画廊与主题工具,帮助你确认组件是否适合自己的项目。
|
|
22
|
+
- **MIT 开源。** TypeScript 源码、公开类型与行为测试,使用和修改自由。
|
|
23
|
+
|
|
24
|
+
“轻量”指聚焦能力、按需分发与可理解的实现。当前 Button 示例测量为 JS **1,944 bytes**、CSS **1,318 bytes**(gzip;JS 经 esbuild 打包压缩,React / ReactDOM 外置,CSS 为构建后的独立样式)。可用 `pnpm check:size` 复测;不同组件会带入各自需要的依赖,这不是与其他库的对比基准。
|
|
25
|
+
|
|
26
|
+
## 安装与使用
|
|
27
|
+
|
|
28
|
+
### 方式一:组件包
|
|
29
|
+
|
|
30
|
+
在已有 React 项目中,选择项目使用的包管理器安装:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install paper-ink
|
|
34
|
+
# 或 cnpm install paper-ink
|
|
35
|
+
# 或 yarn add paper-ink
|
|
36
|
+
# 或 pnpm add paper-ink
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
React 和 ReactDOM 需要由项目提供;已有 React 项目一般已安装。
|
|
40
|
+
|
|
41
|
+
在应用中导入组件和样式:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { Button, Input, Stack } from 'paper-ink';
|
|
45
|
+
import 'paper-ink/style.css';
|
|
46
|
+
|
|
47
|
+
export default function App() {
|
|
48
|
+
return (
|
|
49
|
+
<Stack gap="medium">
|
|
50
|
+
<Input placeholder="输入一些文字" />
|
|
51
|
+
<Button type="primary">保存</Button>
|
|
52
|
+
</Stack>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
也可以只引入需要的组件与样式:
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { Button } from 'paper-ink/button';
|
|
61
|
+
import 'paper-ink/button/style.css';
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
组件 CSS 子路径包含该组件所需的样式依赖。全量样式和按需样式选择一种即可。React 与 ReactDOM 由消费项目提供。
|
|
65
|
+
|
|
66
|
+
### 方式二:CLI 安装源码
|
|
67
|
+
|
|
68
|
+
在你的 React + TypeScript 项目根目录执行:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx @tingxi8087/paper-ink-cli init
|
|
72
|
+
npx @tingxi8087/paper-ink-cli add button input --dry-run
|
|
73
|
+
npx @tingxi8087/paper-ink-cli add button input
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
也可以使用 `pnpm dlx @tingxi8087/paper-ink-cli`。CLI 需要 Node.js 20+,无需安装组件包即可使用本地源码。
|
|
77
|
+
|
|
78
|
+
从目标项目的 `src/App.tsx` 引入:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { Button, Input } from './components/paper-ink';
|
|
82
|
+
import './components/paper-ink/style.css';
|
|
83
|
+
|
|
84
|
+
export default function App() {
|
|
85
|
+
return <><Input placeholder="可以直接修改组件源码" /><Button>保存</Button></>;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
有 `src` 时默认安装到 `src/components/paper-ink`,否则为 `components/paper-ink`。可用 `init --path src/ui/paper-ink` 自定义目录;已有 `@/` 别名也可用于引入,CLI 不修改项目别名。
|
|
90
|
+
|
|
91
|
+
CLI 自动解析共享模块与组件依赖,支持 `list`、`--dry-run` 和 `--no-install`。再次安装保留已修改文件;无归属的冲突文件会停止安装,明确传入 `--overwrite` 才覆盖。请将源码、`paper-ink.json` 与 `paper-ink.lock.json` 一起提交。当前没有自动合并上游修改的更新命令。
|
|
92
|
+
|
|
93
|
+
包与源码分别拥有主题上下文,同一个组件树中建议保持一种来源。源码分发细节见 [分发说明](https://github.com/tingxi8087/paper-ink/blob/main/doc/distribution.md) 和 [CLI 文档](https://github.com/tingxi8087/paper-ink/blob/main/packages/cli/README.md)。
|
|
94
|
+
|
|
95
|
+
<details>
|
|
96
|
+
<summary>从仓库构建安装(组件开发与离线分发)</summary>
|
|
97
|
+
|
|
98
|
+
要求 Node.js 22.12+、pnpm 10:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
git clone https://github.com/tingxi8087/paper-ink.git
|
|
102
|
+
cd paper-ink
|
|
103
|
+
pnpm install
|
|
104
|
+
pnpm --filter paper-ink build
|
|
105
|
+
pnpm --dir packages/paper-ink pack --pack-destination ../../.artifacts
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
将生成的 tgz 文件复制到消费项目后执行 `npm install ./paper-ink-0.2.0.tgz`。本仓库也可通过 `pnpm build:registry` 与 `pnpm cli` 使用未发布的本地 CLI 源码。
|
|
109
|
+
|
|
110
|
+
</details>
|
|
111
|
+
|
|
112
|
+
## 组件与工具
|
|
113
|
+
|
|
114
|
+
| 类型 | 代表组件 |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| 通用与布局 | Button、Typography、Divider、Stack、Inline、SimpleGrid、Layout、Splitter |
|
|
117
|
+
| 数据录入 | Input / Search / Password / TextArea、Select、AutoComplete、Checkbox、Radio、Switch、InputNumber、Slider、Rate、DatePicker、TimePicker、ColorPicker、Upload |
|
|
118
|
+
| 数据展示 | Card、Avatar、Badge、Tag、Descriptions、Statistic、Image、Timeline、Table |
|
|
119
|
+
| 导航与反馈 | Menu、Tabs、Breadcrumb、Steps、Pagination、Dropdown、Tooltip、Popover、Popconfirm、Modal、Drawer、Alert、Message、Notification、Progress、Spin、Skeleton、Empty、Result |
|
|
120
|
+
| 状态与业务工具 | Form、ConfigProvider、App、useBasePageTable、useTableChecked、useRefState、useLocalstorageState、useUpload |
|
|
121
|
+
|
|
122
|
+
这是概览,不代表完整实现对应 Ant Design API。具体用法、示例与支持范围以 [在线文档](https://tingxi8087.github.io/paper-ink/) 为准。组件规划见 [状态清单](https://github.com/tingxi8087/paper-ink/blob/main/doc/component-priorities.md)。
|
|
123
|
+
|
|
124
|
+
## 图标
|
|
125
|
+
|
|
126
|
+
公开图标库采用 [Tabler Icons](https://tabler.io/icons),MIT 许可。安装 Paper Ink 时会自动安装依赖,不需要单独安装图标包:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { IconSearch, IconPlus } from 'paper-ink/icons';
|
|
130
|
+
import { Button } from 'paper-ink';
|
|
131
|
+
|
|
132
|
+
<IconSearch size={20} stroke={2} />;
|
|
133
|
+
<Button icon={<IconPlus size={16} />}>新建</Button>;
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
保留 Tabler 官方组件名称和属性,支持按需打包,无图标 CSS 和额外包装节点。[图标目录](https://tingxi8087.github.io/paper-ink/#/icons)支持搜索、筛选、复制代码与居中辅助线。
|
|
137
|
+
|
|
138
|
+
源码安装:`npx @tingxi8087/paper-ink-cli add tabler-icons`,CLI 自动处理依赖。
|
|
139
|
+
|
|
140
|
+
## 与 Ant Design 的关系
|
|
141
|
+
|
|
142
|
+
Paper Ink 的常用组件 API 与交互习惯参考 [Ant Design](https://ant.design/),采用独立实现,不依赖 `antd`;提供常用 API 子集,主题及部分复杂组件采用独立设计。它不是 Ant Design 的完整替代,也不保证直接迁移所有现有用法。
|
|
143
|
+
|
|
144
|
+
感谢 Ant Design 提供的设计与 API 参考。项目使用的 Day.js、@ctrl/tinycolor 和 @rc-component/mini-decimal 等依赖各自遵循其开源许可证。
|
|
145
|
+
|
|
146
|
+
## 开发与贡献
|
|
147
|
+
|
|
148
|
+
源码、设计文档与贡献说明见 [GitHub 仓库](https://github.com/tingxi8087/paper-ink)。
|
|
149
|
+
|
|
150
|
+
[MIT License](LICENSE)。
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Component, type AriaAttributes, type HTMLAttributes, type MouseEvent, type ReactNode } from 'react';
|
|
2
|
+
export interface AlertClosable extends AriaAttributes {
|
|
3
|
+
closeIcon?: ReactNode;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
onClose?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
6
|
+
afterClose?: () => void;
|
|
7
|
+
}
|
|
8
|
+
export interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
|
|
9
|
+
title?: ReactNode;
|
|
10
|
+
message?: ReactNode;
|
|
11
|
+
description?: ReactNode;
|
|
12
|
+
type?: 'success' | 'info' | 'warning' | 'error';
|
|
13
|
+
showIcon?: boolean;
|
|
14
|
+
icon?: ReactNode;
|
|
15
|
+
banner?: boolean;
|
|
16
|
+
action?: ReactNode;
|
|
17
|
+
variant?: 'outlined' | 'filled';
|
|
18
|
+
closable?: boolean | AlertClosable;
|
|
19
|
+
closeIcon?: ReactNode;
|
|
20
|
+
closeText?: ReactNode;
|
|
21
|
+
onClose?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
22
|
+
afterClose?: () => void;
|
|
23
|
+
}
|
|
24
|
+
export interface AlertErrorBoundaryProps {
|
|
25
|
+
title?: ReactNode;
|
|
26
|
+
message?: ReactNode;
|
|
27
|
+
description?: ReactNode;
|
|
28
|
+
children?: ReactNode;
|
|
29
|
+
}
|
|
30
|
+
declare class ErrorBoundary extends Component<AlertErrorBoundaryProps, {
|
|
31
|
+
error: Error | null;
|
|
32
|
+
}> {
|
|
33
|
+
state: {
|
|
34
|
+
error: Error | null;
|
|
35
|
+
};
|
|
36
|
+
static getDerivedStateFromError(error: Error): {
|
|
37
|
+
error: Error;
|
|
38
|
+
};
|
|
39
|
+
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;
|
|
40
|
+
}
|
|
41
|
+
export declare const Alert: import("react").ForwardRefExoticComponent<AlertProps & import("react").RefAttributes<HTMLDivElement>> & {
|
|
42
|
+
ErrorBoundary: typeof ErrorBoundary;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CloseIcon, StatusIcon } from '../internal/icons.js';
|
|
3
|
+
import { Component, forwardRef, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { useTheme } from '../config-provider/index.js';
|
|
5
|
+
import { themeVariables } from '../theme/variables.js';
|
|
6
|
+
const AlertRoot = forwardRef(function Alert({ title, message, description, type, showIcon, icon, banner = false, action, variant = 'outlined', closable = false, closeIcon, closeText, onClose, afterClose, className, style, ...rest }, ref) {
|
|
7
|
+
const theme = useTheme();
|
|
8
|
+
const [closed, setClosed] = useState(false);
|
|
9
|
+
const resolvedType = type ?? (banner ? 'warning' : 'info');
|
|
10
|
+
const tone = theme.token.color[resolvedType];
|
|
11
|
+
const close = typeof closable === 'object' ? closable : {};
|
|
12
|
+
const onAfter = close.afterClose ?? afterClose;
|
|
13
|
+
const notified = useRef(false);
|
|
14
|
+
useEffect(() => { if (closed && !notified.current) {
|
|
15
|
+
notified.current = true;
|
|
16
|
+
onAfter?.();
|
|
17
|
+
} }, [closed, onAfter]);
|
|
18
|
+
if (closed)
|
|
19
|
+
return null;
|
|
20
|
+
const { closeIcon: objectIcon, disabled, onClose: objectOnClose, afterClose: ignored, ...closeAria } = close;
|
|
21
|
+
return _jsxs("div", { ...rest, ref: ref, role: rest.role ?? 'alert', className: ['pi-alert', banner && 'pi-alert--banner', className].filter(Boolean).join(' '), style: { ...themeVariables(theme), background: tone.subtle, borderColor: variant === 'filled' || banner ? 'transparent' : tone.border, ...style }, children: [(showIcon ?? banner) && _jsx("span", { className: "pi-alert__icon", "aria-hidden": "true", style: { color: tone.solid }, children: icon ?? _jsx(StatusIcon, { type: resolvedType, circle: true, width: 20, height: 20 }) }), _jsxs("div", { className: "pi-alert__content", children: [_jsx("div", { className: "pi-alert__title", children: title ?? message }), description != null && _jsx("div", { className: "pi-alert__description", children: description })] }), action != null && _jsx("div", { className: "pi-alert__action", children: action }), (closable || closeText != null) && _jsx("button", { ...closeAria, type: "button", "aria-label": closeAria['aria-label'] ?? '关闭提示', className: "pi-alert__close", disabled: disabled, onClick: event => { (objectOnClose ?? onClose)?.(event); if (!event.defaultPrevented)
|
|
22
|
+
setClosed(true); }, children: objectIcon ?? closeIcon ?? closeText ?? _jsx(CloseIcon, {}) })] });
|
|
23
|
+
});
|
|
24
|
+
class ErrorBoundary extends Component {
|
|
25
|
+
constructor() {
|
|
26
|
+
super(...arguments);
|
|
27
|
+
this.state = { error: null };
|
|
28
|
+
}
|
|
29
|
+
static getDerivedStateFromError(error) { return { error }; }
|
|
30
|
+
render() { return this.state.error ? _jsx(AlertRoot, { type: "error", showIcon: true, title: this.props.title ?? this.props.message ?? this.state.error.message, description: this.props.description }) : this.props.children; }
|
|
31
|
+
}
|
|
32
|
+
export const Alert = Object.assign(AlertRoot, { ErrorBoundary });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
.pi-theme { display: contents; }
|
|
2
|
+
|
|
3
|
+
.pi-alert { display: flex; align-items: flex-start; gap: var(--pi-spacing-medium); min-width: 0; padding: 8px 12px; border: 1px solid; border-radius: var(--pi-radius-control); color: var(--pi-color-text-primary); font-family: var(--pi-font-family); font-size: var(--pi-font-size); line-height: 1.5; }
|
|
4
|
+
.pi-alert--banner { border-radius: 0; }
|
|
5
|
+
.pi-alert__icon { display: inline-flex; justify-content: center; align-items: center; flex: none; font-weight: 600; width: 20px; height: 20px; font-size: 12px; }
|
|
6
|
+
.pi-alert__content { flex: 1; min-width: 0; overflow-wrap: anywhere; }
|
|
7
|
+
.pi-alert__description { margin-top: 4px; color: var(--pi-color-text-secondary); font-size: 13px; }
|
|
8
|
+
.pi-alert__action { flex: none; }
|
|
9
|
+
.pi-alert__close { display: inline-flex; align-items: center; justify-content: center; padding: 0; border: 0; width: 20px; min-height: 20px; background: transparent; color: var(--pi-color-text-secondary); font: inherit; cursor: pointer; }
|
|
10
|
+
.pi-alert__close:disabled { cursor: not-allowed; opacity: .5; }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode, type ElementType } from 'react';
|
|
2
|
+
import type { ModalProps } from '../modal/types.js';
|
|
3
|
+
import type { MessageConfig, MessageInstance } from '../message/types.js';
|
|
4
|
+
import type { NotificationConfig, NotificationInstance } from '../notification/types.js';
|
|
5
|
+
export interface ModalConfirmConfig extends Omit<ModalProps, 'children' | 'content' | 'open' | 'onOk' | 'onCancel' | 'afterClose'> {
|
|
6
|
+
content?: ReactNode;
|
|
7
|
+
icon?: ReactNode;
|
|
8
|
+
afterClose?: () => void;
|
|
9
|
+
onOk?: () => void | Promise<unknown>;
|
|
10
|
+
onCancel?: () => void | Promise<unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface ModalConfirmHandle extends PromiseLike<boolean> {
|
|
13
|
+
destroy: () => void;
|
|
14
|
+
update: (config: Partial<ModalConfirmConfig> | ((previous: ModalConfirmConfig) => Partial<ModalConfirmConfig>)) => void;
|
|
15
|
+
}
|
|
16
|
+
export interface AppModalInstance {
|
|
17
|
+
confirm: (config: ModalConfirmConfig) => ModalConfirmHandle;
|
|
18
|
+
info: (config: ModalConfirmConfig) => ModalConfirmHandle;
|
|
19
|
+
success: (config: ModalConfirmConfig) => ModalConfirmHandle;
|
|
20
|
+
error: (config: ModalConfirmConfig) => ModalConfirmHandle;
|
|
21
|
+
warning: (config: ModalConfirmConfig) => ModalConfirmHandle;
|
|
22
|
+
destroyAll: () => void;
|
|
23
|
+
}
|
|
24
|
+
export interface AppInstance {
|
|
25
|
+
message: MessageInstance;
|
|
26
|
+
modal: AppModalInstance;
|
|
27
|
+
notification: NotificationInstance;
|
|
28
|
+
}
|
|
29
|
+
export interface AppProps extends HTMLAttributes<HTMLDivElement> {
|
|
30
|
+
component?: ElementType | false;
|
|
31
|
+
message?: MessageConfig;
|
|
32
|
+
notification?: NotificationConfig;
|
|
33
|
+
}
|
|
34
|
+
declare function AppRoot({ children, component, message: config, notification: notificationConfig, className, style, ...rest }: AppProps): import("react").JSX.Element;
|
|
35
|
+
declare function useApp(): AppInstance;
|
|
36
|
+
export declare const App: typeof AppRoot & {
|
|
37
|
+
useApp: typeof useApp;
|
|
38
|
+
};
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Button } from '../button/button.js';
|
|
4
|
+
import { Modal } from '../modal/modal.js';
|
|
5
|
+
import { useMessage } from '../message/message.js';
|
|
6
|
+
import { useNotification } from '../notification/index.js';
|
|
7
|
+
import { useTheme } from '../config-provider/index.js';
|
|
8
|
+
import { themeVariables } from '../theme/variables.js';
|
|
9
|
+
import { StatusIcon } from '../internal/icons.js';
|
|
10
|
+
const Context = createContext(null);
|
|
11
|
+
function ConfirmDialog({ entry, close, complete }) {
|
|
12
|
+
const [pending, setPending] = useState(false);
|
|
13
|
+
const [error, setError] = useState(false);
|
|
14
|
+
const alive = useRef(true);
|
|
15
|
+
const busy = useRef(false);
|
|
16
|
+
useEffect(() => { alive.current = true; return () => { alive.current = false; }; }, []);
|
|
17
|
+
const { content, icon, onOk, onCancel, afterClose, ...props } = entry.config;
|
|
18
|
+
async function act(confirmed) {
|
|
19
|
+
if (busy.current || entry.closing !== undefined)
|
|
20
|
+
return;
|
|
21
|
+
busy.current = true;
|
|
22
|
+
setPending(true);
|
|
23
|
+
setError(false);
|
|
24
|
+
try {
|
|
25
|
+
await (confirmed ? onOk?.() : onCancel?.());
|
|
26
|
+
if (alive.current)
|
|
27
|
+
close(entry.key, confirmed);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
if (alive.current)
|
|
31
|
+
setError(true);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
busy.current = false;
|
|
35
|
+
if (alive.current)
|
|
36
|
+
setPending(false);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const status = entry.type === 'confirm' ? 'warning' : entry.type;
|
|
40
|
+
return _jsx(Modal, { ...props, open: entry.closing === undefined, afterClose: () => complete(entry.key), centered: props.centered ?? true, closable: props.closable ?? false, maskClosable: props.maskClosable ?? false, onOk: () => { void act(true); }, onCancel: () => { void act(false); }, confirmLoading: pending, cancelButtonProps: { ...props.cancelButtonProps, disabled: pending || props.cancelButtonProps?.disabled }, footer: props.footer === undefined ? (entry.type !== 'confirm' ? _jsx(Button, { type: "primary", ...props.okButtonProps, loading: pending, onClick: event => { props.okButtonProps?.onClick?.(event); if (!event.defaultPrevented)
|
|
41
|
+
void act(true); }, children: props.okText ?? '知道了' }) : undefined) : props.footer, children: _jsxs("div", { className: "pi-app-confirm-content", children: [icon !== null && _jsx("span", { className: `pi-app-confirm-icon pi-app-confirm-icon--${status}`, "aria-hidden": "true", children: icon ?? _jsx(StatusIcon, { type: status, circle: true, width: 20, height: 20 }) }), _jsxs("div", { children: [content, error && _jsx("p", { role: "alert", className: "pi-app-confirm-error", children: "\u64CD\u4F5C\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5\u3002" })] })] }) });
|
|
42
|
+
}
|
|
43
|
+
function AppRoot({ children, component = 'div', message: config, notification: notificationConfig, className, style, ...rest }) {
|
|
44
|
+
const theme = useTheme();
|
|
45
|
+
const [api, holder] = useMessage(config);
|
|
46
|
+
const [entries, setEntries] = useState([]);
|
|
47
|
+
const [notificationApi, notificationHolder] = useNotification(notificationConfig);
|
|
48
|
+
const afterCloseQueue = useRef([]);
|
|
49
|
+
const entriesRef = useRef([]);
|
|
50
|
+
const sequence = useRef(0);
|
|
51
|
+
const mounted = useRef(true);
|
|
52
|
+
const lifecycle = useRef(0);
|
|
53
|
+
function close(key, confirmed) {
|
|
54
|
+
const entry = entriesRef.current.find(item => item.key === key);
|
|
55
|
+
if (!entry || entry.closing !== undefined)
|
|
56
|
+
return;
|
|
57
|
+
entriesRef.current = entriesRef.current.map(item => item.key === key ? { ...item, closing: confirmed } : item);
|
|
58
|
+
setEntries(entriesRef.current);
|
|
59
|
+
}
|
|
60
|
+
function complete(key) {
|
|
61
|
+
const entry = entriesRef.current.find(item => item.key === key);
|
|
62
|
+
if (!entry)
|
|
63
|
+
return;
|
|
64
|
+
entriesRef.current = entriesRef.current.filter(item => item.key !== key);
|
|
65
|
+
setEntries(entriesRef.current);
|
|
66
|
+
entry.finish(entry.closing ?? false);
|
|
67
|
+
afterCloseQueue.current.push(entry);
|
|
68
|
+
}
|
|
69
|
+
const modal = useMemo(() => {
|
|
70
|
+
const open = (type, config) => {
|
|
71
|
+
let finish;
|
|
72
|
+
const promise = new Promise(resolve => { finish = resolve; });
|
|
73
|
+
const key = ++sequence.current;
|
|
74
|
+
if (!mounted.current)
|
|
75
|
+
finish(false);
|
|
76
|
+
else {
|
|
77
|
+
entriesRef.current = [...entriesRef.current, { key, type, config, finish }];
|
|
78
|
+
setEntries(entriesRef.current);
|
|
79
|
+
}
|
|
80
|
+
return { destroy: () => close(key, false), update: (update) => {
|
|
81
|
+
entriesRef.current = entriesRef.current.map(entry => entry.key === key ? { ...entry, config: { ...entry.config, ...(typeof update === 'function' ? update(entry.config) : update) } } : entry);
|
|
82
|
+
if (mounted.current)
|
|
83
|
+
setEntries(entriesRef.current);
|
|
84
|
+
}, then: promise.then.bind(promise) };
|
|
85
|
+
};
|
|
86
|
+
return { confirm: config => open('confirm', config), info: config => open('info', config), success: config => open('success', config), error: config => open('error', config), warning: config => open('warning', config), destroyAll: () => { for (const entry of [...entriesRef.current])
|
|
87
|
+
close(entry.key, false); } };
|
|
88
|
+
}, []);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const closed = afterCloseQueue.current;
|
|
91
|
+
afterCloseQueue.current = [];
|
|
92
|
+
for (const entry of closed)
|
|
93
|
+
entry.config.afterClose?.();
|
|
94
|
+
}, [entries]);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
mounted.current = true;
|
|
97
|
+
const generation = ++lifecycle.current;
|
|
98
|
+
return () => {
|
|
99
|
+
queueMicrotask(() => {
|
|
100
|
+
if (lifecycle.current !== generation)
|
|
101
|
+
return;
|
|
102
|
+
mounted.current = false;
|
|
103
|
+
const pending = entriesRef.current;
|
|
104
|
+
entriesRef.current = [];
|
|
105
|
+
for (const entry of pending)
|
|
106
|
+
entry.finish(false);
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
}, []);
|
|
110
|
+
const instance = useMemo(() => ({ message: api, modal, notification: notificationApi }), [api, modal, notificationApi]);
|
|
111
|
+
const content = _jsxs(_Fragment, { children: [children, holder, notificationHolder, entries.map(entry => _jsx(ConfirmDialog, { entry: entry, close: close, complete: complete }, entry.key))] });
|
|
112
|
+
const Wrapper = component;
|
|
113
|
+
return _jsx(Context.Provider, { value: instance, children: Wrapper === false ? content : _jsx(Wrapper, { ...rest, className: ['pi-app', className].filter(Boolean).join(' '), style: { ...themeVariables(theme), ...style }, children: content }) });
|
|
114
|
+
}
|
|
115
|
+
function useApp() { const instance = useContext(Context); if (!instance)
|
|
116
|
+
throw new Error('App.useApp() must be used inside <App>.'); return instance; }
|
|
117
|
+
export const App = Object.assign(AppRoot, { useApp });
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
.pi-theme { display: contents; }
|
|
2
|
+
|
|
3
|
+
.pi-button {
|
|
4
|
+
--btn-color: var(--pi-color-primary-solid); --btn-hover: var(--pi-color-primary-solid-hover); --btn-active: var(--pi-color-primary-solid-active); --btn-on: var(--pi-color-primary-on-solid);
|
|
5
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 8px; vertical-align: middle;
|
|
6
|
+
box-sizing: border-box; height: var(--pi-control-height-middle); padding-inline: var(--pi-button-padding-inline, 15px);
|
|
7
|
+
border: 1px solid var(--pi-color-border-default); border-radius: var(--pi-button-radius, var(--pi-radius-control));
|
|
8
|
+
font: inherit; font-family: var(--pi-font-family); font-size: var(--pi-font-size); line-height: 1; white-space: nowrap;
|
|
9
|
+
color: var(--pi-color-text-primary); background: var(--pi-color-surface-container); text-decoration: none; cursor: pointer;
|
|
10
|
+
transition: background var(--pi-motion-duration), color var(--pi-motion-duration), border-color var(--pi-motion-duration);
|
|
11
|
+
}
|
|
12
|
+
.pi-button:hover:not(:disabled):not([aria-disabled=true]) { color: var(--btn-hover); border-color: var(--btn-hover); }
|
|
13
|
+
.pi-button:active:not(:disabled):not([aria-disabled=true]) { color: var(--btn-active); border-color: var(--btn-active); }
|
|
14
|
+
.pi-button:focus { outline: none; }
|
|
15
|
+
/* Color and appearance are independent; legacy type resolves to these classes. */
|
|
16
|
+
.pi-button { --btn-subtle: var(--pi-color-primary-subtle); }
|
|
17
|
+
.pi-button--color-danger { --btn-color: var(--pi-color-error-solid); --btn-hover: var(--pi-color-error-solid-hover); --btn-active: var(--pi-color-error-solid-active); --btn-on: var(--pi-color-error-on-solid); --btn-subtle: var(--pi-color-error-subtle); }
|
|
18
|
+
.pi-button--color-primary, .pi-button--color-danger { color: var(--btn-color); border-color: var(--btn-color); }
|
|
19
|
+
.pi-button--variant-solid { background: var(--btn-color); border-color: var(--btn-color); color: var(--btn-on); }
|
|
20
|
+
.pi-button--variant-solid:hover:not(:disabled):not([aria-disabled=true]) { background: var(--btn-hover); border-color: var(--btn-hover); color: var(--btn-on); }
|
|
21
|
+
.pi-button--variant-solid:active:not(:disabled):not([aria-disabled=true]) { background: var(--btn-active); border-color: var(--btn-active); color: var(--btn-on); }
|
|
22
|
+
.pi-button--variant-dashed { border-style: dashed; }
|
|
23
|
+
.pi-button--variant-text, .pi-button--variant-link, .pi-button--variant-filled { border-color: transparent; background: transparent; }
|
|
24
|
+
.pi-button--color-default.pi-button--variant-link { --btn-color: var(--pi-color-link-default); --btn-hover: var(--pi-color-link-hover); --btn-active: var(--pi-color-link-active); }
|
|
25
|
+
.pi-button--variant-link { color: var(--btn-color); }
|
|
26
|
+
.pi-button--variant-filled { background: var(--btn-subtle); color: var(--btn-color); }
|
|
27
|
+
.pi-button--color-default.pi-button--variant-filled { background: var(--pi-color-surface-muted); color: var(--pi-color-text-primary); }
|
|
28
|
+
.pi-button--variant-text:hover:not(:disabled):not([aria-disabled=true]), .pi-button--variant-text:active:not(:disabled):not([aria-disabled=true]), .pi-button--variant-filled:hover:not(:disabled):not([aria-disabled=true]), .pi-button--variant-filled:active:not(:disabled):not([aria-disabled=true]) { background: var(--btn-subtle); border-color: transparent; }
|
|
29
|
+
.pi-button--color-default.pi-button--variant-text:hover:not(:disabled):not([aria-disabled=true]), .pi-button--color-default.pi-button--variant-text:active:not(:disabled):not([aria-disabled=true]) { background: var(--pi-color-surface-muted); }
|
|
30
|
+
.pi-button--variant-link:hover:not(:disabled):not([aria-disabled=true]), .pi-button--variant-link:active:not(:disabled):not([aria-disabled=true]) { border-color: transparent; }
|
|
31
|
+
.pi-button--ghost:not(.pi-button--variant-text):not(.pi-button--variant-link) { background: transparent; color: white; border-color: white; }
|
|
32
|
+
.pi-button--ghost.pi-button--color-primary, .pi-button--ghost.pi-button--color-danger { color: var(--btn-color); border-color: var(--btn-color); }
|
|
33
|
+
.pi-button--ghost:hover:not(:disabled):not([aria-disabled=true]) { background: transparent; color: var(--btn-hover); border-color: var(--btn-hover); }
|
|
34
|
+
.pi-button--ghost:active:not(:disabled):not([aria-disabled=true]) { background: transparent; color: var(--btn-active); border-color: var(--btn-active); }
|
|
35
|
+
.pi-button--small { height: var(--pi-control-height-small); padding-inline: var(--pi-button-padding-inline, 7px); font-size: 12px; }
|
|
36
|
+
.pi-button--large { height: var(--pi-control-height-large); padding-inline: var(--pi-button-padding-inline, 19px); font-size: 16px; }
|
|
37
|
+
.pi-button--round { border-radius: 999px; }
|
|
38
|
+
.pi-button--circle { border-radius: 50%; width: var(--pi-control-height-middle); padding: 0; }
|
|
39
|
+
.pi-button--circle.pi-button--small { width: var(--pi-control-height-small); }
|
|
40
|
+
.pi-button--circle.pi-button--large { width: var(--pi-control-height-large); }
|
|
41
|
+
.pi-button--icon-only { padding-inline: 0; aspect-ratio: 1; }
|
|
42
|
+
.pi-button--block { display: flex; width: 100%; }
|
|
43
|
+
.pi-button:disabled, .pi-button[aria-disabled=true] { cursor: not-allowed; color: var(--pi-color-text-disabled); background: var(--pi-color-surface-muted); border-color: var(--pi-color-border-subtle); }
|
|
44
|
+
.pi-button--loading:not(.pi-button--disabled) { opacity: .65; cursor: default; }
|
|
45
|
+
.pi-button--loading.pi-button--variant-solid:not(.pi-button--disabled) { color: var(--btn-on); background: var(--btn-color); border-color: var(--btn-color); }
|
|
46
|
+
.pi-button--loading.pi-button--variant-outlined:not(.pi-button--disabled), .pi-button--loading.pi-button--variant-dashed:not(.pi-button--disabled) { color: var(--pi-color-text-primary); background: var(--pi-color-surface-container); border-color: var(--pi-color-border-default); }
|
|
47
|
+
.pi-button--variant-text:disabled, .pi-button--variant-link:disabled, .pi-button--variant-text[aria-disabled=true], .pi-button--variant-link[aria-disabled=true] { background: transparent; border-color: transparent; }
|
|
48
|
+
.pi-button__content--spaced { letter-spacing: .3em; margin-inline-end: -.3em; }
|
|
49
|
+
.pi-button__icon { display: inline-flex; align-items: center; }
|
|
50
|
+
.pi-button__spinner { display: block; width: 1em; height: 1em; border: 1.5px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: pi-spin .7s linear infinite; }
|
|
51
|
+
@keyframes pi-spin { to { transform: rotate(360deg); } }
|
|
52
|
+
@media (prefers-reduced-motion: reduce) { .pi-button { transition: none; } .pi-button__spinner { animation-duration: 1.5s; } }
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
.pi-modal-root { position: fixed; inset: 0; color: var(--pi-color-text-primary); font-family: var(--pi-font-family); font-size: var(--pi-font-size); }
|
|
56
|
+
.pi-modal-root[hidden] { display: none; }
|
|
57
|
+
.pi-modal-mask { position: absolute; inset: 0; background: rgb(0 0 0 / 42%); }
|
|
58
|
+
.pi-modal-wrap { position: absolute; inset: 0; box-sizing: border-box; overflow-y: auto; overscroll-behavior: none; padding: 80px 16px 24px; }
|
|
59
|
+
.pi-modal-wrap--centered { display: flex; align-items: flex-start; justify-content: center; padding-top: 24px; }
|
|
60
|
+
.pi-modal-root--no-mask { pointer-events: none; }
|
|
61
|
+
.pi-modal-root--no-mask .pi-modal { pointer-events: auto; }
|
|
62
|
+
.pi-modal { position: relative; box-sizing: border-box; max-width: 100%; margin: 0 auto; padding: var(--pi-modal-padding, 24px); border: 1px solid var(--pi-color-border-subtle); border-radius: var(--pi-modal-radius, var(--pi-radius-control)); background: var(--pi-color-surface-elevated); box-shadow: 0 12px 48px rgb(0 0 0 / 16%); outline: none; line-height: 1.6; }
|
|
63
|
+
.pi-modal-wrap--centered .pi-modal { margin-block: auto; }
|
|
64
|
+
.pi-modal-title { padding-right: 28px; margin-bottom: 16px; font-size: 16px; font-weight: 600; line-height: 1.5; }
|
|
65
|
+
.pi-modal-close { position: absolute; top: 16px; right: 16px; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; width: 28px; height: 28px; padding: 0; border: 0; border-radius: var(--pi-radius-control); color: var(--pi-color-text-secondary); background: transparent; cursor: pointer; }
|
|
66
|
+
.pi-modal-close:hover { color: var(--pi-color-text-primary); background: var(--pi-color-surface-muted); }
|
|
67
|
+
.pi-modal-close:focus-visible { outline: 2px solid var(--pi-color-primary-solid); outline-offset: 2px; }
|
|
68
|
+
.pi-modal-footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 24px; }
|
|
69
|
+
@media (max-width: 600px) { .pi-modal-wrap { padding: 24px 12px; } }
|
|
70
|
+
|
|
71
|
+
.pi-modal-root--enter > .pi-modal-wrap > .pi-modal { animation:pi-modal-enter 180ms ease-out both; }
|
|
72
|
+
.pi-modal-root--leave > .pi-modal-wrap > .pi-modal { pointer-events:none; animation:pi-modal-leave 180ms ease-in both; }
|
|
73
|
+
.pi-modal-root--enter > .pi-modal-mask { animation:pi-modal-mask-enter 180ms ease-out both; }
|
|
74
|
+
.pi-modal-root--leave > .pi-modal-mask { animation:pi-modal-mask-leave 180ms ease-in both; }
|
|
75
|
+
@keyframes pi-modal-enter { from { opacity:0; } to { opacity:1; } }
|
|
76
|
+
@keyframes pi-modal-leave { from { opacity:1; } to { opacity:0; } }
|
|
77
|
+
@keyframes pi-modal-mask-enter { from { opacity:0; } to { opacity:1; } }
|
|
78
|
+
@keyframes pi-modal-mask-leave { from { opacity:1; } to { opacity:0; } }
|
|
79
|
+
@media(prefers-reduced-motion:reduce) { .pi-modal-root--enter > .pi-modal-wrap > .pi-modal,.pi-modal-root--leave > .pi-modal-wrap > .pi-modal,.pi-modal-root--enter > .pi-modal-mask,.pi-modal-root--leave > .pi-modal-mask { animation:none; } }
|
|
80
|
+
|
|
81
|
+
.pi-message-holder { position: fixed; inset-inline: 12px; z-index: 1300; display: flex; flex-direction: column; align-items: center; pointer-events: none; font-family: var(--pi-font-family); font-size: var(--pi-font-size); }
|
|
82
|
+
.pi-message { display: inline-flex; align-items: center; gap: 8px; box-sizing: border-box; max-width: min(560px, 100%); padding: 10px 16px; border: 1px solid var(--pi-color-border-subtle); border-radius: var(--pi-radius-control); background: var(--pi-color-surface-elevated); color: var(--pi-color-text-primary); box-shadow: 0 4px 20px rgb(0 0 0 / 10%); line-height: 1.5; overflow-wrap: anywhere; pointer-events: auto; }
|
|
83
|
+
.pi-message-icon { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 50%; background: var(--pi-color-info-solid); color: var(--pi-color-info-on-solid); font-size: 12px; font-weight: 600; }
|
|
84
|
+
.pi-message--success .pi-message-icon { background: var(--pi-color-success-solid); }
|
|
85
|
+
.pi-message--error .pi-message-icon { background: var(--pi-color-error-solid); }
|
|
86
|
+
.pi-message--warning .pi-message-icon { background: var(--pi-color-warning-solid); }
|
|
87
|
+
.pi-message-icon--loading { box-sizing: border-box; border: 2px solid var(--pi-color-primary-solid); border-right-color: transparent; background: transparent; animation: pi-message-spin .8s linear infinite; }
|
|
88
|
+
@keyframes pi-message-spin { to { transform: rotate(360deg); } }
|
|
89
|
+
@media (prefers-reduced-motion: reduce) { .pi-message-icon--loading { animation-duration: 1.6s; } }
|
|
90
|
+
|
|
91
|
+
.pi-message-shell { display:grid; grid-template-rows:1fr; max-width:100%; margin-bottom:8px; }
|
|
92
|
+
.pi-message-clip { min-height:0; overflow:hidden; padding:4px 8px; margin:-4px -8px; }
|
|
93
|
+
.pi-message-shell[data-motion=enter] { animation:pi-message-expand 180ms ease-out both; }
|
|
94
|
+
.pi-message-shell[data-motion=enter] .pi-message { animation:pi-message-enter 180ms ease-out both; }
|
|
95
|
+
.pi-message-shell[data-motion=leave] { animation:pi-message-collapse 180ms ease-in both; }
|
|
96
|
+
.pi-message-shell[data-motion=leave] .pi-message { animation:pi-message-leave 180ms ease-in both; pointer-events:none; }
|
|
97
|
+
@keyframes pi-message-expand { from { grid-template-rows:0fr; margin-bottom:0; } to { grid-template-rows:1fr; margin-bottom:8px; } }
|
|
98
|
+
@keyframes pi-message-collapse { from { grid-template-rows:1fr; margin-bottom:8px; } to { grid-template-rows:0fr; margin-bottom:0; } }
|
|
99
|
+
@keyframes pi-message-enter { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:translateY(0); } }
|
|
100
|
+
@keyframes pi-message-leave { from { opacity:1; transform:translateY(0); } to { opacity:0; transform:translateY(-4px); } }
|
|
101
|
+
@media(prefers-reduced-motion:reduce) { .pi-message-shell[data-motion],.pi-message-shell[data-motion] .pi-message { animation:none; } }
|
|
102
|
+
|
|
103
|
+
.pi-notification-holder { position:fixed; z-index:1300; display:flex; flex-direction:column; gap:8px; width:min(360px,calc(100vw - 32px)); pointer-events:none; font:var(--pi-font-size)/1.5 var(--pi-font-family); }
|
|
104
|
+
.pi-notification-holder--topLeft,.pi-notification-holder--bottomLeft { left:16px; }
|
|
105
|
+
.pi-notification-holder--topRight,.pi-notification-holder--bottomRight { right:16px; }
|
|
106
|
+
.pi-notification { position:relative; display:flex; gap:12px; box-sizing:border-box; padding:16px 36px 16px 16px; border:1px solid var(--pi-color-border-subtle); border-radius:var(--pi-radius-control); background:var(--pi-color-surface-elevated); color:var(--pi-color-text-primary); box-shadow:0 4px 20px rgb(0 0 0 / 10%); pointer-events:auto; overflow-wrap:anywhere; }
|
|
107
|
+
.pi-notification-content { min-width:0; flex:1; }
|
|
108
|
+
.pi-notification-title { font-size:16px; font-weight:600; }
|
|
109
|
+
.pi-notification-description { margin-top:4px; color:var(--pi-color-text-secondary); }
|
|
110
|
+
.pi-notification-actions { display:flex; justify-content:flex-end; margin-top:12px; }
|
|
111
|
+
.pi-notification-close { position:absolute; right:10px; top:12px; display:grid; place-items:center; width:24px; height:24px; border:0; background:transparent; color:var(--pi-color-text-secondary); cursor:pointer; border-radius:var(--pi-radius-control); }
|
|
112
|
+
.pi-notification-close:hover { background:var(--pi-color-surface-muted); color:var(--pi-color-text-primary); }
|
|
113
|
+
.pi-notification-close:focus-visible { outline:1px solid var(--pi-color-primary-solid); }
|
|
114
|
+
.pi-notification-icon { display:inline-flex; padding-top:1px; flex:none; color:var(--pi-color-info-solid); }
|
|
115
|
+
.pi-notification-icon--success { color:var(--pi-color-success-solid); }.pi-notification-icon--warning { color:var(--pi-color-warning-solid); }.pi-notification-icon--error { color:var(--pi-color-error-solid); }
|
|
116
|
+
.pi-notification[data-motion=enter] { animation:pi-notification-enter 180ms ease-out both; }.pi-notification[data-motion=leave] { animation:pi-notification-leave 180ms ease-in both; pointer-events:none; }
|
|
117
|
+
@keyframes pi-notification-enter { from { opacity:0; transform:translateY(-6px); } to { opacity:1; transform:translateY(0); } }
|
|
118
|
+
@keyframes pi-notification-leave { to { opacity:0; transform:translateY(-6px); } }
|
|
119
|
+
@media(prefers-reduced-motion:reduce) { .pi-notification[data-motion] { animation:none; } }
|
|
120
|
+
|
|
121
|
+
.pi-app { color: var(--pi-color-text-primary); font-family: var(--pi-font-family); font-size: var(--pi-font-size); }
|
|
122
|
+
.pi-app-confirm-content { display: flex; gap: 12px; align-items: flex-start; }
|
|
123
|
+
.pi-app-confirm-icon { display: inline-flex; flex: none; padding-top: 1px; }
|
|
124
|
+
.pi-app-confirm-icon--success { color: var(--pi-color-success-solid); }
|
|
125
|
+
.pi-app-confirm-icon--info { color: var(--pi-color-info-solid); }
|
|
126
|
+
.pi-app-confirm-icon--warning { color: var(--pi-color-warning-solid); }
|
|
127
|
+
.pi-app-confirm-icon--error, .pi-app-confirm-error { color: var(--pi-color-error-solid); }
|
|
128
|
+
.pi-app-confirm-error { margin: 8px 0 0; font-size: 12px; }
|
|
129
|
+
.pi-app-confirm-ok { padding: 4px 12px; min-height: var(--pi-control-height-middle); border: 0; border-radius: var(--pi-radius-control); background: var(--pi-color-primary-solid); color: var(--pi-color-primary-on-solid); cursor: pointer; font: inherit; }
|
|
130
|
+
.pi-app-confirm-ok:disabled { opacity: .5; cursor: not-allowed; }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type InputProps, type InputRef } from '../input/index.js';
|
|
3
|
+
export interface AutoCompleteOption {
|
|
4
|
+
value: string;
|
|
5
|
+
label?: ReactNode;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface AutoCompleteProps extends Omit<InputProps, 'value' | 'defaultValue' | 'onChange' | 'onSelect'> {
|
|
9
|
+
value?: string;
|
|
10
|
+
defaultValue?: string;
|
|
11
|
+
options?: AutoCompleteOption[];
|
|
12
|
+
onChange?: (value: string) => void;
|
|
13
|
+
onSearch?: (value: string) => void;
|
|
14
|
+
onSelect?: (value: string, option: AutoCompleteOption) => void;
|
|
15
|
+
filterOption?: boolean | ((input: string, option: AutoCompleteOption) => boolean);
|
|
16
|
+
open?: boolean;
|
|
17
|
+
defaultOpen?: boolean;
|
|
18
|
+
onOpenChange?: (open: boolean) => void;
|
|
19
|
+
notFoundContent?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
export type AutoCompleteRef = InputRef;
|
|
22
|
+
export declare const AutoComplete: import("react").ForwardRefExoticComponent<AutoCompleteProps & import("react").RefAttributes<InputRef>>;
|