react-native-molecules 0.5.0-beta.20 → 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.
Files changed (59) hide show
  1. package/components/Card/Card.tsx +1 -1
  2. package/components/Checkbox/CheckboxBase.ios.tsx +9 -16
  3. package/components/Checkbox/CheckboxBase.tsx +11 -18
  4. package/components/DateField/DateField.tsx +4 -3
  5. package/components/DatePicker/DateCalendar.tsx +4 -4
  6. package/components/DatePicker/DatePickerModal.tsx +35 -23
  7. package/components/DatePicker/DatePickerProvider.tsx +8 -2
  8. package/components/DatePicker/context.tsx +2 -1
  9. package/components/DatePicker/index.tsx +1 -0
  10. package/components/DatePickerInline/DatePickerDockedHeader.tsx +11 -7
  11. package/components/DatePickerInline/DatePickerInline.tsx +1 -1
  12. package/components/DatePickerInline/DatePickerInlineBase.tsx +3 -3
  13. package/components/DatePickerInline/DatePickerInlineHeader.tsx +50 -20
  14. package/components/DatePickerInline/DayNames.tsx +13 -10
  15. package/components/DatePickerInline/HeaderItem.tsx +2 -2
  16. package/components/DatePickerInline/Month.tsx +4 -3
  17. package/components/DatePickerInline/MonthPicker.tsx +74 -54
  18. package/components/DatePickerInline/Swiper.native.tsx +2 -2
  19. package/components/DatePickerInline/Swiper.tsx +3 -3
  20. package/components/DatePickerInline/YearPicker.tsx +136 -112
  21. package/components/DatePickerInline/{DatePickerContext.tsx → store.tsx} +7 -3
  22. package/components/DatePickerInline/types.ts +4 -3
  23. package/components/Divider/Divider.tsx +192 -0
  24. package/components/Divider/index.tsx +11 -0
  25. package/components/Drawer/DrawerItemGroup.tsx +3 -7
  26. package/components/IconButton/IconButton.tsx +2 -12
  27. package/components/List/List.tsx +507 -0
  28. package/components/List/context.tsx +28 -0
  29. package/components/List/index.ts +9 -0
  30. package/components/List/types.ts +149 -0
  31. package/components/{ListItem → List}/utils.ts +47 -50
  32. package/components/Menu/Menu.tsx +156 -12
  33. package/components/Menu/index.tsx +11 -7
  34. package/components/Menu/utils.ts +21 -70
  35. package/components/RadioButton/RadioButtonAndroid.tsx +38 -54
  36. package/components/RadioButton/RadioButtonIOS.tsx +2 -16
  37. package/components/Select/Select.tsx +139 -497
  38. package/components/Select/context.tsx +14 -32
  39. package/components/Select/types.ts +44 -53
  40. package/components/Select/utils.ts +15 -47
  41. package/components/Text/textFactory.tsx +17 -5
  42. package/components/TimeField/TimeField.tsx +1 -1
  43. package/components/TimePicker/TimeInput.tsx +2 -7
  44. package/components/TimePicker/TimePickerModal.tsx +15 -15
  45. package/components/TimePicker/utils.ts +0 -4
  46. package/components/TouchableRipple/TouchableRipple.native.tsx +36 -5
  47. package/components/TouchableRipple/TouchableRipple.tsx +53 -19
  48. package/components/TouchableRipple/rippleFromForegroundColor.ts +21 -0
  49. package/package.json +4 -2
  50. package/components/HorizontalDivider/HorizontalDivider.tsx +0 -103
  51. package/components/HorizontalDivider/index.tsx +0 -9
  52. package/components/ListItem/ListItem.tsx +0 -138
  53. package/components/ListItem/ListItemDescription.tsx +0 -25
  54. package/components/ListItem/ListItemTitle.tsx +0 -25
  55. package/components/ListItem/index.tsx +0 -14
  56. package/components/Menu/MenuDivider.tsx +0 -13
  57. package/components/Menu/MenuItem.tsx +0 -128
  58. package/components/VerticalDivider/VerticalDivider.tsx +0 -100
  59. 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 { DefaultItemT, SelectContextValue, SelectDropdownContextValue } from './types';
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([SelectContext, SelectDropdownContext]);
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
- export type SelectProps<Option extends DefaultItemT = DefaultItemT> = {
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
- export type SelectOptionProps<Option extends DefaultItemT = DefaultItemT> = ViewProps & {
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?: ReactNode;
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: TextProps, ref: any) => {
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 styles =
22
- hasAncestorText && !isBlockLevelElement ? style : [componentStyles?.root, style];
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} />
@@ -60,11 +60,11 @@ function TimeField({ ref, disabled: disabledProp, onBlur, onFocus, ...rest }: Ti
60
60
 
61
61
  return (
62
62
  <TextInput
63
+ placeholder={timeFormat[is24Hour ? '24' : '12'].format}
63
64
  {...rest}
64
65
  ref={ref}
65
66
  disabled={disabled}
66
67
  value={formatted}
67
- placeholder={timeFormat[is24Hour ? '24' : '12'].format}
68
68
  onChangeText={onChangeText}
69
69
  onBlur={onInnerBlur}
70
70
  onFocus={onInnerFocus}
@@ -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 { rippleColor, containerStyle, textInputStyle, buttonStyle } = useMemo(() => {
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}
@@ -5,7 +5,11 @@ import { KeyboardAvoidingView, Platform, View } from 'react-native';
5
5
  import { getRegisteredComponentWithFallback } from '../../core';
6
6
  import { useControlledValue } from '../../hooks';
7
7
  import { DatePickerActions, DatePickerProvider } from '../DatePicker';
8
- import type { DatePickerContextType, DatePickerValue } from '../DatePicker/context';
8
+ import type {
9
+ DatePickerContextType,
10
+ DatePickerLocale,
11
+ DatePickerValue,
12
+ } from '../DatePicker/context';
9
13
  import {
10
14
  DatePickerContext,
11
15
  useDatePickerContext,
@@ -32,6 +36,7 @@ export type TimePickerModalProps = Omit<ModalProps, 'children' | 'isOpen' | 'onC
32
36
  onClose?: () => void;
33
37
  value?: DatePickerValue;
34
38
  onChange?: (value: DatePickerValue) => void;
39
+ locale?: DatePickerLocale;
35
40
  is24Hour?: boolean;
36
41
  inputType?: PossibleInputTypes;
37
42
  defaultInputType?: PossibleInputTypes;
@@ -119,31 +124,24 @@ function TimePickerModalBody({
119
124
  function TimePickerModalLayer({
120
125
  base,
121
126
  draft: draftProp,
122
- bodyProps,
123
- }: {
127
+ ...rest
128
+ }: BodyProps & {
124
129
  base: DatePickerContextType;
125
130
  draft: boolean | undefined;
126
- bodyProps: BodyProps;
127
131
  }) {
128
132
  const effectiveDraft = draftProp ?? base.providerDraft ?? true;
129
133
  const ctx = useMemo(() => withDraftLayer(base, effectiveDraft), [base, effectiveDraft]);
130
134
  if (!base.open) return null;
131
135
  return (
132
136
  <DatePickerContext value={ctx}>
133
- <TimePickerModalBody {...bodyProps} />
137
+ <TimePickerModalBody {...rest} />
134
138
  </DatePickerContext>
135
139
  );
136
140
  }
137
141
 
138
- function TimePickerModalAdapter({
139
- draft,
140
- bodyProps,
141
- }: {
142
- draft: boolean | undefined;
143
- bodyProps: BodyProps;
144
- }) {
142
+ function TimePickerModalAdapter({ draft, ...rest }: BodyProps & { draft: boolean | undefined }) {
145
143
  const base = useDatePickerContext();
146
- return <TimePickerModalLayer base={base} draft={draft} bodyProps={bodyProps} />;
144
+ return <TimePickerModalLayer base={base} draft={draft} {...rest} />;
147
145
  }
148
146
 
149
147
  const TimePickerModalDefault = memo(
@@ -152,6 +150,7 @@ const TimePickerModalDefault = memo(
152
150
  onClose: onCloseProp,
153
151
  value: valueProp,
154
152
  onChange: onChangeProp,
153
+ locale,
155
154
  is24Hour,
156
155
  draft: draftProp,
157
156
  ...rest
@@ -159,7 +158,7 @@ const TimePickerModalDefault = memo(
159
158
  const outer = useOptionalDatePickerContext();
160
159
 
161
160
  if (outer) {
162
- return <TimePickerModalLayer base={outer} draft={draftProp} bodyProps={rest} />;
161
+ return <TimePickerModalLayer base={outer} draft={draftProp} {...rest} />;
163
162
  }
164
163
 
165
164
  return (
@@ -168,11 +167,12 @@ const TimePickerModalDefault = memo(
168
167
  value={valueProp}
169
168
  onChange={onChangeProp}
170
169
  open={isOpenProp}
170
+ locale={locale}
171
171
  onOpenChange={next => {
172
172
  if (!next) onCloseProp?.();
173
173
  }}
174
174
  is24Hour={is24Hour}>
175
- <TimePickerModalAdapter draft={draftProp} bodyProps={rest} />
175
+ <TimePickerModalAdapter draft={draftProp} {...rest} />
176
176
  </DatePickerProvider>
177
177
  );
178
178
  },
@@ -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: _rippleColor } = extractPropertiesFromStyles(
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: rippleColorProp || _rippleColor,
75
- underlayColor: underlayColorProp || rippleColorProp,
97
+ rippleColor: resolvedRipple,
98
+ underlayColor: underlayColorProp ?? resolvedRipple,
76
99
  containerStyle: [borderless && styles.borderless, componentStyles.root, style],
77
100
  };
78
- }, [borderless, componentStyles.root, rippleColorProp, style, underlayColorProp]);
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