@aozi6666/bee-design 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 +73 -0
- package/build/App.d.ts +4 -0
- package/build/App.js +137 -0
- package/build/components/AutoComplete/autoComplete.d.ts +13 -0
- package/build/components/AutoComplete/autoComplete.js +123 -0
- package/build/components/AutoComplete/autoComplete.types.d.ts +19 -0
- package/build/components/AutoComplete/autoComplete.types.js +1 -0
- package/build/components/AutoComplete/autoCompleteDropdown.d.ts +13 -0
- package/build/components/AutoComplete/autoCompleteDropdown.js +17 -0
- package/build/components/AutoComplete/index.d.ts +4 -0
- package/build/components/AutoComplete/index.js +3 -0
- package/build/components/Button/button.d.ts +6 -0
- package/build/components/Button/button.js +43 -0
- package/build/components/Button/button.types.d.ts +24 -0
- package/build/components/Button/button.types.js +19 -0
- package/build/components/Button/index.d.ts +2 -0
- package/build/components/Button/index.js +2 -0
- package/build/components/Icon/icon.d.ts +16 -0
- package/build/components/Icon/icon.js +24 -0
- package/build/components/Icon/icon.types.d.ts +6 -0
- package/build/components/Icon/icon.types.js +2 -0
- package/build/components/Icon/index.d.ts +2 -0
- package/build/components/Icon/index.js +2 -0
- package/build/components/Input/index.d.ts +4 -0
- package/build/components/Input/index.js +3 -0
- package/build/components/Input/input.d.ts +5 -0
- package/build/components/Input/input.js +32 -0
- package/build/components/Input/input.types.d.ts +16 -0
- package/build/components/Input/input.types.js +1 -0
- package/build/components/Menu/index.d.ts +10 -0
- package/build/components/Menu/index.js +9 -0
- package/build/components/Menu/menu.d.ts +34 -0
- package/build/components/Menu/menu.js +48 -0
- package/build/components/Menu/menuItem.d.ts +14 -0
- package/build/components/Menu/menuItem.js +20 -0
- package/build/components/Menu/subMenu.d.ts +11 -0
- package/build/components/Menu/subMenu.js +56 -0
- package/build/components/Progress/index.d.ts +2 -0
- package/build/components/Progress/index.js +2 -0
- package/build/components/Progress/progress.d.ts +4 -0
- package/build/components/Progress/progress.js +6 -0
- package/build/components/Progress/progress.types.d.ts +9 -0
- package/build/components/Progress/progress.types.js +2 -0
- package/build/components/Transition/index.d.ts +3 -0
- package/build/components/Transition/index.js +2 -0
- package/build/components/Transition/transition.d.ts +4 -0
- package/build/components/Transition/transition.js +18 -0
- package/build/components/Transition/transition.types.d.ts +10 -0
- package/build/components/Transition/transition.types.js +1 -0
- package/build/components/Upload/dragger.d.ts +7 -0
- package/build/components/Upload/dragger.js +42 -0
- package/build/components/Upload/index.d.ts +2 -0
- package/build/components/Upload/index.js +2 -0
- package/build/components/Upload/native/axios-react.d.ts +2 -0
- package/build/components/Upload/native/axios-react.js +99 -0
- package/build/components/Upload/native/from-html.d.ts +2 -0
- package/build/components/Upload/native/from-html.js +5 -0
- package/build/components/Upload/upload.d.ts +13 -0
- package/build/components/Upload/upload.js +192 -0
- package/build/components/Upload/upload.types.d.ts +48 -0
- package/build/components/Upload/upload.types.js +3 -0
- package/build/components/Upload/uploadList.d.ts +8 -0
- package/build/components/Upload/uploadList.js +13 -0
- package/build/hooks/useClickOutside.d.ts +3 -0
- package/build/hooks/useClickOutside.js +18 -0
- package/build/hooks/useDebounce.d.ts +2 -0
- package/build/hooks/useDebounce.js +14 -0
- package/build/index.css +856 -0
- package/build/index.css.map +1 -0
- package/build/index.d.ts +9 -0
- package/build/index.js +12 -0
- package/build/main.d.ts +1 -0
- package/build/main.js +7 -0
- package/build/setupTests.d.ts +1 -0
- package/build/setupTests.js +1 -0
- package/package.json +109 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
9
|
+
|
|
10
|
+
## React Compiler
|
|
11
|
+
|
|
12
|
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
13
|
+
|
|
14
|
+
## Expanding the ESLint configuration
|
|
15
|
+
|
|
16
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores(['dist']),
|
|
21
|
+
{
|
|
22
|
+
files: ['**/*.{ts,tsx}'],
|
|
23
|
+
extends: [
|
|
24
|
+
// Other configs...
|
|
25
|
+
|
|
26
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
27
|
+
tseslint.configs.recommendedTypeChecked,
|
|
28
|
+
// Alternatively, use this for stricter rules
|
|
29
|
+
tseslint.configs.strictTypeChecked,
|
|
30
|
+
// Optionally, add this for stylistic rules
|
|
31
|
+
tseslint.configs.stylisticTypeChecked,
|
|
32
|
+
|
|
33
|
+
// Other configs...
|
|
34
|
+
],
|
|
35
|
+
languageOptions: {
|
|
36
|
+
parserOptions: {
|
|
37
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
38
|
+
tsconfigRootDir: import.meta.dirname,
|
|
39
|
+
},
|
|
40
|
+
// other options...
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// eslint.config.js
|
|
50
|
+
import reactX from 'eslint-plugin-react-x'
|
|
51
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
52
|
+
|
|
53
|
+
export default defineConfig([
|
|
54
|
+
globalIgnores(['dist']),
|
|
55
|
+
{
|
|
56
|
+
files: ['**/*.{ts,tsx}'],
|
|
57
|
+
extends: [
|
|
58
|
+
// Other configs...
|
|
59
|
+
// Enable lint rules for React
|
|
60
|
+
reactX.configs['recommended-typescript'],
|
|
61
|
+
// Enable lint rules for React DOM
|
|
62
|
+
reactDom.configs.recommended,
|
|
63
|
+
],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parserOptions: {
|
|
66
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
+
tsconfigRootDir: import.meta.dirname,
|
|
68
|
+
},
|
|
69
|
+
// other options...
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
```
|
package/build/App.d.ts
ADDED
package/build/App.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import './styles/index.scss';
|
|
3
|
+
import Button from './components/Button/button';
|
|
4
|
+
import { ButtonType } from './components/Button/button.types';
|
|
5
|
+
import Upload from './components/Upload';
|
|
6
|
+
import Icon from './components/Icon';
|
|
7
|
+
import Progress from './components/Progress';
|
|
8
|
+
import AutoComplete from './components/AutoComplete';
|
|
9
|
+
const App = () => {
|
|
10
|
+
const defaultUploadList = [
|
|
11
|
+
{
|
|
12
|
+
uid: '1',
|
|
13
|
+
size: 1024,
|
|
14
|
+
name: '已上传文件.png',
|
|
15
|
+
status: 'success',
|
|
16
|
+
percent: 100,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
uid: '2',
|
|
20
|
+
size: 2048,
|
|
21
|
+
name: '上传中文件.jpg',
|
|
22
|
+
status: 'uploading',
|
|
23
|
+
percent: 40,
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
const lakersWithNumber = [
|
|
27
|
+
{ value: 'bradley', number: 11 },
|
|
28
|
+
{ value: 'pope', number: 1 },
|
|
29
|
+
{ value: 'caruso', number: 4 },
|
|
30
|
+
{ value: 'cook', number: 2 },
|
|
31
|
+
{ value: 'cousins', number: 15 },
|
|
32
|
+
{ value: 'james', number: 23 },
|
|
33
|
+
{ value: 'AD', number: 3 },
|
|
34
|
+
{ value: 'green', number: 14 },
|
|
35
|
+
{ value: 'howard', number: 39 },
|
|
36
|
+
{ value: 'kuzma', number: 0 },
|
|
37
|
+
];
|
|
38
|
+
const handleAutoCompleteFetch = (query) => {
|
|
39
|
+
return lakersWithNumber.filter((player) => player.value.toLowerCase().includes(query.toLowerCase()));
|
|
40
|
+
};
|
|
41
|
+
return (_jsx("div", { style: {
|
|
42
|
+
minHeight: '100vh',
|
|
43
|
+
padding: '40px 24px 80px',
|
|
44
|
+
background: 'radial-gradient(circle at top left, #f0f5ff 0, transparent 50%), radial-gradient(circle at bottom right, #fff1f0 0, transparent 55%)',
|
|
45
|
+
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif',
|
|
46
|
+
}, children: _jsxs("div", { style: {
|
|
47
|
+
maxWidth: 960,
|
|
48
|
+
margin: '0 auto',
|
|
49
|
+
}, children: [_jsxs("header", { style: {
|
|
50
|
+
marginBottom: 32,
|
|
51
|
+
display: 'flex',
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
justifyContent: 'space-between',
|
|
54
|
+
gap: 16,
|
|
55
|
+
}, children: [_jsxs("div", { children: [_jsx("h1", { style: { margin: 0, fontSize: 28 }, children: "Cream Design \u7EC4\u4EF6\u5E93" }), _jsx("p", { style: { margin: '8px 0 0', color: '#595959', fontSize: 14 }, children: "\u4E00\u5957\u7528\u6765\u7EC3\u4E60 React + TypeScript \u7684\u8F7B\u91CF\u7EC4\u4EF6\u793A\u4F8B\u3002" })] }), _jsxs("div", { style: { display: 'flex', gap: 12 }, children: [_jsx(Button, { btnType: ButtonType.Primary, children: "\u67E5\u770B\u6587\u6863" }), _jsx(Button, { btnType: ButtonType.Default, children: "Git \u4ED3\u5E93" })] })] }), _jsxs("main", { style: {
|
|
56
|
+
display: 'grid',
|
|
57
|
+
gridTemplateColumns: 'minmax(0, 1.2fr) minmax(0, 1fr)',
|
|
58
|
+
gap: 24,
|
|
59
|
+
alignItems: 'flex-start',
|
|
60
|
+
}, children: [_jsxs("section", { style: {
|
|
61
|
+
display: 'flex',
|
|
62
|
+
flexDirection: 'column',
|
|
63
|
+
gap: 24,
|
|
64
|
+
}, children: [_jsxs("div", { style: {
|
|
65
|
+
padding: 24,
|
|
66
|
+
borderRadius: 12,
|
|
67
|
+
background: '#ffffff',
|
|
68
|
+
boxShadow: '0 8px 24px rgba(15, 23, 42, 0.06)',
|
|
69
|
+
}, children: [_jsx("h2", { style: { margin: 0, marginBottom: 16, fontSize: 18 }, children: "Button \u7EC4\u4EF6\u6F14\u793A" }), _jsx("p", { style: { margin: '0 0 16px', color: '#8c8c8c', fontSize: 13 }, children: "\u4E0D\u540C\u7C7B\u578B\u6309\u94AE\u548C\u94FE\u63A5\u6309\u94AE\uFF0C\u9002\u5408\u4F5C\u4E3A\u9875\u9762\u4E3B\u64CD\u4F5C\u3001\u6B21\u8981\u64CD\u4F5C\u548C\u6587\u672C\u94FE\u63A5\u3002" }), _jsxs("div", { style: {
|
|
70
|
+
display: 'flex',
|
|
71
|
+
flexWrap: 'wrap',
|
|
72
|
+
gap: 12,
|
|
73
|
+
}, children: [_jsx(Button, { btnType: ButtonType.Primary, children: "Primary Button" }), _jsx(Button, { btnType: ButtonType.Default, children: "Default Button" }), _jsx(Button, { btnType: ButtonType.Danger, children: "Danger Button" }), _jsx(Button, { btnType: ButtonType.Link, href: "https://www.baidu.com/", children: "Link \u767E\u5EA6" }), _jsx(Button, { btnType: ButtonType.Link, href: "https://www.baidu.com/", target: "_blank", children: "Link \u767E\u5EA6(\"\u65B0\u7A97\u53E3\u6253\u5F00\")" })] })] }), _jsxs("div", { style: {
|
|
74
|
+
padding: 24,
|
|
75
|
+
borderRadius: 12,
|
|
76
|
+
background: '#ffffff',
|
|
77
|
+
boxShadow: '0 8px 24px rgba(15, 23, 42, 0.06)',
|
|
78
|
+
}, children: [_jsx("h2", { style: { margin: 0, marginBottom: 16, fontSize: 18 }, children: "Upload \u7EC4\u4EF6\u6F14\u793A" }), _jsx("p", { style: { margin: '0 0 16px', color: '#8c8c8c', fontSize: 13 }, children: "\u652F\u6301\u70B9\u51FB\u9009\u62E9\u3001\u62D6\u62FD\u4E0A\u4F20\u3001\u4E0A\u4F20\u8FDB\u5EA6\u663E\u793A\u548C\u6587\u4EF6\u5217\u8868\u7BA1\u7406\u3002" }), _jsxs("div", { style: {
|
|
79
|
+
display: 'flex',
|
|
80
|
+
flexDirection: 'column',
|
|
81
|
+
gap: 16,
|
|
82
|
+
alignItems: 'stretch',
|
|
83
|
+
}, children: [_jsx(Upload, { action: "https://jsonplaceholder.typicode.com/posts", defaultFileList: defaultUploadList, children: _jsx(Button, { btnType: ButtonType.Primary, children: "\u70B9\u51FB\u4E0A\u4F20" }) }), _jsx(Upload, { action: "https://jsonplaceholder.typicode.com/posts", drag: true, children: _jsx("div", { style: {
|
|
84
|
+
padding: '20px 40px',
|
|
85
|
+
border: '1px dashed #d9d9d9',
|
|
86
|
+
borderRadius: 4,
|
|
87
|
+
textAlign: 'center',
|
|
88
|
+
color: '#595959',
|
|
89
|
+
background: '#fafafa',
|
|
90
|
+
}, children: "\u62D6\u62FD\u6587\u4EF6\u5230\u6B64\u5904\uFF0C\u6216\u70B9\u51FB\u4E0A\u4F20" }) })] })] })] }), _jsxs("section", { style: {
|
|
91
|
+
display: 'flex',
|
|
92
|
+
flexDirection: 'column',
|
|
93
|
+
gap: 24,
|
|
94
|
+
}, children: [_jsxs("div", { style: {
|
|
95
|
+
padding: 24,
|
|
96
|
+
borderRadius: 12,
|
|
97
|
+
background: '#ffffff',
|
|
98
|
+
boxShadow: '0 8px 24px rgba(15, 23, 42, 0.06)',
|
|
99
|
+
}, children: [_jsx("h2", { style: { margin: 0, marginBottom: 16, fontSize: 18 }, children: "Icon \u7EC4\u4EF6\u6F14\u793A" }), _jsx("p", { style: { margin: '0 0 16px', color: '#8c8c8c', fontSize: 13 }, children: "\u57FA\u4E8E Font Awesome \u7684\u56FE\u6807\u7EC4\u4EF6\uFF0C\u652F\u6301\u4E3B\u9898\u8272\u548C\u6240\u6709\u539F\u751F\u5C5E\u6027\u3002" }), _jsxs("div", { style: {
|
|
100
|
+
display: 'flex',
|
|
101
|
+
flexWrap: 'wrap',
|
|
102
|
+
gap: 20,
|
|
103
|
+
alignItems: 'center',
|
|
104
|
+
}, children: [_jsx(Icon, { icon: "coffee", size: "2x" }), _jsx(Icon, { icon: "check-circle", size: "2x", theme: "success" }), _jsx(Icon, { icon: "times", size: "2x", theme: "danger" }), _jsx(Icon, { icon: "spinner", size: "2x", spin: true, theme: "primary" })] })] }), _jsxs("div", { style: {
|
|
105
|
+
padding: 24,
|
|
106
|
+
borderRadius: 12,
|
|
107
|
+
background: '#ffffff',
|
|
108
|
+
boxShadow: '0 8px 24px rgba(15, 23, 42, 0.06)',
|
|
109
|
+
}, children: [_jsx("h2", { style: { margin: 0, marginBottom: 16, fontSize: 18 }, children: "Progress \u7EC4\u4EF6\u6F14\u793A" }), _jsx("p", { style: { margin: '0 0 16px', color: '#8c8c8c', fontSize: 13 }, children: "\u5C55\u793A\u4EFB\u52A1\u5B8C\u6210\u8FDB\u5EA6\uFF0C\u652F\u6301\u4E0D\u540C\u4E3B\u9898\u989C\u8272\u548C\u9AD8\u5EA6\u3002" }), _jsxs("div", { style: {
|
|
110
|
+
display: 'flex',
|
|
111
|
+
flexDirection: 'column',
|
|
112
|
+
gap: 12,
|
|
113
|
+
}, children: [_jsx(Progress, { percent: 30 }), _jsx(Progress, { percent: 65, theme: "success" }), _jsx(Progress, { percent: 90, theme: "danger", strokeHeight: 10 })] })] }), _jsxs("div", { style: {
|
|
114
|
+
padding: 24,
|
|
115
|
+
borderRadius: 12,
|
|
116
|
+
background: '#ffffff',
|
|
117
|
+
boxShadow: '0 8px 24px rgba(15, 23, 42, 0.06)',
|
|
118
|
+
}, children: [_jsx("h2", { style: { margin: 0, marginBottom: 16, fontSize: 18 }, children: "AutoComplete \u7EC4\u4EF6\u6F14\u793A" }), _jsx("p", { style: { margin: '0 0 16px', color: '#8c8c8c', fontSize: 13 }, children: "\u8F93\u5165\u5185\u5BB9\u81EA\u52A8\u7ED9\u51FA\u5019\u9009\u9879\uFF0C\u652F\u6301\u952E\u76D8\u9009\u62E9\u4E0E\u81EA\u5B9A\u4E49\u4E0B\u62C9\u6E32\u67D3\u3002" }), _jsx(AutoComplete, { fetchSuggestions: handleAutoCompleteFetch, placeholder: "\u8F93\u5165\u6E56\u4EBA\u961F\u7403\u5458\u82F1\u6587\u540D\u8BD5\u8BD5\uFF08\u5982\uFF1Aja / co\uFF09", renderOption: (item) => {
|
|
119
|
+
const player = item;
|
|
120
|
+
return (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', gap: 12 }, children: [_jsx("b", { children: player.value }), _jsxs("span", { style: { color: '#8c8c8c' }, children: ["#", player.number] })] }));
|
|
121
|
+
} })] })] })] }), _jsxs("div", { style: {
|
|
122
|
+
marginTop: 40,
|
|
123
|
+
padding: 24,
|
|
124
|
+
borderRadius: 12,
|
|
125
|
+
background: '#ffffff',
|
|
126
|
+
boxShadow: '0 8px 24px rgba(15, 23, 42, 0.06)',
|
|
127
|
+
}, children: [_jsx("h2", { style: { margin: 0, marginBottom: 16, fontSize: 18 }, children: "\u539F\u751F\u8868\u5355\u4E0A\u4F20\u793A\u4F8B" }), _jsxs("p", { style: { margin: '0 0 16px', color: '#8c8c8c', fontSize: 13 }, children: ["\u4F7F\u7528\u539F\u751F ", _jsx("code", { children: "<form>" }), " \u63D0\u4EA4\u5230\u540E\u7AEF\u63A5\u53E3\u7684\u6587\u4EF6\u4E0A\u4F20\u65B9\u5F0F\u3002"] }), _jsxs("form", { method: "post", encType: "multipart/form-data", action: "https://jsonplaceholder.typicode.com/posts", style: { marginTop: 8 }, children: [_jsx("input", { type: "file", name: "myFile" }), _jsx("button", { type: "submit", style: {
|
|
128
|
+
marginLeft: 12,
|
|
129
|
+
padding: '4px 12px',
|
|
130
|
+
borderRadius: 4,
|
|
131
|
+
border: '1px solid #1677ff',
|
|
132
|
+
background: '#1677ff',
|
|
133
|
+
color: '#fff',
|
|
134
|
+
cursor: 'pointer',
|
|
135
|
+
}, children: "Submit" })] })] })] }) }));
|
|
136
|
+
};
|
|
137
|
+
export default App;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AutoCompleteProps } from './autoComplete.types';
|
|
2
|
+
export type { AutoCompleteProps, DataSourceType } from './autoComplete.types';
|
|
3
|
+
/**
|
|
4
|
+
* 输入框自动完成功能。当输入值需要自动完成时使用,支持同步和异步两种方式
|
|
5
|
+
* 支持 Input 组件的所有属性 支持键盘事件选择
|
|
6
|
+
* ### 引用方法
|
|
7
|
+
*
|
|
8
|
+
* ~~~js
|
|
9
|
+
* import { AutoComplete } from 'vikingship'
|
|
10
|
+
* ~~~
|
|
11
|
+
*/
|
|
12
|
+
export declare const AutoComplete: (props: AutoCompleteProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export default AutoComplete;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// AutoComplete 组件: 带搜索建议的 Input
|
|
3
|
+
import { useState, useEffect, useRef } from 'react';
|
|
4
|
+
import Input from '../Input/input';
|
|
5
|
+
import useDebounce from '../../hooks/useDebounce';
|
|
6
|
+
import useClickOutside from '../../hooks/useClickOutside';
|
|
7
|
+
import AutoCompleteDropdown from './autoCompleteDropdown';
|
|
8
|
+
/**
|
|
9
|
+
* 输入框自动完成功能。当输入值需要自动完成时使用,支持同步和异步两种方式
|
|
10
|
+
* 支持 Input 组件的所有属性 支持键盘事件选择
|
|
11
|
+
* ### 引用方法
|
|
12
|
+
*
|
|
13
|
+
* ~~~js
|
|
14
|
+
* import { AutoComplete } from 'vikingship'
|
|
15
|
+
* ~~~
|
|
16
|
+
*/
|
|
17
|
+
export const AutoComplete = (props) => {
|
|
18
|
+
const { fetchSuggestions, onSelect, onChange, value, renderOption, ...restProps } = props;
|
|
19
|
+
// 输入框当前显示的文本
|
|
20
|
+
const [inputValue, setInputValue] = useState(value || '');
|
|
21
|
+
// 下拉建议列表的数据源(渲染 `<li>` 就靠它)
|
|
22
|
+
const [suggestions, setSugestions] = useState([]);
|
|
23
|
+
// 异步请求进行中就显示 loading
|
|
24
|
+
const [loading, setLoading] = useState(false);
|
|
25
|
+
// 是否展示下拉(配合 `Transition` 动画)
|
|
26
|
+
const [showDropdown, setShowDropdown] = useState(false);
|
|
27
|
+
// 键盘上下选择时,哪一项高亮(对应 class `is-active`)
|
|
28
|
+
const [highlightIndex, setHighlightIndex] = useState(-1);
|
|
29
|
+
// 两个关键 ref:
|
|
30
|
+
// 用来区分“用户打字触发搜索” vs “用户选中后把值塞回去(不应该再搜一次)”
|
|
31
|
+
const triggerSearch = useRef(false);
|
|
32
|
+
// 挂到最外层 div 上,给 `useClickOutside` 判断“点击是否发生在组件外”
|
|
33
|
+
const componentRef = useRef(null);
|
|
34
|
+
// 防抖Hook:把“频繁输入”变成“停顿后再触发一次”
|
|
35
|
+
const debouncedValue = useDebounce(inputValue, 300);
|
|
36
|
+
// 自定义Hook:点击组件外部时关闭下拉
|
|
37
|
+
/* 在 `document` 上挂一个 `click` 监听
|
|
38
|
+
* @param componentRef 组件的 ref
|
|
39
|
+
* @param callback 点击外部时执行的回调
|
|
40
|
+
*/
|
|
41
|
+
useClickOutside(componentRef, () => {
|
|
42
|
+
setSugestions([]);
|
|
43
|
+
setShowDropdown(false);
|
|
44
|
+
});
|
|
45
|
+
// 监听 `debouncedValue` 变化
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (debouncedValue && triggerSearch.current) {
|
|
48
|
+
setSugestions([]);
|
|
49
|
+
const results = fetchSuggestions(debouncedValue);
|
|
50
|
+
if (results instanceof Promise) {
|
|
51
|
+
setLoading(true);
|
|
52
|
+
results.then(data => {
|
|
53
|
+
setLoading(false);
|
|
54
|
+
setSugestions(data);
|
|
55
|
+
setShowDropdown(data.length > 0);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
setSugestions(results);
|
|
60
|
+
setShowDropdown(results.length > 0);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
setShowDropdown(false);
|
|
65
|
+
}
|
|
66
|
+
setHighlightIndex(-1);
|
|
67
|
+
}, [debouncedValue, fetchSuggestions]);
|
|
68
|
+
const highlight = (index) => {
|
|
69
|
+
if (index < 0)
|
|
70
|
+
index = 0;
|
|
71
|
+
if (index >= suggestions.length) {
|
|
72
|
+
index = suggestions.length - 1;
|
|
73
|
+
}
|
|
74
|
+
setHighlightIndex(index);
|
|
75
|
+
};
|
|
76
|
+
const handleKeyDown = (e) => {
|
|
77
|
+
switch (e.keyCode) {
|
|
78
|
+
case 13:
|
|
79
|
+
if (suggestions[highlightIndex]) {
|
|
80
|
+
handleSelect(suggestions[highlightIndex]);
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case 38:
|
|
84
|
+
highlight(highlightIndex - 1);
|
|
85
|
+
break;
|
|
86
|
+
case 40:
|
|
87
|
+
highlight(highlightIndex + 1);
|
|
88
|
+
break;
|
|
89
|
+
case 27:
|
|
90
|
+
setShowDropdown(false);
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
// 回调:消息框内容发生变化
|
|
97
|
+
const handleChange = (e) => {
|
|
98
|
+
// 获取 输入框内容 (去掉首尾空格)
|
|
99
|
+
const value = e.target.value.trim();
|
|
100
|
+
// 更新 输入框内容
|
|
101
|
+
// `inputValue` 变化 → 经过 `useDebounce` 得到 `debouncedValue`
|
|
102
|
+
setInputValue(value);
|
|
103
|
+
// 组件使用者传来的回调:消息框内容发生变化
|
|
104
|
+
if (onChange) {
|
|
105
|
+
onChange(value);
|
|
106
|
+
}
|
|
107
|
+
// 告诉后面“这次是用户输入,应当触发搜索”
|
|
108
|
+
triggerSearch.current = true;
|
|
109
|
+
};
|
|
110
|
+
// 回调:用户选中某一项
|
|
111
|
+
const handleSelect = (item) => {
|
|
112
|
+
setInputValue(item.value);
|
|
113
|
+
setShowDropdown(false);
|
|
114
|
+
if (onSelect) {
|
|
115
|
+
onSelect(item);
|
|
116
|
+
}
|
|
117
|
+
triggerSearch.current = false;
|
|
118
|
+
};
|
|
119
|
+
return (_jsxs("div", { className: "viking-auto-complete", ref: componentRef, children: [_jsx(Input, { ...restProps, value: inputValue, onChange: handleChange, onKeyDown: handleKeyDown }), _jsx(AutoCompleteDropdown, { loading: loading, showDropdown: showDropdown, suggestions: suggestions, highlightIndex: highlightIndex, onSelect: handleSelect, renderOption: renderOption, onExited: () => {
|
|
120
|
+
setSugestions([]);
|
|
121
|
+
} })] }));
|
|
122
|
+
};
|
|
123
|
+
export default AutoComplete;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { InputProps } from '../Input/input.types';
|
|
3
|
+
interface DataSourceObject {
|
|
4
|
+
value: string;
|
|
5
|
+
}
|
|
6
|
+
export type DataSourceType<T = {}> = T & DataSourceObject;
|
|
7
|
+
export interface AutoCompleteProps extends Omit<InputProps, 'onSelect' | 'onChange'> {
|
|
8
|
+
/**
|
|
9
|
+
* 返回输入建议的方法,可以拿到当前的输入,然后返回同步的数组或者是异步的 Promise
|
|
10
|
+
*/
|
|
11
|
+
fetchSuggestions: (str: string) => DataSourceType[] | Promise<DataSourceType[]>;
|
|
12
|
+
/** 点击 选中建议项(点中/回车) 时触发的回调 */
|
|
13
|
+
onSelect?: (item: DataSourceType) => void;
|
|
14
|
+
/** 文本框发生改变 的时候触发的事件 */
|
|
15
|
+
onChange?: (value: string) => void;
|
|
16
|
+
/** 支持自定义 渲染 下拉项 的 UI,返回 ReactElement类型 */
|
|
17
|
+
renderOption?: (item: DataSourceType) => ReactElement;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FC, ReactElement } from 'react';
|
|
2
|
+
import type { DataSourceType } from './autoComplete.types';
|
|
3
|
+
export interface AutoCompleteDropdownProps {
|
|
4
|
+
loading: boolean;
|
|
5
|
+
showDropdown: boolean;
|
|
6
|
+
suggestions: DataSourceType[];
|
|
7
|
+
highlightIndex: number;
|
|
8
|
+
onSelect: (item: DataSourceType) => void;
|
|
9
|
+
renderOption?: (item: DataSourceType) => ReactElement;
|
|
10
|
+
onExited?: () => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const AutoCompleteDropdown: FC<AutoCompleteDropdownProps>;
|
|
13
|
+
export default AutoCompleteDropdown;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import Icon from '../Icon/icon';
|
|
4
|
+
import Transition from '../Transition/transition';
|
|
5
|
+
export const AutoCompleteDropdown = (props) => {
|
|
6
|
+
const { loading, showDropdown, suggestions, highlightIndex, onSelect, renderOption, onExited, } = props;
|
|
7
|
+
const renderTemplate = (item) => {
|
|
8
|
+
return renderOption ? renderOption(item) : item.value;
|
|
9
|
+
};
|
|
10
|
+
return (_jsx(Transition, { in: showDropdown || loading, animation: "zoom-in-top", timeout: 300, onExited: onExited, children: _jsxs("ul", { className: "viking-suggestion-list", children: [loading && (_jsx("div", { className: "suggestions-loading-icon", children: _jsx(Icon, { icon: "spinner", spin: true }) })), suggestions.map((item, index) => {
|
|
11
|
+
const cnames = classNames('suggestion-item', {
|
|
12
|
+
'is-active': index === highlightIndex,
|
|
13
|
+
});
|
|
14
|
+
return (_jsx("li", { className: cnames, onClick: () => onSelect(item), children: renderTemplate(item) }, index));
|
|
15
|
+
})] }) }));
|
|
16
|
+
};
|
|
17
|
+
export default AutoCompleteDropdown;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import { ButtonSize, ButtonType, type AnchorButtonProps, type ButtonProps, type NativeButtonProps } from './button.types';
|
|
3
|
+
declare const Button: FC<ButtonProps>;
|
|
4
|
+
export default Button;
|
|
5
|
+
export { ButtonSize, ButtonType };
|
|
6
|
+
export type { ButtonProps, AnchorButtonProps, NativeButtonProps };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// 第三方工具库: 自动智能拼接 class 字符串
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { ButtonSize, ButtonType, } from './button.types';
|
|
5
|
+
const Button = ({ className, disabled = false, size, btnType = ButtonType.Default, children, href, ...restProps }) => {
|
|
6
|
+
// btn, btn-lg, btn-primary
|
|
7
|
+
// ① classNames 生成 class
|
|
8
|
+
/*
|
|
9
|
+
用户写:<Button btnType="primary" size="lg">
|
|
10
|
+
组件算出 class:btn btn-primary btn-lg
|
|
11
|
+
*/
|
|
12
|
+
// 返回 字符串
|
|
13
|
+
const classes = classNames('btn', className, {
|
|
14
|
+
/*
|
|
15
|
+
外部传来 <Button btnType={ButtonType.Primary}>
|
|
16
|
+
-> 变成 { "btn-primary": true }
|
|
17
|
+
-> classNames 会加上: btn-primary
|
|
18
|
+
-> 最终变为 : btn btn-primary
|
|
19
|
+
*/
|
|
20
|
+
[`btn-${btnType}`]: btnType,
|
|
21
|
+
[`btn-${size}`]: size,
|
|
22
|
+
// 是 link 按钮 && disabled === true
|
|
23
|
+
// classNames 会加上: disabled
|
|
24
|
+
disabled: btnType === ButtonType.Link && disabled,
|
|
25
|
+
});
|
|
26
|
+
// ② 根据类型决定渲染:
|
|
27
|
+
/*
|
|
28
|
+
用户写:<Button btnType="link" href="https://xxx">
|
|
29
|
+
组件算出: <a class="btn btn-link">...</a>
|
|
30
|
+
*/
|
|
31
|
+
if (btnType === ButtonType.Link && href) {
|
|
32
|
+
return (_jsx("a", { className: classes, href: href, ...restProps, children: children }));
|
|
33
|
+
}
|
|
34
|
+
/*
|
|
35
|
+
用户写:<Button btnType="primary">
|
|
36
|
+
组件算出: <button class="btn btn-primary">
|
|
37
|
+
*/
|
|
38
|
+
return (_jsx("button", { className: classes, disabled: disabled, ...restProps, children: children }));
|
|
39
|
+
};
|
|
40
|
+
// 导出一个 React 组件函数
|
|
41
|
+
export default Button;
|
|
42
|
+
// 重新导出类型和枚举,方便测试等地方从 `./button` 统一导入
|
|
43
|
+
export { ButtonSize, ButtonType };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export declare const ButtonSize: {
|
|
3
|
+
readonly Large: "lg";
|
|
4
|
+
readonly Small: "sm";
|
|
5
|
+
};
|
|
6
|
+
export type ButtonSize = (typeof ButtonSize)[keyof typeof ButtonSize];
|
|
7
|
+
export declare const ButtonType: {
|
|
8
|
+
readonly Primary: "primary";
|
|
9
|
+
readonly Default: "default";
|
|
10
|
+
readonly Danger: "danger";
|
|
11
|
+
readonly Link: "link";
|
|
12
|
+
};
|
|
13
|
+
export type ButtonType = (typeof ButtonType)[keyof typeof ButtonType];
|
|
14
|
+
export interface BaseButtonProps {
|
|
15
|
+
className?: string;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
size?: ButtonSize;
|
|
18
|
+
btnType?: ButtonType;
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
href?: string;
|
|
21
|
+
}
|
|
22
|
+
export type NativeButtonProps = BaseButtonProps & ButtonHTMLAttributes<HTMLButtonElement>;
|
|
23
|
+
export type AnchorButtonProps = BaseButtonProps & AnchorHTMLAttributes<HTMLAnchorElement>;
|
|
24
|
+
export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProps>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* 定义 Button 组件可以接收哪些 props(组件 API) */
|
|
2
|
+
// TS 导出 两个固定值 'lg' / 'sm'
|
|
3
|
+
/*
|
|
4
|
+
as const: 把 ButtonSize对象的值 变成不可修改的 字面量类型
|
|
5
|
+
- 如果没有 as const: Large: string
|
|
6
|
+
- 如果有 as const: Large: "lg"
|
|
7
|
+
|
|
8
|
+
不写 as const: Large: 'lg' 会被 TS 自动推导为 string 类型
|
|
9
|
+
*/
|
|
10
|
+
export const ButtonSize = {
|
|
11
|
+
Large: 'lg',
|
|
12
|
+
Small: 'sm',
|
|
13
|
+
};
|
|
14
|
+
export const ButtonType = {
|
|
15
|
+
Primary: 'primary',
|
|
16
|
+
Default: 'default',
|
|
17
|
+
Danger: 'danger',
|
|
18
|
+
Link: 'link',
|
|
19
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { IconProps } from './icon.types';
|
|
3
|
+
/**
|
|
4
|
+
* 提供了一套常用的图标集合 基于 react-fontawesome。
|
|
5
|
+
*
|
|
6
|
+
* 支持 react-fontawesome的所有属性 可以在这里查询 https://github.com/FortAwesome/react-fontawesome#basic
|
|
7
|
+
*
|
|
8
|
+
* 支持 fontawesome 所有 free-solid-icons,可以在这里查看所有图标 https://fontawesome.com/icons?d=gallery&s=solid&m=free
|
|
9
|
+
* ### 引用方法
|
|
10
|
+
*
|
|
11
|
+
* ~~~js
|
|
12
|
+
* import { Icon } from 'vikingship'
|
|
13
|
+
* ~~~
|
|
14
|
+
*/
|
|
15
|
+
export declare const Icon: FC<IconProps>;
|
|
16
|
+
export default Icon;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
4
|
+
/**
|
|
5
|
+
* 提供了一套常用的图标集合 基于 react-fontawesome。
|
|
6
|
+
*
|
|
7
|
+
* 支持 react-fontawesome的所有属性 可以在这里查询 https://github.com/FortAwesome/react-fontawesome#basic
|
|
8
|
+
*
|
|
9
|
+
* 支持 fontawesome 所有 free-solid-icons,可以在这里查看所有图标 https://fontawesome.com/icons?d=gallery&s=solid&m=free
|
|
10
|
+
* ### 引用方法
|
|
11
|
+
*
|
|
12
|
+
* ~~~js
|
|
13
|
+
* import { Icon } from 'vikingship'
|
|
14
|
+
* ~~~
|
|
15
|
+
*/
|
|
16
|
+
export const Icon = (props) => {
|
|
17
|
+
// icon-primary
|
|
18
|
+
const { className, theme, ...restProps } = props;
|
|
19
|
+
const classes = classNames('viking-icon', className, {
|
|
20
|
+
[`icon-${theme}`]: theme
|
|
21
|
+
});
|
|
22
|
+
return (_jsx(FontAwesomeIcon, { className: classes, ...restProps }));
|
|
23
|
+
};
|
|
24
|
+
export default Icon;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
|
|
2
|
+
export type ThemeProps = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'light' | 'dark';
|
|
3
|
+
export interface IconProps extends FontAwesomeIconProps {
|
|
4
|
+
/** 支持框架主题,根据主题显示不同的颜色 */
|
|
5
|
+
theme?: ThemeProps;
|
|
6
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import Icon from '../Icon/icon';
|
|
5
|
+
export const Input = (props) => {
|
|
6
|
+
const { disabled, size, icon, prepend, append, style, onChange, ...restProps } = props;
|
|
7
|
+
const classes = useMemo(() => classNames('viking-input-wrapper', {
|
|
8
|
+
[`input-size-${size}`]: size,
|
|
9
|
+
'input-group': prepend || append,
|
|
10
|
+
'input-group-prepend': !!prepend,
|
|
11
|
+
'input-group-append': !!append,
|
|
12
|
+
}), [append, prepend, size]);
|
|
13
|
+
const inputClasses = classNames('viking-input-inner', {
|
|
14
|
+
'is-disabled': disabled,
|
|
15
|
+
});
|
|
16
|
+
const handleChange = (e) => {
|
|
17
|
+
if (onChange)
|
|
18
|
+
onChange(e);
|
|
19
|
+
};
|
|
20
|
+
const renderPrepend = () => {
|
|
21
|
+
if (!prepend)
|
|
22
|
+
return null;
|
|
23
|
+
return _jsx("div", { className: "viking-input-group-prepend", children: prepend });
|
|
24
|
+
};
|
|
25
|
+
const renderAppend = () => {
|
|
26
|
+
if (!append && !icon)
|
|
27
|
+
return null;
|
|
28
|
+
return (_jsxs("div", { className: "viking-input-group-append", children: [append, icon ? (_jsx("div", { className: "icon-wrapper", children: _jsx(Icon, { icon: icon }) })) : null] }));
|
|
29
|
+
};
|
|
30
|
+
return (_jsxs("div", { className: classes, style: style, children: [renderPrepend(), _jsx("input", { className: inputClasses, disabled: disabled, onChange: handleChange, ...restProps }), renderAppend()] }));
|
|
31
|
+
};
|
|
32
|
+
export default Input;
|