返回 presentation-ai
PresentModeHeader.tsx
根目录 / src / components / presentation / present-mode / PresentModeHeader.tsx
1 "use client";
2
3 import { Loader2 } from "lucide-react";
4 import { useEffect, useState } from "react";
5 import { createPortal } from "react-dom";
6
7 import { Button } from "@/components/ui/button";
8 import { usePresentationRecordingState } from "@/states/presentation-recording-state";
9 import { usePresentationState } from "@/states/presentation-state";
10
11 type ScreenOrientationController = ScreenOrientation & {
12 unlock?: () => void;
13 };
14
15 function getScreenOrientationController(): ScreenOrientationController | null {
16 if (typeof screen === "undefined" || !("orientation" in screen)) {
17 return null;
18 }
19
20 return screen.orientation as ScreenOrientationController;
21 }
22
23 interface PresentModeHeaderProps {
24 showHeader: boolean;
25 presentationTitle: string | null;
26 }
27
28 export function PresentModeHeader({
29 showHeader,
30 presentationTitle,
31 }: PresentModeHeaderProps) {
32 const [mounted, setMounted] = useState(false);
33 const [isExiting, setIsExiting] = useState(false);
34 const isPresenting = usePresentationState((s) => s.isPresenting);
35
36 useEffect(() => {
37 setMounted(true);
38 }, []);
39
40 useEffect(() => {
41 if (isPresenting) {
42 setIsExiting(false);
43 }
44 }, [isPresenting]);
45
46 if (!mounted || typeof document === "undefined") {
47 return null;
48 }
49
50 return createPortal(
51 <div
52 className={`fixed top-0 right-0 left-0 z-2147483647 transition-all duration-300 ${
53 showHeader ? "translate-y-0" : "-translate-y-full"
54 }`}
55 >
56 <div className="border-b border-white/10 bg-black/80 backdrop-blur-xs">
57 <div className="container mx-auto px-6 py-4">
58 <div className="flex items-center justify-between">
59 <div className="text-lg font-semibold text-white">
60 {presentationTitle}
61 </div>
62 <Button
63 variant="ghost"
64 className="text-white hover:bg-white/20"
65 disabled={isExiting}
66 onClick={async () => {
67 setIsExiting(true);
68 await new Promise((resolve) => setTimeout(resolve, 50));
69
70 usePresentationState.getState().setIsPresenting(false);
71 usePresentationState.getState().setIsPresentingLoading(false);
72 usePresentationState.getState().resetPresentingScaleLocks();
73 usePresentationRecordingState
74 .getState()
75 .setWantsToRecord(false);
76
77 if (document.fullscreenElement) {
78 await document.exitFullscreen().catch(() => undefined);
79 }
80
81 const orientationController = getScreenOrientationController();
82 if (typeof orientationController?.unlock === "function") {
83 orientationController.unlock();
84 }
85 }}
86 >
87 {isExiting ? (
88 <>
89 <Loader2 className="mr-2 size-4 animate-spin" />
90 Exiting…
91 </>
92 ) : (
93 "Exit Presentation"
94 )}
95 </Button>
96 </div>
97 </div>
98 </div>
99 </div>,
100 document.body,
101 );
102 }
103
103 lines Plain Text