@lifecorner/angular-ui-kit 3.34.0 → 3.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-alert.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-alert.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-bottom-nav.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs +6 -6
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-card.mjs +82 -16
- package/fesm2022/lifecorner-angular-ui-kit-card.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs +23 -4
- package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs +43 -5
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-drawer.mjs +30 -2
- package/fesm2022/lifecorner-angular-ui-kit-drawer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs +45 -8
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-hero.mjs +57 -5
- package/fesm2022/lifecorner-angular-ui-kit-hero.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-internal-overlay.mjs +6 -1
- package/fesm2022/lifecorner-angular-ui-kit-internal-overlay.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-list.mjs +24 -5
- package/fesm2022/lifecorner-angular-ui-kit-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-modal.mjs +2 -4
- package/fesm2022/lifecorner-angular-ui-kit-modal.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs +41 -1
- package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-stacked-bar-chart.mjs +47 -5
- package/fesm2022/lifecorner-angular-ui-kit-stacked-bar-chart.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-state-strip.mjs +203 -0
- package/fesm2022/lifecorner-angular-ui-kit-state-strip.mjs.map +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs +25 -4
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +3 -0
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-toast.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit.mjs +1 -0
- package/package.json +5 -1
- package/src/styles/theme-contrast.spec.ts +74 -0
- package/types/lifecorner-angular-ui-kit-alert.d.ts +12 -2
- package/types/lifecorner-angular-ui-kit-bottom-nav.d.ts +5 -1
- package/types/lifecorner-angular-ui-kit-button.d.ts +7 -3
- package/types/lifecorner-angular-ui-kit-card.d.ts +51 -3
- package/types/lifecorner-angular-ui-kit-combobox.d.ts +36 -0
- package/types/lifecorner-angular-ui-kit-description-list.d.ts +40 -0
- package/types/lifecorner-angular-ui-kit-drawer.d.ts +37 -1
- package/types/lifecorner-angular-ui-kit-gallery.d.ts +3 -1
- package/types/lifecorner-angular-ui-kit-header.d.ts +44 -2
- package/types/lifecorner-angular-ui-kit-hero.d.ts +32 -0
- package/types/lifecorner-angular-ui-kit-internal-overlay.d.ts +9 -1
- package/types/lifecorner-angular-ui-kit-list.d.ts +17 -0
- package/types/lifecorner-angular-ui-kit-sidenav.d.ts +42 -1
- package/types/lifecorner-angular-ui-kit-stacked-bar-chart.d.ts +36 -0
- package/types/lifecorner-angular-ui-kit-state-strip.d.ts +107 -0
- package/types/lifecorner-angular-ui-kit-table.d.ts +37 -0
- package/types/lifecorner-angular-ui-kit-textarea.d.ts +17 -0
- package/types/lifecorner-angular-ui-kit-toast.d.ts +4 -2
- package/types/lifecorner-angular-ui-kit.d.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-gallery.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/gallery/gallery.component.ts","../../../../libs/angular-ui-kit/src/lib/gallery/gallery.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n HostListener,\n Injector,\n OnDestroy,\n afterNextRender,\n computed,\n inject,\n input,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport { CdkTrapFocus } from '@angular/cdk/a11y';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_CHEVRON_LEFT, ICON_CHEVRON_RIGHT, ICON_PLUS, ICON_X_MARK } from '@lifecorner/angular-ui-kit/icons';\nimport { NgStyle } from '@angular/common';\nimport { isSafeImageUrl } from '@lifecorner/angular-ui-kit/internal/url';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\nexport type GalleryLayout = 'grid' | 'masonry';\nexport type GallerySize = 'sm' | 'md' | 'lg';\n\nexport interface GalleryItem {\n /** Image source URL */\n src: string;\n /** Thumbnail URL (falls back to src if not provided) */\n thumbnail?: string;\n /** Alt text for accessibility */\n alt?: string;\n /** Optional caption displayed below the image */\n caption?: string;\n /** Optional category for filtering */\n category?: string;\n}\n\nconst SIZE_COLUMNS: Record<GallerySize, number> = {\n sm: 4,\n md: 3,\n lg: 2,\n};\n\n/**\n * Gallery component for displaying image collections with lightbox.\n *\n * Features:\n * - Grid and masonry layout modes\n * - Responsive column count via size presets (sm, md, lg thumbnails)\n * - Configurable custom column count\n * - Lightbox overlay with navigation (prev/next)\n * - Keyboard navigation (Arrow keys, Escape); focus is trapped inside the\n * lightbox and returned to the originating thumbnail on close\n * - Optional captions and category filtering\n * - Lazy loading with placeholder shimmer\n * - Zoom control in lightbox\n * - Download button in lightbox\n * - Dark/light theme support\n *\n * @example\n * ```html\n * <lc-gallery\n * [items]=\"images\"\n * layout=\"grid\"\n * size=\"md\"\n * [showCaptions]=\"true\" />\n * ```\n */\n@Component({\n selector: 'lc-gallery',\n standalone: true,\n imports: [IconComponent, NgStyle, CdkTrapFocus],\n viewProviders: [ɵprovideKitIcons([ICON_PLUS, ICON_X_MARK, ICON_CHEVRON_LEFT, ICON_CHEVRON_RIGHT])],\n templateUrl: './gallery.component.html',\n styleUrls: ['./gallery.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': '\"gallery\"',\n },\n})\nexport class GalleryComponent implements OnDestroy {\n /** Array of gallery items to display */\n readonly items = input.required<GalleryItem[]>();\n\n /** Layout mode */\n readonly layout = input<GalleryLayout>('grid');\n\n /** Thumbnail size preset — controls column count (sm=4col, md=3col, lg=2col) */\n readonly size = input<GallerySize>('md');\n\n /** Custom column count (overrides size preset) */\n readonly columns = input<number | null>(null);\n\n /**\n * Whether to show captions below thumbnails. A shown caption is part of the\n * item's accessible name: `alt` (or `imageFallbackLabel` and its number),\n * then the caption (WCAG 2.5.3 Label in Name).\n * @default false\n */\n readonly showCaptions = input(false);\n\n /** Whether clicking a thumbnail opens the lightbox */\n readonly enableLightbox = input(true);\n\n /** Gap between items in pixels */\n readonly gap = input(8);\n\n /** Aspect ratio for grid items (ignored in masonry mode) */\n readonly aspectRatio = input('1 / 1');\n\n /** Label of the \"all\" category filter (shown when the items span more than one category). */\n readonly filterAllLabel = input('All');\n\n /** Accessible name of the lightbox dialog. */\n readonly lightboxAriaLabel = input('Image lightbox');\n\n /** Accessible name of the lightbox zoom-out button. */\n readonly zoomOutAriaLabel = input('Zoom out');\n\n /** Accessible name of the lightbox zoom-in button. */\n readonly zoomInAriaLabel = input('Zoom in');\n\n /** Accessible name of the lightbox reset-zoom button. */\n readonly resetZoomAriaLabel = input('Reset zoom');\n\n /** Accessible name of the lightbox download button. */\n readonly downloadAriaLabel = input('Download');\n\n /** Accessible name of the lightbox close button. */\n readonly closeAriaLabel = input('Close');\n\n /** Accessible name of the previous-image button. */\n readonly previousAriaLabel = input('Previous image');\n\n /** Accessible name of the next-image button. */\n readonly nextAriaLabel = input('Next image');\n\n /**\n * Accessible name of a thumbnail without `alt`, followed by its number. A\n * shown caption follows (`Bild 3, Sonnenuntergang am See`).\n * @default 'Image'\n */\n readonly imageFallbackLabel = input('Image');\n\n /** Emitted when lightbox opens with item index */\n readonly lightboxOpen = output<number>();\n\n /** Emitted when lightbox closes */\n readonly lightboxClose = output<void>();\n\n // ── Internal state ───────────────────────────────────────────────────\n\n private readonly injector = inject(Injector);\n private readonly closeBtn = viewChild<ElementRef<HTMLButtonElement>>('closeBtn');\n /** Element that opened the lightbox — focus returns there on close. */\n private triggerElement: HTMLElement | null = null;\n\n protected lightboxIndex = signal<number | null>(null);\n protected lightboxZoom = signal(100);\n protected loadedImages = signal<Set<number>>(new Set());\n protected activeFilter = signal<string | null>(null);\n\n // ── Computed ─────────────────────────────────────────────────────────\n\n protected gridColumns = computed(() => {\n return this.columns() ?? SIZE_COLUMNS[this.size()];\n });\n\n protected gridStyle = computed(() => {\n const cols = this.gridColumns();\n const g = this.gap();\n if (this.layout() === 'masonry') {\n return {\n 'column-count': `${cols}`,\n 'column-gap': `${g}px`,\n };\n }\n return {\n 'display': 'grid',\n 'grid-template-columns': `repeat(${cols}, 1fr)`,\n 'gap': `${g}px`,\n };\n });\n\n protected itemStyle = computed(() => {\n if (this.layout() === 'masonry') return {};\n return { 'aspect-ratio': this.aspectRatio() };\n });\n\n protected categories = computed<string[]>(() => {\n const cats = new Set<string>();\n for (const item of this.items()) {\n if (item.category) cats.add(item.category);\n }\n return Array.from(cats).sort();\n });\n\n protected filteredItems = computed(() => {\n const filter = this.activeFilter();\n if (!filter) return this.items();\n return this.items().filter(item => item.category === filter);\n });\n\n protected showFilters = computed(() => this.categories().length > 1);\n\n protected lightboxItem = computed<GalleryItem | null>(() => {\n const idx = this.lightboxIndex();\n if (idx === null) return null;\n const items = this.filteredItems();\n return items[idx] ?? null;\n });\n\n protected lightboxCounter = computed(() => {\n const idx = this.lightboxIndex();\n if (idx === null) return '';\n return `${idx + 1} / ${this.filteredItems().length}`;\n });\n\n protected lightboxTransform = computed(() => {\n const z = this.lightboxZoom() / 100;\n return `scale(${z})`;\n });\n\n protected canGoPrev = computed(() => {\n const idx = this.lightboxIndex();\n return idx !== null && idx > 0;\n });\n\n protected canGoNext = computed(() => {\n const idx = this.lightboxIndex();\n return idx !== null && idx < this.filteredItems().length - 1;\n });\n\n // ── Lifecycle ────────────────────────────────────────────────────────\n\n ngOnDestroy(): void {\n // No focus restore here: the injector is going away with the view.\n this.closeLightbox(false);\n }\n\n // ── Actions ──────────────────────────────────────────────────────────\n\n /**\n * Opens the lightbox for `index`. Like a native button, Space activates on\n * *keyup* (keydown only prevents the page scroll): activating on keydown\n * would move focus to the Close button before the key is released, and the\n * Space keyup would then immediately click Close.\n */\n protected openLightbox(index: number, event?: Event): void {\n if (!this.enableLightbox()) return;\n if (event instanceof KeyboardEvent) event.preventDefault();\n this.triggerElement =\n (event?.currentTarget as HTMLElement | null) ??\n (document.activeElement instanceof HTMLElement ? document.activeElement : null);\n this.lightboxIndex.set(index);\n this.lightboxZoom.set(100);\n this.lightboxOpen.emit(index);\n document.body.style.overflow = 'hidden';\n // The dialog is `@if`-rendered — move focus in once it exists.\n afterNextRender(() => this.closeBtn()?.nativeElement.focus(), { injector: this.injector });\n }\n\n protected closeLightbox(restoreFocus = true): void {\n if (this.lightboxIndex() === null) return;\n this.lightboxIndex.set(null);\n this.lightboxZoom.set(100);\n this.lightboxClose.emit();\n document.body.style.overflow = '';\n const trigger = this.triggerElement;\n this.triggerElement = null;\n if (restoreFocus && trigger?.isConnected) {\n afterNextRender(() => trigger.focus(), { injector: this.injector });\n }\n }\n\n protected goToPrev(): void {\n const idx = this.lightboxIndex();\n if (idx !== null && idx > 0) {\n this.lightboxIndex.set(idx - 1);\n this.lightboxZoom.set(100);\n }\n }\n\n protected goToNext(): void {\n const idx = this.lightboxIndex();\n if (idx !== null && idx < this.filteredItems().length - 1) {\n this.lightboxIndex.set(idx + 1);\n this.lightboxZoom.set(100);\n }\n }\n\n protected zoomIn(): void {\n this.lightboxZoom.update(z => Math.min(z + 25, 500));\n }\n\n protected zoomOut(): void {\n this.lightboxZoom.update(z => Math.max(z - 25, 25));\n }\n\n protected resetZoom(): void {\n this.lightboxZoom.set(100);\n }\n\n protected downloadCurrent(): void {\n const item = this.lightboxItem();\n // A programmatic click on a caller-supplied URL: only image-safe schemes.\n if (!item || !isSafeImageUrl(item.src)) return;\n const a = document.createElement('a');\n a.href = item.src;\n a.download = item.alt || 'image';\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n }\n\n protected setFilter(category: string | null): void {\n this.activeFilter.set(category);\n }\n\n protected onImageLoad(index: number): void {\n this.loadedImages.update(set => {\n const next = new Set(set);\n next.add(index);\n return next;\n });\n }\n\n protected isLoaded(index: number): boolean {\n return this.loadedImages().has(index);\n }\n\n protected getThumbnail(item: GalleryItem): string {\n return item.thumbnail || item.src;\n }\n\n /**\n * Name of a thumbnail (AS-33, WCAG 2.5.3 Label in Name): `alt` (or\n * `imageFallbackLabel` and its number), then the shown caption unless one\n * contains the other.\n */\n protected itemAriaLabel(item: GalleryItem, index: number): string {\n const label = item.alt || `${this.imageFallbackLabel()} ${index + 1}`;\n return nameWithText(label, this.showCaptions() ? (item.caption ?? '') : '');\n }\n\n protected trackByIndex(index: number): number {\n return index;\n }\n\n /**\n * Keyboard handling while the lightbox is open. Bound on the lightbox itself\n * (focus is trapped inside it) and, as a fallback, on `document`. A handled\n * Escape stops propagating so an enclosing modal/drawer doesn't close too.\n */\n @HostListener('document:keydown', ['$event'])\n protected onKeydown(event: KeyboardEvent): void {\n if (this.lightboxIndex() === null) return;\n switch (event.key) {\n case 'Escape':\n event.stopPropagation();\n this.closeLightbox();\n break;\n case 'ArrowLeft':\n event.stopPropagation();\n this.goToPrev();\n break;\n case 'ArrowRight':\n event.stopPropagation();\n this.goToNext();\n break;\n }\n }\n}\n","<!-- Filters -->\n@if (showFilters()) {\n <div class=\"gallery__filters\">\n <button\n type=\"button\"\n class=\"gallery__filter-btn\"\n [class.gallery__filter-btn--active]=\"activeFilter() === null\"\n (click)=\"setFilter(null)\"\n >\n {{ filterAllLabel() }}\n </button>\n @for (cat of categories(); track cat) {\n <button\n type=\"button\"\n class=\"gallery__filter-btn\"\n [class.gallery__filter-btn--active]=\"activeFilter() === cat\"\n (click)=\"setFilter(cat)\"\n >\n {{ cat }}\n </button>\n }\n </div>\n}\n\n<!-- Grid / Masonry -->\n<div class=\"gallery__grid\" [class.gallery__grid--masonry]=\"layout() === 'masonry'\" [ngStyle]=\"gridStyle()\">\n @for (item of filteredItems(); track item.src; let i = $index) {\n <div\n class=\"gallery__item\"\n [class.gallery__item--loaded]=\"isLoaded(i)\"\n [class.gallery__item--clickable]=\"enableLightbox()\"\n [ngStyle]=\"itemStyle()\"\n (click)=\"openLightbox(i, $event)\"\n (keydown.enter)=\"openLightbox(i, $event)\"\n (keydown.space)=\"$event.preventDefault()\"\n (keyup.space)=\"openLightbox(i, $event)\"\n [attr.tabindex]=\"enableLightbox() ? 0 : null\"\n [attr.role]=\"enableLightbox() ? 'button' : null\"\n [attr.aria-label]=\"itemAriaLabel(item, i)\"\n >\n @if (!isLoaded(i)) {\n <div class=\"gallery__placeholder\"></div>\n }\n <img\n class=\"gallery__thumb\"\n [src]=\"getThumbnail(item)\"\n [alt]=\"item.alt || ''\"\n loading=\"lazy\"\n (load)=\"onImageLoad(i)\"\n />\n @if (showCaptions() && item.caption) {\n <div class=\"gallery__caption\">{{ item.caption }}</div>\n }\n </div>\n }\n</div>\n\n<!-- Lightbox overlay -->\n@if (lightboxItem(); as item) {\n <div\n class=\"gallery__lightbox\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"lightboxAriaLabel()\"\n tabindex=\"-1\"\n cdkTrapFocus\n (keydown)=\"onKeydown($event)\"\n >\n <!-- Backdrop -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- backdrop click is a mouse shortcut; Escape and the close button close the lightbox -->\n <div class=\"gallery__lightbox-backdrop\" (click)=\"closeLightbox()\"></div>\n\n <!-- Toolbar -->\n <div class=\"gallery__lightbox-toolbar\">\n <div class=\"gallery__lightbox-counter\">{{ lightboxCounter() }}</div>\n <div class=\"gallery__lightbox-actions\">\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"zoomOutAriaLabel()\" (click)=\"zoomOut()\">\n <lc-icon name=\"minus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n <span class=\"gallery__lightbox-zoom\">{{ lightboxZoom() }}%</span>\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"zoomInAriaLabel()\" (click)=\"zoomIn()\">\n <lc-icon name=\"plus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"resetZoomAriaLabel()\" (click)=\"resetZoom()\">\n <lc-icon name=\"arrows-pointing-out\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"downloadAriaLabel()\" (click)=\"downloadCurrent()\">\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" [decorative]=\"true\" />\n </button>\n <button #closeBtn type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"closeAriaLabel()\" (click)=\"closeLightbox()\">\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n </div>\n </div>\n\n <!-- Navigation -->\n @if (canGoPrev()) {\n <button\n type=\"button\"\n class=\"gallery__lightbox-nav gallery__lightbox-nav--prev\"\n [attr.aria-label]=\"previousAriaLabel()\"\n (click)=\"goToPrev()\"\n >\n <lc-icon name=\"chevron-left\" size=\"lg\" [decorative]=\"true\" />\n </button>\n }\n @if (canGoNext()) {\n <button\n type=\"button\"\n class=\"gallery__lightbox-nav gallery__lightbox-nav--next\"\n [attr.aria-label]=\"nextAriaLabel()\"\n (click)=\"goToNext()\"\n >\n <lc-icon name=\"chevron-right\" size=\"lg\" [decorative]=\"true\" />\n </button>\n }\n\n <!-- Image -->\n <div class=\"gallery__lightbox-content\">\n <img\n class=\"gallery__lightbox-image\"\n [src]=\"item.src\"\n [alt]=\"item.alt || ''\"\n [style.transform]=\"lightboxTransform()\"\n />\n </div>\n\n <!-- Caption -->\n @if (item.caption) {\n <div class=\"gallery__lightbox-caption\">{{ item.caption }}</div>\n }\n </div>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-gallery.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/gallery/gallery.component.ts","../../../../libs/angular-ui-kit/src/lib/gallery/gallery.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n HostListener,\n Injector,\n OnDestroy,\n afterNextRender,\n computed,\n inject,\n input,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport { CdkTrapFocus } from '@angular/cdk/a11y';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_CHEVRON_LEFT, ICON_CHEVRON_RIGHT, ICON_PLUS, ICON_X_MARK } from '@lifecorner/angular-ui-kit/icons';\nimport { NgStyle } from '@angular/common';\nimport { isSafeImageUrl } from '@lifecorner/angular-ui-kit/internal/url';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\nexport type GalleryLayout = 'grid' | 'masonry';\nexport type GallerySize = 'sm' | 'md' | 'lg';\n\nexport interface GalleryItem {\n /** Image source URL */\n src: string;\n /** Thumbnail URL (falls back to src if not provided) */\n thumbnail?: string;\n /** Alt text for accessibility */\n alt?: string;\n /** Optional caption displayed below the image */\n caption?: string;\n /** Optional category for filtering */\n category?: string;\n}\n\nconst SIZE_COLUMNS: Record<GallerySize, number> = {\n sm: 4,\n md: 3,\n lg: 2,\n};\n\n/**\n * Gallery component for displaying image collections with lightbox.\n *\n * Features:\n * - Grid and masonry layout modes\n * - Responsive column count via size presets (sm, md, lg thumbnails)\n * - Configurable custom column count\n * - Lightbox overlay with navigation (prev/next)\n * - Keyboard navigation (Arrow keys, Escape); focus is trapped inside the\n * lightbox and returned to the originating thumbnail on close: the clicked\n * or tapped thumbnail, also in WebKit, where a tap doesn't focus it, so the\n * gallery needs no `returnFocusTo` (3.35)\n * - Optional captions and category filtering\n * - Lazy loading with placeholder shimmer\n * - Zoom control in lightbox\n * - Download button in lightbox\n * - Dark/light theme support\n *\n * @example\n * ```html\n * <lc-gallery\n * [items]=\"images\"\n * layout=\"grid\"\n * size=\"md\"\n * [showCaptions]=\"true\" />\n * ```\n */\n@Component({\n selector: 'lc-gallery',\n standalone: true,\n imports: [IconComponent, NgStyle, CdkTrapFocus],\n viewProviders: [ɵprovideKitIcons([ICON_PLUS, ICON_X_MARK, ICON_CHEVRON_LEFT, ICON_CHEVRON_RIGHT])],\n templateUrl: './gallery.component.html',\n styleUrls: ['./gallery.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': '\"gallery\"',\n },\n})\nexport class GalleryComponent implements OnDestroy {\n /** Array of gallery items to display */\n readonly items = input.required<GalleryItem[]>();\n\n /** Layout mode */\n readonly layout = input<GalleryLayout>('grid');\n\n /** Thumbnail size preset — controls column count (sm=4col, md=3col, lg=2col) */\n readonly size = input<GallerySize>('md');\n\n /** Custom column count (overrides size preset) */\n readonly columns = input<number | null>(null);\n\n /**\n * Whether to show captions below thumbnails. A shown caption is part of the\n * item's accessible name: `alt` (or `imageFallbackLabel` and its number),\n * then the caption (WCAG 2.5.3 Label in Name).\n * @default false\n */\n readonly showCaptions = input(false);\n\n /** Whether clicking a thumbnail opens the lightbox */\n readonly enableLightbox = input(true);\n\n /** Gap between items in pixels */\n readonly gap = input(8);\n\n /** Aspect ratio for grid items (ignored in masonry mode) */\n readonly aspectRatio = input('1 / 1');\n\n /** Label of the \"all\" category filter (shown when the items span more than one category). */\n readonly filterAllLabel = input('All');\n\n /** Accessible name of the lightbox dialog. */\n readonly lightboxAriaLabel = input('Image lightbox');\n\n /** Accessible name of the lightbox zoom-out button. */\n readonly zoomOutAriaLabel = input('Zoom out');\n\n /** Accessible name of the lightbox zoom-in button. */\n readonly zoomInAriaLabel = input('Zoom in');\n\n /** Accessible name of the lightbox reset-zoom button. */\n readonly resetZoomAriaLabel = input('Reset zoom');\n\n /** Accessible name of the lightbox download button. */\n readonly downloadAriaLabel = input('Download');\n\n /** Accessible name of the lightbox close button. */\n readonly closeAriaLabel = input('Close');\n\n /** Accessible name of the previous-image button. */\n readonly previousAriaLabel = input('Previous image');\n\n /** Accessible name of the next-image button. */\n readonly nextAriaLabel = input('Next image');\n\n /**\n * Accessible name of a thumbnail without `alt`, followed by its number. A\n * shown caption follows (`Bild 3, Sonnenuntergang am See`).\n * @default 'Image'\n */\n readonly imageFallbackLabel = input('Image');\n\n /** Emitted when lightbox opens with item index */\n readonly lightboxOpen = output<number>();\n\n /** Emitted when lightbox closes */\n readonly lightboxClose = output<void>();\n\n // ── Internal state ───────────────────────────────────────────────────\n\n private readonly injector = inject(Injector);\n private readonly closeBtn = viewChild<ElementRef<HTMLButtonElement>>('closeBtn');\n /** Element that opened the lightbox — focus returns there on close. */\n private triggerElement: HTMLElement | null = null;\n\n protected lightboxIndex = signal<number | null>(null);\n protected lightboxZoom = signal(100);\n protected loadedImages = signal<Set<number>>(new Set());\n protected activeFilter = signal<string | null>(null);\n\n // ── Computed ─────────────────────────────────────────────────────────\n\n protected gridColumns = computed(() => {\n return this.columns() ?? SIZE_COLUMNS[this.size()];\n });\n\n protected gridStyle = computed(() => {\n const cols = this.gridColumns();\n const g = this.gap();\n if (this.layout() === 'masonry') {\n return {\n 'column-count': `${cols}`,\n 'column-gap': `${g}px`,\n };\n }\n return {\n 'display': 'grid',\n 'grid-template-columns': `repeat(${cols}, 1fr)`,\n 'gap': `${g}px`,\n };\n });\n\n protected itemStyle = computed(() => {\n if (this.layout() === 'masonry') return {};\n return { 'aspect-ratio': this.aspectRatio() };\n });\n\n protected categories = computed<string[]>(() => {\n const cats = new Set<string>();\n for (const item of this.items()) {\n if (item.category) cats.add(item.category);\n }\n return Array.from(cats).sort();\n });\n\n protected filteredItems = computed(() => {\n const filter = this.activeFilter();\n if (!filter) return this.items();\n return this.items().filter(item => item.category === filter);\n });\n\n protected showFilters = computed(() => this.categories().length > 1);\n\n protected lightboxItem = computed<GalleryItem | null>(() => {\n const idx = this.lightboxIndex();\n if (idx === null) return null;\n const items = this.filteredItems();\n return items[idx] ?? null;\n });\n\n protected lightboxCounter = computed(() => {\n const idx = this.lightboxIndex();\n if (idx === null) return '';\n return `${idx + 1} / ${this.filteredItems().length}`;\n });\n\n protected lightboxTransform = computed(() => {\n const z = this.lightboxZoom() / 100;\n return `scale(${z})`;\n });\n\n protected canGoPrev = computed(() => {\n const idx = this.lightboxIndex();\n return idx !== null && idx > 0;\n });\n\n protected canGoNext = computed(() => {\n const idx = this.lightboxIndex();\n return idx !== null && idx < this.filteredItems().length - 1;\n });\n\n // ── Lifecycle ────────────────────────────────────────────────────────\n\n ngOnDestroy(): void {\n // No focus restore here: the injector is going away with the view.\n this.closeLightbox(false);\n }\n\n // ── Actions ──────────────────────────────────────────────────────────\n\n /**\n * Opens the lightbox for `index`. Like a native button, Space activates on\n * *keyup* (keydown only prevents the page scroll): activating on keydown\n * would move focus to the Close button before the key is released, and the\n * Space keyup would then immediately click Close.\n */\n protected openLightbox(index: number, event?: Event): void {\n if (!this.enableLightbox()) return;\n if (event instanceof KeyboardEvent) event.preventDefault();\n this.triggerElement =\n (event?.currentTarget as HTMLElement | null) ??\n (document.activeElement instanceof HTMLElement ? document.activeElement : null);\n this.lightboxIndex.set(index);\n this.lightboxZoom.set(100);\n this.lightboxOpen.emit(index);\n document.body.style.overflow = 'hidden';\n // The dialog is `@if`-rendered — move focus in once it exists.\n afterNextRender(() => this.closeBtn()?.nativeElement.focus(), { injector: this.injector });\n }\n\n protected closeLightbox(restoreFocus = true): void {\n if (this.lightboxIndex() === null) return;\n this.lightboxIndex.set(null);\n this.lightboxZoom.set(100);\n this.lightboxClose.emit();\n document.body.style.overflow = '';\n const trigger = this.triggerElement;\n this.triggerElement = null;\n if (restoreFocus && trigger?.isConnected) {\n afterNextRender(() => trigger.focus(), { injector: this.injector });\n }\n }\n\n protected goToPrev(): void {\n const idx = this.lightboxIndex();\n if (idx !== null && idx > 0) {\n this.lightboxIndex.set(idx - 1);\n this.lightboxZoom.set(100);\n }\n }\n\n protected goToNext(): void {\n const idx = this.lightboxIndex();\n if (idx !== null && idx < this.filteredItems().length - 1) {\n this.lightboxIndex.set(idx + 1);\n this.lightboxZoom.set(100);\n }\n }\n\n protected zoomIn(): void {\n this.lightboxZoom.update(z => Math.min(z + 25, 500));\n }\n\n protected zoomOut(): void {\n this.lightboxZoom.update(z => Math.max(z - 25, 25));\n }\n\n protected resetZoom(): void {\n this.lightboxZoom.set(100);\n }\n\n protected downloadCurrent(): void {\n const item = this.lightboxItem();\n // A programmatic click on a caller-supplied URL: only image-safe schemes.\n if (!item || !isSafeImageUrl(item.src)) return;\n const a = document.createElement('a');\n a.href = item.src;\n a.download = item.alt || 'image';\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n }\n\n protected setFilter(category: string | null): void {\n this.activeFilter.set(category);\n }\n\n protected onImageLoad(index: number): void {\n this.loadedImages.update(set => {\n const next = new Set(set);\n next.add(index);\n return next;\n });\n }\n\n protected isLoaded(index: number): boolean {\n return this.loadedImages().has(index);\n }\n\n protected getThumbnail(item: GalleryItem): string {\n return item.thumbnail || item.src;\n }\n\n /**\n * Name of a thumbnail (AS-33, WCAG 2.5.3 Label in Name): `alt` (or\n * `imageFallbackLabel` and its number), then the shown caption unless one\n * contains the other.\n */\n protected itemAriaLabel(item: GalleryItem, index: number): string {\n const label = item.alt || `${this.imageFallbackLabel()} ${index + 1}`;\n return nameWithText(label, this.showCaptions() ? (item.caption ?? '') : '');\n }\n\n protected trackByIndex(index: number): number {\n return index;\n }\n\n /**\n * Keyboard handling while the lightbox is open. Bound on the lightbox itself\n * (focus is trapped inside it) and, as a fallback, on `document`. A handled\n * Escape stops propagating so an enclosing modal/drawer doesn't close too.\n */\n @HostListener('document:keydown', ['$event'])\n protected onKeydown(event: KeyboardEvent): void {\n if (this.lightboxIndex() === null) return;\n switch (event.key) {\n case 'Escape':\n event.stopPropagation();\n this.closeLightbox();\n break;\n case 'ArrowLeft':\n event.stopPropagation();\n this.goToPrev();\n break;\n case 'ArrowRight':\n event.stopPropagation();\n this.goToNext();\n break;\n }\n }\n}\n","<!-- Filters -->\n@if (showFilters()) {\n <div class=\"gallery__filters\">\n <button\n type=\"button\"\n class=\"gallery__filter-btn\"\n [class.gallery__filter-btn--active]=\"activeFilter() === null\"\n (click)=\"setFilter(null)\"\n >\n {{ filterAllLabel() }}\n </button>\n @for (cat of categories(); track cat) {\n <button\n type=\"button\"\n class=\"gallery__filter-btn\"\n [class.gallery__filter-btn--active]=\"activeFilter() === cat\"\n (click)=\"setFilter(cat)\"\n >\n {{ cat }}\n </button>\n }\n </div>\n}\n\n<!-- Grid / Masonry -->\n<div class=\"gallery__grid\" [class.gallery__grid--masonry]=\"layout() === 'masonry'\" [ngStyle]=\"gridStyle()\">\n @for (item of filteredItems(); track item.src; let i = $index) {\n <div\n class=\"gallery__item\"\n [class.gallery__item--loaded]=\"isLoaded(i)\"\n [class.gallery__item--clickable]=\"enableLightbox()\"\n [ngStyle]=\"itemStyle()\"\n (click)=\"openLightbox(i, $event)\"\n (keydown.enter)=\"openLightbox(i, $event)\"\n (keydown.space)=\"$event.preventDefault()\"\n (keyup.space)=\"openLightbox(i, $event)\"\n [attr.tabindex]=\"enableLightbox() ? 0 : null\"\n [attr.role]=\"enableLightbox() ? 'button' : null\"\n [attr.aria-label]=\"itemAriaLabel(item, i)\"\n >\n @if (!isLoaded(i)) {\n <div class=\"gallery__placeholder\"></div>\n }\n <img\n class=\"gallery__thumb\"\n [src]=\"getThumbnail(item)\"\n [alt]=\"item.alt || ''\"\n loading=\"lazy\"\n (load)=\"onImageLoad(i)\"\n />\n @if (showCaptions() && item.caption) {\n <div class=\"gallery__caption\">{{ item.caption }}</div>\n }\n </div>\n }\n</div>\n\n<!-- Lightbox overlay -->\n@if (lightboxItem(); as item) {\n <div\n class=\"gallery__lightbox\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"lightboxAriaLabel()\"\n tabindex=\"-1\"\n cdkTrapFocus\n (keydown)=\"onKeydown($event)\"\n >\n <!-- Backdrop -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- backdrop click is a mouse shortcut; Escape and the close button close the lightbox -->\n <div class=\"gallery__lightbox-backdrop\" (click)=\"closeLightbox()\"></div>\n\n <!-- Toolbar -->\n <div class=\"gallery__lightbox-toolbar\">\n <div class=\"gallery__lightbox-counter\">{{ lightboxCounter() }}</div>\n <div class=\"gallery__lightbox-actions\">\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"zoomOutAriaLabel()\" (click)=\"zoomOut()\">\n <lc-icon name=\"minus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n <span class=\"gallery__lightbox-zoom\">{{ lightboxZoom() }}%</span>\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"zoomInAriaLabel()\" (click)=\"zoomIn()\">\n <lc-icon name=\"plus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"resetZoomAriaLabel()\" (click)=\"resetZoom()\">\n <lc-icon name=\"arrows-pointing-out\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"downloadAriaLabel()\" (click)=\"downloadCurrent()\">\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" [decorative]=\"true\" />\n </button>\n <button #closeBtn type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"closeAriaLabel()\" (click)=\"closeLightbox()\">\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n </div>\n </div>\n\n <!-- Navigation -->\n @if (canGoPrev()) {\n <button\n type=\"button\"\n class=\"gallery__lightbox-nav gallery__lightbox-nav--prev\"\n [attr.aria-label]=\"previousAriaLabel()\"\n (click)=\"goToPrev()\"\n >\n <lc-icon name=\"chevron-left\" size=\"lg\" [decorative]=\"true\" />\n </button>\n }\n @if (canGoNext()) {\n <button\n type=\"button\"\n class=\"gallery__lightbox-nav gallery__lightbox-nav--next\"\n [attr.aria-label]=\"nextAriaLabel()\"\n (click)=\"goToNext()\"\n >\n <lc-icon name=\"chevron-right\" size=\"lg\" [decorative]=\"true\" />\n </button>\n }\n\n <!-- Image -->\n <div class=\"gallery__lightbox-content\">\n <img\n class=\"gallery__lightbox-image\"\n [src]=\"item.src\"\n [alt]=\"item.alt || ''\"\n [style.transform]=\"lightboxTransform()\"\n />\n </div>\n\n <!-- Caption -->\n @if (item.caption) {\n <div class=\"gallery__lightbox-caption\">{{ item.caption }}</div>\n }\n </div>\n}\n"],"mappings":"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"}
|
|
@@ -10,7 +10,8 @@ import { ICON_BARS_3, ICON_CHEVRON_DOWN, ICON_LOGOUT, ICON_MOON, ICON_SUN, ICON_
|
|
|
10
10
|
import { AvatarComponent, ɵavatarInitials } from "@lifecorner/angular-ui-kit/avatar";
|
|
11
11
|
import { MenuComponent } from "@lifecorner/angular-ui-kit/menu";
|
|
12
12
|
import { ThemeService } from "@lifecorner/angular-ui-kit/theme";
|
|
13
|
-
import {
|
|
13
|
+
import { BadgeComponent } from "@lifecorner/angular-ui-kit/badge";
|
|
14
|
+
import { containsWords, labelWords, nameWithText } from "@lifecorner/angular-ui-kit/internal/a11y";
|
|
14
15
|
var HeaderComponent = class HeaderComponent {
|
|
15
16
|
theme = input("auto", ...ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []);
|
|
16
17
|
logo = input("", ...ngDevMode ? [{ debugName: "logo" }] : /* istanbul ignore next */ []);
|
|
@@ -40,6 +41,7 @@ var HeaderComponent = class HeaderComponent {
|
|
|
40
41
|
contextName = input("", ...ngDevMode ? [{ debugName: "contextName" }] : /* istanbul ignore next */ []);
|
|
41
42
|
contextLabel = input("", ...ngDevMode ? [{ debugName: "contextLabel" }] : /* istanbul ignore next */ []);
|
|
42
43
|
contextPlacement = input("inline", ...ngDevMode ? [{ debugName: "contextPlacement" }] : /* istanbul ignore next */ []);
|
|
44
|
+
contextBadge = input(void 0, ...ngDevMode ? [{ debugName: "contextBadge" }] : /* istanbul ignore next */ []);
|
|
43
45
|
menuSize = input("sm", ...ngDevMode ? [{ debugName: "menuSize" }] : /* istanbul ignore next */ []);
|
|
44
46
|
showProfileMenuItem = input(true, ...ngDevMode ? [{ debugName: "showProfileMenuItem" }] : /* istanbul ignore next */ []);
|
|
45
47
|
showUserMenu = input(true, ...ngDevMode ? [{ debugName: "showUserMenu" }] : /* istanbul ignore next */ []);
|
|
@@ -61,6 +63,7 @@ var HeaderComponent = class HeaderComponent {
|
|
|
61
63
|
profileClick = output();
|
|
62
64
|
contextClick = output();
|
|
63
65
|
injector = inject(Injector);
|
|
66
|
+
hostElement = inject(ElementRef);
|
|
64
67
|
themeServiceInstance = null;
|
|
65
68
|
get themeService() {
|
|
66
69
|
return this.themeServiceInstance ??= this.injector.get(ThemeService);
|
|
@@ -91,7 +94,12 @@ var HeaderComponent = class HeaderComponent {
|
|
|
91
94
|
if (label.trim() === "") return initials;
|
|
92
95
|
return `${label}, ${initials}`;
|
|
93
96
|
}, ...ngDevMode ? [{ debugName: "avatarTriggerAriaLabel" }] : /* istanbul ignore next */ []);
|
|
94
|
-
contextButtonAriaLabel = computed(() =>
|
|
97
|
+
contextButtonAriaLabel = computed(() => {
|
|
98
|
+
const name = nameWithText(this.contextAriaLabel(), [this.contextLabel(), this.contextName()].filter(Boolean).join(" "));
|
|
99
|
+
const badge = this.contextBadge()?.label.replace(/\s+/g, " ").trim() ?? "";
|
|
100
|
+
if (!badge || containsWords(labelWords(name), labelWords(badge))) return name;
|
|
101
|
+
return name ? `${name}, ${badge}` : badge;
|
|
102
|
+
}, ...ngDevMode ? [{ debugName: "contextButtonAriaLabel" }] : /* istanbul ignore next */ []);
|
|
95
103
|
contextBelow = computed(() => this.contextPlacement() === "below" && !!this.contextName(), ...ngDevMode ? [{ debugName: "contextBelow" }] : /* istanbul ignore next */ []);
|
|
96
104
|
menuItems = computed(() => {
|
|
97
105
|
const items = [];
|
|
@@ -127,7 +135,8 @@ var HeaderComponent = class HeaderComponent {
|
|
|
127
135
|
}, { injector: this.injector });
|
|
128
136
|
}
|
|
129
137
|
onHamburgerClick() {
|
|
130
|
-
this.
|
|
138
|
+
const host = this.hamburger()?.nativeElement;
|
|
139
|
+
this.hamburgerClick.emit(host?.querySelector("button") ?? host ?? this.hostElement.nativeElement);
|
|
131
140
|
}
|
|
132
141
|
onThemeButtonClick() {
|
|
133
142
|
if (this.autoToggleTheme()) this.themeService.toggleTheme();
|
|
@@ -356,6 +365,13 @@ var HeaderComponent = class HeaderComponent {
|
|
|
356
365
|
isRequired: false,
|
|
357
366
|
transformFunction: null
|
|
358
367
|
},
|
|
368
|
+
contextBadge: {
|
|
369
|
+
classPropertyName: "contextBadge",
|
|
370
|
+
publicName: "contextBadge",
|
|
371
|
+
isSignal: true,
|
|
372
|
+
isRequired: false,
|
|
373
|
+
transformFunction: null
|
|
374
|
+
},
|
|
359
375
|
menuSize: {
|
|
360
376
|
classPropertyName: "menuSize",
|
|
361
377
|
publicName: "menuSize",
|
|
@@ -485,8 +501,8 @@ var HeaderComponent = class HeaderComponent {
|
|
|
485
501
|
isSignal: true
|
|
486
502
|
}],
|
|
487
503
|
ngImport: i0,
|
|
488
|
-
template: "<header\n class=\"lc-header\"\n [class.lc-header--dark]=\"theme() === 'dark'\"\n [class.lc-header--light]=\"theme() === 'light'\"\n [class.lc-header--size-sm]=\"size() === 'sm'\"\n [class.lc-header--size-md]=\"size() === 'md'\"\n [class.lc-header--size-lg]=\"size() === 'lg'\"\n [class.lc-header--size-xl]=\"size() === 'xl'\"\n [class.lc-header--has-nav]=\"navItems().length > 0\"\n [class.lc-header--static]=\"!sticky()\"\n [class.lc-header--transparent]=\"variant() === 'transparent'\"\n [class.lc-header--context-below]=\"contextBelow()\"\n>\n <!-- Hamburger menu icon (mobile only) -->\n @if (showHamburger()) {\n <lc-button\n #hamburger\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"hamburgerAriaLabel()\"\n (clicked)=\"onHamburgerClick()\"\n class=\"lc-header__hamburger\"\n >\n <lc-icon name=\"bars-3\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Logo (clickable, navigates to home) -->\n <div class=\"lc-header__brand\">\n @if (showLogo()) {\n @if (homeHref()) {\n <!-- Plain link: works without the router and without JavaScript. -->\n <a [href]=\"homeHref()\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n } @else {\n <a routerLink=\"/\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n }\n }\n\n @if (title()) {\n <div class=\"lc-header__title-group\">\n @switch (titleTag()) {\n @case ('h1') {\n <h1 class=\"lc-header__title\">{{ title() }}</h1>\n }\n @case ('h2') {\n <h2 class=\"lc-header__title\">{{ title() }}</h2>\n }\n @case ('h3') {\n <h3 class=\"lc-header__title\">{{ title() }}</h3>\n }\n @case ('p') {\n <p class=\"lc-header__title\">{{ title() }}</p>\n }\n @case ('div') {\n <div class=\"lc-header__title\">{{ title() }}</div>\n }\n @default {\n <span class=\"lc-header__title\">{{ title() }}</span>\n }\n }\n @if (subtitle()) {\n <span class=\"lc-header__subtitle\" [attr.title]=\"subtitle()\">{{ subtitle() }}</span>\n }\n </div>\n }\n </div>\n\n <!-- Main navigation: inline from 1024 px, its own wrapping row below. -->\n @if (navItems().length > 0) {\n <nav class=\"lc-header__nav\" [attr.aria-label]=\"navAriaLabel()\">\n <ul class=\"lc-header__nav-list\">\n @for (item of navItems(); track $index) {\n <li class=\"lc-header__nav-item\">\n <a\n class=\"lc-header__nav-link\"\n [href]=\"item.href\"\n [attr.aria-current]=\"item.current ? 'page' : null\"\n >{{ item.label }}</a>\n </li>\n }\n </ul>\n </nav>\n }\n\n <!-- Zero-width spacer on the brand's row: keeps 3.10's two gaps before the end\n group and the first row's height; grows in a has-nav header from 1024 px. -->\n <div class=\"lc-header__spacer\"></div>\n\n <!-- End of the bar: the end slot (e.g. a language link), theme toggle, context\n info and account menu as one group. It goes to the end of a second row\n when it doesn't fit beside the brand (AS-31). No role; the DOM order and\n so the focus order stay brand → nav → end. -->\n <div class=\"lc-header__end\">\n <ng-content select=\"[slot='end']\" />\n\n <!-- Theme toggle button -->\n @if (showThemeButton()) {\n <lc-button\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"themeService.isDark() ? themeToLightAriaLabel() : themeToDarkAriaLabel()\"\n (clicked)=\"onThemeButtonClick()\"\n class=\"lc-header__theme-toggle\"\n >\n <lc-icon [name]=\"themeService.isDark() ? 'sun' : 'moon'\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Context info (e.g. tenant, organization, project) -->\n @if (contextName()) {\n <button\n #contextButton\n type=\"button\"\n class=\"lc-header__context-info\"\n [attr.aria-label]=\"contextButtonAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n <!-- A real space between label and name: the visible text reads\n \"Tenant Acme Corp\", not \"TenantAcme Corp\" (WCAG 2.5.3). The\n tooltip sits on the name, so it is not a description of the button. -->\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>&ngsp;\n }\n <span class=\"lc-header__context-name\" [attr.title]=\"contextName()\">{{ contextName() }}</span>\n </button>\n }\n\n <!-- User profile menu -->\n @if (showUserMenu()) {\n <lc-menu\n [items]=\"menuItems()\"\n [isOpen]=\"isDropdownOpen()\"\n position=\"bottom-right\"\n minWidth=\"min(220px, calc(100vw - 2rem))\"\n maxWidth=\"min(18rem, calc(100vw - 2rem))\"\n [size]=\"menuSize()\"\n (itemClick)=\"onMenuItemClick($event)\"\n (closed)=\"closeDropdown()\"\n >\n <!-- Menu header with avatar, name and email -->\n @if (userName() || userEmail()) {\n <div header class=\"lc-header__menu-header\">\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n [fallback]=\"userAvatarFallback()\"\n size=\"md\"\n />\n <div class=\"lc-header__menu-user-info\">\n @if (userName()) {\n <div class=\"lc-header__menu-user-name\">{{ userName() }}</div>\n }\n @if (userEmail()) {\n <div class=\"lc-header__menu-user-email\">{{ userEmail() }}</div>\n }\n </div>\n </div>\n }\n\n @if (userMenuTrigger() === 'avatar') {\n <button\n trigger\n class=\"lc-header__profile-trigger lc-header__profile-trigger--avatar\"\n type=\"button\"\n [attr.aria-label]=\"avatarTriggerAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n [fallback]=\"userAvatarFallback()\"\n [decorative]=\"true\"\n size=\"md\"\n />\n </button>\n } @else {\n <button\n trigger\n class=\"lc-header__profile-trigger\"\n type=\"button\"\n [attr.aria-label]=\"userMenuAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-icon name=\"user\" size=\"sm\" [decorative]=\"true\" />\n <lc-icon name=\"chevron-down\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </lc-menu>\n }\n </div>\n</header>\n\n<ng-template #brandLogo>\n <lc-logo\n [variant]=\"logo() ? 'full' : 'emblem'\"\n [size]=\"logoSize()\"\n [clickable]=\"false\"\n [colorMode]=\"theme() === 'dark' ? 'dark' : theme() === 'light' ? 'light' : 'auto'\"\n [src]=\"logoSrc()\"\n [emblemSrc]=\"logoEmblemSrc()\"\n [darkSrc]=\"logoDarkSrc()\"\n [darkEmblemSrc]=\"logoDarkEmblemSrc()\"\n [alt]=\"logoAlt() || title() || 'Logo'\"\n />\n</ng-template>\n",
|
|
489
|
-
styles: [".lc-header{--lc-header-bg: var(--color-surface);--lc-header-fg: var(--color-text-primary);--lc-header-fg-secondary: var(--color-text-secondary);--lc-header-border: var(--color-border);--lc-header-hover-bg: var(--color-surface-hover);--lc-header-trigger-border: var(--color-border);--lc-header-trigger-fg: var(--color-text-secondary);--lc-header-padding-x: var(--lc-density-padding-lg, var(--spacing-6));display:flex;align-items:center;gap:var(--lc-density-gap-md, var(--spacing-4));flex-wrap:wrap;row-gap:0;padding:0 var(--lc-header-padding-x);background-color:var(--lc-header-bg);border-bottom:1px solid var(--lc-header-border);min-height:64px;position:sticky;top:0;z-index:1000}.lc-header.lc-header--size-sm{min-height:56px;--lc-header-row-height: 56px}.lc-header.lc-header--size-md{min-height:64px;--lc-header-row-height: 64px}.lc-header.lc-header--size-lg{min-height:80px;--lc-header-row-height: 80px}.lc-header.lc-header--size-xl{min-height:112px;--lc-header-row-height: 112px}@media(max-width:768px){.lc-header{--lc-header-padding-x: var(--lc-density-padding-md, var(--spacing-4))}}.lc-header--static{position:relative}.lc-header__nav{display:flex;align-items:center;min-width:0}.lc-header__nav-list{display:flex;flex-wrap:wrap;align-items:center;gap:0 var(--lc-header-nav-gap, var(--spacing-1));margin:0;padding:0;list-style:none}.lc-header__nav-item{display:flex}.lc-header__nav-link{display:inline-flex;align-items:center;min-height:var(--size-min-touch-height, 2.75rem);padding:0 var(--spacing-3);border-radius:var(--border-radius-md);color:var(--lc-header-fg-secondary);font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);line-height:var(--line-height-tight);text-decoration:none;transition:color .15s ease,background-color .15s ease}.lc-header__nav-link:hover{color:var(--lc-header-fg);background-color:var(--lc-header-hover-bg)}.lc-header__nav-link:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}.lc-header__nav-link[aria-current=page]{color:var(--lc-header-fg);text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:.45em}@media(max-width:1023.98px){.lc-header--has-nav .lc-header__nav{order:1;flex-basis:100%;margin-left:calc(-1 * var(--spacing-3))}}@media(min-width:1024px){.lc-header--has-nav .lc-header__brand{flex:0 1 auto;width:auto;max-width:none}.lc-header--has-nav .lc-header__title-group{flex:0 1 auto}.lc-header--has-nav .lc-header__title{margin-inline-end:0}.lc-header--has-nav .lc-header__subtitle{contain:none}.lc-header--has-nav .lc-header__spacer{flex:1}}.lc-header--dark{--lc-header-bg: #070e15;--lc-header-fg: #f1f6f8;--lc-header-fg-secondary: #c4d2d9;--lc-header-border: rgba(255, 255, 255, .08);--lc-header-hover-bg: rgba(132, 199, 212, .06);--lc-header-trigger-border: rgba(255, 255, 255, .08);--lc-header-trigger-fg: #c4d2d9}.lc-header--light{--lc-header-bg: #ffffff;--lc-header-fg: #111827;--lc-header-fg-secondary: #4b5563;--lc-header-border: #e5e7eb;--lc-header-hover-bg: #f3f4f6;--lc-header-trigger-border: #e5e7eb;--lc-header-trigger-fg: #374151;--_lc-header-ring: #208497}.lc-header__hamburger{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:24px;margin-left:calc(var(--lc-header-hamburger-slot, 56px) / 2 - var(--lc-header-padding-x) - 12px);margin-right:calc(var(--lc-header-hamburger-gap, .5rem) - var(--lc-density-gap-md, var(--spacing-4)));color:var(--lc-header-trigger-fg)}.lc-header__hamburger .lc-icon{width:24px;height:24px}.lc-header__brand{display:flex;flex:1 1 auto;align-items:center;gap:var(--spacing-3);width:min-content;max-width:calc(100% - var(--lc-density-gap-md, var(--spacing-4)));min-width:0}.lc-header--has-nav .lc-header__brand{min-height:var(--lc-header-row-height, 64px)}.lc-header__logo{display:flex;align-items:center;text-decoration:none;color:var(--lc-header-fg);flex-shrink:0}.lc-header__logo:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:4px;border-radius:var(--border-radius-sm)}.lc-header__logo-img{height:32px;width:auto}.lc-header__title-group{display:flex;flex:1 1 auto;flex-direction:column;gap:.125rem;min-width:0}.lc-header__title{font-size:1.25rem;font-weight:700;color:var(--lc-header-fg);margin:0;margin-inline-end:calc(-1 * var(--lc-density-gap-md, var(--spacing-4)));line-height:1.2;overflow-wrap:break-word;transition:color .2s ease}@media(max-width:640px){.lc-header__title{font-size:1.125rem}}.lc-header__subtitle{display:block;contain:inline-size;max-width:var(--lc-header-subtitle-max-width, 100%);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:.8125rem;line-height:1.3;font-weight:500;color:var(--lc-header-fg-secondary);text-transform:var(--lc-header-subtitle-transform, none);transition:color .2s ease}@media(min-width:1024px){.lc-header__subtitle{font-size:var(--font-size-sm, .875rem)}}.lc-header__spacer{flex:0 0 0;min-height:calc(var(--lc-header-row-height, 64px) - var(--_lc-header-border-width, 1px))}.lc-header__end{display:flex;flex-wrap:wrap;align-items:center;align-self:stretch;justify-content:flex-end;gap:0 var(--lc-density-gap-md, var(--spacing-4));margin-inline-start:auto;min-width:0;max-width:100%}.lc-header__end:empty{display:none}.lc-header__theme-toggle{color:var(--lc-header-trigger-fg)}.lc-header__context-info{display:flex;flex-direction:column;align-items:flex-end;gap:.0625rem;padding:var(--spacing-2) var(--spacing-3);background:transparent;border:none;cursor:pointer;border-radius:var(--border-radius-md);color:var(--lc-header-trigger-fg);transition:background-color .2s ease,color .2s ease;max-width:220px;min-width:0}.lc-header__context-info:hover{background-color:var(--lc-header-hover-bg)}.lc-header__context-info:hover .lc-header__context-name{color:var(--lc-header-fg)}.lc-header__context-info:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}@media(max-width:640px){.lc-header__context-info{display:none}}@media(max-width:640px){.lc-header--context-below{justify-content:flex-end}.lc-header--context-below .lc-header__end{display:contents}.lc-header--context-below .lc-header__context-info{display:flex;order:2;flex:1 0 calc(100% + 2 * var(--spacing-3));margin:0 calc(-1 * var(--spacing-3)) var(--spacing-2);max-width:none;min-height:var(--size-min-touch-height, 2.75rem);align-items:flex-start;justify-content:center;text-align:start}}.lc-header__context-label{font-size:var(--font-size-xs);font-weight:500;color:var(--lc-header-fg-secondary);text-transform:uppercase;letter-spacing:.05em;line-height:1.2}.lc-header__context-name{font-size:var(--font-size-sm);font-weight:600;color:var(--lc-header-fg);line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.lc-header--dark .lc-header__theme-toggle lc-icon,.lc-header--dark .lc-header__theme-toggle .lc-icon,.lc-header--dark .lc-header__theme-toggle svg,.lc-header--dark .lc-header__hamburger lc-icon,.lc-header--dark .lc-header__hamburger .lc-icon,.lc-header--dark .lc-header__hamburger svg,.lc-header--dark .lc-header__profile-trigger lc-icon,.lc-header--dark .lc-header__profile-trigger .lc-icon,.lc-header--dark .lc-header__profile-trigger svg,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg{color:var(--lc-header-trigger-fg)}.lc-header--dark lc-button,html.dark .lc-header:not(.lc-header--light) lc-button,:root.dark .lc-header:not(.lc-header--light) lc-button,.dark .lc-header:not(.lc-header--light) lc-button{--lc-button-ghost-fg: var(--lc-header-trigger-fg);--lc-button-ghost-hover-bg: var(--lc-header-hover-bg);--lc-button-ghost-hover-fg: var(--lc-header-fg);--lc-button-ghost-active-bg: var(--lc-header-hover-bg)}.lc-header__profile-trigger{display:flex;align-items:center;gap:var(--lc-density-gap-sm, var(--spacing-2));min-height:var(--size-min-touch-height, 2.75rem);box-sizing:border-box;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--spacing-3);background:transparent;border:1px solid var(--lc-header-trigger-border);border-radius:var(--border-radius-full);cursor:pointer;color:var(--lc-header-trigger-fg);transition:background-color .2s ease,border-color .2s ease}.lc-header__profile-trigger:hover{background-color:var(--lc-header-hover-bg);border-color:var(--lc-header-fg-secondary)}.lc-header__profile-trigger:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}.lc-header__profile-trigger--avatar{justify-content:center;width:3rem;height:3rem;min-height:3rem;padding:0;border:0;flex-shrink:0}.lc-header__profile-trigger--avatar:hover{border-color:transparent}.lc-header.lc-header--transparent{--lc-header-bg: transparent;--lc-header-border: transparent;--_lc-header-border-width: 0px;border-bottom-width:0}.lc-menu__header{padding:var(--spacing-3) var(--spacing-4);font-size:var(--font-size-sm);color:var(--color-text-secondary, #6b7280);border-bottom:1px solid var(--color-divider, #e5e7eb);margin-bottom:var(--spacing-1);word-break:break-word}.lc-header__menu-header{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4));border-bottom:1px solid var(--color-divider, #e5e7eb);margin-bottom:var(--spacing-1)}.lc-header__menu-user-info{flex:1;min-width:0}.lc-header__menu-user-name{font-size:var(--font-size-sm);font-weight:600;color:var(--color-text-primary);overflow-wrap:break-word;line-height:1.4}.lc-header__menu-user-email{font-size:var(--font-size-xs);color:var(--color-text-secondary, #6b7280);overflow-wrap:anywhere;line-height:1.4}\n"],
|
|
504
|
+
template: "<header\n class=\"lc-header\"\n [class.lc-header--dark]=\"theme() === 'dark'\"\n [class.lc-header--light]=\"theme() === 'light'\"\n [class.lc-header--size-sm]=\"size() === 'sm'\"\n [class.lc-header--size-md]=\"size() === 'md'\"\n [class.lc-header--size-lg]=\"size() === 'lg'\"\n [class.lc-header--size-xl]=\"size() === 'xl'\"\n [class.lc-header--has-nav]=\"navItems().length > 0\"\n [class.lc-header--static]=\"!sticky()\"\n [class.lc-header--transparent]=\"variant() === 'transparent'\"\n [class.lc-header--context-below]=\"contextBelow()\"\n>\n <!-- Hamburger menu icon (mobile only) -->\n @if (showHamburger()) {\n <lc-button\n #hamburger\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"hamburgerAriaLabel()\"\n (clicked)=\"onHamburgerClick()\"\n class=\"lc-header__hamburger\"\n >\n <lc-icon name=\"bars-3\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Logo (clickable, navigates to home) -->\n <div class=\"lc-header__brand\">\n @if (showLogo()) {\n @if (homeHref()) {\n <!-- Plain link: works without the router and without JavaScript. -->\n <a [href]=\"homeHref()\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n } @else {\n <a routerLink=\"/\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n }\n }\n\n @if (title()) {\n <div class=\"lc-header__title-group\">\n @switch (titleTag()) {\n @case ('h1') {\n <h1 class=\"lc-header__title\">{{ title() }}</h1>\n }\n @case ('h2') {\n <h2 class=\"lc-header__title\">{{ title() }}</h2>\n }\n @case ('h3') {\n <h3 class=\"lc-header__title\">{{ title() }}</h3>\n }\n @case ('p') {\n <p class=\"lc-header__title\">{{ title() }}</p>\n }\n @case ('div') {\n <div class=\"lc-header__title\">{{ title() }}</div>\n }\n @default {\n <span class=\"lc-header__title\">{{ title() }}</span>\n }\n }\n @if (subtitle()) {\n <span class=\"lc-header__subtitle\" [attr.title]=\"subtitle()\">{{ subtitle() }}</span>\n }\n </div>\n }\n </div>\n\n <!-- Main navigation: inline from 1024 px, its own wrapping row below. -->\n @if (navItems().length > 0) {\n <nav class=\"lc-header__nav\" [attr.aria-label]=\"navAriaLabel()\">\n <ul class=\"lc-header__nav-list\">\n @for (item of navItems(); track $index) {\n <li class=\"lc-header__nav-item\">\n <a\n class=\"lc-header__nav-link\"\n [href]=\"item.href\"\n [attr.aria-current]=\"item.current ? 'page' : null\"\n >{{ item.label }}</a>\n </li>\n }\n </ul>\n </nav>\n }\n\n <!-- Zero-width spacer on the brand's row: keeps 3.10's two gaps before the end\n group and the first row's height; grows in a has-nav header from 1024 px. -->\n <div class=\"lc-header__spacer\"></div>\n\n <!-- End of the bar: the end slot (e.g. a language link), theme toggle, context\n info and account menu as one group. It goes to the end of a second row\n when it doesn't fit beside the brand (AS-31). No role; the DOM order and\n so the focus order stay brand → nav → end. -->\n <div class=\"lc-header__end\">\n <ng-content select=\"[slot='end']\" />\n\n <!-- Theme toggle button -->\n @if (showThemeButton()) {\n <lc-button\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"themeService.isDark() ? themeToLightAriaLabel() : themeToDarkAriaLabel()\"\n (clicked)=\"onThemeButtonClick()\"\n class=\"lc-header__theme-toggle\"\n >\n <lc-icon [name]=\"themeService.isDark() ? 'sun' : 'moon'\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Context info (e.g. tenant, organization, project) -->\n @if (contextName()) {\n <button\n #contextButton\n type=\"button\"\n class=\"lc-header__context-info\"\n [class.lc-header__context-info--badge]=\"!!contextBadge()\"\n [attr.aria-label]=\"contextButtonAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n <!-- A real space between label and name: the visible text reads\n \"Tenant Acme Corp\", not \"TenantAcme Corp\" (WCAG 2.5.3). The\n tooltip sits on the name, so it is not a description of the button. -->\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>&ngsp;\n }\n @if (contextBadge(); as badge) {\n <!-- 3.36: name and state badge on one row; the name keeps its ellipsis. -->\n <span class=\"lc-header__context-row\">\n <span class=\"lc-header__context-name\" [attr.title]=\"contextName()\">{{ contextName() }}</span>&ngsp;\n <lc-badge class=\"lc-header__context-badge\" size=\"sm\" [rounded]=\"true\" [variant]=\"badge.variant ?? 'default'\">{{ badge.label }}</lc-badge>\n </span>\n } @else {\n <span class=\"lc-header__context-name\" [attr.title]=\"contextName()\">{{ contextName() }}</span>\n }\n </button>\n }\n\n <!-- User profile menu -->\n @if (showUserMenu()) {\n <lc-menu\n [items]=\"menuItems()\"\n [isOpen]=\"isDropdownOpen()\"\n position=\"bottom-right\"\n minWidth=\"min(220px, calc(100vw - 2rem))\"\n maxWidth=\"min(18rem, calc(100vw - 2rem))\"\n [size]=\"menuSize()\"\n (itemClick)=\"onMenuItemClick($event)\"\n (closed)=\"closeDropdown()\"\n >\n <!-- Menu header with avatar, name and email -->\n @if (userName() || userEmail()) {\n <div header class=\"lc-header__menu-header\">\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n [fallback]=\"userAvatarFallback()\"\n size=\"md\"\n />\n <div class=\"lc-header__menu-user-info\">\n @if (userName()) {\n <div class=\"lc-header__menu-user-name\">{{ userName() }}</div>\n }\n @if (userEmail()) {\n <div class=\"lc-header__menu-user-email\">{{ userEmail() }}</div>\n }\n </div>\n </div>\n }\n\n @if (userMenuTrigger() === 'avatar') {\n <button\n trigger\n class=\"lc-header__profile-trigger lc-header__profile-trigger--avatar\"\n type=\"button\"\n [attr.aria-label]=\"avatarTriggerAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n [fallback]=\"userAvatarFallback()\"\n [decorative]=\"true\"\n size=\"md\"\n />\n </button>\n } @else {\n <button\n trigger\n class=\"lc-header__profile-trigger\"\n type=\"button\"\n [attr.aria-label]=\"userMenuAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-icon name=\"user\" size=\"sm\" [decorative]=\"true\" />\n <lc-icon name=\"chevron-down\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </lc-menu>\n }\n </div>\n</header>\n\n<ng-template #brandLogo>\n <lc-logo\n [variant]=\"logo() ? 'full' : 'emblem'\"\n [size]=\"logoSize()\"\n [clickable]=\"false\"\n [colorMode]=\"theme() === 'dark' ? 'dark' : theme() === 'light' ? 'light' : 'auto'\"\n [src]=\"logoSrc()\"\n [emblemSrc]=\"logoEmblemSrc()\"\n [darkSrc]=\"logoDarkSrc()\"\n [darkEmblemSrc]=\"logoDarkEmblemSrc()\"\n [alt]=\"logoAlt() || title() || 'Logo'\"\n />\n</ng-template>\n",
|
|
505
|
+
styles: [".lc-header{--lc-header-bg: var(--color-surface);--lc-header-fg: var(--color-text-primary);--lc-header-fg-secondary: var(--color-text-secondary);--lc-header-border: var(--color-border);--lc-header-hover-bg: var(--color-surface-hover);--lc-header-trigger-border: var(--color-border);--lc-header-trigger-fg: var(--color-text-secondary);--lc-header-padding-x: var(--lc-density-padding-lg, var(--spacing-6));display:flex;align-items:center;gap:var(--lc-density-gap-md, var(--spacing-4));flex-wrap:wrap;row-gap:0;padding:0 var(--lc-header-padding-x);background-color:var(--lc-header-bg);border-bottom:1px solid var(--lc-header-border);min-height:64px;position:sticky;top:0;z-index:1000}.lc-header.lc-header--size-sm{min-height:56px;--lc-header-row-height: 56px}.lc-header.lc-header--size-md{min-height:64px;--lc-header-row-height: 64px}.lc-header.lc-header--size-lg{min-height:80px;--lc-header-row-height: 80px}.lc-header.lc-header--size-xl{min-height:112px;--lc-header-row-height: 112px}@media(max-width:768px){.lc-header{--lc-header-padding-x: var(--lc-density-padding-md, var(--spacing-4))}}.lc-header--static{position:relative}.lc-header__nav{display:flex;align-items:center;min-width:0}.lc-header__nav-list{display:flex;flex-wrap:wrap;align-items:center;gap:0 var(--lc-header-nav-gap, var(--spacing-1));margin:0;padding:0;list-style:none}.lc-header__nav-item{display:flex}.lc-header__nav-link{display:inline-flex;align-items:center;min-height:var(--size-min-touch-height, 2.75rem);padding:0 var(--spacing-3);border-radius:var(--border-radius-md);color:var(--lc-header-fg-secondary);font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);line-height:var(--line-height-tight);text-decoration:none;transition:color .15s ease,background-color .15s ease}.lc-header__nav-link:hover{color:var(--lc-header-fg);background-color:var(--lc-header-hover-bg)}.lc-header__nav-link:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}.lc-header__nav-link[aria-current=page]{color:var(--lc-header-fg);text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:.45em}@media(max-width:1023.98px){.lc-header--has-nav .lc-header__nav{order:1;flex-basis:100%;margin-left:calc(-1 * var(--spacing-3))}}@media(min-width:1024px){.lc-header--has-nav .lc-header__brand{flex:0 1 auto;width:auto;max-width:none}.lc-header--has-nav .lc-header__title-group{flex:0 1 auto}.lc-header--has-nav .lc-header__title{margin-inline-end:0}.lc-header--has-nav .lc-header__subtitle{contain:none}.lc-header--has-nav .lc-header__spacer{flex:1}}.lc-header--dark{--lc-header-bg: #070e15;--lc-header-fg: #f1f6f8;--lc-header-fg-secondary: #c4d2d9;--lc-header-border: rgba(255, 255, 255, .08);--lc-header-hover-bg: rgba(132, 199, 212, .06);--lc-header-trigger-border: rgba(255, 255, 255, .08);--lc-header-trigger-fg: #c4d2d9}.lc-header--light{--lc-header-bg: #ffffff;--lc-header-fg: #111827;--lc-header-fg-secondary: #4b5563;--lc-header-border: #e5e7eb;--lc-header-hover-bg: #f3f4f6;--lc-header-trigger-border: #e5e7eb;--lc-header-trigger-fg: #374151;--_lc-header-ring: #208497}.lc-header__hamburger{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:24px;margin-left:calc(var(--lc-header-hamburger-slot, 56px) / 2 - var(--lc-header-padding-x) - 12px);margin-right:calc(var(--lc-header-hamburger-gap, .5rem) - var(--lc-density-gap-md, var(--spacing-4)));color:var(--lc-header-trigger-fg)}.lc-header__hamburger .lc-icon{width:24px;height:24px}.lc-header__brand{display:flex;flex:1 1 auto;align-items:center;gap:var(--spacing-3);width:min-content;max-width:calc(100% - var(--lc-density-gap-md, var(--spacing-4)));min-width:0}.lc-header--has-nav .lc-header__brand{min-height:var(--lc-header-row-height, 64px)}.lc-header__logo{display:flex;align-items:center;text-decoration:none;color:var(--lc-header-fg);flex-shrink:0}.lc-header__logo:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:4px;border-radius:var(--border-radius-sm)}.lc-header__logo-img{height:32px;width:auto}.lc-header__title-group{display:flex;flex:1 1 auto;flex-direction:column;gap:.125rem;min-width:0}.lc-header__title{font-size:1.25rem;font-weight:700;color:var(--lc-header-fg);margin:0;margin-inline-end:calc(-1 * var(--lc-density-gap-md, var(--spacing-4)));line-height:1.2;overflow-wrap:break-word;transition:color .2s ease}@media(max-width:640px){.lc-header__title{font-size:1.125rem}}.lc-header__subtitle{display:block;contain:inline-size;max-width:var(--lc-header-subtitle-max-width, 100%);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:.8125rem;line-height:1.3;font-weight:500;color:var(--lc-header-fg-secondary);text-transform:var(--lc-header-subtitle-transform, none);transition:color .2s ease}@media(min-width:1024px){.lc-header__subtitle{font-size:var(--font-size-sm, .875rem)}}.lc-header__spacer{flex:0 0 0;min-height:calc(var(--lc-header-row-height, 64px) - var(--_lc-header-border-width, 1px))}.lc-header__end{display:flex;flex-wrap:wrap;align-items:center;align-self:stretch;justify-content:flex-end;gap:0 var(--lc-density-gap-md, var(--spacing-4));margin-inline-start:auto;min-width:0;max-width:100%}.lc-header__end:empty{display:none}.lc-header__theme-toggle{color:var(--lc-header-trigger-fg)}.lc-header__context-info{display:flex;flex-direction:column;align-items:flex-end;gap:.0625rem;padding:var(--spacing-2) var(--spacing-3);background:transparent;border:none;cursor:pointer;border-radius:var(--border-radius-md);color:var(--lc-header-trigger-fg);transition:background-color .2s ease,color .2s ease;max-width:220px;min-width:0}.lc-header__context-info:hover{background-color:var(--lc-header-hover-bg)}.lc-header__context-info:hover .lc-header__context-name{color:var(--lc-header-fg)}.lc-header__context-info:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}@media(max-width:640px){.lc-header__context-info{display:none}}.lc-header__context-info--badge{max-width:340px}.lc-header__context-row{display:flex;align-items:center;gap:var(--spacing-1-5);min-width:0;max-width:100%}.lc-header__context-row .lc-header__context-name{flex:0 1 auto;min-width:0}.lc-header__context-badge{display:inline-flex;flex-shrink:0;margin-block:calc(.65 * var(--font-size-sm) - .75rem)}@media(max-width:640px){.lc-header--context-below{justify-content:flex-end}.lc-header--context-below .lc-header__end{display:contents}.lc-header--context-below .lc-header__context-info{display:flex;order:2;flex:1 0 calc(100% + 2 * var(--spacing-3));margin:0 calc(-1 * var(--spacing-3)) var(--spacing-2);max-width:none;min-height:var(--size-min-touch-height, 2.75rem);align-items:flex-start;justify-content:center;text-align:start}}.lc-header__context-label{font-size:var(--font-size-xs);font-weight:500;color:var(--lc-header-fg-secondary);text-transform:uppercase;letter-spacing:.05em;line-height:1.2}.lc-header__context-name{font-size:var(--font-size-sm);font-weight:600;color:var(--lc-header-fg);line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.lc-header--dark .lc-header__theme-toggle lc-icon,.lc-header--dark .lc-header__theme-toggle .lc-icon,.lc-header--dark .lc-header__theme-toggle svg,.lc-header--dark .lc-header__hamburger lc-icon,.lc-header--dark .lc-header__hamburger .lc-icon,.lc-header--dark .lc-header__hamburger svg,.lc-header--dark .lc-header__profile-trigger lc-icon,.lc-header--dark .lc-header__profile-trigger .lc-icon,.lc-header--dark .lc-header__profile-trigger svg,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg{color:var(--lc-header-trigger-fg)}.lc-header--dark lc-button,html.dark .lc-header:not(.lc-header--light) lc-button,:root.dark .lc-header:not(.lc-header--light) lc-button,.dark .lc-header:not(.lc-header--light) lc-button{--lc-button-ghost-fg: var(--lc-header-trigger-fg);--lc-button-ghost-hover-bg: var(--lc-header-hover-bg);--lc-button-ghost-hover-fg: var(--lc-header-fg);--lc-button-ghost-active-bg: var(--lc-header-hover-bg)}.lc-header__profile-trigger{display:flex;align-items:center;gap:var(--lc-density-gap-sm, var(--spacing-2));min-height:var(--size-min-touch-height, 2.75rem);box-sizing:border-box;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--spacing-3);background:transparent;border:1px solid var(--lc-header-trigger-border);border-radius:var(--border-radius-full);cursor:pointer;color:var(--lc-header-trigger-fg);transition:background-color .2s ease,border-color .2s ease}.lc-header__profile-trigger:hover{background-color:var(--lc-header-hover-bg);border-color:var(--lc-header-fg-secondary)}.lc-header__profile-trigger:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}.lc-header__profile-trigger--avatar{justify-content:center;width:3rem;height:3rem;min-height:3rem;padding:0;border:0;flex-shrink:0}.lc-header__profile-trigger--avatar:hover{border-color:transparent}.lc-header.lc-header--transparent{--lc-header-bg: transparent;--lc-header-border: transparent;--_lc-header-border-width: 0px;border-bottom-width:0}.lc-menu__header{padding:var(--spacing-3) var(--spacing-4);font-size:var(--font-size-sm);color:var(--color-text-secondary, #6b7280);border-bottom:1px solid var(--color-divider, #e5e7eb);margin-bottom:var(--spacing-1);word-break:break-word}.lc-header__menu-header{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4));border-bottom:1px solid var(--color-divider, #e5e7eb);margin-bottom:var(--spacing-1)}.lc-header__menu-user-info{flex:1;min-width:0}.lc-header__menu-user-name{font-size:var(--font-size-sm);font-weight:600;color:var(--color-text-primary);overflow-wrap:break-word;line-height:1.4}.lc-header__menu-user-email{font-size:var(--font-size-xs);color:var(--color-text-secondary, #6b7280);overflow-wrap:anywhere;line-height:1.4}\n"],
|
|
490
506
|
dependencies: [
|
|
491
507
|
{
|
|
492
508
|
kind: "directive",
|
|
@@ -611,6 +627,18 @@ var HeaderComponent = class HeaderComponent {
|
|
|
611
627
|
"maxWidth"
|
|
612
628
|
],
|
|
613
629
|
outputs: ["itemClick", "closed"]
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
kind: "component",
|
|
633
|
+
type: BadgeComponent,
|
|
634
|
+
selector: "lc-badge",
|
|
635
|
+
inputs: [
|
|
636
|
+
"variant",
|
|
637
|
+
"size",
|
|
638
|
+
"rounded",
|
|
639
|
+
"dot",
|
|
640
|
+
"wrap"
|
|
641
|
+
]
|
|
614
642
|
}
|
|
615
643
|
],
|
|
616
644
|
viewProviders: [ɵprovideKitIcons([
|
|
@@ -642,7 +670,8 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
642
670
|
LogoComponent,
|
|
643
671
|
IconComponent,
|
|
644
672
|
AvatarComponent,
|
|
645
|
-
MenuComponent
|
|
673
|
+
MenuComponent,
|
|
674
|
+
BadgeComponent
|
|
646
675
|
],
|
|
647
676
|
viewProviders: [ɵprovideKitIcons([
|
|
648
677
|
ICON_BARS_3,
|
|
@@ -655,8 +684,8 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
655
684
|
encapsulation: ViewEncapsulation.None,
|
|
656
685
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
657
686
|
host: { class: "lc-header-host" },
|
|
658
|
-
template: "<header\n class=\"lc-header\"\n [class.lc-header--dark]=\"theme() === 'dark'\"\n [class.lc-header--light]=\"theme() === 'light'\"\n [class.lc-header--size-sm]=\"size() === 'sm'\"\n [class.lc-header--size-md]=\"size() === 'md'\"\n [class.lc-header--size-lg]=\"size() === 'lg'\"\n [class.lc-header--size-xl]=\"size() === 'xl'\"\n [class.lc-header--has-nav]=\"navItems().length > 0\"\n [class.lc-header--static]=\"!sticky()\"\n [class.lc-header--transparent]=\"variant() === 'transparent'\"\n [class.lc-header--context-below]=\"contextBelow()\"\n>\n <!-- Hamburger menu icon (mobile only) -->\n @if (showHamburger()) {\n <lc-button\n #hamburger\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"hamburgerAriaLabel()\"\n (clicked)=\"onHamburgerClick()\"\n class=\"lc-header__hamburger\"\n >\n <lc-icon name=\"bars-3\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Logo (clickable, navigates to home) -->\n <div class=\"lc-header__brand\">\n @if (showLogo()) {\n @if (homeHref()) {\n <!-- Plain link: works without the router and without JavaScript. -->\n <a [href]=\"homeHref()\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n } @else {\n <a routerLink=\"/\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n }\n }\n\n @if (title()) {\n <div class=\"lc-header__title-group\">\n @switch (titleTag()) {\n @case ('h1') {\n <h1 class=\"lc-header__title\">{{ title() }}</h1>\n }\n @case ('h2') {\n <h2 class=\"lc-header__title\">{{ title() }}</h2>\n }\n @case ('h3') {\n <h3 class=\"lc-header__title\">{{ title() }}</h3>\n }\n @case ('p') {\n <p class=\"lc-header__title\">{{ title() }}</p>\n }\n @case ('div') {\n <div class=\"lc-header__title\">{{ title() }}</div>\n }\n @default {\n <span class=\"lc-header__title\">{{ title() }}</span>\n }\n }\n @if (subtitle()) {\n <span class=\"lc-header__subtitle\" [attr.title]=\"subtitle()\">{{ subtitle() }}</span>\n }\n </div>\n }\n </div>\n\n <!-- Main navigation: inline from 1024 px, its own wrapping row below. -->\n @if (navItems().length > 0) {\n <nav class=\"lc-header__nav\" [attr.aria-label]=\"navAriaLabel()\">\n <ul class=\"lc-header__nav-list\">\n @for (item of navItems(); track $index) {\n <li class=\"lc-header__nav-item\">\n <a\n class=\"lc-header__nav-link\"\n [href]=\"item.href\"\n [attr.aria-current]=\"item.current ? 'page' : null\"\n >{{ item.label }}</a>\n </li>\n }\n </ul>\n </nav>\n }\n\n <!-- Zero-width spacer on the brand's row: keeps 3.10's two gaps before the end\n group and the first row's height; grows in a has-nav header from 1024 px. -->\n <div class=\"lc-header__spacer\"></div>\n\n <!-- End of the bar: the end slot (e.g. a language link), theme toggle, context\n info and account menu as one group. It goes to the end of a second row\n when it doesn't fit beside the brand (AS-31). No role; the DOM order and\n so the focus order stay brand → nav → end. -->\n <div class=\"lc-header__end\">\n <ng-content select=\"[slot='end']\" />\n\n <!-- Theme toggle button -->\n @if (showThemeButton()) {\n <lc-button\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"themeService.isDark() ? themeToLightAriaLabel() : themeToDarkAriaLabel()\"\n (clicked)=\"onThemeButtonClick()\"\n class=\"lc-header__theme-toggle\"\n >\n <lc-icon [name]=\"themeService.isDark() ? 'sun' : 'moon'\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Context info (e.g. tenant, organization, project) -->\n @if (contextName()) {\n <button\n #contextButton\n type=\"button\"\n class=\"lc-header__context-info\"\n [attr.aria-label]=\"contextButtonAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n <!-- A real space between label and name: the visible text reads\n \"Tenant Acme Corp\", not \"TenantAcme Corp\" (WCAG 2.5.3). The\n tooltip sits on the name, so it is not a description of the button. -->\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>&ngsp;\n }\n <span class=\"lc-header__context-name\" [attr.title]=\"contextName()\">{{ contextName() }}</span>\n </button>\n }\n\n <!-- User profile menu -->\n @if (showUserMenu()) {\n <lc-menu\n [items]=\"menuItems()\"\n [isOpen]=\"isDropdownOpen()\"\n position=\"bottom-right\"\n minWidth=\"min(220px, calc(100vw - 2rem))\"\n maxWidth=\"min(18rem, calc(100vw - 2rem))\"\n [size]=\"menuSize()\"\n (itemClick)=\"onMenuItemClick($event)\"\n (closed)=\"closeDropdown()\"\n >\n <!-- Menu header with avatar, name and email -->\n @if (userName() || userEmail()) {\n <div header class=\"lc-header__menu-header\">\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n [fallback]=\"userAvatarFallback()\"\n size=\"md\"\n />\n <div class=\"lc-header__menu-user-info\">\n @if (userName()) {\n <div class=\"lc-header__menu-user-name\">{{ userName() }}</div>\n }\n @if (userEmail()) {\n <div class=\"lc-header__menu-user-email\">{{ userEmail() }}</div>\n }\n </div>\n </div>\n }\n\n @if (userMenuTrigger() === 'avatar') {\n <button\n trigger\n class=\"lc-header__profile-trigger lc-header__profile-trigger--avatar\"\n type=\"button\"\n [attr.aria-label]=\"avatarTriggerAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n [fallback]=\"userAvatarFallback()\"\n [decorative]=\"true\"\n size=\"md\"\n />\n </button>\n } @else {\n <button\n trigger\n class=\"lc-header__profile-trigger\"\n type=\"button\"\n [attr.aria-label]=\"userMenuAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-icon name=\"user\" size=\"sm\" [decorative]=\"true\" />\n <lc-icon name=\"chevron-down\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </lc-menu>\n }\n </div>\n</header>\n\n<ng-template #brandLogo>\n <lc-logo\n [variant]=\"logo() ? 'full' : 'emblem'\"\n [size]=\"logoSize()\"\n [clickable]=\"false\"\n [colorMode]=\"theme() === 'dark' ? 'dark' : theme() === 'light' ? 'light' : 'auto'\"\n [src]=\"logoSrc()\"\n [emblemSrc]=\"logoEmblemSrc()\"\n [darkSrc]=\"logoDarkSrc()\"\n [darkEmblemSrc]=\"logoDarkEmblemSrc()\"\n [alt]=\"logoAlt() || title() || 'Logo'\"\n />\n</ng-template>\n",
|
|
659
|
-
styles: [".lc-header{--lc-header-bg: var(--color-surface);--lc-header-fg: var(--color-text-primary);--lc-header-fg-secondary: var(--color-text-secondary);--lc-header-border: var(--color-border);--lc-header-hover-bg: var(--color-surface-hover);--lc-header-trigger-border: var(--color-border);--lc-header-trigger-fg: var(--color-text-secondary);--lc-header-padding-x: var(--lc-density-padding-lg, var(--spacing-6));display:flex;align-items:center;gap:var(--lc-density-gap-md, var(--spacing-4));flex-wrap:wrap;row-gap:0;padding:0 var(--lc-header-padding-x);background-color:var(--lc-header-bg);border-bottom:1px solid var(--lc-header-border);min-height:64px;position:sticky;top:0;z-index:1000}.lc-header.lc-header--size-sm{min-height:56px;--lc-header-row-height: 56px}.lc-header.lc-header--size-md{min-height:64px;--lc-header-row-height: 64px}.lc-header.lc-header--size-lg{min-height:80px;--lc-header-row-height: 80px}.lc-header.lc-header--size-xl{min-height:112px;--lc-header-row-height: 112px}@media(max-width:768px){.lc-header{--lc-header-padding-x: var(--lc-density-padding-md, var(--spacing-4))}}.lc-header--static{position:relative}.lc-header__nav{display:flex;align-items:center;min-width:0}.lc-header__nav-list{display:flex;flex-wrap:wrap;align-items:center;gap:0 var(--lc-header-nav-gap, var(--spacing-1));margin:0;padding:0;list-style:none}.lc-header__nav-item{display:flex}.lc-header__nav-link{display:inline-flex;align-items:center;min-height:var(--size-min-touch-height, 2.75rem);padding:0 var(--spacing-3);border-radius:var(--border-radius-md);color:var(--lc-header-fg-secondary);font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);line-height:var(--line-height-tight);text-decoration:none;transition:color .15s ease,background-color .15s ease}.lc-header__nav-link:hover{color:var(--lc-header-fg);background-color:var(--lc-header-hover-bg)}.lc-header__nav-link:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}.lc-header__nav-link[aria-current=page]{color:var(--lc-header-fg);text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:.45em}@media(max-width:1023.98px){.lc-header--has-nav .lc-header__nav{order:1;flex-basis:100%;margin-left:calc(-1 * var(--spacing-3))}}@media(min-width:1024px){.lc-header--has-nav .lc-header__brand{flex:0 1 auto;width:auto;max-width:none}.lc-header--has-nav .lc-header__title-group{flex:0 1 auto}.lc-header--has-nav .lc-header__title{margin-inline-end:0}.lc-header--has-nav .lc-header__subtitle{contain:none}.lc-header--has-nav .lc-header__spacer{flex:1}}.lc-header--dark{--lc-header-bg: #070e15;--lc-header-fg: #f1f6f8;--lc-header-fg-secondary: #c4d2d9;--lc-header-border: rgba(255, 255, 255, .08);--lc-header-hover-bg: rgba(132, 199, 212, .06);--lc-header-trigger-border: rgba(255, 255, 255, .08);--lc-header-trigger-fg: #c4d2d9}.lc-header--light{--lc-header-bg: #ffffff;--lc-header-fg: #111827;--lc-header-fg-secondary: #4b5563;--lc-header-border: #e5e7eb;--lc-header-hover-bg: #f3f4f6;--lc-header-trigger-border: #e5e7eb;--lc-header-trigger-fg: #374151;--_lc-header-ring: #208497}.lc-header__hamburger{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:24px;margin-left:calc(var(--lc-header-hamburger-slot, 56px) / 2 - var(--lc-header-padding-x) - 12px);margin-right:calc(var(--lc-header-hamburger-gap, .5rem) - var(--lc-density-gap-md, var(--spacing-4)));color:var(--lc-header-trigger-fg)}.lc-header__hamburger .lc-icon{width:24px;height:24px}.lc-header__brand{display:flex;flex:1 1 auto;align-items:center;gap:var(--spacing-3);width:min-content;max-width:calc(100% - var(--lc-density-gap-md, var(--spacing-4)));min-width:0}.lc-header--has-nav .lc-header__brand{min-height:var(--lc-header-row-height, 64px)}.lc-header__logo{display:flex;align-items:center;text-decoration:none;color:var(--lc-header-fg);flex-shrink:0}.lc-header__logo:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:4px;border-radius:var(--border-radius-sm)}.lc-header__logo-img{height:32px;width:auto}.lc-header__title-group{display:flex;flex:1 1 auto;flex-direction:column;gap:.125rem;min-width:0}.lc-header__title{font-size:1.25rem;font-weight:700;color:var(--lc-header-fg);margin:0;margin-inline-end:calc(-1 * var(--lc-density-gap-md, var(--spacing-4)));line-height:1.2;overflow-wrap:break-word;transition:color .2s ease}@media(max-width:640px){.lc-header__title{font-size:1.125rem}}.lc-header__subtitle{display:block;contain:inline-size;max-width:var(--lc-header-subtitle-max-width, 100%);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:.8125rem;line-height:1.3;font-weight:500;color:var(--lc-header-fg-secondary);text-transform:var(--lc-header-subtitle-transform, none);transition:color .2s ease}@media(min-width:1024px){.lc-header__subtitle{font-size:var(--font-size-sm, .875rem)}}.lc-header__spacer{flex:0 0 0;min-height:calc(var(--lc-header-row-height, 64px) - var(--_lc-header-border-width, 1px))}.lc-header__end{display:flex;flex-wrap:wrap;align-items:center;align-self:stretch;justify-content:flex-end;gap:0 var(--lc-density-gap-md, var(--spacing-4));margin-inline-start:auto;min-width:0;max-width:100%}.lc-header__end:empty{display:none}.lc-header__theme-toggle{color:var(--lc-header-trigger-fg)}.lc-header__context-info{display:flex;flex-direction:column;align-items:flex-end;gap:.0625rem;padding:var(--spacing-2) var(--spacing-3);background:transparent;border:none;cursor:pointer;border-radius:var(--border-radius-md);color:var(--lc-header-trigger-fg);transition:background-color .2s ease,color .2s ease;max-width:220px;min-width:0}.lc-header__context-info:hover{background-color:var(--lc-header-hover-bg)}.lc-header__context-info:hover .lc-header__context-name{color:var(--lc-header-fg)}.lc-header__context-info:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}@media(max-width:640px){.lc-header__context-info{display:none}}@media(max-width:640px){.lc-header--context-below{justify-content:flex-end}.lc-header--context-below .lc-header__end{display:contents}.lc-header--context-below .lc-header__context-info{display:flex;order:2;flex:1 0 calc(100% + 2 * var(--spacing-3));margin:0 calc(-1 * var(--spacing-3)) var(--spacing-2);max-width:none;min-height:var(--size-min-touch-height, 2.75rem);align-items:flex-start;justify-content:center;text-align:start}}.lc-header__context-label{font-size:var(--font-size-xs);font-weight:500;color:var(--lc-header-fg-secondary);text-transform:uppercase;letter-spacing:.05em;line-height:1.2}.lc-header__context-name{font-size:var(--font-size-sm);font-weight:600;color:var(--lc-header-fg);line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.lc-header--dark .lc-header__theme-toggle lc-icon,.lc-header--dark .lc-header__theme-toggle .lc-icon,.lc-header--dark .lc-header__theme-toggle svg,.lc-header--dark .lc-header__hamburger lc-icon,.lc-header--dark .lc-header__hamburger .lc-icon,.lc-header--dark .lc-header__hamburger svg,.lc-header--dark .lc-header__profile-trigger lc-icon,.lc-header--dark .lc-header__profile-trigger .lc-icon,.lc-header--dark .lc-header__profile-trigger svg,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg{color:var(--lc-header-trigger-fg)}.lc-header--dark lc-button,html.dark .lc-header:not(.lc-header--light) lc-button,:root.dark .lc-header:not(.lc-header--light) lc-button,.dark .lc-header:not(.lc-header--light) lc-button{--lc-button-ghost-fg: var(--lc-header-trigger-fg);--lc-button-ghost-hover-bg: var(--lc-header-hover-bg);--lc-button-ghost-hover-fg: var(--lc-header-fg);--lc-button-ghost-active-bg: var(--lc-header-hover-bg)}.lc-header__profile-trigger{display:flex;align-items:center;gap:var(--lc-density-gap-sm, var(--spacing-2));min-height:var(--size-min-touch-height, 2.75rem);box-sizing:border-box;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--spacing-3);background:transparent;border:1px solid var(--lc-header-trigger-border);border-radius:var(--border-radius-full);cursor:pointer;color:var(--lc-header-trigger-fg);transition:background-color .2s ease,border-color .2s ease}.lc-header__profile-trigger:hover{background-color:var(--lc-header-hover-bg);border-color:var(--lc-header-fg-secondary)}.lc-header__profile-trigger:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}.lc-header__profile-trigger--avatar{justify-content:center;width:3rem;height:3rem;min-height:3rem;padding:0;border:0;flex-shrink:0}.lc-header__profile-trigger--avatar:hover{border-color:transparent}.lc-header.lc-header--transparent{--lc-header-bg: transparent;--lc-header-border: transparent;--_lc-header-border-width: 0px;border-bottom-width:0}.lc-menu__header{padding:var(--spacing-3) var(--spacing-4);font-size:var(--font-size-sm);color:var(--color-text-secondary, #6b7280);border-bottom:1px solid var(--color-divider, #e5e7eb);margin-bottom:var(--spacing-1);word-break:break-word}.lc-header__menu-header{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4));border-bottom:1px solid var(--color-divider, #e5e7eb);margin-bottom:var(--spacing-1)}.lc-header__menu-user-info{flex:1;min-width:0}.lc-header__menu-user-name{font-size:var(--font-size-sm);font-weight:600;color:var(--color-text-primary);overflow-wrap:break-word;line-height:1.4}.lc-header__menu-user-email{font-size:var(--font-size-xs);color:var(--color-text-secondary, #6b7280);overflow-wrap:anywhere;line-height:1.4}\n"]
|
|
687
|
+
template: "<header\n class=\"lc-header\"\n [class.lc-header--dark]=\"theme() === 'dark'\"\n [class.lc-header--light]=\"theme() === 'light'\"\n [class.lc-header--size-sm]=\"size() === 'sm'\"\n [class.lc-header--size-md]=\"size() === 'md'\"\n [class.lc-header--size-lg]=\"size() === 'lg'\"\n [class.lc-header--size-xl]=\"size() === 'xl'\"\n [class.lc-header--has-nav]=\"navItems().length > 0\"\n [class.lc-header--static]=\"!sticky()\"\n [class.lc-header--transparent]=\"variant() === 'transparent'\"\n [class.lc-header--context-below]=\"contextBelow()\"\n>\n <!-- Hamburger menu icon (mobile only) -->\n @if (showHamburger()) {\n <lc-button\n #hamburger\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"hamburgerAriaLabel()\"\n (clicked)=\"onHamburgerClick()\"\n class=\"lc-header__hamburger\"\n >\n <lc-icon name=\"bars-3\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Logo (clickable, navigates to home) -->\n <div class=\"lc-header__brand\">\n @if (showLogo()) {\n @if (homeHref()) {\n <!-- Plain link: works without the router and without JavaScript. -->\n <a [href]=\"homeHref()\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n } @else {\n <a routerLink=\"/\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n }\n }\n\n @if (title()) {\n <div class=\"lc-header__title-group\">\n @switch (titleTag()) {\n @case ('h1') {\n <h1 class=\"lc-header__title\">{{ title() }}</h1>\n }\n @case ('h2') {\n <h2 class=\"lc-header__title\">{{ title() }}</h2>\n }\n @case ('h3') {\n <h3 class=\"lc-header__title\">{{ title() }}</h3>\n }\n @case ('p') {\n <p class=\"lc-header__title\">{{ title() }}</p>\n }\n @case ('div') {\n <div class=\"lc-header__title\">{{ title() }}</div>\n }\n @default {\n <span class=\"lc-header__title\">{{ title() }}</span>\n }\n }\n @if (subtitle()) {\n <span class=\"lc-header__subtitle\" [attr.title]=\"subtitle()\">{{ subtitle() }}</span>\n }\n </div>\n }\n </div>\n\n <!-- Main navigation: inline from 1024 px, its own wrapping row below. -->\n @if (navItems().length > 0) {\n <nav class=\"lc-header__nav\" [attr.aria-label]=\"navAriaLabel()\">\n <ul class=\"lc-header__nav-list\">\n @for (item of navItems(); track $index) {\n <li class=\"lc-header__nav-item\">\n <a\n class=\"lc-header__nav-link\"\n [href]=\"item.href\"\n [attr.aria-current]=\"item.current ? 'page' : null\"\n >{{ item.label }}</a>\n </li>\n }\n </ul>\n </nav>\n }\n\n <!-- Zero-width spacer on the brand's row: keeps 3.10's two gaps before the end\n group and the first row's height; grows in a has-nav header from 1024 px. -->\n <div class=\"lc-header__spacer\"></div>\n\n <!-- End of the bar: the end slot (e.g. a language link), theme toggle, context\n info and account menu as one group. It goes to the end of a second row\n when it doesn't fit beside the brand (AS-31). No role; the DOM order and\n so the focus order stay brand → nav → end. -->\n <div class=\"lc-header__end\">\n <ng-content select=\"[slot='end']\" />\n\n <!-- Theme toggle button -->\n @if (showThemeButton()) {\n <lc-button\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"themeService.isDark() ? themeToLightAriaLabel() : themeToDarkAriaLabel()\"\n (clicked)=\"onThemeButtonClick()\"\n class=\"lc-header__theme-toggle\"\n >\n <lc-icon [name]=\"themeService.isDark() ? 'sun' : 'moon'\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Context info (e.g. tenant, organization, project) -->\n @if (contextName()) {\n <button\n #contextButton\n type=\"button\"\n class=\"lc-header__context-info\"\n [class.lc-header__context-info--badge]=\"!!contextBadge()\"\n [attr.aria-label]=\"contextButtonAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n <!-- A real space between label and name: the visible text reads\n \"Tenant Acme Corp\", not \"TenantAcme Corp\" (WCAG 2.5.3). The\n tooltip sits on the name, so it is not a description of the button. -->\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>&ngsp;\n }\n @if (contextBadge(); as badge) {\n <!-- 3.36: name and state badge on one row; the name keeps its ellipsis. -->\n <span class=\"lc-header__context-row\">\n <span class=\"lc-header__context-name\" [attr.title]=\"contextName()\">{{ contextName() }}</span>&ngsp;\n <lc-badge class=\"lc-header__context-badge\" size=\"sm\" [rounded]=\"true\" [variant]=\"badge.variant ?? 'default'\">{{ badge.label }}</lc-badge>\n </span>\n } @else {\n <span class=\"lc-header__context-name\" [attr.title]=\"contextName()\">{{ contextName() }}</span>\n }\n </button>\n }\n\n <!-- User profile menu -->\n @if (showUserMenu()) {\n <lc-menu\n [items]=\"menuItems()\"\n [isOpen]=\"isDropdownOpen()\"\n position=\"bottom-right\"\n minWidth=\"min(220px, calc(100vw - 2rem))\"\n maxWidth=\"min(18rem, calc(100vw - 2rem))\"\n [size]=\"menuSize()\"\n (itemClick)=\"onMenuItemClick($event)\"\n (closed)=\"closeDropdown()\"\n >\n <!-- Menu header with avatar, name and email -->\n @if (userName() || userEmail()) {\n <div header class=\"lc-header__menu-header\">\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n [fallback]=\"userAvatarFallback()\"\n size=\"md\"\n />\n <div class=\"lc-header__menu-user-info\">\n @if (userName()) {\n <div class=\"lc-header__menu-user-name\">{{ userName() }}</div>\n }\n @if (userEmail()) {\n <div class=\"lc-header__menu-user-email\">{{ userEmail() }}</div>\n }\n </div>\n </div>\n }\n\n @if (userMenuTrigger() === 'avatar') {\n <button\n trigger\n class=\"lc-header__profile-trigger lc-header__profile-trigger--avatar\"\n type=\"button\"\n [attr.aria-label]=\"avatarTriggerAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n [fallback]=\"userAvatarFallback()\"\n [decorative]=\"true\"\n size=\"md\"\n />\n </button>\n } @else {\n <button\n trigger\n class=\"lc-header__profile-trigger\"\n type=\"button\"\n [attr.aria-label]=\"userMenuAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-icon name=\"user\" size=\"sm\" [decorative]=\"true\" />\n <lc-icon name=\"chevron-down\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </lc-menu>\n }\n </div>\n</header>\n\n<ng-template #brandLogo>\n <lc-logo\n [variant]=\"logo() ? 'full' : 'emblem'\"\n [size]=\"logoSize()\"\n [clickable]=\"false\"\n [colorMode]=\"theme() === 'dark' ? 'dark' : theme() === 'light' ? 'light' : 'auto'\"\n [src]=\"logoSrc()\"\n [emblemSrc]=\"logoEmblemSrc()\"\n [darkSrc]=\"logoDarkSrc()\"\n [darkEmblemSrc]=\"logoDarkEmblemSrc()\"\n [alt]=\"logoAlt() || title() || 'Logo'\"\n />\n</ng-template>\n",
|
|
688
|
+
styles: [".lc-header{--lc-header-bg: var(--color-surface);--lc-header-fg: var(--color-text-primary);--lc-header-fg-secondary: var(--color-text-secondary);--lc-header-border: var(--color-border);--lc-header-hover-bg: var(--color-surface-hover);--lc-header-trigger-border: var(--color-border);--lc-header-trigger-fg: var(--color-text-secondary);--lc-header-padding-x: var(--lc-density-padding-lg, var(--spacing-6));display:flex;align-items:center;gap:var(--lc-density-gap-md, var(--spacing-4));flex-wrap:wrap;row-gap:0;padding:0 var(--lc-header-padding-x);background-color:var(--lc-header-bg);border-bottom:1px solid var(--lc-header-border);min-height:64px;position:sticky;top:0;z-index:1000}.lc-header.lc-header--size-sm{min-height:56px;--lc-header-row-height: 56px}.lc-header.lc-header--size-md{min-height:64px;--lc-header-row-height: 64px}.lc-header.lc-header--size-lg{min-height:80px;--lc-header-row-height: 80px}.lc-header.lc-header--size-xl{min-height:112px;--lc-header-row-height: 112px}@media(max-width:768px){.lc-header{--lc-header-padding-x: var(--lc-density-padding-md, var(--spacing-4))}}.lc-header--static{position:relative}.lc-header__nav{display:flex;align-items:center;min-width:0}.lc-header__nav-list{display:flex;flex-wrap:wrap;align-items:center;gap:0 var(--lc-header-nav-gap, var(--spacing-1));margin:0;padding:0;list-style:none}.lc-header__nav-item{display:flex}.lc-header__nav-link{display:inline-flex;align-items:center;min-height:var(--size-min-touch-height, 2.75rem);padding:0 var(--spacing-3);border-radius:var(--border-radius-md);color:var(--lc-header-fg-secondary);font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);line-height:var(--line-height-tight);text-decoration:none;transition:color .15s ease,background-color .15s ease}.lc-header__nav-link:hover{color:var(--lc-header-fg);background-color:var(--lc-header-hover-bg)}.lc-header__nav-link:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}.lc-header__nav-link[aria-current=page]{color:var(--lc-header-fg);text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:.45em}@media(max-width:1023.98px){.lc-header--has-nav .lc-header__nav{order:1;flex-basis:100%;margin-left:calc(-1 * var(--spacing-3))}}@media(min-width:1024px){.lc-header--has-nav .lc-header__brand{flex:0 1 auto;width:auto;max-width:none}.lc-header--has-nav .lc-header__title-group{flex:0 1 auto}.lc-header--has-nav .lc-header__title{margin-inline-end:0}.lc-header--has-nav .lc-header__subtitle{contain:none}.lc-header--has-nav .lc-header__spacer{flex:1}}.lc-header--dark{--lc-header-bg: #070e15;--lc-header-fg: #f1f6f8;--lc-header-fg-secondary: #c4d2d9;--lc-header-border: rgba(255, 255, 255, .08);--lc-header-hover-bg: rgba(132, 199, 212, .06);--lc-header-trigger-border: rgba(255, 255, 255, .08);--lc-header-trigger-fg: #c4d2d9}.lc-header--light{--lc-header-bg: #ffffff;--lc-header-fg: #111827;--lc-header-fg-secondary: #4b5563;--lc-header-border: #e5e7eb;--lc-header-hover-bg: #f3f4f6;--lc-header-trigger-border: #e5e7eb;--lc-header-trigger-fg: #374151;--_lc-header-ring: #208497}.lc-header__hamburger{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:24px;margin-left:calc(var(--lc-header-hamburger-slot, 56px) / 2 - var(--lc-header-padding-x) - 12px);margin-right:calc(var(--lc-header-hamburger-gap, .5rem) - var(--lc-density-gap-md, var(--spacing-4)));color:var(--lc-header-trigger-fg)}.lc-header__hamburger .lc-icon{width:24px;height:24px}.lc-header__brand{display:flex;flex:1 1 auto;align-items:center;gap:var(--spacing-3);width:min-content;max-width:calc(100% - var(--lc-density-gap-md, var(--spacing-4)));min-width:0}.lc-header--has-nav .lc-header__brand{min-height:var(--lc-header-row-height, 64px)}.lc-header__logo{display:flex;align-items:center;text-decoration:none;color:var(--lc-header-fg);flex-shrink:0}.lc-header__logo:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:4px;border-radius:var(--border-radius-sm)}.lc-header__logo-img{height:32px;width:auto}.lc-header__title-group{display:flex;flex:1 1 auto;flex-direction:column;gap:.125rem;min-width:0}.lc-header__title{font-size:1.25rem;font-weight:700;color:var(--lc-header-fg);margin:0;margin-inline-end:calc(-1 * var(--lc-density-gap-md, var(--spacing-4)));line-height:1.2;overflow-wrap:break-word;transition:color .2s ease}@media(max-width:640px){.lc-header__title{font-size:1.125rem}}.lc-header__subtitle{display:block;contain:inline-size;max-width:var(--lc-header-subtitle-max-width, 100%);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:.8125rem;line-height:1.3;font-weight:500;color:var(--lc-header-fg-secondary);text-transform:var(--lc-header-subtitle-transform, none);transition:color .2s ease}@media(min-width:1024px){.lc-header__subtitle{font-size:var(--font-size-sm, .875rem)}}.lc-header__spacer{flex:0 0 0;min-height:calc(var(--lc-header-row-height, 64px) - var(--_lc-header-border-width, 1px))}.lc-header__end{display:flex;flex-wrap:wrap;align-items:center;align-self:stretch;justify-content:flex-end;gap:0 var(--lc-density-gap-md, var(--spacing-4));margin-inline-start:auto;min-width:0;max-width:100%}.lc-header__end:empty{display:none}.lc-header__theme-toggle{color:var(--lc-header-trigger-fg)}.lc-header__context-info{display:flex;flex-direction:column;align-items:flex-end;gap:.0625rem;padding:var(--spacing-2) var(--spacing-3);background:transparent;border:none;cursor:pointer;border-radius:var(--border-radius-md);color:var(--lc-header-trigger-fg);transition:background-color .2s ease,color .2s ease;max-width:220px;min-width:0}.lc-header__context-info:hover{background-color:var(--lc-header-hover-bg)}.lc-header__context-info:hover .lc-header__context-name{color:var(--lc-header-fg)}.lc-header__context-info:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}@media(max-width:640px){.lc-header__context-info{display:none}}.lc-header__context-info--badge{max-width:340px}.lc-header__context-row{display:flex;align-items:center;gap:var(--spacing-1-5);min-width:0;max-width:100%}.lc-header__context-row .lc-header__context-name{flex:0 1 auto;min-width:0}.lc-header__context-badge{display:inline-flex;flex-shrink:0;margin-block:calc(.65 * var(--font-size-sm) - .75rem)}@media(max-width:640px){.lc-header--context-below{justify-content:flex-end}.lc-header--context-below .lc-header__end{display:contents}.lc-header--context-below .lc-header__context-info{display:flex;order:2;flex:1 0 calc(100% + 2 * var(--spacing-3));margin:0 calc(-1 * var(--spacing-3)) var(--spacing-2);max-width:none;min-height:var(--size-min-touch-height, 2.75rem);align-items:flex-start;justify-content:center;text-align:start}}.lc-header__context-label{font-size:var(--font-size-xs);font-weight:500;color:var(--lc-header-fg-secondary);text-transform:uppercase;letter-spacing:.05em;line-height:1.2}.lc-header__context-name{font-size:var(--font-size-sm);font-weight:600;color:var(--lc-header-fg);line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.lc-header--dark .lc-header__theme-toggle lc-icon,.lc-header--dark .lc-header__theme-toggle .lc-icon,.lc-header--dark .lc-header__theme-toggle svg,.lc-header--dark .lc-header__hamburger lc-icon,.lc-header--dark .lc-header__hamburger .lc-icon,.lc-header--dark .lc-header__hamburger svg,.lc-header--dark .lc-header__profile-trigger lc-icon,.lc-header--dark .lc-header__profile-trigger .lc-icon,.lc-header--dark .lc-header__profile-trigger svg,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,html.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,:root.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__theme-toggle svg,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__hamburger svg,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger .lc-icon,.dark .lc-header:not(.lc-header--light) .lc-header__profile-trigger svg{color:var(--lc-header-trigger-fg)}.lc-header--dark lc-button,html.dark .lc-header:not(.lc-header--light) lc-button,:root.dark .lc-header:not(.lc-header--light) lc-button,.dark .lc-header:not(.lc-header--light) lc-button{--lc-button-ghost-fg: var(--lc-header-trigger-fg);--lc-button-ghost-hover-bg: var(--lc-header-hover-bg);--lc-button-ghost-hover-fg: var(--lc-header-fg);--lc-button-ghost-active-bg: var(--lc-header-hover-bg)}.lc-header__profile-trigger{display:flex;align-items:center;gap:var(--lc-density-gap-sm, var(--spacing-2));min-height:var(--size-min-touch-height, 2.75rem);box-sizing:border-box;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--spacing-3);background:transparent;border:1px solid var(--lc-header-trigger-border);border-radius:var(--border-radius-full);cursor:pointer;color:var(--lc-header-trigger-fg);transition:background-color .2s ease,border-color .2s ease}.lc-header__profile-trigger:hover{background-color:var(--lc-header-hover-bg);border-color:var(--lc-header-fg-secondary)}.lc-header__profile-trigger:focus-visible{outline:2px solid var(--_lc-header-ring, var(--color-primary-500));outline-offset:2px}.lc-header__profile-trigger--avatar{justify-content:center;width:3rem;height:3rem;min-height:3rem;padding:0;border:0;flex-shrink:0}.lc-header__profile-trigger--avatar:hover{border-color:transparent}.lc-header.lc-header--transparent{--lc-header-bg: transparent;--lc-header-border: transparent;--_lc-header-border-width: 0px;border-bottom-width:0}.lc-menu__header{padding:var(--spacing-3) var(--spacing-4);font-size:var(--font-size-sm);color:var(--color-text-secondary, #6b7280);border-bottom:1px solid var(--color-divider, #e5e7eb);margin-bottom:var(--spacing-1);word-break:break-word}.lc-header__menu-header{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4));border-bottom:1px solid var(--color-divider, #e5e7eb);margin-bottom:var(--spacing-1)}.lc-header__menu-user-info{flex:1;min-width:0}.lc-header__menu-user-name{font-size:var(--font-size-sm);font-weight:600;color:var(--color-text-primary);overflow-wrap:break-word;line-height:1.4}.lc-header__menu-user-email{font-size:var(--font-size-xs);color:var(--color-text-secondary, #6b7280);overflow-wrap:anywhere;line-height:1.4}\n"]
|
|
660
689
|
}]
|
|
661
690
|
}],
|
|
662
691
|
ctorParameters: () => [],
|
|
@@ -885,6 +914,14 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
885
914
|
required: false
|
|
886
915
|
}]
|
|
887
916
|
}],
|
|
917
|
+
contextBadge: [{
|
|
918
|
+
type: i0.Input,
|
|
919
|
+
args: [{
|
|
920
|
+
isSignal: true,
|
|
921
|
+
alias: "contextBadge",
|
|
922
|
+
required: false
|
|
923
|
+
}]
|
|
924
|
+
}],
|
|
888
925
|
menuSize: [{
|
|
889
926
|
type: i0.Input,
|
|
890
927
|
args: [{
|