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