@reliability-design/web 1.3.1 → 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 +153 -158
  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 +46 -11
  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 +292 -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 +154 -82
  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 +63 -19
  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 +172 -91
  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 +1 -2
  103. package/dist/components/rel-topbar/rel-topbar-brand.ts +8 -5
  104. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +10 -8
  105. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +4 -5
  106. package/dist/components/rel-topbar/rel-topbar-item.ts +23 -22
  107. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +0 -1
  108. package/dist/components/rel-topbar/rel-topbar-profile.ts +68 -50
  109. package/dist/components/rel-topbar/rel-topbar-utilities.ts +1 -2
  110. package/dist/components/rel-topbar/rel-topbar.ts +31 -29
  111. package/dist/index.d.mts +151 -39
  112. package/dist/index.d.ts +151 -39
  113. package/dist/index.js +3544 -2289
  114. package/dist/index.js.map +1 -1
  115. package/dist/index.mjs +3876 -2622
  116. package/dist/index.mjs.map +1 -1
  117. package/dist/styles/vars.css +653 -363
  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-F54UGCHM.css +0 -363
  123. package/dist/vars-v2-VMGWTFJZ.css +0 -525
@@ -1,17 +1,42 @@
1
- import "../../styles/vars-v2.css";
2
1
  import "../rel-badge/rel-badge";
3
2
  import { LitElement, html, css } from "lit";
4
3
  import { customElement, property } from "lit/decorators.js";
5
4
  import { ifDefined } from "lit/directives/if-defined.js";
6
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
+
7
22
  @customElement("rel-button")
8
23
  export class RelButton extends LitElement {
9
24
  @property({ type: String })
10
- variant: "default" | "primary" | "secondary" | "text" | "link" | "icon" =
11
- "default";
25
+ variant: ButtonVariant = "default";
12
26
 
13
27
  @property({ type: String })
14
- 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;
15
40
 
16
41
  @property({ type: String })
17
42
  size: "small" | "medium" | "large" = "medium";
@@ -32,6 +57,7 @@ export class RelButton extends LitElement {
32
57
 
33
58
  static styles = css`
34
59
  :host {
60
+ color: var(--rel-text-default);
35
61
  display: inline-flex;
36
62
  position: relative;
37
63
  }
@@ -54,118 +80,229 @@ export class RelButton extends LitElement {
54
80
  }
55
81
 
56
82
  button {
57
- font-family: var(--rel-fontFamilies-primary);
58
- font-weight: var(--rel-fontWeights-semibold);
83
+ font-family: var(--rel-action-md-font-family);
84
+ font-weight: var(--rel-action-md-font-weight);
59
85
  cursor: pointer;
60
86
  border: none;
61
87
  transition: all 0.15s ease;
62
88
  display: inline-flex;
63
89
  align-items: center;
64
90
  justify-content: center;
65
- gap: var(--rel-spacing-space-2xs);
91
+ gap: var(--rel-spacing-2xs);
66
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
+ }
67
138
  }
68
139
 
69
140
  /* Sizes */
70
141
  .size-small {
71
142
  height: 32px;
72
- padding: 0 var(--rel-spacing-space-md);
73
- 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);
74
149
  }
75
150
 
76
151
  .size-medium {
77
152
  height: 40px;
78
- padding: 0 var(--rel-spacing-space-lg);
79
- 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);
80
160
  }
81
161
 
82
162
  .size-large {
83
163
  height: 48px;
84
- padding: 0 var(--rel-spacing-space-xl);
85
- 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);
86
170
  }
87
171
 
88
172
  /* Borders */
89
173
  .border-corner-radius {
90
- border-radius: var(--rel-borders-radius-border-radius-500);
174
+ border-radius: var(--rel-border-radius-full-rounded);
91
175
  }
92
176
 
93
177
  .border-small-corner-radius {
94
- border-radius: var(--rel-borders-radius-border-radius-200);
178
+ border-radius: var(--rel-border-radius-sm);
95
179
  }
96
180
 
97
181
  .border-no-corner-radius {
98
- border-radius: var(--rel-borders-radius-border-radius-0);
182
+ border-radius: var(--rel-border-radius-square);
99
183
  }
100
184
 
101
185
  /* Disabled */
102
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. */
103
193
  cursor: not-allowed;
104
- /* Without this, clicks on a disabled button hit-test fall through the
105
- inner button's pointer-events:none straight onto the host, which still
106
- dispatches a normal click to any externally-attached listener. */
107
- pointer-events: none;
108
194
  }
109
195
 
110
196
  :host([disabled]) button {
111
197
  cursor: not-allowed;
112
- background-color: var(--rel-color-bg-tertiary) !important;
113
- 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;
114
200
  border: none !important;
115
201
  pointer-events: none;
116
202
  filter: none !important;
117
203
  }
118
204
 
119
- :host([disabled]):host-context([data-theme="dark"]) button,
120
- :host([disabled]):host-context(.dark) button {
121
- background-color: var(--rel-color-bg-tertiary) !important;
122
- color: var(--rel-color-text-disabled) !important;
123
- }
124
-
125
- /* Variants & tones (unchanged) */
205
+ /* Variants & tones — V2 action tokens (auto light/dark) */
126
206
  .btn-default.tone-default {
127
- background: var(--rel-colors-gray-primary);
128
- color: var(--rel-colors-white);
207
+ background: var(--rel-action-neutral-fill);
208
+ color: var(--rel-action-neutral-label-icons-fill-inverse);
129
209
  }
130
210
  .btn-default.tone-critical {
131
- background: var(--rel-error-surface-default);
132
- color: #ffffff;
211
+ background: var(--rel-action-danger-fill);
212
+ color: var(--rel-action-danger-label-icons-fill);
133
213
  }
134
214
  .btn-default.tone-success {
135
- background: var(--rel-colors-green-tertiary);
136
- color: #ffffff;
215
+ background: var(--rel-action-success-fill);
216
+ color: var(--rel-text-static-light);
137
217
  }
138
218
 
139
219
  .btn-primary.tone-default {
140
- background: var(--rel-colors-green-primary);
141
- color: #000000;
220
+ background: var(--rel-action-brand-fill);
221
+ color: var(--rel-action-brand-label-icons-primary);
142
222
  }
143
223
  .btn-primary.tone-critical {
144
- background: var(--rel-error-surface-default);
145
- color: #ffffff;
224
+ background: var(--rel-action-danger-fill);
225
+ color: var(--rel-action-danger-label-icons-fill);
146
226
  }
147
227
  .btn-primary.tone-success {
148
- background: var(--rel-colors-green-tertiary);
149
- color: #ffffff;
228
+ background: var(--rel-action-success-fill);
229
+ color: var(--rel-action-success-label-icons-fill);
150
230
  }
151
231
 
152
232
  .btn-secondary {
153
- background: var(--rel-colors-white);
154
- border: 1px solid var(--rel-color-border-strong);
155
- 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);
156
249
  }
157
250
 
158
251
  .btn-text {
159
252
  background: transparent;
160
- 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);
161
258
  }
162
259
 
163
260
  .btn-link {
164
261
  background: transparent;
165
- color: var(--rel-colors-blue-primary);
262
+ color: var(--rel-action-neutral-label-icons-link);
166
263
  text-decoration: underline;
167
264
  }
168
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
+
169
306
  .btn-icon {
170
307
  width: 40px;
171
308
  height: 40px;
@@ -173,67 +310,118 @@ export class RelButton extends LitElement {
173
310
  border-radius: 50%;
174
311
  }
175
312
  .btn-icon.tone-default {
176
- background: var(--rel-colors-gray-light);
177
- color: var(--rel-colors-black);
313
+ background: var(--rel-action-neutral-subtle);
314
+ color: var(--rel-action-neutral-label-icons-ghost);
178
315
  border: none;
179
316
  }
180
317
  .btn-icon.tone-primary {
181
- background: var(--rel-colors-green-primary);
182
- color: #000000;
318
+ background: var(--rel-action-brand-fill);
319
+ color: var(--rel-action-brand-label-icons-primary);
183
320
  border: none;
184
321
  }
185
322
  .btn-icon.tone-secondary {
186
- background: var(--rel-colors-white);
187
- color: var(--rel-colors-black);
188
- 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);
189
326
  }
190
327
  .btn-icon.tone-success {
191
- background: var(--rel-colors-green-tertiary);
192
- color: #ffffff;
328
+ background: var(--rel-action-success-fill);
329
+ color: var(--rel-action-success-label-icons-fill);
193
330
  border: none;
194
331
  }
195
332
  .btn-icon.tone-critical {
196
- background: var(--rel-error-surface-default);
197
- color: #ffffff;
333
+ background: var(--rel-action-danger-fill);
334
+ color: var(--rel-action-danger-label-icons-fill);
198
335
  border: none;
199
336
  }
200
337
 
201
338
  button.btn-primary.tone-default:hover:not(:disabled) {
202
- background: var(--rel-color-brand-hover);
339
+ background: var(--rel-action-brand-fill-hover);
203
340
  }
204
341
 
205
342
  button.btn-primary.tone-critical:hover:not(:disabled) {
206
- filter: brightness(0.9);
343
+ background: var(--rel-action-danger-fill-hover);
207
344
  }
208
345
 
209
346
  button.btn-primary.tone-success:hover:not(:disabled) {
210
- 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);
356
+ }
357
+
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);
211
374
  }
212
375
 
213
- /* Generic hover — excludes icon (handled per-tone below) */
214
- button:hover:not(:disabled):not(.btn-primary):not(.btn-default):not(.btn-icon) {
215
- background: var(--rel-color-bg-hover);
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);
216
390
  }
217
391
 
218
- button.btn-default:hover:not(:disabled) {
219
- filter: brightness(0.85);
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);
220
400
  }
221
401
 
222
402
  /* Icon hover — each tone matches its corresponding button variant */
223
403
  button.btn-icon.tone-default:hover:not(:disabled) {
224
- filter: brightness(0.9);
404
+ background: var(--rel-action-neutral-subtle-hover);
225
405
  }
226
406
  button.btn-icon.tone-primary:hover:not(:disabled) {
227
- background: var(--rel-color-brand-hover);
407
+ background: var(--rel-action-brand-fill-hover);
228
408
  }
229
409
  button.btn-icon.tone-secondary:hover:not(:disabled) {
230
- background: var(--rel-color-bg-hover);
410
+ background: var(--rel-action-neutral-ghost-hover);
411
+ border-color: var(--rel-action-neutral-border-outline-hover);
231
412
  }
232
413
  button.btn-icon.tone-success:hover:not(:disabled) {
233
- filter: brightness(0.9);
414
+ background: var(--rel-action-success-fill-hover);
234
415
  }
235
416
  button.btn-icon.tone-critical:hover:not(:disabled) {
236
- 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
+ );
237
425
  }
238
426
 
239
427
  button:active:not(:disabled) {
@@ -241,37 +429,12 @@ export class RelButton extends LitElement {
241
429
  }
242
430
 
243
431
  button:focus-visible {
244
- outline: 2px solid var(--rel-color-border-focus);
432
+ outline: 2px solid var(--rel-border-focus);
245
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);
246
437
  }
247
-
248
- /* Dark mode overrides */
249
- :host-context([data-theme="dark"]) button.btn-default.tone-default,
250
- :host-context(.dark) button.btn-default.tone-default {
251
- background: var(--rel-gray-text-primary);
252
- color: #000000;
253
- }
254
-
255
- :host-context([data-theme="dark"]) button:focus-visible,
256
- :host-context(.dark) button:focus-visible {
257
- outline-color: var(--rel-color-text-primary);
258
- }
259
-
260
- :host-context([data-theme="dark"]) button.btn-secondary,
261
- :host-context(.dark) button.btn-secondary,
262
- :host-context([data-theme="dark"]) button.btn-icon.tone-secondary,
263
- :host-context(.dark) button.btn-icon.tone-secondary {
264
- background: var(--rel-color-bg-primary);
265
- border-color: var(--rel-color-border-strong);
266
- color: var(--rel-color-text-primary);
267
- }
268
-
269
- :host-context([data-theme="dark"]) button.btn-text,
270
- :host-context(.dark) button.btn-text {
271
- color: var(--rel-color-text-primary);
272
- }
273
-
274
- /* btn-link: blue in both modes — no dark override */
275
438
  `;
276
439
 
277
440
  connectedCallback() {
@@ -289,23 +452,42 @@ export class RelButton extends LitElement {
289
452
  }
290
453
 
291
454
  private handleHostClick = (e: MouseEvent) => {
292
- if (this.disabled) {
455
+ if (this.disabled || this.loading) {
293
456
  e.preventDefault();
294
457
  e.stopImmediatePropagation();
295
458
  }
296
459
  };
297
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
+
298
476
  private get classes() {
477
+ const style = this.styleVariant;
299
478
  return [
300
- `btn-${this.variant}`,
301
- `tone-${this.tone}`,
479
+ `btn-${style}`,
480
+ this.iconOnly && style !== "icon" ? "btn-icon-only" : "",
481
+ `tone-${this.effectiveTone}`,
302
482
  `size-${this.size}`,
303
483
  `border-${this.border}`,
304
- ].join(" ");
484
+ ]
485
+ .filter(Boolean)
486
+ .join(" ");
305
487
  }
306
488
 
307
489
  private handleClick(e: MouseEvent) {
308
- if (this.disabled) {
490
+ if (this.disabled || this.loading) {
309
491
  e.preventDefault();
310
492
  e.stopImmediatePropagation();
311
493
  return;
@@ -322,13 +504,18 @@ export class RelButton extends LitElement {
322
504
 
323
505
  return html`
324
506
  <button
507
+ part="base"
325
508
  class="${this.classes}"
326
509
  ?disabled=${this.disabled}
327
510
  @click=${this.handleClick}
328
511
  aria-disabled=${this.disabled}
512
+ aria-busy=${this.loading}
329
513
  aria-label=${ifDefined(this.ariaLabel ?? undefined)}
330
514
  >
331
- <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>
332
519
  </button>
333
520
  ${showBadge
334
521
  ? html`<rel-badge
@@ -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`