dilums commited on
Commit
2132a82
1 Parent(s): 5295639

initial commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .eslintrc.json +3 -0
  2. .gitignore +35 -0
  3. Dockerfile +65 -0
  4. README.md +2 -1
  5. app/api/detect/data.ts +21 -0
  6. app/api/detect/route.ts +58 -0
  7. app/api/detect/utils.ts +33 -0
  8. app/favicon.ico +0 -0
  9. app/globals.css +83 -0
  10. app/layout.tsx +29 -0
  11. app/page.tsx +35 -0
  12. components.json +16 -0
  13. components/ImageInput/Highlight/Highlight.tsx +53 -0
  14. components/ImageInput/Highlight/index.tsx +3 -0
  15. components/ImageInput/ImageInput.tsx +25 -0
  16. components/ImageInput/Preview/Markers/Markers.tsx +60 -0
  17. components/ImageInput/Preview/Markers/index.tsx +3 -0
  18. components/ImageInput/Preview/Preview.tsx +42 -0
  19. components/ImageInput/Preview/index.tsx +3 -0
  20. components/ImageInput/index.tsx +3 -0
  21. components/ModelSelector/ModelSelector.tsx +59 -0
  22. components/ModelSelector/index.tsx +3 -0
  23. components/Results/Filters/Filters.tsx +36 -0
  24. components/Results/Filters/index.tsx +3 -0
  25. components/Results/Item/Item.tsx +53 -0
  26. components/Results/Item/index.tsx +3 -0
  27. components/Results/Results.tsx +19 -0
  28. components/Results/index.tsx +3 -0
  29. components/layout/Error/Error.tsx +26 -0
  30. components/layout/Error/index.tsx +3 -0
  31. components/layout/Header/Header.tsx +27 -0
  32. components/layout/Header/index.tsx +3 -0
  33. components/layout/Loading/Loading.tsx +22 -0
  34. components/layout/Loading/index.tsx +3 -0
  35. components/layout/SuccessWrapper/SuccessWrapper.tsx +13 -0
  36. components/layout/SuccessWrapper/index.tsx +3 -0
  37. components/layout/ThemeProvider/ThemeProvider.tsx +11 -0
  38. components/layout/ThemeProvider/index.tsx +3 -0
  39. components/layout/ThemeToggle/ThemeToggle.tsx +37 -0
  40. components/layout/ThemeToggle/index.tsx +3 -0
  41. components/ui/alert.tsx +59 -0
  42. components/ui/button.tsx +57 -0
  43. components/ui/card.tsx +76 -0
  44. components/ui/dropdown-menu.tsx +205 -0
  45. components/ui/input.tsx +25 -0
  46. components/ui/label.tsx +26 -0
  47. components/ui/select.tsx +120 -0
  48. components/ui/skeleton.tsx +15 -0
  49. components/ui/slider.tsx +28 -0
  50. components/ui/switch.tsx +29 -0
.eslintrc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "extends": "next/core-web-vitals"
3
+ }
.gitignore ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+
8
+ # testing
9
+ /coverage
10
+
11
+ # next.js
12
+ /.next/
13
+ /out/
14
+
15
+ # production
16
+ /build
17
+
18
+ # misc
19
+ .DS_Store
20
+ *.pem
21
+
22
+ # debug
23
+ npm-debug.log*
24
+ yarn-debug.log*
25
+ yarn-error.log*
26
+
27
+ # local env files
28
+ .env*.local
29
+
30
+ # vercel
31
+ .vercel
32
+
33
+ # typescript
34
+ *.tsbuildinfo
35
+ next-env.d.ts
Dockerfile ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-alpine AS base
2
+
3
+ # Install dependencies only when needed
4
+ FROM base AS deps
5
+ # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
6
+ RUN apk add --no-cache libc6-compat
7
+ WORKDIR /app
8
+
9
+ # Install dependencies based on the preferred package manager
10
+ COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
11
+ RUN \
12
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
13
+ elif [ -f package-lock.json ]; then npm ci; \
14
+ elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
15
+ else echo "Lockfile not found." && exit 1; \
16
+ fi
17
+
18
+ # Uncomment the following lines if you want to use a secret at buildtime,
19
+ # for example to access your private npm packages
20
+ # RUN --mount=type=secret,id=HF_EXAMPLE_SECRET,mode=0444,required=true \
21
+ # $(cat /run/secrets/HF_EXAMPLE_SECRET)
22
+
23
+ # Rebuild the source code only when needed
24
+ FROM base AS builder
25
+ WORKDIR /app
26
+ COPY --from=deps /app/node_modules ./node_modules
27
+ COPY . .
28
+
29
+ # Next.js collects completely anonymous telemetry data about general usage.
30
+ # Learn more here: https://nextjs.org/telemetry
31
+ # Uncomment the following line in case you want to disable telemetry during the build.
32
+ # ENV NEXT_TELEMETRY_DISABLED 1
33
+
34
+ # RUN yarn build
35
+
36
+ # If you use yarn, comment out this line and use the line above
37
+ RUN npm run build
38
+
39
+ # Production image, copy all the files and run next
40
+ FROM base AS runner
41
+ WORKDIR /app
42
+
43
+ ENV NODE_ENV production
44
+ # Uncomment the following line in case you want to disable telemetry during runtime.
45
+ # ENV NEXT_TELEMETRY_DISABLED 1
46
+
47
+ RUN addgroup --system --gid 1001 nodejs
48
+ RUN adduser --system --uid 1001 nextjs
49
+
50
+ COPY --from=builder /app/public ./public
51
+
52
+ # Automatically leverage output traces to reduce image size
53
+ # https://nextjs.org/docs/advanced-features/output-file-tracing
54
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
55
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
56
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/cache ./.next/cache
57
+ # COPY --from=builder --chown=nextjs:nodejs /app/.next/cache/fetch-cache ./.next/cache/fetch-cache
58
+
59
+ USER nextjs
60
+
61
+ EXPOSE 3000
62
+
63
+ ENV PORT 3000
64
+
65
+ CMD ["node", "server.js"]
README.md CHANGED
@@ -4,8 +4,9 @@ emoji: 🏢
4
  colorFrom: green
5
  colorTo: gray
6
  sdk: docker
 
7
  pinned: false
8
  license: mit
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
4
  colorFrom: green
5
  colorTo: gray
6
  sdk: docker
7
+ app_port: 3000
8
  pinned: false
9
  license: mit
10
  ---
11
 
12
+
app/api/detect/data.ts ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ type Data = {
2
+ score: number;
3
+ label: string;
4
+ box: {
5
+ xmin: number;
6
+ ymin: number;
7
+ xmax: number;
8
+ ymax: number;
9
+ };
10
+ }[];
11
+ export function processData(data: Data) {
12
+ return data.map((i, index) => ({
13
+ score: i.score,
14
+ label: i.label,
15
+ x: i.box.xmin,
16
+ y: i.box.ymin,
17
+ width: i.box.xmax - i.box.xmin,
18
+ height: i.box.ymax - i.box.ymin,
19
+ key: index,
20
+ }));
21
+ }
app/api/detect/route.ts ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextResponse } from "next/server";
2
+ import { extractPart, getImageDimensions, isImageValid } from "./utils";
3
+ import { processData } from "./data";
4
+ import constants from "@/constants";
5
+ import type { DetectResponse } from "@/types";
6
+
7
+ async function query(file: File, model: string) {
8
+ const response = await fetch(
9
+ `https://api-inference.huggingface.co/models/${model}`,
10
+ {
11
+ headers: {
12
+ Authorization: `Bearer ${process.env.HF_TOKEN}`,
13
+ },
14
+ method: "POST",
15
+ body: file,
16
+ }
17
+ );
18
+ const result = await response.json();
19
+ return result;
20
+ }
21
+
22
+ export async function POST(request: Request) {
23
+ const formData = await request.formData();
24
+ const image = formData.get("file");
25
+ const model = formData.get("model") as string;
26
+
27
+ if (image instanceof Blob) {
28
+ const buffer = await image.arrayBuffer();
29
+ const isValidImage = await isImageValid(buffer);
30
+ if (isValidImage) {
31
+ const { width, height } = await getImageDimensions(buffer);
32
+ const matches = [];
33
+ const labels: string[] = [];
34
+
35
+ const result = await query(image, model);
36
+ if (Array.isArray(result)) {
37
+ const processed = processData(result);
38
+ for (let i = 0; i < processed.length; i++) {
39
+ const item = processed[i];
40
+ const extract = await extractPart(buffer, item);
41
+ const labelIndex = labels.findIndex((i) => i === item.label);
42
+ let colorIndex;
43
+ if (labelIndex !== -1) {
44
+ colorIndex = labelIndex % constants.INDICATOR_COLORS.length;
45
+ } else {
46
+ colorIndex = labels.length % constants.INDICATOR_COLORS.length;
47
+ labels.push(item.label);
48
+ }
49
+ matches.push({ ...item, extract, colorIndex });
50
+ }
51
+
52
+ const res: DetectResponse = { width, height, matches, labels };
53
+ return NextResponse.json(res);
54
+ }
55
+ }
56
+ }
57
+ return NextResponse.json({ error: true });
58
+ }
app/api/detect/utils.ts ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sharp from "sharp";
2
+
3
+ export async function isImageValid(arrayBuffer: ArrayBuffer): Promise<boolean> {
4
+ try {
5
+ const metadata = await sharp(Buffer.from(arrayBuffer)).metadata();
6
+ return metadata.format !== undefined;
7
+ } catch (error) {
8
+ return false;
9
+ }
10
+ }
11
+
12
+ export async function getImageDimensions(
13
+ arrayBuffer: ArrayBuffer
14
+ ): Promise<{ width: number; height: number }> {
15
+ const metadata = await sharp(Buffer.from(arrayBuffer)).metadata();
16
+ return { width: metadata.width || 0, height: metadata.height || 0 };
17
+ }
18
+
19
+ export async function extractPart(
20
+ arrayBuffer: ArrayBuffer,
21
+ {
22
+ x,
23
+ y,
24
+ width,
25
+ height,
26
+ }: { x: number; y: number; width: number; height: number }
27
+ ) {
28
+ const out = await sharp(arrayBuffer)
29
+ .extract({ left: x, top: y, width, height })
30
+ .toBuffer();
31
+ const img = out.toString("base64");
32
+ return img;
33
+ }
app/favicon.ico ADDED
app/globals.css ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 240 10% 3.9%;
9
+
10
+ --card: 0 0% 100%;
11
+ --card-foreground: 240 10% 3.9%;
12
+
13
+ --popover: 0 0% 100%;
14
+ --popover-foreground: 240 10% 3.9%;
15
+
16
+ --primary: 240 5.9% 10%;
17
+ --primary-foreground: 0 0% 98%;
18
+
19
+ --secondary: 240 4.8% 95.9%;
20
+ --secondary-foreground: 240 5.9% 10%;
21
+
22
+ --muted: 240 4.8% 95.9%;
23
+ --muted-foreground: 240 3.8% 46.1%;
24
+
25
+ --accent: 240 4.8% 95.9%;
26
+ --accent-foreground: 240 5.9% 10%;
27
+
28
+ --destructive: 0 84.2% 60.2%;
29
+ --destructive-foreground: 0 0% 98%;
30
+
31
+ --border: 240 5.9% 90%;
32
+ --input: 240 5.9% 90%;
33
+ --ring: 240 10% 3.9%;
34
+
35
+ --radius: 0.5rem;
36
+ }
37
+
38
+ .dark {
39
+ --background: 240 10% 3.9%;
40
+ --foreground: 0 0% 98%;
41
+
42
+ --card: 240 10% 3.9%;
43
+ --card-foreground: 0 0% 98%;
44
+
45
+ --popover: 240 10% 3.9%;
46
+ --popover-foreground: 0 0% 98%;
47
+
48
+ --primary: 0 0% 98%;
49
+ --primary-foreground: 240 5.9% 10%;
50
+
51
+ --secondary: 240 3.7% 15.9%;
52
+ --secondary-foreground: 0 0% 98%;
53
+
54
+ --muted: 240 3.7% 15.9%;
55
+ --muted-foreground: 240 5% 64.9%;
56
+
57
+ --accent: 240 3.7% 15.9%;
58
+ --accent-foreground: 0 0% 98%;
59
+
60
+ --destructive: 0 62.8% 30.6%;
61
+ --destructive-foreground: 0 0% 98%;
62
+
63
+ --border: 240 3.7% 15.9%;
64
+ --input: 240 3.7% 15.9%;
65
+ --ring: 240 4.9% 83.9%;
66
+ }
67
+ }
68
+
69
+ @layer base {
70
+ * {
71
+ @apply border-border;
72
+ }
73
+ body {
74
+ @apply bg-background text-foreground;
75
+ }
76
+ }
77
+
78
+ .dark .score-fg{
79
+ background-color: #3d8a21 ;
80
+ }
81
+ .light .score-fg{
82
+ background-color: #8ddf9a;
83
+ }
app/layout.tsx ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import "./globals.css";
2
+ import type { Metadata } from "next";
3
+ import { Inter } from "next/font/google";
4
+ import ThemeProvider from "@/components/layout/ThemeProvider";
5
+ import Header from "@/components/layout/Header";
6
+
7
+ const inter = Inter({ subsets: ["latin"] });
8
+
9
+ export const metadata: Metadata = {
10
+ title: "Object Detection",
11
+ description: "Object detection with different models",
12
+ };
13
+
14
+ export default function RootLayout({
15
+ children,
16
+ }: {
17
+ children: React.ReactNode;
18
+ }) {
19
+ return (
20
+ <html lang="en" suppressHydrationWarning>
21
+ <body className={inter.className}>
22
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
23
+ <Header />
24
+ <main className="max-w-5xl mx-auto pt-20 px-4">{children}</main>
25
+ </ThemeProvider>
26
+ </body>
27
+ </html>
28
+ );
29
+ }
app/page.tsx ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Card, CardContent, CardHeader } from "@/components/ui/card";
2
+ import Error from "@/components/layout/Error";
3
+ import Loading from "@/components/layout/Loading";
4
+ import SuccessWrapper from "@/components/layout/SuccessWrapper";
5
+ import ImageInput from "@/components/ImageInput";
6
+ import Results from "@/components/Results";
7
+ import ModelSelector from "@/components/ModelSelector";
8
+ export default function Home() {
9
+ return (
10
+ <main className="pb-20">
11
+ <Card>
12
+ <CardContent className="px-4 pt-4">
13
+ <Card className="w-full mb-4">
14
+ <ModelSelector />
15
+ </Card>
16
+ <ImageInput />
17
+ </CardContent>
18
+ </Card>
19
+ <Card className="mt-6">
20
+ <CardHeader>
21
+ <div className="flex items-center justify-between">
22
+ <h3 className="tracking-tight font-bold text-xl">Results</h3>
23
+ </div>
24
+ </CardHeader>
25
+ <CardContent className="px-4">
26
+ <Loading />
27
+ <Error />
28
+ <SuccessWrapper>
29
+ <Results />
30
+ </SuccessWrapper>
31
+ </CardContent>
32
+ </Card>
33
+ </main>
34
+ );
35
+ }
components.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.js",
8
+ "css": "app/globals.css",
9
+ "baseColor": "zinc",
10
+ "cssVariables": true
11
+ },
12
+ "aliases": {
13
+ "components": "@/components",
14
+ "utils": "@/lib/utils"
15
+ }
16
+ }
components/ImageInput/Highlight/Highlight.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import useStore from "@/store";
2
+ type CompProps = {
3
+ parentRef: React.MutableRefObject<HTMLDivElement | null>;
4
+ };
5
+
6
+ function getPos(ref: React.MutableRefObject<HTMLDivElement | null>) {
7
+ if (!ref.current) {
8
+ return [0, 0];
9
+ }
10
+ const rect = ref.current.getBoundingClientRect();
11
+ return [rect.x + rect.width + 24, rect.top + 24];
12
+ }
13
+
14
+ export default function Highlight({ parentRef }: CompProps) {
15
+ const item = useStore((state) => state.hovering);
16
+
17
+ if (item === null) {
18
+ return null;
19
+ }
20
+
21
+ const [left, top] = getPos(parentRef);
22
+ return (
23
+ <div
24
+ className="fixed z-50 w-52 bg-white shadow-md dark:bg-zinc-950"
25
+ style={{ top: `${top}px`, left: `${left}px` }}
26
+ >
27
+ <div className="border rounded-md overflow-hidden">
28
+ <div className="border-b py-1 px-2 text-center font-semibold capitalize">
29
+ {item.label}
30
+ </div>
31
+ <div className="p-2">
32
+ <div className="relative overflow-hidden aspect-square">
33
+ <img
34
+ src={`data:image/png;base64,${item.extract}`}
35
+ className="absolute left-0 top-0 w-full h-full object-contain"
36
+ />
37
+ </div>
38
+ </div>
39
+ <div className="px-2 border-t pt-2">
40
+ <div className="bg-zinc-200 dark:bg-zinc-800 h-1 rounded-md">
41
+ <div
42
+ className="h-1 score-fg rounded-md"
43
+ style={{ width: `${Math.round(item.score * 100)}%` }}
44
+ />
45
+ </div>
46
+ </div>
47
+ <div className="py-1 px-2 text-center">
48
+ Score : {item.score.toFixed(4)}
49
+ </div>
50
+ </div>
51
+ </div>
52
+ );
53
+ }
components/ImageInput/Highlight/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import Highlight from './Highlight';
2
+
3
+ export default Highlight;
components/ImageInput/ImageInput.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import useStore from "@/store";
3
+ import Preview from "@/components/ImageInput/Preview";
4
+
5
+ type CompProps = {};
6
+ export default function ImageInput({}: CompProps) {
7
+ const handleImageChange = useStore((state) => state.handleImageChange);
8
+ return (
9
+ <>
10
+ <label
11
+ className="grow bg-zinc-100 dark:bg-zinc-900 rounded-md overflow-hidden grid place-items-center py-2"
12
+ htmlFor="picture"
13
+ >
14
+ <Preview />
15
+ <input
16
+ className="hidden"
17
+ id="picture"
18
+ type="file"
19
+ accept="image/*"
20
+ onChange={handleImageChange}
21
+ />
22
+ </label>
23
+ </>
24
+ );
25
+ }
components/ImageInput/Preview/Markers/Markers.tsx ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import useStore from "@/store";
2
+ import constants from "@/constants";
3
+ import type { MatchItem } from "@/types";
4
+ type CompProps = {};
5
+ export default function Markers({}: CompProps) {
6
+ const { matches, width, height } = useStore(({ matches, width, height }) => ({
7
+ matches,
8
+ width,
9
+ height,
10
+ }));
11
+
12
+ return (
13
+ <>
14
+ <svg
15
+ className="absolute inset-x-0 top-0"
16
+ viewBox={`0 0 ${width} ${height}`}
17
+ fill="none"
18
+ xmlns="http://www.w3.org/2000/svg"
19
+ >
20
+ {matches.map((i) => (
21
+ <Marker item={i} key={i.key} />
22
+ ))}
23
+ </svg>
24
+ </>
25
+ );
26
+ }
27
+
28
+ type MarkerProps = {
29
+ item: MatchItem;
30
+ };
31
+ function Marker({ item }: MarkerProps) {
32
+ const { setHovering, clearHovering } = useStore(
33
+ ({ setHovering, clearHovering }) => ({ setHovering, clearHovering })
34
+ );
35
+ const selectedLabel = useStore((state) => state.selectedLabel);
36
+ const isolate = useStore((state) => state.isolate);
37
+ if (selectedLabel !== "all" && selectedLabel !== item.label) {
38
+ return null;
39
+ }
40
+ if (isolate !== null && isolate !== item.key) {
41
+ return null;
42
+ }
43
+
44
+ return (
45
+ <rect
46
+ x={item.x}
47
+ y={item.y}
48
+ width={item.width}
49
+ height={item.height}
50
+ className={constants.INDICATOR_COLORS[item.colorIndex]}
51
+ fill="currentColor"
52
+ fillOpacity="0.1"
53
+ stroke="currentColor"
54
+ strokeWidth="3"
55
+ key={item.key}
56
+ onMouseEnter={() => setHovering(item.key)}
57
+ onMouseLeave={() => clearHovering(item.key)}
58
+ />
59
+ );
60
+ }
components/ImageInput/Preview/Markers/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import Markers from './Markers';
2
+
3
+ export default Markers;
components/ImageInput/Preview/Preview.tsx ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import useStore from "@/store";
3
+ import SuccessWrapper from "@/components/layout/SuccessWrapper";
4
+ import Markers from "@/components/ImageInput/Preview/Markers";
5
+ import Highlight from "@/components/ImageInput/Highlight";
6
+ import { useRef } from "react";
7
+ type CompProps = {};
8
+ export default function Preview({}: CompProps) {
9
+ const previewURL = useStore((state) => state.previewURL);
10
+ const ref = useRef<HTMLDivElement | null>(null);
11
+
12
+ if (!previewURL) {
13
+ return <Placeholder />;
14
+ }
15
+ return (
16
+ <div className="relative inline-flex overflow-hidden max-w-lg" ref={ref}>
17
+ <img alt="" className="object-contain" src={previewURL} />
18
+ <SuccessWrapper>
19
+ <Highlight parentRef={ref} />
20
+ <Markers />
21
+ </SuccessWrapper>
22
+ </div>
23
+ );
24
+ }
25
+
26
+ function Placeholder() {
27
+ return (
28
+ <svg
29
+ fill="none"
30
+ viewBox="0 0 24 24"
31
+ strokeWidth={1.5}
32
+ stroke="currentColor"
33
+ className="w-60 h-60 text-zinc-200 dark:text-zinc-800"
34
+ >
35
+ <path
36
+ strokeLinecap="round"
37
+ strokeLinejoin="round"
38
+ d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
39
+ />
40
+ </svg>
41
+ );
42
+ }
components/ImageInput/Preview/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import Preview from './Preview';
2
+
3
+ export default Preview;
components/ImageInput/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import ImageInput from './ImageInput';
2
+
3
+ export default ImageInput;
components/ModelSelector/ModelSelector.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import {
3
+ Select,
4
+ SelectContent,
5
+ SelectGroup,
6
+ SelectItem,
7
+ SelectTrigger,
8
+ SelectValue,
9
+ } from "@/components/ui/select";
10
+ import useStore from "@/store";
11
+
12
+ type CompProps = {};
13
+ export default function ModelSelector({}: CompProps) {
14
+ return (
15
+ <div className="flex items-center px-4 py-2 justify-between">
16
+ <div className="inline-flex items-center space-x-2">
17
+ <div className="font-bold"> Model : </div>
18
+ <SelectModel />
19
+ </div>
20
+ </div>
21
+ );
22
+ }
23
+
24
+ const options = [
25
+ "facebook/detr-resnet-50",
26
+ "hustvl/yolos-tiny",
27
+ "facebook/detr-resnet-101",
28
+ "hustvl/yolos-small",
29
+ "valentinafeve/yolos-fashionpedia",
30
+ "keremberke/yolov5m-license-plate",
31
+ "facebook/detr-resnet-101-dc5",
32
+ "nickmuchi/yolos-small-finetuned-license-plate-detection",
33
+ "microsoft/table-transformer-structure-recognition",
34
+ "TahaDouaji/detr-doc-table-detection",
35
+ "hustvl/yolos-base",
36
+ "biglam/detr-resnet-50_fine_tuned_nls_chapbooks",
37
+ ];
38
+ function SelectModel() {
39
+ const { model, setModel } = useStore((state) => ({
40
+ model: state.model,
41
+ setModel: state.setModel,
42
+ }));
43
+ return (
44
+ <Select value={model} onValueChange={setModel}>
45
+ <SelectTrigger className="w-[500px]">
46
+ <SelectValue placeholder="Select a fruit" />
47
+ </SelectTrigger>
48
+ <SelectContent className="w-full">
49
+ <SelectGroup>
50
+ {options.map((i) => (
51
+ <SelectItem value={i} key={i}>
52
+ {i}
53
+ </SelectItem>
54
+ ))}
55
+ </SelectGroup>
56
+ </SelectContent>
57
+ </Select>
58
+ );
59
+ }
components/ModelSelector/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import ModelSelector from './ModelSelector';
2
+
3
+ export default ModelSelector;
components/Results/Filters/Filters.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import useStore from "@/store";
2
+ import { useCallback } from "react";
3
+ import clsx from "clsx";
4
+
5
+ export default function Filters() {
6
+ const labels = useStore((state) => state.labels);
7
+ const selectedLabel = useStore((state) => state.selectedLabel);
8
+ const setSelectedLabel = useStore((state) => state.setSelectedLabel);
9
+
10
+ const onClick = useCallback(
11
+ (e: React.MouseEvent<HTMLButtonElement>) => {
12
+ setSelectedLabel(e.currentTarget.id);
13
+ },
14
+ [setSelectedLabel]
15
+ );
16
+ return (
17
+ <div className="flex flex-wrap">
18
+ {["all", ...labels].map((i) => (
19
+ <div className="p-2" key={i}>
20
+ <button
21
+ className={clsx(
22
+ "capitalize px-6 py-1 rounded-full border",
23
+ selectedLabel === i
24
+ ? "bg-zinc-300 dark:bg-zinc-700"
25
+ : "bg-transperant"
26
+ )}
27
+ id={i}
28
+ onClick={onClick}
29
+ >
30
+ {i}
31
+ </button>
32
+ </div>
33
+ ))}
34
+ </div>
35
+ );
36
+ }
components/Results/Filters/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import Filters from './Filters';
2
+
3
+ export default Filters;
components/Results/Item/Item.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { MatchItem } from "@/types";
2
+ import useStore from "@/store";
3
+ import { useCallback } from "react";
4
+ import clsx from "clsx";
5
+ type CompProps = {
6
+ item: MatchItem;
7
+ selected: string;
8
+ };
9
+ export default function Item({ item, selected }: CompProps) {
10
+ const toggleIsolate = useStore((state) => state.toggleIsolate);
11
+ const isolate = useStore((state) => state.isolate);
12
+ const onClick = useCallback(() => {
13
+ toggleIsolate(item.key);
14
+ }, [toggleIsolate, item]);
15
+ if (selected !== "all" && selected !== item.label) {
16
+ return null;
17
+ }
18
+
19
+ return (
20
+ <button className="p-2 w-full sm:w-1/2 md:w-1/3 lg:w-1/4" onClick={onClick}>
21
+ <div
22
+ className={clsx(
23
+ "border rounded-md overflow-hidden",
24
+ isolate === item.key &&
25
+ "dark:border-green-800 dark:bg-green-800/10 border-green-300 bg-green-200/20"
26
+ )}
27
+ >
28
+ <div className="border-b py-1 px-2 text-center font-semibold capitalize">
29
+ {item.label}
30
+ </div>
31
+ <div className="p-2">
32
+ <div className="relative overflow-hidden aspect-square">
33
+ <img
34
+ src={`data:image/png;base64,${item.extract}`}
35
+ className="absolute left-0 top-0 w-full h-full object-contain"
36
+ />
37
+ </div>
38
+ </div>
39
+ <div className="px-2 border-t pt-2">
40
+ <div className="bg-zinc-200 dark:bg-zinc-800 h-1 rounded-md">
41
+ <div
42
+ className="h-1 score-fg rounded-md"
43
+ style={{ width: `${Math.round(item.score * 100)}%` }}
44
+ />
45
+ </div>
46
+ </div>
47
+ <div className="py-1 px-2 text-center">
48
+ Score : {item.score.toFixed(4)}
49
+ </div>
50
+ </div>
51
+ </button>
52
+ );
53
+ }
components/Results/Item/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import Item from './Item';
2
+
3
+ export default Item;
components/Results/Results.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import useStore from "@/store";
3
+ import Filters from "@/components/Results/Filters";
4
+ import Item from "@/components/Results/Item";
5
+ type CompProps = {};
6
+ export default function Results({}: CompProps) {
7
+ const matches = useStore((state) => state.matches);
8
+ const selectedLabel = useStore((state) => state.selectedLabel);
9
+ return (
10
+ <>
11
+ <Filters />
12
+ <div className="flex flex-wrap">
13
+ {matches.map((item) => (
14
+ <Item key={item.key} item={item} selected={selectedLabel} />
15
+ ))}
16
+ </div>
17
+ </>
18
+ );
19
+ }
components/Results/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import Results from './Results';
2
+
3
+ export default Results;
components/layout/Error/Error.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
3
+ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
4
+ import useStore from "@/store";
5
+ type CompProps = {};
6
+ export default function Error({}: CompProps) {
7
+ const error = useStore((state) => state.error);
8
+ const retry = useStore((state) => state.retry);
9
+
10
+ if (!error) {
11
+ return null;
12
+ }
13
+ return (
14
+ <Alert variant="destructive">
15
+ <ExclamationTriangleIcon className="h-4 w-4" />
16
+ <AlertTitle>Error</AlertTitle>
17
+ <AlertDescription>
18
+ Unknown Error Occurred: This could be due to the model still loading.
19
+ <button className="underline" onClick={retry}>
20
+ Please retry
21
+ </button>{" "}
22
+ after a few minutes.
23
+ </AlertDescription>
24
+ </Alert>
25
+ );
26
+ }
components/layout/Error/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import Error from './Error';
2
+
3
+ export default Error;
components/layout/Header/Header.tsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ThemeToggle from "@/components/layout/ThemeToggle";
2
+
3
+ export default function Header() {
4
+ return (
5
+ <div className="fixed top-0 left-0 right-0 supports-backdrop-blur:bg-background/60 border-b bg-background/95 backdrop-blur z-20">
6
+ <nav className="h-14 flex items-center justify-between max-w-5xl mx-auto px-4">
7
+ <div className="">
8
+ <svg
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ fill="none"
11
+ viewBox="0 0 24 24"
12
+ strokeWidth={1.5}
13
+ stroke="currentColor"
14
+ className="w-6 h-6"
15
+ >
16
+ <path
17
+ strokeLinecap="round"
18
+ strokeLinejoin="round"
19
+ d="M12 12.75c1.148 0 2.278.08 3.383.237 1.037.146 1.866.966 1.866 2.013 0 3.728-2.35 6.75-5.25 6.75S6.75 18.728 6.75 15c0-1.046.83-1.867 1.866-2.013A24.204 24.204 0 0112 12.75zm0 0c2.883 0 5.647.508 8.207 1.44a23.91 23.91 0 01-1.152 6.06M12 12.75c-2.883 0-5.647.508-8.208 1.44.125 2.104.52 4.136 1.153 6.06M12 12.75a2.25 2.25 0 002.248-2.354M12 12.75a2.25 2.25 0 01-2.248-2.354M12 8.25c.995 0 1.971-.08 2.922-.236.403-.066.74-.358.795-.762a3.778 3.778 0 00-.399-2.25M12 8.25c-.995 0-1.97-.08-2.922-.236-.402-.066-.74-.358-.795-.762a3.734 3.734 0 01.4-2.253M12 8.25a2.25 2.25 0 00-2.248 2.146M12 8.25a2.25 2.25 0 012.248 2.146M8.683 5a6.032 6.032 0 01-1.155-1.002c.07-.63.27-1.222.574-1.747m.581 2.749A3.75 3.75 0 0115.318 5m0 0c.427-.283.815-.62 1.155-.999a4.471 4.471 0 00-.575-1.752M4.921 6a24.048 24.048 0 00-.392 3.314c1.668.546 3.416.914 5.223 1.082M19.08 6c.205 1.08.337 2.187.392 3.314a23.882 23.882 0 01-5.223 1.082"
20
+ />
21
+ </svg>
22
+ </div>
23
+ <ThemeToggle />
24
+ </nav>
25
+ </div>
26
+ );
27
+ }
components/layout/Header/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import Header from './Header';
2
+
3
+ export default Header;
components/layout/Loading/Loading.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { range } from "@/utils";
3
+ import { Skeleton } from "@/components/ui/skeleton";
4
+ import useStore from "@/store";
5
+ type CompProps = {};
6
+
7
+ export default function Loading({}: CompProps) {
8
+ const loading = useStore((state) => state.loading);
9
+
10
+ if (!loading) {
11
+ return null;
12
+ }
13
+ return (
14
+ <div className="flex flex-wrap">
15
+ {range(8).map((i) => (
16
+ <div className="p-2 w-full sm:w-1/2 md:w-1/3 lg:w-1/4" key={i}>
17
+ <Skeleton className="aspect-square" />
18
+ </div>
19
+ ))}
20
+ </div>
21
+ );
22
+ }
components/layout/Loading/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import Loading from './Loading';
2
+
3
+ export default Loading;
components/layout/SuccessWrapper/SuccessWrapper.tsx ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import useStore from "@/store";
3
+ type CompProps = {
4
+ children: React.ReactNode;
5
+ };
6
+ export default function SuccessWrapper({ children }: CompProps) {
7
+ const success = useStore((state) => state.success);
8
+
9
+ if (!success) {
10
+ return null;
11
+ }
12
+ return children;
13
+ }
components/layout/SuccessWrapper/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import SuccessWrapper from './SuccessWrapper';
2
+
3
+ export default SuccessWrapper;
components/layout/ThemeProvider/ThemeProvider.tsx ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { ThemeProvider as NextThemesProvider } from "next-themes";
4
+ import { type ThemeProviderProps } from "next-themes/dist/types";
5
+
6
+ export default function ThemeProvider({
7
+ children,
8
+ ...props
9
+ }: ThemeProviderProps) {
10
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
11
+ }
components/layout/ThemeProvider/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import ThemeProvider from './ThemeProvider';
2
+
3
+ export default ThemeProvider;
components/layout/ThemeToggle/ThemeToggle.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
3
+ import { useTheme } from "next-themes";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuTrigger,
11
+ } from "@/components/ui/dropdown-menu";
12
+ type CompProps = {};
13
+ export default function ThemeToggle({}: CompProps) {
14
+ const { setTheme } = useTheme();
15
+ return (
16
+ <DropdownMenu>
17
+ <DropdownMenuTrigger asChild>
18
+ <Button variant="outline" size="icon">
19
+ <SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
20
+ <MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
21
+ <span className="sr-only">Toggle theme</span>
22
+ </Button>
23
+ </DropdownMenuTrigger>
24
+ <DropdownMenuContent align="end">
25
+ <DropdownMenuItem onClick={() => setTheme("light")}>
26
+ Light
27
+ </DropdownMenuItem>
28
+ <DropdownMenuItem onClick={() => setTheme("dark")}>
29
+ Dark
30
+ </DropdownMenuItem>
31
+ <DropdownMenuItem onClick={() => setTheme("system")}>
32
+ System
33
+ </DropdownMenuItem>
34
+ </DropdownMenuContent>
35
+ </DropdownMenu>
36
+ );
37
+ }
components/layout/ThemeToggle/index.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import ThemeToggle from './ThemeToggle';
2
+
3
+ export default ThemeToggle;
components/ui/alert.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-background text-foreground",
12
+ destructive:
13
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ const Alert = React.forwardRef<
23
+ HTMLDivElement,
24
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
+ >(({ className, variant, ...props }, ref) => (
26
+ <div
27
+ ref={ref}
28
+ role="alert"
29
+ className={cn(alertVariants({ variant }), className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ Alert.displayName = "Alert"
34
+
35
+ const AlertTitle = React.forwardRef<
36
+ HTMLParagraphElement,
37
+ React.HTMLAttributes<HTMLHeadingElement>
38
+ >(({ className, ...props }, ref) => (
39
+ <h5
40
+ ref={ref}
41
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
42
+ {...props}
43
+ />
44
+ ))
45
+ AlertTitle.displayName = "AlertTitle"
46
+
47
+ const AlertDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <div
52
+ ref={ref}
53
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ AlertDescription.displayName = "AlertDescription"
58
+
59
+ export { Alert, AlertTitle, AlertDescription }
components/ui/button.tsx ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14
+ destructive:
15
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16
+ outline:
17
+ "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20
+ ghost: "hover:bg-accent hover:text-accent-foreground",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2",
25
+ sm: "h-8 rounded-md px-3 text-xs",
26
+ lg: "h-10 rounded-md px-8",
27
+ icon: "h-9 w-9",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: "default",
32
+ size: "default",
33
+ },
34
+ }
35
+ )
36
+
37
+ export interface ButtonProps
38
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
39
+ VariantProps<typeof buttonVariants> {
40
+ asChild?: boolean
41
+ }
42
+
43
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
45
+ const Comp = asChild ? Slot : "button"
46
+ return (
47
+ <Comp
48
+ className={cn(buttonVariants({ variant, size, className }))}
49
+ ref={ref}
50
+ {...props}
51
+ />
52
+ )
53
+ }
54
+ )
55
+ Button.displayName = "Button"
56
+
57
+ export { Button, buttonVariants }
components/ui/card.tsx ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-xl border bg-card text-card-foreground shadow",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLParagraphElement,
34
+ React.HTMLAttributes<HTMLHeadingElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <h3
37
+ ref={ref}
38
+ className={cn("font-semibold leading-none tracking-tight", className)}
39
+ {...props}
40
+ />
41
+ ))
42
+ CardTitle.displayName = "CardTitle"
43
+
44
+ const CardDescription = React.forwardRef<
45
+ HTMLParagraphElement,
46
+ React.HTMLAttributes<HTMLParagraphElement>
47
+ >(({ className, ...props }, ref) => (
48
+ <p
49
+ ref={ref}
50
+ className={cn("text-sm text-muted-foreground", className)}
51
+ {...props}
52
+ />
53
+ ))
54
+ CardDescription.displayName = "CardDescription"
55
+
56
+ const CardContent = React.forwardRef<
57
+ HTMLDivElement,
58
+ React.HTMLAttributes<HTMLDivElement>
59
+ >(({ className, ...props }, ref) => (
60
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
61
+ ))
62
+ CardContent.displayName = "CardContent"
63
+
64
+ const CardFooter = React.forwardRef<
65
+ HTMLDivElement,
66
+ React.HTMLAttributes<HTMLDivElement>
67
+ >(({ className, ...props }, ref) => (
68
+ <div
69
+ ref={ref}
70
+ className={cn("flex items-center p-6 pt-0", className)}
71
+ {...props}
72
+ />
73
+ ))
74
+ CardFooter.displayName = "CardFooter"
75
+
76
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
components/ui/dropdown-menu.tsx ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5
+ import {
6
+ CheckIcon,
7
+ ChevronRightIcon,
8
+ DotFilledIcon,
9
+ } from "@radix-ui/react-icons"
10
+
11
+ import { cn } from "@/lib/utils"
12
+
13
+ const DropdownMenu = DropdownMenuPrimitive.Root
14
+
15
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
16
+
17
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
18
+
19
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
20
+
21
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
22
+
23
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
24
+
25
+ const DropdownMenuSubTrigger = React.forwardRef<
26
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
27
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
28
+ inset?: boolean
29
+ }
30
+ >(({ className, inset, children, ...props }, ref) => (
31
+ <DropdownMenuPrimitive.SubTrigger
32
+ ref={ref}
33
+ className={cn(
34
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
35
+ inset && "pl-8",
36
+ className
37
+ )}
38
+ {...props}
39
+ >
40
+ {children}
41
+ <ChevronRightIcon className="ml-auto h-4 w-4" />
42
+ </DropdownMenuPrimitive.SubTrigger>
43
+ ))
44
+ DropdownMenuSubTrigger.displayName =
45
+ DropdownMenuPrimitive.SubTrigger.displayName
46
+
47
+ const DropdownMenuSubContent = React.forwardRef<
48
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
49
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
50
+ >(({ className, ...props }, ref) => (
51
+ <DropdownMenuPrimitive.SubContent
52
+ ref={ref}
53
+ className={cn(
54
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
55
+ className
56
+ )}
57
+ {...props}
58
+ />
59
+ ))
60
+ DropdownMenuSubContent.displayName =
61
+ DropdownMenuPrimitive.SubContent.displayName
62
+
63
+ const DropdownMenuContent = React.forwardRef<
64
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
65
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
66
+ >(({ className, sideOffset = 4, ...props }, ref) => (
67
+ <DropdownMenuPrimitive.Portal>
68
+ <DropdownMenuPrimitive.Content
69
+ ref={ref}
70
+ sideOffset={sideOffset}
71
+ className={cn(
72
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
73
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
74
+ className
75
+ )}
76
+ {...props}
77
+ />
78
+ </DropdownMenuPrimitive.Portal>
79
+ ))
80
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
81
+
82
+ const DropdownMenuItem = React.forwardRef<
83
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
84
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
85
+ inset?: boolean
86
+ }
87
+ >(({ className, inset, ...props }, ref) => (
88
+ <DropdownMenuPrimitive.Item
89
+ ref={ref}
90
+ className={cn(
91
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
92
+ inset && "pl-8",
93
+ className
94
+ )}
95
+ {...props}
96
+ />
97
+ ))
98
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
99
+
100
+ const DropdownMenuCheckboxItem = React.forwardRef<
101
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
102
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
103
+ >(({ className, children, checked, ...props }, ref) => (
104
+ <DropdownMenuPrimitive.CheckboxItem
105
+ ref={ref}
106
+ className={cn(
107
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
108
+ className
109
+ )}
110
+ checked={checked}
111
+ {...props}
112
+ >
113
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
114
+ <DropdownMenuPrimitive.ItemIndicator>
115
+ <CheckIcon className="h-4 w-4" />
116
+ </DropdownMenuPrimitive.ItemIndicator>
117
+ </span>
118
+ {children}
119
+ </DropdownMenuPrimitive.CheckboxItem>
120
+ ))
121
+ DropdownMenuCheckboxItem.displayName =
122
+ DropdownMenuPrimitive.CheckboxItem.displayName
123
+
124
+ const DropdownMenuRadioItem = React.forwardRef<
125
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
126
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
127
+ >(({ className, children, ...props }, ref) => (
128
+ <DropdownMenuPrimitive.RadioItem
129
+ ref={ref}
130
+ className={cn(
131
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
132
+ className
133
+ )}
134
+ {...props}
135
+ >
136
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
137
+ <DropdownMenuPrimitive.ItemIndicator>
138
+ <DotFilledIcon className="h-4 w-4 fill-current" />
139
+ </DropdownMenuPrimitive.ItemIndicator>
140
+ </span>
141
+ {children}
142
+ </DropdownMenuPrimitive.RadioItem>
143
+ ))
144
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
145
+
146
+ const DropdownMenuLabel = React.forwardRef<
147
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
148
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
149
+ inset?: boolean
150
+ }
151
+ >(({ className, inset, ...props }, ref) => (
152
+ <DropdownMenuPrimitive.Label
153
+ ref={ref}
154
+ className={cn(
155
+ "px-2 py-1.5 text-sm font-semibold",
156
+ inset && "pl-8",
157
+ className
158
+ )}
159
+ {...props}
160
+ />
161
+ ))
162
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
163
+
164
+ const DropdownMenuSeparator = React.forwardRef<
165
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
166
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
167
+ >(({ className, ...props }, ref) => (
168
+ <DropdownMenuPrimitive.Separator
169
+ ref={ref}
170
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
171
+ {...props}
172
+ />
173
+ ))
174
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
175
+
176
+ const DropdownMenuShortcut = ({
177
+ className,
178
+ ...props
179
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
180
+ return (
181
+ <span
182
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
183
+ {...props}
184
+ />
185
+ )
186
+ }
187
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
188
+
189
+ export {
190
+ DropdownMenu,
191
+ DropdownMenuTrigger,
192
+ DropdownMenuContent,
193
+ DropdownMenuItem,
194
+ DropdownMenuCheckboxItem,
195
+ DropdownMenuRadioItem,
196
+ DropdownMenuLabel,
197
+ DropdownMenuSeparator,
198
+ DropdownMenuShortcut,
199
+ DropdownMenuGroup,
200
+ DropdownMenuPortal,
201
+ DropdownMenuSub,
202
+ DropdownMenuSubContent,
203
+ DropdownMenuSubTrigger,
204
+ DropdownMenuRadioGroup,
205
+ }
components/ui/input.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export interface InputProps
6
+ extends React.InputHTMLAttributes<HTMLInputElement> {}
7
+
8
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
+ ({ className, type, ...props }, ref) => {
10
+ return (
11
+ <input
12
+ type={type}
13
+ className={cn(
14
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
15
+ className
16
+ )}
17
+ ref={ref}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+ )
23
+ Input.displayName = "Input"
24
+
25
+ export { Input }
components/ui/label.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const labelVariants = cva(
10
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11
+ )
12
+
13
+ const Label = React.forwardRef<
14
+ React.ElementRef<typeof LabelPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16
+ VariantProps<typeof labelVariants>
17
+ >(({ className, ...props }, ref) => (
18
+ <LabelPrimitive.Root
19
+ ref={ref}
20
+ className={cn(labelVariants(), className)}
21
+ {...props}
22
+ />
23
+ ))
24
+ Label.displayName = LabelPrimitive.Root.displayName
25
+
26
+ export { Label }
components/ui/select.tsx ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
5
+ import * as SelectPrimitive from "@radix-ui/react-select"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Select = SelectPrimitive.Root
10
+
11
+ const SelectGroup = SelectPrimitive.Group
12
+
13
+ const SelectValue = SelectPrimitive.Value
14
+
15
+ const SelectTrigger = React.forwardRef<
16
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
17
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
+ >(({ className, children, ...props }, ref) => (
19
+ <SelectPrimitive.Trigger
20
+ ref={ref}
21
+ className={cn(
22
+ "flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
23
+ className
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ <SelectPrimitive.Icon asChild>
29
+ <CaretSortIcon className="h-4 w-4 opacity-50" />
30
+ </SelectPrimitive.Icon>
31
+ </SelectPrimitive.Trigger>
32
+ ))
33
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34
+
35
+ const SelectContent = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.Content>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
38
+ >(({ className, children, position = "popper", ...props }, ref) => (
39
+ <SelectPrimitive.Portal>
40
+ <SelectPrimitive.Content
41
+ ref={ref}
42
+ className={cn(
43
+ "relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
44
+ position === "popper" &&
45
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
46
+ className
47
+ )}
48
+ position={position}
49
+ {...props}
50
+ >
51
+ <SelectPrimitive.Viewport
52
+ className={cn(
53
+ "p-1",
54
+ position === "popper" &&
55
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
56
+ )}
57
+ >
58
+ {children}
59
+ </SelectPrimitive.Viewport>
60
+ </SelectPrimitive.Content>
61
+ </SelectPrimitive.Portal>
62
+ ))
63
+ SelectContent.displayName = SelectPrimitive.Content.displayName
64
+
65
+ const SelectLabel = React.forwardRef<
66
+ React.ElementRef<typeof SelectPrimitive.Label>,
67
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
68
+ >(({ className, ...props }, ref) => (
69
+ <SelectPrimitive.Label
70
+ ref={ref}
71
+ className={cn("px-2 py-1.5 text-sm font-semibold", className)}
72
+ {...props}
73
+ />
74
+ ))
75
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
76
+
77
+ const SelectItem = React.forwardRef<
78
+ React.ElementRef<typeof SelectPrimitive.Item>,
79
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
80
+ >(({ className, children, ...props }, ref) => (
81
+ <SelectPrimitive.Item
82
+ ref={ref}
83
+ className={cn(
84
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
85
+ className
86
+ )}
87
+ {...props}
88
+ >
89
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
90
+ <SelectPrimitive.ItemIndicator>
91
+ <CheckIcon className="h-4 w-4" />
92
+ </SelectPrimitive.ItemIndicator>
93
+ </span>
94
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
95
+ </SelectPrimitive.Item>
96
+ ))
97
+ SelectItem.displayName = SelectPrimitive.Item.displayName
98
+
99
+ const SelectSeparator = React.forwardRef<
100
+ React.ElementRef<typeof SelectPrimitive.Separator>,
101
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
102
+ >(({ className, ...props }, ref) => (
103
+ <SelectPrimitive.Separator
104
+ ref={ref}
105
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
110
+
111
+ export {
112
+ Select,
113
+ SelectGroup,
114
+ SelectValue,
115
+ SelectTrigger,
116
+ SelectContent,
117
+ SelectLabel,
118
+ SelectItem,
119
+ SelectSeparator,
120
+ }
components/ui/skeleton.tsx ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { cn } from "@/lib/utils"
2
+
3
+ function Skeleton({
4
+ className,
5
+ ...props
6
+ }: React.HTMLAttributes<HTMLDivElement>) {
7
+ return (
8
+ <div
9
+ className={cn("animate-pulse rounded-md bg-primary/10", className)}
10
+ {...props}
11
+ />
12
+ )
13
+ }
14
+
15
+ export { Skeleton }
components/ui/slider.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SliderPrimitive from "@radix-ui/react-slider"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Slider = React.forwardRef<
9
+ React.ElementRef<typeof SliderPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <SliderPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative flex w-full touch-none select-none items-center",
16
+ className
17
+ )}
18
+ {...props}
19
+ >
20
+ <SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
21
+ <SliderPrimitive.Range className="absolute h-full bg-primary" />
22
+ </SliderPrimitive.Track>
23
+ <SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
24
+ </SliderPrimitive.Root>
25
+ ))
26
+ Slider.displayName = SliderPrimitive.Root.displayName
27
+
28
+ export { Slider }
components/ui/switch.tsx ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SwitchPrimitives from "@radix-ui/react-switch"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Switch = React.forwardRef<
9
+ React.ElementRef<typeof SwitchPrimitives.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <SwitchPrimitives.Root
13
+ className={cn(
14
+ "peer inline-flex h-[20px] w-[36px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
15
+ className
16
+ )}
17
+ {...props}
18
+ ref={ref}
19
+ >
20
+ <SwitchPrimitives.Thumb
21
+ className={cn(
22
+ "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
23
+ )}
24
+ />
25
+ </SwitchPrimitives.Root>
26
+ ))
27
+ Switch.displayName = SwitchPrimitives.Root.displayName
28
+
29
+ export { Switch }