File Manager
blockmediaStorage/files page with breadcrumbs, search, grid/list view toggle, upload action, and a storage meter over hardcoded folders and files.
Installation
$pnpm dlx uipkge-ng@latest add file-manager$npx uipkge-ng@latest add file-manager$yarn dlx uipkge-ng@latest add file-manager$bunx uipkge-ng@latest add file-managerManifest:
https://uipkge.dev/r/angular/file-manager.jsonInstalls to:src/app/components/blocks/file-manager/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
FolderIteminterface FolderItem {
id: string
name: string
count: number
}FileIteminterface FileItem {
id: string
name: string
kind: FileKind
size: string
modified: string
}npm dependencies
Files installed (2)
src/app/components/blocks/file-manager/file-manager.component.ts12.4 kB
// Storage/files page with breadcrumbs, search, grid/list view toggle, upload action, // and a storage meter over hardcoded folders and files. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core' import { Download, FileArchive, FileImage, FileSpreadsheet, FileText, FileVideo, Folder, LayoutGrid, List, LucideAngularModule, MoreVertical, Pencil, Search, Share2, Trash2, Upload, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBreadcrumbComponent, UiBreadcrumbItemComponent, UiBreadcrumbLinkComponent, UiBreadcrumbListComponent, UiBreadcrumbPageComponent, UiBreadcrumbSeparatorComponent, } from '@/ui/breadcrumb/breadcrumb.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiDropdownMenuComponent, UiDropdownMenuContentComponent, UiDropdownMenuItemComponent, UiDropdownMenuSeparatorComponent, UiDropdownMenuTriggerComponent, } from '@/ui/dropdown-menu/dropdown-menu.component' import { UiInputComponent } from '@/ui/input/input.component' import { UiProgressComponent } from '@/ui/progress/progress.component' import { UiToggleGroupComponent, UiToggleGroupItemComponent } from '@/ui/toggle-group/toggle-group.component' export type FileKind = 'pdf' | 'image' | 'sheet' | 'video' | 'archive' export interface FolderItem { id: string name: string count: number } export interface FileItem { id: string name: string kind: FileKind size: string modified: string } export type FileManagerRow = (FolderItem & { isFolder: true }) | (FileItem & { isFolder: false }) const FOLDERS: FolderItem[] = [ { id: 'folder-design-assets', name: 'Design Assets', count: 24 }, { id: 'folder-client-files', name: 'Client Files', count: 12 }, { id: 'folder-brand-kit', name: 'Brand Kit', count: 8 }, { id: 'folder-archive', name: 'Archive', count: 56 }, ] const FILES: FileItem[] = [ { id: 'file-brand-guidelines', name: 'brand-guidelines.pdf', kind: 'pdf', size: '4.2 MB', modified: 'Mar 14, 2026' }, { id: 'file-homepage-hero', name: 'homepage-hero.png', kind: 'image', size: '2.8 MB', modified: 'Mar 12, 2026' }, { id: 'file-logo-final', name: 'logo-final.svg', kind: 'image', size: '48 KB', modified: 'Mar 10, 2026' }, { id: 'file-pricing-model', name: 'pricing-model.xlsx', kind: 'sheet', size: '316 KB', modified: 'Mar 8, 2026' }, { id: 'file-launch-video', name: 'launch-video.mp4', kind: 'video', size: '184 MB', modified: 'Mar 5, 2026' }, { id: 'file-app-iconset', name: 'app-iconset.zip', kind: 'archive', size: '12.4 MB', modified: 'Feb 27, 2026' }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-file-manager, [ui-file-manager]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBreadcrumbComponent, UiBreadcrumbItemComponent, UiBreadcrumbLinkComponent, UiBreadcrumbListComponent, UiBreadcrumbPageComponent, UiBreadcrumbSeparatorComponent, UiButtonComponent, UiDropdownMenuComponent, UiDropdownMenuContentComponent, UiDropdownMenuItemComponent, UiDropdownMenuSeparatorComponent, UiDropdownMenuTriggerComponent, UiInputComponent, UiProgressComponent, UiToggleGroupComponent, UiToggleGroupItemComponent, ], template: ` <div data-slot="file-manager" [class]="rootClass"> <div class="bg-card text-card-foreground rounded-xl border shadow-xs"> <div class="flex flex-wrap items-center gap-3 border-b p-4"> <ui-breadcrumb> <ui-breadcrumb-list> <ui-breadcrumb-item> <ui-breadcrumb-link href="#" class="inline-flex min-h-6 items-center">Home</ui-breadcrumb-link> </ui-breadcrumb-item> <ui-breadcrumb-separator /> <ui-breadcrumb-item> <ui-breadcrumb-link href="#" class="inline-flex min-h-6 items-center">Documents</ui-breadcrumb-link> </ui-breadcrumb-item> <ui-breadcrumb-separator /> <ui-breadcrumb-item> <ui-breadcrumb-page>Design</ui-breadcrumb-page> </ui-breadcrumb-item> </ui-breadcrumb-list> </ui-breadcrumb> <div class="relative ml-auto w-full max-w-56 sm:w-auto"> <lucide-icon [img]="Search" class="text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2" /> <ui-input type="search" placeholder="Search files…" class="pl-8" /> </div> <ui-toggle-group type="single" size="sm" [value]="view()" (valueChange)="setView($event)" class="bg-muted items-center gap-0.5 rounded-md p-0.5" aria-label="View mode" > <button ui-toggle-group-item value="grid" class="data-[state=on]:bg-background size-7 rounded-sm p-0 data-[state=on]:shadow-sm" title="Grid view" aria-label="Grid view" > <lucide-icon [img]="LayoutGrid" class="size-4" /> </button> <button ui-toggle-group-item value="list" class="data-[state=on]:bg-background size-7 rounded-sm p-0 data-[state=on]:shadow-sm" title="List view" aria-label="List view" > <lucide-icon [img]="List" class="size-4" /> </button> </ui-toggle-group> <button ui-button size="sm"> <lucide-icon [img]="Upload" class="size-4" /> Upload </button> </div> <div class="flex flex-wrap items-center gap-x-4 gap-y-2 border-b px-4 py-3"> <div ui-progress [value]="36.4" class="h-1.5 min-w-40 flex-1 sm:max-w-xs" aria-label="Storage used"></div> <p class="text-muted-foreground text-xs">18.2 GB of 50 GB used</p> </div> @if (view() === 'grid') { <div class="grid grid-cols-2 gap-3 p-4 sm:grid-cols-3 lg:grid-cols-4"> @for (folder of folders; track folder.id) { <button type="button" class="hover:bg-muted/50 focus-visible:ring-ring flex flex-col items-start gap-3 rounded-lg border p-3 text-left shadow-xs transition-colors focus-visible:ring-2 focus-visible:outline-none" > <span class="bg-warning/10 text-warning grid size-9 place-items-center rounded-md"> <lucide-icon [img]="Folder" class="size-5" /> </span> <span class="w-full min-w-0"> <span class="block truncate text-sm font-medium">{{ folder.name }}</span> <span class="text-muted-foreground block text-xs">{{ folder.count }} items</span> </span> </button> } @for (file of files; track file.id) { <button type="button" class="hover:bg-muted/50 focus-visible:ring-ring group flex flex-col items-start gap-3 rounded-lg border p-3 text-left shadow-xs transition-colors focus-visible:ring-2 focus-visible:outline-none" > <span class="bg-muted text-muted-foreground group-hover:bg-background grid size-9 place-items-center rounded-md transition-colors" > <lucide-icon [img]="kindIcon(file.kind)" class="size-5" /> </span> <span class="min-w-0"> <span class="block truncate text-sm font-medium">{{ file.name }}</span> <span class="text-muted-foreground block text-xs">{{ file.size }} · {{ file.modified }}</span> </span> </button> } </div> } @else { <div role="table" aria-label="Files and folders"> <div role="row" class="text-muted-foreground hidden border-b px-4 py-2 text-xs sm:flex"> <span role="columnheader" class="flex-1 pl-9">Name</span> <span role="columnheader" class="w-20 text-right">Size</span> <span role="columnheader" class="w-28 text-right">Modified</span> <span class="w-9"></span> </div> <ul role="rowgroup" class="divide-y"> @for (item of rows(); track item.id) { <li role="row" class="hover:bg-muted/50 flex items-center gap-3 px-4 py-2.5 transition-colors"> <span class="grid size-6 shrink-0 place-items-center rounded-md" [class]="item.isFolder ? 'bg-warning/10 text-warning' : 'bg-muted text-muted-foreground'" > @if (item.isFolder) { <lucide-icon [img]="Folder" class="size-4" /> } @else { <lucide-icon [img]="kindIcon(item.kind)" class="size-4" /> } </span> <span role="cell" class="min-w-0 flex-1 truncate text-sm font-medium">{{ item.name }}</span> <span role="cell" class="text-muted-foreground hidden w-20 shrink-0 text-right text-xs sm:block"> {{ item.isFolder ? item.count + ' items' : item.size }} </span> <span role="cell" class="text-muted-foreground hidden w-28 shrink-0 text-right text-xs md:block"> {{ item.isFolder ? '—' : item.modified }} </span> <ui-dropdown-menu> <button ui-button ui-dropdown-menu-trigger variant="ghost" size="icon-sm" class="shrink-0" [attr.aria-label]="'Actions for ' + item.name" > <lucide-icon [img]="MoreVertical" class="size-4" /> </button> <ui-dropdown-menu-content align="end"> <ui-dropdown-menu-item> <lucide-icon [img]="Download" class="size-4" /> Download </ui-dropdown-menu-item> <ui-dropdown-menu-item> <lucide-icon [img]="Pencil" class="size-4" /> Rename </ui-dropdown-menu-item> <ui-dropdown-menu-item> <lucide-icon [img]="Share2" class="size-4" /> Share link </ui-dropdown-menu-item> <ui-dropdown-menu-separator /> <ui-dropdown-menu-item variant="destructive"> <lucide-icon [img]="Trash2" class="size-4" /> Delete </ui-dropdown-menu-item> </ui-dropdown-menu-content> </ui-dropdown-menu> </li> } </ul> </div> } </div> </div> `, }) export class UiFileManagerComponent { protected readonly Download = Download protected readonly Folder = Folder protected readonly LayoutGrid = LayoutGrid protected readonly List = List protected readonly MoreVertical = MoreVertical protected readonly Pencil = Pencil protected readonly Search = Search protected readonly Share2 = Share2 protected readonly Trash2 = Trash2 protected readonly Upload = Upload @Input() set variant(v: 'grid' | 'list') { if (v === 'grid' || v === 'list') this.view.set(v) } @Input('class') className?: string readonly view = signal<'grid' | 'list'>('grid') protected readonly folders: FolderItem[] = FOLDERS protected readonly files: FileItem[] = FILES get rootClass(): string { return cn('w-full', this.className) } rows(): FileManagerRow[] { return [ ...this.folders.map((f) => ({ ...f, isFolder: true as const })), ...this.files.map((f) => ({ ...f, isFolder: false as const })), ] } kindIcon(kind: FileKind): typeof FileText { switch (kind) { case 'image': return FileImage case 'sheet': return FileSpreadsheet case 'video': return FileVideo case 'archive': return FileArchive default: return FileText } } setView(v: unknown): void { if (v === 'grid' || v === 'list') this.view.set(v) } }src/app/components/blocks/file-manager/index.ts0.1 kB
export { UiFileManagerComponent, type FileKind, type FolderItem, type FileItem, type FileManagerRow, } from './file-manager.component'
Raw manifest:https://uipkge.dev/r/angular/file-manager.json