Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 50 additions & 61 deletions app/src/components/flagship/reform/DraftHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -393,16 +393,18 @@ export function DraftRunBar({ draft }: { draft: DraftReform }) {
return (
<section
aria-label="Reform actions"
// Stuck to the scroll area's very bottom, over the page's own padding
// (a sticky bar stops at the padding), so nothing shows under it.
className="tw:-bottom-lg tw:pb-lg tw:sm:-bottom-2xl tw:sm:pb-2xl"
style={{
position: 'sticky',
bottom: 0,
// Over the reform scrolling under it, but under the add bar (10),
// whose search results and policy tree open down over the page.
zIndex: 5,
display: 'flex',
flexDirection: 'column',
gap: spacing.xs,
padding: `${spacing.md} 0`,
paddingTop: spacing.md,
// The page's own grey, so the bar reads as part of the page: only a
// hairline above it parts the reform scrolling underneath.
borderTop: `1px solid ${colors.border.light}`,
Expand Down Expand Up @@ -446,6 +448,52 @@ export function DraftRunBar({ draft }: { draft: DraftReform }) {
marginLeft: 'auto',
}}
>
{/* An unnamed reform runs as "Untitled reform": said in the bar's own row, with a quick way to name it. */}
{adjusted.length > 0 &&
(!draft.label.trim() || naming) &&
(naming ? (
<input
autoFocus
value={draft.label}
onChange={(event) => setDraftLabel(event.target.value)}
onBlur={() => setNaming(false)}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === 'Escape') {
setNaming(false);
}
}}
placeholder="Name your reform"
aria-label="Reform name"
style={{
width: 220,
maxWidth: '100%',
height: 36,
padding: `0 ${spacing.sm}`,
border: `1px solid ${colors.border.light}`,
borderRadius: 6,
background: colors.background.primary,
fontSize: typography.fontSize.sm,
fontFamily: typography.fontFamily.primary,
color: colors.text.primary,
}}
/>
) : (
<span
style={{
display: 'inline-flex',
alignItems: 'center',
gap: spacing.xs,
fontSize: typography.fontSize.sm,
color: colors.text.secondary,
}}
>
<span>No name yet. The report will be called “{UNTITLED_REFORM}”.</span>
<Button size="sm" variant="ghost" onClick={() => setNaming(true)}>
<IconPencil size={14} aria-hidden />
Name it
</Button>
</span>
))}
<Button variant="ghost" onClick={() => clearDraftReform()}>
Discard
</Button>
Expand Down Expand Up @@ -485,65 +533,6 @@ export function DraftRunBar({ draft }: { draft: DraftReform }) {
</div>
</div>

{/* An unnamed reform runs as "Untitled reform": said once, with a quick way to name it. */}
{adjusted.length > 0 && (!draft.label.trim() || naming) && (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
flexWrap: 'wrap',
columnGap: spacing.lg,
rowGap: spacing.sm,
width: 'fit-content',
maxWidth: '100%',
marginLeft: 'auto',
marginTop: spacing.sm,
padding: `${spacing.sm} ${spacing.sm} ${spacing.sm} ${spacing.lg}`,
border: `1px solid ${colors.border.light}`,
borderRadius: 10,
background: colors.gray[50],
fontSize: typography.fontSize.sm,
color: colors.text.secondary,
}}
>
{naming ? (
<input
autoFocus
value={draft.label}
onChange={(event) => setDraftLabel(event.target.value)}
onBlur={() => setNaming(false)}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === 'Escape') {
setNaming(false);
}
}}
placeholder="Name your reform"
aria-label="Reform name"
style={{
width: 240,
maxWidth: '100%',
height: 28,
padding: `0 ${spacing.sm}`,
border: `1px solid ${colors.border.light}`,
borderRadius: 6,
background: colors.background.primary,
fontSize: typography.fontSize.sm,
fontFamily: typography.fontFamily.primary,
color: colors.text.primary,
}}
/>
) : (
<>
<span>No name yet. The report will be called “{UNTITLED_REFORM}”.</span>
<Button size="sm" variant="outline" onClick={() => setNaming(true)}>
<IconPencil size={14} aria-hidden />
Name it
</Button>
</>
)}
</div>
)}
{/* Unchanged parameters still leave the run quietly; only "nothing changed" is said. */}
{!empty && adjusted.length === 0 && (
<p
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ describe('DraftHeader', () => {
text === 'Discard' || text === 'Save' || /^Save and run 2026 report$/.test(text ?? '')
);
expect(verbs).toEqual(['Discard', 'Save', 'Save and run 2026 report']);
// It stays in view at the bottom of the window while the reform is long.
expect(bar).toHaveStyle({ position: 'sticky', bottom: '0px' });
// It stays in view at the bottom of the window while the reform is long,
// stuck over the page's bottom padding so nothing shows under it.
expect(bar).toHaveStyle({ position: 'sticky' });
expect(bar).toHaveClass('tw:-bottom-lg', 'tw:sm:-bottom-2xl');
});

test('given nothing changed yet then the run waits for a change', () => {
Expand Down
Loading