diff --git a/.cursorignore b/.cursorignore
index 6735e08ba6432..306b6c986d2ca 100644
--- a/.cursorignore
+++ b/.cursorignore
@@ -11,7 +11,6 @@ documentation/static/
documentation/test/
documentation/versioned_docs/
documentation/versioned_sidebars/
-examples/
hackathon/
patches/
pnpm-lock.yaml
diff --git a/.gitignore b/.gitignore
index 2c9a4eb24ad6f..5f8a7f31f84a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,5 +37,7 @@ examples/**/package-lock.json
# nx daemon
.nx
+# cursor
+.cursorrules
# claude
.claude/
diff --git a/.syncpackrc b/.syncpackrc
index 16a085924bf83..cdebbcae94251 100644
--- a/.syncpackrc
+++ b/.syncpackrc
@@ -4,32 +4,82 @@
"semverGroups": [],
"versionGroups": [
{
- "dependencies": ["@types/**", "@testing-library/**", "typescript"],
- "dependencyTypes": ["prod"],
+ "dependencies": [
+ "@types/**",
+ "@testing-library/**",
+ "typescript"
+ ],
+ "dependencyTypes": [
+ "prod"
+ ],
"isBanned": true,
"label": "These packages should only be under devDependencies."
},
{
- "dependencies": ["@refinedev/**"],
- "packages": ["**"],
+ "dependencies": [
+ "@refinedev/**"
+ ],
+ "packages": [
+ "**"
+ ],
"isIgnored": true,
"label": "@refinedev dependencies are managed by changeset."
},
{
- "dependencies": ["**"],
- "dependencyTypes": ["peer"],
- "packages": ["**"],
+ "dependencies": [
+ "**"
+ ],
+ "dependencyTypes": [
+ "peer"
+ ],
+ "packages": [
+ "**"
+ ],
"isIgnored": true
},
{
- "dependencies": ["nock"],
- "dependencyTypes": ["dev"],
- "packages": ["@refinedev/appwrite", "@refinedev/graphql"],
+ "dependencies": [
+ "nock"
+ ],
+ "dependencyTypes": [
+ "dev"
+ ],
+ "packages": [
+ "@refinedev/appwrite",
+ "@refinedev/graphql"
+ ],
"isIgnored": true
},
{
- "dependencies": ["next", "react", "react-dom", "@types/react", "@types/react-dom"],
- "packages": ["with-nextjs-headless"],
+ "dependencies": [
+ "next",
+ "react",
+ "react-dom",
+ "@types/react",
+ "@types/react-dom"
+ ],
+ "packages": [
+ "with-nextjs-headless"
+ ],
+ "isIgnored": true
+ },
+ {
+ "dependencies": [
+ "next",
+ "react",
+ "react-dom",
+ "@types/react",
+ "@types/react-dom",
+ "eslint",
+ "postcss",
+ "tailwindcss",
+ "recharts",
+ "clsx",
+ "@netlify/plugin-nextjs"
+ ],
+ "packages": [
+ "@refinedev/refine-ui"
+ ],
"isIgnored": true
}
]
diff --git a/packages/refine-ui/README.md b/packages/refine-ui/README.md
new file mode 100644
index 0000000000000..4b09c06e1f0ff
--- /dev/null
+++ b/packages/refine-ui/README.md
@@ -0,0 +1,23 @@
+# registry-template
+
+You can use the `shadcn` CLI to run your own component registry. Running your own
+component registry allows you to distribute your custom components, hooks, pages, and
+other files to any React project.
+
+> [!IMPORTANT]
+> This template uses Tailwind v4. For Tailwind v3, see [registry-template](https://github.com/shadcn-ui/registry-template).
+
+## Getting Started
+
+This is a template for creating a custom registry using Next.js.
+
+- The template uses a `registry.json` file to define components and their files.
+- The `shadcn build` command is used to build the registry.
+- The registry items are served as static files under `public/r/[name].json`.
+- The template also includes a route handler for serving registry items.
+- Every registry item are compatible with the `shadcn` CLI.
+- We have also added v0 integration using the `Open in v0` api.
+
+## Documentation
+
+Visit the [shadcn documentation](https://ui.shadcn.com/docs/registry) to view the full documentation.
diff --git a/packages/refine-ui/app/base-example/[[...slug]]/page.tsx b/packages/refine-ui/app/base-example/[[...slug]]/page.tsx
new file mode 100644
index 0000000000000..180fe86a619be
--- /dev/null
+++ b/packages/refine-ui/app/base-example/[[...slug]]/page.tsx
@@ -0,0 +1,15 @@
+"use client";
+
+import dynamic from "next/dynamic";
+
+const BaseExample = dynamic(
+ () =>
+ import("../../../examples/base-example/app").then((mod) => mod.BaseExample),
+ {
+ ssr: false,
+ },
+);
+
+export default function BaseExamplePage() {
+ return ;
+}
diff --git a/packages/refine-ui/app/favicon.ico b/packages/refine-ui/app/favicon.ico
new file mode 100644
index 0000000000000..718d6fea4835e
Binary files /dev/null and b/packages/refine-ui/app/favicon.ico differ
diff --git a/packages/refine-ui/app/global.css b/packages/refine-ui/app/global.css
new file mode 100644
index 0000000000000..0140c737af7eb
--- /dev/null
+++ b/packages/refine-ui/app/global.css
@@ -0,0 +1,123 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-sidebar-ring: var(--sidebar-ring);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar: var(--sidebar);
+ --color-chart-5: var(--chart-5);
+ --color-chart-4: var(--chart-4);
+ --color-chart-3: var(--chart-3);
+ --color-chart-2: var(--chart-2);
+ --color-chart-1: var(--chart-1);
+ --color-ring: var(--ring);
+ --color-input: var(--input);
+ --color-border: var(--border);
+ --color-destructive: var(--destructive);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-accent: var(--accent);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-muted: var(--muted);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-secondary: var(--secondary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-primary: var(--primary);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-popover: var(--popover);
+ --color-card-foreground: var(--card-foreground);
+ --color-card: var(--card);
+ --radius-sm: calc(var(--radius) - 2px);
+ --radius-md: calc(var(--radius));
+ --radius-lg: calc(var(--radius) + 2px);
+ --radius-xl: calc(var(--radius) + 6px);
+
+ --font-sans: "Inter", sans-serif;
+}
+
+:root {
+ --radius: 0.375rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(54.6% 0.245 262.881);
+ --primary-foreground: oklch(1 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(54.6% 0.245 262.881);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(54.6% 0.245 262.881);
+ --primary-foreground: oklch(1 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(54.6% 0.245 262.881);
+ --sidebar-primary-foreground: oklch(1 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50 antialiased;
+ font-family: var(--font-sans);
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/packages/refine-ui/app/layout.tsx b/packages/refine-ui/app/layout.tsx
new file mode 100644
index 0000000000000..b609f9bf507be
--- /dev/null
+++ b/packages/refine-ui/app/layout.tsx
@@ -0,0 +1,25 @@
+import type { Metadata } from "next";
+import { Inter } from "next/font/google";
+import "./global.css";
+
+const inter = Inter({
+ variable: "--font-inter",
+ subsets: ["latin"],
+});
+
+export const metadata: Metadata = {
+ title: "Create Next App",
+ description: "Generated by create next app",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
{children}
+
+ );
+}
diff --git a/packages/refine-ui/app/page.tsx b/packages/refine-ui/app/page.tsx
new file mode 100644
index 0000000000000..c7fe3bbc69015
--- /dev/null
+++ b/packages/refine-ui/app/page.tsx
@@ -0,0 +1,5 @@
+import * as React from "react";
+
+export default function Home() {
+ return
;
+}
diff --git a/packages/refine-ui/components.json b/packages/refine-ui/components.json
new file mode 100644
index 0000000000000..5592ee706f901
--- /dev/null
+++ b/packages/refine-ui/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/registry/new-york/ui",
+ "utils": "@/lib/utils",
+ "ui": "@/registry/new-york/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/packages/refine-ui/examples/base-example/app.tsx b/packages/refine-ui/examples/base-example/app.tsx
new file mode 100644
index 0000000000000..efdf82c8103c7
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/app.tsx
@@ -0,0 +1,202 @@
+"use client";
+
+import { Refine, Authenticated } from "@refinedev/core";
+import routerProvider, {
+ UnsavedChangesNotifier,
+ DocumentTitleHandler,
+ NavigateToResource,
+ CatchAllNavigate,
+} from "@refinedev/react-router";
+import { BrowserRouter, Outlet, Route, Routes } from "react-router";
+import { Toaster } from "@/registry/new-york/ui/sonner";
+import { Breadcrumb } from "@/registry/new-york/refine-ui/layout/breadcrumb";
+import { ErrorComponent } from "@/registry/new-york/refine-ui/layout/error-component";
+import {
+ BellIcon,
+ CreditCardIcon,
+ DollarSignIcon,
+ FileIcon,
+ FolderIcon,
+ HomeIcon,
+ SettingsIcon,
+ UserIcon,
+} from "lucide-react";
+import { authProvider } from "./providers/auth";
+import { useNotificationProvider } from "./providers/notification";
+import { createDataProvider } from "./providers/data";
+import { LoginForm } from "./components/login-form";
+import { RegisterForm } from "./components/register-form";
+import { AppLayout } from "./components/layout";
+import { PostsListPage } from "./routes/posts/list";
+import { UsersListPage } from "./routes/users/list";
+import { API_URL } from "./constants";
+import { HomePage } from "./routes/home";
+import CreatePost from "./routes/posts/create";
+import ShowPost from "./routes/posts/show";
+import EditPost from "./routes/posts/edit";
+import { ForgotPasswordForm } from "./components/forgot-password-form";
+
+export function BaseExample() {
+ return (
+
+ ,
+ },
+ },
+ {
+ name: "posts",
+ list: "/posts",
+ create: "/posts/create",
+ edit: "/posts/edit/:id",
+ show: "/posts/show/:id",
+ clone: "/posts/clone/:id",
+ meta: {
+ icon: ,
+ },
+ },
+ {
+ name: "users",
+ list: "/users",
+ meta: { icon: },
+ },
+ {
+ name: "settings",
+ meta: { icon: },
+ },
+ {
+ name: "Directory",
+ list: "/directory",
+ meta: { icon: },
+ },
+ {
+ name: "profile",
+ list: "/profile",
+ meta: {
+ parent: "settings",
+ },
+ },
+ {
+ name: "notifications",
+ list: "/settings/notifications",
+ edit: "/settings/notifications/:id/edit",
+ meta: {
+ icon: ,
+ parent: "settings",
+ },
+ },
+ {
+ name: "finance",
+ meta: {
+ group: true,
+ },
+ },
+ {
+ name: "expenses",
+ list: "/expenses",
+ meta: {
+ icon: ,
+ parent: "finance",
+ },
+ },
+ {
+ name: "income",
+ list: "/income",
+ meta: {
+ icon: ,
+ parent: "finance",
+ },
+ },
+ ]}
+ options={{
+ syncWithLocation: true,
+ warnWhenUnsavedChanges: true,
+ }}
+ >
+
+
+ }
+ >
+
+
+
+ }
+ >
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ Settings List Page} />
+
+
+
+ Notifications List Page
+
+ }
+ />
+
+
+
+ Notifications Edit Page
+
+ }
+ />
+ Profile List Page} />
+ Finance List Page} />
+ Expenses List Page} />
+ Income List Page} />
+ Directory List Page} />
+ } />
+
+
+ }>
+
+
+ }
+ >
+ } />
+ } />
+ } />
+
+
+
+
+
+ }
+ >
+ } />
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/refine-ui/examples/base-example/components/forgot-password-form.tsx b/packages/refine-ui/examples/base-example/components/forgot-password-form.tsx
new file mode 100644
index 0000000000000..f26f9c4943676
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/components/forgot-password-form.tsx
@@ -0,0 +1,5 @@
+import { ForgotPasswordForm as BaseForgotPasswordForm } from "@/registry/new-york/refine-ui/form/forgot-password-form";
+
+export const ForgotPasswordForm = () => {
+ return ;
+};
diff --git a/packages/refine-ui/examples/base-example/components/layout.tsx b/packages/refine-ui/examples/base-example/components/layout.tsx
new file mode 100644
index 0000000000000..60b6379e7e26a
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/components/layout.tsx
@@ -0,0 +1,5 @@
+import { Layout } from "@/registry/new-york/refine-ui/layout/layout-01/layout";
+
+export const AppLayout = ({ children }: { children: React.ReactNode }) => {
+ return {children} ;
+};
diff --git a/packages/refine-ui/examples/base-example/components/login-form.tsx b/packages/refine-ui/examples/base-example/components/login-form.tsx
new file mode 100644
index 0000000000000..37052591cdc8c
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/components/login-form.tsx
@@ -0,0 +1,5 @@
+import { SignInForm } from "@/registry/new-york/refine-ui/form/sign-in-form";
+
+export const LoginForm = () => {
+ return ;
+};
diff --git a/packages/refine-ui/examples/base-example/components/register-form.tsx b/packages/refine-ui/examples/base-example/components/register-form.tsx
new file mode 100644
index 0000000000000..32ea2560d773c
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/components/register-form.tsx
@@ -0,0 +1,5 @@
+import { SignUpForm } from "@/registry/new-york/refine-ui/form/sign-up-form";
+
+export const RegisterForm = () => {
+ return ;
+};
diff --git a/packages/refine-ui/examples/base-example/constants.ts b/packages/refine-ui/examples/base-example/constants.ts
new file mode 100644
index 0000000000000..4862046b1a279
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/constants.ts
@@ -0,0 +1 @@
+export const API_URL = "https://api.fake-rest.refine.dev";
diff --git a/packages/refine-ui/examples/base-example/providers/auth.ts b/packages/refine-ui/examples/base-example/providers/auth.ts
new file mode 100644
index 0000000000000..2b53d15417b53
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/providers/auth.ts
@@ -0,0 +1,131 @@
+import type { AuthProvider } from "@refinedev/core";
+
+/**
+ * mock auth credentials to simulate authentication
+ */
+const authCredentials = {
+ email: "demo@refine.dev",
+ password: "demodemo",
+};
+
+const TOKEN_KEY = "refine-auth";
+
+export const authProvider: AuthProvider = {
+ login: async ({ providerName, email }) => {
+ if (providerName === "google") {
+ return {
+ success: true,
+ };
+ }
+
+ if (providerName === "github") {
+ return {
+ success: true,
+ };
+ }
+
+ if (email === authCredentials.email) {
+ localStorage.setItem(TOKEN_KEY, email);
+ return {
+ success: true,
+ redirectTo: "/",
+ };
+ }
+
+ return {
+ success: false,
+ error: {
+ message: "Login failed",
+ name: "Invalid email or password",
+ },
+ };
+ },
+ register: async (params) => {
+ if (params.email === authCredentials.email && params.password) {
+ localStorage.setItem(TOKEN_KEY, params.email);
+ return {
+ success: true,
+ redirectTo: "/",
+ };
+ }
+ return {
+ success: false,
+ error: {
+ message: "Register failed",
+ name: "Invalid email or password",
+ },
+ };
+ },
+ updatePassword: async (params) => {
+ if (params.password === authCredentials.password) {
+ //we can update password here
+ return {
+ success: true,
+ };
+ }
+ return {
+ success: false,
+ error: {
+ message: "Update password failed",
+ name: "Invalid password",
+ },
+ };
+ },
+ forgotPassword: async (params) => {
+ if (params.email === authCredentials.email) {
+ //we can send email with reset password link here
+ return {
+ success: true,
+ };
+ }
+ return {
+ success: false,
+ error: {
+ message: "Forgot password failed",
+ name: "Invalid email",
+ },
+ };
+ },
+ logout: async () => {
+ localStorage.removeItem(TOKEN_KEY);
+ return {
+ success: true,
+ redirectTo: "/login",
+ };
+ },
+ onError: async (error) => {
+ if (error.response?.status === 401) {
+ return {
+ logout: true,
+ };
+ }
+
+ return { error };
+ },
+ check: async () => {
+ const token = localStorage.getItem(TOKEN_KEY);
+
+ return token
+ ? {
+ authenticated: true,
+ }
+ : {
+ authenticated: false,
+ error: {
+ message: "Check failed",
+ name: "Not authenticated",
+ },
+ logout: true,
+ redirectTo: "/login",
+ };
+ },
+ getPermissions: async (params) => params?.permissions,
+ getIdentity: async () => ({
+ id: 1,
+ firstName: "Jane",
+ lastName: "Doe",
+ fullName: "Jane Doe",
+ avatar: "https://unsplash.com/photos/IWLOvomUmWU/download?force=true&w=640",
+ email: "jane.doe@example.com",
+ }),
+};
diff --git a/packages/refine-ui/examples/base-example/providers/data.ts b/packages/refine-ui/examples/base-example/providers/data.ts
new file mode 100644
index 0000000000000..f58155938a7b9
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/providers/data.ts
@@ -0,0 +1,4 @@
+import simpleRestDataProvider from "@refinedev/simple-rest";
+
+export const createDataProvider = (apiUrl: string) =>
+ simpleRestDataProvider(apiUrl);
diff --git a/packages/refine-ui/examples/base-example/providers/notification.ts b/packages/refine-ui/examples/base-example/providers/notification.ts
new file mode 100644
index 0000000000000..ff4ecdb2db8c5
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/providers/notification.ts
@@ -0,0 +1,145 @@
+import type { NotificationProvider } from "@refinedev/core";
+import { toast } from "sonner";
+import React from "react";
+
+export const useNotificationProvider = (): NotificationProvider => {
+ return {
+ open: ({
+ key,
+ type,
+ message,
+ description,
+ undoableTimeout,
+ cancelMutation,
+ }) => {
+ if (type === "success") {
+ toast.success(message, {
+ id: key,
+ description,
+ richColors: true,
+ });
+ }
+
+ if (type === "error") {
+ toast.error(message, {
+ id: key,
+ description,
+ richColors: true,
+ });
+ }
+
+ if (type === "progress") {
+ const toastId = key || Date.now();
+
+ toast.custom(
+ () =>
+ React.createElement(UndoableNotification, {
+ message,
+ description,
+ undoableTimeout,
+ cancelMutation,
+ onClose: () => toast.dismiss(toastId),
+ }),
+ {
+ id: toastId,
+ duration: (undoableTimeout || 5) * 1000,
+ },
+ );
+ }
+ },
+ close: (id) => {
+ toast.dismiss(id);
+ },
+ };
+};
+
+interface UndoableNotificationProps {
+ message: string;
+ description?: string;
+ undoableTimeout?: number;
+ cancelMutation?: () => void;
+ onClose?: () => void;
+}
+
+const UndoableNotification: React.FC = ({
+ message,
+ description,
+ undoableTimeout = 5,
+ cancelMutation,
+ onClose,
+}) => {
+ const [countdown, setCountdown] = React.useState(undoableTimeout);
+
+ React.useEffect(() => {
+ const timer = setInterval(() => {
+ setCountdown((prev) => {
+ if (prev <= 1) {
+ onClose?.();
+ return 0;
+ }
+ return prev - 1;
+ });
+ }, 1000);
+
+ return () => clearInterval(timer);
+ }, [onClose]);
+
+ const handleUndo = () => {
+ cancelMutation?.();
+ onClose?.();
+ };
+
+ const handleClose = () => {
+ onClose?.();
+ };
+
+ return React.createElement(
+ "div",
+ {
+ className:
+ "flex items-center justify-between w-full max-w-md p-4 bg-white border border-gray-200 rounded-lg shadow-lg dark:bg-gray-800 dark:border-gray-700",
+ },
+ React.createElement(
+ "div",
+ { className: "flex-1 mr-3" },
+ React.createElement(
+ "div",
+ { className: "font-medium text-gray-900 dark:text-white" },
+ message,
+ ),
+ description &&
+ React.createElement(
+ "div",
+ { className: "text-sm text-gray-500 dark:text-gray-400 mt-1" },
+ description,
+ ),
+ React.createElement(
+ "div",
+ { className: "text-xs text-gray-400 dark:text-gray-500 mt-2" },
+ `Auto-closes in ${countdown}s`,
+ ),
+ ),
+ React.createElement(
+ "div",
+ { className: "flex items-center gap-2" },
+ React.createElement(
+ "button",
+ {
+ onClick: handleUndo,
+ className:
+ "inline-flex items-center px-3 py-1 text-xs font-medium text-blue-600 bg-blue-50 border border-blue-200 rounded hover:bg-blue-100 dark:bg-blue-900/20 dark:border-blue-800 dark:text-blue-400 dark:hover:bg-blue-900/30",
+ },
+ "Undo",
+ ),
+ React.createElement(
+ "button",
+ {
+ onClick: handleClose,
+ className:
+ "inline-flex items-center justify-center w-8 h-8 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded dark:hover:bg-gray-700 dark:hover:text-gray-300",
+ },
+ "×",
+ ),
+ ),
+ );
+};
diff --git a/packages/refine-ui/examples/base-example/routes/home.tsx b/packages/refine-ui/examples/base-example/routes/home.tsx
new file mode 100644
index 0000000000000..60247bf6ef941
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/routes/home.tsx
@@ -0,0 +1,39 @@
+import { CloneButton } from "@/registry/new-york/refine-ui/buttons/clone";
+import { CreateButton } from "@/registry/new-york/refine-ui/buttons/create";
+import { DeleteButton } from "@/registry/new-york/refine-ui/buttons/delete";
+import { EditButton } from "@/registry/new-york/refine-ui/buttons/edit";
+import { ListButton } from "@/registry/new-york/refine-ui/buttons/list";
+import { RefreshButton } from "@/registry/new-york/refine-ui/buttons/refresh";
+import { ShowButton } from "@/registry/new-york/refine-ui/buttons/show";
+import { Separator } from "@/registry/new-york/ui/separator";
+
+export const HomePage = () => {
+ return (
+
+
+
Navigation Buttons
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Action Buttons
+
+
+
+
+
+
+ );
+};
diff --git a/packages/refine-ui/examples/base-example/routes/posts/create.tsx b/packages/refine-ui/examples/base-example/routes/posts/create.tsx
new file mode 100644
index 0000000000000..59e48d912e688
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/routes/posts/create.tsx
@@ -0,0 +1,162 @@
+import React from "react";
+import type { HttpError } from "@refinedev/core";
+import { useForm } from "@refinedev/react-hook-form";
+import { useNavigate } from "react-router";
+import { zodResolver } from "@hookform/resolvers/zod";
+import * as z from "zod";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/registry/new-york/ui/form";
+import { Input } from "@/registry/new-york/ui/input";
+import { Textarea } from "@/registry/new-york/ui/textarea";
+import { Button } from "@/registry/new-york/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/registry/new-york/ui/select";
+import {
+ CreateView,
+ CreateViewHeader,
+} from "@/registry/new-york/refine-ui/views/create-view";
+import type { Post } from "../../types/resources";
+
+const postFormSchema = z.object({
+ title: z.string().min(2, {
+ message: "Title must be at least 2 characters.",
+ }),
+ content: z.string().min(10, {
+ message: "Content must be at least 10 characters.",
+ }),
+ status: z.string({
+ required_error: "Please select a status.",
+ }),
+});
+
+type PostFormValues = z.infer;
+
+export default function CreatePost() {
+ const navigate = useNavigate();
+
+ const {
+ refineCore: { onFinish, formLoading },
+ ...form
+ } = useForm({
+ resolver: zodResolver(postFormSchema),
+ defaultValues: {
+ title: "",
+ content: "",
+ status: "draft",
+ },
+ refineCoreProps: {
+ resource: "posts",
+ action: "create",
+ },
+ });
+
+ function onSubmit(values: PostFormValues) {
+ onFinish(values);
+ }
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/packages/refine-ui/examples/base-example/routes/posts/edit.tsx b/packages/refine-ui/examples/base-example/routes/posts/edit.tsx
new file mode 100644
index 0000000000000..2621ab8a5e34b
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/routes/posts/edit.tsx
@@ -0,0 +1,247 @@
+import React from "react";
+import type { HttpError } from "@refinedev/core";
+import { useForm } from "@refinedev/react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import * as z from "zod";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/registry/new-york/ui/form";
+import { Input } from "@/registry/new-york/ui/input";
+import { Textarea } from "@/registry/new-york/ui/textarea";
+import { Button } from "@/registry/new-york/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/registry/new-york/ui/select";
+import {
+ EditView,
+ EditViewHeader,
+} from "@/registry/new-york/refine-ui/views/edit-view";
+import { useNavigate } from "react-router";
+import { Loader2 } from "lucide-react";
+import { LoadingOverlay } from "@/registry/new-york/refine-ui/layout/loading-overlay";
+import { AutoSaveIndicator } from "@/registry/new-york/refine-ui/form/auto-save-indicator";
+import { useSelect } from "@refinedev/core";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/registry/new-york/ui/popover";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/registry/new-york/ui/command";
+import { ChevronsUpDown, Check } from "lucide-react";
+import { cn } from "@/lib/utils";
+import type { Post } from "../../types/resources";
+
+const postFormSchema = z.object({
+ title: z.string().min(2, {
+ message: "Title must be at least 2 characters.",
+ }),
+ content: z.string().min(10, {
+ message: "Content must be at least 10 characters.",
+ }),
+ status: z.string({
+ required_error: "Please select a status.",
+ }),
+ category: z.object({
+ id: z.number(),
+ }),
+});
+
+type PostFormValues = z.infer;
+
+export default function EditPost() {
+ const navigate = useNavigate();
+
+ const {
+ refineCore: { onFinish, autoSaveProps, query },
+ ...form
+ } = useForm({
+ resolver: zodResolver(postFormSchema),
+ refineCoreProps: {
+ autoSave: {
+ enabled: true,
+ debounce: 1000,
+ },
+ },
+ defaultValues: {
+ title: "",
+ content: "",
+ status: "draft",
+ category: undefined,
+ },
+ });
+
+ const { options: categoryOptions, query: categoriesQuery } = useSelect({
+ resource: "categories",
+ optionValue: "id",
+ optionLabel: "title",
+ pagination: {
+ pageSize: 999,
+ },
+ });
+
+ function onSubmit(values: PostFormValues) {
+ onFinish(values);
+ }
+
+ const isLoading = !query?.isFetched || query.isRefetching;
+
+ return (
+
+ }
+ />
+
+
+
+ (
+
+ Title
+
+
+
+
+
+ )}
+ />
+ (
+
+ Content
+
+
+
+
+
+ )}
+ />
+ (
+
+ Category
+
+
+
+ {field.value
+ ? categoryOptions?.find(
+ (option) => option.value === field.value,
+ )?.label
+ : "Select category..."}
+
+
+
+
+
+
+
+ No category found.
+
+ {categoryOptions?.map((option) => {
+ return (
+ {
+ field.onChange(option.value);
+ }}
+ >
+
+ {option.label}
+
+ );
+ })}
+
+
+
+
+
+
+
+ )}
+ />
+ {
+ return (
+
+ Status
+
+
+
+
+
+
+
+ Published
+ Draft
+ Rejected
+
+
+
+
+ );
+ }}
+ />
+
+ navigate("/posts")}
+ >
+ Cancel
+
+
+ {isLoading && }
+ Save
+
+
+
+
+
+
+ );
+}
diff --git a/packages/refine-ui/examples/base-example/routes/posts/list.tsx b/packages/refine-ui/examples/base-example/routes/posts/list.tsx
new file mode 100644
index 0000000000000..9a1269cb0e938
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/routes/posts/list.tsx
@@ -0,0 +1,352 @@
+import { useEffect, useMemo } from "react";
+import { useTable } from "@refinedev/react-table";
+import type { ColumnDef } from "@tanstack/react-table";
+import { type GetManyResponse, useList, useMany } from "@refinedev/core";
+import { Button } from "@/registry/new-york/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuTrigger,
+} from "@/registry/new-york/ui/dropdown-menu";
+import { MoreHorizontal } from "lucide-react";
+import { DataTable } from "@/registry/new-york/refine-ui/data-table/data-table";
+import { DataTableSorter } from "@/registry/new-york/refine-ui/data-table/data-table-sorter";
+import {
+ DataTableFilterDropdownText,
+ DataTableFilterCombobox,
+ DataTableFilterDropdownDateRangePicker,
+ DataTableFilterDropdownDateSinglePicker,
+ DataTableFilterDropdownNumeric,
+} from "@/registry/new-york/refine-ui/data-table/data-table-filter";
+import { EditButton } from "@/registry/new-york/refine-ui/buttons/edit";
+import { DeleteButton } from "@/registry/new-york/refine-ui/buttons/delete";
+import {
+ ListViewHeader,
+ ListView,
+} from "@/registry/new-york/refine-ui/views/list-view";
+import { ShowButton } from "@/registry/new-york/refine-ui/buttons/show";
+import { cn } from "@/lib/utils";
+import type { Post, Category, Tag } from "../../types/resources";
+import { Badge } from "@/registry/new-york/ui/badge";
+
+export function PostsListPage() {
+ // fetch all categories to use in the combobox filter
+ const { result } = useList({
+ resource: "categories",
+ pagination: {
+ currentPage: 1,
+ pageSize: 999,
+ },
+ });
+ const categories = result?.data ?? [];
+
+ const columns = useMemo[]>(
+ () => [
+ {
+ id: "id",
+ accessorKey: "id",
+ size: 96,
+ header: ({ column, table }) => {
+ return (
+
+ );
+ },
+ },
+ {
+ id: "title",
+ accessorKey: "title",
+ size: 300,
+ header: ({ column, table }) => {
+ // Added table prop for filter
+ return (
+
+ );
+ },
+ },
+ {
+ id: "description",
+ header: "Description",
+ accessorKey: "content",
+ size: 400,
+ enableSorting: false,
+ enableColumnFilter: false,
+ },
+ {
+ id: "category.id",
+ accessorKey: "category.id", // Ensure this matches your data structure
+ size: 200,
+ header: ({ column }) => {
+ return (
+
+ Category
+ ({
+ label: item.title,
+ value: item.id.toString(),
+ }))}
+ />
+
+ );
+ },
+ cell: ({ getValue }) => {
+ const categoryId = getValue();
+ const category = categories.find((item) => item.id === categoryId);
+ return category?.title || "-";
+ },
+ },
+ {
+ id: "tags",
+ accessorKey: "tags",
+ size: 200,
+ header: "Tags",
+ cell: ({ getValue, table }) => {
+ const meta = table.options.meta as {
+ tagsData: GetManyResponse | undefined;
+ tagsIsLoading: boolean;
+ };
+ const tags = meta?.tagsData?.data ?? [];
+ const tagsByPost = tags.filter((tag) =>
+ getValue().includes(tag.id),
+ );
+
+ return (
+
+ {tagsByPost.map((tag) => (
+ {tag.title}
+ ))}
+
+ );
+ },
+ },
+ {
+ id: "status",
+ accessorKey: "status",
+ size: 100,
+ header: ({ column }) => {
+ return (
+
+ Status
+
+
+ );
+ },
+ cell: ({ getValue }) => {
+ const status = getValue();
+ const variantMap = {
+ published: "default",
+ draft: "outline",
+ rejected: "destructive",
+ };
+ return (
+
+ {status}
+
+ );
+ },
+ },
+ {
+ id: "createdAt",
+ accessorKey: "createdAt",
+ size: 220,
+ header: ({ column }) => {
+ return (
+
+ Created At
+ {
+ if (!dateRange?.from || !dateRange?.to) {
+ return undefined;
+ }
+ return [
+ dateRange.from.toISOString(),
+ dateRange.to.toISOString(),
+ ];
+ }}
+ />
+
+ );
+ },
+ cell: ({ row }) => {
+ return {row.original.createdAt}
;
+ },
+ },
+ {
+ id: "updatedAt", // If you have an updatedAt field
+ accessorKey: "updatedAt", // Change to updatedAt if available
+ size: 220,
+ header: ({ column }) => {
+ return (
+
+ Updated At
+ {
+ if (!date) return undefined;
+ return date.toISOString().split("T")[0];
+ }}
+ />
+
+ );
+ },
+ cell: ({ row }) => {
+ return {row.original.createdAt}
;
+ },
+ },
+ {
+ id: "actions",
+ size: 84,
+ enableSorting: false,
+ enableColumnFilter: false,
+ header: () => {
+ return (
+
+ Actions
+
+ );
+ },
+ cell: ({ row }) => {
+ const post = row.original;
+ return (
+
+
+
+ Open menu
+
+
+
+
+
+
+
+
+
+ );
+ },
+ },
+ ],
+ [categories],
+ );
+
+ const table = useTable({
+ refineCoreProps: {
+ // Pass Refine core props for filtering, sorting, pagination
+ },
+ columns,
+ // TanStack Table options can be passed here
+ initialState: {
+ columnPinning: {
+ left: [],
+ right: ["actions"], // Pin actions column to the right
+ },
+ },
+ });
+
+ const tableData = table.refineCore.tableQuery.data;
+
+ // get all tag ids from the table data to fetch all tags. we will use this to get relational data
+ const tagIds = useMemo(() => {
+ return (
+ tableData?.data?.flatMap((post) => post.tags.map((tag) => tag)) ?? []
+ );
+ }, [tableData]);
+
+ // fetch all tags available in the table data
+ const {
+ result: { data: tagsData },
+ query: { isLoading: tagsIsLoading },
+ } = useMany({
+ resource: "tags",
+ ids: tagIds,
+ queryOptions: {
+ enabled: tagIds.length > 0,
+ },
+ });
+
+ // set the tags data to the table meta to handle relations
+ useEffect(() => {
+ table.reactTable.setOptions((prev) => ({
+ ...prev,
+ meta: { ...prev.meta, tagsData: tagsData, tagsIsLoading: tagsIsLoading },
+ }));
+ }, [table, tagsData, tagsIsLoading]);
+
+ return (
+
+
+
+
+ );
+}
diff --git a/packages/refine-ui/examples/base-example/routes/posts/show.tsx b/packages/refine-ui/examples/base-example/routes/posts/show.tsx
new file mode 100644
index 0000000000000..239c899c86706
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/routes/posts/show.tsx
@@ -0,0 +1,199 @@
+import { useShow } from "@refinedev/core";
+import {
+ ShowView,
+ ShowViewHeader,
+} from "@/registry/new-york/refine-ui/views/show-view";
+import {
+ Card,
+ CardHeader,
+ CardTitle,
+ CardContent,
+ CardDescription,
+} from "@/registry/new-york/ui/card";
+import { Badge } from "@/registry/new-york/ui/badge";
+import {
+ Avatar,
+ AvatarImage,
+ AvatarFallback,
+} from "@/registry/new-york/ui/avatar";
+import { Label } from "@/registry/new-york/ui/label";
+import { Separator } from "@/registry/new-york/ui/separator";
+import { LoadingOverlay } from "@/registry/new-york/refine-ui/layout/loading-overlay";
+import { Calendar, User, Tag, Image as ImageIcon, Hash } from "lucide-react";
+
+import type { Post } from "../../types/resources";
+
+export default function ShowPost() {
+ const { query } = useShow();
+ const post = query.data?.data;
+
+ return (
+
+
+
+ {post ? (
+
+ {/* Title & Status */}
+
+
+
+ {post.image?.[0]?.url ? (
+
+
+ {post.title?.[0] ?? "?"}
+
+ ) : (
+
+ {post.title?.[0] ?? "?"}
+
+ )}
+
+
+
+
+
+ {post.title}
+
+
+ {post.status}
+
+
+
+
+
+ {new Date(post.publishedAt).toLocaleDateString()}
+
+
+
+
+
+ {/* Post Info Grid */}
+
+
+
+
+ User ID
+
+
+ {post.user?.id}
+
+
+
+
+
+ Category ID
+
+
+ {post.category?.id}
+
+
+
+
+
+
Created At
+
+ {new Date(post.createdAt).toLocaleString()}
+
+
+
+
Language ID
+
{post.language}
+
+
+
+ {/* Content Section */}
+
+
Content
+
+ {post.content}
+
+
+ {/* Images Section */}
+
+
+
+ Images
+
+
+ {post.image && post.image.length > 0 ? (
+ post.image.map((img) => (
+
+
+
+
+ {img.name?.[0] ?? "?"}
+
+
+
+ {img.name}
+
+
+ ))
+ ) : (
+
No Images
+ )}
+
+
+ {/* Tags Section */}
+
+
+
+ Tags
+
+
+ {post.tags && post.tags.length > 0 ? (
+ post.tags.map((tag) => (
+
+ {tag}
+
+ ))
+ ) : (
+ No Tags
+ )}
+
+
+ {/* Status Color Section */}
+
+
Status Color
+
+
+
+ {post.status_color}
+
+
+
+
+
+
+ ) : (
+ Loading...
+ )}
+
+
+ );
+}
diff --git a/packages/refine-ui/examples/base-example/routes/users/list.tsx b/packages/refine-ui/examples/base-example/routes/users/list.tsx
new file mode 100644
index 0000000000000..b369c9300827a
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/routes/users/list.tsx
@@ -0,0 +1,119 @@
+import { useTable } from "@refinedev/core";
+import {
+ Table,
+ TableHeader,
+ TableBody,
+ TableRow,
+ TableHead,
+ TableCell,
+} from "@/registry/new-york/ui/table";
+import {
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+} from "@/registry/new-york/ui/avatar";
+import { Badge } from "@/registry/new-york/ui/badge";
+import { DataTablePagination } from "@/registry/new-york/refine-ui/data-table/data-table-pagination";
+import type { User } from "@/examples/base-example/types/resources";
+import { cn } from "@/lib/utils";
+
+export function UsersListPage() {
+ const {
+ tableQuery,
+ currentPage,
+ pageCount,
+ setCurrentPage,
+ pageSize,
+ setPageSize,
+ } = useTable({ resource: "users" });
+ const users = tableQuery.data?.data ?? [];
+ const isLoading = tableQuery.isLoading;
+ const total = tableQuery.data?.total;
+
+ return (
+
+
+
+
+
+
+ Name
+ Email
+ Skills
+ Birthday
+
+
+
+ {isLoading ? (
+
+
+ Loading...
+
+
+ ) : users.length === 0 ? (
+
+
+ No users found.
+
+
+ ) : (
+ users.map((user) => (
+
+
+
+
+
+
+ {user.firstName.charAt(0)}
+ {user.lastName.charAt(0)}
+
+
+
+
+ {user.firstName} {user.lastName}
+
+
+
+
+ {user.email}
+
+
+ {user.skills.map((skill) => (
+
+ {skill}
+
+ ))}
+
+
+
+ {new Date(user.birthday).toLocaleDateString()}
+
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/refine-ui/examples/base-example/types/resources.ts b/packages/refine-ui/examples/base-example/types/resources.ts
new file mode 100644
index 0000000000000..0f75de9d0abf1
--- /dev/null
+++ b/packages/refine-ui/examples/base-example/types/resources.ts
@@ -0,0 +1,55 @@
+export type Post = {
+ id: number;
+ title: string;
+ slug: string;
+ content: string;
+ hit: number;
+ category: {
+ id: number;
+ };
+ user: {
+ id: number;
+ };
+ status: string;
+ status_color: string;
+ createdAt: string;
+ publishedAt: string;
+ image: Array<{
+ url: string;
+ name: string;
+ status: string;
+ type: string;
+ uid: string;
+ }>;
+ tags: Array;
+ language: number;
+};
+
+export type Category = {
+ id: number;
+ title: string;
+};
+
+export type User = {
+ id: number;
+ firstName: string;
+ lastName: string;
+ email: string;
+ status: boolean;
+ birthday: string;
+ skills: Array;
+ avatar: Array<{
+ name: string;
+ percent: number;
+ size: number;
+ status: string;
+ type: string;
+ uid: string;
+ url: string;
+ }>;
+};
+
+export type Tag = {
+ id: number;
+ title: string;
+};
diff --git a/packages/refine-ui/hooks/use-mobile.ts b/packages/refine-ui/hooks/use-mobile.ts
new file mode 100644
index 0000000000000..a93d58393804b
--- /dev/null
+++ b/packages/refine-ui/hooks/use-mobile.ts
@@ -0,0 +1,21 @@
+import * as React from "react";
+
+const MOBILE_BREAKPOINT = 768;
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(
+ undefined,
+ );
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ };
+ mql.addEventListener("change", onChange);
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ return () => mql.removeEventListener("change", onChange);
+ }, []);
+
+ return !!isMobile;
+}
diff --git a/packages/refine-ui/lib/utils.ts b/packages/refine-ui/lib/utils.ts
new file mode 100644
index 0000000000000..a5ef193506d07
--- /dev/null
+++ b/packages/refine-ui/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/packages/refine-ui/netlify.toml b/packages/refine-ui/netlify.toml
new file mode 100644
index 0000000000000..26763a3f2c193
--- /dev/null
+++ b/packages/refine-ui/netlify.toml
@@ -0,0 +1,2 @@
+[[plugins]]
+ package = "@netlify/plugin-nextjs"
\ No newline at end of file
diff --git a/packages/refine-ui/next-env.d.ts b/packages/refine-ui/next-env.d.ts
new file mode 100644
index 0000000000000..1b3be0840f3f6
--- /dev/null
+++ b/packages/refine-ui/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/packages/refine-ui/next.config.mjs b/packages/refine-ui/next.config.mjs
new file mode 100644
index 0000000000000..4678774e6d606
--- /dev/null
+++ b/packages/refine-ui/next.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+
+export default nextConfig;
diff --git a/packages/refine-ui/package.json b/packages/refine-ui/package.json
new file mode 100644
index 0000000000000..f66dec6f25e87
--- /dev/null
+++ b/packages/refine-ui/package.json
@@ -0,0 +1,87 @@
+{
+ "name": "@refinedev/refine-ui",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "build": "pnpm registry:build && next build",
+ "dev": "next dev",
+ "lint": "next lint",
+ "registry:build": "shadcn build",
+ "start": "next start"
+ },
+ "dependencies": {
+ "@hookform/resolvers": "^5.0.1",
+ "@radix-ui/react-accordion": "^1.2.8",
+ "@radix-ui/react-alert-dialog": "^1.1.11",
+ "@radix-ui/react-aspect-ratio": "^1.1.4",
+ "@radix-ui/react-avatar": "^1.1.7",
+ "@radix-ui/react-checkbox": "^1.2.3",
+ "@radix-ui/react-collapsible": "^1.1.8",
+ "@radix-ui/react-context-menu": "^2.2.12",
+ "@radix-ui/react-dialog": "^1.1.11",
+ "@radix-ui/react-dropdown-menu": "^2.1.12",
+ "@radix-ui/react-hover-card": "^1.1.11",
+ "@radix-ui/react-label": "^2.1.4",
+ "@radix-ui/react-menubar": "^1.1.12",
+ "@radix-ui/react-navigation-menu": "^1.2.10",
+ "@radix-ui/react-popover": "^1.1.11",
+ "@radix-ui/react-progress": "^1.1.4",
+ "@radix-ui/react-radio-group": "^1.3.4",
+ "@radix-ui/react-scroll-area": "^1.2.6",
+ "@radix-ui/react-select": "^2.2.2",
+ "@radix-ui/react-separator": "^1.1.4",
+ "@radix-ui/react-slider": "^1.3.2",
+ "@radix-ui/react-slot": "^1.2.0",
+ "@radix-ui/react-switch": "^1.2.2",
+ "@radix-ui/react-tabs": "^1.1.9",
+ "@radix-ui/react-toggle": "^1.1.6",
+ "@radix-ui/react-toggle-group": "^1.1.7",
+ "@radix-ui/react-tooltip": "^1.2.4",
+ "@refinedev/core": "^5.0.1",
+ "@refinedev/react-hook-form": "^5.0.0",
+ "@refinedev/react-router": "^2.0.0",
+ "@refinedev/react-table": "^6.0.0",
+ "@refinedev/simple-rest": "^6.0.0",
+ "@refinedev/ui-types": "^2.0.0",
+ "@tanstack/react-table": "^8.2.6",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "^1.1.1",
+ "date-fns": "^4.1.0",
+ "dayjs": "^1.10.7",
+ "embla-carousel-react": "^8.6.0",
+ "input-otp": "^1.4.2",
+ "lucide-react": "^0.487.0",
+ "next": "15.3.1",
+ "next-themes": "^0.4.6",
+ "postcss": "^8.5.3",
+ "react": "19.1.0",
+ "react-day-picker": "8.10.1",
+ "react-dom": "19.1.0",
+ "react-hook-form": "^7.57.0",
+ "react-resizable-panels": "^2.1.8",
+ "react-router": "^7.0.2",
+ "recharts": "^2.15.3",
+ "shadcn": "^2.4.1",
+ "sonner": "^2.0.3",
+ "tailwind-merge": "^3.2.0",
+ "tw-animate-css": "^1.2.5",
+ "vaul": "^1.1.2",
+ "zod": "^3.24.3"
+ },
+ "devDependencies": {
+ "@eslint/eslintrc": "^3",
+ "@netlify/plugin-nextjs": "^5.11.2",
+ "@tailwindcss/postcss": "^4.1.4",
+ "@types/node": "^20",
+ "@types/react": "^19.1.0",
+ "@types/react-dom": "^19.1.0",
+ "eslint": "^9",
+ "eslint-config-next": "15.3.1",
+ "tailwindcss": "^4.1.4",
+ "typescript": "^5.8.3"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+}
diff --git a/packages/refine-ui/postcss.config.mjs b/packages/refine-ui/postcss.config.mjs
new file mode 100644
index 0000000000000..61e36849cf7cf
--- /dev/null
+++ b/packages/refine-ui/postcss.config.mjs
@@ -0,0 +1,7 @@
+const config = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
+
+export default config;
diff --git a/packages/refine-ui/public/file.svg b/packages/refine-ui/public/file.svg
new file mode 100644
index 0000000000000..004145cddf3f9
--- /dev/null
+++ b/packages/refine-ui/public/file.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/refine-ui/public/globe.svg b/packages/refine-ui/public/globe.svg
new file mode 100644
index 0000000000000..567f17b0d7c7f
--- /dev/null
+++ b/packages/refine-ui/public/globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/refine-ui/public/next.svg b/packages/refine-ui/public/next.svg
new file mode 100644
index 0000000000000..5174b28c565c2
--- /dev/null
+++ b/packages/refine-ui/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/refine-ui/public/r/auto-save-indicator.json b/packages/refine-ui/public/r/auto-save-indicator.json
new file mode 100644
index 0000000000000..14841b42cfd3a
--- /dev/null
+++ b/packages/refine-ui/public/r/auto-save-indicator.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "auto-save-indicator",
+ "type": "registry:component",
+ "title": "Auto Save Indicator",
+ "author": "Refine ",
+ "description": "A visual indicator component for auto-save functionality in Refine forms. Shows loading, success, error, and idle states with smooth transitions and customizable elements.",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": [],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/form/auto-save-indicator.tsx",
+ "content": "\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { useTranslate, type AutoSaveIndicatorProps } from \"@refinedev/core\";\nimport { AlertTriangle, Clock, CheckCircle2, Loader2 } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ntype Props = AutoSaveIndicatorProps;\n\nexport function AutoSaveIndicator({\n status,\n elements: elementsFromProps,\n}: Props) {\n const t = useTranslate();\n const [shouldFadeSuccess, setShouldFadeSuccess] = useState(false);\n\n useEffect(() => {\n if (status === \"success\") {\n const timer = setTimeout(() => {\n setShouldFadeSuccess(true);\n }, 1000);\n\n return () => {\n clearTimeout(timer);\n setShouldFadeSuccess(false);\n };\n }\n setShouldFadeSuccess(false);\n }, [status]);\n\n const elements = {\n pending: elementsFromProps?.loading ?? (\n \n \n \n {t(\"autoSave.saving\", \"Saving\")}\n \n
\n ),\n success: elementsFromProps?.success ?? (\n \n \n \n {t(\"autoSave.saved\", \"Saved\")}\n \n
\n ),\n error: elementsFromProps?.error ?? (\n \n
\n
\n {t(\"autoSave.failed\", \"Failed\")}\n \n
\n ),\n idle: elementsFromProps?.idle ?? (\n \n \n {t(\"autoSave.idle\", \"Idle\")} \n
\n ),\n };\n\n return {elements[status]}
;\n}\n\nAutoSaveIndicator.displayName = \"AutoSaveIndicator\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/auto-save-indicator.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["form", "auto-save", "indicator", "feedback", "ui"]
+}
diff --git a/packages/refine-ui/public/r/breadcrumb.json b/packages/refine-ui/public/r/breadcrumb.json
new file mode 100644
index 0000000000000..144c6fabb62c0
--- /dev/null
+++ b/packages/refine-ui/public/r/breadcrumb.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "breadcrumb",
+ "type": "registry:component",
+ "title": "Refine Breadcrumb",
+ "author": "Refine ",
+ "description": "A breadcrumb navigation component for Refine applications that automatically generates breadcrumbs based on the current route and resource structure.",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": ["breadcrumb"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/layout/breadcrumb.tsx",
+ "content": "\"use client\";\n\nimport { Fragment, useMemo } from \"react\";\nimport { Home } from \"lucide-react\";\nimport {\n matchResourceFromRoute,\n useBreadcrumb,\n useLink,\n useResourceParams,\n} from \"@refinedev/core\";\nimport {\n BreadcrumbSeparator as ShadcnBreadcrumbSeparator,\n BreadcrumbItem as ShadcnBreadcrumbItem,\n BreadcrumbList as ShadcnBreadcrumbList,\n BreadcrumbPage as ShadcnBreadcrumbPage,\n Breadcrumb as ShadcnBreadcrumb,\n} from \"@/registry/new-york/ui/breadcrumb\";\n\nexport function Breadcrumb() {\n const Link = useLink();\n const { breadcrumbs } = useBreadcrumb();\n const { resources } = useResourceParams();\n const rootRouteResource = matchResourceFromRoute(\"/\", resources);\n\n const breadCrumbItems = useMemo(() => {\n const list: {\n key: string;\n href: string;\n Component: React.ReactNode;\n }[] = [];\n\n list.push({\n key: \"breadcrumb-item-home\",\n href: rootRouteResource.matchedRoute ?? \"/\",\n Component: (\n \n {rootRouteResource?.resource?.meta?.icon ?? (\n \n )}\n \n ),\n });\n\n for (const { label, href } of breadcrumbs) {\n list.push({\n key: `breadcrumb-item-${label}`,\n href: href ?? \"\",\n Component: href ? {label} : {label} ,\n });\n }\n\n return list;\n }, [breadcrumbs, Link, rootRouteResource]);\n\n return (\n \n \n {breadCrumbItems.map((item, index) => {\n if (index === breadCrumbItems.length - 1) {\n return (\n \n {item.Component}\n \n );\n }\n\n return (\n \n \n {item.Component}\n \n \n \n );\n })}\n \n \n );\n}\n\nBreadcrumb.displayName = \"Breadcrumb\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/breadcrumb.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["navigation", "breadcrumb", "layout"]
+}
diff --git a/packages/refine-ui/public/r/buttons.json b/packages/refine-ui/public/r/buttons.json
new file mode 100644
index 0000000000000..7f540718038b0
--- /dev/null
+++ b/packages/refine-ui/public/r/buttons.json
@@ -0,0 +1,56 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "buttons",
+ "type": "registry:component",
+ "title": "Refine Buttons",
+ "author": "Refine ",
+ "description": "A comprehensive set of action buttons for Refine applications including create, edit, delete, show, clone, list, and refresh buttons",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": ["button", "popover"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/buttons/create.tsx",
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport { type BaseKey, useCreateButton } from \"@refinedev/core\";\nimport { Plus } from \"lucide-react\";\nimport { Button } from \"@/registry/new-york/ui/button\";\n\ntype CreateButtonProps = {\n /**\n * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.\n * @default Inferred resource name from the route\n */\n resource?: BaseKey;\n /**\n * Access Control configuration for the button\n * @default `{ enabled: true, hideIfUnauthorized: false }`\n */\n accessControl?: {\n enabled?: boolean;\n hideIfUnauthorized?: boolean;\n };\n /**\n * `meta` property is used when creating the URL for the related action and path.\n */\n meta?: Record;\n} & React.ComponentProps;\n\nexport const CreateButton = React.forwardRef<\n React.ComponentRef,\n CreateButtonProps\n>(({ resource, accessControl, meta, children, onClick, ...rest }, ref) => {\n const { hidden, disabled, LinkComponent, to, label } = useCreateButton({\n resource,\n accessControl,\n meta,\n });\n\n const isDisabled = disabled || rest.disabled;\n const isHidden = hidden || rest.hidden;\n\n if (isHidden) return null;\n\n return (\n \n ) => {\n if (isDisabled) {\n e.preventDefault();\n return;\n }\n if (onClick) {\n e.preventDefault();\n onClick(e);\n }\n }}\n >\n {children ?? (\n \n
\n
{label ?? \"Create\"} \n
\n )}\n \n \n );\n});\n\nCreateButton.displayName = \"CreateButton\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/create.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/edit.tsx",
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport { type BaseKey, useEditButton } from \"@refinedev/core\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { Pencil } from \"lucide-react\";\n\ntype EditButtonProps = {\n /**\n * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.\n * @default Inferred resource name from the route\n */\n resource?: string;\n /**\n * Data item identifier for the actions with the API\n * @default Reads `:id` from the URL\n */\n recordItemId?: BaseKey;\n /**\n * Access Control configuration for the button\n * @default `{ enabled: true, hideIfUnauthorized: false }`\n */\n accessControl?: {\n enabled?: boolean;\n hideIfUnauthorized?: boolean;\n };\n /**\n * `meta` property is used when creating the URL for the related action and path.\n */\n meta?: Record;\n} & React.ComponentProps;\n\nexport const EditButton = React.forwardRef<\n React.ComponentRef,\n EditButtonProps\n>(\n (\n { resource, recordItemId, accessControl, meta, children, onClick, ...rest },\n ref,\n ) => {\n const { hidden, disabled, LinkComponent, to, label } = useEditButton({\n resource,\n id: recordItemId,\n accessControl,\n meta,\n });\n\n const isDisabled = disabled || rest.disabled;\n const isHidden = hidden || rest.hidden;\n\n if (isHidden) return null;\n\n return (\n \n ) => {\n if (isDisabled) {\n e.preventDefault();\n return;\n }\n if (onClick) {\n e.preventDefault();\n onClick(e);\n }\n }}\n >\n {children ?? (\n \n )}\n \n \n );\n },\n);\n\nEditButton.displayName = \"EditButton\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/edit.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/show.tsx",
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport { type BaseKey, useShowButton } from \"@refinedev/core\";\nimport { Eye } from \"lucide-react\";\nimport { Button } from \"@/registry/new-york/ui/button\";\n\ntype ShowButtonProps = {\n /**\n * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.\n * @default Inferred resource name from the route\n */\n resource?: string;\n /**\n * Data item identifier for the actions with the API\n * @default Reads `:id` from the URL\n */\n recordItemId?: BaseKey;\n /**\n * Access Control configuration for the button\n * @default `{ enabled: true, hideIfUnauthorized: false }`\n */\n accessControl?: {\n enabled?: boolean;\n hideIfUnauthorized?: boolean;\n };\n /**\n * `meta` property is used when creating the URL for the related action and path.\n */\n meta?: Record;\n} & React.ComponentProps;\n\nexport const ShowButton = React.forwardRef<\n React.ComponentRef,\n ShowButtonProps\n>(\n (\n { resource, recordItemId, accessControl, meta, children, onClick, ...rest },\n ref,\n ) => {\n const { hidden, disabled, LinkComponent, to, label } = useShowButton({\n resource,\n id: recordItemId,\n accessControl,\n meta,\n });\n\n const isDisabled = disabled || rest.disabled;\n const isHidden = hidden || rest.hidden;\n\n if (isHidden) return null;\n\n return (\n \n ) => {\n if (isDisabled) {\n e.preventDefault();\n return;\n }\n if (onClick) {\n e.preventDefault();\n onClick(e);\n }\n }}\n >\n {children ?? (\n \n \n {label} \n
\n )}\n \n \n );\n },\n);\n\nShowButton.displayName = \"ShowButton\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/show.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/delete.tsx",
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport { type BaseKey, useDeleteButton } from \"@refinedev/core\";\nimport { Loader2, Trash } from \"lucide-react\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/new-york/ui/popover\";\n\ntype DeleteButtonProps = {\n /**\n * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.\n * @default Inferred resource name from the route\n */\n resource?: string;\n /**\n * Data item identifier for the actions with the API\n * @default Reads `:id` from the URL\n */\n recordItemId?: BaseKey;\n /**\n * Access Control configuration for the button\n * @default `{ enabled: true, hideIfUnauthorized: false }`\n */\n accessControl?: {\n enabled?: boolean;\n hideIfUnauthorized?: boolean;\n };\n /**\n * `meta` property is used when creating the URL for the related action and path.\n */\n meta?: Record;\n} & React.ComponentProps;\n\nexport const DeleteButton = React.forwardRef<\n React.ComponentRef,\n DeleteButtonProps\n>(({ resource, recordItemId, accessControl, meta, children, ...rest }, ref) => {\n const {\n hidden,\n disabled,\n loading,\n onConfirm,\n label,\n confirmTitle: defaultConfirmTitle,\n confirmOkLabel: defaultConfirmOkLabel,\n cancelLabel: defaultCancelLabel,\n } = useDeleteButton({\n resource,\n id: recordItemId,\n accessControl,\n meta,\n });\n const [open, setOpen] = React.useState(false);\n\n const isDisabled = disabled || rest.disabled || loading;\n const isHidden = hidden || rest.hidden;\n\n if (isHidden) return null;\n\n const confirmCancelText = defaultCancelLabel;\n const confirmOkText = defaultConfirmOkLabel;\n const confirmTitle = defaultConfirmTitle;\n\n return (\n \n \n \n \n {loading && }\n {children ?? (\n \n \n {label} \n
\n )}\n \n \n \n \n \n
{confirmTitle}
\n
\n setOpen(false)}>\n {confirmCancelText}\n \n {\n if (typeof onConfirm === \"function\") {\n onConfirm();\n }\n setOpen(false);\n }}\n >\n {confirmOkText}\n \n
\n
\n \n \n );\n});\n\nDeleteButton.displayName = \"DeleteButton\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/delete.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/clone.tsx",
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport { type BaseKey, useCloneButton } from \"@refinedev/core\";\nimport { Copy } from \"lucide-react\";\nimport { Button } from \"@/registry/new-york/ui/button\";\n\ntype CloneButtonProps = {\n /**\n * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.\n * @default Inferred resource name from the route\n */\n resource?: string;\n /**\n * Data item identifier for the actions with the API\n * @default Reads `:id` from the URL\n */\n recordItemId?: BaseKey;\n /**\n * Access Control configuration for the button\n * @default `{ enabled: true, hideIfUnauthorized: false }`\n */\n accessControl?: {\n enabled?: boolean;\n hideIfUnauthorized?: boolean;\n };\n /**\n * `meta` property is used when creating the URL for the related action and path.\n */\n meta?: Record;\n} & React.ComponentProps;\n\nexport const CloneButton = React.forwardRef<\n React.ComponentRef,\n CloneButtonProps\n>(\n (\n { resource, recordItemId, accessControl, meta, children, onClick, ...rest },\n ref,\n ) => {\n const { hidden, disabled, LinkComponent, to, label } = useCloneButton({\n accessControl,\n resource,\n id: recordItemId,\n meta,\n });\n\n const isDisabled = disabled || rest.disabled;\n const isHidden = hidden || rest.hidden;\n\n if (isHidden) return null;\n\n return (\n \n ) => {\n if (isDisabled) {\n e.preventDefault();\n return;\n }\n if (onClick) {\n e.preventDefault();\n onClick(e);\n }\n }}\n >\n {children ?? (\n \n \n {label} \n
\n )}\n \n \n );\n },\n);\n\nCloneButton.displayName = \"CloneButton\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/clone.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/list.tsx",
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport { type BaseKey, useListButton } from \"@refinedev/core\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { List } from \"lucide-react\";\n\ntype ListButtonProps = {\n /**\n * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.\n * @default Inferred resource name from the route\n */\n resource?: BaseKey;\n /**\n * Access Control configuration for the button\n * @default `{ enabled: true, hideIfUnauthorized: false }`\n */\n accessControl?: {\n enabled?: boolean;\n hideIfUnauthorized?: boolean;\n };\n /**\n * `meta` property is used when creating the URL for the related action and path.\n */\n meta?: Record;\n} & React.ComponentProps;\n\nexport const ListButton = React.forwardRef<\n React.ComponentRef,\n ListButtonProps\n>(({ resource, accessControl, meta, children, onClick, ...rest }, ref) => {\n const { hidden, disabled, LinkComponent, to, label } = useListButton({\n resource,\n accessControl,\n meta,\n });\n\n const isDisabled = disabled || rest.disabled;\n const isHidden = hidden || rest.hidden;\n\n if (isHidden) return null;\n\n return (\n \n ) => {\n if (isDisabled) {\n e.preventDefault();\n return;\n }\n if (onClick) {\n e.preventDefault();\n onClick(e);\n }\n }}\n >\n {children ?? (\n \n
\n {label} \n
\n )}\n \n \n );\n});\n\nListButton.displayName = \"ListButton\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/list.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/refresh.tsx",
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport { type BaseKey, useRefreshButton } from \"@refinedev/core\";\nimport { RefreshCcw } from \"lucide-react\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype RefreshButtonProps = {\n /**\n * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.\n * @default Inferred resource name from the route\n */\n resource?: string;\n /**\n * Data item identifier for the actions with the API\n * @default Reads `:id` from the URL\n */\n recordItemId?: BaseKey;\n /**\n * Target data provider name for API call to be made\n * @default `\"default\"`\n */\n dataProviderName?: string;\n /**\n * `meta` property is used when creating the URL for the related action and path.\n */\n meta?: Record;\n} & React.ComponentProps;\n\nexport const RefreshButton = React.forwardRef<\n React.ComponentRef,\n RefreshButtonProps\n>(\n (\n { resource, recordItemId, dataProviderName, meta, children, ...rest },\n ref,\n ) => {\n const {\n onClick: refresh,\n loading,\n label,\n } = useRefreshButton({\n resource,\n id: recordItemId,\n dataProviderName,\n meta,\n });\n\n const isDisabled = rest.disabled || loading;\n\n return (\n ) => {\n if (isDisabled) {\n e.preventDefault();\n return;\n }\n refresh();\n }}\n {...rest}\n ref={ref}\n disabled={isDisabled}\n >\n {children ?? (\n \n \n {label ?? \"Refresh\"} \n
\n )}\n \n );\n },\n);\n\nRefreshButton.displayName = \"RefreshButton\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/refresh.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["buttons", "actions", "crud", "navigation"]
+}
diff --git a/packages/refine-ui/public/r/data-table.json b/packages/refine-ui/public/r/data-table.json
new file mode 100644
index 0000000000000..5dc0255f467bc
--- /dev/null
+++ b/packages/refine-ui/public/r/data-table.json
@@ -0,0 +1,54 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "data-table",
+ "type": "registry:component",
+ "title": "Data Table",
+ "author": "Refine ",
+ "description": "A comprehensive data table component for Refine with sorting, filtering, pagination, and advanced features",
+ "dependencies": [
+ "@refinedev/core",
+ "@refinedev/react-table",
+ "@tanstack/react-table",
+ "react-day-picker",
+ "lucide-react"
+ ],
+ "registryDependencies": [
+ "table",
+ "button",
+ "input",
+ "badge",
+ "popover",
+ "command",
+ "separator",
+ "calendar",
+ "select"
+ ],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/data-table/data-table.tsx",
+ "content": "\"use client\";\n\nimport type { HttpError, BaseRecord } from \"@refinedev/core\";\nimport type { UseTableReturnType } from \"@refinedev/react-table\";\nimport type { Column } from \"@tanstack/react-table\";\nimport { flexRender } from \"@tanstack/react-table\";\nimport { Loader2 } from \"lucide-react\";\nimport { useEffect, useRef, useState } from \"react\";\n\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/registry/new-york/ui/table\";\nimport { DataTablePagination } from \"@/registry/new-york/refine-ui/data-table/data-table-pagination\";\nimport { cn } from \"@/lib/utils\";\n\ntype DataTableProps = {\n table: UseTableReturnType;\n};\n\nexport function DataTable({\n table,\n}: DataTableProps) {\n const {\n reactTable: { getHeaderGroups, getRowModel, getAllColumns },\n refineCore: {\n tableQuery,\n currentPage,\n setCurrentPage,\n pageCount,\n pageSize,\n setPageSize,\n },\n } = table;\n\n const columns = getAllColumns();\n const leafColumns = table.reactTable.getAllLeafColumns();\n const isLoading = tableQuery.isLoading;\n\n const tableContainerRef = useRef(null);\n const tableRef = useRef(null);\n const [isOverflowing, setIsOverflowing] = useState({\n horizontal: false,\n vertical: false,\n });\n\n useEffect(() => {\n const checkOverflow = () => {\n if (tableRef.current && tableContainerRef.current) {\n const table = tableRef.current;\n const container = tableContainerRef.current;\n\n const horizontalOverflow = table.offsetWidth > container.clientWidth;\n const verticalOverflow = table.offsetHeight > container.clientHeight;\n\n setIsOverflowing({\n horizontal: horizontalOverflow,\n vertical: verticalOverflow,\n });\n }\n };\n\n checkOverflow();\n\n // Check on window resize\n window.addEventListener(\"resize\", checkOverflow);\n\n // Check when table data changes\n const timeoutId = setTimeout(checkOverflow, 100);\n\n return () => {\n window.removeEventListener(\"resize\", checkOverflow);\n clearTimeout(timeoutId);\n };\n }, [tableQuery.data?.data, pageSize]);\n\n return (\n \n
\n
\n \n {getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers.map((header) => {\n const isPlaceholder = header.isPlaceholder;\n\n return (\n \n {isPlaceholder ? null : (\n \n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n
\n )}\n \n );\n })}\n \n ))}\n \n \n {isLoading ? (\n <>\n {Array.from({ length: pageSize < 1 ? 1 : pageSize }).map(\n (_, rowIndex) => (\n \n {leafColumns.map((column) => (\n \n
\n \n ))}\n \n ),\n )}\n \n \n \n \n \n >\n ) : getRowModel().rows?.length ? (\n getRowModel().rows.map((row) => {\n return (\n \n {row.getVisibleCells().map((cell) => {\n return (\n \n \n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n
\n \n );\n })}\n \n );\n })\n ) : (\n \n )}\n \n
\n
\n {!isLoading && getRowModel().rows?.length > 0 && (\n
\n )}\n
\n );\n}\n\nfunction DataTableNoData({\n isOverflowing,\n columnsLength,\n}: {\n isOverflowing: { horizontal: boolean; vertical: boolean };\n columnsLength: number;\n}) {\n return (\n \n \n \n
\n No data to display\n
\n
\n This table is empty for the time being.\n
\n
\n \n \n );\n}\n\nexport function getCommonStyles({\n column,\n isOverflowing,\n}: {\n column: Column;\n isOverflowing: {\n horizontal: boolean;\n vertical: boolean;\n };\n}): React.CSSProperties {\n const isPinned = column.getIsPinned();\n const isLastLeftPinnedColumn =\n isPinned === \"left\" && column.getIsLastColumn(\"left\");\n const isFirstRightPinnedColumn =\n isPinned === \"right\" && column.getIsFirstColumn(\"right\");\n\n return {\n boxShadow:\n isOverflowing.horizontal && isLastLeftPinnedColumn\n ? \"-4px 0 4px -4px var(--border) inset\"\n : isOverflowing.horizontal && isFirstRightPinnedColumn\n ? \"4px 0 4px -4px var(--border) inset\"\n : undefined,\n left:\n isOverflowing.horizontal && isPinned === \"left\"\n ? `${column.getStart(\"left\")}px`\n : undefined,\n right:\n isOverflowing.horizontal && isPinned === \"right\"\n ? `${column.getAfter(\"right\")}px`\n : undefined,\n opacity: 1,\n position: isOverflowing.horizontal && isPinned ? \"sticky\" : \"relative\",\n background: isOverflowing.horizontal && isPinned ? \"var(--background)\" : \"\",\n borderTopRightRadius:\n isOverflowing.horizontal && isPinned === \"right\"\n ? \"var(--radius)\"\n : undefined,\n borderBottomRightRadius:\n isOverflowing.horizontal && isPinned === \"right\"\n ? \"var(--radius)\"\n : undefined,\n borderTopLeftRadius:\n isOverflowing.horizontal && isPinned === \"left\"\n ? \"var(--radius)\"\n : undefined,\n borderBottomLeftRadius:\n isOverflowing.horizontal && isPinned === \"left\"\n ? \"var(--radius)\"\n : undefined,\n width: column.getSize(),\n zIndex: isOverflowing.horizontal && isPinned ? 1 : 0,\n };\n}\n\nDataTable.displayName = \"DataTable\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/data-table/data-table.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/data-table/data-table-filter.tsx",
+ "content": "\"use client\";\n\nimport { useState, useEffect, useMemo } from \"react\";\nimport { useTranslate, type CrudOperators } from \"@refinedev/core\";\nimport type { Column, Table as ReactTable } from \"@tanstack/react-table\";\nimport type { DateRange } from \"react-day-picker\";\nimport { Check, ChevronsUpDown, ListFilter, X } from \"lucide-react\";\n\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { Input } from \"@/registry/new-york/ui/input\";\nimport { Badge } from \"@/registry/new-york/ui/badge\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/new-york/ui/popover\";\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"@/registry/new-york/ui/command\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\nimport { Calendar } from \"@/registry/new-york/ui/calendar\";\nimport { cn } from \"@/lib/utils\";\n\nexport type DataTableFilterDropdownProps = {\n column: Column;\n contentClassName?: string;\n triggerClassName?: string;\n children: (args: {\n isOpen: boolean;\n setIsOpen: React.Dispatch>;\n }) => React.ReactNode;\n};\n\nexport function DataTableFilterDropdown({\n column,\n triggerClassName,\n contentClassName,\n children,\n}: DataTableFilterDropdownProps) {\n const [isOpen, setIsOpen] = useState(false);\n\n const isFiltered = column.getIsFiltered();\n\n return (\n \n \n setIsOpen(true)}\n variant=\"ghost\"\n size=\"icon\"\n className={cn(\n \"data-[state=open]:bg-accent\",\n \"w-5 h-5\",\n {\n \"text-primary\": isFiltered,\n \"text-muted-foreground\": !isFiltered,\n },\n triggerClassName,\n )}\n >\n \n \n \n \n {children({ isOpen, setIsOpen })}\n \n \n );\n}\n\ntype DataTableFilterDropdownActionsProps = {\n className?: string;\n isClearDisabled?: boolean;\n isApplyDisabled?: boolean;\n onClear: () => void;\n onApply: () => void;\n};\n\nexport function DataTableFilterDropdownActions({\n className,\n isClearDisabled,\n isApplyDisabled,\n onClear,\n onApply,\n}: DataTableFilterDropdownActionsProps) {\n const t = useTranslate();\n\n return (\n \n {\n onClear();\n }}\n >\n \n {t(\"buttons.clear\", \"Clear\")}\n \n\n {\n onApply();\n }}\n >\n {t(\"buttons.apply\", \"Apply\")}\n \n
\n );\n}\n\nexport type DataTableFilterDropdownTextProps = {\n column: Column;\n table: ReactTable;\n defaultOperator?: CrudOperators;\n operators?: CrudOperators[];\n placeholder?: string;\n};\n\nexport function DataTableFilterDropdownText({\n column,\n table,\n operators = [\n \"eq\",\n \"ne\",\n \"contains\",\n \"ncontains\",\n \"containss\",\n \"ncontainss\",\n \"startswith\",\n \"nstartswith\",\n \"startswiths\",\n \"nstartswiths\",\n \"endswith\",\n \"nendswith\",\n \"endswiths\",\n \"nendswiths\",\n \"in\",\n \"nin\",\n \"ina\",\n \"nina\",\n ],\n defaultOperator = \"eq\",\n placeholder,\n}: DataTableFilterDropdownTextProps) {\n const t = useTranslate();\n\n return (\n (\n {\n onChange(event.target.value);\n }}\n />\n )}\n />\n );\n}\n\nexport type DataTableFilterDropdownNumericProps = {\n column: Column;\n table: ReactTable;\n defaultOperator?: CrudOperators;\n operators?: CrudOperators[];\n placeholder?: string;\n};\n\nexport function DataTableFilterDropdownNumeric({\n column,\n table,\n operators = [\"eq\", \"ne\", \"gt\", \"lt\", \"gte\", \"lte\"],\n defaultOperator = \"eq\",\n placeholder,\n}: DataTableFilterDropdownNumericProps) {\n const t = useTranslate();\n\n return (\n (\n {\n onChange(event.target.value);\n }}\n />\n )}\n />\n );\n}\n\nexport type DataTableFilterComboboxProps = {\n column: Column;\n table?: ReactTable;\n options: { label: string; value: string }[];\n defaultOperator?: CrudOperators;\n operators?: CrudOperators[];\n placeholder?: string;\n noResultsText?: string;\n multiple?: boolean;\n};\n\nexport function DataTableFilterCombobox({\n column,\n table,\n options,\n defaultOperator = \"eq\",\n operators = [\"eq\", \"ne\", \"in\", \"nin\"],\n placeholder,\n noResultsText,\n multiple = false,\n}: DataTableFilterComboboxProps) {\n const t = useTranslate();\n const [isOpen, setIsOpen] = useState(false);\n\n return (\n {\n const currentValues = multiple\n ? Array.isArray(value)\n ? value\n : value && typeof value === \"string\"\n ? [value]\n : []\n : value && typeof value === \"string\"\n ? [value]\n : [];\n\n const handleSelect = (optionValue: string) => {\n if (multiple) {\n const newValues = currentValues.includes(optionValue)\n ? currentValues.filter((v) => v !== optionValue)\n : [...currentValues, optionValue];\n onChange(newValues);\n } else {\n onChange(optionValue);\n setIsOpen(false);\n }\n };\n\n const handleRemove = (optionValue: string) => {\n if (multiple) {\n const newValues = currentValues.filter((v) => v !== optionValue);\n onChange(newValues);\n }\n };\n\n const getDisplayText = () => {\n if (currentValues.length === 0) {\n return (\n placeholder ?? t(\"table.filter.combobox.placeholder\", \"Select...\")\n );\n }\n\n if (multiple) {\n return `${currentValues.length} selected`;\n }\n\n const selectedOption = options.find(\n (option) => option.value === currentValues[0],\n );\n return selectedOption ? selectedOption.label : currentValues[0];\n };\n\n const getSelectedLabels = () => {\n return currentValues.map((val) => {\n const option = options.find((opt) => opt.value === val);\n return { label: option ? option.label : val, value: val };\n });\n };\n\n return (\n \n \n \n \n {multiple && currentValues.length > 0 ? (\n
\n {getSelectedLabels()\n .slice(0, 3)\n .map(({ label, value: val }) => (\n \n \n {label}\n \n {\n e.preventDefault();\n e.stopPropagation();\n handleRemove(val);\n }}\n >\n \n \n \n ))}\n {currentValues.length > 3 && (\n \n +{currentValues.length - 3} more\n \n )}\n
\n ) : (\n
\n {getDisplayText()}\n \n )}\n\n
\n
\n \n \n \n \n \n \n \n {noResultsText ??\n t(\n \"table.filter.combobox.noResults\",\n \"Results not found.\",\n )}\n \n \n {options.map((option) => (\n handleSelect(option.value)}\n keywords={option.label?.split(\" \") ?? []}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n );\n }}\n />\n );\n}\n\nexport type DataTableFilterDropdownDateSinglePickerProps = {\n column: Column;\n defaultOperator?: CrudOperators;\n formatDate?: (date: Date | undefined) => string | undefined;\n};\n\nexport function DataTableFilterDropdownDateSinglePicker({\n column,\n defaultOperator = \"eq\",\n formatDate,\n}: DataTableFilterDropdownDateSinglePickerProps) {\n const columnFilterValue = column.getFilterValue() as string;\n\n const parseDate = (value: string | undefined): Date | undefined => {\n if (!value) return undefined;\n\n const date = new Date(value);\n\n if (Number.isNaN(date.getTime())) return undefined;\n return date;\n };\n\n const [filterValue, setFilterValue] = useState(() =>\n parseDate(columnFilterValue),\n );\n\n useEffect(() => {\n column.columnDef.meta = {\n ...column.columnDef.meta,\n filterOperator: defaultOperator,\n };\n }, [defaultOperator, column]);\n\n useEffect(() => {\n setFilterValue(parseDate(columnFilterValue));\n }, [columnFilterValue]);\n\n const hasDate = !!filterValue;\n\n const handleApply = () => {\n if (!filterValue) return;\n\n const value = formatDate?.(filterValue) ?? filterValue.toISOString();\n column.setFilterValue(value);\n };\n\n return (\n \n {({ setIsOpen }) => {\n return (\n {\n if (!hasDate) return;\n if (event.key === \"Enter\") {\n handleApply();\n setIsOpen(false);\n }\n }}\n >\n
{\n setFilterValue(date);\n }}\n />\n\n \n \n
\n\n {\n column.setFilterValue(undefined);\n setFilterValue(undefined);\n setIsOpen(false);\n }}\n onApply={() => {\n handleApply();\n setIsOpen(false);\n }}\n />\n \n );\n }}\n \n );\n}\n\nexport type DataTableFilterDropdownDateRangePickerProps = {\n column: Column;\n defaultOperator?: CrudOperators;\n formatDateRange?: (dateRange: DateRange | undefined) => string[] | undefined;\n};\n\nexport function DataTableFilterDropdownDateRangePicker({\n column,\n defaultOperator = \"between\",\n formatDateRange,\n}: DataTableFilterDropdownDateRangePickerProps) {\n const columnFilterValue = column.getFilterValue() as string[];\n\n const parseDateRange = (\n value: string[] | undefined,\n ): DateRange | undefined => {\n if (!value || !Array.isArray(value) || value.length !== 2) return undefined;\n\n const from = value[0] ? new Date(value[0]) : undefined;\n const to = value[1] ? new Date(value[1]) : undefined;\n\n if (\n !from ||\n !to ||\n Number.isNaN(from.getTime()) ||\n Number.isNaN(to.getTime())\n )\n return undefined;\n return { from, to };\n };\n\n const [filterValue, setFilterValue] = useState(() =>\n parseDateRange(columnFilterValue),\n );\n\n useEffect(() => {\n column.columnDef.meta = {\n ...column.columnDef.meta,\n filterOperator: defaultOperator,\n };\n }, [defaultOperator, column]);\n\n useEffect(() => {\n setFilterValue(parseDateRange(columnFilterValue));\n // eslint-disable-next-line react-hooks/exhaustive-deps -- objects are always different\n }, [JSON.stringify(columnFilterValue)]);\n\n const hasDateRange = filterValue?.from && filterValue?.to;\n\n const handleApply = () => {\n if (!filterValue?.from || !filterValue?.to) return;\n\n const values = formatDateRange?.(filterValue) ?? [\n filterValue.from.toISOString(),\n filterValue.to.toISOString(),\n ];\n column.setFilterValue(values);\n };\n\n return (\n \n {({ setIsOpen }) => {\n return (\n {\n if (!hasDateRange) return;\n if (event.key === \"Enter\") {\n handleApply();\n setIsOpen(false);\n }\n }}\n >\n
{\n setFilterValue({\n from: date?.from,\n to: date?.to,\n });\n }}\n />\n\n \n \n
\n\n {\n column.setFilterValue(undefined);\n setFilterValue(undefined);\n setIsOpen(false);\n }}\n onApply={() => {\n handleApply();\n setIsOpen(false);\n }}\n />\n \n );\n }}\n \n );\n}\n\nexport type DataTableFilterInputProps = {\n column: Column;\n table?: ReactTable;\n defaultOperator?: CrudOperators;\n operators?: CrudOperators[];\n renderInput: (props: {\n value: string | string[];\n onChange: (value: string | string[]) => void;\n }) => React.ReactNode;\n};\n\nexport function DataTableFilterInput({\n column: columnFromProps,\n table: tableFromProps,\n operators: operatorsFromProps,\n defaultOperator: defaultOperatorFromProps,\n renderInput,\n}: DataTableFilterInputProps) {\n const [filterValue, setFilterValue] = useState(\n (columnFromProps.getFilterValue() as string | string[]) || \"\",\n );\n\n const [operator, setOperator] = useState(() => {\n if (!tableFromProps) {\n return defaultOperatorFromProps || \"eq\";\n }\n\n const columnFilter = tableFromProps\n .getState()\n .columnFilters.find((filter) => {\n return filter.id === columnFromProps.id;\n });\n\n if (columnFilter && \"operator\" in columnFilter) {\n return columnFilter.operator as CrudOperators;\n }\n\n return defaultOperatorFromProps || \"eq\";\n });\n\n const handleApply = () => {\n columnFromProps.setFilterValue(filterValue);\n };\n\n const handleClear = () => {\n columnFromProps.setFilterValue(undefined);\n setFilterValue(\"\");\n };\n\n const handleOperatorChange = (value: CrudOperators) => {\n setOperator(value);\n columnFromProps.columnDef.meta = {\n ...columnFromProps.columnDef.meta,\n filterOperator: value,\n };\n };\n\n return (\n \n {({ setIsOpen }) => {\n return (\n {\n if (event.key === \"Enter\") {\n handleApply();\n setIsOpen(false);\n }\n }}\n >\n
\n {operatorsFromProps && operatorsFromProps.length > 1 && (\n \n )}\n {renderInput({\n value: filterValue,\n onChange: setFilterValue,\n })}\n
\n
\n \n
\n
{\n handleClear();\n setIsOpen(false);\n }}\n onApply={() => {\n handleApply();\n setIsOpen(false);\n }}\n />\n \n );\n }}\n \n );\n}\n\nconst CRUD_OPERATOR_LABELS: Record<\n Exclude,\n { i18nKey: string; defaultLabel: string }\n> = {\n eq: { i18nKey: \"table.filter.operator.eq\", defaultLabel: \"Equals\" },\n ne: { i18nKey: \"table.filter.operator.ne\", defaultLabel: \"Not equals\" },\n lt: { i18nKey: \"table.filter.operator.lt\", defaultLabel: \"Less than\" },\n gt: { i18nKey: \"table.filter.operator.gt\", defaultLabel: \"Greater than\" },\n lte: {\n i18nKey: \"table.filter.operator.lte\",\n defaultLabel: \"Less than or equal\",\n },\n gte: {\n i18nKey: \"table.filter.operator.gte\",\n defaultLabel: \"Greater than or equal\",\n },\n in: {\n i18nKey: \"table.filter.operator.in\",\n defaultLabel: \"Includes in an array\",\n },\n nin: {\n i18nKey: \"table.filter.operator.nin\",\n defaultLabel: \"Not includes in an array\",\n },\n ina: {\n i18nKey: \"table.filter.operator.ina\",\n defaultLabel: \"Includes in an array (case sensitive)\",\n },\n nina: {\n i18nKey: \"table.filter.operator.nina\",\n defaultLabel: \"Not includes in an array (case sensitive)\",\n },\n contains: {\n i18nKey: \"table.filter.operator.contains\",\n defaultLabel: \"Contains\",\n },\n ncontains: {\n i18nKey: \"table.filter.operator.ncontains\",\n defaultLabel: \"Not contains\",\n },\n containss: {\n i18nKey: \"table.filter.operator.containss\",\n defaultLabel: \"Contains (case sensitive)\",\n },\n ncontainss: {\n i18nKey: \"table.filter.operator.ncontainss\",\n defaultLabel: \"Not contains (case sensitive)\",\n },\n between: {\n i18nKey: \"table.filter.operator.between\",\n defaultLabel: \"Between\",\n },\n nbetween: {\n i18nKey: \"table.filter.operator.nbetween\",\n defaultLabel: \"Not between\",\n },\n null: { i18nKey: \"table.filter.operator.null\", defaultLabel: \"Is null\" },\n nnull: {\n i18nKey: \"table.filter.operator.nnull\",\n defaultLabel: \"Is not null\",\n },\n startswith: {\n i18nKey: \"table.filter.operator.startswith\",\n defaultLabel: \"Starts with\",\n },\n nstartswith: {\n i18nKey: \"table.filter.operator.nstartswith\",\n defaultLabel: \"Not starts with\",\n },\n startswiths: {\n i18nKey: \"table.filter.operator.startswiths\",\n defaultLabel: \"Starts with (case sensitive)\",\n },\n nstartswiths: {\n i18nKey: \"table.filter.operator.nstartswiths\",\n defaultLabel: \"Not starts with (case sensitive)\",\n },\n endswith: {\n i18nKey: \"table.filter.operator.endswith\",\n defaultLabel: \"Ends with\",\n },\n nendswith: {\n i18nKey: \"table.filter.operator.nendswith\",\n defaultLabel: \"Not ends with\",\n },\n endswiths: {\n i18nKey: \"table.filter.operator.endswiths\",\n defaultLabel: \"Ends with (case sensitive)\",\n },\n nendswiths: {\n i18nKey: \"table.filter.operator.nendswiths\",\n defaultLabel: \"Not ends with (case sensitive)\",\n },\n};\n\nexport type DataTableFilterOperatorSelectProps = {\n value: CrudOperators;\n onValueChange: (value: CrudOperators) => void;\n operators?: CrudOperators[];\n placeholder?: string;\n triggerClassName?: string;\n contentClassName?: string;\n};\n\nexport function DataTableFilterOperatorSelect({\n value,\n onValueChange,\n operators: operatorsFromProps,\n placeholder,\n triggerClassName,\n contentClassName,\n}: DataTableFilterOperatorSelectProps) {\n const t = useTranslate();\n\n const [open, setOpen] = useState(false);\n\n const operators = useMemo(() => {\n return Object.entries(CRUD_OPERATOR_LABELS).filter(([operator]) =>\n operatorsFromProps?.includes(operator as CrudOperators),\n );\n }, [operatorsFromProps]);\n\n const selectedLabel = t(\n CRUD_OPERATOR_LABELS[value as Exclude].i18nKey,\n CRUD_OPERATOR_LABELS[value as Exclude]\n .defaultLabel,\n );\n const placeholderText =\n placeholder ?? t(\"table.filter.operator.placeholder\", \"Search operator...\");\n const noResultsText = t(\n \"table.filter.operator.noResults\",\n \"No operator found.\",\n );\n\n return (\n \n \n \n \n {selectedLabel ?? placeholderText}\n
\n \n \n \n \n \n \n \n {noResultsText} \n \n {operators.map(([op, { i18nKey, defaultLabel }]) => (\n {\n onValueChange(op as CrudOperators);\n setOpen(false);\n }}\n >\n \n {t(i18nKey, defaultLabel)}\n \n ))}\n \n \n \n \n \n );\n}\n\nDataTableFilterDropdown.displayName = \"DataTableFilterDropdown\";\nDataTableFilterDropdownText.displayName = \"DataTableFilterDropdownText\";\nDataTableFilterCombobox.displayName = \"DataTableFilterCombobox\";\nDataTableFilterDropdownDateRangePicker.displayName =\n \"DataTableFilterDropdownDateRangePicker\";\nDataTableFilterOperatorSelect.displayName = \"DataTableFilterOperatorSelect\";\nDataTableFilterDropdownActions.displayName = \"DataTableFilterDropdownActions\";\nDataTableFilterDropdownNumeric.displayName = \"DataTableFilterDropdownNumeric\";\nDataTableFilterInput.displayName = \"DataTableFilterInput\";\nDataTableFilterOperatorSelect.displayName = \"DataTableFilterOperatorSelect\";\nDataTableFilterDropdownDateSinglePicker.displayName =\n \"DataTableFilterDropdownDateSinglePicker\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/data-table/data-table-filter.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/data-table/data-table-sorter.tsx",
+ "content": "\"use client\";\n\nimport type { Column } from \"@tanstack/react-table\";\nimport { ArrowDown, ArrowUp, ChevronsUpDown } from \"lucide-react\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nexport type DataTableSorterProps = {\n column: Column;\n} & React.ComponentProps;\n\nexport function DataTableSorter({\n column,\n className,\n ...props\n}: DataTableSorterProps) {\n const title =\n column.getIsSorted() === \"desc\"\n ? `Sort by ${column.id} as descending`\n : column.getIsSorted() === \"asc\"\n ? `Sort by ${column.id} as ascending`\n : `Sort by ${column.id}`;\n\n return (\n column.toggleSorting(undefined, true)}\n title={title}\n aria-label={title}\n {...props}\n className={cn(\"data-[state=open]:bg-accent\", \"w-5 h-5\", className)}\n >\n {column.getIsSorted() === \"desc\" ? (\n \n ) : column.getIsSorted() === \"asc\" ? (\n \n ) : (\n \n )}\n \n );\n}\n\nDataTableSorter.displayName = \"DataTableSorter\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/data-table/data-table-sorter.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/data-table/data-table-pagination.tsx",
+ "content": "\"use client\";\n\nimport {\n ChevronLeft,\n ChevronRight,\n ChevronsLeft,\n ChevronsRight,\n} from \"lucide-react\";\nimport { useMemo } from \"react\";\n\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/new-york/ui/select\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype DataTablePaginationProps = {\n currentPage: number;\n pageCount: number;\n setCurrentPage: (page: number) => void;\n pageSize: number;\n setPageSize: (size: number) => void;\n total?: number;\n};\n\nexport function DataTablePagination({\n currentPage,\n pageCount,\n setCurrentPage,\n pageSize,\n setPageSize,\n total,\n}: DataTablePaginationProps) {\n const pageSizeOptions = useMemo(() => {\n const baseOptions = [10, 20, 30, 40, 50];\n const optionsSet = new Set(baseOptions);\n\n if (!optionsSet.has(pageSize)) {\n optionsSet.add(pageSize);\n }\n\n return Array.from(optionsSet).sort((a, b) => a - b);\n }, [pageSize]);\n\n return (\n \n
\n {typeof total === \"number\" ? `${total} row(s)` : null}\n
\n
\n
\n Rows per page \n setPageSize(Number(v))}\n >\n \n \n \n \n {pageSizeOptions.map((size) => (\n \n {size}\n \n ))}\n \n \n
\n
\n
\n Page {currentPage} of {pageCount}\n
\n
\n setCurrentPage(1)}\n disabled={currentPage === 1}\n aria-label=\"Go to first page\"\n >\n \n \n setCurrentPage(currentPage - 1)}\n disabled={currentPage === 1}\n aria-label=\"Go to previous page\"\n >\n \n \n setCurrentPage(currentPage + 1)}\n disabled={currentPage === pageCount}\n aria-label=\"Go to next page\"\n >\n \n \n setCurrentPage(pageCount)}\n disabled={currentPage === pageCount}\n aria-label=\"Go to last page\"\n >\n \n \n
\n
\n
\n
\n );\n}\n\nDataTablePagination.displayName = \"DataTablePagination\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/data-table/data-table-pagination.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["data", "table", "pagination", "sorting", "filtering"]
+}
diff --git a/packages/refine-ui/public/r/error-component.json b/packages/refine-ui/public/r/error-component.json
new file mode 100644
index 0000000000000..46780e0798b14
--- /dev/null
+++ b/packages/refine-ui/public/r/error-component.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "error-component",
+ "type": "registry:component",
+ "title": "Error Component",
+ "author": "Refine ",
+ "description": "404 error page component for Refine applications with elegant SVG graphics and navigation back to home page.",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": ["button", "tooltip"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/layout/error-component.tsx",
+ "content": "import React, { useEffect, useState } from \"react\";\nimport { useGo, useResourceParams } from \"@refinedev/core\";\nimport { useTranslate } from \"@refinedev/core\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/registry/new-york/ui/tooltip\";\nimport { ChevronLeft, InfoIcon } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * When the app is navigated to a non-existent route, refine shows a default error page.\n * A custom error component can be used for this error page.\n *\n * @see {@link https://refine.dev/docs/packages/documentation/routers/} for more details.\n */\nexport function ErrorComponent() {\n const [errorMessage, setErrorMessage] = useState();\n\n const translate = useTranslate();\n const go = useGo();\n\n const { resource, action } = useResourceParams();\n\n useEffect(() => {\n if (resource && action) {\n setErrorMessage(\n translate(\n \"pages.error.info\",\n {\n action: action,\n resource: resource?.name,\n },\n `You may have forgotten to add the \"${action}\" component to \"${resource?.name}\" resource.`,\n ),\n );\n }\n }, [resource, action, translate]);\n\n return (\n \n
\n
\n
\n 404 text \n \n \n \n \n \n \n \n \n
\n\n
\n
\n {translate(\"pages.error.title\", \"Page not found.\")}\n \n\n
\n
\n {translate(\n \"pages.error.description\",\n \"The page you're looking for does not exist.\",\n )}\n
\n {errorMessage && (\n
\n \n \n \n \n \n {errorMessage}
\n \n \n \n )}\n
\n
\n\n
{\n go({ to: \"/\" });\n }}\n className={cn(\"flex\", \"items-center\", \"gap-2\", \"mx-auto\")}\n >\n \n {translate(\"pages.error.backHome\", \"Back to hompeage\")}\n \n
\n
\n );\n}\n\nErrorComponent.displayName = \"ErrorComponent\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/error-component.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["error", "layout", "404", "pages"]
+}
diff --git a/packages/refine-ui/public/r/forgot-password-form.json b/packages/refine-ui/public/r/forgot-password-form.json
new file mode 100644
index 0000000000000..ba6de0e79e915
--- /dev/null
+++ b/packages/refine-ui/public/r/forgot-password-form.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "forgot-password-form",
+ "type": "registry:component",
+ "title": "Forgot Password Form",
+ "author": "Refine ",
+ "description": "A Forgot Password Form component for Refine to use on the password reset page",
+ "dependencies": ["@refinedev/core"],
+ "registryDependencies": ["button", "input", "label", "card"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/form/forgot-password-form.tsx",
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { ArrowLeft } from \"lucide-react\";\n\nimport { useForgotPassword, useRefineOptions, useLink } from \"@refinedev/core\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { Input } from \"@/registry/new-york/ui/input\";\nimport { Label } from \"@/registry/new-york/ui/label\";\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n CardDescription,\n} from \"@/registry/new-york/ui/card\";\nimport { cn } from \"@/lib/utils\";\n\nexport const ForgotPasswordForm = () => {\n const [email, setEmail] = useState(\"\");\n\n const Link = useLink();\n\n const { title } = useRefineOptions();\n\n const { mutate: forgotPassword } = useForgotPassword();\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault();\n\n forgotPassword({\n email,\n });\n };\n\n return (\n \n
\n {title.icon && (\n
svg]:w-12\", \"[&>svg]:h-12\")}\n >\n {title.icon}\n
\n )}\n
\n\n
\n \n \n Forgot password\n \n \n Enter your email to change your password.\n \n \n\n \n \n \n
Email \n
\n setEmail(e.target.value)}\n className={cn(\"flex-1\")}\n />\n \n Send\n \n
\n
\n \n\n \n \n \n
\n );\n};\n\nForgotPasswordForm.displayName = \"ForgotPasswordForm\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/forgot-password-form.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["auth", "form", "password", "reset"]
+}
diff --git a/packages/refine-ui/public/r/layout-01.json b/packages/refine-ui/public/r/layout-01.json
new file mode 100644
index 0000000000000..f91d6b0219d2c
--- /dev/null
+++ b/packages/refine-ui/public/r/layout-01.json
@@ -0,0 +1,52 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "layout-01",
+ "type": "registry:block",
+ "title": "Layout 01",
+ "author": "Refine ",
+ "description": "A complete layout system with sidebar, header, and main content area for Refine applications",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": [
+ "sidebar",
+ "avatar",
+ "button",
+ "separator",
+ "dropdown-menu",
+ "collapsible",
+ "https://ui.refine.dev/r/theme-provider.json"
+ ],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/layout/layout-01/layout.tsx",
+ "content": "\"use client\";\n\nimport type { PropsWithChildren } from \"react\";\nimport { SidebarProvider, SidebarInset } from \"@/registry/new-york/ui/sidebar\";\nimport { Sidebar } from \"@/registry/new-york/refine-ui/layout/layout-01/sidebar\";\nimport { Header } from \"@/registry/new-york/refine-ui/layout/layout-01/header\";\nimport { ThemeProvider } from \"@/registry/new-york/refine-ui/theme/theme-provider\";\nimport { cn } from \"@/lib/utils\";\n\nexport function Layout({ children }: PropsWithChildren) {\n return (\n \n \n \n \n \n \n {children}\n \n \n \n \n );\n}\n\nLayout.displayName = \"Layout\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/layout.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/layout/layout-01/header.tsx",
+ "content": "import {\n useRefineOptions,\n useActiveAuthProvider,\n useLogout,\n} from \"@refinedev/core\";\nimport {\n DropdownMenu,\n DropdownMenuItem,\n DropdownMenuContent,\n} from \"@/registry/new-york/ui/dropdown-menu\";\nimport { DropdownMenuTrigger } from \"@/registry/new-york/ui/dropdown-menu\";\nimport { ThemeToggle } from \"@/registry/new-york/refine-ui/theme/theme-toggle\";\nimport { UserAvatar } from \"@/registry/new-york/refine-ui/user/user-avatar\";\nimport { useSidebar, SidebarTrigger } from \"@/registry/new-york/ui/sidebar\";\nimport { LogOutIcon } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport const Header = () => {\n const { isMobile } = useSidebar();\n\n return <>{isMobile ? : }>;\n};\n\nfunction DesktopHeader() {\n return (\n \n );\n}\n\nfunction MobileHeader() {\n const { open, isMobile } = useSidebar();\n\n const { title } = useRefineOptions();\n\n return (\n \n \n\n \n
{title.icon}
\n
\n {title.text}\n \n
\n\n \n \n );\n}\n\nconst UserDropdown = () => {\n const { mutate: logout, isPending: isLoggingOut } = useLogout();\n\n const authProvider = useActiveAuthProvider();\n\n if (!authProvider?.getIdentity) {\n return null;\n }\n\n return (\n \n \n \n \n \n {\n logout();\n }}\n >\n \n \n {isLoggingOut ? \"Logging out...\" : \"Logout\"}\n \n \n \n \n );\n};\n\nHeader.displayName = \"Header\";\nMobileHeader.displayName = \"MobileHeader\";\nDesktopHeader.displayName = \"DesktopHeader\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/header.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/layout/layout-01/sidebar.tsx",
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport {\n useMenu,\n useLink,\n useRefineOptions,\n type TreeMenuItem,\n} from \"@refinedev/core\";\nimport {\n SidebarRail as ShadcnSidebarRail,\n Sidebar as ShadcnSidebar,\n SidebarContent as ShadcnSidebarContent,\n SidebarHeader as ShadcnSidebarHeader,\n useSidebar as useShadcnSidebar,\n SidebarTrigger as ShadcnSidebarTrigger,\n} from \"@/registry/new-york/ui/sidebar\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/registry/new-york/ui/dropdown-menu\";\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@/registry/new-york/ui/collapsible\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { ChevronRight, ListIcon } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport function Sidebar() {\n const { open } = useShadcnSidebar();\n const { menuItems, selectedKey } = useMenu();\n\n return (\n \n \n \n \n {menuItems.map((item: TreeMenuItem) => (\n \n ))}\n \n \n );\n}\n\ntype MenuItemProps = {\n item: TreeMenuItem;\n selectedKey?: string;\n};\n\nfunction SidebarItem({ item, selectedKey }: MenuItemProps) {\n const { open } = useShadcnSidebar();\n\n if (item.meta?.group) {\n return ;\n }\n\n if (item.children && item.children.length > 0) {\n if (open) {\n return ;\n }\n return ;\n }\n\n return ;\n}\n\nfunction SidebarItemGroup({ item, selectedKey }: MenuItemProps) {\n const { children } = item;\n const { open } = useShadcnSidebar();\n\n return (\n \n
\n {getDisplayName(item)}\n \n {children && children.length > 0 && (\n
\n {children.map((child: TreeMenuItem) => (\n \n ))}\n
\n )}\n
\n );\n}\n\nfunction SidebarItemCollapsible({ item, selectedKey }: MenuItemProps) {\n const { name, children } = item;\n\n const chevronIcon = (\n \n );\n\n return (\n \n \n \n \n \n {children?.map((child: TreeMenuItem) => (\n \n ))}\n \n \n );\n}\n\nfunction SidebarItemDropdown({ item, selectedKey }: MenuItemProps) {\n const { children } = item;\n const Link = useLink();\n\n return (\n \n \n \n \n \n {children?.map((child: TreeMenuItem) => {\n const { key: childKey } = child;\n const isSelected = childKey === selectedKey;\n\n return (\n \n \n \n {getDisplayName(child)} \n \n \n );\n })}\n \n \n );\n}\n\nfunction SidebarItemLink({ item, selectedKey }: MenuItemProps) {\n const isSelected = item.key === selectedKey;\n\n return ;\n}\n\nfunction SidebarHeader() {\n const { title } = useRefineOptions();\n const { open, isMobile } = useShadcnSidebar();\n\n return (\n \n \n
{title.icon}
\n
\n {title.text}\n \n
\n\n \n \n );\n}\n\nfunction getDisplayName(item: TreeMenuItem) {\n return item.meta?.label ?? item.label ?? item.name;\n}\n\ntype IconProps = {\n icon: React.ReactNode;\n isSelected?: boolean;\n};\n\nfunction ItemIcon({ icon, isSelected }: IconProps) {\n return (\n \n {icon ?? }\n
\n );\n}\n\ntype SidebarButtonProps = React.ComponentProps & {\n item: TreeMenuItem;\n isSelected?: boolean;\n rightIcon?: React.ReactNode;\n asLink?: boolean;\n onClick?: () => void;\n};\n\nfunction SidebarButton({\n item,\n isSelected = false,\n rightIcon,\n asLink = false,\n className,\n onClick,\n ...props\n}: SidebarButtonProps) {\n const Link = useLink();\n\n const buttonContent = (\n <>\n \n \n {getDisplayName(item)}\n \n {rightIcon}\n >\n );\n\n return (\n \n {asLink && item.route ? (\n \n {buttonContent}\n \n ) : (\n buttonContent\n )}\n \n );\n}\n\nSidebar.displayName = \"Sidebar\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/sidebar.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/user/user-avatar.tsx",
+ "content": "import { useGetIdentity } from \"@refinedev/core\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/new-york/ui/avatar\";\nimport { Skeleton } from \"@/registry/new-york/ui/skeleton\";\nimport { cn } from \"@/lib/utils\";\n\ntype User = {\n id: number;\n firstName: string;\n lastName: string;\n fullName: string;\n email: string;\n avatar?: string;\n};\n\nexport function UserAvatar() {\n const { data: user, isLoading: userIsLoading } = useGetIdentity();\n\n if (userIsLoading || !user) {\n return ;\n }\n\n const { fullName, avatar } = user;\n\n return (\n \n {avatar && }\n {getInitials(fullName)} \n \n );\n}\n\nconst getInitials = (name = \"\") => {\n const names = name.split(\" \");\n let initials = names[0].substring(0, 1).toUpperCase();\n\n if (names.length > 1) {\n initials += names[names.length - 1].substring(0, 1).toUpperCase();\n }\n return initials;\n};\n\nUserAvatar.displayName = \"UserAvatar\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/user-avatar.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/user/user-info.tsx",
+ "content": "import { useGetIdentity } from \"@refinedev/core\";\nimport { Skeleton } from \"@/registry/new-york/ui/skeleton\";\nimport { UserAvatar } from \"@/registry/new-york/refine-ui/user/user-avatar\";\nimport { cn } from \"@/lib/utils\";\n\ntype User = {\n id: number;\n firstName: string;\n lastName: string;\n fullName: string;\n email: string;\n avatar?: string;\n};\n\nexport function UserInfo() {\n const { data: user, isLoading: userIsLoading } = useGetIdentity();\n\n if (userIsLoading || !user) {\n return (\n \n );\n }\n\n const { firstName, lastName, email } = user;\n\n return (\n \n
\n
\n \n {firstName} {lastName}\n \n {email} \n
\n
\n );\n}\n\nUserInfo.displayName = \"UserInfo\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/user-info.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["layout", "sidebar", "navigation", "dashboard"]
+}
diff --git a/packages/refine-ui/public/r/loading-overlay.json b/packages/refine-ui/public/r/loading-overlay.json
new file mode 100644
index 0000000000000..6d14af2f020a9
--- /dev/null
+++ b/packages/refine-ui/public/r/loading-overlay.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "loading-overlay",
+ "type": "registry:component",
+ "title": "Loading Overlay",
+ "author": "Refine ",
+ "description": "A loading overlay component that displays a spinner over content while loading. Can be used to provide visual feedback during async operations.",
+ "dependencies": ["lucide-react"],
+ "registryDependencies": [],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/layout/loading-overlay.tsx",
+ "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Loader2 } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface LoadingOverlayProps extends React.HTMLAttributes {\n loading?: boolean;\n children: React.ReactNode;\n}\n\nexport const LoadingOverlay = React.forwardRef<\n HTMLDivElement,\n LoadingOverlayProps\n>(({ className, loading = false, children, ...props }, ref) => {\n if (!loading) return children;\n\n return (\n \n );\n});\n\nLoadingOverlay.displayName = \"LoadingOverlay\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/loading-overlay.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["loading", "overlay", "feedback", "ui"]
+}
diff --git a/packages/refine-ui/public/r/notification-provider.json b/packages/refine-ui/public/r/notification-provider.json
new file mode 100644
index 0000000000000..a0f45866c9718
--- /dev/null
+++ b/packages/refine-ui/public/r/notification-provider.json
@@ -0,0 +1,36 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "notification-provider",
+ "type": "registry:component",
+ "title": "Notification Provider",
+ "author": "Refine ",
+ "description": "A comprehensive notification system for Refine with toast notifications, undoable notifications, and provider hooks using sonner",
+ "dependencies": ["@refinedev/core", "sonner"],
+ "registryDependencies": [
+ "button",
+ "dropdown-menu",
+ "https://ui.refine.dev/r/theme-provider.json"
+ ],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/notification/use-notification-provider.tsx",
+ "content": "import type { NotificationProvider } from \"@refinedev/core\";\nimport { toast } from \"sonner\";\nimport { UndoableNotification } from \"@/registry/new-york/refine-ui/notification/undoable-notification\";\n\nexport function useNotificationProvider(): NotificationProvider {\n return {\n open: ({\n key,\n type,\n message,\n description,\n undoableTimeout,\n cancelMutation,\n }) => {\n switch (type) {\n case \"success\":\n toast.success(message, {\n id: key,\n description,\n richColors: true,\n });\n return;\n\n case \"error\":\n toast.error(message, {\n id: key,\n description,\n richColors: true,\n });\n return;\n\n case \"progress\": {\n const toastId = key || Date.now();\n\n toast(\n () => (\n toast.dismiss(toastId)}\n />\n ),\n {\n id: toastId,\n duration: (undoableTimeout || 5) * 1000,\n unstyled: true,\n },\n );\n return;\n }\n\n default:\n return;\n }\n },\n close: (id) => {\n toast.dismiss(id);\n },\n };\n}\n",
+ "type": "registry:hook",
+ "target": "src/components/refine-ui/notification/use-notification-provider.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/notification/toaster.tsx",
+ "content": "\"use client\";\n\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\";\nimport { useTheme } from \"@/registry/new-york/refine-ui/theme/theme-provider\";\n\nexport function Toaster({ ...props }: ToasterProps) {\n const { theme = \"system\" } = useTheme();\n\n return (\n \n );\n}\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/notification/toaster.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/notification/undoable-notification.tsx",
+ "content": "import React from \"react\";\nimport { useTranslate } from \"@refinedev/core\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype UndoableNotificationProps = {\n message: string;\n description?: string;\n undoableTimeout?: number;\n cancelMutation?: () => void;\n onClose?: () => void;\n};\n\nexport function UndoableNotification({\n message,\n description,\n undoableTimeout = 5,\n cancelMutation,\n onClose,\n}: UndoableNotificationProps) {\n const t = useTranslate();\n\n React.useEffect(() => {\n const timer = setTimeout(() => {\n onClose?.();\n }, undoableTimeout * 1000);\n\n return () => clearTimeout(timer);\n }, [onClose, undoableTimeout]);\n\n const handleUndo = () => {\n cancelMutation?.();\n onClose?.();\n };\n\n return (\n \n
\n
\n
\n {message}\n
\n {description && (\n
\n {description}\n
\n )}\n
\n
\n {t(\"buttons.undo\", \"Undo\")}\n \n
\n
\n );\n}\n\nUndoableNotification.displayName = \"UndoableNotification\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/notification/undoable-notification.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["providers", "notifications", "toast", "hooks", "undoable"]
+}
diff --git a/packages/refine-ui/public/r/sign-in-form.json b/packages/refine-ui/public/r/sign-in-form.json
new file mode 100644
index 0000000000000..785c0ab09d553
--- /dev/null
+++ b/packages/refine-ui/public/r/sign-in-form.json
@@ -0,0 +1,33 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "sign-in-form",
+ "type": "registry:component",
+ "title": "Sign In Form",
+ "author": "Refine ",
+ "description": "A Sign In Form component for Refine to use on the sign in page",
+ "dependencies": ["@refinedev/core"],
+ "registryDependencies": [
+ "button",
+ "input",
+ "label",
+ "card",
+ "checkbox",
+ "separator"
+ ],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/form/sign-in-form.tsx",
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\n\nimport { CircleHelp } from \"lucide-react\";\n\nimport { useLogin, useRefineOptions, useLink } from \"@refinedev/core\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { Input } from \"@/registry/new-york/ui/input\";\nimport { Label } from \"@/registry/new-york/ui/label\";\nimport { Checkbox } from \"@/registry/new-york/ui/checkbox\";\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n CardDescription,\n CardFooter,\n} from \"@/registry/new-york/ui/card\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\nimport { InputPassword } from \"@/registry/new-york/refine-ui/form/input-password\";\nimport { cn } from \"@/lib/utils\";\n\nexport const SignInForm = () => {\n const [rememberMe, setRememberMe] = useState(false);\n const [email, setEmail] = useState(\"\");\n const [password, setPassword] = useState(\"\");\n\n const Link = useLink();\n\n const { title } = useRefineOptions();\n\n const { mutate: login } = useLogin();\n\n const handleSignIn = async (e: React.FormEvent) => {\n e.preventDefault();\n\n login({\n email,\n password,\n });\n };\n\n const handleSignInWithGoogle = () => {\n login({\n providerName: \"google\",\n });\n };\n\n const handleSignInWithGitHub = () => {\n login({\n providerName: \"github\",\n });\n };\n\n return (\n \n
\n {title.icon && (\n
svg]:w-12\", \"[&>svg]:h-12\")}\n >\n {title.icon}\n
\n )}\n
\n\n
\n \n \n Sign in\n \n \n Welcome back\n \n \n\n \n\n \n \n \n Email \n setEmail(e.target.value)}\n />\n
\n \n Password \n setPassword(e.target.value)}\n required\n />\n
\n\n \n
\n \n setRememberMe(checked === \"indeterminate\" ? false : checked)\n }\n />\n Remember me \n
\n
\n
Forgot password \n
\n \n
\n\n \n Sign in\n \n\n \n \n or \n \n
\n\n \n
Sign in using
\n
\n
\n \n \n \n\n Google
\n \n
\n \n \n \n GitHub
\n \n
\n
\n \n \n\n \n\n \n \n \n No account?{\" \"}\n \n \n Sign up\n \n
\n \n \n
\n );\n};\n\nSignInForm.displayName = \"SignInForm\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/sign-in-form.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/form/input-password.tsx",
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Eye, EyeOff } from \"lucide-react\";\nimport { Input } from \"@/registry/new-york/ui/input\";\nimport { cn } from \"@/lib/utils\";\n\ntype InputPasswordProps = React.ComponentProps<\"input\">;\n\nexport const InputPassword = ({ className, ...props }: InputPasswordProps) => {\n const [showPassword, setShowPassword] = useState(false);\n\n return (\n \n \n setShowPassword(!showPassword)}\n >\n {showPassword ? (\n \n ) : (\n \n )}\n \n
\n );\n};\n\nInputPassword.displayName = \"InputPassword\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/input-password.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["auth", "form", "login", "sign-in"]
+}
diff --git a/packages/refine-ui/public/r/sign-up-form.json b/packages/refine-ui/public/r/sign-up-form.json
new file mode 100644
index 0000000000000..3ec10d7186aaa
--- /dev/null
+++ b/packages/refine-ui/public/r/sign-up-form.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "sign-up-form",
+ "type": "registry:component",
+ "title": "Sign Up Form",
+ "author": "Refine ",
+ "description": "A Sign Up Form component for Refine to use on the registration page",
+ "dependencies": ["@refinedev/core"],
+ "registryDependencies": ["button", "input", "label", "card", "separator"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/form/sign-up-form.tsx",
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\n\nimport {\n useRegister,\n useRefineOptions,\n useLink,\n useNotification,\n} from \"@refinedev/core\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { Input } from \"@/registry/new-york/ui/input\";\nimport { Label } from \"@/registry/new-york/ui/label\";\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n CardDescription,\n CardFooter,\n} from \"@/registry/new-york/ui/card\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\nimport { InputPassword } from \"@/registry/new-york/refine-ui/form/input-password\";\nimport { cn } from \"@/lib/utils\";\n\nexport const SignUpForm = () => {\n const [email, setEmail] = useState(\"\");\n const [password, setPassword] = useState(\"\");\n const [confirmPassword, setConfirmPassword] = useState(\"\");\n\n const { open } = useNotification();\n\n const Link = useLink();\n\n const { title } = useRefineOptions();\n\n const { mutate: register } = useRegister();\n\n const handleSignUp = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (password !== confirmPassword) {\n open?.({\n type: \"error\",\n message: \"Passwords don't match\",\n description:\n \"Please make sure both password fields contain the same value.\",\n });\n\n return;\n }\n\n register({\n email,\n password,\n });\n };\n\n const handleSignUpWithGoogle = () => {\n register({\n providerName: \"google\",\n });\n };\n\n const handleSignUpWithGitHub = () => {\n register({\n providerName: \"github\",\n });\n };\n\n return (\n \n
\n {title.icon && (\n
svg]:w-12\", \"[&>svg]:h-12\")}\n >\n {title.icon}\n
\n )}\n
\n\n
\n \n \n Sign up\n \n \n Welcome to lorem ipsum dolor.\n \n \n\n \n\n \n \n \n Email \n setEmail(e.target.value)}\n />\n
\n\n \n Password \n setPassword(e.target.value)}\n required\n />\n
\n\n \n Confirm password \n setConfirmPassword(e.target.value)}\n required\n />\n
\n\n \n Sign up\n \n\n \n \n or \n \n
\n\n \n
\n
\n \n \n \n Google
\n \n
\n \n \n \n GitHub
\n \n
\n
\n \n \n\n \n\n \n \n \n Have an account?{\" \"}\n \n \n Sign in\n \n
\n \n \n
\n );\n};\n\nSignUpForm.displayName = \"SignUpForm\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/sign-up-form.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/form/input-password.tsx",
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Eye, EyeOff } from \"lucide-react\";\nimport { Input } from \"@/registry/new-york/ui/input\";\nimport { cn } from \"@/lib/utils\";\n\ntype InputPasswordProps = React.ComponentProps<\"input\">;\n\nexport const InputPassword = ({ className, ...props }: InputPasswordProps) => {\n const [showPassword, setShowPassword] = useState(false);\n\n return (\n \n \n setShowPassword(!showPassword)}\n >\n {showPassword ? (\n \n ) : (\n \n )}\n \n
\n );\n};\n\nInputPassword.displayName = \"InputPassword\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/input-password.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["auth", "form", "register", "signup"]
+}
diff --git a/packages/refine-ui/public/r/theme-provider.json b/packages/refine-ui/public/r/theme-provider.json
new file mode 100644
index 0000000000000..7116d55fc0995
--- /dev/null
+++ b/packages/refine-ui/public/r/theme-provider.json
@@ -0,0 +1,32 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "theme-provider",
+ "type": "registry:component",
+ "title": "Theme Provider & Toggle & Select",
+ "author": "Refine ",
+ "description": "A complete theme system with provider, toggle, and select components. Supports dark, light, and system themes with localStorage persistence.",
+ "dependencies": ["lucide-react"],
+ "registryDependencies": ["button", "dropdown-menu"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/theme/theme-provider.tsx",
+ "content": "\"use client\";\n\nimport { createContext, useContext, useEffect, useState } from \"react\";\n\ntype Theme = \"dark\" | \"light\" | \"system\";\n\ntype ThemeProviderProps = {\n children: React.ReactNode;\n defaultTheme?: Theme;\n storageKey?: string;\n};\n\ntype ThemeProviderState = {\n theme: Theme;\n setTheme: (theme: Theme) => void;\n};\n\nconst initialState: ThemeProviderState = {\n theme: \"system\",\n setTheme: () => null,\n};\n\nconst ThemeProviderContext = createContext(initialState);\n\nexport function ThemeProvider({\n children,\n defaultTheme = \"system\",\n storageKey = \"refine-ui-theme\",\n ...props\n}: ThemeProviderProps) {\n const [theme, setTheme] = useState(\n () => (localStorage.getItem(storageKey) as Theme) || defaultTheme,\n );\n\n useEffect(() => {\n const root = window.document.documentElement;\n\n root.classList.remove(\"light\", \"dark\");\n\n if (theme === \"system\") {\n const systemTheme = window.matchMedia(\"(prefers-color-scheme: dark)\")\n .matches\n ? \"dark\"\n : \"light\";\n\n root.classList.add(systemTheme);\n return;\n }\n\n root.classList.add(theme);\n }, [theme]);\n\n const value = {\n theme,\n setTheme: (theme: Theme) => {\n localStorage.setItem(storageKey, theme);\n setTheme(theme);\n },\n };\n\n return (\n \n {children}\n \n );\n}\n\nexport function useTheme() {\n const context = useContext(ThemeProviderContext);\n\n if (context === undefined) {\n console.error(\"useTheme must be used within a ThemeProvider\");\n }\n\n return context;\n}\n\nThemeProvider.displayName = \"ThemeProvider\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/theme/theme-provider.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/theme/theme-toggle.tsx",
+ "content": "\"use client\";\n\nimport { useTheme } from \"@/registry/new-york/refine-ui/theme/theme-provider\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { Moon, Sun, Monitor } from \"lucide-react\";\n\ntype ThemeToggleProps = {\n className?: string;\n};\n\nexport function ThemeToggle({ className }: ThemeToggleProps) {\n const { theme, setTheme } = useTheme();\n\n const cycleTheme = () => {\n switch (theme) {\n case \"light\":\n setTheme(\"dark\");\n break;\n case \"dark\":\n setTheme(\"system\");\n break;\n case \"system\":\n setTheme(\"light\");\n break;\n default:\n setTheme(\"light\");\n }\n };\n\n return (\n \n \n \n \n Toggle theme (Light → Dark → System) \n \n );\n}\n\nThemeToggle.displayName = \"ThemeToggle\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/theme/theme-toggle.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/theme/theme-select.tsx",
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport { useTheme } from \"./theme-provider\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/registry/new-york/ui/dropdown-menu\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { Moon, Sun, Monitor, ChevronDown, Check } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ntype ThemeOption = {\n value: \"light\" | \"dark\" | \"system\";\n label: string;\n icon: React.ReactNode;\n};\n\nconst themeOptions: ThemeOption[] = [\n {\n value: \"light\",\n label: \"Light\",\n icon: ,\n },\n {\n value: \"dark\",\n label: \"Dark\",\n icon: ,\n },\n {\n value: \"system\",\n label: \"System\",\n icon: ,\n },\n];\n\nexport function ThemeSelect() {\n const { theme, setTheme } = useTheme();\n\n const currentTheme = themeOptions.find((option) => option.value === theme);\n\n return (\n \n \n \n \n {currentTheme?.icon}\n {currentTheme?.label} \n
\n \n \n \n \n {themeOptions.map((option) => {\n const isSelected = theme === option.value;\n\n return (\n setTheme(option.value)}\n className={cn(\n \"flex items-center gap-2 cursor-pointer relative pr-8\",\n {\n \"bg-accent text-accent-foreground\": isSelected,\n },\n )}\n >\n {option.icon}\n {option.label} \n {isSelected && (\n \n )}\n \n );\n })}\n \n \n );\n}\n\nThemeSelect.displayName = \"ThemeSelect\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/theme/theme-select.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["theme", "dark-mode", "toggle", "select", "provider"]
+}
diff --git a/packages/refine-ui/public/r/views.json b/packages/refine-ui/public/r/views.json
new file mode 100644
index 0000000000000..9890d56655ff2
--- /dev/null
+++ b/packages/refine-ui/public/r/views.json
@@ -0,0 +1,43 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "views",
+ "type": "registry:component",
+ "title": "Refine Views",
+ "author": "Refine ",
+ "description": "A comprehensive set of view components for Refine applications including create, edit, show, and list views with headers and content areas. These components are designed to be used as page templates for CRUD operations.",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": [
+ "separator",
+ "https://ui.refine.dev/r/buttons.json",
+ "https://ui.refine.dev/r/breadcrumb.json",
+ "https://ui.refine.dev/r/loading-overlay.json"
+ ],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/views/create-view.tsx",
+ "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n useBack,\n useResourceParams,\n useUserFriendlyName,\n} from \"@refinedev/core\";\nimport type { PropsWithChildren } from \"react\";\nimport { Breadcrumb } from \"@/registry/new-york/refine-ui/layout/breadcrumb\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { ArrowLeftIcon } from \"lucide-react\";\n\ntype CreateViewProps = PropsWithChildren<{\n className?: string;\n}>;\n\nexport function CreateView({ children, className }: CreateViewProps) {\n return (\n {children}
\n );\n}\n\ntype CreateHeaderProps = PropsWithChildren<{\n resource?: string;\n title?: string;\n wrapperClassName?: string;\n headerClassName?: string;\n}>;\n\nexport const CreateViewHeader = ({\n resource: resourceFromProps,\n title: titleFromProps,\n wrapperClassName,\n headerClassName,\n}: CreateHeaderProps) => {\n const back = useBack();\n\n const getUserFriendlyName = useUserFriendlyName();\n\n const { resource, identifier } = useResourceParams({\n resource: resourceFromProps,\n });\n\n const title =\n titleFromProps ??\n getUserFriendlyName(\n resource?.meta?.label ?? identifier ?? resource?.name,\n \"plural\",\n );\n\n return (\n \n );\n};\n\nCreateView.displayName = \"CreateView\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/views/create-view.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/views/edit-view.tsx",
+ "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n useBack,\n useResourceParams,\n useUserFriendlyName,\n} from \"@refinedev/core\";\nimport type { PropsWithChildren } from \"react\";\nimport { Breadcrumb } from \"@/registry/new-york/refine-ui/layout/breadcrumb\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { RefreshButton } from \"@/registry/new-york/refine-ui/buttons/refresh\";\nimport { ArrowLeftIcon } from \"lucide-react\";\n\ntype EditViewProps = PropsWithChildren<{\n className?: string;\n}>;\n\nexport function EditView({ children, className }: EditViewProps) {\n return (\n {children}
\n );\n}\n\ntype EditViewHeaderProps = PropsWithChildren<{\n resource?: string;\n title?: string;\n wrapperClassName?: string;\n headerClassName?: string;\n actionsSlot?: React.ReactNode;\n}>;\n\nexport const EditViewHeader = ({\n resource: resourceFromProps,\n title: titleFromProps,\n actionsSlot,\n wrapperClassName,\n headerClassName,\n}: EditViewHeaderProps) => {\n const back = useBack();\n\n const getUserFriendlyName = useUserFriendlyName();\n\n const { resource, identifier } = useResourceParams({\n resource: resourceFromProps,\n });\n const { id: recordItemId } = useResourceParams();\n\n const resourceName = resource?.name ?? identifier;\n\n const title =\n titleFromProps ??\n getUserFriendlyName(\n resource?.meta?.label ?? identifier ?? resource?.name,\n \"plural\",\n );\n\n return (\n \n
\n
\n
\n\n
\n {actionsSlot}\n \n
\n
\n
\n );\n};\n\nEditView.displayName = \"EditView\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/views/edit-view.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/views/show-view.tsx",
+ "content": "\"use client\";\n\nimport type { PropsWithChildren } from \"react\";\n\nimport { ArrowLeftIcon } from \"lucide-react\";\nimport {\n useBack,\n useResourceParams,\n useUserFriendlyName,\n} from \"@refinedev/core\";\nimport { Breadcrumb } from \"@/registry/new-york/refine-ui/layout/breadcrumb\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { RefreshButton } from \"@/registry/new-york/refine-ui/buttons/refresh\";\nimport { cn } from \"@/lib/utils\";\nimport { EditButton } from \"../buttons/edit\";\n\ntype ShowViewProps = PropsWithChildren<{\n className?: string;\n}>;\n\nexport function ShowView({ children, className }: ShowViewProps) {\n return (\n {children}
\n );\n}\n\ntype ShowViewHeaderProps = PropsWithChildren<{\n resource?: string;\n title?: string;\n wrapperClassName?: string;\n headerClassName?: string;\n}>;\n\nexport const ShowViewHeader = ({\n resource: resourceFromProps,\n title: titleFromProps,\n wrapperClassName,\n headerClassName,\n}: ShowViewHeaderProps) => {\n const back = useBack();\n\n const getUserFriendlyName = useUserFriendlyName();\n\n const { resource, identifier } = useResourceParams({\n resource: resourceFromProps,\n });\n const { id: recordItemId } = useResourceParams();\n\n const resourceName = resource?.name ?? identifier;\n\n const title =\n titleFromProps ??\n getUserFriendlyName(\n resource?.meta?.label ?? identifier ?? resource?.name,\n \"singular\",\n );\n\n return (\n \n );\n};\n\nShowView.displayName = \"ShowView\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/views/show-view.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/views/list-view.tsx",
+ "content": "\"use client\";\n\nimport type { PropsWithChildren } from \"react\";\n\nimport { useResourceParams, useUserFriendlyName } from \"@refinedev/core\";\nimport { Breadcrumb } from \"@/registry/new-york/refine-ui/layout/breadcrumb\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\nimport { CreateButton } from \"@/registry/new-york/refine-ui/buttons/create\";\nimport { cn } from \"@/lib/utils\";\n\ntype ListViewProps = PropsWithChildren<{\n className?: string;\n}>;\n\nexport function ListView({ children, className }: ListViewProps) {\n return (\n {children}
\n );\n}\n\ntype ListHeaderProps = PropsWithChildren<{\n resource?: string;\n title?: string;\n canCreate?: boolean;\n headerClassName?: string;\n wrapperClassName?: string;\n}>;\n\nexport const ListViewHeader = ({\n canCreate,\n resource: resourceFromProps,\n title: titleFromProps,\n wrapperClassName,\n headerClassName,\n}: ListHeaderProps) => {\n const getUserFriendlyName = useUserFriendlyName();\n\n const { resource, identifier } = useResourceParams({\n resource: resourceFromProps,\n });\n const resourceName = identifier ?? resource?.name;\n\n const isCreateButtonVisible = canCreate ?? !!resource?.create;\n\n const title =\n titleFromProps ??\n getUserFriendlyName(\n resource?.meta?.label ?? identifier ?? resource?.name,\n \"plural\",\n );\n\n return (\n \n
\n
\n
{title} \n {isCreateButtonVisible && (\n
\n \n
\n )}\n
\n
\n );\n};\n\nListView.displayName = \"ListView\";\n",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/views/list-view.tsx"
+ }
+ ],
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["views", "layout", "crud", "pages"]
+}
diff --git a/packages/refine-ui/public/vercel.svg b/packages/refine-ui/public/vercel.svg
new file mode 100644
index 0000000000000..77053960334e2
--- /dev/null
+++ b/packages/refine-ui/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/refine-ui/public/window.svg b/packages/refine-ui/public/window.svg
new file mode 100644
index 0000000000000..b2b2a44f6ebc7
--- /dev/null
+++ b/packages/refine-ui/public/window.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/refine-ui/registry.json b/packages/refine-ui/registry.json
new file mode 100644
index 0000000000000..bea91a8cca16b
--- /dev/null
+++ b/packages/refine-ui/registry.json
@@ -0,0 +1,397 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry.json",
+ "name": "Refine",
+ "homepage": "https://refine.dev",
+ "items": [
+ {
+ "name": "sign-in-form",
+ "type": "registry:component",
+ "title": "Sign In Form",
+ "description": "A Sign In Form component for Refine to use on the sign in page",
+ "dependencies": ["@refinedev/core"],
+ "registryDependencies": [
+ "button",
+ "input",
+ "label",
+ "card",
+ "checkbox",
+ "separator"
+ ],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["auth", "form", "login", "sign-in"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/form/sign-in-form.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/sign-in-form.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/form/input-password.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/input-password.tsx"
+ }
+ ]
+ },
+ {
+ "name": "sign-up-form",
+ "type": "registry:component",
+ "title": "Sign Up Form",
+ "description": "A Sign Up Form component for Refine to use on the registration page",
+ "dependencies": ["@refinedev/core"],
+ "registryDependencies": ["button", "input", "label", "card", "separator"],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["auth", "form", "register", "signup"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/form/sign-up-form.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/sign-up-form.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/form/input-password.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/input-password.tsx"
+ }
+ ]
+ },
+ {
+ "name": "forgot-password-form",
+ "type": "registry:component",
+ "title": "Forgot Password Form",
+ "description": "A Forgot Password Form component for Refine to use on the password reset page",
+ "dependencies": ["@refinedev/core"],
+ "registryDependencies": ["button", "input", "label", "card"],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["auth", "form", "password", "reset"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/form/forgot-password-form.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/forgot-password-form.tsx"
+ }
+ ]
+ },
+ {
+ "name": "notification-provider",
+ "type": "registry:component",
+ "title": "Notification Provider",
+ "description": "A comprehensive notification system for Refine with toast notifications, undoable notifications, and provider hooks using sonner",
+ "dependencies": ["@refinedev/core", "sonner"],
+ "registryDependencies": [
+ "button",
+ "dropdown-menu",
+ "https://ui.refine.dev/r/theme-provider.json"
+ ],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": [
+ "providers",
+ "notifications",
+ "toast",
+ "hooks",
+ "undoable"
+ ],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/notification/use-notification-provider.tsx",
+ "type": "registry:hook",
+ "target": "src/components/refine-ui/notification/use-notification-provider.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/notification/toaster.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/notification/toaster.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/notification/undoable-notification.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/notification/undoable-notification.tsx"
+ }
+ ]
+ },
+ {
+ "name": "layout-01",
+ "type": "registry:block",
+ "title": "Layout 01",
+ "description": "A complete layout system with sidebar, header, and main content area for Refine applications",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": [
+ "sidebar",
+ "avatar",
+ "button",
+ "separator",
+ "dropdown-menu",
+ "collapsible",
+ "https://ui.refine.dev/r/theme-provider.json"
+ ],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["layout", "sidebar", "navigation", "dashboard"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/layout/layout-01/layout.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/layout.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/layout/layout-01/header.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/header.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/layout/layout-01/sidebar.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/sidebar.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/user/user-avatar.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/user-avatar.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/user/user-info.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/user-info.tsx"
+ }
+ ]
+ },
+ {
+ "name": "data-table",
+ "type": "registry:component",
+ "title": "Data Table",
+ "description": "A comprehensive data table component for Refine with sorting, filtering, pagination, and advanced features",
+ "dependencies": [
+ "@refinedev/core",
+ "@refinedev/react-table",
+ "@tanstack/react-table",
+ "react-day-picker",
+ "lucide-react"
+ ],
+ "registryDependencies": [
+ "table",
+ "button",
+ "input",
+ "badge",
+ "popover",
+ "command",
+ "separator",
+ "calendar",
+ "select"
+ ],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["data", "table", "pagination", "sorting", "filtering"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/data-table/data-table.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/data-table/data-table.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/data-table/data-table-filter.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/data-table/data-table-filter.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/data-table/data-table-sorter.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/data-table/data-table-sorter.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/data-table/data-table-pagination.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/data-table/data-table-pagination.tsx"
+ }
+ ]
+ },
+ {
+ "name": "buttons",
+ "type": "registry:component",
+ "title": "Refine Buttons",
+ "description": "A comprehensive set of action buttons for Refine applications including create, edit, delete, show, clone, list, and refresh buttons",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": ["button", "popover"],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["buttons", "actions", "crud", "navigation"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/buttons/create.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/create.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/edit.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/edit.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/show.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/show.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/delete.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/delete.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/clone.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/clone.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/list.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/list.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/buttons/refresh.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/buttons/refresh.tsx"
+ }
+ ]
+ },
+ {
+ "name": "views",
+ "type": "registry:component",
+ "title": "Refine Views",
+ "description": "A comprehensive set of view components for Refine applications including create, edit, show, and list views with headers and content areas. These components are designed to be used as page templates for CRUD operations.",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": [
+ "separator",
+ "https://ui.refine.dev/r/buttons.json",
+ "https://ui.refine.dev/r/breadcrumb.json",
+ "https://ui.refine.dev/r/loading-overlay.json"
+ ],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["views", "layout", "crud", "pages"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/views/create-view.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/views/create-view.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/views/edit-view.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/views/edit-view.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/views/show-view.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/views/show-view.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/views/list-view.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/views/list-view.tsx"
+ }
+ ]
+ },
+ {
+ "name": "breadcrumb",
+ "type": "registry:component",
+ "title": "Refine Breadcrumb",
+ "description": "A breadcrumb navigation component for Refine applications that automatically generates breadcrumbs based on the current route and resource structure.",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": ["breadcrumb"],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["navigation", "breadcrumb", "layout"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/layout/breadcrumb.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/breadcrumb.tsx"
+ }
+ ]
+ },
+ {
+ "name": "loading-overlay",
+ "type": "registry:component",
+ "title": "Loading Overlay",
+ "description": "A loading overlay component that displays a spinner over content while loading. Can be used to provide visual feedback during async operations.",
+ "dependencies": ["lucide-react"],
+ "registryDependencies": [],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["loading", "overlay", "feedback", "ui"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/layout/loading-overlay.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/loading-overlay.tsx"
+ }
+ ]
+ },
+ {
+ "name": "theme-provider",
+ "type": "registry:component",
+ "title": "Theme Provider & Toggle & Select",
+ "description": "A complete theme system with provider, toggle, and select components. Supports dark, light, and system themes with localStorage persistence.",
+ "dependencies": ["lucide-react"],
+ "registryDependencies": ["button", "dropdown-menu"],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["theme", "dark-mode", "toggle", "select", "provider"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/theme/theme-provider.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/theme/theme-provider.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/theme/theme-toggle.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/theme/theme-toggle.tsx"
+ },
+ {
+ "path": "registry/new-york/refine-ui/theme/theme-select.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/theme/theme-select.tsx"
+ }
+ ]
+ },
+ {
+ "name": "error-component",
+ "type": "registry:component",
+ "title": "Error Component",
+ "description": "404 error page component for Refine applications with elegant SVG graphics and navigation back to home page.",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": ["button", "tooltip"],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["error", "layout", "404", "pages"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/layout/error-component.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/layout/error-component.tsx"
+ }
+ ]
+ },
+ {
+ "name": "auto-save-indicator",
+ "type": "registry:component",
+ "title": "Auto Save Indicator",
+ "description": "A visual indicator component for auto-save functionality in Refine forms. Shows loading, success, error, and idle states with smooth transitions and customizable elements.",
+ "dependencies": ["@refinedev/core", "lucide-react"],
+ "registryDependencies": [],
+ "author": "Refine ",
+ "docs": "https://github.com/refinedev/refine",
+ "categories": ["form", "auto-save", "indicator", "feedback", "ui"],
+ "files": [
+ {
+ "path": "registry/new-york/refine-ui/form/auto-save-indicator.tsx",
+ "type": "registry:component",
+ "target": "src/components/refine-ui/form/auto-save-indicator.tsx"
+ }
+ ]
+ }
+ ]
+}
diff --git a/packages/refine-ui/registry/new-york/refine-ui/buttons/clone.tsx b/packages/refine-ui/registry/new-york/refine-ui/buttons/clone.tsx
new file mode 100644
index 0000000000000..63507b8233e16
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/buttons/clone.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+import React from "react";
+import { type BaseKey, useCloneButton } from "@refinedev/core";
+import { Copy } from "lucide-react";
+import { Button } from "@/registry/new-york/ui/button";
+
+type CloneButtonProps = {
+ /**
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
+ * @default Inferred resource name from the route
+ */
+ resource?: string;
+ /**
+ * Data item identifier for the actions with the API
+ * @default Reads `:id` from the URL
+ */
+ recordItemId?: BaseKey;
+ /**
+ * Access Control configuration for the button
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
+ */
+ accessControl?: {
+ enabled?: boolean;
+ hideIfUnauthorized?: boolean;
+ };
+ /**
+ * `meta` property is used when creating the URL for the related action and path.
+ */
+ meta?: Record;
+} & React.ComponentProps;
+
+export const CloneButton = React.forwardRef<
+ React.ComponentRef,
+ CloneButtonProps
+>(
+ (
+ { resource, recordItemId, accessControl, meta, children, onClick, ...rest },
+ ref,
+ ) => {
+ const { hidden, disabled, LinkComponent, to, label } = useCloneButton({
+ accessControl,
+ resource,
+ id: recordItemId,
+ meta,
+ });
+
+ const isDisabled = disabled || rest.disabled;
+ const isHidden = hidden || rest.hidden;
+
+ if (isHidden) return null;
+
+ return (
+
+ ) => {
+ if (isDisabled) {
+ e.preventDefault();
+ return;
+ }
+ if (onClick) {
+ e.preventDefault();
+ onClick(e);
+ }
+ }}
+ >
+ {children ?? (
+
+
+ {label}
+
+ )}
+
+
+ );
+ },
+);
+
+CloneButton.displayName = "CloneButton";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/buttons/create.tsx b/packages/refine-ui/registry/new-york/refine-ui/buttons/create.tsx
new file mode 100644
index 0000000000000..c28f613043a1c
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/buttons/create.tsx
@@ -0,0 +1,70 @@
+"use client";
+
+import React from "react";
+import { type BaseKey, useCreateButton } from "@refinedev/core";
+import { Plus } from "lucide-react";
+import { Button } from "@/registry/new-york/ui/button";
+
+type CreateButtonProps = {
+ /**
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
+ * @default Inferred resource name from the route
+ */
+ resource?: BaseKey;
+ /**
+ * Access Control configuration for the button
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
+ */
+ accessControl?: {
+ enabled?: boolean;
+ hideIfUnauthorized?: boolean;
+ };
+ /**
+ * `meta` property is used when creating the URL for the related action and path.
+ */
+ meta?: Record;
+} & React.ComponentProps;
+
+export const CreateButton = React.forwardRef<
+ React.ComponentRef,
+ CreateButtonProps
+>(({ resource, accessControl, meta, children, onClick, ...rest }, ref) => {
+ const { hidden, disabled, LinkComponent, to, label } = useCreateButton({
+ resource,
+ accessControl,
+ meta,
+ });
+
+ const isDisabled = disabled || rest.disabled;
+ const isHidden = hidden || rest.hidden;
+
+ if (isHidden) return null;
+
+ return (
+
+ ) => {
+ if (isDisabled) {
+ e.preventDefault();
+ return;
+ }
+ if (onClick) {
+ e.preventDefault();
+ onClick(e);
+ }
+ }}
+ >
+ {children ?? (
+
+
+
{label ?? "Create"}
+
+ )}
+
+
+ );
+});
+
+CreateButton.displayName = "CreateButton";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/buttons/delete.tsx b/packages/refine-ui/registry/new-york/refine-ui/buttons/delete.tsx
new file mode 100644
index 0000000000000..d464a8bc41fef
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/buttons/delete.tsx
@@ -0,0 +1,115 @@
+"use client";
+
+import React from "react";
+import { type BaseKey, useDeleteButton } from "@refinedev/core";
+import { Loader2, Trash } from "lucide-react";
+import { Button } from "@/registry/new-york/ui/button";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/registry/new-york/ui/popover";
+
+type DeleteButtonProps = {
+ /**
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
+ * @default Inferred resource name from the route
+ */
+ resource?: string;
+ /**
+ * Data item identifier for the actions with the API
+ * @default Reads `:id` from the URL
+ */
+ recordItemId?: BaseKey;
+ /**
+ * Access Control configuration for the button
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
+ */
+ accessControl?: {
+ enabled?: boolean;
+ hideIfUnauthorized?: boolean;
+ };
+ /**
+ * `meta` property is used when creating the URL for the related action and path.
+ */
+ meta?: Record;
+} & React.ComponentProps;
+
+export const DeleteButton = React.forwardRef<
+ React.ComponentRef,
+ DeleteButtonProps
+>(({ resource, recordItemId, accessControl, meta, children, ...rest }, ref) => {
+ const {
+ hidden,
+ disabled,
+ loading,
+ onConfirm,
+ label,
+ confirmTitle: defaultConfirmTitle,
+ confirmOkLabel: defaultConfirmOkLabel,
+ cancelLabel: defaultCancelLabel,
+ } = useDeleteButton({
+ resource,
+ id: recordItemId,
+ accessControl,
+ meta,
+ });
+ const [open, setOpen] = React.useState(false);
+
+ const isDisabled = disabled || rest.disabled || loading;
+ const isHidden = hidden || rest.hidden;
+
+ if (isHidden) return null;
+
+ const confirmCancelText = defaultCancelLabel;
+ const confirmOkText = defaultConfirmOkLabel;
+ const confirmTitle = defaultConfirmTitle;
+
+ return (
+
+
+
+
+ {loading && }
+ {children ?? (
+
+
+ {label}
+
+ )}
+
+
+
+
+
+
{confirmTitle}
+
+ setOpen(false)}>
+ {confirmCancelText}
+
+ {
+ if (typeof onConfirm === "function") {
+ onConfirm();
+ }
+ setOpen(false);
+ }}
+ >
+ {confirmOkText}
+
+
+
+
+
+ );
+});
+
+DeleteButton.displayName = "DeleteButton";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/buttons/edit.tsx b/packages/refine-ui/registry/new-york/refine-ui/buttons/edit.tsx
new file mode 100644
index 0000000000000..0c7d089297e1c
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/buttons/edit.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+import React from "react";
+import { type BaseKey, useEditButton } from "@refinedev/core";
+import { Button } from "@/registry/new-york/ui/button";
+import { Pencil } from "lucide-react";
+
+type EditButtonProps = {
+ /**
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
+ * @default Inferred resource name from the route
+ */
+ resource?: string;
+ /**
+ * Data item identifier for the actions with the API
+ * @default Reads `:id` from the URL
+ */
+ recordItemId?: BaseKey;
+ /**
+ * Access Control configuration for the button
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
+ */
+ accessControl?: {
+ enabled?: boolean;
+ hideIfUnauthorized?: boolean;
+ };
+ /**
+ * `meta` property is used when creating the URL for the related action and path.
+ */
+ meta?: Record;
+} & React.ComponentProps;
+
+export const EditButton = React.forwardRef<
+ React.ComponentRef,
+ EditButtonProps
+>(
+ (
+ { resource, recordItemId, accessControl, meta, children, onClick, ...rest },
+ ref,
+ ) => {
+ const { hidden, disabled, LinkComponent, to, label } = useEditButton({
+ resource,
+ id: recordItemId,
+ accessControl,
+ meta,
+ });
+
+ const isDisabled = disabled || rest.disabled;
+ const isHidden = hidden || rest.hidden;
+
+ if (isHidden) return null;
+
+ return (
+
+ ) => {
+ if (isDisabled) {
+ e.preventDefault();
+ return;
+ }
+ if (onClick) {
+ e.preventDefault();
+ onClick(e);
+ }
+ }}
+ >
+ {children ?? (
+
+ )}
+
+
+ );
+ },
+);
+
+EditButton.displayName = "EditButton";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/buttons/list.tsx b/packages/refine-ui/registry/new-york/refine-ui/buttons/list.tsx
new file mode 100644
index 0000000000000..92d4320a836a7
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/buttons/list.tsx
@@ -0,0 +1,70 @@
+"use client";
+
+import React from "react";
+import { type BaseKey, useListButton } from "@refinedev/core";
+import { Button } from "@/registry/new-york/ui/button";
+import { List } from "lucide-react";
+
+type ListButtonProps = {
+ /**
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
+ * @default Inferred resource name from the route
+ */
+ resource?: BaseKey;
+ /**
+ * Access Control configuration for the button
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
+ */
+ accessControl?: {
+ enabled?: boolean;
+ hideIfUnauthorized?: boolean;
+ };
+ /**
+ * `meta` property is used when creating the URL for the related action and path.
+ */
+ meta?: Record;
+} & React.ComponentProps;
+
+export const ListButton = React.forwardRef<
+ React.ComponentRef,
+ ListButtonProps
+>(({ resource, accessControl, meta, children, onClick, ...rest }, ref) => {
+ const { hidden, disabled, LinkComponent, to, label } = useListButton({
+ resource,
+ accessControl,
+ meta,
+ });
+
+ const isDisabled = disabled || rest.disabled;
+ const isHidden = hidden || rest.hidden;
+
+ if (isHidden) return null;
+
+ return (
+
+ ) => {
+ if (isDisabled) {
+ e.preventDefault();
+ return;
+ }
+ if (onClick) {
+ e.preventDefault();
+ onClick(e);
+ }
+ }}
+ >
+ {children ?? (
+
+
+ {label}
+
+ )}
+
+
+ );
+});
+
+ListButton.displayName = "ListButton";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/buttons/refresh.tsx b/packages/refine-ui/registry/new-york/refine-ui/buttons/refresh.tsx
new file mode 100644
index 0000000000000..e3b7da584c90e
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/buttons/refresh.tsx
@@ -0,0 +1,80 @@
+"use client";
+
+import React from "react";
+import { type BaseKey, useRefreshButton } from "@refinedev/core";
+import { RefreshCcw } from "lucide-react";
+import { Button } from "@/registry/new-york/ui/button";
+import { cn } from "@/lib/utils";
+
+type RefreshButtonProps = {
+ /**
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
+ * @default Inferred resource name from the route
+ */
+ resource?: string;
+ /**
+ * Data item identifier for the actions with the API
+ * @default Reads `:id` from the URL
+ */
+ recordItemId?: BaseKey;
+ /**
+ * Target data provider name for API call to be made
+ * @default `"default"`
+ */
+ dataProviderName?: string;
+ /**
+ * `meta` property is used when creating the URL for the related action and path.
+ */
+ meta?: Record;
+} & React.ComponentProps;
+
+export const RefreshButton = React.forwardRef<
+ React.ComponentRef,
+ RefreshButtonProps
+>(
+ (
+ { resource, recordItemId, dataProviderName, meta, children, ...rest },
+ ref,
+ ) => {
+ const {
+ onClick: refresh,
+ loading,
+ label,
+ } = useRefreshButton({
+ resource,
+ id: recordItemId,
+ dataProviderName,
+ meta,
+ });
+
+ const isDisabled = rest.disabled || loading;
+
+ return (
+ ) => {
+ if (isDisabled) {
+ e.preventDefault();
+ return;
+ }
+ refresh();
+ }}
+ {...rest}
+ ref={ref}
+ disabled={isDisabled}
+ >
+ {children ?? (
+
+
+ {label ?? "Refresh"}
+
+ )}
+
+ );
+ },
+);
+
+RefreshButton.displayName = "RefreshButton";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/buttons/show.tsx b/packages/refine-ui/registry/new-york/refine-ui/buttons/show.tsx
new file mode 100644
index 0000000000000..80f668d6ba848
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/buttons/show.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+import React from "react";
+import { type BaseKey, useShowButton } from "@refinedev/core";
+import { Eye } from "lucide-react";
+import { Button } from "@/registry/new-york/ui/button";
+
+type ShowButtonProps = {
+ /**
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
+ * @default Inferred resource name from the route
+ */
+ resource?: string;
+ /**
+ * Data item identifier for the actions with the API
+ * @default Reads `:id` from the URL
+ */
+ recordItemId?: BaseKey;
+ /**
+ * Access Control configuration for the button
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
+ */
+ accessControl?: {
+ enabled?: boolean;
+ hideIfUnauthorized?: boolean;
+ };
+ /**
+ * `meta` property is used when creating the URL for the related action and path.
+ */
+ meta?: Record;
+} & React.ComponentProps;
+
+export const ShowButton = React.forwardRef<
+ React.ComponentRef,
+ ShowButtonProps
+>(
+ (
+ { resource, recordItemId, accessControl, meta, children, onClick, ...rest },
+ ref,
+ ) => {
+ const { hidden, disabled, LinkComponent, to, label } = useShowButton({
+ resource,
+ id: recordItemId,
+ accessControl,
+ meta,
+ });
+
+ const isDisabled = disabled || rest.disabled;
+ const isHidden = hidden || rest.hidden;
+
+ if (isHidden) return null;
+
+ return (
+
+ ) => {
+ if (isDisabled) {
+ e.preventDefault();
+ return;
+ }
+ if (onClick) {
+ e.preventDefault();
+ onClick(e);
+ }
+ }}
+ >
+ {children ?? (
+
+
+ {label}
+
+ )}
+
+
+ );
+ },
+);
+
+ShowButton.displayName = "ShowButton";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table-filter.tsx b/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table-filter.tsx
new file mode 100644
index 0000000000000..1a8aa33275bfa
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table-filter.tsx
@@ -0,0 +1,977 @@
+"use client";
+
+import { useState, useEffect, useMemo } from "react";
+import { useTranslate, type CrudOperators } from "@refinedev/core";
+import type { Column, Table as ReactTable } from "@tanstack/react-table";
+import type { DateRange } from "react-day-picker";
+import { Check, ChevronsUpDown, ListFilter, X } from "lucide-react";
+
+import { Button } from "@/registry/new-york/ui/button";
+import { Input } from "@/registry/new-york/ui/input";
+import { Badge } from "@/registry/new-york/ui/badge";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/registry/new-york/ui/popover";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/registry/new-york/ui/command";
+import { Separator } from "@/registry/new-york/ui/separator";
+import { Calendar } from "@/registry/new-york/ui/calendar";
+import { cn } from "@/lib/utils";
+
+export type DataTableFilterDropdownProps = {
+ column: Column;
+ contentClassName?: string;
+ triggerClassName?: string;
+ children: (args: {
+ isOpen: boolean;
+ setIsOpen: React.Dispatch>;
+ }) => React.ReactNode;
+};
+
+export function DataTableFilterDropdown({
+ column,
+ triggerClassName,
+ contentClassName,
+ children,
+}: DataTableFilterDropdownProps) {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const isFiltered = column.getIsFiltered();
+
+ return (
+
+
+ setIsOpen(true)}
+ variant="ghost"
+ size="icon"
+ className={cn(
+ "data-[state=open]:bg-accent",
+ "w-5 h-5",
+ {
+ "text-primary": isFiltered,
+ "text-muted-foreground": !isFiltered,
+ },
+ triggerClassName,
+ )}
+ >
+
+
+
+
+ {children({ isOpen, setIsOpen })}
+
+
+ );
+}
+
+type DataTableFilterDropdownActionsProps = {
+ className?: string;
+ isClearDisabled?: boolean;
+ isApplyDisabled?: boolean;
+ onClear: () => void;
+ onApply: () => void;
+};
+
+export function DataTableFilterDropdownActions({
+ className,
+ isClearDisabled,
+ isApplyDisabled,
+ onClear,
+ onApply,
+}: DataTableFilterDropdownActionsProps) {
+ const t = useTranslate();
+
+ return (
+
+ {
+ onClear();
+ }}
+ >
+
+ {t("buttons.clear", "Clear")}
+
+
+ {
+ onApply();
+ }}
+ >
+ {t("buttons.apply", "Apply")}
+
+
+ );
+}
+
+export type DataTableFilterDropdownTextProps = {
+ column: Column;
+ table: ReactTable;
+ defaultOperator?: CrudOperators;
+ operators?: CrudOperators[];
+ placeholder?: string;
+};
+
+export function DataTableFilterDropdownText({
+ column,
+ table,
+ operators = [
+ "eq",
+ "ne",
+ "contains",
+ "ncontains",
+ "containss",
+ "ncontainss",
+ "startswith",
+ "nstartswith",
+ "startswiths",
+ "nstartswiths",
+ "endswith",
+ "nendswith",
+ "endswiths",
+ "nendswiths",
+ "in",
+ "nin",
+ "ina",
+ "nina",
+ ],
+ defaultOperator = "eq",
+ placeholder,
+}: DataTableFilterDropdownTextProps) {
+ const t = useTranslate();
+
+ return (
+ (
+ {
+ onChange(event.target.value);
+ }}
+ />
+ )}
+ />
+ );
+}
+
+export type DataTableFilterDropdownNumericProps = {
+ column: Column;
+ table: ReactTable;
+ defaultOperator?: CrudOperators;
+ operators?: CrudOperators[];
+ placeholder?: string;
+};
+
+export function DataTableFilterDropdownNumeric({
+ column,
+ table,
+ operators = ["eq", "ne", "gt", "lt", "gte", "lte"],
+ defaultOperator = "eq",
+ placeholder,
+}: DataTableFilterDropdownNumericProps) {
+ const t = useTranslate();
+
+ return (
+ (
+ {
+ onChange(event.target.value);
+ }}
+ />
+ )}
+ />
+ );
+}
+
+export type DataTableFilterComboboxProps = {
+ column: Column;
+ table?: ReactTable;
+ options: { label: string; value: string }[];
+ defaultOperator?: CrudOperators;
+ operators?: CrudOperators[];
+ placeholder?: string;
+ noResultsText?: string;
+ multiple?: boolean;
+};
+
+export function DataTableFilterCombobox({
+ column,
+ table,
+ options,
+ defaultOperator = "eq",
+ operators = ["eq", "ne", "in", "nin"],
+ placeholder,
+ noResultsText,
+ multiple = false,
+}: DataTableFilterComboboxProps) {
+ const t = useTranslate();
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+ {
+ const currentValues = multiple
+ ? Array.isArray(value)
+ ? value
+ : value && typeof value === "string"
+ ? [value]
+ : []
+ : value && typeof value === "string"
+ ? [value]
+ : [];
+
+ const handleSelect = (optionValue: string) => {
+ if (multiple) {
+ const newValues = currentValues.includes(optionValue)
+ ? currentValues.filter((v) => v !== optionValue)
+ : [...currentValues, optionValue];
+ onChange(newValues);
+ } else {
+ onChange(optionValue);
+ setIsOpen(false);
+ }
+ };
+
+ const handleRemove = (optionValue: string) => {
+ if (multiple) {
+ const newValues = currentValues.filter((v) => v !== optionValue);
+ onChange(newValues);
+ }
+ };
+
+ const getDisplayText = () => {
+ if (currentValues.length === 0) {
+ return (
+ placeholder ?? t("table.filter.combobox.placeholder", "Select...")
+ );
+ }
+
+ if (multiple) {
+ return `${currentValues.length} selected`;
+ }
+
+ const selectedOption = options.find(
+ (option) => option.value === currentValues[0],
+ );
+ return selectedOption ? selectedOption.label : currentValues[0];
+ };
+
+ const getSelectedLabels = () => {
+ return currentValues.map((val) => {
+ const option = options.find((opt) => opt.value === val);
+ return { label: option ? option.label : val, value: val };
+ });
+ };
+
+ return (
+
+
+
+
+ {multiple && currentValues.length > 0 ? (
+
+ {getSelectedLabels()
+ .slice(0, 3)
+ .map(({ label, value: val }) => (
+
+
+ {label}
+
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ handleRemove(val);
+ }}
+ >
+
+
+
+ ))}
+ {currentValues.length > 3 && (
+
+ +{currentValues.length - 3} more
+
+ )}
+
+ ) : (
+
+ {getDisplayText()}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {noResultsText ??
+ t(
+ "table.filter.combobox.noResults",
+ "Results not found.",
+ )}
+
+
+ {options.map((option) => (
+ handleSelect(option.value)}
+ keywords={option.label?.split(" ") ?? []}
+ >
+ {option.label}
+
+
+ ))}
+
+
+
+
+
+ );
+ }}
+ />
+ );
+}
+
+export type DataTableFilterDropdownDateSinglePickerProps = {
+ column: Column;
+ defaultOperator?: CrudOperators;
+ formatDate?: (date: Date | undefined) => string | undefined;
+};
+
+export function DataTableFilterDropdownDateSinglePicker({
+ column,
+ defaultOperator = "eq",
+ formatDate,
+}: DataTableFilterDropdownDateSinglePickerProps) {
+ const columnFilterValue = column.getFilterValue() as string;
+
+ const parseDate = (value: string | undefined): Date | undefined => {
+ if (!value) return undefined;
+
+ const date = new Date(value);
+
+ if (Number.isNaN(date.getTime())) return undefined;
+ return date;
+ };
+
+ const [filterValue, setFilterValue] = useState(() =>
+ parseDate(columnFilterValue),
+ );
+
+ useEffect(() => {
+ column.columnDef.meta = {
+ ...column.columnDef.meta,
+ filterOperator: defaultOperator,
+ };
+ }, [defaultOperator, column]);
+
+ useEffect(() => {
+ setFilterValue(parseDate(columnFilterValue));
+ }, [columnFilterValue]);
+
+ const hasDate = !!filterValue;
+
+ const handleApply = () => {
+ if (!filterValue) return;
+
+ const value = formatDate?.(filterValue) ?? filterValue.toISOString();
+ column.setFilterValue(value);
+ };
+
+ return (
+
+ {({ setIsOpen }) => {
+ return (
+ {
+ if (!hasDate) return;
+ if (event.key === "Enter") {
+ handleApply();
+ setIsOpen(false);
+ }
+ }}
+ >
+
{
+ setFilterValue(date);
+ }}
+ />
+
+
+
+
+
+ {
+ column.setFilterValue(undefined);
+ setFilterValue(undefined);
+ setIsOpen(false);
+ }}
+ onApply={() => {
+ handleApply();
+ setIsOpen(false);
+ }}
+ />
+
+ );
+ }}
+
+ );
+}
+
+export type DataTableFilterDropdownDateRangePickerProps = {
+ column: Column;
+ defaultOperator?: CrudOperators;
+ formatDateRange?: (dateRange: DateRange | undefined) => string[] | undefined;
+};
+
+export function DataTableFilterDropdownDateRangePicker({
+ column,
+ defaultOperator = "between",
+ formatDateRange,
+}: DataTableFilterDropdownDateRangePickerProps) {
+ const columnFilterValue = column.getFilterValue() as string[];
+
+ const parseDateRange = (
+ value: string[] | undefined,
+ ): DateRange | undefined => {
+ if (!value || !Array.isArray(value) || value.length !== 2) return undefined;
+
+ const from = value[0] ? new Date(value[0]) : undefined;
+ const to = value[1] ? new Date(value[1]) : undefined;
+
+ if (
+ !from ||
+ !to ||
+ Number.isNaN(from.getTime()) ||
+ Number.isNaN(to.getTime())
+ )
+ return undefined;
+ return { from, to };
+ };
+
+ const [filterValue, setFilterValue] = useState(() =>
+ parseDateRange(columnFilterValue),
+ );
+
+ useEffect(() => {
+ column.columnDef.meta = {
+ ...column.columnDef.meta,
+ filterOperator: defaultOperator,
+ };
+ }, [defaultOperator, column]);
+
+ useEffect(() => {
+ setFilterValue(parseDateRange(columnFilterValue));
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- objects are always different
+ }, [JSON.stringify(columnFilterValue)]);
+
+ const hasDateRange = filterValue?.from && filterValue?.to;
+
+ const handleApply = () => {
+ if (!filterValue?.from || !filterValue?.to) return;
+
+ const values = formatDateRange?.(filterValue) ?? [
+ filterValue.from.toISOString(),
+ filterValue.to.toISOString(),
+ ];
+ column.setFilterValue(values);
+ };
+
+ return (
+
+ {({ setIsOpen }) => {
+ return (
+ {
+ if (!hasDateRange) return;
+ if (event.key === "Enter") {
+ handleApply();
+ setIsOpen(false);
+ }
+ }}
+ >
+
{
+ setFilterValue({
+ from: date?.from,
+ to: date?.to,
+ });
+ }}
+ />
+
+
+
+
+
+ {
+ column.setFilterValue(undefined);
+ setFilterValue(undefined);
+ setIsOpen(false);
+ }}
+ onApply={() => {
+ handleApply();
+ setIsOpen(false);
+ }}
+ />
+
+ );
+ }}
+
+ );
+}
+
+export type DataTableFilterInputProps = {
+ column: Column;
+ table?: ReactTable;
+ defaultOperator?: CrudOperators;
+ operators?: CrudOperators[];
+ renderInput: (props: {
+ value: string | string[];
+ onChange: (value: string | string[]) => void;
+ }) => React.ReactNode;
+};
+
+export function DataTableFilterInput({
+ column: columnFromProps,
+ table: tableFromProps,
+ operators: operatorsFromProps,
+ defaultOperator: defaultOperatorFromProps,
+ renderInput,
+}: DataTableFilterInputProps) {
+ const [filterValue, setFilterValue] = useState(
+ (columnFromProps.getFilterValue() as string | string[]) || "",
+ );
+
+ const [operator, setOperator] = useState(() => {
+ if (!tableFromProps) {
+ return defaultOperatorFromProps || "eq";
+ }
+
+ const columnFilter = tableFromProps
+ .getState()
+ .columnFilters.find((filter) => {
+ return filter.id === columnFromProps.id;
+ });
+
+ if (columnFilter && "operator" in columnFilter) {
+ return columnFilter.operator as CrudOperators;
+ }
+
+ return defaultOperatorFromProps || "eq";
+ });
+
+ const handleApply = () => {
+ columnFromProps.setFilterValue(filterValue);
+ };
+
+ const handleClear = () => {
+ columnFromProps.setFilterValue(undefined);
+ setFilterValue("");
+ };
+
+ const handleOperatorChange = (value: CrudOperators) => {
+ setOperator(value);
+ columnFromProps.columnDef.meta = {
+ ...columnFromProps.columnDef.meta,
+ filterOperator: value,
+ };
+ };
+
+ return (
+
+ {({ setIsOpen }) => {
+ return (
+ {
+ if (event.key === "Enter") {
+ handleApply();
+ setIsOpen(false);
+ }
+ }}
+ >
+
+ {operatorsFromProps && operatorsFromProps.length > 1 && (
+
+ )}
+ {renderInput({
+ value: filterValue,
+ onChange: setFilterValue,
+ })}
+
+
+
+
+
{
+ handleClear();
+ setIsOpen(false);
+ }}
+ onApply={() => {
+ handleApply();
+ setIsOpen(false);
+ }}
+ />
+
+ );
+ }}
+
+ );
+}
+
+const CRUD_OPERATOR_LABELS: Record<
+ Exclude,
+ { i18nKey: string; defaultLabel: string }
+> = {
+ eq: { i18nKey: "table.filter.operator.eq", defaultLabel: "Equals" },
+ ne: { i18nKey: "table.filter.operator.ne", defaultLabel: "Not equals" },
+ lt: { i18nKey: "table.filter.operator.lt", defaultLabel: "Less than" },
+ gt: { i18nKey: "table.filter.operator.gt", defaultLabel: "Greater than" },
+ lte: {
+ i18nKey: "table.filter.operator.lte",
+ defaultLabel: "Less than or equal",
+ },
+ gte: {
+ i18nKey: "table.filter.operator.gte",
+ defaultLabel: "Greater than or equal",
+ },
+ in: {
+ i18nKey: "table.filter.operator.in",
+ defaultLabel: "Includes in an array",
+ },
+ nin: {
+ i18nKey: "table.filter.operator.nin",
+ defaultLabel: "Not includes in an array",
+ },
+ ina: {
+ i18nKey: "table.filter.operator.ina",
+ defaultLabel: "Includes in an array (case sensitive)",
+ },
+ nina: {
+ i18nKey: "table.filter.operator.nina",
+ defaultLabel: "Not includes in an array (case sensitive)",
+ },
+ contains: {
+ i18nKey: "table.filter.operator.contains",
+ defaultLabel: "Contains",
+ },
+ ncontains: {
+ i18nKey: "table.filter.operator.ncontains",
+ defaultLabel: "Not contains",
+ },
+ containss: {
+ i18nKey: "table.filter.operator.containss",
+ defaultLabel: "Contains (case sensitive)",
+ },
+ ncontainss: {
+ i18nKey: "table.filter.operator.ncontainss",
+ defaultLabel: "Not contains (case sensitive)",
+ },
+ between: {
+ i18nKey: "table.filter.operator.between",
+ defaultLabel: "Between",
+ },
+ nbetween: {
+ i18nKey: "table.filter.operator.nbetween",
+ defaultLabel: "Not between",
+ },
+ null: { i18nKey: "table.filter.operator.null", defaultLabel: "Is null" },
+ nnull: {
+ i18nKey: "table.filter.operator.nnull",
+ defaultLabel: "Is not null",
+ },
+ startswith: {
+ i18nKey: "table.filter.operator.startswith",
+ defaultLabel: "Starts with",
+ },
+ nstartswith: {
+ i18nKey: "table.filter.operator.nstartswith",
+ defaultLabel: "Not starts with",
+ },
+ startswiths: {
+ i18nKey: "table.filter.operator.startswiths",
+ defaultLabel: "Starts with (case sensitive)",
+ },
+ nstartswiths: {
+ i18nKey: "table.filter.operator.nstartswiths",
+ defaultLabel: "Not starts with (case sensitive)",
+ },
+ endswith: {
+ i18nKey: "table.filter.operator.endswith",
+ defaultLabel: "Ends with",
+ },
+ nendswith: {
+ i18nKey: "table.filter.operator.nendswith",
+ defaultLabel: "Not ends with",
+ },
+ endswiths: {
+ i18nKey: "table.filter.operator.endswiths",
+ defaultLabel: "Ends with (case sensitive)",
+ },
+ nendswiths: {
+ i18nKey: "table.filter.operator.nendswiths",
+ defaultLabel: "Not ends with (case sensitive)",
+ },
+};
+
+export type DataTableFilterOperatorSelectProps = {
+ value: CrudOperators;
+ onValueChange: (value: CrudOperators) => void;
+ operators?: CrudOperators[];
+ placeholder?: string;
+ triggerClassName?: string;
+ contentClassName?: string;
+};
+
+export function DataTableFilterOperatorSelect({
+ value,
+ onValueChange,
+ operators: operatorsFromProps,
+ placeholder,
+ triggerClassName,
+ contentClassName,
+}: DataTableFilterOperatorSelectProps) {
+ const t = useTranslate();
+
+ const [open, setOpen] = useState(false);
+
+ const operators = useMemo(() => {
+ return Object.entries(CRUD_OPERATOR_LABELS).filter(([operator]) =>
+ operatorsFromProps?.includes(operator as CrudOperators),
+ );
+ }, [operatorsFromProps]);
+
+ const selectedLabel = t(
+ CRUD_OPERATOR_LABELS[value as Exclude].i18nKey,
+ CRUD_OPERATOR_LABELS[value as Exclude]
+ .defaultLabel,
+ );
+ const placeholderText =
+ placeholder ?? t("table.filter.operator.placeholder", "Search operator...");
+ const noResultsText = t(
+ "table.filter.operator.noResults",
+ "No operator found.",
+ );
+
+ return (
+
+
+
+
+ {selectedLabel ?? placeholderText}
+
+
+
+
+
+
+
+
+ {noResultsText}
+
+ {operators.map(([op, { i18nKey, defaultLabel }]) => (
+ {
+ onValueChange(op as CrudOperators);
+ setOpen(false);
+ }}
+ >
+
+ {t(i18nKey, defaultLabel)}
+
+ ))}
+
+
+
+
+
+ );
+}
+
+DataTableFilterDropdown.displayName = "DataTableFilterDropdown";
+DataTableFilterDropdownText.displayName = "DataTableFilterDropdownText";
+DataTableFilterCombobox.displayName = "DataTableFilterCombobox";
+DataTableFilterDropdownDateRangePicker.displayName =
+ "DataTableFilterDropdownDateRangePicker";
+DataTableFilterOperatorSelect.displayName = "DataTableFilterOperatorSelect";
+DataTableFilterDropdownActions.displayName = "DataTableFilterDropdownActions";
+DataTableFilterDropdownNumeric.displayName = "DataTableFilterDropdownNumeric";
+DataTableFilterInput.displayName = "DataTableFilterInput";
+DataTableFilterOperatorSelect.displayName = "DataTableFilterOperatorSelect";
+DataTableFilterDropdownDateSinglePicker.displayName =
+ "DataTableFilterDropdownDateSinglePicker";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table-pagination.tsx b/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table-pagination.tsx
new file mode 100644
index 0000000000000..45851e3c48baf
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table-pagination.tsx
@@ -0,0 +1,146 @@
+"use client";
+
+import {
+ ChevronLeft,
+ ChevronRight,
+ ChevronsLeft,
+ ChevronsRight,
+} from "lucide-react";
+import { useMemo } from "react";
+
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/registry/new-york/ui/select";
+import { Button } from "@/registry/new-york/ui/button";
+import { cn } from "@/lib/utils";
+
+type DataTablePaginationProps = {
+ currentPage: number;
+ pageCount: number;
+ setCurrentPage: (page: number) => void;
+ pageSize: number;
+ setPageSize: (size: number) => void;
+ total?: number;
+};
+
+export function DataTablePagination({
+ currentPage,
+ pageCount,
+ setCurrentPage,
+ pageSize,
+ setPageSize,
+ total,
+}: DataTablePaginationProps) {
+ const pageSizeOptions = useMemo(() => {
+ const baseOptions = [10, 20, 30, 40, 50];
+ const optionsSet = new Set(baseOptions);
+
+ if (!optionsSet.has(pageSize)) {
+ optionsSet.add(pageSize);
+ }
+
+ return Array.from(optionsSet).sort((a, b) => a - b);
+ }, [pageSize]);
+
+ return (
+
+
+ {typeof total === "number" ? `${total} row(s)` : null}
+
+
+
+ Rows per page
+ setPageSize(Number(v))}
+ >
+
+
+
+
+ {pageSizeOptions.map((size) => (
+
+ {size}
+
+ ))}
+
+
+
+
+
+ Page {currentPage} of {pageCount}
+
+
+ setCurrentPage(1)}
+ disabled={currentPage === 1}
+ aria-label="Go to first page"
+ >
+
+
+ setCurrentPage(currentPage - 1)}
+ disabled={currentPage === 1}
+ aria-label="Go to previous page"
+ >
+
+
+ setCurrentPage(currentPage + 1)}
+ disabled={currentPage === pageCount}
+ aria-label="Go to next page"
+ >
+
+
+ setCurrentPage(pageCount)}
+ disabled={currentPage === pageCount}
+ aria-label="Go to last page"
+ >
+
+
+
+
+
+
+ );
+}
+
+DataTablePagination.displayName = "DataTablePagination";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table-sorter.tsx b/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table-sorter.tsx
new file mode 100644
index 0000000000000..84ce2789989f7
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table-sorter.tsx
@@ -0,0 +1,47 @@
+"use client";
+
+import type { Column } from "@tanstack/react-table";
+import { ArrowDown, ArrowUp, ChevronsUpDown } from "lucide-react";
+import { Button } from "@/registry/new-york/ui/button";
+import { cn } from "@/lib/utils";
+
+export type DataTableSorterProps = {
+ column: Column;
+} & React.ComponentProps;
+
+export function DataTableSorter({
+ column,
+ className,
+ ...props
+}: DataTableSorterProps) {
+ const title =
+ column.getIsSorted() === "desc"
+ ? `Sort by ${column.id} as descending`
+ : column.getIsSorted() === "asc"
+ ? `Sort by ${column.id} as ascending`
+ : `Sort by ${column.id}`;
+
+ return (
+ column.toggleSorting(undefined, true)}
+ title={title}
+ aria-label={title}
+ {...props}
+ className={cn("data-[state=open]:bg-accent", "w-5 h-5", className)}
+ >
+ {column.getIsSorted() === "desc" ? (
+
+ ) : column.getIsSorted() === "asc" ? (
+
+ ) : (
+
+ )}
+
+ );
+}
+
+DataTableSorter.displayName = "DataTableSorter";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table.tsx b/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table.tsx
new file mode 100644
index 0000000000000..a871094acc206
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/data-table/data-table.tsx
@@ -0,0 +1,316 @@
+"use client";
+
+import type { HttpError, BaseRecord } from "@refinedev/core";
+import type { UseTableReturnType } from "@refinedev/react-table";
+import type { Column } from "@tanstack/react-table";
+import { flexRender } from "@tanstack/react-table";
+import { Loader2 } from "lucide-react";
+import { useEffect, useRef, useState } from "react";
+
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/registry/new-york/ui/table";
+import { DataTablePagination } from "@/registry/new-york/refine-ui/data-table/data-table-pagination";
+import { cn } from "@/lib/utils";
+
+type DataTableProps = {
+ table: UseTableReturnType;
+};
+
+export function DataTable({
+ table,
+}: DataTableProps) {
+ const {
+ reactTable: { getHeaderGroups, getRowModel, getAllColumns },
+ refineCore: {
+ tableQuery,
+ currentPage,
+ setCurrentPage,
+ pageCount,
+ pageSize,
+ setPageSize,
+ },
+ } = table;
+
+ const columns = getAllColumns();
+ const leafColumns = table.reactTable.getAllLeafColumns();
+ const isLoading = tableQuery.isLoading;
+
+ const tableContainerRef = useRef(null);
+ const tableRef = useRef(null);
+ const [isOverflowing, setIsOverflowing] = useState({
+ horizontal: false,
+ vertical: false,
+ });
+
+ useEffect(() => {
+ const checkOverflow = () => {
+ if (tableRef.current && tableContainerRef.current) {
+ const table = tableRef.current;
+ const container = tableContainerRef.current;
+
+ const horizontalOverflow = table.offsetWidth > container.clientWidth;
+ const verticalOverflow = table.offsetHeight > container.clientHeight;
+
+ setIsOverflowing({
+ horizontal: horizontalOverflow,
+ vertical: verticalOverflow,
+ });
+ }
+ };
+
+ checkOverflow();
+
+ // Check on window resize
+ window.addEventListener("resize", checkOverflow);
+
+ // Check when table data changes
+ const timeoutId = setTimeout(checkOverflow, 100);
+
+ return () => {
+ window.removeEventListener("resize", checkOverflow);
+ clearTimeout(timeoutId);
+ };
+ }, [tableQuery.data?.data, pageSize]);
+
+ return (
+
+
+
+
+ {getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ const isPlaceholder = header.isPlaceholder;
+
+ return (
+
+ {isPlaceholder ? null : (
+
+ {flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ )}
+
+ );
+ })}
+
+ ))}
+
+
+ {isLoading ? (
+ <>
+ {Array.from({ length: pageSize < 1 ? 1 : pageSize }).map(
+ (_, rowIndex) => (
+
+ {leafColumns.map((column) => (
+
+
+
+ ))}
+
+ ),
+ )}
+
+
+
+
+
+ >
+ ) : getRowModel().rows?.length ? (
+ getRowModel().rows.map((row) => {
+ return (
+
+ {row.getVisibleCells().map((cell) => {
+ return (
+
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext(),
+ )}
+
+
+ );
+ })}
+
+ );
+ })
+ ) : (
+
+ )}
+
+
+
+ {!isLoading && getRowModel().rows?.length > 0 && (
+
+ )}
+
+ );
+}
+
+function DataTableNoData({
+ isOverflowing,
+ columnsLength,
+}: {
+ isOverflowing: { horizontal: boolean; vertical: boolean };
+ columnsLength: number;
+}) {
+ return (
+
+
+
+
+ No data to display
+
+
+ This table is empty for the time being.
+
+
+
+
+ );
+}
+
+export function getCommonStyles({
+ column,
+ isOverflowing,
+}: {
+ column: Column;
+ isOverflowing: {
+ horizontal: boolean;
+ vertical: boolean;
+ };
+}): React.CSSProperties {
+ const isPinned = column.getIsPinned();
+ const isLastLeftPinnedColumn =
+ isPinned === "left" && column.getIsLastColumn("left");
+ const isFirstRightPinnedColumn =
+ isPinned === "right" && column.getIsFirstColumn("right");
+
+ return {
+ boxShadow:
+ isOverflowing.horizontal && isLastLeftPinnedColumn
+ ? "-4px 0 4px -4px var(--border) inset"
+ : isOverflowing.horizontal && isFirstRightPinnedColumn
+ ? "4px 0 4px -4px var(--border) inset"
+ : undefined,
+ left:
+ isOverflowing.horizontal && isPinned === "left"
+ ? `${column.getStart("left")}px`
+ : undefined,
+ right:
+ isOverflowing.horizontal && isPinned === "right"
+ ? `${column.getAfter("right")}px`
+ : undefined,
+ opacity: 1,
+ position: isOverflowing.horizontal && isPinned ? "sticky" : "relative",
+ background: isOverflowing.horizontal && isPinned ? "var(--background)" : "",
+ borderTopRightRadius:
+ isOverflowing.horizontal && isPinned === "right"
+ ? "var(--radius)"
+ : undefined,
+ borderBottomRightRadius:
+ isOverflowing.horizontal && isPinned === "right"
+ ? "var(--radius)"
+ : undefined,
+ borderTopLeftRadius:
+ isOverflowing.horizontal && isPinned === "left"
+ ? "var(--radius)"
+ : undefined,
+ borderBottomLeftRadius:
+ isOverflowing.horizontal && isPinned === "left"
+ ? "var(--radius)"
+ : undefined,
+ width: column.getSize(),
+ zIndex: isOverflowing.horizontal && isPinned ? 1 : 0,
+ };
+}
+
+DataTable.displayName = "DataTable";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/form/auto-save-indicator.tsx b/packages/refine-ui/registry/new-york/refine-ui/form/auto-save-indicator.tsx
new file mode 100644
index 0000000000000..4402c0187d73a
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/form/auto-save-indicator.tsx
@@ -0,0 +1,108 @@
+"use client";
+
+import { useState, useEffect } from "react";
+import { useTranslate, type AutoSaveIndicatorProps } from "@refinedev/core";
+import { AlertTriangle, Clock, CheckCircle2, Loader2 } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+type Props = AutoSaveIndicatorProps;
+
+export function AutoSaveIndicator({
+ status,
+ elements: elementsFromProps,
+}: Props) {
+ const t = useTranslate();
+ const [shouldFadeSuccess, setShouldFadeSuccess] = useState(false);
+
+ useEffect(() => {
+ if (status === "success") {
+ const timer = setTimeout(() => {
+ setShouldFadeSuccess(true);
+ }, 1000);
+
+ return () => {
+ clearTimeout(timer);
+ setShouldFadeSuccess(false);
+ };
+ }
+ setShouldFadeSuccess(false);
+ }, [status]);
+
+ const elements = {
+ pending: elementsFromProps?.loading ?? (
+
+
+
+ {t("autoSave.saving", "Saving")}
+
+
+ ),
+ success: elementsFromProps?.success ?? (
+
+
+
+ {t("autoSave.saved", "Saved")}
+
+
+ ),
+ error: elementsFromProps?.error ?? (
+
+
+
+ {t("autoSave.failed", "Failed")}
+
+
+ ),
+ idle: elementsFromProps?.idle ?? (
+
+
+ {t("autoSave.idle", "Idle")}
+
+ ),
+ };
+
+ return {elements[status]}
;
+}
+
+AutoSaveIndicator.displayName = "AutoSaveIndicator";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/form/forgot-password-form.tsx b/packages/refine-ui/registry/new-york/refine-ui/form/forgot-password-form.tsx
new file mode 100644
index 0000000000000..a9d949f30c02c
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/form/forgot-password-form.tsx
@@ -0,0 +1,129 @@
+"use client";
+
+import { useState } from "react";
+import { ArrowLeft } from "lucide-react";
+
+import { useForgotPassword, useRefineOptions, useLink } from "@refinedev/core";
+import { Button } from "@/registry/new-york/ui/button";
+import { Input } from "@/registry/new-york/ui/input";
+import { Label } from "@/registry/new-york/ui/label";
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+} from "@/registry/new-york/ui/card";
+import { cn } from "@/lib/utils";
+
+export const ForgotPasswordForm = () => {
+ const [email, setEmail] = useState("");
+
+ const Link = useLink();
+
+ const { title } = useRefineOptions();
+
+ const { mutate: forgotPassword } = useForgotPassword();
+
+ const handleForgotPassword = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ forgotPassword({
+ email,
+ });
+ };
+
+ return (
+
+
+ {title.icon && (
+
svg]:w-12", "[&>svg]:h-12")}
+ >
+ {title.icon}
+
+ )}
+
+
+
+
+
+ Forgot password
+
+
+ Enter your email to change your password.
+
+
+
+
+
+
+
Email
+
+ setEmail(e.target.value)}
+ className={cn("flex-1")}
+ />
+
+ Send
+
+
+
+
+
+
+
+
+
+ );
+};
+
+ForgotPasswordForm.displayName = "ForgotPasswordForm";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/form/input-password.tsx b/packages/refine-ui/registry/new-york/refine-ui/form/input-password.tsx
new file mode 100644
index 0000000000000..cf0337f92c80b
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/form/input-password.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import { useState } from "react";
+import { Eye, EyeOff } from "lucide-react";
+import { Input } from "@/registry/new-york/ui/input";
+import { cn } from "@/lib/utils";
+
+type InputPasswordProps = React.ComponentProps<"input">;
+
+export const InputPassword = ({ className, ...props }: InputPasswordProps) => {
+ const [showPassword, setShowPassword] = useState(false);
+
+ return (
+
+
+ setShowPassword(!showPassword)}
+ >
+ {showPassword ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
+
+InputPassword.displayName = "InputPassword";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/form/sign-in-form.tsx b/packages/refine-ui/registry/new-york/refine-ui/form/sign-in-form.tsx
new file mode 100644
index 0000000000000..0f88c944a3c2c
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/form/sign-in-form.tsx
@@ -0,0 +1,244 @@
+"use client";
+
+import { useState } from "react";
+
+import { CircleHelp } from "lucide-react";
+
+import { useLogin, useRefineOptions, useLink } from "@refinedev/core";
+import { Button } from "@/registry/new-york/ui/button";
+import { Input } from "@/registry/new-york/ui/input";
+import { Label } from "@/registry/new-york/ui/label";
+import { Checkbox } from "@/registry/new-york/ui/checkbox";
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+ CardFooter,
+} from "@/registry/new-york/ui/card";
+import { Separator } from "@/registry/new-york/ui/separator";
+import { InputPassword } from "@/registry/new-york/refine-ui/form/input-password";
+import { cn } from "@/lib/utils";
+
+export const SignInForm = () => {
+ const [rememberMe, setRememberMe] = useState(false);
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+
+ const Link = useLink();
+
+ const { title } = useRefineOptions();
+
+ const { mutate: login } = useLogin();
+
+ const handleSignIn = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ login({
+ email,
+ password,
+ });
+ };
+
+ const handleSignInWithGoogle = () => {
+ login({
+ providerName: "google",
+ });
+ };
+
+ const handleSignInWithGitHub = () => {
+ login({
+ providerName: "github",
+ });
+ };
+
+ return (
+
+
+ {title.icon && (
+
svg]:w-12", "[&>svg]:h-12")}
+ >
+ {title.icon}
+
+ )}
+
+
+
+
+
+ Sign in
+
+
+ Welcome back
+
+
+
+
+
+
+
+
+ Email
+ setEmail(e.target.value)}
+ />
+
+
+ Password
+ setPassword(e.target.value)}
+ required
+ />
+
+
+
+
+
+ setRememberMe(checked === "indeterminate" ? false : checked)
+ }
+ />
+ Remember me
+
+
+
Forgot password
+
+
+
+
+
+ Sign in
+
+
+
+
+ or
+
+
+
+
+
Sign in using
+
+
+
+
+
+
+ Google
+
+
+
+
+
+ GitHub
+
+
+
+
+
+
+
+
+
+
+
+ No account?{" "}
+
+
+ Sign up
+
+
+
+
+
+ );
+};
+
+SignInForm.displayName = "SignInForm";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/form/sign-up-form.tsx b/packages/refine-ui/registry/new-york/refine-ui/form/sign-up-form.tsx
new file mode 100644
index 0000000000000..3f184fe3898f4
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/form/sign-up-form.tsx
@@ -0,0 +1,246 @@
+"use client";
+
+import { useState } from "react";
+
+import {
+ useRegister,
+ useRefineOptions,
+ useLink,
+ useNotification,
+} from "@refinedev/core";
+import { Button } from "@/registry/new-york/ui/button";
+import { Input } from "@/registry/new-york/ui/input";
+import { Label } from "@/registry/new-york/ui/label";
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+ CardFooter,
+} from "@/registry/new-york/ui/card";
+import { Separator } from "@/registry/new-york/ui/separator";
+import { InputPassword } from "@/registry/new-york/refine-ui/form/input-password";
+import { cn } from "@/lib/utils";
+
+export const SignUpForm = () => {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [confirmPassword, setConfirmPassword] = useState("");
+
+ const { open } = useNotification();
+
+ const Link = useLink();
+
+ const { title } = useRefineOptions();
+
+ const { mutate: register } = useRegister();
+
+ const handleSignUp = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ if (password !== confirmPassword) {
+ open?.({
+ type: "error",
+ message: "Passwords don't match",
+ description:
+ "Please make sure both password fields contain the same value.",
+ });
+
+ return;
+ }
+
+ register({
+ email,
+ password,
+ });
+ };
+
+ const handleSignUpWithGoogle = () => {
+ register({
+ providerName: "google",
+ });
+ };
+
+ const handleSignUpWithGitHub = () => {
+ register({
+ providerName: "github",
+ });
+ };
+
+ return (
+
+
+ {title.icon && (
+
svg]:w-12", "[&>svg]:h-12")}
+ >
+ {title.icon}
+
+ )}
+
+
+
+
+
+ Sign up
+
+
+ Welcome to lorem ipsum dolor.
+
+
+
+
+
+
+
+
+ Email
+ setEmail(e.target.value)}
+ />
+
+
+
+ Password
+ setPassword(e.target.value)}
+ required
+ />
+
+
+
+ Confirm password
+ setConfirmPassword(e.target.value)}
+ required
+ />
+
+
+
+ Sign up
+
+
+
+
+ or
+
+
+
+
+
+
+
+
+
+ Google
+
+
+
+
+
+ GitHub
+
+
+
+
+
+
+
+
+
+
+
+ Have an account?{" "}
+
+
+ Sign in
+
+
+
+
+
+ );
+};
+
+SignUpForm.displayName = "SignUpForm";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/layout/breadcrumb.tsx b/packages/refine-ui/registry/new-york/refine-ui/layout/breadcrumb.tsx
new file mode 100644
index 0000000000000..7c744a406a034
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/layout/breadcrumb.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+import { Fragment, useMemo } from "react";
+import { Home } from "lucide-react";
+import {
+ matchResourceFromRoute,
+ useBreadcrumb,
+ useLink,
+ useResourceParams,
+} from "@refinedev/core";
+import {
+ BreadcrumbSeparator as ShadcnBreadcrumbSeparator,
+ BreadcrumbItem as ShadcnBreadcrumbItem,
+ BreadcrumbList as ShadcnBreadcrumbList,
+ BreadcrumbPage as ShadcnBreadcrumbPage,
+ Breadcrumb as ShadcnBreadcrumb,
+} from "@/registry/new-york/ui/breadcrumb";
+
+export function Breadcrumb() {
+ const Link = useLink();
+ const { breadcrumbs } = useBreadcrumb();
+ const { resources } = useResourceParams();
+ const rootRouteResource = matchResourceFromRoute("/", resources);
+
+ const breadCrumbItems = useMemo(() => {
+ const list: {
+ key: string;
+ href: string;
+ Component: React.ReactNode;
+ }[] = [];
+
+ list.push({
+ key: "breadcrumb-item-home",
+ href: rootRouteResource.matchedRoute ?? "/",
+ Component: (
+
+ {rootRouteResource?.resource?.meta?.icon ?? (
+
+ )}
+
+ ),
+ });
+
+ for (const { label, href } of breadcrumbs) {
+ list.push({
+ key: `breadcrumb-item-${label}`,
+ href: href ?? "",
+ Component: href ? {label} : {label} ,
+ });
+ }
+
+ return list;
+ }, [breadcrumbs, Link, rootRouteResource]);
+
+ return (
+
+
+ {breadCrumbItems.map((item, index) => {
+ if (index === breadCrumbItems.length - 1) {
+ return (
+
+ {item.Component}
+
+ );
+ }
+
+ return (
+
+
+ {item.Component}
+
+
+
+ );
+ })}
+
+
+ );
+}
+
+Breadcrumb.displayName = "Breadcrumb";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/layout/error-component.tsx b/packages/refine-ui/registry/new-york/refine-ui/layout/error-component.tsx
new file mode 100644
index 0000000000000..81f5c756faafc
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/layout/error-component.tsx
@@ -0,0 +1,135 @@
+import React, { useEffect, useState } from "react";
+import { useGo, useResourceParams } from "@refinedev/core";
+import { useTranslate } from "@refinedev/core";
+import { Button } from "@/registry/new-york/ui/button";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/registry/new-york/ui/tooltip";
+import { ChevronLeft, InfoIcon } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+/**
+ * When the app is navigated to a non-existent route, refine shows a default error page.
+ * A custom error component can be used for this error page.
+ *
+ * @see {@link https://refine.dev/docs/packages/documentation/routers/} for more details.
+ */
+export function ErrorComponent() {
+ const [errorMessage, setErrorMessage] = useState();
+
+ const translate = useTranslate();
+ const go = useGo();
+
+ const { resource, action } = useResourceParams();
+
+ useEffect(() => {
+ if (resource && action) {
+ setErrorMessage(
+ translate(
+ "pages.error.info",
+ {
+ action: action,
+ resource: resource?.name,
+ },
+ `You may have forgotten to add the "${action}" component to "${resource?.name}" resource.`,
+ ),
+ );
+ }
+ }, [resource, action, translate]);
+
+ return (
+
+
+
+
+ 404 text
+
+
+
+
+
+
+
+
+
+
+
+
+ {translate("pages.error.title", "Page not found.")}
+
+
+
+
+ {translate(
+ "pages.error.description",
+ "The page you're looking for does not exist.",
+ )}
+
+ {errorMessage && (
+
+
+
+
+
+
+ {errorMessage}
+
+
+
+ )}
+
+
+
+
{
+ go({ to: "/" });
+ }}
+ className={cn("flex", "items-center", "gap-2", "mx-auto")}
+ >
+
+ {translate("pages.error.backHome", "Back to hompeage")}
+
+
+
+ );
+}
+
+ErrorComponent.displayName = "ErrorComponent";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/layout/layout-01/header.tsx b/packages/refine-ui/registry/new-york/refine-ui/layout/layout-01/header.tsx
new file mode 100644
index 0000000000000..4ac53ccdfccbb
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/layout/layout-01/header.tsx
@@ -0,0 +1,154 @@
+import {
+ useRefineOptions,
+ useActiveAuthProvider,
+ useLogout,
+} from "@refinedev/core";
+import {
+ DropdownMenu,
+ DropdownMenuItem,
+ DropdownMenuContent,
+} from "@/registry/new-york/ui/dropdown-menu";
+import { DropdownMenuTrigger } from "@/registry/new-york/ui/dropdown-menu";
+import { ThemeToggle } from "@/registry/new-york/refine-ui/theme/theme-toggle";
+import { UserAvatar } from "@/registry/new-york/refine-ui/user/user-avatar";
+import { useSidebar, SidebarTrigger } from "@/registry/new-york/ui/sidebar";
+import { LogOutIcon } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+export const Header = () => {
+ const { isMobile } = useSidebar();
+
+ return <>{isMobile ? : }>;
+};
+
+function DesktopHeader() {
+ return (
+
+ );
+}
+
+function MobileHeader() {
+ const { open, isMobile } = useSidebar();
+
+ const { title } = useRefineOptions();
+
+ return (
+
+
+
+
+
{title.icon}
+
+ {title.text}
+
+
+
+
+
+ );
+}
+
+const UserDropdown = () => {
+ const { mutate: logout, isPending: isLoggingOut } = useLogout();
+
+ const authProvider = useActiveAuthProvider();
+
+ if (!authProvider?.getIdentity) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ {
+ logout();
+ }}
+ >
+
+
+ {isLoggingOut ? "Logging out..." : "Logout"}
+
+
+
+
+ );
+};
+
+Header.displayName = "Header";
+MobileHeader.displayName = "MobileHeader";
+DesktopHeader.displayName = "DesktopHeader";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/layout/layout-01/layout.tsx b/packages/refine-ui/registry/new-york/refine-ui/layout/layout-01/layout.tsx
new file mode 100644
index 0000000000000..1d380d946a2ff
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/layout/layout-01/layout.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import type { PropsWithChildren } from "react";
+import { SidebarProvider, SidebarInset } from "@/registry/new-york/ui/sidebar";
+import { Sidebar } from "@/registry/new-york/refine-ui/layout/layout-01/sidebar";
+import { Header } from "@/registry/new-york/refine-ui/layout/layout-01/header";
+import { ThemeProvider } from "@/registry/new-york/refine-ui/theme/theme-provider";
+import { cn } from "@/lib/utils";
+
+export function Layout({ children }: PropsWithChildren) {
+ return (
+
+
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
+
+Layout.displayName = "Layout";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/layout/layout-01/sidebar.tsx b/packages/refine-ui/registry/new-york/refine-ui/layout/layout-01/sidebar.tsx
new file mode 100644
index 0000000000000..cdbd2c7ef1282
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/layout/layout-01/sidebar.tsx
@@ -0,0 +1,366 @@
+"use client";
+
+import React from "react";
+import {
+ useMenu,
+ useLink,
+ useRefineOptions,
+ type TreeMenuItem,
+} from "@refinedev/core";
+import {
+ SidebarRail as ShadcnSidebarRail,
+ Sidebar as ShadcnSidebar,
+ SidebarContent as ShadcnSidebarContent,
+ SidebarHeader as ShadcnSidebarHeader,
+ useSidebar as useShadcnSidebar,
+ SidebarTrigger as ShadcnSidebarTrigger,
+} from "@/registry/new-york/ui/sidebar";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/registry/new-york/ui/dropdown-menu";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/registry/new-york/ui/collapsible";
+import { Button } from "@/registry/new-york/ui/button";
+import { ChevronRight, ListIcon } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+export function Sidebar() {
+ const { open } = useShadcnSidebar();
+ const { menuItems, selectedKey } = useMenu();
+
+ return (
+
+
+
+
+ {menuItems.map((item: TreeMenuItem) => (
+
+ ))}
+
+
+ );
+}
+
+type MenuItemProps = {
+ item: TreeMenuItem;
+ selectedKey?: string;
+};
+
+function SidebarItem({ item, selectedKey }: MenuItemProps) {
+ const { open } = useShadcnSidebar();
+
+ if (item.meta?.group) {
+ return ;
+ }
+
+ if (item.children && item.children.length > 0) {
+ if (open) {
+ return ;
+ }
+ return ;
+ }
+
+ return ;
+}
+
+function SidebarItemGroup({ item, selectedKey }: MenuItemProps) {
+ const { children } = item;
+ const { open } = useShadcnSidebar();
+
+ return (
+
+
+ {getDisplayName(item)}
+
+ {children && children.length > 0 && (
+
+ {children.map((child: TreeMenuItem) => (
+
+ ))}
+
+ )}
+
+ );
+}
+
+function SidebarItemCollapsible({ item, selectedKey }: MenuItemProps) {
+ const { name, children } = item;
+
+ const chevronIcon = (
+
+ );
+
+ return (
+
+
+
+
+
+ {children?.map((child: TreeMenuItem) => (
+
+ ))}
+
+
+ );
+}
+
+function SidebarItemDropdown({ item, selectedKey }: MenuItemProps) {
+ const { children } = item;
+ const Link = useLink();
+
+ return (
+
+
+
+
+
+ {children?.map((child: TreeMenuItem) => {
+ const { key: childKey } = child;
+ const isSelected = childKey === selectedKey;
+
+ return (
+
+
+
+ {getDisplayName(child)}
+
+
+ );
+ })}
+
+
+ );
+}
+
+function SidebarItemLink({ item, selectedKey }: MenuItemProps) {
+ const isSelected = item.key === selectedKey;
+
+ return ;
+}
+
+function SidebarHeader() {
+ const { title } = useRefineOptions();
+ const { open, isMobile } = useShadcnSidebar();
+
+ return (
+
+
+
{title.icon}
+
+ {title.text}
+
+
+
+
+
+ );
+}
+
+function getDisplayName(item: TreeMenuItem) {
+ return item.meta?.label ?? item.label ?? item.name;
+}
+
+type IconProps = {
+ icon: React.ReactNode;
+ isSelected?: boolean;
+};
+
+function ItemIcon({ icon, isSelected }: IconProps) {
+ return (
+
+ {icon ?? }
+
+ );
+}
+
+type SidebarButtonProps = React.ComponentProps & {
+ item: TreeMenuItem;
+ isSelected?: boolean;
+ rightIcon?: React.ReactNode;
+ asLink?: boolean;
+ onClick?: () => void;
+};
+
+function SidebarButton({
+ item,
+ isSelected = false,
+ rightIcon,
+ asLink = false,
+ className,
+ onClick,
+ ...props
+}: SidebarButtonProps) {
+ const Link = useLink();
+
+ const buttonContent = (
+ <>
+
+
+ {getDisplayName(item)}
+
+ {rightIcon}
+ >
+ );
+
+ return (
+
+ {asLink && item.route ? (
+
+ {buttonContent}
+
+ ) : (
+ buttonContent
+ )}
+
+ );
+}
+
+Sidebar.displayName = "Sidebar";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/layout/loading-overlay.tsx b/packages/refine-ui/registry/new-york/refine-ui/layout/loading-overlay.tsx
new file mode 100644
index 0000000000000..edb78ae7c0c84
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/layout/loading-overlay.tsx
@@ -0,0 +1,36 @@
+"use client";
+
+import * as React from "react";
+import { Loader2 } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+interface LoadingOverlayProps extends React.HTMLAttributes {
+ loading?: boolean;
+ children: React.ReactNode;
+}
+
+export const LoadingOverlay = React.forwardRef<
+ HTMLDivElement,
+ LoadingOverlayProps
+>(({ className, loading = false, children, ...props }, ref) => {
+ if (!loading) return children;
+
+ return (
+
+ );
+});
+
+LoadingOverlay.displayName = "LoadingOverlay";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/notification/toaster.tsx b/packages/refine-ui/registry/new-york/refine-ui/notification/toaster.tsx
new file mode 100644
index 0000000000000..cb7f788daa29d
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/notification/toaster.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import { Toaster as Sonner, type ToasterProps } from "sonner";
+import { useTheme } from "@/registry/new-york/refine-ui/theme/theme-provider";
+
+export function Toaster({ ...props }: ToasterProps) {
+ const { theme = "system" } = useTheme();
+
+ return (
+
+ );
+}
diff --git a/packages/refine-ui/registry/new-york/refine-ui/notification/undoable-notification.tsx b/packages/refine-ui/registry/new-york/refine-ui/notification/undoable-notification.tsx
new file mode 100644
index 0000000000000..2b42cb4b61ee8
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/notification/undoable-notification.tsx
@@ -0,0 +1,84 @@
+import React from "react";
+import { useTranslate } from "@refinedev/core";
+import { Button } from "@/registry/new-york/ui/button";
+import { cn } from "@/lib/utils";
+
+type UndoableNotificationProps = {
+ message: string;
+ description?: string;
+ undoableTimeout?: number;
+ cancelMutation?: () => void;
+ onClose?: () => void;
+};
+
+export function UndoableNotification({
+ message,
+ description,
+ undoableTimeout = 5,
+ cancelMutation,
+ onClose,
+}: UndoableNotificationProps) {
+ const t = useTranslate();
+
+ React.useEffect(() => {
+ const timer = setTimeout(() => {
+ onClose?.();
+ }, undoableTimeout * 1000);
+
+ return () => clearTimeout(timer);
+ }, [onClose, undoableTimeout]);
+
+ const handleUndo = () => {
+ cancelMutation?.();
+ onClose?.();
+ };
+
+ return (
+
+
+
+
+ {message}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ {t("buttons.undo", "Undo")}
+
+
+
+ );
+}
+
+UndoableNotification.displayName = "UndoableNotification";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/notification/use-notification-provider.tsx b/packages/refine-ui/registry/new-york/refine-ui/notification/use-notification-provider.tsx
new file mode 100644
index 0000000000000..351fb702fa8e7
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/notification/use-notification-provider.tsx
@@ -0,0 +1,62 @@
+import type { NotificationProvider } from "@refinedev/core";
+import { toast } from "sonner";
+import { UndoableNotification } from "@/registry/new-york/refine-ui/notification/undoable-notification";
+
+export function useNotificationProvider(): NotificationProvider {
+ return {
+ open: ({
+ key,
+ type,
+ message,
+ description,
+ undoableTimeout,
+ cancelMutation,
+ }) => {
+ switch (type) {
+ case "success":
+ toast.success(message, {
+ id: key,
+ description,
+ richColors: true,
+ });
+ return;
+
+ case "error":
+ toast.error(message, {
+ id: key,
+ description,
+ richColors: true,
+ });
+ return;
+
+ case "progress": {
+ const toastId = key || Date.now();
+
+ toast(
+ () => (
+ toast.dismiss(toastId)}
+ />
+ ),
+ {
+ id: toastId,
+ duration: (undoableTimeout || 5) * 1000,
+ unstyled: true,
+ },
+ );
+ return;
+ }
+
+ default:
+ return;
+ }
+ },
+ close: (id) => {
+ toast.dismiss(id);
+ },
+ };
+}
diff --git a/packages/refine-ui/registry/new-york/refine-ui/theme/theme-provider.tsx b/packages/refine-ui/registry/new-york/refine-ui/theme/theme-provider.tsx
new file mode 100644
index 0000000000000..f462481d12849
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/theme/theme-provider.tsx
@@ -0,0 +1,78 @@
+"use client";
+
+import { createContext, useContext, useEffect, useState } from "react";
+
+type Theme = "dark" | "light" | "system";
+
+type ThemeProviderProps = {
+ children: React.ReactNode;
+ defaultTheme?: Theme;
+ storageKey?: string;
+};
+
+type ThemeProviderState = {
+ theme: Theme;
+ setTheme: (theme: Theme) => void;
+};
+
+const initialState: ThemeProviderState = {
+ theme: "system",
+ setTheme: () => null,
+};
+
+const ThemeProviderContext = createContext(initialState);
+
+export function ThemeProvider({
+ children,
+ defaultTheme = "system",
+ storageKey = "refine-ui-theme",
+ ...props
+}: ThemeProviderProps) {
+ const [theme, setTheme] = useState(
+ () => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
+ );
+
+ useEffect(() => {
+ const root = window.document.documentElement;
+
+ root.classList.remove("light", "dark");
+
+ if (theme === "system") {
+ const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
+ .matches
+ ? "dark"
+ : "light";
+
+ root.classList.add(systemTheme);
+ return;
+ }
+
+ root.classList.add(theme);
+ }, [theme]);
+
+ const value = {
+ theme,
+ setTheme: (theme: Theme) => {
+ localStorage.setItem(storageKey, theme);
+ setTheme(theme);
+ },
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTheme() {
+ const context = useContext(ThemeProviderContext);
+
+ if (context === undefined) {
+ console.error("useTheme must be used within a ThemeProvider");
+ }
+
+ return context;
+}
+
+ThemeProvider.displayName = "ThemeProvider";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/theme/theme-select.tsx b/packages/refine-ui/registry/new-york/refine-ui/theme/theme-select.tsx
new file mode 100644
index 0000000000000..133f286f7af97
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/theme/theme-select.tsx
@@ -0,0 +1,100 @@
+"use client";
+
+import React from "react";
+import { useTheme } from "./theme-provider";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/registry/new-york/ui/dropdown-menu";
+import { Button } from "@/registry/new-york/ui/button";
+import { Moon, Sun, Monitor, ChevronDown, Check } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+type ThemeOption = {
+ value: "light" | "dark" | "system";
+ label: string;
+ icon: React.ReactNode;
+};
+
+const themeOptions: ThemeOption[] = [
+ {
+ value: "light",
+ label: "Light",
+ icon: ,
+ },
+ {
+ value: "dark",
+ label: "Dark",
+ icon: ,
+ },
+ {
+ value: "system",
+ label: "System",
+ icon: ,
+ },
+];
+
+export function ThemeSelect() {
+ const { theme, setTheme } = useTheme();
+
+ const currentTheme = themeOptions.find((option) => option.value === theme);
+
+ return (
+
+
+
+
+ {currentTheme?.icon}
+ {currentTheme?.label}
+
+
+
+
+
+ {themeOptions.map((option) => {
+ const isSelected = theme === option.value;
+
+ return (
+ setTheme(option.value)}
+ className={cn(
+ "flex items-center gap-2 cursor-pointer relative pr-8",
+ {
+ "bg-accent text-accent-foreground": isSelected,
+ },
+ )}
+ >
+ {option.icon}
+ {option.label}
+ {isSelected && (
+
+ )}
+
+ );
+ })}
+
+
+ );
+}
+
+ThemeSelect.displayName = "ThemeSelect";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/theme/theme-toggle.tsx b/packages/refine-ui/registry/new-york/refine-ui/theme/theme-toggle.tsx
new file mode 100644
index 0000000000000..36d522dc1c22c
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/theme/theme-toggle.tsx
@@ -0,0 +1,93 @@
+"use client";
+
+import { useTheme } from "@/registry/new-york/refine-ui/theme/theme-provider";
+import { Button } from "@/registry/new-york/ui/button";
+import { cn } from "@/lib/utils";
+import { Moon, Sun, Monitor } from "lucide-react";
+
+type ThemeToggleProps = {
+ className?: string;
+};
+
+export function ThemeToggle({ className }: ThemeToggleProps) {
+ const { theme, setTheme } = useTheme();
+
+ const cycleTheme = () => {
+ switch (theme) {
+ case "light":
+ setTheme("dark");
+ break;
+ case "dark":
+ setTheme("system");
+ break;
+ case "system":
+ setTheme("light");
+ break;
+ default:
+ setTheme("light");
+ }
+ };
+
+ return (
+
+
+
+
+ Toggle theme (Light → Dark → System)
+
+ );
+}
+
+ThemeToggle.displayName = "ThemeToggle";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/user/user-avatar.tsx b/packages/refine-ui/registry/new-york/refine-ui/user/user-avatar.tsx
new file mode 100644
index 0000000000000..2bff13d5b1912
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/user/user-avatar.tsx
@@ -0,0 +1,46 @@
+import { useGetIdentity } from "@refinedev/core";
+import {
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+} from "@/registry/new-york/ui/avatar";
+import { Skeleton } from "@/registry/new-york/ui/skeleton";
+import { cn } from "@/lib/utils";
+
+type User = {
+ id: number;
+ firstName: string;
+ lastName: string;
+ fullName: string;
+ email: string;
+ avatar?: string;
+};
+
+export function UserAvatar() {
+ const { data: user, isLoading: userIsLoading } = useGetIdentity();
+
+ if (userIsLoading || !user) {
+ return ;
+ }
+
+ const { fullName, avatar } = user;
+
+ return (
+
+ {avatar && }
+ {getInitials(fullName)}
+
+ );
+}
+
+const getInitials = (name = "") => {
+ const names = name.split(" ");
+ let initials = names[0].substring(0, 1).toUpperCase();
+
+ if (names.length > 1) {
+ initials += names[names.length - 1].substring(0, 1).toUpperCase();
+ }
+ return initials;
+};
+
+UserAvatar.displayName = "UserAvatar";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/user/user-info.tsx b/packages/refine-ui/registry/new-york/refine-ui/user/user-info.tsx
new file mode 100644
index 0000000000000..5049bd803db34
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/user/user-info.tsx
@@ -0,0 +1,53 @@
+import { useGetIdentity } from "@refinedev/core";
+import { Skeleton } from "@/registry/new-york/ui/skeleton";
+import { UserAvatar } from "@/registry/new-york/refine-ui/user/user-avatar";
+import { cn } from "@/lib/utils";
+
+type User = {
+ id: number;
+ firstName: string;
+ lastName: string;
+ fullName: string;
+ email: string;
+ avatar?: string;
+};
+
+export function UserInfo() {
+ const { data: user, isLoading: userIsLoading } = useGetIdentity();
+
+ if (userIsLoading || !user) {
+ return (
+
+ );
+ }
+
+ const { firstName, lastName, email } = user;
+
+ return (
+
+
+
+
+ {firstName} {lastName}
+
+ {email}
+
+
+ );
+}
+
+UserInfo.displayName = "UserInfo";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/views/create-view.tsx b/packages/refine-ui/registry/new-york/refine-ui/views/create-view.tsx
new file mode 100644
index 0000000000000..fec921ab6819f
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/views/create-view.tsx
@@ -0,0 +1,79 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import {
+ useBack,
+ useResourceParams,
+ useUserFriendlyName,
+} from "@refinedev/core";
+import type { PropsWithChildren } from "react";
+import { Breadcrumb } from "@/registry/new-york/refine-ui/layout/breadcrumb";
+import { Separator } from "@/registry/new-york/ui/separator";
+import { Button } from "@/registry/new-york/ui/button";
+import { ArrowLeftIcon } from "lucide-react";
+
+type CreateViewProps = PropsWithChildren<{
+ className?: string;
+}>;
+
+export function CreateView({ children, className }: CreateViewProps) {
+ return (
+ {children}
+ );
+}
+
+type CreateHeaderProps = PropsWithChildren<{
+ resource?: string;
+ title?: string;
+ wrapperClassName?: string;
+ headerClassName?: string;
+}>;
+
+export const CreateViewHeader = ({
+ resource: resourceFromProps,
+ title: titleFromProps,
+ wrapperClassName,
+ headerClassName,
+}: CreateHeaderProps) => {
+ const back = useBack();
+
+ const getUserFriendlyName = useUserFriendlyName();
+
+ const { resource, identifier } = useResourceParams({
+ resource: resourceFromProps,
+ });
+
+ const title =
+ titleFromProps ??
+ getUserFriendlyName(
+ resource?.meta?.label ?? identifier ?? resource?.name,
+ "plural",
+ );
+
+ return (
+
+ );
+};
+
+CreateView.displayName = "CreateView";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/views/edit-view.tsx b/packages/refine-ui/registry/new-york/refine-ui/views/edit-view.tsx
new file mode 100644
index 0000000000000..7db9157d00ae9
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/views/edit-view.tsx
@@ -0,0 +1,97 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import {
+ useBack,
+ useResourceParams,
+ useUserFriendlyName,
+} from "@refinedev/core";
+import type { PropsWithChildren } from "react";
+import { Breadcrumb } from "@/registry/new-york/refine-ui/layout/breadcrumb";
+import { Separator } from "@/registry/new-york/ui/separator";
+import { Button } from "@/registry/new-york/ui/button";
+import { RefreshButton } from "@/registry/new-york/refine-ui/buttons/refresh";
+import { ArrowLeftIcon } from "lucide-react";
+
+type EditViewProps = PropsWithChildren<{
+ className?: string;
+}>;
+
+export function EditView({ children, className }: EditViewProps) {
+ return (
+ {children}
+ );
+}
+
+type EditViewHeaderProps = PropsWithChildren<{
+ resource?: string;
+ title?: string;
+ wrapperClassName?: string;
+ headerClassName?: string;
+ actionsSlot?: React.ReactNode;
+}>;
+
+export const EditViewHeader = ({
+ resource: resourceFromProps,
+ title: titleFromProps,
+ actionsSlot,
+ wrapperClassName,
+ headerClassName,
+}: EditViewHeaderProps) => {
+ const back = useBack();
+
+ const getUserFriendlyName = useUserFriendlyName();
+
+ const { resource, identifier } = useResourceParams({
+ resource: resourceFromProps,
+ });
+ const { id: recordItemId } = useResourceParams();
+
+ const resourceName = resource?.name ?? identifier;
+
+ const title =
+ titleFromProps ??
+ getUserFriendlyName(
+ resource?.meta?.label ?? identifier ?? resource?.name,
+ "plural",
+ );
+
+ return (
+
+
+
+
+
+
+ {actionsSlot}
+
+
+
+
+ );
+};
+
+EditView.displayName = "EditView";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/views/list-view.tsx b/packages/refine-ui/registry/new-york/refine-ui/views/list-view.tsx
new file mode 100644
index 0000000000000..cd843233589c6
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/views/list-view.tsx
@@ -0,0 +1,72 @@
+"use client";
+
+import type { PropsWithChildren } from "react";
+
+import { useResourceParams, useUserFriendlyName } from "@refinedev/core";
+import { Breadcrumb } from "@/registry/new-york/refine-ui/layout/breadcrumb";
+import { Separator } from "@/registry/new-york/ui/separator";
+import { CreateButton } from "@/registry/new-york/refine-ui/buttons/create";
+import { cn } from "@/lib/utils";
+
+type ListViewProps = PropsWithChildren<{
+ className?: string;
+}>;
+
+export function ListView({ children, className }: ListViewProps) {
+ return (
+ {children}
+ );
+}
+
+type ListHeaderProps = PropsWithChildren<{
+ resource?: string;
+ title?: string;
+ canCreate?: boolean;
+ headerClassName?: string;
+ wrapperClassName?: string;
+}>;
+
+export const ListViewHeader = ({
+ canCreate,
+ resource: resourceFromProps,
+ title: titleFromProps,
+ wrapperClassName,
+ headerClassName,
+}: ListHeaderProps) => {
+ const getUserFriendlyName = useUserFriendlyName();
+
+ const { resource, identifier } = useResourceParams({
+ resource: resourceFromProps,
+ });
+ const resourceName = identifier ?? resource?.name;
+
+ const isCreateButtonVisible = canCreate ?? !!resource?.create;
+
+ const title =
+ titleFromProps ??
+ getUserFriendlyName(
+ resource?.meta?.label ?? identifier ?? resource?.name,
+ "plural",
+ );
+
+ return (
+
+
+
+
{title}
+ {isCreateButtonVisible && (
+
+
+
+ )}
+
+
+ );
+};
+
+ListView.displayName = "ListView";
diff --git a/packages/refine-ui/registry/new-york/refine-ui/views/show-view.tsx b/packages/refine-ui/registry/new-york/refine-ui/views/show-view.tsx
new file mode 100644
index 0000000000000..02f03ffddcfdf
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/refine-ui/views/show-view.tsx
@@ -0,0 +1,101 @@
+"use client";
+
+import type { PropsWithChildren } from "react";
+
+import { ArrowLeftIcon } from "lucide-react";
+import {
+ useBack,
+ useResourceParams,
+ useUserFriendlyName,
+} from "@refinedev/core";
+import { Breadcrumb } from "@/registry/new-york/refine-ui/layout/breadcrumb";
+import { Separator } from "@/registry/new-york/ui/separator";
+import { Button } from "@/registry/new-york/ui/button";
+import { RefreshButton } from "@/registry/new-york/refine-ui/buttons/refresh";
+import { cn } from "@/lib/utils";
+import { EditButton } from "../buttons/edit";
+
+type ShowViewProps = PropsWithChildren<{
+ className?: string;
+}>;
+
+export function ShowView({ children, className }: ShowViewProps) {
+ return (
+ {children}
+ );
+}
+
+type ShowViewHeaderProps = PropsWithChildren<{
+ resource?: string;
+ title?: string;
+ wrapperClassName?: string;
+ headerClassName?: string;
+}>;
+
+export const ShowViewHeader = ({
+ resource: resourceFromProps,
+ title: titleFromProps,
+ wrapperClassName,
+ headerClassName,
+}: ShowViewHeaderProps) => {
+ const back = useBack();
+
+ const getUserFriendlyName = useUserFriendlyName();
+
+ const { resource, identifier } = useResourceParams({
+ resource: resourceFromProps,
+ });
+ const { id: recordItemId } = useResourceParams();
+
+ const resourceName = resource?.name ?? identifier;
+
+ const title =
+ titleFromProps ??
+ getUserFriendlyName(
+ resource?.meta?.label ?? identifier ?? resource?.name,
+ "singular",
+ );
+
+ return (
+
+ );
+};
+
+ShowView.displayName = "ShowView";
diff --git a/packages/refine-ui/registry/new-york/ui/accordion.tsx b/packages/refine-ui/registry/new-york/ui/accordion.tsx
new file mode 100644
index 0000000000000..6d60d31ef17a1
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/accordion.tsx
@@ -0,0 +1,66 @@
+"use client";
+
+import * as React from "react";
+import * as AccordionPrimitive from "@radix-ui/react-accordion";
+import { ChevronDownIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function Accordion({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function AccordionItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ svg]:rotate-180",
+ className,
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+ );
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
diff --git a/packages/refine-ui/registry/new-york/ui/alert-dialog.tsx b/packages/refine-ui/registry/new-york/ui/alert-dialog.tsx
new file mode 100644
index 0000000000000..63ffaf59e13e4
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/alert-dialog.tsx
@@ -0,0 +1,157 @@
+"use client";
+
+import * as React from "react";
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
+
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "@/registry/new-york/ui/button";
+
+function AlertDialog({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function AlertDialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ );
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogCancel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+};
diff --git a/packages/refine-ui/registry/new-york/ui/alert.tsx b/packages/refine-ui/registry/new-york/ui/alert.tsx
new file mode 100644
index 0000000000000..aa7de24dd7187
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/alert.tsx
@@ -0,0 +1,66 @@
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
+ {
+ variants: {
+ variant: {
+ default: "bg-card text-card-foreground",
+ destructive:
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+);
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ );
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export { Alert, AlertTitle, AlertDescription };
diff --git a/packages/refine-ui/registry/new-york/ui/aspect-ratio.tsx b/packages/refine-ui/registry/new-york/ui/aspect-ratio.tsx
new file mode 100644
index 0000000000000..c16d6bcb917ec
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/aspect-ratio.tsx
@@ -0,0 +1,11 @@
+"use client";
+
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
+
+function AspectRatio({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+export { AspectRatio };
diff --git a/packages/refine-ui/registry/new-york/ui/avatar.tsx b/packages/refine-ui/registry/new-york/ui/avatar.tsx
new file mode 100644
index 0000000000000..c4475c2e3863d
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/avatar.tsx
@@ -0,0 +1,53 @@
+"use client";
+
+import * as React from "react";
+import * as AvatarPrimitive from "@radix-ui/react-avatar";
+
+import { cn } from "@/lib/utils";
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/packages/refine-ui/registry/new-york/ui/badge.tsx b/packages/refine-ui/registry/new-york/ui/badge.tsx
new file mode 100644
index 0000000000000..46f988c2baee5
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const badgeVariants = cva(
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+ destructive:
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+);
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "span";
+
+ return (
+
+ );
+}
+
+export { Badge, badgeVariants };
diff --git a/packages/refine-ui/registry/new-york/ui/breadcrumb.tsx b/packages/refine-ui/registry/new-york/ui/breadcrumb.tsx
new file mode 100644
index 0000000000000..f63ae19afe17c
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/breadcrumb.tsx
@@ -0,0 +1,109 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { ChevronRight, MoreHorizontal } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
+ return ;
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
+ return (
+
+ );
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ );
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<"a"> & {
+ asChild?: boolean;
+}) {
+ const Comp = asChild ? Slot : "a";
+
+ return (
+
+ );
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+ svg]:size-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+ );
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More
+
+ );
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+};
diff --git a/packages/refine-ui/registry/new-york/ui/button.tsx b/packages/refine-ui/registry/new-york/ui/button.tsx
new file mode 100644
index 0000000000000..2adaf00daf88a
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/button.tsx
@@ -0,0 +1,59 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+);
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean;
+ }) {
+ const Comp = asChild ? Slot : "button";
+
+ return (
+
+ );
+}
+
+export { Button, buttonVariants };
diff --git a/packages/refine-ui/registry/new-york/ui/calendar.tsx b/packages/refine-ui/registry/new-york/ui/calendar.tsx
new file mode 100644
index 0000000000000..fbb86501d0040
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/calendar.tsx
@@ -0,0 +1,75 @@
+"use client";
+
+import * as React from "react";
+import { ChevronLeft, ChevronRight } from "lucide-react";
+import { DayPicker } from "react-day-picker";
+
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "@/registry/new-york/ui/button";
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+ .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
+ : "[&:has([aria-selected])]:rounded-md",
+ ),
+ day: cn(
+ buttonVariants({ variant: "ghost" }),
+ "size-8 p-0 font-normal aria-selected:opacity-100",
+ ),
+ day_range_start:
+ "day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
+ day_range_end:
+ "day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
+ day_selected:
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
+ day_today: "bg-accent text-accent-foreground",
+ day_outside:
+ "day-outside text-muted-foreground aria-selected:text-muted-foreground",
+ day_disabled: "text-muted-foreground opacity-50",
+ day_range_middle:
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
+ day_hidden: "invisible",
+ ...classNames,
+ }}
+ components={{
+ IconLeft: ({ className, ...props }) => (
+
+ ),
+ IconRight: ({ className, ...props }) => (
+
+ ),
+ }}
+ {...props}
+ />
+ );
+}
+
+export { Calendar };
diff --git a/packages/refine-ui/registry/new-york/ui/card.tsx b/packages/refine-ui/registry/new-york/ui/card.tsx
new file mode 100644
index 0000000000000..113d66c74daf9
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+};
diff --git a/packages/refine-ui/registry/new-york/ui/carousel.tsx b/packages/refine-ui/registry/new-york/ui/carousel.tsx
new file mode 100644
index 0000000000000..af563ed900287
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/carousel.tsx
@@ -0,0 +1,241 @@
+"use client";
+
+import * as React from "react";
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react";
+import { ArrowLeft, ArrowRight } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/registry/new-york/ui/button";
+
+type CarouselApi = UseEmblaCarouselType[1];
+type UseCarouselParameters = Parameters;
+type CarouselOptions = UseCarouselParameters[0];
+type CarouselPlugin = UseCarouselParameters[1];
+
+type CarouselProps = {
+ opts?: CarouselOptions;
+ plugins?: CarouselPlugin;
+ orientation?: "horizontal" | "vertical";
+ setApi?: (api: CarouselApi) => void;
+};
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0];
+ api: ReturnType[1];
+ scrollPrev: () => void;
+ scrollNext: () => void;
+ canScrollPrev: boolean;
+ canScrollNext: boolean;
+} & CarouselProps;
+
+const CarouselContext = React.createContext(null);
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext);
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ");
+ }
+
+ return context;
+}
+
+function Carousel({
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & CarouselProps) {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins,
+ );
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) return;
+ setCanScrollPrev(api.canScrollPrev());
+ setCanScrollNext(api.canScrollNext());
+ }, []);
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev();
+ }, [api]);
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext();
+ }, [api]);
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault();
+ scrollPrev();
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault();
+ scrollNext();
+ }
+ },
+ [scrollPrev, scrollNext],
+ );
+
+ React.useEffect(() => {
+ if (!api || !setApi) return;
+ setApi(api);
+ }, [api, setApi]);
+
+ React.useEffect(() => {
+ if (!api) return;
+ onSelect(api);
+ api.on("reInit", onSelect);
+ api.on("select", onSelect);
+
+ return () => {
+ api?.off("select", onSelect);
+ };
+ }, [api, onSelect]);
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
+ const { carouselRef, orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
+ const { orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselPrevious({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
+
+ return (
+
+
+ Previous slide
+
+ );
+}
+
+function CarouselNext({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
+
+ return (
+
+
+ Next slide
+
+ );
+}
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+};
diff --git a/packages/refine-ui/registry/new-york/ui/chart.tsx b/packages/refine-ui/registry/new-york/ui/chart.tsx
new file mode 100644
index 0000000000000..2a37bcb044f5d
--- /dev/null
+++ b/packages/refine-ui/registry/new-york/ui/chart.tsx
@@ -0,0 +1,354 @@
+"use client";
+
+import * as React from "react";
+import * as RechartsPrimitive from "recharts";
+
+import { cn } from "@/lib/utils";
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const;
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode;
+ icon?: React.ComponentType;
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ );
+};
+
+type ChartContextProps = {
+ config: ChartConfig;
+};
+
+const ChartContext = React.createContext(null);
+
+function useChart() {
+ const context = React.useContext(ChartContext);
+
+ if (!context) {
+ throw new Error("useChart must be used within a ");
+ }
+
+ return context;
+}
+
+function ChartContainer({
+ id,
+ className,
+ children,
+ config,
+ ...props
+}: React.ComponentProps<"div"> & {
+ config: ChartConfig;
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"];
+}) {
+ const uniqueId = React.useId();
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+}
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color,
+ );
+
+ if (!colorConfig.length) {
+ return null;
+ }
+
+ return (
+