@livenetworks/ashlar 1.4.1 → 1.6.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 (219) hide show
  1. package/README.md +189 -177
  2. package/js/COMPONENTS.md +55 -49
  3. package/js/index.js +22 -5
  4. package/js/ln-accordion/README.md +186 -137
  5. package/js/ln-accordion/ln-accordion.js +1 -1
  6. package/js/ln-ajax/README.md +73 -4
  7. package/js/ln-ajax/ln-ajax.js +1 -1
  8. package/js/ln-ajax/src/ln-ajax.js +13 -13
  9. package/js/ln-api-connector/README.md +261 -150
  10. package/js/ln-api-connector/ln-api-connector.js +1 -1
  11. package/js/ln-api-connector/src/ln-api-connector.js +368 -265
  12. package/js/ln-api-queue/README.md +219 -0
  13. package/js/ln-api-queue/ln-api-queue.js +1 -0
  14. package/js/ln-api-queue/src/ln-api-queue.js +320 -0
  15. package/js/ln-api-queue/src/queue-storage.js +455 -0
  16. package/js/ln-autoresize/README.md +47 -0
  17. package/js/ln-autoresize/ln-autoresize.js +1 -1
  18. package/js/ln-autosave/README.md +33 -1
  19. package/js/ln-autosave/ln-autosave.js +1 -1
  20. package/js/ln-autosave/src/ln-autosave.js +4 -4
  21. package/js/ln-circular-progress/README.md +27 -13
  22. package/js/ln-circular-progress/ln-circular-progress.js +1 -1
  23. package/js/ln-circular-progress/src/ln-circular-progress.js +18 -5
  24. package/js/ln-confirm/README.md +162 -86
  25. package/js/ln-confirm/_ln-confirm.scss +11 -0
  26. package/js/ln-confirm/ln-confirm.js +1 -1
  27. package/js/ln-confirm/src/ln-confirm.js +77 -41
  28. package/js/ln-core/README.md +896 -0
  29. package/js/ln-core/hash.js +92 -0
  30. package/js/ln-core/helpers.js +337 -8
  31. package/js/ln-core/index.js +4 -2
  32. package/js/ln-core/persist.js +29 -3
  33. package/js/ln-core/positioning.js +1 -35
  34. package/js/ln-core/window-cache.js +213 -0
  35. package/js/ln-couchdb-connector/README.md +177 -156
  36. package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
  37. package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +398 -348
  38. package/js/ln-data-coordinator/README.md +536 -165
  39. package/js/ln-data-coordinator/ln-data-coordinator.js +1 -1
  40. package/js/ln-data-coordinator/src/data-read-policy.js +18 -0
  41. package/js/ln-data-coordinator/src/ln-data-coordinator.js +1042 -249
  42. package/js/ln-data-coordinator/src/mutation-receipts.js +46 -0
  43. package/js/ln-data-store/README.md +241 -94
  44. package/js/ln-data-store/ln-data-store.js +1 -1
  45. package/js/ln-data-store/src/ln-data-store.js +694 -699
  46. package/js/ln-date/README.md +39 -1
  47. package/js/ln-date/ln-date-dev.scss +14 -0
  48. package/js/ln-date/ln-date.js +1 -1
  49. package/js/ln-date/src/ln-date.js +236 -178
  50. package/js/ln-debug/README.md +58 -0
  51. package/js/ln-debug/ln-debug.js +1 -0
  52. package/js/ln-debug/src/ln-debug.js +19 -0
  53. package/js/ln-dropdown/README.md +31 -8
  54. package/js/ln-dropdown/ln-dropdown.js +1 -1
  55. package/js/ln-dropdown/src/ln-dropdown.js +30 -13
  56. package/js/ln-editor/README.md +274 -0
  57. package/js/ln-editor/ln-editor.js +1 -0
  58. package/js/ln-editor/ln-editor.scss +43 -0
  59. package/js/ln-editor/src/ln-editor.js +619 -0
  60. package/js/ln-external-links/README.md +44 -5
  61. package/js/ln-external-links/ln-external-links.js +1 -1
  62. package/js/ln-fill/README.md +199 -0
  63. package/js/ln-fill/ln-fill.js +1 -0
  64. package/js/ln-fill/src/ln-fill.js +87 -0
  65. package/js/ln-filter/README.md +189 -99
  66. package/js/ln-filter/ln-filter-dev.scss +8 -0
  67. package/js/ln-filter/ln-filter.js +1 -1
  68. package/js/ln-filter/src/ln-filter.js +22 -7
  69. package/js/ln-form/README.md +212 -49
  70. package/js/ln-form/ln-form.js +1 -1
  71. package/js/ln-form/src/ln-form.js +99 -199
  72. package/js/ln-http/README.md +125 -89
  73. package/js/ln-http/ln-http.js +1 -1
  74. package/js/ln-http/src/ln-http.js +32 -13
  75. package/js/ln-icon/README.md +130 -0
  76. package/js/ln-icon/ln-icon.js +1 -0
  77. package/js/{ln-icons/src/ln-icons.js → ln-icon/src/ln-icon.js} +20 -15
  78. package/js/ln-link/README.md +47 -9
  79. package/js/ln-link/ln-link.js +1 -1
  80. package/js/ln-link/src/ln-link.js +22 -6
  81. package/js/ln-list/README.md +129 -0
  82. package/js/ln-list/ln-list.js +1 -0
  83. package/js/ln-list/ln-list.scss +28 -0
  84. package/js/ln-list/src/ln-list.js +1216 -0
  85. package/js/ln-modal/README.md +185 -154
  86. package/js/ln-modal/ln-modal-dev.scss +13 -0
  87. package/js/ln-modal/ln-modal.js +1 -1
  88. package/js/ln-modal/ln-modal.scss +28 -4
  89. package/js/ln-modal/src/ln-modal.js +137 -201
  90. package/js/ln-modal-coordinator/README.md +99 -0
  91. package/js/ln-modal-coordinator/ln-modal-coordinator-dev.scss +8 -0
  92. package/js/ln-modal-coordinator/ln-modal-coordinator.js +1 -0
  93. package/js/ln-modal-coordinator/src/ln-modal-coordinator.js +345 -0
  94. package/js/ln-nav/README.md +46 -3
  95. package/js/ln-nav/ln-nav.js +1 -1
  96. package/js/ln-nav/src/ln-nav.js +92 -127
  97. package/js/ln-number/README.md +28 -1
  98. package/js/ln-number/ln-number-dev.scss +13 -0
  99. package/js/ln-number/ln-number.js +1 -1
  100. package/js/ln-number/src/ln-number.js +443 -350
  101. package/js/ln-options/README.md +66 -0
  102. package/js/ln-options/ln-options.js +1 -0
  103. package/js/ln-options/src/ln-options.js +74 -0
  104. package/js/ln-popover/README.md +40 -5
  105. package/js/ln-popover/ln-popover.js +1 -1
  106. package/js/ln-popover/src/ln-popover.js +38 -11
  107. package/js/ln-progress/README.md +413 -442
  108. package/js/ln-progress/ln-progress.js +1 -1
  109. package/js/ln-progress/src/ln-progress.js +98 -150
  110. package/js/ln-router/README.md +432 -0
  111. package/js/ln-router/ln-router.js +1 -0
  112. package/js/ln-router/src/ln-router.js +539 -0
  113. package/js/ln-search/README.md +158 -83
  114. package/js/ln-search/ln-search-dev.scss +8 -0
  115. package/js/ln-search/ln-search.js +1 -1
  116. package/js/ln-search/src/ln-search.js +12 -9
  117. package/js/ln-slug/README.md +110 -0
  118. package/js/ln-slug/ln-slug.js +1 -0
  119. package/js/ln-slug/src/ln-slug.js +74 -0
  120. package/js/ln-sort/README.md +170 -0
  121. package/js/ln-sort/ln-sort-dev.scss +8 -0
  122. package/js/ln-sort/ln-sort.js +1 -0
  123. package/js/ln-sort/src/ln-sort.js +178 -0
  124. package/js/ln-sortable/README.md +133 -95
  125. package/js/ln-sortable/ln-sortable.js +1 -1
  126. package/js/ln-stat/README.md +55 -0
  127. package/js/ln-stat/ln-stat.js +1 -0
  128. package/js/ln-stat/src/ln-stat.js +55 -0
  129. package/js/ln-table/README.md +285 -101
  130. package/js/ln-table/ln-table-dev.scss +17 -0
  131. package/js/ln-table/ln-table.js +1 -1
  132. package/js/ln-table/ln-table.scss +1 -1
  133. package/js/ln-table/src/ln-table.js +1493 -473
  134. package/js/ln-table-coordinator/README.md +73 -0
  135. package/js/ln-table-coordinator/ln-table-coordinator.js +1 -0
  136. package/js/ln-table-coordinator/src/ln-table-coordinator.js +160 -0
  137. package/js/ln-tabs/README.md +221 -137
  138. package/js/ln-tabs/ln-tabs-dev.scss +18 -0
  139. package/js/ln-tabs/ln-tabs.js +1 -1
  140. package/js/ln-tabs/src/ln-tabs.js +41 -24
  141. package/js/ln-time/README.md +14 -0
  142. package/js/ln-time/ln-time.js +1 -1
  143. package/js/ln-time/src/ln-time.js +223 -192
  144. package/js/ln-toast/README.md +178 -122
  145. package/js/ln-toast/ln-toast.js +1 -15
  146. package/js/ln-toast/src/ln-toast.js +232 -210
  147. package/js/ln-toast/template.html +16 -12
  148. package/js/ln-toggle/README.md +61 -2
  149. package/js/ln-toggle/ln-toggle-dev.scss +13 -0
  150. package/js/ln-toggle/ln-toggle.js +1 -1
  151. package/js/ln-toggle/src/ln-toggle.js +56 -43
  152. package/js/ln-tooltip/README.md +95 -58
  153. package/js/ln-tooltip/ln-tooltip-dev.scss +9 -0
  154. package/js/ln-tooltip/ln-tooltip.js +1 -1
  155. package/js/ln-tooltip/src/ln-tooltip.js +45 -6
  156. package/js/ln-translations/README.md +118 -96
  157. package/js/ln-translations/ln-translations.js +1 -1
  158. package/js/ln-translations/src/ln-translations.js +10 -5
  159. package/js/ln-upload/README.md +85 -10
  160. package/js/ln-upload/ln-upload.js +1 -1
  161. package/js/ln-upload/src/ln-upload.js +26 -10
  162. package/js/ln-validate/README.md +72 -0
  163. package/js/ln-validate/ln-validate-dev.scss +15 -0
  164. package/js/ln-validate/ln-validate.js +1 -1
  165. package/js/ln-validate/src/ln-validate.js +64 -0
  166. package/package.json +61 -55
  167. package/scss/base/_global.scss +2 -2
  168. package/scss/base/_typography.scss +1 -2
  169. package/scss/components/_date.scss +1 -0
  170. package/scss/components/_dropdown.scss +0 -5
  171. package/scss/components/_editor.scss +5 -0
  172. package/scss/components/_filter.scss +26 -0
  173. package/scss/components/_form.scss +7 -0
  174. package/scss/components/_ln-table.scss +14 -5
  175. package/scss/components/_sort.scss +10 -0
  176. package/scss/components/_table.scss +68 -1
  177. package/scss/components/_toast.scss +68 -41
  178. package/scss/components/_toggle.scss +16 -0
  179. package/scss/config/_density.scss +205 -117
  180. package/scss/config/_icons.scss +10 -9
  181. package/scss/config/_tokens.scss +69 -47
  182. package/scss/config/mixins/_accordion.scss +2 -7
  183. package/scss/config/mixins/_alert.scss +4 -4
  184. package/scss/config/mixins/_breadcrumbs.scss +2 -2
  185. package/scss/config/mixins/_btn.scss +1 -1
  186. package/scss/config/mixins/_card.scss +89 -14
  187. package/scss/config/mixins/_collapsible.scss +3 -0
  188. package/scss/config/mixins/_colors.scss +1 -1
  189. package/scss/config/mixins/_diagnostics.scss +48 -0
  190. package/scss/config/mixins/_display.scss +1 -0
  191. package/scss/config/mixins/_dropdown.scss +3 -5
  192. package/scss/config/mixins/_editor.scss +124 -0
  193. package/scss/config/mixins/_empty-state.scss +7 -0
  194. package/scss/config/mixins/_filter.scss +47 -0
  195. package/scss/config/mixins/_footer.scss +1 -2
  196. package/scss/config/mixins/_form.scss +207 -21
  197. package/scss/config/mixins/_index.scss +4 -1
  198. package/scss/config/mixins/_ln-table.scss +51 -93
  199. package/scss/config/mixins/_modal.scss +149 -66
  200. package/scss/config/mixins/_nav.scss +3 -4
  201. package/scss/config/mixins/_popover.scss +39 -0
  202. package/scss/config/mixins/_sidebar.scss +1 -1
  203. package/scss/config/mixins/_sort.scss +42 -0
  204. package/scss/config/mixins/_table.scss +232 -38
  205. package/scss/config/mixins/_toast.scss +179 -152
  206. package/scss/config/mixins/_tooltip.scss +17 -7
  207. package/scss/config/mixins/_typography.scss +4 -2
  208. package/scss/ln-ashlar-dev.scss +39 -0
  209. package/scss/ln-ashlar.scss +8 -2
  210. package/js/ln-data-table/README.md +0 -110
  211. package/js/ln-data-table/ln-data-table.js +0 -1
  212. package/js/ln-data-table/ln-data-table.scss +0 -10
  213. package/js/ln-data-table/src/ln-data-table.js +0 -1103
  214. package/js/ln-icons/README.md +0 -88
  215. package/js/ln-icons/ln-icons.js +0 -1
  216. package/js/ln-table/ln-table-sort.js +0 -1
  217. package/js/ln-table/src/ln-table-sort.js +0 -168
  218. package/scss/components/_data-table.scss +0 -83
  219. package/scss/config/mixins/_data-table.scss +0 -346
@@ -21,15 +21,29 @@
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;
29
35
  --radius: var(--radius-lg);
30
36
  border-radius: var(--radius);
31
37
  background: var(--color-bg);
32
- overflow: clip;
38
+ // `clip` not `auto` — no scroll container, so the sticky thead/footer
39
+ // bind to the outer .app-main > section scroll surface (same doctrine
40
+ // as @mixin section-card).
41
+ overflow-x: clip;
42
+ max-width: 100%;
43
+
44
+ colgroup {
45
+ display: none !important;
46
+ }
33
47
 
34
48
  // Table overrides inside component
35
49
  table {
@@ -38,6 +52,8 @@
38
52
  border-spacing: 0;
39
53
  border-radius: 0;
40
54
  box-shadow: none;
55
+ table-layout: auto !important;
56
+ min-width: var(--table-min-width, 768px);
41
57
  }
42
58
 
43
59
  // Whole thead sticky as one unit (toolbar row + column-headers row).
@@ -64,6 +80,8 @@
64
80
 
65
81
  @mixin ln-table-toolbar {
66
82
  @include panel-header;
83
+ justify-content: flex-start;
84
+ gap: var(--size-md);
67
85
  position: sticky;
68
86
  top: 0;
69
87
  z-index: var(--z-sticky);
@@ -97,11 +115,24 @@
97
115
 
98
116
  > div {
99
117
  @include panel-header;
118
+ justify-content: flex-start;
119
+ gap: var(--size-md);
100
120
  }
101
121
  }
102
122
 
103
123
  @mixin ln-table-toolbar-search {
104
- width: 10rem;
124
+ --padding-y: var(--size-2xs); // lowers panel-header min-height floor for this label
125
+ width: auto; // hug the 10rem input; cancel .search's 20rem label cap inside toolbars
126
+
127
+ > .ln-icon {
128
+ padding-top: var(--size-2xs); // tighten leading icon vertical padding (was var(--size-xs))
129
+ padding-bottom: var(--size-2xs); // horizontal padding (size-sm) inherited from form-field-group
130
+ }
131
+
132
+ > input {
133
+ --padding-y: var(--size-2xs); // tighten vertical padding on the input itself
134
+ width: 10rem; // preserve existing width cap
135
+ }
105
136
  }
106
137
 
107
138
  // ─── Count badge ──────────────────────────────────────────
@@ -126,95 +157,6 @@
126
157
  }
127
158
  }
128
159
 
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
160
 
219
161
  // ─── Footer ───────────────────────────────────────────────
220
162
 
@@ -232,6 +174,9 @@
232
174
  @include text-sm;
233
175
  --color-fg: var(--fg-muted);
234
176
  color: var(--color-fg);
177
+ position: sticky;
178
+ bottom: 0;
179
+ z-index: var(--z-sticky);
235
180
 
236
181
  strong {
237
182
  color: var(--color-fg);
@@ -418,3 +363,16 @@
418
363
  background: transparent !important;
419
364
  }
420
365
  }
366
+
367
+ // Windowed-virtualization placeholder row — static fill, row-height chrome
368
+ // only. No shimmer (window-cache.md §4); height stays JS-owned via inline
369
+ // px, spacer geometry depends on the exact value so nothing here may touch it.
370
+ @mixin ln-table-placeholder-row {
371
+ td {
372
+ padding: 0 !important;
373
+ border: none !important;
374
+ background: var(--bg-sunken);
375
+ }
376
+ }
377
+
378
+
@@ -1,66 +1,149 @@
1
- @use 'display' as *;
2
- @use 'spacing' as *;
3
- @use 'position' as *;
4
- @use 'motion' as *;
5
- @use 'card' as *;
6
-
7
- // Modal size mixins
8
- // Usage: #my-modal > form { @include modal-lg; }
9
-
10
- @mixin modal-sm { max-width: 28rem; }
11
- @mixin modal-md { max-width: 32rem; }
12
- @mixin modal-lg { max-width: 42rem; }
13
- @mixin modal-xl { max-width: 48rem; }
14
-
15
- // ─── Modal layout ──────────────────────────────────────
16
-
17
- // Overlay backdrop — covers viewport, darkened + blurred.
18
- // Default: hidden (display:none). JS sets [data-ln-modal="open"]
19
- // to show — that state rule lives in the co-located JS SCSS.
20
- @mixin modal-overlay {
21
- display: none;
22
- @include fixed;
23
- @include inset-0;
24
- background-color: var(--color-scrim);
25
- backdrop-filter: blur(4px);
26
- -webkit-backdrop-filter: blur(4px);
27
- z-index: var(--z-modal);
28
- @include items-center;
29
- @include justify-center;
30
- opacity: 0;
31
- @include motion-safe {
32
- transition: opacity var(--transition);
33
- }
34
- }
35
-
36
- // Content panel — the <form> inside .ln-modal.
37
- // Structurally IS a section-card: reuse its chrome and its nested
38
- // > header / > main / > footer recipes. Add only the modal-specific
39
- // behaviour on top: fixed size envelope, scrollable body, sticky
40
- // footer, slide-in animation, deeper shadow.
41
- @mixin modal-panel {
42
- @include section-card;
43
- margin-bottom: 0;
44
- // Explicit role-scale read — modals use max elevation (deeper than
45
- // the --shadow-floating default used by floating panels).
46
- box-shadow: var(--shadow-xl);
47
- display: grid;
48
- grid-template-rows: auto 1fr auto;
49
- max-width: 600px;
50
- width: 90%;
51
- max-height: 90vh;
52
- @include motion-safe {
53
- animation: ln-modal-slideIn var(--transition-slow);
54
- }
55
-
56
- > main {
57
- min-height: 0;
58
- overflow-y: auto;
59
- overflow-x: hidden;
60
- }
61
-
62
- > footer {
63
- position: sticky;
64
- bottom: 0;
65
- }
66
- }
1
+ @use 'display' as *;
2
+ @use 'spacing' as *;
3
+ @use 'position' as *;
4
+ @use 'motion' as *;
5
+ @use 'card' as *;
6
+ @use 'typography' as *;
7
+
8
+ // Modal size mixins
9
+ // Usage: #my-modal > form { @include modal-lg; }
10
+
11
+ @mixin modal-sm { max-width: 28rem; }
12
+ @mixin modal-md { max-width: 32rem; }
13
+ @mixin modal-lg { max-width: 42rem; }
14
+ @mixin modal-xl { max-width: 48rem; }
15
+
16
+ // ─── Modal layout ──────────────────────────────────────
17
+
18
+ // Overlay backdrop — covers viewport, darkened + blurred.
19
+ // Default: hidden (display:none). JS sets [data-ln-modal="open"]
20
+ // to show — that state rule lives in the co-located JS SCSS.
21
+ @mixin modal-overlay {
22
+ display: none;
23
+ @include fixed;
24
+ @include inset-0;
25
+ z-index: var(--z-modal);
26
+ @include items-center;
27
+ @include justify-center;
28
+
29
+ &::backdrop {
30
+ background-color: var(--color-scrim);
31
+ backdrop-filter: blur(4px);
32
+ -webkit-backdrop-filter: blur(4px);
33
+ opacity: 1;
34
+
35
+ @include motion-safe {
36
+ transition: opacity var(--transition);
37
+ transition-behavior: allow-discrete;
38
+ }
39
+
40
+ @starting-style {
41
+ opacity: 0;
42
+ }
43
+ }
44
+ }
45
+
46
+ // ─── Modal header ──────────────────────────────────────
47
+ // Rich modal header recipe: optional tinted icon square + title/subtitle
48
+ // hgroup + close-button reset. Applied by modal-panel to its > header.
49
+ @mixin modal-header {
50
+ // Modal-specific: white bg overrides section-card's gray panel-header.
51
+ // Section-cards and cards are unaffected (this is modal-panel scope only).
52
+ --color-bg: var(--bg-base);
53
+ gap: var(--size-sm-up);
54
+
55
+ :is(h1, h2, h3, h4, h5, h6) {
56
+ @include typography(heading-sm);
57
+ font-weight: var(--font-semibold);
58
+ }
59
+
60
+ button[data-ln-modal-close] {
61
+ --btn-bg: transparent;
62
+ --btn-border: transparent;
63
+ }
64
+
65
+ // Optional icon square — tinted rounded square with primary-colored icon.
66
+ // Usage: <span class="icon"><svg class="ln-icon" aria-hidden="true">…</svg></span>
67
+ .icon {
68
+ flex-shrink: 0;
69
+ display: inline-flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ width: 2rem;
73
+ height: 2rem;
74
+ border-radius: var(--radius);
75
+ background: var(--color-accent-tint);
76
+
77
+ .ln-icon {
78
+ color: var(--color-accent);
79
+ }
80
+ }
81
+
82
+ // Optional title+subtitle group — <hgroup><h3>…</h3><p>…</p></hgroup>.
83
+ hgroup {
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: var(--size-xs);
87
+ flex: 1;
88
+ min-width: 0;
89
+ margin: 0;
90
+
91
+ p {
92
+ margin: 0;
93
+ @include typography(caption);
94
+ color: var(--fg-muted);
95
+ }
96
+ }
97
+ }
98
+
99
+ // Content panel — the <form> inside .ln-modal.
100
+ // Structurally IS a section-card: reuse its chrome and its nested
101
+ // > header / > main / > footer recipes. Add only the modal-specific
102
+ // behaviour on top: fixed size envelope, scrollable body, sticky
103
+ // footer, slide-in animation, deeper shadow.
104
+ @mixin modal-panel {
105
+ @include section-card;
106
+ margin-bottom: 0;
107
+ // Explicit role-scale read — modals use max elevation (deeper than
108
+ // the --shadow-floating default used by floating panels).
109
+ box-shadow: var(--shadow-xl);
110
+ display: grid;
111
+ grid-template-rows: auto 1fr auto;
112
+ max-width: 600px;
113
+ width: 90%;
114
+ max-height: 90vh;
115
+ @include motion-safe {
116
+ animation: ln-modal-slideIn var(--transition-slow);
117
+ }
118
+
119
+ > main {
120
+ min-height: 0;
121
+ overflow-y: auto;
122
+ overflow-x: hidden;
123
+ }
124
+
125
+ > footer {
126
+ position: sticky;
127
+ bottom: 0;
128
+
129
+ // Destructive footer split: add class="delete-action" to the first
130
+ // button to push it left. The footer gap still applies between all buttons.
131
+ // Color: --color-primary cascades through @mixin btn (same pattern as
132
+ // .delete-action documented in _global.scss).
133
+ .delete-action {
134
+ margin-inline-end: auto;
135
+ --btn-bg: hsl(var(--color-error) / 0.08);
136
+ --btn-fg: hsl(var(--color-error));
137
+ --btn-border: hsl(var(--color-error) / 0.4);
138
+ --btn-bg-hover: hsl(var(--color-error) / 0.16);
139
+ --btn-fg-hover: hsl(var(--color-error));
140
+ --btn-border-hover: hsl(var(--color-error));
141
+ backdrop-filter: blur(4px);
142
+ -webkit-backdrop-filter: blur(4px);
143
+ }
144
+ }
145
+
146
+ > header {
147
+ @include modal-header;
148
+ }
149
+ }
@@ -39,8 +39,7 @@
39
39
  @include items-center;
40
40
  gap: var(--gap);
41
41
  padding-block: var(--padding-y);
42
- font-size: var(--text-body-sm);
43
- line-height: var(--lh-body-sm);
42
+ @include typography(body-sm);
44
43
  color: var(--color-fg);
45
44
  text-decoration: none;
46
45
  // Always-present 1px border slot. Color defaults to transparent
@@ -97,7 +96,7 @@
97
96
  }
98
97
 
99
98
  &.active {
100
- color: var(--color-accent);
99
+ color: var(--color-accent-hover);
101
100
  border-color: var(--nav-link-border-color-active, transparent);
102
101
  @include font-semibold;
103
102
  }
@@ -135,7 +134,7 @@
135
134
  &.active {
136
135
  // accent wash vocabulary (CLAUDE.md L532) — was reaching
137
136
  // through to --color-primary-lighter scale.
138
- background-color: var(--color-accent-tint);
137
+ background-color: var(--color-accent-tint-strong);
139
138
  }
140
139
  }
141
140
  }
@@ -2,11 +2,15 @@
2
2
  @use 'borders' as *;
3
3
  @use 'motion' as *;
4
4
  @use 'card' as *;
5
+ @use 'form' as *;
6
+ @use 'focus' as *;
5
7
 
6
8
  @mixin popover {
7
9
  @include floating-panel;
8
10
  display: none;
9
11
  position: fixed;
12
+ inset: auto;
13
+ margin: 0;
10
14
  min-width: 14rem;
11
15
  max-width: 24rem;
12
16
  padding: var(--padding-y) var(--padding-x);
@@ -22,4 +26,39 @@
22
26
  &:focus {
23
27
  outline: none;
24
28
  }
29
+
30
+ // Filter popover content: optional search + checkbox option list
31
+ > input[type="search"] {
32
+ width: 100%;
33
+ margin-bottom: var(--size-xs);
34
+ box-sizing: border-box;
35
+ }
36
+
37
+ > ul {
38
+ list-style: none;
39
+ padding: 0;
40
+ margin: 0;
41
+ max-height: var(--filter-options-max-height, 15rem);
42
+ overflow-y: auto;
43
+
44
+ // Nav-link treatment for filter options (accent-tint wash on checked).
45
+ // Visually-hidden input (below) keeps the checkbox keyboard-focusable.
46
+ @include check-list-nav;
47
+
48
+ > li > label > input {
49
+ // Override pill's display:none — keep in focus order for keyboard nav
50
+ display: revert;
51
+ position: absolute;
52
+ width: 1px;
53
+ height: 1px;
54
+ overflow: hidden;
55
+ clip-path: inset(50%);
56
+ white-space: nowrap;
57
+ }
58
+
59
+ // Focus ring on label when the hidden input receives keyboard focus
60
+ > li > label:has(> input:focus-visible) {
61
+ @include focus-ring;
62
+ }
63
+ }
25
64
  }
@@ -75,7 +75,7 @@
75
75
 
76
76
  // Search field drops to a full-width second row below the
77
77
  // brand triad (enabled by `flex-wrap` above).
78
- > [data-ln-search] {
78
+ > .sidebar-search {
79
79
  @include w-full;
80
80
  --margin-block: var(--size-xs);
81
81
  @include mt(var(--margin-block));
@@ -0,0 +1,42 @@
1
+ @use 'display' as *;
2
+ @use 'table' as *;
3
+
4
+ // ─── Sort control — 3-trigger cycle (none → asc → desc → none) ─────────
5
+ // Only one <button data-ln-sort-dir> is visible at a time; the circular
6
+ // order lives HERE in CSS, not in JS (js/ln-sort/README.md "No cycle
7
+ // logic in JS"). [data-ln-sort-state] on the root decides which trigger
8
+ // shows — the visible button is always the NEXT action, but the icon it
9
+ // carries reports the CURRENT state, not that action (icon-per-button
10
+ // mapping: js/ln-sort/README.md "Icon convention").
11
+
12
+ @mixin sort {
13
+ @include inline-flex;
14
+ list-style: none !important;
15
+ list-style-type: none !important;
16
+ margin: 0;
17
+ padding: 0;
18
+ vertical-align: middle;
19
+
20
+ li {
21
+ list-style: none !important;
22
+ list-style-type: none !important;
23
+ }
24
+
25
+ button {
26
+ @include table-header-btn-base;
27
+ display: none;
28
+ }
29
+
30
+ &[data-ln-sort-state="none"] [data-ln-sort-dir="asc"],
31
+ &[data-ln-sort-state="asc"] [data-ln-sort-dir="desc"],
32
+ &[data-ln-sort-state="desc"] [data-ln-sort-dir="none"] {
33
+ display: inline-flex;
34
+ }
35
+ }
36
+
37
+ @mixin sort-active {
38
+ [data-ln-sort-dir] {
39
+ opacity: 1;
40
+ color: var(--color-accent);
41
+ }
42
+ }