Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.0.2

### Patch Changes

- 373f5a6: Fix dark theme adaptation for sidebar chrome, dialogs, charts, and scoped studio root tokens.

## 1.0.1

### Patch Changes
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# 变更日志

## [1.0.2] - 2026-08-10

### 已修复

- 修复深色主题:侧栏 Tab/分割条、弹窗、图表与 scoped studio root 的语义色适配

## [1.0.1] - 2026-08-07

### 已更改
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ioai/lerobot-studio",
"private": false,
"version": "1.0.1",
"version": "1.0.2",
"type": "module",
"packageManager": "npm@11.7.0",
"scripts": {
Expand Down
13 changes: 11 additions & 2 deletions src/react/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,24 @@ export function ErrorFallback({ error, onReset }: ErrorFallbackProps) {
const { t } = useTranslation();
const errorDetails = formatErrorForDisplay(error, import.meta.env.DEV);

const preferDark =
typeof window !== 'undefined' &&
(() => {
const saved = window.localStorage?.getItem('theme');
if (saved === 'dark') return true;
if (saved === 'light') return false;
return window.matchMedia('(prefers-color-scheme: dark)').matches;
})();

return (
<div
className="flex flex-col items-center justify-center min-h-screen bg-background p-6 text-center"
className={`flex flex-col items-center justify-center min-h-screen bg-background text-foreground p-6 text-center${preferDark ? ' dark' : ''}`}
role="alert"
>
<div className="bg-destructive/10 p-4 rounded-full mb-6">
<AlertCircle className="h-12 w-12 text-destructive" aria-hidden />
</div>
<h1 className="text-2xl font-bold mb-2">{t('errorBoundary.title')}</h1>
<h1 className="text-2xl font-bold mb-2 text-foreground">{t('errorBoundary.title')}</h1>
<p className="text-muted-foreground mb-8 max-w-md mx-auto">
{t('errorBoundary.description')}
</p>
Expand Down
13 changes: 11 additions & 2 deletions src/react/components/LeRobotStudioProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,22 @@ export const LeRobotStudioProvider: React.FC<LeRobotStudioProviderProps> = ({
return (
<I18nProvider forcedLanguage={language}>
{wrapRoot ? (
<div ref={setRootEl} className={cn('lerobot-root h-full w-full relative', className)}>
<div
ref={setRootEl}
className={cn(
'lerobot-root h-full w-full relative bg-background text-foreground',
className,
)}
>
<StudioTree theme={theme} showToaster={showToaster} portalContainer={rootEl}>
{children}
</StudioTree>
</div>
) : (
<div ref={setRootEl} className={cn('lerobot-root h-full w-full', className)}>
<div
ref={setRootEl}
className={cn('lerobot-root h-full w-full bg-background text-foreground', className)}
>
<StudioTree theme={theme} showToaster={showToaster} portalContainer={rootEl}>
{children}
</StudioTree>
Expand Down
2 changes: 1 addition & 1 deletion src/react/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const Navbar: React.FC<NavbarProps> = ({
const displayName = datasetName || t('common.companyName');

return (
<nav className="sticky top-0 z-50 w-full border-b bg-background">
<nav className="sticky top-0 z-50 w-full border-b bg-background text-foreground">
<div className="flex h-12 items-center px-4 gap-4 relative">
{/* Left: Open & History */}
<div className="flex items-center gap-2 mr-auto">
Expand Down
4 changes: 2 additions & 2 deletions src/react/components/Playback/PlaybackBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const PlaybackBar: React.FC = () => {
if (isLoading || activeTask) {
return (
<div
className="h-16 border-t bg-background px-4 flex items-center gap-4 animate-pulse motion-reduce:animate-none pointer-events-none opacity-60"
className="h-16 border-t bg-background text-foreground px-4 flex items-center gap-4 animate-pulse motion-reduce:animate-none pointer-events-none opacity-60"
role="status"
aria-live="polite"
aria-label={activeTask?.message || t('common.loading')}
Expand Down Expand Up @@ -160,7 +160,7 @@ export const PlaybackBar: React.FC = () => {
}

return (
<div className="h-16 border-t bg-background px-4 flex items-center gap-4">
<div className="h-16 border-t bg-background text-foreground px-4 flex items-center gap-4">
{/* Left: Navigation Controls */}
<div className="flex items-center gap-1">
<Tooltip>
Expand Down
2 changes: 1 addition & 1 deletion src/react/components/Sidebar/ResizableSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const ResizableSidebar: React.FC<ResizableSidebarProps> = ({
<>
<div
ref={sidebarRef}
className={`flex h-full min-w-0 shrink-0 flex-col overflow-x-hidden border-r ${className}`}
className={`flex h-full min-w-0 shrink-0 flex-col overflow-x-hidden border-r bg-background ${className}`}
style={{
width: `${width}px`,
minWidth: `${minWidth}px`,
Expand Down
2 changes: 1 addition & 1 deletion src/react/components/WelcomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export const WelcomeScreen: React.FC<WelcomeScreenProps> = ({
);

return (
<div className="w-full h-[calc(100vh-3rem)] overflow-y-auto bg-background">
<div className="w-full h-[calc(100vh-3rem)] overflow-y-auto bg-background text-foreground">
<div className="relative overflow-hidden">
<motion.div
className={cn('mx-auto px-6 py-12 lg:py-20', showSamples ? 'max-w-6xl' : 'max-w-xl')}
Expand Down
7 changes: 5 additions & 2 deletions src/react/components/panels/ChartPanel/ChartPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1280,7 +1280,10 @@ const ChartPanelContent: React.FC<ChartPanelProps> = ({ params }) => {
);

return (
<div ref={containerRef} className="w-full h-full flex flex-col bg-background overflow-hidden">
<div
ref={containerRef}
className="w-full h-full flex flex-col bg-background text-foreground overflow-hidden"
>
<div className="flex items-center justify-between px-3 py-1.5 border-b bg-muted/20">
<div className="flex items-center gap-2 overflow-hidden mr-2">
<ChartJointFilterDropdown {...jointFilterDropdownProps} />
Expand Down Expand Up @@ -1396,7 +1399,7 @@ const ChartPanelContent: React.FC<ChartPanelProps> = ({ params }) => {
<div ref={chartRef} className="chart-uplot w-full h-full" />
<div
ref={tooltipRef}
className="chart-tooltip pointer-events-auto select-text cursor-text absolute left-3 top-3 z-20 hidden max-w-[420px] rounded-md border bg-background/95 backdrop-blur text-[11px] shadow-lg transition-opacity duration-150"
className="chart-tooltip pointer-events-auto select-text cursor-text absolute left-3 top-3 z-20 hidden max-w-[420px] rounded-md border bg-background/95 text-foreground backdrop-blur text-[11px] shadow-lg transition-opacity duration-150"
style={{ opacity: 1 }}
/>
</div>
Expand Down
12 changes: 6 additions & 6 deletions src/react/components/panels/ChartPanel/uplot-theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
border-radius: 0;
}

/* 选区框使用 design token */
/* 选区框使用 design token(tokens 已是完整 oklch/hsl,不能再包 hsl()) */
.chart-uplot .u-select {
background: hsl(var(--primary) / 0.08);
border: 1px solid hsl(var(--primary) / 0.25);
background: color-mix(in oklch, var(--primary) 8%, transparent);
border: 1px solid color-mix(in oklch, var(--primary) 25%, transparent);
}

/* 避免 uPlot 容器带来的奇怪间距 */
Expand All @@ -33,7 +33,7 @@

.chart-tooltip {
scrollbar-width: thin;
scrollbar-color: hsl(var(--muted-foreground) / 0.55) transparent;
scrollbar-color: color-mix(in oklch, var(--muted-foreground) 55%, transparent) transparent;
}

.chart-tooltip::-webkit-scrollbar {
Expand All @@ -46,9 +46,9 @@

.chart-tooltip::-webkit-scrollbar-thumb {
border-radius: 9999px;
background: hsl(var(--muted-foreground) / 0.45);
background: color-mix(in oklch, var(--muted-foreground) 45%, transparent);
}

.chart-tooltip::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted-foreground) / 0.7);
background: color-mix(in oklch, var(--muted-foreground) 70%, transparent);
}
4 changes: 2 additions & 2 deletions src/react/components/panels/Common/PanelErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ export function PanelErrorFallback({ error, panelName, onRetry }: PanelErrorFall

return (
<div
className="flex flex-col items-center justify-center h-full w-full bg-background p-4 text-center"
className="flex flex-col items-center justify-center h-full w-full bg-background text-foreground p-4 text-center"
role="alert"
>
<div className="bg-destructive/10 p-3 rounded-full mb-4">
<AlertTriangle className="h-6 w-6 text-destructive" aria-hidden />
</div>
<h3 className="text-sm font-semibold mb-1">
<h3 className="text-sm font-semibold mb-1 text-foreground">
{panelName
? t('panelErrorBoundary.namedTitle', { panelName })
: t('panelErrorBoundary.title')}
Expand Down
2 changes: 1 addition & 1 deletion src/react/components/panels/RawPanel/RawPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1083,7 +1083,7 @@ const RawPanelContent: React.FC<RawPanelProps> = ({ params }) => {
const handleFeatureSearchChange = useCallback((v: string) => setFeatureSearch(v), []);

return (
<div className="flex flex-col h-full bg-background overflow-hidden">
<div className="flex flex-col h-full bg-background text-foreground overflow-hidden">
<RawPanelHeader
isPaused={isPaused}
copied={copied}
Expand Down
5 changes: 4 additions & 1 deletion src/react/lib/chartTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
* shadcn Base UI tokens are full CSS colors (oklch/hsl/rgb).
*/

/** Prefer the themed studio root (`.lerobot-root[.dark]`); `#lerobot-root` is only the mount host. */
const LEROOT =
typeof document !== 'undefined'
? () => document.querySelector('#lerobot-root') as HTMLElement | null
? () =>
(document.querySelector('.lerobot-root') as HTMLElement | null) ??
(document.getElementById('lerobot-root') as HTMLElement | null)
: () => null;

export function getCssVarColor(
Expand Down
4 changes: 2 additions & 2 deletions src/ui/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function DialogContent({
className={cn(
// Use CSS `translate` (Tailwind translate-*) for centering so zoom animations on `transform` do not cancel it.
// Do not also set transform: translate(...) — Tailwind v4 stacks both and double-offsets the dialog.
'fixed top-[50vh] left-[50vw] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg',
'fixed top-[50vh] left-[50vw] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 text-foreground shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg',
className,
)}
{...props}
Expand Down Expand Up @@ -124,7 +124,7 @@ function DialogTitle({ className, ...props }: React.ComponentProps<typeof Dialog
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn('text-lg leading-none font-semibold', className)}
className={cn('text-lg leading-none font-semibold text-foreground', className)}
{...props}
/>
);
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function SheetContent({
<SheetPrimitive.Content
data-slot="sheet-content"
className={cn(
'fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500',
'fixed z-50 flex flex-col gap-4 bg-background text-foreground shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500',
side === 'right' &&
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
side === 'left' &&
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/ui/slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function Slider({
<SliderPrimitive.Thumb
data-slot="slider-thumb"
key={index}
className="block size-4 shrink-0 rounded-full border border-primary bg-white shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
className="block size-4 shrink-0 rounded-full border border-primary bg-background shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
/>
))}
</SliderPrimitive.Root>
Expand Down
7 changes: 5 additions & 2 deletions src/ui/portal-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ export function PortalContainerProvider({
/**
* Resolve the portal mount node for overlays.
* Prefer the nearest Studio root provided via context so multi-instance embeds
* keep Dialog/Menu/Tooltip inside CSS-scoped `#lerobot-root` trees.
* keep Dialog/Menu/Tooltip inside the themed `.lerobot-root` tree (where `.dark` lives).
*/
export function usePortalContainer(): HTMLElement | null {
const fromContext = React.useContext(PortalContainerContext);
if (fromContext) return fromContext;
if (typeof document === 'undefined') return null;
return document.getElementById('lerobot-root');
return (
(document.querySelector('.lerobot-root') as HTMLElement | null) ??
document.getElementById('lerobot-root')
);
}
3 changes: 2 additions & 1 deletion src/web/app/AppProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export function AppProviders({ children }: { children: React.ReactNode }) {
return (
<ErrorBoundary>
<LeRobotStudioProvider showToaster={false} wrapRoot={false} className="h-full w-full">
{children}
{/* Inner boundary keeps crash UI inside the themed `.lerobot-root`. */}
<ErrorBoundary>{children}</ErrorBoundary>
</LeRobotStudioProvider>
</ErrorBoundary>
);
Expand Down
2 changes: 1 addition & 1 deletion src/web/app/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export function AppShell() {
const shouldShowSidebar = hasData && sidebarVisible;

return (
<div className="flex flex-col h-screen overflow-hidden">
<div className="flex flex-col h-screen overflow-hidden bg-background text-foreground">
<Navbar
onOpenDirectory={() => controller.openDirectory()}
onOpenLocalArchive={() => controller.openLocalArchive()}
Expand Down