@compsych-ui-components/angular 6.1.0 → 6.1.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/sidebar/sidebar.ts","../../../../../../packages/angular/src/lib/sidebar/sidebar.html"],"names":[],"mappings":"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","sourcesContent":["import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n model,\r\n input,\r\n output,\r\n signal,\r\n} from '@angular/core';\r\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\r\nimport { LUCIDE_ICONS } from '@compsych-ui-components/shared/assets/lucide-icons.generated';\r\n\r\nexport interface SidebarItem {\r\n /** Stable identifier used by `activeId` and emitted by `itemClick`. */\r\n id: string;\r\n /** Text label displayed next to the icon. */\r\n label: string;\r\n /** Lucide icon name (kebab-case), e.g. `'house'` or `'folder'`. */\r\n icon: string;\r\n /** Child items — renders this item as an expandable group with a chevron toggle. Child items cannot have their own children. */\r\n children?: SidebarItem[];\r\n /** When true, renders a horizontal divider above this item in the list. */\r\n dividerBefore?: boolean;\r\n /** When true, the item is non-interactive — it cannot be clicked and receives no hover or active styling. */\r\n disabled?: boolean;\r\n /**\r\n * When true, clicking the item emits `itemClick` but does not highlight it\r\n * as active or update `activeId`. Use for action items (e.g. logout)\r\n * that are not navigation destinations.\r\n */\r\n nonRoute?: boolean;\r\n}\r\n\r\n/** Default content from the Figma (node 260:4700). */\r\nconst DEFAULT_ITEMS: SidebarItem[] = [\r\n { id: 'home', label: 'Home', icon: 'house' },\r\n { id: 'connect', label: 'Connect Me', icon: 'message-circle', dividerBefore: true },\r\n { id: 'guide', label: 'Guide Me', icon: 'compass' },\r\n { id: 'assess', label: 'Assess Me', icon: 'binoculars' },\r\n {\r\n id: 'resources',\r\n label: 'Resource Library',\r\n icon: 'folder',\r\n dividerBefore: true,\r\n children: [\r\n { id: 'wellness', label: 'Wellness', icon: 'leaf' },\r\n { id: 'relationships', label: 'Relationships', icon: 'users' },\r\n { id: 'work', label: 'Work & Education', icon: 'briefcase' },\r\n { id: 'financial', label: 'Financial', icon: 'landmark' },\r\n { id: 'legal', label: 'Legal', icon: 'scale' },\r\n { id: 'lifestyle', label: 'Lifestyle', icon: 'smile' },\r\n { id: 'home-auto', label: 'Home & Auto', icon: 'car' },\r\n { id: 'abroad', label: 'Working Abroad', icon: 'plane' },\r\n ],\r\n },\r\n];\r\n\r\nconst DEFAULT_FOOTER: SidebarItem[] = [\r\n { id: 'location', label: 'Location & Language', icon: 'globe' },\r\n { id: 'logout', label: 'Logout', icon: 'log-out' },\r\n];\r\n\r\n/**\r\n * Sidebar — vertical app navigation from the ComPsych Design System.\r\n *\r\n * Figma: Core Components — base 260:4700, parts 213:1585.\r\n *\r\n * Icon + label items with default / hover / selected / focus / disabled states,\r\n * section dividers, expandable groups (chevron), and a pinned footer\r\n * (location & language, logout). Data-driven via `items` / `footerItems`,\r\n * defaulting to the design's content. All values are sys tokens.\r\n */\r\n@Component({\r\n selector: 'compsych-sidebar',\r\n standalone: true,\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n imports: [],\r\n templateUrl: './sidebar.html',\r\n styleUrl: './sidebar.css',\r\n host: { role: 'navigation', 'aria-label': 'Sidebar' },\r\n})\r\nexport class SidebarComponent {\r\n /** Navigation items rendered in the scrollable main area. Defaults to the standard ComPsych nav set. */\r\n readonly items = input<SidebarItem[]>(DEFAULT_ITEMS);\r\n /** Items pinned to the bottom of the sidebar, outside the scroll region. Intended for utility actions such as language selection and logout. */\r\n readonly footerItems = input<SidebarItem[]>(DEFAULT_FOOTER);\r\n /** Currently active item id — supports two-way binding via `[(activeId)]`. Updated on click for route items; unchanged for `nonRoute` items. */\r\n readonly activeId = model<string>('home');\r\n /**\r\n * Emitted with the item's `id` whenever a non-disabled, non-group item is clicked.\r\n * Fires for both route items and `nonRoute` items.\r\n * Group items (those with `children`) do not emit — they only toggle their expanded state.\r\n */\r\n readonly itemClick = output<string>();\r\n\r\n private readonly expanded = signal<Set<string>>(new Set());\r\n\r\n constructor(private readonly sanitizer: DomSanitizer) {}\r\n\r\n isExpanded(id: string): boolean {\r\n return this.expanded().has(id);\r\n }\r\n\r\n isActive(id: string): boolean {\r\n return this.activeId() === id;\r\n }\r\n\r\n onItemClick(item: SidebarItem): void {\r\n if (item.disabled) return;\r\n if (item.children?.length) {\r\n this.expanded.update((s) => {\r\n const next = new Set(s);\r\n next.has(item.id) ? next.delete(item.id) : next.add(item.id);\r\n return next;\r\n });\r\n return;\r\n }\r\n if (item.nonRoute) {\r\n this.itemClick.emit(item.id);\r\n return;\r\n }\r\n this.activeId.set(item.id);\r\n }\r\n\r\n /**\r\n * Looks up a Lucide icon by name and injects explicit width/height attributes\r\n * so the SVG renders at the correct size when injected via [innerHTML].\r\n * Angular's CSS encapsulation does not add _ngcontent attributes to dynamically\r\n * injected content, so explicit SVG attributes are the reliable solution.\r\n */\r\n iconSvg(name: string, size = 20, strokeWidth = 1.5): SafeHtml {\r\n let svg = LUCIDE_ICONS[name] ?? '';\r\n svg = svg.replace('<svg ', `<svg width=\"${size}\" height=\"${size}\" `);\r\n if (strokeWidth !== 1.5) {\r\n svg = svg.replace('stroke-width=\"1.5\"', `stroke-width=\"${strokeWidth}\"`);\r\n }\r\n return this.sanitizer.bypassSecurityTrustHtml(svg);\r\n }\r\n}\r\n","<div class=\"cs-sb__scroll\">\r\n <ul class=\"cs-sb__list\">\r\n @for (item of items(); track item.id) {\r\n @if (item.dividerBefore) {\r\n <li class=\"cs-sb__divider\" aria-hidden=\"true\"></li>\r\n }\r\n <li class=\"cs-sb__item\">\r\n <button\r\n type=\"button\"\r\n class=\"cs-sb__row\"\r\n [class.cs-sb__row--active]=\"!item.nonRoute && isActive(item.id)\"\r\n [disabled]=\"item.disabled || null\"\r\n [attr.aria-current]=\"!item.nonRoute && isActive(item.id) ? 'page' : null\"\r\n [attr.aria-expanded]=\"item.children?.length ? isExpanded(item.id) : null\"\r\n (click)=\"onItemClick(item)\"\r\n >\r\n <span\r\n class=\"cs-sb__icon\"\r\n [innerHTML]=\"iconSvg(item.icon, 20, !item.nonRoute && isActive(item.id) ? 2 : 1.5)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span class=\"cs-sb__label\">{{ item.label }}</span>\r\n @if (item.children?.length) {\r\n <span\r\n class=\"cs-sb__chevron\"\r\n [class.cs-sb__chevron--open]=\"isExpanded(item.id)\"\r\n [innerHTML]=\"iconSvg('chevron-down', 18)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n }\r\n </button>\r\n\r\n @if (item.children?.length && isExpanded(item.id)) {\r\n <ul class=\"cs-sb__sublist\">\r\n @for (child of item.children; track child.id) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"cs-sb__row cs-sb__row--sub\"\r\n [class.cs-sb__row--active]=\"isActive(child.id)\"\r\n [disabled]=\"child.disabled || null\"\r\n [attr.aria-current]=\"isActive(child.id) ? 'page' : null\"\r\n (click)=\"onItemClick(child)\"\r\n >\r\n <span\r\n class=\"cs-sb__icon\"\r\n [innerHTML]=\"iconSvg(child.icon, 20, isActive(child.id) ? 2 : 1.5)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span class=\"cs-sb__label\">{{ child.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</div>\r\n\r\n<div class=\"cs-sb__footer\">\r\n <ul class=\"cs-sb__list\">\r\n @for (item of footerItems(); track item.id) {\r\n <li class=\"cs-sb__item\">\r\n <button\r\n type=\"button\"\r\n class=\"cs-sb__row\"\r\n [class.cs-sb__row--active]=\"!item.nonRoute && isActive(item.id)\"\r\n [disabled]=\"item.disabled || null\"\r\n (click)=\"onItemClick(item)\"\r\n >\r\n <span\r\n class=\"cs-sb__icon\"\r\n [innerHTML]=\"iconSvg(item.icon, 20, !item.nonRoute && isActive(item.id) ? 2 : 1.5)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span class=\"cs-sb__label\">{{ item.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n</div>\r\n"]}
1
+ {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/sidebar/sidebar.ts","../../../../../../packages/angular/src/lib/sidebar/sidebar.html"],"names":[],"mappings":"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","sourcesContent":["import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n model,\r\n input,\r\n output,\r\n signal,\r\n} from '@angular/core';\r\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\r\nimport { LUCIDE_ICONS } from '@compsych-ui-components/shared/assets/lucide-icons.generated';\r\n\r\nexport interface SidebarItem {\r\n /** Stable identifier used by `activeId` and emitted by `itemClick`. */\r\n id: string;\r\n /** Text label displayed next to the icon. */\r\n label: string;\r\n /** Lucide icon name (kebab-case), e.g. `'house'` or `'folder'`. */\r\n icon: string;\r\n /** Child items — renders this item as an expandable group with a chevron toggle. Child items cannot have their own children. */\r\n children?: SidebarItem[];\r\n /** When true, renders a horizontal divider above this item in the list. */\r\n dividerBefore?: boolean;\r\n /** When true, the item is non-interactive — it cannot be clicked and receives no hover or active styling. */\r\n disabled?: boolean;\r\n /**\r\n * When true, clicking the item emits `itemClick` but does not highlight it\r\n * as active or update `activeId`. Use for action items (e.g. logout)\r\n * that are not navigation destinations.\r\n */\r\n nonRoute?: boolean;\r\n}\r\n\r\n/** Default content from the Figma (node 260:4700). */\r\nconst DEFAULT_ITEMS: SidebarItem[] = [\r\n { id: 'home', label: 'Home', icon: 'house' },\r\n { id: 'connect', label: 'Connect Me', icon: 'message-circle', dividerBefore: true },\r\n { id: 'guide', label: 'Guide Me', icon: 'compass' },\r\n { id: 'assess', label: 'Assess Me', icon: 'binoculars' },\r\n {\r\n id: 'resources',\r\n label: 'Resource Library',\r\n icon: 'folder',\r\n dividerBefore: true,\r\n children: [\r\n { id: 'wellness', label: 'Wellness', icon: 'leaf' },\r\n { id: 'relationships', label: 'Relationships', icon: 'users' },\r\n { id: 'work', label: 'Work & Education', icon: 'briefcase' },\r\n { id: 'financial', label: 'Financial', icon: 'landmark' },\r\n { id: 'legal', label: 'Legal', icon: 'scale' },\r\n { id: 'lifestyle', label: 'Lifestyle', icon: 'smile' },\r\n { id: 'home-auto', label: 'Home & Auto', icon: 'car' },\r\n { id: 'abroad', label: 'Working Abroad', icon: 'plane' },\r\n ],\r\n },\r\n];\r\n\r\nconst DEFAULT_FOOTER: SidebarItem[] = [\r\n { id: 'location', label: 'Location & Language', icon: 'globe', nonRoute: true },\r\n { id: 'logout', label: 'Logout', icon: 'log-out', nonRoute: true },\r\n];\r\n\r\n/**\r\n * Sidebar — vertical app navigation from the ComPsych Design System.\r\n *\r\n * Figma: Core Components — base 260:4700, parts 213:1585.\r\n *\r\n * Icon + label items with default / hover / selected / focus / disabled states,\r\n * section dividers, expandable groups (chevron), and a pinned footer\r\n * (location & language, logout). Data-driven via `items` / `footerItems`,\r\n * defaulting to the design's content. All values are sys tokens.\r\n */\r\n@Component({\r\n selector: 'compsych-sidebar',\r\n standalone: true,\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n imports: [],\r\n templateUrl: './sidebar.html',\r\n styleUrl: './sidebar.css',\r\n host: { role: 'navigation', 'aria-label': 'Sidebar' },\r\n})\r\nexport class SidebarComponent {\r\n /** Navigation items rendered in the scrollable main area. Defaults to the standard ComPsych nav set. */\r\n readonly items = input<SidebarItem[]>(DEFAULT_ITEMS);\r\n /** Items pinned to the bottom of the sidebar, outside the scroll region. Intended for utility actions such as language selection and logout. */\r\n readonly footerItems = input<SidebarItem[]>(DEFAULT_FOOTER);\r\n /** Currently active item id — supports two-way binding via `[(activeId)]`. Updated on click for route items; unchanged for `nonRoute` items. */\r\n readonly activeId = model<string>('home');\r\n /**\r\n * Emitted with the item's `id` whenever a non-disabled, non-group item is clicked.\r\n * Fires for both route items and `nonRoute` items.\r\n * Group items (those with `children`) do not emit — they only toggle their expanded state.\r\n */\r\n readonly itemClick = output<string>();\r\n\r\n private readonly expanded = signal<Set<string>>(new Set());\r\n\r\n constructor(private readonly sanitizer: DomSanitizer) {}\r\n\r\n isExpanded(id: string): boolean {\r\n return this.expanded().has(id);\r\n }\r\n\r\n isActive(id: string): boolean {\r\n return this.activeId() === id;\r\n }\r\n\r\n onItemClick(item: SidebarItem): void {\r\n if (item.disabled) return;\r\n if (item.children?.length) {\r\n this.expanded.update((s) => {\r\n const next = new Set(s);\r\n next.has(item.id) ? next.delete(item.id) : next.add(item.id);\r\n return next;\r\n });\r\n return;\r\n }\r\n if (item.nonRoute) {\r\n this.itemClick.emit(item.id);\r\n return;\r\n }\r\n this.activeId.set(item.id);\r\n }\r\n\r\n /**\r\n * Looks up a Lucide icon by name and injects explicit width/height attributes\r\n * so the SVG renders at the correct size when injected via [innerHTML].\r\n * Angular's CSS encapsulation does not add _ngcontent attributes to dynamically\r\n * injected content, so explicit SVG attributes are the reliable solution.\r\n */\r\n iconSvg(name: string, size = 20, strokeWidth = 1.5): SafeHtml {\r\n let svg = LUCIDE_ICONS[name] ?? '';\r\n svg = svg.replace('<svg ', `<svg width=\"${size}\" height=\"${size}\" `);\r\n if (strokeWidth !== 1.5) {\r\n svg = svg.replace('stroke-width=\"1.5\"', `stroke-width=\"${strokeWidth}\"`);\r\n }\r\n return this.sanitizer.bypassSecurityTrustHtml(svg);\r\n }\r\n}\r\n","<div class=\"cs-sb__scroll\">\r\n <ul class=\"cs-sb__list\">\r\n @for (item of items(); track item.id) {\r\n @if (item.dividerBefore) {\r\n <li class=\"cs-sb__divider\" aria-hidden=\"true\"></li>\r\n }\r\n <li class=\"cs-sb__item\">\r\n <button\r\n type=\"button\"\r\n class=\"cs-sb__row\"\r\n [class.cs-sb__row--active]=\"!item.nonRoute && isActive(item.id)\"\r\n [disabled]=\"item.disabled || null\"\r\n [attr.aria-current]=\"!item.nonRoute && isActive(item.id) ? 'page' : null\"\r\n [attr.aria-expanded]=\"item.children?.length ? isExpanded(item.id) : null\"\r\n (click)=\"onItemClick(item)\"\r\n >\r\n <span\r\n class=\"cs-sb__icon\"\r\n [innerHTML]=\"iconSvg(item.icon, 20, !item.nonRoute && isActive(item.id) ? 2 : 1.5)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span class=\"cs-sb__label\">{{ item.label }}</span>\r\n @if (item.children?.length) {\r\n <span\r\n class=\"cs-sb__chevron\"\r\n [class.cs-sb__chevron--open]=\"isExpanded(item.id)\"\r\n [innerHTML]=\"iconSvg('chevron-down', 18)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n }\r\n </button>\r\n\r\n @if (item.children?.length && isExpanded(item.id)) {\r\n <ul class=\"cs-sb__sublist\">\r\n @for (child of item.children; track child.id) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"cs-sb__row cs-sb__row--sub\"\r\n [class.cs-sb__row--active]=\"isActive(child.id)\"\r\n [disabled]=\"child.disabled || null\"\r\n [attr.aria-current]=\"isActive(child.id) ? 'page' : null\"\r\n (click)=\"onItemClick(child)\"\r\n >\r\n <span\r\n class=\"cs-sb__icon\"\r\n [innerHTML]=\"iconSvg(child.icon, 20, isActive(child.id) ? 2 : 1.5)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span class=\"cs-sb__label\">{{ child.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</div>\r\n\r\n<div class=\"cs-sb__footer\">\r\n <ul class=\"cs-sb__list\">\r\n @for (item of footerItems(); track item.id) {\r\n <li class=\"cs-sb__item\">\r\n <button\r\n type=\"button\"\r\n class=\"cs-sb__row\"\r\n [class.cs-sb__row--active]=\"!item.nonRoute && isActive(item.id)\"\r\n [attr.aria-current]=\"!item.nonRoute && isActive(item.id) ? 'page' : null\"\r\n [disabled]=\"item.disabled || null\"\r\n (click)=\"onItemClick(item)\"\r\n >\r\n <span\r\n class=\"cs-sb__icon\"\r\n [innerHTML]=\"iconSvg(item.icon, 20, !item.nonRoute && isActive(item.id) ? 2 : 1.5)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span class=\"cs-sb__label\">{{ item.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n</div>\r\n"]}
package/index.d.ts CHANGED
@@ -1,18 +1,21 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import * as i1 from "./lib/button/button";
3
3
  import * as i2 from "./lib/accordion/accordion";
4
- import * as i3 from "./lib/header-nav/header-nav";
5
- import * as i4 from "./lib/sidebar/sidebar";
4
+ import * as i3 from "./lib/footer/footer";
5
+ import * as i4 from "./lib/header-nav/header-nav";
6
+ import * as i5 from "./lib/sidebar/sidebar";
6
7
  export { ButtonComponent } from './lib/button/button';
7
- export type { ButtonVariant } from './lib/button/button';
8
+ export type { ButtonVariant, ButtonSize, ButtonType } from './lib/button/button';
8
9
  export { AccordionComponent } from './lib/accordion/accordion';
9
10
  export type { AccordionItem, AccordionButton, AccordionVariant, NestedAccordionItem } from './lib/accordion/accordion-types';
11
+ export { FooterComponent } from './lib/footer/footer';
12
+ export type { FooterLink } from './lib/footer/footer';
10
13
  export { HeaderNavComponent } from './lib/header-nav/header-nav';
11
14
  export type { HeaderNavLevel, NavAction } from './lib/header-nav/header-nav';
12
15
  export { SidebarComponent } from './lib/sidebar/sidebar';
13
16
  export type { SidebarItem } from './lib/sidebar/sidebar';
14
17
  export declare class CompsychAngularModule {
15
18
  static ɵfac: i0.ɵɵFactoryDeclaration<CompsychAngularModule, never>;
16
- static ɵmod: i0.ɵɵNgModuleDeclaration<CompsychAngularModule, never, [typeof i1.ButtonComponent, typeof i2.AccordionComponent, typeof i3.HeaderNavComponent, typeof i4.SidebarComponent], [typeof i1.ButtonComponent, typeof i2.AccordionComponent, typeof i3.HeaderNavComponent, typeof i4.SidebarComponent]>;
19
+ static ɵmod: i0.ɵɵNgModuleDeclaration<CompsychAngularModule, never, [typeof i1.ButtonComponent, typeof i2.AccordionComponent, typeof i3.FooterComponent, typeof i4.HeaderNavComponent, typeof i5.SidebarComponent], [typeof i1.ButtonComponent, typeof i2.AccordionComponent, typeof i3.FooterComponent, typeof i4.HeaderNavComponent, typeof i5.SidebarComponent]>;
17
20
  static ɵinj: i0.ɵɵInjectorDeclaration<CompsychAngularModule>;
18
21
  }
@@ -1,38 +1,81 @@
1
- import { AfterViewInit, OnDestroy } from '@angular/core';
2
- import { FocusOrigin } from '@angular/cdk/a11y';
3
- import { BooleanInput } from '@angular/cdk/coercion';
1
+ import { OnChanges } from '@angular/core';
2
+ import { SafeHtml } from '@angular/platform-browser';
4
3
  import * as i0 from "@angular/core";
5
4
  /** Visual style of a `compsych-button`. Controls color, border, and elevation. */
6
5
  export type ButtonVariant = 'filled' | 'tonal' | 'outlined' | 'elevated' | 'text' | 'danger' | 'danger-outlined';
7
- export declare class ButtonComponent implements AfterViewInit, OnDestroy {
8
- private readonly focusMonitor;
9
- private readonly elementRef;
6
+ /** Button dimensions controls height, padding, font size, and icon size. */
7
+ export type ButtonSize = 'small' | 'medium' | 'large' | 'xlarge';
8
+ /** Native button behaviour. `'submit'`/`'reset'` make the component a real form control. */
9
+ export type ButtonType = 'button' | 'submit' | 'reset';
10
+ /**
11
+ * A themeable button that renders a native `<button>` element, so it is fully
12
+ * form-submittable and keyboard/screen-reader accessible out of the box. Icons
13
+ * are Lucide glyphs (matching the rest of the library); a button may carry a
14
+ * leading icon, a trailing icon, or both.
15
+ */
16
+ export declare class ButtonComponent implements OnChanges {
17
+ private readonly sanitizer;
10
18
  /** Text label rendered on the button. */
11
19
  label: string;
12
20
  /** Visual style of the button. Defaults to `'filled'`. */
13
21
  variant: ButtonVariant;
14
- /** Button dimensions — controls height, padding, and font size. Defaults to `'small'`. */
15
- size: 'small' | 'medium' | 'large' | 'xlarge';
16
- /** Material Symbols icon name shown to the left of the label (e.g. `'search'`, `'add'`). Omit to show label only. */
17
- icon?: string;
18
- /** When true, hides the label and renders a square icon-only button. Requires `icon` to be set. Supports boolean coercion. */
19
- get iconOnly(): boolean;
20
- set iconOnly(value: BooleanInput);
21
- private _iconOnly;
22
- /** When true, the button is non-interactive — `disabled` and `aria-disabled` attributes are set and `tabIndex` is set to `-1`. Supports boolean coercion. */
23
- get disabled(): boolean;
24
- set disabled(value: BooleanInput);
25
- private _disabled;
26
- protected _focused: boolean;
27
- protected _focusOrigin: FocusOrigin;
28
- get hostClass(): string;
29
- get ariaDisabled(): string | null;
30
- get attrDisabled(): '' | null;
31
- onEnterKeydown(event: Event): void;
32
- onSpaceKeydown(event: Event): void;
33
- onSpaceKeyup(): void;
34
- ngAfterViewInit(): void;
35
- ngOnDestroy(): void;
22
+ /** Button dimensions — controls height, padding, font size, and icon size. Defaults to `'small'`. */
23
+ size: ButtonSize;
24
+ /** Lucide icon name shown before the label (e.g. `'house'`). Swapped for a spinner when loading. */
25
+ leadingIcon?: string;
26
+ /** Lucide icon name shown after the label (e.g. `'chevron-right'`). Can be combined with `leadingIcon`. */
27
+ trailingIcon?: string;
28
+ /** Native button type. Use `'submit'` inside a `<form>` to submit it. Defaults to `'button'`. */
29
+ type: ButtonType;
30
+ /**
31
+ * Accessible name. Required for icon-only buttons since the icon is decorative.
32
+ * When `iconOnly` or `loading` is set and this is omitted, `label` is used.
33
+ */
34
+ ariaLabel?: string;
35
+ /** Hides the label and renders a square icon-only button. Uses `leadingIcon`. Supports boolean coercion. */
36
+ iconOnly: boolean;
37
+ /** Disables the button — non-interactive and rendered in a neutral grey. Supports boolean coercion. */
38
+ disabled: boolean;
39
+ /**
40
+ * Loading state. Swaps the leading icon for a spinner and suppresses activation
41
+ * (native `disabled` blocks click/keyboard), while keeping the label and exposing
42
+ * `aria-busy="true"`. Retains the variant's colours. Supports boolean coercion.
43
+ */
44
+ loading: boolean;
45
+ /** Stretches the button to the full width of its container. Supports boolean coercion. */
46
+ fullWidth: boolean;
47
+ /** Mirrors `fullWidth` onto the host so it can stretch. */
48
+ get hostFullWidth(): boolean;
49
+ /** Pre-sanitized icon markup, recomputed on input change (avoids re-sanitizing every CD cycle). */
50
+ protected leadingIconSvg: SafeHtml | null;
51
+ protected trailingIconSvg: SafeHtml | null;
52
+ ngOnChanges(): void;
53
+ /** Space-separated class list applied to the inner native button. */
54
+ get buttonClasses(): string;
55
+ /** Resolves the accessible name. Icon-only buttons have no visible text, so they fall back to `label`. */
56
+ get computedAriaLabel(): string | null;
57
+ /**
58
+ * Looks up a Lucide icon by name and injects an explicit width/height so the SVG
59
+ * renders at the correct size when injected via `[innerHTML]` (Angular's CSS
60
+ * encapsulation does not reach dynamically injected content).
61
+ */
62
+ private buildIconSvg;
63
+ /** M3 pressed ripple: pointer-down spawns a ripple at the pointer position. */
64
+ onPointerDown(event: PointerEvent): void;
65
+ /** M3 pressed ripple: keyboard activation (Enter/Space) spawns a centered ripple. */
66
+ onKeyDown(event: KeyboardEvent): void;
67
+ /** Diameter that reaches the farthest corner from (x, y), so the ripple always covers the button. */
68
+ private farthestCornerDiameter;
69
+ /**
70
+ * Spawns an expanding ripple circle. Created imperatively with inline styles and
71
+ * animated via the Web Animations API — Angular's emulated encapsulation does not
72
+ * reach dynamically created nodes, so it cannot rely on the component stylesheet.
73
+ */
74
+ private spawnRipple;
36
75
  static ɵfac: i0.ɵɵFactoryDeclaration<ButtonComponent, never>;
37
- static ɵcmp: i0.ɵɵComponentDeclaration<ButtonComponent, "compsych-button", never, { "label": { "alias": "label"; "required": false; }; "variant": { "alias": "variant"; "required": false; }; "size": { "alias": "size"; "required": false; }; "icon": { "alias": "icon"; "required": false; }; "iconOnly": { "alias": "iconOnly"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; }, {}, never, never, true, never>;
76
+ static ɵcmp: i0.ɵɵComponentDeclaration<ButtonComponent, "compsych-button", never, { "label": { "alias": "label"; "required": false; }; "variant": { "alias": "variant"; "required": false; }; "size": { "alias": "size"; "required": false; }; "leadingIcon": { "alias": "leadingIcon"; "required": false; }; "trailingIcon": { "alias": "trailingIcon"; "required": false; }; "type": { "alias": "type"; "required": false; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; }; "iconOnly": { "alias": "iconOnly"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "fullWidth": { "alias": "fullWidth"; "required": false; }; }, {}, never, ["*"], true, never>;
77
+ static ngAcceptInputType_iconOnly: unknown;
78
+ static ngAcceptInputType_disabled: unknown;
79
+ static ngAcceptInputType_loading: unknown;
80
+ static ngAcceptInputType_fullWidth: unknown;
38
81
  }
@@ -0,0 +1,50 @@
1
+ import { ElementRef, OnChanges } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ /** A single navigation link in the footer. */
4
+ export interface FooterLink {
5
+ /** Text displayed in the link. */
6
+ label: string;
7
+ /** URL the link navigates to. */
8
+ href: string;
9
+ }
10
+ /**
11
+ * Footer — bottom bar from the ComPsych Design System.
12
+ *
13
+ * Displays a copyright notice and an optional list of navigation links.
14
+ * On initialisation the component measures the natural widths of the copyright
15
+ * and links to establish a breakpoint. On every window resize it compares the
16
+ * host width against that breakpoint and switches between a row layout
17
+ * (copyright left, links right) and a stacked, centered column layout.
18
+ */
19
+ export declare class FooterComponent implements OnChanges {
20
+ private readonly el;
21
+ /**
22
+ * Navigation links rendered alongside the copyright notice.
23
+ * The layout switches automatically based on their combined natural width.
24
+ */
25
+ links: FooterLink[];
26
+ /**
27
+ * Copyright notice generated from the current calendar year.
28
+ * Left-aligned in the row layout and centered when stacked.
29
+ */
30
+ readonly copyright: string;
31
+ /**
32
+ * Reflects whether the row layout is active.
33
+ * Signal so OnPush picks up DOM-driven updates from async callbacks.
34
+ */
35
+ protected readonly isRow: import("@angular/core").WritableSignal<boolean>;
36
+ /** Combined natural width of copyright + links + gap + padding. */
37
+ private breakpoint;
38
+ constructor(el: ElementRef<HTMLElement>);
39
+ ngOnChanges(): void;
40
+ protected onResize(): void;
41
+ /**
42
+ * Measures the natural widths of the copyright and links elements once and
43
+ * stores their sum (plus padding and gap) as the layout breakpoint.
44
+ */
45
+ private measureBreakpoint;
46
+ /** Switches to the row layout when the host is wide enough for both children. */
47
+ private checkLayout;
48
+ static ɵfac: i0.ɵɵFactoryDeclaration<FooterComponent, never>;
49
+ static ɵcmp: i0.ɵɵComponentDeclaration<FooterComponent, "compsych-footer", never, { "links": { "alias": "links"; "required": false; }; }, {}, never, never, true, never>;
50
+ }
@@ -15,6 +15,14 @@ export interface NavAction {
15
15
  label: string;
16
16
  /** Button style for action buttons. Defaults to `'filled'` when omitted. Not used for breadcrumbs. */
17
17
  variant?: ButtonVariant;
18
+ /** Lucide icon shown before the label (e.g. `'house'`). Not used for breadcrumbs. */
19
+ leadingIcon?: string;
20
+ /** Lucide icon shown after the label (e.g. `'chevron-right'`). Not used for breadcrumbs. */
21
+ trailingIcon?: string;
22
+ /** Renders the action button disabled. */
23
+ disabled?: boolean;
24
+ /** Renders the action button in a loading state (spinner, interaction blocked). */
25
+ loading?: boolean;
18
26
  }
19
27
  /**
20
28
  * Header Navigation — top app bar from the ComPsych Design System.
@@ -119,10 +127,10 @@ export declare class HeaderNavComponent {
119
127
  get notificationAriaLabel(): string;
120
128
  /**
121
129
  * Looks up a Lucide icon by name and injects explicit width/height attributes
122
- * (20 × 20) so the SVG renders at the correct size when injected via [innerHTML].
123
- * Angular's CSS encapsulation does not add _ngcontent attributes to dynamically
124
- * injected content, so CSS descendant selectors targeting `svg` would not apply —
125
- * explicit SVG attributes are the reliable solution.
130
+ * (default 20 × 20) so the SVG renders at the correct size when injected via
131
+ * [innerHTML]. Angular's CSS encapsulation does not add _ngcontent attributes to
132
+ * dynamically injected content, so CSS descendant selectors targeting `svg` would
133
+ * not apply — explicit SVG attributes are the reliable solution.
126
134
  */
127
135
  private trustedIcon;
128
136
  static ɵfac: i0.ɵɵFactoryDeclaration<HeaderNavComponent, never>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compsych-ui-components/angular",
3
- "version": "6.1.0",
3
+ "version": "6.1.2",
4
4
  "description": "Angular UI components for compsych",
5
5
  "publishConfig": {
6
6
  "access": "public",