From 6c667681169f20d389c4519b389d2cd3200b717f Mon Sep 17 00:00:00 2001 From: Rami Bitar Date: Sun, 4 Oct 2026 13:26:02 -0400 Subject: [PATCH] Initial commit of vite-starter template Co-Authored-By: Claude Opus 5.5 --- .gitignore | 12 + .yarnrc.yml | 1 + README.md | 75 + components.json | 28 + package.json | 52 + public/icon.svg | 4 + src/components/route-states.tsx | 34 + src/components/theme-provider.tsx | 9 + src/components/ui/accordion.tsx | 79 + src/components/ui/alert-dialog.tsx | 197 + src/components/ui/alert.tsx | 76 + src/components/ui/aspect-ratio.tsx | 9 + src/components/ui/attachment.tsx | 206 + src/components/ui/avatar.tsx | 110 + src/components/ui/badge.tsx | 49 + src/components/ui/breadcrumb.tsx | 122 + src/components/ui/bubble.tsx | 125 + src/components/ui/button-group.tsx | 83 + src/components/ui/button.tsx | 67 + src/components/ui/calendar.tsx | 220 + src/components/ui/card.tsx | 103 + src/components/ui/carousel.tsx | 240 + src/components/ui/checkbox.tsx | 31 + src/components/ui/collapsible.tsx | 31 + src/components/ui/combobox.tsx | 297 ++ src/components/ui/command.tsx | 193 + src/components/ui/context-menu.tsx | 261 + src/components/ui/dialog.tsx | 175 + src/components/ui/direction.tsx | 20 + src/components/ui/drawer.tsx | 132 + src/components/ui/dropdown-menu.tsx | 267 + src/components/ui/empty.tsx | 104 + src/components/ui/field.tsx | 236 + src/components/ui/globe.tsx | 133 + src/components/ui/hover-card.tsx | 42 + src/components/ui/input-group.tsx | 154 + src/components/ui/input-otp.tsx | 85 + src/components/ui/input.tsx | 19 + src/components/ui/item.tsx | 196 + src/components/ui/kbd.tsx | 26 + src/components/ui/label.tsx | 22 + src/components/ui/marker.tsx | 69 + src/components/ui/marquee.tsx | 74 + src/components/ui/menubar.tsx | 278 + src/components/ui/message-scroller.tsx | 129 + src/components/ui/message.tsx | 92 + src/components/ui/native-select.tsx | 61 + src/components/ui/navigation-menu.tsx | 164 + src/components/ui/pagination.tsx | 129 + src/components/ui/popover.tsx | 87 + src/components/ui/progress.tsx | 29 + src/components/ui/radio-group.tsx | 42 + src/components/ui/resizable.tsx | 48 + src/components/ui/scroll-area.tsx | 53 + src/components/ui/select.tsx | 190 + src/components/ui/separator.tsx | 26 + src/components/ui/sheet.tsx | 147 + src/components/ui/sidebar.tsx | 700 +++ src/components/ui/skeleton.tsx | 13 + src/components/ui/slider.tsx | 57 + src/components/ui/sonner.tsx | 47 + src/components/ui/spinner.tsx | 10 + src/components/ui/switch.tsx | 31 + src/components/ui/table.tsx | 114 + src/components/ui/tabs.tsx | 88 + src/components/ui/textarea.tsx | 18 + src/components/ui/toggle-group.tsx | 87 + src/components/ui/toggle.tsx | 45 + src/components/ui/tooltip.tsx | 55 + src/hooks/use-mobile.ts | 19 + src/lib/seo.ts | 41 + src/lib/site.ts | 15 + src/lib/utils.ts | 6 + src/router.tsx | 20 + src/routes/__root.tsx | 52 + src/routes/index.tsx | 17 + src/server.ts | 19 + src/server/app.ts | 11 + src/styles.css | 164 + src/vite-env.d.ts | 1 + tsconfig.json | 21 + vercel.json | 6 + vite.config.mjs | 25 + yarn.lock | 6511 ++++++++++++++++++++++++ 84 files changed, 14136 insertions(+) create mode 100644 .gitignore create mode 100644 .yarnrc.yml create mode 100644 README.md create mode 100644 components.json create mode 100644 package.json create mode 100644 public/icon.svg create mode 100644 src/components/route-states.tsx create mode 100644 src/components/theme-provider.tsx create mode 100644 src/components/ui/accordion.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/aspect-ratio.tsx create mode 100644 src/components/ui/attachment.tsx create mode 100644 src/components/ui/avatar.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/breadcrumb.tsx create mode 100644 src/components/ui/bubble.tsx create mode 100644 src/components/ui/button-group.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/calendar.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/carousel.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/collapsible.tsx create mode 100644 src/components/ui/combobox.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/context-menu.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/direction.tsx create mode 100644 src/components/ui/drawer.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/empty.tsx create mode 100644 src/components/ui/field.tsx create mode 100644 src/components/ui/globe.tsx create mode 100644 src/components/ui/hover-card.tsx create mode 100644 src/components/ui/input-group.tsx create mode 100644 src/components/ui/input-otp.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/item.tsx create mode 100644 src/components/ui/kbd.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/marker.tsx create mode 100644 src/components/ui/marquee.tsx create mode 100644 src/components/ui/menubar.tsx create mode 100644 src/components/ui/message-scroller.tsx create mode 100644 src/components/ui/message.tsx create mode 100644 src/components/ui/native-select.tsx create mode 100644 src/components/ui/navigation-menu.tsx create mode 100644 src/components/ui/pagination.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/radio-group.tsx create mode 100644 src/components/ui/resizable.tsx create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/sheet.tsx create mode 100644 src/components/ui/sidebar.tsx create mode 100644 src/components/ui/skeleton.tsx create mode 100644 src/components/ui/slider.tsx create mode 100644 src/components/ui/sonner.tsx create mode 100644 src/components/ui/spinner.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/tabs.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/components/ui/toggle-group.tsx create mode 100644 src/components/ui/toggle.tsx create mode 100644 src/components/ui/tooltip.tsx create mode 100644 src/hooks/use-mobile.ts create mode 100644 src/lib/seo.ts create mode 100644 src/lib/site.ts create mode 100644 src/lib/utils.ts create mode 100644 src/router.tsx create mode 100644 src/routes/__root.tsx create mode 100644 src/routes/index.tsx create mode 100644 src/server.ts create mode 100644 src/server/app.ts create mode 100644 src/styles.css create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.json create mode 100644 vercel.json create mode 100644 vite.config.mjs create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3afb834 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.DS_Store +node_modules/* +dist +.output +.nitro +.vercel +.yarn/install-state.gz +.env*.local +src/routeTree.gen.ts +*.tsbuildinfo + +.tanstack diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..1ffcf1e --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Vite Starter + +The base template for new projects: Vite, TanStack Start, Hono, Nitro, +Tailwind CSS v4 and shadcn/ui, with a single "Start prompting." landing page. + +Runs in the nodesandbox runtime: `npm install` → `npm run dev` → port 3000. + +## Getting started + +```sh +corepack enable # uses the Yarn 4 pinned in package.json +yarn install +yarn dev # http://localhost:3000 +``` + +| Script | What it does | +| ---------------- | --------------------------------------------------------- | +| `yarn dev` | Vite dev server with SSR | +| `yarn build` | Production build; Nitro packages the server for the host | +| `yarn start` | Run the Node build from `.output/` | +| `yarn typecheck` | `tsc` | + +## Metadata + +`src/lib/site.ts` holds the site name, description, URL, share image and theme +colors. `__root.tsx` turns it into the default `` tags (title, +description, Open Graph, Twitter, theme-color, canonical). A route overrides +them with `seo()` from `src/lib/seo.ts`: + +```tsx +export const Route = createFileRoute('/about')({ + head: () => seo({ title: 'About', description: 'Who we are.', path: '/about' }), + component: About, +}); +``` + +## API routes + +`src/server.ts` is the server entry. Hono handles `/api/*` (defined in +`src/server/app.ts`, e.g. `GET /api/health`), and every other request falls +through to TanStack Start. + +## Deploying to Vercel + +Push and deploy — no dashboard settings. `vercel.json` selects the TanStack +Start preset and installs with Yarn 4 via Corepack; Nitro writes +`.vercel/output/` during `vite build`. + +## nodesandbox compatibility + +The sandbox runs Vite's native tools as WebAssembly (`NAPI_RS_FORCE_WASI`), and +its installer resolves from `package.json` ranges rather than `yarn.lock`. The +toolchain versions match `vite-shopify-storefront`: + +- `vite` is pinned to **8.3.2** (rolldown 1.2.x WASI binding). +- WASI bindings (`@rolldown/binding-wasm32-wasi`, `@tailwindcss/oxide-wasm32-wasi`) + are **not** listed: the runtime installs each package's `*-wasm32-wasi` + optional dependency itself, and npm on other hosts rejects them. +- `@vitejs/plugin-react` (6.0.0) and Tailwind (4.3.1) match the harness samples. +- Nitro (the hosting adapter) only loads for `vite build`, never in `vite dev`. + +## Layout + +``` +src/ + server.ts Server entry: Hono for /api/*, TanStack Start for the rest + server/app.ts Hono API routes + router.tsx Router setup + routes/ File routes (__root.tsx is the document shell, index.tsx the page) + components/ui/ shadcn/ui components + lib/site.ts Site name and metadata + lib/seo.ts Per-route meta tag helper + styles.css Tailwind v4 theme +public/ Static assets served from / +``` diff --git a/components.json b/components.json new file mode 100644 index 0000000..c6a8ac1 --- /dev/null +++ b/components.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "radix-nova", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/styles.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": { + "@magicui": "https://magicui.design/r/{name}", + "@ai-elements": "https://ai-sdk.dev/elements/api/registry/{name}.json" + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f841132 --- /dev/null +++ b/package.json @@ -0,0 +1,52 @@ +{ + "name": "vite-starter", + "type": "module", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "vite dev --host 0.0.0.0 --port 3000", + "build": "vite build", + "start": "node .output/server/index.mjs", + "typecheck": "tsc --noEmit" + }, + "packageManager": "yarn@4.18.0+sha512.fcb8716fe7cd0eece141ffc18b92193a9df9204c1ba83189c288835223fc0bbe64af473bab0d5e9927a7daeb5caf2bb07eb2787cc9338ca040ea125f2a1f2f7e", + "dependencies": { + "@base-ui/react": "^1.6.0", + "@remixicon/react": "^4.9.0", + "@shadcn/react": "^0.1.0", + "@tanstack/react-router": "^1.170.41", + "@tanstack/react-start": "^1.168.60", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "cobe": "^0.6.4", + "date-fns": "^4.4.0", + "embla-carousel-react": "^8.6.0", + "hono": "^4.13.12", + "input-otp": "^1.4.2", + "lucide-react": "^1.22.0", + "motion": "^13.3.0", + "next-themes": "^0.4.6", + "nitro": "3.0.260903-beta", + "radix-ui": "^1.6.0", + "react": "^19.3.0", + "react-day-picker": "^10.0.1", + "react-dom": "^19.3.0", + "react-resizable-panels": "^4.12.0", + "shadcn": "^4.12.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.6.0", + "tw-animate-css": "^1.4.0", + "vaul": "^1.1.2" + }, + "devDependencies": { + "@tailwindcss/vite": "4.3.1", + "@types/node": "^26.6.4", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "6.0.0", + "tailwindcss": "4.3.1", + "typescript": "^5.7.3", + "vite": "8.3.2" + } +} diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..427de68 --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/route-states.tsx b/src/components/route-states.tsx new file mode 100644 index 0000000..f10e016 --- /dev/null +++ b/src/components/route-states.tsx @@ -0,0 +1,34 @@ +import { Link } from '@tanstack/react-router'; + +// Stand-ins for Next's built-in 404 and error pages. + +export function NotFound() { + return ( +
+
+
404
+
+
+ This page could not be found.{' '} + + Go home + +
+
+
+ ); +} + +export function ErrorMessage({ error }: { error: unknown }) { + return ( +
+
+
Something went wrong
+
+
+ {error instanceof Error ? error.message : String(error)} +
+
+
+ ); +} diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx new file mode 100644 index 0000000..2e1dd00 --- /dev/null +++ b/src/components/theme-provider.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; +import { ThemeProvider as NextThemesProvider } from 'next-themes'; + +export function ThemeProvider({ + children, + ...props +}: React.ComponentProps) { + return {children}; +} diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx new file mode 100644 index 0000000..fcfee5c --- /dev/null +++ b/src/components/ui/accordion.tsx @@ -0,0 +1,79 @@ +import * as React from "react" +import { Accordion as AccordionPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { ChevronDownIcon, ChevronUpIcon } from "lucide-react" + +function Accordion({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + + ) +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
+ {children} +
+
+ ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..b8171d5 --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,197 @@ +import * as React from "react" +import { AlertDialog as AlertDialogPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + size = "default", + ...props +}: React.ComponentProps & { + size?: "default" | "sm" +}) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogMedia({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + variant = "default", + size = "default", + ...props +}: React.ComponentProps & + Pick, "variant" | "size">) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + variant = "outline", + size = "default", + ...props +}: React.ComponentProps & + Pick, "variant" | "size">) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogMedia, + AlertDialogOverlay, + AlertDialogPortal, + AlertDialogTitle, + AlertDialogTrigger, +} diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..1fe3176 --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,76 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground", + className + )} + {...props} + /> + ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription, AlertAction } diff --git a/src/components/ui/aspect-ratio.tsx b/src/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..ae5f48c --- /dev/null +++ b/src/components/ui/aspect-ratio.tsx @@ -0,0 +1,9 @@ +import { AspectRatio as AspectRatioPrimitive } from "radix-ui" + +function AspectRatio({ + ...props +}: React.ComponentProps) { + return +} + +export { AspectRatio } diff --git a/src/components/ui/attachment.tsx b/src/components/ui/attachment.tsx new file mode 100644 index 0000000..05ee0c9 --- /dev/null +++ b/src/components/ui/attachment.tsx @@ -0,0 +1,206 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +const attachmentVariants = cva( + "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed", + { + variants: { + size: { + default: + "gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2", + sm: "gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5", + xs: "gap-1.5 rounded-lg text-xs has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1", + }, + orientation: { + horizontal: "min-w-40 items-center", + vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30", + }, + }, + } +) + +function Attachment({ + className, + state = "done", + size = "default", + orientation = "horizontal", + ...props +}: React.ComponentProps<"div"> & + VariantProps & { + state?: "idle" | "uploading" | "processing" | "error" | "done" + }) { + const resolvedOrientation = orientation ?? "horizontal" + + return ( +
+ ) +} + +const attachmentMediaVariants = cva( + "relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5", + { + variants: { + variant: { + icon: "", + image: + "opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover", + }, + }, + defaultVariants: { + variant: "icon", + }, + } +) + +function AttachmentMedia({ + className, + variant = "icon", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function AttachmentContent({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AttachmentTitle({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +function AttachmentDescription({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +function AttachmentActions({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AttachmentAction({ + className, + variant, + size = "icon-xs", + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CarouselNext({ + className, + variant = "outline", + size = "icon-sm", + ...props +}: React.ComponentProps) { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) +} + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, + useCarousel, +} diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..cec7a77 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,31 @@ +import * as React from "react" +import { Checkbox as CheckboxPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { CheckIcon } from "lucide-react" + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { Checkbox } diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx new file mode 100644 index 0000000..63fc8ef --- /dev/null +++ b/src/components/ui/collapsible.tsx @@ -0,0 +1,31 @@ +import { Collapsible as CollapsiblePrimitive } from "radix-ui" + +function Collapsible({ + ...props +}: React.ComponentProps) { + return +} + +function CollapsibleTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CollapsibleContent({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/src/components/ui/combobox.tsx b/src/components/ui/combobox.tsx new file mode 100644 index 0000000..2660a0a --- /dev/null +++ b/src/components/ui/combobox.tsx @@ -0,0 +1,297 @@ +import * as React from "react" +import { Combobox as ComboboxPrimitive } from "@base-ui/react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { + InputGroup, + InputGroupAddon, + InputGroupButton, + InputGroupInput, +} from "@/components/ui/input-group" +import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react" + +const Combobox = ComboboxPrimitive.Root + +function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) { + return +} + +function ComboboxTrigger({ + className, + children, + ...props +}: ComboboxPrimitive.Trigger.Props) { + return ( + + {children} + + + ) +} + +function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) { + return ( + } + className={cn(className)} + {...props} + > + + + ) +} + +function ComboboxInput({ + className, + children, + disabled = false, + showTrigger = true, + showClear = false, + ...props +}: ComboboxPrimitive.Input.Props & { + showTrigger?: boolean + showClear?: boolean +}) { + return ( + + } + {...props} + /> + + {showTrigger && ( + + + + )} + {showClear && } + + {children} + + ) +} + +function ComboboxContent({ + className, + side = "bottom", + sideOffset = 6, + align = "start", + alignOffset = 0, + anchor, + ...props +}: ComboboxPrimitive.Popup.Props & + Pick< + ComboboxPrimitive.Positioner.Props, + "side" | "align" | "sideOffset" | "alignOffset" | "anchor" + >) { + return ( + + + + + + ) +} + +function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) { + return ( + + ) +} + +function ComboboxItem({ + className, + children, + ...props +}: ComboboxPrimitive.Item.Props) { + return ( + + {children} + + } + > + + + + ) +} + +function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) { + return ( + + ) +} + +function ComboboxLabel({ + className, + ...props +}: ComboboxPrimitive.GroupLabel.Props) { + return ( + + ) +} + +function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) { + return ( + + ) +} + +function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { + return ( + + ) +} + +function ComboboxSeparator({ + className, + ...props +}: ComboboxPrimitive.Separator.Props) { + return ( + + ) +} + +function ComboboxChips({ + className, + ...props +}: React.ComponentPropsWithRef & + ComboboxPrimitive.Chips.Props) { + return ( + + ) +} + +function ComboboxChip({ + className, + children, + showRemove = true, + ...props +}: ComboboxPrimitive.Chip.Props & { + showRemove?: boolean +}) { + return ( + + {children} + {showRemove && ( + } + className="-ml-1 opacity-50 hover:opacity-100" + data-slot="combobox-chip-remove" + > + + + )} + + ) +} + +function ComboboxChipsInput({ + className, + ...props +}: ComboboxPrimitive.Input.Props) { + return ( + + ) +} + +function useComboboxAnchor() { + return React.useRef(null) +} + +export { + Combobox, + ComboboxInput, + ComboboxContent, + ComboboxList, + ComboboxItem, + ComboboxGroup, + ComboboxLabel, + ComboboxCollection, + ComboboxEmpty, + ComboboxSeparator, + ComboboxChips, + ComboboxChip, + ComboboxChipsInput, + ComboboxTrigger, + ComboboxValue, + useComboboxAnchor, +} diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 0000000..b70649b --- /dev/null +++ b/src/components/ui/command.tsx @@ -0,0 +1,193 @@ +import * as React from "react" +import { Command as CommandPrimitive } from "cmdk" + +import { cn } from "@/lib/utils" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + InputGroup, + InputGroupAddon, +} from "@/components/ui/input-group" +import { SearchIcon, CheckIcon } from "lucide-react" + +function Command({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandDialog({ + title = "Command Palette", + description = "Search for a command to run...", + children, + className, + showCloseButton = false, + ...props +}: React.ComponentProps & { + title?: string + description?: string + className?: string + showCloseButton?: boolean +}) { + return ( + + + {title} + {description} + + + {children} + + + ) +} + +function CommandInput({ + className, + ...props +}: React.ComponentProps) { + return ( +
+ + + + + + +
+ ) +} + +function CommandList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandEmpty({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + {children} + + + ) +} + +function CommandShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/src/components/ui/context-menu.tsx b/src/components/ui/context-menu.tsx new file mode 100644 index 0000000..25fd8ab --- /dev/null +++ b/src/components/ui/context-menu.tsx @@ -0,0 +1,261 @@ +import * as React from "react" +import { ContextMenu as ContextMenuPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { ChevronRightIcon, CheckIcon } from "lucide-react" + +function ContextMenu({ + ...props +}: React.ComponentProps) { + return +} + +function ContextMenuTrigger({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function ContextMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuContent({ + className, + ...props +}: React.ComponentProps & { + side?: "top" | "right" | "bottom" | "left" +}) { + return ( + + + + ) +} + +function ContextMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function ContextMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function ContextMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuCheckboxItem({ + className, + children, + checked, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + + + + + + {children} + + ) +} + +function ContextMenuRadioItem({ + className, + children, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + + + + + + {children} + + ) +} + +function ContextMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function ContextMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, + ContextMenuCheckboxItem, + ContextMenuRadioItem, + ContextMenuLabel, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuGroup, + ContextMenuPortal, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuRadioGroup, +} diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..e51b731 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,175 @@ +import * as React from "react" +import { Dialog as DialogPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { XIcon } from "lucide-react" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + // Radix's Portal calls createPortal(children, document.body) as soon as it + // renders. During static prerendering there's no `document`, so gate the + // portal behind an effect that only runs in the browser. Closed dialogs + // render nothing on the server anyway, so there's no visual/hydration change. + const [mounted, setMounted] = React.useState(false) + React.useEffect(() => setMounted(true), []) + + if (!mounted) return null + + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ + className, + showCloseButton = false, + children, + ...props +}: React.ComponentProps<"div"> & { + showCloseButton?: boolean +}) { + return ( +
+ {children} + {showCloseButton && ( + + + + )} +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/src/components/ui/direction.tsx b/src/components/ui/direction.tsx new file mode 100644 index 0000000..5156938 --- /dev/null +++ b/src/components/ui/direction.tsx @@ -0,0 +1,20 @@ +import * as React from "react" +import { Direction } from "radix-ui" + +function DirectionProvider({ + dir, + direction, + children, +}: React.ComponentProps & { + direction?: React.ComponentProps["dir"] +}) { + return ( + + {children} + + ) +} + +const useDirection = Direction.useDirection + +export { DirectionProvider, useDirection } diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx new file mode 100644 index 0000000..8242639 --- /dev/null +++ b/src/components/ui/drawer.tsx @@ -0,0 +1,132 @@ +import * as React from "react" +import { Drawer as DrawerPrimitive } from "vaul" + +import { cn } from "@/lib/utils" + +function Drawer({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerClose({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DrawerContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + +
+ {children} + + + ) +} + +function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DrawerTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DrawerDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..a4695ec --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,267 @@ +import * as React from "react" +import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { CheckIcon, ChevronRightIcon } from "lucide-react" + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuContent({ + className, + align = "start", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuRadioItem({ + className, + children, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +} diff --git a/src/components/ui/empty.tsx b/src/components/ui/empty.tsx new file mode 100644 index 0000000..b23187b --- /dev/null +++ b/src/components/ui/empty.tsx @@ -0,0 +1,104 @@ +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +function Empty({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +const emptyMediaVariants = cva( + "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-transparent", + icon: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function EmptyMedia({ + className, + variant = "default", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) { + return ( +
a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", + className + )} + {...props} + /> + ) +} + +function EmptyContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Empty, + EmptyHeader, + EmptyTitle, + EmptyDescription, + EmptyContent, + EmptyMedia, +} diff --git a/src/components/ui/field.tsx b/src/components/ui/field.tsx new file mode 100644 index 0000000..654b9b4 --- /dev/null +++ b/src/components/ui/field.tsx @@ -0,0 +1,236 @@ +import { useMemo } from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" +import { Separator } from "@/components/ui/separator" + +function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { + return ( +
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", + className + )} + {...props} + /> + ) +} + +function FieldLegend({ + className, + variant = "legend", + ...props +}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { + return ( + + ) +} + +function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +const fieldVariants = cva( + "group/field flex w-full gap-2 data-[invalid=true]:text-destructive", + { + variants: { + orientation: { + vertical: "flex-col *:w-full [&>.sr-only]:w-auto", + horizontal: + "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + responsive: + "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + }, + }, + defaultVariants: { + orientation: "vertical", + }, + } +) + +function Field({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function FieldContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function FieldLabel({ + className, + ...props +}: React.ComponentProps) { + return ( +