@compsych-ui-components/angular 7.0.4 → 7.0.5
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/esm2022/index.js +36 -3
- package/esm2022/index.js.map +1 -1
- package/esm2022/lib/card-base/card.js +158 -0
- package/esm2022/lib/card-base/card.js.map +1 -0
- package/esm2022/lib/card-base/ripple.directive.js +106 -0
- package/esm2022/lib/card-base/ripple.directive.js.map +1 -0
- package/esm2022/lib/card-grid/card-grid.js +40 -0
- package/esm2022/lib/card-grid/card-grid.js.map +1 -0
- package/esm2022/lib/footer/footer.js +30 -38
- package/esm2022/lib/footer/footer.js.map +1 -1
- package/index.d.ts +8 -1
- package/lib/card-base/card.d.ts +94 -0
- package/lib/card-base/ripple.directive.d.ts +31 -0
- package/lib/card-grid/card-grid.d.ts +26 -0
- package/lib/footer/footer.d.ts +12 -21
- package/package.json +1 -1
package/esm2022/index.js
CHANGED
|
@@ -4,22 +4,55 @@ import { AccordionComponent } from './lib/accordion/accordion';
|
|
|
4
4
|
import { FooterComponent } from './lib/footer/footer';
|
|
5
5
|
import { HeaderNavComponent } from './lib/header-nav/header-nav';
|
|
6
6
|
import { SidebarComponent } from './lib/sidebar/sidebar';
|
|
7
|
+
import { CardComponent } from './lib/card-base/card';
|
|
8
|
+
import { CardGridComponent } from './lib/card-grid/card-grid';
|
|
7
9
|
import * as i0 from "@angular/core";
|
|
8
10
|
export { ButtonComponent } from './lib/button/button';
|
|
9
11
|
export { AccordionComponent } from './lib/accordion/accordion';
|
|
10
12
|
export { FooterComponent } from './lib/footer/footer';
|
|
11
13
|
export { HeaderNavComponent } from './lib/header-nav/header-nav';
|
|
12
14
|
export { SidebarComponent } from './lib/sidebar/sidebar';
|
|
15
|
+
export { CardComponent } from './lib/card-base/card';
|
|
16
|
+
export { RippleDirective } from './lib/card-base/ripple.directive';
|
|
17
|
+
export { CardGridComponent } from './lib/card-grid/card-grid';
|
|
13
18
|
export class CompsychAngularModule {
|
|
14
19
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CompsychAngularModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
15
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.14", ngImport: i0, type: CompsychAngularModule, imports: [ButtonComponent,
|
|
20
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.14", ngImport: i0, type: CompsychAngularModule, imports: [ButtonComponent,
|
|
21
|
+
AccordionComponent,
|
|
22
|
+
FooterComponent,
|
|
23
|
+
HeaderNavComponent,
|
|
24
|
+
SidebarComponent,
|
|
25
|
+
CardComponent,
|
|
26
|
+
CardGridComponent], exports: [ButtonComponent,
|
|
27
|
+
AccordionComponent,
|
|
28
|
+
FooterComponent,
|
|
29
|
+
HeaderNavComponent,
|
|
30
|
+
SidebarComponent,
|
|
31
|
+
CardComponent,
|
|
32
|
+
CardGridComponent] });
|
|
16
33
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CompsychAngularModule });
|
|
17
34
|
}
|
|
18
35
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CompsychAngularModule, decorators: [{
|
|
19
36
|
type: NgModule,
|
|
20
37
|
args: [{
|
|
21
|
-
imports: [
|
|
22
|
-
|
|
38
|
+
imports: [
|
|
39
|
+
ButtonComponent,
|
|
40
|
+
AccordionComponent,
|
|
41
|
+
FooterComponent,
|
|
42
|
+
HeaderNavComponent,
|
|
43
|
+
SidebarComponent,
|
|
44
|
+
CardComponent,
|
|
45
|
+
CardGridComponent,
|
|
46
|
+
],
|
|
47
|
+
exports: [
|
|
48
|
+
ButtonComponent,
|
|
49
|
+
AccordionComponent,
|
|
50
|
+
FooterComponent,
|
|
51
|
+
HeaderNavComponent,
|
|
52
|
+
SidebarComponent,
|
|
53
|
+
CardComponent,
|
|
54
|
+
CardGridComponent,
|
|
55
|
+
],
|
|
23
56
|
}]
|
|
24
57
|
}] });
|
|
25
58
|
//# sourceMappingURL=index.js.map
|
package/esm2022/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/angular/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/angular/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;;AAE9D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAuB9D,MAAM,OAAO,qBAAqB;wGAArB,qBAAqB;yGAArB,qBAAqB,YAlB9B,eAAe;YACf,kBAAkB;YAClB,eAAe;YACf,kBAAkB;YAClB,gBAAgB;YAChB,aAAa;YACb,iBAAiB,aAGjB,eAAe;YACf,kBAAkB;YAClB,eAAe;YACf,kBAAkB;YAClB,gBAAgB;YAChB,aAAa;YACb,iBAAiB;yGAGR,qBAAqB;;4FAArB,qBAAqB;kBApBjC,QAAQ;mBAAC;oBACR,OAAO,EAAE;wBACP,eAAe;wBACf,kBAAkB;wBAClB,eAAe;wBACf,kBAAkB;wBAClB,gBAAgB;wBAChB,aAAa;wBACb,iBAAiB;qBAClB;oBACD,OAAO,EAAE;wBACP,eAAe;wBACf,kBAAkB;wBAClB,eAAe;wBACf,kBAAkB;wBAClB,gBAAgB;wBAChB,aAAa;wBACb,iBAAiB;qBAClB;iBACF","sourcesContent":["import { NgModule } from '@angular/core';\r\nimport { ButtonComponent } from './lib/button/button';\r\nimport { AccordionComponent } from './lib/accordion/accordion';\r\nimport { FooterComponent } from './lib/footer/footer';\r\nimport { HeaderNavComponent } from './lib/header-nav/header-nav';\r\nimport { SidebarComponent } from './lib/sidebar/sidebar';\r\nimport { CardComponent } from './lib/card-base/card';\r\nimport { CardGridComponent } from './lib/card-grid/card-grid';\r\n\r\nexport { ButtonComponent } from './lib/button/button';\r\nexport type { ButtonVariant, ButtonSize, ButtonType } from './lib/button/button';\r\nexport { AccordionComponent } from './lib/accordion/accordion';\r\nexport type { AccordionItem, AccordionButton, AccordionVariant, NestedAccordionItem } from './lib/accordion/accordion-types';\r\nexport { FooterComponent } from './lib/footer/footer';\r\nexport type { FooterLink } from './lib/footer/footer';\r\nexport { HeaderNavComponent } from './lib/header-nav/header-nav';\r\nexport type { HeaderNavLevel, NavAction } from './lib/header-nav/header-nav';\r\nexport { SidebarComponent } from './lib/sidebar/sidebar';\r\nexport type { SidebarItem } from './lib/sidebar/sidebar';\r\nexport { CardComponent } from './lib/card-base/card';\r\nexport type { CardVariant, CardAppearance, CardRadius, CardPadding, CardOrientation } from './lib/card-base/card';\r\nexport { RippleDirective } from './lib/card-base/ripple.directive';\r\nexport { CardGridComponent } from './lib/card-grid/card-grid';\r\nexport type { CardGridLayout } from './lib/card-grid/card-grid';\r\n\r\n@NgModule({\r\n imports: [\r\n ButtonComponent,\r\n AccordionComponent,\r\n FooterComponent,\r\n HeaderNavComponent,\r\n SidebarComponent,\r\n CardComponent,\r\n CardGridComponent,\r\n ],\r\n exports: [\r\n ButtonComponent,\r\n AccordionComponent,\r\n FooterComponent,\r\n HeaderNavComponent,\r\n SidebarComponent,\r\n CardComponent,\r\n CardGridComponent,\r\n ],\r\n})\r\nexport class CompsychAngularModule {}\r\n"]}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, effect, inject, input, output, } from '@angular/core';
|
|
2
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
3
|
+
import { LUCIDE_ICONS } from '@compsych-ui-components/shared/assets/lucide-icons.generated';
|
|
4
|
+
import { RippleDirective } from './ripple.directive';
|
|
5
|
+
import * as i0 from "@angular/core";
|
|
6
|
+
import * as i1 from "./ripple.directive";
|
|
7
|
+
/**
|
|
8
|
+
* Card — the single Cards component for the ComPsych Design System.
|
|
9
|
+
*
|
|
10
|
+
* Figma: Core Components — "Cards" (file VFBn7KCDy3FSIlvhNo3ylq, 1117:9829).
|
|
11
|
+
*
|
|
12
|
+
* One component, switched by `variant` (like Accordion's `variant`):
|
|
13
|
+
* - `basic` — a bare surface; project your own content.
|
|
14
|
+
* - `content` — icon badge / image, category chip, title, subtitle, description,
|
|
15
|
+
* action pill, arrow; hero (`headline`) and journey (`plainIcon`) layouts.
|
|
16
|
+
* - `action` — compact interactive row (icon · label/description · arrow); horizontal/vertical.
|
|
17
|
+
* - `metric` — centered value + label; `current` (selected) state.
|
|
18
|
+
*
|
|
19
|
+
* `appearance` is the orthogonal surface skin (outlined / filled / gradient / image).
|
|
20
|
+
* When `interactive`, the whole card is a button (role/tabindex/keyboard) with the
|
|
21
|
+
* hover / focus / pressed ripple. All values are sys design tokens.
|
|
22
|
+
*
|
|
23
|
+
* <compsych-card variant="content" appearance="gradient" icon="circle-play"
|
|
24
|
+
* category="Video" title="Mindful breathing" actionLabel="Watch" [interactive]="true" />
|
|
25
|
+
* <compsych-card variant="metric" value="24" label="Sessions" [current]="true" [interactive]="true" />
|
|
26
|
+
* <compsych-card>Projected content</compsych-card>
|
|
27
|
+
*/
|
|
28
|
+
export class CardComponent {
|
|
29
|
+
// ── Surface (all variants) ──────────────────────────────
|
|
30
|
+
/** Card type. Defaults to `basic` (projected content). */
|
|
31
|
+
variant = input('basic', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
32
|
+
/** Surface treatment. Defaults to `outlined`. */
|
|
33
|
+
appearance = input('outlined', ...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
|
|
34
|
+
/** Corner radius token. Defaults to `lg` (16px). */
|
|
35
|
+
radius = input('lg', ...(ngDevMode ? [{ debugName: "radius" }] : /* istanbul ignore next */ []));
|
|
36
|
+
/** Inner padding for `variant="basic"`. Typed variants size themselves. */
|
|
37
|
+
padding = input('lg', ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
|
|
38
|
+
/** Whole-card click target: adds role/tabindex, hover/press states and ripple. */
|
|
39
|
+
interactive = input(false, ...(ngDevMode ? [{ debugName: "interactive" }] : /* istanbul ignore next */ []));
|
|
40
|
+
/** Disabled visual + non-interactive. */
|
|
41
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
42
|
+
/** Background image URL for `appearance="image"`. */
|
|
43
|
+
imageSrc = input('', ...(ngDevMode ? [{ debugName: "imageSrc" }] : /* istanbul ignore next */ []));
|
|
44
|
+
// ── Content variant ─────────────────────────────────────
|
|
45
|
+
/** Lucide icon name (content badge / action leading icon). */
|
|
46
|
+
icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
47
|
+
/** Content: render the icon as a large plain glyph, text anchored bottom (journey layout). */
|
|
48
|
+
plainIcon = input(false, ...(ngDevMode ? [{ debugName: "plainIcon" }] : /* istanbul ignore next */ []));
|
|
49
|
+
/** Content: category chip text. */
|
|
50
|
+
category = input('', ...(ngDevMode ? [{ debugName: "category" }] : /* istanbul ignore next */ []));
|
|
51
|
+
/** Content: title. */
|
|
52
|
+
title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
53
|
+
/** Content: subtitle. */
|
|
54
|
+
subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
|
|
55
|
+
/** Content/action: supporting description. */
|
|
56
|
+
description = input('', ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
|
|
57
|
+
/** Content: large hero display text (switches to the XL hero layout). */
|
|
58
|
+
headline = input('', ...(ngDevMode ? [{ debugName: "headline" }] : /* istanbul ignore next */ []));
|
|
59
|
+
/** Content: presentational action pill label. */
|
|
60
|
+
actionLabel = input('', ...(ngDevMode ? [{ debugName: "actionLabel" }] : /* istanbul ignore next */ []));
|
|
61
|
+
/** Content: show the trailing arrow-up-right affordance. */
|
|
62
|
+
showArrow = input(true, ...(ngDevMode ? [{ debugName: "showArrow" }] : /* istanbul ignore next */ []));
|
|
63
|
+
// ── Action variant ──────────────────────────────────────
|
|
64
|
+
/** Action/metric: primary label. */
|
|
65
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
66
|
+
/** Action: trailing Lucide icon (horizontal layout). */
|
|
67
|
+
trailingIcon = input('arrow-up-right', ...(ngDevMode ? [{ debugName: "trailingIcon" }] : /* istanbul ignore next */ []));
|
|
68
|
+
/** Action: layout orientation. */
|
|
69
|
+
orientation = input('horizontal', ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
70
|
+
// ── Metric variant ──────────────────────────────────────
|
|
71
|
+
/** Metric: the prominent value. */
|
|
72
|
+
value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
73
|
+
/** Metric: selected/current state (primary border + text). */
|
|
74
|
+
current = input(false, ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
|
|
75
|
+
/** Emits on click / Enter / Space when interactive and not disabled. */
|
|
76
|
+
cardClick = output();
|
|
77
|
+
sanitizer = inject(DomSanitizer);
|
|
78
|
+
ripple = inject(RippleDirective);
|
|
79
|
+
isImage = computed(() => this.appearance() === 'image', ...(ngDevMode ? [{ debugName: "isImage" }] : /* istanbul ignore next */ []));
|
|
80
|
+
/** CSS `url(...)` for the media layer, or null when no image is set. */
|
|
81
|
+
mediaUrl = computed(() => {
|
|
82
|
+
const src = this.imageSrc();
|
|
83
|
+
return src ? `url("${src}")` : null;
|
|
84
|
+
}, ...(ngDevMode ? [{ debugName: "mediaUrl" }] : /* istanbul ignore next */ []));
|
|
85
|
+
/** Padding resolved per variant; `basic` honors the `padding` input. */
|
|
86
|
+
effectivePadding = computed(() => {
|
|
87
|
+
switch (this.variant()) {
|
|
88
|
+
case 'content':
|
|
89
|
+
return this.isImage() || this.headline() ? 'none' : this.plainIcon() ? 'xl' : 'lg';
|
|
90
|
+
case 'action':
|
|
91
|
+
return this.orientation() === 'vertical' ? 'lg' : 'sm';
|
|
92
|
+
case 'metric':
|
|
93
|
+
return 'sm';
|
|
94
|
+
default:
|
|
95
|
+
return this.padding();
|
|
96
|
+
}
|
|
97
|
+
}, ...(ngDevMode ? [{ debugName: "effectivePadding" }] : /* istanbul ignore next */ []));
|
|
98
|
+
constructor() {
|
|
99
|
+
effect(() => {
|
|
100
|
+
this.ripple.suppressed = !this.interactive() || this.disabled();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
onActivate() {
|
|
104
|
+
if (!this.interactive() || this.disabled())
|
|
105
|
+
return;
|
|
106
|
+
this.cardClick.emit();
|
|
107
|
+
}
|
|
108
|
+
onKeyDown(event) {
|
|
109
|
+
if (!this.interactive() || this.disabled())
|
|
110
|
+
return;
|
|
111
|
+
if (event.repeat)
|
|
112
|
+
return;
|
|
113
|
+
if (event.key !== 'Enter' && event.key !== ' ')
|
|
114
|
+
return;
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
this.cardClick.emit();
|
|
117
|
+
}
|
|
118
|
+
/** Renders a sized/weighted Lucide SVG for template use. */
|
|
119
|
+
iconSvg(name, size, strokeWidth = 1.75) {
|
|
120
|
+
let svg = LUCIDE_ICONS[name] ?? '';
|
|
121
|
+
svg = svg.replace('<svg ', `<svg width="${size}" height="${size}" `);
|
|
122
|
+
if (strokeWidth !== 1.5) {
|
|
123
|
+
svg = svg.replace('stroke-width="1.5"', `stroke-width="${strokeWidth}"`);
|
|
124
|
+
}
|
|
125
|
+
return this.sanitizer.bypassSecurityTrustHtml(svg);
|
|
126
|
+
}
|
|
127
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
128
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: CardComponent, isStandalone: true, selector: "compsych-card", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, radius: { classPropertyName: "radius", publicName: "radius", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, imageSrc: { classPropertyName: "imageSrc", publicName: "imageSrc", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, plainIcon: { classPropertyName: "plainIcon", publicName: "plainIcon", isSignal: true, isRequired: false, transformFunction: null }, category: { classPropertyName: "category", publicName: "category", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, headline: { classPropertyName: "headline", publicName: "headline", isSignal: true, isRequired: false, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, showArrow: { classPropertyName: "showArrow", publicName: "showArrow", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, trailingIcon: { classPropertyName: "trailingIcon", publicName: "trailingIcon", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, current: { classPropertyName: "current", publicName: "current", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardClick: "cardClick" }, host: { listeners: { "click": "onActivate()", "keydown": "onKeyDown($event)" }, properties: { "class.cmp-card--outlined": "appearance() === 'outlined'", "class.cmp-card--filled": "appearance() === 'filled'", "class.cmp-card--gradient": "appearance() === 'gradient'", "class.cmp-card--image": "appearance() === 'image'", "class.cmp-card--radius-md": "radius() === 'md'", "class.cmp-card--radius-lg": "radius() === 'lg'", "class.cmp-card--radius-xl": "radius() === 'xl'", "class.cmp-card--pad-none": "effectivePadding() === 'none'", "class.cmp-card--pad-sm": "effectivePadding() === 'sm'", "class.cmp-card--pad-md": "effectivePadding() === 'md'", "class.cmp-card--pad-lg": "effectivePadding() === 'lg'", "class.cmp-card--pad-xl": "effectivePadding() === 'xl'", "class.cmp-card--interactive": "interactive()", "class.cmp-card--disabled": "disabled()", "attr.role": "interactive() ? 'button' : null", "attr.tabindex": "interactive() && !disabled() ? 0 : null", "attr.aria-disabled": "disabled() || null", "attr.aria-pressed": "interactive() && variant() === 'metric' ? current() : null", "style.border-color": "variant() === 'metric' && current() ? 'var(--accent-primary-sys-primary)' : null", "style.border-width": "variant() === 'metric' && current() ? 'var(--border-width-sys-stroke-thick)' : null" }, classAttribute: "cmp-card" }, hostDirectives: [{ directive: i1.RippleDirective }], ngImport: i0, template: "@if (isImage() && mediaUrl()) {\r\n <div class=\"cmp-card__media\" [style.background-image]=\"mediaUrl()\"></div>\r\n <div class=\"cmp-card__scrim\" aria-hidden=\"true\"></div>\r\n}\r\n\r\n<div class=\"cmp-card__body\">\r\n @switch (variant()) {\r\n @case ('content') {\r\n @if (headline()) {\r\n <!-- Hero (image/filled): icon top-left, eyebrow + display headline + action bottom. -->\r\n <div class=\"cc cc--hero\" [class.cc--image]=\"isImage()\" [class.cc--filled]=\"appearance() === 'filled'\">\r\n @if (icon()) {\r\n @if (isImage()) {\r\n <div class=\"cc__media-icon\" [innerHTML]=\"iconSvg(icon(), 28)\" aria-hidden=\"true\"></div>\r\n } @else {\r\n <div class=\"cc__icon\" [innerHTML]=\"iconSvg(icon(), 24)\" aria-hidden=\"true\"></div>\r\n }\r\n }\r\n <div class=\"cc__img-foot\">\r\n <div class=\"cc__hero-text\">\r\n @if (title()) { <p class=\"cc__eyebrow\">{{ title() }}</p> }\r\n <p class=\"cc__headline\">{{ headline() }}</p>\r\n @if (description()) { <p class=\"cc__desc\">{{ description() }}</p> }\r\n </div>\r\n @if (actionLabel()) {\r\n <span class=\"cc__button cc__button--solid\">{{ actionLabel() }}</span>\r\n } @else if (showArrow()) {\r\n <span class=\"cc__arrow\" [innerHTML]=\"iconSvg('arrow-up-right', 24)\" aria-hidden=\"true\"></span>\r\n }\r\n </div>\r\n </div>\r\n } @else if (isImage()) {\r\n <div class=\"cc cc--image\">\r\n <div class=\"cc__img-top\">\r\n @if (category()) { <span class=\"cc__chip cc__chip--glass\">{{ category() }}</span> }\r\n @if (title() || subtitle()) {\r\n <div class=\"cc__heading\">\r\n @if (title()) { <p class=\"cc__title\">{{ title() }}</p> }\r\n @if (subtitle()) { <p class=\"cc__subtitle\">{{ subtitle() }}</p> }\r\n </div>\r\n }\r\n @if (description()) { <p class=\"cc__desc\">{{ description() }}</p> }\r\n </div>\r\n <div class=\"cc__img-foot cc__img-foot--end\">\r\n @if (actionLabel()) {\r\n <span class=\"cc__button cc__button--solid\">{{ actionLabel() }}</span>\r\n } @else if (showArrow()) {\r\n <span class=\"cc__arrow\" [innerHTML]=\"iconSvg('arrow-up-right', 24)\" aria-hidden=\"true\"></span>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"cc\" [class.cc--filled]=\"appearance() === 'filled'\" [class.cc--journey]=\"plainIcon()\">\r\n <div class=\"cc__top\">\r\n @if (icon()) {\r\n @if (plainIcon()) {\r\n <div class=\"cc__plain-icon\" [innerHTML]=\"iconSvg(icon(), 48, 1.5)\" aria-hidden=\"true\"></div>\r\n } @else {\r\n <div class=\"cc__icon\" [innerHTML]=\"iconSvg(icon(), 24)\" aria-hidden=\"true\"></div>\r\n }\r\n }\r\n <div class=\"cc__top-end\">\r\n @if (category()) { <span class=\"cc__chip\">{{ category() }}</span> }\r\n @if (showArrow()) {\r\n <span class=\"cc__arrow\" [innerHTML]=\"iconSvg('arrow-up-right', 24)\" aria-hidden=\"true\"></span>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"cc__text\">\r\n @if (title() || subtitle()) {\r\n <div class=\"cc__heading\">\r\n @if (title()) { <p class=\"cc__title\">{{ title() }}</p> }\r\n @if (subtitle()) { <p class=\"cc__subtitle\">{{ subtitle() }}</p> }\r\n </div>\r\n }\r\n @if (description()) { <p class=\"cc__desc\">{{ description() }}</p> }\r\n </div>\r\n @if (actionLabel()) { <span class=\"cc__button\">{{ actionLabel() }}</span> }\r\n </div>\r\n }\r\n }\r\n\r\n @case ('action') {\r\n @if (orientation() === 'vertical') {\r\n <div class=\"ac ac--vertical\">\r\n @if (icon()) {\r\n <div class=\"ac__badge\" [innerHTML]=\"iconSvg(icon(), 24, 1.5)\" aria-hidden=\"true\"></div>\r\n }\r\n <div class=\"ac__text\">\r\n <span class=\"ac__label\">{{ label() }}</span>\r\n @if (description()) { <span class=\"ac__desc\">{{ description() }}</span> }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"ac\">\r\n @if (icon()) {\r\n <div class=\"ac__icon\" [innerHTML]=\"iconSvg(icon(), 24)\" aria-hidden=\"true\"></div>\r\n }\r\n <div class=\"ac__text\">\r\n <span class=\"ac__label\">{{ label() }}</span>\r\n @if (description()) { <span class=\"ac__desc\">{{ description() }}</span> }\r\n </div>\r\n <span class=\"ac__arrow\" [innerHTML]=\"iconSvg(trailingIcon(), 20)\" aria-hidden=\"true\"></span>\r\n </div>\r\n }\r\n }\r\n\r\n @case ('metric') {\r\n <div class=\"mc\" [class.mc--current]=\"current()\">\r\n <span class=\"mc__value\">{{ value() }}</span>\r\n @if (label()) { <span class=\"mc__label\">{{ label() }}</span> }\r\n </div>\r\n }\r\n\r\n @default {\r\n <ng-content />\r\n }\r\n }\r\n</div>\r\n", styles: [":host{position:relative;display:flex;flex-direction:column;box-sizing:border-box;overflow:hidden;border-radius:var(--border-radius-sys-radius-lg);background-color:var(--surface-surface-container-sys-surface-container-lowest);color:var(--surface-surface-sys-on-surface);isolation:isolate}:host.cmp-card--radius-md{border-radius:var(--border-radius-sys-radius-md)}:host.cmp-card--radius-lg{border-radius:var(--border-radius-sys-radius-lg)}:host.cmp-card--radius-xl{border-radius:var(--border-radius-sys-radius-xl)}.cmp-card__body{position:relative;z-index:1;display:flex;flex-direction:column;flex:1 1 auto;min-height:0}:host.cmp-card--pad-none .cmp-card__body{padding:0}:host.cmp-card--pad-sm .cmp-card__body{padding:var(--spacing-padding-sys-padding-16)}:host.cmp-card--pad-md .cmp-card__body{padding:var(--spacing-padding-sys-padding-20)}:host.cmp-card--pad-lg .cmp-card__body{padding:var(--spacing-padding-sys-padding-24)}:host.cmp-card--pad-xl .cmp-card__body{padding:var(--spacing-padding-sys-padding-32)}:host.cmp-card--outlined{background-color:var(--surface-surface-container-sys-surface-container-lowest);border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline)}:host.cmp-card--filled{background-color:var(--accent-primary-sys-primary-container);color:var(--accent-primary-sys-on-primary-container)}:host.cmp-card--gradient{background-image:linear-gradient(180deg,var(--transparent-primary-sys-primary-08) 0%,var(--transparent-neutral-sys-white-10) 100%);background-color:var(--surface-surface-container-sys-surface-container-lowest);border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline)}:host.cmp-card--image{color:var(--accent-primary-sys-on-primary-container);background-color:var(--surface-surface-container-sys-surface-container-high)}.cmp-card__media{position:absolute;inset:0;z-index:0;background-size:cover;background-position:center}.cmp-card__scrim{position:absolute;inset:0;z-index:0;background-image:linear-gradient(180deg,var(--transparent-neutral-sys-black-40) 0%,var(--transparent-neutral-sys-black-10) 32%,var(--transparent-neutral-sys-black-10) 64%,var(--transparent-neutral-sys-black-50) 100%);mix-blend-mode:multiply}:host.cmp-card--interactive{cursor:pointer;transition:box-shadow .14s ease-out,background-color .14s ease-out,border-color .14s ease-out}:host.cmp-card--interactive:focus-visible{outline:none;border-color:var(--add-on-primary-fixed-sys-on-primary-fixed-variant);box-shadow:0 0 0 var(--spacing-padding-sys-padding-4) var(--transparent-primary-sys-primary-08)}:host.cmp-card--interactive.cmp-card--outlined:hover,:host.cmp-card--interactive.cmp-card--gradient:hover{background-color:var(--surface-surface-container-sys-surface-container);box-shadow:0 .25rem 1rem #0000001a,0 .125rem .25rem #00000014}:host.cmp-card--interactive.cmp-card--filled:hover,:host.cmp-card--interactive.cmp-card--image:hover{box-shadow:0 .25rem 1rem #0000002e,0 .125rem .25rem #0000001f}:host.cmp-card--disabled{pointer-events:none;opacity:.38}@media(prefers-reduced-motion:reduce){:host.cmp-card--interactive{transition:none}}@media(forced-colors:active){:host{border:1px solid CanvasText;forced-color-adjust:none}:host.cmp-card--interactive:focus-visible{outline:2px solid Highlight;outline-offset:2px}}.cc{display:flex;flex-direction:column;flex:1 1 auto;gap:var(--spacing-padding-sys-padding-24);font-family:var(--body-medium-sys-font-family)}.cc__top{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--spacing-padding-sys-padding-8)}.cc__icon{display:inline-flex;align-items:center;justify-content:center;width:var(--sys-size-xl);height:var(--sys-size-xl);flex-shrink:0;border-radius:var(--border-radius-sys-radius-full);background-color:var(--accent-primary-sys-primary);color:var(--accent-primary-sys-on-primary);line-height:0}.cc__plain-icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--surface-surface-sys-on-surface);line-height:0}.cc--journey{justify-content:space-between}.cc--journey .cc__title{font-weight:var(--title-small-sys-font-weight)}.cc__top-end{display:inline-flex;align-items:center;gap:var(--spacing-padding-sys-padding-8)}.cc__arrow{display:inline-flex;color:var(--surface-surface-sys-on-surface-variant);flex-shrink:0;line-height:0}.cc__text{display:flex;flex-direction:column;gap:var(--spacing-padding-sys-padding-12)}.cc__heading{display:flex;flex-direction:column;gap:var(--spacing-padding-sys-padding-4)}.cc__title{margin:0;font-family:var(--title-small-sys-font-family);font-size:var(--title-small-sys-font-size);line-height:var(--title-small-sys-line-height);font-weight:var(--title-small-sys-font-weight-emphasized);letter-spacing:var(--title-small-sys-tracking);color:var(--surface-surface-sys-on-surface)}.cc__subtitle{margin:0;font-family:var(--title-small-sys-font-family);font-size:var(--title-small-sys-font-size);line-height:var(--title-small-sys-line-height);font-weight:var(--title-small-sys-font-weight-emphasized);letter-spacing:var(--title-small-sys-tracking);color:var(--surface-surface-sys-on-surface-variant)}.cc__desc{margin:0;font-family:var(--body-medium-sys-font-family);font-size:var(--body-medium-sys-font-size);line-height:var(--body-medium-sys-line-height);letter-spacing:var(--body-medium-sys-tracking);color:var(--surface-surface-sys-on-surface-variant)}.cc__chip{display:inline-flex;align-items:center;padding:var(--spacing-padding-sys-padding-2) var(--spacing-padding-sys-padding-12);border-radius:var(--border-radius-sys-radius-full);background-color:var(--custom-info-sys-info-container);color:var(--custom-info-sys-on-info-container);font-family:var(--label-medium-sys-font-family);font-size:var(--label-medium-sys-font-size);line-height:var(--label-medium-sys-line-height);white-space:nowrap}.cc__chip--glass{background-color:var(--transparent-neutral-sys-white-20);color:var(--accent-primary-sys-on-primary);-webkit-backdrop-filter:blur(.375rem);backdrop-filter:blur(.375rem)}.cc__button{align-self:flex-start;display:inline-flex;align-items:center;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-32);border-radius:var(--border-radius-sys-radius-full);border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline);background-color:var(--surface-surface-container-sys-surface-container-lowest);color:var(--surface-surface-sys-on-surface);font-family:var(--label-medium-sys-font-family);font-size:var(--label-medium-sys-font-size);font-weight:var(--label-medium-sys-font-weight-emphasized);line-height:var(--label-medium-sys-line-height);white-space:nowrap}.cc--filled .cc__icon{background-color:var(--surface-surface-container-sys-surface-container-lowest);color:var(--accent-primary-sys-primary)}.cc--filled .cc__title{color:var(--accent-primary-sys-on-primary-container)}.cc--filled .cc__subtitle,.cc--filled .cc__desc,.cc--filled .cc__arrow{color:var(--accent-primary-sys-on-primary-container-variant)}.cc--filled .cc__button{border-color:transparent}.cc--image{position:relative;display:flex;flex-direction:column;flex:1 1 auto;min-height:12.5rem;justify-content:space-between;gap:var(--spacing-padding-sys-padding-24);padding:var(--spacing-padding-sys-padding-24)}.cc--hero{position:relative;display:flex;flex-direction:column;flex:1 1 auto;justify-content:space-between;gap:var(--spacing-padding-sys-padding-24);min-height:22.5rem;padding:var(--spacing-padding-sys-padding-32)}.cc__media-icon{display:inline-flex;align-items:center;justify-content:center;width:var(--sys-size-xl);height:var(--sys-size-xl);flex-shrink:0;border-radius:var(--border-radius-sys-radius-full);background-color:var(--transparent-neutral-sys-white-20);color:var(--accent-primary-sys-on-primary);-webkit-backdrop-filter:blur(.375rem);backdrop-filter:blur(.375rem);line-height:0}.cc__img-top{display:flex;flex-direction:column;align-items:flex-start;gap:var(--spacing-padding-sys-padding-12)}.cc__img-foot{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--spacing-padding-sys-padding-16)}.cc__img-foot--end{justify-content:flex-end}.cc__hero-text{display:flex;flex-direction:column;gap:var(--spacing-padding-sys-padding-8);min-width:0}.cc__eyebrow{margin:0;font-family:var(--title-small-sys-font-family);font-size:var(--title-small-sys-font-size);line-height:var(--title-small-sys-line-height);color:var(--accent-primary-sys-on-primary)}.cc__headline{margin:0;font-family:var(--headline-large-sys-font-family);font-size:var(--headline-large-sys-font-size);line-height:var(--headline-large-sys-line-height);font-weight:var(--headline-large-sys-font-weight-emphasized);letter-spacing:var(--headline-large-sys-tracking);color:var(--accent-primary-sys-on-primary)}.cc__button--solid{align-self:flex-end;flex-shrink:0;border-color:transparent;box-shadow:0 .125rem .25rem #0000000f,0 .125rem .5rem #0000000a}.cc--image .cc__title,.cc--image .cc__arrow{color:var(--accent-primary-sys-on-primary)}.cc--image .cc__subtitle,.cc--image .cc__desc{color:var(--transparent-neutral-sys-white-80)}.ac{display:flex;flex:1 1 auto;align-items:center;gap:var(--spacing-padding-sys-padding-12);font-family:var(--body-medium-sys-font-family)}.ac__icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--surface-surface-sys-on-surface);line-height:0}.ac__text{display:flex;flex-direction:column;gap:var(--spacing-padding-sys-padding-4);flex:1 1 auto;min-width:0}.ac__label{font-family:var(--label-large-sys-font-family);font-size:var(--label-large-sys-font-size);line-height:var(--label-large-sys-line-height);font-weight:var(--label-large-sys-font-weight-emphasized);letter-spacing:var(--label-large-sys-tracking);color:var(--surface-surface-sys-on-surface);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ac__desc{font-family:var(--body-small-sys-font-family);font-size:var(--body-small-sys-font-size);line-height:var(--body-small-sys-line-height);letter-spacing:var(--body-small-sys-tracking);color:var(--surface-surface-sys-on-surface-variant)}.ac__arrow{display:inline-flex;flex-shrink:0;color:var(--surface-surface-sys-on-surface-variant);line-height:0}.ac--vertical{flex-direction:column;align-items:flex-start;justify-content:flex-start;gap:var(--spacing-padding-sys-padding-16)}.ac__badge{display:inline-flex;align-items:center;justify-content:center;width:var(--sys-size-xl);height:var(--sys-size-xl);flex-shrink:0;border-radius:var(--border-radius-sys-radius-full);background-color:var(--surface-surface-container-sys-surface-container);color:var(--surface-surface-sys-on-surface);transition:background-color .14s ease-out;line-height:0}:host(:hover) .ac__badge{background-color:var(--surface-surface-container-sys-surface-container-lowest)}.ac--vertical .ac__label{white-space:pre-line;overflow:visible;font-size:var(--title-small-sys-font-size);line-height:var(--title-small-sys-line-height);font-weight:var(--title-small-sys-font-weight)}@media(prefers-reduced-motion:reduce){.ac__badge{transition:none}}.mc{display:flex;flex:1 1 auto;flex-direction:column;align-items:center;justify-content:center;gap:var(--spacing-padding-sys-padding-4);padding:var(--spacing-padding-sys-padding-8) 0;text-align:center;font-family:var(--body-medium-sys-font-family)}.mc__value{font-family:var(--headline-small-sys-font-family);font-size:var(--headline-small-sys-font-size);line-height:var(--headline-small-sys-line-height);font-weight:var(--headline-small-sys-font-weight-emphasized);letter-spacing:var(--headline-small-sys-tracking);color:var(--surface-surface-sys-on-surface)}.mc__label{font-family:var(--label-medium-sys-font-family);font-size:var(--label-medium-sys-font-size);line-height:var(--label-medium-sys-line-height);letter-spacing:var(--label-medium-sys-tracking);color:var(--surface-surface-sys-on-surface-variant)}.mc--current .mc__value,.mc--current .mc__label{color:var(--accent-primary-sys-primary)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
129
|
+
}
|
|
130
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CardComponent, decorators: [{
|
|
131
|
+
type: Component,
|
|
132
|
+
args: [{ selector: 'compsych-card', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [RippleDirective], host: {
|
|
133
|
+
class: 'cmp-card',
|
|
134
|
+
'[class.cmp-card--outlined]': "appearance() === 'outlined'",
|
|
135
|
+
'[class.cmp-card--filled]': "appearance() === 'filled'",
|
|
136
|
+
'[class.cmp-card--gradient]': "appearance() === 'gradient'",
|
|
137
|
+
'[class.cmp-card--image]': "appearance() === 'image'",
|
|
138
|
+
'[class.cmp-card--radius-md]': "radius() === 'md'",
|
|
139
|
+
'[class.cmp-card--radius-lg]': "radius() === 'lg'",
|
|
140
|
+
'[class.cmp-card--radius-xl]': "radius() === 'xl'",
|
|
141
|
+
'[class.cmp-card--pad-none]': "effectivePadding() === 'none'",
|
|
142
|
+
'[class.cmp-card--pad-sm]': "effectivePadding() === 'sm'",
|
|
143
|
+
'[class.cmp-card--pad-md]': "effectivePadding() === 'md'",
|
|
144
|
+
'[class.cmp-card--pad-lg]': "effectivePadding() === 'lg'",
|
|
145
|
+
'[class.cmp-card--pad-xl]': "effectivePadding() === 'xl'",
|
|
146
|
+
'[class.cmp-card--interactive]': 'interactive()',
|
|
147
|
+
'[class.cmp-card--disabled]': 'disabled()',
|
|
148
|
+
'[attr.role]': "interactive() ? 'button' : null",
|
|
149
|
+
'[attr.tabindex]': 'interactive() && !disabled() ? 0 : null',
|
|
150
|
+
'[attr.aria-disabled]': 'disabled() || null',
|
|
151
|
+
'[attr.aria-pressed]': "interactive() && variant() === 'metric' ? current() : null",
|
|
152
|
+
'[style.border-color]': "variant() === 'metric' && current() ? 'var(--accent-primary-sys-primary)' : null",
|
|
153
|
+
'[style.border-width]': "variant() === 'metric' && current() ? 'var(--border-width-sys-stroke-thick)' : null",
|
|
154
|
+
'(click)': 'onActivate()',
|
|
155
|
+
'(keydown)': 'onKeyDown($event)',
|
|
156
|
+
}, template: "@if (isImage() && mediaUrl()) {\r\n <div class=\"cmp-card__media\" [style.background-image]=\"mediaUrl()\"></div>\r\n <div class=\"cmp-card__scrim\" aria-hidden=\"true\"></div>\r\n}\r\n\r\n<div class=\"cmp-card__body\">\r\n @switch (variant()) {\r\n @case ('content') {\r\n @if (headline()) {\r\n <!-- Hero (image/filled): icon top-left, eyebrow + display headline + action bottom. -->\r\n <div class=\"cc cc--hero\" [class.cc--image]=\"isImage()\" [class.cc--filled]=\"appearance() === 'filled'\">\r\n @if (icon()) {\r\n @if (isImage()) {\r\n <div class=\"cc__media-icon\" [innerHTML]=\"iconSvg(icon(), 28)\" aria-hidden=\"true\"></div>\r\n } @else {\r\n <div class=\"cc__icon\" [innerHTML]=\"iconSvg(icon(), 24)\" aria-hidden=\"true\"></div>\r\n }\r\n }\r\n <div class=\"cc__img-foot\">\r\n <div class=\"cc__hero-text\">\r\n @if (title()) { <p class=\"cc__eyebrow\">{{ title() }}</p> }\r\n <p class=\"cc__headline\">{{ headline() }}</p>\r\n @if (description()) { <p class=\"cc__desc\">{{ description() }}</p> }\r\n </div>\r\n @if (actionLabel()) {\r\n <span class=\"cc__button cc__button--solid\">{{ actionLabel() }}</span>\r\n } @else if (showArrow()) {\r\n <span class=\"cc__arrow\" [innerHTML]=\"iconSvg('arrow-up-right', 24)\" aria-hidden=\"true\"></span>\r\n }\r\n </div>\r\n </div>\r\n } @else if (isImage()) {\r\n <div class=\"cc cc--image\">\r\n <div class=\"cc__img-top\">\r\n @if (category()) { <span class=\"cc__chip cc__chip--glass\">{{ category() }}</span> }\r\n @if (title() || subtitle()) {\r\n <div class=\"cc__heading\">\r\n @if (title()) { <p class=\"cc__title\">{{ title() }}</p> }\r\n @if (subtitle()) { <p class=\"cc__subtitle\">{{ subtitle() }}</p> }\r\n </div>\r\n }\r\n @if (description()) { <p class=\"cc__desc\">{{ description() }}</p> }\r\n </div>\r\n <div class=\"cc__img-foot cc__img-foot--end\">\r\n @if (actionLabel()) {\r\n <span class=\"cc__button cc__button--solid\">{{ actionLabel() }}</span>\r\n } @else if (showArrow()) {\r\n <span class=\"cc__arrow\" [innerHTML]=\"iconSvg('arrow-up-right', 24)\" aria-hidden=\"true\"></span>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"cc\" [class.cc--filled]=\"appearance() === 'filled'\" [class.cc--journey]=\"plainIcon()\">\r\n <div class=\"cc__top\">\r\n @if (icon()) {\r\n @if (plainIcon()) {\r\n <div class=\"cc__plain-icon\" [innerHTML]=\"iconSvg(icon(), 48, 1.5)\" aria-hidden=\"true\"></div>\r\n } @else {\r\n <div class=\"cc__icon\" [innerHTML]=\"iconSvg(icon(), 24)\" aria-hidden=\"true\"></div>\r\n }\r\n }\r\n <div class=\"cc__top-end\">\r\n @if (category()) { <span class=\"cc__chip\">{{ category() }}</span> }\r\n @if (showArrow()) {\r\n <span class=\"cc__arrow\" [innerHTML]=\"iconSvg('arrow-up-right', 24)\" aria-hidden=\"true\"></span>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"cc__text\">\r\n @if (title() || subtitle()) {\r\n <div class=\"cc__heading\">\r\n @if (title()) { <p class=\"cc__title\">{{ title() }}</p> }\r\n @if (subtitle()) { <p class=\"cc__subtitle\">{{ subtitle() }}</p> }\r\n </div>\r\n }\r\n @if (description()) { <p class=\"cc__desc\">{{ description() }}</p> }\r\n </div>\r\n @if (actionLabel()) { <span class=\"cc__button\">{{ actionLabel() }}</span> }\r\n </div>\r\n }\r\n }\r\n\r\n @case ('action') {\r\n @if (orientation() === 'vertical') {\r\n <div class=\"ac ac--vertical\">\r\n @if (icon()) {\r\n <div class=\"ac__badge\" [innerHTML]=\"iconSvg(icon(), 24, 1.5)\" aria-hidden=\"true\"></div>\r\n }\r\n <div class=\"ac__text\">\r\n <span class=\"ac__label\">{{ label() }}</span>\r\n @if (description()) { <span class=\"ac__desc\">{{ description() }}</span> }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"ac\">\r\n @if (icon()) {\r\n <div class=\"ac__icon\" [innerHTML]=\"iconSvg(icon(), 24)\" aria-hidden=\"true\"></div>\r\n }\r\n <div class=\"ac__text\">\r\n <span class=\"ac__label\">{{ label() }}</span>\r\n @if (description()) { <span class=\"ac__desc\">{{ description() }}</span> }\r\n </div>\r\n <span class=\"ac__arrow\" [innerHTML]=\"iconSvg(trailingIcon(), 20)\" aria-hidden=\"true\"></span>\r\n </div>\r\n }\r\n }\r\n\r\n @case ('metric') {\r\n <div class=\"mc\" [class.mc--current]=\"current()\">\r\n <span class=\"mc__value\">{{ value() }}</span>\r\n @if (label()) { <span class=\"mc__label\">{{ label() }}</span> }\r\n </div>\r\n }\r\n\r\n @default {\r\n <ng-content />\r\n }\r\n }\r\n</div>\r\n", styles: [":host{position:relative;display:flex;flex-direction:column;box-sizing:border-box;overflow:hidden;border-radius:var(--border-radius-sys-radius-lg);background-color:var(--surface-surface-container-sys-surface-container-lowest);color:var(--surface-surface-sys-on-surface);isolation:isolate}:host.cmp-card--radius-md{border-radius:var(--border-radius-sys-radius-md)}:host.cmp-card--radius-lg{border-radius:var(--border-radius-sys-radius-lg)}:host.cmp-card--radius-xl{border-radius:var(--border-radius-sys-radius-xl)}.cmp-card__body{position:relative;z-index:1;display:flex;flex-direction:column;flex:1 1 auto;min-height:0}:host.cmp-card--pad-none .cmp-card__body{padding:0}:host.cmp-card--pad-sm .cmp-card__body{padding:var(--spacing-padding-sys-padding-16)}:host.cmp-card--pad-md .cmp-card__body{padding:var(--spacing-padding-sys-padding-20)}:host.cmp-card--pad-lg .cmp-card__body{padding:var(--spacing-padding-sys-padding-24)}:host.cmp-card--pad-xl .cmp-card__body{padding:var(--spacing-padding-sys-padding-32)}:host.cmp-card--outlined{background-color:var(--surface-surface-container-sys-surface-container-lowest);border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline)}:host.cmp-card--filled{background-color:var(--accent-primary-sys-primary-container);color:var(--accent-primary-sys-on-primary-container)}:host.cmp-card--gradient{background-image:linear-gradient(180deg,var(--transparent-primary-sys-primary-08) 0%,var(--transparent-neutral-sys-white-10) 100%);background-color:var(--surface-surface-container-sys-surface-container-lowest);border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline)}:host.cmp-card--image{color:var(--accent-primary-sys-on-primary-container);background-color:var(--surface-surface-container-sys-surface-container-high)}.cmp-card__media{position:absolute;inset:0;z-index:0;background-size:cover;background-position:center}.cmp-card__scrim{position:absolute;inset:0;z-index:0;background-image:linear-gradient(180deg,var(--transparent-neutral-sys-black-40) 0%,var(--transparent-neutral-sys-black-10) 32%,var(--transparent-neutral-sys-black-10) 64%,var(--transparent-neutral-sys-black-50) 100%);mix-blend-mode:multiply}:host.cmp-card--interactive{cursor:pointer;transition:box-shadow .14s ease-out,background-color .14s ease-out,border-color .14s ease-out}:host.cmp-card--interactive:focus-visible{outline:none;border-color:var(--add-on-primary-fixed-sys-on-primary-fixed-variant);box-shadow:0 0 0 var(--spacing-padding-sys-padding-4) var(--transparent-primary-sys-primary-08)}:host.cmp-card--interactive.cmp-card--outlined:hover,:host.cmp-card--interactive.cmp-card--gradient:hover{background-color:var(--surface-surface-container-sys-surface-container);box-shadow:0 .25rem 1rem #0000001a,0 .125rem .25rem #00000014}:host.cmp-card--interactive.cmp-card--filled:hover,:host.cmp-card--interactive.cmp-card--image:hover{box-shadow:0 .25rem 1rem #0000002e,0 .125rem .25rem #0000001f}:host.cmp-card--disabled{pointer-events:none;opacity:.38}@media(prefers-reduced-motion:reduce){:host.cmp-card--interactive{transition:none}}@media(forced-colors:active){:host{border:1px solid CanvasText;forced-color-adjust:none}:host.cmp-card--interactive:focus-visible{outline:2px solid Highlight;outline-offset:2px}}.cc{display:flex;flex-direction:column;flex:1 1 auto;gap:var(--spacing-padding-sys-padding-24);font-family:var(--body-medium-sys-font-family)}.cc__top{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--spacing-padding-sys-padding-8)}.cc__icon{display:inline-flex;align-items:center;justify-content:center;width:var(--sys-size-xl);height:var(--sys-size-xl);flex-shrink:0;border-radius:var(--border-radius-sys-radius-full);background-color:var(--accent-primary-sys-primary);color:var(--accent-primary-sys-on-primary);line-height:0}.cc__plain-icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--surface-surface-sys-on-surface);line-height:0}.cc--journey{justify-content:space-between}.cc--journey .cc__title{font-weight:var(--title-small-sys-font-weight)}.cc__top-end{display:inline-flex;align-items:center;gap:var(--spacing-padding-sys-padding-8)}.cc__arrow{display:inline-flex;color:var(--surface-surface-sys-on-surface-variant);flex-shrink:0;line-height:0}.cc__text{display:flex;flex-direction:column;gap:var(--spacing-padding-sys-padding-12)}.cc__heading{display:flex;flex-direction:column;gap:var(--spacing-padding-sys-padding-4)}.cc__title{margin:0;font-family:var(--title-small-sys-font-family);font-size:var(--title-small-sys-font-size);line-height:var(--title-small-sys-line-height);font-weight:var(--title-small-sys-font-weight-emphasized);letter-spacing:var(--title-small-sys-tracking);color:var(--surface-surface-sys-on-surface)}.cc__subtitle{margin:0;font-family:var(--title-small-sys-font-family);font-size:var(--title-small-sys-font-size);line-height:var(--title-small-sys-line-height);font-weight:var(--title-small-sys-font-weight-emphasized);letter-spacing:var(--title-small-sys-tracking);color:var(--surface-surface-sys-on-surface-variant)}.cc__desc{margin:0;font-family:var(--body-medium-sys-font-family);font-size:var(--body-medium-sys-font-size);line-height:var(--body-medium-sys-line-height);letter-spacing:var(--body-medium-sys-tracking);color:var(--surface-surface-sys-on-surface-variant)}.cc__chip{display:inline-flex;align-items:center;padding:var(--spacing-padding-sys-padding-2) var(--spacing-padding-sys-padding-12);border-radius:var(--border-radius-sys-radius-full);background-color:var(--custom-info-sys-info-container);color:var(--custom-info-sys-on-info-container);font-family:var(--label-medium-sys-font-family);font-size:var(--label-medium-sys-font-size);line-height:var(--label-medium-sys-line-height);white-space:nowrap}.cc__chip--glass{background-color:var(--transparent-neutral-sys-white-20);color:var(--accent-primary-sys-on-primary);-webkit-backdrop-filter:blur(.375rem);backdrop-filter:blur(.375rem)}.cc__button{align-self:flex-start;display:inline-flex;align-items:center;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-32);border-radius:var(--border-radius-sys-radius-full);border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline);background-color:var(--surface-surface-container-sys-surface-container-lowest);color:var(--surface-surface-sys-on-surface);font-family:var(--label-medium-sys-font-family);font-size:var(--label-medium-sys-font-size);font-weight:var(--label-medium-sys-font-weight-emphasized);line-height:var(--label-medium-sys-line-height);white-space:nowrap}.cc--filled .cc__icon{background-color:var(--surface-surface-container-sys-surface-container-lowest);color:var(--accent-primary-sys-primary)}.cc--filled .cc__title{color:var(--accent-primary-sys-on-primary-container)}.cc--filled .cc__subtitle,.cc--filled .cc__desc,.cc--filled .cc__arrow{color:var(--accent-primary-sys-on-primary-container-variant)}.cc--filled .cc__button{border-color:transparent}.cc--image{position:relative;display:flex;flex-direction:column;flex:1 1 auto;min-height:12.5rem;justify-content:space-between;gap:var(--spacing-padding-sys-padding-24);padding:var(--spacing-padding-sys-padding-24)}.cc--hero{position:relative;display:flex;flex-direction:column;flex:1 1 auto;justify-content:space-between;gap:var(--spacing-padding-sys-padding-24);min-height:22.5rem;padding:var(--spacing-padding-sys-padding-32)}.cc__media-icon{display:inline-flex;align-items:center;justify-content:center;width:var(--sys-size-xl);height:var(--sys-size-xl);flex-shrink:0;border-radius:var(--border-radius-sys-radius-full);background-color:var(--transparent-neutral-sys-white-20);color:var(--accent-primary-sys-on-primary);-webkit-backdrop-filter:blur(.375rem);backdrop-filter:blur(.375rem);line-height:0}.cc__img-top{display:flex;flex-direction:column;align-items:flex-start;gap:var(--spacing-padding-sys-padding-12)}.cc__img-foot{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--spacing-padding-sys-padding-16)}.cc__img-foot--end{justify-content:flex-end}.cc__hero-text{display:flex;flex-direction:column;gap:var(--spacing-padding-sys-padding-8);min-width:0}.cc__eyebrow{margin:0;font-family:var(--title-small-sys-font-family);font-size:var(--title-small-sys-font-size);line-height:var(--title-small-sys-line-height);color:var(--accent-primary-sys-on-primary)}.cc__headline{margin:0;font-family:var(--headline-large-sys-font-family);font-size:var(--headline-large-sys-font-size);line-height:var(--headline-large-sys-line-height);font-weight:var(--headline-large-sys-font-weight-emphasized);letter-spacing:var(--headline-large-sys-tracking);color:var(--accent-primary-sys-on-primary)}.cc__button--solid{align-self:flex-end;flex-shrink:0;border-color:transparent;box-shadow:0 .125rem .25rem #0000000f,0 .125rem .5rem #0000000a}.cc--image .cc__title,.cc--image .cc__arrow{color:var(--accent-primary-sys-on-primary)}.cc--image .cc__subtitle,.cc--image .cc__desc{color:var(--transparent-neutral-sys-white-80)}.ac{display:flex;flex:1 1 auto;align-items:center;gap:var(--spacing-padding-sys-padding-12);font-family:var(--body-medium-sys-font-family)}.ac__icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--surface-surface-sys-on-surface);line-height:0}.ac__text{display:flex;flex-direction:column;gap:var(--spacing-padding-sys-padding-4);flex:1 1 auto;min-width:0}.ac__label{font-family:var(--label-large-sys-font-family);font-size:var(--label-large-sys-font-size);line-height:var(--label-large-sys-line-height);font-weight:var(--label-large-sys-font-weight-emphasized);letter-spacing:var(--label-large-sys-tracking);color:var(--surface-surface-sys-on-surface);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ac__desc{font-family:var(--body-small-sys-font-family);font-size:var(--body-small-sys-font-size);line-height:var(--body-small-sys-line-height);letter-spacing:var(--body-small-sys-tracking);color:var(--surface-surface-sys-on-surface-variant)}.ac__arrow{display:inline-flex;flex-shrink:0;color:var(--surface-surface-sys-on-surface-variant);line-height:0}.ac--vertical{flex-direction:column;align-items:flex-start;justify-content:flex-start;gap:var(--spacing-padding-sys-padding-16)}.ac__badge{display:inline-flex;align-items:center;justify-content:center;width:var(--sys-size-xl);height:var(--sys-size-xl);flex-shrink:0;border-radius:var(--border-radius-sys-radius-full);background-color:var(--surface-surface-container-sys-surface-container);color:var(--surface-surface-sys-on-surface);transition:background-color .14s ease-out;line-height:0}:host(:hover) .ac__badge{background-color:var(--surface-surface-container-sys-surface-container-lowest)}.ac--vertical .ac__label{white-space:pre-line;overflow:visible;font-size:var(--title-small-sys-font-size);line-height:var(--title-small-sys-line-height);font-weight:var(--title-small-sys-font-weight)}@media(prefers-reduced-motion:reduce){.ac__badge{transition:none}}.mc{display:flex;flex:1 1 auto;flex-direction:column;align-items:center;justify-content:center;gap:var(--spacing-padding-sys-padding-4);padding:var(--spacing-padding-sys-padding-8) 0;text-align:center;font-family:var(--body-medium-sys-font-family)}.mc__value{font-family:var(--headline-small-sys-font-family);font-size:var(--headline-small-sys-font-size);line-height:var(--headline-small-sys-line-height);font-weight:var(--headline-small-sys-font-weight-emphasized);letter-spacing:var(--headline-small-sys-tracking);color:var(--surface-surface-sys-on-surface)}.mc__label{font-family:var(--label-medium-sys-font-family);font-size:var(--label-medium-sys-font-size);line-height:var(--label-medium-sys-line-height);letter-spacing:var(--label-medium-sys-tracking);color:var(--surface-surface-sys-on-surface-variant)}.mc--current .mc__value,.mc--current .mc__label{color:var(--accent-primary-sys-primary)}\n"] }]
|
|
157
|
+
}], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], radius: [{ type: i0.Input, args: [{ isSignal: true, alias: "radius", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], imageSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageSrc", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], plainIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "plainIcon", required: false }] }], category: [{ type: i0.Input, args: [{ isSignal: true, alias: "category", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], headline: [{ type: i0.Input, args: [{ isSignal: true, alias: "headline", required: false }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], showArrow: [{ type: i0.Input, args: [{ isSignal: true, alias: "showArrow", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], trailingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingIcon", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], current: [{ type: i0.Input, args: [{ isSignal: true, alias: "current", required: false }] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }] } });
|
|
158
|
+
//# sourceMappingURL=card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/card-base/card.ts","../../../../../../packages/angular/src/lib/card-base/card.html"],"names":[],"mappings":"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","sourcesContent":["import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n computed,\r\n effect,\r\n inject,\r\n input,\r\n output,\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\nimport { RippleDirective } from './ripple.directive';\r\n\r\n/** Card type — selects the projected/laid-out content. Mirrors the library's `variant` pattern. */\r\nexport type CardVariant = 'basic' | 'content' | 'action' | 'metric';\r\n/** Visual surface treatment. Mirrors the Figma \"Card Item\" Style property. */\r\nexport type CardAppearance = 'outlined' | 'filled' | 'gradient' | 'image';\r\n/** Corner radius token: `md` (12px), `lg` (16px), `xl` (24px). */\r\nexport type CardRadius = 'md' | 'lg' | 'xl';\r\n/** Inner padding token (used by `variant=\"basic\"`; typed variants size themselves). */\r\nexport type CardPadding = 'none' | 'sm' | 'md' | 'lg' | 'xl';\r\n/** Action-card layout. */\r\nexport type CardOrientation = 'horizontal' | 'vertical';\r\n\r\n/**\r\n * Card — the single Cards component for the ComPsych Design System.\r\n *\r\n * Figma: Core Components — \"Cards\" (file VFBn7KCDy3FSIlvhNo3ylq, 1117:9829).\r\n *\r\n * One component, switched by `variant` (like Accordion's `variant`):\r\n * - `basic` — a bare surface; project your own content.\r\n * - `content` — icon badge / image, category chip, title, subtitle, description,\r\n * action pill, arrow; hero (`headline`) and journey (`plainIcon`) layouts.\r\n * - `action` — compact interactive row (icon · label/description · arrow); horizontal/vertical.\r\n * - `metric` — centered value + label; `current` (selected) state.\r\n *\r\n * `appearance` is the orthogonal surface skin (outlined / filled / gradient / image).\r\n * When `interactive`, the whole card is a button (role/tabindex/keyboard) with the\r\n * hover / focus / pressed ripple. All values are sys design tokens.\r\n *\r\n * <compsych-card variant=\"content\" appearance=\"gradient\" icon=\"circle-play\"\r\n * category=\"Video\" title=\"Mindful breathing\" actionLabel=\"Watch\" [interactive]=\"true\" />\r\n * <compsych-card variant=\"metric\" value=\"24\" label=\"Sessions\" [current]=\"true\" [interactive]=\"true\" />\r\n * <compsych-card>Projected content</compsych-card>\r\n */\r\n@Component({\r\n selector: 'compsych-card',\r\n standalone: true,\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n hostDirectives: [RippleDirective],\r\n templateUrl: './card.html',\r\n styleUrl: './card.css',\r\n host: {\r\n class: 'cmp-card',\r\n '[class.cmp-card--outlined]': \"appearance() === 'outlined'\",\r\n '[class.cmp-card--filled]': \"appearance() === 'filled'\",\r\n '[class.cmp-card--gradient]': \"appearance() === 'gradient'\",\r\n '[class.cmp-card--image]': \"appearance() === 'image'\",\r\n '[class.cmp-card--radius-md]': \"radius() === 'md'\",\r\n '[class.cmp-card--radius-lg]': \"radius() === 'lg'\",\r\n '[class.cmp-card--radius-xl]': \"radius() === 'xl'\",\r\n '[class.cmp-card--pad-none]': \"effectivePadding() === 'none'\",\r\n '[class.cmp-card--pad-sm]': \"effectivePadding() === 'sm'\",\r\n '[class.cmp-card--pad-md]': \"effectivePadding() === 'md'\",\r\n '[class.cmp-card--pad-lg]': \"effectivePadding() === 'lg'\",\r\n '[class.cmp-card--pad-xl]': \"effectivePadding() === 'xl'\",\r\n '[class.cmp-card--interactive]': 'interactive()',\r\n '[class.cmp-card--disabled]': 'disabled()',\r\n '[attr.role]': \"interactive() ? 'button' : null\",\r\n '[attr.tabindex]': 'interactive() && !disabled() ? 0 : null',\r\n '[attr.aria-disabled]': 'disabled() || null',\r\n '[attr.aria-pressed]': \"interactive() && variant() === 'metric' ? current() : null\",\r\n '[style.border-color]': \"variant() === 'metric' && current() ? 'var(--accent-primary-sys-primary)' : null\",\r\n '[style.border-width]': \"variant() === 'metric' && current() ? 'var(--border-width-sys-stroke-thick)' : null\",\r\n '(click)': 'onActivate()',\r\n '(keydown)': 'onKeyDown($event)',\r\n },\r\n})\r\nexport class CardComponent {\r\n // ── Surface (all variants) ──────────────────────────────\r\n /** Card type. Defaults to `basic` (projected content). */\r\n readonly variant = input<CardVariant>('basic');\r\n /** Surface treatment. Defaults to `outlined`. */\r\n readonly appearance = input<CardAppearance>('outlined');\r\n /** Corner radius token. Defaults to `lg` (16px). */\r\n readonly radius = input<CardRadius>('lg');\r\n /** Inner padding for `variant=\"basic\"`. Typed variants size themselves. */\r\n readonly padding = input<CardPadding>('lg');\r\n /** Whole-card click target: adds role/tabindex, hover/press states and ripple. */\r\n readonly interactive = input<boolean>(false);\r\n /** Disabled visual + non-interactive. */\r\n readonly disabled = input<boolean>(false);\r\n /** Background image URL for `appearance=\"image\"`. */\r\n readonly imageSrc = input<string>('');\r\n\r\n // ── Content variant ─────────────────────────────────────\r\n /** Lucide icon name (content badge / action leading icon). */\r\n readonly icon = input<string>('');\r\n /** Content: render the icon as a large plain glyph, text anchored bottom (journey layout). */\r\n readonly plainIcon = input<boolean>(false);\r\n /** Content: category chip text. */\r\n readonly category = input<string>('');\r\n /** Content: title. */\r\n readonly title = input<string>('');\r\n /** Content: subtitle. */\r\n readonly subtitle = input<string>('');\r\n /** Content/action: supporting description. */\r\n readonly description = input<string>('');\r\n /** Content: large hero display text (switches to the XL hero layout). */\r\n readonly headline = input<string>('');\r\n /** Content: presentational action pill label. */\r\n readonly actionLabel = input<string>('');\r\n /** Content: show the trailing arrow-up-right affordance. */\r\n readonly showArrow = input<boolean>(true);\r\n\r\n // ── Action variant ──────────────────────────────────────\r\n /** Action/metric: primary label. */\r\n readonly label = input<string>('');\r\n /** Action: trailing Lucide icon (horizontal layout). */\r\n readonly trailingIcon = input<string>('arrow-up-right');\r\n /** Action: layout orientation. */\r\n readonly orientation = input<CardOrientation>('horizontal');\r\n\r\n // ── Metric variant ──────────────────────────────────────\r\n /** Metric: the prominent value. */\r\n readonly value = input<string | number>('');\r\n /** Metric: selected/current state (primary border + text). */\r\n readonly current = input<boolean>(false);\r\n\r\n /** Emits on click / Enter / Space when interactive and not disabled. */\r\n readonly cardClick = output<void>();\r\n\r\n private readonly sanitizer = inject(DomSanitizer);\r\n private readonly ripple = inject(RippleDirective);\r\n\r\n readonly isImage = computed(() => this.appearance() === 'image');\r\n\r\n /** CSS `url(...)` for the media layer, or null when no image is set. */\r\n readonly mediaUrl = computed(() => {\r\n const src = this.imageSrc();\r\n return src ? `url(\"${src}\")` : null;\r\n });\r\n\r\n /** Padding resolved per variant; `basic` honors the `padding` input. */\r\n readonly effectivePadding = computed<CardPadding>(() => {\r\n switch (this.variant()) {\r\n case 'content':\r\n return this.isImage() || this.headline() ? 'none' : this.plainIcon() ? 'xl' : 'lg';\r\n case 'action':\r\n return this.orientation() === 'vertical' ? 'lg' : 'sm';\r\n case 'metric':\r\n return 'sm';\r\n default:\r\n return this.padding();\r\n }\r\n });\r\n\r\n constructor() {\r\n effect(() => {\r\n this.ripple.suppressed = !this.interactive() || this.disabled();\r\n });\r\n }\r\n\r\n onActivate(): void {\r\n if (!this.interactive() || this.disabled()) return;\r\n this.cardClick.emit();\r\n }\r\n\r\n onKeyDown(event: KeyboardEvent): void {\r\n if (!this.interactive() || this.disabled()) return;\r\n if (event.repeat) return;\r\n if (event.key !== 'Enter' && event.key !== ' ') return;\r\n event.preventDefault();\r\n this.cardClick.emit();\r\n }\r\n\r\n /** Renders a sized/weighted Lucide SVG for template use. */\r\n iconSvg(name: string, size: number, strokeWidth = 1.75): 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","@if (isImage() && mediaUrl()) {\r\n <div class=\"cmp-card__media\" [style.background-image]=\"mediaUrl()\"></div>\r\n <div class=\"cmp-card__scrim\" aria-hidden=\"true\"></div>\r\n}\r\n\r\n<div class=\"cmp-card__body\">\r\n @switch (variant()) {\r\n @case ('content') {\r\n @if (headline()) {\r\n <!-- Hero (image/filled): icon top-left, eyebrow + display headline + action bottom. -->\r\n <div class=\"cc cc--hero\" [class.cc--image]=\"isImage()\" [class.cc--filled]=\"appearance() === 'filled'\">\r\n @if (icon()) {\r\n @if (isImage()) {\r\n <div class=\"cc__media-icon\" [innerHTML]=\"iconSvg(icon(), 28)\" aria-hidden=\"true\"></div>\r\n } @else {\r\n <div class=\"cc__icon\" [innerHTML]=\"iconSvg(icon(), 24)\" aria-hidden=\"true\"></div>\r\n }\r\n }\r\n <div class=\"cc__img-foot\">\r\n <div class=\"cc__hero-text\">\r\n @if (title()) { <p class=\"cc__eyebrow\">{{ title() }}</p> }\r\n <p class=\"cc__headline\">{{ headline() }}</p>\r\n @if (description()) { <p class=\"cc__desc\">{{ description() }}</p> }\r\n </div>\r\n @if (actionLabel()) {\r\n <span class=\"cc__button cc__button--solid\">{{ actionLabel() }}</span>\r\n } @else if (showArrow()) {\r\n <span class=\"cc__arrow\" [innerHTML]=\"iconSvg('arrow-up-right', 24)\" aria-hidden=\"true\"></span>\r\n }\r\n </div>\r\n </div>\r\n } @else if (isImage()) {\r\n <div class=\"cc cc--image\">\r\n <div class=\"cc__img-top\">\r\n @if (category()) { <span class=\"cc__chip cc__chip--glass\">{{ category() }}</span> }\r\n @if (title() || subtitle()) {\r\n <div class=\"cc__heading\">\r\n @if (title()) { <p class=\"cc__title\">{{ title() }}</p> }\r\n @if (subtitle()) { <p class=\"cc__subtitle\">{{ subtitle() }}</p> }\r\n </div>\r\n }\r\n @if (description()) { <p class=\"cc__desc\">{{ description() }}</p> }\r\n </div>\r\n <div class=\"cc__img-foot cc__img-foot--end\">\r\n @if (actionLabel()) {\r\n <span class=\"cc__button cc__button--solid\">{{ actionLabel() }}</span>\r\n } @else if (showArrow()) {\r\n <span class=\"cc__arrow\" [innerHTML]=\"iconSvg('arrow-up-right', 24)\" aria-hidden=\"true\"></span>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"cc\" [class.cc--filled]=\"appearance() === 'filled'\" [class.cc--journey]=\"plainIcon()\">\r\n <div class=\"cc__top\">\r\n @if (icon()) {\r\n @if (plainIcon()) {\r\n <div class=\"cc__plain-icon\" [innerHTML]=\"iconSvg(icon(), 48, 1.5)\" aria-hidden=\"true\"></div>\r\n } @else {\r\n <div class=\"cc__icon\" [innerHTML]=\"iconSvg(icon(), 24)\" aria-hidden=\"true\"></div>\r\n }\r\n }\r\n <div class=\"cc__top-end\">\r\n @if (category()) { <span class=\"cc__chip\">{{ category() }}</span> }\r\n @if (showArrow()) {\r\n <span class=\"cc__arrow\" [innerHTML]=\"iconSvg('arrow-up-right', 24)\" aria-hidden=\"true\"></span>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"cc__text\">\r\n @if (title() || subtitle()) {\r\n <div class=\"cc__heading\">\r\n @if (title()) { <p class=\"cc__title\">{{ title() }}</p> }\r\n @if (subtitle()) { <p class=\"cc__subtitle\">{{ subtitle() }}</p> }\r\n </div>\r\n }\r\n @if (description()) { <p class=\"cc__desc\">{{ description() }}</p> }\r\n </div>\r\n @if (actionLabel()) { <span class=\"cc__button\">{{ actionLabel() }}</span> }\r\n </div>\r\n }\r\n }\r\n\r\n @case ('action') {\r\n @if (orientation() === 'vertical') {\r\n <div class=\"ac ac--vertical\">\r\n @if (icon()) {\r\n <div class=\"ac__badge\" [innerHTML]=\"iconSvg(icon(), 24, 1.5)\" aria-hidden=\"true\"></div>\r\n }\r\n <div class=\"ac__text\">\r\n <span class=\"ac__label\">{{ label() }}</span>\r\n @if (description()) { <span class=\"ac__desc\">{{ description() }}</span> }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"ac\">\r\n @if (icon()) {\r\n <div class=\"ac__icon\" [innerHTML]=\"iconSvg(icon(), 24)\" aria-hidden=\"true\"></div>\r\n }\r\n <div class=\"ac__text\">\r\n <span class=\"ac__label\">{{ label() }}</span>\r\n @if (description()) { <span class=\"ac__desc\">{{ description() }}</span> }\r\n </div>\r\n <span class=\"ac__arrow\" [innerHTML]=\"iconSvg(trailingIcon(), 20)\" aria-hidden=\"true\"></span>\r\n </div>\r\n }\r\n }\r\n\r\n @case ('metric') {\r\n <div class=\"mc\" [class.mc--current]=\"current()\">\r\n <span class=\"mc__value\">{{ value() }}</span>\r\n @if (label()) { <span class=\"mc__label\">{{ label() }}</span> }\r\n </div>\r\n }\r\n\r\n @default {\r\n <ng-content />\r\n }\r\n }\r\n</div>\r\n"]}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Directive, ElementRef, NgZone, inject } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Material 3 press ripple for interactive cards.
|
|
5
|
+
*
|
|
6
|
+
* Angular port of the ComPsych DS ripple. Apply to a positioned,
|
|
7
|
+
* `overflow: hidden` element. On primary pointer-down it spawns a circle at the
|
|
8
|
+
* pointer that scales out to the farthest corner and fades; keyboard activation
|
|
9
|
+
* (Enter/Space) spawns a centered ripple.
|
|
10
|
+
*
|
|
11
|
+
* The ripple nodes are created imperatively and animated with the Web Animations
|
|
12
|
+
* API, and their colour is `currentColor` — so no global stylesheet is needed
|
|
13
|
+
* and the ripple inherits the host's text colour (white on filled/image, dark on
|
|
14
|
+
* outlined/gradient), matching the reference.
|
|
15
|
+
*/
|
|
16
|
+
export class RippleDirective {
|
|
17
|
+
el = inject((ElementRef));
|
|
18
|
+
zone = inject(NgZone);
|
|
19
|
+
/**
|
|
20
|
+
* Imperative suppression — the card surface only ripples when `interactive`
|
|
21
|
+
* and not `disabled`. Set by the host component. Defaults to off.
|
|
22
|
+
*/
|
|
23
|
+
suppressed = false;
|
|
24
|
+
static DURATION_MS = 450;
|
|
25
|
+
layer = null;
|
|
26
|
+
onPointerDown(event) {
|
|
27
|
+
if (this.suppressed || event.button !== 0)
|
|
28
|
+
return;
|
|
29
|
+
const rect = this.el.nativeElement.getBoundingClientRect();
|
|
30
|
+
const x = event.clientX - rect.left;
|
|
31
|
+
const y = event.clientY - rect.top;
|
|
32
|
+
const corners = [
|
|
33
|
+
[0, 0],
|
|
34
|
+
[rect.width, 0],
|
|
35
|
+
[0, rect.height],
|
|
36
|
+
[rect.width, rect.height],
|
|
37
|
+
];
|
|
38
|
+
const maxRadius = Math.max(...corners.map(([cx, cy]) => Math.hypot(cx - x, cy - y)));
|
|
39
|
+
this.spawn(x, y, maxRadius * 2);
|
|
40
|
+
}
|
|
41
|
+
onKeyDown(event) {
|
|
42
|
+
if (this.suppressed || event.repeat)
|
|
43
|
+
return;
|
|
44
|
+
if (event.key !== 'Enter' && event.key !== ' ')
|
|
45
|
+
return;
|
|
46
|
+
const rect = this.el.nativeElement.getBoundingClientRect();
|
|
47
|
+
this.spawn(rect.width / 2, rect.height / 2, Math.hypot(rect.width, rect.height));
|
|
48
|
+
}
|
|
49
|
+
ensureLayer() {
|
|
50
|
+
if (this.layer && this.layer.isConnected)
|
|
51
|
+
return this.layer;
|
|
52
|
+
const layer = document.createElement('span');
|
|
53
|
+
layer.setAttribute('aria-hidden', 'true');
|
|
54
|
+
Object.assign(layer.style, {
|
|
55
|
+
position: 'absolute',
|
|
56
|
+
inset: '0',
|
|
57
|
+
overflow: 'hidden',
|
|
58
|
+
borderRadius: 'inherit',
|
|
59
|
+
pointerEvents: 'none',
|
|
60
|
+
zIndex: '0',
|
|
61
|
+
});
|
|
62
|
+
this.el.nativeElement.appendChild(layer);
|
|
63
|
+
this.layer = layer;
|
|
64
|
+
return layer;
|
|
65
|
+
}
|
|
66
|
+
spawn(x, y, size) {
|
|
67
|
+
if (typeof Element.prototype.animate !== 'function')
|
|
68
|
+
return;
|
|
69
|
+
const layer = this.ensureLayer();
|
|
70
|
+
const dot = document.createElement('span');
|
|
71
|
+
Object.assign(dot.style, {
|
|
72
|
+
position: 'absolute',
|
|
73
|
+
left: `${x}px`,
|
|
74
|
+
top: `${y}px`,
|
|
75
|
+
width: `${size}px`,
|
|
76
|
+
height: `${size}px`,
|
|
77
|
+
marginLeft: `${-size / 2}px`,
|
|
78
|
+
marginTop: `${-size / 2}px`,
|
|
79
|
+
borderRadius: '50%',
|
|
80
|
+
backgroundColor: 'currentColor',
|
|
81
|
+
pointerEvents: 'none',
|
|
82
|
+
});
|
|
83
|
+
layer.appendChild(dot);
|
|
84
|
+
this.zone.runOutsideAngular(() => {
|
|
85
|
+
const anim = dot.animate([
|
|
86
|
+
{ transform: 'scale(0)', opacity: 0.12 },
|
|
87
|
+
{ transform: 'scale(1)', opacity: 0 },
|
|
88
|
+
], { duration: RippleDirective.DURATION_MS, easing: 'ease-out' });
|
|
89
|
+
anim.onfinish = () => dot.remove();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: RippleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
93
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.14", type: RippleDirective, isStandalone: true, selector: "[compsychRipple]", host: { listeners: { "pointerdown": "onPointerDown($event)", "keydown": "onKeyDown($event)" } }, ngImport: i0 });
|
|
94
|
+
}
|
|
95
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: RippleDirective, decorators: [{
|
|
96
|
+
type: Directive,
|
|
97
|
+
args: [{
|
|
98
|
+
selector: '[compsychRipple]',
|
|
99
|
+
standalone: true,
|
|
100
|
+
host: {
|
|
101
|
+
'(pointerdown)': 'onPointerDown($event)',
|
|
102
|
+
'(keydown)': 'onKeyDown($event)',
|
|
103
|
+
},
|
|
104
|
+
}]
|
|
105
|
+
}] });
|
|
106
|
+
//# sourceMappingURL=ripple.directive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ripple.directive.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/card-base/ripple.directive.ts"],"names":[],"mappings":"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","sourcesContent":["import { Directive, ElementRef, NgZone, inject } from '@angular/core';\r\n\r\n/**\r\n * Material 3 press ripple for interactive cards.\r\n *\r\n * Angular port of the ComPsych DS ripple. Apply to a positioned,\r\n * `overflow: hidden` element. On primary pointer-down it spawns a circle at the\r\n * pointer that scales out to the farthest corner and fades; keyboard activation\r\n * (Enter/Space) spawns a centered ripple.\r\n *\r\n * The ripple nodes are created imperatively and animated with the Web Animations\r\n * API, and their colour is `currentColor` — so no global stylesheet is needed\r\n * and the ripple inherits the host's text colour (white on filled/image, dark on\r\n * outlined/gradient), matching the reference.\r\n */\r\n@Directive({\r\n selector: '[compsychRipple]',\r\n standalone: true,\r\n host: {\r\n '(pointerdown)': 'onPointerDown($event)',\r\n '(keydown)': 'onKeyDown($event)',\r\n },\r\n})\r\nexport class RippleDirective {\r\n private readonly el = inject(ElementRef<HTMLElement>);\r\n private readonly zone = inject(NgZone);\r\n\r\n /**\r\n * Imperative suppression — the card surface only ripples when `interactive`\r\n * and not `disabled`. Set by the host component. Defaults to off.\r\n */\r\n suppressed = false;\r\n\r\n private static readonly DURATION_MS = 450;\r\n private layer: HTMLElement | null = null;\r\n\r\n onPointerDown(event: PointerEvent): void {\r\n if (this.suppressed || event.button !== 0) return;\r\n const rect = this.el.nativeElement.getBoundingClientRect();\r\n const x = event.clientX - rect.left;\r\n const y = event.clientY - rect.top;\r\n const corners: Array<[number, number]> = [\r\n [0, 0],\r\n [rect.width, 0],\r\n [0, rect.height],\r\n [rect.width, rect.height],\r\n ];\r\n const maxRadius = Math.max(...corners.map(([cx, cy]) => Math.hypot(cx - x, cy - y)));\r\n this.spawn(x, y, maxRadius * 2);\r\n }\r\n\r\n onKeyDown(event: KeyboardEvent): void {\r\n if (this.suppressed || event.repeat) return;\r\n if (event.key !== 'Enter' && event.key !== ' ') return;\r\n const rect = this.el.nativeElement.getBoundingClientRect();\r\n this.spawn(rect.width / 2, rect.height / 2, Math.hypot(rect.width, rect.height));\r\n }\r\n\r\n private ensureLayer(): HTMLElement {\r\n if (this.layer && this.layer.isConnected) return this.layer;\r\n const layer = document.createElement('span');\r\n layer.setAttribute('aria-hidden', 'true');\r\n Object.assign(layer.style, {\r\n position: 'absolute',\r\n inset: '0',\r\n overflow: 'hidden',\r\n borderRadius: 'inherit',\r\n pointerEvents: 'none',\r\n zIndex: '0',\r\n });\r\n this.el.nativeElement.appendChild(layer);\r\n this.layer = layer;\r\n return layer;\r\n }\r\n\r\n private spawn(x: number, y: number, size: number): void {\r\n if (typeof (Element.prototype as { animate?: unknown }).animate !== 'function') return;\r\n const layer = this.ensureLayer();\r\n const dot = document.createElement('span');\r\n Object.assign(dot.style, {\r\n position: 'absolute',\r\n left: `${x}px`,\r\n top: `${y}px`,\r\n width: `${size}px`,\r\n height: `${size}px`,\r\n marginLeft: `${-size / 2}px`,\r\n marginTop: `${-size / 2}px`,\r\n borderRadius: '50%',\r\n backgroundColor: 'currentColor',\r\n pointerEvents: 'none',\r\n });\r\n layer.appendChild(dot);\r\n\r\n this.zone.runOutsideAngular(() => {\r\n const anim = dot.animate(\r\n [\r\n { transform: 'scale(0)', opacity: 0.12 },\r\n { transform: 'scale(1)', opacity: 0 },\r\n ],\r\n { duration: RippleDirective.DURATION_MS, easing: 'ease-out' },\r\n );\r\n anim.onfinish = () => dot.remove();\r\n });\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, ViewEncapsulation, input } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Card Grid — the layout primitive for the Cards organism.
|
|
5
|
+
*
|
|
6
|
+
* Figma: Core Components — "Card Grid" base (file VFBn7KCDy3FSIlvhNo3ylq, 59621:14856).
|
|
7
|
+
*
|
|
8
|
+
* Arranges projected cards into one of the design's layouts: full-width,
|
|
9
|
+
* 2/3/4/5-column even grids, a Bento grid (one feature cell + a stacked column),
|
|
10
|
+
* or 2/3-column masonry. A 16px gutter (sys-padding-16) throughout. Cards are
|
|
11
|
+
* projected, so any card type can fill the cells.
|
|
12
|
+
*
|
|
13
|
+
* <compsych-card-grid layout="3-col">
|
|
14
|
+
* <compsych-content-card … /> …
|
|
15
|
+
* </compsych-card-grid>
|
|
16
|
+
*
|
|
17
|
+
* Uses `ViewEncapsulation.None` with `cmp-card-grid`-namespaced selectors so it
|
|
18
|
+
* can lay out the *projected* card children (bento span, masonry flow), which
|
|
19
|
+
* emulated encapsulation cannot reach.
|
|
20
|
+
*/
|
|
21
|
+
export class CardGridComponent {
|
|
22
|
+
layout = input('3-col', ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
23
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CardGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
24
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.14", type: CardGridComponent, isStandalone: true, selector: "compsych-card-grid", inputs: { layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.cmp-card-grid--full": "layout() === 'full'", "class.cmp-card-grid--2-col": "layout() === '2-col'", "class.cmp-card-grid--3-col": "layout() === '3-col'", "class.cmp-card-grid--4-col": "layout() === '4-col'", "class.cmp-card-grid--5-col": "layout() === '5-col'", "class.cmp-card-grid--bento": "layout() === 'bento'", "class.cmp-card-grid--masonry-2": "layout() === 'masonry-2'", "class.cmp-card-grid--masonry-3": "layout() === 'masonry-3'" }, classAttribute: "cmp-card-grid" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [".cmp-card-grid{--cmp-grid-gap: var(--spacing-padding-sys-padding-16);display:grid;gap:var(--cmp-grid-gap);width:100%;align-items:stretch}.cmp-card-grid--full{grid-template-columns:1fr}.cmp-card-grid--2-col{grid-template-columns:repeat(2,minmax(0,1fr))}.cmp-card-grid--3-col{grid-template-columns:repeat(3,minmax(0,1fr))}.cmp-card-grid--4-col{grid-template-columns:repeat(4,minmax(0,1fr))}.cmp-card-grid--5-col{grid-template-columns:repeat(5,minmax(0,1fr))}.cmp-card-grid--bento{grid-template-columns:2fr 1fr;grid-auto-rows:1fr}.cmp-card-grid--bento>:first-child{grid-row:span 2}.cmp-card-grid--masonry-2,.cmp-card-grid--masonry-3{display:block;column-gap:var(--cmp-grid-gap)}.cmp-card-grid--masonry-2{column-count:2}.cmp-card-grid--masonry-3{column-count:3}.cmp-card-grid--masonry-2>*,.cmp-card-grid--masonry-3>*{break-inside:avoid;margin-bottom:var(--cmp-grid-gap)}@media(max-width:56.25rem){.cmp-card-grid--3-col,.cmp-card-grid--4-col,.cmp-card-grid--5-col{grid-template-columns:repeat(2,minmax(0,1fr))}.cmp-card-grid--masonry-3{column-count:2}.cmp-card-grid--bento{grid-template-columns:1fr;grid-auto-rows:auto}.cmp-card-grid--bento>:first-child{grid-row:auto}}@media(max-width:35rem){.cmp-card-grid--2-col,.cmp-card-grid--3-col,.cmp-card-grid--4-col,.cmp-card-grid--5-col{grid-template-columns:1fr}.cmp-card-grid--masonry-2,.cmp-card-grid--masonry-3{column-count:1}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
25
|
+
}
|
|
26
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CardGridComponent, decorators: [{
|
|
27
|
+
type: Component,
|
|
28
|
+
args: [{ selector: 'compsych-card-grid', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `<ng-content />`, host: {
|
|
29
|
+
class: 'cmp-card-grid',
|
|
30
|
+
'[class.cmp-card-grid--full]': "layout() === 'full'",
|
|
31
|
+
'[class.cmp-card-grid--2-col]': "layout() === '2-col'",
|
|
32
|
+
'[class.cmp-card-grid--3-col]': "layout() === '3-col'",
|
|
33
|
+
'[class.cmp-card-grid--4-col]': "layout() === '4-col'",
|
|
34
|
+
'[class.cmp-card-grid--5-col]': "layout() === '5-col'",
|
|
35
|
+
'[class.cmp-card-grid--bento]': "layout() === 'bento'",
|
|
36
|
+
'[class.cmp-card-grid--masonry-2]': "layout() === 'masonry-2'",
|
|
37
|
+
'[class.cmp-card-grid--masonry-3]': "layout() === 'masonry-3'",
|
|
38
|
+
}, styles: [".cmp-card-grid{--cmp-grid-gap: var(--spacing-padding-sys-padding-16);display:grid;gap:var(--cmp-grid-gap);width:100%;align-items:stretch}.cmp-card-grid--full{grid-template-columns:1fr}.cmp-card-grid--2-col{grid-template-columns:repeat(2,minmax(0,1fr))}.cmp-card-grid--3-col{grid-template-columns:repeat(3,minmax(0,1fr))}.cmp-card-grid--4-col{grid-template-columns:repeat(4,minmax(0,1fr))}.cmp-card-grid--5-col{grid-template-columns:repeat(5,minmax(0,1fr))}.cmp-card-grid--bento{grid-template-columns:2fr 1fr;grid-auto-rows:1fr}.cmp-card-grid--bento>:first-child{grid-row:span 2}.cmp-card-grid--masonry-2,.cmp-card-grid--masonry-3{display:block;column-gap:var(--cmp-grid-gap)}.cmp-card-grid--masonry-2{column-count:2}.cmp-card-grid--masonry-3{column-count:3}.cmp-card-grid--masonry-2>*,.cmp-card-grid--masonry-3>*{break-inside:avoid;margin-bottom:var(--cmp-grid-gap)}@media(max-width:56.25rem){.cmp-card-grid--3-col,.cmp-card-grid--4-col,.cmp-card-grid--5-col{grid-template-columns:repeat(2,minmax(0,1fr))}.cmp-card-grid--masonry-3{column-count:2}.cmp-card-grid--bento{grid-template-columns:1fr;grid-auto-rows:auto}.cmp-card-grid--bento>:first-child{grid-row:auto}}@media(max-width:35rem){.cmp-card-grid--2-col,.cmp-card-grid--3-col,.cmp-card-grid--4-col,.cmp-card-grid--5-col{grid-template-columns:1fr}.cmp-card-grid--masonry-2,.cmp-card-grid--masonry-3{column-count:1}}\n"] }]
|
|
39
|
+
}], propDecorators: { layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }] } });
|
|
40
|
+
//# sourceMappingURL=card-grid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-grid.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/card-grid/card-grid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,SAAS,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;;AAa7F;;;;;;;;;;;;;;;;;GAiBG;AAoBH,MAAM,OAAO,iBAAiB;IACnB,MAAM,GAAG,KAAK,CAAiB,OAAO,6EAAC,CAAC;wGADtC,iBAAiB;4FAAjB,iBAAiB,8sBAdlB,gBAAgB;;4FAcf,iBAAiB;kBAnB7B,SAAS;+BACE,oBAAoB,cAClB,IAAI,mBACC,uBAAuB,CAAC,MAAM,iBAChC,iBAAiB,CAAC,IAAI,YAC3B,gBAAgB,QAEpB;wBACJ,KAAK,EAAE,eAAe;wBACtB,6BAA6B,EAAE,qBAAqB;wBACpD,8BAA8B,EAAE,sBAAsB;wBACtD,8BAA8B,EAAE,sBAAsB;wBACtD,8BAA8B,EAAE,sBAAsB;wBACtD,8BAA8B,EAAE,sBAAsB;wBACtD,8BAA8B,EAAE,sBAAsB;wBACtD,kCAAkC,EAAE,0BAA0B;wBAC9D,kCAAkC,EAAE,0BAA0B;qBAC/D","sourcesContent":["import { ChangeDetectionStrategy, Component, ViewEncapsulation, input } from '@angular/core';\r\n\r\n/** Grid arrangement. Mirrors the Figma \"Card Grid\" base type property. */\r\nexport type CardGridLayout =\r\n | 'full'\r\n | '2-col'\r\n | '3-col'\r\n | '4-col'\r\n | '5-col'\r\n | 'bento'\r\n | 'masonry-2'\r\n | 'masonry-3';\r\n\r\n/**\r\n * Card Grid — the layout primitive for the Cards organism.\r\n *\r\n * Figma: Core Components — \"Card Grid\" base (file VFBn7KCDy3FSIlvhNo3ylq, 59621:14856).\r\n *\r\n * Arranges projected cards into one of the design's layouts: full-width,\r\n * 2/3/4/5-column even grids, a Bento grid (one feature cell + a stacked column),\r\n * or 2/3-column masonry. A 16px gutter (sys-padding-16) throughout. Cards are\r\n * projected, so any card type can fill the cells.\r\n *\r\n * <compsych-card-grid layout=\"3-col\">\r\n * <compsych-content-card … /> …\r\n * </compsych-card-grid>\r\n *\r\n * Uses `ViewEncapsulation.None` with `cmp-card-grid`-namespaced selectors so it\r\n * can lay out the *projected* card children (bento span, masonry flow), which\r\n * emulated encapsulation cannot reach.\r\n */\r\n@Component({\r\n selector: 'compsych-card-grid',\r\n standalone: true,\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.None,\r\n template: `<ng-content />`,\r\n styleUrl: './card-grid.css',\r\n host: {\r\n class: 'cmp-card-grid',\r\n '[class.cmp-card-grid--full]': \"layout() === 'full'\",\r\n '[class.cmp-card-grid--2-col]': \"layout() === '2-col'\",\r\n '[class.cmp-card-grid--3-col]': \"layout() === '3-col'\",\r\n '[class.cmp-card-grid--4-col]': \"layout() === '4-col'\",\r\n '[class.cmp-card-grid--5-col]': \"layout() === '5-col'\",\r\n '[class.cmp-card-grid--bento]': \"layout() === 'bento'\",\r\n '[class.cmp-card-grid--masonry-2]': \"layout() === 'masonry-2'\",\r\n '[class.cmp-card-grid--masonry-3]': \"layout() === 'masonry-3'\",\r\n },\r\n})\r\nexport class CardGridComponent {\r\n readonly layout = input<CardGridLayout>('3-col');\r\n}\r\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component, HostListener, Input, afterNextRender, signal, } from '@angular/core';
|
|
1
|
+
import { ChangeDetectionStrategy, Component, EventEmitter, HostListener, Input, Output, afterNextRender, signal, } from '@angular/core';
|
|
2
2
|
import * as i0 from "@angular/core";
|
|
3
3
|
/**
|
|
4
4
|
* Footer — bottom bar from the ComPsych Design System.
|
|
@@ -16,44 +16,51 @@ export class FooterComponent {
|
|
|
16
16
|
* The layout switches automatically based on their combined natural width.
|
|
17
17
|
*/
|
|
18
18
|
links = [];
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
copyright = `Copyright \u00a9 ${new Date().getFullYear()} ComPsych\u00ae. All rights reserved.`;
|
|
19
|
+
/** Copyright text displayed in the footer. */
|
|
20
|
+
copyright = '';
|
|
21
|
+
/** Emits the id of the link button that was clicked. */
|
|
22
|
+
linkClick = new EventEmitter();
|
|
24
23
|
/**
|
|
25
24
|
* Reflects whether the row layout is active.
|
|
26
25
|
* Signal so OnPush picks up DOM-driven updates from async callbacks.
|
|
27
26
|
*/
|
|
28
27
|
isRow = signal(false, ...(ngDevMode ? [{ debugName: "isRow" }] : /* istanbul ignore next */ []));
|
|
29
|
-
/**
|
|
30
|
-
* Indices of link items whose leading divider should be hidden because
|
|
31
|
-
* the item has wrapped onto a new row.
|
|
32
|
-
*/
|
|
33
|
-
hiddenDividerIndices = signal(new Set(), ...(ngDevMode ? [{ debugName: "hiddenDividerIndices" }] : /* istanbul ignore next */ []));
|
|
34
28
|
/** Combined natural width of copyright + links + gap + padding. */
|
|
35
29
|
breakpoint = 0;
|
|
30
|
+
widthObserver = new ResizeObserver(() => {
|
|
31
|
+
requestAnimationFrame(() => {
|
|
32
|
+
const items = Array.from(this.el.nativeElement.querySelectorAll('.compsych-footer__links li'));
|
|
33
|
+
const dividers = Array.from(this.el.nativeElement.querySelectorAll('.compsych-footer__divider'));
|
|
34
|
+
dividers.forEach((d) => (d.style.display = ''));
|
|
35
|
+
let prevTop = null;
|
|
36
|
+
items.forEach((item, i) => {
|
|
37
|
+
const top = item.offsetTop;
|
|
38
|
+
if (prevTop !== null && top > prevTop) {
|
|
39
|
+
dividers[i - 1].style.display = 'none';
|
|
40
|
+
}
|
|
41
|
+
prevTop = top;
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
36
45
|
constructor(el) {
|
|
37
46
|
this.el = el;
|
|
38
47
|
afterNextRender(() => {
|
|
48
|
+
this.widthObserver.observe(this.el.nativeElement);
|
|
39
49
|
this.measureBreakpoint();
|
|
40
50
|
this.checkLayout();
|
|
41
|
-
requestAnimationFrame(() => this.updateDividers());
|
|
42
51
|
});
|
|
43
52
|
}
|
|
53
|
+
ngOnDestroy() {
|
|
54
|
+
this.widthObserver.disconnect();
|
|
55
|
+
}
|
|
44
56
|
ngOnChanges() {
|
|
45
|
-
// Wait for Angular to update the DOM with the new links before re-measuring.
|
|
46
57
|
requestAnimationFrame(() => {
|
|
47
58
|
this.measureBreakpoint();
|
|
48
59
|
this.checkLayout();
|
|
49
|
-
// Second rAF ensures the layout class change has been rendered before
|
|
50
|
-
// measuring vertical positions for divider visibility.
|
|
51
|
-
requestAnimationFrame(() => this.updateDividers());
|
|
52
60
|
});
|
|
53
61
|
}
|
|
54
62
|
onResize() {
|
|
55
63
|
this.checkLayout();
|
|
56
|
-
requestAnimationFrame(() => this.updateDividers());
|
|
57
64
|
}
|
|
58
65
|
/**
|
|
59
66
|
* Measures the natural widths of the copyright and links elements once and
|
|
@@ -79,36 +86,21 @@ export class FooterComponent {
|
|
|
79
86
|
return;
|
|
80
87
|
this.isRow.set(this.el.nativeElement.offsetWidth >= this.breakpoint);
|
|
81
88
|
}
|
|
82
|
-
/**
|
|
83
|
-
* Compares the vertical positions of adjacent link list items and marks
|
|
84
|
-
* dividers as hidden for any item that has wrapped onto a new row.
|
|
85
|
-
*/
|
|
86
|
-
updateDividers() {
|
|
87
|
-
const items = Array.from(this.el.nativeElement.querySelectorAll('.compsych-footer__links li'));
|
|
88
|
-
if (items.length === 0)
|
|
89
|
-
return;
|
|
90
|
-
const hidden = new Set();
|
|
91
|
-
let prevTop = items[0].getBoundingClientRect().top;
|
|
92
|
-
for (let i = 1; i < items.length; i++) {
|
|
93
|
-
const top = items[i].getBoundingClientRect().top;
|
|
94
|
-
if (Math.round(top) !== Math.round(prevTop)) {
|
|
95
|
-
hidden.add(i);
|
|
96
|
-
}
|
|
97
|
-
prevTop = top;
|
|
98
|
-
}
|
|
99
|
-
this.hiddenDividerIndices.set(hidden);
|
|
100
|
-
}
|
|
101
89
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FooterComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
102
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: FooterComponent, isStandalone: true, selector: "compsych-footer", inputs: { links: "links" }, host: { attributes: { "role": "contentinfo" }, listeners: { "window:resize": "onResize()" }, properties: { "class.compsych-footer--row": "isRow()" } }, usesOnChanges: true, ngImport: i0, template: "@if (links.length) {\r\n <nav class=\"compsych-footer__links\" aria-label=\"Footer navigation\">\r\n <ul role=\"list\">\r\n @for (link of links; track link.
|
|
90
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: FooterComponent, isStandalone: true, selector: "compsych-footer", inputs: { links: "links", copyright: "copyright" }, outputs: { linkClick: "linkClick" }, host: { attributes: { "role": "contentinfo" }, listeners: { "window:resize": "onResize()" }, properties: { "class.compsych-footer--row": "isRow()" } }, usesOnChanges: true, ngImport: i0, template: "@if (links.length) {\r\n <nav class=\"compsych-footer__links\" aria-label=\"Footer navigation\">\r\n <ul role=\"list\">\r\n @for (link of links; track link.id; let first = $first) {\r\n <li>\r\n @if (!first) {\r\n <span class=\"compsych-footer__divider\" aria-hidden=\"true\"></span>\r\n }\r\n <button class=\"compsych-footer__link\" type=\"button\" (click)=\"linkClick.emit(link.id)\">{{ link.label }}</button>\r\n </li>\r\n }\r\n </ul>\r\n </nav>\r\n}\r\n\r\n@if (copyright) {\r\n <p class=\"compsych-footer__copyright\">{{ copyright }}</p>\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;align-items:center;gap:var(--spacing-padding-sys-padding-8);min-height:var(--sys-size-xl);padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-24);background:var(--surface-surface-container-sys-surface-container-lowest);border-top:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline);box-sizing:border-box;font-family:var(--label-small-sys-font-family);font-size:var(--label-small-sys-font-size);line-height:var(--label-small-sys-line-height);letter-spacing:var(--label-small-sys-tracking);color:var(--surface-surface-sys-on-surface-variant)}:host(.compsych-footer--row){flex-direction:row;justify-content:space-between;align-items:center}.compsych-footer__copyright{margin:0;text-align:center;font-weight:var(--label-small-sys-font-weight);overflow-wrap:anywhere}:host(.compsych-footer--row) .compsych-footer__copyright{text-align:left;order:-1}.compsych-footer__links{display:flex;align-items:center}.compsych-footer__links ul{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;justify-content:center;align-items:center;column-gap:var(--spacing-padding-sys-padding-16);row-gap:var(--spacing-padding-sys-padding-8)}.compsych-footer__links li{display:flex;align-items:center;gap:var(--spacing-padding-sys-padding-16)}.compsych-footer__link{appearance:none;background:none;border:none;padding:0;margin:0;cursor:pointer;color:var(--surface-surface-sys-on-surface-variant);font-family:var(--label-small-sys-font-family);font-size:var(--label-small-sys-font-size);font-weight:var(--label-small-sys-font-weight-emphasized);line-height:var(--label-small-sys-line-height);letter-spacing:var(--label-small-sys-tracking);white-space:nowrap}.compsych-footer__link:hover{text-decoration:underline}.compsych-footer__link:focus-visible{outline:var(--border-width-sys-stroke-thick) solid var(--add-on-primary-fixed-sys-on-primary-fixed-variant);outline-offset:var(--border-width-sys-stroke-thick);border-radius:var(--border-radius-sys-radius-xs)}.compsych-footer__divider{display:block;width:0;height:var(--sys-size-xs);border-left:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline-fixed);flex-shrink:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
103
91
|
}
|
|
104
92
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FooterComponent, decorators: [{
|
|
105
93
|
type: Component,
|
|
106
94
|
args: [{ selector: 'compsych-footer', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [], host: {
|
|
107
95
|
role: 'contentinfo',
|
|
108
96
|
'[class.compsych-footer--row]': 'isRow()',
|
|
109
|
-
}, template: "@if (links.length) {\r\n <nav class=\"compsych-footer__links\" aria-label=\"Footer navigation\">\r\n <ul role=\"list\">\r\n @for (link of links; track link.
|
|
97
|
+
}, template: "@if (links.length) {\r\n <nav class=\"compsych-footer__links\" aria-label=\"Footer navigation\">\r\n <ul role=\"list\">\r\n @for (link of links; track link.id; let first = $first) {\r\n <li>\r\n @if (!first) {\r\n <span class=\"compsych-footer__divider\" aria-hidden=\"true\"></span>\r\n }\r\n <button class=\"compsych-footer__link\" type=\"button\" (click)=\"linkClick.emit(link.id)\">{{ link.label }}</button>\r\n </li>\r\n }\r\n </ul>\r\n </nav>\r\n}\r\n\r\n@if (copyright) {\r\n <p class=\"compsych-footer__copyright\">{{ copyright }}</p>\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;align-items:center;gap:var(--spacing-padding-sys-padding-8);min-height:var(--sys-size-xl);padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-24);background:var(--surface-surface-container-sys-surface-container-lowest);border-top:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline);box-sizing:border-box;font-family:var(--label-small-sys-font-family);font-size:var(--label-small-sys-font-size);line-height:var(--label-small-sys-line-height);letter-spacing:var(--label-small-sys-tracking);color:var(--surface-surface-sys-on-surface-variant)}:host(.compsych-footer--row){flex-direction:row;justify-content:space-between;align-items:center}.compsych-footer__copyright{margin:0;text-align:center;font-weight:var(--label-small-sys-font-weight);overflow-wrap:anywhere}:host(.compsych-footer--row) .compsych-footer__copyright{text-align:left;order:-1}.compsych-footer__links{display:flex;align-items:center}.compsych-footer__links ul{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;justify-content:center;align-items:center;column-gap:var(--spacing-padding-sys-padding-16);row-gap:var(--spacing-padding-sys-padding-8)}.compsych-footer__links li{display:flex;align-items:center;gap:var(--spacing-padding-sys-padding-16)}.compsych-footer__link{appearance:none;background:none;border:none;padding:0;margin:0;cursor:pointer;color:var(--surface-surface-sys-on-surface-variant);font-family:var(--label-small-sys-font-family);font-size:var(--label-small-sys-font-size);font-weight:var(--label-small-sys-font-weight-emphasized);line-height:var(--label-small-sys-line-height);letter-spacing:var(--label-small-sys-tracking);white-space:nowrap}.compsych-footer__link:hover{text-decoration:underline}.compsych-footer__link:focus-visible{outline:var(--border-width-sys-stroke-thick) solid var(--add-on-primary-fixed-sys-on-primary-fixed-variant);outline-offset:var(--border-width-sys-stroke-thick);border-radius:var(--border-radius-sys-radius-xs)}.compsych-footer__divider{display:block;width:0;height:var(--sys-size-xs);border-left:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline-fixed);flex-shrink:0}\n"] }]
|
|
110
98
|
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { links: [{
|
|
111
99
|
type: Input
|
|
100
|
+
}], copyright: [{
|
|
101
|
+
type: Input
|
|
102
|
+
}], linkClick: [{
|
|
103
|
+
type: Output
|
|
112
104
|
}], onResize: [{
|
|
113
105
|
type: HostListener,
|
|
114
106
|
args: ['window:resize']
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/footer/footer.ts","../../../../../../packages/angular/src/lib/footer/footer.html"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,SAAS,EAET,YAAY,EACZ,KAAK,
|
|
1
|
+
{"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/footer/footer.ts","../../../../../../packages/angular/src/lib/footer/footer.html"],"names":[],"mappings":"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","sourcesContent":["import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ElementRef,\r\n EventEmitter,\r\n HostListener,\r\n Input,\r\n OnChanges,\r\n OnDestroy,\r\n Output,\r\n afterNextRender,\r\n signal,\r\n} from '@angular/core';\r\n\r\n/** A single navigation link in the footer. */\r\nexport interface FooterLink {\r\n /** Text displayed in the button. */\r\n label: string;\r\n /** Identifier emitted when the button is clicked. */\r\n id: string;\r\n}\r\n\r\n/**\r\n * Footer — bottom bar from the ComPsych Design System.\r\n *\r\n * Displays a copyright notice and an optional list of navigation links.\r\n * On initialisation the component measures the natural widths of the copyright\r\n * and links to establish a breakpoint. On every window resize it compares the\r\n * host width against that breakpoint and switches between a row layout\r\n * (copyright left, links right) and a stacked, centered column layout.\r\n */\r\n@Component({\r\n selector: 'compsych-footer',\r\n standalone: true,\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n imports: [],\r\n templateUrl: './footer.html',\r\n styleUrl: './footer.css',\r\n host: {\r\n role: 'contentinfo',\r\n '[class.compsych-footer--row]': 'isRow()',\r\n },\r\n})\r\nexport class FooterComponent implements OnChanges, OnDestroy {\r\n /**\r\n * Navigation links rendered alongside the copyright notice.\r\n * The layout switches automatically based on their combined natural width.\r\n */\r\n @Input() links: FooterLink[] = [];\r\n\r\n /** Copyright text displayed in the footer. */\r\n @Input() copyright = '';\r\n\r\n /** Emits the id of the link button that was clicked. */\r\n @Output() linkClick = new EventEmitter<string>();\r\n\r\n /**\r\n * Reflects whether the row layout is active.\r\n * Signal so OnPush picks up DOM-driven updates from async callbacks.\r\n */\r\n protected readonly isRow = signal(false);\r\n\r\n /** Combined natural width of copyright + links + gap + padding. */\r\n private breakpoint = 0;\r\n\r\n private readonly widthObserver = new ResizeObserver(() => {\r\n requestAnimationFrame(() => {\r\n const items = Array.from(\r\n this.el.nativeElement.querySelectorAll<HTMLElement>('.compsych-footer__links li'),\r\n );\r\n const dividers = Array.from(\r\n this.el.nativeElement.querySelectorAll<HTMLElement>('.compsych-footer__divider'),\r\n );\r\n dividers.forEach((d) => (d.style.display = ''));\r\n let prevTop: number | null = null;\r\n items.forEach((item, i) => {\r\n const top = item.offsetTop;\r\n if (prevTop !== null && top > prevTop) {\r\n dividers[i - 1].style.display = 'none';\r\n }\r\n prevTop = top;\r\n });\r\n });\r\n });\r\n\r\n constructor(private readonly el: ElementRef<HTMLElement>) {\r\n afterNextRender(() => {\r\n this.widthObserver.observe(this.el.nativeElement);\r\n this.measureBreakpoint();\r\n this.checkLayout();\r\n });\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.widthObserver.disconnect();\r\n }\r\n\r\n ngOnChanges(): void {\r\n requestAnimationFrame(() => {\r\n this.measureBreakpoint();\r\n this.checkLayout();\r\n });\r\n }\r\n\r\n @HostListener('window:resize')\r\n protected onResize(): void {\r\n this.checkLayout();\r\n }\r\n\r\n /**\r\n * Measures the natural widths of the copyright and links elements once and\r\n * stores their sum (plus padding and gap) as the layout breakpoint.\r\n */\r\n private measureBreakpoint(): void {\r\n const host = this.el.nativeElement;\r\n const copyrightEl = host.querySelector<HTMLElement>('.compsych-footer__copyright');\r\n const linksEl = host.querySelector<HTMLElement>('.compsych-footer__links');\r\n if (!copyrightEl) return;\r\n\r\n const style = getComputedStyle(host);\r\n const paddingInline = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);\r\n const gap = parseFloat(style.columnGap) || 0;\r\n\r\n this.breakpoint =\r\n copyrightEl.scrollWidth +\r\n (linksEl ? linksEl.scrollWidth + gap : 0) +\r\n paddingInline;\r\n }\r\n\r\n /** Switches to the row layout when the host is wide enough for both children. */\r\n private checkLayout(): void {\r\n if (!this.breakpoint) return;\r\n this.isRow.set(this.el.nativeElement.offsetWidth >= this.breakpoint);\r\n }\r\n\r\n}\r\n","@if (links.length) {\r\n <nav class=\"compsych-footer__links\" aria-label=\"Footer navigation\">\r\n <ul role=\"list\">\r\n @for (link of links; track link.id; let first = $first) {\r\n <li>\r\n @if (!first) {\r\n <span class=\"compsych-footer__divider\" aria-hidden=\"true\"></span>\r\n }\r\n <button class=\"compsych-footer__link\" type=\"button\" (click)=\"linkClick.emit(link.id)\">{{ link.label }}</button>\r\n </li>\r\n }\r\n </ul>\r\n </nav>\r\n}\r\n\r\n@if (copyright) {\r\n <p class=\"compsych-footer__copyright\">{{ copyright }}</p>\r\n}\r\n"]}
|
package/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import * as i2 from "./lib/accordion/accordion";
|
|
|
4
4
|
import * as i3 from "./lib/footer/footer";
|
|
5
5
|
import * as i4 from "./lib/header-nav/header-nav";
|
|
6
6
|
import * as i5 from "./lib/sidebar/sidebar";
|
|
7
|
+
import * as i6 from "./lib/card-base/card";
|
|
8
|
+
import * as i7 from "./lib/card-grid/card-grid";
|
|
7
9
|
export { ButtonComponent } from './lib/button/button';
|
|
8
10
|
export type { ButtonVariant, ButtonSize, ButtonType } from './lib/button/button';
|
|
9
11
|
export { AccordionComponent } from './lib/accordion/accordion';
|
|
@@ -14,8 +16,13 @@ export { HeaderNavComponent } from './lib/header-nav/header-nav';
|
|
|
14
16
|
export type { HeaderNavLevel, NavAction } from './lib/header-nav/header-nav';
|
|
15
17
|
export { SidebarComponent } from './lib/sidebar/sidebar';
|
|
16
18
|
export type { SidebarItem } from './lib/sidebar/sidebar';
|
|
19
|
+
export { CardComponent } from './lib/card-base/card';
|
|
20
|
+
export type { CardVariant, CardAppearance, CardRadius, CardPadding, CardOrientation } from './lib/card-base/card';
|
|
21
|
+
export { RippleDirective } from './lib/card-base/ripple.directive';
|
|
22
|
+
export { CardGridComponent } from './lib/card-grid/card-grid';
|
|
23
|
+
export type { CardGridLayout } from './lib/card-grid/card-grid';
|
|
17
24
|
export declare class CompsychAngularModule {
|
|
18
25
|
static ɵfac: i0.ɵɵFactoryDeclaration<CompsychAngularModule, never>;
|
|
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]>;
|
|
26
|
+
static ɵmod: i0.ɵɵNgModuleDeclaration<CompsychAngularModule, never, [typeof i1.ButtonComponent, typeof i2.AccordionComponent, typeof i3.FooterComponent, typeof i4.HeaderNavComponent, typeof i5.SidebarComponent, typeof i6.CardComponent, typeof i7.CardGridComponent], [typeof i1.ButtonComponent, typeof i2.AccordionComponent, typeof i3.FooterComponent, typeof i4.HeaderNavComponent, typeof i5.SidebarComponent, typeof i6.CardComponent, typeof i7.CardGridComponent]>;
|
|
20
27
|
static ɵinj: i0.ɵɵInjectorDeclaration<CompsychAngularModule>;
|
|
21
28
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { SafeHtml } from '@angular/platform-browser';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
import * as i1 from "./ripple.directive";
|
|
4
|
+
/** Card type — selects the projected/laid-out content. Mirrors the library's `variant` pattern. */
|
|
5
|
+
export type CardVariant = 'basic' | 'content' | 'action' | 'metric';
|
|
6
|
+
/** Visual surface treatment. Mirrors the Figma "Card Item" Style property. */
|
|
7
|
+
export type CardAppearance = 'outlined' | 'filled' | 'gradient' | 'image';
|
|
8
|
+
/** Corner radius token: `md` (12px), `lg` (16px), `xl` (24px). */
|
|
9
|
+
export type CardRadius = 'md' | 'lg' | 'xl';
|
|
10
|
+
/** Inner padding token (used by `variant="basic"`; typed variants size themselves). */
|
|
11
|
+
export type CardPadding = 'none' | 'sm' | 'md' | 'lg' | 'xl';
|
|
12
|
+
/** Action-card layout. */
|
|
13
|
+
export type CardOrientation = 'horizontal' | 'vertical';
|
|
14
|
+
/**
|
|
15
|
+
* Card — the single Cards component for the ComPsych Design System.
|
|
16
|
+
*
|
|
17
|
+
* Figma: Core Components — "Cards" (file VFBn7KCDy3FSIlvhNo3ylq, 1117:9829).
|
|
18
|
+
*
|
|
19
|
+
* One component, switched by `variant` (like Accordion's `variant`):
|
|
20
|
+
* - `basic` — a bare surface; project your own content.
|
|
21
|
+
* - `content` — icon badge / image, category chip, title, subtitle, description,
|
|
22
|
+
* action pill, arrow; hero (`headline`) and journey (`plainIcon`) layouts.
|
|
23
|
+
* - `action` — compact interactive row (icon · label/description · arrow); horizontal/vertical.
|
|
24
|
+
* - `metric` — centered value + label; `current` (selected) state.
|
|
25
|
+
*
|
|
26
|
+
* `appearance` is the orthogonal surface skin (outlined / filled / gradient / image).
|
|
27
|
+
* When `interactive`, the whole card is a button (role/tabindex/keyboard) with the
|
|
28
|
+
* hover / focus / pressed ripple. All values are sys design tokens.
|
|
29
|
+
*
|
|
30
|
+
* <compsych-card variant="content" appearance="gradient" icon="circle-play"
|
|
31
|
+
* category="Video" title="Mindful breathing" actionLabel="Watch" [interactive]="true" />
|
|
32
|
+
* <compsych-card variant="metric" value="24" label="Sessions" [current]="true" [interactive]="true" />
|
|
33
|
+
* <compsych-card>Projected content</compsych-card>
|
|
34
|
+
*/
|
|
35
|
+
export declare class CardComponent {
|
|
36
|
+
/** Card type. Defaults to `basic` (projected content). */
|
|
37
|
+
readonly variant: import("@angular/core").InputSignal<CardVariant>;
|
|
38
|
+
/** Surface treatment. Defaults to `outlined`. */
|
|
39
|
+
readonly appearance: import("@angular/core").InputSignal<CardAppearance>;
|
|
40
|
+
/** Corner radius token. Defaults to `lg` (16px). */
|
|
41
|
+
readonly radius: import("@angular/core").InputSignal<CardRadius>;
|
|
42
|
+
/** Inner padding for `variant="basic"`. Typed variants size themselves. */
|
|
43
|
+
readonly padding: import("@angular/core").InputSignal<CardPadding>;
|
|
44
|
+
/** Whole-card click target: adds role/tabindex, hover/press states and ripple. */
|
|
45
|
+
readonly interactive: import("@angular/core").InputSignal<boolean>;
|
|
46
|
+
/** Disabled visual + non-interactive. */
|
|
47
|
+
readonly disabled: import("@angular/core").InputSignal<boolean>;
|
|
48
|
+
/** Background image URL for `appearance="image"`. */
|
|
49
|
+
readonly imageSrc: import("@angular/core").InputSignal<string>;
|
|
50
|
+
/** Lucide icon name (content badge / action leading icon). */
|
|
51
|
+
readonly icon: import("@angular/core").InputSignal<string>;
|
|
52
|
+
/** Content: render the icon as a large plain glyph, text anchored bottom (journey layout). */
|
|
53
|
+
readonly plainIcon: import("@angular/core").InputSignal<boolean>;
|
|
54
|
+
/** Content: category chip text. */
|
|
55
|
+
readonly category: import("@angular/core").InputSignal<string>;
|
|
56
|
+
/** Content: title. */
|
|
57
|
+
readonly title: import("@angular/core").InputSignal<string>;
|
|
58
|
+
/** Content: subtitle. */
|
|
59
|
+
readonly subtitle: import("@angular/core").InputSignal<string>;
|
|
60
|
+
/** Content/action: supporting description. */
|
|
61
|
+
readonly description: import("@angular/core").InputSignal<string>;
|
|
62
|
+
/** Content: large hero display text (switches to the XL hero layout). */
|
|
63
|
+
readonly headline: import("@angular/core").InputSignal<string>;
|
|
64
|
+
/** Content: presentational action pill label. */
|
|
65
|
+
readonly actionLabel: import("@angular/core").InputSignal<string>;
|
|
66
|
+
/** Content: show the trailing arrow-up-right affordance. */
|
|
67
|
+
readonly showArrow: import("@angular/core").InputSignal<boolean>;
|
|
68
|
+
/** Action/metric: primary label. */
|
|
69
|
+
readonly label: import("@angular/core").InputSignal<string>;
|
|
70
|
+
/** Action: trailing Lucide icon (horizontal layout). */
|
|
71
|
+
readonly trailingIcon: import("@angular/core").InputSignal<string>;
|
|
72
|
+
/** Action: layout orientation. */
|
|
73
|
+
readonly orientation: import("@angular/core").InputSignal<CardOrientation>;
|
|
74
|
+
/** Metric: the prominent value. */
|
|
75
|
+
readonly value: import("@angular/core").InputSignal<string | number>;
|
|
76
|
+
/** Metric: selected/current state (primary border + text). */
|
|
77
|
+
readonly current: import("@angular/core").InputSignal<boolean>;
|
|
78
|
+
/** Emits on click / Enter / Space when interactive and not disabled. */
|
|
79
|
+
readonly cardClick: import("@angular/core").OutputEmitterRef<void>;
|
|
80
|
+
private readonly sanitizer;
|
|
81
|
+
private readonly ripple;
|
|
82
|
+
readonly isImage: import("@angular/core").Signal<boolean>;
|
|
83
|
+
/** CSS `url(...)` for the media layer, or null when no image is set. */
|
|
84
|
+
readonly mediaUrl: import("@angular/core").Signal<string | null>;
|
|
85
|
+
/** Padding resolved per variant; `basic` honors the `padding` input. */
|
|
86
|
+
readonly effectivePadding: import("@angular/core").Signal<CardPadding>;
|
|
87
|
+
constructor();
|
|
88
|
+
onActivate(): void;
|
|
89
|
+
onKeyDown(event: KeyboardEvent): void;
|
|
90
|
+
/** Renders a sized/weighted Lucide SVG for template use. */
|
|
91
|
+
iconSvg(name: string, size: number, strokeWidth?: number): SafeHtml;
|
|
92
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<CardComponent, never>;
|
|
93
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<CardComponent, "compsych-card", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "appearance": { "alias": "appearance"; "required": false; "isSignal": true; }; "radius": { "alias": "radius"; "required": false; "isSignal": true; }; "padding": { "alias": "padding"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "imageSrc": { "alias": "imageSrc"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "plainIcon": { "alias": "plainIcon"; "required": false; "isSignal": true; }; "category": { "alias": "category"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "headline": { "alias": "headline"; "required": false; "isSignal": true; }; "actionLabel": { "alias": "actionLabel"; "required": false; "isSignal": true; }; "showArrow": { "alias": "showArrow"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "trailingIcon": { "alias": "trailingIcon"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "current": { "alias": "current"; "required": false; "isSignal": true; }; }, { "cardClick": "cardClick"; }, never, ["*"], true, [{ directive: typeof i1.RippleDirective; inputs: {}; outputs: {}; }]>;
|
|
94
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
/**
|
|
3
|
+
* Material 3 press ripple for interactive cards.
|
|
4
|
+
*
|
|
5
|
+
* Angular port of the ComPsych DS ripple. Apply to a positioned,
|
|
6
|
+
* `overflow: hidden` element. On primary pointer-down it spawns a circle at the
|
|
7
|
+
* pointer that scales out to the farthest corner and fades; keyboard activation
|
|
8
|
+
* (Enter/Space) spawns a centered ripple.
|
|
9
|
+
*
|
|
10
|
+
* The ripple nodes are created imperatively and animated with the Web Animations
|
|
11
|
+
* API, and their colour is `currentColor` — so no global stylesheet is needed
|
|
12
|
+
* and the ripple inherits the host's text colour (white on filled/image, dark on
|
|
13
|
+
* outlined/gradient), matching the reference.
|
|
14
|
+
*/
|
|
15
|
+
export declare class RippleDirective {
|
|
16
|
+
private readonly el;
|
|
17
|
+
private readonly zone;
|
|
18
|
+
/**
|
|
19
|
+
* Imperative suppression — the card surface only ripples when `interactive`
|
|
20
|
+
* and not `disabled`. Set by the host component. Defaults to off.
|
|
21
|
+
*/
|
|
22
|
+
suppressed: boolean;
|
|
23
|
+
private static readonly DURATION_MS;
|
|
24
|
+
private layer;
|
|
25
|
+
onPointerDown(event: PointerEvent): void;
|
|
26
|
+
onKeyDown(event: KeyboardEvent): void;
|
|
27
|
+
private ensureLayer;
|
|
28
|
+
private spawn;
|
|
29
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<RippleDirective, never>;
|
|
30
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<RippleDirective, "[compsychRipple]", never, {}, {}, never, never, true, never>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
/** Grid arrangement. Mirrors the Figma "Card Grid" base type property. */
|
|
3
|
+
export type CardGridLayout = 'full' | '2-col' | '3-col' | '4-col' | '5-col' | 'bento' | 'masonry-2' | 'masonry-3';
|
|
4
|
+
/**
|
|
5
|
+
* Card Grid — the layout primitive for the Cards organism.
|
|
6
|
+
*
|
|
7
|
+
* Figma: Core Components — "Card Grid" base (file VFBn7KCDy3FSIlvhNo3ylq, 59621:14856).
|
|
8
|
+
*
|
|
9
|
+
* Arranges projected cards into one of the design's layouts: full-width,
|
|
10
|
+
* 2/3/4/5-column even grids, a Bento grid (one feature cell + a stacked column),
|
|
11
|
+
* or 2/3-column masonry. A 16px gutter (sys-padding-16) throughout. Cards are
|
|
12
|
+
* projected, so any card type can fill the cells.
|
|
13
|
+
*
|
|
14
|
+
* <compsych-card-grid layout="3-col">
|
|
15
|
+
* <compsych-content-card … /> …
|
|
16
|
+
* </compsych-card-grid>
|
|
17
|
+
*
|
|
18
|
+
* Uses `ViewEncapsulation.None` with `cmp-card-grid`-namespaced selectors so it
|
|
19
|
+
* can lay out the *projected* card children (bento span, masonry flow), which
|
|
20
|
+
* emulated encapsulation cannot reach.
|
|
21
|
+
*/
|
|
22
|
+
export declare class CardGridComponent {
|
|
23
|
+
readonly layout: import("@angular/core").InputSignal<CardGridLayout>;
|
|
24
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<CardGridComponent, never>;
|
|
25
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<CardGridComponent, "compsych-card-grid", never, { "layout": { "alias": "layout"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
26
|
+
}
|
package/lib/footer/footer.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { ElementRef, OnChanges } from '@angular/core';
|
|
1
|
+
import { ElementRef, EventEmitter, OnChanges, OnDestroy } from '@angular/core';
|
|
2
2
|
import * as i0 from "@angular/core";
|
|
3
3
|
/** A single navigation link in the footer. */
|
|
4
4
|
export interface FooterLink {
|
|
5
|
-
/** Text displayed in the
|
|
5
|
+
/** Text displayed in the button. */
|
|
6
6
|
label: string;
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/** Identifier emitted when the button is clicked. */
|
|
8
|
+
id: string;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Footer — bottom bar from the ComPsych Design System.
|
|
@@ -16,31 +16,27 @@ export interface FooterLink {
|
|
|
16
16
|
* host width against that breakpoint and switches between a row layout
|
|
17
17
|
* (copyright left, links right) and a stacked, centered column layout.
|
|
18
18
|
*/
|
|
19
|
-
export declare class FooterComponent implements OnChanges {
|
|
19
|
+
export declare class FooterComponent implements OnChanges, OnDestroy {
|
|
20
20
|
private readonly el;
|
|
21
21
|
/**
|
|
22
22
|
* Navigation links rendered alongside the copyright notice.
|
|
23
23
|
* The layout switches automatically based on their combined natural width.
|
|
24
24
|
*/
|
|
25
25
|
links: FooterLink[];
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
readonly copyright: string;
|
|
26
|
+
/** Copyright text displayed in the footer. */
|
|
27
|
+
copyright: string;
|
|
28
|
+
/** Emits the id of the link button that was clicked. */
|
|
29
|
+
linkClick: EventEmitter<string>;
|
|
31
30
|
/**
|
|
32
31
|
* Reflects whether the row layout is active.
|
|
33
32
|
* Signal so OnPush picks up DOM-driven updates from async callbacks.
|
|
34
33
|
*/
|
|
35
34
|
protected readonly isRow: import("@angular/core").WritableSignal<boolean>;
|
|
36
|
-
/**
|
|
37
|
-
* Indices of link items whose leading divider should be hidden because
|
|
38
|
-
* the item has wrapped onto a new row.
|
|
39
|
-
*/
|
|
40
|
-
protected readonly hiddenDividerIndices: import("@angular/core").WritableSignal<ReadonlySet<number>>;
|
|
41
35
|
/** Combined natural width of copyright + links + gap + padding. */
|
|
42
36
|
private breakpoint;
|
|
37
|
+
private readonly widthObserver;
|
|
43
38
|
constructor(el: ElementRef<HTMLElement>);
|
|
39
|
+
ngOnDestroy(): void;
|
|
44
40
|
ngOnChanges(): void;
|
|
45
41
|
protected onResize(): void;
|
|
46
42
|
/**
|
|
@@ -50,11 +46,6 @@ export declare class FooterComponent implements OnChanges {
|
|
|
50
46
|
private measureBreakpoint;
|
|
51
47
|
/** Switches to the row layout when the host is wide enough for both children. */
|
|
52
48
|
private checkLayout;
|
|
53
|
-
/**
|
|
54
|
-
* Compares the vertical positions of adjacent link list items and marks
|
|
55
|
-
* dividers as hidden for any item that has wrapped onto a new row.
|
|
56
|
-
*/
|
|
57
|
-
private updateDividers;
|
|
58
49
|
static ɵfac: i0.ɵɵFactoryDeclaration<FooterComponent, never>;
|
|
59
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<FooterComponent, "compsych-footer", never, { "links": { "alias": "links"; "required": false; }; }, {}, never, never, true, never>;
|
|
50
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<FooterComponent, "compsych-footer", never, { "links": { "alias": "links"; "required": false; }; "copyright": { "alias": "copyright"; "required": false; }; }, { "linkClick": "linkClick"; }, never, never, true, never>;
|
|
60
51
|
}
|