@fe-free/core 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/package.json +3 -2
- package/src/crud/crud.stories.tsx +14 -0
- package/src/crud/crud.tsx +83 -53
- package/src/crud/crud_of_simple.stories.tsx +83 -7
- package/src/crud/crud_of_simple.tsx +27 -11
- package/src/crud/style.scss +29 -0
- package/src/crud/types.tsx +8 -0
- package/src/index.ts +1 -0
- package/src/use_localforage_state.tsx +30 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fe-free/core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"author": "",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"axios": "^1.6.5",
|
|
30
30
|
"classnames": "^2.5.1",
|
|
31
31
|
"github-markdown-css": "^5.8.1",
|
|
32
|
+
"localforage": "^1.10.0",
|
|
32
33
|
"lodash-es": "^4.17.21",
|
|
33
34
|
"react-ace": "^11.0.1",
|
|
34
35
|
"react-markdown": "^9.1.0",
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
"remark-gfm": "^4.0.1",
|
|
39
40
|
"vanilla-jsoneditor": "^0.23.1",
|
|
40
41
|
"zustand": "^4.5.4",
|
|
41
|
-
"@fe-free/tool": "2.1.
|
|
42
|
+
"@fe-free/tool": "2.1.1"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"@ant-design/pro-components": "^2.8.7",
|
|
@@ -183,6 +183,14 @@ export const MoreCustom: Story = {
|
|
|
183
183
|
},
|
|
184
184
|
}}
|
|
185
185
|
createButton={<Button type="primary">自定义新建文本</Button>}
|
|
186
|
+
readProps={{
|
|
187
|
+
operateIsDisabled: (record) => {
|
|
188
|
+
if (record.id % 3) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return true;
|
|
192
|
+
},
|
|
193
|
+
}}
|
|
186
194
|
requestDeleteByRecord={fakeDeleteByRecord}
|
|
187
195
|
deleteProps={{
|
|
188
196
|
nameIndex: 'name',
|
|
@@ -214,6 +222,12 @@ export const MoreCustom: Story = {
|
|
|
214
222
|
}
|
|
215
223
|
return true;
|
|
216
224
|
},
|
|
225
|
+
operateIsHidden: (record) => {
|
|
226
|
+
if (record.id % 4) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
},
|
|
217
231
|
}}
|
|
218
232
|
/>
|
|
219
233
|
);
|
package/src/crud/crud.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ActionType } from '@ant-design/pro-components';
|
|
2
2
|
import { Button, message } from 'antd';
|
|
3
|
+
import classNames from 'classnames';
|
|
3
4
|
import { isString } from 'lodash-es';
|
|
4
5
|
import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from 'react';
|
|
5
6
|
import { Link } from 'react-router-dom';
|
|
@@ -112,67 +113,97 @@ function CRUDComponent<
|
|
|
112
113
|
const btns: React.ReactNode[] = [];
|
|
113
114
|
|
|
114
115
|
if (actions.includes('read')) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
116
|
+
const hidden = readProps?.operateIsHidden?.(record) || false;
|
|
117
|
+
if (!hidden) {
|
|
118
|
+
const disabled = readProps?.operateIsDisabled?.(record) || false;
|
|
119
|
+
if (disabled) {
|
|
120
|
+
btns.push(
|
|
121
|
+
<span key="read" className="text-desc cursor-not-allowed">
|
|
122
|
+
{readProps?.operateText || '查看'}
|
|
123
|
+
</span>,
|
|
124
|
+
);
|
|
125
|
+
} else {
|
|
126
|
+
btns.push(
|
|
127
|
+
<CRUDDetail
|
|
128
|
+
key="read"
|
|
129
|
+
id={record[idField]}
|
|
130
|
+
record={record}
|
|
131
|
+
onSuccess={handleReload}
|
|
132
|
+
trigger={<a>{readProps?.operateText || '查看'}</a>}
|
|
133
|
+
action="read"
|
|
134
|
+
{...detailProps}
|
|
135
|
+
/>,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
if (actions.includes('read_detail')) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
const hidden = readProps?.operateIsHidden?.(record) || false;
|
|
143
|
+
if (!hidden) {
|
|
144
|
+
const disabled = readProps?.operateIsDisabled?.(record) || false;
|
|
145
|
+
if (disabled) {
|
|
146
|
+
btns.push(
|
|
147
|
+
<span key="read" className="text-desc cursor-not-allowed">
|
|
148
|
+
{readProps?.operateText || '查看'}
|
|
149
|
+
</span>,
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
btns.push(
|
|
153
|
+
<Link
|
|
154
|
+
key="read_detail"
|
|
155
|
+
to={`./detail/${record[detailIdIndex || 'id']}`}
|
|
156
|
+
target={readProps?.target}
|
|
157
|
+
>
|
|
158
|
+
{readProps?.operateText || '查看'}
|
|
159
|
+
</Link>,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
138
163
|
}
|
|
139
164
|
|
|
140
165
|
if (actions.includes('update')) {
|
|
141
|
-
const
|
|
166
|
+
const hidden = updateProps?.operateIsHidden?.(record) || false;
|
|
167
|
+
if (!hidden) {
|
|
168
|
+
const disabled = updateProps?.operateIsDisabled?.(record) || false;
|
|
142
169
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
if (disabled) {
|
|
171
|
+
btns.push(
|
|
172
|
+
<span key="update" className="text-desc cursor-not-allowed">
|
|
173
|
+
{updateProps?.operateText || '编辑'}
|
|
174
|
+
</span>,
|
|
175
|
+
);
|
|
176
|
+
} else {
|
|
177
|
+
btns.push(
|
|
178
|
+
<CRUDDetail
|
|
179
|
+
key="update"
|
|
180
|
+
id={record[idField]}
|
|
181
|
+
record={record}
|
|
182
|
+
onSuccess={handleReload}
|
|
183
|
+
trigger={<a>{updateProps?.operateText || '编辑'}</a>}
|
|
184
|
+
action="update"
|
|
185
|
+
{...detailProps}
|
|
186
|
+
/>,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
161
189
|
}
|
|
162
190
|
}
|
|
163
191
|
|
|
164
192
|
if (actions.includes('delete') && deleteProps) {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
193
|
+
const hidden = deleteProps?.operateIsHidden?.(record) || false;
|
|
194
|
+
if (!hidden) {
|
|
195
|
+
const disabled = deleteProps?.operateIsDisabled?.(record) || false;
|
|
196
|
+
btns.push(
|
|
197
|
+
<OperateDelete
|
|
198
|
+
key="delete"
|
|
199
|
+
name={record[deleteProps.nameIndex]}
|
|
200
|
+
desc={deleteProps.desc}
|
|
201
|
+
operateText={deleteProps.operateText}
|
|
202
|
+
disabled={disabled}
|
|
203
|
+
onDelete={getHandleDelete(record)}
|
|
204
|
+
/>,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
176
207
|
}
|
|
177
208
|
|
|
178
209
|
return (
|
|
@@ -206,9 +237,8 @@ function CRUDComponent<
|
|
|
206
237
|
operateColumnProps,
|
|
207
238
|
actions,
|
|
208
239
|
deleteProps,
|
|
240
|
+
readProps,
|
|
209
241
|
handleReload,
|
|
210
|
-
readProps?.operateText,
|
|
211
|
-
readProps?.target,
|
|
212
242
|
detailProps,
|
|
213
243
|
detailIdIndex,
|
|
214
244
|
updateProps,
|
|
@@ -259,7 +289,7 @@ function CRUDComponent<
|
|
|
259
289
|
});
|
|
260
290
|
|
|
261
291
|
return (
|
|
262
|
-
<div className=
|
|
292
|
+
<div className={classNames('fec-crud')}>
|
|
263
293
|
<Table<DataSource>
|
|
264
294
|
rowKey="id"
|
|
265
295
|
{...tableProps}
|
|
@@ -17,10 +17,45 @@ export const Normal: Story = {
|
|
|
17
17
|
render: () => {
|
|
18
18
|
const columns = [
|
|
19
19
|
{
|
|
20
|
-
title: '
|
|
21
|
-
dataIndex: '
|
|
20
|
+
title: '名字(省略)',
|
|
21
|
+
dataIndex: 'name',
|
|
22
22
|
search: true,
|
|
23
|
+
ellipsis: true,
|
|
23
24
|
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<CRUDOfSimple
|
|
29
|
+
actions={['create', 'delete']}
|
|
30
|
+
tableProps={{
|
|
31
|
+
columns,
|
|
32
|
+
request: fakeRequest,
|
|
33
|
+
pagination: false,
|
|
34
|
+
}}
|
|
35
|
+
requestDeleteByRecord={fakeDeleteByRecord}
|
|
36
|
+
deleteProps={{
|
|
37
|
+
nameIndex: 'name',
|
|
38
|
+
}}
|
|
39
|
+
detailForm={() => (
|
|
40
|
+
<>
|
|
41
|
+
<ProFormText
|
|
42
|
+
name="name"
|
|
43
|
+
label="名字"
|
|
44
|
+
required
|
|
45
|
+
rules={[{ required: true }]}
|
|
46
|
+
extra="extra extra extra extra"
|
|
47
|
+
/>
|
|
48
|
+
</>
|
|
49
|
+
)}
|
|
50
|
+
requestCreateByValues={fakeCreate}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const WithSearch: Story = {
|
|
57
|
+
render: () => {
|
|
58
|
+
const columns = [
|
|
24
59
|
{
|
|
25
60
|
title: '名字(省略)',
|
|
26
61
|
dataIndex: 'name',
|
|
@@ -53,19 +88,59 @@ export const Normal: Story = {
|
|
|
53
88
|
</>
|
|
54
89
|
)}
|
|
55
90
|
requestCreateByValues={fakeCreate}
|
|
91
|
+
simpleSearchProps={{
|
|
92
|
+
name: 'name',
|
|
93
|
+
widthFull: true,
|
|
94
|
+
}}
|
|
56
95
|
/>
|
|
57
96
|
);
|
|
58
97
|
},
|
|
59
98
|
};
|
|
60
99
|
|
|
61
|
-
export const
|
|
100
|
+
export const HoverShow: Story = {
|
|
62
101
|
render: () => {
|
|
63
102
|
const columns = [
|
|
64
103
|
{
|
|
65
|
-
title: '
|
|
66
|
-
dataIndex: '
|
|
104
|
+
title: '名字(省略)',
|
|
105
|
+
dataIndex: 'name',
|
|
67
106
|
search: true,
|
|
107
|
+
ellipsis: true,
|
|
68
108
|
},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<CRUDOfSimple
|
|
113
|
+
actions={['create', 'delete']}
|
|
114
|
+
tableProps={{
|
|
115
|
+
columns,
|
|
116
|
+
request: fakeRequest,
|
|
117
|
+
pagination: false,
|
|
118
|
+
}}
|
|
119
|
+
requestDeleteByRecord={fakeDeleteByRecord}
|
|
120
|
+
deleteProps={{
|
|
121
|
+
nameIndex: 'name',
|
|
122
|
+
}}
|
|
123
|
+
detailForm={() => (
|
|
124
|
+
<>
|
|
125
|
+
<ProFormText
|
|
126
|
+
name="name"
|
|
127
|
+
label="名字"
|
|
128
|
+
required
|
|
129
|
+
rules={[{ required: true }]}
|
|
130
|
+
extra="extra extra extra extra"
|
|
131
|
+
/>
|
|
132
|
+
</>
|
|
133
|
+
)}
|
|
134
|
+
requestCreateByValues={fakeCreate}
|
|
135
|
+
simpleOperateHoverShow
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const JustSearch: Story = {
|
|
142
|
+
render: () => {
|
|
143
|
+
const columns = [
|
|
69
144
|
{
|
|
70
145
|
title: '名字(省略)',
|
|
71
146
|
dataIndex: 'name',
|
|
@@ -76,7 +151,7 @@ export const WithSearch: Story = {
|
|
|
76
151
|
|
|
77
152
|
return (
|
|
78
153
|
<CRUDOfSimple
|
|
79
|
-
actions={['
|
|
154
|
+
actions={['delete']}
|
|
80
155
|
tableProps={{
|
|
81
156
|
columns,
|
|
82
157
|
request: fakeRequest,
|
|
@@ -99,7 +174,8 @@ export const WithSearch: Story = {
|
|
|
99
174
|
)}
|
|
100
175
|
requestCreateByValues={fakeCreate}
|
|
101
176
|
simpleSearchProps={{
|
|
102
|
-
name: '
|
|
177
|
+
name: 'name',
|
|
178
|
+
widthFull: true,
|
|
103
179
|
}}
|
|
104
180
|
/>
|
|
105
181
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useDebounce } from 'ahooks';
|
|
2
2
|
import { Input } from 'antd';
|
|
3
|
+
import classNames from 'classnames';
|
|
3
4
|
import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
5
|
import { CRUD } from './crud';
|
|
5
6
|
import type { CRUDMethods, CRUDProps } from './types';
|
|
@@ -8,12 +9,15 @@ interface CRUDOfSimpleProps<
|
|
|
8
9
|
DataSource extends Record<string, any> = any,
|
|
9
10
|
Key extends string | number = string,
|
|
10
11
|
> extends CRUDProps<DataSource, Key> {
|
|
12
|
+
simpleOperateHoverShow?: boolean;
|
|
11
13
|
// 传才开启搜索
|
|
12
14
|
simpleSearchProps?: {
|
|
13
15
|
/** 搜索项的名称,默认 keywords */
|
|
14
16
|
name: string;
|
|
15
17
|
/** 搜索项的 placeholder,默认 请输入 */
|
|
16
18
|
placeholder?: string;
|
|
19
|
+
/** 占满宽度 */
|
|
20
|
+
widthFull?: boolean;
|
|
17
21
|
};
|
|
18
22
|
}
|
|
19
23
|
|
|
@@ -39,12 +43,13 @@ function SearchRender(props: {
|
|
|
39
43
|
allowClear
|
|
40
44
|
value={props.value}
|
|
41
45
|
onChange={(e) => props.onChange(e.target.value)}
|
|
46
|
+
className="w-full"
|
|
42
47
|
/>
|
|
43
48
|
);
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
function CRUDOfSimpleComponent(props: CRUDOfSimpleProps, ref: React.ForwardedRef<CRUDMethods>) {
|
|
47
|
-
const { simpleSearchProps, tableProps, ...rest } = props;
|
|
52
|
+
const { simpleSearchProps, tableProps, simpleOperateHoverShow, ...rest } = props;
|
|
48
53
|
|
|
49
54
|
useTips(props);
|
|
50
55
|
const [searchValue, setSearchValue] = useState<string>('');
|
|
@@ -61,15 +66,16 @@ function CRUDOfSimpleComponent(props: CRUDOfSimpleProps, ref: React.ForwardedRef
|
|
|
61
66
|
const toolBarRender = useCallback(
|
|
62
67
|
(...args) => {
|
|
63
68
|
return [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
simpleSearchProps && (
|
|
70
|
+
<SearchRender
|
|
71
|
+
key="search-input"
|
|
72
|
+
placeholder={simpleSearchProps.placeholder}
|
|
73
|
+
value={searchValue}
|
|
74
|
+
onChange={(value) => setSearchValue(value)}
|
|
75
|
+
/>
|
|
76
|
+
),
|
|
77
|
+
// 留更多间隔,避免直接贴右边。
|
|
78
|
+
simpleSearchProps && <div key="search-gap" />,
|
|
73
79
|
// @ts-ignore
|
|
74
80
|
...(tableProps.toolBarRender ? tableProps.toolBarRender(...args) : []),
|
|
75
81
|
];
|
|
@@ -89,7 +95,12 @@ function CRUDOfSimpleComponent(props: CRUDOfSimpleProps, ref: React.ForwardedRef
|
|
|
89
95
|
}, [debouncedSearchValue, simpleSearchProps, tableProps.params]);
|
|
90
96
|
|
|
91
97
|
return (
|
|
92
|
-
<div
|
|
98
|
+
<div
|
|
99
|
+
className={classNames('fec-crud-of-simple', {
|
|
100
|
+
'fec-crud-of-simple-hover-show': simpleOperateHoverShow,
|
|
101
|
+
'fec-crud-of-simple-search-width-full': simpleSearchProps?.widthFull,
|
|
102
|
+
})}
|
|
103
|
+
>
|
|
93
104
|
<CRUD
|
|
94
105
|
ref={ref}
|
|
95
106
|
{...rest}
|
|
@@ -104,6 +115,11 @@ function CRUDOfSimpleComponent(props: CRUDOfSimpleProps, ref: React.ForwardedRef
|
|
|
104
115
|
// 简单的隐藏搜索栏
|
|
105
116
|
search: false,
|
|
106
117
|
}}
|
|
118
|
+
operateColumnProps={{
|
|
119
|
+
// hoverShow 情况下,默认 width 1
|
|
120
|
+
width: simpleOperateHoverShow ? 1 : undefined,
|
|
121
|
+
...props.operateColumnProps,
|
|
122
|
+
}}
|
|
107
123
|
/>
|
|
108
124
|
</div>
|
|
109
125
|
);
|
package/src/crud/style.scss
CHANGED
|
@@ -13,4 +13,33 @@
|
|
|
13
13
|
.ant-pro-table-list-toolbar {
|
|
14
14
|
border-bottom: 1px solid #f0f0f0;
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
&.fec-crud-of-simple-hover-show {
|
|
18
|
+
.ant-table-cell-fix-right {
|
|
19
|
+
position: absolute !important;
|
|
20
|
+
display: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.ant-table-row {
|
|
24
|
+
&:hover {
|
|
25
|
+
.ant-table-cell-fix-right {
|
|
26
|
+
display: block;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&.fec-crud-of-simple-search-width-full {
|
|
33
|
+
.ant-pro-table-list-toolbar-container {
|
|
34
|
+
justify-content: unset;
|
|
35
|
+
|
|
36
|
+
.ant-pro-table-list-toolbar-right {
|
|
37
|
+
justify-content: unset;
|
|
38
|
+
|
|
39
|
+
& > div {
|
|
40
|
+
flex: 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
16
45
|
}
|
package/src/crud/types.tsx
CHANGED
|
@@ -62,6 +62,10 @@ interface CRUDProps<DataSource = any, Key = string> {
|
|
|
62
62
|
readProps?: {
|
|
63
63
|
/** 文本 */
|
|
64
64
|
operateText?: string;
|
|
65
|
+
/** ”查看”是否禁用 */
|
|
66
|
+
operateIsDisabled?: (record: DataSource) => boolean;
|
|
67
|
+
/** ”查看”是否隐藏 */
|
|
68
|
+
operateIsHidden?: (record: DataSource) => boolean;
|
|
65
69
|
/** 打开方式, action 为 read_detail 有效 */
|
|
66
70
|
target?: '_blank';
|
|
67
71
|
/** 保存按钮文本 */
|
|
@@ -81,6 +85,8 @@ interface CRUDProps<DataSource = any, Key = string> {
|
|
|
81
85
|
operateText?: string;
|
|
82
86
|
/** ”编辑”是否禁用 */
|
|
83
87
|
operateIsDisabled?: (record: DataSource) => boolean;
|
|
88
|
+
/** ”编辑”是否隐藏 */
|
|
89
|
+
operateIsHidden?: (record: DataSource) => boolean;
|
|
84
90
|
/** 保存按钮文本 */
|
|
85
91
|
submitText?: string;
|
|
86
92
|
/** 重置按钮文本 */
|
|
@@ -99,6 +105,8 @@ interface CRUDProps<DataSource = any, Key = string> {
|
|
|
99
105
|
operateText?: string;
|
|
100
106
|
/** “删除”是否禁用 */
|
|
101
107
|
operateIsDisabled?: (record: DataSource) => boolean;
|
|
108
|
+
/** ”删除”是否隐藏 */
|
|
109
|
+
operateIsHidden?: (record: DataSource) => boolean;
|
|
102
110
|
/** 显示名称索引 */
|
|
103
111
|
nameIndex: keyof DataSource;
|
|
104
112
|
/** 删除确认描述 */
|
package/src/index.ts
CHANGED
|
@@ -29,4 +29,5 @@ export {
|
|
|
29
29
|
export { Markdown } from './markdown';
|
|
30
30
|
export { Table } from './table';
|
|
31
31
|
export type { TableProps } from './table';
|
|
32
|
+
export { useLocalforageState } from './use_localforage_state';
|
|
32
33
|
export { CustomValueTypeEnum, customValueTypeMap } from './value_type_map';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import localforage from 'localforage';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
const useLocalforageState = <T = any,>(
|
|
5
|
+
key: string,
|
|
6
|
+
options: {
|
|
7
|
+
defaultValue?: T;
|
|
8
|
+
},
|
|
9
|
+
): [T | undefined, (value: T) => void, boolean] => {
|
|
10
|
+
const [ready, setReady] = useState(false);
|
|
11
|
+
const [value, setValue] = useState<T | undefined>(options.defaultValue);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
localforage.getItem(key).then((v) => {
|
|
15
|
+
if (v !== undefined) {
|
|
16
|
+
setValue(v as T);
|
|
17
|
+
}
|
|
18
|
+
setReady(true);
|
|
19
|
+
});
|
|
20
|
+
}, [key]);
|
|
21
|
+
|
|
22
|
+
const setValueAndSave = (v: T) => {
|
|
23
|
+
setValue(v);
|
|
24
|
+
localforage.setItem(key, v);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return [value, setValueAndSave, ready];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { useLocalforageState };
|