portosaurus 3.0.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +26 -126
  2. package/bin/portosaurus.mjs +8 -0
  3. package/package.json +6 -3
  4. package/src/assets/img/icon.png +0 -0
  5. package/src/assets/img/{icon.svg → svg/icon.svg} +35 -37
  6. package/src/assets/img/svg/project-blank.svg +140 -0
  7. package/src/assets/sample-resume.pdf +0 -0
  8. package/src/cli/build.mjs +2 -5
  9. package/src/cli/dev.mjs +27 -5
  10. package/src/cli/init.mjs +6 -12
  11. package/src/cli/schema.mjs +211 -0
  12. package/src/core/buildDocuConfig.mjs +305 -188
  13. package/src/core/constants.mjs +7 -1
  14. package/src/template/config.yml +150 -0
  15. package/src/template/notes/welcome.mdx +6 -0
  16. package/src/template/package.json +3 -3
  17. package/src/theme/MDXComponents.js +0 -1
  18. package/src/theme/components/AboutSection/index.js +32 -17
  19. package/src/theme/components/AboutSection/styles.module.css +151 -344
  20. package/src/theme/components/ContactSection/index.js +23 -14
  21. package/src/theme/components/ContactSection/styles.module.css +19 -8
  22. package/src/theme/components/ExperienceSection/index.js +12 -5
  23. package/src/theme/components/HeroSection/index.js +4 -3
  24. package/src/theme/components/HeroSection/styles.module.css +17 -16
  25. package/src/theme/components/NavArrow/index.js +114 -0
  26. package/src/theme/components/NavArrow/styles.module.css +107 -0
  27. package/src/theme/components/NoteIndex/index.js +66 -95
  28. package/src/theme/components/NoteIndex/styles.module.css +85 -89
  29. package/src/theme/components/Preview/components/FeedbackStates.js +3 -1
  30. package/src/theme/components/Preview/components/PreviewContent.js +91 -0
  31. package/src/theme/components/Preview/components/PreviewHeader.js +41 -33
  32. package/src/theme/components/Preview/components/Triggers/Pv.js +129 -72
  33. package/src/theme/components/Preview/components/ViewerWindow.js +198 -234
  34. package/src/theme/components/Preview/hooks/useAdaptiveSizing.js +115 -0
  35. package/src/theme/components/Preview/hooks/useDeepLinkHash.js +18 -23
  36. package/src/theme/components/Preview/hooks/useDockLayout.js +48 -8
  37. package/src/theme/components/Preview/hooks/useTouchZoom.js +118 -0
  38. package/src/theme/components/Preview/renderers/CodeRenderer.js +64 -25
  39. package/src/theme/components/Preview/state/index.js +70 -17
  40. package/src/theme/components/Preview/styles.module.css +181 -45
  41. package/src/theme/components/Preview/utils/index.js +11 -10
  42. package/src/theme/components/ProjectsSection/index.js +138 -148
  43. package/src/theme/components/ProjectsSection/styles.module.css +178 -112
  44. package/src/theme/components/SocialLinks/index.js +9 -7
  45. package/src/theme/components/Tooltip/index.js +31 -20
  46. package/src/theme/components/Tooltip/styles.module.css +101 -38
  47. package/src/theme/config/iconMappings.js +2 -0
  48. package/src/theme/css/custom.css +72 -0
  49. package/src/theme/hooks/useScrollReveal.js +30 -0
  50. package/src/theme/pages/index.js +7 -27
  51. package/src/theme/pages/notes.js +2 -2
  52. package/src/theme/pages/tasks.js +12 -11
  53. package/src/utils/cliUtils.mjs +23 -51
  54. package/src/utils/configUtils.mjs +95 -84
  55. package/src/utils/systemUtils.mjs +171 -0
  56. package/src/template/config.js +0 -68
  57. package/src/theme/components/ScrollToTop/index.js +0 -95
  58. package/src/theme/components/ScrollToTop/styles.module.css +0 -97
  59. package/src/theme/config/metaTags.js +0 -21
  60. /package/src/template/{.nojekyll → static/.nojekyll} +0 -0
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useCallback, useRef } from "react";
1
+ import { useState, useEffect, useCallback, useRef } from "react";
2
2
  import { createPortal } from "react-dom";
3
3
  import { useLocation } from "@docusaurus/router";
4
4
  import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
@@ -10,33 +10,33 @@ import { classify, getExt, resolveUrl } from "../utils";
10
10
  import { useFileFetch } from "../hooks/useFileFetch";
11
11
  import { useDockLayout } from "../hooks/useDockLayout";
12
12
  import { useDeepLinkHash } from "../hooks/useDeepLinkHash";
13
+ import { useAdaptiveSizing } from "../hooks/useAdaptiveSizing";
14
+ import { useTouchZoom } from "../hooks/useTouchZoom";
13
15
 
14
16
  import PreviewHeader from "./PreviewHeader";
15
17
  import FileTabs from "./FileTabs";
16
- import { LoadingState, ErrorState, OfflineState } from "./FeedbackStates";
17
-
18
- import ImageRenderer from "../renderers/ImageRenderer";
19
- import PdfRenderer from "../renderers/PdfRenderer";
20
- import CodeRenderer from "../renderers/CodeRenderer";
21
- import WebRenderer from "../renderers/WebRenderer";
22
-
18
+ import PreviewContent from "./PreviewContent";
23
19
  import styles from "../styles.module.css";
24
20
 
25
21
  /**
26
22
  * The main Preview Viewer — orchestrates layout modes (floating, docked, mobile peek)
27
- * using react-rnd for window management and framer-motion for animations.
23
+ * utilizing modular hooks for sizing, interactions, and layout management.
28
24
  */
29
25
  export default function PreviewViewer() {
30
26
  const {
31
27
  isOpen,
32
- isDocked,
28
+ mode,
33
29
  sources,
34
30
  activeIndex,
31
+ baseSlug,
35
32
  dockWidth,
33
+ peekHeight,
34
+ modeSwitch,
36
35
  closePreview,
37
- setDocked,
36
+ toggleMode,
38
37
  setActiveIndex,
39
38
  setDockWidth,
39
+ setPeekHeight,
40
40
  floatingState,
41
41
  setFloatingState,
42
42
  } = usePreview();
@@ -45,35 +45,26 @@ export default function PreviewViewer() {
45
45
  const corsProxyList = siteConfig?.customFields?.corsProxyList || [];
46
46
 
47
47
  const location = useLocation();
48
- const [mounted, setMounted] = useState(false);
48
+ const [mounted, setMounted] = useState(typeof window !== "undefined");
49
49
  const [zoomLevel, setZoomLevel] = useState(1.0);
50
50
  const [isOnline, setIsOnline] = useState(
51
51
  typeof window !== "undefined" ? window.navigator.onLine : true,
52
52
  );
53
53
  const [windowWidth, setWindowWidth] = useState(
54
- typeof window !== "undefined" ? window.innerWidth : 1200,
54
+ typeof window !== "undefined" ? document.documentElement.clientWidth : 1200,
55
55
  );
56
56
  const [isInteracting, setIsInteracting] = useState(false);
57
57
  const [isDownloading, setIsDownloading] = useState(false);
58
- const tabRefs = useRef([]);
59
58
 
60
- // --- Mount and Resize Listeners ---
59
+ const popupBodyRef = useRef(null);
60
+
61
+ // --- 1. Mount & Basic Resize Management ---
61
62
  useEffect(() => {
62
63
  setMounted(true);
63
64
  const handleOnline = () => setIsOnline(true);
64
65
  const handleOffline = () => setIsOnline(false);
65
-
66
- const handleResize = () => {
67
- const newWidth = window.innerWidth;
68
- // If docked, adjust dockWidth to maintain roughly the same ratio
69
- if (isDocked && windowWidth > 0) {
70
- const ratio = dockWidth / windowWidth;
71
- const targetWidth = Math.floor(newWidth * ratio);
72
- // Clamp between 380px and 80% of window
73
- setDockWidth(Math.max(380, Math.min(targetWidth, newWidth * 0.8)));
74
- }
75
- setWindowWidth(newWidth);
76
- };
66
+ const handleResize = () =>
67
+ setWindowWidth(document.documentElement.clientWidth);
77
68
 
78
69
  window.addEventListener("online", handleOnline);
79
70
  window.addEventListener("offline", handleOffline);
@@ -85,9 +76,41 @@ export default function PreviewViewer() {
85
76
  window.removeEventListener("offline", handleOffline);
86
77
  window.removeEventListener("resize", handleResize);
87
78
  };
88
- }, [isDocked, dockWidth, windowWidth, setDockWidth]);
79
+ }, []);
80
+
81
+ // --- 2. Adaptive Sizing & Layout Math ---
82
+ const layout = useAdaptiveSizing({
83
+ mode,
84
+ windowWidth,
85
+ floatingState,
86
+ dockWidth,
87
+ peekHeight,
88
+ setFloatingState,
89
+ });
90
+
91
+ const { isDockMode, showAsPeek, isPipMode, isPopupMode, isMobile } = layout;
89
92
 
90
- // --- Auto-close on route change ---
93
+ // --- 3. Interaction & Animation State ---
94
+ useTouchZoom({
95
+ containerRef: popupBodyRef,
96
+ isOpen,
97
+ zoomLevel,
98
+ setZoomLevel,
99
+ });
100
+
101
+ // --- 4. Sidebar & URL Sync ---
102
+ useDockLayout({
103
+ isOpen,
104
+ isPopupMode,
105
+ isSidebarDock: isDockMode,
106
+ isPeekDock: showAsPeek,
107
+ dockWidth,
108
+ peekHeight,
109
+ });
110
+
111
+ useDeepLinkHash(isOpen, sources, activeIndex, mode, baseSlug);
112
+
113
+ // --- 5. Navigation & Lifecycle ---
91
114
  const prevPathRef = useRef(location.pathname);
92
115
  useEffect(() => {
93
116
  if (prevPathRef.current !== location.pathname) {
@@ -96,57 +119,45 @@ export default function PreviewViewer() {
96
119
  }
97
120
  }, [location.pathname, isOpen, closePreview]);
98
121
 
99
- // --- Derived state ---
122
+ useEffect(() => {
123
+ if (isOpen) setZoomLevel(1.0);
124
+ }, [mode, isOpen]);
125
+
126
+ // Escape key support
127
+ useEffect(() => {
128
+ if (!isOpen) return;
129
+ const handler = (e) => {
130
+ if (e.key === "Escape") closePreview();
131
+ };
132
+ window.addEventListener("keydown", handler, { capture: true });
133
+ return () =>
134
+ window.removeEventListener("keydown", handler, { capture: true });
135
+ }, [isOpen, closePreview]);
136
+
137
+ // --- 6. Content Fetching ---
100
138
  const currentFile = sources[activeIndex] ?? sources[0] ?? null;
101
139
  const fileType = currentFile ? classify(currentFile.path) : null;
102
140
  const ext = currentFile ? getExt(currentFile.path) : "";
103
141
  const fileUrl = currentFile ? resolveUrl(currentFile.path) : "";
104
142
 
105
- // --- Hooks ---
106
143
  const {
107
144
  content: textContent,
108
145
  loading: textLoading,
109
- error: fetchError,
110
146
  errors: fetchErrors,
111
147
  retry: retryFetch,
112
148
  setError,
113
149
  } = useFileFetch(currentFile?.path, fileType, isOpen);
114
150
 
115
- useDockLayout(isOpen, isDocked, dockWidth);
116
- useDeepLinkHash(isOpen, sources, activeIndex, tabRefs, isDocked);
117
-
118
- // --- Escape key to close floating window ---
119
- useEffect(() => {
120
- if (!isOpen || isDocked) return;
121
- const handler = (e) => {
122
- if (e.key === "Escape") closePreview();
123
- };
124
- window.addEventListener("keydown", handler);
125
- return () => window.removeEventListener("keydown", handler);
126
- }, [isOpen, isDocked, closePreview]);
127
-
128
- // Reset zoom when switching modes
129
- useEffect(() => {
130
- if (isOpen) setZoomLevel(1.0);
131
- }, [isDocked, isOpen]);
132
-
133
- // --- Download handler ---
134
- // --- Download handler ---
151
+ // --- 7. Download Handler ---
135
152
  const handleDownload = useCallback(async () => {
136
153
  if (!fileUrl) return;
137
154
  setIsDownloading(true);
138
-
139
155
  try {
140
156
  const downloadName =
141
157
  currentFile.label || currentFile.path.split("/").pop();
142
-
143
158
  const triggerBlobDownload = async (url) => {
144
- const resp = await fetch(url, {
145
- mode: "cors",
146
- cache: "no-cache", // Ensure we don't get a tainted cache version
147
- });
159
+ const resp = await fetch(url, { mode: "cors", cache: "no-cache" });
148
160
  if (!resp.ok) throw new Error("Fetch failed");
149
-
150
161
  const blob = await resp.blob();
151
162
  const blobUrl = URL.createObjectURL(blob);
152
163
  const a = document.createElement("a");
@@ -157,47 +168,25 @@ export default function PreviewViewer() {
157
168
  document.body.removeChild(a);
158
169
  setTimeout(() => URL.revokeObjectURL(blobUrl), 100);
159
170
  };
160
-
161
171
  try {
162
- // 1. Try standard CORS fetch with cache busting
163
172
  const bustUrl = fileUrl.includes("?")
164
173
  ? `${fileUrl}&cb=${Date.now()}`
165
174
  : `${fileUrl}?cb=${Date.now()}`;
166
175
  await triggerBlobDownload(bustUrl);
167
- return;
168
176
  } catch (e1) {
169
- console.warn("Standard download failed, trying proxies...", e1);
170
-
171
177
  let proxySuccess = false;
172
-
173
- // Iterate through our fallback proxy array
174
178
  for (const proxyBaseUrl of corsProxyList) {
175
179
  try {
176
- console.warn(`Trying proxy: ${proxyBaseUrl}`);
177
180
  const proxyUrl = `${proxyBaseUrl}${encodeURIComponent(fileUrl)}`;
178
181
  await triggerBlobDownload(proxyUrl);
179
182
  proxySuccess = true;
180
- break; // Success! Exit the loop.
181
- } catch (proxyError) {
182
- console.warn(`Proxy ${proxyBaseUrl} failed:`, proxyError);
183
- // Continue loop to try the next proxy
184
- }
183
+ break;
184
+ } catch (pE) {}
185
185
  }
186
-
187
186
  if (!proxySuccess) {
188
- // Absolute Fallback: direct link redirect
189
- console.warn(
190
- "All proxy downloads failed, falling back to direct link",
191
- );
192
-
193
- alert(
194
- "This file is heavily protected by browser security (CORS) and cannot be downloaded directly. It will be opened in a new tab instead.",
195
- );
196
-
197
187
  const link = document.createElement("a");
198
188
  link.href = fileUrl;
199
189
  link.target = "_blank";
200
- link.rel = "noopener noreferrer";
201
190
  link.setAttribute("download", downloadName);
202
191
  document.body.appendChild(link);
203
192
  link.click();
@@ -207,106 +196,36 @@ export default function PreviewViewer() {
207
196
  } finally {
208
197
  setIsDownloading(false);
209
198
  }
210
- }, [fileUrl, currentFile]);
199
+ }, [fileUrl, currentFile, corsProxyList]);
211
200
 
212
- // --- Guard ---
201
+ // --- Render Helpers ---
213
202
  if (!mounted || !currentFile) return null;
214
203
 
215
- // --- Display title ---
216
204
  const displayTitle =
217
- fileType === "web"
205
+ currentFile.title ||
206
+ (fileType === "web"
218
207
  ? currentFile.path.replace(/^https?:\/\//, "").split("/")[0]
219
- : currentFile.label || currentFile.path.split("/").pop();
220
-
221
- // --- Content Router ---
222
- const renderContent = () => {
223
- const path = currentFile?.path;
224
- const isExternal = path?.startsWith("http") || path?.startsWith("//");
225
-
226
- // Offline guard for external resources
227
- if (!isOnline && isExternal) {
228
- return <OfflineState onRetry={retryFetch} />;
229
- }
230
-
231
- // Error state
232
- const errorMsg = fetchErrors?.[path];
233
- if (errorMsg) {
234
- return (
235
- <ErrorState
236
- path={path}
237
- message={errorMsg}
238
- fileType={fileType}
239
- fileUrl={fileUrl}
240
- onRetry={retryFetch}
241
- />
242
- );
243
- }
244
-
245
- // Loading state (for text files only — renderers handle their own loading)
246
- if (textLoading && fileType === "text") {
247
- return <LoadingState />;
248
- }
208
+ : currentFile.label || currentFile.path.split("/").pop());
249
209
 
250
- switch (fileType) {
251
- case "image":
252
- return (
253
- <ImageRenderer
254
- key={fileUrl}
255
- fileUrl={fileUrl}
256
- label={currentFile.label}
257
- zoomLevel={zoomLevel}
258
- onError={(msg) => setError(path, msg)}
259
- />
260
- );
261
- case "pdf":
262
- return (
263
- <PdfRenderer
264
- key={fileUrl}
265
- fileUrl={fileUrl}
266
- zoomLevel={zoomLevel}
267
- onError={(msg) => setError(path, msg)}
268
- />
269
- );
270
- case "web":
271
- return (
272
- <WebRenderer
273
- key={fileUrl}
274
- fileUrl={fileUrl}
275
- label={currentFile.label}
276
- onError={(msg) => setError(path, msg)}
277
- />
278
- );
279
- default: {
280
- // Text / code
281
- if (!textContent) return <LoadingState />;
282
- return <CodeRenderer code={textContent} language={ext} />;
283
- }
284
- }
285
- };
286
-
287
- // --- Layout calculations ---
288
- const isMobile = windowWidth <= 768;
289
- const showAsDock = !isMobile && isDocked;
290
- const showAsFloating = !isMobile && !isDocked;
291
- const showAsPeek = isMobile && isDocked;
292
-
293
- // --- Header (shared across all modes) ---
294
210
  const header = (
295
- <PreviewHeader
296
- displayTitle={displayTitle}
297
- fileType={fileType}
298
- fileUrl={fileUrl}
299
- isDocked={isDocked}
300
- zoomLevel={zoomLevel}
301
- onZoomChange={setZoomLevel}
302
- onToggleDock={() => setDocked(!isDocked)}
303
- onClose={closePreview}
304
- onDownload={handleDownload}
305
- isDownloading={isDownloading}
306
- />
211
+ <div className={styles.headerWrapper}>
212
+ {showAsPeek && <div className={styles.peekHandle} />}
213
+ <PreviewHeader
214
+ displayTitle={displayTitle}
215
+ fileType={fileType}
216
+ fileUrl={fileUrl}
217
+ mode={mode}
218
+ zoomLevel={zoomLevel}
219
+ onZoomChange={setZoomLevel}
220
+ onToggleMode={toggleMode}
221
+ onClose={closePreview}
222
+ onDownload={handleDownload}
223
+ isDownloading={isDownloading}
224
+ modeSwitch={modeSwitch}
225
+ />
226
+ </div>
307
227
  );
308
228
 
309
- // --- Inner content (shared across all modes) ---
310
229
  const innerContent = (
311
230
  <div className={styles.windowContent}>
312
231
  <FileTabs
@@ -314,106 +233,153 @@ export default function PreviewViewer() {
314
233
  activeIndex={activeIndex}
315
234
  onSelect={setActiveIndex}
316
235
  />
317
- <div className={styles.modalBody}>{renderContent()}</div>
236
+ <div
237
+ className={`${styles.popupBody} ${fileType === "text" ? styles.isText : styles.isGrabbable}`}
238
+ ref={(el) => {
239
+ popupBodyRef.current = el;
240
+ if (el && isOpen) el.focus({ preventScroll: true });
241
+ }}
242
+ tabIndex={-1}
243
+ >
244
+ <PreviewContent
245
+ currentFile={currentFile}
246
+ fileType={fileType}
247
+ fileUrl={fileUrl}
248
+ isOnline={isOnline}
249
+ fetchErrors={fetchErrors}
250
+ textLoading={textLoading}
251
+ textContent={textContent}
252
+ zoomLevel={zoomLevel}
253
+ ext={ext}
254
+ retryFetch={retryFetch}
255
+ setError={setError}
256
+ />
257
+ </div>
318
258
  </div>
319
259
  );
320
260
 
321
- // --- Rnd-based unified window ---
322
- const rndPosition = showAsDock
323
- ? { x: windowWidth - dockWidth, y: 0 }
324
- : {
325
- x:
326
- floatingState.x ??
327
- Math.max(0, (windowWidth - (floatingState.width ?? 720)) / 2),
328
- y:
329
- floatingState.y ??
330
- Math.max(0, window.innerHeight - (floatingState.height ?? 500) - 20),
331
- };
332
-
333
- const rndSize = showAsDock
334
- ? { width: dockWidth, height: window.innerHeight }
335
- : {
336
- width: floatingState.width ?? 800,
337
- height: floatingState.height ?? 600,
338
- };
339
-
340
- const rndEnableResizing = showAsDock
261
+ // Rnd Configuration
262
+ const rndEnableResizing = isDockMode
263
+ ? { left: true }
264
+ : showAsPeek
265
+ ? { top: true }
266
+ : true;
267
+ const rndResizeHandleStyles = showAsPeek
341
268
  ? {
342
- left: true,
343
- right: false,
344
- top: false,
345
- bottom: false,
346
- topLeft: false,
347
- topRight: false,
348
- bottomLeft: false,
349
- bottomRight: false,
269
+ top: {
270
+ height: "24px",
271
+ top: "-12px",
272
+ cursor: "row-resize",
273
+ zIndex: 100,
274
+ },
350
275
  }
351
- : true; // All directions enabled for floating
276
+ : isDockMode
277
+ ? { left: { width: "20px", left: "-10px" } }
278
+ : isPipMode
279
+ ? {
280
+ bottom: { height: "20px", bottom: "-10px" },
281
+ right: { width: "20px", right: "-10px" },
282
+ left: { width: "20px", left: "-10px" },
283
+ top: { height: "20px", top: "-10px" },
284
+ bottomRight: {
285
+ width: "30px",
286
+ height: "30px",
287
+ bottom: "-15px",
288
+ right: "-15px",
289
+ },
290
+ bottomLeft: {
291
+ width: "30px",
292
+ height: "30px",
293
+ bottom: "-15px",
294
+ left: "-15px",
295
+ },
296
+ topRight: {
297
+ width: "30px",
298
+ height: "30px",
299
+ top: "-15px",
300
+ right: "-15px",
301
+ },
302
+ topLeft: {
303
+ width: "30px",
304
+ height: "30px",
305
+ top: "-15px",
306
+ left: "-15px",
307
+ },
308
+ }
309
+ : {};
310
+
311
+ const rndMinWidth = isDockMode ? 380 : showAsPeek ? windowWidth : 380;
312
+ const rndMinHeight = showAsPeek ? 150 : isDockMode ? undefined : 60;
313
+ const rndMaxWidth = isDockMode
314
+ ? windowWidth * 0.8
315
+ : showAsPeek
316
+ ? windowWidth
317
+ : undefined;
318
+ const rndMaxHeight = showAsPeek ? layout.vh * 0.85 : undefined;
352
319
 
353
320
  return createPortal(
354
321
  <AnimatePresence>
355
322
  {isOpen && (
356
323
  <motion.div
357
324
  id="pv-viewer"
358
- data-mode={isDocked ? "dock" : "popup"}
359
- className={`
360
- ${styles.previewSystem}
361
- ${showAsPeek ? styles.modePeek : ""}
362
- ${showAsDock ? styles.modeDock : ""}
363
- ${showAsFloating ? styles.modeFloating : ""}
364
- `}
325
+ data-mode={mode}
326
+ className={`${styles.previewSystem} ${showAsPeek ? styles.modePeek : ""} ${isDockMode ? styles.modeDock : ""} ${isPipMode ? styles.modePip : ""} ${isPopupMode ? styles.modePopup : ""}`}
365
327
  initial={{ opacity: 0 }}
366
328
  animate={{ opacity: 1 }}
367
329
  exit={{ opacity: 0 }}
368
330
  transition={{ duration: 0.2 }}
331
+ onWheel={(e) => e.stopPropagation()}
369
332
  >
370
- {showAsPeek ? (
371
- /* Mobile peek: simple fixed bottom sheet */
333
+ {isPopupMode && (
334
+ <div className={styles.previewBackdrop} onClick={closePreview} />
335
+ )}
336
+
337
+ {isPopupMode ? (
372
338
  <motion.div
339
+ key="desktop-popup"
373
340
  className={styles.windowFrame}
374
- initial={{ y: "100%" }}
375
- animate={{ y: 0 }}
376
- exit={{ y: "100%" }}
341
+ initial={{ opacity: 0, scale: 0.9, y: "-45%", x: "-50%" }}
342
+ animate={{ opacity: 1, scale: 1, y: "-50%", x: "-50%" }}
343
+ exit={{ opacity: 0, scale: 0.9, y: "-45%", x: "-50%" }}
377
344
  transition={{ type: "spring", damping: 25, stiffness: 300 }}
378
345
  onClick={(e) => e.stopPropagation()}
379
346
  >
380
- <div className={styles.peekHandle} />
381
347
  <div className={styles.dragHandleWrapper}>{header}</div>
382
348
  {innerContent}
383
349
  </motion.div>
384
350
  ) : (
385
- /* Desktop: Rnd-powered window for both floating and docked */
386
351
  <Rnd
387
- position={rndPosition}
388
- size={rndSize}
389
- disableDragging={showAsDock}
352
+ key={`${mode}-${showAsPeek}`}
353
+ position={layout.rndPosition}
354
+ size={layout.rndSize}
355
+ disableDragging={isDockMode || showAsPeek}
390
356
  enableResizing={rndEnableResizing}
391
357
  dragHandleClassName={styles.dragHandleWrapper}
392
- minWidth={showAsDock ? 380 : 380}
393
- minHeight={showAsDock ? undefined : 350}
394
- maxWidth={showAsDock ? window.innerWidth * 0.8 : undefined}
395
- bounds="window"
358
+ minWidth={rndMinWidth}
359
+ minHeight={rndMinHeight}
360
+ maxWidth={rndMaxWidth}
361
+ maxHeight={rndMaxHeight}
362
+ bounds={layout.rndBounds}
363
+ resizeHandleStyles={rndResizeHandleStyles}
396
364
  onDragStart={() => setIsInteracting(true)}
397
365
  onDragStop={(e, d) => {
398
366
  setIsInteracting(false);
399
- if (!showAsDock) {
367
+ if (!isDockMode && !showAsPeek)
400
368
  setFloatingState({ x: d.x, y: d.y });
401
- }
402
369
  }}
403
370
  onResizeStart={() => setIsInteracting(true)}
404
371
  onResizeStop={(e, direction, ref, delta, position) => {
405
372
  setIsInteracting(false);
406
373
  const newWidth = parseInt(ref.style.width, 10);
407
374
  const newHeight = parseInt(ref.style.height, 10);
408
- if (showAsDock) {
409
- setDockWidth(newWidth);
410
- } else {
375
+ if (isDockMode) setDockWidth(newWidth);
376
+ else if (showAsPeek) setPeekHeight(newHeight);
377
+ else
411
378
  setFloatingState({
412
379
  width: newWidth,
413
380
  height: newHeight,
414
381
  ...position,
415
382
  });
416
- }
417
383
  }}
418
384
  className={styles.rndWrapper}
419
385
  style={{
@@ -424,9 +390,7 @@ export default function PreviewViewer() {
424
390
  }}
425
391
  >
426
392
  <div
427
- className={`${styles.windowFrame} ${
428
- isInteracting ? styles.windowInteracting : ""
429
- }`}
393
+ className={`${styles.windowFrame} ${isInteracting ? styles.windowInteracting : ""}`}
430
394
  style={{ width: "100%", height: "100%", position: "relative" }}
431
395
  onClick={(e) => e.stopPropagation()}
432
396
  >