@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.4.57",
3
+ "version": "0.4.59",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -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 = { msg, data };
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
- if (!Repository || !action) {
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.post,
59
+ method = Repository.methods.edit,
44
60
  uri = Repository.getModel() + '/' + action,
45
- formValues = self.children.form.formGetValues(),
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
- alert(result.message);
51
- reset();
68
+ resetToInitialState();
52
69
  return;
53
70
  }
54
71
 
55
- let results = <CenterBox><Text>Success</Text></CenterBox>;
72
+ let results = <Text>Success</Text>;
56
73
  if (response.message) {
57
- const decodedMessage = JSON.parse(response.message);
58
- results = _.isArray(decodedMessage) ?
74
+ let message = response.message;
75
+ if (isJson(message)) {
76
+ message = JSON.parse(message);
77
+ }
78
+ results = _.isArray(message) ?
59
79
  <VStack>
60
- {decodedMessage?.map((line, ix)=> {
80
+ {message?.map((line, ix)=> {
61
81
  return <Text key={ix}>{line}</Text>;
62
82
  })}
63
- </VStack> :
64
- <Text>{decodedMessage}</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="Initiate"
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={() => reset()}
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
- [currentTabIx, setCurrentTab] = useState(0),
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
- reset = () => {
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: 'Initiate',
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: results,
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
- buttonProps: {
92
- className: buttonPropsClassName,
93
- ...buttonPropsToPass
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
- } = getButtonProps();
107
+ } = getTabProps();
104
108
 
105
- let buttonClassName = buttonPropsClassName,
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
- {...buttonPropsToPass}
126
+ {...tabPropsToPass}
123
127
  icon={icon}
124
128
  _icon={_icon}
125
- className={buttonClassName}
129
+ className={tabClassName}
126
130
  tooltip={isCollapsed ? 'Expand' : 'Collapse'}
127
131
  />;
128
132
  } else {
129
- buttonClassName += `
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
- {...buttonPropsToPass}
143
+ {...tabPropsToPass}
140
144
  icon={icon}
141
145
  _icon={_icon}
142
- className={buttonClassName}
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
- getButtonProps = () => {
158
+ getTabProps = () => {
155
159
  const
156
- buttonProps = {
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
- buttonProps.className += `
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
- buttonProps.className += `
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
- buttonProps,
218
+ tabProps,
221
219
  textProps,
222
220
  iconProps,
223
221
  };
224
222
  },
225
223
  renderTabs = () => {
226
224
  const {
227
- buttonProps: {
228
- className: buttonPropsClassName,
229
- ...buttonPropsToPass
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
- } = getButtonProps(),
240
- buttons = [];
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
- useIconButton = (isCollapsed || !tab.title),
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 buttonClassName = buttonPropsClassName,
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
- buttonClassName += ' ' + tab._button.className;
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
- let button;
288
- if (useIconButton) {
289
- button = <IconButton
290
- {...testProps(tab.path)}
291
- key={'tabIconBtn' + ix}
292
- onPress={onPress}
293
- {...buttonPropsToPass}
294
- icon={tab.icon}
295
- _icon={_icon}
296
- className={buttonClassName}
297
- tooltip={tab.title}
298
- isDisabled={tab.isDisabled}
299
- />;
300
- } else {
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
- tooltip="Close Tab"
317
- className="p-0"
318
- />;
319
- }
320
- button = <Button
321
- {...testProps(tab.path)}
322
- key={'tabBtn' + ix}
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
- useIconButton = (isCollapsed || !additionalButton.text),
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 buttonClassName = buttonPropsClassName,
319
+ let tabClassName = tabPropsClassName,
360
320
  textClassName = textPropsClassName,
361
321
  iconClassName = iconPropsClassName;
362
322
 
363
323
  // overrides
364
324
  if (additionalButton._button?.className) {
365
- buttonClassName += ' ' + additionalButton._button.className;
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
- buttonClassName += ' mt-6';
338
+ tabClassName += ' mt-6';
379
339
  break;
380
340
  case HORIZONTAL:
381
- buttonClassName += ' ml-6';
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 (useIconButton) {
356
+ if (useIconTab) {
397
357
  button = <IconButton
398
358
  {...testProps('additionalBtn' + ix)}
399
359
  key={'additionalBtn' + ix}
400
360
  onPress={onPress}
401
- {...buttonPropsToPass}
361
+ {...tabPropsToPass}
402
362
  _icon={_icon}
403
- className={buttonClassName}
363
+ className={tabClassName}
404
364
  tooltip={additionalButton.text}
405
365
  />;
406
366
  } else {
407
367
  if (direction === VERTICAL) {
408
- buttonClassName += ' w-[200px]';
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
- {...buttonPropsToPass}
374
+ {...tabPropsToPass}
415
375
  _icon={_icon}
416
- className={buttonClassName}
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
- buttons.push(button);
386
+ tabComponents.push(button);
427
387
  });
428
388
  }
429
389
 
430
- return buttons;
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';
@@ -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: 'active:text-primary-800',
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,