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 },
});