fa5485049a
web-platform-release / Build and publish release (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled
66 lines
2.3 KiB
TypeScript
66 lines
2.3 KiB
TypeScript
import { Database, PanelRight, SlidersHorizontal, type LucideIcon } from 'lucide-react';
|
|
|
|
export type RightSidebarView = 'inspector' | 'controls' | 'data';
|
|
|
|
const VIEWS: ReadonlyArray<{
|
|
value: RightSidebarView;
|
|
label: string;
|
|
icon: LucideIcon;
|
|
}> = [
|
|
{ value: 'inspector', label: '检查器', icon: PanelRight },
|
|
{ value: 'controls', label: '控制台', icon: SlidersHorizontal },
|
|
{ value: 'data', label: '数据录制', icon: Database },
|
|
];
|
|
|
|
export function RightSidebarTabs({
|
|
value,
|
|
onChange,
|
|
}: {
|
|
value: RightSidebarView;
|
|
onChange: (value: RightSidebarView) => void;
|
|
}) {
|
|
return (
|
|
<div
|
|
role="tablist"
|
|
aria-label="右侧工作区视图"
|
|
className="grid shrink-0 grid-cols-3 gap-1 border-b border-border bg-panel-muted/40 p-2"
|
|
>
|
|
{VIEWS.map((view, index) => {
|
|
const Icon = view.icon;
|
|
const selected = value === view.value;
|
|
return (
|
|
<button
|
|
key={view.value}
|
|
type="button"
|
|
role="tab"
|
|
aria-selected={selected}
|
|
tabIndex={selected ? 0 : -1}
|
|
className={`flex min-w-0 items-center justify-center gap-1.5 rounded-md px-1 py-2 text-[11px] font-medium transition-colors ${
|
|
selected
|
|
? 'bg-panel text-accent shadow-sm'
|
|
: 'text-text-tertiary hover:bg-element-hover hover:text-text-primary'
|
|
}`}
|
|
onClick={() => onChange(view.value)}
|
|
onKeyDown={(event) => {
|
|
let nextIndex: number | undefined;
|
|
if (event.key === 'ArrowLeft') nextIndex = (index + VIEWS.length - 1) % VIEWS.length;
|
|
else if (event.key === 'ArrowRight') nextIndex = (index + 1) % VIEWS.length;
|
|
else if (event.key === 'Home') nextIndex = 0;
|
|
else if (event.key === 'End') nextIndex = VIEWS.length - 1;
|
|
if (nextIndex === undefined) return;
|
|
event.preventDefault();
|
|
onChange(VIEWS[nextIndex].value);
|
|
const tabs =
|
|
event.currentTarget.parentElement?.querySelectorAll<HTMLElement>('[role="tab"]');
|
|
tabs?.[nextIndex]?.focus();
|
|
}}
|
|
>
|
|
<Icon className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
|
<span className="truncate">{view.label}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|