Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Components can be exported as native web components using Angular Elements, maki
| `<mfp-declarative-table>` | `<mfp-wc-declarative-table>` | [docs/declarative-table.md](docs/declarative-table.md) |
| `<mfp-declarative-form>` | `<mfp-wc-declarative-form>` | [docs/declarative-form.md](docs/declarative-form.md) |
| `<mfp-declarative-table-card>` | `<mfp-wc-declarative-table-card>` | [docs/declarative-table-card.md](docs/declarative-table-card.md) |
| `<mfp-dashboard>` | `<mfp-wc-dashboard>` | [docs/dashboard.md](docs/declarative-dashboard.md) |
| `<mfp-dashboard>` | `<mfp-wc-dashboard>` | [docs/dashboard.md](docs/dashboard.md) |

## NeoNephos Foundation

Expand Down
7 changes: 4 additions & 3 deletions docs/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,10 @@ const cards: CardConfig[] = [

### Outputs

| Output | Payload | Description |
| ------- | ---------------------------------------------------- | ------------------------------- |
| `saved` | `{ sections: SectionConfig[]; cards: CardConfig[] }` | Emits when the user saves edits |
| Output | Payload | Description |
| -------------------- | ---------------------------------------------------- | ---------------------------------------------------------------- |
| `saved` | `{ sections: SectionConfig[]; cards: CardConfig[] }` | Emits when the user saves edits |
| `actionButtonClick` | `{ event: MouseEvent; action: ButtonSettings }` | Emits when a custom action button from `config.customActions` is clicked |

### Static methods

Expand Down
6 changes: 3 additions & 3 deletions projects/ngx/cards/favorites/favorites.component.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<div class="favorites">
<ui5-title level="H5" class="favorites__title">Favorites</ui5-title>
<ui5-title class="favorites__title" level="H5">Favorites</ui5-title>
<div class="favorites__list">
@for (item of items; track item.action) {
<div class="favorites__item">
<ui5-icon name="{{ item.icon }}" class="favorites__icon"></ui5-icon>
<ui5-icon class="favorites__icon" [name]="item.icon" />
<span class="favorites__label">{{ item.label }}</span>
<ui5-button design="Transparent" icon="{{ item.icon }}">{{ item.label }}</ui5-button>
<ui5-button design="Transparent" [icon]="item.icon">{{ item.label }}</ui5-button>
</div>
}
</div>
Expand Down
6 changes: 3 additions & 3 deletions projects/ngx/cards/favorites/favorites.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { Title } from '@fundamental-ngx/ui5-webcomponents/title';

@Component({
selector: 'mfp-favorites',
templateUrl: './favorites.component.html',
styleUrls: ['./favorites.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom,
imports: [Button, Icon, Title],
templateUrl: './favorites.component.html',
styleUrl: './favorites.component.scss',
encapsulation: ViewEncapsulation.ShadowDom
})
export class Favorites {
readonly items = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<div class="service-status">
<ui5-title level="H5" class="service-status__title">Service Availability</ui5-title>
<ui5-title class="service-status__title" level="H5">Service Availability</ui5-title>
<div class="service-status__list">
@for (service of services; track service.name) {
<div class="service-status__item">
<ui5-icon name="{{ service.icon }}" class="service-status__icon"></ui5-icon>
<ui5-icon class="service-status__icon" [name]="service.icon" />
<span class="service-status__name">{{ service.name }}</span>
<div class="service-status__status {{ statusConfig[service.status].colorClass }}">
<ui5-icon name="{{ statusConfig[service.status].icon }}" class="service-status__status-icon"></ui5-icon>
<div [class]="'service-status__status ' + statusConfig[service.status].colorClass">
<ui5-icon class="service-status__status-icon" [name]="statusConfig[service.status].icon" />
<span class="service-status__status-label">{{ statusConfig[service.status].label }}</span>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ import { Component, ViewEncapsulation } from '@angular/core';
import { Icon } from '@fundamental-ngx/ui5-webcomponents/icon';
import { Title } from '@fundamental-ngx/ui5-webcomponents/title';

/** Possible health states for a service. */
export type ServiceStatusValue = 'operational' | 'degraded' | 'outage' | 'maintenance';

/** A single service entry displayed in the service-status card. */
export interface ServiceStatusItem {
/** Display name of the service. */
name: string;
/** SAP UI5 icon name used as the service icon. */
icon: string;
/** Current health status of the service. */
status: ServiceStatusValue;
}

@Component({
selector: 'mfp-service-status-card',
templateUrl: './service-status-card.component.html',
styleUrls: ['./service-status-card.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom,
imports: [Icon, Title],
templateUrl: './service-status-card.component.html',
styleUrl: './service-status-card.component.scss',
encapsulation: ViewEncapsulation.ShadowDom
})
export class ServiceStatusCard {
readonly services: ServiceStatusItem[] = [
Expand Down
22 changes: 11 additions & 11 deletions projects/ngx/cards/stories/visited-service-card.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,31 @@ import { MessageStrip } from '@fundamental-ngx/ui5-webcomponents/message-strip';
import type { Meta, StoryObj } from '@storybook/angular';

@Component({
selector: 'visited-service-card-story',
selector: 'mfp-visited-service-card-story',
imports: [MessageStrip, VisitedServiceCard],
template: `
<mfp-visited-service-card
[path]="path"
[serviceDescription]="serviceDescription"
[serviceIcon]="serviceIcon"
[serviceName]="serviceName"
[serviceType]="serviceType"
(click)="onCardClick()"
(cardClick)="onCardClick()"
/>
@if (clicked) {
<ui5-message-strip design="Information" style="margin-top: 1rem;">
Card clicked — would navigate to: {{ path }}
</ui5-message-strip>
}
`,
imports: [MessageStrip, VisitedServiceCard],
`
})
class VisitedServiceCardStory {
@Input() serviceType = '';
@Input() serviceName = '';
@Input() serviceIcon = '';
@Input() serviceDescription = '';
@Input() path = '';
@Output() click = new EventEmitter<string>();
@Output() readonly cardClick = new EventEmitter<string>();
clicked = false;
onCardClick() {
this.clicked = true;
Expand Down Expand Up @@ -117,12 +117,12 @@ export const AllCards: Story = {
render: () => ({
template: `
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; padding: 1rem;">
<visited-service-card-story serviceType="SAP HANA Cloud" serviceName="olc-hana-db" serviceIcon="database" serviceDescription="My Subaccount 1/Space dev" path="/hana/olc-hana-db"></visited-service-card-story>
<visited-service-card-story serviceType="Cloud Identity Service" serviceName="Cloud Identity Service" serviceIcon="customer" serviceDescription="My Subaccount 1/Space dev" path="/identity/cloud-identity-service"></visited-service-card-story>
<visited-service-card-story serviceType="SAP HANA Cloud" serviceName="olc-hana-db-test" serviceIcon="database" serviceDescription="My Subaccount 1/Space dev" path="/hana/olc-hana-db-test"></visited-service-card-story>
<visited-service-card-story serviceType="Application Autoscaler" serviceName="applicationtest" serviceIcon="accelerated" serviceDescription="My Subaccount 2/Space prod" path="/autoscaler/applicationtest"></visited-service-card-story>
<visited-service-card-story serviceType="Cloud Identity Service" serviceName="Cloud Identity Service" serviceIcon="customer" serviceDescription="Long text Subaccount 1/Space" path="/identity/cloud-identity-service"></visited-service-card-story>
<visited-service-card-story serviceType="Audit Log Service" serviceName="auditlog-name" serviceIcon="log" serviceDescription="My Subaccount 4/Space dev" path="/auditlog/auditlog-name"></visited-service-card-story>
<mfp-visited-service-card-story serviceType="SAP HANA Cloud" serviceName="olc-hana-db" serviceIcon="database" serviceDescription="My Subaccount 1/Space dev" path="/hana/olc-hana-db"></mfp-visited-service-card-story>
<mfp-visited-service-card-story serviceType="Cloud Identity Service" serviceName="Cloud Identity Service" serviceIcon="customer" serviceDescription="My Subaccount 1/Space dev" path="/identity/cloud-identity-service"></mfp-visited-service-card-story>
<mfp-visited-service-card-story serviceType="SAP HANA Cloud" serviceName="olc-hana-db-test" serviceIcon="database" serviceDescription="My Subaccount 1/Space dev" path="/hana/olc-hana-db-test"></mfp-visited-service-card-story>
<mfp-visited-service-card-story serviceType="Application Autoscaler" serviceName="applicationtest" serviceIcon="accelerated" serviceDescription="My Subaccount 2/Space prod" path="/autoscaler/applicationtest"></mfp-visited-service-card-story>
<mfp-visited-service-card-story serviceType="Cloud Identity Service" serviceName="Cloud Identity Service" serviceIcon="customer" serviceDescription="Long text Subaccount 1/Space" path="/identity/cloud-identity-service"></mfp-visited-service-card-story>
<mfp-visited-service-card-story serviceType="Audit Log Service" serviceName="auditlog-name" serviceIcon="log" serviceDescription="My Subaccount 4/Space dev" path="/auditlog/auditlog-name"></mfp-visited-service-card-story>
</div>
`,
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
<div class="visited-card-wrapper" (click)="click.emit(path())">
<!-- eslint-disable @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
<div
class="visited-card-wrapper"
(click)="cardClick.emit(path())"
>
<span class="visited-card__type-badge">{{ serviceType() }}</span>
<ui5-card class="visited-card">
<ui5-card-header
slot="header"
[titleText]="serviceName()"
[subtitleText]="serviceDescription()"
[interactive]="true"
[subtitleText]="serviceDescription()"
[titleText]="serviceName()"
>
<ui5-icon
class="visited-card__icon"
slot="avatar"
[name]="serviceIcon()"
class="visited-card__icon"
></ui5-icon>
/>
</ui5-card-header>
</ui5-card>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import '@ui5/webcomponents-icons/dist/AllIcons.js';

@Component({
selector: 'mfp-visited-service-card',
imports: [Card, CardHeader, Icon],
templateUrl: './visited-service-card.component.html',
styleUrls: ['./visited-service-card.component.scss'],
styleUrl: './visited-service-card.component.scss',
encapsulation: ViewEncapsulation.ShadowDom,
imports: [Card, CardHeader, Icon],
})
export class VisitedServiceCard {
serviceType = input.required<string>();
Expand All @@ -18,5 +18,5 @@ export class VisitedServiceCard {
serviceIcon = input.required<string>();
path = input.required<string>();

click = output<string>();
readonly cardClick = output<string>();
}
6 changes: 3 additions & 3 deletions projects/ngx/cards/whats-new/whats-new.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<div class="whats-new">
<ui5-title level="H5" class="whats-new__title">What's New</ui5-title>
<ui5-list separators="Inner" class="whats-new__list">
<ui5-title class="whats-new__title" level="H5">What's New</ui5-title>
<ui5-list class="whats-new__list" separators="Inner">
@for (item of headlines; track item.title) {
<ui5-li icon="{{ item.icon }}" description="{{ item.description }}">
<ui5-li [description]="item.description" [icon]="item.icon">
{{ item.title }}
</ui5-li>
}
Expand Down
6 changes: 3 additions & 3 deletions projects/ngx/cards/whats-new/whats-new.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { Title } from '@fundamental-ngx/ui5-webcomponents/title';

@Component({
selector: 'mfp-whats-new',
templateUrl: './whats-new.component.html',
styleUrls: ['./whats-new.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom,
imports: [List, ListItemStandard, Title],
templateUrl: './whats-new.component.html',
styleUrl: './whats-new.component.scss',
encapsulation: ViewEncapsulation.ShadowDom
})
export class WhatsNew {
readonly headlines = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
<ui5-dialog [open]="open()" (ui5BeforeClose)="cancel.emit()">
<div slot="header" class="add-card-dialog__header">
<ui5-dialog [open]="open()" (ui5BeforeClose)="cancelled.emit()">
<div class="add-card-dialog__header" slot="header">
<ui5-title level="H5">Add Card</ui5-title>
</div>
<div class="add-card-dialog">
@if (availableCards().length === 0) {
<p class="add-card-dialog__empty">No cards available.</p>
}

@for (ac of availableCards(); track ac.id) {
@let alreadyAdded = addedCardsIds().has(ac.id);
<div
Expand All @@ -15,14 +13,16 @@
<ui5-checkbox
[checked]="alreadyAdded || selectedIds().has(ac.id)"
[disabled]="alreadyAdded"
(ui5Change)="toggle(ac.id)"
[text]="ac.label || ac.component"
(ui5Change)="toggle(ac.id)"
/>
</div>
} @empty {
<p class="add-card-dialog__empty">No cards available.</p>
}
</div>
<div slot="footer" class="add-card-dialog__footer">
<div class="add-card-dialog__footer" slot="footer">
<ui5-button design="Emphasized" (click)="confirmAdd()">Add</ui5-button>
<ui5-button design="Transparent" (click)="cancel.emit()">Cancel</ui5-button>
<ui5-button design="Transparent" (click)="cancelled.emit()">Cancel</ui5-button>
</div>
</ui5-dialog>
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('AddCardDialog', () => {
const { fixture, component } = setup();
let emitted = 0;

component.cancel.subscribe(() => emitted++);
component.cancelled.subscribe(() => emitted++);
fixture.componentRef.setInput('open', true);
fixture.componentRef.setInput('availableCards', []);
fixture.detectChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ import { Title } from '@fundamental-ngx/ui5-webcomponents/title';

@Component({
selector: 'mfp-add-card-dialog',
templateUrl: './add-card-dialog.component.html',
styleUrls: ['./add-card-dialog.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom,
imports: [Button, CheckBox, Dialog, Title],
templateUrl: './add-card-dialog.component.html',
styleUrl: './add-card-dialog.component.scss',
encapsulation: ViewEncapsulation.ShadowDom
})
export class AddCardDialog {
availableCards = input<CardConfig[]>([]);
addedCardsIds = input<Set<string>>(new Set());
open = input<boolean>(false);

confirm = output<CardConfig[]>();
cancel = output<void>();
readonly confirm = output<CardConfig[]>();
readonly cancelled = output<void>();

selectedIds = signal<Set<string>>(new Set());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
</div>
}
<div
[style.pointer-events]="editMode() ? 'none' : 'auto'"
class="component-host"
[style.pointer-events]="editMode() ? 'none' : 'auto'"
>
<div #elementHost></div>
</div>
</div>
} @else {
<div class="card">
<div
[style.pointer-events]="editMode() ? 'none' : 'auto'"
class="card__body"
[style.pointer-events]="editMode() ? 'none' : 'auto'"
>
<ng-content />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,36 @@ import {
import { Component } from '@angular/core';

@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'dashboard-test-card',
standalone: true,
template: 'dashboard test card',
})
class DashboardTestCard {}

@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: '[dashboard-test-card]',
standalone: true,
template: 'dashboard attr card',
})
class DashboardAttrCard {}

@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'dashboard-test-card',
standalone: true,
template: 'dashboard duplicate card',
host: {
'data-test-duplicate': 'true',
},
template: 'dashboard duplicate card',
}
})
class DashboardDuplicateCard {}

@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'dashboard-non-standalone-card',
// eslint-disable-next-line @angular-eslint/prefer-standalone
standalone: false,
template: 'dashboard non-standalone card',
})
Expand All @@ -41,35 +46,35 @@ describe('dashboard card registry', () => {
});

it('registers standalone Angular components by selector', () => {
expect(() => addComponentToRegistry([DashboardTestCard])).not.toThrow();
expect(() => { addComponentToRegistry([DashboardTestCard]); }).not.toThrow();
});

it('rejects non-component registrations', () => {
class NotAComponent {}

expect(() => addComponentToRegistry([NotAComponent])).toThrowError(
expect(() => { addComponentToRegistry([NotAComponent]); }).toThrow(
'Dashboard card registration failed: "NotAComponent" is not an Angular component.',
);
});

it('rejects selectors that are not a single element selector', () => {
expect(() => addComponentToRegistry([DashboardAttrCard])).toThrowError(
expect(() => { addComponentToRegistry([DashboardAttrCard]); }).toThrow(
/must use a single element selector\. Received "\[dashboard-test-card\]"./,
);
});

it('rejects non-standalone Angular components', () => {
expect(() =>
addComponentToRegistry([DashboardNonStandaloneCard]),
).toThrowError(
{ addComponentToRegistry([DashboardNonStandaloneCard]); },
).toThrow(
'Dashboard card registration failed: "dashboard-non-standalone-card" must be a standalone Angular component.',
);
});

it('rejects duplicate selector registrations for different component types', () => {
addComponentToRegistry([DashboardTestCard]);

expect(() => addComponentToRegistry([DashboardDuplicateCard])).toThrowError(
expect(() => { addComponentToRegistry([DashboardDuplicateCard]); }).toThrow(
'Dashboard card registration failed: selector "dashboard-test-card" is already registered.',
);
});
Expand Down
Loading