From 05e572073cd08850f41689ee5d272d7698a50de8 Mon Sep 17 00:00:00 2001 From: Alan Daniel Date: Tue, 7 Apr 2026 11:41:29 -0400 Subject: [PATCH] Import Circle UI base system --- .../src/routes/_protected/dashboard.tsx | 108 +- apps/dashboard/src/routes/index.tsx | 184 +- apps/dashboard/src/routes/login.tsx | 44 +- packages/ui/package.json | 24 + packages/ui/src/components/alert-dialog.tsx | 157 ++ packages/ui/src/components/alert.tsx | 66 + packages/ui/src/components/avatar.tsx | 53 + packages/ui/src/components/badge.tsx | 46 + packages/ui/src/components/breadcrumb.tsx | 107 + packages/ui/src/components/button.tsx | 60 + packages/ui/src/components/calendar.tsx | 81 + packages/ui/src/components/card.tsx | 75 + packages/ui/src/components/checkbox.tsx | 32 + packages/ui/src/components/collapsible.tsx | 33 + packages/ui/src/components/command.tsx | 177 ++ packages/ui/src/components/context-menu.tsx | 252 ++ packages/ui/src/components/dialog.tsx | 135 + packages/ui/src/components/dropdown-menu.tsx | 257 ++ packages/ui/src/components/form.tsx | 166 ++ packages/ui/src/components/input.tsx | 21 + packages/ui/src/components/label.tsx | 24 + packages/ui/src/components/popover.tsx | 48 + packages/ui/src/components/progress.tsx | 31 + packages/ui/src/components/resizable.tsx | 56 + packages/ui/src/components/select.tsx | 181 ++ packages/ui/src/components/separator.tsx | 28 + packages/ui/src/components/sheet.tsx | 139 + packages/ui/src/components/sidebar.tsx | 728 +++++ packages/ui/src/components/skeleton.tsx | 13 + packages/ui/src/components/sonner.tsx | 30 + packages/ui/src/components/switch.tsx | 31 + packages/ui/src/components/table.tsx | 116 + packages/ui/src/components/tabs.tsx | 66 + packages/ui/src/components/textarea.tsx | 18 + packages/ui/src/components/toggle.tsx | 47 + packages/ui/src/components/tooltip.tsx | 60 + packages/ui/src/hooks/use-mobile.ts | 21 + packages/ui/src/styles/globals.css | 180 +- pnpm-lock.yaml | 2510 ++++++++++++++--- 39 files changed, 5822 insertions(+), 583 deletions(-) create mode 100644 packages/ui/src/components/alert-dialog.tsx create mode 100644 packages/ui/src/components/alert.tsx create mode 100644 packages/ui/src/components/avatar.tsx create mode 100644 packages/ui/src/components/badge.tsx create mode 100644 packages/ui/src/components/breadcrumb.tsx create mode 100644 packages/ui/src/components/button.tsx create mode 100644 packages/ui/src/components/calendar.tsx create mode 100644 packages/ui/src/components/card.tsx create mode 100644 packages/ui/src/components/checkbox.tsx create mode 100644 packages/ui/src/components/collapsible.tsx create mode 100644 packages/ui/src/components/command.tsx create mode 100644 packages/ui/src/components/context-menu.tsx create mode 100644 packages/ui/src/components/dialog.tsx create mode 100644 packages/ui/src/components/dropdown-menu.tsx create mode 100644 packages/ui/src/components/form.tsx create mode 100644 packages/ui/src/components/input.tsx create mode 100644 packages/ui/src/components/label.tsx create mode 100644 packages/ui/src/components/popover.tsx create mode 100644 packages/ui/src/components/progress.tsx create mode 100644 packages/ui/src/components/resizable.tsx create mode 100644 packages/ui/src/components/select.tsx create mode 100644 packages/ui/src/components/separator.tsx create mode 100644 packages/ui/src/components/sheet.tsx create mode 100644 packages/ui/src/components/sidebar.tsx create mode 100644 packages/ui/src/components/skeleton.tsx create mode 100644 packages/ui/src/components/sonner.tsx create mode 100644 packages/ui/src/components/switch.tsx create mode 100644 packages/ui/src/components/table.tsx create mode 100644 packages/ui/src/components/tabs.tsx create mode 100644 packages/ui/src/components/textarea.tsx create mode 100644 packages/ui/src/components/toggle.tsx create mode 100644 packages/ui/src/components/tooltip.tsx create mode 100644 packages/ui/src/hooks/use-mobile.ts diff --git a/apps/dashboard/src/routes/_protected/dashboard.tsx b/apps/dashboard/src/routes/_protected/dashboard.tsx index 465b5c6..69804db 100644 --- a/apps/dashboard/src/routes/_protected/dashboard.tsx +++ b/apps/dashboard/src/routes/_protected/dashboard.tsx @@ -1,3 +1,16 @@ +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@quickhub/ui/components/avatar"; +import { Badge } from "@quickhub/ui/components/badge"; +import { Button } from "@quickhub/ui/components/button"; +import { + Card, + CardDescription, + CardHeader, + CardTitle, +} from "@quickhub/ui/components/card"; import { createFileRoute } from "@tanstack/react-router"; import { signOut } from "#/lib/auth.client"; @@ -7,31 +20,80 @@ export const Route = createFileRoute("/_protected/dashboard")({ function DashboardPage() { const { user } = Route.useRouteContext(); + const displayName = user.name ?? user.email ?? "QuickHub user"; + const initials = displayName + .split(" ") + .map((part) => part[0]) + .join("") + .slice(0, 2) + .toUpperCase(); return ( -
-
-
- {user.image && ( - {user.name} - )} -

Welcome, {user.name}

-
- +
+
+ + +
+ + + {initials} + +
+
+ + Welcome, {displayName} + + Connected +
+ + The dashboard now pulls from the shared Circle-derived + component library in @quickhub/ui. + +
+
+ +
+
+ +
+ + + Theme + + Circle tokens now define the workspace palette and surface + language. + + + + + + Primitives + + Shared buttons, cards, badges, dialogs, forms, and more now live + in the UI package. + + + + + + Next step + + Build QuickHub-specific layouts on top of these imported + foundation pieces. + + + +
); diff --git a/apps/dashboard/src/routes/index.tsx b/apps/dashboard/src/routes/index.tsx index 2af65f7..7346b84 100644 --- a/apps/dashboard/src/routes/index.tsx +++ b/apps/dashboard/src/routes/index.tsx @@ -1,3 +1,12 @@ +import { Badge } from "@quickhub/ui/components/badge"; +import { Button } from "@quickhub/ui/components/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@quickhub/ui/components/card"; import { createFileRoute, Link } from "@tanstack/react-router"; import { getSession } from "#/lib/auth.functions"; import { getUserRepos } from "#/lib/github.functions"; @@ -18,68 +27,127 @@ export const Route = createFileRoute("/")({ function Home() { const { session } = Route.useRouteContext(); const { repos } = Route.useLoaderData(); + const ctaCopy = session ? "Open dashboard" : "Continue with GitHub"; + const ctaLink = session ? "/dashboard" : "/login"; return ( -
-
-

QuickHub

-

- A lightweight, focused view of GitHub. -

- {session ? ( - - Go to Dashboard - - ) : ( - - Sign in with GitHub - +
+
+
+ + + + Circle base imported + +
+ + QuickHub, now on a shared UI foundation. + + + Circle's tokens and primitives are now the baseline for + QuickHub, so the app can grow from a consistent design system + instead of ad hoc styles. + +
+
+ + +

+ Shared theme tokens live in @quickhub/ui and can + now drive future screens. +

+
+
+ + + + Base kit + + Imported primitives ready to extend. + + + +
+ Theme tokens + active +
+
+ 32 UI primitives + shared +
+
+ Dashboard wired + validated +
+
+
+
+ + {repos.length > 0 && ( +
+
+

+ Recent repositories +

+

+ Current data rendered with the shared card, badge, and button + language. +

+
+
+ {repos.map((repo) => ( + + +
+
+ + + {repo.name} + + + + {repo.description ?? + "A focused repository surfaced from your GitHub account."} + +
+ {repo.isPrivate ? ( + Private + ) : ( + Public + )} +
+
+ +
+ {repo.language ? {repo.language} : null} + {repo.stars ? {repo.stars} stars : null} +
+ +
+
+ ))} +
+
)}
- - {repos.length > 0 && ( -
-

Recent repositories

-
    - {repos.map((repo) => ( -
  • -
    -
    - - {repo.name} - - {repo.isPrivate && ( - - private - - )} -
    -
    - {repo.language && {repo.language}} - {repo.stars ? {repo.stars} stars : null} -
    -
    - {repo.description && ( -

    - {repo.description} -

    - )} -
  • - ))} -
-
- )}
); } diff --git a/apps/dashboard/src/routes/login.tsx b/apps/dashboard/src/routes/login.tsx index c782b2a..5e5910d 100644 --- a/apps/dashboard/src/routes/login.tsx +++ b/apps/dashboard/src/routes/login.tsx @@ -1,3 +1,11 @@ +import { Button } from "@quickhub/ui/components/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@quickhub/ui/components/card"; import { createFileRoute, redirect } from "@tanstack/react-router"; import { signIn } from "#/lib/auth.client"; import { getSession } from "#/lib/auth.functions"; @@ -12,20 +20,28 @@ export const Route = createFileRoute("/login")({ function LoginPage() { return ( -
-
-

Sign in to QuickHub

-

- Connect your GitHub account to get started. -

- -
+
+ + + + Sign in to QuickHub + + + Connect GitHub to start from the imported Circle base theme and grow + your own workflow from there. + + + + + +
); } diff --git a/packages/ui/package.json b/packages/ui/package.json index d787d5e..939bcf7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -16,9 +16,33 @@ "format": "biome format" }, "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.6", + "@radix-ui/react-avatar": "^1.1.3", + "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-collapsible": "^1.1.3", + "@radix-ui/react-context-menu": "^2.2.10", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-progress": "^1.1.2", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-separator": "^1.1.2", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-toggle": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.8", + "cmdk": "1.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.545.0", + "next-themes": "^0.4.4", + "react-day-picker": "8.10.1", + "react-hook-form": "^7.54.2", + "react-resizable-panels": "^3.0.3", + "sonner": "^2.0.1", + "tailwindcss-animate": "^1.0.7", "tailwind-merge": "^3.3.0" }, "devDependencies": { diff --git a/packages/ui/src/components/alert-dialog.tsx b/packages/ui/src/components/alert-dialog.tsx new file mode 100644 index 0000000..325679d --- /dev/null +++ b/packages/ui/src/components/alert-dialog.tsx @@ -0,0 +1,157 @@ +"use client"; + +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; +import type * as React from "react"; + +import { cn } from "../lib/utils"; +import { buttonVariants } from "./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, + ...props +}: React.ComponentProps) { + return ( + + + + + ); +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/packages/ui/src/components/alert.tsx b/packages/ui/src/components/alert.tsx new file mode 100644 index 0000000..74bd49e --- /dev/null +++ b/packages/ui/src/components/alert.tsx @@ -0,0 +1,66 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import type * as React from "react"; + +import { cn } from "../lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "text-destructive-foreground [&>svg]:text-current *:data-[slot=alert-description]:text-destructive-foreground/80", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ); +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { Alert, AlertTitle, AlertDescription }; diff --git a/packages/ui/src/components/avatar.tsx b/packages/ui/src/components/avatar.tsx new file mode 100644 index 0000000..35de531 --- /dev/null +++ b/packages/ui/src/components/avatar.tsx @@ -0,0 +1,53 @@ +"use client"; + +import * as AvatarPrimitive from "@radix-ui/react-avatar"; +import type * as React from "react"; + +import { cn } from "../lib/utils"; + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/packages/ui/src/components/badge.tsx b/packages/ui/src/components/badge.tsx new file mode 100644 index 0000000..464a7f7 --- /dev/null +++ b/packages/ui/src/components/badge.tsx @@ -0,0 +1,46 @@ +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import type * as React from "react"; + +import { cn } from "../lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-md border px-3 py-1 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-auto", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span"; + + return ( + + ); +} + +export { Badge, badgeVariants }; diff --git a/packages/ui/src/components/breadcrumb.tsx b/packages/ui/src/components/breadcrumb.tsx new file mode 100644 index 0000000..3d77ee1 --- /dev/null +++ b/packages/ui/src/components/breadcrumb.tsx @@ -0,0 +1,107 @@ +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; +import type * as React from "react"; + +import { cn } from "../lib/utils"; + +function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { + return