@kawaiininja/layouts 2.1.0 → 3.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.
- package/dist/MobileLayout.js +95 -241
- package/dist/PcLayout.js +55 -201
- package/dist/animations/index.d.ts +1 -0
- package/dist/animations/index.js +1 -0
- package/dist/animations/layout.d.ts +4 -0
- package/dist/animations/layout.js +83 -0
- package/dist/components/Header.d.ts +9 -0
- package/dist/components/Header.js +7 -0
- package/dist/components/HorizontalTabs.d.ts +9 -0
- package/dist/components/HorizontalTabs.js +5 -0
- package/dist/components/QuickMenu.d.ts +12 -0
- package/dist/components/QuickMenu.js +11 -0
- package/dist/components/RailNavButton.d.ts +16 -0
- package/dist/components/RailNavButton.js +8 -0
- package/dist/components/RailNavIndicator.d.ts +6 -0
- package/dist/components/RailNavIndicator.js +2 -0
- package/dist/components/RailNavThemeToggle.d.ts +5 -0
- package/dist/components/RailNavThemeToggle.js +11 -0
- package/dist/{ThemeContext.d.ts → contexts/ThemeContext.d.ts} +0 -4
- package/dist/{ThemeContext.js → contexts/ThemeContext.js} +1 -7
- package/dist/hooks/useDrawerStack.d.ts +12 -0
- package/dist/hooks/useDrawerStack.js +39 -0
- package/dist/hooks/useOnyxNavigation.d.ts +27 -0
- package/dist/hooks/useOnyxNavigation.js +98 -0
- package/dist/hooks/useOverlayHistory.d.ts +14 -0
- package/dist/hooks/useOverlayHistory.js +151 -0
- package/dist/hooks/useRailNavScroll.d.ts +10 -0
- package/dist/hooks/useRailNavScroll.js +45 -0
- package/dist/hooks/useTheme.d.ts +4 -0
- package/dist/hooks/useTheme.js +8 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +10 -3
- package/dist/library/Library.d.ts +3 -0
- package/dist/library/Library.js +145 -0
- package/dist/library/LibraryDetail.d.ts +17 -0
- package/dist/library/LibraryDetail.js +59 -0
- package/dist/library/LibraryHome.d.ts +20 -0
- package/dist/library/LibraryHome.js +136 -0
- package/dist/library/MediaPreview.d.ts +7 -0
- package/dist/library/MediaPreview.js +56 -0
- package/dist/library/details/DetailActions.d.ts +8 -0
- package/dist/library/details/DetailActions.js +11 -0
- package/dist/library/details/DetailApiReference.d.ts +7 -0
- package/dist/library/details/DetailApiReference.js +7 -0
- package/dist/library/details/DetailDocumentation.d.ts +8 -0
- package/dist/library/details/DetailDocumentation.js +9 -0
- package/dist/library/details/DetailFooterCTA.d.ts +10 -0
- package/dist/library/details/DetailFooterCTA.js +17 -0
- package/dist/library/details/DetailHero.d.ts +14 -0
- package/dist/library/details/DetailHero.js +69 -0
- package/dist/library/details/DetailInstallation.d.ts +9 -0
- package/dist/library/details/DetailInstallation.js +7 -0
- package/dist/library/details/DetailMetadata.d.ts +7 -0
- package/dist/library/details/DetailMetadata.js +25 -0
- package/dist/library/details/DetailNavbar.d.ts +13 -0
- package/dist/library/details/DetailNavbar.js +13 -0
- package/dist/library/details/DetailPolicy.d.ts +7 -0
- package/dist/library/details/DetailPolicy.js +10 -0
- package/dist/library/details/DetailSidebar.d.ts +9 -0
- package/dist/library/details/DetailSidebar.js +29 -0
- package/dist/library/details/DetailUsage.d.ts +7 -0
- package/dist/library/details/DetailUsage.js +8 -0
- package/dist/library/details/DetailUtils.d.ts +3 -0
- package/dist/library/details/DetailUtils.js +67 -0
- package/dist/library/index.d.ts +2 -0
- package/dist/library/index.js +2 -0
- package/dist/{asset-store → library}/types.d.ts +29 -4
- package/dist/library/ui/Badge.d.ts +8 -0
- package/dist/library/ui/Badge.js +11 -0
- package/dist/library/ui/GlyphDetailView.d.ts +14 -0
- package/dist/library/ui/GlyphDetailView.js +80 -0
- package/dist/library/ui/Skeleton.d.ts +1 -0
- package/dist/library/ui/Skeleton.js +2 -0
- package/dist/library/ui/SyntaxHighlighter.d.ts +6 -0
- package/dist/library/ui/SyntaxHighlighter.js +104 -0
- package/dist/types/drawer.d.ts +9 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +4 -0
- package/dist/types/layout.d.ts +28 -0
- package/dist/types/layout.js +1 -0
- package/dist/types/navigation.d.ts +28 -0
- package/dist/types/navigation.js +1 -0
- package/dist/types/user.d.ts +5 -0
- package/dist/types/user.js +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/layout.d.ts +19 -0
- package/dist/utils/layout.js +37 -0
- package/package.json +9 -5
- package/dist/asset-store/AssetStore.d.ts +0 -2
- package/dist/asset-store/AssetStore.js +0 -334
- package/dist/asset-store/index.d.ts +0 -2
- package/dist/asset-store/index.js +0 -2
- package/dist/types.d.ts +0 -63
- /package/dist/{asset-store → library}/types.js +0 -0
- /package/dist/{types.js → types/drawer.js} +0 -0
package/dist/MobileLayout.js
CHANGED
|
@@ -1,139 +1,38 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { AnimatePresence, motion } from "framer-motion";
|
|
3
|
-
import { Loader2, LogOut
|
|
4
|
-
import React, { useEffect,
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return (_jsxs("aside", { className: "fixed right-20 z-[100] min-w-[180px] bg-[rgb(var(--bg-elevated))]/95 backdrop-blur-xl rounded-2xl shadow-2xl p-2 border border-[rgb(var(--color-border))] flex flex-col gap-1 transition-all duration-200 ease-out origin-right", style: {
|
|
19
|
-
top: positionY,
|
|
20
|
-
transform: "translateY(-50%) scale(1)",
|
|
21
|
-
animation: "popIn 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275)",
|
|
22
|
-
}, children: [items.map((item, idx) => (_jsxs("button", { className: `flex items-center gap-3 p-3 rounded-xl transition-all duration-150 w-full text-left ${selectedIndex === idx
|
|
23
|
-
? "bg-[rgb(var(--color-accent))] text-white scale-105 shadow-lg"
|
|
24
|
-
: "text-[rgb(var(--text-secondary))] hover:bg-[rgb(var(--bg-tertiary))]"}`, children: [item.icon && _jsx(item.icon, { size: 18 }), _jsx("span", { className: "text-sm font-medium", children: item.label })] }, idx))), _jsx("div", { className: "absolute top-1/2 -right-2 w-4 h-4 bg-[rgb(var(--bg-elevated))]/95 rotate-45 border-r border-t border-[rgb(var(--color-border))] -translate-y-1/2" }), _jsx("style", { children: `
|
|
25
|
-
@keyframes popIn {
|
|
26
|
-
from { opacity: 0; transform: translateY(-50%) scale(0.8) translateX(20px); }
|
|
27
|
-
to { opacity: 1; transform: translateY(-50%) scale(1) translateX(0); }
|
|
28
|
-
}
|
|
29
|
-
` })] }));
|
|
30
|
-
};
|
|
31
|
-
const Header = ({ title, onMenuClick, rightAction }) => (_jsxs("header", { className: "sticky top-0 z-20 bg-[rgb(var(--bg-surface))]/90 backdrop-blur-md border-b border-[rgb(var(--color-border-subtle))] px-4 pt-[calc(env(safe-area-inset-top,0px)+12px)] pb-3 flex justify-between items-center shadow-sm select-none", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("button", { onClick: onMenuClick, className: "p-1 -ml-1 text-[rgb(var(--text-secondary))] active:text-[rgb(var(--text-primary))] touch-manipulation", children: _jsx(Menu, { size: 24 }) }), _jsx("h1", { className: "text-xl font-bold text-[rgb(var(--text-primary))] tracking-tight", children: title })] }), rightAction] }));
|
|
32
|
-
const HorizontalTabs = ({ tabs, active, onChange }) => (_jsx("div", { className: "w-full bg-[rgb(var(--bg-main))] border-b border-[rgb(var(--color-border-subtle))] z-10 shrink-0", children: _jsx("div", { className: "flex px-4 gap-6 overflow-x-auto no-scrollbar", children: tabs.map((tab) => (_jsxs("button", { onClick: () => onChange(tab.label), className: `relative py-3 flex items-center gap-2 text-sm font-medium transition-colors whitespace-nowrap ${active === tab.label
|
|
33
|
-
? "text-[rgb(var(--text-primary))]"
|
|
34
|
-
: "text-[rgb(var(--text-muted))]"}`, children: [tab.icon && _jsx(tab.icon, { size: 16 }), tab.label, active === tab.label && (_jsx(motion.div, { layoutId: "activeTabIndicator", className: "absolute bottom-0 left-0 right-0 h-0.5 bg-[rgb(var(--color-accent))]", transition: { type: "spring", stiffness: 300, damping: 30 } }))] }, tab.label))) }) }));
|
|
35
|
-
// --- Hook: useRailNavScroll ---
|
|
36
|
-
function useRailNavScroll(active, navKeys) {
|
|
37
|
-
const activeIndex = Math.max(0, navKeys.indexOf(active));
|
|
38
|
-
const scrollContainerRef = useRef(null);
|
|
39
|
-
const buttonRefs = useRef([]);
|
|
40
|
-
const [measurements, setMeasurements] = useState({
|
|
41
|
-
heights: [],
|
|
42
|
-
offsets: [],
|
|
43
|
-
});
|
|
44
|
-
useLayoutEffect(() => {
|
|
45
|
-
const heights = buttonRefs.current.map((btn) => btn?.offsetHeight || 0);
|
|
46
|
-
let currentOffset = 0;
|
|
47
|
-
const offsets = [];
|
|
48
|
-
for (let i = 0; i < heights.length; i++) {
|
|
49
|
-
offsets.push(currentOffset);
|
|
50
|
-
currentOffset += heights[i];
|
|
51
|
-
}
|
|
52
|
-
setMeasurements({ heights, offsets });
|
|
53
|
-
}, [navKeys]);
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
if (!scrollContainerRef.current ||
|
|
56
|
-
measurements.offsets.length === 0 ||
|
|
57
|
-
measurements.offsets.length <= activeIndex)
|
|
58
|
-
return;
|
|
59
|
-
const container = scrollContainerRef.current;
|
|
60
|
-
const targetY = measurements.offsets[activeIndex];
|
|
61
|
-
const targetHeight = measurements.heights[activeIndex];
|
|
62
|
-
const paddingOffset = 24;
|
|
63
|
-
const absoluteTargetY = targetY + paddingOffset;
|
|
64
|
-
const scrollTarget = absoluteTargetY - container.clientHeight / 2 + targetHeight / 2;
|
|
65
|
-
container.scrollTo({ top: scrollTarget, behavior: "smooth" });
|
|
66
|
-
}, [activeIndex, measurements]);
|
|
67
|
-
const registerButtonRef = (index, el) => {
|
|
68
|
-
buttonRefs.current[index] = el;
|
|
69
|
-
};
|
|
70
|
-
return {
|
|
71
|
-
scrollContainerRef,
|
|
72
|
-
activeHeight: measurements.heights[activeIndex] || 0,
|
|
73
|
-
activeOffset: measurements.offsets[activeIndex] || 0,
|
|
74
|
-
registerButtonRef,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
3
|
+
import { Loader2, LogOut } from "lucide-react";
|
|
4
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
5
|
+
import { mobileLayoutVariants } from "./animations";
|
|
6
|
+
import { Header } from "./components/Header";
|
|
7
|
+
import { HorizontalTabs } from "./components/HorizontalTabs";
|
|
8
|
+
import { QuickMenu } from "./components/QuickMenu";
|
|
9
|
+
import { RailNavButton } from "./components/RailNavButton";
|
|
10
|
+
import { RailNavIndicator } from "./components/RailNavIndicator";
|
|
11
|
+
import { RailNavThemeToggle } from "./components/RailNavThemeToggle";
|
|
12
|
+
import { ThemeProvider } from "./contexts/ThemeContext";
|
|
13
|
+
import { useDrawerStack } from "./hooks/useDrawerStack";
|
|
14
|
+
import { useOnyxNavigation } from "./hooks/useOnyxNavigation";
|
|
15
|
+
import { useOverlayHistory } from "./hooks/useOverlayHistory";
|
|
16
|
+
import { useRailNavScroll } from "./hooks/useRailNavScroll";
|
|
17
|
+
import { checkRefreshing, clamp, getUiZoom, validateLayoutProps, } from "./utils";
|
|
77
18
|
// --- Main Layout Component ---
|
|
78
19
|
const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh, isRefreshing: externalRefreshing, rightAction, initialTab = "home", activeTab: externalActiveTab, activeSubTab: externalActiveSubTab, onNavigate, drawerItems = [], }) => {
|
|
79
20
|
// --- Safety Validations ---
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
21
|
+
validateLayoutProps(tabs, user, "OnyxMobileLayout");
|
|
22
|
+
const { drawerStack, activeDrawer, handleOpenDrawer, handleBackDrawer, handleCloseDrawers, setDrawerStack, } = useDrawerStack();
|
|
23
|
+
const { activeTab, activeSubTab, currentTabConfig, subTabs, navigateTo, motionData, navKeys, } = useOnyxNavigation({
|
|
24
|
+
tabs,
|
|
25
|
+
initialTab,
|
|
26
|
+
externalActiveTab,
|
|
27
|
+
externalActiveSubTab,
|
|
28
|
+
onNavigate,
|
|
29
|
+
});
|
|
88
30
|
const [isOpen, setIsOpen] = useState(false);
|
|
89
31
|
const [isDragging, setIsDragging] = useState(false);
|
|
90
|
-
const [activeDrawer, setActiveDrawer] = useState(null);
|
|
91
32
|
const [pullY, setPullY] = useState(0);
|
|
92
33
|
const [internalRefreshing, setInternalRefreshing] = useState(false);
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const [internalSubTab, setInternalSubTab] = useState(subTabs[0]?.label || "");
|
|
96
|
-
const subTab = externalActiveSubTab || internalSubTab;
|
|
97
|
-
const navigateTo = (tabId, subTabLabel) => {
|
|
98
|
-
let nextTab = tabId || activeTab;
|
|
99
|
-
let nextSub = subTabLabel || subTab;
|
|
100
|
-
if (tabId) {
|
|
101
|
-
setInternalActiveTab(tabId);
|
|
102
|
-
const targetTab = tabs.find((t) => t.id === tabId);
|
|
103
|
-
const isValid = (targetTab?.subTabs || []).some((st) => st.label === nextSub);
|
|
104
|
-
if (!isValid && targetTab?.subTabs?.[0]) {
|
|
105
|
-
nextSub = targetTab.subTabs[0].label;
|
|
106
|
-
setInternalSubTab(nextSub);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
if (subTabLabel) {
|
|
110
|
-
nextSub = subTabLabel;
|
|
111
|
-
setInternalSubTab(subTabLabel);
|
|
112
|
-
}
|
|
113
|
-
if (onNavigate) {
|
|
114
|
-
const targetTab = tabs.find((t) => t.id === nextTab);
|
|
115
|
-
let path = targetTab?.path || "";
|
|
116
|
-
const st = (targetTab?.subTabs || []).find((s) => s.label === nextSub);
|
|
117
|
-
if (st?.path) {
|
|
118
|
-
if (st.path.startsWith("/")) {
|
|
119
|
-
path = st.path;
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
const base = path.endsWith("/") ? path : path + "/";
|
|
123
|
-
path = base + st.path;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (path)
|
|
127
|
-
onNavigate(path);
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
const currentSubTabConfig = subTabs.find((st) => st.label === subTab);
|
|
131
|
-
const isRefreshing = !!(currentSubTabConfig?.isRefreshing ||
|
|
132
|
-
currentTabConfig?.isRefreshing ||
|
|
133
|
-
externalRefreshing ||
|
|
134
|
-
internalRefreshing);
|
|
135
|
-
const navKeys = useMemo(() => tabs.map((t) => t.id), [tabs]);
|
|
136
|
-
const { scrollContainerRef, activeHeight, activeOffset, registerButtonRef } = useRailNavScroll(activeTab, navKeys);
|
|
34
|
+
const isRefreshing = checkRefreshing(subTabs, activeSubTab, currentTabConfig, externalRefreshing, internalRefreshing);
|
|
35
|
+
const { scrollContainerRef, activeHeight, activeOffset, registerButtonRef } = useRailNavScroll(activeTab, navKeys, 24);
|
|
137
36
|
const DRAWER_WIDTH = 220;
|
|
138
37
|
const startX = useRef(0);
|
|
139
38
|
const startY = useRef(0);
|
|
@@ -156,112 +55,47 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
156
55
|
const holdTimer = useRef(null);
|
|
157
56
|
const startYRef = useRef(0);
|
|
158
57
|
const isDraggingMenu = useRef(false);
|
|
159
|
-
const isClosingViaBack = useRef(false);
|
|
160
58
|
const isAnyOverlayOpen = isOpen || !!activeDrawer;
|
|
59
|
+
console.log("[OnyxMobileLayout] Render State:", {
|
|
60
|
+
isOpen,
|
|
61
|
+
activeDrawer,
|
|
62
|
+
isAnyOverlayOpen,
|
|
63
|
+
stackLength: drawerStack.length,
|
|
64
|
+
activeTab,
|
|
65
|
+
activeSubTab,
|
|
66
|
+
});
|
|
161
67
|
// Handle Hardware Back Button for Drawer and Active Panels
|
|
162
|
-
|
|
163
|
-
|
|
68
|
+
const lastBackTime = useRef(0);
|
|
69
|
+
const handleOverlayBack = React.useCallback(() => {
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const timeSinceLastCall = now - lastBackTime.current;
|
|
72
|
+
// Debounce: Ignore rapid duplicate calls (e.g., from event propagation)
|
|
73
|
+
if (timeSinceLastCall < 150 && lastBackTime.current !== 0) {
|
|
74
|
+
console.warn(`[OnyxMobileLayout] ⚠️ Ignoring duplicate onBack (${timeSinceLastCall}ms since last call)`, "\nTip: Add e.stopPropagation() to your drawer close button click handler");
|
|
164
75
|
return;
|
|
165
|
-
const marker = "onyx-overlay-" + Math.random().toString(36).substring(7);
|
|
166
|
-
window.history.pushState({ onyxMarker: marker }, "");
|
|
167
|
-
const onPopState = () => {
|
|
168
|
-
isClosingViaBack.current = true;
|
|
169
|
-
setIsOpen(false);
|
|
170
|
-
setActiveDrawer(null);
|
|
171
|
-
};
|
|
172
|
-
window.addEventListener("popstate", onPopState);
|
|
173
|
-
return () => {
|
|
174
|
-
window.removeEventListener("popstate", onPopState);
|
|
175
|
-
if (!isClosingViaBack.current) {
|
|
176
|
-
if (window.history.state?.onyxMarker === marker) {
|
|
177
|
-
window.history.back();
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
isClosingViaBack.current = false;
|
|
181
|
-
};
|
|
182
|
-
}, [isAnyOverlayOpen]);
|
|
183
|
-
// Navigation Logic
|
|
184
|
-
useEffect(() => {
|
|
185
|
-
if (currentTabConfig && currentTabConfig.subTabs.length > 0) {
|
|
186
|
-
const isValid = currentTabConfig.subTabs.some((st) => st.label === subTab);
|
|
187
|
-
if (!isValid && currentTabConfig.subTabs.length > 0)
|
|
188
|
-
navigateTo(undefined, currentTabConfig.subTabs[0].label);
|
|
189
76
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const nextIdx = navKeys.indexOf(activeTab);
|
|
201
|
-
setMotionData({
|
|
202
|
-
tab: activeTab,
|
|
203
|
-
sub: subTab,
|
|
204
|
-
dir: nextIdx > prevIdx ? 1 : -1,
|
|
205
|
-
type: "tab",
|
|
77
|
+
lastBackTime.current = now;
|
|
78
|
+
console.log("[OnyxMobileLayout] Overlay onBack triggered (Hardware/PopState)");
|
|
79
|
+
setDrawerStack((prev) => {
|
|
80
|
+
if (prev.length > 0) {
|
|
81
|
+
console.log("[OnyxMobileLayout] Popping drawer stack, new length:", prev.length - 1);
|
|
82
|
+
return prev.slice(0, -1);
|
|
83
|
+
}
|
|
84
|
+
console.log("[OnyxMobileLayout] No drawers left, closing main drawer");
|
|
85
|
+
setIsOpen(false);
|
|
86
|
+
return prev;
|
|
206
87
|
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
type: "subtab",
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
// Just sync without directional animation if it's an jump/reset
|
|
223
|
-
setMotionData({ ...motionData, sub: subTab, dir: 0 });
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
// Animation variants
|
|
227
|
-
const variants = {
|
|
228
|
-
enter: (d) => ({
|
|
229
|
-
x: d.type === "subtab" ? (d.dir > 0 ? 80 : -80) : 0,
|
|
230
|
-
y: d.type === "tab" ? (d.dir > 0 ? "100%" : "-100%") : 0,
|
|
231
|
-
scale: d.type === "subtab" ? 0.95 : 1,
|
|
232
|
-
scaleY: d.type === "tab" ? 0.7 : 1,
|
|
233
|
-
skewY: d.type === "tab" ? (d.dir > 0 ? 15 : -15) : 0,
|
|
234
|
-
opacity: 0,
|
|
235
|
-
}),
|
|
236
|
-
center: {
|
|
237
|
-
x: 0,
|
|
238
|
-
y: 0,
|
|
239
|
-
scale: 1,
|
|
240
|
-
scaleY: 1,
|
|
241
|
-
skewY: 0,
|
|
242
|
-
opacity: 1,
|
|
243
|
-
transition: {
|
|
244
|
-
duration: 0.5,
|
|
245
|
-
ease: [0.16, 1, 0.3, 1],
|
|
246
|
-
scaleY: {
|
|
247
|
-
values: [0.7, 1.15, 1],
|
|
248
|
-
times: [0, 0.5, 1],
|
|
249
|
-
duration: 0.5,
|
|
250
|
-
ease: [0.16, 1, 0.3, 1],
|
|
251
|
-
},
|
|
252
|
-
skewY: { duration: 0.4, ease: [0.16, 1, 0.3, 1] },
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
exit: (d) => ({
|
|
256
|
-
x: d.type === "subtab" ? (d.dir > 0 ? -80 : 80) : 0,
|
|
257
|
-
y: d.type === "tab" ? (d.dir > 0 ? "-100%" : "100%") : 0,
|
|
258
|
-
scale: d.type === "subtab" ? 1.05 : 1,
|
|
259
|
-
scaleY: d.type === "tab" ? 1.2 : 1,
|
|
260
|
-
skewY: d.type === "tab" ? (d.dir > 0 ? -10 : 10) : 0,
|
|
261
|
-
opacity: 0,
|
|
262
|
-
transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1] },
|
|
263
|
-
}),
|
|
264
|
-
};
|
|
88
|
+
}, [setDrawerStack]);
|
|
89
|
+
const handleOverlayClose = React.useCallback(() => {
|
|
90
|
+
console.log("[OnyxMobileLayout] Overlay onClose triggered (Direct Close)");
|
|
91
|
+
setIsOpen(false);
|
|
92
|
+
}, []);
|
|
93
|
+
const depth = (isOpen ? 1 : 0) + drawerStack.length;
|
|
94
|
+
useOverlayHistory({
|
|
95
|
+
depth,
|
|
96
|
+
onBack: handleOverlayBack,
|
|
97
|
+
platform: "mobile",
|
|
98
|
+
});
|
|
265
99
|
const Drawer = () => (_jsxs("aside", { className: "fixed inset-y-0 left-0 w-[220px] bg-[var(--drawer-bg,rgb(18,18,18))] text-[var(--drawer-text,rgb(255,255,255))] z-0 flex flex-col pt-12 pb-6 px-6 overflow-hidden", children: [_jsxs("div", { className: "mb-8 pl-2", children: [_jsx("div", { className: "w-16 h-16 rounded-full bg-gradient-to-tr from-[rgb(var(--color-secondary))] to-[rgb(var(--color-accent))] mb-4 p-0.5", children: _jsx("img", { src: user.avatar, className: "w-full h-full rounded-full object-cover border-2 border-[rgb(var(--bg-main))]" }) }), _jsx("h2", { className: "text-xl font-bold whitespace-nowrap", children: user.name }), _jsx("p", { className: "text-sm text-[rgb(var(--text-muted))]", children: user.handle })] }), _jsx("div", { className: "flex-1 space-y-1", children: (drawerItems.length > 0
|
|
266
100
|
? drawerItems
|
|
267
101
|
: (tabs[0]?.subTabs || []).map((st) => ({
|
|
@@ -273,12 +107,12 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
273
107
|
navigateTo(item.targetTab, item.targetSubTab);
|
|
274
108
|
if (item.onClick)
|
|
275
109
|
item.onClick({
|
|
276
|
-
openDrawer: (id) =>
|
|
110
|
+
openDrawer: (id) => handleOpenDrawer(id),
|
|
277
111
|
});
|
|
278
112
|
setIsOpen(false);
|
|
279
113
|
}, className: "w-full flex items-center gap-4 p-3 text-[rgb(var(--text-muted))] hover:text-[rgb(var(--text-primary))] hover:bg-[rgb(var(--bg-tertiary))] rounded-xl transition-all group font-medium", children: [_jsx(item.icon, { size: 20, className: "group-hover:scale-110 transition-transform" }), _jsx("span", { children: item.label })] }, item.label))) }), _jsx("div", { className: "mt-auto", children: _jsxs("button", { onClick: onSignOut, className: "flex items-center gap-3 text-[rgb(var(--status-error))] font-medium p-3 w-full hover:bg-[rgb(var(--bg-tertiary))] rounded-xl transition-colors", children: [_jsx(LogOut, { size: 20 }), _jsx("span", { children: "Sign Out" })] }) })] }));
|
|
280
114
|
const handleTouchStart = (e) => {
|
|
281
|
-
const zoom =
|
|
115
|
+
const zoom = getUiZoom();
|
|
282
116
|
startX.current = e.touches[0].clientX / zoom;
|
|
283
117
|
startY.current = e.touches[0].clientY / zoom;
|
|
284
118
|
dragXRef.current = isOpen ? DRAWER_WIDTH : 0;
|
|
@@ -291,13 +125,13 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
291
125
|
const handleTouchMove = (e) => {
|
|
292
126
|
if (!isGesturing.current || quickMenu.visible || isDraggingMenu.current)
|
|
293
127
|
return;
|
|
294
|
-
const zoom =
|
|
128
|
+
const zoom = getUiZoom();
|
|
295
129
|
const dx = e.touches[0].clientX / zoom - startX.current;
|
|
296
130
|
const dy = e.touches[0].clientY / zoom - startY.current;
|
|
297
131
|
lastDxRef.current = dx;
|
|
298
132
|
if (!isHorizontal.current && !isVerticalPull.current && !isDragging) {
|
|
299
133
|
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 10) {
|
|
300
|
-
if (!isOpen && dx > 0 && subTabs[0]?.label ===
|
|
134
|
+
if (!isOpen && dx > 0 && subTabs[0]?.label === activeSubTab) {
|
|
301
135
|
isHorizontal.current = true;
|
|
302
136
|
setIsDragging(true);
|
|
303
137
|
}
|
|
@@ -313,7 +147,7 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
313
147
|
}
|
|
314
148
|
}
|
|
315
149
|
if (isHorizontal.current) {
|
|
316
|
-
const newX =
|
|
150
|
+
const newX = clamp((isOpen ? DRAWER_WIDTH : 0) + dx, 0, DRAWER_WIDTH);
|
|
317
151
|
dragXRef.current = newX;
|
|
318
152
|
setRenderDragX(newX);
|
|
319
153
|
}
|
|
@@ -335,7 +169,7 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
335
169
|
if (isTabSwipe.current) {
|
|
336
170
|
isTabSwipe.current = false;
|
|
337
171
|
if (Math.abs(lastDxRef.current) > 50) {
|
|
338
|
-
const idx = subTabs.findIndex((t) => t.label ===
|
|
172
|
+
const idx = subTabs.findIndex((t) => t.label === activeSubTab);
|
|
339
173
|
if (lastDxRef.current < 0 && idx < subTabs.length - 1)
|
|
340
174
|
navigateTo(undefined, subTabs[idx + 1].label);
|
|
341
175
|
else if (lastDxRef.current > 0 && idx > 0)
|
|
@@ -344,7 +178,7 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
344
178
|
}
|
|
345
179
|
if (isVerticalPull.current) {
|
|
346
180
|
if (pullY > 60) {
|
|
347
|
-
const currentSubTab = subTabs.find((st) => st.label ===
|
|
181
|
+
const currentSubTab = subTabs.find((st) => st.label === activeSubTab);
|
|
348
182
|
const refreshHandler = currentSubTab?.onRefresh || currentTabConfig?.onRefresh || onRefresh;
|
|
349
183
|
if (refreshHandler) {
|
|
350
184
|
const result = refreshHandler();
|
|
@@ -429,7 +263,7 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
429
263
|
const ITEM_HEIGHT = 48;
|
|
430
264
|
const offset = (quickMenu.items.length - 1) / 2;
|
|
431
265
|
const idx = Math.round(diff / ITEM_HEIGHT + offset);
|
|
432
|
-
const clampedIdx =
|
|
266
|
+
const clampedIdx = clamp(idx, 0, quickMenu.items.length - 1);
|
|
433
267
|
setQuickMenu((prev) => ({ ...prev, selectedIndex: clampedIdx }));
|
|
434
268
|
};
|
|
435
269
|
const handleNavTouchEnd = (tabId) => {
|
|
@@ -439,7 +273,7 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
439
273
|
if (quickMenu.selectedIndex !== -1 &&
|
|
440
274
|
quickMenu.items[quickMenu.selectedIndex]?.onClick) {
|
|
441
275
|
quickMenu.items[quickMenu.selectedIndex].onClick({
|
|
442
|
-
openDrawer: (id) =>
|
|
276
|
+
openDrawer: (id) => handleOpenDrawer(id),
|
|
443
277
|
});
|
|
444
278
|
}
|
|
445
279
|
setQuickMenu((prev) => ({ ...prev, visible: false, selectedIndex: -1 }));
|
|
@@ -460,7 +294,7 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
460
294
|
? "none"
|
|
461
295
|
: "transform 0.3s cubic-bezier(0.32, 0.72, 0, 1), border-radius 0.3s",
|
|
462
296
|
overflow: "hidden",
|
|
463
|
-
}, children: [isOpen && !isDragging && (_jsx("div", { className: "absolute inset-0 z-50 bg-black/20", onClick: () => setIsOpen(false) })), _jsxs("div", { className: "flex-1 flex flex-col h-full mr-12 relative bg-[rgb(var(--bg-main))]", children: [_jsx(Header, { title: currentTabConfig?.navTitle || currentTabConfig?.label || "App", onMenuClick: () => setIsOpen(!isOpen), rightAction: currentTabConfig?.rightAction || rightAction }), _jsx(HorizontalTabs, { tabs: subTabs, active:
|
|
297
|
+
}, children: [isOpen && !isDragging && (_jsx("div", { className: "absolute inset-0 z-50 bg-black/20", onClick: () => setIsOpen(false) })), _jsxs("div", { className: "flex-1 flex flex-col h-full mr-12 relative bg-[rgb(var(--bg-main))]", children: [_jsx(Header, { title: currentTabConfig?.navTitle || currentTabConfig?.label || "App", onMenuClick: () => setIsOpen(!isOpen), rightAction: currentTabConfig?.rightAction || rightAction, isMobile: true }), _jsx(HorizontalTabs, { tabs: subTabs, active: activeSubTab, onChange: (label) => navigateTo(undefined, label), isMobile: true }), _jsxs("main", { ref: mainScrollRef, className: "flex-1 overflow-y-auto no-scrollbar overscroll-contain relative touch-pan-y", children: [_jsx("div", { className: "absolute top-0 left-0 right-0 flex justify-center items-center pointer-events-none z-0", style: {
|
|
464
298
|
height: `${pullY}px`,
|
|
465
299
|
opacity: Math.min(pullY / 40, 1),
|
|
466
300
|
transition: isVerticalPull.current
|
|
@@ -468,7 +302,7 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
468
302
|
: "height 0.4s cubic-bezier(0.19, 1, 0.22, 1), opacity 0.4s",
|
|
469
303
|
}, children: _jsx("div", { className: `p-2 bg-[rgb(var(--bg-surface))] rounded-full shadow-md border border-[rgb(var(--color-border-subtle))] ${isRefreshing || (pullY > 20 && !isGesturing.current)
|
|
470
304
|
? "animate-spin text-[rgb(var(--color-accent))]"
|
|
471
|
-
: "text-[rgb(var(--text-secondary))]"}`, children: _jsx(Loader2, { size: 20 }) }) }), _jsx(AnimatePresence, { mode: "popLayout", custom: motionData, initial: false, children: _jsx(motion.div, { custom: motionData, variants:
|
|
305
|
+
: "text-[rgb(var(--text-secondary))]"}`, children: _jsx(Loader2, { size: 20 }) }) }), _jsx(AnimatePresence, { mode: "popLayout", custom: motionData, initial: false, children: _jsx(motion.div, { custom: motionData, variants: mobileLayoutVariants, initial: "enter", animate: "center", exit: "exit", className: "w-full min-h-full origin-center", style: {
|
|
472
306
|
width: "100%",
|
|
473
307
|
willChange: "transform, opacity, filter",
|
|
474
308
|
}, children: _jsx("div", { style: {
|
|
@@ -478,8 +312,28 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
478
312
|
transition: isVerticalPull.current
|
|
479
313
|
? "none"
|
|
480
314
|
: "transform 0.3s ease-out",
|
|
481
|
-
}, children: subTabs.find((st) => st.label ===
|
|
482
|
-
React.createElement(subTabs.find((st) => st.label ===
|
|
315
|
+
}, children: subTabs.find((st) => st.label === activeSubTab)?.view &&
|
|
316
|
+
React.createElement(subTabs.find((st) => st.label === activeSubTab)
|
|
317
|
+
.view, {
|
|
318
|
+
onOpenDrawer: handleOpenDrawer,
|
|
319
|
+
openDrawer: handleOpenDrawer,
|
|
320
|
+
}) }) }, `${activeTab}-${activeSubTab}`) })] })] }), _jsxs("nav", { className: "absolute right-0 top-0 bottom-0 w-12 bg-[rgb(var(--bg-tertiary))] border-l border-[rgb(var(--color-border-subtle))] flex flex-col z-50 pb-[env(safe-area-inset-bottom,0px)]", children: [_jsxs("div", { ref: scrollContainerRef, className: "flex-1 overflow-y-auto no-scrollbar py-4 w-full relative", children: [_jsx(RailNavIndicator, { height: activeHeight, offset: activeOffset }), tabs.map((tab, idx) => (_jsx(RailNavButton, { title: tab.label, icon: tab.icon, active: activeTab === tab.id, onClick: () => handleNavTouchEnd(tab.id), onTouchStart: (e) => handleNavTouchStart(e, tab), onTouchMove: handleNavTouchMove, onTouchEnd: () => handleNavTouchEnd(tab.id), onRegisterRef: (el) => registerButtonRef(idx, el), isMobile: true }, tab.id)))] }), _jsx(RailNavThemeToggle, {})] })] }), _jsx(QuickMenu, { ...quickMenu, isMobile: true }), drawerStack.map((id, index) => {
|
|
321
|
+
const DrawerComp = drawers[id];
|
|
322
|
+
if (!DrawerComp)
|
|
323
|
+
return null;
|
|
324
|
+
const isTop = index === drawerStack.length - 1;
|
|
325
|
+
return (_jsx(DrawerComp, { isOpen: isTop, onClose: () => {
|
|
326
|
+
if (isTop) {
|
|
327
|
+
console.log("[OnyxMobileLayout] Drawer onClose Event:", id);
|
|
328
|
+
handleBackDrawer();
|
|
329
|
+
}
|
|
330
|
+
}, onBack: () => {
|
|
331
|
+
if (isTop) {
|
|
332
|
+
console.log("[OnyxMobileLayout] Drawer onBack Event:", id);
|
|
333
|
+
handleOverlayBack();
|
|
334
|
+
}
|
|
335
|
+
}, onOpenDrawer: handleOpenDrawer, openDrawer: handleOpenDrawer }, `${id}-${index}`));
|
|
336
|
+
}), _jsx("style", { children: `
|
|
483
337
|
.no-scrollbar::-webkit-scrollbar { display: none; }
|
|
484
338
|
.touch-manipulation { touch-action: manipulation; }
|
|
485
339
|
.touch-pan-y { touch-action: pan-y; }
|