Skip to content

Commit 64a0120

Browse files
authored
Merge pull request #2665 from Teradata/fix/DSYS-502
feat(breadcrumbs): new breadcrumbs behavior on responsive screens
2 parents cdc31fa + 2e9c904 commit 64a0120

File tree

12 files changed

+555
-152
lines changed

12 files changed

+555
-152
lines changed

apps/docs-app/src/app/content/components/component-demos/markdown-navigator/demos/markdown-navigator-demo-basic/markdown-navigator-demo-basic.component.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,24 @@ export class MarkdownNavigatorDemoBasicComponent {
4343
icon: 'code_fork_outlined;covalent',
4444
children: [
4545
{
46-
title: 'Sub item',
46+
title: 'Covalent Sub item Folder 1',
4747
icon: 'folder',
4848
markdownString: '# Sub Item\n\nA nested example.',
49+
children: [
50+
{
51+
title: 'Covalent Sub item Folder 2',
52+
icon: 'folder',
53+
markdownString: '# Sub Item\n\nA nested example.',
54+
children: [
55+
{
56+
title: 'Covalent Sub item 3',
57+
icon: 'folder',
58+
markdownString: '# Sub Item\n\nA nested example.',
59+
children: [],
60+
},
61+
],
62+
},
63+
],
4964
},
5065
],
5166
},

apps/docs-app/src/styles.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@use '@angular/material' as mat;
22
@use '@covalent/tokens' as variables;
3+
@import '@covalent/tokens/index.css';
34
@import '../../../libs/angular/theming/all-theme';
45
@import '../../../libs/markdown/markdown-theme';
56
@import '../../../libs/angular-highlight/highlight-theme';

libs/angular/breadcrumbs/README.md

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
# td-breadcrumbs
22

3-
`td-breadcrumbs` element generates breadcrumbs for navigation. Handles Responsive by removing breadcrumbs from the beginning of the list as allowable space decreases.
3+
`td-breadcrumbs` element generates breadcrumbs for navigation. Handles responsive layouts intelligently by always showing the first and last breadcrumb, while middle items are hidden in an overflow menu (···) when space is limited. The last breadcrumb is truncated with ellipsis if too long.
44

55
## API Summary
66

77
#### Inputs
88

99
- separatorIcon?: string
1010
- Sets the icon url shown between breadcrumbs. Defaults to 'chevron_right'.
11+
- size?: string
12+
- Sets the typography size. Options: 'headline1', 'headline2', 'headline3', 'headline4', 'headline5', 'headline6', 'subtitle1', 'subtitle2', 'body1', 'body2', 'button', 'caption'
13+
- **Note:** Requires `@covalent/tokens/index.css` to be imported in your global styles
1114

1215
#### Methods
1316

@@ -17,7 +20,9 @@
1720
#### Attributes
1821

1922
- hiddenBreadcrumbs: TdBreadcrumbComponent[]
20-
- Array of currently hidden breadcrumbs (responsive)
23+
- Array of currently hidden breadcrumbs shown in the overflow menu (responsive)
24+
- showOverflowButton: boolean
25+
- Whether the overflow menu button (···) is currently displayed
2126

2227
# td-breadcrumb
2328

@@ -34,6 +39,23 @@
3439

3540
## Setup
3641

42+
### Standalone Components (Recommended)
43+
44+
Import the standalone components directly:
45+
46+
```typescript
47+
import { TdBreadcrumbsComponent, TdBreadcrumbComponent } from '@covalent/core/breadcrumbs';
48+
49+
@Component({
50+
standalone: true,
51+
imports: [TdBreadcrumbsComponent, TdBreadcrumbComponent, ...],
52+
...
53+
})
54+
export class MyComponent {}
55+
```
56+
57+
### NgModule (Legacy)
58+
3759
Import the [CovalentBreadcrumbsModule] in your NgModule:
3860

3961
```typescript
@@ -62,10 +84,21 @@ Basic Example:
6284
</td-breadcrumbs>
6385
```
6486

87+
Example with typography size:
88+
89+
```html
90+
<td-breadcrumbs size="caption">
91+
<a td-breadcrumb [routerLink]="'/'">Home</a>
92+
<a td-breadcrumb [routerLink]="'/projects'">Projects</a>
93+
<a td-breadcrumb [routerLink]="'/project/123'">Project Details</a>
94+
<td-breadcrumb>Settings</td-breadcrumb>
95+
</td-breadcrumbs>
96+
```
97+
6598
Example with all inputs/outputs:
6699

67100
```html
68-
<td-breadcrumbs #breadcrumbs separatorIcon="motorcycle">
101+
<td-breadcrumbs #breadcrumbs separatorIcon="motorcycle" size="body2">
69102
<a td-breadcrumb [routerLink]="'/'">Home</a>
70103
<a td-breadcrumb [routerLink]="'/layouts'">Layouts</a>
71104
<a td-breadcrumb [routerLink]="'/layouts2'">Layouts2</a>
@@ -74,20 +107,23 @@ Example with all inputs/outputs:
74107
</td-breadcrumbs>
75108
<mat-divider></mat-divider>
76109
<div>Total Breadcrumbs Count: {{breadcrumbs.count}}</div>
77-
<div>
78-
Hidden Breadcrumbs Count (shrink window to see):
79-
{{breadcrumbs.hiddenBreadcrumbs.length}}
80-
</div>
81-
<ng-template
82-
let-breadcrumb
83-
let-index="index"
84-
ngFor
85-
[ngForOf]="breadcrumbs.hiddenBreadcrumbs"
86-
>
110+
<div>Hidden Breadcrumbs Count (shrink window to see overflow menu): {{breadcrumbs.hiddenBreadcrumbs.length}}</div>
111+
<div *ngIf="breadcrumbs.showOverflowButton">Overflow menu (···) is visible with {{breadcrumbs.hiddenBreadcrumbs.length}} hidden items</div>
112+
<ng-template let-breadcrumb let-index="index" ngFor [ngForOf]="breadcrumbs.hiddenBreadcrumbs">
87113
<div>
88-
<p>Breadcrumb Number: {{index}}</p>
89-
<p>Breadcrumb Width: {{breadcrumb?.width}}</p>
114+
<p>Hidden Breadcrumb {{index}}: {{breadcrumb?.fullText}}</p>
115+
<p>Original Width: {{breadcrumb?.width}}px</p>
90116
<mat-divider></mat-divider>
91117
</div>
92118
</ng-template>
93119
```
120+
121+
## Responsive Behavior
122+
123+
The breadcrumbs component automatically handles responsive layouts:
124+
125+
- **First breadcrumb**: Always visible (typically "Home" or root)
126+
- **Last breadcrumb**: Always visible (current page), truncated with ellipsis if too long
127+
- **Middle breadcrumbs**: Hidden in overflow menu (···) when space is limited
128+
- **Overflow menu**: Clickable button that shows all hidden breadcrumbs
129+
- **Tooltips**: Displayed on truncated breadcrumbs showing the full text
Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1-
<ng-content></ng-content>
2-
<mat-icon
3-
*ngIf="displayIcon"
4-
class="td-breadcrumb-separator-icon"
5-
[style.cursor]="'default'"
6-
(click)="_handleIconClick($event)"
7-
>{{ separatorIcon }}</mat-icon
1+
<span
2+
class="td-breadcrumb-text"
3+
[class.td-breadcrumb-truncated]="shouldTruncate"
4+
[matTooltip]="shouldTruncate ? fullText : ''"
5+
[matTooltipDisabled]="!shouldTruncate"
6+
[attr.aria-label]="shouldTruncate ? fullText : null"
87
>
8+
<ng-content></ng-content>
9+
</span>
10+
@if (displayIcon) {
11+
<mat-icon
12+
class="td-breadcrumb-separator-icon"
13+
[style.cursor]="'default'"
14+
(click)="_handleIconClick($event)"
15+
aria-hidden="true"
16+
>{{ separatorIcon }}</mat-icon
17+
>
18+
}

libs/angular/breadcrumbs/src/breadcrumb/breadcrumb.component.scss

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
align-content: center;
88
flex-shrink: 0;
99
justify-content: flex-end;
10+
max-width: 100%;
1011

1112
::ng-deep > * {
1213
margin: 0 10px;
@@ -24,3 +25,22 @@
2425
padding: 0;
2526
}
2627
}
28+
29+
.td-breadcrumb-text {
30+
margin: 0;
31+
padding: 0;
32+
flex: 1 1 auto;
33+
min-width: 0;
34+
35+
&.td-breadcrumb-truncated {
36+
display: block;
37+
overflow: hidden;
38+
text-overflow: ellipsis;
39+
white-space: nowrap;
40+
max-width: 100%;
41+
}
42+
}
43+
44+
mat-icon.td-breadcrumb-separator-icon {
45+
flex-shrink: 0;
46+
}

libs/angular/breadcrumbs/src/breadcrumb/breadcrumb.component.ts

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,34 @@ import {
99
inject,
1010
} from '@angular/core';
1111
import { MatIcon } from '@angular/material/icon';
12+
import { MatTooltipModule } from '@angular/material/tooltip';
1213

1314
@Component({
1415
selector: 'td-breadcrumb, a[td-breadcrumb]',
1516
styleUrls: ['./breadcrumb.component.scss'],
1617
templateUrl: './breadcrumb.component.html',
17-
imports: [CommonModule, MatIcon],
18+
imports: [CommonModule, MatIcon, MatTooltipModule],
1819
changeDetection: ChangeDetectionStrategy.OnPush,
1920
})
2021
export class TdBreadcrumbComponent implements AfterViewInit {
21-
private _elementRef = inject(ElementRef);
2222
private _changeDetectorRef = inject(ChangeDetectorRef);
2323

2424
private _displayCrumb = true;
2525
private _width = 0;
2626
private _displayIcon = true;
2727
private _separatorIcon = 'chevron_right';
28+
private _shouldTruncate = false;
29+
private _maxWidth?: number;
30+
31+
public elementRef = inject(ElementRef);
32+
public fullText = '';
33+
public flexOrder = 0;
34+
public isCurrentPage = false;
35+
36+
@HostBinding('attr.aria-current')
37+
get ariaCurrent(): string | null {
38+
return this.isCurrentPage ? 'page' : null;
39+
}
2840

2941
@HostBinding('class.mdc-button') matButtonClass = true;
3042
@HostBinding('class.td-breadcrumb') tdBreadCrumbClass = true;
@@ -51,14 +63,36 @@ export class TdBreadcrumbComponent implements AfterViewInit {
5163
});
5264
}
5365

66+
get shouldTruncate(): boolean {
67+
return this._shouldTruncate;
68+
}
69+
70+
public set shouldTruncate(shouldTruncate: boolean) {
71+
this._shouldTruncate = shouldTruncate;
72+
setTimeout(() => {
73+
this._changeDetectorRef.markForCheck();
74+
});
75+
}
76+
77+
get maxWidth(): number | undefined {
78+
return this._maxWidth;
79+
}
80+
81+
set maxWidth(maxWidth: number | undefined) {
82+
this._maxWidth = maxWidth;
83+
setTimeout(() => {
84+
this._changeDetectorRef.markForCheck();
85+
});
86+
}
87+
5488
get displayCrumb(): boolean {
5589
return this._displayCrumb;
5690
}
5791

5892
/**
5993
* Whether to display the crumb or not
6094
*/
61-
set displayCrumb(shouldDisplay: boolean) {
95+
public set displayCrumb(shouldDisplay: boolean) {
6296
this._displayCrumb = shouldDisplay;
6397
setTimeout(() => {
6498
this._changeDetectorRef.markForCheck();
@@ -82,12 +116,28 @@ export class TdBreadcrumbComponent implements AfterViewInit {
82116
return this._displayCrumb ? undefined : 'none';
83117
}
84118

119+
@HostBinding('style.max-width.px')
120+
get maxWidthBinding(): number | undefined {
121+
return this._shouldTruncate ? this._maxWidth : undefined;
122+
}
123+
124+
@HostBinding('style.order')
125+
get orderBinding(): number {
126+
return this.flexOrder;
127+
}
128+
85129
ngAfterViewInit(): void {
86130
// set the width from the actual rendered DOM element
87131
setTimeout(() => {
88132
this._width = (<HTMLElement>(
89-
this._elementRef.nativeElement
133+
this.elementRef.nativeElement
90134
)).getBoundingClientRect().width;
135+
const textSpan = this.elementRef.nativeElement.querySelector(
136+
'.td-breadcrumb-text',
137+
);
138+
if (textSpan) {
139+
this.fullText = textSpan.textContent?.trim() || '';
140+
}
91141
this._changeDetectorRef.markForCheck();
92142
});
93143
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,34 @@
11
<ng-content></ng-content>
2+
3+
@if (showOverflowButton) {
4+
<button
5+
mat-icon-button
6+
class="td-breadcrumbs-overflow-button"
7+
[matMenuTriggerFor]="overflowMenu"
8+
[attr.aria-label]="overflowButtonAriaLabel"
9+
[style.order]="overflowButtonOrder"
10+
[attr.data-test-id]="'breadcrumb-overflow-button'"
11+
>
12+
<mat-icon>more_horiz</mat-icon>
13+
</button>
14+
<mat-icon
15+
class="td-breadcrumb-separator-icon"
16+
[style.order]="overflowButtonOrder"
17+
color="primary"
18+
aria-hidden="true"
19+
>{{ separatorIcon }}</mat-icon
20+
>
21+
}
22+
23+
<mat-menu #overflowMenu="matMenu" class="td-breadcrumbs-overflow-menu">
24+
@for (item of overflowMenuItems; track item; let index = $index) {
25+
<button
26+
mat-menu-item
27+
(click)="handleOverflowItemClick(item)"
28+
[attr.data-test-id]="'breadcrumb-overflow-item-' + index"
29+
[attr.aria-label]="'Navigate to ' + getItemText(item)"
30+
>
31+
{{ getItemText(item) }}
32+
</button>
33+
}
34+
</mat-menu>

libs/angular/breadcrumbs/src/breadcrumbs.component.scss

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,37 @@
66
white-space: nowrap;
77
}
88
}
9+
10+
.td-breadcrumbs-overflow-button {
11+
margin: 0 4px;
12+
flex-shrink: 0;
13+
14+
mat-icon {
15+
font-size: 18px;
16+
height: 18px;
17+
width: 18px;
18+
line-height: unset;
19+
}
20+
21+
+ .td-breadcrumb-separator-icon {
22+
order: inherit;
23+
font-size: 16px;
24+
width: 16px;
25+
height: 16px;
26+
flex-shrink: 0;
27+
margin: 0 10px;
28+
}
29+
}
30+
31+
.td-breadcrumb-overflow-menu {
32+
max-width: 300px;
33+
34+
button {
35+
max-width: 100%;
36+
37+
::ng-deep .mat-mdc-menu-item-text {
38+
white-space: normal;
39+
line-height: 1.4;
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)