UI
Page-composition primitives — shell, section, empty state. Presentational and *not* `"use client"`, so they render in a Server Component.
Generated from @cosmoops/connect-sdk/ui's TypeScript source and its own doc comments - see Reference for the other entry points. Regenerated on every deploy; don't hand-edit.
Interfaces
Interface: ConnectEmptyStateProps
Properties
| Property | Type | Description |
|---|---|---|
icon? | ReactNode | Rendered at size-8. Any icon element - lucide, an SVG, a glyph. |
title | ReactNode | What is absent, in the user's words. One line. |
description? | ReactNode | - |
action? | ReactNode | The one control that resolves the emptiness - "New contact" for a list that has never had anything in it, "Clear filter" for one whose contents are merely hidden. |
variant? | "card" | "dashed" | "plain" | - |
className? | string | - |
Interface: ConnectPageHeaderProps
Properties
| Property | Type | Description |
|---|---|---|
title | ReactNode | - |
description? | ReactNode | - |
action? | ReactNode | Rendered opposite the title. Keep it to one control - a page has one primary action, and a second here has nothing to align against. |
clamp? | boolean | Set false when the description carries a link that must stay readable on a phone. |
className? | string | - |
Interface: ConnectPageShellProps
Properties
| Property | Type | Description |
|---|---|---|
title? | ReactNode | - |
description? | ReactNode | - |
action? | ReactNode | - |
clamp? | boolean | - |
above? | ReactNode | Rendered above the header - a breadcrumb trail, a back link, a banner. |
className? | string | Merged onto the page root, so a utility here beats the default rather than sitting beside it - className="space-y-8" really does widen the page gap. See cn. |
transition? | boolean | The route transition - the outgoing page fades out, the arriving one fades in and rises. On by default; pass false where a host already animates its routes, or the two play over each other. See RouteTransition. |
children? | ReactNode | - |
Interface: ConnectSectionProps
Properties
| Property | Type | Description |
|---|---|---|
title | ReactNode | - |
description? | ReactNode | What the block is for. Omit when the title says everything. |
action? | ReactNode | Rendered opposite the title. One control - a section has one action. |
titleId? | string | Set to reference the heading from an aria-labelledby in the body. |
className? | string | - |
bodyClassName? | string | - |
children? | ReactNode | - |
Interface: ConnectSettingRowProps
Properties
| Property | Type | Description |
|---|---|---|
title | ReactNode | - |
description? | ReactNode | - |
action? | ReactNode | The control this row is about - a switch, a button, a value with an edit link. |
children? | ReactNode | Rendered under both, full width - a validation message, or a nested field the row reveals. |
className? | string | - |
Interface: ConnectStatusPresentation<TStatus>
How one domain's status union is presented: a tone and a human label per member.
Both are total Records, and that is the load-bearing property - adding
a member to the status union fails the type gate until it has been given a
presentation. A loose Record<string, …> map silently renders the new
status as undefined.
Type Parameters
| Type Parameter |
|---|
TStatus extends string |
Properties
| Property | Modifier | Type |
|---|---|---|
tone | readonly | Readonly<Record<TStatus, ConnectStatusTone>> |
label | readonly | Readonly<Record<TStatus, string>> |
Type Aliases
Type Alias: ConnectStatusTone
type ConnectStatusTone = "success" | "warning" | "danger" | "accent" | "default";The closed set of tones a status may resolve to.
Deliberately five, and deliberately narrower than HeroUI's Chip colour
union: a status is either fine, needs attention, is broken, is in progress,
or is inert. A sixth tone would have to mean one of those five again, which
is where hand-written status chips start disagreeing with each other.
Variables
Variable: cn
const cn: <T>(...classnames: T) => CnReturn;Combines class names and merges conflicting Tailwind classes (default config).
Type Parameters
| Type Parameter |
|---|
T extends CnOptions |
Parameters
| Parameter | Type |
|---|---|
...classnames | T |
Returns
CnReturn
Functions
Function: ConnectEmptyState()
function ConnectEmptyState(__namedParameters: Readonly<ConnectEmptyStateProps>): Element;What a list, grid or table shows when it has nothing to show.
The two empty states are different facts and the copy must say so. "You
have no contacts" and "no contacts match this filter" call for different
words and a different action - create the thing, versus undo the filter
hiding it. Both render through here; the caller decides which by what it
passes as action. Getting this wrong costs a user real time: an
unfiltered-looking empty state in front of a filtered list reads as data
loss.
variant chooses the container by what the state stands in: card (the
default), dashed for a region that will be filled in place - a file grid,
a board column - where the outline is what says "things appear here", and
plain for a slot already inside a card that would otherwise nest two
borders.
Parameters
| Parameter | Type |
|---|---|
__namedParameters | Readonly<ConnectEmptyStateProps> |
Returns
Element
Function: ConnectPageHeader()
function ConnectPageHeader(__namedParameters: Readonly<ConnectPageHeaderProps>): Element;The title / description / action block a page opens with.
The structure is the point, not the styling. The action is a peer of the title, not of the block, so nothing below the first line can move it - the common hand-rolled shape puts the action after the description, which makes the vertical position of a page's primary control a function of how long someone wrote the paragraph above it. On a phone that pushes it most of a thumb's reach down the screen.
Mobile-first, and the two axes are separate decisions: the title row is a
justify-between at every width (title left, action right, where a thumb
expects it), while the description is clamped to two lines on a phone and
released from sm up.
An h2, because the document's h1 is the application itself.
No hooks and no state, so this renders from a Server Component - which is
why it lives in @cosmoops/connect-sdk/ui rather than /client.
Parameters
| Parameter | Type |
|---|---|
__namedParameters | Readonly<ConnectPageHeaderProps> |
Returns
Element
Function: ConnectPageShell()
function ConnectPageShell(__namedParameters: Readonly<ConnectPageShellProps>): Element;The frame a page opens with: its vertical rhythm, its header, and the suspended regions beneath.
The header is a prop, so it renders in the static shell - above and
outside every <Suspense> boundary. That is the one bug this component
exists to make unrepresentable: rendering the header inside the async child
makes a page's own title wait on a data read, and under Next's partial
prefetching the route's shared App Shell loses it as well. There is no
arrangement of children that reaches the header slot.
A detail route is the honest exception, and it has its own door:
ConnectPageShell.Header renders the identical header from inside an async
child, for a page whose title is data it does not have yet. Pair it with a
skeleton header in the fallback. The rule is that a static title must
never be made to wait, not that every title can be static.
space-y-5 is the page gap, owned here so it has exactly one definition.
It is also where the route transition lives, because it is the one
thing every page opens with. A transition has to wrap the page: a layout
persists across navigation, so its enter and exit never fire, and a
template.tsx remounts its subtree on every navigation, discarding the
form drafts and scroll positions Next's <Activity> keeps. Where a layout
draws the shell instead - a record with tabs - entering and leaving the
record animates and switching tabs does not, which is right: a tab is the
same place. default="none" keeps it still during anything else - a
filter written to the URL, a router.refresh() after a save. The
animations are connect-page-enter/connect-page-exit in theme.css.
Parameters
| Parameter | Type |
|---|---|
__namedParameters | Readonly<ConnectPageShellProps> |
Returns
Element
Function: ConnectSection()
function ConnectSection(__namedParameters: Readonly<ConnectSectionProps>): Element;One titled block inside a page: a card, its heading, what it is for, and the one control that acts on it.
The heading is an h3 - ConnectPageHeader renders the page title as an
h2, so a block inside a page is a level below it. Hand-rolled sections
reliably use h2, which makes every settings block a sibling of the page it
sits in and flattens the outline a screen reader announces.
As in the page header, the action is a peer of the title rather than of the block, so its position does not move with the length of the description.
Parameters
| Parameter | Type |
|---|---|
__namedParameters | Readonly<ConnectSectionProps> |
Returns
Element
Function: ConnectSettingRow()
function ConnectSettingRow(__namedParameters: Readonly<ConnectSettingRowProps>): Element;One labelled setting inside a ConnectSection.
Stacks on a phone and goes side-by-side from sm up, rather than shrinking
a two-column row until neither column is readable. The control keeps its
intrinsic width so truncation lands on the description.
Parameters
| Parameter | Type |
|---|---|
__namedParameters | Readonly<ConnectSettingRowProps> |
Returns
Element
Function: ConnectStatusChip()
function ConnectStatusChip<TStatus>(__namedParameters: Readonly<{
status: TStatus;
presentation: ConnectStatusPresentation<TStatus>;
className?: string;
}>): Element;One status, rendered the way every status should be.
Domain knowledge stays in the domain: each caller exports its own
ConnectStatusPresentation next to the union it describes, and this renders
it. A status whose rendering carries real logic - a scheduled cancellation
outranking a running trial, say - keeps its own component and calls this for
the final chip. A map cannot express precedence.
Type Parameters
| Type Parameter |
|---|
TStatus extends string |
Parameters
| Parameter | Type |
|---|---|
__namedParameters | Readonly<{ status: TStatus; presentation: ConnectStatusPresentation<TStatus>; className?: string; }> |
Returns
Element