diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..12bc7fa7e05091e2d413d235ef695c1d95805c8a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,49 @@
+# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
+
+# compiled output
+dist
+tmp
+out-tsc
+*.tsbuildinfo
+.expo
+.expo-shared
+
+# dependencies
+node_modules
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+
+# System Files
+.DS_Store
+Thumbs.db
+
+.cursor/rules/nx-rules.mdc
+.github/instructions/nx.instructions.md
+
+# Replit
+.cache/
+.local/
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000000000000000000000000000000000000..61e34c2e5dde6b5b6b9d1a478945726c27993d4b
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+auto-install-peers=false
+strict-peer-dependencies=false
diff --git a/.replit b/.replit
new file mode 100644
index 0000000000000000000000000000000000000000..8dc8fddff04309d1d915ff3502bb525051956bb4
--- /dev/null
+++ b/.replit
@@ -0,0 +1,26 @@
+modules = ["nodejs-24"]
+
+[[artifacts]]
+id = "artifacts/api-server"
+
+[[artifacts]]
+id = "artifacts/mockup-sandbox"
+
+[deployment]
+router = "application"
+deploymentTarget = "autoscale"
+
+[deployment.postBuild]
+args = ["pnpm", "store", "prune"]
+env = { "CI" = "true" }
+
+[workflows]
+runButton = "Project"
+
+[agent]
+stack = "PNPM_WORKSPACE"
+expertMode = true
+
+[postMerge]
+path = "scripts/post-merge.sh"
+timeoutMs = 20000
diff --git a/.replitignore b/.replitignore
new file mode 100644
index 0000000000000000000000000000000000000000..9eb019cd54e4f8ea1737967adb19aa1f53562cf2
--- /dev/null
+++ b/.replitignore
@@ -0,0 +1,5 @@
+# The format of this file is identical to `.dockerignore`.
+# It is used to reduce the size of deployed images to make the process of publishing faster.
+
+# No need to store the pnpm store twice.
+.local
diff --git a/artifacts/api-server/.replit-artifact/artifact.toml b/artifacts/api-server/.replit-artifact/artifact.toml
new file mode 100644
index 0000000000000000000000000000000000000000..814b9ad51b47b2302b7364ccb37849767aec680e
--- /dev/null
+++ b/artifacts/api-server/.replit-artifact/artifact.toml
@@ -0,0 +1,32 @@
+kind = "api"
+previewPath = "/api" # TODO - should be excluded from preview in the first place
+title = "API Server"
+version = "1.0.0"
+id = "3B4_FFSkEVBkAeYMFRJ2e"
+
+[[services]]
+localPort = 8080
+name = "API Server"
+paths = ["/api"]
+
+[services.development]
+run = "pnpm --filter @workspace/api-server run dev"
+
+[services.production]
+
+[services.production.build]
+args = ["pnpm", "--filter", "@workspace/api-server", "run", "build"]
+
+[services.production.build.env]
+NODE_ENV = "production"
+
+[services.production.run]
+# we don't run through pnpm to make startup faster in production
+args = ["node", "--enable-source-maps", "artifacts/api-server/dist/index.mjs"]
+
+[services.production.run.env]
+PORT = "8080"
+NODE_ENV = "production"
+
+[services.production.health.startup]
+path = "/api/healthz"
diff --git a/artifacts/api-server/build.mjs b/artifacts/api-server/build.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..86ebf7faaa9322b1ab55cdbbda7febaad27b35cd
--- /dev/null
+++ b/artifacts/api-server/build.mjs
@@ -0,0 +1,126 @@
+import { createRequire } from "node:module";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { build as esbuild } from "esbuild";
+import esbuildPluginPino from "esbuild-plugin-pino";
+import { rm } from "node:fs/promises";
+
+// Plugins (e.g. 'esbuild-plugin-pino') may use `require` to resolve dependencies
+globalThis.require = createRequire(import.meta.url);
+
+const artifactDir = path.dirname(fileURLToPath(import.meta.url));
+
+async function buildAll() {
+ const distDir = path.resolve(artifactDir, "dist");
+ await rm(distDir, { recursive: true, force: true });
+
+ await esbuild({
+ entryPoints: [path.resolve(artifactDir, "src/index.ts")],
+ platform: "node",
+ bundle: true,
+ format: "esm",
+ outdir: distDir,
+ outExtension: { ".js": ".mjs" },
+ logLevel: "info",
+ // Some packages may not be bundleable, so we externalize them, we can add more here as needed.
+ // Some of the packages below may not be imported or installed, but we're adding them in case they are in the future.
+ // Examples of unbundleable packages:
+ // - uses native modules and loads them dynamically (e.g. sharp)
+ // - use path traversal to read files (e.g. @google-cloud/secret-manager loads sibling .proto files)
+ external: [
+ "*.node",
+ "sharp",
+ "better-sqlite3",
+ "sqlite3",
+ "canvas",
+ "bcrypt",
+ "argon2",
+ "fsevents",
+ "re2",
+ "farmhash",
+ "xxhash-addon",
+ "bufferutil",
+ "utf-8-validate",
+ "ssh2",
+ "cpu-features",
+ "dtrace-provider",
+ "isolated-vm",
+ "lightningcss",
+ "pg-native",
+ "oracledb",
+ "mongodb-client-encryption",
+ "nodemailer",
+ "handlebars",
+ "knex",
+ "typeorm",
+ "protobufjs",
+ "onnxruntime-node",
+ "@tensorflow/*",
+ "@prisma/client",
+ "@mikro-orm/*",
+ "@grpc/*",
+ "@swc/*",
+ "@aws-sdk/*",
+ "@azure/*",
+ "@opentelemetry/*",
+ "@google-cloud/*",
+ "@google/*",
+ "googleapis",
+ "firebase-admin",
+ "@parcel/watcher",
+ "@sentry/profiling-node",
+ "@tree-sitter/*",
+ "aws-sdk",
+ "classic-level",
+ "dd-trace",
+ "ffi-napi",
+ "grpc",
+ "hiredis",
+ "kerberos",
+ "leveldown",
+ "miniflare",
+ "mysql2",
+ "newrelic",
+ "odbc",
+ "piscina",
+ "realm",
+ "ref-napi",
+ "rocksdb",
+ "sass-embedded",
+ "sequelize",
+ "serialport",
+ "snappy",
+ "tinypool",
+ "usb",
+ "workerd",
+ "wrangler",
+ "zeromq",
+ "zeromq-prebuilt",
+ "playwright",
+ "puppeteer",
+ "puppeteer-core",
+ "electron",
+ ],
+ sourcemap: "linked",
+ plugins: [
+ // pino relies on workers to handle logging, instead of externalizing it we use a plugin to handle it
+ esbuildPluginPino({ transports: ["pino-pretty"] })
+ ],
+ // Make sure packages that are cjs only (e.g. express) but are bundled continue to work in our esm output file
+ banner: {
+ js: `import { createRequire as __bannerCrReq } from 'node:module';
+import __bannerPath from 'node:path';
+import __bannerUrl from 'node:url';
+
+globalThis.require = __bannerCrReq(import.meta.url);
+globalThis.__filename = __bannerUrl.fileURLToPath(import.meta.url);
+globalThis.__dirname = __bannerPath.dirname(globalThis.__filename);
+ `,
+ },
+ });
+}
+
+buildAll().catch((err) => {
+ console.error(err);
+ process.exit(1);
+});
diff --git a/artifacts/api-server/package.json b/artifacts/api-server/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..1c15a36a7c274ec5bf992948c958e3d31900ec63
--- /dev/null
+++ b/artifacts/api-server/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@workspace/api-server",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "export NODE_ENV=development && pnpm run build && pnpm run start",
+ "build": "node ./build.mjs",
+ "start": "node --enable-source-maps ./dist/index.mjs",
+ "typecheck": "tsc -p tsconfig.json --noEmit"
+ },
+ "dependencies": {
+ "@workspace/api-zod": "workspace:*",
+ "@workspace/db": "workspace:*",
+ "cookie-parser": "^1.4.7",
+ "cors": "^2",
+ "drizzle-orm": "catalog:",
+ "express": "^5",
+ "pino": "^9",
+ "pino-http": "^10"
+ },
+ "devDependencies": {
+ "@types/cookie-parser": "^1.4.10",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.6",
+ "@types/node": "catalog:",
+ "esbuild": "^0.27.3",
+ "esbuild-plugin-pino": "^2.3.3",
+ "pino-pretty": "^13",
+ "thread-stream": "3.1.0"
+ }
+}
diff --git a/artifacts/api-server/src/app.ts b/artifacts/api-server/src/app.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f32f71eb2abef174efb08a2cc118c7e76c6df951
--- /dev/null
+++ b/artifacts/api-server/src/app.ts
@@ -0,0 +1,34 @@
+import express, { type Express } from "express";
+import cors from "cors";
+import pinoHttp from "pino-http";
+import router from "./routes";
+import { logger } from "./lib/logger";
+
+const app: Express = express();
+
+app.use(
+ pinoHttp({
+ logger,
+ serializers: {
+ req(req) {
+ return {
+ id: req.id,
+ method: req.method,
+ url: req.url?.split("?")[0],
+ };
+ },
+ res(res) {
+ return {
+ statusCode: res.statusCode,
+ };
+ },
+ },
+ }),
+);
+app.use(cors());
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+
+app.use("/api", router);
+
+export default app;
diff --git a/artifacts/api-server/src/index.ts b/artifacts/api-server/src/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b1f024dd070f40db5058650016bf63746994c0ac
--- /dev/null
+++ b/artifacts/api-server/src/index.ts
@@ -0,0 +1,25 @@
+import app from "./app";
+import { logger } from "./lib/logger";
+
+const rawPort = process.env["PORT"];
+
+if (!rawPort) {
+ throw new Error(
+ "PORT environment variable is required but was not provided.",
+ );
+}
+
+const port = Number(rawPort);
+
+if (Number.isNaN(port) || port <= 0) {
+ throw new Error(`Invalid PORT value: "${rawPort}"`);
+}
+
+app.listen(port, (err) => {
+ if (err) {
+ logger.error({ err }, "Error listening on port");
+ process.exit(1);
+ }
+
+ logger.info({ port }, "Server listening");
+});
diff --git a/artifacts/api-server/src/lib/.gitkeep b/artifacts/api-server/src/lib/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/artifacts/api-server/src/lib/logger.ts b/artifacts/api-server/src/lib/logger.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d9c67f79a82872af8336ab384014fa69bbe0695f
--- /dev/null
+++ b/artifacts/api-server/src/lib/logger.ts
@@ -0,0 +1,20 @@
+import pino from "pino";
+
+const isProduction = process.env.NODE_ENV === "production";
+
+export const logger = pino({
+ level: process.env.LOG_LEVEL ?? "info",
+ redact: [
+ "req.headers.authorization",
+ "req.headers.cookie",
+ "res.headers['set-cookie']",
+ ],
+ ...(isProduction
+ ? {}
+ : {
+ transport: {
+ target: "pino-pretty",
+ options: { colorize: true },
+ },
+ }),
+});
diff --git a/artifacts/api-server/src/middlewares/.gitkeep b/artifacts/api-server/src/middlewares/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/artifacts/api-server/src/routes/health.ts b/artifacts/api-server/src/routes/health.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c0a144626055cbc728d5d813d63b389ae1e82bee
--- /dev/null
+++ b/artifacts/api-server/src/routes/health.ts
@@ -0,0 +1,11 @@
+import { Router, type IRouter } from "express";
+import { HealthCheckResponse } from "@workspace/api-zod";
+
+const router: IRouter = Router();
+
+router.get("/healthz", (_req, res) => {
+ const data = HealthCheckResponse.parse({ status: "ok" });
+ res.json(data);
+});
+
+export default router;
diff --git a/artifacts/api-server/src/routes/index.ts b/artifacts/api-server/src/routes/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5a1f77abda69515f1657b86f551c97570d05d6a3
--- /dev/null
+++ b/artifacts/api-server/src/routes/index.ts
@@ -0,0 +1,8 @@
+import { Router, type IRouter } from "express";
+import healthRouter from "./health";
+
+const router: IRouter = Router();
+
+router.use(healthRouter);
+
+export default router;
diff --git a/artifacts/api-server/tsconfig.json b/artifacts/api-server/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..b60e718d970f7497ab0d119899c3b6b9cc881b32
--- /dev/null
+++ b/artifacts/api-server/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "types": ["node"]
+ },
+ "include": ["src"],
+ "references": [
+ {
+ "path": "../../lib/db"
+ },
+ {
+ "path": "../../lib/api-zod"
+ }
+ ]
+}
diff --git a/artifacts/mockup-sandbox/.replit-artifact/artifact.toml b/artifacts/mockup-sandbox/.replit-artifact/artifact.toml
new file mode 100644
index 0000000000000000000000000000000000000000..4e9156ef3a9f736e06cdf80287c956271eb5fe9d
--- /dev/null
+++ b/artifacts/mockup-sandbox/.replit-artifact/artifact.toml
@@ -0,0 +1,17 @@
+kind = "design"
+previewPath = "/__mockup"
+title = "Component Preview Server"
+version = "1.0.0"
+id = "XegfDyZt7HqfW2Bb8Ghoy"
+
+[[services]]
+localPort = 8081
+name = "Component Preview Server"
+paths = ["/__mockup"]
+
+[services.env]
+PORT = "8081"
+BASE_PATH = "/__mockup"
+
+[services.development]
+run = "pnpm --filter @workspace/mockup-sandbox run dev"
diff --git a/artifacts/mockup-sandbox/components.json b/artifacts/mockup-sandbox/components.json
new file mode 100644
index 0000000000000000000000000000000000000000..ba0c18c0c790c4270fe1d596a400e4e24dcc643e
--- /dev/null
+++ b/artifacts/mockup-sandbox/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/index.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "iconLibrary": "lucide",
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ }
+}
diff --git a/artifacts/mockup-sandbox/index.html b/artifacts/mockup-sandbox/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..1397a638300ec564af33d63612ff217ea2c4012b
--- /dev/null
+++ b/artifacts/mockup-sandbox/index.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mockup Canvas
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/artifacts/mockup-sandbox/mockupPreviewPlugin.ts b/artifacts/mockup-sandbox/mockupPreviewPlugin.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c09ed92cbbc0cbe8e5499340d22a576720eb3c8f
--- /dev/null
+++ b/artifacts/mockup-sandbox/mockupPreviewPlugin.ts
@@ -0,0 +1,180 @@
+import { mkdirSync, writeFileSync } from "fs";
+import path from "path";
+import glob from "fast-glob";
+import chokidar from "chokidar";
+import type { FSWatcher } from "chokidar";
+import type { Plugin } from "vite";
+
+const MOCKUPS_DIR = "src/components/mockups";
+const GENERATED_MODULE = "src/.generated/mockup-components.ts";
+
+interface DiscoveredComponent {
+ globKey: string;
+ importPath: string;
+}
+
+export function mockupPreviewPlugin(): Plugin {
+ let root = "";
+ let currentSource = "";
+ let watcher: FSWatcher | null = null;
+
+ function getMockupsAbsDir(): string {
+ return path.join(root, MOCKUPS_DIR);
+ }
+
+ function getGeneratedModuleAbsPath(): string {
+ return path.join(root, GENERATED_MODULE);
+ }
+
+ function isMockupFile(absolutePath: string): boolean {
+ const rel = path.relative(getMockupsAbsDir(), absolutePath);
+ return (
+ !rel.startsWith("..") && !path.isAbsolute(rel) && rel.endsWith(".tsx")
+ );
+ }
+
+ function isPreviewTarget(relativeToMockups: string): boolean {
+ return relativeToMockups
+ .split(path.sep)
+ .every((segment) => !segment.startsWith("_"));
+ }
+
+ async function discoverComponents(): Promise> {
+ const files = await glob(`${MOCKUPS_DIR}/**/*.tsx`, {
+ cwd: root,
+ ignore: ["**/_*/**", "**/_*.tsx"],
+ });
+
+ return files.map((f) => ({
+ globKey: "./" + f.slice("src/".length),
+ importPath: path.posix.relative("src/.generated", f),
+ }));
+ }
+
+ function generateSource(components: Array): string {
+ const entries = components
+ .map(
+ (c) =>
+ ` ${JSON.stringify(c.globKey)}: () => import(${JSON.stringify(c.importPath)})`,
+ )
+ .join(",\n");
+
+ return [
+ "// This file is auto-generated by mockupPreviewPlugin.ts.",
+ "type ModuleMap = Record Promise>>;",
+ "export const modules: ModuleMap = {",
+ entries,
+ "};",
+ "",
+ ].join("\n");
+ }
+
+ function shouldAutoRescan(pathname: string): boolean {
+ return (
+ pathname.includes("/components/mockups/") ||
+ pathname.includes("/.generated/mockup-components")
+ );
+ }
+
+ let refreshInFlight = false;
+ let refreshQueued = false;
+
+ async function refresh(): Promise {
+ if (refreshInFlight) {
+ refreshQueued = true;
+ return false;
+ }
+
+ refreshInFlight = true;
+ let changed = false;
+ try {
+ const components = await discoverComponents();
+ const newSource = generateSource(components);
+ if (newSource !== currentSource) {
+ currentSource = newSource;
+ const generatedModuleAbsPath = getGeneratedModuleAbsPath();
+ mkdirSync(path.dirname(generatedModuleAbsPath), { recursive: true });
+ writeFileSync(generatedModuleAbsPath, currentSource);
+ changed = true;
+ }
+ } finally {
+ refreshInFlight = false;
+ }
+
+ if (refreshQueued) {
+ refreshQueued = false;
+ const followUp = await refresh();
+ return changed || followUp;
+ }
+
+ return changed;
+ }
+
+ async function onFileAddedOrRemoved(): Promise {
+ await refresh();
+ }
+
+ return {
+ name: "mockup-preview",
+ enforce: "pre",
+
+ configResolved(config) {
+ root = config.root;
+ },
+
+ async buildStart() {
+ await refresh();
+ },
+
+ async configureServer(viteServer) {
+ await refresh();
+
+ const mockupsAbsDir = getMockupsAbsDir();
+ mkdirSync(mockupsAbsDir, { recursive: true });
+
+ watcher = chokidar.watch(mockupsAbsDir, {
+ ignoreInitial: true,
+ awaitWriteFinish: {
+ stabilityThreshold: 100,
+ pollInterval: 50,
+ },
+ });
+
+ watcher.on("add", (file) => {
+ if (
+ isMockupFile(file) &&
+ isPreviewTarget(path.relative(mockupsAbsDir, file))
+ ) {
+ void onFileAddedOrRemoved();
+ }
+ });
+
+ watcher.on("unlink", (file) => {
+ if (isMockupFile(file)) {
+ void onFileAddedOrRemoved();
+ }
+ });
+
+ viteServer.middlewares.use((req, res, next) => {
+ const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
+ const pathname = requestUrl.pathname;
+ const originalEnd = res.end.bind(res);
+
+ res.end = ((...args: Parameters) => {
+ if (res.statusCode === 404 && shouldAutoRescan(pathname)) {
+ void refresh();
+ }
+ return originalEnd(...args);
+ }) as typeof res.end;
+
+ next();
+ });
+ },
+
+ async closeWatcher() {
+ if (watcher) {
+ await watcher.close();
+ }
+ },
+ };
+}
diff --git a/artifacts/mockup-sandbox/package.json b/artifacts/mockup-sandbox/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..20e280611ead90d8869b7460aedbaa2fb14b506b
--- /dev/null
+++ b/artifacts/mockup-sandbox/package.json
@@ -0,0 +1,74 @@
+{
+ "name": "@workspace/mockup-sandbox",
+ "version": "2.0.0",
+ "type": "module",
+ "private": true,
+ "scripts": {
+ "dev": "vite dev",
+ "build": "vite build",
+ "preview": "vite preview",
+ "typecheck": "tsc -p tsconfig.json --noEmit"
+ },
+ "devDependencies": {
+ "@hookform/resolvers": "^3.10.0",
+ "@radix-ui/react-accordion": "^1.2.12",
+ "@radix-ui/react-alert-dialog": "^1.1.15",
+ "@radix-ui/react-aspect-ratio": "^1.1.8",
+ "@radix-ui/react-avatar": "^1.1.11",
+ "@radix-ui/react-checkbox": "^1.3.3",
+ "@radix-ui/react-collapsible": "^1.1.12",
+ "@radix-ui/react-context-menu": "^2.2.16",
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-hover-card": "^1.1.15",
+ "@radix-ui/react-label": "^2.1.8",
+ "@radix-ui/react-menubar": "^1.1.16",
+ "@radix-ui/react-navigation-menu": "^1.2.14",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.8",
+ "@radix-ui/react-radio-group": "^1.3.8",
+ "@radix-ui/react-scroll-area": "^1.2.10",
+ "@radix-ui/react-select": "^2.2.6",
+ "@radix-ui/react-separator": "^1.1.8",
+ "@radix-ui/react-slider": "^1.3.6",
+ "@radix-ui/react-slot": "^1.2.4",
+ "@radix-ui/react-switch": "^1.2.6",
+ "@radix-ui/react-tabs": "^1.1.13",
+ "@radix-ui/react-toast": "^1.2.7",
+ "@radix-ui/react-toggle": "^1.1.10",
+ "@radix-ui/react-toggle-group": "^1.1.11",
+ "@radix-ui/react-tooltip": "^1.2.8",
+ "@replit/vite-plugin-cartographer": "catalog:",
+ "@replit/vite-plugin-runtime-error-modal": "catalog:",
+ "@tailwindcss/vite": "catalog:",
+ "@types/node": "catalog:",
+ "@types/react": "catalog:",
+ "@types/react-dom": "catalog:",
+ "@vitejs/plugin-react": "catalog:",
+ "chokidar": "^4.0.3",
+ "class-variance-authority": "catalog:",
+ "clsx": "catalog:",
+ "cmdk": "^1.1.1",
+ "date-fns": "^3.6.0",
+ "embla-carousel-react": "^8.6.0",
+ "fast-glob": "^3.3.3",
+ "framer-motion": "catalog:",
+ "input-otp": "^1.4.2",
+ "lucide-react": "catalog:",
+ "next-themes": "^0.4.6",
+ "react": "catalog:",
+ "react-day-picker": "^9.11.1",
+ "react-dom": "catalog:",
+ "react-hook-form": "^7.66.0",
+ "react-resizable-panels": "^2.1.9",
+ "recharts": "^2.15.4",
+ "sonner": "^2.0.7",
+ "tailwind-merge": "catalog:",
+ "tailwindcss": "catalog:",
+ "tailwindcss-animate": "^1.0.7",
+ "tw-animate-css": "^1.4.0",
+ "vaul": "^1.1.2",
+ "vite": "catalog:",
+ "zod": "catalog:"
+ }
+}
diff --git a/artifacts/mockup-sandbox/src/.generated/mockup-components.ts b/artifacts/mockup-sandbox/src/.generated/mockup-components.ts
new file mode 100644
index 0000000000000000000000000000000000000000..97c87e1fe941456236fbbaa8a06da5a8dbab649c
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/.generated/mockup-components.ts
@@ -0,0 +1,5 @@
+// This file is auto-generated by mockupPreviewPlugin.ts.
+type ModuleMap = Record Promise>>;
+export const modules: ModuleMap = {
+
+};
diff --git a/artifacts/mockup-sandbox/src/App.tsx b/artifacts/mockup-sandbox/src/App.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d9c2317f0d952aefe737473c57dc4afe359048a0
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/App.tsx
@@ -0,0 +1,146 @@
+import { useEffect, useState, type ComponentType } from "react";
+
+import { modules as discoveredModules } from "./.generated/mockup-components";
+
+type ModuleMap = Record Promise>>;
+
+function _resolveComponent(
+ mod: Record,
+ name: string,
+): ComponentType | undefined {
+ const fns = Object.values(mod).filter(
+ (v) => typeof v === "function",
+ ) as ComponentType[];
+ return (
+ (mod.default as ComponentType) ||
+ (mod.Preview as ComponentType) ||
+ (mod[name] as ComponentType) ||
+ fns[fns.length - 1]
+ );
+}
+
+function PreviewRenderer({
+ componentPath,
+ modules,
+}: {
+ componentPath: string;
+ modules: ModuleMap;
+}) {
+ const [Component, setComponent] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ let cancelled = false;
+
+ setComponent(null);
+ setError(null);
+
+ async function loadComponent(): Promise {
+ const key = `./components/mockups/${componentPath}.tsx`;
+ const loader = modules[key];
+ if (!loader) {
+ setError(`No component found at ${componentPath}.tsx`);
+ return;
+ }
+
+ try {
+ const mod = await loader();
+ if (cancelled) {
+ return;
+ }
+ const name = componentPath.split("/").pop()!;
+ const comp = _resolveComponent(mod, name);
+ if (!comp) {
+ setError(
+ `No exported React component found in ${componentPath}.tsx\n\nMake sure the file has at least one exported function component.`,
+ );
+ return;
+ }
+ setComponent(() => comp);
+ } catch (e) {
+ if (cancelled) {
+ return;
+ }
+
+ const message = e instanceof Error ? e.message : String(e);
+ setError(`Failed to load preview.\n${message}`);
+ }
+ }
+
+ void loadComponent();
+
+ return () => {
+ cancelled = true;
+ };
+ }, [componentPath, modules]);
+
+ if (error) {
+ return (
+
+ {error}
+
+ );
+ }
+
+ if (!Component) return null;
+
+ return ;
+}
+
+function getBasePath(): string {
+ return import.meta.env.BASE_URL.replace(/\/$/, "");
+}
+
+function getPreviewExamplePath(): string {
+ const basePath = getBasePath();
+ return `${basePath}/preview/ComponentName`;
+}
+
+function Gallery() {
+ return (
+
+
+
+ Component Preview Server
+
+
+ This server renders individual components for the workspace canvas.
+
+
+ Access component previews at{" "}
+
+ {getPreviewExamplePath()}
+
+
+
+
+ );
+}
+
+function getPreviewPath(): string | null {
+ const basePath = getBasePath();
+ const { pathname } = window.location;
+ const local =
+ basePath && pathname.startsWith(basePath)
+ ? pathname.slice(basePath.length) || "/"
+ : pathname;
+ const match = local.match(/^\/preview\/(.+)$/);
+ return match ? match[1] : null;
+}
+
+function App() {
+ const previewPath = getPreviewPath();
+
+ if (previewPath) {
+ return (
+
+ );
+ }
+
+ return ;
+}
+
+export default App;
diff --git a/artifacts/mockup-sandbox/src/components/ui/accordion.tsx b/artifacts/mockup-sandbox/src/components/ui/accordion.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e1797c93458f3751a2e0f1189ff50d77f20ae3ed
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/components/ui/accordion.tsx
@@ -0,0 +1,55 @@
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+))
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/artifacts/mockup-sandbox/src/components/ui/alert-dialog.tsx b/artifacts/mockup-sandbox/src/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fa2b4429f4303210462ad03f9f49280ac5ae07ce
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,139 @@
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogHeader.displayName = "AlertDialogHeader"
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogFooter.displayName = "AlertDialogFooter"
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/artifacts/mockup-sandbox/src/components/ui/alert.tsx b/artifacts/mockup-sandbox/src/components/ui/alert.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5afd41d142c95c74069ebb5460117e74ead3b9df
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/components/ui/alert.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+ "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",
+ {
+ variants: {
+ variant: {
+ default: "bg-background text-foreground",
+ destructive:
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Alert = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & VariantProps
+>(({ className, variant, ...props }, ref) => (
+
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/artifacts/mockup-sandbox/src/components/ui/aspect-ratio.tsx b/artifacts/mockup-sandbox/src/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c4abbf37f217c715a0eaade7f45ac78600df419f
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/components/ui/aspect-ratio.tsx
@@ -0,0 +1,5 @@
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
+
+const AspectRatio = AspectRatioPrimitive.Root
+
+export { AspectRatio }
diff --git a/artifacts/mockup-sandbox/src/components/ui/avatar.tsx b/artifacts/mockup-sandbox/src/components/ui/avatar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..51e507ba9d08bcdbb1fb630498f1cbdf2bf50093
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/artifacts/mockup-sandbox/src/components/ui/badge.tsx b/artifacts/mockup-sandbox/src/components/ui/badge.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cfd176a05b00824f7a6a029b10bb935ef38d10bb
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/components/ui/badge.tsx
@@ -0,0 +1,37 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const badgeVariants = cva(
+ "whitespace-nowrap inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2" +
+ " hover-elevate ",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground shadow-xs",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground",
+ destructive:
+ "border-transparent bg-destructive text-destructive-foreground shadow-xs",
+ outline: "text-foreground border [border-color:var(--badge-outline)]",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+export interface BadgeProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/artifacts/mockup-sandbox/src/components/ui/breadcrumb.tsx b/artifacts/mockup-sandbox/src/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..60e6c96f72f0350d08b47e4730cab8f3975dc853
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,115 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Breadcrumb = React.forwardRef<
+ HTMLElement,
+ React.ComponentPropsWithoutRef<"nav"> & {
+ separator?: React.ReactNode
+ }
+>(({ ...props }, ref) => )
+Breadcrumb.displayName = "Breadcrumb"
+
+const BreadcrumbList = React.forwardRef<
+ HTMLOListElement,
+ React.ComponentPropsWithoutRef<"ol">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbList.displayName = "BreadcrumbList"
+
+const BreadcrumbItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentPropsWithoutRef<"li">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbItem.displayName = "BreadcrumbItem"
+
+const BreadcrumbLink = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentPropsWithoutRef<"a"> & {
+ asChild?: boolean
+ }
+>(({ asChild, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+
+ )
+})
+BreadcrumbLink.displayName = "BreadcrumbLink"
+
+const BreadcrumbPage = React.forwardRef<
+ HTMLSpanElement,
+ React.ComponentPropsWithoutRef<"span">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbPage.displayName = "BreadcrumbPage"
+
+const BreadcrumbSeparator = ({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) => (
+ svg]:w-3.5 [&>svg]:h-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+)
+BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
+
+const BreadcrumbEllipsis = ({
+ className,
+ ...props
+}: React.ComponentProps<"span">) => (
+
+
+ More
+
+)
+BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/artifacts/mockup-sandbox/src/components/ui/button-group.tsx b/artifacts/mockup-sandbox/src/components/ui/button-group.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d6e78014fc491465c059253d541b10afdaa3227a
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/components/ui/button-group.tsx
@@ -0,0 +1,83 @@
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { Separator } from "@/components/ui/separator"
+
+const buttonGroupVariants = cva(
+ "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
+ vertical:
+ "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
+ },
+ },
+ defaultVariants: {
+ orientation: "horizontal",
+ },
+ }
+)
+
+function ButtonGroup({
+ className,
+ orientation,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function ButtonGroupText({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"div"> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot : "div"
+
+ return (
+
+ )
+}
+
+function ButtonGroupSeparator({
+ className,
+ orientation = "vertical",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+ buttonGroupVariants,
+}
diff --git a/artifacts/mockup-sandbox/src/components/ui/button.tsx b/artifacts/mockup-sandbox/src/components/ui/button.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a471859fcdc6b46cc07cf9445f8b7a1765d563df
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/components/ui/button.tsx
@@ -0,0 +1,58 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0" +
+" hover-elevate active-elevate-2",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground border border-primary-border",
+ destructive:
+ "bg-destructive text-destructive-foreground shadow-sm border-destructive-border",
+ outline:
+ "border [border-color:var(--button-outline)] shadow-xs active:shadow-none",
+ secondary:
+ "border bg-secondary text-secondary-foreground border border-secondary-border",
+ ghost: "border border-transparent",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "min-h-9 px-4 py-2",
+ sm: "min-h-8 rounded-md px-3 text-xs",
+ lg: "min-h-10 rounded-md px-8",
+ icon: "h-9 w-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/artifacts/mockup-sandbox/src/components/ui/calendar.tsx b/artifacts/mockup-sandbox/src/components/ui/calendar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a623682dab53fbe9e1777b83b2e93763837653d8
--- /dev/null
+++ b/artifacts/mockup-sandbox/src/components/ui/calendar.tsx
@@ -0,0 +1,213 @@
+"use client"
+
+import * as React from "react"
+import {
+ ChevronDownIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+} from "lucide-react"
+import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { Button, buttonVariants } from "@/components/ui/button"
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = "label",
+ buttonVariant = "ghost",
+ formatters,
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps["variant"]
+}) {
+ const defaultClassNames = getDefaultClassNames()
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className
+ )}
+ captionLayout={captionLayout}
+ formatters={{
+ formatMonthDropdown: (date) =>
+ date.toLocaleString("default", { month: "short" }),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn("w-fit", defaultClassNames.root),
+ months: cn(
+ "relative flex flex-col gap-4 md:flex-row",
+ defaultClassNames.months
+ ),
+ month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
+ nav: cn(
+ "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
+ defaultClassNames.nav
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
+ defaultClassNames.button_previous
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
+ defaultClassNames.button_next
+ ),
+ month_caption: cn(
+ "flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
+ defaultClassNames.month_caption
+ ),
+ dropdowns: cn(
+ "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
+ defaultClassNames.dropdowns
+ ),
+ dropdown_root: cn(
+ "has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
+ defaultClassNames.dropdown_root
+ ),
+ dropdown: cn(
+ "bg-popover absolute inset-0 opacity-0",
+ defaultClassNames.dropdown
+ ),
+ caption_label: cn(
+ "select-none font-medium",
+ captionLayout === "label"
+ ? "text-sm"
+ : "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
+ defaultClassNames.caption_label
+ ),
+ table: "w-full border-collapse",
+ weekdays: cn("flex", defaultClassNames.weekdays),
+ weekday: cn(
+ "text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
+ defaultClassNames.weekday
+ ),
+ week: cn("mt-2 flex w-full", defaultClassNames.week),
+ week_number_header: cn(
+ "w-[--cell-size] select-none",
+ defaultClassNames.week_number_header
+ ),
+ week_number: cn(
+ "text-muted-foreground select-none text-[0.8rem]",
+ defaultClassNames.week_number
+ ),
+ day: cn(
+ "group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
+ defaultClassNames.day
+ ),
+ range_start: cn(
+ "bg-accent rounded-l-md",
+ defaultClassNames.range_start
+ ),
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
+ range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
+ today: cn(
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
+ defaultClassNames.today
+ ),
+ outside: cn(
+ "text-muted-foreground aria-selected:text-muted-foreground",
+ defaultClassNames.outside
+ ),
+ disabled: cn(
+ "text-muted-foreground opacity-50",
+ defaultClassNames.disabled
+ ),
+ hidden: cn("invisible", defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ )
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === "left") {
+ return (
+
+ )
+ }
+
+ if (orientation === "right") {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+ |
+ )
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ )
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames()
+
+ const ref = React.useRef(null)
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus()
+ }, [modifiers.focused])
+
+ return (
+