Resource Manager
A customizable frontend for any data source
An enhanced Shadcn/UI data table with TypeScript support, resizable columns, and a dynamic form generator powered by Zod validation and integrated file uploads.
Installation
npx shadcn@latest add https://alex-amayo.github.io/resource-manager/ui/r/resource-manager.json
Demo
Live Code Editor
function Demo() { const [data, setData] = React.useState(initialData); const fields = [ { key: 'title', label: 'Title', inputType: 'text', fieldType: 'string', zodSchema: z.string().min(1, 'Title is required') }, { key: 'author', label: 'Author', inputType: 'text', fieldType: 'string', zodSchema: z.string().min(1, 'Author is required') }, { key: 'year', label: 'Year Released', inputType: 'number', fieldType: 'number', zodSchema: z.number() }, { key: 'description', label: 'Description', inputType: 'textarea', fieldType: 'string', zodSchema: z.string().optional() }, { key: 'genre', label: 'Genre', inputType: 'select', fieldType: 'string', options: [ { value: 'fiction', label: 'Fiction' }, { value: 'nonfiction', label: 'Nonfiction' }, { value: 'other', label: 'Other' } ], zodSchema: z.string().min(1, 'Genre is required') }, { key: 'cover', label: 'Book Cover', inputType: 'file', fieldType: 'file', renderCell: (value: any) => { if (typeof value === 'string' && value.startsWith('http')) { return value; } return '-'; }, onFileUpload: async (file) => { await new Promise(res => setTimeout(res, 500)); return `https://dummyimage.com/100x150/cccccc/000000&text=${encodeURIComponent(file.name)}`; }, zodSchema: z.string().optional() } ]; function handleAdd(resource: any) { setData(prev => [ ...prev, { ...resource, id: String(Date.now()), cover: resource.cover instanceof File ? (resource.cover.url || resource.cover.name) : resource.cover || '' } ]); } function handleDelete(id: string) { setData(prev => prev.filter(item => item.id !== id)); } function handleUpdate(id: string, values: any) { setData(prev => prev.map(item => item.id === id ? { ...item, ...values, cover: values.cover instanceof File ? (values.cover.url || values.cover.name) : values.cover || item.cover } : item )); } return ( <ResourceManager data={data} fields={fields} handleCreate={handleAdd} handleUpdate={handleUpdate} handleDelete={handleDelete} /> ); }