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

@@ -46,3 +46,16 @@ export const dropdownItems = [
{ id: 'd3', title: 'Archive', onPress: () => {} },
{ id: 'd4', title: 'Delete', onPress: () => {} },
];
export const featureLegendItems = [
{ key: 'payment', name: 'Payment', icon: null, state: 'supported' as const },
{ key: 'access', name: 'Access', icon: null, state: 'supported' as const },
{ key: 'clone', name: 'Clone', icon: null, state: 'disabled' as const },
{ key: 'crypto', name: 'Crypto', icon: null, state: 'unsupported' as const },
{ key: 'sensing', name: 'Sensing', icon: null, state: 'supported' as const },
{ key: 'temp', name: 'Temp', icon: null, state: 'supported' as const },
{ key: 'fitness', name: 'Fitness', icon: null, state: 'disabled' as const },
{ key: 'explore', name: 'Explore', icon: null, state: 'supported' as const },
{ key: 'vibration', name: 'Vibration', icon: null, state: 'unsupported' as const },
{ key: 'sharing', name: 'Sharing', icon: null, state: 'supported' as const },
];

View File

@@ -10,6 +10,9 @@ import { FormsScreen } from '../screens/FormsScreen';
import { FeedbackScreen } from '../screens/FeedbackScreen';
import { OverlaysScreen } from '../screens/OverlaysScreen';
import { ThemeScreen } from '../screens/ThemeScreen';
import { AnimationsScreen } from '../screens/AnimationsScreen';
import { CardsAdvancedScreen } from '../screens/CardsAdvancedScreen';
import { FiltersScreen } from '../screens/FiltersScreen';
const Stack = createNativeStackNavigator<RootStackParamList>();
@@ -42,6 +45,11 @@ export function RootNavigator() {
component={CardsScreen}
options={{ title: 'CARDS & LABELS' }}
/>
<Stack.Screen
name="CardsAdvanced"
component={CardsAdvancedScreen}
options={{ title: 'ADVANCED CARDS' }}
/>
<Stack.Screen
name="Forms"
component={FormsScreen}
@@ -57,6 +65,16 @@ export function RootNavigator() {
component={OverlaysScreen}
options={{ title: 'OVERLAYS & NAV' }}
/>
<Stack.Screen
name="Animations"
component={AnimationsScreen}
options={{ title: 'ANIMATIONS' }}
/>
<Stack.Screen
name="Filters"
component={FiltersScreen}
options={{ title: 'FILTERS & FEATURES' }}
/>
<Stack.Screen
name="Theme"
component={ThemeScreen}

View File

@@ -2,8 +2,11 @@ export type RootStackParamList = {
Home: undefined;
Buttons: undefined;
Cards: undefined;
CardsAdvanced: undefined;
Forms: undefined;
Feedback: undefined;
Overlays: undefined;
Animations: undefined;
Filters: undefined;
Theme: undefined;
};

View File

@@ -0,0 +1,90 @@
import React from 'react';
import { View } from 'react-native';
import { Text } from 'react-native-paper';
import {
DTCard,
DTLabel,
DTStaggerContainer,
useDTTheme,
useScaleIn,
usePulse,
} from '@dangerousthings/react-native';
import type { DTVariant } from '@dangerousthings/react-native';
import { ScreenContainer } from '../components/ScreenContainer';
import { DemoSection } from '../components/DemoSection';
import { CodeLabel } from '../components/CodeLabel';
import { Animated } from 'react-native';
export function AnimationsScreen() {
const theme = useDTTheme();
const scaleAnim = useScaleIn({ duration: 600 });
const pulseAnim = usePulse(true);
const modes: DTVariant[] = ['normal', 'emphasis', 'warning', 'success', 'other'];
return (
<ScreenContainer>
{/* Stagger Container */}
<DemoSection
title="DTStaggerContainer"
variant="normal"
description="Staggered scale-in entrance animation for child elements."
>
<DTStaggerContainer duration={330} interval={75}>
{modes.map((mode) => (
<DTCard key={mode} mode={mode} title={mode.toUpperCase()} style={{ marginBottom: 12 }}>
<Text variant="bodySmall" style={{ color: theme.colors.onSurface }}>
Staggered entrance with scale animation
</Text>
</DTCard>
))}
</DTStaggerContainer>
<CodeLabel text="<DTStaggerContainer duration={330} interval={75}>" />
</DemoSection>
{/* Stagger with Labels */}
<DemoSection
title="Staggered Labels"
variant="emphasis"
description="Labels with staggered entrance animation."
>
<DTStaggerContainer duration={400} interval={100}>
{modes.map((mode) => (
<View key={mode} style={{ marginBottom: 8 }}>
<DTLabel primaryText={mode.toUpperCase()} mode={mode} />
</View>
))}
</DTStaggerContainer>
<CodeLabel text="DTStaggerContainer > DTLabel — customizable timing" />
</DemoSection>
{/* Scale-In Hook */}
<DemoSection
title="useScaleIn"
variant="success"
description="Scale 0→1 entrance animation hook for individual elements."
>
<Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
<DTCard mode="success" title="SCALE IN">
<Text variant="bodySmall" style={{ color: theme.colors.onSurface }}>
This card used useScaleIn(&#123; duration: 600 &#125;)
</Text>
</DTCard>
</Animated.View>
<CodeLabel text="const scale = useScaleIn({ duration: 600 })" />
</DemoSection>
{/* Pulse Hook */}
<DemoSection
title="usePulse"
variant="warning"
description="Looping opacity pulse animation for active/loading states."
>
<Animated.View style={{ opacity: pulseAnim }}>
<DTLabel primaryText="PULSING LABEL" mode="warning" />
</Animated.View>
<CodeLabel text="const opacity = usePulse(true)" />
</DemoSection>
</ScreenContainer>
);
}

View File

@@ -0,0 +1,111 @@
import React from 'react';
import { View } from 'react-native';
import { Text } from 'react-native-paper';
import {
DTCard,
DTChip,
DTBadgeOverlay,
DTStaggerContainer,
useDTTheme,
} from '@dangerousthings/react-native';
import type { DTVariant } from '@dangerousthings/react-native';
import { ScreenContainer } from '../components/ScreenContainer';
import { DemoSection } from '../components/DemoSection';
import { CodeLabel } from '../components/CodeLabel';
export function CardsAdvancedScreen() {
const theme = useDTTheme();
const modes: DTVariant[] = ['normal', 'emphasis', 'warning', 'success', 'other'];
return (
<ScreenContainer>
{/* Progress Bar */}
<DemoSection
title="Progress Bar"
variant="normal"
description="Vertical left-edge progress indicator (01)."
>
{[0, 0.25, 0.5, 0.75, 1].map((val, i) => (
<DTCard
key={val}
mode={modes[i]}
title={`${Math.round(val * 100)}% PROGRESS`}
progress={val}
style={{ marginBottom: 12 }}
>
<Text variant="bodySmall" style={{ color: theme.colors.onSurface }}>
progress=&#123;{val}&#125;
</Text>
</DTCard>
))}
<CodeLabel text="<DTCard progress={0.5}> — width matches bevelSizeSmall" />
</DemoSection>
{/* Card Badges */}
<DemoSection
title="Card Badges"
variant="other"
description="Bottom-right chip badges. Color matches badge category, not card mode."
>
<DTCard mode="normal" title="PRODUCT CARD" style={{ marginBottom: 16 }}>
<View style={{ minHeight: 40 }}>
<Text variant="bodySmall" style={{ color: theme.colors.onSurface }}>
Badge color independent of card mode
</Text>
</View>
<DTBadgeOverlay position="bottom-right">
<DTChip variant="warning">LAB</DTChip>
</DTBadgeOverlay>
</DTCard>
<DTCard mode="emphasis" title="PRODUCT CARD" style={{ marginBottom: 16 }}>
<View style={{ minHeight: 40 }}>
<Text variant="bodySmall" style={{ color: theme.colors.onSurface }}>
Badge color independent of card mode
</Text>
</View>
<DTBadgeOverlay position="bottom-right">
<DTChip variant="other">BUNDLE</DTChip>
</DTBadgeOverlay>
</DTCard>
<DTCard mode="success" title="PRODUCT CARD" style={{ marginBottom: 16 }}>
<View style={{ minHeight: 40 }}>
<Text variant="bodySmall" style={{ color: theme.colors.onSurface }}>
Badge color independent of card mode
</Text>
</View>
<DTBadgeOverlay position="bottom-right">
<DTChip variant="emphasis">NEW</DTChip>
</DTBadgeOverlay>
</DTCard>
<CodeLabel text="<DTBadgeOverlay position='bottom-right'><DTChip variant='warning'>LAB</DTChip>" />
</DemoSection>
{/* Stagger + Progress */}
<DemoSection
title="Staggered Cards with Progress"
variant="success"
description="Stagger container with progress bars across all modes."
>
<DTStaggerContainer>
{modes.map((mode) => (
<DTCard
key={mode}
mode={mode}
title={mode.toUpperCase()}
progress={Math.random()}
style={{ marginBottom: 12 }}
>
<Text variant="bodySmall" style={{ color: theme.colors.onSurface }}>
Staggered + progress
</Text>
</DTCard>
))}
</DTStaggerContainer>
<CodeLabel text="DTStaggerContainer > DTCard progress" />
</DemoSection>
</ScreenContainer>
);
}

View File

@@ -135,6 +135,20 @@ export function CardsScreen() {
</View>
</DemoSection>
{/* DTCard - Selected & Progress (quick preview) */}
<DemoSection
title="Progress Bar"
variant="warning"
description="Quick preview — see Advanced Cards screen for full demos."
>
<DTCard mode="normal" title="WITH PROGRESS" progress={0.6} style={{ marginBottom: 16 }}>
<Text variant="bodyMedium" style={{ color: theme.colors.onSurface }}>
progress=0.6 vertical left-edge bar
</Text>
</DTCard>
<CodeLabel text="See Advanced Cards for full progress/badge demos" />
</DemoSection>
{/* DTMediaFrame */}
<DemoSection
title="DTMediaFrame"

View File

@@ -0,0 +1,116 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import { Text } from 'react-native-paper';
import {
DTFeatureLegend,
DTMobileFilterOverlay,
DTButton,
DTAccordion,
useDTTheme,
} from '@dangerousthings/react-native';
import type { DTFeatureItem } from '@dangerousthings/react-native';
import { ScreenContainer } from '../components/ScreenContainer';
import { DemoSection } from '../components/DemoSection';
import { CodeLabel } from '../components/CodeLabel';
const sampleFeatures: DTFeatureItem[] = [
{ key: 'payment', name: 'Payment', icon: null, state: 'supported' },
{ key: 'access', name: 'Access', icon: null, state: 'supported' },
{ key: 'clone', name: 'Clone', icon: null, state: 'disabled' },
{ key: 'crypto', name: 'Crypto', icon: null, state: 'unsupported' },
{ key: 'sensing', name: 'Sensing', icon: null, state: 'supported' },
{ key: 'temp', name: 'Temp', icon: null, state: 'supported' },
{ key: 'fitness', name: 'Fitness', icon: null, state: 'disabled' },
{ key: 'explore', name: 'Explore', icon: null, state: 'supported' },
{ key: 'vibration', name: 'Vibration', icon: null, state: 'unsupported' },
{ key: 'sharing', name: 'Sharing', icon: null, state: 'supported' },
];
export function FiltersScreen() {
const theme = useDTTheme();
const [overlayVisible, setOverlayVisible] = useState(false);
return (
<ScreenContainer>
{/* Feature Legend */}
<DemoSection
title="DTFeatureLegend"
variant="normal"
description="Product feature grid with icons and rotated labels. Color indicates feature state."
>
<DTFeatureLegend
features={sampleFeatures}
variant="normal"
title="NFC FEATURES"
columns={5}
/>
<CodeLabel text='<DTFeatureLegend features={...} variant="normal" columns={5}>' />
</DemoSection>
{/* Feature Legend — other variant */}
<DemoSection
title="Emphasis Variant"
variant="emphasis"
description="Feature legend with emphasis header color."
>
<DTFeatureLegend
features={sampleFeatures.slice(0, 5)}
variant="emphasis"
title="KEY FEATURES"
columns={5}
/>
<CodeLabel text='variant="emphasis" — header uses emphasis color' />
</DemoSection>
{/* Mobile Filter Overlay */}
<DemoSection
title="DTMobileFilterOverlay"
variant="warning"
description="Full-screen slide-up overlay for mobile filter menus."
>
<DTButton
variant="normal"
onPress={() => setOverlayVisible(true)}
>
OPEN FILTER OVERLAY
</DTButton>
<CodeLabel text="<DTMobileFilterOverlay visible onDismiss={...}>" />
<DTMobileFilterOverlay
visible={overlayVisible}
onDismiss={() => setOverlayVisible(false)}
heading="FILTERS"
activeFilterCount={3}
onClearAll={() => setOverlayVisible(false)}
variant="normal"
>
<DTAccordion
sections={[
{
key: 'chip-type',
title: 'CHIP TYPE',
children: (
<View style={{ gap: 8 }}>
<Text style={{ color: theme.colors.onSurface }}>NTAG215</Text>
<Text style={{ color: theme.colors.onSurface }}>NTAG216</Text>
<Text style={{ color: theme.colors.onSurface }}>DESFire EV2</Text>
</View>
),
},
{
key: 'frequency',
title: 'FREQUENCY',
children: (
<View style={{ gap: 8 }}>
<Text style={{ color: theme.colors.onSurface }}>13.56 MHz (HF)</Text>
<Text style={{ color: theme.colors.onSurface }}>125 kHz (LF)</Text>
</View>
),
},
]}
/>
</DTMobileFilterOverlay>
</DemoSection>
</ScreenContainer>
);
}

View File

@@ -56,6 +56,27 @@ const categories: {
route: 'Overlays',
count: 5,
},
{
title: 'ADVANCED CARDS',
subtitle: 'Selected state, progress bar, badge overlays, stagger',
mode: 'emphasis',
route: 'CardsAdvanced',
count: 4,
},
{
title: 'ANIMATIONS',
subtitle: 'DTStaggerContainer, useScaleIn, usePulse',
mode: 'success',
route: 'Animations',
count: 3,
},
{
title: 'FILTERS & FEATURES',
subtitle: 'DTFeatureLegend, DTMobileFilterOverlay',
mode: 'other',
route: 'Filters',
count: 2,
},
{
title: 'THEME REFERENCE',
subtitle: 'Colors, Typography, Spacing',