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,13 @@
> Why do I have a folder named ".expo" in my project?
The ".expo" folder is created when an Expo project is started using "expo start" command.
> What do the files contain?
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
- "settings.json": contains the server configuration that is used to serve the application manifest.
> Should I commit the ".expo" folder?
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.

View File

@@ -0,0 +1,3 @@
{
"devices": []
}

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',