@halazv2/react-file-manager 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 halazv2
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # @halazv2/react-file-manager
2
+
3
+ Headless-ish React file browser with **Finder-style spring-loaded folders** and drag-and-drop. Bring your own data and API.
4
+
5
+ Hover a folder while dragging — it expands in the sidebar and opens in the browser after a short delay, just like macOS Finder.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @halazv2/react-file-manager
11
+ ```
12
+
13
+ Peer dependencies: `react` and `react-dom` ≥ 18.
14
+
15
+ ### Tailwind (default styling)
16
+
17
+ The UI is built with Tailwind classes. Point Tailwind at the package, or import the prebuilt stylesheet.
18
+
19
+ **Tailwind v4**
20
+
21
+ ```css
22
+ @import "tailwindcss";
23
+ @source "../node_modules/@halazv2/react-file-manager/dist";
24
+
25
+ @theme {
26
+ --color-rfm-primary: #2563eb;
27
+ --color-rfm-hover: color-mix(in srgb, #2563eb 12%, transparent);
28
+ }
29
+ ```
30
+
31
+ **Prebuilt CSS** (no Tailwind setup required)
32
+
33
+ ```ts
34
+ import "@halazv2/react-file-manager/styles.css";
35
+ ```
36
+
37
+ Theme with CSS variables on `.rfm-root`, or override the Tailwind theme tokens:
38
+
39
+ ```css
40
+ .rfm-root {
41
+ --rfm-primary: #2563eb;
42
+ --rfm-hover: color-mix(in srgb, var(--rfm-primary) 12%, transparent);
43
+ }
44
+ ```
45
+
46
+ ## Quick start
47
+
48
+ ```tsx
49
+ import { FileManager, moveNodes, type FileManagerNode } from "@halazv2/react-file-manager";
50
+ import { useState } from "react";
51
+
52
+ const initial: FileManagerNode[] = [
53
+ {
54
+ id: "docs",
55
+ name: "Documents",
56
+ kind: "folder",
57
+ children: [{ id: "notes", name: "notes.txt", kind: "file" }]
58
+ }
59
+ ];
60
+
61
+ export function App() {
62
+ const [nodes, setNodes] = useState(initial);
63
+
64
+ return (
65
+ <div style={{ height: 560 }}>
66
+ <FileManager
67
+ nodes={nodes}
68
+ onMove={(ids, folderId) =>
69
+ setNodes((current) => moveNodes(current, ids, folderId))
70
+ }
71
+ onOpenFile={(id) => console.log("open", id)}
72
+ onUpload={(files, folderId) => console.log(files, folderId)}
73
+ onCreateFolder={(parentId) => console.log("new folder in", parentId)}
74
+ />
75
+ </div>
76
+ );
77
+ }
78
+ ```
79
+
80
+ The component fills its parent. Give the parent a height.
81
+
82
+ ## What it includes
83
+
84
+ - Folder sidebar + list/cards browser
85
+ - Breadcrumbs
86
+ - Multi-select and keyboard navigation
87
+ - Drag-and-drop move with spring-loaded folders
88
+ - Controlled or uncontrolled search
89
+ - Generic nodes — you own upload, preview, and menus
90
+
91
+ ## What it leaves out
92
+
93
+ No backend, document preview pipeline, or app-specific menus. Pass `renderActions` / `renderPreview` if you need those.
94
+
95
+ ## Props
96
+
97
+ | Prop | Type | Notes |
98
+ | --- | --- | --- |
99
+ | `nodes` | `FileManagerNode[]` | Nested tree. Root is implied. |
100
+ | `folderId` / `defaultFolderId` | `string \| null` | Current folder. `null` is root. |
101
+ | `onMove` | `(ids, folderId) => void` | Fired on drop. Use `moveNodes` for local state. |
102
+ | `onOpenFile` | `(id) => void` | Double-click or Enter on a file. |
103
+ | `onUpload` | `(files, folderId) => void` | OS file drop or empty-state upload. |
104
+ | `onCreateFolder` | `(parentId) => void` | New folder action. |
105
+ | `onDelete` | `(ids) => void` | Delete / Backspace. |
106
+ | `canManage` | `boolean` | Disables drag, drop, and mutations. Default `true`. |
107
+ | `springLoadDelay` | `number` | Hover delay in ms. Default `500`. |
108
+ | `showDetails` | `boolean` | Inspector pane. Default `true`. |
109
+ | `renderIcon` | `(node) => ReactNode` | Custom icons. |
110
+ | `renderPreview` | `(node) => ReactNode` | Replace the details pane. |
111
+ | `renderActions` | `(node) => ReactNode` | Per-item actions. |
112
+
113
+ `FileManagerNode`:
114
+
115
+ ```ts
116
+ type FileManagerNode = {
117
+ id: string;
118
+ name: string;
119
+ kind: "folder" | "file";
120
+ children?: FileManagerNode[];
121
+ extension?: string;
122
+ size?: number;
123
+ meta?: Record<string, unknown>;
124
+ };
125
+ ```
126
+
127
+ ## Local demo
128
+
129
+ ```bash
130
+ npm install
131
+ npm run dev
132
+ ```
133
+
134
+ ## License
135
+
136
+ MIT
@@ -0,0 +1,2 @@
1
+ import { FileManagerProps } from './types';
2
+ export declare function FileManager(props: FileManagerProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function Breadcrumbs(): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function Browser(): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function DetailsPane(): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { FileManagerItem } from '../types';
2
+ export declare function Item({ item, index }: {
3
+ item: FileManagerItem;
4
+ index: number;
5
+ }): import("react").JSX.Element;
6
+ export declare function FolderTree({ folders, depth }: {
7
+ folders: FileManagerItem[];
8
+ depth?: number;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function Sidebar(): import("react").JSX.Element;
@@ -0,0 +1,46 @@
1
+ import { DragEvent, KeyboardEvent, MouseEvent, ReactNode, RefObject } from 'react';
2
+ import { DropTargetId, FileManagerItem, FileManagerNode, FileManagerView } from './types';
3
+ export interface FileManagerContextValue {
4
+ nodes: FileManagerNode[];
5
+ folderId: string | null;
6
+ viewFolderId: string | null;
7
+ expandedIds: Set<string>;
8
+ dropTargetId: DropTargetId;
9
+ selectedIds: string[];
10
+ selectedNode: FileManagerItem | null;
11
+ focusedIndex: number;
12
+ view: FileManagerView;
13
+ searchQuery: string;
14
+ canManage: boolean;
15
+ rootLabel: string;
16
+ isBusy: boolean;
17
+ items: FileManagerItem[];
18
+ viewItems: FileManagerItem[];
19
+ breadcrumbs: FileManagerNode[];
20
+ showDetails: boolean;
21
+ fileInputRef: RefObject<HTMLInputElement | null>;
22
+ folderDropHandlers: (targetId: string | "root") => {
23
+ onDragEnter: (event: DragEvent<HTMLElement>) => void;
24
+ onDragOver: (event: DragEvent<HTMLElement>) => void;
25
+ onDragLeave: (event: DragEvent<HTMLElement>) => void;
26
+ onDrop: (event: DragEvent<HTMLElement>) => void;
27
+ };
28
+ openFolder: (id: string | null) => void;
29
+ toggleExpanded: (event: MouseEvent, id: string) => void;
30
+ collapseAll: () => void;
31
+ selectItem: (node: FileManagerItem, event?: MouseEvent) => void;
32
+ activateItem: (node: FileManagerItem) => void;
33
+ onDragStart: (node: FileManagerItem, event: DragEvent) => void;
34
+ onInternalDragEnd: () => void;
35
+ onDropOnFolder: (event: DragEvent, targetFolderId: string | null) => void | Promise<void>;
36
+ setView: (view: FileManagerView) => void;
37
+ setSearchQuery: (query: string) => void;
38
+ handleKeyDown: (event: KeyboardEvent<HTMLDivElement>) => void;
39
+ renderIcon?: (node: FileManagerNode) => ReactNode;
40
+ renderPreview?: (node: FileManagerItem | null) => ReactNode;
41
+ renderActions?: (node: FileManagerNode) => ReactNode;
42
+ onCreateFolder?: (parentId: string | null) => void;
43
+ onUpload?: (files: File[], folderId: string | null) => void | Promise<void>;
44
+ }
45
+ export declare const FileManagerContext: import('react').Context<FileManagerContextValue | null>;
46
+ export declare function useFileManagerContext(): FileManagerContextValue;
@@ -0,0 +1,16 @@
1
+ import { DragEvent, Dispatch, SetStateAction } from 'react';
2
+ import { DropTargetId } from './types';
3
+ export interface FolderDropTargetOptions {
4
+ canManage: boolean;
5
+ targetId: string | "root";
6
+ setDropTargetId: Dispatch<SetStateAction<DropTargetId>>;
7
+ onDropOnFolder: (event: DragEvent, targetFolderId: string | null) => void | Promise<void>;
8
+ onHoverExpand?: () => void;
9
+ clearExpandTimer?: () => void;
10
+ }
11
+ export declare function folderDropTargetHandlers({ canManage, targetId, setDropTargetId, onDropOnFolder, onHoverExpand, clearExpandTimer }: FolderDropTargetOptions): {
12
+ onDragEnter: (event: DragEvent<HTMLElement>) => void;
13
+ onDragOver: (event: DragEvent<HTMLElement>) => void;
14
+ onDragLeave: (event: DragEvent<HTMLElement>) => void;
15
+ onDrop: (event: DragEvent<HTMLElement>) => void;
16
+ };
@@ -0,0 +1,14 @@
1
+ import { SVGProps } from 'react';
2
+ type IconProps = SVGProps<SVGSVGElement> & {
3
+ size?: number;
4
+ };
5
+ export declare function FolderIcon(props: IconProps): import("react").JSX.Element;
6
+ export declare function FileIcon(props: IconProps): import("react").JSX.Element;
7
+ export declare function HomeIcon(props: IconProps): import("react").JSX.Element;
8
+ export declare function ChevronRightIcon(props: IconProps): import("react").JSX.Element;
9
+ export declare function ListIcon(props: IconProps): import("react").JSX.Element;
10
+ export declare function CardsIcon(props: IconProps): import("react").JSX.Element;
11
+ export declare function CollapseIcon(props: IconProps): import("react").JSX.Element;
12
+ export declare function SearchIcon(props: IconProps): import("react").JSX.Element;
13
+ export declare function UploadIcon(props: IconProps): import("react").JSX.Element;
14
+ export {};
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react/jsx-runtime"),t=require("react");function n({size:e=16,className:t,...n}){return{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:1.8,strokeLinecap:`round`,strokeLinejoin:`round`,width:e,height:e,className:t,"aria-hidden":!0,...n}}function r(t){return(0,e.jsx)(`svg`,{...n(t),children:(0,e.jsx)(`path`,{d:`M3 7.5A1.5 1.5 0 0 1 4.5 6h4.2l1.8 1.8H19.5A1.5 1.5 0 0 1 21 9.3v7.2A1.5 1.5 0 0 1 19.5 18h-15A1.5 1.5 0 0 1 3 16.5z`})})}function i(t){return(0,e.jsxs)(`svg`,{...n(t),children:[(0,e.jsx)(`path`,{d:`M7 3.5h7l5 5V20a1.5 1.5 0 0 1-1.5 1.5h-10.5A1.5 1.5 0 0 1 5.5 20V5A1.5 1.5 0 0 1 7 3.5z`}),(0,e.jsx)(`path`,{d:`M14 3.5V9h5.5`})]})}function a(t){return(0,e.jsxs)(`svg`,{...n(t),children:[(0,e.jsx)(`path`,{d:`M4 11.5 12 4.5l8 7`}),(0,e.jsx)(`path`,{d:`M6.5 10.5V19h11v-8.5`})]})}function o(t){return(0,e.jsx)(`svg`,{...n(t),children:(0,e.jsx)(`path`,{d:`m9 6 6 6-6 6`})})}function s(t){return(0,e.jsxs)(`svg`,{...n(t),children:[(0,e.jsx)(`path`,{d:`M8 7h12M8 12h12M8 17h12`}),(0,e.jsx)(`path`,{d:`M4 7h.01M4 12h.01M4 17h.01`})]})}function c(t){return(0,e.jsx)(`svg`,{...n(t),children:(0,e.jsx)(`path`,{d:`M4 5h7v7H4zM13 5h7v7h-7zM4 14h7v6H4zM13 14h7v6h-7z`})})}function l(t){return(0,e.jsxs)(`svg`,{...n(t),children:[(0,e.jsx)(`path`,{d:`M8 4H4v4M16 4h4v4M8 20H4v-4M16 20h4v-4`}),(0,e.jsx)(`path`,{d:`m14 10-4 4m0-4 4 4`})]})}function u(t){return(0,e.jsxs)(`svg`,{...n(t),children:[(0,e.jsx)(`circle`,{cx:`11`,cy:`11`,r:`6.5`}),(0,e.jsx)(`path`,{d:`m16 16 4 4`})]})}function d(t){return(0,e.jsxs)(`svg`,{...n(t),children:[(0,e.jsx)(`path`,{d:`M12 16V5`}),(0,e.jsx)(`path`,{d:`m8 9 4-4 4 4`}),(0,e.jsx)(`path`,{d:`M5 19h14`})]})}var f=(0,t.createContext)(null);function p(){let e=(0,t.useContext)(f);if(!e)throw Error(`FileManager components must be used inside <FileManager>`);return e}function m(...e){return e.filter(Boolean).join(` `)}var h=`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rfm-primary`,g=`transition-colors duration-150`;m(`inline-flex shrink-0 items-center justify-center rounded-md border-0 bg-transparent text-gray-500 hover:bg-black/[0.06] hover:text-gray-900`,g,h);var _=`bg-rfm-hover outline-dashed outline-2 outline-rfm-primary`;function v(){let{breadcrumbs:t,dropTargetId:n,rootLabel:r,openFolder:i,folderDropHandlers:a}=p();return(0,e.jsxs)(`nav`,{className:`flex min-w-0 flex-1 items-center gap-0.5 overflow-x-auto overflow-y-hidden text-[13px] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden`,"aria-label":`Breadcrumb`,children:[(0,e.jsx)(`button`,{type:`button`,className:m(`cursor-pointer whitespace-nowrap rounded-sm border-0 bg-transparent p-0 text-gray-700 hover:text-rfm-primary`,n===`root`?_:``),onClick:()=>i(null),...a(`root`),children:r}),t.map(t=>(0,e.jsxs)(`span`,{className:`inline-flex items-center gap-1`,children:[(0,e.jsx)(o,{className:`text-gray-400`,size:14}),(0,e.jsx)(`button`,{type:`button`,className:m(`cursor-pointer whitespace-nowrap rounded-sm border-0 bg-transparent p-0 text-gray-700 hover:text-rfm-primary`,n===t.id?_:``),onClick:()=>i(t.id),...a(t.id),children:t.name})]},t.id))]})}function y(e,t){for(let n of e){if(n.id===t)return n;if(n.children?.length){let e=y(n.children,t);if(e)return e}}return null}function b(e,t){return e.id===t||(e.children??[]).some(e=>b(e,t))}function x(e){return(e.children??[]).some(e=>e.kind===`folder`)}function S(e,t){return t?y(e,t)?.children??[]:e}function C(e,t){return[...S(e,t)].sort((e,t)=>e.kind===t.kind?e.name.localeCompare(t.name):e.kind===`folder`?-1:1)}function w(e,t){if(!t)return[];let n=[],r=e=>{for(let i of e){if(i.id===t)return n.push(i),!0;if(i.kind===`folder`&&r(i.children??[]))return n.unshift(i),!0}return!1};return r(e),n}function T(e,t){return[t,...e].join(` / `)}function ee(e,t,n=`My files`){let r=t.trim().toLowerCase();if(!r)return[];let i=[],a=(e,t)=>{let o=T(t,n);for(let n of e)n.name.toLowerCase().includes(r)&&i.push({...n,path:o}),n.kind===`folder`&&a(n.children??[],[...t,n.name])};return a(e,[]),i}function E(e){if(e.kind!==`folder`)return e.extension?e.extension.replace(/^\./,``).toLowerCase():e.name.match(/\.([^.]+)$/)?.[1]?.toLowerCase()}function te(e,t,n){let r=new Set(t),i=[];for(let r of t){let t=y(e,r);if(t&&t.kind===`folder`&&n&&b(t,n))return e}let a=e=>e.flatMap(e=>r.has(e.id)?(i.push(e),[]):e.children?[{...e,children:a(e.children)}]:[e]),o=a(e);if(!i.length)return e;if(!n)return[...o,...i];let s=e=>e.map(e=>e.id===n?{...e,children:[...e.children??[],...i]}:e.children?{...e,children:s(e.children)}:e);return s(o)}function D({item:t,index:n}){let{view:a,selectedIds:o,focusedIndex:s,dropTargetId:c,canManage:l,selectItem:u,activateItem:d,onDragStart:f,folderDropHandlers:h,renderIcon:v,renderActions:y}=p(),b=o.includes(t.id),x=s===n,S=t.kind===`folder`,C=c===t.id&&S,w=S?t.children?.length??0:null,T=E(t);return(0,e.jsx)(`div`,{className:`rfm-row min-w-0`,children:(0,e.jsxs)(`div`,{draggable:l,role:`option`,"aria-selected":b,"aria-label":t.name,className:m(`cursor-pointer`,a===`cards`?`group/card relative flex min-h-[116px] flex-col items-center justify-center gap-2 overflow-hidden rounded-xl border border-gray-200 p-3 text-center shadow-[0_1px_2px_rgba(16,24,40,0.05)] transition duration-150 hover:-translate-y-px hover:border-gray-300 hover:shadow-[0_4px_12px_rgba(16,24,40,0.08)]`:m(`group/row grid grid-cols-[22px_minmax(0,1fr)_auto_auto] items-center gap-2 rounded-lg px-2.5 py-2`,g),b?`bg-rfm-hover`:a===`list`?`hover:bg-gray-100`:`bg-white`,x&&a===`list`?`outline outline-1 outline-rfm-primary`:``,C?_:``),onDragStart:e=>f(t,e),...S?h(t.id):{},onClick:e=>u(t,e),onDoubleClick:()=>d(t),children:[v?.(t)??(S?(0,e.jsx)(r,{size:a===`cards`?36:18}):(0,e.jsx)(i,{size:a===`cards`?36:18})),(0,e.jsxs)(`span`,{className:`flex min-w-0 flex-col gap-px`,children:[(0,e.jsx)(`span`,{className:m(`min-w-0 text-[13px] text-gray-900`,a===`cards`?`line-clamp-2 w-full break-words text-center`:`truncate`),children:t.name}),t.path&&(0,e.jsx)(`span`,{className:`min-w-0 truncate text-[11px] text-gray-500`,children:t.path})]}),a===`list`&&(0,e.jsx)(`span`,{className:`text-[11px] text-gray-500`,children:S?`${w} item${w===1?``:`s`}`:(T||`file`).toUpperCase()}),y&&(0,e.jsx)(`span`,{className:m(`rfm-more`,`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rfm-primary`,b||x?`opacity-100`:``,a===`cards`?`absolute right-1.5 top-1.5`:``),children:y(t)})]})})}function O({folders:t,depth:n=0}){let{viewFolderId:i,dropTargetId:a,expandedIds:o,openFolder:s,toggleExpanded:c,folderDropHandlers:l,renderIcon:u,renderActions:d}=p();return(0,e.jsx)(e.Fragment,{children:t.filter(e=>e.kind===`folder`).map(t=>{let f=x(t),p=o.has(t.id),v=i===t.id,y=a===t.id;return(0,e.jsxs)(`div`,{className:`min-w-0`,children:[(0,e.jsxs)(`div`,{role:`treeitem`,"aria-selected":v,"aria-label":t.name,tabIndex:0,className:m(`rfm-tree-row group/tree mb-0.5 flex w-full cursor-pointer items-center gap-1 rounded-md py-1 pr-1 text-left text-[13px]`,v?`bg-rfm-hover font-semibold text-rfm-primary`:`text-gray-900 hover:bg-black/[0.04]`,y?_:``,g,h),style:{paddingLeft:6+n*12},onClick:()=>s(t.id),...l(t.id),children:[(0,e.jsx)(`button`,{type:`button`,className:m(`inline-flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded border-0 bg-transparent p-0 text-gray-500`,p?`rotate-90`:``,f?`cursor-pointer hover:bg-black/[0.06] hover:text-gray-900`:`invisible pointer-events-none`,g,h),"aria-label":p?`Collapse folder`:`Expand folder`,disabled:!f,onClick:e=>{f&&c(e,t.id)},children:(0,e.jsx)(k,{})}),u?.(t)??(0,e.jsx)(r,{size:16}),(0,e.jsx)(`span`,{className:`min-w-0 flex-1 truncate`,title:t.name,children:t.name}),d&&(0,e.jsx)(`span`,{className:`rfm-more`,children:d(t)})]}),f&&p&&(0,e.jsx)(`div`,{className:`min-w-0`,children:(0,e.jsx)(O,{folders:t.children??[],depth:n+1})})]},t.id)})})}function k(){return(0,e.jsx)(`svg`,{viewBox:`0 0 24 24`,width:14,height:14,fill:`none`,stroke:`currentColor`,strokeWidth:`1.8`,"aria-hidden":!0,children:(0,e.jsx)(`path`,{d:`m9 6 6 6-6 6`})})}function A(){let{view:t,searchQuery:n,canManage:r,isBusy:i,items:a,viewItems:o,viewFolderId:l,folderId:d,selectedIds:f,setView:g,setSearchQuery:_,onDropOnFolder:y,onUpload:b,fileInputRef:x}=p(),S=t===`cards`?`grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] content-start gap-2.5 p-3`:`flex flex-col gap-0.5 px-2.5 pb-4 pt-2`,C=o!==a,w=C?o:a;return(0,e.jsxs)(`section`,{className:`relative flex min-h-0 min-w-0 flex-col overflow-hidden bg-white`,onDragOver:e=>{r&&(e.preventDefault(),e.dataTransfer.dropEffect=`move`)},onDrop:e=>{r&&y(e,l)},children:[(0,e.jsxs)(`div`,{className:`flex shrink-0 flex-wrap items-center justify-between gap-2 border-b border-black/[0.06] px-3 py-2.5`,children:[(0,e.jsx)(v,{}),(0,e.jsxs)(`div`,{className:`ml-auto flex items-center gap-2`,children:[(0,e.jsxs)(`label`,{className:`relative`,children:[(0,e.jsx)(u,{className:`pointer-events-none absolute top-1/2 left-2 -translate-y-1/2 text-gray-400`,size:14}),(0,e.jsx)(`input`,{type:`search`,value:n,placeholder:`Search`,"aria-label":`Search files`,className:m(`h-7 w-40 rounded-md border border-gray-200 bg-gray-50 pr-2 pl-7 text-[13px] text-gray-900 outline-none placeholder:text-gray-400 focus:border-rfm-primary`,h),onChange:e=>_(e.target.value)})]}),(0,e.jsxs)(`div`,{className:`flex gap-1`,children:[(0,e.jsx)(M,{label:`List view`,pressed:t===`list`,onClick:()=>g(`list`),children:(0,e.jsx)(s,{size:16})}),(0,e.jsx)(M,{label:`Card view`,pressed:t===`cards`,onClick:()=>g(`cards`),children:(0,e.jsx)(c,{size:16})})]})]})]}),(0,e.jsxs)(`div`,{className:`relative min-h-0 flex-1 overflow-hidden`,children:[(0,e.jsx)(j,{items:w,className:S,emptyLabel:n.trim()&&!C?`No matching files`:`This folder is empty`,showEmptyActions:!n.trim()||C,folderId:C?l:d,isBusy:i}),f.length>1&&(0,e.jsxs)(`div`,{className:`pointer-events-none absolute bottom-4 left-1/2 z-10 flex -translate-x-1/2 items-center gap-2 rounded-full bg-gray-900 py-1.5 px-4 text-xs text-white shadow-[0_12px_32px_rgba(15,23,42,0.35)]`,children:[f.length,` selected`]})]}),r&&b&&(0,e.jsx)(`input`,{ref:x,type:`file`,multiple:!0,className:`hidden`,"aria-label":`Upload files`,onChange:e=>{e.target.files?.length&&(b(Array.from(e.target.files),d),e.target.value=``)}})]})}function j({items:t,className:n,emptyLabel:i,showEmptyActions:a,folderId:o,isBusy:s}){let{view:c,canManage:l,onCreateFolder:u,fileInputRef:f}=p();return(0,e.jsxs)(`div`,{className:`relative h-full overflow-y-auto overscroll-contain`,children:[s&&(0,e.jsx)(`div`,{className:`absolute inset-0 z-20 bg-white/50`,"aria-busy":`true`}),(0,e.jsx)(`div`,{className:n,role:`listbox`,"aria-label":`Folder contents`,children:t.length===0?(0,e.jsxs)(`div`,{className:m(`flex min-h-[240px] flex-col items-center justify-center gap-3 p-6 text-center text-[13px] text-gray-500`,c===`cards`?`col-span-full`:``),children:[(0,e.jsx)(`p`,{className:`m-0`,children:i}),a&&l&&(0,e.jsxs)(`div`,{className:`flex flex-wrap justify-center gap-2`,children:[(0,e.jsxs)(`button`,{type:`button`,className:m(`inline-flex cursor-pointer items-center gap-1.5 rounded-lg border-0 bg-gray-900 px-3 py-2 text-[13px] font-semibold text-white hover:bg-gray-700`,`transition-colors duration-150`,`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rfm-primary`),onClick:()=>f.current?.click(),children:[(0,e.jsx)(d,{size:16}),`Upload files`]}),u&&(0,e.jsxs)(`button`,{type:`button`,className:m(`inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-gray-200 bg-white px-3 py-2 text-[13px] font-semibold text-gray-700 hover:border-gray-300 hover:bg-gray-50`,`transition-colors duration-150`,`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rfm-primary`),onClick:()=>u(o),children:[(0,e.jsx)(r,{size:16}),`Create folder`]})]})]}):t.map((t,n)=>(0,e.jsx)(D,{item:t,index:n},t.id))})]})}function M({label:t,pressed:n,onClick:r,children:i}){return(0,e.jsx)(`button`,{type:`button`,className:m(`inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border-0`,n?`bg-rfm-primary text-white`:`bg-transparent text-gray-500 hover:bg-black/5`,g,h),"aria-label":t,"aria-pressed":n,onClick:r,children:i})}function N(){let{selectedNode:t,renderIcon:n,renderPreview:a,renderActions:o}=p();if(a)return(0,e.jsx)(`aside`,{className:`flex min-h-0 flex-col overflow-hidden border-l border-black/[0.06] bg-gray-50`,children:(0,e.jsx)(`div`,{className:`flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain p-4`,children:a(t)})});if(!t)return(0,e.jsx)(`aside`,{className:`flex min-h-0 flex-col overflow-hidden border-l border-black/[0.06] bg-gray-50`,children:(0,e.jsxs)(`div`,{className:`flex h-full flex-col items-center justify-center gap-3 px-2 text-center text-[13px] text-gray-500`,children:[(0,e.jsx)(r,{size:56}),(0,e.jsx)(`p`,{className:`m-0`,children:`Select a file or folder to view details`}),(0,e.jsx)(`p`,{className:`m-0 max-w-[180px] text-[11px] leading-snug text-gray-400`,children:`↑↓ navigate · Shift range · ⌘/Ctrl toggle · Enter open · Esc clear`})]})});let s=t.kind===`folder`,c=E(t),l=t.children?.length??0;return(0,e.jsx)(`aside`,{className:`flex min-h-0 flex-col overflow-hidden border-l border-black/[0.06] bg-gray-50`,children:(0,e.jsxs)(`div`,{className:`flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain p-4`,children:[(0,e.jsxs)(`div`,{className:`relative flex min-h-[220px] flex-1 flex-col items-center justify-center rounded-lg bg-gray-100/80 px-4 py-5`,children:[n?.(t)??(s?(0,e.jsx)(r,{size:64}):(0,e.jsx)(i,{size:64})),(0,e.jsx)(`span`,{className:`mt-2 text-xs text-gray-500`,children:s?`${l} item${l===1?``:`s`}`:(c||`file`).toUpperCase()})]}),(0,e.jsxs)(`div`,{className:`mt-3`,children:[(0,e.jsx)(`h3`,{className:`m-0 mb-2.5 break-words text-sm font-semibold text-gray-900`,children:t.name}),t.path&&(0,e.jsx)(`p`,{className:`m-0 -mt-1.5 mb-2.5 break-words text-xs text-gray-500`,children:t.path}),(0,e.jsxs)(`dl`,{className:`m-0`,children:[(0,e.jsxs)(`div`,{className:`flex justify-between gap-3 border-b border-gray-200/70 py-2 text-xs`,children:[(0,e.jsx)(`dt`,{className:`text-gray-500`,children:`Type`}),(0,e.jsx)(`dd`,{className:`m-0 font-medium text-gray-900`,children:s?`Folder`:(c||`file`).toUpperCase()})]}),s&&(0,e.jsxs)(`div`,{className:`flex justify-between gap-3 py-2 text-xs`,children:[(0,e.jsx)(`dt`,{className:`text-gray-500`,children:`Items`}),(0,e.jsx)(`dd`,{className:`m-0 font-medium text-gray-900`,children:l})]})]}),o&&(0,e.jsx)(`div`,{className:m(`mt-3`,`transition-colors duration-150`,`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rfm-primary`),children:o(t)})]})]})})}function P(){let{nodes:t,viewFolderId:n,dropTargetId:r,canManage:i,rootLabel:o,openFolder:s,collapseAll:c,folderDropHandlers:u,onCreateFolder:d}=p();return(0,e.jsxs)(`aside`,{className:`flex min-h-0 flex-col gap-2 overflow-hidden border-r border-black/[0.06] bg-gray-100/80 p-3`,children:[i&&d&&(0,e.jsx)(`button`,{type:`button`,className:m(`flex h-9 w-full shrink-0 cursor-pointer items-center justify-center gap-1.5 rounded-lg border-0 bg-rfm-primary text-sm font-semibold text-white hover:brightness-95`,`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rfm-primary`),onClick:()=>d(n),children:`New folder`}),(0,e.jsx)(`div`,{className:`flex shrink-0 justify-end`,children:(0,e.jsxs)(`button`,{type:`button`,className:m(`inline-flex cursor-pointer items-center gap-1 rounded-md border-0 bg-transparent px-1.5 py-1 text-[11px] font-semibold text-gray-500 hover:bg-black/5 hover:text-gray-900`,g,h),onClick:c,children:[(0,e.jsx)(l,{size:14}),`Collapse all`]})}),(0,e.jsxs)(`div`,{className:`min-h-0 flex-1 overflow-y-auto overscroll-contain`,children:[(0,e.jsxs)(`button`,{type:`button`,className:m(`flex w-full cursor-pointer items-center gap-1 rounded-md border-0 bg-transparent py-1 text-left text-[13px]`,n===null?`bg-rfm-hover font-semibold text-rfm-primary`:`text-gray-900 hover:bg-black/[0.04]`,r===`root`?_:``,g,h),style:{paddingLeft:8},onClick:()=>s(null),...u(`root`),children:[(0,e.jsx)(`span`,{className:`invisible inline-block h-[18px] w-[18px] shrink-0`}),(0,e.jsx)(a,{className:`h-4 w-4 shrink-0`,size:16}),(0,e.jsx)(`span`,{className:`min-w-0 flex-1 truncate`,children:o})]}),(0,e.jsx)(`div`,{role:`tree`,children:(0,e.jsx)(O,{folders:t})})]})]})}function F(e){let t=e.relatedTarget;return!(t&&e.currentTarget.contains(t))}function ne({canManage:e,targetId:t,setDropTargetId:n,onDropOnFolder:r,onHoverExpand:i,clearExpandTimer:a}){let o=t===`root`?null:t;return{onDragEnter:r=>{e&&F(r)&&(r.preventDefault(),n(t),i?.())},onDragOver:r=>{e&&(r.preventDefault(),r.dataTransfer.dropEffect=`move`,n(t))},onDragLeave:e=>{F(e)&&(n(e=>e===t?null:e),a?.())},onDrop:e=>{r(e,o)}}}function re(e,t,n){let r=Math.min(t,n),i=Math.max(t,n);return e.slice(r,i+1).map(e=>e.id)}var ie=`application/x-file-manager-item`;function I(e,n,r){let[i,a]=(0,t.useState)(n),o=e!==void 0;return[o?e:i,(0,t.useCallback)(e=>{o||a(e),r?.(e)},[o,r])]}function ae(e){let{nodes:n,canManage:r=!0,rootLabel:i=`My files`,showDetails:a=!0,springLoadDelay:o=500,isBusy:s=!1,onMove:c,onOpenFile:l,onOpenFolder:u,onUpload:d,onCreateFolder:f,onDelete:p,renderIcon:m,renderPreview:h,renderActions:g}=e,[_,v]=I(e.folderId,e.defaultFolderId??null,e.onFolderChange),[S,T]=I(e.selectedIds,e.defaultSelectedIds??[],e.onSelectionChange),[E,te]=I(e.view,e.defaultView??`list`,e.onViewChange),[D,O]=I(e.searchQuery,e.defaultSearchQuery??``,e.onSearchChange),k=(0,t.useDeferredValue)(D),[A,j]=(0,t.useState)(()=>new Set),[M,N]=(0,t.useState)(null),[P,F]=(0,t.useState)(-1),[ae,L]=(0,t.useState)(null),[R,z]=(0,t.useState)(void 0),oe=(0,t.useRef)(null),B=(0,t.useRef)(null),V=(0,t.useRef)([]),H=(0,t.useRef)(!1),U=(0,t.useRef)(0),W=(0,t.useRef)(null),G=(0,t.useRef)(null),K=(0,t.useRef)(null),q=(0,t.useRef)(null),J=(0,t.useRef)(!1);V.current=S;let Y=R===void 0?_:R,X=(0,t.useMemo)(()=>k.trim()&&R===void 0?ee(n,k,i):C(n,_),[k,_,n,i,R]),se=(0,t.useMemo)(()=>R===void 0?X:C(n,R),[X,n,R]),ce=(0,t.useMemo)(()=>w(n,Y),[n,Y]);(0,t.useEffect)(()=>{let e=ce.map(e=>e.id);e.length&&j(t=>{let n=new Set(t),r=!1;for(let t of e)n.has(t)||(n.add(t),r=!0);return r?n:t})},[ce]),(0,t.useEffect)(()=>{F(-1),U.current=0},[_,k,E]),(0,t.useEffect)(()=>()=>{W.current&&clearTimeout(W.current),K.current&&clearTimeout(K.current)},[]);let Z=(0,t.useCallback)(()=>{O(``)},[O]),le=(0,t.useCallback)(e=>{v(e),N(null),T([]),Z(),u?.(e)},[Z,u,v,T]),Q=(0,t.useCallback)(()=>{W.current&&=(clearTimeout(W.current),null),G.current=null,K.current&&=(clearTimeout(K.current),null),q.current=null},[]),ue=(0,t.useCallback)(e=>{A.has(e)||(B.current?.kind!==`folder`||B.current.id!==e)&&G.current!==e&&(W.current&&clearTimeout(W.current),G.current=e,W.current=setTimeout(()=>{G.current=null,j(t=>{if(t.has(e))return t;let n=new Set(t);return n.add(e),n})},o))},[A,o]),de=(0,t.useCallback)(e=>{if(!B.current||(R===void 0?_:R)===e)return;if(e!==null&&B.current.kind===`folder`){if(B.current.id===e)return;let t=y(n,B.current.id);if(t&&b(t,e))return}let t=e===null?`root`:`folder-${e}`;q.current!==t&&(K.current&&clearTimeout(K.current),q.current=t,K.current=setTimeout(()=>{q.current=null,z(e),L(null)},o))},[_,n,R,o]),fe=(0,t.useCallback)((e,t)=>{r&&(H.current=!0,J.current=!1,t.dataTransfer.effectAllowed=`move`,t.dataTransfer.setData(ie,JSON.stringify({id:e.id,kind:e.kind})),t.dataTransfer.setData(`text/plain`,`${e.kind}:${e.id}`),B.current={id:e.id,kind:e.kind},V.current.includes(e.id)||(T([e.id]),N(e)))},[r,T]),pe=(0,t.useCallback)(async(e,t)=>{if(e.preventDefault(),e.stopPropagation(),J.current=!0,L(null),Q(),z(void 0),v(t),e.dataTransfer.files.length){await d?.(Array.from(e.dataTransfer.files),t);return}let n=B.current;if(B.current=null,!n||!r)return;let i=V.current,a=i.length>1&&i.includes(n.id)?i:[n.id];await c?.(a,t),T([]),N(null)},[r,Q,c,d,v,T]),me=(0,t.useCallback)(()=>{Q(),L(null),J.current||z(void 0),J.current=!1,B.current=null},[Q]),he=(0,t.useCallback)(e=>{let t=e===`root`?null:e,i=e===`root`?null:y(n,e);return ne({canManage:r,targetId:e,setDropTargetId:L,onDropOnFolder:pe,onHoverExpand:()=>{i&&x(i)&&ue(e),de(t)},clearExpandTimer:Q})},[r,Q,n,pe,ue,de]),ge=(0,t.useCallback)((e,t)=>{e.stopPropagation(),j(e=>{let n=new Set(e);return n.has(t)?n.delete(t):n.add(t),n})},[]),_e=(0,t.useCallback)(()=>{j(new Set)},[]),ve=(0,t.useCallback)((e,t)=>{if(H.current){H.current=!1;return}let n=X.findIndex(t=>t.id===e.id);if(t?.shiftKey&&n>=0){let t=re(X,U.current,n);F(n),T(t),N(e);return}if(t?.metaKey||t?.ctrlKey){n>=0&&(F(n),U.current=n),T(V.current.includes(e.id)?V.current.filter(t=>t!==e.id):[...V.current,e.id]),N(e);return}n>=0&&(F(n),U.current=n),T([e.id]),N(e)},[X,T]),$=(0,t.useCallback)(e=>{e.kind===`folder`?le(e.id):l?.(e.id)},[l,le]);return{nodes:n,folderId:_,viewFolderId:Y,expandedIds:A,dropTargetId:ae,selectedIds:S,selectedNode:M,focusedIndex:P,view:E,searchQuery:D,canManage:r,rootLabel:i,isBusy:s,items:X,viewItems:se,breadcrumbs:ce,showDetails:a,fileInputRef:oe,folderDropHandlers:he,openFolder:le,toggleExpanded:ge,collapseAll:_e,selectItem:ve,activateItem:$,onDragStart:fe,onInternalDragEnd:me,onDropOnFolder:pe,setView:te,setSearchQuery:O,handleKeyDown:(0,t.useCallback)(e=>{let t=e.target;if(t.tagName===`INPUT`||t.tagName===`TEXTAREA`||t.isContentEditable){e.key===`Escape`&&Z();return}let n=R===void 0?X:se;if(!n.length){e.key===`Escape`&&D&&Z();return}if(e.key===`ArrowDown`||e.key===`ArrowUp`){e.preventDefault();let t=e.key===`ArrowDown`?1:-1,r=Math.min(Math.max((P<0?t>0?-1:0:P)+t,0),n.length-1);F(r),e.shiftKey||(U.current=r);let i=n[r];if(!i)return;e.shiftKey&&T(re(n,U.current,r)),N(i);return}if(e.key===`Enter`){e.preventDefault();let t=n[P];t&&$(t);return}if(e.key===` `){e.preventDefault();let t=n[P];t&&ve(t);return}if(e.key===`Escape`){if(D){Z();return}N(null),T([]);return}if((e.key===`Delete`||e.key===`Backspace`)&&r){let t=S.length>0?S:n[P]?[n[P].id]:[];if(!t.length)return;e.preventDefault(),p?.(t)}},[$,r,Z,P,X,p,D,ve,S,T,R,se]),renderIcon:m,renderPreview:h,renderActions:g,onCreateFolder:f,onUpload:d}}function L(t){let n=ae(t),r=n.showDetails?`grid-cols-[216px_minmax(0,1fr)_280px]`:`grid-cols-[216px_minmax(0,1fr)]`;return(0,e.jsx)(f.Provider,{value:n,children:(0,e.jsxs)(`div`,{className:m(`rfm-root relative grid h-full min-h-0 overflow-hidden outline-none`,r,t.className),tabIndex:0,"aria-label":`File manager`,onKeyDown:n.handleKeyDown,onDragEnd:n.onInternalDragEnd,children:[(0,e.jsx)(P,{}),(0,e.jsx)(A,{}),n.showDetails&&(0,e.jsx)(N,{})]})})}exports.FileManager=L,exports.folderContainsId=b,exports.folderDropTargetHandlers=ne,exports.folderHasChildFolders=x,exports.getBreadcrumbs=w,exports.getExtension=E,exports.getFolderContents=S,exports.getNodeById=y,exports.listFolder=C,exports.moveNodes=te,exports.searchNodes=ee;
@@ -0,0 +1,4 @@
1
+ export { FileManager } from './FileManager';
2
+ export { folderDropTargetHandlers } from './dropTarget';
3
+ export { folderContainsId, folderHasChildFolders, getBreadcrumbs, getExtension, getFolderContents, getNodeById, listFolder, moveNodes, searchNodes } from './tree';
4
+ export type { DropTargetId, FileManagerItem, FileManagerKind, FileManagerNode, FileManagerProps, FileManagerView } from './types';