@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,332 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { tokenStyles } from '../shared-styles.js';
3
+ import './column.js';
4
+
5
+ export class ArcDataTable extends LitElement {
6
+ static properties = {
7
+ rows: { type: Array },
8
+ sortable: { type: Boolean },
9
+ selectable: { type: Boolean },
10
+ sortColumn: { type: String, attribute: 'sort-column' },
11
+ sortDirection: { type: String, reflect: true, attribute: 'sort-direction' },
12
+ _columns: { state: true },
13
+ _selectedRows: { state: true },
14
+ };
15
+
16
+ static styles = [
17
+ tokenStyles,
18
+ css`
19
+ :host { display: block; font-family: var(--font-body); }
20
+
21
+ .table-wrapper {
22
+ overflow-x: auto;
23
+ border: 1px solid var(--border-subtle);
24
+ border-radius: var(--radius-md);
25
+ }
26
+
27
+ table {
28
+ width: 100%;
29
+ border-collapse: collapse;
30
+ font-size: var(--text-sm);
31
+ color: var(--text-primary);
32
+ }
33
+
34
+ thead {
35
+ background: var(--bg-elevated);
36
+ position: sticky;
37
+ top: 0;
38
+ z-index: 1;
39
+ }
40
+
41
+ th {
42
+ text-align: left;
43
+ padding: var(--space-sm) var(--space-md);
44
+ font-family: var(--font-accent);
45
+ font-weight: 600;
46
+ font-size: var(--text-xs);
47
+ letter-spacing: 1px;
48
+ text-transform: uppercase;
49
+ color: var(--text-muted);
50
+ border-bottom: 1px solid var(--border-default);
51
+ white-space: nowrap;
52
+ user-select: none;
53
+ }
54
+
55
+ th.sortable {
56
+ cursor: pointer;
57
+ transition: color var(--transition-fast);
58
+ }
59
+
60
+ th.sortable:hover {
61
+ color: var(--text-primary);
62
+ }
63
+
64
+ th.sorted {
65
+ color: var(--accent-primary);
66
+ }
67
+
68
+ .sort-indicator {
69
+ display: inline-block;
70
+ margin-left: var(--space-xs);
71
+ font-size: var(--text-xs);
72
+ vertical-align: middle;
73
+ }
74
+
75
+ td {
76
+ padding: var(--space-sm) var(--space-md);
77
+ border-bottom: 1px solid var(--border-subtle);
78
+ color: var(--text-secondary);
79
+ line-height: 1.5;
80
+ }
81
+
82
+ tr:last-child td {
83
+ border-bottom: none;
84
+ }
85
+
86
+ tbody tr:nth-child(odd) {
87
+ background: var(--bg-surface);
88
+ }
89
+
90
+ tbody tr:nth-child(even) {
91
+ background: var(--bg-card);
92
+ }
93
+
94
+ tbody tr:hover {
95
+ background: var(--bg-elevated);
96
+ }
97
+
98
+ tbody tr.selected {
99
+ background: rgba(var(--accent-primary-rgb), 0.08);
100
+ }
101
+
102
+ .checkbox-cell {
103
+ width: 40px;
104
+ text-align: center;
105
+ }
106
+
107
+ input[type="checkbox"] {
108
+ appearance: none;
109
+ width: 16px;
110
+ height: 16px;
111
+ border: 1px solid var(--border-bright);
112
+ border-radius: var(--radius-sm);
113
+ background: var(--bg-card);
114
+ cursor: pointer;
115
+ position: relative;
116
+ vertical-align: middle;
117
+ transition: border-color var(--transition-fast), background var(--transition-fast);
118
+ }
119
+
120
+ input[type="checkbox"]:hover {
121
+ border-color: var(--accent-primary);
122
+ }
123
+
124
+ input[type="checkbox"]:checked {
125
+ background: var(--accent-primary);
126
+ border-color: var(--accent-primary);
127
+ }
128
+
129
+ input[type="checkbox"]:checked::after {
130
+ content: '';
131
+ position: absolute;
132
+ top: 2px;
133
+ left: 5px;
134
+ width: 4px;
135
+ height: 8px;
136
+ border: solid var(--text-primary);
137
+ border-width: 0 2px 2px 0;
138
+ transform: rotate(45deg);
139
+ }
140
+
141
+ input[type="checkbox"]:focus-visible {
142
+ outline: none;
143
+ box-shadow: var(--focus-glow);
144
+ }
145
+
146
+ .empty-state {
147
+ padding: var(--space-xl);
148
+ text-align: center;
149
+ color: var(--text-muted);
150
+ font-style: italic;
151
+ }
152
+
153
+ .data-table__slot-host { display: none; }
154
+
155
+ @media (prefers-reduced-motion: reduce) {
156
+ :host *,
157
+ :host *::before,
158
+ :host *::after {
159
+ animation-duration: 0.01ms !important;
160
+ animation-iteration-count: 1 !important;
161
+ transition-duration: 0.01ms !important;
162
+ }
163
+ }
164
+ `,
165
+ ];
166
+
167
+ constructor() {
168
+ super();
169
+ this.rows = [];
170
+ this.sortable = false;
171
+ this.selectable = false;
172
+ this.sortColumn = '';
173
+ this.sortDirection = 'asc';
174
+ this._columns = [];
175
+ this._selectedRows = new Set();
176
+ }
177
+
178
+ _onSlotChange(e) {
179
+ this._columns = e.target.assignedElements({ flatten: true })
180
+ .filter(el => el.tagName === 'ARC-COLUMN');
181
+ }
182
+
183
+ _handleSort(column) {
184
+ if (!this.sortable || !column.sortable) return;
185
+
186
+ if (this.sortColumn === column.key) {
187
+ this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
188
+ } else {
189
+ this.sortColumn = column.key;
190
+ this.sortDirection = 'asc';
191
+ }
192
+
193
+ this.dispatchEvent(new CustomEvent('arc-sort', {
194
+ detail: { column: this.sortColumn, direction: this.sortDirection },
195
+ bubbles: true,
196
+ composed: true,
197
+ }));
198
+ }
199
+
200
+ _handleSelectAll(e) {
201
+ const checked = e.target.checked;
202
+ if (checked) {
203
+ this._selectedRows = new Set(this.rows.map((_, i) => i));
204
+ } else {
205
+ this._selectedRows = new Set();
206
+ }
207
+
208
+ this.dispatchEvent(new CustomEvent('arc-select-all', {
209
+ detail: { selected: checked },
210
+ bubbles: true,
211
+ composed: true,
212
+ }));
213
+ }
214
+
215
+ _handleRowSelect(e, row, index) {
216
+ const checked = e.target.checked;
217
+ const next = new Set(this._selectedRows);
218
+
219
+ if (checked) {
220
+ next.add(index);
221
+ } else {
222
+ next.delete(index);
223
+ }
224
+
225
+ this._selectedRows = next;
226
+
227
+ this.dispatchEvent(new CustomEvent('arc-row-select', {
228
+ detail: { selected: checked, row },
229
+ bubbles: true,
230
+ composed: true,
231
+ }));
232
+ }
233
+
234
+ get _sortedRows() {
235
+ if (!this.sortColumn) return this.rows;
236
+
237
+ return [...this.rows].sort((a, b) => {
238
+ const aVal = a[this.sortColumn];
239
+ const bVal = b[this.sortColumn];
240
+
241
+ if (aVal == null && bVal == null) return 0;
242
+ if (aVal == null) return 1;
243
+ if (bVal == null) return -1;
244
+
245
+ let cmp;
246
+ if (typeof aVal === 'number' && typeof bVal === 'number') {
247
+ cmp = aVal - bVal;
248
+ } else {
249
+ cmp = String(aVal).localeCompare(String(bVal));
250
+ }
251
+
252
+ return this.sortDirection === 'desc' ? -cmp : cmp;
253
+ });
254
+ }
255
+
256
+ get _allSelected() {
257
+ return this.rows.length > 0 && this._selectedRows.size === this.rows.length;
258
+ }
259
+
260
+ _renderSortIndicator(column) {
261
+ if (!this.sortable || !column.sortable) return '';
262
+ if (this.sortColumn !== column.key) {
263
+ return html`<span class="sort-indicator" aria-hidden="true">\u2195</span>`;
264
+ }
265
+ return html`<span class="sort-indicator" aria-hidden="true">${this.sortDirection === 'asc' ? '\u2191' : '\u2193'}</span>`;
266
+ }
267
+
268
+ render() {
269
+ const rows = this._sortedRows;
270
+
271
+ return html`
272
+ <div class="data-table__slot-host">
273
+ <slot @slotchange=${this._onSlotChange}></slot>
274
+ </div>
275
+ <div class="table-wrapper" part="wrapper" role="region" aria-label="Data table">
276
+ <table part="table">
277
+ <thead part="head">
278
+ <tr>
279
+ ${this.selectable ? html`
280
+ <th class="checkbox-cell">
281
+ <input
282
+ type="checkbox"
283
+ aria-label="Select all rows"
284
+ .checked=${this._allSelected}
285
+ @change=${this._handleSelectAll}
286
+ />
287
+ </th>
288
+ ` : ''}
289
+ ${this._columns.map(col => html`
290
+ <th
291
+ class="${this.sortable && col.sortable ? 'sortable' : ''} ${this.sortColumn === col.key ? 'sorted' : ''}"
292
+ style="${col.width ? `width: ${col.width}` : ''}"
293
+ @click=${() => this._handleSort(col)}
294
+ aria-sort=${this.sortColumn === col.key ? (this.sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
295
+ >
296
+ ${col.label}${this._renderSortIndicator(col)}
297
+ </th>
298
+ `)}
299
+ </tr>
300
+ </thead>
301
+ <tbody part="body">
302
+ ${rows.length === 0 ? html`
303
+ <tr>
304
+ <td class="empty-state" colspan=${this._columns.length + (this.selectable ? 1 : 0)}>
305
+ No data available
306
+ </td>
307
+ </tr>
308
+ ` : rows.map((row, i) => html`
309
+ <tr class="${this._selectedRows.has(i) ? 'selected' : ''}" part="row">
310
+ ${this.selectable ? html`
311
+ <td class="checkbox-cell">
312
+ <input
313
+ type="checkbox"
314
+ aria-label="Select row ${i + 1}"
315
+ .checked=${this._selectedRows.has(i)}
316
+ @change=${(e) => this._handleRowSelect(e, row, i)}
317
+ />
318
+ </td>
319
+ ` : ''}
320
+ ${this._columns.map(col => html`
321
+ <td part="cell">${row[col.key] ?? ''}</td>
322
+ `)}
323
+ </tr>
324
+ `)}
325
+ </tbody>
326
+ </table>
327
+ </div>
328
+ `;
329
+ }
330
+ }
331
+
332
+ customElements.define('arc-data-table', ArcDataTable);
@@ -0,0 +1,153 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { tokenStyles } from '../shared-styles.js';
3
+
4
+ export class ArcDivider extends LitElement {
5
+ static properties = {
6
+ variant: { type: String, reflect: true },
7
+ align: { type: String, reflect: true },
8
+ vertical: { type: Boolean, reflect: true },
9
+ };
10
+
11
+ static styles = [
12
+ tokenStyles,
13
+ css`
14
+ :host { display: block; width: 100%; }
15
+
16
+ .divider { width: 100%; height: 1px; }
17
+
18
+ :host(:not([variant])) .divider,
19
+ :host([variant="subtle"]) .divider { background: var(--gradient-divider); }
20
+
21
+ :host([variant="glow"]) .divider {
22
+ position: relative;
23
+ background: var(--gradient-divider-glow);
24
+ box-shadow: 0 0 6px rgba(var(--accent-primary-rgb),0.08);
25
+ }
26
+ :host([variant="glow"]) .divider::after {
27
+ content: '';
28
+ position: absolute;
29
+ inset: 0;
30
+ background: linear-gradient(90deg, transparent 0%, rgba(var(--accent-secondary-rgb),0.4) 50%, transparent 100%);
31
+ background-size: 200% 100%;
32
+ animation: divider-shimmer 6s ease-in-out infinite;
33
+ mix-blend-mode: screen;
34
+ }
35
+
36
+ :host([variant="line-white"]) .divider,
37
+ :host([variant="line-primary"]) .divider,
38
+ :host([variant="line-gradient"]) .divider {
39
+ height: 2px;
40
+ margin-inline: auto;
41
+ }
42
+ :host([variant="line-white"]) .divider {
43
+ max-width: 160px;
44
+ background: linear-gradient(90deg, transparent, rgba(var(--text-primary-rgb),0.35), transparent);
45
+ }
46
+ :host([variant="line-primary"]) .divider {
47
+ max-width: 200px;
48
+ background: linear-gradient(90deg, transparent, rgba(var(--accent-primary-rgb),0.7), transparent);
49
+ box-shadow: 0 0 8px rgba(var(--accent-primary-rgb),0.3);
50
+ }
51
+ :host([variant="line-gradient"]) .divider {
52
+ max-width: 240px;
53
+ background: var(--glow-line-gradient);
54
+ box-shadow: 0 0 10px rgba(var(--accent-primary-rgb),0.25);
55
+ }
56
+
57
+ /* Alignment — rewrite gradients to originate from one edge */
58
+ :host([align="left"]) .divider { margin-inline: 0; }
59
+ :host([align="right"]) .divider { margin-left: auto; margin-right: 0; }
60
+
61
+ :host([align="left"]:not([variant])) .divider,
62
+ :host([align="left"][variant="subtle"]) .divider {
63
+ background: linear-gradient(90deg, var(--border-default), transparent);
64
+ }
65
+ :host([align="right"]:not([variant])) .divider,
66
+ :host([align="right"][variant="subtle"]) .divider {
67
+ background: linear-gradient(90deg, transparent, var(--border-default));
68
+ }
69
+
70
+ :host([align="left"][variant="glow"]) .divider {
71
+ background: linear-gradient(90deg, rgba(var(--accent-primary-rgb),0.5), rgba(var(--accent-secondary-rgb),0.3), transparent);
72
+ }
73
+ :host([align="right"][variant="glow"]) .divider {
74
+ background: linear-gradient(90deg, transparent, rgba(var(--accent-secondary-rgb),0.3), rgba(var(--accent-primary-rgb),0.5));
75
+ }
76
+
77
+ :host([align="left"][variant="line-white"]) .divider {
78
+ background: linear-gradient(90deg, rgba(var(--text-primary-rgb),0.35), transparent);
79
+ }
80
+ :host([align="right"][variant="line-white"]) .divider {
81
+ background: linear-gradient(90deg, transparent, rgba(var(--text-primary-rgb),0.35));
82
+ }
83
+
84
+ :host([align="left"][variant="line-primary"]) .divider {
85
+ background: linear-gradient(90deg, rgba(var(--accent-primary-rgb),0.7), transparent);
86
+ }
87
+ :host([align="right"][variant="line-primary"]) .divider {
88
+ background: linear-gradient(90deg, transparent, rgba(var(--accent-primary-rgb),0.7));
89
+ }
90
+
91
+ :host([align="left"][variant="line-gradient"]) .divider {
92
+ background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary), transparent);
93
+ }
94
+ :host([align="right"][variant="line-gradient"]) .divider {
95
+ background: linear-gradient(90deg, transparent, var(--accent-primary), var(--accent-secondary));
96
+ }
97
+
98
+ /* ── Vertical ── */
99
+ :host([vertical]) { display: inline-flex; width: auto; height: 100%; }
100
+ :host([vertical]) .divider { width: 1px; height: 100%; }
101
+
102
+ :host([vertical]:not([variant])) .divider,
103
+ :host([vertical][variant="subtle"]) .divider {
104
+ background: linear-gradient(180deg, transparent, var(--border-default), transparent);
105
+ }
106
+
107
+ :host([vertical][variant="glow"]) .divider {
108
+ background: linear-gradient(180deg, transparent, rgba(var(--accent-primary-rgb),0.5), rgba(var(--accent-secondary-rgb),0.3), transparent);
109
+ }
110
+
111
+ :host([vertical][variant="line-gradient"]) .divider {
112
+ width: 2px;
113
+ max-width: none;
114
+ background: linear-gradient(180deg, transparent, var(--accent-primary), var(--accent-secondary), transparent);
115
+ box-shadow: 0 0 10px rgba(var(--accent-primary-rgb),0.25);
116
+ }
117
+
118
+ :host([vertical][variant="line-primary"]) .divider {
119
+ width: 2px;
120
+ max-width: none;
121
+ background: linear-gradient(180deg, transparent, rgba(var(--accent-primary-rgb),0.7), transparent);
122
+ box-shadow: 0 0 8px rgba(var(--accent-primary-rgb),0.3);
123
+ }
124
+
125
+ :host([vertical][variant="line-white"]) .divider {
126
+ width: 2px;
127
+ max-width: none;
128
+ background: linear-gradient(180deg, transparent, rgba(var(--text-primary-rgb),0.35), transparent);
129
+ }
130
+
131
+ @keyframes divider-shimmer {
132
+ 0%, 100% { background-position: 200% 0; }
133
+ 50% { background-position: -100% 0; }
134
+ }
135
+
136
+ @media (prefers-reduced-motion: reduce) {
137
+ :host([variant="glow"]) .divider::after { animation: none; }
138
+ }
139
+ `,
140
+ ];
141
+
142
+ constructor() {
143
+ super();
144
+ this.variant = 'subtle';
145
+ this.vertical = false;
146
+ }
147
+
148
+ render() {
149
+ return html`<div class="divider" role="separator" aria-orientation=${this.vertical ? 'vertical' : 'horizontal'} part="divider"></div>`;
150
+ }
151
+ }
152
+
153
+ customElements.define('arc-divider', ArcDivider);
@@ -0,0 +1,78 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { tokenStyles } from '../shared-styles.js';
3
+
4
+ export class ArcEmptyState extends LitElement {
5
+ static properties = {
6
+ heading: { type: String },
7
+ description: { type: String },
8
+ };
9
+
10
+ static styles = [
11
+ tokenStyles,
12
+ css`
13
+ :host { display: block; }
14
+
15
+ .empty {
16
+ display: flex;
17
+ flex-direction: column;
18
+ align-items: center;
19
+ text-align: center;
20
+ padding: var(--space-2xl) var(--space-xl);
21
+ border: 1px dashed var(--border-default);
22
+ border-radius: var(--radius-lg);
23
+ background: var(--bg-card);
24
+ }
25
+
26
+ .empty__icon {
27
+ margin-bottom: var(--space-lg);
28
+ color: var(--text-ghost);
29
+ font-size: 40px; /* icon size, not text */
30
+ }
31
+
32
+ .empty__heading {
33
+ margin: 0 0 var(--space-sm);
34
+ font-family: var(--font-body);
35
+ font-size: var(--heading-size);
36
+ font-weight: var(--heading-weight);
37
+ color: var(--text-primary);
38
+ }
39
+
40
+ .empty__desc {
41
+ margin: 0 0 var(--space-lg);
42
+ font-family: var(--font-body);
43
+ font-size: var(--body-size);
44
+ line-height: var(--body-lh);
45
+ color: var(--text-muted);
46
+ max-width: 360px;
47
+ }
48
+
49
+ .empty__actions {
50
+ display: flex;
51
+ gap: var(--space-sm);
52
+ }
53
+ `,
54
+ ];
55
+
56
+ constructor() {
57
+ super();
58
+ this.heading = '';
59
+ this.description = '';
60
+ }
61
+
62
+ render() {
63
+ return html`
64
+ <div class="empty" part="container" role="status">
65
+ <div class="empty__icon" part="icon" aria-hidden="true">
66
+ <slot name="icon"></slot>
67
+ </div>
68
+ ${this.heading ? html`<h3 class="empty__heading" part="heading">${this.heading}</h3>` : ''}
69
+ ${this.description ? html`<p class="empty__desc" part="description">${this.description}</p>` : ''}
70
+ <div class="empty__actions" part="actions">
71
+ <slot name="action"></slot>
72
+ </div>
73
+ </div>
74
+ `;
75
+ }
76
+ }
77
+
78
+ customElements.define('arc-empty-state', ArcEmptyState);
@@ -0,0 +1,142 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { tokenStyles } from '../shared-styles.js';
3
+
4
+ export class ArcFeatureCard extends LitElement {
5
+ static properties = {
6
+ icon: { type: String },
7
+ heading: { type: String },
8
+ description: { type: String },
9
+ href: { type: String },
10
+ };
11
+
12
+ static styles = [
13
+ tokenStyles,
14
+ css`
15
+ :host { display: block; height: 100%; }
16
+
17
+ .card {
18
+ position: relative;
19
+ border-radius: var(--radius-lg);
20
+ height: 100%;
21
+ padding: 1px;
22
+ background: var(--border-subtle);
23
+ transition: background var(--transition-slow);
24
+ text-decoration: none;
25
+ display: flex;
26
+ flex-direction: column;
27
+ }
28
+
29
+ .card:hover {
30
+ background: linear-gradient(135deg, rgba(var(--accent-primary-rgb),0.3), rgba(var(--accent-secondary-rgb),0.15), var(--border-default));
31
+ }
32
+
33
+ .card__inner {
34
+ position: relative;
35
+ background: var(--bg-card);
36
+ border-radius: calc(var(--radius-lg) - 1px);
37
+ padding: var(--space-xl) var(--space-lg);
38
+ display: flex;
39
+ flex-direction: column;
40
+ gap: var(--space-md);
41
+ flex: 1;
42
+ transition: box-shadow var(--transition-slow);
43
+ z-index: 1;
44
+ }
45
+
46
+ .card:hover .card__inner {
47
+ box-shadow: inset 0 1px 0 var(--bg-hover), var(--glow-card-hover);
48
+ }
49
+
50
+ .card__icon {
51
+ width: 44px;
52
+ height: 44px;
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ border-radius: var(--radius-md);
57
+ background: var(--accent-primary-subtle);
58
+ border: 1px solid var(--accent-primary-border);
59
+ color: var(--accent-primary);
60
+ font-size: 20px; /* icon size, not text */
61
+ transition: box-shadow var(--transition-slow), border-color var(--transition-slow), transform var(--transition-slow);
62
+ }
63
+
64
+ .card:hover .card__icon {
65
+ border-color: rgba(var(--accent-primary-rgb),0.3);
66
+ box-shadow: 0 0 20px var(--accent-primary-glow), 0 0 6px var(--accent-primary-border);
67
+ transform: translateY(-2px);
68
+ }
69
+
70
+ .card__title {
71
+ font-size: 17px; /* heading size, keep hardcoded */
72
+ font-weight: 600;
73
+ color: var(--text-primary);
74
+ transition: color var(--transition-slow);
75
+ margin: 0;
76
+ }
77
+
78
+ .card:hover .card__title { color: var(--text-primary); }
79
+
80
+ .card__desc {
81
+ color: var(--text-secondary);
82
+ font-family: var(--font-body);
83
+ font-size: var(--text-sm);
84
+ line-height: 1.7;
85
+ flex: 1;
86
+ margin: 0;
87
+ }
88
+
89
+ .card__rule {
90
+ width: 32px;
91
+ height: 1px;
92
+ background: linear-gradient(90deg, var(--accent-primary), transparent);
93
+ opacity: 0;
94
+ transition: opacity var(--transition-slow), width var(--transition-slow);
95
+ }
96
+
97
+ .card:hover .card__rule { opacity: 0.5; width: 48px; }
98
+
99
+ .card:focus-visible { outline: none; box-shadow: var(--focus-glow); border-radius: var(--radius-lg); }
100
+
101
+ @media (max-width: 768px) {
102
+ .card__inner { padding: var(--space-lg) var(--space-md); }
103
+ }
104
+
105
+ @media (prefers-reduced-motion: reduce) {
106
+ :host *,
107
+ :host *::before,
108
+ :host *::after {
109
+ animation-duration: 0.01ms !important;
110
+ animation-iteration-count: 1 !important;
111
+ transition-duration: 0.01ms !important;
112
+ }
113
+ }
114
+ `,
115
+ ];
116
+
117
+ constructor() {
118
+ super();
119
+ this.icon = '';
120
+ this.heading = '';
121
+ this.description = '';
122
+ this.href = '';
123
+ }
124
+
125
+ render() {
126
+ const inner = html`
127
+ <div class="card__inner" part="inner">
128
+ <div class="card__icon" part="icon"><slot name="icon">${this.icon}</slot></div>
129
+ <h3 class="card__title" part="title">${this.heading}</h3>
130
+ <p class="card__desc" part="description">${this.description}</p>
131
+ <span class="card__rule"></span>
132
+ </div>
133
+ `;
134
+
135
+ if (this.href) {
136
+ return html`<a class="card" href=${this.href} part="card">${inner}</a>`;
137
+ }
138
+ return html`<div class="card" part="card">${inner}</div>`;
139
+ }
140
+ }
141
+
142
+ customElements.define('arc-feature-card', ArcFeatureCard);