feat: Implement responsive design for the experiment designer and enhance general UI components with hover effects and shadows.

This commit is contained in:
2026-02-02 12:51:53 -05:00
parent 7fd0d97a67
commit 89c44efcf7
8 changed files with 245 additions and 178 deletions

View File

@@ -43,7 +43,7 @@ function ProfileContent({ user }: { user: ProfileUser }) {
{/* Profile Information */} {/* Profile Information */}
<div className="space-y-6 lg:col-span-2"> <div className="space-y-6 lg:col-span-2">
{/* Basic Information */} {/* Basic Information */}
<Card> <Card className="hover:shadow-md transition-shadow duration-200">
<CardHeader> <CardHeader>
<CardTitle>Basic Information</CardTitle> <CardTitle>Basic Information</CardTitle>
<CardDescription> <CardDescription>
@@ -63,7 +63,7 @@ function ProfileContent({ user }: { user: ProfileUser }) {
</Card> </Card>
{/* Password Change */} {/* Password Change */}
<Card> <Card className="hover:shadow-md transition-shadow duration-200">
<CardHeader> <CardHeader>
<CardTitle>Password</CardTitle> <CardTitle>Password</CardTitle>
<CardDescription>Change your account password</CardDescription> <CardDescription>Change your account password</CardDescription>
@@ -116,7 +116,7 @@ function ProfileContent({ user }: { user: ProfileUser }) {
{/* Sidebar */} {/* Sidebar */}
<div className="space-y-6"> <div className="space-y-6">
{/* User Summary */} {/* User Summary */}
<Card> <Card className="hover:shadow-md transition-shadow duration-200">
<CardHeader> <CardHeader>
<CardTitle>Account Summary</CardTitle> <CardTitle>Account Summary</CardTitle>
</CardHeader> </CardHeader>

View File

@@ -56,6 +56,8 @@ export function DesignerPageClient({
]); ]);
return ( return (
<DesignerRoot experimentId={experiment.id} initialDesign={initialDesign} /> <div className="h-[calc(100vh-4rem-2rem)] w-full overflow-hidden border rounded-lg bg-background">
<DesignerRoot experimentId={experiment.id} initialDesign={initialDesign} />
</div>
); );
} }

View File

@@ -185,7 +185,7 @@ export default function DashboardPage() {
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
{scheduledTrials.map((trial) => ( {scheduledTrials.map((trial) => (
<div key={trial.id} className="flex items-center justify-between rounded-lg border p-3 hover:bg-muted/50 transition-colors"> <div key={trial.id} className="flex items-center justify-between rounded-lg border p-3 bg-muted/10 hover:bg-muted/50 hover:shadow-sm transition-all duration-200">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400"> <div className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400">
<Calendar className="h-5 w-5" /> <Calendar className="h-5 w-5" />
@@ -302,7 +302,7 @@ function StatsCard({
trend?: string; trend?: string;
}) { }) {
return ( return (
<Card className="border-muted/40 shadow-sm"> <Card className="border-muted/40 shadow-sm hover:shadow-md transition-all duration-200 hover:border-primary/20">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">{title}</CardTitle> <CardTitle className="text-sm font-medium">{title}</CardTitle>
<Icon className="h-4 w-4 text-muted-foreground" /> <Icon className="h-4 w-4 text-muted-foreground" />

View File

@@ -2,7 +2,9 @@
import * as React from "react"; import * as React from "react";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
import { Sheet, SheetContent, SheetTrigger } from "~/components/ui/sheet";
import { Button } from "~/components/ui/button";
import { PanelLeft, Settings2 } from "lucide-react";
type Edge = "left" | "right"; type Edge = "left" | "right";
export interface PanelsContainerProps { export interface PanelsContainerProps {
@@ -261,81 +263,128 @@ export function PanelsContainer({
return ( return (
<div <>
ref={rootRef} {/* Mobile Layout (Flex + Sheets) */}
aria-label={ariaLabel} <div className={cn("flex flex-col h-full w-full md:hidden", className)}>
style={styleVars} {/* Mobile Header/Toolbar for access to panels */}
className={cn( <div className="flex items-center justify-between border-b px-4 py-2 bg-background">
"relative grid h-full min-h-0 w-full overflow-hidden select-none", <div className="flex items-center gap-2">
gridCols, {hasLeft && (
className, <Sheet>
)} <SheetTrigger asChild>
> <Button variant="outline" size="icon" className="h-8 w-8">
{hasLeft && ( <PanelLeft className="h-4 w-4" />
<Panel </Button>
panelClassName={panelClassName} </SheetTrigger>
contentClassName={contentClassName} <SheetContent side="left" className="w-[85vw] p-0 sm:max-w-md">
> <div className="h-full overflow-hidden">
{left} {left}
</Panel> </div>
)} </SheetContent>
</Sheet>
)}
<span className="text-sm font-medium">Designer</span>
</div>
{hasCenter && ( {hasRight && (
<Panel <Sheet>
className={centerDividers} <SheetTrigger asChild>
panelClassName={panelClassName} <Button variant="outline" size="icon" className="h-8 w-8">
contentClassName={contentClassName} <Settings2 className="h-4 w-4" />
> </Button>
</SheetTrigger>
<SheetContent side="right" className="w-[85vw] p-0 sm:max-w-md">
<div className="h-full overflow-hidden">
{right}
</div>
</SheetContent>
</Sheet>
)}
</div>
{/* Main Content (Center) */}
<div className="flex-1 min-h-0 overflow-hidden relative">
{center} {center}
</Panel> </div>
)} </div>
{hasRight && ( {/* Desktop Layout (Grid) */}
<Panel <div
panelClassName={panelClassName} ref={rootRef}
contentClassName={contentClassName} aria-label={ariaLabel}
> style={styleVars}
{right} className={cn(
</Panel> "relative hidden md:grid h-full min-h-0 w-full overflow-hidden select-none",
)} gridCols,
className,
)}
>
{hasLeft && (
<Panel
panelClassName={panelClassName}
contentClassName={contentClassName}
>
{left}
</Panel>
)}
{/* Resize handles (only render where applicable) */} {hasCenter && (
{hasCenter && hasLeft && ( <Panel
<button className={centerDividers}
type="button" panelClassName={panelClassName}
role="separator" contentClassName={contentClassName}
aria-label="Resize left panel" >
aria-orientation="vertical" {center}
onPointerDown={startDrag("left")} </Panel>
onKeyDown={onKeyResize("left")} )}
className={cn(
"absolute inset-y-0 z-10 w-1 cursor-col-resize outline-none",
"focus-visible:ring-ring focus-visible:ring-2",
)}
// Position at the boundary between left and center
style={{ left: "var(--col-left)", transform: "translateX(-0.5px)" }}
tabIndex={0}
/>
)}
{hasCenter && hasRight && ( {hasRight && (
<button <Panel
type="button" panelClassName={panelClassName}
role="separator" contentClassName={contentClassName}
aria-label="Resize right panel" >
aria-orientation="vertical" {right}
onPointerDown={startDrag("right")} </Panel>
onKeyDown={onKeyResize("right")} )}
className={cn(
"absolute inset-y-0 z-10 w-1 cursor-col-resize outline-none", {/* Resize handles (only render where applicable) */}
"focus-visible:ring-ring focus-visible:ring-2", {hasCenter && hasLeft && (
)} <button
// Position at the boundary between center and right (offset from the right) type="button"
style={{ right: "var(--col-right)", transform: "translateX(0.5px)" }} role="separator"
tabIndex={0} aria-label="Resize left panel"
/> aria-orientation="vertical"
)} onPointerDown={startDrag("left")}
</div> onKeyDown={onKeyResize("left")}
className={cn(
"absolute inset-y-0 z-10 w-1 cursor-col-resize outline-none",
"focus-visible:ring-ring focus-visible:ring-2",
)}
// Position at the boundary between left and center
style={{ left: "var(--col-left)", transform: "translateX(-0.5px)" }}
tabIndex={0}
/>
)}
{hasCenter && hasRight && (
<button
type="button"
role="separator"
aria-label="Resize right panel"
aria-orientation="vertical"
onPointerDown={startDrag("right")}
onKeyDown={onKeyResize("right")}
className={cn(
"absolute inset-y-0 z-10 w-1 cursor-col-resize outline-none",
"focus-visible:ring-ring focus-visible:ring-2",
)}
// Position at the boundary between center and right (offset from the right)
style={{ right: "var(--col-right)", transform: "translateX(0.5px)" }}
tabIndex={0}
/>
)}
</div>
</>
); );
} }

View File

@@ -211,7 +211,7 @@ export function DataTable<TData, TValue>({
</DropdownMenu> </DropdownMenu>
</div> </div>
</div> </div>
<div className="min-w-0 overflow-hidden rounded-md border"> <div className="min-w-0 overflow-hidden rounded-md border shadow-sm bg-card">
<div className="min-w-0 overflow-x-auto overflow-y-hidden"> <div className="min-w-0 overflow-x-auto overflow-y-hidden">
<Table className="min-w-[600px]"> <Table className="min-w-[600px]">
<TableHeader> <TableHeader>

View File

@@ -20,7 +20,7 @@ export function Logo({
}: LogoProps) { }: LogoProps) {
return ( return (
<div className={cn("flex items-center gap-2", className)}> <div className={cn("flex items-center gap-2", className)}>
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square items-center justify-center rounded-lg p-1"> <div className="bg-primary text-primary-foreground flex aspect-square items-center justify-center rounded-lg p-1">
<Bot className={iconSizes[iconSize]} /> <Bot className={iconSizes[iconSize]} />
</div> </div>
{showText && ( {showText && (

View File

@@ -18,7 +18,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
return ( return (
<thead <thead
data-slot="table-header" data-slot="table-header"
className={cn("[&_tr]:border-b", className)} className={cn("[&_tr]:border-b bg-secondary/30", className)}
{...props} {...props}
/> />
); );

View File

@@ -72,39 +72,48 @@
} }
:root { :root {
--radius: 0rem; /* Light Mode (Inverted: White BG, gray Cards) */
--background: oklch(0.98 0.005 60); --radius: 0.5rem;
--foreground: oklch(0.15 0.005 240); --background: hsl(0 0% 100%);
--card: oklch(0.995 0.001 60); /* Pure White Background */
--card-foreground: oklch(0.15 0.005 240); --foreground: hsl(240 10% 3.9%);
--popover: oklch(0.99 0.002 60); --card: hsl(240 4.8% 95.9%);
--popover-foreground: oklch(0.15 0.005 240); /* Light Gray Card */
--primary: oklch(0.55 0.08 240); --card-foreground: hsl(240 10% 3.9%);
--primary-foreground: oklch(0.98 0.01 250); --popover: hsl(0 0% 100%);
--secondary: oklch(0.94 0.01 240); --popover-foreground: hsl(240 10% 3.9%);
--secondary-foreground: oklch(0.25 0.02 240); --primary: hsl(221.2 83.2% 53.3%);
--muted: oklch(0.95 0.008 240); /* Indigo-600 */
--muted-foreground: oklch(0.52 0.015 240); --primary-foreground: hsl(210 40% 98%);
--accent: oklch(0.92 0.015 240); --secondary: hsl(210 40% 96.1%);
--accent-foreground: oklch(0.2 0.02 240); --secondary-foreground: hsl(222.2 47.4% 11.2%);
--destructive: oklch(0.583 0.2387 28.4765); --muted: hsl(210 40% 96.1%);
--border: oklch(0.9 0.008 240); --muted-foreground: hsl(215.4 16.3% 46.9%);
--input: oklch(0.96 0.005 240); --accent: hsl(210 40% 96.1%);
--ring: oklch(0.55 0.08 240); --accent-foreground: hsl(222.2 47.4% 11.2%);
--chart-1: oklch(0.55 0.08 240); --destructive: hsl(0 84.2% 60.2%);
--chart-2: oklch(0.6 0.1 200); --destructive-foreground: hsl(210 40% 98%);
--chart-3: oklch(0.65 0.12 160); --border: hsl(214.3 31.8% 91.4%);
--chart-4: oklch(0.7 0.1 120); --input: hsl(214.3 31.8% 91.4%);
--chart-5: oklch(0.6 0.15 80); --ring: hsl(221.2 83.2% 53.3%);
--sidebar: oklch(0.97 0.015 250); --chart-1: hsl(221.2 83.2% 53.3%);
--sidebar-foreground: oklch(0.2 0.03 240); --chart-2: hsl(173 58% 39%);
--sidebar-primary: oklch(0.3 0.08 240); --chart-3: hsl(197 37% 24%);
--sidebar-primary-foreground: oklch(0.98 0.01 250); --chart-4: hsl(43 74% 66%);
--sidebar-accent: oklch(0.92 0.025 245); --chart-5: hsl(27 87% 67%);
--sidebar-accent-foreground: oklch(0.25 0.05 240); --sidebar: hsl(240 4.8% 95.9%);
--sidebar-border: oklch(0.85 0.03 245); /* Zinc-100: Distinct contrast against white BG */
--sidebar-ring: oklch(0.6 0.05 240); --sidebar-foreground: hsl(240 10% 3.9%);
--destructive-foreground: oklch(0.9702 0 0); /* Dark Text */
--sidebar-primary: hsl(221.2 83.2% 53.3%);
/* Indigo Accent */
--sidebar-primary-foreground: hsl(0 0% 98%);
--sidebar-accent: hsl(240 5.9% 90%);
/* Zinc-200: Slightly darker for hover */
--sidebar-accent-foreground: hsl(240 5.9% 10%);
--sidebar-border: hsl(240 5.9% 90%);
/* Zinc-200 Border */
--sidebar-ring: hsl(217.2 91.2% 59.8%);
--shadow-color: hsl(0 0% 0%); --shadow-color: hsl(0 0% 0%);
--shadow-opacity: 0; --shadow-opacity: 0;
@@ -131,75 +140,81 @@
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--background: oklch(0.12 0.008 250); /* Dark Mode (Inverted: Lighter BG, Black Cards) */
--foreground: oklch(0.95 0.005 250); --background: hsl(240 3.7% 15.9%);
--card: oklch(0.18 0.008 250); /* Lighter Dark BG */
--card-foreground: oklch(0.95 0.005 250); --foreground: hsl(0 0% 98%);
--popover: oklch(0.2 0.01 250); --card: hsl(240 10% 3.9%);
--popover-foreground: oklch(0.95 0.005 250); /* Deep Black Card */
--primary: oklch(0.65 0.1 240); --card-foreground: hsl(0 0% 98%);
--primary-foreground: oklch(0.08 0.02 250); --popover: hsl(240 10% 3.9%);
--secondary: oklch(0.25 0.015 245); --popover-foreground: hsl(0 0% 98%);
--secondary-foreground: oklch(0.92 0.008 250); --primary: hsl(0 0% 98%);
--muted: oklch(0.22 0.01 250); --primary-foreground: hsl(240 5.9% 10%);
--muted-foreground: oklch(0.65 0.02 245); --secondary: hsl(240 3.7% 15.9%);
--accent: oklch(0.35 0.025 245); --secondary-foreground: hsl(0 0% 98%);
--accent-foreground: oklch(0.92 0.008 250); --muted: hsl(240 3.7% 15.9%);
--destructive: oklch(0.7022 0.1892 22.2279); --muted-foreground: hsl(240 5% 64.9%);
--border: oklch(0.3 0.015 250); --accent: hsl(240 3.7% 15.9%);
--input: oklch(0.28 0.015 250); --accent-foreground: hsl(0 0% 98%);
--ring: oklch(0.65 0.1 240); --destructive: hsl(0 62.8% 30.6%);
--chart-1: oklch(0.65 0.1 240); --destructive-foreground: hsl(0 0% 98%);
--chart-2: oklch(0.7 0.12 200); --border: hsl(240 3.7% 15.9%);
--chart-3: oklch(0.75 0.15 160); --input: hsl(240 3.7% 15.9%);
--chart-4: oklch(0.8 0.12 120); --ring: hsl(240 4.9% 83.9%);
--chart-5: oklch(0.7 0.18 80); --chart-1: hsl(220 70% 50%);
--sidebar: oklch(0.14 0.025 250); --chart-2: hsl(160 60% 45%);
--sidebar-foreground: oklch(0.88 0.02 250); --chart-3: hsl(30 80% 55%);
--sidebar-primary: oklch(0.8 0.06 240); --chart-4: hsl(280 65% 60%);
--sidebar-primary-foreground: oklch(0.12 0.025 250); --chart-5: hsl(340 75% 55%);
--sidebar-accent: oklch(0.22 0.04 245); --sidebar: hsl(240 5.9% 10%);
--sidebar-accent-foreground: oklch(0.88 0.02 250); --sidebar-foreground: hsl(240 4.8% 95.9%);
--sidebar-border: oklch(0.32 0.035 250); --sidebar-primary: hsl(224.3 76.3% 48%);
--sidebar-ring: oklch(0.55 0.08 240); --sidebar-primary-foreground: hsl(0 0% 100%);
--destructive-foreground: oklch(0.95 0.01 250); --sidebar-accent: hsl(240 3.7% 15.9%);
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
--sidebar-border: hsl(240 3.7% 15.9%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
} }
} }
@layer base { @layer base {
.dark { .dark {
--background: oklch(0.12 0.008 250); /* Dark Mode (Zinc) */
--foreground: oklch(0.95 0.005 250); --background: hsl(240 10% 3.9%);
--card: oklch(0.18 0.008 250); --foreground: hsl(0 0% 98%);
--card-foreground: oklch(0.95 0.005 250); --card: hsl(240 3.7% 15.9%);
--popover: oklch(0.2 0.01 250); --card-foreground: hsl(0 0% 98%);
--popover-foreground: oklch(0.95 0.005 250); --popover: hsl(240 10% 3.9%);
--primary: oklch(0.65 0.1 240); --popover-foreground: hsl(0 0% 98%);
--primary-foreground: oklch(0.08 0.02 250); --primary: hsl(217.2 91.2% 59.8%);
--secondary: oklch(0.25 0.015 245); /* Indigo-400 */
--secondary-foreground: oklch(0.92 0.008 250); --primary-foreground: hsl(222.2 47.4% 11.2%);
--muted: oklch(0.22 0.01 250); --secondary: hsl(217.2 32.6% 17.5%);
--muted-foreground: oklch(0.65 0.02 245); --secondary-foreground: hsl(210 40% 98%);
--accent: oklch(0.35 0.025 245); --muted: hsl(217.2 32.6% 17.5%);
--accent-foreground: oklch(0.92 0.008 250); --muted-foreground: hsl(215 20.2% 65.1%);
--destructive: oklch(0.7022 0.1892 22.2279); --accent: hsl(217.2 32.6% 17.5%);
--border: oklch(0.3 0.015 250); --accent-foreground: hsl(210 40% 98%);
--input: oklch(0.28 0.015 250); --destructive: hsl(0 62.8% 30.6%);
--ring: oklch(0.65 0.1 240); --destructive-foreground: hsl(210 40% 98%);
--chart-1: oklch(0.65 0.1 240); --border: hsl(217.2 32.6% 17.5%);
--chart-2: oklch(0.7 0.12 200); --input: hsl(217.2 32.6% 17.5%);
--chart-3: oklch(0.75 0.15 160); --ring: hsl(217.2 91.2% 59.8%);
--chart-4: oklch(0.8 0.12 120); --chart-1: hsl(220 70% 50%);
--chart-5: oklch(0.7 0.18 80); --chart-2: hsl(160 60% 45%);
--sidebar: oklch(0.14 0.025 250); --chart-3: hsl(30 80% 55%);
--sidebar-foreground: oklch(0.88 0.02 250); --chart-4: hsl(280 65% 60%);
--sidebar-primary: oklch(0.8 0.06 240); --chart-5: hsl(340 75% 55%);
--sidebar-primary-foreground: oklch(0.12 0.025 250); --sidebar: hsl(240 5.9% 10%);
--sidebar-accent: oklch(0.22 0.04 245); --sidebar-foreground: hsl(240 4.8% 95.9%);
--sidebar-accent-foreground: oklch(0.88 0.02 250); --sidebar-primary: hsl(224.3 76.3% 48%);
--sidebar-border: oklch(0.32 0.035 250); --sidebar-primary-foreground: hsl(0 0% 100%);
--sidebar-ring: oklch(0.55 0.08 240); --sidebar-accent: hsl(240 3.7% 15.9%);
--destructive-foreground: oklch(0.95 0.01 250); --sidebar-accent-foreground: hsl(240 4.8% 95.9%);
--sidebar-border: hsl(240 3.7% 15.9%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
} }
} }
@@ -207,6 +222,7 @@
* { * {
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
letter-spacing: var(--tracking-normal); letter-spacing: var(--tracking-normal);