@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.
Files changed (140) hide show
  1. package/.github/workflows/pipeline.yaml +41 -0
  2. package/.storybook/main.ts +34 -0
  3. package/.storybook/preview.ts +29 -0
  4. package/.storybook/tsconfig.json +11 -0
  5. package/AGENTS.md +153 -0
  6. package/CODEOWNERS +6 -0
  7. package/CONTRIBUTING.md +95 -0
  8. package/LICENSE +201 -0
  9. package/LICENSES/Apache-2.0.txt +73 -0
  10. package/README.md +91 -0
  11. package/REUSE.toml +9 -0
  12. package/angular.json +157 -0
  13. package/docs/dashboard.md +358 -0
  14. package/docs/declarative-form.md +178 -0
  15. package/docs/declarative-table-card.md +235 -0
  16. package/docs/declarative-table.md +315 -0
  17. package/eslint.config.js +41 -0
  18. package/package.json +73 -0
  19. package/projects/ngx/cards/favorites/favorites.component.html +12 -0
  20. package/projects/ngx/cards/favorites/favorites.component.scss +50 -0
  21. package/projects/ngx/cards/favorites/favorites.component.ts +19 -0
  22. package/projects/ngx/cards/public-api.ts +4 -0
  23. package/projects/ngx/cards/service-status/service-status-card.component.html +15 -0
  24. package/projects/ngx/cards/service-status/service-status-card.component.scss +87 -0
  25. package/projects/ngx/cards/service-status/service-status-card.component.ts +36 -0
  26. package/projects/ngx/cards/stories/visited-service-card.stories.ts +149 -0
  27. package/projects/ngx/cards/visited-service-card/visited-service-card.component.html +17 -0
  28. package/projects/ngx/cards/visited-service-card/visited-service-card.component.scss +34 -0
  29. package/projects/ngx/cards/visited-service-card/visited-service-card.component.ts +22 -0
  30. package/projects/ngx/cards/whats-new/whats-new.component.html +10 -0
  31. package/projects/ngx/cards/whats-new/whats-new.component.scss +25 -0
  32. package/projects/ngx/cards/whats-new/whats-new.component.ts +46 -0
  33. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.html +28 -0
  34. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.scss +44 -0
  35. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.spec.ts +85 -0
  36. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.ts +58 -0
  37. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.html +29 -0
  38. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.scss +63 -0
  39. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.spec.ts +255 -0
  40. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.ts +75 -0
  41. package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.spec.ts +76 -0
  42. package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.ts +109 -0
  43. package/projects/ngx/declarative-ui/dashboard/card/utils/index.ts +4 -0
  44. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.spec.ts +141 -0
  45. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.ts +44 -0
  46. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.spec.ts +142 -0
  47. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.ts +52 -0
  48. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.spec.ts +107 -0
  49. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.ts +22 -0
  50. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.html +134 -0
  51. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.scss +88 -0
  52. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.spec.ts +354 -0
  53. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.ts +238 -0
  54. package/projects/ngx/declarative-ui/dashboard/dashboard/index.ts +1 -0
  55. package/projects/ngx/declarative-ui/dashboard/index.ts +5 -0
  56. package/projects/ngx/declarative-ui/dashboard/models/constants.ts +2 -0
  57. package/projects/ngx/declarative-ui/dashboard/models/dashboard.model.ts +50 -0
  58. package/projects/ngx/declarative-ui/dashboard/models/index.ts +1 -0
  59. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.html +28 -0
  60. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.scss +85 -0
  61. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.spec.ts +104 -0
  62. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.ts +23 -0
  63. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.html +62 -0
  64. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.scss +12 -0
  65. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.spec.ts +301 -0
  66. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.ts +166 -0
  67. package/projects/ngx/declarative-ui/form/declarative-form/index.ts +1 -0
  68. package/projects/ngx/declarative-ui/form/index.ts +2 -0
  69. package/projects/ngx/declarative-ui/form/models/form-field-definition.ts +15 -0
  70. package/projects/ngx/declarative-ui/form/models/index.ts +1 -0
  71. package/projects/ngx/declarative-ui/form/utils/set-property-by-path.ts +30 -0
  72. package/projects/ngx/declarative-ui/models/index.ts +2 -0
  73. package/projects/ngx/declarative-ui/models/resource.ts +5 -0
  74. package/projects/ngx/declarative-ui/models/ui-definition.ts +95 -0
  75. package/projects/ngx/declarative-ui/public-api.ts +4 -0
  76. package/projects/ngx/declarative-ui/stories/add-card-dialog.stories.ts +91 -0
  77. package/projects/ngx/declarative-ui/stories/background-lightblue.png +0 -0
  78. package/projects/ngx/declarative-ui/stories/dashboard.cards.ts +107 -0
  79. package/projects/ngx/declarative-ui/stories/dashboard.stories.ts +296 -0
  80. package/projects/ngx/declarative-ui/stories/declarative-form.stories.ts +149 -0
  81. package/projects/ngx/declarative-ui/stories/declarative-table-card.stories.ts +358 -0
  82. package/projects/ngx/declarative-ui/stories/declarative-table.stories.ts +363 -0
  83. package/projects/ngx/declarative-ui/stories/pods-table.config.ts +188 -0
  84. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.html +138 -0
  85. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.scss +21 -0
  86. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.spec.ts +345 -0
  87. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.ts +61 -0
  88. package/projects/ngx/declarative-ui/table/declarative-table/index.ts +1 -0
  89. package/projects/ngx/declarative-ui/table/index.ts +2 -0
  90. package/projects/ngx/declarative-ui/table/models/index.ts +14 -0
  91. package/projects/ngx/declarative-ui/table/models/table.model.ts +17 -0
  92. package/projects/ngx/declarative-ui/table/utils/cssRules.engine.spec.ts +146 -0
  93. package/projects/ngx/declarative-ui/table/utils/cssRules.engine.ts +69 -0
  94. package/projects/ngx/declarative-ui/table/utils/field-definition.utils.spec.ts +70 -0
  95. package/projects/ngx/declarative-ui/table/utils/field-definition.utils.ts +13 -0
  96. package/projects/ngx/declarative-ui/table/utils/proccess-fields.spec.ts +511 -0
  97. package/projects/ngx/declarative-ui/table/utils/proccess-fields.ts +71 -0
  98. package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.spec.ts +372 -0
  99. package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.ts +98 -0
  100. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-cell.constants.ts +5 -0
  101. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.html +1 -0
  102. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.scss +0 -0
  103. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.spec.ts +119 -0
  104. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.ts +35 -0
  105. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.html +7 -0
  106. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.scss +0 -0
  107. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.spec.ts +114 -0
  108. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.ts +19 -0
  109. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.html +7 -0
  110. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.scss +10 -0
  111. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.spec.ts +188 -0
  112. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.ts +16 -0
  113. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.html +59 -0
  114. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.scss +33 -0
  115. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.spec.ts +316 -0
  116. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.ts +115 -0
  117. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.html +156 -0
  118. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.scss +123 -0
  119. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.spec.ts +786 -0
  120. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.ts +286 -0
  121. package/projects/ngx/declarative-ui/table-card/index.ts +2 -0
  122. package/projects/ngx/declarative-ui/table-card/models/configs.ts +46 -0
  123. package/projects/ngx/declarative-ui/tsconfig.lib.json +11 -0
  124. package/projects/ngx/declarative-ui/tsconfig.lib.prod.json +9 -0
  125. package/projects/ngx/declarative-ui/tsconfig.spec.json +9 -0
  126. package/projects/ngx/ng-package.json +8 -0
  127. package/projects/ngx/package.json +22 -0
  128. package/projects/ngx/public-api.ts +2 -0
  129. package/projects/ngx/tsconfig.lib.json +11 -0
  130. package/projects/ngx/tsconfig.lib.prod.json +9 -0
  131. package/projects/webcomponents/main.ts +92 -0
  132. package/projects/webcomponents/tsconfig.app.json +9 -0
  133. package/projects/webcomponents-dashboard/main.ts +15 -0
  134. package/projects/webcomponents-dashboard/tsconfig.app.json +9 -0
  135. package/renovate.json +6 -0
  136. package/scripts/bundle-wc.mjs +79 -0
  137. package/tsconfig.json +37 -0
  138. package/tsconfig.spec.json +8 -0
  139. package/tsconfig.storybook.json +16 -0
  140. 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 |
@@ -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,4 @@
1
+ export * from './visited-service-card/visited-service-card.component';
2
+ export * from './favorites/favorites.component';
3
+ export * from './service-status/service-status-card.component';
4
+ export * from './whats-new/whats-new.component';
@@ -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
+ }