From 5ab2fdbcc6d81bf45280010596f5c7922e0dd5c1 Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Wed, 26 Nov 2025 15:47:15 +0100 Subject: [PATCH 01/19] feat: add dark mode a bit --- core/app/c/[communitySlug]/ContentLayout.tsx | 8 +- core/app/c/[communitySlug]/LoginSwitcher.tsx | 4 +- core/app/c/[communitySlug]/NavLink.tsx | 5 +- core/app/c/[communitySlug]/SideNav.tsx | 6 +- core/app/c/[communitySlug]/layout.tsx | 4 +- core/app/c/[communitySlug]/pubs/PubHeader.tsx | 30 -- .../c/[communitySlug]/pubs/PubSearchInput.tsx | 11 +- core/app/c/[communitySlug]/pubs/page.tsx | 11 +- .../stages/components/StageList.tsx | 6 +- core/app/c/[communitySlug]/stages/page.tsx | 8 +- core/app/components/pubs/CreatePubButton.tsx | 11 + core/app/components/pubs/PubCard/PubCard.tsx | 16 +- core/app/components/theme/DarkmodeToggle.tsx | 33 ++ core/app/components/theme/ThemeProvider.tsx | 10 + core/app/layout.tsx | 10 +- core/package.json | 6 +- packages/ui/components.json | 2 +- packages/ui/src/button.tsx | 65 ++-- packages/ui/src/dialog.tsx | 181 +++++---- packages/ui/src/dropdown-menu.tsx | 360 ++++++++++-------- packages/ui/src/popover.tsx | 53 ++- packages/ui/styles.css | 137 +++---- packages/ui/tailwind.config.cjs | 56 +-- pnpm-lock.yaml | 4 +- 24 files changed, 575 insertions(+), 462 deletions(-) delete mode 100644 core/app/c/[communitySlug]/pubs/PubHeader.tsx create mode 100644 core/app/components/theme/DarkmodeToggle.tsx create mode 100644 core/app/components/theme/ThemeProvider.tsx diff --git a/core/app/c/[communitySlug]/ContentLayout.tsx b/core/app/c/[communitySlug]/ContentLayout.tsx index 768546909a..9e3224b367 100644 --- a/core/app/c/[communitySlug]/ContentLayout.tsx +++ b/core/app/c/[communitySlug]/ContentLayout.tsx @@ -14,7 +14,7 @@ const Heading = ({ right?: ReactNode }) => { return ( -
+
{COLLAPSIBLE_TYPE === "icon" ? null : } {left}

@@ -42,7 +42,11 @@ export const ContentLayout = ({
-
{children}
+
+ {children} +
) diff --git a/core/app/c/[communitySlug]/LoginSwitcher.tsx b/core/app/c/[communitySlug]/LoginSwitcher.tsx index 70044dabf3..f1d2a3eea8 100644 --- a/core/app/c/[communitySlug]/LoginSwitcher.tsx +++ b/core/app/c/[communitySlug]/LoginSwitcher.tsx @@ -58,7 +58,7 @@ export default async function LoginSwitcher() { variant="ghost" size="sm" asChild - className="w-full justify-start gap-2 rounded-none hover:bg-sidebar-accent hover:text-sidebar-accent-foreground" + className="w-full justify-start gap-2 rounded-none" > diff --git a/core/app/c/[communitySlug]/NavLink.tsx b/core/app/c/[communitySlug]/NavLink.tsx index 6ab6832eba..cc6c551e89 100644 --- a/core/app/c/[communitySlug]/NavLink.tsx +++ b/core/app/c/[communitySlug]/NavLink.tsx @@ -40,7 +40,10 @@ export default function NavLink({ const isActive = regex.test(pathname) const content = ( - + {icon ? icon : null} {text} diff --git a/core/app/c/[communitySlug]/SideNav.tsx b/core/app/c/[communitySlug]/SideNav.tsx index 7861608d5b..2c397176ba 100644 --- a/core/app/c/[communitySlug]/SideNav.tsx +++ b/core/app/c/[communitySlug]/SideNav.tsx @@ -34,6 +34,7 @@ import { SidebarSeparator, } from "ui/sidebar" +import { DarkmodeToggle } from "~/app/components/theme/DarkmodeToggle" import { getLoginData } from "~/lib/authentication/loginData" import { userCan, userCanViewStagePage } from "~/lib/authorization/capabilities" import CommunitySwitcher from "./CommunitySwitcher" @@ -322,7 +323,7 @@ const LinkGroup = async ({ return ( - + {group.name} @@ -345,7 +346,7 @@ const SideNav: React.FC = async ({ community, availableCommunities }) => } return ( - + @@ -372,6 +373,7 @@ const SideNav: React.FC = async ({ community, availableCommunities }) => + diff --git a/core/app/c/[communitySlug]/layout.tsx b/core/app/c/[communitySlug]/layout.tsx index ec71cadd55..7037eb337c 100644 --- a/core/app/c/[communitySlug]/layout.tsx +++ b/core/app/c/[communitySlug]/layout.tsx @@ -70,7 +70,9 @@ export default async function MainLayout(props: Props) {
-
{children}
+
+ {children} +
= ({ communityId }) => { - return ( -
-

Pubs

-
- }> - - - -
-
- ) -} -export default PubHeader diff --git a/core/app/c/[communitySlug]/pubs/PubSearchInput.tsx b/core/app/c/[communitySlug]/pubs/PubSearchInput.tsx index 0935b273d4..cc9a455595 100644 --- a/core/app/c/[communitySlug]/pubs/PubSearchInput.tsx +++ b/core/app/c/[communitySlug]/pubs/PubSearchInput.tsx @@ -84,10 +84,10 @@ export const PubSearch = (props: PubSearchProps) => { return (
-
+
{ setQuery(e.target.value) }} placeholder="Search updates as you type..." - className={cn( - "bg-white pl-8 tracking-wide shadow-none", - inputValues && "pr-8" - )} + className={cn("pl-8 tracking-wide shadow-none", inputValues && "pr-8")} /> {inputValues?.query && ( )} {canCreateAnyPub && ( - }> - - + )}
} diff --git a/core/app/c/[communitySlug]/stages/components/StageList.tsx b/core/app/c/[communitySlug]/stages/components/StageList.tsx index 39a52500e8..04bef0f5b7 100644 --- a/core/app/c/[communitySlug]/stages/components/StageList.tsx +++ b/core/app/c/[communitySlug]/stages/components/StageList.tsx @@ -79,7 +79,7 @@ async function StageCard({ const communitySlug = await getCommunitySlug() return ( -
+
@@ -87,11 +87,11 @@ async function StageCard({ href={`/c/${communitySlug}/stages/${stage.id}`} className="group underline" > -

+

{stage.name}

-

+

{stage.pubsCount === 0 ? "No Pubs in this stage" : `${stage.pubsCount} ${stage.pubsCount === 1 ? "Pub" : "Pubs"}`} diff --git a/core/app/c/[communitySlug]/stages/page.tsx b/core/app/c/[communitySlug]/stages/page.tsx index 151f4bf221..f77ee2b0c9 100644 --- a/core/app/c/[communitySlug]/stages/page.tsx +++ b/core/app/c/[communitySlug]/stages/page.tsx @@ -6,7 +6,7 @@ import { notFound } from "next/navigation" import { Button } from "ui/button" import { FlagTriangleRightIcon } from "ui/icon" -import { CreatePubButton } from "~/app/components/pubs/CreatePubButton" +import { MainCreatePubButton } from "~/app/components/pubs/CreatePubButton" import { getPageLoginData } from "~/lib/authentication/loginData" import { userCanViewStagePage } from "~/lib/authorization/capabilities" import { findCommunityBySlug } from "~/lib/server/community" @@ -55,11 +55,7 @@ export default async function Page(props: Props) { - +

} > diff --git a/core/app/components/pubs/CreatePubButton.tsx b/core/app/components/pubs/CreatePubButton.tsx index 3199e48b50..4c623687e2 100644 --- a/core/app/components/pubs/CreatePubButton.tsx +++ b/core/app/components/pubs/CreatePubButton.tsx @@ -2,6 +2,8 @@ import type { CommunitiesId, PubsId, PubTypesId, StagesId } from "db/public" import type { ButtonProps } from "ui/button" import type { PubTypeWithForm } from "~/lib/authorization/capabilities" +import { Suspense } from "react" + import { Plus } from "ui/icon" import { getLoginData } from "~/lib/authentication/loginData" @@ -11,6 +13,7 @@ import { findCommunityBySlug } from "~/lib/server/community" import { getPubFields } from "~/lib/server/pubFields" import { ContextEditorContextProvider } from "../ContextEditor/ContextEditorContext" import { PathAwareDialog } from "../PathAwareDialog" +import { SkeletonButton } from "../skeletons/SkeletonButton" import { InitialCreatePubForm } from "./InitialCreatePubForm" type RelatedPubData = { @@ -136,3 +139,11 @@ export const CreatePubButton = async (props: Props) => { ) } + +export const MainCreatePubButton = (props: Props) => { + return ( + }> + + + ) +} diff --git a/core/app/components/pubs/PubCard/PubCard.tsx b/core/app/components/pubs/PubCard/PubCard.tsx index ba224acdb0..5906819b6a 100644 --- a/core/app/components/pubs/PubCard/PubCard.tsx +++ b/core/app/components/pubs/PubCard/PubCard.tsx @@ -87,7 +87,7 @@ export const PubCard = async ({ a:focus]:border-black has-[h3>a:focus]:ring-2 has-[h3>a:focus]:ring-gray-200" )} @@ -160,14 +160,18 @@ export const PubCard = async ({ ))}
)} - +
- - {formatDateAsMonthDayYear(new Date(pub.createdAt))} + +
- - {formatDateAsPossiblyDistance(new Date(pub.updatedAt))} + +
diff --git a/core/app/components/theme/DarkmodeToggle.tsx b/core/app/components/theme/DarkmodeToggle.tsx new file mode 100644 index 0000000000..a57203d2fa --- /dev/null +++ b/core/app/components/theme/DarkmodeToggle.tsx @@ -0,0 +1,33 @@ +"use client" + +import { Moon, Sun } from "lucide-react" +import { useTheme } from "next-themes" + +import { Button } from "ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "ui/dropdown-menu" + +export function DarkmodeToggle() { + const { setTheme } = useTheme() + + return ( + + + + + + setTheme("light")}>Light + setTheme("dark")}>Dark + setTheme("system")}>System + + + ) +} diff --git a/core/app/components/theme/ThemeProvider.tsx b/core/app/components/theme/ThemeProvider.tsx new file mode 100644 index 0000000000..0bdfc04886 --- /dev/null +++ b/core/app/components/theme/ThemeProvider.tsx @@ -0,0 +1,10 @@ +"use client" + +import { ThemeProvider as NextThemesProvider } from "next-themes" + +export function ThemeProvider({ + children, + ...props +}: React.ComponentProps) { + return {children} +} diff --git a/core/app/layout.tsx b/core/app/layout.tsx index e997eae50d..e423020e3e 100644 --- a/core/app/layout.tsx +++ b/core/app/layout.tsx @@ -12,6 +12,7 @@ import { getLoginData } from "~/lib/authentication/loginData" import { env } from "~/lib/env/env" import { ReactQueryProvider } from "./components/providers/QueryProvider" import { UserProvider } from "./components/providers/UserProvider" +import { ThemeProvider } from "./components/theme/ThemeProvider" import { RootToaster } from "./RootToaster" export const metadata = { @@ -37,7 +38,14 @@ export default async function RootLayout({ children }: { children: React.ReactNo - {children} + + {children} + diff --git a/core/package.json b/core/package.json index ab01187637..57c9d5c429 100644 --- a/core/package.json +++ b/core/package.json @@ -48,7 +48,10 @@ "storybook": "SKIP_VALIDATION=true PUBPUB_URL=http://localhost:6006 storybook dev -p 6006 --no-open", "build-storybook": "SKIP_VALIDATION=true storybook build" }, - "files": [".next", "public"], + "files": [ + ".next", + "public" + ], "prisma": { "__comment": "The #register-loader goes to the correct file based on the .imports setting below", "seed": "tsx --import #register-loader prisma/seed.ts" @@ -116,6 +119,7 @@ "mudder": "^2.1.1", "next": "catalog:", "next-connect": "^1.0.0", + "next-themes": "^0.4.6", "nodemailer": "^6.10.1", "nuqs": "catalog:", "openapi3-ts": "^4.5.0", diff --git a/packages/ui/components.json b/packages/ui/components.json index 6e805b1d62..7282991126 100644 --- a/packages/ui/components.json +++ b/packages/ui/components.json @@ -8,7 +8,7 @@ "config": "tailwind.config.cjs", "css": "./styles.css", "baseColor": "gray", - "cssVariables": false + "cssVariables": true }, "aliases": { "components": "src", diff --git a/packages/ui/src/button.tsx b/packages/ui/src/button.tsx index c0296863e2..87e539527e 100644 --- a/packages/ui/src/button.tsx +++ b/packages/ui/src/button.tsx @@ -1,32 +1,31 @@ -import type { VariantProps } from "class-variance-authority" - import * as React from "react" import { Slot } from "@radix-ui/react-slot" -import { cva } from "class-variance-authority" +import { cva, type VariantProps } from "class-variance-authority" import { cn } from "utils" const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-950 disabled:pointer-events-none disabled:opacity-50 dark:focus-visible:ring-gray-300 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + "inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", { variants: { variant: { - default: - "bg-gray-900 text-gray-50 shadow hover:bg-gray-900/90 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-50/90", + default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: - "bg-red-500 text-gray-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-gray-50 dark:hover:bg-red-900/90", + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40", outline: - "border border-gray-200 bg-white shadow-sm hover:bg-gray-100 hover:text-gray-900 dark:border-gray-800 dark:bg-gray-950 dark:hover:bg-gray-800 dark:hover:text-gray-50", - secondary: - "bg-gray-100 text-gray-900 shadow-sm hover:bg-gray-100/80 dark:bg-gray-800 dark:text-gray-50 dark:hover:bg-gray-800/80", - ghost: "hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-50", - link: "text-gray-900 underline-offset-4 hover:underline dark:text-gray-50", + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + create: "bg-emerald-500 text-white hover:bg-emerald-600", }, size: { - default: "h-9 px-4 py-2", - sm: "h-8 rounded-md px-3 text-xs", - lg: "h-10 rounded-md px-8", - icon: "h-9 w-9", + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-sm": "size-8", + "icon-lg": "size-10", }, }, defaultVariants: { @@ -36,25 +35,21 @@ const buttonVariants = cva( } ) -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 ( - - ) +export type ButtonProps = React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean } -) -Button.displayName = "Button" + +function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) { + const Comp = asChild ? Slot : "button" + + return ( + + ) +} export { Button, buttonVariants } diff --git a/packages/ui/src/dialog.tsx b/packages/ui/src/dialog.tsx index b9907949fa..3f91a3b2cd 100644 --- a/packages/ui/src/dialog.tsx +++ b/packages/ui/src/dialog.tsx @@ -2,103 +2,128 @@ import * as React from "react" import * as DialogPrimitive from "@radix-ui/react-dialog" -import { X } from "lucide-react" +import { XIcon } from "lucide-react" import { cn } from "utils" -const Dialog = DialogPrimitive.Root +function Dialog({ ...props }: React.ComponentProps) { + return +} -const DialogTrigger = DialogPrimitive.Trigger +function DialogTrigger({ ...props }: React.ComponentProps) { + return +} -const DialogPortal = DialogPrimitive.Portal +function DialogPortal({ ...props }: React.ComponentProps) { + return +} -const DialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName +function DialogClose({ ...props }: React.ComponentProps) { + return +} -const DialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - ) { + return ( + - {children} - - - Close - - - -)) -DialogContent.displayName = DialogPrimitive.Content.displayName + /> + ) +} -const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( -
-) -DialogHeader.displayName = "DialogHeader" +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} -const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( -
-) -DialogFooter.displayName = "DialogFooter" +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} -const DialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogTitle.displayName = DialogPrimitive.Title.displayName +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} -const DialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogDescription.displayName = DialogPrimitive.Description.displayName +function DialogTitle({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} export { Dialog, - DialogPortal, - DialogOverlay, - DialogTrigger, + DialogClose, DialogContent, - DialogHeader, + DialogDescription, DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, DialogTitle, - DialogDescription, + DialogTrigger, } diff --git a/packages/ui/src/dropdown-menu.tsx b/packages/ui/src/dropdown-menu.tsx index 14bd21a6a0..908a742de4 100644 --- a/packages/ui/src/dropdown-menu.tsx +++ b/packages/ui/src/dropdown-menu.tsx @@ -2,187 +2,227 @@ import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" -import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons" +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" import { cn } from "utils" -const DropdownMenu = DropdownMenuPrimitive.Root - -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger - -const DropdownMenuGroup = DropdownMenuPrimitive.Group - -const DropdownMenuPortal = DropdownMenuPrimitive.Portal - -const DropdownMenuSub = DropdownMenuPrimitive.Sub - -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup - -const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } ->(({ className, inset, children, ...props }, ref) => ( - - {children} - - -)) -DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName - -const DropdownMenuSubContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName - -const DropdownMenuContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - - ) { + return +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuContent({ + className, + 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 ( + - -)) -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName - -const DropdownMenuItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } ->(({ className, inset, ...props }, ref) => ( - -)) -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName - -const DropdownMenuCheckboxItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, checked, ...props }, ref) => ( - - - - - - - {children} - -)) -DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName - -const DropdownMenuRadioItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - - - - {children} - -)) -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName - -const DropdownMenuLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } ->(({ className, inset, ...props }, ref) => ( - -)) -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName - -const DropdownMenuSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName - -const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { return ( - + + + + + + + {children} + + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + 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 ( + ) } -DropdownMenuShortcut.displayName = "DropdownMenuShortcut" export { DropdownMenu, + DropdownMenuPortal, DropdownMenuTrigger, DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, DropdownMenuItem, DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, DropdownMenuRadioItem, - DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, DropdownMenuSub, - DropdownMenuSubContent, DropdownMenuSubTrigger, - DropdownMenuRadioGroup, + DropdownMenuSubContent, } diff --git a/packages/ui/src/popover.tsx b/packages/ui/src/popover.tsx index 94ad4c975d..f2f27d8470 100644 --- a/packages/ui/src/popover.tsx +++ b/packages/ui/src/popover.tsx @@ -5,27 +5,38 @@ import * as PopoverPrimitive from "@radix-ui/react-popover" import { cn } from "utils" -const Popover = PopoverPrimitive.Root +function Popover({ ...props }: React.ComponentProps) { + return +} -const PopoverTrigger = PopoverPrimitive.Trigger +function PopoverTrigger({ ...props }: React.ComponentProps) { + return +} -const PopoverContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( - - - -)) -PopoverContent.displayName = PopoverPrimitive.Content.displayName +function PopoverContent({ + className, + align = "center", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} -export { Popover, PopoverTrigger, PopoverContent } +function PopoverAnchor({ ...props }: React.ComponentProps) { + return +} + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/packages/ui/styles.css b/packages/ui/styles.css index 35a34c7e4d..5eac0d2e95 100644 --- a/packages/ui/styles.css +++ b/packages/ui/styles.css @@ -4,77 +4,80 @@ @layer base { :root { - --background: 0 0% 100%; - --foreground: 240 10% 3.9%; - --card: 0 0% 100%; - --card-foreground: 240 10% 3.9%; - --popover: 0 0% 100%; - --popover-foreground: 240 10% 3.9%; - --primary: 240 5.9% 10%; - --primary-foreground: 0 0% 98%; - --secondary: 240 4.8% 95.9%; - --secondary-foreground: 240 5.9% 10%; - --muted: 240 4.8% 95.9%; - --muted-foreground: 240 3.8% 46.1%; - --accent: 240 4.8% 95.9%; - --accent-foreground: 240 5.9% 10%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - --border: 240 5.9% 90%; - --input: 240 5.9% 90%; - --ring: 240 5.9% 10%; - --radius: 0.5rem; - - /* slate-50 */ - /* --sidebar-background: 210, 40%, 98%; */ - /* gray-100 */ - --sidebar-background: 210, 20%, 98%; - --sidebar-foreground: 240 5.3% 26.1%; - --sidebar-primary: 240 5.9% 10%; - --sidebar-primary-foreground: 0 0% 98%; - /* slate-150ish */ - /* --sidebar-accent: 210 40% 95.1%; */ - /* slate-100 */ - /* --sidebar-accent: 210, 40%, 96%; */ - /* gray-100 */ - --sidebar-accent: 220, 14%, 96%; - /* gray-200 */ - --sidebar-active: 220, 13%, 91%; - --sidebar-accent-foreground: 240 5.9% 10%; - --sidebar-border: 220 13% 91%; - --sidebar-ring: 217.2 91.2% 59.8%; + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); } .dark { - --background: 240 10% 3.9%; - --foreground: 0 0% 98%; - --card: 240 10% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 240 10% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 240 5.9% 10%; - --secondary: 240 3.7% 15.9%; - --secondary-foreground: 0 0% 98%; - --muted: 240 3.7% 15.9%; - --muted-foreground: 240 5% 64.9%; - --accent: 240 3.7% 15.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 240 3.7% 15.9%; - --input: 240 3.7% 15.9%; - --ring: 240 4.9% 83.9%; - --radius: 0.5rem; + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); + } - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--border, currentColor); } } diff --git a/packages/ui/tailwind.config.cjs b/packages/ui/tailwind.config.cjs index 4eac1c7675..95a3e1c858 100644 --- a/packages/ui/tailwind.config.cjs +++ b/packages/ui/tailwind.config.cjs @@ -18,35 +18,35 @@ module.exports = { }, extend: { colors: { - destructive: "hsl(var(--destructive))", - "destructive-foreground": "hsl(var(--destructive-foreground))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - card: "hsl(var(--card))", - "card-foreground": "hsl(var(--card-foreground))", - primary: "hsl(var(--primary))", - "primary-foreground": "hsl(var(--primary-foreground))", - popover: "hsl(var(--popover))", - "popover-foreground": "hsl(var(--popover-foreground))", - secondary: "hsl(var(--secondary))", - "secondary-foreground": "hsl(var(--secondary-foreground))", - muted: "hsl(var(--muted))", - "muted-foreground": "hsl(var(--muted-foreground))", - accent: "hsl(var(--accent))", - "accent-foreground": "hsl(var(--accent-foreground))", - input: "hsl(var(--input))", - border: "hsl(var(--border))", - ring: "hsl(var(--ring))", + destructive: "var(--destructive)", + "destructive-foreground": "var(--destructive-foreground)", + background: "var(--background)", + foreground: "var(--foreground)", + card: "var(--card)", + "card-foreground": "var(--card-foreground)", + primary: "var(--primary)", + "primary-foreground": "var(--primary-foreground)", + popover: "var(--popover)", + "popover-foreground": "var(--popover-foreground)", + secondary: "var(--secondary)", + "secondary-foreground": "var(--secondary-foreground)", + muted: "var(--muted)", + "muted-foreground": "var(--muted-foreground)", + accent: "var(--accent)", + "accent-foreground": "var(--accent-foreground)", + input: "var(--input)", + border: "var(--border)", + ring: "var(--ring)", sidebar: { - DEFAULT: "hsl(var(--sidebar-background))", - foreground: "hsl(var(--sidebar-foreground))", - primary: "hsl(var(--sidebar-primary))", - "primary-foreground": "hsl(var(--sidebar-primary-foreground))", - accent: "hsl(var(--sidebar-accent))", - "accent-foreground": "hsl(var(--sidebar-accent-foreground))", - border: "hsl(var(--sidebar-border))", - ring: "hsl(var(--sidebar-ring))", - active: "hsl(var(--sidebar-active))", + DEFAULT: "var(--sidebar)", + foreground: "var(--sidebar-foreground)", + primary: "var(--sidebar-primary)", + "primary-foreground": "var(--sidebar-primary-foreground)", + accent: "var(--sidebar-accent)", + "accent-foreground": "var(--sidebar-accent-foreground)", + border: "var(--sidebar-border)", + ring: "var(--sidebar-ring)", + active: "var(--sidebar-active)", }, }, keyframes: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9877f34a24..e7831c2712 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -368,6 +368,9 @@ importers: next-connect: specifier: ^1.0.0 version: 1.0.0 + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) nodemailer: specifier: ^6.10.1 version: 6.10.1 @@ -10356,7 +10359,6 @@ packages: resolution: {integrity: sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==} engines: {node: '>=8.17.0'} hasBin: true - bundledDependencies: [] jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} From 0ba0491838e803fdf3683726582acaa0330924bb Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Wed, 26 Nov 2025 15:57:59 +0100 Subject: [PATCH 02/19] dev: upgrade ui to tailwind v4 --- packages/ui/package.json | 3 +- packages/ui/postcss.config.cjs | 3 +- packages/ui/src/auto-form/fields/file.tsx | 2 +- packages/ui/src/autocomplete.tsx | 4 +- packages/ui/src/badge.tsx | 2 +- packages/ui/src/breadcrumb.tsx | 2 +- packages/ui/src/button-group.tsx | 6 +- packages/ui/src/button.tsx | 4 +- packages/ui/src/calendar.tsx | 2 +- packages/ui/src/card.tsx | 2 +- packages/ui/src/checkbox.tsx | 2 +- packages/ui/src/command.tsx | 8 +- packages/ui/src/context-menu.tsx | 12 +-- .../customRenderers/confidence/confidence.tsx | 6 +- .../components/data-table-pagination.tsx | 2 +- packages/ui/src/dropdown-menu.tsx | 14 +-- packages/ui/src/editors/LexicalEditor.tsx | 2 +- .../src/editors/PlainTextWithTokensEditor.tsx | 2 +- packages/ui/src/field.tsx | 18 ++-- packages/ui/src/hover-card.tsx | 2 +- packages/ui/src/input.tsx | 2 +- packages/ui/src/item.tsx | 8 +- packages/ui/src/kbd.tsx | 2 +- packages/ui/src/menubar.tsx | 16 ++-- packages/ui/src/multi-select.tsx | 4 +- packages/ui/src/multiblock.tsx | 2 +- packages/ui/src/navigation-menu.tsx | 8 +- packages/ui/src/pagination.tsx | 2 +- .../pubFieldSelect/PubFieldSelect.tsx | 4 +- packages/ui/src/radio-group.tsx | 4 +- packages/ui/src/select.tsx | 8 +- packages/ui/src/separator.tsx | 2 +- packages/ui/src/sheet.tsx | 2 +- packages/ui/src/show-more.tsx | 2 +- packages/ui/src/sidebar.tsx | 44 ++++----- packages/ui/src/slider.tsx | 2 +- packages/ui/src/switch.tsx | 2 +- packages/ui/src/table.tsx | 2 +- packages/ui/src/tabs.tsx | 4 +- packages/ui/src/textarea.tsx | 2 +- packages/ui/src/toast.tsx | 10 +- packages/ui/src/toggle.tsx | 4 +- packages/ui/styles.css | 90 +++++++++++++++++- packages/ui/tailwind.config.cjs | 95 ------------------- 44 files changed, 204 insertions(+), 215 deletions(-) delete mode 100644 packages/ui/tailwind.config.cjs diff --git a/packages/ui/package.json b/packages/ui/package.json index b665ee7be8..163b52d9a9 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -216,10 +216,11 @@ "zod": "catalog:" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.17", "@types/react": "catalog:react19", "postcss": "catalog:", "react": "catalog:react19", - "tailwindcss": "catalog:", + "tailwindcss": "4.1.17", "tsconfig": "workspace:*", "typescript": "catalog:", "zod": "catalog:" diff --git a/packages/ui/postcss.config.cjs b/packages/ui/postcss.config.cjs index 3b2e926f83..9597f513ff 100644 --- a/packages/ui/postcss.config.cjs +++ b/packages/ui/postcss.config.cjs @@ -3,7 +3,6 @@ module.exports = { plugins: { - tailwindcss: {}, - autoprefixer: {}, + '@tailwindcss/postcss': {}, }, } diff --git a/packages/ui/src/auto-form/fields/file.tsx b/packages/ui/src/auto-form/fields/file.tsx index 5165955935..d90af4e651 100644 --- a/packages/ui/src/auto-form/fields/file.tsx +++ b/packages/ui/src/auto-form/fields/file.tsx @@ -60,7 +60,7 @@ export default function AutoFormFile({ )} {file && ( -
+

{fileName}