snice 1.14.3 → 2.1.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 (185) hide show
  1. package/bin/templates/base/tsconfig.json +5 -4
  2. package/components/accordion/demo.html +403 -0
  3. package/components/accordion/snice-accordion-item.css +85 -0
  4. package/components/accordion/snice-accordion-item.ts +226 -0
  5. package/components/accordion/snice-accordion.css +31 -0
  6. package/components/accordion/snice-accordion.ts +182 -0
  7. package/components/accordion/snice-accordion.types.ts +32 -0
  8. package/components/alert/demo.html +445 -0
  9. package/components/alert/snice-alert.css +195 -0
  10. package/components/alert/snice-alert.ts +141 -0
  11. package/components/alert/snice-alert.types.ts +12 -0
  12. package/components/avatar/demo.html +598 -0
  13. package/components/avatar/snice-avatar.css +131 -0
  14. package/components/avatar/snice-avatar.ts +136 -0
  15. package/components/avatar/snice-avatar.types.ts +13 -0
  16. package/components/badge/demo.html +523 -0
  17. package/components/badge/snice-badge.css +161 -0
  18. package/components/badge/snice-badge.ts +117 -0
  19. package/components/badge/snice-badge.types.ts +16 -0
  20. package/components/breadcrumbs/demo.html +404 -0
  21. package/components/breadcrumbs/snice-breadcrumbs.css +133 -0
  22. package/components/breadcrumbs/snice-breadcrumbs.ts +191 -0
  23. package/components/breadcrumbs/snice-breadcrumbs.types.ts +26 -0
  24. package/components/breadcrumbs/snice-crumb.ts +26 -0
  25. package/components/button/demo.html +42 -0
  26. package/components/button/snice-button.css +230 -0
  27. package/components/button/snice-button.ts +169 -0
  28. package/components/button/snice-button.types.ts +25 -0
  29. package/components/card/demo.html +525 -0
  30. package/components/card/snice-card.css +140 -0
  31. package/components/card/snice-card.ts +102 -0
  32. package/components/card/snice-card.types.ts +10 -0
  33. package/components/checkbox/demo.html +253 -0
  34. package/components/checkbox/snice-checkbox.css +164 -0
  35. package/components/checkbox/snice-checkbox.ts +223 -0
  36. package/components/checkbox/snice-checkbox.types.ts +22 -0
  37. package/components/chip/demo.html +383 -0
  38. package/components/chip/snice-chip.css +195 -0
  39. package/components/chip/snice-chip.ts +139 -0
  40. package/components/chip/snice-chip.types.ts +15 -0
  41. package/components/date-picker/README.md +233 -0
  42. package/components/date-picker/demo.html +191 -0
  43. package/components/date-picker/snice-date-picker.css +330 -0
  44. package/components/date-picker/snice-date-picker.ts +777 -0
  45. package/components/date-picker/snice-date-picker.types.ts +83 -0
  46. package/components/divider/demo.html +233 -0
  47. package/components/divider/snice-divider.css +155 -0
  48. package/components/divider/snice-divider.ts +69 -0
  49. package/components/divider/snice-divider.types.ts +15 -0
  50. package/components/drawer/demo.html +328 -0
  51. package/components/drawer/snice-drawer.css +476 -0
  52. package/components/drawer/snice-drawer.ts +287 -0
  53. package/components/drawer/snice-drawer.types.ts +17 -0
  54. package/components/global.d.ts +14 -0
  55. package/components/input/demo.html +303 -0
  56. package/components/input/snice-input.css +257 -0
  57. package/components/input/snice-input.ts +442 -0
  58. package/components/input/snice-input.types.ts +59 -0
  59. package/components/input/test.html +77 -0
  60. package/components/layout/README.md +260 -0
  61. package/components/layout/demo.html +538 -0
  62. package/components/layout/snice-layout-blog.css +129 -0
  63. package/components/layout/snice-layout-blog.ts +48 -0
  64. package/components/layout/snice-layout-card.css +104 -0
  65. package/components/layout/snice-layout-card.ts +35 -0
  66. package/components/layout/snice-layout-centered.css +51 -0
  67. package/components/layout/snice-layout-centered.ts +22 -0
  68. package/components/layout/snice-layout-dashboard.css +98 -0
  69. package/components/layout/snice-layout-dashboard.ts +45 -0
  70. package/components/layout/snice-layout-fullscreen.css +72 -0
  71. package/components/layout/snice-layout-fullscreen.ts +34 -0
  72. package/components/layout/snice-layout-landing.css +92 -0
  73. package/components/layout/snice-layout-landing.ts +47 -0
  74. package/components/layout/snice-layout-minimal.css +16 -0
  75. package/components/layout/snice-layout-minimal.ts +19 -0
  76. package/components/layout/snice-layout-sidebar.css +117 -0
  77. package/components/layout/snice-layout-sidebar.ts +48 -0
  78. package/components/layout/snice-layout-split.css +103 -0
  79. package/components/layout/snice-layout-split.ts +29 -0
  80. package/components/layout/snice-layout.css +72 -0
  81. package/components/layout/snice-layout.ts +35 -0
  82. package/components/layout/snice-layout.types.ts +5 -0
  83. package/components/login/demo-auth-controller.ts +185 -0
  84. package/components/login/demo.html +470 -0
  85. package/components/login/snice-login.css +204 -0
  86. package/components/login/snice-login.ts +337 -0
  87. package/components/login/snice-login.types.ts +34 -0
  88. package/components/modal/demo.html +291 -0
  89. package/components/modal/snice-modal.css +203 -0
  90. package/components/modal/snice-modal.ts +233 -0
  91. package/components/modal/snice-modal.types.ts +21 -0
  92. package/components/pagination/demo.html +395 -0
  93. package/components/pagination/snice-pagination.ts +333 -0
  94. package/components/pagination/snice-pagination.types.ts +21 -0
  95. package/components/progress/demo.html +510 -0
  96. package/components/progress/snice-progress.css +267 -0
  97. package/components/progress/snice-progress.ts +247 -0
  98. package/components/progress/snice-progress.types.ts +19 -0
  99. package/components/radio/demo.html +287 -0
  100. package/components/radio/snice-radio.css +171 -0
  101. package/components/radio/snice-radio.ts +218 -0
  102. package/components/radio/snice-radio.types.ts +21 -0
  103. package/components/select/demo.html +511 -0
  104. package/components/select/snice-option.ts +52 -0
  105. package/components/select/snice-option.types.ts +14 -0
  106. package/components/select/snice-select.css +392 -0
  107. package/components/select/snice-select.ts +796 -0
  108. package/components/select/snice-select.types.ts +55 -0
  109. package/components/skeleton/demo.html +514 -0
  110. package/components/skeleton/snice-skeleton.css +109 -0
  111. package/components/skeleton/snice-skeleton.ts +126 -0
  112. package/components/skeleton/snice-skeleton.types.ts +11 -0
  113. package/components/switch/demo.html +284 -0
  114. package/components/switch/snice-switch.css +221 -0
  115. package/components/switch/snice-switch.ts +229 -0
  116. package/components/switch/snice-switch.types.ts +23 -0
  117. package/components/symbols.ts +23 -0
  118. package/components/table/demo-table-controller.ts +100 -0
  119. package/components/table/demo.html +480 -0
  120. package/components/table/snice-cell-boolean.ts +112 -0
  121. package/components/table/snice-cell-date.ts +210 -0
  122. package/components/table/snice-cell-duration.ts +91 -0
  123. package/components/table/snice-cell-filesize.ts +90 -0
  124. package/components/table/snice-cell-number.ts +165 -0
  125. package/components/table/snice-cell-progress.ts +83 -0
  126. package/components/table/snice-cell-rating.ts +82 -0
  127. package/components/table/snice-cell-sparkline.ts +253 -0
  128. package/components/table/snice-cell-text.ts +125 -0
  129. package/components/table/snice-cell.css +296 -0
  130. package/components/table/snice-cell.ts +473 -0
  131. package/components/table/snice-column.ts +353 -0
  132. package/components/table/snice-header.css +243 -0
  133. package/components/table/snice-header.ts +261 -0
  134. package/components/table/snice-progress.ts +66 -0
  135. package/components/table/snice-rating.ts +45 -0
  136. package/components/table/snice-row.css +255 -0
  137. package/components/table/snice-row.ts +331 -0
  138. package/components/table/snice-table.css +241 -0
  139. package/components/table/snice-table.ts +737 -0
  140. package/components/table/snice-table.types.ts +158 -0
  141. package/components/tabs/demo.html +487 -0
  142. package/components/tabs/snice-tab-panel.css +264 -0
  143. package/components/tabs/snice-tab-panel.ts +47 -0
  144. package/components/tabs/snice-tab.css +96 -0
  145. package/components/tabs/snice-tab.ts +65 -0
  146. package/components/tabs/snice-tabs.css +189 -0
  147. package/components/tabs/snice-tabs.ts +332 -0
  148. package/components/tabs/snice-tabs.types.ts +28 -0
  149. package/components/theme/theme.css +234 -0
  150. package/components/toast/demo.html +329 -0
  151. package/components/toast/snice-toast-container.ts +256 -0
  152. package/components/toast/snice-toast.css +213 -0
  153. package/components/toast/snice-toast.ts +276 -0
  154. package/components/toast/snice-toast.types.ts +35 -0
  155. package/components/tooltip/demo.html +350 -0
  156. package/components/tooltip/snice-tooltip-portal.css +79 -0
  157. package/components/tooltip/snice-tooltip.css +117 -0
  158. package/components/tooltip/snice-tooltip.ts +612 -0
  159. package/components/tooltip/snice-tooltip.types.ts +32 -0
  160. package/components/transitions.ts +94 -0
  161. package/components/tsconfig.json +18 -0
  162. package/dist/index.cjs +441 -329
  163. package/dist/index.cjs.map +1 -1
  164. package/dist/index.cjs.min.map +1 -1
  165. package/dist/index.esm.js +441 -329
  166. package/dist/index.esm.js.map +1 -1
  167. package/dist/index.esm.min.js +3 -3
  168. package/dist/index.esm.min.js.map +1 -1
  169. package/dist/index.iife.js +441 -329
  170. package/dist/index.iife.js.map +1 -1
  171. package/dist/index.iife.min.js +3 -3
  172. package/dist/index.iife.min.js.map +1 -1
  173. package/dist/symbols.esm.js +1 -1
  174. package/dist/transitions.esm.js +1 -1
  175. package/dist/types/controller.d.ts +1 -1
  176. package/dist/types/element.d.ts +10 -10
  177. package/dist/types/events.d.ts +2 -2
  178. package/dist/types/index.d.ts +1 -1
  179. package/dist/types/observe.d.ts +1 -1
  180. package/dist/types/request-response.d.ts +2 -3
  181. package/dist/types/router.d.ts +1 -1
  182. package/package.json +9 -3
  183. package/dist/index.cjs.min +0 -15
  184. package/dist/symbols.cjs +0 -103
  185. package/dist/transitions.cjs +0 -219
@@ -0,0 +1,331 @@
1
+ import { element, property, on, watch, ready, dispatch } from 'snice';
2
+ import css from './snice-row.css?inline';
3
+ import type {
4
+ SniceRowElement,
5
+ ColumnDefinition,
6
+ SniceCellElement
7
+ } from './snice-table.types';
8
+ import './snice-cell';
9
+ import './snice-cell-text';
10
+ import './snice-cell-number';
11
+ import './snice-cell-date';
12
+ import './snice-cell-sparkline';
13
+
14
+ @element('snice-row')
15
+ export class SniceRow extends HTMLElement implements SniceRowElement {
16
+ @property({ type: Boolean, reflect: true })
17
+ selected: boolean = false;
18
+
19
+ @property({ type: Boolean, reflect: true })
20
+ hoverable: boolean = true;
21
+
22
+ @property({ type: Boolean, reflect: true })
23
+ clickable: boolean = false;
24
+
25
+ @property({ type: Boolean, reflect: true })
26
+ selectable: boolean = false;
27
+
28
+ @property({ type: Object })
29
+ data: any = {};
30
+
31
+ // Extract data from data-* attributes - pass raw values, let cells handle conversion
32
+ private extractDataFromAttributes() {
33
+ const data: any = {};
34
+
35
+ // Get all attribute names that start with data-
36
+ const attrNames = this.getAttributeNames().filter(name =>
37
+ name.startsWith('data-') && name !== 'data-column-index'
38
+ );
39
+
40
+ attrNames.forEach(attrName => {
41
+ const key = attrName.slice(5); // Remove 'data-' prefix
42
+ const value = this.getAttribute(attrName);
43
+ if (value !== null) {
44
+ data[key] = value; // Keep as raw string, let cells convert
45
+ }
46
+ });
47
+ return data;
48
+ }
49
+
50
+ @property({ type: Number, reflect: true })
51
+ index: number = 0;
52
+
53
+ @property({ type: Array })
54
+ columns: ColumnDefinition[] = [];
55
+
56
+ html() {
57
+ return `
58
+ <div class="row-container" part="container">
59
+ ${this.selectable ? this.renderCheckbox() : ''}
60
+ <div class="cells-container">
61
+ ${this.columns && this.columns.length ? this.columns.map((column, columnIndex) => this.renderCell(column, columnIndex)).join('') : '<!-- No columns set yet -->'}
62
+ </div>
63
+ </div>
64
+ `;
65
+ }
66
+
67
+ css() {
68
+ return css;
69
+ }
70
+
71
+ @ready()
72
+ init() {
73
+ // Extract data from data-* attributes if data object is empty
74
+ if (!this.data || Object.keys(this.data).length === 0) {
75
+ this.data = this.extractDataFromAttributes();
76
+ }
77
+ this.updateRowAttributes();
78
+ this.configureCells();
79
+ }
80
+
81
+ private configureCells() {
82
+ if (!this.columns) {
83
+ return;
84
+ }
85
+
86
+ this.columns.forEach((column, index) => {
87
+ const cellElement = this.shadowRoot?.querySelector(`[data-column-index="${index}"]`) as any;
88
+ if (cellElement) {
89
+ const value = this.data[column.key];
90
+ cellElement.value = value;
91
+ cellElement.column = column;
92
+ cellElement.rowData = this.data;
93
+
94
+ // For sparkline cells, also set the data property if value is an array
95
+ if (column.type === 'sparkline' && Array.isArray(value)) {
96
+ cellElement.data = value;
97
+ }
98
+ }
99
+ });
100
+ }
101
+
102
+ @watch('selected')
103
+ updateSelection() {
104
+ this.updateRowAttributes();
105
+ const checkbox = this.shadowRoot?.querySelector('.row-checkbox') as HTMLInputElement;
106
+ if (checkbox) {
107
+ checkbox.checked = this.selected;
108
+ }
109
+ }
110
+
111
+ @watch('hoverable', 'clickable', 'selectable')
112
+ updateRowAttributes() {
113
+ this.classList.toggle('row--hoverable', this.hoverable);
114
+ this.classList.toggle('row--clickable', this.clickable);
115
+ this.classList.toggle('row--selectable', this.selectable);
116
+ this.classList.toggle('row--selected', this.selected);
117
+ }
118
+
119
+ @watch('data', 'columns')
120
+ updateCells() {
121
+ const cellsContainer = this.shadowRoot?.querySelector('.cells-container');
122
+ if (cellsContainer && this.columns && this.columns.length) {
123
+ cellsContainer.innerHTML = this.renderCells();
124
+ this.configureCells();
125
+ }
126
+ }
127
+
128
+ @on('click')
129
+ handleRowClick(e: MouseEvent) {
130
+ // Don't trigger row click if clicking on checkbox or cell interactive elements
131
+ const target = e.target as HTMLElement;
132
+ if (target.matches('input[type="checkbox"], button, a, .interactive')) {
133
+ return;
134
+ }
135
+
136
+ if (this.clickable) {
137
+ this.dispatchRowClick();
138
+ }
139
+
140
+ if (this.selectable) {
141
+ this.toggleSelection();
142
+ }
143
+ }
144
+
145
+ @on('change', '.row-checkbox')
146
+ handleCheckboxChange(e: Event) {
147
+ const checkbox = e.target as HTMLInputElement;
148
+ this.selected = checkbox.checked;
149
+ this.dispatchRowSelect();
150
+ }
151
+
152
+ @on('keydown')
153
+ handleKeyDown(e: KeyboardEvent) {
154
+ if (e.key === 'Enter' || e.key === ' ') {
155
+ e.preventDefault();
156
+
157
+ if (this.selectable) {
158
+ this.toggleSelection();
159
+ } else if (this.clickable) {
160
+ this.dispatchRowClick();
161
+ }
162
+ }
163
+ }
164
+
165
+ private renderCheckbox(): string {
166
+ return `
167
+ <div class="cell cell--checkbox" part="checkbox-cell">
168
+ <input
169
+ type="checkbox"
170
+ class="row-checkbox"
171
+ ${this.selected ? 'checked' : ''}
172
+ tabindex="-1"
173
+ />
174
+ </div>
175
+ `;
176
+ }
177
+
178
+ private renderCells(): string {
179
+ if (!this.columns || !this.columns.length) {
180
+ return '<!-- No columns available -->';
181
+ }
182
+ return this.columns.map((column, columnIndex) => this.renderCell(column, columnIndex)).join('');
183
+ }
184
+
185
+ private renderCell(column: ColumnDefinition, columnIndex: number): string {
186
+ const value = this.data[column.key];
187
+ const cellComponent = this.getCellComponent(column.type);
188
+
189
+ return `
190
+ <div class="cell cell--${column.type}" part="cell" style="${this.getCellStyles(column)}">
191
+ <${cellComponent}
192
+ type="${column.type}"
193
+ align="${column.align || 'left'}"
194
+ data-column-index="${columnIndex}"
195
+ ></${cellComponent}>
196
+ </div>
197
+ `;
198
+ }
199
+
200
+ private getCellComponent(type: string | undefined): string {
201
+ switch (type) {
202
+ case 'text':
203
+ return 'snice-cell-text';
204
+ case 'number':
205
+ case 'currency':
206
+ case 'percent':
207
+ case 'accounting':
208
+ case 'scientific':
209
+ case 'fraction':
210
+ return 'snice-cell-number';
211
+ case 'date':
212
+ return 'snice-cell-date';
213
+ case 'boolean':
214
+ return 'snice-cell-boolean';
215
+ case 'sparkline':
216
+ return 'snice-cell-sparkline';
217
+ case 'rating':
218
+ return 'snice-cell-rating';
219
+ case 'progress':
220
+ return 'snice-cell-progress';
221
+ case 'duration':
222
+ return 'snice-cell-duration';
223
+ case 'filesize':
224
+ return 'snice-cell-filesize';
225
+ case 'custom':
226
+ default:
227
+ return 'snice-cell';
228
+ }
229
+ }
230
+
231
+ private getCellStyles(column: ColumnDefinition): string {
232
+ let styles = [];
233
+
234
+ if (column.width) {
235
+ styles.push(`width: ${column.width}`);
236
+ styles.push(`min-width: ${column.width}`);
237
+ styles.push(`max-width: ${column.width}`);
238
+ }
239
+
240
+ if (column.align) {
241
+ styles.push(`text-align: ${column.align}`);
242
+ }
243
+
244
+ return styles.join('; ');
245
+ }
246
+
247
+ private escapeHtml(value: any): string {
248
+ if (value === null || value === undefined) {
249
+ return '';
250
+ }
251
+
252
+ const str = String(value);
253
+ const div = document.createElement('div');
254
+ div.textContent = str;
255
+ return div.innerHTML;
256
+ }
257
+
258
+ private toggleSelection() {
259
+ this.selected = !this.selected;
260
+ this.dispatchRowSelect();
261
+ }
262
+
263
+ @dispatch('@snice/row-click', { bubbles: true, composed: true })
264
+ private dispatchRowClick() {
265
+ return {
266
+ data: this.data,
267
+ index: this.index,
268
+ element: this
269
+ };
270
+ }
271
+
272
+ @dispatch('@snice/row-select', { bubbles: true, composed: true })
273
+ private dispatchRowSelect() {
274
+ return {
275
+ selected: this.selected,
276
+ data: this.data,
277
+ index: this.index,
278
+ element: this
279
+ };
280
+ }
281
+
282
+ @dispatch('@snice/row-hover', { bubbles: true, composed: true })
283
+ private dispatchRowHover() {
284
+ return {
285
+ data: this.data,
286
+ index: this.index,
287
+ element: this
288
+ };
289
+ }
290
+
291
+ // Public API methods
292
+ select() {
293
+ this.selected = true;
294
+ this.dispatchRowSelect();
295
+ }
296
+
297
+ deselect() {
298
+ this.selected = false;
299
+ this.dispatchRowSelect();
300
+ }
301
+
302
+ focusRow() {
303
+ super.focus();
304
+ this.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
305
+ }
306
+
307
+ getCellValue(columnKey: string): any {
308
+ return this.data[columnKey];
309
+ }
310
+
311
+ setCellValue(columnKey: string, value: any) {
312
+ this.data = { ...this.data, [columnKey]: value };
313
+ this.updateCells();
314
+ }
315
+
316
+ getCellElement(columnKey: string): SniceCellElement | null {
317
+ const column = this.columns.find(col => col.key === columnKey);
318
+ if (!column) return null;
319
+
320
+ const columnIndex = this.columns.indexOf(column);
321
+ return this.shadowRoot?.querySelector(`[data-column-index="${columnIndex}"]`) as SniceCellElement || null;
322
+ }
323
+
324
+ // Highlight row temporarily (useful for showing updates)
325
+ highlight(duration: number = 2000) {
326
+ this.classList.add('row--highlighted');
327
+ setTimeout(() => {
328
+ this.classList.remove('row--highlighted');
329
+ }, duration);
330
+ }
331
+ }
@@ -0,0 +1,241 @@
1
+ /* Force host element to have dimensions */
2
+ :host {
3
+ display: block;
4
+ width: 100%;
5
+ min-width: 400px;
6
+ min-height: 200px;
7
+ }
8
+
9
+ /* Table Container */
10
+ .table-container {
11
+ display: flex;
12
+ flex-direction: column;
13
+ width: 100%;
14
+ min-width: 400px;
15
+ min-height: 200px;
16
+ background: #ffffff;
17
+ border: 1px solid #ddd;
18
+ border-radius: 8px;
19
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
20
+ overflow: hidden;
21
+ }
22
+
23
+ /* Table Controls */
24
+ .table-controls {
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: space-between;
28
+ padding: var(--snice-spacing-md);
29
+ background: var(--snice-color-background-secondary);
30
+ border-bottom: 1px solid var(--snice-color-border);
31
+ }
32
+
33
+ .table-controls.hidden {
34
+ display: none;
35
+ }
36
+
37
+ .search-wrapper {
38
+ flex: 1;
39
+ max-width: 20rem; /* 320px */
40
+ }
41
+
42
+ .search-wrapper.hidden {
43
+ display: none;
44
+ }
45
+
46
+ .table-search {
47
+ width: 100%;
48
+ }
49
+
50
+ /* Table Wrapper */
51
+ .table-wrapper {
52
+ position: relative;
53
+ overflow: auto;
54
+ flex: 1;
55
+ min-height: 6.25rem; /* 100px converted to rem */
56
+ }
57
+
58
+ .table {
59
+ display: block; /* Use block with CSS flexbox behavior */
60
+ width: 100%;
61
+ min-width: 100%;
62
+ min-height: 3.125rem; /* 50px converted to rem */
63
+ font-size: var(--snice-font-size-sm, 0.875rem); /* 14px */
64
+ line-height: var(--snice-line-height-normal, 1.5);
65
+ }
66
+
67
+ /* Table Body - flexbox container for rows */
68
+ .table-body {
69
+ display: flex;
70
+ flex-direction: column;
71
+ min-height: 2.5rem; /* 40px converted to rem */
72
+ }
73
+
74
+ /* Loading State */
75
+ .table-loading {
76
+ position: absolute;
77
+ top: 0;
78
+ left: 0;
79
+ right: 0;
80
+ bottom: 0;
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ background: rgba(255, 255, 255, 0.8);
85
+ z-index: 10;
86
+ }
87
+
88
+ .table-loading.hidden {
89
+ display: none;
90
+ }
91
+
92
+ /* Empty State */
93
+ .table-empty {
94
+ padding: var(--snice-spacing-2xl) var(--snice-spacing-md);
95
+ text-align: center;
96
+ color: var(--snice-color-text-secondary);
97
+ font-style: italic;
98
+ }
99
+
100
+ .table-empty.hidden {
101
+ display: none;
102
+ }
103
+
104
+ /* Table Body */
105
+ .table-body {
106
+ background: var(--snice-color-background);
107
+ }
108
+
109
+ /* Pagination */
110
+ .table-pagination {
111
+ padding: var(--snice-spacing-md);
112
+ border-top: 1px solid var(--snice-color-border);
113
+ background: var(--snice-color-background-secondary);
114
+ }
115
+
116
+ .table-pagination.hidden {
117
+ display: none;
118
+ }
119
+
120
+ /* Size Variants */
121
+ :host([size="small"]) .table {
122
+ font-size: var(--snice-font-size-xs);
123
+ line-height: var(--snice-line-height-dense);
124
+ }
125
+
126
+ :host([size="small"]) .table-controls {
127
+ padding: var(--snice-spacing-sm);
128
+ }
129
+
130
+ :host([size="large"]) .table {
131
+ font-size: var(--snice-font-size-md);
132
+ line-height: var(--snice-line-height-loose);
133
+ }
134
+
135
+ :host([size="large"]) .table-controls {
136
+ padding: var(--snice-spacing-lg);
137
+ }
138
+
139
+ /* Variant Styles */
140
+ :host([variant="striped"]) .table-body snice-row:nth-child(even) {
141
+ background-color: var(--snice-color-background-secondary);
142
+ }
143
+
144
+ :host([variant="bordered"]) .table {
145
+ border: 1px solid var(--snice-color-border);
146
+ }
147
+
148
+ :host([variant="bordered"]) .table-body snice-row {
149
+ border-bottom: 1px solid var(--snice-color-border);
150
+ }
151
+
152
+ /* State Classes */
153
+ :host([striped]) .table-body snice-row:nth-child(even) {
154
+ background-color: var(--snice-color-background-secondary);
155
+ }
156
+
157
+ :host([bordered]) .table {
158
+ border: 1px solid var(--snice-color-border);
159
+ }
160
+
161
+ :host([bordered]) .table-body snice-row {
162
+ border-bottom: 1px solid var(--snice-color-border);
163
+ }
164
+
165
+ :host([hoverable]) .table-body snice-row:hover {
166
+ background-color: var(--snice-color-background-tertiary);
167
+ }
168
+
169
+ /* Sticky Header */
170
+ :host([sticky-header]) snice-header {
171
+ position: sticky;
172
+ top: 0;
173
+ z-index: 5;
174
+ background: white;
175
+ border-bottom: 2px solid #dee2e6;
176
+ }
177
+
178
+ /* Loading State */
179
+ :host([loading]) .table-wrapper {
180
+ opacity: 0.7;
181
+ pointer-events: none;
182
+ }
183
+
184
+ /* Responsive Design */
185
+ @media (max-width: 768px) {
186
+ .table-controls {
187
+ flex-direction: column;
188
+ gap: 0.75rem; /* 12px */
189
+ }
190
+
191
+ .search-wrapper {
192
+ max-width: 100%;
193
+ }
194
+
195
+ .table-wrapper {
196
+ overflow-x: auto;
197
+ }
198
+
199
+ .table {
200
+ min-width: 40rem; /* 640px - minimum table width on mobile */
201
+ }
202
+ }
203
+
204
+ /* State Classes */
205
+ :host(.table--loading) .table-wrapper {
206
+ opacity: 0.7;
207
+ pointer-events: none;
208
+ }
209
+
210
+ :host(.table--error) .table-wrapper {
211
+ border: 1px solid var(--snice-color-danger);
212
+ }
213
+
214
+ :host(.table--error) .table-empty::before {
215
+ content: "⚠️ ";
216
+ color: var(--snice-color-danger);
217
+ }
218
+
219
+ /* Focus Styles */
220
+ :host(:focus-within) {
221
+ outline: var(--snice-focus-ring-width) solid var(--snice-focus-ring-color);
222
+ outline-offset: var(--snice-focus-ring-offset);
223
+ }
224
+
225
+ /* Accessibility */
226
+ @media (prefers-reduced-motion: reduce) {
227
+ * {
228
+ transition: none !important;
229
+ }
230
+ }
231
+
232
+ /* High Contrast Mode */
233
+ @media (prefers-contrast: high) {
234
+ .table {
235
+ border: 2px solid;
236
+ }
237
+
238
+ .table-controls {
239
+ border: 1px solid;
240
+ }
241
+ }