Skip to content

Commit

Permalink
Add alert-style badges to user menu (#73)
Browse files Browse the repository at this point in the history
* Add highlight dot to announce menuItem features

* Write tests
  • Loading branch information
jeffdaley authored Mar 7, 2023
1 parent 421f04c commit 9057120
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 43 deletions.
37 changes: 31 additions & 6 deletions web/app/components/header/nav.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@

<div class="primary-links">
<LinkTo
data-test-nav-link="all"
@route="authenticated.all"
@current-when="authenticated.all"
@query={{this.defaultBrowseScreenQueryParams}}
>
All Docs
</LinkTo>
<LinkTo
data-test-nav-link="my"
@route="authenticated.my"
@current-when="authenticated.my"
@query={{this.defaultBrowseScreenQueryParams}}
>
My Docs
</LinkTo>
<LinkTo
data-test-nav-link="drafts"
@route="authenticated.drafts"
@current-when="authenticated.drafts"
@query={{this.defaultBrowseScreenQueryParams}}
Expand All @@ -29,7 +32,7 @@
</LinkTo>
</div>

<Header::Search class="search-bar" />
<Header::Search data-test-nav-search-bar class="search-bar" />

<div class="user-buttons">
<Hds::Button
Expand All @@ -40,6 +43,9 @@
class="create-draft-button"
/>
<div class="relative">
{{#if this.userMenuHighlightIsShown}}
<Header::UserMenuHighlight />
{{/if}}
{{! Workaround until `referrerPolicy` is supported in dd.ToggleIcon }}
<img
src={{this.profile.picture}}
Expand All @@ -48,18 +54,37 @@
referrerpolicy="no-referrer"
/>
<Hds::Dropdown as |dd|>
<dd.ToggleIcon @text="User menu" @icon="user" />
<dd.Title @text={{this.profile.name}} class="text-body-200" />
<dd.Description @text={{this.profile.email}} class="text-body-200" />
<dd.ToggleIcon
data-test-user-menu-toggle
@text="User menu"
@icon="user"
/>
<dd.Title
data-test-user-menu-title
{{did-insert this.onDropdownOpen}}
{{will-destroy this.onDropdownClose}}
@text={{this.profile.name}}
class="text-body-200"
/>
<dd.Description
data-test-user-menu-email
@text={{this.profile.email}}
class="text-body-200"
/>
<dd.Separator class="mt-2" />
<dd.Interactive
data-test-user-menu-item="email-notifications"
@route="authenticated.settings"
@text="Email notifications"
class={{if
this.emailNotificationsHighlightIsShown
"highlighted-new"
}}
/>

<dd.Interactive
data-test-user-menu-item="sign-out"
{{on "click" this.invalidateSession}}
@text="Sign Out"
@text="Sign out"
/>
</Hds::Dropdown>
</div>
Expand Down
36 changes: 0 additions & 36 deletions web/app/components/header/nav.js

This file was deleted.

82 changes: 82 additions & 0 deletions web/app/components/header/nav.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import ConfigService from "hermes/services/config";
import SessionService from "hermes/services/session";
import RouterService from "@ember/routing/router-service";
import AuthenticatedUserService, {
AuthenticatedUser,
} from "hermes/services/authenticated-user";
import window from "ember-window-mock";
import { tracked } from "@glimmer/tracking";

interface NavComponentSignature {
Args: {};
}

export default class NavComponent extends Component<NavComponentSignature> {
@service("config") declare configSvc: ConfigService;
@service declare session: SessionService;
@service declare router: RouterService;
@service declare authenticatedUser: AuthenticatedUserService;

protected get profile(): AuthenticatedUser {
return this.authenticatedUser.info;
}

protected get currentRouteName(): string {
return this.router.currentRouteName;
}

/**
* The default query params for the browse screens.
* Ensures a clear filter state when navigating tabs.
*/
protected defaultBrowseScreenQueryParams = {
docType: [],
owners: [],
page: 1,
product: [],
status: [],
sortBy: "dateDesc",
};

/**
* Whether to the "new" badge should appear on the email notifications menuitem.
* Will be false if the user has previously closed the dropdown.
*/
@tracked protected emailNotificationsHighlightIsShown =
window.localStorage.getItem("emailNotificationsHighlightIsShown") !==
"false";

/**
* Whether a highlight icon should appear over the user avatar.
* True when the user hasn't seen the menu's active highlights,
* (i.e., when we've just announced a feature), as determined by localStorage.
* Set false when the user menu is opened.
*/
@tracked protected userMenuHighlightIsShown =
this.emailNotificationsHighlightIsShown;

/**
* The actions to take when the dropdown menu is opened.
* Force-hides the highlight icon if it's open.
* (We assume the user to have seen the highlight when they open the menu.)
*/
@action protected onDropdownOpen(): void {
this.userMenuHighlightIsShown = false;
window.localStorage.setItem("emailNotificationsHighlightIsShown", "false");
}

/**
* The actions to take when the dropdown menu is closed.
* Force-hides the emailNotificationsHighlight if it's visible.
*/
@action protected onDropdownClose(): void {
this.emailNotificationsHighlightIsShown = false;
}

@action protected invalidateSession(): void {
this.session.invalidate();
}
}
12 changes: 12 additions & 0 deletions web/app/components/header/user-menu-highlight.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div
data-test-user-menu-highlight
class="absolute h-4 w-4 top-[3px] left-[33px] z-20 -translate-y-1/2 -translate-x-1/2 grid place-items-center"
>
<div
class="h-4 w-4 absolute bg-gradient-to-tr from-color-palette-purple-200 to-color-palette-purple-100 rounded-full opacity-75"
></div>

<div
class="h-2.5 w-2.5 rounded-full bg-color-page-faint border-4 border-color-palette-purple-200 relative"
></div>
</div>
7 changes: 7 additions & 0 deletions web/app/components/header/user-menu-highlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Component from "@glimmer/component";

interface HeaderUserMenuHighlightsSignature {
Args: {};
}

export default class HeaderUserMenuHighlights extends Component<HeaderUserMenuHighlightsSignature> {}
3 changes: 2 additions & 1 deletion web/app/services/authenticated-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import FetchService from "hermes/services/fetch";
import SessionService from "./session";

export interface AuthenticatedUser {
name: string;
email: string;
given_name: string;
picture: string;
Expand All @@ -30,7 +31,7 @@ export default class AuthenticatedUserService extends Service {
@service declare store: Store;

@tracked subscriptions: Subscription[] | null = null;
@tracked private _info: AuthenticatedUser | null = null;
@tracked _info: AuthenticatedUser | null = null;

get info(): AuthenticatedUser {
assert("Authenticated must exist", this._info);
Expand Down
9 changes: 9 additions & 0 deletions web/app/styles/components/nav.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,13 @@
.create-draft-button {
@apply rounded-full w-9 p-0;
}

.highlighted-new {
.hds-dropdown-list-item__interactive-text {
&::after {
content: "New";
@apply text-body-100 px-2 py-1 rounded font-medium bg-color-surface-highlight text-color-foreground-highlight ml-0.5;
}
}
}
}
3 changes: 3 additions & 0 deletions web/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ module.exports = {
"color-surface-action": "var(--token-color-surface-action)",
"color-surface-critical": "var(--token-color-surface-critical)",
"color-surface-faint": "var(--token-color-surface-faint)",
"color-surface-highlight": "var(--token-color-surface-highlight)",
"color-surface-interactive": "var(--token-color-surface-interactive)",
"color-surface-interactive-hover":
"var(--token-color-surface-interactive-hover)",
Expand Down Expand Up @@ -83,6 +84,7 @@ module.exports = {
"color-foreground-faint": "var(--token-color-foreground-faint)",
"color-foreground-high-contrast":
"var(--token-color-foreground-high-contrast)",
"color-foreground-highlight": "var(--token-color-foreground-highlight)",
"color-foreground-primary": "var(--token-color-foreground-primary)",
"color-foreground-strong": "var(--token-color-foreground-strong)",
"color-foreground-warning": "var(--token-color-foreground-warning)",
Expand Down Expand Up @@ -164,6 +166,7 @@ module.exports = {
// Non-Semantic Color
"color-palette-blue-200": "var(--token-color-palette-blue-200)",
"color-palette-green-200": "var(--token-color-palette-green-200)",
"color-palette-purple-100": "var(--token-color-palette-purple-100)",
"color-palette-purple-200": "var(--token-color-palette-purple-200)",
},
},
Expand Down
87 changes: 87 additions & 0 deletions web/tests/integration/components/header/nav-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { module, test } from "qunit";
import { setupRenderingTest } from "ember-qunit";
import { click, render } from "@ember/test-helpers";
import { hbs } from "ember-cli-htmlbars";
import { setupWindowMock } from "ember-window-mock/test-support";
import AuthenticatedUserService from "hermes/services/authenticated-user";
import { setupMirage } from "ember-cli-mirage/test-support";
import window from "ember-window-mock";

module("Integration | Component | header/nav", function (hooks) {
setupRenderingTest(hooks);
setupWindowMock(hooks);
setupMirage(hooks);

hooks.beforeEach(function () {
let authenticatedUserSvc = this.owner.lookup(
"service:authenticated-user"
) as AuthenticatedUserService;

authenticatedUserSvc._info = {
email: "[email protected]",
given_name: "Foo",
name: "Foo Bar",
picture: "",
subscriptions: [],
};
});

test("it renders correctly", async function (assert) {
await render(hbs`
<Header::Nav />
`);

assert.dom(".header-nav").exists();
assert.dom('[data-test-nav-link="all"]').hasAttribute("href", "/all");
assert.dom('[data-test-nav-link="my"]').hasAttribute("href", "/my");
assert.dom('[data-test-nav-link="drafts"]').hasAttribute("href", "/drafts");

assert.dom("[data-test-nav-search-bar]").exists();

await click("[data-test-user-menu-toggle]");

assert.dom("[data-test-user-menu-title]").hasText("Foo Bar");
assert.dom("[data-test-user-menu-email]").hasText("[email protected]");

assert
.dom('[data-test-user-menu-item="email-notifications"]')
.hasText("Email notifications");

assert.dom('[data-test-user-menu-item="sign-out"]').hasText("Sign out");
});

test("it shows an icon when the user menu has something to highlight", async function (assert) {
await render(hbs`
<Header::Nav />
`);

assert.equal(
window.localStorage.getItem("emailNotificationsHighlightIsShown"),
null
);

assert.dom("[data-test-user-menu-highlight]").exists("highlight is shown");

await click("[data-test-user-menu-toggle]");

assert
.dom("[data-test-user-menu-highlight]")
.doesNotExist("highlight is hidden when the menu is open");

assert
.dom(".highlighted-new .hds-dropdown-list-item__interactive-text")
.hasPseudoElementStyle(
"after",
{ content: '"New"' },
"highlighted link has the correct class and pseudoElement text"
);

// close and reopen the menu
await click("[data-test-user-menu-toggle]");
await click("[data-test-user-menu-toggle]");

assert
.dom(".highlighted-new")
.doesNotExist("highlight is hidden after the menu is closed");
});
});

0 comments on commit 9057120

Please sign in to comment.