@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.137 → 2.0.0-next.138

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.
@@ -0,0 +1,308 @@
1
+ import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
+ import {html, nothing, type TemplateResult} from 'lit';
3
+ import {ifDefined} from 'lit/directives/if-defined.js';
4
+ import {
5
+ ObcAlertSubsystemCounter,
6
+ ObcAlertSubsystemCounterOrientation,
7
+ } from './alert-subsystem-counter.js';
8
+ import './alert-subsystem-counter.js';
9
+ import '../badge/badge.js';
10
+ import {BadgeType, BadgeVariant} from '../badge/badge.js';
11
+ import '../../icons/icon-placeholder.js';
12
+
13
+ const badges = html`
14
+ <obc-badge slot="badges" size="large" type="alarm" .number=${9}></obc-badge>
15
+ <obc-badge slot="badges" size="large" type="warning" .number=${4}></obc-badge>
16
+ <obc-badge slot="badges" size="large" type="caution" .number=${2}></obc-badge>
17
+ `;
18
+
19
+ const levelBadges = html`
20
+ <obc-badge
21
+ slot="badges"
22
+ size="large"
23
+ type=${BadgeType.levelCritical}
24
+ .number=${1}
25
+ ></obc-badge>
26
+ <obc-badge
27
+ slot="badges"
28
+ size="large"
29
+ type=${BadgeType.levelHigh}
30
+ .number=${9}
31
+ ></obc-badge>
32
+ <obc-badge
33
+ slot="badges"
34
+ size="large"
35
+ type=${BadgeType.levelMedium}
36
+ .number=${4}
37
+ ></obc-badge>
38
+ <obc-badge
39
+ slot="badges"
40
+ size="large"
41
+ type=${BadgeType.levelLow}
42
+ .number=${2}
43
+ ></obc-badge>
44
+ <obc-badge
45
+ slot="badges"
46
+ size="large"
47
+ type=${BadgeType.levelDiagnostic}
48
+ .number=${7}
49
+ ></obc-badge>
50
+ `;
51
+
52
+ const iconBadges = html`
53
+ <obc-badge
54
+ slot="badges"
55
+ size="large"
56
+ type="alarm"
57
+ variant=${BadgeVariant.flat}
58
+ showIcon
59
+ .number=${9}
60
+ ></obc-badge>
61
+ <obc-badge
62
+ slot="badges"
63
+ size="large"
64
+ type="warning"
65
+ variant=${BadgeVariant.flat}
66
+ showIcon
67
+ .number=${4}
68
+ ></obc-badge>
69
+ <obc-badge
70
+ slot="badges"
71
+ size="large"
72
+ type="caution"
73
+ variant=${BadgeVariant.flat}
74
+ showIcon
75
+ .number=${2}
76
+ ></obc-badge>
77
+ `;
78
+
79
+ type CounterArgs = Pick<
80
+ ObcAlertSubsystemCounter,
81
+ 'label' | 'orientation' | 'hasAlert' | 'emptyText'
82
+ >;
83
+
84
+ // `emptyText` is gated on `hasAlert==false`, so Storybook drops it from the
85
+ // args while `hasAlert` is on; attribute bindings with `ifDefined` leave the
86
+ // element's own default in place instead of writing `undefined` into it.
87
+ const renderCounter = (
88
+ args: CounterArgs,
89
+ options: {width?: string; withIcon?: boolean; badges?: TemplateResult} = {}
90
+ ) =>
91
+ html`<div style="width:${options.width ?? '191px'}">
92
+ <obc-alert-subsystem-counter
93
+ label=${ifDefined(args.label)}
94
+ .orientation=${args.orientation}
95
+ .hasAlert=${args.hasAlert}
96
+ emptytext=${ifDefined(args.emptyText)}
97
+ >
98
+ ${options.withIcon === false
99
+ ? nothing
100
+ : html`<obi-placeholder slot="icon"></obi-placeholder>`}
101
+ ${options.badges ?? badges}
102
+ </obc-alert-subsystem-counter>
103
+ </div>`;
104
+
105
+ const meta: Meta<typeof ObcAlertSubsystemCounter> = {
106
+ title: 'Application Components/Alerts/Alert Subsystem Counter',
107
+ tags: ['6.0', 'beta'],
108
+ component: 'obc-alert-subsystem-counter',
109
+ args: {
110
+ label: 'Label',
111
+ orientation: ObcAlertSubsystemCounterOrientation.Horizontal,
112
+ hasAlert: true,
113
+ emptyText: 'No alerts',
114
+ },
115
+ argTypes: {
116
+ orientation: {
117
+ control: {type: 'inline-radio'},
118
+ options: Object.values(ObcAlertSubsystemCounterOrientation),
119
+ },
120
+ hasAlert: {control: {type: 'boolean'}},
121
+ label: {control: {type: 'text'}},
122
+ emptyText: {control: {type: 'text'}},
123
+ },
124
+ render: (args) => renderCounter(args),
125
+ } satisfies Meta<ObcAlertSubsystemCounter>;
126
+
127
+ export default meta;
128
+ type Story = StoryObj<ObcAlertSubsystemCounter>;
129
+
130
+ export const Default: Story = {};
131
+
132
+ export const Abbreviation: Story = {
133
+ args: {label: 'ABC'},
134
+ render: (args) => renderCounter(args, {width: '172px'}),
135
+ };
136
+
137
+ export const NoAlerts: Story = {
138
+ args: {hasAlert: false},
139
+ };
140
+
141
+ export const Vertical: Story = {
142
+ args: {orientation: ObcAlertSubsystemCounterOrientation.Vertical},
143
+ };
144
+
145
+ export const VerticalNoAlerts: Story = {
146
+ args: {
147
+ orientation: ObcAlertSubsystemCounterOrientation.Vertical,
148
+ hasAlert: false,
149
+ },
150
+ };
151
+
152
+ export const AbbreviationVertical: Story = {
153
+ args: {
154
+ label: 'ABC',
155
+ orientation: ObcAlertSubsystemCounterOrientation.Vertical,
156
+ },
157
+ render: (args) => renderCounter(args, {width: 'fit-content'}),
158
+ };
159
+
160
+ export const WithoutIcon: Story = {
161
+ render: (args) => renderCounter(args, {withIcon: false}),
162
+ };
163
+
164
+ export const LongLabel: Story = {
165
+ args: {label: 'Auxiliary cooling water system'},
166
+ parameters: {
167
+ docs: {
168
+ description: {
169
+ story:
170
+ 'The label is the only element that gives way: it truncates with an ellipsis while the icon and badges keep their size.',
171
+ },
172
+ },
173
+ },
174
+ };
175
+
176
+ export const LevelSeverities: Story = {
177
+ render: (args) =>
178
+ renderCounter(args, {width: 'fit-content', badges: levelBadges}),
179
+ parameters: {
180
+ docs: {
181
+ description: {
182
+ story:
183
+ 'The badge `type` accepts the whole `AlertType` vocabulary, including the `level-*` family. Slot them highest severity first, in `ALERT_SEVERITY_PRIORITY` order.',
184
+ },
185
+ },
186
+ },
187
+ };
188
+
189
+ export const WithBadgeIcons: Story = {
190
+ render: (args) =>
191
+ renderCounter(args, {width: 'fit-content', badges: iconBadges}),
192
+ parameters: {
193
+ docs: {
194
+ description: {
195
+ story:
196
+ 'Any `obc-badge` configuration works in the `badges` slot — here the `flat` variant with `showIcon` so each count carries its severity symbol.',
197
+ },
198
+ },
199
+ },
200
+ };
201
+
202
+ type OverviewEntry = {
203
+ label: string;
204
+ hasAlert: boolean;
205
+ badges?: TemplateResult;
206
+ withIcon?: boolean;
207
+ };
208
+
209
+ const overview: OverviewEntry[] = [
210
+ {
211
+ label: 'Propulsion',
212
+ hasAlert: true,
213
+ badges: html`<obc-badge
214
+ slot="badges"
215
+ size="large"
216
+ type="alarm"
217
+ .number=${2}
218
+ ></obc-badge>
219
+ <obc-badge
220
+ slot="badges"
221
+ size="large"
222
+ type="warning"
223
+ .number=${1}
224
+ ></obc-badge>`,
225
+ },
226
+ {label: 'Navigation', hasAlert: false},
227
+ {
228
+ label: 'Power',
229
+ hasAlert: true,
230
+ badges: html`<obc-badge
231
+ slot="badges"
232
+ size="large"
233
+ type="caution"
234
+ .number=${3}
235
+ ></obc-badge>`,
236
+ },
237
+ {
238
+ label: 'HVAC',
239
+ hasAlert: true,
240
+ badges: html`<obc-badge
241
+ slot="badges"
242
+ size="large"
243
+ type=${BadgeType.levelHigh}
244
+ .number=${9}
245
+ ></obc-badge>
246
+ <obc-badge
247
+ slot="badges"
248
+ size="large"
249
+ type=${BadgeType.levelMedium}
250
+ .number=${4}
251
+ ></obc-badge>
252
+ <obc-badge
253
+ slot="badges"
254
+ size="large"
255
+ type=${BadgeType.levelLow}
256
+ .number=${2}
257
+ ></obc-badge>`,
258
+ },
259
+ {label: 'Cargo', hasAlert: false, withIcon: false},
260
+ {
261
+ label: 'Fire detection',
262
+ hasAlert: true,
263
+ withIcon: false,
264
+ badges: html`<obc-badge
265
+ slot="badges"
266
+ size="large"
267
+ type="alarm"
268
+ .number=${12}
269
+ ></obc-badge>`,
270
+ },
271
+ ];
272
+
273
+ export const Overview: Story = {
274
+ render: (args) =>
275
+ html`<div
276
+ style="display:grid;grid-template-columns:repeat(2, 240px);gap:8px"
277
+ >
278
+ ${overview.map((entry) =>
279
+ renderCounter(
280
+ {...args, label: entry.label, hasAlert: entry.hasAlert},
281
+ {width: '240px', withIcon: entry.withIcon, badges: entry.badges}
282
+ )
283
+ )}
284
+ </div>`,
285
+ parameters: {
286
+ docs: {
287
+ description: {
288
+ story:
289
+ 'A status overview composes one counter per subsystem in a grid. Counters with and without alerts, with and without icons, and with different badge families share one row height.',
290
+ },
291
+ },
292
+ },
293
+ };
294
+
295
+ export const OverviewVertical: Story = {
296
+ args: {orientation: ObcAlertSubsystemCounterOrientation.Vertical},
297
+ render: (args) =>
298
+ html`<div
299
+ style="display:grid;grid-template-columns:repeat(3, 120px);gap:8px;align-items:stretch"
300
+ >
301
+ ${overview.map((entry) =>
302
+ renderCounter(
303
+ {...args, label: entry.label, hasAlert: entry.hasAlert},
304
+ {width: '120px', withIcon: entry.withIcon, badges: entry.badges}
305
+ )
306
+ )}
307
+ </div>`,
308
+ };
@@ -0,0 +1,137 @@
1
+ import {LitElement, html, unsafeCSS} from 'lit';
2
+ import {property, state} from 'lit/decorators.js';
3
+ import {classMap} from 'lit/directives/class-map.js';
4
+ import componentStyle from './alert-subsystem-counter.css?inline';
5
+ import {customElement} from '../../decorator.js';
6
+
7
+ /**
8
+ * `ObcAlertSubsystemCounterOrientation` – Layout direction for the counter.
9
+ *
10
+ * - `horizontal`: Icon, label, and badges sit in a single row.
11
+ * - `vertical`: Badges (or empty text) sit on their own line below the label.
12
+ */
13
+ export enum ObcAlertSubsystemCounterOrientation {
14
+ Horizontal = 'horizontal',
15
+ Vertical = 'vertical',
16
+ }
17
+
18
+ /**
19
+ * `<obc-alert-subsystem-counter>` – A framed summary of a subsystem's alert
20
+ * counts: a leading icon, a label, and a trailing set of slotted count badges.
21
+ *
22
+ * When there are no active alerts it shows a muted label and an empty-state
23
+ * message instead of the badges. The count badges themselves are supplied by
24
+ * the consumer as slotted `obc-badge` elements; this component provides only
25
+ * the frame, layout, label, and empty state.
26
+ *
27
+ * ---
28
+ *
29
+ * ### Features
30
+ * - **Orientation:** `horizontal` (single row) or `vertical` (badges on a line
31
+ * below the label).
32
+ * - **Alert state:** `hasAlert` toggles between an active (bold label, badges)
33
+ * and an inactive (muted label, empty text) presentation.
34
+ * - **Freeform label:** Pass a full title or an abbreviation — the label is
35
+ * plain text decided by the caller and truncates with an ellipsis.
36
+ * - **Configurable empty text:** Customize the message shown when there are no
37
+ * alerts via `emptyText`.
38
+ * - **Optional leading icon:** Provide an icon through the `icon` slot; omit it
39
+ * and the leading box collapses with no empty gap.
40
+ *
41
+ * ---
42
+ *
43
+ * ### Usage Guidelines
44
+ * - Use this component to give an at-a-glance count of outstanding alerts for a
45
+ * single subsystem, grouped by severity.
46
+ * - Provide one slotted badge per severity in the `badges` slot, typically
47
+ * `<obc-badge size="large">` with a severity `type` and a `number`. The
48
+ * badge `type` values are the same severity vocabulary as `AlertType`
49
+ * (`alarm`, `warning`, `caution` and the `level-*` family); order them
50
+ * highest severity first, as `ALERT_SEVERITY_PRIORITY` does.
51
+ * - Set `hasAlert` to `false` to present the resolved/clear state; the badges
52
+ * are then not rendered and `emptyText` is shown. The component does not
53
+ * derive the state from the slotted badges — the consumer owns it.
54
+ * - Choose `vertical` orientation in narrow containers where the badges do not
55
+ * fit beside the label.
56
+ * - The counter is display-only: it renders no button and fires no events. For
57
+ * a clickable subsystem entry with badges, see `obc-tree-navigation-item`
58
+ * (`alertBadges`) or `obc-integration-fleet-button` (`alerts`).
59
+ *
60
+ * ---
61
+ *
62
+ * ### Slots
63
+ *
64
+ * | Slot Name | Renders When... | Purpose |
65
+ * |-----------|------------------------|----------------------------------------------------|
66
+ * | `icon` | The slot has content | Leading 24px icon (e.g. `<obi-placeholder>`). |
67
+ * | `badges` | `hasAlert` is true | Count badges, one per severity (`<obc-badge>`). |
68
+ *
69
+ * ---
70
+ *
71
+ * ### Example
72
+ *
73
+ * ```html
74
+ * <obc-alert-subsystem-counter label="Label" hasAlert>
75
+ * <obi-placeholder slot="icon"></obi-placeholder>
76
+ * <obc-badge slot="badges" size="large" type="alarm" .number=${9}></obc-badge>
77
+ * <obc-badge slot="badges" size="large" type="warning" .number=${4}></obc-badge>
78
+ * <obc-badge slot="badges" size="large" type="caution" .number=${2}></obc-badge>
79
+ * </obc-alert-subsystem-counter>
80
+ * ```
81
+ *
82
+ * @property label - The subsystem label. Pass a full title or an abbreviation as plain text.
83
+ * @property orientation - Layout direction: `horizontal` (default) keeps icon, label and badges in one row; `vertical` moves the badges (or empty text) to a line below the label.
84
+ * @property hasAlert - Whether the subsystem has active alerts. `true` shows the label bold and renders the `badges` slot; `false` mutes the label and shows `emptyText` instead of the badges.
85
+ * @property emptyText - Text shown in the trailing area when `hasAlert` is `false`.
86
+ * @availableWhen emptyText hasAlert==false
87
+ * @slot icon - Optional leading 24px icon.
88
+ * @slot badges - Count badges shown when `hasAlert` is true.
89
+ * @beta
90
+ */
91
+ @customElement('obc-alert-subsystem-counter')
92
+ export class ObcAlertSubsystemCounter extends LitElement {
93
+ @property({type: String}) label = '';
94
+ @property({type: String}) orientation: ObcAlertSubsystemCounterOrientation =
95
+ ObcAlertSubsystemCounterOrientation.Horizontal;
96
+ @property({type: Boolean}) hasAlert = false;
97
+ @property({type: String}) emptyText = 'No alerts';
98
+
99
+ @state() private hasIcon = false;
100
+
101
+ private handleIconSlotChange(e: Event) {
102
+ const slot = e.target as HTMLSlotElement;
103
+ this.hasIcon = slot.assignedNodes({flatten: true}).length > 0;
104
+ }
105
+
106
+ override render() {
107
+ return html`
108
+ <div
109
+ class=${classMap({
110
+ frame: true,
111
+ ['orientation-' + this.orientation]: true,
112
+ 'has-alert': this.hasAlert,
113
+ })}
114
+ >
115
+ <div class="leading-container">
116
+ <div class=${classMap({icon: true, 'no-icon': !this.hasIcon})}>
117
+ <slot name="icon" @slotchange=${this.handleIconSlotChange}></slot>
118
+ </div>
119
+ <div class="label">${this.label}</div>
120
+ </div>
121
+ <div class="trailing-container">
122
+ ${this.hasAlert
123
+ ? html`<slot name="badges" class="badges"></slot>`
124
+ : html`<span class="empty">${this.emptyText}</span>`}
125
+ </div>
126
+ </div>
127
+ `;
128
+ }
129
+
130
+ static override styles = unsafeCSS(componentStyle);
131
+ }
132
+
133
+ declare global {
134
+ interface HTMLElementTagNameMap {
135
+ 'obc-alert-subsystem-counter': ObcAlertSubsystemCounter;
136
+ }
137
+ }