Skip to content

Commit

Permalink
feat(design): improve sidebar component (#2569)
Browse files Browse the repository at this point in the history
- Added new sidebar header and footer components
- Added new "navPlacement" property that can be used with "side-fixed" sidebars
- Clean up animations between sidebar modes
- General code cleanup and bug fixing across different browsers

Accessibility Improvements:
  - Added focus trapping on "over" and "under" sidebars for 
  - Add ESC key handling to close sidebars
  • Loading branch information
xelaint committed Dec 14, 2023
1 parent ce5a331 commit b2f1e3e
Show file tree
Hide file tree
Showing 91 changed files with 1,862 additions and 535 deletions.
68 changes: 36 additions & 32 deletions apps/design-land/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,46 @@ import {
RouterModule,
} from '@angular/router';

import { DesignLandTemplateComponent } from './core/template/template.component';

export const appRoutes: Routes = [
{
path: '',
redirectTo: '/button',
pathMatch: 'full',
component: DesignLandTemplateComponent,
children: [
{ path: '', pathMatch: 'full', loadChildren: () => import('./foundations/color/color.module').then(m => m.DesignLandColorModule) },
{ path: 'accordion', loadChildren: () => import('./accordion/accordion.module').then(m => m.DesignLandAccordionModule) },
{ path: 'article', loadChildren: () => import('./article/article.module').then(m => m.DesignLandArticleModule) },
{ path: 'button', loadChildren: () => import('./button/button.module').then(m => m.DesignLandButtonModule) },
{ path: 'callout', loadChildren: () => import('./callout/callout.module').then(m => m.DesignLandCalloutModule) },
{ path: 'card', loadChildren: () => import('./card/card.module').then(m => m.DesignLandCardModule) },
{ path: 'checkbox', loadChildren: () => import('./checkbox/checkbox.module').then(m => m.DesignLandCheckboxModule) },
{ path: 'color', loadChildren: () => import('./foundations/color/color.module').then(m => m.DesignLandColorModule) },
{ path: 'container', loadChildren: () => import('./container/container.module').then(m => m.DesignLandContainerModule) },
{ path: 'feature', loadChildren: () => import('./feature/feature.module').then(m => m.DesignLandFeatureModule) },
{ path: 'form', loadChildren: () => import('./form/form.module').then(m => m.DesignLandFormModule) },
{ path: 'hero', loadChildren: () => import('./hero/hero.module').then(m => m.DesignLandHeroModule) },
{ path: 'link-set', loadChildren: () => import('./link-set/link-set.module').then(m => m.DesignLandLinkSetModule) },
{ path: 'list', loadChildren: () => import('./list/list.module').then(m => m.DesignLandListModule) },
{ path: 'loading-icon', loadChildren: () => import('./loading-icon/loading-icon.module').then(m => m.DesignLandLoadingIconModule) },
{ path: 'image', loadChildren: () => import('./image/image.module').then(m => m.DesignLandImageModule) },
{ path: 'image-gallery', loadChildren: () => import('./image-gallery/image-gallery.module').then(m => m.DesignLandImageGalleryModule) },
{ path: 'input', loadChildren: () => import('./input/input.module').then(m => m.DesignLandInputModule) },
{ path: 'navbar', loadChildren: () => import('./navbar/navbar.module').then(m => m.DesignLandNavbarModule) },
{ path: 'media-gallery', loadChildren: () => import('./media-gallery/media-gallery.module').then(m => m.DesignLandMediaGalleryModule) },
{ path: 'menu', loadChildren: () => import('./menu/menu.module').then(m => m.DesignLandMenuModule) },
{ path: 'modal', loadChildren: () => import('./modal/modal.module').then(m => m.DesignLandModalModule) },
{ path: 'notification', loadChildren: () => import('./notification/notification.module').then(m => m.DesignLandNotificationModule) },
{ path: 'paginator', loadChildren: () => import('./paginator/paginator.module').then(m => m.DesignLandPaginatorModule) },
{ path: 'progress-indicator', loadChildren: () => import('./progress-indicator/progress-indicator.module').then(m => m.DesignLandProgressIndicatorModule) },
{ path: 'qty-dropdown', loadChildren: () => import('./qty-dropdown/qty-dropdown.module').then(m => m.DesignLandQtyDropdownModule) },
{ path: 'quantity-field', loadChildren: () => import('./quantity-field/quantity-field.module').then(m => m.DesignLandQuantityFieldModule) },
{ path: 'sidebar', loadChildren: () => import('./sidebar/sidebar.module').then(m => m.DesignLandSidebarModule) },
{ path: 'radio', loadChildren: () => import('./radio/radio.module').then(m => m.DesignLandRadioModule) },
{ path: 'typography', loadChildren: () => import('./typography/typography.module').then(m => m.DesignLandTypographyModule) },
{ path: 'variables', loadChildren: () => import('./foundations/variables/variables.module').then(m => m.DesignLandVariablesModule) },
],
},
{ path: 'accordion', loadChildren: () => import('./accordion/accordion.module').then(m => m.DesignLandAccordionModule) },
{ path: 'article', loadChildren: () => import('./article/article.module').then(m => m.DesignLandArticleModule) },
{ path: 'button', loadChildren: () => import('./button/button.module').then(m => m.DesignLandButtonModule) },
{ path: 'callout', loadChildren: () => import('./callout/callout.module').then(m => m.DesignLandCalloutModule) },
{ path: 'card', loadChildren: () => import('./card/card.module').then(m => m.DesignLandCardModule) },
{ path: 'checkbox', loadChildren: () => import('./checkbox/checkbox.module').then(m => m.DesignLandCheckboxModule) },
{ path: 'color', loadChildren: () => import('./foundations/color/color.module').then(m => m.DesignLandColorModule) },
{ path: 'container', loadChildren: () => import('./container/container.module').then(m => m.DesignLandContainerModule) },
{ path: 'feature', loadChildren: () => import('./feature/feature.module').then(m => m.DesignLandFeatureModule) },
{ path: 'form', loadChildren: () => import('./form/form.module').then(m => m.DesignLandFormModule) },
{ path: 'hero', loadChildren: () => import('./hero/hero.module').then(m => m.DesignLandHeroModule) },
{ path: 'link-set', loadChildren: () => import('./link-set/link-set.module').then(m => m.DesignLandLinkSetModule) },
{ path: 'list', loadChildren: () => import('./list/list.module').then(m => m.DesignLandListModule) },
{ path: 'loading-icon', loadChildren: () => import('./loading-icon/loading-icon.module').then(m => m.DesignLandLoadingIconModule) },
{ path: 'image', loadChildren: () => import('./image/image.module').then(m => m.DesignLandImageModule) },
{ path: 'image-gallery', loadChildren: () => import('./image-gallery/image-gallery.module').then(m => m.DesignLandImageGalleryModule) },
{ path: 'input', loadChildren: () => import('./input/input.module').then(m => m.DesignLandInputModule) },
{ path: 'navbar', loadChildren: () => import('./navbar/navbar.module').then(m => m.DesignLandNavbarModule) },
{ path: 'media-gallery', loadChildren: () => import('./media-gallery/media-gallery.module').then(m => m.DesignLandMediaGalleryModule) },
{ path: 'menu', loadChildren: () => import('./menu/menu.module').then(m => m.DesignLandMenuModule) },
{ path: 'modal', loadChildren: () => import('./modal/modal.module').then(m => m.DesignLandModalModule) },
{ path: 'notification', loadChildren: () => import('./notification/notification.module').then(m => m.DesignLandNotificationModule) },
{ path: 'paginator', loadChildren: () => import('./paginator/paginator.module').then(m => m.DesignLandPaginatorModule) },
{ path: 'progress-indicator', loadChildren: () => import('./progress-indicator/progress-indicator.module').then(m => m.DesignLandProgressIndicatorModule) },
{ path: 'qty-dropdown', loadChildren: () => import('./qty-dropdown/qty-dropdown.module').then(m => m.DesignLandQtyDropdownModule) },
{ path: 'quantity-field', loadChildren: () => import('./quantity-field/quantity-field.module').then(m => m.DesignLandQuantityFieldModule) },
{ path: 'sidebar', loadChildren: () => import('./sidebar/sidebar.module').then(m => m.DesignLandSidebarModule) },
{ path: 'radio', loadChildren: () => import('./radio/radio.module').then(m => m.DesignLandRadioModule) },
{ path: 'typography', loadChildren: () => import('./typography/typography.module').then(m => m.DesignLandTypographyModule) },
{ path: 'variables', loadChildren: () => import('./foundations/variables/variables.module').then(m => m.DesignLandVariablesModule) },
];

@NgModule({
Expand Down
17 changes: 1 addition & 16 deletions apps/design-land/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,16 +1 @@
<daff-sidebar-viewport class="design-land__viewport" (backdropClicked)="toggleOpen()">
<daff-sidebar class="design-land__sidebar" [mode]="mode$ | async" [open]="open">
<design-land-nav></design-land-nav>
</daff-sidebar>
<nav daff-sidebar-viewport-nav daff-navbar>
<button daff-icon-button (click)="toggleOpen()" aria-label="Open Menu">
<fa-icon [icon]="faBars"></fa-icon>
</button>
<daff-theme-switch-button class="design-land__theme-switch"></daff-theme-switch-button>
</nav>
<div class="design-land__content">
<daff-article>
<router-outlet></router-outlet>
</daff-article>
</div>
</daff-sidebar-viewport>
<router-outlet></router-outlet>
22 changes: 0 additions & 22 deletions apps/design-land/src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -1,22 +0,0 @@
:host {
--daff-sidebar-side-fixed-top-shift: 64px;
}

.design-land {

&__theme-switch {
margin-left: auto;
}

&__content {
padding: 24px 48px 48px;
}
}

daff-link-set {
margin: 0 0 24px 0;

&:last-child {
margin: 0;
}
}
29 changes: 0 additions & 29 deletions apps/design-land/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
import { BreakpointObserver } from '@angular/cdk/layout';
import {
Component,
Injector,
ComponentFactoryResolver,
} from '@angular/core';
import { faBars } from '@fortawesome/free-solid-svg-icons';
import {
Observable,
map,
tap,
} from 'rxjs';

import {
DaffBreakpoints,
DaffSidebarMode,
DaffSidebarModeEnum,
} from '@daffodil/design';
import { ACCORDION_EXAMPLES } from '@daffodil/design/accordion/examples';
import { ARTICLE_EXAMPLES } from '@daffodil/design/article/examples';
import { BUTTON_EXAMPLES } from '@daffodil/design/button/examples';
Expand All @@ -40,24 +28,15 @@ import { SIDEBAR_EXAMPLES } from '@daffodil/design/sidebar/examples';

import { createCustomElementFromExample } from './core/elements/create-element-from-example';



@Component({
selector: 'design-land-app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class DesignLandAppComponent {
faBars = faBars;

public mode$: Observable<DaffSidebarMode>;

public open = false;

constructor(
injector: Injector,
private componentFactoryResolver: ComponentFactoryResolver,
private breakpoint: BreakpointObserver,
) {
[
...ARTICLE_EXAMPLES,
Expand Down Expand Up @@ -89,13 +68,5 @@ export class DesignLandAppComponent {
customElement.element,
);
});
this.open = this.breakpoint.isMatched(DaffBreakpoints.BIG_TABLET);
this.mode$ = this.breakpoint.observe(DaffBreakpoints.BIG_TABLET).pipe(
map((match) => match.matches ? DaffSidebarModeEnum.SideFixed : DaffSidebarModeEnum.Under),
);
}

toggleOpen() {
this.open = !this.open;
}
}
2 changes: 2 additions & 0 deletions apps/design-land/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { DaffThemeSwitchButtonModule } from '@daffodil/theme-switch';
import { DesignLandAppRoutingModule } from './app-routing.module';
import { DesignLandAppComponent } from './app.component';
import { DesignLandNavModule } from './core/nav/nav.module';
import { DesignLandTemplateModule } from './core/template/template.module';

@NgModule({
imports: [
Expand All @@ -32,6 +33,7 @@ import { DesignLandNavModule } from './core/nav/nav.module';
DaffButtonModule,
FontAwesomeModule,
DesignLandNavModule,
DesignLandTemplateModule,
],
declarations: [
DesignLandAppComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@use 'sass:map';
@use 'theme' as daff-theme;

@mixin sidebar-viewport-theme($theme) {
$gray: daff-theme.daff-map-deep-get($theme, 'core.gray');
$base: daff-theme.daff-map-deep-get($theme, 'core.base');
$base-contrast: daff-theme.daff-map-deep-get($theme, 'core.base-contrast');
$primary: map.get($theme, primary);

.design-land-sidebar-viewport {
&__sidebar {
border-right: 1px solid daff-theme.daff-illuminate($base, $gray, 2);
}

&__get-started-button {
background: daff-theme.daff-illuminate($base, $gray, 1);

&:hover {
background: daff-theme.daff-illuminate($base, $gray, 2);
}
}

&__docs-tag {
background: $base-contrast;
color: $base;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<daff-sidebar-viewport class="design-land-sidebar-viewport" (backdropClicked)="toggleOpen()" navPlacement="beside">
<daff-sidebar class="design-land-sidebar-viewport__sidebar" [mode]="mode$ | async" [open]="open" (escapePressed)="toggleOpen()">
<daff-sidebar-header>
<div class="design-land-sidebar-viewport__docs-logo">
<daff-branding-logo type="full" class="design-land-sidebar-viewport__logo"></daff-branding-logo>
<div class="design-land-sidebar-viewport__docs-tag">Docs</div>
</div>
</daff-sidebar-header>
<design-land-nav></design-land-nav>
<daff-sidebar-footer>
<a href="https://github.com/graycoreio/daffodil" target="_blank" class="design-land-sidebar-viewport__get-started-button">Get Started</a>
</daff-sidebar-footer>
</daff-sidebar>
<nav daff-sidebar-viewport-nav daff-navbar>
<button daff-icon-button color="theme-contrast" (click)="toggleOpen()" [attr.aria-label]="ariaLabel">
<fa-icon [icon]="faBars"></fa-icon>
</button>
<daff-theme-switch-button class="design-land-sidebar-viewport__theme-switch"></daff-theme-switch-button>
</nav>
<div class="design-land-sidebar-viewport__content">
<daff-article>
<ng-content></ng-content>
</daff-article>
</div>
</daff-sidebar-viewport>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@use 'utilities' as daff;

:host {
--daff-sidebar-side-fixed-top-shift: 64px;
}

.design-land-sidebar-viewport {
&__theme-switch {
margin-left: auto;
}

&__content {
padding: 24px;

@include daff.breakpoint(mobile) {
padding: 48px;
}
}

&__sidebar {
width: 288px;
}

&__get-started-button {
display: block;
font-weight: 500;
padding: 16px;
text-align: center;
text-decoration: none;
}

&__docs-logo {
display: flex;
align-items: center;
gap: 8px;
}

&__logo {
display: flex;
align-items: center;
max-width: 144px;
}

&__docs-tag {
border-radius: 4px;
font-size: 0.875rem;
font-weight: 600;
line-height: 1rem;
padding: 4px;
text-transform: uppercase;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import {
ComponentFixture,
TestBed,
waitForAsync,
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { DaffioGuidesNavModule } from 'apps/daffio/src/app/guides/components/guides-nav/guides-nav.module';

import {
DaffSidebarModule,
DaffSidebarViewportComponent,
DaffSidebarComponent,
} from '@daffodil/design';

import { DesignLandSidebarViewportComponent } from './sidebar-viewport.component';

describe('DesignLandSidebarViewportComponent', () => {
let component: DesignLandSidebarViewportComponent;
let fixture: ComponentFixture<DesignLandSidebarViewportComponent>;

let daffSidebarViewport: DaffSidebarViewportComponent;
let daffSidebar: DaffSidebarComponent;


beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule,
NoopAnimationsModule,
DaffSidebarModule,
DaffioGuidesNavModule,
HttpClientTestingModule,
],
declarations: [
DesignLandSidebarViewportComponent,
],
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(DesignLandSidebarViewportComponent);
component = fixture.componentInstance;

fixture.detectChanges();
daffSidebar = fixture.debugElement.query(By.css('daff-sidebar')).componentInstance;
daffSidebarViewport = fixture.debugElement.query(By.css('daff-sidebar-viewport')).componentInstance;
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Loading

0 comments on commit b2f1e3e

Please sign in to comment.