@kawaiininja/layouts 3.0.0 → 3.1.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/README.md CHANGED
@@ -184,6 +184,36 @@ const drawerItems = [
184
184
 
185
185
  The framework will automatically calculate the correct path from your `TabConfig` and trigger `onNavigate`.
186
186
 
187
+ ### 5. Triggering Drawers from Views
188
+
189
+ Every component rendered inside a sub-tab `view` automatically receives the `openDrawer` prop (and its alias `onOpenDrawer`). This allows you to trigger your registered drawers from any button or interaction within your content.
190
+
191
+ **New**: You can now pass a data object as the second argument. This data will be passed as props to the drawer component.
192
+
193
+ ```jsx
194
+ // Inside a component used as a sub-tab 'view'
195
+ const MyFeedView = ({ openDrawer }) => {
196
+ return (
197
+ <div className="p-6">
198
+ <h1>Welcome to the Feed</h1>
199
+ {/* Pass custom data to the drawer */}
200
+ <button onClick={() => openDrawer("editor", { initialTitle: "Hello World" })}>
201
+ Compose New Post
202
+ </button>
203
+ </div>
204
+ );
205
+ };
206
+
207
+ // The Drawer component then receives it as props
208
+ const MyEditorDrawer = ({ initialTitle, isOpen, onClose }) => {
209
+ return (
210
+ <AdaptiveDrawer isOpen={isOpen} onClose={onClose} title="New Post">
211
+ <input defaultValue={initialTitle} />
212
+ </AdaptiveDrawer>
213
+ );
214
+ };
215
+ ```
216
+
187
217
  ---
188
218
 
189
219
  ## 🔄 Data Refresh & Safety
@@ -317,12 +317,13 @@ const OnyxMobileLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh,
317
317
  .view, {
318
318
  onOpenDrawer: handleOpenDrawer,
319
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) => {
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((item, index) => {
321
+ const { id, data } = item;
321
322
  const DrawerComp = drawers[id];
322
323
  if (!DrawerComp)
323
324
  return null;
324
325
  const isTop = index === drawerStack.length - 1;
325
- return (_jsx(DrawerComp, { isOpen: isTop, onClose: () => {
326
+ return (_jsx(DrawerComp, { isOpen: isTop, ...(data || {}), onClose: () => {
326
327
  if (isTop) {
327
328
  console.log("[OnyxMobileLayout] Drawer onClose Event:", id);
328
329
  handleBackDrawer();
package/dist/PcLayout.js CHANGED
@@ -106,12 +106,13 @@ const OnyxPcLayoutBase = ({ tabs, user, drawers = {}, onSignOut, onRefresh, isRe
106
106
  }) }) }, `${activeTab}-${activeSubTab}`) })] })] }), _jsxs("nav", { className: "w-16 bg-[rgb(var(--bg-surface))] 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: () => handleNavClick(tab.id), onMouseEnter: (e) => handleMouseEnter(tab, e), onMouseLeave: handleMouseLeave, onRegisterRef: (el) => registerButtonRef(idx, el) }, tab.id)))] }), _jsx(RailNavThemeToggle, {})] }), _jsx(QuickMenu, { ...quickMenu, onSelect: handleQuickAction, onMouseEnter: () => {
107
107
  if (closeTimer.current)
108
108
  clearTimeout(closeTimer.current);
109
- }, onMouseLeave: handleMouseLeave }), drawerStack.map((id, index) => {
109
+ }, onMouseLeave: handleMouseLeave }), drawerStack.map((item, index) => {
110
+ const { id, data } = item;
110
111
  const DrawerComp = drawers[id];
111
112
  if (!DrawerComp)
112
113
  return null;
113
114
  const isTop = index === drawerStack.length - 1;
114
- return (_jsx(DrawerComp, { isOpen: isTop, onClose: () => {
115
+ return (_jsx(DrawerComp, { isOpen: isTop, ...(data || {}), onClose: () => {
115
116
  if (isTop)
116
117
  handleBackDrawer();
117
118
  }, onBack: () => {
@@ -1,12 +1,16 @@
1
+ export interface DrawerStackItem {
2
+ id: string;
3
+ data?: any;
4
+ }
1
5
  /**
2
6
  * useDrawerStack
3
7
  * Manages a stack of drawers for nested navigation.
4
8
  */
5
9
  export declare function useDrawerStack(): {
6
- drawerStack: string[];
10
+ drawerStack: DrawerStackItem[];
7
11
  activeDrawer: string | null;
8
- handleOpenDrawer: (id: string) => void;
12
+ handleOpenDrawer: (id: string, data?: any) => void;
9
13
  handleBackDrawer: () => void;
10
14
  handleCloseDrawers: () => void;
11
- setDrawerStack: import("react").Dispatch<import("react").SetStateAction<string[]>>;
15
+ setDrawerStack: import("react").Dispatch<import("react").SetStateAction<DrawerStackItem[]>>;
12
16
  };
@@ -5,27 +5,22 @@ import { useCallback, useMemo, useState } from "react";
5
5
  */
6
6
  export function useDrawerStack() {
7
7
  const [drawerStack, setDrawerStack] = useState([]);
8
- const activeDrawer = useMemo(() => drawerStack[drawerStack.length - 1] || null, [drawerStack]);
9
- const handleOpenDrawer = useCallback((id) => {
10
- console.log("[useDrawerStack] Opening Drawer:", id);
8
+ const activeDrawer = useMemo(() => drawerStack[drawerStack.length - 1]?.id || null, [drawerStack]);
9
+ const handleOpenDrawer = useCallback((id, data) => {
10
+ console.log("[useDrawerStack] Opening Drawer:", id, "with data:", data);
11
11
  setDrawerStack((prev) => {
12
- console.log("[useDrawerStack] Current Stack before push:", prev);
13
- // Don't push if it's already the top drawer
14
- if (prev[prev.length - 1] === id) {
15
- console.log("[useDrawerStack] Drawer already at top, skipping push:", id);
12
+ // Don't push if it's already the top drawer with same data (optional check)
13
+ if (prev[prev.length - 1]?.id === id &&
14
+ prev[prev.length - 1]?.data === data) {
16
15
  return prev;
17
16
  }
18
- const next = [...prev, id];
19
- console.log("[useDrawerStack] New Stack after push:", next);
20
- return next;
17
+ return [...prev, { id, data }];
21
18
  });
22
19
  }, []);
23
20
  const handleBackDrawer = useCallback(() => {
24
- console.log("[useDrawerStack] Popping Stack");
25
21
  setDrawerStack((prev) => (prev.length > 0 ? prev.slice(0, -1) : prev));
26
22
  }, []);
27
23
  const handleCloseDrawers = useCallback(() => {
28
- console.log("[useDrawerStack] Clearing Stack");
29
24
  setDrawerStack([]);
30
25
  }, []);
31
26
  return {
@@ -70,7 +70,11 @@ export const LibraryHome = ({ config, search, isLoading, hasMore, filteredItems,
70
70
  : item.preview })) : (_jsx(Package, { size: 20, className: "text-muted group-hover:text-accent" })) }), _jsxs("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-3 px-3 py-1.5 bg-accent text-primary text-[10px] font-bold uppercase tracking-widest rounded-lg opacity-0 group-hover:opacity-100 animate-float pointer-events-none whitespace-nowrap z-50 shadow-lg shadow-accent/20", children: [item.name, _jsx("div", { className: "absolute top-full left-1/2 -translate-x-1/2 -mt-1 border-4 border-transparent border-t-accent" })] })] }, item.id))) })) : ["Wallpapers", "Marketing", "Motion & Media"].includes(activeCategory) ? (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8", children: (activeSubCategory
71
71
  ? displayedItems.filter((i) => (i.subCategory || "General") ===
72
72
  activeSubCategory)
73
- : displayedItems).map((item) => (_jsxs("div", { onClick: () => onSelect(item.id), onMouseEnter: (e) => e.currentTarget.querySelector("video")?.play(), onMouseLeave: (e) => {
73
+ : displayedItems).map((item) => (_jsxs("div", { onClick: () => onSelect(item.id), onMouseEnter: (e) => {
74
+ const video = e.currentTarget.querySelector("video");
75
+ if (video)
76
+ video.play().catch(() => { });
77
+ }, onMouseLeave: (e) => {
74
78
  const v = e.currentTarget.querySelector("video");
75
79
  if (v) {
76
80
  v.pause();
@@ -90,7 +94,11 @@ export const LibraryHome = ({ config, search, isLoading, hasMore, filteredItems,
90
94
  : "none" }) })] }, item.id))) })) : (_jsx("div", { className: "flex flex-col gap-16", children: (activeSubCategory
91
95
  ? displayedItems.filter((i) => (i.subCategory || "General") ===
92
96
  activeSubCategory)
93
- : displayedItems).map((item) => (_jsxs("div", { onClick: () => onSelect(item.id), className: "group cursor-pointer flex flex-col lg:flex-row gap-10 items-start transition-all", children: [_jsxs("div", { className: "shrink-0 w-full lg:w-64 aspect-[16/10] relative flex items-center justify-center transition-all bg-tertiary rounded-xl group-hover:bg-accent/5 border border-subtle group-hover:border-accent/30 overflow-hidden", onMouseEnter: (e) => e.currentTarget.querySelector("video")?.play(), onMouseLeave: (e) => {
97
+ : displayedItems).map((item) => (_jsxs("div", { onClick: () => onSelect(item.id), className: "group cursor-pointer flex flex-col lg:flex-row gap-10 items-start transition-all", children: [_jsxs("div", { className: "shrink-0 w-full lg:w-64 aspect-[16/10] relative flex items-center justify-center transition-all bg-tertiary rounded-xl group-hover:bg-accent/5 border border-subtle group-hover:border-accent/30 overflow-hidden", onMouseEnter: (e) => {
98
+ const video = e.currentTarget.querySelector("video");
99
+ if (video)
100
+ video.play().catch(() => { });
101
+ }, onMouseLeave: (e) => {
94
102
  const v = e.currentTarget.querySelector("video");
95
103
  if (v) {
96
104
  v.pause();
@@ -8,19 +8,23 @@ export const MediaPreview = ({ product }) => {
8
8
  const audioRef = useRef(null);
9
9
  const toggleVideo = () => {
10
10
  if (videoRef.current) {
11
- if (isPlaying)
11
+ if (isPlaying) {
12
12
  videoRef.current.pause();
13
- else
14
- videoRef.current.play();
13
+ }
14
+ else {
15
+ videoRef.current.play().catch(() => { });
16
+ }
15
17
  setIsPlaying(!isPlaying);
16
18
  }
17
19
  };
18
20
  const toggleAudio = () => {
19
21
  if (audioRef.current) {
20
- if (isAudioPlaying)
22
+ if (isAudioPlaying) {
21
23
  audioRef.current.pause();
22
- else
23
- audioRef.current.play();
24
+ }
25
+ else {
26
+ audioRef.current.play().catch(() => { });
27
+ }
24
28
  setIsAudioPlaying(!isAudioPlaying);
25
29
  }
26
30
  };
@@ -38,6 +42,7 @@ export const MediaPreview = ({ product }) => {
38
42
  } })] })), _jsx("div", { className: "relative rounded-3xl overflow-hidden border border-subtle aspect-square md:aspect-video bg-black/40 backdrop-blur-xl flex items-center justify-center shadow-2xl", children: _jsxs("div", { className: "relative z-10 w-full max-w-xs md:max-w-md p-6 md:p-8 bg-surface/30 backdrop-blur-md rounded-3xl border border-white/10 shadow-xl flex flex-col items-center gap-6 md:gap-8 mx-4", children: [product.image && (_jsxs("div", { className: `relative w-40 h-40 rounded-full shadow-[0_0_40px_-5px_rgba(var(--color-accent),0.4)] transition-all duration-700 ${isAudioPlaying ? "animate-[spin_4s_linear_infinite]" : ""}`, children: [_jsx("img", { src: product.image, className: "w-full h-full rounded-full object-cover border-4 border-surface/50", alt: "Album Art" }), _jsx("div", { className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 bg-surface/80 rounded-full border border-white/20" })] })), _jsxs("div", { className: "w-full text-center space-y-4", children: [_jsx("h3", { className: "text-2xl font-black text-white mb-2 tracking-tight drop-shadow-md", children: product.name }), _jsx("button", { onClick: toggleAudio, className: "w-16 h-16 rounded-full bg-white text-black flex items-center justify-center mx-auto hover:scale-110 active:scale-95 transition-all shadow-lg shadow-white/20", children: isAudioPlaying ? (_jsx(Pause, { size: 24, className: "fill-black" })) : (_jsx(Play, { size: 24, className: "fill-black ml-1" })) }), _jsx("audio", { ref: audioRef, src: product.previewUrl, onEnded: () => setIsAudioPlaying(false), className: "hidden" })] })] }) })] }));
39
43
  }
40
44
  if ((product.type === "video" ||
45
+ product.type === "media" ||
41
46
  product.type === "app" ||
42
47
  product.type === "software") &&
43
48
  product.previewUrl) {
@@ -6,7 +6,7 @@ export interface AssetProduct {
6
6
  category: string;
7
7
  framework: string;
8
8
  description: string;
9
- type?: "code" | "util" | "image" | "audio" | "video" | "book" | "app" | "software" | "glyph";
9
+ type?: "code" | "util" | "image" | "audio" | "video" | "media" | "book" | "app" | "software" | "glyph";
10
10
  previewUrl?: string;
11
11
  preview?: React.ReactNode;
12
12
  tags?: string[];
@@ -4,6 +4,6 @@ export interface DrawerItemConfig {
4
4
  targetTab?: string;
5
5
  targetSubTab?: string;
6
6
  onClick?: (ctx: {
7
- openDrawer: (id: string) => void;
7
+ openDrawer: (id: string, data?: any) => void;
8
8
  }) => void;
9
9
  }
@@ -6,13 +6,7 @@ export interface OnyxMobileLayoutProps {
6
6
  tabs: TabConfig[];
7
7
  user: UserConfig;
8
8
  drawers?: {
9
- [key: string]: ComponentType<{
10
- isOpen: boolean;
11
- onClose: () => void;
12
- onBack?: () => void;
13
- onOpenDrawer: (id: string) => void;
14
- openDrawer?: (id: string) => void;
15
- }>;
9
+ [key: string]: ComponentType<any>;
16
10
  };
17
11
  onSignOut?: () => void;
18
12
  onRefresh?: () => void | Promise<void>;
@@ -11,7 +11,7 @@ export interface QuickActionConfig {
11
11
  label: string;
12
12
  icon: ComponentType<any>;
13
13
  onClick?: (ctx: {
14
- openDrawer: (id: string) => void;
14
+ openDrawer: (id: string, data?: any) => void;
15
15
  }) => void;
16
16
  }
17
17
  export interface TabConfig {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kawaiininja/layouts",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "category": "UI Components",
5
5
  "framework": "React / Framer Motion",
6
6
  "description": "High-performance, premium mobile-first layouts for the Onyx Framework, featuring gesture-driven navigation, radial quick actions, and integrated theme support.",