Skip to content

Commit 9023a7c

Browse files
CopilotTechQuery
andauthored
[add] FileUploader, RestForm, RestFormModal & SearchableInput components with Shadcn UI (#4)
Co-authored-by: TechQuery <[email protected]>
1 parent dcb0019 commit 9023a7c

File tree

17 files changed

+1078
-3
lines changed

17 files changed

+1078
-3
lines changed

app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ComponentCard } from "@/components/component-card";
1+
import { ComponentCard } from "@/components/example/component-card";
22
import { HelloWorld } from "@/registry/new-york/blocks/hello-world/hello-world";
33
import { ExampleForm } from "@/registry/new-york/blocks/example-form/example-form";
44
import PokemonPage from "@/registry/new-york/blocks/complex-component/page";

components/example/form.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { GitRepository } from "mobx-github";
2+
3+
import { i18n, topicStore } from "@/models/example";
4+
import { Field } from "@/registry/new-york/blocks/rest-form/rest-form";
5+
import { SearchableInput } from "@/registry/new-york/blocks/searchable-input/searchable-input";
6+
7+
export const fields: Field<GitRepository>[] = [
8+
{
9+
key: "full_name",
10+
renderLabel: "Repository Name",
11+
required: true,
12+
minLength: 3,
13+
invalidMessage: "Input 3 characters at least",
14+
},
15+
{ key: "homepage", type: "url", renderLabel: "Home Page" },
16+
{ key: "language", renderLabel: "Programming Language" },
17+
{
18+
key: "topics",
19+
renderLabel: "Topic",
20+
renderInput: ({ topics }) => (
21+
<SearchableInput
22+
translator={i18n}
23+
store={topicStore}
24+
labelKey="name"
25+
valueKey="name"
26+
placeholder="search GitHub topics"
27+
multiple
28+
defaultValue={topics?.map((value) => ({ value, label: value }))}
29+
/>
30+
),
31+
},
32+
{ key: "stargazers_count", type: "number", renderLabel: "Star Count" },
33+
{ key: "description", renderLabel: "Description", rows: 3 },
34+
];

models/example.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { components, operations } from "@octokit/openapi-types";
2+
import { githubClient, RepositoryModel } from "mobx-github";
3+
import { TranslationModel } from "mobx-i18n";
4+
import { ListModel, Filter } from "mobx-restful";
5+
import { buildURLData } from "web-utility";
6+
7+
export const i18n = new TranslationModel({
8+
en_US: {
9+
load_more: "Load more",
10+
no_more: "No more",
11+
submit: "Submit",
12+
cancel: "Cancel",
13+
},
14+
});
15+
16+
type Topic = components["schemas"]["topic-search-result-item"];
17+
18+
type TopicSearchResponse =
19+
operations["search/topics"]["responses"]["200"]["content"]["application/json"];
20+
21+
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
22+
23+
githubClient.use(({ request }, next) => {
24+
if (GITHUB_TOKEN)
25+
request.headers = {
26+
...request.headers,
27+
Authorization: `Bearer ${GITHUB_TOKEN}`,
28+
};
29+
return next();
30+
});
31+
32+
class GitHubTopicModel extends ListModel<Topic> {
33+
baseURI = "search/topics";
34+
client = githubClient;
35+
36+
async loadPage(pageIndex: number, pageSize: number, { name }: Filter<Topic>) {
37+
const { body } = await this.client.get<TopicSearchResponse>(
38+
`${this.baseURI}?${buildURLData({
39+
q: name,
40+
page: pageIndex,
41+
per_page: pageSize,
42+
})}`
43+
);
44+
return { pageData: body!.items, totalCount: body!.total_count };
45+
}
46+
}
47+
48+
export const repositoryStore = new RepositoryModel("idea2app"),
49+
topicStore = new GitHubTopicModel();

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@babel/plugin-transform-typescript": "^7.28.5",
3636
"@babel/preset-react": "^7.28.5",
3737
"@eslint/eslintrc": "^3.3.3",
38+
"@octokit/openapi-types": "^27.0.0",
3839
"@tailwindcss/postcss": "^4.1.18",
3940
"@types/lodash.debounce": "^4.0.9",
4041
"@types/node": "^22.19.3",

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

registry.json

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,95 @@
222222
"type": "registry:component"
223223
}
224224
]
225+
},
226+
{
227+
"name": "file-uploader",
228+
"type": "registry:component",
229+
"title": "File Uploader",
230+
"description": "A file uploader component with drag-and-drop support for managing multiple files using MobX.",
231+
"registryDependencies": ["file-picker"],
232+
"dependencies": [
233+
"mobx",
234+
"mobx-react",
235+
"mobx-react-helper",
236+
"mobx-restful"
237+
],
238+
"files": [
239+
{
240+
"path": "registry/new-york/blocks/file-uploader/file-uploader.tsx",
241+
"type": "registry:component"
242+
}
243+
]
244+
},
245+
{
246+
"name": "rest-form",
247+
"type": "registry:component",
248+
"title": "REST Form",
249+
"description": "A comprehensive form component for CRUD operations with MobX RESTful integration, supporting various field types and validation.",
250+
"registryDependencies": [
251+
"button",
252+
"label",
253+
"badge-input",
254+
"file-preview",
255+
"file-uploader",
256+
"form-field"
257+
],
258+
"dependencies": [
259+
"mobx",
260+
"mobx-i18n",
261+
"mobx-react",
262+
"mobx-react-helper",
263+
"mobx-restful",
264+
"web-utility"
265+
],
266+
"files": [
267+
{
268+
"path": "registry/new-york/blocks/rest-form/rest-form.tsx",
269+
"type": "registry:component"
270+
}
271+
]
272+
},
273+
{
274+
"name": "rest-form-modal",
275+
"type": "registry:component",
276+
"title": "REST Form Modal",
277+
"description": "A modal wrapper for REST Form component, displaying forms in a dialog for editing data.",
278+
"registryDependencies": ["dialog", "rest-form"],
279+
"dependencies": ["mobx-react", "mobx-restful", "web-utility"],
280+
"files": [
281+
{
282+
"path": "registry/new-york/blocks/rest-form-modal/rest-form-modal.tsx",
283+
"type": "registry:component"
284+
}
285+
]
286+
},
287+
{
288+
"name": "searchable-input",
289+
"type": "registry:component",
290+
"title": "Searchable Input",
291+
"description": "A searchable input component with autocomplete, supporting multiple selections and inline creation of new items.",
292+
"registryDependencies": [
293+
"button",
294+
"input",
295+
"badge-bar",
296+
"badge-input",
297+
"rest-form-modal",
298+
"scroll-list"
299+
],
300+
"dependencies": [
301+
"lodash.debounce",
302+
"mobx",
303+
"mobx-react",
304+
"mobx-react-helper",
305+
"mobx-restful",
306+
"web-utility"
307+
],
308+
"files": [
309+
{
310+
"path": "registry/new-york/blocks/searchable-input/searchable-input.tsx",
311+
"type": "registry:component"
312+
}
313+
]
225314
}
226315
]
227316
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use client";
2+
3+
import { FileModel, FileUploader } from "./file-uploader";
4+
5+
class MyFileModel extends FileModel {}
6+
7+
const store = new MyFileModel();
8+
9+
export const FileUploaderExample = () => (
10+
<div className="w-full space-y-8">
11+
<div>
12+
<h3 className="text-lg font-semibold mb-4">Single File Upload</h3>
13+
<FileUploader
14+
store={store}
15+
name="single-file"
16+
accept="image/*"
17+
defaultValue={[]}
18+
/>
19+
<p className="text-sm text-muted-foreground mt-2">
20+
Upload a single image file
21+
</p>
22+
</div>
23+
24+
<div>
25+
<h3 className="text-lg font-semibold mb-4">Multiple Files Upload</h3>
26+
<FileUploader
27+
store={store}
28+
name="multiple-files"
29+
accept="image/*"
30+
multiple
31+
defaultValue={[]}
32+
/>
33+
<p className="text-sm text-muted-foreground mt-2">
34+
Upload multiple image files (drag to reorder)
35+
</p>
36+
</div>
37+
38+
<div>
39+
<h3 className="text-lg font-semibold mb-4">Uploaded Files</h3>
40+
<pre className="p-4 bg-muted rounded-md text-xs overflow-auto">
41+
{JSON.stringify(store.files, null, 2)}
42+
</pre>
43+
</div>
44+
</div>
45+
);

0 commit comments

Comments
 (0)