Define semantic page archetypes

This commit is contained in:
2026-08-19 14:26:25 +02:00
parent c042244da8
commit 41db78c201
18 changed files with 315 additions and 87 deletions
+32 -2
View File
@@ -22,6 +22,13 @@ import StatePanel from "../src/components/StatePanel";
import WorkspaceLayout from "../src/components/WorkspaceLayout";
import WorkspaceFrame from "../src/components/WorkspaceFrame";
// @ts-expect-error Refreshable pages must provide a Reload action.
const refreshableWithoutReload = <PageActionBar variant="detail" refreshable />;
// @ts-expect-error Editor pages must declare dirty state and persistence actions.
const editorWithoutPersistence = <PageActionBar variant="editor" />;
void refreshableWithoutReload;
void editorWithoutPersistence;
const toolbarMarkup = renderToStaticMarkup(
<PlatformLanguageProvider>
<ActionToolbar label="Object actions" density="compact" justify="between" surface="subtle" interfaceId="object.toolbar">
@@ -44,26 +51,49 @@ const editorActionBarMarkup = renderToStaticMarkup(
<PlatformLanguageProvider>
<PageActionBar
variant="editor"
refreshable
dirty
label="Editor actions"
reloadAction={<button type="button">Reload</button>}
contextActions={<button type="button">Preview</button>}
helpAction={<button type="button">Help</button>}
discardAction={<button type="button">Discard</button>}
saveAction={<button type="button">Save</button>}
destructiveActions={<button type="button">Delete</button>}
discardAction={{ label: "Discard" }}
saveAction={{ label: "Save" }}
/>
</PlatformLanguageProvider>
);
assert(editorActionBarMarkup.includes('data-page-action-archetype="editor"'), "page action bars expose their semantic archetype");
assert(editorActionBarMarkup.includes('aria-label="Editor actions"'), "page action bars are named toolbars");
assert(editorActionBarMarkup.includes('data-page-refreshable="true"'), "refreshable pages expose their refresh contract");
assert(editorActionBarMarkup.includes('data-page-dirty="true"'), "editors expose their dirty state");
assert(editorActionBarMarkup.includes('data-page-dirty-state="dirty"'), "dirty editors announce unsaved changes");
assert(editorActionBarMarkup.indexOf('data-page-action-slot="reload"') < editorActionBarMarkup.indexOf('data-page-action-slot="context"'), "reload precedes contextual actions");
assert(editorActionBarMarkup.indexOf('data-page-action-slot="help"') < editorActionBarMarkup.indexOf('data-page-action-slot="discard"'), "help precedes editor persistence actions");
assert(editorActionBarMarkup.indexOf('data-page-action-slot="destructive"') < editorActionBarMarkup.indexOf('data-page-action-slot="discard"'), "destructive editor actions are separated from persistence actions");
assert(editorActionBarMarkup.includes('data-page-action-separation="destructive"'), "destructive actions expose their visual boundary");
assert(editorActionBarMarkup.indexOf('data-page-action-slot="discard"') < editorActionBarMarkup.indexOf('data-page-action-slot="save"'), "save remains the far-right editor action");
const cleanEditorActionBarMarkup = renderToStaticMarkup(
<PlatformLanguageProvider>
<PageActionBar
variant="editor"
dirty={false}
discardAction={{ label: "Discard" }}
saveAction={{ label: "Save" }}
/>
</PlatformLanguageProvider>
);
assert(cleanEditorActionBarMarkup.includes('data-page-dirty-state="clean"'), "clean editors announce their persisted state");
assert((cleanEditorActionBarMarkup.match(/disabled=""/g) ?? []).length === 2, "clean editors keep Save and Discard visible but disabled");
assert((cleanEditorActionBarMarkup.match(/disabled-action-tooltip/g) ?? []).length === 2, "clean editor actions expose explanations for unavailable persistence");
const collectionActionBarMarkup = renderToStaticMarkup(
<PlatformLanguageProvider>
<PageActionBar
variant="collection"
refreshable
reloadAction={<button type="button">Reload</button>}
createAction={<button type="button">Create</button>}
/>