react-native-molecules 0.5.0-beta.28 → 0.5.0-beta.29
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/Button/utils.ts +1 -1
- package/components/Checkbox/Checkbox.tsx +124 -88
- package/components/Checkbox/CheckboxBase.ios.tsx +2 -4
- package/components/Checkbox/CheckboxBase.tsx +3 -10
- package/components/Checkbox/context.tsx +14 -0
- package/components/Checkbox/index.tsx +11 -4
- package/components/Checkbox/types.ts +63 -29
- package/components/Checkbox/utils.ts +25 -83
- package/components/IconButton/IconButton.tsx +16 -17
- package/components/IconButton/types.ts +8 -0
- package/components/IconButton/utils.ts +25 -0
- package/components/Menu/Menu.tsx +31 -20
- package/components/Menu/index.tsx +2 -1
- package/components/Radio/Radio.tsx +188 -0
- package/components/Radio/RadioBase.ios.tsx +69 -0
- package/components/{RadioButton/RadioButtonAndroid.tsx → Radio/RadioBase.tsx} +27 -63
- package/components/Radio/context.tsx +23 -0
- package/components/Radio/index.tsx +20 -0
- package/components/Radio/types.ts +101 -0
- package/components/Radio/utils.ts +115 -0
- package/package.json +1 -1
- package/components/RadioButton/RadioButton.tsx +0 -138
- package/components/RadioButton/RadioButtonGroup.tsx +0 -97
- package/components/RadioButton/RadioButtonIOS.tsx +0 -92
- package/components/RadioButton/RadioButtonItem.tsx +0 -232
- package/components/RadioButton/index.ts +0 -22
- package/components/RadioButton/utils.ts +0 -165
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
export type IconButtonVariant = 'default' | 'outlined' | 'contained' | 'contained-tonal';
|
|
2
2
|
export type IconButtonShape = 'round' | 'square';
|
|
3
3
|
export type IconButtonWidth = 'narrow' | 'default' | 'wide';
|
|
4
|
+
|
|
5
|
+
export type IconButtonDefaultProps = {
|
|
6
|
+
size: 'xs' | 'sm' | 'md' | 'lg' | number;
|
|
7
|
+
variant: IconButtonVariant;
|
|
8
|
+
shape: IconButtonShape;
|
|
9
|
+
width: IconButtonWidth;
|
|
10
|
+
animated: boolean;
|
|
11
|
+
};
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getRegisteredComponentStylesWithFallback,
|
|
5
5
|
getRegisteredComponentUtilsWithFallback,
|
|
6
6
|
} from './../../core/componentsRegistry';
|
|
7
|
+
import type { IconButtonDefaultProps } from './types';
|
|
7
8
|
|
|
8
9
|
export type States =
|
|
9
10
|
| 'selectedAndDisabled'
|
|
@@ -19,6 +20,22 @@ const iconButtonSizeToIconSizeMapDefault = {
|
|
|
19
20
|
lg: 26,
|
|
20
21
|
};
|
|
21
22
|
|
|
23
|
+
const iconButtonConstantsDefault = {
|
|
24
|
+
minContainerSize: 32,
|
|
25
|
+
containerPadding: 16,
|
|
26
|
+
narrowWidthAdjustment: -8,
|
|
27
|
+
wideWidthAdjustment: 12,
|
|
28
|
+
squareCornerRadius: 12,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const iconButtonDefaultPropsDefault: IconButtonDefaultProps = {
|
|
32
|
+
size: 24,
|
|
33
|
+
variant: 'default',
|
|
34
|
+
shape: 'round',
|
|
35
|
+
width: 'default',
|
|
36
|
+
animated: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
22
39
|
const iconButtonStylesDefault = StyleSheet.create(theme => ({
|
|
23
40
|
root: {
|
|
24
41
|
borderColor: theme.colors.outline,
|
|
@@ -427,3 +444,11 @@ export const iconButtonSizeToIconSizeMap = getRegisteredComponentUtilsWithFallba
|
|
|
427
444
|
iconButtonSizeToIconSizeMapDefault,
|
|
428
445
|
'iconButtonSizeToIconSizeMap',
|
|
429
446
|
);
|
|
447
|
+
export const iconButtonConstants = {
|
|
448
|
+
...iconButtonConstantsDefault,
|
|
449
|
+
...getRegisteredComponentUtilsWithFallback('IconButton', {}, 'iconButtonConstants'),
|
|
450
|
+
};
|
|
451
|
+
export const iconButtonDefaultProps: IconButtonDefaultProps = {
|
|
452
|
+
...iconButtonDefaultPropsDefault,
|
|
453
|
+
...getRegisteredComponentUtilsWithFallback('IconButton', {}, 'iconButtonDefaultProps'),
|
|
454
|
+
};
|
package/components/Menu/Menu.tsx
CHANGED
|
@@ -22,14 +22,11 @@ import {
|
|
|
22
22
|
import { Popover, type PopoverProps } from '../Popover';
|
|
23
23
|
import { MenuContext, MenuRootContext, menuStyles } from './utils';
|
|
24
24
|
|
|
25
|
-
type MenuBaseProps =
|
|
26
|
-
PopoverProps,
|
|
27
|
-
'setIsOpen' | 'onClose' | 'isOpen' | 'triggerRef' | 'children'
|
|
28
|
-
> & {
|
|
29
|
-
style?: ViewStyle;
|
|
30
|
-
closeOnSelect?: boolean;
|
|
25
|
+
type MenuBaseProps = {
|
|
31
26
|
children: ReactElement | ReactElement[];
|
|
27
|
+
closeOnSelect?: boolean;
|
|
32
28
|
disabled?: boolean;
|
|
29
|
+
error?: boolean;
|
|
33
30
|
allowDeselect?: boolean;
|
|
34
31
|
};
|
|
35
32
|
|
|
@@ -59,23 +56,16 @@ export type Props = MenuBaseProps & (SingleMenuProps | MultipleMenuProps);
|
|
|
59
56
|
|
|
60
57
|
const Menu = ({
|
|
61
58
|
children,
|
|
62
|
-
style: styleProp,
|
|
63
59
|
closeOnSelect = true,
|
|
64
60
|
value,
|
|
65
61
|
defaultValue,
|
|
66
62
|
onChange,
|
|
67
63
|
multiple,
|
|
68
64
|
disabled,
|
|
65
|
+
error,
|
|
69
66
|
allowDeselect,
|
|
70
|
-
...rest
|
|
71
67
|
}: Props) => {
|
|
72
|
-
const {
|
|
73
|
-
|
|
74
|
-
const { style } = useMemo(() => {
|
|
75
|
-
return {
|
|
76
|
-
style: [menuStyles.root, styleProp] as unknown as ViewStyle,
|
|
77
|
-
};
|
|
78
|
-
}, [styleProp]);
|
|
68
|
+
const { onClose } = useContext(MenuRootContext);
|
|
79
69
|
|
|
80
70
|
const contextValue = useMemo(
|
|
81
71
|
() => ({
|
|
@@ -91,15 +81,14 @@ const Menu = ({
|
|
|
91
81
|
defaultValue,
|
|
92
82
|
onChange,
|
|
93
83
|
disabled,
|
|
84
|
+
error,
|
|
94
85
|
allowDeselect,
|
|
95
86
|
} as ListProps<DefaultListItemT>;
|
|
96
87
|
|
|
97
88
|
return (
|
|
98
|
-
<
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
</List>
|
|
102
|
-
</Popover>
|
|
89
|
+
<List {...listProps}>
|
|
90
|
+
<MenuContext.Provider value={contextValue}>{children}</MenuContext.Provider>
|
|
91
|
+
</List>
|
|
103
92
|
);
|
|
104
93
|
};
|
|
105
94
|
|
|
@@ -124,6 +113,28 @@ export const MenuRoot = memo(({ children }: MenuRootProps) => {
|
|
|
124
113
|
|
|
125
114
|
MenuRoot.displayName = 'Menu_Root';
|
|
126
115
|
|
|
116
|
+
export type MenuPopoverProps = Omit<PopoverProps, 'isOpen' | 'onClose' | 'triggerRef'> & {
|
|
117
|
+
isOpen?: boolean;
|
|
118
|
+
onClose?: () => void;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const MenuPopover = memo(({ children, style: styleProp, ...rest }: MenuPopoverProps) => {
|
|
122
|
+
const { isOpen, onClose, triggerRef } = useContext(MenuRootContext);
|
|
123
|
+
const { style } = useMemo(() => {
|
|
124
|
+
return {
|
|
125
|
+
style: [menuStyles.root, styleProp] as unknown as ViewStyle,
|
|
126
|
+
};
|
|
127
|
+
}, [styleProp]);
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<Popover isOpen={isOpen} onClose={onClose} triggerRef={triggerRef} style={style} {...rest}>
|
|
131
|
+
{children}
|
|
132
|
+
</Popover>
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
MenuPopover.displayName = 'Menu_Popover';
|
|
137
|
+
|
|
127
138
|
export type MenuTriggerProps = {
|
|
128
139
|
children: ReactElement;
|
|
129
140
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getRegisteredComponentWithFallback } from '../../core';
|
|
2
2
|
import { List } from '../List';
|
|
3
|
-
import MenuComponent, { MenuItem, MenuRoot, MenuTrigger } from './Menu';
|
|
3
|
+
import MenuComponent, { MenuItem, MenuPopover, MenuRoot, MenuTrigger } from './Menu';
|
|
4
4
|
import { MenuRootContext } from './utils';
|
|
5
5
|
|
|
6
6
|
export const MenuDefault = Object.assign(MenuComponent, {
|
|
@@ -9,6 +9,7 @@ export const MenuDefault = Object.assign(MenuComponent, {
|
|
|
9
9
|
Item: MenuItem,
|
|
10
10
|
Content: List.Content,
|
|
11
11
|
RootContext: MenuRootContext,
|
|
12
|
+
Popover: MenuPopover,
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
export const Menu = getRegisteredComponentWithFallback('Menu', MenuDefault);
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { useControlledValue } from '@react-native-molecules/utils/hooks';
|
|
2
|
+
import { forwardRef, memo, useCallback, useContext, useId, useMemo } from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { resolveStateVariant } from '../../utils';
|
|
6
|
+
import { Text } from '../Text';
|
|
7
|
+
import { RadioGroupContext, RadioItemContext } from './context';
|
|
8
|
+
import RadioBase from './RadioBase';
|
|
9
|
+
import type { RadioGroupProps, RadioLabelProps, RadioProps, RadioRowProps } from './types';
|
|
10
|
+
import { radioRowStyles } from './utils';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The radio control (the circle). Use inside a RadioRow, or standalone under a RadioGroup with `value`.
|
|
14
|
+
*/
|
|
15
|
+
const Radio = (
|
|
16
|
+
{
|
|
17
|
+
value,
|
|
18
|
+
disabled: disabledProp,
|
|
19
|
+
size: sizeProp,
|
|
20
|
+
color,
|
|
21
|
+
uncheckedColor,
|
|
22
|
+
stateLayerProps,
|
|
23
|
+
testID,
|
|
24
|
+
...rest
|
|
25
|
+
}: RadioProps,
|
|
26
|
+
ref: any,
|
|
27
|
+
) => {
|
|
28
|
+
const item = useContext(RadioItemContext);
|
|
29
|
+
const group = useContext(RadioGroupContext);
|
|
30
|
+
|
|
31
|
+
// Inside a RadioRow the item context drives state; standalone (bare) mode uses group context.
|
|
32
|
+
const checked = item ? item.checked : group?.value === value;
|
|
33
|
+
const disabled = disabledProp ?? item?.disabled ?? group?.disabled;
|
|
34
|
+
const size = sizeProp ?? item?.size ?? group?.size ?? 'md';
|
|
35
|
+
const labelId = item?.labelId;
|
|
36
|
+
|
|
37
|
+
const onPress = useCallback(() => {
|
|
38
|
+
if (disabled) return;
|
|
39
|
+
if (item) {
|
|
40
|
+
item.onSelect();
|
|
41
|
+
} else if (value !== undefined) {
|
|
42
|
+
group?.onValueChange(value);
|
|
43
|
+
}
|
|
44
|
+
}, [disabled, item, group, value]);
|
|
45
|
+
|
|
46
|
+
const accessibilityState = useMemo(
|
|
47
|
+
() => ({ checked: !!checked, disabled: !!disabled }),
|
|
48
|
+
[checked, disabled],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<RadioBase
|
|
53
|
+
{...rest}
|
|
54
|
+
ref={ref}
|
|
55
|
+
checked={!!checked}
|
|
56
|
+
disabled={disabled}
|
|
57
|
+
size={size}
|
|
58
|
+
color={color}
|
|
59
|
+
uncheckedColor={uncheckedColor}
|
|
60
|
+
onPress={onPress}
|
|
61
|
+
stateLayerProps={stateLayerProps}
|
|
62
|
+
testID={testID}
|
|
63
|
+
accessibilityRole="radio"
|
|
64
|
+
accessibilityState={accessibilityState}
|
|
65
|
+
accessibilityLabelledBy={labelId}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The label for a RadioRow. Pressing it selects the row's value, and it is wired to the control via
|
|
72
|
+
* `nativeID` / `accessibilityLabelledBy` (web `id` / `aria-labelledby`).
|
|
73
|
+
*/
|
|
74
|
+
export const RadioLabel = memo(({ children, style, ...rest }: RadioLabelProps) => {
|
|
75
|
+
const item = useContext(RadioItemContext);
|
|
76
|
+
|
|
77
|
+
const state = resolveStateVariant({
|
|
78
|
+
disabled: !!item?.disabled,
|
|
79
|
+
checked: !!item?.checked,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
radioRowStyles.useVariants({ state: state as any });
|
|
83
|
+
|
|
84
|
+
if (!item) {
|
|
85
|
+
return (
|
|
86
|
+
<Text style={style} {...rest}>
|
|
87
|
+
{children}
|
|
88
|
+
</Text>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Text
|
|
94
|
+
nativeID={item.labelId}
|
|
95
|
+
onPress={item.disabled ? undefined : item.onSelect}
|
|
96
|
+
disabled={item.disabled}
|
|
97
|
+
selectable={false}
|
|
98
|
+
style={[radioRowStyles.label, style]}
|
|
99
|
+
{...rest}>
|
|
100
|
+
{children}
|
|
101
|
+
</Text>
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
RadioLabel.displayName = 'Radio_Label';
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A row that binds a value to a Radio control and its Radio.Label. The row itself is not pressable —
|
|
109
|
+
* only the Radio and Radio.Label inside it are. Children may be in any order.
|
|
110
|
+
*/
|
|
111
|
+
export const RadioRow = memo(
|
|
112
|
+
forwardRef(
|
|
113
|
+
({ value, disabled: disabledProp, style, children, ...rest }: RadioRowProps, ref: any) => {
|
|
114
|
+
const group = useContext(RadioGroupContext);
|
|
115
|
+
const labelId = useId();
|
|
116
|
+
|
|
117
|
+
const disabled = disabledProp ?? group?.disabled;
|
|
118
|
+
const checked = group?.value === value;
|
|
119
|
+
|
|
120
|
+
const onSelect = useCallback(() => {
|
|
121
|
+
if (disabled) return;
|
|
122
|
+
group?.onValueChange(value);
|
|
123
|
+
}, [disabled, group, value]);
|
|
124
|
+
|
|
125
|
+
const contextValue = useMemo(
|
|
126
|
+
() => ({ value, checked, onSelect, disabled, size: group?.size, labelId }),
|
|
127
|
+
[value, checked, onSelect, disabled, group?.size, labelId],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<RadioItemContext.Provider value={contextValue}>
|
|
132
|
+
<View ref={ref} style={[radioRowStyles.row, style]} {...rest}>
|
|
133
|
+
{children}
|
|
134
|
+
</View>
|
|
135
|
+
</RadioItemContext.Provider>
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
RadioRow.displayName = 'Radio_Row';
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Controls a group of radios, holding the selected value.
|
|
145
|
+
*
|
|
146
|
+
* ```tsx
|
|
147
|
+
* <RadioGroup value={value} onValueChange={setValue}>
|
|
148
|
+
* <RadioRow value="first">
|
|
149
|
+
* <Radio />
|
|
150
|
+
* <Radio.Label>First option</Radio.Label>
|
|
151
|
+
* </RadioRow>
|
|
152
|
+
* </RadioGroup>
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export const RadioGroup = memo(
|
|
156
|
+
({
|
|
157
|
+
value: valueProp,
|
|
158
|
+
defaultValue,
|
|
159
|
+
onValueChange: onChange,
|
|
160
|
+
disabled,
|
|
161
|
+
size,
|
|
162
|
+
children,
|
|
163
|
+
...rest
|
|
164
|
+
}: RadioGroupProps) => {
|
|
165
|
+
const [value, onValueChange] = useControlledValue({
|
|
166
|
+
value: valueProp,
|
|
167
|
+
defaultValue,
|
|
168
|
+
onChange,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const contextValue = useMemo(
|
|
172
|
+
() => ({ value, onValueChange, disabled, size }),
|
|
173
|
+
[value, onValueChange, disabled, size],
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<RadioGroupContext.Provider value={contextValue}>
|
|
178
|
+
<View accessibilityRole="radiogroup" {...rest}>
|
|
179
|
+
{children}
|
|
180
|
+
</View>
|
|
181
|
+
</RadioGroupContext.Provider>
|
|
182
|
+
);
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
RadioGroup.displayName = 'Radio_Group';
|
|
187
|
+
|
|
188
|
+
export default memo(forwardRef(Radio));
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { forwardRef, memo, useMemo } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { resolveStateVariant } from '../../utils';
|
|
5
|
+
import { tokenStylesParser } from '../../utils/tokenStylesParser';
|
|
6
|
+
import { Icon } from '../Icon';
|
|
7
|
+
import { TouchableRipple } from '../TouchableRipple';
|
|
8
|
+
import type { RadioBaseProps } from './types';
|
|
9
|
+
import { iconSizeMap, radioStyles } from './utils';
|
|
10
|
+
|
|
11
|
+
const RadioBaseIOS = (
|
|
12
|
+
{
|
|
13
|
+
disabled = false,
|
|
14
|
+
size = 'md',
|
|
15
|
+
style,
|
|
16
|
+
color: colorProp,
|
|
17
|
+
checked,
|
|
18
|
+
onPress,
|
|
19
|
+
uncheckedColor: uncheckedColorProp,
|
|
20
|
+
testID,
|
|
21
|
+
...rest
|
|
22
|
+
}: RadioBaseProps,
|
|
23
|
+
ref: any,
|
|
24
|
+
) => {
|
|
25
|
+
const state = resolveStateVariant({
|
|
26
|
+
disabled,
|
|
27
|
+
checked,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
radioStyles.useVariants({
|
|
31
|
+
state: state as any,
|
|
32
|
+
size: size as any,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const { containerStyle, iconContainerStyle, iconStyle } = useMemo(() => {
|
|
36
|
+
const _color = tokenStylesParser.getColor(checked ? colorProp : uncheckedColorProp);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
containerStyle: [radioStyles.container, radioStyles.root, style],
|
|
40
|
+
iconContainerStyle: { opacity: checked ? 1 : 0 },
|
|
41
|
+
iconStyle: [radioStyles.icon, _color],
|
|
42
|
+
};
|
|
43
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
|
+
}, [checked, colorProp, style, state, size, uncheckedColorProp]);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<TouchableRipple
|
|
48
|
+
{...rest}
|
|
49
|
+
ref={ref}
|
|
50
|
+
onPress={onPress}
|
|
51
|
+
disabled={disabled}
|
|
52
|
+
borderless
|
|
53
|
+
style={containerStyle}
|
|
54
|
+
testID={testID}>
|
|
55
|
+
<View style={iconContainerStyle}>
|
|
56
|
+
<Icon
|
|
57
|
+
allowFontScaling={false}
|
|
58
|
+
name="check"
|
|
59
|
+
size={iconSizeMap[size]}
|
|
60
|
+
style={iconStyle}
|
|
61
|
+
/>
|
|
62
|
+
</View>
|
|
63
|
+
</TouchableRipple>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
RadioBaseIOS.displayName = 'Radio_Base';
|
|
68
|
+
|
|
69
|
+
export default memo(forwardRef(RadioBaseIOS));
|
|
@@ -1,52 +1,20 @@
|
|
|
1
|
-
import { forwardRef, memo,
|
|
2
|
-
import type { ViewProps } from 'react-native';
|
|
1
|
+
import { forwardRef, memo, useEffect, useMemo, useRef } from 'react';
|
|
3
2
|
import { Animated, StyleSheet, View } from 'react-native';
|
|
4
3
|
|
|
5
4
|
import { useActionState } from '../../hooks';
|
|
6
5
|
import { resolveStateVariant } from '../../utils';
|
|
7
6
|
import { tokenStylesParser } from '../../utils/tokenStylesParser';
|
|
8
7
|
import { StateLayer } from '../StateLayer';
|
|
9
|
-
import { TouchableRipple
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
export type Props = Omit<TouchableRippleProps, 'children'> & {
|
|
13
|
-
/**
|
|
14
|
-
* Status of radio button.
|
|
15
|
-
*/
|
|
16
|
-
status?: 'checked' | 'unchecked';
|
|
17
|
-
/**
|
|
18
|
-
* Whether radio is disabled.
|
|
19
|
-
*/
|
|
20
|
-
disabled?: boolean;
|
|
21
|
-
/**
|
|
22
|
-
* Custom color for unchecked radio.
|
|
23
|
-
*/
|
|
24
|
-
uncheckedColor?: string;
|
|
25
|
-
/**
|
|
26
|
-
* Custom color for radio.
|
|
27
|
-
*/
|
|
28
|
-
color?: string;
|
|
29
|
-
/**
|
|
30
|
-
* testID to be used on tests.
|
|
31
|
-
*/
|
|
32
|
-
testID?: string;
|
|
33
|
-
/**
|
|
34
|
-
* passed from RadioButton component
|
|
35
|
-
*/
|
|
36
|
-
checked: boolean;
|
|
37
|
-
onPress: (() => void) | undefined;
|
|
38
|
-
/**
|
|
39
|
-
* props for the stateLayer
|
|
40
|
-
*/
|
|
41
|
-
stateLayerProps?: PropsWithoutRef<ViewProps>;
|
|
42
|
-
};
|
|
8
|
+
import { TouchableRipple } from '../TouchableRipple';
|
|
9
|
+
import type { RadioBaseProps } from './types';
|
|
10
|
+
import { ANIMATION_DURATION, radioStyles } from './utils';
|
|
43
11
|
|
|
44
12
|
const BORDER_WIDTH = 2;
|
|
45
13
|
|
|
46
|
-
const
|
|
14
|
+
const RadioBaseAndroid = (
|
|
47
15
|
{
|
|
48
16
|
disabled = false,
|
|
49
|
-
|
|
17
|
+
size = 'md',
|
|
50
18
|
testID,
|
|
51
19
|
color: colorProp,
|
|
52
20
|
uncheckedColor: uncheckedColorProp,
|
|
@@ -55,14 +23,12 @@ const RadioButtonAndroid = (
|
|
|
55
23
|
onPress,
|
|
56
24
|
stateLayerProps = {},
|
|
57
25
|
...rest
|
|
58
|
-
}:
|
|
26
|
+
}: RadioBaseProps,
|
|
59
27
|
ref: any,
|
|
60
28
|
) => {
|
|
61
29
|
const { actionsRef, hovered } = useActionState({ ref, actionsToListen: ['hover'] });
|
|
62
30
|
const { current: borderAnim } = useRef<Animated.Value>(new Animated.Value(BORDER_WIDTH));
|
|
63
|
-
|
|
64
31
|
const { current: radioAnim } = useRef<Animated.Value>(new Animated.Value(1));
|
|
65
|
-
|
|
66
32
|
const isFirstRendering = useRef<boolean>(true);
|
|
67
33
|
|
|
68
34
|
const state = resolveStateVariant({
|
|
@@ -72,36 +38,33 @@ const RadioButtonAndroid = (
|
|
|
72
38
|
hovered,
|
|
73
39
|
});
|
|
74
40
|
|
|
75
|
-
|
|
41
|
+
radioStyles.useVariants({
|
|
76
42
|
state: state as any,
|
|
43
|
+
size: size as any,
|
|
77
44
|
});
|
|
78
45
|
|
|
79
|
-
const { containerStyles,
|
|
46
|
+
const { containerStyles, radioStyle, dotStyles, dotContainerStyles, stateLayerStyle } =
|
|
80
47
|
useMemo(() => {
|
|
81
48
|
return {
|
|
82
|
-
containerStyles: [
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{
|
|
86
|
-
borderWidth: borderAnim,
|
|
87
|
-
},
|
|
49
|
+
containerStyles: [radioStyles.container, radioStyles.root, style],
|
|
50
|
+
radioStyle: [
|
|
51
|
+
radioStyles.radio,
|
|
52
|
+
{ borderWidth: borderAnim },
|
|
88
53
|
tokenStylesParser.getColor(
|
|
89
54
|
checked ? colorProp : uncheckedColorProp,
|
|
90
55
|
'borderColor',
|
|
91
56
|
),
|
|
92
57
|
],
|
|
93
|
-
dotContainerStyles: [StyleSheet.absoluteFill,
|
|
58
|
+
dotContainerStyles: [StyleSheet.absoluteFill, radioStyles.radioContainer],
|
|
94
59
|
dotStyles: [
|
|
95
|
-
|
|
96
|
-
{
|
|
97
|
-
transform: [{ scale: radioAnim }],
|
|
98
|
-
},
|
|
60
|
+
radioStyles.dot,
|
|
61
|
+
{ transform: [{ scale: radioAnim }] },
|
|
99
62
|
tokenStylesParser.getColor(
|
|
100
63
|
checked ? colorProp : uncheckedColorProp,
|
|
101
64
|
'backgroundColor',
|
|
102
65
|
),
|
|
103
66
|
],
|
|
104
|
-
stateLayerStyle: [
|
|
67
|
+
stateLayerStyle: [radioStyles.stateLayer, stateLayerProps?.style],
|
|
105
68
|
};
|
|
106
69
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
107
70
|
}, [
|
|
@@ -113,18 +76,18 @@ const RadioButtonAndroid = (
|
|
|
113
76
|
uncheckedColorProp,
|
|
114
77
|
style,
|
|
115
78
|
state,
|
|
79
|
+
size,
|
|
116
80
|
]);
|
|
117
81
|
|
|
118
82
|
useEffect(() => {
|
|
119
|
-
// Do not run animation on very first
|
|
83
|
+
// Do not run animation on the very first render.
|
|
120
84
|
if (isFirstRendering.current) {
|
|
121
85
|
isFirstRendering.current = false;
|
|
122
86
|
return;
|
|
123
87
|
}
|
|
124
88
|
|
|
125
|
-
if (
|
|
89
|
+
if (checked) {
|
|
126
90
|
radioAnim.setValue(1.2);
|
|
127
|
-
|
|
128
91
|
Animated.timing(radioAnim, {
|
|
129
92
|
toValue: 1,
|
|
130
93
|
duration: ANIMATION_DURATION,
|
|
@@ -132,24 +95,25 @@ const RadioButtonAndroid = (
|
|
|
132
95
|
}).start();
|
|
133
96
|
} else {
|
|
134
97
|
borderAnim.setValue(10);
|
|
135
|
-
|
|
136
98
|
Animated.timing(borderAnim, {
|
|
137
99
|
toValue: BORDER_WIDTH,
|
|
138
100
|
duration: ANIMATION_DURATION,
|
|
139
101
|
useNativeDriver: false,
|
|
140
102
|
}).start();
|
|
141
103
|
}
|
|
142
|
-
}, [
|
|
104
|
+
}, [checked, borderAnim, radioAnim]);
|
|
143
105
|
|
|
144
106
|
return (
|
|
145
107
|
<TouchableRipple
|
|
146
108
|
{...rest}
|
|
147
109
|
ref={actionsRef}
|
|
148
110
|
onPress={onPress}
|
|
111
|
+
disabled={disabled}
|
|
112
|
+
borderless
|
|
149
113
|
style={containerStyles}
|
|
150
114
|
testID={testID}>
|
|
151
115
|
<>
|
|
152
|
-
<Animated.View style={
|
|
116
|
+
<Animated.View style={radioStyle}>
|
|
153
117
|
{checked ? (
|
|
154
118
|
<View style={dotContainerStyles}>
|
|
155
119
|
<Animated.View style={dotStyles} />
|
|
@@ -167,6 +131,6 @@ const RadioButtonAndroid = (
|
|
|
167
131
|
);
|
|
168
132
|
};
|
|
169
133
|
|
|
170
|
-
|
|
134
|
+
RadioBaseAndroid.displayName = 'Radio_Base';
|
|
171
135
|
|
|
172
|
-
export default memo(forwardRef(
|
|
136
|
+
export default memo(forwardRef(RadioBaseAndroid));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { Size } from './types';
|
|
4
|
+
|
|
5
|
+
export type RadioGroupContextType = {
|
|
6
|
+
value?: string;
|
|
7
|
+
onValueChange: (value: string) => void;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
size?: Size;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const RadioGroupContext = createContext<RadioGroupContextType | null>(null);
|
|
13
|
+
|
|
14
|
+
export type RadioItemContextType = {
|
|
15
|
+
value: string;
|
|
16
|
+
checked: boolean;
|
|
17
|
+
onSelect: () => void;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
size?: Size;
|
|
20
|
+
labelId: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const RadioItemContext = createContext<RadioItemContextType | null>(null);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getRegisteredComponentWithFallback } from '../../core';
|
|
2
|
+
// @component ./Radio.tsx
|
|
3
|
+
import RadioControl, {
|
|
4
|
+
RadioGroup as RadioGroupComponent,
|
|
5
|
+
RadioLabel,
|
|
6
|
+
RadioRow as RadioRowComponent,
|
|
7
|
+
} from './Radio';
|
|
8
|
+
|
|
9
|
+
const RadioDefault = Object.assign(RadioControl, {
|
|
10
|
+
Label: RadioLabel,
|
|
11
|
+
Group: RadioGroupComponent,
|
|
12
|
+
Row: RadioRowComponent,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const Radio = getRegisteredComponentWithFallback('Radio', RadioDefault);
|
|
16
|
+
export const RadioGroup = RadioGroupComponent;
|
|
17
|
+
export const RadioRow = RadioRowComponent;
|
|
18
|
+
|
|
19
|
+
export type { RadioGroupProps, RadioLabelProps, RadioProps, RadioRowProps } from './types';
|
|
20
|
+
export { radioRowStyles, radioStyles } from './utils';
|