feat(eidos): port 8 layout primitives from air (box, flex, grid, stack, group, wrap, container, section)
Visual-only primitives translated from the legacy `air/components/layout`
on the morfo-runtime branch. Architectural adjustments for the eidos
target: drop the `air-` class prefix, target `[data-{name}]` directly,
rename CSS vars to `--{name}-*`, replace `var(--air-space-N)` with the
eidos foundation `var(--space-N)` token, resolve responsive props via
`ActiveEidos.resolve(prop)`.
- `box` — universal box-model utility (size, padding, margin, gap,
position, overflow, flex/grid item props). Every prop maps to a
CSS custom property; unset props fall through via `revert-layer`.
- `flex` / `grid` / `stack` / `group` / `wrap` — layout containers
that render through Box (single `<div>` per primitive, no
semantic-tag prop in this batch). Markers stack as
`[data-box][data-{name}]`.
- `container` — max-width centered (sm/md/lg/xl/full).
- `section` — semantic-padding block with background tone variants.
Shared helpers at `_layout/shared.ts` (formatLayoutSpace /
formatLayoutLength / formatLayoutRaw / pushStyleVar / composeStyle)
plus an optional `Layout.*` namespace barrel for consumers who prefer
that style; the individual `import { Box }` from each component dir
also works.
Each primitive ships a demo at `/uix/components/{name}/` with a live
example, a props table, and a reference comparison row (radix-themes /
chakra-ui / mantine).
svelte-check: 0 errors / 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
parent
20709caf9c
commit
9ec2a57a85
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Layout namespace — re-exports the 8 layout primitives as a single
|
||||||
|
* group for consumers who prefer the dotted style:
|
||||||
|
*
|
||||||
|
* import { Layout } from '$uix/eidos/components/_layout';
|
||||||
|
* <Layout.Box>…</Layout.Box>
|
||||||
|
* <Layout.Flex>…</Layout.Flex>
|
||||||
|
*
|
||||||
|
* The individual barrels still work and are the preferred form for
|
||||||
|
* tree-shaken imports:
|
||||||
|
*
|
||||||
|
* import { Box } from '$uix/eidos/components/box';
|
||||||
|
*/
|
||||||
|
export { default as Box } from '../box';
|
||||||
|
export { default as Flex } from '../flex';
|
||||||
|
export { default as Grid } from '../grid';
|
||||||
|
export { default as Stack } from '../stack';
|
||||||
|
export { default as Group } from '../group';
|
||||||
|
export { default as Wrap } from '../wrap';
|
||||||
|
export { default as Container } from '../container';
|
||||||
|
export { default as Section } from '../section';
|
||||||
|
|
||||||
|
export type { BoxProps } from '../box';
|
||||||
|
export type { FlexProps } from '../flex';
|
||||||
|
export type { GridProps } from '../grid';
|
||||||
|
export type { StackProps, StackDirection } from '../stack';
|
||||||
|
export type { GroupProps } from '../group';
|
||||||
|
export type { WrapProps } from '../wrap';
|
||||||
|
export type { ContainerProps, ContainerSize, ContainerAlign } from '../container';
|
||||||
|
export type { SectionProps, SectionSize } from '../section';
|
||||||
|
|
||||||
|
export type {
|
||||||
|
LayoutSpaceValue,
|
||||||
|
LayoutLengthValue,
|
||||||
|
LayoutDisplay,
|
||||||
|
LayoutOverflow,
|
||||||
|
LayoutPosition,
|
||||||
|
LayoutAlignSelf,
|
||||||
|
LayoutJustifySelf,
|
||||||
|
LayoutFlexDirection,
|
||||||
|
LayoutFlexWrap,
|
||||||
|
LayoutAlignItems,
|
||||||
|
LayoutJustifyContent,
|
||||||
|
LayoutGridAutoFlow
|
||||||
|
} from './shared';
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
/**
|
||||||
|
* Shared layout helpers for eidos layout primitives.
|
||||||
|
*
|
||||||
|
* Ported from the legacy `air/components/layout/shared.ts` with one
|
||||||
|
* architectural shift: numeric space values now emit `var(--space-N)`
|
||||||
|
* tokens from the eidos foundation (`src/uix/eidos/generated/base.css`),
|
||||||
|
* not the retired `--air-space-N` aliases.
|
||||||
|
*
|
||||||
|
* formatLayoutSpace(2) -> 'var(--space-2)'
|
||||||
|
* formatLayoutSpace('1rem') -> '1rem'
|
||||||
|
* formatLayoutLength(120) -> '120px'
|
||||||
|
* formatLayoutLength('50%') -> '50%'
|
||||||
|
* formatLayoutRaw(3) -> '3'
|
||||||
|
*
|
||||||
|
* The shape is identical to air's helpers so the per-primitive Svelte
|
||||||
|
* components below stay tiny — they just push CSS custom properties
|
||||||
|
* into a declarations list and join.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type LayoutSpaceValue = number | string;
|
||||||
|
export type LayoutLengthValue = number | string;
|
||||||
|
export type LayoutDisplay =
|
||||||
|
| 'block'
|
||||||
|
| 'inline'
|
||||||
|
| 'inline-block'
|
||||||
|
| 'flex'
|
||||||
|
| 'inline-flex'
|
||||||
|
| 'grid'
|
||||||
|
| 'inline-grid'
|
||||||
|
| 'contents'
|
||||||
|
| 'none';
|
||||||
|
export type LayoutOverflow = 'visible' | 'hidden' | 'clip' | 'scroll' | 'auto';
|
||||||
|
export type LayoutPosition = 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky';
|
||||||
|
export type LayoutAlignSelf =
|
||||||
|
| 'auto'
|
||||||
|
| 'start'
|
||||||
|
| 'end'
|
||||||
|
| 'center'
|
||||||
|
| 'stretch'
|
||||||
|
| 'flex-start'
|
||||||
|
| 'flex-end'
|
||||||
|
| 'baseline';
|
||||||
|
export type LayoutJustifySelf = 'auto' | 'start' | 'end' | 'center' | 'stretch';
|
||||||
|
export type LayoutFlexDirection = 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
||||||
|
export type LayoutFlexWrap = 'nowrap' | 'wrap' | 'wrap-reverse';
|
||||||
|
export type LayoutAlignItems =
|
||||||
|
| 'stretch'
|
||||||
|
| 'start'
|
||||||
|
| 'end'
|
||||||
|
| 'center'
|
||||||
|
| 'baseline'
|
||||||
|
| 'flex-start'
|
||||||
|
| 'flex-end';
|
||||||
|
export type LayoutJustifyContent =
|
||||||
|
| 'start'
|
||||||
|
| 'end'
|
||||||
|
| 'center'
|
||||||
|
| 'stretch'
|
||||||
|
| 'space-between'
|
||||||
|
| 'space-around'
|
||||||
|
| 'space-evenly'
|
||||||
|
| 'flex-start'
|
||||||
|
| 'flex-end';
|
||||||
|
export type LayoutGridAutoFlow = 'row' | 'column' | 'dense' | 'row dense' | 'column dense';
|
||||||
|
|
||||||
|
function parseNumberish(value: number | string): number | undefined {
|
||||||
|
if (typeof value === 'number') return Number.isFinite(value) ? value : undefined;
|
||||||
|
if (typeof value !== 'string') return undefined;
|
||||||
|
const normalized = value.trim();
|
||||||
|
if (!normalized) return undefined;
|
||||||
|
if (!/^-?\d+(\.\d+)?$/.test(normalized)) return undefined;
|
||||||
|
const parsed = Number(normalized);
|
||||||
|
return Number.isFinite(parsed) ? parsed : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a space-scale value (gap, padding, margin). Numbers map to the
|
||||||
|
* `--space-N` token; strings are emitted verbatim so consumers can pass
|
||||||
|
* `'1rem'`, `'auto'`, or a custom `var(--…)` chain.
|
||||||
|
*/
|
||||||
|
export function formatLayoutSpace(value: LayoutSpaceValue | undefined): string | undefined {
|
||||||
|
if (value === undefined || value === null || value === '') return undefined;
|
||||||
|
const parsed = parseNumberish(value);
|
||||||
|
return parsed !== undefined ? `var(--space-${parsed})` : String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a length value (width, height, inset). Numbers become px;
|
||||||
|
* strings pass through.
|
||||||
|
*/
|
||||||
|
export function formatLayoutLength(value: LayoutLengthValue | undefined): string | undefined {
|
||||||
|
if (value === undefined || value === null || value === '') return undefined;
|
||||||
|
const parsed = parseNumberish(value);
|
||||||
|
return parsed !== undefined ? `${parsed}px` : String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a raw value (flex, grow, shrink, order). Always stringifies.
|
||||||
|
*/
|
||||||
|
export function formatLayoutRaw(value: string | number | undefined): string | undefined {
|
||||||
|
if (value === undefined || value === null || value === '') return undefined;
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a CSS custom-property declaration to the list, but only when the
|
||||||
|
* value is defined. Keeps the resulting `style=""` attribute compact —
|
||||||
|
* primitives don't emit dozens of `--…: ;` no-ops.
|
||||||
|
*/
|
||||||
|
export function pushStyleVar(
|
||||||
|
declarations: string[],
|
||||||
|
name: `--${string}`,
|
||||||
|
value: string | undefined
|
||||||
|
): void {
|
||||||
|
if (!value) return;
|
||||||
|
declarations.push(`${name}:${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose a final style string from a list of `--var:value` declarations
|
||||||
|
* and an optional caller-provided `style` prop.
|
||||||
|
*/
|
||||||
|
export function composeStyle(declarations: string[], style: string | undefined): string {
|
||||||
|
const own = declarations.join(';');
|
||||||
|
if (!style) return own;
|
||||||
|
if (!own) return style;
|
||||||
|
return `${own};${style}`;
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Box recipe — universal box-model utility. Every CSS property falls back
|
||||||
|
* to `revert-layer` so unset Box props inherit the normal cascade rather
|
||||||
|
* than forcing an initial value.
|
||||||
|
*
|
||||||
|
* Cascade pattern (padding example):
|
||||||
|
* padding-top = --box-padding-top
|
||||||
|
* || --box-padding-block
|
||||||
|
* || --box-padding
|
||||||
|
* || revert-layer
|
||||||
|
*
|
||||||
|
* The shorthand props (`padding`, `paddingX`, `paddingY`, `margin`,
|
||||||
|
* `marginX`, `marginY`, `inset`, `insetX`, `insetY`) supply default
|
||||||
|
* values for the per-side props at the CSS layer, not in JS — so a
|
||||||
|
* caller passing both `padding={4}` and `paddingTop={6}` gets exactly
|
||||||
|
* the expected behavior without `box.svelte` needing to know about
|
||||||
|
* shorthand resolution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-box] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
display: var(--box-display, revert-layer);
|
||||||
|
position: var(--box-position, revert-layer);
|
||||||
|
width: var(--box-width, revert-layer);
|
||||||
|
min-width: var(--box-min-width, 0);
|
||||||
|
max-width: var(--box-max-width, revert-layer);
|
||||||
|
height: var(--box-height, revert-layer);
|
||||||
|
min-height: var(--box-min-height, revert-layer);
|
||||||
|
max-height: var(--box-max-height, revert-layer);
|
||||||
|
|
||||||
|
padding-top: var(
|
||||||
|
--box-padding-top,
|
||||||
|
var(--box-padding-block, var(--box-padding, revert-layer))
|
||||||
|
);
|
||||||
|
padding-right: var(
|
||||||
|
--box-padding-right,
|
||||||
|
var(--box-padding-inline, var(--box-padding, revert-layer))
|
||||||
|
);
|
||||||
|
padding-bottom: var(
|
||||||
|
--box-padding-bottom,
|
||||||
|
var(--box-padding-block, var(--box-padding, revert-layer))
|
||||||
|
);
|
||||||
|
padding-left: var(
|
||||||
|
--box-padding-left,
|
||||||
|
var(--box-padding-inline, var(--box-padding, revert-layer))
|
||||||
|
);
|
||||||
|
|
||||||
|
margin-top: var(--box-margin-top, var(--box-margin-block, var(--box-margin, revert-layer)));
|
||||||
|
margin-right: var(
|
||||||
|
--box-margin-right,
|
||||||
|
var(--box-margin-inline, var(--box-margin, revert-layer))
|
||||||
|
);
|
||||||
|
margin-bottom: var(
|
||||||
|
--box-margin-bottom,
|
||||||
|
var(--box-margin-block, var(--box-margin, revert-layer))
|
||||||
|
);
|
||||||
|
margin-left: var(--box-margin-left, var(--box-margin-inline, var(--box-margin, revert-layer)));
|
||||||
|
|
||||||
|
gap: var(--box-gap, revert-layer);
|
||||||
|
|
||||||
|
top: var(--box-top, revert-layer);
|
||||||
|
right: var(--box-right, revert-layer);
|
||||||
|
bottom: var(--box-bottom, revert-layer);
|
||||||
|
left: var(--box-left, revert-layer);
|
||||||
|
inset-block-start: var(--box-top, var(--box-inset-block, var(--box-inset, revert-layer)));
|
||||||
|
inset-inline-end: var(--box-right, var(--box-inset-inline, var(--box-inset, revert-layer)));
|
||||||
|
inset-block-end: var(--box-bottom, var(--box-inset-block, var(--box-inset, revert-layer)));
|
||||||
|
inset-inline-start: var(--box-left, var(--box-inset-inline, var(--box-inset, revert-layer)));
|
||||||
|
|
||||||
|
overflow: var(--box-overflow, revert-layer);
|
||||||
|
overflow-x: var(--box-overflow-x, var(--box-overflow, revert-layer));
|
||||||
|
overflow-y: var(--box-overflow-y, var(--box-overflow, revert-layer));
|
||||||
|
|
||||||
|
flex: var(--box-flex, revert-layer);
|
||||||
|
flex-grow: var(--box-grow, revert-layer);
|
||||||
|
flex-shrink: var(--box-shrink, revert-layer);
|
||||||
|
flex-basis: var(--box-basis, revert-layer);
|
||||||
|
order: var(--box-order, revert-layer);
|
||||||
|
align-self: var(--box-align-self, revert-layer);
|
||||||
|
justify-self: var(--box-justify-self, revert-layer);
|
||||||
|
}
|
||||||
@ -0,0 +1,129 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Eidos `<Box>` — universal box-model utility. Visual-only primitive:
|
||||||
|
* every prop maps to a `--box-*` custom property on a single `<div
|
||||||
|
* data-box>` root, and the recipe in `box.css` consumes those vars
|
||||||
|
* with `revert-layer` fallbacks so unset props inherit normal cascade.
|
||||||
|
*
|
||||||
|
* <Box padding={4} maxWidth={600} display="flex" gap={3}>…</Box>
|
||||||
|
*
|
||||||
|
* `Box` is the foundation that Flex, Grid, Stack, Group, Wrap,
|
||||||
|
* Container and Section all build on. It owns no behavior and no
|
||||||
|
* morfo — it just translates props to CSS variables.
|
||||||
|
*/
|
||||||
|
import { ActiveEidos } from '$uix/eidos';
|
||||||
|
import type { BoxProps } from './types';
|
||||||
|
import {
|
||||||
|
composeStyle,
|
||||||
|
formatLayoutLength,
|
||||||
|
formatLayoutRaw,
|
||||||
|
formatLayoutSpace,
|
||||||
|
pushStyleVar
|
||||||
|
} from '../_layout/shared';
|
||||||
|
|
||||||
|
let {
|
||||||
|
display,
|
||||||
|
width,
|
||||||
|
minWidth,
|
||||||
|
maxWidth,
|
||||||
|
height,
|
||||||
|
minHeight,
|
||||||
|
maxHeight,
|
||||||
|
padding,
|
||||||
|
paddingX,
|
||||||
|
paddingY,
|
||||||
|
paddingTop,
|
||||||
|
paddingRight,
|
||||||
|
paddingBottom,
|
||||||
|
paddingLeft,
|
||||||
|
margin,
|
||||||
|
marginX,
|
||||||
|
marginY,
|
||||||
|
marginTop,
|
||||||
|
marginRight,
|
||||||
|
marginBottom,
|
||||||
|
marginLeft,
|
||||||
|
gap,
|
||||||
|
position,
|
||||||
|
top,
|
||||||
|
right,
|
||||||
|
bottom,
|
||||||
|
left,
|
||||||
|
inset,
|
||||||
|
insetX,
|
||||||
|
insetY,
|
||||||
|
overflow,
|
||||||
|
overflowX,
|
||||||
|
overflowY,
|
||||||
|
flex,
|
||||||
|
grow,
|
||||||
|
shrink,
|
||||||
|
basis,
|
||||||
|
order,
|
||||||
|
alignSelf,
|
||||||
|
justifySelf,
|
||||||
|
style,
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: BoxProps = $props();
|
||||||
|
|
||||||
|
const eidos = ActiveEidos.require();
|
||||||
|
|
||||||
|
const resolvedStyle = $derived.by(() => {
|
||||||
|
const decls: string[] = [];
|
||||||
|
|
||||||
|
pushStyleVar(decls, '--box-display', eidos.resolve(display));
|
||||||
|
pushStyleVar(decls, '--box-width', formatLayoutLength(eidos.resolve(width)));
|
||||||
|
pushStyleVar(decls, '--box-min-width', formatLayoutLength(eidos.resolve(minWidth)));
|
||||||
|
pushStyleVar(decls, '--box-max-width', formatLayoutLength(eidos.resolve(maxWidth)));
|
||||||
|
pushStyleVar(decls, '--box-height', formatLayoutLength(eidos.resolve(height)));
|
||||||
|
pushStyleVar(decls, '--box-min-height', formatLayoutLength(eidos.resolve(minHeight)));
|
||||||
|
pushStyleVar(decls, '--box-max-height', formatLayoutLength(eidos.resolve(maxHeight)));
|
||||||
|
|
||||||
|
pushStyleVar(decls, '--box-padding', formatLayoutSpace(eidos.resolve(padding)));
|
||||||
|
pushStyleVar(decls, '--box-padding-inline', formatLayoutSpace(eidos.resolve(paddingX)));
|
||||||
|
pushStyleVar(decls, '--box-padding-block', formatLayoutSpace(eidos.resolve(paddingY)));
|
||||||
|
pushStyleVar(decls, '--box-padding-top', formatLayoutSpace(eidos.resolve(paddingTop)));
|
||||||
|
pushStyleVar(decls, '--box-padding-right', formatLayoutSpace(eidos.resolve(paddingRight)));
|
||||||
|
pushStyleVar(decls, '--box-padding-bottom', formatLayoutSpace(eidos.resolve(paddingBottom)));
|
||||||
|
pushStyleVar(decls, '--box-padding-left', formatLayoutSpace(eidos.resolve(paddingLeft)));
|
||||||
|
|
||||||
|
pushStyleVar(decls, '--box-margin', formatLayoutSpace(eidos.resolve(margin)));
|
||||||
|
pushStyleVar(decls, '--box-margin-inline', formatLayoutSpace(eidos.resolve(marginX)));
|
||||||
|
pushStyleVar(decls, '--box-margin-block', formatLayoutSpace(eidos.resolve(marginY)));
|
||||||
|
pushStyleVar(decls, '--box-margin-top', formatLayoutSpace(eidos.resolve(marginTop)));
|
||||||
|
pushStyleVar(decls, '--box-margin-right', formatLayoutSpace(eidos.resolve(marginRight)));
|
||||||
|
pushStyleVar(decls, '--box-margin-bottom', formatLayoutSpace(eidos.resolve(marginBottom)));
|
||||||
|
pushStyleVar(decls, '--box-margin-left', formatLayoutSpace(eidos.resolve(marginLeft)));
|
||||||
|
|
||||||
|
pushStyleVar(decls, '--box-gap', formatLayoutSpace(eidos.resolve(gap)));
|
||||||
|
|
||||||
|
pushStyleVar(decls, '--box-position', eidos.resolve(position));
|
||||||
|
pushStyleVar(decls, '--box-top', formatLayoutLength(eidos.resolve(top)));
|
||||||
|
pushStyleVar(decls, '--box-right', formatLayoutLength(eidos.resolve(right)));
|
||||||
|
pushStyleVar(decls, '--box-bottom', formatLayoutLength(eidos.resolve(bottom)));
|
||||||
|
pushStyleVar(decls, '--box-left', formatLayoutLength(eidos.resolve(left)));
|
||||||
|
pushStyleVar(decls, '--box-inset', formatLayoutLength(eidos.resolve(inset)));
|
||||||
|
pushStyleVar(decls, '--box-inset-inline', formatLayoutLength(eidos.resolve(insetX)));
|
||||||
|
pushStyleVar(decls, '--box-inset-block', formatLayoutLength(eidos.resolve(insetY)));
|
||||||
|
|
||||||
|
pushStyleVar(decls, '--box-overflow', eidos.resolve(overflow));
|
||||||
|
pushStyleVar(decls, '--box-overflow-x', eidos.resolve(overflowX));
|
||||||
|
pushStyleVar(decls, '--box-overflow-y', eidos.resolve(overflowY));
|
||||||
|
|
||||||
|
pushStyleVar(decls, '--box-flex', formatLayoutRaw(eidos.resolve(flex)));
|
||||||
|
pushStyleVar(decls, '--box-grow', formatLayoutRaw(eidos.resolve(grow)));
|
||||||
|
pushStyleVar(decls, '--box-shrink', formatLayoutRaw(eidos.resolve(shrink)));
|
||||||
|
pushStyleVar(decls, '--box-basis', formatLayoutLength(eidos.resolve(basis)));
|
||||||
|
pushStyleVar(decls, '--box-order', formatLayoutRaw(eidos.resolve(order)));
|
||||||
|
pushStyleVar(decls, '--box-align-self', eidos.resolve(alignSelf));
|
||||||
|
pushStyleVar(decls, '--box-justify-self', eidos.resolve(justifySelf));
|
||||||
|
|
||||||
|
return composeStyle(decls, style);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div {...restProps} data-box="" class={className} style={resolvedStyle}>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
// Box — universal box-model utility.
|
||||||
|
//
|
||||||
|
// import { Box } from '$uix/eidos/components/box';
|
||||||
|
//
|
||||||
|
// <Box padding={4} maxWidth={600} display="flex" gap={3}>…</Box>
|
||||||
|
import Box from './box.svelte';
|
||||||
|
|
||||||
|
export { Box };
|
||||||
|
export default Box;
|
||||||
|
export type { BoxProps } from './types';
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
import type { ResponsiveProp } from '$uix/eidos/lib/types';
|
||||||
|
import type {
|
||||||
|
LayoutAlignSelf,
|
||||||
|
LayoutDisplay,
|
||||||
|
LayoutJustifySelf,
|
||||||
|
LayoutLengthValue,
|
||||||
|
LayoutOverflow,
|
||||||
|
LayoutPosition,
|
||||||
|
LayoutSpaceValue
|
||||||
|
} from '../_layout/shared';
|
||||||
|
|
||||||
|
export type BoxProps = Omit<HTMLAttributes<HTMLElement>, 'style' | 'children'> & {
|
||||||
|
/** `display`. */
|
||||||
|
display?: ResponsiveProp<LayoutDisplay>;
|
||||||
|
/** `width`. Number -> px, string passes through. */
|
||||||
|
width?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
/** `min-width`. */
|
||||||
|
minWidth?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
/** `max-width`. */
|
||||||
|
maxWidth?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
/** `height`. */
|
||||||
|
height?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
/** `min-height`. */
|
||||||
|
minHeight?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
/** `max-height`. */
|
||||||
|
maxHeight?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
/** `padding`. Number -> `--space-N`, string passes through. */
|
||||||
|
padding?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
/** `padding-inline` (left+right). */
|
||||||
|
paddingX?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
/** `padding-block` (top+bottom). */
|
||||||
|
paddingY?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
paddingTop?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
paddingRight?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
paddingBottom?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
paddingLeft?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
/** `margin`. */
|
||||||
|
margin?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
/** `margin-inline`. */
|
||||||
|
marginX?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
/** `margin-block`. */
|
||||||
|
marginY?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
marginTop?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
marginRight?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
marginBottom?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
marginLeft?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
/** `gap` (flex/grid). */
|
||||||
|
gap?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
/** `position`. */
|
||||||
|
position?: ResponsiveProp<LayoutPosition>;
|
||||||
|
top?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
right?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
bottom?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
left?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
/** Shorthand for all four insets. */
|
||||||
|
inset?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
/** `inset-inline` (left+right). */
|
||||||
|
insetX?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
/** `inset-block` (top+bottom). */
|
||||||
|
insetY?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
/** `overflow`. */
|
||||||
|
overflow?: ResponsiveProp<LayoutOverflow>;
|
||||||
|
overflowX?: ResponsiveProp<LayoutOverflow>;
|
||||||
|
overflowY?: ResponsiveProp<LayoutOverflow>;
|
||||||
|
/** Flex item: `flex` shorthand. */
|
||||||
|
flex?: ResponsiveProp<string | number>;
|
||||||
|
/** Flex item: `flex-grow`. */
|
||||||
|
grow?: ResponsiveProp<string | number>;
|
||||||
|
/** Flex item: `flex-shrink`. */
|
||||||
|
shrink?: ResponsiveProp<string | number>;
|
||||||
|
/** Flex item: `flex-basis`. */
|
||||||
|
basis?: ResponsiveProp<LayoutLengthValue>;
|
||||||
|
/** Flex/grid item: `order`. */
|
||||||
|
order?: ResponsiveProp<string | number>;
|
||||||
|
/** Flex/grid item: `align-self`. */
|
||||||
|
alignSelf?: ResponsiveProp<LayoutAlignSelf>;
|
||||||
|
/** Grid item: `justify-self`. */
|
||||||
|
justifySelf?: ResponsiveProp<LayoutJustifySelf>;
|
||||||
|
/** Extra inline style. Merged after the box's own CSS variable declarations. */
|
||||||
|
style?: string;
|
||||||
|
/** Extra class names. */
|
||||||
|
class?: string;
|
||||||
|
children?: Snippet;
|
||||||
|
};
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Container recipe — all sizing/alignment is driven via the Box prop
|
||||||
|
* channel (maxWidth, marginLeft/Right). The dedicated selector exists
|
||||||
|
* so consumers can target `[data-container]` for archetype-level
|
||||||
|
* adjustments without needing to know the Box marker.
|
||||||
|
*
|
||||||
|
* Width and padding fall back to foundation tokens when the JS layer
|
||||||
|
* hasn't pushed them (e.g. SSR snapshots before hydration).
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-box][data-container] {
|
||||||
|
max-width: var(--box-max-width, var(--container-width-xl));
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Eidos `<Container>` — max-width content container.
|
||||||
|
*
|
||||||
|
* Caps content width at the `--container-width-{size}` token from the
|
||||||
|
* eidos foundation, applies a horizontal padding so text doesn't run
|
||||||
|
* to the viewport edge on small screens, and centers the content
|
||||||
|
* (`align='center'`) by default.
|
||||||
|
*
|
||||||
|
* <Container size="lg">…</Container>
|
||||||
|
* <Container size="xl" align="left">…</Container>
|
||||||
|
*/
|
||||||
|
import { ActiveEidos } from '$uix/eidos';
|
||||||
|
import Box from '../box/box.svelte';
|
||||||
|
import type { ContainerAlign, ContainerProps, ContainerSize } from './types';
|
||||||
|
|
||||||
|
const SIZE_TO_WIDTH: Record<ContainerSize, string> = {
|
||||||
|
sm: 'var(--container-width-sm)',
|
||||||
|
md: 'var(--container-width-md)',
|
||||||
|
lg: 'var(--container-width-lg)',
|
||||||
|
xl: 'var(--container-width-xl)',
|
||||||
|
xxl: 'var(--container-width-xxl)',
|
||||||
|
full: 'var(--container-width-full)'
|
||||||
|
};
|
||||||
|
|
||||||
|
const ALIGN_TO_MARGINS: Record<ContainerAlign, { left: string; right: string }> = {
|
||||||
|
left: { left: '0', right: 'auto' },
|
||||||
|
center: { left: 'auto', right: 'auto' },
|
||||||
|
right: { left: 'auto', right: '0' }
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
size = 'xl',
|
||||||
|
align = 'center',
|
||||||
|
paddingX,
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: ContainerProps = $props();
|
||||||
|
|
||||||
|
const eidos = ActiveEidos.require();
|
||||||
|
|
||||||
|
const resolvedSize = $derived(eidos.resolve(size, 'xl'));
|
||||||
|
const resolvedAlign = $derived(eidos.resolve(align, 'center'));
|
||||||
|
const resolvedMaxWidth = $derived(SIZE_TO_WIDTH[resolvedSize]);
|
||||||
|
const resolvedMargins = $derived(ALIGN_TO_MARGINS[resolvedAlign]);
|
||||||
|
// Default horizontal padding comes from the foundation token; caller
|
||||||
|
// can override via the BoxProps `paddingX` prop.
|
||||||
|
const resolvedPaddingX = $derived(paddingX ?? 'var(--container-padding-inline)');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
{...restProps}
|
||||||
|
width="100%"
|
||||||
|
maxWidth={resolvedMaxWidth}
|
||||||
|
paddingX={resolvedPaddingX}
|
||||||
|
marginLeft={resolvedMargins.left}
|
||||||
|
marginRight={resolvedMargins.right}
|
||||||
|
class={className}
|
||||||
|
data-container=""
|
||||||
|
data-size={resolvedSize}
|
||||||
|
data-align={resolvedAlign}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</Box>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
// Container — max-width centered content container.
|
||||||
|
//
|
||||||
|
// import { Container } from '$uix/eidos/components/container';
|
||||||
|
//
|
||||||
|
// <Container size="lg">…</Container>
|
||||||
|
import Container from './container.svelte';
|
||||||
|
|
||||||
|
export { Container };
|
||||||
|
export default Container;
|
||||||
|
export type { ContainerProps, ContainerSize, ContainerAlign } from './types';
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import type { ResponsiveProp, Size } from '$uix/eidos/lib/types';
|
||||||
|
import type { BoxProps } from '../box/types';
|
||||||
|
|
||||||
|
export type ContainerSize = Extract<Size, 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'full'>;
|
||||||
|
export type ContainerAlign = 'left' | 'center' | 'right';
|
||||||
|
|
||||||
|
export type ContainerProps = BoxProps & {
|
||||||
|
/**
|
||||||
|
* Max content width. Maps to the `--container-width-{size}` tokens
|
||||||
|
* defined in the eidos foundation. `full` removes the cap.
|
||||||
|
* @default 'xl'
|
||||||
|
*/
|
||||||
|
size?: ResponsiveProp<ContainerSize>;
|
||||||
|
/** Horizontal alignment within the parent. @default 'center' */
|
||||||
|
align?: ResponsiveProp<ContainerAlign>;
|
||||||
|
};
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Flex recipe — additional CSS variables layered on top of the Box
|
||||||
|
* recipe. `row-gap` and `column-gap` fall back to the Box-level `--box-gap`
|
||||||
|
* so callers can use `gap={N}` to set both axes at once.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-box][data-flex] {
|
||||||
|
flex-direction: var(--flex-direction, row);
|
||||||
|
align-items: var(--flex-align, stretch);
|
||||||
|
justify-content: var(--flex-justify, flex-start);
|
||||||
|
flex-wrap: var(--flex-wrap, nowrap);
|
||||||
|
row-gap: var(--flex-row-gap, var(--box-gap, revert-layer));
|
||||||
|
column-gap: var(--flex-column-gap, var(--box-gap, revert-layer));
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Eidos `<Flex>` — `display:flex` container. Wraps `<Box>` and adds the
|
||||||
|
* flex-axis vars (`direction`, `align`, `justify`, `wrap`, row/column
|
||||||
|
* gap). Marker is `data-flex` and the resolved direction is exposed
|
||||||
|
* as `data-direction` for selector-driven recipes (Group needs it to
|
||||||
|
* compute attached overlap on the correct axis).
|
||||||
|
*
|
||||||
|
* <Flex direction="row" gap={3} align="center" justify="space-between">…</Flex>
|
||||||
|
*/
|
||||||
|
import { ActiveEidos } from '$uix/eidos';
|
||||||
|
import Box from '../box/box.svelte';
|
||||||
|
import { composeStyle, formatLayoutSpace, pushStyleVar } from '../_layout/shared';
|
||||||
|
import type { FlexProps } from './types';
|
||||||
|
|
||||||
|
let {
|
||||||
|
inline = false,
|
||||||
|
direction = 'row',
|
||||||
|
align,
|
||||||
|
justify,
|
||||||
|
wrap = 'nowrap',
|
||||||
|
rowGap,
|
||||||
|
columnGap,
|
||||||
|
style,
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: FlexProps = $props();
|
||||||
|
|
||||||
|
const eidos = ActiveEidos.require();
|
||||||
|
|
||||||
|
const resolvedStyle = $derived.by(() => {
|
||||||
|
const decls: string[] = [];
|
||||||
|
pushStyleVar(decls, '--flex-direction', eidos.resolve(direction));
|
||||||
|
pushStyleVar(decls, '--flex-align', eidos.resolve(align));
|
||||||
|
pushStyleVar(decls, '--flex-justify', eidos.resolve(justify));
|
||||||
|
pushStyleVar(decls, '--flex-wrap', eidos.resolve(wrap));
|
||||||
|
pushStyleVar(decls, '--flex-row-gap', formatLayoutSpace(eidos.resolve(rowGap)));
|
||||||
|
pushStyleVar(decls, '--flex-column-gap', formatLayoutSpace(eidos.resolve(columnGap)));
|
||||||
|
return composeStyle(decls, style);
|
||||||
|
});
|
||||||
|
|
||||||
|
const resolvedDirection = $derived(eidos.resolve(direction) ?? 'row');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
{...restProps}
|
||||||
|
display={inline ? 'inline-flex' : 'flex'}
|
||||||
|
class={className}
|
||||||
|
style={resolvedStyle}
|
||||||
|
data-flex=""
|
||||||
|
data-direction={resolvedDirection}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</Box>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
// Flex — display:flex container.
|
||||||
|
//
|
||||||
|
// import { Flex } from '$uix/eidos/components/flex';
|
||||||
|
//
|
||||||
|
// <Flex direction="row" gap={3} align="center">…</Flex>
|
||||||
|
import Flex from './flex.svelte';
|
||||||
|
|
||||||
|
export { Flex };
|
||||||
|
export default Flex;
|
||||||
|
export type { FlexProps } from './types';
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import type { ResponsiveProp } from '$uix/eidos/lib/types';
|
||||||
|
import type { BoxProps } from '../box/types';
|
||||||
|
import type {
|
||||||
|
LayoutAlignItems,
|
||||||
|
LayoutFlexDirection,
|
||||||
|
LayoutFlexWrap,
|
||||||
|
LayoutJustifyContent,
|
||||||
|
LayoutSpaceValue
|
||||||
|
} from '../_layout/shared';
|
||||||
|
|
||||||
|
export type FlexProps = Omit<BoxProps, 'display'> & {
|
||||||
|
/** Use `inline-flex` instead of `flex`. @default false */
|
||||||
|
inline?: boolean;
|
||||||
|
/** `flex-direction`. @default 'row' */
|
||||||
|
direction?: ResponsiveProp<LayoutFlexDirection>;
|
||||||
|
/** `align-items`. */
|
||||||
|
align?: ResponsiveProp<LayoutAlignItems>;
|
||||||
|
/** `justify-content`. */
|
||||||
|
justify?: ResponsiveProp<LayoutJustifyContent>;
|
||||||
|
/** `flex-wrap`. @default 'nowrap' */
|
||||||
|
wrap?: ResponsiveProp<LayoutFlexWrap>;
|
||||||
|
/** `row-gap`. Overrides `gap` for the row axis. */
|
||||||
|
rowGap?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
/** `column-gap`. Overrides `gap` for the column axis. */
|
||||||
|
columnGap?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
};
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Grid recipe — additional CSS variables layered on top of the Box
|
||||||
|
* recipe. Track and placement props (`place-items`, `place-content`,
|
||||||
|
* `grid-column`, `grid-row`, `grid-area`) fall through to `revert-layer`
|
||||||
|
* so they only apply when explicitly set.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-box][data-grid] {
|
||||||
|
grid-template-columns: var(--grid-template-columns, none);
|
||||||
|
grid-template-rows: var(--grid-template-rows, none);
|
||||||
|
grid-auto-columns: var(--grid-auto-columns, auto);
|
||||||
|
grid-auto-rows: var(--grid-auto-rows, auto);
|
||||||
|
grid-auto-flow: var(--grid-auto-flow, row);
|
||||||
|
align-items: var(--grid-align, stretch);
|
||||||
|
justify-content: var(--grid-justify, start);
|
||||||
|
place-items: var(--grid-place-items, revert-layer);
|
||||||
|
place-content: var(--grid-place-content, revert-layer);
|
||||||
|
grid-column: var(--grid-column, revert-layer);
|
||||||
|
grid-row: var(--grid-row, revert-layer);
|
||||||
|
grid-area: var(--grid-area, revert-layer);
|
||||||
|
row-gap: var(--grid-row-gap, var(--grid-gap, revert-layer));
|
||||||
|
column-gap: var(--grid-column-gap, var(--grid-gap, revert-layer));
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Eidos `<Grid>` — `display:grid` container. Adds the grid-template /
|
||||||
|
* auto-flow / placement vars on top of `<Box>`. The `gap` prop here
|
||||||
|
* lives in the grid layer so it can be authored independently from
|
||||||
|
* the Box-level `gap` if a caller mixes Box-as-Grid-item with the
|
||||||
|
* Grid container's own gap.
|
||||||
|
*
|
||||||
|
* <Grid templateColumns="repeat(3, 1fr)" gap={3}>…</Grid>
|
||||||
|
*/
|
||||||
|
import { ActiveEidos } from '$uix/eidos';
|
||||||
|
import Box from '../box/box.svelte';
|
||||||
|
import { composeStyle, formatLayoutSpace, pushStyleVar } from '../_layout/shared';
|
||||||
|
import type { GridProps } from './types';
|
||||||
|
|
||||||
|
let {
|
||||||
|
templateColumns,
|
||||||
|
templateRows,
|
||||||
|
autoColumns,
|
||||||
|
autoRows,
|
||||||
|
autoFlow,
|
||||||
|
columnGap,
|
||||||
|
rowGap,
|
||||||
|
gap,
|
||||||
|
align,
|
||||||
|
justify,
|
||||||
|
placeItems,
|
||||||
|
placeContent,
|
||||||
|
gridColumn,
|
||||||
|
gridRow,
|
||||||
|
gridArea,
|
||||||
|
style,
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: GridProps = $props();
|
||||||
|
|
||||||
|
const eidos = ActiveEidos.require();
|
||||||
|
|
||||||
|
const resolvedStyle = $derived.by(() => {
|
||||||
|
const decls: string[] = [];
|
||||||
|
pushStyleVar(decls, '--grid-template-columns', eidos.resolve(templateColumns));
|
||||||
|
pushStyleVar(decls, '--grid-template-rows', eidos.resolve(templateRows));
|
||||||
|
pushStyleVar(decls, '--grid-auto-columns', eidos.resolve(autoColumns));
|
||||||
|
pushStyleVar(decls, '--grid-auto-rows', eidos.resolve(autoRows));
|
||||||
|
pushStyleVar(decls, '--grid-auto-flow', eidos.resolve(autoFlow));
|
||||||
|
pushStyleVar(decls, '--grid-align', eidos.resolve(align));
|
||||||
|
pushStyleVar(decls, '--grid-justify', eidos.resolve(justify));
|
||||||
|
pushStyleVar(decls, '--grid-place-items', eidos.resolve(placeItems));
|
||||||
|
pushStyleVar(decls, '--grid-place-content', eidos.resolve(placeContent));
|
||||||
|
pushStyleVar(decls, '--grid-column', eidos.resolve(gridColumn));
|
||||||
|
pushStyleVar(decls, '--grid-row', eidos.resolve(gridRow));
|
||||||
|
pushStyleVar(decls, '--grid-area', eidos.resolve(gridArea));
|
||||||
|
pushStyleVar(decls, '--grid-gap', formatLayoutSpace(eidos.resolve(gap)));
|
||||||
|
pushStyleVar(decls, '--grid-column-gap', formatLayoutSpace(eidos.resolve(columnGap)));
|
||||||
|
pushStyleVar(decls, '--grid-row-gap', formatLayoutSpace(eidos.resolve(rowGap)));
|
||||||
|
return composeStyle(decls, style);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Box {...restProps} display="grid" class={className} style={resolvedStyle} data-grid="">
|
||||||
|
{@render children?.()}
|
||||||
|
</Box>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
// Grid — display:grid container.
|
||||||
|
//
|
||||||
|
// import { Grid } from '$uix/eidos/components/grid';
|
||||||
|
//
|
||||||
|
// <Grid templateColumns="repeat(3, 1fr)" gap={3}>…</Grid>
|
||||||
|
import Grid from './grid.svelte';
|
||||||
|
|
||||||
|
export { Grid };
|
||||||
|
export default Grid;
|
||||||
|
export type { GridProps } from './types';
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import type { ResponsiveProp } from '$uix/eidos/lib/types';
|
||||||
|
import type { BoxProps } from '../box/types';
|
||||||
|
import type {
|
||||||
|
LayoutAlignItems,
|
||||||
|
LayoutGridAutoFlow,
|
||||||
|
LayoutJustifyContent,
|
||||||
|
LayoutSpaceValue
|
||||||
|
} from '../_layout/shared';
|
||||||
|
|
||||||
|
export type GridProps = Omit<BoxProps, 'display'> & {
|
||||||
|
/** `grid-template-columns`. */
|
||||||
|
templateColumns?: ResponsiveProp<string>;
|
||||||
|
/** `grid-template-rows`. */
|
||||||
|
templateRows?: ResponsiveProp<string>;
|
||||||
|
/** `grid-auto-columns`. */
|
||||||
|
autoColumns?: ResponsiveProp<string>;
|
||||||
|
/** `grid-auto-rows`. */
|
||||||
|
autoRows?: ResponsiveProp<string>;
|
||||||
|
/** `grid-auto-flow`. */
|
||||||
|
autoFlow?: ResponsiveProp<LayoutGridAutoFlow>;
|
||||||
|
/** `column-gap`. */
|
||||||
|
columnGap?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
/** `row-gap`. */
|
||||||
|
rowGap?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
/** Shorthand for both `row-gap` and `column-gap`. */
|
||||||
|
gap?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
/** `align-items`. */
|
||||||
|
align?: ResponsiveProp<LayoutAlignItems>;
|
||||||
|
/** `justify-content`. */
|
||||||
|
justify?: ResponsiveProp<LayoutJustifyContent>;
|
||||||
|
/** `place-items` shorthand. */
|
||||||
|
placeItems?: ResponsiveProp<string>;
|
||||||
|
/** `place-content` shorthand. */
|
||||||
|
placeContent?: ResponsiveProp<string>;
|
||||||
|
/** Grid item: `grid-column`. */
|
||||||
|
gridColumn?: ResponsiveProp<string>;
|
||||||
|
/** Grid item: `grid-row`. */
|
||||||
|
gridRow?: ResponsiveProp<string>;
|
||||||
|
/** Grid item: `grid-area`. */
|
||||||
|
gridArea?: ResponsiveProp<string>;
|
||||||
|
};
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Group recipe — cluster behavior driven by `data-grow` and
|
||||||
|
* `data-attached`. The attached overlap defaults to 1px so adjacent
|
||||||
|
* 1px borders collapse cleanly; consumers can override via
|
||||||
|
* `--group-attached-overlap`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-box][data-flex][data-group] {
|
||||||
|
--group-attached-overlap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-box][data-flex][data-group][data-grow] > * {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Attached: row direction ───────────────────────────────────────── */
|
||||||
|
[data-box][data-flex][data-group][data-attached]:not([data-direction='column']):not(
|
||||||
|
[data-direction='column-reverse']
|
||||||
|
)
|
||||||
|
> :not(:first-child) {
|
||||||
|
margin-inline-start: calc(var(--group-attached-overlap) * -1);
|
||||||
|
border-start-start-radius: 0 !important;
|
||||||
|
border-end-start-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-box][data-flex][data-group][data-attached]:not([data-direction='column']):not(
|
||||||
|
[data-direction='column-reverse']
|
||||||
|
)
|
||||||
|
> :not(:last-child) {
|
||||||
|
border-start-end-radius: 0 !important;
|
||||||
|
border-end-end-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Attached: column direction ────────────────────────────────────── */
|
||||||
|
[data-box][data-flex][data-group][data-attached][data-direction='column'] > :not(:first-child),
|
||||||
|
[data-box][data-flex][data-group][data-attached][data-direction='column-reverse']
|
||||||
|
> :not(:first-child) {
|
||||||
|
margin-block-start: calc(var(--group-attached-overlap) * -1);
|
||||||
|
border-start-start-radius: 0 !important;
|
||||||
|
border-start-end-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-box][data-flex][data-group][data-attached][data-direction='column'] > :not(:last-child),
|
||||||
|
[data-box][data-flex][data-group][data-attached][data-direction='column-reverse']
|
||||||
|
> :not(:last-child) {
|
||||||
|
border-end-start-radius: 0 !important;
|
||||||
|
border-end-end-radius: 0 !important;
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Eidos `<Group>` — inline cluster. Flex row by default, with two
|
||||||
|
* cluster-specific behaviors:
|
||||||
|
*
|
||||||
|
* `grow` — every child stretches to equal width (`flex: 1 1 0`)
|
||||||
|
* `attached` — adjacent children share an edge: the inner radii are
|
||||||
|
* squared, and a 1px overlap absorbs doubled borders.
|
||||||
|
* Use for segmented controls and button groups.
|
||||||
|
*
|
||||||
|
* <Group gap={2}>…</Group>
|
||||||
|
* <Group attached>…</Group>
|
||||||
|
* <Group grow>…</Group>
|
||||||
|
*/
|
||||||
|
import Flex from '../flex/flex.svelte';
|
||||||
|
import type { GroupProps } from './types';
|
||||||
|
|
||||||
|
let {
|
||||||
|
grow = false,
|
||||||
|
attached = false,
|
||||||
|
direction = 'row',
|
||||||
|
gap,
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: GroupProps = $props();
|
||||||
|
|
||||||
|
// `attached` implies `gap=0`; an explicit `gap` prop still wins.
|
||||||
|
const effectiveGap = $derived(gap !== undefined ? gap : attached ? 0 : undefined);
|
||||||
|
const toBoolAttr = (value: boolean) => (value ? '' : undefined);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
{...restProps}
|
||||||
|
{direction}
|
||||||
|
gap={effectiveGap}
|
||||||
|
class={className}
|
||||||
|
data-group=""
|
||||||
|
data-grow={toBoolAttr(grow)}
|
||||||
|
data-attached={toBoolAttr(attached)}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</Flex>
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
// Group — inline cluster (flex row by default).
|
||||||
|
//
|
||||||
|
// import { Group } from '$uix/eidos/components/group';
|
||||||
|
//
|
||||||
|
// <Group gap={2}>…</Group>
|
||||||
|
// <Group attached>…</Group>
|
||||||
|
// <Group grow>…</Group>
|
||||||
|
import Group from './group.svelte';
|
||||||
|
|
||||||
|
export { Group };
|
||||||
|
export default Group;
|
||||||
|
export type { GroupProps } from './types';
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import type { ResponsiveProp } from '$uix/eidos/lib/types';
|
||||||
|
import type { FlexProps } from '../flex/types';
|
||||||
|
|
||||||
|
export type GroupProps = Omit<FlexProps, 'wrap'> & {
|
||||||
|
/**
|
||||||
|
* Make every child flex to equal width (`flex: 1 1 0`). Useful for
|
||||||
|
* toolbars where buttons should fill the row evenly.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
grow?: boolean;
|
||||||
|
/**
|
||||||
|
* Attach children edge-to-edge, hiding adjacent borders/radii to
|
||||||
|
* produce a single visual unit (segmented control, button group).
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
attached?: boolean;
|
||||||
|
/** @default 'row' */
|
||||||
|
direction?: ResponsiveProp<'row' | 'column'>;
|
||||||
|
};
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
// Section — top-level section with block-axis padding.
|
||||||
|
//
|
||||||
|
// import { Section } from '$uix/eidos/components/section';
|
||||||
|
//
|
||||||
|
// <Section size="lg">…</Section>
|
||||||
|
import Section from './section.svelte';
|
||||||
|
|
||||||
|
export { Section };
|
||||||
|
export default Section;
|
||||||
|
export type { SectionProps, SectionSize } from './types';
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Section recipe — defines the size-keyed padding tokens locally. These
|
||||||
|
* are intentionally not in the generated foundation: Section is the
|
||||||
|
* sole consumer and the values live with the component that owns the
|
||||||
|
* scale.
|
||||||
|
*
|
||||||
|
* sm -> 32px
|
||||||
|
* md -> 48px
|
||||||
|
* lg -> 64px
|
||||||
|
* xl -> 96px
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-box][data-section] {
|
||||||
|
--section-padding-block-sm: var(--space-8);
|
||||||
|
--section-padding-block-md: var(--space-12);
|
||||||
|
--section-padding-block-lg: var(--space-16);
|
||||||
|
--section-padding-block-xl: calc(var(--space-16) * 1.5);
|
||||||
|
|
||||||
|
inline-size: 100%;
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Eidos `<Section>` — top-level page section with consistent
|
||||||
|
* block-axis padding.
|
||||||
|
*
|
||||||
|
* Visually this is a Box with a default `padding-block` keyed off
|
||||||
|
* `size`. Per the batch-1 simplification we render as a `<div>` — if
|
||||||
|
* a consumer needs an actual `<section>` element for landmark
|
||||||
|
* semantics they can wrap or nest one.
|
||||||
|
*
|
||||||
|
* <Section size="lg">…</Section>
|
||||||
|
* <Section size="sm" paddingY={4}>…</Section>
|
||||||
|
*/
|
||||||
|
import { ActiveEidos } from '$uix/eidos';
|
||||||
|
import Box from '../box/box.svelte';
|
||||||
|
import type { SectionProps, SectionSize } from './types';
|
||||||
|
|
||||||
|
const SIZE_TO_PADDING: Record<SectionSize, string> = {
|
||||||
|
sm: 'var(--section-padding-block-sm)',
|
||||||
|
md: 'var(--section-padding-block-md)',
|
||||||
|
lg: 'var(--section-padding-block-lg)',
|
||||||
|
xl: 'var(--section-padding-block-xl)'
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
size = 'lg',
|
||||||
|
paddingY,
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: SectionProps = $props();
|
||||||
|
|
||||||
|
const eidos = ActiveEidos.require();
|
||||||
|
|
||||||
|
const resolvedSize = $derived(eidos.resolve(size, 'lg'));
|
||||||
|
const resolvedPaddingY = $derived(paddingY ?? SIZE_TO_PADDING[resolvedSize]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
{...restProps}
|
||||||
|
display="block"
|
||||||
|
width="100%"
|
||||||
|
paddingY={resolvedPaddingY}
|
||||||
|
class={className}
|
||||||
|
data-section=""
|
||||||
|
data-size={resolvedSize}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</Box>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import type { ResponsiveProp, Size } from '$uix/eidos/lib/types';
|
||||||
|
import type { BoxProps } from '../box/types';
|
||||||
|
|
||||||
|
export type SectionSize = Extract<Size, 'sm' | 'md' | 'lg' | 'xl'>;
|
||||||
|
|
||||||
|
export type SectionProps = BoxProps & {
|
||||||
|
/**
|
||||||
|
* Vertical (block-axis) padding. Maps to size-scoped padding tokens
|
||||||
|
* defined in `section.css`. Override per-instance by passing
|
||||||
|
* `paddingY` directly.
|
||||||
|
* @default 'lg'
|
||||||
|
*/
|
||||||
|
size?: ResponsiveProp<SectionSize>;
|
||||||
|
};
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
// Stack — vertical (default) or horizontal stack.
|
||||||
|
//
|
||||||
|
// import { Stack } from '$uix/eidos/components/stack';
|
||||||
|
//
|
||||||
|
// <Stack gap={2}>…</Stack>
|
||||||
|
// <Stack direction="row" gap={3}>…</Stack>
|
||||||
|
import Stack from './stack.svelte';
|
||||||
|
|
||||||
|
export { Stack };
|
||||||
|
export default Stack;
|
||||||
|
export type { StackProps, StackDirection } from './types';
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Stack recipe — no per-axis override; Stack reuses the Flex `gap`
|
||||||
|
* channel directly. Marker selector exists so consumers can target
|
||||||
|
* `[data-stack]` for archetype-level rules.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-box][data-flex][data-stack] {
|
||||||
|
/* All visual behavior comes from Flex; this rule exists so the marker
|
||||||
|
* selector matches at least one declaration and isn't dropped by the
|
||||||
|
* build's empty-rule pruning. */
|
||||||
|
display: var(--box-display, flex);
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Eidos `<Stack>` — vertical (default) or horizontal stack with a `gap`.
|
||||||
|
* Thin specialization of `<Flex>`: locks `wrap` to nowrap-by-default,
|
||||||
|
* defaults `direction` to `column`, and routes `gap` through the
|
||||||
|
* shared Flex gap channel.
|
||||||
|
*
|
||||||
|
* <Stack gap={2}>…</Stack>
|
||||||
|
* <Stack direction="row" gap={3} align="center">…</Stack>
|
||||||
|
*/
|
||||||
|
import Flex from '../flex/flex.svelte';
|
||||||
|
import type { StackProps } from './types';
|
||||||
|
|
||||||
|
let {
|
||||||
|
direction = 'column',
|
||||||
|
gap,
|
||||||
|
align,
|
||||||
|
justify,
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: StackProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Flex {...restProps} {direction} {gap} {align} {justify} class={className} data-stack="">
|
||||||
|
{@render children?.()}
|
||||||
|
</Flex>
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import type { ResponsiveProp } from '$uix/eidos/lib/types';
|
||||||
|
import type { FlexProps } from '../flex/types';
|
||||||
|
import type {
|
||||||
|
LayoutAlignItems,
|
||||||
|
LayoutFlexDirection,
|
||||||
|
LayoutJustifyContent,
|
||||||
|
LayoutSpaceValue
|
||||||
|
} from '../_layout/shared';
|
||||||
|
|
||||||
|
export type StackDirection = Extract<
|
||||||
|
LayoutFlexDirection,
|
||||||
|
'row' | 'row-reverse' | 'column' | 'column-reverse'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type StackProps = Omit<
|
||||||
|
FlexProps,
|
||||||
|
'direction' | 'wrap' | 'inline' | 'rowGap' | 'columnGap'
|
||||||
|
> & {
|
||||||
|
/** @default 'column' */
|
||||||
|
direction?: ResponsiveProp<StackDirection>;
|
||||||
|
gap?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
align?: ResponsiveProp<LayoutAlignItems>;
|
||||||
|
justify?: ResponsiveProp<LayoutJustifyContent>;
|
||||||
|
};
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
// Wrap — flex row that always wraps.
|
||||||
|
//
|
||||||
|
// import { Wrap } from '$uix/eidos/components/wrap';
|
||||||
|
//
|
||||||
|
// <Wrap gap={2}>{#each tags as t}<Tag>{t}</Tag>{/each}</Wrap>
|
||||||
|
import Wrap from './wrap.svelte';
|
||||||
|
|
||||||
|
export { Wrap };
|
||||||
|
export default Wrap;
|
||||||
|
export type { WrapProps } from './types';
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import type { ResponsiveProp } from '$uix/eidos/lib/types';
|
||||||
|
import type { FlexProps } from '../flex/types';
|
||||||
|
import type { LayoutAlignItems, LayoutJustifyContent, LayoutSpaceValue } from '../_layout/shared';
|
||||||
|
|
||||||
|
export type WrapProps = Omit<FlexProps, 'direction' | 'wrap'> & {
|
||||||
|
gap?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
rowGap?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
columnGap?: ResponsiveProp<LayoutSpaceValue>;
|
||||||
|
align?: ResponsiveProp<LayoutAlignItems>;
|
||||||
|
justify?: ResponsiveProp<LayoutJustifyContent>;
|
||||||
|
};
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Wrap recipe — locks `flex-wrap: wrap` over the Flex default. The
|
||||||
|
* data-wrap attribute also gates any future archetype rules that want
|
||||||
|
* to distinguish "wrap by intent" from "wrap=wrap configured on a Flex".
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-box][data-flex][data-wrap] {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Eidos `<Wrap>` — flex row that always wraps. Use when you want a
|
||||||
|
* cluster of tags / chips / pills that should reflow across multiple
|
||||||
|
* lines on narrow viewports.
|
||||||
|
*
|
||||||
|
* <Wrap gap={2}>{#each tags as t}<Tag>{t}</Tag>{/each}</Wrap>
|
||||||
|
*/
|
||||||
|
import Flex from '../flex/flex.svelte';
|
||||||
|
import type { WrapProps } from './types';
|
||||||
|
|
||||||
|
let {
|
||||||
|
gap,
|
||||||
|
rowGap,
|
||||||
|
columnGap,
|
||||||
|
align,
|
||||||
|
justify,
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WrapProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
{...restProps}
|
||||||
|
wrap="wrap"
|
||||||
|
{gap}
|
||||||
|
{rowGap}
|
||||||
|
{columnGap}
|
||||||
|
{align}
|
||||||
|
{justify}
|
||||||
|
class={className}
|
||||||
|
data-wrap=""
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</Flex>
|
||||||
@ -0,0 +1,133 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Box } from '$uix/eidos/components/box';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-uix-canvas-inner>
|
||||||
|
<header>
|
||||||
|
<div data-uix-eyebrow>Layout · Box</div>
|
||||||
|
<h1 data-uix-page-title>Box</h1>
|
||||||
|
<p data-uix-page-lede>
|
||||||
|
Universal box-model utility. Every prop maps to a CSS custom property; unset
|
||||||
|
props fall through to the normal cascade.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Live example</h2>
|
||||||
|
<Box
|
||||||
|
padding={4}
|
||||||
|
maxWidth={480}
|
||||||
|
display="flex"
|
||||||
|
gap={3}
|
||||||
|
style="background: var(--color-surface-1); border: 1px solid var(--color-border); border-radius: 8px"
|
||||||
|
>
|
||||||
|
<Box padding={2} style="background: var(--color-surface-2); border-radius: 4px">
|
||||||
|
Cell A
|
||||||
|
</Box>
|
||||||
|
<Box padding={2} style="background: var(--color-surface-2); border-radius: 4px">
|
||||||
|
Cell B
|
||||||
|
</Box>
|
||||||
|
<Box padding={2} style="background: var(--color-surface-2); border-radius: 4px">
|
||||||
|
Cell C
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Props</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Prop</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>display</code></td>
|
||||||
|
<td>LayoutDisplay</td>
|
||||||
|
<td>block · inline · flex · grid · …</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>width</code> / <code>minWidth</code> / <code>maxWidth</code></td>
|
||||||
|
<td>number | string</td>
|
||||||
|
<td>number → px, string passes through</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>height</code> / <code>minHeight</code> / <code>maxHeight</code></td>
|
||||||
|
<td>number | string</td>
|
||||||
|
<td>same as above</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>padding</code> / <code>paddingX</code> / <code>paddingY</code></td>
|
||||||
|
<td>number | string</td>
|
||||||
|
<td>number → <code>var(--space-N)</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>paddingTop</code> / <code>paddingRight</code> /
|
||||||
|
<code>paddingBottom</code> / <code>paddingLeft</code>
|
||||||
|
</td>
|
||||||
|
<td>number | string</td>
|
||||||
|
<td>per-side overrides</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>margin</code> / <code>marginX</code> / <code>marginY</code> / sides</td>
|
||||||
|
<td>number | string</td>
|
||||||
|
<td>same mapping as padding</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>gap</code></td>
|
||||||
|
<td>number | string</td>
|
||||||
|
<td>for flex/grid containers</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>position</code> / <code>top</code> / <code>right</code> /
|
||||||
|
<code>bottom</code> / <code>left</code>
|
||||||
|
</td>
|
||||||
|
<td>various</td>
|
||||||
|
<td>position + insets</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>inset</code> / <code>insetX</code> / <code>insetY</code></td>
|
||||||
|
<td>number | string</td>
|
||||||
|
<td>shorthand insets</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>overflow</code> / <code>overflowX</code> / <code>overflowY</code></td>
|
||||||
|
<td>LayoutOverflow</td>
|
||||||
|
<td>visible · hidden · clip · scroll · auto</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>flex</code> / <code>grow</code> / <code>shrink</code> / <code>basis</code> /
|
||||||
|
<code>order</code> / <code>alignSelf</code> / <code>justifySelf</code>
|
||||||
|
</td>
|
||||||
|
<td>various</td>
|
||||||
|
<td>item-side flex/grid props</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>style</code></td>
|
||||||
|
<td>string</td>
|
||||||
|
<td>extra inline style; merged after Box vars</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Reference</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>radix-themes Box</strong> — same idea: token-driven shorthand for the
|
||||||
|
box model; we mirror the responsive prop shape.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>chakra-ui Box</strong> — origin of the per-side / shorthand prop split
|
||||||
|
(<code>paddingX</code>, <code>paddingY</code>, …).
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Container, type ContainerSize } from '$uix/eidos/components/container';
|
||||||
|
|
||||||
|
const sizes: ContainerSize[] = ['sm', 'md', 'lg', 'xl', 'xxl', 'full'];
|
||||||
|
let size = $state<ContainerSize>('lg');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-uix-canvas-inner>
|
||||||
|
<header>
|
||||||
|
<div data-uix-eyebrow>Layout · Container</div>
|
||||||
|
<h1 data-uix-page-title>Container</h1>
|
||||||
|
<p data-uix-page-lede>
|
||||||
|
Max-width content container. Caps content at the
|
||||||
|
<code>--container-width-{'{size}'}</code> token and centers it.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Live example</h2>
|
||||||
|
<div style="display: flex; gap: 8px; margin-bottom: 12px">
|
||||||
|
{#each sizes as s (s)}
|
||||||
|
<label style="display: inline-flex; gap: 4px; align-items: center">
|
||||||
|
<input type="radio" name="size" value={s} bind:group={size} />
|
||||||
|
<span><code>{s}</code></span>
|
||||||
|
</label>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<Container
|
||||||
|
{size}
|
||||||
|
padding={4}
|
||||||
|
style="background: var(--color-surface-1); border: 1px dashed var(--color-border); border-radius: 8px"
|
||||||
|
>
|
||||||
|
<p style="margin: 0">Container content (size: <code>{size}</code>).</p>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Props</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Prop</th>
|
||||||
|
<th>Default</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>size</code></td>
|
||||||
|
<td>'xl'</td>
|
||||||
|
<td>sm · md · lg · xl · xxl · full</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>align</code></td>
|
||||||
|
<td>'center'</td>
|
||||||
|
<td>left · center · right</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>paddingX</code></td>
|
||||||
|
<td><code>var(--container-padding-inline)</code></td>
|
||||||
|
<td>override per-instance</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3"><em>… plus every <code>BoxProps</code> prop</em></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Reference</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>radix-themes Container</strong> — same surface; sizes map to fixed
|
||||||
|
pixel widths via tokens.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>chakra-ui Container</strong> — origin of the centered max-width pattern.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Flex } from '$uix/eidos/components/flex';
|
||||||
|
import { Box } from '$uix/eidos/components/box';
|
||||||
|
|
||||||
|
const cellStyle =
|
||||||
|
'background: var(--color-surface-2); border-radius: 4px; padding: 8px 12px';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-uix-canvas-inner>
|
||||||
|
<header>
|
||||||
|
<div data-uix-eyebrow>Layout · Flex</div>
|
||||||
|
<h1 data-uix-page-title>Flex</h1>
|
||||||
|
<p data-uix-page-lede>
|
||||||
|
<code>display:flex</code> container with direction, wrap, alignment and gap.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Live example</h2>
|
||||||
|
<Flex
|
||||||
|
direction="row"
|
||||||
|
gap={3}
|
||||||
|
align="center"
|
||||||
|
justify="space-between"
|
||||||
|
padding={4}
|
||||||
|
style="background: var(--color-surface-1); border: 1px solid var(--color-border); border-radius: 8px"
|
||||||
|
>
|
||||||
|
<Box style={cellStyle}>Start</Box>
|
||||||
|
<Box style={cellStyle}>Middle</Box>
|
||||||
|
<Box style={cellStyle}>End</Box>
|
||||||
|
</Flex>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Props</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Prop</th>
|
||||||
|
<th>Default</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>inline</code></td>
|
||||||
|
<td>false</td>
|
||||||
|
<td>switch to <code>inline-flex</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>direction</code></td>
|
||||||
|
<td>'row'</td>
|
||||||
|
<td>row · row-reverse · column · column-reverse</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>wrap</code></td>
|
||||||
|
<td>'nowrap'</td>
|
||||||
|
<td>nowrap · wrap · wrap-reverse</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>align</code></td>
|
||||||
|
<td>—</td>
|
||||||
|
<td>stretch · start · center · end · baseline</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>justify</code></td>
|
||||||
|
<td>—</td>
|
||||||
|
<td>start · center · end · space-between · space-around · space-evenly</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>gap</code> / <code>rowGap</code> / <code>columnGap</code></td>
|
||||||
|
<td>—</td>
|
||||||
|
<td>row/columnGap override <code>gap</code> per axis</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3"><em>… plus every <code>BoxProps</code> prop except <code>display</code></em></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Reference</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>radix-themes Flex</strong> — same surface (<code>direction</code>,
|
||||||
|
<code>align</code>, <code>justify</code>, <code>wrap</code>, <code>gap</code>).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>chakra-ui Flex</strong> — origin of the responsive Flex container API;
|
||||||
|
our prop names match.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Grid } from '$uix/eidos/components/grid';
|
||||||
|
import { Box } from '$uix/eidos/components/box';
|
||||||
|
|
||||||
|
const cellStyle =
|
||||||
|
'background: var(--color-surface-2); border-radius: 4px; padding: 12px; text-align: center';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-uix-canvas-inner>
|
||||||
|
<header>
|
||||||
|
<div data-uix-eyebrow>Layout · Grid</div>
|
||||||
|
<h1 data-uix-page-title>Grid</h1>
|
||||||
|
<p data-uix-page-lede>
|
||||||
|
<code>display:grid</code> container with track and placement props.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Live example</h2>
|
||||||
|
<Grid
|
||||||
|
templateColumns="repeat(3, 1fr)"
|
||||||
|
gap={3}
|
||||||
|
padding={4}
|
||||||
|
style="background: var(--color-surface-1); border: 1px solid var(--color-border); border-radius: 8px"
|
||||||
|
>
|
||||||
|
<Box style={cellStyle}>1</Box>
|
||||||
|
<Box style={cellStyle}>2</Box>
|
||||||
|
<Box style={cellStyle}>3</Box>
|
||||||
|
<Box style={cellStyle}>4</Box>
|
||||||
|
<Box style={cellStyle}>5</Box>
|
||||||
|
<Box style={cellStyle}>6</Box>
|
||||||
|
</Grid>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Props</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Prop</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>templateColumns</code> / <code>templateRows</code></td>
|
||||||
|
<td>raw <code>grid-template-*</code> strings</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>autoColumns</code> / <code>autoRows</code></td>
|
||||||
|
<td>implicit track sizes</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>autoFlow</code></td>
|
||||||
|
<td>row · column · dense · row dense · column dense</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>gap</code> / <code>rowGap</code> / <code>columnGap</code></td>
|
||||||
|
<td>row/columnGap override <code>gap</code> per axis</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>align</code> / <code>justify</code></td>
|
||||||
|
<td><code>align-items</code> / <code>justify-content</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>placeItems</code> / <code>placeContent</code></td>
|
||||||
|
<td>raw shorthand strings</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>gridColumn</code> / <code>gridRow</code> / <code>gridArea</code></td>
|
||||||
|
<td>per-item placement</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><em>… plus every <code>BoxProps</code> prop except <code>display</code></em></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Reference</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>CSS Grid spec</strong> — props are 1:1 with the CSS properties they wrap.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>radix-themes Grid</strong> — same prop names (<code>columns</code> is
|
||||||
|
called <code>templateColumns</code> here, for symmetry with <code>templateRows</code>).
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Group } from '$uix/eidos/components/group';
|
||||||
|
|
||||||
|
const btnStyle =
|
||||||
|
'background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: 6px; padding: 6px 12px; font: inherit';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-uix-canvas-inner>
|
||||||
|
<header>
|
||||||
|
<div data-uix-eyebrow>Layout · Group</div>
|
||||||
|
<h1 data-uix-page-title>Group</h1>
|
||||||
|
<p data-uix-page-lede>
|
||||||
|
Inline cluster of children. Supports <code>grow</code> (equal-width children)
|
||||||
|
and <code>attached</code> (edge-merged children).
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Default</h2>
|
||||||
|
<Group gap={2}>
|
||||||
|
<button type="button" style={btnStyle}>One</button>
|
||||||
|
<button type="button" style={btnStyle}>Two</button>
|
||||||
|
<button type="button" style={btnStyle}>Three</button>
|
||||||
|
</Group>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Attached (segmented control)</h2>
|
||||||
|
<Group attached>
|
||||||
|
<button type="button" style={btnStyle}>Day</button>
|
||||||
|
<button type="button" style={btnStyle}>Week</button>
|
||||||
|
<button type="button" style={btnStyle}>Month</button>
|
||||||
|
</Group>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Grow (equal widths)</h2>
|
||||||
|
<Group grow gap={2} style="width: 320px">
|
||||||
|
<button type="button" style={btnStyle}>Cancel</button>
|
||||||
|
<button type="button" style={btnStyle}>Save</button>
|
||||||
|
</Group>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Props</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Prop</th>
|
||||||
|
<th>Default</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>direction</code></td>
|
||||||
|
<td>'row'</td>
|
||||||
|
<td>row · column</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>grow</code></td>
|
||||||
|
<td>false</td>
|
||||||
|
<td>children get <code>flex: 1 1 0</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>attached</code></td>
|
||||||
|
<td>false</td>
|
||||||
|
<td>collapses inner radii + overlaps 1px to merge borders</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>gap</code></td>
|
||||||
|
<td>0 if <code>attached</code></td>
|
||||||
|
<td>defaults to <code>0</code> when <code>attached</code> is on</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3"><em>… plus FlexProps minus <code>wrap</code></em></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Reference</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>mantine Group</strong> — same name and intent.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>chakra-ui ButtonGroup</strong> — origin of the
|
||||||
|
<code>attached</code> behavior, generalized here.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Section, type SectionSize } from '$uix/eidos/components/section';
|
||||||
|
|
||||||
|
const sizes: SectionSize[] = ['sm', 'md', 'lg', 'xl'];
|
||||||
|
let size = $state<SectionSize>('lg');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-uix-canvas-inner>
|
||||||
|
<header>
|
||||||
|
<div data-uix-eyebrow>Layout · Section</div>
|
||||||
|
<h1 data-uix-page-title>Section</h1>
|
||||||
|
<p data-uix-page-lede>
|
||||||
|
Top-level page section with consistent block-axis padding scaled by
|
||||||
|
<code>size</code>.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Live example</h2>
|
||||||
|
<div style="display: flex; gap: 8px; margin-bottom: 12px">
|
||||||
|
{#each sizes as s (s)}
|
||||||
|
<label style="display: inline-flex; gap: 4px; align-items: center">
|
||||||
|
<input type="radio" name="size" value={s} bind:group={size} />
|
||||||
|
<span><code>{s}</code></span>
|
||||||
|
</label>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<Section
|
||||||
|
{size}
|
||||||
|
style="background: var(--color-surface-1); border: 1px dashed var(--color-border); border-radius: 8px"
|
||||||
|
>
|
||||||
|
<p style="margin: 0; text-align: center">
|
||||||
|
Section content (size: <code>{size}</code>).
|
||||||
|
</p>
|
||||||
|
</Section>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Padding scale</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Size</th>
|
||||||
|
<th>Block padding</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>sm</code></td><td>var(--space-8) = 32px</td></tr>
|
||||||
|
<tr><td><code>md</code></td><td>var(--space-12) = 48px</td></tr>
|
||||||
|
<tr><td><code>lg</code></td><td>var(--space-16) = 64px</td></tr>
|
||||||
|
<tr><td><code>xl</code></td><td>calc(var(--space-16) * 1.5) = 96px</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Props</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Prop</th>
|
||||||
|
<th>Default</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>size</code></td>
|
||||||
|
<td>'lg'</td>
|
||||||
|
<td>sm · md · lg · xl</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>paddingY</code></td>
|
||||||
|
<td>—</td>
|
||||||
|
<td>override the size-keyed default</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3"><em>… plus every <code>BoxProps</code> prop</em></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Reference</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>radix-themes Section</strong> — same intent (size-keyed block padding
|
||||||
|
for vertical rhythm).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>HTML <code><section></code></strong> — Section is a `<div>`
|
||||||
|
in batch 1; wrap with a real <code><section></code> when you need landmark semantics.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Stack } from '$uix/eidos/components/stack';
|
||||||
|
import { Box } from '$uix/eidos/components/box';
|
||||||
|
|
||||||
|
const cellStyle = 'background: var(--color-surface-2); border-radius: 4px; padding: 8px 12px';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-uix-canvas-inner>
|
||||||
|
<header>
|
||||||
|
<div data-uix-eyebrow>Layout · Stack</div>
|
||||||
|
<h1 data-uix-page-title>Stack</h1>
|
||||||
|
<p data-uix-page-lede>
|
||||||
|
Vertical (default) or horizontal stack. Thin alias over Flex with
|
||||||
|
<code>direction='column'</code>.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Vertical (default)</h2>
|
||||||
|
<Stack
|
||||||
|
gap={2}
|
||||||
|
padding={4}
|
||||||
|
style="background: var(--color-surface-1); border: 1px solid var(--color-border); border-radius: 8px"
|
||||||
|
>
|
||||||
|
<Box style={cellStyle}>Row 1</Box>
|
||||||
|
<Box style={cellStyle}>Row 2</Box>
|
||||||
|
<Box style={cellStyle}>Row 3</Box>
|
||||||
|
</Stack>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Horizontal</h2>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
gap={2}
|
||||||
|
padding={4}
|
||||||
|
align="center"
|
||||||
|
style="background: var(--color-surface-1); border: 1px solid var(--color-border); border-radius: 8px"
|
||||||
|
>
|
||||||
|
<Box style={cellStyle}>A</Box>
|
||||||
|
<Box style={cellStyle}>B</Box>
|
||||||
|
<Box style={cellStyle}>C</Box>
|
||||||
|
</Stack>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Props</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Prop</th>
|
||||||
|
<th>Default</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>direction</code></td>
|
||||||
|
<td>'column'</td>
|
||||||
|
<td>row · row-reverse · column · column-reverse</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>gap</code></td>
|
||||||
|
<td>—</td>
|
||||||
|
<td>maps to <code>var(--space-N)</code> when numeric</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>align</code> / <code>justify</code></td>
|
||||||
|
<td>—</td>
|
||||||
|
<td>inherited from Flex</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3"><em>… plus FlexProps minus <code>wrap</code>, <code>inline</code>, <code>rowGap</code>, <code>columnGap</code></em></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Reference</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>chakra-ui Stack / VStack / HStack</strong> — same intent; we collapse
|
||||||
|
the three into one component with a <code>direction</code> prop.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>radix-themes Flex direction='column'</strong> — equivalent shape; Stack
|
||||||
|
is the named shortcut.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Wrap } from '$uix/eidos/components/wrap';
|
||||||
|
|
||||||
|
const tagStyle =
|
||||||
|
'background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: 999px; padding: 4px 10px; font-size: 0.875rem';
|
||||||
|
|
||||||
|
const tags = [
|
||||||
|
'design',
|
||||||
|
'systems',
|
||||||
|
'reactive',
|
||||||
|
'svelte',
|
||||||
|
'typescript',
|
||||||
|
'css',
|
||||||
|
'tokens',
|
||||||
|
'accessibility',
|
||||||
|
'i18n',
|
||||||
|
'documentation'
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-uix-canvas-inner>
|
||||||
|
<header>
|
||||||
|
<div data-uix-eyebrow>Layout · Wrap</div>
|
||||||
|
<h1 data-uix-page-title>Wrap</h1>
|
||||||
|
<p data-uix-page-lede>
|
||||||
|
Flex row that always wraps. Use for tag clouds, chip groups and any
|
||||||
|
row-of-children that should reflow on narrow viewports.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Live example</h2>
|
||||||
|
<Wrap
|
||||||
|
gap={2}
|
||||||
|
padding={4}
|
||||||
|
style="background: var(--color-surface-1); border: 1px solid var(--color-border); border-radius: 8px; max-width: 360px"
|
||||||
|
>
|
||||||
|
{#each tags as t (t)}
|
||||||
|
<span style={tagStyle}>{t}</span>
|
||||||
|
{/each}
|
||||||
|
</Wrap>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Props</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Prop</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>gap</code> / <code>rowGap</code> / <code>columnGap</code></td>
|
||||||
|
<td>independent row/column gaps</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>align</code> / <code>justify</code></td>
|
||||||
|
<td>main- and cross-axis alignment</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><em>… plus FlexProps minus <code>direction</code> and <code>wrap</code></em></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Reference</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>mantine Group wrap</strong> — same intent; we name the wrap-locked
|
||||||
|
variant explicitly to make intent obvious at the call site.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>chakra-ui Wrap</strong> — origin of the dedicated wrap component.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
Loading…
Reference in new issue