From 53ef847b2ddebee7a68aebcdc058f1106919c44b Mon Sep 17 00:00:00 2001 From: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com> Date: Sat, 25 Jul 2026 07:48:49 +0530 Subject: [PATCH] fix(web): normalize compact UI geometry --- .../src/components/ProjectScriptsControl.tsx | 15 ++++++--- apps/web/src/components/SidebarV2.tsx | 20 ++++++------ .../clerk/T3ConnectSidebarSignIn.tsx | 2 +- .../settings/SettingsSidebarNav.tsx | 10 +++--- .../src/components/sidebar/SidebarChrome.tsx | 2 +- apps/web/src/components/ui/button.test.tsx | 31 +++++++++++++++++++ apps/web/src/components/ui/button.tsx | 10 +++--- apps/web/src/components/ui/command.tsx | 9 ++++-- apps/web/src/components/ui/sidebar.test.tsx | 3 ++ apps/web/src/components/ui/sidebar.tsx | 2 +- apps/web/src/index.css | 12 +++++++ 11 files changed, 87 insertions(+), 29 deletions(-) create mode 100644 apps/web/src/components/ui/button.test.tsx diff --git a/apps/web/src/components/ProjectScriptsControl.tsx b/apps/web/src/components/ProjectScriptsControl.tsx index 79e43f4c042..26377725952 100644 --- a/apps/web/src/components/ProjectScriptsControl.tsx +++ b/apps/web/src/components/ProjectScriptsControl.tsx @@ -334,15 +334,16 @@ export default function ProjectScriptsControl({ onRunScript(primaryScript)} /> } > - + {primaryScript.name} @@ -428,11 +429,17 @@ export default function ProjectScriptsControl({ + , + ); + + expect(html).toContain("rounded-[var(--control-radius)]"); + expect(html).toContain("[--control-icon-color:var(--muted-foreground)]"); + expect(html).toContain("text-[var(--control-icon-color)]"); + expect(html).not.toContain("opacity-80"); + }); + + it("keeps compact icon buttons square at every breakpoint", () => { + const html = renderToStaticMarkup( + , + ); + + expect(html).toContain("size-7"); + expect(html).toContain("sm:size-6"); + }); +}); diff --git a/apps/web/src/components/ui/button.tsx b/apps/web/src/components/ui/button.tsx index f6d09f7799a..778574ba010 100644 --- a/apps/web/src/components/ui/button.tsx +++ b/apps/web/src/components/ui/button.tsx @@ -8,7 +8,7 @@ import type * as React from "react"; import { cn } from "~/lib/utils"; const buttonVariants = cva( - "[&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", + "[--control-icon-color:currentColor] [&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-[var(--control-radius)] border font-medium text-base outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--control-radius)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 sm:text-sm [&_svg:not([class*='text-'])]:text-[var(--control-icon-color)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", { defaultVariants: { size: "default", @@ -23,11 +23,11 @@ const buttonVariants = cva( "icon-xl": "size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5", "icon-xs": - "size-7 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5", + "size-7 sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5", lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9", sm: "h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:h-7", xl: "h-11 px-[calc(--spacing(4)-1px)] text-lg sm:h-10 sm:text-base [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5", - xs: "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-sm before:rounded-[calc(var(--radius-md)-1px)] sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5", + xs: "h-7 gap-1 px-[calc(--spacing(2)-1px)] text-sm sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5", }, variant: { default: @@ -37,10 +37,10 @@ const buttonVariants = cva( "destructive-outline": "border-input bg-popover not-dark:bg-clip-padding text-destructive-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:border-destructive/32 [:hover,[data-pressed]]:bg-destructive/4", ghost: - "border-transparent text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent [&_svg:not([class*='text-'])]:text-muted-foreground", + "[--control-icon-color:var(--muted-foreground)] border-transparent text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent", link: "border-transparent underline-offset-4 [:hover,[data-pressed]]:underline", outline: - "border-input bg-popover not-dark:bg-clip-padding text-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64 [&_svg:not([class*='text-'])]:text-muted-foreground", + "[--control-icon-color:var(--muted-foreground)] border-input bg-popover not-dark:bg-clip-padding text-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64", secondary: "border-transparent bg-secondary text-secondary-foreground [:active,[data-pressed]]:bg-secondary/80 [:hover,[data-pressed]]:bg-secondary/90", }, diff --git a/apps/web/src/components/ui/command.tsx b/apps/web/src/components/ui/command.tsx index deb2fbfe8f9..55974159725 100644 --- a/apps/web/src/components/ui/command.tsx +++ b/apps/web/src/components/ui/command.tsx @@ -103,7 +103,12 @@ function CommandInput({ wrapperClassName?: string | undefined; }) { return ( -
+
) { return (
{ expect(html).toContain('data-slot="sidebar-menu-button"'); expect(html).toContain("cursor-pointer"); + expect(html).toContain("rounded-[var(--control-radius)]"); + expect(html).toContain("gap-[var(--sidebar-control-gap)]"); + expect(html).toContain("p-[var(--sidebar-content-inset)]"); }); it("lets project drag handles override the default pointer cursor", () => { diff --git a/apps/web/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx index af90a7cc65b..0f07b76a6bf 100644 --- a/apps/web/src/components/ui/sidebar.tsx +++ b/apps/web/src/components/ui/sidebar.tsx @@ -783,7 +783,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) { } const sidebarMenuButtonVariants = cva( - "peer/menu-button flex w-full cursor-pointer items-center gap-2 overflow-hidden rounded-lg p-2 text-left text-sm outline-hidden ring-ring transition-[width,height,padding] hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-row-active active:text-sidebar-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pe-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-row-selected data-[active=true]:font-medium data-[active=true]:text-sidebar-foreground data-[state=open]:hover:bg-sidebar-row-hover data-[state=open]:hover:text-sidebar-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0", + "peer/menu-button flex w-full cursor-pointer items-center gap-[var(--sidebar-control-gap)] overflow-hidden rounded-[var(--control-radius)] p-[var(--sidebar-content-inset)] text-left text-sm outline-hidden ring-ring transition-[width,height,padding] hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-row-active active:text-sidebar-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pe-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-row-selected data-[active=true]:font-medium data-[active=true]:text-sidebar-foreground data-[state=open]:hover:bg-sidebar-row-hover data-[state=open]:hover:text-sidebar-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-[var(--sidebar-content-inset)]! [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0", { defaultVariants: { size: "default", diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 29dd99b8e6d..a856ab1502a 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -77,6 +77,18 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil :root { --app-scrollbar-width: 6px; + /* + * Compact UI geometry. Keep these values semantic so sidebar, palette, + * tooltip, and toolbar controls cannot quietly drift apart. + */ + --control-radius: 0.5rem; + --sidebar-content-inset: 0.5rem; + --sidebar-control-gap: 0.5rem; + --sidebar-icon-color: color-mix(in srgb, var(--sidebar-muted-foreground) 60%, var(--sidebar)); + --sidebar-row-content-inset: 0.5rem; + --command-shell-inset: 0.5rem; + --command-content-inset: 1rem; + --floating-content-inset: 0.75rem; --glass-blur: 12px; --glass-opacity: 80%; --glass-saturation: 1.14;