@reliability-design/web 1.0.2 → 1.0.6

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 (145) hide show
  1. package/dist/components/rel-accordion/README.md +132 -0
  2. package/dist/components/rel-accordion/rel-accordion.ts +61 -29
  3. package/dist/components/rel-alert/README.md +141 -0
  4. package/dist/components/rel-alert/rel-alert.ts +110 -67
  5. package/dist/components/rel-avatar/README.md +119 -0
  6. package/dist/components/rel-avatar/rel-avatar.ts +8 -15
  7. package/dist/components/rel-badge/README.md +86 -0
  8. package/dist/components/rel-badge/rel-badge.ts +28 -21
  9. package/dist/components/rel-breadcrumb/README.md +102 -0
  10. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +24 -18
  11. package/dist/components/rel-button/README.md +130 -0
  12. package/dist/components/rel-button/rel-button.ts +39 -59
  13. package/dist/components/rel-calendar/README.md +109 -0
  14. package/dist/components/rel-calendar/index.ts +1 -0
  15. package/dist/components/rel-calendar/rel-calendar.ts +467 -0
  16. package/dist/components/rel-card/README.md +94 -0
  17. package/dist/components/rel-card/rel-card.ts +21 -11
  18. package/dist/components/rel-cardmedia/README.md +127 -0
  19. package/dist/components/rel-cardmedia/rel-cardmedia.ts +22 -18
  20. package/dist/components/rel-carousel/README.md +166 -0
  21. package/dist/components/rel-carousel/index.ts +1 -0
  22. package/dist/components/rel-carousel/rel-carousel.ts +469 -0
  23. package/dist/components/rel-chart/README.md +286 -0
  24. package/dist/components/rel-chart/index.ts +1 -0
  25. package/dist/components/rel-chart/rel-chart.ts +736 -0
  26. package/dist/components/rel-checkbox/README.md +77 -0
  27. package/dist/components/rel-checkbox/rel-checkbox.ts +36 -45
  28. package/dist/components/rel-chip/README.md +114 -0
  29. package/dist/components/rel-chip/rel-chip.ts +47 -122
  30. package/dist/components/rel-container/README.md +99 -0
  31. package/dist/components/rel-container/rel-container.ts +1 -6
  32. package/dist/components/rel-date-picker/README.md +253 -0
  33. package/dist/components/rel-date-picker/index.ts +1 -0
  34. package/dist/components/rel-date-picker/rel-date-picker.ts +916 -0
  35. package/dist/components/rel-divider/README.md +100 -0
  36. package/dist/components/rel-divider/rel-divider.ts +37 -37
  37. package/dist/components/rel-drawer/README.md +200 -0
  38. package/dist/components/rel-drawer/index.ts +1 -0
  39. package/dist/components/rel-drawer/rel-drawer.ts +621 -0
  40. package/dist/components/rel-kbd/README.md +151 -0
  41. package/dist/components/rel-kbd/rel-kbd.ts +118 -0
  42. package/dist/components/rel-layout/README.md +88 -0
  43. package/dist/components/rel-layout/rel-layout.ts +12 -14
  44. package/dist/components/rel-menu/README.md +151 -0
  45. package/dist/components/rel-menu/rel-menu.ts +18 -10
  46. package/dist/components/rel-menubar/README.md +329 -0
  47. package/dist/components/rel-menubar/index.ts +2 -0
  48. package/dist/components/rel-menubar/rel-menubar-menu.ts +16 -0
  49. package/dist/components/rel-menubar/rel-menubar.ts +576 -0
  50. package/dist/components/rel-nav-menu/README.md +289 -0
  51. package/dist/components/rel-nav-menu/index.ts +3 -0
  52. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +143 -0
  53. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +53 -0
  54. package/dist/components/rel-nav-menu/rel-nav-menu.ts +331 -0
  55. package/dist/components/rel-navbar/README.md +126 -0
  56. package/dist/components/rel-navbar/rel-navbar.ts +91 -0
  57. package/dist/components/rel-otp/README.md +440 -0
  58. package/dist/components/rel-otp/index.ts +1 -0
  59. package/dist/components/rel-otp/rel-otp.ts +504 -0
  60. package/dist/components/rel-pagination/README.md +410 -0
  61. package/dist/components/rel-pagination/index.ts +1 -0
  62. package/dist/components/rel-pagination/rel-pagination.ts +376 -0
  63. package/dist/components/rel-popover/README.md +568 -0
  64. package/dist/components/rel-popover/index.ts +2 -0
  65. package/dist/components/rel-popover/rel-popover-content.ts +55 -0
  66. package/dist/components/rel-popover/rel-popover.ts +327 -0
  67. package/dist/components/rel-progress/README.md +110 -0
  68. package/dist/components/rel-progress/rel-progress.ts +58 -20
  69. package/dist/components/rel-quote-page/README.md +166 -0
  70. package/dist/components/rel-quote-page/rel-quote-page.ts +816 -0
  71. package/dist/components/rel-radio/README.md +120 -0
  72. package/dist/components/rel-radio/rel-radio.ts +41 -73
  73. package/dist/components/rel-resizable/README.md +140 -0
  74. package/dist/components/rel-resizable/index.ts +3 -0
  75. package/dist/components/rel-resizable/rel-resizable-group.ts +374 -0
  76. package/dist/components/rel-resizable/rel-resizable-handle.ts +149 -0
  77. package/dist/components/rel-resizable/rel-resizable-panel.ts +47 -0
  78. package/dist/components/rel-segmented-button/README.md +141 -0
  79. package/dist/components/rel-segmented-button/rel-segmented-button.ts +18 -13
  80. package/dist/components/rel-select/README.md +107 -0
  81. package/dist/components/rel-select/rel-select.ts +14 -14
  82. package/dist/components/rel-sheet/README.md +224 -0
  83. package/dist/components/rel-sheet/rel-sheet.ts +683 -0
  84. package/dist/components/rel-sidebar/README.md +209 -0
  85. package/dist/components/rel-sidebar/rel-sidebar.ts +1158 -0
  86. package/dist/components/rel-slider/README.md +135 -0
  87. package/dist/components/rel-slider/rel-slider.ts +19 -7
  88. package/dist/components/rel-snackbar/README.md +134 -0
  89. package/dist/components/rel-snackbar/rel-snackbar.ts +73 -2
  90. package/dist/components/rel-stepper/README.md +160 -0
  91. package/dist/components/rel-stepper/rel-stepper.ts +29 -8
  92. package/dist/components/rel-switch/README.md +102 -0
  93. package/dist/components/rel-switch/rel-switch.ts +22 -11
  94. package/dist/components/rel-tab/README.md +163 -0
  95. package/dist/components/rel-tab/rel-tab.ts +46 -22
  96. package/dist/components/rel-table/README.md +178 -0
  97. package/dist/components/rel-table/rel-table-body.ts +16 -0
  98. package/dist/components/rel-table/rel-table-caption.ts +22 -0
  99. package/dist/components/rel-table/rel-table-cell.ts +49 -0
  100. package/dist/components/rel-table/rel-table-footer.ts +18 -0
  101. package/dist/components/rel-table/rel-table-head.ts +33 -0
  102. package/dist/components/rel-table/rel-table-header.ts +16 -0
  103. package/dist/components/rel-table/rel-table-row.ts +32 -0
  104. package/dist/components/rel-table/rel-table.ts +47 -0
  105. package/dist/components/rel-textfield/README.md +90 -0
  106. package/dist/components/rel-textfield/rel-textfield.ts +37 -21
  107. package/dist/components/rel-toast/README.md +148 -0
  108. package/dist/components/rel-toast/rel-toast.ts +32 -14
  109. package/dist/components/rel-tooltip/README.md +103 -0
  110. package/dist/components/rel-tooltip/rel-tooltip.ts +9 -9
  111. package/dist/index.d.mts +846 -7
  112. package/dist/index.d.ts +846 -7
  113. package/dist/index.js +10860 -1803
  114. package/dist/index.js.map +1 -1
  115. package/dist/index.mjs +10820 -1809
  116. package/dist/index.mjs.map +1 -1
  117. package/dist/styles/vars.css +293 -183
  118. package/dist/vars-ITL2K3KO.css +294 -0
  119. package/package.json +2 -2
  120. package/dist/components/rel-accordion/rel-accordion.stories.ts +0 -313
  121. package/dist/components/rel-alert/rel-alert.stories.ts +0 -412
  122. package/dist/components/rel-avatar/rel-avatar.stories.ts +0 -327
  123. package/dist/components/rel-badge/rel-badge.stories.ts +0 -350
  124. package/dist/components/rel-breadcrumb/rel-breadcrumb.stories.ts +0 -306
  125. package/dist/components/rel-button/rel-button.stories.ts +0 -356
  126. package/dist/components/rel-card/rel-card.stories.ts +0 -299
  127. package/dist/components/rel-cardmedia/rel-cardmedia.stories.ts +0 -368
  128. package/dist/components/rel-checkbox/rel-checkbox.stories.ts +0 -414
  129. package/dist/components/rel-chip/rel-chip.stories.ts +0 -552
  130. package/dist/components/rel-container/rel-container.stories.ts +0 -239
  131. package/dist/components/rel-divider/rel-divider.stories.ts +0 -229
  132. package/dist/components/rel-layout/rel-layout.stories.ts +0 -713
  133. package/dist/components/rel-menu/rel-menu.stories.ts +0 -910
  134. package/dist/components/rel-progress/rel-progress.stories.ts +0 -817
  135. package/dist/components/rel-radio/rel-radio.stories.ts +0 -1048
  136. package/dist/components/rel-segmented-button/rel-segmented-button.stories.ts +0 -1079
  137. package/dist/components/rel-select/rel-select.stories.ts +0 -1201
  138. package/dist/components/rel-slider/rel-slider.stories.ts +0 -730
  139. package/dist/components/rel-snackbar/rel-snackbar.stories.ts +0 -808
  140. package/dist/components/rel-stepper/rel-stepper.stories.ts +0 -752
  141. package/dist/components/rel-switch/rel-switch.stories.ts +0 -441
  142. package/dist/components/rel-tab/rel-tab.stories.ts +0 -492
  143. package/dist/components/rel-textfield/rel-textfield.stories.ts +0 -710
  144. package/dist/components/rel-toast/rel-toast.stories.ts +0 -394
  145. package/dist/components/rel-tooltip/rel-tooltip.stories.ts +0 -364
@@ -0,0 +1,467 @@
1
+ import { LitElement, html, css, nothing } from "lit";
2
+ import { customElement, property, state } from "lit/decorators.js";
3
+
4
+ interface CalendarDay {
5
+ date: Date;
6
+ day: number;
7
+ isCurrentMonth: boolean;
8
+ isToday: boolean;
9
+ isSelected: boolean;
10
+ isDisabled: boolean;
11
+ }
12
+
13
+ @customElement("rel-calendar")
14
+ export class RelCalendar extends LitElement {
15
+ @property({ type: String, reflect: true }) value: string | null = null;
16
+ @property({ type: String }) min: string | null = null;
17
+ @property({ type: String }) max: string | null = null;
18
+ @property({ type: Boolean, reflect: true }) disabled = false;
19
+ @property({ type: String, attribute: "week-start" }) weekStart: "sun" | "mon" = "sun";
20
+ @property({ type: String }) locale = "en-US";
21
+
22
+ // Custom disabled predicate (not settable via attribute)
23
+ public isDateDisabled?: (date: Date) => boolean;
24
+
25
+ @state() private viewYear: number = new Date().getFullYear();
26
+ @state() private viewMonth: number = new Date().getMonth();
27
+ @state() private focusedDate: Date | null = null;
28
+
29
+ static styles = css`
30
+ :host {
31
+ display: block;
32
+ font-family: var(--rel-fontFamilies-primary, sans-serif);
33
+ background: var(--rel-colors-white, #fff);
34
+ border: 1px solid var(--rel-border-default, #ddd);
35
+ border-radius: var(--rel-borders-radius-border-radius-200, 8px);
36
+ padding: var(--rel-spacing-space-md, 16px);
37
+ max-width: 320px;
38
+ color: var(--rel-text-primary, #333);
39
+ }
40
+
41
+ :host([disabled]) {
42
+ opacity: 0.6;
43
+ pointer-events: none;
44
+ }
45
+
46
+ .header {
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: space-between;
50
+ margin-bottom: var(--rel-spacing-space-md);
51
+ }
52
+
53
+ .header-center {
54
+ display: flex;
55
+ align-items: center;
56
+ gap: var(--rel-spacing-space-xs);
57
+ font-size: var(--rel-fontSizes-md);
58
+ font-weight: var(--rel-fontWeights-semibold);
59
+ color: var(--rel-text-heading);
60
+ }
61
+
62
+ .nav-button {
63
+ background: transparent;
64
+ border: none;
65
+ width: 32px;
66
+ height: 32px;
67
+ border-radius: var(--rel-borders-radius-border-radius-200);
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ cursor: pointer;
72
+ color: var(--rel-colors-gray-primary);
73
+ transition: background-color 0.15s ease;
74
+ }
75
+
76
+ .nav-button:hover {
77
+ background: var(--rel-surface-secondary);
78
+ }
79
+
80
+ .nav-button:focus-visible {
81
+ outline: 2px solid var(--rel-colors-blue-primary);
82
+ outline-offset: 2px;
83
+ }
84
+
85
+ .calendar-grid {
86
+ display: grid;
87
+ grid-template-columns: repeat(7, 1fr);
88
+ gap: var(--rel-spacing-space-3xs);
89
+ }
90
+
91
+ .weekday {
92
+ text-align: center;
93
+ font-size: var(--rel-fontSizes-xs);
94
+ font-weight: var(--rel-fontWeights-semibold);
95
+ color: var(--rel-colors-gray-placeholder);
96
+ padding: var(--rel-spacing-space-xs);
97
+ text-transform: uppercase;
98
+ }
99
+
100
+ .day-button {
101
+ aspect-ratio: 1;
102
+ border: none;
103
+ background: transparent;
104
+ border-radius: var(--rel-borders-radius-border-radius-200);
105
+ font-size: var(--rel-fontSizes-sm);
106
+ font-weight: var(--rel-fontWeights-medium);
107
+ color: var(--rel-text-primary);
108
+ cursor: pointer;
109
+ position: relative;
110
+ transition: all 0.15s ease;
111
+ font-family: var(--rel-fontFamilies-primary);
112
+ }
113
+
114
+ .day-button:hover:not(:disabled):not(.selected) {
115
+ background: var(--rel-surface-secondary);
116
+ }
117
+
118
+ .day-button:focus-visible {
119
+ outline: 2px solid var(--rel-colors-blue-primary);
120
+ outline-offset: -2px;
121
+ z-index: 1;
122
+ }
123
+
124
+ .day-button.today:not(.selected) {
125
+ border: 2px solid var(--rel-colors-blue-primary);
126
+ }
127
+
128
+ .day-button.selected {
129
+ background: var(--rel-colors-green-primary);
130
+ color: var(--rel-colors-black);
131
+ font-weight: var(--rel-fontWeights-semibold);
132
+ }
133
+
134
+ .day-button.other-month {
135
+ color: var(--rel-colors-gray-placeholder);
136
+ opacity: 0.5;
137
+ }
138
+
139
+ .day-button:disabled {
140
+ color: var(--rel-text-disabled);
141
+ cursor: not-allowed;
142
+ opacity: 0.5;
143
+ }
144
+
145
+ .day-button:disabled:hover {
146
+ background: transparent;
147
+ }
148
+ `;
149
+
150
+ connectedCallback() {
151
+ super.connectedCallback();
152
+ if (this.value) {
153
+ const date = this.parseISODate(this.value);
154
+ if (date) {
155
+ this.viewYear = date.getFullYear();
156
+ this.viewMonth = date.getMonth();
157
+ }
158
+ }
159
+ }
160
+
161
+ updated(changedProperties: Map<string, any>) {
162
+ if (changedProperties.has("value") && this.value) {
163
+ const date = this.parseISODate(this.value);
164
+ if (date) {
165
+ this.viewYear = date.getFullYear();
166
+ this.viewMonth = date.getMonth();
167
+ }
168
+ }
169
+ }
170
+
171
+ private parseISODate(iso: string): Date | null {
172
+ const match = iso.match(/^(\d{4})-(\d{2})-(\d{2})$/);
173
+ if (!match) return null;
174
+ const [, year, month, day] = match;
175
+ return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
176
+ }
177
+
178
+ private formatISODate(date: Date): string {
179
+ const year = date.getFullYear();
180
+ const month = String(date.getMonth() + 1).padStart(2, "0");
181
+ const day = String(date.getDate()).padStart(2, "0");
182
+ return `${year}-${month}-${day}`;
183
+ }
184
+
185
+ private isSameDay(date1: Date, date2: Date): boolean {
186
+ return (
187
+ date1.getFullYear() === date2.getFullYear() &&
188
+ date1.getMonth() === date2.getMonth() &&
189
+ date1.getDate() === date2.getDate()
190
+ );
191
+ }
192
+
193
+ private isDateDisabledInternal(date: Date): boolean {
194
+ if (this.disabled) return true;
195
+
196
+ if (this.min) {
197
+ const minDate = this.parseISODate(this.min);
198
+ if (minDate && date < minDate) return true;
199
+ }
200
+
201
+ if (this.max) {
202
+ const maxDate = this.parseISODate(this.max);
203
+ if (maxDate && date > maxDate) return true;
204
+ }
205
+
206
+ if (this.isDateDisabled && this.isDateDisabled(date)) {
207
+ return true;
208
+ }
209
+
210
+ return false;
211
+ }
212
+
213
+ private getCalendarDays(): CalendarDay[] {
214
+ const days: CalendarDay[] = [];
215
+ const firstDayOfMonth = new Date(this.viewYear, this.viewMonth, 1);
216
+ const lastDayOfMonth = new Date(this.viewYear, this.viewMonth + 1, 0);
217
+ const today = new Date();
218
+ const selectedDate = this.value ? this.parseISODate(this.value) : null;
219
+
220
+ let startDay = firstDayOfMonth.getDay();
221
+ if (this.weekStart === "mon") {
222
+ startDay = startDay === 0 ? 6 : startDay - 1;
223
+ }
224
+
225
+ // Leading days from previous month
226
+ for (let i = startDay - 1; i >= 0; i--) {
227
+ const date = new Date(this.viewYear, this.viewMonth, -i);
228
+ days.push({
229
+ date,
230
+ day: date.getDate(),
231
+ isCurrentMonth: false,
232
+ isToday: this.isSameDay(date, today),
233
+ isSelected: selectedDate ? this.isSameDay(date, selectedDate) : false,
234
+ isDisabled: this.isDateDisabledInternal(date),
235
+ });
236
+ }
237
+
238
+ // Current month days
239
+ for (let day = 1; day <= lastDayOfMonth.getDate(); day++) {
240
+ const date = new Date(this.viewYear, this.viewMonth, day);
241
+ days.push({
242
+ date,
243
+ day,
244
+ isCurrentMonth: true,
245
+ isToday: this.isSameDay(date, today),
246
+ isSelected: selectedDate ? this.isSameDay(date, selectedDate) : false,
247
+ isDisabled: this.isDateDisabledInternal(date),
248
+ });
249
+ }
250
+
251
+ // Trailing days from next month
252
+ const remainingCells = 42 - days.length; // 6 weeks max
253
+ for (let i = 1; i <= remainingCells; i++) {
254
+ const date = new Date(this.viewYear, this.viewMonth + 1, i);
255
+ days.push({
256
+ date,
257
+ day: date.getDate(),
258
+ isCurrentMonth: false,
259
+ isToday: this.isSameDay(date, today),
260
+ isSelected: selectedDate ? this.isSameDay(date, selectedDate) : false,
261
+ isDisabled: this.isDateDisabledInternal(date),
262
+ });
263
+ }
264
+
265
+ return days;
266
+ }
267
+
268
+ private getWeekdayLabels(): string[] {
269
+ const formatter = new Intl.DateTimeFormat(this.locale, { weekday: "short" });
270
+ const days = [];
271
+ const baseDate = new Date(2024, 0, 7); // Sunday, Jan 7, 2024
272
+
273
+ for (let i = 0; i < 7; i++) {
274
+ const offset = this.weekStart === "mon" ? i + 1 : i;
275
+ const date = new Date(baseDate);
276
+ date.setDate(baseDate.getDate() + offset);
277
+ days.push(formatter.format(date).substring(0, 2));
278
+ }
279
+
280
+ return days;
281
+ }
282
+
283
+ private getMonthName(): string {
284
+ const formatter = new Intl.DateTimeFormat(this.locale, { month: "long" });
285
+ return formatter.format(new Date(this.viewYear, this.viewMonth, 1));
286
+ }
287
+
288
+ private previousMonth() {
289
+ if (this.viewMonth === 0) {
290
+ this.viewMonth = 11;
291
+ this.viewYear--;
292
+ } else {
293
+ this.viewMonth--;
294
+ }
295
+ }
296
+
297
+ private nextMonth() {
298
+ if (this.viewMonth === 11) {
299
+ this.viewMonth = 0;
300
+ this.viewYear++;
301
+ } else {
302
+ this.viewMonth++;
303
+ }
304
+ }
305
+
306
+ private selectDay(day: CalendarDay) {
307
+ if (day.isDisabled || this.disabled) return;
308
+
309
+ const newValue = this.formatISODate(day.date);
310
+ this.value = newValue;
311
+ this.focusedDate = day.date;
312
+
313
+ this.dispatchEvent(
314
+ new CustomEvent("rel-change", {
315
+ detail: {
316
+ value: newValue,
317
+ date: new Date(day.date),
318
+ },
319
+ bubbles: true,
320
+ composed: true,
321
+ })
322
+ );
323
+ }
324
+
325
+ private handleKeyDown(e: KeyboardEvent, day: CalendarDay, index: number) {
326
+ const days = this.getCalendarDays();
327
+ let targetIndex = index;
328
+
329
+ switch (e.key) {
330
+ case "ArrowLeft":
331
+ e.preventDefault();
332
+ targetIndex = index - 1;
333
+ break;
334
+ case "ArrowRight":
335
+ e.preventDefault();
336
+ targetIndex = index + 1;
337
+ break;
338
+ case "ArrowUp":
339
+ e.preventDefault();
340
+ targetIndex = index - 7;
341
+ break;
342
+ case "ArrowDown":
343
+ e.preventDefault();
344
+ targetIndex = index + 7;
345
+ break;
346
+ case "Home":
347
+ e.preventDefault();
348
+ targetIndex = index - (index % 7);
349
+ break;
350
+ case "End":
351
+ e.preventDefault();
352
+ targetIndex = index - (index % 7) + 6;
353
+ break;
354
+ case "PageUp":
355
+ e.preventDefault();
356
+ if (e.shiftKey) {
357
+ this.viewYear--;
358
+ } else {
359
+ this.previousMonth();
360
+ }
361
+ return;
362
+ case "PageDown":
363
+ e.preventDefault();
364
+ if (e.shiftKey) {
365
+ this.viewYear++;
366
+ } else {
367
+ this.nextMonth();
368
+ }
369
+ return;
370
+ case "Enter":
371
+ case " ":
372
+ e.preventDefault();
373
+ this.selectDay(day);
374
+ return;
375
+ default:
376
+ return;
377
+ }
378
+
379
+ if (targetIndex >= 0 && targetIndex < days.length) {
380
+ const targetDay = days[targetIndex];
381
+ if (!targetDay.isCurrentMonth) {
382
+ if (targetIndex < index) {
383
+ this.previousMonth();
384
+ } else {
385
+ this.nextMonth();
386
+ }
387
+ }
388
+ this.focusedDate = targetDay.date;
389
+ this.requestUpdate();
390
+ this.updateComplete.then(() => {
391
+ const buttons = this.shadowRoot?.querySelectorAll(".day-button");
392
+ if (buttons && buttons[targetIndex]) {
393
+ (buttons[targetIndex] as HTMLButtonElement).focus();
394
+ }
395
+ });
396
+ }
397
+ }
398
+
399
+ render() {
400
+ const days = this.getCalendarDays();
401
+ const weekdayLabels = this.getWeekdayLabels();
402
+
403
+ return html`
404
+ <div role="grid" aria-label="Calendar">
405
+ <div class="header">
406
+ <button
407
+ class="nav-button"
408
+ @click=${this.previousMonth}
409
+ aria-label="Previous month"
410
+ ?disabled=${this.disabled}
411
+ >
412
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
413
+ <path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
414
+ </svg>
415
+ </button>
416
+
417
+ <div class="header-center">
418
+ <span>${this.getMonthName()}</span>
419
+ <span>${this.viewYear}</span>
420
+ </div>
421
+
422
+ <button
423
+ class="nav-button"
424
+ @click=${this.nextMonth}
425
+ aria-label="Next month"
426
+ ?disabled=${this.disabled}
427
+ >
428
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
429
+ <path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
430
+ </svg>
431
+ </button>
432
+ </div>
433
+
434
+ <div class="calendar-grid" role="row">
435
+ ${weekdayLabels.map(
436
+ (label) => html`
437
+ <div class="weekday" role="columnheader" aria-label="${label}">
438
+ ${label}
439
+ </div>
440
+ `
441
+ )}
442
+ </div>
443
+
444
+ <div class="calendar-grid">
445
+ ${days.map(
446
+ (day, index) => html`
447
+ <button
448
+ class="day-button ${day.isSelected ? "selected" : ""} ${day.isToday
449
+ ? "today"
450
+ : ""} ${!day.isCurrentMonth ? "other-month" : ""}"
451
+ role="gridcell"
452
+ aria-label="${this.formatISODate(day.date)}"
453
+ aria-selected="${day.isSelected}"
454
+ ?disabled=${day.isDisabled || (!day.isCurrentMonth && day.isDisabled)}
455
+ @click=${() => this.selectDay(day)}
456
+ @keydown=${(e: KeyboardEvent) => this.handleKeyDown(e, day, index)}
457
+ tabindex="${day.isSelected || (index === 0 && !this.focusedDate) ? 0 : -1}"
458
+ >
459
+ ${day.day}
460
+ </button>
461
+ `
462
+ )}
463
+ </div>
464
+ </div>
465
+ `;
466
+ }
467
+ }
@@ -0,0 +1,94 @@
1
+ # rel-card
2
+
3
+ ## Overview
4
+ A simple, versatile card container component with built-in elevation shadow and hover lift effect. Provides a clean, elevated surface for grouping related content. Respects user motion preferences by disabling animations when requested.
5
+
6
+ ## Usage
7
+ ```html
8
+ <!-- Basic card with text content -->
9
+ <rel-card>
10
+ <h3>Card Title</h3>
11
+ <p>This is some content inside the card.</p>
12
+ </rel-card>
13
+
14
+ <!-- Card with mixed content -->
15
+ <rel-card>
16
+ <h2>Product Name</h2>
17
+ <img src="product.jpg" alt="Product" style="width: 100%; border-radius: 8px;">
18
+ <p>Product description goes here.</p>
19
+ <button>Add to Cart</button>
20
+ </rel-card>
21
+
22
+ <!-- Multiple cards in a grid -->
23
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;">
24
+ <rel-card>
25
+ <h4>Feature 1</h4>
26
+ <p>Description of feature 1</p>
27
+ </rel-card>
28
+ <rel-card>
29
+ <h4>Feature 2</h4>
30
+ <p>Description of feature 2</p>
31
+ </rel-card>
32
+ <rel-card>
33
+ <h4>Feature 3</h4>
34
+ <p>Description of feature 3</p>
35
+ </rel-card>
36
+ </div>
37
+ ```
38
+
39
+ ## API
40
+
41
+ ### Attributes/Properties
42
+ None. This component is purely presentational and controlled via slots.
43
+
44
+ ## Events
45
+ None.
46
+
47
+ ## Slots
48
+ | Slot | Description |
49
+ |------|-------------|
50
+ | (default) | Card content (any HTML elements) |
51
+
52
+ ## CSS Parts
53
+ None exposed.
54
+
55
+ ## Tokens / CSS Variables
56
+
57
+ ### Semantic Tokens
58
+ - `--rel-fontFamilies-primary` - Font family
59
+
60
+ ### Color Tokens
61
+ - `--rel-colors-white` - Card background color
62
+ - `--rel-text-primary` - Text color
63
+
64
+ ### Shadow Tokens
65
+ - `--rel-shadows-shadow-200` - Default elevation shadow
66
+ - `--rel-shadows-shadow-300` - Hover elevation shadow (larger)
67
+
68
+ ### Border Tokens
69
+ - `--rel-borders-radius-border-radius-300` - Card border radius
70
+
71
+ ### Spacing Tokens
72
+ - `--rel-spacing-space-md` - Internal padding
73
+
74
+ ## Accessibility
75
+ - The card itself has no specific ARIA role (it's a generic container)
76
+ - Ensure slotted content includes proper semantic HTML and ARIA attributes where needed
77
+ - Hover effect is purely visual and does not affect accessibility
78
+
79
+ ### Keyboard Interactions
80
+ - None (purely presentational container)
81
+
82
+ ### Focus Behavior
83
+ - Does not receive focus (container only)
84
+ - Ensure slotted interactive elements are keyboard accessible
85
+
86
+ ## Notes / Edge Cases
87
+ - The card is `display: block` and takes full width of its container by default
88
+ - **Hover effect**: Translates the card upward by 3px (`translateY(-3px)`) and increases shadow depth
89
+ - **Animation**: Uses `transition: transform 0.2s ease, box-shadow 0.2s ease`
90
+ - **Motion preferences**: When `prefers-reduced-motion: reduce`, both hover transform and transitions are disabled
91
+ - The card has consistent padding on all sides via `--rel-spacing-space-md`
92
+ - The internal `.card` wrapper is where all styling is applied
93
+ - The component provides no default content styling—you control the layout of slotted elements
94
+ - Works well with CSS Grid or Flexbox for creating card layouts
@@ -6,28 +6,38 @@ export class RelCard extends LitElement {
6
6
  static styles = css`
7
7
  :host {
8
8
  display: block;
9
+ font-family: var(--rel-fontFamilies-primary, sans-serif);
9
10
  }
10
11
 
11
12
  .card {
12
- background-color: var(--rel-colors-white, #ffffff);
13
- border-radius: var(--rel-borderRadius-lg, 12px);
14
- padding: var(--rel-spacing-space-md, 16px);
15
-
16
- box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.3),
17
- 0px 6px 10px 4px rgba(0, 0, 0, 0.15);
18
-
19
- font-family: var(--rel-fontFamilies-primary);
13
+ background-color: var(--rel-colors-white, #fff);
14
+ color: var(--rel-text-primary, #333);
15
+ border-radius: var(--rel-borders-radius-border-radius-300);
16
+ padding: var(--rel-spacing-space-md);
17
+ box-shadow: var(--rel-shadows-shadow-200);
20
18
  transition: transform 0.2s ease, box-shadow 0.2s ease;
21
19
  }
22
20
 
23
21
  .card:hover {
24
22
  transform: translateY(-3px);
25
- box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.35),
26
- 0px 8px 14px 4px rgba(0, 0, 0, 0.2);
23
+ box-shadow: var(--rel-shadows-shadow-300);
24
+ }
25
+
26
+ @media (prefers-reduced-motion: reduce) {
27
+ .card {
28
+ transition: none;
29
+ }
30
+ .card:hover {
31
+ transform: none;
32
+ }
27
33
  }
28
34
  `;
29
35
 
30
36
  render() {
31
- return html` <div class="card"><slot></slot></div> `;
37
+ return html`
38
+ <div class="card">
39
+ <slot></slot>
40
+ </div>
41
+ `;
32
42
  }
33
43
  }