Files
hristudio/docs/ui-design.md
Sean O'Connor ab08c1b724 feat: Enhance plugin store and experiment design infrastructure
- Add plugin store system with dynamic loading of robot actions
- Implement plugin store API routes and database schema
- Update experiment designer to support plugin-based actions
- Refactor environment configuration and sidebar navigation
- Improve authentication session handling with additional user details
- Update Tailwind CSS configuration and global styles
- Remove deprecated files and consolidate project structure
2025-02-28 11:10:56 -05:00

6.7 KiB

UI Design & User Experience

Design System

Color System

Our color system is defined in CSS variables with both light and dark mode variants:

:root {
  /* Core colors */
  --background: 0 0% 100%;
  --foreground: 222 47% 11%;
  
  /* Primary colors */
  --primary: 217 91% 60%;
  --primary-foreground: 0 0% 100%;
  
  /* Card colors */
  --card: 0 0% 100%;
  --card-foreground: 222 47% 11%;
  
  /* Additional semantic colors */
  --muted: 210 40% 96%;
  --muted-foreground: 215 16% 47%;
  --accent: 210 40% 96%;
  --accent-foreground: 222 47% 11%;
  
  /* ... additional color definitions ... */
}

.dark {
  --background: 222 47% 11%;
  --foreground: 210 40% 98%;
  /* ... dark mode variants ... */
}

Typography

We use the Geist font family for its clean, modern appearance:

import { GeistSans } from 'geist/font/sans';

<body className={cn(
  "min-h-screen bg-background font-sans antialiased",
  GeistSans.className
)}>

Spacing System

Consistent spacing using Tailwind's scale:

  • space-1: 0.25rem (4px)
  • space-2: 0.5rem (8px)
  • space-4: 1rem (16px)
  • space-6: 1.5rem (24px)
  • space-8: 2rem (32px)

Component Architecture

Base Components

All base components are built on Radix UI primitives and styled with Tailwind:

// Example Button Component
const Button = React.forwardRef<
  HTMLButtonElement,
  ButtonProps
>(({ className, variant, size, ...props }, ref) => {
  return (
    <button
      className={cn(
        "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
        "focus-visible:outline-none focus-visible:ring-2",
        "disabled:opacity-50 disabled:pointer-events-none",
        buttonVariants({ variant, size, className })
      )}
      ref={ref}
      {...props}
    />
  );
});

Layout Components

Page Layout

export function PageLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex h-full min-h-screen w-full">
      <AppSidebar />
      <div className="flex w-0 flex-1 flex-col">
        <Header />
        <main className="flex-1 overflow-auto p-4">
          <PageTransition>
            {children}
          </PageTransition>
        </main>
      </div>
    </div>
  );
}

Sidebar Navigation

The sidebar uses a floating design with dynamic content based on context:

export function AppSidebar({ ...props }: SidebarProps) {
  return (
    <Sidebar 
      collapsible="icon" 
      variant="floating"
      className="border-none"
      {...props}
    >
      <SidebarHeader>
        <StudySwitcher />
      </SidebarHeader>
      <SidebarContent>
        <NavMain items={navItems} />
      </SidebarContent>
      <SidebarFooter>
        <NavUser />
      </SidebarFooter>
      <SidebarRail />
    </Sidebar>
  );
}

Form Components

Forms use React Hook Form with Zod validation:

const form = useForm<FormData>({
  resolver: zodResolver(schema),
  defaultValues: {
    title: "",
    description: "",
  },
});

return (
  <Form {...form}>
    <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
      <FormField
        control={form.control}
        name="title"
        render={({ field }) => (
          <FormItem>
            <FormLabel>Title</FormLabel>
            <FormControl>
              <Input {...field} />
            </FormControl>
            <FormMessage />
          </FormItem>
        )}
      />
      {/* Additional form fields */}
    </form>
  </Form>
);

Responsive Design

Breakpoints

We follow Tailwind's default breakpoints:

  • sm: 640px
  • md: 768px
  • lg: 1024px
  • xl: 1280px
  • 2xl: 1536px

Mobile-First Approach

export function StudyCard({ study }: StudyCardProps) {
  return (
    <Card className="
      w-full
      p-4
      sm:p-6
      md:hover:shadow-lg
      transition-all
      duration-200
    ">
      {/* Card content */}
    </Card>
  );
}

Animation System

Transition Utilities

Common transitions are defined in Tailwind config:

theme: {
  extend: {
    transitionTimingFunction: {
      'bounce-ease': 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
    },
  },
}

Page Transitions

Using Framer Motion for smooth page transitions:

export function PageTransition({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: 20 }}
      transition={{ duration: 0.2 }}
    >
      {children}
    </motion.div>
  );
}

Loading States

Skeleton Components

export function CardSkeleton() {
  return (
    <div className="p-6 space-y-4">
      <Skeleton className="h-7 w-[40%]" />
      <Skeleton className="h-4 w-[60%]" />
      <div className="pt-4">
        <Skeleton className="h-4 w-[25%]" />
      </div>
    </div>
  );
}

Loading Indicators

export function LoadingSpinner({ size = "default" }: { size?: "sm" | "default" | "lg" }) {
  return (
    <div
      className={cn(
        "animate-spin rounded-full border-2",
        "border-background border-t-foreground",
        {
          "h-4 w-4": size === "sm",
          "h-6 w-6": size === "default",
          "h-8 w-8": size === "lg",
        }
      )}
    />
  );
}

Accessibility

ARIA Labels

All interactive components include proper ARIA labels:

export function IconButton({ label, icon: Icon, ...props }: IconButtonProps) {
  return (
    <Button
      {...props}
      aria-label={label}
      className="p-2 hover:bg-muted/50 rounded-full"
    >
      <Icon className="h-4 w-4" />
      <span className="sr-only">{label}</span>
    </Button>
  );
}

Keyboard Navigation

Support for keyboard navigation in all interactive components:

export function NavigationMenu() {
  return (
    <nav
      role="navigation"
      className="focus-within:outline-none"
      onKeyDown={(e) => {
        if (e.key === "Escape") {
          // Handle escape key
        }
      }}
    >
      {/* Navigation items */}
    </nav>
  );
}

Best Practices

  1. Component Organization:

    • One component per file
    • Clear prop interfaces
    • Consistent file naming
  2. Style Organization:

    • Use Tailwind utility classes
    • Extract common patterns to components
    • Maintain consistent spacing
  3. Performance:

    • Lazy load non-critical components
    • Use React.memo for expensive renders
    • Implement proper loading states
  4. Accessibility:

    • Include ARIA labels
    • Support keyboard navigation
    • Maintain proper contrast ratios
  5. Testing:

    • Component unit tests
    • Integration tests for flows
    • Visual regression testing