@reliability-design/web 1.2.0 → 1.3.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.
@@ -1,135 +1,289 @@
1
- import "../../styles/vars-v2.css";
2
- import { LitElement, html, css } from "lit";
3
- import { customElement, property } from "lit/decorators.js";
4
- @customElement("rel-accordion")
5
- export class RelAccordion extends LitElement {
6
- @property({ type: String }) title = "Accordion";
7
- @property({ type: Boolean, reflect: true }) open = false;
8
- static styles = css`
9
- :host {
10
- display: block;
11
- width: 100%;
12
- font-family: var(--rel-fontFamilies-primary, sans-serif);
13
- }
14
- .accordion {
15
- background: var(--rel-color-bg-primary);
16
- border: none;
17
- border-bottom: 1px solid var(--rel-gray-text-placeholder);
18
- border-radius: 0;
19
- overflow: hidden;
20
- }
21
- /* HEADER --------------------------------------------------- */
22
- .header {
23
- display: flex;
24
- justify-content: space-between;
25
- align-items: center;
26
- padding: var(--rel-spacing-space-md, 16px);
27
- cursor: pointer;
28
- user-select: none;
29
- border-radius: 0;
30
- }
31
- .header:hover {
32
- background: var(--rel-surface-surface, #f5f5f5);
33
- }
34
- :host([open]) .header:hover {
35
- background: transparent;
36
- }
37
- .header:focus-visible {
38
- outline: 2px solid var(--rel-color-border-focus);
39
- outline-offset: -2px;
40
- }
41
- .title {
42
- font-size: var(--rel-fontSizes-lg, 20px);
43
- font-weight: var(--rel-fontWeights-medium, 500);
44
- color: var(--rel-text-heading, #2b2b2b);
45
- }
46
- /* ICON ----------------------------------------------------- */
47
- .icon {
48
- width: 20px;
49
- height: 20px;
50
- display: flex;
51
- align-items: center;
52
- justify-content: center;
53
- transition: transform 0.25s ease;
54
- }
55
- .icon svg {
56
- width: 20px;
57
- height: 20px;
58
- stroke: var(--rel-text-primary, #333);
59
- stroke-width: 2;
60
- fill: none;
61
- stroke-linecap: round;
62
- stroke-linejoin: round;
63
- }
64
- .icon.rotated {
65
- transform: rotate(180deg);
66
- }
67
- /* BODY ------------------------------------------------------ */
68
- .body {
69
- overflow: hidden;
70
- max-height: 0;
71
- padding: 0 var(--rel-spacing-space-md, 16px);
72
- font-size: var(--rel-fontSizes-md, 14px);
73
- color: var(--rel-text-primary, #333);
74
- transition:
75
- max-height 0.25s ease,
76
- padding 0.2s ease;
77
- }
78
- .body.open {
79
- max-height: 1000px;
80
- padding: var(--rel-spacing-space-xl, 24px)
81
- var(--rel-spacing-space-md, 16px) var(--rel-spacing-space-lg, 20px);
82
- }
83
- @media (prefers-reduced-motion: reduce) {
84
- .icon,
85
- .body {
86
- transition: none;
87
- }
88
- }
89
- `;
90
- private toggle() {
91
- this.open = !this.open;
92
- this.dispatchEvent(
93
- new CustomEvent<boolean>("open-change", {
94
- detail: this.open,
95
- bubbles: true,
96
- composed: true,
97
- }),
98
- );
99
- }
100
- private chevron() {
101
- return html`
102
- <svg viewBox="0 0 24 24">
103
- <polyline points="6 9 12 15 18 9"></polyline>
104
- </svg>
105
- `;
106
- }
107
- render() {
108
- return html`
109
- <div class="accordion">
110
- <div
111
- class="header"
112
- role="button"
113
- tabindex="0"
114
- aria-expanded="${this.open}"
115
- @click=${this.toggle}
116
- @keydown=${(e: KeyboardEvent) => {
117
- if (e.key === "Enter" || e.key === " ") {
118
- e.preventDefault();
119
- this.toggle();
120
- }
121
- }}
122
- >
123
- <div class="title">${this.title}</div>
124
- <div class="icon ${this.open ? "rotated" : ""}">
125
- ${this.chevron()}
126
- </div>
127
- </div>
128
- <!-- IMPORTANT: slot is ALWAYS rendered -->
129
- <div class="body ${this.open ? "open" : ""}">
130
- <slot></slot>
131
- </div>
132
- </div>
133
- `;
134
- }
135
- }
1
+ import "../../styles/vars-v2.css";
2
+ import "@reliability-design/icons/rel-icon";
3
+ import { LitElement, html, css } from "lit";
4
+ import { customElement, property } from "lit/decorators.js";
5
+
6
+ @customElement("rel-accordion")
7
+ export class RelAccordion extends LitElement {
8
+ @property({ type: String }) title = "Accordion";
9
+ @property({ type: Boolean, reflect: true }) open = false;
10
+ @property({ type: String, reflect: true }) size: "md" | "sm" = "md";
11
+ @property({ type: String, reflect: true }) type: "default" | "action" = "default";
12
+
13
+ static styles = css`
14
+ :host {
15
+ display: block;
16
+ width: 100%;
17
+ font-family: var(--rel-fontFamilies-primary, sans-serif);
18
+ }
19
+
20
+ .accordion {
21
+ background: var(--rel-color-bg-primary, #ffffff);
22
+ border-radius: 8px;
23
+ overflow: hidden;
24
+ box-shadow: 0 2px 4px rgba(11, 11, 11, 0.1);
25
+ }
26
+
27
+ /* ── Header ───────────────────────────────────────────────── */
28
+
29
+ .header {
30
+ display: flex;
31
+ justify-content: space-between;
32
+ align-items: center;
33
+ cursor: pointer;
34
+ user-select: none;
35
+ box-sizing: border-box;
36
+ /* matches .accordion's radius so the focus-visible outline isn't clipped square at the corners by the parent's overflow:hidden */
37
+ border-radius: 8px;
38
+ }
39
+
40
+ :host([size="md"]) .header {
41
+ padding: 8px 8px 8px 16px;
42
+ min-height: 56px;
43
+ }
44
+
45
+ :host([size="sm"]) .header {
46
+ padding: 8px 8px 8px 12px;
47
+ min-height: 48px;
48
+ }
49
+
50
+ .header:hover {
51
+ background: var(--rel-color-bg-secondary, #f5f5f5);
52
+ }
53
+
54
+ .header:focus-visible {
55
+ outline: 2px solid var(--rel-color-border-focus);
56
+ outline-offset: -2px;
57
+ }
58
+
59
+ /* ── Title row ────────────────────────────────────────────── */
60
+
61
+ .title-row {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 8px;
65
+ flex: 1;
66
+ }
67
+
68
+ .title {
69
+ font-weight: var(--rel-fontWeights-semibold, 600);
70
+ color: var(--rel-color-text-default, #0b0b0b);
71
+ letter-spacing: -0.3px;
72
+ }
73
+
74
+ :host([size="md"]) .title {
75
+ font-size: var(--rel-fontSizes-lg, 20px);
76
+ line-height: 28px;
77
+ }
78
+
79
+ :host([size="sm"]) .title {
80
+ font-size: 16px;
81
+ line-height: 24px;
82
+ }
83
+
84
+ /* ── Leading icon (action type) ───────────────────────────── */
85
+
86
+ .leading-icon {
87
+ display: inline-flex;
88
+ align-items: center;
89
+ justify-content: center;
90
+ flex-shrink: 0;
91
+ color: var(--rel-color-text-default, #0b0b0b);
92
+ }
93
+
94
+ .leading-icon ::slotted(svg) {
95
+ width: 24px;
96
+ height: 24px;
97
+ }
98
+
99
+ .leading-icon rel-icon,
100
+ .leading-icon ::slotted(rel-icon) {
101
+ --rel-icon-size: 24px;
102
+ }
103
+
104
+ :host([size="sm"]) .leading-icon ::slotted(svg) {
105
+ width: 20px;
106
+ height: 20px;
107
+ }
108
+
109
+ :host([size="sm"]) .leading-icon rel-icon,
110
+ :host([size="sm"]) .leading-icon ::slotted(rel-icon) {
111
+ --rel-icon-size: 20px;
112
+ }
113
+
114
+ /* ── Chevron ──────────────────────────────────────────────── */
115
+
116
+ .chevron {
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ flex-shrink: 0;
121
+ }
122
+
123
+ :host([size="md"]) .chevron {
124
+ width: 40px;
125
+ height: 40px;
126
+ }
127
+
128
+ :host([size="sm"]) .chevron {
129
+ width: 32px;
130
+ height: 32px;
131
+ }
132
+
133
+ .chevron ::slotted(svg) {
134
+ width: 24px;
135
+ height: 24px;
136
+ }
137
+
138
+ .chevron rel-icon,
139
+ .chevron ::slotted(rel-icon) {
140
+ --rel-icon-size: 24px;
141
+ color: var(--rel-color-text-default, #0b0b0b);
142
+ }
143
+
144
+ :host([size="sm"]) .chevron ::slotted(svg) {
145
+ width: 20px;
146
+ height: 20px;
147
+ }
148
+
149
+ :host([size="sm"]) .chevron rel-icon,
150
+ :host([size="sm"]) .chevron ::slotted(rel-icon) {
151
+ --rel-icon-size: 20px;
152
+ }
153
+
154
+ /* ── Icon rotation ────────────────────────────────────────── */
155
+ /* Only the shadow-DOM fallback wrappers rotate on open.
156
+ Slotted (light-DOM) icons are never targeted, so they stay static. */
157
+
158
+ .icon-rotate-on-open,
159
+ .chevron-rotate {
160
+ display: inline-flex;
161
+ align-items: center;
162
+ transition: transform 0.25s ease;
163
+ }
164
+
165
+ /* Leading fallback chevron: 90° (points right when open) */
166
+ :host([open]) .icon-rotate-on-open {
167
+ transform: rotate(90deg);
168
+ }
169
+
170
+ /* Trailing fallback chevron (type="default" only): 180° flip */
171
+ :host([open]) .chevron-rotate {
172
+ transform: rotate(180deg);
173
+ }
174
+
175
+ /* ── Body ─────────────────────────────────────────────────── */
176
+
177
+ .body {
178
+ overflow: hidden;
179
+ max-height: 0;
180
+ padding: 0 16px;
181
+ font-size: var(--rel-fontSizes-md, 14px);
182
+ line-height: 22px;
183
+ /* Figma spec value — --rel-color-text-secondary resolves to #9e9e9e (gray-disabled-text), not this */
184
+ color: var(--rel-accordion-body-color, #525252);
185
+ transition:
186
+ max-height 0.25s ease,
187
+ padding 0.2s ease;
188
+ }
189
+
190
+ :host([size="sm"]) .body {
191
+ font-size: var(--rel-fontSizes-sm, 12px);
192
+ line-height: 20px;
193
+ }
194
+
195
+ .body.open {
196
+ max-height: 1000px;
197
+ padding: 4px 16px 16px;
198
+ }
199
+
200
+ /* ── Dark mode ────────────────────────────────────────────── */
201
+
202
+ :host-context([data-theme="dark"]) .accordion,
203
+ [data-theme="dark"] .accordion {
204
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
205
+ }
206
+
207
+ :host-context([data-theme="dark"]) .title,
208
+ [data-theme="dark"] .title {
209
+ color: var(--rel-color-text-default-dark, #f5f5f5);
210
+ }
211
+
212
+ :host-context([data-theme="dark"]) .chevron rel-icon,
213
+ [data-theme="dark"] .chevron rel-icon,
214
+ :host-context([data-theme="dark"]) .chevron ::slotted(rel-icon),
215
+ [data-theme="dark"] .chevron ::slotted(rel-icon) {
216
+ color: var(--rel-color-text-default-dark, #f5f5f5);
217
+ }
218
+
219
+ :host-context([data-theme="dark"]) .leading-icon,
220
+ [data-theme="dark"] .leading-icon {
221
+ color: var(--rel-color-text-default-dark, #f5f5f5);
222
+ }
223
+
224
+ :host-context([data-theme="dark"]) .body,
225
+ [data-theme="dark"] .body {
226
+ color: var(--rel-accordion-body-color-dark, #a3a3a3);
227
+ }
228
+
229
+ @media (prefers-reduced-motion: reduce) {
230
+ .icon-rotate-on-open,
231
+ .chevron-rotate,
232
+ .body {
233
+ transition: none;
234
+ }
235
+ }
236
+ `;
237
+
238
+ private toggle() {
239
+ this.open = !this.open;
240
+ this.dispatchEvent(
241
+ new CustomEvent<boolean>("open-change", {
242
+ detail: this.open,
243
+ bubbles: true,
244
+ composed: true,
245
+ })
246
+ );
247
+ }
248
+
249
+ render() {
250
+ return html`
251
+ <div class="accordion">
252
+ <div
253
+ class="header"
254
+ role="button"
255
+ tabindex="0"
256
+ aria-expanded="${this.open}"
257
+ @click=${this.toggle}
258
+ @keydown=${(e: KeyboardEvent) => {
259
+ if (e.key === "Enter" || e.key === " ") {
260
+ e.preventDefault();
261
+ this.toggle();
262
+ }
263
+ }}
264
+ >
265
+ <div class="title-row">
266
+ ${this.type === "action"
267
+ ? html`<span class="leading-icon">
268
+ <slot name="icon">
269
+ <span class="icon-rotate-on-open"><rel-icon name="chevron-right"></rel-icon></span>
270
+ </slot>
271
+ </span>`
272
+ : ""}
273
+ <span class="title">${this.title}</span>
274
+ </div>
275
+ <div class="chevron">
276
+ <slot name="trailing-icon">
277
+ ${this.type === "default"
278
+ ? html`<span class="chevron-rotate"><rel-icon name="chevron-down"></rel-icon></span>`
279
+ : html`<rel-icon name="arrow-expansion"></rel-icon>`}
280
+ </slot>
281
+ </div>
282
+ </div>
283
+ <div class="body ${this.open ? "open" : ""}">
284
+ <slot></slot>
285
+ </div>
286
+ </div>
287
+ `;
288
+ }
289
+ }
@@ -1,7 +1,7 @@
1
1
  # rel-avatar
2
2
 
3
3
  ## Overview
4
- A circular avatar component supporting three display types: image, text initials, or custom icon content. Provides four size variants with automatic text transformation and SVG sizing for icons.
4
+ A circular avatar component supporting three display types: image, text initials, or custom icon content. Provides four size variants and three style variants (fill, subtle, outline), with automatic text transformation and SVG sizing for icons.
5
5
 
6
6
  ## Usage
7
7
  ```html
@@ -31,6 +31,11 @@ A circular avatar component supporting three display types: image, text initials
31
31
  <rel-avatar type="text" text="MD" size="md"></rel-avatar>
32
32
  <rel-avatar type="text" text="LG" size="lg"></rel-avatar>
33
33
 
34
+ <!-- Style variants -->
35
+ <rel-avatar type="text" text="AA" variant="fill"></rel-avatar>
36
+ <rel-avatar type="text" text="AA" variant="subtle"></rel-avatar>
37
+ <rel-avatar type="text" text="AA" variant="outline"></rel-avatar>
38
+
34
39
  <!-- User list with avatars -->
35
40
  <div style="display: flex; gap: 8px; align-items: center;">
36
41
  <rel-avatar type="image" src="user1.jpg" size="sm"></rel-avatar>
@@ -52,9 +57,11 @@ A circular avatar component supporting three display types: image, text initials
52
57
  |------|------|---------|-------------|
53
58
  | `size` (reflected) | `"lg" \| "md" \| "sm" \| "xs"` | `"md"` | Avatar size variant |
54
59
  | `type` (reflected) | `"image" \| "text" \| "icon"` | `"image"` | Display type |
60
+ | `variant` (reflected) | `"fill" \| "subtle" \| "outline"` | `"fill"` | Style variant |
55
61
  | `src` | `string` | `""` | Image URL (for `type="image"`) |
56
62
  | `text` | `string` | `""` | Text content (for `type="text"`, usually initials) |
57
63
  | `alt` | `string` | `"Avatar"` | Alt text for the image |
64
+ | `bg` | `string` | `""` | Direct CSS background-color override (any valid CSS color), takes precedence over `variant` |
58
65
 
59
66
  ## Events
60
67
  None.
@@ -79,9 +86,13 @@ None exposed.
79
86
  - `--rel-fontSizes-xs` - Extra small avatar text size
80
87
  - `--rel-fontWeights-bold` - Text font weight
81
88
 
82
- ### Color Tokens
83
- - `--rel-colors-gray-heading` - Avatar background color
84
- - `--rel-colors-white` - Text/icon color
89
+ ### Color Tokens (by variant)
90
+ - **fill** (default): `--rel-avatar-bg-fill` (default `--rel-colors-gray-disabled-text`), `--rel-avatar-color-fill` (default `#ffffff`)
91
+ - **subtle**: `--rel-avatar-bg-subtle` (default `--rel-color-bg-tertiary`), `--rel-avatar-color-subtle` (default `--rel-color-text-heading`)
92
+ - **outline**: `--rel-avatar-bg-outline` (default `--rel-color-bg-primary`), `--rel-avatar-border-outline` (default `--rel-color-border-default`), `--rel-avatar-color-outline` (default `--rel-color-text-heading`)
93
+ - `--rel-borders-width-stroke-100` - Border width (all variants; `fill`/`subtle` use a transparent border to keep sizing consistent)
94
+
95
+ All of the above semantic tokens (`--rel-color-bg-*`, `--rel-color-text-heading`, `--rel-color-border-*`, `--rel-colors-gray-disabled-text`) have dark-mode overrides defined in `vars-v2.css` under `:root[data-theme="dark"]`, so all three variants automatically adapt to dark mode with no extra configuration.
85
96
 
86
97
  ## Accessibility
87
98
  - Image avatars include `alt` attribute for screen readers