Workspace Switcher

Segmented workspace picker for switching between canvas views and operational contexts.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/workspace-switcher.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

"use client"; import { forwardRef, useMemo, useState } from "react"; import { cn } from "../../lib/utils"; export type WorkspaceOption = { description?: string; id: string; label: string; }; export type WorkspaceSwitcherProps = Omit< React.ComponentPropsWithoutRef<"div">, "defaultValue" | "onChange" > & { defaultValue?: string; onValueChange?: (value: string) => void; value?: string; workspaces: WorkspaceOption[]; }; const WorkspaceSwitcher = forwardRef<HTMLDivElement, WorkspaceSwitcherProps>( ( { className, defaultValue, onValueChange, value, workspaces, ...props }, ref, ) => { const fallbackValue = defaultValue ?? workspaces[0]?.id ?? ""; const [internalValue, setInternalValue] = useState(fallbackValue); const currentValue = value ?? internalValue; const currentWorkspace = useMemo( () => workspaces.find((workspace) => workspace.id === currentValue), [currentValue, workspaces], ); function handleSelect(nextValue: string) { if (value === undefined) { setInternalValue(nextValue); } onValueChange?.(nextValue); } return ( <div className={cn( "inline-flex min-w-0 items-center gap-1 rounded-full border border-border/70 bg-muted/50 p-1", className, )} ref={ref} role="radiogroup" {...props} > {workspaces.map((workspace) => { const isActive = workspace.id === currentValue; return ( <button aria-checked={isActive} className={cn( "rounded-full px-3 py-1.5 text-sm font-medium transition-colors", isActive ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground", )} key={workspace.id} onClick={() => { handleSelect(workspace.id); }} role="radio" title={workspace.description} type="button" > {workspace.label} </button> ); })} {currentWorkspace?.description ? ( <span className="hidden pl-2 pr-1 text-xs text-muted-foreground md:inline"> {currentWorkspace.description} </span> ) : null} </div> ); }, ); WorkspaceSwitcher.displayName = "WorkspaceSwitcher"; export { WorkspaceSwitcher };

Dependencies

  • @vllnt/ui@^0.2.1