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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
type PossibleRef<T> = React.Ref<T> | undefined;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Set a given ref to a given value
|
|
7
|
+
* This utility takes care of different types of refs: callback refs and RefObject(s)
|
|
8
|
+
*/
|
|
9
|
+
function setRef<T>(ref: PossibleRef<T>, value: T) {
|
|
10
|
+
if (typeof ref === 'function') {
|
|
11
|
+
return ref(value);
|
|
12
|
+
} else if (ref !== null && ref !== undefined) {
|
|
13
|
+
ref.current = value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A utility to compose multiple refs together
|
|
19
|
+
* Accepts callback refs and RefObject(s)
|
|
20
|
+
*/
|
|
21
|
+
function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
|
|
22
|
+
return node => {
|
|
23
|
+
let hasCleanup = false;
|
|
24
|
+
const cleanups = refs.map(ref => {
|
|
25
|
+
const cleanup = setRef(ref, node);
|
|
26
|
+
if (!hasCleanup && typeof cleanup === 'function') {
|
|
27
|
+
hasCleanup = true;
|
|
28
|
+
}
|
|
29
|
+
return cleanup;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// React <19 will log an error to the console if a callback ref returns a
|
|
33
|
+
// value. We don't use ref cleanups internally so this will only happen if a
|
|
34
|
+
// user's ref callback returns a value, which we only expect if they are
|
|
35
|
+
// using the cleanup functionality added in React 19.
|
|
36
|
+
if (hasCleanup) {
|
|
37
|
+
return () => {
|
|
38
|
+
for (let i = 0; i < cleanups.length; i++) {
|
|
39
|
+
const cleanup = cleanups[i];
|
|
40
|
+
if (typeof cleanup === 'function') {
|
|
41
|
+
cleanup();
|
|
42
|
+
} else {
|
|
43
|
+
setRef(refs[i], null);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A custom hook that composes multiple refs
|
|
55
|
+
* Accepts callback refs and RefObject(s)
|
|
56
|
+
*/
|
|
57
|
+
function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
|
|
58
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59
|
+
return React.useCallback(composeRefs(...refs), refs);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { composeRefs, useComposedRefs };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getRegisteredComponentWithFallback } from '../../core';
|
|
2
|
+
import { Slot as SlotComponent, Slottable } from './Slot';
|
|
3
|
+
|
|
4
|
+
const SlotDefault = Object.assign(SlotComponent, { Slottable, Root: SlotComponent });
|
|
5
|
+
|
|
6
|
+
export const Slot = getRegisteredComponentWithFallback('Slot', SlotDefault);
|
|
7
|
+
|
|
8
|
+
export { createSlot, createSlottable, type SlotProps } from './Slot';
|
|
@@ -2,11 +2,13 @@ import { forwardRef, memo, type ReactNode, useMemo } from 'react';
|
|
|
2
2
|
import { Animated, type StyleProp, View, type ViewProps, type ViewStyle } from 'react-native';
|
|
3
3
|
import { useUnistyles } from 'react-native-unistyles';
|
|
4
4
|
|
|
5
|
-
import { inputRange } from '../../styles/shadow';
|
|
6
5
|
import type { MD3Elevation } from '../../types/theme';
|
|
7
6
|
import { extractPropertiesFromStyles } from '../../utils/extractPropertiesFromStyles';
|
|
7
|
+
import { Slot } from '../Slot';
|
|
8
8
|
import { BackgroundContextWrapper } from './BackgroundContextWrapper';
|
|
9
|
-
import { defaultStyles
|
|
9
|
+
import { defaultStyles } from './utils';
|
|
10
|
+
|
|
11
|
+
const AnimatedView = Animated.createAnimatedComponent(View);
|
|
10
12
|
|
|
11
13
|
export type Props = ViewProps & {
|
|
12
14
|
/**
|
|
@@ -20,11 +22,31 @@ export type Props = ViewProps & {
|
|
|
20
22
|
* TestID used for testing purposes
|
|
21
23
|
*/
|
|
22
24
|
testID?: string;
|
|
25
|
+
/**
|
|
26
|
+
* When `true`, the component will not render a wrapper element. Instead, it will
|
|
27
|
+
* merge its props (styles, elevation shadow, ref) onto its immediate child element.
|
|
28
|
+
* This follows the Radix UI "Slot" pattern for flexible component composition.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* // With asChild - merges elevation styles onto the child
|
|
33
|
+
* <Surface asChild elevation={2}>
|
|
34
|
+
* <Card><Text>Content</Text></Card>
|
|
35
|
+
* </Surface>
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @note When `asChild` is `true`, only a single child element is allowed.
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
asChild?: boolean;
|
|
23
42
|
};
|
|
24
43
|
|
|
25
44
|
const elevationLevel = [0, 1, 2, 6, 8, 12];
|
|
26
45
|
|
|
27
|
-
const Surface = (
|
|
46
|
+
const Surface = (
|
|
47
|
+
{ elevation: _elevation = 1, style, children, testID, asChild = false, ...props }: Props,
|
|
48
|
+
ref: any,
|
|
49
|
+
) => {
|
|
28
50
|
const { theme } = useUnistyles();
|
|
29
51
|
|
|
30
52
|
const backgroundColor = (() => {
|
|
@@ -33,6 +55,7 @@ const Surface = ({ elevation = 1, style, children, testID, ...props }: Props, re
|
|
|
33
55
|
})();
|
|
34
56
|
|
|
35
57
|
const { memoizedStyles, surfaceBackground } = useMemo(() => {
|
|
58
|
+
const elevation = typeof _elevation === 'number' ? (_elevation > 5 ? 5 : _elevation) : 0;
|
|
36
59
|
return {
|
|
37
60
|
memoizedStyles: [
|
|
38
61
|
{
|
|
@@ -41,7 +64,7 @@ const Surface = ({ elevation = 1, style, children, testID, ...props }: Props, re
|
|
|
41
64
|
defaultStyles.root,
|
|
42
65
|
style,
|
|
43
66
|
{
|
|
44
|
-
elevation:
|
|
67
|
+
elevation: elevationLevel[elevation],
|
|
45
68
|
},
|
|
46
69
|
] as StyleProp<ViewStyle>,
|
|
47
70
|
surfaceBackground: extractPropertiesFromStyles(
|
|
@@ -49,13 +72,15 @@ const Surface = ({ elevation = 1, style, children, testID, ...props }: Props, re
|
|
|
49
72
|
['backgroundColor'],
|
|
50
73
|
).backgroundColor,
|
|
51
74
|
};
|
|
52
|
-
}, [backgroundColor,
|
|
75
|
+
}, [backgroundColor, _elevation, style]);
|
|
76
|
+
|
|
77
|
+
const Component = asChild ? Slot : AnimatedView;
|
|
53
78
|
|
|
54
79
|
return (
|
|
55
80
|
<BackgroundContextWrapper backgroundColor={surfaceBackground}>
|
|
56
|
-
<
|
|
81
|
+
<Component ref={ref} {...props} testID={testID} style={memoizedStyles}>
|
|
57
82
|
{children}
|
|
58
|
-
</
|
|
83
|
+
</Component>
|
|
59
84
|
</BackgroundContextWrapper>
|
|
60
85
|
);
|
|
61
86
|
};
|
|
@@ -4,8 +4,11 @@ import { useUnistyles } from 'react-native-unistyles';
|
|
|
4
4
|
|
|
5
5
|
import type { MD3Elevation } from '../../types/theme';
|
|
6
6
|
import { extractPropertiesFromStyles } from '../../utils/extractPropertiesFromStyles';
|
|
7
|
+
import { Slot } from '../Slot';
|
|
7
8
|
import { BackgroundContextWrapper } from './BackgroundContextWrapper';
|
|
8
|
-
import { defaultStyles,
|
|
9
|
+
import { defaultStyles, getCombinedShadowStyle } from './utils';
|
|
10
|
+
|
|
11
|
+
const AnimatedView = Animated.createAnimatedComponent(View);
|
|
9
12
|
|
|
10
13
|
export type Props = ComponentPropsWithRef<typeof View> & {
|
|
11
14
|
/**
|
|
@@ -27,6 +30,23 @@ export type Props = ComponentPropsWithRef<typeof View> & {
|
|
|
27
30
|
* TestID used for testing purposes
|
|
28
31
|
*/
|
|
29
32
|
testID?: string;
|
|
33
|
+
/**
|
|
34
|
+
* When `true`, the component will not render a wrapper element. Instead, it will
|
|
35
|
+
* merge its props (styles, elevation shadow, ref) onto its immediate child element.
|
|
36
|
+
* This follows the Radix UI "Slot" pattern for flexible component composition.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* // With asChild - merges elevation styles onto the child
|
|
41
|
+
* <Surface asChild elevation={2}>
|
|
42
|
+
* <Card><Text>Content</Text></Card>
|
|
43
|
+
* </Surface>
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @note When `asChild` is `true`, only a single child element is allowed.
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
asChild?: boolean;
|
|
30
50
|
};
|
|
31
51
|
|
|
32
52
|
/**
|
|
@@ -71,53 +91,38 @@ export type Props = ComponentPropsWithRef<typeof View> & {
|
|
|
71
91
|
* });
|
|
72
92
|
* ```
|
|
73
93
|
*/
|
|
74
|
-
const Surface = (
|
|
94
|
+
const Surface = (
|
|
95
|
+
{ elevation = 1, style, children, testID, asChild = false, ...props }: Props,
|
|
96
|
+
ref: any,
|
|
97
|
+
) => {
|
|
75
98
|
const { theme } = useUnistyles();
|
|
76
99
|
const backgroundColor = (() => {
|
|
77
100
|
// @ts-ignore
|
|
78
101
|
return theme.colors.elevation?.[`level${elevation}`];
|
|
79
102
|
})();
|
|
80
103
|
|
|
81
|
-
const { surfaceBackground,
|
|
82
|
-
const { position, alignSelf, top, left, right, bottom, borderRadius } =
|
|
83
|
-
extractPropertiesFromStyles(
|
|
84
|
-
[defaultStyles.root as ViewStyle, style],
|
|
85
|
-
['position', 'alignSelf', 'top', 'left', 'right', 'bottom', 'borderRadius'],
|
|
86
|
-
);
|
|
87
|
-
const absoluteStyle = { position, alignSelf, top, right, bottom, left };
|
|
88
|
-
|
|
104
|
+
const { surfaceBackground, combinedStyle } = useMemo(() => {
|
|
89
105
|
return {
|
|
90
106
|
surfaceBackground: extractPropertiesFromStyles(
|
|
91
107
|
[defaultStyles.root as ViewStyle, style],
|
|
92
108
|
['backgroundColor'],
|
|
93
109
|
).backgroundColor,
|
|
94
|
-
|
|
95
|
-
{ backgroundColor
|
|
110
|
+
combinedStyle: [
|
|
111
|
+
{ backgroundColor },
|
|
112
|
+
getCombinedShadowStyle(elevation),
|
|
96
113
|
defaultStyles.root,
|
|
97
114
|
style,
|
|
98
|
-
{
|
|
99
|
-
position: undefined,
|
|
100
|
-
alignSelf: undefined,
|
|
101
|
-
top: undefined,
|
|
102
|
-
left: undefined,
|
|
103
|
-
right: undefined,
|
|
104
|
-
bottom: undefined,
|
|
105
|
-
},
|
|
106
115
|
],
|
|
107
|
-
layer0Style: [getStyleForShadowLayer(0, elevation), absoluteStyle, { borderRadius }],
|
|
108
|
-
layer1Style: [getStyleForShadowLayer(1, elevation), { borderRadius }],
|
|
109
116
|
};
|
|
110
117
|
}, [backgroundColor, elevation, style]);
|
|
111
118
|
|
|
119
|
+
const Component = asChild ? Slot : AnimatedView;
|
|
120
|
+
|
|
112
121
|
return (
|
|
113
122
|
<BackgroundContextWrapper backgroundColor={surfaceBackground}>
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
{children}
|
|
118
|
-
</View>
|
|
119
|
-
</View>
|
|
120
|
-
</View>
|
|
123
|
+
<Component ref={ref} {...props} testID={testID} style={combinedStyle}>
|
|
124
|
+
{children}
|
|
125
|
+
</Component>
|
|
121
126
|
</BackgroundContextWrapper>
|
|
122
127
|
);
|
|
123
128
|
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { forwardRef, memo, type ReactNode, useMemo } from 'react';
|
|
2
|
-
import { type StyleProp, View, type ViewProps, type ViewStyle } from 'react-native';
|
|
2
|
+
import { Animated, type StyleProp, View, type ViewProps, type ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import shadow from '../../styles/shadow';
|
|
5
5
|
import type { MD3Elevation } from '../../types/theme';
|
|
6
|
+
import { Slot } from '../Slot';
|
|
6
7
|
import { BackgroundContextWrapper } from './BackgroundContextWrapper';
|
|
7
8
|
import { defaultStyles } from './utils';
|
|
8
9
|
|
|
10
|
+
const AnimatedView = Animated.createAnimatedComponent(View);
|
|
11
|
+
|
|
9
12
|
export type Props = ViewProps & {
|
|
10
13
|
/**
|
|
11
14
|
* Content of the `Surface`.
|
|
@@ -18,11 +21,33 @@ export type Props = ViewProps & {
|
|
|
18
21
|
* TestID used for testing purposes
|
|
19
22
|
*/
|
|
20
23
|
testID?: string;
|
|
24
|
+
/**
|
|
25
|
+
* When `true`, the component will not render a wrapper element. Instead, it will
|
|
26
|
+
* merge its props (styles, elevation shadow, ref) onto its immediate child element.
|
|
27
|
+
* This follows the Radix UI "Slot" pattern for flexible component composition.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* // Without asChild - renders an AnimatedView wrapper
|
|
32
|
+
* <Surface elevation={2}>
|
|
33
|
+
* <Card><Text>Content</Text></Card>
|
|
34
|
+
* </Surface>
|
|
35
|
+
*
|
|
36
|
+
* // With asChild - merges elevation styles onto the child
|
|
37
|
+
* <Surface asChild elevation={2}>
|
|
38
|
+
* <Card><Text>Content</Text></Card>
|
|
39
|
+
* </Surface>
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @note When `asChild` is `true`, only a single child element is allowed.
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
asChild?: boolean;
|
|
21
46
|
};
|
|
22
47
|
|
|
23
48
|
// for Web
|
|
24
49
|
const Surface = (
|
|
25
|
-
{ elevation = 1, style, children, testID, backgroundColor, ...props }: Props,
|
|
50
|
+
{ elevation = 1, style, children, testID, backgroundColor, asChild = false, ...props }: Props,
|
|
26
51
|
ref: any,
|
|
27
52
|
) => {
|
|
28
53
|
const { surfaceStyle } = useMemo(() => {
|
|
@@ -36,11 +61,13 @@ const Surface = (
|
|
|
36
61
|
};
|
|
37
62
|
}, [backgroundColor, elevation, style]);
|
|
38
63
|
|
|
64
|
+
const Component = asChild ? Slot : AnimatedView;
|
|
65
|
+
|
|
39
66
|
return (
|
|
40
67
|
<BackgroundContextWrapper backgroundColor={backgroundColor!}>
|
|
41
|
-
<
|
|
68
|
+
<Component ref={ref} {...props} testID={testID} style={surfaceStyle}>
|
|
42
69
|
{children}
|
|
43
|
-
</
|
|
70
|
+
</Component>
|
|
44
71
|
</BackgroundContextWrapper>
|
|
45
72
|
);
|
|
46
73
|
};
|
|
@@ -72,10 +72,48 @@ export const getStyleForShadowLayer = (
|
|
|
72
72
|
};
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
) => {
|
|
80
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Combines the two shadow layers into a single shadow style.
|
|
77
|
+
* This approximates the two-layer shadow effect using a single shadow.
|
|
78
|
+
*/
|
|
79
|
+
export const getCombinedShadowStyle = (elevation: number, shadowColor = _shadowColor) => {
|
|
80
|
+
if (elevation === 0) {
|
|
81
|
+
return {
|
|
82
|
+
shadowColor,
|
|
83
|
+
shadowOpacity: 0,
|
|
84
|
+
shadowOffset: { width: 0, height: 0 },
|
|
85
|
+
shadowRadius: 0,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const layer0 = iOSShadowOutputRanges[0];
|
|
90
|
+
const layer1 = iOSShadowOutputRanges[1];
|
|
91
|
+
|
|
92
|
+
// Use the larger shadow offset (from layer 0)
|
|
93
|
+
const shadowOffsetHeight = layer0.height[elevation];
|
|
94
|
+
|
|
95
|
+
// Use the larger shadow radius (from layer 0)
|
|
96
|
+
const shadowRadius = layer0.shadowRadius[elevation];
|
|
97
|
+
|
|
98
|
+
// Combine opacities (additive, capped at 1.0)
|
|
99
|
+
// This approximates the visual effect of two overlapping shadows
|
|
100
|
+
const shadowOpacity = Math.min(1.0, layer0.shadowOpacity + layer1.shadowOpacity);
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
shadowColor,
|
|
104
|
+
shadowOpacity,
|
|
105
|
+
shadowOffset: {
|
|
106
|
+
width: 0,
|
|
107
|
+
height: shadowOffsetHeight,
|
|
108
|
+
},
|
|
109
|
+
shadowRadius,
|
|
110
|
+
};
|
|
81
111
|
};
|
|
112
|
+
|
|
113
|
+
// export const getElevationAndroid = (
|
|
114
|
+
// elevation: number,
|
|
115
|
+
// _inputRange: number[],
|
|
116
|
+
// elevationLevel: number[],
|
|
117
|
+
// ) => {
|
|
118
|
+
// return elevationLevel[elevation];
|
|
119
|
+
// };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useControlledValue } from '@react-native-molecules/utils/hooks';
|
|
1
2
|
import { forwardRef, memo } from 'react';
|
|
2
3
|
import {
|
|
3
4
|
Switch as NativeSwitch,
|
|
@@ -6,7 +7,6 @@ import {
|
|
|
6
7
|
type ViewStyle,
|
|
7
8
|
} from 'react-native';
|
|
8
9
|
|
|
9
|
-
import { useControlledValue } from '../../hooks';
|
|
10
10
|
import type { IconType } from '../Icon';
|
|
11
11
|
|
|
12
12
|
export type Props = SwitchProps & {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useControlledValue, useLatest } from '@react-native-molecules/utils/hooks';
|
|
1
2
|
import { forwardRef, memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
3
|
import {
|
|
3
4
|
Animated,
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
type ViewStyle,
|
|
10
11
|
} from 'react-native';
|
|
11
12
|
|
|
12
|
-
import { useActionState
|
|
13
|
+
import { useActionState } from '../../hooks';
|
|
13
14
|
import { resolveStateVariant } from '../../utils';
|
|
14
15
|
import { Icon, type IconType } from '../Icon';
|
|
15
16
|
import { switchStyles, useSwitchColors } from './utils';
|
|
@@ -70,6 +71,7 @@ const Switch = (
|
|
|
70
71
|
ref,
|
|
71
72
|
actionsToListen: ['focus', 'hover', 'press'],
|
|
72
73
|
});
|
|
74
|
+
const isFirstRender = useRef(true);
|
|
73
75
|
|
|
74
76
|
const [value, onChange] = useControlledValue({
|
|
75
77
|
value: valueProp,
|
|
@@ -98,8 +100,8 @@ const Switch = (
|
|
|
98
100
|
state,
|
|
99
101
|
});
|
|
100
102
|
|
|
101
|
-
const toggleMarginAnimation = useRef(new Animated.Value(value ?
|
|
102
|
-
const toggleSizeAnimation = useRef(new Animated.Value(value ?
|
|
103
|
+
const toggleMarginAnimation = useRef(new Animated.Value(value ? 1 : 0)).current;
|
|
104
|
+
const toggleSizeAnimation = useRef(new Animated.Value(value ? 1 : 0)).current;
|
|
103
105
|
|
|
104
106
|
const thumbPosition = toggleMarginAnimation.interpolate({
|
|
105
107
|
inputRange: [0, 1],
|
|
@@ -130,6 +132,11 @@ const Switch = (
|
|
|
130
132
|
});
|
|
131
133
|
|
|
132
134
|
useEffect(() => {
|
|
135
|
+
if (isFirstRender.current) {
|
|
136
|
+
isFirstRender.current = false;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
133
140
|
Animated.timing(toggleMarginAnimation, {
|
|
134
141
|
toValue: value ? 1 : 0,
|
|
135
142
|
duration: 300,
|
|
@@ -1,29 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Children,
|
|
3
|
-
cloneElement,
|
|
4
|
-
forwardRef,
|
|
5
|
-
isValidElement,
|
|
6
|
-
type JSXElementConstructor,
|
|
7
|
-
memo,
|
|
8
|
-
type ReactElement,
|
|
9
|
-
useCallback,
|
|
10
|
-
useMemo,
|
|
11
|
-
useRef,
|
|
12
|
-
useState,
|
|
13
|
-
} from 'react';
|
|
1
|
+
import { forwardRef, memo, type ReactNode, useCallback, useMemo, useRef, useState } from 'react';
|
|
14
2
|
import { type LayoutChangeEvent, View, type ViewProps, type ViewStyle } from 'react-native';
|
|
15
3
|
|
|
16
4
|
import { useActionState } from '../../hooks';
|
|
17
5
|
import { resolveStateVariant } from '../../utils';
|
|
18
6
|
import { StateLayer } from '../StateLayer';
|
|
19
7
|
import { TouchableRipple, type TouchableRippleProps } from '../TouchableRipple';
|
|
20
|
-
import { tabsItemStyles } from './utils';
|
|
8
|
+
import { TabItemContext, tabsItemStyles } from './utils';
|
|
21
9
|
|
|
22
|
-
export type TabItemProps
|
|
10
|
+
export type TabItemProps<T extends string | number> = Omit<
|
|
11
|
+
TouchableRippleProps,
|
|
12
|
+
'children' | 'ref'
|
|
13
|
+
> & {
|
|
23
14
|
/**
|
|
24
15
|
* name of the tab. This should be unique like a route name
|
|
25
16
|
* */
|
|
26
|
-
name:
|
|
17
|
+
name: T;
|
|
27
18
|
/**
|
|
28
19
|
* Allows to define if TabItem is active.
|
|
29
20
|
* */
|
|
@@ -37,22 +28,11 @@ export type TabItemProps = Omit<TouchableRippleProps, 'children' | 'ref'> & {
|
|
|
37
28
|
contentsContainerProps?: Omit<ViewProps, 'children' | 'style' | 'onLayout'>;
|
|
38
29
|
onLayoutContent?: (e: LayoutChangeEvent) => void;
|
|
39
30
|
accessibilityLabel?: string;
|
|
40
|
-
children:
|
|
41
|
-
{
|
|
42
|
-
active: boolean;
|
|
43
|
-
hovered: boolean;
|
|
44
|
-
variant: 'primary' | 'secondary';
|
|
45
|
-
},
|
|
46
|
-
JSXElementConstructor<{
|
|
47
|
-
active: boolean;
|
|
48
|
-
hovered: boolean;
|
|
49
|
-
variant: 'primary' | 'secondary';
|
|
50
|
-
}>
|
|
51
|
-
>;
|
|
31
|
+
children: ReactNode;
|
|
52
32
|
stateLayerProps?: ViewProps;
|
|
53
33
|
};
|
|
54
34
|
|
|
55
|
-
const TabItem = (
|
|
35
|
+
const TabItem = <T extends string | number>(
|
|
56
36
|
{
|
|
57
37
|
active = false,
|
|
58
38
|
variant = 'primary',
|
|
@@ -65,7 +45,7 @@ const TabItem = (
|
|
|
65
45
|
children,
|
|
66
46
|
stateLayerProps,
|
|
67
47
|
...rest
|
|
68
|
-
}: TabItemProps
|
|
48
|
+
}: TabItemProps<T>,
|
|
69
49
|
ref: any,
|
|
70
50
|
) => {
|
|
71
51
|
const { hovered, actionsRef } = useActionState({ ref, actionsToListen: ['hover'] });
|
|
@@ -110,36 +90,33 @@ const TabItem = (
|
|
|
110
90
|
[active, accessibilityLabel, state],
|
|
111
91
|
);
|
|
112
92
|
|
|
93
|
+
const contextValue = useMemo(() => ({ active, hovered, variant }), [active, hovered, variant]);
|
|
94
|
+
|
|
113
95
|
return (
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
active,
|
|
131
|
-
hovered,
|
|
132
|
-
variant,
|
|
133
|
-
});
|
|
134
|
-
})}
|
|
135
|
-
</View>
|
|
96
|
+
<TabItemContext.Provider value={contextValue}>
|
|
97
|
+
<TouchableRipple
|
|
98
|
+
style={containerStyle}
|
|
99
|
+
accessibilityRole="tab"
|
|
100
|
+
accessibilityState={accessibilityState}
|
|
101
|
+
accessibilityValue={accessibilityValue}
|
|
102
|
+
{...rest}
|
|
103
|
+
ref={actionsRef}
|
|
104
|
+
onLayout={onLayout}>
|
|
105
|
+
<>
|
|
106
|
+
<View
|
|
107
|
+
style={[tabsItemStyles.contentsContainer, contentsContainerStyleProp]}
|
|
108
|
+
{...contentsContainerProps}
|
|
109
|
+
onLayout={onLayoutHandled}>
|
|
110
|
+
{children}
|
|
111
|
+
</View>
|
|
136
112
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
113
|
+
<StateLayer
|
|
114
|
+
{...stateLayerProps}
|
|
115
|
+
style={[tabsItemStyles.stateLayer, stateLayerProps?.style]}
|
|
116
|
+
/>
|
|
117
|
+
</>
|
|
118
|
+
</TouchableRipple>
|
|
119
|
+
</TabItemContext.Provider>
|
|
143
120
|
);
|
|
144
121
|
};
|
|
145
122
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { memo, useContext, useMemo } from 'react';
|
|
2
2
|
import { type TextProps, type TextStyle } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import { Icon, type IconProps, type IconType } from '../Icon';
|
|
5
5
|
import { Text } from '../Text';
|
|
6
|
-
import { tabsLabelStyles } from './utils';
|
|
6
|
+
import { TabItemContext, tabsLabelStyles } from './utils';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_ICON_SIZE = 24;
|
|
9
9
|
|
|
@@ -23,11 +23,6 @@ export type TabLabelProps = {
|
|
|
23
23
|
iconStyle?: TextStyle;
|
|
24
24
|
|
|
25
25
|
activeColor?: string;
|
|
26
|
-
|
|
27
|
-
active: boolean;
|
|
28
|
-
hovered: boolean;
|
|
29
|
-
|
|
30
|
-
variant: 'primary' | 'secondary';
|
|
31
26
|
};
|
|
32
27
|
|
|
33
28
|
const TabLabel = memo((props: TabLabelProps) => {
|
|
@@ -40,9 +35,10 @@ const TabLabel = memo((props: TabLabelProps) => {
|
|
|
40
35
|
labelStyle: labelStyleProp,
|
|
41
36
|
labelProps,
|
|
42
37
|
label,
|
|
43
|
-
active,
|
|
44
38
|
} = props;
|
|
45
39
|
|
|
40
|
+
const { active } = useContext(TabItemContext);
|
|
41
|
+
|
|
46
42
|
// tabsLabelStyles.useVariants({
|
|
47
43
|
// variant,
|
|
48
44
|
// state: resolveStateVariant({
|
|
@@ -81,4 +77,4 @@ const TabLabel = memo((props: TabLabelProps) => {
|
|
|
81
77
|
|
|
82
78
|
TabLabel.displayName = 'TabLabel';
|
|
83
79
|
|
|
84
|
-
export default TabLabel
|
|
80
|
+
export default TabLabel;
|