From fc47133dc9085ecc0238d6244f63f5f90d6b79dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:40:03 +0000 Subject: [PATCH 1/6] Initial plan From 82d2b306536d60065048bad4e2050e2cc8f39b94 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:47:29 +0000 Subject: [PATCH 2/6] Add RestTable component with Shadcn UI and update README Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- README.md | 227 ++++++++- package.json | 1 + pnpm-lock.yaml | 63 +++ registry.json | 31 ++ .../new-york/blocks/rest-table/example.tsx | 85 ++++ .../new-york/blocks/rest-table/rest-table.tsx | 456 ++++++++++++++++++ 6 files changed, 848 insertions(+), 15 deletions(-) create mode 100644 registry/new-york/blocks/rest-table/example.tsx create mode 100644 registry/new-york/blocks/rest-table/rest-table.tsx diff --git a/README.md b/README.md index 1a3ec4d..5ba5d3f 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,226 @@ # MobX RESTful Shadcn -A **Pagination Table** & **Scroll List** component suite for [CRUD operation][1], which is based on [MobX RESTful][2] & [React][3]. +A **Pagination Table** & **Scroll List** component suite for [CRUD operation][1], which is based on [MobX RESTful][2], [React][3] & [Shadcn UI][4]. -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. +[![MobX compatibility](https://img.shields.io/badge/Compatible-1?logo=mobx&label=MobX%206%2F7)][5] -> [!IMPORTANT] -> This template uses Tailwind v4. For Tailwind v3, see [registry-template-v3](https://github.com/shadcn-ui/registry-template-v3). +## Components -## Getting Started +1. [Badge Bar](https://mobx-restful-shadcn.vercel.app/) +2. [Badge Input](https://mobx-restful-shadcn.vercel.app/) +3. [Image Preview](https://mobx-restful-shadcn.vercel.app/) +4. [File Preview](https://mobx-restful-shadcn.vercel.app/) +5. [File Picker](https://mobx-restful-shadcn.vercel.app/) +6. [File Uploader](https://mobx-restful-shadcn.vercel.app/) +7. [Form Field](https://mobx-restful-shadcn.vercel.app/) +8. [Range Input](https://mobx-restful-shadcn.vercel.app/) +9. [Array Field](https://mobx-restful-shadcn.vercel.app/) +10. [REST Form](https://mobx-restful-shadcn.vercel.app/) +11. [REST Form Modal](https://mobx-restful-shadcn.vercel.app/) +12. [Pager](https://mobx-restful-shadcn.vercel.app/) +13. [REST Table](https://mobx-restful-shadcn.vercel.app/) +14. [Scroll Boundary](https://mobx-restful-shadcn.vercel.app/) +15. [Scroll List](https://mobx-restful-shadcn.vercel.app/) +16. [Searchable Input](https://mobx-restful-shadcn.vercel.app/) -This is a template for creating a custom registry using Next.js. +## Installation -- 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. +You can use the `shadcn` CLI to install components from this registry to your project. + +### Prerequisites + +```shell +npm i react \ + mobx \ + mobx-react \ + mobx-i18n \ + mobx-restful +``` + +### Install Components + +```shell +npx shadcn@latest add https://mobx-restful-shadcn.vercel.app/r/rest-table.json +``` + +Replace `rest-table` with any component name from the list above. + +## Configuration + +### Internationalization + +Set up i18n translation model for UI text: + +```typescript +import { TranslationModel } from "mobx-i18n"; + +export const i18n = new TranslationModel({ + en_US: { + submit: "Submit", + cancel: "Cancel", + create: "Create", + edit: "Edit", + delete: "Delete", + view: "View", + total_x_rows: "Total {{totalCount}} rows", + sure_to_delete_x: "Are you sure to delete {{keys}}?", + }, +}); +``` + +### Data Source + +Set up HTTP client and implement Model class: + +```typescript +import { githubClient, RepositoryModel } from "mobx-github"; + +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; + +githubClient.use(({ request }, next) => { + if (GITHUB_TOKEN) + request.headers = { + ...request.headers, + Authorization: `Bearer ${GITHUB_TOKEN}`, + }; + return next(); +}); + +export const repositoryStore = new RepositoryModel("idea2app"); +``` + +## Usage + +### Pagination Table + +```tsx +import { computed } from "mobx"; +import { observer } from "mobx-react"; +import { BadgeBar } from "@/components/ui/badge-bar"; +import { RestTable, Column } from "@/components/ui/rest-table"; +import repositoryStore, { Repository } from "@/models/Repository"; +import { i18n } from "@/models/Translation"; + +export default observer(() => { + const columns = computed[]>(() => [ + { + key: "full_name", + renderHead: "Repository Name", + renderBody: ({ html_url, full_name }) => ( + + {full_name} + + ), + required: true, + minLength: 3, + invalidMessage: "Input 3 characters at least", + }, + { key: "homepage", type: "url", renderHead: "Home Page" }, + { key: "language", renderHead: "Programming Language" }, + { + key: "topics", + renderHead: "Topic", + renderBody: ({ topics }) => ( + ({ + text, + link: `https://github.com/topics/${text}`, + }))} + /> + ), + }, + { key: "stargazers_count", type: "number", renderHead: "Star Count" }, + { + key: "description", + renderHead: "Description", + rows: 3, + }, + ]).get(); + + return ( + + ); +}); +``` + +### Scroll List + +```tsx +import { observer } from "mobx-react"; +import { ScrollList } from "@/components/ui/scroll-list"; +import repositoryStore from "@/models/Repository"; +import { i18n } from "@/models/Translation"; + +export default observer(() => ( + ( +
+ {allItems.map((item) => ( +
+

{item.name}

+

{item.description}

+
+ ))} +
+ )} + /> +)); +``` + +### File Uploader + +```tsx +import { FileUploader } from "@/components/ui/file-uploader"; +import fileStore from "@/models/File"; + +export const EditorPage = () => ( + +); +``` + +## Development + +This is a custom component registry built with Next.js and compatible with the `shadcn` CLI. + +### Getting Started + +1. Clone the repository +2. Install dependencies: `npm install` +3. Run development server: `npm run dev` +4. Build registry: `npm run registry:build` +5. Build project: `npm run build` + +### Registry Structure + +- The `registry.json` file defines all components and their files +- Components are located in `registry/new-york/blocks/` +- Each component has its implementation and example files +- The `shadcn build` command generates registry items in `public/r/` ## Documentation -Visit the [shadcn documentation](https://ui.shadcn.com/docs/registry) to view the full documentation. +- [Shadcn UI Documentation](https://ui.shadcn.com/docs) +- [MobX RESTful Documentation](https://github.com/idea2app/MobX-RESTful) +- [Component Registry Documentation](https://ui.shadcn.com/docs/registry) [1]: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete [2]: https://github.com/idea2app/MobX-RESTful [3]: https://reactjs.org/ +[4]: https://ui.shadcn.com/ +[5]: https://mobx.js.org/ diff --git a/package.json b/package.json index 098ae13..f780370 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "start": "next start" }, "dependencies": { + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-slot": "^1.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39fbf5a..7a69a97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,9 @@ importers: .: dependencies: + '@radix-ui/react-checkbox': + specifier: ^1.3.3 + version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@radix-ui/react-dialog': specifier: ^1.1.15 version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -695,6 +698,19 @@ packages: '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': ^19.1.2 + '@types/react-dom': ^19.1.2 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.1.2': resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: @@ -898,6 +914,24 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': ^19.1.2 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': ^19.1.2 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -4054,6 +4088,22 @@ snapshots: '@radix-ui/primitive@1.1.3': {} + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.7)(react@19.2.3)': dependencies: react: 19.2.3 @@ -4220,6 +4270,19 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.7)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.7)(react@19.2.3)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.7 + '@rtsao/scc@1.1.0': {} '@sec-ant/readable-stream@0.4.1': {} diff --git a/registry.json b/registry.json index bd9e155..b0e24a7 100644 --- a/registry.json +++ b/registry.json @@ -311,6 +311,37 @@ "type": "registry:component" } ] + }, + { + "name": "rest-table", + "type": "registry:component", + "title": "REST Table", + "description": "A comprehensive pagination table component for CRUD operations with MobX RESTful integration, supporting sorting, filtering, and inline editing.", + "registryDependencies": [ + "button", + "checkbox", + "table", + "badge-bar", + "file-preview", + "pager", + "rest-form", + "rest-form-modal" + ], + "dependencies": [ + "lodash.debounce", + "mobx", + "mobx-i18n", + "mobx-react", + "mobx-react-helper", + "mobx-restful", + "web-utility" + ], + "files": [ + { + "path": "registry/new-york/blocks/rest-table/rest-table.tsx", + "type": "registry:component" + } + ] } ] } diff --git a/registry/new-york/blocks/rest-table/example.tsx b/registry/new-york/blocks/rest-table/example.tsx new file mode 100644 index 0000000..2986481 --- /dev/null +++ b/registry/new-york/blocks/rest-table/example.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { computed } from "mobx"; +import { observer } from "mobx-react"; +import { GitRepository } from "mobx-github"; + +import { BadgeBar } from "../badge-bar/badge-bar"; +import { i18n, repositoryStore } from "@/models/example"; +import { Column, RestTable } from "./rest-table"; + +export const RestTableExample = observer(() => { + const columns = computed[]>(() => [ + { + key: "full_name", + renderHead: "Repository Name", + renderBody: ({ html_url, full_name }) => ( + + {full_name} + + ), + required: true, + minLength: 3, + invalidMessage: "Input 3 characters at least", + }, + { key: "homepage", type: "url", renderHead: "Home Page" }, + { key: "language", renderHead: "Programming Language" }, + { + key: "topics", + renderHead: "Topic", + renderBody: ({ topics }) => ( + ({ + text, + link: `https://github.com/topics/${text}`, + }))} + /> + ), + }, + { key: "stargazers_count", type: "number", renderHead: "Star Count" }, + { + key: "description", + renderHead: "Description", + rows: 3, + renderBody: ({ description }) => ( +

+ {description} +

+ ), + }, + ]).get(); + + return ( +
+ ) => { + const translations: Record = { + create: "Create", + view: "View", + edit: "Edit", + delete: "Delete", + total_x_rows: `Total ${data?.totalCount || 0} rows`, + sure_to_delete_x: `Are you sure to delete ${data?.keys || ""}?`, + }; + return translations[key] || key; + }, + }} + onCheck={(keys) => console.log("Checked keys:", keys)} + /> +
+ ); +}); diff --git a/registry/new-york/blocks/rest-table/rest-table.tsx b/registry/new-york/blocks/rest-table/rest-table.tsx new file mode 100644 index 0000000..1d1254a --- /dev/null +++ b/registry/new-york/blocks/rest-table/rest-table.tsx @@ -0,0 +1,456 @@ +"use client"; + +import { computed, observable } from "mobx"; +import { TranslationModel } from "mobx-i18n"; +import { observer } from "mobx-react"; +import { ObservedComponent } from "mobx-react-helper"; +import { DataObject, Filter, IDType } from "mobx-restful"; +import { HTMLAttributes, ReactNode } from "react"; +import { isEmpty } from "web-utility"; +import debounce from "lodash.debounce"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Table, + TableBody, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { BadgeBar } from "../badge-bar/badge-bar"; +import { FilePreview } from "../file-preview/file-preview"; +import { Pager } from "../pager/pager"; +import { Field, RestForm, RestFormProps } from "../rest-form/rest-form"; +import { RestFormModal } from "../rest-form-modal/rest-form-modal"; + +export interface Column + extends Omit, "renderLabel"> { + renderHead?: Field["renderLabel"]; + renderBody?: (data: T) => ReactNode; + renderFoot?: ReactNode | ((data: keyof T) => ReactNode); +} + +type Translator = RestFormProps["translator"] & + TranslationModel< + string, + | "create" + | "view" + | "edit" + | "delete" + | "total_x_rows" + | "sure_to_delete_x" + >; + +export interface RestTableProps< + D extends DataObject, + F extends Filter = Filter +> extends Omit< + HTMLAttributes, + "onSubmit" | "onReset" | "children" + >, + Omit, "id" | "size" | "fields" | "translator"> { + filter?: F; + filterFields?: Field[]; + editable?: boolean; + deletable?: boolean; + columns: Column[]; + translator: Translator; + size?: "default" | "sm" | "lg"; + onCheck?: (keys: IDType[]) => any; + striped?: boolean; + hover?: boolean; + responsive?: boolean; +} + +@observer +export class RestTable< + D extends DataObject, + F extends Filter = Filter +> extends ObservedComponent> { + static readonly displayName = "RestTable"; + + componentDidMount() { + const { store, filter } = this.props; + + if (store) { + store.clear(); + store.getList(filter); + } + } + + @computed + get fieldSize() { + const { size } = this.observedProps; + + return !size || size === "default" ? "default" : size; + } + + @observable + accessor checkedKeys: IDType[] = []; + + toggleCheck(key: IDType) { + const { checkedKeys } = this; + const index = checkedKeys.indexOf(key); + + this.checkedKeys = + index < 0 + ? [...checkedKeys, key] + : [...checkedKeys.slice(0, index), ...checkedKeys.slice(index + 1)]; + + this.props.onCheck?.(this.checkedKeys); + } + + toggleCheckAll = () => { + const { store, onCheck } = this.props; + if (!store) return; + + const { indexKey, currentPage } = store; + + this.checkedKeys = + this.checkedKeys.length + ? [] + : currentPage.map(({ [indexKey]: ID }) => ID); + + onCheck?.(this.checkedKeys); + }; + + @computed + get checkColumn(): Column { + const { checkedKeys, toggleCheckAll } = this; + const { store } = this.observedProps; + if (!store) return {} as Column; + + const { indexKey, currentPage } = store; + + return { + renderHead: () => ( + + checkedKeys.includes(ID) + ) + } + onCheckedChange={toggleCheckAll} + aria-label="Select all" + /> + ), + renderBody: ({ [indexKey]: ID }) => ( + this.toggleCheck(ID)} + aria-label={`Select row ${ID}`} + /> + ), + }; + } + + @computed + get operateColumn(): Column { + const { editable, deletable, columns, store, translator } = + this.observedProps; + if (!store) return {} as Column; + + const { fieldSize } = this; + const { t } = translator; + const readOnly = columns.every(({ readOnly }) => readOnly); + const disabled = columns.every(({ disabled }) => disabled); + + return { + renderHead: () => <>, + renderBody: (data) => ( +
+ {!disabled && + editable && ( + + )} + {deletable && ( + + )} +
+ ), + }; + } + + @computed + get columns(): Column[] { + const { editable, deletable, columns, onCheck } = this.observedProps; + + return [ + onCheck && this.checkColumn, + ...columns.map( + ({ renderBody, ...column }) => + ({ + ...column, + renderBody: renderBody ?? this.renderCustomBody(column), + } as Column) + ), + (editable || deletable) && this.operateColumn, + ].filter(Boolean) as Column[]; + } + + @computed + get hasHeader() { + return this.columns.some(({ renderHead }) => renderHead); + } + + @computed + get hasFooter() { + return this.columns.some(({ renderFoot }) => renderFoot); + } + + @computed + get editing() { + const { store } = this.observedProps; + return !isEmpty(store?.currentOne); + } + + renderCustomBody = ({ + key, + type, + multiple, + options, + accept, + rows, + }: Column): Column["renderBody"] => + type === "url" + ? ({ [key!]: value }) => + value && ( + + {value as string} + + ) + : type === "email" + ? ({ [key!]: value }) => + value && ( + + {value as string} + + ) + : type === "tel" + ? ({ [key!]: value }) => + value && ( + + {value as string} + + ) + : type === "file" + ? ({ [key!]: value }) => + ((Array.isArray(value) ? value : [value]) as string[]).map( + (path) => + path && + ) + : options || multiple + ? ({ [key!]: value }) => + value && ( + ({ text }))} + /> + ) + : !options && rows + ? ({ [key!]: value }) => ( +

+ {value as string} +

+ ) + : undefined; + + renderTable() { + const { store, className: _, ...tableProps } = this.props; + if (!store) return null; + + const { hasHeader, hasFooter, columns, editing } = this; + const { indexKey, downloading, currentPage } = store; + + return ( + + {hasHeader && ( + + + {columns.map( + ({ key, renderHead }, index) => + (key || renderHead) && ( + + {typeof renderHead === "function" + ? renderHead(key!) + : renderHead || (key as string)} + + ) + )} + + + )} + + {!editing && downloading > 0 ? ( + + +
+
+
+
+
+ ) : ( + currentPage.map((data) => ( + + {columns.map( + ({ key, renderBody }, index) => + (key || renderBody) && ( + + {renderBody?.(data) || (key && data[key])} + + ) + )} + + )) + )} +
+ + {hasFooter && ( + + + {columns.map( + ({ key, renderFoot }, index) => + (key || renderFoot) && ( + + {typeof renderFoot === "function" + ? renderFoot(key!) + : renderFoot || (key as string)} + + ) + )} + + + )} +
+ ); + } + + getList = debounce(({ pageIndex, pageSize }) => { + const { store, filter } = this.props; + + if (store && store.downloading < 1) + store.getList(filter, pageIndex, pageSize); + }, 300); + + async deleteList(keys: IDType[]) { + const { translator, store } = this.props; + if (!store) return; + + if (confirm(translator.t("sure_to_delete_x", { keys }))) + for (const key of keys) await store.deleteOne(key); + } + + render() { + const { + className = "overflow-auto flex flex-col gap-3", + editable, + deletable, + filterFields, + store, + translator, + onSubmit, + onReset, + striped, + hover, + responsive, + ...props + } = this.props; + + if (!store) return null; + + const { fieldSize } = this; + const { t } = translator; + const { indexKey, pageSize, pageIndex, pageCount, totalCount } = store; + + return ( +
+
+ {filterFields && ( + store.getList(filter as F, 1)} + onReset={() => store.getList({} as F, 1)} + /> + )} +
+
+ {deletable && ( + + )} + {editable && ( + + )} +
+
+
+ + {this.renderTable()} + +
+ {!!totalCount && ( + + {t("total_x_rows", { totalCount })} + + )} + + +
+ + {editable && ( + ({ + ...field, + renderLabel: renderHead, + }))} + {...{ store, translator, onSubmit }} + /> + )} +
+ ); + } +} From 191c2c9e0ea8e45971e57ab20a47659e44a5226b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:51:06 +0000 Subject: [PATCH 3/6] Fix TypeScript issues in RestTable and example Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- .../new-york/blocks/rest-table/example.tsx | 33 ++++++++++--------- .../new-york/blocks/rest-table/rest-table.tsx | 9 +++-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/registry/new-york/blocks/rest-table/example.tsx b/registry/new-york/blocks/rest-table/example.tsx index 2986481..00572b4 100644 --- a/registry/new-york/blocks/rest-table/example.tsx +++ b/registry/new-york/blocks/rest-table/example.tsx @@ -1,13 +1,29 @@ "use client"; import { computed } from "mobx"; +import { TranslationModel } from "mobx-i18n"; import { observer } from "mobx-react"; import { GitRepository } from "mobx-github"; import { BadgeBar } from "../badge-bar/badge-bar"; -import { i18n, repositoryStore } from "@/models/example"; +import { repositoryStore } from "@/models/example"; import { Column, RestTable } from "./rest-table"; +const tableI18n = new TranslationModel({ + en_US: { + load_more: "Load more", + no_more: "No more", + submit: "Submit", + cancel: "Cancel", + create: "Create", + view: "View", + edit: "Edit", + delete: "Delete", + total_x_rows: "Total {{totalCount}} rows", + sure_to_delete_x: "Are you sure to delete {{keys}}?", + }, +}); + export const RestTableExample = observer(() => { const columns = computed[]>(() => [ { @@ -64,20 +80,7 @@ export const RestTableExample = observer(() => { deletable columns={columns} store={repositoryStore} - translator={{ - ...i18n, - t: (key: string, data?: Record) => { - const translations: Record = { - create: "Create", - view: "View", - edit: "Edit", - delete: "Delete", - total_x_rows: `Total ${data?.totalCount || 0} rows`, - sure_to_delete_x: `Are you sure to delete ${data?.keys || ""}?`, - }; - return translations[key] || key; - }, - }} + translator={tableI18n} onCheck={(keys) => console.log("Checked keys:", keys)} /> diff --git a/registry/new-york/blocks/rest-table/rest-table.tsx b/registry/new-york/blocks/rest-table/rest-table.tsx index 1d1254a..6bf7269 100644 --- a/registry/new-york/blocks/rest-table/rest-table.tsx +++ b/registry/new-york/blocks/rest-table/rest-table.tsx @@ -48,11 +48,7 @@ type Translator = RestFormProps["translator"] & export interface RestTableProps< D extends DataObject, F extends Filter = Filter -> extends Omit< - HTMLAttributes, - "onSubmit" | "onReset" | "children" - >, - Omit, "id" | "size" | "fields" | "translator"> { +> extends Omit, "onSubmit" | "onReset" | "children"> { filter?: F; filterFields?: Field[]; editable?: boolean; @@ -64,6 +60,9 @@ export interface RestTableProps< striped?: boolean; hover?: boolean; responsive?: boolean; + store?: RestFormProps["store"]; + onSubmit?: RestFormProps["onSubmit"]; + onReset?: RestFormProps["onReset"]; } @observer From 888796381c23a171106917610ea6f3c066831bdc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:54:18 +0000 Subject: [PATCH 4/6] Add RestTable example to main page Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- app/page.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/page.tsx b/app/page.tsx index b17f03d..a6e9175 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -14,6 +14,7 @@ import { BadgeInputExample } from "@/registry/new-york/blocks/badge-input/exampl import { RangeInputExample } from "@/registry/new-york/blocks/range-input/example"; import { FilePickerExample } from "@/registry/new-york/blocks/file-picker/example"; import { FormFieldExample } from "@/registry/new-york/blocks/form-field/example"; +import { RestTableExample } from "@/registry/new-york/blocks/rest-table/example"; export default function Home() { return ( @@ -130,6 +131,14 @@ export default function Home() { > + + + + ); From 327fbf86310cf16da0dd33f27231ffd91644a859 Mon Sep 17 00:00:00 2001 From: TechQuery Date: Sat, 20 Dec 2025 03:53:09 +0800 Subject: [PATCH 5/6] [refactor] simplify Copilot's source codes --- README.md | 201 +++++++++--------- components/example/form.tsx | 36 +++- components/example/open-in-v0-button.tsx | 77 ++++--- components/index.ini | 4 +- models/example.ts | 10 +- package.json | 2 +- registry.json | 4 +- .../new-york/blocks/rest-table/example.tsx | 101 ++------- .../new-york/blocks/rest-table/rest-table.tsx | 150 ++++++------- 9 files changed, 261 insertions(+), 324 deletions(-) diff --git a/README.md b/README.md index 5ba5d3f..571687a 100644 --- a/README.md +++ b/README.md @@ -3,44 +3,32 @@ A **Pagination Table** & **Scroll List** component suite for [CRUD operation][1], which is based on [MobX RESTful][2], [React][3] & [Shadcn UI][4]. [![MobX compatibility](https://img.shields.io/badge/Compatible-1?logo=mobx&label=MobX%206%2F7)][5] +[![NPM Dependency](https://img.shields.io/librariesio/github/idea2app/MobX-RESTful-Shadcn.svg)][6] +[![CI & CD](https://github.com/idea2app/MobX-RESTful-Shadcn/actions/workflows/main.yml/badge.svg)][7] ## Components -1. [Badge Bar](https://mobx-restful-shadcn.vercel.app/) -2. [Badge Input](https://mobx-restful-shadcn.vercel.app/) -3. [Image Preview](https://mobx-restful-shadcn.vercel.app/) -4. [File Preview](https://mobx-restful-shadcn.vercel.app/) -5. [File Picker](https://mobx-restful-shadcn.vercel.app/) -6. [File Uploader](https://mobx-restful-shadcn.vercel.app/) -7. [Form Field](https://mobx-restful-shadcn.vercel.app/) -8. [Range Input](https://mobx-restful-shadcn.vercel.app/) -9. [Array Field](https://mobx-restful-shadcn.vercel.app/) -10. [REST Form](https://mobx-restful-shadcn.vercel.app/) -11. [REST Form Modal](https://mobx-restful-shadcn.vercel.app/) -12. [Pager](https://mobx-restful-shadcn.vercel.app/) -13. [REST Table](https://mobx-restful-shadcn.vercel.app/) -14. [Scroll Boundary](https://mobx-restful-shadcn.vercel.app/) -15. [Scroll List](https://mobx-restful-shadcn.vercel.app/) -16. [Searchable Input](https://mobx-restful-shadcn.vercel.app/) +1. [Badge Bar](https://mobx-restful-shadcn.idea2.app/) +2. [Badge Input](https://mobx-restful-shadcn.idea2.app/) +3. [Image Preview](https://mobx-restful-shadcn.idea2.app/) +4. [File Preview](https://mobx-restful-shadcn.idea2.app/) +5. [File Picker](https://mobx-restful-shadcn.idea2.app/) +6. [File Uploader](https://mobx-restful-shadcn.idea2.app/) +7. [Form Field](https://mobx-restful-shadcn.idea2.app/) +8. [Range Input](https://mobx-restful-shadcn.idea2.app/) +9. [Array Field](https://mobx-restful-shadcn.idea2.app/) +10. [REST Form](https://mobx-restful-shadcn.idea2.app/) +11. [REST Form Modal](https://mobx-restful-shadcn.idea2.app/) +12. [Pager](https://mobx-restful-shadcn.idea2.app/) +13. [REST Table](https://mobx-restful-shadcn.idea2.app/) +14. [Scroll Boundary](https://mobx-restful-shadcn.idea2.app/) +15. [Scroll List](https://mobx-restful-shadcn.idea2.app/) +16. [Searchable Input](https://mobx-restful-shadcn.idea2.app/) ## Installation -You can use the `shadcn` CLI to install components from this registry to your project. - -### Prerequisites - -```shell -npm i react \ - mobx \ - mobx-react \ - mobx-i18n \ - mobx-restful -``` - -### Install Components - ```shell -npx shadcn@latest add https://mobx-restful-shadcn.vercel.app/r/rest-table.json +npx shadcn-helper add https://mobx-restful-shadcn.idea2.app/r/rest-table.json ``` Replace `rest-table` with any component name from the list above. @@ -53,17 +41,22 @@ Set up i18n translation model for UI text: ```typescript import { TranslationModel } from "mobx-i18n"; +import { IDType } from "mobx-restful"; export const i18n = new TranslationModel({ en_US: { + load_more: "Load more", + no_more: "No more", + create: "Create", + view: "View", submit: "Submit", cancel: "Cancel", - create: "Create", edit: "Edit", delete: "Delete", - view: "View", - total_x_rows: "Total {{totalCount}} rows", - sure_to_delete_x: "Are you sure to delete {{keys}}?", + total_x_rows: ({ totalCount }: { totalCount: number }) => + `Total ${totalCount} rows`, + sure_to_delete_x: ({ keys }: { keys: IDType[] }) => + `Are you sure to delete ${keys.join(", ")}?`, }, }); ``` @@ -96,95 +89,103 @@ export const repositoryStore = new RepositoryModel("idea2app"); ```tsx import { computed } from "mobx"; import { observer } from "mobx-react"; +import { Component } from "react"; + import { BadgeBar } from "@/components/ui/badge-bar"; import { RestTable, Column } from "@/components/ui/rest-table"; import repositoryStore, { Repository } from "@/models/Repository"; import { i18n } from "@/models/Translation"; -export default observer(() => { - const columns = computed[]>(() => [ - { - key: "full_name", - renderHead: "Repository Name", - renderBody: ({ html_url, full_name }) => ( - - {full_name} - - ), - required: true, - minLength: 3, - invalidMessage: "Input 3 characters at least", - }, - { key: "homepage", type: "url", renderHead: "Home Page" }, - { key: "language", renderHead: "Programming Language" }, - { - key: "topics", - renderHead: "Topic", - renderBody: ({ topics }) => ( - ({ - text, - link: `https://github.com/topics/${text}`, - }))} - /> - ), - }, - { key: "stargazers_count", type: "number", renderHead: "Star Count" }, - { - key: "description", - renderHead: "Description", - rows: 3, - }, - ]).get(); - - return ( - - ); -}); +@observer +export class RepositoryTable extends Component { + @computed + get columns() { + return [ + { + key: "full_name", + renderHead: "Repository Name", + renderBody: ({ html_url, full_name }) => ( + + {full_name} + + ), + required: true, + minLength: 3, + invalidMessage: "Input 3 characters at least", + }, + { key: "homepage", type: "url", renderHead: "Home Page" }, + { key: "language", renderHead: "Programming Language" }, + { + key: "topics", + renderHead: "Topic", + renderBody: ({ topics }) => ( + ({ + text, + link: `https://github.com/topics/${text}`, + }))} + /> + ), + }, + { key: "stargazers_count", type: "number", renderHead: "Star Count" }, + { key: "description", renderHead: "Description", rows: 3 }, + ]; + } + + render() { + return ( + + ); + } +} ``` ### Scroll List ```tsx import { observer } from "mobx-react"; + import { ScrollList } from "@/components/ui/scroll-list"; import repositoryStore from "@/models/Repository"; import { i18n } from "@/models/Translation"; -export default observer(() => ( +export const ScrollListExample = () => ( ( -
- {allItems.map((item) => ( -
-

{item.name}

-

{item.description}

-
+
    + {allItems.map(({ id, name, description }) => ( +
  • +

    {name}

    +

    {description}

    +
  • ))} -
+ )} /> -)); +); ``` ### File Uploader ```tsx -import { FileUploader } from "@/components/ui/file-uploader"; -import fileStore from "@/models/File"; +import { FileModel, FileUploader } from "@/components/ui/file-uploader"; + +class MyFileModel extends FileModel {} + +const store = new MyFileModel(); export const EditorPage = () => ( [] = [ +export const columns: Column[] = [ { key: "full_name", - renderLabel: "Repository Name", + renderHead: "Repository Name", + renderBody: ({ html_url, full_name }) => ( + + {full_name} + + ), required: true, minLength: 3, invalidMessage: "Input 3 characters at least", }, - { key: "homepage", type: "url", renderLabel: "Home Page" }, - { key: "language", renderLabel: "Programming Language" }, + { key: "homepage", type: "url", renderHead: "Home Page" }, + { key: "language", renderHead: "Programming Language" }, { key: "topics", - renderLabel: "Topic", + renderHead: "Topic", + renderBody: ({ topics }) => ( + ({ + text, + link: `https://github.com/topics/${text}`, + }))} + /> + ), renderInput: ({ topics }) => ( [] = [ /> ), }, - { key: "stargazers_count", type: "number", renderLabel: "Star Count" }, - { key: "description", renderLabel: "Description", rows: 3 }, + { key: "stargazers_count", type: "number", renderHead: "Star Count" }, + { key: "description", renderHead: "Description", rows: 3 }, ]; + +export const fields: Field[] = columns.map( + ({ renderHead, renderBody, ...meta }) => ({ + ...meta, + renderLabel: renderHead, + }) +); diff --git a/components/example/open-in-v0-button.tsx b/components/example/open-in-v0-button.tsx index 96f9eaa..0e1772c 100644 --- a/components/example/open-in-v0-button.tsx +++ b/components/example/open-in-v0-button.tsx @@ -1,42 +1,41 @@ -import { Button } from "@/registry/new-york/ui/button" -import { cn } from "@/lib/utils" +import { ComponentProps, FC } from "react"; -export function OpenInV0Button({ - name, - className, -}: { name: string } & React.ComponentProps) { - return ( - - ) -} + + + + + +); diff --git a/components/index.ini b/components/index.ini index e3be92f..ffb497d 100755 --- a/components/index.ini +++ b/components/index.ini @@ -2,4 +2,6 @@ badge button dialog input -label \ No newline at end of file +label +checkbox +table \ No newline at end of file diff --git a/models/example.ts b/models/example.ts index 2f4e437..6558a93 100644 --- a/models/example.ts +++ b/models/example.ts @@ -1,15 +1,23 @@ import { components, operations } from "@octokit/openapi-types"; import { githubClient, RepositoryModel } from "mobx-github"; import { TranslationModel } from "mobx-i18n"; -import { ListModel, Filter } from "mobx-restful"; +import { ListModel, Filter, IDType } from "mobx-restful"; import { buildURLData } from "web-utility"; export const i18n = new TranslationModel({ en_US: { load_more: "Load more", no_more: "No more", + create: "Create", + view: "View", submit: "Submit", cancel: "Cancel", + edit: "Edit", + delete: "Delete", + total_x_rows: ({ totalCount }: { totalCount: number }) => + `Total ${totalCount} rows`, + sure_to_delete_x: ({ keys }: { keys: IDType[] }) => + `Are you sure to delete ${keys.join(", ")}?`, }, }); diff --git a/package.json b/package.json index f780370..a5f2fc5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mobx-restful-shadcn", - "version": "0.6.0", + "version": "1.5.0", "private": true, "scripts": { "postinstall": "shadcn-helper install", diff --git a/registry.json b/registry.json index b0e24a7..4cb3e20 100644 --- a/registry.json +++ b/registry.json @@ -1,7 +1,7 @@ { "$schema": "https://ui.shadcn.com/schema/registry.json", - "name": "acme", - "homepage": "https://acme.com", + "name": "MobX RESTful Shadcn", + "homepage": "https://mobx-restful-shadcn.idea2.app", "items": [ { "name": "complex-component", diff --git a/registry/new-york/blocks/rest-table/example.tsx b/registry/new-york/blocks/rest-table/example.tsx index 00572b4..b39d92d 100644 --- a/registry/new-york/blocks/rest-table/example.tsx +++ b/registry/new-york/blocks/rest-table/example.tsx @@ -1,88 +1,19 @@ "use client"; -import { computed } from "mobx"; -import { TranslationModel } from "mobx-i18n"; -import { observer } from "mobx-react"; -import { GitRepository } from "mobx-github"; +import { columns } from "@/components/example/form"; +import { i18n, repositoryStore } from "@/models/example"; +import { RestTable } from "./rest-table"; -import { BadgeBar } from "../badge-bar/badge-bar"; -import { repositoryStore } from "@/models/example"; -import { Column, RestTable } from "./rest-table"; - -const tableI18n = new TranslationModel({ - en_US: { - load_more: "Load more", - no_more: "No more", - submit: "Submit", - cancel: "Cancel", - create: "Create", - view: "View", - edit: "Edit", - delete: "Delete", - total_x_rows: "Total {{totalCount}} rows", - sure_to_delete_x: "Are you sure to delete {{keys}}?", - }, -}); - -export const RestTableExample = observer(() => { - const columns = computed[]>(() => [ - { - key: "full_name", - renderHead: "Repository Name", - renderBody: ({ html_url, full_name }) => ( - - {full_name} - - ), - required: true, - minLength: 3, - invalidMessage: "Input 3 characters at least", - }, - { key: "homepage", type: "url", renderHead: "Home Page" }, - { key: "language", renderHead: "Programming Language" }, - { - key: "topics", - renderHead: "Topic", - renderBody: ({ topics }) => ( - ({ - text, - link: `https://github.com/topics/${text}`, - }))} - /> - ), - }, - { key: "stargazers_count", type: "number", renderHead: "Star Count" }, - { - key: "description", - renderHead: "Description", - rows: 3, - renderBody: ({ description }) => ( -

- {description} -

- ), - }, - ]).get(); - - return ( -
- console.log("Checked keys:", keys)} - /> -
- ); -}); +export const RestTableExample = () => ( +
+ console.log("Checked keys:", keys)} + /> +
+); diff --git a/registry/new-york/blocks/rest-table/rest-table.tsx b/registry/new-york/blocks/rest-table/rest-table.tsx index 6bf7269..f11476d 100644 --- a/registry/new-york/blocks/rest-table/rest-table.tsx +++ b/registry/new-york/blocks/rest-table/rest-table.tsx @@ -1,5 +1,6 @@ "use client"; +import debounce from "lodash.debounce"; import { computed, observable } from "mobx"; import { TranslationModel } from "mobx-i18n"; import { observer } from "mobx-react"; @@ -7,9 +8,7 @@ import { ObservedComponent } from "mobx-react-helper"; import { DataObject, Filter, IDType } from "mobx-restful"; import { HTMLAttributes, ReactNode } from "react"; import { isEmpty } from "web-utility"; -import debounce from "lodash.debounce"; -import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { @@ -37,32 +36,21 @@ export interface Column type Translator = RestFormProps["translator"] & TranslationModel< string, - | "create" - | "view" - | "edit" - | "delete" - | "total_x_rows" - | "sure_to_delete_x" + "create" | "view" | "edit" | "delete" | "total_x_rows" | "sure_to_delete_x" >; export interface RestTableProps< D extends DataObject, F extends Filter = Filter -> extends Omit, "onSubmit" | "onReset" | "children"> { +> extends Omit, "onSubmit" | "onReset">, + Pick, "size" | "store" | "onSubmit" | "onReset"> { filter?: F; filterFields?: Field[]; editable?: boolean; deletable?: boolean; columns: Column[]; translator: Translator; - size?: "default" | "sm" | "lg"; onCheck?: (keys: IDType[]) => any; - striped?: boolean; - hover?: boolean; - responsive?: boolean; - store?: RestFormProps["store"]; - onSubmit?: RestFormProps["onSubmit"]; - onReset?: RestFormProps["onReset"]; } @observer @@ -75,10 +63,8 @@ export class RestTable< componentDidMount() { const { store, filter } = this.props; - if (store) { - store.clear(); - store.getList(filter); - } + store?.clear(); + store?.getList(filter); } @computed @@ -105,14 +91,14 @@ export class RestTable< toggleCheckAll = () => { const { store, onCheck } = this.props; + if (!store) return; const { indexKey, currentPage } = store; - this.checkedKeys = - this.checkedKeys.length - ? [] - : currentPage.map(({ [indexKey]: ID }) => ID); + this.checkedKeys = this.checkedKeys.length + ? [] + : currentPage.map(({ [indexKey]: ID }) => ID); onCheck?.(this.checkedKeys); }; @@ -121,6 +107,7 @@ export class RestTable< get checkColumn(): Column { const { checkedKeys, toggleCheckAll } = this; const { store } = this.observedProps; + if (!store) return {} as Column; const { indexKey, currentPage } = store; @@ -130,9 +117,7 @@ export class RestTable< - checkedKeys.includes(ID) - ) + currentPage.every(({ [indexKey]: ID }) => checkedKeys.includes(ID)) } onCheckedChange={toggleCheckAll} aria-label="Select all" @@ -152,32 +137,32 @@ export class RestTable< get operateColumn(): Column { const { editable, deletable, columns, store, translator } = this.observedProps; + if (!store) return {} as Column; - const { fieldSize } = this; - const { t } = translator; - const readOnly = columns.every(({ readOnly }) => readOnly); - const disabled = columns.every(({ disabled }) => disabled); + const { fieldSize } = this, + { t } = translator, + readOnly = columns.every(({ readOnly }) => readOnly), + disabled = columns.every(({ disabled }) => disabled); return { renderHead: () => <>, renderBody: (data) => (
- {!disabled && - editable && ( - - )} + {!disabled && editable && ( + + )} {deletable && ( @@ -188,7 +173,7 @@ export class RestTable< } @computed - get columns(): Column[] { + get columns() { const { editable, deletable, columns, onCheck } = this.observedProps; return [ @@ -216,8 +201,7 @@ export class RestTable< @computed get editing() { - const { store } = this.observedProps; - return !isEmpty(store?.currentOne); + return !isEmpty(this.observedProps.store?.currentOne); } renderCustomBody = ({ @@ -232,10 +216,10 @@ export class RestTable< ? ({ [key!]: value }) => value && ( {value as string} @@ -244,8 +228,8 @@ export class RestTable< ? ({ [key!]: value }) => value && ( {value as string} @@ -253,7 +237,7 @@ export class RestTable< : type === "tel" ? ({ [key!]: value }) => value && ( - + {value as string} ) @@ -266,9 +250,7 @@ export class RestTable< : options || multiple ? ({ [key!]: value }) => value && ( - ({ text }))} - /> + ({ text }))} /> ) : !options && rows ? ({ [key!]: value }) => ( @@ -279,7 +261,8 @@ export class RestTable< : undefined; renderTable() { - const { store, className: _, ...tableProps } = this.props; + const { store } = this.props; + if (!store) return null; const { hasHeader, hasFooter, columns, editing } = this; @@ -308,7 +291,7 @@ export class RestTable<
-
+
@@ -353,14 +336,13 @@ export class RestTable< if (store && store.downloading < 1) store.getList(filter, pageIndex, pageSize); - }, 300); + }); async deleteList(keys: IDType[]) { const { translator, store } = this.props; - if (!store) return; if (confirm(translator.t("sure_to_delete_x", { keys }))) - for (const key of keys) await store.deleteOne(key); + for (const key of keys) await store?.deleteOne(key); } render() { @@ -373,9 +355,6 @@ export class RestTable< translator, onSubmit, onReset, - striped, - hover, - responsive, ...props } = this.props; @@ -386,42 +365,36 @@ export class RestTable< const { indexKey, pageSize, pageIndex, pageCount, totalCount } = store; return ( -
-
+
+
{filterFields && ( store.getList(filter as F, 1)} - onReset={() => store.getList({} as F, 1)} + onSubmit={(filter) => store.getList(filter, 1)} + onReset={() => store.getList({}, 1)} /> )} -
-
- {deletable && ( - - )} - {editable && ( - - )} -
-
+ {deletable && ( + + )} + {editable && ( + + )}
{this.renderTable()} @@ -432,7 +405,6 @@ export class RestTable< {t("total_x_rows", { totalCount })} )} - Date: Sat, 20 Dec 2025 04:19:43 +0800 Subject: [PATCH 6/6] [fix] 2 detail bugs --- app/globals.css | 9 +++++++-- registry/new-york/blocks/rest-table/example.tsx | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/globals.css b/app/globals.css index 1b09b35..2a53fcb 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,6 +1,10 @@ @import "tailwindcss"; @import "tw-animate-css"; +.overflow-x-auto { + overflow-x: auto; +} + @custom-variant dark (&:is(.dark *)); @theme inline { @@ -44,8 +48,9 @@ } :root { - --font-geist-sans: 'Geist Sans', ui-sans-serif, system-ui, sans-serif; - --font-geist-mono: 'Geist Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace; + --font-geist-sans: "Geist Sans", ui-sans-serif, system-ui, sans-serif; + --font-geist-mono: "Geist Mono", ui-monospace, "Cascadia Code", + "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace; --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); diff --git a/registry/new-york/blocks/rest-table/example.tsx b/registry/new-york/blocks/rest-table/example.tsx index b39d92d..1f7eb42 100644 --- a/registry/new-york/blocks/rest-table/example.tsx +++ b/registry/new-york/blocks/rest-table/example.tsx @@ -1,9 +1,13 @@ "use client"; +import { configure } from "mobx"; + import { columns } from "@/components/example/form"; import { i18n, repositoryStore } from "@/models/example"; import { RestTable } from "./rest-table"; +configure({ enforceActions: "never" }); + export const RestTableExample = () => (