@livenetworks/ashlar 1.4.1 → 1.5.1

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.
@@ -21,8 +21,14 @@
21
21
  // [data-ln-table] { @include ln-table; }
22
22
  // .ln-table__toolbar { @include ln-table-toolbar; }
23
23
  //
24
- // State-driven CSS (spacer rows, sort icon .hidden toggle, content overflow
25
- // fix) stays in the co-located JS SCSS file.
24
+ // Sort/filter button styling is SHARED via scss/components/_table.scss
25
+ // (.table-sort / .table-filter structural helper classes + the :has() stack
26
+ // in @mixin table-base). No ln-table-specific sort/filter button mixins here.
27
+ // Under D1 (Option A) the <th> is no longer the sort click target, so no
28
+ // ln-table-specific cursor:pointer / select-none on the <th> is needed.
29
+ //
30
+ // State-driven CSS (spacer rows, content overflow fix) stays in
31
+ // the co-located JS SCSS file.
26
32
 
27
33
  @mixin ln-table {
28
34
  @include border;
@@ -126,95 +132,6 @@
126
132
  }
127
133
  }
128
134
 
129
- // ─── Column filter dropdowns in th ────────────────────────
130
-
131
- @mixin ln-table-column-filter {
132
- th [data-ln-dropdown] {
133
- @include inline-flex;
134
- @include items-center;
135
- }
136
-
137
- th [data-ln-dropdown] > button {
138
- @include flex;
139
- @include items-center;
140
- --gap: var(--size-xs);
141
- gap: var(--gap);
142
- background: none;
143
- border: none;
144
- color: inherit;
145
- font: inherit;
146
- @include cursor-pointer;
147
- --btn-padding-y: 0;
148
- --btn-padding-x: 0;
149
-
150
- .ln-icon-filter {
151
- opacity: 0.4;
152
- @include motion-safe {
153
- transition: opacity var(--transition-fast);
154
- }
155
- }
156
-
157
- &:hover .ln-icon-filter {
158
- opacity: 1;
159
- }
160
- }
161
-
162
- th [data-ln-dropdown-menu] button[data-ln-filter-key] {
163
- @include w-full;
164
- @include flex;
165
- @include items-center;
166
- @include text-left;
167
- --btn-padding-y: var(--size-xs);
168
- --btn-padding-x: var(--size-sm);
169
- @include text-sm;
170
- @include font-normal;
171
- background: none;
172
- border: none;
173
- border-radius: 0;
174
- color: var(--color-fg);
175
- @include cursor-pointer;
176
- @include transition-fast;
177
-
178
- &:hover {
179
- --color-bg: var(--bg-sunken);
180
- background: var(--color-bg);
181
- }
182
-
183
- &[data-active] {
184
- background-color: color-mix(in srgb, var(--color-accent) 10%, transparent);
185
- color: var(--color-accent);
186
- @include font-medium;
187
- }
188
- }
189
- }
190
-
191
- // ─── Sort indicators on th ────────────────────────────────
192
-
193
- @mixin ln-table-sort {
194
- th[data-ln-sort] {
195
- @include cursor-pointer;
196
- @include select-none;
197
-
198
- [data-ln-sort-icon] {
199
- @include size(0.875rem);
200
- --margin-inline: var(--size-xs);
201
- margin-left: var(--margin-inline);
202
- vertical-align: middle;
203
- opacity: 0.4;
204
- @include motion-safe {
205
- transition: opacity var(--transition-fast);
206
- }
207
- }
208
-
209
- &:hover [data-ln-sort-icon] {
210
- opacity: 0.7;
211
- }
212
-
213
- &[data-ln-sort-active] [data-ln-sort-icon] {
214
- opacity: 1;
215
- }
216
- }
217
- }
218
135
 
219
136
  // ─── Footer ───────────────────────────────────────────────
220
137
 
@@ -418,3 +335,5 @@
418
335
  background: transparent !important;
419
336
  }
420
337
  }
338
+
339
+
@@ -6,30 +6,59 @@
6
6
  @use 'borders' as *;
7
7
  @use 'transitions' as *;
8
8
  @use 'position' as *;
9
+ @use 'motion' as *;
10
+ @use 'interaction' as *;
11
+ @use 'form' as *;
12
+ @use 'btn' as *;
13
+ @use 'card' as *;
14
+ @use 'ln-table';
9
15
 
10
16
  // ─── Base table ─────────────────────────────────────────────────
11
17
 
12
- @mixin table-base {
18
+ @mixin table-base($sticky: false) {
13
19
  @include w-full;
14
20
  background: var(--color-bg);
15
21
  --radius: var(--radius-lg);
16
22
  border-radius: var(--radius);
17
23
  // `overflow: clip` not `overflow: hidden` — clips at rounded corners
18
24
  // without establishing a scroll container, so sticky descendants
19
- // (e.g. `@mixin data-table` thead/tfoot) bind to the outer
20
- // `.app-main > section` scroll surface instead of being trapped
21
- // inside the <table> element. Same rationale as `@mixin section-card`
22
- // and `@mixin ln-table`.
25
+ // bind to the outer scroll surface instead of being trapped inside.
23
26
  overflow: clip;
24
27
  --shadow: var(--shadow-resting);
25
28
  box-shadow: var(--shadow);
26
- border-collapse: collapse;
29
+
30
+ @if $sticky {
31
+ @include relative;
32
+ border-collapse: separate;
33
+ border-spacing: 0;
34
+ } @else {
35
+ border-collapse: collapse;
36
+ }
27
37
  font-variant-numeric: tabular-nums;
28
38
 
39
+ // Row hover transition color. Sticky/enhanced tables use a relative lightness calculation
40
+ // to make the hovered row visually distinct from --bg-sunken chrome headers/footers.
41
+ $bg-hover: var(--bg-sunken);
42
+ @if $sticky {
43
+ $bg-hover: hsl(from var(--bg-base) h s calc(l - 4));
44
+ }
45
+ --bg-row-hover: #{$bg-hover};
46
+
29
47
  thead {
30
48
  --color-bg: var(--bg-sunken);
31
49
  background: var(--color-bg);
32
50
  border-block-end: var(--border-block-end, var(--border-width-strong) solid var(--color-border));
51
+
52
+ @if $sticky {
53
+ @include sticky-top;
54
+ }
55
+ }
56
+
57
+ // Support for in-thead toolbar inside sticky tables
58
+ @if $sticky {
59
+ thead > tr:first-child:has(> th[colspan]) > th {
60
+ @include ln-table.ln-table-thead-toolbar;
61
+ }
33
62
  }
34
63
 
35
64
  th {
@@ -41,6 +70,54 @@
41
70
  letter-spacing: 0.4px;
42
71
  @include uppercase;
43
72
  color: var(--color-fg);
73
+ position: relative;
74
+ top: auto;
75
+ z-index: auto;
76
+
77
+ // Shared header-button stack — keyed off the unprefixed structural button classes,
78
+ // so table / ln-table / ln-data-table all share one positioning rule.
79
+ &:has(.table-sort):has(.table-filter) {
80
+ padding-inline-end: calc(var(--padding-x) + 2.5rem);
81
+
82
+ .table-filter {
83
+ position: absolute;
84
+ top: 50%;
85
+ transform: translateY(-50%);
86
+ right: var(--padding-x);
87
+ margin: 0;
88
+ }
89
+ .table-sort {
90
+ position: absolute;
91
+ top: 50%;
92
+ transform: translateY(-50%);
93
+ right: calc(var(--padding-x) + 1.25rem);
94
+ margin: 0;
95
+ }
96
+ }
97
+
98
+ &:has(.table-sort):not(:has(.table-filter)) {
99
+ padding-inline-end: calc(var(--padding-x) + 1.25rem);
100
+
101
+ .table-sort {
102
+ position: absolute;
103
+ top: 50%;
104
+ transform: translateY(-50%);
105
+ right: var(--padding-x);
106
+ margin: 0;
107
+ }
108
+ }
109
+
110
+ &:has(.table-filter):not(:has(.table-sort)) {
111
+ padding-inline-end: calc(var(--padding-x) + 1.25rem);
112
+
113
+ .table-filter {
114
+ position: absolute;
115
+ top: 50%;
116
+ transform: translateY(-50%);
117
+ right: var(--padding-x);
118
+ margin: 0;
119
+ }
120
+ }
44
121
  }
45
122
 
46
123
  tbody tr {
@@ -48,10 +125,7 @@
48
125
  min-height: var(--density-row-h);
49
126
 
50
127
  &:hover {
51
- // Default hover stays --bg-sunken for standalone tables. Data-table
52
- // re-binds --color-bg locally (see _data-table.scss) so its hover
53
- // disambiguates from chrome that also uses --bg-sunken.
54
- --color-bg: var(--bg-sunken);
128
+ --color-bg: var(--bg-row-hover);
55
129
  background: var(--color-bg);
56
130
  }
57
131
  }
@@ -83,6 +157,21 @@
83
157
  th.nowrap {
84
158
  @include whitespace-nowrap;
85
159
  }
160
+
161
+ // Sticky section footer inside sticky/enhanced tables
162
+ @if $sticky {
163
+ > footer {
164
+ padding: var(--padding-y) var(--padding-x);
165
+ border-block-start: var(--border-block-start, var(--border-width) solid var(--color-border));
166
+ --color-bg: var(--bg-sunken);
167
+ background: var(--color-bg);
168
+ --color-fg: var(--fg-muted);
169
+ color: var(--color-fg);
170
+ font-size: var(--text-body-sm);
171
+ position: sticky;
172
+ z-index: var(--z-sticky);
173
+ }
174
+ }
86
175
  }
87
176
 
88
177
  // ─── Responsive (mobile stacked layout) ─────────────────────────
@@ -182,34 +271,114 @@
182
271
  }
183
272
  }
184
273
 
185
- // ─── Column filter button ──────────────────────────────────────
186
- // Button inside <th> that triggers a filter popover/dropdown.
187
- // Convention: add class="filter" to the trigger button.
188
- // ln-table.js sets data-ln-filter-active on the <th> when active.
189
-
190
- @mixin table-filter-button {
191
- th .filter {
192
- @include inline-flex;
193
- @include items-center;
194
- float: right;
195
- --btn-padding-y: var(--size-2xs);
196
- --btn-padding-x: var(--size-2xs);
197
- @include rounded-sm;
198
- opacity: 0.5;
274
+ // ─── Column sort & filter buttons ──────────────────────────────
199
275
 
200
- &:hover {
201
- opacity: 1;
202
- --color-bg: var(--bg-sunken);
203
- background: var(--color-bg);
204
- }
276
+ @mixin table-sort {
277
+ opacity: 0.4;
278
+ @include motion-safe {
279
+ transition: opacity 0.15s;
205
280
  }
281
+ @include cursor-pointer;
282
+ background: none;
283
+ border: none;
284
+ --btn-padding-y: var(--size-2xs);
285
+ --btn-padding-x: var(--size-2xs);
286
+ --margin-inline: var(--size-xs);
287
+ margin-inline-start: var(--margin-inline);
288
+ vertical-align: middle;
206
289
 
207
- th [data-ln-dropdown] {
208
- float: right;
290
+ [data-ln-sort-icon="asc"],
291
+ [data-ln-sort-icon="desc"] {
292
+ display: none;
209
293
  }
294
+ }
295
+
296
+ @mixin table-sort-active {
297
+ opacity: 1;
298
+ color: var(--color-accent);
299
+ }
210
300
 
211
- th[data-ln-filter-active] .filter {
301
+ @mixin table-filter {
302
+ opacity: 0.4;
303
+ @include motion-safe {
304
+ transition: opacity 0.15s;
305
+ }
306
+ @include cursor-pointer;
307
+ background: none;
308
+ border: none;
309
+ --btn-padding-y: var(--size-2xs);
310
+ --btn-padding-x: var(--size-2xs);
311
+ --margin-inline: var(--size-2xs);
312
+ margin-inline-start: var(--margin-inline);
313
+ vertical-align: middle;
314
+ @include relative;
315
+ }
316
+
317
+ @mixin table-filter-active {
318
+ opacity: 1;
319
+ color: var(--color-accent);
320
+
321
+ &::after {
322
+ content: '';
323
+ @include absolute;
324
+ top: 0;
325
+ right: 0;
326
+ width: 0.375rem;
327
+ height: 0.375rem;
328
+ border-radius: var(--radius-full);
329
+ background: var(--color-accent);
330
+ }
331
+ }
332
+
333
+
334
+
335
+
336
+
337
+ @mixin table-row-action {
338
+ background: none;
339
+ border: none;
340
+ --shadow: none;
341
+ --btn-padding-y: var(--size-2xs);
342
+ --btn-padding-x: var(--size-2xs);
343
+ opacity: 0.5;
344
+ @include motion-safe {
345
+ transition: opacity 0.15s;
346
+ }
347
+
348
+ &:hover:not(:disabled) {
349
+ background: none;
212
350
  opacity: 1;
213
- color: var(--color-accent);
214
351
  }
215
352
  }
353
+
354
+ @mixin table-row-action-delete {
355
+ --color-primary: var(--color-error);
356
+ color: hsl(var(--color-primary));
357
+ }
358
+
359
+ @mixin table-row {
360
+ cursor: pointer;
361
+
362
+ &:hover {
363
+ --color-bg: var(--bg-sunken);
364
+ background: var(--color-bg);
365
+ }
366
+ }
367
+
368
+ @mixin table-row-selected {
369
+ background: var(--color-accent-tint);
370
+ }
371
+
372
+ @mixin table-row-focused {
373
+ --color-accent-focus: hsl(from var(--color-accent) h s calc(l + 8));
374
+ outline: 2px solid var(--color-accent-focus);
375
+ outline-offset: -2px;
376
+ }
377
+
378
+ @mixin table-spacer-row {
379
+ td {
380
+ padding: 0 !important;
381
+ border: none !important;
382
+ }
383
+ }
384
+
@@ -0,0 +1,21 @@
1
+ // ==========================================================================
2
+ // Dev & Validation Diagnostics — ln-ashlar
3
+ // ==========================================================================
4
+ // This stylesheet contains diagnostic CSS tools to validate correct HTML
5
+ // structure, missing integration attributes, and accessibility warnings during
6
+ // development. Do not compile or deploy this file to production.
7
+ // ==========================================================================
8
+
9
+ @use 'config/mixins' as *;
10
+
11
+ // 1. Table Sort: missing sort button warning
12
+ // Detects when a th[data-ln-sort] is sortable but doesn't contain a <button data-ln-table-sort>
13
+ th[data-ln-sort]:not(:has([data-ln-table-sort])) {
14
+ @include dev-inline-error("missing sort button");
15
+ }
16
+
17
+ // 2. Table Integration: missing ID warning
18
+ // Warns if a table wrapper [data-ln-table] is missing an ID, which breaks search/filter integration
19
+ [data-ln-table]:not([id]) {
20
+ @include dev-dom-error("[data-ln-table] is missing a required 'id' attribute for filter mapping.");
21
+ }
@@ -34,6 +34,7 @@
34
34
  @use 'components/scrollbar';
35
35
  @use 'components/tooltip';
36
36
  @use 'components/popover';
37
+ @use 'components/filter';
37
38
  @use 'components/chip';
38
39
  @use 'components/loader';
39
40
  @use 'components/progress';
@@ -45,7 +46,6 @@
45
46
  @use 'components/toast';
46
47
  @use 'components/upload';
47
48
  @use 'components/translations';
48
- @use 'components/data-table';
49
49
  @use 'components/ln-table';
50
50
  @use 'components/ajax';
51
51
 
@@ -1,83 +0,0 @@
1
- @use '../config/mixins' as *;
2
-
3
- [data-ln-data-table] {
4
- @include data-table;
5
- }
6
-
7
- // ─── Sort indicators ──────────────────────────────────────
8
-
9
- [data-ln-col-sort] {
10
- @include data-table-sort-button;
11
- }
12
-
13
- th:hover [data-ln-col-sort] {
14
- opacity: 0.7;
15
- }
16
-
17
- .ln-sort-asc [data-ln-col-sort],
18
- .ln-sort-desc [data-ln-col-sort] {
19
- @include data-table-sort-active;
20
- }
21
-
22
- // Active-state icon swap — hide the neutral dual-arrow, show the
23
- // matching directional icon. Mixin defaults all three to "none shown,
24
- // asc/desc hidden"; we override per active class.
25
- .ln-sort-asc [data-ln-col-sort] {
26
- [data-ln-sort-icon="none"] { display: none; }
27
- [data-ln-sort-icon="asc"] { display: inline-block; }
28
- }
29
-
30
- .ln-sort-desc [data-ln-col-sort] {
31
- [data-ln-sort-icon="none"] { display: none; }
32
- [data-ln-sort-icon="desc"] { display: inline-block; }
33
- }
34
-
35
- // ─── Filter button ────────────────────────────────────────
36
-
37
- [data-ln-col-filter] {
38
- @include data-table-filter-button;
39
- }
40
-
41
- th:hover [data-ln-col-filter] {
42
- opacity: 0.7;
43
- }
44
-
45
- .ln-filter-active {
46
- @include data-table-filter-active;
47
- }
48
-
49
- // ─── Filter dropdown ──────────────────────────────────────
50
-
51
- .column-filter-dropdown {
52
- @include data-table-filter-dropdown;
53
- }
54
-
55
- // ─── Row action buttons ─────────────────────────────────
56
-
57
- [data-ln-row-action] {
58
- @include data-table-row-action;
59
- }
60
-
61
- [data-ln-row-action="delete"] {
62
- @include data-table-row-action-delete;
63
- }
64
-
65
- // ─── Row interaction states ─────────────────────────────
66
-
67
- [data-ln-row] {
68
- @include data-table-row;
69
- }
70
-
71
- .ln-row-selected {
72
- @include data-table-row-selected;
73
- }
74
-
75
- .ln-row-focused {
76
- @include data-table-row-focused;
77
- }
78
-
79
- // ─── Virtual-scroll spacer ──────────────────────────────
80
-
81
- .ln-data-table__spacer {
82
- @include data-table-spacer-row;
83
- }