@onehat/ui 0.2.81 → 0.3.0
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 +9 -10
- package/src/Components/Grid/Grid.js +2 -0
- package/src/Components/Hoc/withEditor.js +12 -0
- package/src/Components/Hoc/withSideEditor.js +4 -7
- package/src/Components/Panel/GridPanel.js +5 -1
- package/src/Components/Screens/Manager.js +4 -2
- 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.
|
|
3
|
+
"version": "0.3.0",
|
|
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.
|
|
29
|
-
"@hookform/resolvers": "^3.
|
|
28
|
+
"@onehat/data": "^1.19.0",
|
|
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
|
|
@@ -287,12 +287,14 @@ function Form(props) {
|
|
|
287
287
|
return <Element key={ix} title={title} {...defaults} {...propsToPass} {...editorTypeProps}>{children}</Element>;
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
if (!label && Repository && model[name].title) {
|
|
291
|
+
label = model[name].title;
|
|
292
|
+
}
|
|
293
|
+
|
|
290
294
|
if (isViewOnly || !isEditable) {
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
const value = (record && record[name]) || (startingValues && startingValues[name]) || null;
|
|
295
|
+
const
|
|
296
|
+
Text = getComponentFromType('Text'),
|
|
297
|
+
value = (record && record[name]) || (startingValues && startingValues[name]) || null;
|
|
296
298
|
let element = <Text
|
|
297
299
|
value={value}
|
|
298
300
|
{...propsToPass}
|
|
@@ -307,9 +309,6 @@ function Form(props) {
|
|
|
307
309
|
return <Row key={ix} px={2} pb={1}>{element}</Row>;
|
|
308
310
|
}
|
|
309
311
|
|
|
310
|
-
if (!label && Repository && model.titles?.[name]) {
|
|
311
|
-
label = model.titles[name];
|
|
312
|
-
}
|
|
313
312
|
|
|
314
313
|
|
|
315
314
|
// // These rules are for fields *outside* the model
|
|
@@ -427,7 +426,7 @@ function Form(props) {
|
|
|
427
426
|
fontWeight="bold"
|
|
428
427
|
>{title}</Text>;
|
|
429
428
|
}
|
|
430
|
-
return <Column key={'ancillary-' + ix}
|
|
429
|
+
return <Column key={'ancillary-' + ix} mx={2} my={5}>{title}{element}</Column>;
|
|
431
430
|
});
|
|
432
431
|
}
|
|
433
432
|
return components;
|
|
@@ -499,7 +498,7 @@ function Form(props) {
|
|
|
499
498
|
// for all other editor types
|
|
500
499
|
formComponents = buildFromItems();
|
|
501
500
|
const formAncillaryComponents = buildAncillary();
|
|
502
|
-
editor = <ScrollView
|
|
501
|
+
editor = <ScrollView _web={{ height: 1 }} width="100%" pb={1}>
|
|
503
502
|
<Row>{formComponents}</Row>
|
|
504
503
|
<Column pt={4}>{formAncillaryComponents}</Column>
|
|
505
504
|
</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
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useState, } from 'react';
|
|
2
1
|
import {
|
|
3
2
|
EDITOR_TYPE__SIDE,
|
|
4
3
|
} from '../../Constants/Editor.js';
|
|
@@ -13,9 +12,7 @@ export default function withSideEditor(WrappedComponent, isTree = false) {
|
|
|
13
12
|
Editor,
|
|
14
13
|
editorProps = {},
|
|
15
14
|
sideFlex = 100,
|
|
16
|
-
} = props
|
|
17
|
-
[selection, setSelection] = useState(null);
|
|
18
|
-
|
|
15
|
+
} = props;
|
|
19
16
|
|
|
20
17
|
if (!Editor) {
|
|
21
18
|
throw Error('Editor is not defined');
|
|
@@ -25,13 +22,13 @@ export default function withSideEditor(WrappedComponent, isTree = false) {
|
|
|
25
22
|
center={<WrappedComponent
|
|
26
23
|
isTree={isTree}
|
|
27
24
|
{...props}
|
|
28
|
-
onSelectionChange={setSelection}
|
|
29
25
|
/>}
|
|
30
26
|
east={<Editor
|
|
27
|
+
{...props}
|
|
31
28
|
editorType={EDITOR_TYPE__SIDE}
|
|
32
29
|
flex={sideFlex}
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
borderLeftWidth={1}
|
|
31
|
+
borderLeftColor="#ccc"
|
|
35
32
|
{...editorProps}
|
|
36
33
|
/>}
|
|
37
34
|
/>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect,
|
|
1
|
+
import React, { useState, useEffect, } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Column,
|
|
4
4
|
Row,
|
|
@@ -20,8 +20,8 @@ export default function ManagerScreen(props) {
|
|
|
20
20
|
title,
|
|
21
21
|
sideModeComponent,
|
|
22
22
|
fullModeComponent,
|
|
23
|
+
id,
|
|
23
24
|
} = props,
|
|
24
|
-
id = useId(),
|
|
25
25
|
[isReady, setIsReady] = useState(false),
|
|
26
26
|
[mode, setModeRaw] = useState(MODE_FULL),
|
|
27
27
|
setMode = (newMode) => {
|
|
@@ -64,6 +64,8 @@ export default function ManagerScreen(props) {
|
|
|
64
64
|
<Row
|
|
65
65
|
h="80px"
|
|
66
66
|
py={2}
|
|
67
|
+
borderBottomWidth={2}
|
|
68
|
+
borderBottomColor="#ccc"
|
|
67
69
|
>
|
|
68
70
|
<Text p={4} fontSize="26" fontWeight={700}>{title}</Text>
|
|
69
71
|
<IconButton
|
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
|
+
}
|