Files
RenewCMS/docs/design/components.md
T

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

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