@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap');

.ux-box {
	margin: 0;
	font-family: Outfit, sans-serif;
}

.ux-box--padding {
	padding: var(--ux-box--padding, 1rem);
}

.ux-box--gap {
	gap: var(--ux-box--gap, .5rem);
}

.ux-box--priority-primary {
	background: var(--ux-box--primary-bg, white);
	color: var(--ux-box--primary-fg, black);
	border-width: var(--ux-box--primary-bdw, 1px);
	border-style: solid;
	border-color: var(--ux-box--primary-bdc, Gainsboro);
	border-radius: var(--ux-box--primary-bdr, .25rem);
	box-shadow: var(--ux-box-primary-bs, 0 4px 14px rgba(160,160,160,.6));
}

.ux-box--priority-secondary {
	background: var(--ux-box--secondary-bg, white);
	color: var(--ux-box--secondary-fg, black);
	border-width: var(--ux-box--secondary-bdw, 1px);
	border-style: solid;
	border-color: var(--ux-box--secondary-bdc, WhiteSmoke);
	border-radius: var(--ux-box--secondary-bdr, .25rem);
	box-shadow: var(--ux-box-secondary-bs, 0 4px 14px rgba(160,160,160,.1));
}

/* Some standard media element */
img {
	display: block;
	max-width: 400px;
	width: 100%;
	aspect-ratio: 4/3;
	text-indent: 100%;
	overflow: hidden;
}

/* Some standard text element */
:where(h2, p):empty {
	max-width: 6em;
}

[data-standby="true"]:empty,
img[src="#"] {
	/* These will need to be intents triggered by mode */
	--skeleton-bg: #ededed;
	--skeleton-shine: white;
	min-height: 1lh;
	min-width: 1em;
	
	background-color: var(--skeleton-bg);
	background-image: linear-gradient(
	    100deg,
	    transparent 40%,
	    color-mix(in srgb, var(--skeleton-shine), transparent 50%) 50%,
	    transparent 60%
	  );
	background-size: 200% 100%;
	background-position-x: 120%;
}


@keyframes loading {
	to { background-position-x: -20% }
}

@media (prefers-reduced-motion: no-preference) {
	[data-standby="true"]:empty,
	img[src="#"] {
		background-position-x: 180%;
		animation: 2s loading ease-in-out infinite;
	}
}
import React from "https://esm.sh/react";
import ReactDOM from "https://esm.sh/react-dom";
import classnames from "https://esm.sh/classnames";

const PRIORITIES = ['primary', 'secondary'];

function updateLogical(value) {
	switch(value) {
		case 'left':
		case 'top':
			return 'start';
		case 'right':
		case 'bottom':
			return 'end';
		default:
			return value;
	}
}

function createBox(TagName) {
	return function(props) {
		const {
			distribute,
			gap,
			inset,
			logical = true,
			outset,
			padding,
			priority,
			stack,
			standby,
			stretch,
			style: userStyle = {},
			wrap,
			...rest
		} = props;
		
		let innerLayout = [/* inline, block */];
		let outerLayout = [/* inline, block */];
		
		if (typeof inset === 'string') {
			innerLayout = [inset, inset];
		} else if (inset && typeof inset === 'object') {
			innerLayout = [inset.inline, inset.block];
		}
		
		if (typeof outset === 'string') {
			outerLayout = [outset, outset];
		} else if (outset && typeof outset === 'object') {
			outerLayout = [outset.inline, outset.block];
		}
		
		if (logical) {
			innerLayout.map(updateLogical);
			outerLayout.map(updateLogical);
		}
		
		if (stack) {
			innerLayout.reverse();
		}
		
		const style = {
			justifyContent: innerLayout.at(0),
			alignItems: innerLayout.at(1),
			display: stretch ? 'flex' : 'inline-flex',
			flex: stretch ? 1 : 'initial',
			flexDirection: stack ? 'column' : 'row',
			wrap: wrap ? 'wrap' : 'nowrap',
			justifySelf: outerLayout.at(0),
			alignSelf: outerLayout.at(1)
		}

	
		if (distribute) {
			style.justifyContent = `space-${distribute}`;
		}
		
		const className = classnames('ux-box', {
			'ux-box--gap': gap,
			'ux-box--padding': padding,
			[`ux-box--priority-${priority}`]: PRIORITIES.includes(priority)
		});
		
		return <TagName
				  { ...rest }
				  data-standby={ standby }
				  style={ Object.assign(userStyle, style) }
				  className={ className }/>
	}
}

const cache = new Map();
const box = new Proxy(createBox, {
	get: (_, tagName) => {
		if (!cache.has(tagName))
			cache.set(tagName, createBox(tagName));
		return cache.get(tagName);
	},
});

function Card(props) {
	const {
		description,
		image = '#',
		loading,
		title,
		...rest
	} = props;
	
	return (
		<box.div 
			padding
			gap
			stack
			stretch
			priority={ loading ? 'secondary' : 'primary' }>
			<img src={ image }/>
			<box.h2 standby={ loading }>{ title }</box.h2>
			<box.p standby={ loading }>{ description }</box.p>
		</box.div>
	);
}

function App() {	
	return (
		<box.div gap padding distribute='between'>
			<Card
				title='My Card'
				description='Lorem ipsum'
				image='https://baconmockup.com/400/300/'/>
			<Card loading/>
		</box.div>
	)
}

const root = ReactDOM.createRoot(document.body);
root.render(<App/>);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.