import React, { useState } from 'react'; import { View, Text, StyleSheet, ScrollView } from 'react-native'; import { useUx4gTheme } from '../theme/Ux4gThemeContext'; import { UX4GColors } from '../foundation/colors'; import { Ux4gIcons } from '../foundation/icons'; import { Ux4gButton } from '../components/button'; import { Ux4gBottomSheet, Ux4gBottomSheetSize, Ux4gBottomSheetFooterAlign, } from '../components/bottom-sheet'; /** Filler used as the sheet body so scrolling behaviour is visible. */ const DemoBody: React.FC<{ lines?: number; color: string }> = ({ lines = 6, color }) => ( <> {Array.from({ length: lines }).map((_, i) => ( {i + 1}. Bottom sheet body content. Pass anything you like as `children` — this slot maps to the `Content` slot in the Figma component. ))} ); export const BottomSheetShowcase: React.FC = () => { const theme = useUx4gTheme(); const muted = theme.isDark ? UX4GColors.neutral300 : UX4GColors.neutral700; const [sizeSheet, setSizeSheet] = useState(null); const [basicOpen, setBasicOpen] = useState(false); const [iconOpen, setIconOpen] = useState(false); const [noHeaderOpen, setNoHeaderOpen] = useState(false); const [destructiveOpen, setDestructiveOpen] = useState(false); const [noFooterOpen, setNoFooterOpen] = useState(false); const [dividerOpen, setDividerOpen] = useState(false); const [noBackdropOpen, setNoBackdropOpen] = useState(false); const [footerAlign, setFooterAlign] = useState(null); const cardStyle = [ styles.card, { backgroundColor: theme.isDark ? UX4GColors.neutral900 : UX4GColors.neutral50, borderColor: theme.isDark ? UX4GColors.neutral800 : UX4GColors.neutral200, }, ]; return ( Ux4gBottomSheet (`Ux4gBottomSheetProps`) Modal bottom sheet built to the UX4G Figma `Bottom Sheet` component set: four size presets, optional header/footer, swipe-to-dismiss and Android back handling. {/* 1. Sizes */} 1. Sizes (`peek`, `half`, `expanded`, `full`) Heights come from the Figma `Size` variant, as a ratio of screen height: peek 26%, half 50%, expanded 75%, full 94%. {(['peek', 'half', 'expanded', 'full'] as Ux4gBottomSheetSize[]).map((s) => ( setSizeSheet(s)} /> ))} {/* 2. Header variations */} 2. Header variations setBasicOpen(true)} /> setIconOpen(true)} /> setNoHeaderOpen(true)} /> {/* 3. Footer */} 3. Footer (`footerAlign`, `isDestructive`, `showFooter`) `split` is the Figma default — both actions stretch to fill the row. {(['split', 'left', 'center', 'right'] as Ux4gBottomSheetFooterAlign[]).map((a) => ( setFooterAlign(a)} /> ))} setDestructiveOpen(true)} /> setNoFooterOpen(true)} /> {/* 4. Options */} 4. Options (`showBottomDivider`, `showBackdrop`) setDividerOpen(true)} /> setNoBackdropOpen(true)} /> {/* 5. Draggable & Snap Points */} 5. Draggable & Snap Points (`draggable`, `enableDragToExpand`) Drag the handle or header UP to expand through snap points (peek 26% → half 50% → expanded 75% → full 94%), or drag DOWN to collapse and dismiss. setBasicOpen(true)} /> {/* ── Sheets ── */} setSizeSheet(null)} size={sizeSheet ?? 'half'} title={`Size = ${sizeSheet ?? ''}`} description="Drag the handle down to dismiss." onPrimaryPress={() => setSizeSheet(null)} > setBasicOpen(false)} size="half" title="Header" description="Write description here" primaryButtonText="Confirm" secondaryButtonText="Cancel" onPrimaryPress={() => setBasicOpen(false)} > setIconOpen(false)} size="half" icon={Ux4gIcons.info({ size: 24, color: theme.colors.primary })} title="Header" subtleText="Subtle text" description="The description indents to align with the title when an icon is present." onPrimaryPress={() => setIconOpen(false)} > setNoHeaderOpen(false)} size="peek" showHeader={false} onPrimaryPress={() => setNoHeaderOpen(false)} > `showHeader={'{false}'}` hides the whole header block — useful for simple action sheets. setFooterAlign(null)} size="peek" title={`footerAlign = ${footerAlign ?? ''}`} footerAlign={footerAlign ?? 'split'} onPrimaryPress={() => setFooterAlign(null)} > Compare the footer arrangement. setDestructiveOpen(false)} size="peek" title="Delete this item?" description="This action cannot be undone." isDestructive primaryButtonText="Delete" secondaryButtonText="Cancel" onPrimaryPress={() => setDestructiveOpen(false)} > `isDestructive` renders the primary action in the error color. setNoFooterOpen(false)} size="half" title="No footer" showFooter={false} > setDividerOpen(false)} size="half" title="Bottom divider" description="Figma `? Bottom Divider` boolean, off by default." showBottomDivider onPrimaryPress={() => setDividerOpen(false)} > setNoBackdropOpen(false)} size="peek" title="No backdrop" showBackdrop={false} onPrimaryPress={() => setNoBackdropOpen(false)} > The scrim is hidden, so content behind stays visible. Tap the close button or swipe down. ); }; const styles = StyleSheet.create({ container: { flex: 1 }, contentContainer: { padding: 16, paddingBottom: 40 }, header: { paddingBottom: 16, marginBottom: 20, borderBottomWidth: 1 }, title: { fontSize: 24, fontWeight: '700', marginBottom: 6 }, subtitle: { fontSize: 14, lineHeight: 20 }, section: { marginBottom: 24 }, sectionTitle: { fontSize: 18, fontWeight: '700', marginBottom: 12 }, card: { borderWidth: 1, borderRadius: 12, padding: 16 }, note: { fontSize: 13, lineHeight: 18, marginBottom: 12 }, buttonRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 }, });