@widergy/mobile-ui 1.23.1 → 1.24.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [1.24.0](https://github.com/widergy/mobile-ui/compare/v1.23.2...v1.24.0) (2024-09-17)
2
+
3
+
4
+ ### Features
5
+
6
+ * ut button group ([#356](https://github.com/widergy/mobile-ui/issues/356)) ([ebdb6a2](https://github.com/widergy/mobile-ui/commit/ebdb6a2d69ee182f2a1794aa7bb55eea6ce8e261))
7
+
8
+ ## [1.23.2](https://github.com/widergy/mobile-ui/compare/v1.23.1...v1.23.2) (2024-09-12)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * fix input bug ([#355](https://github.com/widergy/mobile-ui/issues/355)) ([82869dd](https://github.com/widergy/mobile-ui/commit/82869ddabb526824d2fd2c6da0f86bf0c8fca92d))
14
+
1
15
  ## [1.23.1](https://github.com/widergy/mobile-ui/compare/v1.23.0...v1.23.1) (2024-09-04)
2
16
 
3
17
 
@@ -5,6 +5,12 @@ import PrefixAdornment from './components/PrefixAdornment';
5
5
  import SuffixAdornment from './components/SuffixAdornment';
6
6
  import TooltipAdornment from './components/TooltipAdornment';
7
7
 
8
+ export const DEBOUNCE_DELAY = 150;
9
+ export const DEBOUNCE_CONFIG = {
10
+ leading: false,
11
+ trailing: true
12
+ };
13
+
8
14
  export const TYPES = {
9
15
  EMAIL: 'email',
10
16
  NUMBER: 'number',
@@ -4,16 +4,19 @@ import React, {
4
4
  useEffect,
5
5
  useImperativeHandle,
6
6
  useLayoutEffect,
7
+ useMemo,
7
8
  useRef,
8
9
  useState
9
10
  } from 'react';
10
11
  import { View, TextInput } from 'react-native';
12
+ import debounce from 'lodash/debounce';
11
13
 
12
14
  import { useTheme } from '../../theming';
13
15
  import UTLabel from '../UTLabel';
14
16
 
17
+ import { COMPONENTS_MAPPER, DEBOUNCE_CONFIG, DEBOUNCE_DELAY } from './constants';
15
18
  import { defaultProps, propTypes } from './proptypes';
16
- import { KEYBOARD_BY_TYPE, TYPES, COMPONENTS_MAPPER } from './constants';
19
+ import { getPropsByType } from './utils';
17
20
  import { LINE_HEIGHT, retrieveStyle } from './theme';
18
21
 
19
22
  const UTBaseInputField = forwardRef(
@@ -45,7 +48,6 @@ const UTBaseInputField = forwardRef(
45
48
  },
46
49
  ref
47
50
  ) => {
48
- const [displayValue, setDisplayValue] = useState(value);
49
51
  const [focused, setFocused] = useState(false);
50
52
  const inputWidthRef = useRef(null);
51
53
  const inputRef = useRef(null);
@@ -53,35 +55,55 @@ const UTBaseInputField = forwardRef(
53
55
  const theme = useTheme();
54
56
  const multiline = maxRows > 1;
55
57
 
58
+ const { secureTextEntry, autoCapitalize, keyboardType } = getPropsByType(type);
59
+
60
+ const setNativeText = useMemo(
61
+ () => debounce(text => inputRef.current?.setNativeProps({ text }), DEBOUNCE_DELAY, DEBOUNCE_CONFIG),
62
+ []
63
+ );
64
+
56
65
  const handleFocus = useCallback(
57
66
  event => {
58
67
  setFocused(true);
59
- if (!multiline) setDisplayValue(value);
68
+ setNativeText(value);
60
69
  onFocus?.(event);
61
70
  },
62
- [onFocus, value, multiline]
71
+ [onFocus, value, setNativeText]
63
72
  );
64
73
 
65
- const truncateText = (text, width) => {
66
- const currentWidth = width || inputWidthRef.current;
67
- if (!currentWidth || !text) return text;
68
- const charWidth = 8;
69
- const maxCharsPerLine = Math.floor(currentWidth / charWidth);
70
- return text.length > maxCharsPerLine ? `${text.substring(0, maxCharsPerLine - 3)}...` : text;
71
- };
74
+ const truncateText = useCallback(
75
+ (text, width) => {
76
+ if (multiline) return text;
77
+ const currentWidth = width || inputWidthRef.current;
78
+ if (!currentWidth || !text) return text;
79
+ const charWidth = 8;
80
+ const maxCharsPerLine = Math.floor(currentWidth / charWidth);
81
+ return text.length > maxCharsPerLine ? `${text.substring(0, maxCharsPerLine - 3)}...` : text;
82
+ },
83
+ [multiline, inputWidthRef]
84
+ );
85
+
86
+ useEffect(() => {
87
+ if (!focused && !multiline) {
88
+ const truncated = truncateText(value, inputWidthRef.current);
89
+ setNativeText(truncated);
90
+ } else {
91
+ setNativeText(value);
92
+ }
93
+ }, [value, focused, multiline, setNativeText, truncateText]);
72
94
 
73
95
  useLayoutEffect(() => {
74
96
  if (inputRef.current) {
75
97
  inputRef.current.measure((_x, _y, width) => {
76
98
  inputWidthRef.current = width;
77
- setDisplayValue(truncateText(value, width));
78
99
  });
79
100
  }
80
- }, [value]);
101
+ }, [value, focused]);
81
102
 
82
103
  const handleBlur = event => {
83
104
  setFocused(false);
84
- if (!multiline) setDisplayValue(truncateText(value));
105
+ const truncated = truncateText(value, inputWidthRef.current);
106
+ setNativeText(truncated);
85
107
  onBlur?.(event);
86
108
  };
87
109
 
@@ -92,26 +114,27 @@ const UTBaseInputField = forwardRef(
92
114
  onSubmitEditing?.(event);
93
115
  };
94
116
 
95
- const { actionStyle, containerStyle, inputRowStyle, inputStyle, textLengthRowStyle } = retrieveStyle({
96
- disabled: disabled && !readOnly,
97
- error,
98
- focused,
99
- inputSize,
100
- maxRows,
101
- multiline,
102
- readOnly,
103
- style,
104
- theme,
105
- variant
106
- });
107
-
108
- useEffect(() => {
109
- setDisplayValue(value);
110
- }, [value]);
117
+ const { actionStyle, containerStyle, inputRowStyle, inputStyle, textLengthRowStyle } = useMemo(
118
+ () =>
119
+ retrieveStyle({
120
+ disabled: disabled && !readOnly,
121
+ error,
122
+ focused,
123
+ inputSize,
124
+ maxRows,
125
+ multiline,
126
+ readOnly,
127
+ style,
128
+ theme,
129
+ variant
130
+ }),
131
+ [disabled, readOnly, error, focused, inputSize, maxRows, multiline, style, theme, variant]
132
+ );
111
133
 
112
134
  useImperativeHandle(ref, () => ({
113
135
  truncate: () => {
114
- setDisplayValue(truncateText(value));
136
+ const truncated = truncateText(value, inputWidthRef.current);
137
+ setNativeText(truncated);
115
138
  },
116
139
  focus: () => {
117
140
  if (inputRef.current) {
@@ -126,10 +149,6 @@ const UTBaseInputField = forwardRef(
126
149
  ...inputRef.current
127
150
  }));
128
151
 
129
- const secureTextEntry = [TYPES.PASSWORD, TYPES.PASSWORD_NUMERIC].includes(type);
130
- const autoCapitalize = type === TYPES.EMAIL ? 'none' : 'sentences';
131
- const keyboardType = KEYBOARD_BY_TYPE[type] || 'default';
132
-
133
152
  const renderElement = useCallback(
134
153
  element => {
135
154
  const Component = COMPONENTS_MAPPER[element.name];
@@ -160,15 +179,16 @@ const UTBaseInputField = forwardRef(
160
179
  <TextInput
161
180
  autoCapitalize={autoCapitalize}
162
181
  autoCorrect={false}
182
+ defaultValue={value}
163
183
  editable={!disabled && !readOnly && editable}
164
184
  id={id ? `${id}` : undefined}
165
185
  keyboardType={keyboardType}
166
186
  maxLength={maxLength}
167
187
  multiline={multiline}
168
188
  numberOfLines={maxRows}
169
- onChangeText={onChange}
170
- onEndEditing={handleBlur}
171
189
  onBlur={handleBlur}
190
+ onChangeText={text => setNativeText(value) || onChange(text)}
191
+ onEndEditing={handleBlur}
172
192
  onFocus={handleFocus}
173
193
  onSubmitEditing={handleSubmitEditing}
174
194
  placeholder={!focused && !alwaysShowPlaceholder ? '' : placeholder}
@@ -180,7 +200,6 @@ const UTBaseInputField = forwardRef(
180
200
  selectionColor={inputStyle.selectionColor}
181
201
  style={inputStyle.root}
182
202
  type={type}
183
- value={focused || multiline ? value : displayValue}
184
203
  />
185
204
  {rightAdornments.map(renderElement)}
186
205
  </View>
@@ -0,0 +1,13 @@
1
+ import { KEYBOARD_BY_TYPE, TYPES } from './constants';
2
+
3
+ export const getPropsByType = type => {
4
+ const autoCapitalize = type === TYPES.EMAIL ? 'none' : 'sentences';
5
+ const keyboardType = KEYBOARD_BY_TYPE[type] || 'default';
6
+ const secureTextEntry = [TYPES.PASSWORD, TYPES.PASSWORD_NUMERIC].includes(type);
7
+
8
+ return {
9
+ autoCapitalize,
10
+ keyboardType,
11
+ secureTextEntry
12
+ };
13
+ };
@@ -0,0 +1,11 @@
1
+ # UTButton
2
+
3
+ ## Props
4
+
5
+
6
+ | Name | Type | Default | Description |
7
+ | ------------ | :---------------- | ----------- | ---------------------------------------------------------------------------------------- |
8
+ | actions | array | | Array of actions to render. Each action must include:`Icon`, `id` and `onPress` props. |
9
+ | colorTheme | string | 'primary' | The color theme to use. One of:`primary`, `secondary`, `negative`. |
10
+ | type | string | 'square' | Type of the button. One of:`square`, `circle`. |
11
+ | selected | string / number | | Id of the active button. |
@@ -0,0 +1,18 @@
1
+ export const BACKGROUND_COLOR_MAPPER = theme => {
2
+ const negativeTheme = theme.Palette.negative;
3
+ const lightTheme = theme.Palette.light;
4
+
5
+ const lightBackground = { backgroundColor: lightTheme['03'] };
6
+ const negativeBackground = { backgroundColor: negativeTheme['02'] };
7
+
8
+ return {
9
+ primary: lightBackground,
10
+ negative: negativeBackground,
11
+ neutral: lightBackground
12
+ };
13
+ };
14
+
15
+ export const CIRCLE_TYPE = 'circle';
16
+ export const SQUARE_TYPE = 'square';
17
+ export const DEFAULT_TYPE = SQUARE_TYPE;
18
+ export const DEFAULT_COLOR_THEME = 'primary';
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { View } from 'react-native';
3
+ import { arrayOf, element, func, number, oneOfType, shape, string } from 'prop-types';
4
+
5
+ import { useTheme } from '../../theming';
6
+ import { mergeMultipleStyles } from '../../utils/styleUtils';
7
+ import UTButton from '../UTButton';
8
+
9
+ import { BACKGROUND_COLOR_MAPPER, DEFAULT_COLOR_THEME, DEFAULT_TYPE } from './constants';
10
+ import { getTypeStyles, ownStyles } from './styles';
11
+
12
+ const UTButtonGroup = ({ actions, colorTheme = DEFAULT_COLOR_THEME, selected, type = DEFAULT_TYPE }) => {
13
+ const theme = useTheme();
14
+ const backgroundColor =
15
+ BACKGROUND_COLOR_MAPPER(theme)[colorTheme] || BACKGROUND_COLOR_MAPPER(theme).primary;
16
+ const themedStyles = mergeMultipleStyles(ownStyles, getTypeStyles(type), theme?.UTButtonGroup);
17
+
18
+ return (
19
+ <View style={[themedStyles.container, backgroundColor]}>
20
+ {actions.map(({ Icon, id, onPress }, index) => (
21
+ <UTButton
22
+ colorTheme={colorTheme}
23
+ Icon={Icon}
24
+ key={id}
25
+ onPress={onPress}
26
+ size="large"
27
+ style={{
28
+ root: {
29
+ ...themedStyles.buttonRoot,
30
+ ...(index === 0 ? themedStyles.firstButton : {}),
31
+ ...(index === actions.length - 1 ? themedStyles.lastButton : {}),
32
+ ...themedStyles.button
33
+ }
34
+ }}
35
+ variant={selected === id ? 'filled' : 'text'}
36
+ />
37
+ ))}
38
+ </View>
39
+ );
40
+ };
41
+
42
+ UTButtonGroup.propTypes = {
43
+ actions: arrayOf(
44
+ shape({
45
+ Icon: oneOfType([string, element]),
46
+ id: oneOfType([number, string]),
47
+ onClick: func
48
+ })
49
+ ).isRequired,
50
+ colorTheme: string,
51
+ selected: oneOfType([number, string]).isRequired,
52
+ type: string
53
+ };
54
+
55
+ export default UTButtonGroup;
@@ -0,0 +1,41 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ import { CIRCLE_TYPE, SQUARE_TYPE } from './constants';
4
+
5
+ const BORDER_RADIUS_DEFAULT = 8;
6
+ const BORDER_RADIUS_SQUARE = BORDER_RADIUS_DEFAULT - 1;
7
+ const BORDER_RADIUS_ROUNDED = 100;
8
+
9
+ export const getTypeStyles = type =>
10
+ ({
11
+ [CIRCLE_TYPE]: {
12
+ container: {
13
+ borderRadius: BORDER_RADIUS_ROUNDED
14
+ },
15
+ button: {
16
+ borderRadius: BORDER_RADIUS_ROUNDED
17
+ }
18
+ },
19
+ [SQUARE_TYPE]: {
20
+ container: {
21
+ borderRadius: BORDER_RADIUS_DEFAULT
22
+ },
23
+ firstButton: {
24
+ borderTopLeftRadius: BORDER_RADIUS_SQUARE,
25
+ borderBottomLeftRadius: BORDER_RADIUS_SQUARE
26
+ },
27
+ lastButton: {
28
+ borderTopRightRadius: BORDER_RADIUS_SQUARE,
29
+ borderBottomRightRadius: BORDER_RADIUS_SQUARE
30
+ }
31
+ }
32
+ })[type];
33
+
34
+ export const ownStyles = StyleSheet.create({
35
+ container: {
36
+ flexDirection: 'row'
37
+ },
38
+ buttonRoot: {
39
+ borderRadius: 0
40
+ }
41
+ });
package/lib/index.js CHANGED
@@ -39,6 +39,7 @@ export { default as UTAutocomplete } from './components/UTAutocomplete';
39
39
  export { default as UTBadge } from './components/UTBadge';
40
40
  export { default as UTBottomSheet } from './components/UTBottomSheet';
41
41
  export { default as UTButton } from './components/UTButton';
42
+ export { default as UTButtonGroup } from './components/UTButtonGroup';
42
43
  export { default as UTCBUInput } from './components/UTCBUInput';
43
44
  export { default as UTCheckBox } from './components/UTCheckBox';
44
45
  export { default as UTCheckList } from './components/UTCheckList';
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.23.1",
5
+ "version": "1.24.0",
6
6
  "repository": "https://github.com/widergy/mobile-ui.git",
7
7
  "main": "lib/index.js",
8
8
  "files": [