@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.152 → 2.0.0-next.153

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 (90) hide show
  1. package/.storybook/vitest.setup.ts +1 -1
  2. package/bundle/openbridge-webcomponents.bundle.js +4717 -4485
  3. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  4. package/custom-elements.json +374 -52
  5. package/dist/alert-severity.d.ts +19 -1
  6. package/dist/alert-severity.d.ts.map +1 -1
  7. package/dist/alert-severity.js +48 -2
  8. package/dist/alert-severity.js.map +1 -1
  9. package/dist/components/alert-button/alert-button.css.js +7 -1513
  10. package/dist/components/alert-button/alert-button.css.js.map +1 -1
  11. package/dist/components/alert-button/alert-button.d.ts +40 -96
  12. package/dist/components/alert-button/alert-button.d.ts.map +1 -1
  13. package/dist/components/alert-button/alert-button.js +40 -107
  14. package/dist/components/alert-button/alert-button.js.map +1 -1
  15. package/dist/components/alert-button-item/alert-button-item.css.js +1570 -0
  16. package/dist/components/alert-button-item/alert-button-item.css.js.map +1 -0
  17. package/dist/components/alert-button-item/alert-button-item.d.ts +134 -0
  18. package/dist/components/alert-button-item/alert-button-item.d.ts.map +1 -0
  19. package/dist/components/alert-button-item/alert-button-item.js +210 -0
  20. package/dist/components/alert-button-item/alert-button-item.js.map +1 -0
  21. package/dist/components/alert-counter-item/alert-counter-item.css.js +25 -0
  22. package/dist/components/alert-counter-item/alert-counter-item.css.js.map +1 -0
  23. package/dist/components/alert-counter-item/alert-counter-item.d.ts +77 -0
  24. package/dist/components/alert-counter-item/alert-counter-item.d.ts.map +1 -0
  25. package/dist/components/alert-counter-item/alert-counter-item.js +87 -0
  26. package/dist/components/alert-counter-item/alert-counter-item.js.map +1 -0
  27. package/dist/components/alert-frame/alert-frame.css.js +23 -27
  28. package/dist/components/alert-frame/alert-frame.css.js.map +1 -1
  29. package/dist/components/alert-frame/alert-frame.d.ts +7 -6
  30. package/dist/components/alert-frame/alert-frame.d.ts.map +1 -1
  31. package/dist/components/alert-frame/alert-frame.js +6 -9
  32. package/dist/components/alert-frame/alert-frame.js.map +1 -1
  33. package/dist/components/tree-navigation-item/tree-navigation-item.d.ts +7 -34
  34. package/dist/components/tree-navigation-item/tree-navigation-item.d.ts.map +1 -1
  35. package/dist/components/tree-navigation-item/tree-navigation-item.js +3 -32
  36. package/dist/components/tree-navigation-item/tree-navigation-item.js.map +1 -1
  37. package/dist/generated/locales/es-419.d.ts +11 -3
  38. package/dist/generated/locales/es-419.d.ts.map +1 -1
  39. package/dist/generated/locales/es-419.js +11 -3
  40. package/dist/generated/locales/es-419.js.map +1 -1
  41. package/dist/generated/locales/fi-FI.d.ts +8 -0
  42. package/dist/generated/locales/fi-FI.d.ts.map +1 -1
  43. package/dist/generated/locales/fi-FI.js +8 -0
  44. package/dist/generated/locales/fi-FI.js.map +1 -1
  45. package/dist/navigation-instruments/readout/readout.css.js +4 -29
  46. package/dist/navigation-instruments/readout/readout.css.js.map +1 -1
  47. package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
  48. package/dist/navigation-instruments/readout/readout.js +1 -9
  49. package/dist/navigation-instruments/readout/readout.js.map +1 -1
  50. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +5 -34
  51. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
  52. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
  53. package/dist/navigation-instruments/readout-list-item/readout-list-item.js +1 -9
  54. package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
  55. package/dist/palettes/blinking.d.ts +6 -2
  56. package/dist/palettes/blinking.d.ts.map +1 -1
  57. package/dist/palettes/blinking.js +6 -2
  58. package/dist/palettes/blinking.js.map +1 -1
  59. package/dist/types.d.ts +15 -0
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/types.js.map +1 -1
  62. package/package.json +1 -1
  63. package/src/alert-severity.spec.ts +81 -2
  64. package/src/alert-severity.ts +74 -0
  65. package/src/components/alert-button/alert-button.css +7 -196
  66. package/src/components/alert-button/alert-button.spec.ts +119 -68
  67. package/src/components/alert-button/alert-button.stories.ts +24 -0
  68. package/src/components/alert-button/alert-button.ts +49 -211
  69. package/src/components/alert-button-item/alert-button-item.css +242 -0
  70. package/src/components/alert-button-item/alert-button-item.spec.ts +299 -0
  71. package/src/components/alert-button-item/alert-button-item.stories.ts +178 -0
  72. package/src/components/alert-button-item/alert-button-item.ts +289 -0
  73. package/src/components/alert-counter-item/alert-counter-item.css +15 -0
  74. package/src/components/alert-counter-item/alert-counter-item.spec.ts +91 -0
  75. package/src/components/alert-counter-item/alert-counter-item.stories.ts +66 -0
  76. package/src/components/alert-counter-item/alert-counter-item.ts +128 -0
  77. package/src/components/alert-frame/alert-frame.css +23 -27
  78. package/src/components/alert-frame/alert-frame.spec.ts +19 -1
  79. package/src/components/alert-frame/alert-frame.stories.ts +47 -0
  80. package/src/components/alert-frame/alert-frame.ts +16 -17
  81. package/src/components/tree-navigation-item/tree-navigation-item.ts +10 -60
  82. package/src/generated/locales/es-419.ts +11 -3
  83. package/src/generated/locales/fi-FI.ts +8 -0
  84. package/src/navigation-instruments/readout/readout.css +4 -29
  85. package/src/navigation-instruments/readout/readout.ts +1 -9
  86. package/src/navigation-instruments/readout-list-item/readout-list-item.css +5 -33
  87. package/src/navigation-instruments/readout-list-item/readout-list-item.ts +1 -9
  88. package/src/palettes/blinking.spec.ts +38 -23
  89. package/src/palettes/blinking.ts +12 -4
  90. package/src/types.ts +16 -0
@@ -0,0 +1,289 @@
1
+ import {LitElement, html, nothing, unsafeCSS} from 'lit';
2
+ import {property} from 'lit/decorators.js';
3
+ import {classMap} from 'lit/directives/class-map.js';
4
+ import {localized, msg} from '@lit/localize';
5
+ import componentStyle from './alert-button-item.css?inline';
6
+ import {customElement} from '../../decorator.js';
7
+ import '../../icons/icon-alerts.js';
8
+ import '../../icons/icon-alerts-active.js';
9
+ import '../../icons/icon-alerts-alarm-twotone.js';
10
+ import '../../icons/icon-alerts-warning-twotone.js';
11
+ import '../../icons/icon-alerts-caution-twotone.js';
12
+ import '../../manual-icon/icon-alerts-critical-twotone.js';
13
+ import '../../manual-icon/icon-alerts-diagnostic-twotone.js';
14
+ import '../alert-counter-item/alert-counter-item.js';
15
+ import {ObcAlertCounterItemType} from '../alert-counter-item/alert-counter-item.js';
16
+ import {
17
+ AlertType,
18
+ FlashingSpeed,
19
+ type AlertCounts,
20
+ type ResolvedFlashingSpeed,
21
+ } from '../../types.js';
22
+ import {
23
+ AlertFlashPhase,
24
+ AlertTwotoneComponent,
25
+ alertCountsLabel,
26
+ alertTypeLabel,
27
+ getAlertTwotoneComponent,
28
+ rankAlertCounts,
29
+ resolveFlashingSpeed,
30
+ } from '../../alert-severity.js';
31
+ import {FlashingController} from '../../palettes/flashing-controller.js';
32
+
33
+ /**
34
+ * `ObcAlertButtonType` – Enum for alert button visual and behavioral variants.
35
+ *
36
+ * - `flat`: Minimal, icon-only button for compact spaces.
37
+ * - `normal`: Standard button with icon and optional counter.
38
+ * - `enhanced`: Emphasized button with additional styling for high-priority alerts.
39
+ */
40
+ export enum ObcAlertButtonType {
41
+ Flat = 'flat',
42
+ Normal = 'normal',
43
+ Enhanced = 'enhanced',
44
+ }
45
+
46
+ /**
47
+ * `<obc-alert-button-item>` – The bell button that shows the alert state and count.
48
+ *
49
+ * Shows the alert status as a bell in the style of the given alert type, with
50
+ * an optional counter, or as a global counter: one badge per severity, then
51
+ * the bell and the total. `obc-alert-button` renders the item next to its
52
+ * silence button; use the item on its own where no silence button is needed.
53
+ *
54
+ * ---
55
+ *
56
+ * ### Features
57
+ * - **Variants (`type`):** `flat` (icon only), `normal` (bordered, icon and
58
+ * counter) and `enhanced` (filled in the severity colour).
59
+ * - **Alert types:** `alarm`, `warning`, `caution` and the `level-*` family;
60
+ * `nAlerts` of 0 shows the idle bell.
61
+ * - **Counter:** `counter` shows `nAlerts` next to the bell, except in `flat`.
62
+ * - **Global counter:** `globalCounter` shows the per-severity `counts` as
63
+ * badges, then the bell and `nAlerts`, always in the normal style.
64
+ * - **Flashing:** `blinking` flashes the bell at the tempo `flashingSpeed`
65
+ * resolves (#1224).
66
+ * - **Fill height:** `fillHeight` stretches the visible button to the host
67
+ * height, as the tall `obc-alert-button` does.
68
+ *
69
+ * ---
70
+ *
71
+ * ### Usage Guidelines
72
+ * - Keep `nAlerts` and `counts` consistent; the item shows both as given.
73
+ * - The generated label reads e.g. "Alerts, 12, 2 Alarm, 4 Warning,
74
+ * 6 Caution"; an `aria-label` on the host replaces it.
75
+ * - A native button, so Enter and Space activate it (APG button pattern).
76
+ * - A parent that joins another button to the item's end sets the
77
+ * `data-group-item-not-last` attribute on the host.
78
+ * - **TODO(designer):** the Figma file draws the flat item's hover, active and
79
+ * focused variants at 20 % opacity; the item uses the standard flat states.
80
+ * - **TODO(designer):** the Figma file defines no flashing for the global
81
+ * counter, so it never flashes.
82
+ *
83
+ * ---
84
+ *
85
+ * ### Example
86
+ *
87
+ * ```html
88
+ * <obc-alert-button-item alertType="alarm" nAlerts="3" counter blinking></obc-alert-button-item>
89
+ * <obc-alert-button-item
90
+ * globalCounter
91
+ * nAlerts="12"
92
+ * .counts=${{countAlarm: 2, countWarning: 4, countCaution: 6}}
93
+ * ></obc-alert-button-item>
94
+ * ```
95
+ *
96
+ * @property type - Visual variant: `flat` (icon only), `normal` (default, bordered with the counter) or `enhanced` (filled in the severity colour).
97
+ * @availableWhen type globalCounter==false
98
+ * @property alertType - Alert type that selects the icon and colours: `alarm`, `warning`, `caution` or a `level-*` severity.
99
+ * @availableWhen alertType globalCounter==false
100
+ * @property nAlerts - Number of active alerts; 0 shows the idle bell and hides the counter.
101
+ * @property counter - Shows `nAlerts` next to the bell (not in `flat`).
102
+ * @availableWhen counter globalCounter==false
103
+ * @property globalCounter - Shows the per-severity counts, the bell and the total instead of a single alert status.
104
+ * @property counts - Alert count per severity for the global counter badges.
105
+ * @availableWhen counts globalCounter==true
106
+ * @property shelvedCount - Number of shelved alerts, shown after the global counter badges.
107
+ * @availableWhen shelvedCount globalCounter==true
108
+ * @property blinking - Flashes the bell while there are active alerts of a flashing alert type.
109
+ * @availableWhen blinking globalCounter==false
110
+ * @property flashingSpeed - Flash tempo while `blinking` is on: `default` resolves from `alertType`, `fast`, `slow`, `very-slow` force a tempo, `fixed` never flashes.
111
+ * @availableWhen flashingSpeed blinking==true
112
+ * @property fillHeight - Stretches the visible button to the host height instead of the 32 px visual target.
113
+ * @property ariaLabel - Accessible name forwarded to the inner `<button>`, mapped to the `aria-label` attribute; replaces the generated label. `aria-labelledby` is not supported: ID references cannot cross the shadow boundary.
114
+ * @fires click - Fired when the button is clicked.
115
+ * @experimental
116
+ */
117
+ @customElement('obc-alert-button-item')
118
+ @localized()
119
+ export class ObcAlertButtonItem extends LitElement {
120
+ @property({type: String}) type: ObcAlertButtonType =
121
+ ObcAlertButtonType.Normal;
122
+ @property({type: String}) alertType?: AlertType;
123
+ @property({type: Number}) nAlerts = 0;
124
+ @property({type: Boolean}) counter = false;
125
+ @property({type: Boolean}) globalCounter = false;
126
+ @property({type: Object, attribute: false}) counts: AlertCounts = {};
127
+ @property({type: Number}) shelvedCount = 0;
128
+ @property({type: Boolean}) blinking = false;
129
+ @property({type: String}) flashingSpeed: FlashingSpeed =
130
+ FlashingSpeed.Default;
131
+ @property({type: Boolean}) fillHeight = false;
132
+
133
+ // Reactive so a consumer changing the name re-renders the shadow button.
134
+ @property({type: String, attribute: 'aria-label'})
135
+ override ariaLabel: string | null = null;
136
+
137
+ protected readonly flashing = new FlashingController(
138
+ this,
139
+ () => this.resolvedFlashingSpeed
140
+ );
141
+
142
+ get resolvedFlashingSpeed(): ResolvedFlashingSpeed {
143
+ if (
144
+ this.globalCounter ||
145
+ !this.blinking ||
146
+ this.nAlerts <= 0 ||
147
+ this.alertType === undefined
148
+ ) {
149
+ return FlashingSpeed.Fixed;
150
+ }
151
+ return resolveFlashingSpeed(
152
+ this.flashingSpeed,
153
+ this.alertType,
154
+ AlertFlashPhase.Active
155
+ );
156
+ }
157
+
158
+ private get accessibleName(): string {
159
+ if (this.ariaLabel) {
160
+ return this.ariaLabel;
161
+ }
162
+ const parts = [msg('Alerts')];
163
+ if (this.nAlerts > 0) {
164
+ parts.push(String(this.nAlerts));
165
+ }
166
+ if (this.globalCounter) {
167
+ const breakdown = alertCountsLabel(this.counts, this.shelvedCount);
168
+ if (breakdown) {
169
+ parts.push(breakdown);
170
+ }
171
+ } else if (this.nAlerts > 0 && this.alertType) {
172
+ parts.push(alertTypeLabel(this.alertType));
173
+ }
174
+ return parts.join(', ');
175
+ }
176
+
177
+ private renderTwotoneIcon() {
178
+ const twotone = this.alertType
179
+ ? getAlertTwotoneComponent(this.alertType)
180
+ : AlertTwotoneComponent.Caution;
181
+ switch (twotone) {
182
+ case AlertTwotoneComponent.Critical:
183
+ return html`<obi-alerts-critical-twotone
184
+ useCssColor
185
+ class="icon"
186
+ ></obi-alerts-critical-twotone>`;
187
+ case AlertTwotoneComponent.Diagnostic:
188
+ return html`<obi-alerts-diagnostic-twotone
189
+ useCssColor
190
+ class="icon"
191
+ ></obi-alerts-diagnostic-twotone>`;
192
+ case AlertTwotoneComponent.Warning:
193
+ return html`<obi-alerts-warning-twotone
194
+ useCssColor
195
+ class="icon"
196
+ ></obi-alerts-warning-twotone>`;
197
+ case AlertTwotoneComponent.Caution:
198
+ return html`<obi-alerts-caution-twotone
199
+ useCssColor
200
+ class="icon"
201
+ ></obi-alerts-caution-twotone>`;
202
+ default:
203
+ return html`<obi-alerts-alarm-twotone
204
+ useCssColor
205
+ class="icon"
206
+ ></obi-alerts-alarm-twotone>`;
207
+ }
208
+ }
209
+
210
+ /** The blink layer shows the opposite of the steady layer in each flash. */
211
+ private renderIcon(styleType: ObcAlertButtonType, blinkLayer: boolean) {
212
+ if (this.nAlerts === 0) {
213
+ return html`<obi-alerts class="icon"></obi-alerts>`;
214
+ }
215
+ const enhanced = styleType === ObcAlertButtonType.Enhanced;
216
+ if (blinkLayer) {
217
+ return enhanced
218
+ ? this.renderTwotoneIcon()
219
+ : html`<obi-alerts class="icon"></obi-alerts>`;
220
+ }
221
+ return enhanced || this.globalCounter
222
+ ? html`<obi-alerts-active class="icon"></obi-alerts-active>`
223
+ : this.renderTwotoneIcon();
224
+ }
225
+
226
+ private renderCount() {
227
+ return html`<span class="count">${this.nAlerts}</span>`;
228
+ }
229
+
230
+ private renderCounts() {
231
+ if (rankAlertCounts(this.counts).length === 0 && this.shelvedCount <= 0) {
232
+ return nothing;
233
+ }
234
+ return html`<obc-alert-counter-item
235
+ .type=${ObcAlertCounterItemType.Badges}
236
+ .counts=${this.counts}
237
+ .shelvedCount=${this.shelvedCount}
238
+ ></obc-alert-counter-item>`;
239
+ }
240
+
241
+ override render() {
242
+ const styleType = this.globalCounter
243
+ ? ObcAlertButtonType.Normal
244
+ : this.type;
245
+ const showCount =
246
+ this.nAlerts > 0 &&
247
+ (this.globalCounter ||
248
+ (this.counter && styleType !== ObcAlertButtonType.Flat));
249
+ const tempo = this.resolvedFlashingSpeed;
250
+ return html`
251
+ <button
252
+ class=${classMap({
253
+ 'alert-button': true,
254
+ [`type-${styleType}`]: true,
255
+ [`alert-type-${this.alertType ?? 'none'}`]: true,
256
+ counter: showCount && !this.globalCounter,
257
+ 'global-counter': this.globalCounter,
258
+ 'fill-height': this.fillHeight,
259
+ [`flash-${tempo}`]: true,
260
+ })}
261
+ aria-label=${this.accessibleName}
262
+ >
263
+ ${tempo === FlashingSpeed.Fixed
264
+ ? nothing
265
+ : html`<div class="blink" aria-hidden="true">
266
+ <div class="content">
267
+ ${this.renderIcon(styleType, true)}
268
+ ${showCount ? this.renderCount() : nothing}
269
+ </div>
270
+ </div>`}
271
+ <div class="visible-wrapper">
272
+ ${this.globalCounter ? this.renderCounts() : nothing}
273
+ <div class="content">
274
+ ${this.renderIcon(styleType, false)}
275
+ ${showCount ? this.renderCount() : nothing}
276
+ </div>
277
+ </div>
278
+ </button>
279
+ `;
280
+ }
281
+
282
+ static override styles = unsafeCSS(componentStyle);
283
+ }
284
+
285
+ declare global {
286
+ interface HTMLElementTagNameMap {
287
+ 'obc-alert-button-item': ObcAlertButtonItem;
288
+ }
289
+ }
@@ -0,0 +1,15 @@
1
+ :host {
2
+ display: inline-block;
3
+ }
4
+
5
+ .row {
6
+ display: flex;
7
+ align-items: center;
8
+ gap: var(--app-components-alert-counter-item-badge-spacing);
9
+ }
10
+
11
+ obi-alerts-shelf {
12
+ display: block; /* fills the badge icon box that sizes the badge-icon slot */
13
+ width: 100%;
14
+ height: 100%;
15
+ }
@@ -0,0 +1,91 @@
1
+ import {describe, expect, it} from 'vitest';
2
+ import {render} from 'vitest-browser-lit';
3
+ import {html} from 'lit';
4
+ import './alert-counter-item.js';
5
+ import {
6
+ ObcAlertCounterItem,
7
+ ObcAlertCounterItemType,
8
+ } from './alert-counter-item.js';
9
+ import type {ObcBadge} from '../badge/badge.js';
10
+ import type {AlertCounts} from '../../types.js';
11
+
12
+ async function setup(
13
+ counts: AlertCounts,
14
+ options: {type?: ObcAlertCounterItemType; shelvedCount?: number} = {}
15
+ ) {
16
+ const screen = render(
17
+ html`<obc-alert-counter-item
18
+ .counts=${counts}
19
+ .type=${options.type ?? ObcAlertCounterItemType.Badges}
20
+ .shelvedCount=${options.shelvedCount ?? 0}
21
+ ></obc-alert-counter-item>`
22
+ );
23
+ const el = screen.container.querySelector(
24
+ 'obc-alert-counter-item'
25
+ ) as ObcAlertCounterItem;
26
+ await el.updateComplete;
27
+ return el;
28
+ }
29
+
30
+ function badges(el: ObcAlertCounterItem): ObcBadge[] {
31
+ return [...el.shadowRoot!.querySelectorAll<ObcBadge>('obc-badge')];
32
+ }
33
+
34
+ function lastBadge(el: ObcAlertCounterItem): ObcBadge {
35
+ const all = badges(el);
36
+ return all[all.length - 1];
37
+ }
38
+
39
+ describe('obc-alert-counter-item', () => {
40
+ it('renders one filled badge per positive count, most severe first', async () => {
41
+ const el = await setup({countCaution: 6, countAlarm: 2, countWarning: 0});
42
+
43
+ expect(
44
+ badges(el).map((b) => [b.type, b.number, b.variant, b.showIcon])
45
+ ).toEqual([
46
+ ['alarm', 2, 'default', false],
47
+ ['caution', 6, 'default', false],
48
+ ]);
49
+ });
50
+
51
+ it('draws flat badges with glyphs in the alert-level type', async () => {
52
+ const el = await setup(
53
+ {countWarning: 4},
54
+ {type: ObcAlertCounterItemType.AlertLevel}
55
+ );
56
+
57
+ expect(badges(el).map((b) => [b.type, b.variant, b.showIcon])).toEqual([
58
+ ['warning', 'flat', true],
59
+ ]);
60
+ });
61
+
62
+ it('adds the shelved count last', async () => {
63
+ const filled = await setup({countAlarm: 2}, {shelvedCount: 9});
64
+ expect([lastBadge(filled).type, lastBadge(filled).number]).toEqual([
65
+ 'regular',
66
+ 9,
67
+ ]);
68
+
69
+ const level = await setup(
70
+ {countAlarm: 2},
71
+ {type: ObcAlertCounterItemType.AlertLevel, shelvedCount: 9}
72
+ );
73
+ expect(lastBadge(level).querySelector('obi-alerts-shelf')).not.toBeNull();
74
+ });
75
+
76
+ it('renders nothing when nothing is counted', async () => {
77
+ const el = await setup({countAlarm: 0});
78
+
79
+ expect(el.shadowRoot!.querySelector('.row')).toBeNull();
80
+ });
81
+
82
+ it('labels the row with the counts', async () => {
83
+ const el = await setup({countWarning: 4, countAlarm: 2}, {shelvedCount: 1});
84
+ const row = el.shadowRoot!.querySelector('.row')!;
85
+
86
+ expect(row.getAttribute('role')).toBe('img');
87
+ expect(row.getAttribute('aria-label')).toBe(
88
+ '2 Alarm, 4 Warning, 1 Shelved'
89
+ );
90
+ });
91
+ });
@@ -0,0 +1,66 @@
1
+ import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
+ import {html} from 'lit';
3
+ import {
4
+ ObcAlertCounterItem,
5
+ ObcAlertCounterItemType,
6
+ } from './alert-counter-item.js';
7
+ import './alert-counter-item.js';
8
+ import type {AlertCounts} from '../../types.js';
9
+
10
+ const LEVEL_COUNTS: AlertCounts = {
11
+ countLevelCritical: 1,
12
+ countLevelHigh: 9,
13
+ countLevelMedium: 4,
14
+ countLevelLow: 2,
15
+ countLevelDiagnostic: 7,
16
+ };
17
+
18
+ const meta: Meta<typeof ObcAlertCounterItem> = {
19
+ title: 'Application Components/Alerts/Alert Counter Item',
20
+ tags: ['autodocs', '6.1', 'experimental'],
21
+ component: 'obc-alert-counter-item',
22
+ args: {
23
+ type: ObcAlertCounterItemType.Badges,
24
+ counts: {countAlarm: 2, countWarning: 4, countCaution: 6},
25
+ shelvedCount: 0,
26
+ },
27
+ argTypes: {
28
+ type: {
29
+ control: {type: 'inline-radio'},
30
+ options: Object.values(ObcAlertCounterItemType),
31
+ },
32
+ counts: {control: {type: 'object'}},
33
+ shelvedCount: {control: {type: 'number', min: 0}},
34
+ },
35
+ render: (args) =>
36
+ html`<obc-alert-counter-item
37
+ .type=${args.type}
38
+ .counts=${args.counts}
39
+ .shelvedCount=${args.shelvedCount}
40
+ ></obc-alert-counter-item>`,
41
+ } satisfies Meta<ObcAlertCounterItem>;
42
+
43
+ export default meta;
44
+ type Story = StoryObj<ObcAlertCounterItem>;
45
+
46
+ export const Badges: Story = {};
47
+
48
+ export const BadgesWithShelved: Story = {
49
+ args: {shelvedCount: 9},
50
+ };
51
+
52
+ export const AlertLevel: Story = {
53
+ args: {type: ObcAlertCounterItemType.AlertLevel},
54
+ };
55
+
56
+ export const AlertLevelWithShelved: Story = {
57
+ args: {type: ObcAlertCounterItemType.AlertLevel, shelvedCount: 9},
58
+ };
59
+
60
+ export const LevelSeverities: Story = {
61
+ args: {counts: LEVEL_COUNTS},
62
+ };
63
+
64
+ export const LevelSeveritiesAlertLevel: Story = {
65
+ args: {type: ObcAlertCounterItemType.AlertLevel, counts: LEVEL_COUNTS},
66
+ };
@@ -0,0 +1,128 @@
1
+ import {LitElement, html, nothing, unsafeCSS} from 'lit';
2
+ import {property} from 'lit/decorators.js';
3
+ import {repeat} from 'lit/directives/repeat.js';
4
+ import {localized} from '@lit/localize';
5
+ import componentStyle from './alert-counter-item.css?inline';
6
+ import {customElement} from '../../decorator.js';
7
+ import '../badge/badge.js';
8
+ import {BadgeSize, BadgeType, BadgeVariant} from '../badge/badge.js';
9
+ import '../../icons/icon-alerts-shelf.js';
10
+ import type {AlertCounts} from '../../types.js';
11
+ import {alertCountsLabel, rankAlertCounts} from '../../alert-severity.js';
12
+
13
+ /**
14
+ * `ObcAlertCounterItemType` – How each count is drawn (Figma `Type`).
15
+ *
16
+ * - `badges`: filled badges in the severity colour (Figma Badges counter).
17
+ * - `alert-level`: flat badges with the severity glyph (Figma Alert level counter).
18
+ */
19
+ export enum ObcAlertCounterItemType {
20
+ Badges = 'badges',
21
+ AlertLevel = 'alert-level',
22
+ }
23
+
24
+ /**
25
+ * `<obc-alert-counter-item>` – A row of alert counts, one badge per severity.
26
+ *
27
+ * Renders a large `obc-badge` for every severity with a count above zero, most
28
+ * severe first, followed by an optional shelved count. Nothing renders when
29
+ * nothing is counted.
30
+ *
31
+ * ---
32
+ *
33
+ * ### Features
34
+ * - **Two types:** `badges` draws filled badges in the severity colour;
35
+ * `alert-level` draws flat badges with the severity glyph.
36
+ * - **Every severity:** `counts` takes the whole `AlertType` vocabulary,
37
+ * including the `level-*` family, ordered by `ALERT_SEVERITY_PRIORITY`.
38
+ * - **Shelved count:** `shelvedCount` adds a neutral badge at the end, with
39
+ * the shelf icon in `alert-level`.
40
+ * - **Accessible summary:** the row is announced as one image labelled with
41
+ * the counts, e.g. "2 Alarm, 4 Warning, 6 Caution".
42
+ *
43
+ * ---
44
+ *
45
+ * ### Usage Guidelines
46
+ * - Show how many alerts are outstanding per severity in a compact space;
47
+ * `obc-alert-button-item` nests the counter for its global counter.
48
+ * - Pass the counts as one object: `.counts=${{countAlarm: 2, countWarning: 4}}`.
49
+ * - The counter is display-only: it renders no button and fires no events.
50
+ * - For a framed per-subsystem summary with a label, see
51
+ * `obc-alert-subsystem-counter`.
52
+ * - **TODO(designer):** the Figma component also has an Unacked counter type;
53
+ * how its icons map to counts is not defined yet.
54
+ *
55
+ * ---
56
+ *
57
+ * ### Example
58
+ *
59
+ * ```html
60
+ * <obc-alert-counter-item
61
+ * .counts=${{countAlarm: 2, countWarning: 4, countCaution: 6}}
62
+ * shelvedCount="9"
63
+ * ></obc-alert-counter-item>
64
+ * ```
65
+ *
66
+ * @property type - How each count is drawn: `badges` (filled severity badges, default) or `alert-level` (flat badges with the severity glyph).
67
+ * @property counts - Alert count per severity (`countAlarm`, `countWarning`, `countCaution` and the `level-*` counts); counts of zero or less are not shown.
68
+ * @property shelvedCount - Number of shelved alerts, shown as a neutral badge after the severities; hidden at zero.
69
+ * @experimental
70
+ */
71
+ @customElement('obc-alert-counter-item')
72
+ @localized()
73
+ export class ObcAlertCounterItem extends LitElement {
74
+ @property({type: String}) type: ObcAlertCounterItemType =
75
+ ObcAlertCounterItemType.Badges;
76
+ @property({type: Object, attribute: false}) counts: AlertCounts = {};
77
+ @property({type: Number}) shelvedCount = 0;
78
+
79
+ override render() {
80
+ const ranked = rankAlertCounts(this.counts);
81
+ if (ranked.length === 0 && this.shelvedCount <= 0) {
82
+ return nothing;
83
+ }
84
+ const alertLevel = this.type === ObcAlertCounterItemType.AlertLevel;
85
+ const variant = alertLevel ? BadgeVariant.flat : BadgeVariant.default;
86
+ return html`
87
+ <div
88
+ class="row"
89
+ role="img"
90
+ aria-label=${alertCountsLabel(this.counts, this.shelvedCount)}
91
+ >
92
+ ${repeat(
93
+ ranked,
94
+ (entry) => entry.type,
95
+ (entry) =>
96
+ html`<obc-badge
97
+ size=${BadgeSize.large}
98
+ type=${entry.type}
99
+ .variant=${variant}
100
+ .showIcon=${alertLevel}
101
+ .number=${entry.count}
102
+ ></obc-badge>`
103
+ )}
104
+ ${this.shelvedCount > 0
105
+ ? html`<obc-badge
106
+ size=${BadgeSize.large}
107
+ type=${BadgeType.regular}
108
+ .variant=${variant}
109
+ .showIcon=${alertLevel}
110
+ .number=${this.shelvedCount}
111
+ >
112
+ ${alertLevel
113
+ ? html`<obi-alerts-shelf slot="badge-icon"></obi-alerts-shelf>`
114
+ : nothing}
115
+ </obc-badge>`
116
+ : nothing}
117
+ </div>
118
+ `;
119
+ }
120
+
121
+ static override styles = unsafeCSS(componentStyle);
122
+ }
123
+
124
+ declare global {
125
+ interface HTMLElementTagNameMap {
126
+ 'obc-alert-counter-item': ObcAlertCounterItem;
127
+ }
128
+ }