@loke/design-system
Version:
A design system with individually importable components
290 lines (276 loc) • 12.7 kB
JavaScript
module.exports=`<!-- Parent: ../AGENTS.md -->
<!-- Generated: 2026-04-07 | Updated: 2026-04-07 -->
<h1>layout</h1>
<h2>Purpose</h2>
<p>This directory contains 6 layout primitive components designed for composing page structure and spacing. Unlike presentational components, layout primitives expose low-level control over flex/grid layout, spacing, sizing, and positioning. They form the foundation for responsive layouts and can be combined to build complex UIs.</p>
<h2>Key Components</h2>
<table>
<thead>
<tr><th>Component</th><th>Purpose</th></tr>
</thead>
<tbody>
<tr><td><strong>Box</strong></td><td>Universal layout primitive. Wraps any element with flexible layout and spacing props. Foundation for all other layout components.</td></tr>
<tr><td><strong>Stack</strong></td><td>Flex column with default gap. Shorthand for vertical layouts.</td></tr>
<tr><td><strong>Columns</strong></td><td>Grid-based multi-column layout. Responsive grid with automatic column count.</td></tr>
<tr><td><strong>Inline</strong></td><td>Flex row with wrapping. Horizontally stacks items with gap and wrap support.</td></tr>
<tr><td><strong>MaxWidthWrapper</strong></td><td>Constrains content to max-width and centers. Typical page-width container.</td></tr>
<tr><td><strong>PageLayout</strong></td><td>Complete page structure primitive. Combines Box with semantic layout divisions.</td></tr>
</tbody>
</table>
<h2>Directory Structure</h2>
<p>Each layout component follows the standard structure:</p>
<pre><code>src/layout/ComponentName/
├── component-name.tsx # Implementation (usually minimal wrapper)
├── component-name.stories.tsx # Storybook stories
├── index.ts # Named export
└── README.mdx # Documentation (optional for simple components)
</code></pre>
<p><strong>Note:</strong> Layout components typically have simple implementations and may skip test files since they compose Box and other primitives.</p>
<h2>For AI Agents</h2>
<h3>Component Characteristics</h3>
<p>Layout components differ from UI components in key ways:</p>
<ul>
<li><strong>Minimal styling:</strong> Mostly expose Tailwind utilities as props</li>
<li><strong>Composition-first:</strong> Designed to wrap other components</li>
<li><strong>Prop-heavy:</strong> Many optional spacing/sizing props</li>
<li><strong>Responsive:</strong> Use ResponsiveProps for breakpoint-aware values</li>
<li><strong>Flexible:</strong> Use <code>as</code> prop to render different HTML elements</li>
</ul>
<h3>Box — The Foundation</h3>
<p>Box is the most powerful layout component. All others build on it.</p>
<pre><code class="language-tsx">import { Box, type BoxProps } from "@loke/design-system/box";
// Box exposes nearly all Tailwind utilities as props:
<Box
display="flex"
flexDirection="col"
gap={4}
padding={2}
marginTop="auto"
alignItems="center"
justifyContent="between"
width="full"
height="screen"
className="custom-classes"
as="section"
asChild={false}
style={{ zIndex: 10 }}
>
{children}
</Box>
</code></pre>
<p><strong>Key Box Props:</strong></p>
<table>
<thead>
<tr><th>Category</th><th>Props</th></tr>
</thead>
<tbody>
<tr><td><strong>Display</strong></td><td>display, flexDirection, flexWrap, gap, gapX, gapY, spaceX, spaceY</td></tr>
<tr><td><strong>Alignment</strong></td><td>alignItems, justifyContent</td></tr>
<tr><td><strong>Sizing</strong></td><td>width, height, minWidth, minHeight, maxWidth, maxHeight</td></tr>
<tr><td><strong>Spacing</strong></td><td>padding, paddingX, paddingY, margin, marginX, marginY</td></tr>
<tr><td><strong>Positioning</strong></td><td>position, top, left, right, bottom, zIndex</td></tr>
<tr><td><strong>Styling</strong></td><td>background, border, borderColor, borderRadius, boxShadow</td></tr>
<tr><td><strong>Behavior</strong></td><td>overflow, overflowX, overflowY, pointerEvents, cursor, container</td></tr>
<tr><td><strong>Polymorphism</strong></td><td>as (HTML element), asChild (Slot composition), className, style</td></tr>
</tbody>
</table>
<p>Box variants are generated at build time using macros (<code>getSizeVariants</code>, <code>getDimensionVariants</code>, etc.).</p>
<h3>Stack — Vertical Flex</h3>
<p>Simplest layout component. Wraps Box with sensible defaults for column layout.</p>
<pre><code class="language-tsx">import { Stack, type StackProps } from "@loke/design-system/stack";
<Stack gap={2}>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</Stack>
</code></pre>
<p><strong>Implementation:</strong></p>
<pre><code class="language-tsx">type StackProps = Omit<BoxProps, "flexDirection" | "display">;
const Stack = ({ gap = 2, ...props }: StackProps) => {
return <Box display="flex" flexDirection="col" gap={gap} {...props} />;
};
</code></pre>
<p>Stack allows any Box prop except flexDirection and display (which are locked to column).</p>
<h3>Columns — Responsive Grid</h3>
<p>Multi-column layout that responds to container width.</p>
<pre><code class="language-tsx">import { Columns, type ColumnsProps } from "@loke/design-system/columns";
<Columns columns={{ initial: 1, sm: 2, md: 3, lg: 4 }} gap={4}>
{items.map((item) => <div key={item.id}>{item}</div>)}
</Columns>
</code></pre>
<p><strong>Responsive Prop Pattern:</strong></p>
<pre><code class="language-tsx">interface ResponsiveValue<T> {
initial?: T;
sm?: T;
md?: T;
lg?: T;
xl?: T;
}
columns: ResponsiveValue<number>
</code></pre>
<h3>Inline — Horizontal Flex with Wrapping</h3>
<p>Flexbox row that wraps items with gap.</p>
<pre><code class="language-tsx">import { Inline, type InlineProps } from "@loke/design-system/inline";
<Inline gap={2}>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
</Inline>
</code></pre>
<p>Useful for button groups, tag lists, and horizontal navigation.</p>
<h3>MaxWidthWrapper — Page Width Container</h3>
<p>Constrains content to a max-width and centers horizontally.</p>
<pre><code class="language-tsx">import { MaxWidthWrapper } from "@loke/design-system/max-width-wrapper";
<MaxWidthWrapper>
<h1>Page content</h1>
<p>Automatically centered and max-widthed</p>
</MaxWidthWrapper>
</code></pre>
<p>Typical use: Wrap page sections to maintain consistent content width.</p>
<h3>PageLayout — Complete Page Structure</h3>
<p>Higher-level component for page-level layout divisions.</p>
<pre><code class="language-tsx">import { PageLayout } from "@loke/design-system/page-layout";
<PageLayout>
{/* Automatically provides semantic structure */}
</PageLayout>
</code></pre>
<p>Check the component source for full prop interface.</p>
<h3>Responsive Props Pattern</h3>
<p>Layout components support responsive values via <code>ResponsiveProps<T></code>:</p>
<pre><code class="language-tsx"><Box
display={{ initial: "block", md: "flex" }}
gap={{ initial: 2, md: 4 }}
width={{ initial: "full", lg: "90%" }}
/>
</code></pre>
<p>This generates media query rules for each breakpoint. The <code>createResponsiveComponent</code> utility handles this at compile time.</p>
<h3>Macros and Build-Time Optimization</h3>
<p>Layout variants are generated using build-time macros (defined in <code>#macros/variants</code>):</p>
<pre><code class="language-tsx">// These are evaluated at build time, not runtime
import {
getColorVariants, // Returns Tailwind color classes
getDimensionVariants, // Width/height utilities
getSizeVariants, // Spacing utilities (gap, padding, margin)
getNegativeSizeVariants, // Negative margins/positions
} from "#macros/variants" with { type: "macro" };
const boxVariants = cva("", {
variants: {
background: getColorVariants("bg"),
gap: getSizeVariants("gap"),
width: getDimensionVariants("w"),
},
});
</code></pre>
<p>This ensures the generated CSS is minimal—only classes actually used are included.</p>
<h3>Adding a New Layout Component</h3>
<p>Layout components are much simpler than UI components. Most are thin wrappers over Box.</p>
<ol>
<li>
<p><strong>Create folder:</strong> <code>src/layout/ComponentName/</code></p>
</li>
<li>
<p><strong>Create implementation</strong> (<code>component-name.tsx</code>):</p>
<pre><code class="language-tsx">import { Box, type BoxProps } from "@loke/design-system/box";
type YourComponentProps = Omit<BoxProps, "some-locked-prop">;
const YourComponent = ({ defaultProp = "value", ...props }: YourComponentProps) => {
return <Box display="flex" defaultProp={defaultProp} {...props} />;
};
export { YourComponent };
export type { YourComponentProps };
</code></pre>
</li>
<li>
<p><strong>Create stories</strong> (<code>component-name.stories.tsx</code>):</p>
<ul>
<li>Show default usage</li>
<li>Show responsive variants</li>
<li>Show different content types</li>
<li>Keep stories minimal (layout components are mostly about composition)</li>
</ul>
</li>
<li>
<p><strong>Create index</strong> (<code>index.ts</code>):</p>
<pre><code class="language-tsx">export { YourComponent } from "./your-component";
export type { YourComponentProps } from "./your-component";
</code></pre>
</li>
<li>
<p><strong>Optional: Create README</strong> (<code>README.mdx</code>):</p>
<ul>
<li>Explain the layout metaphor</li>
<li>Show typical use cases</li>
<li>Document responsive patterns</li>
</ul>
</li>
<li>
<p><strong>Update package.json exports</strong> if needed</p>
</li>
</ol>
<h3>Spacing and Sizing Tokens</h3>
<p>All spacing props use Tailwind-compatible tokens:</p>
<pre><code>0, 1, 2, 4, 6, 8, 10, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, ...
</code></pre>
<p>Use numbers directly as prop values:</p>
<pre><code class="language-tsx"><Box gap={4} padding={2} margin={{ initial: 2, md: 4 }} />
</code></pre>
<h3>Common Patterns</h3>
<h4>Centered Layout</h4>
<pre><code class="language-tsx"><Box
display="flex"
alignItems="center"
justifyContent="center"
width="full"
height="screen"
>
{children}
</Box>
</code></pre>
<h4>Two-Column Layout</h4>
<pre><code class="language-tsx"><Box display="grid" gridTemplateColumns="1fr 2fr" gap={4}>
<Sidebar />
<MainContent />
</Box>
</code></pre>
<h4>Responsive Grid</h4>
<pre><code class="language-tsx"><Columns columns={{ initial: 1, sm: 2, lg: 3 }} gap={4}>
{items.map((item) => <Card key={item.id}>{item}</Card>)}
</Columns>
</code></pre>
<h4>Spacer/Flex Grow</h4>
<pre><code class="language-tsx"><Box display="flex" gap={2}>
<div>Start</div>
<div style={{ flex: 1 }} /> {/* Pushes to end */}
<div>End</div>
</Box>
</code></pre>
<p>Or use Box's flexGrow:</p>
<pre><code class="language-tsx"><Box display="flex" gap={2}>
<div>Start</div>
<Box flexGrow />
<div>End</div>
</Box>
</code></pre>
<h3>Testing Layout Components</h3>
<p>Layout components are tested through visual inspection in Storybook. Functional tests verify that props are applied correctly:</p>
<pre><code class="language-tsx">// Example smoke test for layout component
import { createSmokeTests } from "@loke/design-system/test";
import * as stories from "./stack.stories";
createSmokeTests(stories, "Stack");
</code></pre>
<h3>Debugging Layout Issues</h3>
<ul>
<li><strong>Unintended wrapping:</strong> Check gap and parent width</li>
<li><strong>Items not centered:</strong> Verify alignItems and justifyContent are set</li>
<li><strong>Responsive not working:</strong> Ensure prop is ResponsiveValue format</li>
<li><strong>Spacing inconsistent:</strong> Check margin/padding conflicting; use gap instead</li>
<li><strong>Elements overflow:</strong> Check maxWidth, minWidth constraints</li>
</ul>
<h3>Performance Considerations</h3>
<ul>
<li>Layout components are very cheap—they're mostly utility props</li>
<li>Responsive props generate CSS at build time, not runtime</li>
<li>No re-renders triggered by prop changes (pure styling)</li>
<li>Safe to use liberally for layout structure</li>
</ul>
<!-- MANUAL: -->
`;