@onehat/ui 0.2.80 → 0.2.84
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 +5 -3
- package/src/Components/Editor/Editor.js +6 -2
- package/src/Components/Editor/Viewer.js +4 -3
- package/src/Components/Form/Form.js +2 -2
- package/src/Components/Grid/Grid.js +2 -0
- package/src/Components/Hoc/withEditor.js +12 -0
- package/src/Components/Hoc/withSideEditor.js +8 -3
- package/src/Components/Icons/FullWidth.js +28 -0
- package/src/Components/Icons/SideBySide.js +23 -0
- package/src/Components/Panel/GridPanel.js +5 -1
- package/src/Components/Screens/Manager.js +94 -0
- package/src/Components/Tab/TabBar.js +5 -5
- package/src/Constants/Styles.js +2 -0
- package/src/Hooks/useRedux.js +40 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onehat/ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.84",
|
|
4
4
|
"description": "Base UI for OneHat apps",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -25,12 +25,14 @@
|
|
|
25
25
|
},
|
|
26
26
|
"license": "UNLICENSED",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@onehat/data": "^1.18.
|
|
29
|
-
"@hookform/resolvers": "^3.
|
|
28
|
+
"@onehat/data": "^1.18.12",
|
|
29
|
+
"@hookform/resolvers": "^3.3.1",
|
|
30
30
|
"@k-renwick/colour-mixer": "^1.2.1",
|
|
31
|
+
"@reduxjs/toolkit": "^1.9.5",
|
|
31
32
|
"js-cookie": "^3.0.5",
|
|
32
33
|
"native-base": "^3.4.28",
|
|
33
34
|
"react-hook-form": "^7.45.0",
|
|
35
|
+
"react-redux": "^8.1.2",
|
|
34
36
|
"yup": "^1.2.0"
|
|
35
37
|
},
|
|
36
38
|
"peerDependencies": {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
} from 'native-base';
|
|
1
4
|
import {
|
|
2
5
|
EDITOR_MODE__VIEW,
|
|
3
6
|
} from '../../Constants/Editor.js';
|
|
@@ -25,10 +28,11 @@ export default function Editor(props) {
|
|
|
25
28
|
} = props;
|
|
26
29
|
|
|
27
30
|
if (_.isEmpty(selection)) {
|
|
28
|
-
return null;
|
|
31
|
+
return null; // hide the editor when no selection
|
|
32
|
+
return <Box {...props} bg="#ddd" />;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
if (Repository
|
|
35
|
+
if (Repository?.isRemotePhantomMode && selection.length === 1 && editorMode === EDITOR_MODE__VIEW) {
|
|
32
36
|
return <Viewer
|
|
33
37
|
{...props}
|
|
34
38
|
record={selection[0]}
|
|
@@ -31,6 +31,7 @@ export default function Viewer(props) {
|
|
|
31
31
|
onDelete,
|
|
32
32
|
} = props,
|
|
33
33
|
styles = UiGlobals.styles,
|
|
34
|
+
flex = props.flex || 1,
|
|
34
35
|
buildAncillary = () => {
|
|
35
36
|
let components = [];
|
|
36
37
|
if (ancillaryItems.length) {
|
|
@@ -55,14 +56,14 @@ export default function Viewer(props) {
|
|
|
55
56
|
fontWeight="bold"
|
|
56
57
|
>{title}</Text>;
|
|
57
58
|
}
|
|
58
|
-
return <Column key={'ancillary-' + ix}
|
|
59
|
+
return <Column key={'ancillary-' + ix} my={5}>{title}{element}</Column>;
|
|
59
60
|
});
|
|
60
61
|
}
|
|
61
62
|
return components;
|
|
62
63
|
};
|
|
63
64
|
|
|
64
|
-
return <Column flex={
|
|
65
|
-
<ScrollView
|
|
65
|
+
return <Column flex={flex} {...props}>
|
|
66
|
+
<ScrollView width="100%" _web={{ height: 1 }}>
|
|
66
67
|
<Column m={2}>
|
|
67
68
|
{onEditMode && <Row mb={4} justifyContent="flex-end">
|
|
68
69
|
<Button
|
|
@@ -427,7 +427,7 @@ function Form(props) {
|
|
|
427
427
|
fontWeight="bold"
|
|
428
428
|
>{title}</Text>;
|
|
429
429
|
}
|
|
430
|
-
return <Column key={'ancillary-' + ix}
|
|
430
|
+
return <Column key={'ancillary-' + ix} mx={2} my={5}>{title}{element}</Column>;
|
|
431
431
|
});
|
|
432
432
|
}
|
|
433
433
|
return components;
|
|
@@ -499,7 +499,7 @@ function Form(props) {
|
|
|
499
499
|
// for all other editor types
|
|
500
500
|
formComponents = buildFromItems();
|
|
501
501
|
const formAncillaryComponents = buildAncillary();
|
|
502
|
-
editor = <ScrollView
|
|
502
|
+
editor = <ScrollView _web={{ height: 1 }} width="100%" pb={1}>
|
|
503
503
|
<Row>{formComponents}</Row>
|
|
504
504
|
<Column pt={4}>{formAncillaryComponents}</Column>
|
|
505
505
|
</ScrollView>;
|
|
@@ -52,6 +52,17 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
52
52
|
[isEditorShown, setIsEditorShown] = useState(false),
|
|
53
53
|
[isEditorViewOnly, setIsEditorViewOnly] = useState(false),
|
|
54
54
|
[lastSelection, setLastSelection] = useState(),
|
|
55
|
+
setSelectionDecorated = (newSelection) => {
|
|
56
|
+
function doIt() {
|
|
57
|
+
setSelection(newSelection);
|
|
58
|
+
}
|
|
59
|
+
const formState = editorStateRef.current;
|
|
60
|
+
if (!_.isEmpty(formState?.dirtyFields) && newSelection !== selection && editorMode === EDITOR_MODE__EDIT) {
|
|
61
|
+
confirm('This record has unsaved changes. Are you sure you want to cancel editing? Changes will be lost.', doIt);
|
|
62
|
+
} else {
|
|
63
|
+
doIt();
|
|
64
|
+
}
|
|
65
|
+
},
|
|
55
66
|
getListeners = () => {
|
|
56
67
|
return listeners.current;
|
|
57
68
|
},
|
|
@@ -342,6 +353,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
342
353
|
disableDelete={disableDelete}
|
|
343
354
|
disableDuplicate={disableDuplicate}
|
|
344
355
|
disableView ={disableView}
|
|
356
|
+
setSelection={setSelectionDecorated}
|
|
345
357
|
/>;
|
|
346
358
|
};
|
|
347
359
|
}
|
|
@@ -19,13 +19,18 @@ export default function withSideEditor(WrappedComponent, isTree = false) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
return <Container
|
|
22
|
-
center={<WrappedComponent
|
|
22
|
+
center={<WrappedComponent
|
|
23
|
+
isTree={isTree}
|
|
24
|
+
{...props}
|
|
25
|
+
/>}
|
|
23
26
|
east={<Editor
|
|
27
|
+
{...props}
|
|
24
28
|
editorType={EDITOR_TYPE__SIDE}
|
|
25
29
|
flex={sideFlex}
|
|
26
|
-
{
|
|
30
|
+
borderLeftWidth={1}
|
|
31
|
+
borderLeftColor="#ccc"
|
|
27
32
|
{...editorProps}
|
|
28
33
|
/>}
|
|
29
34
|
/>;
|
|
30
|
-
}
|
|
35
|
+
});
|
|
31
36
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import Svg, { Defs, G, Path } from "react-native-svg"
|
|
3
|
+
import { Icon } from 'native-base';
|
|
4
|
+
|
|
5
|
+
function SvgComponent(props) {
|
|
6
|
+
return (
|
|
7
|
+
<Icon
|
|
8
|
+
id="Layer_2"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
viewBox="0 0 237.17 235.06"
|
|
11
|
+
{...props}
|
|
12
|
+
>
|
|
13
|
+
<Defs></Defs>
|
|
14
|
+
<G id="Layer_1-2">
|
|
15
|
+
<Path
|
|
16
|
+
className="cls-1"
|
|
17
|
+
d="M237.17 235.06H0V0h237.17v235.06zM13.46 221.59H223.7V13.47H13.47V221.6z"
|
|
18
|
+
/>
|
|
19
|
+
<Path
|
|
20
|
+
className="cls-1"
|
|
21
|
+
d="M200.77 192.33H36.4V42.74h164.36v149.59zm-150.9-13.47H187.3V56.2H49.87v122.66z"
|
|
22
|
+
/>
|
|
23
|
+
</G>
|
|
24
|
+
</Icon>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default SvgComponent
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import Svg, { G, Path } from "react-native-svg"
|
|
3
|
+
import { Icon } from 'native-base';
|
|
4
|
+
|
|
5
|
+
function SvgComponent(props) {
|
|
6
|
+
return (
|
|
7
|
+
<Icon
|
|
8
|
+
id="Layer_2"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
viewBox="0 0 237.17 235.06"
|
|
11
|
+
{...props}
|
|
12
|
+
>
|
|
13
|
+
<G id="Layer_1-2">
|
|
14
|
+
<Path
|
|
15
|
+
d="M85.22 235.06H0V0h85.22v235.06zm-71.75-13.47h58.29V13.47H13.47V221.6zM237.17 235.06H109.74V0h127.43v235.06zm-113.96-13.47h100.5V13.47h-100.5V221.6z"
|
|
16
|
+
strokeWidth={0}
|
|
17
|
+
/>
|
|
18
|
+
</G>
|
|
19
|
+
</Icon>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default SvgComponent
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React, { useState, useEffect, } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Column,
|
|
4
|
+
Row,
|
|
5
|
+
Text,
|
|
6
|
+
} from 'native-base';
|
|
7
|
+
import IconButton from '../Buttons/IconButton';
|
|
8
|
+
import FullWidth from '../Icons/FullWidth';
|
|
9
|
+
import SideBySide from '../Icons/SideBySide';
|
|
10
|
+
import getSaved from '../../Functions/getSaved.js';
|
|
11
|
+
import setSaved from '../../Functions/setSaved.js';
|
|
12
|
+
import _ from 'lodash';
|
|
13
|
+
|
|
14
|
+
const
|
|
15
|
+
MODE_FULL = 'MODE_FULL',
|
|
16
|
+
MODE_SIDE = 'MODE_SIDE';
|
|
17
|
+
|
|
18
|
+
export default function ManagerScreen(props) {
|
|
19
|
+
const {
|
|
20
|
+
title,
|
|
21
|
+
sideModeComponent,
|
|
22
|
+
fullModeComponent,
|
|
23
|
+
id,
|
|
24
|
+
} = props,
|
|
25
|
+
[isReady, setIsReady] = useState(false),
|
|
26
|
+
[mode, setModeRaw] = useState(MODE_FULL),
|
|
27
|
+
setMode = (newMode) => {
|
|
28
|
+
if (newMode === mode) {
|
|
29
|
+
return; // no change
|
|
30
|
+
}
|
|
31
|
+
setModeRaw(newMode);
|
|
32
|
+
setSaved(id + '-mode', newMode);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
// Restore saved settings
|
|
37
|
+
(async () => {
|
|
38
|
+
|
|
39
|
+
let key, val;
|
|
40
|
+
key = id + '-mode';
|
|
41
|
+
val = await getSaved(key);
|
|
42
|
+
if (!_.isNil(val)) {
|
|
43
|
+
setMode(val);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!isReady) {
|
|
47
|
+
setIsReady(true);
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
if (!isReady) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let whichComponent;
|
|
57
|
+
if (mode === MODE_FULL) {
|
|
58
|
+
whichComponent = fullModeComponent;
|
|
59
|
+
} else if (mode === MODE_SIDE) {
|
|
60
|
+
whichComponent = sideModeComponent;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return <Column flex={1} w="100%">
|
|
64
|
+
<Row
|
|
65
|
+
h="80px"
|
|
66
|
+
py={2}
|
|
67
|
+
borderBottomWidth={2}
|
|
68
|
+
borderBottomColor="#ccc"
|
|
69
|
+
>
|
|
70
|
+
<Text p={4} fontSize="26" fontWeight={700}>{title}</Text>
|
|
71
|
+
<IconButton
|
|
72
|
+
icon={FullWidth}
|
|
73
|
+
_icon={{
|
|
74
|
+
size: '30px',
|
|
75
|
+
color: mode === MODE_FULL ? 'primary.100' : '#000',
|
|
76
|
+
}}
|
|
77
|
+
disabled={mode === MODE_FULL}
|
|
78
|
+
onPress={() => setMode(MODE_FULL)}
|
|
79
|
+
tooltip="Full Width"
|
|
80
|
+
/>
|
|
81
|
+
<IconButton
|
|
82
|
+
icon={SideBySide}
|
|
83
|
+
_icon={{
|
|
84
|
+
size: '30px',
|
|
85
|
+
color: mode === MODE_SIDE ? 'primary.100' : '#000',
|
|
86
|
+
}}
|
|
87
|
+
disabled={mode === MODE_SIDE}
|
|
88
|
+
onPress={() => setMode(MODE_SIDE)}
|
|
89
|
+
tooltip="Side Editor"
|
|
90
|
+
/>
|
|
91
|
+
</Row>
|
|
92
|
+
{whichComponent}
|
|
93
|
+
</Column>;
|
|
94
|
+
}
|
|
@@ -32,7 +32,6 @@ export default function TabBar(props) {
|
|
|
32
32
|
startsCollapsed = true,
|
|
33
33
|
onChangeCurrentTab,
|
|
34
34
|
onChangeIsCollapsed,
|
|
35
|
-
saveCurrentTab = true,
|
|
36
35
|
...propsToPass
|
|
37
36
|
} = props,
|
|
38
37
|
styles = UiGlobals.styles,
|
|
@@ -55,15 +54,16 @@ export default function TabBar(props) {
|
|
|
55
54
|
return currentTabIx;
|
|
56
55
|
},
|
|
57
56
|
setCurrentTab = (ix) => {
|
|
57
|
+
if ((useLocal && ix === currentTabIxLocal) || ix === currentTabIx) {
|
|
58
|
+
return; // no change
|
|
59
|
+
}
|
|
58
60
|
if (useLocal) {
|
|
59
61
|
setCurrentTabIxLocal(ix);
|
|
62
|
+
setSaved(id + '-currentTabIx', ix);
|
|
60
63
|
}
|
|
61
64
|
if (onChangeCurrentTab) {
|
|
62
65
|
onChangeCurrentTab(ix);
|
|
63
66
|
}
|
|
64
|
-
if (saveCurrentTab) {
|
|
65
|
-
setSaved(id + '-currentTabIx', ix);
|
|
66
|
-
}
|
|
67
67
|
},
|
|
68
68
|
onToggleCollapse = () => {
|
|
69
69
|
setIsCollapsed(!isCollapsed);
|
|
@@ -310,7 +310,7 @@ export default function TabBar(props) {
|
|
|
310
310
|
setIsCollapsed(val);
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
if (
|
|
313
|
+
if (useLocal) {
|
|
314
314
|
key = id + '-currentTabIx';
|
|
315
315
|
val = await getSaved(key);
|
|
316
316
|
if (!_.isNil(val)) {
|
package/src/Constants/Styles.js
CHANGED
|
@@ -63,6 +63,8 @@ const defaults = {
|
|
|
63
63
|
GRID_ROW_HOVER_BG: 'hover',
|
|
64
64
|
GRID_ROW_SELECTED_BG: 'selected',
|
|
65
65
|
GRID_ROW_SELECTED_HOVER_BG: 'selectedHover',
|
|
66
|
+
GRID_BORDER_WIDTH: 1,
|
|
67
|
+
GRID_BORDER_COLOR: 'trueGray.300',
|
|
66
68
|
ICON_BUTTON_BG: 'trueGray.200:alpha.0',
|
|
67
69
|
ICON_BUTTON_BG_DISABLED: 'trueGray.200',
|
|
68
70
|
ICON_BUTTON_BG_HOVER: '#000:alpha.20',
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useSelector, useDispatch } from 'react-redux'
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// Usage example:
|
|
6
|
+
// const [
|
|
7
|
+
// {
|
|
8
|
+
// user,
|
|
9
|
+
// isLoading,
|
|
10
|
+
// },
|
|
11
|
+
// dispatch
|
|
12
|
+
// ] = useRedux([
|
|
13
|
+
// 'user',
|
|
14
|
+
// 'isLoading',
|
|
15
|
+
// ]);
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export default function useRedux(properties) {
|
|
19
|
+
let values = {};
|
|
20
|
+
_.forEach(properties, (property) => {
|
|
21
|
+
values[property] = useSelector((state) => getPropertyFromState(property, state));
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return [
|
|
25
|
+
values,
|
|
26
|
+
useDispatch(),
|
|
27
|
+
];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function getPropertyFromState(property, state) {
|
|
31
|
+
let found;
|
|
32
|
+
const reducers = _.keys(state);
|
|
33
|
+
_.each(reducers, (reducer) => {
|
|
34
|
+
if (state[reducer].hasOwnProperty(property)) {
|
|
35
|
+
found = state[reducer][property];
|
|
36
|
+
return false; // break
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return found;
|
|
40
|
+
}
|