@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 +30 -0
- package/dist/MobileLayout.js +3 -2
- package/dist/PcLayout.js +3 -2
- package/dist/hooks/useDrawerStack.d.ts +7 -3
- package/dist/hooks/useDrawerStack.js +7 -12
- package/dist/library/LibraryHome.js +10 -2
- package/dist/library/MediaPreview.js +11 -6
- package/dist/library/types.d.ts +1 -1
- package/dist/types/drawer.d.ts +1 -1
- package/dist/types/layout.d.ts +1 -7
- package/dist/types/navigation.d.ts +1 -1
- package/package.json +1 -1
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
|
package/dist/MobileLayout.js
CHANGED
|
@@ -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((
|
|
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((
|
|
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:
|
|
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<
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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) =>
|
|
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) =>
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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) {
|
package/dist/library/types.d.ts
CHANGED
|
@@ -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[];
|
package/dist/types/drawer.d.ts
CHANGED
package/dist/types/layout.d.ts
CHANGED
|
@@ -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>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kawaiininja/layouts",
|
|
3
|
-
"version": "3.
|
|
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.",
|