"use client";
import { filterAnnotationsByChannel } from "@prototype/lib/prototype-comments/core/annotation-channel";
import { countUnresolvedAnnotations } from "@prototype/lib/prototype-comments/core/annotation-status";
import { getRootAnnotations } from "@prototype/lib/prototype-comments/core/comment-threads";
import { useCommentStore } from "@prototype/lib/prototype-comments/react/CommentProvider";
import { CommentHeaderCount } from "@prototype/lib/prototype-comments/ui/CommentHeaderCount";
import { CommentModeToggleButton } from "@prototype/lib/prototype-comments/ui/CommentModeToggleButton";
import { CommentsGrid } from "@prototype/lib/prototype-comments/ui/CommentsGrid";
import {
usePrototypeReview,
type PrototypeVariantSet,
} from "@prototype/lib/prototypes/prototype-review-context";
import { VariantSetLucideIcon } from "@prototype/lib/prototypes/variant-set-lucide-icon";
import { usePrototypeToolTheme } from "@prototype/lib/prototypes/use-prototype-tool-theme";
import { cn } from "@prototype/lib/utils";
import {
Sidebar,
SIDEBAR_TITLE_CLASS,
} from "@prototype/components/platform-ui/sidebar";
import { EmptyState } from "@prototype/components/platform-ui/empty-state";
import { PrototypeChangeLogPanel } from "@prototype/components/prototypes/prototype-change-log-panel";
import { PrototypeCommentStorageEmptyState } from "@prototype/components/prototypes/prototype-comment-storage-empty-state";
import { PrototypeSpecPanelEmptyState } from "@prototype/components/prototypes/prototype-spec-panel-empty-state";
import { miniPillTextFromLabel } from "@prototype/components/prototypes/mini-pill-label";
import pillStyles from "@prototype/components/prototypes/prototype-floating-pill.module.scss";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@prototype/components/ui/dropdown-menu";
import { ChevronDown } from "lucide-react";
import { useMemo, type ReactNode } from "react";
type PrototypeReviewSidebarProps = {
onSelect: (id: string) => void;
selectedId?: string | null;
onClose: () => void;
isCommentModeActive?: boolean;
onToggleCommentMode?: () => void;
};
const SIDEBAR_HEADER_TRIGGER_CLASS = cn(
SIDEBAR_TITLE_CLASS,
"inline-flex min-w-0 max-w-full cursor-pointer items-center gap-1.5 rounded-md px-1.5 py-0.5 -mx-1.5",
"hover:bg-[var(--tool-chrome-gray-highlight)]",
"focus:outline-none focus-visible:ring-0 focus-visible:ring-offset-0",
"data-[state=open]:bg-[var(--tool-chrome-gray-highlight)]",
);
const SIDEBAR_HEADER_MENU_CONTENT_CLASS = cn(
pillStyles.dropdownContent,
pillStyles.dropdownContentFooterMenu,
"z-[1051] border-0 bg-transparent p-0 text-foreground shadow-none ring-0",
"data-[state=open]:animate-none data-[state=closed]:animate-none",
);
type HandoffSidebarPanel = "change-log" | "spec";
const HANDOFF_PANEL_OPTIONS: { id: HandoffSidebarPanel; label: string }[] = [
{ id: "change-log", label: "Overview" },
{ id: "spec", label: "PRs" },
];
function SidebarHeaderDropdown({
label,
ariaLabel,
labelAddon,
children,
}: {
label: string;
ariaLabel: string;
labelAddon?: ReactNode;
children: ReactNode;
}) {
const { commentTheme } = usePrototypeToolTheme();
return (
event.preventDefault()}
>
{children}
);
}
function HandoffPanelHeaderSwitcher({
activePanel,
onSelect,
}: {
activePanel: HandoffSidebarPanel;
onSelect: (panel: HandoffSidebarPanel) => void;
}) {
const activeLabel =
HANDOFF_PANEL_OPTIONS.find((option) => option.id === activePanel)?.label ??
"Overview";
return (
{HANDOFF_PANEL_OPTIONS.map((option) => {
const selected = activePanel === option.id;
return (
onSelect(option.id)}
>
{option.label}
);
})}
);
}
function VariantSetHeaderSwitcher({
label,
variantSets,
activeVariantSetId,
onSelect,
}: {
label: string;
variantSets: PrototypeVariantSet[];
activeVariantSetId: string | null;
onSelect: (id: string) => void;
}) {
const activeVariantSet =
variantSets.find((set) => set.id === activeVariantSetId) ?? variantSets[0];
const titleIcon = activeVariantSet ? (
) : null;
if (variantSets.length <= 1) {
return (
{titleIcon}
{label}
);
}
return (
{variantSets.map((variantSet) => {
const selected = activeVariantSetId === variantSet.id;
return (
onSelect(variantSet.id)}
>
{miniPillTextFromLabel(variantSet.label)}
);
})}
);
}
export function PrototypeReviewSidebar({
onSelect,
selectedId,
onClose,
isCommentModeActive = false,
onToggleCommentMode,
}: PrototypeReviewSidebarProps) {
const review = usePrototypeReview();
const { annotations, deleteAnnotation, updateAnnotation, resolveAnnotation, storageError, storageReady, storageConfigured } = useCommentStore();
const isVariantsPanel = review.sidebarPanel === "variants";
const isChangeLogPanel = review.sidebarPanel === "change-log";
const isSpecPanel = review.sidebarPanel === "spec";
const activeVariantLabel = useMemo(() => {
if (!review.activeVariantSetId) return "Variants";
const label = review.variantSets.find(
(set) => set.id === review.activeVariantSetId,
)?.label;
return label ? miniPillTextFromLabel(label) : "Variants";
}, [review.activeVariantSetId, review.variantSets]);
const variantsContent = useMemo(() => {
if (!isVariantsPanel) return null;
if (review.activeVariantSetId) {
return review.getVariantSidebarContent(review.activeVariantSetId);
}
return review.getLegacyVariantSidebarContent();
}, [
isVariantsPanel,
review.activeVariantSetId,
review.variantSidebarRevision,
review.getVariantSidebarContent,
review.getLegacyVariantSidebarContent,
]);
const commentAnnotations = useMemo(
() => filterAnnotationsByChannel(annotations, "comment"),
[annotations],
);
const unresolvedCount = countUnresolvedAnnotations(commentAnnotations);
const totalCount = getRootAnnotations(commentAnnotations).length;
const specContent = isSpecPanel ? review.specContentRef.current : null;
const isCommentsPanel =
!isVariantsPanel && !isChangeLogPanel && !isSpecPanel;
const isHandoffPanel = isChangeLogPanel || isSpecPanel;
const sidebarText: ReactNode = isVariantsPanel
? (
)
: isHandoffPanel
? (
review.openSidebar(panel)}
/>
)
: "Comments";
return (
) : undefined
}
headerActions={
isCommentsPanel && onToggleCommentMode ? (
) : undefined
}
onClose={
isVariantsPanel
? review.closeVariants
: isSpecPanel
? review.closeSidebar
: isChangeLogPanel
? review.closeSidebar
: onClose
}
ariaLabel={
isVariantsPanel
? "Variants panel"
: isChangeLogPanel
? "Overview panel"
: isSpecPanel
? "PRs panel"
: "Review panel"
}
dataAttribute="data-prototype-review-sidebar"
widthCssVar="--comments-sidebar-width"
>
{isSpecPanel ? (
specContent ??
) : isVariantsPanel ? (
variantsContent ?? (
No variant overview available.
)
) : isChangeLogPanel ? (
) : !storageReady ? (
Loading comments…
) : storageConfigured === false ? (
) : storageError ? (
{storageError}
) : (
)}
);
}