@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.
- package/dist/components/rel-accordion/README.md +13 -13
- package/dist/components/rel-accordion/rel-accordion.ts +80 -45
- package/dist/components/rel-alert/README.md +15 -15
- package/dist/components/rel-alert/rel-alert.ts +161 -159
- package/dist/components/rel-avatar/README.md +15 -15
- package/dist/components/rel-avatar/rel-avatar.ts +42 -38
- package/dist/components/rel-badge/README.md +5 -5
- package/dist/components/rel-badge/rel-badge.ts +69 -17
- package/dist/components/rel-breadcrumb/README.md +7 -7
- package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +16 -13
- package/dist/components/rel-button/README.md +20 -20
- package/dist/components/rel-button/rel-button.ts +327 -105
- package/dist/components/rel-calendar/README.md +5 -5
- package/dist/components/rel-calendar/rel-calendar.ts +61 -45
- package/dist/components/rel-card/README.md +6 -6
- package/dist/components/rel-card/rel-card.ts +88 -35
- package/dist/components/rel-cardmedia/README.md +9 -9
- package/dist/components/rel-cardmedia/rel-cardmedia.ts +27 -20
- package/dist/components/rel-carousel/README.md +3 -3
- package/dist/components/rel-carousel/rel-carousel.ts +174 -90
- package/dist/components/rel-chart/README.md +8 -8
- package/dist/components/rel-chart/rel-chart.ts +59 -41
- package/dist/components/rel-checkbox/README.md +7 -7
- package/dist/components/rel-checkbox/rel-checkbox.ts +61 -61
- package/dist/components/rel-chip/README.md +16 -16
- package/dist/components/rel-chip/rel-chip.ts +143 -111
- package/dist/components/rel-container/README.md +11 -11
- package/dist/components/rel-container/rel-container.ts +35 -9
- package/dist/components/rel-date-picker/README.md +43 -7
- package/dist/components/rel-date-picker/date-format.test.ts +43 -0
- package/dist/components/rel-date-picker/date-format.ts +47 -0
- package/dist/components/rel-date-picker/rel-date-picker.ts +257 -83
- package/dist/components/rel-divider/README.md +9 -9
- package/dist/components/rel-divider/rel-divider.ts +10 -9
- package/dist/components/rel-drawer/README.md +1 -1
- package/dist/components/rel-drawer/rel-drawer-footer.ts +1 -1
- package/dist/components/rel-drawer/rel-drawer.ts +56 -28
- package/dist/components/rel-kbd/README.md +20 -20
- package/dist/components/rel-kbd/rel-kbd.ts +119 -25
- package/dist/components/rel-layout/README.md +1 -1
- package/dist/components/rel-layout/rel-layout.ts +2 -2
- package/dist/components/rel-menu/README.md +15 -15
- package/dist/components/rel-menu/rel-menu.ts +155 -88
- package/dist/components/rel-menubar/README.md +20 -20
- package/dist/components/rel-menubar/rel-menubar.ts +47 -31
- package/dist/components/rel-nav-menu/README.md +21 -21
- package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +17 -14
- package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +33 -14
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +14 -7
- package/dist/components/rel-navbar/README.md +7 -7
- package/dist/components/rel-navbar/rel-navbar.ts +12 -11
- package/dist/components/rel-otp/README.md +3 -3
- package/dist/components/rel-otp/rel-otp.ts +46 -30
- package/dist/components/rel-pagination/README.md +18 -18
- package/dist/components/rel-pagination/rel-pagination.ts +141 -90
- package/dist/components/rel-popover/README.md +13 -13
- package/dist/components/rel-popover/rel-popover-content.ts +39 -14
- package/dist/components/rel-popover/rel-popover.ts +18 -7
- package/dist/components/rel-progress/README.md +5 -5
- package/dist/components/rel-progress/rel-progress.ts +16 -12
- package/dist/components/rel-radio/README.md +9 -9
- package/dist/components/rel-radio/rel-radio.ts +64 -50
- package/dist/components/rel-range-picker/rel-range-picker.ts +120 -117
- package/dist/components/rel-resizable/README.md +4 -4
- package/dist/components/rel-resizable/rel-resizable-group.ts +15 -4
- package/dist/components/rel-resizable/rel-resizable-handle.ts +13 -9
- package/dist/components/rel-resizable/rel-resizable-panel.ts +11 -0
- package/dist/components/rel-segmented-button/README.md +10 -10
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +39 -40
- package/dist/components/rel-select/README.md +14 -14
- package/dist/components/rel-select/rel-select.ts +120 -188
- package/dist/components/rel-sheet/README.md +10 -10
- package/dist/components/rel-sheet/rel-sheet.ts +82 -23
- package/dist/components/rel-sidebar/README.md +5 -5
- package/dist/components/rel-sidebar/rel-sidebar.ts +75 -60
- package/dist/components/rel-skeleton/rel-skeleton.ts +8 -13
- package/dist/components/rel-slider/README.md +10 -10
- package/dist/components/rel-slider/rel-slider.ts +230 -57
- package/dist/components/rel-snackbar/README.md +9 -9
- package/dist/components/rel-snackbar/rel-snackbar.ts +44 -32
- package/dist/components/rel-stepper/README.md +10 -10
- package/dist/components/rel-stepper/rel-stepper.ts +110 -46
- package/dist/components/rel-switch/README.md +5 -5
- package/dist/components/rel-switch/rel-switch.ts +23 -12
- package/dist/components/rel-tab/README.md +13 -13
- package/dist/components/rel-tab/rel-tab.ts +73 -23
- package/dist/components/rel-table/README.md +7 -7
- package/dist/components/rel-table/rel-table-body.ts +2 -1
- package/dist/components/rel-table/rel-table-caption.ts +10 -7
- package/dist/components/rel-table/rel-table-cell.ts +7 -6
- package/dist/components/rel-table/rel-table-footer.ts +4 -3
- package/dist/components/rel-table/rel-table-head.ts +11 -9
- package/dist/components/rel-table/rel-table-header.ts +2 -1
- package/dist/components/rel-table/rel-table-row.ts +5 -4
- package/dist/components/rel-table/rel-table.ts +16 -7
- package/dist/components/rel-textfield/README.md +11 -11
- package/dist/components/rel-textfield/rel-textfield.ts +718 -397
- package/dist/components/rel-toast/README.md +11 -11
- package/dist/components/rel-toast/rel-toast.ts +33 -30
- package/dist/components/rel-tooltip/README.md +7 -7
- package/dist/components/rel-tooltip/rel-tooltip.ts +160 -123
- package/dist/components/rel-topbar/rel-topbar-actions.ts +5 -2
- package/dist/components/rel-topbar/rel-topbar-brand.ts +12 -5
- package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +14 -8
- package/dist/components/rel-topbar/rel-topbar-dropdown.ts +8 -5
- package/dist/components/rel-topbar/rel-topbar-item.ts +27 -22
- package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +4 -1
- package/dist/components/rel-topbar/rel-topbar-profile.ts +71 -50
- package/dist/components/rel-topbar/rel-topbar-utilities.ts +5 -2
- package/dist/components/rel-topbar/rel-topbar.ts +37 -29
- package/dist/index.d.mts +227 -40
- package/dist/index.d.ts +227 -40
- package/dist/index.js +3930 -2247
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4311 -2629
- package/dist/index.mjs.map +1 -1
- package/dist/styles/vars.css +653 -346
- package/dist/vars-WY2XCF5G.css +653 -0
- package/package.json +3 -2
- package/dist/components/rel-range-picker/rel-range-picker.css +0 -349
- package/dist/styles/vars-v2.css +0 -525
- package/dist/vars-I2PC74XD.css +0 -346
- package/dist/vars-v2-VMGWTFJZ.css +0 -525
|
@@ -1,16 +1,42 @@
|
|
|
1
|
-
import "
|
|
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:
|
|
10
|
-
"default";
|
|
25
|
+
variant: ButtonVariant = "default";
|
|
11
26
|
|
|
12
27
|
@property({ type: String })
|
|
13
|
-
tone:
|
|
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-
|
|
33
|
-
font-weight: var(--rel-
|
|
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-
|
|
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-
|
|
48
|
-
font-
|
|
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
|
-
|
|
54
|
-
|
|
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-
|
|
60
|
-
font-
|
|
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-
|
|
174
|
+
border-radius: var(--rel-border-radius-full-rounded);
|
|
66
175
|
}
|
|
67
176
|
|
|
68
177
|
.border-small-corner-radius {
|
|
69
|
-
border-radius: var(--rel-
|
|
178
|
+
border-radius: var(--rel-border-radius-sm);
|
|
70
179
|
}
|
|
71
180
|
|
|
72
181
|
.border-no-corner-radius {
|
|
73
|
-
border-radius: var(--rel-
|
|
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-
|
|
88
|
-
color: var(--rel-
|
|
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
|
-
|
|
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-
|
|
103
|
-
color: var(--rel-
|
|
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-
|
|
107
|
-
color:
|
|
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-
|
|
111
|
-
color:
|
|
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-
|
|
116
|
-
color:
|
|
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-
|
|
120
|
-
color:
|
|
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-
|
|
124
|
-
color:
|
|
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-
|
|
129
|
-
border: 1px solid var(--rel-
|
|
130
|
-
color: var(--rel-
|
|
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-
|
|
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-
|
|
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-
|
|
152
|
-
color: var(--rel-
|
|
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-
|
|
157
|
-
color:
|
|
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-
|
|
162
|
-
color: var(--rel-
|
|
163
|
-
border: 1px solid var(--rel-
|
|
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-
|
|
167
|
-
color:
|
|
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-
|
|
172
|
-
color:
|
|
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-
|
|
339
|
+
background: var(--rel-action-brand-fill-hover);
|
|
178
340
|
}
|
|
179
341
|
|
|
180
342
|
button.btn-primary.tone-critical:hover:not(:disabled) {
|
|
181
|
-
|
|
343
|
+
background: var(--rel-action-danger-fill-hover);
|
|
182
344
|
}
|
|
183
345
|
|
|
184
346
|
button.btn-primary.tone-success:hover:not(:disabled) {
|
|
185
|
-
|
|
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
|
-
/*
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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
|
-
|
|
404
|
+
background: var(--rel-action-neutral-subtle-hover);
|
|
200
405
|
}
|
|
201
406
|
button.btn-icon.tone-primary:hover:not(:disabled) {
|
|
202
|
-
background: var(--rel-
|
|
407
|
+
background: var(--rel-action-brand-fill-hover);
|
|
203
408
|
}
|
|
204
409
|
button.btn-icon.tone-secondary:hover:not(:disabled) {
|
|
205
|
-
background: var(--rel-
|
|
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
|
-
|
|
414
|
+
background: var(--rel-action-success-fill-hover);
|
|
209
415
|
}
|
|
210
416
|
button.btn-icon.tone-critical:hover:not(:disabled) {
|
|
211
|
-
|
|
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-
|
|
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-${
|
|
276
|
-
|
|
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
|
-
]
|
|
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
|
-
|
|
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-
|
|
104
|
+
- `--rel-bg-*` / `--rel-text-*` / `--rel-border-*` semantic colors
|
|
105
105
|
- `--rel-spacing-*` for spacing
|
|
106
|
-
- `--rel-
|
|
107
|
-
- `--rel-
|
|
108
|
-
- Focus outline uses `--rel-
|
|
109
|
-
- Selected state uses `--rel-
|
|
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`
|