From 41d810d4f7c2ad656c477ee5b0330923cef221f6 Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Fri, 14 Aug 2026 10:53:28 +0100 Subject: [PATCH] feat(ui): restyle unsaved-changes bar as a compact inverted card (#893) * feat(ui): restyle unsaved-changes bar as a compact inverted card Show Page.SettingsActions only when the form is dirty, as a centered dark card with a secondary Discard button and a primary Save button. Co-authored-by: best * fix(ui): announce settings actions from a persistent live region Keep role=status mounted outside the dirty-state card so screen readers still hear the status when the bar appears, without live-region wrapping the Discard/Save buttons. Co-authored-by: best --------- Co-authored-by: Cursor Agent --- AGENTS.md | 5 +- .../src/molecules/page/page.stories.svelte | 4 +- packages/ui/README.md | 8 +- .../base/page/page-settings-actions.svelte | 93 +++++++++---------- 4 files changed, 56 insertions(+), 54 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5ff9dedb..b6f95f7f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -481,8 +481,9 @@ Use `@cio/ui/base/page` for dashboard page shells. See `packages/ui/README.md` **Every settings page with save/discard** must use `Page.SettingsActions` as the last child of `Page.Root` (after `Page.Body`). Do not put Save in `Page.Header` or inline at the bottom of form sections. -- Sticky at the viewport bottom while scrolling; docks naturally at the end of the page content -- Save and Discard are disabled when `hasChanges` is false +- Compact centered card (not full width), sticky at the viewport bottom while scrolling; docks naturally at the end of the page content +- Only appears when `hasChanges` is true +- Save is a primary button (not a split/combo control); Discard is secondary - Labels come from dashboard translations: `common.unsaved_changes.label`, `common.discard`, `common.save_changes` - Pair with `UnsavedChanges` for navigation guards where appropriate diff --git a/packages/storybook/src/molecules/page/page.stories.svelte b/packages/storybook/src/molecules/page/page.stories.svelte index e471375f..df7f80ae 100644 --- a/packages/storybook/src/molecules/page/page.stories.svelte +++ b/packages/storybook/src/molecules/page/page.stories.svelte @@ -428,7 +428,9 @@ {#snippet child()} -
All changes saved.
+
+ All changes saved. The compact save card stays hidden until the form is dirty. +
{/snippet}
void | Promise; onDiscard?: () => void; } = $props(); - - const actionsDisabled = $derived(!hasChanges || loading); - - let dockSentinel: HTMLDivElement | null = $state(null); - let isDocked = $state(true); - - $effect(() => { - if (!dockSentinel) return; - - const observer = new IntersectionObserver(([entry]) => { - isDocked = entry.isIntersecting; - }); - observer.observe(dockSentinel); - - return () => observer.disconnect(); - }); -
-
-

{statusLabel}

- -
- - -
-
+
+ {hasChanges ? statusLabel : ''}
- +{#if hasChanges} +
+
+
+ +

{statusLabel}

+
+ +
+ + +
+
+
+{/if}