[HXCS-1479] Breadcrumbs as secondary entry point (#8750)

* Move breadcrumb outside of the core

* Fix styling

* Fix storybook
This commit is contained in:
Popovics András
2023-07-20 16:13:09 +02:00
committed by GitHub
parent e0ab94c680
commit 1ebac21251
19 changed files with 74 additions and 28 deletions

View File

@@ -0,0 +1,75 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Meta, moduleMetadata, Story } from '@storybook/angular';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatIconModule } from '@angular/material/icon';
import { BreadcrumbComponent } from '../components/breadcrumb/breadcrumb.component';
import { BreadcrumbItemComponent } from '../components/breadcrumb-item/breadcrumb-item.component';
import { DemoBreadcrumbComponent } from './demo-breadcrumb.component';
import { CoreStoryModule } from '../../../src/lib/testing/core.story.module';
// https://stackoverflow.com/a/58210459/8820824
type NonFunctionPropertyNames<T> = {[K in keyof T]: T[K] extends () => any ? never : K}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
type StoryWithoutFunction<T> = NonFunctionProperties<Story<T>>;
function storybookCopyStory<T>( story: Story<T>, annotations?: StoryWithoutFunction<T> ): Story<T> {
const cloned = story.bind({});
return Object.assign(cloned, annotations);
}
const meta: Meta = {
title: 'Core/Breadcrumb',
decorators: [
moduleMetadata({
imports: [
CoreStoryModule,
BreadcrumbComponent,
BreadcrumbItemComponent,
MatButtonModule,
MatMenuModule,
MatIconModule
]
})
],
args: {
compact: false,
showBreadcrumbItemWithMenu: false
},
argTypes: {
compact: {control: 'boolean'},
showBreadcrumbItemWithMenu: {control: 'boolean'}
}
};
export default meta;
export const breadcrumb: Story = args => ({
component: DemoBreadcrumbComponent,
props: args
});
export const compact = storybookCopyStory(breadcrumb);
compact.args = {
compact: true
};
export const withMenu = storybookCopyStory(breadcrumb);
withMenu.args = {
showBreadcrumbItemWithMenu: true
};

View File

@@ -0,0 +1,63 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Component} from '@angular/core';
@Component({
selector: 'adf-demo-breadcrumb',
template: `
<adf-breadcrumb [compact]="compact">
<adf-breadcrumb-item>
<a href="/">Home</a>
</adf-breadcrumb-item>
<adf-breadcrumb-item>
<a href="https://www.alfresco.com/">Alfresco</a>
</adf-breadcrumb-item>
<adf-breadcrumb-item>
<a href="https://www.alfresco.com">External Link 1</a>
</adf-breadcrumb-item>
<adf-breadcrumb-item>
<a href="https://www.alfresco.com/">External Link 2</a>
</adf-breadcrumb-item>
<adf-breadcrumb-item>
<a href="https://www.alfresco.com/">External Link 3</a>
</adf-breadcrumb-item>
<adf-breadcrumb-item *ngIf="showBreadcrumbItemWithMenu" aria-current="location" aria-haspopup="true" >
<div>
Current Page
<button mat-icon-button [matMenuTriggerFor]="menu" aria-label="Menu">
<mat-icon>menu_open</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item>Menu Item 1</button>
<button mat-menu-item>Menu Item 2</button>
</mat-menu>
</div>
</adf-breadcrumb-item>
</adf-breadcrumb>
`
})
export class DemoBreadcrumbComponent {
compact = false;
showBreadcrumbItemWithMenu = false;
}

View File

@@ -0,0 +1,32 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, TemplateRef, ViewChild } from '@angular/core';
@Component({
standalone: true,
selector: 'adf-breadcrumb-item',
template: `
<ng-template #breadcrumbItemTemplate>
<ng-content></ng-content>
</ng-template>
`
})
export class BreadcrumbItemComponent {
@ViewChild('breadcrumbItemTemplate', { static: true })
templateRef!: TemplateRef<any>;
}

View File

@@ -0,0 +1,3 @@
<svg width="5" height="8" viewBox="0 0 5 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.229292 0.234315C-0.0751287 0.546734 -0.0751287 1.05327 0.229292 1.36569L2.79616 4L0.229293 6.63432C-0.0751284 6.94673 -0.0751284 7.45327 0.229293 7.76569C0.533714 8.0781 1.02728 8.0781 1.3317 7.76569L4.44943 4.56604L5.00098 4L1.3317 0.234315C1.02728 -0.0781049 0.533713 -0.0781049 0.229292 0.234315Z" fill="#6B7280"/>
</svg>

After

Width:  |  Height:  |  Size: 469 B

View File

@@ -0,0 +1,26 @@
<ng-container>
<nav class="adf-breadcrumb" [class.adf-breadcrumb--compact]="compact" [attr.aria-label]="'CORE.BREADCRUMBS.TITLE' | translate" >
<ol>
<ng-container *ngFor="let breadcrumbTemplate of selectedBreadcrumbs; last as last">
<li adf-breadcrumb-focus class="adf-breadcrumb__item-wrapper">
<ng-container *ngTemplateOutlet="breadcrumbTemplate"></ng-container>
<div *ngIf="!last" class="adf-breadcrumb__chevron" [class.adf-breadcrumb__chevron-before--compact]="compact" ></div>
</li>
<li *ngIf="!last && compact === true" class="adf-breadcrumb__show-all-button-wrapper">
<button
mat-icon-button
(click)="toggleCompact()"
color="primary"
[matTooltip]="'CORE.BREADCRUMBS.SHOWALL' | translate"
matTooltipClass="adf-tooltip"
[attr.aria-label]="'CORE.BREADCRUMBS.SHOWALL' | translate"
>
<mat-icon class="adf-breadcrumb__show-all-button-icon--rotate">more_vert</mat-icon >
</button>
<div class="adf-breadcrumb__chevron" [class.adf-breadcrumb__chevron-after--compact]="compact" ></div>
</li>
</ng-container>
</ol>
</nav>
</ng-container>

View File

@@ -0,0 +1,50 @@
ol {
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
min-height: 40px;
}
.adf-breadcrumb__item-wrapper {
display: flex;
align-items: center;
}
.adf-breadcrumb__chevron {
background-image: url("breadcrumb-chevron.svg");
background-repeat: no-repeat;
background-position: center;
width: 5px;
height: 8px;
margin: 0 8px;
:host-context([dir="rtl"]) & {
transform: scaleX(-1);
}
}
.adf-breadcrumb__chevron-before--compact {
margin: 0 4px 0 8px;
:host-context([dir="rtl"]) & {
margin: 0 8px 0 4px;
}
}
.adf-breadcrumb__chevron-after--compact {
margin: 0 8px 0 4px;
:host-context([dir="rtl"]) & {
margin: 0 4px 0 8px;
}
}
.adf-breadcrumb__show-all-button-wrapper {
display: flex;
align-items: center;
}
.adf-breadcrumb__show-all-button-icon--rotate {
transform: rotate(90deg);
}

View File

@@ -0,0 +1,98 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, EventEmitter, Input, OnChanges, Output, QueryList, SimpleChanges, TemplateRef, ViewChildren } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { map, startWith } from 'rxjs/operators';
import { TranslateModule } from '@ngx-translate/core';
import { BreadcrumbFocusDirective } from '../../directives/breadcrumb-focus.directive';
import { BreadcrumbItemComponent } from '../breadcrumb-item/breadcrumb-item.component';
@Component({
standalone: true,
selector: 'adf-breadcrumb',
templateUrl: './breadcrumb.component.html',
styleUrls: ['./breadcrumb.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ CommonModule, MatIconModule, TranslateModule, MatButtonModule, MatTooltipModule ]
})
export class BreadcrumbComponent implements AfterContentInit, OnChanges {
private _breadcrumbTemplateRefs: Array<TemplateRef<unknown>> = [];
@Input()
compact = false;
@Output()
compactChange: EventEmitter<boolean> = new EventEmitter();
@ViewChildren(BreadcrumbFocusDirective)
breadcrumbFocusItems!: QueryList<BreadcrumbFocusDirective>;
@ContentChildren(BreadcrumbItemComponent)
breadcrumbItems!: QueryList<BreadcrumbItemComponent>;
selectedBreadcrumbs: Array<TemplateRef<unknown>> = [];
constructor(private cdr: ChangeDetectorRef) {}
ngAfterContentInit() {
this.breadcrumbItems.changes
.pipe(
startWith(this.breadcrumbItems),
map((breadcrumbItems: QueryList<BreadcrumbItemComponent>) =>
this.mapToTemplateRefs(breadcrumbItems)
)
)
.subscribe((templateRefs) => {
this._breadcrumbTemplateRefs = templateRefs;
this.setBreadcrumbs(templateRefs);
});
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.compact) {
this.setBreadcrumbs(this._breadcrumbTemplateRefs);
}
}
toggleCompact(compact = false) {
this.compact = compact;
this.setBreadcrumbs(this._breadcrumbTemplateRefs);
this.compactChange.emit(this.compact);
if (!compact) {
this.breadcrumbFocusItems.get(1)?.focusOnFirstFocusableElement();
}
}
private setBreadcrumbs(breadcrumbs: Array<TemplateRef<unknown>>) {
this.selectedBreadcrumbs =
this.compact && breadcrumbs.length > 2
? [breadcrumbs[0], breadcrumbs[breadcrumbs.length - 1]]
: [...breadcrumbs];
this.cdr.detectChanges();
}
private mapToTemplateRefs( breadcrumbItems: QueryList<BreadcrumbItemComponent> ) {
return breadcrumbItems
.toArray()
.map((breadcrumbItem) => breadcrumbItem.templateRef);
}
}

View File

@@ -0,0 +1,46 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Directive, ElementRef } from '@angular/core';
/** @internal */
@Directive({
standalone: true,
selector: '[adf-breadcrumb-focus]',
host: {
class: 'adf-breadcrumb-focus'
}
})
export class BreadcrumbFocusDirective {
constructor(private elementRef: ElementRef) {}
focusOnFirstFocusableElement() {
this.getFocusableElements(this.elementRef.nativeElement)[0].focus();
}
private getFocusableElements(root: HTMLElement): HTMLElement[] {
const allFocusableElements = `button, a, input, select, textarea, [tabindex]:not([tabindex="-1"])`;
return Array.from(
root.querySelectorAll(
allFocusableElements
) as NodeListOf<HTMLElement>
).filter(
(element) =>
!element.hasAttribute('disabled') && element.tabIndex >= 0
);
}
}

View File

@@ -0,0 +1,19 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { BreadcrumbComponent } from './components/breadcrumb/breadcrumb.component';
export { BreadcrumbItemComponent } from './components/breadcrumb-item/breadcrumb-item.component';

View File

@@ -0,0 +1,33 @@
@use 'sass:map';
@use '@angular/material' as mat;
@mixin adf-breadcrumb-theme($theme) {
$config: mat.get-color-config($theme);
$foreground-palette: map.get($config, foreground);
$primary-palette: map.get($config, primary);
$text-color: mat.get-color-from-palette($foreground-palette, text);
$primary: mat.get-color-from-palette($primary-palette, text);
adf-breadcrumb {
.adf-breadcrumb__show-all-button-icon--rotate {
color: mat.get-color-from-palette($primary-palette, 500);
}
.adf-breadcrumb__item-wrapper {
a,
a:active,
a:visited {
color: $primary;
}
&:last-child a {
text-decoration: none;
color: $text-color;
}
&:last-child a:hover {
text-decoration: none;
}
}
}
}