@arclux/arc-ui 1.0.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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/package.json +186 -0
  4. package/src/content/accordion-item.js +27 -0
  5. package/src/content/accordion.js +151 -0
  6. package/src/content/animated-number.js +160 -0
  7. package/src/content/aspect-ratio.js +78 -0
  8. package/src/content/avatar-group.js +101 -0
  9. package/src/content/avatar.js +92 -0
  10. package/src/content/badge.js +98 -0
  11. package/src/content/callout.js +141 -0
  12. package/src/content/card.js +75 -0
  13. package/src/content/carousel.js +300 -0
  14. package/src/content/code-block.js +152 -0
  15. package/src/content/collapsible.js +142 -0
  16. package/src/content/color-swatch.js +86 -0
  17. package/src/content/column.js +28 -0
  18. package/src/content/data-table.js +332 -0
  19. package/src/content/divider.js +153 -0
  20. package/src/content/empty-state.js +78 -0
  21. package/src/content/feature-card.js +142 -0
  22. package/src/content/highlight.js +63 -0
  23. package/src/content/icon-library.js +30 -0
  24. package/src/content/icon-registry.js +39 -0
  25. package/src/content/icon.js +95 -0
  26. package/src/content/index.js +44 -0
  27. package/src/content/infinite-scroll.js +144 -0
  28. package/src/content/kbd.js +40 -0
  29. package/src/content/markdown.js +294 -0
  30. package/src/content/marquee.js +166 -0
  31. package/src/content/meter.js +167 -0
  32. package/src/content/scroll-area.js +107 -0
  33. package/src/content/skeleton.js +85 -0
  34. package/src/content/spinner.js +77 -0
  35. package/src/content/stack.js +68 -0
  36. package/src/content/stat.js +72 -0
  37. package/src/content/step.js +22 -0
  38. package/src/content/stepper.js +202 -0
  39. package/src/content/table.js +134 -0
  40. package/src/content/tag.js +156 -0
  41. package/src/content/text.js +111 -0
  42. package/src/content/timeline-item.js +29 -0
  43. package/src/content/timeline.js +170 -0
  44. package/src/content/truncate.js +161 -0
  45. package/src/content/value-card.js +94 -0
  46. package/src/feedback/alert.js +187 -0
  47. package/src/feedback/command-item.js +28 -0
  48. package/src/feedback/command-palette.js +346 -0
  49. package/src/feedback/context-menu.js +299 -0
  50. package/src/feedback/dialog.js +298 -0
  51. package/src/feedback/dropdown-menu.js +259 -0
  52. package/src/feedback/hover-card.js +186 -0
  53. package/src/feedback/index.js +17 -0
  54. package/src/feedback/modal.js +226 -0
  55. package/src/feedback/notification-panel.js +196 -0
  56. package/src/feedback/popover.js +184 -0
  57. package/src/feedback/progress.js +169 -0
  58. package/src/feedback/sheet.js +249 -0
  59. package/src/feedback/toast.js +207 -0
  60. package/src/feedback/tooltip.js +189 -0
  61. package/src/icons/lucide.d.ts +1915 -0
  62. package/src/icons/lucide.js +1915 -0
  63. package/src/icons/phosphor.d.ts +1517 -0
  64. package/src/icons/phosphor.js +1517 -0
  65. package/src/icons/types.d.ts +8 -0
  66. package/src/index.js +9 -0
  67. package/src/input/button.js +127 -0
  68. package/src/input/calendar.js +340 -0
  69. package/src/input/checkbox.js +159 -0
  70. package/src/input/chip.js +120 -0
  71. package/src/input/color-picker.js +461 -0
  72. package/src/input/combobox.js +295 -0
  73. package/src/input/copy-button.js +144 -0
  74. package/src/input/date-picker.js +534 -0
  75. package/src/input/file-upload.js +333 -0
  76. package/src/input/form.js +179 -0
  77. package/src/input/icon-button.js +179 -0
  78. package/src/input/index.js +31 -0
  79. package/src/input/input.js +158 -0
  80. package/src/input/multi-select.js +392 -0
  81. package/src/input/number-input.js +239 -0
  82. package/src/input/otp-input.js +221 -0
  83. package/src/input/pin-input.js +294 -0
  84. package/src/input/radio-group.js +177 -0
  85. package/src/input/radio.js +28 -0
  86. package/src/input/rating.js +195 -0
  87. package/src/input/search.js +371 -0
  88. package/src/input/segmented-control.js +174 -0
  89. package/src/input/select.js +267 -0
  90. package/src/input/slider.js +217 -0
  91. package/src/input/sortable-list.js +345 -0
  92. package/src/input/suggestion.js +26 -0
  93. package/src/input/textarea.js +203 -0
  94. package/src/input/theme-toggle.js +196 -0
  95. package/src/input/toggle.js +166 -0
  96. package/src/layout/app-shell.js +266 -0
  97. package/src/layout/auth-shell.js +153 -0
  98. package/src/layout/container.js +37 -0
  99. package/src/layout/dashboard-grid.js +62 -0
  100. package/src/layout/index.js +15 -0
  101. package/src/layout/page-header.js +100 -0
  102. package/src/layout/page-layout.js +112 -0
  103. package/src/layout/resizable.js +221 -0
  104. package/src/layout/section.js +54 -0
  105. package/src/layout/settings-layout.js +91 -0
  106. package/src/layout/split-pane.js +172 -0
  107. package/src/layout/status-bar.js +84 -0
  108. package/src/layout/toolbar.js +92 -0
  109. package/src/navigation/breadcrumb-item.js +26 -0
  110. package/src/navigation/breadcrumb.js +129 -0
  111. package/src/navigation/drawer.js +183 -0
  112. package/src/navigation/footer.js +99 -0
  113. package/src/navigation/index.js +22 -0
  114. package/src/navigation/link.js +120 -0
  115. package/src/navigation/nav-item.js +46 -0
  116. package/src/navigation/navigation-menu.js +687 -0
  117. package/src/navigation/pagination.js +186 -0
  118. package/src/navigation/scroll-spy.js +198 -0
  119. package/src/navigation/scroll-to-top.js +163 -0
  120. package/src/navigation/sidebar-link.js +28 -0
  121. package/src/navigation/sidebar-section.js +45 -0
  122. package/src/navigation/sidebar.js +336 -0
  123. package/src/navigation/spy-link.js +26 -0
  124. package/src/navigation/tab.js +26 -0
  125. package/src/navigation/tabs.js +202 -0
  126. package/src/navigation/top-bar.js +263 -0
  127. package/src/navigation/tree-item.js +38 -0
  128. package/src/navigation/tree-view.js +255 -0
  129. package/src/shared/index.js +6 -0
  130. package/src/shared/menu-divider.js +9 -0
  131. package/src/shared/menu-item.js +30 -0
  132. package/src/shared/option.js +31 -0
  133. package/src/shared-styles.js +81 -0
  134. package/src/tokens.js +445 -0
  135. package/types/index.d.ts +973 -0
@@ -0,0 +1,534 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { tokenStyles } from '../shared-styles.js';
3
+
4
+ export class ArcDatePicker extends LitElement {
5
+ static properties = {
6
+ value: { type: String },
7
+ min: { type: String },
8
+ max: { type: String },
9
+ placeholder: { type: String },
10
+ disabled: { type: Boolean, reflect: true },
11
+ label: { type: String },
12
+ _open: { state: true },
13
+ _viewMonth: { state: true },
14
+ _viewYear: { state: true },
15
+ _mode: { state: true },
16
+ };
17
+
18
+ static styles = [
19
+ tokenStyles,
20
+ css`
21
+ :host { display: inline-block; font-family: var(--font-body); position: relative; }
22
+ :host([disabled]) { opacity: 0.4; pointer-events: none; }
23
+
24
+ .wrapper {
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: var(--space-xs);
28
+ }
29
+
30
+ label {
31
+ font-family: var(--font-accent);
32
+ font-weight: 600;
33
+ font-size: var(--text-xs);
34
+ letter-spacing: 1px;
35
+ text-transform: uppercase;
36
+ color: var(--text-muted);
37
+ }
38
+
39
+ .input-wrapper {
40
+ position: relative;
41
+ display: flex;
42
+ align-items: center;
43
+ }
44
+
45
+ input {
46
+ font-family: var(--font-body);
47
+ font-size: var(--text-sm);
48
+ color: var(--text-primary);
49
+ background: var(--bg-card);
50
+ border: 1px solid var(--border-default);
51
+ border-radius: var(--radius-md);
52
+ padding: var(--space-sm) var(--space-md);
53
+ padding-right: 36px;
54
+ outline: none;
55
+ width: 100%;
56
+ box-sizing: border-box;
57
+ cursor: pointer;
58
+ transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
59
+ }
60
+
61
+ input::placeholder { color: var(--text-muted); }
62
+ input:hover:not(:focus) { border-color: var(--border-bright); }
63
+ input:focus { outline: none; border-color: rgba(var(--accent-primary-rgb), 0.4); box-shadow: var(--focus-glow); }
64
+
65
+ .calendar-icon {
66
+ position: absolute;
67
+ right: var(--space-sm);
68
+ color: var(--text-muted);
69
+ font-size: var(--text-sm);
70
+ pointer-events: none;
71
+ }
72
+
73
+ .dropdown {
74
+ position: absolute;
75
+ top: 100%;
76
+ left: 0;
77
+ z-index: 1000;
78
+ margin-top: var(--space-xs);
79
+ background: var(--bg-card);
80
+ border: 1px solid var(--border-default);
81
+ border-radius: var(--radius-lg);
82
+ box-shadow: var(--shadow-overlay);
83
+ padding: var(--space-sm);
84
+ min-width: 280px;
85
+ animation: dropdown-in 150ms ease-out;
86
+ }
87
+
88
+ @keyframes dropdown-in {
89
+ from { opacity: 0; transform: translateY(-4px); }
90
+ to { opacity: 1; transform: translateY(0); }
91
+ }
92
+
93
+ .calendar-header {
94
+ display: flex;
95
+ align-items: center;
96
+ justify-content: space-between;
97
+ padding: var(--space-xs) 0;
98
+ margin-bottom: var(--space-xs);
99
+ }
100
+
101
+ .calendar-title {
102
+ font-family: var(--font-accent);
103
+ font-weight: 600;
104
+ font-size: var(--text-xs);
105
+ letter-spacing: 1.5px;
106
+ text-transform: uppercase;
107
+ color: var(--text-primary);
108
+ background: none;
109
+ border: none;
110
+ cursor: pointer;
111
+ padding: var(--space-xs) var(--space-sm);
112
+ border-radius: var(--radius-sm);
113
+ transition: color var(--transition-fast), background var(--transition-fast);
114
+ }
115
+
116
+ .calendar-title:hover {
117
+ color: var(--accent-primary);
118
+ background: var(--bg-hover);
119
+ }
120
+
121
+ .calendar-title:focus-visible {
122
+ outline: none;
123
+ box-shadow: var(--focus-glow);
124
+ }
125
+
126
+ .picker-grid {
127
+ display: grid;
128
+ grid-template-columns: repeat(3, 1fr);
129
+ gap: var(--space-xs);
130
+ }
131
+
132
+ .picker-cell {
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ padding: var(--space-sm) var(--space-xs);
137
+ font-family: var(--font-accent);
138
+ font-size: var(--text-xs);
139
+ font-weight: 500;
140
+ letter-spacing: 1px;
141
+ text-transform: uppercase;
142
+ color: var(--text-secondary);
143
+ background: none;
144
+ border: none;
145
+ border-radius: var(--radius-sm);
146
+ cursor: pointer;
147
+ transition: background var(--transition-fast), color var(--transition-fast);
148
+ }
149
+
150
+ .picker-cell:hover {
151
+ background: var(--bg-elevated);
152
+ color: var(--text-primary);
153
+ }
154
+
155
+ .picker-cell.current {
156
+ background: var(--accent-primary);
157
+ color: var(--bg-deep);
158
+ font-weight: 600;
159
+ }
160
+
161
+ .picker-cell:focus-visible {
162
+ outline: none;
163
+ box-shadow: var(--focus-glow);
164
+ }
165
+
166
+ .nav-btn {
167
+ background: none;
168
+ border: none;
169
+ color: var(--text-secondary);
170
+ cursor: pointer;
171
+ font-size: var(--text-md);
172
+ padding: var(--space-xs);
173
+ border-radius: var(--radius-sm);
174
+ line-height: 1;
175
+ transition: color var(--transition-fast), background var(--transition-fast);
176
+ }
177
+
178
+ .nav-btn:hover {
179
+ color: var(--text-primary);
180
+ background: var(--bg-elevated);
181
+ }
182
+
183
+ .nav-btn:focus-visible {
184
+ outline: none;
185
+ box-shadow: var(--focus-glow);
186
+ }
187
+
188
+ .weekdays {
189
+ display: grid;
190
+ grid-template-columns: repeat(7, 1fr);
191
+ text-align: center;
192
+ margin-bottom: var(--space-xs);
193
+ }
194
+
195
+ .weekday {
196
+ font-size: var(--text-xs);
197
+ font-weight: 600;
198
+ color: var(--text-muted);
199
+ padding: var(--space-xs) 0;
200
+ text-transform: uppercase;
201
+ letter-spacing: 0.5px;
202
+ }
203
+
204
+ .days {
205
+ display: grid;
206
+ grid-template-columns: repeat(7, 1fr);
207
+ gap: 2px;
208
+ }
209
+
210
+ .day {
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: center;
214
+ width: 36px;
215
+ height: 36px;
216
+ margin: 0 auto;
217
+ font-size: var(--text-sm);
218
+ color: var(--text-secondary);
219
+ background: none;
220
+ border: none;
221
+ border-radius: var(--radius-full);
222
+ cursor: pointer;
223
+ transition: background var(--transition-fast), color var(--transition-fast);
224
+ position: relative;
225
+ }
226
+
227
+ .day:hover:not(.disabled):not(.empty) {
228
+ background: var(--bg-elevated);
229
+ color: var(--text-primary);
230
+ }
231
+
232
+ .day:focus-visible {
233
+ outline: none;
234
+ box-shadow: var(--focus-glow);
235
+ }
236
+
237
+ .day.today {
238
+ box-shadow: inset 0 0 0 1px var(--border-bright);
239
+ }
240
+
241
+ .day.selected {
242
+ background: var(--accent-primary);
243
+ color: var(--text-primary);
244
+ font-weight: 600;
245
+ }
246
+
247
+ .day.selected.today {
248
+ box-shadow: none;
249
+ }
250
+
251
+ .day.disabled {
252
+ color: var(--text-muted);
253
+ opacity: 0.3;
254
+ cursor: default;
255
+ }
256
+
257
+ .day.outside {
258
+ color: var(--text-muted);
259
+ opacity: 0.4;
260
+ }
261
+
262
+ .day.empty {
263
+ visibility: hidden;
264
+ }
265
+
266
+ @media (prefers-reduced-motion: reduce) {
267
+ .dropdown { animation: none; }
268
+ }
269
+ `,
270
+ ];
271
+
272
+ constructor() {
273
+ super();
274
+ this.value = '';
275
+ this.min = '';
276
+ this.max = '';
277
+ this.placeholder = 'Select date';
278
+ this.disabled = false;
279
+ this.label = '';
280
+ this._open = false;
281
+ this._mode = 'days'; // 'days' | 'months' | 'years'
282
+
283
+ const today = new Date();
284
+ this._viewMonth = today.getMonth();
285
+ this._viewYear = today.getFullYear();
286
+
287
+ this._handleOutsideClick = this._handleOutsideClick.bind(this);
288
+ this._handleEscape = this._handleEscape.bind(this);
289
+ }
290
+
291
+ connectedCallback() {
292
+ super.connectedCallback();
293
+ document.addEventListener('click', this._handleOutsideClick);
294
+ document.addEventListener('keydown', this._handleEscape);
295
+ }
296
+
297
+ disconnectedCallback() {
298
+ super.disconnectedCallback();
299
+ document.removeEventListener('click', this._handleOutsideClick);
300
+ document.removeEventListener('keydown', this._handleEscape);
301
+ }
302
+
303
+ _handleOutsideClick(e) {
304
+ if (this._open && !e.composedPath().includes(this)) {
305
+ this._open = false;
306
+ }
307
+ }
308
+
309
+ _handleEscape(e) {
310
+ if (this._open && e.key === 'Escape') {
311
+ this._open = false;
312
+ this.shadowRoot.querySelector('input')?.focus();
313
+ }
314
+ }
315
+
316
+ _toggleDropdown() {
317
+ if (this.disabled) return;
318
+ this._open = !this._open;
319
+
320
+ if (!this._open) this._mode = 'days';
321
+ if (this._open && this.value) {
322
+ const d = new Date(this.value + 'T00:00:00');
323
+ if (!isNaN(d)) {
324
+ this._viewMonth = d.getMonth();
325
+ this._viewYear = d.getFullYear();
326
+ }
327
+ }
328
+ }
329
+
330
+ _cycleMode() {
331
+ if (this._mode === 'days') this._mode = 'months';
332
+ else if (this._mode === 'months') this._mode = 'years';
333
+ else this._mode = 'days';
334
+ }
335
+
336
+ _selectMonth(month) {
337
+ this._viewMonth = month;
338
+ this._mode = 'days';
339
+ }
340
+
341
+ _selectYear(year) {
342
+ this._viewYear = year;
343
+ this._mode = 'months';
344
+ }
345
+
346
+ _prevMonth() {
347
+ if (this._viewMonth === 0) {
348
+ this._viewMonth = 11;
349
+ this._viewYear--;
350
+ } else {
351
+ this._viewMonth--;
352
+ }
353
+ }
354
+
355
+ _nextMonth() {
356
+ if (this._viewMonth === 11) {
357
+ this._viewMonth = 0;
358
+ this._viewYear++;
359
+ } else {
360
+ this._viewMonth++;
361
+ }
362
+ }
363
+
364
+ _prev() {
365
+ if (this._mode === 'days') this._prevMonth();
366
+ else if (this._mode === 'months') this._viewYear--;
367
+ else this._viewYear -= 12;
368
+ }
369
+
370
+ _next() {
371
+ if (this._mode === 'days') this._nextMonth();
372
+ else if (this._mode === 'months') this._viewYear++;
373
+ else this._viewYear += 12;
374
+ }
375
+
376
+ _selectDate(dateStr) {
377
+ this.value = dateStr;
378
+ this._open = false;
379
+
380
+ this.dispatchEvent(new CustomEvent('arc-change', {
381
+ detail: { value: this.value },
382
+ bubbles: true,
383
+ composed: true,
384
+ }));
385
+ }
386
+
387
+ _isDisabledDate(dateStr) {
388
+ if (this.min && dateStr < this.min) return true;
389
+ if (this.max && dateStr > this.max) return true;
390
+ return false;
391
+ }
392
+
393
+ _toISO(year, month, day) {
394
+ return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
395
+ }
396
+
397
+ _getMonthName(month) {
398
+ const names = ['January', 'February', 'March', 'April', 'May', 'June',
399
+ 'July', 'August', 'September', 'October', 'November', 'December'];
400
+ return names[month];
401
+ }
402
+
403
+ _formatDisplay(isoDate) {
404
+ if (!isoDate) return '';
405
+ const d = new Date(isoDate + 'T00:00:00');
406
+ if (isNaN(d)) return isoDate;
407
+ return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
408
+ }
409
+
410
+ _buildCalendarDays() {
411
+ const year = this._viewYear;
412
+ const month = this._viewMonth;
413
+ const firstDay = new Date(year, month, 1).getDay();
414
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
415
+ const daysInPrevMonth = new Date(year, month, 0).getDate();
416
+
417
+ const today = new Date();
418
+ const todayISO = this._toISO(today.getFullYear(), today.getMonth(), today.getDate());
419
+
420
+ const days = [];
421
+
422
+ // Previous month fill
423
+ for (let i = firstDay - 1; i >= 0; i--) {
424
+ const day = daysInPrevMonth - i;
425
+ const prevMonth = month === 0 ? 11 : month - 1;
426
+ const prevYear = month === 0 ? year - 1 : year;
427
+ const iso = this._toISO(prevYear, prevMonth, day);
428
+ days.push({ day, iso, outside: true, disabled: this._isDisabledDate(iso) });
429
+ }
430
+
431
+ // Current month
432
+ for (let d = 1; d <= daysInMonth; d++) {
433
+ const iso = this._toISO(year, month, d);
434
+ days.push({
435
+ day: d,
436
+ iso,
437
+ outside: false,
438
+ today: iso === todayISO,
439
+ selected: iso === this.value,
440
+ disabled: this._isDisabledDate(iso),
441
+ });
442
+ }
443
+
444
+ // Next month fill — fill to 42 cells (6 rows)
445
+ const remaining = 42 - days.length;
446
+ for (let d = 1; d <= remaining; d++) {
447
+ const nextMonth = month === 11 ? 0 : month + 1;
448
+ const nextYear = month === 11 ? year + 1 : year;
449
+ const iso = this._toISO(nextYear, nextMonth, d);
450
+ days.push({ day: d, iso, outside: true, disabled: this._isDisabledDate(iso) });
451
+ }
452
+
453
+ return days;
454
+ }
455
+
456
+ render() {
457
+ const weekdays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
458
+ const days = this._buildCalendarDays();
459
+
460
+ return html`
461
+ <div class="wrapper" part="wrapper">
462
+ ${this.label ? html`<label part="label">${this.label}</label>` : ''}
463
+
464
+ <div class="input-wrapper" part="input-wrapper">
465
+ <input
466
+ part="input"
467
+ type="text"
468
+ readonly
469
+ .value=${this._formatDisplay(this.value)}
470
+ placeholder=${this.placeholder}
471
+ ?disabled=${this.disabled}
472
+ aria-haspopup="dialog"
473
+ aria-expanded=${this._open ? 'true' : 'false'}
474
+ aria-label=${this.label || 'Choose date'}
475
+ @click=${this._toggleDropdown}
476
+ />
477
+ <span class="calendar-icon" aria-hidden="true">\u{1F4C5}</span>
478
+ </div>
479
+
480
+ ${this._open ? html`
481
+ <div class="dropdown" part="dropdown" role="dialog" aria-label="Date picker">
482
+ <div class="calendar-header">
483
+ <button class="nav-btn" @click=${this._prev} aria-label="Previous">\u2039</button>
484
+ <button class="calendar-title" @click=${this._cycleMode}>
485
+ ${this._mode === 'days' ? `${this._getMonthName(this._viewMonth)} ${this._viewYear}`
486
+ : this._mode === 'months' ? `${this._viewYear}`
487
+ : `${this._viewYear - 5} – ${this._viewYear + 6}`}
488
+ </button>
489
+ <button class="nav-btn" @click=${this._next} aria-label="Next">\u203A</button>
490
+ </div>
491
+
492
+ ${this._mode === 'days' ? html`
493
+ <div class="weekdays">
494
+ ${weekdays.map(d => html`<span class="weekday">${d}</span>`)}
495
+ </div>
496
+ <div class="days" role="grid" aria-label="Calendar">
497
+ ${days.map(d => html`
498
+ <button
499
+ class="day ${d.outside ? 'outside' : ''} ${d.today ? 'today' : ''} ${d.selected ? 'selected' : ''} ${d.disabled ? 'disabled' : ''}"
500
+ ?disabled=${d.disabled}
501
+ @click=${() => !d.disabled && this._selectDate(d.iso)}
502
+ aria-label="${d.iso}"
503
+ aria-selected=${d.selected ? 'true' : 'false'}
504
+ role="gridcell"
505
+ >${d.day}</button>
506
+ `)}
507
+ </div>
508
+ ` : this._mode === 'months' ? html`
509
+ <div class="picker-grid">
510
+ ${['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'].map((name, i) => html`
511
+ <button
512
+ class="picker-cell ${i === this._viewMonth ? 'current' : ''}"
513
+ @click=${() => this._selectMonth(i)}
514
+ >${name}</button>
515
+ `)}
516
+ </div>
517
+ ` : html`
518
+ <div class="picker-grid">
519
+ ${Array.from({ length: 12 }, (_, i) => this._viewYear - 5 + i).map(y => html`
520
+ <button
521
+ class="picker-cell ${y === this._viewYear ? 'current' : ''}"
522
+ @click=${() => this._selectYear(y)}
523
+ >${y}</button>
524
+ `)}
525
+ </div>
526
+ `}
527
+ </div>
528
+ ` : ''}
529
+ </div>
530
+ `;
531
+ }
532
+ }
533
+
534
+ customElements.define('arc-date-picker', ArcDatePicker);