Skip to content
Merged
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
19 changes: 11 additions & 8 deletions src/renderer/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,9 @@
--button-default-borderColor-rest: transparent;
}

/* Row hover is a light glass wash instead of the solid Classic fill. */
.gitify-translucent .hover\:bg-gitify-notification-hover:hover {
/* Row hover/focus is a light glass wash instead of the solid Classic fill. */
.gitify-translucent .hover\:bg-gitify-notification-hover:hover,
.gitify-translucent .focus-within\:bg-gitify-notification-hover:focus-within {
background-color: var(--gitify-glass-control-rest);
}

Expand Down Expand Up @@ -278,10 +279,10 @@
/* Row/header action cluster: a frosted, hairlined surface (the macOS tooltip
* material) so the row content never shows through the controls. This replaces
* the old bare-icon treatment, which relied on fading the row content out from
* beneath the buttons. The `:hover` variant guards against the layered
* `group-hover:bg-*` utility winning on specificity when the row is hovered. */
* beneath the buttons. Keep the same surface during hover and keyboard focus. */
.gitify-translucent .gitify-hover-actions,
.gitify-translucent .group:hover .gitify-hover-actions {
.gitify-translucent .group:hover .gitify-hover-actions,
.gitify-translucent .group:focus-within .gitify-hover-actions {
background-color: light-dark(
color-mix(in oklab, var(--bgColor-default), transparent 8%),
color-mix(in oklab, var(--bgColor-neutral-emphasis), transparent 30%)
Expand All @@ -296,7 +297,8 @@
* background under the row's hover wash) so the rail blends into the row
* while still hiding the content beneath it. */
.gitify-translucent.gitify-linux .gitify-hover-actions,
.gitify-translucent.gitify-linux .group:hover .gitify-hover-actions {
.gitify-translucent.gitify-linux .group:hover .gitify-hover-actions,
.gitify-translucent.gitify-linux .group:focus-within .gitify-hover-actions {
background:
linear-gradient(var(--gitify-glass-control-rest), var(--gitify-glass-control-rest)),
var(--gitify-background);
Expand Down Expand Up @@ -354,13 +356,14 @@
--button-invisible-bgColor-hover: transparent;
--button-invisible-bgColor-active: transparent;

/* Bare icons still need affordance: dimmed at rest, full colour on hover,
/* Bare icons still need affordance: dimmed at rest, full colour on hover/focus,
* a press dip on active. */
opacity: 0.65;
transition: opacity 120ms ease-in-out;
}

.gitify-translucent .gitify-hover-actions :where(button):hover {
.gitify-translucent .gitify-hover-actions :where(button):hover,
.gitify-translucent .gitify-hover-actions :where(button):focus-visible {
opacity: 1;
}

Expand Down
6 changes: 3 additions & 3 deletions src/renderer/components/notifications/NotificationRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function NotificationRow({
className={cn(
'group relative border-b',
'pl-2.75 pr-1 py-0.75',
'text-gitify-font border-gitify-notification-border hover:bg-gitify-notification-hover',
'text-gitify-font border-gitify-notification-border hover:bg-gitify-notification-hover focus-within:bg-gitify-notification-hover',
'min-h-0 min-w-0',
isNotificationRead && Opacity.READ,
)}
Expand Down Expand Up @@ -144,12 +144,12 @@ export function NotificationRow({
title={notification.display.title}
>
<NotificationTitle title={notification.subject.title} />
{/* Hidden (keeping its space) while the row is hovered, so the
{/* Hidden (keeping its space) while the row is hovered or focused, so the
full-height action rail can never leave the number partially
rendered. */}
<Text
className={cn(
'text-xxs ml-auto mr-2 group-hover:invisible',
'text-xxs ml-auto mr-2 group-hover:invisible group-focus-within:invisible',
Opacity.READ,
(isGroupByDate() || !showNumber) && 'hidden',
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,61 @@ describe('renderer/components/notifications/NotificationRow visual regression',
});

describe.each(THEME_VARIANTS)('$theme $designLanguage', ({ designLanguage, theme }) => {
it('reveals row and header actions on focus with a backdrop, then hides them on blur', async () => {
renderRoute(<NotificationsRoute />, {
theme,
designLanguage,
accounts: [mockGitHubCloudAccount],
settings: { showAccountHeader: true },
notifications,
});
await page.getByTestId('visual-root').hover({ position: { x: 480, y: 380 } });

for (const testId of [
'account-toggle',
'repository-mark-as-read',
'notification-mark-as-read',
]) {
const button = document.querySelector<HTMLButtonElement>(`[data-testid="${testId}"]`);
const rail = button?.closest<HTMLElement>('.gitify-hover-actions');
if (!button || !rail) {
throw new Error(`Missing action rail for ${testId}`);
}

await waitFor(() => expect(getComputedStyle(rail).opacity).toBe('0'));
button.focus();
expect(button).toHaveFocus();
await waitFor(() => expect(getComputedStyle(rail).opacity).toBe('1'));

const { backgroundColor, backgroundImage } = getComputedStyle(rail);
expect(backgroundColor !== 'rgba(0, 0, 0, 0)' || backgroundImage !== 'none').toBe(true);

button.blur();
await waitFor(() => expect(getComputedStyle(rail).opacity).toBe('0'));
}
});

it('hides the notification number while its actions have focus', async () => {
await renderNotifications(theme, designLanguage);
await page.getByTestId('visual-root').hover({ position: { x: 480, y: 380 } });

const button = document.querySelector<HTMLButtonElement>(
'[data-testid="notification-mark-as-read"]',
);
const reference = document.querySelector<HTMLElement>(
'[data-testid="notification-reference"]',
);
if (!button || !reference) {
throw new Error('First notification action or reference is missing');
}

expect(getComputedStyle(reference).visibility).toBe('visible');
button.focus();
expect(getComputedStyle(reference).visibility).toBe('hidden');
button.blur();
expect(getComputedStyle(reference).visibility).toBe('visible');
});

it('reveals the action rail on hover', async () => {
await renderNotifications(theme, designLanguage);
const { row } = await hoverFirstRow();
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 12 additions & 2 deletions src/renderer/components/primitives/HoverGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@ interface HoverGroupProps {
fullHeight?: boolean;
}

// Keep state-prefixed classes: the plain background classes also identify
// header cards in App.css and would apply their geometry to the action rail.
const FOCUS_BACKGROUNDS = {
'group-hover:bg-gitify-account-rest': 'group-focus-within:bg-gitify-account-rest',
'group-hover:bg-gitify-account-error': 'group-focus-within:bg-gitify-account-error',
'group-hover:bg-gitify-repository': 'group-focus-within:bg-gitify-repository',
'group-hover:bg-gitify-notification-hover': 'group-focus-within:bg-gitify-notification-hover',
} satisfies Record<HoverGroupProps['bgColor'], string>;

/**
* Action cluster revealed on row/header hover.
* Action cluster revealed on row/header hover or keyboard focus.
*
* Spans the full height of its host (`inset-y-0`) and carries its own backdrop
* (the `bgColor` fill plus the Glass treatment in `App.css`). In the
Expand All @@ -37,8 +46,9 @@ export function HoverGroup({ bgColor, children, fullHeight = false }: Readonly<H
'gitify-hover-actions',
fullHeight && 'gitify-hover-actions-full',
'absolute inset-y-0 right-0 pr-1',
'opacity-0 transition-opacity group-hover:opacity-100',
'opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100',
bgColor,
FOCUS_BACKGROUNDS[bgColor],
)}
direction="horizontal"
gap="none"
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions src/renderer/routes/routes.visual.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ async function captureRoute(
settings: { ...route.state?.settings, ...settings },
});

// These baselines capture idle routes. Hover/focus interactions have their
// own coverage; park the pointer on empty sidebar space between captures.
await page.getByTestId('visual-root').hover({ position: { x: 1, y: 399 } });
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}

await expect(page.getByTestId('visual-root')).toMatchScreenshot(
`${name}-${theme.toLowerCase()}-${designLanguage}`,
);
Expand Down
Loading