@livenetworks/ashlar 1.4.0 → 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.
- package/js/ln-accordion/ln-accordion.js +1 -1
- package/js/ln-api-connector/ln-api-connector.js +1 -1
- package/js/ln-autoresize/ln-autoresize.js +1 -1
- package/js/ln-autosave/ln-autosave.js +1 -1
- package/js/ln-circular-progress/ln-circular-progress.js +1 -1
- package/js/ln-confirm/ln-confirm.js +1 -1
- package/js/ln-core/helpers.js +6 -3
- package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
- package/js/ln-data-coordinator/ln-data-coordinator.js +1 -1
- package/js/ln-data-store/ln-data-store.js +1 -1
- package/js/ln-data-table/ln-data-table.js +1 -1
- package/js/ln-data-table/src/ln-data-table.js +0 -16
- package/js/ln-date/ln-date.js +1 -1
- package/js/ln-dropdown/ln-dropdown.js +1 -1
- package/js/ln-filter/README.md +99 -99
- package/js/ln-filter/ln-filter.js +1 -1
- package/js/ln-form/ln-form.js +1 -1
- package/js/ln-modal/ln-modal.js +1 -1
- package/js/ln-number/ln-number.js +1 -1
- package/js/ln-popover/ln-popover.js +1 -1
- package/js/ln-search/ln-search.js +1 -1
- package/js/ln-sortable/ln-sortable.js +1 -1
- package/js/ln-table/ln-table-sort.js +1 -1
- package/js/ln-table/ln-table.js +1 -1
- package/js/ln-table/src/ln-table-sort.js +156 -168
- package/js/ln-tabs/ln-tabs.js +1 -1
- package/js/ln-time/ln-time.js +1 -1
- package/js/ln-toggle/ln-toggle.js +1 -1
- package/js/ln-tooltip/ln-tooltip.js +1 -1
- package/js/ln-translations/ln-translations.js +1 -1
- package/js/ln-validate/ln-validate.js +1 -1
- package/package.json +3 -2
- package/scss/components/_filter.scss +25 -0
- package/scss/components/_ln-table.scss +1 -2
- package/scss/components/_table.scss +55 -1
- package/scss/config/mixins/_diagnostics.scss +37 -0
- package/scss/config/mixins/_filter.scss +49 -0
- package/scss/config/mixins/_index.scss +2 -1
- package/scss/config/mixins/_ln-table.scss +10 -91
- package/scss/config/mixins/_table.scss +202 -33
- package/scss/ln-ashlar-dev.scss +21 -0
- package/scss/ln-ashlar.scss +1 -1
- package/scss/components/_data-table.scss +0 -83
- package/scss/config/mixins/_data-table.scss +0 -346
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
@use '../config/mixins' as *;
|
|
2
|
+
|
|
3
|
+
// Filter dropdown content chrome (search input + options list; NO clear button —
|
|
4
|
+
// the data-ln-filter-reset "All" sentinel is the reset affordance).
|
|
5
|
+
// Floating shell is supplied separately:
|
|
6
|
+
// • popover-driven (ln-filter static): @mixin popover on the [data-ln-popover] wrapper
|
|
7
|
+
// • ln-data-table dynamic clone: floating-panel + box below
|
|
8
|
+
.column-filter-dropdown {
|
|
9
|
+
@include filter-dropdown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// ln-data-table appends its cloned dropdown directly into the <th> and
|
|
13
|
+
// manages its own open/close — it does NOT route through ln-popover, so it
|
|
14
|
+
// needs the floating shell applied here (the popover path gets it from
|
|
15
|
+
// @mixin popover instead).
|
|
16
|
+
[data-ln-data-table] .column-filter-dropdown {
|
|
17
|
+
@include floating-panel;
|
|
18
|
+
position: absolute;
|
|
19
|
+
top: 100%;
|
|
20
|
+
right: 0;
|
|
21
|
+
left: auto;
|
|
22
|
+
margin-top: var(--size-xs);
|
|
23
|
+
min-width: 14rem;
|
|
24
|
+
padding: var(--size-sm);
|
|
25
|
+
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
table {
|
|
4
4
|
@include table-base;
|
|
5
|
-
@include table-filter-button;
|
|
6
5
|
|
|
7
6
|
tr.section-header td {
|
|
8
7
|
@include table-section-header;
|
|
@@ -17,3 +16,58 @@ table {
|
|
|
17
16
|
table { @include table-responsive; }
|
|
18
17
|
}
|
|
19
18
|
}
|
|
19
|
+
|
|
20
|
+
// ─── Shared Column Sort & Filter Buttons ────────────────────────
|
|
21
|
+
// CSS targets the unprefixed structural helper class on the button, NOT the data-ln-* JS
|
|
22
|
+
// hook. .table-sort / .table-filter are thin bindings that mirror the mixins; a consumer
|
|
23
|
+
// may instead @include table-sort / table-filter on their own selector (the class is a
|
|
24
|
+
// convenience, not a mandate — see "Naming & class architecture"). Behavior hooks
|
|
25
|
+
// (data-ln-col-sort / data-ln-table-sort / data-ln-popover-for / data-ln-col-filter) are
|
|
26
|
+
// the per-system functional layer and intentionally differ.
|
|
27
|
+
|
|
28
|
+
.table-sort { @include table-sort; }
|
|
29
|
+
th:hover .table-sort { opacity: 0.7; }
|
|
30
|
+
|
|
31
|
+
// Active sort — JS-state class on the <th>, set by sort JS (client) or server.
|
|
32
|
+
.ln-sort-asc .table-sort,
|
|
33
|
+
.ln-sort-desc .table-sort { @include table-sort-active; }
|
|
34
|
+
|
|
35
|
+
.ln-sort-asc .table-sort {
|
|
36
|
+
[data-ln-sort-icon="none"] { display: none; }
|
|
37
|
+
[data-ln-sort-icon="asc"] { display: inline-block; }
|
|
38
|
+
}
|
|
39
|
+
.ln-sort-desc .table-sort {
|
|
40
|
+
[data-ln-sort-icon="none"] { display: none; }
|
|
41
|
+
[data-ln-sort-icon="desc"] { display: inline-block; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.table-filter { @include table-filter; }
|
|
45
|
+
th:hover .table-filter { opacity: 0.7; }
|
|
46
|
+
|
|
47
|
+
// Active filter — JS-state class set DIRECTLY on the button (self-selector), combined with
|
|
48
|
+
// the structural helper class. Fixes the latent ln-data-table bug: JS toggles
|
|
49
|
+
// .ln-filter-active on the button itself, but the old CSS used a descendant combinator
|
|
50
|
+
// (.ln-filter-active [data-ln-col-filter]) that never matched, so the dot never rendered.
|
|
51
|
+
// See D2.
|
|
52
|
+
.table-filter.ln-filter-active { @include table-filter-active; }
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// ─── Data Table JS-State Classes ───────────────────────────────
|
|
57
|
+
// JS-added state classes only. Row chrome (clickable cursor/hover, action
|
|
58
|
+
// buttons, sticky header) is a CONSUMER concern — applied via semantic
|
|
59
|
+
// selectors in project SCSS, never keyed off the data-ln-* JS hooks.
|
|
60
|
+
|
|
61
|
+
.ln-row-selected {
|
|
62
|
+
@include table-row-selected;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.ln-row-focused {
|
|
66
|
+
@include table-row-focused;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Virtual-scroll spacer
|
|
70
|
+
.ln-data-table__spacer {
|
|
71
|
+
@include table-spacer-row;
|
|
72
|
+
}
|
|
73
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
@use 'colors' as *;
|
|
2
|
+
@use 'spacing' as *;
|
|
3
|
+
@use 'position' as *;
|
|
4
|
+
|
|
5
|
+
// ─── DOM Diagnostics & Linter Mixins ───────────────────────────
|
|
6
|
+
// These mixins are for developer-only diagnostic overlays and validations.
|
|
7
|
+
// They should be completely isolated from production bundles.
|
|
8
|
+
|
|
9
|
+
// Generic block element DOM diagnostic error
|
|
10
|
+
// Applies a red dashed border and a prominent warning banner at the top.
|
|
11
|
+
@mixin dev-dom-error($message) {
|
|
12
|
+
outline: 2px dashed hsl(var(--color-error));
|
|
13
|
+
@include relative;
|
|
14
|
+
|
|
15
|
+
&::before {
|
|
16
|
+
content: " ⚠ #{$message}";
|
|
17
|
+
display: block;
|
|
18
|
+
background: color-mix(in srgb, hsl(var(--color-error)) 15%, transparent);
|
|
19
|
+
color: hsl(var(--color-error));
|
|
20
|
+
padding: var(--size-xs) var(--size-sm);
|
|
21
|
+
font-size: var(--text-xs, 0.75rem);
|
|
22
|
+
font-weight: bold;
|
|
23
|
+
border-bottom: 1px solid hsl(var(--color-strong));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Generic inline/element-level DOM diagnostic warning
|
|
28
|
+
// Appends a red inline text warning adjacent to the targeted element.
|
|
29
|
+
@mixin dev-inline-error($message) {
|
|
30
|
+
&::after {
|
|
31
|
+
content: " ⚠ #{$message}";
|
|
32
|
+
color: hsl(var(--color-error));
|
|
33
|
+
font-weight: normal;
|
|
34
|
+
white-space: nowrap;
|
|
35
|
+
margin-inline-start: var(--size-xs, 0.5rem);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@use 'spacing' as *;
|
|
2
|
+
@use 'display' as *;
|
|
3
|
+
@use 'interaction' as *;
|
|
4
|
+
@use 'transitions' as *;
|
|
5
|
+
@use 'form' as *;
|
|
6
|
+
|
|
7
|
+
// ─── Filter dropdown — content chrome only ─────────────────────────
|
|
8
|
+
// The floating shell (chrome, fixed positioning, min-width, show/hide)
|
|
9
|
+
// comes from `@mixin popover` on the popover-driven path, OR from
|
|
10
|
+
// `@mixin floating-panel` + scroll box on the ln-data-table dynamic
|
|
11
|
+
// path (see components/_filter.scss). This mixin styles ONLY the inner
|
|
12
|
+
// content: scrollable options box, search input, and the options list
|
|
13
|
+
// (pills via check-list).
|
|
14
|
+
//
|
|
15
|
+
// NOTE: there is intentionally NO clear-button block. The redundant
|
|
16
|
+
// "Clear Filter" button was removed library-wide — the `data-ln-filter-reset`
|
|
17
|
+
// "All" sentinel checkbox already performs the reset. Do not re-introduce a
|
|
18
|
+
// `[data-ln-filter-clear]` selector here.
|
|
19
|
+
//
|
|
20
|
+
// Tunables (consumer-overridable via --var with sane defaults):
|
|
21
|
+
// --filter-dropdown-max-height (default 20rem)
|
|
22
|
+
// --filter-dropdown-overflow-y (default auto)
|
|
23
|
+
// --filter-dropdown-text (default var(--text-xs)) ← sm density
|
|
24
|
+
// --filter-dropdown-item-pad-y (default var(--size-3xs))
|
|
25
|
+
//
|
|
26
|
+
// Usage:
|
|
27
|
+
// .column-filter-dropdown { @include filter-dropdown; }
|
|
28
|
+
|
|
29
|
+
@mixin filter-dropdown {
|
|
30
|
+
max-height: var(--filter-dropdown-max-height, 20rem);
|
|
31
|
+
overflow-y: var(--filter-dropdown-overflow-y, auto);
|
|
32
|
+
text-transform: none; // reset inherited header uppercase
|
|
33
|
+
|
|
34
|
+
[data-ln-filter-search] {
|
|
35
|
+
@include form-input;
|
|
36
|
+
font-size: var(--filter-dropdown-text, var(--text-xs));
|
|
37
|
+
--padding-y: var(--size-xs);
|
|
38
|
+
--padding-x: var(--size-sm);
|
|
39
|
+
width: 100%;
|
|
40
|
+
margin-bottom: var(--size-sm);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
[data-ln-filter-options] {
|
|
44
|
+
@include check-list;
|
|
45
|
+
font-size: var(--filter-dropdown-text, var(--text-xs));
|
|
46
|
+
|
|
47
|
+
li { padding: var(--filter-dropdown-item-pad-y, var(--size-3xs)) 0; }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
@forward 'nav';
|
|
26
26
|
@forward 'btn';
|
|
27
27
|
@forward 'form';
|
|
28
|
+
@forward 'filter';
|
|
28
29
|
@forward 'modal';
|
|
29
30
|
@forward 'breadcrumbs';
|
|
30
31
|
@forward 'loader';
|
|
@@ -53,6 +54,6 @@
|
|
|
53
54
|
@forward 'toast';
|
|
54
55
|
@forward 'upload';
|
|
55
56
|
@forward 'translations';
|
|
56
|
-
@forward 'data-table';
|
|
57
57
|
@forward 'ln-table';
|
|
58
58
|
@forward 'ajax';
|
|
59
|
+
@forward 'diagnostics';
|
|
@@ -21,8 +21,14 @@
|
|
|
21
21
|
// [data-ln-table] { @include ln-table; }
|
|
22
22
|
// .ln-table__toolbar { @include ln-table-toolbar; }
|
|
23
23
|
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/scss/ln-ashlar.scss
CHANGED
|
@@ -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
|
|