diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/default-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/default-auto.png new file mode 100644 index 000000000..9a2c12813 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/default-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/large-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/large-auto.png new file mode 100644 index 000000000..c7ab4c6d5 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/large-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/medium-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/medium-auto.png new file mode 100644 index 000000000..5c4e163dc Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/medium-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/regular-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/regular-auto.png new file mode 100644 index 000000000..17856beae Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/regular-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/small-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/small-auto.png new file mode 100644 index 000000000..9a6753e6e Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/small-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/tag-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/tag-auto.png new file mode 100644 index 000000000..c4a6268cd Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/tag-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/with-advice-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/with-advice-auto.png new file mode 100644 index 000000000..22a13d407 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/with-advice-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/with-icon-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/with-icon-auto.png new file mode 100644 index 000000000..17856beae Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/with-icon-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/zero-padded-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/zero-padded-auto.png new file mode 100644 index 000000000..caa64f5d7 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter-button/transmitter-button.stories.ts/zero-padded-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/horizontal-graph-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/horizontal-graph-auto.png new file mode 100644 index 000000000..bc2007176 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/horizontal-graph-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/indicator-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/indicator-auto.png new file mode 100644 index 000000000..8c9729c9a Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/indicator-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/usage-with-pipe-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/usage-with-pipe-auto.png new file mode 100644 index 000000000..41d6c7e53 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/usage-with-pipe-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/value-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/value-auto.png new file mode 100644 index 000000000..533e77da5 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/value-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/vertical-graph-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/vertical-graph-auto.png new file mode 100644 index 000000000..66f9fe713 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/vertical-graph-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/zero-padded-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/zero-padded-auto.png new file mode 100644 index 000000000..502651958 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/automation/transmitter/transmitter.stories.ts/zero-padded-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/below-zero-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/below-zero-auto.png index bd97dda43..0027512b8 100644 Binary files a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/below-zero-auto.png and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/below-zero-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/enhanced-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/enhanced-auto.png index c71d3506e..33a062dfc 100644 Binary files a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/enhanced-auto.png and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/enhanced-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/large-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/large-auto.png index 919fc5c69..c46283f87 100644 Binary files a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/large-auto.png and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/large-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/medium-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/medium-auto.png index 47af4fc2f..587b557a5 100644 Binary files a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/medium-auto.png and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/medium-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/no-zero-line-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/no-zero-line-auto.png index 186a3701f..8c9de940a 100644 Binary files a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/no-zero-line-auto.png and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/no-zero-line-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/primary-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/primary-auto.png index 47af4fc2f..587b557a5 100644 Binary files a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/primary-auto.png and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/primary-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/small-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/small-auto.png index 1a2bc3b82..960da298f 100644 Binary files a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/small-auto.png and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/small-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/with-fill-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/with-fill-auto.png new file mode 100644 index 000000000..2f511a511 Binary files /dev/null and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/with-fill-auto.png differ diff --git a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/with-range-auto.png b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/with-range-auto.png index ee0d282f8..2cfc99156 100644 Binary files a/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/with-range-auto.png and b/packages/openbridge-webcomponents/__vis__/linux/__baselines__/navigation-instruments/indicator-graph/indicator-graph.stories.ts/with-range-auto.png differ diff --git a/packages/openbridge-webcomponents/src/automation/transmitter-button/transmitter-button.css b/packages/openbridge-webcomponents/src/automation/transmitter-button/transmitter-button.css new file mode 100644 index 000000000..3f3e94d61 --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/transmitter-button/transmitter-button.css @@ -0,0 +1,124 @@ +* { + box-sizing: border-box; +} + +:host { + display: inline-block; +} + +.button { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: fit-content; + padding: 0; + appearance: none; + background: none; + border: none; + cursor: pointer; + + @mixin style style=normal className=value visibleWrapperClass=.visible-wrapper; +} + +.visible-wrapper { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 4px; + border: 1px solid var(--normal-enabled-border-color); + border-radius: 4px; +} + +.value-container { + display: inline-flex; + align-items: center; + gap: 2px; + padding: 0 2px; +} + +.icon { + display: inline-flex; + align-items: center; + justify-content: center; +} + +.advice, +.setpoint { + display: inline-flex; + align-items: center; +} + +.value { + color: var(--element-neutral-color); + text-align: right; +} + +.hinted-zero { + color: var(--border-outline-color); + pointer-events: none; + + &.is-hidden { + opacity: 0; + } +} + +.unit { + @mixin font-instrument-unit; + align-self: flex-end; + color: var(--element-neutral-color); +} + +.size-small .icon ::slotted(*) { + width: 12px; + height: 12px; +} + +.size-regular .icon ::slotted(*) { + width: 16px; + height: 16px; +} + +.size-medium .icon ::slotted(*) { + width: 20px; + height: 20px; +} + +.size-large .icon ::slotted(*) { + width: 24px; + height: 24px; +} + +.size-small .value { + @mixin font-instrument-value-small-neutral; +} + +.size-regular .value { + @mixin font-instrument-value-regular-neutral; +} + +.size-medium .value { + @mixin font-instrument-value-m-neutral; +} + +.size-large .value { + @mixin font-instrument-value-enhanced-neutral; +} + +.size-medium .visible-wrapper:not(.tag), +.size-large .visible-wrapper:not(.tag) { + padding: 4px 8px; + border-radius: 6px; +} + +.visible-wrapper.tag { + min-height: 24px; + padding: 2px 8px; + border-radius: 100px; + background-color: var(--container-section-color); +} + +.label { + @mixin font-instrument-value-regular-neutral; + color: var(--element-neutral-color); +} diff --git a/packages/openbridge-webcomponents/src/automation/transmitter-button/transmitter-button.stories.ts b/packages/openbridge-webcomponents/src/automation/transmitter-button/transmitter-button.stories.ts new file mode 100644 index 000000000..c603c5010 --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/transmitter-button/transmitter-button.stories.ts @@ -0,0 +1,135 @@ +import type {Meta, StoryObj} from '@storybook/web-components-vite'; +import {html} from 'lit'; +import {ObcTransmitterButton} from './transmitter-button.js'; +import './transmitter-button.js'; +import { + TransmitterButtonSize, + TransmitterButtonVariant, +} from './transmitter-button.js'; +import '../../icons/icon-temperature-air.js'; + +const meta: Meta = { + title: 'Automation/Transmitter/Transmitter Button', + tags: ['autodocs'], + component: 'obc-transmitter-button', + args: { + variant: TransmitterButtonVariant.value, + size: TransmitterButtonSize.regular, + value: 12.3, + unit: '°C', + fractionDigits: 1, + minValueLength: 0, + hasHintedZeros: false, + showZeroPadding: false, + hasIcon: false, + hasAdvice: false, + adviceValue: 123, + hasSetPoint: false, + setpointValue: 123, + label: 'TT', + }, + argTypes: { + variant: { + options: [TransmitterButtonVariant.value, TransmitterButtonVariant.tag], + control: {type: 'radio'}, + }, + size: { + options: [ + TransmitterButtonSize.small, + TransmitterButtonSize.regular, + TransmitterButtonSize.medium, + TransmitterButtonSize.large, + ], + control: {type: 'radio'}, + }, + value: {control: {type: 'range', min: -99, max: 999, step: 0.1}}, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +function renderComponent(args: ObcTransmitterButton) { + return html` + + ${args.hasIcon + ? html`` + : ''} + + `; +} + +export const Default: Story = { + render: (args) => renderComponent(args as ObcTransmitterButton), +}; + +export const WithIcon: Story = { + args: {hasIcon: true}, + render: (args) => renderComponent(args as ObcTransmitterButton), +}; + +export const WithAdvice: Story = { + args: {hasIcon: true, hasAdvice: true}, + render: (args) => renderComponent(args as ObcTransmitterButton), +}; + +export const WithSetpoint: Story = { + args: {hasIcon: true, hasSetPoint: true}, + render: (args) => renderComponent(args as ObcTransmitterButton), +}; + +export const WithAdviceAndSetpoint: Story = { + args: {hasIcon: true, hasAdvice: true, hasSetPoint: true}, + render: (args) => renderComponent(args as ObcTransmitterButton), +}; + +export const Small: Story = { + args: {size: TransmitterButtonSize.small, hasIcon: true}, + render: (args) => renderComponent(args as ObcTransmitterButton), +}; + +export const Regular: Story = { + args: {size: TransmitterButtonSize.regular, hasIcon: true}, + render: (args) => renderComponent(args as ObcTransmitterButton), +}; + +export const Medium: Story = { + args: {size: TransmitterButtonSize.medium, hasIcon: true}, + render: (args) => renderComponent(args as ObcTransmitterButton), +}; + +export const Large: Story = { + args: {size: TransmitterButtonSize.large, hasIcon: true}, + render: (args) => renderComponent(args as ObcTransmitterButton), +}; + +export const Tag: Story = { + args: {variant: TransmitterButtonVariant.tag, label: 'TT'}, + render: (args) => renderComponent(args as ObcTransmitterButton), +}; + +export const ZeroPadded: Story = { + args: { + value: 12.3, + fractionDigits: 1, + minValueLength: 5, + hasHintedZeros: true, + hasIcon: true, + }, + render: (args) => renderComponent(args as ObcTransmitterButton), +}; diff --git a/packages/openbridge-webcomponents/src/automation/transmitter-button/transmitter-button.ts b/packages/openbridge-webcomponents/src/automation/transmitter-button/transmitter-button.ts new file mode 100644 index 000000000..0033f2a9e --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/transmitter-button/transmitter-button.ts @@ -0,0 +1,221 @@ +import {LitElement, html, nothing, unsafeCSS} from 'lit'; +import {property} from 'lit/decorators.js'; +import {classMap} from 'lit/directives/class-map.js'; +import componentStyle from './transmitter-button.css?inline'; +import {customElement} from '../../decorator.js'; +import { + formatNumericValue, + getHintZeros, + type ReadoutNumericFormatOptions, +} from '../../navigation-instruments/readout/readout-formatters.js'; +import { + ReadoutSetpointMode, + ReadoutSetpointSize, + ReadoutSetpointVariant, +} from '../../navigation-instruments/readout-setpoint/readout-setpoint.js'; +import '../../navigation-instruments/readout-setpoint/readout-setpoint.js'; +import '../../navigation-instruments/readout-advice/readout-advice.js'; +import '../../icons/icon-input-right.js'; + +export enum TransmitterButtonVariant { + value = 'value', + tag = 'tag', +} + +export enum TransmitterButtonSize { + small = 'small', + regular = 'regular', + medium = 'medium', + large = 'large', +} + +const readoutSizeBySize: Record = { + [TransmitterButtonSize.small]: ReadoutSetpointSize.small, + [TransmitterButtonSize.regular]: ReadoutSetpointSize.regular, + [TransmitterButtonSize.medium]: ReadoutSetpointSize.medium, + [TransmitterButtonSize.large]: ReadoutSetpointSize.large, +}; + +/** + * `` – The pressable readout chip used as the core of a + * transmitter on a process diagram. + * + * The value content (leading icon, optional advice and setpoint segments, + * value, unit) is laid out inline. Numeric formatting follows the + * readout-setpoint convention via the shared `readout-formatters` helpers, so it + * supports fixed-width values and muted leading zeros. The leading icon is + * slotted so the consumer provides the type-specific content; the advice and + * setpoint segments reuse `` and `` + * and share the value's numeric formatting. + * + * ### Features / Variants + * - **`value`** – white, bordered box showing an icon, value and unit. Opt into a + * leading advice segment with `hasAdvice`/`adviceValue` and a setpoint segment + * with `hasSetPoint`/`setpointValue`. + * - **`tag`** – a static rounded pill showing a short identifier (e.g. `TT`) + * from the `label` property, with no live value. + * - **`size`** – `small`, `regular`, `medium` or `large`, scaling the value text, + * the icon glyph and the advice/setpoint segments. The unit stays at a fixed + * size across all sizes. + * - **Formatting** – `fractionDigits` sets the decimal precision, `minValueLength` + * reserves a minimum total digit count, and `hasHintedZeros` renders the + * reserved leading positions as muted zeros (e.g. `0012.3`). `showZeroPadding` + * pads the dashed fallback shown when no value is set. The advice and setpoint + * segments reuse `fractionDigits`, `minValueLength` and `hasHintedZeros`. + * + * ### Usage Guidelines + * Use as a building block for ``; it is the part that carries + * the measured value or the tag identifier. Slot in the type-specific icon; the + * advice and setpoint segments are value-driven and read-only. + * + * ### Slots + * | Slot Name | Conditions | Purpose | + * |-----------|-------------------------------|--------------------------------| + * | icon | `value` variant and `hasIcon` | Leading icon beside the value. | + */ +@customElement('obc-transmitter-button') +export class ObcTransmitterButton extends LitElement { + @property({type: String}) variant: TransmitterButtonVariant = + TransmitterButtonVariant.value; + @property({type: String}) size: TransmitterButtonSize = + TransmitterButtonSize.regular; + @property({type: Number}) value?: number; + @property({type: String}) unit = ''; + @property({type: Number}) fractionDigits = 1; + @property({type: Number}) minValueLength = 0; + @property({type: Boolean}) hasHintedZeros = false; + @property({type: Boolean}) showZeroPadding = false; + @property({type: Boolean}) hasIcon = false; + @property({type: Boolean}) hasAdvice = false; + + /** Advisory value shown in the leading advice segment when `hasAdvice`. */ + @property({type: Number}) adviceValue?: number; + + @property({type: Boolean}) hasSetPoint = false; + + /** Target value shown in the setpoint segment when `hasSetPoint`. */ + @property({type: Number}) setpointValue?: number; + + /** Short tag identifier shown in the `tag` variant (e.g. `TT`). */ + @property({type: String}) label = ''; + + private get isTag() { + return this.variant === TransmitterButtonVariant.tag; + } + + private get numericFormatOptions(): ReadoutNumericFormatOptions { + return { + showZeroPadding: this.showZeroPadding, + minValueLength: this.minValueLength, + fractionDigits: this.fractionDigits, + }; + } + + private get normalizedValue(): number | undefined { + if (this.value === undefined || Number.isNaN(this.value)) { + return undefined; + } + return this.value; + } + + private renderValue() { + const hintedText = getHintZeros( + this.normalizedValue, + this.numericFormatOptions + ); + const valueText = formatNumericValue( + this.normalizedValue, + this.numericFormatOptions + ); + + return html`${hintedText + ? html`` + : nothing}${valueText}`; + } + + private renderAdvice() { + if (!this.hasAdvice) { + return nothing; + } + return html` + + `; + } + + private renderSetpoint() { + if (!this.hasSetPoint) { + return nothing; + } + return html` + + + + `; + } + + private renderContent() { + if (this.isTag) { + return html`${this.label}`; + } + + return html` + ${this.renderAdvice()} ${this.renderSetpoint()} +
+ ${this.hasIcon + ? html`
` + : nothing} + ${this.renderValue()} + ${this.unit ? html`${this.unit}` : nothing} +
+ `; + } + + override render() { + return html` + + `; + } + + static override styles = unsafeCSS(componentStyle); +} + +declare global { + interface HTMLElementTagNameMap { + 'obc-transmitter-button': ObcTransmitterButton; + } +} diff --git a/packages/openbridge-webcomponents/src/automation/transmitter/transmitter.css b/packages/openbridge-webcomponents/src/automation/transmitter/transmitter.css new file mode 100644 index 000000000..19c0f332f --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/transmitter/transmitter.css @@ -0,0 +1,141 @@ +* { + box-sizing: border-box; +} + +:host { + display: block; +} + +.transmitter { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: fit-content; + + &.orientation-bottom { + padding-top: 8px; + top: calc(1px + var(--offset)); + transform: translateX(-50%); + } + + &.orientation-top { + padding-bottom: 8px; + transform: translate(-50%, calc(-100% - 1px - var(--offset))); + } + + &.orientation-left { + padding-right: 8px; + transform: translate(calc(-100% - 4px - var(--offset)), -50%); + } + + &.orientation-right { + padding-left: 8px; + transform: translateY(-50%); + right: calc(-4px - var(--offset)); + } + + &::before { + content: ""; + display: block; + position: absolute; + border: var(--normal-enabled-border-color) solid 1px; + } + + &.orientation-bottom::before, + &.orientation-top::before { + width: 0; + height: 8px; + } + + &.orientation-bottom::before { + top: -1px; + } + + &.orientation-top::before { + bottom: -1px; + } + + &.orientation-left::before, + &.orientation-right::before { + height: 0; + width: 8px; + } + + &.orientation-left::before { + right: -4px; + } + + &.orientation-right::before { + left: -4px; + } +} + +.content { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; +} + +.alert-frame .content { + padding: 4px; +} + +/* Top orientation places the id-tag above the chip (leader line below). */ +.orientation-top .content { + flex-direction: column-reverse; +} + +.body { + display: flex; +} + +.type-horizontal-graph .body { + flex-direction: row; + align-items: stretch; +} + +.type-vertical-graph .body { + flex-direction: column; + align-items: stretch; +} + +.graph-box { + overflow: hidden; + border: 1px solid var(--instrument-regular-tertiary-color); +} + +.type-horizontal-graph .graph-box { + width: 64px; + border-left: none; + border-radius: 0 4px 4px 0; +} + +.type-vertical-graph .graph-box { + height: 48px; + border-top: none; + border-radius: 0 0 4px 4px; +} + +/* Flatten the chip corners and drop the shared border where the graph joins. */ +.type-horizontal-graph .chip::part(button) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: none; +} + +.type-vertical-graph .chip::part(button) { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: none; +} + +.type-vertical-graph .chip { + width: 100%; +} + +.id-tag { + @mixin font-instrument-label; + color: var(--element-neutral-color); +} diff --git a/packages/openbridge-webcomponents/src/automation/transmitter/transmitter.stories.ts b/packages/openbridge-webcomponents/src/automation/transmitter/transmitter.stories.ts new file mode 100644 index 000000000..fbb607eae --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/transmitter/transmitter.stories.ts @@ -0,0 +1,183 @@ +import type {Meta, StoryObj} from '@storybook/web-components-vite'; +import {html} from 'lit'; +import {ObcTransmitter} from './transmitter.js'; +import './transmitter.js'; +import {TransmitterOrientation, TransmitterType} from './transmitter.js'; +import {TransmitterButtonSize} from '../transmitter-button/transmitter-button.js'; +import {crossDecorator} from '../../storybook-util.js'; +import '../../icons/icon-temperature-air.js'; +import '../horizontal-line/horizontal-line.js'; +import {LineMedium, LineType} from '../index.js'; + +const sineData: [number[], number[]] = [ + Array.from({length: 30}, (_, i) => i), + Array.from({length: 30}, (_, i) => 2 + Math.sin((i / 30) * 2 * Math.PI)), +]; + +const meta: Meta = { + title: 'Automation/Transmitter/Transmitter', + tags: ['autodocs'], + component: 'obc-transmitter', + decorators: [crossDecorator], + args: { + orientation: TransmitterOrientation.bottom, + type: TransmitterType.value, + size: TransmitterButtonSize.regular, + lineType: LineType.fluid, + value: 12.3, + unit: '°C', + fractionDigits: 1, + minValueLength: 0, + hasHintedZeros: false, + showZeroPadding: false, + hasIcon: true, + hasAdvice: false, + adviceValue: 123, + hasSetPoint: false, + setpointValue: 123, + hasAlert: false, + tag: 'TT', + idTag: '#0000', + data: sineData, + }, + argTypes: { + orientation: { + options: [ + TransmitterOrientation.top, + TransmitterOrientation.right, + TransmitterOrientation.bottom, + TransmitterOrientation.left, + ], + control: {type: 'radio'}, + }, + type: { + options: [ + TransmitterType.indicator, + TransmitterType.value, + TransmitterType.horizontalGraph, + TransmitterType.verticalGraph, + ], + control: {type: 'radio'}, + }, + size: { + options: [ + TransmitterButtonSize.small, + TransmitterButtonSize.regular, + TransmitterButtonSize.medium, + TransmitterButtonSize.large, + ], + control: {type: 'radio'}, + }, + lineType: { + options: [ + LineType.air, + LineType.connector, + LineType.electric, + LineType.fluid, + ], + control: {type: 'radio'}, + }, + value: {control: {type: 'range', min: -99, max: 999, step: 0.1}}, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +function renderComponent(args: ObcTransmitter) { + return html` + + ${args.hasIcon + ? html`` + : ''} + + `; +} + +export const Value: Story = { + render: (args) => renderComponent(args as ObcTransmitter), +}; + +export const Indicator: Story = { + args: {type: TransmitterType.indicator}, + render: (args) => renderComponent(args as ObcTransmitter), +}; + +export const HorizontalGraph: Story = { + args: {type: TransmitterType.horizontalGraph}, + render: (args) => renderComponent(args as ObcTransmitter), +}; + +export const VerticalGraph: Story = { + args: {type: TransmitterType.verticalGraph}, + render: (args) => renderComponent(args as ObcTransmitter), +}; + +export const WithAdviceAndSetpoint: Story = { + args: {type: TransmitterType.value, hasAdvice: true, hasSetPoint: true}, + render: (args) => renderComponent(args as ObcTransmitter), +}; + +export const WithAlert: Story = { + args: {type: TransmitterType.value, hasAlert: true}, + render: (args) => renderComponent(args as ObcTransmitter), +}; + +export const ZeroPadded: Story = { + args: { + type: TransmitterType.value, + value: 12.3, + fractionDigits: 1, + minValueLength: 5, + hasHintedZeros: true, + }, + render: (args) => renderComponent(args as ObcTransmitter), +}; + +export const UsageWithPipe: Story = { + args: {orientation: TransmitterOrientation.bottom}, + render: (args) => html` + +
+ ${renderComponent(args as ObcTransmitter)} + +
+ `, +}; diff --git a/packages/openbridge-webcomponents/src/automation/transmitter/transmitter.ts b/packages/openbridge-webcomponents/src/automation/transmitter/transmitter.ts new file mode 100644 index 000000000..795116f6e --- /dev/null +++ b/packages/openbridge-webcomponents/src/automation/transmitter/transmitter.ts @@ -0,0 +1,205 @@ +import {LitElement, html, nothing, unsafeCSS} from 'lit'; +import {property} from 'lit/decorators.js'; +import {classMap} from 'lit/directives/class-map.js'; +import componentStyle from './transmitter.css?inline'; +import {customElement} from '../../decorator.js'; +import {LineType, lineWidth} from '../index.js'; +import { + TransmitterButtonSize, + TransmitterButtonVariant, +} from '../transmitter-button/transmitter-button.js'; +import '../transmitter-button/transmitter-button.js'; +import { + ObcIndicatorGraphSize, + type ObcIndicatorGraphLayout, +} from '../../navigation-instruments/indicator-graph/indicator-graph.js'; +import '../../navigation-instruments/indicator-graph/indicator-graph.js'; +import {ObcAlertFrameType} from '../../components/alert-frame/alert-frame.js'; +import '../../components/alert-frame/alert-frame.js'; + +export enum TransmitterOrientation { + top = 'top', + right = 'right', + bottom = 'bottom', + left = 'left', +} + +export enum TransmitterType { + indicator = 'indicator', + value = 'value', + horizontalGraph = 'horizontal-graph', + verticalGraph = 'vertical-graph', +} + +/** + * `` – A readout label that attaches to a line on a process + * diagram via a leader line, showing a measured value, a tag identifier, or a + * value paired with a trend graph. + * + * Positioning (orientation + leader line) follows ``. + * The value chip is an `` and the trend is an + * `` with its area fill enabled. + * + * ### Features / Variants + * - **`type`** – `indicator` (tag pill), `value` (icon/value/unit), or value + * paired with a `horizontal-graph` (beside) or `vertical-graph` (below). + * - **`orientation`** – `top`, `right`, `bottom`, `left`; controls which edge + * the leader line attaches to. + * - **Segments** – opt into a leading advice segment with `hasAdvice`/ + * `adviceValue` and a setpoint segment with `hasSetPoint`/`setpointValue`; + * both are read-only and shown in the value chip for non-`indicator` types. + * - **`hasAlert`** – wraps the whole transmitter in an alarm ``. + * - **Formatting** – `fractionDigits`, `minValueLength`, `hasHintedZeros` and + * `showZeroPadding` are forwarded to the value chip to control decimal + * precision and muted leading-zero padding (e.g. `0012.3`). The advice and + * setpoint segments reuse the same formatting. + * + * ### Slots + * | Slot Name | Conditions | Purpose | + * |-----------|-------------------------|---------------------------------| + * | icon | value/graph + `hasIcon` | Leading icon in the value chip. | + */ +@customElement('obc-transmitter') +export class ObcTransmitter extends LitElement { + @property({type: String}) orientation: TransmitterOrientation = + TransmitterOrientation.bottom; + @property({type: String}) type: TransmitterType = TransmitterType.value; + @property({type: String}) lineType: LineType | undefined = undefined; + + @property({type: Number}) value?: number; + @property({type: String}) unit = ''; + @property({type: Number}) fractionDigits = 1; + @property({type: Number}) minValueLength = 0; + @property({type: Boolean}) hasHintedZeros = false; + @property({type: Boolean}) showZeroPadding = false; + @property({type: String}) size: TransmitterButtonSize = + TransmitterButtonSize.regular; + @property({type: Boolean}) hasIcon = false; + @property({type: Boolean}) hasAdvice = false; + + /** Wrap the transmitter in an `` (alarm) when true. */ + @property({type: Boolean}) hasAlert = false; + + /** Advisory value shown in the leading advice segment when `hasAdvice`. */ + @property({type: Number}) adviceValue?: number; + + @property({type: Boolean}) hasSetPoint = false; + + /** Target value shown in the setpoint segment when `hasSetPoint`. */ + @property({type: Number}) setpointValue?: number; + + /** Tag identifier shown when `type` is `indicator` (e.g. `TT`). */ + @property({type: String}) tag = ''; + + /** Optional identifier shown below the chip (e.g. `#0000`). */ + @property({type: String}) idTag = ''; + + /** Trend data for the graph types: `[xValues, yValues]`. */ + @property({type: Array}) data: [number[], number[]] = [[], []]; + + private get hasGraph() { + return ( + this.type === TransmitterType.horizontalGraph || + this.type === TransmitterType.verticalGraph + ); + } + + private get lineOffset() { + return this.lineType === undefined ? 0 : lineWidth(this.lineType) / 2; + } + + private renderButton() { + const isIndicator = this.type === TransmitterType.indicator; + return html` + + + + `; + } + + private renderGraph() { + if (!this.hasGraph) { + return nothing; + } + const layout: ObcIndicatorGraphLayout = { + size: ObcIndicatorGraphSize.small, + fill: true, + }; + return html` +
+ + +
+ `; + } + + private renderLabel() { + if (!this.idTag) { + return nothing; + } + return html`
${this.idTag}
`; + } + + private renderContent() { + return html` +
+
${this.renderButton()} ${this.renderGraph()}
+ ${this.renderLabel()} +
+ `; + } + + override render() { + const content = this.hasAlert + ? html` + + ${this.renderContent()} + + ` + : this.renderContent(); + + return html` +
+ ${content} +
+ `; + } + + static override styles = unsafeCSS(componentStyle); +} + +declare global { + interface HTMLElementTagNameMap { + 'obc-transmitter': ObcTransmitter; + } +} diff --git a/packages/openbridge-webcomponents/src/navigation-instruments/indicator-graph/indicator-graph.stories.ts b/packages/openbridge-webcomponents/src/navigation-instruments/indicator-graph/indicator-graph.stories.ts index 682c5b20d..2e0b6c6c0 100644 --- a/packages/openbridge-webcomponents/src/navigation-instruments/indicator-graph/indicator-graph.stories.ts +++ b/packages/openbridge-webcomponents/src/navigation-instruments/indicator-graph/indicator-graph.stories.ts @@ -81,6 +81,15 @@ export const Large: Story = { }, }; +export const WithFill: Story = { + args: { + layout: { + size: ObcIndicatorGraphSize.medium, + fill: true, + }, + }, +}; + export const Realtime: Story = { tags: ['skip-test'], args: { diff --git a/packages/openbridge-webcomponents/src/navigation-instruments/indicator-graph/indicator-graph.ts b/packages/openbridge-webcomponents/src/navigation-instruments/indicator-graph/indicator-graph.ts index 68a645016..32f84533a 100644 --- a/packages/openbridge-webcomponents/src/navigation-instruments/indicator-graph/indicator-graph.ts +++ b/packages/openbridge-webcomponents/src/navigation-instruments/indicator-graph/indicator-graph.ts @@ -15,10 +15,10 @@ export enum ObcIndicatorGraphPriority { enhanced = 'enhanced', } -// showZeroLine is defaulted to true export interface ObcIndicatorGraphLayout { size?: ObcIndicatorGraphSize; priority?: ObcIndicatorGraphPriority; + fill?: boolean; y?: {min?: number; max?: number; showZeroLine?: boolean}; } @@ -70,6 +70,7 @@ export class ObcIndicatorGraph extends LitElement { { stroke: this._getStrokeColor(), width: this._getStrokeWidth(), + fill: this._effectiveFill ? () => this._getFillColor() : undefined, points: {show: false}, }, ], @@ -130,6 +131,14 @@ export class ObcIndicatorGraph extends LitElement { return this.layout.priority ?? ObcIndicatorGraphPriority.regular; } + private get _effectiveFill() { + return this.layout.fill ?? false; + } + + private _getFillColor() { + return this.getCssColor('--instrument-regular-tertiary-color') || '#bebebe'; + } + private _getStrokeWidth() { switch (this._effectiveSize) { case ObcIndicatorGraphSize.small: @@ -157,6 +166,9 @@ export class ObcIndicatorGraph extends LitElement { const series = this.uplot.series[1]; series.stroke = () => this._getStrokeColor(); series.width = this._getStrokeWidth(); + if (this._effectiveFill) { + series.fill = () => this._getFillColor(); + } this.uplot.redraw(); }