Commit be929ed2 authored by Alejandro Celaya's avatar Alejandro Celaya Committed by Alejandro Celaya

Fix mouse events in pending updates notification

parent 9eea0434
import { Button, DownloadIcon } from '@hypothesis/frontend-shared';
import classnames from 'classnames';
import type { ComponentChildren } from 'preact';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { pluralize } from '../../shared/pluralize';
......@@ -17,6 +18,27 @@ export type PendingUpdatesNotificationProps = {
clearTimeout_?: typeof clearTimeout;
};
type HideableBlockProps = {
hidden: boolean;
children: ComponentChildren;
};
/**
* A component that can be hidden but will be unconditionally displayed when the
* group it belongs to is focused or hovered
*/
function HideableBlock({ hidden, children }: HideableBlockProps) {
return (
<span
className={classnames('group-hover:inline group-focus:inline', {
hidden,
})}
>
{children}
</span>
);
}
const collapseDelay = 5000;
function PendingUpdatesNotification({
......@@ -66,25 +88,23 @@ function PendingUpdatesNotification({
classes={classnames(
'flex gap-1.5 items-center py-1 px-2',
'rounded shadow-lg bg-gray-900 text-white',
'group focus-visible-ring',
)}
onMouseEnter={() => setCollapsed(false)}
onFocus={() => setCollapsed(false)}
onMouseLeave={() => !timeout.current && setCollapsed(true)}
onBlur={() => !timeout.current && setCollapsed(true)}
>
<DownloadIcon className="w-em h-em opacity-80" />
{!collapsed && (
<span data-testid="full-notification" className="whitespace-nowrap">
Load <span className="font-bold">{pendingUpdateCount}</span>{' '}
{pluralize(pendingUpdateCount, 'update', 'updates')}{' '}
<span className="sr-only">by pressing l</span>
</span>
)}
{collapsed && (
<span data-testid="collapsed-notification" className="font-bold">
{pendingUpdateCount}
</span>
)}
<div
data-testid={
collapsed ? 'collapsed-notification' : 'full-notification'
}
className="flex gap-1"
>
<HideableBlock hidden={collapsed}>Load</HideableBlock>
<span className="font-bold">{pendingUpdateCount}</span>
<HideableBlock hidden={collapsed}>
{pluralize(pendingUpdateCount, 'update', 'updates')}
</HideableBlock>
</div>
<span className="sr-only">by pressing l</span>
</Button>
</div>
);
......
......@@ -133,19 +133,6 @@ function SidebarView({
return (
<div className="relative">
<h2 className="sr-only">Annotations</h2>
{showFilterControls && <FilterControls withCardContainer />}
<LoginPromptPanel onLogin={onLogin} onSignUp={onSignUp} />
{hasDirectLinkedAnnotationError && (
<SidebarContentError
errorType="annotation"
onLoginRequest={onLogin}
showClearSelection={true}
/>
)}
{hasDirectLinkedGroupError && (
<SidebarContentError errorType="group" onLoginRequest={onLogin} />
)}
{!hasContentError && <SidebarTabs isLoading={isLoading} />}
{pendingUpdatesNotification && (
<div
className={classnames(
......@@ -159,6 +146,19 @@ function SidebarView({
<PendingUpdatesNotification />
</div>
)}
{showFilterControls && <FilterControls withCardContainer />}
<LoginPromptPanel onLogin={onLogin} onSignUp={onSignUp} />
{hasDirectLinkedAnnotationError && (
<SidebarContentError
errorType="annotation"
onLoginRequest={onLogin}
showClearSelection={true}
/>
)}
{hasDirectLinkedGroupError && (
<SidebarContentError errorType="group" onLoginRequest={onLogin} />
)}
{!hasContentError && <SidebarTabs isLoading={isLoading} />}
{showLoggedOutMessage && <LoggedOutMessage onLogin={onLogin} />}
</div>
);
......
......@@ -134,40 +134,4 @@ describe('PendingUpdatesNotification', () => {
}
});
});
['onMouseLeave', 'onBlur'].forEach(handler => {
it('collapses notification when mouse or focus leaves button', () => {
const wrapper = createComponent();
assert.isFalse(notificationIsCollapsed(wrapper));
wrapper.find('Button').prop(handler)();
wrapper.update();
assert.isTrue(notificationIsCollapsed(wrapper));
});
it('does not collapse notification when mouse or focus leaves button if timeout is in progress', () => {
fakeSetTimeout.returns(1);
const wrapper = createComponent();
assert.isFalse(notificationIsCollapsed(wrapper));
wrapper.find('Button').prop(handler)();
wrapper.update();
assert.isFalse(notificationIsCollapsed(wrapper));
});
});
['onMouseEnter', 'onFocus'].forEach(handler => {
it('expands notification when button is hovered or focused', async () => {
const promise = timeoutAsPromise();
const wrapper = createComponent();
await promise; // Wait for timeout callback to be invoked
wrapper.update();
assert.isTrue(notificationIsCollapsed(wrapper));
wrapper.find('Button').prop(handler)();
wrapper.update();
assert.isFalse(notificationIsCollapsed(wrapper));
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment