front: composant <app-breadcrumb> mutualisé (dédup markup DSFR)
Extrait le fil d'Ariane DSFR dupliqué dans browse/api-detail/upload vers un composant présentationnel piloté par une liste d'items. Chaque page construit ses items (computed pour browse/api-detail, statique pour upload). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,34 +14,17 @@ import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { ApiService } from '../../core/api.service';
|
||||
import { CategoryService } from '../../core/category.service';
|
||||
import { BreadcrumbComponent, BreadcrumbItem } from '../../shared/breadcrumb/breadcrumb.component';
|
||||
import { ApiEntry, ApiEntryListItem, Category } from '@datacat/shared';
|
||||
|
||||
@Component({
|
||||
selector: 'app-api-detail',
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterLink, ReactiveFormsModule],
|
||||
imports: [CommonModule, RouterLink, ReactiveFormsModule, BreadcrumbComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="fr-container fr-mt-4w fr-mb-4w">
|
||||
<!-- Fil d'Ariane -->
|
||||
<nav class="fr-breadcrumb fr-mb-3w" aria-label="vous êtes ici">
|
||||
<ol class="fr-breadcrumb__list">
|
||||
<li>
|
||||
<a class="fr-breadcrumb__link" routerLink="/">Accueil</a>
|
||||
</li>
|
||||
@if (categoryBreadcrumb().length === 0) {
|
||||
<li><a class="fr-breadcrumb__link" routerLink="/browse">Parcourir</a></li>
|
||||
}
|
||||
@for (crumb of categoryBreadcrumb(); track crumb.id) {
|
||||
<li><a class="fr-breadcrumb__link" [routerLink]="['/browse', crumb.id]">{{ crumb.name }}</a></li>
|
||||
}
|
||||
<li>
|
||||
<a class="fr-breadcrumb__link" aria-current="page">
|
||||
{{ api()?.title ?? 'Chargement...' }}
|
||||
</a>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<app-breadcrumb [items]="breadcrumbItems()" />
|
||||
|
||||
@if (loading()) {
|
||||
<div class="fr-callout fr-mb-4w">
|
||||
@@ -436,6 +419,15 @@ export class ApiDetailComponent implements OnInit, OnDestroy {
|
||||
return ['/browse'];
|
||||
});
|
||||
|
||||
breadcrumbItems = computed<BreadcrumbItem[]>(() => {
|
||||
const crumbs = this.categoryBreadcrumb();
|
||||
const items: BreadcrumbItem[] = [{ label: 'Accueil', link: '/' }];
|
||||
if (crumbs.length === 0) items.push({ label: 'Parcourir', link: '/browse' });
|
||||
for (const crumb of crumbs) items.push({ label: crumb.name, link: ['/browse', crumb.id] });
|
||||
items.push({ label: this.api()?.title ?? 'Chargement...' });
|
||||
return items;
|
||||
});
|
||||
|
||||
/* Onglets */
|
||||
activeTab = signal<'info' | 'contacts'>('info');
|
||||
editingInfo = signal(false);
|
||||
|
||||
@@ -17,32 +17,16 @@ import { CategoryBrowseResponse, Category, CategoryWithCounts, ApiEntryListItem
|
||||
import { forkJoin } from 'rxjs';
|
||||
import { CategoryFormModalComponent } from '../../shared/category-form-modal/category-form-modal.component';
|
||||
import { CategoryDeleteModalComponent } from '../../shared/category-delete-modal/category-delete-modal.component';
|
||||
import { BreadcrumbComponent, BreadcrumbItem } from '../../shared/breadcrumb/breadcrumb.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-browse',
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterLink, CategoryFormModalComponent, CategoryDeleteModalComponent],
|
||||
imports: [CommonModule, RouterLink, CategoryFormModalComponent, CategoryDeleteModalComponent, BreadcrumbComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="fr-container fr-mt-4w fr-mb-4w">
|
||||
<!-- Fil d'Ariane -->
|
||||
<nav class="fr-breadcrumb fr-mb-3w" aria-label="vous êtes ici">
|
||||
<ol class="fr-breadcrumb__list">
|
||||
<li>
|
||||
<a class="fr-breadcrumb__link" routerLink="/">Accueil</a>
|
||||
</li>
|
||||
@for (crumb of ancestors(); track crumb.id) {
|
||||
<li>
|
||||
<a class="fr-breadcrumb__link" [routerLink]="['/browse', crumb.id]">{{ crumb.name }}</a>
|
||||
</li>
|
||||
}
|
||||
@if (browseData()?.category) {
|
||||
<li>
|
||||
<a class="fr-breadcrumb__link" aria-current="page">{{ browseData()!.category!.name }}</a>
|
||||
</li>
|
||||
}
|
||||
</ol>
|
||||
</nav>
|
||||
<app-breadcrumb [items]="breadcrumbItems()" />
|
||||
|
||||
<!-- En-tête -->
|
||||
@if (categoryId()) {
|
||||
@@ -309,6 +293,16 @@ export class BrowseComponent implements OnInit {
|
||||
return d.breadcrumb.slice(0, -1);
|
||||
});
|
||||
|
||||
breadcrumbItems = computed<BreadcrumbItem[]>(() => {
|
||||
const items: BreadcrumbItem[] = [{ label: 'Accueil', link: '/' }];
|
||||
for (const crumb of this.ancestors()) {
|
||||
items.push({ label: crumb.name, link: ['/browse', crumb.id] });
|
||||
}
|
||||
const current = this.browseData()?.category;
|
||||
if (current) items.push({ label: current.name });
|
||||
return items;
|
||||
});
|
||||
|
||||
backUrl = computed<string[]>(() => {
|
||||
const anc = this.ancestors();
|
||||
if (anc.length > 0) return ['/browse', anc[anc.length - 1].id];
|
||||
|
||||
@@ -13,23 +13,17 @@ import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
|
||||
import { Router, RouterLink, ActivatedRoute } from '@angular/router';
|
||||
import { ApiService } from '../../core/api.service';
|
||||
import { CategoryService } from '../../core/category.service';
|
||||
import { BreadcrumbComponent, BreadcrumbItem } from '../../shared/breadcrumb/breadcrumb.component';
|
||||
import { ApiEntry, ApiEntryListItem, Category, API_CONVENTION_LABELS, ApiConvention } from '@datacat/shared';
|
||||
|
||||
@Component({
|
||||
selector: 'app-upload',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ReactiveFormsModule, RouterLink],
|
||||
imports: [CommonModule, ReactiveFormsModule, RouterLink, BreadcrumbComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="fr-container fr-mt-4w fr-mb-4w">
|
||||
<!-- Fil d'Ariane -->
|
||||
<nav class="fr-breadcrumb fr-mb-3w" aria-label="vous êtes ici">
|
||||
<ol class="fr-breadcrumb__list">
|
||||
<li><a class="fr-breadcrumb__link" routerLink="/browse">Parcourir</a></li>
|
||||
<li><a class="fr-breadcrumb__link" routerLink="/catalog">Catalogue</a></li>
|
||||
<li><a class="fr-breadcrumb__link" aria-current="page">Importer une API</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<app-breadcrumb [items]="breadcrumbItems" />
|
||||
|
||||
<h1 class="fr-h2 fr-mb-4w">Importer une API</h1>
|
||||
|
||||
@@ -511,6 +505,12 @@ export class UploadComponent implements OnInit {
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
step = signal(1);
|
||||
readonly breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ label: 'Parcourir', link: '/browse' },
|
||||
{ label: 'Catalogue', link: '/catalog' },
|
||||
{ label: 'Importer une API' },
|
||||
];
|
||||
|
||||
private readonly maxFileSize = 5 * 1024 * 1024; // 5 Mo
|
||||
selectedFiles = signal<File[]>([]);
|
||||
detectedType = signal<'ASYNCAPI' | 'OPENAPI' | null>(null);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
/** Un maillon du fil d'Ariane. `link` absent => élément courant (aria-current). */
|
||||
export interface BreadcrumbItem {
|
||||
label: string;
|
||||
link?: string | unknown[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fil d'Ariane DSFR générique (présentationnel). Chaque page construit sa liste
|
||||
* d'items et la passe ; le dernier item sans `link` est marqué comme courant.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-breadcrumb',
|
||||
standalone: true,
|
||||
imports: [RouterLink],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<nav class="fr-breadcrumb fr-mb-3w" aria-label="vous êtes ici">
|
||||
<ol class="fr-breadcrumb__list">
|
||||
@for (item of items(); track $index) {
|
||||
<li>
|
||||
@if (item.link) {
|
||||
<a class="fr-breadcrumb__link" [routerLink]="item.link">{{ item.label }}</a>
|
||||
} @else {
|
||||
<a class="fr-breadcrumb__link" aria-current="page">{{ item.label }}</a>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ol>
|
||||
</nav>
|
||||
`,
|
||||
})
|
||||
export class BreadcrumbComponent {
|
||||
items = input.required<BreadcrumbItem[]>();
|
||||
}
|
||||
Reference in New Issue
Block a user