@nadicodeai/design-system 12.1.89 → 12.1.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +19 -7
- package/package.json +1 -1
package/DESIGN.md
CHANGED
|
@@ -368,7 +368,7 @@ The neutral spine carries grounds, chrome, text and every control: `{colors.ink}
|
|
|
368
368
|
- **Text**: `{colors.ink}` for headings and primary text, `{colors.body}` for supporting text, which clears AA on every canvas. `{colors.muted}` is for placeholders and disabled or inactive text only, never for descriptions, labels or help. These roles distinguish purpose, not a guaranteed contrast step: their similar neutrals keep supporting text and placeholders near the Geist reference while both meet our AA text floor.
|
|
369
369
|
- **Lines**: `{colors.line}` is the one decorative 1 px divider and is deliberately quiet. `{colors.input}` is the boundary of a field or any control whose edge identifies it (an input, a select, a checkbox, a radio), and clears the 3:1 non-text floor. A button is identified by its label, so an outline or secondary button's edge is the `{colors.line}` hairline, never `{colors.input}`. Never darken a decorative line locally to make it act as a control edge.
|
|
370
370
|
- **Focus** (`{colors.focus-ring}`): a full-opacity outline of `{spacing.focus-outline-width}` at `{spacing.focus-outline-offset}`, the maximum-contrast neutral on each ground, never a box-shadow ring and never a hue.
|
|
371
|
-
- **Interaction states** are contrast steps on the spine. Hover takes the `accent` role (`{colors.canvas-soft-2}`) and a selected row the `muted` role (`{colors.canvas-soft}`); a control's own hover is a step inside its family, as `{colors.action-hover}` is. Persistent navigation marks the current destination with `{colors.navigation-selection-bg}` and `{colors.navigation-selection-fg}`, and local tabs mark the current view with the same pair through the `selection` role.
|
|
371
|
+
- **Interaction states** are contrast steps on the spine. Hover takes the `accent` role (`{colors.canvas-soft-2}`) and a selected row the `muted` role (`{colors.canvas-soft}`); a control's own hover is a step inside its family, as `{colors.action-hover}` is. Persistent navigation marks the current destination with `{colors.navigation-selection-bg}` and `{colors.navigation-selection-fg}`, and local tabs mark the current view with the same pair through the `selection` role. A segmented choice, such as the theme switcher's tiles, marks its chosen option with that pair too, and the fill never comes without its foreground: on the `selection` ground, a radio's on-primary label reads white on near-white in light and black on black in dark.
|
|
372
372
|
- **Scrim** (`{colors.scrim}`): the modal overlay, black in both themes at the `scrim-opacity` material value, never blurred: the page behind steps back and the panel in front keeps its full contrast; an overlay never greys the page.
|
|
373
373
|
|
|
374
374
|
### Colour as stage
|
|
@@ -669,7 +669,7 @@ Every example and consuming surface covers its reachable populated, empty, loadi
|
|
|
669
669
|
| [Description](https://vercel.com/geist/description) | Component: `ObjectFacts`, `SettingsFactsSection`, `TableValue`. | [Card](https://ui.shadcn.com/docs/components/base/card) title/description or [Item](https://ui.shadcn.com/docs/components/base/item); semantic description list for key/value facts. | Key/value object facts in a semantic definition list. Static metadata is not a two-column comparison table. Key names the fact; value retains exact identifiers, links or copy action. Unknown uses accessible dash, zero remains zero. Long prose may use a block fact. |
|
|
670
670
|
| [Destructive Action Modal](https://vercel.com/geist/destructive-action-modal) | Component for ordinary confirmation: `ConfirmDialog`; gap for typed-name confirmation. | [Alert Dialog](https://ui.shadcn.com/docs/components/base/alert-dialog) + [Field](https://ui.shadcn.com/docs/components/base/field) and Input for typed verification. | Name object, irreversible effects and recovery before destructive command. Keep failed mutation open, preserve focus and prevent duplicate commit. Use the [ConfirmDialog owner](../ui/src/components/confirm-dialog.tsx) for ordinary confirmation. A typed-name gate for a high-stakes operation needs a shared extension before a surface promises it. |
|
|
671
671
|
| [Dots Menu](https://vercel.com/geist/dots-menu) | Component: `ActionMenu` and `DropdownMenuCommand`. | [Dropdown Menu](https://ui.shadcn.com/docs/components/base/dropdown-menu) + named icon Button. | One discoverable overflow trigger for secondary record actions. Name whose actions the [ActionMenu owner](../ui/src/components/action-menu.tsx) opens and supply real menu commands. Keep the target easy to hit and the menu inside the viewport. |
|
|
672
|
-
| [Drawer](https://vercel.com/geist/drawer) | Component: low-level `Drawer` family; `FormDrawer` owns
|
|
672
|
+
| [Drawer](https://vercel.com/geist/drawer) | Component: low-level `Drawer` family; `FormDrawer` owns the right-side drawer for a catalog or a setup in steps ([Menus and commands](#menus-and-commands)). | [Drawer](https://ui.shadcn.com/docs/components/base/drawer) pattern; keep our Base UI implementation. | Geist uses mobile bottom drawers; do not silently call our right-side form behavior identical. Choose one shared adaptive recipe if parity is adopted. Preserve dirty fields, visible footer actions, contained scroll, dismissal and focus return. Destructive decisions use a blocking confirmation. |
|
|
673
673
|
| [Empty State](https://vercel.com/geist/empty-state) | Component: [EmptyState](../ui/src/components/empty-state.tsx), following the [absence contract](#elevation--depth). | [Empty](https://ui.shadcn.com/docs/components/base/empty) + appropriate action. | Absence, unmatched filters and failed fetch are different states. First-use action exists only with permission and a real creation path. Search offers clear/reset; failure offers actual recovery. Put outside an empty table body. Permission/upgrade/cleared variants are not exported and must not be faked as noResults. |
|
|
674
674
|
| [Entity](https://vercel.com/geist/entity) | Component: `ItemGroup`, `Item`, `ItemMedia`, `ItemContent`, `ItemTitle`, `ItemDescription`, `ItemActions`; domain `CatalogEntry`, `AgentListRow`. | [Item](https://ui.shadcn.com/docs/components/base/item) with media, content and actions. | Descriptive row with identity, secondary detail and action, not sortable comparative columns. Use divided group for peers and one coherent hit target with independent nested controls. Skeleton matches row geometry. Primary direct action may remain visible; secondary commands use overflow. |
|
|
675
675
|
| [Error](https://vercel.com/geist/error) | Component: `FieldError`, `MessageNotice`, `EmptyState kind="failed"`, `FeedbackState` for existing owned surfaces. | [Field](https://ui.shadcn.com/docs/components/base/field) error or [Alert](https://ui.shadcn.com/docs/components/base/alert) at the relevant scope. | Put field validation by field, action failure by action, failed read where content would be. State what failed, retained work and concrete recovery. Catalog-resolved messages own product wording. Diagnostic identifier only when available; never raw exception or fake error ID. Async failure is announced without stealing useful input. |
|
|
@@ -729,7 +729,7 @@ These shared components also follow the same foundations, although Geist does no
|
|
|
729
729
|
| `Sidebar`, `DashboardShell`, `DesktopShell`, `PortalSidebarFrame` | Persistent product navigation and workspace chrome. The product owns destinations and active state; the shell owns compact behavior, landmarks and layout. Never add a competing sidebar to a page. |
|
|
730
730
|
| `NavigationMenu` | A site's grouped navigation links, with genuine destinations and usable keyboard paths. Product object views use shared tabs; commands use a menu. |
|
|
731
731
|
| `Menubar` | A persistent application command bar with related menus. Use only when the workspace has that command model; do not substitute it for website navigation or a row's actions. |
|
|
732
|
-
| `Popover` | A bounded contextual control such as filters or a scope chooser. Name its trigger, keep essential meaning visible outside hover, and restore focus when it closes.
|
|
732
|
+
| `Popover` | A bounded contextual control such as filters or a scope chooser. Name its trigger, keep essential meaning visible outside hover, and restore focus when it closes. Which form surface a job takes is decided in [Menus and commands](#menus-and-commands). |
|
|
733
733
|
| `InputOTP` and its group/slot parts | A fixed-length verification code. Keep one logical input with paste and keyboard behavior, a visible label, invalid/retry state and a real resend policy. The product owns length, expiry and submission. |
|
|
734
734
|
| `Carousel` | A finite horizontal rail where adjacent media or items have a meaningful sequence. Give previous/next controls clear names and boundaries; preserve keyboard access and reduced motion. A sortable record collection uses a table or entity list. |
|
|
735
735
|
| `ResizablePanelGroup`, `ResizablePanel`, `ResizableHandle` | A workspace with genuinely adjustable panes. Keep keyboard resizing, meaningful minimum sizes, readable content and a compact alternative. The product owns persistence of pane sizes. |
|
|
@@ -749,11 +749,12 @@ Choose the owner for the whole task. Keep its slots and state handling together
|
|
|
749
749
|
| --- | --- | --- |
|
|
750
750
|
| Independently saved settings | `ReadingColumn` → `SettingsSection` → `SettingsField`; Portal selects the configuration width and form presentation through `SettingsPage` and its `SettingsSection` adapter. `SettingsForm` and `SettingsSubmitButton` own native form dirty/pending state. `footerHint` and `footer` stay in the recessed strip. | Values, validation, dirty/pending state, local Save, retained input and failure. Header and content share one body plane; the footer has one seam. |
|
|
751
751
|
| Read-only facts | `SettingsFactsSection` or `ObjectFacts`, with `TableValue` for missing facts. | Label/value pairs, actual timestamps, identifier format, links and permission-aware actions. Unknown remains distinct from zero. |
|
|
752
|
+
| Entity collection | `CollectionPage`: the title's actions, search and sort, then rows that identify themselves (`RecordIdentity` with its `detail`, a status only for the exception, `RecordTime`), the row's command or menu, an action's failure inside its row, the edit inside its row, and its loading, empty and no-match states. | The records, what each one says, its commands as data, its catalogue message when an action fails, and the edit form. Never its own toolbar, table, rows or menus. |
|
|
752
753
|
| Comparable collection | `PageHeader`/`PageActions`, `CollectionToolbar`, `DataTable` within `TableCard` when framed. | Stable record IDs, columns, visible headings, search/filter/sort, URL state, row commands, pagination and `empty`. Numeric columns align at the trailing edge; totals and paging use their own footer zone. |
|
|
753
754
|
| Descriptive entity list | `ItemGroup divided` with `ItemMedia`, `ItemContent`, `ItemTitle`, `ItemDescription`, `ItemActions`; domain owners include `AgentListRow` and `CatalogEntry`. | Identity, a short description, categorical facts, one primary task and secondary menu. Long values wrap or truncate predictably; a row never gains a second nested hit target by accident. |
|
|
754
755
|
| Overview figures | `CardRow` with `KpiCard` or the chosen chart card. | Measurement, unit, comparison period, truthful delta and stable categorical assignment. Related cards align; status colour and series colour retain different meanings. |
|
|
755
756
|
| Object workspace | `ObjectOverview`, `ObjectIdentity`, `ObjectFacts`, `ObjectSection` and shared tabs. | The actual object, scope, hierarchy, sections, routes and commands. Agent identity retains its orb and the Agent-specific composition. |
|
|
756
|
-
| Short
|
|
757
|
+
| Short form: a new record, or one or two values from a command | `FormDialog` with Field parts; Portal binds it through `ActionDialogForm`. A record in a list edits inside its row (Entity collection); a catalog or a setup in steps uses `FormDrawer` ([Menus and commands](#menus-and-commands)). | Submit operation, labels, validation, dirty state, retained input, permissions and dismissal policy. Use the owned scroll body and fixed footer; do not nest another overlay. |
|
|
757
758
|
| Destructive confirmation | `ConfirmDialog`; the app's confirm-action adapter. | Named target, real consequence, labels, pending state, failure and operation. Typed-name verification is not a supported prop; extend the owner when that capability is needed. |
|
|
758
759
|
| Global commands or record actions | `CommandDialog` for cross-resource search; `ActionMenu` and `DropdownMenuCommand` for one record. | Real commands, grouping, permissions, disabled reasons and keyboard shortcuts. Choose by scope; keep the destructive group last. |
|
|
759
760
|
| Absence or failed read | `EmptyState` through `DataTable.empty` or the affected content region; `MessageNotice` for persistent conditions. | First use, nothing yet, no matches or failed read, with the real recovery action. Empty state belongs outside the semantic table body. |
|
|
@@ -788,16 +789,27 @@ Keep Geist's hierarchy, control metrics, typography roles, neutral surface zones
|
|
|
788
789
|
|
|
789
790
|
### Menus and commands
|
|
790
791
|
|
|
791
|
-
A record's primary destination is its visible name or a labelled link. Navigation is never hidden in its action menu. Keep the main task visible: Edit
|
|
792
|
+
A record's primary destination is its visible name or a labelled link. Navigation is never hidden in its action menu. Keep the main task visible: Edit opens the record inside its row, as in Vercel Domains; Connect belongs beside an available connection. Additional mutations use an end-aligned `ActionMenu`, with destructive commands after a separator. A menu is not required when the row has no secondary mutations. Disabled commands retain an explanation. The three-dot trigger is a `{spacing.control-height}` ghost icon button. Menu rows put the label first and a decorative icon at the trailing edge; a destructive command never becomes the loudest repeated action.
|
|
792
793
|
|
|
793
|
-
Choose the surface
|
|
794
|
+
Choose the surface by where the thing being changed is shown and how big the job is. Each surface has one owner:
|
|
795
|
+
|
|
796
|
+
| The person wants to | Surface | Owner | Example |
|
|
797
|
+
| --- | --- | --- | --- |
|
|
798
|
+
| Change a record shown in a list | Its row opens into the form, with Cancel and Save in the form's footer; the other rows stay where they are | `CollectionPage`'s `editor` with `RowEditForm` | Vercel Domains › Edit; a connected channel's Settings |
|
|
799
|
+
| Change a fact on a record's own page or card | In place: a pencil turns the fact into its field, saved by the card's Save | `AgentProfileCard`; `SettingsSection` on a settings page | An Agent's name and description |
|
|
800
|
+
| Add a record through a few fields, or change one or two values from a command | A centred dialog | `FormDialog`; the Portal's `ActionDialogForm` | Add password, Add MCP server |
|
|
801
|
+
| Choose from a catalog, or set up in several steps with a wait | A drawer at the right edge, the page still behind it | `FormDrawer` with `job="catalog" \| "steps"` | Vercel Domains › Add; Add skill from the Library; Telegram's QR setup |
|
|
802
|
+
| Do a workflow a person could link to | Its own page | The page's composition | Vercel Add New › Project; New Agent |
|
|
803
|
+
| Confirm a consequence | A confirmation | `ConfirmDialog` | Remove, Disconnect |
|
|
804
|
+
|
|
805
|
+
A record already on screen is never edited in a dialog or a drawer, and a drawer is never chosen because an action takes input: `FormDrawer` requires a `job`, `"catalog"` or `"steps"`, and owns its one width, 560 px (the full width on a compact screen), which no caller sets. A settings page is the Configuration composition. These choices follow the live Vercel Domains, Projects and settings pages and the [Geist Modal](https://vercel.com/geist/modal) and [Fieldset](https://vercel.com/geist/fieldset) patterns.
|
|
794
806
|
|
|
795
807
|
Collection search and filters stay immediately above their rows. The title row holds the collection's primary action, unless an inline form already exposes that task. Scope selectors preserve the authorized organization or account. Filters do not compete with primary actions. The account menu identifies the signed-in person and exposes their account settings, theme and language.
|
|
796
808
|
|
|
797
809
|
|
|
798
810
|
### Tables and missing values
|
|
799
811
|
|
|
800
|
-
A table is native table structure: 40 px rows for comparable facts and 48 px rows for entity collections. Visible headings use 14 px / 20 px at weight 500 in a 36 px row; numeric columns align to the trailing edge with tabular figures. A collection row is one line: the record's identity and at most about five facts, and anything more lives on the record's page. Column headings are visible by default and remain visible when people compare or sort values. A compact entity collection whose cells identify themselves may keep accessible headings visually hidden with `showColumnHeaders={false}`; choose this exception deliberately. A simple descriptive list uses `ItemGroup` and `Item` instead of table semantics. The table inherits its enclosing surface, whose Card owns the outer edge; a visible header and every record except the last end with a quiet 1 px `border` divider. An interactive body row takes the full `accent` fill on hover, on keyboard focus within it and while its menu is open; a passive table does not imply an action on hover; a selected row takes `muted`; headers and footers never highlight. A collection fits its container without sideways scrolling: identity and actions keep their columns, and supporting facts move into a detail row inside the same record before the table gets cramped. The identity column claims up to a third of the table, gives supporting columns more room in a wide comparison, gives it back down to a floor, and holds its name on one line: a long name ends in an ellipsis and is whole in its title, as a dashboard row does. A fact holds one line at its own width; a status sentence in a figure column wraps at word boundaries while the figure keeps its numeric alignment. A column of words, such as a summary or a sentence, holds one line up to a cap of `16rem`, shrinks to a floor of its own in a narrow table, and past the cap ends in an ellipsis, whole in its title, as a long name does. A document's table, such as a legal page's, is read whole instead: its columns take the `wraps` option, so their words wrap and the row grows, and it shows its header row. In the compact layout every cell but the identity wraps between words. Text anywhere in the product breaks between words, and inside a word only when that word alone cannot fit a line; a machine string such as a hash or a key may break at any character. One categorical fact in a row, such as an installation or a partner, is a `Tag`: a 24 px pill with its mark, dashed when the fact states an absence. An app in a row is its brand mark in the 28 px tile.
|
|
812
|
+
A table is native table structure: 40 px rows for comparable facts and 48 px rows for entity collections. Visible headings use 14 px / 20 px at weight 500 in a 36 px row; numeric columns align to the trailing edge with tabular figures. A collection row is one line: the record's identity and at most about five facts, and anything more lives on the record's page. Column headings are visible by default and remain visible when people compare or sort values. A compact entity collection whose cells identify themselves may keep accessible headings visually hidden with `showColumnHeaders={false}`; choose this exception deliberately. `CollectionPage` (`@nadicodeai/ui/components/collection-page`) is the entity collection whole, and it makes that exception by drawing rows that identify themselves: the record's name with one muted line saying what it is or where it comes from (`RecordIdentity`'s `detail`, such as "Company skill · every Agent has it"); a status only when it tells the row apart from the others; a time in its verb's sentence (`RecordTime`: "Updated 3 hours ago", the date after a week, the exact moment in its title); one command that is not destructive as a button and otherwise the row's menu; an action that failed or was refused as its catalogue message inside its own row, with its next step, never a notice under the row; and an edit open inside its row. A screen gives it the records and what each one says, never its own toolbar, table, rows or menus. A simple descriptive list uses `ItemGroup` and `Item` instead of table semantics. The table inherits its enclosing surface, whose Card owns the outer edge; a visible header and every record except the last end with a quiet 1 px `border` divider. An interactive body row takes the full `accent` fill on hover, on keyboard focus within it and while its menu is open; a passive table does not imply an action on hover; a selected row takes `muted`; headers and footers never highlight. A collection fits its container without sideways scrolling: identity and actions keep their columns, and supporting facts move into a detail row inside the same record before the table gets cramped. The identity column claims up to a third of the table, gives supporting columns more room in a wide comparison, gives it back down to a floor, and holds its name on one line: a long name ends in an ellipsis and is whole in its title, as a dashboard row does. A fact holds one line at its own width; a status sentence in a figure column wraps at word boundaries while the figure keeps its numeric alignment. A column of words, such as a summary or a sentence, holds one line up to a cap of `16rem`, shrinks to a floor of its own in a narrow table, and past the cap ends in an ellipsis, whole in its title, as a long name does. A document's table, such as a legal page's, is read whole instead: its columns take the `wraps` option, so their words wrap and the row grows, and it shows its header row. In the compact layout every cell but the identity wraps between words. Text anywhere in the product breaks between words, and inside a word only when that word alone cannot fit a line; a machine string such as a hash or a key may break at any character. One categorical fact in a row, such as an installation or a partner, is a `Tag`: a 24 px pill with its mark, dashed when the fact states an absence. An app in a row is its brand mark in the 28 px tile.
|
|
801
813
|
|
|
802
814
|
An unknown or not-applicable value in a table, a comparison list or a set of facts is a muted `—` with screen-reader text, in the same slot as a present value; zero stays zero. An absent report is not `None`: `None` is only for a complete report that found nothing. A stale value keeps its last reading with a short `Stale` qualifier. Loading, failure, missing permission and actionable setup each keep their own visible state and never turn into a dash. Essential reasons and actions never live only in a hover tooltip.
|
|
803
815
|
|