Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add alert-style badges to user menu #73

Merged
merged 2 commits into from
Mar 7, 2023
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
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");
});
});