From 4e623e621920a3d15820d75c3876f37fe21e4884 Mon Sep 17 00:00:00 2001 From: Ben Allenden Date: Sun, 12 Oct 2025 16:23:09 +0100 Subject: [PATCH 01/17] Move libs/ui components to libs/ui/components/ --- apps/client/src/app/loading.tsx | 4 ++-- .../src/features/auth/components/auth-form-wrapper.tsx | 2 +- apps/client/src/features/auth/components/back-to-home.tsx | 4 ++-- apps/client/src/features/landing/components/cta.tsx | 6 +++--- apps/client/src/features/landing/components/footer.tsx | 2 +- apps/client/src/features/landing/components/hero.tsx | 6 +++--- .../features/onboarding/components/multi-step-loader.tsx | 4 ++-- apps/client/src/libs/ui/{ => components}/button.tsx | 0 apps/client/src/libs/ui/{ => components}/container.tsx | 0 apps/client/src/libs/ui/{ => components}/page.tsx | 0 apps/client/src/libs/ui/{ => components}/separator.tsx | 0 apps/client/src/libs/ui/{ => components}/typography/h1.tsx | 0 apps/client/src/libs/ui/{ => components}/typography/h3.tsx | 0 .../src/libs/ui/{ => components}/typography/large.tsx | 0 .../client/src/libs/ui/{ => components}/typography/lead.tsx | 0 .../src/libs/ui/{ => components}/typography/muted.tsx | 0 apps/client/src/libs/ui/{ => components}/typography/p.tsx | 0 .../src/libs/ui/{ => components}/typography/small.tsx | 0 apps/client/src/routes/(auth)/_layout.tsx | 2 +- apps/client/src/routes/(auth)/index.tsx | 2 +- apps/client/src/routes/(onboarding)/onboarding.tsx | 2 +- 21 files changed, 17 insertions(+), 17 deletions(-) rename apps/client/src/libs/ui/{ => components}/button.tsx (100%) rename apps/client/src/libs/ui/{ => components}/container.tsx (100%) rename apps/client/src/libs/ui/{ => components}/page.tsx (100%) rename apps/client/src/libs/ui/{ => components}/separator.tsx (100%) rename apps/client/src/libs/ui/{ => components}/typography/h1.tsx (100%) rename apps/client/src/libs/ui/{ => components}/typography/h3.tsx (100%) rename apps/client/src/libs/ui/{ => components}/typography/large.tsx (100%) rename apps/client/src/libs/ui/{ => components}/typography/lead.tsx (100%) rename apps/client/src/libs/ui/{ => components}/typography/muted.tsx (100%) rename apps/client/src/libs/ui/{ => components}/typography/p.tsx (100%) rename apps/client/src/libs/ui/{ => components}/typography/small.tsx (100%) diff --git a/apps/client/src/app/loading.tsx b/apps/client/src/app/loading.tsx index c557951d..804f8eb1 100644 --- a/apps/client/src/app/loading.tsx +++ b/apps/client/src/app/loading.tsx @@ -3,8 +3,8 @@ import { useEffect, useState } from "react" import { AnimatePresence, motion } from "motion/react" import { EchoLogo } from "@/common/components/logos/echo-logo" -import { MotionContainer } from "@/libs/ui/container" -import { Page } from "@/libs/ui/page" +import { MotionContainer } from "@/libs/ui/components/container" +import { Page } from "@/libs/ui/components/page" interface Props { isReady: boolean diff --git a/apps/client/src/features/auth/components/auth-form-wrapper.tsx b/apps/client/src/features/auth/components/auth-form-wrapper.tsx index 0bff344c..e85f72de 100644 --- a/apps/client/src/features/auth/components/auth-form-wrapper.tsx +++ b/apps/client/src/features/auth/components/auth-form-wrapper.tsx @@ -1,6 +1,6 @@ import type React from "react" -import { MotionContainer } from "@/libs/ui/container" +import { MotionContainer } from "@/libs/ui/components/container" import { BackToHome } from "./back-to-home" diff --git a/apps/client/src/features/auth/components/back-to-home.tsx b/apps/client/src/features/auth/components/back-to-home.tsx index d5a9c764..ac27ecf0 100644 --- a/apps/client/src/features/auth/components/back-to-home.tsx +++ b/apps/client/src/features/auth/components/back-to-home.tsx @@ -1,8 +1,8 @@ import { Link } from "@tanstack/react-router" import { ArrowLeft } from "lucide-react" -import { Button } from "@/libs/ui/button" -import { MotionContainer } from "@/libs/ui/container" +import { Button } from "@/libs/ui/components/button" +import { MotionContainer } from "@/libs/ui/components/container" export function BackToHome() { return ( diff --git a/apps/client/src/features/landing/components/cta.tsx b/apps/client/src/features/landing/components/cta.tsx index 7bf58e5c..3eb42312 100644 --- a/apps/client/src/features/landing/components/cta.tsx +++ b/apps/client/src/features/landing/components/cta.tsx @@ -1,8 +1,8 @@ import { Link } from "@tanstack/react-router" -import { Button } from "@/libs/ui/button" -import { MotionContainer } from "@/libs/ui/container" -import { LabelledSeparator } from "@/libs/ui/separator" +import { Button } from "@/libs/ui/components/button" +import { MotionContainer } from "@/libs/ui/components/container" +import { LabelledSeparator } from "@/libs/ui/components/separator" export function CallToAction() { return ( diff --git a/apps/client/src/features/landing/components/footer.tsx b/apps/client/src/features/landing/components/footer.tsx index 455a9d79..6b5adbb9 100644 --- a/apps/client/src/features/landing/components/footer.tsx +++ b/apps/client/src/features/landing/components/footer.tsx @@ -1,4 +1,4 @@ -import { MotionContainer } from "@/libs/ui/container" +import { MotionContainer } from "@/libs/ui/components/container" export function Footer() { return ( diff --git a/apps/client/src/features/landing/components/hero.tsx b/apps/client/src/features/landing/components/hero.tsx index 8335474a..947b3afb 100644 --- a/apps/client/src/features/landing/components/hero.tsx +++ b/apps/client/src/features/landing/components/hero.tsx @@ -1,7 +1,7 @@ import { EchoLogo } from "@/common/components/logos/echo-logo" -import { MotionContainer } from "@/libs/ui/container" -import H1 from "@/libs/ui/typography/h1" -import Lead from "@/libs/ui/typography/lead" +import { MotionContainer } from "@/libs/ui/components/container" +import H1 from "@/libs/ui/components/typography/h1" +import Lead from "@/libs/ui/components/typography/lead" export function Hero() { return ( diff --git a/apps/client/src/features/onboarding/components/multi-step-loader.tsx b/apps/client/src/features/onboarding/components/multi-step-loader.tsx index d850dcbf..b9cafa7e 100644 --- a/apps/client/src/features/onboarding/components/multi-step-loader.tsx +++ b/apps/client/src/features/onboarding/components/multi-step-loader.tsx @@ -4,8 +4,8 @@ import type { MutationStatus } from "@tanstack/react-query" import { AnimatePresence } from "motion/react" import Spinner from "@/common/components/spinner" -import { Button } from "@/libs/ui/button" -import { MotionContainer } from "@/libs/ui/container" +import { Button } from "@/libs/ui/components/button" +import { MotionContainer } from "@/libs/ui/components/container" interface Props { status: MutationStatus diff --git a/apps/client/src/libs/ui/button.tsx b/apps/client/src/libs/ui/components/button.tsx similarity index 100% rename from apps/client/src/libs/ui/button.tsx rename to apps/client/src/libs/ui/components/button.tsx diff --git a/apps/client/src/libs/ui/container.tsx b/apps/client/src/libs/ui/components/container.tsx similarity index 100% rename from apps/client/src/libs/ui/container.tsx rename to apps/client/src/libs/ui/components/container.tsx diff --git a/apps/client/src/libs/ui/page.tsx b/apps/client/src/libs/ui/components/page.tsx similarity index 100% rename from apps/client/src/libs/ui/page.tsx rename to apps/client/src/libs/ui/components/page.tsx diff --git a/apps/client/src/libs/ui/separator.tsx b/apps/client/src/libs/ui/components/separator.tsx similarity index 100% rename from apps/client/src/libs/ui/separator.tsx rename to apps/client/src/libs/ui/components/separator.tsx diff --git a/apps/client/src/libs/ui/typography/h1.tsx b/apps/client/src/libs/ui/components/typography/h1.tsx similarity index 100% rename from apps/client/src/libs/ui/typography/h1.tsx rename to apps/client/src/libs/ui/components/typography/h1.tsx diff --git a/apps/client/src/libs/ui/typography/h3.tsx b/apps/client/src/libs/ui/components/typography/h3.tsx similarity index 100% rename from apps/client/src/libs/ui/typography/h3.tsx rename to apps/client/src/libs/ui/components/typography/h3.tsx diff --git a/apps/client/src/libs/ui/typography/large.tsx b/apps/client/src/libs/ui/components/typography/large.tsx similarity index 100% rename from apps/client/src/libs/ui/typography/large.tsx rename to apps/client/src/libs/ui/components/typography/large.tsx diff --git a/apps/client/src/libs/ui/typography/lead.tsx b/apps/client/src/libs/ui/components/typography/lead.tsx similarity index 100% rename from apps/client/src/libs/ui/typography/lead.tsx rename to apps/client/src/libs/ui/components/typography/lead.tsx diff --git a/apps/client/src/libs/ui/typography/muted.tsx b/apps/client/src/libs/ui/components/typography/muted.tsx similarity index 100% rename from apps/client/src/libs/ui/typography/muted.tsx rename to apps/client/src/libs/ui/components/typography/muted.tsx diff --git a/apps/client/src/libs/ui/typography/p.tsx b/apps/client/src/libs/ui/components/typography/p.tsx similarity index 100% rename from apps/client/src/libs/ui/typography/p.tsx rename to apps/client/src/libs/ui/components/typography/p.tsx diff --git a/apps/client/src/libs/ui/typography/small.tsx b/apps/client/src/libs/ui/components/typography/small.tsx similarity index 100% rename from apps/client/src/libs/ui/typography/small.tsx rename to apps/client/src/libs/ui/components/typography/small.tsx diff --git a/apps/client/src/routes/(auth)/_layout.tsx b/apps/client/src/routes/(auth)/_layout.tsx index 73438d48..92915df3 100644 --- a/apps/client/src/routes/(auth)/_layout.tsx +++ b/apps/client/src/routes/(auth)/_layout.tsx @@ -1,7 +1,7 @@ import { Outlet, createFileRoute, redirect } from "@tanstack/react-router" import { isAuthenticated } from "@/common/utils/auth" -import { Page } from "@/libs/ui/page" +import { Page } from "@/libs/ui/components/page" /** * Routes nested within the `/(auth)` pathless layout should only be accessible diff --git a/apps/client/src/routes/(auth)/index.tsx b/apps/client/src/routes/(auth)/index.tsx index f731d6a0..c8a7b61e 100644 --- a/apps/client/src/routes/(auth)/index.tsx +++ b/apps/client/src/routes/(auth)/index.tsx @@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router" import { CallToAction } from "@/features/landing/components/cta" import { Footer } from "@/features/landing/components/footer" import { Hero } from "@/features/landing/components/hero" -import { Container } from "@/libs/ui/container" +import { Container } from "@/libs/ui/components/container" export const Route = createFileRoute("/(auth)/")({ component: LandingPage diff --git a/apps/client/src/routes/(onboarding)/onboarding.tsx b/apps/client/src/routes/(onboarding)/onboarding.tsx index a4015377..f2c55078 100644 --- a/apps/client/src/routes/(onboarding)/onboarding.tsx +++ b/apps/client/src/routes/(onboarding)/onboarding.tsx @@ -4,7 +4,7 @@ import { createFileRoute } from "@tanstack/react-router" import { useEffectOnce } from "@/common/hooks/use-effect-once" import { onboardingMutationOptions } from "@/features/onboarding/api/options" import { OnboardingAnimation } from "@/features/onboarding/components/multi-step-loader" -import { Page } from "@/libs/ui/page" +import { Page } from "@/libs/ui/components/page" export const Route = createFileRoute("/(onboarding)/onboarding")({ component: OnboardingPage From 9a2de0dde3415733eaec8f52ddd35cbffdc68ae3 Mon Sep 17 00:00:00 2001 From: Ben Allenden Date: Sun, 12 Oct 2025 16:23:57 +0100 Subject: [PATCH 02/17] Move ui utils file to the ui directory --- apps/client/src/libs/ui/components/button.tsx | 2 +- apps/client/src/libs/ui/components/page.tsx | 2 +- apps/client/src/libs/ui/components/separator.tsx | 2 +- apps/client/src/libs/ui/components/typography/h1.tsx | 2 +- apps/client/src/libs/ui/components/typography/h3.tsx | 2 +- apps/client/src/libs/ui/components/typography/large.tsx | 2 +- apps/client/src/libs/ui/components/typography/lead.tsx | 2 +- apps/client/src/libs/ui/components/typography/muted.tsx | 2 +- apps/client/src/libs/ui/components/typography/p.tsx | 2 +- apps/client/src/libs/ui/components/typography/small.tsx | 2 +- apps/client/src/libs/{utils/index.ts => ui/utils.ts} | 0 11 files changed, 10 insertions(+), 10 deletions(-) rename apps/client/src/libs/{utils/index.ts => ui/utils.ts} (100%) diff --git a/apps/client/src/libs/ui/components/button.tsx b/apps/client/src/libs/ui/components/button.tsx index 1859cf57..82113dd9 100644 --- a/apps/client/src/libs/ui/components/button.tsx +++ b/apps/client/src/libs/ui/components/button.tsx @@ -3,7 +3,7 @@ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { type VariantProps, cva } from "class-variance-authority" -import { cn } from "@/libs/utils" +import { cn } from "@/libs/ui/utils" const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-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", diff --git a/apps/client/src/libs/ui/components/page.tsx b/apps/client/src/libs/ui/components/page.tsx index 63d6c3a8..4d05e9a7 100644 --- a/apps/client/src/libs/ui/components/page.tsx +++ b/apps/client/src/libs/ui/components/page.tsx @@ -1,6 +1,6 @@ import React from "react" -import { cn } from "@/libs/utils" +import { cn } from "@/libs/ui/utils" type Props = React.ComponentPropsWithoutRef<"div"> & { pad?: boolean; center?: boolean; landingGradient?: boolean } diff --git a/apps/client/src/libs/ui/components/separator.tsx b/apps/client/src/libs/ui/components/separator.tsx index 79671c2f..515ded6a 100644 --- a/apps/client/src/libs/ui/components/separator.tsx +++ b/apps/client/src/libs/ui/components/separator.tsx @@ -2,7 +2,7 @@ import * as React from "react" import * as SeparatorPrimitive from "@radix-ui/react-separator" -import { cn } from "@/libs/utils" +import { cn } from "@/libs/ui/utils" type SeparatorProps = React.ComponentProps diff --git a/apps/client/src/libs/ui/components/typography/h1.tsx b/apps/client/src/libs/ui/components/typography/h1.tsx index 4299aebe..aa6fa6f0 100644 --- a/apps/client/src/libs/ui/components/typography/h1.tsx +++ b/apps/client/src/libs/ui/components/typography/h1.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/libs/utils" +import { cn } from "@/libs/ui/utils" interface Props { className?: string diff --git a/apps/client/src/libs/ui/components/typography/h3.tsx b/apps/client/src/libs/ui/components/typography/h3.tsx index ce28fb62..97e31f86 100644 --- a/apps/client/src/libs/ui/components/typography/h3.tsx +++ b/apps/client/src/libs/ui/components/typography/h3.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/libs/utils" +import { cn } from "@/libs/ui/utils" interface Props { className?: string diff --git a/apps/client/src/libs/ui/components/typography/large.tsx b/apps/client/src/libs/ui/components/typography/large.tsx index b53dad97..da681111 100644 --- a/apps/client/src/libs/ui/components/typography/large.tsx +++ b/apps/client/src/libs/ui/components/typography/large.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/libs/utils" +import { cn } from "@/libs/ui/utils" interface Props { className?: string diff --git a/apps/client/src/libs/ui/components/typography/lead.tsx b/apps/client/src/libs/ui/components/typography/lead.tsx index fd977a5e..b3e00131 100644 --- a/apps/client/src/libs/ui/components/typography/lead.tsx +++ b/apps/client/src/libs/ui/components/typography/lead.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/libs/utils" +import { cn } from "@/libs/ui/utils" interface Props { className?: string diff --git a/apps/client/src/libs/ui/components/typography/muted.tsx b/apps/client/src/libs/ui/components/typography/muted.tsx index 74ef0892..ff985ee6 100644 --- a/apps/client/src/libs/ui/components/typography/muted.tsx +++ b/apps/client/src/libs/ui/components/typography/muted.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/libs/utils" +import { cn } from "@/libs/ui/utils" interface Props { className?: string diff --git a/apps/client/src/libs/ui/components/typography/p.tsx b/apps/client/src/libs/ui/components/typography/p.tsx index c5e402f1..12fed24a 100644 --- a/apps/client/src/libs/ui/components/typography/p.tsx +++ b/apps/client/src/libs/ui/components/typography/p.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/libs/utils" +import { cn } from "@/libs/ui/utils" interface Props { className?: string diff --git a/apps/client/src/libs/ui/components/typography/small.tsx b/apps/client/src/libs/ui/components/typography/small.tsx index 85aa8ebe..0cafd521 100644 --- a/apps/client/src/libs/ui/components/typography/small.tsx +++ b/apps/client/src/libs/ui/components/typography/small.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/libs/utils" +import { cn } from "@/libs/ui/utils" interface Props { className?: string diff --git a/apps/client/src/libs/utils/index.ts b/apps/client/src/libs/ui/utils.ts similarity index 100% rename from apps/client/src/libs/utils/index.ts rename to apps/client/src/libs/ui/utils.ts From 3d39ef7a2bdc8a3a36d079ef9fd5f7cf97a15d20 Mon Sep 17 00:00:00 2001 From: Ben Allenden Date: Sun, 12 Oct 2025 16:25:27 +0100 Subject: [PATCH 03/17] Update shadcn components.json aliases --- apps/client/components.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/client/components.json b/apps/client/components.json index fc9f93ae..b6232ec4 100644 --- a/apps/client/components.json +++ b/apps/client/components.json @@ -11,11 +11,10 @@ }, "iconLibrary": "lucide", "aliases": { - "components": "@/common/components", - "utils": "@/libs/utils", - "ui": "@/libs/ui", - "lib": "@/libs", - "hooks": "@/common/hooks" + "ui": "@/libs/ui/components", + "components": "@/libs/ui/components", + "hooks": "@/libs/ui/hooks", + "utils": "@/libs/ui/utils.ts" }, "registries": {}, "$schema": "https://ui.shadcn.com/schema.json" From e30c8284c375fa68852b310f5bda9eed2189873c Mon Sep 17 00:00:00 2001 From: Ben Allenden Date: Sun, 12 Oct 2025 16:27:48 +0100 Subject: [PATCH 04/17] Add initial app layout grid --- apps/client/src/routes/(app)/_layout.tsx | 22 +++++++++++++++++++++- apps/client/src/routes/(app)/home.tsx | 10 ++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/apps/client/src/routes/(app)/_layout.tsx b/apps/client/src/routes/(app)/_layout.tsx index 86bc9e96..fce51750 100644 --- a/apps/client/src/routes/(app)/_layout.tsx +++ b/apps/client/src/routes/(app)/_layout.tsx @@ -1,6 +1,8 @@ import { Outlet, createFileRoute, redirect } from "@tanstack/react-router" import { isAuthenticated, isOnboarded } from "@/common/utils/auth" +import { Container } from "@/libs/ui/components/container" +import { Page } from "@/libs/ui/components/page" /** * Routes nested within the `/(app)` pathless layout should only be accessible @@ -25,5 +27,23 @@ export const Route = createFileRoute("/(app)")({ }) } }, - component: () => + component: AppLayout }) + +function AppLayout() { + return ( + +
+
Sidebar Header
+
Sidebar Nav
+
Account Button
+
+ + + +
+ ) +} diff --git a/apps/client/src/routes/(app)/home.tsx b/apps/client/src/routes/(app)/home.tsx index 131c3c91..5683a9e2 100644 --- a/apps/client/src/routes/(app)/home.tsx +++ b/apps/client/src/routes/(app)/home.tsx @@ -20,9 +20,11 @@ function HomePage() { }, []) return ( -
- Hello "/(protected)/home"! - -
+ <> +
Optional Content Header
+
+ Hello "/(app)/home"! +
+ ) } From 70219a17d9a2f5ab647539304511c6c7a260cda3 Mon Sep 17 00:00:00 2001 From: Ben Allenden Date: Sun, 12 Oct 2025 16:28:19 +0100 Subject: [PATCH 05/17] Move router devtools out of the way --- apps/client/src/routes/__root.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/routes/__root.tsx b/apps/client/src/routes/__root.tsx index 5a6d8c52..e253e5b3 100644 --- a/apps/client/src/routes/__root.tsx +++ b/apps/client/src/routes/__root.tsx @@ -12,7 +12,7 @@ export const Route = createRootRouteWithContext()({ component: () => ( <> - + ) }) From 8a84d59768bc08ccb829121c98d427f1ec2cae70 Mon Sep 17 00:00:00 2001 From: Ben Allenden Date: Mon, 13 Oct 2025 13:48:01 +0100 Subject: [PATCH 06/17] Install shadcn sidebar --- apps/client/package-lock.json | 2 + apps/client/package.json | 2 + apps/client/src/libs/ui/components/input.tsx | 25 + apps/client/src/libs/ui/components/sheet.tsx | 145 ++++ .../client/src/libs/ui/components/sidebar.tsx | 718 ++++++++++++++++++ .../src/libs/ui/components/skeleton.tsx | 13 + .../client/src/libs/ui/components/tooltip.tsx | 71 ++ apps/client/src/libs/ui/hooks/use-mobile.ts | 29 + apps/client/src/main.css | 43 +- 9 files changed, 1026 insertions(+), 22 deletions(-) create mode 100644 apps/client/src/libs/ui/components/input.tsx create mode 100644 apps/client/src/libs/ui/components/sheet.tsx create mode 100644 apps/client/src/libs/ui/components/sidebar.tsx create mode 100644 apps/client/src/libs/ui/components/skeleton.tsx create mode 100644 apps/client/src/libs/ui/components/tooltip.tsx create mode 100644 apps/client/src/libs/ui/hooks/use-mobile.ts diff --git a/apps/client/package-lock.json b/apps/client/package-lock.json index fcfa52a1..5e4df75d 100644 --- a/apps/client/package-lock.json +++ b/apps/client/package-lock.json @@ -10,8 +10,10 @@ "dependencies": { "@clerk/clerk-react": "^5.35.3", "@clerk/types": "^4.90.0", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/vite": "^4.1.11", "@tanstack/react-form": "^1.15.0", "@tanstack/react-query": "^5.90.2", diff --git a/apps/client/package.json b/apps/client/package.json index f56068a4..c05e5255 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -17,8 +17,10 @@ "dependencies": { "@clerk/clerk-react": "^5.35.3", "@clerk/types": "^4.90.0", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/vite": "^4.1.11", "@tanstack/react-form": "^1.15.0", "@tanstack/react-query": "^5.90.2", diff --git a/apps/client/src/libs/ui/components/input.tsx b/apps/client/src/libs/ui/components/input.tsx new file mode 100644 index 00000000..de1acda9 --- /dev/null +++ b/apps/client/src/libs/ui/components/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "@/libs/ui/utils.ts" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/apps/client/src/libs/ui/components/sheet.tsx b/apps/client/src/libs/ui/components/sheet.tsx new file mode 100644 index 00000000..c43e3b6d --- /dev/null +++ b/apps/client/src/libs/ui/components/sheet.tsx @@ -0,0 +1,145 @@ +import * as React from "react" + +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@/libs/ui/utils.ts" + +function Sheet({ ...props }: React.ComponentProps) { + return ( + + ) +} + +function SheetTrigger({ ...props }: React.ComponentProps) { + return ( + + ) +} + +function SheetClose({ ...props }: React.ComponentProps) { + return ( + + ) +} + +function SheetPortal({ ...props }: React.ComponentProps) { + return ( + + ) +} + +function SheetOverlay({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function SheetContent({ + className, + children, + side = "right", + ...props +}: React.ComponentProps & { + side?: "top" | "right" | "bottom" | "left" +}) { + return ( + + + + {children} + + + Close + + + + ) +} + +function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetTitle({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function SheetDescription({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription } diff --git a/apps/client/src/libs/ui/components/sidebar.tsx b/apps/client/src/libs/ui/components/sidebar.tsx new file mode 100644 index 00000000..161fcf0e --- /dev/null +++ b/apps/client/src/libs/ui/components/sidebar.tsx @@ -0,0 +1,718 @@ +import React from "react" + +import { Slot } from "@radix-ui/react-slot" +import { type VariantProps, cva } from "class-variance-authority" +import { PanelLeftIcon } from "lucide-react" + +import { Button } from "@/libs/ui/components/button" +import { Input } from "@/libs/ui/components/input" +import { Separator } from "@/libs/ui/components/separator" +import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/libs/ui/components/sheet" +import { Skeleton } from "@/libs/ui/components/skeleton" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/libs/ui/components/tooltip" +import { useIsMobile } from "@/libs/ui/hooks/use-mobile" +import { cn } from "@/libs/ui/utils" + +const SIDEBAR_COOKIE_NAME = "sidebar_state" +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 +const SIDEBAR_WIDTH = "16rem" +const SIDEBAR_WIDTH_MOBILE = "18rem" +const SIDEBAR_WIDTH_ICON = "3rem" +const SIDEBAR_KEYBOARD_SHORTCUT = "b" + +type SidebarContextProps = { + state: "expanded" | "collapsed" + open: boolean + setOpen: (open: boolean) => void + openMobile: boolean + setOpenMobile: (open: boolean) => void + isMobile: boolean + toggleSidebar: () => void +} + +const SidebarContext = React.createContext(undefined) + +function useSidebar() { + const context = React.useContext(SidebarContext) + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider.") + } + + return context +} + +function SidebarProvider({ + defaultOpen = true, + open: openProperty, + onOpenChange: setOpenProperty, + className, + style, + children, + ...props +}: React.ComponentProps<"div"> & { + defaultOpen?: boolean + open?: boolean + onOpenChange?: (open: boolean) => void +}) { + const isMobile = useIsMobile() + const [openMobile, setOpenMobile] = React.useState(false) + + // This is the internal state of the sidebar. + // We use openProp and setOpenProp for control from outside the component. + const [_open, _setOpen] = React.useState(defaultOpen) + const open = openProperty ?? _open + const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === "function" ? value(open) : value + if (setOpenProperty) { + setOpenProperty(openState) + } else { + _setOpen(openState) + } + + // This sets the cookie to keep the sidebar state. + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` + }, + [setOpenProperty, open] + ) + + // Helper to toggle the sidebar. + const toggleSidebar = React.useCallback(() => { + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open) + }, [isMobile, setOpen, setOpenMobile]) + + // Adds a keyboard shortcut to toggle the sidebar. + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) { + event.preventDefault() + toggleSidebar() + } + } + + globalThis.addEventListener("keydown", handleKeyDown) + return () => globalThis.removeEventListener("keydown", handleKeyDown) + }, [toggleSidebar]) + + // We add a state so that we can do data-state="expanded" or "collapsed". + // This makes it easier to style the sidebar with Tailwind classes. + const state = open ? "expanded" : "collapsed" + + const contextValue = React.useMemo( + () => ({ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar + }), + [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] + ) + + return ( + + +
+ {children} +
+
+
+ ) +} + +function Sidebar({ + side = "left", + variant = "sidebar", + collapsible = "offcanvas", + className, + children, + ...props +}: React.ComponentProps<"div"> & { + side?: "left" | "right" + variant?: "sidebar" | "floating" | "inset" + collapsible?: "offcanvas" | "icon" | "none" +}) { + const { isMobile, state, openMobile, setOpenMobile } = useSidebar() + + if (collapsible === "none") { + return ( +
+ {children} +
+ ) + } + + if (isMobile) { + return ( + + + + Sidebar + Displays the mobile sidebar. + +
{children}
+
+
+ ) + } + + return ( +
+ {/* This is what handles the sidebar gap on desktop */} +
+ +
+ ) +} + +function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps) { + const { toggleSidebar } = useSidebar() + + return ( + + ) +} + +function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { + const { toggleSidebar } = useSidebar() + + return ( +