@livenetworks/ashlar 1.3.2

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 (232) hide show
  1. package/README.md +177 -0
  2. package/js/COMPONENTS.md +1102 -0
  3. package/js/index.js +41 -0
  4. package/js/ln-accordion/README.md +137 -0
  5. package/js/ln-accordion/ln-accordion.js +1 -0
  6. package/js/ln-accordion/src/ln-accordion.js +41 -0
  7. package/js/ln-ajax/README.md +91 -0
  8. package/js/ln-ajax/ln-ajax.js +1 -0
  9. package/js/ln-ajax/src/ln-ajax.js +277 -0
  10. package/js/ln-api-connector/README.md +150 -0
  11. package/js/ln-api-connector/ln-api-connector.js +1 -0
  12. package/js/ln-api-connector/src/ln-api-connector.js +265 -0
  13. package/js/ln-autoresize/README.md +80 -0
  14. package/js/ln-autoresize/ln-autoresize.js +1 -0
  15. package/js/ln-autoresize/src/ln-autoresize.js +47 -0
  16. package/js/ln-autosave/README.md +92 -0
  17. package/js/ln-autosave/ln-autosave.js +1 -0
  18. package/js/ln-autosave/src/ln-autosave.js +147 -0
  19. package/js/ln-circular-progress/README.md +161 -0
  20. package/js/ln-circular-progress/ln-circular-progress.js +1 -0
  21. package/js/ln-circular-progress/src/ln-circular-progress.js +133 -0
  22. package/js/ln-confirm/README.md +86 -0
  23. package/js/ln-confirm/_ln-confirm.scss +13 -0
  24. package/js/ln-confirm/ln-confirm.js +1 -0
  25. package/js/ln-confirm/src/ln-confirm.js +131 -0
  26. package/js/ln-core/crypto.js +83 -0
  27. package/js/ln-core/helpers.js +411 -0
  28. package/js/ln-core/index.js +5 -0
  29. package/js/ln-core/persist.js +71 -0
  30. package/js/ln-core/positioning.js +207 -0
  31. package/js/ln-core/reactive.js +74 -0
  32. package/js/ln-couchdb-connector/README.md +156 -0
  33. package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -0
  34. package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +348 -0
  35. package/js/ln-data-coordinator/README.md +165 -0
  36. package/js/ln-data-coordinator/ln-data-coordinator.js +1 -0
  37. package/js/ln-data-coordinator/src/ln-data-coordinator.js +249 -0
  38. package/js/ln-data-store/README.md +94 -0
  39. package/js/ln-data-store/ln-data-store.js +1 -0
  40. package/js/ln-data-store/src/ln-data-store.js +699 -0
  41. package/js/ln-data-table/README.md +110 -0
  42. package/js/ln-data-table/ln-data-table.js +1 -0
  43. package/js/ln-data-table/ln-data-table.scss +10 -0
  44. package/js/ln-data-table/src/ln-data-table.js +1103 -0
  45. package/js/ln-date/README.md +151 -0
  46. package/js/ln-date/ln-date.js +1 -0
  47. package/js/ln-date/src/ln-date.js +442 -0
  48. package/js/ln-dropdown/README.md +117 -0
  49. package/js/ln-dropdown/ln-dropdown.js +1 -0
  50. package/js/ln-dropdown/ln-dropdown.scss +15 -0
  51. package/js/ln-dropdown/src/ln-dropdown.js +174 -0
  52. package/js/ln-external-links/README.md +341 -0
  53. package/js/ln-external-links/ln-external-links.js +1 -0
  54. package/js/ln-external-links/src/ln-external-links.js +116 -0
  55. package/js/ln-filter/README.md +99 -0
  56. package/js/ln-filter/ln-filter.js +1 -0
  57. package/js/ln-filter/ln-filter.scss +7 -0
  58. package/js/ln-filter/src/ln-filter.js +404 -0
  59. package/js/ln-form/README.md +101 -0
  60. package/js/ln-form/ln-form.js +1 -0
  61. package/js/ln-form/src/ln-form.js +199 -0
  62. package/js/ln-http/README.md +89 -0
  63. package/js/ln-http/ln-http.js +1 -0
  64. package/js/ln-http/src/ln-http.js +219 -0
  65. package/js/ln-icons/README.md +88 -0
  66. package/js/ln-icons/ln-icons.js +1 -0
  67. package/js/ln-icons/src/ln-icons.js +169 -0
  68. package/js/ln-link/README.md +303 -0
  69. package/js/ln-link/ln-link.js +1 -0
  70. package/js/ln-link/src/ln-link.js +196 -0
  71. package/js/ln-modal/README.md +154 -0
  72. package/js/ln-modal/ln-modal.js +1 -0
  73. package/js/ln-modal/ln-modal.scss +11 -0
  74. package/js/ln-modal/src/ln-modal.js +201 -0
  75. package/js/ln-nav/README.md +70 -0
  76. package/js/ln-nav/ln-nav.js +1 -0
  77. package/js/ln-nav/src/ln-nav.js +177 -0
  78. package/js/ln-number/README.md +122 -0
  79. package/js/ln-number/ln-number.js +1 -0
  80. package/js/ln-number/src/ln-number.js +302 -0
  81. package/js/ln-popover/README.md +127 -0
  82. package/js/ln-popover/ln-popover.js +1 -0
  83. package/js/ln-popover/src/ln-popover.js +288 -0
  84. package/js/ln-progress/README.md +442 -0
  85. package/js/ln-progress/ln-progress.js +1 -0
  86. package/js/ln-progress/src/ln-progress.js +150 -0
  87. package/js/ln-search/README.md +83 -0
  88. package/js/ln-search/ln-search.js +1 -0
  89. package/js/ln-search/ln-search.scss +7 -0
  90. package/js/ln-search/src/ln-search.js +114 -0
  91. package/js/ln-sortable/README.md +95 -0
  92. package/js/ln-sortable/ln-sortable.js +1 -0
  93. package/js/ln-sortable/src/ln-sortable.js +203 -0
  94. package/js/ln-table/README.md +101 -0
  95. package/js/ln-table/ln-table-sort.js +1 -0
  96. package/js/ln-table/ln-table.js +1 -0
  97. package/js/ln-table/ln-table.scss +11 -0
  98. package/js/ln-table/src/ln-table-sort.js +168 -0
  99. package/js/ln-table/src/ln-table.js +473 -0
  100. package/js/ln-tabs/README.md +137 -0
  101. package/js/ln-tabs/ln-tabs.js +1 -0
  102. package/js/ln-tabs/src/ln-tabs.js +171 -0
  103. package/js/ln-time/README.md +81 -0
  104. package/js/ln-time/ln-time.js +1 -0
  105. package/js/ln-time/src/ln-time.js +192 -0
  106. package/js/ln-toast/README.md +122 -0
  107. package/js/ln-toast/ln-toast.js +15 -0
  108. package/js/ln-toast/src/ln-toast.js +210 -0
  109. package/js/ln-toast/template.html +14 -0
  110. package/js/ln-toggle/README.md +137 -0
  111. package/js/ln-toggle/ln-toggle.js +1 -0
  112. package/js/ln-toggle/src/ln-toggle.js +139 -0
  113. package/js/ln-tooltip/README.md +58 -0
  114. package/js/ln-tooltip/ln-tooltip.js +1 -0
  115. package/js/ln-tooltip/ln-tooltip.scss +9 -0
  116. package/js/ln-tooltip/src/ln-tooltip.js +169 -0
  117. package/js/ln-translations/README.md +96 -0
  118. package/js/ln-translations/ln-translations.js +1 -0
  119. package/js/ln-translations/src/ln-translations.js +275 -0
  120. package/js/ln-upload/README.md +180 -0
  121. package/js/ln-upload/ln-upload.js +1 -0
  122. package/js/ln-upload/ln-upload.scss +20 -0
  123. package/js/ln-upload/src/ln-upload.js +407 -0
  124. package/js/ln-validate/README.md +108 -0
  125. package/js/ln-validate/ln-validate.js +1 -0
  126. package/js/ln-validate/src/ln-validate.js +160 -0
  127. package/package.json +55 -0
  128. package/scss/base/_global.scss +83 -0
  129. package/scss/base/_reset.scss +17 -0
  130. package/scss/base/_typography.scss +125 -0
  131. package/scss/components/_accordion.scss +34 -0
  132. package/scss/components/_ajax.scss +15 -0
  133. package/scss/components/_alert.scss +5 -0
  134. package/scss/components/_app-shell.scss +15 -0
  135. package/scss/components/_avatar.scss +6 -0
  136. package/scss/components/_breadcrumbs.scss +33 -0
  137. package/scss/components/_button.scss +20 -0
  138. package/scss/components/_card.scss +10 -0
  139. package/scss/components/_chip.scss +5 -0
  140. package/scss/components/_circular-progress.scss +29 -0
  141. package/scss/components/_confirm.scss +5 -0
  142. package/scss/components/_data-table.scss +83 -0
  143. package/scss/components/_dropdown.scss +25 -0
  144. package/scss/components/_empty-state.scss +22 -0
  145. package/scss/components/_form.scss +100 -0
  146. package/scss/components/_layout.scss +8 -0
  147. package/scss/components/_link.scss +11 -0
  148. package/scss/components/_ln-table.scss +60 -0
  149. package/scss/components/_loader.scss +6 -0
  150. package/scss/components/_modal.scss +20 -0
  151. package/scss/components/_nav.scss +9 -0
  152. package/scss/components/_page-header.scss +10 -0
  153. package/scss/components/_popover.scss +10 -0
  154. package/scss/components/_progress.scss +17 -0
  155. package/scss/components/_prose.scss +5 -0
  156. package/scss/components/_scrollbar.scss +32 -0
  157. package/scss/components/_sections.scss +12 -0
  158. package/scss/components/_sidebar.scss +5 -0
  159. package/scss/components/_stat-card.scss +5 -0
  160. package/scss/components/_status-badge.scss +4 -0
  161. package/scss/components/_stepper.scss +5 -0
  162. package/scss/components/_table.scss +19 -0
  163. package/scss/components/_tabs.scss +21 -0
  164. package/scss/components/_timeline.scss +14 -0
  165. package/scss/components/_toast.scss +41 -0
  166. package/scss/components/_toggle.scss +81 -0
  167. package/scss/components/_tooltip.scss +18 -0
  168. package/scss/components/_translations.scss +111 -0
  169. package/scss/components/_upload.scss +51 -0
  170. package/scss/config/_breakpoints.scss +72 -0
  171. package/scss/config/_density.scss +117 -0
  172. package/scss/config/_icons.scss +37 -0
  173. package/scss/config/_mixins.scss +13 -0
  174. package/scss/config/_theme.scss +216 -0
  175. package/scss/config/_tokens.scss +419 -0
  176. package/scss/config/mixins/_accordion.scss +52 -0
  177. package/scss/config/mixins/_ajax.scss +39 -0
  178. package/scss/config/mixins/_alert.scss +82 -0
  179. package/scss/config/mixins/_app-shell.scss +312 -0
  180. package/scss/config/mixins/_avatar.scss +109 -0
  181. package/scss/config/mixins/_borders.scss +36 -0
  182. package/scss/config/mixins/_breadcrumbs.scss +72 -0
  183. package/scss/config/mixins/_breakpoints.scss +62 -0
  184. package/scss/config/mixins/_btn.scss +179 -0
  185. package/scss/config/mixins/_card.scss +338 -0
  186. package/scss/config/mixins/_chip.scss +66 -0
  187. package/scss/config/mixins/_circular-progress.scss +71 -0
  188. package/scss/config/mixins/_collapsible.scss +24 -0
  189. package/scss/config/mixins/_colors.scss +46 -0
  190. package/scss/config/mixins/_confirm.scss +31 -0
  191. package/scss/config/mixins/_data-table.scss +346 -0
  192. package/scss/config/mixins/_display.scss +32 -0
  193. package/scss/config/mixins/_dropdown.scss +143 -0
  194. package/scss/config/mixins/_empty-state.scss +30 -0
  195. package/scss/config/mixins/_focus.scss +55 -0
  196. package/scss/config/mixins/_footer.scss +42 -0
  197. package/scss/config/mixins/_form.scss +601 -0
  198. package/scss/config/mixins/_index.scss +58 -0
  199. package/scss/config/mixins/_interaction.scss +15 -0
  200. package/scss/config/mixins/_kbd.scss +22 -0
  201. package/scss/config/mixins/_layout.scss +117 -0
  202. package/scss/config/mixins/_link.scss +55 -0
  203. package/scss/config/mixins/_ln-table.scss +420 -0
  204. package/scss/config/mixins/_loader.scss +26 -0
  205. package/scss/config/mixins/_modal.scss +66 -0
  206. package/scss/config/mixins/_motion.scss +19 -0
  207. package/scss/config/mixins/_nav.scss +273 -0
  208. package/scss/config/mixins/_page-header.scss +69 -0
  209. package/scss/config/mixins/_popover.scss +25 -0
  210. package/scss/config/mixins/_position.scss +32 -0
  211. package/scss/config/mixins/_progress.scss +56 -0
  212. package/scss/config/mixins/_prose.scss +127 -0
  213. package/scss/config/mixins/_shadows.scss +8 -0
  214. package/scss/config/mixins/_sidebar.scss +95 -0
  215. package/scss/config/mixins/_sizing.scss +6 -0
  216. package/scss/config/mixins/_spacing.scss +19 -0
  217. package/scss/config/mixins/_stat-card.scss +68 -0
  218. package/scss/config/mixins/_status-badge.scss +83 -0
  219. package/scss/config/mixins/_stepper.scss +78 -0
  220. package/scss/config/mixins/_table.scss +215 -0
  221. package/scss/config/mixins/_tabs.scss +64 -0
  222. package/scss/config/mixins/_timeline.scss +69 -0
  223. package/scss/config/mixins/_toast.scss +148 -0
  224. package/scss/config/mixins/_tooltip.scss +111 -0
  225. package/scss/config/mixins/_transitions.scss +10 -0
  226. package/scss/config/mixins/_translations.scss +124 -0
  227. package/scss/config/mixins/_typography.scss +57 -0
  228. package/scss/config/mixins/_upload.scss +168 -0
  229. package/scss/ln-ashlar.scss +62 -0
  230. package/scss/tabler-icons.txt +5039 -0
  231. package/scss/utilities/_animations.scss +83 -0
  232. package/scss/utilities/_utilities.scss +49 -0
@@ -0,0 +1,71 @@
1
+ @use 'display' as *;
2
+ @use 'sizing' as *;
3
+ @use 'typography' as *;
4
+ @use 'colors' as *;
5
+ @use 'position' as *;
6
+ @use 'motion' as *;
7
+
8
+ // Circular progress ring — SVG-based progress indicator with center label.
9
+ //
10
+ // Usage:
11
+ // [data-ln-circular-progress] { @include circular-progress; }
12
+ //
13
+ // Size variants: circular-progress-sm, circular-progress-lg, circular-progress-xl
14
+ // Color variants: override --color-primary on the element or a wrapper:
15
+ // [data-ln-circular-progress].success { --color-primary: var(--color-success); }
16
+
17
+ @mixin circular-progress {
18
+ @include relative;
19
+ @include inline-flex;
20
+ @include flex-center;
21
+ @include size(4rem);
22
+
23
+ svg {
24
+ @include size(100%);
25
+ }
26
+
27
+ .ln-circular-progress__track {
28
+ stroke: var(--color-border);
29
+ }
30
+
31
+ .ln-circular-progress__fill {
32
+ stroke: var(--color-accent);
33
+ @include motion-safe {
34
+ transition: stroke-dashoffset var(--transition-base);
35
+ }
36
+ }
37
+
38
+ .ln-circular-progress__label {
39
+ @include absolute;
40
+ @include text-xs;
41
+ @include font-semibold;
42
+ color: var(--color-fg);
43
+ }
44
+ }
45
+
46
+ // ─── Size variants ─────────────────────────────────────
47
+ @mixin circular-progress-sm {
48
+ @include size(2.5rem);
49
+
50
+ .ln-circular-progress__label {
51
+ font-size: 0.625rem;
52
+ line-height: 0.75rem;
53
+ }
54
+ }
55
+
56
+ @mixin circular-progress-lg {
57
+ @include size(6rem);
58
+
59
+ .ln-circular-progress__label {
60
+ @include text-base;
61
+ }
62
+ }
63
+
64
+ @mixin circular-progress-xl {
65
+ @include size(8rem);
66
+
67
+ .ln-circular-progress__label {
68
+ @include text-xl;
69
+ }
70
+ }
71
+
@@ -0,0 +1,24 @@
1
+ // Collapsible — grid-template-rows animation for expand/collapse
2
+ //
3
+ // Usage:
4
+ // .my-panel { @include collapsible; }
5
+ // .my-panel > .inner { @include collapsible-content; }
6
+
7
+ @use 'transitions' as *;
8
+ @use 'motion' as *;
9
+
10
+ @mixin collapsible {
11
+ display: grid;
12
+ grid-template-rows: 0fr;
13
+ @include motion-safe {
14
+ transition: grid-template-rows var(--transition-base);
15
+ }
16
+
17
+ &.open {
18
+ grid-template-rows: 1fr;
19
+ }
20
+ }
21
+
22
+ @mixin collapsible-content {
23
+ overflow: hidden;
24
+ }
@@ -0,0 +1,46 @@
1
+ // Colors — references --color-* tokens
2
+
3
+ @mixin text-primary { color: var(--color-fg); }
4
+ @mixin text-white { color: hsl(var(--color-white)); }
5
+ @mixin text-error { color: hsl(var(--color-error)); }
6
+ @mixin text-success { color: hsl(var(--color-success)); }
7
+ @mixin text-warning { color: hsl(var(--color-warning)); }
8
+
9
+ @mixin bg-primary { background-color: var(--color-bg); }
10
+
11
+ // Tinted surface — semi-transparent primary fill + solid primary text.
12
+ // Override --color-primary on the element/ancestor to restyle (e.g. error, success).
13
+ @mixin tinted-surface($opacity: 0.08) {
14
+ background-color: hsl(var(--color-primary) / #{$opacity});
15
+ color: hsl(var(--color-primary));
16
+ }
17
+
18
+ // Semantic color recipe — re-binds the --color-primary family AND the
19
+ // --color-accent vocabulary to a status family (success / warning /
20
+ // error / info / secondary). Used by .success / .warning / .error /
21
+ // .info / .secondary classes in _utilities.scss. Cascading both families
22
+ // ensures every downstream consumer (btn, toast-side, card-accent,
23
+ // alert, badge, etc.) picks up the status color through inherited
24
+ // custom-property values — no per-mixin redeclare required.
25
+ @mixin semantic-color($family) {
26
+ // Re-bind the base color and the theme-controlled tint companions.
27
+ // --color-primary-hover / -focus are NOT rebound — consumers derive
28
+ // hover/focus from --color-accent locally via CSS relative color
29
+ // syntax. See _btn.scss / _form.scss / _data-table.scss / _global.scss
30
+ // for the derivation pattern. --color-primary-light / -lighter ARE
31
+ // rebound because their values are theme-controlled (light themes
32
+ // use near-white tints, dark themes use subtle dark surfaces) and
33
+ // cannot be derived from the base.
34
+ --color-primary: var(--color-#{$family});
35
+ --color-primary-light: var(--color-#{$family}-light);
36
+ --color-primary-lighter: var(--color-#{$family}-lighter);
37
+
38
+ // Re-declare accent vocabulary at this scope so var() resolves
39
+ // here instead of staying frozen at :root. Without this, descendants
40
+ // reading --color-accent get :root's primary blue regardless of the
41
+ // semantic class. --color-accent-fg stays at :root (white) — text on
42
+ // any colored accent surface should stay white.
43
+ --color-accent: hsl(var(--color-primary));
44
+ --color-accent-tint: hsl(var(--color-primary-lighter));
45
+ --color-accent-tint-strong: hsl(var(--color-primary-light));
46
+ }
@@ -0,0 +1,31 @@
1
+ @use 'spacing' as *;
2
+ @use 'typography' as *;
3
+ @use 'shadows' as *;
4
+ @use 'tooltip' as *;
5
+
6
+ // Confirm tooltip — visual tooltip for icon-only confirm buttons.
7
+ //
8
+ // Usage:
9
+ // .ln-confirm-tooltip { @include confirm-tooltip; }
10
+ //
11
+ // JS adds .ln-confirm-tooltip class and data-tooltip-text attribute
12
+ // to icon-only buttons entering confirm state.
13
+
14
+ @mixin confirm-tooltip {
15
+ position: relative;
16
+ overflow: visible !important;
17
+
18
+ // Confirm icon inherits this color
19
+ color: hsl(var(--color-error)) !important;
20
+
21
+ &::after {
22
+ @include tooltip-bubble;
23
+ content: attr(data-tooltip-text);
24
+ position: absolute;
25
+ bottom: 100%;
26
+ left: 50%;
27
+ transform: translateX(-50%);
28
+ --margin-block: var(--size-sm);
29
+ margin-bottom: var(--margin-block);
30
+ }
31
+ }
@@ -0,0 +1,346 @@
1
+ @use 'display' as *;
2
+ @use 'spacing' as *;
3
+ @use 'sizing' as *;
4
+ @use 'typography' as *;
5
+ @use 'colors' as *;
6
+ @use 'borders' as *;
7
+ @use 'shadows' as *;
8
+ @use 'position' as *;
9
+ @use 'transitions' as *;
10
+ @use 'interaction' as *;
11
+ @use 'motion' as *;
12
+ @use 'form' as *;
13
+ @use 'btn' as *;
14
+ @use 'card' as *;
15
+ @use 'ln-table';
16
+
17
+ // Data Table -- enhanced table with toolbar, sort, filter, virtual scroll.
18
+ //
19
+ // Usage:
20
+ // [data-ln-data-table] { @include data-table; }
21
+ //
22
+ // State-driven CSS (loading, selected, focused, spacer) stays in
23
+ // the co-located JS SCSS file.
24
+
25
+ @mixin data-table {
26
+ @include relative;
27
+
28
+ // Row hover token — local to data-table so chrome (toolbar, thead,
29
+ // footer) reading --bg-sunken stays visually distinct from the row
30
+ // the cursor is on. --bg-elevated is unsuitable: it equals --bg-base
31
+ // in light theme, making hover invisible. Derive a 4-point lightness
32
+ // step from --bg-base via CSS relative color syntax (number form, see
33
+ // _btn.scss header for percentage-vs-number rationale).
34
+ //
35
+ // Light theme: hsl(white) → ~hsl(0 0% 96%)
36
+ // Dark theme: hsl(220 16% 13%) → hsl(220 16% 17%) ≈ --bg-elevated dark
37
+ //
38
+ // The 4-point step is monotonically distinct from --bg-sunken
39
+ // (light: neutral-100 ≈ 95%; dark: 220 16% 20%) on both sides.
40
+ --bg-row-hover: hsl(from var(--bg-base) h s calc(l - 4));
41
+
42
+ // Override @mixin table-base's `tbody tr:hover` to use the local hover
43
+ // token instead of --bg-sunken. Default hover stays --bg-sunken for
44
+ // standalone tables; data-table re-binds so its hover disambiguates from
45
+ // chrome that also uses --bg-sunken.
46
+ > table tbody tr:hover {
47
+ --color-bg: var(--bg-row-hover);
48
+ background: var(--color-bg);
49
+ }
50
+
51
+ // Inner table — `border-collapse: separate` + `border-spacing: 0` is
52
+ // required for `position: sticky` on `<thead> th` to actually pin in
53
+ // Chrome. With the `border-collapse: collapse` that `@mixin table-base`
54
+ // sets by default, Chrome silently drops sticky behavior on table cells —
55
+ // the same reason `@mixin ln-table` applies this exact override. Borders
56
+ // are restored cell-by-cell via table-base's `td { @include border-b }`,
57
+ // so no visual change.
58
+ > table {
59
+ border-collapse: separate;
60
+ border-spacing: 0;
61
+ }
62
+
63
+
64
+
65
+ // Sticky header - Pins the entire thead (including the in-thead toolbar) at top:0
66
+ thead {
67
+ @include sticky-top;
68
+ }
69
+
70
+ thead th {
71
+ position: relative;
72
+ top: auto;
73
+ z-index: auto;
74
+ background: var(--bg-sunken);
75
+
76
+ &[data-ln-col] {
77
+ // Maintain standard table-cell layout so columns don't break or stack vertically
78
+ white-space: normal;
79
+
80
+ // Add padding-inline-end to prevent text from overlapping absolute buttons
81
+
82
+ // Case 1: Has both sort and filter buttons
83
+ &:has([data-ln-col-sort]):has([data-ln-col-filter]) {
84
+ padding-inline-end: calc(var(--padding-x) + 2.5rem);
85
+
86
+ [data-ln-col-filter] {
87
+ position: absolute;
88
+ top: 50%;
89
+ transform: translateY(-50%);
90
+ right: var(--padding-x);
91
+ margin: 0;
92
+ }
93
+
94
+ [data-ln-col-sort] {
95
+ position: absolute;
96
+ top: 50%;
97
+ transform: translateY(-50%);
98
+ right: calc(var(--padding-x) + 1.25rem);
99
+ margin: 0;
100
+ }
101
+ }
102
+
103
+ // Case 2: Has only sort button
104
+ &:has([data-ln-col-sort]):not(:has([data-ln-col-filter])) {
105
+ padding-inline-end: calc(var(--padding-x) + 1.25rem);
106
+
107
+ [data-ln-col-sort] {
108
+ position: absolute;
109
+ top: 50%;
110
+ transform: translateY(-50%);
111
+ right: var(--padding-x);
112
+ margin: 0;
113
+ }
114
+ }
115
+
116
+ // Case 3: Has only filter button
117
+ &:has([data-ln-col-filter]):not(:has([data-ln-col-sort])) {
118
+ padding-inline-end: calc(var(--padding-x) + 1.25rem);
119
+
120
+ [data-ln-col-filter] {
121
+ position: absolute;
122
+ top: 50%;
123
+ transform: translateY(-50%);
124
+ right: var(--padding-x);
125
+ margin: 0;
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ // Support for the in-thead toolbar row inside ln-data-table
132
+ thead > tr:first-child:has(> th[colspan]) > th {
133
+ @include ln-table.ln-table-thead-toolbar;
134
+ }
135
+
136
+ // Section footer — sibling of <table>, NOT inside it. Moved out of
137
+ // <tfoot> because Chromium drops sticky on table cells when a
138
+ // virtual-scroll spacer <tr> sits between the data and the tfoot row.
139
+ // As a section-level <footer> sticky works reliably and shares the
140
+ // same scroll container as the toolbar.
141
+ //
142
+ // Chrome surface visually symmetric with panel-header: --bg-sunken
143
+ // fill, muted fg, body-sm text, border-block-start for separation.
144
+ > footer {
145
+ padding: var(--padding-y) var(--padding-x);
146
+ border-block-start: var(--border-block-start, var(--border-width) solid var(--color-border));
147
+ --color-bg: var(--bg-sunken);
148
+ background: var(--color-bg);
149
+ --color-fg: var(--fg-muted);
150
+ color: var(--color-fg);
151
+ font-size: var(--text-body-sm);
152
+ position: sticky;
153
+ bottom: 0;
154
+ z-index: var(--z-sticky);
155
+ }
156
+ }
157
+
158
+ // ─── Sort indicators ──────────────────────────────────────
159
+
160
+ @mixin data-table-sort-button {
161
+ opacity: 0.4;
162
+ @include motion-safe {
163
+ transition: opacity 0.15s;
164
+ }
165
+ @include cursor-pointer;
166
+ background: none;
167
+ border: none;
168
+ --btn-padding-y: var(--size-2xs);
169
+ --btn-padding-x: var(--size-2xs);
170
+ --margin-inline: var(--size-xs);
171
+ margin-inline-start: var(--margin-inline);
172
+ vertical-align: middle;
173
+
174
+ // Icon-trio visibility — neutral state shows the dual-arrow icon.
175
+ // Active states (.ln-sort-asc / .ln-sort-desc on ancestor <th>)
176
+ // flip to a single directional icon. See component file for the
177
+ // active-state overrides.
178
+ [data-ln-sort-icon="asc"],
179
+ [data-ln-sort-icon="desc"] {
180
+ display: none;
181
+ }
182
+ }
183
+
184
+ @mixin data-table-sort-active {
185
+ opacity: 1;
186
+ color: var(--color-accent);
187
+ }
188
+
189
+ // ─── Filter button ────────────────────────────────────────
190
+
191
+ @mixin data-table-filter-button {
192
+ opacity: 0.4;
193
+ @include motion-safe {
194
+ transition: opacity 0.15s;
195
+ }
196
+ @include cursor-pointer;
197
+ background: none;
198
+ border: none;
199
+ --btn-padding-y: var(--size-2xs);
200
+ --btn-padding-x: var(--size-2xs);
201
+ --margin-inline: var(--size-2xs);
202
+ margin-inline-start: var(--margin-inline);
203
+ vertical-align: middle;
204
+ @include relative;
205
+ }
206
+
207
+ @mixin data-table-filter-active {
208
+ opacity: 1;
209
+ color: var(--color-accent);
210
+
211
+ &::after {
212
+ content: '';
213
+ @include absolute;
214
+ top: 0;
215
+ right: 0;
216
+ width: 0.375rem;
217
+ height: 0.375rem;
218
+ border-radius: var(--radius-full);
219
+ background: var(--color-accent);
220
+ }
221
+ }
222
+
223
+ // ─── Row action buttons ───────────────────────────────────
224
+ //
225
+ // `[data-ln-row-action]` is the ln-data-table attribute for in-row
226
+ // action buttons (edit, delete, custom). They're conventionally
227
+ // icon-only and live in narrow `<td>` cells — full button chrome
228
+ // inflates row height and visually competes with the row content.
229
+ //
230
+ // Style is ghost: no bg, no border, no shadow, dim opacity at rest,
231
+ // full opacity on hover. Targets the BUTTON via its own attribute,
232
+ // not via ancestor selectors — a `data-ln-row-action` button looks
233
+ // the same anywhere.
234
+
235
+ @mixin data-table-row-action {
236
+ background: none;
237
+ border: none;
238
+ --shadow: none;
239
+ --btn-padding-y: var(--size-2xs);
240
+ --btn-padding-x: var(--size-2xs);
241
+ opacity: 0.5;
242
+ @include motion-safe {
243
+ transition: opacity 0.15s;
244
+ }
245
+
246
+ &:hover:not(:disabled) {
247
+ background: none;
248
+ opacity: 1;
249
+ }
250
+ }
251
+
252
+ // Destructive variant — semantic-color cascade via --color-primary
253
+ // rebind so the icon and any confirm-state overlay both read the
254
+ // same source. Applied on top of `data-table-row-action`.
255
+ @mixin data-table-row-action-delete {
256
+ --color-primary: var(--color-error);
257
+ color: hsl(var(--color-primary));
258
+ }
259
+
260
+ // ─── Filter dropdown ──────────────────────────────────────
261
+
262
+ @mixin data-table-filter-dropdown {
263
+ @include absolute;
264
+ top: 100%;
265
+ left: 0;
266
+ z-index: var(--z-dropdown);
267
+ --color-bg: var(--bg-elevated);
268
+ background: var(--color-bg);
269
+ --shadow: var(--shadow-floating);
270
+ border-block-start: var(--border-block-start, var(--border-width) solid var(--color-border));
271
+ border-block-end: var(--border-block-end, var(--border-width) solid var(--color-border));
272
+ border-inline-start: var(--border-inline-start, var(--border-width) solid var(--color-border));
273
+ border-inline-end: var(--border-inline-end, var(--border-width) solid var(--color-border));
274
+ border-radius: var(--radius);
275
+ box-shadow: var(--shadow);
276
+ --padding-y: var(--size-sm);
277
+ --padding-x: var(--size-sm);
278
+ padding: var(--padding-y) var(--padding-x);
279
+ min-width: 12rem;
280
+ max-height: 20rem;
281
+ overflow-y: auto;
282
+
283
+ [data-ln-filter-search] {
284
+ @include form-input;
285
+ width: 100%;
286
+ --margin-block: var(--size-sm);
287
+ margin-bottom: var(--margin-block);
288
+ }
289
+
290
+ ul {
291
+ }
292
+
293
+ li {
294
+ --padding-y: var(--size-xs);
295
+ padding: var(--padding-y) 0;
296
+ }
297
+
298
+ label {
299
+ display: flex;
300
+ align-items: center;
301
+ gap: var(--gap);
302
+ @include cursor-pointer;
303
+ font-size: var(--text-sm);
304
+ }
305
+
306
+ [data-ln-filter-clear] {
307
+ @include btn;
308
+ width: 100%;
309
+ --margin-block: var(--size-sm);
310
+ margin-top: var(--margin-block);
311
+ font-size: var(--text-sm);
312
+ }
313
+ }
314
+
315
+ // Row interaction state — applied to JS-marked clickable rows.
316
+
317
+ @mixin data-table-row {
318
+ cursor: pointer;
319
+
320
+ &:hover {
321
+ --color-bg: var(--bg-sunken);
322
+ background: var(--color-bg);
323
+ }
324
+ }
325
+
326
+ @mixin data-table-row-selected {
327
+ background: var(--color-accent-tint);
328
+ }
329
+
330
+ @mixin data-table-row-focused {
331
+ // Derive focus from --color-accent at this scope (same hue/saturation,
332
+ // lightness +8). Number form (not percentage) — see _btn.scss for the
333
+ // syntax rationale.
334
+ --color-accent-focus: hsl(from var(--color-accent) h s calc(l + 8));
335
+ outline: 2px solid var(--color-accent-focus);
336
+ outline-offset: -2px;
337
+ }
338
+
339
+ // Virtual-scroll spacer rows (JS-injected) — neutralize default cell padding/border.
340
+
341
+ @mixin data-table-spacer-row {
342
+ td {
343
+ padding: 0 !important;
344
+ border: none !important;
345
+ }
346
+ }
@@ -0,0 +1,32 @@
1
+ // Display & Flex
2
+
3
+ @mixin flex { display: flex; }
4
+ @mixin inline-flex { display: inline-flex; }
5
+ @mixin block { display: block; }
6
+ @mixin inline-block { display: inline-block; }
7
+ @mixin hidden { display: none; }
8
+
9
+ @mixin flex-col { display: flex; flex-direction: column; }
10
+ @mixin flex-row { display: flex; flex-direction: row; }
11
+ @mixin flex-wrap { flex-wrap: wrap; }
12
+ @mixin flex-1 { flex: 1; }
13
+ @mixin flex-shrink-0 { flex-shrink: 0; }
14
+
15
+ @mixin items-center { align-items: center; }
16
+ @mixin items-start { align-items: flex-start; }
17
+ @mixin items-end { align-items: flex-end; }
18
+ @mixin justify-center { justify-content: center; }
19
+ @mixin justify-between { justify-content: space-between; }
20
+ @mixin justify-end { justify-content: flex-end; }
21
+
22
+ @mixin flex-center {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ }
27
+
28
+ @mixin inline-flex-center {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ }
@@ -0,0 +1,143 @@
1
+ @use 'display' as *;
2
+ @use 'spacing' as *;
3
+ @use 'colors' as *;
4
+ @use 'borders' as *;
5
+ @use 'shadows' as *;
6
+ @use 'typography' as *;
7
+ @use 'sizing' as *;
8
+ @use 'position' as *;
9
+ @use 'interaction' as *;
10
+ @use 'transitions' as *;
11
+ @use 'motion' as *;
12
+ @use 'card' as *;
13
+
14
+ // Dropdown menu — floating panel with menu items.
15
+ //
16
+ // Usage:
17
+ // [data-ln-dropdown] { @include dropdown; }
18
+ // [data-ln-dropdown-menu] { @include dropdown-menu; }
19
+ //
20
+ // Open/close state (display:none ↔ display:block + .open) is JS-driven
21
+ // and lives in the co-located JS SCSS, not here.
22
+
23
+ @mixin dropdown {
24
+ @include relative;
25
+ }
26
+
27
+ // ─── Menu items — shared recipe ────────────────────────
28
+ // The "list of interactive menu items" recipe, without container
29
+ // chrome or positioning. Composes with any floating container
30
+ // (dropdown, popover-with-menu, other future surfaces).
31
+ //
32
+ // Expected HTML:
33
+ // <ul>
34
+ // <li><a>…</a></li>
35
+ // <li><button>…</button></li>
36
+ // <li><button aria-current="true">Active item</button></li>
37
+ // <li><hr></li>
38
+ // <li><form><button type="submit">Logout</button></form></li>
39
+ // </ul>
40
+ //
41
+ // Selection state — `aria-current="true"` (boolean flavor) marks the
42
+ // active item in single-select pickers (theme, language). NOT the
43
+ // `="page"` / `="step"` flavors — those belong to breadcrumbs /
44
+ // stepper and have their own styling in those mixins.
45
+
46
+ @mixin menu-items {
47
+ // ─── Row resets ────────────────────────────────────
48
+ li {
49
+ margin: 0;
50
+ padding: 0;
51
+ }
52
+
53
+ form {
54
+ margin: 0;
55
+ padding: 0;
56
+ display: contents;
57
+ }
58
+
59
+ // ─── Interactive items ─────────────────────────────
60
+ a,
61
+ button,
62
+ input[type="submit"],
63
+ input[type="reset"],
64
+ input[type="button"] {
65
+ @include w-full;
66
+ @include flex;
67
+ @include items-center;
68
+ justify-content: flex-start;
69
+ gap: var(--gap);
70
+ @include text-left;
71
+ @include text-sm;
72
+ @include cursor-pointer;
73
+ @include transition-fast;
74
+ background: none;
75
+ border: none;
76
+ border-radius: 0;
77
+ color: var(--color-fg);
78
+ text-decoration: none;
79
+
80
+ &:hover:not(:disabled),
81
+ &:active:not(:disabled) {
82
+ --color-bg: var(--bg-sunken);
83
+ background: var(--color-bg);
84
+ color: var(--color-fg);
85
+ }
86
+
87
+ &:focus {
88
+ box-shadow: none;
89
+ }
90
+
91
+ // Active item — single-select selection state.
92
+ // Scoped to the boolean flavor so we don't collide with
93
+ // breadcrumbs (`="page"`) or stepper (`="step"`).
94
+ &[aria-current="true"] {
95
+ background: var(--color-accent-tint);
96
+ color: var(--color-accent);
97
+ }
98
+ }
99
+
100
+ a {
101
+ --padding-y: var(--size-xs);
102
+ --padding-x: var(--size-sm);
103
+ padding: var(--padding-y) var(--padding-x);
104
+ }
105
+
106
+ :is(button, input[type="submit"], input[type="reset"], input[type="button"]) {
107
+ --btn-padding-y: var(--size-xs);
108
+ --btn-padding-x: var(--size-sm);
109
+ }
110
+
111
+ // Inter-item divider — SOFT --border-block-start primitive read with
112
+ // `none` fallback. Default theme: no divider. Themes that opt in
113
+ // (Glass) rebind --border-block-start in scope.
114
+ li + li {
115
+ border-block-start: var(--border-block-start, none);
116
+ }
117
+
118
+ // ─── Separator ─────────────────────────────────────
119
+ hr {
120
+ border: none;
121
+ border-block-start: var(--border-block-start, var(--border-width) solid var(--color-border));
122
+ --margin-block: var(--size-xs);
123
+ margin-block: var(--margin-block);
124
+ }
125
+ }
126
+
127
+ // ─── Dropdown menu — floating panel + menu items ───────
128
+ // Dropdown-specific chrome (anchor-positioned absolute panel) plus
129
+ // the shared menu-items recipe.
130
+
131
+ @mixin dropdown-menu {
132
+ @include floating-panel;
133
+ @include absolute;
134
+ right: 0;
135
+ top: 100%;
136
+ --margin-block: var(--size-xs);
137
+ margin-top: var(--margin-block);
138
+ --padding-y: var(--size-xs);
139
+ padding-block: var(--padding-y);
140
+ min-width: 10rem;
141
+
142
+ @include menu-items;
143
+ }