import * as React from "react"
import { cn } from "@/registry/lib/utils"
const STACK_GAP = {
bind: "var(--spacing-rhythm-bind)",
cluster: "var(--spacing-rhythm-cluster)",
pair: "var(--spacing-rhythm-pair)",
// The trimmed twin of `pair`, for a group whose title carries `cap-trim`. It
// is a bigger number for two reasons that stack. Trimming removes the
// half-leading a plain text box supplies on both sides, so the rung has to
// state what it used to inherit; that part alone put it at 8px, paint-
// identical to `pair`. Then the trim measures the title's bottom edge at the
// BASELINE, and a descender still paints below that, so a title with a `p` or
// a `j` in it eats 0.254em of the gap on Geist and reads cramped where the
// same title without one does not. 12px is what covers both. Reach for a
// component that encodes the
// pairing (PageHeader, DialogHeader and friends) before hand-building a
// heading group; `verify-ui`'s `trim-rung-mismatch` flags one that picks wrong.
// ui-md-allow-trim-rung: this is the rung TABLE, not a call site. Every rung
// is listed here by definition, with no heading in sight.
"trim-pair": "var(--spacing-rhythm-trim-pair)",
// Three or more short lines reading as ONE unit: a value and the lines that
// qualify it, a label over two facts. `pair` is the two-line case and
// `cluster` is where they stop being one unit; this fills the hole between
// them, which the ladder used to jump. Assumes the lines are cap-trimmed,
// since an untrimmed stack already gets this much air from its dead space.
lines: "var(--spacing-rhythm-lines)",
label: "var(--spacing-rhythm-label)",
help: "var(--spacing-rhythm-help)",
field: "var(--spacing-rhythm-field)",
peer: "var(--spacing-rhythm-peer)",
region: "var(--spacing-rhythm-region)",
section: "var(--spacing-rhythm-section)",
// The step between a HEADING and the content it introduces, keyed to the
// heading's size the same way PageHeader's own ramp is: 20 / 24 / 32 for
// sm / md / lg. These are the identical tokens PageHeader applies under its
// heading group when it is given children, exposed here for the case where
// the heading and its content are SIBLINGS rather than parent and child.
//
// That case is not exotic, it is what PageTitleRow requires. That component
// ships no bottom margin on purpose and says to put it and the page body in a
// Stack, and until these rungs existed the nearest thing Stack could name was
// `cluster` (16px), which reads visibly tight under a page title: the ladder
// jumped 16 -> 40 across exactly where a heading step belongs. Settling for
// `cluster` because it was the closest available number is the "match a pixel
// count" failure the rules warn about, one rung down.
//
// Pick by the heading beside it, not by the gap you want: a page h1 takes
// `heading-lg`, a section h2 `heading-md`, a small label `heading-sm`.
"heading-sm": "var(--spacing-rhythm-heading-sm)",
"heading-md": "var(--spacing-rhythm-heading-md)",
"heading-lg": "var(--spacing-rhythm-heading-lg)",
} as const
type StackGap = keyof typeof STACK_GAP
// @use-when vertical space between blocks. The layout primitive that OWNS the
// gap, so a child never sets a margin to make room for a sibling. Not Section,
// which is a page band.
function Stack({
className,
gap = "peer",
style,
...props
}: React.ComponentProps<"div"> & { gap?: StackGap }) {
return (
<div
data-slot="stack"
className={cn("flex flex-col", className)}
style={{ ...style, gap: STACK_GAP[gap] }}
{...props}
/>
)
}
// Gap is set via inline style, not a gap-* utility class: Tailwind inlines
// spacing utility values at compile time (probed in the base rung-token
// commit), so a gap-rhythm-* class would freeze the compiled length and a
// dial or theme override of --spacing-rhythm-* would never reach it.
export { Stack, STACK_GAP, type StackGap }