Sidebar is a component used to display additional information to the side of a page that may be fixed or collapsible.
+
+
Overview
+
Sidebars are often used for navigation, but it's built to be flexible and extensible so that it can be used for any content. Sidebar supports a header and footer component with minimal default styling.
+
+
Basic sidebar
+
The default setting for sidebar is mode="side" and side="left".
+
+
Implementing the main and sidebar content
+
The main and sidebar content should be placed inside of the <daff-sidebar-viewport>. The sidebar content should be placed inside of the <daff-sidebar>.
+
+
A viewport navigation can be:
+
+
Placed alongside the <daff-sidebar> using the [daff-sidebar-viewport-nav] selector.
Additionally, a [daff-sidebar-viewport-nav] that is paired with a side-fixed sidebar can be placed above or beside to the sidebar. It's placed above by default but can be updated by using the navPlacement property on the <daff-sidebar-viewport>.
+
+
Placed inside of the viewport content by omitting the [daff-sidebar-viewport-nav] selector from the nav component.
The BrowserAnimationsModule or NoopAnimationsModule must be imported in the particular Angular @NgModule the sidebar is used in for the sidebar to render and work properly.
+
+
Header and footer
+
The <daff-sidebar-header> includes optional title ([daffSidebarHeaderTitle]) and action ([daffSidebarHeaderAction]) selectors, and a slot to render custom content. The action selector should be used along with the <daff-icon-button> (view Button Documentation) to make sure that the action is positioned correctly and it passes WCAG guidelines.
+
+
The <daff-sidebar-footer> is a "holder" component with minimal default styling. Its main purpose is to position the footer at the bottom of the sidebar, allowing the sidebar's content to overflow and scroll while ensuring that the footer remains constantly visible.
+
+
Both the header and footer are optional components that will not render in the DOM if they are not used.
+
+
Opening and closing a sidebar
+
The open property is used to set the open state for a sidebar.
+
+
By default, sidebar supports two methods of closing itself: clicking on the backdrop of the sidebar viewport or pressing ESC when the sidebar has focus.
+
+
+
+
+
Method
+
+
+
+
+
+
+ (backdropClicked)
+
+
Set on the <daff-sidebar-viewport>
+
+
+
+ (escapePressed)
+
+
Set on the <daff-sidebar>
+
+
+
+
+
Modes
+
<daff-sidebar> can be rendered four different ways by using the mode property. If mode is not specified, side is used by default.
+
+
+
+
+
Mode
+
Description
+
+
+
+
+
side
+
Sidebar is placed alongside the content
+
+
+
side-fixed
+
Sidebar is placed alongside the content and will scroll separately from the content
+
+
+
over
+
Sidebar slides over the rest of the content in the viewport and covering it with a backdrop
+
+
+
under
+
Sidebar freezes in place and and the content slides above it, while also being covered by a backdrop
+
+
+
+
+
Sides
+
<daff-sidebar> can be positioned on either side of a screen by using the side property. If side is not specified, left is used by default.
+
+
+
+
+
Side
+
Description
+
+
+
+
+
left
+
Places sidebar on the left side of the screen
+
+
+
right
+
Places sidebar on the right side of the screen
+
+
+
+
+
Custom styles
+
Changing a sidebar's width
+
The default width of a sidebar is 240px. It can be changed by setting it via CSS:
+
+
daff-sidebar {
+ width: 320px;
+}
+
+
Changing a side-fixed sidebar's top offset position
+
The default offset position of a sidebar is 0px. The --daff-sidebar-side-fixed-top-shift variable can be used to adjust the top offset position for a primary sidebar and its viewport content.
+
+
body {
+ --daff-sidebar-side-fixed-top-shift: 64px;
+}
+
+
Examples
+
Over and under sidebars
-
+
+
Side fixed sidebar
-
+
+
Sidebar with sticky content
-
+
-
-
-
\ No newline at end of file
+
Accessibility
+
A meaningful role should be set on all sidebars depending on how they are used. When the <daff-sidebar-header> is not used or there is no title for the sidebar, a meaningful aria-label should be set to describe the sidebar.
+
+
Focus
+
Focus trapping is enabled for over and under modes, and disabled for side and side-fixed modes. When a sidebar is opened, the first tabbable element within the will receive focus. When a sidebar is closed, the element that was focused before the sidebar was opened will be re-focused.
\ No newline at end of file
diff --git a/apps/design-land/src/app/sidebar/sidebar.component.ts b/apps/design-land/src/app/sidebar/sidebar.component.ts
index 96ae6feaf7..c713f74d5a 100644
--- a/apps/design-land/src/app/sidebar/sidebar.component.ts
+++ b/apps/design-land/src/app/sidebar/sidebar.component.ts
@@ -1,8 +1,12 @@
-import { Component } from '@angular/core';
+import {
+ ChangeDetectionStrategy,
+ Component,
+} from '@angular/core';
@Component({
selector: 'design-land-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DesignLandSidebarComponent {}
diff --git a/apps/design-land/src/app/sidebar/sidebar.module.ts b/apps/design-land/src/app/sidebar/sidebar.module.ts
index 41a4d102d5..c521915a44 100644
--- a/apps/design-land/src/app/sidebar/sidebar.module.ts
+++ b/apps/design-land/src/app/sidebar/sidebar.module.ts
@@ -1,6 +1,8 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
+import { DaffArticleModule } from '@daffodil/design/article';
+
import { DesignLandSidebarRoutingModule } from './sidebar-routing.module';
import { DesignLandSidebarComponent } from './sidebar.component';
import { DesignLandArticleEncapsulatedModule } from '../core/article-encapsulated/article-encapsulated.module';
@@ -12,6 +14,7 @@ import { DesignLandExampleViewerModule } from '../core/code-preview/container/ex
],
imports: [
CommonModule,
+ DaffArticleModule,
DesignLandExampleViewerModule,
DesignLandSidebarRoutingModule,
DesignLandArticleEncapsulatedModule,
diff --git a/apps/design-land/src/scss/component-themes.scss b/apps/design-land/src/scss/component-themes.scss
index deeff0d7b0..89625c8a00 100644
--- a/apps/design-land/src/scss/component-themes.scss
+++ b/apps/design-land/src/scss/component-themes.scss
@@ -1,9 +1,11 @@
@use '../app/core/code-preview/component/code-preview-theme' as code-preview;
@use '../app/typography/typography-theme' as typography;
@use '../app/foundations/color/color-theme' as color;
+@use '../app/core/sidebar-viewport/sidebar-viewport-theme' as sidebar-viewport;
@mixin component-themes($theme) {
@include code-preview.code-preview-theme($theme);
- @include typography.typography-theme($theme);
- @include color.color-theme($theme);
-}
\ No newline at end of file
+ @include typography.typography-theme($theme);
+ @include color.color-theme($theme);
+ @include sidebar-viewport.sidebar-viewport-theme($theme);
+}
diff --git a/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.component.html b/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.component.html
index 3f0f5f6299..7eec23ab12 100644
--- a/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.component.html
+++ b/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.component.html
@@ -1,11 +1,8 @@
-
- Accordion
- Article
+
+ Sidebar content
-
-
+
+ Page content
\ No newline at end of file
diff --git a/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.component.scss b/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.component.scss
new file mode 100644
index 0000000000..5e4783b017
--- /dev/null
+++ b/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.component.scss
@@ -0,0 +1,7 @@
+daff-sidebar-viewport {
+ height: 288px;
+}
+
+.basic-sidebar {
+ border-right: 1px solid rgb(var(--daff-theme-contrast-rgb), 0.1);
+}
\ No newline at end of file
diff --git a/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.component.ts b/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.component.ts
index 224221771e..a69233bfd5 100644
--- a/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.component.ts
+++ b/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.component.ts
@@ -7,7 +7,7 @@ import {
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'basic-sidebar',
templateUrl: './basic-sidebar.component.html',
- styles: ['daff-sidebar-viewport { height: 300px }'],
+ styleUrls: ['./basic-sidebar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BasicSidebarComponent {
diff --git a/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.module.ts b/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.module.ts
index 8fd4158eb1..8f99ec5dfa 100644
--- a/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.module.ts
+++ b/libs/design/sidebar/examples/src/basic-sidebar/basic-sidebar.module.ts
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import {
DaffSidebarModule,
DaffNavbarModule,
+ DaffLinkSetModule,
} from '@daffodil/design';
import { BasicSidebarComponent } from './basic-sidebar.component';
@@ -11,6 +12,7 @@ import { BasicSidebarComponent } from './basic-sidebar.component';
imports: [
DaffSidebarModule,
DaffNavbarModule,
+ DaffLinkSetModule,
],
declarations: [
BasicSidebarComponent,
diff --git a/libs/design/sidebar/examples/src/fixed-and-over-sidebar/fixed-and-over-sidebar.component.html b/libs/design/sidebar/examples/src/fixed-and-over-sidebar/fixed-and-over-sidebar.component.html
deleted file mode 100644
index 8cacd9570b..0000000000
--- a/libs/design/sidebar/examples/src/fixed-and-over-sidebar/fixed-and-over-sidebar.component.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
- Accordion
- Article
-
-
- Accordion
- Article
-
-
-
\ No newline at end of file
diff --git a/libs/design/sidebar/examples/src/under-sidebar/under-sidebar.component.scss b/libs/design/sidebar/examples/src/under-sidebar/under-sidebar.component.scss
deleted file mode 100644
index 3751e38a2e..0000000000
--- a/libs/design/sidebar/examples/src/under-sidebar/under-sidebar.component.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-nav {
- justify-content: space-between;
-}
-daff-sidebar-viewport { height: 300px }
-
-.inner-layout {
- position: relative;
- display: flex;
-}
-
-.filler {
- flex-grow: 1;
- height: 600px;
- padding: 0 16px;
-}
-
-.inner-sticky {
- position: sticky;
- padding: 20px;
- top: 0;
- height: 200px;
- width: 300px;
-}
\ No newline at end of file
diff --git a/libs/design/sidebar/examples/src/under-sidebar/under-sidebar.component.ts b/libs/design/sidebar/examples/src/under-sidebar/under-sidebar.component.ts
deleted file mode 100644
index 6b575e5ae0..0000000000
--- a/libs/design/sidebar/examples/src/under-sidebar/under-sidebar.component.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import {
- ChangeDetectionStrategy,
- Component,
-} from '@angular/core';
-
-@Component({
- // eslint-disable-next-line @angular-eslint/component-selector
- selector: 'under-sidebar',
- templateUrl: './under-sidebar.component.html',
- styleUrls: [
- 'under-sidebar.component.scss',
- ],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class UnderSidebarComponent {
- openLeft = false;
- openRight = false;
-
- openLeftSidebar(){
- this.openLeft = !this.openLeft;
- }
-
- openRightSidebar(){
- this.openRight = !this.openRight;
- }
-
- closeSidebar(){
- this.openLeft = false;
- this.openRight = false;
- }
-}
diff --git a/libs/design/sidebar/examples/src/under-sidebar/under-sidebar.module.ts b/libs/design/sidebar/examples/src/under-sidebar/under-sidebar.module.ts
deleted file mode 100644
index cec5fee30c..0000000000
--- a/libs/design/sidebar/examples/src/under-sidebar/under-sidebar.module.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { NgModule } from '@angular/core';
-
-import {
- DaffSidebarModule,
- DaffNavbarModule,
-} from '@daffodil/design';
-import { DaffButtonModule } from '@daffodil/design/button';
-
-import { UnderSidebarComponent } from './under-sidebar.component';
-
-@NgModule({
- imports: [
- DaffSidebarModule,
- DaffNavbarModule,
- DaffButtonModule,
- ],
- declarations: [
- UnderSidebarComponent,
- ],
- exports: [
- UnderSidebarComponent,
- ],
-})
-export class UnderSidebarModule { }
diff --git a/libs/design/src/molecules/sidebar/README.md b/libs/design/src/molecules/sidebar/README.md
index a8d0b15e4d..66496b9da0 100644
--- a/libs/design/src/molecules/sidebar/README.md
+++ b/libs/design/src/molecules/sidebar/README.md
@@ -1,20 +1,135 @@
-# Sidebar Component
+# Sidebar
+Sidebar is a component used to display additional information to the side of a page that may be fixed or collapsible.
-## Usage
+## Overview
+Sidebars are often used for navigation, but it's built to be flexible and extensible so that it can be used for any content. Sidebar supports a header and footer component with minimal default styling.
+### Basic sidebar
+The default setting for sidebar is `mode="side"` and `side="left"`.
+
+
+
+### Implementing the main and sidebar content
+The main and sidebar content should be placed inside of the ``. The sidebar content should be placed inside of the ``.
+
+A viewport navigation can either be:
+
+- Placed alongside the `` using the `[daff-sidebar-viewport-nav]` selector.
+
+```html
+
+
+
+
+ Sidebar content
+
+
+
+ Page content
+
+
```
-
-
-
Some Content
+
+- Placed inside of the viewport content by **omitting** the `[daff-sidebar-viewport-nav]` selector from the nav component.
+
+```html
+
+
+
+
+ Sidebar content
+
+
+
+ Page content
+
```
+#### Required Imports
+The `BrowserAnimationsModule` or `NoopAnimationsModule` must be imported in the particular Angular `@NgModule` the sidebar is used in for the sidebar to render and work properly.
+
+### Header and footer
+The `` includes optional title (`[daffSidebarHeaderTitle]`) and action (`[daffSidebarHeaderAction]`) selectors, and a slot to render custom content. The action selector should be used along with the `` (view [Button Documentation](../../atoms//button//README.md)) to make sure that the action is positioned correctly and it passes WCAG guidelines.
+
+The `` is a "holder" component with minimal default styling. Its main purpose is to position the footer at the bottom of the sidebar, allowing the sidebar's content to overflow and scroll while ensuring that the footer remains constantly visible.
+
+Both the header and footer are optional components that will not render in the DOM if they are not used.
+
+### Opening and closing a sidebar
+THe `open` property is used to set the open state for a sidebar.
+
+By default, sidebar supports two methods of closing itself: clicking on the backdrop of the sidebar viewport or pressing `ESC` when the sidebar has focus.
+
+| Method | ------------------------------------ |
+| `(backdropClicked)` | Set on the `` |
+| `(escapePressed)` | Set on the `` |
+
+### Modes
+`` can be rendered four different ways by using the `mode` property. If `mode` is not specified, `side` is used by default.
+
+| Mode | Description |
+| ---------- | ---------------------------------------------------------------------------------------------------- |
+| side | Sidebar is placed alongside the content |
+| side-fixed | Sidebar is placed alongside the content and will scroll separately from the content |
+| over | Sidebar slides over the rest of the content in the viewport and covering it with a backdrop |
+| under | Sidebar freezes in place and and the content slides above it, while also being covered by a backdrop |
+
+#### Over sidebar
+
+
+#### Under sidebar
+
+
+#### Two fixed sidebars on either side
+
+
+#### Fixed and over sidebar
+
+
+### Sides
+`` can be positioned on either side of a screen by using the `side` property. If `side` is not specificed, `left` is used by default.
+
+| Side | Description |
+| ----- | ---------------------------------------------- |
+| left | Places sidebar on the left side of the screen |
+| right | Places sidebar on the right side of the screen |
+
+### Custom styles
+
+#### Setting a sidebar's width
+The default size of a sidebar is `240px`. The width can be changed by setting it via CSS:
+
+```scss
+daff-sidebar {
+ width: 240px;
+}
+```
+
+### Changing a side-fixed sidebar's top offset position
+The default offset position of a sidebar is `0px`. The `--daff-sidebar-side-fixed-top-shift` variable can be used to adjust the top offset position for a primary sidebar and its viewport content.
+
+```scss
+body {
+ --daff-sidebar-side-fixed-top-shift: 64px;
+}
+```
+
+### Examples
+#### Over and under sidebars
+
+
+#### Side fixed sidebar
+
+
+### Accessibility
+A meaningful `role` should be set on all sidebars depending on how they are used.
-### Goals
+When the `` is not used or there is no title for the sidebar, a meaningful `aria-label` should be set to describe the sidebar.
-1. Support position sticky inside sidebar content
-2. Support 1 sidebar at a time on each side
-3. Support viewports inside of other viewports.
-4. A `global` flag that can be set to use `dvh`
-5. Sidebars as the window size changes from `side` to `over` and vice-versa by default.
-6. Where does the body scrollbar start and end? Does it hit the header of go to the top of the document.
\ No newline at end of file
+#### Focus
+Focus trapping is enabled for `over` and `under` modes, and disabled for `side` and `side-fixed` modes. When a sidebar is opened, the first tabbable element within the will receive focus. When a sidebar is opened, the first tabbable element within the will receive focus. When a sidebar is closed, the element that was focused before the sidebar was opened will be re-focused.
\ No newline at end of file
diff --git a/libs/design/src/molecules/sidebar/animation/sidebar-animation-state.spec.ts b/libs/design/src/molecules/sidebar/animation/sidebar-animation-state.spec.ts
index ae015c0fca..6baac43f83 100644
--- a/libs/design/src/molecules/sidebar/animation/sidebar-animation-state.spec.ts
+++ b/libs/design/src/molecules/sidebar/animation/sidebar-animation-state.spec.ts
@@ -1,17 +1,31 @@
import { DaffSidebarAnimationStates } from './sidebar-animation';
import { getAnimationState } from './sidebar-animation-state';
+import { DaffSidebarModeEnum } from '../helper/sidebar-mode';
describe('SidebarAnimationState Calculation', () => {
- it('should return `open` if it is open', () => {
- expect(getAnimationState(true)).toEqual(DaffSidebarAnimationStates.OPEN);
+ it('should return `none` if the sidebar mode is `side`', () => {
+ expect(getAnimationState(false, DaffSidebarModeEnum.Side)).toEqual(DaffSidebarAnimationStates.NONE);
+ expect(getAnimationState(true, DaffSidebarModeEnum.Side)).toEqual(DaffSidebarAnimationStates.NONE);
+ });
+
+ it('should return `side-fixed-open` or `side-fixed-closed` if the sidebar mode is `side-fixed`', () => {
+ expect(getAnimationState(false, DaffSidebarModeEnum.SideFixed)).toEqual(DaffSidebarAnimationStates.SIDEFIXEDCLOSED);
+ expect(getAnimationState(true, DaffSidebarModeEnum.SideFixed)).toEqual(DaffSidebarAnimationStates.SIDEFIXEDOPEN);
+ });
+
+ it('should return `under-open` if it is open and the sidebar mode is `under', () => {
+ expect(getAnimationState(true, DaffSidebarModeEnum.Under)).toEqual(DaffSidebarAnimationStates.UNDEROPEN);
});
- it('should return `none` if it is disabled`', () => {
- expect(getAnimationState(false, false)).toEqual(DaffSidebarAnimationStates.NONE);
- expect(getAnimationState(true, false)).toEqual(DaffSidebarAnimationStates.NONE);
+ it('should return `under-closed` if it is not open and the sidebar mode is `under', () => {
+ expect(getAnimationState(false, DaffSidebarModeEnum.Under)).toEqual(DaffSidebarAnimationStates.UNDERCLOSED);
});
it('should return `closed` if it is not open', () => {
- expect(getAnimationState(false, true)).toEqual(DaffSidebarAnimationStates.CLOSED);
+ expect(getAnimationState(false, DaffSidebarModeEnum.Over)).toEqual(DaffSidebarAnimationStates.CLOSED);
+ });
+
+ it('should return `open` if it is open', () => {
+ expect(getAnimationState(true, DaffSidebarModeEnum.Over)).toEqual(DaffSidebarAnimationStates.OPEN);
});
});
diff --git a/libs/design/src/molecules/sidebar/animation/sidebar-animation-state.ts b/libs/design/src/molecules/sidebar/animation/sidebar-animation-state.ts
index ed6151e2fa..b63c700b58 100644
--- a/libs/design/src/molecules/sidebar/animation/sidebar-animation-state.ts
+++ b/libs/design/src/molecules/sidebar/animation/sidebar-animation-state.ts
@@ -1,10 +1,30 @@
-export type DaffSidebarAnimationState = 'open' | 'closed' | 'none';
+import { DaffSidebarMode } from '../helper/sidebar-mode';
-export const getAnimationState = (open: boolean, enabled: boolean = true): DaffSidebarAnimationState => {
- if(!enabled){
+export type DaffSidebarAnimationState =
+'open' | 'closed' | 'under-open' | 'under-closed' | 'side-fixed-open' | 'side-fixed-closed' | 'none';
+
+export const getAnimationState = (open: boolean, mode: DaffSidebarMode): DaffSidebarAnimationState => {
+ if(mode === 'side') {
return 'none';
}
- if(open && enabled){
+
+ if(mode === 'side-fixed' && open) {
+ return 'side-fixed-open';
+ }
+
+ if(mode === 'side-fixed' && !open) {
+ return 'side-fixed-closed';
+ }
+
+ if(open && mode === 'under') {
+ return 'under-open';
+ }
+
+ if(!open && mode === 'under') {
+ return 'under-closed';
+ }
+
+ if(open) {
return 'open';
} else {
return 'closed';
diff --git a/libs/design/src/molecules/sidebar/animation/sidebar-animation.ts b/libs/design/src/molecules/sidebar/animation/sidebar-animation.ts
index 628dbf6692..85813faf8b 100644
--- a/libs/design/src/molecules/sidebar/animation/sidebar-animation.ts
+++ b/libs/design/src/molecules/sidebar/animation/sidebar-animation.ts
@@ -17,8 +17,8 @@ export const daffSidebarAnimations: {
readonly backdropTrigger: AnimationTriggerMetadata;
} = {
transformSidebar: trigger('transformSidebar', [
- // We remove the `transform` here completely, rather than setting it to zero, because:
- // 1. 3d transforms causes text to appear blurry on IE and Edge.
+ // We remove the `transform` here completely, rather than setting it to zero, because
+ // 3d transforms causes text to appear blurry on IE and Edge.
state('open', style({
transform: 'none',
})),
@@ -29,8 +29,8 @@ export const daffSidebarAnimations: {
transition('closed => open', animate(duration + ' ' + sidebarAnimateInTransition)),
]),
transformContent: trigger('transformContent', [
- // We remove the `transform` here completely, rather than setting it to zero, because:
- // 1. 3d transforms causes text to appear blurry on IE and Edge.
+ // We remove the `transform` here completely, rather than setting it to zero, because
+ // 3d transforms causes text to appear blurry on IE and Edge.
state('closed', style({
transform: 'none',
})),
@@ -55,5 +55,9 @@ export const daffSidebarAnimations: {
export enum DaffSidebarAnimationStates {
OPEN = 'open',
CLOSED = 'closed',
+ UNDEROPEN = 'under-open',
+ UNDERCLOSED = 'under-closed',
+ SIDEFIXEDOPEN = 'side-fixed-open',
+ SIDEFIXEDCLOSED = 'side-fixed-closed',
NONE = 'none'
}
diff --git a/libs/design/src/molecules/sidebar/animation/sidebar-viewport-animation-state.ts b/libs/design/src/molecules/sidebar/animation/sidebar-viewport-animation-state.ts
index 96f23bcd9f..e2881e5cc7 100644
--- a/libs/design/src/molecules/sidebar/animation/sidebar-viewport-animation-state.ts
+++ b/libs/design/src/molecules/sidebar/animation/sidebar-viewport-animation-state.ts
@@ -1,8 +1,16 @@
+import { DaffSidebarAnimationStates } from './sidebar-animation';
import { DaffSidebarAnimationState } from './sidebar-animation-state';
import { AnimationStateWithParams } from '../../../core/public_api';
+export type DaffSidebarViewportAnimationState = DaffSidebarAnimationStates.OPEN | DaffSidebarAnimationStates.CLOSED;
+
+
export interface DaffSidebarAnimationStateParams {
shift: string;
};
-export type DaffSidebarViewportAnimationState = AnimationStateWithParams;
+export type DaffSidebarViewportAnimationStateWithParams = AnimationStateWithParams;
+
+
+export const getSidebarViewportAnimationState = (open: boolean): DaffSidebarViewportAnimationState =>
+ open ? DaffSidebarAnimationStates.OPEN : DaffSidebarAnimationStates.CLOSED;
diff --git a/libs/design/src/molecules/sidebar/helper/_variables.scss b/libs/design/src/molecules/sidebar/helper/_variables.scss
index dee4faebe2..e82e43d1e8 100644
--- a/libs/design/src/molecules/sidebar/helper/_variables.scss
+++ b/libs/design/src/molecules/sidebar/helper/_variables.scss
@@ -1,7 +1,8 @@
// Stacking Context Layers
-$daff-sidebar-sidebar-over-z-index: 5;
-$daff-sidebar-backdrop-z-index: 4;
-$daff-sidebar-sidebar-side-fixed-z-index: 4;
+$daff-sidebar-sidebar-over-z-index: 7;
+$daff-sidebar-backdrop-z-index: 6;
+$daff-sidebar-sidebar-side-fixed-z-index: 5;
+$daff-sidebar-nav-z-index: 4;
$daff-sidebar-content-z-index: 3;
$daff-sidebar-sidebar-under-z-index: 2;
-$daff-sidebar-viewport-z-index: 1;
\ No newline at end of file
+$daff-sidebar-viewport-z-index: 1;
diff --git a/libs/design/src/molecules/sidebar/public_api.ts b/libs/design/src/molecules/sidebar/public_api.ts
index 8fb2e2e649..1d62e5c7cf 100644
--- a/libs/design/src/molecules/sidebar/public_api.ts
+++ b/libs/design/src/molecules/sidebar/public_api.ts
@@ -1,6 +1,10 @@
export { DaffSidebarModule } from './sidebar.module';
export * from './sidebar-viewport/sidebar-viewport.component';
export * from './sidebar/sidebar.component';
+export * from './sidebar-header/sidebar-header.component';
+export * from './sidebar-footer/sidebar-footer.component';
+export * from './sidebar-header/sidebar-header-title/sidebar-header-title.directive';
+export * from './sidebar-header/sidebar-header-action/sidebar-header-action.directive';
export {
DaffSidebarMode,
DaffSidebarModeEnum,
diff --git a/libs/design/src/molecules/sidebar/sidebar-footer/sidebar-footer.component.scss b/libs/design/src/molecules/sidebar/sidebar-footer/sidebar-footer.component.scss
new file mode 100644
index 0000000000..8e4ee52f89
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-footer/sidebar-footer.component.scss
@@ -0,0 +1,5 @@
+:host {
+ display: block;
+ width: 100%;
+ flex-grow: 0;
+}
diff --git a/libs/design/src/molecules/sidebar/sidebar-footer/sidebar-footer.component.spec.ts b/libs/design/src/molecules/sidebar/sidebar-footer/sidebar-footer.component.spec.ts
new file mode 100644
index 0000000000..e0d6d7ed3b
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-footer/sidebar-footer.component.spec.ts
@@ -0,0 +1,47 @@
+import {
+ Component,
+ DebugElement,
+} from '@angular/core';
+import {
+ waitForAsync,
+ ComponentFixture,
+ TestBed,
+} from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+
+import { DaffSidebarFooterComponent } from './sidebar-footer.component';
+
+@Component({ template: `
+ Footer
+` })
+class WrapperComponent {}
+
+describe('DaffSidebarFooterComponent', () => {
+ let wrapper: WrapperComponent;
+ let fixture: ComponentFixture;
+ let sidebarFooter: DaffSidebarFooterComponent;
+ let sidebarFooterDe: DebugElement;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ WrapperComponent,
+ DaffSidebarFooterComponent,
+ ],
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(WrapperComponent);
+ wrapper = fixture.componentInstance;
+
+ sidebarFooterDe = fixture.debugElement.query(By.css('daff-sidebar-footer'));
+ sidebarFooter = sidebarFooterDe.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(wrapper).toBeTruthy();
+ });
+});
diff --git a/libs/design/src/molecules/sidebar/sidebar-footer/sidebar-footer.component.ts b/libs/design/src/molecules/sidebar/sidebar-footer/sidebar-footer.component.ts
new file mode 100644
index 0000000000..8c6596253f
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-footer/sidebar-footer.component.ts
@@ -0,0 +1,15 @@
+import {
+ Component,
+ HostBinding,
+ ChangeDetectionStrategy,
+} from '@angular/core';
+
+@Component({
+ selector: 'daff-sidebar-footer',
+ template: '',
+ styleUrls: ['./sidebar-footer.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class DaffSidebarFooterComponent {
+ @HostBinding('class.daff-sidebar-footer') class = true;
+}
diff --git a/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-action/sidebar-header-action.directive.spec.ts b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-action/sidebar-header-action.directive.spec.ts
new file mode 100644
index 0000000000..96a5d61d20
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-action/sidebar-header-action.directive.spec.ts
@@ -0,0 +1,54 @@
+import {
+ Component,
+ DebugElement,
+} from '@angular/core';
+import {
+ waitForAsync,
+ ComponentFixture,
+ TestBed,
+} from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+
+import { DaffSidebarHeaderActionDirective } from './sidebar-header-action.directive';
+
+@Component({
+ template: `
+
Action
+ `,
+})
+class WrapperComponent {}
+
+describe('DaffSidebarHeaderActionDirective', () => {
+ let wrapper: WrapperComponent;
+ let de: DebugElement;
+ let fixture: ComponentFixture;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ DaffSidebarHeaderActionDirective,
+ WrapperComponent,
+ ],
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(WrapperComponent);
+ wrapper = fixture.componentInstance;
+ de = fixture.debugElement.query(By.css('[daffSidebarHeaderAction]'));
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(wrapper).toBeTruthy();
+ });
+
+ describe('[daffSidebarHeaderAction]',() => {
+ it('should add a class of `daff-sidebar-header__action` to its host element', () => {
+ expect(de.classes).toEqual(jasmine.objectContaining({
+ 'daff-sidebar-header__action': true,
+ }));
+ });
+ });
+});
diff --git a/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-action/sidebar-header-action.directive.ts b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-action/sidebar-header-action.directive.ts
new file mode 100644
index 0000000000..29b3829af6
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-action/sidebar-header-action.directive.ts
@@ -0,0 +1,14 @@
+import {
+ Directive,
+ HostBinding,
+} from '@angular/core';
+
+@Directive({
+ selector: '[daffSidebarHeaderAction]',
+})
+export class DaffSidebarHeaderActionDirective {
+ /**
+ * @docs-private
+ */
+ @HostBinding('class.daff-sidebar-header__action') class = true;
+}
diff --git a/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-title/sidebar-header-title.directive.spec.ts b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-title/sidebar-header-title.directive.spec.ts
new file mode 100644
index 0000000000..01a9a0c390
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-title/sidebar-header-title.directive.spec.ts
@@ -0,0 +1,54 @@
+import {
+ Component,
+ DebugElement,
+} from '@angular/core';
+import {
+ waitForAsync,
+ ComponentFixture,
+ TestBed,
+} from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+
+import { DaffSidebarHeaderTitleDirective } from './sidebar-header-title.directive';
+
+@Component({
+ template: `
+
Title
+ `,
+})
+class WrapperComponent {}
+
+describe('DaffSidebarHeaderTitleDirective', () => {
+ let wrapper: WrapperComponent;
+ let de: DebugElement;
+ let fixture: ComponentFixture;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ DaffSidebarHeaderTitleDirective,
+ WrapperComponent,
+ ],
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(WrapperComponent);
+ wrapper = fixture.componentInstance;
+ de = fixture.debugElement.query(By.css('[daffSidebarHeaderTitle]'));
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(wrapper).toBeTruthy();
+ });
+
+ describe('[daffSidebarHeaderTitle]',() => {
+ it('should add a class of `daff-sidebar-header__title` to its host element', () => {
+ expect(de.classes).toEqual(jasmine.objectContaining({
+ 'daff-sidebar-header__title': true,
+ }));
+ });
+ });
+});
diff --git a/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-title/sidebar-header-title.directive.ts b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-title/sidebar-header-title.directive.ts
new file mode 100644
index 0000000000..bf4430e160
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header-title/sidebar-header-title.directive.ts
@@ -0,0 +1,14 @@
+import {
+ Directive,
+ HostBinding,
+} from '@angular/core';
+
+@Directive({
+ selector: '[daffSidebarHeaderTitle]',
+})
+export class DaffSidebarHeaderTitleDirective {
+ /**
+ * @docs-private
+ */
+ @HostBinding('class.daff-sidebar-header__title') class = true;
+}
diff --git a/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.html b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.html
new file mode 100644
index 0000000000..28d3dcc5fb
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.scss b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.scss
new file mode 100644
index 0000000000..52d9774d90
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.scss
@@ -0,0 +1,48 @@
+@use '../../../../scss/typography' as t;
+
+.daff-sidebar-header {
+ display: flex;
+ align-items: center;
+ position: relative;
+ width: 100%;
+
+ &__action {
+ position: absolute;
+ }
+
+ &__title {
+ @include t.text-truncate();
+ font-size: 1rem;
+ line-height: 1rem;
+ font-weight: 500;
+ }
+
+ &__action + &__title {
+ margin: 0 0 0 29px;
+ }
+}
+
+.daff-sidebar {
+ .daff-sidebar-header {
+ padding: 16px;
+
+ &__action {
+ left: 0;
+ right: initial;
+ top: 0;
+ }
+ }
+
+ &.right {
+ .daff-sidebar-header {
+ &__action {
+ left: initial;
+ right: 0;
+ }
+
+ &__title {
+ margin: 0 29px 0 0;
+ }
+ }
+ }
+}
diff --git a/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.spec.ts b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.spec.ts
new file mode 100644
index 0000000000..f93df53c33
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.spec.ts
@@ -0,0 +1,53 @@
+import {
+ Component,
+ DebugElement,
+} from '@angular/core';
+import {
+ waitForAsync,
+ ComponentFixture,
+ TestBed,
+} from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+
+import { DaffSidebarHeaderComponent } from './sidebar-header.component';
+
+@Component({ template: `
+ Header
+` })
+class WrapperComponent {}
+
+describe('DaffSidebarHeaderComponent', () => {
+ let wrapper: WrapperComponent;
+ let fixture: ComponentFixture;
+ let component: DaffSidebarHeaderComponent;
+ let de: DebugElement;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ WrapperComponent,
+ DaffSidebarHeaderComponent,
+ ],
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(WrapperComponent);
+ wrapper = fixture.componentInstance;
+
+ de = fixture.debugElement.query(By.css('daff-sidebar-header'));
+ component = de.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(wrapper).toBeTruthy();
+ });
+
+ it('should add a class of "daff-sidebar-header" to the host element', () => {
+ expect(de.classes).toEqual(jasmine.objectContaining({
+ 'daff-sidebar-header': true,
+ }));
+ });
+});
diff --git a/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.ts b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.ts
new file mode 100644
index 0000000000..58c7d09280
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-header/sidebar-header.component.ts
@@ -0,0 +1,24 @@
+import {
+ Component,
+ HostBinding,
+ ChangeDetectionStrategy,
+ Output,
+ EventEmitter,
+ Input,
+ ViewEncapsulation,
+ ContentChild,
+ ElementRef,
+} from '@angular/core';
+import { faTimes } from '@fortawesome/free-solid-svg-icons';
+
+@Component({
+ selector: 'daff-sidebar-header',
+ templateUrl: './sidebar-header.component.html',
+ styleUrls: ['./sidebar-header.component.scss'],
+ encapsulation: ViewEncapsulation.None,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+
+})
+export class DaffSidebarHeaderComponent {
+ @HostBinding('class.daff-sidebar-header') class = true;
+}
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/helper/has-parent-viewport.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/helper/has-parent-viewport.ts
new file mode 100644
index 0000000000..9e08f2dc4c
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/helper/has-parent-viewport.ts
@@ -0,0 +1,17 @@
+/**
+ * Fallback function for determining whether or not a viewport has a parent viewport.
+ * This is really only used in the Daffodil docs when the injector heirarchy
+ * doesn't act as anticipated as a result of custom elements.
+ */
+export const hasParentViewport = (element: HTMLElement) => {
+ let currentElement = element.parentElement;
+
+ while (currentElement !== null) {
+ if (currentElement.tagName === 'DAFF-SIDEBAR-VIEWPORT') {
+ return true;
+ }
+ currentElement = currentElement.parentElement;
+ }
+
+ return false;
+};
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/nav-placement.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/nav-placement.ts
new file mode 100644
index 0000000000..871bfb505c
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/nav-placement.ts
@@ -0,0 +1,14 @@
+/**
+ * The placement of the nav in relation to the sidebar.
+ * See {@link DaffNavPlacementEnum }
+ */
+export type DaffNavPlacement = 'above' | 'beside';
+
+/**
+ * The placement of the nav in relation to the sidebar.
+ * See {@link DaffNavPlacement }
+ */
+export enum DaffNavPlacementEnum {
+ ABOVE = 'above',
+ BESIDE = 'beside',
+}
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/scroll-token/scroll.token.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/scroll-token/scroll.token.ts
new file mode 100644
index 0000000000..3c6486b271
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/scroll-token/scroll.token.ts
@@ -0,0 +1,55 @@
+import { DOCUMENT } from '@angular/common';
+import {
+ ElementRef,
+ InjectionToken,
+ inject,
+} from '@angular/core';
+
+/**
+ * An interface that enables a user to enable or disable scrolling on sidebars.
+ *
+ * See {@link DAFF_SIDEBAR_SCROLL_TOKEN}
+ */
+export interface DaffSidebarScroll {
+ enable(): void;
+ disable(): void;
+}
+
+/**
+ * An injection token that can be used within a sidebar to determine
+ * what to do enabling and disabling scrolling. By default, the body
+ * is the element where scrolling is controlled.
+ */
+export const DAFF_SIDEBAR_SCROLL_TOKEN = new InjectionToken('DAFF_SIDEBAR_SCROLL_TOKEN', {
+ providedIn: 'root',
+ factory: () => {
+ const document = inject(DOCUMENT);
+ return {
+ enable: () => {
+ document.body.style.overflow = null;
+ },
+ disable: () => {
+ document.body.style.overflow = 'hidden';
+ },
+ };
+ },
+});
+
+
+/**
+ * A factory function that return a DaffSidebarScroll
+ * for the current sidebar viewport.
+ *
+ * See the providers of {@link DaffSidebarViewportComponent}
+ */
+export const daffSidebarViewportScrollFactory = (): DaffSidebarScroll => {
+ const element = inject(ElementRef).nativeElement;
+ return {
+ enable: () => {
+ element.style.overflow = null;
+ },
+ disable: () => {
+ element.style.overflow = 'hidden';
+ },
+ };
+};
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.html b/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.html
index 41d5a17ae5..45333ae46a 100644
--- a/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.html
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.html
@@ -1,16 +1,18 @@
-
+
+ (backdropClicked)="_backdropClicked()">
+
+
+
+
+
-
-
-
\ No newline at end of file
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.scss b/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.scss
index 4cccdb81ac..41722eb34d 100644
--- a/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.scss
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.scss
@@ -1,15 +1,12 @@
@use '../helper/variables';
:host {
- overflow: hidden;
// fixes a bug where certain elements show in front of backdrop for a second.
display: flex;
- min-height: 100%;
+ overflow: hidden;
position: relative;
width: 100%;
z-index: variables.$daff-sidebar-viewport-z-index;
- height: 100vh;
- height: 100dvh;
}
.daff-sidebar-viewport {
@@ -23,22 +20,18 @@
height: 100%;
}
- &__sidebar {
- flex-shrink: 0;
- }
-
&__nav {
position: fixed;
top: 0;
width: 100%;
- z-index: variables.$daff-sidebar-content-z-index;
+ z-index: variables.$daff-sidebar-nav-z-index;
&:empty {
display: none;
}
&:not(:empty) {
- + #{$root}__inner {
+ + #{$root}__content {
margin-top: var(--daff-sidebar-side-fixed-top-shift);
}
}
@@ -48,11 +41,14 @@
height: 100%;
position: absolute;
width: 100%;
+ pointer-events: auto;
z-index: variables.$daff-sidebar-backdrop-z-index;
}
}
:host-context(daff-sidebar-viewport daff-sidebar-viewport) {
+ transform: translateX(0px);
+
.daff-sidebar-viewport__inner {
padding-left: 0px !important;
padding-right: 0px !important;
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.spec.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.spec.ts
index 40025209d7..e3db70f728 100644
--- a/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.spec.ts
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.spec.ts
@@ -1,6 +1,7 @@
import { CommonModule } from '@angular/common';
import {
Component,
+ DebugElement,
Input,
} from '@angular/core';
import {
@@ -11,6 +12,7 @@ import {
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { DaffNavPlacement } from './nav-placement';
import { DaffSidebarViewportComponent } from './sidebar-viewport.component';
import {
DaffBackdropComponent,
@@ -20,7 +22,7 @@ import { DaffSidebarComponent } from '../sidebar/sidebar.component';
@Component({ template: `
-
+
` })
@@ -34,6 +36,7 @@ class WrapperComponent {
reset() {
this.backdropClickedCounter = 0;
}
+ navPlacement: DaffNavPlacement = 'above';
}
describe('DaffSidebarViewportComponent | Usage', () => {
@@ -41,6 +44,7 @@ describe('DaffSidebarViewportComponent | Usage', () => {
let fixture: ComponentFixture;
let component: DaffSidebarViewportComponent;
let backdrop: DaffBackdropComponent;
+ let de: DebugElement;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
@@ -61,7 +65,9 @@ describe('DaffSidebarViewportComponent | Usage', () => {
fixture = TestBed.createComponent(WrapperComponent);
wrapper = fixture.componentInstance;
- component = fixture.debugElement.query(By.css('daff-sidebar-viewport')).componentInstance;
+ de = fixture.debugElement.query(By.css('daff-sidebar-viewport'));
+
+ component = de.componentInstance;
fixture.detectChanges();
});
@@ -70,6 +76,29 @@ describe('DaffSidebarViewportComponent | Usage', () => {
expect(wrapper).toBeTruthy();
});
+ describe('', () => {
+ it('should add a class of "daff-sidebar-viewport" to the host element', () => {
+ expect(de.nativeElement.classList.contains('daff-sidebar-viewport')).toBeTruthy();
+ });
+ });
+
+ describe('navPlacement', () => {
+ it('should be able to take `navPlacement` as an input', () => {
+ expect(component.navPlacement).toEqual(wrapper.navPlacement);
+ });
+
+ it('should set the default navPlacement to above', () => {
+ expect(component.navPlacement).toEqual('above');
+ });
+
+ it('should add a class of `.beside` if navPlacement="beside"', () => {
+ wrapper.navPlacement = 'beside';
+ fixture.detectChanges();
+
+ expect(de.nativeElement.classList.contains('beside')).toBeTruthy();
+ });
+ });
+
describe('when emits backdropClicked', () => {
beforeEach(() => {
fixture.detectChanges();
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.ts
index 968df51c65..72d7869fc8 100644
--- a/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.ts
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/sidebar-viewport.component.ts
@@ -7,17 +7,38 @@ import {
ContentChildren,
QueryList,
AfterContentChecked,
+ ElementRef,
+ Input,
+ HostBinding,
+ Inject,
+ SkipSelf,
+ Optional,
} from '@angular/core';
-import { sidebarViewportBackdropInteractable } from './backdrop-interactable';
-import { sidebarViewportContentPadding } from './content-pad';
+import { hasParentViewport } from './helper/has-parent-viewport';
+import {
+ DaffNavPlacement,
+ DaffNavPlacementEnum,
+} from './nav-placement';
+import {
+ DAFF_SIDEBAR_SCROLL_TOKEN,
+ DaffSidebarScroll,
+ daffSidebarViewportScrollFactory,
+} from './scroll-token/scroll.token';
+import { sidebarViewportBackdropInteractable } from './utils/backdrop-interactable';
+import { sidebarViewportContentPadding } from './utils/content-pad';
import {
isViewportContentShifted,
sidebarViewportContentShift,
-} from './content-shift';
-import { daffSidebarAnimations } from '../animation/sidebar-animation';
-import { getAnimationState } from '../animation/sidebar-animation-state';
-import { DaffSidebarViewportAnimationState } from '../animation/sidebar-viewport-animation-state';
+} from './utils/content-shift';
+import {
+ DaffSidebarAnimationStates,
+ daffSidebarAnimations,
+} from '../animation/sidebar-animation';
+import {
+ DaffSidebarViewportAnimationStateWithParams,
+ getSidebarViewportAnimationState,
+} from '../animation/sidebar-viewport-animation-state';
import { DaffSidebarMode } from '../helper/sidebar-mode';
import { DaffSidebarComponent } from '../sidebar/sidebar.component';
@@ -36,11 +57,9 @@ import { DaffSidebarComponent } from '../sidebar/sidebar.component';
* at the same time. @see {@link DaffSidebarMode }
*
* Since this is a functional component, it's possible to have multiple "open" sidebars
- * within at the same time. As a result, this component attempts to
- * gracefully handle these situations. However, importantly, this sidebar
- * has a constraint, there's only allowed to be one sidebar,
- * of each mode, on each side, at any given time. If this is violated,
- * this component will throw an exception.
+ * at the same time. As a result, this component attempts to gracefully handle these situations.
+ * However, importantly, there can only be one sidebar of each mode, on each side, at any given time.
+ * If this is violated, this component will throw an exception.
*/
@Component({
selector: 'daff-sidebar-viewport',
@@ -50,10 +69,37 @@ import { DaffSidebarComponent } from '../sidebar/sidebar.component';
animations: [
daffSidebarAnimations.transformContent,
],
+ providers: [
+ { provide: DAFF_SIDEBAR_SCROLL_TOKEN, useFactory: daffSidebarViewportScrollFactory },
+ ],
})
export class DaffSidebarViewportComponent implements AfterContentChecked {
+ @HostBinding('class.daff-sidebar-viewport') hostClass = true;
- constructor(private cdRef: ChangeDetectorRef) { }
+ @HostBinding('class') get classes() {
+ return {
+ 'daff-sidebar-viewport': true,
+ [this.navPlacement]: true,
+ };
+ };
+
+ get isNavOnSide() {
+ return this.navPlacement === DaffNavPlacementEnum.BESIDE;
+ }
+
+ /**
+ * The placement of the nav in relation to the sidebar. The default is set to `top`.
+ * Note that this is really only available when there is a `side-fixed` sidebar.
+ */
+ @Input() navPlacement: DaffNavPlacement = DaffNavPlacementEnum.ABOVE;
+
+ constructor(
+ private cdRef: ChangeDetectorRef,
+ private _elementRef: ElementRef,
+ @Inject(DAFF_SIDEBAR_SCROLL_TOKEN) @SkipSelf() private bodyScroll: DaffSidebarScroll,
+ @Inject(DaffSidebarViewportComponent) @SkipSelf() @Optional() private parentViewport,
+ @Inject(DAFF_SIDEBAR_SCROLL_TOKEN) private scroll: DaffSidebarScroll,
+ ) { }
/**
* The list of sidebars in the viewport.
@@ -73,11 +119,21 @@ export class DaffSidebarViewportComponent implements AfterContentChecked {
*/
public _contentPadLeft = 0;
+ /**
+ * The left padding on the nav when left side-fixed sidebars are open.
+ */
+ public _navPadLeft = 0;
+
/**
* The right padding on the content when right side-fixed sidebars are open.
*/
public _contentPadRight = 0;
+ /**
+ * The right padding on the content when right side-fixed sidebars are open.
+ */
+ public _navPadRight = 0;
+
/**
* Whether or not the backdrop is interactable
*/
@@ -86,11 +142,10 @@ export class DaffSidebarViewportComponent implements AfterContentChecked {
/**
* The animation state
*/
- _animationState: DaffSidebarViewportAnimationState = { value: 'closed', params: { shift: '0px' }};
+ _animationState: DaffSidebarViewportAnimationStateWithParams = { value: DaffSidebarAnimationStates.CLOSED, params: { shift: '0px' }};
/**
- * Event fired when the backdrop is clicked
- * This is often used to close the sidebar
+ * Event fired when the backdrop is clicked. This is often used to close the sidebar.
*/
@Output() backdropClicked: EventEmitter = new EventEmitter();
@@ -107,11 +162,25 @@ export class DaffSidebarViewportComponent implements AfterContentChecked {
this._backdropInteractable = nextBackdropInteractable;
this.updateAnimationState();
this.cdRef.markForCheck();
+ if(nextBackdropInteractable) {
+ if(!this.parentViewport && !hasParentViewport(this._elementRef.nativeElement)) {
+ this.bodyScroll.disable();
+ } else {
+ this.scroll.disable();
+ }
+ } else { //if we are hiding the sidebars
+ if(!this.parentViewport && !hasParentViewport(this._elementRef.nativeElement)) {
+ this.bodyScroll.enable();
+ } else {
+ this.scroll.enable();
+ }
+ }
};
const nextLeftPadding = sidebarViewportContentPadding(this.sidebars, 'left');
if(this._contentPadLeft !== nextLeftPadding) {
this._contentPadLeft = nextLeftPadding;
+ this._navPadLeft = this.isNavOnSide ? this._contentPadLeft : null;
this.updateAnimationState();
this.cdRef.markForCheck();
}
@@ -119,6 +188,7 @@ export class DaffSidebarViewportComponent implements AfterContentChecked {
const nextRightPadding = sidebarViewportContentPadding(this.sidebars, 'right');
if(this._contentPadRight !== nextRightPadding) {
this._contentPadRight = nextRightPadding;
+ this._navPadRight = this.isNavOnSide ? this._contentPadRight : null;
this.updateAnimationState();
this.cdRef.markForCheck();
}
@@ -132,7 +202,7 @@ export class DaffSidebarViewportComponent implements AfterContentChecked {
*/
private updateAnimationState() {
this._animationState = {
- value: getAnimationState(
+ value: getSidebarViewportAnimationState(
this.sidebars.reduce((acc: boolean, sidebar) => acc || isViewportContentShifted(sidebar.mode, sidebar.open), false),
),
params: { shift: this._shift },
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/specs/defaults.spec.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/specs/defaults.spec.ts
index c83374941f..c6e9abe574 100644
--- a/libs/design/src/molecules/sidebar/sidebar-viewport/specs/defaults.spec.ts
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/specs/defaults.spec.ts
@@ -8,6 +8,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { DaffSidebarViewportComponent } from './../sidebar-viewport.component';
import { DaffBackdropModule } from '../../../backdrop/public_api';
+import { DaffSidebarAnimationStates } from '../../animation/sidebar-animation';
import { DaffSidebarComponent } from '../../sidebar/sidebar.component';
describe('DaffSidebarViewportComponent | Defaults', () => {
@@ -43,6 +44,6 @@ describe('DaffSidebarViewportComponent | Defaults', () => {
});
it('should have the _animationState should be `open` by default', () => {
- expect(component._animationState).toEqual({ value: 'closed', params: { shift: '0px' }});
+ expect(component._animationState).toEqual({ value: DaffSidebarAnimationStates.CLOSED, params: { shift: '0px' }});
});
});
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/backdrop-interactable.spec.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/backdrop-interactable.spec.ts
similarity index 97%
rename from libs/design/src/molecules/sidebar/sidebar-viewport/backdrop-interactable.spec.ts
rename to libs/design/src/molecules/sidebar/sidebar-viewport/utils/backdrop-interactable.spec.ts
index f8da92198c..0cfd3462ec 100644
--- a/libs/design/src/molecules/sidebar/sidebar-viewport/backdrop-interactable.spec.ts
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/backdrop-interactable.spec.ts
@@ -6,7 +6,7 @@ import {
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { sidebarViewportBackdropInteractable } from './backdrop-interactable';
-import { DaffSidebarComponent } from '../sidebar/sidebar.component';
+import { DaffSidebarComponent } from '../../sidebar/sidebar.component';
describe('@daffodil/design | sidebar-viewport | backdrop-interactable', () => {
beforeEach(waitForAsync(() => {
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/backdrop-interactable.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/backdrop-interactable.ts
similarity index 84%
rename from libs/design/src/molecules/sidebar/sidebar-viewport/backdrop-interactable.ts
rename to libs/design/src/molecules/sidebar/sidebar-viewport/utils/backdrop-interactable.ts
index 16ca9725ad..e20f59b64e 100644
--- a/libs/design/src/molecules/sidebar/sidebar-viewport/backdrop-interactable.ts
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/backdrop-interactable.ts
@@ -1,6 +1,6 @@
import { QueryList } from '@angular/core';
-import { DaffSidebarComponent } from '../sidebar/sidebar.component';
+import { DaffSidebarComponent } from '../../sidebar/sidebar.component';
/**
* Determines, given a list of sidebars, whether or not the backdrop is interactable (typically clickable).
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/content-pad.spec.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-pad.spec.ts
similarity index 97%
rename from libs/design/src/molecules/sidebar/sidebar-viewport/content-pad.spec.ts
rename to libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-pad.spec.ts
index 845e2a4964..68932d7eda 100644
--- a/libs/design/src/molecules/sidebar/sidebar-viewport/content-pad.spec.ts
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-pad.spec.ts
@@ -13,7 +13,7 @@ import {
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { sidebarViewportContentPadding } from './content-pad';
-import { DaffSidebarComponent } from '../sidebar/sidebar.component';
+import { DaffSidebarComponent } from '../../sidebar/sidebar.component';
@Component({
template: `
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/content-pad.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-pad.ts
similarity index 76%
rename from libs/design/src/molecules/sidebar/sidebar-viewport/content-pad.ts
rename to libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-pad.ts
index bb62c607cf..f5000bdd77 100644
--- a/libs/design/src/molecules/sidebar/sidebar-viewport/content-pad.ts
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-pad.ts
@@ -1,9 +1,8 @@
import { QueryList } from '@angular/core';
-import { DaffSidebarModeEnum } from '../helper/sidebar-mode';
-import { DaffSidebarSide } from '../helper/sidebar-side';
-import { DaffSidebarComponent } from '../sidebar/sidebar.component';
-
+import { DaffSidebarModeEnum } from '../../helper/sidebar-mode';
+import { DaffSidebarSide } from '../../helper/sidebar-side';
+import { DaffSidebarComponent } from '../../sidebar/sidebar.component';
/**
* Given a list of sidebars, compute the associated content shift.
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/content-shift.spec.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-shift.spec.ts
similarity index 97%
rename from libs/design/src/molecules/sidebar/sidebar-viewport/content-shift.spec.ts
rename to libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-shift.spec.ts
index d441bb4726..43ca080c4c 100644
--- a/libs/design/src/molecules/sidebar/sidebar-viewport/content-shift.spec.ts
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-shift.spec.ts
@@ -13,7 +13,7 @@ import {
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { sidebarViewportContentShift } from './content-shift';
-import { DaffSidebarComponent } from '../sidebar/sidebar.component';
+import { DaffSidebarComponent } from '../../sidebar/sidebar.component';
@Component({
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/content-shift.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-shift.ts
similarity index 86%
rename from libs/design/src/molecules/sidebar/sidebar-viewport/content-shift.ts
rename to libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-shift.ts
index f9b356f2b4..07da23e326 100644
--- a/libs/design/src/molecules/sidebar/sidebar-viewport/content-shift.ts
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/content-shift.ts
@@ -1,7 +1,7 @@
import { QueryList } from '@angular/core';
-import { DaffSidebarMode } from '../helper/sidebar-mode';
-import { DaffSidebarComponent } from '../sidebar/sidebar.component';
+import { DaffSidebarMode } from '../../helper/sidebar-mode';
+import { DaffSidebarComponent } from '../../sidebar/sidebar.component';
export const isViewportContentShifted = (mode: DaffSidebarMode, open: boolean): boolean => (mode === 'under' && open);
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/utils/viewport-height.spec.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/viewport-height.spec.ts
new file mode 100644
index 0000000000..5f7991aee9
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/viewport-height.spec.ts
@@ -0,0 +1,93 @@
+import { QueryList } from '@angular/core';
+import {
+ TestBed,
+ waitForAsync,
+} from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+
+import { sidebarViewportHeight } from './viewport-height';
+import { DaffSidebarComponent } from '../../sidebar/sidebar.component';
+
+describe('@daffodil/design | sidebar-viewport | viewport-height', () => {
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ NoopAnimationsModule,
+ ],
+ declarations: [
+ DaffSidebarComponent,
+ ],
+ }).compileComponents();
+ }));
+
+ it('should not set a height on the viewport if there are no sidebars', () => {
+ expect(sidebarViewportHeight(new QueryList())).toEqual(false);
+ });
+
+ it('should not set a height on the viewport if there are no open sidebars', () => {
+ const list = new QueryList();
+ list.reset([
+ TestBed.createComponent(DaffSidebarComponent).componentInstance,
+ ]);
+ expect(sidebarViewportHeight(list)).toEqual(false);
+ });
+
+ it('should not set a height on the viewport if there are only "side" or "side-fixed" sidebars', () => {
+ const sideLeft = TestBed.createComponent(DaffSidebarComponent).componentInstance;
+ sideLeft.mode = 'side';
+ sideLeft.side = 'left';
+ const sideFixedLeft = TestBed.createComponent(DaffSidebarComponent).componentInstance;
+ sideFixedLeft.mode = 'side-fixed';
+ sideFixedLeft.side = 'left';
+ const sideRight = TestBed.createComponent(DaffSidebarComponent).componentInstance;
+ sideRight.mode = 'side';
+ sideRight.side = 'right';
+ const sideFixedRight = TestBed.createComponent(DaffSidebarComponent).componentInstance;
+ sideFixedRight.mode = 'side-fixed';
+ sideFixedRight.side = 'right';
+
+ const list = new QueryList();
+ list.reset([
+ sideLeft,
+ sideFixedLeft,
+ sideRight,
+ sideFixedRight,
+ ]);
+ expect(sidebarViewportHeight(list)).toEqual(false);
+ });
+
+ it('should set height on the viewport if there is at least one open "over" sidebar', () => {
+ const leftSidebar = TestBed.createComponent(DaffSidebarComponent).componentInstance;
+ leftSidebar.mode = 'side';
+ leftSidebar.side = 'left';
+ const rightSidebar = TestBed.createComponent(DaffSidebarComponent).componentInstance;
+ rightSidebar.mode = 'over';
+ rightSidebar.side = 'right';
+ rightSidebar.open = true;
+
+ const list = new QueryList();
+ list.reset([
+ leftSidebar,
+ rightSidebar,
+ ]);
+ expect(sidebarViewportHeight(list)).toEqual(true);
+
+ });
+
+ it('should set height on the viewport if there is at least one open "under" sidebar', () => {
+ const leftSidebar = TestBed.createComponent(DaffSidebarComponent).componentInstance;
+ leftSidebar.mode = 'side';
+ leftSidebar.side = 'left';
+ const rightSidebar = TestBed.createComponent(DaffSidebarComponent).componentInstance;
+ rightSidebar.mode = 'under';
+ rightSidebar.side = 'right';
+ rightSidebar.open = true;
+
+ const list = new QueryList();
+ list.reset([
+ leftSidebar,
+ rightSidebar,
+ ]);
+ expect(sidebarViewportHeight(list)).toEqual(true);
+ });
+});
diff --git a/libs/design/src/molecules/sidebar/sidebar-viewport/utils/viewport-height.ts b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/viewport-height.ts
new file mode 100644
index 0000000000..76ade5d703
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar-viewport/utils/viewport-height.ts
@@ -0,0 +1,10 @@
+import { QueryList } from '@angular/core';
+
+import { DaffSidebarComponent } from '../../sidebar/sidebar.component';
+
+/**
+ * Determines whether or not the sidebar viewport should have a pre-defined height, give a list of sidebars.
+ */
+export const sidebarViewportHeight = (sidebars: QueryList): boolean => sidebars.reduce(
+ (acc: boolean, sidebar) => ((sidebar.mode === 'over' || sidebar.mode === 'under') && sidebar.open) || acc,
+ false);
diff --git a/libs/design/src/molecules/sidebar/sidebar.module.ts b/libs/design/src/molecules/sidebar/sidebar.module.ts
index 08f9bfd13e..d4f234e545 100644
--- a/libs/design/src/molecules/sidebar/sidebar.module.ts
+++ b/libs/design/src/molecules/sidebar/sidebar.module.ts
@@ -3,23 +3,34 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { DaffSidebarComponent } from './sidebar/sidebar.component';
+import { DaffSidebarFooterComponent } from './sidebar-footer/sidebar-footer.component';
+import { DaffSidebarHeaderActionDirective } from './sidebar-header/sidebar-header-action/sidebar-header-action.directive';
+import { DaffSidebarHeaderTitleDirective } from './sidebar-header/sidebar-header-title/sidebar-header-title.directive';
+import { DaffSidebarHeaderComponent } from './sidebar-header/sidebar-header.component';
import { DaffSidebarViewportComponent } from './sidebar-viewport/sidebar-viewport.component';
import { DaffBackdropModule } from '../backdrop/backdrop.module';
-
@NgModule({
imports: [
CommonModule,
- DaffBackdropModule,
A11yModule,
+ DaffBackdropModule,
],
declarations: [
DaffSidebarComponent,
DaffSidebarViewportComponent,
+ DaffSidebarHeaderComponent,
+ DaffSidebarFooterComponent,
+ DaffSidebarHeaderTitleDirective,
+ DaffSidebarHeaderActionDirective,
],
exports: [
DaffSidebarComponent,
DaffSidebarViewportComponent,
+ DaffSidebarHeaderComponent,
+ DaffSidebarFooterComponent,
+ DaffSidebarHeaderTitleDirective,
+ DaffSidebarHeaderActionDirective,
],
})
export class DaffSidebarModule { }
diff --git a/libs/design/src/molecules/sidebar/sidebar/is-opening.ts b/libs/design/src/molecules/sidebar/sidebar/is-opening.ts
new file mode 100644
index 0000000000..5f6b88a3c9
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar/is-opening.ts
@@ -0,0 +1,15 @@
+import { DaffSidebarAnimationState } from '../animation/sidebar-animation-state';
+
+/**
+ * Determine whether or animation states are going to an open state from a non-open state.
+ */
+export const isOpening = (fromState: DaffSidebarAnimationState, toState: DaffSidebarAnimationState): boolean => {
+ if(fromState === 'under-open' && toState === 'open' || fromState === 'open' && toState === 'under-open') {
+ return false;
+ }
+
+ if(toState === 'under-open' || toState === 'open') {
+ return true;
+ }
+ return false;
+};
diff --git a/libs/design/src/molecules/sidebar/sidebar/sidebar-theme.scss b/libs/design/src/molecules/sidebar/sidebar/sidebar-theme.scss
index cdc515be9d..2f6677d9cd 100644
--- a/libs/design/src/molecules/sidebar/sidebar/sidebar-theme.scss
+++ b/libs/design/src/molecules/sidebar/sidebar/sidebar-theme.scss
@@ -3,7 +3,6 @@
@mixin daff-sidebar-theme($theme) {
$base: core.daff-map-deep-get($theme, 'core.base');
$base-contrast: core.daff-map-deep-get($theme, 'core.base-contrast');
- $black: core.daff-map-deep-get($theme, 'core.black');
$font-color: core.daff-map-deep-get($theme, 'core.font-color');
.daff-sidebar {
diff --git a/libs/design/src/molecules/sidebar/sidebar/sidebar.component.html b/libs/design/src/molecules/sidebar/sidebar/sidebar.component.html
new file mode 100644
index 0000000000..7184edce2b
--- /dev/null
+++ b/libs/design/src/molecules/sidebar/sidebar/sidebar.component.html
@@ -0,0 +1,5 @@
+
+