import React, { forwardRef, ForwardedRef, ElementType, ComponentPropsWithoutRef } from 'react'; /** * WARNING: DO NOT VIOLATE THE PURPOSE OF THIS PRIMITIVE. * * Box is a basic container primitive for spacing, sizing and basic styling. * * - DO NOT add layout props (flex, grid, gap) - use Stack or Grid instead. * - DO NOT add positioning props (absolute, top, zIndex) - create a specific component. * - DO NOT add animation props - create a specific component. * * If you need more complex behavior, create a specific 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 ResponsiveSpacing { base?: Spacing; sm?: Spacing; md?: Spacing; lg?: Spacing; xl?: Spacing; } export type ResponsiveValue = { base?: T; sm?: T; md?: T; lg?: T; xl?: T; '2xl'?: T; }; export interface BoxProps { as?: T; children?: React.ReactNode; className?: string; // Spacing m?: Spacing | ResponsiveSpacing; mt?: Spacing | ResponsiveSpacing; mb?: Spacing | ResponsiveSpacing; ml?: Spacing | 'auto' | ResponsiveSpacing; mr?: Spacing | 'auto' | ResponsiveSpacing; mx?: Spacing | 'auto' | ResponsiveSpacing; my?: Spacing | ResponsiveSpacing; p?: Spacing | ResponsiveSpacing; pt?: Spacing | ResponsiveSpacing; pb?: Spacing | ResponsiveSpacing; pl?: Spacing | ResponsiveSpacing; pr?: Spacing | ResponsiveSpacing; px?: Spacing | ResponsiveSpacing; py?: Spacing | ResponsiveSpacing; // Sizing w?: string | number | ResponsiveValue; h?: string | number | ResponsiveValue; width?: string | number; height?: string | number; maxWidth?: string | ResponsiveValue; minWidth?: string | ResponsiveValue; maxHeight?: string | ResponsiveValue; minHeight?: string | ResponsiveValue; fullWidth?: boolean; fullHeight?: boolean; aspectRatio?: string; // Display display?: 'block' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none' | string | ResponsiveValue<'block' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none' | string>; center?: boolean; overflow?: 'auto' | 'hidden' | 'visible' | 'scroll' | string; overflowX?: 'auto' | 'hidden' | 'visible' | 'scroll'; overflowY?: 'auto' | 'hidden' | 'visible' | 'scroll'; textAlign?: 'left' | 'center' | 'right' | 'justify' | string; visibility?: 'visible' | 'hidden' | 'collapse'; // Positioning position?: 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'; top?: string | number | ResponsiveValue; right?: string | number | ResponsiveValue; bottom?: string | number | ResponsiveValue; left?: string | number | ResponsiveValue; inset?: string | number; insetY?: string | number; insetX?: string | number; zIndex?: number; // Basic Styling rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full' | string | boolean; border?: boolean | string; borderTop?: boolean | string; borderBottom?: boolean | string; borderLeft?: boolean | string; borderRight?: boolean | string; borderWidth?: string | number; borderStyle?: 'solid' | 'dashed' | 'dotted' | 'none' | string; borderColor?: string; borderOpacity?: number; bg?: string; backgroundColor?: string; backgroundImage?: string; backgroundSize?: string; backgroundPosition?: string; bgOpacity?: number; color?: string; shadow?: string; opacity?: number; blur?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | string; pointerEvents?: 'auto' | 'none' | string; // Flex/Grid Item props flex?: number | string; flexShrink?: number; flexGrow?: number; flexDirection?: 'row' | 'row-reverse' | 'col' | 'col-reverse' | string | ResponsiveValue; flexWrap?: 'wrap' | 'nowrap' | 'wrap-reverse' | string; alignItems?: 'start' | 'center' | 'end' | 'stretch' | 'baseline' | string | ResponsiveValue; justifyContent?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly' | string | ResponsiveValue; alignSelf?: 'auto' | 'start' | 'end' | 'center' | 'stretch' | 'baseline'; gap?: number | string | ResponsiveValue; gridCols?: number | ResponsiveValue; responsiveGridCols?: number | ResponsiveValue; colSpan?: number | ResponsiveValue; responsiveColSpan?: number | ResponsiveValue; order?: number | string | ResponsiveValue; // Transform transform?: string | boolean; translate?: string; translateX?: string; translateY?: string; // Animation (Framer Motion support) initial?: any; animate?: any; exit?: any; transition?: any; variants?: any; whileHover?: any; whileTap?: any; onHoverStart?: any; onHoverEnd?: any; whileInView?: any; viewport?: any; custom?: any; // Interaction group?: boolean; groupHoverTextColor?: string; groupHoverScale?: boolean; groupHoverOpacity?: number; groupHoverBorderColor?: string; hoverBorderColor?: string; hoverBg?: string; hoverTextColor?: string; hoverScale?: boolean | number; clickable?: boolean; // Events onMouseEnter?: React.MouseEventHandler; onMouseLeave?: React.MouseEventHandler; onClick?: React.MouseEventHandler; onMouseDown?: React.MouseEventHandler; onMouseUp?: React.MouseEventHandler; onMouseMove?: React.MouseEventHandler; onKeyDown?: React.KeyboardEventHandler; onBlur?: React.FocusEventHandler; onSubmit?: React.FormEventHandler; onScroll?: React.UIEventHandler; style?: React.CSSProperties; id?: string; role?: React.AriaRole; tabIndex?: number; // Other type?: 'button' | 'submit' | 'reset' | string; disabled?: boolean; cursor?: string; fontSize?: string | ResponsiveValue; weight?: string; fontWeight?: string | number; letterSpacing?: string; lineHeight?: string | number; font?: string; ring?: string; hideScrollbar?: boolean; truncate?: boolean; src?: string; alt?: string; draggable?: boolean; min?: string | number; max?: string | number; step?: string | number; value?: string | number; onChange?: React.ChangeEventHandler; placeholder?: string; title?: string; padding?: Spacing | ResponsiveSpacing; paddingLeft?: Spacing | ResponsiveSpacing; paddingRight?: Spacing | ResponsiveSpacing; paddingTop?: Spacing | ResponsiveSpacing; paddingBottom?: Spacing | ResponsiveSpacing; size?: string | number | ResponsiveValue; accept?: string; autoPlay?: boolean; loop?: boolean; muted?: boolean; playsInline?: boolean; objectFit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'; } export const Box = forwardRef(( { as, children, className = '', m, mt, mb, ml, mr, mx, my, p, pt, pb, pl, pr, px, py, w, h, width, height, maxWidth, minWidth, maxHeight, minHeight, fullWidth, fullHeight, aspectRatio, display, center, overflow, overflowX, overflowY, textAlign, visibility, position, top, right, bottom, left, inset, insetY, insetX, zIndex, rounded, border, borderTop, borderBottom, borderLeft, borderRight, borderWidth, borderStyle, borderColor, borderOpacity, bg, backgroundColor, backgroundImage, backgroundSize, backgroundPosition, bgOpacity, color, shadow, opacity, blur, pointerEvents, flex, flexShrink, flexGrow, flexDirection, flexWrap, alignItems, justifyContent, alignSelf, gap, gridCols, responsiveGridCols, colSpan, responsiveColSpan, order, transform, translate, translateX, translateY, initial, animate, exit, transition, variants, whileHover, whileTap, onHoverStart, onHoverEnd, whileInView, viewport, custom, group, groupHoverTextColor, groupHoverScale, groupHoverOpacity, groupHoverBorderColor, hoverBorderColor, hoverBg, hoverTextColor, hoverScale, clickable, onMouseEnter, onMouseLeave, onClick, onMouseDown, onMouseUp, onMouseMove, onKeyDown, onBlur, onSubmit, onScroll, style: styleProp, id, role, tabIndex, type, disabled, cursor, fontSize, weight, fontWeight, letterSpacing, lineHeight, font, ring, hideScrollbar, truncate, src, alt, draggable, min, max, step, value, onChange, placeholder, title, padding, paddingLeft, paddingRight, paddingTop, paddingBottom, size, accept, autoPlay, loop, muted, playsInline, objectFit, ...props }: BoxProps & ComponentPropsWithoutRef, ref: ForwardedRef ) => { const Tag = (as as ElementType) || 'div'; const spacingMap: Record = { 0: '0', 0.5: '0.5', 1: '1', 1.5: '1.5', 2: '2', 2.5: '2.5', 3: '3', 3.5: '3.5', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12', 14: '14', 16: '16', 20: '20', 24: '24', 28: '28', 32: '32', 36: '36', 40: '40', 44: '44', 48: '48', 52: '52', 56: '56', 60: '60', 64: '64', 72: '72', 80: '80', 96: '96', 'auto': 'auto' }; const getSpacingClass = (prefix: string, value: Spacing | 'auto' | ResponsiveSpacing | undefined) => { if (value === undefined) return ''; if (typeof value === 'object') { const classes = []; if (value.base !== undefined) classes.push(`${prefix}-${spacingMap[value.base]}`); if (value.sm !== undefined) classes.push(`sm:${prefix}-${spacingMap[value.sm]}`); if (value.md !== undefined) classes.push(`md:${prefix}-${spacingMap[value.md]}`); if (value.lg !== undefined) classes.push(`lg:${prefix}-${spacingMap[value.lg]}`); if (value.xl !== undefined) classes.push(`xl:${prefix}-${spacingMap[value.xl]}`); return classes.join(' '); } return `${prefix}-${spacingMap[value]}`; }; const getResponsiveClasses = (prefix: string, value: any | ResponsiveValue | undefined) => { if (value === undefined) return ''; if (typeof value === 'object') { const classes = []; if (value.base !== undefined) classes.push(prefix ? `${prefix}-${value.base}` : String(value.base)); if (value.sm !== undefined) classes.push(prefix ? `sm:${prefix}-${value.sm}` : `sm:${value.sm}`); if (value.md !== undefined) classes.push(prefix ? `md:${prefix}-${value.md}` : `md:${value.md}`); if (value.lg !== undefined) classes.push(prefix ? `lg:${prefix}-${value.lg}` : `lg:${value.lg}`); if (value.xl !== undefined) classes.push(prefix ? `xl:${prefix}-${value.xl}` : `xl:${value.xl}`); if (value['2xl'] !== undefined) classes.push(prefix ? `2xl:${prefix}-${value['2xl']}` : `2xl:${value['2xl']}`); return classes.join(' '); } return prefix ? `${prefix}-${value}` : String(value); }; const classes = [ getSpacingClass('m', m), getSpacingClass('mt', mt), getSpacingClass('mb', mb), getSpacingClass('ml', ml), getSpacingClass('mr', mr), getSpacingClass('mx', mx), getSpacingClass('my', my), getSpacingClass('p', p || padding), getSpacingClass('pt', pt || paddingTop), getSpacingClass('pb', pb || paddingBottom), getSpacingClass('pl', pl || paddingLeft), getSpacingClass('pr', pr || paddingRight), getSpacingClass('px', px), getSpacingClass('py', py), fullWidth ? 'w-full' : getResponsiveClasses('w', w), fullHeight ? 'h-full' : getResponsiveClasses('h', h), getResponsiveClasses('max-w', maxWidth), getResponsiveClasses('min-w', minWidth), getResponsiveClasses('max-h', maxHeight), getResponsiveClasses('min-h', minHeight), getResponsiveClasses('', display), center ? 'flex items-center justify-center' : '', overflow ? (overflow.includes(':') ? overflow : `overflow-${overflow}`) : '', overflowX ? `overflow-x-${overflowX}` : '', overflowY ? `overflow-y-${overflowY}` : '', textAlign ? `text-${textAlign}` : '', visibility ? visibility : '', position ? position : '', getResponsiveClasses('top', top), getResponsiveClasses('right', right), getResponsiveClasses('bottom', bottom), getResponsiveClasses('left', left), inset !== undefined ? `inset-${inset}` : '', insetY !== undefined ? `inset-y-${insetY}` : '', insetX !== undefined ? `inset-x-${insetX}` : '', zIndex !== undefined ? `z-${zIndex}` : '', rounded === true ? 'rounded' : (rounded === false ? 'rounded-none' : (typeof rounded === 'string' ? (rounded.includes('-') ? rounded : `rounded-${rounded}`) : '')), border === true ? 'border' : (typeof border === 'string' ? (border === 'none' ? 'border-none' : border) : ''), borderTop === true ? 'border-t' : (typeof borderTop === 'string' ? borderTop : ''), borderBottom === true ? 'border-b' : (typeof borderBottom === 'string' ? borderBottom : ''), borderLeft === true ? 'border-l' : (typeof borderLeft === 'string' ? borderLeft : ''), borderRight === true ? 'border-r' : (typeof borderRight === 'string' ? borderRight : ''), borderStyle ? `border-${borderStyle}` : '', borderColor ? borderColor : '', borderOpacity !== undefined ? `border-opacity-${borderOpacity * 100}` : '', bg ? bg : '', backgroundColor ? backgroundColor : '', bgOpacity !== undefined ? `bg-opacity-${bgOpacity * 100}` : '', color ? color : '', shadow ? shadow : '', opacity !== undefined ? `opacity-${opacity * 100}` : '', blur ? (blur === 'none' ? 'blur-none' : `blur-${blur}`) : '', pointerEvents ? `pointer-events-${pointerEvents}` : '', flex !== undefined ? `flex-${flex}` : '', flexShrink !== undefined ? `flex-shrink-${flexShrink}` : '', flexGrow !== undefined ? `flex-grow-${flexGrow}` : '', getResponsiveClasses('flex', flexDirection), flexWrap ? `flex-${flexWrap}` : '', getResponsiveClasses('items', alignItems), getResponsiveClasses('justify', justifyContent), alignSelf !== undefined ? `self-${alignSelf}` : '', getResponsiveClasses('gap', gap), getResponsiveClasses('grid-cols', gridCols || responsiveGridCols), getResponsiveClasses('col-span', colSpan || responsiveColSpan), getResponsiveClasses('order', order), getResponsiveClasses('text', fontSize), group ? 'group' : '', groupHoverTextColor ? `group-hover:text-${groupHoverTextColor}` : '', groupHoverScale ? 'group-hover:scale-105 transition-transform' : '', groupHoverOpacity !== undefined ? `group-hover:opacity-${groupHoverOpacity * 100}` : '', groupHoverBorderColor ? `group-hover:border-${groupHoverBorderColor}` : '', hoverBorderColor ? `hover:border-${hoverBorderColor}` : '', hoverBg ? `hover:bg-${hoverBg}` : '', hoverTextColor ? `hover:text-${hoverTextColor}` : '', hoverScale === true ? 'hover:scale-105 transition-transform' : (typeof hoverScale === 'number' ? `hover:scale-${hoverScale} transition-transform` : ''), clickable ? 'cursor-pointer active:opacity-80 transition-all' : '', ring ? `ring-${ring}` : '', hideScrollbar ? 'scrollbar-hide' : '', truncate ? 'truncate' : '', transform === true ? 'transform' : (transform === false ? 'transform-none' : ''), className ].filter(Boolean).join(' '); const style: React.CSSProperties = { ...(typeof width === 'string' || typeof width === 'number' ? { width } : {}), ...(typeof height === 'string' || typeof height === 'number' ? { height } : {}), ...(typeof maxWidth === 'string' ? { maxWidth } : {}), ...(typeof minWidth === 'string' ? { minWidth } : {}), ...(typeof maxHeight === 'string' ? { maxHeight } : {}), ...(typeof minHeight === 'string' ? { minHeight } : {}), ...(aspectRatio ? { aspectRatio } : {}), ...(typeof top === 'string' || typeof top === 'number' ? { top } : {}), ...(typeof right === 'string' || typeof right === 'number' ? { right } : {}), ...(typeof bottom === 'string' || typeof bottom === 'number' ? { bottom } : {}), ...(typeof left === 'string' || typeof left === 'number' ? { left } : {}), ...(borderWidth !== undefined ? { borderWidth } : {}), ...(typeof transform === 'string' ? { transform } : {}), ...(translate ? { translate } : {}), ...(translateX ? { transform: `translateX(${translateX})` } : {}), ...(translateY ? { transform: `translateY(${translateY})` } : {}), ...(cursor ? { cursor } : {}), ...(fontSize && typeof fontSize === 'string' && !fontSize.includes(':') ? { fontSize } : {}), ...(weight ? { fontWeight: weight } : {}), ...(fontWeight ? { fontWeight } : {}), ...(letterSpacing ? { letterSpacing } : {}), ...(lineHeight ? { lineHeight } : {}), ...(font ? { fontFamily: font } : {}), ...(typeof size === 'string' || typeof size === 'number' ? { width: size, height: size } : {}), ...(backgroundImage ? { backgroundImage } : {}), ...(backgroundSize ? { backgroundSize } : {}), ...(backgroundPosition ? { backgroundPosition } : {}), ...(objectFit ? { objectFit } : {}), ...(styleProp || {}) }; return ( } className={classes} onClick={onClick} onMouseDown={onMouseDown} onMouseUp={onMouseUp} onMouseMove={onMouseMove} onKeyDown={onKeyDown} onBlur={onBlur} onSubmit={onSubmit} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} onScroll={onScroll} style={style} id={id} role={role} tabIndex={tabIndex} type={type} disabled={disabled} src={src} alt={alt} draggable={draggable} min={min} max={max} step={step} value={value} onChange={onChange} placeholder={placeholder} title={title} autoPlay={autoPlay} loop={loop} muted={muted} playsInline={playsInline} {...props} > {children} ); }); Box.displayName = 'Box';