Workspace Switcher

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

Signaler un 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
bash

Storybook

Explorez les variantes, controles et verifications d'accessibilite dans le playground Storybook interactif.

Voir dans 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 };
typescript

Dependances

  • @vllnt/ui@^0.2.1