Files
vending-machine-game/src/components/shared/Sheet.jsx
T
HamedandClaude Opus 5.5 5a4dffc9a3 feat: minimal HUD with one floating menu and objective chip
Phase 4 of turning the game into the company landing page.

- Only the menu button, a compact objective chip and the touch
  controls stay on screen; speed shows only while driving.
- The objective chip shows the next goal in one line, its distance and
  an arrow that turns towards it (replaces the compass strip); tapping
  it opens the mission list.
- One floating menu holds map, missions, status, products, real-world,
  camera, stable mode, sound, graphics, minimap, fullscreen, help,
  restart and back to the start screen. It closes on a second tap, a
  tap outside, Escape, or after an action.
- Missions, status and help open as sheets; street label, rank bar,
  status panel, toolbar and compass are gone. The minimap is optional
  and off by default.
- Share the icon set and sheet dialog between start screen and HUD.
- Layout checked for phone portrait, landscape and safe areas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 08:50:29 +03:30

48 lines
1.5 KiB
React

import { useEffect, useRef } from 'react';
import { Icon } from './Icon.jsx';
/** @typedef {{id: string, open: boolean, title: string, closeLabel: string, onClose: function,
* children?: import('react').ReactNode}} SheetProps */
/**
* Modal panel built on the native dialog (focus trap and Escape for free). Styled by the
* `.info-sheet` rules: a centred card on wide screens, a bottom sheet on phones.
* @param {SheetProps} props - Visibility, title, close label and callback, and content.
* @returns {import('react').ReactElement} Dialog element.
*/
export function Sheet({ id, open, title, closeLabel, onClose, children }) {
const dialog = useRef(null);
// Keep the native dialog in step with `open`; closing by Escape reports back through onClose.
useEffect(() => {
const element = dialog.current;
if (!element) return;
if (open && !element.open) element.showModal();
if (!open && element.open) element.close();
}, [open]);
return (
<dialog
ref={dialog}
id={id}
aria-labelledby={`${id}-title`}
onClose={onClose}
className="info-sheet"
>
<header className="mb-4 flex items-center justify-between gap-4">
<h2 id={`${id}-title`} className="text-lg font-bold">
{title}
</h2>
<button
type="button"
className="start-icon-button"
aria-label={closeLabel}
onClick={onClose}
>
<Icon name="close" className="h-5 w-5" />
</button>
</header>
{open && children}
</dialog>
);
}