Files
dt-design-system/packages/showcase/desktop/src/renderer/pages/AnimationsPage.tsx
michael 0cfa83259e Remove glows, refine feature legend, forms, and showcase pages
- Delete glows.css and GlowsPage (unused)
- Enhance DTFeatureLegend and feature-legend.css with improved layout
- Update DTRadioGroup styling
- Refine bevels.css, forms-dt.css
- Update AnimationsPage, CardsAdvancedPage, FormsPage, HomePage
- Add type:module to web package.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 10:03:48 -07:00

219 lines
7.8 KiB
TypeScript

import { useState, useEffect, useRef } from 'react';
import { DTCard, DTStaggerContainer } from '@dangerousthings/react';
import { Section, Row, CodeLabel } from '../components/Section';
function AnimBox({ className, label }: { className: string; label: string }) {
return (
<div style={{ textAlign: 'center' }}>
<div
className={className}
style={{
background: 'var(--color-primary)',
width: 80,
height: 80,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 700,
color: 'var(--color-bg)',
fontSize: '0.7rem',
}}
>
{label}
</div>
</div>
);
}
const VARIANTS = ['normal', 'emphasis', 'warning', 'success', 'other'] as const;
function ProgressBarDemo() {
const [progress, setProgress] = useState<number[]>([0, 0, 0, 0, 0]);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const [running, setRunning] = useState(false);
const animate = () => {
// Reset then animate up
setProgress([0, 0, 0, 0, 0]);
setRunning(true);
let tick = 0;
if (intervalRef.current) clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
tick++;
setProgress(prev =>
prev.map((_, i) => Math.min(100, Math.round(tick * (1.5 + i * 0.4))))
);
if (tick > 80) {
if (intervalRef.current) clearInterval(intervalRef.current);
setRunning(false);
}
}, 40);
};
useEffect(() => {
// Auto-run on mount
const t = setTimeout(animate, 500);
return () => {
clearTimeout(t);
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, []);
return (
<div>
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', marginBottom: 16 }}>
{VARIANTS.map((v, i) => (
<DTCard
key={v}
variant={v}
title={`${v.toUpperCase()} ${progress[i]}%`}
progress={progress[i]}
style={{ width: 160, transition: 'all 0.1s ease-out' }}
>
<div className="card-body" style={{ fontSize: '0.75rem', minHeight: 60, display: 'flex', alignItems: 'center' }}>
Progress fills the left edge from bottom to top
</div>
</DTCard>
))}
</div>
<button
className="btn-secondary"
onClick={animate}
disabled={running}
type="button"
>
{running ? 'ANIMATING...' : 'REPLAY PROGRESS'}
</button>
</div>
);
}
export function AnimationsPage() {
const [entranceKey, setEntranceKey] = useState(0);
const [staggerKey, setStaggerKey] = useState(0);
const [accordionExpanded, setAccordionExpanded] = useState(false);
return (
<>
<h1 className="page-title">Animations</h1>
<p className="page-subtitle">Entrance animations, interactive effects, stagger containers, and transition utilities.</p>
<Section title="Entrance Animations" description="One-shot animations for elements entering the viewport.">
<Row key={entranceKey}>
<AnimBox className="dt-animate-scale-in" label="SCALE IN" />
<AnimBox className="dt-animate-fade-in" label="FADE IN" />
<AnimBox className="dt-animate-slide-up" label="SLIDE UP" />
</Row>
<button className="btn-secondary" style={{ marginTop: 'var(--space-4)' }} onClick={() => setEntranceKey(k => k + 1)} type="button">
REPLAY
</button>
<CodeLabel text=".dt-animate-scale-in | .dt-animate-fade-in | .dt-animate-slide-up" />
</Section>
<Section title="Interactive Animations" description="Looping animations for active/loading states.">
<Row>
<AnimBox className="dt-animate-pulse" label="PULSE" />
<AnimBox className="dt-animate-ping" label="PING" />
<AnimBox className="dt-animate-spin" label="SPIN" />
</Row>
<CodeLabel text=".dt-animate-pulse | .dt-animate-ping | .dt-animate-spin" />
</Section>
<Section title="Stagger Container" description="Automatic staggered scale-in animation for child elements. Customizable via --dt-stagger-duration and --dt-stagger-interval.">
<DTStaggerContainer
key={staggerKey}
className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-dt-3"
>
{Array.from({ length: 12 }, (_, i) => (
<DTCard key={i} title={`CARD ${i + 1}`} style={{ textAlign: 'center' }}>
<div className="card-body" style={{ fontSize: '0.75rem' }}>Item #{i + 1}</div>
</DTCard>
))}
</DTStaggerContainer>
<button
className="btn-secondary"
style={{ marginTop: 'var(--space-4)' }}
onClick={() => setStaggerKey(k => k + 1)}
type="button"
>
REPLAY STAGGER
</button>
<CodeLabel text="<DTStaggerContainer> — nth-child delays up to 24 children" />
</Section>
<Section title="Transition Utilities" description="Accordion expand, chevron rotation, and progress bar transitions.">
<div style={{ maxWidth: 400 }}>
<button
className="dt-accent-top"
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
padding: 'var(--space-3) var(--space-4)',
background: 'var(--color-surface)',
border: '1px solid var(--color-border)',
cursor: 'pointer',
color: 'var(--color-text)',
fontWeight: 600,
textTransform: 'uppercase',
}}
aria-expanded={accordionExpanded}
onClick={() => setAccordionExpanded(!accordionExpanded)}
type="button"
>
Click to expand
<span style={{
display: 'inline-block',
transition: 'transform 250ms ease-in-out',
transform: accordionExpanded ? 'rotate(180deg)' : undefined,
}}>
&#x25BC;
</span>
</button>
<div style={{
maxHeight: accordionExpanded ? 200 : 0,
overflow: 'hidden',
transition: 'max-height 250ms ease-in-out',
}}>
<div style={{
padding: 'var(--space-4)',
background: 'var(--color-surface)',
border: '1px solid var(--color-border)',
borderTop: 'none',
}}>
This content expands with a smooth max-height transition. The chevron rotates 180 degrees.
</div>
</div>
</div>
<CodeLabel text=".dt-transition-accordion | .dt-transition-chevron | .dt-transition-progress" />
</Section>
<Section title="Card Progress Bar" description="Cards have a vertical progress bar on the left edge. Animate it by transitioning --dt-card-progress from 0 to 100.">
<ProgressBarDemo />
<CodeLabel text="<DTCard progress={value} /> — CSS ::after gradient driven by --dt-card-progress" />
</Section>
<Section title="Scrollbar Styling" description="Thin neon scrollbar scoped under [data-brand='dt'].">
<div
className="dt-scrollbar"
style={{
maxHeight: 120,
overflowY: 'auto',
background: 'var(--color-surface)',
border: '1px solid var(--color-border)',
padding: 'var(--space-3)',
}}
>
{Array.from({ length: 20 }, (_, i) => (
<div key={i} style={{ padding: '4px 0', color: 'var(--color-text)' }}>
Scrollbar line {i + 1} thin neon scrollbar styled with --color-primary
</div>
))}
</div>
<CodeLabel text=".dt-scrollbar — scrollbar-color: var(--color-primary)" />
</Section>
</>
);
}