From 037dce0ae7a8b508b42f515abf26b4168640361d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Popovics=20Andr=C3=A1s?= Date: Tue, 27 Jun 2023 15:09:37 +0200 Subject: [PATCH] [HXCS-1479] Core breadcrumbs component (#8695) * Moving breadcrumb component from Hyland UI to ADF and renaming selectors and entities with proper prefix * Cleaning up a bit the breadcumbs * Stabilising breadcrumbs and adding storibook * Fix change requests --- .../src/lib/breadcrumb/_breadcrumb.theme.scss | 22 +++++ .../breadcrumb/_stories/breadcrumb.stories.ts | 75 ++++++++++++++ .../_stories/demo-breadcrumb.component.ts | 63 ++++++++++++ .../breadcrumb-item.component.ts | 32 ++++++ .../breadcrumb/breadcrumb-chevron.svg | 3 + .../breadcrumb/breadcrumb.component.html | 26 +++++ .../breadcrumb/breadcrumb.component.scss | 50 ++++++++++ .../breadcrumb/breadcrumb.component.ts | 98 +++++++++++++++++++ .../directives/breadcrumb-focus.directive.ts | 46 +++++++++ lib/core/src/lib/breadcrumb/public-api.ts | 19 ++++ lib/core/src/lib/i18n/en.json | 4 + lib/core/src/lib/styles/_index.scss | 2 + 12 files changed, 440 insertions(+) create mode 100644 lib/core/src/lib/breadcrumb/_breadcrumb.theme.scss create mode 100644 lib/core/src/lib/breadcrumb/_stories/breadcrumb.stories.ts create mode 100644 lib/core/src/lib/breadcrumb/_stories/demo-breadcrumb.component.ts create mode 100644 lib/core/src/lib/breadcrumb/components/breadcrumb-item/breadcrumb-item.component.ts create mode 100644 lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb-chevron.svg create mode 100644 lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.html create mode 100644 lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.scss create mode 100644 lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.ts create mode 100644 lib/core/src/lib/breadcrumb/directives/breadcrumb-focus.directive.ts create mode 100644 lib/core/src/lib/breadcrumb/public-api.ts diff --git a/lib/core/src/lib/breadcrumb/_breadcrumb.theme.scss b/lib/core/src/lib/breadcrumb/_breadcrumb.theme.scss new file mode 100644 index 0000000000..4a24cc87f5 --- /dev/null +++ b/lib/core/src/lib/breadcrumb/_breadcrumb.theme.scss @@ -0,0 +1,22 @@ +@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); + + .adf-breadcrumb__show-all-button-icon--rotate { + color: mat.get-color-from-palette($primary-palette, 500); + } + + .adf-breadcrumb .adf-breadcrumb__item-wrapper:last-child a { + text-decoration: none; + color: $text-color; + } + + .adf-breadcrumb .adf-breadcrumb__item-wrapper:last-child a:hover { + text-decoration: none; + } +} diff --git a/lib/core/src/lib/breadcrumb/_stories/breadcrumb.stories.ts b/lib/core/src/lib/breadcrumb/_stories/breadcrumb.stories.ts new file mode 100644 index 0000000000..17d11e1f62 --- /dev/null +++ b/lib/core/src/lib/breadcrumb/_stories/breadcrumb.stories.ts @@ -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 '../../testing/core.story.module'; + +// https://stackoverflow.com/a/58210459/8820824 +type NonFunctionPropertyNames = {[K in keyof T]: T[K] extends () => any ? never : K}[keyof T]; +type NonFunctionProperties = Pick>; +type StoryWithoutFunction = NonFunctionProperties>; + +function storybookCopyStory( story: Story, annotations?: StoryWithoutFunction ): Story { + 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 +}; diff --git a/lib/core/src/lib/breadcrumb/_stories/demo-breadcrumb.component.ts b/lib/core/src/lib/breadcrumb/_stories/demo-breadcrumb.component.ts new file mode 100644 index 0000000000..14a03c8f93 --- /dev/null +++ b/lib/core/src/lib/breadcrumb/_stories/demo-breadcrumb.component.ts @@ -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: ` + + + Home + + + + Alfresco + + + + External Link 1 + + + + External Link 2 + + + + External Link 3 + + + +
+ Current Page + + + + + + +
+
+
+ ` +}) +export class DemoBreadcrumbComponent { + compact = false; + showBreadcrumbItemWithMenu = false; +} diff --git a/lib/core/src/lib/breadcrumb/components/breadcrumb-item/breadcrumb-item.component.ts b/lib/core/src/lib/breadcrumb/components/breadcrumb-item/breadcrumb-item.component.ts new file mode 100644 index 0000000000..e95199148c --- /dev/null +++ b/lib/core/src/lib/breadcrumb/components/breadcrumb-item/breadcrumb-item.component.ts @@ -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: ` + + + + ` +}) +export class BreadcrumbItemComponent { + @ViewChild('breadcrumbItemTemplate', { static: true }) + templateRef!: TemplateRef; +} diff --git a/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb-chevron.svg b/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb-chevron.svg new file mode 100644 index 0000000000..bba24a1b65 --- /dev/null +++ b/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb-chevron.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.html b/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.html new file mode 100644 index 0000000000..d976cb278c --- /dev/null +++ b/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.html @@ -0,0 +1,26 @@ + + + diff --git a/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.scss b/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.scss new file mode 100644 index 0000000000..9ffb3ffdb2 --- /dev/null +++ b/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.scss @@ -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); +} diff --git a/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.ts b/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.ts new file mode 100644 index 0000000000..c19776ada0 --- /dev/null +++ b/lib/core/src/lib/breadcrumb/components/breadcrumb/breadcrumb.component.ts @@ -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> = []; + + @Input() + compact = false; + + @Output() + compactChange: EventEmitter = new EventEmitter(); + + @ViewChildren(BreadcrumbFocusDirective) + breadcrumbFocusItems!: QueryList; + + @ContentChildren(BreadcrumbItemComponent) + breadcrumbItems!: QueryList; + + selectedBreadcrumbs: Array> = []; + + constructor(private cdr: ChangeDetectorRef) {} + + ngAfterContentInit() { + this.breadcrumbItems.changes + .pipe( + startWith(this.breadcrumbItems), + map((breadcrumbItems: QueryList) => + 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>) { + this.selectedBreadcrumbs = + this.compact && breadcrumbs.length > 2 + ? [breadcrumbs[0], breadcrumbs[breadcrumbs.length - 1]] + : [...breadcrumbs]; + this.cdr.detectChanges(); + } + + private mapToTemplateRefs( breadcrumbItems: QueryList ) { + return breadcrumbItems + .toArray() + .map((breadcrumbItem) => breadcrumbItem.templateRef); + } +} diff --git a/lib/core/src/lib/breadcrumb/directives/breadcrumb-focus.directive.ts b/lib/core/src/lib/breadcrumb/directives/breadcrumb-focus.directive.ts new file mode 100644 index 0000000000..ff7996dce4 --- /dev/null +++ b/lib/core/src/lib/breadcrumb/directives/breadcrumb-focus.directive.ts @@ -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 + ).filter( + (element) => + !element.hasAttribute('disabled') && element.tabIndex >= 0 + ); + } +} diff --git a/lib/core/src/lib/breadcrumb/public-api.ts b/lib/core/src/lib/breadcrumb/public-api.ts new file mode 100644 index 0000000000..3f7fb33524 --- /dev/null +++ b/lib/core/src/lib/breadcrumb/public-api.ts @@ -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'; diff --git a/lib/core/src/lib/i18n/en.json b/lib/core/src/lib/i18n/en.json index 362a9b32a7..9a6a2f690f 100644 --- a/lib/core/src/lib/i18n/en.json +++ b/lib/core/src/lib/i18n/en.json @@ -106,6 +106,10 @@ "UPDATE": "Update" } }, + "BREADCRUMBS": { + "TITLE": "Breadcrumbs", + "SHOWALL": "Show all" + }, "FILE_DIALOG": { "FILE_LOCK": "Lock file", "ALLOW_OTHERS_CHECKBOX": "Allow the owner to modify this file", diff --git a/lib/core/src/lib/styles/_index.scss b/lib/core/src/lib/styles/_index.scss index 63e23e3fa0..ade51a2f33 100644 --- a/lib/core/src/lib/styles/_index.scss +++ b/lib/core/src/lib/styles/_index.scss @@ -8,6 +8,7 @@ @import '../styles/mixins'; @import '../form/components/widgets/form.theme'; @import '../clipboard/clipboard.theme'; +@import '../breadcrumb/breadcrumb.theme'; @import './snackbar.theme'; @import './material.theme'; @import './components-variables'; @@ -111,6 +112,7 @@ } @include mat-datetimepicker-theme($theme); + @include adf-breadcrumb-theme($theme); @include adf-snackbar-theme; @include adf-material-theme; @include adf-components-variables;