Files
RenewCMS/docs/design/components.md
T

16 KiB
Raw Blame History

Components

Frames: AppFrame.dc.html (shell: sidebar, topbar, mobile nav drawer, modal, toasts, articles, editor, users, settings, profile), ActivityFrame, MediaFrame, VariantsFrame (page bodies only), IntegrationPage, IntegrationImages, ApiKeysPage, CodeSnippet, StatesMatrix. The page-body frames mount inside AppFrame through two props (slotBody, slotOverlays) so there is one sidebar, one topbar and one drawer.

Overlay pattern (one rule, applies everywhere)

Kind Desktop Mobile (390) Use
Dialog Centered horizontally, 140 px from the top, width 460 (560 wide forms, 640 crop), --rc-shadow-modal Bottom sheet: full width, flush to the bottom, grabber (36×4, --ui-border-accented) Confirmations and forms with up to about four fields
Slide-over Right panel, full height, over a scrim (Variant form 960 wide with live preview, Media detail, Activity details 480) Tall bottom sheet, 92% height, grabber, close ×, sticky footer. Never a full page Only forms with a live preview, and record details. Nothing else may be a slide-over
Menu Popover under the trigger Bottom sheet with a title and a close × Action lists (Export, row menus on mobile)

Dialog anatomy: header (title 16/24 weight 600, optional description in --ui-text-muted, close × 28 px desktop / 44 px mobile), body (padding 20 24 0, gap 16), footer (padding 20 24). Footer order: secondary on the left, primary on the right, aligned right. On mobile the buttons stack with the primary on top, 44 px high. Destructive confirms use --ui-error fill with --ui-bg text and the verb of the action ("Delete image", "Revoke key"). The scrim is --rc-overlay. Esc, the scrim and swipe close unless an action is in flight. In flight, the primary shows loader-circle and the verb in -ing form; Cancel is disabled; the dialog cannot be dismissed. Focus is trapped and returns to the trigger. Cancel is replaced by an explicit label where "Cancel" would read as the action: "Keep invite", "Keep key", "Restart later". Redrawn to this pattern in pass 2: variant form (mobile full page became a sheet), Activity export dialog (was centered, with a header bar and a 480 wide box), API keys dialogs (bordered box, 18 px title, 44 px buttons, 120 px offset), all Media and Variants dialogs (mobile floated with a 16 px inset; now flush sheet with grabber).

Unified in pass 2

  • Sidebar: ActivityFrame, MediaFrame and VariantsFrame each had their own copy (VariantsFrame's with the Settings group, MediaFrame's with a raw colour). Removed. AppFrame's is the only one. Prototype's own mobile drawer was replaced by AppFrame's drawer (s.drawer).
  • Topbar (breadcrumb on desktop, title and lead button on mobile): three copies removed; AppFrame's is used.
  • Mobile nav drawer: VariantsFrame's drawer removed; AppFrame renders one (with the Settings > Media group).
  • Code block: Integration endpoints, Authentication example, error response, API keys (one-time key), base URL field and the image URL builder all use CodeSnippet.
  • Modal geometry: AppFrame, MediaFrame, VariantsFrame, ActivityFrame (export) and ApiKeysPage now draw the same dialog and sheet.
  • Toast width: 356 px desktop everywhere (was 356, 360, 372). Still separate implementations that look the same (not merged in code, see README "Not done"): toast (5 copies), badge (Bd in MediaFrame and VariantsFrame), empty state (MediaFrame and VariantsFrame), modal shell (AppFrame template and the JS shells).

CodeSnippet

One component for every code or copyable value. Props: tabs ({ k, label, code }[]), active, copied. A header row (min 44 px) holds the tabs and the Copy button; selected tab has a 2 px --ui-primary underline and --ui-text-highlighted text. With one tab the label is plain muted text (no tab). Copy reads "Copy", then "Copied" with check for 2 s (--ui-success), announced politely. Body is pre, IBM Plex Mono 12/19, scrolls horizontally, tabindex="0". No syntax colouring. Background --ui-bg-elevated, border --ui-border.

  • Toolbar button (icon link, after the list buttons): default --ui-text-toned; hover --ui-bg-elevated with tooltip "Insert link (Ctrl+K)" (macOS: "Insert link (⌘K)"); focus-visible 2 px --ui-primary outline; active when the cursor is inside a link (aria-pressed, --rc-sand and --rc-on-sand); disabled at 40% opacity when an image is selected. 28 px desktop, 40 px mobile.
  • LinkModal: dialog on desktop, bottom sheet on mobile. Fields "Link text" (prefilled from the selection), "URL" (http, https, mailto, relative path, #anchor), toggle "Open in new tab". Footer: Cancel left, "Insert link" or "Save link" right; "Remove link" as a text button in edit mode. Errors: "Enter the link text." and "Enter a valid URL. Use http(s), mailto, a path or #anchor."
  • Link popover (desktop): under the link, 288 px, URL in mono on top, then "Edit", "Open", "Remove" (error colour). Opens on click or Enter on a focused link; Esc closes. Touch: bottom sheet with the link text, the URL and three 48 px rows "Edit link", "Open link", "Remove link". Remove needs no confirmation (undo restores).
  • Rendered link: --ui-text-highlighted, 1 px underline, 3 px offset. Hover 2 px underline. Focus 2 px --ui-primary outline, offset 2. Selected: --rc-sand fill, --rc-on-sand text.
  • Toolbar at 390 px: the full toolbar (22 buttons) does not fit. It scrolls sideways in one row, never wraps; a 24 px fade (--ui-bg to transparent) marks hidden content on each side. Buttons stay 40 px.

Paginator

First, previous, page numbers, next, last; "Showing 26–50 of 312"; page-size select "5 per page", "25 per page", "50 per page", "100 per page" (default 25). Mobile: previous, "Page 2 of 13", next. Hidden when there is one page. Desktop layout, one row: the summary on the left (it takes the free space), the page-size select next to it, the page controls right-aligned; Sites, Authors, Articles and Media draw it the same way. Buttons 32 px (44 px mobile), current page --rc-sand with --ui-primary border, disabled controls 40% opacity, focus ring 2 px --ui-primary. Page size and page live in the URL as size and page.

Variant for Sites and Authors (no new component): when everything fits on one page the page-size select is hidden together with the controls and the footer shows only the count line "N sites" or "N authors" in --ui-text-muted. Loading dims the whole pager to 40% and disables it; the summary reads "Loading page N…". Sizes are unchanged: 5, 25, 50, 100.

Activity export

Menu: "Export current view" (CSV, active filters, all pages) and "Export all activity…" (CSV, ignores filters; owner and admin only). Dialog: Format (read-only "CSV"; a CSV and JSON control appears when JSON ships, proposed), Date range (default "All time"). States: configure, "Preparing download…" (indeterminate bar, no percentage, Cancel left, "Preparing…" disabled right), "Download started" (Close), "Couldn't prepare the download" (Cancel, Retry), "Nothing to export" (Close), 403 "You can't export all activity" (Close). Synchronous: no job, no progress, no later notification.

FilterCardToggle

Button with aria-pressed; pressed uses --rc-sand and --rc-on-sand with a --ui-primary border; pressing again clears. Synced with the Outcome segment and the chip.

NavItem and NavGroup

Row 32 px (44 px in the mobile drawer). Parent selected: --rc-sand, --rc-on-sand, weight 500. Child selected: parent unfilled, --ui-text-highlighted, weight 500, chevron-down; child filled as above. Group label "Media" 11 px uppercase --ui-text-muted. Children indent 19 px with a 1 px --ui-border rail.

Media badges (Media library, picker, Variants board)

Unused (warning soft, icon unlink), Used in N (outline), Duplicate (info soft, icon copy), N variants (info soft, icon layers; "1 variant"; none shown at 0). The Media grid card, the list row and the mobile row show all of them. Size column and "Size" meta use the original file size only; the "with variants" size and the sort are on the Variants board (see README, Not done).

Other

  • PurgeDuplicatesDialog: UModal, sm:max-w-md, sheet on mobile, cancel first, confirm color="error". Props open, preview, status (loading, ready, purging, nothing, error, partial, conflict). Emits purged.
  • MediaCleanupCard: square USwitch, UInputNumber with "days" suffix, saves on change.
  • MediaLinksCard: rows "Manage image variants" and "Cache usage", 44 px min height.
  • StatesMatrix: the board "States matrix: controls".

Sites and authors (pass 3)

Frames: DirectoryFrame.dc.html (Sites, Authors, editor section content; the list, slide-over, delete-with-reassign dialog, slug field and media field are defined once there and used by both Sites and Authors); AppFrame.dc.html changed in place (sidebar items, Articles columns and filter, editor section and mobile details sheet, Settings card, Profile card, role-based sidebar). Existing components are reused unchanged: dialogs, toasts, empty states, skeletons, paginator, media picker.

Component Status Nuxt UI Notes
SitesTable, AuthorsTable New UTable; below 640 px a list of UCard Same row heights as Users. Defined once as DirectoryList in DirectoryFrame and used by both. Read-only variant has no actions, chevron instead of the menu
SiteForm, AuthorForm New USlideover + UForm, UFormField, UInput, UTextarea, URadioGroup, USwitch Sheet on mobile. Close with changes opens the discard UModal
SlugField New UInput + UButton (icon) + UBadge Defined once in DirectoryFrame. Auto badge, pencil, "Reset to match the name". Locked read-only variant (lock icon, "Used in API paths, so it can't change.") for the site key after creation
SocialLinksRepeater New USelect + UInput + UButton, in UFormField Max 5 rows. Placeholder follows the type
MediaField New UButton + UModal (existing picker) Defined once in DirectoryFrame. Used for the social image and the avatar
AuthorViewPanel New USlideover, read-only Detail for roles without author rights. Close is the only action
AuthorAvatar New UAvatar Initials fallback, square, radius 0
AuthorStack New UAvatarGroup max=3 Shows "+N"
AuthorList New List of UButton, UAvatar, UBadge; sorting with useSortable No Nuxt UI match for sortable rows. Keyboard reordering is custom
AuthorSelect New USelectMenu multiple with create-item Footer item "Create author…" opens the dialog
SiteSelect New USelectMenu Item slot shows name and host
UserSelect New USelectMenu Only users without an author
ReassignDialog New UModal + USelectMenu + UAlert Defined once in DirectoryFrame. Used for site and author deletion
MetaSection New UCollapsible Desktop sidebar and mobile sheet. Site select for anyone who can edit the article. No "Create author…" row without author rights
AuthorProfileCard New UCard + UButton + UAlert Profile screen. Defined once in AppFrame; every Profile frame shows it
CreateAuthorHint New UAlert closable Dismissal stored per user
Default badge, Linked badge, Guest badge New UBadge Sand fill, info soft fill, outline
Row menu with a disabled item Existing + change UDropdownMenu + UTooltip The item stays focusable
SearchSharingSection New UCollapsible + UButton + USeparator Editor sidebar and Details sheet, after Site and authors. Summary: Ready, N suggestions, Hidden from search, Checking…
SeoPanel New USlideover (desktop 960 px) / bottom sheet (mobile) + UFormField, UInput, UTextarea, USwitch, UCollapsible Defined once in DirectoryFrame. Reuses SlugField, MediaField and the picker. Fallback chip: dashed outline, "Using …"
SoftCounter New text Mono, "n / max". Amber (--ui-warning) outside the recommended range, never red, never blocks
SearchPreview New UCard Desktop and Mobile toggle (UTabs, 2 items). Title cut at 60 (desktop) or 70 (mobile) characters including the template, description at 160 or 120
SocialCardPreview New UCard 1.91:1 image, host, title, 2-line description; fallback chips
SuggestionList New UAlert-style rows + UButton (link, ghost icon) Calm elevated block. States: list, all good, checking. Per-article dismiss; "Show dismissed"
ApiPreviewDialog New UModal / bottom sheet + UTabs + UButton + UAlert Formatted and Raw; Copy JSON; loading, error, draft note
SeoStatusChip New UBadge Lightbulb + count (warning soft) or eye-off "Hidden" (outline). Articles rows and cards
NeedsAttentionToggle New UButton (pressed state) Articles toolbar, sand when on
TitleTemplateField New UInput + live example Sites form

Custom content types (pass 4)

Frames: SchemaFrame.dc.html (all schema, content and entry bodies; relation, permissions and import frames are Later), mounted in AppFrame.dc.html. Existing pieces are reused unchanged: dialogs and sheets, toasts, alerts, paginator, skeletons, empty states, SlugField, CodeSnippet, the single switch.

AppFrame changes. Sidebar: a "Schema" item (icon shapes) after Users, owner and admin only. A "Content" group under the main items, one row per custom type (default icon files, name truncated with the full name in the tooltip, selected row sand). Over 5 types it shows the first 5 and the selected one, then "Show all N". Mobile drawer: the group is one collapsed row "All content types" with the count; expanded it lists the types at 44 px. New props on the shell state: content (types), contentOn, contentOpen. The group is hidden when there are no custom types. No existing item changed.

Component Status Nuxt UI Notes
SchemaTable New UTable; below 640 px a list of UCard Same pattern as SitesTable. Built-in rows add the lock and a Built-in badge
BuiltInBadge New UBadge outline + lock icon Existing badges have no lock; same size as the Linked badge
FieldList New List of UButton rows, useSortable, UBadge No Nuxt UI sortable list. Keyboard: the grip is a button; Space picks up, arrows move, Space drops, Esc cancels. Move up and Move down buttons on the selected row. Same approach as AuthorList
FieldInspector New UForm, UFormField, UInput, UInputNumber, UInputDate, USelect, UTextarea, USwitch Composition only. Controls follow the field type
FieldTypePicker New URadioGroup variant="card" in a grid, icon in the label Existing radio cards fit; only the icon and the lock state are added
KeyField Existing SlugField (UInput + UButton + UBadge) Reused for the type key and field keys. Locked variant already exists
ChoicesEditor New UInput + UButton rows, useSortable UInputTags can't reorder or flag a blank or duplicate per row
ApiPreview New UCollapsible + CodeSnippet + UButton CodeSnippet is reused. Sheet on mobile
ChangeReview New UModal (sheet on mobile), UBadge, list rows Dialog pattern unchanged. Rows reuse the UAlert-style row of SuggestionList
SchemaRefusal, DeleteTypeDialog New UModal + UAlert Dialog pattern unchanged. Delete uses color="error"
ContentNavGroup New UNavigationMenu group "Content" (NavGroup) Adds the 5 + selected cap, "Show all N" and the collapsed mobile row
EntryTable New UTable with column visibility Cells per field type (FieldCell). Cards on mobile
ColumnPicker New UPopover + UCheckbox list; bottom sheet on mobile UDropdownMenu closes on each pick and has no cap message
SortSelect New USelect + UButton icon ID, Created and Updated only
FieldControl New UInput, UInputNumber, UInputDate, USelect, USwitch (the single toggle), MarkdownField One switch per field type, generated from the schema
MarkdownField New UTextarea + UTabs (Write, Preview) + toolbar of UButton The article editor is a full document editor. This is a compact one
FieldCell New text, UBadge outline (choice), icon + text (yes or no) Renders one value per type, with the empty dash
EntryForm New UForm generated from the schema Provenance footer, schema-changed alert
RelationPicker (later) New URadioGroup card + USelect Sentence preview is text
RelationField (later) New UInputMenu multiple Chips and search
PermissionsGrid (later) New UTable + UCheckbox; UCard list on mobile
SchemaImportDialog (later) New UModal + UFileUpload