Skip to content

Commit ee43b32

Browse files
committed
Export useSafeAreaInsets hook and calc
1 parent 64a55c0 commit ee43b32

File tree

6 files changed

+86
-11
lines changed

6 files changed

+86
-11
lines changed

app/src/App.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
1-
import {
2-
Button as ButtonBase,
3-
Container,
4-
Popover,
5-
ScrollView,
6-
Text,
7-
View,
8-
} from '@react-universal/components';
1+
import { Button as ButtonBase, Container, Popover, Text, View } from '@react-universal/components';
92
import { isWeb, styled, UniversalProvider } from '@react-universal/core';
103
import { Defs, G, Path, Svg, Text as TextSvg, TSpan, Use } from '@react-universal/svg';
114
import * as ScreenOrientation from 'expo-screen-orientation';
125
import { StatusBar } from 'expo-status-bar';
136
import { useRef, useState } from 'react';
7+
import { SafeScrollView } from './SafeScrollView';
148

159
if (!isWeb) {
1610
ScreenOrientation.unlockAsync().catch(console.log);
@@ -63,7 +57,7 @@ export default function App() {
6357
<UniversalProvider>
6458
<View sx={{ flex: 1 }}>
6559
<StatusBar style="auto" />
66-
<ScrollView sx={{ flex: 1 }}>
60+
<SafeScrollView>
6761
<Svg
6862
sx={{
6963
aspectRatio: 620 / 472,
@@ -298,7 +292,7 @@ export default function App() {
298292
</Card>
299293
))}
300294
</Grid>
301-
</ScrollView>
295+
</SafeScrollView>
302296
</View>
303297
</UniversalProvider>
304298
);

app/src/SafeScrollView.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { ScrollViewProps } from '@react-universal/components';
2+
import { ScrollView } from '@react-universal/components';
3+
import { calc, max, useSafeAreaInsets } from '@react-universal/core';
4+
5+
const defaultContentInset = {
6+
bottom: 0,
7+
left: 0,
8+
right: 0,
9+
top: 0,
10+
};
11+
12+
export const SafeScrollView: React.FC<ScrollViewProps> = ({
13+
contentContainerStyle,
14+
contentInset = defaultContentInset,
15+
scrollIndicatorInsets,
16+
...props
17+
}) => {
18+
const insets = useSafeAreaInsets();
19+
20+
const bottom = max(calc(contentInset.bottom ?? 0, '-', insets.bottom), 0) as number;
21+
22+
return (
23+
<ScrollView
24+
contentContainerStyle={[{ paddingBottom: insets.bottom }, contentContainerStyle]}
25+
contentInset={{ ...contentInset, bottom }}
26+
scrollIndicatorInsets={scrollIndicatorInsets ?? { bottom }}
27+
sx={{ flex: 1 }}
28+
{...props}
29+
/>
30+
);
31+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { useUnistyles } from 'react-native-unistyles';
2+
3+
export function useSafeAreaInsets() {
4+
return useUnistyles().rt.insets;
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { StyleRuntime } from '../StyleRuntime';
2+
3+
export function useSafeAreaInsets() {
4+
return StyleRuntime.insets;
5+
}

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export * from './hooks/useLazyRef';
1717
export * from './hooks/useOwnerState';
1818
export * from './hooks/usePlatformMethods';
1919
export * from './hooks/useResponderEvents';
20+
export * from './hooks/useSafeAreaInsets';
2021
export * from './math';
2122
export * from './normalizeColor';
2223
export * from './normalizeLayoutEvent';

packages/core/src/math.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
import { isNumber } from '@react-universal/utils';
1+
import { isNumber, isString } from '@react-universal/utils';
22
import { isWeb } from '@tamagui/constants';
33
import { parseRem } from './utils/parseRem';
44

55
type Operand = string | number | { toString: () => string };
66

7+
const operators = {
8+
'+': (a: number, b: number): number => a + b,
9+
'-': (a: number, b: number): number => a - b,
10+
'/': (a: number, b: number): number => a / b,
11+
'*': (a: number, b: number): number => a * b,
12+
};
13+
14+
type Operator = keyof typeof operators;
15+
716
function parsePx(value: Operand) {
817
return isNumber(value) ? `${value}px` : value.toString();
918
}
@@ -25,3 +34,33 @@ export function clamp(min: Operand, value: Operand, max: Operand) {
2534
? `clamp(${parsePx(min)},${parsePx(value)},${parsePx(max)})`
2635
: Math.min(Math.max(parseRem(value), parseRem(min)), parseRem(max));
2736
}
37+
38+
export function calc(...valuesAndOperators: (Operand | Operator)[]): string | number {
39+
if (isWeb) {
40+
let result = 'calc(';
41+
for (const value of valuesAndOperators) {
42+
if (isString(value) && value in operators) {
43+
result += ` ${value} `; // spaces are significant
44+
} else {
45+
result += parsePx(value);
46+
}
47+
}
48+
return `${result})`;
49+
}
50+
51+
let result = 0;
52+
let operator: ((a: number, b: number) => number) | undefined;
53+
54+
for (const value of valuesAndOperators) {
55+
if (isString(value) && value in operators) {
56+
operator = operators[value as Operator];
57+
} else if (operator) {
58+
result = operator(result, parseRem(value));
59+
operator = undefined;
60+
} else {
61+
result = parseRem(value);
62+
}
63+
}
64+
65+
return result;
66+
}

0 commit comments

Comments
 (0)