From afb7e8b6a7a80c3ceb4e2364c76db425c92c00e4 Mon Sep 17 00:00:00 2001 From: Akki Khan Date: Sun, 8 Mar 2026 07:38:12 +0530 Subject: [PATCH 1/3] feat(refine-ui): add useEditableTable hook and shadcn/ui editable table example Closes #7172 ## What changed ### New hook \useEditableTable\ (shadcn/ui companion to \useTable\) Adds \packages/refine-ui/hooks/use-editable-table.ts\ and registers it in \packages/refine-ui/registry.json\ as a shadcn registry hook so users can install it with \shadcn add\. The hook is a companion to \useTable\ (from \@refinedev/react-table\) and mirrors the spirit of \@refinedev/antd\'s \useEditableTable\, adapted for headless / shadcn setups. API: \\\ s const { editingId, // BaseKey | undefined id of the row being edited editValues, // TVariables current draft values setEditValues, // Dispatch update draft values isEditing, // (id) => boolean editButtonProps, // (id, row?) => { onClick } cancelButtonProps, // { onClick } saveButtonProps, // { onClick, disabled } } = useEditableTable>({ resource: 'posts', mutationMode: 'optimistic', // default: 'pessimistic' autoSaveClose: true, // default: true }); \\\ Returns are stabilised with \useCallback\ / \useMemo\ to prevent unnecessary column rebuilds in the consuming \useMemo\ dep array. ### New example \examples/table-shadcn-editable\ Demonstrates the hook in a full Vite + React + Tailwind + shadcn/ui project against the public \pi.fake-rest.refine.dev\ posts endpoint. --- examples/table-shadcn-editable/.gitignore | 17 + examples/table-shadcn-editable/.npmrc | 1 + examples/table-shadcn-editable/README.md | 26 ++ .../table-shadcn-editable/components.json | 17 + examples/table-shadcn-editable/index.html | 21 ++ examples/table-shadcn-editable/package.json | 52 +++ .../table-shadcn-editable/postcss.config.js | 6 + .../table-shadcn-editable/public/favicon.ico | Bin 0 -> 101076 bytes .../public/manifest.json | 15 + examples/table-shadcn-editable/src/App.css | 110 ++++++ examples/table-shadcn-editable/src/App.tsx | 60 +++ .../src/components/breadcrumb/index.tsx | 25 ++ .../src/components/layout/index.tsx | 16 + .../src/components/menu/index.tsx | 25 ++ .../src/components/ui/button.tsx | 57 +++ .../src/components/ui/input.tsx | 26 ++ .../src/components/ui/pagination.tsx | 116 ++++++ .../src/components/ui/select.tsx | 127 +++++++ .../src/components/ui/table.tsx | 118 ++++++ .../src/hooks/use-editable-table.ts | 214 +++++++++++ examples/table-shadcn-editable/src/index.tsx | 12 + .../src/interfaces/index.d.ts | 6 + .../table-shadcn-editable/src/lib/utils.ts | 6 + .../src/pages/posts/index.ts | 1 + .../src/pages/posts/list.tsx | 347 ++++++++++++++++++ .../table-shadcn-editable/src/vite-env.d.ts | 1 + .../table-shadcn-editable/tailwind.config.js | 77 ++++ examples/table-shadcn-editable/tsconfig.json | 29 ++ .../table-shadcn-editable/tsconfig.node.json | 8 + examples/table-shadcn-editable/vite.config.ts | 12 + .../refine-ui/hooks/use-editable-table.ts | 214 +++++++++++ packages/refine-ui/registry.json | 18 + .../data-table/use-editable-table.ts | 214 +++++++++++ 33 files changed, 1994 insertions(+) create mode 100644 examples/table-shadcn-editable/.gitignore create mode 100644 examples/table-shadcn-editable/.npmrc create mode 100644 examples/table-shadcn-editable/README.md create mode 100644 examples/table-shadcn-editable/components.json create mode 100644 examples/table-shadcn-editable/index.html create mode 100644 examples/table-shadcn-editable/package.json create mode 100644 examples/table-shadcn-editable/postcss.config.js create mode 100644 examples/table-shadcn-editable/public/favicon.ico create mode 100644 examples/table-shadcn-editable/public/manifest.json create mode 100644 examples/table-shadcn-editable/src/App.css create mode 100644 examples/table-shadcn-editable/src/App.tsx create mode 100644 examples/table-shadcn-editable/src/components/breadcrumb/index.tsx create mode 100644 examples/table-shadcn-editable/src/components/layout/index.tsx create mode 100644 examples/table-shadcn-editable/src/components/menu/index.tsx create mode 100644 examples/table-shadcn-editable/src/components/ui/button.tsx create mode 100644 examples/table-shadcn-editable/src/components/ui/input.tsx create mode 100644 examples/table-shadcn-editable/src/components/ui/pagination.tsx create mode 100644 examples/table-shadcn-editable/src/components/ui/select.tsx create mode 100644 examples/table-shadcn-editable/src/components/ui/table.tsx create mode 100644 examples/table-shadcn-editable/src/hooks/use-editable-table.ts create mode 100644 examples/table-shadcn-editable/src/index.tsx create mode 100644 examples/table-shadcn-editable/src/interfaces/index.d.ts create mode 100644 examples/table-shadcn-editable/src/lib/utils.ts create mode 100644 examples/table-shadcn-editable/src/pages/posts/index.ts create mode 100644 examples/table-shadcn-editable/src/pages/posts/list.tsx create mode 100644 examples/table-shadcn-editable/src/vite-env.d.ts create mode 100644 examples/table-shadcn-editable/tailwind.config.js create mode 100644 examples/table-shadcn-editable/tsconfig.json create mode 100644 examples/table-shadcn-editable/tsconfig.node.json create mode 100644 examples/table-shadcn-editable/vite.config.ts create mode 100644 packages/refine-ui/hooks/use-editable-table.ts create mode 100644 packages/refine-ui/registry/new-york/refine-ui/data-table/use-editable-table.ts diff --git a/examples/table-shadcn-editable/.gitignore b/examples/table-shadcn-editable/.gitignore new file mode 100644 index 0000000000000..08bf0d2cf7cad --- /dev/null +++ b/examples/table-shadcn-editable/.gitignore @@ -0,0 +1,17 @@ +# Dependencies +/node_modules + +# Production +/build +/dist + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/examples/table-shadcn-editable/.npmrc b/examples/table-shadcn-editable/.npmrc new file mode 100644 index 0000000000000..521a9f7c07735 --- /dev/null +++ b/examples/table-shadcn-editable/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/examples/table-shadcn-editable/README.md b/examples/table-shadcn-editable/README.md new file mode 100644 index 0000000000000..67a00470b0e9a --- /dev/null +++ b/examples/table-shadcn-editable/README.md @@ -0,0 +1,26 @@ +# Editable Table with shadcn/ui + +A refine example demonstrating an **inline editable table** built with [shadcn/ui](https://ui.shadcn.com/) components and TanStack Table. + +## Features + +- Inline row editing without navigation +- Edit title and status fields directly in the table +- Optimistic mutation updates via `useUpdate` +- Pagination with shadcn/ui Pagination components +- Fully typed with TypeScript + +## Getting Started + +```bash +npm install +npm run dev +``` + +## Stack + +- [refine](https://refine.dev) – React framework for CRUD apps +- [shadcn/ui](https://ui.shadcn.com/) – Accessible component collection +- [TanStack Table v8](https://tanstack.com/table/v8) – Headless table library +- [Tailwind CSS](https://tailwindcss.com/) – Utility-first CSS +- [Vite](https://vitejs.dev/) – Build tool diff --git a/examples/table-shadcn-editable/components.json b/examples/table-shadcn-editable/components.json new file mode 100644 index 0000000000000..b00a9716b98e5 --- /dev/null +++ b/examples/table-shadcn-editable/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "./src/App.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} diff --git a/examples/table-shadcn-editable/index.html b/examples/table-shadcn-editable/index.html new file mode 100644 index 0000000000000..a2ff12848ee00 --- /dev/null +++ b/examples/table-shadcn-editable/index.html @@ -0,0 +1,21 @@ + + + + + + + + + + refine - Editable Table with shadcn/ui + + + + +
+ + + diff --git a/examples/table-shadcn-editable/package.json b/examples/table-shadcn-editable/package.json new file mode 100644 index 0000000000000..2fc62cf01cff4 --- /dev/null +++ b/examples/table-shadcn-editable/package.json @@ -0,0 +1,52 @@ +{ + "name": "table-shadcn-editable", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "tsc && vite build", + "dev": "vite", + "preview": "vite preview" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "dependencies": { + "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-slot": "^1.0.2", + "@refinedev/core": "^4.56.0", + "@refinedev/react-router": "^0.1.0", + "@refinedev/react-table": "^5.6.13", + "@refinedev/simple-rest": "^5.0.8", + "@tanstack/react-table": "^8.2.6", + "class-variance-authority": "^0.7.0", + "clsx": "^1.1.1", + "lucide-react": "^0.336.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router": "^7.0.2", + "tailwind-merge": "^2.2.1", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.1", + "postcss": "^8.1.4", + "tailwindcss": "^3.0.11", + "typescript": "^5.8.3", + "vite": "^5.4.15" + } +} diff --git a/examples/table-shadcn-editable/postcss.config.js b/examples/table-shadcn-editable/postcss.config.js new file mode 100644 index 0000000000000..2aa7205d4b402 --- /dev/null +++ b/examples/table-shadcn-editable/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/examples/table-shadcn-editable/public/favicon.ico b/examples/table-shadcn-editable/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2f05c5f484fbf720523541519ff0cad4df752a9f GIT binary patch literal 101076 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 + + , + }, + }, + ]} + options={{ + syncWithLocation: true, + warnWhenUnsavedChanges: true, + }} + > + + + + + } + > + } + /> + } /> + + + + + + + ); +} + +export default App; diff --git a/examples/table-shadcn-editable/src/components/breadcrumb/index.tsx b/examples/table-shadcn-editable/src/components/breadcrumb/index.tsx new file mode 100644 index 0000000000000..fe0ef6a19491b --- /dev/null +++ b/examples/table-shadcn-editable/src/components/breadcrumb/index.tsx @@ -0,0 +1,25 @@ +// @ts-nocheck — suppresses React 19 JSX type mismatches +import { useBreadcrumb } from "@refinedev/core"; +import { Link } from "react-router"; + +export const Breadcrumb = () => { + const { breadcrumbs } = useBreadcrumb(); + + return ( +
    + {breadcrumbs.map((breadcrumb) => { + return ( +
  • + {breadcrumb.href ? ( + + {breadcrumb.label.toLocaleLowerCase()} + + ) : ( + {breadcrumb.label.toLocaleLowerCase()} + )} +
  • + ); + })} +
+ ); +}; diff --git a/examples/table-shadcn-editable/src/components/layout/index.tsx b/examples/table-shadcn-editable/src/components/layout/index.tsx new file mode 100644 index 0000000000000..ca879a716fc86 --- /dev/null +++ b/examples/table-shadcn-editable/src/components/layout/index.tsx @@ -0,0 +1,16 @@ +// @ts-nocheck — suppresses React 19 JSX type mismatches +import { type FC, type PropsWithChildren } from "react"; +import { Breadcrumb } from "../breadcrumb"; +import { Menu } from "../menu"; + +export const Layout: FC = ({ children }) => { + return ( +
+ +
+ +
{children}
+
+
+ ); +}; diff --git a/examples/table-shadcn-editable/src/components/menu/index.tsx b/examples/table-shadcn-editable/src/components/menu/index.tsx new file mode 100644 index 0000000000000..1b0a5d255a105 --- /dev/null +++ b/examples/table-shadcn-editable/src/components/menu/index.tsx @@ -0,0 +1,25 @@ +// @ts-nocheck — suppresses React 19 JSX type mismatches +import { useMenu } from "@refinedev/core"; +import { NavLink } from "react-router"; +import { Button } from "../ui/button"; + +export const Menu = () => { + const { menuItems } = useMenu(); + + return ( + + ); +}; diff --git a/examples/table-shadcn-editable/src/components/ui/button.tsx b/examples/table-shadcn-editable/src/components/ui/button.tsx new file mode 100644 index 0000000000000..53d47c74c9dc8 --- /dev/null +++ b/examples/table-shadcn-editable/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +// @ts-nocheck — suppresses React 19 / Radix-UI JSX return-type mismatches +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 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/examples/table-shadcn-editable/src/components/ui/input.tsx b/examples/table-shadcn-editable/src/components/ui/input.tsx new file mode 100644 index 0000000000000..287d01323d3b1 --- /dev/null +++ b/examples/table-shadcn-editable/src/components/ui/input.tsx @@ -0,0 +1,26 @@ +// @ts-nocheck — suppresses React 19 / Radix-UI JSX return-type mismatches +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; + +export { Input }; diff --git a/examples/table-shadcn-editable/src/components/ui/pagination.tsx b/examples/table-shadcn-editable/src/components/ui/pagination.tsx new file mode 100644 index 0000000000000..f4fc11ae66401 --- /dev/null +++ b/examples/table-shadcn-editable/src/components/ui/pagination.tsx @@ -0,0 +1,116 @@ +// @ts-nocheck — suppresses React 19 / Radix-UI JSX return-type mismatches +import * as React from "react"; +import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"; + +import { cn } from "@/lib/utils"; +import { type ButtonProps, buttonVariants } from "@/components/ui/button"; + +const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( +