react-native-molecules 0.5.0-beta.3 → 0.5.0-beta.30
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/Accordion/Accordion.tsx +2 -6
- package/components/Accordion/AccordionItem.tsx +16 -12
- package/components/Accordion/AccordionItemContent.tsx +6 -1
- package/components/Accordion/AccordionItemHeader.tsx +1 -1
- package/components/Accordion/utils.ts +6 -0
- package/components/ActivityIndicator/ActivityIndicator.tsx +6 -15
- package/components/Appbar/AppbarBase.tsx +18 -13
- package/components/Button/Button.tsx +211 -264
- package/components/Button/index.tsx +9 -3
- package/components/Button/types.ts +16 -2
- package/components/Button/utils.ts +230 -208
- package/components/Card/Card.tsx +1 -1
- package/components/Checkbox/Checkbox.tsx +125 -88
- package/components/Checkbox/CheckboxBase.ios.tsx +14 -23
- package/components/Checkbox/CheckboxBase.tsx +21 -137
- 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 -108
- package/components/Chip/Chip.tsx +41 -52
- package/components/Chip/utils.ts +3 -7
- package/components/DateField/DateField.tsx +111 -0
- package/components/DateField/index.tsx +6 -0
- package/components/{DatePickerInput/inputUtils.ts → DateField/useDateFieldState.ts} +19 -51
- package/components/DatePicker/DateCalendar.tsx +83 -0
- package/components/DatePicker/DatePickerActions.tsx +73 -0
- package/components/DatePicker/DatePickerModal.tsx +246 -0
- package/components/DatePicker/DatePickerPopover.tsx +79 -0
- package/components/DatePicker/DatePickerProvider.tsx +158 -0
- package/components/DatePicker/DatePickerTrigger.tsx +23 -0
- package/components/DatePicker/context.tsx +83 -0
- package/components/DatePicker/index.tsx +45 -0
- package/components/DatePicker/utils.ts +295 -0
- package/components/DatePickerInline/DatePickerDockedHeader.tsx +117 -0
- package/components/DatePickerInline/DatePickerInline.tsx +17 -16
- package/components/DatePickerInline/DatePickerInlineBase.tsx +11 -5
- package/components/DatePickerInline/DatePickerInlineHeader.tsx +50 -20
- package/components/DatePickerInline/Day.tsx +25 -1
- package/components/DatePickerInline/DayNames.tsx +13 -10
- package/components/DatePickerInline/DayRange.tsx +2 -4
- package/components/DatePickerInline/HeaderItem.tsx +44 -29
- package/components/DatePickerInline/Month.tsx +48 -67
- package/components/DatePickerInline/MonthPicker.tsx +80 -92
- package/components/DatePickerInline/Swiper.native.tsx +21 -4
- package/components/DatePickerInline/Swiper.tsx +169 -14
- package/components/DatePickerInline/SwiperUtils.ts +1 -1
- package/components/DatePickerInline/Week.tsx +6 -1
- package/components/DatePickerInline/YearPicker.tsx +220 -78
- package/components/DatePickerInline/dateUtils.tsx +18 -13
- package/components/DatePickerInline/store.tsx +27 -0
- package/components/DatePickerInline/types.ts +6 -2
- package/components/DatePickerInline/utils.ts +66 -29
- package/components/Divider/Divider.tsx +192 -0
- package/components/Divider/index.tsx +10 -0
- package/components/Drawer/Drawer.tsx +17 -6
- package/components/Drawer/DrawerItemGroup.tsx +3 -7
- package/components/ElementGroup/ElementGroup.tsx +1 -1
- package/components/FilePicker/FilePicker.tsx +48 -78
- package/components/FilePicker/index.tsx +2 -1
- package/components/FilePicker/utils.ts +9 -0
- package/components/HelperText/HelperText.tsx +0 -35
- package/components/Icon/iconFactory.tsx +5 -4
- package/components/Icon/index.tsx +1 -1
- package/components/Icon/types.ts +17 -6
- package/components/IconButton/IconButton.tsx +84 -84
- package/components/IconButton/index.tsx +1 -0
- package/components/IconButton/types.ts +10 -0
- package/components/IconButton/utils.ts +167 -33
- package/components/List/List.tsx +276 -0
- package/components/List/context.tsx +27 -0
- package/components/List/index.ts +8 -0
- package/components/List/types.ts +117 -0
- package/components/List/utils.ts +79 -0
- 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/components/Menu/Menu.tsx +162 -39
- package/components/Menu/index.tsx +10 -7
- package/components/Menu/utils.ts +21 -70
- package/components/NavigationRail/NavigationRail.tsx +15 -9
- package/components/Popover/Popover.tsx +119 -145
- package/components/Popover/PopoverRoot.tsx +60 -0
- package/components/Popover/common.ts +54 -34
- package/components/Popover/index.ts +12 -1
- package/components/Popover/usePlatformMeasure.native.ts +90 -0
- package/components/Popover/usePlatformMeasure.ts +120 -0
- package/components/Popover/utils.ts +34 -0
- package/components/Portal/Portal.tsx +1 -2
- package/components/Radio/Radio.tsx +188 -0
- package/components/Radio/RadioBase.ios.tsx +69 -0
- package/components/Radio/RadioBase.tsx +136 -0
- 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/components/Rating/Rating.tsx +1 -1
- package/components/Select/Select.tsx +521 -785
- package/components/Select/context.tsx +81 -0
- package/components/Select/index.ts +26 -14
- package/components/Select/types.ts +65 -58
- package/components/Select/utils.ts +126 -0
- package/components/Slot/Slot.tsx +244 -0
- package/components/Slot/compose-refs.tsx +62 -0
- package/components/Slot/index.tsx +8 -0
- package/components/Surface/Surface.android.tsx +32 -7
- package/components/Surface/Surface.ios.tsx +34 -29
- package/components/Surface/Surface.tsx +31 -4
- package/components/Surface/utils.ts +44 -6
- package/components/Switch/Switch.ios.tsx +1 -1
- package/components/Switch/Switch.tsx +10 -3
- package/components/Tabs/TabItem.tsx +35 -58
- package/components/Tabs/TabLabel.tsx +5 -9
- package/components/Tabs/Tabs.tsx +156 -150
- package/components/Tabs/utils.ts +15 -2
- package/components/Text/textFactory.tsx +17 -5
- package/components/TextInput/TextInput.tsx +663 -579
- package/components/TextInput/index.tsx +19 -3
- package/components/TextInput/types.ts +77 -28
- package/components/TextInput/utils.ts +235 -145
- package/components/TimeField/TimeField.tsx +75 -0
- package/components/TimeField/index.tsx +6 -0
- package/components/TimeField/useTimeFieldState.ts +70 -0
- package/components/{TimePickerField/sanitizeTime.ts → TimeField/utils.ts} +77 -10
- package/components/TimePicker/AnalogClock.tsx +1 -1
- package/components/TimePicker/TimeInput.tsx +87 -42
- package/components/TimePicker/TimeInputs.tsx +138 -50
- package/components/TimePicker/TimePicker.tsx +74 -11
- package/components/TimePicker/TimePickerModal.tsx +186 -0
- package/components/TimePicker/context.tsx +17 -0
- package/components/TimePicker/index.tsx +15 -3
- package/components/TimePicker/utils.ts +93 -4
- package/components/Tooltip/Tooltip.tsx +42 -67
- package/components/Tooltip/TooltipContent.tsx +32 -5
- package/components/Tooltip/TooltipTrigger.tsx +20 -20
- package/components/Tooltip/index.tsx +1 -1
- package/components/TouchableRipple/TouchableRipple.native.tsx +83 -16
- package/components/TouchableRipple/TouchableRipple.tsx +150 -102
- package/components/TouchableRipple/rippleFromForegroundColor.ts +21 -0
- package/hocs/index.tsx +1 -1
- package/hocs/withKeyboardAccessibility.tsx +2 -3
- package/hocs/withPortal.tsx +1 -1
- package/hooks/index.tsx +2 -12
- package/hooks/useActionState.tsx +19 -8
- package/hooks/useContrastColor.ts +1 -2
- package/hooks/useFilePicker.tsx +7 -17
- package/hooks/useHandleNumberFormat.tsx +2 -2
- package/hooks/useMediaQuery.tsx +1 -2
- package/package.json +95 -111
- package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +6 -3
- package/shortcuts-manager/ShortcutsManager/utils.tsx +1 -1
- package/shortcuts-manager/useSetScopes/useSetScopes.tsx +1 -1
- package/shortcuts-manager/useShortcut/useShortcut.tsx +1 -1
- package/styles/shadow.ts +2 -1
- package/styles/themes/LightTheme.tsx +1 -1
- package/utils/DocumentPicker/documentPicker.ts +78 -27
- package/utils/DocumentPicker/types.ts +0 -1
- package/utils/extractSubcomponents.ts +89 -0
- package/utils/extractTextStyles.ts +1 -2
- package/utils/formatNumberWithMask/formatNumberWithMask.ts +2 -1
- package/utils/index.ts +0 -3
- package/utils/normalizeToNumberString/normalizeToNumberString.ts +1 -1
- package/components/DatePickerDocked/DatePickerDocked.tsx +0 -30
- package/components/DatePickerDocked/DatePickerDockedHeader.tsx +0 -129
- package/components/DatePickerDocked/index.tsx +0 -17
- package/components/DatePickerDocked/types.ts +0 -11
- package/components/DatePickerDocked/utils.ts +0 -157
- package/components/DatePickerInline/DatePickerContext.tsx +0 -21
- package/components/DatePickerInput/DatePickerInput.tsx +0 -139
- package/components/DatePickerInput/DatePickerInputModal.tsx +0 -48
- package/components/DatePickerInput/DatePickerInputWithoutModal.tsx +0 -77
- package/components/DatePickerInput/DateRangeInput.tsx +0 -88
- package/components/DatePickerInput/index.tsx +0 -10
- package/components/DatePickerInput/types.ts +0 -28
- package/components/DatePickerInput/utils.ts +0 -15
- package/components/DatePickerModal/AnimatedCrossView.tsx +0 -94
- package/components/DatePickerModal/CalendarEdit.tsx +0 -139
- package/components/DatePickerModal/DatePickerModal.tsx +0 -85
- package/components/DatePickerModal/DatePickerModalContent.tsx +0 -155
- package/components/DatePickerModal/DatePickerModalContentHeader.tsx +0 -213
- package/components/DatePickerModal/DatePickerModalHeader.tsx +0 -74
- package/components/DatePickerModal/DatePickerModalHeaderBackground.tsx +0 -13
- package/components/DatePickerModal/index.tsx +0 -16
- package/components/DatePickerModal/types.ts +0 -92
- package/components/DatePickerModal/utils.ts +0 -122
- package/components/DateTimePicker/DateTimePicker.tsx +0 -172
- package/components/DateTimePicker/index.tsx +0 -10
- package/components/DateTimePicker/utils.ts +0 -12
- package/components/HorizontalDivider/HorizontalDivider.tsx +0 -103
- package/components/HorizontalDivider/index.tsx +0 -9
- package/components/ListItem/ListItem.tsx +0 -136
- package/components/ListItem/ListItemDescription.tsx +0 -25
- package/components/ListItem/ListItemTitle.tsx +0 -25
- package/components/ListItem/index.tsx +0 -14
- package/components/ListItem/utils.ts +0 -115
- package/components/Menu/MenuDivider.tsx +0 -13
- package/components/Menu/MenuItem.tsx +0 -128
- package/components/Popover/Popover.native.tsx +0 -185
- package/components/RadioButton/RadioButton.tsx +0 -138
- package/components/RadioButton/RadioButtonAndroid.tsx +0 -188
- package/components/RadioButton/RadioButtonGroup.tsx +0 -98
- package/components/RadioButton/RadioButtonIOS.tsx +0 -106
- package/components/RadioButton/RadioButtonItem.tsx +0 -232
- package/components/RadioButton/index.ts +0 -22
- package/components/RadioButton/utils.ts +0 -165
- package/components/TimePickerField/TimePickerField.tsx +0 -152
- package/components/TimePickerField/index.tsx +0 -10
- package/components/TimePickerField/utils.ts +0 -94
- package/components/TimePickerModal/TimePickerModal.tsx +0 -115
- package/components/TimePickerModal/index.tsx +0 -10
- package/components/TimePickerModal/utils.ts +0 -47
- package/components/VerticalDivider/VerticalDivider.tsx +0 -100
- package/components/VerticalDivider/index.tsx +0 -9
- package/context-bridge/index.tsx +0 -87
- package/fast-context/index.tsx +0 -190
- package/hocs/typedMemo.tsx +0 -5
- package/hooks/useControlledValue.tsx +0 -68
- package/hooks/useLatest.tsx +0 -9
- package/hooks/useMergedRefs.ts +0 -14
- package/hooks/usePrevious.ts +0 -13
- package/hooks/useSearchable.tsx +0 -74
- package/hooks/useSubcomponents.tsx +0 -59
- package/hooks/useToggle.tsx +0 -24
- package/utils/color.ts +0 -22
- package/utils/compare/index.ts +0 -54
- package/utils/lodash.ts +0 -49
- package/utils/repository.ts +0 -53
package/components/Tabs/Tabs.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { noop } from '@react-native-molecules/utils/helpers/lodash';
|
|
2
|
+
import { useControlledValue } from '@react-native-molecules/utils/hooks';
|
|
1
3
|
import {
|
|
2
|
-
Children,
|
|
3
4
|
cloneElement,
|
|
4
|
-
type
|
|
5
|
-
isValidElement,
|
|
6
|
-
memo,
|
|
5
|
+
type ComponentType,
|
|
7
6
|
type ReactElement,
|
|
8
7
|
useCallback,
|
|
9
8
|
useEffect,
|
|
@@ -24,21 +23,20 @@ import {
|
|
|
24
23
|
type ViewStyle,
|
|
25
24
|
} from 'react-native';
|
|
26
25
|
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { HorizontalDivider, type HorizontalDividerProps } from '../HorizontalDivider';
|
|
26
|
+
import { typedMemo } from '../../hocs';
|
|
27
|
+
import { extractSubcomponents } from '../../utils/extractSubcomponents';
|
|
30
28
|
import type { TabItemProps } from './TabItem';
|
|
31
29
|
import { tabsStyles } from './utils';
|
|
32
30
|
|
|
33
|
-
export type TabsProps = ViewProps & {
|
|
31
|
+
export type TabsProps<T extends string | number> = ViewProps & {
|
|
34
32
|
/**
|
|
35
33
|
* child tab name
|
|
36
34
|
* */
|
|
37
|
-
value?:
|
|
35
|
+
value?: T;
|
|
38
36
|
/**
|
|
39
37
|
* defaultValue to preselected for uncontrolled mode
|
|
40
38
|
* */
|
|
41
|
-
defaultValue?:
|
|
39
|
+
defaultValue?: T;
|
|
42
40
|
/**
|
|
43
41
|
* to enable scroll
|
|
44
42
|
* */
|
|
@@ -46,7 +44,7 @@ export type TabsProps = ViewProps & {
|
|
|
46
44
|
/**
|
|
47
45
|
* on name change callback.
|
|
48
46
|
* */
|
|
49
|
-
onChange?: (value:
|
|
47
|
+
onChange?: (value: T) => void;
|
|
50
48
|
/**
|
|
51
49
|
* Disable the active indicator below.
|
|
52
50
|
* */
|
|
@@ -58,10 +56,6 @@ export type TabsProps = ViewProps & {
|
|
|
58
56
|
|
|
59
57
|
indicatorProps?: Omit<ViewStyle, 'style'>;
|
|
60
58
|
|
|
61
|
-
dividerStyle?: ViewStyle;
|
|
62
|
-
|
|
63
|
-
dividerProps?: Omit<HorizontalDividerProps, 'style'>;
|
|
64
|
-
|
|
65
59
|
/** Define the background Variant. */
|
|
66
60
|
variant?: 'primary' | 'secondary';
|
|
67
61
|
activeColor?: string;
|
|
@@ -69,7 +63,7 @@ export type TabsProps = ViewProps & {
|
|
|
69
63
|
|
|
70
64
|
const emptyObj = {};
|
|
71
65
|
|
|
72
|
-
export const TabBase = ({
|
|
66
|
+
export const TabBase = <T extends string | number>({
|
|
73
67
|
children,
|
|
74
68
|
value: valueProp,
|
|
75
69
|
defaultValue,
|
|
@@ -80,84 +74,75 @@ export const TabBase = ({
|
|
|
80
74
|
style,
|
|
81
75
|
variant = 'primary',
|
|
82
76
|
indicatorProps,
|
|
83
|
-
dividerStyle: dividerStyleProp = emptyObj,
|
|
84
|
-
dividerProps,
|
|
85
77
|
activeColor: activeColorProp,
|
|
86
78
|
testID,
|
|
87
79
|
...rest
|
|
88
|
-
}: TabsProps) => {
|
|
80
|
+
}: TabsProps<T>) => {
|
|
89
81
|
tabsStyles.useVariants({
|
|
90
82
|
variant,
|
|
91
83
|
});
|
|
92
84
|
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
),
|
|
98
|
-
[children],
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const nameToIndexMap = useMemo(
|
|
102
|
-
() =>
|
|
103
|
-
validChildren.reduce((acc, child, currentIndex) => {
|
|
104
|
-
acc[(child as ReactElement<TabItemProps>).props?.name] = currentIndex;
|
|
85
|
+
const { Tabs_Item: tabItems } = extractSubcomponents({
|
|
86
|
+
children,
|
|
87
|
+
allowedChildren: ['Tabs_Item'],
|
|
88
|
+
});
|
|
105
89
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
90
|
+
// Get ordered list of tab names for current children
|
|
91
|
+
const tabNames = useMemo(
|
|
92
|
+
() => tabItems.map(child => (child as ReactElement<TabItemProps<T>>).props?.name),
|
|
93
|
+
[tabItems],
|
|
109
94
|
);
|
|
110
95
|
|
|
111
96
|
const [value, onChange] = useControlledValue({
|
|
112
97
|
value: valueProp,
|
|
113
98
|
onChange: onChangeProp,
|
|
114
|
-
defaultValue: defaultValue ||
|
|
99
|
+
defaultValue: defaultValue || tabNames[0],
|
|
115
100
|
});
|
|
116
101
|
|
|
117
|
-
const valueIndex =
|
|
102
|
+
const valueIndex = useMemo(() => tabNames.indexOf(value), [value, tabNames]);
|
|
103
|
+
const previousTabCountRef = useRef(tabNames.length);
|
|
118
104
|
|
|
119
105
|
const positionAnimationRef = useRef(new Animated.Value(0));
|
|
120
106
|
const widthAnimationRef = useRef(new Animated.Value(0));
|
|
121
107
|
const scrollViewRef = useRef<RNScrollView>(null);
|
|
122
108
|
const scrollViewPosition = useRef(0);
|
|
123
109
|
|
|
124
|
-
const tabItemPositions = useRef<
|
|
110
|
+
const tabItemPositions = useRef<Map<T, { width: number; contentWidth: number }>>(new Map());
|
|
125
111
|
const [tabContainerWidth, setTabContainerWidth] = useState(0);
|
|
112
|
+
const [layoutVersion, setLayoutVersion] = useState(0);
|
|
126
113
|
|
|
127
114
|
const itemPositionsMap = useMemo(() => {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return totalWidth;
|
|
135
|
-
}, 0);
|
|
115
|
+
// Build positions based on current render order
|
|
116
|
+
let accumulatedWidth = 0;
|
|
117
|
+
return tabNames.reduce((acc, name, index) => {
|
|
118
|
+
const itemData = tabItemPositions.current.get(name);
|
|
119
|
+
if (!itemData) return acc;
|
|
136
120
|
|
|
137
121
|
acc[index] =
|
|
138
122
|
variant === 'primary'
|
|
139
|
-
?
|
|
140
|
-
:
|
|
123
|
+
? accumulatedWidth + (itemData.width - itemData.contentWidth) / 2
|
|
124
|
+
: accumulatedWidth;
|
|
141
125
|
|
|
126
|
+
accumulatedWidth += itemData.width || 0;
|
|
142
127
|
return acc;
|
|
143
128
|
}, {} as Record<number, number>);
|
|
144
|
-
// to make useMemo in sync with the ref
|
|
145
129
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
146
|
-
}, [variant,
|
|
130
|
+
}, [variant, tabNames, layoutVersion]);
|
|
147
131
|
|
|
148
132
|
const itemWidthsMap = useMemo(() => {
|
|
149
|
-
return
|
|
150
|
-
|
|
133
|
+
return tabNames.reduce((acc, name, index) => {
|
|
134
|
+
const itemData = tabItemPositions.current.get(name);
|
|
135
|
+
if (!itemData) return acc;
|
|
151
136
|
|
|
137
|
+
acc[index] = variant === 'primary' ? itemData.contentWidth : itemData.width;
|
|
152
138
|
return acc;
|
|
153
139
|
}, {} as Record<number, number>);
|
|
154
|
-
// to make useMemo in sync with the ref
|
|
155
140
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
156
|
-
}, [variant,
|
|
141
|
+
}, [variant, tabNames, layoutVersion]);
|
|
157
142
|
|
|
158
143
|
const scrollHandler = useCallback(
|
|
159
144
|
(currValue: number) => {
|
|
160
|
-
if (tabItemPositions.current.
|
|
145
|
+
if (tabItemPositions.current.size > currValue) {
|
|
161
146
|
const itemStartPosition = currValue === 0 ? 0 : itemPositionsMap[currValue - 1];
|
|
162
147
|
const itemEndPosition = itemPositionsMap[currValue];
|
|
163
148
|
|
|
@@ -182,45 +167,76 @@ export const TabBase = ({
|
|
|
182
167
|
[itemPositionsMap, tabContainerWidth],
|
|
183
168
|
);
|
|
184
169
|
|
|
170
|
+
// Animate indicator position and width when value changes
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
Animated.parallel([
|
|
173
|
+
Animated.timing(positionAnimationRef.current, {
|
|
174
|
+
toValue: valueIndex,
|
|
175
|
+
useNativeDriver: false,
|
|
176
|
+
duration: 170,
|
|
177
|
+
}),
|
|
178
|
+
Animated.timing(widthAnimationRef.current, {
|
|
179
|
+
toValue: valueIndex,
|
|
180
|
+
useNativeDriver: false,
|
|
181
|
+
duration: 170,
|
|
182
|
+
}),
|
|
183
|
+
]).start();
|
|
184
|
+
|
|
185
|
+
if (scrollable) {
|
|
186
|
+
requestAnimationFrame(() => scrollHandler(valueIndex));
|
|
187
|
+
}
|
|
188
|
+
}, [scrollHandler, valueIndex, scrollable]);
|
|
189
|
+
|
|
190
|
+
// Handle tab count changes
|
|
185
191
|
useEffect(() => {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
const currentTabCount = tabNames.length;
|
|
193
|
+
|
|
194
|
+
if (currentTabCount !== previousTabCountRef.current) {
|
|
195
|
+
// Clean up positions for removed tabs
|
|
196
|
+
const currentTabNamesSet = new Set(tabNames);
|
|
197
|
+
tabItemPositions.current.forEach((_, name) => {
|
|
198
|
+
if (!currentTabNamesSet.has(name)) {
|
|
199
|
+
tabItemPositions.current.delete(name);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
191
202
|
|
|
192
|
-
|
|
193
|
-
|
|
203
|
+
// Trigger re-calculation
|
|
204
|
+
setLayoutVersion(v => v + 1);
|
|
194
205
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
206
|
+
// Clamp animated values when tabs are removed
|
|
207
|
+
if (currentTabCount < previousTabCountRef.current) {
|
|
208
|
+
const maxValidIndex = Math.max(0, currentTabCount - 1);
|
|
209
|
+
positionAnimationRef.current.setValue(Math.min(valueIndex, maxValidIndex));
|
|
210
|
+
widthAnimationRef.current.setValue(Math.min(valueIndex, maxValidIndex));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
previousTabCountRef.current = currentTabCount;
|
|
215
|
+
}, [tabNames, valueIndex]);
|
|
202
216
|
|
|
203
217
|
const onScrollHandler = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
204
218
|
scrollViewPosition.current = event.nativeEvent.contentOffset.x;
|
|
205
219
|
}, []);
|
|
206
220
|
|
|
207
|
-
const transitionInterpolateWithMap = useCallback(
|
|
208
|
-
(obj
|
|
209
|
-
|
|
210
|
-
if (countItems < 2 || !tabItemPositions.current.length) {
|
|
211
|
-
// if there's only one item, use the value of that
|
|
212
|
-
return Object.values(obj)[0] || 0;
|
|
213
|
-
}
|
|
214
|
-
const inputRange = Array.from(Array(countItems).keys());
|
|
215
|
-
const outputRange = Object.values(obj);
|
|
221
|
+
const transitionInterpolateWithMap = useCallback((obj: Record<number, number>) => {
|
|
222
|
+
const entries = Object.entries(obj);
|
|
223
|
+
const countItems = entries.length;
|
|
216
224
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
225
|
+
if (countItems < 2 || !tabItemPositions.current.size) {
|
|
226
|
+
// If there's only one item or no layout data, use the first value
|
|
227
|
+
return Object.values(obj)[0] || 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Use indices from the map entries to ensure inputRange matches outputRange
|
|
231
|
+
const inputRange = entries.map(([key]) => Number(key));
|
|
232
|
+
const outputRange = entries.map(([, val]) => val);
|
|
233
|
+
|
|
234
|
+
return positionAnimationRef.current.interpolate({
|
|
235
|
+
inputRange,
|
|
236
|
+
outputRange,
|
|
237
|
+
extrapolate: 'clamp',
|
|
238
|
+
});
|
|
239
|
+
}, []);
|
|
224
240
|
|
|
225
241
|
const indicatorTransitionInterpolate = useMemo(() => {
|
|
226
242
|
return transitionInterpolateWithMap(itemPositionsMap);
|
|
@@ -230,14 +246,12 @@ export const TabBase = ({
|
|
|
230
246
|
return transitionInterpolateWithMap(itemWidthsMap);
|
|
231
247
|
}, [transitionInterpolateWithMap, itemWidthsMap]);
|
|
232
248
|
|
|
233
|
-
const { containerStyle,
|
|
234
|
-
const { indicator, itemsContainer
|
|
249
|
+
const { containerStyle, indicatorStyle } = useMemo(() => {
|
|
250
|
+
const { indicator, itemsContainer } = tabsStyles;
|
|
235
251
|
const { activeColor, ...restStyle } = tabsStyles.root;
|
|
236
252
|
|
|
237
253
|
return {
|
|
238
|
-
containerStyle: [restStyle, style],
|
|
239
|
-
itemsContainerStyle: itemsContainer,
|
|
240
|
-
dividerStyle: [divider, dividerStyleProp],
|
|
254
|
+
containerStyle: [restStyle, itemsContainer, style],
|
|
241
255
|
indicatorStyle: [
|
|
242
256
|
indicator,
|
|
243
257
|
{
|
|
@@ -254,7 +268,6 @@ export const TabBase = ({
|
|
|
254
268
|
};
|
|
255
269
|
}, [
|
|
256
270
|
style,
|
|
257
|
-
dividerStyleProp,
|
|
258
271
|
activeColorProp,
|
|
259
272
|
indicatorTransitionInterpolate,
|
|
260
273
|
widthTransitionInterpolate,
|
|
@@ -268,7 +281,6 @@ export const TabBase = ({
|
|
|
268
281
|
ref: scrollViewRef,
|
|
269
282
|
onScroll: onScrollHandler,
|
|
270
283
|
showsHorizontalScrollIndicator: false,
|
|
271
|
-
style: itemsContainerStyle,
|
|
272
284
|
}
|
|
273
285
|
: {};
|
|
274
286
|
|
|
@@ -276,95 +288,88 @@ export const TabBase = ({
|
|
|
276
288
|
setTabContainerWidth(layout.width);
|
|
277
289
|
}, []);
|
|
278
290
|
|
|
279
|
-
const onLayoutItem = useCallback((event: LayoutChangeEvent,
|
|
291
|
+
const onLayoutItem = useCallback((event: LayoutChangeEvent, name: T) => {
|
|
280
292
|
const { width } = event.nativeEvent.layout;
|
|
281
293
|
|
|
282
|
-
const currentItemPosition = tabItemPositions.current
|
|
294
|
+
const currentItemPosition = tabItemPositions.current.get(name) || {
|
|
295
|
+
width: 0,
|
|
296
|
+
contentWidth: 0,
|
|
297
|
+
};
|
|
283
298
|
|
|
284
|
-
tabItemPositions.current
|
|
299
|
+
tabItemPositions.current.set(name, {
|
|
285
300
|
...currentItemPosition,
|
|
286
301
|
width: width,
|
|
287
|
-
};
|
|
302
|
+
});
|
|
303
|
+
setLayoutVersion(v => v + 1);
|
|
288
304
|
}, []);
|
|
289
305
|
|
|
290
|
-
const onLayoutText = useCallback((event: LayoutChangeEvent,
|
|
306
|
+
const onLayoutText = useCallback((event: LayoutChangeEvent, name: T) => {
|
|
291
307
|
const { width } = event.nativeEvent.layout;
|
|
292
308
|
|
|
293
|
-
const currentItemPosition = tabItemPositions.current
|
|
309
|
+
const currentItemPosition = tabItemPositions.current.get(name) || {
|
|
310
|
+
width: 0,
|
|
311
|
+
contentWidth: 0,
|
|
312
|
+
};
|
|
294
313
|
|
|
295
|
-
tabItemPositions.current
|
|
314
|
+
tabItemPositions.current.set(name, {
|
|
296
315
|
...currentItemPosition,
|
|
297
316
|
contentWidth: width,
|
|
298
|
-
};
|
|
317
|
+
});
|
|
318
|
+
setLayoutVersion(v => v + 1);
|
|
299
319
|
}, []);
|
|
300
320
|
|
|
301
321
|
return (
|
|
302
|
-
<
|
|
322
|
+
<Container
|
|
303
323
|
{...rest}
|
|
304
324
|
testID={testID}
|
|
325
|
+
{...containerProps}
|
|
305
326
|
style={containerStyle}
|
|
306
327
|
accessibilityRole="tablist"
|
|
307
328
|
onLayout={onLayout}>
|
|
308
|
-
|
|
309
|
-
<
|
|
310
|
-
|
|
311
|
-
{
|
|
312
|
-
|
|
313
|
-
{
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
{!disableIndicator && (
|
|
328
|
-
<Animated.View
|
|
329
|
-
testID={testID && `${testID}--active-indicator`}
|
|
330
|
-
{...indicatorProps}
|
|
331
|
-
style={indicatorStyle}
|
|
332
|
-
/>
|
|
333
|
-
)}
|
|
334
|
-
</Container>
|
|
335
|
-
|
|
336
|
-
<HorizontalDivider
|
|
337
|
-
testID={testID && `${testID}--divider`}
|
|
338
|
-
{...dividerProps}
|
|
339
|
-
style={dividerStyle}
|
|
329
|
+
{tabItems.map(child => (
|
|
330
|
+
<ChildItem
|
|
331
|
+
key={(child as ReactElement<TabItemProps<T>>).props?.name}
|
|
332
|
+
testID={testID && `${testID}--tab-item`}
|
|
333
|
+
value={value}
|
|
334
|
+
child={child as ReactElement<TabItemProps<T>>}
|
|
335
|
+
onChange={onChange}
|
|
336
|
+
onLayout={onLayoutItem}
|
|
337
|
+
onLayoutContent={onLayoutText}
|
|
338
|
+
variant={variant}
|
|
339
|
+
/>
|
|
340
|
+
))}
|
|
341
|
+
|
|
342
|
+
{!disableIndicator && (
|
|
343
|
+
<Animated.View
|
|
344
|
+
testID={testID && `${testID}--active-indicator`}
|
|
345
|
+
{...indicatorProps}
|
|
346
|
+
style={indicatorStyle}
|
|
340
347
|
/>
|
|
341
|
-
|
|
342
|
-
</
|
|
348
|
+
)}
|
|
349
|
+
</Container>
|
|
343
350
|
);
|
|
344
351
|
};
|
|
345
352
|
|
|
346
|
-
type ChildItemProps = {
|
|
347
|
-
variant: TabsProps['variant'];
|
|
348
|
-
child: ReactElement<TabItemProps
|
|
349
|
-
onChange: TabsProps['onChange'];
|
|
350
|
-
onLayout: (event: LayoutChangeEvent,
|
|
351
|
-
onLayoutContent: (event: LayoutChangeEvent,
|
|
352
|
-
value: string;
|
|
353
|
-
index: number;
|
|
353
|
+
type ChildItemProps<T extends string | number> = {
|
|
354
|
+
variant: TabsProps<T>['variant'];
|
|
355
|
+
child: ReactElement<TabItemProps<T>>;
|
|
356
|
+
onChange: TabsProps<T>['onChange'];
|
|
357
|
+
onLayout: (event: LayoutChangeEvent, name: T) => void;
|
|
358
|
+
onLayoutContent: (event: LayoutChangeEvent, name: T) => void;
|
|
359
|
+
value: string | number;
|
|
354
360
|
testID?: string;
|
|
355
361
|
};
|
|
356
362
|
|
|
357
|
-
const ChildItem =
|
|
358
|
-
({
|
|
363
|
+
const ChildItem = typedMemo(
|
|
364
|
+
<T extends string | number>({
|
|
359
365
|
value,
|
|
360
366
|
child,
|
|
361
367
|
onChange,
|
|
362
368
|
onLayout: onLayoutProp,
|
|
363
369
|
onLayoutContent: onLayoutContentProp,
|
|
364
370
|
variant,
|
|
365
|
-
index,
|
|
366
371
|
testID,
|
|
367
|
-
}: ChildItemProps) => {
|
|
372
|
+
}: ChildItemProps<T>) => {
|
|
368
373
|
const name = child.props?.name;
|
|
369
374
|
|
|
370
375
|
const onPress = useCallback(() => {
|
|
@@ -373,16 +378,16 @@ const ChildItem = memo(
|
|
|
373
378
|
|
|
374
379
|
const onLayout = useCallback(
|
|
375
380
|
(e: LayoutChangeEvent) => {
|
|
376
|
-
onLayoutProp(e,
|
|
381
|
+
onLayoutProp(e, name);
|
|
377
382
|
},
|
|
378
|
-
[
|
|
383
|
+
[name, onLayoutProp],
|
|
379
384
|
);
|
|
380
385
|
|
|
381
386
|
const onLayoutContent = useCallback(
|
|
382
387
|
(e: LayoutChangeEvent) => {
|
|
383
|
-
onLayoutContentProp(e,
|
|
388
|
+
onLayoutContentProp(e, name);
|
|
384
389
|
},
|
|
385
|
-
[
|
|
390
|
+
[name, onLayoutContentProp],
|
|
386
391
|
);
|
|
387
392
|
|
|
388
393
|
return cloneElement(child, {
|
|
@@ -396,4 +401,5 @@ const ChildItem = memo(
|
|
|
396
401
|
},
|
|
397
402
|
);
|
|
398
403
|
|
|
404
|
+
(ChildItem as ComponentType).displayName = 'Tabs_ChildItem';
|
|
399
405
|
TabBase.displayName = 'Tabs';
|
package/components/Tabs/utils.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
1
2
|
import { StyleSheet } from 'react-native-unistyles';
|
|
2
3
|
|
|
3
4
|
import { getRegisteredComponentStylesWithFallback } from '../../core';
|
|
4
5
|
|
|
6
|
+
export type TabItemContextType = {
|
|
7
|
+
active: boolean;
|
|
8
|
+
hovered: boolean;
|
|
9
|
+
variant: 'primary' | 'secondary';
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const TabItemContext = createContext<TabItemContextType>({
|
|
13
|
+
active: false,
|
|
14
|
+
hovered: false,
|
|
15
|
+
variant: 'primary',
|
|
16
|
+
});
|
|
17
|
+
|
|
5
18
|
const tabsStylesDefault = StyleSheet.create(theme => ({
|
|
6
19
|
root: {
|
|
7
20
|
activeColor: theme.colors.primary,
|
|
21
|
+
borderBottomWidth: 1,
|
|
22
|
+
borderBottomColor: theme.colors.outlineVariant,
|
|
8
23
|
} as any,
|
|
9
24
|
|
|
10
25
|
itemsContainer: {
|
|
@@ -30,8 +45,6 @@ const tabsStylesDefault = StyleSheet.create(theme => ({
|
|
|
30
45
|
},
|
|
31
46
|
},
|
|
32
47
|
},
|
|
33
|
-
|
|
34
|
-
divider: {},
|
|
35
48
|
}));
|
|
36
49
|
|
|
37
50
|
const tabsItemStylesDefault = StyleSheet.create(theme => ({
|
|
@@ -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} />
|