@orange-halo/components 2.1.0 → 3.0.0

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 (130) hide show
  1. package/custom-elements.json +968 -362
  2. package/dist/base.d.ts.map +1 -1
  3. package/dist/base.js +1 -2
  4. package/dist/base.js.map +1 -1
  5. package/dist/index.d.ts +4 -8
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -4
  8. package/dist/index.js.map +1 -1
  9. package/dist/oh-accordion.d.ts +53 -8
  10. package/dist/oh-accordion.d.ts.map +1 -1
  11. package/dist/oh-accordion.js +99 -29
  12. package/dist/oh-accordion.js.map +1 -1
  13. package/dist/oh-alert.d.ts +26 -8
  14. package/dist/oh-alert.d.ts.map +1 -1
  15. package/dist/oh-alert.js +96 -36
  16. package/dist/oh-alert.js.map +1 -1
  17. package/dist/oh-badge.d.ts +32 -13
  18. package/dist/oh-badge.d.ts.map +1 -1
  19. package/dist/oh-badge.js +38 -19
  20. package/dist/oh-badge.js.map +1 -1
  21. package/dist/oh-breadcrumb.d.ts +39 -12
  22. package/dist/oh-breadcrumb.d.ts.map +1 -1
  23. package/dist/oh-breadcrumb.js +66 -25
  24. package/dist/oh-breadcrumb.js.map +1 -1
  25. package/dist/oh-button.d.ts +49 -23
  26. package/dist/oh-button.d.ts.map +1 -1
  27. package/dist/oh-button.figma.js +1 -3
  28. package/dist/oh-button.figma.js.map +1 -1
  29. package/dist/oh-button.js +133 -82
  30. package/dist/oh-button.js.map +1 -1
  31. package/dist/oh-card.d.ts +47 -19
  32. package/dist/oh-card.d.ts.map +1 -1
  33. package/dist/oh-card.js +143 -96
  34. package/dist/oh-card.js.map +1 -1
  35. package/dist/oh-checkbox-group.d.ts +56 -12
  36. package/dist/oh-checkbox-group.d.ts.map +1 -1
  37. package/dist/oh-checkbox-group.figma.js +4 -6
  38. package/dist/oh-checkbox-group.figma.js.map +1 -1
  39. package/dist/oh-checkbox-group.js +114 -49
  40. package/dist/oh-checkbox-group.js.map +1 -1
  41. package/dist/oh-checkbox.d.ts +54 -17
  42. package/dist/oh-checkbox.d.ts.map +1 -1
  43. package/dist/oh-checkbox.js +117 -60
  44. package/dist/oh-checkbox.js.map +1 -1
  45. package/dist/oh-cluster.d.ts +19 -14
  46. package/dist/oh-cluster.d.ts.map +1 -1
  47. package/dist/oh-cluster.js +19 -19
  48. package/dist/oh-cluster.js.map +1 -1
  49. package/dist/oh-container.d.ts +19 -16
  50. package/dist/oh-container.d.ts.map +1 -1
  51. package/dist/oh-container.js +21 -23
  52. package/dist/oh-container.js.map +1 -1
  53. package/dist/oh-drawer.d.ts +28 -11
  54. package/dist/oh-drawer.d.ts.map +1 -1
  55. package/dist/oh-drawer.js +52 -24
  56. package/dist/oh-drawer.js.map +1 -1
  57. package/dist/oh-footer.d.ts +37 -18
  58. package/dist/oh-footer.d.ts.map +1 -1
  59. package/dist/oh-footer.js +38 -19
  60. package/dist/oh-footer.js.map +1 -1
  61. package/dist/oh-grid.d.ts +27 -14
  62. package/dist/oh-grid.d.ts.map +1 -1
  63. package/dist/oh-grid.js +28 -21
  64. package/dist/oh-grid.js.map +1 -1
  65. package/dist/oh-icon.d.ts +21 -5
  66. package/dist/oh-icon.d.ts.map +1 -1
  67. package/dist/oh-icon.js +27 -9
  68. package/dist/oh-icon.js.map +1 -1
  69. package/dist/oh-input.d.ts +110 -21
  70. package/dist/oh-input.d.ts.map +1 -1
  71. package/dist/oh-input.figma.js +2 -2
  72. package/dist/oh-input.figma.js.map +1 -1
  73. package/dist/oh-input.js +227 -71
  74. package/dist/oh-input.js.map +1 -1
  75. package/dist/oh-link.d.ts +42 -19
  76. package/dist/oh-link.d.ts.map +1 -1
  77. package/dist/oh-link.js +83 -38
  78. package/dist/oh-link.js.map +1 -1
  79. package/dist/oh-modal.d.ts +35 -6
  80. package/dist/oh-modal.d.ts.map +1 -1
  81. package/dist/oh-modal.js +90 -39
  82. package/dist/oh-modal.js.map +1 -1
  83. package/dist/oh-navbar.d.ts +42 -19
  84. package/dist/oh-navbar.d.ts.map +1 -1
  85. package/dist/oh-navbar.js +44 -20
  86. package/dist/oh-navbar.js.map +1 -1
  87. package/dist/oh-progress.d.ts +26 -7
  88. package/dist/oh-progress.d.ts.map +1 -1
  89. package/dist/oh-progress.js +35 -14
  90. package/dist/oh-progress.js.map +1 -1
  91. package/dist/oh-radio-group.d.ts +55 -13
  92. package/dist/oh-radio-group.d.ts.map +1 -1
  93. package/dist/oh-radio-group.figma.js +2 -2
  94. package/dist/oh-radio-group.figma.js.map +1 -1
  95. package/dist/oh-radio-group.js +122 -51
  96. package/dist/oh-radio-group.js.map +1 -1
  97. package/dist/oh-radio.d.ts +55 -17
  98. package/dist/oh-radio.d.ts.map +1 -1
  99. package/dist/oh-radio.js +120 -61
  100. package/dist/oh-radio.js.map +1 -1
  101. package/dist/oh-select.d.ts +59 -16
  102. package/dist/oh-select.d.ts.map +1 -1
  103. package/dist/oh-select.js +141 -69
  104. package/dist/oh-select.js.map +1 -1
  105. package/dist/oh-skip-link.d.ts +33 -15
  106. package/dist/oh-skip-link.d.ts.map +1 -1
  107. package/dist/oh-skip-link.js +43 -32
  108. package/dist/oh-skip-link.js.map +1 -1
  109. package/dist/oh-stack.d.ts +17 -14
  110. package/dist/oh-stack.d.ts.map +1 -1
  111. package/dist/oh-stack.js +18 -20
  112. package/dist/oh-stack.js.map +1 -1
  113. package/dist/oh-tabs.d.ts +55 -30
  114. package/dist/oh-tabs.d.ts.map +1 -1
  115. package/dist/oh-tabs.js +100 -76
  116. package/dist/oh-tabs.js.map +1 -1
  117. package/dist/oh-textarea.d.ts +129 -0
  118. package/dist/oh-textarea.d.ts.map +1 -0
  119. package/dist/oh-textarea.js +401 -0
  120. package/dist/oh-textarea.js.map +1 -0
  121. package/dist/oh-toggle.d.ts +45 -12
  122. package/dist/oh-toggle.d.ts.map +1 -1
  123. package/dist/oh-toggle.js +84 -34
  124. package/dist/oh-toggle.js.map +1 -1
  125. package/dist/styles/typography.js +1 -1
  126. package/package.json +7 -7
  127. package/dist/oh-hero.d.ts +0 -33
  128. package/dist/oh-hero.d.ts.map +0 -1
  129. package/dist/oh-hero.js +0 -246
  130. package/dist/oh-hero.js.map +0 -1
package/dist/oh-card.js CHANGED
@@ -1,28 +1,25 @@
1
1
  /**
2
2
  * oh-card
3
3
  *
4
- * A tactile, raised surface with visible frame.
5
- * Designed with the same layered construction as oh-button.
4
+ * A surface that groups related content, with optional header and footer.
6
5
  *
7
6
  * DESIGN DECISIONS:
8
7
  * ─────────────────
9
- * 1. LAYERED CONSTRUCTION
10
- * - Surface: subtle gradient for depth
11
- * - Border: visible warm edge
12
- * - Inner highlight: sunlit top edge
13
- * - Outer shadow: soft, slightly offset
8
+ * 1. THREE PARTS: header, content and footer are separate parts so each can
9
+ * be styled.
14
10
  *
15
- * 2. RETRO-MODERN DETAILS
16
- * - Warm, tactile shadows (never harsh)
17
- * - Visible border that warms on hover
18
- * - Subtle gradient background
11
+ * 2. VARIANTS BY ROLE, LOOK FROM TOKENS
12
+ * - default, featured (the one card that should stand out), subtle (quieter)
13
+ * - Each variant's surface, border and shadow come from its own
14
+ * card.variant.<variant>.* tokens and an elevation.surface.* token, so a brand
15
+ * changes the look without touching this file.
19
16
  *
20
- * 3. VARIANTS
21
- * - Raised: default with layered depth
22
- * - Sunlit: featured with warm gradient
23
- * - Flat: border-only, minimal shadow
17
+ * 3. INTERACTIVE MODE: role="button" on the inner card, Enter/Space and
18
+ * click fire oh-click. Focus is a solid focus.ring* outline; any
19
+ * focus.glowColor shadow is decoration only.
24
20
  *
25
- * @element oh-card
21
+ * 4. CONTRAST: card surfaces remap color.text.link to color.text.linkRaised
22
+ * so links inside keep 4.5:1.
26
23
  */
27
24
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
28
25
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -31,135 +28,151 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
31
28
  return c > 3 && r && Object.defineProperty(target, key, r), r;
32
29
  };
33
30
  import { html, css, nothing } from "lit";
34
- import { customElement, property } from "lit/decorators.js";
31
+ import { customElement, property, state } from "lit/decorators.js";
35
32
  import { OhElement, baseStyles } from "./base.js";
33
+ /**
34
+ * A surface that groups related content (a summary, a teaser, a settings group), with optional header and footer.
35
+ *
36
+ * **Use it for** a self-contained block of content among others, or a whole-card link to more detail (`interactive`). **Don't use it for** page layout or spacing alone (use `oh-stack`, `oh-grid`); for a message that needs attention (use `oh-alert`); or for a single action (use `oh-button`).
37
+ *
38
+ * **Variants:** `default`, the standard card; `featured`, for the one card that should stand out; `subtle`, a quieter card, for dense layouts. **Padding:** `none`, `sm`, `md` (default), `lg` set the content padding (and the header and footer padding to match). With `none`, the content sets its own padding.
39
+ *
40
+ * **States (interactive only):** rest, hover, active (pressed) and focus-visible.
41
+ *
42
+ * **Behaviour:** with `interactive`, the card is focusable (`tabindex="0"`), has `role="button"`, and fires `oh-click` on click, Enter or Space. Hover movement is turned off under `prefers-reduced-motion`.
43
+ *
44
+ * **Accessibility:** focus shows a solid outline (`focus.ringWidth`, `focus.ringColor`, `focus.ringOffset`). Links inside use `color.text.linkRaised`, which keeps contrast on card surfaces. An interactive card is one button: give it a short heading so its accessible name makes sense, and don't put other buttons or links inside it.
45
+ *
46
+ * **Look:** set by tokens. `card.variant.<variant>.gradientStart`/`gradientMid`/`gradientEnd` set each variant's surface, `card.variant.<variant>.borderColor` and `card.base.borderWidth` its border, `shape.large` the corners, and `elevation.surface.default`/`subtle`/`featured` its shadow. Hover uses `color.border.strong` (or `card.variant.featured.borderColorHover`), `elevation.surface.hover`/`featuredHover` and `lift.hoverMd`/`lift.hoverSm`; active uses `elevation.surface.active`; focus sets the border to `color.brand.primary` and adds `focus.glowColor` with `elevation.surface.focus`/`featuredFocus`. Header and footer dividers use `card.section.dividerColor` and `stroke.divider`; the header surface uses `card.section.headerGradientStart`/`End`, the footer `color.surface.raised` to `color.surface.inset`; motion uses `motion.gentle` and `motion.easeGentle`.
47
+ *
48
+ * @element oh-card
49
+ * @slot - The card's main content.
50
+ * @slot header - Optional header, above the content, with a divider below. Not drawn when nothing is slotted.
51
+ * @slot footer - Optional footer, below the content, with a divider above (e.g. actions). Not drawn when nothing is slotted.
52
+ * @csspart card - The card surface (the button when `interactive`).
53
+ * @csspart header - The header wrapper.
54
+ * @csspart content - The content wrapper.
55
+ * @csspart footer - The footer wrapper.
56
+ * @fires oh-click - When an `interactive` card is clicked or activated with Enter or Space. `detail.originalEvent` holds the source event.
57
+ */
36
58
  let OhCard = class OhCard extends OhElement {
37
59
  constructor() {
38
60
  super(...arguments);
39
- this.variant = "raised";
61
+ this.variant = "default";
40
62
  this.interactive = false;
41
63
  this.padding = "md";
64
+ this._hasHeader = false;
65
+ this._hasFooter = false;
42
66
  }
43
67
  static { this.styles = [
44
68
  baseStyles,
45
69
  css `
46
70
  /* ═══════════════════════════════════════════════════════════════
47
- ORANGE HALO CARD
48
- Tactile • Layered • Warm • Personal
71
+ CARD
72
+ The look comes from card.*, elevation.*, shape.* and lift.* tokens
49
73
  ═══════════════════════════════════════════════════════════════ */
50
74
 
51
75
  :host {
52
76
  display: block;
53
- /* Card surfaces fade to linen, where text.link falls below 4.5:1 */
77
+ /* On card surfaces text.link can fall below 4.5:1; linkRaised keeps it */
54
78
  --oh-color-text-link: var(--oh-color-text-link-raised);
55
79
  }
56
80
 
57
81
  /* ═══════════════════════════════════════════════════════════════
58
82
  BASE CARD
59
- Layered construction — feels like a physical object
60
83
  ═══════════════════════════════════════════════════════════════ */
61
84
 
62
85
  .card {
63
- /* Shape: friendly rounded corners */
64
86
  border-radius: var(--oh-shape-large);
65
87
  overflow: hidden;
66
-
67
- transition:
88
+
89
+ transition:
68
90
  transform var(--oh-motion-gentle) var(--oh-motion-ease-gentle),
69
91
  box-shadow var(--oh-motion-gentle) var(--oh-motion-ease-gentle),
70
92
  border-color var(--oh-motion-gentle) var(--oh-motion-ease-gentle);
71
93
  }
72
94
 
73
95
  /* ═══════════════════════════════════════════════════════════════
74
- RAISED VARIANT (Default)
75
- Subtle gradient surface with layered shadows
96
+ DEFAULT VARIANT
76
97
  ═══════════════════════════════════════════════════════════════ */
77
98
 
78
- .card--raised {
79
- /* Surface: subtle gradient for depth */
99
+ .card--default {
100
+ /* Surface: card.default gradient tokens */
80
101
  background: linear-gradient(
81
102
  180deg,
82
- var(--oh-card-raised-gradient-start) 0%,
83
- var(--oh-card-raised-gradient-mid) 40%,
84
- var(--oh-card-raised-gradient-end) 100%
103
+ var(--oh-card-variant-default-gradient-start) 0%,
104
+ var(--oh-card-variant-default-gradient-mid) 40%,
105
+ var(--oh-card-variant-default-gradient-end) 100%
85
106
  );
86
-
87
- /* Border: visible warm edge */
88
- border: var(--oh-card-base-border-width) solid var(--oh-card-raised-border-color);
89
-
90
- /* Layered shadows:
91
- 1. Outer: soft, offset shadow (tactile)
92
- 2. Deeper: ambient depth
93
- 3. Inner top: sunlit highlight */
94
- box-shadow: var(--oh-elevation-surface-raised);
107
+
108
+ border: var(--oh-card-base-border-width) solid
109
+ var(--oh-card-variant-default-border-color);
110
+
111
+ box-shadow: var(--oh-elevation-surface-default);
95
112
  }
96
113
 
97
114
  /* ═══════════════════════════════════════════════════════════════
98
- SUNLIT VARIANT
99
- Featured content with warm gradient and glow
115
+ FEATURED VARIANT
116
+ The one card that should stand out
100
117
  ═══════════════════════════════════════════════════════════════ */
101
118
 
102
- .card--sunlit {
103
- /* Surface: warm sunlit gradient */
119
+ .card--featured {
104
120
  background: linear-gradient(
105
121
  180deg,
106
- var(--oh-card-sunlit-gradient-start) 0%,
107
- var(--oh-card-sunlit-gradient-mid) 30%,
108
- var(--oh-card-sunlit-gradient-end) 100%
122
+ var(--oh-card-variant-featured-gradient-start) 0%,
123
+ var(--oh-card-variant-featured-gradient-mid) 30%,
124
+ var(--oh-card-variant-featured-gradient-end) 100%
109
125
  );
110
-
111
- /* Border: warm tinted edge */
112
- border: var(--oh-card-base-border-width) solid var(--oh-card-sunlit-border-color);
113
-
114
- /* Layered shadows with warm glow */
126
+
127
+ border: var(--oh-card-base-border-width) solid
128
+ var(--oh-card-variant-featured-border-color);
129
+
115
130
  box-shadow: var(--oh-elevation-surface-featured);
116
131
  }
117
132
 
118
133
  /* ═══════════════════════════════════════════════════════════════
119
- FLAT VARIANT
120
- Border-only, minimal shadow — still layered
134
+ SUBTLE VARIANT
135
+ Quieter card: elevation.surface.subtle
121
136
  ═══════════════════════════════════════════════════════════════ */
122
137
 
123
- .card--flat {
124
- /* Surface: subtle gradient */
138
+ .card--subtle {
125
139
  background: linear-gradient(
126
140
  180deg,
127
- var(--oh-card-flat-gradient-start) 0%,
128
- var(--oh-card-flat-gradient-end) 100%
141
+ var(--oh-card-variant-subtle-gradient-start) 0%,
142
+ var(--oh-card-variant-subtle-gradient-end) 100%
129
143
  );
130
-
131
- /* Border: visible edge */
132
- border: var(--oh-card-base-border-width) solid var(--oh-card-flat-border-color);
133
-
134
- /* Minimal shadow */
135
- box-shadow: var(--oh-elevation-surface-flat);
144
+
145
+ border: var(--oh-card-base-border-width) solid
146
+ var(--oh-card-variant-subtle-border-color);
147
+
148
+ box-shadow: var(--oh-elevation-surface-subtle);
136
149
  }
137
150
 
138
151
  /* ═══════════════════════════════════════════════════════════════
139
152
  INTERACTIVE STATES
140
- Hover lifts; focus is a solid outline, with a warm glow as decoration
153
+ Focus is a solid outline; focus.glowColor is decoration only
141
154
  ═══════════════════════════════════════════════════════════════ */
142
155
 
143
156
  .card--interactive {
144
157
  cursor: pointer;
145
158
  }
146
159
 
147
- /* Hover: warm light, lifted */
148
- .card--interactive.card--raised:hover {
160
+ /* Hover: lift.hover* and elevation.surface.hover */
161
+ .card--interactive.card--default:hover {
149
162
  border-color: var(--oh-color-border-strong);
150
- box-shadow: var(--oh-elevation-surface-lifted);
163
+ box-shadow: var(--oh-elevation-surface-hover);
151
164
  transform: translateY(var(--oh-lift-hover-md));
152
165
  }
153
166
 
154
- .card--interactive.card--sunlit:hover {
155
- border-color: var(--oh-card-sunlit-border-color-hover);
156
- box-shadow: var(--oh-elevation-surface-featured-lifted);
167
+ .card--interactive.card--featured:hover {
168
+ border-color: var(--oh-card-variant-featured-border-color-hover);
169
+ box-shadow: var(--oh-elevation-surface-featured-hover);
157
170
  transform: translateY(var(--oh-lift-hover-md));
158
171
  }
159
172
 
160
- .card--interactive.card--flat:hover {
173
+ .card--interactive.card--subtle:hover {
161
174
  border-color: var(--oh-color-border-strong);
162
- box-shadow: var(--oh-elevation-surface-raised);
175
+ box-shadow: var(--oh-elevation-surface-default);
163
176
  transform: translateY(var(--oh-lift-hover-sm));
164
177
  }
165
178
 
@@ -168,8 +181,8 @@ let OhCard = class OhCard extends OhElement {
168
181
  transform: translateY(0) !important;
169
182
  }
170
183
 
171
- .card--interactive.card--raised:active {
172
- box-shadow: var(--oh-elevation-surface-pressed);
184
+ .card--interactive.card--default:active {
185
+ box-shadow: var(--oh-elevation-surface-active);
173
186
  }
174
187
 
175
188
  /* Focus: solid outline is the indicator; the glow is decoration only */
@@ -179,22 +192,32 @@ let OhCard = class OhCard extends OhElement {
179
192
  border-color: var(--oh-color-brand-primary);
180
193
  }
181
194
 
182
- .card--interactive.card--raised:focus-visible,
183
- .card--interactive.card--flat:focus-visible {
184
- box-shadow: 0 0 16px var(--oh-focus-glow-color), var(--oh-elevation-surface-focused);
195
+ .card--interactive.card--default:focus-visible,
196
+ .card--interactive.card--subtle:focus-visible {
197
+ box-shadow:
198
+ 0 0 16px var(--oh-focus-glow-color),
199
+ var(--oh-elevation-surface-focus);
185
200
  }
186
201
 
187
- .card--interactive.card--sunlit:focus-visible {
188
- box-shadow: 0 0 20px var(--oh-focus-glow-color), var(--oh-elevation-surface-featured-focused);
202
+ .card--interactive.card--featured:focus-visible {
203
+ box-shadow:
204
+ 0 0 20px var(--oh-focus-glow-color),
205
+ var(--oh-elevation-surface-featured-focus);
189
206
  }
190
207
 
191
208
  /* ═══════════════════════════════════════════════════════════════
192
209
  PADDING VARIANTS
193
210
  ═══════════════════════════════════════════════════════════════ */
194
211
 
195
- .card--padding-sm .content { padding: var(--oh-spacing-3-5); }
196
- .card--padding-md .content { padding: var(--oh-spacing-5); }
197
- .card--padding-lg .content { padding: var(--oh-spacing-7); }
212
+ .card--padding-sm .content {
213
+ padding: var(--oh-spacing-3-5);
214
+ }
215
+ .card--padding-md .content {
216
+ padding: var(--oh-spacing-5);
217
+ }
218
+ .card--padding-lg .content {
219
+ padding: var(--oh-spacing-7);
220
+ }
198
221
 
199
222
  /* ═══════════════════════════════════════════════════════════════
200
223
  SLOTS: Header, Content, Footer
@@ -202,7 +225,8 @@ let OhCard = class OhCard extends OhElement {
202
225
 
203
226
  .header {
204
227
  padding: var(--oh-spacing-4) var(--oh-spacing-5);
205
- border-bottom: var(--oh-stroke-divider) solid var(--oh-card-section-divider-color);
228
+ border-bottom: var(--oh-stroke-divider) solid
229
+ var(--oh-card-section-divider-color);
206
230
  background: linear-gradient(
207
231
  180deg,
208
232
  var(--oh-card-section-header-gradient-start) 0%,
@@ -210,15 +234,14 @@ let OhCard = class OhCard extends OhElement {
210
234
  );
211
235
  }
212
236
 
213
- .header:empty { display: none; }
214
-
215
237
  .content {
216
238
  /* Padding handled by variant */
217
239
  }
218
240
 
219
241
  .footer {
220
242
  padding: var(--oh-spacing-4) var(--oh-spacing-5);
221
- border-top: var(--oh-stroke-divider) solid var(--oh-card-section-divider-color);
243
+ border-top: var(--oh-stroke-divider) solid
244
+ var(--oh-card-section-divider-color);
222
245
  background: linear-gradient(
223
246
  180deg,
224
247
  var(--oh-color-surface-raised) 0%,
@@ -226,7 +249,12 @@ let OhCard = class OhCard extends OhElement {
226
249
  );
227
250
  }
228
251
 
229
- .footer:empty { display: none; }
252
+ /* A header or footer with nothing slotted isn't drawn (:empty can't
253
+ see slotted content, so the element tracks it) */
254
+ .header[hidden],
255
+ .footer[hidden] {
256
+ display: none;
257
+ }
230
258
 
231
259
  /* Padding adjustments for slots */
232
260
  .card--padding-sm .header,
@@ -247,13 +275,26 @@ let OhCard = class OhCard extends OhElement {
247
275
  .card {
248
276
  transition-duration: 0.01ms !important;
249
277
  }
250
-
278
+
251
279
  .card--interactive:hover {
252
280
  transform: none !important;
253
281
  }
254
282
  }
255
283
  `,
256
284
  ]; }
285
+ /** True when the slot holds an element or non-whitespace text */
286
+ _slotHasContent(slot) {
287
+ return slot
288
+ .assignedNodes({ flatten: true })
289
+ .some((node) => node.nodeType === Node.ELEMENT_NODE ||
290
+ (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()));
291
+ }
292
+ _onHeaderSlotChange(event) {
293
+ this._hasHeader = this._slotHasContent(event.target);
294
+ }
295
+ _onFooterSlotChange(event) {
296
+ this._hasFooter = this._slotHasContent(event.target);
297
+ }
257
298
  _handleClick(event) {
258
299
  if (this.interactive) {
259
300
  this.emit("oh-click", { originalEvent: event });
@@ -281,16 +322,16 @@ let OhCard = class OhCard extends OhElement {
281
322
  @click=${this._handleClick}
282
323
  @keydown=${this._handleKeyDown}
283
324
  >
284
- <div class="header" part="header">
285
- <slot name="header"></slot>
325
+ <div class="header" part="header" ?hidden=${!this._hasHeader}>
326
+ <slot name="header" @slotchange=${this._onHeaderSlotChange}></slot>
286
327
  </div>
287
-
328
+
288
329
  <div class="content" part="content">
289
330
  <slot></slot>
290
331
  </div>
291
-
292
- <div class="footer" part="footer">
293
- <slot name="footer"></slot>
332
+
333
+ <div class="footer" part="footer" ?hidden=${!this._hasFooter}>
334
+ <slot name="footer" @slotchange=${this._onFooterSlotChange}></slot>
294
335
  </div>
295
336
  </div>
296
337
  `;
@@ -305,6 +346,12 @@ __decorate([
305
346
  __decorate([
306
347
  property({ type: String, reflect: true })
307
348
  ], OhCard.prototype, "padding", void 0);
349
+ __decorate([
350
+ state()
351
+ ], OhCard.prototype, "_hasHeader", void 0);
352
+ __decorate([
353
+ state()
354
+ ], OhCard.prototype, "_hasFooter", void 0);
308
355
  OhCard = __decorate([
309
356
  customElement("oh-card")
310
357
  ], OhCard);
@@ -1 +1 @@
1
- {"version":3,"file":"oh-card.js","sourceRoot":"","sources":["../src/oh-card.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;;;;;;;AAEH,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAM3C,IAAM,MAAM,GAAZ,MAAM,MAAO,SAAQ,SAAS;IAA9B;;QAyNL,YAAO,GAAgB,QAAQ,CAAC;QAGhC,gBAAW,GAAG,KAAK,CAAC;QAGpB,YAAO,GAAgB,IAAI,CAAC;IA8C9B,CAAC;aA5QiB,WAAM,GAAG;QACvB,UAAU;QACV,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAkNF;KACF,AArNqB,CAqNpB;IAWM,YAAY,CAAC,KAAiB;QACpC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAEO,cAAc,CAAC,KAAoB;QACzC,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC;YACrE,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,IAAI,CAAC,YAAY,CAAC,KAA8B,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,MAAM;QACJ,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC5B,IAAI,EAAE,IAAI;YACV,CAAC,SAAS,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI;YAC/B,CAAC,iBAAiB,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI;YACvC,mBAAmB,EAAE,IAAI,CAAC,WAAW;SACtC,CAAC,CAAC;QAEH,OAAO,IAAI,CAAA;;;gBAGC,OAAO;eACR,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO;mBACjC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO;iBAClC,IAAI,CAAC,YAAY;mBACf,IAAI,CAAC,cAAc;;;;;;;;;;;;;;KAcjC,CAAC;IACJ,CAAC;;AAnDD;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;uCACV;AAGhC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;2CACvB;AAGpB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;uCACd;AA/NjB,MAAM;IADlB,aAAa,CAAC,SAAS,CAAC;GACZ,MAAM,CA6QlB"}
1
+ {"version":3,"file":"oh-card.js","sourceRoot":"","sources":["../src/oh-card.ts"],"names":[],"mappings":"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"}
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * oh-checkbox-group
3
3
  *
4
- * A checkbox group with proper semantics and group-level error handling.
4
+ * A checkbox group with fieldset semantics and group-level error handling.
5
5
  * For single checkboxes, use oh-checkbox directly.
6
6
  *
7
- * ACCESSIBILITY:
8
- * ─────────────
9
- * - Uses <fieldset> and <legend> for proper semantics
10
- * - Group-level error messaging with aria-describedby
11
- * - Visible focus ring (outline, not inset)
12
- * - aria-invalid for error state
13
- *
14
- * @element oh-checkbox-group
7
+ * DESIGN DECISIONS:
8
+ * ─────────────────
9
+ * - Uses <fieldset> and <legend>, so the legend names the group
10
+ * - One error for the group, after the legend and before the options,
11
+ * linked with aria-describedby; aria-invalid on the fieldset
12
+ * - Each option is a visually hidden native checkbox; focus underlines its
13
+ * label (a solid indicator)
14
+ * - The drawn boxes use the same tokens as oh-checkbox
15
15
  */
16
16
  import { type PropertyValues } from "lit";
17
17
  import { OhElement } from "./base.js";
@@ -20,15 +20,58 @@ export interface CheckboxOption {
20
20
  value: string;
21
21
  checked?: boolean;
22
22
  }
23
+ /**
24
+ * A set of related checkboxes under one legend, where any number can be
25
+ * chosen.
26
+ *
27
+ * **Use it for** choosing any number of options from a short list, such as
28
+ * interests or notification types. **Don't use it for** one standalone
29
+ * yes/no option (use `oh-checkbox`), exactly one choice (use
30
+ * `oh-radio-group`, or `oh-select` for a long list), or settings that apply
31
+ * straight away (use `oh-toggle`).
32
+ *
33
+ * **Parts:** a `label` (rendered as the fieldset's `<legend>`) and a list of `options` (`{ label, value }`).
34
+ * `value` is the array of checked option values. **States:** each option
35
+ * unchecked, checked, hover, active and focus; the group can be `required`
36
+ * (a marker on the label) and in error (set `error-text`).
37
+ *
38
+ * **Behaviour:** Tab moves through the options and Space toggles one. Each
39
+ * change updates `value` and fires `oh-change` with the new array. The
40
+ * native checkboxes sit in the shadow root, so they don't submit with a
41
+ * native form: read `value` or listen for `oh-change`. `required` doesn't
42
+ * validate; set the error yourself.
43
+ *
44
+ * **Accessibility:** a `<fieldset>` with a `<legend>`, so screen readers
45
+ * read the label with each option. Always set `label`. The error shows
46
+ * after the legend and before the options, with an icon; the fieldset gets
47
+ * `aria-invalid` and `aria-describedby`, and the error is announced
48
+ * (`role="alert"`) only when it appears after first render. Focus underlines
49
+ * the option's label.
50
+ *
51
+ * **Look:** set by tokens — the same as `oh-checkbox`: `stroke.control` in
52
+ * `color.border.emphasis` (border), `shape.compact` (corners),
53
+ * `elevation.offset.*` with `elevation.color.secondary` / `elevation.color.primary` (shadow),
54
+ * `lift.hoverSm` / `lift.pressSm` (movement on hover and press),
55
+ * `color.brand.primary` and `color.text.onBrand` (checked),
56
+ * `color.feedback.error` and `elevation.color.error` (error); `typography.control.*`
57
+ * for the legend and labels.
58
+ *
59
+ * @element oh-checkbox-group
60
+ * @fires oh-change - An option was toggled. detail: { value } (the checked values)
61
+ */
23
62
  export declare class OhCheckboxGroup extends OhElement {
24
63
  static styles: import("lit").CSSResult[];
25
- legend: string;
64
+ /** The group's label, rendered as the fieldset's <legend>. Always set one */
65
+ label: string;
26
66
  name: string;
27
67
  options: CheckboxOption[];
28
68
  value: string[];
29
69
  required: boolean;
30
- error: boolean;
31
- errorMessage: string;
70
+ /**
71
+ * Error shown with the control and linked to it. Setting it puts the
72
+ * control in its error state; clear it to leave that state.
73
+ */
74
+ errorText: string;
32
75
  private _handleChange;
33
76
  /**
34
77
  * Errors that appear after first render (e.g. on submit) are announced via
@@ -36,6 +79,7 @@ export declare class OhCheckboxGroup extends OhElement {
36
79
  * aria-describedby, so they aren't read out unprompted.
37
80
  */
38
81
  private _announceError;
82
+ updated(changed: PropertyValues): void;
39
83
  willUpdate(changed: PropertyValues): void;
40
84
  render(): import("lit").TemplateResult<1>;
41
85
  }
@@ -1 +1 @@
1
- {"version":3,"file":"oh-checkbox-group.d.ts","sourceRoot":"","sources":["../src/oh-checkbox-group.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAE9D,OAAO,EAAE,SAAS,EAAc,MAAM,WAAW,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBACa,eAAgB,SAAQ,SAAS;IAC5C,OAAgB,MAAM,4BAgMpB;IAGF,MAAM,SAAM;IAGZ,IAAI,SAAM;IAGV,OAAO,EAAE,cAAc,EAAE,CAAM;IAG/B,KAAK,EAAE,MAAM,EAAE,CAAM;IAGrB,QAAQ,UAAS;IAGjB,KAAK,UAAS;IAGd,YAAY,SAAM;IAElB,OAAO,CAAC,aAAa;IAYrB;;;;OAIG;IAEH,OAAO,CAAC,cAAc,CAAS;IAE/B,UAAU,CAAC,OAAO,EAAE,cAAc;IAMlC,MAAM;CA8CP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,mBAAmB,EAAE,eAAe,CAAC;KACtC;CACF"}
1
+ {"version":3,"file":"oh-checkbox-group.d.ts","sourceRoot":"","sources":["../src/oh-checkbox-group.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAE9D,OAAO,EAAE,SAAS,EAAc,MAAM,WAAW,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBACa,eAAgB,SAAQ,SAAS;IAC5C,OAAgB,MAAM,4BAwMpB;IAEF,6EAA6E;IAE7E,KAAK,SAAM;IAGX,IAAI,SAAM;IAGV,OAAO,EAAE,cAAc,EAAE,CAAM;IAG/B,KAAK,EAAE,MAAM,EAAE,CAAM;IAGrB,QAAQ,UAAS;IAEjB;;;OAGG;IAEH,SAAS,SAAM;IAEf,OAAO,CAAC,aAAa;IAYrB;;;;OAIG;IAEH,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,OAAO,EAAE,cAAc;IAM/B,UAAU,CAAC,OAAO,EAAE,cAAc;IAMlC,MAAM;CA0DP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,mBAAmB,EAAE,eAAe,CAAC;KACtC;CACF"}
@@ -6,17 +6,15 @@ import figma, { html } from "@figma/code-connect/html";
6
6
  */
7
7
  figma.connect("https://www.figma.com/design/q9ZYrOj2w9DrTVmgQFU7bu/OHDS?node-id=1-77", {
8
8
  props: {
9
- legend: figma.string("Legend"),
9
+ label: figma.string("Legend"),
10
10
  required: figma.boolean("Required"),
11
- error: figma.boolean("Error"),
12
- errorMessage: figma.string("Error Message"),
11
+ errorText: figma.string("Error Message"),
13
12
  },
14
13
  example: (props) => html `
15
14
  <oh-checkbox-group
16
- legend="${props.legend}"
15
+ label="${props.label}"
17
16
  ?required="${props.required}"
18
- ?error="${props.error}"
19
- errormessage="${props.errorMessage}"
17
+ error-text="${props.errorText}"
20
18
  .options="\${options}"
21
19
  ></oh-checkbox-group>
22
20
  `,
@@ -1 +1 @@
1
- {"version":3,"file":"oh-checkbox-group.figma.js","sourceRoot":"","sources":["../src/oh-checkbox-group.figma.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;;GAIG;AAEH,KAAK,CAAC,OAAO,CACX,uEAAuE,EACvE;IACE,KAAK,EAAE;QACL,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC9B,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;QACnC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAC7B,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC;KAC5C;IACD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAA;;kBAEV,KAAK,CAAC,MAAM;qBACT,KAAK,CAAC,QAAQ;kBACjB,KAAK,CAAC,KAAK;wBACL,KAAK,CAAC,YAAY;;;KAGrC;CACF,CACF,CAAC"}
1
+ {"version":3,"file":"oh-checkbox-group.figma.js","sourceRoot":"","sources":["../src/oh-checkbox-group.figma.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;;GAIG;AAEH,KAAK,CAAC,OAAO,CACX,uEAAuE,EACvE;IACE,KAAK,EAAE;QACL,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC7B,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;QACnC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC;KACzC;IACD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAA;;iBAEX,KAAK,CAAC,KAAK;qBACP,KAAK,CAAC,QAAQ;sBACb,KAAK,CAAC,SAAS;;;KAGhC;CACF,CACF,CAAC"}