-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AppSideNav
- Component branch (HDS-3800) (#2384)
Co-authored-by: Alex <[email protected]> Co-authored-by: Jory Tindall <[email protected]> Co-authored-by: Cristiano Rastelli <[email protected]> Co-authored-by: Lee White <[email protected]>
- Loading branch information
1 parent
9130bb8
commit 70489d6
Showing
107 changed files
with
5,098 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
packages/components/src/components/hds/app-side-nav/index.hbs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
{{! | ||
Copyright (c) HashiCorp, Inc. | ||
SPDX-License-Identifier: MPL-2.0 | ||
}} | ||
{{! IMPORTANT: we need to add "squishies" here (~) because otherwise the whitespace added by Ember causes the empty element to still have visible padding - See https://handlebarsjs.com/guide/expressions.html#whitespace-control }} | ||
<div | ||
class={{this.classNames}} | ||
...attributes | ||
{{on "transitionstart" (fn this.setTransition "start")}} | ||
{{on "transitionend" (fn this.setTransition "end")}} | ||
{{! @glint-expect-error - https://github.com/josemarluedke/ember-focus-trap/issues/86 }} | ||
{{focus-trap isActive=this.shouldTrapFocus}} | ||
{{did-insert this.didInsert}} | ||
> | ||
<h2 class="sr-only" id="hds-app-side-nav-header">Application local navigation</h2> | ||
|
||
<div class="hds-app-side-nav__wrapper"> | ||
{{#if this.showToggleButton}} | ||
{{! template-lint-disable no-invalid-interactive}} | ||
<div class="hds-app-side-nav__overlay" {{on "click" this.toggleMinimizedStatus}} /> | ||
{{! template-lint-enable no-invalid-interactive}} | ||
<Hds::AppSideNav::ToggleButton | ||
aria-labelledby="hds-app-side-nav-header" | ||
aria-expanded={{if this.isMinimized "false" "true"}} | ||
@icon={{if this.isMinimized "chevrons-right" "chevrons-left"}} | ||
{{on "click" this.toggleMinimizedStatus}} | ||
/> | ||
{{/if}} | ||
|
||
<div class="hds-app-side-nav__wrapper-body"> | ||
{{~yield~}} | ||
</div> | ||
</div> | ||
</div> |
210 changes: 210 additions & 0 deletions
210
packages/components/src/components/hds/app-side-nav/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
/** | ||
* Copyright (c) HashiCorp, Inc. | ||
* SPDX-License-Identifier: MPL-2.0 | ||
*/ | ||
|
||
import Component from '@glimmer/component'; | ||
import { tracked } from '@glimmer/tracking'; | ||
import { action } from '@ember/object'; | ||
import { registerDestructor } from '@ember/destroyable'; | ||
|
||
export interface HdsAppSideNavSignature { | ||
Args: { | ||
isResponsive?: boolean; | ||
isCollapsible?: boolean; | ||
isMinimized?: boolean; | ||
onToggleMinimizedStatus?: (arg: boolean) => void; | ||
onDesktopViewportChange?: (arg: boolean) => void; | ||
}; | ||
Blocks: { | ||
default?: []; | ||
}; | ||
Element: HTMLDivElement; | ||
} | ||
|
||
export default class HdsAppSideNav extends Component<HdsAppSideNavSignature> { | ||
@tracked isMinimized; | ||
@tracked isAnimating = false; | ||
@tracked isDesktop = true; | ||
|
||
body!: HTMLElement; | ||
bodyInitialOverflowValue = ''; | ||
desktopMQ: MediaQueryList; | ||
containersToHide!: NodeListOf<Element>; | ||
|
||
desktopMQVal = getComputedStyle(document.documentElement).getPropertyValue( | ||
'--hds-app-desktop-breakpoint' | ||
); | ||
|
||
constructor(owner: unknown, args: HdsAppSideNavSignature['Args']) { | ||
super(owner, args); | ||
this.isMinimized = this.args.isMinimized ?? false; // sets the default state on 'desktop' viewports | ||
this.desktopMQ = window.matchMedia(`(min-width:${this.desktopMQVal})`); | ||
this.addEventListeners(); | ||
registerDestructor(this, (): void => { | ||
this.removeEventListeners(); | ||
}); | ||
} | ||
|
||
addEventListeners(): void { | ||
document.addEventListener('keydown', this.escapePress, true); | ||
this.desktopMQ.addEventListener('change', this.updateDesktopVariable, true); | ||
// if not instantiated as minimized via arguments | ||
if (!this.args.isMinimized) { | ||
// set initial state based on viewport using a "synthetic" event | ||
const syntheticEvent = new MediaQueryListEvent('change', { | ||
matches: this.desktopMQ.matches, | ||
media: this.desktopMQ.media, | ||
}); | ||
this.updateDesktopVariable(syntheticEvent); | ||
} | ||
} | ||
|
||
removeEventListeners(): void { | ||
document.removeEventListener('keydown', this.escapePress, true); | ||
this.desktopMQ.removeEventListener( | ||
'change', | ||
this.updateDesktopVariable, | ||
true | ||
); | ||
} | ||
|
||
// controls if the component reacts to viewport changes | ||
get isResponsive(): boolean { | ||
return this.args.isResponsive ?? true; | ||
} | ||
|
||
// controls if users can collapse the appsidenav on 'desktop' viewports | ||
get isCollapsible(): boolean { | ||
return this.args.isCollapsible ?? false; | ||
} | ||
|
||
get shouldTrapFocus(): boolean { | ||
return this.isResponsive && !this.isDesktop && !this.isMinimized; | ||
} | ||
|
||
get showToggleButton(): boolean { | ||
return (this.isResponsive && !this.isDesktop) || this.isCollapsible; | ||
} | ||
|
||
get classNames(): string { | ||
const classes = [`hds-app-side-nav`]; | ||
|
||
// add specific class names for the different possible states | ||
if (this.isResponsive) { | ||
classes.push('hds-app-side-nav--is-responsive'); | ||
} | ||
if (!this.isDesktop && this.isResponsive) { | ||
classes.push('hds-app-side-nav--is-mobile'); | ||
} else { | ||
classes.push('hds-app-side-nav--is-desktop'); | ||
} | ||
if (this.isMinimized && this.isResponsive) { | ||
classes.push('hds-app-side-nav--is-minimized'); | ||
} else { | ||
classes.push('hds-app-side-nav--is-not-minimized'); | ||
} | ||
if (this.isAnimating) { | ||
classes.push('hds-app-side-nav--is-animating'); | ||
} | ||
|
||
return classes.join(' '); | ||
} | ||
|
||
synchronizeInert(): void { | ||
this.containersToHide?.forEach((element): void => { | ||
if (this.isMinimized) { | ||
element.setAttribute('inert', ''); | ||
} else { | ||
element.removeAttribute('inert'); | ||
} | ||
}); | ||
} | ||
|
||
lockBodyScroll(): void { | ||
if (this.body) { | ||
// Prevent page from scrolling when the dialog is open | ||
this.body.style.setProperty('overflow', 'hidden'); | ||
} | ||
} | ||
|
||
unlockBodyScroll(): void { | ||
// Reset page `overflow` property | ||
if (this.body) { | ||
this.body.style.removeProperty('overflow'); | ||
if (this.bodyInitialOverflowValue === '') { | ||
if (this.body.style.length === 0) { | ||
this.body.removeAttribute('style'); | ||
} | ||
} else { | ||
this.body.style.setProperty('overflow', this.bodyInitialOverflowValue); | ||
} | ||
} | ||
} | ||
|
||
@action | ||
escapePress(event: KeyboardEvent): void { | ||
if (event.key === 'Escape' && !this.isMinimized && !this.isDesktop) { | ||
this.isMinimized = true; | ||
this.synchronizeInert(); | ||
} | ||
} | ||
|
||
@action | ||
toggleMinimizedStatus(): void { | ||
this.isMinimized = !this.isMinimized; | ||
this.synchronizeInert(); | ||
|
||
const { onToggleMinimizedStatus } = this.args; | ||
|
||
if (typeof onToggleMinimizedStatus === 'function') { | ||
onToggleMinimizedStatus(this.isMinimized); | ||
} | ||
|
||
if (this.isMinimized) { | ||
this.unlockBodyScroll(); | ||
} else { | ||
this.lockBodyScroll(); | ||
} | ||
} | ||
|
||
@action | ||
didInsert(element: HTMLElement): void { | ||
this.containersToHide = element.querySelectorAll( | ||
'.hds-app-side-nav-hide-when-minimized' | ||
); | ||
this.body = document.body; | ||
// Store the initial `overflow` value of `<body>` so we can reset to it | ||
this.bodyInitialOverflowValue = | ||
this.body.style.getPropertyValue('overflow'); | ||
} | ||
|
||
@action | ||
setTransition(phase: string, event: TransitionEvent): void { | ||
// we only want to respond to `width` animation/transitions | ||
if (event.propertyName !== 'width') { | ||
return; | ||
} | ||
if (phase === 'start') { | ||
this.isAnimating = true; | ||
} else { | ||
this.isAnimating = false; | ||
} | ||
} | ||
|
||
@action | ||
updateDesktopVariable(event: MediaQueryListEvent): void { | ||
this.isDesktop = event.matches; | ||
|
||
// automatically minimize on narrow viewports (when not in desktop mode) | ||
this.isMinimized = !this.isDesktop; | ||
|
||
this.synchronizeInert(); | ||
|
||
const { onDesktopViewportChange } = this.args; | ||
|
||
if (typeof onDesktopViewportChange === 'function') { | ||
onDesktopViewportChange(this.isDesktop); | ||
} | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
packages/components/src/components/hds/app-side-nav/list/back-link.hbs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{{! | ||
Copyright (c) HashiCorp, Inc. | ||
SPDX-License-Identifier: MPL-2.0 | ||
}} | ||
|
||
<Hds::AppSideNav::List::Item> | ||
<Hds::Interactive | ||
class="hds-app-side-nav__list-item-link hds-app-side-nav__list-item-link--back-link" | ||
@current-when={{@current-when}} | ||
@models={{hds-link-to-models @model @models}} | ||
@query={{hds-link-to-query @query}} | ||
@replace={{@replace}} | ||
@route={{@route}} | ||
@isRouteExternal={{@isRouteExternal}} | ||
@href={{@href}} | ||
@isHrefExternal={{@isHrefExternal}} | ||
...attributes | ||
> | ||
<Hds::Icon class="hds-app-side-nav__list-item-icon-leading" @name="chevron-left" /> | ||
<span class="hds-app-side-nav__list-item-text hds-typography-body-200 hds-font-weight-medium"> | ||
{{@text}} | ||
</span> | ||
</Hds::Interactive> | ||
</Hds::AppSideNav::List::Item> |
20 changes: 20 additions & 0 deletions
20
packages/components/src/components/hds/app-side-nav/list/back-link.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** | ||
* Copyright (c) HashiCorp, Inc. | ||
* SPDX-License-Identifier: MPL-2.0 | ||
*/ | ||
|
||
import TemplateOnlyComponent from '@ember/component/template-only'; | ||
|
||
import type { HdsInteractiveSignature } from '../../interactive'; | ||
|
||
export interface HdsAppSideNavListBackLinkSignature { | ||
Args: HdsInteractiveSignature['Args'] & { | ||
text: string; | ||
}; | ||
Element: HdsInteractiveSignature['Element']; | ||
} | ||
|
||
const HdsAppSideNavListBackLink = | ||
TemplateOnlyComponent<HdsAppSideNavListBackLinkSignature>(); | ||
|
||
export default HdsAppSideNavListBackLink; |
19 changes: 19 additions & 0 deletions
19
packages/components/src/components/hds/app-side-nav/list/index.hbs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{{! | ||
Copyright (c) HashiCorp, Inc. | ||
SPDX-License-Identifier: MPL-2.0 | ||
}} | ||
|
||
<nav class="hds-app-side-nav__list-wrapper" aria-labelledby="hds-app-side-nav-header" ...attributes> | ||
{{yield (hash ExtraBefore=(component "hds/yield"))}} | ||
<ul class="hds-app-side-nav__list" role="list" aria-labelledby={{this.titleIds}}> | ||
{{yield | ||
(hash | ||
Item=(component "hds/app-side-nav/list/item") | ||
BackLink=(component "hds/app-side-nav/list/back-link") | ||
Title=(component "hds/app-side-nav/list/title" didInsertTitle=this.didInsertTitle) | ||
Link=(component "hds/app-side-nav/list/link") | ||
) | ||
}} | ||
</ul> | ||
{{yield (hash ExtraAfter=(component "hds/yield"))}} | ||
</nav> |
43 changes: 43 additions & 0 deletions
43
packages/components/src/components/hds/app-side-nav/list/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/** | ||
* Copyright (c) HashiCorp, Inc. | ||
* SPDX-License-Identifier: MPL-2.0 | ||
*/ | ||
|
||
import Component from '@glimmer/component'; | ||
import { action } from '@ember/object'; | ||
import { tracked } from '@glimmer/tracking'; | ||
import type { ComponentLike } from '@glint/template'; | ||
import type { HdsYieldSignature } from '../../yield'; | ||
import type { HdsAppSideNavListItemSignature } from './item'; | ||
import type { HdsAppSideNavListBackLinkSignature } from './back-link'; | ||
import type { HdsAppSideNavListTitleSignature } from './title'; | ||
import type { HdsAppSideNavListLinkSignature } from './link'; | ||
|
||
export interface HdsAppSideNavListSignature { | ||
Blocks: { | ||
default: [ | ||
{ | ||
ExtraBefore?: ComponentLike<HdsYieldSignature>; | ||
Item?: ComponentLike<HdsAppSideNavListItemSignature>; | ||
BackLink?: ComponentLike<HdsAppSideNavListBackLinkSignature>; | ||
Title?: ComponentLike<HdsAppSideNavListTitleSignature>; | ||
Link?: ComponentLike<HdsAppSideNavListLinkSignature>; | ||
ExtraAfter?: ComponentLike<HdsYieldSignature>; | ||
}, | ||
]; | ||
}; | ||
Element: HTMLElement; | ||
} | ||
|
||
export default class HdsAppSideNavList extends Component<HdsAppSideNavListSignature> { | ||
@tracked _titleIds: string[] = []; | ||
|
||
get titleIds(): string { | ||
return this._titleIds.join(' '); | ||
} | ||
|
||
@action | ||
didInsertTitle(titleId: string): void { | ||
this._titleIds = [...this._titleIds, titleId]; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
packages/components/src/components/hds/app-side-nav/list/item.hbs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{{! | ||
Copyright (c) HashiCorp, Inc. | ||
SPDX-License-Identifier: MPL-2.0 | ||
}} | ||
|
||
<li class="hds-app-side-nav__list-item" ...attributes> | ||
{{yield}} | ||
</li> |
Oops, something went wrong.