"use client" import * as React from "react" import { cn } from "../../lib/utils" // SHARED, not copied. `space()` already reproduces WordPress's 4px-grid rule for ui/spacer, and a // second copy is how two answers to one question drift apart ( lesson). import { space } from "./spacer" /** * The ui/* replacement for `@wordpress/components`' Flex family - **1,415 live usages * across 159 files**: `Flex` 516 - `FlexItem` 753 - `FlexBlock` 146. The largest WordPress dependency * left after `ui/text` retired ``. * * A PROPLESS `` IS NOT `display: flex`, AND THAT IS WHY THIS IS AN ADAPTER. * 149 of the 516 `` sites pass no props at all, which reads as "a flex row". WordPress's * `useFlex` destructures four defaults a bare `
` does not share: * * justify = 'space-between' vs a plain div's flex-start * gap = 2 -> space(2) -> calc(4px * 2) vs 0 * align -> align ?? (isColumn ? 'normal': 'center') vs stretch * expanded = true -> width:100% (row) / height:100% (column) vs auto * * a naive swap changes FOUR layout properties on all 516 sites. Unlike the `ui/text` swap, this one * is *visible* - rows would collapse to their content and lose their spacing - but 159 files have * essentially no tests, so nothing here would have caught it before a human did. * * The one rule an inline style CANNOT express * WP also applies `> * { min-width: 0 }` on rows and `> * { min-height: 0 }` on columns. That is a * DESCENDANT selector, so it cannot live in a style object. It is the classic flexbox overflow fix: * without `min-width: 0` a long unbroken string refuses to shrink and blows the row out. Reproduced * here with Tailwind arbitrary variants (`[&>*]:min-w-0` / `[&>*]:min-h-0`), which compile to exactly * that selector. * Mitigating, and worth knowing: `FlexItem`/`FlexBlock` already carry `min-width: 0` and * `min-height: 0` in their OWN style, so the `> *` rule only ever mattered for children that are not * one of those two. * * Only the props our tree actually uses are accepted - measured across all 1,415 call sites, not * assumed: `align` - `justify` - `gap` - `direction` - `wrap` - `expanded` - `style` - `className`, * plus normal div attributes (`key`/`onClick`/`role`/`tabIndex` are all live at 1-6 sites each). * WP's `isReversed` (deprecated since 5.9) is deliberately absent. */ export type FlexDirection = "row" | "column" | "row-reverse" | "column-reverse" export interface FlexStyleProps { align?: React.CSSProperties["alignItems"] justify?: React.CSSProperties["justifyContent"] /** A bare number is multiplied by the 4px grid, so `gap={2}` is 8px. A CSS length passes through. */ gap?: number | string direction?: FlexDirection wrap?: boolean /** WP defaults this TRUE, which is where the surprising `width: 100%` comes from. */ expanded?: boolean } /** * Reproduces `useFlex`'s emitted style exactly, including the four defaults above. This function IS * the specification; the components only render what it returns. */ export function resolveFlexStyle({ align, justify = "space-between", gap = 2, direction = "row", wrap = false, expanded = true, }: FlexStyleProps): React.CSSProperties { const isColumn = direction.includes("column") return { display: "flex", // `align ?? (isColumn ? 'normal': 'center')` - a row centres its children by DEFAULT. alignItems: align ?? (isColumn ? "normal" : "center"), flexDirection: direction, gap: space(gap), justifyContent: justify, ...(wrap ? { flexWrap: "wrap" as const } : {}), ...(isColumn && expanded ? { height: "100%" } : {}), ...(!isColumn && expanded ? { width: "100%" } : {}), } } /** * `styles.Item` from WordPress, verbatim. Frozen: it is shared by 899 FlexItem/FlexBlock sites, so a * caller mutating it would re-lay-out the application. */ export const FLEX_ITEM_STYLE: Readonly = Object.freeze({ display: "block", maxHeight: "100%", maxWidth: "100%", minHeight: 0, minWidth: 0, } as React.CSSProperties) type DivProps = React.ComponentProps<"div"> function Flex({ align, justify, gap, direction, wrap, expanded, className, style, ...props }: Omit & FlexStyleProps) { const isColumn = (direction ?? "row").includes("column") return (
*` rule WP applies via ItemsRow / ItemsColumn, as an arbitrary variant. className={cn(isColumn ? "[&>*]:min-h-0" : "[&>*]:min-w-0", className)} // A caller's own `style` wins, but is MERGED - 51 Flex sites pass one, and spreading it // over the top would let any of them silently delete the layout this component exists for. style={{ ...resolveFlexStyle({ align, justify, gap, direction, wrap, expanded }), ...style }} {...props} /> ) } function FlexItem({ className, style, ...props }: DivProps) { return (
) } /** `FlexBlock` is `FlexItem` plus `flex: 1` - that one declaration is the whole difference. */ function FlexBlock({ className, style, ...props }: DivProps) { return (
) } export { Flex, FlexItem, FlexBlock }