···11+// Copyright (c) 2026 Joseph Hale
22+//
33+// This Source Code Form is subject to the terms of the Mozilla Public
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/.
66+77+import React from "react"
88+import { View } from "react-native";
99+1010+export interface ColumnProps extends React.ComponentProps<typeof View> {
1111+ reverse?: boolean;
1212+}
1313+1414+export default function Column(props: ColumnProps) {
1515+ const direction = props.reverse ? 'column-reverse' : 'column';
1616+ return (
1717+ <View {...props} style={[props.style, { flexDirection: direction }]}>
1818+ {props.children}
1919+ </View>
2020+ )
2121+}
+84
src/layouts/Flow.tsx
···11+// Copyright (c) 2026 Joseph Hale
22+//
33+// This Source Code Form is subject to the terms of the Mozilla Public
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/.
66+77+import Column from "./Column";
88+import Row from "./Row";
99+import { range } from "../utils/iteration";
1010+1111+export interface FlowDefinition {
1212+ origin: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
1313+ direction: 'row' | 'column';
1414+}
1515+1616+export interface FlowProps {
1717+ rows: number;
1818+ columns: number;
1919+ children: React.ReactNode[];
2020+ flow: FlowDefinition;
2121+}
2222+2323+export interface FlowDirectionProps {
2424+ rows: number;
2525+ columns: number;
2626+ children: React.ReactNode[];
2727+ origin: FlowDefinition['origin'];
2828+}
2929+3030+function Flow(props: FlowProps) {
3131+ if (props.flow.direction === 'column') {
3232+ return (
3333+ <ColumnFlow {...props} origin={props.flow.origin}>
3434+ {props.children}
3535+ </ColumnFlow>
3636+ )
3737+ } else if (props.flow.direction === 'row') {
3838+ return (
3939+ <RowFlow {...props} origin={props.flow.origin}>
4040+ {props.children}
4141+ </RowFlow>
4242+ )
4343+ } else {
4444+ throw new Error(`Invalid flow direction: ${props.flow.direction}`);
4545+ }
4646+}
4747+4848+function ColumnFlow(props: FlowDirectionProps) {
4949+ const { rows, columns, children, origin } = props;
5050+5151+ return (
5252+ <Row reverse={origin.endsWith("right")}>
5353+ {range(0, columns).map((column) => (
5454+ <Column key={column} reverse={origin.startsWith("bottom")}>
5555+ {range(0, rows).map((row) => children[column * rows + row])}
5656+ </Column>
5757+ ))}
5858+ </Row>
5959+ )
6060+}
6161+6262+function RowFlow(props: FlowDirectionProps) {
6363+ const { rows, columns, children, origin } = props;
6464+6565+ return (
6666+ <Column reverse={origin.startsWith("bottom")}>
6767+ {range(0, rows).map((row) => (
6868+ <Row key={row} reverse={origin.endsWith("right")}>
6969+ {range(0, columns).map((column) => children[row * columns + column])}
7070+ </Row>
7171+ ))}
7272+ </Column>
7373+ )
7474+}
7575+7676+interface FlowComponent extends React.FC<FlowProps> {
7777+ Column: typeof ColumnFlow;
7878+ Row: typeof RowFlow;
7979+}
8080+8181+const _Flow = Flow as FlowComponent;
8282+_Flow.Column = ColumnFlow;
8383+_Flow.Row = RowFlow;
8484+export default _Flow;
+21
src/layouts/Row.tsx
···11+// Copyright (c) 2026 Joseph Hale
22+//
33+// This Source Code Form is subject to the terms of the Mozilla Public
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/.
66+77+import React from "react"
88+import { View } from "react-native";
99+1010+export interface RowProps extends React.ComponentProps<typeof View> {
1111+ reverse?: boolean;
1212+}
1313+1414+export default function Row(props: RowProps) {
1515+ const direction = props.reverse ? 'row-reverse' : 'row';
1616+ return (
1717+ <View {...props} style={[props.style, { flexDirection: direction }]}>
1818+ {props.children}
1919+ </View>
2020+ )
2121+}
+1
src/layouts/index.ts
···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+export { default as Flow, type FlowProps, type FlowDirectionProps, type FlowDefinition } from "./Flow";
78export { default as Measured, useMeasurements } from "./Measured";
+12
src/utils/iteration.ts
···11+// Copyright (c) 2026 Joseph Hale
22+//
33+// This Source Code Form is subject to the terms of the Mozilla Public
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/.
66+77+/**
88+ * Produces an array of numbers from [start, end).
99+ */
1010+export function range(start: number, end: number) {
1111+ return Array.from({ length: end - start }).map((_, index) => index + start);
1212+}