Skip to content
Open
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
1 change: 1 addition & 0 deletions src/renderer/src/views/components/Header/HelpSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const HelpSection: FC = () => {
return (
<Dropdown>
<Menu.Button
aria-label={t("Help")}
as={IconButton}
iconPath={mdiHelpCircleOutline}
title={t("Help")}
Expand Down
79 changes: 51 additions & 28 deletions src/renderer/src/views/components/Header/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@ import { Icon } from "../../../ui/components/icon/Icon";
import { Typography } from "../../../ui/components/typography/Typography";
import { joinClasses } from "../../../ui/utils/joinClasses";

interface IconButtonProps
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
appearance?: 'primary' | 'secondary';
interface IconButtonProps extends Omit<
ButtonHTMLAttributes<HTMLButtonElement>,
"children"
> {
appearance?: "primary" | "secondary";
className?: string;
iconPath: string;
itemCount?: number;
"aria-label": string; // Requires an accessible name
}

const appearanceMap: Record<IconButtonProps["appearance"], string> = {
// Map the appearances to their respective Tailwind CSS classes
const appearanceMap: Record<
NonNullable<IconButtonProps["appearance"]>,
string
> = {
primary:
"text-neutral-moderate hover:text-neutral-strong aria-expanded:text-neutral-strong",
secondary:
Expand All @@ -21,30 +28,46 @@ const appearanceMap: Record<IconButtonProps["appearance"], string> = {

export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
(
{ appearance = "primary", className, iconPath, itemCount, ...props },
{
appearance = "primary",
className,
iconPath,
itemCount,
"aria-label": ariaLabel,
...props
},
ref,
) => (
<button
className={joinClasses([
"group/icon-button relative flex size-7 items-center justify-center rounded-sm transition-colors hover:bg-surface-translucent-mid aria-expanded:bg-surface-translucent-mid",
appearanceMap[appearance],
className,
])}
ref={ref}
{...props}
>
<Icon className="size-5" path={iconPath} size="none" />
) => {
// Dynamically append the item count if it exists
const finalAriaLabel = itemCount
? `${ariaLabel}, ${itemCount} items`
: ariaLabel;

{!!itemCount && (
<Typography
appearance="inverted"
as="span"
className="absolute -top-1 -right-1 flex h-4.5 min-w-4.5 items-center justify-center rounded-full border-2 border-neutral-inverted bg-primary-moderate px-1 leading-none font-semibold transition-colors group-hover/icon-button:bg-primary-strong group-aria-expanded/icon-button:bg-primary-strong"
typographyType="body-xs"
>
{itemCount > 9 ? "9+" : itemCount}
</Typography>
)}
</button>
),
return (
<button
aria-label={finalAriaLabel}
className={joinClasses([
"group/icon-button relative flex size-7 items-center justify-center rounded-sm transition-colors hover:bg-surface-translucent-mid aria-expanded:bg-surface-translucent-mid",
appearanceMap[appearance],
className,
])}
ref={ref}
{...props}
>
<Icon className="size-5" path={iconPath} size="none" />

{!!itemCount && (
<Typography
appearance="inverted"
aria-hidden="true" // Hide from screen readers since finalAriaLabel handles it
as="span"
className="absolute -top-1 -right-1 flex h-4.5 min-w-4.5 items-center justify-center rounded-full border-2 border-neutral-inverted bg-primary-moderate px-1 leading-none font-semibold transition-colors group-hover/icon-button:bg-primary-strong group-aria-expanded/icon-button:bg-primary-strong"
typographyType="body-xs"
>
{itemCount > 9 ? "9+" : itemCount}
</Typography>
)}
</button>
);
},
);
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { Popover } from "@headlessui/react";
import { mdiBell, mdiBellOutline } from "@mdi/js";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { useSelector } from "react-redux";

import { useExtensionContext } from "../../../../ExtensionProvider";
Expand Down Expand Up @@ -71,6 +77,7 @@ const NotificationsContent: React.FC<{ popoverOpen: boolean }> = ({
return (
<>
<Popover.Button
aria-label="Notifications"
as={IconButton}
disabled={visibleCount === 0}
iconPath={visibleCount > 0 ? mdiBell : mdiBellOutline}
Expand Down
11 changes: 6 additions & 5 deletions src/renderer/src/views/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import { useSelector } from "react-redux";

import { useWindowContext } from "../../../contexts";
import { Typography } from "../../../ui/components/typography/Typography";
import {
nxmPanelClose,
nxmPanelOpen,
} from "../../../ui/icon-paths";
import { nxmPanelClose, nxmPanelOpen } from "../../../ui/icon-paths";
import {
activeProfile as activeProfileSelector,
gameProfiles as gameProfilesSelector,
Expand Down Expand Up @@ -62,6 +59,8 @@ export const Header: FC = () => {
>
<IconButton
appearance="secondary"
aria-expanded={!menuIsCollapsed}
aria-label={menuIsCollapsed ? "Open menu" : "Collapse menu"}
iconPath={menuIsCollapsed ? nxmPanelOpen : nxmPanelClose}
title={menuIsCollapsed ? "Open menu" : "Collapse menu"}
onClick={handleToggleMenu}
Expand All @@ -74,7 +73,9 @@ export const Header: FC = () => {
<span className="shrink-0 text-neutral-strong">{title}</span>

{profileName && (
<span className="min-w-0 max-w-[33%] truncate text-neutral-subdued">{profileName}</span>
<span className="max-w-[33%] min-w-0 truncate text-neutral-subdued">
{profileName}
</span>
)}
</Typography>
</div>
Expand Down
Loading