@widergy/mobile-ui 1.26.4 → 1.27.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.
Files changed (28) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/lib/components/RadioGroup/components/RadioButton/index.js +1 -1
  3. package/lib/components/RadioGroup/index.js +5 -5
  4. package/lib/components/UTActionCard/README.md +25 -0
  5. package/lib/components/UTActionCard/components/AdditionalMessage/index.js +28 -0
  6. package/lib/components/UTActionCard/components/AdditionalMessage/styles.js +19 -0
  7. package/lib/components/UTActionCard/components/BottomActions/index.js +62 -0
  8. package/lib/components/UTActionCard/components/BottomActions/styles.js +48 -0
  9. package/lib/components/UTActionCard/components/Header/components/Avatar/index.js +44 -0
  10. package/lib/components/UTActionCard/components/Header/components/Avatar/styles.js +9 -0
  11. package/lib/components/UTActionCard/components/Header/components/Status/index.js +33 -0
  12. package/lib/components/UTActionCard/components/Header/components/Status/styles.js +18 -0
  13. package/lib/components/UTActionCard/components/Header/constants.js +12 -0
  14. package/lib/components/UTActionCard/components/Header/index.js +101 -0
  15. package/lib/components/UTActionCard/components/Header/styles.js +30 -0
  16. package/lib/components/UTActionCard/components/Header/utils.js +80 -0
  17. package/lib/components/UTActionCard/components/HeaderActions/index.js +80 -0
  18. package/lib/components/UTActionCard/components/HeaderActions/styles.js +21 -0
  19. package/lib/components/UTActionCard/components/HeaderActions/utils.js +17 -0
  20. package/lib/components/UTActionCard/constants.js +20 -0
  21. package/lib/components/UTActionCard/index.js +157 -0
  22. package/lib/components/UTActionCard/styles.js +39 -0
  23. package/lib/components/UTIcon/index.js +4 -3
  24. package/lib/components/UTLabel/constants.js +5 -5
  25. package/lib/components/UTMenu/proptypes.js +1 -1
  26. package/lib/components/UTWorkflowContainer/versions/V0/index.js +1 -1
  27. package/lib/index.js +1 -0
  28. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [1.27.0](https://github.com/widergy/mobile-ui/compare/v1.26.5...v1.27.0) (2024-10-07)
2
+
3
+
4
+ ### Features
5
+
6
+ * ut action card ([#363](https://github.com/widergy/mobile-ui/issues/363)) ([9d539f0](https://github.com/widergy/mobile-ui/commit/9d539f0584489daf1b0f170ffc696402cf333446))
7
+
8
+ ## [1.26.5](https://github.com/widergy/mobile-ui/compare/v1.26.4...v1.26.5) (2024-10-04)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * visual fixes ([#367](https://github.com/widergy/mobile-ui/issues/367)) ([b916b9b](https://github.com/widergy/mobile-ui/commit/b916b9b641c2fbff90b8b5ab2efb8b6b8ae447b6))
14
+
1
15
  ## [1.26.4](https://github.com/widergy/mobile-ui/compare/v1.26.3...v1.26.4) (2024-10-01)
2
16
 
3
17
 
@@ -58,7 +58,7 @@ class RadioButton extends Component {
58
58
  <View style={[styles.label, labelComponent && styles.labelComponent]}>
59
59
  <UTLabel
60
60
  colorTheme={status ? 'accent' : 'dark'}
61
- variant="small"
61
+ variant="body"
62
62
  weight={status ? 'bold' : 'regular'}
63
63
  >
64
64
  {label}
@@ -3,7 +3,7 @@ import { ViewPropTypes } from 'deprecated-react-native-prop-types';
3
3
  import { View } from 'react-native';
4
4
  import PropTypes from 'prop-types';
5
5
 
6
- import Label from '../Label';
6
+ import UTLabel from '../UTLabel';
7
7
 
8
8
  import RadioButton from './components/RadioButton';
9
9
  import { DEFAULT_KEY_FIELD } from './constants';
@@ -40,14 +40,14 @@ class RadioGroup extends Component {
40
40
  return (
41
41
  <View style={styles.container}>
42
42
  {title && (
43
- <Label medium primary {...titleProps}>
43
+ <UTLabel variant="body" {...titleProps}>
44
44
  {title}
45
- </Label>
45
+ </UTLabel>
46
46
  )}
47
47
  {options.map(this.renderOptions)}
48
- <Label error small>
48
+ <UTLabel colorTheme="error" shade="05" variant="body">
49
49
  {error}
50
- </Label>
50
+ </UTLabel>
51
51
  </View>
52
52
  );
53
53
  }
@@ -0,0 +1,25 @@
1
+ # UTButton
2
+
3
+ ## Props
4
+
5
+
6
+ | Name | Type | Default | Description |
7
+ | :----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------ |
8
+ | `additionalMessage` | `shape({ Icon: elementType, iconProps: shape({ colorTheme: string, size: string }), labelProps: shape({ colorTheme: string, variant: string }), message: string })` | | Additional message information, including icons and labels. |
9
+ | `adornment` | `shape({ alignment: oneOf(['center', 'end', 'start']), position: string, props: object, type: string })` | | Adornment object to customize alignment, position, and type. |
10
+ | `BackgroundImage` | `elementType` | | A React component for the background image. |
11
+ | `bottomActions` | `arrayOf(shape({ colorTheme: string, disabled: bool, Icon: elementType, label: string, loading: bool, onPress: func }))` | | Defines the list of actions to be displayed at the bottom of the card. |
12
+ | `bottomActionsVariant` | `oneOf(['default', 'redirection'])` | | Sets the style variant for bottom actions. |
13
+ | `classNames` | `objectOf(string)` | | Css classes |
14
+ | `description` | `string` | | Description text to be displayed. |
15
+ | `descriptionProps` | `shape({ colorTheme: string, variant: string })` | | Props to customize the description's style. |
16
+ | `headerActions` | `arrayOf(shape({ Icon: oneOfType([elementType, string]), id: oneOfType([number, string]), label: string, loading: bool, onPress: func }))` | | List of actions available in the header section. |
17
+ | `headerActionsProps` | `shape({ alignment: oneOf(['center', 'end', 'start']), buttonGroupProps: shape({ colorTheme: string, shape: string }), variant: oneOf(['default', 'buttonGroup']) })` | | Props to configure header actions alignment, button group, etc. |
18
+ | `mainAction` | `func` | | Main action function for the card. |
19
+ | `size` | `oneOf(['medium', 'small'])` | `'medium'` | Size of the card, either small or medium. |
20
+ | `status` | `string` | | Status text to be displayed. |
21
+ | `statusLabel` | `string` | | Status label for additional status info. |
22
+ | `statusAlignment` | `oneOf(['center', 'end', 'start'])` | | Alignment of the status element. |
23
+ | `title` | `string` | | The title of the card. |
24
+ | `titleProps` | `shape({ variant: string, weight: string })` | | Props to configure the title style. |
25
+ | `withBodyPadding` | `bool` | `false` | Adds padding to the card body if`true`. |
@@ -0,0 +1,28 @@
1
+ import React, { memo } from 'react';
2
+ import { oneOf, shape, string } from 'prop-types';
3
+ import { View } from 'react-native';
4
+
5
+ import UTIcon from '../../../UTIcon';
6
+ import UTLabel from '../../../UTLabel';
7
+ import { SIZES } from '../../constants';
8
+
9
+ import styles from './styles';
10
+
11
+ const AdditionalMessage = ({ icon, iconProps = {}, labelProps = {}, message, size = SIZES.SMALL }) => (
12
+ <View style={[styles.additionalMessageContainer, styles[size]]}>
13
+ {icon && <UTIcon area colorTheme="gray" name={icon} size={16} {...iconProps} />}
14
+ <UTLabel colorTheme="gray" variant="small" withMarkdown {...labelProps}>
15
+ {message}
16
+ </UTLabel>
17
+ </View>
18
+ );
19
+
20
+ AdditionalMessage.propTypes = {
21
+ icon: string,
22
+ iconProps: shape({ colorTheme: string, size: string }),
23
+ labelProps: shape({ colorTheme: string, variant: string }),
24
+ message: string,
25
+ size: oneOf([SIZES.MEDIUM, SIZES.SMALL])
26
+ };
27
+
28
+ export default memo(AdditionalMessage);
@@ -0,0 +1,19 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ additionalMessageContainer: {
5
+ alignItems: 'center',
6
+ flexDirection: 'row',
7
+ gap: 8
8
+ },
9
+ medium: {
10
+ paddingTop: 0,
11
+ paddingHorizontal: 24,
12
+ paddingBottom: 24
13
+ },
14
+ small: {
15
+ paddingTop: 0,
16
+ paddingHorizontal: 16,
17
+ paddingBottom: 16
18
+ }
19
+ });
@@ -0,0 +1,62 @@
1
+ import React, { Fragment, memo } from 'react';
2
+ import { arrayOf, bool, elementType, func, oneOfType, shape, string } from 'prop-types';
3
+ import { View } from 'react-native';
4
+
5
+ import UTButton from '../../../UTButton';
6
+ import { ACTION_TYPES } from '../../constants';
7
+ import { useTheme } from '../../../../theming';
8
+ import { mergeMultipleStyles } from '../../../../utils/styleUtils';
9
+
10
+ import styles, { getThemeStyles } from './styles';
11
+
12
+ const BottomActions = ({ actions = [], bottomActionsVariant }) => {
13
+ const theme = useTheme();
14
+ const type = actions.length > 2 ? ACTION_TYPES.REDIRECTION : bottomActionsVariant || ACTION_TYPES.DEFAULT;
15
+ const isRedirection = type === ACTION_TYPES.REDIRECTION;
16
+ const themedStyles = mergeMultipleStyles(styles, getThemeStyles(theme, isRedirection));
17
+
18
+ return (
19
+ <View style={[themedStyles.actionsContainer, themedStyles.redirectionActionsContainer]}>
20
+ {actions.map(({ colorTheme = 'primary', disabled, Icon, label, loading, onClick }, i) => (
21
+ <Fragment key={label}>
22
+ {!isRedirection && i > 0 && <View style={[themedStyles.horizontalSeparator]} />}
23
+ <UTButton
24
+ style={{
25
+ icon: themedStyles.icon,
26
+ root: themedStyles.actionButton
27
+ }}
28
+ colorTheme={colorTheme}
29
+ disabled={disabled}
30
+ Icon={isRedirection ? 'IconChevronRight' : Icon}
31
+ iconPlacement={isRedirection ? 'right' : 'left'}
32
+ loading={loading}
33
+ onClick={onClick}
34
+ size="large"
35
+ variant="text"
36
+ >
37
+ {label}
38
+ </UTButton>
39
+ {isRedirection && actions.length > 1 && i !== actions.length - 1 && (
40
+ <View style={[themedStyles.verticalSeparator]} />
41
+ )}
42
+ </Fragment>
43
+ ))}
44
+ </View>
45
+ );
46
+ };
47
+
48
+ BottomActions.propTypes = {
49
+ actions: arrayOf(
50
+ shape({
51
+ colorTheme: string,
52
+ disabled: bool,
53
+ Icon: oneOfType([elementType, string]),
54
+ label: string,
55
+ loading: bool,
56
+ onClick: func
57
+ })
58
+ ),
59
+ bottomActionsVariant: string
60
+ };
61
+
62
+ export default memo(BottomActions);
@@ -0,0 +1,48 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export const getThemeStyles = (theme = {}, isRedirection) => {
4
+ const lightPalette = theme.Palette.light;
5
+
6
+ const commonStyles = {
7
+ actionsContainer: {
8
+ borderTopColor: lightPalette['04']
9
+ }
10
+ };
11
+
12
+ if (isRedirection) {
13
+ return {
14
+ ...commonStyles,
15
+ verticalSeparator: {
16
+ backgroundColor: lightPalette['04'],
17
+ height: 1
18
+ },
19
+ redirectionActionsContainer: {
20
+ flexDirection: 'column'
21
+ }
22
+ };
23
+ }
24
+
25
+ return {
26
+ ...commonStyles,
27
+ actionButton: {
28
+ alignItems: 'center',
29
+ flex: 1
30
+ },
31
+ horizontalSeparator: {
32
+ backgroundColor: lightPalette['04'],
33
+ width: 1
34
+ }
35
+ };
36
+ };
37
+
38
+ const styles = StyleSheet.create({
39
+ actionsContainer: {
40
+ borderTopWidth: 1,
41
+ flexDirection: 'row'
42
+ },
43
+ icon: {
44
+ marginLeft: 'auto'
45
+ }
46
+ });
47
+
48
+ export default styles;
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { string, number } from 'prop-types';
3
+ import { View } from 'react-native';
4
+
5
+ import { useTheme } from '../../../../../../theming';
6
+ import UTLabel from '../../../../../UTLabel';
7
+
8
+ import styles from './styles';
9
+
10
+ const Avatar = ({ avatarColor, avatarSize, text = '', textColor = 'dark', textVariant = 'title1' }) => {
11
+ const theme = useTheme();
12
+ const backgroundColor = avatarColor || theme.colors.primary;
13
+ const avatarText = text?.trim().substring(0, 1) || '';
14
+
15
+ return (
16
+ <View
17
+ style={[
18
+ {
19
+ width: avatarSize,
20
+ height: avatarSize,
21
+ borderRadius: avatarSize / 2,
22
+ backgroundColor
23
+ },
24
+ styles.container
25
+ ]}
26
+ >
27
+ {avatarText && (
28
+ <UTLabel colorTheme={textColor || theme.colors.secondary} numberOfLines={1} variant={textVariant}>
29
+ {avatarText}
30
+ </UTLabel>
31
+ )}
32
+ </View>
33
+ );
34
+ };
35
+
36
+ Avatar.propTypes = {
37
+ avatarColor: string,
38
+ avatarSize: number,
39
+ text: string,
40
+ textColor: string,
41
+ textVariant: string
42
+ };
43
+
44
+ export default Avatar;
@@ -0,0 +1,9 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ container: {
5
+ justifyContent: 'center',
6
+ alignItems: 'center',
7
+ elevation: 1
8
+ }
9
+ });
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { string } from 'prop-types';
3
+ import { View } from 'react-native';
4
+
5
+ import UTLabel from '../../../../../UTLabel';
6
+
7
+ import styles from './styles';
8
+
9
+ const Status = ({
10
+ alignment = 'start',
11
+ backgroundColor,
12
+ colorTheme = 'dark',
13
+ value,
14
+ variant = 'small',
15
+ weight = 'medium'
16
+ }) => (
17
+ <View style={[styles.container, styles[`alignSelf_${alignment}`], { backgroundColor }]}>
18
+ <UTLabel colorTheme={colorTheme} variant={variant} weight={weight}>
19
+ {value}
20
+ </UTLabel>
21
+ </View>
22
+ );
23
+
24
+ Status.propTypes = {
25
+ alignment: string,
26
+ backgroundColor: string,
27
+ colorTheme: string,
28
+ value: string,
29
+ variant: string,
30
+ weight: string
31
+ };
32
+
33
+ export default Status;
@@ -0,0 +1,18 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ container: {
5
+ borderRadius: 4,
6
+ paddingHorizontal: 8,
7
+ paddingVertical: 4
8
+ },
9
+ alignSelf_center: {
10
+ alignSelf: 'center'
11
+ },
12
+ alignSelf_start: {
13
+ alignSelf: 'flex-start'
14
+ },
15
+ alignSelf_end: {
16
+ alignSelf: 'flex-end'
17
+ }
18
+ });
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { Image } from 'react-native';
3
+
4
+ import UTIcon from '../../../UTIcon';
5
+
6
+ import Avatar from './components/Avatar';
7
+
8
+ export const ADORNMENT_COMPONENT_MAPPER = {
9
+ icon: UTIcon,
10
+ avatar: props => <Avatar text={props.userName} avatarSize={64} {...props} />,
11
+ image: props => <Image resizeMode="contain" {...props} />
12
+ };
@@ -0,0 +1,101 @@
1
+ import React, { memo } from 'react';
2
+ import { View } from 'react-native';
3
+ import {
4
+ arrayOf,
5
+ bool,
6
+ elementType,
7
+ func,
8
+ number,
9
+ object,
10
+ objectOf,
11
+ oneOf,
12
+ oneOfType,
13
+ shape,
14
+ string
15
+ } from 'prop-types';
16
+ import { isEmpty } from '@widergy/web-utils/lib/array';
17
+
18
+ import UTLabel from '../../../UTLabel';
19
+ import HeaderActions from '../HeaderActions';
20
+ import { HEADER_ACTIONS_VARIANTS, PLACE_SELF_TYPES, SIZES } from '../../constants';
21
+ import { useTheme } from '../../../../theming';
22
+
23
+ import { renderAdornment, statusPropsMapper } from './utils';
24
+ import Status from './components/Status';
25
+ import styles from './styles';
26
+
27
+ const Header = ({
28
+ adornment,
29
+ classNames,
30
+ description,
31
+ descriptionProps,
32
+ headerActions,
33
+ headerActionsProps,
34
+ setMainActionHoverVisible,
35
+ size,
36
+ status,
37
+ statusAlignment,
38
+ statusLabel,
39
+ title,
40
+ titleProps
41
+ }) => {
42
+ const theme = useTheme();
43
+
44
+ return (
45
+ <View style={[styles.header, styles[size], classNames.header]}>
46
+ {renderAdornment(adornment, 'left', size)}
47
+ <View style={styles.headerTitles}>
48
+ {renderAdornment(adornment, 'top', size)}
49
+ <UTLabel variant="title3" weight="medium" {...titleProps}>
50
+ {title}
51
+ </UTLabel>
52
+ {description && (
53
+ <UTLabel colorTheme="gray" {...descriptionProps}>
54
+ {description}
55
+ </UTLabel>
56
+ )}
57
+ </View>
58
+ {status ? (
59
+ <Status
60
+ {...statusPropsMapper(status, theme)}
61
+ alignment={statusAlignment}
62
+ value={statusLabel || status}
63
+ />
64
+ ) : null}
65
+ {!isEmpty(headerActions) && (
66
+ <HeaderActions {...{ headerActions, headerActionsProps, setMainActionHoverVisible }} />
67
+ )}
68
+ </View>
69
+ );
70
+ };
71
+
72
+ Header.propTypes = {
73
+ adornment: shape({ position: string, props: object, type: string }),
74
+ classNames: objectOf(string),
75
+ description: string,
76
+ descriptionProps: shape({ colorTheme: string, variant: string }),
77
+ headerActions: arrayOf(
78
+ shape({
79
+ Icon: oneOfType([elementType, string]),
80
+ id: oneOfType([number, string]),
81
+ isPrimary: bool,
82
+ label: string,
83
+ loading: bool,
84
+ onPress: func
85
+ })
86
+ ),
87
+ headerActionsProps: shape({
88
+ alignment: oneOf([PLACE_SELF_TYPES.CENTER, PLACE_SELF_TYPES.END, PLACE_SELF_TYPES.START]),
89
+ buttonGroupProps: shape({ colorTheme: string, shape: string }),
90
+ variant: oneOf([HEADER_ACTIONS_VARIANTS.DEFAULT, HEADER_ACTIONS_VARIANTS.BUTTON_GROUP])
91
+ }),
92
+ setMainActionHoverVisible: string,
93
+ size: oneOf([SIZES.MEDIUM, SIZES.SMALL]),
94
+ status: string,
95
+ statusAlignment: oneOf([PLACE_SELF_TYPES.CENTER, PLACE_SELF_TYPES.END, PLACE_SELF_TYPES.START]),
96
+ statusLabel: string,
97
+ title: string,
98
+ titleProps: shape({ variant: string, weight: string })
99
+ };
100
+
101
+ export default memo(Header);
@@ -0,0 +1,30 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ header: {
5
+ flexDirection: 'row',
6
+ gap: 16,
7
+ justifyContent: 'space-between'
8
+ },
9
+ headerTitles: {
10
+ flex: 1,
11
+ flexDirection: 'column',
12
+ gap: 8,
13
+ marginRight: 'auto'
14
+ },
15
+ medium: {
16
+ padding: 24
17
+ },
18
+ small: {
19
+ padding: 16
20
+ },
21
+ alignSelf_center: {
22
+ alignSelf: 'center'
23
+ },
24
+ alignSelf_start: {
25
+ alignSelf: 'flex-start'
26
+ },
27
+ alignSelf_end: {
28
+ alignSelf: 'flex-end'
29
+ }
30
+ });
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import { isEmpty } from 'lodash';
3
+
4
+ import { PLACE_SELF_TYPES } from '../../constants';
5
+
6
+ import { ADORNMENT_COMPONENT_MAPPER } from './constants';
7
+ import styles from './styles';
8
+
9
+ const ACTIVE = 'active';
10
+ const APPROVED = 'approved';
11
+ const CHARGED_BILL = 'factura cobrada';
12
+ const FINISHED = 'finished';
13
+ const INACTIVE = 'inactive';
14
+ const PENDING = 'pending';
15
+ const BILLED = 'cobrada';
16
+ const PAID = 'pagado';
17
+ const ABOUT_TO_EXPIRE = 'por vencer';
18
+ const UNPAID = 'impaga';
19
+ const SUBSCRIBED = 'subscribed_from_utility';
20
+ const UNSUBSCRIBED = 'unsubscribed';
21
+ const CLOSED_STATUS = 'completed';
22
+ const OBSERVED_STATUS = 'observed';
23
+ const ONGOING_STATUS = 'pending';
24
+ const FORM_STATUS = {
25
+ CANCELED: 'cancelled',
26
+ FINISHED: 'finished',
27
+ IN_PROGRESS: 'in_progress',
28
+ ON_HOLD: 'on_hold',
29
+ COMPLETED: 'completed'
30
+ };
31
+ const PAYMENTPLAN_STATUS = {
32
+ ACTIVE: 'active',
33
+ CANCELLED: 'canceled',
34
+ DRAFT: 'draft',
35
+ FINISHED: 'finished',
36
+ PENDING: 'pending',
37
+ PAYMENT_IN_PROGRESS: 'payment_in_progress',
38
+ PENDING_OFFLINE_PAYMENT: 'pending_offline_payment',
39
+ PENDING_PAYMENT: 'pending_payment'
40
+ };
41
+
42
+ export const statusPropsMapper = (status, theme) => {
43
+ const paletteVariantMap = {
44
+ success: [ACTIVE, APPROVED, BILLED, CHARGED_BILL, FINISHED, PAID, SUBSCRIBED],
45
+ error: [INACTIVE, PAYMENTPLAN_STATUS.CANCELLED, UNSUBSCRIBED, FORM_STATUS.CANCELED],
46
+ warning: [
47
+ OBSERVED_STATUS,
48
+ PAYMENTPLAN_STATUS.PAYMENT_IN_PROGRESS,
49
+ PAYMENTPLAN_STATUS.PENDING_OFFLINE_PAYMENT,
50
+ PAYMENTPLAN_STATUS.PENDING_PAYMENT,
51
+ UNPAID,
52
+ FORM_STATUS.ON_HOLD
53
+ ],
54
+ information: [ONGOING_STATUS, PENDING, FORM_STATUS.IN_PROGRESS],
55
+ unassigned: [CLOSED_STATUS, PAYMENTPLAN_STATUS.DRAFT, ABOUT_TO_EXPIRE]
56
+ };
57
+
58
+ const variant =
59
+ Object.entries(paletteVariantMap).find(([, statuses]) => statuses.includes(status?.toLowerCase()))?.[0] ||
60
+ 'error';
61
+
62
+ return { backgroundColor: theme.Palette[variant]['01'] };
63
+ };
64
+
65
+ export const renderAdornment = (adornment, position, size) => {
66
+ if (isEmpty(adornment) || adornment.position !== position) return null;
67
+ const AdornmentComponent = ADORNMENT_COMPONENT_MAPPER[adornment.type];
68
+ const defaultPlaceSelf = adornment.position === 'left' ? PLACE_SELF_TYPES.CENTER : PLACE_SELF_TYPES.START;
69
+
70
+ return (
71
+ <AdornmentComponent
72
+ style={[
73
+ styles[`alignSelf_${adornment.alignment || defaultPlaceSelf}`],
74
+ position === 'top' && styles[`gap_${size}`],
75
+ adornment.type === 'image' && { width: adornment.props?.width, height: adornment.props?.height }
76
+ ]}
77
+ {...(adornment.props || {})}
78
+ />
79
+ );
80
+ };
@@ -0,0 +1,80 @@
1
+ import React, { memo, useState } from 'react';
2
+ import { View } from 'react-native';
3
+ import isEmpty from 'lodash/isEmpty';
4
+ import { arrayOf, bool, elementType, func, number, oneOf, oneOfType, shape, string } from 'prop-types';
5
+
6
+ import UTButton from '../../../UTButton';
7
+ import UTButtonGroup from '../../../UTButtonGroup';
8
+ import UTMenu from '../../../UTMenu';
9
+ import UTIcon from '../../../UTIcon';
10
+ import { HEADER_ACTIONS_VARIANTS, PLACE_SELF_TYPES } from '../../constants';
11
+
12
+ import { processActions } from './utils';
13
+ import styles from './styles';
14
+
15
+ const HeaderActions = ({ headerActions, headerActionsProps }) => {
16
+ const [selectedAction, setSelectedAction] = useState(null);
17
+ const showButtonGroup = headerActionsProps.variant === HEADER_ACTIONS_VARIANTS.BUTTON_GROUP;
18
+ const { colorTheme: buttonGroupColorTheme = 'secondary', shape: buttonGroupShape } =
19
+ headerActionsProps?.buttonGroupProps || {};
20
+
21
+ const { primaryActions, secondaryActions } = processActions(headerActions);
22
+
23
+ return (
24
+ <View style={[styles.headerActionsContainer, styles[`alignSelf_${headerActionsProps.alignment}`]]}>
25
+ {primaryActions.map((buttonProps, i) =>
26
+ !buttonProps.onPress ? (
27
+ <UTIcon
28
+ name={buttonProps.Icon}
29
+ style={styles.notClickableIconContainer}
30
+ {...buttonProps}
31
+ key={buttonProps?.id || i}
32
+ />
33
+ ) : (
34
+ <UTButton size="medium" variant="text" {...buttonProps} key={buttonProps?.id || i} />
35
+ )
36
+ )}
37
+ {!isEmpty(secondaryActions) &&
38
+ (!showButtonGroup ? (
39
+ <View>
40
+ <UTMenu options={secondaryActions} horizontalOffset={-80}>
41
+ <UTIcon name="IconDots" />
42
+ </UTMenu>
43
+ </View>
44
+ ) : (
45
+ <UTButtonGroup
46
+ actions={secondaryActions.map(action => ({
47
+ ...action,
48
+ onPress: () => {
49
+ setSelectedAction(action.id);
50
+ action.onPress();
51
+ }
52
+ }))}
53
+ colorTheme={buttonGroupColorTheme}
54
+ selected={selectedAction || secondaryActions[0]?.id}
55
+ shape={buttonGroupShape}
56
+ />
57
+ ))}
58
+ </View>
59
+ );
60
+ };
61
+
62
+ HeaderActions.propTypes = {
63
+ headerActions: arrayOf(
64
+ shape({
65
+ Icon: oneOfType([elementType, string]),
66
+ id: oneOfType([number, string]),
67
+ isPrimary: bool,
68
+ label: string,
69
+ loading: bool,
70
+ onPress: func
71
+ })
72
+ ),
73
+ headerActionsProps: shape({
74
+ alignment: oneOf([PLACE_SELF_TYPES.CENTER, PLACE_SELF_TYPES.END, PLACE_SELF_TYPES.START]),
75
+ buttonGroupProps: shape({ colorTheme: string, shape: string }),
76
+ variant: oneOf([HEADER_ACTIONS_VARIANTS.DEFAULT, HEADER_ACTIONS_VARIANTS.BUTTON_GROUP])
77
+ })
78
+ };
79
+
80
+ export default memo(HeaderActions);
@@ -0,0 +1,21 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ headerActionsContainer: {
5
+ alignItems: 'center',
6
+ flexDirection: 'row',
7
+ gap: 8
8
+ },
9
+ alignSelf_center: {
10
+ alignSelf: 'center'
11
+ },
12
+ alignSelf_start: {
13
+ alignSelf: 'flex-start'
14
+ },
15
+ alignSelf_end: {
16
+ alignSelf: 'flex-end'
17
+ },
18
+ notClickableIconContainer: {
19
+ padding: 8
20
+ }
21
+ });
@@ -0,0 +1,17 @@
1
+ import { isEmpty } from 'lodash';
2
+
3
+ const mapAction = action => ({
4
+ ...action,
5
+ action: () => {
6
+ action?.onPress?.();
7
+ }
8
+ });
9
+
10
+ export const processActions = headerActions =>
11
+ headerActions.reduce(
12
+ (final, current) => {
13
+ if (current.isPrimary && isEmpty(final.primaryActions)) return { ...final, primaryActions: [current] };
14
+ return { ...final, secondaryActions: [...final.secondaryActions, mapAction(current)] };
15
+ },
16
+ { primaryActions: [], secondaryActions: [] }
17
+ );
@@ -0,0 +1,20 @@
1
+ export const SIZES = {
2
+ MEDIUM: 'medium',
3
+ SMALL: 'small'
4
+ };
5
+
6
+ export const ACTION_TYPES = {
7
+ DEFAULT: 'default',
8
+ REDIRECTION: 'redirection'
9
+ };
10
+
11
+ export const HEADER_ACTIONS_VARIANTS = {
12
+ BUTTON_GROUP: 'buttonGroup',
13
+ DEFAULT: 'default'
14
+ };
15
+
16
+ export const PLACE_SELF_TYPES = {
17
+ CENTER: 'center',
18
+ END: 'end',
19
+ START: 'start'
20
+ };
@@ -0,0 +1,157 @@
1
+ import React, { memo } from 'react';
2
+ import { View } from 'react-native';
3
+ import {
4
+ arrayOf,
5
+ bool,
6
+ elementType,
7
+ func,
8
+ number,
9
+ object,
10
+ objectOf,
11
+ oneOf,
12
+ oneOfType,
13
+ shape,
14
+ string
15
+ } from 'prop-types';
16
+ import { isEmpty } from 'lodash';
17
+
18
+ import Surface from '../Surface';
19
+ import Touchable from '../Touchable';
20
+
21
+ import { ACTION_TYPES, HEADER_ACTIONS_VARIANTS, PLACE_SELF_TYPES, SIZES } from './constants';
22
+ import Header from './components/Header';
23
+ import AdditionalMessage from './components/AdditionalMessage';
24
+ import BottomActions from './components/BottomActions';
25
+ import styles from './styles';
26
+
27
+ const UTActionCard = ({
28
+ additionalMessage,
29
+ adornment,
30
+ backgroundHeight = '100%',
31
+ BackgroundImage,
32
+ backgroundWidth = '100%',
33
+ bottomActions,
34
+ bottomActionsVariant,
35
+ children,
36
+ classNames = {},
37
+ description,
38
+ descriptionProps = {},
39
+ headerActions,
40
+ headerActionsProps = { variant: HEADER_ACTIONS_VARIANTS.DEFAULT },
41
+ mainAction,
42
+ size = SIZES.SMALL,
43
+ status,
44
+ statusAlignment,
45
+ statusLabel,
46
+ title,
47
+ titleProps = {},
48
+ withBodyPadding = true
49
+ }) => {
50
+ return (
51
+ <Surface elevation={1} style={[styles.cardContainer, classNames.container]}>
52
+ <Touchable
53
+ onPress={mainAction && (() => mainAction?.())}
54
+ style={[styles.innerContainer, classNames.innerContainer, mainAction && styles.withMainAction]}
55
+ >
56
+ <View>
57
+ {BackgroundImage && (
58
+ <BackgroundImage
59
+ height={backgroundHeight}
60
+ style={[styles.backgroundImage, classNames.backgroundImage]}
61
+ width={backgroundWidth}
62
+ />
63
+ )}
64
+ <View style={[styles.headerAndChildrenContainer, classNames.headerAndChildrenContainer]}>
65
+ <Header
66
+ {...{
67
+ adornment,
68
+ classNames,
69
+ description,
70
+ descriptionProps,
71
+ headerActions,
72
+ headerActionsProps,
73
+ mainAction,
74
+ size,
75
+ status,
76
+ statusAlignment,
77
+ statusLabel,
78
+ title,
79
+ titleProps
80
+ }}
81
+ />
82
+ {children && (
83
+ <View
84
+ style={[
85
+ withBodyPadding ? styles[`bodyPadding_${size}`] : styles[`withoutBodyPadding_${size}`]
86
+ ]}
87
+ >
88
+ {children}
89
+ </View>
90
+ )}
91
+ </View>
92
+ </View>
93
+ </Touchable>
94
+ {!isEmpty(additionalMessage) && <AdditionalMessage {...additionalMessage} size={size} />}
95
+ {!isEmpty(bottomActions) && (
96
+ <BottomActions actions={bottomActions} bottomActionsVariant={bottomActionsVariant} />
97
+ )}
98
+ </Surface>
99
+ );
100
+ };
101
+
102
+ UTActionCard.propTypes = {
103
+ additionalMessage: shape({
104
+ Icon: elementType,
105
+ iconProps: shape({ colorTheme: string, size: string }),
106
+ labelProps: shape({ colorTheme: string, variant: string }),
107
+ message: string
108
+ }),
109
+ adornment: shape({
110
+ alignment: oneOf([PLACE_SELF_TYPES.CENTER, PLACE_SELF_TYPES.END, PLACE_SELF_TYPES.START]),
111
+ position: string,
112
+ props: object,
113
+ type: string
114
+ }),
115
+ backgroundHeight: oneOfType([string, number]),
116
+ BackgroundImage: elementType,
117
+ backgroundWidth: oneOfType([string, number]),
118
+ bottomActions: arrayOf(
119
+ shape({
120
+ colorTheme: string,
121
+ disabled: bool,
122
+ Icon: elementType,
123
+ label: string,
124
+ loading: bool,
125
+ onClick: func
126
+ })
127
+ ),
128
+ bottomActionsVariant: oneOf([ACTION_TYPES.DEFAULT, ACTION_TYPES.REDIRECTION]),
129
+ classNames: objectOf(string),
130
+ description: string,
131
+ descriptionProps: shape({ colorTheme: string, variant: string }),
132
+ headerActions: arrayOf(
133
+ shape({
134
+ Icon: oneOfType([elementType, string]),
135
+ id: oneOfType([number, string]),
136
+ isPrimary: bool,
137
+ label: string,
138
+ loading: bool,
139
+ onPress: func
140
+ })
141
+ ),
142
+ headerActionsProps: shape({
143
+ alignment: oneOf([PLACE_SELF_TYPES.CENTER, PLACE_SELF_TYPES.END, PLACE_SELF_TYPES.START]),
144
+ buttonGroupProps: shape({ colorTheme: string, shape: string }),
145
+ variant: oneOf([HEADER_ACTIONS_VARIANTS.DEFAULT, HEADER_ACTIONS_VARIANTS.BUTTON_GROUP])
146
+ }),
147
+ mainAction: func,
148
+ size: oneOf([SIZES.MEDIUM, SIZES.SMALL]),
149
+ status: string,
150
+ statusLabel: string,
151
+ statusAlignment: oneOf([PLACE_SELF_TYPES.CENTER, PLACE_SELF_TYPES.END, PLACE_SELF_TYPES.START]),
152
+ title: string,
153
+ titleProps: shape({ variant: string, weight: string }),
154
+ withBodyPadding: bool
155
+ };
156
+
157
+ export default memo(UTActionCard);
@@ -0,0 +1,39 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ cardContainer: {
5
+ borderRadius: 8,
6
+ overflow: 'hidden'
7
+ },
8
+ innerContainer: {
9
+ position: 'relative',
10
+ width: '100%'
11
+ },
12
+ backgroundImage: {
13
+ position: 'absolute',
14
+ zIndex: 0
15
+ },
16
+ headerAndChildrenContainer: {
17
+ flexDirection: 'column',
18
+ width: '100%',
19
+ zIndex: 1
20
+ },
21
+ bodyPadding_medium: {
22
+ paddingTop: 0,
23
+ paddingLeft: 24,
24
+ paddingRight: 24,
25
+ paddingBottom: 24
26
+ },
27
+ bodyPadding_small: {
28
+ paddingTop: 0,
29
+ paddingLeft: 16,
30
+ paddingRight: 16,
31
+ paddingBottom: 16
32
+ },
33
+ withoutBodyPadding_small: {
34
+ paddingBottom: 16
35
+ },
36
+ withoutBodyPadding_medium: {
37
+ paddingBottom: 24
38
+ }
39
+ });
@@ -40,13 +40,14 @@ const UTIcon = ({
40
40
 
41
41
  return (
42
42
  <View
43
- style={
43
+ style={[
44
44
  area && [
45
45
  ownStyles.withArea,
46
46
  ownStyles[`size${size}`],
47
47
  getVariantStyle({ color: colorTheme || color, shade, theme, variant })
48
- ]
49
- }
48
+ ],
49
+ iconProps.style
50
+ ]}
50
51
  >
51
52
  <IconComponent {...iconProps} />
52
53
  </View>
@@ -1,9 +1,9 @@
1
1
  export const VARIANTS_SIZES = {
2
- title1: 30,
3
- title2: 24,
4
- title3: 22,
5
- subtitle1: 18,
6
- subtitle2: 16,
2
+ title1: 24,
3
+ title2: 22,
4
+ title3: 18,
5
+ subtitle1: 16,
6
+ subtitle2: 14,
7
7
  body: 16,
8
8
  small: 14,
9
9
  xsmall: 11,
@@ -2,7 +2,7 @@ import { arrayOf, bool, func, number, oneOfType, shape, string } from 'prop-type
2
2
 
3
3
  const optionType = shape({
4
4
  label: string.isRequired,
5
- id: string.isRequired,
5
+ id: oneOfType([number, string]).isRequired,
6
6
  action: func,
7
7
  value: oneOfType([number, string])
8
8
  });
@@ -38,7 +38,7 @@ const UTWorkflowContainer = ({
38
38
  const Title = React.isValidElement(title) ? (
39
39
  title
40
40
  ) : (
41
- <Label big bold {...titleProps} style={themedStyles.title}>
41
+ <Label big {...titleProps} style={themedStyles.title}>
42
42
  {title}
43
43
  </Label>
44
44
  );
package/lib/index.js CHANGED
@@ -35,6 +35,7 @@ export { default as Touchable } from './components/Touchable';
35
35
  export { default as TransformView } from './components/TransformView';
36
36
  export { default as TransitionText } from './components/TransitionText';
37
37
  export { default as TransitionView } from './components/TransitionView';
38
+ export { default as UTActionCard } from './components/UTActionCard';
38
39
  export { default as UTAutocomplete } from './components/UTAutocomplete';
39
40
  export { default as UTBadge } from './components/UTBadge';
40
41
  export { default as UTBottomSheet } from './components/UTBottomSheet';
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@widergy/mobile-ui",
3
3
  "description": "Widergy Mobile Components",
4
4
  "author": "widergy",
5
- "version": "1.26.4",
5
+ "version": "1.27.0",
6
6
  "repository": "https://github.com/widergy/mobile-ui.git",
7
7
  "main": "lib/index.js",
8
8
  "files": [