Design system

Details dialog

Edits the name, description, folder and tags of a library component or template, which is what the agent reads to choose it.

import { DetailsDialog } from "@/pages/library/parts";
import "@/pages/library/library.css";

DetailsDialog is the form behind "Edit details, folder and tags" on a workspace card and behind "Save as template" in the page builder. Its fields are not only labels for people: the agent reads the name, description, folder and tags when it decides which component or template to build a page from. The hint under the fields says so.

It is a medium Dialog (650px) inside the library's scrim, with its own state for each field. It closes on Escape, on a click outside, on the close icon and on Cancel.

Editing a component

Everything can change. The folder buttons under the fields set the folder in one click, and the one that matches the folder field turns blue.

{editing && (
  <DetailsDialog
    title="Component details"
    item={editing}
    folders={folders}
    onClose={() => setEditing(null)}
    onSave={(details) => { updateDetails(editing.uid, details); toast.success("Saved."); }}
  />
)}

onSave gets { name, description, folder, tags }. Text is trimmed, and tags are split on commas, trimmed, lowercased and de-duplicated by parseTags in src/mocks/library.js. The dialog then calls onClose itself, so onSave does not have to close it.

A template from the library

With fixed, the name and description are hidden. A template taken from the library keeps the library's wording, so only its folder and tags are the workspace's to change. onSave then gets only { folder, tags }.

<DetailsDialog
  title="Template details"
  noun="template"
  item={template}
  folders={folders}
  fixed={!template.saved}
  onClose={close}
  onSave={(details) => updateTemplate(template.id, details)}
/>

Saving a page as a template

The page builder reuses the dialog with its own confirm label and hint, and a name worked out from the page's name.

<DetailsDialog
  title="Save as template"
  noun="template"
  confirmLabel="Save template"
  item={{ name: `${page.name.replace(/ page$/i, "")} template`, description: "", folder: "", tags: [] }}
  folders={folders}
  hint="The page is saved as it is now. Describe when to use it: the agent reads the name, description, folder and tags to pick a template."
  onClose={() => setSaving(false)}
  onSave={(details) => library.saveTemplate(details, page.sections)}
/>

States

StateWhat happens
Name emptyThe Name field shows its error, "Give the component a name." (or "template"), as soon as it is empty. Save does nothing until a name is typed.
fixedName and description are hidden. Save always works.
No folders yetWith an empty folders list, the "Your folders" row is hidden.
Folder pickedThe folder button that matches the Folder field, after trimming, gets .lib-tag--on.

The error shows the moment the field is empty, before anyone has tried to save, and the Save button stays enabled and silently does nothing. The Dialog panel has no way to disable Confirm in its default state, which is why the check lives in onConfirm.

Layout

PartStyle
PanelDialog at md, 650px, with .lib-dialog .lib-dialog--form: body fields stretch to full width with a 16px gap.
Name, DescriptionA TextInput and a three-row TextArea. Placeholders follow noun: "Component name", "When should this component be used?".
Folder and TagsTwo TextInputs side by side in .lib-dialog__row, a two-column grid with a 16px gap. Tags placeholder: "demo, webinar".
Your folders.lib-card__filing with a 12px label and one .lib-tag button per folder.
Hint.lib-dialog__hint, 12px in --color-grey-500.
FooterCancel and Save, both 32px tall. .lib-dialog .dialog__footer .btn brings them down from the Dialog's usual 36px at this size.
Scrim.lib-dialog__scrim: fixed, z-index: 80, 16px padding, rgba(31, 33, 46, 0.45).

Props

PropTypeDefaultNotes
titlestringThe dialog title and its accessible name.
nounstring"component"Used in placeholders, the error and the default hint.
item{ name?, description?, folder?, tags? }Required. The starting values. Read once, when the dialog mounts.
foldersstring[][]Folder shortcuts. Hidden when empty.
fixedbooleanfalseHides name and description.
confirmLabelstring"Save"
hintstringReplaces the default line about what the agent reads.
onSave(details) => voidRequired. Gets { name, description, folder, tags }, or { folder, tags } when fixed.
onClose() => voidRequired. Escape, scrim click, close icon, Cancel and after a save.

Accessibility

  • The wrapper has role="dialog", aria-modal="true" and aria-label set to title.
  • The Name field has autoFocus, so focus lands there when the dialog opens. With fixed nothing is focused, and focus stays on the button that opened it, behind the scrim.
  • The name error is linked to the field with aria-describedby and announced with role="alert".
  • Escape closes it through a keydown listener on window.
  • Focus is not kept inside the dialog and is not returned to the trigger on close. See Dialog for the wrapper that does both.

Do and don't

  • Do write the description as when to use the component ("Use when the only goal is a booked demo"). That sentence is what the agent follows.
  • Do pass folders so people reuse folder names instead of typing near-duplicates.
  • Do use fixed for anything whose wording belongs to the library.
  • Don't use this dialog for page settings or publishing. Those have their own dialogs: Page settings and Publish.

When to use it

Use it for any library item the agent chooses from by description. For a plain rename or a single field elsewhere, use a small Dialog from @/ui with one TextInput.

Where it is used

FileTitleNotes
src/pages/library/LibraryPage.jsx, WorkspaceComponents"Component details"Saves through updateDetails in useLibraryStore.js.
src/pages/library/LibraryPage.jsx, Templates"Template details"fixed for library templates, editable for templates saved from a page.
src/pages/library/PageBuilderPage.jsx"Save as template"Custom confirmLabel and hint. Saves through saveTemplate.

On this page