@djangocfg/layouts 2.1.613 → 2.1.616
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/package.json +12 -13
- package/src/index.ts +0 -4
- package/src/layouts/AppLayout/BaseApp.tsx +7 -8
- package/src/layouts/AuthLayout/styles/auth.css +4 -2
- package/src/layouts/PrivateLayout/README.md +1 -1
- package/src/layouts/PrivateLayout/components/PrivateContent.test.tsx +10 -71
- package/src/layouts/PrivateLayout/components/PrivateContent.tsx +5 -20
- package/src/layouts/types/layout.types.ts +16 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.616",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -91,11 +91,11 @@
|
|
|
91
91
|
"test": "vitest run"
|
|
92
92
|
},
|
|
93
93
|
"peerDependencies": {
|
|
94
|
-
"@djangocfg/analytics": "^2.1.
|
|
95
|
-
"@djangocfg/api": "^2.1.
|
|
96
|
-
"@djangocfg/devtools": "^2.1.
|
|
97
|
-
"@djangocfg/i18n": "^2.1.
|
|
98
|
-
"@djangocfg/ui-core": "^2.1.
|
|
94
|
+
"@djangocfg/analytics": "^2.1.616",
|
|
95
|
+
"@djangocfg/api": "^2.1.616",
|
|
96
|
+
"@djangocfg/devtools": "^2.1.616",
|
|
97
|
+
"@djangocfg/i18n": "^2.1.616",
|
|
98
|
+
"@djangocfg/ui-core": "^2.1.616",
|
|
99
99
|
"@hookform/resolvers": "^5.2.2 || ^7.0.2",
|
|
100
100
|
"consola": "^3.4.2",
|
|
101
101
|
"lucide-react": "^1.68.0",
|
|
@@ -120,16 +120,15 @@
|
|
|
120
120
|
},
|
|
121
121
|
"dependencies": {
|
|
122
122
|
"libphonenumber-js": "^1.13.15",
|
|
123
|
-
"nextjs-toploader": "^3.9.17",
|
|
124
123
|
"qrcode.react": "^4.2.0"
|
|
125
124
|
},
|
|
126
125
|
"devDependencies": {
|
|
127
|
-
"@djangocfg/analytics": "^2.1.
|
|
128
|
-
"@djangocfg/api": "^2.1.
|
|
129
|
-
"@djangocfg/devtools": "^2.1.
|
|
130
|
-
"@djangocfg/i18n": "^2.1.
|
|
131
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
132
|
-
"@djangocfg/ui-core": "^2.1.
|
|
126
|
+
"@djangocfg/analytics": "^2.1.616",
|
|
127
|
+
"@djangocfg/api": "^2.1.616",
|
|
128
|
+
"@djangocfg/devtools": "^2.1.616",
|
|
129
|
+
"@djangocfg/i18n": "^2.1.616",
|
|
130
|
+
"@djangocfg/typescript-config": "^2.1.616",
|
|
131
|
+
"@djangocfg/ui-core": "^2.1.616",
|
|
133
132
|
"@storybook/react-vite": "^10.6.5",
|
|
134
133
|
"@types/node": "^25.9.5",
|
|
135
134
|
"@types/react": "19.3.0",
|
package/src/index.ts
CHANGED
|
@@ -35,10 +35,6 @@ export * from './layouts';
|
|
|
35
35
|
// Typed theme style bridge + presets (BaseApp `theme.style`)
|
|
36
36
|
export * from './theme';
|
|
37
37
|
|
|
38
|
-
// Re-export useRouter from nextjs-toploader for progress bar support
|
|
39
|
-
// Use this instead of 'next/navigation' useRouter for router.push() to trigger progress
|
|
40
|
-
export { useRouter } from 'nextjs-toploader/app';
|
|
41
|
-
|
|
42
38
|
// Snippets - Reusable UI components (includes Analytics)
|
|
43
39
|
export * from './snippets';
|
|
44
40
|
|
|
@@ -43,7 +43,6 @@
|
|
|
43
43
|
'use client';
|
|
44
44
|
|
|
45
45
|
import { nodeEnv } from "@djangocfg/ui-core/lib";
|
|
46
|
-
import NextTopLoader from 'nextjs-toploader';
|
|
47
46
|
import { SWRConfig } from 'swr';
|
|
48
47
|
|
|
49
48
|
import { Devtools, devtools } from '@djangocfg/devtools';
|
|
@@ -52,6 +51,7 @@ import { AuthProvider } from '@djangocfg/api/auth';
|
|
|
52
51
|
import { UiProviders } from '@djangocfg/ui-core/providers';
|
|
53
52
|
import { useScrollToTop } from '@djangocfg/ui-core/hooks';
|
|
54
53
|
import { NextRouterAdapter, NextLinkProvider } from '@djangocfg/ui-core/adapters/nextjs';
|
|
54
|
+
import { RouteProgressProvider } from '@djangocfg/ui-core/route-progress';
|
|
55
55
|
import { NextIntlLinkBridge } from './NextIntlLinkBridge';
|
|
56
56
|
import { ThemeProvider } from '@djangocfg/ui-core/theme';
|
|
57
57
|
import { AppErrorFallback } from '../../components/errors/AppErrorFallback';
|
|
@@ -107,6 +107,7 @@ export function BaseApp({
|
|
|
107
107
|
devtools: devtoolsConfig,
|
|
108
108
|
i18n,
|
|
109
109
|
pwaMeta = true,
|
|
110
|
+
progressBar = true,
|
|
110
111
|
}: BaseAppProps) {
|
|
111
112
|
// auth=false disables AuthProvider entirely (public apps with no login)
|
|
112
113
|
const authEnabled = auth !== false;
|
|
@@ -138,6 +139,10 @@ export function BaseApp({
|
|
|
138
139
|
* correctly. Placed near the top so every provider below
|
|
139
140
|
* (Auth, anything that may navigate) gets it.
|
|
140
141
|
*/}
|
|
142
|
+
<RouteProgressProvider
|
|
143
|
+
enabled={progressBar !== false}
|
|
144
|
+
{...(typeof progressBar === 'object' ? progressBar : {})}
|
|
145
|
+
>
|
|
141
146
|
<NextRouterAdapter>
|
|
142
147
|
<NextLinkProvider>
|
|
143
148
|
{/* Single top-level boundary lives inside UiProviders. We feed it the
|
|
@@ -180,13 +185,6 @@ export function BaseApp({
|
|
|
180
185
|
)}
|
|
181
186
|
</ProjectNameProvider>
|
|
182
187
|
</LayoutI18nProvider>
|
|
183
|
-
<NextTopLoader
|
|
184
|
-
color="var(--primary)"
|
|
185
|
-
height={3}
|
|
186
|
-
showSpinner={false}
|
|
187
|
-
shadow="0 0 10px var(--primary), 0 0 5px var(--primary)"
|
|
188
|
-
/>
|
|
189
|
-
|
|
190
188
|
{/* Auth Dialog - only when auth is enabled */}
|
|
191
189
|
{authEnabled && <AuthDialog authPath={authConfig?.routes?.auth} />}
|
|
192
190
|
</ErrorTrackingProvider>
|
|
@@ -196,6 +194,7 @@ export function BaseApp({
|
|
|
196
194
|
</UiProviders>
|
|
197
195
|
</NextLinkProvider>
|
|
198
196
|
</NextRouterAdapter>
|
|
197
|
+
</RouteProgressProvider>
|
|
199
198
|
</ThemeProvider>
|
|
200
199
|
</>
|
|
201
200
|
);
|
|
@@ -79,8 +79,10 @@
|
|
|
79
79
|
height: 1.25rem;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
/* The locale stays at the right edge even when the link is hidden.
|
|
83
|
-
|
|
82
|
+
/* The locale stays at the right edge even when the link is hidden. `:not(:first-child)`
|
|
83
|
+
keeps the rule off the link itself: without a locale switcher the link is the
|
|
84
|
+
only child, hence also `:last-child`, and it would be pushed to the right. */
|
|
85
|
+
.auth-topbar > :last-child:not(:first-child) {
|
|
84
86
|
margin-inline-start: auto;
|
|
85
87
|
}
|
|
86
88
|
|
|
@@ -280,7 +280,7 @@ const header: HeaderConfig = {
|
|
|
280
280
|
|---|---|
|
|
281
281
|
| Docked | Width from `--sidebar-width`; drag the right edge, arrow keys, double-click to reset. |
|
|
282
282
|
| Collapsed (desktop) | Hidden. The top bar (toggle top-left, title, actions, avatar) shows; resting the pointer at the left screen edge (not on the toggle) floats the sidebar over the page. The panel stays while the pointer is on it or a menu opened from it is open; Esc, navigation and moving away close it. Clicking the trigger docks it. |
|
|
283
|
-
| Mobile (< md) | Drawer 88vw wide (max 22rem, the dimmed strip taps it closed) and full height (`fixed inset-y-0`: the real visible area in a browser tab and in an iOS standalone PWA): 40px rows (44px hit area), 14px text, 20px icons, 8px container gutter + 10px row padding (icon column and logo at 18px; dividers, highlighted rows and footer share the 8px gutter), header is the brand only (48px under safe-top, no toggle or close button; it closes by backdrop tap, swipe, Escape or navigating). Three zones: pinned header (gets a bottom hairline once the nav scrolls), the nav as the ONLY scroller (`overscroll-contain`), pinned footer with a full-width top border and its own background; the drawer panel itself is `overflow-hidden` (`DrawerContent bare`). Header and footer respect the safe areas on top, bottom and sides (at least 1rem vertically). On a phone (below md) the layout draws a plain top bar: `safe-top + 3rem` tall (`--layout-bar-height`, `0px` from md up and without a sidebar), `bg-background` with a 1px bottom border, holding the sidebar toggle (the same ghost button as in the sidebar header) and `header.title`. It overlays the content column and
|
|
283
|
+
| Mobile (< md) | Drawer 88vw wide (max 22rem, the dimmed strip taps it closed) and full height (`fixed inset-y-0`: the real visible area in a browser tab and in an iOS standalone PWA): 40px rows (44px hit area), 14px text, 20px icons, 8px container gutter + 10px row padding (icon column and logo at 18px; dividers, highlighted rows and footer share the 8px gutter), header is the brand only (48px under safe-top, no toggle or close button; it closes by backdrop tap, swipe, Escape or navigating). Three zones: pinned header (gets a bottom hairline once the nav scrolls), the nav as the ONLY scroller (`overscroll-contain`), pinned footer with a full-width top border and its own background; the drawer panel itself is `overflow-hidden` (`DrawerContent bare`). Header and footer respect the safe areas on top, bottom and sides (at least 1rem vertically). On a phone (below md) the layout draws a plain top bar: `safe-top + 3rem` tall (`--layout-bar-height`, `0px` from md up and without a sidebar), `bg-background` with a 1px bottom border, holding the sidebar toggle (the same ghost button as in the sidebar header) and `header.title`. It overlays the content column and never hides on scroll. Content starts below the bar for every page, so pages do nothing; apps may use `--layout-bar-height` to offset their own sticky elements. `PrivateLayout` takes `topBar={{ title, actions }}` (types: `PrivateTopBarConfig`): `title` replaces `header.title` in the bar, `actions` is a ReactNode on the right (vertically centred, never shrinks the toggle; the title takes the rest and truncates). The same bar shows on desktop while the sidebar is collapsed (static: it never hides on scroll) and carries the account avatar for a signed-in user (`topBar.account={false}` to opt out); on a phone the drawer then has no footer account. Recommended actions are compact ghost icon `Button`s (36px); several are fine, keep them few. The toggle stays left and is not configurable. It is rendered by the phone bar only; desktop shows nothing (the sidebar is the navigation). |
|
|
284
284
|
|
|
285
285
|
Row metrics (`geometry.ts`): desktop rows 36px, mobile rows 40px (with a 44px hit area from an invisible `::after`), both `text-sm` with 16px (desktop) or 20px (mobile) icons and group labels in `text-xs`. Safe-area insets only exist when the host app sets `viewportFit: "cover"` in its Next.js `viewport` export; without it the offsets fall back to the fixed minimums. For an installed iOS PWA also set `appleWebApp.statusBarStyle` (`black-translucent` draws the page under the status bar and relies on the insets; `default` keeps it below). The drawer height is pinned with `inset-y-0` rather than `100dvh`, which can disagree with the visible area in standalone mode.
|
|
286
286
|
|
|
@@ -85,13 +85,13 @@ describe('PrivateContent top bar', () => {
|
|
|
85
85
|
expect(bar.className).toContain('pt-[var(--safe-top');
|
|
86
86
|
expect(bar.className).toContain('pl-[calc(var(--safe-left');
|
|
87
87
|
expect(bar.className).toContain('pr-[calc(var(--safe-right');
|
|
88
|
-
// Overlays the column
|
|
88
|
+
// Overlays the column; the content pads by the bar height. It never moves on scroll.
|
|
89
89
|
expect(bar.className).toMatch(/\babsolute\b/);
|
|
90
|
-
expect(bar.className).toContain('
|
|
90
|
+
expect(bar.className).not.toContain('translate');
|
|
91
|
+
expect(bar.hasAttribute('data-hidden')).toBe(false);
|
|
91
92
|
expect((bar.nextElementSibling as HTMLElement).className).toContain('layout-bar-height');
|
|
92
93
|
expect(bar.querySelector('[data-sidebar="trigger"]')!.className).not.toMatch(/\b(rounded-full|border|shadow)/);
|
|
93
94
|
expect(bar.textContent).toBe('Acme');
|
|
94
|
-
expect(bar.getAttribute('data-hidden')).toBe('false');
|
|
95
95
|
// Content is the next sibling: it starts under the bar and carries no trigger offsets.
|
|
96
96
|
const scroller = bar.nextElementSibling as HTMLElement;
|
|
97
97
|
expect(scroller.className).not.toContain('layout-trigger');
|
|
@@ -114,77 +114,16 @@ describe('PrivateContent top bar', () => {
|
|
|
114
114
|
expect(scroller.className).toMatch(/\boverflow-y-auto\b/);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
it('never hides on scroll, on a phone or on desktop', () => {
|
|
118
|
+
for (const isPhone of [true, false]) {
|
|
119
119
|
phone = isPhone;
|
|
120
|
-
|
|
121
|
-
let id = 0;
|
|
122
|
-
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
|
|
123
|
-
void Promise.resolve().then(() => cb(0));
|
|
124
|
-
return ++id;
|
|
125
|
-
});
|
|
126
|
-
const el = render(false, 'default', 'Acme');
|
|
127
|
-
const bar = el.querySelector('[data-sidebar="topbar"]') as HTMLElement;
|
|
120
|
+
const bar = render(false, 'default', 'Acme').querySelector('[data-sidebar="topbar"]') as HTMLElement;
|
|
128
121
|
const scroller = bar.nextElementSibling as HTMLElement;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
scroller.scrollTop = top;
|
|
134
|
-
scroller.dispatchEvent(new Event('scroll'));
|
|
135
|
-
await Promise.resolve();
|
|
136
|
-
await Promise.resolve();
|
|
137
|
-
});
|
|
138
|
-
return { el, bar, scroller, scrollTo };
|
|
122
|
+
scroller.scrollTop = 400;
|
|
123
|
+
scroller.dispatchEvent(new Event('scroll'));
|
|
124
|
+
expect(bar.hasAttribute('data-hidden')).toBe(false);
|
|
125
|
+
expect(bar.className).not.toContain('translate');
|
|
139
126
|
}
|
|
140
|
-
afterEach(() => vi.unstubAllGlobals());
|
|
141
|
-
|
|
142
|
-
it('hides on scroll down, shows on scroll up and at the top', async () => {
|
|
143
|
-
const { bar, scrollTo } = mountPhone();
|
|
144
|
-
expect(bar.getAttribute('data-hidden')).toBe('false');
|
|
145
|
-
await scrollTo(120);
|
|
146
|
-
await scrollTo(200);
|
|
147
|
-
expect(bar.getAttribute('data-hidden')).toBe('true');
|
|
148
|
-
await scrollTo(180);
|
|
149
|
-
await scrollTo(150);
|
|
150
|
-
expect(bar.getAttribute('data-hidden')).toBe('false');
|
|
151
|
-
await scrollTo(300);
|
|
152
|
-
await scrollTo(400);
|
|
153
|
-
expect(bar.getAttribute('data-hidden')).toBe('true');
|
|
154
|
-
await scrollTo(0);
|
|
155
|
-
expect(bar.getAttribute('data-hidden')).toBe('false');
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('desktop (sidebar collapsed): the bar is static and never hides', async () => {
|
|
159
|
-
const { bar, scrollTo } = mountPhone(false);
|
|
160
|
-
await scrollTo(120);
|
|
161
|
-
await scrollTo(400);
|
|
162
|
-
expect(bar.getAttribute('data-hidden')).toBe('false');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('stays visible while the drawer is open', async () => {
|
|
166
|
-
const { bar, scrollTo } = mountPhone();
|
|
167
|
-
act(() => (bar.querySelector('[data-sidebar="trigger"]') as HTMLElement).click());
|
|
168
|
-
await scrollTo(120);
|
|
169
|
-
await scrollTo(400);
|
|
170
|
-
expect(bar.getAttribute('data-hidden')).toBe('false');
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('stays visible while a field has focus', async () => {
|
|
174
|
-
const { el, bar, scrollTo } = mountPhone();
|
|
175
|
-
act(() => (el.querySelector('input') as HTMLInputElement).focus());
|
|
176
|
-
await scrollTo(120);
|
|
177
|
-
await scrollTo(400);
|
|
178
|
-
expect(bar.getAttribute('data-hidden')).toBe('false');
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('never hides when the content is not scrollable', async () => {
|
|
182
|
-
const { bar, scroller, scrollTo } = mountPhone();
|
|
183
|
-
Object.defineProperty(scroller, 'scrollHeight', { configurable: true, value: 800 });
|
|
184
|
-
await scrollTo(120);
|
|
185
|
-
await scrollTo(400);
|
|
186
|
-
expect(bar.getAttribute('data-hidden')).toBe('false');
|
|
187
|
-
});
|
|
188
127
|
});
|
|
189
128
|
|
|
190
129
|
describe('topBar content', () => {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Private layout main column: the scroll surface, plus ONE top bar whenever the sidebar is not visible: always on a
|
|
3
3
|
* phone (below md), on desktop while the sidebar is collapsed. Same markup and props at every width: sidebar toggle,
|
|
4
4
|
* title, actions, account avatar. Height `--layout-bar-height` = safe-top + 3rem; it overlays the column and the
|
|
5
|
-
* content has that height as top padding, so pages need to know nothing about it.
|
|
6
|
-
*
|
|
5
|
+
* content has that height as top padding, so pages need to know nothing about it. It never hides on scroll (owner decision,
|
|
6
|
+
* 2026-11-05: it got in the way): static at every width.
|
|
7
7
|
* `--layout-bar-height` is `0px` only when no bar is shown (docked sidebar, or no sidebar at all).
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -17,7 +17,6 @@ import { cn } from '@djangocfg/ui-core/lib';
|
|
|
17
17
|
import type { LayoutVisualConfig } from '../../types';
|
|
18
18
|
import type { HeaderConfig } from '../types';
|
|
19
19
|
import { LAYOUT_BAR_HEIGHT } from '../geometry';
|
|
20
|
-
import { useNavbarScroll } from '../../PublicLayout/hooks/useNavbarScroll';
|
|
21
20
|
import { PrivateSidebarTrigger } from './PrivateSidebarTrigger';
|
|
22
21
|
import { PrivateTopBarAccount } from './PrivateTopBarAccount';
|
|
23
22
|
|
|
@@ -77,22 +76,9 @@ export function PrivateContent({
|
|
|
77
76
|
scrollRef.current?.scrollTo?.({ top: 0 });
|
|
78
77
|
}, [pathname]);
|
|
79
78
|
|
|
80
|
-
const { isMobile, state
|
|
79
|
+
const { isMobile, state } = useSidebar();
|
|
81
80
|
|
|
82
81
|
const showBar = hasSidebar && (isMobile || state === 'collapsed');
|
|
83
|
-
const mobileBar = showBar && isMobile;
|
|
84
|
-
|
|
85
|
-
// Auto-hide: the content scroller (the document never scrolls) drives the shared navbar policy. The bar stays put
|
|
86
|
-
// while the drawer is open or a field has focus (iOS keyboard), and when there is nothing to scroll.
|
|
87
|
-
const { hidden: scrolledAway } = useNavbarScroll({ hideNavOnScroll: mobileBar, target: scrollRef, respectReducedMotion: false });
|
|
88
|
-
const [fieldFocused, setFieldFocused] = React.useState(false);
|
|
89
|
-
const onFocusChange = (e: React.FocusEvent) => {
|
|
90
|
-
const el = e.target as HTMLElement;
|
|
91
|
-
setFieldFocused(
|
|
92
|
-
e.type === 'focus' && (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) || el.isContentEditable),
|
|
93
|
-
);
|
|
94
|
-
};
|
|
95
|
-
const barHidden = mobileBar && scrolledAway && !openMobile && !fieldFocused;
|
|
96
82
|
|
|
97
83
|
const topPadding = showBar
|
|
98
84
|
? 'pt-[calc(var(--layout-bar-height)+1rem)]'
|
|
@@ -122,8 +108,7 @@ export function PrivateContent({
|
|
|
122
108
|
{showBar ? (
|
|
123
109
|
<div
|
|
124
110
|
data-sidebar="topbar"
|
|
125
|
-
|
|
126
|
-
className="absolute inset-x-0 top-0 z-20 box-border flex h-[var(--layout-bar-height)] shrink-0 items-center gap-1 transition-transform duration-200 ease-out motion-reduce:transition-none data-[hidden=true]:-translate-y-full border-b border-border bg-background pl-[calc(var(--safe-left,env(safe-area-inset-left,0px))+0.75rem)] pr-[calc(var(--safe-right,env(safe-area-inset-right,0px))+0.75rem)] pt-[var(--safe-top,env(safe-area-inset-top,0px))]"
|
|
111
|
+
className="absolute inset-x-0 top-0 z-20 box-border flex h-[var(--layout-bar-height)] shrink-0 items-center gap-1 border-b border-border bg-background pl-[calc(var(--safe-left,env(safe-area-inset-left,0px))+0.75rem)] pr-[calc(var(--safe-right,env(safe-area-inset-right,0px))+0.75rem)] pt-[var(--safe-top,env(safe-area-inset-top,0px))]"
|
|
127
112
|
>
|
|
128
113
|
<PrivateSidebarTrigger className="text-muted-foreground hover:text-foreground" />
|
|
129
114
|
<span className="min-w-0 flex-1 truncate text-base font-semibold tracking-tight">
|
|
@@ -147,7 +132,7 @@ export function PrivateContent({
|
|
|
147
132
|
) : null}
|
|
148
133
|
</div>
|
|
149
134
|
) : null}
|
|
150
|
-
<div ref={scrollRef} className={scrollAreaClass}
|
|
135
|
+
<div ref={scrollRef} className={scrollAreaClass}>
|
|
151
136
|
{innerWidthClass ? <div className={innerWidthClass}>{children}</div> : children}
|
|
152
137
|
</div>
|
|
153
138
|
</div>
|
|
@@ -34,6 +34,13 @@ import type { DevtoolsProps } from '@djangocfg/devtools';
|
|
|
34
34
|
* Core configuration for BaseApp component.
|
|
35
35
|
* All provider configs are imported from their respective modules.
|
|
36
36
|
*/
|
|
37
|
+
/** Tuning for the top loading bar, see `BaseLayoutProps.progressBar`. */
|
|
38
|
+
export interface RouteProgressOptions {
|
|
39
|
+
color?: string;
|
|
40
|
+
height?: number;
|
|
41
|
+
shouldShow?: (from: URL, to: URL) => boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
export interface BaseLayoutProps {
|
|
38
45
|
children: ReactNode;
|
|
39
46
|
|
|
@@ -79,6 +86,15 @@ export interface BaseLayoutProps {
|
|
|
79
86
|
* pass `false` to opt out.
|
|
80
87
|
*/
|
|
81
88
|
pwaMeta?: boolean;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The top loading bar shown while moving between pages (`@djangocfg/ui-core/route-progress`). On by default;
|
|
92
|
+
* `false` removes it for the whole app. An object tunes it: `color`, `height` (px) and `shouldShow(from, to)`,
|
|
93
|
+
* the app's route rule (default: the pathname changes, so `?tab=2` and `#hash` never show it). A page or
|
|
94
|
+
* section can silence the bar, and the jump to the top, while it is mounted:
|
|
95
|
+
* `useNavigationBehavior({ progress: false, scroll: false })`.
|
|
96
|
+
*/
|
|
97
|
+
progressBar?: boolean | RouteProgressOptions;
|
|
82
98
|
}
|
|
83
99
|
|
|
84
100
|
/** Devtools config for BaseApp — the `<Devtools>` props minus children. */
|