Files
RenewCMS/docs/design/components.md
T

12 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. 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