// ~/src/siteplane/components/resource-list/resource-list.tsx "use client"; import { mergeProps } from "@base-ui/react/merge-props"; import { useRender } from "@base-ui/react/use-render"; import * as React from "react"; import { useResolvedDensity } from "../../system/provider/siteplane-ui-provider"; import { cn } from "../../system/utils/cn"; import { slotClass } from "../../system/utils/slot-class"; import { Button } from "../button"; import { SelectionCheckIcon } from "../action-icons"; import { MotionItem, MotionList, type MotionListProps } from "../motion"; import { useFocusHandoff } from "./resource-list-focus"; export type ResourceListDensity = "default" | "compact"; export type ResourceListVariant = "default" | "soft" | "divided"; export type ResourceListIndicatorTone = | "chart-1" | "chart-2" | "chart-3" | "chart-4" | "chart-5" | "error" | "info" | "neutral" | "success" | "warning"; /** @deprecated `muted` is the earlier name of `neutral`. */ export type ResourceListIndicatorDeprecatedTone = "muted"; export type ResourceListIndicatorShape = "bar" | "diamond" | "dot" | "ring"; export interface ResourceListProps extends useRender.ComponentProps<"div"> { density?: ResourceListDensity; /** * Sets the row content flush with the edges of the list, so that it lines up * with the content around the list, such as a Filter Bar or a Card heading. * The rows reach past the edges by their own padding, which keeps the hover, * current and selected fills inset around the content. The container needs * that much room, as a Card panel has. */ flush?: boolean; /** * `soft` groups the rows on a subtle surface. `divided` draws the kit's * hairline between two rows, across the row content, and keeps no space * above the first row's content or below the last row's: the list reaches * past its top and bottom by the row padding, as `flush` makes it reach past * its sides. With clickable (`interactive`) rows the line is as wide as their * hover surface. A `ResourceListHeader` gets the same line below it. */ variant?: ResourceListVariant; } export interface ResourceListItemProps extends useRender.ComponentProps<"div"> { active?: boolean; disabled?: boolean; interactive?: boolean; selected?: boolean; } export interface ResourceListIndicatorProps extends useRender.ComponentProps<"span"> { icon?: React.ReactNode; label?: string; shape?: ResourceListIndicatorShape; /** * The mark's color: a status value, `neutral` (the default) or a chart * color. `muted` is the deprecated earlier name of `neutral`. */ tone?: ResourceListIndicatorTone | ResourceListIndicatorDeprecatedTone; } const resourceListIndicatorShapeClassNames = { bar: "h-4 w-1 rounded-full", diamond: "size-2 rotate-45 rounded-[2px]", dot: "size-1.5 rounded-full", ring: "size-2.5 rounded-full border-2 border-current bg-transparent", } as const satisfies Record; /** Rows read this to know they are in a list, which animates them (rulebook M6, M7). */ const ResourceListContext = React.createContext(false); // The line between two rows of a divided list, and below its header: the // kit's hairline (rulebook B7), across the row content, in the middle of the // space between the rows. With clickable rows it is as wide as their hover // surface (design-rework.css). const resourceListDivider = (