diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/abbreviation-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/abbreviation-auto.png new file mode 100644 index 000000000..a7117efe3 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/abbreviation-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/abbreviation-vertical-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/abbreviation-vertical-auto.png new file mode 100644 index 000000000..4df0d38f5 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/abbreviation-vertical-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/default-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/default-auto.png new file mode 100644 index 000000000..1df963bd2 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/default-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/no-alerts-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/no-alerts-auto.png new file mode 100644 index 000000000..d82f66d21 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/no-alerts-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/vertical-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/vertical-auto.png new file mode 100644 index 000000000..cb74de18e Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/vertical-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/vertical-no-alerts-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/vertical-no-alerts-auto.png new file mode 100644 index 000000000..1cf0cca2b Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/vertical-no-alerts-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/without-icon-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/without-icon-auto.png new file mode 100644 index 000000000..fa406abe1 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts/without-icon-auto.png differ diff --git a/packages/openbridge-webcomponents/src/components/alert-subsystem-counter/alert-subsystem-counter.css b/packages/openbridge-webcomponents/src/components/alert-subsystem-counter/alert-subsystem-counter.css new file mode 100644 index 000000000..60f8ac0e6 --- /dev/null +++ b/packages/openbridge-webcomponents/src/components/alert-subsystem-counter/alert-subsystem-counter.css @@ -0,0 +1,73 @@ +:host { + display: block; +} + +.frame { + box-sizing: border-box; + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + border-radius: 6px; + background: var(--container-section-color); + border: 1px solid var(--border-outline-color); +} + +.frame.orientation-vertical { + flex-direction: column; + align-items: stretch; +} + +.leading-container { + display: flex; + flex: 1 0 0; + align-items: center; + gap: 4px; + min-width: 0; +} + +.icon { + flex-shrink: 0; + width: 24px; + height: 24px; + color: var(--on-indent-neutral-color); +} + +.icon.no-icon { + display: none; +} + +.label { + @mixin font-body; + flex: 1 0 0; + min-width: 0; + padding-left: 4px; + color: var(--element-inactive-color); + font-feature-settings: "ss04" 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.frame.has-alert .label { + @mixin font-body-active; + color: var(--on-indent-active-color); +} + +.trailing-container { + display: flex; + flex-shrink: 0; + align-items: center; + gap: 4px; +} + +.frame.orientation-horizontal .trailing-container { + justify-content: flex-end; +} + +.empty { + @mixin font-label; + color: var(--element-inactive-color); + white-space: nowrap; + font-feature-settings: "ss04" 1; +} diff --git a/packages/openbridge-webcomponents/src/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts b/packages/openbridge-webcomponents/src/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts new file mode 100644 index 000000000..34c60536f --- /dev/null +++ b/packages/openbridge-webcomponents/src/components/alert-subsystem-counter/alert-subsystem-counter.stories.ts @@ -0,0 +1,105 @@ +import type {Meta, StoryObj} from '@storybook/web-components-vite'; +import {html} from 'lit'; +import { + ObcAlertSubsystemCounter, + ObcAlertSubsystemCounterOrientation, +} from './alert-subsystem-counter.js'; +import './alert-subsystem-counter.js'; +import '../badge/badge.js'; +import '../../icons/icon-placeholder.js'; + +const badges = html` + + + +`; + +const meta: Meta = { + title: 'Application Components/Alerts/Alert Subsystem Counter', + tags: ['autodocs', '6.0'], + component: 'obc-alert-subsystem-counter', + args: { + label: 'Label', + orientation: ObcAlertSubsystemCounterOrientation.Horizontal, + hasAlert: true, + emptyText: 'No alerts', + }, + argTypes: { + orientation: { + control: {type: 'inline-radio'}, + options: Object.values(ObcAlertSubsystemCounterOrientation), + }, + hasAlert: {control: {type: 'boolean'}}, + label: {control: {type: 'text'}}, + emptyText: {control: {type: 'text'}}, + }, + render: (args) => + html`
+ + + ${badges} + +
`, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const Abbreviation: Story = { + args: {label: 'ABC'}, +}; + +export const NoAlerts: Story = { + args: {hasAlert: false}, +}; + +export const Vertical: Story = { + args: {orientation: ObcAlertSubsystemCounterOrientation.Vertical}, +}; + +export const VerticalNoAlerts: Story = { + args: { + orientation: ObcAlertSubsystemCounterOrientation.Vertical, + hasAlert: false, + }, +}; + +export const AbbreviationVertical: Story = { + args: { + label: 'ABC', + orientation: ObcAlertSubsystemCounterOrientation.Vertical, + }, + render: (args) => + html`
+ + + ${badges} + +
`, +}; + +export const WithoutIcon: Story = { + render: (args) => + html`
+ + ${badges} + +
`, +}; diff --git a/packages/openbridge-webcomponents/src/components/alert-subsystem-counter/alert-subsystem-counter.ts b/packages/openbridge-webcomponents/src/components/alert-subsystem-counter/alert-subsystem-counter.ts new file mode 100644 index 000000000..ee72f4b57 --- /dev/null +++ b/packages/openbridge-webcomponents/src/components/alert-subsystem-counter/alert-subsystem-counter.ts @@ -0,0 +1,146 @@ +import {LitElement, html, unsafeCSS} from 'lit'; +import {property, state} from 'lit/decorators.js'; +import {classMap} from 'lit/directives/class-map.js'; +import componentStyle from './alert-subsystem-counter.css?inline'; +import {customElement} from '../../decorator.js'; + +/** + * `ObcAlertSubsystemCounterOrientation` – Layout direction for the counter. + * + * - `horizontal`: Icon, label, and badges sit in a single row. + * - `vertical`: Badges (or empty text) sit on their own line below the label. + */ +export enum ObcAlertSubsystemCounterOrientation { + Horizontal = 'horizontal', + Vertical = 'vertical', +} + +/** + * `` – A framed summary of a subsystem's alert + * counts: a leading icon, a label, and a trailing set of slotted count badges. + * + * When there are no active alerts it shows a muted label and an empty-state + * message instead of the badges. The count badges themselves are supplied by + * the consumer as slotted `obc-badge` elements; this component provides only + * the frame, layout, label, and empty state. + * + * --- + * + * ### Features + * - **Orientation:** `horizontal` (single row) or `vertical` (badges on a line + * below the label). + * - **Alert state:** `hasAlert` toggles between an active (bold label, badges) + * and an inactive (muted label, empty text) presentation. + * - **Freeform label:** Pass a full title or an abbreviation — the label is + * plain text decided by the caller. + * - **Configurable empty text:** Customize the message shown when there are no + * alerts via `emptyText`. + * - **Optional leading icon:** Provide an icon through the `icon` slot; omit it + * and the leading box collapses with no empty gap. + * + * --- + * + * ### Usage Guidelines + * - Use this component to give an at-a-glance count of outstanding alerts for a + * single subsystem, grouped by severity. + * - Provide one slotted badge per severity in the `badges` slot, typically + * `` with a severity `type` and a `number`. + * - Set `hasAlert` to `false` to present the resolved/clear state; the badges + * are then hidden and `emptyText` is shown. + * - Choose `vertical` orientation in narrow containers where the badges do not + * fit beside the label. + * + * --- + * + * ### Slots + * + * | Slot Name | Renders When... | Purpose | + * |-----------|------------------------|----------------------------------------------------| + * | `icon` | The slot has content | Leading 24px icon (e.g. ``). | + * | `badges` | `hasAlert` is true | Count badges, one per severity (``). | + * + * --- + * + * ### Example + * + * ```html + * + * + * + * + * + * + * ``` + * + * @slot icon - Optional leading 24px icon. + * @slot badges - Count badges shown when `hasAlert` is true. + */ +@customElement('obc-alert-subsystem-counter') +export class ObcAlertSubsystemCounter extends LitElement { + /** + * The subsystem label. Pass a full title or an abbreviation as plain text. + */ + @property({type: String}) label = ''; + + /** + * Layout direction. + * + * - `horizontal` (default): icon, label, and badges in one row. + * - `vertical`: badges (or empty text) on a line below the label. + */ + @property({type: String}) orientation: ObcAlertSubsystemCounterOrientation = + ObcAlertSubsystemCounterOrientation.Horizontal; + + /** + * Whether the subsystem has active alerts. + * + * When `true`, the label is shown active (bold) and the `badges` slot is + * rendered. When `false`, the label is muted and `emptyText` is shown + * instead of the badges. + */ + @property({type: Boolean}) hasAlert = false; + + /** + * Text shown in the trailing area when `hasAlert` is `false`. + */ + @property({type: String}) emptyText = 'No alerts'; + + @state() private hasIcon = false; + + private handleIconSlotChange(e: Event) { + const slot = e.target as HTMLSlotElement; + this.hasIcon = slot.assignedNodes({flatten: true}).length > 0; + } + + override render() { + return html` +
+
+
+ +
+
${this.label}
+
+
+ ${this.hasAlert + ? html`` + : html`${this.emptyText}`} +
+
+ `; + } + + static override styles = unsafeCSS(componentStyle); +} + +declare global { + interface HTMLElementTagNameMap { + 'obc-alert-subsystem-counter': ObcAlertSubsystemCounter; + } +}