diff --git a/apps/v4/__registry__/index.tsx b/apps/v4/__registry__/index.tsx index 93c1577005f..624cf5bfc86 100644 --- a/apps/v4/__registry__/index.tsx +++ b/apps/v4/__registry__/index.tsx @@ -6,7 +6,7 @@ import * as React from "react" export const Index: Record<string, any> = { - "index": { + index: { name: "index", description: "", type: "registry:style", @@ -15,36 +15,48 @@ export const Index: Record<string, any> = { component: null, meta: undefined, }, - "accordion": { + accordion: { name: "accordion", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/accordion.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/accordion.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/accordion.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "alert": { + alert: { name: "alert", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/alert.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/alert.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/alert.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -54,14 +66,20 @@ export const Index: Record<string, any> = { description: "", type: "registry:ui", registryDependencies: ["button"], - files: [{ - path: "registry/ui/alert-dialog.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/alert-dialog.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/alert-dialog.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -71,201 +89,273 @@ export const Index: Record<string, any> = { description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/aspect-ratio.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/aspect-ratio.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/aspect-ratio.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "avatar": { + avatar: { name: "avatar", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/avatar.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/avatar.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/avatar.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "badge": { + badge: { name: "badge", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/badge.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/badge.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/badge.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "breadcrumb": { + breadcrumb: { name: "breadcrumb", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/breadcrumb.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/breadcrumb.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/breadcrumb.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "button": { + button: { name: "button", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/button.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/button.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/button.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "calendar": { + calendar: { name: "calendar", description: "", type: "registry:ui", registryDependencies: ["button"], - files: [{ - path: "registry/ui/calendar.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/calendar.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/calendar.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "card": { + card: { name: "card", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/card.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/card.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/card.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "carousel": { + carousel: { name: "carousel", description: "", type: "registry:ui", registryDependencies: ["button"], - files: [{ - path: "registry/ui/carousel.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/carousel.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/carousel.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "chart": { + chart: { name: "chart", description: "", type: "registry:ui", registryDependencies: ["card"], - files: [{ - path: "registry/ui/chart.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/chart.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/chart.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "checkbox": { + checkbox: { name: "checkbox", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/checkbox.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/checkbox.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/checkbox.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "collapsible": { + collapsible: { name: "collapsible", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/collapsible.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/collapsible.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/collapsible.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "command": { + command: { name: "command", description: "", type: "registry:ui", registryDependencies: ["dialog"], - files: [{ - path: "registry/ui/command.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/command.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/command.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -275,48 +365,66 @@ export const Index: Record<string, any> = { description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/context-menu.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/context-menu.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/context-menu.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "dialog": { + dialog: { name: "dialog", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/dialog.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/dialog.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/dialog.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "drawer": { + drawer: { name: "drawer", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/drawer.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/drawer.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/drawer.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -326,31 +434,43 @@ export const Index: Record<string, any> = { description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/dropdown-menu.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/dropdown-menu.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/dropdown-menu.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "form": { + form: { name: "form", description: "", type: "registry:ui", - registryDependencies: ["button","label"], - files: [{ - path: "registry/ui/form.tsx", - type: "registry:ui", - target: "" - }], + registryDependencies: ["button", "label"], + files: [ + { + path: "registry/ui/form.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/form.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -360,31 +480,43 @@ export const Index: Record<string, any> = { description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/hover-card.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/hover-card.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/hover-card.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "input": { + input: { name: "input", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/input.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/input.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/input.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -394,48 +526,66 @@ export const Index: Record<string, any> = { description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/input-otp.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/input-otp.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/input-otp.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "label": { + label: { name: "label", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/label.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/label.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/label.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "menubar": { + menubar: { name: "menubar", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/menubar.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/menubar.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/menubar.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -445,65 +595,89 @@ export const Index: Record<string, any> = { description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/navigation-menu.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/navigation-menu.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/navigation-menu.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "pagination": { + pagination: { name: "pagination", description: "", type: "registry:ui", registryDependencies: ["button"], - files: [{ - path: "registry/ui/pagination.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/pagination.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/pagination.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "popover": { + popover: { name: "popover", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/popover.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/popover.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/popover.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "progress": { + progress: { name: "progress", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/progress.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/progress.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/progress.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -513,31 +687,43 @@ export const Index: Record<string, any> = { description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/radio-group.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/radio-group.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/radio-group.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "resizable": { + resizable: { name: "resizable", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/resizable.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/resizable.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/resizable.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -547,218 +733,304 @@ export const Index: Record<string, any> = { description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/scroll-area.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/scroll-area.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/scroll-area.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "select": { + select: { name: "select", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/select.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/select.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/select.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "separator": { + separator: { name: "separator", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/separator.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/separator.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/separator.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "sheet": { + sheet: { name: "sheet", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/sheet.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/sheet.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/sheet.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "sidebar": { + sidebar: { name: "sidebar", description: "", type: "registry:ui", - registryDependencies: ["button","separator","sheet","tooltip","input","use-mobile","skeleton"], - files: [{ - path: "registry/ui/sidebar.tsx", - type: "registry:ui", - target: "" - }], + registryDependencies: [ + "button", + "separator", + "sheet", + "tooltip", + "input", + "use-mobile", + "skeleton", + ], + files: [ + { + path: "registry/ui/sidebar.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/sidebar.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "skeleton": { + skeleton: { name: "skeleton", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/skeleton.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/skeleton.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/skeleton.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "slider": { + slider: { name: "slider", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/slider.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/slider.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/slider.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "sonner": { + sonner: { name: "sonner", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/sonner.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/sonner.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/sonner.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "switch": { + switch: { name: "switch", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/switch.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/switch.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/switch.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "table": { + table: { name: "table", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/table.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/table.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/table.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "tabs": { + tabs: { name: "tabs", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/tabs.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/tabs.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/tabs.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "textarea": { + textarea: { name: "textarea", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/textarea.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/textarea.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/textarea.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "toggle": { + toggle: { name: "toggle", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/toggle.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/toggle.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/toggle.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -768,31 +1040,43 @@ export const Index: Record<string, any> = { description: "", type: "registry:ui", registryDependencies: ["toggle"], - files: [{ - path: "registry/ui/toggle-group.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/toggle-group.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/toggle-group.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "tooltip": { + tooltip: { name: "tooltip", description: "", type: "registry:ui", registryDependencies: undefined, - files: [{ - path: "registry/ui/tooltip.tsx", - type: "registry:ui", - target: "" - }], + files: [ + { + path: "registry/ui/tooltip.tsx", + type: "registry:ui", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/ui/tooltip.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -801,27 +1085,44 @@ export const Index: Record<string, any> = { name: "sidebar-01", description: "A simple sidebar with navigation grouped by section.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","label","dropdown-menu"], - files: [{ - path: "registry/blocks/sidebar-01/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-01/components/app-sidebar.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-01/components/search-form.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-01/components/version-switcher.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-01/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + registryDependencies: [ + "sidebar", + "breadcrumb", + "separator", + "label", + "dropdown-menu", + ], + files: [ + { + path: "registry/blocks/sidebar-01/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-01/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-01/components/search-form.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-01/components/version-switcher.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-01/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -830,27 +1131,44 @@ export const Index: Record<string, any> = { name: "sidebar-02", description: "A sidebar with collapsible sections.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","label","dropdown-menu"], - files: [{ - path: "registry/blocks/sidebar-02/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-02/components/app-sidebar.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-02/components/search-form.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-02/components/version-switcher.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-02/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + registryDependencies: [ + "sidebar", + "breadcrumb", + "separator", + "label", + "dropdown-menu", + ], + files: [ + { + path: "registry/blocks/sidebar-02/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-02/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-02/components/search-form.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-02/components/version-switcher.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-02/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -859,19 +1177,28 @@ export const Index: Record<string, any> = { name: "sidebar-03", description: "A sidebar with submenus.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb"], - files: [{ - path: "registry/blocks/sidebar-03/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-03/components/app-sidebar.tsx", - type: "registry:component", - target: "" - }], + registryDependencies: ["sidebar", "breadcrumb"], + files: [ + { + path: "registry/blocks/sidebar-03/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-03/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-03/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-03/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -880,19 +1207,28 @@ export const Index: Record<string, any> = { name: "sidebar-04", description: "A floating sidebar with submenus.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator"], - files: [{ - path: "registry/blocks/sidebar-04/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-04/components/app-sidebar.tsx", - type: "registry:component", - target: "" - }], + registryDependencies: ["sidebar", "breadcrumb", "separator"], + files: [ + { + path: "registry/blocks/sidebar-04/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-04/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-04/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-04/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -901,23 +1237,39 @@ export const Index: Record<string, any> = { name: "sidebar-05", description: "A sidebar with collapsible submenus.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","label","collapsible"], - files: [{ - path: "registry/blocks/sidebar-05/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-05/components/app-sidebar.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-05/components/search-form.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-05/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + registryDependencies: [ + "sidebar", + "breadcrumb", + "separator", + "label", + "collapsible", + ], + files: [ + { + path: "registry/blocks/sidebar-05/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-05/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-05/components/search-form.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-05/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -926,27 +1278,44 @@ export const Index: Record<string, any> = { name: "sidebar-06", description: "A sidebar with submenus as dropdowns.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","card","dropdown-menu"], - files: [{ - path: "registry/blocks/sidebar-06/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-06/components/app-sidebar.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-06/components/nav-main.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-06/components/sidebar-opt-in-form.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-06/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + registryDependencies: [ + "sidebar", + "breadcrumb", + "separator", + "card", + "dropdown-menu", + ], + files: [ + { + path: "registry/blocks/sidebar-06/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-06/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-06/components/nav-main.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-06/components/sidebar-opt-in-form.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-06/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -955,35 +1324,55 @@ export const Index: Record<string, any> = { name: "sidebar-07", description: "A sidebar that collapses to icons.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","collapsible","dropdown-menu","avatar"], - files: [{ - path: "registry/blocks/sidebar-07/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-07/components/app-sidebar.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-07/components/nav-main.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-07/components/nav-projects.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-07/components/nav-user.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-07/components/team-switcher.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-07/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + registryDependencies: [ + "sidebar", + "breadcrumb", + "separator", + "collapsible", + "dropdown-menu", + "avatar", + ], + files: [ + { + path: "registry/blocks/sidebar-07/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-07/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-07/components/nav-main.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-07/components/nav-projects.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-07/components/nav-user.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-07/components/team-switcher.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-07/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -992,35 +1381,55 @@ export const Index: Record<string, any> = { name: "sidebar-08", description: "An inset sidebar with secondary navigation.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","collapsible","dropdown-menu","avatar"], - files: [{ - path: "registry/blocks/sidebar-08/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-08/components/app-sidebar.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-08/components/nav-main.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-08/components/nav-projects.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-08/components/nav-secondary.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-08/components/nav-user.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-08/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + registryDependencies: [ + "sidebar", + "breadcrumb", + "separator", + "collapsible", + "dropdown-menu", + "avatar", + ], + files: [ + { + path: "registry/blocks/sidebar-08/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-08/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-08/components/nav-main.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-08/components/nav-projects.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-08/components/nav-secondary.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-08/components/nav-user.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-08/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1029,23 +1438,42 @@ export const Index: Record<string, any> = { name: "sidebar-09", description: "Collapsible nested sidebars.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","collapsible","dropdown-menu","avatar","switch","label"], - files: [{ - path: "registry/blocks/sidebar-09/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-09/components/app-sidebar.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-09/components/nav-user.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-09/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + registryDependencies: [ + "sidebar", + "breadcrumb", + "separator", + "collapsible", + "dropdown-menu", + "avatar", + "switch", + "label", + ], + files: [ + { + path: "registry/blocks/sidebar-09/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-09/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-09/components/nav-user.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-09/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1054,43 +1482,65 @@ export const Index: Record<string, any> = { name: "sidebar-10", description: "A sidebar in a popover.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","popover","collapsible","dropdown-menu"], - files: [{ - path: "registry/blocks/sidebar-10/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-10/components/app-sidebar.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-10/components/nav-actions.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-10/components/nav-favorites.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-10/components/nav-main.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-10/components/nav-secondary.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-10/components/nav-workspaces.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-10/components/team-switcher.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-10/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + registryDependencies: [ + "sidebar", + "breadcrumb", + "separator", + "popover", + "collapsible", + "dropdown-menu", + ], + files: [ + { + path: "registry/blocks/sidebar-10/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-10/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-10/components/nav-actions.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-10/components/nav-favorites.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-10/components/nav-main.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-10/components/nav-secondary.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-10/components/nav-workspaces.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-10/components/team-switcher.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-10/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1099,19 +1549,28 @@ export const Index: Record<string, any> = { name: "sidebar-11", description: "A sidebar with a collapsible file tree.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","collapsible"], - files: [{ - path: "registry/blocks/sidebar-11/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-11/components/app-sidebar.tsx", - type: "registry:component", - target: "" - }], + registryDependencies: ["sidebar", "breadcrumb", "separator", "collapsible"], + files: [ + { + path: "registry/blocks/sidebar-11/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-11/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-11/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-11/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1120,31 +1579,51 @@ export const Index: Record<string, any> = { name: "sidebar-12", description: "A sidebar with a calendar.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","collapsible","calendar","dropdown-menu","avatar"], - files: [{ - path: "registry/blocks/sidebar-12/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-12/components/app-sidebar.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-12/components/calendars.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-12/components/date-picker.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-12/components/nav-user.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-12/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + registryDependencies: [ + "sidebar", + "breadcrumb", + "separator", + "collapsible", + "calendar", + "dropdown-menu", + "avatar", + ], + files: [ + { + path: "registry/blocks/sidebar-12/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-12/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-12/components/calendars.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-12/components/date-picker.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-12/components/nav-user.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-12/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1153,19 +1632,28 @@ export const Index: Record<string, any> = { name: "sidebar-13", description: "A sidebar in a dialog.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","button","dialog"], - files: [{ - path: "registry/blocks/sidebar-13/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-13/components/settings-dialog.tsx", - type: "registry:component", - target: "" - }], + registryDependencies: ["sidebar", "breadcrumb", "button", "dialog"], + files: [ + { + path: "registry/blocks/sidebar-13/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-13/components/settings-dialog.tsx", + type: "registry:component", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-13/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-13/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1174,19 +1662,28 @@ export const Index: Record<string, any> = { name: "sidebar-14", description: "A sidebar on the right.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb"], - files: [{ - path: "registry/blocks/sidebar-14/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-14/components/app-sidebar.tsx", - type: "registry:component", - target: "" - }], + registryDependencies: ["sidebar", "breadcrumb"], + files: [ + { + path: "registry/blocks/sidebar-14/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-14/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-14/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-14/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1195,55 +1692,82 @@ export const Index: Record<string, any> = { name: "sidebar-15", description: "A left and right sidebar.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","popover","collapsible","dropdown-menu","calendar","avatar"], - files: [{ - path: "registry/blocks/sidebar-15/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-15/components/calendars.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-15/components/date-picker.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-15/components/nav-favorites.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-15/components/nav-main.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-15/components/nav-secondary.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-15/components/nav-user.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-15/components/nav-workspaces.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-15/components/sidebar-left.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-15/components/sidebar-right.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-15/components/team-switcher.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-15/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + registryDependencies: [ + "sidebar", + "breadcrumb", + "separator", + "popover", + "collapsible", + "dropdown-menu", + "calendar", + "avatar", + ], + files: [ + { + path: "registry/blocks/sidebar-15/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-15/components/calendars.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-15/components/date-picker.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-15/components/nav-favorites.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-15/components/nav-main.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-15/components/nav-secondary.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-15/components/nav-user.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-15/components/nav-workspaces.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-15/components/sidebar-left.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-15/components/sidebar-right.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-15/components/team-switcher.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-15/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1252,43 +1776,67 @@ export const Index: Record<string, any> = { name: "sidebar-16", description: "A sidebar with a sticky site header.", type: "registry:block", - registryDependencies: ["sidebar","breadcrumb","separator","collapsible","dropdown-menu","avatar","button","label"], - files: [{ - path: "registry/blocks/sidebar-16/page.tsx", - type: "registry:page", - target: "app/dashboard/page.tsx" - },{ - path: "registry/blocks/sidebar-16/components/app-sidebar.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-16/components/nav-main.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-16/components/nav-projects.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-16/components/nav-secondary.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-16/components/nav-user.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-16/components/search-form.tsx", - type: "registry:component", - target: "" - },{ - path: "registry/blocks/sidebar-16/components/site-header.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/sidebar-16/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + registryDependencies: [ + "sidebar", + "breadcrumb", + "separator", + "collapsible", + "dropdown-menu", + "avatar", + "button", + "label", + ], + files: [ + { + path: "registry/blocks/sidebar-16/page.tsx", + type: "registry:page", + target: "app/dashboard/page.tsx", + }, + { + path: "registry/blocks/sidebar-16/components/app-sidebar.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-16/components/nav-main.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-16/components/nav-projects.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-16/components/nav-secondary.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-16/components/nav-user.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-16/components/search-form.tsx", + type: "registry:component", + target: "", + }, + { + path: "registry/blocks/sidebar-16/components/site-header.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/sidebar-16/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1297,19 +1845,28 @@ export const Index: Record<string, any> = { name: "login-01", description: "A simple login form.", type: "registry:block", - registryDependencies: ["button","card","input","label"], - files: [{ - path: "registry/blocks/login-01/page.tsx", - type: "registry:page", - target: "app/login/page.tsx" - },{ - path: "registry/blocks/login-01/components/login-form.tsx", - type: "registry:component", - target: "" - }], + registryDependencies: ["button", "card", "input", "label"], + files: [ + { + path: "registry/blocks/login-01/page.tsx", + type: "registry:page", + target: "app/login/page.tsx", + }, + { + path: "registry/blocks/login-01/components/login-form.tsx", + type: "registry:component", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/login-01/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/blocks/login-01/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1318,19 +1875,28 @@ export const Index: Record<string, any> = { name: "login-02", description: "A two column login page with a cover image.", type: "registry:block", - registryDependencies: ["button","card","input","label"], - files: [{ - path: "registry/blocks/login-02/page.tsx", - type: "registry:page", - target: "app/login/page.tsx" - },{ - path: "registry/blocks/login-02/components/login-form.tsx", - type: "registry:component", - target: "" - }], + registryDependencies: ["button", "card", "input", "label"], + files: [ + { + path: "registry/blocks/login-02/page.tsx", + type: "registry:page", + target: "app/login/page.tsx", + }, + { + path: "registry/blocks/login-02/components/login-form.tsx", + type: "registry:component", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/login-02/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/blocks/login-02/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1339,19 +1905,28 @@ export const Index: Record<string, any> = { name: "login-03", description: "A login page with a muted background color.", type: "registry:block", - registryDependencies: ["button","card","input","label"], - files: [{ - path: "registry/blocks/login-03/page.tsx", - type: "registry:page", - target: "app/login/page.tsx" - },{ - path: "registry/blocks/login-03/components/login-form.tsx", - type: "registry:component", - target: "" - }], + registryDependencies: ["button", "card", "input", "label"], + files: [ + { + path: "registry/blocks/login-03/page.tsx", + type: "registry:page", + target: "app/login/page.tsx", + }, + { + path: "registry/blocks/login-03/components/login-form.tsx", + type: "registry:component", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/login-03/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/blocks/login-03/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1360,19 +1935,28 @@ export const Index: Record<string, any> = { name: "login-04", description: "A login page with form and image.", type: "registry:block", - registryDependencies: ["button","card","input","label"], - files: [{ - path: "registry/blocks/login-04/page.tsx", - type: "registry:page", - target: "app/login/page.tsx" - },{ - path: "registry/blocks/login-04/components/login-form.tsx", - type: "registry:component", - target: "" - }], + registryDependencies: ["button", "card", "input", "label"], + files: [ + { + path: "registry/blocks/login-04/page.tsx", + type: "registry:page", + target: "app/login/page.tsx", + }, + { + path: "registry/blocks/login-04/components/login-form.tsx", + type: "registry:component", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/login-04/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/blocks/login-04/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1381,19 +1965,28 @@ export const Index: Record<string, any> = { name: "login-05", description: "A simple email-only login page.", type: "registry:block", - registryDependencies: ["button","card","input","label"], - files: [{ - path: "registry/blocks/login-05/page.tsx", - type: "registry:page", - target: "app/login/page.tsx" - },{ - path: "registry/blocks/login-05/components/login-form.tsx", - type: "registry:component", - target: "" - }], + registryDependencies: ["button", "card", "input", "label"], + files: [ + { + path: "registry/blocks/login-05/page.tsx", + type: "registry:page", + target: "app/login/page.tsx", + }, + { + path: "registry/blocks/login-05/components/login-form.tsx", + type: "registry:component", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/login-05/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/blocks/login-05/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1402,15 +1995,23 @@ export const Index: Record<string, any> = { name: "chart-area-axes", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-area-axes.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-area-axes.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-area-axes.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-area-axes.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1419,15 +2020,23 @@ export const Index: Record<string, any> = { name: "chart-area-default", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-area-default.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-area-default.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-area-default.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-area-default.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1436,15 +2045,23 @@ export const Index: Record<string, any> = { name: "chart-area-gradient", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-area-gradient.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-area-gradient.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-area-gradient.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-area-gradient.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1453,15 +2070,23 @@ export const Index: Record<string, any> = { name: "chart-area-icons", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-area-icons.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-area-icons.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-area-icons.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-area-icons.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1470,15 +2095,23 @@ export const Index: Record<string, any> = { name: "chart-area-interactive", description: "", type: "registry:block", - registryDependencies: ["card","chart","select"], - files: [{ - path: "registry/charts/chart-area-interactive.tsx", - type: "registry:component", - target: "" - }], + registryDependencies: ["card", "chart", "select"], + files: [ + { + path: "registry/charts/chart-area-interactive.tsx", + type: "registry:component", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-area-interactive.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-area-interactive.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1487,15 +2120,23 @@ export const Index: Record<string, any> = { name: "chart-area-legend", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-area-legend.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-area-legend.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-area-legend.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-area-legend.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1504,15 +2145,23 @@ export const Index: Record<string, any> = { name: "chart-area-linear", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-area-linear.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-area-linear.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-area-linear.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-area-linear.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1521,15 +2170,23 @@ export const Index: Record<string, any> = { name: "chart-area-stacked-expand", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-area-stacked-expand.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-area-stacked-expand.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-area-stacked-expand.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-area-stacked-expand.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1538,15 +2195,23 @@ export const Index: Record<string, any> = { name: "chart-area-stacked", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-area-stacked.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-area-stacked.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-area-stacked.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-area-stacked.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1555,15 +2220,23 @@ export const Index: Record<string, any> = { name: "chart-area-step", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-area-step.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-area-step.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-area-step.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-area-step.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1572,15 +2245,23 @@ export const Index: Record<string, any> = { name: "chart-bar-active", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-bar-active.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-bar-active.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-bar-active.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-bar-active.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1589,15 +2270,23 @@ export const Index: Record<string, any> = { name: "chart-bar-default", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-bar-default.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-bar-default.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-bar-default.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-bar-default.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1606,15 +2295,23 @@ export const Index: Record<string, any> = { name: "chart-bar-horizontal", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-bar-horizontal.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-bar-horizontal.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-bar-horizontal.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-bar-horizontal.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1623,15 +2320,23 @@ export const Index: Record<string, any> = { name: "chart-bar-interactive", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-bar-interactive.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-bar-interactive.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-bar-interactive.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-bar-interactive.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1640,15 +2345,23 @@ export const Index: Record<string, any> = { name: "chart-bar-label-custom", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-bar-label-custom.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-bar-label-custom.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-bar-label-custom.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-bar-label-custom.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1657,15 +2370,23 @@ export const Index: Record<string, any> = { name: "chart-bar-label", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-bar-label.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-bar-label.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-bar-label.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-bar-label.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1674,15 +2395,23 @@ export const Index: Record<string, any> = { name: "chart-bar-mixed", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-bar-mixed.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-bar-mixed.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-bar-mixed.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-bar-mixed.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1691,15 +2420,23 @@ export const Index: Record<string, any> = { name: "chart-bar-multiple", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-bar-multiple.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-bar-multiple.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-bar-multiple.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-bar-multiple.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1708,15 +2445,23 @@ export const Index: Record<string, any> = { name: "chart-bar-negative", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-bar-negative.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-bar-negative.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-bar-negative.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-bar-negative.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1725,15 +2470,23 @@ export const Index: Record<string, any> = { name: "chart-bar-stacked", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-bar-stacked.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-bar-stacked.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-bar-stacked.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-bar-stacked.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1742,15 +2495,23 @@ export const Index: Record<string, any> = { name: "chart-line-default", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-line-default.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-line-default.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-line-default.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-line-default.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1759,15 +2520,23 @@ export const Index: Record<string, any> = { name: "chart-line-dots-colors", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-line-dots-colors.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-line-dots-colors.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-line-dots-colors.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-line-dots-colors.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1776,15 +2545,23 @@ export const Index: Record<string, any> = { name: "chart-line-dots-custom", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-line-dots-custom.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-line-dots-custom.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-line-dots-custom.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-line-dots-custom.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1793,15 +2570,23 @@ export const Index: Record<string, any> = { name: "chart-line-dots", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-line-dots.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-line-dots.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-line-dots.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-line-dots.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1810,15 +2595,23 @@ export const Index: Record<string, any> = { name: "chart-line-interactive", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-line-interactive.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-line-interactive.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-line-interactive.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-line-interactive.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1827,15 +2620,23 @@ export const Index: Record<string, any> = { name: "chart-line-label-custom", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-line-label-custom.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-line-label-custom.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-line-label-custom.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-line-label-custom.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1844,15 +2645,23 @@ export const Index: Record<string, any> = { name: "chart-line-label", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-line-label.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-line-label.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-line-label.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-line-label.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1861,15 +2670,23 @@ export const Index: Record<string, any> = { name: "chart-line-linear", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-line-linear.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-line-linear.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-line-linear.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-line-linear.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1878,15 +2695,23 @@ export const Index: Record<string, any> = { name: "chart-line-multiple", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-line-multiple.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-line-multiple.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-line-multiple.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-line-multiple.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1895,15 +2720,23 @@ export const Index: Record<string, any> = { name: "chart-line-step", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-line-step.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-line-step.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-line-step.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-line-step.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1912,15 +2745,23 @@ export const Index: Record<string, any> = { name: "chart-pie-donut-active", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-pie-donut-active.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-pie-donut-active.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-pie-donut-active.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-pie-donut-active.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1929,15 +2770,23 @@ export const Index: Record<string, any> = { name: "chart-pie-donut-text", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-pie-donut-text.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-pie-donut-text.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-pie-donut-text.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-pie-donut-text.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1946,15 +2795,23 @@ export const Index: Record<string, any> = { name: "chart-pie-donut", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-pie-donut.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-pie-donut.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-pie-donut.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-pie-donut.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1963,15 +2820,23 @@ export const Index: Record<string, any> = { name: "chart-pie-interactive", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-pie-interactive.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-pie-interactive.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-pie-interactive.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-pie-interactive.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1980,15 +2845,23 @@ export const Index: Record<string, any> = { name: "chart-pie-label-custom", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-pie-label-custom.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-pie-label-custom.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-pie-label-custom.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-pie-label-custom.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -1997,15 +2870,23 @@ export const Index: Record<string, any> = { name: "chart-pie-label-list", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-pie-label-list.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-pie-label-list.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-pie-label-list.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-pie-label-list.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2014,15 +2895,23 @@ export const Index: Record<string, any> = { name: "chart-pie-label", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-pie-label.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-pie-label.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-pie-label.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-pie-label.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2031,15 +2920,23 @@ export const Index: Record<string, any> = { name: "chart-pie-legend", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-pie-legend.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-pie-legend.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-pie-legend.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-pie-legend.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2048,15 +2945,23 @@ export const Index: Record<string, any> = { name: "chart-pie-separator-none", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-pie-separator-none.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-pie-separator-none.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-pie-separator-none.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-pie-separator-none.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2065,15 +2970,23 @@ export const Index: Record<string, any> = { name: "chart-pie-simple", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-pie-simple.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-pie-simple.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-pie-simple.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-pie-simple.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2082,15 +2995,23 @@ export const Index: Record<string, any> = { name: "chart-pie-stacked", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-pie-stacked.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-pie-stacked.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-pie-stacked.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-pie-stacked.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2099,15 +3020,23 @@ export const Index: Record<string, any> = { name: "chart-radar-default", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-default.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-default.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-default.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-default.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2116,15 +3045,23 @@ export const Index: Record<string, any> = { name: "chart-radar-dots", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-dots.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-dots.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-dots.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-dots.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2133,15 +3070,23 @@ export const Index: Record<string, any> = { name: "chart-radar-grid-circle-fill", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-grid-circle-fill.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-grid-circle-fill.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-grid-circle-fill.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-grid-circle-fill.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2150,15 +3095,23 @@ export const Index: Record<string, any> = { name: "chart-radar-grid-circle-no-lines", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-grid-circle-no-lines.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-grid-circle-no-lines.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-grid-circle-no-lines.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-grid-circle-no-lines.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2167,15 +3120,23 @@ export const Index: Record<string, any> = { name: "chart-radar-grid-circle", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-grid-circle.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-grid-circle.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-grid-circle.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-grid-circle.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2184,15 +3145,23 @@ export const Index: Record<string, any> = { name: "chart-radar-grid-custom", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-grid-custom.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-grid-custom.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-grid-custom.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-grid-custom.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2201,15 +3170,23 @@ export const Index: Record<string, any> = { name: "chart-radar-grid-fill", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-grid-fill.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-grid-fill.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-grid-fill.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-grid-fill.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2218,15 +3195,23 @@ export const Index: Record<string, any> = { name: "chart-radar-grid-none", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-grid-none.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-grid-none.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-grid-none.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-grid-none.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2235,15 +3220,23 @@ export const Index: Record<string, any> = { name: "chart-radar-icons", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-icons.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-icons.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-icons.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-icons.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2252,15 +3245,23 @@ export const Index: Record<string, any> = { name: "chart-radar-label-custom", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-label-custom.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-label-custom.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-label-custom.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-label-custom.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2269,15 +3270,23 @@ export const Index: Record<string, any> = { name: "chart-radar-legend", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-legend.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-legend.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-legend.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-legend.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2286,15 +3295,23 @@ export const Index: Record<string, any> = { name: "chart-radar-lines-only", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-lines-only.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-lines-only.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-lines-only.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-lines-only.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2303,15 +3320,23 @@ export const Index: Record<string, any> = { name: "chart-radar-multiple", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-multiple.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-multiple.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-multiple.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-multiple.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2320,15 +3345,23 @@ export const Index: Record<string, any> = { name: "chart-radar-radius", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radar-radius.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radar-radius.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radar-radius.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radar-radius.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2337,15 +3370,23 @@ export const Index: Record<string, any> = { name: "chart-radial-grid", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radial-grid.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radial-grid.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radial-grid.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radial-grid.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2354,15 +3395,23 @@ export const Index: Record<string, any> = { name: "chart-radial-label", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radial-label.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radial-label.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radial-label.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radial-label.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2371,15 +3420,23 @@ export const Index: Record<string, any> = { name: "chart-radial-shape", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radial-shape.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radial-shape.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radial-shape.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radial-shape.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2388,15 +3445,23 @@ export const Index: Record<string, any> = { name: "chart-radial-simple", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radial-simple.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radial-simple.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radial-simple.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radial-simple.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2405,15 +3470,23 @@ export const Index: Record<string, any> = { name: "chart-radial-stacked", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radial-stacked.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radial-stacked.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radial-stacked.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radial-stacked.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2422,15 +3495,23 @@ export const Index: Record<string, any> = { name: "chart-radial-text", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-radial-text.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-radial-text.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-radial-text.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-radial-text.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2439,15 +3520,23 @@ export const Index: Record<string, any> = { name: "chart-tooltip-default", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-tooltip-default.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-tooltip-default.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-tooltip-default.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-tooltip-default.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2456,15 +3545,23 @@ export const Index: Record<string, any> = { name: "chart-tooltip-indicator-line", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-tooltip-indicator-line.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-tooltip-indicator-line.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-tooltip-indicator-line.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-tooltip-indicator-line.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2473,15 +3570,23 @@ export const Index: Record<string, any> = { name: "chart-tooltip-indicator-none", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-tooltip-indicator-none.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-tooltip-indicator-none.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-tooltip-indicator-none.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-tooltip-indicator-none.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2490,15 +3595,23 @@ export const Index: Record<string, any> = { name: "chart-tooltip-label-none", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-tooltip-label-none.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-tooltip-label-none.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-tooltip-label-none.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-tooltip-label-none.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2507,15 +3620,23 @@ export const Index: Record<string, any> = { name: "chart-tooltip-label-custom", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-tooltip-label-custom.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-tooltip-label-custom.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-tooltip-label-custom.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-tooltip-label-custom.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2524,15 +3645,23 @@ export const Index: Record<string, any> = { name: "chart-tooltip-label-formatter", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-tooltip-label-formatter.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-tooltip-label-formatter.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-tooltip-label-formatter.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-tooltip-label-formatter.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2541,15 +3670,23 @@ export const Index: Record<string, any> = { name: "chart-tooltip-formatter", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-tooltip-formatter.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-tooltip-formatter.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-tooltip-formatter.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-tooltip-formatter.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2558,15 +3695,23 @@ export const Index: Record<string, any> = { name: "chart-tooltip-icons", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-tooltip-icons.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-tooltip-icons.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-tooltip-icons.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-tooltip-icons.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2575,32 +3720,46 @@ export const Index: Record<string, any> = { name: "chart-tooltip-advanced", description: "", type: "registry:block", - registryDependencies: ["card","chart"], - files: [{ - path: "registry/charts/chart-tooltip-advanced.tsx", - type: "registry:block", - target: "" - }], + registryDependencies: ["card", "chart"], + files: [ + { + path: "registry/charts/chart-tooltip-advanced.tsx", + type: "registry:block", + target: "", + }, + ], component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/charts/chart-tooltip-advanced.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const mod = await import( + "@/registry/new-york-v4/charts/chart-tooltip-advanced.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, }, - "utils": { + utils: { name: "utils", description: "", type: "registry:lib", registryDependencies: undefined, - files: [{ - path: "registry/lib/utils.ts", - type: "registry:lib", - target: "" - }], + files: [ + { + path: "registry/lib/utils.ts", + type: "registry:lib", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/lib/utils.ts") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2610,14 +3769,20 @@ export const Index: Record<string, any> = { description: "", type: "registry:hook", registryDependencies: undefined, - files: [{ - path: "registry/hooks/use-mobile.ts", - type: "registry:hook", - target: "" - }], + files: [ + { + path: "registry/hooks/use-mobile.ts", + type: "registry:hook", + target: "", + }, + ], component: React.lazy(async () => { const mod = await import("@/registry/new-york-v4/hooks/use-mobile.ts") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name return { default: mod.default || mod[exportName] } }), meta: undefined, @@ -2626,21 +3791,39 @@ export const Index: Record<string, any> = { name: "products-01", description: "A table of products", type: "registry:block", - registryDependencies: ["checkbox","badge","button","dropdown-menu","pagination","table","tabs","select"], - files: [{ - path: "registry/blocks/products-01/page.tsx", - type: "registry:page", - target: "app/products/page.tsx" - },{ - path: "registry/blocks/products-01/components/products-table.tsx", - type: "registry:component", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/new-york-v4/blocks/products-01/page.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name - return { default: mod.default || mod[exportName] } - }), - meta: undefined, - }, - } \ No newline at end of file + registryDependencies: [ + "checkbox", + "badge", + "button", + "dropdown-menu", + "pagination", + "table", + "tabs", + "select", + ], + files: [ + { + path: "registry/blocks/products-01/page.tsx", + type: "registry:page", + target: "app/products/page.tsx", + }, + { + path: "registry/blocks/products-01/components/products-table.tsx", + type: "registry:component", + target: "", + }, + ], + component: React.lazy(async () => { + const mod = await import( + "@/registry/new-york-v4/blocks/products-01/page.tsx" + ) + const exportName = + Object.keys(mod).find( + (key) => + typeof mod[key] === "function" || typeof mod[key] === "object" + ) || item.name + return { default: mod.default || mod[exportName] } + }), + meta: undefined, + }, +} diff --git a/apps/v4/registry/new-york-v4/lib/composition.ts b/apps/v4/registry/new-york-v4/lib/composition.ts new file mode 100644 index 00000000000..65bd733c7f1 --- /dev/null +++ b/apps/v4/registry/new-york-v4/lib/composition.ts @@ -0,0 +1,87 @@ +import * as React from "react" + +/** + * A utility to compose multiple event handlers into a single event handler. + * Run originalEventHandler first, then ourEventHandler unless prevented. + */ +function composeEventHandlers<E>( + originalEventHandler?: (event: E) => void, + ourEventHandler?: (event: E) => void, + { checkForDefaultPrevented = true } = {} +) { + return function handleEvent(event: E) { + originalEventHandler?.(event) + + if ( + checkForDefaultPrevented === false || + !(event as unknown as Event).defaultPrevented + ) { + return ourEventHandler?.(event) + } + } +} + +/** + * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx + */ + +type PossibleRef<T> = React.Ref<T> | undefined + +/** + * Set a given ref to a given value. + * This utility takes care of different types of refs: callback refs and RefObject(s). + */ +function setRef<T>(ref: PossibleRef<T>, value: T) { + if (typeof ref === "function") { + return ref(value) + } + + if (ref !== null && ref !== undefined) { + ref.current = value + } +} + +/** + * A utility to compose multiple refs together. + * Accepts callback refs and RefObject(s). + */ +function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> { + return (node) => { + let hasCleanup = false + const cleanups = refs.map((ref) => { + const cleanup = setRef(ref, node) + if (!hasCleanup && typeof cleanup === "function") { + hasCleanup = true + } + return cleanup + }) + + // React <19 will log an error to the console if a callback ref returns a + // value. We don't use ref cleanups internally so this will only happen if a + // user's ref callback returns a value, which we only expect if they are + // using the cleanup functionality added in React 19. + if (hasCleanup) { + return () => { + for (let i = 0; i < cleanups.length; i++) { + const cleanup = cleanups[i] + if (typeof cleanup === "function") { + cleanup() + } else { + setRef(refs[i], null) + } + } + } + } + } +} + +/** + * A custom hook that composes multiple refs. + * Accepts callback refs and RefObject(s). + */ +function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> { + // eslint-disable-next-line react-hooks/exhaustive-deps + return React.useCallback(composeRefs(...refs), refs) +} + +export { composeEventHandlers, composeRefs, useComposedRefs } diff --git a/apps/v4/registry/new-york-v4/ui/editable.tsx b/apps/v4/registry/new-york-v4/ui/editable.tsx new file mode 100644 index 00000000000..0c2e9191295 --- /dev/null +++ b/apps/v4/registry/new-york-v4/ui/editable.tsx @@ -0,0 +1,864 @@ +"use client" + +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" + +import { cn } from "@/lib/utils" +import { + composeEventHandlers, + useComposedRefs, +} from "@/registry/new-york-v4/lib/composition" + +const DATA_ACTION_ATTR = "data-action" + +const ROOT_NAME = "Editable" +const AREA_NAME = "EditableArea" +const PREVIEW_NAME = "EditablePreview" +const INPUT_NAME = "EditableInput" +const TRIGGER_NAME = "EditableTrigger" +const LABEL_NAME = "EditableLabel" +const TOOLBAR_NAME = "EditableToolbar" +const CANCEL_NAME = "EditableCancel" +const SUBMIT_NAME = "EditableSubmit" + +const EDITABLE_ERRORS = { + [ROOT_NAME]: `\`${ROOT_NAME}\` components must be within \`${ROOT_NAME}\``, + [AREA_NAME]: `\`${AREA_NAME}\` must be within \`${ROOT_NAME}\``, + [PREVIEW_NAME]: `\`${PREVIEW_NAME}\` must be within \`${ROOT_NAME}\``, + [INPUT_NAME]: `\`${INPUT_NAME}\` must be within \`${ROOT_NAME}\``, + [TRIGGER_NAME]: `\`${TRIGGER_NAME}\` must be within \`${ROOT_NAME}\``, + [LABEL_NAME]: `\`${LABEL_NAME}\` must be within \`${ROOT_NAME}\``, + [TOOLBAR_NAME]: `\`${TOOLBAR_NAME}\` must be within \`${ROOT_NAME}\``, + [CANCEL_NAME]: `\`${CANCEL_NAME}\` must be within \`${ROOT_NAME}\``, + [SUBMIT_NAME]: `\`${SUBMIT_NAME}\` must be within \`${ROOT_NAME}\``, +} as const + +type Direction = "ltr" | "rtl" + +const DirectionContext = React.createContext<Direction | undefined>(undefined) + +function useDirection(dirProp?: Direction): Direction { + const contextDir = React.useContext(DirectionContext) + return dirProp ?? contextDir ?? "ltr" +} + +interface EditableContextValue { + id: string + inputId: string + labelId: string + defaultValue: string + value: string + onValueChange: (value: string) => void + editing: boolean + onCancel: () => void + onEdit: () => void + onSubmit: (value: string) => void + onEscapeKeyDown?: (event: KeyboardEvent) => void + dir?: Direction + maxLength?: number + placeholder?: string + triggerMode: "click" | "dblclick" | "focus" + autosize: boolean + disabled?: boolean + readOnly?: boolean + required?: boolean + invalid?: boolean +} + +const EditableContext = React.createContext<EditableContextValue | null>(null) +EditableContext.displayName = ROOT_NAME + +function useEditableContext(name: keyof typeof EDITABLE_ERRORS) { + const context = React.useContext(EditableContext) + if (!context) { + throw new Error(EDITABLE_ERRORS[name]) + } + return context +} + +type RootElement = React.ComponentRef<typeof Editable> + +interface EditableRootProps + extends Omit<React.ComponentPropsWithoutRef<"div">, "onSubmit"> { + id?: string + defaultValue?: string + value?: string + onValueChange?: (value: string) => void + defaultEditing?: boolean + editing?: boolean + onEditingChange?: (editing: boolean) => void + onCancel?: () => void + onEdit?: () => void + onSubmit?: (value: string) => void + onEscapeKeyDown?: (event: KeyboardEvent) => void + dir?: Direction + maxLength?: number + name?: string + placeholder?: string + triggerMode?: EditableContextValue["triggerMode"] + asChild?: boolean + autosize?: boolean + disabled?: boolean + readOnly?: boolean + required?: boolean + invalid?: boolean +} + +const EditableRoot = React.forwardRef<HTMLDivElement, EditableRootProps>( + (props, forwardedRef) => { + const { + defaultValue = "", + value: valueProp, + onValueChange: onValueChangeProp, + defaultEditing = false, + editing: editingProp, + onEditingChange: onEditingChangeProp, + onCancel: onCancelProp, + onEdit: onEditProp, + onSubmit: onSubmitProp, + onEscapeKeyDown, + dir: dirProp, + maxLength, + name, + placeholder, + triggerMode = "click", + asChild, + autosize = false, + disabled, + required, + readOnly, + invalid, + className, + ...rootProps + } = props + + const id = React.useId() + const inputId = React.useId() + const labelId = React.useId() + + const dir = useDirection(dirProp) + + const isControlled = valueProp !== undefined + const [uncontrolledValue, setUncontrolledValue] = + React.useState(defaultValue) + const value = isControlled ? valueProp : uncontrolledValue + const previousValueRef = React.useRef(value) + const onValueChangeRef = React.useRef(onValueChangeProp) + + const isEditingControlled = editingProp !== undefined + const [uncontrolledEditing, setUncontrolledEditing] = + React.useState(defaultEditing) + const editing = isEditingControlled ? editingProp : uncontrolledEditing + const onEditingChangeRef = React.useRef(onEditingChangeProp) + + React.useEffect(() => { + onValueChangeRef.current = onValueChangeProp + onEditingChangeRef.current = onEditingChangeProp + }) + + const onValueChange = React.useCallback( + (nextValue: string) => { + if (!isControlled) { + setUncontrolledValue(nextValue) + } + onValueChangeRef.current?.(nextValue) + }, + [isControlled] + ) + + const onEditingChange = React.useCallback( + (nextEditing: boolean) => { + if (!isEditingControlled) { + setUncontrolledEditing(nextEditing) + } + onEditingChangeRef.current?.(nextEditing) + }, + [isEditingControlled] + ) + + React.useEffect(() => { + if (isControlled && valueProp !== previousValueRef.current) { + previousValueRef.current = valueProp + } + }, [isControlled, valueProp]) + + const [formTrigger, setFormTrigger] = React.useState<RootElement | null>( + null + ) + const composedRef = useComposedRefs(forwardedRef, (node) => + setFormTrigger(node) + ) + const isFormControl = formTrigger ? !!formTrigger.closest("form") : true + + const onCancel = React.useCallback(() => { + const prevValue = previousValueRef.current + onValueChange(prevValue) + onEditingChange(false) + onCancelProp?.() + }, [onValueChange, onCancelProp, onEditingChange]) + + const onEdit = React.useCallback(() => { + previousValueRef.current = value + onEditingChange(true) + onEditProp?.() + }, [value, onEditProp, onEditingChange]) + + const onSubmit = React.useCallback( + (newValue: string) => { + onValueChange(newValue) + onEditingChange(false) + onSubmitProp?.(newValue) + }, + + [onValueChange, onSubmitProp, onEditingChange] + ) + + const contextValue = React.useMemo<EditableContextValue>( + () => ({ + id, + inputId, + labelId, + defaultValue, + value, + onValueChange, + editing, + onSubmit, + onEdit, + onCancel, + onEscapeKeyDown, + dir, + maxLength, + placeholder, + triggerMode, + autosize, + disabled, + readOnly, + required, + invalid, + }), + [ + id, + inputId, + labelId, + defaultValue, + value, + onValueChange, + editing, + onSubmit, + onCancel, + onEdit, + onEscapeKeyDown, + dir, + maxLength, + placeholder, + triggerMode, + autosize, + disabled, + required, + readOnly, + invalid, + ] + ) + + const RootPrimitive = asChild ? Slot : "div" + + return ( + <EditableContext.Provider value={contextValue}> + <RootPrimitive + data-slot="editable" + {...rootProps} + ref={composedRef} + id={id} + className={cn("flex min-w-0 flex-col gap-2", className)} + /> + {isFormControl && ( + <VisuallyHiddenInput + type="hidden" + control={formTrigger} + name={name} + value={value} + disabled={disabled} + readOnly={readOnly} + required={required} + /> + )} + </EditableContext.Provider> + ) + } +) +EditableRoot.displayName = ROOT_NAME + +interface EditableLabelProps extends React.ComponentPropsWithoutRef<"label"> { + asChild?: boolean +} + +const EditableLabel = React.forwardRef<HTMLLabelElement, EditableLabelProps>( + (props, forwardedRef) => { + const { asChild, className, children, ...labelProps } = props + const context = useEditableContext(LABEL_NAME) + + const LabelPrimitive = asChild ? Slot : "label" + + return ( + <LabelPrimitive + data-disabled={context.disabled ? "" : undefined} + data-invalid={context.invalid ? "" : undefined} + data-required={context.required ? "" : undefined} + data-slot="editable-label" + {...labelProps} + ref={forwardedRef} + id={context.labelId} + htmlFor={context.inputId} + className={cn( + "data-required:after:text-destructive text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70 data-required:after:ml-0.5 data-required:after:content-['*']", + className + )} + > + {children} + </LabelPrimitive> + ) + } +) +EditableLabel.displayName = LABEL_NAME + +interface EditableAreaProps extends React.ComponentPropsWithoutRef<"div"> { + asChild?: boolean +} + +const EditableArea = React.forwardRef<HTMLDivElement, EditableAreaProps>( + (props, forwardedRef) => { + const { asChild, className, ...areaProps } = props + const context = useEditableContext(AREA_NAME) + + const AreaPrimitive = asChild ? Slot : "div" + + return ( + <AreaPrimitive + role="group" + data-disabled={context.disabled ? "" : undefined} + data-editing={context.editing ? "" : undefined} + data-slot="editable-area" + dir={context.dir} + {...areaProps} + ref={forwardedRef} + className={cn( + "relative inline-block min-w-0 data-disabled:cursor-not-allowed data-disabled:opacity-50", + className + )} + /> + ) + } +) +EditableArea.displayName = AREA_NAME + +interface EditablePreviewProps extends React.ComponentPropsWithoutRef<"div"> { + asChild?: boolean +} + +const EditablePreview = React.forwardRef<HTMLDivElement, EditablePreviewProps>( + (props, forwardedRef) => { + const { asChild, className, ...previewProps } = props + const context = useEditableContext(PREVIEW_NAME) + + const onTrigger = React.useCallback(() => { + if (context.disabled || context.readOnly) return + context.onEdit() + }, [context]) + + const PreviewPrimitive = asChild ? Slot : "div" + + if (context.editing || context.readOnly) return null + + return ( + <PreviewPrimitive + role="button" + aria-disabled={context.disabled || context.readOnly} + data-empty={!context.value ? "" : undefined} + data-disabled={context.disabled ? "" : undefined} + data-readonly={context.readOnly ? "" : undefined} + data-slot="editable-preview" + tabIndex={context.disabled || context.readOnly ? undefined : 0} + {...previewProps} + ref={forwardedRef} + onClick={composeEventHandlers( + previewProps.onClick, + context.triggerMode === "click" ? onTrigger : undefined + )} + onDoubleClick={composeEventHandlers( + previewProps.onDoubleClick, + context.triggerMode === "dblclick" ? onTrigger : undefined + )} + onFocus={composeEventHandlers( + previewProps.onFocus, + context.triggerMode === "focus" ? onTrigger : undefined + )} + className={cn( + "data-empty:text-muted-foreground focus-visible:ring-ring cursor-text truncate rounded-sm border border-transparent py-1 text-base focus-visible:ring-1 focus-visible:outline-hidden data-disabled:cursor-not-allowed data-disabled:opacity-50 data-readonly:cursor-default md:text-sm", + className + )} + > + {context.value || context.placeholder} + </PreviewPrimitive> + ) + } +) +EditablePreview.displayName = PREVIEW_NAME + +const useIsomorphicLayoutEffect = + typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect + +type InputElement = React.ComponentRef<typeof EditableInput> + +interface EditableInputProps extends React.ComponentPropsWithoutRef<"input"> { + asChild?: boolean + maxLength?: number +} + +const EditableInput = React.forwardRef<HTMLInputElement, EditableInputProps>( + (props, forwardedRef) => { + const { + asChild, + className, + disabled, + readOnly, + required, + maxLength, + ...inputProps + } = props + const context = useEditableContext(INPUT_NAME) + const inputRef = React.useRef<InputElement>(null) + const composedRef = useComposedRefs(forwardedRef, inputRef) + + const isDisabled = disabled || context.disabled + const isReadOnly = readOnly || context.readOnly + const isRequired = required || context.required + + const onAutosize = React.useCallback( + (target: HTMLInputElement | HTMLTextAreaElement) => { + if (!context.autosize) return + + if (target instanceof HTMLTextAreaElement) { + target.style.height = "0" + target.style.height = `${target.scrollHeight}px` + } else { + target.style.width = "0" + target.style.width = `${target.scrollWidth + 4}px` + } + }, + [context.autosize] + ) + const onBlur = React.useCallback( + (event: React.FocusEvent<InputElement>) => { + if (isReadOnly) return + const relatedTarget = event.relatedTarget + + const isAction = + relatedTarget instanceof HTMLElement && + relatedTarget.closest(`[${DATA_ACTION_ATTR}=""]`) + + if (!isAction) { + context.onSubmit(context.value) + } + }, + [context, isReadOnly] + ) + + const onChange = React.useCallback( + (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { + if (isReadOnly) return + context.onValueChange(event.target.value) + onAutosize(event.target) + }, + [context, isReadOnly, onAutosize] + ) + + const onKeyDown = React.useCallback( + (event: React.KeyboardEvent<InputElement>) => { + if (isReadOnly) return + if (event.key === "Escape") { + const nativeEvent = event.nativeEvent + if (context.onEscapeKeyDown) { + context.onEscapeKeyDown(nativeEvent) + if (nativeEvent.defaultPrevented) return + } + context.onCancel() + } else if (event.key === "Enter") { + context.onSubmit(context.value) + } + }, + [context, isReadOnly] + ) + + useIsomorphicLayoutEffect(() => { + if (!context.editing || isReadOnly || !inputRef.current) return + + const frameId = window.requestAnimationFrame(() => { + if (!inputRef.current) return + + inputRef.current.focus() + inputRef.current.select() + onAutosize(inputRef.current) + }) + + return () => { + window.cancelAnimationFrame(frameId) + } + }, [context.editing, isReadOnly, onAutosize]) + + const InputPrimitive = asChild ? Slot : "input" + + if (!context.editing && !isReadOnly) return null + + return ( + <InputPrimitive + aria-required={isRequired} + aria-invalid={context.invalid} + data-slot="editable-input" + dir={context.dir} + disabled={isDisabled} + readOnly={isReadOnly} + required={isRequired} + {...inputProps} + id={context.inputId} + aria-labelledby={context.labelId} + ref={composedRef} + maxLength={maxLength} + placeholder={context.placeholder} + value={context.value} + onBlur={composeEventHandlers(inputProps.onBlur, onBlur)} + onChange={composeEventHandlers(inputProps.onChange, onChange)} + onKeyDown={composeEventHandlers(inputProps.onKeyDown, onKeyDown)} + className={cn( + "border-input file:text-foreground placeholder:text-muted-foreground focus-visible:ring-ring flex rounded-sm border bg-transparent py-1 text-base shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-1 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", + context.autosize ? "w-auto" : "w-full", + className + )} + /> + ) + } +) +EditableInput.displayName = INPUT_NAME + +interface EditableTriggerProps + extends React.ComponentPropsWithoutRef<"button"> { + asChild?: boolean + forceMount?: boolean +} + +const EditableTrigger = React.forwardRef< + HTMLButtonElement, + EditableTriggerProps +>((props, forwardedRef) => { + const { asChild, forceMount = false, ...triggerProps } = props + const context = useEditableContext(TRIGGER_NAME) + + const onTrigger = React.useCallback(() => { + if (context.disabled || context.readOnly) return + context.onEdit() + }, [context]) + + const TriggerPrimitive = asChild ? Slot : "button" + + if (!forceMount && (context.editing || context.readOnly)) return null + + return ( + <TriggerPrimitive + type="button" + aria-controls={context.id} + aria-disabled={context.disabled || context.readOnly} + data-disabled={context.disabled ? "" : undefined} + data-readonly={context.readOnly ? "" : undefined} + data-slot="editable-trigger" + {...triggerProps} + ref={forwardedRef} + onClick={context.triggerMode === "click" ? onTrigger : undefined} + onDoubleClick={context.triggerMode === "dblclick" ? onTrigger : undefined} + /> + ) +}) +EditableTrigger.displayName = TRIGGER_NAME + +interface EditableToolbarProps extends React.ComponentPropsWithoutRef<"div"> { + asChild?: boolean + orientation?: "horizontal" | "vertical" +} + +const EditableToolbar = React.forwardRef<HTMLDivElement, EditableToolbarProps>( + (props, forwardedRef) => { + const { + asChild, + className, + orientation = "horizontal", + ...toolbarProps + } = props + const context = useEditableContext(TOOLBAR_NAME) + + const ToolbarPrimitive = asChild ? Slot : "div" + + return ( + <ToolbarPrimitive + role="toolbar" + aria-controls={context.id} + aria-orientation={orientation} + data-slot="editable-toolbar" + dir={context.dir} + {...toolbarProps} + ref={forwardedRef} + className={cn( + "flex items-center gap-2", + orientation === "vertical" && "flex-col", + className + )} + /> + ) + } +) +EditableToolbar.displayName = TOOLBAR_NAME + +interface EditableCancelProps extends React.ComponentPropsWithoutRef<"button"> { + asChild?: boolean +} + +const EditableCancel = React.forwardRef<HTMLButtonElement, EditableCancelProps>( + (props, forwardedRef) => { + const { asChild, ...cancelProps } = props + const context = useEditableContext(CANCEL_NAME) + + const CancelPrimitive = asChild ? Slot : "button" + + if (!context.editing && !context.readOnly) return null + + return ( + <CancelPrimitive + type="button" + aria-controls={context.id} + data-slot="editable-cancel" + {...{ [DATA_ACTION_ATTR]: "" }} + {...cancelProps} + onClick={composeEventHandlers(cancelProps.onClick, () => { + context.onCancel() + })} + ref={forwardedRef} + /> + ) + } +) +EditableCancel.displayName = CANCEL_NAME + +interface EditableSubmitProps extends React.ComponentPropsWithoutRef<"button"> { + asChild?: boolean +} + +const EditableSubmit = React.forwardRef<HTMLButtonElement, EditableSubmitProps>( + (props, forwardedRef) => { + const { asChild, ...submitProps } = props + const context = useEditableContext(SUBMIT_NAME) + + const SubmitPrimitive = asChild ? Slot : "button" + + if (!context.editing && !context.readOnly) return null + + return ( + <SubmitPrimitive + type="button" + aria-controls={context.id} + data-slot="editable-submit" + {...{ [DATA_ACTION_ATTR]: "" }} + {...submitProps} + ref={forwardedRef} + onClick={composeEventHandlers(submitProps.onClick, () => { + context.onSubmit(context.value) + })} + /> + ) + } +) +EditableSubmit.displayName = SUBMIT_NAME + +type InputValue = string[] | string + +interface VisuallyHiddenInputProps<T = InputValue> + extends Omit< + React.InputHTMLAttributes<HTMLInputElement>, + "value" | "checked" | "onReset" + > { + value?: T + checked?: boolean + control: HTMLElement | null + bubbles?: boolean +} + +function VisuallyHiddenInput<T = InputValue>( + props: VisuallyHiddenInputProps<T> +) { + const { + control, + value, + checked, + bubbles = true, + type = "hidden", + style, + ...inputProps + } = props + + const isCheckInput = React.useMemo( + () => type === "checkbox" || type === "radio" || type === "switch", + [type] + ) + const inputRef = React.useRef<HTMLInputElement>(null) + + const prevValueRef = React.useRef<{ + value: T | boolean | undefined + previous: T | boolean | undefined + }>({ + value: isCheckInput ? checked : value, + previous: isCheckInput ? checked : value, + }) + + const prevValue = React.useMemo(() => { + const currentValue = isCheckInput ? checked : value + if (prevValueRef.current.value !== currentValue) { + prevValueRef.current.previous = prevValueRef.current.value + prevValueRef.current.value = currentValue + } + return prevValueRef.current.previous + }, [isCheckInput, value, checked]) + + const [controlSize, setControlSize] = React.useState<{ + width?: number + height?: number + }>({}) + + React.useLayoutEffect(() => { + if (!control) { + setControlSize({}) + return + } + + setControlSize({ + width: control.offsetWidth, + height: control.offsetHeight, + }) + + if (typeof window === "undefined") return + + const resizeObserver = new ResizeObserver((entries) => { + if (!Array.isArray(entries) || !entries.length) return + + const entry = entries[0] + if (!entry) return + + let width: number + let height: number + + if ("borderBoxSize" in entry) { + const borderSizeEntry = entry.borderBoxSize + const borderSize = Array.isArray(borderSizeEntry) + ? borderSizeEntry[0] + : borderSizeEntry + width = borderSize.inlineSize + height = borderSize.blockSize + } else { + width = control.offsetWidth + height = control.offsetHeight + } + + setControlSize({ width, height }) + }) + + resizeObserver.observe(control, { box: "border-box" }) + return () => { + resizeObserver.disconnect() + } + }, [control]) + + React.useEffect(() => { + const input = inputRef.current + if (!input) return + + const inputProto = window.HTMLInputElement.prototype + const propertyKey = isCheckInput ? "checked" : "value" + const eventType = isCheckInput ? "click" : "input" + const currentValue = isCheckInput ? checked : value + + const serializedCurrentValue = isCheckInput + ? checked + : typeof value === "object" && value !== null + ? JSON.stringify(value) + : value + + const descriptor = Object.getOwnPropertyDescriptor(inputProto, propertyKey) + + const setter = descriptor?.set + + if (prevValue !== currentValue && setter) { + const event = new Event(eventType, { bubbles }) + setter.call(input, serializedCurrentValue) + input.dispatchEvent(event) + } + }, [prevValue, value, checked, bubbles, isCheckInput]) + + const composedStyle = React.useMemo<React.CSSProperties>(() => { + return { + ...style, + ...(controlSize.width !== undefined && controlSize.height !== undefined + ? controlSize + : {}), + border: 0, + clip: "rect(0 0 0 0)", + clipPath: "inset(50%)", + height: "1px", + margin: "-1px", + overflow: "hidden", + padding: 0, + position: "absolute", + whiteSpace: "nowrap", + width: "1px", + } + }, [style, controlSize]) + + return ( + <input + type={type} + {...inputProps} + ref={inputRef} + aria-hidden={isCheckInput} + tabIndex={-1} + defaultChecked={isCheckInput ? checked : undefined} + style={composedStyle} + /> + ) +} + +const Editable = EditableRoot +const Root = EditableRoot +const Label = EditableLabel +const Area = EditableArea +const Preview = EditablePreview +const Input = EditableInput +const Trigger = EditableTrigger +const Toolbar = EditableToolbar +const Cancel = EditableCancel +const Submit = EditableSubmit + +export { + Editable, + EditableLabel, + EditableArea, + EditablePreview, + EditableInput, + EditableToolbar, + EditableCancel, + EditableSubmit, + EditableTrigger, + // + Root, + Label, + Area, + Preview, + Input, + Toolbar, + Cancel, + Submit, + Trigger, +} diff --git a/apps/www/__registry__/icons.tsx b/apps/www/__registry__/icons.tsx index 82d24685219..3c8f028fcb3 100644 --- a/apps/www/__registry__/icons.tsx +++ b/apps/www/__registry__/icons.tsx @@ -4,264 +4,448 @@ import * as React from "react" export const Icons = { - "AlertCircle": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.AlertCircle - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.ExclamationTriangleIcon - }))), -}, "ArrowLeft": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.ArrowLeft - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.ArrowLeftIcon - }))), -}, "ArrowRight": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.ArrowRight - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.ArrowRightIcon - }))), -}, "ArrowUpDown": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.ArrowUpDown - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.CaretSortIcon - }))), -}, "BellRing": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.BellRing - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.BellIcon - }))), -}, "Bold": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Bold - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.FontBoldIcon - }))), -}, "Calculator": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Calculator - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.ComponentPlaceholderIcon - }))), -}, "Calendar": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Calendar - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.CalendarIcon - }))), -}, "Check": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Check - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.CheckIcon - }))), -}, "ChevronDown": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.ChevronDown - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.ChevronDownIcon - }))), -}, "ChevronLeft": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.ChevronLeft - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.ChevronLeftIcon - }))), -}, "ChevronRight": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.ChevronRight - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.ChevronRightIcon - }))), -}, "ChevronUp": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.ChevronUp - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.ChevronUpIcon - }))), -}, "ChevronsUpDown": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.ChevronsUpDown - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.CaretSortIcon - }))), -}, "Circle": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Circle - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.DotFilledIcon - }))), -}, "Copy": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Copy - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.CopyIcon - }))), -}, "CreditCard": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.CreditCard - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.ComponentPlaceholderIcon - }))), -}, "GripVertical": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.GripVertical - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.DragHandleDots2Icon - }))), -}, "Italic": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Italic - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.FontItalicIcon - }))), -}, "Loader2": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Loader2 - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.ReloadIcon - }))), -}, "Mail": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Mail - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.EnvelopeClosedIcon - }))), -}, "MailOpen": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.MailOpen - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.EnvelopeOpenIcon - }))), -}, "Minus": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Minus - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.MinusIcon - }))), -}, "Moon": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Moon - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.MoonIcon - }))), -}, "MoreHorizontal": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.MoreHorizontal - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.DotsHorizontalIcon - }))), -}, "PanelLeft": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.PanelLeft - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.ViewVerticalIcon - }))), -}, "Plus": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Plus - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.PlusIcon - }))), -}, "Search": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Search - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.MagnifyingGlassIcon - }))), -}, "Send": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Send - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.PaperPlaneIcon - }))), -}, "Settings": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Settings - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.GearIcon - }))), -}, "Slash": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Slash - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.SlashIcon - }))), -}, "Smile": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Smile - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.FaceIcon - }))), -}, "Sun": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Sun - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.SunIcon - }))), -}, "Terminal": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Terminal - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.RocketIcon - }))), -}, "Underline": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.Underline - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.UnderlineIcon - }))), -}, "User": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.User - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.PersonIcon - }))), -}, "X": { - lucide: React.lazy(() => import("lucide-react").then(mod => ({ - default: mod.X - }))), - radix: React.lazy(() => import("@radix-ui/react-icons").then(mod => ({ - default: mod.Cross2Icon - }))), -}, + AlertCircle: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.AlertCircle, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.ExclamationTriangleIcon, + })) + ), + }, + ArrowLeft: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.ArrowLeft, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.ArrowLeftIcon, + })) + ), + }, + ArrowRight: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.ArrowRight, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.ArrowRightIcon, + })) + ), + }, + ArrowUpDown: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.ArrowUpDown, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.CaretSortIcon, + })) + ), + }, + BellRing: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.BellRing, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.BellIcon, + })) + ), + }, + Bold: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Bold, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.FontBoldIcon, + })) + ), + }, + Calculator: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Calculator, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.ComponentPlaceholderIcon, + })) + ), + }, + Calendar: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Calendar, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.CalendarIcon, + })) + ), + }, + Check: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Check, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.CheckIcon, + })) + ), + }, + ChevronDown: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.ChevronDown, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.ChevronDownIcon, + })) + ), + }, + ChevronLeft: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.ChevronLeft, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.ChevronLeftIcon, + })) + ), + }, + ChevronRight: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.ChevronRight, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.ChevronRightIcon, + })) + ), + }, + ChevronUp: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.ChevronUp, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.ChevronUpIcon, + })) + ), + }, + ChevronsUpDown: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.ChevronsUpDown, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.CaretSortIcon, + })) + ), + }, + Circle: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Circle, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.DotFilledIcon, + })) + ), + }, + Copy: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Copy, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.CopyIcon, + })) + ), + }, + CreditCard: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.CreditCard, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.ComponentPlaceholderIcon, + })) + ), + }, + GripVertical: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.GripVertical, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.DragHandleDots2Icon, + })) + ), + }, + Italic: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Italic, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.FontItalicIcon, + })) + ), + }, + Loader2: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Loader2, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.ReloadIcon, + })) + ), + }, + Mail: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Mail, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.EnvelopeClosedIcon, + })) + ), + }, + MailOpen: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.MailOpen, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.EnvelopeOpenIcon, + })) + ), + }, + Minus: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Minus, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.MinusIcon, + })) + ), + }, + Moon: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Moon, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.MoonIcon, + })) + ), + }, + MoreHorizontal: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.MoreHorizontal, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.DotsHorizontalIcon, + })) + ), + }, + PanelLeft: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.PanelLeft, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.ViewVerticalIcon, + })) + ), + }, + Plus: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Plus, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.PlusIcon, + })) + ), + }, + Search: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Search, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.MagnifyingGlassIcon, + })) + ), + }, + Send: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Send, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.PaperPlaneIcon, + })) + ), + }, + Settings: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Settings, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.GearIcon, + })) + ), + }, + Slash: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Slash, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.SlashIcon, + })) + ), + }, + Smile: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Smile, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.FaceIcon, + })) + ), + }, + Sun: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Sun, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.SunIcon, + })) + ), + }, + Terminal: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Terminal, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.RocketIcon, + })) + ), + }, + Underline: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.Underline, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.UnderlineIcon, + })) + ), + }, + User: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.User, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.PersonIcon, + })) + ), + }, + X: { + lucide: React.lazy(() => + import("lucide-react").then((mod) => ({ + default: mod.X, + })) + ), + radix: React.lazy(() => + import("@radix-ui/react-icons").then((mod) => ({ + default: mod.Cross2Icon, + })) + ), + }, } diff --git a/apps/www/config/docs.ts b/apps/www/config/docs.ts index 7309e7f666f..7ba3099b371 100644 --- a/apps/www/config/docs.ts +++ b/apps/www/config/docs.ts @@ -269,6 +269,12 @@ export const docsConfig: DocsConfig = { href: "/docs/components/drawer", items: [], }, + { + title: "Editable", + href: "/docs/components/editable", + items: [], + label: "New", + }, { title: "Dropdown Menu", href: "/docs/components/dropdown-menu", diff --git a/apps/www/content/docs/components/editable.mdx b/apps/www/content/docs/components/editable.mdx new file mode 100644 index 00000000000..0c4db7ecee7 --- /dev/null +++ b/apps/www/content/docs/components/editable.mdx @@ -0,0 +1,92 @@ +--- +title: Editable +description: An accessible inline editable component for editing text content in place. +component: true +--- + +<ComponentPreview + name="editable-demo" + title="Editable" + description="An accessible inline editable component for editing text content in place." +/> + +## Installation + +<CodeTabs> + +<TabsList> + <TabsTrigger value="cli">CLI</TabsTrigger> + <TabsTrigger value="manual">Manual</TabsTrigger> +</TabsList> +<TabsContent value="cli"> + +```bash +npx shadcn@latest add editable +``` + +</TabsContent> + +<TabsContent value="manual"> + +<Steps> + +<Step>Install the following dependencies:</Step> + +```bash +npm install @radix-ui/react-slot +``` + + <Step> + Copy the composition utilities into your `lib/composition.ts` file. + + <ComponentSource src="./lib/composition.ts" /> + + </Step> + +<Step>Copy and paste the following code into your project.</Step> + +<ComponentSource name="editable" /> + +<Step>Update the import paths to match your project setup.</Step> + +</Steps> + +</TabsContent> + +</CodeTabs> + +## Usage + +```tsx +import { + Editable, + EditableArea, + EditableCancel, + EditableInput, + EditableLabel, + EditablePreview, + EditableSubmit, + EditableToolbar, +} from "@/components/ui/editable" +``` + +```tsx +<Editable> + <EditableLabel>Fruit</EditableLabel> + <EditableArea> + <EditablePreview /> + <EditableInput placeholder="Apple" /> + <EditableTrigger>Edit</EditableTrigger> + </EditableArea> + <EditableToolbar> + <EditableCancel>Cancel</EditableCancel> + <EditableSubmit>Save</EditableSubmit> + </EditableToolbar> +</Editable> +``` + +## Examples + +### Form + +<ComponentPreview name="editable-form-demo" /> diff --git a/apps/www/registry/default/examples/editable-demo.tsx b/apps/www/registry/default/examples/editable-demo.tsx new file mode 100644 index 00000000000..ce863a24932 --- /dev/null +++ b/apps/www/registry/default/examples/editable-demo.tsx @@ -0,0 +1,93 @@ +"use client" + +import * as React from "react" +import { Edit, Trash2 } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/registry/default/ui/button" +import { Checkbox } from "@/registry/default/ui/checkbox" +import { + Editable, + EditableArea, + EditableInput, + EditablePreview, + EditableTrigger, +} from "@/registry/default/ui/editable" + +interface Todo { + id: string + text: string + completed: boolean +} + +export default function EditableDemo() { + const [todos, setTodos] = React.useState<Todo[]>([ + { id: "1", text: "Ollie", completed: false }, + { id: "2", text: "Kickflip", completed: false }, + { id: "3", text: "360 flip", completed: false }, + { id: "4", text: "540 flip", completed: false }, + ]) + + const onDeleteTodo = React.useCallback((id: string) => { + setTodos((prev) => prev.filter((todo) => todo.id !== id)) + }, []) + + const onToggleTodo = React.useCallback((id: string) => { + setTodos((prev) => + prev.map((todo) => + todo.id === id ? { ...todo, completed: !todo.completed } : todo + ) + ) + }, []) + + const onUpdateTodo = React.useCallback((id: string, newText: string) => { + setTodos((prev) => + prev.map((todo) => (todo.id === id ? { ...todo, text: newText } : todo)) + ) + }, []) + + return ( + <div className="flex w-full min-w-0 flex-col gap-2"> + <span className="text-lg font-semibold">Tricks to learn</span> + {todos.map((todo) => ( + <div + key={todo.id} + className="flex items-center gap-2 rounded-lg border bg-card px-4 py-2" + > + <Checkbox + checked={todo.completed} + onCheckedChange={() => onToggleTodo(todo.id)} + /> + <Editable + key={todo.id} + defaultValue={todo.text} + onSubmit={(value) => onUpdateTodo(todo.id, value)} + className="flex flex-1 flex-row items-center gap-1.5" + > + <EditableArea className="flex-1"> + <EditablePreview + className={cn("w-full rounded-md px-1.5 py-1", { + "text-muted-foreground line-through": todo.completed, + })} + /> + <EditableInput className="px-1.5 py-1" /> + </EditableArea> + <EditableTrigger asChild> + <Button variant="ghost" size="icon" className="size-7"> + <Edit /> + </Button> + </EditableTrigger> + </Editable> + <Button + variant="ghost" + size="icon" + className="size-7 text-destructive" + onClick={() => onDeleteTodo(todo.id)} + > + <Trash2 /> + </Button> + </div> + ))} + </div> + ) +} diff --git a/apps/www/registry/default/examples/editable-form-demo.tsx b/apps/www/registry/default/examples/editable-form-demo.tsx new file mode 100644 index 00000000000..315bf62174f --- /dev/null +++ b/apps/www/registry/default/examples/editable-form-demo.tsx @@ -0,0 +1,139 @@ +"use client" + +import * as React from "react" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { toast } from "sonner" +import * as z from "zod" + +import { Button } from "@/registry/default/ui/button" +import { + Editable, + EditableArea, + EditableInput, + EditableLabel, + EditablePreview, + EditableTrigger, +} from "@/registry/default/ui/editable" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/registry/default/ui/form" + +const formSchema = z.object({ + name: z + .string() + .min(2, "Name must be at least 2 characters") + .max(50, "Name must be less than 50 characters"), + title: z + .string() + .min(3, "Title must be at least 3 characters") + .max(100, "Title must be less than 100 characters"), +}) + +type FormValues = z.infer<typeof formSchema> + +export default function EditableFormDemo() { + const form = useForm<FormValues>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "Rodney Mullen", + title: "Skateboarder", + }, + }) + + const onSubmit = React.useCallback((input: FormValues) => { + toast.success( + <pre className="w-full">{JSON.stringify(input, null, 2)}</pre> + ) + }, []) + + return ( + <Form {...form}> + <form + onSubmit={form.handleSubmit(onSubmit)} + className="flex w-full flex-col gap-2 rounded-md border p-4 shadow-sm" + > + <FormField + control={form.control} + name="name" + render={({ field }) => ( + <FormItem> + <FormControl> + <Editable + defaultValue={field.value} + onSubmit={field.onChange} + invalid={!!form.formState.errors.name} + > + <FormLabel asChild> + <EditableLabel>Name</EditableLabel> + </FormLabel> + <div className="flex items-start gap-4"> + <EditableArea className="flex-1"> + <EditablePreview /> + <EditableInput /> + </EditableArea> + <EditableTrigger asChild> + <Button type="button" variant="outline" size="sm"> + Edit + </Button> + </EditableTrigger> + </div> + <FormMessage /> + </Editable> + </FormControl> + </FormItem> + )} + /> + <FormField + control={form.control} + name="title" + render={({ field }) => ( + <FormItem> + <FormControl> + <Editable + defaultValue={field.value} + onSubmit={field.onChange} + invalid={!!form.formState.errors.title} + > + <FormLabel asChild> + <EditableLabel>Title</EditableLabel> + </FormLabel> + <div className="flex items-start gap-4"> + <EditableArea className="flex-1"> + <EditablePreview /> + <EditableInput /> + </EditableArea> + <EditableTrigger asChild> + <Button type="button" variant="outline" size="sm"> + Edit + </Button> + </EditableTrigger> + </div> + <FormMessage /> + </Editable> + </FormControl> + </FormItem> + )} + /> + <div className="flex w-fit gap-2 self-end"> + <Button type="submit" className="w-full"> + Update + </Button> + <Button + type="button" + variant="outline" + className="w-full" + onClick={() => form.reset()} + > + Cancel + </Button> + </div> + </form> + </Form> + ) +} diff --git a/apps/www/registry/default/lib/composition.ts b/apps/www/registry/default/lib/composition.ts new file mode 100644 index 00000000000..013e3be6677 --- /dev/null +++ b/apps/www/registry/default/lib/composition.ts @@ -0,0 +1,88 @@ +import * as React from "react" + +/** + * A utility to compose multiple event handlers into a single event handler. + * Run originalEventHandler first, then ourEventHandler unless prevented. + */ +function composeEventHandlers<E>( + originalEventHandler?: (event: E) => void, + ourEventHandler?: (event: E) => void, + { checkForDefaultPrevented = true } = {} +) { + return function handleEvent(event: E) { + originalEventHandler?.(event) + + if ( + checkForDefaultPrevented === false || + !(event as unknown as Event).defaultPrevented + ) { + return ourEventHandler?.(event) + } + } +} + +/** + * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx + */ + +type PossibleRef<T> = React.Ref<T> | undefined + +/** + * Set a given ref to a given value. + * This utility takes care of different types of refs: callback refs and RefObject(s). + */ +function setRef<T>(ref: PossibleRef<T>, value: T) { + if (typeof ref === "function") { + return ref(value) + } + + if (ref !== null && ref !== undefined) { + // @ts-expect-error - ref.current is writable + ref.current = value + } +} + +/** + * A utility to compose multiple refs together. + * Accepts callback refs and RefObject(s). + */ +function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> { + return (node) => { + let hasCleanup = false + const cleanups = refs.map((ref) => { + const cleanup = setRef(ref, node) + if (!hasCleanup && typeof cleanup === "function") { + hasCleanup = true + } + return cleanup + }) + + // React <19 will log an error to the console if a callback ref returns a + // value. We don't use ref cleanups internally so this will only happen if a + // user's ref callback returns a value, which we only expect if they are + // using the cleanup functionality added in React 19. + if (hasCleanup) { + return () => { + for (let i = 0; i < cleanups.length; i++) { + const cleanup = cleanups[i] + if (typeof cleanup === "function") { + cleanup() + } else { + setRef(refs[i], null) + } + } + } + } + } +} + +/** + * A custom hook that composes multiple refs. + * Accepts callback refs and RefObject(s). + */ +function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> { + // eslint-disable-next-line react-hooks/exhaustive-deps + return React.useCallback(composeRefs(...refs), refs) +} + +export { composeEventHandlers, composeRefs, useComposedRefs } diff --git a/apps/www/registry/default/ui/editable.tsx b/apps/www/registry/default/ui/editable.tsx new file mode 100644 index 00000000000..1e13fefc88d --- /dev/null +++ b/apps/www/registry/default/ui/editable.tsx @@ -0,0 +1,864 @@ +"use client" + +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" + +import { cn } from "@/lib/utils" +import { + composeEventHandlers, + useComposedRefs, +} from "@/registry/default/lib/composition" + +const DATA_ACTION_ATTR = "data-action" + +const ROOT_NAME = "Editable" +const AREA_NAME = "EditableArea" +const PREVIEW_NAME = "EditablePreview" +const INPUT_NAME = "EditableInput" +const TRIGGER_NAME = "EditableTrigger" +const LABEL_NAME = "EditableLabel" +const TOOLBAR_NAME = "EditableToolbar" +const CANCEL_NAME = "EditableCancel" +const SUBMIT_NAME = "EditableSubmit" + +const EDITABLE_ERRORS = { + [ROOT_NAME]: `\`${ROOT_NAME}\` components must be within \`${ROOT_NAME}\``, + [AREA_NAME]: `\`${AREA_NAME}\` must be within \`${ROOT_NAME}\``, + [PREVIEW_NAME]: `\`${PREVIEW_NAME}\` must be within \`${ROOT_NAME}\``, + [INPUT_NAME]: `\`${INPUT_NAME}\` must be within \`${ROOT_NAME}\``, + [TRIGGER_NAME]: `\`${TRIGGER_NAME}\` must be within \`${ROOT_NAME}\``, + [LABEL_NAME]: `\`${LABEL_NAME}\` must be within \`${ROOT_NAME}\``, + [TOOLBAR_NAME]: `\`${TOOLBAR_NAME}\` must be within \`${ROOT_NAME}\``, + [CANCEL_NAME]: `\`${CANCEL_NAME}\` must be within \`${ROOT_NAME}\``, + [SUBMIT_NAME]: `\`${SUBMIT_NAME}\` must be within \`${ROOT_NAME}\``, +} as const + +type Direction = "ltr" | "rtl" + +const DirectionContext = React.createContext<Direction | undefined>(undefined) + +function useDirection(dirProp?: Direction): Direction { + const contextDir = React.useContext(DirectionContext) + return dirProp ?? contextDir ?? "ltr" +} + +interface EditableContextValue { + id: string + inputId: string + labelId: string + defaultValue: string + value: string + onValueChange: (value: string) => void + editing: boolean + onCancel: () => void + onEdit: () => void + onSubmit: (value: string) => void + onEscapeKeyDown?: (event: KeyboardEvent) => void + dir?: Direction + maxLength?: number + placeholder?: string + triggerMode: "click" | "dblclick" | "focus" + autosize: boolean + disabled?: boolean + readOnly?: boolean + required?: boolean + invalid?: boolean +} + +const EditableContext = React.createContext<EditableContextValue | null>(null) +EditableContext.displayName = ROOT_NAME + +function useEditableContext(name: keyof typeof EDITABLE_ERRORS) { + const context = React.useContext(EditableContext) + if (!context) { + throw new Error(EDITABLE_ERRORS[name]) + } + return context +} + +type RootElement = React.ComponentRef<typeof Editable> + +interface EditableRootProps + extends Omit<React.ComponentPropsWithoutRef<"div">, "onSubmit"> { + id?: string + defaultValue?: string + value?: string + onValueChange?: (value: string) => void + defaultEditing?: boolean + editing?: boolean + onEditingChange?: (editing: boolean) => void + onCancel?: () => void + onEdit?: () => void + onSubmit?: (value: string) => void + onEscapeKeyDown?: (event: KeyboardEvent) => void + dir?: Direction + maxLength?: number + name?: string + placeholder?: string + triggerMode?: EditableContextValue["triggerMode"] + asChild?: boolean + autosize?: boolean + disabled?: boolean + readOnly?: boolean + required?: boolean + invalid?: boolean +} + +const EditableRoot = React.forwardRef<HTMLDivElement, EditableRootProps>( + (props, forwardedRef) => { + const { + defaultValue = "", + value: valueProp, + onValueChange: onValueChangeProp, + defaultEditing = false, + editing: editingProp, + onEditingChange: onEditingChangeProp, + onCancel: onCancelProp, + onEdit: onEditProp, + onSubmit: onSubmitProp, + onEscapeKeyDown, + dir: dirProp, + maxLength, + name, + placeholder, + triggerMode = "click", + asChild, + autosize = false, + disabled, + required, + readOnly, + invalid, + className, + ...rootProps + } = props + + const id = React.useId() + const inputId = React.useId() + const labelId = React.useId() + + const dir = useDirection(dirProp) + + const isControlled = valueProp !== undefined + const [uncontrolledValue, setUncontrolledValue] = + React.useState(defaultValue) + const value = isControlled ? valueProp : uncontrolledValue + const previousValueRef = React.useRef(value) + const onValueChangeRef = React.useRef(onValueChangeProp) + + const isEditingControlled = editingProp !== undefined + const [uncontrolledEditing, setUncontrolledEditing] = + React.useState(defaultEditing) + const editing = isEditingControlled ? editingProp : uncontrolledEditing + const onEditingChangeRef = React.useRef(onEditingChangeProp) + + React.useEffect(() => { + onValueChangeRef.current = onValueChangeProp + onEditingChangeRef.current = onEditingChangeProp + }) + + const onValueChange = React.useCallback( + (nextValue: string) => { + if (!isControlled) { + setUncontrolledValue(nextValue) + } + onValueChangeRef.current?.(nextValue) + }, + [isControlled] + ) + + const onEditingChange = React.useCallback( + (nextEditing: boolean) => { + if (!isEditingControlled) { + setUncontrolledEditing(nextEditing) + } + onEditingChangeRef.current?.(nextEditing) + }, + [isEditingControlled] + ) + + React.useEffect(() => { + if (isControlled && valueProp !== previousValueRef.current) { + previousValueRef.current = valueProp + } + }, [isControlled, valueProp]) + + const [formTrigger, setFormTrigger] = React.useState<RootElement | null>( + null + ) + const composedRef = useComposedRefs(forwardedRef, (node) => + setFormTrigger(node) + ) + const isFormControl = formTrigger ? !!formTrigger.closest("form") : true + + const onCancel = React.useCallback(() => { + const prevValue = previousValueRef.current + onValueChange(prevValue) + onEditingChange(false) + onCancelProp?.() + }, [onValueChange, onCancelProp, onEditingChange]) + + const onEdit = React.useCallback(() => { + previousValueRef.current = value + onEditingChange(true) + onEditProp?.() + }, [value, onEditProp, onEditingChange]) + + const onSubmit = React.useCallback( + (newValue: string) => { + onValueChange(newValue) + onEditingChange(false) + onSubmitProp?.(newValue) + }, + + [onValueChange, onSubmitProp, onEditingChange] + ) + + const contextValue = React.useMemo<EditableContextValue>( + () => ({ + id, + inputId, + labelId, + defaultValue, + value, + onValueChange, + editing, + onSubmit, + onEdit, + onCancel, + onEscapeKeyDown, + dir, + maxLength, + placeholder, + triggerMode, + autosize, + disabled, + readOnly, + required, + invalid, + }), + [ + id, + inputId, + labelId, + defaultValue, + value, + onValueChange, + editing, + onSubmit, + onCancel, + onEdit, + onEscapeKeyDown, + dir, + maxLength, + placeholder, + triggerMode, + autosize, + disabled, + required, + readOnly, + invalid, + ] + ) + + const RootPrimitive = asChild ? Slot : "div" + + return ( + <EditableContext.Provider value={contextValue}> + <RootPrimitive + data-slot="editable" + {...rootProps} + ref={composedRef} + id={id} + className={cn("flex min-w-0 flex-col gap-2", className)} + /> + {isFormControl && ( + <VisuallyHiddenInput + type="hidden" + control={formTrigger} + name={name} + value={value} + disabled={disabled} + readOnly={readOnly} + required={required} + /> + )} + </EditableContext.Provider> + ) + } +) +EditableRoot.displayName = ROOT_NAME + +interface EditableLabelProps extends React.ComponentPropsWithoutRef<"label"> { + asChild?: boolean +} + +const EditableLabel = React.forwardRef<HTMLLabelElement, EditableLabelProps>( + (props, forwardedRef) => { + const { asChild, className, children, ...labelProps } = props + const context = useEditableContext(LABEL_NAME) + + const LabelPrimitive = asChild ? Slot : "label" + + return ( + <LabelPrimitive + data-disabled={context.disabled ? "" : undefined} + data-invalid={context.invalid ? "" : undefined} + data-required={context.required ? "" : undefined} + data-slot="editable-label" + {...labelProps} + ref={forwardedRef} + id={context.labelId} + htmlFor={context.inputId} + className={cn( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 data-[required]:after:ml-0.5 data-[required]:after:text-destructive data-[required]:after:content-['*']", + className + )} + > + {children} + </LabelPrimitive> + ) + } +) +EditableLabel.displayName = LABEL_NAME + +interface EditableAreaProps extends React.ComponentPropsWithoutRef<"div"> { + asChild?: boolean +} + +const EditableArea = React.forwardRef<HTMLDivElement, EditableAreaProps>( + (props, forwardedRef) => { + const { asChild, className, ...areaProps } = props + const context = useEditableContext(AREA_NAME) + + const AreaPrimitive = asChild ? Slot : "div" + + return ( + <AreaPrimitive + role="group" + data-disabled={context.disabled ? "" : undefined} + data-editing={context.editing ? "" : undefined} + data-slot="editable-area" + dir={context.dir} + {...areaProps} + ref={forwardedRef} + className={cn( + "relative inline-block min-w-0 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50", + className + )} + /> + ) + } +) +EditableArea.displayName = AREA_NAME + +interface EditablePreviewProps extends React.ComponentPropsWithoutRef<"div"> { + asChild?: boolean +} + +const EditablePreview = React.forwardRef<HTMLDivElement, EditablePreviewProps>( + (props, forwardedRef) => { + const { asChild, className, ...previewProps } = props + const context = useEditableContext(PREVIEW_NAME) + + const onTrigger = React.useCallback(() => { + if (context.disabled || context.readOnly) return + context.onEdit() + }, [context]) + + const PreviewPrimitive = asChild ? Slot : "div" + + if (context.editing || context.readOnly) return null + + return ( + <PreviewPrimitive + role="button" + aria-disabled={context.disabled || context.readOnly} + data-empty={!context.value ? "" : undefined} + data-disabled={context.disabled ? "" : undefined} + data-readonly={context.readOnly ? "" : undefined} + data-slot="editable-preview" + tabIndex={context.disabled || context.readOnly ? undefined : 0} + {...previewProps} + ref={forwardedRef} + onClick={composeEventHandlers( + previewProps.onClick, + context.triggerMode === "click" ? onTrigger : undefined + )} + onDoubleClick={composeEventHandlers( + previewProps.onDoubleClick, + context.triggerMode === "dblclick" ? onTrigger : undefined + )} + onFocus={composeEventHandlers( + previewProps.onFocus, + context.triggerMode === "focus" ? onTrigger : undefined + )} + className={cn( + "focus-visible:outline-hidden cursor-text truncate rounded-sm border border-transparent py-1 text-base focus-visible:ring-1 focus-visible:ring-ring data-[disabled]:cursor-not-allowed data-[readonly]:cursor-default data-[empty]:text-muted-foreground data-[disabled]:opacity-50 md:text-sm", + className + )} + > + {context.value || context.placeholder} + </PreviewPrimitive> + ) + } +) +EditablePreview.displayName = PREVIEW_NAME + +const useIsomorphicLayoutEffect = + typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect + +type InputElement = React.ComponentRef<typeof EditableInput> + +interface EditableInputProps extends React.ComponentPropsWithoutRef<"input"> { + asChild?: boolean + maxLength?: number +} + +const EditableInput = React.forwardRef<HTMLInputElement, EditableInputProps>( + (props, forwardedRef) => { + const { + asChild, + className, + disabled, + readOnly, + required, + maxLength, + ...inputProps + } = props + const context = useEditableContext(INPUT_NAME) + const inputRef = React.useRef<InputElement>(null) + const composedRef = useComposedRefs(forwardedRef, inputRef) + + const isDisabled = disabled || context.disabled + const isReadOnly = readOnly || context.readOnly + const isRequired = required || context.required + + const onAutosize = React.useCallback( + (target: HTMLInputElement | HTMLTextAreaElement) => { + if (!context.autosize) return + + if (target instanceof HTMLTextAreaElement) { + target.style.height = "0" + target.style.height = `${target.scrollHeight}px` + } else { + target.style.width = "0" + target.style.width = `${target.scrollWidth + 4}px` + } + }, + [context.autosize] + ) + const onBlur = React.useCallback( + (event: React.FocusEvent<InputElement>) => { + if (isReadOnly) return + const relatedTarget = event.relatedTarget + + const isAction = + relatedTarget instanceof HTMLElement && + relatedTarget.closest(`[${DATA_ACTION_ATTR}=""]`) + + if (!isAction) { + context.onSubmit(context.value) + } + }, + [context, isReadOnly] + ) + + const onChange = React.useCallback( + (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { + if (isReadOnly) return + context.onValueChange(event.target.value) + onAutosize(event.target) + }, + [context, isReadOnly, onAutosize] + ) + + const onKeyDown = React.useCallback( + (event: React.KeyboardEvent<InputElement>) => { + if (isReadOnly) return + if (event.key === "Escape") { + const nativeEvent = event.nativeEvent + if (context.onEscapeKeyDown) { + context.onEscapeKeyDown(nativeEvent) + if (nativeEvent.defaultPrevented) return + } + context.onCancel() + } else if (event.key === "Enter") { + context.onSubmit(context.value) + } + }, + [context, isReadOnly] + ) + + useIsomorphicLayoutEffect(() => { + if (!context.editing || isReadOnly || !inputRef.current) return + + const frameId = window.requestAnimationFrame(() => { + if (!inputRef.current) return + + inputRef.current.focus() + inputRef.current.select() + onAutosize(inputRef.current) + }) + + return () => { + window.cancelAnimationFrame(frameId) + } + }, [context.editing, isReadOnly, onAutosize]) + + const InputPrimitive = asChild ? Slot : "input" + + if (!context.editing && !isReadOnly) return null + + return ( + <InputPrimitive + aria-required={isRequired} + aria-invalid={context.invalid} + data-slot="editable-input" + dir={context.dir} + disabled={isDisabled} + readOnly={isReadOnly} + required={isRequired} + {...inputProps} + id={context.inputId} + aria-labelledby={context.labelId} + ref={composedRef} + maxLength={maxLength} + placeholder={context.placeholder} + value={context.value} + onBlur={composeEventHandlers(inputProps.onBlur, onBlur)} + onChange={composeEventHandlers(inputProps.onChange, onChange)} + onKeyDown={composeEventHandlers(inputProps.onKeyDown, onKeyDown)} + className={cn( + "shadow-xs focus-visible:outline-hidden flex rounded-sm border border-input bg-transparent py-1 text-base transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", + context.autosize ? "w-auto" : "w-full", + className + )} + /> + ) + } +) +EditableInput.displayName = INPUT_NAME + +interface EditableTriggerProps + extends React.ComponentPropsWithoutRef<"button"> { + asChild?: boolean + forceMount?: boolean +} + +const EditableTrigger = React.forwardRef< + HTMLButtonElement, + EditableTriggerProps +>((props, forwardedRef) => { + const { asChild, forceMount = false, ...triggerProps } = props + const context = useEditableContext(TRIGGER_NAME) + + const onTrigger = React.useCallback(() => { + if (context.disabled || context.readOnly) return + context.onEdit() + }, [context]) + + const TriggerPrimitive = asChild ? Slot : "button" + + if (!forceMount && (context.editing || context.readOnly)) return null + + return ( + <TriggerPrimitive + type="button" + aria-controls={context.id} + aria-disabled={context.disabled || context.readOnly} + data-disabled={context.disabled ? "" : undefined} + data-readonly={context.readOnly ? "" : undefined} + data-slot="editable-trigger" + {...triggerProps} + ref={forwardedRef} + onClick={context.triggerMode === "click" ? onTrigger : undefined} + onDoubleClick={context.triggerMode === "dblclick" ? onTrigger : undefined} + /> + ) +}) +EditableTrigger.displayName = TRIGGER_NAME + +interface EditableToolbarProps extends React.ComponentPropsWithoutRef<"div"> { + asChild?: boolean + orientation?: "horizontal" | "vertical" +} + +const EditableToolbar = React.forwardRef<HTMLDivElement, EditableToolbarProps>( + (props, forwardedRef) => { + const { + asChild, + className, + orientation = "horizontal", + ...toolbarProps + } = props + const context = useEditableContext(TOOLBAR_NAME) + + const ToolbarPrimitive = asChild ? Slot : "div" + + return ( + <ToolbarPrimitive + role="toolbar" + aria-controls={context.id} + aria-orientation={orientation} + data-slot="editable-toolbar" + dir={context.dir} + {...toolbarProps} + ref={forwardedRef} + className={cn( + "flex items-center gap-2", + orientation === "vertical" && "flex-col", + className + )} + /> + ) + } +) +EditableToolbar.displayName = TOOLBAR_NAME + +interface EditableCancelProps extends React.ComponentPropsWithoutRef<"button"> { + asChild?: boolean +} + +const EditableCancel = React.forwardRef<HTMLButtonElement, EditableCancelProps>( + (props, forwardedRef) => { + const { asChild, ...cancelProps } = props + const context = useEditableContext(CANCEL_NAME) + + const CancelPrimitive = asChild ? Slot : "button" + + if (!context.editing && !context.readOnly) return null + + return ( + <CancelPrimitive + type="button" + aria-controls={context.id} + data-slot="editable-cancel" + {...{ [DATA_ACTION_ATTR]: "" }} + {...cancelProps} + onClick={composeEventHandlers(cancelProps.onClick, () => { + context.onCancel() + })} + ref={forwardedRef} + /> + ) + } +) +EditableCancel.displayName = CANCEL_NAME + +interface EditableSubmitProps extends React.ComponentPropsWithoutRef<"button"> { + asChild?: boolean +} + +const EditableSubmit = React.forwardRef<HTMLButtonElement, EditableSubmitProps>( + (props, forwardedRef) => { + const { asChild, ...submitProps } = props + const context = useEditableContext(SUBMIT_NAME) + + const SubmitPrimitive = asChild ? Slot : "button" + + if (!context.editing && !context.readOnly) return null + + return ( + <SubmitPrimitive + type="button" + aria-controls={context.id} + data-slot="editable-submit" + {...{ [DATA_ACTION_ATTR]: "" }} + {...submitProps} + ref={forwardedRef} + onClick={composeEventHandlers(submitProps.onClick, () => { + context.onSubmit(context.value) + })} + /> + ) + } +) +EditableSubmit.displayName = SUBMIT_NAME + +type InputValue = string[] | string + +interface VisuallyHiddenInputProps<T = InputValue> + extends Omit< + React.InputHTMLAttributes<HTMLInputElement>, + "value" | "checked" | "onReset" + > { + value?: T + checked?: boolean + control: HTMLElement | null + bubbles?: boolean +} + +function VisuallyHiddenInput<T = InputValue>( + props: VisuallyHiddenInputProps<T> +) { + const { + control, + value, + checked, + bubbles = true, + type = "hidden", + style, + ...inputProps + } = props + + const isCheckInput = React.useMemo( + () => type === "checkbox" || type === "radio" || type === "switch", + [type] + ) + const inputRef = React.useRef<HTMLInputElement>(null) + + const prevValueRef = React.useRef<{ + value: T | boolean | undefined + previous: T | boolean | undefined + }>({ + value: isCheckInput ? checked : value, + previous: isCheckInput ? checked : value, + }) + + const prevValue = React.useMemo(() => { + const currentValue = isCheckInput ? checked : value + if (prevValueRef.current.value !== currentValue) { + prevValueRef.current.previous = prevValueRef.current.value + prevValueRef.current.value = currentValue + } + return prevValueRef.current.previous + }, [isCheckInput, value, checked]) + + const [controlSize, setControlSize] = React.useState<{ + width?: number + height?: number + }>({}) + + React.useLayoutEffect(() => { + if (!control) { + setControlSize({}) + return + } + + setControlSize({ + width: control.offsetWidth, + height: control.offsetHeight, + }) + + if (typeof window === "undefined") return + + const resizeObserver = new ResizeObserver((entries) => { + if (!Array.isArray(entries) || !entries.length) return + + const entry = entries[0] + if (!entry) return + + let width: number + let height: number + + if ("borderBoxSize" in entry) { + const borderSizeEntry = entry.borderBoxSize + const borderSize = Array.isArray(borderSizeEntry) + ? borderSizeEntry[0] + : borderSizeEntry + width = borderSize.inlineSize + height = borderSize.blockSize + } else { + width = control.offsetWidth + height = control.offsetHeight + } + + setControlSize({ width, height }) + }) + + resizeObserver.observe(control, { box: "border-box" }) + return () => { + resizeObserver.disconnect() + } + }, [control]) + + React.useEffect(() => { + const input = inputRef.current + if (!input) return + + const inputProto = window.HTMLInputElement.prototype + const propertyKey = isCheckInput ? "checked" : "value" + const eventType = isCheckInput ? "click" : "input" + const currentValue = isCheckInput ? checked : value + + const serializedCurrentValue = isCheckInput + ? checked + : typeof value === "object" && value !== null + ? JSON.stringify(value) + : value + + const descriptor = Object.getOwnPropertyDescriptor(inputProto, propertyKey) + + const setter = descriptor?.set + + if (prevValue !== currentValue && setter) { + const event = new Event(eventType, { bubbles }) + setter.call(input, serializedCurrentValue) + input.dispatchEvent(event) + } + }, [prevValue, value, checked, bubbles, isCheckInput]) + + const composedStyle = React.useMemo<React.CSSProperties>(() => { + return { + ...style, + ...(controlSize.width !== undefined && controlSize.height !== undefined + ? controlSize + : {}), + border: 0, + clip: "rect(0 0 0 0)", + clipPath: "inset(50%)", + height: "1px", + margin: "-1px", + overflow: "hidden", + padding: 0, + position: "absolute", + whiteSpace: "nowrap", + width: "1px", + } + }, [style, controlSize]) + + return ( + <input + type={type} + {...inputProps} + ref={inputRef} + aria-hidden={isCheckInput} + tabIndex={-1} + defaultChecked={isCheckInput ? checked : undefined} + style={composedStyle} + /> + ) +} + +const Editable = EditableRoot +const Root = EditableRoot +const Label = EditableLabel +const Area = EditableArea +const Preview = EditablePreview +const Input = EditableInput +const Trigger = EditableTrigger +const Toolbar = EditableToolbar +const Cancel = EditableCancel +const Submit = EditableSubmit + +export { + Editable, + EditableLabel, + EditableArea, + EditablePreview, + EditableInput, + EditableToolbar, + EditableCancel, + EditableSubmit, + EditableTrigger, + // + Root, + Label, + Area, + Preview, + Input, + Toolbar, + Cancel, + Submit, + Trigger, +} diff --git a/apps/www/registry/new-york/examples/editable-demo.tsx b/apps/www/registry/new-york/examples/editable-demo.tsx new file mode 100644 index 00000000000..8cc67c52750 --- /dev/null +++ b/apps/www/registry/new-york/examples/editable-demo.tsx @@ -0,0 +1,93 @@ +"use client" + +import * as React from "react" +import { Edit, Trash2 } from "lucide-react" + +import { cn } from "@/lib/utils" +import { + Editable, + EditableArea, + EditableInput, + EditablePreview, + EditableTrigger, +} from "@/registry/default/ui/editable" +import { Button } from "@/registry/new-york/ui/button" +import { Checkbox } from "@/registry/new-york/ui/checkbox" + +interface Todo { + id: string + text: string + completed: boolean +} + +export default function EditableDemo() { + const [todos, setTodos] = React.useState<Todo[]>([ + { id: "1", text: "Ollie", completed: false }, + { id: "2", text: "Kickflip", completed: false }, + { id: "3", text: "360 flip", completed: false }, + { id: "4", text: "540 flip", completed: false }, + ]) + + const onDeleteTodo = React.useCallback((id: string) => { + setTodos((prev) => prev.filter((todo) => todo.id !== id)) + }, []) + + const onToggleTodo = React.useCallback((id: string) => { + setTodos((prev) => + prev.map((todo) => + todo.id === id ? { ...todo, completed: !todo.completed } : todo + ) + ) + }, []) + + const onUpdateTodo = React.useCallback((id: string, newText: string) => { + setTodos((prev) => + prev.map((todo) => (todo.id === id ? { ...todo, text: newText } : todo)) + ) + }, []) + + return ( + <div className="flex w-full min-w-0 flex-col gap-2"> + <span className="text-lg font-semibold">Tricks to learn</span> + {todos.map((todo) => ( + <div + key={todo.id} + className="flex items-center gap-2 rounded-lg border bg-card px-4 py-2" + > + <Checkbox + checked={todo.completed} + onCheckedChange={() => onToggleTodo(todo.id)} + /> + <Editable + key={todo.id} + defaultValue={todo.text} + onSubmit={(value) => onUpdateTodo(todo.id, value)} + className="flex flex-1 flex-row items-center gap-1.5" + > + <EditableArea className="flex-1"> + <EditablePreview + className={cn("w-full rounded-md px-1.5 py-1", { + "text-muted-foreground line-through": todo.completed, + })} + /> + <EditableInput className="px-1.5 py-1" /> + </EditableArea> + <EditableTrigger asChild> + <Button variant="ghost" size="icon" className="size-7"> + <Edit /> + </Button> + </EditableTrigger> + </Editable> + <Button + variant="ghost" + size="icon" + className="size-7 text-destructive" + onClick={() => onDeleteTodo(todo.id)} + > + <Trash2 /> + </Button> + </div> + ))} + </div> + ) +} diff --git a/apps/www/registry/new-york/examples/editable-form-demo.tsx b/apps/www/registry/new-york/examples/editable-form-demo.tsx new file mode 100644 index 00000000000..70ba4161676 --- /dev/null +++ b/apps/www/registry/new-york/examples/editable-form-demo.tsx @@ -0,0 +1,139 @@ +"use client" + +import * as React from "react" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { toast } from "sonner" +import * as z from "zod" + +import { Button } from "@/registry/new-york/ui/button" +import { + Editable, + EditableArea, + EditableInput, + EditableLabel, + EditablePreview, + EditableTrigger, +} from "@/registry/new-york/ui/editable" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/registry/new-york/ui/form" + +const formSchema = z.object({ + name: z + .string() + .min(2, "Name must be at least 2 characters") + .max(50, "Name must be less than 50 characters"), + title: z + .string() + .min(3, "Title must be at least 3 characters") + .max(100, "Title must be less than 100 characters"), +}) + +type FormValues = z.infer<typeof formSchema> + +export default function EditableFormDemo() { + const form = useForm<FormValues>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "Rodney Mullen", + title: "Skateboarder", + }, + }) + + const onSubmit = React.useCallback((input: FormValues) => { + toast.success( + <pre className="w-full">{JSON.stringify(input, null, 2)}</pre> + ) + }, []) + + return ( + <Form {...form}> + <form + onSubmit={form.handleSubmit(onSubmit)} + className="flex w-full flex-col gap-2 rounded-md border p-4 shadow-sm" + > + <FormField + control={form.control} + name="name" + render={({ field }) => ( + <FormItem> + <FormControl> + <Editable + defaultValue={field.value} + onSubmit={field.onChange} + invalid={!!form.formState.errors.name} + > + <FormLabel asChild> + <EditableLabel>Name</EditableLabel> + </FormLabel> + <div className="flex items-start gap-4"> + <EditableArea className="flex-1"> + <EditablePreview /> + <EditableInput /> + </EditableArea> + <EditableTrigger asChild> + <Button type="button" variant="outline" size="sm"> + Edit + </Button> + </EditableTrigger> + </div> + <FormMessage /> + </Editable> + </FormControl> + </FormItem> + )} + /> + <FormField + control={form.control} + name="title" + render={({ field }) => ( + <FormItem> + <FormControl> + <Editable + defaultValue={field.value} + onSubmit={field.onChange} + invalid={!!form.formState.errors.title} + > + <FormLabel asChild> + <EditableLabel>Title</EditableLabel> + </FormLabel> + <div className="flex items-start gap-4"> + <EditableArea className="flex-1"> + <EditablePreview /> + <EditableInput /> + </EditableArea> + <EditableTrigger asChild> + <Button type="button" variant="outline" size="sm"> + Edit + </Button> + </EditableTrigger> + </div> + <FormMessage /> + </Editable> + </FormControl> + </FormItem> + )} + /> + <div className="flex w-fit gap-2 self-end"> + <Button type="submit" className="w-full"> + Update + </Button> + <Button + type="button" + variant="outline" + className="w-full" + onClick={() => form.reset()} + > + Cancel + </Button> + </div> + </form> + </Form> + ) +} diff --git a/apps/www/registry/new-york/ui/editable.tsx b/apps/www/registry/new-york/ui/editable.tsx new file mode 100644 index 00000000000..1e13fefc88d --- /dev/null +++ b/apps/www/registry/new-york/ui/editable.tsx @@ -0,0 +1,864 @@ +"use client" + +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" + +import { cn } from "@/lib/utils" +import { + composeEventHandlers, + useComposedRefs, +} from "@/registry/default/lib/composition" + +const DATA_ACTION_ATTR = "data-action" + +const ROOT_NAME = "Editable" +const AREA_NAME = "EditableArea" +const PREVIEW_NAME = "EditablePreview" +const INPUT_NAME = "EditableInput" +const TRIGGER_NAME = "EditableTrigger" +const LABEL_NAME = "EditableLabel" +const TOOLBAR_NAME = "EditableToolbar" +const CANCEL_NAME = "EditableCancel" +const SUBMIT_NAME = "EditableSubmit" + +const EDITABLE_ERRORS = { + [ROOT_NAME]: `\`${ROOT_NAME}\` components must be within \`${ROOT_NAME}\``, + [AREA_NAME]: `\`${AREA_NAME}\` must be within \`${ROOT_NAME}\``, + [PREVIEW_NAME]: `\`${PREVIEW_NAME}\` must be within \`${ROOT_NAME}\``, + [INPUT_NAME]: `\`${INPUT_NAME}\` must be within \`${ROOT_NAME}\``, + [TRIGGER_NAME]: `\`${TRIGGER_NAME}\` must be within \`${ROOT_NAME}\``, + [LABEL_NAME]: `\`${LABEL_NAME}\` must be within \`${ROOT_NAME}\``, + [TOOLBAR_NAME]: `\`${TOOLBAR_NAME}\` must be within \`${ROOT_NAME}\``, + [CANCEL_NAME]: `\`${CANCEL_NAME}\` must be within \`${ROOT_NAME}\``, + [SUBMIT_NAME]: `\`${SUBMIT_NAME}\` must be within \`${ROOT_NAME}\``, +} as const + +type Direction = "ltr" | "rtl" + +const DirectionContext = React.createContext<Direction | undefined>(undefined) + +function useDirection(dirProp?: Direction): Direction { + const contextDir = React.useContext(DirectionContext) + return dirProp ?? contextDir ?? "ltr" +} + +interface EditableContextValue { + id: string + inputId: string + labelId: string + defaultValue: string + value: string + onValueChange: (value: string) => void + editing: boolean + onCancel: () => void + onEdit: () => void + onSubmit: (value: string) => void + onEscapeKeyDown?: (event: KeyboardEvent) => void + dir?: Direction + maxLength?: number + placeholder?: string + triggerMode: "click" | "dblclick" | "focus" + autosize: boolean + disabled?: boolean + readOnly?: boolean + required?: boolean + invalid?: boolean +} + +const EditableContext = React.createContext<EditableContextValue | null>(null) +EditableContext.displayName = ROOT_NAME + +function useEditableContext(name: keyof typeof EDITABLE_ERRORS) { + const context = React.useContext(EditableContext) + if (!context) { + throw new Error(EDITABLE_ERRORS[name]) + } + return context +} + +type RootElement = React.ComponentRef<typeof Editable> + +interface EditableRootProps + extends Omit<React.ComponentPropsWithoutRef<"div">, "onSubmit"> { + id?: string + defaultValue?: string + value?: string + onValueChange?: (value: string) => void + defaultEditing?: boolean + editing?: boolean + onEditingChange?: (editing: boolean) => void + onCancel?: () => void + onEdit?: () => void + onSubmit?: (value: string) => void + onEscapeKeyDown?: (event: KeyboardEvent) => void + dir?: Direction + maxLength?: number + name?: string + placeholder?: string + triggerMode?: EditableContextValue["triggerMode"] + asChild?: boolean + autosize?: boolean + disabled?: boolean + readOnly?: boolean + required?: boolean + invalid?: boolean +} + +const EditableRoot = React.forwardRef<HTMLDivElement, EditableRootProps>( + (props, forwardedRef) => { + const { + defaultValue = "", + value: valueProp, + onValueChange: onValueChangeProp, + defaultEditing = false, + editing: editingProp, + onEditingChange: onEditingChangeProp, + onCancel: onCancelProp, + onEdit: onEditProp, + onSubmit: onSubmitProp, + onEscapeKeyDown, + dir: dirProp, + maxLength, + name, + placeholder, + triggerMode = "click", + asChild, + autosize = false, + disabled, + required, + readOnly, + invalid, + className, + ...rootProps + } = props + + const id = React.useId() + const inputId = React.useId() + const labelId = React.useId() + + const dir = useDirection(dirProp) + + const isControlled = valueProp !== undefined + const [uncontrolledValue, setUncontrolledValue] = + React.useState(defaultValue) + const value = isControlled ? valueProp : uncontrolledValue + const previousValueRef = React.useRef(value) + const onValueChangeRef = React.useRef(onValueChangeProp) + + const isEditingControlled = editingProp !== undefined + const [uncontrolledEditing, setUncontrolledEditing] = + React.useState(defaultEditing) + const editing = isEditingControlled ? editingProp : uncontrolledEditing + const onEditingChangeRef = React.useRef(onEditingChangeProp) + + React.useEffect(() => { + onValueChangeRef.current = onValueChangeProp + onEditingChangeRef.current = onEditingChangeProp + }) + + const onValueChange = React.useCallback( + (nextValue: string) => { + if (!isControlled) { + setUncontrolledValue(nextValue) + } + onValueChangeRef.current?.(nextValue) + }, + [isControlled] + ) + + const onEditingChange = React.useCallback( + (nextEditing: boolean) => { + if (!isEditingControlled) { + setUncontrolledEditing(nextEditing) + } + onEditingChangeRef.current?.(nextEditing) + }, + [isEditingControlled] + ) + + React.useEffect(() => { + if (isControlled && valueProp !== previousValueRef.current) { + previousValueRef.current = valueProp + } + }, [isControlled, valueProp]) + + const [formTrigger, setFormTrigger] = React.useState<RootElement | null>( + null + ) + const composedRef = useComposedRefs(forwardedRef, (node) => + setFormTrigger(node) + ) + const isFormControl = formTrigger ? !!formTrigger.closest("form") : true + + const onCancel = React.useCallback(() => { + const prevValue = previousValueRef.current + onValueChange(prevValue) + onEditingChange(false) + onCancelProp?.() + }, [onValueChange, onCancelProp, onEditingChange]) + + const onEdit = React.useCallback(() => { + previousValueRef.current = value + onEditingChange(true) + onEditProp?.() + }, [value, onEditProp, onEditingChange]) + + const onSubmit = React.useCallback( + (newValue: string) => { + onValueChange(newValue) + onEditingChange(false) + onSubmitProp?.(newValue) + }, + + [onValueChange, onSubmitProp, onEditingChange] + ) + + const contextValue = React.useMemo<EditableContextValue>( + () => ({ + id, + inputId, + labelId, + defaultValue, + value, + onValueChange, + editing, + onSubmit, + onEdit, + onCancel, + onEscapeKeyDown, + dir, + maxLength, + placeholder, + triggerMode, + autosize, + disabled, + readOnly, + required, + invalid, + }), + [ + id, + inputId, + labelId, + defaultValue, + value, + onValueChange, + editing, + onSubmit, + onCancel, + onEdit, + onEscapeKeyDown, + dir, + maxLength, + placeholder, + triggerMode, + autosize, + disabled, + required, + readOnly, + invalid, + ] + ) + + const RootPrimitive = asChild ? Slot : "div" + + return ( + <EditableContext.Provider value={contextValue}> + <RootPrimitive + data-slot="editable" + {...rootProps} + ref={composedRef} + id={id} + className={cn("flex min-w-0 flex-col gap-2", className)} + /> + {isFormControl && ( + <VisuallyHiddenInput + type="hidden" + control={formTrigger} + name={name} + value={value} + disabled={disabled} + readOnly={readOnly} + required={required} + /> + )} + </EditableContext.Provider> + ) + } +) +EditableRoot.displayName = ROOT_NAME + +interface EditableLabelProps extends React.ComponentPropsWithoutRef<"label"> { + asChild?: boolean +} + +const EditableLabel = React.forwardRef<HTMLLabelElement, EditableLabelProps>( + (props, forwardedRef) => { + const { asChild, className, children, ...labelProps } = props + const context = useEditableContext(LABEL_NAME) + + const LabelPrimitive = asChild ? Slot : "label" + + return ( + <LabelPrimitive + data-disabled={context.disabled ? "" : undefined} + data-invalid={context.invalid ? "" : undefined} + data-required={context.required ? "" : undefined} + data-slot="editable-label" + {...labelProps} + ref={forwardedRef} + id={context.labelId} + htmlFor={context.inputId} + className={cn( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 data-[required]:after:ml-0.5 data-[required]:after:text-destructive data-[required]:after:content-['*']", + className + )} + > + {children} + </LabelPrimitive> + ) + } +) +EditableLabel.displayName = LABEL_NAME + +interface EditableAreaProps extends React.ComponentPropsWithoutRef<"div"> { + asChild?: boolean +} + +const EditableArea = React.forwardRef<HTMLDivElement, EditableAreaProps>( + (props, forwardedRef) => { + const { asChild, className, ...areaProps } = props + const context = useEditableContext(AREA_NAME) + + const AreaPrimitive = asChild ? Slot : "div" + + return ( + <AreaPrimitive + role="group" + data-disabled={context.disabled ? "" : undefined} + data-editing={context.editing ? "" : undefined} + data-slot="editable-area" + dir={context.dir} + {...areaProps} + ref={forwardedRef} + className={cn( + "relative inline-block min-w-0 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50", + className + )} + /> + ) + } +) +EditableArea.displayName = AREA_NAME + +interface EditablePreviewProps extends React.ComponentPropsWithoutRef<"div"> { + asChild?: boolean +} + +const EditablePreview = React.forwardRef<HTMLDivElement, EditablePreviewProps>( + (props, forwardedRef) => { + const { asChild, className, ...previewProps } = props + const context = useEditableContext(PREVIEW_NAME) + + const onTrigger = React.useCallback(() => { + if (context.disabled || context.readOnly) return + context.onEdit() + }, [context]) + + const PreviewPrimitive = asChild ? Slot : "div" + + if (context.editing || context.readOnly) return null + + return ( + <PreviewPrimitive + role="button" + aria-disabled={context.disabled || context.readOnly} + data-empty={!context.value ? "" : undefined} + data-disabled={context.disabled ? "" : undefined} + data-readonly={context.readOnly ? "" : undefined} + data-slot="editable-preview" + tabIndex={context.disabled || context.readOnly ? undefined : 0} + {...previewProps} + ref={forwardedRef} + onClick={composeEventHandlers( + previewProps.onClick, + context.triggerMode === "click" ? onTrigger : undefined + )} + onDoubleClick={composeEventHandlers( + previewProps.onDoubleClick, + context.triggerMode === "dblclick" ? onTrigger : undefined + )} + onFocus={composeEventHandlers( + previewProps.onFocus, + context.triggerMode === "focus" ? onTrigger : undefined + )} + className={cn( + "focus-visible:outline-hidden cursor-text truncate rounded-sm border border-transparent py-1 text-base focus-visible:ring-1 focus-visible:ring-ring data-[disabled]:cursor-not-allowed data-[readonly]:cursor-default data-[empty]:text-muted-foreground data-[disabled]:opacity-50 md:text-sm", + className + )} + > + {context.value || context.placeholder} + </PreviewPrimitive> + ) + } +) +EditablePreview.displayName = PREVIEW_NAME + +const useIsomorphicLayoutEffect = + typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect + +type InputElement = React.ComponentRef<typeof EditableInput> + +interface EditableInputProps extends React.ComponentPropsWithoutRef<"input"> { + asChild?: boolean + maxLength?: number +} + +const EditableInput = React.forwardRef<HTMLInputElement, EditableInputProps>( + (props, forwardedRef) => { + const { + asChild, + className, + disabled, + readOnly, + required, + maxLength, + ...inputProps + } = props + const context = useEditableContext(INPUT_NAME) + const inputRef = React.useRef<InputElement>(null) + const composedRef = useComposedRefs(forwardedRef, inputRef) + + const isDisabled = disabled || context.disabled + const isReadOnly = readOnly || context.readOnly + const isRequired = required || context.required + + const onAutosize = React.useCallback( + (target: HTMLInputElement | HTMLTextAreaElement) => { + if (!context.autosize) return + + if (target instanceof HTMLTextAreaElement) { + target.style.height = "0" + target.style.height = `${target.scrollHeight}px` + } else { + target.style.width = "0" + target.style.width = `${target.scrollWidth + 4}px` + } + }, + [context.autosize] + ) + const onBlur = React.useCallback( + (event: React.FocusEvent<InputElement>) => { + if (isReadOnly) return + const relatedTarget = event.relatedTarget + + const isAction = + relatedTarget instanceof HTMLElement && + relatedTarget.closest(`[${DATA_ACTION_ATTR}=""]`) + + if (!isAction) { + context.onSubmit(context.value) + } + }, + [context, isReadOnly] + ) + + const onChange = React.useCallback( + (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { + if (isReadOnly) return + context.onValueChange(event.target.value) + onAutosize(event.target) + }, + [context, isReadOnly, onAutosize] + ) + + const onKeyDown = React.useCallback( + (event: React.KeyboardEvent<InputElement>) => { + if (isReadOnly) return + if (event.key === "Escape") { + const nativeEvent = event.nativeEvent + if (context.onEscapeKeyDown) { + context.onEscapeKeyDown(nativeEvent) + if (nativeEvent.defaultPrevented) return + } + context.onCancel() + } else if (event.key === "Enter") { + context.onSubmit(context.value) + } + }, + [context, isReadOnly] + ) + + useIsomorphicLayoutEffect(() => { + if (!context.editing || isReadOnly || !inputRef.current) return + + const frameId = window.requestAnimationFrame(() => { + if (!inputRef.current) return + + inputRef.current.focus() + inputRef.current.select() + onAutosize(inputRef.current) + }) + + return () => { + window.cancelAnimationFrame(frameId) + } + }, [context.editing, isReadOnly, onAutosize]) + + const InputPrimitive = asChild ? Slot : "input" + + if (!context.editing && !isReadOnly) return null + + return ( + <InputPrimitive + aria-required={isRequired} + aria-invalid={context.invalid} + data-slot="editable-input" + dir={context.dir} + disabled={isDisabled} + readOnly={isReadOnly} + required={isRequired} + {...inputProps} + id={context.inputId} + aria-labelledby={context.labelId} + ref={composedRef} + maxLength={maxLength} + placeholder={context.placeholder} + value={context.value} + onBlur={composeEventHandlers(inputProps.onBlur, onBlur)} + onChange={composeEventHandlers(inputProps.onChange, onChange)} + onKeyDown={composeEventHandlers(inputProps.onKeyDown, onKeyDown)} + className={cn( + "shadow-xs focus-visible:outline-hidden flex rounded-sm border border-input bg-transparent py-1 text-base transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", + context.autosize ? "w-auto" : "w-full", + className + )} + /> + ) + } +) +EditableInput.displayName = INPUT_NAME + +interface EditableTriggerProps + extends React.ComponentPropsWithoutRef<"button"> { + asChild?: boolean + forceMount?: boolean +} + +const EditableTrigger = React.forwardRef< + HTMLButtonElement, + EditableTriggerProps +>((props, forwardedRef) => { + const { asChild, forceMount = false, ...triggerProps } = props + const context = useEditableContext(TRIGGER_NAME) + + const onTrigger = React.useCallback(() => { + if (context.disabled || context.readOnly) return + context.onEdit() + }, [context]) + + const TriggerPrimitive = asChild ? Slot : "button" + + if (!forceMount && (context.editing || context.readOnly)) return null + + return ( + <TriggerPrimitive + type="button" + aria-controls={context.id} + aria-disabled={context.disabled || context.readOnly} + data-disabled={context.disabled ? "" : undefined} + data-readonly={context.readOnly ? "" : undefined} + data-slot="editable-trigger" + {...triggerProps} + ref={forwardedRef} + onClick={context.triggerMode === "click" ? onTrigger : undefined} + onDoubleClick={context.triggerMode === "dblclick" ? onTrigger : undefined} + /> + ) +}) +EditableTrigger.displayName = TRIGGER_NAME + +interface EditableToolbarProps extends React.ComponentPropsWithoutRef<"div"> { + asChild?: boolean + orientation?: "horizontal" | "vertical" +} + +const EditableToolbar = React.forwardRef<HTMLDivElement, EditableToolbarProps>( + (props, forwardedRef) => { + const { + asChild, + className, + orientation = "horizontal", + ...toolbarProps + } = props + const context = useEditableContext(TOOLBAR_NAME) + + const ToolbarPrimitive = asChild ? Slot : "div" + + return ( + <ToolbarPrimitive + role="toolbar" + aria-controls={context.id} + aria-orientation={orientation} + data-slot="editable-toolbar" + dir={context.dir} + {...toolbarProps} + ref={forwardedRef} + className={cn( + "flex items-center gap-2", + orientation === "vertical" && "flex-col", + className + )} + /> + ) + } +) +EditableToolbar.displayName = TOOLBAR_NAME + +interface EditableCancelProps extends React.ComponentPropsWithoutRef<"button"> { + asChild?: boolean +} + +const EditableCancel = React.forwardRef<HTMLButtonElement, EditableCancelProps>( + (props, forwardedRef) => { + const { asChild, ...cancelProps } = props + const context = useEditableContext(CANCEL_NAME) + + const CancelPrimitive = asChild ? Slot : "button" + + if (!context.editing && !context.readOnly) return null + + return ( + <CancelPrimitive + type="button" + aria-controls={context.id} + data-slot="editable-cancel" + {...{ [DATA_ACTION_ATTR]: "" }} + {...cancelProps} + onClick={composeEventHandlers(cancelProps.onClick, () => { + context.onCancel() + })} + ref={forwardedRef} + /> + ) + } +) +EditableCancel.displayName = CANCEL_NAME + +interface EditableSubmitProps extends React.ComponentPropsWithoutRef<"button"> { + asChild?: boolean +} + +const EditableSubmit = React.forwardRef<HTMLButtonElement, EditableSubmitProps>( + (props, forwardedRef) => { + const { asChild, ...submitProps } = props + const context = useEditableContext(SUBMIT_NAME) + + const SubmitPrimitive = asChild ? Slot : "button" + + if (!context.editing && !context.readOnly) return null + + return ( + <SubmitPrimitive + type="button" + aria-controls={context.id} + data-slot="editable-submit" + {...{ [DATA_ACTION_ATTR]: "" }} + {...submitProps} + ref={forwardedRef} + onClick={composeEventHandlers(submitProps.onClick, () => { + context.onSubmit(context.value) + })} + /> + ) + } +) +EditableSubmit.displayName = SUBMIT_NAME + +type InputValue = string[] | string + +interface VisuallyHiddenInputProps<T = InputValue> + extends Omit< + React.InputHTMLAttributes<HTMLInputElement>, + "value" | "checked" | "onReset" + > { + value?: T + checked?: boolean + control: HTMLElement | null + bubbles?: boolean +} + +function VisuallyHiddenInput<T = InputValue>( + props: VisuallyHiddenInputProps<T> +) { + const { + control, + value, + checked, + bubbles = true, + type = "hidden", + style, + ...inputProps + } = props + + const isCheckInput = React.useMemo( + () => type === "checkbox" || type === "radio" || type === "switch", + [type] + ) + const inputRef = React.useRef<HTMLInputElement>(null) + + const prevValueRef = React.useRef<{ + value: T | boolean | undefined + previous: T | boolean | undefined + }>({ + value: isCheckInput ? checked : value, + previous: isCheckInput ? checked : value, + }) + + const prevValue = React.useMemo(() => { + const currentValue = isCheckInput ? checked : value + if (prevValueRef.current.value !== currentValue) { + prevValueRef.current.previous = prevValueRef.current.value + prevValueRef.current.value = currentValue + } + return prevValueRef.current.previous + }, [isCheckInput, value, checked]) + + const [controlSize, setControlSize] = React.useState<{ + width?: number + height?: number + }>({}) + + React.useLayoutEffect(() => { + if (!control) { + setControlSize({}) + return + } + + setControlSize({ + width: control.offsetWidth, + height: control.offsetHeight, + }) + + if (typeof window === "undefined") return + + const resizeObserver = new ResizeObserver((entries) => { + if (!Array.isArray(entries) || !entries.length) return + + const entry = entries[0] + if (!entry) return + + let width: number + let height: number + + if ("borderBoxSize" in entry) { + const borderSizeEntry = entry.borderBoxSize + const borderSize = Array.isArray(borderSizeEntry) + ? borderSizeEntry[0] + : borderSizeEntry + width = borderSize.inlineSize + height = borderSize.blockSize + } else { + width = control.offsetWidth + height = control.offsetHeight + } + + setControlSize({ width, height }) + }) + + resizeObserver.observe(control, { box: "border-box" }) + return () => { + resizeObserver.disconnect() + } + }, [control]) + + React.useEffect(() => { + const input = inputRef.current + if (!input) return + + const inputProto = window.HTMLInputElement.prototype + const propertyKey = isCheckInput ? "checked" : "value" + const eventType = isCheckInput ? "click" : "input" + const currentValue = isCheckInput ? checked : value + + const serializedCurrentValue = isCheckInput + ? checked + : typeof value === "object" && value !== null + ? JSON.stringify(value) + : value + + const descriptor = Object.getOwnPropertyDescriptor(inputProto, propertyKey) + + const setter = descriptor?.set + + if (prevValue !== currentValue && setter) { + const event = new Event(eventType, { bubbles }) + setter.call(input, serializedCurrentValue) + input.dispatchEvent(event) + } + }, [prevValue, value, checked, bubbles, isCheckInput]) + + const composedStyle = React.useMemo<React.CSSProperties>(() => { + return { + ...style, + ...(controlSize.width !== undefined && controlSize.height !== undefined + ? controlSize + : {}), + border: 0, + clip: "rect(0 0 0 0)", + clipPath: "inset(50%)", + height: "1px", + margin: "-1px", + overflow: "hidden", + padding: 0, + position: "absolute", + whiteSpace: "nowrap", + width: "1px", + } + }, [style, controlSize]) + + return ( + <input + type={type} + {...inputProps} + ref={inputRef} + aria-hidden={isCheckInput} + tabIndex={-1} + defaultChecked={isCheckInput ? checked : undefined} + style={composedStyle} + /> + ) +} + +const Editable = EditableRoot +const Root = EditableRoot +const Label = EditableLabel +const Area = EditableArea +const Preview = EditablePreview +const Input = EditableInput +const Trigger = EditableTrigger +const Toolbar = EditableToolbar +const Cancel = EditableCancel +const Submit = EditableSubmit + +export { + Editable, + EditableLabel, + EditableArea, + EditablePreview, + EditableInput, + EditableToolbar, + EditableCancel, + EditableSubmit, + EditableTrigger, + // + Root, + Label, + Area, + Preview, + Input, + Toolbar, + Cancel, + Submit, + Trigger, +} diff --git a/apps/www/registry/registry-examples.ts b/apps/www/registry/registry-examples.ts index 4f07b12ed65..03138817ffb 100644 --- a/apps/www/registry/registry-examples.ts +++ b/apps/www/registry/registry-examples.ts @@ -687,6 +687,28 @@ export const examples: Registry["items"] = [ }, ], }, + { + name: "editable-demo", + type: "registry:example", + registryDependencies: ["editable"], + files: [ + { + path: "examples/editable-demo.tsx", + type: "registry:example", + }, + ], + }, + { + name: "editable-form-demo", + type: "registry:example", + registryDependencies: ["editable", "form"], + files: [ + { + path: "examples/editable-form-demo.tsx", + type: "registry:example", + }, + ], + }, { name: "hover-card-demo", type: "registry:example", diff --git a/apps/www/registry/registry-lib.ts b/apps/www/registry/registry-lib.ts index e6012e45b54..20a0e47096b 100644 --- a/apps/www/registry/registry-lib.ts +++ b/apps/www/registry/registry-lib.ts @@ -12,4 +12,14 @@ export const lib: Registry["items"] = [ }, ], }, + { + name: "composition", + type: "registry:lib", + files: [ + { + path: "lib/composition.ts", + type: "registry:lib", + }, + ], + }, ] diff --git a/apps/www/registry/registry-ui.ts b/apps/www/registry/registry-ui.ts index cc9675d9855..e7a9d2b8c64 100644 --- a/apps/www/registry/registry-ui.ts +++ b/apps/www/registry/registry-ui.ts @@ -235,6 +235,21 @@ export const ui: Registry["items"] = [ }, ], }, + { + name: "editable", + type: "registry:ui", + dependencies: ["@radix-ui/react-slot"], + files: [ + { + path: "ui/editable.tsx", + type: "registry:ui", + }, + { + path: "lib/composition.ts", + type: "registry:lib", + }, + ], + }, { name: "form", type: "registry:ui",