@kawaiininja/layouts 1.0.6 → 1.0.7
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 +23 -10
- package/package.json +1 -1
package/dist/MobileLayout.js
CHANGED
|
@@ -33,15 +33,15 @@ const useTheme = () => {
|
|
|
33
33
|
};
|
|
34
34
|
// --- Sub-components (Generic Versions) ---
|
|
35
35
|
const RailNavIndicator = ({ height, offset, }) => (_jsx("div", { className: "absolute left-2 right-2 rounded-[24px] bg-[rgb(var(--bg-elevated))] transition-all duration-300 ease-out z-0 pointer-events-none border border-[rgb(var(--color-border-subtle))]/30", style: { height: `${height}px`, transform: `translateY(${offset}px)` } }));
|
|
36
|
-
const RailNavButton = ({ title, icon: Icon, active, onClick, onRegisterRef, onTouchStart, onTouchMove, onTouchEnd, }) => (_jsxs("button", { ref: onRegisterRef, type: "button", role: "tab", onClick: onClick, onTouchStart: onTouchStart, onTouchMove: onTouchMove, onTouchEnd: onTouchEnd, onContextMenu: (e) => e.preventDefault(), className: `flex flex-col shrink-0 items-center justify-center w-full py-8 px-2 min-h-[120px] gap-4 transition-colors relative z-10 ${active
|
|
36
|
+
const RailNavButton = ({ title, icon: Icon, active, onClick, onRegisterRef, onTouchStart, onTouchMove, onTouchEnd, }) => (_jsxs("button", { ref: onRegisterRef, type: "button", role: "tab", "aria-label": title, onClick: onClick, onTouchStart: onTouchStart, onTouchMove: onTouchMove, onTouchEnd: onTouchEnd, onContextMenu: (e) => e.preventDefault(), className: `flex flex-col shrink-0 items-center justify-center w-full py-8 px-2 min-h-[120px] gap-4 transition-colors relative z-10 select-none ${active
|
|
37
37
|
? "text-[rgb(var(--color-accent))]"
|
|
38
|
-
: "text-[rgb(var(--text-secondary))] hover:text-[rgb(var(--text-primary))]"}`, children: [_jsx("span", { className: `text-xs font-bold uppercase tracking-[0.2em] [writing-mode:vertical-rl] rotate-180 transition-all duration-300 ${active ? "scale-110 opacity-100" : "scale-100 opacity-60"}`, children: title }), Icon && (_jsx(Icon, { size: 20, className: `transition-all duration-300 -rotate-90 ${active ? "scale-110 opacity-100" : "scale-100 opacity-60"}` }))] }));
|
|
38
|
+
: "text-[rgb(var(--text-secondary))] hover:text-[rgb(var(--text-primary))]"}`, children: [_jsx("span", { className: `text-xs font-bold uppercase tracking-[0.2em] [writing-mode:vertical-rl] rotate-180 transition-all duration-300 pointer-events-none ${active ? "scale-110 opacity-100" : "scale-100 opacity-60"}`, children: title }), Icon && (_jsx(Icon, { size: 20, className: `transition-all duration-300 -rotate-90 pointer-events-none ${active ? "scale-110 opacity-100" : "scale-100 opacity-60"}` }))] }));
|
|
39
39
|
const RailNavThemeToggle = () => {
|
|
40
40
|
const { isDark, toggleTheme } = useTheme();
|
|
41
41
|
return (_jsx("button", { onClick: toggleTheme, className: "mt-auto mb-4 mx-auto p-2 rounded-xl text-[rgb(var(--text-tertiary))] hover:text-[rgb(var(--text-primary))] hover:bg-[rgb(var(--bg-tertiary))] transition-all active:scale-90 group", children: isDark ? (_jsx(Sun, { size: 20, className: "duration-500 group-hover:rotate-180 text-[rgb(var(--color-accent))]" })) : (_jsx(Moon, { size: 20, className: "duration-300 group-hover:-rotate-12 text-[rgb(var(--color-secondary))]" })) }));
|
|
42
42
|
};
|
|
43
43
|
const QuickMenu = ({ visible, items, positionY, selectedIndex }) => {
|
|
44
|
-
if (!visible || !items)
|
|
44
|
+
if (!visible || !items || items.length === 0)
|
|
45
45
|
return null;
|
|
46
46
|
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: {
|
|
47
47
|
top: positionY,
|
|
@@ -56,7 +56,7 @@ const QuickMenu = ({ visible, items, positionY, selectedIndex }) => {
|
|
|
56
56
|
}
|
|
57
57
|
` })] }));
|
|
58
58
|
};
|
|
59
|
-
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
|
|
59
|
+
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] }));
|
|
60
60
|
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
|
|
61
61
|
? "text-[rgb(var(--text-primary))]"
|
|
62
62
|
: "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))) }) }));
|
|
@@ -104,6 +104,13 @@ function useRailNavScroll(active, navKeys) {
|
|
|
104
104
|
}
|
|
105
105
|
// --- Main Layout Component ---
|
|
106
106
|
const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh, isRefreshing: externalRefreshing, rightAction, initialTab = "home", activeTab: externalActiveTab, activeSubTab: externalActiveSubTab, onNavigate, drawerItems = [], }) => {
|
|
107
|
+
// --- Safety Validations ---
|
|
108
|
+
if (!tabs || !Array.isArray(tabs) || tabs.length === 0) {
|
|
109
|
+
throw new Error("[OnyxMobileLayout] Critical Error: 'tabs' prop is required and must be a non-empty array.");
|
|
110
|
+
}
|
|
111
|
+
if (!user) {
|
|
112
|
+
throw new Error("[OnyxMobileLayout] Critical Error: 'user' prop is required to display profile information.");
|
|
113
|
+
}
|
|
107
114
|
const [internalActiveTab, setInternalActiveTab] = useState(initialTab);
|
|
108
115
|
const activeTab = externalActiveTab || internalActiveTab;
|
|
109
116
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -112,7 +119,7 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
112
119
|
const [pullY, setPullY] = useState(0);
|
|
113
120
|
const [internalRefreshing, setInternalRefreshing] = useState(false);
|
|
114
121
|
const currentTabConfig = tabs.find((t) => t.id === activeTab);
|
|
115
|
-
const subTabs = currentTabConfig?.subTabs
|
|
122
|
+
const subTabs = currentTabConfig?.subTabs ?? [];
|
|
116
123
|
const [internalSubTab, setInternalSubTab] = useState(subTabs[0]?.label || "");
|
|
117
124
|
const subTab = externalActiveSubTab || internalSubTab;
|
|
118
125
|
const navigateTo = (tabId, subTabLabel) => {
|
|
@@ -124,7 +131,7 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
124
131
|
const targetTab = tabs.find((t) => t.id === (tabId || activeTab));
|
|
125
132
|
let path = targetTab?.path || "";
|
|
126
133
|
if (subTabLabel) {
|
|
127
|
-
const st = targetTab?.subTabs.find((s) => s.label === subTabLabel);
|
|
134
|
+
const st = (targetTab?.subTabs || []).find((s) => s.label === subTabLabel);
|
|
128
135
|
if (st?.path) {
|
|
129
136
|
if (st.path.startsWith("/")) {
|
|
130
137
|
path = st.path;
|
|
@@ -350,7 +357,7 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
350
357
|
navigator.vibrate(50);
|
|
351
358
|
setQuickMenu({
|
|
352
359
|
visible: true,
|
|
353
|
-
items: tab.quickActions,
|
|
360
|
+
items: tab.quickActions || [],
|
|
354
361
|
positionY: y,
|
|
355
362
|
selectedIndex: -1,
|
|
356
363
|
tabId: tab.id,
|
|
@@ -393,14 +400,16 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
393
400
|
};
|
|
394
401
|
const currentTranslate = isDragging ? renderDragX : isOpen ? DRAWER_WIDTH : 0;
|
|
395
402
|
const progress = currentTranslate / DRAWER_WIDTH;
|
|
396
|
-
return (_jsxs("div", { className: "w-full bg-[rgb(var(--bg-main))] overflow-hidden relative h-full
|
|
403
|
+
return (_jsxs("div", { className: "w-full bg-[rgb(var(--bg-main))] overflow-hidden relative h-full safe-top safe-bottom", style: {
|
|
404
|
+
height: "100dvh",
|
|
405
|
+
}, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, children: [_jsx(Drawer, {}), _jsxs("div", { className: "absolute inset-0 z-10 bg-[rgb(var(--bg-main))] flex shadow-2xl origin-left", style: {
|
|
397
406
|
transform: `translateX(${currentTranslate}px) scale(${1 - progress * 0.05})`,
|
|
398
407
|
borderRadius: `${progress * 24}px`,
|
|
399
408
|
transition: isDragging || isVerticalPull.current
|
|
400
409
|
? "none"
|
|
401
410
|
: "transform 0.3s cubic-bezier(0.32, 0.72, 0, 1), border-radius 0.3s",
|
|
402
411
|
overflow: "hidden",
|
|
403
|
-
}, 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: subTab, onChange: (label) => navigateTo(undefined, label) }), _jsxs("main", { ref: mainScrollRef, className: "flex-1 overflow-y-auto no-scrollbar overscroll-contain relative mr-4", children: [_jsx("div", { className: "absolute top-0 left-0 right-0 flex justify-center items-center pointer-events-none z-0", style: {
|
|
412
|
+
}, 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: subTab, onChange: (label) => navigateTo(undefined, label) }), _jsxs("main", { ref: mainScrollRef, className: "flex-1 overflow-y-auto no-scrollbar overscroll-contain relative mr-4 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: {
|
|
404
413
|
height: `${pullY}px`,
|
|
405
414
|
opacity: Math.min(pullY / 40, 1),
|
|
406
415
|
}, children: _jsx("div", { className: `p-2 bg-[rgb(var(--bg-surface))] rounded-full shadow-md border border-[rgb(var(--color-border-subtle))] ${isRefreshing
|
|
@@ -409,6 +418,10 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
|
|
|
409
418
|
transform: `translateY(${pullY}px)`,
|
|
410
419
|
minHeight: "100%",
|
|
411
420
|
}, children: subTabs.find((st) => st.label === subTab)?.view &&
|
|
412
|
-
React.createElement(subTabs.find((st) => st.label === subTab).view, { onOpenDrawer: setActiveDrawer }) }) }, activeTab) })] })] }), _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", 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) }, tab.id)))] }), _jsx(RailNavThemeToggle, {})] })] }), _jsx(QuickMenu, { ...quickMenu }), Object.entries(drawers).map(([key, DrawerComp]) => (_jsx(DrawerComp, { isOpen: activeDrawer === key, onClose: () => setActiveDrawer(null) }, key))), _jsx("style", { children:
|
|
421
|
+
React.createElement(subTabs.find((st) => st.label === subTab).view, { onOpenDrawer: setActiveDrawer }) }) }, activeTab) })] })] }), _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) }, tab.id)))] }), _jsx(RailNavThemeToggle, {})] })] }), _jsx(QuickMenu, { ...quickMenu }), Object.entries(drawers || {}).map(([key, DrawerComp]) => (_jsx(DrawerComp, { isOpen: activeDrawer === key, onClose: () => setActiveDrawer(null) }, key))), _jsx("style", { children: `
|
|
422
|
+
.no-scrollbar::-webkit-scrollbar { display: none; }
|
|
423
|
+
.touch-manipulation { touch-action: manipulation; }
|
|
424
|
+
.touch-pan-y { touch-action: pan-y; }
|
|
425
|
+
` })] }));
|
|
413
426
|
};
|
|
414
427
|
export const OnyxMobileLayout = (props) => (_jsx(ThemeProvider, { children: _jsx(OnyxMobileLayoutBase, { ...props }) }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kawaiininja/layouts",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "High-performance, premium mobile-first layouts for the Onyx Framework, featuring gesture-driven navigation, radial quick actions, and integrated theme support.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|