@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.
- package/README.md +177 -0
- package/js/COMPONENTS.md +1102 -0
- package/js/index.js +41 -0
- package/js/ln-accordion/README.md +137 -0
- package/js/ln-accordion/ln-accordion.js +1 -0
- package/js/ln-accordion/src/ln-accordion.js +41 -0
- package/js/ln-ajax/README.md +91 -0
- package/js/ln-ajax/ln-ajax.js +1 -0
- package/js/ln-ajax/src/ln-ajax.js +277 -0
- package/js/ln-api-connector/README.md +150 -0
- package/js/ln-api-connector/ln-api-connector.js +1 -0
- package/js/ln-api-connector/src/ln-api-connector.js +265 -0
- package/js/ln-autoresize/README.md +80 -0
- package/js/ln-autoresize/ln-autoresize.js +1 -0
- package/js/ln-autoresize/src/ln-autoresize.js +47 -0
- package/js/ln-autosave/README.md +92 -0
- package/js/ln-autosave/ln-autosave.js +1 -0
- package/js/ln-autosave/src/ln-autosave.js +147 -0
- package/js/ln-circular-progress/README.md +161 -0
- package/js/ln-circular-progress/ln-circular-progress.js +1 -0
- package/js/ln-circular-progress/src/ln-circular-progress.js +133 -0
- package/js/ln-confirm/README.md +86 -0
- package/js/ln-confirm/_ln-confirm.scss +13 -0
- package/js/ln-confirm/ln-confirm.js +1 -0
- package/js/ln-confirm/src/ln-confirm.js +131 -0
- package/js/ln-core/crypto.js +83 -0
- package/js/ln-core/helpers.js +411 -0
- package/js/ln-core/index.js +5 -0
- package/js/ln-core/persist.js +71 -0
- package/js/ln-core/positioning.js +207 -0
- package/js/ln-core/reactive.js +74 -0
- package/js/ln-couchdb-connector/README.md +156 -0
- package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -0
- package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +348 -0
- package/js/ln-data-coordinator/README.md +165 -0
- package/js/ln-data-coordinator/ln-data-coordinator.js +1 -0
- package/js/ln-data-coordinator/src/ln-data-coordinator.js +249 -0
- package/js/ln-data-store/README.md +94 -0
- package/js/ln-data-store/ln-data-store.js +1 -0
- package/js/ln-data-store/src/ln-data-store.js +699 -0
- package/js/ln-data-table/README.md +110 -0
- package/js/ln-data-table/ln-data-table.js +1 -0
- package/js/ln-data-table/ln-data-table.scss +10 -0
- package/js/ln-data-table/src/ln-data-table.js +1103 -0
- package/js/ln-date/README.md +151 -0
- package/js/ln-date/ln-date.js +1 -0
- package/js/ln-date/src/ln-date.js +442 -0
- package/js/ln-dropdown/README.md +117 -0
- package/js/ln-dropdown/ln-dropdown.js +1 -0
- package/js/ln-dropdown/ln-dropdown.scss +15 -0
- package/js/ln-dropdown/src/ln-dropdown.js +174 -0
- package/js/ln-external-links/README.md +341 -0
- package/js/ln-external-links/ln-external-links.js +1 -0
- package/js/ln-external-links/src/ln-external-links.js +116 -0
- package/js/ln-filter/README.md +99 -0
- package/js/ln-filter/ln-filter.js +1 -0
- package/js/ln-filter/ln-filter.scss +7 -0
- package/js/ln-filter/src/ln-filter.js +404 -0
- package/js/ln-form/README.md +101 -0
- package/js/ln-form/ln-form.js +1 -0
- package/js/ln-form/src/ln-form.js +199 -0
- package/js/ln-http/README.md +89 -0
- package/js/ln-http/ln-http.js +1 -0
- package/js/ln-http/src/ln-http.js +219 -0
- package/js/ln-icons/README.md +88 -0
- package/js/ln-icons/ln-icons.js +1 -0
- package/js/ln-icons/src/ln-icons.js +169 -0
- package/js/ln-link/README.md +303 -0
- package/js/ln-link/ln-link.js +1 -0
- package/js/ln-link/src/ln-link.js +196 -0
- package/js/ln-modal/README.md +154 -0
- package/js/ln-modal/ln-modal.js +1 -0
- package/js/ln-modal/ln-modal.scss +11 -0
- package/js/ln-modal/src/ln-modal.js +201 -0
- package/js/ln-nav/README.md +70 -0
- package/js/ln-nav/ln-nav.js +1 -0
- package/js/ln-nav/src/ln-nav.js +177 -0
- package/js/ln-number/README.md +122 -0
- package/js/ln-number/ln-number.js +1 -0
- package/js/ln-number/src/ln-number.js +302 -0
- package/js/ln-popover/README.md +127 -0
- package/js/ln-popover/ln-popover.js +1 -0
- package/js/ln-popover/src/ln-popover.js +288 -0
- package/js/ln-progress/README.md +442 -0
- package/js/ln-progress/ln-progress.js +1 -0
- package/js/ln-progress/src/ln-progress.js +150 -0
- package/js/ln-search/README.md +83 -0
- package/js/ln-search/ln-search.js +1 -0
- package/js/ln-search/ln-search.scss +7 -0
- package/js/ln-search/src/ln-search.js +114 -0
- package/js/ln-sortable/README.md +95 -0
- package/js/ln-sortable/ln-sortable.js +1 -0
- package/js/ln-sortable/src/ln-sortable.js +203 -0
- package/js/ln-table/README.md +101 -0
- package/js/ln-table/ln-table-sort.js +1 -0
- package/js/ln-table/ln-table.js +1 -0
- package/js/ln-table/ln-table.scss +11 -0
- package/js/ln-table/src/ln-table-sort.js +168 -0
- package/js/ln-table/src/ln-table.js +473 -0
- package/js/ln-tabs/README.md +137 -0
- package/js/ln-tabs/ln-tabs.js +1 -0
- package/js/ln-tabs/src/ln-tabs.js +171 -0
- package/js/ln-time/README.md +81 -0
- package/js/ln-time/ln-time.js +1 -0
- package/js/ln-time/src/ln-time.js +192 -0
- package/js/ln-toast/README.md +122 -0
- package/js/ln-toast/ln-toast.js +15 -0
- package/js/ln-toast/src/ln-toast.js +210 -0
- package/js/ln-toast/template.html +14 -0
- package/js/ln-toggle/README.md +137 -0
- package/js/ln-toggle/ln-toggle.js +1 -0
- package/js/ln-toggle/src/ln-toggle.js +139 -0
- package/js/ln-tooltip/README.md +58 -0
- package/js/ln-tooltip/ln-tooltip.js +1 -0
- package/js/ln-tooltip/ln-tooltip.scss +9 -0
- package/js/ln-tooltip/src/ln-tooltip.js +169 -0
- package/js/ln-translations/README.md +96 -0
- package/js/ln-translations/ln-translations.js +1 -0
- package/js/ln-translations/src/ln-translations.js +275 -0
- package/js/ln-upload/README.md +180 -0
- package/js/ln-upload/ln-upload.js +1 -0
- package/js/ln-upload/ln-upload.scss +20 -0
- package/js/ln-upload/src/ln-upload.js +407 -0
- package/js/ln-validate/README.md +108 -0
- package/js/ln-validate/ln-validate.js +1 -0
- package/js/ln-validate/src/ln-validate.js +160 -0
- package/package.json +55 -0
- package/scss/base/_global.scss +83 -0
- package/scss/base/_reset.scss +17 -0
- package/scss/base/_typography.scss +125 -0
- package/scss/components/_accordion.scss +34 -0
- package/scss/components/_ajax.scss +15 -0
- package/scss/components/_alert.scss +5 -0
- package/scss/components/_app-shell.scss +15 -0
- package/scss/components/_avatar.scss +6 -0
- package/scss/components/_breadcrumbs.scss +33 -0
- package/scss/components/_button.scss +20 -0
- package/scss/components/_card.scss +10 -0
- package/scss/components/_chip.scss +5 -0
- package/scss/components/_circular-progress.scss +29 -0
- package/scss/components/_confirm.scss +5 -0
- package/scss/components/_data-table.scss +83 -0
- package/scss/components/_dropdown.scss +25 -0
- package/scss/components/_empty-state.scss +22 -0
- package/scss/components/_form.scss +100 -0
- package/scss/components/_layout.scss +8 -0
- package/scss/components/_link.scss +11 -0
- package/scss/components/_ln-table.scss +60 -0
- package/scss/components/_loader.scss +6 -0
- package/scss/components/_modal.scss +20 -0
- package/scss/components/_nav.scss +9 -0
- package/scss/components/_page-header.scss +10 -0
- package/scss/components/_popover.scss +10 -0
- package/scss/components/_progress.scss +17 -0
- package/scss/components/_prose.scss +5 -0
- package/scss/components/_scrollbar.scss +32 -0
- package/scss/components/_sections.scss +12 -0
- package/scss/components/_sidebar.scss +5 -0
- package/scss/components/_stat-card.scss +5 -0
- package/scss/components/_status-badge.scss +4 -0
- package/scss/components/_stepper.scss +5 -0
- package/scss/components/_table.scss +19 -0
- package/scss/components/_tabs.scss +21 -0
- package/scss/components/_timeline.scss +14 -0
- package/scss/components/_toast.scss +41 -0
- package/scss/components/_toggle.scss +81 -0
- package/scss/components/_tooltip.scss +18 -0
- package/scss/components/_translations.scss +111 -0
- package/scss/components/_upload.scss +51 -0
- package/scss/config/_breakpoints.scss +72 -0
- package/scss/config/_density.scss +117 -0
- package/scss/config/_icons.scss +37 -0
- package/scss/config/_mixins.scss +13 -0
- package/scss/config/_theme.scss +216 -0
- package/scss/config/_tokens.scss +419 -0
- package/scss/config/mixins/_accordion.scss +52 -0
- package/scss/config/mixins/_ajax.scss +39 -0
- package/scss/config/mixins/_alert.scss +82 -0
- package/scss/config/mixins/_app-shell.scss +312 -0
- package/scss/config/mixins/_avatar.scss +109 -0
- package/scss/config/mixins/_borders.scss +36 -0
- package/scss/config/mixins/_breadcrumbs.scss +72 -0
- package/scss/config/mixins/_breakpoints.scss +62 -0
- package/scss/config/mixins/_btn.scss +179 -0
- package/scss/config/mixins/_card.scss +338 -0
- package/scss/config/mixins/_chip.scss +66 -0
- package/scss/config/mixins/_circular-progress.scss +71 -0
- package/scss/config/mixins/_collapsible.scss +24 -0
- package/scss/config/mixins/_colors.scss +46 -0
- package/scss/config/mixins/_confirm.scss +31 -0
- package/scss/config/mixins/_data-table.scss +346 -0
- package/scss/config/mixins/_display.scss +32 -0
- package/scss/config/mixins/_dropdown.scss +143 -0
- package/scss/config/mixins/_empty-state.scss +30 -0
- package/scss/config/mixins/_focus.scss +55 -0
- package/scss/config/mixins/_footer.scss +42 -0
- package/scss/config/mixins/_form.scss +601 -0
- package/scss/config/mixins/_index.scss +58 -0
- package/scss/config/mixins/_interaction.scss +15 -0
- package/scss/config/mixins/_kbd.scss +22 -0
- package/scss/config/mixins/_layout.scss +117 -0
- package/scss/config/mixins/_link.scss +55 -0
- package/scss/config/mixins/_ln-table.scss +420 -0
- package/scss/config/mixins/_loader.scss +26 -0
- package/scss/config/mixins/_modal.scss +66 -0
- package/scss/config/mixins/_motion.scss +19 -0
- package/scss/config/mixins/_nav.scss +273 -0
- package/scss/config/mixins/_page-header.scss +69 -0
- package/scss/config/mixins/_popover.scss +25 -0
- package/scss/config/mixins/_position.scss +32 -0
- package/scss/config/mixins/_progress.scss +56 -0
- package/scss/config/mixins/_prose.scss +127 -0
- package/scss/config/mixins/_shadows.scss +8 -0
- package/scss/config/mixins/_sidebar.scss +95 -0
- package/scss/config/mixins/_sizing.scss +6 -0
- package/scss/config/mixins/_spacing.scss +19 -0
- package/scss/config/mixins/_stat-card.scss +68 -0
- package/scss/config/mixins/_status-badge.scss +83 -0
- package/scss/config/mixins/_stepper.scss +78 -0
- package/scss/config/mixins/_table.scss +215 -0
- package/scss/config/mixins/_tabs.scss +64 -0
- package/scss/config/mixins/_timeline.scss +69 -0
- package/scss/config/mixins/_toast.scss +148 -0
- package/scss/config/mixins/_tooltip.scss +111 -0
- package/scss/config/mixins/_transitions.scss +10 -0
- package/scss/config/mixins/_translations.scss +124 -0
- package/scss/config/mixins/_typography.scss +57 -0
- package/scss/config/mixins/_upload.scss +168 -0
- package/scss/ln-ashlar.scss +62 -0
- package/scss/tabler-icons.txt +5039 -0
- package/scss/utilities/_animations.scss +83 -0
- 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
|
+
}
|