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:
@@ -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 },
|
||||
];
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
90
packages/showcase/mobile/src/screens/AnimationsScreen.tsx
Normal file
90
packages/showcase/mobile/src/screens/AnimationsScreen.tsx
Normal 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({ duration: 600 })
|
||||
</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>
|
||||
);
|
||||
}
|
||||
111
packages/showcase/mobile/src/screens/CardsAdvancedScreen.tsx
Normal file
111
packages/showcase/mobile/src/screens/CardsAdvancedScreen.tsx
Normal 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 (0–1)."
|
||||
>
|
||||
{[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={{val}}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
116
packages/showcase/mobile/src/screens/FiltersScreen.tsx
Normal file
116
packages/showcase/mobile/src/screens/FiltersScreen.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user