Skip to Content
UIComponentsPageDrawer

DropdownButton

Description

The PageDrawer component. Component is displayed at the bottom of the page and can be expanded to show additional content.

Parameters

{ expanded, onChange, children, height, minHeight, onResize, color, bgColor, slots, ...rest }
PageDrawerProps

=>

react_jsx_runtime.JSX.Element

The PageDrawer component.

Example

Inspect

Source code

/packages/react-ui/PageDrawer/PageDrawer.tsx
import { useState, useRef, type HTMLAttributes, type ElementRef, type TouchEvent as ReactTouchEvent, type MouseEvent as ReactMouseEvent } from 'react'; import { Accordion, AccordionDetails, AccordionSummary, Box, type SxProps, type Theme } from '@mui/material'; import { ExpandMore } from '@mui/icons-material'; const isScrollable = (node: Element) => { if (!(node instanceof HTMLElement || node instanceof SVGElement)) { return false } const style = getComputedStyle(node) return ['overflow', 'overflow-x', 'overflow-y'].some((propertyName) => { const value = style.getPropertyValue(propertyName) return value === 'auto' || value === 'scroll' }) } const getScrollParent = (node: Element): Element => { let currentParent = node.parentElement while (currentParent) { if (isScrollable(currentParent)) { return currentParent } currentParent = currentParent.parentElement } return document.scrollingElement || document.documentElement } /** * The PageDrawer component props. * @public */ export type PageDrawerProps = HTMLAttributes<HTMLDivElement> & { /** * @defaultValue `primary.dark` */ color?: string; /** * @defaultValue `background.default` */ bgColor?: string; expanded?: boolean; height?: number; /** * @defaultValue `50` */ minHeight?: number; onChange?: () => void; onResize?: (height: number | undefined) => void; slots?: { root?: { sx?: SxProps<Theme>; }, summary?: { sx?: SxProps<Theme>; }, details?: { sx?: SxProps<Theme>; } } }; /** * The PageDrawer component. * Component is displayed at the bottom of the page and can be expanded to show additional content. * @param props - The PageDrawer component props. * @returns The PageDrawer component. * @public */ export function PageDrawer({ expanded, onChange, children, height, minHeight = 50, onResize, color = 'primary.dark', bgColor = 'background.default', slots = {}, ...rest }: PageDrawerProps) { const isResizingRef = useRef(false); const didResize = useRef(false); const ref = useRef<ElementRef<'div'>>(null); const contentRef = useRef<ElementRef<'div'>>(null); const handleRef = useRef<ElementRef<'div'>>(null); const [isExpanded, setIsExpanded] = useState(expanded ?? false); const realExpanded = expanded ?? isExpanded; const handleOnChange = () => { if (didResize.current) return; setIsExpanded(!realExpanded); if (onChange) { onChange(); } }; const handleTouchStart = (event: ReactTouchEvent<HTMLDivElement>) => { event.stopPropagation(); isResizingRef.current = true; didResize.current = false; document.addEventListener('touchmove', handleTouchMove); document.addEventListener('touchend', handleTouchEnd); } const handleMouseDown = (event: ReactMouseEvent<HTMLDivElement, MouseEvent>) => { event.preventDefault(); event.stopPropagation(); isResizingRef.current = true; didResize.current = false; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); }; const handleTouchMove = (event: TouchEvent) => { handleMove(event.touches[0].clientY); }; const handleMouseMove = (event: MouseEvent) => { handleMove(event.clientY); }; const handleMove = (clientY: number) => { if (!isResizingRef.current) return; const containerRect = ref.current?.getBoundingClientRect(); const handleClientRect = handleRef.current?.getBoundingClientRect(); let newHeight: number | undefined = (containerRect?.y ?? 0) + (containerRect?.height ?? 0) - clientY - (handleClientRect?.height ?? 0) / 2; // Correct for min height if (newHeight < minHeight) return; // Correct for max height // We use scroll parent as container because the drawer is sticky const containerClientRect = getScrollParent(ref.current ?? document.documentElement).getBoundingClientRect(); if (newHeight > containerClientRect.height) { newHeight = containerClientRect?.height - (handleClientRect?.height ?? 0); } if (contentRef.current && newHeight != null) { didResize.current = true; contentRef.current.style.setProperty('height', `${newHeight}px`); if (onResize) { onResize(newHeight); } } }; const handleTouchEnd = (event: TouchEvent) => { if (didResize.current) { event.stopPropagation(); } isResizingRef.current = false; document.removeEventListener('touchmove', handleTouchMove); document.removeEventListener('touchend', handleTouchEnd); }; const handleMouseUp = (event: MouseEvent) => { if (didResize.current) { event.preventDefault(); event.stopPropagation(); } isResizingRef.current = false; document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; return ( <Accordion ref={ref} sx={{ position: 'sticky', inset: 'auto 0 0 0', bgcolor: 'transparent', backgroundImage: 'none', border: 'none', '&::before': { display: 'none' }, ...(slots.root?.sx ?? {}) }} expanded={realExpanded} onChange={handleOnChange} {...rest} > <AccordionSummary ref={handleRef} onMouseDown={handleMouseDown} onTouchStart={handleTouchStart} expandIcon={<ExpandMore />} sx={{ position: 'relative', minHeight: 32, height: 32, '.MuiAccordionSummary-expandIconWrapper': { bgcolor: color, color: 'primary.main', borderRadius: 1 }, '&.Mui-expanded': { minHeight: 24, height: 24, }, '&::before': { display: 'block', content: '""', position: 'absolute', top: '40%', left: 0, right: 0, bottom: 0, bgcolor: bgColor }, ...(slots.summary?.sx ?? {}) }} > <Box sx={{ borderTop: '1px solid', borderBottom: '1px solid', borderColor: color, height: 3, width: '100%', position: 'absolute', left: 0, right: 0, }} /> </AccordionSummary> <AccordionDetails ref={contentRef} sx={{ bgcolor: bgColor, overflowX: 'hidden', overflowY: 'auto', height: height ? `${height}px` : undefined, ...(slots.details?.sx ?? {}) }} > {children} </AccordionDetails> </Accordion> ); };
Last updated on