@reliability-design/web 1.2.0 → 1.3.1

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 (41) hide show
  1. package/dist/components/rel-accordion/rel-accordion.ts +289 -135
  2. package/dist/components/rel-alert/rel-alert.ts +8 -1
  3. package/dist/components/rel-avatar/README.md +15 -4
  4. package/dist/components/rel-avatar/rel-avatar.ts +138 -96
  5. package/dist/components/rel-badge/README.md +7 -0
  6. package/dist/components/rel-badge/rel-badge.ts +61 -8
  7. package/dist/components/rel-button/rel-button.ts +64 -7
  8. package/dist/components/rel-chip/README.md +7 -0
  9. package/dist/components/rel-chip/rel-chip.ts +250 -130
  10. package/dist/components/rel-date-picker/rel-date-picker.ts +116 -14
  11. package/dist/components/rel-drawer/rel-drawer.ts +5 -2
  12. package/dist/components/rel-menubar/rel-menubar.ts +1 -0
  13. package/dist/components/rel-pagination/rel-pagination.ts +442 -425
  14. package/dist/components/rel-radio/rel-radio.ts +16 -16
  15. package/dist/components/rel-range-picker/rel-range-picker.css +349 -349
  16. package/dist/components/rel-range-picker/rel-range-picker.ts +1080 -1079
  17. package/dist/components/rel-select/README.md +34 -28
  18. package/dist/components/rel-select/rel-select.ts +1015 -346
  19. package/dist/components/rel-sheet/rel-sheet.ts +6 -3
  20. package/dist/components/rel-skeleton/rel-skeleton.ts +139 -0
  21. package/dist/components/rel-tab/rel-tab.ts +10 -4
  22. package/dist/components/rel-textfield/rel-textfield.ts +637 -349
  23. package/dist/components/rel-toast/rel-toast.ts +1 -6
  24. package/dist/components/rel-topbar/rel-topbar-actions.ts +4 -0
  25. package/dist/components/rel-topbar/rel-topbar-brand.ts +4 -0
  26. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +4 -0
  27. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +4 -0
  28. package/dist/components/rel-topbar/rel-topbar-item.ts +4 -0
  29. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +4 -0
  30. package/dist/components/rel-topbar/rel-topbar-profile.ts +3 -0
  31. package/dist/components/rel-topbar/rel-topbar-utilities.ts +4 -0
  32. package/dist/components/rel-topbar/rel-topbar.ts +6 -0
  33. package/dist/index.d.mts +239 -22
  34. package/dist/index.d.ts +239 -22
  35. package/dist/index.js +2609 -981
  36. package/dist/index.js.map +1 -1
  37. package/dist/index.mjs +2621 -994
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/styles/vars.css +17 -0
  40. package/dist/{vars-I2PC74XD.css → vars-F54UGCHM.css} +17 -0
  41. package/package.json +1 -1
@@ -1,96 +1,138 @@
1
- import "../../styles/vars-v2.css";
2
- import { LitElement, html, css } from "lit";
3
- import { customElement, property } from "lit/decorators.js";
4
-
5
- @customElement("rel-avatar")
6
- export class RelAvatar extends LitElement {
7
- @property({ type: String, reflect: true })
8
- size: "lg" | "md" | "sm" | "xs" = "md";
9
-
10
- @property({ type: String, reflect: true })
11
- type: "image" | "text" | "icon" = "image";
12
-
13
- @property({ type: String }) src = "";
14
- @property({ type: String }) text = "";
15
- @property({ type: String }) alt = "Avatar";
16
- @property({ type: String }) bg: string = "";
17
-
18
- static styles = css`
19
- :host {
20
- display: inline-flex;
21
- font-family: var(--rel-fontFamilies-primary);
22
- }
23
-
24
- .avatar {
25
- display: inline-flex;
26
- align-items: center;
27
- justify-content: center;
28
- border-radius: 50%;
29
- overflow: hidden;
30
-
31
- background-color: var(--rel-colors-gray-disabled-text);
32
- color: var(--rel-avatar-color, #ffffff);
33
- font-weight: var(--rel-fontWeights-bold);
34
-
35
- user-select: none;
36
- text-transform: uppercase;
37
- }
38
-
39
- /* Sizes */
40
- .avatar-lg {
41
- width: 40px;
42
- height: 40px;
43
- font-size: var(--rel-fontSizes-xl);
44
- }
45
-
46
- .avatar-md {
47
- width: 32px;
48
- height: 32px;
49
- font-size: var(--rel-fontSizes-lg);
50
- }
51
-
52
- .avatar-sm {
53
- width: 24px;
54
- height: 24px;
55
- font-size: var(--rel-fontSizes-md);
56
- }
57
-
58
- .avatar-xs {
59
- width: 18px;
60
- height: 18px;
61
- font-size: var(--rel-fontSizes-xs);
62
- }
63
-
64
- img {
65
- width: 100%;
66
- height: 100%;
67
- object-fit: cover;
68
- }
69
-
70
- .icon-slot {
71
- width: 100%;
72
- height: 100%;
73
- display: inline-flex;
74
- align-items: center;
75
- justify-content: center;
76
- color: #ffffff;
77
- }
78
-
79
- .icon-slot ::slotted(svg) {
80
- width: 50%;
81
- height: 50%;
82
- }
83
- `;
84
-
85
- render() {
86
- return html`
87
- <div class="avatar avatar-${this.size}" style=${this.bg ? `background-color: ${this.bg}` : ""}>
88
- ${this.type === "image" && this.src
89
- ? html`<img src=${this.src} alt=${this.alt} />`
90
- : this.type === "text"
91
- ? html`<span>${this.text}</span>`
92
- : html`<span class="icon-slot"><slot></slot></span>`}
93
- </div>
94
- `;
95
- }
96
- }
1
+ import "../../styles/vars-v2.css";
2
+ import { LitElement, html, css } from "lit";
3
+ import { customElement, property } from "lit/decorators.js";
4
+
5
+ @customElement("rel-avatar")
6
+ export class RelAvatar extends LitElement {
7
+ @property({ type: String, reflect: true })
8
+ size: "lg" | "md" | "sm" | "xs" = "md";
9
+
10
+ @property({ type: String, reflect: true })
11
+ type: "image" | "text" | "icon" = "image";
12
+
13
+ @property({ type: String, reflect: true })
14
+ variant: "fill" | "subtle" | "outline" = "fill";
15
+
16
+ @property({ type: String }) src = "";
17
+ @property({ type: String }) text = "";
18
+ @property({ type: String }) alt = "Avatar";
19
+ @property({ type: String }) bg: string = "";
20
+ @property({ type: String }) color: string = "";
21
+
22
+ static styles = css`
23
+ :host {
24
+ display: inline-flex;
25
+ font-family: var(--rel-fontFamilies-primary);
26
+ }
27
+
28
+ .avatar {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ border-radius: 50%;
33
+ overflow: hidden;
34
+ box-sizing: border-box;
35
+ border: var(--rel-borders-width-stroke-100, 1px) solid transparent;
36
+ font-weight: var(--rel-fontWeights-semibold);
37
+ user-select: none;
38
+ text-transform: uppercase;
39
+ }
40
+
41
+ /* ── Variants ─────────────────────────────────────────────── */
42
+
43
+ :host([variant="fill"]) .avatar {
44
+ background-color: var(--rel-avatar-bg-fill, #0b0b0b);
45
+ color: var(--rel-avatar-color-fill, #ffffff);
46
+ }
47
+
48
+ :host([variant="subtle"]) .avatar {
49
+ background-color: var(--rel-avatar-bg-subtle, #f5f5f5);
50
+ color: var(--rel-avatar-color-subtle, #0b0b0b);
51
+ }
52
+
53
+ :host([variant="outline"]) .avatar {
54
+ background-color: transparent;
55
+ border-color: var(--rel-avatar-border-outline, var(--rel-color-border-default));
56
+ color: var(--rel-avatar-color-outline, #0b0b0b);
57
+ }
58
+
59
+ /* ── Sizes ────────────────────────────────────────────────── */
60
+
61
+ .avatar-lg {
62
+ width: 40px;
63
+ height: 40px;
64
+ font-size: var(--rel-fontSizes-lg); /* 20px */
65
+ }
66
+
67
+ .avatar-md {
68
+ width: 32px;
69
+ height: 32px;
70
+ font-size: 16px;
71
+ }
72
+
73
+ .avatar-sm {
74
+ width: 24px;
75
+ height: 24px;
76
+ font-size: var(--rel-fontSizes-sm); /* 12px */
77
+ }
78
+
79
+ .avatar-xs {
80
+ width: 20px;
81
+ height: 20px;
82
+ font-size: var(--rel-fontSizes-xs); /* 10px */
83
+ }
84
+
85
+ /* ── Image ────────────────────────────────────────────────── */
86
+
87
+ img {
88
+ width: 100%;
89
+ height: 100%;
90
+ object-fit: cover;
91
+ }
92
+
93
+ /* ── Icon slot ────────────────────────────────────────────── */
94
+
95
+ .icon-slot {
96
+ width: 100%;
97
+ height: 100%;
98
+ display: inline-flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ color: inherit;
102
+ }
103
+
104
+ .icon-slot ::slotted(svg) {
105
+ width: 50%;
106
+ height: 50%;
107
+ }
108
+
109
+ /* ── Dark mode ────────────────────────────────────────────────
110
+ :host() can never be preceded by a combinator inside a shadow-tree
111
+ stylesheet, so ":host-context(dark) :host([variant=...]) .avatar"
112
+ (the previous approach) is invalid and silently matches nothing.
113
+ Instead, redefine the same custom properties the variant rules
114
+ above already read via var(--token, fallback) — a plain
115
+ :host-context() selector with no compounding, guaranteed valid. ── */
116
+
117
+ :host-context([data-theme="dark"]) {
118
+ --rel-avatar-bg-fill: #ffffff;
119
+ --rel-avatar-color-fill: #0b0b0b;
120
+ --rel-avatar-bg-subtle: #2b2b2b;
121
+ --rel-avatar-color-subtle: #f5f5f5;
122
+ --rel-avatar-border-outline: #525252;
123
+ --rel-avatar-color-outline: #f5f5f5;
124
+ }
125
+ `;
126
+
127
+ render() {
128
+ return html`
129
+ <div class="avatar avatar-${this.size}" style=${[this.bg ? `background-color:${this.bg}` : "", this.color ? `color:${this.color}` : ""].filter(Boolean).join(";")}>
130
+ ${this.type === "image" && this.src
131
+ ? html`<img src=${this.src} alt=${this.alt} />`
132
+ : this.type === "text"
133
+ ? html`<span>${this.text}</span>`
134
+ : html`<span class="icon-slot"><slot></slot></span>`}
135
+ </div>
136
+ `;
137
+ }
138
+ }
@@ -25,6 +25,11 @@ A simple notification badge component displaying numeric counts or dot indicator
25
25
  <rel-badge number="1" size="sm"></rel-badge>
26
26
  <rel-badge number="2" size="md"></rel-badge>
27
27
  <rel-badge number="3" size="lg"></rel-badge>
28
+
29
+ <!-- Custom colors -->
30
+ <rel-badge number="3" color="#7d7d7d"></rel-badge>
31
+ <rel-badge number="3" color="teal" text-color="#1a1a1a"></rel-badge>
32
+ <rel-badge variant="dot" color="lavender"></rel-badge>
28
33
  ```
29
34
 
30
35
  ## API
@@ -35,6 +40,8 @@ A simple notification badge component displaying numeric counts or dot indicator
35
40
  | `number` | `string` | `""` | Text to display in number variant (can be numeric or text like "99+") |
36
41
  | `size` | `"sm" \| "md" \| "lg"` (reflected) | `"md"` | Size variant |
37
42
  | `variant` | `"dot" \| "number"` (reflected) | `"number"` | Display mode (number shows text, dot shows indicator only) |
43
+ | `color` | `string` (reflected) | `""` | Direct CSS color override for the badge background. Accepts any valid CSS color (hex, rgb, hsl, named, `var(...)`). Falls back to the default token when empty |
44
+ | `text-color` | `string` (reflected) | `""` | Direct CSS color override for the badge text/icon color. Accepts any valid CSS color. Falls back to white when empty |
38
45
 
39
46
  ## Events
40
47
  None.
@@ -16,6 +16,24 @@ export class RelBadge extends LitElement {
16
16
  @property({ type: String, reflect: true })
17
17
  variant: "dot" | "number" = "number";
18
18
 
19
+ /**
20
+ * Direct CSS color override for the badge background. Accepts any valid
21
+ * CSS color value: hex, rgb(...), hsl(...), named color, or var(--token).
22
+ *
23
+ * Clear it (set to `''`) to fall back to the default token.
24
+ */
25
+ @property({ type: String, reflect: true })
26
+ color = "";
27
+
28
+ /**
29
+ * Direct CSS color override for the badge text/icon color. Accepts any
30
+ * valid CSS color value: hex, rgb(...), hsl(...), named color, or var(--token).
31
+ *
32
+ * Clear it (set to `''`) to fall back to the default (white).
33
+ */
34
+ @property({ type: String, reflect: true, attribute: "text-color" })
35
+ textColor = "";
36
+
19
37
  static styles = css`
20
38
  :host {
21
39
  display: inline-flex;
@@ -25,46 +43,63 @@ export class RelBadge extends LitElement {
25
43
  display: inline-flex;
26
44
  align-items: center;
27
45
  justify-content: center;
46
+ box-sizing: border-box;
28
47
  font-family: var(--rel-fontFamilies-primary, sans-serif);
29
48
  font-weight: var(--rel-fontWeights-semibold, 600);
30
- background-color: var(--rel-error-surface-default);
31
- color: #ffffff;
32
- border-radius: 50%;
49
+ background-color: var(--rel-badge-bg, var(--rel-error-surface-default));
50
+ color: var(--rel-badge-color, #ffffff);
51
+ /* Large fixed radius (not 50%) — stays a perfect circle for 1-2 digits,
52
+ but becomes a pill instead of clipping when content like "999+"
53
+ is wider than the minimum circle. */
54
+ border-radius: 999px;
55
+ white-space: nowrap;
33
56
  }
34
57
 
35
- /* NUMBER BADGES */
58
+ /* NUMBER BADGES — min-width (not width) lets multi-character content
59
+ like "999+" grow the badge into a pill instead of overflowing a fixed circle. */
36
60
  .badge-sm {
37
- width: 20px;
61
+ min-width: 20px;
38
62
  height: 20px;
63
+ padding: 0 4px;
39
64
  font-size: var(--rel-fontSizes-xs, 12px);
40
65
  }
41
66
 
42
67
  .badge-md {
43
- width: 28px;
68
+ min-width: 28px;
44
69
  height: 28px;
70
+ padding: 0 6px;
45
71
  font-size: var(--rel-fontSizes-sm, 14px);
46
72
  }
47
73
 
48
74
  .badge-lg {
49
- width: 40px;
75
+ min-width: 40px;
50
76
  height: 40px;
77
+ padding: 0 8px;
51
78
  font-size: var(--rel-fontSizes-lg, 20px);
52
79
  }
53
80
 
54
- /* DOT BADGES */
81
+ /* DOT BADGES — explicitly reset min-width/padding inherited from the
82
+ number-badge size rules above; otherwise min-width wins over this
83
+ smaller width and the dot gets stretched into a squashed pill. */
55
84
  .badge-dot.badge-sm {
56
85
  width: 8px;
57
86
  height: 8px;
87
+ min-width: 0;
88
+ padding: 0;
58
89
  }
59
90
 
60
91
  .badge-dot.badge-md {
61
92
  width: 10px;
62
93
  height: 10px;
94
+ min-width: 0;
95
+ padding: 0;
63
96
  }
64
97
 
65
98
  .badge-dot.badge-lg {
66
99
  width: 12px;
67
100
  height: 12px;
101
+ min-width: 0;
102
+ padding: 0;
68
103
  }
69
104
  `;
70
105
 
@@ -72,6 +107,24 @@ export class RelBadge extends LitElement {
72
107
  return ["badge", `badge-${this.size}`, `badge-${this.variant}`].join(" ");
73
108
  }
74
109
 
110
+ override updated(changedProperties: Map<PropertyKey, unknown>): void {
111
+ if (changedProperties.has("color")) {
112
+ if (this.color) {
113
+ this.style.setProperty("--rel-badge-bg", this.color);
114
+ } else {
115
+ this.style.removeProperty("--rel-badge-bg");
116
+ }
117
+ }
118
+
119
+ if (changedProperties.has("textColor")) {
120
+ if (this.textColor) {
121
+ this.style.setProperty("--rel-badge-color", this.textColor);
122
+ } else {
123
+ this.style.removeProperty("--rel-badge-color");
124
+ }
125
+ }
126
+ }
127
+
75
128
  render() {
76
129
  return html`
77
130
  <span class="${this.classes}" aria-label=${this.ariaLabel || `${this.number} ${this.suffix}`}>
@@ -1,4 +1,5 @@
1
1
  import "../../styles/vars-v2.css";
2
+ import "../rel-badge/rel-badge";
2
3
  import { LitElement, html, css } from "lit";
3
4
  import { customElement, property } from "lit/decorators.js";
4
5
  import { ifDefined } from "lit/directives/if-defined.js";
@@ -23,9 +24,33 @@ export class RelButton extends LitElement {
23
24
 
24
25
  @property({ type: String, attribute: "aria-label" }) ariaLabel: string | null = null;
25
26
 
27
+ /** Numeric/overflow text shown in a corner badge (e.g. "3", "999+"). Ignored when badgeDot is set. */
28
+ @property({ type: String }) badge = "";
29
+
30
+ /** Shows a small corner dot instead of a numbered badge — takes precedence over `badge`. */
31
+ @property({ type: Boolean, attribute: "badge-dot" }) badgeDot = false;
32
+
26
33
  static styles = css`
27
34
  :host {
28
35
  display: inline-flex;
36
+ position: relative;
37
+ }
38
+
39
+ .btn-badge {
40
+ position: absolute;
41
+ top: -6px;
42
+ right: -6px;
43
+ pointer-events: none;
44
+ }
45
+
46
+ /* Icon buttons are a 40px circle (radius 20). An 8px dot's center needs to
47
+ land ON that circle's edge at the 45° corner point — which works out to
48
+ roughly 2px inward from each edge (R - R/√2 - dot-radius ≈ 1.9px), not
49
+ outside it like the numbered badge. Negative offsets here would float
50
+ the dot in the empty bounding-box corner outside the visible circle. */
51
+ .btn-badge[variant="dot"] {
52
+ top: 2px;
53
+ right: 2px;
29
54
  }
30
55
 
31
56
  button {
@@ -76,6 +101,10 @@ export class RelButton extends LitElement {
76
101
  /* Disabled */
77
102
  :host([disabled]) {
78
103
  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;
79
108
  }
80
109
 
81
110
  :host([disabled]) button {
@@ -245,6 +274,27 @@ export class RelButton extends LitElement {
245
274
  /* btn-link: blue in both modes — no dark override */
246
275
  `;
247
276
 
277
+ connectedCallback() {
278
+ super.connectedCallback();
279
+ // Capture-phase, on the host itself: blocks programmatic el.click() calls
280
+ // and any other path that bypasses the inner button's pointer-events:none
281
+ // (e.g. CSS pointer-events doesn't stop .click(), which targets the host
282
+ // directly regardless of hit-testing).
283
+ this.addEventListener("click", this.handleHostClick, { capture: true });
284
+ }
285
+
286
+ disconnectedCallback() {
287
+ this.removeEventListener("click", this.handleHostClick, { capture: true });
288
+ super.disconnectedCallback();
289
+ }
290
+
291
+ private handleHostClick = (e: MouseEvent) => {
292
+ if (this.disabled) {
293
+ e.preventDefault();
294
+ e.stopImmediatePropagation();
295
+ }
296
+ };
297
+
248
298
  private get classes() {
249
299
  return [
250
300
  `btn-${this.variant}`,
@@ -261,16 +311,15 @@ export class RelButton extends LitElement {
261
311
  return;
262
312
  }
263
313
 
264
- // Re-emit click from host (Angular-friendly)
265
- this.dispatchEvent(
266
- new MouseEvent("click", {
267
- bubbles: true,
268
- composed: true,
269
- })
270
- );
314
+ // Native click events are already composed: true, so this bubbles
315
+ // through the shadow boundary to the host on its own — no re-dispatch
316
+ // needed. (A manual re-dispatch here previously caused every @click
317
+ // listener on <rel-button> to fire twice per real click.)
271
318
  }
272
319
 
273
320
  render() {
321
+ const showBadge = this.badgeDot || !!this.badge;
322
+
274
323
  return html`
275
324
  <button
276
325
  class="${this.classes}"
@@ -281,6 +330,14 @@ export class RelButton extends LitElement {
281
330
  >
282
331
  <slot></slot>
283
332
  </button>
333
+ ${showBadge
334
+ ? html`<rel-badge
335
+ class="btn-badge"
336
+ variant=${this.badgeDot ? "dot" : "number"}
337
+ number=${this.badge}
338
+ size="sm"
339
+ ></rel-badge>`
340
+ : null}
284
341
  `;
285
342
  }
286
343
  }
@@ -17,6 +17,9 @@ A compact chip component for displaying tags, labels, or selected options. Suppo
17
17
  <!-- Outline variant -->
18
18
  <rel-chip label="Outline Style" tone="blue" outline></rel-chip>
19
19
 
20
+ <!-- Small size variant -->
21
+ <rel-chip label="Small" size="sm" tone="blue"></rel-chip>
22
+
20
23
  <!-- With avatar -->
21
24
  <rel-chip id="avatarChip" label="John Doe" avatar="./user.jpg"></rel-chip>
22
25
 
@@ -43,6 +46,7 @@ A compact chip component for displaying tags, labels, or selected options. Suppo
43
46
  | Name | Type | Default | Description |
44
47
  |------|------|---------|-------------|
45
48
  | `label` | `string` | `"Label"` | Text displayed on the chip |
49
+ | `size` (reflected) | `"sm" \| "md"` | `"md"` | Chip size variant |
46
50
  | `tone` | `"default" \| "blue" \| "yellow" \| "red" \| "green"` | `"default"` | Color tone variant |
47
51
  | `leadingIcon` | `string` | - | SVG or HTML string for leading icon |
48
52
  | `trailingIcon` | `string` | - | SVG or HTML string for trailing icon |
@@ -103,6 +107,9 @@ None exposed.
103
107
  - Remove button focus: 2px white outline with 1px offset
104
108
 
105
109
  ## Notes / Edge Cases
110
+ - **Size variants**:
111
+ - **md** (default): `--rel-fontSizes-md` text, `--rel-spacing-space-2xs`/`--rel-spacing-space-xs` padding, 20px × 20px avatar, 16px × 16px icons
112
+ - **sm**: `--rel-fontSizes-sm` text, `--rel-spacing-space-3xs`/`--rel-spacing-space-2xs` padding, 16px × 16px avatar, 12px × 12px icons
106
113
  - Icons (`leadingIcon`, `trailingIcon`) must be passed as raw HTML/SVG strings via JavaScript (see examples)
107
114
  - Icons are injected using `renderIcon()` which creates a template element and clones the content
108
115
  - Avatar images are 20px × 20px circular (`border-radius: 50%`, `object-fit: cover`)