Files
dt-design-system/packages/react/src/components/DTHexagon.tsx
michael e74c285b70 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>
2026-03-06 10:18:39 -08:00

110 lines
3.0 KiB
TypeScript

/**
* DTHexagon — Decorative SVG hexagon with optional animations.
*
* Uses inline SVG (not CSS classes) — same approach as the RN version.
* Animation classes from animations.css applied to wrapper div.
*/
import type { ReactNode, CSSProperties } from 'react';
import type { DTVariant } from '@dangerousthings/tokens';
import { cx } from '../utils/cx';
import { getVariantClass } from '../utils/variantClasses';
interface DTHexagonProps {
/** Diameter in pixels */
size: number;
/** Color variant @default 'normal' */
variant?: DTVariant;
/** Filled or outline @default true */
filled?: boolean;
/** Enable animation @default false */
animated?: boolean;
/** Animation type @default 'rotate' */
animationType?: 'rotate' | 'pulse' | 'none';
/** Animation duration in ms @default 2000 */
animationDuration?: number;
/** Outline stroke width @default 2 */
borderWidth?: number;
/** Custom color override */
color?: string;
/** Opacity @default 1 */
opacity?: number;
/** Centered content inside hexagon */
children?: ReactNode;
className?: string;
style?: CSSProperties;
}
/** Flat-top hexagon points for given size */
function hexPoints(size: number): string {
const r = size / 2;
const cx = r;
const cy = r;
const points: [number, number][] = [];
for (let i = 0; i < 6; i++) {
const angle = (Math.PI / 3) * i - Math.PI / 6;
points.push([cx + r * Math.cos(angle), cy + r * Math.sin(angle)]);
}
return points.map(([x, y]) => `${x},${y}`).join(' ');
}
export function DTHexagon({
size,
variant = 'normal',
filled = true,
animated = false,
animationType = 'rotate',
animationDuration = 2000,
borderWidth = 2,
color,
opacity = 1,
children,
className,
style,
}: DTHexagonProps) {
const animClass = animated
? animationType === 'rotate'
? 'dt-animate-spin'
: animationType === 'pulse'
? 'dt-animate-pulse'
: undefined
: undefined;
return (
<div
className={cx(getVariantClass(variant), animClass, className)}
style={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: size,
height: size,
position: 'relative',
opacity,
...(animated && animationDuration !== 2000
? { animationDuration: `${animationDuration}ms` }
: {}),
...style,
}}>
<svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
style={{ position: 'absolute', top: 0, left: 0 }}>
<polygon
points={hexPoints(size - borderWidth)}
transform={`translate(${borderWidth / 2}, ${borderWidth / 2})`}
fill={filled ? (color || 'var(--dt-card-color, var(--color-primary))') : 'none'}
stroke={color || 'var(--dt-card-color, var(--color-primary))'}
strokeWidth={borderWidth}
/>
</svg>
{children && (
<div style={{ position: 'relative', zIndex: 1 }}>
{children}
</div>
)}
</div>
);
}