Utilities and UI components for cross-platform React Native apps
0

Configure Feed

Select the types of activity you want to include in your feed.

refactor: cleanup and document layout components

Joseph Hale (Jan 24, 2026, 11:36 PM -0700) 19cf110e 79981035

+60 -28
+3
src/layouts/Centered.tsx
··· 6 6 import React from "react" 7 7 import { StyleSheet, View } from "react-native"; 8 8 9 + /** 10 + * A `View` that centers its children vertically and horizontally. 11 + */ 9 12 export default function Centered(props: React.ComponentProps<typeof View>) { 10 13 return ( 11 14 <View {...props} style={[props.style, styles.container]}>
+5
src/layouts/Filled.tsx
··· 7 7 import React from "react" 8 8 import { StyleSheet, View } from "react-native"; 9 9 10 + /** 11 + * A `View` that fills all available space. 12 + * 13 + * i.e. forces `{ width: '100%', height: '100%' }` 14 + */ 10 15 export default function Filled(props: React.ComponentProps<typeof View>) { 11 16 return ( 12 17 <View {...props} style={[props.style, styles.container]}>
+51 -28
src/layouts/Measured.tsx
··· 4 4 // License, v. 2.0. If a copy of the MPL was not distributed with this 5 5 // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 6 7 - import React, { createContext, useCallback, useContext, useRef, useState, type ComponentRef } from "react"; 7 + import React, { createContext, useContext, useState } from "react"; 8 8 import { View } from "react-native"; 9 + import Filled from "./Filled"; 10 + 11 + /** 12 + * Children of this component can `useMeasurements` to get the 13 + * dimensions of this layout. 14 + * 15 + * Use sparingly, as it requires an extra layout pass. 16 + * 17 + * @example 18 + * ```tsx 19 + * import { Measured, useMeasurements } from 'react-native-expressive'; 20 + * 21 + * function Parent() { 22 + * return ( 23 + * <Measured> 24 + * <Child /> 25 + * </Measured> 26 + * ); 27 + * } 28 + * 29 + * function Child() { 30 + * const { x, y, width, height } = useMeasurements(); 31 + * // ... 32 + * } 33 + * ``` 34 + */ 35 + export default function Measured({ children }: { children: React.ReactNode }) { 36 + const [measurements, setMeasurements] = useState(UNMEASURED); 37 + const measure: OnLayout = ({ nativeEvent }) => { 38 + setMeasurements({ ...nativeEvent.layout, timestamp: Date.now() }) 39 + }; 40 + 41 + return ( 42 + <Filled onLayout={measure}> 43 + {measurements.width > 0 && measurements.height > 0 && ( 44 + <MeasurementsContext value={measurements}> 45 + {children} 46 + </MeasurementsContext> 47 + )} 48 + </Filled> 49 + ); 50 + } 51 + 52 + export function useMeasurements() { 53 + return useContext(MeasurementsContext); 54 + } 55 + 56 + const UNMEASURED = Object.freeze({ x: -1, y: -1, width: -1, height: -1, timestamp: -1 } satisfies Measurements); 57 + const MeasurementsContext = createContext<Measurements>(UNMEASURED); 9 58 10 59 type Measurements = Readonly<{ 11 60 x: number; 12 61 y: number; 13 62 width: number; 14 63 height: number; 64 + timestamp: number; 15 65 }> 16 - 17 - const UNMEASURED = Object.freeze({ x: -1, y: -1, width: -1, height: -1 } satisfies Measurements); 18 - 19 - const MeasurementsContext = createContext<Measurements>(UNMEASURED); 20 - 21 - export function useMeasurements() { 22 - return useContext(MeasurementsContext); 23 - } 24 - 25 66 type OnLayout = NonNullable<React.ComponentProps<typeof View>['onLayout']>; 26 - 27 - export default function Measured({ children }: { children: React.ReactNode }) { 28 - const ref = useRef<ComponentRef<View>>(null); 29 - const [measurements, setMeasurements] = useState(UNMEASURED); 30 - const saveMeasurements = useCallback<OnLayout>( 31 - ({ nativeEvent }) => { setMeasurements(nativeEvent.layout) }, 32 - [setMeasurements] 33 - ); 34 - return ( 35 - <Filled ref={ref} onLayout={saveMeasurements}> 36 - {measurements.width > 0 && measurements.height > 0 && ( 37 - <MeasurementsContext.Provider value={measurements}> 38 - {children} 39 - </MeasurementsContext.Provider> 40 - )} 41 - </Filled> 42 - ); 43 - }
+1
src/layouts/index.ts
··· 5 5 // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 6 7 7 export { default as Centered } from './Centered'; 8 + export { default as Filled } from './Filled'; 8 9 export { default as Measured, useMeasurements } from "./Measured";