@onehat/ui 0.2.78 → 0.2.80

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.
@@ -0,0 +1,389 @@
1
+ import React, { useState, useEffect, useId, } from 'react';
2
+ import {
3
+ Button,
4
+ Column,
5
+ Icon,
6
+ Row,
7
+ Text,
8
+ } from 'native-base';
9
+ import {
10
+ HORIZONTAL,
11
+ VERTICAL,
12
+ } from '../../Constants/Directions.js';
13
+ import UiGlobals from '../../UiGlobals.js';
14
+ import IconButton from '../Buttons/IconButton.js';
15
+ import Minimize from '../Icons/Minimize.js';
16
+ import Maximize from '../Icons/Maximize.js';
17
+ import getSaved from '../../Functions/getSaved.js';
18
+ import setSaved from '../../Functions/setSaved.js';
19
+ import _ from 'lodash';
20
+
21
+
22
+ export default function TabBar(props) {
23
+ const {
24
+ tabs = [], // { _icon, title, content, path, items, }
25
+ content, // e.g. Expo Router slot
26
+ direction = HORIZONTAL,
27
+ tabWidth = 150, // used on VERTICAL mode only
28
+ tabHeight = '44px', // used on HORIZONTAL mode only
29
+ additionalButtons,
30
+ initialTabIx = 0,
31
+ currentTabIx,
32
+ startsCollapsed = true,
33
+ onChangeCurrentTab,
34
+ onChangeIsCollapsed,
35
+ saveCurrentTab = true,
36
+ ...propsToPass
37
+ } = props,
38
+ styles = UiGlobals.styles,
39
+ id = useId(),
40
+ useLocal = _.isNil(currentTabIx),
41
+ [isReady, setIsReady] = useState(false),
42
+ [currentTabIxLocal, setCurrentTabIxLocal] = useState(initialTabIx),
43
+ [isCollapsed, setIsCollapsedRaw] = useState(startsCollapsed),
44
+ setIsCollapsed = (isCollapsed) => {
45
+ setIsCollapsedRaw(isCollapsed);
46
+ if (onChangeIsCollapsed) {
47
+ onChangeIsCollapsed(isCollapsed);
48
+ }
49
+ setSaved(id + '-isCollapsed', isCollapsed);
50
+ },
51
+ getCurrentTab = () => {
52
+ if (useLocal) {
53
+ return currentTabIxLocal;
54
+ }
55
+ return currentTabIx;
56
+ },
57
+ setCurrentTab = (ix) => {
58
+ if (useLocal) {
59
+ setCurrentTabIxLocal(ix);
60
+ }
61
+ if (onChangeCurrentTab) {
62
+ onChangeCurrentTab(ix);
63
+ }
64
+ if (saveCurrentTab) {
65
+ setSaved(id + '-currentTabIx', ix);
66
+ }
67
+ },
68
+ onToggleCollapse = () => {
69
+ setIsCollapsed(!isCollapsed);
70
+ },
71
+ renderToggleButton = () => {
72
+ const
73
+ {
74
+ buttonProps,
75
+ textProps,
76
+ } = getButtonProps();
77
+
78
+ let button;
79
+ if (isCollapsed) {
80
+ button = <IconButton
81
+ key="toggleBtn"
82
+ onPress={onToggleCollapse}
83
+ {...buttonProps}
84
+ _icon={{
85
+ as: Maximize,
86
+ color: styles.TAB_ICON_COLOR,
87
+ }}
88
+ _hover={{
89
+ bg: styles.TAB_HOVER_BG,
90
+ }}
91
+ bg={styles.TAB_BG}
92
+ tooltip={isCollapsed ? 'Expand' : 'Collapse'}
93
+ />;
94
+ } else {
95
+ button = <Button
96
+ key="toggleBtn"
97
+ onPress={onToggleCollapse}
98
+ leftIcon={<Icon
99
+ as={Minimize}
100
+ color={styles.TAB_ICON_COLOR}
101
+ />}
102
+ _hover={{
103
+ bg: styles.TAB_HOVER_BG,
104
+ }}
105
+ bg={styles.TAB_BG}
106
+ {...buttonProps}
107
+ // {...thisButtonProps}
108
+ >
109
+ <Text
110
+ color={styles.TAB_COLOR}
111
+ fontSize={styles.TAB_FONTSIZE}
112
+ numberOfLines={1}
113
+ ellipsizeMode="head"
114
+ {...textProps}
115
+ >Collapse</Text>
116
+ </Button>;
117
+ }
118
+ return button;
119
+ },
120
+ getButtonProps = () => {
121
+ const
122
+ iconProps = {
123
+ size: 'md',
124
+ },
125
+ textProps = {},
126
+ buttonProps = {
127
+ bg: styles.TAB_BG,
128
+ color: styles.TAB_COLOR,
129
+ fontSize: styles.TAB_FONTSIZE,
130
+ textAlign: 'left',
131
+ justifyContent: isCollapsed ? 'center' : 'flex-start',
132
+ };
133
+ switch(direction) {
134
+ case VERTICAL:
135
+ buttonProps.borderLeftRadius = 4;
136
+ buttonProps.borderRightRadius = 0;
137
+ buttonProps.w = '100%';
138
+ buttonProps.mb = 1;
139
+ textProps.w = '100%';
140
+ textProps.py = 0;
141
+ textProps.pl = 3;
142
+ textProps.mb = 1;
143
+ break;
144
+ case HORIZONTAL:
145
+ buttonProps.borderTopRadius = 4;
146
+ buttonProps.borderBottomRadius = 0;
147
+ textProps.borderTopRadius = 4;
148
+ buttonProps.mr = 1;
149
+ buttonProps.py = 1;
150
+ textProps.mr = 1;
151
+ textProps.px = 1;
152
+ textProps.py = 1;
153
+ break;
154
+ default:
155
+ }
156
+ return {
157
+ buttonProps,
158
+ textProps,
159
+ iconProps,
160
+ };
161
+ },
162
+ renderTabs = () => {
163
+ const
164
+ {
165
+ buttonProps,
166
+ textProps,
167
+ iconProps,
168
+ } = getButtonProps(),
169
+ buttons = [];
170
+
171
+ _.each(tabs, (tab, ix) => {
172
+ if (!tab._icon) {
173
+ throw new Error('tab._icon required!');
174
+ }
175
+ let button;
176
+ const
177
+ isCurrentTab = ix === getCurrentTab(),
178
+ thisButtonProps = {};
179
+ if (isCollapsed) {
180
+ button = <IconButton
181
+ key={'tab' + ix}
182
+ onPress={() => setCurrentTab(ix)}
183
+ {...buttonProps}
184
+ // {...thisButtonProps}
185
+ _icon={{
186
+ color: isCurrentTab ? styles.TAB_ACTIVE_ICON_COLOR : styles.TAB_ICON_COLOR,
187
+ ...iconProps,
188
+ ...tab._icon,
189
+ }}
190
+ _hover={{
191
+ bg: isCurrentTab? styles.TAB_ACTIVE_HOVER_BG : styles.TAB_HOVER_BG,
192
+ }}
193
+ bg={isCurrentTab ? styles.TAB_ACTIVE_BG : styles.TAB_BG}
194
+ tooltip={tab.title}
195
+ />;
196
+ } else {
197
+ button = <Button
198
+ key={'tab' + ix}
199
+ onPress={() => setCurrentTab(ix)}
200
+ leftIcon={<Icon
201
+ color={isCurrentTab ? styles.TAB_ACTIVE_ICON_COLOR : styles.TAB_ICON_COLOR}
202
+ {...iconProps}
203
+ {...tab._icon}
204
+ />}
205
+ {...buttonProps}
206
+ {...thisButtonProps}
207
+ _hover={{
208
+ bg: isCurrentTab? styles.TAB_ACTIVE_HOVER_BG : styles.TAB_HOVER_BG,
209
+ }}
210
+ bg={isCurrentTab ? styles.TAB_ACTIVE_BG : styles.TAB_BG}
211
+ >
212
+ <Text
213
+ color={isCurrentTab ? styles.TAB_ACTIVE_COLOR : styles.TAB_COLOR}
214
+ fontSize={styles.TAB_FONTSIZE}
215
+ numberOfLines={1}
216
+ ellipsizeMode="head"
217
+ {...textProps}
218
+ >{tab.title}</Text>
219
+ </Button>;
220
+ }
221
+ buttons.push(button);
222
+ });
223
+
224
+ if (additionalButtons) {
225
+ _.each(additionalButtons, (additionalButton, ix) => {
226
+ if (!additionalButton._icon) {
227
+ throw new Error('additionalButton._icon required!');
228
+ }
229
+ let button;
230
+ const thisButtonProps = {};
231
+ if (!ix) {
232
+ // First button should have gap before it
233
+ switch(direction) {
234
+ case VERTICAL:
235
+ thisButtonProps.mt = 6;
236
+ break;
237
+ case HORIZONTAL:
238
+ thisButtonProps.ml = 6;
239
+ break;
240
+ default:
241
+ }
242
+ }
243
+ if (isCollapsed) {
244
+ button = <IconButton
245
+ key={'additionalBtn' + ix}
246
+ onPress={additionalButton.onPress}
247
+ {...buttonProps}
248
+ {...thisButtonProps}
249
+ _icon={{
250
+ ...additionalButton._icon,
251
+ color: styles.TAB_ICON_COLOR,
252
+ }}
253
+ _hover={{
254
+ bg: styles.TAB_HOVER_BG,
255
+ }}
256
+ bg={styles.TAB_BG}
257
+ tooltip={additionalButton.text}
258
+ />;
259
+ } else {
260
+ button = <Button
261
+ key={'additionalBtn' + ix}
262
+ onPress={additionalButton.onPress}
263
+ leftIcon={<Icon
264
+ color={styles.TAB_ICON_COLOR}
265
+ {...additionalButton._icon}
266
+ />}
267
+ _hover={{
268
+ bg: styles.TAB_HOVER_BG,
269
+ }}
270
+ bg={styles.TAB_BG}
271
+ {...buttonProps}
272
+ {...thisButtonProps}
273
+ >
274
+ <Text
275
+ color={styles.TAB_COLOR}
276
+ fontSize={styles.TAB_FONTSIZE}
277
+ numberOfLines={1}
278
+ ellipsizeMode="head"
279
+ {...textProps}
280
+ >{additionalButton.text}</Text>
281
+ </Button>;
282
+ }
283
+ buttons.push(button);
284
+ });
285
+ }
286
+
287
+ return buttons;
288
+ },
289
+ renderCurrentTabContent = () => {
290
+ if (content) {
291
+ return content;
292
+ }
293
+
294
+ const currentTabIx = getCurrentTab();
295
+ if (tabs[currentTabIx].content) {
296
+ return tabs[currentTabIx].content;
297
+ }
298
+ return _.map(tabs[currentTabIx].items, (item, ix) => {
299
+ return React.cloneElement(item, { key: ix });
300
+ });
301
+ };
302
+
303
+ useEffect(() => {
304
+ // Restore saved settings
305
+ (async () => {
306
+ let key, val;
307
+ key = id + '-isCollapsed';
308
+ val = await getSaved(key);
309
+ if (!_.isNil(val)) {
310
+ setIsCollapsed(val);
311
+ }
312
+
313
+ if (saveCurrentTab) {
314
+ key = id + '-currentTabIx';
315
+ val = await getSaved(key);
316
+ if (!_.isNil(val)) {
317
+ setCurrentTab(val);
318
+ }
319
+ }
320
+
321
+ if (!isReady) {
322
+ setIsReady(true);
323
+ }
324
+ })();
325
+ }, []);
326
+
327
+ if (!isReady) {
328
+ return null;
329
+ }
330
+
331
+ const
332
+ renderedTabs = renderTabs(),
333
+ renderedCurrentTabContent = renderCurrentTabContent(),
334
+ renderedToggleButton = renderToggleButton();
335
+
336
+
337
+ if (direction === VERTICAL) {
338
+ return <Row flex={1} w="100%" {...propsToPass}>
339
+ <Column
340
+ alignItems="center"
341
+ justifyContent="flex-start"
342
+ py={2}
343
+ pl={isCollapsed ? 1 : 4}
344
+ bg={styles.TAB_BAR_BG}
345
+ w={isCollapsed ? '50px' : tabWidth}
346
+ >
347
+ {renderedTabs}
348
+ <Column flex={1} w="100%" justifyContent="flex-end">
349
+ {renderedToggleButton}
350
+ </Column>
351
+ </Column>
352
+ {renderedCurrentTabContent &&
353
+ <Column
354
+ alignItems="center"
355
+ justifyContent="flex-start"
356
+ flex={1}
357
+ >
358
+ {renderedCurrentTabContent}
359
+ </Column>}
360
+ </Row>;
361
+ }
362
+
363
+ // HORIZONTAL
364
+ return <Column flex={1} w="100%" {...propsToPass}>
365
+ <Row
366
+ alignItems="center"
367
+ justifyContent="flex-start"
368
+ p={2}
369
+ pb={0}
370
+ bg={styles.TAB_BAR_BG}
371
+ h={isCollapsed ? '30px' : tabHeight}
372
+ >
373
+ {renderedTabs}
374
+ <Row flex={1} h="100%" justifyContent="flex-end">
375
+ <Row h="100%">
376
+ {renderedToggleButton}
377
+ </Row>
378
+ </Row>
379
+ </Row>
380
+ {renderedCurrentTabContent &&
381
+ <Column
382
+ alignItems="center"
383
+ justifyContent="flex-start"
384
+ flex={1}
385
+ >
386
+ {renderedCurrentTabContent}
387
+ </Column>}
388
+ </Column>;
389
+ }
@@ -13,7 +13,7 @@ import BooleanCombo from './Form/Field/Combo/BooleanCombo.js';
13
13
  import CheckboxGroup from './Form/Field/CheckboxGroup/CheckboxGroup.js';
14
14
  import Color from './Form/Field/Color.js';
15
15
  import Combo from './Form/Field/Combo/Combo.js';
16
- // import ComboEditor from '../Components/Form/Field/Combo/ComboEditor.js';
16
+ // import { ComboEditor } from '../Components/Form/Field/Combo/Combo.js';
17
17
  import Container from './Container/Container.js';
18
18
  import DataMgt from './Screens/DataMgt.js';
19
19
  import Date from './Form/Field/Date.js';
@@ -1,22 +0,0 @@
1
- import withAlert from '../../../Hoc/withAlert.js';
2
- import withData from '../../../Hoc/withData.js';
3
- import withPresetButtons from '../../../Hoc/withPresetButtons.js';
4
- import withSelection from '../../../Hoc/withSelection.js';
5
- import withValue from '../../../Hoc/withValue.js';
6
- import withWindowedEditor from '../../../Hoc/withWindowedEditor.js';
7
- import { Combo } from './Combo.js';
8
-
9
- export default
10
- // withAlert(
11
- withData(
12
- withValue(
13
- withSelection(
14
- withWindowedEditor(
15
- withPresetButtons(
16
- Combo
17
- )
18
- )
19
- )
20
- )
21
- );
22
- //);
@@ -1,39 +0,0 @@
1
- import { useEffect, useState, } from 'react';
2
- import Panel from './Panel.js';
3
- import { InlineGridEditor, } from '../Grid/Grid.js';
4
- import _ from 'lodash';
5
-
6
- export function GridPanel(props) {
7
- const {
8
- disableTitleChange = false,
9
- selectorSelected,
10
- } = props,
11
- originalTitle = props.title,
12
- [isReady, setIsReady] = useState(disableTitleChange),
13
- [title, setTitle] = useState(originalTitle);
14
-
15
- useEffect(() => {
16
- if (!disableTitleChange && originalTitle) {
17
- let newTitle = originalTitle;
18
- if (selectorSelected?.[0]?.displayValue) {
19
- newTitle = originalTitle + ' for ' + selectorSelected[0].displayValue;
20
- }
21
- if (newTitle !== title) {
22
- setTitle(newTitle);
23
- }
24
- }
25
- if (!isReady) {
26
- setIsReady(true);
27
- }
28
- }, [selectorSelected, disableTitleChange, originalTitle]);
29
-
30
- if (!isReady) {
31
- return null;
32
- }
33
-
34
- return <Panel {...props} title={title}>
35
- <InlineGridEditor {...props} />
36
- </Panel>;
37
- }
38
-
39
- export default GridPanel;
@@ -1,39 +0,0 @@
1
- import { useEffect, useState, } from 'react';
2
- import Panel from './Panel.js';
3
- import { SideGridEditor, } from '../Grid/Grid.js';
4
- import _ from 'lodash';
5
-
6
- export function GridPanel(props) {
7
- const {
8
- disableTitleChange = false,
9
- selectorSelected,
10
- } = props,
11
- originalTitle = props.title,
12
- [isReady, setIsReady] = useState(disableTitleChange),
13
- [title, setTitle] = useState(originalTitle);
14
-
15
- useEffect(() => {
16
- if (!disableTitleChange && originalTitle) {
17
- let newTitle = originalTitle;
18
- if (selectorSelected?.[0]?.displayValue) {
19
- newTitle = originalTitle + ' for ' + selectorSelected[0].displayValue;
20
- }
21
- if (newTitle !== title) {
22
- setTitle(newTitle);
23
- }
24
- }
25
- if (!isReady) {
26
- setIsReady(true);
27
- }
28
- }, [selectorSelected, disableTitleChange, originalTitle]);
29
-
30
- if (!isReady) {
31
- return null;
32
- }
33
-
34
- return <Panel {...props} title={title}>
35
- <SideGridEditor {...props} />
36
- </Panel>;
37
- }
38
-
39
- export default GridPanel;
@@ -1,39 +0,0 @@
1
- import { useEffect, useState, } from 'react';
2
- import Panel from './Panel.js';
3
- import { SideTreeEditor, } from '../Tree/Tree.js';
4
- import _ from 'lodash';
5
-
6
- export function TreePanel(props) {
7
- const {
8
- disableTitleChange = false,
9
- selectorSelected,
10
- } = props,
11
- originalTitle = props.title,
12
- [isReady, setIsReady] = useState(disableTitleChange),
13
- [title, setTitle] = useState(originalTitle);
14
-
15
- useEffect(() => {
16
- if (!disableTitleChange && originalTitle) {
17
- let newTitle = originalTitle;
18
- if (selectorSelected?.[0]?.displayValue) {
19
- newTitle = originalTitle + ' for ' + selectorSelected[0].displayValue;
20
- }
21
- if (newTitle !== title) {
22
- setTitle(newTitle);
23
- }
24
- }
25
- if (!isReady) {
26
- setIsReady(true);
27
- }
28
- }, [selectorSelected, disableTitleChange, originalTitle]);
29
-
30
- if (!isReady) {
31
- return null;
32
- }
33
-
34
- return <Panel {...props} title={title}>
35
- <SideTreeEditor {...props} />
36
- </Panel>;
37
- }
38
-
39
- export default TreePanel;
@@ -1,39 +0,0 @@
1
- import { useEffect, useState, } from 'react';
2
- import Panel from './Panel.js';
3
- import { WindowedGridEditor, } from '../Grid/Grid.js';
4
- import _ from 'lodash';
5
-
6
- export function GridPanel(props) {
7
- const {
8
- disableTitleChange = false,
9
- selectorSelected,
10
- } = props,
11
- originalTitle = props.title,
12
- [isReady, setIsReady] = useState(disableTitleChange),
13
- [title, setTitle] = useState(originalTitle);
14
-
15
- useEffect(() => {
16
- if (!disableTitleChange && originalTitle) {
17
- let newTitle = originalTitle;
18
- if (selectorSelected?.[0]?.displayValue) {
19
- newTitle = originalTitle + ' for ' + selectorSelected[0].displayValue;
20
- }
21
- if (newTitle !== title) {
22
- setTitle(newTitle);
23
- }
24
- }
25
- if (!isReady) {
26
- setIsReady(true);
27
- }
28
- }, [selectorSelected, disableTitleChange, originalTitle]);
29
-
30
- if (!isReady) {
31
- return null;
32
- }
33
-
34
- return <Panel {...props} title={title}>
35
- <WindowedGridEditor {...props} />
36
- </Panel>;
37
- }
38
-
39
- export default GridPanel;
@@ -1,39 +0,0 @@
1
- import { useEffect, useState, } from 'react';
2
- import Panel from './Panel.js';
3
- import { WindowedTreeEditor, } from '../Tree/Tree.js';
4
- import _ from 'lodash';
5
-
6
- export function TreePanel(props) {
7
- const {
8
- disableTitleChange = false,
9
- selectorSelected,
10
- } = props,
11
- originalTitle = props.title,
12
- [isReady, setIsReady] = useState(disableTitleChange),
13
- [title, setTitle] = useState(originalTitle);
14
-
15
- useEffect(() => {
16
- if (!disableTitleChange && originalTitle) {
17
- let newTitle = originalTitle;
18
- if (selectorSelected?.[0]?.displayValue) {
19
- newTitle = originalTitle + ' for ' + selectorSelected[0].displayValue;
20
- }
21
- if (newTitle !== title) {
22
- setTitle(newTitle);
23
- }
24
- }
25
- if (!isReady) {
26
- setIsReady(true);
27
- }
28
- }, [selectorSelected, disableTitleChange, originalTitle]);
29
-
30
- if (!isReady) {
31
- return null;
32
- }
33
-
34
- return <Panel {...props} title={title}>
35
- <WindowedTreeEditor {...props} />
36
- </Panel>;
37
- }
38
-
39
- export default TreePanel;