···66import React from "react"
77import { StyleSheet, View } from "react-native";
8899+/**
1010+ * A `View` that centers its children vertically and horizontally.
1111+ */
912export default function Centered(props: React.ComponentProps<typeof View>) {
1013 return (
1114 <View {...props} style={[props.style, styles.container]}>
+5
src/layouts/Filled.tsx
···77import React from "react"
88import { StyleSheet, View } from "react-native";
991010+/**
1111+ * A `View` that fills all available space.
1212+ *
1313+ * i.e. forces `{ width: '100%', height: '100%' }`
1414+ */
1015export default function Filled(props: React.ComponentProps<typeof View>) {
1116 return (
1217 <View {...props} style={[props.style, styles.container]}>
+51-28
src/layouts/Measured.tsx
···44// License, v. 2.0. If a copy of the MPL was not distributed with this
55// file, You can obtain one at https://mozilla.org/MPL/2.0/.
6677-import React, { createContext, useCallback, useContext, useRef, useState, type ComponentRef } from "react";
77+import React, { createContext, useContext, useState } from "react";
88import { View } from "react-native";
99+import Filled from "./Filled";
1010+1111+/**
1212+ * Children of this component can `useMeasurements` to get the
1313+ * dimensions of this layout.
1414+ *
1515+ * Use sparingly, as it requires an extra layout pass.
1616+ *
1717+ * @example
1818+ * ```tsx
1919+ * import { Measured, useMeasurements } from 'react-native-expressive';
2020+ *
2121+ * function Parent() {
2222+ * return (
2323+ * <Measured>
2424+ * <Child />
2525+ * </Measured>
2626+ * );
2727+ * }
2828+ *
2929+ * function Child() {
3030+ * const { x, y, width, height } = useMeasurements();
3131+ * // ...
3232+ * }
3333+ * ```
3434+ */
3535+export default function Measured({ children }: { children: React.ReactNode }) {
3636+ const [measurements, setMeasurements] = useState(UNMEASURED);
3737+ const measure: OnLayout = ({ nativeEvent }) => {
3838+ setMeasurements({ ...nativeEvent.layout, timestamp: Date.now() })
3939+ };
4040+4141+ return (
4242+ <Filled onLayout={measure}>
4343+ {measurements.width > 0 && measurements.height > 0 && (
4444+ <MeasurementsContext value={measurements}>
4545+ {children}
4646+ </MeasurementsContext>
4747+ )}
4848+ </Filled>
4949+ );
5050+}
5151+5252+export function useMeasurements() {
5353+ return useContext(MeasurementsContext);
5454+}
5555+5656+const UNMEASURED = Object.freeze({ x: -1, y: -1, width: -1, height: -1, timestamp: -1 } satisfies Measurements);
5757+const MeasurementsContext = createContext<Measurements>(UNMEASURED);
9581059type Measurements = Readonly<{
1160 x: number;
1261 y: number;
1362 width: number;
1463 height: number;
6464+ timestamp: number;
1565}>
1616-1717-const UNMEASURED = Object.freeze({ x: -1, y: -1, width: -1, height: -1 } satisfies Measurements);
1818-1919-const MeasurementsContext = createContext<Measurements>(UNMEASURED);
2020-2121-export function useMeasurements() {
2222- return useContext(MeasurementsContext);
2323-}
2424-2566type OnLayout = NonNullable<React.ComponentProps<typeof View>['onLayout']>;
2626-2727-export default function Measured({ children }: { children: React.ReactNode }) {
2828- const ref = useRef<ComponentRef<View>>(null);
2929- const [measurements, setMeasurements] = useState(UNMEASURED);
3030- const saveMeasurements = useCallback<OnLayout>(
3131- ({ nativeEvent }) => { setMeasurements(nativeEvent.layout) },
3232- [setMeasurements]
3333- );
3434- return (
3535- <Filled ref={ref} onLayout={saveMeasurements}>
3636- {measurements.width > 0 && measurements.height > 0 && (
3737- <MeasurementsContext.Provider value={measurements}>
3838- {children}
3939- </MeasurementsContext.Provider>
4040- )}
4141- </Filled>
4242- );
4343-}
+1
src/layouts/index.ts
···55// file, You can obtain one at https://mozilla.org/MPL/2.0/.
6677export { default as Centered } from './Centered';
88+export { default as Filled } from './Filled';
89export { default as Measured, useMeasurements } from "./Measured";