"use client"; import React from "react"; import { setTheme, useTheme } from "@/utils/theme"; /** * Shared building blocks for the analysis tabs (Statistics, Filtering, Frames, Action Insights), so * their headings, cards, loading states and toggles look the same everywhere. */ /** Tab title and one-line description, with an optional control on the right. */ export function PageHeader({ title, description, children, }: { title: string; description?: React.ReactNode; children?: React.ReactNode; }) { return (

{title}

{description && (

{description}

)}
{children &&
{children}
}
); } /** A labelled number: the label styled like the sidebar's, the value large and tabular. */ export function StatCard({ label, value, }: { label: string; value: React.ReactNode; }) { return (

{label}

{value}

); } export function Spinner({ className = "h-4 w-4" }: { className?: string }) { return ( ); } /** A spinner with a label, for a section that is still loading. */ export function InlineLoading({ label }: { label: string }) { return (
{label}
); } /** Two-state switch with a label on each side; the active side is highlighted. */ export function Switch({ checked, onChange, offLabel, onLabel, ariaLabel, }: { checked: boolean; onChange: (checked: boolean) => void; offLabel: React.ReactNode; onLabel: React.ReactNode; ariaLabel: string; }) { const labelClass = (active: boolean) => `text-sm ${active ? "text-fg font-medium" : "text-fg-faint"}`; return (
{offLabel} {onLabel}
); } /** Repo and episode above the videos, on every tab that plays an episode. */ export function EpisodeHeader({ repoId, episodeId, }: { repoId: string; episodeId: number; }) { return (
{/* eslint-disable-next-line @next/next/no-img-element */} LeRobot Logo

{repoId}

Episode ยท {episodeId}

); } /** Switches between the light and dark theme; the choice is remembered. */ export function ThemeToggle() { const theme = useTheme(); const next = theme === "dark" ? "light" : "dark"; return ( ); }