@momo-kits/foundation 0.167.2-beta.1 → 0.168.1-beta.1
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/Application/BottomSheet.tsx +113 -14
- package/Application/BottomTab/BottomTabBar.tsx +6 -3
- package/Application/BottomTab/CustomBottomTabItem.tsx +3 -5
- package/Application/BottomTab/DuoBarCompression.tsx +135 -0
- package/Application/BottomTab/DuoTabBar.tsx +537 -0
- package/Application/BottomTab/index.tsx +145 -134
- package/Application/Components/DuoGlassBackground.tsx +158 -0
- package/Application/Components/DuoQuickReturnHeader.tsx +42 -0
- package/Application/Components/HeaderAnimated.tsx +9 -3
- package/Application/Components/HeaderExtendHeader.tsx +70 -28
- package/Application/Components/HeaderLeft.tsx +38 -0
- package/Application/Components/HeaderRight.tsx +144 -9
- package/Application/Components/HeaderTitle.tsx +43 -7
- package/Application/Components/SearchHeader.tsx +20 -15
- package/Application/Components/SideBarButton.tsx +134 -0
- package/Application/ModalScreen.tsx +16 -2
- package/Application/NavigationContainer.tsx +22 -38
- package/Application/Navigator.ts +1 -3
- package/Application/StackScreen.tsx +2 -5
- package/Application/device.ts +29 -0
- package/Application/types.ts +10 -1
- package/Application/utils.tsx +10 -7
- package/Badge/Badge.tsx +2 -3
- package/Badge/BadgeDotAnimation.tsx +3 -3
- package/Button/index.tsx +11 -11
- package/Consts/theme.ts +3 -4
- package/Input/Input.tsx +3 -4
- package/Input/InputMoney.tsx +8 -12
- package/Input/InputOTP.tsx +9 -12
- package/Input/InputPhoneNumber.tsx +3 -4
- package/Input/InputSearch.tsx +17 -14
- package/Input/InputTextArea.tsx +3 -4
- package/Input/SystemTextInput.tsx +2 -3
- package/Input/TextTyping.tsx +11 -6
- package/Input/utils.ts +2 -3
- package/Layout/Card.tsx +8 -26
- package/Layout/FloatingButton.tsx +3 -3
- package/Layout/Item.tsx +1 -10
- package/Layout/ReservedRegions.tsx +78 -0
- package/Layout/Screen.tsx +242 -48
- package/Layout/Section.tsx +5 -18
- package/Layout/index.ts +4 -4
- package/Layout/styles.ts +5 -0
- package/Layout/utils.ts +477 -5
- package/Pagination/Dot.tsx +3 -6
- package/Popup/PopupNotify.tsx +33 -22
- package/Popup/PopupPromotion.tsx +21 -8
- package/Popup/utils.ts +32 -0
- package/Radio/index.tsx +2 -3
- package/Text/utils.ts +1 -2
- package/Title/index.tsx +8 -11
- package/package.json +5 -2
- package/Layout/GridSystem.tsx +0 -137
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import {
|
|
3
9
|
BackHandler,
|
|
4
10
|
Dimensions,
|
|
11
|
+
Keyboard,
|
|
5
12
|
KeyboardAvoidingView,
|
|
13
|
+
KeyboardEvent,
|
|
14
|
+
LayoutAnimation,
|
|
6
15
|
Modal,
|
|
7
16
|
PanResponder,
|
|
8
17
|
Platform,
|
|
9
18
|
Pressable,
|
|
10
19
|
StyleSheet,
|
|
11
20
|
TouchableOpacity,
|
|
21
|
+
useWindowDimensions,
|
|
12
22
|
View,
|
|
13
23
|
} from 'react-native';
|
|
14
24
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
@@ -22,22 +32,37 @@ import Animated, {
|
|
|
22
32
|
Easing,
|
|
23
33
|
Extrapolation,
|
|
24
34
|
interpolate,
|
|
25
|
-
runOnJS,
|
|
26
35
|
useAnimatedStyle,
|
|
27
36
|
useSharedValue,
|
|
28
37
|
withSpring,
|
|
29
38
|
withTiming,
|
|
30
39
|
} from 'react-native-reanimated';
|
|
40
|
+
import { scheduleOnRN } from 'react-native-worklets';
|
|
31
41
|
import layoutStyles from '../Layout/styles';
|
|
42
|
+
import { getDuoTopInset, isIPhoneDuo } from './device';
|
|
43
|
+
|
|
44
|
+
// iPhone Duo inner display: center the sheet instead of spanning the width
|
|
45
|
+
const DUO_REGULAR_MIN_WIDTH = 600;
|
|
32
46
|
|
|
33
47
|
const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
34
48
|
const { theme, navigator } = useContext(ApplicationContext);
|
|
35
49
|
const context = useContext<any>(MiniAppContext);
|
|
36
|
-
const
|
|
50
|
+
const windowSize = useWindowDimensions();
|
|
51
|
+
const isDuo = isIPhoneDuo();
|
|
52
|
+
const isDuoCentered = isDuo && windowSize.width >= DUO_REGULAR_MIN_WIDTH;
|
|
53
|
+
const isPortrait = windowSize.height > windowSize.width;
|
|
54
|
+
// 'screen' is the outer display on iPhone Duo, even when unfolded
|
|
55
|
+
const screenHeight = Dimensions.get('screen').height;
|
|
56
|
+
const heightDevice = isDuo ? windowSize.height : screenHeight;
|
|
37
57
|
const action = useRef<undefined | string>(undefined);
|
|
38
58
|
const insets = useSafeAreaInsets();
|
|
39
59
|
const heightHeader = useHeaderHeight();
|
|
40
|
-
const
|
|
60
|
+
const topGap = Math.max(
|
|
61
|
+
getDuoTopInset(insets, windowSize.width, windowSize.height),
|
|
62
|
+
Spacing.XL,
|
|
63
|
+
);
|
|
64
|
+
const isIOS = Platform.OS === 'ios';
|
|
65
|
+
const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
|
|
41
66
|
const keyboardOffset = heightHeader - bottomInset;
|
|
42
67
|
|
|
43
68
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
@@ -58,6 +83,43 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
58
83
|
}: BottomSheetParams = props.route.params;
|
|
59
84
|
|
|
60
85
|
const translateY = useSharedValue(heightDevice);
|
|
86
|
+
const [keyboardLift, setKeyboardLift] = useState(0);
|
|
87
|
+
|
|
88
|
+
// KeyboardAvoidingView lifts the whole sheet past the top on iPhone Duo,
|
|
89
|
+
// so lift it here and shrink maxHeight by the same amount instead
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!isDuo || !useKeyboardAvoidingView) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const onKeyboardChange = (e: KeyboardEvent) => {
|
|
95
|
+
const overlap = windowSize.height - e.endCoordinates.screenY;
|
|
96
|
+
const lift = Math.max(
|
|
97
|
+
overlap - bottomInset - (keyboardVerticalOffset ?? 0),
|
|
98
|
+
0,
|
|
99
|
+
);
|
|
100
|
+
if (e.duration) {
|
|
101
|
+
LayoutAnimation.configureNext({
|
|
102
|
+
duration: e.duration,
|
|
103
|
+
update: {
|
|
104
|
+
duration: e.duration,
|
|
105
|
+
type: LayoutAnimation.Types[e.easing] || 'keyboard',
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
setKeyboardLift(lift);
|
|
110
|
+
};
|
|
111
|
+
const subscriptions = [
|
|
112
|
+
Keyboard.addListener('keyboardWillChangeFrame', onKeyboardChange),
|
|
113
|
+
Keyboard.addListener('keyboardWillHide', onKeyboardChange),
|
|
114
|
+
];
|
|
115
|
+
return () => subscriptions.forEach(sub => sub.remove());
|
|
116
|
+
}, [
|
|
117
|
+
bottomInset,
|
|
118
|
+
isDuo,
|
|
119
|
+
keyboardVerticalOffset,
|
|
120
|
+
useKeyboardAvoidingView,
|
|
121
|
+
windowSize.height,
|
|
122
|
+
]);
|
|
61
123
|
|
|
62
124
|
const openAnimation = useCallback(() => {
|
|
63
125
|
translateY.value = withTiming(0, {
|
|
@@ -74,7 +136,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
74
136
|
duration: 200,
|
|
75
137
|
easing: Easing.bezier(0.3, 0.0, 0.8, 0.15),
|
|
76
138
|
},
|
|
77
|
-
() =>
|
|
139
|
+
() => scheduleOnRN(callback),
|
|
78
140
|
);
|
|
79
141
|
},
|
|
80
142
|
[heightDevice, translateY],
|
|
@@ -137,9 +199,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
137
199
|
}
|
|
138
200
|
closeAnimation(() => {
|
|
139
201
|
navigator?.pop();
|
|
140
|
-
|
|
202
|
+
scheduleOnRN(() => {
|
|
141
203
|
callback?.();
|
|
142
|
-
})
|
|
204
|
+
});
|
|
143
205
|
});
|
|
144
206
|
},
|
|
145
207
|
[barrierDismissible, closeAnimation, navigator],
|
|
@@ -248,6 +310,22 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
248
310
|
};
|
|
249
311
|
});
|
|
250
312
|
|
|
313
|
+
// landscape side buttons span the full height, so inset the content; in
|
|
314
|
+
// portrait the side status bar only covers the top corner
|
|
315
|
+
const needsSideInset = isDuo && !isDuoCentered && !isPortrait;
|
|
316
|
+
const sideInsetStyle = needsSideInset && {
|
|
317
|
+
paddingLeft: insets.left,
|
|
318
|
+
paddingRight: insets.right,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const screen = (
|
|
322
|
+
<Screen
|
|
323
|
+
{...props}
|
|
324
|
+
{...props.route.params}
|
|
325
|
+
onRequestClose={onRequestClose}
|
|
326
|
+
/>
|
|
327
|
+
);
|
|
328
|
+
|
|
251
329
|
return (
|
|
252
330
|
<Container
|
|
253
331
|
transparent
|
|
@@ -264,7 +342,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
264
342
|
android: undefined,
|
|
265
343
|
})}
|
|
266
344
|
keyboardVerticalOffset={keyboardVerticalOffset ?? keyboardOffset}
|
|
267
|
-
enabled={useKeyboardAvoidingView}
|
|
345
|
+
enabled={useKeyboardAvoidingView && !isDuo}
|
|
268
346
|
contentContainerStyle={styles.container}
|
|
269
347
|
style={styles.container}
|
|
270
348
|
>
|
|
@@ -287,15 +365,34 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
287
365
|
borderTopRightRadius: Radius.M,
|
|
288
366
|
overflow: 'hidden',
|
|
289
367
|
},
|
|
368
|
+
isDuo && {
|
|
369
|
+
maxHeight: windowSize.height - topGap - keyboardLift,
|
|
370
|
+
marginBottom: keyboardLift,
|
|
371
|
+
},
|
|
372
|
+
isDuoCentered && {
|
|
373
|
+
// keeps the portrait width in landscape, like system sheets
|
|
374
|
+
width: Math.min(
|
|
375
|
+
Math.min(windowSize.width, windowSize.height) - Spacing.L * 2,
|
|
376
|
+
windowSize.width -
|
|
377
|
+
Math.max(insets.left, insets.right, Spacing.L) * 2,
|
|
378
|
+
),
|
|
379
|
+
alignSelf: 'center',
|
|
380
|
+
},
|
|
290
381
|
showBaseLineDebug && styles.debugBaseLine,
|
|
291
382
|
]}
|
|
292
383
|
>
|
|
293
|
-
{
|
|
294
|
-
|
|
295
|
-
{
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
384
|
+
{isDuo ? (
|
|
385
|
+
// content shrinks so a tall sheet keeps its header and footer
|
|
386
|
+
<View style={[styles.shrink, sideInsetStyle]}>
|
|
387
|
+
{renderHeader()}
|
|
388
|
+
<View style={styles.shrink}>{screen}</View>
|
|
389
|
+
</View>
|
|
390
|
+
) : (
|
|
391
|
+
<>
|
|
392
|
+
{renderHeader()}
|
|
393
|
+
{screen}
|
|
394
|
+
</>
|
|
395
|
+
)}
|
|
299
396
|
{footerComponent && (
|
|
300
397
|
<View
|
|
301
398
|
style={[
|
|
@@ -304,6 +401,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
304
401
|
paddingBottom: Spacing.S,
|
|
305
402
|
backgroundColor: theme.colors.background.surface,
|
|
306
403
|
},
|
|
404
|
+
sideInsetStyle,
|
|
307
405
|
]}
|
|
308
406
|
>
|
|
309
407
|
{footerComponent}
|
|
@@ -353,6 +451,7 @@ const styles = StyleSheet.create({
|
|
|
353
451
|
justifyContent: 'center',
|
|
354
452
|
marginHorizontal: Spacing.M,
|
|
355
453
|
},
|
|
454
|
+
shrink: { flexShrink: 1 },
|
|
356
455
|
debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
|
|
357
456
|
});
|
|
358
457
|
|
|
@@ -58,11 +58,13 @@ export default function BottomTabBar({
|
|
|
58
58
|
const { theme } = useContext(ApplicationContext);
|
|
59
59
|
const { routes } = state;
|
|
60
60
|
const widthDevice = useWindowDimensions()?.width;
|
|
61
|
+
const insets = useSafeAreaInsets();
|
|
61
62
|
const useFloating = floatingButton && state.routes.length % 2 === 0;
|
|
62
|
-
const itemWidth =
|
|
63
|
+
const itemWidth =
|
|
64
|
+
(widthDevice - insets.left - insets.right) /
|
|
65
|
+
(state.routes.length + (useFloating ? 1 : 0));
|
|
63
66
|
// const buildLink = useLinkBuilder();
|
|
64
67
|
const indicatorAnimated = useSharedValue(0);
|
|
65
|
-
const insets = useSafeAreaInsets();
|
|
66
68
|
|
|
67
69
|
const indicatorStyle = useAnimatedStyle(() => ({
|
|
68
70
|
width: itemWidth,
|
|
@@ -246,7 +248,8 @@ export default function BottomTabBar({
|
|
|
246
248
|
},
|
|
247
249
|
{
|
|
248
250
|
paddingBottom: paddingBottom,
|
|
249
|
-
|
|
251
|
+
paddingLeft: insets.left,
|
|
252
|
+
paddingRight: insets.right,
|
|
250
253
|
},
|
|
251
254
|
style,
|
|
252
255
|
]}
|
|
@@ -116,17 +116,15 @@ const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
|
|
|
116
116
|
});
|
|
117
117
|
};
|
|
118
118
|
|
|
119
|
+
const background = focused ? activeBackgroundColor : inactiveBackgroundColor;
|
|
120
|
+
|
|
119
121
|
return (
|
|
120
122
|
<TouchableOpacity
|
|
121
123
|
onPress={onPress}
|
|
122
124
|
onLongPress={onLongPress}
|
|
123
125
|
style={[
|
|
124
126
|
styles.container,
|
|
125
|
-
{
|
|
126
|
-
backgroundColor: focused
|
|
127
|
-
? activeBackgroundColor
|
|
128
|
-
: inactiveBackgroundColor,
|
|
129
|
-
},
|
|
127
|
+
{ backgroundColor: background },
|
|
130
128
|
style,
|
|
131
129
|
showBaseLineDebug && styles.debugBaseLine,
|
|
132
130
|
]}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
ReactNode,
|
|
4
|
+
useContext,
|
|
5
|
+
useMemo,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { Spacing } from '../../Consts';
|
|
9
|
+
import { useScreenLayout } from '../../Layout';
|
|
10
|
+
import { DuoMetrics } from '../../Layout/utils';
|
|
11
|
+
|
|
12
|
+
export type DuoBarCompressionBehavior = 'toolbar' | 'tabBar';
|
|
13
|
+
|
|
14
|
+
type DuoBarCompression = {
|
|
15
|
+
toolbarCompressed: boolean;
|
|
16
|
+
tabBarCompressed: boolean;
|
|
17
|
+
// the open rail needs the toolbar button slot too: only Back stays
|
|
18
|
+
toolbarHidden?: boolean;
|
|
19
|
+
// tapping the compressed bar expands it and compresses the other one
|
|
20
|
+
expandToolbar?: () => void;
|
|
21
|
+
expandTabBar?: () => void;
|
|
22
|
+
// a rail opened from the compressed state closes once a tab is picked
|
|
23
|
+
onTabSelected?: () => void;
|
|
24
|
+
// header buttons report their full height so the tab bar knows what is left
|
|
25
|
+
setToolbarHeight?: (height: number) => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const RAIL_PADDING = 6;
|
|
29
|
+
const RAIL_GAP = Spacing.XXS;
|
|
30
|
+
// space kept between the top and the bottom groups (HIG Vertical controls)
|
|
31
|
+
const GROUP_GAP = Spacing.M;
|
|
32
|
+
// Back, then the right toolkit: help + home capsule
|
|
33
|
+
const DEFAULT_TOOLBAR_HEIGHT =
|
|
34
|
+
DuoMetrics.controlSize + Spacing.M + 2 * DuoMetrics.itemPitch;
|
|
35
|
+
// Back, then the single button the toolbar folds into
|
|
36
|
+
const COMPRESSED_TOOLBAR_HEIGHT = 2 * DuoMetrics.controlSize + Spacing.M;
|
|
37
|
+
|
|
38
|
+
const NO_COMPRESSION: DuoBarCompression = {
|
|
39
|
+
toolbarCompressed: false,
|
|
40
|
+
tabBarCompressed: false,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const DuoBarCompressionContext =
|
|
44
|
+
createContext<DuoBarCompression>(NO_COMPRESSION);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* height of the vertical tab rail, plus the QR button under it
|
|
48
|
+
*/
|
|
49
|
+
const getRailHeight = (tabCount: number, hasQr: boolean) =>
|
|
50
|
+
2 * RAIL_PADDING +
|
|
51
|
+
tabCount * DuoMetrics.controlSize +
|
|
52
|
+
Math.max(tabCount - 1, 0) * RAIL_GAP +
|
|
53
|
+
(hasQr ? Spacing.M + DuoMetrics.controlSize : 0);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* keeps the side bar's toolbar and tab bar clear of each other (HIG, Designing for
|
|
57
|
+
* iPhone Duo: Vertical controls):
|
|
58
|
+
* - both fit: both stay whole
|
|
59
|
+
* - one fits whole beside the other compressed: one is compressed, tapping it swaps them
|
|
60
|
+
* - otherwise both rest compressed; tapping one opens it for a moment (the rail closes
|
|
61
|
+
* again once a tab is picked)
|
|
62
|
+
*/
|
|
63
|
+
const DuoBarCompressionProvider: React.FC<{
|
|
64
|
+
behavior?: DuoBarCompressionBehavior;
|
|
65
|
+
tabCount: number;
|
|
66
|
+
hasQr: boolean;
|
|
67
|
+
children: ReactNode;
|
|
68
|
+
}> = ({ behavior = 'toolbar', tabCount, hasQr, children }) => {
|
|
69
|
+
const { height, sideBar } = useScreenLayout();
|
|
70
|
+
const [toolbarHeight, setToolbarHeight] = useState(DEFAULT_TOOLBAR_HEIGHT);
|
|
71
|
+
// 'toolbar' compression keeps the tab bar whole until the toolbar is tapped
|
|
72
|
+
const [expanded, setExpanded] = useState<'toolbar' | 'tabBar'>(
|
|
73
|
+
behavior === 'toolbar' ? 'tabBar' : 'toolbar',
|
|
74
|
+
);
|
|
75
|
+
// bar opened for a moment while both rest compressed
|
|
76
|
+
const [opened, setOpened] = useState<'toolbar' | 'tabBar'>();
|
|
77
|
+
|
|
78
|
+
const value = useMemo(() => {
|
|
79
|
+
if (!sideBar) {
|
|
80
|
+
return NO_COMPRESSION;
|
|
81
|
+
}
|
|
82
|
+
const room = height - sideBar.bottom - sideBar.top - GROUP_GAP;
|
|
83
|
+
const tabBar = getRailHeight(tabCount, hasQr);
|
|
84
|
+
const compressedTabBar = getRailHeight(1, hasQr) - 2 * RAIL_PADDING;
|
|
85
|
+
const fits = (toolbar: number, tabs: number) => toolbar + tabs <= room;
|
|
86
|
+
|
|
87
|
+
if (fits(toolbarHeight, tabBar)) {
|
|
88
|
+
return { ...NO_COMPRESSION, setToolbarHeight };
|
|
89
|
+
}
|
|
90
|
+
const canSwap =
|
|
91
|
+
fits(COMPRESSED_TOOLBAR_HEIGHT, tabBar) &&
|
|
92
|
+
fits(toolbarHeight, compressedTabBar);
|
|
93
|
+
if (canSwap) {
|
|
94
|
+
return {
|
|
95
|
+
toolbarCompressed: expanded === 'tabBar',
|
|
96
|
+
tabBarCompressed: expanded === 'toolbar',
|
|
97
|
+
expandToolbar: () => setExpanded('toolbar'),
|
|
98
|
+
expandTabBar: () => setExpanded('tabBar'),
|
|
99
|
+
setToolbarHeight,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const open = {
|
|
104
|
+
expandToolbar: () => setOpened('toolbar'),
|
|
105
|
+
expandTabBar: () => setOpened('tabBar'),
|
|
106
|
+
setToolbarHeight,
|
|
107
|
+
};
|
|
108
|
+
// an opened rail always shows, even when only the gap between the groups is short:
|
|
109
|
+
// it closes again once a tab is picked
|
|
110
|
+
if (opened === 'tabBar') {
|
|
111
|
+
return {
|
|
112
|
+
...open,
|
|
113
|
+
toolbarCompressed: true,
|
|
114
|
+
toolbarHidden: true,
|
|
115
|
+
tabBarCompressed: false,
|
|
116
|
+
onTabSelected: () => setOpened(undefined),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
...open,
|
|
121
|
+
toolbarCompressed: opened !== 'toolbar',
|
|
122
|
+
tabBarCompressed: true,
|
|
123
|
+
};
|
|
124
|
+
}, [expanded, opened, hasQr, height, sideBar, tabCount, toolbarHeight]);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<DuoBarCompressionContext.Provider value={value}>
|
|
128
|
+
{children}
|
|
129
|
+
</DuoBarCompressionContext.Provider>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const useDuoBarCompression = () => useContext(DuoBarCompressionContext);
|
|
134
|
+
|
|
135
|
+
export { DuoBarCompressionProvider, useDuoBarCompression };
|