@lifecorner/angular-ui-kit 3.11.3 → 3.11.4
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/fesm2022/lifecorner-angular-ui-kit-avatar.mjs +12 -12
- package/fesm2022/lifecorner-angular-ui-kit-avatar.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs +11 -3
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-menu.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-menu.mjs.map +1 -1
- package/package.json +1 -1
- package/types/lifecorner-angular-ui-kit-avatar.d.ts +7 -0
- package/types/lifecorner-angular-ui-kit-header.d.ts +22 -2
|
@@ -15,6 +15,16 @@ const AVATAR_MEMBER_COLORS = [
|
|
|
15
15
|
"member-5",
|
|
16
16
|
"member-6"
|
|
17
17
|
];
|
|
18
|
+
function ɵavatarInitials(name) {
|
|
19
|
+
if (!name || name.trim() === "") return "?";
|
|
20
|
+
const parts = name.trim().split(/\s+/).filter((p) => p.length > 0);
|
|
21
|
+
if (parts.length === 0) return "?";
|
|
22
|
+
if (parts.length === 1) return parts[0]?.[0]?.toUpperCase() ?? "?";
|
|
23
|
+
const firstInitial = parts[0]?.[0];
|
|
24
|
+
const lastInitial = parts[parts.length - 1]?.[0];
|
|
25
|
+
if (firstInitial && lastInitial) return (firstInitial + lastInitial).toUpperCase();
|
|
26
|
+
return "?";
|
|
27
|
+
}
|
|
18
28
|
var AvatarComponent = class AvatarComponent {
|
|
19
29
|
src = input(...ngDevMode ? [void 0, { debugName: "src" }] : /* istanbul ignore next */ []);
|
|
20
30
|
alt = input(...ngDevMode ? [void 0, { debugName: "alt" }] : /* istanbul ignore next */ []);
|
|
@@ -25,17 +35,7 @@ var AvatarComponent = class AvatarComponent {
|
|
|
25
35
|
color = input("default", ...ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []);
|
|
26
36
|
ring = input(false, ...ngDevMode ? [{ debugName: "ring" }] : /* istanbul ignore next */ []);
|
|
27
37
|
decorative = input(false, ...ngDevMode ? [{ debugName: "decorative" }] : /* istanbul ignore next */ []);
|
|
28
|
-
initials = computed(() => {
|
|
29
|
-
const fullName = this.name();
|
|
30
|
-
if (!fullName || fullName.trim() === "") return "?";
|
|
31
|
-
const parts = fullName.trim().split(/\s+/).filter((p) => p.length > 0);
|
|
32
|
-
if (parts.length === 0) return "?";
|
|
33
|
-
if (parts.length === 1) return parts[0]?.[0]?.toUpperCase() ?? "?";
|
|
34
|
-
const firstInitial = parts[0]?.[0];
|
|
35
|
-
const lastInitial = parts[parts.length - 1]?.[0];
|
|
36
|
-
if (firstInitial && lastInitial) return (firstInitial + lastInitial).toUpperCase();
|
|
37
|
-
return "?";
|
|
38
|
-
}, ...ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []);
|
|
38
|
+
initials = computed(() => ɵavatarInitials(this.name()), ...ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []);
|
|
39
39
|
avatarClasses = computed(() => {
|
|
40
40
|
const classes = ["lc-avatar"];
|
|
41
41
|
classes.push(`lc-avatar--${this.size()}`);
|
|
@@ -235,6 +235,6 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
235
235
|
}]
|
|
236
236
|
}
|
|
237
237
|
});
|
|
238
|
-
export { AVATAR_MEMBER_COLORS, AvatarComponent };
|
|
238
|
+
export { AVATAR_MEMBER_COLORS, AvatarComponent, ɵavatarInitials };
|
|
239
239
|
|
|
240
240
|
//# sourceMappingURL=lifecorner-angular-ui-kit-avatar.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-avatar.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/avatar/avatar.component.ts","../../../../libs/angular-ui-kit/src/lib/avatar/avatar.component.html"],"sourcesContent":["import { Component, ChangeDetectionStrategy, input, computed, linkedSignal } from '@angular/core';\n\nexport type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\nexport type AvatarStatus = 'online' | 'offline' | 'away' | 'busy';\n\n/**\n * How the browser loads the image (3.11.2, G-40). The same union as\n * `CharacterLoading`, kept separate because entries don't import each other.\n * - `eager`: no attribute, the browser default.\n * - `lazy`: `loading=\"lazy\"`, for an avatar below the fold.\n */\nexport type AvatarLoading = 'eager' | 'lazy';\n\n/** The box of each size in px at 100 % text, written as the `<img>` `width`/`height` (G-40). */\nconst AVATAR_PX: Readonly<Record<AvatarSize, number>> = { xs: 24, sm: 32, md: 40, lg: 48, xl: 64 };\n\n/**\n * Background of the initials. `default` is the neutral kit avatar; `member-1`\n * … `member-6` are the member colours (`--color-member-1` … `-6`: teal, rust,\n * violet, sage, berry, slate) with white initials, each at least 4.5:1.\n * Assign them per person in a stable order (e.g. join order) and always show\n * the name or initials beside the colour — colour is never the only cue.\n */\nexport type AvatarColor =\n | 'default'\n | 'member-1'\n | 'member-2'\n | 'member-3'\n | 'member-4'\n | 'member-5'\n | 'member-6';\n\n/** The six member colours in their order (`AvatarColor` without `default`). */\nexport const AVATAR_MEMBER_COLORS: readonly AvatarColor[] = [\n 'member-1',\n 'member-2',\n 'member-3',\n 'member-4',\n 'member-5',\n 'member-6',\n];\n\n/**\n * Avatar component for circular user representation.\n *\n * Features:\n * - Image display with fallback to initials\n * - Auto-generated initials from user name\n * - Status indicator (online, offline, busy, away)\n * - Multiple size variants (xs, sm, md, lg, xl)\n * - Member colours (`color`: `member-1` … `member-6`) with white initials\n * - `ring`: a gap in the page colour around the avatar, for stacks and for\n * avatars on images or tints\n * - Accessible with alt text support\n *\n * @example\n * ```html\n * <lc-avatar src=\"https://example.com/avatar.jpg\" alt=\"John Doe\" />\n * <lc-avatar name=\"John Doe\" status=\"online\" size=\"lg\" />\n * <lc-avatar name=\"Tom Ek\" color=\"member-2\" [ring]=\"true\" />\n * ```\n */\n@Component({\n selector: 'lc-avatar',\n standalone: true,\n imports: [],\n templateUrl: './avatar.component.html',\n styleUrl: './avatar.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AvatarComponent {\n /** Image source URL */\n src = input<string>();\n\n /** Alt text for the image */\n alt = input<string>();\n\n /** Full name for generating initials */\n name = input<string>();\n\n /**\n * Size of the avatar: xs 24, sm 32, md 40, lg 48, xl 64 px (1.5 … 4 rem).\n * The `<img>` carries the size's px as `width`/`height`; CSS sizes the box,\n * so at 200 % text the box grows and the attributes only give the 1:1 ratio.\n * @default 'md'\n */\n size = input<AvatarSize>('md');\n\n /**\n * How the browser loads the image. `lazy`: for an avatar below the fold;\n * writes `loading=\"lazy\"`. `eager` writes no attribute. The initials still\n * replace an image that fails.\n * @default 'eager'\n */\n loading = input<AvatarLoading>('eager');\n\n /** Status indicator */\n status = input<AvatarStatus>();\n\n /**\n * Background of the initials: `default` (neutral) or a member colour\n * `member-1` … `member-6` with white initials (LK-7).\n */\n color = input<AvatarColor>('default');\n\n /**\n * Draw a ring in the page colour around the avatar (3 px; 2 px for `xs` and\n * `sm`), so stacked or overlapping avatars stay apart. The ring colour is\n * `--lc-avatar-ring-color` (default `--color-background`); set it to the\n * surface the avatar sits on.\n */\n ring = input<boolean>(false);\n\n /**\n * Hide the avatar from assistive technology (no `img` role, no label), for\n * avatars inside a control that has its own accessible name — e.g. a menu\n * button labelled \"Menu for Tom Ek\".\n */\n decorative = input<boolean>(false);\n\n /**\n * Computed initials from name\n */\n initials = computed(() => {\n const fullName = this.name();\n if (!fullName || fullName.trim() === '') {\n return '?';\n }\n\n const parts = fullName\n .trim()\n .split(/\\s+/)\n .filter((p) => p.length > 0);\n\n if (parts.length === 0) {\n return '?';\n }\n\n if (parts.length === 1) {\n return parts[0]?.[0]?.toUpperCase() ?? '?';\n }\n\n // First and last name initials\n const firstInitial = parts[0]?.[0];\n const lastInitial = parts[parts.length - 1]?.[0];\n\n if (firstInitial && lastInitial) {\n return (firstInitial + lastInitial).toUpperCase();\n }\n return '?';\n });\n\n /**\n * Computed CSS classes for the avatar\n */\n avatarClasses = computed(() => {\n const classes = ['lc-avatar'];\n\n classes.push(`lc-avatar--${this.size()}`);\n\n if (this.status()) {\n classes.push('lc-avatar--with-status');\n }\n\n if (this.color() !== 'default') {\n classes.push('lc-avatar--member', `lc-avatar--${this.color()}`);\n }\n\n if (this.ring()) {\n classes.push('lc-avatar--ring');\n }\n\n return classes.join(' ');\n });\n\n /**\n * Get aria-label for accessibility\n */\n ariaLabel = computed(() => {\n return this.alt() || this.name() || 'Avatar';\n });\n\n /** The size's px, written as the `<img>` `width` and `height` (G-40). */\n protected readonly imagePx = computed(() => AVATAR_PX[this.size()]);\n\n /**\n * Check if image should be displayed\n */\n showImage = computed(() => {\n return this.src() && !this.imageError();\n });\n\n /**\n * Track if image failed to load. Resets whenever `src` changes so a new\n * image gets a fresh attempt instead of staying stuck on the initials.\n */\n protected imageError = linkedSignal<string | undefined, boolean>({\n source: this.src,\n computation: () => false,\n });\n\n /**\n * Handle image load error\n */\n onImageError(): void {\n this.imageError.set(true);\n }\n}\n","<div \n [class]=\"avatarClasses()\"\n [attr.role]=\"decorative() ? null : 'img'\"\n [attr.aria-label]=\"decorative() ? null : ariaLabel()\"\n [attr.aria-hidden]=\"decorative() ? 'true' : null\"\n>\n @if (showImage()) {\n <!-- `loading` before `src`: attribute bindings run in template order, after\n every property binding, so `[attr.src]` keeps the source behind\n `loading` (G-40; an engine may start the request when src is set). -->\n <img\n [alt]=\"alt() || name() || 'Avatar'\"\n class=\"lc-avatar__image\"\n [attr.width]=\"imagePx()\"\n [attr.height]=\"imagePx()\"\n [attr.loading]=\"loading() === 'lazy' ? 'lazy' : null\"\n [attr.src]=\"src()!\"\n (error)=\"onImageError()\"\n />\n } @else {\n <span class=\"lc-avatar__initials\">{{ initials() }}</span>\n }\n\n @if (status()) {\n <span \n class=\"lc-avatar__status lc-avatar__status--{{ status() }}\"\n [attr.aria-label]=\"status() + ' status'\"\n ></span>\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-avatar.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/avatar/avatar.component.ts","../../../../libs/angular-ui-kit/src/lib/avatar/avatar.component.html"],"sourcesContent":["import { Component, ChangeDetectionStrategy, input, computed, linkedSignal } from '@angular/core';\n\nexport type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\nexport type AvatarStatus = 'online' | 'offline' | 'away' | 'busy';\n\n/**\n * How the browser loads the image (3.11.2, G-40). The same union as\n * `CharacterLoading`, kept separate because entries don't import each other.\n * - `eager`: no attribute, the browser default.\n * - `lazy`: `loading=\"lazy\"`, for an avatar below the fold.\n */\nexport type AvatarLoading = 'eager' | 'lazy';\n\n/** The box of each size in px at 100 % text, written as the `<img>` `width`/`height` (G-40). */\nconst AVATAR_PX: Readonly<Record<AvatarSize, number>> = { xs: 24, sm: 32, md: 40, lg: 48, xl: 64 };\n\n/**\n * Background of the initials. `default` is the neutral kit avatar; `member-1`\n * … `member-6` are the member colours (`--color-member-1` … `-6`: teal, rust,\n * violet, sage, berry, slate) with white initials, each at least 4.5:1.\n * Assign them per person in a stable order (e.g. join order) and always show\n * the name or initials beside the colour — colour is never the only cue.\n */\nexport type AvatarColor =\n | 'default'\n | 'member-1'\n | 'member-2'\n | 'member-3'\n | 'member-4'\n | 'member-5'\n | 'member-6';\n\n/** The six member colours in their order (`AvatarColor` without `default`). */\nexport const AVATAR_MEMBER_COLORS: readonly AvatarColor[] = [\n 'member-1',\n 'member-2',\n 'member-3',\n 'member-4',\n 'member-5',\n 'member-6',\n];\n\n/**\n * The initials `lc-avatar` shows for `name`: first and last word's first\n * letter, upper case; one word: its first letter; empty: `?`. Kit-internal\n * (used by `lc-header`, so the account button's name holds exactly the text\n * the avatar shows, AS-33); no semver guarantee.\n */\nexport function ɵavatarInitials(name: string | undefined): string {\n if (!name || name.trim() === '') {\n return '?';\n }\n\n const parts = name\n .trim()\n .split(/\\s+/)\n .filter((p) => p.length > 0);\n\n if (parts.length === 0) {\n return '?';\n }\n\n if (parts.length === 1) {\n return parts[0]?.[0]?.toUpperCase() ?? '?';\n }\n\n // First and last name initials\n const firstInitial = parts[0]?.[0];\n const lastInitial = parts[parts.length - 1]?.[0];\n\n if (firstInitial && lastInitial) {\n return (firstInitial + lastInitial).toUpperCase();\n }\n return '?';\n}\n\n/**\n * Avatar component for circular user representation.\n *\n * Features:\n * - Image display with fallback to initials\n * - Auto-generated initials from user name\n * - Status indicator (online, offline, busy, away)\n * - Multiple size variants (xs, sm, md, lg, xl)\n * - Member colours (`color`: `member-1` … `member-6`) with white initials\n * - `ring`: a gap in the page colour around the avatar, for stacks and for\n * avatars on images or tints\n * - Accessible with alt text support\n *\n * @example\n * ```html\n * <lc-avatar src=\"https://example.com/avatar.jpg\" alt=\"John Doe\" />\n * <lc-avatar name=\"John Doe\" status=\"online\" size=\"lg\" />\n * <lc-avatar name=\"Tom Ek\" color=\"member-2\" [ring]=\"true\" />\n * ```\n */\n@Component({\n selector: 'lc-avatar',\n standalone: true,\n imports: [],\n templateUrl: './avatar.component.html',\n styleUrl: './avatar.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AvatarComponent {\n /** Image source URL */\n src = input<string>();\n\n /** Alt text for the image */\n alt = input<string>();\n\n /** Full name for generating initials */\n name = input<string>();\n\n /**\n * Size of the avatar: xs 24, sm 32, md 40, lg 48, xl 64 px (1.5 … 4 rem).\n * The `<img>` carries the size's px as `width`/`height`; CSS sizes the box,\n * so at 200 % text the box grows and the attributes only give the 1:1 ratio.\n * @default 'md'\n */\n size = input<AvatarSize>('md');\n\n /**\n * How the browser loads the image. `lazy`: for an avatar below the fold;\n * writes `loading=\"lazy\"`. `eager` writes no attribute. The initials still\n * replace an image that fails.\n * @default 'eager'\n */\n loading = input<AvatarLoading>('eager');\n\n /** Status indicator */\n status = input<AvatarStatus>();\n\n /**\n * Background of the initials: `default` (neutral) or a member colour\n * `member-1` … `member-6` with white initials (LK-7).\n */\n color = input<AvatarColor>('default');\n\n /**\n * Draw a ring in the page colour around the avatar (3 px; 2 px for `xs` and\n * `sm`), so stacked or overlapping avatars stay apart. The ring colour is\n * `--lc-avatar-ring-color` (default `--color-background`); set it to the\n * surface the avatar sits on.\n */\n ring = input<boolean>(false);\n\n /**\n * Hide the avatar from assistive technology (no `img` role, no label), for\n * avatars inside a control that has its own accessible name — e.g. a menu\n * button labelled \"Menu for Tom Ek\".\n */\n decorative = input<boolean>(false);\n\n /**\n * Computed initials from name\n */\n initials = computed(() => ɵavatarInitials(this.name()));\n\n /**\n * Computed CSS classes for the avatar\n */\n avatarClasses = computed(() => {\n const classes = ['lc-avatar'];\n\n classes.push(`lc-avatar--${this.size()}`);\n\n if (this.status()) {\n classes.push('lc-avatar--with-status');\n }\n\n if (this.color() !== 'default') {\n classes.push('lc-avatar--member', `lc-avatar--${this.color()}`);\n }\n\n if (this.ring()) {\n classes.push('lc-avatar--ring');\n }\n\n return classes.join(' ');\n });\n\n /**\n * Get aria-label for accessibility\n */\n ariaLabel = computed(() => {\n return this.alt() || this.name() || 'Avatar';\n });\n\n /** The size's px, written as the `<img>` `width` and `height` (G-40). */\n protected readonly imagePx = computed(() => AVATAR_PX[this.size()]);\n\n /**\n * Check if image should be displayed\n */\n showImage = computed(() => {\n return this.src() && !this.imageError();\n });\n\n /**\n * Track if image failed to load. Resets whenever `src` changes so a new\n * image gets a fresh attempt instead of staying stuck on the initials.\n */\n protected imageError = linkedSignal<string | undefined, boolean>({\n source: this.src,\n computation: () => false,\n });\n\n /**\n * Handle image load error\n */\n onImageError(): void {\n this.imageError.set(true);\n }\n}\n","<div \n [class]=\"avatarClasses()\"\n [attr.role]=\"decorative() ? null : 'img'\"\n [attr.aria-label]=\"decorative() ? null : ariaLabel()\"\n [attr.aria-hidden]=\"decorative() ? 'true' : null\"\n>\n @if (showImage()) {\n <!-- `loading` before `src`: attribute bindings run in template order, after\n every property binding, so `[attr.src]` keeps the source behind\n `loading` (G-40; an engine may start the request when src is set). -->\n <img\n [alt]=\"alt() || name() || 'Avatar'\"\n class=\"lc-avatar__image\"\n [attr.width]=\"imagePx()\"\n [attr.height]=\"imagePx()\"\n [attr.loading]=\"loading() === 'lazy' ? 'lazy' : null\"\n [attr.src]=\"src()!\"\n (error)=\"onImageError()\"\n />\n } @else {\n <span class=\"lc-avatar__initials\">{{ initials() }}</span>\n }\n\n @if (status()) {\n <span \n class=\"lc-avatar__status lc-avatar__status--{{ status() }}\"\n [attr.aria-label]=\"status() + ' status'\"\n ></span>\n }\n</div>\n"],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ import { ButtonComponent } from "@lifecorner/angular-ui-kit/button";
|
|
|
7
7
|
import { LogoComponent } from "@lifecorner/angular-ui-kit/logo";
|
|
8
8
|
import { IconComponent, ɵprovideKitIcons } from "@lifecorner/angular-ui-kit/icon";
|
|
9
9
|
import { ICON_BARS_3, ICON_CHEVRON_DOWN, ICON_LOGOUT, ICON_MOON, ICON_SUN, ICON_USER } from "@lifecorner/angular-ui-kit/icons";
|
|
10
|
-
import { AvatarComponent } from "@lifecorner/angular-ui-kit/avatar";
|
|
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
13
|
var HeaderComponent = class HeaderComponent {
|
|
@@ -78,6 +78,14 @@ var HeaderComponent = class HeaderComponent {
|
|
|
78
78
|
else button.removeAttribute("aria-controls");
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
|
+
avatarTriggerAriaLabel = computed(() => {
|
|
82
|
+
const initials = ɵavatarInitials(this.userName());
|
|
83
|
+
const label = this.userMenuAriaLabel();
|
|
84
|
+
if (!/[\p{L}\p{N}]/u.test(initials)) return label;
|
|
85
|
+
if (label.replace(/\([^()]*\)/g, " ").toLowerCase().split(/[^\p{L}\p{N}]+/u).includes(initials.toLowerCase())) return label;
|
|
86
|
+
if (label.trim() === "") return initials;
|
|
87
|
+
return `${label}, ${initials}`;
|
|
88
|
+
}, ...ngDevMode ? [{ debugName: "avatarTriggerAriaLabel" }] : /* istanbul ignore next */ []);
|
|
81
89
|
menuItems = computed(() => {
|
|
82
90
|
const items = [];
|
|
83
91
|
if (this.showProfileMenuItem()) items.push({
|
|
@@ -443,7 +451,7 @@ var HeaderComponent = class HeaderComponent {
|
|
|
443
451
|
isSignal: true
|
|
444
452
|
}],
|
|
445
453
|
ngImport: i0,
|
|
446
|
-
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>\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\" />\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\" />\n </lc-button>\n }\n\n <!-- Context info (e.g. tenant, organization, project) -->\n @if (contextName()) {\n <button\n type=\"button\"\n class=\"lc-header__context-info\"\n [attr.title]=\"contextName()\"\n [attr.aria-label]=\"contextAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>\n }\n <span class=\"lc-header__context-name\">{{ 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 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]=\"
|
|
454
|
+
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>\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 type=\"button\"\n class=\"lc-header__context-info\"\n [attr.title]=\"contextName()\"\n [attr.aria-label]=\"contextAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>\n }\n <span class=\"lc-header__context-name\">{{ 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 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 [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",
|
|
447
455
|
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(--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__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(--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(--color-primary-500);outline-offset:2px}@media(max-width:640px){.lc-header__context-info{display:none}}.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(--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"],
|
|
448
456
|
dependencies: [
|
|
449
457
|
{
|
|
@@ -610,7 +618,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
610
618
|
encapsulation: ViewEncapsulation.None,
|
|
611
619
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
612
620
|
host: { class: "lc-header-host" },
|
|
613
|
-
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>\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\" />\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\" />\n </lc-button>\n }\n\n <!-- Context info (e.g. tenant, organization, project) -->\n @if (contextName()) {\n <button\n type=\"button\"\n class=\"lc-header__context-info\"\n [attr.title]=\"contextName()\"\n [attr.aria-label]=\"contextAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>\n }\n <span class=\"lc-header__context-name\">{{ 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 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]=\"
|
|
621
|
+
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>\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 type=\"button\"\n class=\"lc-header__context-info\"\n [attr.title]=\"contextName()\"\n [attr.aria-label]=\"contextAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>\n }\n <span class=\"lc-header__context-name\">{{ 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 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 [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",
|
|
614
622
|
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(--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__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(--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(--color-primary-500);outline-offset:2px}@media(max-width:640px){.lc-header__context-info{display:none}}.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(--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"]
|
|
615
623
|
}]
|
|
616
624
|
}],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-header.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/header/header.component.ts","../../../../libs/angular-ui-kit/src/lib/header/header.component.html"],"sourcesContent":["import {\n Component,\n ElementRef,\n Injector,\n input,\n output,\n signal,\n computed,\n inject,\n viewChild,\n afterRenderEffect,\n ChangeDetectionStrategy,\n ViewEncapsulation,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { RouterModule } from '@angular/router';\nimport { ButtonComponent } from '@lifecorner/angular-ui-kit/button';\nimport { LogoComponent } from '@lifecorner/angular-ui-kit/logo';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_BARS_3, ICON_CHEVRON_DOWN, ICON_LOGOUT, ICON_MOON, ICON_SUN, ICON_USER } from '@lifecorner/angular-ui-kit/icons';\nimport { AvatarColor, AvatarComponent } from '@lifecorner/angular-ui-kit/avatar';\nimport { MenuComponent, MenuItem } from '@lifecorner/angular-ui-kit/menu';\nimport { ThemeService } from '@lifecorner/angular-ui-kit/theme';\n\n/** A link in the header's main navigation (`navItems`). */\nexport interface HeaderNavItem {\n /** Visible link text. */\n label: string;\n /** Plain URL — rendered as a native `href`, no router involved. */\n href: string;\n /** Marks the page the visitor is on (`aria-current=\"page\"` + underline). */\n current?: boolean;\n}\n\n/** Element used for the header title (`titleTag`). */\nexport type HeaderTitleTag = 'h1' | 'h2' | 'h3' | 'p' | 'span' | 'div';\n\n/**\n * Surface of the header: `default` is the bar on `--color-surface` with a\n * bottom border; `transparent` has no background and no border, for a header\n * that sits on the page (\"Weite\").\n */\nexport type HeaderVariant = 'default' | 'transparent';\n\n/**\n * What the account-menu button shows: `icon` (a user icon and a chevron) or\n * `avatar` (the person's initials avatar, `userName`, `userAvatarSrc`,\n * `userAvatarColor`, in a 48 px round button).\n */\nexport type HeaderUserMenuTrigger = 'icon' | 'avatar';\n\n/**\n * AppHeaderComponent - Global application header for the LifeCorner shell\n *\n * Features:\n * - Clickable logo for home navigation\n * - Optional title and subtitle display\n * - User profile dropdown with avatar, name, email, optional Profile link, and Logout\n * - Optional theme toggle button in header. By default the button toggles the\n * `ThemeService` itself and additionally emits `themeToggleClick`, so the\n * handler must NOT toggle again (that would undo the switch). To own the\n * toggling in the app, set `[autoToggleTheme]=\"false\"` and toggle in the\n * handler.\n * - Hamburger menu toggle for mobile sidebar; bind `sidenavOpen` (and\n * `sidenavId`) so the toggle exposes `aria-expanded` / `aria-controls`.\n * - OnPush change detection for performance\n *\n * Public, prerendered sites (no router, no JavaScript needed):\n * - `homeHref` makes the brand a plain link (instead of `routerLink=\"/\"`).\n * - `navItems` render a labelled main navigation (`<nav aria-label><ul>` with\n * `aria-current=\"page\"` on the current item); below 1024 px it moves to a\n * second row and wraps.\n * - `[slot='end']` takes content at the end of the first row (e.g. a language\n * link), `showUserMenu` hides the account menu, `titleTag` picks the title\n * element (the page keeps its own `<h1>`), `sticky` can be switched off.\n * - The end content (slot, theme button, context, account menu) is one group:\n * when it doesn't fit beside the logo and the title's longest word (200 %\n * text on a phone), it takes the end of a second row instead of covering the\n * brand or widening the page (3.10.1, AS-31). The subtitle ends with \"…\".\n * App shell (\"Weite\", kit 3.3):\n * - `variant=\"transparent\"`: no background and no border, the header sits on\n * the page (pair it with `[sticky]=\"false\"`, a header without a surface\n * cannot stay over scrolling text).\n * - `userMenuTrigger=\"avatar\"`: the account button is the person's initials\n * avatar (`userAvatarColor` for the member colour, `userAvatarSrc` for a\n * photo) in a 48 px round button; the icon trigger is 44 px high in every\n * density.\n * - The subtitle (e.g. a household name) stays on one line and ends with \"…\";\n * the full text is its `title` and stays in the accessible text. It is shown\n * as typed (no uppercase).\n * - The account menu is at most `min(18rem, 100vw − 2rem)` wide; the name and\n * email in its header wrap instead of being cut.\n * - Every built-in label is an input (`homeAriaLabel`, `hamburgerAriaLabel`,\n * `userMenuAriaLabel`, `navAriaLabel`, `profileLabel`, `logoutLabel`,\n * `themeToDarkAriaLabel`, `themeToLightAriaLabel`, `contextAriaLabel`);\n * the defaults stay English.\n * - The `ThemeService` (which persists to localStorage) is only created when\n * the theme button is shown.\n *\n * @example\n * ```html\n * <lc-header\n * [logo]=\"'/assets/logo.svg'\"\n * [title]=\"'LifeCorner'\"\n * [subtitle]=\"'User Profile'\"\n * [userName]=\"'John Doe'\"\n * [userEmail]=\"'user@example.com'\"\n * [showHamburger]=\"true\"\n * [sidenavOpen]=\"sidenavOpen\"\n * sidenavId=\"app-sidenav\"\n * [showThemeButton]=\"true\"\n * [showProfileMenuItem]=\"true\"\n * (hamburgerClick)=\"toggleSidebar()\"\n * (themeToggleClick)=\"trackThemeToggle()\"\n * (profileClick)=\"navigateToProfile()\"\n * (logoutClick)=\"handleLogout()\"\n * />\n *\n * <!-- App-controlled theme: the header only reports the click -->\n * <lc-header\n * [showThemeButton]=\"true\"\n * [autoToggleTheme]=\"false\"\n * (themeToggleClick)=\"toggleTheme()\"\n * />\n *\n * <!-- Public site: plain links, labelled nav, language link at the end -->\n * <lc-header\n * size=\"sm\"\n * [sticky]=\"false\"\n * [showUserMenu]=\"false\"\n * homeHref=\"/de/\"\n * homeAriaLabel=\"Produkt – Startseite\"\n * navAriaLabel=\"Hauptnavigation\"\n * [navItems]=\"[\n * { label: 'Sicherheit', href: '/de/sicherheit', current: true },\n * { label: 'Roadmap', href: '/de/roadmap' }\n * ]\"\n * >\n * <a slot=\"end\" lcButton variant=\"ghost\" href=\"/en/security\" hreflang=\"en\" lang=\"en\">English</a>\n * </lc-header>\n * ```\n */\n@Component({\n selector: 'lc-header',\n standalone: true,\n imports: [\n NgTemplateOutlet,\n RouterModule,\n ButtonComponent,\n LogoComponent,\n IconComponent,\n AvatarComponent,\n MenuComponent,\n ],\n // Its own glyphs, and the profile menu's (lc-menu renders them in this view).\n viewProviders: [ɵprovideKitIcons([ICON_BARS_3, ICON_SUN, ICON_MOON, ICON_USER, ICON_CHEVRON_DOWN, ICON_LOGOUT])],\n templateUrl: './header.component.html',\n styleUrl: './header.component.scss',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'lc-header-host',\n },\n})\nexport class HeaderComponent {\n readonly theme = input<'light' | 'dark' | 'auto'>('auto');\n readonly logo = input('');\n /**\n * Render the brand block. On its own this shows the emblem; with `logo` set\n * it shows the full lockup. (It used to additionally require `logo` or\n * `title`, so `showLogo` alone rendered an empty 0px-wide brand area with no\n * error — an app could end up with no branding at all and no clue why.)\n */\n readonly showLogo = input(true);\n /** Custom URL for the full brand logo (forwarded to inner `<lc-logo>`). */\n readonly logoSrc = input('');\n /** Custom URL for the emblem-only brand logo. */\n readonly logoEmblemSrc = input('');\n /** Optional dark-theme URL for the full brand logo. */\n readonly logoDarkSrc = input('');\n /** Optional dark-theme URL for the emblem-only brand logo. */\n readonly logoDarkEmblemSrc = input('');\n /** Alt text for the brand logo (forwarded to inner `<lc-logo>`). */\n readonly logoAlt = input('');\n /** Size of the brand logo (forwarded to inner `<lc-logo>`). Defaults to `md`. */\n readonly logoSize = input<'xs' | 'sm' | 'md' | 'lg' | 'xl'>('md');\n /**\n * Overall header height. Useful to align the header with a sidebar brand block.\n * Maps to the same `min-height` scale the sidenav logo area uses:\n * `sm` 56px · `md` 64px (default) · `lg` 80px · `xl` 112px.\n */\n readonly size = input<'sm' | 'md' | 'lg' | 'xl'>('md');\n readonly title = input('');\n /**\n * Surface: `default` (the bar on `--color-surface` with a bottom border) or\n * `transparent` (no background, no border — the header sits on the page).\n */\n readonly variant = input<HeaderVariant>('default');\n /**\n * Element for the title. `h1` keeps the app-shell behaviour; on a site whose\n * pages carry their own `<h1>`, use `span` (or `p`/`div`).\n */\n readonly titleTag = input<HeaderTitleTag>('h1');\n /**\n * Second line under the title (e.g. the household name). One line, cut\n * with \"…\" when it does not fit; the full text is its `title` attribute.\n * Shown as typed.\n */\n readonly subtitle = input('');\n readonly userEmail = input('');\n readonly userName = input('');\n /**\n * What the account-menu button shows: `icon` (user icon and chevron) or\n * `avatar` (the initials of `userName` in a 48 px round button).\n */\n readonly userMenuTrigger = input<HeaderUserMenuTrigger>('icon');\n /** Photo for the account avatar (trigger and menu header); initials when empty or broken. */\n readonly userAvatarSrc = input('');\n /** Colour of the account avatar's initials: `default` or a member colour `member-1` … `member-6`. */\n readonly userAvatarColor = input<AvatarColor>('default');\n readonly showHamburger = input(false);\n /**\n * Open state of the sidenav the hamburger controls. When bound, the toggle\n * announces it via `aria-expanded`; leave `undefined` when the header does\n * not know (the attribute is then omitted rather than lying).\n */\n readonly sidenavOpen = input<boolean | undefined>(undefined);\n /** DOM id of the sidenav the hamburger controls (`aria-controls`). */\n readonly sidenavId = input('');\n readonly showThemeButton = input(false);\n /**\n * Whether the theme button toggles the `ThemeService` itself before emitting\n * `themeToggleClick`. Set to `false` when the app owns the theme switch and\n * toggles in the `(themeToggleClick)` handler — otherwise both would toggle\n * and the click would be a no-op.\n */\n readonly autoToggleTheme = input(true);\n readonly contextName = input('');\n readonly contextLabel = input('');\n readonly menuSize = input<'sm' | 'md' | 'lg'>('sm');\n readonly showProfileMenuItem = input(true);\n /** Render the account menu (avatar trigger with Profile/Logout). */\n readonly showUserMenu = input(true);\n /**\n * Plain URL for the brand link. When set, the brand is a native\n * `<a href>` — no router needed (static or prerendered sites). Empty keeps\n * `routerLink=\"/\"`.\n */\n readonly homeHref = input('');\n /**\n * Main navigation links, rendered as `<nav><ul><li><a href>` with\n * `aria-current=\"page\"` on the current one. On screens below 1024 px the\n * navigation takes a second row and wraps.\n */\n readonly navItems = input<readonly HeaderNavItem[]>([]);\n /** Keep the header at the top while scrolling (`position: sticky`). */\n readonly sticky = input(true);\n\n // Labels (defaults are English; translate them in the app).\n /** Accessible name of the brand link. */\n readonly homeAriaLabel = input('Go to home');\n /** Accessible name of the hamburger button. */\n readonly hamburgerAriaLabel = input('Toggle sidebar menu');\n /** Accessible name of the account menu trigger. */\n readonly userMenuAriaLabel = input('Open user menu');\n /** Accessible name of the `<nav>` around `navItems`. */\n readonly navAriaLabel = input('Main navigation');\n /** Label of the theme button while the light theme is active. */\n readonly themeToDarkAriaLabel = input('Switch to dark mode');\n /** Label of the theme button while the dark theme is active. */\n readonly themeToLightAriaLabel = input('Switch to light mode');\n /** Accessible name of the context button (`contextName`). */\n readonly contextAriaLabel = input('Context info');\n /** Label of the Profile entry in the account menu. */\n readonly profileLabel = input('Profile');\n /** Label of the Logout entry in the account menu. */\n readonly logoutLabel = input('Logout');\n\n readonly hamburgerClick = output<void>();\n readonly themeToggleClick = output<void>();\n readonly logoutClick = output<void>();\n readonly profileClick = output<void>();\n readonly contextClick = output<void>();\n\n private readonly injector = inject(Injector);\n private themeServiceInstance: ThemeService | null = null;\n\n /**\n * Resolved on first use, i.e. only when the theme button is shown: creating\n * the ThemeService reads and writes the theme class and localStorage, which a\n * site without a theme switch must not do.\n */\n protected get themeService(): ThemeService {\n return (this.themeServiceInstance ??= this.injector.get(ThemeService));\n }\n\n private readonly hamburger = viewChild('hamburger', { read: ElementRef<HTMLElement> });\n\n constructor() {\n // The hamburger is an <lc-button>; the ARIA state belongs on its native\n // <button> (the element that has the button role), not on the custom\n // element host — so it is reflected after render rather than bound in the\n // template.\n afterRenderEffect(() => {\n const button = this.hamburger()?.nativeElement.querySelector('button');\n if (!button) return;\n const open = this.sidenavOpen();\n const controls = this.sidenavId();\n if (open === undefined) {\n button.removeAttribute('aria-expanded');\n } else {\n button.setAttribute('aria-expanded', String(open));\n }\n if (controls) {\n button.setAttribute('aria-controls', controls);\n } else {\n button.removeAttribute('aria-controls');\n }\n });\n }\n\n /**\n * Get menu items for profile dropdown\n */\n protected readonly menuItems = computed<MenuItem[]>(() => {\n const items: MenuItem[] = [];\n\n // Add profile link if enabled\n if (this.showProfileMenuItem()) {\n items.push({\n id: 'profile',\n label: this.profileLabel(),\n icon: 'user',\n dividerAfter: true,\n });\n }\n\n // Add logout\n items.push({\n id: 'logout',\n label: this.logoutLabel(),\n icon: 'arrow-right-start-on-rectangle',\n variant: 'danger',\n });\n\n return items;\n });\n\n /**\n * Dropdown open state (using Angular signals)\n */\n private readonly dropdownOpen = signal<boolean>(false);\n\n /**\n * Get dropdown open state\n */\n isDropdownOpen(): boolean {\n return this.dropdownOpen();\n }\n\n /**\n * Toggle profile dropdown visibility\n */\n toggleDropdown(): void {\n this.dropdownOpen.set(!this.dropdownOpen());\n }\n\n /**\n * Close profile dropdown\n */\n closeDropdown(): void {\n this.dropdownOpen.set(false);\n }\n\n /**\n * Handle hamburger click\n */\n onHamburgerClick(): void {\n this.hamburgerClick.emit();\n }\n\n /**\n * Handle theme button click (separate button, not in menu). Toggles the\n * theme unless the app opted out via `autoToggleTheme`, then emits.\n */\n onThemeButtonClick(): void {\n if (this.autoToggleTheme()) {\n this.themeService.toggleTheme();\n }\n this.themeToggleClick.emit();\n }\n\n /**\n * Handle logout click\n */\n onLogoutClick(): void {\n this.logoutClick.emit();\n this.closeDropdown();\n }\n\n /**\n * Handle profile click\n */\n onProfileClick(): void {\n this.profileClick.emit();\n this.closeDropdown();\n }\n\n /**\n * Handle menu item click\n */\n onMenuItemClick(item: MenuItem): void {\n if (item.id === 'logout') {\n this.onLogoutClick();\n } else if (item.id === 'profile') {\n this.onProfileClick();\n }\n }\n}\n","<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>\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\" />\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\" />\n </lc-button>\n }\n\n <!-- Context info (e.g. tenant, organization, project) -->\n @if (contextName()) {\n <button\n type=\"button\"\n class=\"lc-header__context-info\"\n [attr.title]=\"contextName()\"\n [attr.aria-label]=\"contextAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>\n }\n <span class=\"lc-header__context-name\">{{ 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 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]=\"userMenuAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\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\" />\n <lc-icon name=\"chevron-down\" size=\"xs\" />\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-header.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/header/header.component.ts","../../../../libs/angular-ui-kit/src/lib/header/header.component.html"],"sourcesContent":["import {\n Component,\n ElementRef,\n Injector,\n input,\n output,\n signal,\n computed,\n inject,\n viewChild,\n afterRenderEffect,\n ChangeDetectionStrategy,\n ViewEncapsulation,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { RouterModule } from '@angular/router';\nimport { ButtonComponent } from '@lifecorner/angular-ui-kit/button';\nimport { LogoComponent } from '@lifecorner/angular-ui-kit/logo';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_BARS_3, ICON_CHEVRON_DOWN, ICON_LOGOUT, ICON_MOON, ICON_SUN, ICON_USER } from '@lifecorner/angular-ui-kit/icons';\nimport { AvatarColor, AvatarComponent, ɵavatarInitials } from '@lifecorner/angular-ui-kit/avatar';\nimport { MenuComponent, MenuItem } from '@lifecorner/angular-ui-kit/menu';\nimport { ThemeService } from '@lifecorner/angular-ui-kit/theme';\n\n/** A link in the header's main navigation (`navItems`). */\nexport interface HeaderNavItem {\n /** Visible link text. */\n label: string;\n /** Plain URL — rendered as a native `href`, no router involved. */\n href: string;\n /** Marks the page the visitor is on (`aria-current=\"page\"` + underline). */\n current?: boolean;\n}\n\n/** Element used for the header title (`titleTag`). */\nexport type HeaderTitleTag = 'h1' | 'h2' | 'h3' | 'p' | 'span' | 'div';\n\n/**\n * Surface of the header: `default` is the bar on `--color-surface` with a\n * bottom border; `transparent` has no background and no border, for a header\n * that sits on the page (\"Weite\").\n */\nexport type HeaderVariant = 'default' | 'transparent';\n\n/**\n * What the account-menu button shows: `icon` (a user icon and a chevron) or\n * `avatar` (the person's initials avatar, `userName`, `userAvatarSrc`,\n * `userAvatarColor`, in a 48 px round button).\n */\nexport type HeaderUserMenuTrigger = 'icon' | 'avatar';\n\n/**\n * AppHeaderComponent - Global application header for the LifeCorner shell\n *\n * Features:\n * - Clickable logo for home navigation\n * - Optional title and subtitle display\n * - User profile dropdown with avatar, name, email, optional Profile link, and Logout\n * - Optional theme toggle button in header. By default the button toggles the\n * `ThemeService` itself and additionally emits `themeToggleClick`, so the\n * handler must NOT toggle again (that would undo the switch). To own the\n * toggling in the app, set `[autoToggleTheme]=\"false\"` and toggle in the\n * handler.\n * - Hamburger menu toggle for mobile sidebar; bind `sidenavOpen` (and\n * `sidenavId`) so the toggle exposes `aria-expanded` / `aria-controls`.\n * - OnPush change detection for performance\n *\n * Public, prerendered sites (no router, no JavaScript needed):\n * - `homeHref` makes the brand a plain link (instead of `routerLink=\"/\"`).\n * - `navItems` render a labelled main navigation (`<nav aria-label><ul>` with\n * `aria-current=\"page\"` on the current item); below 1024 px it moves to a\n * second row and wraps.\n * - `[slot='end']` takes content at the end of the first row (e.g. a language\n * link), `showUserMenu` hides the account menu, `titleTag` picks the title\n * element (the page keeps its own `<h1>`), `sticky` can be switched off.\n * - The end content (slot, theme button, context, account menu) is one group:\n * when it doesn't fit beside the logo and the title's longest word (200 %\n * text on a phone), it takes the end of a second row instead of covering the\n * brand or widening the page (3.10.1, AS-31). The subtitle ends with \"…\".\n * App shell (\"Weite\", kit 3.3):\n * - `variant=\"transparent\"`: no background and no border, the header sits on\n * the page (pair it with `[sticky]=\"false\"`, a header without a surface\n * cannot stay over scrolling text).\n * - `userMenuTrigger=\"avatar\"`: the account button is the person's initials\n * avatar (`userAvatarColor` for the member colour, `userAvatarSrc` for a\n * photo) in a 48 px round button; the icon trigger is 44 px high in every\n * density.\n * - The subtitle (e.g. a household name) stays on one line and ends with \"…\";\n * the full text is its `title` and stays in the accessible text. It is shown\n * as typed (no uppercase).\n * - The account menu is at most `min(18rem, 100vw − 2rem)` wide; the name and\n * email in its header wrap instead of being cut.\n * - Every built-in label is an input (`homeAriaLabel`, `hamburgerAriaLabel`,\n * `userMenuAriaLabel`, `navAriaLabel`, `profileLabel`, `logoutLabel`,\n * `themeToDarkAriaLabel`, `themeToLightAriaLabel`, `contextAriaLabel`);\n * the defaults stay English.\n * - The `ThemeService` (which persists to localStorage) is only created when\n * the theme button is shown.\n *\n * @example\n * ```html\n * <lc-header\n * [logo]=\"'/assets/logo.svg'\"\n * [title]=\"'LifeCorner'\"\n * [subtitle]=\"'User Profile'\"\n * [userName]=\"'John Doe'\"\n * [userEmail]=\"'user@example.com'\"\n * [showHamburger]=\"true\"\n * [sidenavOpen]=\"sidenavOpen\"\n * sidenavId=\"app-sidenav\"\n * [showThemeButton]=\"true\"\n * [showProfileMenuItem]=\"true\"\n * (hamburgerClick)=\"toggleSidebar()\"\n * (themeToggleClick)=\"trackThemeToggle()\"\n * (profileClick)=\"navigateToProfile()\"\n * (logoutClick)=\"handleLogout()\"\n * />\n *\n * <!-- App-controlled theme: the header only reports the click -->\n * <lc-header\n * [showThemeButton]=\"true\"\n * [autoToggleTheme]=\"false\"\n * (themeToggleClick)=\"toggleTheme()\"\n * />\n *\n * <!-- Public site: plain links, labelled nav, language link at the end -->\n * <lc-header\n * size=\"sm\"\n * [sticky]=\"false\"\n * [showUserMenu]=\"false\"\n * homeHref=\"/de/\"\n * homeAriaLabel=\"Produkt – Startseite\"\n * navAriaLabel=\"Hauptnavigation\"\n * [navItems]=\"[\n * { label: 'Sicherheit', href: '/de/sicherheit', current: true },\n * { label: 'Roadmap', href: '/de/roadmap' }\n * ]\"\n * >\n * <a slot=\"end\" lcButton variant=\"ghost\" href=\"/en/security\" hreflang=\"en\" lang=\"en\">English</a>\n * </lc-header>\n * ```\n */\n@Component({\n selector: 'lc-header',\n standalone: true,\n imports: [\n NgTemplateOutlet,\n RouterModule,\n ButtonComponent,\n LogoComponent,\n IconComponent,\n AvatarComponent,\n MenuComponent,\n ],\n // Its own glyphs, and the profile menu's (lc-menu renders them in this view).\n viewProviders: [ɵprovideKitIcons([ICON_BARS_3, ICON_SUN, ICON_MOON, ICON_USER, ICON_CHEVRON_DOWN, ICON_LOGOUT])],\n templateUrl: './header.component.html',\n styleUrl: './header.component.scss',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'lc-header-host',\n },\n})\nexport class HeaderComponent {\n readonly theme = input<'light' | 'dark' | 'auto'>('auto');\n readonly logo = input('');\n /**\n * Render the brand block. On its own this shows the emblem; with `logo` set\n * it shows the full lockup. (It used to additionally require `logo` or\n * `title`, so `showLogo` alone rendered an empty 0px-wide brand area with no\n * error — an app could end up with no branding at all and no clue why.)\n */\n readonly showLogo = input(true);\n /** Custom URL for the full brand logo (forwarded to inner `<lc-logo>`). */\n readonly logoSrc = input('');\n /** Custom URL for the emblem-only brand logo. */\n readonly logoEmblemSrc = input('');\n /** Optional dark-theme URL for the full brand logo. */\n readonly logoDarkSrc = input('');\n /** Optional dark-theme URL for the emblem-only brand logo. */\n readonly logoDarkEmblemSrc = input('');\n /** Alt text for the brand logo (forwarded to inner `<lc-logo>`). */\n readonly logoAlt = input('');\n /** Size of the brand logo (forwarded to inner `<lc-logo>`). Defaults to `md`. */\n readonly logoSize = input<'xs' | 'sm' | 'md' | 'lg' | 'xl'>('md');\n /**\n * Overall header height. Useful to align the header with a sidebar brand block.\n * Maps to the same `min-height` scale the sidenav logo area uses:\n * `sm` 56px · `md` 64px (default) · `lg` 80px · `xl` 112px.\n */\n readonly size = input<'sm' | 'md' | 'lg' | 'xl'>('md');\n readonly title = input('');\n /**\n * Surface: `default` (the bar on `--color-surface` with a bottom border) or\n * `transparent` (no background, no border — the header sits on the page).\n */\n readonly variant = input<HeaderVariant>('default');\n /**\n * Element for the title. `h1` keeps the app-shell behaviour; on a site whose\n * pages carry their own `<h1>`, use `span` (or `p`/`div`).\n */\n readonly titleTag = input<HeaderTitleTag>('h1');\n /**\n * Second line under the title (e.g. the household name). One line, cut\n * with \"…\" when it does not fit; the full text is its `title` attribute.\n * Shown as typed.\n */\n readonly subtitle = input('');\n readonly userEmail = input('');\n readonly userName = input('');\n /**\n * What the account-menu button shows: `icon` (user icon and chevron) or\n * `avatar` (the initials of `userName` in a 48 px round button; the button\n * is named `userMenuAriaLabel`, a comma and the initials).\n * @default 'icon'\n */\n readonly userMenuTrigger = input<HeaderUserMenuTrigger>('icon');\n /** Photo for the account avatar (trigger and menu header); initials when empty or broken. */\n readonly userAvatarSrc = input('');\n /** Colour of the account avatar's initials: `default` or a member colour `member-1` … `member-6`. */\n readonly userAvatarColor = input<AvatarColor>('default');\n readonly showHamburger = input(false);\n /**\n * Open state of the sidenav the hamburger controls. When bound, the toggle\n * announces it via `aria-expanded`; leave `undefined` when the header does\n * not know (the attribute is then omitted rather than lying).\n */\n readonly sidenavOpen = input<boolean | undefined>(undefined);\n /** DOM id of the sidenav the hamburger controls (`aria-controls`). */\n readonly sidenavId = input('');\n readonly showThemeButton = input(false);\n /**\n * Whether the theme button toggles the `ThemeService` itself before emitting\n * `themeToggleClick`. Set to `false` when the app owns the theme switch and\n * toggles in the `(themeToggleClick)` handler — otherwise both would toggle\n * and the click would be a no-op.\n */\n readonly autoToggleTheme = input(true);\n readonly contextName = input('');\n readonly contextLabel = input('');\n readonly menuSize = input<'sm' | 'md' | 'lg'>('sm');\n readonly showProfileMenuItem = input(true);\n /** Render the account menu (avatar trigger with Profile/Logout). */\n readonly showUserMenu = input(true);\n /**\n * Plain URL for the brand link. When set, the brand is a native\n * `<a href>` — no router needed (static or prerendered sites). Empty keeps\n * `routerLink=\"/\"`.\n */\n readonly homeHref = input('');\n /**\n * Main navigation links, rendered as `<nav><ul><li><a href>` with\n * `aria-current=\"page\"` on the current one. On screens below 1024 px the\n * navigation takes a second row and wraps.\n */\n readonly navItems = input<readonly HeaderNavItem[]>([]);\n /** Keep the header at the top while scrolling (`position: sticky`). */\n readonly sticky = input(true);\n\n // Labels (defaults are English; translate them in the app).\n /** Accessible name of the brand link. */\n readonly homeAriaLabel = input('Go to home');\n /** Accessible name of the hamburger button. */\n readonly hamburgerAriaLabel = input('Toggle sidebar menu');\n /**\n * Accessible name of the account menu trigger. With `userMenuTrigger=\"avatar\"`\n * the button is named this text, a comma and the initials it shows\n * (`Menü für Johanna Müller, JM`), so a voice-control user can say what they\n * see (WCAG 2.5.3). Nothing is added when `userName` gives no initials, or\n * when the text already contains them as a word. The name is the same with a\n * photo.\n * @default 'Open user menu'\n */\n readonly userMenuAriaLabel = input('Open user menu');\n /** Accessible name of the `<nav>` around `navItems`. */\n readonly navAriaLabel = input('Main navigation');\n /** Label of the theme button while the light theme is active. */\n readonly themeToDarkAriaLabel = input('Switch to dark mode');\n /** Label of the theme button while the dark theme is active. */\n readonly themeToLightAriaLabel = input('Switch to light mode');\n /** Accessible name of the context button (`contextName`). */\n readonly contextAriaLabel = input('Context info');\n /** Label of the Profile entry in the account menu. */\n readonly profileLabel = input('Profile');\n /** Label of the Logout entry in the account menu. */\n readonly logoutLabel = input('Logout');\n\n readonly hamburgerClick = output<void>();\n readonly themeToggleClick = output<void>();\n readonly logoutClick = output<void>();\n readonly profileClick = output<void>();\n readonly contextClick = output<void>();\n\n private readonly injector = inject(Injector);\n private themeServiceInstance: ThemeService | null = null;\n\n /**\n * Resolved on first use, i.e. only when the theme button is shown: creating\n * the ThemeService reads and writes the theme class and localStorage, which a\n * site without a theme switch must not do.\n */\n protected get themeService(): ThemeService {\n return (this.themeServiceInstance ??= this.injector.get(ThemeService));\n }\n\n private readonly hamburger = viewChild('hamburger', { read: ElementRef<HTMLElement> });\n\n constructor() {\n // The hamburger is an <lc-button>; the ARIA state belongs on its native\n // <button> (the element that has the button role), not on the custom\n // element host — so it is reflected after render rather than bound in the\n // template.\n afterRenderEffect(() => {\n const button = this.hamburger()?.nativeElement.querySelector('button');\n if (!button) return;\n const open = this.sidenavOpen();\n const controls = this.sidenavId();\n if (open === undefined) {\n button.removeAttribute('aria-expanded');\n } else {\n button.setAttribute('aria-expanded', String(open));\n }\n if (controls) {\n button.setAttribute('aria-controls', controls);\n } else {\n button.removeAttribute('aria-controls');\n }\n });\n }\n\n /**\n * Name of the avatar trigger (AS-33, WCAG 2.5.3 Label in Name):\n * `userMenuAriaLabel`, a comma and the initials the avatar shows, so the\n * visible text is a word of the name. Built from the same function as the\n * avatar's initials and independent of the photo, so the prerendered name\n * and the page's agree. Nothing is added for the `?` placeholder or when\n * the label already holds the initials as a word (parentheticals dropped,\n * as axe-core 4.14 does); an empty label becomes the initials alone.\n */\n protected readonly avatarTriggerAriaLabel = computed(() => {\n const initials = ɵavatarInitials(this.userName());\n const label = this.userMenuAriaLabel();\n if (!/[\\p{L}\\p{N}]/u.test(initials)) return label;\n // toLowerCase, not toLocaleLowerCase: server and browser must agree.\n const words = label\n .replace(/\\([^()]*\\)/g, ' ')\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u);\n if (words.includes(initials.toLowerCase())) return label;\n if (label.trim() === '') return initials;\n return `${label}, ${initials}`;\n });\n\n /**\n * Get menu items for profile dropdown\n */\n protected readonly menuItems = computed<MenuItem[]>(() => {\n const items: MenuItem[] = [];\n\n // Add profile link if enabled\n if (this.showProfileMenuItem()) {\n items.push({\n id: 'profile',\n label: this.profileLabel(),\n icon: 'user',\n dividerAfter: true,\n });\n }\n\n // Add logout\n items.push({\n id: 'logout',\n label: this.logoutLabel(),\n icon: 'arrow-right-start-on-rectangle',\n variant: 'danger',\n });\n\n return items;\n });\n\n /**\n * Dropdown open state (using Angular signals)\n */\n private readonly dropdownOpen = signal<boolean>(false);\n\n /**\n * Get dropdown open state\n */\n isDropdownOpen(): boolean {\n return this.dropdownOpen();\n }\n\n /**\n * Toggle profile dropdown visibility\n */\n toggleDropdown(): void {\n this.dropdownOpen.set(!this.dropdownOpen());\n }\n\n /**\n * Close profile dropdown\n */\n closeDropdown(): void {\n this.dropdownOpen.set(false);\n }\n\n /**\n * Handle hamburger click\n */\n onHamburgerClick(): void {\n this.hamburgerClick.emit();\n }\n\n /**\n * Handle theme button click (separate button, not in menu). Toggles the\n * theme unless the app opted out via `autoToggleTheme`, then emits.\n */\n onThemeButtonClick(): void {\n if (this.autoToggleTheme()) {\n this.themeService.toggleTheme();\n }\n this.themeToggleClick.emit();\n }\n\n /**\n * Handle logout click\n */\n onLogoutClick(): void {\n this.logoutClick.emit();\n this.closeDropdown();\n }\n\n /**\n * Handle profile click\n */\n onProfileClick(): void {\n this.profileClick.emit();\n this.closeDropdown();\n }\n\n /**\n * Handle menu item click\n */\n onMenuItemClick(item: MenuItem): void {\n if (item.id === 'logout') {\n this.onLogoutClick();\n } else if (item.id === 'profile') {\n this.onProfileClick();\n }\n }\n}\n","<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>\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 type=\"button\"\n class=\"lc-header__context-info\"\n [attr.title]=\"contextName()\"\n [attr.aria-label]=\"contextAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>\n }\n <span class=\"lc-header__context-name\">{{ 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 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 [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"],"mappings":"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"}
|
|
@@ -209,7 +209,7 @@ var MenuComponent = class MenuComponent {
|
|
|
209
209
|
isSignal: true
|
|
210
210
|
}],
|
|
211
211
|
ngImport: i0,
|
|
212
|
-
template: "<div class=\"lc-menu\">\n <!-- Trigger slot -->\n <div class=\"lc-menu__trigger\">\n <ng-content select=\"[trigger]\" />\n </div>\n\n <!-- Dropdown menu -->\n @if (isOpen()) {\n <div\n class=\"lc-menu__dropdown\"\n [class.lc-menu__dropdown--bottom-left]=\"position() === 'bottom-left'\"\n [class.lc-menu__dropdown--bottom-right]=\"position() === 'bottom-right'\"\n [class.lc-menu__dropdown--top-left]=\"position() === 'top-left'\"\n [class.lc-menu__dropdown--top-right]=\"position() === 'top-right'\"\n [class.lc-menu__dropdown--sm]=\"size() === 'sm'\"\n [class.lc-menu__dropdown--lg]=\"size() === 'lg'\"\n [style.min-width]=\"minWidth()\"\n [style.max-width]=\"maxWidth() || null\"\n [class.lc-menu__dropdown--capped]=\"!!maxWidth()\"\n >\n <!-- Optional header slot (outside the menu role: it is not an item) -->\n <ng-content select=\"[header]\" />\n\n <!-- Menu items -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- keydown bubbles from the focused menu items (roving tabindex) -->\n <div\n class=\"lc-menu__items\"\n role=\"menu\"\n [id]=\"panelId\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n @for (item of items(); track item.id; let i = $index) {\n @if (item.href) {\n <!-- Link item -->\n <a\n #menuItem\n role=\"menuitem\"\n [href]=\"item.href\"\n [target]=\"item.target || '_self'\"\n [attr.rel]=\"item.target === '_blank' ? 'noopener noreferrer' : null\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </a>\n } @else {\n <!-- Button item -->\n <button\n #menuItem\n type=\"button\"\n role=\"menuitem\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </button>\n }\n\n @if (item.dividerAfter) {\n <hr class=\"lc-menu__divider\" />\n }\n }\n </div>\n\n <!-- Optional footer slot -->\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n",
|
|
212
|
+
template: "<div class=\"lc-menu\">\n <!-- Trigger slot -->\n <div class=\"lc-menu__trigger\">\n <ng-content select=\"[trigger]\" />\n </div>\n\n <!-- Dropdown menu -->\n @if (isOpen()) {\n <div\n class=\"lc-menu__dropdown\"\n [class.lc-menu__dropdown--bottom-left]=\"position() === 'bottom-left'\"\n [class.lc-menu__dropdown--bottom-right]=\"position() === 'bottom-right'\"\n [class.lc-menu__dropdown--top-left]=\"position() === 'top-left'\"\n [class.lc-menu__dropdown--top-right]=\"position() === 'top-right'\"\n [class.lc-menu__dropdown--sm]=\"size() === 'sm'\"\n [class.lc-menu__dropdown--lg]=\"size() === 'lg'\"\n [style.min-width]=\"minWidth()\"\n [style.max-width]=\"maxWidth() || null\"\n [class.lc-menu__dropdown--capped]=\"!!maxWidth()\"\n >\n <!-- Optional header slot (outside the menu role: it is not an item) -->\n <ng-content select=\"[header]\" />\n\n <!-- Menu items -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- keydown bubbles from the focused menu items (roving tabindex) -->\n <div\n class=\"lc-menu__items\"\n role=\"menu\"\n [id]=\"panelId\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n @for (item of items(); track item.id; let i = $index) {\n @if (item.href) {\n <!-- Link item -->\n <a\n #menuItem\n role=\"menuitem\"\n [href]=\"item.href\"\n [target]=\"item.target || '_self'\"\n [attr.rel]=\"item.target === '_blank' ? 'noopener noreferrer' : null\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" [decorative]=\"true\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </a>\n } @else {\n <!-- Button item -->\n <button\n #menuItem\n type=\"button\"\n role=\"menuitem\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" [decorative]=\"true\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </button>\n }\n\n @if (item.dividerAfter) {\n <hr class=\"lc-menu__divider\" />\n }\n }\n </div>\n\n <!-- Optional footer slot -->\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n",
|
|
213
213
|
styles: [".lc-menu{position:relative;display:inline-block}.lc-menu__trigger{display:inline-block}.lc-menu__dropdown{position:absolute;background-color:var(--color-surface);background-image:var(--gradient-surface);border:1px solid var(--color-border-strong);border-radius:var(--border-radius-lg, .5rem);box-shadow:var(--elevation-4);padding:var(--lc-density-padding-xs, var(--spacing-2)) 0;z-index:9999;animation:slideDown .15s ease-out}.lc-menu__dropdown--bottom-left{top:calc(100% + var(--spacing-2));left:0}.lc-menu__dropdown--bottom-right{top:calc(100% + var(--spacing-2));right:0}.lc-menu__dropdown--top-left{bottom:calc(100% + var(--spacing-2));left:0}.lc-menu__dropdown--top-right{bottom:calc(100% + var(--spacing-2));right:0}.lc-menu__dropdown--capped{width:max-content}.lc-menu__item{display:flex;align-items:center;gap:var(--spacing-3);width:100%;padding:var(--lc-density-padding-sm, var(--spacing-3)) var(--lc-density-padding-md, var(--spacing-4));background:transparent;border:none;text-align:left;text-decoration:none;color:var(--color-text-primary);font-size:var(--font-size-base);font-family:var(--font-family-base);cursor:pointer;transition:background-color .2s ease}.lc-menu__item:hover:not(.lc-menu__item--disabled){background-color:var(--color-surface-hover)}.lc-menu__item:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px;background-color:var(--color-surface-hover)}.lc-menu__item--danger{color:var(--color-error)}.lc-menu__item--danger:hover:not(.lc-menu__item--danger--disabled){background-color:#9d0e0e24}.lc-menu__item--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.lc-menu__item-content{display:flex;flex-direction:column;gap:var(--lc-density-gap-xs, var(--spacing-1));flex:1}.lc-menu__item-label{font-size:var(--font-size-base);font-weight:var(--font-weight-medium);line-height:var(--line-height-tight)}.lc-menu__item-subtitle{font-size:var(--font-size-sm);color:var(--color-text-secondary);line-height:var(--line-height-tight)}.lc-menu__item-badge{padding:var(--spacing-1) var(--spacing-2);background-color:var(--color-primary-100);color:var(--color-primary-700);font-size:var(--font-size-xs);font-weight:var(--font-weight-semibold);border-radius:var(--border-radius-full);line-height:1}.lc-menu__divider{margin:var(--spacing-2) 0;border:none;border-top:1px solid var(--color-divider)}.lc-menu__dropdown--sm{padding:var(--spacing-1, .25rem) 0}.lc-menu__dropdown--sm .lc-menu__item{gap:var(--lc-density-gap-sm, var(--spacing-2));padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));font-size:var(--font-size-sm)}.lc-menu__dropdown--sm .lc-menu__item-label{font-size:var(--font-size-sm)}.lc-menu__dropdown--sm .lc-menu__item-subtitle{font-size:var(--font-size-xs)}.lc-menu__dropdown--sm .lc-menu__item-badge{font-size:.625rem;padding:1px var(--spacing-1)}.lc-menu__dropdown--sm .lc-menu__divider{margin:var(--spacing-1) 0}.lc-menu__dropdown--lg .lc-menu__item{gap:var(--lc-density-gap-md, var(--spacing-4));padding:var(--lc-density-padding-md, var(--spacing-4)) var(--spacing-5);font-size:var(--font-size-lg)}.lc-menu__dropdown--lg .lc-menu__item-label{font-size:var(--font-size-lg)}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}\n"],
|
|
214
214
|
dependencies: [{
|
|
215
215
|
kind: "component",
|
|
@@ -240,7 +240,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
240
240
|
standalone: true,
|
|
241
241
|
imports: [IconComponent],
|
|
242
242
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
243
|
-
template: "<div class=\"lc-menu\">\n <!-- Trigger slot -->\n <div class=\"lc-menu__trigger\">\n <ng-content select=\"[trigger]\" />\n </div>\n\n <!-- Dropdown menu -->\n @if (isOpen()) {\n <div\n class=\"lc-menu__dropdown\"\n [class.lc-menu__dropdown--bottom-left]=\"position() === 'bottom-left'\"\n [class.lc-menu__dropdown--bottom-right]=\"position() === 'bottom-right'\"\n [class.lc-menu__dropdown--top-left]=\"position() === 'top-left'\"\n [class.lc-menu__dropdown--top-right]=\"position() === 'top-right'\"\n [class.lc-menu__dropdown--sm]=\"size() === 'sm'\"\n [class.lc-menu__dropdown--lg]=\"size() === 'lg'\"\n [style.min-width]=\"minWidth()\"\n [style.max-width]=\"maxWidth() || null\"\n [class.lc-menu__dropdown--capped]=\"!!maxWidth()\"\n >\n <!-- Optional header slot (outside the menu role: it is not an item) -->\n <ng-content select=\"[header]\" />\n\n <!-- Menu items -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- keydown bubbles from the focused menu items (roving tabindex) -->\n <div\n class=\"lc-menu__items\"\n role=\"menu\"\n [id]=\"panelId\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n @for (item of items(); track item.id; let i = $index) {\n @if (item.href) {\n <!-- Link item -->\n <a\n #menuItem\n role=\"menuitem\"\n [href]=\"item.href\"\n [target]=\"item.target || '_self'\"\n [attr.rel]=\"item.target === '_blank' ? 'noopener noreferrer' : null\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </a>\n } @else {\n <!-- Button item -->\n <button\n #menuItem\n type=\"button\"\n role=\"menuitem\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </button>\n }\n\n @if (item.dividerAfter) {\n <hr class=\"lc-menu__divider\" />\n }\n }\n </div>\n\n <!-- Optional footer slot -->\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n",
|
|
243
|
+
template: "<div class=\"lc-menu\">\n <!-- Trigger slot -->\n <div class=\"lc-menu__trigger\">\n <ng-content select=\"[trigger]\" />\n </div>\n\n <!-- Dropdown menu -->\n @if (isOpen()) {\n <div\n class=\"lc-menu__dropdown\"\n [class.lc-menu__dropdown--bottom-left]=\"position() === 'bottom-left'\"\n [class.lc-menu__dropdown--bottom-right]=\"position() === 'bottom-right'\"\n [class.lc-menu__dropdown--top-left]=\"position() === 'top-left'\"\n [class.lc-menu__dropdown--top-right]=\"position() === 'top-right'\"\n [class.lc-menu__dropdown--sm]=\"size() === 'sm'\"\n [class.lc-menu__dropdown--lg]=\"size() === 'lg'\"\n [style.min-width]=\"minWidth()\"\n [style.max-width]=\"maxWidth() || null\"\n [class.lc-menu__dropdown--capped]=\"!!maxWidth()\"\n >\n <!-- Optional header slot (outside the menu role: it is not an item) -->\n <ng-content select=\"[header]\" />\n\n <!-- Menu items -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- keydown bubbles from the focused menu items (roving tabindex) -->\n <div\n class=\"lc-menu__items\"\n role=\"menu\"\n [id]=\"panelId\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n @for (item of items(); track item.id; let i = $index) {\n @if (item.href) {\n <!-- Link item -->\n <a\n #menuItem\n role=\"menuitem\"\n [href]=\"item.href\"\n [target]=\"item.target || '_self'\"\n [attr.rel]=\"item.target === '_blank' ? 'noopener noreferrer' : null\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" [decorative]=\"true\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </a>\n } @else {\n <!-- Button item -->\n <button\n #menuItem\n type=\"button\"\n role=\"menuitem\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" [decorative]=\"true\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </button>\n }\n\n @if (item.dividerAfter) {\n <hr class=\"lc-menu__divider\" />\n }\n }\n </div>\n\n <!-- Optional footer slot -->\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n",
|
|
244
244
|
styles: [".lc-menu{position:relative;display:inline-block}.lc-menu__trigger{display:inline-block}.lc-menu__dropdown{position:absolute;background-color:var(--color-surface);background-image:var(--gradient-surface);border:1px solid var(--color-border-strong);border-radius:var(--border-radius-lg, .5rem);box-shadow:var(--elevation-4);padding:var(--lc-density-padding-xs, var(--spacing-2)) 0;z-index:9999;animation:slideDown .15s ease-out}.lc-menu__dropdown--bottom-left{top:calc(100% + var(--spacing-2));left:0}.lc-menu__dropdown--bottom-right{top:calc(100% + var(--spacing-2));right:0}.lc-menu__dropdown--top-left{bottom:calc(100% + var(--spacing-2));left:0}.lc-menu__dropdown--top-right{bottom:calc(100% + var(--spacing-2));right:0}.lc-menu__dropdown--capped{width:max-content}.lc-menu__item{display:flex;align-items:center;gap:var(--spacing-3);width:100%;padding:var(--lc-density-padding-sm, var(--spacing-3)) var(--lc-density-padding-md, var(--spacing-4));background:transparent;border:none;text-align:left;text-decoration:none;color:var(--color-text-primary);font-size:var(--font-size-base);font-family:var(--font-family-base);cursor:pointer;transition:background-color .2s ease}.lc-menu__item:hover:not(.lc-menu__item--disabled){background-color:var(--color-surface-hover)}.lc-menu__item:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px;background-color:var(--color-surface-hover)}.lc-menu__item--danger{color:var(--color-error)}.lc-menu__item--danger:hover:not(.lc-menu__item--danger--disabled){background-color:#9d0e0e24}.lc-menu__item--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.lc-menu__item-content{display:flex;flex-direction:column;gap:var(--lc-density-gap-xs, var(--spacing-1));flex:1}.lc-menu__item-label{font-size:var(--font-size-base);font-weight:var(--font-weight-medium);line-height:var(--line-height-tight)}.lc-menu__item-subtitle{font-size:var(--font-size-sm);color:var(--color-text-secondary);line-height:var(--line-height-tight)}.lc-menu__item-badge{padding:var(--spacing-1) var(--spacing-2);background-color:var(--color-primary-100);color:var(--color-primary-700);font-size:var(--font-size-xs);font-weight:var(--font-weight-semibold);border-radius:var(--border-radius-full);line-height:1}.lc-menu__divider{margin:var(--spacing-2) 0;border:none;border-top:1px solid var(--color-divider)}.lc-menu__dropdown--sm{padding:var(--spacing-1, .25rem) 0}.lc-menu__dropdown--sm .lc-menu__item{gap:var(--lc-density-gap-sm, var(--spacing-2));padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));font-size:var(--font-size-sm)}.lc-menu__dropdown--sm .lc-menu__item-label{font-size:var(--font-size-sm)}.lc-menu__dropdown--sm .lc-menu__item-subtitle{font-size:var(--font-size-xs)}.lc-menu__dropdown--sm .lc-menu__item-badge{font-size:.625rem;padding:1px var(--spacing-1)}.lc-menu__dropdown--sm .lc-menu__divider{margin:var(--spacing-1) 0}.lc-menu__dropdown--lg .lc-menu__item{gap:var(--lc-density-gap-md, var(--spacing-4));padding:var(--lc-density-padding-md, var(--spacing-4)) var(--spacing-5);font-size:var(--font-size-lg)}.lc-menu__dropdown--lg .lc-menu__item-label{font-size:var(--font-size-lg)}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}\n"]
|
|
245
245
|
}]
|
|
246
246
|
}],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-menu.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/menu/menu.component.ts","../../../../libs/angular-ui-kit/src/lib/menu/menu.component.html"],"sourcesContent":["import {\n Component,\n input,\n output,\n signal,\n effect,\n untracked,\n afterNextRender,\n afterRenderEffect,\n viewChildren,\n ChangeDetectionStrategy,\n HostListener,\n ElementRef,\n Injector,\n Renderer2,\n OnDestroy,\n inject,\n} from '@angular/core';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\nimport { OverlayStackService } from '@lifecorner/angular-ui-kit/internal/overlay';\n\nexport interface MenuItem {\n id: string;\n label: string;\n icon?: string;\n iconSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n href?: string;\n target?: '_blank' | '_self' | '_parent' | '_top';\n variant?: 'default' | 'danger';\n dividerAfter?: boolean;\n disabled?: boolean;\n metadata?: {\n subtitle?: string;\n badge?: string;\n };\n}\n\n/** Natively focusable elements a projected trigger may be or contain. */\nconst INTERACTIVE_SELECTOR = 'button, a[href], input, select, textarea, [tabindex]';\n\n/**\n * Menu Component - Dropdown menu for navigation and actions\n *\n * Features:\n * - Customizable menu items with icons\n * - Support for links and buttons\n * - Dividers between menu sections\n * - Optional subtitle/metadata for items\n * - Danger variant for destructive actions\n * - Click outside to close (top-most overlay only)\n * - Keyboard navigation: focus moves into the menu on open, Arrow keys /\n * Home / End move between items (roving tabindex), Enter / Space activate,\n * Escape closes and returns focus to the trigger, Tab closes\n * - ARIA menu semantics (`aria-haspopup` / `aria-expanded` / `aria-controls`\n * on the projected trigger, `role=\"menu\"` / `role=\"menuitem\"` on the panel)\n * - OnPush change detection for performance\n *\n * @example\n * ```html\n * <lc-menu\n * [items]=\"menuItems\"\n * [isOpen]=\"isMenuOpen\"\n * (itemClick)=\"handleMenuClick($event)\"\n * (closed)=\"isMenuOpen = false\"\n * >\n * <button trigger>Open Menu</button>\n * <div header>User Profile</div>\n * </lc-menu>\n * ```\n */\n@Component({\n selector: 'lc-menu',\n standalone: true,\n imports: [IconComponent],\n templateUrl: './menu.component.html',\n styleUrl: './menu.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class MenuComponent implements OnDestroy {\n private static nextId = 0;\n /** Identifies this instance in the overlay stack. */\n private readonly menuId = `lc-menu-${++MenuComponent.nextId}`;\n\n readonly items = input<MenuItem[]>([]);\n readonly isOpen = input(false);\n readonly position = input<'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'>('bottom-right');\n readonly size = input<'sm' | 'md' | 'lg'>('md');\n readonly minWidth = input('220px');\n /**\n * Maximum width of the panel (any CSS width, e.g. `min(18rem, calc(100vw - 2rem))`).\n * Empty leaves it unbounded. Long item labels and header lines wrap within it.\n */\n readonly maxWidth = input('');\n\n readonly itemClick = output<MenuItem>();\n readonly closed = output<void>();\n\n /** Id of the `role=\"menu\"` element, referenced by the trigger's `aria-controls`. */\n protected readonly panelId = `${this.menuId}-panel`;\n\n /** Index (into `items()`) of the item that currently owns the roving tabindex. */\n protected readonly activeIndex = signal(-1);\n\n private readonly itemElements = viewChildren<ElementRef<HTMLElement>>('menuItem');\n private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly renderer = inject(Renderer2);\n private readonly injector = inject(Injector);\n private readonly overlayStack = inject(OverlayStackService);\n\n constructor() {\n // Whether the menu has been through its first open-state evaluation; a menu\n // that is already open when it renders (demo pages) must not steal focus.\n let initialized = false;\n effect(() => {\n const open = this.isOpen();\n untracked(() => {\n if (open) {\n this.overlayStack.push(this.menuId);\n this.activeIndex.set(this.firstEnabledIndex());\n // Move focus into the menu once the panel has rendered\n if (initialized) {\n afterNextRender(() => this.focusActiveItem(), { injector: this.injector });\n }\n } else {\n this.overlayStack.remove(this.menuId);\n this.restoreFocus();\n this.activeIndex.set(-1);\n }\n initialized = true;\n });\n });\n\n // Mirror the open state onto the projected trigger so assistive tech knows\n // it opens a menu (the trigger is caller-supplied, so we set the\n // attributes rather than bind them).\n afterRenderEffect(() => {\n const open = this.isOpen();\n const trigger = this.triggerElement();\n if (!trigger) return;\n this.renderer.setAttribute(trigger, 'aria-haspopup', 'menu');\n this.renderer.setAttribute(trigger, 'aria-expanded', String(open));\n if (open) {\n this.renderer.setAttribute(trigger, 'aria-controls', this.panelId);\n } else {\n this.renderer.removeAttribute(trigger, 'aria-controls');\n }\n });\n }\n\n ngOnDestroy(): void {\n this.overlayStack.remove(this.menuId);\n }\n\n /**\n * Handle menu item click\n */\n onItemClick(item: MenuItem, event: Event): void {\n if (item.disabled) {\n event.preventDefault();\n return;\n }\n\n // Links navigate natively; button items only emit\n if (!item.href) {\n event.preventDefault();\n }\n\n this.itemClick.emit(item);\n }\n\n /**\n * Close the menu\n */\n close(): void {\n this.closed.emit();\n }\n\n /**\n * Handle click outside to close menu — only while the menu is the top-most\n * overlay, so a click inside a layer above it (a modal opened from a menu\n * item) is left alone.\n */\n @HostListener('document:click', ['$event'])\n onDocumentClick(event: MouseEvent): void {\n if (!this.isOpen()) {\n return;\n }\n\n const target = event.target;\n if (target instanceof Node && this.elementRef.nativeElement.contains(target)) {\n return;\n }\n if (!this.overlayStack.claim(this.menuId, event)) return;\n this.close();\n }\n\n /**\n * Escape closes the menu (top-most overlay only) and returns focus to the\n * trigger; the event is consumed so overlays underneath stay open.\n */\n @HostListener('document:keydown.escape', ['$event'])\n onEscapeKey(event: Event): void {\n if (!this.isOpen()) return;\n if (!this.overlayStack.claim(this.menuId, event)) return;\n event.stopPropagation();\n this.triggerElement()?.focus();\n this.close();\n }\n\n /** Keyboard navigation inside the `role=\"menu\"` panel. */\n protected onPanelKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n this.focusIndex(this.nextEnabledIndex(this.activeIndex(), 1));\n break;\n case 'ArrowUp':\n event.preventDefault();\n this.focusIndex(this.nextEnabledIndex(this.activeIndex(), -1));\n break;\n case 'Home':\n event.preventDefault();\n this.focusIndex(this.firstEnabledIndex());\n break;\n case 'End':\n event.preventDefault();\n this.focusIndex(this.nextEnabledIndex(0, -1));\n break;\n case ' ': {\n // Buttons activate on Space natively, links only on Enter\n const target = event.target as HTMLElement | null;\n if (target?.matches('a[role=\"menuitem\"]')) {\n event.preventDefault();\n target.click();\n }\n break;\n }\n case 'Tab':\n // Focus leaves the menu — let it move on naturally, but close the menu\n this.close();\n break;\n }\n }\n\n /** Roving tabindex: only the active item is in the tab sequence. */\n protected tabindexFor(index: number, item: MenuItem): number {\n return !item.disabled && index === this.activeIndex() ? 0 : -1;\n }\n\n private focusIndex(index: number): void {\n if (index < 0) return;\n this.activeIndex.set(index);\n this.focusActiveItem();\n }\n\n private focusActiveItem(): void {\n this.itemElements()[this.activeIndex()]?.nativeElement.focus();\n }\n\n private firstEnabledIndex(): number {\n return this.items().findIndex((item) => !item.disabled);\n }\n\n /** Index of the next enabled item in `direction`, wrapping around; -1 if none. */\n private nextEnabledIndex(from: number, direction: 1 | -1): number {\n const items = this.items();\n for (let step = 1; step <= items.length; step++) {\n const index = (from + direction * step + items.length) % items.length;\n if (!items[index]?.disabled) return index;\n }\n return -1;\n }\n\n /**\n * The element that carries the menu-button ARIA attributes and receives\n * focus back: the projected `[trigger]` node itself when it is natively\n * interactive, otherwise the first interactive element inside it (e.g. the\n * `<button>` inside an `<lc-button trigger>`).\n */\n private triggerElement(): HTMLElement | null {\n const slot = this.elementRef.nativeElement.querySelector<HTMLElement>('.lc-menu__trigger [trigger]');\n if (!slot) return null;\n return slot.matches(INTERACTIVE_SELECTOR)\n ? slot\n : (slot.querySelector<HTMLElement>(INTERACTIVE_SELECTOR) ?? slot);\n }\n\n /**\n * Closing removes the panel from the DOM; if focus is inside it (an item was\n * activated by keyboard) it would silently drop to `<body>`, so send it back\n * to the trigger.\n */\n private restoreFocus(): void {\n if (typeof document === 'undefined') return;\n const panel = this.elementRef.nativeElement.querySelector<HTMLElement>('.lc-menu__dropdown');\n if (panel?.contains(document.activeElement)) {\n this.triggerElement()?.focus();\n }\n }\n}\n","<div class=\"lc-menu\">\n <!-- Trigger slot -->\n <div class=\"lc-menu__trigger\">\n <ng-content select=\"[trigger]\" />\n </div>\n\n <!-- Dropdown menu -->\n @if (isOpen()) {\n <div\n class=\"lc-menu__dropdown\"\n [class.lc-menu__dropdown--bottom-left]=\"position() === 'bottom-left'\"\n [class.lc-menu__dropdown--bottom-right]=\"position() === 'bottom-right'\"\n [class.lc-menu__dropdown--top-left]=\"position() === 'top-left'\"\n [class.lc-menu__dropdown--top-right]=\"position() === 'top-right'\"\n [class.lc-menu__dropdown--sm]=\"size() === 'sm'\"\n [class.lc-menu__dropdown--lg]=\"size() === 'lg'\"\n [style.min-width]=\"minWidth()\"\n [style.max-width]=\"maxWidth() || null\"\n [class.lc-menu__dropdown--capped]=\"!!maxWidth()\"\n >\n <!-- Optional header slot (outside the menu role: it is not an item) -->\n <ng-content select=\"[header]\" />\n\n <!-- Menu items -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- keydown bubbles from the focused menu items (roving tabindex) -->\n <div\n class=\"lc-menu__items\"\n role=\"menu\"\n [id]=\"panelId\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n @for (item of items(); track item.id; let i = $index) {\n @if (item.href) {\n <!-- Link item -->\n <a\n #menuItem\n role=\"menuitem\"\n [href]=\"item.href\"\n [target]=\"item.target || '_self'\"\n [attr.rel]=\"item.target === '_blank' ? 'noopener noreferrer' : null\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </a>\n } @else {\n <!-- Button item -->\n <button\n #menuItem\n type=\"button\"\n role=\"menuitem\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </button>\n }\n\n @if (item.dividerAfter) {\n <hr class=\"lc-menu__divider\" />\n }\n }\n </div>\n\n <!-- Optional footer slot -->\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-menu.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/menu/menu.component.ts","../../../../libs/angular-ui-kit/src/lib/menu/menu.component.html"],"sourcesContent":["import {\n Component,\n input,\n output,\n signal,\n effect,\n untracked,\n afterNextRender,\n afterRenderEffect,\n viewChildren,\n ChangeDetectionStrategy,\n HostListener,\n ElementRef,\n Injector,\n Renderer2,\n OnDestroy,\n inject,\n} from '@angular/core';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\nimport { OverlayStackService } from '@lifecorner/angular-ui-kit/internal/overlay';\n\nexport interface MenuItem {\n id: string;\n label: string;\n icon?: string;\n iconSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n href?: string;\n target?: '_blank' | '_self' | '_parent' | '_top';\n variant?: 'default' | 'danger';\n dividerAfter?: boolean;\n disabled?: boolean;\n metadata?: {\n subtitle?: string;\n badge?: string;\n };\n}\n\n/** Natively focusable elements a projected trigger may be or contain. */\nconst INTERACTIVE_SELECTOR = 'button, a[href], input, select, textarea, [tabindex]';\n\n/**\n * Menu Component - Dropdown menu for navigation and actions\n *\n * Features:\n * - Customizable menu items with icons\n * - Support for links and buttons\n * - Dividers between menu sections\n * - Optional subtitle/metadata for items\n * - Danger variant for destructive actions\n * - Click outside to close (top-most overlay only)\n * - Keyboard navigation: focus moves into the menu on open, Arrow keys /\n * Home / End move between items (roving tabindex), Enter / Space activate,\n * Escape closes and returns focus to the trigger, Tab closes\n * - ARIA menu semantics (`aria-haspopup` / `aria-expanded` / `aria-controls`\n * on the projected trigger, `role=\"menu\"` / `role=\"menuitem\"` on the panel)\n * - OnPush change detection for performance\n *\n * @example\n * ```html\n * <lc-menu\n * [items]=\"menuItems\"\n * [isOpen]=\"isMenuOpen\"\n * (itemClick)=\"handleMenuClick($event)\"\n * (closed)=\"isMenuOpen = false\"\n * >\n * <button trigger>Open Menu</button>\n * <div header>User Profile</div>\n * </lc-menu>\n * ```\n */\n@Component({\n selector: 'lc-menu',\n standalone: true,\n imports: [IconComponent],\n templateUrl: './menu.component.html',\n styleUrl: './menu.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class MenuComponent implements OnDestroy {\n private static nextId = 0;\n /** Identifies this instance in the overlay stack. */\n private readonly menuId = `lc-menu-${++MenuComponent.nextId}`;\n\n readonly items = input<MenuItem[]>([]);\n readonly isOpen = input(false);\n readonly position = input<'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'>('bottom-right');\n readonly size = input<'sm' | 'md' | 'lg'>('md');\n readonly minWidth = input('220px');\n /**\n * Maximum width of the panel (any CSS width, e.g. `min(18rem, calc(100vw - 2rem))`).\n * Empty leaves it unbounded. Long item labels and header lines wrap within it.\n */\n readonly maxWidth = input('');\n\n readonly itemClick = output<MenuItem>();\n readonly closed = output<void>();\n\n /** Id of the `role=\"menu\"` element, referenced by the trigger's `aria-controls`. */\n protected readonly panelId = `${this.menuId}-panel`;\n\n /** Index (into `items()`) of the item that currently owns the roving tabindex. */\n protected readonly activeIndex = signal(-1);\n\n private readonly itemElements = viewChildren<ElementRef<HTMLElement>>('menuItem');\n private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly renderer = inject(Renderer2);\n private readonly injector = inject(Injector);\n private readonly overlayStack = inject(OverlayStackService);\n\n constructor() {\n // Whether the menu has been through its first open-state evaluation; a menu\n // that is already open when it renders (demo pages) must not steal focus.\n let initialized = false;\n effect(() => {\n const open = this.isOpen();\n untracked(() => {\n if (open) {\n this.overlayStack.push(this.menuId);\n this.activeIndex.set(this.firstEnabledIndex());\n // Move focus into the menu once the panel has rendered\n if (initialized) {\n afterNextRender(() => this.focusActiveItem(), { injector: this.injector });\n }\n } else {\n this.overlayStack.remove(this.menuId);\n this.restoreFocus();\n this.activeIndex.set(-1);\n }\n initialized = true;\n });\n });\n\n // Mirror the open state onto the projected trigger so assistive tech knows\n // it opens a menu (the trigger is caller-supplied, so we set the\n // attributes rather than bind them).\n afterRenderEffect(() => {\n const open = this.isOpen();\n const trigger = this.triggerElement();\n if (!trigger) return;\n this.renderer.setAttribute(trigger, 'aria-haspopup', 'menu');\n this.renderer.setAttribute(trigger, 'aria-expanded', String(open));\n if (open) {\n this.renderer.setAttribute(trigger, 'aria-controls', this.panelId);\n } else {\n this.renderer.removeAttribute(trigger, 'aria-controls');\n }\n });\n }\n\n ngOnDestroy(): void {\n this.overlayStack.remove(this.menuId);\n }\n\n /**\n * Handle menu item click\n */\n onItemClick(item: MenuItem, event: Event): void {\n if (item.disabled) {\n event.preventDefault();\n return;\n }\n\n // Links navigate natively; button items only emit\n if (!item.href) {\n event.preventDefault();\n }\n\n this.itemClick.emit(item);\n }\n\n /**\n * Close the menu\n */\n close(): void {\n this.closed.emit();\n }\n\n /**\n * Handle click outside to close menu — only while the menu is the top-most\n * overlay, so a click inside a layer above it (a modal opened from a menu\n * item) is left alone.\n */\n @HostListener('document:click', ['$event'])\n onDocumentClick(event: MouseEvent): void {\n if (!this.isOpen()) {\n return;\n }\n\n const target = event.target;\n if (target instanceof Node && this.elementRef.nativeElement.contains(target)) {\n return;\n }\n if (!this.overlayStack.claim(this.menuId, event)) return;\n this.close();\n }\n\n /**\n * Escape closes the menu (top-most overlay only) and returns focus to the\n * trigger; the event is consumed so overlays underneath stay open.\n */\n @HostListener('document:keydown.escape', ['$event'])\n onEscapeKey(event: Event): void {\n if (!this.isOpen()) return;\n if (!this.overlayStack.claim(this.menuId, event)) return;\n event.stopPropagation();\n this.triggerElement()?.focus();\n this.close();\n }\n\n /** Keyboard navigation inside the `role=\"menu\"` panel. */\n protected onPanelKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n this.focusIndex(this.nextEnabledIndex(this.activeIndex(), 1));\n break;\n case 'ArrowUp':\n event.preventDefault();\n this.focusIndex(this.nextEnabledIndex(this.activeIndex(), -1));\n break;\n case 'Home':\n event.preventDefault();\n this.focusIndex(this.firstEnabledIndex());\n break;\n case 'End':\n event.preventDefault();\n this.focusIndex(this.nextEnabledIndex(0, -1));\n break;\n case ' ': {\n // Buttons activate on Space natively, links only on Enter\n const target = event.target as HTMLElement | null;\n if (target?.matches('a[role=\"menuitem\"]')) {\n event.preventDefault();\n target.click();\n }\n break;\n }\n case 'Tab':\n // Focus leaves the menu — let it move on naturally, but close the menu\n this.close();\n break;\n }\n }\n\n /** Roving tabindex: only the active item is in the tab sequence. */\n protected tabindexFor(index: number, item: MenuItem): number {\n return !item.disabled && index === this.activeIndex() ? 0 : -1;\n }\n\n private focusIndex(index: number): void {\n if (index < 0) return;\n this.activeIndex.set(index);\n this.focusActiveItem();\n }\n\n private focusActiveItem(): void {\n this.itemElements()[this.activeIndex()]?.nativeElement.focus();\n }\n\n private firstEnabledIndex(): number {\n return this.items().findIndex((item) => !item.disabled);\n }\n\n /** Index of the next enabled item in `direction`, wrapping around; -1 if none. */\n private nextEnabledIndex(from: number, direction: 1 | -1): number {\n const items = this.items();\n for (let step = 1; step <= items.length; step++) {\n const index = (from + direction * step + items.length) % items.length;\n if (!items[index]?.disabled) return index;\n }\n return -1;\n }\n\n /**\n * The element that carries the menu-button ARIA attributes and receives\n * focus back: the projected `[trigger]` node itself when it is natively\n * interactive, otherwise the first interactive element inside it (e.g. the\n * `<button>` inside an `<lc-button trigger>`).\n */\n private triggerElement(): HTMLElement | null {\n const slot = this.elementRef.nativeElement.querySelector<HTMLElement>('.lc-menu__trigger [trigger]');\n if (!slot) return null;\n return slot.matches(INTERACTIVE_SELECTOR)\n ? slot\n : (slot.querySelector<HTMLElement>(INTERACTIVE_SELECTOR) ?? slot);\n }\n\n /**\n * Closing removes the panel from the DOM; if focus is inside it (an item was\n * activated by keyboard) it would silently drop to `<body>`, so send it back\n * to the trigger.\n */\n private restoreFocus(): void {\n if (typeof document === 'undefined') return;\n const panel = this.elementRef.nativeElement.querySelector<HTMLElement>('.lc-menu__dropdown');\n if (panel?.contains(document.activeElement)) {\n this.triggerElement()?.focus();\n }\n }\n}\n","<div class=\"lc-menu\">\n <!-- Trigger slot -->\n <div class=\"lc-menu__trigger\">\n <ng-content select=\"[trigger]\" />\n </div>\n\n <!-- Dropdown menu -->\n @if (isOpen()) {\n <div\n class=\"lc-menu__dropdown\"\n [class.lc-menu__dropdown--bottom-left]=\"position() === 'bottom-left'\"\n [class.lc-menu__dropdown--bottom-right]=\"position() === 'bottom-right'\"\n [class.lc-menu__dropdown--top-left]=\"position() === 'top-left'\"\n [class.lc-menu__dropdown--top-right]=\"position() === 'top-right'\"\n [class.lc-menu__dropdown--sm]=\"size() === 'sm'\"\n [class.lc-menu__dropdown--lg]=\"size() === 'lg'\"\n [style.min-width]=\"minWidth()\"\n [style.max-width]=\"maxWidth() || null\"\n [class.lc-menu__dropdown--capped]=\"!!maxWidth()\"\n >\n <!-- Optional header slot (outside the menu role: it is not an item) -->\n <ng-content select=\"[header]\" />\n\n <!-- Menu items -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- keydown bubbles from the focused menu items (roving tabindex) -->\n <div\n class=\"lc-menu__items\"\n role=\"menu\"\n [id]=\"panelId\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n @for (item of items(); track item.id; let i = $index) {\n @if (item.href) {\n <!-- Link item -->\n <a\n #menuItem\n role=\"menuitem\"\n [href]=\"item.href\"\n [target]=\"item.target || '_self'\"\n [attr.rel]=\"item.target === '_blank' ? 'noopener noreferrer' : null\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" [decorative]=\"true\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </a>\n } @else {\n <!-- Button item -->\n <button\n #menuItem\n type=\"button\"\n role=\"menuitem\"\n class=\"lc-menu__item\"\n [class.lc-menu__item--danger]=\"item.variant === 'danger'\"\n [class.lc-menu__item--disabled]=\"item.disabled\"\n [attr.tabindex]=\"tabindexFor(i, item)\"\n [disabled]=\"item.disabled\"\n (click)=\"onItemClick(item, $event)\"\n >\n @if (item.icon) {\n <lc-icon [name]=\"item.icon\" [size]=\"item.iconSize || 'sm'\" [decorative]=\"true\" />\n }\n <span class=\"lc-menu__item-content\">\n <span class=\"lc-menu__item-label\">{{ item.label }}</span>\n @if (item.metadata?.subtitle; as subtitle) {\n <span class=\"lc-menu__item-subtitle\">{{ subtitle }}</span>\n }\n </span>\n @if (item.metadata?.badge; as badge) {\n <span class=\"lc-menu__item-badge\">{{ badge }}</span>\n }\n </button>\n }\n\n @if (item.dividerAfter) {\n <hr class=\"lc-menu__divider\" />\n }\n }\n </div>\n\n <!-- Optional footer slot -->\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -18,6 +18,13 @@ export type AvatarLoading = 'eager' | 'lazy';
|
|
|
18
18
|
export type AvatarColor = 'default' | 'member-1' | 'member-2' | 'member-3' | 'member-4' | 'member-5' | 'member-6';
|
|
19
19
|
/** The six member colours in their order (`AvatarColor` without `default`). */
|
|
20
20
|
export declare const AVATAR_MEMBER_COLORS: readonly AvatarColor[];
|
|
21
|
+
/**
|
|
22
|
+
* The initials `lc-avatar` shows for `name`: first and last word's first
|
|
23
|
+
* letter, upper case; one word: its first letter; empty: `?`. Kit-internal
|
|
24
|
+
* (used by `lc-header`, so the account button's name holds exactly the text
|
|
25
|
+
* the avatar shows, AS-33); no semver guarantee.
|
|
26
|
+
*/
|
|
27
|
+
export declare function ɵavatarInitials(name: string | undefined): string;
|
|
21
28
|
/**
|
|
22
29
|
* Avatar component for circular user representation.
|
|
23
30
|
*
|
|
@@ -165,7 +165,9 @@ export declare class HeaderComponent {
|
|
|
165
165
|
readonly userName: import("@angular/core").InputSignal<string>;
|
|
166
166
|
/**
|
|
167
167
|
* What the account-menu button shows: `icon` (user icon and chevron) or
|
|
168
|
-
* `avatar` (the initials of `userName` in a 48 px round button
|
|
168
|
+
* `avatar` (the initials of `userName` in a 48 px round button; the button
|
|
169
|
+
* is named `userMenuAriaLabel`, a comma and the initials).
|
|
170
|
+
* @default 'icon'
|
|
169
171
|
*/
|
|
170
172
|
readonly userMenuTrigger: import("@angular/core").InputSignal<HeaderUserMenuTrigger>;
|
|
171
173
|
/** Photo for the account avatar (trigger and menu header); initials when empty or broken. */
|
|
@@ -213,7 +215,15 @@ export declare class HeaderComponent {
|
|
|
213
215
|
readonly homeAriaLabel: import("@angular/core").InputSignal<string>;
|
|
214
216
|
/** Accessible name of the hamburger button. */
|
|
215
217
|
readonly hamburgerAriaLabel: import("@angular/core").InputSignal<string>;
|
|
216
|
-
/**
|
|
218
|
+
/**
|
|
219
|
+
* Accessible name of the account menu trigger. With `userMenuTrigger="avatar"`
|
|
220
|
+
* the button is named this text, a comma and the initials it shows
|
|
221
|
+
* (`Menü für Johanna Müller, JM`), so a voice-control user can say what they
|
|
222
|
+
* see (WCAG 2.5.3). Nothing is added when `userName` gives no initials, or
|
|
223
|
+
* when the text already contains them as a word. The name is the same with a
|
|
224
|
+
* photo.
|
|
225
|
+
* @default 'Open user menu'
|
|
226
|
+
*/
|
|
217
227
|
readonly userMenuAriaLabel: import("@angular/core").InputSignal<string>;
|
|
218
228
|
/** Accessible name of the `<nav>` around `navItems`. */
|
|
219
229
|
readonly navAriaLabel: import("@angular/core").InputSignal<string>;
|
|
@@ -242,6 +252,16 @@ export declare class HeaderComponent {
|
|
|
242
252
|
protected get themeService(): ThemeService;
|
|
243
253
|
private readonly hamburger;
|
|
244
254
|
constructor();
|
|
255
|
+
/**
|
|
256
|
+
* Name of the avatar trigger (AS-33, WCAG 2.5.3 Label in Name):
|
|
257
|
+
* `userMenuAriaLabel`, a comma and the initials the avatar shows, so the
|
|
258
|
+
* visible text is a word of the name. Built from the same function as the
|
|
259
|
+
* avatar's initials and independent of the photo, so the prerendered name
|
|
260
|
+
* and the page's agree. Nothing is added for the `?` placeholder or when
|
|
261
|
+
* the label already holds the initials as a word (parentheticals dropped,
|
|
262
|
+
* as axe-core 4.14 does); an empty label becomes the initials alone.
|
|
263
|
+
*/
|
|
264
|
+
protected readonly avatarTriggerAriaLabel: import("@angular/core").Signal<string>;
|
|
245
265
|
/**
|
|
246
266
|
* Get menu items for profile dropdown
|
|
247
267
|
*/
|