@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.128 → 2.0.0-next.129

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 (38) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +12321 -11895
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +227 -3
  4. package/dist/building-blocks/readout-block/readout-block.css.js +53 -19
  5. package/dist/building-blocks/readout-block/readout-block.css.js.map +1 -1
  6. package/dist/building-blocks/readout-block/readout-block.d.ts +55 -0
  7. package/dist/building-blocks/readout-block/readout-block.d.ts.map +1 -1
  8. package/dist/building-blocks/readout-block/readout-block.js +57 -2
  9. package/dist/building-blocks/readout-block/readout-block.js.map +1 -1
  10. package/dist/navigation-instruments/readout/readout-shared.d.ts +23 -0
  11. package/dist/navigation-instruments/readout/readout-shared.d.ts.map +1 -1
  12. package/dist/navigation-instruments/readout/readout-shared.js +8 -0
  13. package/dist/navigation-instruments/readout/readout-shared.js.map +1 -1
  14. package/dist/navigation-instruments/readout/readout.css.js +138 -31
  15. package/dist/navigation-instruments/readout/readout.css.js.map +1 -1
  16. package/dist/navigation-instruments/readout/readout.d.ts +63 -3
  17. package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
  18. package/dist/navigation-instruments/readout/readout.js +127 -27
  19. package/dist/navigation-instruments/readout/readout.js.map +1 -1
  20. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +64 -1
  21. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
  22. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts +77 -2
  23. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
  24. package/dist/navigation-instruments/readout-list-item/readout-list-item.js +100 -23
  25. package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/building-blocks/readout-block/readout-block.css +49 -20
  28. package/src/building-blocks/readout-block/readout-block.stories.ts +54 -0
  29. package/src/building-blocks/readout-block/readout-block.ts +101 -2
  30. package/src/navigation-instruments/instrument-field/instrument-field.stories.ts +1150 -180
  31. package/src/navigation-instruments/readout/readout-shared.spec.ts +50 -0
  32. package/src/navigation-instruments/readout/readout-shared.ts +33 -0
  33. package/src/navigation-instruments/readout/readout.css +124 -32
  34. package/src/navigation-instruments/readout/readout.stories.ts +536 -19
  35. package/src/navigation-instruments/readout/readout.ts +184 -31
  36. package/src/navigation-instruments/readout-list-item/readout-list-item.css +55 -1
  37. package/src/navigation-instruments/readout-list-item/readout-list-item.stories.ts +341 -0
  38. package/src/navigation-instruments/readout-list-item/readout-list-item.ts +169 -25
@@ -14,14 +14,19 @@ import {
14
14
  ReadoutDataQuality,
15
15
  ReadoutSetpointInteraction,
16
16
  ReadoutSourceInteraction,
17
+ ReadoutAdviceCategory,
18
+ ReadoutSourceState,
17
19
  ObcTextboxFontWeight,
18
20
  type ReadoutValueOptions,
19
21
  type ReadoutSetpointOptions,
20
22
  type ReadoutAdviceOptions,
23
+ type ReadoutLabelOptions,
21
24
  type ReadoutReserverOptions,
22
25
  type ReadoutSourceOptions,
23
26
  ReadoutValueType,
24
27
  } from './readout.js';
28
+ import {ObcTextboxSize} from '../../components/textbox/textbox.js';
29
+ import '../../icons/icon-input-right.js';
25
30
  import {
26
31
  type AlertFrameConfig,
27
32
  ObcAlertFrameMode,
@@ -55,12 +60,20 @@ type ReadoutStoryArgs = {
55
60
  'options.fractionDigits': number;
56
61
  'options.maxDigits': number;
57
62
  'options.dataQuality': ReadoutDataQuality | typeof NONE;
63
+ 'options.hasLeadingIcon': boolean;
58
64
  'options.value.weight': ObcTextboxFontWeight;
59
65
  'options.value.hasIcon': boolean;
60
66
  'options.value.hintedZeros': boolean;
61
67
  'options.setpoint.interaction': ReadoutSetpointInteraction;
62
68
  'options.setpoint.touching': boolean;
69
+ 'options.advice.category': ReadoutAdviceCategory;
70
+ 'options.advice.active': boolean;
71
+ 'options.label.size': ObcTextboxSize | typeof NONE;
72
+ 'options.label.spaceReserver': string;
63
73
  'options.src.interaction': ReadoutSourceInteraction;
74
+ 'options.src.state': ReadoutSourceState;
75
+ 'options.src.deviation'?: number;
76
+ 'options.src.spaceReserver': string;
64
77
  'options.unit.spaceReserver': string;
65
78
  };
66
79
 
@@ -80,9 +93,11 @@ type StoryOptions = {
80
93
  maxDigits?: number;
81
94
  dataQuality?: ReadoutDataQuality;
82
95
  alert?: false | AlertFrameConfig;
96
+ hasLeadingIcon?: boolean;
83
97
  value?: ReadoutValueOptions;
84
98
  setpoint?: ReadoutSetpointOptions;
85
99
  advice?: ReadoutAdviceOptions;
100
+ label?: ReadoutLabelOptions;
86
101
  unit?: ReadoutReserverOptions;
87
102
  src?: ReadoutSourceOptions;
88
103
  };
@@ -179,9 +194,11 @@ function renderReadout(config: ReadoutConfig) {
179
194
  .maxDigits=${o.maxDigits ?? 0}
180
195
  .dataQuality=${o.dataQuality}
181
196
  .alert=${o.alert ?? false}
197
+ .hasLeadingIcon=${o.hasLeadingIcon ?? false}
182
198
  .valueOptions=${o.value}
183
199
  .setpointOptions=${o.setpoint}
184
200
  .adviceOptions=${o.advice}
201
+ .labelOptions=${o.label}
185
202
  .unitOptions=${o.unit}
186
203
  .srcOptions=${o.src}
187
204
  .showDebugOverlay=${config.showDebugOverlay ?? false}
@@ -189,6 +206,9 @@ function renderReadout(config: ReadoutConfig) {
189
206
  ${config.hasValueIcon
190
207
  ? html`<obi-placeholder slot="value-icon"></obi-placeholder>`
191
208
  : nothing}
209
+ ${o.hasLeadingIcon
210
+ ? html`<obi-placeholder slot="leading-icon"></obi-placeholder>`
211
+ : nothing}
192
212
  </obc-readout>
193
213
  `;
194
214
  }
@@ -261,12 +281,19 @@ const defaultArgs: ReadoutStoryArgs = {
261
281
  'options.fractionDigits': 0,
262
282
  'options.maxDigits': 0,
263
283
  'options.dataQuality': NONE,
284
+ 'options.hasLeadingIcon': false,
264
285
  'options.value.weight': ObcTextboxFontWeight.regular,
265
286
  'options.value.hasIcon': false,
266
287
  'options.value.hintedZeros': false,
267
288
  'options.setpoint.interaction': ReadoutSetpointInteraction.alwaysVisible,
268
289
  'options.setpoint.touching': false,
290
+ 'options.advice.category': ReadoutAdviceCategory.regular,
291
+ 'options.advice.active': false,
292
+ 'options.label.size': NONE,
293
+ 'options.label.spaceReserver': '',
269
294
  'options.src.interaction': ReadoutSourceInteraction.none,
295
+ 'options.src.state': ReadoutSourceState.regular,
296
+ 'options.src.spaceReserver': '',
270
297
  'options.unit.spaceReserver': '',
271
298
  };
272
299
 
@@ -285,6 +312,7 @@ function argsToOptions(args: ReadoutStoryArgs): StoryOptions {
285
312
  args['options.dataQuality'] === NONE
286
313
  ? undefined
287
314
  : args['options.dataQuality'],
315
+ hasLeadingIcon: args['options.hasLeadingIcon'],
288
316
  value: {
289
317
  weight: args['options.value.weight'],
290
318
  hasIcon: args['options.value.hasIcon'],
@@ -294,7 +322,23 @@ function argsToOptions(args: ReadoutStoryArgs): StoryOptions {
294
322
  interaction: args['options.setpoint.interaction'],
295
323
  touching: args['options.setpoint.touching'],
296
324
  },
297
- src: {interaction: args['options.src.interaction']},
325
+ advice: {
326
+ category: args['options.advice.category'],
327
+ active: args['options.advice.active'],
328
+ },
329
+ label: {
330
+ size:
331
+ args['options.label.size'] === NONE
332
+ ? undefined
333
+ : args['options.label.size'],
334
+ spaceReserver: args['options.label.spaceReserver'] || undefined,
335
+ },
336
+ src: {
337
+ interaction: args['options.src.interaction'],
338
+ state: args['options.src.state'],
339
+ deviation: args['options.src.deviation'],
340
+ spaceReserver: args['options.src.spaceReserver'] || undefined,
341
+ },
298
342
  unit: {spaceReserver: args['options.unit.spaceReserver'] || undefined},
299
343
  };
300
344
  }
@@ -357,8 +401,11 @@ const meta = {
357
401
  },
358
402
  'options.size': {
359
403
  name: 'Size',
404
+ description:
405
+ 'Vertical only — the horizontal arrangement exists in the large tier alone.',
360
406
  control: {type: 'select'},
361
407
  options: Object.values(ReadoutSize),
408
+ if: {arg: 'options.direction', eq: ReadoutDirection.vertical},
362
409
  table: {category: 'Layout'},
363
410
  },
364
411
  'options.priority': {
@@ -436,12 +483,59 @@ const meta = {
436
483
  if: {arg: 'hasSetpoint', truthy: true},
437
484
  table: {category: 'Setpoint'},
438
485
  },
486
+ 'options.hasLeadingIcon': {
487
+ name: 'Has Leading Icon',
488
+ table: {category: 'Label'},
489
+ },
490
+ 'options.label.size': {
491
+ name: 'Label Size',
492
+ description:
493
+ 'Default derives from the tier (large → s, otherwise xs); the design defines xs and s.',
494
+ control: {type: 'select'},
495
+ options: [NONE, ObcTextboxSize.xs, ObcTextboxSize.s],
496
+ table: {category: 'Label'},
497
+ },
498
+ 'options.label.spaceReserver': {
499
+ name: 'Label Space Reserver',
500
+ control: {type: 'text'},
501
+ table: {category: 'Label'},
502
+ },
503
+ 'options.advice.category': {
504
+ name: 'Advice Category',
505
+ control: {type: 'select'},
506
+ options: Object.values(ReadoutAdviceCategory),
507
+ if: {arg: 'hasAdvice', truthy: true},
508
+ table: {category: 'Advice'},
509
+ },
510
+ 'options.advice.active': {
511
+ name: 'Advice Active (triggered)',
512
+ if: {arg: 'hasAdvice', truthy: true},
513
+ table: {category: 'Advice'},
514
+ },
439
515
  'options.src.interaction': {
440
516
  name: 'Source Interaction',
441
517
  control: {type: 'select'},
442
518
  options: Object.values(ReadoutSourceInteraction),
443
519
  table: {category: 'Source'},
444
520
  },
521
+ 'options.src.state': {
522
+ name: 'Source State',
523
+ description:
524
+ 'Applies to the plain source only — picker / flyout buttons carry no state chip.',
525
+ control: {type: 'select'},
526
+ options: Object.values(ReadoutSourceState),
527
+ table: {category: 'Source'},
528
+ },
529
+ 'options.src.deviation': {
530
+ name: 'Source Deviation (Δ)',
531
+ control: {type: 'number'},
532
+ table: {category: 'Source'},
533
+ },
534
+ 'options.src.spaceReserver': {
535
+ name: 'Source Space Reserver',
536
+ control: {type: 'text'},
537
+ table: {category: 'Source'},
538
+ },
445
539
  'options.unit.spaceReserver': {
446
540
  name: 'Unit Space Reserver',
447
541
  control: {type: 'text'},
@@ -464,8 +558,11 @@ const SIZES = [
464
558
  const INTERACTIONS = Object.values(ReadoutSetpointInteraction);
465
559
 
466
560
  /** One row per size; columns = interaction modes × value≠/=setpoint. */
467
- function interactionMatrix(direction: ReadoutDirection): ShowcaseCase[] {
468
- return SIZES.flatMap((size) =>
561
+ function interactionMatrix(
562
+ direction: ReadoutDirection,
563
+ sizes: readonly ReadoutSize[] = SIZES
564
+ ): ShowcaseCase[] {
565
+ return sizes.flatMap((size) =>
469
566
  INTERACTIONS.flatMap((interaction) =>
470
567
  [123, 120].map((value) => ({
471
568
  label: `${size} / ${interaction} / ${
@@ -504,37 +601,42 @@ export const Vertical: Story = {
504
601
  },
505
602
  {
506
603
  title: 'Vertical — Setpoint Interactions',
507
- columns: 6,
604
+ columns: 8,
508
605
  cases: interactionMatrix(ReadoutDirection.vertical),
509
606
  },
510
607
  ]),
511
608
  };
512
609
 
610
+ /**
611
+ * The horizontal arrangement exists in the large tier only (Figma 6.1: it
612
+ * relies on the label+unit stack aligning with the L-size value caps — label
613
+ * cap top and unit cap bottom line up with the value's cap edges). `size` is
614
+ * ignored when `direction` is `horizontal`.
615
+ */
513
616
  export const Horizontal: Story = {
514
617
  render: () =>
515
618
  renderShowcase([
516
619
  {
517
- title: 'Horizontal Sizes × Priority',
620
+ title: 'Horizontal (large Only) Priority',
518
621
  columns: 2,
519
- cases: SIZES.flatMap((size) =>
520
- [ReadoutPriority.regular, ReadoutPriority.enhanced].map(
521
- (priority) => ({
522
- label: `${size} / ${priority}`,
523
- config: {
524
- options: {
525
- size,
526
- priority,
527
- direction: ReadoutDirection.horizontal,
528
- },
622
+ cases: [ReadoutPriority.regular, ReadoutPriority.enhanced].map(
623
+ (priority) => ({
624
+ label: `large / ${priority}`,
625
+ config: {
626
+ options: {
627
+ priority,
628
+ direction: ReadoutDirection.horizontal,
529
629
  },
530
- })
531
- )
630
+ },
631
+ })
532
632
  ),
533
633
  },
534
634
  {
535
635
  title: 'Horizontal — Setpoint Interactions',
536
- columns: 6,
537
- cases: interactionMatrix(ReadoutDirection.horizontal),
636
+ columns: 8,
637
+ cases: interactionMatrix(ReadoutDirection.horizontal, [
638
+ ReadoutSize.large,
639
+ ]),
538
640
  },
539
641
  ]),
540
642
  };
@@ -921,6 +1023,35 @@ export const SetpointPopUpAnimated: Story = {
921
1023
  tags: ['skip-test'],
922
1024
  };
923
1025
 
1026
+ export const SetpointAlwaysVisibleAnimated: Story = {
1027
+ ...animatedInteractionStory({
1028
+ name: 'Always-Visible (Animated)',
1029
+ interaction: ReadoutSetpointInteraction.alwaysVisible,
1030
+ intro:
1031
+ 'Always-visible (the default, Figma "Primary secondary"): the setpoint stays at the secondary size in both states — nothing resizes or hides as the value reaches the setpoint (120); only the touch highlight shows during the sweep. Contrast with flip-flop / pop-up. Press Play, or use the buttons.',
1032
+ awayLabel: 'Away (124)',
1033
+ reachLabel: 'Reach setpoint (120)',
1034
+ }),
1035
+ tags: ['skip-test'],
1036
+ };
1037
+
1038
+ export const SetpointEqualSizeAnimated: Story = {
1039
+ ...animatedInteractionStory({
1040
+ name: 'Equal-Size (Animated)',
1041
+ interaction: ReadoutSetpointInteraction.equalSize,
1042
+ intro:
1043
+ 'Equal-size (Figma 6.1 "Equal size"): value and setpoint share the primary size in both states — like always-visible, nothing resizes or hides as the value reaches the setpoint (120); only the touch highlight shows during the sweep. Press Play, or use the buttons.',
1044
+ awayLabel: 'Away (124)',
1045
+ reachLabel: 'Reach setpoint (120)',
1046
+ }),
1047
+ tags: ['skip-test'],
1048
+ };
1049
+
1050
+ /**
1051
+ * The degree glyph renders on the ACTUAL VALUE only — setpoint and advice
1052
+ * blocks never carry one (Figma 6.1 review: the unit is written once for the
1053
+ * readout, and the degree follows the same rule).
1054
+ */
924
1055
  export const Degree: Story = {
925
1056
  render: () =>
926
1057
  renderShowcase([
@@ -950,6 +1081,35 @@ export const Degree: Story = {
950
1081
  },
951
1082
  ],
952
1083
  },
1084
+ {
1085
+ title: 'Degree on The Actual Value Only (setpoint / Advice Carry None)',
1086
+ columns: 2,
1087
+ cases: [
1088
+ {
1089
+ label: 'vertical / setpoint + advice',
1090
+ config: {
1091
+ label: 'HDG',
1092
+ unit: 'DEG',
1093
+ hasSetpoint: true,
1094
+ hasAdvice: true,
1095
+ options: {hasDegree: true},
1096
+ },
1097
+ },
1098
+ {
1099
+ label: 'horizontal / setpoint + advice',
1100
+ config: {
1101
+ label: 'HDG',
1102
+ unit: 'DEG',
1103
+ hasSetpoint: true,
1104
+ hasAdvice: true,
1105
+ options: {
1106
+ hasDegree: true,
1107
+ direction: ReadoutDirection.horizontal,
1108
+ },
1109
+ },
1110
+ },
1111
+ ],
1112
+ },
953
1113
  ]),
954
1114
  };
955
1115
 
@@ -1451,3 +1611,360 @@ export const TestCases: Story = {
1451
1611
  </div>`;
1452
1612
  },
1453
1613
  };
1614
+
1615
+ /**
1616
+ * Figma 6.1 "Equal size": value and setpoint always share the primary size —
1617
+ * the missing fourth mode beside primary-secondary (`always-visible`),
1618
+ * flip-flop and pop-up. Static in both states; only flip-flop / pop-up
1619
+ * resize or hide at runtime — see the `SetpointEqualSizeAnimated` story for
1620
+ * the in-motion contrast. `touching` keeps the setpoint at the primary size
1621
+ * (it only adds the SemiBold focus weight and marker highlight), and the
1622
+ * marker arrow follows the block's rendered size, so an equal-size setpoint
1623
+ * carries the large arrow. The sizing rule lives in `readoutSetpointSize()`
1624
+ * (`readout-shared.ts`, unit-tested), shared with `obc-readout-list-item` /
1625
+ * `obc-readout`.
1626
+ */
1627
+ export const SetpointEqualSize: Story = {
1628
+ render: () =>
1629
+ renderShowcase([
1630
+ {
1631
+ title: 'Equal Size — Off/At Setpoint × Direction',
1632
+ columns: 2,
1633
+ cases: [ReadoutDirection.vertical, ReadoutDirection.horizontal].flatMap(
1634
+ (direction) =>
1635
+ [123, 120].map((value) => ({
1636
+ label: `${direction} / ${value === 120 ? 'at' : 'off'} setpoint`,
1637
+ config: {
1638
+ value,
1639
+ hasSetpoint: true,
1640
+ setpoint: 120,
1641
+ options: {
1642
+ size: ReadoutSize.large,
1643
+ priority: ReadoutPriority.enhanced,
1644
+ direction,
1645
+ setpoint: {
1646
+ interaction: ReadoutSetpointInteraction.equalSize,
1647
+ },
1648
+ },
1649
+ },
1650
+ }))
1651
+ ),
1652
+ },
1653
+ ]),
1654
+ };
1655
+
1656
+ /**
1657
+ * Label ("readout-block-title") options — Figma 6.1. Large readouts default
1658
+ * the label to textbox `s` (a scannable label should not sit at the smallest
1659
+ * size); `labelOptions.size` opts a tight layout back down to `xs`, and
1660
+ * `labelOptions.spaceReserver` aligns ragged labels across stacked readouts
1661
+ * (the legacy `AlignMultiple` gap). The label is SemiBold only on enhanced
1662
+ * readouts.
1663
+ */
1664
+ export const LabelOptions: Story = {
1665
+ render: () =>
1666
+ renderShowcase([
1667
+ {
1668
+ title: 'Label size — tier default vs explicit',
1669
+ columns: 3,
1670
+ cases: [
1671
+ {
1672
+ label: 'large / default (s)',
1673
+ config: {options: {size: ReadoutSize.large}},
1674
+ },
1675
+ {
1676
+ label: 'large / xs override',
1677
+ config: {
1678
+ options: {
1679
+ size: ReadoutSize.large,
1680
+ label: {size: ObcTextboxSize.xs},
1681
+ },
1682
+ },
1683
+ },
1684
+ {
1685
+ label: 'small / default (xs)',
1686
+ config: {options: {size: ReadoutSize.small}},
1687
+ },
1688
+ ],
1689
+ },
1690
+ {
1691
+ title: 'Label weight — regular vs enhanced priority',
1692
+ columns: 2,
1693
+ cases: [
1694
+ {
1695
+ label: 'regular (label regular)',
1696
+ config: {
1697
+ options: {
1698
+ size: ReadoutSize.large,
1699
+ priority: ReadoutPriority.regular,
1700
+ },
1701
+ },
1702
+ },
1703
+ {
1704
+ label: 'enhanced (label semibold)',
1705
+ config: {
1706
+ options: {
1707
+ size: ReadoutSize.large,
1708
+ priority: ReadoutPriority.enhanced,
1709
+ },
1710
+ },
1711
+ },
1712
+ ],
1713
+ },
1714
+ {
1715
+ title: 'Label reserver — stacked readouts align',
1716
+ columns: 1,
1717
+ cases: [
1718
+ {
1719
+ label: 'labelOptions.spaceReserver: "DEPTH"',
1720
+ config: {
1721
+ label: 'SOG',
1722
+ options: {
1723
+ size: ReadoutSize.large,
1724
+ stacking: ReadoutStacking.stacked,
1725
+ label: {spaceReserver: 'DEPTH'},
1726
+ },
1727
+ },
1728
+ },
1729
+ ],
1730
+ },
1731
+ ]),
1732
+ };
1733
+
1734
+ /** The `leading-icon` slot before the label/unit meta zone (Figma `has Leading icon`). */
1735
+ export const LeadingIcon: Story = {
1736
+ render: () =>
1737
+ renderShowcase([
1738
+ {
1739
+ title: 'Leading icon × size',
1740
+ columns: 3,
1741
+ cases: SIZES.map((size) => ({
1742
+ label: size,
1743
+ config: {options: {size, hasLeadingIcon: true}},
1744
+ })),
1745
+ },
1746
+ {
1747
+ title: 'Leading icon — horizontal',
1748
+ columns: 1,
1749
+ cases: [
1750
+ {
1751
+ label: 'horizontal / large',
1752
+ config: {
1753
+ options: {
1754
+ size: ReadoutSize.large,
1755
+ direction: ReadoutDirection.horizontal,
1756
+ hasLeadingIcon: true,
1757
+ },
1758
+ },
1759
+ },
1760
+ ],
1761
+ },
1762
+ ]),
1763
+ };
1764
+
1765
+ /**
1766
+ * Advice semantic categories (Figma 6.1 Readout-block-advice): resting
1767
+ * categories carry a neutral outline marker; `active` swaps in the filled /
1768
+ * status icon and the triggered text styling. See the Readout Block
1769
+ * `AdviceCategories` story for the full per-block matrix.
1770
+ */
1771
+ export const AdviceCategories: Story = {
1772
+ render: () =>
1773
+ renderShowcase([
1774
+ {
1775
+ title: 'Advice categories — resting vs active',
1776
+ columns: 7,
1777
+ cases: Object.values(ReadoutAdviceCategory).flatMap((category) =>
1778
+ [false, true].map((active) => ({
1779
+ label: `${category}${active ? ' / active' : ''}`,
1780
+ config: {
1781
+ hasAdvice: true,
1782
+ options: {
1783
+ size: ReadoutSize.large,
1784
+ advice: {category, active},
1785
+ },
1786
+ },
1787
+ }))
1788
+ ),
1789
+ },
1790
+ ]),
1791
+ };
1792
+
1793
+ /**
1794
+ * Source states + deviation (Figma 6.1 Readout-block-source): `enhanced`
1795
+ * renders the amplified chip, `caution` / `warning` the alert chip, and
1796
+ * `deviation` adds the Δ line under the source name. The chips use `outline`
1797
+ * so a live state change never shifts the layout.
1798
+ */
1799
+ export const SourceStates: Story = {
1800
+ render: () =>
1801
+ renderShowcase([
1802
+ {
1803
+ title: 'Source state × deviation',
1804
+ columns: 4,
1805
+ cases: Object.values(ReadoutSourceState).flatMap((state) =>
1806
+ [undefined, 0.5].map((deviation) => ({
1807
+ label: `${state}${deviation !== undefined ? ' / Δ' : ''}`,
1808
+ config: {
1809
+ src: 'GPS 1',
1810
+ options: {
1811
+ size: ReadoutSize.large,
1812
+ src: {state, deviation},
1813
+ },
1814
+ },
1815
+ }))
1816
+ ),
1817
+ },
1818
+ ]),
1819
+ };
1820
+
1821
+ /**
1822
+ * TODO(designer): open question from the 6.1 review — while a pop-up setpoint
1823
+ * is hidden at-setpoint, should the reading keep a setpoint arrow so it still
1824
+ * reads as "a value you are in control of"? The existing `value-icon` slot
1825
+ * already expresses that today, demonstrated here; no new API until decided.
1826
+ */
1827
+ export const SetpointPopUpWithValueArrow: Story = {
1828
+ render: () => html`
1829
+ <style>
1830
+ ${showcaseStyle}
1831
+ </style>
1832
+ <div class="ro-sections">
1833
+ <p class="ro-note">
1834
+ Pop-up at setpoint, with an <code>obi-input-right</code> slotted into
1835
+ <code>value-icon</code> so the collapsed reading keeps the in-control
1836
+ marker.
1837
+ </p>
1838
+ <obc-readout
1839
+ .label=${'HDG'}
1840
+ .unit=${'/min'}
1841
+ .src=${'Source'}
1842
+ .value=${10}
1843
+ .hasSetpoint=${true}
1844
+ .setpoint=${10}
1845
+ .size=${ReadoutSize.large}
1846
+ .priority=${ReadoutPriority.enhanced}
1847
+ .setpointOptions=${{interaction: ReadoutSetpointInteraction.popUp}}
1848
+ .valueOptions=${{hasIcon: true}}
1849
+ >
1850
+ <obi-input-right slot="value-icon"></obi-input-right>
1851
+ </obc-readout>
1852
+ </div>
1853
+ `,
1854
+ };
1855
+
1856
+ // ---------------------------------------------------------------------------
1857
+ // Figma 6.1 bird's-eye matrices — static expansions of the design file's
1858
+ // component sheets (parent node 46596-102878), so every combination can be
1859
+ // reviewed at a glance without touching controls:
1860
+ // - Readout-vertical-trailing-title (46390-197995): Size × Priority ×
1861
+ // Value priority × State × has Degree — split into one story per
1862
+ // interaction below so each snapshot stays a reviewable size. The
1863
+ // `has Leading icon` axis is covered by the LeadingIcon story, and the
1864
+ // Readout-vertical-block sheet (46551-303919) differs only in title sizing
1865
+ // (tracked by the medium-tier label TODO); the leading-title sheet
1866
+ // (46551-341292) is marked WIP in Figma and deliberately not expanded.
1867
+ // - Readout-horizontal (46471-75814): large-only, one story. Its
1868
+ // leading-label stacking column is obc-readout-list-item's job (see the
1869
+ // Readout List Item FigmaMatrix story).
1870
+ // ---------------------------------------------------------------------------
1871
+
1872
+ const MATRIX_PRIORITIES = [
1873
+ ReadoutPriority.regular,
1874
+ ReadoutPriority.enhanced,
1875
+ ] as const;
1876
+
1877
+ /** The composite sheets' State axis (At/Not-at setpoint, Low integrity, Invalid). */
1878
+ const MATRIX_STATES: {
1879
+ key: string;
1880
+ value: number;
1881
+ dataQuality?: ReadoutDataQuality;
1882
+ }[] = [
1883
+ {key: 'off setpoint', value: 123},
1884
+ {key: 'at setpoint', value: 120},
1885
+ {
1886
+ key: 'low-integrity',
1887
+ value: 123,
1888
+ dataQuality: ReadoutDataQuality.lowIntegrity,
1889
+ },
1890
+ {key: 'invalid', value: 123, dataQuality: ReadoutDataQuality.invalid},
1891
+ ];
1892
+
1893
+ function verticalMatrixStory(
1894
+ interaction: ReadoutSetpointInteraction
1895
+ ): Story['render'] {
1896
+ return () =>
1897
+ renderShowcase(
1898
+ MATRIX_STATES.map((state) => ({
1899
+ title: `${interaction} — ${state.key}`,
1900
+ columns: 4,
1901
+ cases: SIZES.flatMap((size) =>
1902
+ MATRIX_PRIORITIES.flatMap((priority) =>
1903
+ [false, true].map((hasDegree) => ({
1904
+ label: `${size} / ${priority}${hasDegree ? ' / °' : ''}`,
1905
+ config: {
1906
+ value: state.value,
1907
+ hasSetpoint: true,
1908
+ setpoint: 120,
1909
+ hasAdvice: true,
1910
+ options: {
1911
+ size,
1912
+ priority,
1913
+ hasDegree,
1914
+ dataQuality: state.dataQuality,
1915
+ setpoint: {interaction},
1916
+ },
1917
+ },
1918
+ }))
1919
+ )
1920
+ ),
1921
+ }))
1922
+ );
1923
+ }
1924
+
1925
+ export const FigmaMatrixVerticalAlwaysVisible: Story = {
1926
+ render: verticalMatrixStory(ReadoutSetpointInteraction.alwaysVisible),
1927
+ };
1928
+
1929
+ export const FigmaMatrixVerticalEqualSize: Story = {
1930
+ render: verticalMatrixStory(ReadoutSetpointInteraction.equalSize),
1931
+ };
1932
+
1933
+ export const FigmaMatrixVerticalFlipFlop: Story = {
1934
+ render: verticalMatrixStory(ReadoutSetpointInteraction.flipFlop),
1935
+ };
1936
+
1937
+ export const FigmaMatrixVerticalPopUp: Story = {
1938
+ render: verticalMatrixStory(ReadoutSetpointInteraction.popUp),
1939
+ };
1940
+
1941
+ export const FigmaMatrixHorizontal: Story = {
1942
+ render: () =>
1943
+ renderShowcase(
1944
+ INTERACTIONS.map((interaction) => ({
1945
+ title: `${interaction}`,
1946
+ columns: 4,
1947
+ cases: MATRIX_STATES.flatMap((state) =>
1948
+ MATRIX_PRIORITIES.flatMap((priority) =>
1949
+ [false, true].map((hasDegree) => ({
1950
+ label: `${state.key} / ${priority}${hasDegree ? ' / °' : ''}`,
1951
+ config: {
1952
+ value: state.value,
1953
+ hasSetpoint: true,
1954
+ setpoint: 120,
1955
+ hasAdvice: true,
1956
+ src: 'GPS 1',
1957
+ options: {
1958
+ direction: ReadoutDirection.horizontal,
1959
+ priority,
1960
+ hasDegree,
1961
+ dataQuality: state.dataQuality,
1962
+ setpoint: {interaction},
1963
+ },
1964
+ },
1965
+ }))
1966
+ )
1967
+ ),
1968
+ }))
1969
+ ),
1970
+ };