howl/src/stack.tsx
2025-11-30 08:53:25 -08:00

212 lines
5.8 KiB
TypeScript

import type { TailwindSize } from "./types"
import "hono/jsx"
import type { FC, PropsWithChildren, JSX } from "hono/jsx"
import { Grid } from "./grid"
import { Section } from "./section"
import { H2 } from "./text"
import { RedBox, GreenBox, BlueBox } from "./box"
import { CodeExamples } from "./code"
export const VStack: FC<VStackProps> = (props) => {
return (
<Stack
direction="col"
mainAxis={props.v}
crossAxis={props.h}
wrap={props.wrap}
gap={props.gap}
class={props.class}
style={props.style}
>
{props.children}
</Stack>
)
}
export const HStack: FC<HStackProps> = (props) => {
return (
<Stack
direction="row"
mainAxis={props.h}
crossAxis={props.v}
wrap={props.wrap}
gap={props.gap}
class={props.class}
style={props.style}
>
{props.children}
</Stack>
)
}
const Stack: FC<StackProps> = (props) => {
const gapPx = props.gap ? props.gap * 4 : 0
const baseStyles: JSX.CSSProperties = {
display: "flex",
flexDirection: props.direction === "row" ? "row" : "column",
flexWrap: props.wrap ? "wrap" : "nowrap",
gap: `${gapPx}px`,
}
if (props.mainAxis) {
baseStyles.justifyContent = getJustifyContent(props.mainAxis)
}
if (props.crossAxis) {
baseStyles.alignItems = getAlignItems(props.crossAxis)
}
const combinedStyles = {
...baseStyles,
...props.style,
}
return <div class={props.class} style={combinedStyles}>{props.children}</div>
}
export const Test = () => {
const mainAxisOpts: MainAxisOpts[] = ["start", "center", "end", "between", "around", "evenly"]
const crossAxisOpts: CrossAxisOpts[] = ["start", "center", "end", "stretch", "baseline"]
return (
<Section gap={8} style={{ padding: "16px" }}>
{/* API Usage Examples */}
<CodeExamples
examples={[
'<VStack>...</VStack>',
'<VStack gap={4} v="center">...</VStack>',
'<HStack>...</HStack>',
'<HStack gap={6} h="between" v="center">...</HStack>',
]}
/>
{/* HStack layout matrix */}
<VStack gap={2}>
<H2>HStack Layout</H2>
<div style={{ overflowX: "auto" }}>
<Grid cols={7} gap={1} style={{ gridTemplateColumns: "auto repeat(6, 1fr)" }}>
{/* Header row: blank + h labels */}
<div></div>
{mainAxisOpts.map((h) => (
<div key={h} style={{ fontSize: "14px", fontWeight: "500", textAlign: "center" }}>
h: {h}
</div>
))}
{/* Each row: v label + HStack cells */}
{crossAxisOpts.map((v) => [
<div key={v} style={{ fontSize: "14px", fontWeight: "500" }}>
v: {v}
</div>,
...mainAxisOpts.map((h) => (
<HStack
key={`${h}-${v}`}
h={h}
v={v}
style={{ backgroundColor: "#f3f4f6", padding: "8px", height: "96px", border: "1px solid #9ca3af" }}
>
<RedBox>Aa</RedBox>
<GreenBox>Aa</GreenBox>
<BlueBox>Aa</BlueBox>
</HStack>
)),
])}
</Grid>
</div>
</VStack>
{/* VStack layout matrix */}
<VStack gap={2}>
<H2>VStack Layout</H2>
<div style={{ overflowX: "auto" }}>
<Grid cols={6} gap={1} style={{ gridTemplateColumns: "auto repeat(5, 1fr)" }}>
{/* Header row: blank + h labels */}
<div></div>
{crossAxisOpts.map((h) => (
<div key={h} style={{ fontSize: "14px", fontWeight: "500", textAlign: "center" }}>
h: {h}
</div>
))}
{/* Each row: v label + VStack cells */}
{mainAxisOpts.map((v) => [
<div key={v} style={{ fontSize: "14px", fontWeight: "500" }}>
v: {v}
</div>,
...crossAxisOpts.map((h) => (
<VStack
key={`${h}-${v}`}
v={v}
h={h}
style={{ backgroundColor: "#f3f4f6", padding: "8px", height: "168px", border: "1px solid #9ca3af" }}
>
<RedBox>Aa</RedBox>
<GreenBox>Aa</GreenBox>
<BlueBox>Aa</BlueBox>
</VStack>
)),
])}
</Grid>
</div>
</VStack>
</Section>
)
}
type StackDirection = "row" | "col"
type StackProps = {
direction: StackDirection
mainAxis?: string
crossAxis?: string
wrap?: boolean
gap?: TailwindSize
class?: string
style?: JSX.CSSProperties
children?: any
}
type MainAxisOpts = "start" | "center" | "end" | "between" | "around" | "evenly"
type CrossAxisOpts = "start" | "center" | "end" | "stretch" | "baseline"
type CommonStackProps = PropsWithChildren & {
wrap?: boolean
gap?: TailwindSize
class?: string
style?: JSX.CSSProperties
}
type VStackProps = CommonStackProps & {
v?: MainAxisOpts // main axis for vertical stack
h?: CrossAxisOpts // cross axis for vertical stack
}
type HStackProps = CommonStackProps & {
h?: MainAxisOpts // main axis for horizontal stack
v?: CrossAxisOpts // cross axis for horizontal stack
}
function getJustifyContent(axis: string): string {
const map: Record<string, string> = {
start: "flex-start",
center: "center",
end: "flex-end",
between: "space-between",
around: "space-around",
evenly: "space-evenly",
}
return map[axis] || "flex-start"
}
function getAlignItems(axis: string): string {
const map: Record<string, string> = {
start: "flex-start",
center: "center",
end: "flex-end",
stretch: "stretch",
baseline: "baseline",
}
return map[axis] || "stretch"
}