@reliability-design/web 1.3.0 → 1.4.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 (123) hide show
  1. package/dist/components/rel-accordion/README.md +13 -13
  2. package/dist/components/rel-accordion/rel-accordion.ts +80 -45
  3. package/dist/components/rel-alert/README.md +15 -15
  4. package/dist/components/rel-alert/rel-alert.ts +161 -159
  5. package/dist/components/rel-avatar/README.md +15 -15
  6. package/dist/components/rel-avatar/rel-avatar.ts +42 -38
  7. package/dist/components/rel-badge/README.md +5 -5
  8. package/dist/components/rel-badge/rel-badge.ts +69 -17
  9. package/dist/components/rel-breadcrumb/README.md +7 -7
  10. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +16 -13
  11. package/dist/components/rel-button/README.md +20 -20
  12. package/dist/components/rel-button/rel-button.ts +327 -105
  13. package/dist/components/rel-calendar/README.md +5 -5
  14. package/dist/components/rel-calendar/rel-calendar.ts +61 -45
  15. package/dist/components/rel-card/README.md +6 -6
  16. package/dist/components/rel-card/rel-card.ts +88 -35
  17. package/dist/components/rel-cardmedia/README.md +9 -9
  18. package/dist/components/rel-cardmedia/rel-cardmedia.ts +27 -20
  19. package/dist/components/rel-carousel/README.md +3 -3
  20. package/dist/components/rel-carousel/rel-carousel.ts +174 -90
  21. package/dist/components/rel-chart/README.md +8 -8
  22. package/dist/components/rel-chart/rel-chart.ts +59 -41
  23. package/dist/components/rel-checkbox/README.md +7 -7
  24. package/dist/components/rel-checkbox/rel-checkbox.ts +61 -61
  25. package/dist/components/rel-chip/README.md +16 -16
  26. package/dist/components/rel-chip/rel-chip.ts +143 -111
  27. package/dist/components/rel-container/README.md +11 -11
  28. package/dist/components/rel-container/rel-container.ts +35 -9
  29. package/dist/components/rel-date-picker/README.md +43 -7
  30. package/dist/components/rel-date-picker/date-format.test.ts +43 -0
  31. package/dist/components/rel-date-picker/date-format.ts +47 -0
  32. package/dist/components/rel-date-picker/rel-date-picker.ts +257 -83
  33. package/dist/components/rel-divider/README.md +9 -9
  34. package/dist/components/rel-divider/rel-divider.ts +10 -9
  35. package/dist/components/rel-drawer/README.md +1 -1
  36. package/dist/components/rel-drawer/rel-drawer-footer.ts +1 -1
  37. package/dist/components/rel-drawer/rel-drawer.ts +56 -28
  38. package/dist/components/rel-kbd/README.md +20 -20
  39. package/dist/components/rel-kbd/rel-kbd.ts +119 -25
  40. package/dist/components/rel-layout/README.md +1 -1
  41. package/dist/components/rel-layout/rel-layout.ts +2 -2
  42. package/dist/components/rel-menu/README.md +15 -15
  43. package/dist/components/rel-menu/rel-menu.ts +155 -88
  44. package/dist/components/rel-menubar/README.md +20 -20
  45. package/dist/components/rel-menubar/rel-menubar.ts +47 -31
  46. package/dist/components/rel-nav-menu/README.md +21 -21
  47. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +17 -14
  48. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +33 -14
  49. package/dist/components/rel-nav-menu/rel-nav-menu.ts +14 -7
  50. package/dist/components/rel-navbar/README.md +7 -7
  51. package/dist/components/rel-navbar/rel-navbar.ts +12 -11
  52. package/dist/components/rel-otp/README.md +3 -3
  53. package/dist/components/rel-otp/rel-otp.ts +46 -30
  54. package/dist/components/rel-pagination/README.md +18 -18
  55. package/dist/components/rel-pagination/rel-pagination.ts +141 -90
  56. package/dist/components/rel-popover/README.md +13 -13
  57. package/dist/components/rel-popover/rel-popover-content.ts +39 -14
  58. package/dist/components/rel-popover/rel-popover.ts +18 -7
  59. package/dist/components/rel-progress/README.md +5 -5
  60. package/dist/components/rel-progress/rel-progress.ts +16 -12
  61. package/dist/components/rel-radio/README.md +9 -9
  62. package/dist/components/rel-radio/rel-radio.ts +64 -50
  63. package/dist/components/rel-range-picker/rel-range-picker.ts +120 -117
  64. package/dist/components/rel-resizable/README.md +4 -4
  65. package/dist/components/rel-resizable/rel-resizable-group.ts +15 -4
  66. package/dist/components/rel-resizable/rel-resizable-handle.ts +13 -9
  67. package/dist/components/rel-resizable/rel-resizable-panel.ts +11 -0
  68. package/dist/components/rel-segmented-button/README.md +10 -10
  69. package/dist/components/rel-segmented-button/rel-segmented-button.ts +39 -40
  70. package/dist/components/rel-select/README.md +14 -14
  71. package/dist/components/rel-select/rel-select.ts +120 -188
  72. package/dist/components/rel-sheet/README.md +10 -10
  73. package/dist/components/rel-sheet/rel-sheet.ts +82 -23
  74. package/dist/components/rel-sidebar/README.md +5 -5
  75. package/dist/components/rel-sidebar/rel-sidebar.ts +75 -60
  76. package/dist/components/rel-skeleton/rel-skeleton.ts +8 -13
  77. package/dist/components/rel-slider/README.md +10 -10
  78. package/dist/components/rel-slider/rel-slider.ts +230 -57
  79. package/dist/components/rel-snackbar/README.md +9 -9
  80. package/dist/components/rel-snackbar/rel-snackbar.ts +44 -32
  81. package/dist/components/rel-stepper/README.md +10 -10
  82. package/dist/components/rel-stepper/rel-stepper.ts +110 -46
  83. package/dist/components/rel-switch/README.md +5 -5
  84. package/dist/components/rel-switch/rel-switch.ts +23 -12
  85. package/dist/components/rel-tab/README.md +13 -13
  86. package/dist/components/rel-tab/rel-tab.ts +73 -23
  87. package/dist/components/rel-table/README.md +7 -7
  88. package/dist/components/rel-table/rel-table-body.ts +2 -1
  89. package/dist/components/rel-table/rel-table-caption.ts +10 -7
  90. package/dist/components/rel-table/rel-table-cell.ts +7 -6
  91. package/dist/components/rel-table/rel-table-footer.ts +4 -3
  92. package/dist/components/rel-table/rel-table-head.ts +11 -9
  93. package/dist/components/rel-table/rel-table-header.ts +2 -1
  94. package/dist/components/rel-table/rel-table-row.ts +5 -4
  95. package/dist/components/rel-table/rel-table.ts +16 -7
  96. package/dist/components/rel-textfield/README.md +11 -11
  97. package/dist/components/rel-textfield/rel-textfield.ts +718 -397
  98. package/dist/components/rel-toast/README.md +11 -11
  99. package/dist/components/rel-toast/rel-toast.ts +33 -30
  100. package/dist/components/rel-tooltip/README.md +7 -7
  101. package/dist/components/rel-tooltip/rel-tooltip.ts +160 -123
  102. package/dist/components/rel-topbar/rel-topbar-actions.ts +5 -2
  103. package/dist/components/rel-topbar/rel-topbar-brand.ts +12 -5
  104. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +14 -8
  105. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +8 -5
  106. package/dist/components/rel-topbar/rel-topbar-item.ts +27 -22
  107. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +4 -1
  108. package/dist/components/rel-topbar/rel-topbar-profile.ts +71 -50
  109. package/dist/components/rel-topbar/rel-topbar-utilities.ts +5 -2
  110. package/dist/components/rel-topbar/rel-topbar.ts +37 -29
  111. package/dist/index.d.mts +227 -40
  112. package/dist/index.d.ts +227 -40
  113. package/dist/index.js +3930 -2247
  114. package/dist/index.js.map +1 -1
  115. package/dist/index.mjs +4311 -2629
  116. package/dist/index.mjs.map +1 -1
  117. package/dist/styles/vars.css +653 -346
  118. package/dist/vars-WY2XCF5G.css +653 -0
  119. package/package.json +3 -2
  120. package/dist/components/rel-range-picker/rel-range-picker.css +0 -349
  121. package/dist/styles/vars-v2.css +0 -525
  122. package/dist/vars-I2PC74XD.css +0 -346
  123. package/dist/vars-v2-VMGWTFJZ.css +0 -525
@@ -1,16 +1,42 @@
1
- import "../../styles/vars-v2.css";
1
+ import "../rel-badge/rel-badge";
2
2
  import { LitElement, html, css } from "lit";
3
3
  import { customElement, property } from "lit/decorators.js";
4
4
  import { ifDefined } from "lit/directives/if-defined.js";
5
5
 
6
+ /**
7
+ * Style axis. Canonical values are the historical ones; "outline"/"ghost" are
8
+ * accepted Figma-vocabulary aliases for "secondary"/"text". "static" is the
9
+ * Figma Style=Static treatment (transparent, static-light label — for use on
10
+ * colored/dark surfaces). Legacy "icon" is the pre-2.0 collapsed icon-button
11
+ * variant and keeps its exact behavior; prefer icon-only + a style instead.
12
+ */
13
+ export type ButtonVariant =
14
+ | "default" | "primary" | "secondary" | "text" | "link" | "icon" | "static"
15
+ | "outline" | "ghost";
16
+
17
+ /** Tone axis. "neutral"/"brand"/"danger" are Figma-vocabulary aliases. */
18
+ export type ButtonTone =
19
+ | "default" | "primary" | "secondary" | "critical" | "success"
20
+ | "neutral" | "brand" | "danger";
21
+
6
22
  @customElement("rel-button")
7
23
  export class RelButton extends LitElement {
8
24
  @property({ type: String })
9
- variant: "default" | "primary" | "secondary" | "text" | "link" | "icon" =
10
- "default";
25
+ variant: ButtonVariant = "default";
11
26
 
12
27
  @property({ type: String })
13
- tone: "default" | "primary" | "secondary" | "critical" | "success" = "default";
28
+ tone: ButtonTone = "default";
29
+
30
+ /**
31
+ * Figma "Type=Icon Only" as an independent axis: a square button of the
32
+ * current style/tone. Unlike legacy variant="icon", this composes with
33
+ * every style (outline/ghost/link included).
34
+ */
35
+ @property({ type: Boolean, reflect: true, attribute: "icon-only" })
36
+ iconOnly = false;
37
+
38
+ /** Figma "State=Loading": shows a spinner, blocks interaction. */
39
+ @property({ type: Boolean, reflect: true }) loading = false;
14
40
 
15
41
  @property({ type: String })
16
42
  size: "small" | "medium" | "large" = "medium";
@@ -23,124 +49,260 @@ export class RelButton extends LitElement {
23
49
 
24
50
  @property({ type: String, attribute: "aria-label" }) ariaLabel: string | null = null;
25
51
 
52
+ /** Numeric/overflow text shown in a corner badge (e.g. "3", "999+"). Ignored when badgeDot is set. */
53
+ @property({ type: String }) badge = "";
54
+
55
+ /** Shows a small corner dot instead of a numbered badge — takes precedence over `badge`. */
56
+ @property({ type: Boolean, attribute: "badge-dot" }) badgeDot = false;
57
+
26
58
  static styles = css`
27
59
  :host {
60
+ color: var(--rel-text-default);
28
61
  display: inline-flex;
62
+ position: relative;
63
+ }
64
+
65
+ .btn-badge {
66
+ position: absolute;
67
+ top: -6px;
68
+ right: -6px;
69
+ pointer-events: none;
70
+ }
71
+
72
+ /* Icon buttons are a 40px circle (radius 20). An 8px dot's center needs to
73
+ land ON that circle's edge at the 45° corner point — which works out to
74
+ roughly 2px inward from each edge (R - R/√2 - dot-radius ≈ 1.9px), not
75
+ outside it like the numbered badge. Negative offsets here would float
76
+ the dot in the empty bounding-box corner outside the visible circle. */
77
+ .btn-badge[variant="dot"] {
78
+ top: 2px;
79
+ right: 2px;
29
80
  }
30
81
 
31
82
  button {
32
- font-family: var(--rel-fontFamilies-primary);
33
- font-weight: var(--rel-fontWeights-semibold);
83
+ font-family: var(--rel-action-md-font-family);
84
+ font-weight: var(--rel-action-md-font-weight);
34
85
  cursor: pointer;
35
86
  border: none;
36
87
  transition: all 0.15s ease;
37
88
  display: inline-flex;
38
89
  align-items: center;
39
90
  justify-content: center;
40
- gap: var(--rel-spacing-space-2xs);
91
+ gap: var(--rel-spacing-2xs);
41
92
  outline: none;
93
+ position: relative;
94
+ }
95
+
96
+ /* Content wrapper mirrors the button's flex so slotted icon+label keep
97
+ their gap; it exists so loading can hide the label without collapsing
98
+ the button's width. */
99
+ .content {
100
+ display: inline-flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ gap: inherit;
104
+ }
105
+
106
+ /* Loading (Figma State=Loading): spinner replaces the label in place */
107
+ :host([loading]) {
108
+ pointer-events: none;
109
+ }
110
+
111
+ :host([loading]) .content {
112
+ visibility: hidden;
113
+ }
114
+
115
+ .spinner {
116
+ position: absolute;
117
+ left: 50%;
118
+ top: 50%;
119
+ width: 16px;
120
+ height: 16px;
121
+ margin: -8px 0 0 -8px;
122
+ border-radius: 50%;
123
+ border: 2px solid currentColor;
124
+ border-top-color: transparent;
125
+ animation: rel-btn-spin 0.8s linear infinite;
126
+ }
127
+
128
+ @keyframes rel-btn-spin {
129
+ to {
130
+ transform: rotate(360deg);
131
+ }
132
+ }
133
+
134
+ @media (prefers-reduced-motion: reduce) {
135
+ .spinner {
136
+ animation-duration: 2s;
137
+ }
42
138
  }
43
139
 
44
140
  /* Sizes */
45
141
  .size-small {
46
142
  height: 32px;
47
- padding: 0 var(--rel-spacing-space-md);
48
- font-size: var(--rel-fontSizes-sm);
143
+ padding: 0 var(--rel-spacing-sm);
144
+ font-family: var(--rel-action-md-font-family);
145
+ font-weight: var(--rel-action-md-font-weight);
146
+ font-size: var(--rel-action-md-font-size);
147
+ line-height: var(--rel-action-md-line-height);
148
+ letter-spacing: var(--rel-action-md-letter-spacing);
49
149
  }
50
150
 
51
151
  .size-medium {
52
152
  height: 40px;
53
- padding: 0 var(--rel-spacing-space-lg);
54
- font-size: var(--rel-fontSizes-md);
153
+ gap: var(--rel-spacing-xs);
154
+ padding: 0 var(--rel-spacing-md);
155
+ font-family: var(--rel-action-lg-font-family);
156
+ font-weight: var(--rel-action-lg-font-weight);
157
+ font-size: var(--rel-action-lg-font-size);
158
+ line-height: var(--rel-action-lg-line-height);
159
+ letter-spacing: var(--rel-action-lg-letter-spacing);
55
160
  }
56
161
 
57
162
  .size-large {
58
163
  height: 48px;
59
- padding: 0 var(--rel-spacing-space-xl);
60
- font-size: var(--rel-fontSizes-lg);
164
+ padding: 0 var(--rel-spacing-lg);
165
+ font-family: var(--rel-action-xl-font-family);
166
+ font-weight: var(--rel-action-xl-font-weight);
167
+ font-size: var(--rel-action-xl-font-size);
168
+ line-height: var(--rel-action-xl-line-height);
169
+ letter-spacing: var(--rel-action-xl-letter-spacing);
61
170
  }
62
171
 
63
172
  /* Borders */
64
173
  .border-corner-radius {
65
- border-radius: var(--rel-borders-radius-border-radius-500);
174
+ border-radius: var(--rel-border-radius-full-rounded);
66
175
  }
67
176
 
68
177
  .border-small-corner-radius {
69
- border-radius: var(--rel-borders-radius-border-radius-200);
178
+ border-radius: var(--rel-border-radius-sm);
70
179
  }
71
180
 
72
181
  .border-no-corner-radius {
73
- border-radius: var(--rel-borders-radius-border-radius-0);
182
+ border-radius: var(--rel-border-radius-square);
74
183
  }
75
184
 
76
185
  /* Disabled */
77
186
  :host([disabled]) {
187
+ /* not-allowed must stay VISIBLE: the host keeps pointer-events so the
188
+ cursor renders. Clicks are neutralised two ways — the inner button's
189
+ pointer-events:none (below) and the capture-phase handleHostClick
190
+ guard (which also stops programmatic .click()). Putting
191
+ pointer-events:none on the HOST would make it transparent to the
192
+ pointer and suppress the cursor entirely. */
78
193
  cursor: not-allowed;
79
- /* Without this, clicks on a disabled button hit-test fall through the
80
- inner button's pointer-events:none straight onto the host, which still
81
- dispatches a normal click to any externally-attached listener. */
82
- pointer-events: none;
83
194
  }
84
195
 
85
196
  :host([disabled]) button {
86
197
  cursor: not-allowed;
87
- background-color: var(--rel-color-bg-tertiary) !important;
88
- color: var(--rel-color-text-disabled) !important;
198
+ background-color: var(--rel-action-brand-fill-disabled) !important;
199
+ color: var(--rel-action-brand-label-icons-primary-disabled) !important;
89
200
  border: none !important;
90
201
  pointer-events: none;
91
202
  filter: none !important;
92
203
  }
93
204
 
94
- :host([disabled]):host-context([data-theme="dark"]) button,
95
- :host([disabled]):host-context(.dark) button {
96
- background-color: var(--rel-color-bg-tertiary) !important;
97
- color: var(--rel-color-text-disabled) !important;
98
- }
99
-
100
- /* Variants & tones (unchanged) */
205
+ /* Variants & tones — V2 action tokens (auto light/dark) */
101
206
  .btn-default.tone-default {
102
- background: var(--rel-colors-gray-primary);
103
- color: var(--rel-colors-white);
207
+ background: var(--rel-action-neutral-fill);
208
+ color: var(--rel-action-neutral-label-icons-fill-inverse);
104
209
  }
105
210
  .btn-default.tone-critical {
106
- background: var(--rel-error-surface-default);
107
- color: #ffffff;
211
+ background: var(--rel-action-danger-fill);
212
+ color: var(--rel-action-danger-label-icons-fill);
108
213
  }
109
214
  .btn-default.tone-success {
110
- background: var(--rel-colors-green-tertiary);
111
- color: #ffffff;
215
+ background: var(--rel-action-success-fill);
216
+ color: var(--rel-text-static-light);
112
217
  }
113
218
 
114
219
  .btn-primary.tone-default {
115
- background: var(--rel-colors-green-primary);
116
- color: #000000;
220
+ background: var(--rel-action-brand-fill);
221
+ color: var(--rel-action-brand-label-icons-primary);
117
222
  }
118
223
  .btn-primary.tone-critical {
119
- background: var(--rel-error-surface-default);
120
- color: #ffffff;
224
+ background: var(--rel-action-danger-fill);
225
+ color: var(--rel-action-danger-label-icons-fill);
121
226
  }
122
227
  .btn-primary.tone-success {
123
- background: var(--rel-colors-green-tertiary);
124
- color: #ffffff;
228
+ background: var(--rel-action-success-fill);
229
+ color: var(--rel-action-success-label-icons-fill);
125
230
  }
126
231
 
127
232
  .btn-secondary {
128
- background: var(--rel-colors-white);
129
- border: 1px solid var(--rel-color-border-strong);
130
- color: var(--rel-colors-black);
233
+ background: var(--rel-bg-surface);
234
+ border: 1px solid var(--rel-action-neutral-border-outline);
235
+ color: var(--rel-action-neutral-label-icons-outline);
236
+ }
237
+
238
+ /* Secondary critical / success tones. The danger outline border is
239
+ deliberately the SUBTLE red at rest (red-100) and only goes solid
240
+ (red-600) on hover/pressed — verified against the Figma design nodes,
241
+ not just the variable names. Do not "fix" it to a solid red. */
242
+ .btn-secondary.tone-critical {
243
+ border-color: var(--rel-action-danger-border-outline);
244
+ color: var(--rel-action-danger-label-icons-outline);
245
+ }
246
+ .btn-secondary.tone-success {
247
+ border-color: var(--rel-border-success);
248
+ color: var(--rel-text-success);
131
249
  }
132
250
 
133
251
  .btn-text {
134
252
  background: transparent;
135
- color: var(--rel-colors-black);
253
+ color: var(--rel-action-neutral-label-icons-ghost);
254
+ }
255
+
256
+ .btn-text.tone-critical {
257
+ color: var(--rel-action-danger-label-icons-ghost);
136
258
  }
137
259
 
138
260
  .btn-link {
139
261
  background: transparent;
140
- color: var(--rel-colors-blue-primary);
262
+ color: var(--rel-action-neutral-label-icons-link);
141
263
  text-decoration: underline;
142
264
  }
143
265
 
266
+ button.btn-link:hover:not(:disabled) {
267
+ color: var(--rel-action-neutral-label-icons-link-hover);
268
+ }
269
+
270
+ .btn-link.tone-critical {
271
+ color: var(--rel-action-danger-label-icons-link);
272
+ }
273
+
274
+ button.btn-link.tone-critical:hover:not(:disabled) {
275
+ color: var(--rel-action-danger-label-icons-link-hover);
276
+ }
277
+
278
+ /* Static style (Figma Style=Static, Tone=Neutral only): transparent with a
279
+ static-light label, for placement on colored or dark surfaces */
280
+ .btn-static {
281
+ background: transparent;
282
+ color: var(--rel-text-static-light);
283
+ }
284
+
285
+ button.btn-static:hover:not(:disabled) {
286
+ background: var(--rel-action-neutral-static-hover);
287
+ }
288
+
289
+ /* Icon Only as an independent axis: square button of the current style */
290
+ .btn-icon-only {
291
+ padding: 0;
292
+ }
293
+
294
+ .btn-icon-only.size-small {
295
+ width: 32px;
296
+ }
297
+
298
+ .btn-icon-only.size-medium {
299
+ width: 40px;
300
+ }
301
+
302
+ .btn-icon-only.size-large {
303
+ width: 48px;
304
+ }
305
+
144
306
  .btn-icon {
145
307
  width: 40px;
146
308
  height: 40px;
@@ -148,67 +310,118 @@ export class RelButton extends LitElement {
148
310
  border-radius: 50%;
149
311
  }
150
312
  .btn-icon.tone-default {
151
- background: var(--rel-colors-gray-light);
152
- color: var(--rel-colors-black);
313
+ background: var(--rel-action-neutral-subtle);
314
+ color: var(--rel-action-neutral-label-icons-ghost);
153
315
  border: none;
154
316
  }
155
317
  .btn-icon.tone-primary {
156
- background: var(--rel-colors-green-primary);
157
- color: #000000;
318
+ background: var(--rel-action-brand-fill);
319
+ color: var(--rel-action-brand-label-icons-primary);
158
320
  border: none;
159
321
  }
160
322
  .btn-icon.tone-secondary {
161
- background: var(--rel-colors-white);
162
- color: var(--rel-colors-black);
163
- border: 1px solid var(--rel-color-border-strong);
323
+ background: var(--rel-bg-surface);
324
+ color: var(--rel-action-neutral-label-icons-outline);
325
+ border: 1px solid var(--rel-action-neutral-border-outline);
164
326
  }
165
327
  .btn-icon.tone-success {
166
- background: var(--rel-colors-green-tertiary);
167
- color: #ffffff;
328
+ background: var(--rel-action-success-fill);
329
+ color: var(--rel-action-success-label-icons-fill);
168
330
  border: none;
169
331
  }
170
332
  .btn-icon.tone-critical {
171
- background: var(--rel-error-surface-default);
172
- color: #ffffff;
333
+ background: var(--rel-action-danger-fill);
334
+ color: var(--rel-action-danger-label-icons-fill);
173
335
  border: none;
174
336
  }
175
337
 
176
338
  button.btn-primary.tone-default:hover:not(:disabled) {
177
- background: var(--rel-color-brand-hover);
339
+ background: var(--rel-action-brand-fill-hover);
178
340
  }
179
341
 
180
342
  button.btn-primary.tone-critical:hover:not(:disabled) {
181
- filter: brightness(0.9);
343
+ background: var(--rel-action-danger-fill-hover);
182
344
  }
183
345
 
184
346
  button.btn-primary.tone-success:hover:not(:disabled) {
185
- filter: brightness(0.9);
347
+ background: var(--rel-action-success-fill-hover);
348
+ }
349
+
350
+ /* Generic hover — excludes styles handled per-style. Now also excludes
351
+ .btn-text: ghost gets tone-accent hovers below (this generic grey overlay
352
+ has higher specificity than the per-tone ghost rules and was overriding
353
+ them, which is why every ghost hover looked grey). */
354
+ button:hover:not(:disabled):not(.btn-primary):not(.btn-default):not(.btn-icon):not(.btn-secondary):not(.btn-static):not(.btn-text) {
355
+ background: var(--rel-action-overlay-hover);
186
356
  }
187
357
 
188
- /* Generic hover — excludes icon (handled per-tone below) */
189
- button:hover:not(:disabled):not(.btn-primary):not(.btn-default):not(.btn-icon) {
190
- background: var(--rel-color-bg-hover);
358
+ /* Outline (secondary): fill stays on the surface (white) on hover AND
359
+ pressed — only the border reacts. Matches Figma (transparent outline fill). */
360
+ button.btn-secondary:hover:not(:disabled),
361
+ button.btn-secondary:active:not(:disabled) {
362
+ background: var(--rel-bg-surface);
363
+ border-color: var(--rel-action-neutral-border-outline-hover);
364
+ }
365
+ button.btn-secondary.tone-critical:hover:not(:disabled),
366
+ button.btn-secondary.tone-critical:active:not(:disabled) {
367
+ background: var(--rel-bg-surface);
368
+ border-color: var(--rel-action-danger-border-outline-hover);
369
+ }
370
+ button.btn-secondary.tone-success:hover:not(:disabled),
371
+ button.btn-secondary.tone-success:active:not(:disabled) {
372
+ background: var(--rel-bg-surface);
373
+ border-color: var(--rel-border-success-strong);
191
374
  }
192
375
 
193
- button.btn-default:hover:not(:disabled) {
194
- filter: brightness(0.85);
376
+ /* Ghost (text): tone-accent fill on hover AND pressed — the same tint the
377
+ outline used to carry (neutral = subtle overlay, danger = red-50,
378
+ success = green-50). Matches Figma. */
379
+ button.btn-text:hover:not(:disabled),
380
+ button.btn-text:active:not(:disabled) {
381
+ background: var(--rel-action-neutral-ghost-hover);
382
+ }
383
+ button.btn-text.tone-critical:hover:not(:disabled),
384
+ button.btn-text.tone-critical:active:not(:disabled) {
385
+ background: var(--rel-action-danger-ghost-hover);
386
+ }
387
+ button.btn-text.tone-success:hover:not(:disabled),
388
+ button.btn-text.tone-success:active:not(:disabled) {
389
+ background: var(--rel-action-success-ghost-hover);
390
+ }
391
+
392
+ button.btn-default.tone-default:hover:not(:disabled) {
393
+ background: var(--rel-action-neutral-fill-hover);
394
+ }
395
+ button.btn-default.tone-critical:hover:not(:disabled) {
396
+ background: var(--rel-action-danger-fill-hover);
397
+ }
398
+ button.btn-default.tone-success:hover:not(:disabled) {
399
+ background: var(--rel-action-success-fill-hover);
195
400
  }
196
401
 
197
402
  /* Icon hover — each tone matches its corresponding button variant */
198
403
  button.btn-icon.tone-default:hover:not(:disabled) {
199
- filter: brightness(0.9);
404
+ background: var(--rel-action-neutral-subtle-hover);
200
405
  }
201
406
  button.btn-icon.tone-primary:hover:not(:disabled) {
202
- background: var(--rel-color-brand-hover);
407
+ background: var(--rel-action-brand-fill-hover);
203
408
  }
204
409
  button.btn-icon.tone-secondary:hover:not(:disabled) {
205
- background: var(--rel-color-bg-hover);
410
+ background: var(--rel-action-neutral-ghost-hover);
411
+ border-color: var(--rel-action-neutral-border-outline-hover);
206
412
  }
207
413
  button.btn-icon.tone-success:hover:not(:disabled) {
208
- filter: brightness(0.9);
414
+ background: var(--rel-action-success-fill-hover);
209
415
  }
210
416
  button.btn-icon.tone-critical:hover:not(:disabled) {
211
- filter: brightness(0.9);
417
+ background: var(--rel-action-danger-fill-hover);
418
+ }
419
+
420
+ button.btn-default.tone-default:active:not(:disabled) {
421
+ background-image: linear-gradient(
422
+ var(--rel-action-neutral-static-hover),
423
+ var(--rel-action-neutral-static-hover)
424
+ );
212
425
  }
213
426
 
214
427
  button:active:not(:disabled) {
@@ -216,37 +429,12 @@ export class RelButton extends LitElement {
216
429
  }
217
430
 
218
431
  button:focus-visible {
219
- outline: 2px solid var(--rel-color-border-focus);
432
+ outline: 2px solid var(--rel-border-focus);
220
433
  outline-offset: 2px;
434
+ box-shadow:
435
+ 0 0 0 2px var(--rel-border-focus-inner),
436
+ 0 0 0 8px var(--rel-border-focus-subtle);
221
437
  }
222
-
223
- /* Dark mode overrides */
224
- :host-context([data-theme="dark"]) button.btn-default.tone-default,
225
- :host-context(.dark) button.btn-default.tone-default {
226
- background: var(--rel-gray-text-primary);
227
- color: #000000;
228
- }
229
-
230
- :host-context([data-theme="dark"]) button:focus-visible,
231
- :host-context(.dark) button:focus-visible {
232
- outline-color: var(--rel-color-text-primary);
233
- }
234
-
235
- :host-context([data-theme="dark"]) button.btn-secondary,
236
- :host-context(.dark) button.btn-secondary,
237
- :host-context([data-theme="dark"]) button.btn-icon.tone-secondary,
238
- :host-context(.dark) button.btn-icon.tone-secondary {
239
- background: var(--rel-color-bg-primary);
240
- border-color: var(--rel-color-border-strong);
241
- color: var(--rel-color-text-primary);
242
- }
243
-
244
- :host-context([data-theme="dark"]) button.btn-text,
245
- :host-context(.dark) button.btn-text {
246
- color: var(--rel-color-text-primary);
247
- }
248
-
249
- /* btn-link: blue in both modes — no dark override */
250
438
  `;
251
439
 
252
440
  connectedCallback() {
@@ -264,23 +452,42 @@ export class RelButton extends LitElement {
264
452
  }
265
453
 
266
454
  private handleHostClick = (e: MouseEvent) => {
267
- if (this.disabled) {
455
+ if (this.disabled || this.loading) {
268
456
  e.preventDefault();
269
457
  e.stopImmediatePropagation();
270
458
  }
271
459
  };
272
460
 
461
+ /** Style axis with Figma-vocabulary aliases folded onto canonical values. */
462
+ private get styleVariant(): string {
463
+ if (this.variant === "outline") return "secondary";
464
+ if (this.variant === "ghost") return "text";
465
+ return this.variant;
466
+ }
467
+
468
+ /** Tone axis with Figma-vocabulary aliases folded onto canonical values. */
469
+ private get effectiveTone(): string {
470
+ if (this.tone === "neutral") return "default";
471
+ if (this.tone === "brand") return "primary";
472
+ if (this.tone === "danger") return "critical";
473
+ return this.tone;
474
+ }
475
+
273
476
  private get classes() {
477
+ const style = this.styleVariant;
274
478
  return [
275
- `btn-${this.variant}`,
276
- `tone-${this.tone}`,
479
+ `btn-${style}`,
480
+ this.iconOnly && style !== "icon" ? "btn-icon-only" : "",
481
+ `tone-${this.effectiveTone}`,
277
482
  `size-${this.size}`,
278
483
  `border-${this.border}`,
279
- ].join(" ");
484
+ ]
485
+ .filter(Boolean)
486
+ .join(" ");
280
487
  }
281
488
 
282
489
  private handleClick(e: MouseEvent) {
283
- if (this.disabled) {
490
+ if (this.disabled || this.loading) {
284
491
  e.preventDefault();
285
492
  e.stopImmediatePropagation();
286
493
  return;
@@ -293,16 +500,31 @@ export class RelButton extends LitElement {
293
500
  }
294
501
 
295
502
  render() {
503
+ const showBadge = this.badgeDot || !!this.badge;
504
+
296
505
  return html`
297
506
  <button
507
+ part="base"
298
508
  class="${this.classes}"
299
509
  ?disabled=${this.disabled}
300
510
  @click=${this.handleClick}
301
511
  aria-disabled=${this.disabled}
512
+ aria-busy=${this.loading}
302
513
  aria-label=${ifDefined(this.ariaLabel ?? undefined)}
303
514
  >
304
- <slot></slot>
515
+ ${this.loading
516
+ ? html`<span part="indicator" class="spinner" aria-hidden="true"></span>`
517
+ : null}
518
+ <span class="content"><slot></slot></span>
305
519
  </button>
520
+ ${showBadge
521
+ ? html`<rel-badge
522
+ class="btn-badge"
523
+ variant=${this.badgeDot ? "dot" : "number"}
524
+ number=${this.badge}
525
+ size="sm"
526
+ ></rel-badge>`
527
+ : null}
306
528
  `;
307
529
  }
308
530
  }
@@ -101,9 +101,9 @@ calendar.addEventListener('rel-change', (e) => {
101
101
  The component uses design tokens from the Reliability Design System. All colors, spacing, and typography are inherited from CSS variables.
102
102
 
103
103
  Key tokens used:
104
- - `--rel-colors-*` for colors
104
+ - `--rel-bg-*` / `--rel-text-*` / `--rel-border-*` semantic colors
105
105
  - `--rel-spacing-*` for spacing
106
- - `--rel-fontSizes-*`, `--rel-fontWeights-*` for typography
107
- - `--rel-borders-radius-*` for border radius
108
- - Focus outline uses `--rel-colors-blue-primary`
109
- - Selected state uses `--rel-colors-green-primary`
106
+ - `--rel-body-*` / `--rel-headings-*` composite typography roles
107
+ - `--rel-border-radius-*` for border radius
108
+ - Focus outline uses `--rel-bg-info`
109
+ - Selected state uses `--rel-bg-brand`