@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,39 @@
1
+ @use 'display' as *;
2
+ @use 'spacing' as *;
3
+ @use 'sizing' as *;
4
+ @use 'position' as *;
5
+ @use 'borders' as *;
6
+ @use 'interaction' as *;
7
+ @use 'motion' as *;
8
+
9
+ // Ajax loading overlay + spinner — visual treatment for JS-driven loading state.
10
+ //
11
+ // Usage:
12
+ // .ln-ajax--loading { @include ajax-loading; }
13
+ // .ln-ajax-spinner { @include ajax-spinner; }
14
+
15
+ @mixin ajax-loading {
16
+ @include relative;
17
+ pointer-events: none;
18
+ @include cursor-not-allowed;
19
+ // Alpha composition: blend the page surface at 60% opacity so the
20
+ // loading overlay tints the content beneath. color-mix produces the
21
+ // equivalent of the old hsl(triplet / 0.6) without needing a bare
22
+ // triplet. Adapts automatically via --color-bg cascade (dark/theme).
23
+ box-shadow: inset 0 0 0 9999px color-mix(in srgb, var(--color-bg) 60%, transparent);
24
+ }
25
+
26
+ @mixin ajax-spinner {
27
+ @include absolute;
28
+ top: 50%;
29
+ left: 50%;
30
+ transform: translate(-50%, -50%);
31
+ @include size(1rem);
32
+ border: 2px solid var(--color-accent);
33
+ @include rounded-full;
34
+ border-top-color: transparent;
35
+ @include motion-safe {
36
+ animation: ln-ajax-spin 0.6s linear infinite;
37
+ }
38
+ @include z(2);
39
+ }
@@ -0,0 +1,82 @@
1
+ @use 'spacing' as *;
2
+ @use 'display' as *;
3
+ @use 'typography' as *;
4
+ @use 'colors' as *;
5
+ @use 'borders' as *;
6
+ @use 'btn' as *;
7
+
8
+ // Inline alert — contextual feedback element inside content.
9
+ //
10
+ // Usage (project SCSS — apply to semantic selector):
11
+ // .alert { @include alert; } ← info (default)
12
+ // .alert.success { --color-primary: var(--color-success); }
13
+ // .alert.warning { --color-primary: var(--color-warning); }
14
+ // .alert.error { --color-primary: var(--color-error); }
15
+ //
16
+ // HTML:
17
+ // <div class="alert" role="alert">
18
+ // <svg class="ln-icon" aria-hidden="true"><use href="#ln-info-circle"></use></svg>
19
+ // <p>Message text.</p>
20
+ // <button type="button" aria-label="Dismiss">
21
+ // <svg class="ln-icon" aria-hidden="true"><use href="#ln-x"></use></svg>
22
+ // </button>
23
+ // </div>
24
+
25
+ @mixin alert {
26
+ @include flex;
27
+ @include items-center;
28
+ gap: var(--gap);
29
+ --padding-y: var(--size-sm);
30
+ --padding-x: var(--size-md);
31
+ padding: var(--padding-y) var(--padding-x);
32
+ border-radius: var(--radius);
33
+ @include text-sm;
34
+ border-left: 3px solid var(--color-accent);
35
+ @include tinted-surface(0.08);
36
+ color: var(--color-fg);
37
+
38
+ .ln-icon {
39
+ color: var(--color-accent);
40
+ @include flex-shrink-0;
41
+ }
42
+
43
+ p {
44
+ @include flex-1;
45
+ @include m(0);
46
+ line-height: 1.5;
47
+ }
48
+
49
+ button {
50
+ --btn-padding-y: var(--size-2xs);
51
+ --btn-padding-x: var(--size-2xs);
52
+ @include flex-shrink-0;
53
+ margin-left: auto;
54
+ }
55
+ }
56
+
57
+ // Banner variant — full-width strip, bottom-border, no radius.
58
+ // Apply ON TOP of @include alert (sets the structural delta only).
59
+ //
60
+ // Usage:
61
+ // .alert.banner { @include alert-banner; }
62
+ //
63
+ // HTML (dismissible via ln-toggle, dismissed state persists across reloads):
64
+ // <div class="alert banner" role="status" aria-live="polite"
65
+ // data-ln-toggle="open" data-ln-persist id="trial-notice">
66
+ // <svg class="ln-icon" aria-hidden="true"><use href="#ln-info-circle"></use></svg>
67
+ // <p>Trial expires in 3 days.</p>
68
+ // <button type="button" aria-label="Dismiss"
69
+ // data-ln-toggle-for="trial-notice"
70
+ // data-ln-toggle-action="close">
71
+ // <svg class="ln-icon" aria-hidden="true"><use href="#ln-x"></use></svg>
72
+ // </button>
73
+ // </div>
74
+
75
+ @mixin alert-banner {
76
+ border-left: none;
77
+ border-radius: 0;
78
+ border-bottom: var(--border-width) solid color-mix(in srgb, var(--color-accent) 20%, transparent);
79
+ --padding-x: var(--size-lg);
80
+ padding-inline: var(--padding-x);
81
+ width: 100%;
82
+ }
@@ -0,0 +1,312 @@
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 'position' as *;
8
+ @use 'motion' as *;
9
+ @use 'layout' as *;
10
+ @use 'breakpoints' as *;
11
+ @use 'transitions' as *;
12
+ @use 'interaction' as *;
13
+ @use 'sidebar' as *;
14
+
15
+ // ─── App Shell ──────────────────────────────────────────────────────────────
16
+ // Mobile-ready application shell: fixed header, sidebar drawer, scrim overlay.
17
+ //
18
+ // Intrinsic tokens (component design, not spacing rhythm):
19
+ // --app-header-height: 3.5rem — fixed bar height; hard-code acceptable
20
+ // --app-sidebar-width: 16rem — sidebar width; intrinsic to design
21
+ // --app-scrim-bg — semi-transparent overlay behind drawer
22
+ //
23
+ // Expected HTML:
24
+ // <div class="app-wrapper">
25
+ // <header class="app-header">…</header>
26
+ // <main class="app-main">
27
+ // <aside class="app-sidebar" id="…" data-ln-toggle="open">…</aside>
28
+ // <section>…content…</section>
29
+ // <footer class="app-footer">…</footer>
30
+ // <div class="app-scrim"></div>
31
+ // </main>
32
+ // </div>
33
+ //
34
+ // Usage (project):
35
+ // #shell { @include app-wrapper; }
36
+ // #shell > header { @include app-header; }
37
+ // #shell > main { @include app-main; }
38
+ // #my-sidebar { @include sidebar; @include sidebar-drawer; }
39
+ // #my-scrim { @include app-scrim; }
40
+ // .app-footer { @include app-footer; } (global binding — no project work needed)
41
+ // ────────────────────────────────────────────────────────────────────────────
42
+
43
+ @mixin app-wrapper {
44
+ @include flex-col;
45
+ // Bounded shell — body never scrolls. The .app-main child is the
46
+ // inner viewport (viewport − fixed header). dvh tracks live viewport
47
+ // so mobile URL bar collapse/expand stays in sync.
48
+ height: 100dvh;
49
+ overflow: hidden;
50
+ background: var(--bg-recessed);
51
+ }
52
+
53
+ @mixin app-header {
54
+ --color-border: var(--border-subtle);
55
+ --shadow: var(--shadow-resting);
56
+ @include fixed;
57
+ top: 0;
58
+ left: 0;
59
+ right: 0;
60
+ height: var(--app-header-height);
61
+ z-index: var(--z-sticky);
62
+ padding: var(--padding-y) var(--padding-x);
63
+ background: var(--color-bg);
64
+ border-block-end: var(--border-block-end, var(--border-width) solid var(--color-border));
65
+ box-shadow: var(--shadow);
66
+ @include flex;
67
+ @include items-center;
68
+ @include justify-between;
69
+ gap: var(--gap);
70
+
71
+ // Ghost surface by default for every button in the bar. Variants
72
+ // (e.g. @mixin app-header-actions) re-bind --btn-border and padding
73
+ // on top of this default. Padding is rebound via primitives (not a
74
+ // literal `padding: 0`) so variants' --padding-* rebinds resolve
75
+ // through button-base's `padding: var(--padding-y) var(--padding-x)`.
76
+ button {
77
+ --btn-bg: transparent;
78
+ --btn-border: transparent;
79
+ --color-fg: var(--fg-muted);
80
+ --btn-padding-y: 0;
81
+ --btn-padding-x: 0;
82
+ color: var(--color-fg);
83
+ box-shadow: none;
84
+
85
+ &:focus {
86
+ box-shadow: none;
87
+ }
88
+ }
89
+ }
90
+
91
+ @mixin app-header-left {
92
+ @include flex;
93
+ @include items-center;
94
+ --gap: var(--size-sm-up);
95
+ gap: var(--gap);
96
+
97
+ .menu-toggle {
98
+ @include flex-center;
99
+ @include size(2rem);
100
+ @include rounded-sm;
101
+ }
102
+
103
+ h1 {
104
+ @include text-xl;
105
+ @include font-bold;
106
+ @include text-primary;
107
+ margin: 0;
108
+ }
109
+ }
110
+
111
+ @mixin app-header-right {
112
+ @include flex;
113
+ @include items-center;
114
+ --gap: var(--size-md);
115
+ gap: var(--gap);
116
+ }
117
+
118
+ @mixin app-header-actions {
119
+ @include flex;
120
+ @include items-center;
121
+ --gap: var(--size-sm);
122
+ gap: var(--gap);
123
+
124
+ // Equalize height across icon-only and text-only siblings AND
125
+ // search-icon labels. Geometric — locks the border-box chrome to
126
+ // the icon-content height (1.25rem) plus rebound --padding-y and
127
+ // border-width on both edges. Buttons rebind --padding-y below;
128
+ // search labels read --padding-y from @mixin form-input-icon-group
129
+ // (same var(--size-xs) value), so the formula resolves identically
130
+ // for both. Border-box is the project default (scss/base/_reset.scss).
131
+ :is(button, label) {
132
+ min-height: calc(1.25rem + 2 * var(--padding-y) + 2 * var(--border-width));
133
+ }
134
+
135
+ button {
136
+ // Bordered variant re-bound over @mixin app-header's ghost
137
+ // default. --color-border is already a composed hsl() value;
138
+ // read directly (no extra hsl() wrap).
139
+ --btn-border: var(--color-border);
140
+ --btn-padding-y: var(--size-xs);
141
+ --btn-padding-x: var(--size-sm-up);
142
+ @include text-xs;
143
+ font-weight: var(--font-medium);
144
+ border-radius: var(--radius-sm);
145
+
146
+ // Label visually-hidden on small viewports — icon remains.
147
+ > span {
148
+ @include mq-down(sm) {
149
+ position: absolute;
150
+ width: 1px;
151
+ height: 1px;
152
+ padding: 0;
153
+ margin: -1px;
154
+ overflow: hidden;
155
+ clip: rect(0, 0, 0, 0);
156
+ white-space: nowrap;
157
+ border: 0;
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ @mixin header-avatar {
164
+ // Plain 2rem circular thumbnail. Distinct from @mixin avatar
165
+ // (profile-button with ring + hover) — this is a non-interactive
166
+ // image circle used as a popover trigger target.
167
+ @include size(2rem);
168
+ @include rounded-full;
169
+ @include overflow-hidden;
170
+ @include cursor-pointer;
171
+
172
+ img {
173
+ @include w-full;
174
+ @include h-full;
175
+ object-fit: cover;
176
+ }
177
+ }
178
+
179
+ @mixin app-main {
180
+ @include flex-col;
181
+ @include flex-1;
182
+ margin-top: var(--app-header-height);
183
+ // Bounded inner viewport: exactly the space below the fixed
184
+ // .app-header. The `> section` child flex-fills and becomes the
185
+ // scroll surface (its overflow-auto finally activates here).
186
+ // Footer sits below section as a sibling, intrinsic height,
187
+ // pinned to bottom by flex distribution.
188
+ height: calc(100dvh - var(--app-header-height));
189
+ overflow: hidden;
190
+
191
+ // When the sidebar drawer is open on desktop, reserve its space
192
+ // on THIS element (the parent) so both content and footer shift
193
+ // together. Padding on the parent keeps child widths at 100% and
194
+ // avoids the horizontal overflow caused by margin-left on
195
+ // width:100% children.
196
+ &:has(.app-sidebar[data-ln-toggle="open"]) {
197
+ padding-left: var(--app-sidebar-width);
198
+ }
199
+
200
+ // On mobile the sidebar overlays (scrim covers content) —
201
+ // no content shift.
202
+ @include mq-down(md) {
203
+ &:has(.app-sidebar[data-ln-toggle="open"]) {
204
+ padding-left: 0;
205
+ }
206
+ }
207
+
208
+ // Content column: centred, capped, padded, vertical stack.
209
+ // flex-1 + min-height: 0 — bounded parent + flex-shrink past content
210
+ // height activates @include overflow-auto. Without min-height: 0,
211
+ // flex items default to min-height: auto and refuse to shrink below
212
+ // intrinsic content size, defeating the bounded scroll contract.
213
+ > section {
214
+ @include flex-1;
215
+ min-height: 0;
216
+ // NO padding-block — sticky descendants (e.g. ln-table's <thead>
217
+ // at top:0, ln-data-table's <footer> at bottom:0) pin flush at the
218
+ // section edges instead of at the inner padding edge. The lost
219
+ // breathing room is restored INSIDE the scroll flow via
220
+ // `> *:first-child` / `> *:last-child` margins below — those
221
+ // margins scroll past with the content, leaving sticky to pin
222
+ // against the actual section edges.
223
+ padding-inline: var(--padding-x);
224
+ @include overflow-auto;
225
+ @include w-full;
226
+ @include stack(var(--gap));
227
+ @include transition;
228
+
229
+ // Children keep intrinsic height — wrapper's overflow-auto
230
+ // handles the scroll. Without this, section-cards (which set
231
+ // overflow: clip, making their min-height: auto resolve to 0)
232
+ // get flex-shrunk to fit the bounded parent before scroll
233
+ // activates — visible as "briefly open, then collapse" on load.
234
+ > * {
235
+ flex-shrink: 0;
236
+ }
237
+
238
+ // First / last children carry the visual breathing room that used to
239
+ // be padding-block-start / padding-block-end. As margin, it lives
240
+ // inside the scroll flow — scrolls past, doesn't push sticky
241
+ // descendants (sticky top:0 toolbar/thead, sticky bottom:0 footer)
242
+ // to the inside of the padding edge.
243
+ > *:first-child {
244
+ margin-block-start: var(--padding-x);
245
+ }
246
+
247
+ > *:last-child {
248
+ margin-block-end: var(--padding-x);
249
+ }
250
+ }
251
+ }
252
+
253
+ @mixin sidebar-drawer {
254
+ // Layered on top of @mixin sidebar. Adds drawer positioning and
255
+ // open/close transform. Apply AFTER @include sidebar.
256
+ @include fixed;
257
+ top: var(--app-header-height);
258
+ left: 0;
259
+ height: calc(100vh - var(--app-header-height));
260
+ width: var(--app-sidebar-width);
261
+ z-index: var(--z-sticky);
262
+ transform: translateX(-100%);
263
+
264
+ @include motion-safe {
265
+ transition: transform var(--transition);
266
+ }
267
+
268
+ &[data-ln-toggle="open"] {
269
+ transform: translateX(0);
270
+ }
271
+
272
+ @include mq-down(md) {
273
+ z-index: var(--z-overlay);
274
+ }
275
+ }
276
+
277
+ @mixin app-scrim {
278
+ @include fixed;
279
+ @include inset-0;
280
+ background: var(--app-scrim-bg);
281
+ z-index: calc(var(--z-overlay) - 1);
282
+ opacity: 0;
283
+ pointer-events: none;
284
+
285
+ @include motion-safe {
286
+ transition: opacity var(--transition);
287
+ }
288
+
289
+ // State-driven via sibling combinator — no JS wiring needed.
290
+ // The scrim reacts to the sidebar drawer's existing toggle attribute,
291
+ // which is owned by the ln-toggle JS component.
292
+ aside[data-ln-toggle="open"] ~ & {
293
+ opacity: 1;
294
+ pointer-events: auto;
295
+ }
296
+
297
+ @include mq-up(md) {
298
+ display: none;
299
+ }
300
+ }
301
+
302
+ // @mixin app-footer — extracted to _footer.scss.
303
+ // Forwarded by _index.scss. Consumers: `.app-footer` (components/_app-shell.scss)
304
+ // and `<aside> > footer` (via @mixin sidebar in _sidebar.scss).
305
+
306
+ @mixin app-content-wrapper {
307
+ max-width: var(--max-w-container);
308
+ @include mx(auto);
309
+ @include w-full;
310
+ @include stack(var(--gap));
311
+ }
312
+
@@ -0,0 +1,109 @@
1
+ @use 'spacing' as *;
2
+ @use 'display' as *;
3
+ @use 'sizing' as *;
4
+ @use 'typography' as *;
5
+ @use 'colors' as *;
6
+ @use 'borders' as *;
7
+ @use 'shadows' as *;
8
+ @use 'transitions' as *;
9
+ @use 'interaction' as *;
10
+ @use 'focus' as *;
11
+
12
+ // Avatar — profile image or initials, with optional name/role text
13
+ //
14
+ // Standalone: <span class="avatar"><img src="..." alt=""></span>
15
+ // Initials: <abbr class="avatar">SO</abbr>
16
+ // With text: <button class="avatar"><img ...><span>Name<small>Role</small></span></button>
17
+ // Sizes: avatar-sm (2rem), avatar (2.5rem), avatar-lg (3rem), avatar-xl (5rem)
18
+ // Ring: double box-shadow (bg + border) — Tabler-inspired
19
+
20
+ // ─── Shared ring ───────────────────────────────────────
21
+ @mixin _avatar-ring {
22
+ box-shadow: 0 0 0 var(--border-width-strong) var(--color-bg),
23
+ 0 0 0 calc(var(--border-width-strong) + var(--border-width)) var(--color-border);
24
+ }
25
+
26
+ // ─── Shared image / initials base ──────────────────────
27
+ @mixin _avatar-media {
28
+ @include size(2.5rem);
29
+ border-radius: var(--radius);
30
+ @include flex-shrink-0;
31
+ @include _avatar-ring;
32
+ }
33
+
34
+ // ─── Base ──────────────────────────────────────────────
35
+ @mixin avatar {
36
+ @include inline-flex;
37
+ @include items-center;
38
+ --gap: var(--size-sm-up);
39
+ gap: var(--gap);
40
+ --padding-y: var(--size-xs-up);
41
+ --padding-x: var(--size-xs-up);
42
+ padding: var(--padding-y) var(--padding-x);
43
+ border-radius: var(--radius);
44
+ @include cursor-pointer;
45
+ @include transition-fast;
46
+ @include select-none;
47
+ @include border-none;
48
+ background: transparent;
49
+ text-decoration: none;
50
+ color: inherit;
51
+ font: inherit;
52
+
53
+ &:hover {
54
+ --color-bg: var(--bg-sunken);
55
+ background: var(--color-bg);
56
+ }
57
+
58
+ &:focus-visible {
59
+ @include focus-ring;
60
+ }
61
+
62
+ // Photo
63
+ img {
64
+ @include _avatar-media;
65
+ object-fit: cover;
66
+ }
67
+
68
+ // Initials fallback — <abbr class="avatar">SO</abbr>
69
+ &:not(:has(img)):not(:has(span)) {
70
+ @include _avatar-media;
71
+ @include flex-center;
72
+ @include text-sm;
73
+ @include font-semibold;
74
+ background-color: color-mix(in srgb, var(--color-accent) 15%, transparent);
75
+ color: var(--color-accent);
76
+ padding: 0;
77
+ }
78
+
79
+ // Name + role
80
+ span {
81
+ @include flex-col;
82
+ @include text-sm;
83
+ @include font-medium;
84
+ color: var(--color-fg);
85
+ @include text-left;
86
+ line-height: 1.3;
87
+
88
+ small {
89
+ @include text-xs;
90
+ @include font-normal;
91
+ --color-fg: var(--fg-subtle);
92
+ color: var(--color-fg);
93
+ }
94
+ }
95
+ }
96
+
97
+ // ─── Sizes ─────────────────────────────────────────────
98
+ @mixin _avatar-size($size, $font-size, $line-height) {
99
+ img { @include size($size); }
100
+ &:not(:has(img)):not(:has(span)) {
101
+ @include size($size);
102
+ font-size: $font-size;
103
+ line-height: $line-height;
104
+ }
105
+ }
106
+
107
+ @mixin avatar-sm { @include _avatar-size(2rem, var(--text-xs), 1rem); }
108
+ @mixin avatar-lg { @include _avatar-size(3rem, var(--text-base), 1.5rem); }
109
+ @mixin avatar-xl { @include _avatar-size(5rem, var(--text-xl), 1.75rem); }
@@ -0,0 +1,36 @@
1
+ // Borders — references --color-border and --radius-* tokens
2
+
3
+ @mixin border {
4
+ border-block-start: var(--border-block-start, var(--border-width) solid var(--color-border));
5
+ border-block-end: var(--border-block-end, var(--border-width) solid var(--color-border));
6
+ border-inline-start: var(--border-inline-start, var(--border-width) solid var(--color-border));
7
+ border-inline-end: var(--border-inline-end, var(--border-width) solid var(--color-border));
8
+ }
9
+
10
+ @mixin border-t { border-block-start: var(--border-block-start, var(--border-width) solid var(--color-border)); }
11
+ @mixin border-b { border-block-end: var(--border-block-end, var(--border-width) solid var(--color-border)); }
12
+ @mixin border-l { border-inline-start: var(--border-inline-start, var(--border-width) solid var(--color-border)); }
13
+ @mixin border-r { border-inline-end: var(--border-inline-end, var(--border-width) solid var(--color-border)); }
14
+
15
+ @mixin border-none { border: none; }
16
+
17
+ @mixin shadow-b {
18
+ position: relative;
19
+
20
+ &::after {
21
+ content: '';
22
+ position: absolute;
23
+ bottom: calc(-1 * var(--size-sm-up)); // Geometric — shadow-b decoration offset, not spacing rhythm.
24
+ left: 0;
25
+ right: 0;
26
+ height: var(--size-sm-up); // Intrinsic shadow-decoration height, not spacing rhythm.
27
+ background: linear-gradient(to bottom, hsl(var(--color-neutral-200) / 0.5), transparent);
28
+ pointer-events: none;
29
+ }
30
+ }
31
+
32
+ @mixin rounded-sm { border-radius: var(--radius-sm); }
33
+ @mixin rounded-md { border-radius: var(--radius-md); }
34
+ @mixin rounded-lg { border-radius: var(--radius-lg); }
35
+ @mixin rounded-xl { border-radius: var(--radius-xl); }
36
+ @mixin rounded-full { border-radius: var(--radius-full); }
@@ -0,0 +1,72 @@
1
+ @use 'display' as *;
2
+ @use 'borders' as *;
3
+ @use 'focus' as *;
4
+ @use 'interaction' as *;
5
+
6
+ // ─── Breadcrumbs ────────────────────────────────────────────────────────────
7
+ // CSS-only navigation trail. No JS involvement.
8
+ //
9
+ // Applied to the semantic `<nav>` container (WAI-ARIA breadcrumb pattern).
10
+ // The mixin internally targets the inner `> ol` so the browser's default
11
+ // ordered-list styling (decimal markers, padding-left, li margins from
12
+ // base typography) gets reset at the right depth. Applying the mixin
13
+ // directly to an `<ol>` would not reset the parent `<nav>` and would
14
+ // also not compose with the semantic a11y root.
15
+ //
16
+ // Usage:
17
+ // .breadcrumbs { @include breadcrumbs; }
18
+ //
19
+ // <nav class="breadcrumbs" aria-label="Breadcrumb">
20
+ // <ol>
21
+ // <li><a href="/">Home</a></li>
22
+ // <li><a href="/users">Users</a></li>
23
+ // <li aria-current="page">Edit</li>
24
+ // </ol>
25
+ // </nav>
26
+
27
+ @mixin breadcrumbs {
28
+ @include select-none;
29
+
30
+ > ol {
31
+ @include flex;
32
+ @include items-center;
33
+ @include flex-wrap;
34
+ gap: var(--gap);
35
+
36
+ > li {
37
+ @include flex;
38
+ @include items-center;
39
+ gap: var(--gap);
40
+ font-size: var(--text-body-sm);
41
+ line-height: var(--lh-body-sm);
42
+ margin: 0;
43
+
44
+ + li::before {
45
+ content: '\00BB';
46
+ // Pseudo-element announces as text by some screen readers.
47
+ // Breadcrumb separators are decorative — the list order
48
+ // carries the meaning.
49
+ speak: never;
50
+ }
51
+ }
52
+
53
+ a {
54
+ @include inline-flex;
55
+ @include items-center;
56
+ --padding-y: var(--size-2xs);
57
+ --padding-x: var(--size-xs);
58
+ padding: var(--padding-y) var(--padding-x);
59
+ @include rounded-sm;
60
+
61
+ &:focus-visible {
62
+ @include focus-ring;
63
+ }
64
+ }
65
+
66
+ [aria-current="page"] {
67
+ --padding-y: var(--size-2xs);
68
+ --padding-x: var(--size-xs);
69
+ padding: var(--padding-y) var(--padding-x);
70
+ }
71
+ }
72
+ }