@drawnagency/primitives 0.1.77 → 0.1.78
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/{chunk-B2DLHZKC.js → chunk-2DDWWTRX.js} +1 -1
- package/dist/{chunk-S55OZ7F2.js → chunk-3LNKOLZP.js} +4 -0
- package/dist/{chunk-SWIHUIFT.js → chunk-4SIWBWKZ.js} +12 -2
- package/dist/{chunk-RO4E5UJI.js → chunk-BAQYZSFF.js} +6 -6
- package/dist/{chunk-IFHRHV7S.js → chunk-EMGLZQCM.js} +1 -1
- package/dist/{chunk-YP7IHMC3.js → chunk-K4JD6FD3.js} +1 -1
- package/dist/{chunk-SFKYUKVN.js → chunk-MK4POMLZ.js} +1 -1
- package/dist/{chunk-63BQQNZO.js → chunk-NS3UTJ3E.js} +3 -3
- package/dist/{chunk-UUCOJL7O.js → chunk-UA6OCJJK.js} +2 -2
- package/dist/{chunk-XFDEUK37.js → chunk-Z5IYWUW3.js} +1 -1
- package/dist/components/editor/index.js +4 -4
- package/dist/components/primitives/index.js +5 -5
- package/dist/components/sections/brand-guide.js +5 -5
- package/dist/components/sections/register-schemas.js +8 -8
- package/dist/components/sections/register.js +8 -8
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/MediaBreadcrumb.d.ts +10 -0
- package/dist/components/shell/MediaBreadcrumb.d.ts.map +1 -0
- package/dist/components/shell/MediaFolderTile.d.ts +15 -0
- package/dist/components/shell/MediaFolderTile.d.ts.map +1 -0
- package/dist/components/shell/MediaFolderTree.d.ts +20 -0
- package/dist/components/shell/MediaFolderTree.d.ts.map +1 -0
- package/dist/components/shell/MediaLibraryModal.d.ts +15 -3
- package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
- package/dist/components/shell/MoveToFolderDialog.d.ts +16 -0
- package/dist/components/shell/MoveToFolderDialog.d.ts.map +1 -0
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/hooks/useMediaFolderDnd.d.ts +27 -0
- package/dist/hooks/useMediaFolderDnd.d.ts.map +1 -0
- package/dist/hooks/useMediaPipeline.d.ts +14 -2
- package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/lib/index.js +2 -2
- package/dist/lib/media-dnd.d.ts +26 -0
- package/dist/lib/media-dnd.d.ts.map +1 -0
- package/dist/lib/media-folders.d.ts +59 -0
- package/dist/lib/media-folders.d.ts.map +1 -0
- package/dist/media/folder-drop.d.ts +22 -0
- package/dist/media/folder-drop.d.ts.map +1 -0
- package/dist/media/index.js +6 -2
- package/dist/media/types.d.ts +1 -1
- package/dist/media/types.d.ts.map +1 -1
- package/dist/media/utils.d.ts +7 -0
- package/dist/media/utils.d.ts.map +1 -1
- package/dist/schemas/index.js +4 -2
- package/dist/schemas/media.d.ts +11 -0
- package/dist/schemas/media.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/shell/EditorShell.tsx +20 -4
- package/src/components/shell/MediaBreadcrumb.tsx +77 -0
- package/src/components/shell/MediaFolderTile.tsx +141 -0
- package/src/components/shell/MediaFolderTree.tsx +127 -0
- package/src/components/shell/MediaLibraryModal.tsx +611 -229
- package/src/components/shell/MoveToFolderDialog.tsx +78 -0
- package/src/hooks/useEditorPublish.ts +14 -11
- package/src/hooks/useMediaFolderDnd.ts +129 -0
- package/src/hooks/useMediaPipeline.ts +162 -6
- package/src/lib/media-dnd.ts +62 -0
- package/src/lib/media-folders.ts +232 -0
- package/src/media/folder-drop.ts +62 -0
- package/src/media/types.ts +2 -0
- package/src/media/utils.ts +10 -0
- package/src/schemas/media.ts +13 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
import { ChevronRight, Folder, LayoutGrid } from "lucide-react";
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import type { MediaFolder, MediaItem } from "../../media/types";
|
|
5
|
+
import { buildFolderTree, type FolderTreeNode } from "../../lib/media-folders";
|
|
6
|
+
import { useFolderDropTarget } from "../../hooks/useMediaFolderDnd";
|
|
7
|
+
|
|
8
|
+
export interface MediaFolderDndBindings {
|
|
9
|
+
foldersRef: React.RefObject<Record<string, MediaFolder>>;
|
|
10
|
+
itemsByIdRef: React.RefObject<Record<string, MediaItem>>;
|
|
11
|
+
onMoveMedia: (ids: string[], target: string | null) => void;
|
|
12
|
+
onMoveFolder: (id: string, target: string | null) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MediaFolderTreeProps {
|
|
16
|
+
folders: Record<string, MediaFolder>;
|
|
17
|
+
counts: Record<string, number>;
|
|
18
|
+
totalCount: number;
|
|
19
|
+
currentFolderId: string | null;
|
|
20
|
+
onNavigate: (id: string | null) => void;
|
|
21
|
+
expandedIds: Set<string>;
|
|
22
|
+
onExpand: (id: string) => void;
|
|
23
|
+
onToggleExpand: (id: string) => void;
|
|
24
|
+
dnd?: MediaFolderDndBindings;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
function useRowDropTarget(
|
|
29
|
+
targetFolderId: string | null,
|
|
30
|
+
dnd: MediaFolderDndBindings | undefined,
|
|
31
|
+
onDragHover?: () => void,
|
|
32
|
+
) {
|
|
33
|
+
const emptyFoldersRef = useRef<Record<string, MediaFolder>>({});
|
|
34
|
+
const emptyItemsRef = useRef<Record<string, MediaItem>>({});
|
|
35
|
+
return useFolderDropTarget<HTMLDivElement>({
|
|
36
|
+
targetFolderId,
|
|
37
|
+
enabled: dnd !== undefined,
|
|
38
|
+
foldersRef: dnd?.foldersRef ?? emptyFoldersRef,
|
|
39
|
+
itemsByIdRef: dnd?.itemsByIdRef ?? emptyItemsRef,
|
|
40
|
+
onMoveMedia: dnd?.onMoveMedia ?? (() => {}),
|
|
41
|
+
onMoveFolder: dnd?.onMoveFolder ?? (() => {}),
|
|
42
|
+
onDragHover,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const rowClass = (active: boolean, isDropTarget: boolean) =>
|
|
47
|
+
cn(
|
|
48
|
+
"flex w-full cursor-pointer items-center gap-1.5 rounded px-2 py-1.5 text-sm transition-colors",
|
|
49
|
+
active ? "bg-primary/10 font-medium text-primary" : "text-base-contrast hover:bg-base-accent",
|
|
50
|
+
isDropTarget && "ring-2 ring-primary ring-inset",
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
function TreeRow({
|
|
54
|
+
node, depth, props,
|
|
55
|
+
}: { node: FolderTreeNode; depth: number; props: MediaFolderTreeProps }) {
|
|
56
|
+
const { currentFolderId, onNavigate, expandedIds, onExpand, onToggleExpand, counts, dnd } = props;
|
|
57
|
+
const expanded = expandedIds.has(node.id);
|
|
58
|
+
const { ref, isDropTarget } = useRowDropTarget(node.id, dnd, () => onExpand(node.id));
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div>
|
|
62
|
+
<div
|
|
63
|
+
ref={ref}
|
|
64
|
+
role="button"
|
|
65
|
+
tabIndex={0}
|
|
66
|
+
onClick={() => onNavigate(node.id)}
|
|
67
|
+
onKeyDown={(e) => { if (e.key === "Enter") onNavigate(node.id); }}
|
|
68
|
+
className={rowClass(currentFolderId === node.id, isDropTarget)}
|
|
69
|
+
style={{ paddingLeft: `${8 + depth * 14}px` }}
|
|
70
|
+
>
|
|
71
|
+
{node.children.length > 0 ? (
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
aria-label={`${expanded ? "Collapse" : "Expand"} ${node.name}`}
|
|
75
|
+
aria-expanded={expanded}
|
|
76
|
+
onClick={(e) => { e.stopPropagation(); onToggleExpand(node.id); }}
|
|
77
|
+
// Enter/Space keydown bubbles to the row BEFORE the button's
|
|
78
|
+
// synthetic click fires — stop it so the row doesn't navigate.
|
|
79
|
+
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") e.stopPropagation(); }}
|
|
80
|
+
className="shrink-0 rounded p-0.5 hover:bg-base-200"
|
|
81
|
+
>
|
|
82
|
+
<ChevronRight size={12} className={cn("transition-transform", expanded && "rotate-90")} />
|
|
83
|
+
</button>
|
|
84
|
+
) : (
|
|
85
|
+
<span className="w-[17px] shrink-0" />
|
|
86
|
+
)}
|
|
87
|
+
<Folder size={14} className="shrink-0 text-base-contrast-light" />
|
|
88
|
+
<span className="min-w-0 flex-1 truncate">{node.name}</span>
|
|
89
|
+
{(counts[node.id] ?? 0) > 0 && (
|
|
90
|
+
<span className="shrink-0 text-xs text-base-contrast-light">{counts[node.id]}</span>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
{expanded &&
|
|
94
|
+
node.children.map((child) => (
|
|
95
|
+
<TreeRow key={child.id} node={child} depth={depth + 1} props={props} />
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function MediaFolderTree(props: MediaFolderTreeProps) {
|
|
102
|
+
const { folders, totalCount, currentFolderId, onNavigate, dnd } = props;
|
|
103
|
+
const tree = buildFolderTree(folders);
|
|
104
|
+
const root = useRowDropTarget(null, dnd);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<nav aria-label="Media folders" className="flex flex-col gap-0.5">
|
|
108
|
+
<div
|
|
109
|
+
ref={root.ref}
|
|
110
|
+
role="button"
|
|
111
|
+
tabIndex={0}
|
|
112
|
+
onClick={() => onNavigate(null)}
|
|
113
|
+
onKeyDown={(e) => { if (e.key === "Enter") onNavigate(null); }}
|
|
114
|
+
className={rowClass(currentFolderId === null, root.isDropTarget)}
|
|
115
|
+
>
|
|
116
|
+
<LayoutGrid size={14} className="shrink-0 text-base-contrast-light" />
|
|
117
|
+
<span className="min-w-0 flex-1 truncate">All media</span>
|
|
118
|
+
{totalCount > 0 && (
|
|
119
|
+
<span className="shrink-0 text-xs text-base-contrast-light">{totalCount}</span>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
{tree.map((node) => (
|
|
123
|
+
<TreeRow key={node.id} node={node} depth={0} props={props} />
|
|
124
|
+
))}
|
|
125
|
+
</nav>
|
|
126
|
+
);
|
|
127
|
+
}
|