@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 +14 -0
- package/lib/components/UTBaseInputField/constants.js +6 -0
- package/lib/components/UTBaseInputField/index.js +57 -38
- package/lib/components/UTBaseInputField/utils.js +13 -0
- package/lib/components/UTButtonGroup/README.md +11 -0
- package/lib/components/UTButtonGroup/constants.js +18 -0
- package/lib/components/UTButtonGroup/index.js +55 -0
- package/lib/components/UTButtonGroup/styles.js +41 -0
- package/lib/index.js +1 -0
- package/package.json +1 -1
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 {
|
|
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
|
-
|
|
68
|
+
setNativeText(value);
|
|
60
69
|
onFocus?.(event);
|
|
61
70
|
},
|
|
62
|
-
[onFocus, value,
|
|
71
|
+
[onFocus, value, setNativeText]
|
|
63
72
|
);
|
|
64
73
|
|
|
65
|
-
const truncateText = (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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 } =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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