@onehat/ui 0.4.27 → 0.4.29

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.27",
3
+ "version": "0.4.29",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -126,7 +126,10 @@ export default function Accordion(props) {
126
126
  return <ScrollView
127
127
  ref={scrollViewRef}
128
128
  keyboardShouldPersistTaps="always"
129
- className="flex-1 w-full"
129
+ className="Accordion-ScrollView flex-1 w-full"
130
+ contentContainerStyle={{
131
+ height: '100%',
132
+ }}
130
133
  >
131
134
  <VStackNative
132
135
  {...propsToPass}
@@ -182,6 +182,7 @@ export function ColorElement(props) {
182
182
  border-bottom-right-radius-6
183
183
  ${styles.FORM_COLOR_INPUT_CLASSNAME}
184
184
  `}
185
+ textAlignIsCenter={true}
185
186
  onLayout={(e) => {
186
187
  // On web, this is not needed, but on RN it might be, so leave it in for now
187
188
  const {
@@ -577,6 +577,7 @@ export function ComboComponent(props) {
577
577
  input = disableDirectEntry ?
578
578
  <Pressable
579
579
  {...testProps('toggleMenuBtn')}
580
+ ref={inputRef}
580
581
  onPress={toggleMenu}
581
582
  className={`
582
583
  Combo-toggleMenuBtn
@@ -587,7 +588,8 @@ export function ComboComponent(props) {
587
588
  justify-center
588
589
  items-center
589
590
  m-0
590
- p-1
591
+ p-2
592
+ bg-white
591
593
  border
592
594
  border-grey-400
593
595
  rounded-r-none
@@ -596,13 +598,10 @@ export function ComboComponent(props) {
596
598
  >
597
599
  {inputIconElement}
598
600
  <TextNative
599
- ref={inputRef}
600
601
  numberOfLines={1}
601
602
  ellipsizeMode="head"
602
603
  className={`
603
604
  Combo-TextNative
604
- h-auto
605
- self-stretch
606
605
  flex-1
607
606
  ${_.isEmpty(textInputValue) ? "text-grey-400" : "text-black"}
608
607
  ${styles.FORM_COMBO_INPUT_CLASSNAME}
@@ -878,6 +877,7 @@ export function ComboComponent(props) {
878
877
  dropdownMenu-Box
879
878
  flex-1
880
879
  overflow-auto
880
+ bg-white
881
881
  p-0
882
882
  rounded-none
883
883
  border
@@ -378,6 +378,7 @@ export const DateElement = forwardRef((props, ref) => {
378
378
  `}
379
379
  autoSubmitDelay={1000}
380
380
  placeholder={placeholder}
381
+ textAlignIsCenter={true}
381
382
  {..._input}
382
383
  />;
383
384
  }
@@ -29,7 +29,7 @@ const InputElement = forwardRef((props, ref) => {
29
29
  rightIcon,
30
30
  rightIconHandler,
31
31
  placeholder,
32
- textAlignIsCenter = true,
32
+ textAlignIsCenter = false,
33
33
  className,
34
34
  ...propsToPass
35
35
  } = props,
@@ -171,6 +171,7 @@ function NumberElement(props) {
171
171
  text-center
172
172
  rounded-none
173
173
  `}
174
+ textAlignIsCenter={true}
174
175
  style={{
175
176
  flex: 3
176
177
  }}
@@ -182,6 +182,7 @@ const
182
182
  isDisabled={isDisabled}
183
183
  disableAutoFlex={true}
184
184
  className={inputClassName}
185
+ textAlignIsCenter={true}
185
186
  {...props._input}
186
187
  />
187
188
  <HStack className="flex-1">
@@ -1350,12 +1350,15 @@ function Form(props) {
1350
1350
  {editorType !== EDITOR_TYPE__INLINE &&
1351
1351
  <ScrollView
1352
1352
  className={`
1353
- ScrollView
1353
+ Form-ScrollView
1354
1354
  w-full
1355
1355
  flex-1
1356
1356
  pb-1
1357
1357
  web:min-h-[${minHeight}px]
1358
1358
  `}
1359
+ contentContainerStyle={{
1360
+ height: '100%',
1361
+ }}
1359
1362
  >
1360
1363
  {modeHeader}
1361
1364
  {formHeader}
@@ -6,6 +6,7 @@ import {
6
6
  } from '@project-components/Gluestack';
7
7
  import {
8
8
  ScrollView,
9
+ Platform,
9
10
  } from 'react-native'
10
11
  import {
11
12
  EDITOR_TYPE__PLAIN,
@@ -35,6 +36,8 @@ import _ from 'lodash';
35
36
  //
36
37
  // Model defaultFilters should adjust to this new arrangement
37
38
 
39
+ const isWindows = Platform.OS === 'windows';
40
+
38
41
  export default function withFilters(WrappedComponent) {
39
42
  return forwardRef((props, ref) => {
40
43
 
@@ -140,9 +143,11 @@ export default function withFilters(WrappedComponent) {
140
143
 
141
144
  const
142
145
  filterCallbackRef = useRef(),
146
+ scrollViewRef = useRef(),
143
147
  [filters, setFiltersRaw] = useState(formattedStartingFilters), // array of formatted filters
144
148
  [slots, setSlots] = useState(startingSlots), // array of field names user is currently filtering on; blank slots have a null entry in array
145
149
  [previousFilterNames, setPreviousFilterNames] = useState([]), // names of filters the repository used last query
150
+ [isHorizontalScrollbarShown, setIsHorizontalScrollbarShown] = useState(false),
146
151
  setFilters = (filters, doSetSlots = true, save = true) => {
147
152
  setFiltersRaw(filters);
148
153
 
@@ -349,6 +354,16 @@ export default function withFilters(WrappedComponent) {
349
354
  w: 500,
350
355
  });
351
356
  },
357
+ onContentSizeChange = (contentWidth, contentHeight) => {
358
+ if (!isWindows) {
359
+ return;
360
+ }
361
+ if (scrollViewRef.current) {
362
+ scrollViewRef.current.measure((x, y, width, height, pageX, pageY) => {
363
+ setIsHorizontalScrollbarShown(contentWidth > width);
364
+ });
365
+ }
366
+ },
352
367
  buildModalBody = (modalFilters, modalSlots) => {
353
368
 
354
369
  const
@@ -603,6 +618,25 @@ export default function withFilters(WrappedComponent) {
603
618
  })();
604
619
  }, [filters]);
605
620
 
621
+ useEffect(() => {
622
+ if (!isWindows) {
623
+ return;
624
+ }
625
+
626
+ // NOTE: On Windows machines, I was getting horizontal scrollbars when the ScrollView
627
+ // was not wide enough to contain all the filters. This workaround adds pb-5 to the ScrollView
628
+ // when the scrollbar is shown.
629
+
630
+ if (scrollViewRef.current) {
631
+ scrollViewRef.current.addEventListener('contentSizeChange', onContentSizeChange);
632
+ }
633
+ return () => {
634
+ if (scrollViewRef.current) {
635
+ scrollViewRef.current.removeEventListener('contentSizeChange', onContentSizeChange);
636
+ }
637
+ };
638
+ }, []);
639
+
606
640
  if (!isReady) {
607
641
  return null;
608
642
  }
@@ -615,9 +649,16 @@ export default function withFilters(WrappedComponent) {
615
649
  const
616
650
  renderedFilters = renderFilters(),
617
651
  hasFilters = !!renderedFilters.length,
652
+ scrollViewClass = isWindows && isHorizontalScrollbarShown ? 'pb-5' : '',
618
653
  toolbar = <Toolbar>
619
654
  <HStack className="withFilters-scrollViewContainer flex-1 items-center">
620
- <ScrollView className="ScrollView" horizontal={true} contentContainerStyle={{ alignItems: 'center' }}>
655
+ <ScrollView
656
+ ref={scrollViewRef}
657
+ className={`withFilters-ScrollView ${scrollViewClass}`}
658
+ horizontal={true}
659
+ contentContainerStyle={{ alignItems: 'center' }}
660
+ onContentSizeChange={onContentSizeChange}
661
+ >
621
662
  <Text
622
663
  className={`
623
664
  withFilters-filtersLabel
@@ -4,8 +4,8 @@ import { Path, Svg } from 'react-native-svg';
4
4
 
5
5
  const SvgComponent = createIcon({
6
6
  Root: Svg,
7
- viewBox: '0 0 9.51 12.68',
8
- path: <Path d="M4.76 0C3.72 0 2.84.66 2.52 1.59h-.93C.72 1.59 0 2.3 0 3.18v7.93c0 .87.71 1.59 1.59 1.59h6.34c.87 0 1.59-.71 1.59-1.59V3.17c0-.87-.71-1.59-1.59-1.59H7A2.384 2.384 0 004.76-.01zm0 1.59c.44 0 .79.35.79.79s-.35.79-.79.79-.79-.35-.79-.79.35-.79.79-.79zm2.8 5.18L4.39 9.94c-.23.23-.61.23-.84 0L1.96 8.35c-.23-.23-.23-.61 0-.84s.61-.23.84 0l1.16 1.16 2.75-2.75c.23-.23.61-.23.84 0s.23.61 0 .84z" />
7
+ viewBox: '0 0 384 512',
8
+ path: <Path d="M192 0c-41.8 0-77.4 26.7-90.5 64L64 64C28.7 64 0 92.7 0 128L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64l-37.5 0C269.4 26.7 233.8 0 192 0zm0 64a32 32 0 1 1 0 64 32 32 0 1 1 0-64zM305 273L177 401c-9.4 9.4-24.6 9.4-33.9 0L79 337c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L271 239c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z" />
9
9
  });
10
10
 
11
11
  export default SvgComponent
@@ -1,4 +1,6 @@
1
- import { Box } from "../Gluestack";
1
+ import {
2
+ Box
3
+ } from '@project-components/Gluestack';
2
4
 
3
5
  export default function CenterBox(props) {
4
6
  let className = `
@@ -0,0 +1,73 @@
1
+ import {
2
+ HStack,
3
+ Icon,
4
+ Text,
5
+ VStack,
6
+ } from '@project-components/Gluestack';
7
+ import {
8
+ SCREEN_MODES__FULL,
9
+ SCREEN_MODES__SIDE,
10
+ } from '../../Constants/ScreenModes.js'
11
+ import FullWidth from '../Icons/FullWidth';
12
+ import SideBySide from '../Icons/SideBySide';
13
+ import UiGlobals from '../../UiGlobals.js';
14
+ import IconButton from '../Buttons/IconButton';
15
+ import testProps from '../../Functions/testProps.js';
16
+ import _ from 'lodash';
17
+
18
+ export default function ScreenHeader(props) {
19
+ const {
20
+ title,
21
+ icon,
22
+ useModeIcons = false,
23
+ allowSideBySide = false,
24
+ actualMode,
25
+ onFullWidth,
26
+ onSideBySide,
27
+ } = props,
28
+ textProps = {},
29
+ styles = UiGlobals.styles;
30
+ if (styles.MANAGER_SCREEN_TITLE) {
31
+ textProps.style = {
32
+ fontFamily: styles.MANAGER_SCREEN_TITLE,
33
+ };
34
+ }
35
+ return <HStack className="ScreenHeader-HStack h-[80px] items-center border-b-[2px] border-b-[#ccc]">
36
+ {icon &&
37
+ <Icon
38
+ as={icon}
39
+ className={`
40
+ ml-5
41
+ text-black
42
+ `}
43
+ size="xl"
44
+ />}
45
+ <Text {...textProps} className="ScreenHeader-Text pl-4 text-[26px] font-[700]">{title}</Text>
46
+ {useModeIcons && allowSideBySide &&
47
+ <>
48
+ <IconButton
49
+ {...testProps('fullModeBtn')}
50
+ icon={FullWidth}
51
+ _icon={{
52
+ size: 'xl',
53
+ className: 'text-black',
54
+ }}
55
+ isDisabled={actualMode === SCREEN_MODES__FULL}
56
+ onPress={onFullWidth}
57
+ tooltip="To full width"
58
+ className="ml-5"
59
+ />
60
+ <IconButton
61
+ {...testProps('sideModeBtn')}
62
+ icon={SideBySide}
63
+ _icon={{
64
+ size: 'xl',
65
+ className: 'text-black',
66
+ }}
67
+ isDisabled={actualMode === SCREEN_MODES__SIDE}
68
+ onPress={onSideBySide}
69
+ tooltip="To side editor"
70
+ />
71
+ </>}
72
+ </HStack>;
73
+ }
@@ -0,0 +1,22 @@
1
+ import {
2
+ TextNative,
3
+ Tooltip, TooltipContent, TooltipText,
4
+ } from '@project-components/Gluestack';
5
+
6
+ export default function TextWithTooltip(props) {
7
+ const {
8
+ tooltip,
9
+ children,
10
+ ...propsToPass
11
+ } = props;
12
+ return <Tooltip
13
+ placement="bottom"
14
+ trigger={(triggerProps) => {
15
+ return <TextNative {...triggerProps} {...propsToPass}>{children}</TextNative>
16
+ }}
17
+ >
18
+ <TooltipContent>
19
+ <TooltipText>{tooltip}</TooltipText>
20
+ </TooltipContent>
21
+ </Tooltip>;
22
+ }
@@ -15,6 +15,7 @@ import {
15
15
  UI_MODE_NATIVE,
16
16
  } from '../../Constants/UiModes.js';
17
17
  import UiGlobals from '../../UiGlobals.js';
18
+ import testProps from '../../Functions/testProps.js';
18
19
  import Minus from '../Icons/Minus.js';
19
20
  import Plus from '../Icons/Plus.js';
20
21
  import Xmark from '../Icons/Xmark.js';
@@ -55,6 +56,7 @@ export default function Header(props) {
55
56
  closeClassName += ' mb-1';
56
57
  }
57
58
  closeBtn = <IconButton
59
+ {...testProps('closeBtn')}
58
60
  onPress={onClose}
59
61
  icon={Xmark}
60
62
  _icon={{
@@ -120,7 +120,7 @@ function Panel(props) {
120
120
  }
121
121
 
122
122
  // frame
123
- className += ' border-primary-600' + (isWindow ? ' rounded-lg shadow-lg ' : '') + (frame ? ' border-2' : ' border-none');
123
+ className += ' border-grey-300' + (isWindow ? ' rounded-lg shadow-lg ' : '') + (frame ? ' border-2' : ' border-none');
124
124
 
125
125
  if (props.className) {
126
126
  className += ' ' + props.className;
@@ -143,8 +143,13 @@ function Panel(props) {
143
143
  overflow-hidden
144
144
  `}
145
145
  >
146
- {isScrollable ?
147
- <ScrollView className="Panel-ScrollView">
146
+ {isScrollable ?
147
+ <ScrollView
148
+ className="Panel-ScrollView"
149
+ contentContainerStyle={{
150
+ height: '100%',
151
+ }}
152
+ >
148
153
  {children}
149
154
  </ScrollView> :
150
155
  children}
@@ -1,27 +1,22 @@
1
1
  import {
2
- HStack,
3
- Icon,
4
- Text,
5
2
  VStackNative,
6
3
  } from '@project-components/Gluestack';
7
4
  import React, { useState, useEffect, } from 'react';
5
+ import {
6
+ SCREEN_MODES__FULL,
7
+ SCREEN_MODES__SIDE,
8
+ } from '../../Constants/ScreenModes.js'
8
9
  import withComponent from '../Hoc/withComponent.js';
9
10
  import testProps from '../../Functions/testProps.js';
10
- import UiGlobals from '../../UiGlobals.js';
11
- import IconButton from '../Buttons/IconButton';
12
- import FullWidth from '../Icons/FullWidth';
13
- import SideBySide from '../Icons/SideBySide';
11
+ import ScreenHeader from '../Layout/ScreenHeader';
14
12
  import getSaved from '../../Functions/getSaved.js';
15
13
  import setSaved from '../../Functions/setSaved.js';
16
14
  import _ from 'lodash';
17
15
 
18
- const
19
- MODE_FULL = 'MODE_FULL',
20
- MODE_SIDE = 'MODE_SIDE';
21
-
22
16
  function ManagerScreen(props) {
23
17
  const {
24
18
  title,
19
+ icon,
25
20
  sideModeComponent,
26
21
  fullModeComponent,
27
22
  onChangeMode,
@@ -29,15 +24,14 @@ function ManagerScreen(props) {
29
24
  // withComponent
30
25
  self,
31
26
  } = props,
32
- styles = UiGlobals.styles,
33
27
  id = props.id || props.self?.path,
34
28
  [isRendered, setIsRendered] = useState(false),
35
29
  [isModeSet, setIsModeSet] = useState(false),
36
30
  [allowSideBySide, setAllowSideBySide] = useState(false),
37
- [mode, setModeRaw] = useState(MODE_FULL),
38
- actualMode = (!allowSideBySide || mode === MODE_FULL) ? MODE_FULL : MODE_SIDE,
31
+ [mode, setModeRaw] = useState(SCREEN_MODES__FULL),
32
+ actualMode = (!allowSideBySide || mode === SCREEN_MODES__FULL) ? SCREEN_MODES__FULL : SCREEN_MODES__SIDE,
39
33
  setMode = (newMode) => {
40
- if (!allowSideBySide && newMode === MODE_SIDE) {
34
+ if (!allowSideBySide && newMode === SCREEN_MODES__SIDE) {
41
35
  return;
42
36
  }
43
37
  if (newMode === mode) {
@@ -84,58 +78,22 @@ function ManagerScreen(props) {
84
78
  self.mode = actualMode;
85
79
  }
86
80
 
87
- const
88
- whichComponent = actualMode === MODE_FULL ? fullModeComponent : sideModeComponent,
89
- textProps = {};
90
- if (styles.MANAGER_SCREEN_TITLE) {
91
- textProps.style = {
92
- fontFamily: styles.MANAGER_SCREEN_TITLE,
93
- };
94
- }
81
+ const whichComponent = actualMode === SCREEN_MODES__FULL ? fullModeComponent : sideModeComponent;
95
82
 
96
83
  return <VStackNative
97
84
  {...testProps(self)}
98
85
  onLayout={onLayout}
99
86
  className="max-h-screen overflow-hidden flex-1 w-full"
100
87
  >
101
- <HStack className="h-[80px] items-center border-b-[2px] border-b-[#ccc]">
102
- {props.icon ?
103
- <Icon
104
- as={props.icon}
105
- className={`
106
- ml-5
107
- `}
108
- size="xl"
109
- color="#000"
110
- /> : null}
111
- <Text {...textProps} className="pl-4 text-[26px] font-[700]">{title}</Text>
112
- {allowSideBySide &&
113
- <>
114
- <IconButton
115
- {...testProps('fullModeBtn')}
116
- icon={FullWidth}
117
- _icon={{
118
- size: 'xl',
119
- className: 'text-black',
120
- }}
121
- isDisabled={actualMode === MODE_FULL}
122
- onPress={() => setMode(MODE_FULL)}
123
- tooltip="To full width"
124
- className="ml-5"
125
- />
126
- <IconButton
127
- {...testProps('sideModeBtn')}
128
- icon={SideBySide}
129
- _icon={{
130
- size: 'xl',
131
- className: 'text-black',
132
- }}
133
- isDisabled={actualMode === MODE_SIDE}
134
- onPress={() => setMode(MODE_SIDE)}
135
- tooltip="To side editor"
136
- />
137
- </>}
138
- </HStack>
88
+ <ScreenHeader
89
+ title={title}
90
+ icon={icon}
91
+ useModeIcons={true}
92
+ actualMode={actualMode}
93
+ allowSideBySide={allowSideBySide}
94
+ onFullWidth={() => setMode(SCREEN_MODES__FULL)}
95
+ onSideBySide={() => setMode(SCREEN_MODES__SIDE)}
96
+ />
139
97
  {isRendered && isModeSet && whichComponent}
140
98
  </VStackNative>;
141
99
  }
@@ -0,0 +1,62 @@
1
+ import { useState, } from 'react';
2
+ import {
3
+ HStack,
4
+ ScrollView,
5
+ VStack,
6
+ VStackNative,
7
+ } from '@project-components/Gluestack';
8
+ import ChartPie from '../Icons/ChartPie.js';
9
+ import ScreenHeader from '../Layout/ScreenHeader.js';
10
+
11
+ const CONTAINER_THRESHOLD = 1100;
12
+
13
+ export default function ReportsManager(props) {
14
+ const {
15
+ reports = [],
16
+ isActive = false,
17
+ } = props,
18
+ [containerWidth, setContainerWidth] = useState(),
19
+ onLayout = (e) => {
20
+ setContainerWidth(e.nativeEvent.layout.width);
21
+ };
22
+
23
+ if (!isActive) {
24
+ return null;
25
+ }
26
+
27
+ let reportElements = [];
28
+ if (containerWidth) {
29
+ if (containerWidth >= CONTAINER_THRESHOLD) {
30
+ // two column layout
31
+ const
32
+ reportsPerColumn = Math.ceil(reports.length / 2),
33
+ col1Reports = reports.slice(0, reportsPerColumn),
34
+ col2Reports = reports.slice(reportsPerColumn);
35
+ reportElements = <HStack className="gap-3">
36
+ <VStack className="flex-1">
37
+ {col1Reports}
38
+ </VStack>
39
+ <VStack className="flex-1">
40
+ {col2Reports}
41
+ </VStack>
42
+ </HStack>;
43
+ } else {
44
+ // one column layout
45
+ reportElements = reports;
46
+ }
47
+ }
48
+
49
+ return <VStack
50
+ className="overflow-hidden flex-1 w-full"
51
+ >
52
+ <ScreenHeader
53
+ title="Reports"
54
+ icon={ChartPie}
55
+ />
56
+ <ScrollView className="flex-1 w-full">
57
+ <VStackNative className="w-full p-4" onLayout={onLayout}>
58
+ {containerWidth && reportElements}
59
+ </VStackNative>
60
+ </ScrollView>
61
+ </VStack>;
62
+ }
@@ -126,6 +126,7 @@ export default function Pagination(props) {
126
126
  text-center
127
127
  bg-grey-100
128
128
  `}
129
+ textAlignIsCenter={true}
129
130
  tooltip="Set Page"
130
131
  />);
131
132
  items.push(<Text
@@ -1396,7 +1396,13 @@ function TreeComponent(props) {
1396
1396
  }}
1397
1397
  className="Tree-deselector w-full flex-1 p-1 bg-white"
1398
1398
  >
1399
- <ScrollView {...testProps('ScrollView')} className="ScrollView flex-1 w-full">
1399
+ <ScrollView
1400
+ {...testProps('ScrollView')}
1401
+ className="Tree-ScrollView flex-1 w-full"
1402
+ contentContainerStyle={{
1403
+ height: '100%',
1404
+ }}
1405
+ >
1400
1406
  {!treeNodes?.length ?
1401
1407
  <CenterBox>
1402
1408
  {Repository.isLoading ? <Loading /> : <NoRecordsFound text={noneFoundText} onRefresh={reloadTree} />}
@@ -109,7 +109,9 @@ export default function TreeNode(props) {
109
109
  text-ellipsis
110
110
  ${styles.TREE_NODE_CLASSNAME}
111
111
  `}
112
- style={{ userSelect: 'none', }}
112
+ style={{
113
+ // userSelect: 'none',
114
+ }}
113
115
  >{text}</TextNative> : null}
114
116
 
115
117
  {content}
@@ -0,0 +1,2 @@
1
+ export const SCREEN_MODES__FULL = 'SCREEN_MODES__FULL';
2
+ export const SCREEN_MODES__SIDE = 'SCREEN_MODES__SIDE';