import type { PageComponentImpact } from "@openstatus/db/src/schema/page_components/constants";
import { Add } from "@openstatus/icons";
import { Button } from "@openstatus/ui/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@openstatus/ui/components/ui/dropdown-menu";
import { SelectTrigger } from "@openstatus/ui/components/ui/select";
import { cn } from "@openstatus/ui/lib/utils";
import { Fragment } from "react";
import { StatusDot } from "@/components/common/status-dot";
import {
type OrderedComponent,
toComponentSections,
} from "@/data/page-components.client";
import { impactDisplay } from "@/data/status-report-updates.client";
export function ComponentList({
children,
className,
...props
}: React.ComponentProps<"ul">) {
return (
);
}
export function ComponentListItem({
children,
className,
...props
}: React.ComponentProps<"li">) {
return (
{children}
);
}
/** `group` renders muted after the name for components in a group. */
export function ComponentListName({
children,
group,
className,
...props
}: React.ComponentProps<"span"> & { group?: string | null }) {
return (
{children}
{group ? (
{group}
) : null}
);
}
/** Dot + label for an impact; nullish reads as untriaged. */
export function ComponentImpact({
impact,
className,
...props
}: React.ComponentProps<"span"> & {
impact: PageComponentImpact | null | undefined;
}) {
const display = impactDisplay(impact);
return (
{display.label}
);
}
/** Right-aligned controls on a row, like `DetailActions`. */
export function ComponentListActions({
children,
className,
...props
}: React.ComponentProps<"div">) {
return (
{children}
);
}
export function ComponentListEmpty({
children = "No components affected.",
className,
...props
}: React.ComponentProps<"p">) {
return (
{children}
);
}
// Borderless until hovered, like PropertySelectTrigger.
export function ComponentListSelectTrigger({
className,
...props
}: React.ComponentProps) {
return (
);
}
/**
* Adds one of `components` not yet on the list, in page order: a group's
* members sit under its label, runs of ungrouped components in between,
* separated. Renders nothing once everything is added, unless a
* `placeholder` explains the empty state.
*/
export function ComponentListAdd({
components,
groups = [],
onAdd,
disabled = false,
placeholder,
className,
}: {
components: OrderedComponent[];
groups?: { id: number; name: string }[];
onAdd: (id: number) => void;
disabled?: boolean;
/** Shown as a disabled button when there is nothing to add. */
placeholder?: string;
className?: string;
}) {
const sections = toComponentSections(components, groups);
// -ml-2.5 puts the label on the names' edge
const buttonClassName = cn(
"text-muted-foreground -ml-2.5 h-7 self-start font-normal",
className,
);
if (components.length === 0) {
if (!placeholder) return null;
return (
);
}
const item = (c: OrderedComponent) => (
onAdd(c.id)}
>
{c.name}
);
return (
{/* long pages: cap at 20rem, still bounded by the viewport on short screens */}
{sections.map((section, i) => {
const key = section.group?.id ?? `ungrouped-${i}`;
return (
{i > 0 ? : null}
{section.group ? (
{section.group.name}
{section.items.map(item)}
) : (
section.items.map(item)
)}
);
})}
);
}