UNPKG

@razorpay/blade-mcp

Version:

Model Context Protocol server for Blade

656 lines (576 loc) 18.8 kB
## Component Name BottomSheet ## Description BottomSheet is a component commonly used in mobile applications to display additional information or actions without obstructing the main content of the screen. It slides up from the bottom of the viewport and can be configured with various heights through snap points. BottomSheet provides a structured layout with optional header, body, and footer sections, making it ideal for presenting forms, selection menus, and detailed information in a mobile-friendly interface. ## TypeScript Types The following types represent the props that the BottomSheet component and its subcomponents accept. These types allow you to properly configure the bottom sheet according to your needs. ```typescript /** * Props for the main BottomSheet component */ type BottomSheetProps = { /** * Controls whether the BottomSheet is opened */ isOpen: boolean; /** * Callback function when the sheet is dismissed */ onDismiss: () => void; /** * The content of the BottomSheet */ children: React.ReactNode; /** * Array of numbers between 0 and 1 that define the height of the BottomSheet * Default is [0.35, 0.5, 0.85] */ snapPoints?: number[]; /** * Reference to the element that should receive focus when the BottomSheet opens * By default, focus is set to the close button */ initialFocusRef?: React.RefObject<HTMLElement>; /** * The z-index value for the BottomSheet * @default 400 */ zIndex?: number; } & TestID & DataAnalyticsAttribute; /** * Props for the BottomSheetHeader component */ type BottomSheetHeaderProps = { /** * The title text of the BottomSheet */ title?: string; /** * The subtitle text of the BottomSheet */ subtitle?: string; /** * Element to be displayed next to the title * Accepts a Counter component */ titleSuffix?: React.ReactNode; /** * Trailing element displayed on the right side of the header * Accepts one of Badge, Text, Button, Link */ trailing?: React.ReactNode; /** * Whether to show a back button in the header * @default false */ showBackButton?: boolean; /** * Callback function when the back button is clicked */ onBackButtonClick?: () => void; /** * Leading element displayed on the left side of the header */ leading?: React.ReactNode; } & TestID & DataAnalyticsAttribute; /** * Props for the BottomSheetBody component */ type BottomSheetBodyProps = { /** * The content of the BottomSheetBody */ children: React.ReactNode; /** * Padding to be applied to the body * @default "spacing.5" */ padding?: string; } & TestID & DataAnalyticsAttribute; /** * Props for the BottomSheetFooter component */ type BottomSheetFooterProps = { /** * The content of the BottomSheetFooter */ children: React.ReactNode; } & TestID & DataAnalyticsAttribute; ``` ## Example Here are several examples demonstrating different uses of the BottomSheet component: ### Basic BottomSheet with Terms and Conditions This example shows a basic BottomSheet with header, body, and footer sections used for displaying terms and conditions with an agreement checkbox. ```tsx import React, { useState } from 'react'; import { BottomSheet, BottomSheetHeader, BottomSheetBody, BottomSheetFooter, Button, Box, Text, Checkbox, } from '@razorpay/blade/components'; const TermsAndConditionsBottomSheet = () => { const [isOpen, setIsOpen] = useState(false); const [termsAccepted, setTermsAccepted] = useState(false); return ( <> <Button onClick={() => setIsOpen(true)}>Open Terms & Conditions</Button> <BottomSheet isOpen={isOpen} onDismiss={() => setIsOpen(false)}> <BottomSheetHeader title="Terms & Conditions" subtitle="Read carefully before accepting" /> <BottomSheetBody> <Text> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla facilisi. Maecenas consequat, dolor vel lobortis ultrices, risus velit volutpat erat, ac pretium urna nibh sit amet magna. Donec tristique quam at lectus blandit, non eleifend dui dictum. </Text> <Text marginTop="spacing.3"> Curabitur id neque vel metus tincidunt efficitur. Morbi vitae arcu lorem. Vivamus at tortor et metus placerat elementum vel ac neque. Nulla facilisi. Praesent vel dolor orci. </Text> </BottomSheetBody> <BottomSheetFooter> <Box display="flex" flexDirection="row" alignItems="center" justifyContent="space-between" > <Checkbox isChecked={termsAccepted} onChange={() => setTermsAccepted(!termsAccepted)}> I accept terms and conditions </Checkbox> <Button variant="primary" isDisabled={!termsAccepted} onClick={() => setIsOpen(false)}> Continue </Button> </Box> </BottomSheetFooter> </BottomSheet> </> ); }; export default TermsAndConditionsBottomSheet; ``` ### Form with Custom Initial Focus This example demonstrates a BottomSheet with form controls and custom initial focus reference, along with custom snap points. ```tsx import React, { useRef, useState } from 'react'; import { BottomSheet, BottomSheetHeader, BottomSheetBody, BottomSheetFooter, Button, Box, Text, TextInput, ActionList, ActionListItem, ActionListItemIcon, } from '@razorpay/blade/components'; import { CustomersIcon } from '@razorpay/blade/components'; const SearchUsersBottomSheet = () => { const [isOpen, setIsOpen] = useState(false); const searchInputRef = useRef(null); return ( <> <Button onClick={() => setIsOpen(true)}>Search Users</Button> <BottomSheet isOpen={isOpen} onDismiss={() => setIsOpen(false)} initialFocusRef={searchInputRef} snapPoints={[0.4, 0.7, 0.95]} // Custom snap points > <BottomSheetHeader title="Search Users" showBackButton onBackButtonClick={() => setIsOpen(false)} /> <BottomSheetBody> <TextInput label="Search for users" placeholder="Type a name or email" ref={searchInputRef} marginBottom="spacing.5" /> <Text variant="body" size="small" color="surface.text.gray.muted" marginBottom="spacing.3" > Recently viewed users </Text> <ActionList> <ActionListItem title="Anurag Hazra" value="user1" leading={<ActionListItemIcon icon={CustomersIcon} />} /> <ActionListItem title="Kamlesh Chandnani" value="user2" leading={<ActionListItemIcon icon={CustomersIcon} />} /> <ActionListItem title="Divyanshu Maithani" value="user3" leading={<ActionListItemIcon icon={CustomersIcon} />} /> </ActionList> </BottomSheetBody> <BottomSheetFooter> <Button isFullWidth onClick={() => setIsOpen(false)}> Done </Button> </BottomSheetFooter> </BottomSheet> </> ); }; export default SearchUsersBottomSheet; ``` ### Header Variants and Radio Selection This example shows all possible header variants with title, subtitle, titleSuffix, trailing, and back button, along with radio button selection. ```tsx import React, { useState } from 'react'; import { BottomSheet, BottomSheetHeader, BottomSheetBody, BottomSheetFooter, Button, Badge, Counter, Radio, RadioGroup, } from '@razorpay/blade/components'; const AddressSelectionBottomSheet = () => { const [isOpen, setIsOpen] = useState(false); const [selectedAddress, setSelectedAddress] = useState(''); return ( <> <Button onClick={() => setIsOpen(true)}>Select Address</Button> <BottomSheet isOpen={isOpen} onDismiss={() => setIsOpen(false)}> <BottomSheetHeader title="Address Details" subtitle="Saving addresses will improve your checkout experience" titleSuffix={<Counter value={3} color="positive" />} trailing={<Badge color="positive">Action Needed</Badge>} showBackButton onBackButtonClick={() => setIsOpen(false)} /> <BottomSheetBody> <RadioGroup label="Addresses" value={selectedAddress} onChange={({ value }) => setSelectedAddress(value)} > <Radio value="home">Home - 11850 Florida 24, Cedar Key, Florida</Radio> <Radio value="office-1">Office - 2033 Florida 21, Cedar Key, Florida</Radio> <Radio value="office-2">Work - 5938 New York, Main Street</Radio> </RadioGroup> </BottomSheetBody> <BottomSheetFooter> <Button isFullWidth variant="tertiary" marginBottom="spacing.3"> Remove address </Button> <Button isFullWidth onClick={() => setIsOpen(false)}> Add address </Button> </BottomSheetFooter> </BottomSheet> </> ); }; export default AddressSelectionBottomSheet; ``` ### Phone Verification Form with Validation This example demonstrates a BottomSheet with form validation and error handling. ```tsx import React, { useState } from 'react'; import { BottomSheet, BottomSheetHeader, BottomSheetBody, BottomSheetFooter, Button, Box, Text, TextInput, Link, } from '@razorpay/blade/components'; import { ArrowRightIcon } from '@razorpay/blade/components'; const PhoneVerificationBottomSheet = () => { const [isOpen, setIsOpen] = useState(false); const [phoneNumber, setPhoneNumber] = useState(''); const [verificationError, setVerificationError] = useState(''); const handleVerify = () => { if (!phoneNumber) { setVerificationError('Please enter a phone number'); return; } setVerificationError(''); setIsOpen(false); // Handle verification logic here }; return ( <> <Button onClick={() => setIsOpen(true)}>Verify Phone Number</Button> <BottomSheet isOpen={isOpen} onDismiss={() => setIsOpen(false)}> <BottomSheetHeader title="Verify Phone Number" showBackButton onBackButtonClick={() => setIsOpen(false)} /> <BottomSheetBody> <TextInput label="Phone Number" placeholder="Enter your phone number" value={phoneNumber} onChange={({ value }) => setPhoneNumber(value)} errorText={verificationError} validationState={verificationError ? 'error' : 'none'} marginBottom="spacing.5" /> <Text variant="body" size="small" textAlign="center"> By continuing, you agree to our <Link href="#">Terms of Service</Link> and{' '} <Link href="#">Privacy Policy</Link> </Text> </BottomSheetBody> <BottomSheetFooter> <Box display="flex" flexDirection="column" gap="spacing.3"> <Button isFullWidth icon={ArrowRightIcon} iconPosition="right" onClick={handleVerify}> Verify </Button> <Button isFullWidth variant="tertiary" onClick={() => setIsOpen(false)}> Cancel </Button> </Box> </BottomSheetFooter> </BottomSheet> </> ); }; export default PhoneVerificationBottomSheet; ``` ### Integration with Dropdown This example shows how to use BottomSheet with the Dropdown component. ```tsx import React from 'react'; import { Dropdown, SelectInput, BottomSheet, BottomSheetHeader, BottomSheetBody, ActionList, ActionListItem, ActionListItemIcon, } from '@razorpay/blade/components'; import { CustomersIcon, ClockIcon, ThumbsUpIcon } from '@razorpay/blade/components'; const DropdownWithBottomSheet = () => { return ( <Dropdown selectionType="single"> <SelectInput label="Sort Items" /> <BottomSheet> <BottomSheetHeader title="Sort By" /> <BottomSheetBody> <ActionList> <ActionListItem leading={<ActionListItemIcon icon={CustomersIcon} />} title="Relevance (Default)" value="relevance" /> <ActionListItem leading={<ActionListItemIcon icon={ClockIcon} />} title="Delivery Time" value="delivery-time" /> <ActionListItem leading={<ActionListItemIcon icon={ThumbsUpIcon} />} title="Rating" value="rating" /> </ActionList> </BottomSheetBody> </BottomSheet> </Dropdown> ); }; export default DropdownWithBottomSheet; ``` ### Stacked BottomSheets This example demonstrates how to implement a multi-step flow using stacked BottomSheets. ```tsx import React, { useState } from 'react'; import { BottomSheet, BottomSheetHeader, BottomSheetBody, BottomSheetFooter, Button, Box, ActionList, ActionListItem, ActionListSection, ActionListItemIcon, } from '@razorpay/blade/components'; import { CustomersIcon, ClockIcon, ThumbsUpIcon, TrendingUpIcon, TrendingDownIcon, } from '@razorpay/blade/components'; const StackedBottomSheets = () => { const [isFirstSheetOpen, setIsFirstSheetOpen] = useState(false); const [isSecondSheetOpen, setIsSecondSheetOpen] = useState(false); const openSecondSheet = () => { setIsFirstSheetOpen(false); setIsSecondSheetOpen(true); }; const backToFirstSheet = () => { setIsSecondSheetOpen(false); setIsFirstSheetOpen(true); }; return ( <> <Button onClick={() => setIsFirstSheetOpen(true)}>Open Multi-step Flow</Button> {/* First sheet with sort options */} <BottomSheet isOpen={isFirstSheetOpen} onDismiss={() => setIsFirstSheetOpen(false)}> <BottomSheetHeader title="Sort Options" subtitle="Choose how to organize your data" /> <BottomSheetBody> <ActionList> <ActionListItem leading={<ActionListItemIcon icon={CustomersIcon} />} title="Relevance (Default)" value="relevance" /> <ActionListItem leading={<ActionListItemIcon icon={ClockIcon} />} title="Delivery Time" value="delivery-time" /> <ActionListItem leading={<ActionListItemIcon icon={ThumbsUpIcon} />} title="Rating" value="rating" /> <ActionListItem leading={<ActionListItemIcon icon={TrendingUpIcon} />} title="Cost: Low to High" value="cost-low-high" /> <ActionListItem leading={<ActionListItemIcon icon={TrendingDownIcon} />} title="Cost: High to Low" value="cost-high-low" /> </ActionList> </BottomSheetBody> <BottomSheetFooter> <Button isFullWidth onClick={openSecondSheet}> Next: Filter Options </Button> </BottomSheetFooter> </BottomSheet> {/* Second sheet with filter options */} <BottomSheet isOpen={isSecondSheetOpen} onDismiss={() => setIsSecondSheetOpen(false)}> <BottomSheetHeader title="Filter By Cuisines" /> <BottomSheetBody> <ActionList> <ActionListSection title="Asia"> <ActionListItem title="Chinese" value="Chinese" /> <ActionListItem title="Indian" value="Indian" /> <ActionListItem title="Thai" value="Thai" /> <ActionListItem title="Japanese" value="Japanese" /> </ActionListSection> <ActionListSection title="Europe"> <ActionListItem title="Italian" value="Italian" /> <ActionListItem title="French" value="French" /> <ActionListItem title="Spanish" value="Spanish" /> </ActionListSection> </ActionList> </BottomSheetBody> <BottomSheetFooter> <Box display="flex" flexDirection="column" gap="spacing.3"> <Button isFullWidth onClick={() => setIsSecondSheetOpen(false)}> Apply Filters </Button> <Button isFullWidth variant="tertiary" onClick={backToFirstSheet}> Back to Sort Options </Button> </Box> </BottomSheetFooter> </BottomSheet> </> ); }; export default StackedBottomSheets; ``` ### BottomSheet with Zero Padding This example shows how to create a BottomSheet with zero padding for full-width content like images or banners. ```tsx import React, { useState } from 'react'; import { BottomSheet, BottomSheetHeader, BottomSheetBody, BottomSheetFooter, Button, Box, Text, Heading, } from '@razorpay/blade/components'; const ZeroPaddingBottomSheet = () => { const [isOpen, setIsOpen] = useState(false); return ( <> <Button onClick={() => setIsOpen(true)}>Open Full-Width Content</Button> <BottomSheet isOpen={isOpen} onDismiss={() => setIsOpen(false)}> <BottomSheetHeader /> <BottomSheetBody padding="spacing.0"> <Box> {/* Full-width banner/image container */} <Box position="relative" height="200px" backgroundColor="surface.background.cloud.subtle" display="flex" alignItems="flex-end" padding="spacing.5" > <Heading size="large" color="surface.text.gray.normal"> All-in-one Management Platform </Heading> </Box> {/* Content with padding */} <Box padding="spacing.5"> <Text> We bring together all services in ONE place to deliver a seamless user experience for you. Work with our experts to ensure your transfers are always compliant, safe & effortless. </Text> <Text marginTop="spacing.3" color="surface.text.gray.muted"> 100% secure | Instant payouts | Unbeatable pricing </Text> </Box> </Box> </BottomSheetBody> <BottomSheetFooter> <Button isFullWidth>Talk To Our Experts</Button> </BottomSheetFooter> </BottomSheet> </> ); }; export default ZeroPaddingBottomSheet; ```