react-native-molecules 0.5.0-beta.7 → 0.5.0-beta.8
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/components/ActivityIndicator/ActivityIndicator.tsx +6 -15
- package/components/Button/Button.tsx +206 -246
- package/components/Button/index.tsx +9 -3
- package/components/Button/types.ts +16 -2
- package/components/Button/utils.ts +230 -207
- package/components/LoadingIndicator/LoadingIndicator.tsx +253 -0
- package/components/LoadingIndicator/LoadingIndicator.web.tsx +136 -0
- package/components/LoadingIndicator/index.tsx +13 -0
- package/components/LoadingIndicator/utils.ts +117 -0
- package/hooks/useSubcomponents.tsx +56 -22
- package/package.json +9 -2
- package/styles/themes/LightTheme.tsx +1 -1
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
type ActivityIndicatorProps,
|
|
4
|
-
Animated,
|
|
5
|
-
Easing,
|
|
6
|
-
Platform,
|
|
7
|
-
View,
|
|
8
|
-
type ViewStyle,
|
|
9
|
-
} from 'react-native';
|
|
2
|
+
import { type ActivityIndicatorProps, Animated, Easing, Platform, View } from 'react-native';
|
|
10
3
|
import { StyleSheet } from 'react-native-unistyles';
|
|
11
4
|
|
|
12
5
|
import { getRegisteredComponentStylesWithFallback } from '../../core';
|
|
13
6
|
import AnimatedSpinner from './AnimatedSpinner';
|
|
14
7
|
|
|
15
|
-
export type Props = ActivityIndicatorProps & {
|
|
8
|
+
export type Props = Omit<ActivityIndicatorProps, 'color'> & {
|
|
16
9
|
/**
|
|
17
10
|
* Whether to show the indicator or hide it.
|
|
18
11
|
*/
|
|
19
12
|
animating?: boolean;
|
|
20
13
|
/**
|
|
21
14
|
* The color of the spinner.
|
|
22
|
-
*/
|
|
23
15
|
color?: string;
|
|
24
16
|
/**
|
|
25
17
|
* Size of the indicator.
|
|
@@ -29,7 +21,7 @@ export type Props = ActivityIndicatorProps & {
|
|
|
29
21
|
* Whether the indicator should hide when not animating.
|
|
30
22
|
*/
|
|
31
23
|
hidesWhenStopped?: boolean;
|
|
32
|
-
|
|
24
|
+
color?: string;
|
|
33
25
|
};
|
|
34
26
|
|
|
35
27
|
const DURATION = 2400;
|
|
@@ -63,7 +55,7 @@ const mapIndicatorSize = (indicatorSize: 'small' | 'large' | number | undefined)
|
|
|
63
55
|
*/
|
|
64
56
|
const ActivityIndicator = ({
|
|
65
57
|
animating = true,
|
|
66
|
-
color
|
|
58
|
+
color,
|
|
67
59
|
hidesWhenStopped = true,
|
|
68
60
|
size: indicatorSize = 'small',
|
|
69
61
|
style: styleProp,
|
|
@@ -83,13 +75,12 @@ const ActivityIndicator = ({
|
|
|
83
75
|
|
|
84
76
|
const size = mapIndicatorSize(indicatorSize);
|
|
85
77
|
|
|
86
|
-
const {
|
|
78
|
+
const { viewStyle, animatedViewStyle } = useMemo(() => {
|
|
87
79
|
return {
|
|
88
|
-
color: indicatorColorProp,
|
|
89
80
|
viewStyle: [componentStyles.container, styleProp],
|
|
90
81
|
animatedViewStyle: [{ width: size, height: size, opacity: fade }],
|
|
91
82
|
};
|
|
92
|
-
}, [componentStyles.container, fade,
|
|
83
|
+
}, [componentStyles.container, fade, size, styleProp]);
|
|
93
84
|
|
|
94
85
|
const startRotation = useCallback(() => {
|
|
95
86
|
// Show indicator
|
|
@@ -1,139 +1,92 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef, memo, type PropsWithoutRef, type ReactNode, useContext, useMemo } from 'react';
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type
|
|
6
|
-
type
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from 'react';
|
|
10
|
-
import { type StyleProp, type TextStyle, View, type ViewProps, type ViewStyle } from 'react-native';
|
|
3
|
+
type StyleProp,
|
|
4
|
+
type TextProps,
|
|
5
|
+
type TextStyle,
|
|
6
|
+
type ViewProps,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
} from 'react-native';
|
|
11
9
|
|
|
12
10
|
import { useActionState } from '../../hooks';
|
|
13
11
|
import type { MD3Elevation } from '../../types/theme';
|
|
14
12
|
import { resolveStateVariant } from '../../utils';
|
|
15
|
-
import { ActivityIndicator } from '../ActivityIndicator';
|
|
16
|
-
import { Icon, type
|
|
13
|
+
import { ActivityIndicator, type ActivityIndicatorProps } from '../ActivityIndicator';
|
|
14
|
+
import { Icon, type IconProps } from '../Icon';
|
|
17
15
|
import { StateLayer } from '../StateLayer';
|
|
18
16
|
import { Surface, type SurfaceProps } from '../Surface';
|
|
19
17
|
import { Text } from '../Text';
|
|
20
|
-
import { TouchableRipple } from '../TouchableRipple';
|
|
21
|
-
import type { ButtonSize, ButtonVariant } from './types';
|
|
22
|
-
import {
|
|
18
|
+
import { TouchableRipple, type TouchableRippleProps } from '../TouchableRipple';
|
|
19
|
+
import type { ButtonContextType, ButtonShape, ButtonSize, ButtonVariant } from './types';
|
|
20
|
+
import {
|
|
21
|
+
buttonActivityIndicatorStyles,
|
|
22
|
+
ButtonContext,
|
|
23
|
+
buttonIconStyles,
|
|
24
|
+
buttonStyles,
|
|
25
|
+
buttonTextStyles,
|
|
26
|
+
elevationMap,
|
|
27
|
+
sizeToIconSizeMap,
|
|
28
|
+
} from './utils';
|
|
23
29
|
|
|
24
|
-
export type Props = Omit<SurfaceProps, 'style'> &
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
*/
|
|
78
|
-
onPressOut?: () => void;
|
|
79
|
-
/**
|
|
80
|
-
* Function to execute on long press.
|
|
81
|
-
*/
|
|
82
|
-
onLongPress?: () => void;
|
|
83
|
-
/**
|
|
84
|
-
* Style of button's inner content.
|
|
85
|
-
* Use this prop to apply custom height and width and to set the icon on the right with `flexDirection: 'row-reverse'`.
|
|
86
|
-
*/
|
|
87
|
-
contentStyle?: StyleProp<ViewStyle>;
|
|
88
|
-
style?: StyleProp<TextStyle>;
|
|
89
|
-
/**
|
|
90
|
-
* Style for the button text.
|
|
91
|
-
*/
|
|
92
|
-
labelStyle?: TextStyle;
|
|
93
|
-
/**
|
|
94
|
-
* Style for the Icon
|
|
95
|
-
*/
|
|
96
|
-
iconContainerStyle?: StyleProp<ViewStyle>;
|
|
97
|
-
iconStyle?: StyleProp<TextStyle>;
|
|
98
|
-
/*
|
|
99
|
-
* Size
|
|
100
|
-
* */
|
|
101
|
-
size?: ButtonSize;
|
|
102
|
-
/*
|
|
103
|
-
* Elevation level
|
|
104
|
-
* */
|
|
105
|
-
elevation?: MD3Elevation;
|
|
106
|
-
/**
|
|
107
|
-
* testID to be used on tests.
|
|
108
|
-
*/
|
|
109
|
-
testID?: string;
|
|
110
|
-
/**
|
|
111
|
-
* props for the stateLayer
|
|
112
|
-
*/
|
|
113
|
-
stateLayerProps?: PropsWithoutRef<ViewProps>;
|
|
114
|
-
textRelatedStyle?: StyleProp<TextStyle>;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const elevationMap: Record<string, Record<string, number>> = {
|
|
118
|
-
true: {
|
|
119
|
-
contained: 1,
|
|
120
|
-
'contained-tonal': 1,
|
|
121
|
-
elevated: 2,
|
|
122
|
-
},
|
|
123
|
-
false: {
|
|
124
|
-
elevated: 1,
|
|
125
|
-
},
|
|
126
|
-
};
|
|
30
|
+
export type Props = Omit<SurfaceProps, 'style'> &
|
|
31
|
+
Pick<TouchableRippleProps, 'onPress' | 'onPressIn' | 'onPressOut' | 'onLongPress'> & {
|
|
32
|
+
/**
|
|
33
|
+
* Mode of the button. You can change the mode to adjust the styling to give it desired emphasis.
|
|
34
|
+
* - `text` - flat button without background or outline, used for the lowest priority actions, especially when presenting multiple options.
|
|
35
|
+
* - `outlined` - button with an outline without background, typically used for important, but not primary action – represents medium emphasis.
|
|
36
|
+
* - `contained` - button with a background color, used for important action, have the most visual impact and high emphasis.
|
|
37
|
+
* - `elevated` - button with a background color and elevation, used when absolutely necessary e.g. button requires visual separation from a patterned background. @supported Available in v5.x with theme version 3
|
|
38
|
+
* - `contained-tonal` - button with a secondary background color, an alternative middle ground between contained and outlined buttons. @supported Available in v5.x with theme version 3
|
|
39
|
+
*/
|
|
40
|
+
variant?: ButtonVariant;
|
|
41
|
+
/**
|
|
42
|
+
* Shape of the button.
|
|
43
|
+
* - `rounded` - fully rounded corners (default)
|
|
44
|
+
* - `square` - square corners with medium border radius
|
|
45
|
+
*/
|
|
46
|
+
shape?: ButtonShape;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the button is disabled. A disabled button is greyed out and `onPress` is not called on touch.
|
|
49
|
+
*/
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Content of the button. Use Button.Icon and Button.Text compound components.
|
|
53
|
+
*/
|
|
54
|
+
children?: ReactNode;
|
|
55
|
+
/**
|
|
56
|
+
* Accessibility label for the button. This is read by the screen reader when the user taps the button.
|
|
57
|
+
*/
|
|
58
|
+
accessibilityLabel?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Accessibility hint for the button. This is read by the screen reader when the user taps the button.
|
|
61
|
+
*/
|
|
62
|
+
accessibilityHint?: string;
|
|
63
|
+
style?: StyleProp<TextStyle>;
|
|
64
|
+
/*
|
|
65
|
+
* Size
|
|
66
|
+
* */
|
|
67
|
+
size?: ButtonSize;
|
|
68
|
+
/*
|
|
69
|
+
* Elevation level
|
|
70
|
+
* */
|
|
71
|
+
elevation?: MD3Elevation;
|
|
72
|
+
/**
|
|
73
|
+
* testID to be used on tests.
|
|
74
|
+
*/
|
|
75
|
+
testID?: string;
|
|
76
|
+
/**
|
|
77
|
+
* props for the stateLayer
|
|
78
|
+
*/
|
|
79
|
+
stateLayerProps?: PropsWithoutRef<ViewProps>;
|
|
80
|
+
textRelatedStyle?: StyleProp<TextStyle>;
|
|
81
|
+
disabledPress?: boolean;
|
|
82
|
+
};
|
|
127
83
|
|
|
128
84
|
const Button = (
|
|
129
85
|
{
|
|
130
86
|
disabled = false,
|
|
131
87
|
variant = 'text',
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
iconType,
|
|
135
|
-
iconName,
|
|
136
|
-
buttonColor: customButtonColor,
|
|
88
|
+
shape = 'rounded',
|
|
89
|
+
size = 'sm',
|
|
137
90
|
children,
|
|
138
91
|
accessibilityLabel,
|
|
139
92
|
accessibilityHint,
|
|
@@ -142,17 +95,13 @@ const Button = (
|
|
|
142
95
|
onPressOut,
|
|
143
96
|
onLongPress,
|
|
144
97
|
style: styleProp,
|
|
145
|
-
contentStyle,
|
|
146
|
-
labelStyle,
|
|
147
|
-
iconContainerStyle: iconContainerStyleProp,
|
|
148
98
|
testID,
|
|
149
99
|
accessible,
|
|
150
100
|
stateLayerProps = {},
|
|
151
101
|
elevation: elevationProp,
|
|
152
|
-
iconSize: _iconSizeProp,
|
|
153
102
|
textRelatedStyle,
|
|
154
|
-
|
|
155
|
-
...
|
|
103
|
+
disabledPress,
|
|
104
|
+
...restProps
|
|
156
105
|
}: Props,
|
|
157
106
|
ref: any,
|
|
158
107
|
) => {
|
|
@@ -163,110 +112,48 @@ const Button = (
|
|
|
163
112
|
hovered,
|
|
164
113
|
});
|
|
165
114
|
|
|
166
|
-
|
|
115
|
+
buttonStyles.useVariants({
|
|
167
116
|
variant,
|
|
168
117
|
// @ts-ignore // TODO - fix this
|
|
169
118
|
state: state as any,
|
|
170
119
|
size,
|
|
120
|
+
shape,
|
|
171
121
|
});
|
|
172
|
-
|
|
173
|
-
// const componentStyles = useComponentStyles(
|
|
174
|
-
// 'Button',
|
|
175
|
-
// [styleProp, { customButtonColor, customTextColor }],
|
|
176
|
-
// {
|
|
177
|
-
// variant,
|
|
178
|
-
// state: disabled ? 'disabled' : hovered ? 'hovered' : undefined,
|
|
179
|
-
// size,
|
|
180
|
-
// },
|
|
181
|
-
// );
|
|
182
|
-
|
|
183
|
-
// console.log({ hovered, componentStyles });
|
|
184
|
-
|
|
185
|
-
const isVariant = useCallback(
|
|
186
|
-
(variantComponent: ButtonVariant) => {
|
|
187
|
-
return variant === variantComponent;
|
|
188
|
-
},
|
|
189
|
-
[variant],
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
const iconSize = _iconSizeProp ?? sizeToIconSizeMap[size] ?? sizeToIconSizeMap.md;
|
|
122
|
+
const iconSize = sizeToIconSizeMap[size] ?? sizeToIconSizeMap.md;
|
|
193
123
|
const elevationLevel = elevationMap[(!!hovered).toString()][variant] ?? 0;
|
|
194
124
|
|
|
195
|
-
const {
|
|
196
|
-
customLabelColor,
|
|
197
|
-
customLabelSize,
|
|
198
|
-
rippleColor,
|
|
199
|
-
surfaceStyle,
|
|
200
|
-
textStyle,
|
|
201
|
-
iconStyle,
|
|
202
|
-
iconContainerStyle,
|
|
203
|
-
accessibilityState,
|
|
204
|
-
stateLayerStyle,
|
|
205
|
-
} = useMemo(() => {
|
|
206
|
-
const { button, content, icon, iconTextMode, label, labelText, labelTextAddons } =
|
|
207
|
-
defaultStyles;
|
|
208
|
-
|
|
209
|
-
const backgroundColor = customButtonColor && !disabled ? customButtonColor : undefined;
|
|
210
|
-
|
|
211
|
-
const _iconStyle = [icon, isVariant('text') && iconTextMode];
|
|
212
|
-
|
|
213
|
-
const { color: labelColor, fontSize: labelFontSize } = labelStyle ?? {};
|
|
214
|
-
|
|
215
|
-
// TODO - remove this workaround
|
|
216
|
-
let _rippleColor: string | undefined;
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
_rippleColor = setColor(labelColor).alpha(0.12).rgb().string();
|
|
220
|
-
} catch (e) {
|
|
221
|
-
_rippleColor = undefined;
|
|
222
|
-
}
|
|
223
|
-
|
|
125
|
+
const { surfaceStyle, accessibilityState, stateLayerStyle, contextValue } = useMemo(() => {
|
|
224
126
|
return {
|
|
225
|
-
|
|
226
|
-
customLabelSize: labelFontSize,
|
|
227
|
-
rippleColor: _rippleColor,
|
|
228
|
-
surfaceStyle: [
|
|
229
|
-
button,
|
|
230
|
-
content,
|
|
231
|
-
backgroundColor ? { backgroundColor } : {},
|
|
232
|
-
defaultStyles.root,
|
|
233
|
-
styleProp,
|
|
234
|
-
contentStyle,
|
|
235
|
-
],
|
|
236
|
-
|
|
237
|
-
iconStyle: [_iconStyle, textRelatedStyle, _iconStyleProp] as unknown as ViewStyle,
|
|
238
|
-
iconContainerStyle: [defaultStyles.iconContainer, iconContainerStyleProp],
|
|
239
|
-
textStyle: [
|
|
240
|
-
// @ts-ignore // TODO - fix this
|
|
241
|
-
isVariant('text') ? (iconName || loading ? labelTextAddons : labelText) : label,
|
|
242
|
-
textRelatedStyle,
|
|
243
|
-
labelStyle,
|
|
244
|
-
],
|
|
127
|
+
surfaceStyle: [buttonStyles.root, styleProp],
|
|
245
128
|
accessibilityState: { disabled },
|
|
246
|
-
stateLayerStyle: [
|
|
129
|
+
stateLayerStyle: [buttonStyles.stateLayer, stateLayerProps?.style],
|
|
130
|
+
contextValue: {
|
|
131
|
+
variant,
|
|
132
|
+
size,
|
|
133
|
+
state: state as ButtonContextType['state'],
|
|
134
|
+
disabled,
|
|
135
|
+
iconSize,
|
|
136
|
+
textRelatedStyle,
|
|
137
|
+
} as ButtonContextType,
|
|
247
138
|
};
|
|
248
|
-
// eslint-disable-next-line
|
|
139
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
249
140
|
}, [
|
|
141
|
+
shape,
|
|
250
142
|
state,
|
|
251
143
|
variant,
|
|
252
144
|
size,
|
|
253
|
-
contentStyle,
|
|
254
|
-
customButtonColor,
|
|
255
145
|
disabled,
|
|
256
|
-
iconContainerStyleProp,
|
|
257
|
-
iconName,
|
|
258
|
-
isVariant,
|
|
259
|
-
labelStyle,
|
|
260
|
-
loading,
|
|
261
146
|
stateLayerProps?.style,
|
|
262
147
|
styleProp,
|
|
148
|
+
iconSize,
|
|
149
|
+
textRelatedStyle,
|
|
263
150
|
]);
|
|
264
151
|
|
|
265
152
|
const elevation = elevationProp === undefined ? elevationLevel ?? 0 : elevationProp;
|
|
266
153
|
|
|
267
154
|
return (
|
|
268
155
|
<Surface
|
|
269
|
-
{...
|
|
156
|
+
{...restProps}
|
|
270
157
|
style={surfaceStyle}
|
|
271
158
|
elevation={
|
|
272
159
|
(disabled
|
|
@@ -287,50 +174,123 @@ const Button = (
|
|
|
287
174
|
accessibilityRole="button"
|
|
288
175
|
accessibilityState={accessibilityState}
|
|
289
176
|
accessible={accessible}
|
|
290
|
-
disabled={disabled}
|
|
291
|
-
rippleColor={rippleColor}
|
|
177
|
+
disabled={disabled || disabledPress}
|
|
292
178
|
ref={actionsRef}
|
|
293
179
|
testID={testID}>
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
<View style={iconContainerStyle}>
|
|
297
|
-
<Icon
|
|
298
|
-
type={iconType}
|
|
299
|
-
name={iconName}
|
|
300
|
-
size={iconSize ?? customLabelSize}
|
|
301
|
-
color={
|
|
302
|
-
typeof customLabelColor === 'string'
|
|
303
|
-
? customLabelColor
|
|
304
|
-
: undefined
|
|
305
|
-
}
|
|
306
|
-
style={iconStyle}
|
|
307
|
-
/>
|
|
308
|
-
</View>
|
|
309
|
-
) : null}
|
|
310
|
-
{loading ? (
|
|
311
|
-
<ActivityIndicator
|
|
312
|
-
size={customLabelSize ?? iconSize}
|
|
313
|
-
color={
|
|
314
|
-
(typeof customLabelColor === 'string'
|
|
315
|
-
? customLabelColor
|
|
316
|
-
: undefined) as string
|
|
317
|
-
}
|
|
318
|
-
style={iconStyle}
|
|
319
|
-
/>
|
|
320
|
-
) : null}
|
|
321
|
-
<Text selectable={false} numberOfLines={1} style={textStyle}>
|
|
180
|
+
<ButtonContext.Provider value={contextValue}>
|
|
181
|
+
<>
|
|
322
182
|
{children}
|
|
323
|
-
</Text>
|
|
324
183
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
184
|
+
<StateLayer
|
|
185
|
+
testID={testID ? `${testID}-stateLayer` : ''}
|
|
186
|
+
{...stateLayerProps}
|
|
187
|
+
style={stateLayerStyle}
|
|
188
|
+
/>
|
|
189
|
+
</>
|
|
190
|
+
</ButtonContext.Provider>
|
|
331
191
|
</TouchableRipple>
|
|
332
192
|
</Surface>
|
|
333
193
|
);
|
|
334
194
|
};
|
|
335
195
|
|
|
336
196
|
export default memo(forwardRef(Button));
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Button.Icon - Renders an icon within the button
|
|
200
|
+
*/
|
|
201
|
+
export const ButtonIcon = memo(
|
|
202
|
+
({ type, name, size: sizeProp, color: colorProp, style, ...rest }: IconProps) => {
|
|
203
|
+
const { labelColor, iconSize, variant, state, disabled, textRelatedStyle } =
|
|
204
|
+
useContext(ButtonContext);
|
|
205
|
+
|
|
206
|
+
const iconSizeResolved = sizeProp ?? iconSize;
|
|
207
|
+
const colorResolved =
|
|
208
|
+
colorProp ?? (typeof labelColor === 'string' ? labelColor : undefined);
|
|
209
|
+
|
|
210
|
+
buttonIconStyles.useVariants({
|
|
211
|
+
variant,
|
|
212
|
+
// @ts-ignore - state includes 'default' which is valid but not typed
|
|
213
|
+
state,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<Icon
|
|
218
|
+
type={type}
|
|
219
|
+
name={name}
|
|
220
|
+
size={iconSizeResolved}
|
|
221
|
+
color={disabled ? undefined : colorResolved}
|
|
222
|
+
style={[buttonIconStyles.root, textRelatedStyle, style]}
|
|
223
|
+
{...rest}
|
|
224
|
+
/>
|
|
225
|
+
);
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
ButtonIcon.displayName = 'Button_Icon';
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Button.Text - Renders text within the button
|
|
233
|
+
*/
|
|
234
|
+
export const ButtonText = memo(({ children, style, ...rest }: TextProps) => {
|
|
235
|
+
const { variant, state, size, textRelatedStyle } = useContext(ButtonContext);
|
|
236
|
+
|
|
237
|
+
buttonTextStyles.useVariants({
|
|
238
|
+
variant,
|
|
239
|
+
// @ts-ignore - state includes 'default' which is valid but not typed
|
|
240
|
+
state,
|
|
241
|
+
// @ts-ignore - size type mismatch
|
|
242
|
+
size,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
// @ts-ignore - deep type instantiation
|
|
247
|
+
<Text
|
|
248
|
+
selectable={false}
|
|
249
|
+
numberOfLines={1}
|
|
250
|
+
{...rest}
|
|
251
|
+
style={[buttonTextStyles.root, textRelatedStyle, style]}>
|
|
252
|
+
{children}
|
|
253
|
+
</Text>
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
ButtonText.displayName = 'Button_Text';
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Button.Loading - Renders a loading indicator within the button
|
|
261
|
+
*/
|
|
262
|
+
export const ButtonActivityIndicator = memo(
|
|
263
|
+
({
|
|
264
|
+
size: sizeProp,
|
|
265
|
+
color: colorProp,
|
|
266
|
+
style,
|
|
267
|
+
...rest
|
|
268
|
+
}: Omit<ActivityIndicatorProps, 'animating'>) => {
|
|
269
|
+
const { iconSize, variant, state } = useContext(ButtonContext);
|
|
270
|
+
|
|
271
|
+
const sizeResolved = sizeProp ?? iconSize;
|
|
272
|
+
// Default to onPrimary for contained variants, primary for others
|
|
273
|
+
const colorResolved = colorProp ?? (variant === 'contained' ? 'onPrimary' : 'primary');
|
|
274
|
+
|
|
275
|
+
buttonActivityIndicatorStyles.useVariants({
|
|
276
|
+
variant,
|
|
277
|
+
// @ts-ignore - state includes 'default' which is valid but not typed
|
|
278
|
+
state,
|
|
279
|
+
});
|
|
280
|
+
const activityIndicatorStyle = useMemo(() => {
|
|
281
|
+
return [buttonActivityIndicatorStyles.root, style] as StyleProp<ViewStyle>;
|
|
282
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
283
|
+
}, [style, variant, state]);
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<ActivityIndicator
|
|
287
|
+
size={sizeResolved}
|
|
288
|
+
color={colorResolved}
|
|
289
|
+
style={activityIndicatorStyle}
|
|
290
|
+
{...rest}
|
|
291
|
+
/>
|
|
292
|
+
);
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
ButtonActivityIndicator.displayName = 'Button_ActivityIndicator';
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { getRegisteredComponentWithFallback } from '../../core';
|
|
2
|
-
import ButtonDefault from './Button';
|
|
2
|
+
import ButtonDefault, { ButtonActivityIndicator, ButtonIcon, ButtonText } from './Button';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const ButtonBase = getRegisteredComponentWithFallback('Button', ButtonDefault);
|
|
5
|
+
|
|
6
|
+
export const Button = Object.assign(ButtonBase, {
|
|
7
|
+
Icon: ButtonIcon,
|
|
8
|
+
Text: ButtonText,
|
|
9
|
+
ActivityIndicator: ButtonActivityIndicator,
|
|
10
|
+
});
|
|
5
11
|
|
|
6
12
|
export type { Props as ButtonProps } from './Button';
|
|
7
|
-
export {
|
|
13
|
+
export { buttonIconStyles, buttonStyles, buttonTextStyles } from './utils';
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
import type { StyleProp, TextStyle } from 'react-native';
|
|
2
|
+
|
|
1
3
|
export type ButtonVariant = 'text' | 'outlined' | 'contained' | 'elevated' | 'contained-tonal';
|
|
2
4
|
|
|
3
|
-
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
5
|
+
export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
6
|
+
|
|
7
|
+
export type ButtonStates = 'hovered' | 'disabled' | 'default';
|
|
8
|
+
|
|
9
|
+
export type ButtonShape = 'rounded' | 'square';
|
|
4
10
|
|
|
5
|
-
export type
|
|
11
|
+
export type ButtonContextType = {
|
|
12
|
+
variant: ButtonVariant;
|
|
13
|
+
size: ButtonSize;
|
|
14
|
+
state: ButtonStates;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
labelColor?: string;
|
|
17
|
+
iconSize?: number;
|
|
18
|
+
textRelatedStyle?: StyleProp<TextStyle>;
|
|
19
|
+
};
|