react-native-molecules 0.5.0-beta.21 → 0.5.0-beta.22
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/Card/Card.tsx +1 -1
- package/components/Checkbox/CheckboxBase.ios.tsx +1 -4
- package/components/Checkbox/CheckboxBase.tsx +2 -7
- package/components/DatePicker/DateCalendar.tsx +4 -4
- package/components/DatePicker/DatePickerModal.tsx +2 -1
- package/components/DatePickerInline/DatePickerDockedHeader.tsx +3 -3
- package/components/DatePickerInline/DatePickerInline.tsx +1 -1
- package/components/DatePickerInline/DatePickerInlineBase.tsx +2 -2
- package/components/DatePickerInline/DatePickerInlineHeader.tsx +43 -17
- package/components/DatePickerInline/HeaderItem.tsx +2 -2
- package/components/DatePickerInline/MonthPicker.tsx +64 -54
- package/components/DatePickerInline/Swiper.native.tsx +2 -2
- package/components/DatePickerInline/Swiper.tsx +3 -3
- package/components/DatePickerInline/YearPicker.tsx +136 -112
- package/components/DatePickerInline/{DatePickerContext.tsx → store.tsx} +7 -3
- package/components/DatePickerInline/types.ts +1 -1
- package/components/Divider/Divider.tsx +192 -0
- package/components/Divider/index.tsx +11 -0
- package/components/Drawer/DrawerItemGroup.tsx +3 -7
- package/components/IconButton/IconButton.tsx +2 -12
- package/components/List/List.tsx +507 -0
- package/components/List/context.tsx +28 -0
- package/components/List/index.ts +9 -0
- package/components/List/types.ts +149 -0
- package/components/{ListItem → List}/utils.ts +47 -50
- package/components/Menu/Menu.tsx +156 -12
- package/components/Menu/index.tsx +11 -7
- package/components/Menu/utils.ts +21 -70
- package/components/RadioButton/RadioButtonAndroid.tsx +38 -54
- package/components/RadioButton/RadioButtonIOS.tsx +2 -16
- package/components/Select/Select.tsx +139 -497
- package/components/Select/context.tsx +14 -32
- package/components/Select/types.ts +44 -53
- package/components/Select/utils.ts +15 -47
- package/components/Text/textFactory.tsx +17 -5
- package/components/TimePicker/TimeInput.tsx +2 -7
- package/components/TimePicker/utils.ts +0 -4
- package/components/TouchableRipple/TouchableRipple.native.tsx +36 -5
- package/components/TouchableRipple/TouchableRipple.tsx +53 -19
- package/components/TouchableRipple/rippleFromForegroundColor.ts +21 -0
- package/package.json +4 -2
- package/components/HorizontalDivider/HorizontalDivider.tsx +0 -103
- package/components/HorizontalDivider/index.tsx +0 -9
- package/components/ListItem/ListItem.tsx +0 -138
- package/components/ListItem/ListItemDescription.tsx +0 -25
- package/components/ListItem/ListItemTitle.tsx +0 -25
- package/components/ListItem/index.tsx +0 -14
- package/components/Menu/MenuDivider.tsx +0 -13
- package/components/Menu/MenuItem.tsx +0 -128
- package/components/VerticalDivider/VerticalDivider.tsx +0 -100
- package/components/VerticalDivider/index.tsx +0 -9
|
@@ -1,44 +1,27 @@
|
|
|
1
1
|
import type { View } from 'react-native';
|
|
2
2
|
|
|
3
3
|
import { createFastContext } from '../../fast-context';
|
|
4
|
+
import {
|
|
5
|
+
ListContext,
|
|
6
|
+
ListContextProvider,
|
|
7
|
+
useListContext,
|
|
8
|
+
useListContextValue,
|
|
9
|
+
useListStoreRef,
|
|
10
|
+
} from '../List';
|
|
4
11
|
import { registerPortalContext } from '../Portal';
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
// SelectContext - holds value, onAdd, onRemove with fast-context for optimized rendering
|
|
8
|
-
const selectContextDefaultValue: SelectContextValue<DefaultItemT> = {
|
|
9
|
-
value: null,
|
|
10
|
-
multiple: false,
|
|
11
|
-
onAdd: () => {},
|
|
12
|
-
onRemove: () => {},
|
|
13
|
-
disabled: false,
|
|
14
|
-
error: false,
|
|
15
|
-
labelKey: 'label',
|
|
16
|
-
options: [],
|
|
17
|
-
searchQuery: '',
|
|
18
|
-
setSearchQuery: () => {},
|
|
19
|
-
filteredOptions: [],
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const {
|
|
23
|
-
useStoreRef: useSelectStoreRef,
|
|
24
|
-
Provider: SelectContextProvider,
|
|
25
|
-
useContext: useSelectContext,
|
|
26
|
-
useContextValue: useSelectContextValue,
|
|
27
|
-
Context: SelectContext,
|
|
28
|
-
} = createFastContext<SelectContextValue<DefaultItemT>>(selectContextDefaultValue, true);
|
|
12
|
+
import type { SelectDropdownContextValue } from './types';
|
|
29
13
|
|
|
30
14
|
export {
|
|
31
|
-
SelectContext,
|
|
32
|
-
SelectContextProvider,
|
|
33
|
-
useSelectContext,
|
|
34
|
-
useSelectContextValue,
|
|
35
|
-
useSelectStoreRef,
|
|
15
|
+
ListContext as SelectContext,
|
|
16
|
+
ListContextProvider as SelectContextProvider,
|
|
17
|
+
useListContext as useSelectContext,
|
|
18
|
+
useListContextValue as useSelectContextValue,
|
|
19
|
+
useListStoreRef as useSelectStoreRef,
|
|
36
20
|
};
|
|
37
21
|
|
|
38
22
|
// SelectDropdownContext - holds isOpen, onClose, triggerRef with fast-context
|
|
39
23
|
export type SelectDropdownContextType = SelectDropdownContextValue & {
|
|
40
24
|
triggerRef: React.RefObject<View> | null;
|
|
41
|
-
contentRef: React.RefObject<any> | null;
|
|
42
25
|
triggerLayout: { width: number; height: number } | null;
|
|
43
26
|
setTriggerLayout: (layout: { width: number; height: number }) => void;
|
|
44
27
|
};
|
|
@@ -48,7 +31,6 @@ const selectDropdownContextDefaultValue: SelectDropdownContextType = {
|
|
|
48
31
|
onClose: () => {},
|
|
49
32
|
onOpen: () => {},
|
|
50
33
|
triggerRef: null,
|
|
51
|
-
contentRef: null,
|
|
52
34
|
triggerLayout: null,
|
|
53
35
|
setTriggerLayout: () => {},
|
|
54
36
|
};
|
|
@@ -69,4 +51,4 @@ export {
|
|
|
69
51
|
useSelectDropdownStoreRef,
|
|
70
52
|
};
|
|
71
53
|
|
|
72
|
-
registerPortalContext([
|
|
54
|
+
registerPortalContext([SelectDropdownContext]);
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
-
import type {
|
|
3
|
-
GestureResponderEvent,
|
|
4
|
-
ScrollViewProps,
|
|
5
|
-
TextInputProps,
|
|
6
|
-
ViewProps,
|
|
7
|
-
} from 'react-native';
|
|
2
|
+
import type { GestureResponderEvent, ViewProps } from 'react-native';
|
|
8
3
|
|
|
4
|
+
import type { ListValue } from '../List';
|
|
9
5
|
import type { PopoverProps } from '../Popover';
|
|
10
6
|
|
|
7
|
+
export type {
|
|
8
|
+
ListContentProps as SelectContentProps,
|
|
9
|
+
ListContextValue as SelectContextValue,
|
|
10
|
+
ListGroupProps as SelectGroupProps,
|
|
11
|
+
ListSearchInputProps as SelectSearchInputProps,
|
|
12
|
+
} from '../List';
|
|
13
|
+
|
|
11
14
|
export type DefaultItemT = {
|
|
12
15
|
id: string | number;
|
|
13
16
|
label?: string;
|
|
@@ -15,21 +18,6 @@ export type DefaultItemT = {
|
|
|
15
18
|
[key: string]: any;
|
|
16
19
|
};
|
|
17
20
|
|
|
18
|
-
// SelectContext types
|
|
19
|
-
export type SelectContextValue<Option extends DefaultItemT = DefaultItemT> = {
|
|
20
|
-
value: Option | Option[] | null;
|
|
21
|
-
multiple: boolean;
|
|
22
|
-
onAdd: (item: Option) => void;
|
|
23
|
-
onRemove: (item: Option) => void;
|
|
24
|
-
disabled?: boolean;
|
|
25
|
-
error?: boolean;
|
|
26
|
-
labelKey?: string;
|
|
27
|
-
options: Option[];
|
|
28
|
-
searchQuery: string;
|
|
29
|
-
setSearchQuery: (query: string) => void;
|
|
30
|
-
filteredOptions: Option[];
|
|
31
|
-
};
|
|
32
|
-
|
|
33
21
|
// SelectDropdownContext types
|
|
34
22
|
export type SelectDropdownContextValue = {
|
|
35
23
|
isOpen: boolean;
|
|
@@ -38,25 +26,41 @@ export type SelectDropdownContextValue = {
|
|
|
38
26
|
};
|
|
39
27
|
|
|
40
28
|
// SelectProvider props
|
|
41
|
-
|
|
29
|
+
type SelectPropsBase<Option extends DefaultItemT = DefaultItemT> = {
|
|
42
30
|
children: ReactNode;
|
|
43
|
-
value?: Option['id'] | Option['id'][] | null;
|
|
44
|
-
defaultValue?: Option['id'] | Option['id'][] | null;
|
|
45
|
-
onChange?: (
|
|
46
|
-
value: Option['id'] | Option['id'][] | null,
|
|
47
|
-
item: Option,
|
|
48
|
-
event?: GestureResponderEvent,
|
|
49
|
-
) => void;
|
|
50
|
-
multiple?: boolean;
|
|
51
31
|
disabled?: boolean;
|
|
52
32
|
error?: boolean;
|
|
53
|
-
labelKey?: string;
|
|
54
33
|
options: Option[];
|
|
55
34
|
searchKey?: string;
|
|
56
35
|
onSearchChange?: (query: string) => void;
|
|
57
36
|
hideSelected?: boolean;
|
|
58
37
|
};
|
|
59
38
|
|
|
39
|
+
type SingleSelectProps<Option extends DefaultItemT = DefaultItemT> = {
|
|
40
|
+
multiple?: false | undefined;
|
|
41
|
+
value?: ListValue<Option, false>;
|
|
42
|
+
defaultValue?: ListValue<Option, false>;
|
|
43
|
+
onChange?: (
|
|
44
|
+
value: ListValue<Option, false>,
|
|
45
|
+
item: Option,
|
|
46
|
+
event?: GestureResponderEvent,
|
|
47
|
+
) => void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type MultipleSelectProps<Option extends DefaultItemT = DefaultItemT> = {
|
|
51
|
+
multiple: true;
|
|
52
|
+
value?: ListValue<Option, true>;
|
|
53
|
+
defaultValue?: ListValue<Option, true>;
|
|
54
|
+
onChange?: (
|
|
55
|
+
value: ListValue<Option, true>,
|
|
56
|
+
item: Option,
|
|
57
|
+
event?: GestureResponderEvent,
|
|
58
|
+
) => void;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type SelectProps<Option extends DefaultItemT = DefaultItemT> = SelectPropsBase<Option> &
|
|
62
|
+
(SingleSelectProps<Option> | MultipleSelectProps<Option>);
|
|
63
|
+
|
|
60
64
|
// Select.Trigger props
|
|
61
65
|
export type SelectTriggerProps = ViewProps & {
|
|
62
66
|
children?: ReactNode;
|
|
@@ -65,6 +69,7 @@ export type SelectTriggerProps = ViewProps & {
|
|
|
65
69
|
// Select.Value props
|
|
66
70
|
export type SelectValueProps = ViewProps & {
|
|
67
71
|
placeholder?: string;
|
|
72
|
+
labelKey?: string;
|
|
68
73
|
renderValue?: (value: DefaultItemT | DefaultItemT[] | null) => ReactNode;
|
|
69
74
|
};
|
|
70
75
|
|
|
@@ -78,36 +83,22 @@ export type SelectDropdownProps = Omit<
|
|
|
78
83
|
wrapperComponentProps?: Record<string, any>;
|
|
79
84
|
};
|
|
80
85
|
|
|
81
|
-
// Select.Content props
|
|
82
|
-
export type SelectContentProps<Option extends DefaultItemT = DefaultItemT> = Omit<
|
|
83
|
-
ScrollViewProps,
|
|
84
|
-
'children'
|
|
85
|
-
> & {
|
|
86
|
-
children: (item: Option, isSelected: boolean) => ReactNode;
|
|
87
|
-
ContainerComponent?: ComponentType<any>;
|
|
88
|
-
emptyState?: ReactNode;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// Select.Group props
|
|
92
|
-
export type SelectGroupProps = ViewProps & {
|
|
93
|
-
children: ReactNode;
|
|
94
|
-
label?: string;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
86
|
// Select.Option props
|
|
98
|
-
|
|
87
|
+
// `accessibilityRole` and `role` are intentionally omitted: Select.Option forces them to
|
|
88
|
+
// "option" on web so the dropdown's keyboard navigator (which queries [role="option"]) can
|
|
89
|
+
// always find the rows. Allowing callers to override would silently break keyboard nav.
|
|
90
|
+
export type SelectOptionProps<Option extends DefaultItemT = DefaultItemT> = Omit<
|
|
91
|
+
ViewProps,
|
|
92
|
+
'accessibilityRole' | 'role'
|
|
93
|
+
> & {
|
|
99
94
|
/**
|
|
100
95
|
* Unique id for the option
|
|
101
96
|
*/
|
|
102
97
|
value: Option['id'];
|
|
103
|
-
children
|
|
104
|
-
renderItem?: (item: Option, isSelected: boolean) => ReactNode;
|
|
98
|
+
children: ReactNode;
|
|
105
99
|
onPress?: (item: Option, event: GestureResponderEvent) => void;
|
|
106
100
|
/**
|
|
107
101
|
* When true, the option can't be selected (similar to HTML option disabled)
|
|
108
102
|
*/
|
|
109
103
|
disabled?: boolean;
|
|
110
104
|
};
|
|
111
|
-
|
|
112
|
-
// Select.SearchInput props
|
|
113
|
-
export type SelectSearchInputProps = Omit<TextInputProps, 'value' | 'onChangeText'> & {};
|
|
@@ -2,6 +2,21 @@ import { StyleSheet } from 'react-native-unistyles';
|
|
|
2
2
|
|
|
3
3
|
import { getRegisteredComponentStylesWithFallback } from '../../core';
|
|
4
4
|
|
|
5
|
+
/** Web-only marker on Select.Option roots so keyboard nav does not depend on role/accessibilityRole overrides. */
|
|
6
|
+
export const SELECT_OPTION_DATA_ATTR = 'data-molecules-select-option';
|
|
7
|
+
|
|
8
|
+
const SELECT_OPTION_SELECTOR = `[${SELECT_OPTION_DATA_ATTR}], [data-option-id], [role="option"]`;
|
|
9
|
+
|
|
10
|
+
export function collectWebSelectKeyboardOptionElements(container: ParentNode): HTMLElement[] {
|
|
11
|
+
return Array.from(container.querySelectorAll(SELECT_OPTION_SELECTOR)).filter(
|
|
12
|
+
(el): el is HTMLElement => {
|
|
13
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
14
|
+
if (el.getAttribute('aria-disabled') === 'true') return false;
|
|
15
|
+
return true;
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
const triggerDefaultStyles = StyleSheet.create(theme => ({
|
|
6
21
|
trigger: {
|
|
7
22
|
borderRadius: theme.shapes.corner.extraSmall,
|
|
@@ -83,57 +98,10 @@ export const defaultStyles = StyleSheet.create(theme => ({
|
|
|
83
98
|
gap: 6,
|
|
84
99
|
maxWidth: '90%',
|
|
85
100
|
},
|
|
86
|
-
groupLabel: {
|
|
87
|
-
paddingHorizontal: theme.spacings['4'],
|
|
88
|
-
paddingVertical: theme.spacings['2'],
|
|
89
|
-
fontWeight: '600',
|
|
90
|
-
color: theme.colors.onSurface,
|
|
91
|
-
},
|
|
92
|
-
item: {
|
|
93
|
-
paddingHorizontal: theme.spacings['4'],
|
|
94
|
-
paddingVertical: theme.spacings['3'],
|
|
95
|
-
backgroundColor: 'transparent',
|
|
96
|
-
|
|
97
|
-
_web: {
|
|
98
|
-
cursor: 'pointer',
|
|
99
|
-
outlineStyle: 'none',
|
|
100
|
-
_hover: {
|
|
101
|
-
backgroundColor: theme.colors.stateLayer.hover.primary,
|
|
102
|
-
},
|
|
103
|
-
_focus: {
|
|
104
|
-
backgroundColor: theme.colors.stateLayer.focussed.primary,
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
itemSelected: {
|
|
109
|
-
backgroundColor: theme.colors.stateLayer.hover.primary,
|
|
110
|
-
},
|
|
111
|
-
itemDisabled: {
|
|
112
|
-
opacity: 0.38,
|
|
113
|
-
_web: {
|
|
114
|
-
cursor: 'not-allowed',
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
itemDisabledText: {
|
|
118
|
-
color: theme.colors.onSurfaceVariant,
|
|
119
|
-
},
|
|
120
101
|
searchInput: {
|
|
121
102
|
marginHorizontal: theme.spacings['2'],
|
|
122
103
|
marginVertical: theme.spacings['3'],
|
|
123
104
|
},
|
|
124
|
-
searchInputInput: {
|
|
125
|
-
height: 42,
|
|
126
|
-
},
|
|
127
|
-
emptyState: {
|
|
128
|
-
paddingHorizontal: theme.spacings['4'],
|
|
129
|
-
paddingVertical: theme.spacings['6'],
|
|
130
|
-
alignItems: 'center',
|
|
131
|
-
justifyContent: 'center',
|
|
132
|
-
},
|
|
133
|
-
emptyStateText: {
|
|
134
|
-
color: theme.colors.onSurfaceVariant,
|
|
135
|
-
fontSize: 14,
|
|
136
|
-
},
|
|
137
105
|
}));
|
|
138
106
|
|
|
139
107
|
export const triggerStyles = getRegisteredComponentStylesWithFallback(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type ComponentType, createContext, forwardRef, memo, useContext } from 'react';
|
|
2
2
|
import { Text, type TextProps } from 'react-native';
|
|
3
|
-
import { StyleSheet } from 'react-native-unistyles';
|
|
3
|
+
import { StyleSheet, useUnistyles } from 'react-native-unistyles';
|
|
4
|
+
|
|
5
|
+
import { MD3TypescaleKey } from '../../types/theme';
|
|
4
6
|
|
|
5
7
|
const HasAncestorContext = createContext(false);
|
|
6
8
|
|
|
@@ -8,18 +10,28 @@ const defaultStyles = StyleSheet.create(theme => ({
|
|
|
8
10
|
root: { color: theme.colors.onSurface, ...theme.typescale.bodyMedium },
|
|
9
11
|
}));
|
|
10
12
|
|
|
13
|
+
export type TypescaleKey = `${MD3TypescaleKey}`;
|
|
14
|
+
|
|
15
|
+
export type TextFactoryProps = TextProps & {
|
|
16
|
+
typescale?: TypescaleKey;
|
|
17
|
+
};
|
|
18
|
+
|
|
11
19
|
export const textFactory = (
|
|
12
20
|
componentStyles: typeof defaultStyles = defaultStyles,
|
|
13
21
|
isBlockLevelElement = false,
|
|
14
22
|
DefaultComponent: ComponentType<any> = Text,
|
|
15
23
|
) => {
|
|
16
24
|
return memo(
|
|
17
|
-
forwardRef((props:
|
|
18
|
-
const { style, ...rest } = props;
|
|
25
|
+
forwardRef((props: TextFactoryProps, ref: any) => {
|
|
26
|
+
const { style, typescale, ...rest } = props;
|
|
19
27
|
const hasAncestorText = useContext(HasAncestorContext);
|
|
28
|
+
const { theme } = useUnistyles();
|
|
29
|
+
|
|
30
|
+
const typescaleStyle = typescale ? theme.typescale[typescale] : undefined;
|
|
20
31
|
|
|
21
|
-
const
|
|
22
|
-
hasAncestorText && !isBlockLevelElement ?
|
|
32
|
+
const baseStyle =
|
|
33
|
+
hasAncestorText && !isBlockLevelElement ? null : componentStyles?.root;
|
|
34
|
+
const styles = [baseStyle, typescaleStyle, style];
|
|
23
35
|
|
|
24
36
|
return hasAncestorText ? (
|
|
25
37
|
<DefaultComponent ref={ref} style={styles} {...rest} />
|
|
@@ -66,7 +66,7 @@ function TimeInput(
|
|
|
66
66
|
return str.length === 1 ? `0${str}` : str;
|
|
67
67
|
}, [value, inputFocused, rawText, error]);
|
|
68
68
|
|
|
69
|
-
const {
|
|
69
|
+
const { containerStyle, textInputStyle, buttonStyle } = useMemo(() => {
|
|
70
70
|
const {
|
|
71
71
|
container,
|
|
72
72
|
input,
|
|
@@ -79,7 +79,6 @@ function TimeInput(
|
|
|
79
79
|
const isKeyboardInput = inputType === inputTypes.keyboard;
|
|
80
80
|
|
|
81
81
|
return {
|
|
82
|
-
rippleColor: timePickerInputStyles.root?._rippleColor,
|
|
83
82
|
containerStyle: container,
|
|
84
83
|
textInputStyle: [
|
|
85
84
|
input,
|
|
@@ -146,11 +145,7 @@ function TimeInput(
|
|
|
146
145
|
/>
|
|
147
146
|
<>
|
|
148
147
|
{onPress && inputType === inputTypes.picker ? (
|
|
149
|
-
<TouchableRipple
|
|
150
|
-
style={buttonStyle}
|
|
151
|
-
rippleColor={rippleColor}
|
|
152
|
-
onPress={onPressInput}
|
|
153
|
-
borderless={true}>
|
|
148
|
+
<TouchableRipple style={buttonStyle} onPress={onPressInput} borderless={true}>
|
|
154
149
|
<View />
|
|
155
150
|
</TouchableRipple>
|
|
156
151
|
) : null}
|
|
@@ -97,10 +97,6 @@ const timePickerInputsStylesDefault = StyleSheet.create(theme => ({
|
|
|
97
97
|
}));
|
|
98
98
|
|
|
99
99
|
const timePickerInputStylesDefault = StyleSheet.create(theme => ({
|
|
100
|
-
root: {
|
|
101
|
-
rippleColor: theme.colors.onPrimaryContainer,
|
|
102
|
-
} as any,
|
|
103
|
-
|
|
104
100
|
container: {
|
|
105
101
|
position: 'relative',
|
|
106
102
|
},
|
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
type ViewStyle,
|
|
11
11
|
} from 'react-native';
|
|
12
12
|
|
|
13
|
+
import { useTheme } from '../../hooks/useTheme';
|
|
13
14
|
import { extractPropertiesFromStyles } from '../../utils/extractPropertiesFromStyles';
|
|
14
15
|
import { Slot } from '../Slot';
|
|
16
|
+
import { rippleColorFromBackground } from './rippleFromForegroundColor';
|
|
15
17
|
import { touchableRippleStyles } from './utils';
|
|
16
18
|
|
|
17
19
|
const ANDROID_VERSION_LOLLIPOP = 21;
|
|
@@ -24,6 +26,7 @@ type Props = ComponentProps<typeof TouchableWithoutFeedback> & {
|
|
|
24
26
|
onPress?: () => void | null;
|
|
25
27
|
rippleColor?: string;
|
|
26
28
|
underlayColor?: string;
|
|
29
|
+
rippleAlpha?: number;
|
|
27
30
|
children: ReactNode;
|
|
28
31
|
style?: StyleProp<ViewStyle>;
|
|
29
32
|
/**
|
|
@@ -55,6 +58,7 @@ const TouchableRipple = (
|
|
|
55
58
|
disabled: disabledProp,
|
|
56
59
|
rippleColor: rippleColorProp,
|
|
57
60
|
underlayColor: underlayColorProp,
|
|
61
|
+
rippleAlpha = 0.24,
|
|
58
62
|
children,
|
|
59
63
|
asChild = false,
|
|
60
64
|
...rest
|
|
@@ -62,20 +66,47 @@ const TouchableRipple = (
|
|
|
62
66
|
ref: any,
|
|
63
67
|
) => {
|
|
64
68
|
const disabled = disabledProp;
|
|
69
|
+
const theme = useTheme();
|
|
65
70
|
|
|
66
71
|
const componentStyles = touchableRippleStyles;
|
|
67
72
|
|
|
68
73
|
const { rippleColor, underlayColor, containerStyle } = useMemo(() => {
|
|
69
|
-
const { rippleColor:
|
|
74
|
+
const { rippleColor: themeRippleColor, backgroundColor } = extractPropertiesFromStyles(
|
|
70
75
|
[componentStyles.root, style],
|
|
71
|
-
['rippleColor'],
|
|
76
|
+
['rippleColor', 'backgroundColor'],
|
|
72
77
|
);
|
|
78
|
+
const tokenResolvedColor =
|
|
79
|
+
typeof rippleColorProp === 'string'
|
|
80
|
+
? theme.colors[rippleColorProp as keyof typeof theme.colors]
|
|
81
|
+
: undefined;
|
|
82
|
+
const rippleColorResolvedProp =
|
|
83
|
+
typeof tokenResolvedColor === 'string' ? tokenResolvedColor : rippleColorProp;
|
|
84
|
+
|
|
85
|
+
const fallback =
|
|
86
|
+
typeof themeRippleColor === 'string' && themeRippleColor.length > 0
|
|
87
|
+
? themeRippleColor
|
|
88
|
+
: 'rgba(0, 0, 0, 0.32)';
|
|
89
|
+
|
|
90
|
+
const resolvedRipple =
|
|
91
|
+
rippleColorResolvedProp ??
|
|
92
|
+
(backgroundColor != null && backgroundColor !== ''
|
|
93
|
+
? rippleColorFromBackground(String(backgroundColor), fallback, rippleAlpha)
|
|
94
|
+
: fallback);
|
|
95
|
+
|
|
73
96
|
return {
|
|
74
|
-
rippleColor:
|
|
75
|
-
underlayColor: underlayColorProp
|
|
97
|
+
rippleColor: resolvedRipple,
|
|
98
|
+
underlayColor: underlayColorProp ?? resolvedRipple,
|
|
76
99
|
containerStyle: [borderless && styles.borderless, componentStyles.root, style],
|
|
77
100
|
};
|
|
78
|
-
}, [
|
|
101
|
+
}, [
|
|
102
|
+
borderless,
|
|
103
|
+
componentStyles.root,
|
|
104
|
+
rippleColorProp,
|
|
105
|
+
style,
|
|
106
|
+
underlayColorProp,
|
|
107
|
+
rippleAlpha,
|
|
108
|
+
theme,
|
|
109
|
+
]);
|
|
79
110
|
|
|
80
111
|
// A workaround for ripple on Android P is to use useForeground + overflow: 'hidden'
|
|
81
112
|
// https://github.com/facebook/react-native/issues/6480
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { forwardRef, memo, type ReactNode, useCallback,
|
|
1
|
+
import { forwardRef, memo, type ReactNode, useCallback, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
type GestureResponderEvent,
|
|
4
|
+
Platform,
|
|
4
5
|
Pressable,
|
|
5
6
|
type PressableProps,
|
|
6
7
|
type StyleProp,
|
|
@@ -9,7 +10,9 @@ import {
|
|
|
9
10
|
} from 'react-native';
|
|
10
11
|
import { StyleSheet } from 'react-native-unistyles';
|
|
11
12
|
|
|
13
|
+
import { useTheme } from '../../hooks/useTheme';
|
|
12
14
|
import { Slot } from '../Slot';
|
|
15
|
+
import { rippleColorFromBackground } from './rippleFromForegroundColor';
|
|
13
16
|
import { touchableRippleStyles } from './utils';
|
|
14
17
|
|
|
15
18
|
export type Props = PressableProps & {
|
|
@@ -46,6 +49,11 @@ export type Props = PressableProps & {
|
|
|
46
49
|
* Color of the underlay for the highlight effect (Android < 5.0 and iOS).
|
|
47
50
|
*/
|
|
48
51
|
underlayColor?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Alpha used for auto-derived ripple color when `rippleColor` is not provided.
|
|
54
|
+
* @default 0.24
|
|
55
|
+
*/
|
|
56
|
+
rippleAlpha?: number;
|
|
49
57
|
/**
|
|
50
58
|
* Content of the `TouchableRipple`.
|
|
51
59
|
*/
|
|
@@ -114,6 +122,7 @@ const TouchableRipple = (
|
|
|
114
122
|
disabled: disabledProp,
|
|
115
123
|
rippleColor: rippleColorProp,
|
|
116
124
|
underlayColor: _underlayColor,
|
|
125
|
+
rippleAlpha = 0.24,
|
|
117
126
|
onPress,
|
|
118
127
|
children,
|
|
119
128
|
onPressIn: onPressInProp,
|
|
@@ -126,24 +135,27 @@ const TouchableRipple = (
|
|
|
126
135
|
) => {
|
|
127
136
|
// TODO - enable ripple onLongPress, need to check for mobile as well
|
|
128
137
|
const disabled = disabledProp;
|
|
138
|
+
const theme = useTheme();
|
|
129
139
|
|
|
130
140
|
const componentStyles = touchableRippleStyles;
|
|
131
141
|
|
|
132
|
-
const { rippleColor
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
142
|
+
const { rippleColor: themeRippleFallback } = componentStyles.root;
|
|
143
|
+
|
|
144
|
+
const tokenResolvedColor =
|
|
145
|
+
typeof rippleColorProp === 'string'
|
|
146
|
+
? theme.colors[rippleColorProp as keyof typeof theme.colors]
|
|
147
|
+
: undefined;
|
|
148
|
+
|
|
149
|
+
const rippleColorResolvedProp =
|
|
150
|
+
typeof tokenResolvedColor === 'string' ? tokenResolvedColor : rippleColorProp;
|
|
151
|
+
const containerStyle = [
|
|
152
|
+
styles.touchable,
|
|
153
|
+
{ borderRadius: 'inherit' },
|
|
154
|
+
borderless && styles.borderless,
|
|
155
|
+
// ...(Platform.OS === 'web' && !disabled ? ({ cursor: 'pointer' } as any) : {}),
|
|
156
|
+
componentStyles.root,
|
|
157
|
+
style,
|
|
158
|
+
];
|
|
147
159
|
|
|
148
160
|
// Track whether pointer is currently down for handling pointer leave
|
|
149
161
|
const isPointerDownRef = useRef(false);
|
|
@@ -165,6 +177,16 @@ const TouchableRipple = (
|
|
|
165
177
|
const computedStyle = window.getComputedStyle(button);
|
|
166
178
|
const dimensions = button.getBoundingClientRect();
|
|
167
179
|
|
|
180
|
+
const resolvedRippleColor =
|
|
181
|
+
rippleColorResolvedProp ??
|
|
182
|
+
(Platform.OS === 'web'
|
|
183
|
+
? rippleColorFromBackground(
|
|
184
|
+
computedStyle.backgroundColor,
|
|
185
|
+
String(themeRippleFallback),
|
|
186
|
+
rippleAlpha,
|
|
187
|
+
)
|
|
188
|
+
: String(themeRippleFallback));
|
|
189
|
+
|
|
168
190
|
let touchX: number;
|
|
169
191
|
let touchY: number;
|
|
170
192
|
|
|
@@ -225,7 +247,7 @@ const TouchableRipple = (
|
|
|
225
247
|
Object.assign(ripple.style, {
|
|
226
248
|
position: 'absolute',
|
|
227
249
|
pointerEvents: 'none',
|
|
228
|
-
backgroundColor:
|
|
250
|
+
backgroundColor: resolvedRippleColor,
|
|
229
251
|
borderRadius: '50%',
|
|
230
252
|
|
|
231
253
|
/* Transition configuration */
|
|
@@ -261,7 +283,14 @@ const TouchableRipple = (
|
|
|
261
283
|
});
|
|
262
284
|
});
|
|
263
285
|
},
|
|
264
|
-
[
|
|
286
|
+
[
|
|
287
|
+
onPressInProp,
|
|
288
|
+
disabled,
|
|
289
|
+
centered,
|
|
290
|
+
rippleColorResolvedProp,
|
|
291
|
+
themeRippleFallback,
|
|
292
|
+
rippleAlpha,
|
|
293
|
+
],
|
|
265
294
|
);
|
|
266
295
|
|
|
267
296
|
const fadeOutRipples = useCallback((target: HTMLElement) => {
|
|
@@ -345,9 +374,14 @@ const TouchableRipple = (
|
|
|
345
374
|
onPointerCancel: handlePointerCancel,
|
|
346
375
|
};
|
|
347
376
|
|
|
377
|
+
const accessibilityRoleProp = (rest as { accessibilityRole?: unknown }).accessibilityRole;
|
|
378
|
+
const roleProp = (rest as { role?: unknown }).role;
|
|
379
|
+
const applyDefaultWebButtonRole =
|
|
380
|
+
!!onPress && accessibilityRoleProp === undefined && roleProp === undefined;
|
|
381
|
+
|
|
348
382
|
return (
|
|
349
383
|
<Component
|
|
350
|
-
{...(
|
|
384
|
+
{...(applyDefaultWebButtonRole ? { role: 'button' } : {})}
|
|
351
385
|
{...rest}
|
|
352
386
|
style={containerStyle}
|
|
353
387
|
ref={ref}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import setColor from 'color';
|
|
2
|
+
|
|
3
|
+
/** Ripple ink derived from background color for better contrast. */
|
|
4
|
+
export function rippleColorFromBackground(
|
|
5
|
+
backgroundColor: string | undefined,
|
|
6
|
+
fallback: string,
|
|
7
|
+
alpha: number = 0.24,
|
|
8
|
+
): string {
|
|
9
|
+
if (!backgroundColor || backgroundColor === '') {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const base = setColor(backgroundColor);
|
|
14
|
+
if (base.alpha() < 0.05) {
|
|
15
|
+
return fallback;
|
|
16
|
+
}
|
|
17
|
+
return base.isLight() ? `rgba(0, 0, 0, ${alpha})` : `rgba(255, 255, 255, ${alpha})`;
|
|
18
|
+
} catch {
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-molecules",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.22",
|
|
4
4
|
"author": "Thet Aung <thetaung.dev@gmail.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"sideEffects": [
|
|
8
8
|
"components/DatePicker/context.tsx",
|
|
9
|
+
"components/DatePickerInline/store.tsx",
|
|
10
|
+
"components/List/context.tsx",
|
|
9
11
|
"components/Select/context.tsx",
|
|
10
12
|
"components/TimePicker/context.tsx"
|
|
11
13
|
],
|
|
@@ -78,7 +80,7 @@
|
|
|
78
80
|
"react-native": "0.81.4",
|
|
79
81
|
"react-native-builder-bob": "^0.17.1",
|
|
80
82
|
"react-native-reanimated": "~4.1.1",
|
|
81
|
-
"react-native-unistyles": "^3.
|
|
83
|
+
"react-native-unistyles": "^3.2.4",
|
|
82
84
|
"react-native-web": "~0.21.1"
|
|
83
85
|
},
|
|
84
86
|
"eslintIgnore": [
|