Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 215 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,229 @@
# 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]
[![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]

> [!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.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/)

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.
```shell
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.

## Configuration

### Internationalization

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",
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(", ")}?`,
},
});
```

### 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 { 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";

@observer
export class RepositoryTable extends Component {
@computed
get columns() {
return [
{
key: "full_name",
renderHead: "Repository Name",
renderBody: ({ html_url, full_name }) => (
<a target="_blank" href={html_url}>
{full_name}
</a>
),
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 }) => (
<BadgeBar
list={(topics || []).map((text) => ({
text,
link: `https://github.com/topics/${text}`,
}))}
/>
),
},
{ key: "stargazers_count", type: "number", renderHead: "Star Count" },
{ key: "description", renderHead: "Description", rows: 3 },
];
}

render() {
return (
<RestTable
editable
deletable
columns={this.columns}
store={repositoryStore}
translator={i18n}
onCheck={console.log}
/>
);
}
}
```

### 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 const ScrollListExample = () => (
<ScrollList
translator={i18n}
store={repositoryStore}
renderList={(allItems) => (
<ul className="grid grid-cols-1 md:grid-cols-2 gap-4">
{allItems.map(({ id, name, description }) => (
<li key={id} className="p-4 border rounded">
<h3>{name}</h3>
<p>{description}</p>
</li>
))}
</ul>
)}
/>
);
```

### File Uploader

```tsx
import { FileModel, FileUploader } from "@/components/ui/file-uploader";

class MyFileModel extends FileModel {}

const store = new MyFileModel();

export const EditorPage = () => (
<FileUploader
store={store}
accept="image/*"
name="images"
multiple
required
onChange={console.log}
/>
);
```

## 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: `pnpm install`
3. Run development server: `pnpm dev`
4. Build registry: `pnpm registry:build`
5. Build project: `pnpm 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)
- [Component Registry Documentation](https://ui.shadcn.com/docs/registry)
- [MobX RESTful Documentation][2]

[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/
[6]: https://libraries.io/npm/mobx-restful-shadcn
[7]: https://github.com/idea2app/MobX-RESTful-Shadcn/actions/workflows/main.yml
9 changes: 7 additions & 2 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
@import "tailwindcss";
@import "tw-animate-css";

.overflow-x-auto {
overflow-x: auto;
}

@custom-variant dark (&:is(.dark *));

@theme inline {
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -130,6 +131,14 @@ export default function Home() {
>
<FormFieldExample />
</ComponentCard>

<ComponentCard
name="rest-table"
description="A comprehensive pagination table component for CRUD operations with MobX RESTful integration."
minHeight="min-h-[600px]"
>
<RestTableExample />
</ComponentCard>
</main>
</div>
);
Expand Down
36 changes: 29 additions & 7 deletions components/example/form.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
import { GitRepository } from "mobx-github";

import { i18n, topicStore } from "@/models/example";
import { BadgeBar } from "@/registry/new-york/blocks/badge-bar/badge-bar";
import { Field } from "@/registry/new-york/blocks/rest-form/rest-form";
import { Column } from "@/registry/new-york/blocks/rest-table/rest-table";
import { SearchableInput } from "@/registry/new-york/blocks/searchable-input/searchable-input";

export const fields: Field<GitRepository>[] = [
export const columns: Column<GitRepository>[] = [
{
key: "full_name",
renderLabel: "Repository Name",
renderHead: "Repository Name",
renderBody: ({ html_url, full_name }) => (
<a target="_blank" href={html_url} rel="noreferrer">
{full_name}
</a>
),
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 }) => (
<BadgeBar
list={(topics || []).map((text) => ({
text,
link: `https://github.com/topics/${text}`,
}))}
/>
),
renderInput: ({ topics }) => (
<SearchableInput
translator={i18n}
Expand All @@ -29,6 +44,13 @@ export const fields: Field<GitRepository>[] = [
/>
),
},
{ 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<GitRepository>[] = columns.map(
({ renderHead, renderBody, ...meta }) => ({
...meta,
renderLabel: renderHead,
})
);
Loading