Data display
List
Bulleted and numbered lists with square markers; nested items hang off the trace.
- Constellation reads every channel
- Problems are traced to a cause
- Grouped across calls, chats and reviews
- Ranked by volume and trend
- Week on week
- Against the same week last year
- Telos drafts the fix
- Name the approvers for each system
- Set the limits Telos acts within
- Which systems it may change
- How many customers one change may touch
- Review the first ten actions together
pnpm dlx shadcn@latest add @paragon/listBrings incn
registry/paragon/components/list.tsx
"use client"
import * as React from "react"
import { cn } from "cn"
type ListVariant = "bullet" | "number"
const ListContext = React.createContext<{ depth: number; variant: ListVariant }>({ depth: -1, variant: "bullet" })
/**
* Lists for reading. Top-level items take a square marker, the mark's square
* end, or a mono numeral. Nested items hang off the trace instead of taking a
* second marker, so depth reads as one line turning, not as more bullets.
* Every level indents the same: the rail drops from the parent's first letter
* and turns into text 1.25rem further in.
*/
function List({
variant = "bullet",
className,
...props
}: React.ComponentProps<"ul"> & { variant?: ListVariant }) {
const depth = React.useContext(ListContext).depth + 1
const context = React.useMemo(() => ({ depth, variant }), [depth, variant])
// Same props either way; an ordered list only adds attributes.
const Tag = (variant === "number" ? "ol" : "ul") as "ul"
return (
<ListContext.Provider value={context}>
<Tag
data-slot="list"
data-variant={variant}
className={cn(
"flex flex-col text-[0.9375rem] leading-6 text-foreground/85 [counter-reset:list]",
// Nested items space with padding, not gap, so the rail runs unbroken between them.
depth > 0 ? "ms-5 mt-1" : "gap-1.5",
className
)}
{...props}
/>
</ListContext.Provider>
)
}
function ListItem({ className, children, ...props }: React.ComponentProps<"li">) {
const { depth, variant } = React.useContext(ListContext)
if (depth > 0) {
return (
<li
data-slot="list-item"
className={cn(
"relative pb-1 last:pb-0",
// The rail from the parent's first letter, stopping at the last turn, and the square turn into the text.
"before:absolute before:-start-5 before:inset-y-0 before:w-px before:bg-border first:before:-top-1 last:before:bottom-[calc(100%-0.8125rem)]",
"after:absolute after:-start-5 after:top-3 after:h-px after:w-3.5 after:bg-border",
className
)}
{...props}
>
{children}
</li>
)
}
return (
<li
data-slot="list-item"
className={cn(
"grid grid-cols-[1.75rem_minmax(0,1fr)] items-baseline",
variant === "bullet"
? "before:size-1.25 before:translate-x-0.5 before:-translate-y-[0.1em] before:bg-foreground/45 rtl:before:-translate-x-0.5"
: "[counter-increment:list] before:font-mono before:text-[0.75rem] before:text-muted-foreground before:tabular-nums before:content-[counter(list,decimal-leading-zero)]",
className
)}
{...props}
>
<div className="min-w-0">{children}</div>
</li>
)
}
export { List, ListItem }