Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 2024-05-19 - Replace HTML disabled with aria-disabled="true" for Accessible Tooltips
**Learning:** Native HTML `disabled` attributes completely hide elements from screen readers and block all pointer/hover events, preventing tooltips from functioning for disabled elements.
**Action:** Replace `disabled` with `aria-disabled="true"`, enforce block click handlers via `e.preventDefault()`, and add a title tooltip directly to the element to maintain full tooltip accessibility and keyboard focus support for visually impaired and mouse users.
## 2023-10-27 - Added disabled titles to ScoreViewer navigation
**Learning:** Added tooltips explicitly explaining *why* navigation buttons in ScoreViewer are disabled (e.g. "Already at the first page" or "Already at the last page"), which provides significantly better a11y context than leaving them silently disabled, matching the UX guidance pattern. Also modified tests accordingly to assert for the right translated keys.
**Action:** When converting HTML `disabled` attributes to `aria-disabled="true"` for interactive elements, always consider if there is an opportunity to communicate the reason for the disabled state to the user via a `title` or `aria-describedby` attribute, and ensure translations and testing are properly set up for this specific state.
12 changes: 8 additions & 4 deletions apps/desktop/src/features/score/ScoreViewer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ describe("ScoreViewer", () => {
expect(page.render).toHaveBeenCalled();
});
expect(page.getViewport).toHaveBeenCalledWith({ scale: 1 });
expect(screen.getByRole("button", { name: "Previous page" })).toBeDisabled();
expect(screen.getByRole("button", { name: "Next page" })).toBeEnabled();
expect(screen.getByRole("button", { name: "Previous page" })).toHaveAttribute("aria-disabled", "true");
expect(screen.getByRole("button", { name: "Previous page" })).toHaveAttribute("title", "scoreViewerAlreadyFirstPage");
expect(screen.getByRole("button", { name: "Next page" })).not.toHaveAttribute("aria-disabled", "true");
expect(screen.getByRole("button", { name: "Next page" })).not.toHaveAttribute("title", "scoreViewerAlreadyLastPage");
});

it("shows the file name when provided", async () => {
Expand Down Expand Up @@ -174,14 +176,16 @@ describe("ScoreViewer", () => {
expect(await screen.findByText("Page 1 of 3")).toBeInTheDocument();
const previousButton = screen.getByRole("button", { name: "Previous page" });
const nextButton = screen.getByRole("button", { name: "Next page" });
expect(previousButton).toBeDisabled();
expect(previousButton).toHaveAttribute("aria-disabled", "true");
expect(previousButton).toHaveAttribute("title", "scoreViewerAlreadyFirstPage");

fireEvent.click(nextButton);
expect(screen.getByText("Page 2 of 3")).toBeInTheDocument();

fireEvent.click(nextButton);
expect(screen.getByText("Page 3 of 3")).toBeInTheDocument();
expect(nextButton).toBeDisabled();
expect(nextButton).toHaveAttribute("aria-disabled", "true");
expect(nextButton).toHaveAttribute("title", "scoreViewerAlreadyLastPage");

await waitFor(() => {
expect(doc.getPage).toHaveBeenCalledWith(3);
Expand Down
10 changes: 6 additions & 4 deletions apps/desktop/src/features/score/ScoreViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,9 @@ export function ScoreViewer({ data, fileName, onStatusChange }: ScoreViewerProps
size="icon-lg"
className="size-14"
aria-label={t("scoreViewerPrevPage")}
disabled={pageNumber <= 1}
onClick={goToPreviousPage}
aria-disabled={pageNumber <= 1 ? true : undefined}
title={pageNumber <= 1 ? t("scoreViewerAlreadyFirstPage") : undefined}
onClick={pageNumber <= 1 ? undefined : goToPreviousPage}
>
<ChevronLeft className="size-6" aria-hidden="true" />
</Button>
Expand All @@ -305,8 +306,9 @@ export function ScoreViewer({ data, fileName, onStatusChange }: ScoreViewerProps
size="icon-lg"
className="size-14"
aria-label={t("scoreViewerNextPage")}
disabled={pageNumber >= pageCount}
onClick={goToNextPage}
aria-disabled={pageNumber >= pageCount ? true : undefined}
title={pageNumber >= pageCount ? t("scoreViewerAlreadyLastPage") : undefined}
onClick={pageNumber >= pageCount ? undefined : goToNextPage}
>
<ChevronRight className="size-6" aria-hidden="true" />
</Button>
Expand Down
4 changes: 3 additions & 1 deletion apps/desktop/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,7 @@
"workspaceFirstRangeClash": "{roleName} sits {lowestNote}โ€“{highestNote} in {sectionLabel}. Hear that clash on your instrument before the {sectionLabel}.",
"workspaceFirstRangeMissing": "Tonight's first range still needs an ear check. Confirm the high and low notes on the selected part before the first section.",
"sectionRangeLabel": "Range",
"sectionRangeNextAction": "Check this span on your instrument before {sectionLabel}."
"sectionRangeNextAction": "Check this span on your instrument before {sectionLabel}.",
"scoreViewerAlreadyFirstPage": "Already at the first page",
"scoreViewerAlreadyLastPage": "Already at the last page"
}
4 changes: 3 additions & 1 deletion apps/desktop/src/locales/ko/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,7 @@
"workspaceFirstRangeClash": "{sectionLabel}์˜ {roleName}์€ {lowestNote}โ€“{highestNote}์ด๊ณ  ๋‹ค๋ฅธ ํŒŒํŠธ์™€ ๊ฒน์นฉ๋‹ˆ๋‹ค. {sectionLabel} ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— ๊ทธ ์ถฉ๋Œ์„ ์•…๊ธฐ๋กœ ๋“ค์–ด ๋ณด์„ธ์š”.",
"workspaceFirstRangeMissing": "์˜ค๋Š˜ ๋จผ์ € ๋ณผ ์Œ์—ญ์€ ์•„์ง ๊ท€๋กœ ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์„ ํƒํ•œ ํŒŒํŠธ์˜ ์ตœ์ €ยท์ตœ๊ณ ์Œ์„ ์ฒซ ๊ตฌ๊ฐ„ ์ „์— ํ™•์ธํ•ด ๋ณด์„ธ์š”.",
"sectionRangeLabel": "์Œ์—ญ",
"sectionRangeNextAction": "{sectionLabel} ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— ์ด ์Œ์—ญ์„ ์•…๊ธฐ๋กœ ํ™•์ธํ•ด ๋ณด์„ธ์š”."
"sectionRangeNextAction": "{sectionLabel} ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— ์ด ์Œ์—ญ์„ ์•…๊ธฐ๋กœ ํ™•์ธํ•ด ๋ณด์„ธ์š”.",
"scoreViewerAlreadyFirstPage": "์ด๋ฏธ ์ฒซ ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค",
"scoreViewerAlreadyLastPage": "์ด๋ฏธ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค"
}
Loading