@dash-ui/layout
Version:
A framework-agnostic layout library written in Dash
1,104 lines (1,070 loc) • 31.4 kB
text/typescript
import compound from "@dash-ui/compound";
import type { ResponsiveStyles } from "@dash-ui/responsive";
import responsive from "@dash-ui/responsive";
import type {
DashThemes,
DashTokens,
StyleObject,
Styles,
} from "@dash-ui/styles";
/**
* Creates new layout style utilities using an existing Dash `styles` instance.
*
* @param styles - An `styles` instance from `@dash-ui/styles`
* @param mediaQueries - The media queries to use for responsive styles
*/
function layout<
Tokens extends
| DashTokens
| {
gap: any;
pad: any;
borderWidth: any;
shadow: any;
radius: any;
color: any;
zIndex: any;
},
Themes extends DashThemes,
MQ extends Record<string, string>
>(styles: Styles<Tokens, Themes>, mediaQueries?: MQ) {
const compoundStyles = compound(styles);
const responsiveStyles: ResponsiveStyles<Tokens, Themes, MQ> = responsive(
styles,
mediaQueries || {}
);
const box = compoundStyles(
{
/**
* Sets a `display` CSS property on your component
*/
display: responsiveStyles.variants({
flex: { display: "flex" },
inlineFlex: { display: "inline-flex" },
grid: { display: "grid" },
inlineGrid: { display: "inlineGrid" },
block: { display: "block" },
inlineBlock: { display: "inline-block" },
inline: { display: "inline" },
table: { display: "table" },
tableCell: { display: "table-cell" },
tableRowGroup: { display: "table-row-group" },
tableRow: { display: "table-row" },
tableColumn: { display: "table-column" },
tableColumnGroup: { display: "table-column-group" },
tableHeader: { display: "table-header" },
tableHeaderGroup: { display: "table-header-group" },
tableFooterGroup: { display: "table-footer-group" },
listItem: { display: "list-item" },
contents: { display: "contents" },
runIn: { display: "run-in" },
none: { display: "none" },
inherit: { display: "inherit" },
initial: { display: "initial" },
unset: { display: "unset" },
revert: { display: "revert" },
}),
/**
* Sets a `position` CSS property on your component
*/
position: responsiveStyles.variants({
absolute: { position: "absolute" },
relative: { position: "relative" },
fixed: { position: "fixed" },
sticky: { position: "sticky" },
static: { position: "static" },
inherit: { position: "inherit" },
initial: { position: "initial" },
unset: { position: "unset" },
revert: { position: "revert" },
}),
/**
* Sets a `width` CSS property on your component
*/
width: responsiveStyles.lazy((width: number | string) => ({ width })),
/**
* Sets a `height` CSS property on your component
*/
height: responsiveStyles.lazy((height: number | string) => ({ height })),
/**
* Sets a `max-width` CSS property on your component
*/
maxWidth: responsiveStyles.lazy((maxWidth: number | string) => ({
maxWidth,
})),
/**
* Sets a `max-height` CSS property on your component
*/
maxHeight: responsiveStyles.lazy((maxHeight: number | string) => ({
maxHeight,
})),
/**
* Sets a `max-width` CSS property on your component
*/
minWidth: responsiveStyles.lazy((minWidth: number | string) => ({
minWidth,
})),
/**
* Sets a `max-height` CSS property on your component
*/
minHeight: responsiveStyles.lazy((minHeight: number | string) => ({
minHeight,
})),
/**
* Sets a `width` and `height` CSS property on your component
*/
size: responsiveStyles.lazy((size: number | string) => ({
width: size,
height: size,
})),
/**
* Sets a `padding` CSS property on your component using the "pad"
* token in your theme
*/
pad: responsiveStyles.lazy(
(
value:
| Extract<keyof Tokens["pad"], string | number>
| Extract<keyof Tokens["pad"], string | number>[]
) =>
({ pad }): StyleObject => ({
padding: Array.isArray(value)
? value.map((k) => pad[k]).join(" ")
: pad[value],
})
),
/**
* Sets a `background-color` CSS property on your component using the "color"
* token in your theme
*/
bg: responsiveStyles.variants(
reduce(
styles.tokens.color as Record<
Extract<keyof Tokens["color"], string | number>,
string | number
>,
(
acc: Record<
Extract<keyof Tokens["color"], string | number> | "default",
StyleObject
>,
key
) => {
acc[key] = {
backgroundColor: styles.tokens.color[key],
};
return acc;
},
{}
)
),
/**
* Sets a `border-color` CSS property on your component using the "color"
* token in your theme and a `border-width` property using the "borderWidth"
* token.
*/
border: responsiveStyles.lazy(
([width, borderColor]: [
(
| Extract<keyof Tokens["borderWidth"], string | number>
| Extract<keyof Tokens["borderWidth"], string | number>[]
),
Extract<keyof Tokens["color"], string | number>
]) =>
({ borderWidth, color }): StyleObject => ({
borderWidth: Array.isArray(width)
? width.map((w) => borderWidth[w]).join(" ")
: borderWidth[width],
borderStyle: "solid",
borderColor: color[borderColor],
})
),
/**
* Sets a `box-shadow` CSS property on your component using the "shadow"
* token in your theme
*/
shadow: responsiveStyles.variants(
reduce(
styles.tokens.shadow as Record<
Extract<keyof Tokens["shadow"], string | number>,
string
>,
(
acc: Record<
Extract<keyof Tokens["shadow"], string | number> | "default",
StyleObject
>,
key
) => {
acc[key] = {
boxShadow: styles.tokens.shadow[key],
};
return acc;
},
{}
)
),
/**
* Sets a `border-radius` CSS property on your component using the "radius"
* token in your theme
*/
radius: responsiveStyles.lazy(
(
value:
| Extract<keyof Tokens["radius"], string | number>
| Extract<keyof Tokens["radius"], string | number>[]
) =>
({ radius }): StyleObject => ({
borderRadius: Array.isArray(value)
? value.map((k) => radius[k]).join(" ")
: radius[value],
})
),
/**
* Sets the top, right, bottom, left position of the element
*
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/inset
*/
inset: responsiveStyles.lazy(
(value: string | number | (string | number)[]) => {
if (Array.isArray(value)) {
return {
"@supports (inset: 10px)": {
inset: value.map(unit).join(" "),
},
"@supports not (inset: 10px)": {
top: value[0],
right: value[1] ?? value[0],
bottom: value[2] ?? value[0],
left: value[3] ?? value[1] ?? value[0],
},
};
}
return {
"@supports (inset: 10px)": {
inset: value,
},
"@supports not (inset: 10px)": {
top: value,
right: value,
bottom: value,
left: value,
},
};
}
),
/**
* Sets a `z-index` CSS property on your component
*/
z: responsiveStyles.variants(
reduce(
styles.tokens.zIndex as Record<
Extract<keyof Tokens["zIndex"], string | number>,
string | number
>,
(
acc: Record<
Extract<keyof Tokens["zIndex"], string | number> | "default",
StyleObject
>,
key
) => {
acc[key] = {
zIndex: styles.tokens.zIndex[key],
};
return acc;
},
{}
)
),
} as const,
{ atomic: true }
);
const alignItems = responsiveStyles.variants({
start: {
"@supports (align-items: start)": {
alignItems: "start",
},
"@supports not (align-items: start)": {
alignItems: "flex-start",
},
},
end: {
"@supports (align-items: end)": {
alignItems: "end",
},
"@supports not (align-items: end)": {
alignItems: "flex-end",
},
},
center: { alignItems: "center" },
baseline: { alignItems: "baseline" },
stretch: { alignItems: "stretch" },
normal: { alignItems: "normal" },
inherit: { alignItems: "inherit" },
initial: { alignItems: "initial" },
unset: { alignItems: "unset" },
revert: { alignItems: "revert" },
} as const);
const justifyItems = responsiveStyles.variants({
start: {
"@supports (justify-items: start)": {
justifyItems: "start",
},
"@supports not (justify-items: start)": {
justifyItems: "flex-start",
},
},
end: {
"@supports (justify-items: end)": {
justifyItems: "end",
},
"@supports not (justify-items: end)": {
justifyItems: "flex-end",
},
},
center: { justifyItems: "center" },
baseline: { justifyItems: "baseline" },
stretch: { justifyItems: "stretch" },
normal: { justifyItems: "normal" },
inherit: { justifyItems: "inherit" },
initial: { justifyItems: "initial" },
unset: { justifyItems: "unset" },
revert: { justifyItems: "revert" },
} as const);
const justifyContent = responsiveStyles.variants({
start: {
"@supports (justify-content: start)": {
justifyContent: "start",
},
"@supports not (justify-content: start)": {
justifyContent: "flex-start",
},
},
end: {
"@supports (justify-content: end)": {
justifyContent: "end",
},
"@supports not (justify-content: end)": {
justifyContent: "flex-end",
},
},
center: { justifyContent: "center" },
around: { justifyContent: "space-around" },
between: { justifyContent: "space-between" },
evenly: { justifyContent: "space-evenly" },
baseline: { justifyContent: "baseline" },
stretch: { justifyContent: "stretch" },
normal: { justifyContent: "normal" },
inherit: { justifyContent: "inherit" },
initial: { justifyContent: "initial" },
unset: { justifyContent: "unset" },
revert: { justifyContent: "revert" },
} as const);
const alignContent = responsiveStyles.variants({
start: {
"@supports (align-content: start)": {
alignContent: "start",
},
"@supports not (align-content: start)": {
alignContent: "flex-start",
},
},
end: {
"@supports (align-content: end)": {
alignContent: "end",
},
"@supports not (align-content: end)": {
alignContent: "flex-end",
},
},
center: { alignContent: "center" },
around: { alignContent: "space-around" },
between: { alignContent: "space-between" },
evenly: { alignContent: "space-evenly" },
baseline: { alignContent: "baseline" },
stretch: { alignContent: "stretch" },
normal: { alignContent: "normal" },
inherit: { alignContent: "inherit" },
initial: { alignContent: "initial" },
unset: { alignContent: "unset" },
revert: { alignContent: "revert" },
} as const);
const alignSelf = responsiveStyles.variants({
start: {
"@supports (align-self: start)": {
alignSelf: "start",
},
"@supports not (align-self: start)": {
alignSelf: "flex-start",
},
},
end: {
"@supports (align-self: end)": {
alignSelf: "end",
},
"@supports not (align-self: end)": {
alignSelf: "flex-end",
},
},
center: { alignSelf: "center" },
baseline: { alignSelf: "baseline" },
stretch: { alignSelf: "stretch" },
auto: { alignSelf: "auto" },
normal: { alignSelf: "normal" },
inherit: { alignSelf: "inherit" },
initial: { alignSelf: "initial" },
unset: { alignSelf: "unset" },
revert: { alignSelf: "revert" },
} as const);
const justifySelf = responsiveStyles.variants({
start: {
"@supports (justify-self: start)": {
justifySelf: "start",
},
"@supports not (justify-self: start)": {
justifySelf: "flex-start",
},
},
end: {
"@supports (justify-self: end)": {
justifySelf: "end",
},
"@supports not (justify-self: end)": {
justifySelf: "flex-end",
},
},
center: { justifySelf: "center" },
around: { justifySelf: "space-around" },
between: { justifySelf: "space-between" },
evenly: { justifySelf: "space-evenly" },
baseline: { justifySelf: "baseline" },
stretch: { justifySelf: "stretch" },
auto: { justifySelf: "auto" },
normal: { justifySelf: "normal" },
inherit: { justifySelf: "inherit" },
initial: { justifySelf: "initial" },
unset: { justifySelf: "unset" },
revert: { justifySelf: "revert" },
} as const);
const flexItem = compoundStyles(
{
/**
* Sets a `align-self` CSS property on your component
*/
align: alignSelf,
/**
* Sets a `justify-self` CSS property on your component
*/
basis: responsiveStyles.lazy((value: string | number) => ({
flexBasis: value,
})),
/**
* Sets a `justify-self` CSS property on your component
*/
distribute: justifySelf,
/**
* Sets a `flex-grow` CSS property on your component
*/
grow: responsiveStyles.lazy((value: number | boolean) => ({
flexGrow: Number(value),
})),
/**
* Sets a `order` CSS property on your component
*/
order: responsiveStyles.lazy((value: number) => ({ order: value })),
/**
* Sets a `flex-shrink` CSS property on your component
*/
shrink: responsiveStyles.lazy((value: number | boolean) => ({
flexShrink: Number(value),
})),
...box.styles,
},
{ atomic: true }
);
const inline = compoundStyles(
{
default: responsiveStyles.one({
display: "flex",
flexWrap: "wrap",
justifyContent: "flex-start",
"& > *": {
flexShrink: 0,
},
}),
/**
* Sets a vertical and horizontal gap between the child elements in the
* inline using the "gap" token in your theme
*/
gap: responsiveStyles.variants(
reduce(
styles.tokens.gap as Record<
Extract<keyof Tokens["gap"], string | number>,
string | number
>,
(
acc: Record<
Extract<keyof Tokens["gap"], string | number> | "default",
StyleObject
>,
value
) => {
const gap = styles.tokens.gap;
acc[value] = {
"@supports (display: flex) and (gap: 1em)": {
gap: gap[value],
},
"@supports not (display: flex) and (gap: 1em)": {
marginTop: `calc(-1 * ${gap[value]})!important`,
marginLeft: `calc(-1 * ${gap[value]})!important`,
"& > *": {
marginTop: `${gap[value]}!important`,
marginLeft: `${gap[value]}!important`,
},
},
};
return acc;
},
{}
)
),
/**
* Distributed alignment properties on the x-axis using `justify-content`
*/
distribute: justifyContent,
/**
* Positional alignment for its child items on the y-axis using `align-items`
*/
align: alignItems,
...box.styles,
} as const,
{ atomic: true }
);
const sharedGrid = compoundStyles(
{
default: responsiveStyles.one({ display: "grid" }),
/**
* Makes the component display as an `inline-grid` rather than `grid`
*/
inline: responsiveStyles.one({ display: "inline-grid" }),
/**
* Sets a `justify-items` CSS property on your component
*/
alignX: justifyItems,
/**
* Sets an `align-items` CSS property on your component
*/
alignY: alignItems,
/**
* Sets a `justify-content` CSS property on your component
*/
distributeX: justifyContent,
/**
* Sets an `align-content` CSS property on your component
*/
distributeY: alignContent,
/**
* Sets a horizontal and vertical gap between the child elements in the row
* using the "gap" token in your theme
*/
gap: responsiveStyles.lazy(
(
value:
| Extract<keyof Tokens["gap"], number | string>
| [
Extract<keyof Tokens["gap"], number | string>,
Extract<keyof Tokens["gap"], number | string>
]
) =>
({ gap }): StyleObject => ({
gridGap: Array.isArray(value)
? value.map((p) => gap[p]).join(" ")
: gap[value] + " " + gap[value],
gap: Array.isArray(value)
? value.map((p) => gap[p]).join(" ")
: gap[value] + " " + gap[value],
})
),
/**
* Sets a `grid-template-rows` CSS property on your component
*/
rows: responsiveStyles.lazy(
(value: number | (number | string)[]): StyleObject => {
let rows: (number | string)[];
if (Array.isArray(value)) rows = value;
// ie doesn't have repeat
else rows = [`repeat(${value},minmax(0,1fr))`];
return { gridTemplateRows: rows.map((row) => unit(row)).join(" ") };
}
),
},
{ atomic: true }
);
const grid = compoundStyles(
{
/**
* Sets a `grid-template-columns` CSS property on your component
*/
cols: responsiveStyles.lazy((value: number | (number | string)[]) => {
let columns: (number | string)[];
if (Array.isArray(value)) columns = value;
// ie doesn't have repeat
else columns = [`repeat(${value},minmax(0,1fr))`];
return {
gridTemplateColumns: columns.map((col) => unit(col)).join(" "),
};
}),
...sharedGrid.styles,
...box.styles,
} as const,
{ atomic: true }
);
const gridItem = compoundStyles(
{
/**
* Sets a `justify-self` CSS property on your component
*/
distribute: justifySelf,
/**
* Sets an `align-self` CSS property on your component
*/
align: alignSelf,
/**
* Sets a `grid-column-start` CSS property on your component
*/
colStart: responsiveStyles.lazy((gridColumnStart: number | string) => ({
gridColumnStart,
})),
/**
* Sets a `grid-column-end` CSS property on your component
*/
colEnd: responsiveStyles.lazy((gridColumnEnd: number | string) => ({
gridColumnEnd,
})),
/**
* Sets a `grid-row-start` CSS property on your component
*/
rowStart: responsiveStyles.lazy((gridRowStart: number | string) => ({
gridRowStart,
})),
/**
* Sets a `grid-row-end` CSS property on your component
*/
rowEnd: responsiveStyles.lazy((gridRowEnd: number | string) => ({
gridRowEnd,
})),
...box.styles,
} as const,
{ atomic: true }
);
const autoGrid = compoundStyles(
{
/**
* The minimum width of a grid item
*/
itemWidth: responsiveStyles.lazy((itemWidth: number | string) => ({
gridTemplateColumns: `repeat(auto-fit, minmax(${unit(
itemWidth
)}, 1fr))`,
})),
...sharedGrid.styles,
} as const,
{ atomic: true }
);
const hstack = compoundStyles(
{
default: responsiveStyles.one({
display: "flex",
flexDirection: "row",
"& > *": {
flexShrink: 0,
},
}),
/**
* Reverses the order of the child elements
*/
reversed: responsiveStyles.one({
flexDirection: "row-reverse",
}),
/**
* Sets a horizontal gap between the child elements in the hstack using the "gap"
* token in your theme
*/
gap: responsiveStyles.variants(
reduce(
styles.tokens.gap as Record<
Extract<keyof Tokens["gap"], string | number>,
string | number
>,
(
acc: Record<
Extract<keyof Tokens["gap"], string | number> | "default",
StyleObject
>,
value
) => {
const gap = styles.tokens.gap;
const margin = {
"& > * + *": {
marginLeft: `${gap[value]}!important`,
},
};
acc[value] = ("" + value).startsWith("-")
? margin
: {
"@supports (display: flex) and (gap: 1em)": {
gap: gap[value],
},
"@supports not (display: flex) and (gap: 1em)": margin,
};
return acc;
},
{}
)
),
/**
* Distributed alignment properties on the y-axis using `justify-content`
*/
distribute: justifyContent,
/**
* Positional alignment for its child items on the x-axis using `align-items`
*/
align: alignItems,
...box.styles,
} as const,
{ atomic: true }
);
const vstack = compoundStyles(
{
default: responsiveStyles.one({
display: "flex",
flexDirection: "column",
"& > *": {
flexShrink: 0,
},
}),
/**
* Reverses the order of the child elements
*/
reversed: responsiveStyles.one({
flexDirection: "column-reverse",
}),
/**
* Sets a vertical gap between the child elements in the vstack using the "gap"
* token in your theme
*/
gap: responsiveStyles.variants(
reduce(
styles.tokens.gap as Record<
Extract<keyof Tokens["gap"], string | number>,
string | number
>,
(
acc: Record<
Extract<keyof Tokens["gap"], string | number> | "default",
StyleObject
>,
value
) => {
const gap = styles.tokens.gap;
const margin = {
"& > * + *": {
marginTop: `${gap[value]}!important`,
},
};
acc[value] = (value + "").startsWith("-")
? margin
: {
"@supports (display: flex) and (gap: 1em)": {
gap: gap[value],
},
"@supports not (display: flex) and (gap: 1em)": margin,
};
return acc;
},
{}
)
),
/**
* Distributed alignment properties on the y-axis using `justify-content`
*/
distribute: justifyContent,
/**
* Positional alignment for its child items on the y-axis using `align-items`
*/
align: alignItems,
...box.styles,
} as const,
{ atomic: true }
);
const zstack = compoundStyles(
{
default: responsiveStyles.one({
display: "grid",
"> *": {
gridArea: "1/1/1/1",
},
}),
inline: grid.styles.inline,
alignX: justifyItems,
alignY: alignItems,
distributeX: justifyContent,
distributeY: alignContent,
center: responsiveStyles.one({
alignItems: "center",
justifyItems: "center",
}),
...box.styles,
} as const,
{ atomic: true }
);
const overlay = compoundStyles(
{
default: responsiveStyles.one({ position: "absolute" }),
/**
* Sets a `margin` between the edges of the overlay item's container
*/
offset: responsiveStyles.lazy((margin: number | string) => ({ margin })),
/**
* Sets the placement of your overlay item relative to its container
*/
placement: responsiveStyles.variants({
top: { top: 0, left: "50%", transform: "translateX(-50%)" },
right: { right: 0, top: "50%", transform: "translateY(-50%)" },
bottom: { bottom: 0, left: "50%", transform: "translateX(-50%)" },
left: { left: 0, top: "50%", transform: "translateY(-50%)" },
center: {
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
},
topRight: { top: 0, right: 0 },
topLeft: { top: 0, left: 0 },
bottomRight: { bottom: 0, right: 0 },
bottomLeft: { bottom: 0, left: 0 },
}),
...box.styles,
} as const,
{ atomic: true }
);
const bleed = compoundStyles(
{
/**
* Sets a negative margin on itself using the "pad" token in your theme
*/
amount: responsiveStyles.lazy(
(
value:
| Extract<keyof Tokens["pad"], string | number>
| Extract<keyof Tokens["pad"], string | number>[]
) =>
({ pad }): StyleObject => ({
margin: Array.isArray(value)
? value
.map((k) =>
String(k).startsWith("-")
? `${pad[k]}`
: `calc(-1 * ${pad[k]})`
)
.join(" ") + "!important"
: String(value).startsWith("-")
? `${pad[value]}!important`
: `calc(-1 * ${pad[value]})!important`,
})
),
...box.styles,
} as const,
{ atomic: true }
);
return {
/**
* Sets a `align-items` CSS property on your component
*/
alignItems,
/**
* Sets a `justify-items` CSS property on your component
*/
justifyItems,
/**
* Sets a `justify-content` CSS property on your component
*/
justifyContent,
/**
* Sets a `align-content` CSS property on your component
*/
alignContent,
/**
* Sets a `align-self` CSS property on your component
*/
alignSelf,
/**
* Sets a `justify-self` CSS property on your component
*/
justifySelf,
/**
* A layout style for adding size, padding, position, color, and more
* using tokens from your CSS variable theme.
*
* @example
* <div className={box({size: 300, bg: {sm: 'red', md: 'blue'}})} />
*/
box,
/**
* Arranges child nodes horizontally, wrapping to multiple lines if needed,
* with equal spacing between items.
*
* If there is only a single child node, no space will be rendered.
*
* ```
* ☐☐☐☐☐
* ☐☐☐☐☐☐
* ☐☐☐☐☐
* ☐☐☐
* ```
*
* Some use cases include input chips and tags.
*
* @example
* <div className={inline({gap: 'sm})}>
* <Item/>
* <Item/>
* </div>
*/
inline,
/**
* A layout style that can add positioning properties to itself inside
* of a flex container.
*
* @example
* <div className={flexItem({alignSelf: 'center', order: 2})}/>
*/
flexItem,
/**
* A layout style that distributes its children in a grid like so:
*
* ```
* ☐ ☐ ☐
* ☐ ☐ ☐
* ☐ ☐ ☐
* ```
*
* @example
* <div className={grid({rows: 2, cols: 2})}>
* <GridItem/>
* <GridItem/>
* <GridItem/>
* <GridItem/>
* </div>
*/
grid,
/**
* A layout style that can add positioning properties to itself inside
* of a `<Grid>` component.
*
* @example
* <div className={grid({cols: 2, rows: 2})}>
* // Occupies 2 columns
* <div className={gridItem({colStart: 1, colEnd: 2})} />
* <div/>
* <div/>
* </div>
*/
gridItem,
/**
* A grid that automatically chooses a number of columns based on a preferred
* minimum grid item width. The items will grow to fit snugly within the grid
* container, like with flex wrapping, but no item will grow larger than the
* column size, unlike flex wrapping.
*
* ☐ ☐ ☐
* ☐ ☐ ☐
* ☐ ☐
*/
autoGrid,
/**
* A layout style that distributes its items in a row without wrapping
* like so:
*
* ```
* ☐ ☐ ☐ ☐ ☐ ☐ ☐
* ```
*
* @example
* <div className={hstack({gap: 'sm'})}>
* <Item/>
* <Item/>
* </div>
*/
hstack,
/**
* A layout style that distributes its items in a column without wrapping
* like so:
*
* ```
* ☐
* ☐
* ☐
* ☐
* ```
*
* @example
* <div className={vstack({gap: 'sm'})}>
* <Item/>
* <Item/>
* </div>
*/
vstack,
/**
* A layout style that stacks its items on top of one another, like so:
*
* ```
* _____
* | ☐ |
* _____
* ```
*
* @example
* <div className={zstack({center: true})} />
*/
zstack,
/**
* A layout style than positions itself absolutely inside of its
* container in whichever placement you decide.
*
* @example
* <div className={overlay({placement: 'bottomRight', offset: 24})} />
*/
overlay,
/**
* A layout style that sets a negative left/top margin on itself using
* the "pad" token in your theme. This is useful for increasing the
* tap area of a component while maintaining the desired visual padding.
*
* @example
* <div className={bleed({amount: 'sm', pad: 'lg'})}>
* <Item/>
* <Item/>
* </div>
*/
bleed,
} as const;
}
export default layout;
const keys: <T>(obj: T) => (keyof T)[] = Object.keys;
function reduce<T, U>(
obj: T,
fn: (acc: U, key: keyof T, currentIndex: number, arr: (keyof T)[]) => U,
init: Partial<U>
): U {
return keys(obj).reduce(fn, init as U);
}
/**
* Adds a `px` unit to numeric values and returns non-numeric values as
* they are.
*
* @param value - The value you want to maybe add a unit to
*/
function unit<T extends number | string>(value: T): T | string {
return !isNaN(value as any) && value !== 0 ? `${value}px` : value;
}