website refactor

This commit is contained in:
2026-01-18 16:57:16 +01:00
parent b263de3a35
commit 489deb2991
6 changed files with 233 additions and 359 deletions

View File

@@ -1,6 +1,18 @@
import React, { ReactNode, ElementType } from 'react';
import { Box, BoxProps, ResponsiveValue } from './Box';
/**
* WARNING: DO NOT VIOLATE THE PURPOSE OF THIS PRIMITIVE.
*
* Stack is for flexbox-based layouts (stacking elements).
*
* - DO NOT add positioning props (absolute, top, zIndex).
* - DO NOT add grid props.
* - DO NOT add background/border props unless it's a specific styled stack.
*
* If you need a more specific layout, create a new component in apps/website/components.
*/
type Spacing = 0 | 0.5 | 1 | 1.5 | 2 | 2.5 | 3 | 3.5 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | 64 | 72 | 80 | 96;
interface ResponsiveGap {
@@ -20,7 +32,8 @@ interface ResponsiveSpacing {
'2xl'?: Spacing;
}
export interface StackProps<T extends ElementType> extends Omit<BoxProps<T>, 'children' | 'className' | 'gap'> {
export interface StackProps<T extends ElementType> {
as?: T;
children: ReactNode;
className?: string;
direction?: 'row' | 'col' | { base?: 'row' | 'col'; md?: 'row' | 'col'; lg?: 'row' | 'col' };
@@ -28,7 +41,7 @@ export interface StackProps<T extends ElementType> extends Omit<BoxProps<T>, 'ch
align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline' | ResponsiveValue<'start' | 'center' | 'end' | 'stretch' | 'baseline'>;
justify?: 'start' | 'center' | 'end' | 'between' | 'around' | ResponsiveValue<'start' | 'center' | 'end' | 'between' | 'around'>;
wrap?: boolean;
center?: boolean;
// Spacing (allowed for layout)
m?: Spacing | ResponsiveSpacing;
mt?: Spacing | ResponsiveSpacing;
mb?: Spacing | ResponsiveSpacing;
@@ -41,11 +54,21 @@ export interface StackProps<T extends ElementType> extends Omit<BoxProps<T>, 'ch
pr?: Spacing | ResponsiveSpacing;
px?: Spacing | ResponsiveSpacing;
py?: Spacing | ResponsiveSpacing;
// Sizing (allowed for layout)
w?: string | ResponsiveValue<string>;
h?: string | ResponsiveValue<string>;
minWidth?: string | ResponsiveValue<string>;
maxWidth?: string | ResponsiveValue<string>;
minHeight?: string | ResponsiveValue<string>;
maxHeight?: string | ResponsiveValue<string>;
// Basic styling (sometimes needed for containers)
rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';
// Flex item props
flex?: number | string;
flexGrow?: number;
flexShrink?: number;
alignSelf?: 'auto' | 'start' | 'end' | 'center' | 'stretch' | 'baseline';
style?: React.CSSProperties;
role?: string;
'aria-label'?: string;
'aria-live'?: 'polite' | 'assertive' | 'off';
}
export function Stack<T extends ElementType = 'div'>({
@@ -56,10 +79,14 @@ export function Stack<T extends ElementType = 'div'>({
align,
justify,
wrap = false,
center = false,
m, mt, mb, ml, mr,
p, pt, pb, pl, pr, px, py,
w, h, minWidth, maxWidth, minHeight, maxHeight,
rounded,
flex,
flexGrow,
flexShrink,
alignSelf,
as,
...props
}: StackProps<T>) {
@@ -150,16 +177,58 @@ export function Stack<T extends ElementType = 'div'>({
className
].filter(Boolean).join(' ');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { alignItems, justifyContent, ...restProps } = props as any;
const getAlignItemsClass = (value: StackProps<ElementType>['align']) => {
if (!value) return '';
const map: Record<string, string> = { start: 'items-start', center: 'items-center', end: 'items-end', stretch: 'items-stretch', baseline: 'items-baseline' };
if (typeof value === 'object') {
const classes = [];
if (value.base) classes.push(map[value.base]);
if (value.sm) classes.push(`sm:${map[value.sm]}`);
if (value.md) classes.push(`md:${map[value.md]}`);
if (value.lg) classes.push(`lg:${map[value.lg]}`);
if (value.xl) classes.push(`xl:${map[value.xl]}`);
if (value['2xl']) classes.push(`2xl:${map[value['2xl']]}`);
return classes.join(' ');
}
return map[value];
};
const getJustifyContentClass = (value: StackProps<ElementType>['justify']) => {
if (!value) return '';
const map: Record<string, string> = { start: 'justify-start', center: 'justify-center', end: 'justify-end', between: 'justify-between', around: 'justify-around' };
if (typeof value === 'object') {
const classes = [];
if (value.base) classes.push(map[value.base]);
if (value.sm) classes.push(`sm:${map[value.sm]}`);
if (value.md) classes.push(`md:${map[value.md]}`);
if (value.lg) classes.push(`lg:${map[value.lg]}`);
if (value.xl) classes.push(`xl:${map[value.xl]}`);
if (value['2xl']) classes.push(`2xl:${map[value['2xl']]}`);
return classes.join(' ');
}
return map[value];
};
const layoutClasses = [
getAlignItemsClass(align),
getJustifyContentClass(justify)
].filter(Boolean).join(' ');
return (
<Box
as={as}
className={classes}
alignItems={center ? 'center' : (align || alignItems)}
justifyContent={center ? 'center' : (justify || justifyContent)}
{...restProps}
className={`${classes} ${layoutClasses}`}
w={w}
h={h}
minWidth={minWidth}
maxWidth={maxWidth}
minHeight={minHeight}
maxHeight={maxHeight}
flex={flex}
flexGrow={flexGrow}
flexShrink={flexShrink}
alignSelf={alignSelf}
{...props}
>
{children}
</Box>