ab-nextjs-core 0.1.4 โ 0.1.6
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/CHANGELOG.md +20 -0
- package/README.md +48 -2
- package/ab-app-layout/index.tsx +81 -22
- package/ab-app-layout/styles.module.css +218 -1
- package/ab-aside-layout/index.tsx +76 -30
- package/ab-aside-layout/styles.module.css +54 -1
- package/ab-linear-progress/index.tsx +1 -1
- package/ab-main-layout/index.tsx +57 -23
- package/ab-main-layout/styles.module.css +43 -1
- package/ab-page-provider/index.tsx +144 -6
- package/ab-page-provider/scroll-registrar.tsx +71 -0
- package/ab-page-provider/slot.ts +51 -0
- package/ab-screen-layout/index.tsx +3 -3
- package/ab-screen-layout/styles.module.css +1 -1
- package/index.ts +1 -1
- package/package.json +4 -3
- package/server/ab-app-layout/index.tsx +56 -25
- package/server/ab-app-layout/styles.module.css +218 -1
- package/server/ab-aside-layout/index.tsx +62 -28
- package/server/ab-aside-layout/styles.module.css +43 -1
- package/server/ab-main-layout/index.tsx +59 -25
- package/server/ab-main-layout/styles.module.css +43 -1
- package/server/ab-screen-layout/index.tsx +3 -3
- package/server/ab-screen-layout/styles.module.css +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
### 0.1.6 (2026-10-16)
|
|
6
|
+
|
|
7
|
+
* ๐งญ **Docs-shell panes (G17):** `AbAppLayout` (client + server) gets `variant="panes"` and `railCollapsed`. Four breakpoints: mobile (bottom bar, aside as a full-screen sheet), tablet 768โ1023 (280px sidebar or 72px rail, overlay aside on the right, hidden by default), laptop 1024โ1439 (joined panes, hairline dividers), desktop โฅ 1440 (gapped panes with radii). Uses the theme 0.3.4 tokens with fallbacks. The default variant is unchanged
|
|
8
|
+
* ๐งท **Rail:** 200ms width transition, instant under reduced motion; stays expanded with JS off (`@media (scripting: none)`)
|
|
9
|
+
* ๐ **Aside toggles (useAbPage):** `toggleAside`, `toggleAsideOnMobile`, `asideId` (the aside's `id`), and **`useAbAsideToggle()`**, which picks the sheet or the pane for the viewport and returns `aria-expanded` / `aria-controls` / `onClick`. `Escape` closes the sheet
|
|
10
|
+
* ๐๏ธ **Aside slide:** `slideFromRight` (ab-nextjs-animations) at **240ms** (`asideDuration` default 240, was 300); under reduced motion no slide and instant open/close (`getAbAsideDuration`)
|
|
11
|
+
* ๐ **Layout rule (D35, G50โG52, G54):** the page never scrolls. In panes the app is a `100dvh` grid (`auto minmax(0,1fr) auto`, `overflow:hidden`) and `<html>`/`<body>` are locked while it is mounted (`:has()` plus a `data-ab-layout-lock` fallback, counted by `acquireAbLayoutLock()` so unmounting one of two layouts keeps it). Main, aside and sidebar each scroll on their own: `min-height:0`, `overscroll-behavior: contain`, `scrollbar-gutter: stable`, safe-area padding. The 64px `--ab-app-bar-height` offset and the sticky columns are gone
|
|
12
|
+
* ๐ซฅ **Empty chrome is 0px (G50):** null header/footer slots are not rendered, and `:empty` / `:has()` collapse the rest; strings are wrapped (`toAbSlot`) so text-only slots never collapse
|
|
13
|
+
* ๐ **New slots (G52):** `AbMainLayout` `mainHeader` / `mainBar`, `AbAsideLayout` `asideHeader` / `asideBar` (`data-ab-part="main-header"` โฆ). `header` / `footer` / `chrome` stay as deprecated aliases
|
|
14
|
+
* ๐ **Scrollers on useAbPage (G55):** `mainScrollElement` / `asideScrollElement` (content wrappers are `data-ab-part="main-content"` / `"aside-content"`), for hooks 0.1.7 `useAbScrollSpy({ root })`. The server `AbMainLayout` / `AbAsideLayout` set them too, through a tiny client island (`AbScrollRegistrar`, one hidden `<span>` as the scroller's first child) (Joseph)
|
|
15
|
+
* ๐งฑ **No nested headers:** in panes (or when the slot child is already a `<header>`) the header wrapper is a `<div>`; `overlays` drops the legacy layers (off by default in panes); the pane CSS also targets AbSidebar's `data-ab-part="rail"`
|
|
16
|
+
* ๐ **AbMainLayout / AbAsideLayout:** `id` prop, and `chrome={false}` drops their empty header/footer wrappers and backdrop/menus/dialogs/toasts/progress/canvas (default `true`, unchanged). Server `AbAsideLayout` sets `id` (default `ab-aside`)
|
|
17
|
+
* ๐ซ๏ธ **Sheet (James):** tablet backdrop (click closes), focus moves into the sheet and back to the opener, `Escape` skips events an inner menu already `preventDefault`ed, and one open/close timer so quick toggles never race
|
|
18
|
+
* ๐ง `typecheck` script; ๐ house `@authors` email in the code file headers
|
|
19
|
+
* Tests: per-breakpoint CSS, rail + reduced motion + JS off, aside 240ms / reduced motion, toggles and aria. Checked in WebKit at 390 / 768 / 1280 / 1600px
|
|
20
|
+
|
|
21
|
+
### 0.1.5 (2026-10-05)
|
|
22
|
+
|
|
23
|
+
* **Landmarks:** header / footer wrappers set `data-ab-part="header"` / `data-ab-part="footer"`
|
|
24
|
+
|
|
5
25
|
### 0.1.4 (2026-10-05)
|
|
6
26
|
|
|
7
27
|
* **Semantics:** layout wrappers use landmark tags โ `<header>` / `<footer>` (was `<div>`); `<main>` and `<aside>` unchanged; sidebar / bottom bar stay the consumer's `<nav>` (`AbSidebar` / `AbNavbar` in components)
|
package/README.md
CHANGED
|
@@ -56,13 +56,59 @@ Optional: wrap client `AbAsideLayout` with `AbPageProvider` / `useAbPage` for as
|
|
|
56
56
|
|:--------------|:---------|:---------------|
|
|
57
57
|
| `AbMainLayout` | `<main>` | `main` |
|
|
58
58
|
| `AbAsideLayout` | `<aside>` | `aside` |
|
|
59
|
-
| Header wrapper | `<header>` |
|
|
60
|
-
| Footer wrapper | `<footer>` |
|
|
59
|
+
| Header wrapper | `<header>` | `header` |
|
|
60
|
+
| Footer wrapper | `<footer>` | `footer` |
|
|
61
|
+
| Main slots (0.1.6) | `<header>` / `<footer>` | `main-header` / `main-bar`, scroller `main-content` |
|
|
62
|
+
| Aside slots (0.1.6) | `<header>` / `<footer>` | `aside-header` / `aside-bar`, scroller `aside-content` |
|
|
61
63
|
| Sidebar / bottom bar (via components) | `<nav>` (`AbSidebar` / `AbNavbar`) | `sidebar` (sidebar) |
|
|
62
64
|
|
|
63
65
|
The client aside opens with the `.slideFromRight` class from `ab-nextjs-animations` (import `ab-nextjs-animations/slide-from-right/styles.css`).
|
|
64
66
|
|
|
65
67
|
|
|
68
|
+
### Docs shell panes (0.1.6)
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
'use client'
|
|
72
|
+
import AbAppLayout from 'ab-nextjs-core/ab-app-layout'
|
|
73
|
+
import AbMainLayout from 'ab-nextjs-core/ab-main-layout'
|
|
74
|
+
import AbAsideLayout from 'ab-nextjs-core/ab-aside-layout'
|
|
75
|
+
import { AbPageProvider, useAbAsideToggle } from 'ab-nextjs-core/ab-page-provider'
|
|
76
|
+
|
|
77
|
+
<AbPageProvider>
|
|
78
|
+
<AbAppLayout
|
|
79
|
+
orientation="vertical"
|
|
80
|
+
variant="panes" // ๐งญ 4 breakpoints
|
|
81
|
+
railCollapsed={rail} // ๐งท icons-only sidebar (tablet+)
|
|
82
|
+
header={<AppBar />}
|
|
83
|
+
sideBar={<nav data-ab-part="sidebar" aria-label="Documentation">โฆ</nav>}
|
|
84
|
+
content={<><AbMainLayout โฆ /><AbAsideLayout โฆ /></>}
|
|
85
|
+
navBar={<nav data-ab-part="bottomBar" aria-label="Primary">โฆ</nav>}
|
|
86
|
+
/>
|
|
87
|
+
</AbPageProvider>
|
|
88
|
+
|
|
89
|
+
// ๐ one button for the TOC sheet (<1024px) and the ยป/ยซ pane toggle (โฅ1024px)
|
|
90
|
+
const { props, expanded } = useAbAsideToggle() // aria-expanded + aria-controls + onClick
|
|
91
|
+
<button {...props}>{expanded ? 'Hide page outline' : 'Show page outline'}</button>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Width | Sidebar | Main | Aside | Bottom bar |
|
|
95
|
+
|---|---|---|---|---|
|
|
96
|
+
| < 768 | hidden | full | full-screen sheet (`isAsideOpenOnMobile`) | shown (safe-area padding) |
|
|
97
|
+
| 768โ1023 | 280px, or the 72px rail | rest | overlay on the right, hidden by default | hidden |
|
|
98
|
+
| 1024โ1439 | joined, hairline divider | | in the flow, `isAsideOpen` | hidden |
|
|
99
|
+
| โฅ 1440 | gapped panes (`--ab-pane-gap`) with radii (`--ab-pane-radius`) | | | hidden |
|
|
100
|
+
|
|
101
|
+
* ๐ **The page never scrolls.** Panes are a `100dvh` box and `<html>`/`<body>` are locked while mounted. Main, aside and sidebar scroll on their own (`min-height:0`, `overscroll-behavior: contain`, `scrollbar-gutter: stable`, safe-area padding). Hand `useAbPage().mainScrollElement` to `useAbScrollSpy({ root })` (hooks 0.1.7). The client and the server layouts both set it (the server ones through a hidden client `AbScrollRegistrar`); wrap the app in `AbPageProvider`.
|
|
102
|
+
* ๐ฑ **Viewport meta for the app:** add `viewport-fit=cover` so `env(safe-area-inset-*)` is non-zero on notched phones (the panes pad the bars with it), and `interactive-widget=resizes-content` so the on-screen keyboard shrinks the `100dvh` box instead of sliding the locked page. In Next.js:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
export const viewport = { width: 'device-width', initialScale: 1, viewportFit: 'cover', interactiveWidget: 'resizes-content' }
|
|
106
|
+
```
|
|
107
|
+
* ๐ซฅ Empty header / footer slots render nothing and measure 0px. Use `mainHeader` / `mainBar` and `asideHeader` / `asideBar`; `header` / `footer` / `chrome` are deprecated aliases.
|
|
108
|
+
* The rail animates its width over 200ms and snaps under reduced motion. Without JS the sidebar stays expanded (`@media (scripting: none)`).
|
|
109
|
+
* The aside uses the `slideFromRight` keyframe from **ab-nextjs-animations** (load its `slide-from-right/styles.css`) at 240ms. Under reduced motion there's no slide and the open/close timers are 0. `Escape` closes the sheet.
|
|
110
|
+
* Tokens come from **ab-nextjs-theme โฅ 0.3.4** (`--ab-pane-gap`, `--ab-pane-radius`, `--ab-divider-hairline`), with fallbacks.
|
|
111
|
+
|
|
66
112
|
## Getting Started
|
|
67
113
|
|
|
68
114
|
### Installation
|
package/ab-app-layout/index.tsx
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* SOFTWARE.
|
|
25
25
|
*
|
|
26
26
|
* @project: ab-nextjs-core
|
|
27
|
-
* @authors: Abraham Ukachi <
|
|
27
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
/*
|
|
@@ -35,10 +35,32 @@
|
|
|
35
35
|
|
|
36
36
|
'use client';
|
|
37
37
|
|
|
38
|
-
import type
|
|
38
|
+
import { isValidElement, useEffect, type ReactElement, type ReactNode } from 'react';
|
|
39
39
|
import clsx from 'clsx';
|
|
40
40
|
import AbLinearProgress from '../ab-linear-progress';
|
|
41
41
|
import styles from './styles.module.css';
|
|
42
|
+
// ๐ซฅ G50: empty-slot helper
|
|
43
|
+
import { toAbSlot } from '../ab-page-provider/slot';
|
|
44
|
+
|
|
45
|
+
// ๐ข how many panes layouts are mounted (module scope: one document per bundle)
|
|
46
|
+
let abLayoutLocks = 0;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* ๐ acquireAbLayoutLock โ sets `data-ab-layout-lock` on `<html>` and returns the release function.
|
|
50
|
+
* Counted: the attribute only goes away when the LAST mounted panes layout releases (James nit).
|
|
51
|
+
* Releasing twice is a no-op.
|
|
52
|
+
*/
|
|
53
|
+
export function acquireAbLayoutLock(doc: Document = document): () => void {
|
|
54
|
+
abLayoutLocks += 1; // โ
|
|
55
|
+
doc.documentElement.setAttribute('data-ab-layout-lock', ''); // ๐
|
|
56
|
+
let released = false; // ๐ก๏ธ StrictMode double cleanups
|
|
57
|
+
return () => {
|
|
58
|
+
if (released) return;
|
|
59
|
+
released = true;
|
|
60
|
+
abLayoutLocks = Math.max(0, abLayoutLocks - 1); // โ
|
|
61
|
+
if (abLayoutLocks === 0) doc.documentElement.removeAttribute('data-ab-layout-lock'); // ๐ last one out
|
|
62
|
+
};
|
|
63
|
+
}
|
|
42
64
|
|
|
43
65
|
export interface AbAppLayoutProps {
|
|
44
66
|
orientation: 'vertical' | 'horizontal';
|
|
@@ -52,6 +74,13 @@ export interface AbAppLayoutProps {
|
|
|
52
74
|
footer?: ReactNode;
|
|
53
75
|
children?: ReactNode;
|
|
54
76
|
menus?: ReactNode;
|
|
77
|
+
/** ๐งญ 0.1.6 (G17): `'panes'` = the docs shell (4 breakpoints, rail, overlay aside). Default keeps the old layout. */
|
|
78
|
+
variant?: 'default' | 'panes';
|
|
79
|
+
/** ๐งท 0.1.6: collapse the sidebar to the icon rail (tablet and up, `variant="panes"`). */
|
|
80
|
+
railCollapsed?: boolean;
|
|
81
|
+
/** ๐งฐ 0.1.6: render the legacy #backdrop / #menus / #dialogs / #toasts / progress / canvas layers.
|
|
82
|
+
* Default: on for `variant="default"`, off for `"panes"` (the shell brings its own menus and dialogs). */
|
|
83
|
+
overlays?: boolean;
|
|
55
84
|
}
|
|
56
85
|
|
|
57
86
|
/** Client markup mirror of server AbAppLayout (slots only). */
|
|
@@ -67,39 +96,69 @@ const AbAppLayout = ({
|
|
|
67
96
|
footer,
|
|
68
97
|
children,
|
|
69
98
|
menus,
|
|
99
|
+
variant = 'default', // ๐งญ
|
|
100
|
+
railCollapsed = false, // ๐งท
|
|
101
|
+
overlays, // ๐งฐ
|
|
70
102
|
}: AbAppLayoutProps): ReactElement => {
|
|
103
|
+
// ๐งฑ panes: the header slot is a plain grid row (0px when empty). Its child (e.g. AbAppBar) brings its own
|
|
104
|
+
// <header>, so we never nest one header inside another. Same when the slot child IS a <header>.
|
|
105
|
+
const HeaderTag = variant === 'panes' || (isValidElement(header) && header.type === 'header') ? 'div' : 'header';
|
|
106
|
+
// ๐งฐ legacy overlay layers (off in panes unless asked for)
|
|
107
|
+
const showOverlays = overlays ?? variant !== 'panes';
|
|
108
|
+
// ๐ซฅ G50 (D37): panes never render an empty header / footer row (0px); the CSS :empty / :has() catch the rest
|
|
109
|
+
const top = toAbSlot(header);
|
|
110
|
+
const showHeader = variant !== 'panes' || top != null;
|
|
111
|
+
const showFooter = variant !== 'panes' || toAbSlot(footer) != null;
|
|
112
|
+
// ๐ G54: lock the document while a panes layout is mounted (CSS :has() does it too; this covers older engines)
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (variant !== 'panes' || typeof document === 'undefined') return;
|
|
115
|
+
return acquireAbLayoutLock(); // ๐ counted, so unmounting one of two layouts keeps the lock (James)
|
|
116
|
+
}, [variant]);
|
|
71
117
|
return (
|
|
72
118
|
<div
|
|
73
119
|
className={clsx(['AbAppLayout', styles.abAppLayout, className])}
|
|
74
120
|
hidden={hidden}
|
|
75
121
|
data-page-name={pageName}
|
|
76
122
|
data-orientation={orientation ?? ''}
|
|
123
|
+
data-variant={variant} // ๐งญ CSS hook for the panes
|
|
124
|
+
data-rail={railCollapsed ? 'true' : 'false'} // ๐งท
|
|
77
125
|
>
|
|
78
|
-
|
|
79
|
-
{header
|
|
80
|
-
|
|
126
|
+
{showHeader && (
|
|
127
|
+
<HeaderTag className={clsx(['HeaderWrapper', styles.abAppLayout__headerWrapper])} data-ab-part="header">
|
|
128
|
+
{variant === 'panes' ? top : header}
|
|
129
|
+
</HeaderTag>
|
|
130
|
+
)}
|
|
81
131
|
<div className={clsx(['ContentWrapper', styles.abAppLayout__contentWrapper])}>
|
|
82
132
|
{sideBar}
|
|
83
133
|
{content ?? children}
|
|
84
134
|
{navBar}
|
|
85
135
|
</div>
|
|
86
|
-
|
|
87
|
-
{footer
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
136
|
+
{showFooter && (
|
|
137
|
+
<footer className={clsx(['FooterWrapper', styles.abAppLayout__footerWrapper])} data-ab-part="footer">
|
|
138
|
+
{footer}
|
|
139
|
+
</footer>
|
|
140
|
+
)}
|
|
141
|
+
{/* ๐งฐ legacy layers (ids kept for old apps) */}
|
|
142
|
+
{showOverlays && (
|
|
143
|
+
<>
|
|
144
|
+
<div id="backdrop" className={clsx(['Backdrop', styles.abAppLayout__backdrop])} hidden />
|
|
145
|
+
<div id="menus" className={clsx(['Menus', styles.abAppLayout__menus])} hidden>
|
|
146
|
+
{menus}
|
|
147
|
+
</div>
|
|
148
|
+
<div id="dialogs" className={clsx(['Dialogs', styles.abAppLayout__dialogs])} hidden />
|
|
149
|
+
<div id="toasts" className={clsx(['Toasts', styles.abAppLayout__toasts])} hidden />
|
|
150
|
+
<AbLinearProgress
|
|
151
|
+
className="absolute top-0 left-0 right-0 z-50 !h-0.5 lg:!h-1"
|
|
152
|
+
color="secondary"
|
|
153
|
+
hidden={true}
|
|
154
|
+
isIndeterminate={true}
|
|
155
|
+
progress={25}
|
|
156
|
+
/>
|
|
157
|
+
<canvas className={clsx(['Canvas', styles.abAppLayout__canvas])} />
|
|
158
|
+
</>
|
|
159
|
+
)}
|
|
160
|
+
{/* ๐ panes: still render menus when the app passes some */}
|
|
161
|
+
{!showOverlays && menus != null && <div className={clsx(['Menus', styles.abAppLayout__menus])}>{menus}</div>}
|
|
103
162
|
</div>
|
|
104
163
|
);
|
|
105
164
|
};
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* @name: AB App Layout - Component Stylesheet (plain CSS, Tailwind-independent)
|
|
28
28
|
* @file: ab-app-layout/styles.module.css
|
|
29
29
|
* @type: CSS Module
|
|
30
|
-
* @authors: Abraham Ukachi <
|
|
30
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
/*
|
|
@@ -143,3 +143,220 @@
|
|
|
143
143
|
from { opacity: 0; }
|
|
144
144
|
to { opacity: 1; }
|
|
145
145
|
}
|
|
146
|
+
|
|
147
|
+
/* ================================================================== */
|
|
148
|
+
/* ๐งญ 0.1.6 (G17 + D34/D35 layout rule): docs-shell panes */
|
|
149
|
+
/* opt in with variant="panes". Children of the content wrapper */
|
|
150
|
+
/* are matched by data-ab-part: */
|
|
151
|
+
/* sidebar | rail (nav) ยท main ยท aside ยท bottomBar (nav) */
|
|
152
|
+
/* ๐ฑ <768 ยท ๐ 768โ1023 ยท ๐ป 1024โ1439 joined ยท ๐ฅ๏ธ โฅ1440 gapped */
|
|
153
|
+
/* */
|
|
154
|
+
/* ๐ THE LAYOUT RULE (God, D34): */
|
|
155
|
+
/* ยท the app is ONE 100dvh box and the DOCUMENT NEVER SCROLLS */
|
|
156
|
+
/* ยท header / footer rows are 0px (not rendered / :empty) when empty*/
|
|
157
|
+
/* ยท sidebar, main content and aside content each scroll on their */
|
|
158
|
+
/* own (min-height:0 + overflow:auto + overscroll containment) */
|
|
159
|
+
/* ยท no hard-coded bar heights: rows size to their content */
|
|
160
|
+
/* Tokens: --ab-pane-gap / --ab-pane-radius / --ab-divider-hairline */
|
|
161
|
+
/* (ab-nextjs-theme โฅ 0.3.4), with safe fallbacks */
|
|
162
|
+
/* ================================================================== */
|
|
163
|
+
|
|
164
|
+
/* ๐ G54: lock the document while a panes layout is on the page (CSS-only via :has, so the server twin
|
|
165
|
+
* works without JS; the client twin also sets html[data-ab-layout-lock] for browsers without :has) */
|
|
166
|
+
:global(html:has(.AbAppLayout[data-variant="panes"])),
|
|
167
|
+
:global(html:has(.AbAppLayout[data-variant="panes"])) :global(body),
|
|
168
|
+
:global(html[data-ab-layout-lock]),
|
|
169
|
+
:global(html[data-ab-layout-lock]) :global(body) {
|
|
170
|
+
height: 100%; /* ๐ */
|
|
171
|
+
overflow: hidden; /* ๐ซ the page itself never scrolls */
|
|
172
|
+
overscroll-behavior: none; /* ๐ซ no iOS rubber band pulling the whole app */
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.abAppLayout[data-variant="panes"] {
|
|
176
|
+
--ab-sidebar-width: 280px; /* ๐๏ธ expanded sidebar */
|
|
177
|
+
--ab-rail-width: 72px; /* ๐งท collapsed rail (icons only) */
|
|
178
|
+
--ab-aside-width: min(40vw, 360px); /* ๐ "On this page" */
|
|
179
|
+
--ab-rail-duration: 200ms; /* ๐๏ธ rail collapse (brief ยง4) */
|
|
180
|
+
display: grid; /* ๐งฑ rows: header ยท panes ยท footer */
|
|
181
|
+
grid-template-rows: auto minmax(0, 1fr) auto; /* ๐ empty header/footer rows are 0px */
|
|
182
|
+
grid-template-columns: minmax(0, 1fr); /* ๐ซ no horizontal overflow */
|
|
183
|
+
width: 100%; /* โ๏ธ */
|
|
184
|
+
height: 100vh; /* ๐ฐ๏ธ fallback */
|
|
185
|
+
height: 100dvh; /* ๐ฑ the dynamic viewport (mobile toolbars) */
|
|
186
|
+
min-height: 0; /* ๐ซ */
|
|
187
|
+
overflow: hidden; /* ๐ the app box never scrolls */
|
|
188
|
+
overscroll-behavior: contain; /* ๐ซ scroll chaining */
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* ๐๐ฆถ app header / footer: plain grid rows in the flow, 0px + invisible when empty (G50) */
|
|
192
|
+
.abAppLayout[data-variant="panes"] > .abAppLayout__headerWrapper,
|
|
193
|
+
.abAppLayout[data-variant="panes"] > .abAppLayout__footerWrapper {
|
|
194
|
+
position: relative; /* ๐ in the flow (no overlay) */
|
|
195
|
+
inset: auto; /* ๐งน the legacy absolute overlay */
|
|
196
|
+
z-index: 25; /* โฌ๏ธ above the panes, below the sheet (30) */
|
|
197
|
+
pointer-events: auto; /* ๐ฑ๏ธ the legacy wrapper disabled them */
|
|
198
|
+
min-width: 0; /* ๐ซ overflow */
|
|
199
|
+
padding-inline: env(safe-area-inset-left, 0px) env(safe-area-inset-right, 0px); /* ๐ฑ notch */
|
|
200
|
+
}
|
|
201
|
+
.abAppLayout[data-variant="panes"] > .abAppLayout__headerWrapper { grid-row: 1; padding-top: env(safe-area-inset-top, 0px); } /* ๐ row 1 ยท ๐ฑ */
|
|
202
|
+
/* ๐ WebKit e2e (2026-10-20): pin the panes to the 1fr row. Auto-placement moved them into row 1 (auto) when
|
|
203
|
+
the header was absent or collapsed, and the panes measured 0px tall */
|
|
204
|
+
.abAppLayout[data-variant="panes"] > .abAppLayout__contentWrapper { grid-row: 2; } /* ๐งฑ the minmax(0,1fr) row */
|
|
205
|
+
.abAppLayout[data-variant="panes"] > .abAppLayout__footerWrapper { grid-row: 3; padding-bottom: env(safe-area-inset-bottom, 0px); } /* ๐ฑ */
|
|
206
|
+
/* ๐ซฅ D37: not rendered when the prop is null; :empty / :has() collapse what still reaches the DOM */
|
|
207
|
+
.abAppLayout[data-variant="panes"] > .abAppLayout__headerWrapper:empty,
|
|
208
|
+
.abAppLayout[data-variant="panes"] > .abAppLayout__footerWrapper:empty,
|
|
209
|
+
.abAppLayout[data-variant="panes"] > .abAppLayout__headerWrapper:not(:has(> *)),
|
|
210
|
+
.abAppLayout[data-variant="panes"] > .abAppLayout__footerWrapper:not(:has(> *)) {
|
|
211
|
+
display: none; /* ๐ 0px */
|
|
212
|
+
}
|
|
213
|
+
/* ๐ menus the app passes: no full-screen click shield */
|
|
214
|
+
.abAppLayout[data-variant="panes"] > .abAppLayout__menus {
|
|
215
|
+
display: contents; /* ๐ซฅ */
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* ๐งฑ the panes row: a bounded box (the positioning context for sheets and future drawers) */
|
|
219
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper {
|
|
220
|
+
position: relative; /* ๐ sheets / drawers are scoped to it */
|
|
221
|
+
flex-direction: column; /* ๐ฑ stack */
|
|
222
|
+
height: 100%; /* ๐ the 1fr row */
|
|
223
|
+
min-height: 0; /* ๐ซ lets the children shrink and scroll */
|
|
224
|
+
min-width: 0; /* ๐ซ overflow */
|
|
225
|
+
overflow: hidden; /* ๐ */
|
|
226
|
+
justify-content: flex-start; /* โฌ๏ธ */
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* ๐ every pane: bounded, scrolls on its own, never chains to the page (G51 + G54) */
|
|
230
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > :is([data-ab-part="sidebar"], [data-ab-part="rail"], [data-ab-part="main"], [data-ab-part="aside"]) {
|
|
231
|
+
min-height: 0; /* ๐ซ */
|
|
232
|
+
max-height: none; /* ๐งน legacy 100vh caps */
|
|
233
|
+
box-sizing: border-box; /* ๐ฆ */
|
|
234
|
+
overscroll-behavior: contain; /* ๐ซ scroll chaining */
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* ๐ฑ mobile: one column, no sidebar, bottom bar visible, aside only as a full-pane sheet */
|
|
238
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > :is([data-ab-part="sidebar"], [data-ab-part="rail"]) {
|
|
239
|
+
display: none; /* ๐ the bottom bar takes over */
|
|
240
|
+
}
|
|
241
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="main"] {
|
|
242
|
+
flex: 1 1 0; /* โ๏ธโ๏ธ the rest of the box */
|
|
243
|
+
min-width: 0; /* ๐ซ overflow */
|
|
244
|
+
height: auto; /* ๐ the flex item size */
|
|
245
|
+
overflow: hidden; /* ๐ its content wrapper is the scroller */
|
|
246
|
+
}
|
|
247
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="aside"] {
|
|
248
|
+
position: absolute; /* ๐ sheet over main, INSIDE the app box */
|
|
249
|
+
inset: 0; /* ๐งฑ 100% (TOC button) */
|
|
250
|
+
width: 100%;
|
|
251
|
+
height: auto; /* ๐ from inset */
|
|
252
|
+
z-index: 30; /* โฌ๏ธ above main + header, below menus/dialogs (50) */
|
|
253
|
+
overflow: hidden; /* ๐ its content wrapper scrolls */
|
|
254
|
+
}
|
|
255
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="aside"]:not([data-open-on-mobile="true"]) {
|
|
256
|
+
display: none; /* ๐ closed by default on mobile */
|
|
257
|
+
}
|
|
258
|
+
/* ๐ซ๏ธ sheet backdrop (rendered by AbAsideLayout): only while the sheet is open */
|
|
259
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="aside-backdrop"] {
|
|
260
|
+
display: none; /* ๐ */
|
|
261
|
+
}
|
|
262
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="bottomBar"] {
|
|
263
|
+
flex: none; /* ๐ its own height: a row at the bottom of the box (no sticky needed) */
|
|
264
|
+
padding-bottom: env(safe-area-inset-bottom, 0px); /* ๐ฑ home indicator */
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* ๐ tablet: sidebar (or rail) + main; the aside overlays on the right over a backdrop; no bottom bar */
|
|
268
|
+
@media (min-width: 768px) {
|
|
269
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper {
|
|
270
|
+
flex-direction: row; /* โก๏ธ */
|
|
271
|
+
align-items: stretch; /* ๐ every pane is full height */
|
|
272
|
+
}
|
|
273
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > :is([data-ab-part="sidebar"], [data-ab-part="rail"]) {
|
|
274
|
+
display: flex; /* ๐ */
|
|
275
|
+
flex: none;
|
|
276
|
+
width: var(--ab-sidebar-width); /* ๐๏ธ */
|
|
277
|
+
max-width: none; /* ๐งน the component's own cap */
|
|
278
|
+
height: auto; /* ๐ stretched by the row */
|
|
279
|
+
overflow: hidden auto; /* ๐ D42: the sidebar is the third scroller */
|
|
280
|
+
scrollbar-gutter: stable; /* ๐ no jump when it starts scrolling */
|
|
281
|
+
transition: width var(--ab-rail-duration) ease; /* ๐๏ธ rail */
|
|
282
|
+
}
|
|
283
|
+
.abAppLayout[data-variant="panes"][data-rail="true"] .abAppLayout__contentWrapper > :is([data-ab-part="sidebar"], [data-ab-part="rail"]),
|
|
284
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="rail"] {
|
|
285
|
+
width: var(--ab-rail-width); /* ๐งท rail (layout flag or AbSidebar's own rail markup) */
|
|
286
|
+
}
|
|
287
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="bottomBar"] {
|
|
288
|
+
display: none; /* ๐ sidebar replaces it */
|
|
289
|
+
}
|
|
290
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="aside"] {
|
|
291
|
+
inset: 0 0 0 auto; /* โก๏ธ pinned right, inside the panes box */
|
|
292
|
+
width: var(--ab-aside-width); /* ๐ ~30โ40% */
|
|
293
|
+
border-left: var(--ab-divider-hairline, 1px solid rgb(0 0 0 / 0.12)); /* โ */
|
|
294
|
+
background: var(--ab-color-surface, Canvas); /* ๐จ opaque over the page */
|
|
295
|
+
}
|
|
296
|
+
/* ๐ซ๏ธ tablet overlay backdrop (James review): dims the panes, click closes the sheet */
|
|
297
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="aside-backdrop"][data-open="true"] {
|
|
298
|
+
display: block; /* ๐ */
|
|
299
|
+
position: absolute; /* ๐ inside the panes box */
|
|
300
|
+
inset: 0; /* ๐งฑ */
|
|
301
|
+
z-index: 29; /* โฌ๏ธ just under the sheet */
|
|
302
|
+
background: rgb(0 0 0 / 0.32); /* ๐ซ๏ธ scrim */
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* ๐ป laptop: three joined panes with hairline dividers, aside in the flow */
|
|
307
|
+
@media (min-width: 1024px) {
|
|
308
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > :is([data-ab-part="sidebar"], [data-ab-part="rail"]) {
|
|
309
|
+
border-right: var(--ab-divider-hairline, 1px solid rgb(0 0 0 / 0.12)); /* โ joined */
|
|
310
|
+
}
|
|
311
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="aside"] {
|
|
312
|
+
position: relative; /* ๐ back in the flow, a full-height column */
|
|
313
|
+
inset: auto; /* ๐งน the tablet overlay */
|
|
314
|
+
height: auto; /* ๐ stretched by the row */
|
|
315
|
+
flex: none;
|
|
316
|
+
z-index: auto;
|
|
317
|
+
background: none; /* ๐จ */
|
|
318
|
+
}
|
|
319
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="aside"][data-opened="true"] {
|
|
320
|
+
display: flex; /* ๐ laptop+ follows data-opened (isAsideOpen), not the small-screen flag (same specificity as the :not() rule, later wins) */
|
|
321
|
+
}
|
|
322
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="aside"]:not([data-opened="true"]) {
|
|
323
|
+
display: none; /* ๐ closed with the ยป/ยซ toggle */
|
|
324
|
+
}
|
|
325
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="aside-backdrop"] {
|
|
326
|
+
display: none !important; /* ๐ no sheet on laptop+ */
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* ๐ฅ๏ธ desktop: gapped panes with radii, no dividers */
|
|
331
|
+
@media (min-width: 1440px) {
|
|
332
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper {
|
|
333
|
+
gap: var(--ab-pane-gap, 12px); /* ๐งฑ */
|
|
334
|
+
padding: var(--ab-pane-gap, 12px); /* ๐ผ๏ธ outer gap */
|
|
335
|
+
box-sizing: border-box;
|
|
336
|
+
}
|
|
337
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > :is([data-ab-part="sidebar"], [data-ab-part="rail"]),
|
|
338
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="main"],
|
|
339
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > [data-ab-part="aside"] {
|
|
340
|
+
border: 0; /* ๐ซ dividers */
|
|
341
|
+
border-radius: var(--ab-pane-radius, 16px); /* ๐ฒ */
|
|
342
|
+
overflow: hidden; /* โ๏ธ radius */
|
|
343
|
+
margin: 0; /* ๐งน legacy 2xl margins */
|
|
344
|
+
}
|
|
345
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > :is([data-ab-part="sidebar"], [data-ab-part="rail"]) {
|
|
346
|
+
overflow: hidden auto; /* ๐ the sidebar still scrolls (radius kept) */
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* โฟ reduced motion: the rail snaps instead of animating */
|
|
351
|
+
@media (prefers-reduced-motion: reduce) {
|
|
352
|
+
.abAppLayout[data-variant="panes"] .abAppLayout__contentWrapper > :is([data-ab-part="sidebar"], [data-ab-part="rail"]) {
|
|
353
|
+
transition: none;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* ๐ซ JS off: nothing can toggle the rail, so the sidebar stays expanded (gap-review, Joseph) */
|
|
358
|
+
@media (scripting: none) {
|
|
359
|
+
.abAppLayout[data-variant="panes"][data-rail="true"] .abAppLayout__contentWrapper > :is([data-ab-part="sidebar"], [data-ab-part="rail"]) {
|
|
360
|
+
width: var(--ab-sidebar-width);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* SOFTWARE.
|
|
25
25
|
*
|
|
26
26
|
* @project: ab-nextjs-core
|
|
27
|
-
* @authors: Abraham Ukachi <
|
|
27
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
/*
|
|
@@ -41,18 +41,31 @@ import clsx from 'clsx';
|
|
|
41
41
|
import { useAbPage } from '../ab-page-provider';
|
|
42
42
|
import AbLinearProgress from '../ab-linear-progress';
|
|
43
43
|
import styles from './styles.module.css';
|
|
44
|
+
// ๐ซฅ G52: empty-slot helper (strings get a <span>, so the :has() collapse never hides text)
|
|
45
|
+
import { toAbSlot } from '../ab-page-provider/slot';
|
|
44
46
|
|
|
45
47
|
export interface AbAsideLayoutProps {
|
|
46
48
|
orientation: 'vertical' | 'horizontal';
|
|
47
49
|
className?: string;
|
|
48
50
|
hidden?: boolean;
|
|
49
51
|
pageName?: string;
|
|
52
|
+
/** ๐ 0.1.6 (G52): the aside pane's header slot (data-ab-part="aside-header"); 0px and not rendered when empty. */
|
|
53
|
+
asideHeader?: ReactNode;
|
|
54
|
+
/** ๐ 0.1.6 (G52): the aside pane's bottom bar slot (data-ab-part="aside-bar"); 0px and not rendered when empty. */
|
|
55
|
+
asideBar?: ReactNode;
|
|
56
|
+
/** @deprecated 0.1.6: use `asideHeader` (kept as an alias for one minor). */
|
|
50
57
|
header?: ReactNode;
|
|
51
58
|
content?: ReactNode;
|
|
59
|
+
/** @deprecated 0.1.6: use `asideBar` (kept as an alias for one minor). */
|
|
52
60
|
footer?: ReactNode;
|
|
53
61
|
children?: ReactNode;
|
|
54
62
|
dividerHidden?: boolean;
|
|
55
63
|
menus?: ReactNode;
|
|
64
|
+
/** ๐ 0.1.6: element id (e.g. the skip-link target), no inner wrapper needed. */
|
|
65
|
+
id?: string;
|
|
66
|
+
/** @deprecated 0.1.6: `false` drops the legacy backdrop / menus / dialogs / toasts / progress / canvas layers.
|
|
67
|
+
* Empty header / bar slots are never rendered any more, so this only gates those layers (default `true`). */
|
|
68
|
+
chrome?: boolean;
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
/**
|
|
@@ -64,12 +77,16 @@ const AbAsideLayout = ({
|
|
|
64
77
|
className,
|
|
65
78
|
hidden,
|
|
66
79
|
pageName,
|
|
67
|
-
|
|
80
|
+
asideHeader,
|
|
81
|
+
asideBar,
|
|
82
|
+
header, // ๐ฐ๏ธ deprecated alias
|
|
68
83
|
content,
|
|
69
|
-
footer,
|
|
84
|
+
footer, // ๐ฐ๏ธ deprecated alias
|
|
70
85
|
children,
|
|
71
86
|
dividerHidden,
|
|
72
87
|
menus,
|
|
88
|
+
id, // ๐
|
|
89
|
+
chrome = true, // ๐งฐ
|
|
73
90
|
}: AbAsideLayoutProps): ReactElement => {
|
|
74
91
|
const {
|
|
75
92
|
isAsideOpen,
|
|
@@ -80,6 +97,8 @@ const AbAsideLayout = ({
|
|
|
80
97
|
isAsideOpening,
|
|
81
98
|
isAsideClosing,
|
|
82
99
|
isAsideLoading,
|
|
100
|
+
asideId,
|
|
101
|
+
setAsideOpenOnMobile,
|
|
83
102
|
} = useAbPage();
|
|
84
103
|
|
|
85
104
|
const asideRef = useRef<HTMLElement | null>(null);
|
|
@@ -129,15 +148,31 @@ const AbAsideLayout = ({
|
|
|
129
148
|
asideDuration,
|
|
130
149
|
]);
|
|
131
150
|
|
|
151
|
+
// ๐๐ G52: the new slot names win, the 0.1.5 names still work
|
|
152
|
+
const top = toAbSlot(asideHeader ?? header);
|
|
153
|
+
const bar = toAbSlot(asideBar ?? footer);
|
|
154
|
+
// ๐ G55: publish this pane's scroller (the content wrapper) so scroll spies / TOCs can use it as `root`
|
|
155
|
+
const { setAsideScrollElement } = useAbPage();
|
|
132
156
|
return (
|
|
157
|
+
<>
|
|
158
|
+
{/* ๐ซ๏ธ 0.1.6 (James review): tablet/mobile sheet backdrop, a sibling so AbAppLayout's pane CSS shows it; click closes */}
|
|
159
|
+
<div
|
|
160
|
+
data-ab-part="aside-backdrop"
|
|
161
|
+
data-open={isAsideOpenOnMobile ? 'true' : 'false'}
|
|
162
|
+
aria-hidden="true"
|
|
163
|
+
onClick={() => setAsideOpenOnMobile(false)}
|
|
164
|
+
hidden={!isAsideOpenOnMobile}
|
|
165
|
+
/>
|
|
133
166
|
<aside
|
|
134
167
|
ref={asideRef}
|
|
168
|
+
id={id ?? asideId} // ๐ 0.1.6: target of aria-controls (useAbAsideToggle)
|
|
135
169
|
className={clsx(
|
|
136
170
|
['AbAsideLayout', 'group/aside-layout', styles.abAsideLayout, className],
|
|
137
171
|
{ 'is-open': isAsideOpen },
|
|
138
|
-
{ 'slideFromRight': isAsideOpen },
|
|
172
|
+
{ 'slideFromRight': isAsideOpen || isAsideOpenOnMobile }, // ๐๏ธ ab-nextjs-animations keyframe (240ms here, none under reduced motion)
|
|
139
173
|
{ 'is-open-on-mobile': isAsideOpenOnMobile },
|
|
140
|
-
|
|
174
|
+
// ๐ 0.1.6: stay mounted-visible while the mobile/tablet sheet is open, even if the laptop pane is closed
|
|
175
|
+
{ '!hidden': !isAsideOpen && !isAsideOpenOnMobile && !isAsideOpening && !isAsideClosing },
|
|
141
176
|
)}
|
|
142
177
|
hidden={hidden}
|
|
143
178
|
data-page-name={pageName}
|
|
@@ -145,9 +180,11 @@ const AbAsideLayout = ({
|
|
|
145
180
|
data-opened={isAsideOpen}
|
|
146
181
|
data-open-on-mobile={isAsideOpenOnMobile}
|
|
147
182
|
>
|
|
148
|
-
|
|
149
|
-
{header
|
|
150
|
-
|
|
183
|
+
{top != null && ( // ๐ซฅ not rendered when empty (D37)
|
|
184
|
+
<header className={clsx(['HeaderWrapper', styles.abAsideLayout__headerWrapper])} data-ab-part="aside-header">
|
|
185
|
+
{top}
|
|
186
|
+
</header>
|
|
187
|
+
)}
|
|
151
188
|
|
|
152
189
|
<div
|
|
153
190
|
className={clsx([
|
|
@@ -155,35 +192,44 @@ const AbAsideLayout = ({
|
|
|
155
192
|
styles.abAsideLayout__contentWrapper,
|
|
156
193
|
orientation === 'horizontal' && '!flex-row',
|
|
157
194
|
])}
|
|
195
|
+
data-ab-part="aside-content" // ๐ G51/G55: THE scroller of this pane (useAbScrollSpy root)
|
|
196
|
+
ref={setAsideScrollElement} // ๐ G55: useAbPage().asideScrollElement
|
|
158
197
|
>
|
|
159
198
|
{content ?? children}
|
|
160
199
|
</div>
|
|
161
200
|
|
|
162
|
-
|
|
163
|
-
{
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
<div className={clsx(['Backdrop', styles.abAsideLayout__backdrop])} hidden />
|
|
167
|
-
<div className={clsx(['Menus', styles.abAsideLayout__menus])} hidden>
|
|
168
|
-
{menus}
|
|
169
|
-
</div>
|
|
170
|
-
<div className={clsx(['Dialogs', styles.abAsideLayout__dialogs])} hidden />
|
|
171
|
-
<div className={clsx(['Toasts', styles.abAsideLayout__toasts])} hidden />
|
|
172
|
-
|
|
173
|
-
{!dividerHidden && (
|
|
174
|
-
<span className={clsx(['divider', 'vertical', 'left', styles.abAsideLayout__divider])} />
|
|
201
|
+
{bar != null && ( // ๐ซฅ not rendered when empty (D37)
|
|
202
|
+
<footer className={clsx(['FooterWrapper', styles.abAsideLayout__footerWrapper])} data-ab-part="aside-bar">
|
|
203
|
+
{bar}
|
|
204
|
+
</footer>
|
|
175
205
|
)}
|
|
176
206
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
207
|
+
{chrome && ( // ๐งฐ 0.1.6: legacy layers
|
|
208
|
+
<>
|
|
209
|
+
<div className={clsx(['Backdrop', styles.abAsideLayout__backdrop])} hidden />
|
|
210
|
+
<div className={clsx(['Menus', styles.abAsideLayout__menus])} hidden>
|
|
211
|
+
{menus}
|
|
212
|
+
</div>
|
|
213
|
+
<div className={clsx(['Dialogs', styles.abAsideLayout__dialogs])} hidden />
|
|
214
|
+
<div className={clsx(['Toasts', styles.abAsideLayout__toasts])} hidden />
|
|
215
|
+
|
|
216
|
+
{!dividerHidden && (
|
|
217
|
+
<span className={clsx(['divider', 'vertical', 'left', styles.abAsideLayout__divider])} />
|
|
218
|
+
)}
|
|
219
|
+
|
|
220
|
+
<AbLinearProgress
|
|
221
|
+
className="absolute top-0 left-0 right-0 z-50 !h-0.5 lg:!h-1"
|
|
222
|
+
color="secondary"
|
|
223
|
+
hidden={!isAsideLoading}
|
|
224
|
+
isIndeterminate={true}
|
|
225
|
+
progress={25}
|
|
226
|
+
/>
|
|
227
|
+
|
|
228
|
+
<canvas className={clsx(['Canvas', styles.abAsideLayout__canvas])} />
|
|
229
|
+
</>
|
|
230
|
+
)}
|
|
186
231
|
</aside>
|
|
232
|
+
</>
|
|
187
233
|
);
|
|
188
234
|
};
|
|
189
235
|
|