Files
site-builder/craft/src/panels/mobile/BottomSheet.tsx
T

67 lines
2.2 KiB
TypeScript
Raw Normal View History

import React, { useEffect } from 'react';
export interface BottomSheetProps {
open: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
}
/**
* Mobile-only bottom sheet (Phase A). Slides up from the bottom of the
* viewport to host one of the existing side-panel components
* (BlocksPanel/PagesPanel/LayersPanel/AssetsPanel/GuidedStyles) unchanged,
* as the mobile replacement for the desktop fixed left/right panel columns.
* See `MobilePanelBar`, which owns which sheet (if any) is open.
*
* Deliberately NOT a generic replacement for `src/ui/Modal.tsx` (centered
* dialog chrome) -- this is anchored to the bottom, sized to ~65dvh, and
* has its own drag-handle affordance + internally scrollable body, all of
* which Modal doesn't need for its centered use cases.
*/
export const BottomSheet: React.FC<BottomSheetProps> = ({ open, onClose, title, children }) => {
useEffect(() => {
if (!open) return;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleKeyDown);
const prevOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
window.removeEventListener('keydown', handleKeyDown);
document.body.style.overflow = prevOverflow;
};
}, [open, onClose]);
if (!open) return null;
return (
<div
className="mobile-sheet-backdrop"
onClick={(e) => {
if (e.target === e.currentTarget) onClose();
}}
>
<div className="mobile-sheet" role="dialog" aria-modal="true" aria-label={title}>
<div className="mobile-sheet-handle-row">
<span className="mobile-sheet-handle" aria-hidden="true" />
</div>
<div className="mobile-sheet-header">
<span className="mobile-sheet-title">{title}</span>
<button
type="button"
className="mobile-sheet-close"
onClick={onClose}
aria-label={`Close ${title}`}
>
<i className="fa fa-times" aria-hidden="true" />
</button>
</div>
<div className="mobile-sheet-body">{children}</div>
</div>
</div>
);
};