Compare commits

...

22 Commits

Author SHA1 Message Date
Zamitto
ba9232b821 Merge branch 'main' into feature/accessibility-improvements 2024-11-01 09:19:02 -03:00
cj do gta sander pegando fogo aaa aaa aaa
a432306b1d Merge branch 'main' into feature/accessibility-improvements 2024-10-30 01:16:04 -03:00
cj do gta sander pegando fogo aaa aaa aaa
69d96cc290 Merge branch 'main' into feature/accessibility-improvements 2024-10-29 18:27:49 -03:00
cj-do-gta-sander
8f4919615f lint: how-long-to-beat-section.tsx 2024-10-24 14:53:36 -03:00
cj-do-gta-sander
a25a960235 feat: add aria labels to How Long to Beat and Achievements in game details sidebar 2024-10-24 14:52:54 -03:00
cj-do-gta-sander
c754710171 feat: enable tab navigation through carousel previews, replacing arrow buttons 2024-10-24 14:27:54 -03:00
cj-do-gta-sander
bf6ce2b465 feat: add ability to blur search bar with Escape key 2024-10-24 12:24:20 -03:00
cj-do-gta-sander
3adc8662dc feat: add keyboard shortcut to focus header search bar 2024-10-24 12:11:49 -03:00
cj-do-gta-sander
2a6346cb69 feat: add tooltip and screen reader support to sidebar resize button 2024-10-24 11:50:01 -03:00
cj-do-gta-sander
455016c1a7 lint: sidebar.tsx 2024-10-24 11:46:37 -03:00
cj-do-gta-sander
e0ec79b105 feat: add keyboard control for sidebar size adjustment 2024-10-24 11:45:41 -03:00
cj-do-gta-sander
ba7e4c979d feat: add tooltip and screen reader support to clear search button 2024-10-24 11:06:03 -03:00
cj-do-gta-sander
a54983c339 feat: add tabindex 0 to search bar icon for direct focus 2024-10-24 11:02:48 -03:00
cj-do-gta-sander
b754b1e052 feat: add tooltip and screen reader support to back button in header 2024-10-24 10:58:00 -03:00
cj-do-gta-sander
79763b6072 chore: remove redundancy in "View Profile" and "Game" button for screen readers 2024-10-24 10:49:01 -03:00
cj-do-gta-sander
3ff15d2d61 chore: remove redundancy in list semantic markup 2024-10-24 10:04:34 -03:00
cj-do-gta-sander
50303251a2 feat: add semantic roles and aria labels to achievement list 2024-10-24 09:57:11 -03:00
cj-do-gta-sander
012f872f60 chore: chore: remove unnecessary locale string and use locked status strings 2024-10-24 09:55:22 -03:00
cj-do-gta-sander
e9f68977fe feat: add aria label to achievement summary 2024-10-24 09:39:15 -03:00
cj-do-gta-sander
c5d4db0a1e chore: change locale param names to match with args 2024-10-24 09:37:51 -03:00
cj-do-gta-sander
3b02a3c43f chore: move aria-labels strings to their respective pages in locales 2024-10-24 09:27:39 -03:00
cj-do-gta-sander
ab7625a314 feat: add aria-label strings to locales for achievements page 2024-10-24 09:03:48 -03:00
9 changed files with 111 additions and 10 deletions

View File

@@ -25,7 +25,9 @@
"queued": "{{title}} (Queued)",
"game_has_no_executable": "Game has no executable selected",
"sign_in": "Sign in",
"friends": "Friends"
"friends": "Friends",
"aria_view_profile": "View profile",
"resize_sidebar": "Resize sidebar"
},
"header": {
"search": "Search games",
@@ -35,7 +37,9 @@
"search_results": "Search results",
"settings": "Settings",
"version_available_install": "Version {{version}} available. Click here to restart and install.",
"version_available_download": "Version {{version}} available. Click here to download."
"version_available_download": "Version {{version}} available. Click here to download.",
"back": "Back",
"clear_search": "Clear search"
},
"bottom_panel": {
"no_downloads_in_progress": "No downloads in progress",
@@ -132,6 +136,7 @@
"warning": "Warning:",
"hydra_needs_to_remain_open": "for this download, Hydra needs to remain open util it's completed. If Hydra closes before completing, you will lose your progress.",
"achievements": "Achievements",
"achievement": "Achievement",
"achievements_count": "Achievements {{unlockedCount}}/{{achievementsCount}}",
"cloud_save": "Cloud save",
"cloud_save_description": "Save your progress in the cloud and continue playing on any device",
@@ -358,11 +363,13 @@
},
"achievement": {
"achievement_unlocked": "Achievement unlocked",
"achievement_locked": "Achievement locked",
"user_achievements": "{{displayName}}'s Achievements",
"your_achievements": "Your Achievements",
"unlocked_at": "Unlocked at:",
"subscription_needed": "A Hydra Cloud subscription is required to see this content",
"new_achievements_unlocked": "Unlocked {{achievementCount}} new achievements from {{gameCount}} games"
"new_achievements_unlocked": "Unlocked {{achievementCount}} new achievements from {{gameCount}} games",
"aria_achievement_summary": "{{userDisplayName}} achievements for {{gameTitle}}, {{userAchievementCount}} unlocked of {{userTotalAchievementCount}}, {{percentage}} completed"
},
"tour": {
"subscription_tour_title": "Hydra Cloud Subscription",

View File

@@ -25,7 +25,9 @@
"queued": "{{title}} (Na fila)",
"game_has_no_executable": "Jogo não possui executável selecionado",
"sign_in": "Login",
"friends": "Amigos"
"friends": "Amigos",
"aria_view_profile": "Ver perfil",
"resize_sidebar": "Redimensionar barra lateral"
},
"header": {
"search": "Buscar jogos",
@@ -35,7 +37,9 @@
"settings": "Ajustes",
"home": "Início",
"version_available_install": "Versão {{version}} disponível. Clique aqui para reiniciar e instalar.",
"version_available_download": "Versão {{version}} disponível. Clique aqui para fazer o download."
"version_available_download": "Versão {{version}} disponível. Clique aqui para fazer o download.",
"back": "Voltar",
"clear_search": "Limpar busca"
},
"bottom_panel": {
"no_downloads_in_progress": "Sem downloads em andamento",
@@ -128,6 +132,7 @@
"warning": "Aviso:",
"hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.",
"achievements": "Conquistas",
"achievement": "Conquista",
"achievements_count": "Conquistas ({{unlockedCount}}/{{achievementsCount}})",
"cloud_save": "Salvamento em nuvem",
"cloud_save_description": "Mantenha seu progresso na nuvem e continue de onde parou em qualquer dispositivo",
@@ -356,11 +361,13 @@
},
"achievement": {
"achievement_unlocked": "Conquista desbloqueada",
"achievement_locked": "Conquista bloqueada",
"your_achievements": "Suas Conquistas",
"user_achievements": "Conquistas de {{displayName}}",
"unlocked_at": "Desbloqueado em:",
"subscription_needed": "Você precisa de uma assinatura Hydra Cloud para visualizar este conteúdo",
"new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos"
"new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos",
"aria_achievement_summary": "Conquistas de {{userDisplayName}} em {{gameTitle}}, {{userAchievementCount}} desbloqueadas de {{userTotalAchievementCount}}, {{percentage}} concluídas"
},
"tour": {
"subscription_tour_title": "Assinatura Hydra Cloud",

View File

@@ -65,6 +65,24 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
navigate(-1);
};
useEffect(() => {
window.onkeydown = (event: KeyboardEvent) => {
const { key, ctrlKey } = event;
if (!isFocused && ctrlKey && key === "k") {
focusInput();
}
if (isFocused && key === "Escape" && inputRef.current) {
inputRef.current.blur();
handleBlur();
}
};
return () => {
window.onkeydown = null;
};
}, [isFocused]);
return (
<>
<header
@@ -81,6 +99,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
})}
onClick={handleBackButtonClick}
disabled={location.key === "default"}
title={t("back")}
>
<ArrowLeftIcon />
</button>
@@ -100,6 +119,8 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
type="button"
className={styles.actionButton}
onClick={focusInput}
tabIndex={-1}
title={t("search")}
>
<SearchIcon />
</button>
@@ -121,6 +142,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
type="button"
onClick={onClear}
className={styles.actionButton}
title={t("clear_search")}
>
<XIcon />
</button>

View File

@@ -89,6 +89,7 @@ export function SidebarProfile() {
type="button"
className={styles.profileButton}
onClick={handleProfileClick}
aria-label={t("aria_view_profile")}
>
<div className={styles.profileButtonContent}>
<Avatar

View File

@@ -68,6 +68,26 @@ export function Sidebar() {
sidebarRef.current?.clientWidth || SIDEBAR_INITIAL_WIDTH;
};
const handleKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (
event
) => {
const { key } = event;
if (key === "ArrowRight") {
setSidebarWidth((prevWidth) =>
prevWidth < SIDEBAR_INITIAL_WIDTH
? SIDEBAR_INITIAL_WIDTH
: SIDEBAR_MAX_WIDTH
);
} else if (key === "ArrowLeft") {
setSidebarWidth((prevWidth) =>
prevWidth > SIDEBAR_INITIAL_WIDTH
? SIDEBAR_INITIAL_WIDTH
: SIDEBAR_MIN_WIDTH
);
}
};
const handleFilter: React.ChangeEventHandler<HTMLInputElement> = (event) => {
setFilteredLibrary(
sortedLibrary.filter((game) =>
@@ -219,6 +239,7 @@ export function Sidebar() {
type="button"
className={styles.menuItemButton}
onClick={(event) => handleSidebarGameClick(event, game)}
aria-label={game.title}
>
{game.iconUrl ? (
<img
@@ -245,6 +266,8 @@ export function Sidebar() {
type="button"
className={styles.handle}
onMouseDown={handleMouseDown}
onKeyDown={handleKeyDown}
title={t("resize_sidebar")}
/>
</aside>
);

View File

@@ -16,6 +16,7 @@ import { average } from "color.js";
import Color from "color";
import { Link } from "@renderer/components";
import { ComparedAchievementList } from "./compared-achievement-list";
import { TFunction } from "i18next/typescript/t";
interface UserInfo {
id: string;
@@ -39,10 +40,35 @@ interface AchievementSummaryProps {
isComparison?: boolean;
}
const ariaLabelSummary = (
t: TFunction,
gameTitle: string,
user: UserInfo
): string => {
return t("aria_achievement_summary", {
userDisplayName: user.displayName,
gameTitle: gameTitle,
userAchievementCount: user.unlockedAchievementCount,
userTotalAchievementCount: user.totalAchievementCount,
percentage: formatDownloadProgress(
user.unlockedAchievementCount / user.totalAchievementCount
),
});
};
const ariaLabelAchievement = (
t: TFunction,
achievement: UserAchievement
): string => {
return `${
achievement.unlocked ? t("achievement_unlocked") : t("achievement_locked")
}, ${achievement.displayName}, ${achievement.description}`;
};
function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
const { t } = useTranslation("achievement");
const { userDetails, hasActiveSubscription } = useUserDetails();
const { handleClickOpenCheckout } = useContext(gameDetailsContext);
const { handleClickOpenCheckout, gameTitle } = useContext(gameDetailsContext);
const getProfileImage = (
user: Pick<UserInfo, "profileImageUrl" | "displayName">
@@ -124,6 +150,8 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
alignItems: "center",
padding: `${SPACING_UNIT}px`,
}}
role="region"
aria-label={ariaLabelSummary(t, gameTitle, user)}
>
{getProfileImage(user)}
<div
@@ -178,7 +206,12 @@ function AchievementList({ achievements }: AchievementListProps) {
return (
<ul className={styles.list}>
{achievements.map((achievement, index) => (
<li key={index} className={styles.listItem} style={{ display: "flex" }}>
<li
key={index}
className={styles.listItem}
style={{ display: "flex" }}
aria-label={ariaLabelAchievement(t, achievement)}
>
<img
className={styles.listItemImage({
unlocked: achievement.unlocked,

View File

@@ -140,7 +140,7 @@ export function GallerySlider() {
direction: "left",
})}
aria-label={t("previous_screenshot")}
tabIndex={0}
tabIndex={-1}
>
<ChevronLeftIcon size={36} />
</button>
@@ -153,7 +153,7 @@ export function GallerySlider() {
direction: "right",
})}
aria-label={t("next_screenshot")}
tabIndex={0}
tabIndex={-1}
>
<ChevronRightIcon size={36} />
</button>
@@ -169,6 +169,7 @@ export function GallerySlider() {
})}
onClick={() => setMediaIndex(i)}
aria-label={t("open_screenshot", { number: i + 1 })}
onFocus={() => setMediaIndex(i)}
>
<img
src={media.thumbnail}

View File

@@ -38,6 +38,9 @@ export function HowLongToBeatSection({
<li
key={category.title}
className={styles.howLongToBeatCategory}
aria-label={`${category.title}, ${getDuration(
category.duration
)}`}
>
<p
className={styles.howLongToBeatCategoryLabel}

View File

@@ -194,6 +194,10 @@ export function Sidebar() {
})}
className={styles.listItem}
title={achievement.description}
aria-label={`
${t("achievement")} ${index + 1},
${achievement.displayName}
`}
>
<img
className={styles.listItemImage({