Add storefront component migration: cards, badges, progress bars, animations, and React wrapper package

Major feature migration from dt-shopify-storefront into the design system:

- Card system: per-card color modes (normal/emphasis/warning/success/other),
  progress bars, full-bleed card body (card-body-flush), image bevel clip-paths,
  and visible structural borders at bevel corners
- Badge system: dt-card-badge with position-aware offsets for cards vs media frames
- Media frames: dt-bevel-media with inner surface fill, image clipping, and
  "MISSING MEDIA" placeholder for empty containers
- Interactive bevel buttons: dt-btn with hover/selected states and pulse animation
- Menu items: dt-menu-item with active states and level indentation
- Animations: scale-in, fade-in, slide-up, pulse, ping, spin keyframes
- Feature legend: CSS grid for product feature icons with state colors
- Scrollbar styling for DT brand
- New @dangerousthings/react package wrapping web CSS components
- New @dangerousthings/tailwind-preset package
- New @dangerousthings/hex-background package
- Desktop showcase rewritten in React with Tailwind CSS
- Mobile showcase updated with new screens (animations, filters, advanced cards)
- Tokens: added mode color tokens, RGB variants, and selected-state tokens

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-05 13:26:03 -08:00
parent 47e8085581
commit e74c285b70
109 changed files with 7196 additions and 1026 deletions

View File

@@ -0,0 +1,108 @@
/**
* DTMenu — Hierarchical menu with beveled items.
*
* CSS reference: forms-dt.css .dt-menu-item, .active, .selected
*/
import { useState, useCallback, type ReactNode, type CSSProperties } from 'react';
import type { DTVariant } from '@dangerousthings/tokens';
import { cx } from '../utils/cx';
import { getVariantClass } from '../utils/variantClasses';
export interface DTMenuItem {
id: string;
title: string;
icon?: ReactNode;
items?: DTMenuItem[];
isActive?: boolean;
isSelected?: boolean;
}
interface DTMenuProps {
items: DTMenuItem[];
/** Color variant @default 'normal' */
variant?: DTVariant;
onItemClick?: (id: string) => void;
className?: string;
style?: CSSProperties;
}
export function DTMenu({
items,
variant = 'normal',
onItemClick,
className,
style,
}: DTMenuProps) {
return (
<div
className={cx(getVariantClass(variant), className)}
style={{ display: 'flex', flexDirection: 'column', gap: '4px', ...style }}>
{items.map(item => (
<MenuItem
key={item.id}
item={item}
level={0}
onItemClick={onItemClick}
/>
))}
</div>
);
}
function MenuItem({
item,
level,
onItemClick,
}: {
item: DTMenuItem;
level: number;
onItemClick?: (id: string) => void;
}) {
const [expanded, setExpanded] = useState(false);
const hasChildren = item.items && item.items.length > 0;
const handleClick = useCallback(() => {
if (hasChildren) {
setExpanded(prev => !prev);
}
onItemClick?.(item.id);
}, [hasChildren, item.id, onItemClick]);
return (
<>
<button
className={cx(
'dt-menu-item',
item.isActive && 'active',
item.isSelected && 'selected',
)}
style={{ '--dt-menu-level': level } as CSSProperties}
onClick={handleClick}
aria-expanded={hasChildren ? expanded : undefined}
type="button">
<span style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
{item.icon}
{item.title}
</span>
{hasChildren && (
<span
className="dt-accordion-chevron"
style={{
transition: 'transform 250ms ease-in-out',
transform: expanded ? 'rotate(180deg)' : undefined,
}}
/>
)}
</button>
{hasChildren && expanded && item.items!.map(child => (
<MenuItem
key={child.id}
item={child}
level={level + 1}
onItemClick={onItemClick}
/>
))}
</>
);
}