@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.
Files changed (63) hide show
  1. package/dist/{chunk-B2DLHZKC.js → chunk-2DDWWTRX.js} +1 -1
  2. package/dist/{chunk-S55OZ7F2.js → chunk-3LNKOLZP.js} +4 -0
  3. package/dist/{chunk-SWIHUIFT.js → chunk-4SIWBWKZ.js} +12 -2
  4. package/dist/{chunk-RO4E5UJI.js → chunk-BAQYZSFF.js} +6 -6
  5. package/dist/{chunk-IFHRHV7S.js → chunk-EMGLZQCM.js} +1 -1
  6. package/dist/{chunk-YP7IHMC3.js → chunk-K4JD6FD3.js} +1 -1
  7. package/dist/{chunk-SFKYUKVN.js → chunk-MK4POMLZ.js} +1 -1
  8. package/dist/{chunk-63BQQNZO.js → chunk-NS3UTJ3E.js} +3 -3
  9. package/dist/{chunk-UUCOJL7O.js → chunk-UA6OCJJK.js} +2 -2
  10. package/dist/{chunk-XFDEUK37.js → chunk-Z5IYWUW3.js} +1 -1
  11. package/dist/components/editor/index.js +4 -4
  12. package/dist/components/primitives/index.js +5 -5
  13. package/dist/components/sections/brand-guide.js +5 -5
  14. package/dist/components/sections/register-schemas.js +8 -8
  15. package/dist/components/sections/register.js +8 -8
  16. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  17. package/dist/components/shell/MediaBreadcrumb.d.ts +10 -0
  18. package/dist/components/shell/MediaBreadcrumb.d.ts.map +1 -0
  19. package/dist/components/shell/MediaFolderTile.d.ts +15 -0
  20. package/dist/components/shell/MediaFolderTile.d.ts.map +1 -0
  21. package/dist/components/shell/MediaFolderTree.d.ts +20 -0
  22. package/dist/components/shell/MediaFolderTree.d.ts.map +1 -0
  23. package/dist/components/shell/MediaLibraryModal.d.ts +15 -3
  24. package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
  25. package/dist/components/shell/MoveToFolderDialog.d.ts +16 -0
  26. package/dist/components/shell/MoveToFolderDialog.d.ts.map +1 -0
  27. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  28. package/dist/hooks/useMediaFolderDnd.d.ts +27 -0
  29. package/dist/hooks/useMediaFolderDnd.d.ts.map +1 -0
  30. package/dist/hooks/useMediaPipeline.d.ts +14 -2
  31. package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
  32. package/dist/index.js +8 -4
  33. package/dist/lib/index.js +2 -2
  34. package/dist/lib/media-dnd.d.ts +26 -0
  35. package/dist/lib/media-dnd.d.ts.map +1 -0
  36. package/dist/lib/media-folders.d.ts +59 -0
  37. package/dist/lib/media-folders.d.ts.map +1 -0
  38. package/dist/media/folder-drop.d.ts +22 -0
  39. package/dist/media/folder-drop.d.ts.map +1 -0
  40. package/dist/media/index.js +6 -2
  41. package/dist/media/types.d.ts +1 -1
  42. package/dist/media/types.d.ts.map +1 -1
  43. package/dist/media/utils.d.ts +7 -0
  44. package/dist/media/utils.d.ts.map +1 -1
  45. package/dist/schemas/index.js +4 -2
  46. package/dist/schemas/media.d.ts +11 -0
  47. package/dist/schemas/media.d.ts.map +1 -1
  48. package/package.json +1 -1
  49. package/src/components/shell/EditorShell.tsx +20 -4
  50. package/src/components/shell/MediaBreadcrumb.tsx +77 -0
  51. package/src/components/shell/MediaFolderTile.tsx +141 -0
  52. package/src/components/shell/MediaFolderTree.tsx +127 -0
  53. package/src/components/shell/MediaLibraryModal.tsx +611 -229
  54. package/src/components/shell/MoveToFolderDialog.tsx +78 -0
  55. package/src/hooks/useEditorPublish.ts +14 -11
  56. package/src/hooks/useMediaFolderDnd.ts +129 -0
  57. package/src/hooks/useMediaPipeline.ts +162 -6
  58. package/src/lib/media-dnd.ts +62 -0
  59. package/src/lib/media-folders.ts +232 -0
  60. package/src/media/folder-drop.ts +62 -0
  61. package/src/media/types.ts +2 -0
  62. package/src/media/utils.ts +10 -0
  63. 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
+ }