@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.
@@ -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 py-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))]", children: _jsx(Menu, { size: 24 }) }), _jsx("h1", { className: "text-xl font-bold text-[rgb(var(--text-primary))] tracking-tight", children: title })] }), rightAction] }));
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", 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: {
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: `.no-scrollbar::-webkit-scrollbar { display: none; }` })] }));
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.6",
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",