Skip to content

Commit 733cfdb

Browse files
committed
fix(accessibility): Fix for #644 and updated CSV download language
1 parent 121f4a5 commit 733cfdb

4 files changed

Lines changed: 119 additions & 55 deletions

File tree

apps/frontend/src/components/BlockersTable.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,7 @@ export const BlockersTable = ({ auditId, isShared }: BlockersTableProps) => {
869869
>
870870
<FaDownload className="icon-small" aria-hidden="true" />
871871
<div>
872-
<div className={style["export-dropdown-item-label"]}>Export filtered view</div>
872+
<div className={style["export-dropdown-item-label"]}>Export filtered blockers</div>
873873
<div className={style["export-dropdown-item-desc"]}>
874874
{data?.pagination?.totalCount
875875
? `${data.pagination.totalCount.toLocaleString()} blockers matching current filters`
@@ -898,7 +898,7 @@ export const BlockersTable = ({ auditId, isShared }: BlockersTableProps) => {
898898
>
899899
<FaRegFilePdf className="icon-small" aria-hidden="true" />
900900
<div>
901-
<div className={style["export-dropdown-item-label"]}>Export PDF source links</div>
901+
<div className={style["export-dropdown-item-label"]}>Export PDF Source Page URLs</div>
902902
<div className={style["export-dropdown-item-desc"]}>Source pages and linked PDF URLs found in this audit</div>
903903
</div>
904904
</DropdownMenu.Item>

apps/frontend/src/global-styles/flex.scss

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,27 @@
7878
display: flex;
7979
flex-wrap: wrap;
8080
gap: calc(variables.$spacing * 2);
81+
padding: 0;
82+
list-style: none;
8183
.card {
8284
width: calc(33.33333% - (12px));
8385

8486
@media screen and (max-width: variables.$breakpoint-small) {
8587
width: 100%;
8688
}
8789
}
90+
91+
// when used as a <ul>, each item is an <li> wrapping a .card
92+
> li {
93+
list-style: none;
94+
width: calc(33.33333% - (12px));
95+
96+
@media screen and (max-width: variables.$breakpoint-small) {
97+
width: 100%;
98+
}
99+
100+
.card {
101+
width: 100%;
102+
}
103+
}
88104
}

apps/frontend/src/routes/QuickScans.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@
4848
}
4949

5050
.scan-url {
51+
margin: 0;
52+
font-size: 1em;
5153
font-weight: 600;
54+
line-height: inherit;
55+
letter-spacing: normal;
5256
word-break: break-all;
5357
}
5458

apps/frontend/src/routes/QuickScans.tsx

Lines changed: 97 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from "react";
1+
import { useEffect, useRef, useState } from "react";
22
import { useQuery, useQueryClient } from "@tanstack/react-query";
33
import { formatId, useGlobalStore } from "../utils";
44
import * as API from "aws-amplify/api";
@@ -39,6 +39,37 @@ export const QuickScans = () => {
3939
},
4040
});
4141

42+
const prevStatusesRef = useRef<Record<string, string>>({});
43+
44+
useEffect(() => {
45+
if (!quickScans) return;
46+
const prevStatuses = prevStatusesRef.current;
47+
const nextStatuses: Record<string, string> = {};
48+
const justFinished: { url: string; label: string }[] = [];
49+
50+
for (const scan of quickScans) {
51+
nextStatuses[scan.id] = scan.scan_status;
52+
const wasActive =
53+
prevStatuses[scan.id] &&
54+
prevStatuses[scan.id] !== "complete" &&
55+
prevStatuses[scan.id] !== "failed";
56+
const isNowDone = scan.scan_status === "complete" || scan.scan_status === "failed";
57+
if (wasActive && isNowDone) {
58+
justFinished.push({ url: scan.url, label: getScanStatusLabel(scan) });
59+
}
60+
}
61+
prevStatusesRef.current = nextStatuses;
62+
63+
if (justFinished.length === 1) {
64+
setAnnounceMessage(
65+
`Scan for ${justFinished[0].url} finished: ${justFinished[0].label}`,
66+
justFinished[0].label === "Complete" ? "success" : "error"
67+
);
68+
} else if (justFinished.length > 1) {
69+
setAnnounceMessage(`${justFinished.length} scans finished`, "success");
70+
}
71+
}, [quickScans, setAnnounceMessage]);
72+
4273
const formatUrl = (input: string): string | null => {
4374
let u = input.trim();
4475
if (!u) return null;
@@ -142,61 +173,66 @@ export const QuickScans = () => {
142173
<div className={styles["quick-scans-list"]}>
143174
<h2>Scan History</h2>
144175
{isLoading ? (
145-
<SkeletonAuditGrid count={3} />
176+
<>
177+
<p className="sr-only" role="status">
178+
Loading scan history…
179+
</p>
180+
<SkeletonAuditGrid count={3} />
181+
</>
146182
) : quickScans?.length > 0 ? (
147-
<div className="cards-33">
148-
{quickScans.map((scan: any, index: number) => (
149-
<Card variant="light" key={index}>
150-
<Link to={`/quick-scans/${formatId(scan.id)}`}>
151-
<div className={styles["scan-card"]}>
152-
<div>
153-
<div className={styles["scan-url"]}>{scan.url}</div>
154-
<div className={styles["scan-meta"]}>
155-
<span
156-
className={`${styles["scan-status"]} ${
157-
styles[scan.scan_status] || ""
158-
}`}
159-
>
160-
{scan.scan_status === "processing"
161-
? `Scanning${
162-
scan.scan_percentage
163-
? ` (${scan.scan_percentage}%)`
164-
: "..."
165-
}`
166-
: scan.scan_status === "complete"
167-
? "Complete"
168-
: scan.scan_status || "Pending"}
169-
</span>
170-
<span>{scan.type?.toUpperCase()}</span>
183+
<ul className="cards-33">
184+
{quickScans.map((scan: any, index: number) => {
185+
const statusLabel = getScanStatusLabel(scan);
186+
return (
187+
<li key={scan.id ?? index}>
188+
<Card variant="light">
189+
<Link
190+
to={`/quick-scans/${formatId(scan.id)}`}
191+
aria-label={`View scan of ${scan.url}, status: ${statusLabel}, type: ${scan.type?.toUpperCase()}`}
192+
>
193+
<div className={styles["scan-card"]}>
194+
<div>
195+
<h3 className={styles["scan-url"]}>{scan.url}</h3>
196+
<div className={styles["scan-meta"]}>
197+
<span
198+
className={`${styles["scan-status"]} ${
199+
styles[scan.scan_status] || ""
200+
}`}
201+
>
202+
{statusLabel}
203+
</span>
204+
<span>{scan.type?.toUpperCase()}</span>
205+
</div>
206+
</div>
171207
</div>
208+
</Link>
209+
<div style={{ marginTop: "8px" }}>
210+
<DataRow
211+
variant="highlight"
212+
the_key="Blockers"
213+
the_value={scan.blocker_count ?? "—"}
214+
/>
215+
<DataRow
216+
the_key="Scanned"
217+
the_value={
218+
scan.scan_updated_at
219+
? prettyDate(scan.scan_updated_at) +
220+
" at " +
221+
prettyTime(scan.scan_updated_at)
222+
: "Not scanned yet"
223+
}
224+
/>
225+
<DataRow
226+
variant="no-border"
227+
the_key="Created"
228+
the_value={prettyDate(scan.created_at)}
229+
/>
172230
</div>
173-
</div>
174-
</Link>
175-
<div style={{ marginTop: "8px" }}>
176-
<DataRow
177-
variant="highlight"
178-
the_key="Blockers"
179-
the_value={scan.blocker_count ?? "—"}
180-
/>
181-
<DataRow
182-
the_key="Scanned"
183-
the_value={
184-
scan.scan_updated_at
185-
? prettyDate(scan.scan_updated_at) +
186-
" at " +
187-
prettyTime(scan.scan_updated_at)
188-
: "Not scanned yet"
189-
}
190-
/>
191-
<DataRow
192-
variant="no-border"
193-
the_key="Created"
194-
the_value={prettyDate(scan.created_at)}
195-
/>
196-
</div>
197-
</Card>
198-
))}
199-
</div>
231+
</Card>
232+
</li>
233+
);
234+
})}
235+
</ul>
200236
) : (
201237
<Card variant="light">
202238
<div className={styles["empty-state"]}>
@@ -209,6 +245,14 @@ export const QuickScans = () => {
209245
);
210246
};
211247

248+
function getScanStatusLabel(scan: any) {
249+
if (scan.scan_status === "processing") {
250+
return `Scanning${scan.scan_percentage ? ` (${scan.scan_percentage}%)` : "..."}`;
251+
}
252+
if (scan.scan_status === "complete") return "Complete";
253+
return scan.scan_status || "Pending";
254+
}
255+
212256
function prettyDate(dateTime: string) {
213257
return new Date(dateTime).toLocaleDateString("en-US", {
214258
weekday: "short",

0 commit comments

Comments
 (0)