@livenetworks/ashlar 1.5.1 → 1.6.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.
Files changed (217) 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 +165 -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 +82 -42
  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 +92 -2
  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 -56
  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 -25
  173. package/scss/components/_form.scss +7 -0
  174. package/scss/components/_ln-table.scss +13 -3
  175. package/scss/components/_sort.scss +10 -0
  176. package/scss/components/_table.scss +36 -23
  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 +11 -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 +3 -5
  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 +2 -0
  198. package/scss/config/mixins/_ln-table.scss +41 -2
  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 +85 -60
  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 +27 -9
  209. package/scss/ln-ashlar.scss +7 -1
  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 -1087
  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 -156
@@ -20,7 +20,6 @@
20
20
  // Tunables (consumer-overridable via --var with sane defaults):
21
21
  // --filter-dropdown-max-height (default 20rem)
22
22
  // --filter-dropdown-overflow-y (default auto)
23
- // --filter-dropdown-text (default var(--text-xs)) ← sm density
24
23
  // --filter-dropdown-item-pad-y (default var(--size-3xs))
25
24
  //
26
25
  // Usage:
@@ -34,15 +33,14 @@
34
33
  [data-ln-filter-search] {
35
34
  @include form-input;
36
35
  font-size: var(--filter-dropdown-text, var(--text-xs));
37
- --padding-y: var(--size-xs);
36
+ --padding-y: var(--size-2xs);
38
37
  --padding-x: var(--size-sm);
39
38
  width: 100%;
40
- margin-bottom: var(--size-sm);
39
+ margin-bottom: var(--size-xs);
41
40
  }
42
41
 
43
42
  [data-ln-filter-options] {
44
- @include check-list;
45
- font-size: var(--filter-dropdown-text, var(--text-xs));
43
+ @include check-list-nav;
46
44
 
47
45
  li { padding: var(--filter-dropdown-item-pad-y, var(--size-3xs)) 0; }
48
46
  }
@@ -25,8 +25,7 @@
25
25
  padding: var(--padding-y) var(--padding-x);
26
26
  border-block-start: var(--border-block-start, var(--border-width) solid var(--color-border));
27
27
  background: var(--color-bg);
28
- font-size: var(--text-body-sm);
29
- line-height: var(--lh-body-sm);
28
+ @include typography(body-sm);
30
29
  color: var(--color-fg);
31
30
  @include flex;
32
31
  @include items-center;
@@ -17,9 +17,8 @@
17
17
 
18
18
  @mixin form-label {
19
19
  @include block;
20
- font-size: var(--text-label-md);
21
- line-height: var(--lh-label-md);
22
- @include font-medium;
20
+ @include typography(label-sm);
21
+ @include font-semibold;
23
22
  color: var(--color-fg);
24
23
  // Structural label-to-input gap (not shell rhythm).
25
24
  --margin-block: var(--size-xs);
@@ -91,12 +90,13 @@
91
90
 
92
91
  @mixin form-input {
93
92
  @include w-full;
94
- --padding-y: var(--size-sm);
93
+ --padding-y: var(--input-padding-y, var(--size-sm));
95
94
  --padding-x: var(--size-md);
96
95
  padding: var(--padding-y) var(--padding-x);
97
96
  font-size: var(--font-size);
98
97
  line-height: var(--line-height);
99
98
  color: var(--color-fg);
99
+ --color-bg: var(--input-bg, var(--bg-recessed));
100
100
  background: var(--color-bg);
101
101
  @include border;
102
102
  border-radius: var(--radius);
@@ -211,6 +211,7 @@
211
211
  padding: var(--size-xs) var(--size-sm); // icon provides its own row spacing
212
212
  --color-fg: var(--fg-subtle);
213
213
  color: var(--color-fg);
214
+ box-sizing: content-box;
214
215
  }
215
216
 
216
217
  > input,
@@ -262,27 +263,93 @@
262
263
  @mixin form-input-icon-group {
263
264
  @include form-field-group;
264
265
 
265
- > input {
266
- // Hide the clear button while the field is empty.
267
- &:placeholder-shown ~ [data-ln-search-clear] {
268
- @include hidden;
269
- }
266
+ // Visually hide the clear button while the field is empty (visibility:hidden
267
+ // preserves layout space so the input width stays stable).
268
+ // Structural target: the only button in this group is the clear control.
269
+ > input:placeholder-shown ~ button {
270
+ @include invisible;
270
271
  }
271
272
 
272
- > [data-ln-search-clear] {
273
- @include flex-shrink-0;
274
- --padding-y: var(--size-2xs);
275
- --padding-x: var(--size-2xs);
276
- padding: var(--padding-y) var(--padding-x);
277
- @include cursor-pointer;
278
- --color-fg: var(--fg-subtle);
279
- color: var(--color-fg);
273
+ // Clear control — square hit area, circular highlight on hover only.
274
+ // Overrides the flush date/submit-button styling inherited from
275
+ // form-field-group > button (equal specificity, emitted later → wins).
276
+ > button {
277
+ align-self: center; // cancel inherited stretch
278
+ aspect-ratio: 1; // square hit area
279
+ padding: var(--size-2xs); // equal padding all sides (cancels L+R size-sm and T+B 0)
280
+ border-radius: var(--radius-full); // circular highlight (square in flat theme)
281
+ background: transparent; // transparent idle
280
282
  @include transition-colors;
281
283
 
282
- &:hover {
284
+ > .ln-icon {
285
+ --color-fg: var(--fg-subtle); // idle icon color
283
286
  color: var(--color-fg);
287
+ @include transition-colors; // icon recolor must animate (parent transition won't drive child color)
288
+ }
289
+
290
+ &:hover {
291
+ background: var(--btn-bg-hover); // circle highlight appears
292
+ > .ln-icon {
293
+ --color-fg: var(--fg-default);
294
+ }
284
295
  }
285
296
  }
297
+
298
+ // Fix C — pull the leading icon and the input text closer.
299
+ // Inherited from form-field-group at equal specificity; emitted later → wins per property.
300
+
301
+ // 1. Zero the input's OWN adjacent side padding (the user's explicit minimum).
302
+ // Mirror form-field-group's :not() structure so the override lands at equal
303
+ // specificity and replaces only the horizontal padding it set.
304
+ > input,
305
+ > select {
306
+ &:not(:first-child) {
307
+ padding-left: 0;
308
+ }
309
+ &:not(:last-child) {
310
+ padding-right: 0;
311
+ }
312
+ }
313
+
314
+ // 2. Reduce ONLY the leading icon's INNER (right) padding so the magnifier sits
315
+ // close to the text. KEEP the left padding (inherited --size-sm outer breathing
316
+ // room) and KEEP vertical padding. Set padding-right ONLY — never the `padding`
317
+ // shorthand (it would clobber left + vertical).
318
+ > .ln-icon {
319
+ padding-right: var(--size-xs); // 8px → 4px; left + vertical inherited from form-field-group
320
+ }
321
+ }
322
+
323
+ // ─── Search field — compact icon-group variant (delta-only) ───────
324
+ //
325
+ // Opt-in refinements layered on top of the icon-group chrome that the
326
+ // global auto-bind (components/_form.scss: label:has(> .ln-icon):has(>
327
+ // search/text input)) already supplies. PREREQUISITE: the consumer is an
328
+ // icon + search-input <label> so the auto-bind provides the base shell;
329
+ // on a plain label this emits only the tweaks below, no chrome.
330
+ //
331
+ // Usage (project/demo SCSS):
332
+ // label.search { @include search; }
333
+
334
+ @mixin search {
335
+ width: min(100%, 20rem); // intrinsic capped field width
336
+ --color-bg: var(--bg-recessed); // recessed fill so the white clear-hover reads
337
+ --btn-bg-hover: var(--bg-base); // clear button highlights white on hover (token rebind, no specificity override)
338
+
339
+ // Density-immune: pin to the dense base scale so the field is one size
340
+ // everywhere, regardless of ambient [data-density]. Mirror the values
341
+ // in config/_density.scss — keep in sync.
342
+ --size-xs: 0.125rem;
343
+ --size-sm: 0.375rem;
344
+ --font-size: 0.875rem;
345
+ --line-height: 1.5;
346
+
347
+ // Compact field — smaller vertical padding. form-input self-declares
348
+ // --padding-y on the input, so override it on the input directly.
349
+ > input { --padding-y: var(--size-xs); }
350
+
351
+ // Compact icons via the --icon-size primitive (no ln-icon--sm class in markup).
352
+ .ln-icon { --icon-size: 1rem; }
286
353
  }
287
354
 
288
355
  // ─── Checkbox & Radio ───────────────────────────────────────────
@@ -349,7 +416,7 @@
349
416
  @include flex;
350
417
  @include items-center;
351
418
  gap: var(--gap);
352
- --padding-y: var(--size-sm);
419
+ --padding-y: var(--input-padding-y, var(--size-sm));
353
420
  --padding-x: var(--size-md);
354
421
  padding: var(--padding-y) var(--padding-x);
355
422
  @include cursor-pointer;
@@ -516,6 +583,71 @@
516
583
  }
517
584
  }
518
585
 
586
+ // Segmented track control pills
587
+ @mixin pills-segmented {
588
+ @include inline-flex;
589
+ @include items-center;
590
+ list-style: none;
591
+ padding: 2px;
592
+ margin: 0;
593
+ background-color: var(--bg-recessed);
594
+ border-radius: var(--radius);
595
+ border: var(--border-width) solid var(--color-border);
596
+ width: 100%;
597
+
598
+ > li {
599
+ flex: 1 1 0%;
600
+ margin: 0;
601
+ padding: 0;
602
+ display: block;
603
+ }
604
+
605
+ > li > label:has(> input[type="checkbox"]),
606
+ > li > label:has(> input[type="radio"]) {
607
+ @include flex;
608
+ @include items-center;
609
+ @include justify-center;
610
+ padding: var(--size-xs) var(--size-md);
611
+ border-radius: calc(var(--radius) - 2px);
612
+ @include cursor-pointer;
613
+ @include text-sm;
614
+ @include font-medium;
615
+ color: var(--fg-muted);
616
+ @include transition;
617
+ user-select: none;
618
+ margin: 0;
619
+ text-align: center;
620
+ border: none;
621
+ background: transparent;
622
+
623
+ > input {
624
+ position: absolute;
625
+ width: 1px;
626
+ height: 1px;
627
+ padding: 0;
628
+ margin: -1px;
629
+ overflow: hidden;
630
+ clip: rect(0, 0, 0, 0);
631
+ border: 0;
632
+ }
633
+
634
+ &:hover {
635
+ color: var(--color-fg);
636
+ }
637
+
638
+ &:has(> input:checked) {
639
+ background-color: var(--bg-base);
640
+ color: var(--color-fg);
641
+ @include font-semibold;
642
+ box-shadow: var(--shadow-resting);
643
+ }
644
+
645
+ &:has(> input:focus-visible) {
646
+ @include focus-ring;
647
+ }
648
+ }
649
+ }
650
+
519
651
  @mixin toggle-switch {
520
652
  appearance: none;
521
653
  position: relative;
@@ -616,6 +748,50 @@
616
748
  li label { @include pill; }
617
749
  }
618
750
 
751
+ // ─── Check list nav — filter list styled with the nav-link vocabulary ──
752
+ // Same checkbox-filter list as check-list, but the selected item uses the
753
+ // sidebar nav-link active treatment (accent-tint wash + accent text +
754
+ // semibold) instead of the solid-accent pill fill. Shared by the filter
755
+ // popover (@mixin popover) and the ln-table column-filter dropdown
756
+ // (@mixin filter-dropdown) so every filter list matches the sidebar nav
757
+ // (nav-links-rounded). Extends check-list to keep the hidden-input filled
758
+ // structure; overrides only color, weight and size. Consumers that need
759
+ // the checkbox kept keyboard-focusable (popover a11y) re-reveal the input
760
+ // themselves after including this.
761
+ @mixin check-list-nav {
762
+ @include check-list;
763
+
764
+ li label {
765
+ // Smaller — match the nav link (13px).
766
+ @include typography(body-sm);
767
+ // Muted idle text, transparent surface (nav idle).
768
+ --color-fg: var(--fg-muted);
769
+ color: var(--color-fg);
770
+ background: transparent;
771
+ border: none;
772
+ // Tighter vertical padding — match nav link height (~31px).
773
+ --padding-y: var(--size-xs-up);
774
+ padding: var(--padding-y) var(--padding-x);
775
+
776
+ // Hover (unchecked) — subtle accent wash (nav hover).
777
+ &:hover:not(:has(> input:checked)) {
778
+ background-color: color-mix(in srgb, var(--color-accent) 7%, transparent);
779
+ }
780
+
781
+ // Selected — nav active vocabulary (replaces solid-accent + white).
782
+ &:has(> input:checked) {
783
+ background-color: var(--color-accent-tint-strong);
784
+ color: var(--color-accent);
785
+ @include font-semibold;
786
+ }
787
+
788
+ // Selected + hover — keep the wash, don't darken (nav has no darken).
789
+ &:hover:has(> input:checked) {
790
+ background-color: var(--color-accent-tint-strong);
791
+ }
792
+ }
793
+ }
794
+
619
795
  // ─── Validation states ─────────────────────────────────────────
620
796
  // Status colors (error/success) stay raw — no logical --color-status
621
797
  // token in scope (see parent plan §6.2).
@@ -642,7 +818,17 @@
642
818
  // Structural error-list margin (not shell rhythm).
643
819
  --margin-block: var(--size-xs);
644
820
  margin-top: var(--margin-block);
645
- font-size: var(--text-caption);
646
- line-height: var(--lh-caption);
821
+ @include typography(caption);
647
822
  color: hsl(var(--color-error));
823
+ min-height: 1.25rem; // Reserves space to prevent form layout shifts when errors appear
824
+ list-style: none;
825
+ padding: 0;
826
+ margin-bottom: 0;
827
+
828
+ li {
829
+ margin: 0;
830
+ &.hidden {
831
+ display: none;
832
+ }
833
+ }
648
834
  }
@@ -31,6 +31,7 @@
31
31
  @forward 'loader';
32
32
  @forward 'tabs';
33
33
  @forward 'table';
34
+ @forward 'sort';
34
35
  @forward 'avatar';
35
36
  @forward 'footer';
36
37
  @forward 'sidebar';
@@ -43,6 +44,7 @@
43
44
  @forward 'timeline';
44
45
  @forward 'stat-card';
45
46
  @forward 'prose';
47
+ @forward 'editor';
46
48
  @forward 'kbd';
47
49
  @forward 'tooltip';
48
50
  @forward 'popover';
@@ -35,7 +35,15 @@
35
35
  --radius: var(--radius-lg);
36
36
  border-radius: var(--radius);
37
37
  background: var(--color-bg);
38
- 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
+ }
39
47
 
40
48
  // Table overrides inside component
41
49
  table {
@@ -44,6 +52,8 @@
44
52
  border-spacing: 0;
45
53
  border-radius: 0;
46
54
  box-shadow: none;
55
+ table-layout: auto !important;
56
+ min-width: var(--table-min-width, 768px);
47
57
  }
48
58
 
49
59
  // Whole thead sticky as one unit (toolbar row + column-headers row).
@@ -70,6 +80,8 @@
70
80
 
71
81
  @mixin ln-table-toolbar {
72
82
  @include panel-header;
83
+ justify-content: flex-start;
84
+ gap: var(--size-md);
73
85
  position: sticky;
74
86
  top: 0;
75
87
  z-index: var(--z-sticky);
@@ -103,11 +115,24 @@
103
115
 
104
116
  > div {
105
117
  @include panel-header;
118
+ justify-content: flex-start;
119
+ gap: var(--size-md);
106
120
  }
107
121
  }
108
122
 
109
123
  @mixin ln-table-toolbar-search {
110
- 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
+ }
111
136
  }
112
137
 
113
138
  // ─── Count badge ──────────────────────────────────────────
@@ -149,6 +174,9 @@
149
174
  @include text-sm;
150
175
  --color-fg: var(--fg-muted);
151
176
  color: var(--color-fg);
177
+ position: sticky;
178
+ bottom: 0;
179
+ z-index: var(--z-sticky);
152
180
 
153
181
  strong {
154
182
  color: var(--color-fg);
@@ -336,4 +364,15 @@
336
364
  }
337
365
  }
338
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
+
339
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
  }