Skip to content

Breadcrumbs feature #129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<nb-card>
<nb-card-header>
<h1>Breadcrumbs Component Examples</h1>
<p class="subtitle">
Flexible, dynamic, and static breadcrumbs for Angular projects.
</p>
</nb-card-header>
<nb-card-body>
<section id="static">
<h2>Static Example</h2>
<p>
Use static breadcrumbs for simple, non-dynamic navigation trails. Icons,
custom classes, and collapsing are supported.
</p>
<lib-breadcrumbs
[staticBreadcrumbs]="[
{label: 'Home', url: '/home', icon: 'home-outline'},
{label: 'Library', url: '/library', icon: 'book-outline'},
{
label: 'Data',
url: '/library/data',
icon: 'folder-outline',
disabled: true,
},
]"
separator=">"
[showIcons]="true"
[maxItems]="5"
itemClass="custom-breadcrumb-item"
activeClass="custom-active"
disabledClass="custom-disabled"
separatorClass="custom-separator"
aria-label="breadcrumb"
></lib-breadcrumbs>
<div class="code-block">
<button class="copy-btn" (click)="copy(staticCode)">Copy</button>
<pre><code [innerText]="staticCode"></code></pre>
</div>
</section>
<section id="dynamic">
<h2>Dynamic Example (User/Notes)</h2>
<p>
This example demonstrates dynamic breadcrumbs using route data and
resolvers.<br />
Click a button to see the breadcrumbs update with resolved user and note
titles.
</p>
<div class="demo-links">
<button class="nav-btn" [routerLink]="['user', 12, 'notes', 1]">
User 12 / Note 1
</button>
<button class="nav-btn" [routerLink]="['user', 7, 'notes', 2]">
User 7 / Note 2
</button>
</div>
<div class="dynamic-info">
<div class="current-path">
<strong>Current path:</strong> <code>{{ currentPath }}</code>
</div>
<div *ngIf="resolvedUser || resolvedNote" class="resolved-data">
<strong>Resolved data:</strong>
<ng-container *ngIf="resolvedUser">
User:
<a
[routerLink]="['/main/components/arc-comp/user', getUserId()]"
class="user-link"
title="Go to user page"
>
<nb-icon
icon="person-outline"
style="vertical-align: middle; margin-right: 4px"
></nb-icon>
{{ resolvedUser.name }}
</a>
</ng-container>
<span *ngIf="resolvedNote"> | Note: {{ resolvedNote.title }}</span>
</div>
</div>
<lib-breadcrumbs aria-label="breadcrumb"></lib-breadcrumbs>
<div class="how-it-works">
<h3>How it works</h3>
<ol>
<li>
You navigate to a dynamic route (e.g.,
<code>/user/12/notes/1</code>).
</li>
<li>
Resolvers fetch user and note data before the route activates.
</li>
<li>
The <code>breadcrumb</code> function uses this data for labels.
</li>
<li>The breadcrumb trail updates automatically.</li>
</ol>
</div>
<div class="code-block">
<button class="copy-btn" (click)="copy(dynamicCode)">Copy</button>
<pre><code [innerText]="dynamicCode"></code></pre>
</div>
<hr class="section-divider" />
</section>
<section id="api">
<h2>API</h2>
<p>See all available inputs for customization:</p>
<table class="api-table">
<tr>
<th>Input</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>staticBreadcrumbs</td>
<td>Breadcrumb[]</td>
<td>Static breadcrumb items (optional)</td>
</tr>
<tr>
<td>separator</td>
<td>string</td>
<td>Separator character/string (default: /)</td>
</tr>
<tr>
<td>maxItems</td>
<td>number</td>
<td>Max breadcrumbs before collapsing (default: 8)</td>
</tr>
<tr>
<td>showIcons</td>
<td>boolean</td>
<td>Show icons if provided (default: false)</td>
</tr>
<tr>
<td>itemClass</td>
<td>string</td>
<td>Custom class for breadcrumb items</td>
</tr>
<tr>
<td>activeClass</td>
<td>string</td>
<td>Class for active (last) item</td>
</tr>
<tr>
<td>disabledClass</td>
<td>string</td>
<td>Class for disabled items</td>
</tr>
<tr>
<td>separatorClass</td>
<td>string</td>
<td>Class for separator</td>
</tr>
</table>
</section>
</nb-card-body>
</nb-card>
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
.breadcrumbs-demo-nav {
margin-bottom: 1rem;
a {
margin-right: 0.5rem;
color: #3366ff;
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
}

.demo-links {
margin-bottom: 1rem;
display: flex;
gap: 1rem;
}
.nav-btn {
background: #3366ff;
color: #fff;
border: none;
border-radius: 4px;
padding: 0.4rem 1.2rem;
font-size: 1em;
cursor: pointer;
transition: background 0.2s;
box-shadow: 0 1px 4px rgba(51, 102, 255, 0.08);
}
.nav-btn:hover {
background: #254edb;
}
.dynamic-info {
background: #f7faff;
border: 1px solid #e0e7ef;
border-radius: 6px;
padding: 0.7rem 1rem;
margin-bottom: 1rem;
font-size: 0.98em;
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.resolved-data {
color: #3366ff;
font-weight: 500;
margin-top: 0.2rem;
}
.section-divider {
border: none;
border-top: 2px solid #e0e7ef;
margin: 2.5rem 0 1.5rem 0;
}

section {
margin-bottom: 2.5rem;
}

pre {
background: #f5f5f5;
padding: 1rem;
border-radius: 4px;
overflow-x: auto;
font-size: 0.95em;
}

h2 {
margin-top: 2rem;
font-size: 1.25rem;
}

.code-block {
background: #f5f5f5;
border-radius: 6px;
padding: 1rem;
margin: 1rem 0 2rem 0;
position: relative;
overflow-x: auto;
}
.copy-btn {
position: absolute;
top: 1rem;
right: 1rem;
background: #3366ff;
color: #fff;
border: none;
border-radius: 4px;
padding: 0.3rem 0.8rem;
font-size: 0.95em;
cursor: pointer;
transition: background 0.2s;
z-index: 2;
}
.copy-btn:hover {
background: #254edb;
}
.current-path {
margin: 0.5rem 0 1rem 0;
font-family: monospace;
background: #eef2fa;
border-left: 3px solid #3366ff;
padding: 0.3rem 0.8rem;
border-radius: 4px;
font-size: 0.98em;
}
.how-it-works {
background: #eaf6ff;
border-radius: 6px;
padding: 1rem;
margin-top: 1.5rem;
font-size: 0.98em;
}
.api-table {
width: 100%;
border-collapse: collapse;
margin-top: 1rem;
background: #f8fafd;
border-radius: 6px;
overflow: hidden;
}
.api-table th,
.api-table td {
border: 1px solid #e0e0e0;
padding: 0.5rem 0.8rem;
text-align: left;
}
.api-table th {
background: #f0f4fa;
font-weight: 600;
}
.subtitle {
color: #6c7a99;
font-size: 1.1em;
margin-top: 0.5rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import {Component} from '@angular/core';
import {CommonModule} from '@angular/common';
import {NbCardModule, NbIconModule} from '@nebular/theme';
import {BreadcrumbsComponent} from './breadcrumbs.component';
import {RouterModule, Router, ActivatedRoute} from '@angular/router';

@Component({
selector: 'breadcrumbs-demo',
standalone: true,
imports: [
CommonModule,
NbCardModule,
NbIconModule,
BreadcrumbsComponent,
RouterModule,
],
templateUrl: './breadcrumbs-demo.component.html',
styleUrls: ['./breadcrumbs-demo.component.scss'],
})
export class BreadcrumbsDemoComponent {
staticCode = `<lib-breadcrumbs
[staticBreadcrumbs]="[...your items...]"
separator=">"
[showIcons]="true"
[maxItems]="5"
itemClass="custom-breadcrumb-item"
activeClass="custom-active"
disabledClass="custom-disabled"
separatorClass="custom-separator"
aria-label="breadcrumb"
></lib-breadcrumbs>`;

dynamicCode = `// In your routing module
{
path: 'user/:userId',
resolve: { user: UserResolver },
data: {
breadcrumb: (data, params) => data.user?.name || \`User #\${params.userId}\`
},
children: [
{
path: 'notes/:noteId',
resolve: { note: NoteResolver },
data: {
breadcrumb: (data, params) => data.note?.title || \`Note #\${params.noteId}\`
},
component: NoteDetailComponent
}
]
}

// In your template
<lib-breadcrumbs aria-label="breadcrumb"></lib-breadcrumbs>`;

currentPath = '';
resolvedUser: any = null;
resolvedNote: any = null;

constructor(
private router: Router,
private route: ActivatedRoute,
) {
this.router.events.subscribe(() => {
this.currentPath = this.router.url;
this.updateResolvedData();
});
this.currentPath = this.router.url;
this.updateResolvedData();
}

updateResolvedData() {
let snapshot = this.route.snapshot;
// Traverse to the deepest child
while (snapshot.firstChild) {
snapshot = snapshot.firstChild;
}
this.resolvedUser = snapshot.data['user'] || null;
this.resolvedNote = snapshot.data['note'] || null;
}

getUserId(): string | null {
let snapshot = this.route.snapshot;
while (snapshot.firstChild) {
snapshot = snapshot.firstChild;
}
return snapshot.paramMap.get('userId');
}

copy(code: string) {
navigator.clipboard.writeText(code);
}
}
Loading