Extract shared SectionHeader and FormFooter components (ticket 087)
Params/Metrics/Alerts/Dashboards each hand-assembled the same collapsible- section header wiring (role/tabIndex/aria-expanded/onClick/onKeyDown around CollapseToggle+HelpIcon+PopOutButton), and Dashboards duplicated it a second time for its no-active-profile branch. Params/Metrics/Alerts also duplicated the declare/edit form footer (editing banner, submit label toggle, Cancel button). Added SectionHeader and FormFooter to CollapsibleSection.tsx and adopted them at all five call sites; pure extraction, no behavior change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TeyhRfMYjMditek6geDRoQ
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
000efa5166
commit
8008e5611e
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import type { TimeRange, TimeRangePresetId } from '../lib/timeRange'
|
||||
import { HelpIcon } from './HelpIcon'
|
||||
|
||||
/// Shared look for a primary section's title bar: shaded so it reads as a
|
||||
/// distinct band rather than blending into the section body below it.
|
||||
@@ -95,6 +96,100 @@ export function handleSectionHeaderKeyDown(onToggle: () => void) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The header row every settings panel (Params/Metrics/Alerts/Dashboards) opens with: a click/keyboard-toggleable
|
||||
/// title bar pairing a title + `HelpIcon` on the left with `CollapseToggle` (and, for panels with a standalone
|
||||
/// popped-out view, a `PopOutButton`) on the right. Assembles `sectionHeaderInteractiveClassName` /
|
||||
/// `handleSectionHeaderKeyDown` / `sectionHeaderLeftClassName` so callers don't each re-wire the same
|
||||
/// `role="button"`/`tabIndex`/`aria-expanded` boilerplate by hand.
|
||||
export function SectionHeader({
|
||||
title,
|
||||
helpSectionId,
|
||||
helpLabel,
|
||||
collapsed,
|
||||
onToggle,
|
||||
popOut,
|
||||
}: {
|
||||
title: string
|
||||
helpSectionId: string
|
||||
helpLabel?: string
|
||||
collapsed: boolean
|
||||
onToggle: () => void
|
||||
popOut?: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={sectionHeaderInteractiveClassName}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={!collapsed}
|
||||
onClick={onToggle}
|
||||
onKeyDown={handleSectionHeaderKeyDown(onToggle)}
|
||||
>
|
||||
<div className={sectionHeaderLeftClassName}>
|
||||
{title}
|
||||
<HelpIcon sectionId={helpSectionId} label={helpLabel ?? `Help: ${title}`} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<CollapseToggle collapsed={collapsed} />
|
||||
{popOut}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/// The declare/edit form footer shared by `Params.tsx`/`Metrics.tsx`/`Alerts.tsx`: a submit button whose label
|
||||
/// swaps between create/edit and idle/in-flight variants, plus a Cancel button that only appears while editing.
|
||||
/// Doesn't render the "Editing X" banner some callers show above this — that banner's placement relative to
|
||||
/// `submitError` differs per caller, so it stays theirs to render; this covers only the part that's identical
|
||||
/// everywhere. Label text stays a per-caller prop (e.g. "Declare param" vs "Declare metric") rather than
|
||||
/// hardcoded here, since that's the one part of this footer each panel's copy actually differs on.
|
||||
export function FormFooter({
|
||||
editing,
|
||||
submitting,
|
||||
idleLabel,
|
||||
editingLabel,
|
||||
submittingLabel = 'Saving…',
|
||||
editingSubmittingLabel,
|
||||
onCancel,
|
||||
}: {
|
||||
editing: boolean
|
||||
submitting: boolean
|
||||
idleLabel: string
|
||||
editingLabel: string
|
||||
submittingLabel?: string
|
||||
editingSubmittingLabel?: string
|
||||
onCancel: () => void
|
||||
}) {
|
||||
const label = submitting
|
||||
? editing
|
||||
? (editingSubmittingLabel ?? submittingLabel)
|
||||
: submittingLabel
|
||||
: editing
|
||||
? editingLabel
|
||||
: idleLabel
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50"
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
{editing && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
disabled={submitting}
|
||||
className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/// The four primary sections poppable into their own tab; matches the `?section=` query param a standalone page reads.
|
||||
export type PopOutSectionId = 'log' | 'params' | 'metrics' | 'dashboards'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user