@openmfp/webcomponents 0.6.1
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/.github/workflows/pipeline.yaml +41 -0
- package/.storybook/main.ts +34 -0
- package/.storybook/preview.ts +29 -0
- package/.storybook/tsconfig.json +11 -0
- package/AGENTS.md +153 -0
- package/CODEOWNERS +6 -0
- package/CONTRIBUTING.md +95 -0
- package/LICENSE +201 -0
- package/LICENSES/Apache-2.0.txt +73 -0
- package/README.md +91 -0
- package/REUSE.toml +9 -0
- package/angular.json +157 -0
- package/docs/dashboard.md +358 -0
- package/docs/declarative-form.md +178 -0
- package/docs/declarative-table-card.md +235 -0
- package/docs/declarative-table.md +315 -0
- package/eslint.config.js +41 -0
- package/package.json +73 -0
- package/projects/ngx/cards/favorites/favorites.component.html +12 -0
- package/projects/ngx/cards/favorites/favorites.component.scss +50 -0
- package/projects/ngx/cards/favorites/favorites.component.ts +19 -0
- package/projects/ngx/cards/public-api.ts +4 -0
- package/projects/ngx/cards/service-status/service-status-card.component.html +15 -0
- package/projects/ngx/cards/service-status/service-status-card.component.scss +87 -0
- package/projects/ngx/cards/service-status/service-status-card.component.ts +36 -0
- package/projects/ngx/cards/stories/visited-service-card.stories.ts +149 -0
- package/projects/ngx/cards/visited-service-card/visited-service-card.component.html +17 -0
- package/projects/ngx/cards/visited-service-card/visited-service-card.component.scss +34 -0
- package/projects/ngx/cards/visited-service-card/visited-service-card.component.ts +22 -0
- package/projects/ngx/cards/whats-new/whats-new.component.html +10 -0
- package/projects/ngx/cards/whats-new/whats-new.component.scss +25 -0
- package/projects/ngx/cards/whats-new/whats-new.component.ts +46 -0
- package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.html +28 -0
- package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.scss +44 -0
- package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.spec.ts +85 -0
- package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.ts +58 -0
- package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.html +29 -0
- package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.scss +63 -0
- package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.spec.ts +255 -0
- package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.ts +75 -0
- package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.spec.ts +76 -0
- package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.ts +109 -0
- package/projects/ngx/declarative-ui/dashboard/card/utils/index.ts +4 -0
- package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.spec.ts +141 -0
- package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.ts +44 -0
- package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.spec.ts +142 -0
- package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.ts +52 -0
- package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.spec.ts +107 -0
- package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.ts +22 -0
- package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.html +134 -0
- package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.scss +88 -0
- package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.spec.ts +354 -0
- package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.ts +238 -0
- package/projects/ngx/declarative-ui/dashboard/dashboard/index.ts +1 -0
- package/projects/ngx/declarative-ui/dashboard/index.ts +5 -0
- package/projects/ngx/declarative-ui/dashboard/models/constants.ts +2 -0
- package/projects/ngx/declarative-ui/dashboard/models/dashboard.model.ts +50 -0
- package/projects/ngx/declarative-ui/dashboard/models/index.ts +1 -0
- package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.html +28 -0
- package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.scss +85 -0
- package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.spec.ts +104 -0
- package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.ts +23 -0
- package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.html +62 -0
- package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.scss +12 -0
- package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.spec.ts +301 -0
- package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.ts +166 -0
- package/projects/ngx/declarative-ui/form/declarative-form/index.ts +1 -0
- package/projects/ngx/declarative-ui/form/index.ts +2 -0
- package/projects/ngx/declarative-ui/form/models/form-field-definition.ts +15 -0
- package/projects/ngx/declarative-ui/form/models/index.ts +1 -0
- package/projects/ngx/declarative-ui/form/utils/set-property-by-path.ts +30 -0
- package/projects/ngx/declarative-ui/models/index.ts +2 -0
- package/projects/ngx/declarative-ui/models/resource.ts +5 -0
- package/projects/ngx/declarative-ui/models/ui-definition.ts +95 -0
- package/projects/ngx/declarative-ui/public-api.ts +4 -0
- package/projects/ngx/declarative-ui/stories/add-card-dialog.stories.ts +91 -0
- package/projects/ngx/declarative-ui/stories/background-lightblue.png +0 -0
- package/projects/ngx/declarative-ui/stories/dashboard.cards.ts +107 -0
- package/projects/ngx/declarative-ui/stories/dashboard.stories.ts +296 -0
- package/projects/ngx/declarative-ui/stories/declarative-form.stories.ts +149 -0
- package/projects/ngx/declarative-ui/stories/declarative-table-card.stories.ts +358 -0
- package/projects/ngx/declarative-ui/stories/declarative-table.stories.ts +363 -0
- package/projects/ngx/declarative-ui/stories/pods-table.config.ts +188 -0
- package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.html +138 -0
- package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.scss +21 -0
- package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.spec.ts +345 -0
- package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.ts +61 -0
- package/projects/ngx/declarative-ui/table/declarative-table/index.ts +1 -0
- package/projects/ngx/declarative-ui/table/index.ts +2 -0
- package/projects/ngx/declarative-ui/table/models/index.ts +14 -0
- package/projects/ngx/declarative-ui/table/models/table.model.ts +17 -0
- package/projects/ngx/declarative-ui/table/utils/cssRules.engine.spec.ts +146 -0
- package/projects/ngx/declarative-ui/table/utils/cssRules.engine.ts +69 -0
- package/projects/ngx/declarative-ui/table/utils/field-definition.utils.spec.ts +70 -0
- package/projects/ngx/declarative-ui/table/utils/field-definition.utils.ts +13 -0
- package/projects/ngx/declarative-ui/table/utils/proccess-fields.spec.ts +511 -0
- package/projects/ngx/declarative-ui/table/utils/proccess-fields.ts +71 -0
- package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.spec.ts +372 -0
- package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.ts +98 -0
- package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-cell.constants.ts +5 -0
- package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.html +1 -0
- package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.scss +0 -0
- package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.spec.ts +119 -0
- package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.ts +35 -0
- package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.html +7 -0
- package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.scss +0 -0
- package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.spec.ts +114 -0
- package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.ts +19 -0
- package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.html +7 -0
- package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.scss +10 -0
- package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.spec.ts +188 -0
- package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.ts +16 -0
- package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.html +59 -0
- package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.scss +33 -0
- package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.spec.ts +316 -0
- package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.ts +115 -0
- package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.html +156 -0
- package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.scss +123 -0
- package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.spec.ts +786 -0
- package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.ts +286 -0
- package/projects/ngx/declarative-ui/table-card/index.ts +2 -0
- package/projects/ngx/declarative-ui/table-card/models/configs.ts +46 -0
- package/projects/ngx/declarative-ui/tsconfig.lib.json +11 -0
- package/projects/ngx/declarative-ui/tsconfig.lib.prod.json +9 -0
- package/projects/ngx/declarative-ui/tsconfig.spec.json +9 -0
- package/projects/ngx/ng-package.json +8 -0
- package/projects/ngx/package.json +22 -0
- package/projects/ngx/public-api.ts +2 -0
- package/projects/ngx/tsconfig.lib.json +11 -0
- package/projects/ngx/tsconfig.lib.prod.json +9 -0
- package/projects/webcomponents/main.ts +92 -0
- package/projects/webcomponents/tsconfig.app.json +9 -0
- package/projects/webcomponents-dashboard/main.ts +15 -0
- package/projects/webcomponents-dashboard/tsconfig.app.json +9 -0
- package/renovate.json +6 -0
- package/scripts/bundle-wc.mjs +79 -0
- package/tsconfig.json +37 -0
- package/tsconfig.spec.json +8 -0
- package/tsconfig.storybook.json +16 -0
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# DeclarativeTable
|
|
2
|
+
|
|
3
|
+
A data table web component that renders rows and columns from a declarative column definition. Supports pagination, grouped columns, conditional cell styling, and multiple cell display modes (plain text, link, boolean icon, secret, tooltip, button, image).
|
|
4
|
+
|
|
5
|
+
## Tags
|
|
6
|
+
|
|
7
|
+
| Usage | Tag |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Angular component | `<mfp-declarative-table>` |
|
|
10
|
+
| Web Component (framework-agnostic) | `<mfp-wc-declarative-table>` |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Usage as a web component
|
|
15
|
+
|
|
16
|
+
Include the bundle and set properties via JavaScript. Because the component uses Shadow DOM, no extra CSS setup is needed.
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<!DOCTYPE html>
|
|
20
|
+
<html>
|
|
21
|
+
<head>
|
|
22
|
+
<script type="module" src="declarative-table.js"/>
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
<mfp-wc-declarative-table id="table"></mfp-wc-declarative-table>
|
|
26
|
+
|
|
27
|
+
<script type="module">
|
|
28
|
+
const table = document.getElementById('table');
|
|
29
|
+
|
|
30
|
+
table.columns = [
|
|
31
|
+
{ label: 'Name', property: 'metadata.name' },
|
|
32
|
+
{ label: 'Status', property: 'status.phase' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
table.resources = [
|
|
36
|
+
{ metadata: { name: 'pod-1' }, status: { phase: 'Running' } },
|
|
37
|
+
{ metadata: { name: 'pod-2' }, status: { phase: 'Pending' } },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
table.trackByPath = 'metadata.name';
|
|
41
|
+
</script>
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> `columns`, `resources`, and `trackBy` are JavaScript properties, not HTML attributes. They must be set programmatically after the element is available in the DOM.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Usage as an Angular component
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { DeclarativeTable } from '@openmfp/webcomponents';
|
|
54
|
+
import { TableFieldDefinition } from '@openmfp/webcomponents';
|
|
55
|
+
|
|
56
|
+
@Component({
|
|
57
|
+
imports: [DeclarativeTable],
|
|
58
|
+
template: `
|
|
59
|
+
<mfp-declarative-table
|
|
60
|
+
[columns]="columns"
|
|
61
|
+
[resources]="resources"
|
|
62
|
+
[trackByPath]="trackByPath"
|
|
63
|
+
[hasMore]="hasMore"
|
|
64
|
+
[paginationLimit]="pageSize"
|
|
65
|
+
[totalItemsCount]="total"
|
|
66
|
+
(tableRowClicked)="onRowClick($event)"
|
|
67
|
+
(loadMoreResources)="loadMore()"
|
|
68
|
+
(paginationLimitChanged)="onPageSizeChange($event)"
|
|
69
|
+
/>
|
|
70
|
+
`,
|
|
71
|
+
})
|
|
72
|
+
export class MyComponent {
|
|
73
|
+
columns: TableFieldDefinition[] = [
|
|
74
|
+
{ label: 'Name', property: 'metadata.name' },
|
|
75
|
+
{ label: 'Status', property: 'status.phase' },
|
|
76
|
+
];
|
|
77
|
+
resources = [...];
|
|
78
|
+
trackByPath = 'metadata.name';
|
|
79
|
+
hasMore = false;
|
|
80
|
+
pageSize = 10;
|
|
81
|
+
total = 0;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## API
|
|
88
|
+
|
|
89
|
+
### Inputs
|
|
90
|
+
|
|
91
|
+
| Input | Type | Required | Default | Description |
|
|
92
|
+
|---|---|---|---|---|
|
|
93
|
+
| `columns` | `TableFieldDefinition[]` | yes | — | Column definitions |
|
|
94
|
+
| `resources` | `GenericResource[]` | yes | — | Data rows |
|
|
95
|
+
| `trackByPath` | `string` | no | `'id'` | JSONPath (dot-notation) into each resource used as the row identity key |
|
|
96
|
+
| `totalItemsCount` | `number` | no | — | Total count of all items across pages |
|
|
97
|
+
| `paginationLimit` | `number` | no | `5` | Rows per page shown in the page-size selector |
|
|
98
|
+
| `hasMore` | `boolean` | no | `false` | Show the load-more trigger at the bottom |
|
|
99
|
+
|
|
100
|
+
### Outputs / Events
|
|
101
|
+
|
|
102
|
+
| Event | Detail payload | Description |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `tableRowClicked` | row object | Fires when a row is clicked |
|
|
105
|
+
| `buttonClick` | `{ event, field, resource }` | Fires when a button cell is clicked |
|
|
106
|
+
| `loadMoreResources` | — | Fires when the user triggers load more |
|
|
107
|
+
| `paginationLimitChanged` | `number` | Fires when the user changes the page size |
|
|
108
|
+
|
|
109
|
+
**Listening to events from a web component:**
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
table.addEventListener('tableRowClicked', (e) => console.log(e.detail));
|
|
113
|
+
table.addEventListener('loadMoreResources', () => fetchNextPage());
|
|
114
|
+
table.addEventListener('paginationLimitChanged', (e) => {
|
|
115
|
+
table.paginationLimit = e.detail;
|
|
116
|
+
});
|
|
117
|
+
table.addEventListener('buttonClick', (e) => {
|
|
118
|
+
const { field, resource } = e.detail;
|
|
119
|
+
console.log('Button clicked for', resource);
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Column definition (`TableFieldDefinition`)
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
interface TableFieldDefinition {
|
|
129
|
+
label?: string;
|
|
130
|
+
property?: string; // dot-notation path into the resource object
|
|
131
|
+
jsonPathExpression?: string; // explicit JSONPath expression
|
|
132
|
+
propertyField?: PropertyField; // access a sub-key with optional transforms
|
|
133
|
+
value?: string; // static fallback value
|
|
134
|
+
uiSettings?: UiSettings;
|
|
135
|
+
group?: {
|
|
136
|
+
name: string; // columns sharing the same name are merged into one header
|
|
137
|
+
label?: string; // label for the merged header
|
|
138
|
+
delimiter?: string; // separator between values (default: space)
|
|
139
|
+
multiline?: boolean; // render values on separate lines instead
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Resolving cell values
|
|
145
|
+
|
|
146
|
+
Values are resolved in this order:
|
|
147
|
+
|
|
148
|
+
1. `jsonPathExpression` — evaluated as a JSONPath query against the resource (e.g. `$.spec.containers[0].image`)
|
|
149
|
+
2. `property` — dot-notation path; a `$.` prefix is added automatically if missing
|
|
150
|
+
3. `propertyField` — accesses `resource[property][propertyField.key]` with optional transforms
|
|
151
|
+
4. `value` — static string, used as a fallback when the resource yields no value
|
|
152
|
+
|
|
153
|
+
**Examples:**
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
// Simple dot-notation
|
|
157
|
+
{ label: 'Name', property: 'metadata.name' }
|
|
158
|
+
|
|
159
|
+
// Explicit JSONPath
|
|
160
|
+
{ label: 'Image', jsonPathExpression: '$.spec.containers[0].image' }
|
|
161
|
+
|
|
162
|
+
// Sub-key with transform
|
|
163
|
+
{ label: 'Created', property: 'metadata', propertyField: { key: 'creationTimestamp', transform: ['uppercase'] } }
|
|
164
|
+
|
|
165
|
+
// Static fallback when property may be absent
|
|
166
|
+
{ label: 'Message', property: 'status.message', value: 'N/A' }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `PropertyField` transforms
|
|
170
|
+
|
|
171
|
+
Transforms can be chained and are applied left to right.
|
|
172
|
+
|
|
173
|
+
| Transform | Effect |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `uppercase` | `hello` → `HELLO` |
|
|
176
|
+
| `lowercase` | `HELLO` → `hello` |
|
|
177
|
+
| `capitalize` | `hello` → `Hello` |
|
|
178
|
+
| `encode` | Base64-encodes the value |
|
|
179
|
+
| `decode` | Base64-decodes the value |
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
{ property: 'metadata', propertyField: { key: 'name', transform: ['capitalize'] } }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Cell display modes (`uiSettings.displayAs`)
|
|
188
|
+
|
|
189
|
+
By default a cell renders its value as plain text. Use `uiSettings.displayAs` to change the rendering:
|
|
190
|
+
|
|
191
|
+
| `displayAs` | Renders as |
|
|
192
|
+
|---|---|
|
|
193
|
+
| _(unset)_ | Plain text |
|
|
194
|
+
| `'secret'` | Masked value with a toggle-visibility button |
|
|
195
|
+
| `'boolIcon'` | Check / X icon for `"true"` / `"false"` string values |
|
|
196
|
+
| `'link'` | Clickable anchor (the value must be a valid URL) |
|
|
197
|
+
| `'tooltip'` | Text with an info icon; the full value appears on hover |
|
|
198
|
+
| `'alert'` | Alert-styled text |
|
|
199
|
+
| `'img'` | `<img>` element using the value as `src` |
|
|
200
|
+
| `'button'` | Action button (requires `buttonSettings`) |
|
|
201
|
+
|
|
202
|
+
### Copy button
|
|
203
|
+
|
|
204
|
+
Add a copy-to-clipboard icon to any cell:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
{ label: 'ID', property: 'metadata.uid', uiSettings: { withCopyButton: true } }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Button cells
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
{
|
|
214
|
+
label: 'Actions',
|
|
215
|
+
property: 'metadata.name',
|
|
216
|
+
uiSettings: {
|
|
217
|
+
displayAs: 'button',
|
|
218
|
+
buttonSettings: {
|
|
219
|
+
text: 'Open',
|
|
220
|
+
icon: 'action',
|
|
221
|
+
design: 'Emphasized', // 'Default' | 'Positive' | 'Negative' | 'Transparent' | 'Emphasized' | 'Attention'
|
|
222
|
+
action: 'navigate', // 'navigate' | 'openInModal'
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
A `buttonClick` event fires with `{ event, field, resource }` when clicked.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Grouped columns
|
|
233
|
+
|
|
234
|
+
Columns that share the same `group.name` are collapsed into a single table column. Their values are displayed together, separated by `group.delimiter` or on separate lines when `group.multiline` is `true`.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
columns = [
|
|
238
|
+
{
|
|
239
|
+
label: 'First name',
|
|
240
|
+
property: 'firstName',
|
|
241
|
+
group: { name: 'fullName', label: 'Full Name', delimiter: ' ' },
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
label: 'Last name',
|
|
245
|
+
property: 'lastName',
|
|
246
|
+
group: { name: 'fullName' },
|
|
247
|
+
},
|
|
248
|
+
{ label: 'Email', property: 'email' },
|
|
249
|
+
];
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The above produces two visible columns: **Full Name** and **Email**.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Conditional cell styling (`cssRules`)
|
|
257
|
+
|
|
258
|
+
Apply inline styles to a cell when its value meets a condition:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
{
|
|
262
|
+
label: 'Status',
|
|
263
|
+
property: 'status.phase',
|
|
264
|
+
uiSettings: {
|
|
265
|
+
cssRules: [
|
|
266
|
+
{ if: { condition: 'equals', value: 'Running' }, styles: { color: 'green' } },
|
|
267
|
+
{ if: { condition: 'equals', value: 'Failed' }, styles: { color: 'red', fontWeight: 'bold' } },
|
|
268
|
+
{ if: { condition: 'contains', value: 'Pending' }, styles: { color: 'orange' } },
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Available conditions: `equals`, `notEquals`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual`, `contains`.
|
|
275
|
+
|
|
276
|
+
`cssCustomization` applies static styles unconditionally:
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
uiSettings: { cssCustomization: { fontStyle: 'italic' } }
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Pagination
|
|
285
|
+
|
|
286
|
+
When `hasMore` is `true` a **Load More** trigger appears at the bottom of the table. A page-size selector is always present.
|
|
287
|
+
|
|
288
|
+
```js
|
|
289
|
+
table.hasMore = true;
|
|
290
|
+
table.totalItemsCount = 100;
|
|
291
|
+
table.paginationLimit = 10;
|
|
292
|
+
|
|
293
|
+
table.addEventListener('loadMoreResources', () => {
|
|
294
|
+
fetchNextPage().then((rows) => {
|
|
295
|
+
table.resources = [...table.resources, ...rows];
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
table.addEventListener('paginationLimitChanged', (e) => {
|
|
300
|
+
table.paginationLimit = e.detail;
|
|
301
|
+
reloadWithNewLimit(e.detail);
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Resource shape
|
|
308
|
+
|
|
309
|
+
Any plain object works as a resource. Three optional fields control table behaviour:
|
|
310
|
+
|
|
311
|
+
| Field | Type | Description |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| `id` | `string` | Default `trackByPath` target; used as the row identity key unless overridden |
|
|
314
|
+
| `isAvailable` | `boolean` | When `false`, the row is rendered as non-interactive |
|
|
315
|
+
| `accessibleName` | `string` | Accessible label attached to the row element |
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import angularConfig from '@openmfp/eslint-config-typescript/angular.js';
|
|
3
|
+
import tsEslint from 'typescript-eslint';
|
|
4
|
+
|
|
5
|
+
export default tsEslint.config(
|
|
6
|
+
{
|
|
7
|
+
ignores: ['dist', 'coverage', '.angular'],
|
|
8
|
+
},
|
|
9
|
+
...angularConfig,
|
|
10
|
+
{
|
|
11
|
+
// Disable jest rules — this project uses Vitest, not Jest
|
|
12
|
+
files: ['**/*.spec.ts'],
|
|
13
|
+
rules: {
|
|
14
|
+
'jest/no-deprecated-functions': 'off',
|
|
15
|
+
'jest/expect-expect': 'off',
|
|
16
|
+
'jest/valid-title': 'off',
|
|
17
|
+
'jest/no-conditional-expect': 'off',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
files: ['**/*.ts'],
|
|
22
|
+
languageOptions: {
|
|
23
|
+
parserOptions: {
|
|
24
|
+
projectService: true,
|
|
25
|
+
tsconfigRootDir: import.meta.dirname,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
rules: {
|
|
29
|
+
// Override shared config defaults for this library
|
|
30
|
+
'@angular-eslint/prefer-on-push-component-change-detection': 'off',
|
|
31
|
+
'@angular-eslint/directive-selector': [
|
|
32
|
+
'error',
|
|
33
|
+
{ type: 'attribute', prefix: 'mfp', style: 'camelCase' },
|
|
34
|
+
],
|
|
35
|
+
'@angular-eslint/component-selector': [
|
|
36
|
+
'error',
|
|
37
|
+
{ type: 'element', prefix: 'mfp', style: 'kebab-case' },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openmfp/webcomponents",
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build:ngx": "ng build ngx",
|
|
7
|
+
"build:wc": "ng build webcomponents && ng build webcomponents-dashboard && node scripts/bundle-wc.mjs",
|
|
8
|
+
"build": "npm run build:ngx && npm run build:wc",
|
|
9
|
+
"build:watch": "node -e \"require('fs').mkdirSync('dist',{recursive:true})\" && nodemon --ignore dist --ignore public --ext js,yml,yaml,ts,html,css,scss,json,md --exec \"rimraf dist && npm run build && yalc publish dist/ngx --push --sig\"",
|
|
10
|
+
"test": "ng test ngx --watch=false",
|
|
11
|
+
"test:watch": "ng test ngx",
|
|
12
|
+
"test:cov": "ng test ngx --watch=false --coverage",
|
|
13
|
+
"storybook": "ng run ngx:storybook",
|
|
14
|
+
"build:storybook": "ng run ngx:build-storybook",
|
|
15
|
+
"lint": "ng lint",
|
|
16
|
+
"lint:fix": "ng lint --fix",
|
|
17
|
+
"format": "prettier . --write --list-different",
|
|
18
|
+
"check-format": "prettier . --check"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20.0.0",
|
|
22
|
+
"npm": ">=10.0.0"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"gridstack": "12.6.0",
|
|
26
|
+
"tslib": "2.8.1"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@angular/animations": "^21.2.0",
|
|
30
|
+
"@angular/common": "^21.2.0",
|
|
31
|
+
"@angular/compiler": "^21.2.0",
|
|
32
|
+
"@angular/core": "^21.2.0",
|
|
33
|
+
"@angular/elements": "^21.2.0",
|
|
34
|
+
"@angular/forms": "^21.2.0",
|
|
35
|
+
"@angular/platform-browser": "^21.2.0",
|
|
36
|
+
"@fundamental-ngx/ui5-webcomponents": "0.59.1",
|
|
37
|
+
"@fundamental-ngx/ui5-webcomponents-fiori": "0.59.1",
|
|
38
|
+
"jsonpath": "^1.1.1",
|
|
39
|
+
"rxjs": "^7.8.2"
|
|
40
|
+
},
|
|
41
|
+
"prettier": "@openmfp/config-prettier",
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@angular-devkit/build-angular": "21.2.7",
|
|
44
|
+
"@angular-eslint/builder": "^21.3.1",
|
|
45
|
+
"@angular/build": "^21.2.7",
|
|
46
|
+
"@angular/cli": "21.2.7",
|
|
47
|
+
"@angular/compiler-cli": "21.2.12",
|
|
48
|
+
"@compodoc/compodoc": "^1.2.1",
|
|
49
|
+
"@openmfp/config-prettier": "0.9.2",
|
|
50
|
+
"@openmfp/eslint-config-typescript": "0.7.11",
|
|
51
|
+
"@storybook/addon-docs": "^10.3.5",
|
|
52
|
+
"@storybook/addon-links": "^10.3.5",
|
|
53
|
+
"@storybook/angular": "^10.3.5",
|
|
54
|
+
"@types/jsonpath": "^0.2.4",
|
|
55
|
+
"@types/node": "24.12.2",
|
|
56
|
+
"@vitest/coverage-v8": "4.1.5",
|
|
57
|
+
"concurrently": "9.2.1",
|
|
58
|
+
"eslint": "9.39.4",
|
|
59
|
+
"jsdom": "29.1.1",
|
|
60
|
+
"ng-packagr": "^21.1.0",
|
|
61
|
+
"nodemon": "3.1.14",
|
|
62
|
+
"prettier": "3.8.3",
|
|
63
|
+
"rimraf": "6.1.3",
|
|
64
|
+
"storybook": "^10.3.5",
|
|
65
|
+
"typescript": "5.9.3",
|
|
66
|
+
"typescript-eslint": "^8.33.0",
|
|
67
|
+
"vitest": "4.1.5",
|
|
68
|
+
"wait-on": "9.0.5"
|
|
69
|
+
},
|
|
70
|
+
"overrides": {
|
|
71
|
+
"underscore": "1.13.8"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div class="favorites">
|
|
2
|
+
<ui5-title level="H5" class="favorites__title">Favorites</ui5-title>
|
|
3
|
+
<div class="favorites__list">
|
|
4
|
+
@for (item of items; track item.action) {
|
|
5
|
+
<div class="favorites__item">
|
|
6
|
+
<ui5-icon name="{{ item.icon }}" class="favorites__icon"></ui5-icon>
|
|
7
|
+
<span class="favorites__label">{{ item.label }}</span>
|
|
8
|
+
<ui5-button design="Transparent" icon="{{ item.icon }}">{{ item.label }}</ui5-button>
|
|
9
|
+
</div>
|
|
10
|
+
}
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
height: 100%;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.favorites {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
height: 100%;
|
|
10
|
+
background: var(--sapTile_Background, #fff);
|
|
11
|
+
border-radius: var(--_ui5_card_border-radius, 0.5rem);
|
|
12
|
+
border: var(--_ui5_card_border, 1px solid var(--sapTile_BorderColor, #e5e5e5));
|
|
13
|
+
box-shadow: var(--_ui5_card_box_shadow, var(--sapContent_Shadow0));
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
|
|
16
|
+
&__title {
|
|
17
|
+
padding: 1rem 1rem 0.5rem;
|
|
18
|
+
flex-shrink: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&__list {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
flex: 1;
|
|
25
|
+
overflow-y: auto;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&__item {
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 0.75rem;
|
|
32
|
+
padding: 0.5rem 1rem;
|
|
33
|
+
border-bottom: 1px solid var(--sapList_BorderColor, #e5e5e5);
|
|
34
|
+
|
|
35
|
+
&:last-child {
|
|
36
|
+
border-bottom: none;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&__icon {
|
|
41
|
+
flex-shrink: 0;
|
|
42
|
+
color: var(--sapHighlightColor, #0070f2);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&__label {
|
|
46
|
+
flex: 1;
|
|
47
|
+
font-size: var(--sapFontSize, 0.875rem);
|
|
48
|
+
color: var(--sapTextColor, #32363a);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Component, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { Button } from '@fundamental-ngx/ui5-webcomponents/button';
|
|
3
|
+
import { Icon } from '@fundamental-ngx/ui5-webcomponents/icon';
|
|
4
|
+
import { Title } from '@fundamental-ngx/ui5-webcomponents/title';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'mfp-favorites',
|
|
8
|
+
templateUrl: './favorites.component.html',
|
|
9
|
+
styleUrls: ['./favorites.component.scss'],
|
|
10
|
+
encapsulation: ViewEncapsulation.ShadowDom,
|
|
11
|
+
imports: [Button, Icon, Title],
|
|
12
|
+
})
|
|
13
|
+
export class Favorites {
|
|
14
|
+
readonly items = [
|
|
15
|
+
{ label: 'Create Account', icon: 'add', action: 'create-account' },
|
|
16
|
+
{ label: 'Start Approval', icon: 'workflow-tasks', action: 'start-approval' },
|
|
17
|
+
{ label: 'Add User to Account', icon: 'person-placeholder', action: 'add-user' },
|
|
18
|
+
];
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<div class="service-status">
|
|
2
|
+
<ui5-title level="H5" class="service-status__title">Service Availability</ui5-title>
|
|
3
|
+
<div class="service-status__list">
|
|
4
|
+
@for (service of services; track service.name) {
|
|
5
|
+
<div class="service-status__item">
|
|
6
|
+
<ui5-icon name="{{ service.icon }}" class="service-status__icon"></ui5-icon>
|
|
7
|
+
<span class="service-status__name">{{ service.name }}</span>
|
|
8
|
+
<div class="service-status__status {{ statusConfig[service.status].colorClass }}">
|
|
9
|
+
<ui5-icon name="{{ statusConfig[service.status].icon }}" class="service-status__status-icon"></ui5-icon>
|
|
10
|
+
<span class="service-status__status-label">{{ statusConfig[service.status].label }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
}
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
height: 100%;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.service-status {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
height: 100%;
|
|
10
|
+
background: var(--sapTile_Background, #fff);
|
|
11
|
+
border-radius: var(--_ui5_card_border-radius, 0.5rem);
|
|
12
|
+
border: var(--_ui5_card_border, 1px solid var(--sapTile_BorderColor, #e5e5e5));
|
|
13
|
+
box-shadow: var(--_ui5_card_box_shadow, var(--sapContent_Shadow0));
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
|
|
16
|
+
&__title {
|
|
17
|
+
padding: 1rem 1rem 0.5rem;
|
|
18
|
+
flex-shrink: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&__list {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
flex: 1;
|
|
25
|
+
overflow-y: auto;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&__item {
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 0.75rem;
|
|
32
|
+
padding: 0.5rem 1rem;
|
|
33
|
+
border-bottom: 1px solid var(--sapList_BorderColor, #e5e5e5);
|
|
34
|
+
|
|
35
|
+
&:last-child {
|
|
36
|
+
border-bottom: none;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&__icon {
|
|
41
|
+
flex-shrink: 0;
|
|
42
|
+
color: var(--sapHighlightColor, #0070f2);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&__name {
|
|
46
|
+
flex: 1;
|
|
47
|
+
font-size: var(--sapFontSize, 0.875rem);
|
|
48
|
+
color: var(--sapTextColor, #32363a);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&__status {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
gap: 0.25rem;
|
|
55
|
+
flex-shrink: 0;
|
|
56
|
+
font-size: 0.75rem;
|
|
57
|
+
font-weight: 500;
|
|
58
|
+
border-radius: 0.75rem;
|
|
59
|
+
padding: 0.125rem 0.5rem;
|
|
60
|
+
|
|
61
|
+
&--operational {
|
|
62
|
+
color: var(--sapPositiveColor, #256f3a);
|
|
63
|
+
background: var(--sapPositiveBackground, #f1fdf6);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
&--degraded {
|
|
67
|
+
color: var(--sapCriticalColor, #e76500);
|
|
68
|
+
background: var(--sapCriticalBackground, #fef7f1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&--outage {
|
|
72
|
+
color: var(--sapNegativeColor, #aa0808);
|
|
73
|
+
background: var(--sapNegativeBackground, #fff1f1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&--maintenance {
|
|
77
|
+
color: var(--sapNeutralColor, #5b738b);
|
|
78
|
+
background: var(--sapNeutralBackground, #f5f6f7);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&__status-icon {
|
|
83
|
+
font-size: 0.75rem;
|
|
84
|
+
width: 0.875rem;
|
|
85
|
+
height: 0.875rem;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Component, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { Icon } from '@fundamental-ngx/ui5-webcomponents/icon';
|
|
3
|
+
import { Title } from '@fundamental-ngx/ui5-webcomponents/title';
|
|
4
|
+
|
|
5
|
+
export type ServiceStatusValue = 'operational' | 'degraded' | 'outage' | 'maintenance';
|
|
6
|
+
|
|
7
|
+
export interface ServiceStatusItem {
|
|
8
|
+
name: string;
|
|
9
|
+
icon: string;
|
|
10
|
+
status: ServiceStatusValue;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'mfp-service-status-card',
|
|
15
|
+
templateUrl: './service-status-card.component.html',
|
|
16
|
+
styleUrls: ['./service-status-card.component.scss'],
|
|
17
|
+
encapsulation: ViewEncapsulation.ShadowDom,
|
|
18
|
+
imports: [Icon, Title],
|
|
19
|
+
})
|
|
20
|
+
export class ServiceStatusCard {
|
|
21
|
+
readonly services: ServiceStatusItem[] = [
|
|
22
|
+
{ name: 'API Gateway', icon: 'connected', status: 'operational' },
|
|
23
|
+
{ name: 'Identity Service', icon: 'customer', status: 'operational' },
|
|
24
|
+
{ name: 'Database Cluster', icon: 'database', status: 'degraded' },
|
|
25
|
+
{ name: 'Message Queue', icon: 'email', status: 'operational' },
|
|
26
|
+
{ name: 'Object Storage', icon: 'storage', status: 'maintenance' },
|
|
27
|
+
{ name: 'Audit Log', icon: 'log', status: 'outage' },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
readonly statusConfig: Record<ServiceStatusValue, { label: string; icon: string; colorClass: string }> = {
|
|
31
|
+
operational: { label: 'Operational', icon: 'status-positive', colorClass: 'status--operational' },
|
|
32
|
+
degraded: { label: 'Degraded', icon: 'status-critical', colorClass: 'status--degraded' },
|
|
33
|
+
outage: { label: 'Outage', icon: 'status-negative', colorClass: 'status--outage' },
|
|
34
|
+
maintenance: { label: 'Maintenance', icon: 'status-inactive', colorClass: 'status--maintenance' },
|
|
35
|
+
};
|
|
36
|
+
}
|