@onehat/ui 0.4.114 → 0.4.115

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.114",
3
+ "version": "0.4.115",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -1,4 +1,4 @@
1
- import { cloneElement, forwardRef, isValidElement, useRef } from 'react';
1
+ import { cloneElement, forwardRef, isValidElement, useContext, useRef } from 'react';
2
2
  import {
3
3
  Button,
4
4
  ButtonText,
@@ -10,9 +10,12 @@ import addIconProps from '../../Functions/addIconProps.js';
10
10
  import clsx from 'clsx';
11
11
  import withComponent from '../Hoc/withComponent.js';
12
12
  import withTooltip from '../Hoc/withTooltip.js';
13
+ import FormContext from '../Form/FormContext.js';
13
14
  import _ from 'lodash';
14
15
 
15
16
  const ButtonComponent = forwardRef((props, ref) => {
17
+
18
+ const formContext = useContext(FormContext);
16
19
  let {
17
20
  self,
18
21
  text, // the text to display on the button
@@ -32,6 +35,16 @@ const ButtonComponent = forwardRef((props, ref) => {
32
35
  propsToPass.onPress = propsToPass.handler; // alias
33
36
  }
34
37
 
38
+ const {
39
+ disableOnInvalid,
40
+ ...propsToPassWithoutDisableOnInvalid
41
+ } = propsToPass;
42
+ propsToPass = propsToPassWithoutDisableOnInvalid;
43
+
44
+ if (_.isNil(propsToPass.isDisabled) && disableOnInvalid && formContext && !formContext.isValid) {
45
+ propsToPass.isDisabled = true;
46
+ }
47
+
35
48
  if (icon) {
36
49
  if (isValidElement(icon)) {
37
50
  if (_icon) {
@@ -64,8 +77,10 @@ const ButtonComponent = forwardRef((props, ref) => {
64
77
  'flex',
65
78
  'flex-row',
66
79
  'items-center',
67
- 'disabled:opacity-40',
68
- 'disabled:cursor-not-allowed',
80
+ 'data-[disabled=true]:opacity-40',
81
+ 'data-[disabled=true]:cursor-not-allowed',
82
+ 'web:disabled:opacity-40',
83
+ 'web:disabled:cursor-not-allowed',
69
84
  );
70
85
  if (isExpandToFillVertical) {
71
86
  // IMPORTANT! Otherwise the button will cut off the vertical content due to size classes automatically added by Gluestack (e.g. h-10)
@@ -1,4 +1,4 @@
1
- import { useEffect, useCallback, useState, useRef, isValidElement, } from 'react';
1
+ import { useEffect, useCallback, useState, useRef, isValidElement, cloneElement, Children, } from 'react';
2
2
  import {
3
3
  Box,
4
4
  HStack,
@@ -59,6 +59,7 @@ import Xmark from '../Icons/Xmark.js';
59
59
  import Check from '../Icons/Check.js';
60
60
  import Footer from '../Layout/Footer.js';
61
61
  import Label from '../Form/Label.js';
62
+ import FormContext from '../Form/FormContext.js';
62
63
  import _ from 'lodash';
63
64
 
64
65
  // TODO: memoize field Components
@@ -1119,6 +1120,42 @@ function Form(props) {
1119
1120
  alert(errors.message);
1120
1121
  }
1121
1122
  },
1123
+ decorateAdditionalFooterItems = (elements) => {
1124
+ const decorateElement = (element) => {
1125
+ if (!isValidElement(element)) {
1126
+ return element;
1127
+ }
1128
+
1129
+ const
1130
+ elementProps = element.props || {},
1131
+ propsToInject = {};
1132
+
1133
+ if (elementProps.disableOnInvalid && !formState.isValid) {
1134
+ propsToInject.isDisabled = true;
1135
+ }
1136
+
1137
+ if (typeof elementProps.onPress === 'function' && (elementProps.disableOnInvalid || elementProps.skipSubmit || elementProps.submitWithForm)) {
1138
+ const originalOnPress = elementProps.onPress;
1139
+ if (elementProps.skipSubmit) {
1140
+ propsToInject.onPress = () => originalOnPress();
1141
+ } else {
1142
+ propsToInject.onPress = (e) => handleSubmit(originalOnPress, onSubmitError)(e);
1143
+ }
1144
+ }
1145
+
1146
+ if (elementProps.children) {
1147
+ propsToInject.children = Children.map(elementProps.children, decorateElement);
1148
+ }
1149
+
1150
+ if (_.isEmpty(propsToInject)) {
1151
+ return element;
1152
+ }
1153
+
1154
+ return cloneElement(element, propsToInject);
1155
+ };
1156
+
1157
+ return Children.map(elements, decorateElement);
1158
+ },
1122
1159
  doReset = (values) => {
1123
1160
  reset(values);
1124
1161
  if (onReset) {
@@ -1444,7 +1481,7 @@ function Form(props) {
1444
1481
  }
1445
1482
  footerItems =
1446
1483
  <>
1447
- {additionalFooterItems}
1484
+ {decorateAdditionalFooterItems(additionalFooterItems)}
1448
1485
  {!additionalFooterItems && additionalFooterButtons && _.map(additionalFooterButtons, (props, ix) => {
1449
1486
  let isDisabled = false;
1450
1487
  if (props.disableOnInvalid) {
@@ -1597,7 +1634,8 @@ function Form(props) {
1597
1634
  className += ' ' + props.className;
1598
1635
  }
1599
1636
  const scrollToTopAnchor = <Box ref={(el) => (ancillaryItemsRef.current[0] = el)} className="h-0" />;
1600
- return <VStackNative
1637
+ return <FormContext.Provider value={{ isValid: formState.isValid }}>
1638
+ <VStackNative
1601
1639
  ref={formRef}
1602
1640
  {...testProps(self)}
1603
1641
  style={style}
@@ -1639,7 +1677,8 @@ function Form(props) {
1639
1677
  {isFabVisible && fab}
1640
1678
 
1641
1679
  </>}
1642
- </VStackNative>;
1680
+ </VStackNative>
1681
+ </FormContext.Provider>;
1643
1682
  }
1644
1683
 
1645
1684
  // helper fns
@@ -0,0 +1,7 @@
1
+ import { createContext } from 'react';
2
+
3
+ const FormContext = createContext({
4
+ isValid: true,
5
+ });
6
+
7
+ export default FormContext;