adaedb70ef
Mobile Navigation: - Add MobileNav component with bottom tab bar - Show mobile nav on small screens, hide desktop sidebar - Add session count badge to Sessions tab - Add safe area padding for notched devices Session Management: - Redesign SessionCard for mobile with action menu - Add MobileActionSheet for session actions - Keep primary action prominent Forms & Dialogs: - Stack form fields vertically on mobile - Ensure 44px minimum touch targets - Update dialogs for 320px viewport Responsive Layout: - Add MobilePageHeader with back button - Reduce page padding on mobile - Stack multi-column grids vertically Touch & Interaction: - Add active states to interactive elements - Ensure 8px spacing between touch targets Complex Pages: - Update Repo Workspace for mobile - Update Tool Workshop and Config Profiles Build: TypeScript check passes, production build succeeds
89 lines
2.2 KiB
TypeScript
89 lines
2.2 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|
import { Icon } from "./icon";
|
|
import type { IconName } from "./icon";
|
|
|
|
export interface MobileActionSheetItem {
|
|
id: string;
|
|
label: string;
|
|
icon?: IconName;
|
|
variant?: "default" | "danger";
|
|
onClick: () => void;
|
|
}
|
|
|
|
interface MobileActionSheetProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
title: string;
|
|
actions: MobileActionSheetItem[];
|
|
}
|
|
|
|
export function MobileActionSheet({
|
|
isOpen,
|
|
onClose,
|
|
title,
|
|
actions,
|
|
}: MobileActionSheetProps) {
|
|
const sheetRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
document.body.style.overflow = "hidden";
|
|
} else {
|
|
document.body.style.overflow = "";
|
|
}
|
|
return () => {
|
|
document.body.style.overflow = "";
|
|
};
|
|
}, [isOpen]);
|
|
|
|
useEffect(() => {
|
|
const handleEscape = (e: KeyboardEvent) => {
|
|
if (e.key === "Escape" && isOpen) {
|
|
onClose();
|
|
}
|
|
};
|
|
document.addEventListener("keydown", handleEscape);
|
|
return () => document.removeEventListener("keydown", handleEscape);
|
|
}, [isOpen, onClose]);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<div className="mobile-action-sheet-overlay" onClick={onClose}>
|
|
<div
|
|
ref={sheetRef}
|
|
className="mobile-action-sheet"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="mobile-action-sheet-header">
|
|
<div className="mobile-action-sheet-handle" />
|
|
<h3>{title}</h3>
|
|
</div>
|
|
<div className="mobile-action-sheet-actions">
|
|
{actions.map((action) => (
|
|
<button
|
|
key={action.id}
|
|
className={`mobile-action-sheet-button ${action.variant || "default"}`}
|
|
onClick={() => {
|
|
action.onClick();
|
|
onClose();
|
|
}}
|
|
type="button"
|
|
>
|
|
{action.icon && <Icon name={action.icon} size="md" />}
|
|
<span>{action.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
<button
|
|
className="mobile-action-sheet-cancel"
|
|
onClick={onClose}
|
|
type="button"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|