Skip to content

Commit

Permalink
Convert User menu to X::DropdownList (#461)
Browse files Browse the repository at this point in the history
* Refactor nav dropdown

* Improve open/focus styles

* Improve badge

* Update tests

* Test ExternalLink

* Update nav.ts

* Chevron test

* Use `hasChevron`

* Remove unnecessary styles

* Revert some changes

* Improve hover
  • Loading branch information
jeffdaley authored Nov 29, 2023
1 parent 0e02946 commit 4d1e24d
Show file tree
Hide file tree
Showing 13 changed files with 230 additions and 96 deletions.
4 changes: 1 addition & 3 deletions web/app/components/header.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
<header
class="z-20 mb-7 border-b border-b-color-border-faint bg-color-page-faint"
>
<header class="mb-7 border-b border-b-color-border-faint bg-color-page-faint">
<Header::Nav />
</header>
127 changes: 63 additions & 64 deletions web/app/components/header/nav.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -41,70 +41,69 @@
@icon="plus"
class="pl-2.5 pr-4"
/>
<div class="relative">
{{#if this.userMenuHighlightIsShown}}
<Header::UserMenuHighlight />
{{/if}}
{{#if this.profile.picture}}
{{! Workaround until `referrerPolicy` is supported in dd.ToggleIcon }}
<img
src={{this.profile.picture}}
class="user-avatar"
role="presentation"
referrerpolicy="no-referrer"
/>
{{/if}}
<Hds::Dropdown as |dd|>
<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-github
@href={{this.gitHubRepoURL}}
@isHrefExternal={{true}}
@text="GitHub"
@icon="external-link"
class="user-menu-external-link"
/>
{{#if this.supportDocsURL}}
<dd.Interactive
data-test-user-menu-support
@href={{this.supportDocsURL}}
@isHrefExternal={{true}}
@text="Support"
@icon="external-link"
class="user-menu-external-link"
/>
{{/if}}
{{#if this.showSignOut}}
<dd.Interactive
data-test-user-menu-item="sign-out"
{{on "click" this.invalidateSession}}
@text="Sign out"
/>
{{/if}}
</Hds::Dropdown>
<div class="relative flex">
<X::DropdownList
@items={{this.dropdownListItems}}
@placement="bottom-end"
class="user-dropdown"
>
<:anchor as |dd|>
<dd.ToggleAction
@hasChevron={{true}}
aria-label="User menu"
class="user-menu-button -mr-1"
>
<Person::Avatar
@email={{this.profile.email}}
@imgURL={{this.profile.picture}}
@size="large"
data-test-user-menu-toggle
/>
</dd.ToggleAction>
{{#if this.userMenuHighlightIsShown}}
<Header::UserMenuHighlight />
{{/if}}
</:anchor>
<:header>
<div
{{did-insert this.onDropdownOpen}}
class="mb-1 border-b border-b-color-border-primary py-3.5 px-5"
>
<div class="font-semibold" data-test-user-menu-title>
{{this.profile.name}}
</div>
<div class="text-color-foreground-faint" data-test-user-menu-email>
{{this.profile.email}}
</div>
</div>
</:header>
<:item as |dd|>
<div
data-test-user-menu-item={{dasherize dd.attrs.label}}
class="w-full"
>
{{#if dd.attrs.route}}
<dd.LinkTo @route={{dd.attrs.route}}>
{{dd.attrs.label}}
{{#if dd.attrs.isNew}}
<Hds::Badge
@text="New"
class="highlighted-new-badge ml-1.5"
/>
{{/if}}
</dd.LinkTo>
{{else if dd.attrs.href}}
<dd.ExternalLink href={{dd.attrs.href}} @iconIsShown={{true}}>
{{dd.attrs.label}}
</dd.ExternalLink>
{{else if dd.attrs.action}}
<dd.Action {{on "click" dd.attrs.action}}>
{{dd.attrs.label}}
</dd.Action>
{{/if}}
</div>
</:item>
</X::DropdownList>
</div>
</div>
</nav>
54 changes: 46 additions & 8 deletions web/app/components/header/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ import { HERMES_GITHUB_REPO_URL } from "hermes/utils/hermes-urls";
import { SortByValue } from "./toolbar";
import FlagsService from "hermes/services/flags";

interface UserNavItem {
label: string;
isNew?: boolean;
}

interface UserNavLinkTo extends UserNavItem {
route: string;
}

interface UserNavExternalLink extends UserNavItem {
href: string;
}

interface UserNavAction extends UserNavItem {
action: () => void;
}

type UserNavMenuItem = UserNavLinkTo | UserNavExternalLink | UserNavAction;

interface HeaderNavComponentSignature {
Args: {};
}
Expand Down Expand Up @@ -44,6 +63,33 @@ export default class HeaderNavComponent extends Component<HeaderNavComponentSign
return this.configSvc.config.support_link_url;
}

protected get dropdownListItems(): UserNavMenuItem[] {
const defaultItems = [
{
label: "Email notifications",
route: "authenticated.settings",
isNew: this.emailNotificationsHighlightIsShown,
},
{
label: "GitHub",
href: this.gitHubRepoURL,
},
{
label: "Support",
href: this.supportDocsURL,
},
] as UserNavMenuItem[];

if (this.showSignOut) {
defaultItems.push({
label: "Sign out",
action: this.invalidateSession,
});
}

return defaultItems;
}

/**
* The default query params for the browse screens.
* Ensures a clear filter state when navigating tabs.
Expand Down Expand Up @@ -90,14 +136,6 @@ export default class HeaderNavComponent extends Component<HeaderNavComponentSign
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();
}
Expand Down
15 changes: 15 additions & 0 deletions web/app/components/x/dropdown-list/external-link.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<ExternalLink
data-test-x-dropdown-list-item-external-link
{{did-insert @registerElement}}
{{on "mouseenter" @focusMouseTarget}}
{{on "click" @onClick}}
role={{@role}}
aria-selected={{@isAriaSelected}}
tabindex="-1"
aria-checked={{@isAriaChecked}}
@iconIsShown={{@iconIsShown}}
class="x-dropdown-list-item-link {{if @isAriaSelected 'is-aria-selected'}}"
...attributes
>
{{yield}}
</ExternalLink>
20 changes: 20 additions & 0 deletions web/app/components/x/dropdown-list/external-link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Component from "@glimmer/component";
import { XDropdownListInteractiveComponentArgs } from "./_shared";

interface XDropdownListExternalLinkComponentSignature {
Element: HTMLAnchorElement;
Args: XDropdownListInteractiveComponentArgs & {
iconIsShown?: boolean;
};
Blocks: {
default: [];
};
}

export default class XDropdownListExternalLinkComponent extends Component<XDropdownListExternalLinkComponentSignature> {}

declare module "@glint/environment-ember-loose/registry" {
export default interface Registry {
"x/dropdown-list/external-link": typeof XDropdownListExternalLinkComponent;
}
}
9 changes: 9 additions & 0 deletions web/app/components/x/dropdown-list/item.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@
focusMouseTarget=(perform this.maybeFocusMouseTarget)
onClick=this.onClick
)
ExternalLink=(component
"x/dropdown-list/external-link"
role=@listItemRole
isAriaSelected=this.isAriaSelected
isAriaChecked=@isSelected
registerElement=this.registerElement
focusMouseTarget=(perform this.maybeFocusMouseTarget)
onClick=this.onClick
)
contentID=@contentID
value=@value
attrs=@attributes
Expand Down
5 changes: 5 additions & 0 deletions web/app/components/x/dropdown-list/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import XDropdownListActionComponent from "./action";
import XDropdownListLinkToComponent from "./link-to";
import { restartableTask, timeout } from "ember-concurrency";
import { FocusDirection } from ".";
import XDropdownListExternalLinkComponent from "./external-link";

type XDropdownListInteractiveComponentBoundArgs =
| "role"
Expand All @@ -27,6 +28,10 @@ export interface XDropdownListItemAPI {
typeof XDropdownListLinkToComponent,
XDropdownListInteractiveComponentBoundArgs
>;
ExternalLink: WithBoundArgs<
typeof XDropdownListExternalLinkComponent,
XDropdownListInteractiveComponentBoundArgs
>;
contentID: string;
value: any;
selected?: any;
Expand Down
49 changes: 40 additions & 9 deletions web/app/styles/components/nav.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,43 @@
}

.user-buttons {
@apply order-4 flex items-center justify-end space-x-1.5 sm:ml-6 md:ml-8;
@apply order-4 flex items-center justify-end gap-2.5 sm:ml-6 md:ml-8;
}

.user-avatar {
@apply pointer-events-none absolute top-1/2 left-[3px] z-10 h-[30px] w-[30px] -translate-y-1/2 rounded-[3px];
.user-dropdown {
li {
a,
button {
@apply px-4;
}
}
}

.user-menu-button {
&:hover {
.avatar {
box-shadow: 0 0 0 3px var(--token-color-border-strong);
}
}

&:focus,
&.open {
.avatar {
@apply shadow-focus-ring-action;
}
}
}

.user-menu-button {
&:hover,
&:focus,
&.open {
@apply outline-none;

.avatar {
@apply outline outline-1 outline-color-page-faint;
}
}
}

.user-menu-external-link {
Expand Down Expand Up @@ -69,12 +101,11 @@
}
}

.highlighted-new {
.hds-dropdown-list-item__interactive-text {
&::after {
content: "New";
@apply ml-0.5 rounded bg-color-surface-highlight px-2 py-1 text-body-100 font-medium text-color-foreground-highlight;
}
.is-aria-selected .highlighted-new-badge {
@apply bg-color-palette-alpha-300 mix-blend-normal;

.hds-badge__text {
@apply text-color-page-primary;
}
}
}
2 changes: 1 addition & 1 deletion web/app/templates/application.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
</div>

{{#if this.animatedToolsAreShown}}
<AnimatedTools class="!z-20" />
<AnimatedTools />
{{/if}}
2 changes: 2 additions & 0 deletions web/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ module.exports = {
boxShadow: {
"surface-low": "var(--token-surface-low-box-shadow)",
"surface-mid": "var(--token-surface-mid-box-shadow)",
"focus-ring-action": "var(--token-focus-ring-action-box-shadow)",
},
colors: {
// Note: Because the tokens are HEX and not RGB values,
Expand Down Expand Up @@ -169,6 +170,7 @@ module.exports = {

// Alpha
"color-palette-alpha-100": "var(--token-color-palette-alpha-100)",
"color-palette-alpha-300": "var(--token-color-palette-alpha-300)",

// Neutral
"color-palette-neutral-50": "var(--token-color-palette-neutral-50)",
Expand Down
2 changes: 1 addition & 1 deletion web/tests/acceptance/application-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ module("Acceptance | application", function (hooks) {
await click("[data-test-user-menu-toggle]");

assert
.dom("[data-test-user-menu-support")
.dom("[data-test-user-menu-item='support'] a")
.hasAttribute("href", TEST_SUPPORT_URL);
});
});
Loading

0 comments on commit 4d1e24d

Please sign in to comment.