mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Canonicalize mobile row disclosures
This commit is contained in:
@@ -1167,6 +1167,14 @@ not a replacement status card, CTA band, or page-local nested card.
|
||||
disclosure; a non-expandable identity cell must otherwise expose its
|
||||
complete value without requiring hover. Default data rows stay single-line
|
||||
so scan density and row rhythm are not traded for automatic text wrapping.
|
||||
When a platform table row itself owns that keyboard- and touch-operable
|
||||
disclosure, `SummaryRowActionButton` removes the redundant visible chevron
|
||||
below the shared mobile breakpoint while preserving the button for screen
|
||||
readers and revealing it on keyboard focus. `PlatformResourceDetailToggleButton`
|
||||
applies that policy to provider-native platform tables, and workload rows
|
||||
opt in only while their compact row remains the disclosure target. Rows
|
||||
whose click action does something other than expand details must keep a
|
||||
visible disclosure control.
|
||||
They must not rely on a global rule that reveals
|
||||
every hidden column: phone priority remains source-specific because state, capacity,
|
||||
freshness, and recovery posture do not carry equal value for every resource
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "791e29ad8d4cc991837ac43d0bf7c7a42d5fa54a",
|
||||
"verified_at": "2026-08-24T09:50:53Z",
|
||||
"base_sha": "d50f5c664e74e3a4d6a00abf76ef1e2c554e79a6",
|
||||
"verified_at": "2026-08-24T10:04:07Z",
|
||||
"result": "passed",
|
||||
"changed_paths": ["frontend-modern/src/components/Workloads/GuestRow.tsx"],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/Workloads/GuestRow.tsx": "64776e4a32d5361a2ec541344178e9e7d1e401a3d09086e68ad4f923240bf715"
|
||||
},
|
||||
"routes": [
|
||||
"/proxmox"
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/Workloads/GuestRow.tsx",
|
||||
"frontend-modern/src/components/shared/SummaryRowActionButton.tsx",
|
||||
"frontend-modern/src/features/platformPage/PlatformResourceDetailTableRow.tsx"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/Workloads/GuestRow.tsx": "f8927883b484789cae4ca999845a77a7409aed800656d931be621a1f4d996ca8",
|
||||
"frontend-modern/src/components/shared/SummaryRowActionButton.tsx": "626741b5b725d19dd594a4c071888170d74b0e8352513cc2aa8557c86c986c88",
|
||||
"frontend-modern/src/features/platformPage/PlatformResourceDetailTableRow.tsx": "7907a58eeace6c2648f1e92e63a27bfd82fe305347e0100b76b59ab0e367ce61"
|
||||
},
|
||||
"routes": ["/proxmox"],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 390,
|
||||
@@ -21,16 +25,16 @@
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"managed hot-dev mock estate with 50 nodes and 929 guests",
|
||||
"phone Workloads table filtered to one guest with the compact header and collapsed row visible",
|
||||
"phone Workloads table with the guest detail row expanded",
|
||||
"desktop Workloads table with the same filtered guest"
|
||||
"managed hot-dev mock estate with the canonical Proxmox Nodes and Guests tables visible",
|
||||
"phone Nodes and Guests tables with their collapsed rows visible",
|
||||
"phone Nodes and Guests tables after row-owned detail expansion",
|
||||
"desktop Nodes and Guests tables after the responsive breakpoint transition"
|
||||
],
|
||||
"interactions": [
|
||||
"confirmed the visible disclosure control is removed from compact rows while its screen-reader and keyboard-focus path remains available",
|
||||
"confirmed the Type sort header renders without clipping at 390x844 and the availability column receives a larger compact-layout share when present",
|
||||
"tapped the workload name to expand the filtered phone row and confirmed the accessible disclosure changes to Collapse",
|
||||
"confirmed the expanded state survives the transition from 390x844 to 1440x900 and the desktop chevron remains visible",
|
||||
"restored the browser viewport and real-node runtime after verification"
|
||||
"confirmed provider node and workload disclosure controls remain in the accessibility tree while their redundant phone chevrons are visually removed",
|
||||
"tapped the Analytics A node row and the artifact-cache-310 guest row and confirmed both detail regions expanded",
|
||||
"confirmed both disclosure controls become visible again at the 1440x900 desktop breakpoint",
|
||||
"confirmed Type, ID, CPU, Mem, Disk, and Age remain separated in the compact Workloads header",
|
||||
"restored the browser viewport after verification"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ export function GuestRow(props: GuestRowProps) {
|
||||
subjectLabel={props.guest.name}
|
||||
expanded={props.isExpanded === true}
|
||||
controlsId={detailControlsId()}
|
||||
class={usesCompactTableLayout() ? 'sr-only focus:not-sr-only' : undefined}
|
||||
hideWhenRowTappableOnMobile={usesCompactTableLayout()}
|
||||
onAction={() => props.onClick?.()}
|
||||
onPreviewClear={() => props.onHoverChange?.(null)}
|
||||
/>
|
||||
|
||||
@@ -924,6 +924,7 @@ describe('GuestRow', () => {
|
||||
|
||||
expect(disclosureButton.className).toContain('sr-only');
|
||||
expect(disclosureButton.className).toContain('focus:not-sr-only');
|
||||
expect(disclosureButton.className).toContain('sm:not-sr-only');
|
||||
});
|
||||
|
||||
it('keeps the visible disclosure control in desktop layouts', () => {
|
||||
|
||||
@@ -1554,8 +1554,11 @@ describe('shared primitive guardrails', () => {
|
||||
expect(summaryRowActionButtonSource).toContain('aria-expanded');
|
||||
expect(summaryRowActionButtonSource).toContain('aria-pressed');
|
||||
expect(summaryRowActionButtonSource).toContain('data-row-action="true"');
|
||||
expect(summaryRowActionButtonSource).toContain('hideWhenRowTappableOnMobile');
|
||||
expect(summaryRowActionButtonSource).toContain('sm:not-sr-only');
|
||||
expect(platformResourceDetailTableRowSource).toContain('PlatformResourceDetailToggleButton');
|
||||
expect(platformResourceDetailTableRowSource).toContain('SummaryRowActionButton');
|
||||
expect(platformResourceDetailTableRowSource).toContain('hideWhenRowTappableOnMobile');
|
||||
|
||||
for (const source of [
|
||||
guestRowSource,
|
||||
|
||||
@@ -11,6 +11,7 @@ type SummaryRowActionButtonProps =
|
||||
subjectLabel: string;
|
||||
controlsId?: string;
|
||||
class?: string;
|
||||
hideWhenRowTappableOnMobile?: boolean;
|
||||
onAction: () => void;
|
||||
onPreviewClear?: () => void;
|
||||
}
|
||||
@@ -46,12 +47,16 @@ export const SummaryRowActionButton: Component<SummaryRowActionButtonProps> = (p
|
||||
scopeProps()?.pressed
|
||||
? `Unpin summary scope for ${props.subjectLabel}`
|
||||
: `Pin summary scope for ${props.subjectLabel}`;
|
||||
const mobileRowTargetClass = () =>
|
||||
disclosureProps()?.hideWhenRowTappableOnMobile
|
||||
? 'sr-only focus:not-sr-only sm:not-sr-only'
|
||||
: '';
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-row-action="true"
|
||||
class={`${props.kind === 'disclosure' ? DISCLOSURE_BUTTON_CLASS : SCOPE_BUTTON_CLASS} ${props.class ?? ''}`.trim()}
|
||||
class={`${props.kind === 'disclosure' ? DISCLOSURE_BUTTON_CLASS : SCOPE_BUTTON_CLASS} ${mobileRowTargetClass()} ${props.class ?? ''}`.trim()}
|
||||
aria-label={props.kind === 'disclosure' ? disclosureLabel() : scopeLabel()}
|
||||
aria-expanded={disclosureProps()?.expanded}
|
||||
aria-controls={disclosureProps()?.controlsId}
|
||||
|
||||
@@ -86,4 +86,21 @@ describe('SummaryRowActionButton', () => {
|
||||
fireEvent.keyDown(button, { key: 'Space', code: 'Space' });
|
||||
expect(onAction).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('removes redundant mobile chrome when the whole row is tappable', () => {
|
||||
render(() => (
|
||||
<SummaryRowActionButton
|
||||
kind="disclosure"
|
||||
subjectLabel="alpha"
|
||||
expanded={false}
|
||||
hideWhenRowTappableOnMobile
|
||||
onAction={vi.fn()}
|
||||
/>
|
||||
));
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Expand alpha' });
|
||||
expect(button).toHaveClass('sr-only');
|
||||
expect(button).toHaveClass('focus:not-sr-only');
|
||||
expect(button).toHaveClass('sm:not-sr-only');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,6 +39,7 @@ export const PlatformResourceDetailToggleButton: Component<{
|
||||
subjectLabel={`details for ${props.resourceLabel}`}
|
||||
controlsId={props.controlsId}
|
||||
class={props.class}
|
||||
hideWhenRowTappableOnMobile
|
||||
onAction={props.onToggle}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user