@reliability-design/web 1.0.6 → 1.2.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 (78) hide show
  1. package/dist/components/rel-accordion/rel-accordion.ts +20 -31
  2. package/dist/components/rel-alert/rel-alert.ts +81 -11
  3. package/dist/components/rel-avatar/rel-avatar.ts +6 -3
  4. package/dist/components/rel-badge/rel-badge.ts +8 -3
  5. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +6 -6
  6. package/dist/components/rel-button/rel-button.ts +118 -16
  7. package/dist/components/rel-calendar/rel-calendar.ts +16 -9
  8. package/dist/components/rel-card/rel-card.ts +9 -17
  9. package/dist/components/rel-cardmedia/rel-cardmedia.ts +20 -23
  10. package/dist/components/rel-carousel/rel-carousel.ts +596 -469
  11. package/dist/components/rel-checkbox/rel-checkbox.ts +98 -15
  12. package/dist/components/rel-chip/rel-chip.ts +291 -186
  13. package/dist/components/rel-container/rel-container.ts +2 -2
  14. package/dist/components/rel-date-picker/rel-date-picker.ts +23 -19
  15. package/dist/components/rel-divider/rel-divider.ts +12 -3
  16. package/dist/components/rel-drawer/rel-drawer-footer.ts +27 -0
  17. package/dist/components/rel-drawer/rel-drawer.ts +57 -113
  18. package/dist/components/rel-kbd/rel-kbd.ts +94 -118
  19. package/dist/components/rel-layout/rel-layout.ts +49 -58
  20. package/dist/components/rel-menu/rel-menu.ts +359 -327
  21. package/dist/components/rel-menubar/rel-menubar-menu.css +3 -0
  22. package/dist/components/rel-menubar/rel-menubar-menu.ts +2 -1
  23. package/dist/components/rel-menubar/rel-menubar.ts +602 -576
  24. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +240 -143
  25. package/dist/components/rel-nav-menu/rel-nav-menu.ts +227 -331
  26. package/dist/components/rel-navbar/rel-navbar.ts +13 -0
  27. package/dist/components/rel-otp/rel-otp.ts +22 -25
  28. package/dist/components/rel-pagination/rel-pagination.ts +112 -63
  29. package/dist/components/rel-popover/rel-popover-content.ts +6 -5
  30. package/dist/components/rel-popover/rel-popover.ts +42 -20
  31. package/dist/components/rel-progress/rel-progress.ts +10 -9
  32. package/dist/components/rel-radio/rel-radio.ts +228 -155
  33. package/dist/components/rel-range-picker/rel-range-picker.css +349 -0
  34. package/dist/components/rel-range-picker/rel-range-picker.ts +1079 -0
  35. package/dist/components/rel-resizable/rel-resizable-handle.ts +12 -15
  36. package/dist/components/rel-segmented-button/rel-segmented-button.ts +41 -20
  37. package/dist/components/rel-select/rel-select.ts +112 -27
  38. package/dist/components/rel-sheet/rel-sheet.ts +362 -683
  39. package/dist/components/rel-sidebar/rel-sidebar.ts +13 -5
  40. package/dist/components/rel-slider/rel-slider.ts +74 -29
  41. package/dist/components/rel-snackbar/rel-snackbar.ts +67 -37
  42. package/dist/components/rel-stepper/rel-stepper.ts +43 -31
  43. package/dist/components/rel-switch/rel-switch.ts +26 -12
  44. package/dist/components/rel-tab/rel-tab.ts +57 -37
  45. package/dist/components/rel-table/rel-table-body.ts +5 -0
  46. package/dist/components/rel-table/rel-table-caption.ts +6 -1
  47. package/dist/components/rel-table/rel-table-cell.ts +42 -49
  48. package/dist/components/rel-table/rel-table-footer.ts +5 -0
  49. package/dist/components/rel-table/rel-table-head.ts +13 -1
  50. package/dist/components/rel-table/rel-table-header.ts +5 -0
  51. package/dist/components/rel-table/rel-table-row.ts +7 -3
  52. package/dist/components/rel-table/rel-table.ts +8 -2
  53. package/dist/components/rel-textfield/rel-textfield.ts +153 -44
  54. package/dist/components/rel-toast/rel-toast.ts +56 -22
  55. package/dist/components/rel-tooltip/rel-tooltip.ts +52 -9
  56. package/dist/components/rel-topbar/rel-topbar-actions.ts +24 -0
  57. package/dist/components/rel-topbar/rel-topbar-brand.ts +47 -0
  58. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +63 -0
  59. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +28 -0
  60. package/dist/components/rel-topbar/rel-topbar-item.ts +320 -0
  61. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +24 -0
  62. package/dist/components/rel-topbar/rel-topbar-profile.ts +222 -0
  63. package/dist/components/rel-topbar/rel-topbar-utilities.ts +24 -0
  64. package/dist/components/rel-topbar/rel-topbar.ts +438 -0
  65. package/dist/index.d.mts +375 -411
  66. package/dist/index.d.ts +375 -411
  67. package/dist/index.js +7587 -7980
  68. package/dist/index.js.map +1 -1
  69. package/dist/index.mjs +7647 -8023
  70. package/dist/index.mjs.map +1 -1
  71. package/dist/rel-menubar-menu-BEEQDAXW.css +3 -0
  72. package/dist/styles/vars-v2.css +525 -0
  73. package/dist/styles/vars.css +79 -27
  74. package/dist/{vars-ITL2K3KO.css → vars-I2PC74XD.css} +79 -27
  75. package/dist/vars-v2-VMGWTFJZ.css +525 -0
  76. package/package.json +3 -1
  77. package/dist/components/rel-quote-page/README.md +0 -166
  78. package/dist/components/rel-quote-page/rel-quote-page.ts +0 -816
@@ -0,0 +1,63 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+ import "../../styles/vars-v2.css";
4
+
5
+ @customElement("rel-topbar-dropdown-item")
6
+ export class RelTopbarDropdownItem extends LitElement {
7
+ @property({ type: String }) label: string = "";
8
+ @property({ type: String }) href: string = "#";
9
+ @property({ type: Boolean, reflect: true }) active = false;
10
+
11
+ static styles = css`
12
+ .dropdown-item {
13
+ display: block;
14
+ padding: var(--rel-spacing-space-xs, 8px) var(--rel-spacing-space-md, 16px);
15
+ color: var(--rel-color-text-primary);
16
+ text-decoration: none;
17
+ font-size: var(--rel-fontSizes-md);
18
+ font-family: var(--rel-fontFamilies-primary, sans-serif);
19
+ }
20
+
21
+ .dropdown-item:hover {
22
+ background: var(--rel-color-bg-hover);
23
+ border-radius: 0;
24
+ }
25
+
26
+ :host([active]) .dropdown-item {
27
+ color: var(--rel-topbar-item-active-color, var(--rel-colors-green-tertiary, #00a86b));
28
+ font-weight: var(--rel-fontWeights-semibold, 600);
29
+ }
30
+
31
+ `;
32
+
33
+ private _handleClick(e: MouseEvent) {
34
+ if (this.hasAttribute("disabled")) {
35
+ e.preventDefault();
36
+ return;
37
+ }
38
+ e.preventDefault();
39
+ this.dispatchEvent(new CustomEvent("rel-select", {
40
+ detail: { value: this.href, label: this.label },
41
+ bubbles: true,
42
+ composed: true,
43
+ }));
44
+ this.dispatchEvent(new CustomEvent("rel-nav-select", {
45
+ detail: { value: this.href, label: this.label },
46
+ bubbles: true,
47
+ composed: true,
48
+ }));
49
+ this.active = true;
50
+ const siblings = this.parentElement?.querySelectorAll("rel-topbar-dropdown-item");
51
+ siblings?.forEach(s => { if (s !== this) (s as any).active = false; });
52
+ }
53
+
54
+ render() {
55
+ return html`<a href="${this.href}" class="dropdown-item" @click=${this._handleClick}>${this.label}</a>`;
56
+ }
57
+ }
58
+
59
+ declare global {
60
+ interface HTMLElementTagNameMap {
61
+ "rel-topbar-dropdown-item": RelTopbarDropdownItem;
62
+ }
63
+ }
@@ -0,0 +1,28 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement } from "lit/decorators.js";
3
+ import "../../styles/vars-v2.css";
4
+
5
+ @customElement("rel-topbar-dropdown")
6
+ export class RelTopbarDropdown extends LitElement {
7
+ static styles = css`
8
+ .dropdown {
9
+ display: flex;
10
+ flex-direction: column;
11
+ min-width: 160px;
12
+ background: var(--rel-color-bg-primary);
13
+ border-radius: var(--rel-borders-radius-border-radius-200);
14
+ box-shadow: var(--rel-shadow-200);
15
+ padding: var(--rel-spacing-space-2xs) 0;
16
+ }
17
+ `;
18
+
19
+ render() {
20
+ return html`<div class="dropdown"><slot></slot></div>`;
21
+ }
22
+ }
23
+
24
+ declare global {
25
+ interface HTMLElementTagNameMap {
26
+ "rel-topbar-dropdown": RelTopbarDropdown;
27
+ }
28
+ }
@@ -0,0 +1,320 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property, state } from "lit/decorators.js";
3
+ import "./rel-topbar-dropdown";
4
+ import "./rel-topbar-dropdown-item";
5
+ import "../../styles/vars-v2.css";
6
+ import "@reliability-design/icons/rel-icon";
7
+
8
+ /**
9
+ * `rel-topbar-item` — A navigation item within rel-topbar.
10
+ *
11
+ * Renders as an `<a>` when `href` is supplied, otherwise a `<button>`.
12
+ *
13
+ * Slots:
14
+ * (default) — Label text / icon (overrides `label` property)
15
+ * prefix — Icon/content before the label
16
+ * suffix — Icon/content after the label (e.g. badge)
17
+ * dropdown — Expandable panel content shown below the item
18
+ *
19
+ * Events:
20
+ * rel-select — fired when a non-disabled, non-expandable item is activated
21
+ * rel-toggle — fired when an expandable item is opened/closed { open: boolean }
22
+ */
23
+ @customElement("rel-topbar-item")
24
+ export class RelTopbarItem extends LitElement {
25
+ /** Accessible label — also shown as text when no default slot content */
26
+ @property({ type: String }) label?: string;
27
+
28
+ /** Navigation href — renders an anchor element when set */
29
+ @property({ type: String }) href?: string;
30
+
31
+ /** Marks this item as the current/active page */
32
+ @property({ type: Boolean, reflect: true }) active = false;
33
+
34
+ /** Prevents interaction */
35
+ @property({ type: Boolean, reflect: true }) disabled = false;
36
+
37
+ /** Shows a chevron and enables dropdown panel */
38
+ @property({ type: Boolean, reflect: true }) expandable = false;
39
+
40
+ /** Controlled open state of the dropdown panel */
41
+ @property({ type: Boolean, reflect: true }) open = false;
42
+
43
+ /** Optional string value forwarded in rel-select / rel-toggle events */
44
+ @property({ type: String }) value?: string;
45
+
46
+ /** @internal tracks whether dropdown slot has content */
47
+ @state() private _hasDropdown = false;
48
+
49
+ static styles = css`
50
+ /* ─── Component tokens ──────────────────────────────────────────── */
51
+ :host {
52
+ --_item-color: var(--rel-topbar-item-color, var(--rel-text-primary, #333));
53
+ --_item-active-color: var(--rel-topbar-item-active-color, var(--rel-colors-green-tertiary, #00a86b));
54
+ --_item-active-indicator: var(--rel-topbar-item-active-indicator, var(--rel-colors-green-primary, #00f49c));
55
+ --_item-hover-bg: var(--rel-topbar-item-hover-bg, var(--rel-surface-secondary, #fafafa));
56
+
57
+ display: inline-block;
58
+ position: relative;
59
+ font-family: var(--rel-fontFamilies-primary, Poppins, sans-serif);
60
+ }
61
+
62
+ :host([hidden]) {
63
+ display: none !important;
64
+ }
65
+
66
+ /* ─── Trigger (shared for <a> and <button>) ─────────────────────── */
67
+ .trigger {
68
+ display: inline-flex;
69
+ align-items: center;
70
+ gap: var(--rel-spacing-space-2xs, 4px);
71
+ padding: var(--rel-spacing-space-xs, 8px) var(--rel-spacing-space-sm, 12px);
72
+ border: none;
73
+ background: transparent;
74
+ border-radius: var(--rel-borders-radius-border-radius-200, 8px);
75
+ cursor: pointer;
76
+ color: var(--_item-color);
77
+ font-family: inherit;
78
+ font-size: var(--rel-fontSizes-md, 14px);
79
+ font-weight: var(--rel-fontWeights-medium, 500);
80
+ line-height: var(--rel-lineHeights-md, 20px);
81
+ text-decoration: none;
82
+ white-space: nowrap;
83
+ position: relative;
84
+ transition: background 0.15s ease, color 0.15s ease;
85
+ box-sizing: border-box;
86
+ }
87
+
88
+ .trigger::after {
89
+ content: "";
90
+ position: absolute;
91
+ bottom: -2px;
92
+ left: var(--rel-spacing-space-sm, 12px);
93
+ right: var(--rel-spacing-space-sm, 12px);
94
+ height: 2px;
95
+ border-radius: 1px;
96
+ background: transparent;
97
+ transition: background 0.15s ease;
98
+ }
99
+
100
+ /* Active state */
101
+ :host([active]) .trigger {
102
+ color: var(--_item-active-color);
103
+ font-weight: var(--rel-fontWeights-semibold, 600);
104
+ }
105
+
106
+ :host([active]) .trigger::after {
107
+ background: var(--_item-active-indicator);
108
+ }
109
+
110
+ /* Hover */
111
+ .trigger:hover:not(:disabled) {
112
+ background: var(--_item-hover-bg);
113
+ }
114
+
115
+ /* Focus */
116
+ .trigger:focus-visible {
117
+ outline: 2px solid var(--_item-active-indicator);
118
+ outline-offset: 2px;
119
+ }
120
+
121
+ /* Disabled */
122
+ :host([disabled]) {
123
+ cursor: not-allowed;
124
+ }
125
+
126
+ :host([disabled]) .trigger {
127
+ opacity: 0.45;
128
+ cursor: not-allowed;
129
+ pointer-events: none;
130
+ }
131
+
132
+ /* Open state (expandable) */
133
+ :host([open]) .trigger {
134
+ background: var(--_item-hover-bg);
135
+ }
136
+
137
+ /* ─── Prefix / suffix slots ─────────────────────────────────────── */
138
+ .prefix,
139
+ .suffix {
140
+ display: contents;
141
+ }
142
+
143
+ /* ─── Chevron icon ──────────────────────────────────────────────── */
144
+ .chevron {
145
+ display: inline-flex;
146
+ align-items: center;
147
+ flex-shrink: 0;
148
+ transition: transform 0.2s ease;
149
+ color: inherit;
150
+ }
151
+
152
+ :host([open]) .chevron {
153
+ transform: rotate(180deg);
154
+ }
155
+
156
+ /* ─── Dropdown panel ────────────────────────────────────────────── */
157
+ .dropdown {
158
+ position: absolute;
159
+ top: calc(100% + 6px);
160
+ left: 0;
161
+ min-width: 200px;
162
+ background: var(--rel-color-bg-primary);
163
+ border: var(--rel-borders-width-stroke-100, 1px) solid var(--rel-color-border-default);
164
+ border-radius: var(--rel-borders-radius-border-radius-200, 8px);
165
+ box-shadow: var(--rel-shadows-shadow-300);
166
+ padding: var(--rel-spacing-space-2xs, 4px);
167
+ z-index: 200;
168
+ display: none;
169
+ }
170
+
171
+ :host([open]) .dropdown {
172
+ display: block;
173
+ }
174
+ `;
175
+
176
+ connectedCallback() {
177
+ super.connectedCallback();
178
+ document.addEventListener("click", this._handleOutsideClick);
179
+ document.addEventListener("keydown", this._handleDocKeydown);
180
+ }
181
+
182
+ disconnectedCallback() {
183
+ super.disconnectedCallback();
184
+ document.removeEventListener("click", this._handleOutsideClick);
185
+ document.removeEventListener("keydown", this._handleDocKeydown);
186
+ }
187
+
188
+ private _handleOutsideClick = (e: MouseEvent) => {
189
+ if (!this.open) return;
190
+ const path = e.composedPath() as Node[];
191
+ if (!path.includes(this)) {
192
+ this._setOpen(false);
193
+ }
194
+ };
195
+
196
+ private _handleDocKeydown = (e: KeyboardEvent) => {
197
+ if (e.key === "Escape" && this.open) {
198
+ this._setOpen(false);
199
+ (this.shadowRoot?.querySelector(".trigger") as HTMLElement)?.focus();
200
+ }
201
+ };
202
+
203
+ private _setOpen(value: boolean) {
204
+ this.open = value;
205
+ this.dispatchEvent(
206
+ new CustomEvent("rel-toggle", {
207
+ detail: { open: this.open, value: this.value },
208
+ bubbles: true,
209
+ composed: true,
210
+ })
211
+ );
212
+ }
213
+
214
+ private _handleTriggerClick(e: MouseEvent) {
215
+ if (this.disabled) {
216
+ e.preventDefault();
217
+ return;
218
+ }
219
+
220
+ if (this.expandable || this._hasDropdown) {
221
+ e.preventDefault();
222
+ this._setOpen(!this.open);
223
+ return;
224
+ }
225
+
226
+ if (!this.href) {
227
+ e.preventDefault();
228
+ }
229
+
230
+ this.dispatchEvent(
231
+ new CustomEvent("rel-select", {
232
+ detail: { value: this.value, label: this.label },
233
+ bubbles: true,
234
+ composed: true,
235
+ })
236
+ );
237
+
238
+ // Bubble up to topbar
239
+ this.dispatchEvent(
240
+ new CustomEvent("rel-nav-select", {
241
+ detail: { value: this.value, label: this.label },
242
+ bubbles: true,
243
+ composed: true,
244
+ })
245
+ );
246
+ }
247
+
248
+ private _routeSlots(e: Event) {
249
+ const slot = e.target as HTMLSlotElement;
250
+ for (const el of slot.assignedElements({ flatten: true })) {
251
+ const targetSlot = (el as HTMLElement).tagName.toLowerCase() === "rel-topbar-dropdown" ? "dropdown" : "";
252
+ if ((el as HTMLElement).slot !== targetSlot) {
253
+ (el as HTMLElement).slot = targetSlot;
254
+ }
255
+ }
256
+ }
257
+
258
+ private _handleDropdownSlotChange(e: Event) {
259
+ const slot = e.target as HTMLSlotElement;
260
+ const nodes = slot.assignedElements();
261
+ this._hasDropdown = nodes.length > 0;
262
+ }
263
+
264
+ render() {
265
+ const isLink = !!this.href && !this.disabled;
266
+ const showChevron = this.expandable || this._hasDropdown;
267
+ const ariaCurrent = this.active ? "page" : undefined;
268
+ const ariaExpanded = showChevron ? (this.open ? "true" : "false") : undefined;
269
+ const ariaHasPopup = showChevron ? "true" : undefined;
270
+
271
+ const triggerContent = html`
272
+ <span class="prefix"><slot name="prefix"></slot></span>
273
+ <slot>${this.label ?? ""}</slot>
274
+ <span class="suffix"><slot name="suffix"></slot></span>
275
+ ${showChevron
276
+ ? html`<span class="chevron" aria-hidden="true">
277
+ <rel-icon name="chevron-down" size="sm"></rel-icon>
278
+ </span>`
279
+ : null}
280
+ `;
281
+
282
+ const trigger = isLink
283
+ ? html`
284
+ <a
285
+ class="trigger"
286
+ href=${this.href!}
287
+ aria-current=${ariaCurrent as string}
288
+ aria-expanded=${ariaExpanded as string}
289
+ aria-haspopup=${ariaHasPopup as string}
290
+ @click=${this._handleTriggerClick}
291
+ >
292
+ ${triggerContent}
293
+ </a>
294
+ `
295
+ : html`
296
+ <button
297
+ class="trigger"
298
+ type="button"
299
+ ?disabled=${this.disabled}
300
+ aria-current=${ariaCurrent as string}
301
+ aria-expanded=${ariaExpanded as string}
302
+ aria-haspopup=${ariaHasPopup as string}
303
+ @click=${this._handleTriggerClick}
304
+ >
305
+ ${triggerContent}
306
+ </button>
307
+ `;
308
+
309
+ return html`
310
+ <!-- router: auto-assigns slot="dropdown" for rel-topbar-dropdown children -->
311
+ <slot style="display:none" @slotchange=${this._routeSlots}></slot>
312
+
313
+ ${trigger}
314
+ <div class="dropdown" role="region">
315
+ <slot name="dropdown" @slotchange=${this._handleDropdownSlotChange}></slot>
316
+ </div>
317
+ `;
318
+ }
319
+ }
320
+
@@ -0,0 +1,24 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement } from "lit/decorators.js";
3
+ import "../../styles/vars-v2.css";
4
+
5
+ @customElement("rel-topbar-mobile-panel")
6
+ export class RelTopbarMobilePanel extends LitElement {
7
+ static styles = css`
8
+ .mobile-panel {
9
+ display: flex;
10
+ flex-direction: column;
11
+ width: 100%;
12
+ }
13
+ `;
14
+
15
+ render() {
16
+ return html`<div class="mobile-panel"><slot></slot></div>`;
17
+ }
18
+ }
19
+
20
+ declare global {
21
+ interface HTMLElementTagNameMap {
22
+ "rel-topbar-mobile-panel": RelTopbarMobilePanel;
23
+ }
24
+ }
@@ -0,0 +1,222 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+ import "../../styles/vars-v2.css";
4
+ import "../rel-avatar/rel-avatar.js";
5
+
6
+ export type TopbarProfileType = "avatar" | "carrier";
7
+ export type TopbarRewardTier = "gold" | "silver" | "platinum" | "bronze" | "regular";
8
+
9
+ /**
10
+ * `rel-topbar-profile` — Profile / user tile for the topbar right region.
11
+ *
12
+ * type="avatar" Customer avatar with initials circle + optional name/subtitle.
13
+ * type="carrier" Carrier reward tile with reward-tier badge + name/subtitle.
14
+ */
15
+ @customElement("rel-topbar-profile")
16
+ export class RelTopbarProfile extends LitElement {
17
+ /** Visual mode */
18
+ @property({ type: String, reflect: true }) type: TopbarProfileType = "avatar";
19
+
20
+ /** One or two initials shown in the avatar circle */
21
+ @property({ type: String }) initials?: string;
22
+
23
+ /** Primary display name */
24
+ @property({ type: String }) name?: string;
25
+
26
+ /** Secondary line (role, plan, etc.) */
27
+ @property({ type: String }) subtitle?: string;
28
+
29
+ /** Reward tier — only meaningful when type="carrier" */
30
+ @property({ type: String, reflect: true }) reward: TopbarRewardTier = "regular";
31
+
32
+ /** Compact mode — avatar only, no text */
33
+ @property({ type: Boolean, reflect: true }) compact = false;
34
+
35
+ static styles = css`
36
+ /* ─── Component tokens (reward tier palette) ────────────────────── */
37
+ :host {
38
+ --_profile-radius: var(--rel-borders-radius-border-radius-500, 1000px);
39
+ --_avatar-size: 36px;
40
+ --_avatar-size-sm: 28px;
41
+ --_avatar-bg: var(--rel-colors-green-primary, #00f49c);
42
+ --_avatar-color: var(--rel-color-text-primary);
43
+
44
+ /* Reward tier colours (reference existing token palette) */
45
+ --rel-topbar-reward-gold: var(--rel-colors-gold, #d6ba00);
46
+ --rel-topbar-reward-gold-bg: var(--rel-colors-gold-light, #f6efd7);
47
+ --rel-topbar-reward-silver: var(--rel-colors-silver, #bababa);
48
+ --rel-topbar-reward-silver-bg: var(--rel-colors-silver-secondary, #f0f0f0);
49
+ --rel-topbar-reward-platinum: var(--rel-colors-platinum, #8392c9);
50
+ --rel-topbar-reward-platinum-bg: var(--rel-colors-platinum-secondary, #e4e6f2);
51
+ --rel-topbar-reward-bronze: var(--rel-colors-bronze, #7d5300);
52
+ --rel-topbar-reward-bronze-bg: var(--rel-colors-bronze-secondary, #e2dac7);
53
+ --rel-topbar-reward-regular: var(--rel-colors-gray-divider, #bdbdbd);
54
+ --rel-topbar-reward-regular-bg: var(--rel-color-bg-secondary);
55
+
56
+ display: inline-flex;
57
+ align-items: center;
58
+ gap: var(--rel-spacing-space-xs, 8px);
59
+ font-family: var(--rel-fontFamilies-primary, Poppins, sans-serif);
60
+ cursor: pointer;
61
+ }
62
+
63
+ /* ─── Name / subtitle stack ─────────────────────────────────────── */
64
+ .text-stack {
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: 0;
68
+ min-width: 0;
69
+ }
70
+
71
+ :host([compact]) .text-stack {
72
+ display: none;
73
+ }
74
+
75
+ .name {
76
+ font-size: var(--rel-fontSizes-md, 14px);
77
+ font-weight: var(--rel-fontWeights-medium, 500);
78
+ color: var(--rel-color-text-primary);
79
+ line-height: var(--rel-lineHeights-md, 20px);
80
+ white-space: nowrap;
81
+ overflow: hidden;
82
+ text-overflow: ellipsis;
83
+ }
84
+
85
+ .subtitle {
86
+ font-size: var(--rel-fontSizes-sm, 12px);
87
+ font-weight: var(--rel-fontWeights-regular, 400);
88
+ color: var(--rel-color-text-placeholder);
89
+ line-height: var(--rel-lineHeights-sm, 18px);
90
+ white-space: nowrap;
91
+ overflow: hidden;
92
+ text-overflow: ellipsis;
93
+ }
94
+
95
+ /* ─── Carrier reward badge ──────────────────────────────────────── */
96
+ .reward-badge {
97
+ display: inline-flex;
98
+ align-items: center;
99
+ gap: var(--rel-spacing-space-3xs, 2px);
100
+ padding: 2px var(--rel-spacing-space-2xs, 4px);
101
+ border-radius: var(--rel-borders-radius-border-radius-100, 2px);
102
+ font-size: var(--rel-fontSizes-xs, 10px);
103
+ font-weight: var(--rel-fontWeights-bold, 700);
104
+ text-transform: uppercase;
105
+ letter-spacing: 0.4px;
106
+ flex-shrink: 0;
107
+ }
108
+
109
+ /* Gold */
110
+ :host([reward="gold"]) .reward-badge {
111
+ background: var(--rel-topbar-reward-gold-bg);
112
+ color: var(--rel-topbar-reward-gold);
113
+ }
114
+
115
+ /* Silver */
116
+ :host([reward="silver"]) .reward-badge {
117
+ background: var(--rel-topbar-reward-silver-bg);
118
+ color: var(--rel-colors-gray-primary, #333);
119
+ }
120
+
121
+ /* Platinum */
122
+ :host([reward="platinum"]) .reward-badge {
123
+ background: var(--rel-topbar-reward-platinum-bg);
124
+ color: var(--rel-topbar-reward-platinum);
125
+ }
126
+
127
+ /* Bronze */
128
+ :host([reward="bronze"]) .reward-badge {
129
+ background: var(--rel-topbar-reward-bronze-bg);
130
+ color: var(--rel-topbar-reward-bronze);
131
+ }
132
+
133
+ /* Regular */
134
+ :host([reward="regular"]) .reward-badge {
135
+ background: var(--rel-topbar-reward-regular-bg);
136
+ color: var(--rel-colors-gray-primary, #333);
137
+ }
138
+
139
+ /* ─── Carrier tile shell ────────────────────────────────────────── */
140
+ .carrier-tile {
141
+ display: flex;
142
+ align-items: center;
143
+ gap: var(--rel-spacing-space-xs, 8px);
144
+ padding: var(--rel-spacing-space-2xs, 4px) var(--rel-spacing-space-xs, 8px);
145
+ border-radius: var(--rel-borders-radius-border-radius-200, 8px);
146
+ background: var(--rel-surface-secondary, #fafafa);
147
+ border: var(--rel-borders-width-stroke-100, 1px) solid var(--rel-color-border-default);
148
+ }
149
+
150
+ :host([compact]) .carrier-tile {
151
+ padding: var(--rel-spacing-space-2xs, 4px);
152
+ }
153
+
154
+ .carrier-meta {
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: 1px;
158
+ min-width: 0;
159
+ }
160
+
161
+ :host([compact]) .carrier-meta {
162
+ display: none;
163
+ }
164
+ `;
165
+
166
+ render() {
167
+ if (this.type === "carrier") {
168
+ return this._renderCarrier();
169
+ }
170
+ return this._renderAvatar();
171
+ }
172
+
173
+ private _renderAvatar() {
174
+ return html`
175
+ <rel-avatar
176
+ type="text"
177
+ text=${this.initials ?? ""}
178
+ size=${this.compact ? "xs" : "sm"}
179
+ bg=${this._avatarBg()}
180
+ ></rel-avatar>
181
+ ${!this.compact && (this.name || this.subtitle)
182
+ ? html`
183
+ <div class="text-stack">
184
+ ${this.name ? html`<span class="name">${this.name}</span>` : null}
185
+ ${this.subtitle ? html`<span class="subtitle">${this.subtitle}</span>` : null}
186
+ </div>
187
+ `
188
+ : null}
189
+ `;
190
+ }
191
+
192
+ private _avatarBg(): string {
193
+ const map: Record<string, string> = {
194
+ gold: "var(--rel-colors-gold)",
195
+ platinum: "var(--rel-colors-platinum)",
196
+ silver: "var(--rel-colors-silver)",
197
+ bronze: "var(--rel-colors-bronze)",
198
+ regular: "var(--rel-colors-green-primary)",
199
+ };
200
+ return map[this.reward] ?? "var(--rel-colors-green-primary)";
201
+ }
202
+
203
+ private _renderCarrier() {
204
+ const rewardLabel = this.reward.charAt(0).toUpperCase() + this.reward.slice(1);
205
+
206
+ return html`
207
+ <div class="carrier-tile" role="img" aria-label="${this.name ?? "Profile"} – ${rewardLabel} member">
208
+ <rel-avatar
209
+ type="text"
210
+ text=${this.initials ?? ""}
211
+ size="sm"
212
+ bg=${this._avatarBg()}
213
+ ></rel-avatar>
214
+ <div class="carrier-meta">
215
+ ${this.name ? html`<span class="name">${this.name}</span>` : null}
216
+ <span class="reward-badge">${rewardLabel}</span>
217
+ ${this.subtitle ? html`<span class="subtitle">${this.subtitle}</span>` : null}
218
+ </div>
219
+ </div>
220
+ `;
221
+ }
222
+ }
@@ -0,0 +1,24 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement } from "lit/decorators.js";
3
+ import "../../styles/vars-v2.css";
4
+
5
+ @customElement("rel-topbar-utilities")
6
+ export class RelTopbarUtilities extends LitElement {
7
+ static styles = css`
8
+ .utilities {
9
+ display: inline-flex;
10
+ align-items: center;
11
+ gap: var(--rel-spacing-space-xs, 8px);
12
+ }
13
+ `;
14
+
15
+ render() {
16
+ return html`<div class="utilities"><slot></slot></div>`;
17
+ }
18
+ }
19
+
20
+ declare global {
21
+ interface HTMLElementTagNameMap {
22
+ "rel-topbar-utilities": RelTopbarUtilities;
23
+ }
24
+ }