@onehat/ui 0.4.57 → 0.4.59
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/package.json +1 -1
- package/src/Components/Hoc/withEditor.js +1 -1
- package/src/Components/Layout/AsyncOperation.js +169 -26
- package/src/Components/Tab/Tab.js +93 -0
- package/src/Components/Tab/TabBar.js +62 -102
- package/src/Components/Tab/TabButton.js +82 -0
- package/src/Constants/Progress.js +1 -0
- package/src/Constants/Styles.js +3 -1
package/package.json
CHANGED
|
@@ -502,7 +502,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
502
502
|
setIsSaving(true);
|
|
503
503
|
let success = true;
|
|
504
504
|
const tempListener = (msg, data) => {
|
|
505
|
-
success =
|
|
505
|
+
success = false;
|
|
506
506
|
};
|
|
507
507
|
|
|
508
508
|
Repository.on('error', tempListener); // add a temporary listener for the error event
|
|
@@ -1,28 +1,46 @@
|
|
|
1
|
-
import { useState, } from 'react';
|
|
1
|
+
import { useState, useRef, useEffect, } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
Box,
|
|
4
|
+
ScrollView,
|
|
3
5
|
Text,
|
|
4
6
|
VStack,
|
|
5
7
|
} from '@project-components/Gluestack';
|
|
8
|
+
import { useSelector, useDispatch, } from 'react-redux';
|
|
9
|
+
import { PROGRESS_COMPLETED } from '../../Constants/Progress.js';
|
|
10
|
+
import useForceUpdate from '../../Hooks/useForceUpdate.js';
|
|
11
|
+
import isJson from '../../Functions/isJson.js';
|
|
6
12
|
import Form from '../Form/Form.js';
|
|
7
13
|
import Button from '../Buttons/Button.js';
|
|
8
|
-
import CenterBox from '../Layout/CenterBox.js';
|
|
9
14
|
import withComponent from '../Hoc/withComponent.js';
|
|
10
15
|
import withAlert from '../Hoc/withAlert.js';
|
|
11
16
|
import ChevronLeft from '../Icons/ChevronLeft.js';
|
|
12
17
|
import ChevronRight from '../Icons/ChevronRight.js';
|
|
13
18
|
import Play from '../Icons/Play.js';
|
|
19
|
+
import EllipsisHorizontal from '../Icons/EllipsisHorizontal.js';
|
|
14
20
|
import Stop from '../Icons/Stop.js';
|
|
15
21
|
import TabBar from '../Tab/TabBar.js';
|
|
16
22
|
import Panel from '../Panel/Panel.js';
|
|
17
23
|
import Toolbar from '../Toolbar/Toolbar.js';
|
|
18
24
|
import _ from 'lodash';
|
|
19
25
|
|
|
26
|
+
// NOTE: This component assumes you have an AppSlice, that has
|
|
27
|
+
// an 'operationsInProgress' state var and a 'setOperationsInProgress' action.
|
|
20
28
|
|
|
21
29
|
function AsyncOperation(props) {
|
|
30
|
+
|
|
31
|
+
if (!props.Repository || !props.action) {
|
|
32
|
+
throw Error('AsyncOperation: Repository and action are required!');
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
const {
|
|
23
36
|
action,
|
|
24
37
|
Repository,
|
|
25
38
|
formItems = [],
|
|
39
|
+
formStartingValues = {},
|
|
40
|
+
getProgressUpdates = false,
|
|
41
|
+
parseProgress, // optional fn, accepts 'response' as arg and returns progress string
|
|
42
|
+
progressStuckThreshold = null, // e.g. 3, if left blank, doesn't check for stuck state
|
|
43
|
+
updateInterval = 10000, // ms
|
|
26
44
|
|
|
27
45
|
// withComponent
|
|
28
46
|
self,
|
|
@@ -30,38 +48,40 @@ function AsyncOperation(props) {
|
|
|
30
48
|
// withAlert
|
|
31
49
|
alert,
|
|
32
50
|
} = props,
|
|
51
|
+
dispatch = useDispatch(),
|
|
33
52
|
initiate = async () => {
|
|
34
53
|
|
|
35
|
-
|
|
36
|
-
alert('AsyncOperation: Repository and action are required!');
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
54
|
+
clearProgress();
|
|
40
55
|
setFooter(getFooter('processing'));
|
|
56
|
+
setIsInProgress(true);
|
|
41
57
|
|
|
42
58
|
const
|
|
43
|
-
method = Repository.methods.
|
|
59
|
+
method = Repository.methods.edit,
|
|
44
60
|
uri = Repository.getModel() + '/' + action,
|
|
45
|
-
formValues = self
|
|
61
|
+
formValues = self?.children?.form?.formGetValues() || {},
|
|
46
62
|
result = await Repository._send(method, uri, formValues);
|
|
47
|
-
|
|
63
|
+
|
|
64
|
+
setFormValues(formValues);
|
|
65
|
+
|
|
48
66
|
const response = Repository._processServerResponse(result);
|
|
49
67
|
if (!response.success) {
|
|
50
|
-
|
|
51
|
-
reset();
|
|
68
|
+
resetToInitialState();
|
|
52
69
|
return;
|
|
53
70
|
}
|
|
54
71
|
|
|
55
|
-
let results = <
|
|
72
|
+
let results = <Text>Success</Text>;
|
|
56
73
|
if (response.message) {
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
let message = response.message;
|
|
75
|
+
if (isJson(message)) {
|
|
76
|
+
message = JSON.parse(message);
|
|
77
|
+
}
|
|
78
|
+
results = _.isArray(message) ?
|
|
59
79
|
<VStack>
|
|
60
|
-
{
|
|
80
|
+
{message?.map((line, ix)=> {
|
|
61
81
|
return <Text key={ix}>{line}</Text>;
|
|
62
82
|
})}
|
|
63
|
-
</VStack> :
|
|
64
|
-
<Text>{
|
|
83
|
+
</VStack> :
|
|
84
|
+
<Text>{message}</Text>;
|
|
65
85
|
}
|
|
66
86
|
showResults(results);
|
|
67
87
|
},
|
|
@@ -70,7 +90,7 @@ function AsyncOperation(props) {
|
|
|
70
90
|
case 'initiate':
|
|
71
91
|
return <Toolbar>
|
|
72
92
|
<Button
|
|
73
|
-
text="
|
|
93
|
+
text="Start"
|
|
74
94
|
rightIcon={ChevronRight}
|
|
75
95
|
onPress={() => initiate()}
|
|
76
96
|
/>
|
|
@@ -88,29 +108,144 @@ function AsyncOperation(props) {
|
|
|
88
108
|
<Button
|
|
89
109
|
text="Reset"
|
|
90
110
|
icon={ChevronLeft}
|
|
91
|
-
onPress={() =>
|
|
111
|
+
onPress={() => resetToInitialState()}
|
|
92
112
|
/>
|
|
93
113
|
</Toolbar>;
|
|
94
114
|
}
|
|
95
115
|
},
|
|
116
|
+
operationsInProgress = useSelector((state) => state.app.operationsInProgress),
|
|
117
|
+
isInProgress = operationsInProgress.includes(action),
|
|
118
|
+
forceUpdate = useForceUpdate(),
|
|
96
119
|
[footer, setFooter] = useState(getFooter()),
|
|
97
|
-
[results, setResults] = useState(''),
|
|
98
|
-
[
|
|
120
|
+
[results, setResults] = useState(isInProgress ? 'Checking progress...' : null),
|
|
121
|
+
[progress, setProgress] = useState(null),
|
|
122
|
+
[isStuck, setIsStuck] = useState(false),
|
|
123
|
+
[currentTabIx, setCurrentTab] = useState(isInProgress ? 1 : 0),
|
|
124
|
+
previousProgressRef = useRef(null),
|
|
125
|
+
unchangedProgressCountRef = useRef(0),
|
|
126
|
+
intervalRef = useRef(null),
|
|
127
|
+
formValuesRef = useRef(null),
|
|
128
|
+
getPreviousProgress = () => {
|
|
129
|
+
return previousProgressRef.current;
|
|
130
|
+
},
|
|
131
|
+
setPreviousProgress = (progress) => {
|
|
132
|
+
previousProgressRef.current = progress;
|
|
133
|
+
},
|
|
134
|
+
getUnchangedProgressCount = () => {
|
|
135
|
+
return unchangedProgressCountRef.current;
|
|
136
|
+
},
|
|
137
|
+
setUnchangedProgressCount = (count) => {
|
|
138
|
+
unchangedProgressCountRef.current = count;
|
|
139
|
+
forceUpdate();
|
|
140
|
+
},
|
|
141
|
+
getInterval = () => {
|
|
142
|
+
return intervalRef.current;
|
|
143
|
+
},
|
|
144
|
+
setIntervalRef = (interval) => { // 'setInterval' is a reserved name
|
|
145
|
+
intervalRef.current = interval;
|
|
146
|
+
},
|
|
147
|
+
getFormValues = () => {
|
|
148
|
+
return formValuesRef.current;
|
|
149
|
+
},
|
|
150
|
+
setFormValues = (values) => {
|
|
151
|
+
formValuesRef.current = values;
|
|
152
|
+
},
|
|
99
153
|
showResults = (results) => {
|
|
100
154
|
setCurrentTab(1);
|
|
101
155
|
setFooter(getFooter('results'));
|
|
102
156
|
setResults(results);
|
|
157
|
+
getProgress();
|
|
103
158
|
},
|
|
104
|
-
|
|
159
|
+
getProgress = (immediately = false) => {
|
|
160
|
+
if (getProgressUpdates) {
|
|
161
|
+
|
|
162
|
+
async function fetchProgress() {
|
|
163
|
+
const
|
|
164
|
+
method = Repository.methods.edit,
|
|
165
|
+
progressAction = 'get' + action.charAt(0).toUpperCase() + action.slice(1) + 'Progress',
|
|
166
|
+
uri = Repository.getModel() + '/' + progressAction,
|
|
167
|
+
result = await Repository._send(method, uri, getFormValues());
|
|
168
|
+
|
|
169
|
+
const response = Repository._processServerResponse(result);
|
|
170
|
+
if (!response.success) {
|
|
171
|
+
alert(result.message);
|
|
172
|
+
clearProgress();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const progress = parseProgress ? parseProgress(response) : response.message
|
|
177
|
+
if (progress === PROGRESS_COMPLETED) {
|
|
178
|
+
clearProgress();
|
|
179
|
+
setProgress(progress);
|
|
180
|
+
} else {
|
|
181
|
+
// in process
|
|
182
|
+
let newUnchangedProgressCount = getUnchangedProgressCount();
|
|
183
|
+
if (progress === getPreviousProgress()) {
|
|
184
|
+
newUnchangedProgressCount++;
|
|
185
|
+
setUnchangedProgressCount(newUnchangedProgressCount);
|
|
186
|
+
if (progressStuckThreshold !== null && newUnchangedProgressCount >= progressStuckThreshold) {
|
|
187
|
+
clearProgress();
|
|
188
|
+
setProgress('The operation appears to be stuck.');
|
|
189
|
+
setIsStuck(true);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
setPreviousProgress(progress);
|
|
193
|
+
setProgress(progress);
|
|
194
|
+
setUnchangedProgressCount(0);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
if (immediately) {
|
|
200
|
+
fetchProgress();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const interval = setInterval(fetchProgress, updateInterval);
|
|
204
|
+
setIntervalRef(interval);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
resetToInitialState = () => {
|
|
105
208
|
setCurrentTab(0);
|
|
106
209
|
setFooter(getFooter());
|
|
210
|
+
clearProgress();
|
|
211
|
+
},
|
|
212
|
+
clearProgress = () => {
|
|
213
|
+
setIsInProgress(false);
|
|
214
|
+
setIsStuck(false);
|
|
215
|
+
setProgress(null);
|
|
216
|
+
setPreviousProgress(null);
|
|
217
|
+
setUnchangedProgressCount(0);
|
|
218
|
+
clearInterval(getInterval());
|
|
219
|
+
setIntervalRef(null);
|
|
220
|
+
},
|
|
221
|
+
setIsInProgress = (isInProgress) => {
|
|
222
|
+
dispatch({
|
|
223
|
+
type: 'app/setOperationsInProgress',
|
|
224
|
+
payload: {
|
|
225
|
+
operation: action,
|
|
226
|
+
isInProgress,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
unchangedProgressCount = getUnchangedProgressCount();
|
|
231
|
+
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
|
|
234
|
+
if (isInProgress) {
|
|
235
|
+
getProgress(true); // true to fetch immediately
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return () => {
|
|
239
|
+
// clear the interval when the component unmounts
|
|
240
|
+
clearInterval(getInterval());
|
|
107
241
|
};
|
|
242
|
+
}, []);
|
|
108
243
|
|
|
109
244
|
return <Panel {...props} footer={footer}>
|
|
110
245
|
<TabBar
|
|
111
246
|
tabs={[
|
|
112
247
|
{
|
|
113
|
-
title: '
|
|
248
|
+
title: 'Start',
|
|
114
249
|
icon: Play,
|
|
115
250
|
isDisabled: currentTabIx !== 0,
|
|
116
251
|
content: <Form
|
|
@@ -119,17 +254,25 @@ function AsyncOperation(props) {
|
|
|
119
254
|
className="w-full h-full flex-1"
|
|
120
255
|
disableFooter={true}
|
|
121
256
|
items={formItems}
|
|
257
|
+
startingValues={formStartingValues}
|
|
122
258
|
/>,
|
|
123
259
|
},
|
|
124
260
|
{
|
|
125
261
|
title: 'Results',
|
|
126
|
-
icon: Stop,
|
|
262
|
+
icon: isInProgress ? EllipsisHorizontal : Stop,
|
|
127
263
|
isDisabled: currentTabIx !== 1,
|
|
128
|
-
content:
|
|
264
|
+
content: <ScrollView className="ScrollView h-full w-full">
|
|
265
|
+
<Box className={`p-4 ${isStuck ? 'text-red-400 font-bold' : ''}`}>
|
|
266
|
+
{progress ?
|
|
267
|
+
progress + (unchangedProgressCount > 0 ? ' (unchanged x' + unchangedProgressCount + ')' : '') :
|
|
268
|
+
results}
|
|
269
|
+
</Box>
|
|
270
|
+
</ScrollView>,
|
|
129
271
|
},
|
|
130
272
|
]}
|
|
131
273
|
currentTabIx={currentTabIx}
|
|
132
274
|
canToggleCollapse={false}
|
|
275
|
+
tabsAreButtons={false}
|
|
133
276
|
/>
|
|
134
277
|
</Panel>;
|
|
135
278
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { forwardRef, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
HStack,
|
|
4
|
+
Icon,
|
|
5
|
+
} from '@project-components/Gluestack';
|
|
6
|
+
import {
|
|
7
|
+
HORIZONTAL,
|
|
8
|
+
VERTICAL,
|
|
9
|
+
} from '../../Constants/Directions.js';
|
|
10
|
+
import withTooltip from '../Hoc/withTooltip.js';
|
|
11
|
+
import IconButton from '../Buttons/IconButton.js';
|
|
12
|
+
import Xmark from '../Icons/Xmark.js';
|
|
13
|
+
import UiGlobals from '../../UiGlobals.js';
|
|
14
|
+
import _ from 'lodash';
|
|
15
|
+
|
|
16
|
+
const Tab = forwardRef((props, ref) => {
|
|
17
|
+
let {
|
|
18
|
+
className,
|
|
19
|
+
direction,
|
|
20
|
+
isDisabled,
|
|
21
|
+
isCurrentTab,
|
|
22
|
+
text,
|
|
23
|
+
_text,
|
|
24
|
+
useIconOnly,
|
|
25
|
+
icon,
|
|
26
|
+
_icon,
|
|
27
|
+
useCloseBtn,
|
|
28
|
+
onClose,
|
|
29
|
+
onPress, // remove it from propsToPass
|
|
30
|
+
...propsToPass
|
|
31
|
+
} = props,
|
|
32
|
+
styles = UiGlobals.styles;
|
|
33
|
+
|
|
34
|
+
if (!ref) {
|
|
35
|
+
ref = useRef();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (isCurrentTab) {
|
|
39
|
+
className += ' ' + styles.TAB_BG_CURRENT;
|
|
40
|
+
_icon.className += ' ' + styles.TAB_ICON_COLOR_CURRENT;
|
|
41
|
+
_text.className += ' ' + styles.TAB_COLOR_CURRENT;
|
|
42
|
+
}
|
|
43
|
+
if (isDisabled) {
|
|
44
|
+
className += ' ' + styles.TAB_BG_DISABLED_2;
|
|
45
|
+
_icon.className += ' ' + styles.TAB_COLOR_DISABLED;
|
|
46
|
+
_text.className += ' ' + styles.TAB_COLOR_DISABLED;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let tab = null;
|
|
50
|
+
if (useIconOnly) {
|
|
51
|
+
tab = <HStack className={className + ' Tab px-[20px] py-2'}>
|
|
52
|
+
<Icon
|
|
53
|
+
{...propsToPass}
|
|
54
|
+
className={className}
|
|
55
|
+
ref={ref}
|
|
56
|
+
{..._icon}
|
|
57
|
+
as={icon}
|
|
58
|
+
/>
|
|
59
|
+
</HStack>;
|
|
60
|
+
} else {
|
|
61
|
+
if (direction === VERTICAL) {
|
|
62
|
+
className += ' w-[200px]';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let closeBtn = null;
|
|
66
|
+
if (useCloseBtn) {
|
|
67
|
+
closeBtn = <IconButton
|
|
68
|
+
{...testProps('tabCloseButton-' + ix)}
|
|
69
|
+
onPress={onClose}
|
|
70
|
+
icon={Xmark}
|
|
71
|
+
_icon={_icon}
|
|
72
|
+
tooltip="Close Tab"
|
|
73
|
+
className="p-0"
|
|
74
|
+
/>;
|
|
75
|
+
}
|
|
76
|
+
tab = <HStack
|
|
77
|
+
{...propsToPass}
|
|
78
|
+
className={className}
|
|
79
|
+
ref={ref}
|
|
80
|
+
>
|
|
81
|
+
<Icon
|
|
82
|
+
{..._icon}
|
|
83
|
+
as={icon}
|
|
84
|
+
/>
|
|
85
|
+
<Text {..._text}>{text}</Text>
|
|
86
|
+
{closeBtn}
|
|
87
|
+
</HStack>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return tab;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export default withTooltip(Tab);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { cloneElement, useState, useEffect, } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
Box,
|
|
3
4
|
HStack,
|
|
4
5
|
HStackNative,
|
|
6
|
+
Icon,
|
|
5
7
|
ScrollView,
|
|
6
8
|
VStack,
|
|
7
9
|
VStackNative,
|
|
@@ -10,6 +12,8 @@ import {
|
|
|
10
12
|
HORIZONTAL,
|
|
11
13
|
VERTICAL,
|
|
12
14
|
} from '../../Constants/Directions.js';
|
|
15
|
+
import Tab from './Tab.js';
|
|
16
|
+
import TabButton from './TabButton.js';
|
|
13
17
|
import Button from '../Buttons/Button.js';
|
|
14
18
|
import UiGlobals from '../../UiGlobals.js';
|
|
15
19
|
import getComponentFromType from '../../Functions/getComponentFromType.js';
|
|
@@ -20,7 +24,6 @@ import Minimize from '../Icons/Minimize.js';
|
|
|
20
24
|
import Maximize from '../Icons/Maximize.js';
|
|
21
25
|
import getSaved from '../../Functions/getSaved.js';
|
|
22
26
|
import setSaved from '../../Functions/setSaved.js';
|
|
23
|
-
import Xmark from '../Icons/Xmark.js';
|
|
24
27
|
import _ from 'lodash';
|
|
25
28
|
|
|
26
29
|
|
|
@@ -37,6 +40,7 @@ function TabBar(props) {
|
|
|
37
40
|
disableCollapse = false,
|
|
38
41
|
startsCollapsed = true,
|
|
39
42
|
canToggleCollapse = true,
|
|
43
|
+
tabsAreButtons = true,
|
|
40
44
|
onChangeCurrentTab,
|
|
41
45
|
onChangeIsCollapsed,
|
|
42
46
|
onPressTab,
|
|
@@ -88,9 +92,9 @@ function TabBar(props) {
|
|
|
88
92
|
},
|
|
89
93
|
renderToggleButton = () => {
|
|
90
94
|
const {
|
|
91
|
-
|
|
92
|
-
className:
|
|
93
|
-
...
|
|
95
|
+
tabProps: {
|
|
96
|
+
className: tabPropsClassName,
|
|
97
|
+
...tabPropsToPass
|
|
94
98
|
},
|
|
95
99
|
textProps: {
|
|
96
100
|
className: textPropsClassName,
|
|
@@ -100,9 +104,9 @@ function TabBar(props) {
|
|
|
100
104
|
className: iconPropsClassName,
|
|
101
105
|
...iconPropsToPass
|
|
102
106
|
},
|
|
103
|
-
} =
|
|
107
|
+
} = getTabProps();
|
|
104
108
|
|
|
105
|
-
let
|
|
109
|
+
let tabClassName = tabPropsClassName,
|
|
106
110
|
textClassName = textPropsClassName,
|
|
107
111
|
iconClassName = iconPropsClassName;
|
|
108
112
|
|
|
@@ -119,14 +123,14 @@ function TabBar(props) {
|
|
|
119
123
|
{...testProps('toggleBtn')}
|
|
120
124
|
key="toggleBtn"
|
|
121
125
|
onPress={onPress}
|
|
122
|
-
{...
|
|
126
|
+
{...tabPropsToPass}
|
|
123
127
|
icon={icon}
|
|
124
128
|
_icon={_icon}
|
|
125
|
-
className={
|
|
129
|
+
className={tabClassName}
|
|
126
130
|
tooltip={isCollapsed ? 'Expand' : 'Collapse'}
|
|
127
131
|
/>;
|
|
128
132
|
} else {
|
|
129
|
-
|
|
133
|
+
tabClassName += `
|
|
130
134
|
${direction === VERTICAL ? 'w-[200px]' : ''}
|
|
131
135
|
pr-0
|
|
132
136
|
mr-0
|
|
@@ -136,10 +140,10 @@ function TabBar(props) {
|
|
|
136
140
|
{...testProps('toggleBtn')}
|
|
137
141
|
key="toggleBtn"
|
|
138
142
|
onPress={onPress}
|
|
139
|
-
{...
|
|
143
|
+
{...tabPropsToPass}
|
|
140
144
|
icon={icon}
|
|
141
145
|
_icon={_icon}
|
|
142
|
-
className={
|
|
146
|
+
className={tabClassName}
|
|
143
147
|
text="Collapse"
|
|
144
148
|
_text={{
|
|
145
149
|
className: textClassName,
|
|
@@ -151,15 +155,12 @@ function TabBar(props) {
|
|
|
151
155
|
}
|
|
152
156
|
return button;
|
|
153
157
|
},
|
|
154
|
-
|
|
158
|
+
getTabProps = () => {
|
|
155
159
|
const
|
|
156
|
-
|
|
160
|
+
tabProps = {
|
|
157
161
|
className: `
|
|
158
162
|
${styles.TAB_BG}
|
|
159
163
|
${isCollapsed ? 'justify-center' : 'justify-start'}
|
|
160
|
-
${styles.TAB_BG_HOVER}
|
|
161
|
-
${styles.TAB_BG_ACTIVE}
|
|
162
|
-
${styles.TAB_BG_DISABLED}
|
|
163
164
|
`,
|
|
164
165
|
},
|
|
165
166
|
textProps = {
|
|
@@ -174,14 +175,11 @@ function TabBar(props) {
|
|
|
174
175
|
// size: 'md',
|
|
175
176
|
className: `
|
|
176
177
|
${styles.TAB_ICON_COLOR}
|
|
177
|
-
${styles.TAB_ICON_COLOR_HOVER}
|
|
178
|
-
${styles.TAB_ICON_COLOR_ACTIVE}
|
|
179
|
-
${styles.TAB_ICON_COLOR_DISABLED}
|
|
180
178
|
`,
|
|
181
179
|
};
|
|
182
180
|
switch(direction) {
|
|
183
181
|
case VERTICAL:
|
|
184
|
-
|
|
182
|
+
tabProps.className += `
|
|
185
183
|
rounded-l-lg
|
|
186
184
|
rounded-r-none
|
|
187
185
|
w-full
|
|
@@ -202,7 +200,7 @@ function TabBar(props) {
|
|
|
202
200
|
`;
|
|
203
201
|
break;
|
|
204
202
|
case HORIZONTAL:
|
|
205
|
-
|
|
203
|
+
tabProps.className += `
|
|
206
204
|
rounded-t
|
|
207
205
|
rounded-b-none
|
|
208
206
|
mr-1
|
|
@@ -217,16 +215,16 @@ function TabBar(props) {
|
|
|
217
215
|
default:
|
|
218
216
|
}
|
|
219
217
|
return {
|
|
220
|
-
|
|
218
|
+
tabProps,
|
|
221
219
|
textProps,
|
|
222
220
|
iconProps,
|
|
223
221
|
};
|
|
224
222
|
},
|
|
225
223
|
renderTabs = () => {
|
|
226
224
|
const {
|
|
227
|
-
|
|
228
|
-
className:
|
|
229
|
-
...
|
|
225
|
+
tabProps: {
|
|
226
|
+
className: tabPropsClassName,
|
|
227
|
+
...tabPropsToPass
|
|
230
228
|
},
|
|
231
229
|
textProps: {
|
|
232
230
|
className: textPropsClassName,
|
|
@@ -236,8 +234,8 @@ function TabBar(props) {
|
|
|
236
234
|
className: iconPropsClassName,
|
|
237
235
|
...iconPropsToPass
|
|
238
236
|
},
|
|
239
|
-
} =
|
|
240
|
-
|
|
237
|
+
} = getTabProps(),
|
|
238
|
+
tabComponents = [];
|
|
241
239
|
|
|
242
240
|
_.each(tabs, (tab, ix) => {
|
|
243
241
|
if (!tab.icon) {
|
|
@@ -245,7 +243,7 @@ function TabBar(props) {
|
|
|
245
243
|
}
|
|
246
244
|
const
|
|
247
245
|
isCurrentTab = ix === getCurrentTab(),
|
|
248
|
-
|
|
246
|
+
useIconTab = (isCollapsed || !tab.title),
|
|
249
247
|
tabIcon = tab._icon ? _.clone(tab._icon) : {};
|
|
250
248
|
if (tabIcon.as && _.isString(tabIcon.as)) {
|
|
251
249
|
const Type = getComponentFromType(tabIcon.as);
|
|
@@ -254,20 +252,13 @@ function TabBar(props) {
|
|
|
254
252
|
}
|
|
255
253
|
}
|
|
256
254
|
|
|
257
|
-
let
|
|
255
|
+
let tabClassName = tabPropsClassName,
|
|
258
256
|
textClassName = textPropsClassName,
|
|
259
257
|
iconClassName = iconPropsClassName;
|
|
260
258
|
|
|
261
|
-
if (isCurrentTab) {
|
|
262
|
-
buttonClassName += ' ' + styles.TAB_BG_CURRENT +
|
|
263
|
-
' ' + styles.TAB_BG_CURRENT_HOVER;
|
|
264
|
-
iconClassName += ' ' + styles.TAB_ICON_COLOR_CURRENT;
|
|
265
|
-
textClassName += ' ' + styles.TAB_COLOR_CURRENT;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
259
|
// overrides
|
|
269
260
|
if (tab._button?.className) {
|
|
270
|
-
|
|
261
|
+
tabClassName += ' ' + tab._button.className;
|
|
271
262
|
}
|
|
272
263
|
if (tab._text?.className) {
|
|
273
264
|
textClassName += ' ' + tab._text.className;
|
|
@@ -284,59 +275,28 @@ function TabBar(props) {
|
|
|
284
275
|
},
|
|
285
276
|
onPress = () => setCurrentTab(ix);
|
|
286
277
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (direction === VERTICAL) {
|
|
302
|
-
buttonClassName += ' w-[200px]';
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
let closeBtn = null;
|
|
306
|
-
if (onTabClose && !tab.disableCloseBox) {
|
|
307
|
-
closeBtn = <IconButton
|
|
308
|
-
{...testProps('tabCloseButton-' + ix)}
|
|
309
|
-
key={'tabCloseButton' + ix}
|
|
310
|
-
onPress={() => onTabClose(ix)}
|
|
311
|
-
icon={Xmark}
|
|
312
|
-
_icon={{
|
|
313
|
-
...iconProps,
|
|
314
|
-
className: iconClassName,
|
|
278
|
+
const WhichTabType = tabsAreButtons ? TabButton : Tab
|
|
279
|
+
tabComponents.push(<WhichTabType
|
|
280
|
+
{...testProps(tab.path)}
|
|
281
|
+
key={'tab' + ix}
|
|
282
|
+
onPress={onPress}
|
|
283
|
+
{...tabPropsToPass}
|
|
284
|
+
icon={tab.icon}
|
|
285
|
+
_icon={_icon}
|
|
286
|
+
className={tabClassName}
|
|
287
|
+
tooltip={tab.title}
|
|
288
|
+
text={tab.title}
|
|
289
|
+
_text={{
|
|
290
|
+
className: textClassName,
|
|
291
|
+
...textPropsToPass,
|
|
315
292
|
}}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
onPress={onPress}
|
|
324
|
-
{...buttonPropsToPass}
|
|
325
|
-
icon={tab.icon}
|
|
326
|
-
_icon={_icon}
|
|
327
|
-
rightIcon={closeBtn}
|
|
328
|
-
className={buttonClassName}
|
|
329
|
-
text={tab.title}
|
|
330
|
-
_text={{
|
|
331
|
-
className: textClassName,
|
|
332
|
-
...textPropsToPass,
|
|
333
|
-
}}
|
|
334
|
-
isDisabled={tab.isDisabled}
|
|
335
|
-
action="none"
|
|
336
|
-
variant="none"
|
|
337
|
-
/>;
|
|
338
|
-
}
|
|
339
|
-
buttons.push(button);
|
|
293
|
+
isDisabled={tab.isDisabled}
|
|
294
|
+
isCurrentTab={isCurrentTab}
|
|
295
|
+
useIconOnly={useIconTab}
|
|
296
|
+
direction={direction}
|
|
297
|
+
useCloseBtn={onTabClose && !tab.disableCloseBox}
|
|
298
|
+
onClose={() => onTabClose(ix)}
|
|
299
|
+
/>);
|
|
340
300
|
});
|
|
341
301
|
|
|
342
302
|
if (additionalButtons) {
|
|
@@ -346,7 +306,7 @@ function TabBar(props) {
|
|
|
346
306
|
}
|
|
347
307
|
|
|
348
308
|
const
|
|
349
|
-
|
|
309
|
+
useIconTab = (isCollapsed || !additionalButton.text),
|
|
350
310
|
additionalButtonIcon = _.clone(additionalButton._icon);
|
|
351
311
|
|
|
352
312
|
if (additionalButtonIcon.as && _.isString(additionalButtonIcon.as)) {
|
|
@@ -356,13 +316,13 @@ function TabBar(props) {
|
|
|
356
316
|
}
|
|
357
317
|
}
|
|
358
318
|
|
|
359
|
-
let
|
|
319
|
+
let tabClassName = tabPropsClassName,
|
|
360
320
|
textClassName = textPropsClassName,
|
|
361
321
|
iconClassName = iconPropsClassName;
|
|
362
322
|
|
|
363
323
|
// overrides
|
|
364
324
|
if (additionalButton._button?.className) {
|
|
365
|
-
|
|
325
|
+
tabClassName += ' ' + additionalButton._button.className;
|
|
366
326
|
}
|
|
367
327
|
if (additionalButton._text?.className) {
|
|
368
328
|
textClassName += ' ' + additionalButton._text.className;
|
|
@@ -375,10 +335,10 @@ function TabBar(props) {
|
|
|
375
335
|
// First button should have a gap before it
|
|
376
336
|
switch(direction) {
|
|
377
337
|
case VERTICAL:
|
|
378
|
-
|
|
338
|
+
tabClassName += ' mt-6';
|
|
379
339
|
break;
|
|
380
340
|
case HORIZONTAL:
|
|
381
|
-
|
|
341
|
+
tabClassName += ' ml-6';
|
|
382
342
|
break;
|
|
383
343
|
default:
|
|
384
344
|
}
|
|
@@ -393,27 +353,27 @@ function TabBar(props) {
|
|
|
393
353
|
onPress = additionalButton.onPress;
|
|
394
354
|
|
|
395
355
|
let button;
|
|
396
|
-
if (
|
|
356
|
+
if (useIconTab) {
|
|
397
357
|
button = <IconButton
|
|
398
358
|
{...testProps('additionalBtn' + ix)}
|
|
399
359
|
key={'additionalBtn' + ix}
|
|
400
360
|
onPress={onPress}
|
|
401
|
-
{...
|
|
361
|
+
{...tabPropsToPass}
|
|
402
362
|
_icon={_icon}
|
|
403
|
-
className={
|
|
363
|
+
className={tabClassName}
|
|
404
364
|
tooltip={additionalButton.text}
|
|
405
365
|
/>;
|
|
406
366
|
} else {
|
|
407
367
|
if (direction === VERTICAL) {
|
|
408
|
-
|
|
368
|
+
tabClassName += ' w-[200px]';
|
|
409
369
|
}
|
|
410
370
|
button = <Button
|
|
411
371
|
{...testProps('additionalBtn' + ix)}
|
|
412
372
|
key={'additionalBtn' + ix}
|
|
413
373
|
onPress={onPress}
|
|
414
|
-
{...
|
|
374
|
+
{...tabPropsToPass}
|
|
415
375
|
_icon={_icon}
|
|
416
|
-
className={
|
|
376
|
+
className={tabClassName}
|
|
417
377
|
text={additionalButton.text}
|
|
418
378
|
_text={{
|
|
419
379
|
className: textClassName,
|
|
@@ -423,11 +383,11 @@ function TabBar(props) {
|
|
|
423
383
|
variant="none"
|
|
424
384
|
/>;
|
|
425
385
|
}
|
|
426
|
-
|
|
386
|
+
tabComponents.push(button);
|
|
427
387
|
});
|
|
428
388
|
}
|
|
429
389
|
|
|
430
|
-
return
|
|
390
|
+
return tabComponents;
|
|
431
391
|
},
|
|
432
392
|
renderCurrentTabContent = () => {
|
|
433
393
|
if (content) {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HORIZONTAL,
|
|
3
|
+
VERTICAL,
|
|
4
|
+
} from '../../Constants/Directions.js';
|
|
5
|
+
import Button from '../Buttons/Button.js';
|
|
6
|
+
import IconButton from '../Buttons/IconButton.js';
|
|
7
|
+
import Xmark from '../Icons/Xmark.js';
|
|
8
|
+
import UiGlobals from '../../UiGlobals.js';
|
|
9
|
+
import _ from 'lodash';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export default function TabButton(props) {
|
|
13
|
+
let {
|
|
14
|
+
className,
|
|
15
|
+
direction,
|
|
16
|
+
isDisabled,
|
|
17
|
+
isCurrentTab,
|
|
18
|
+
text,
|
|
19
|
+
_text,
|
|
20
|
+
useIconOnly,
|
|
21
|
+
_icon,
|
|
22
|
+
useCloseBtn,
|
|
23
|
+
onClose,
|
|
24
|
+
...propsToPass
|
|
25
|
+
} = props,
|
|
26
|
+
styles = UiGlobals.styles;
|
|
27
|
+
|
|
28
|
+
className += ' ' + styles.TAB_BG_HOVER +
|
|
29
|
+
' ' + styles.TAB_BG_ACTIVE;
|
|
30
|
+
_icon.className += ' ' + styles.TAB_ICON_COLOR_HOVER +
|
|
31
|
+
' ' + styles.TAB_ICON_COLOR_ACTIVE;
|
|
32
|
+
|
|
33
|
+
if (isCurrentTab) {
|
|
34
|
+
className += ' ' + styles.TAB_BG_CURRENT +
|
|
35
|
+
' ' + styles.TAB_BG_CURRENT_HOVER;
|
|
36
|
+
_icon.className += ' ' + styles.TAB_ICON_COLOR_CURRENT;
|
|
37
|
+
_text.className += ' ' + styles.TAB_COLOR_CURRENT;
|
|
38
|
+
}
|
|
39
|
+
if (isDisabled) {
|
|
40
|
+
className += ' ' + styles.TAB_BG_DISABLED;
|
|
41
|
+
_icon.className += ' ' + styles.TAB_ICON_COLOR_DISABLED;
|
|
42
|
+
_text.className += ' ' + styles.TAB_COLOR_DISABLED;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let tab = null;
|
|
46
|
+
if (useIconOnly) {
|
|
47
|
+
tab = <IconButton
|
|
48
|
+
{...propsToPass}
|
|
49
|
+
{..._icon}
|
|
50
|
+
className={className}
|
|
51
|
+
/>;
|
|
52
|
+
|
|
53
|
+
} else {
|
|
54
|
+
if (direction === VERTICAL) {
|
|
55
|
+
className += ' w-[200px]';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let closeBtn = null;
|
|
59
|
+
if (useCloseBtn) {
|
|
60
|
+
closeBtn = <IconButton
|
|
61
|
+
{...testProps('tabCloseButton-' + ix)}
|
|
62
|
+
onPress={onClose}
|
|
63
|
+
icon={Xmark}
|
|
64
|
+
_icon={_icon}
|
|
65
|
+
tooltip="Close Tab"
|
|
66
|
+
className="p-0"
|
|
67
|
+
/>;
|
|
68
|
+
}
|
|
69
|
+
tab = <Button
|
|
70
|
+
{...propsToPass}
|
|
71
|
+
className={className}
|
|
72
|
+
text={text}
|
|
73
|
+
_text={_text}
|
|
74
|
+
_icon={_icon}
|
|
75
|
+
rightIcon={closeBtn}
|
|
76
|
+
action="none"
|
|
77
|
+
variant="none"
|
|
78
|
+
/>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return tab;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PROGRESS_COMPLETED = 'Completed';
|
package/src/Constants/Styles.js
CHANGED
|
@@ -103,12 +103,14 @@ const defaults = {
|
|
|
103
103
|
TAB_BG_ACTIVE: 'active:bg-grey-900/50',
|
|
104
104
|
TAB_BG_CURRENT: 'bg-grey-0',
|
|
105
105
|
TAB_BG_DISABLED: 'disabled:bg-grey-200',
|
|
106
|
+
TAB_BG_DISABLED_2: 'bg-grey-200',
|
|
106
107
|
TAB_BG_HOVER: 'hover:bg-grey-900/30',
|
|
107
108
|
TAB_BG_ACTIVE_HOVER: 'hover:bg-grey-200',
|
|
108
109
|
TAB_BG_CURRENT_HOVER: 'hover:bg-grey-900/30',
|
|
109
110
|
TAB_COLOR: 'text-' + BLACK,
|
|
110
111
|
TAB_COLOR_ACTIVE: 'active:text-primary-800',
|
|
111
|
-
TAB_COLOR_CURRENT: '
|
|
112
|
+
TAB_COLOR_CURRENT: 'text-primary-800',
|
|
113
|
+
TAB_COLOR_DISABLED: 'text-grey-400',
|
|
112
114
|
TAB_ICON_COLOR: 'text-' + BLACK,
|
|
113
115
|
TAB_ICON_COLOR_ACTIVE: 'text-' + BLACK,
|
|
114
116
|
TAB_ICON_COLOR_CURRENT: 'text-' + BLACK,
|