@protobase/ui 0.14.0 → 0.14.1
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/dist/app/displays/list-cards.js +2 -2
- package/dist/app/displays/virtual-cards.js +43 -65
- package/dist/app/list-page.js +9 -2
- package/dist/app-shell/app-shell.js +54 -38
- package/dist/app-shell/bottom-bar.d.ts +1 -0
- package/dist/app-shell/bottom-bar.js +3 -2
- package/dist/app-shell/index.d.ts +1 -0
- package/dist/app-shell/page-scroll.d.ts +1 -0
- package/dist/app-shell/page-scroll.js +56 -0
- package/dist/app-shell/sidebar.js +2 -2
- package/dist/app-shell/top-bar.js +3 -2
- package/dist/data-grid/data-grid.js +92 -85
- package/dist/data-grid/geometry.js +26 -6
- package/dist/data-grid/grid-footer.js +5 -12
- package/dist/data-grid/scroll-parent.js +35 -0
- package/dist/data-grid/use-virtual-rows.js +123 -18
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-Dkz6z_t_.js +66 -0
- package/dist/shell/assets/{index-CwUFCDf9.js → index-BCLK6g9a.js} +1 -1
- package/dist/shell/assets/index-BQ4FIU4i.css +2 -0
- package/dist/shell/assets/{maplibre-gl-QGc1hPKt.js → maplibre-gl-DWx1yzYO.js} +1 -1
- package/dist/shell/assets/{record-map-D5vqYcYw.js → record-map-C2M8dEjy.js} +2 -2
- package/dist/shell/assets/{records-map-DqqaK1rm.js → records-map-huS0J9xV.js} +1 -1
- package/dist/shell/assets/{shared-protobase-ui-0ozDlcDr.js → shared-protobase-ui-CsJxPCx6.js} +1 -1
- package/dist/shell/index.html +4 -4
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +4 -4
- package/dist/data-grid/use-grid-scroll.js +0 -87
- package/dist/shell/assets/app-DtPlcCA3.js +0 -66
- package/dist/shell/assets/index-CYWfU_c1.css +0 -2
|
@@ -10,8 +10,8 @@ const grid = "grid grid-cols-[repeat(auto-fill,minmax(15rem,1fr))] gap-3";
|
|
|
10
10
|
const pictureHeight = (width) => width * .75;
|
|
11
11
|
const plainHeight = 44;
|
|
12
12
|
/**
|
|
13
|
-
* The list page's cards display: a card per record (see `RecordCard`), scrolled like the table
|
|
14
|
-
*
|
|
13
|
+
* The list page's cards display: a card per record (see `RecordCard`), scrolled like the table. An embedded list,
|
|
14
|
+
* which shows a few cards whole, draws them with `CardsDisplay`.
|
|
15
15
|
*/
|
|
16
16
|
const ListCards = ({ model, view, display, list }) => {
|
|
17
17
|
const plural = view?.names?.plural ?? humanize(model.name);
|
|
@@ -3,15 +3,17 @@ import { usePagedRows } from "../../data-grid/use-paged-rows.js";
|
|
|
3
3
|
import { useVirtualRows } from "../../data-grid/use-virtual-rows.js";
|
|
4
4
|
import { anchoredTop, cardColumns, cardWidth, recordRange } from "./card-layout.js";
|
|
5
5
|
import { useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
6
|
-
import { jsx
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
7
|
//#region src/app/displays/virtual-cards.tsx
|
|
8
8
|
/**
|
|
9
|
-
* Cards in rows as wide as the list, scrolled like the table
|
|
10
|
-
*
|
|
11
|
-
* pages and the first record in view. Every card in a width is as tall as the tallest
|
|
9
|
+
* Cards in rows as wide as the list, scrolled like the table by the page or the nearest scrolling ancestor: the list is
|
|
10
|
+
* as tall as all its rows, only the rows in view are drawn, and the records are loaded in the table's pages, so a
|
|
11
|
+
* change of width keeps the loaded pages and the first record in view. Every card in a width is as tall as the tallest
|
|
12
|
+
* drawn so far.
|
|
12
13
|
*/
|
|
13
14
|
const VirtualCards = ({ source, label, initialIndex, onScrollIndex, minHeight, children }) => {
|
|
14
15
|
const { total } = source;
|
|
16
|
+
const [startIndex] = useState(initialIndex);
|
|
15
17
|
const [width, setWidth] = useState(0);
|
|
16
18
|
const columns = cardColumns(width);
|
|
17
19
|
const itemWidth = cardWidth(width, columns);
|
|
@@ -29,13 +31,15 @@ const VirtualCards = ({ source, label, initialIndex, onScrollIndex, minHeight, c
|
|
|
29
31
|
layout,
|
|
30
32
|
top: 0
|
|
31
33
|
});
|
|
32
|
-
const changed = anchor.current.layout.columns !== columns || anchor.current.layout.rowHeight !== rowHeight;
|
|
34
|
+
const changed = anchor.current.top > 0 && (anchor.current.layout.columns !== columns || anchor.current.layout.rowHeight !== rowHeight);
|
|
33
35
|
const settle = (top) => changed ? anchoredTop(anchor.current.top, anchor.current.layout, layout) : top;
|
|
36
|
+
const ready = itemWidth > 0 && measured.width === itemWidth;
|
|
34
37
|
const scroll = useVirtualRows({
|
|
35
38
|
total: Math.ceil(total / columns),
|
|
36
39
|
rowHeight,
|
|
37
|
-
initialIndex: Math.floor(
|
|
38
|
-
settle
|
|
40
|
+
initialIndex: Math.floor(startIndex / columns),
|
|
41
|
+
settle,
|
|
42
|
+
ready
|
|
39
43
|
});
|
|
40
44
|
useLayoutEffect(() => {
|
|
41
45
|
if (changed) scroll.scrollTo(scroll.top);
|
|
@@ -43,7 +47,7 @@ const VirtualCards = ({ source, label, initialIndex, onScrollIndex, minHeight, c
|
|
|
43
47
|
layout,
|
|
44
48
|
top: scroll.top
|
|
45
49
|
};
|
|
46
|
-
if (!changed) onScrollIndex(Math.floor(scroll.top / rowHeight) * columns);
|
|
50
|
+
if (!changed) onScrollIndex(Math.floor(Math.max(0, scroll.top) / rowHeight) * columns);
|
|
47
51
|
});
|
|
48
52
|
const range = recordRange(scroll.first, scroll.last, columns, total);
|
|
49
53
|
const getRow = usePagedRows({
|
|
@@ -52,9 +56,8 @@ const VirtualCards = ({ source, label, initialIndex, onScrollIndex, minHeight, c
|
|
|
52
56
|
loadRows: source.loadRows,
|
|
53
57
|
latencyMs: 0
|
|
54
58
|
}, range.start, range.end);
|
|
55
|
-
const bodyRef = useRef(null);
|
|
56
59
|
useLayoutEffect(() => {
|
|
57
|
-
const body =
|
|
60
|
+
const body = scroll.ref.current;
|
|
58
61
|
if (!body) return;
|
|
59
62
|
const observer = new ResizeObserver(() => setWidth(body.clientWidth));
|
|
60
63
|
observer.observe(body);
|
|
@@ -77,61 +80,36 @@ const VirtualCards = ({ source, label, initialIndex, onScrollIndex, minHeight, c
|
|
|
77
80
|
observer.observe(item);
|
|
78
81
|
return () => observer.unobserve(item);
|
|
79
82
|
}, [observer]);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
style: place,
|
|
111
|
-
children: /* @__PURE__ */ jsx(Skeleton, { className: "h-full rounded-lg" })
|
|
112
|
-
}, `skeleton-${index}`);
|
|
113
|
-
return /* @__PURE__ */ jsx("li", {
|
|
114
|
-
ref: observe,
|
|
115
|
-
"aria-posinset": index + 1,
|
|
116
|
-
"aria-setsize": total,
|
|
117
|
-
className: "absolute grid grid-cols-[minmax(0,1fr)]",
|
|
118
|
-
style: place,
|
|
119
|
-
children: children(row)
|
|
120
|
-
}, row.id);
|
|
121
|
-
}))
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
})
|
|
125
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
126
|
-
className: "py-3 text-xs tabular-nums text-muted-foreground",
|
|
127
|
-
children: [
|
|
128
|
-
firstShown.toLocaleString("en-IE"),
|
|
129
|
-
"–",
|
|
130
|
-
lastShown.toLocaleString("en-IE"),
|
|
131
|
-
" of ",
|
|
132
|
-
total.toLocaleString("en-IE")
|
|
133
|
-
]
|
|
134
|
-
})]
|
|
83
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
84
|
+
ref: scroll.ref,
|
|
85
|
+
className: "relative min-w-0 flex-auto",
|
|
86
|
+
"aria-label": label,
|
|
87
|
+
style: { height: scroll.geometry.contentHeight },
|
|
88
|
+
children: scroll.rows.flatMap(({ index: rowIndex, offset }) => Array.from({ length: columns }, (_, column) => {
|
|
89
|
+
const index = rowIndex * columns + column;
|
|
90
|
+
if (index >= total) return [];
|
|
91
|
+
const row = getRow(index);
|
|
92
|
+
const place = {
|
|
93
|
+
top: offset,
|
|
94
|
+
left: column * (itemWidth + 12),
|
|
95
|
+
width: itemWidth,
|
|
96
|
+
minHeight: itemHeight
|
|
97
|
+
};
|
|
98
|
+
if (!row) return /* @__PURE__ */ jsx("li", {
|
|
99
|
+
"aria-hidden": true,
|
|
100
|
+
className: "absolute grid grid-cols-[minmax(0,1fr)]",
|
|
101
|
+
style: place,
|
|
102
|
+
children: /* @__PURE__ */ jsx(Skeleton, { className: "h-full rounded-lg" })
|
|
103
|
+
}, `skeleton-${index}`);
|
|
104
|
+
return /* @__PURE__ */ jsx("li", {
|
|
105
|
+
ref: observe,
|
|
106
|
+
"aria-posinset": index + 1,
|
|
107
|
+
"aria-setsize": total,
|
|
108
|
+
className: "absolute grid grid-cols-[minmax(0,1fr)]",
|
|
109
|
+
style: place,
|
|
110
|
+
children: children(row)
|
|
111
|
+
}, row.id);
|
|
112
|
+
}))
|
|
135
113
|
});
|
|
136
114
|
};
|
|
137
115
|
//#endregion
|
package/dist/app/list-page.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useIsDesktop } from "../lib/use-media-query.js";
|
|
2
|
+
import { usePageScroll } from "../app-shell/page-scroll.js";
|
|
2
3
|
import { PageHeader } from "../app-shell/page-header.js";
|
|
3
4
|
import { useAuth } from "../auth/auth-provider.js";
|
|
4
5
|
import { Button } from "../primitives/button.js";
|
|
@@ -100,6 +101,8 @@ const LoadedList = ({ model, address }) => {
|
|
|
100
101
|
orderBy
|
|
101
102
|
});
|
|
102
103
|
const display = shownDisplay(view, address.display);
|
|
104
|
+
const pageScroll = !display || display.kind === "cards";
|
|
105
|
+
usePageScroll(pageScroll);
|
|
103
106
|
const displayRows = useDisplayRows(model, rows.page, rows.key, display?.kind === "custom");
|
|
104
107
|
const scroll = useListScroll();
|
|
105
108
|
const titleField = recordTitleField(model, view);
|
|
@@ -255,7 +258,8 @@ const LoadedList = ({ model, address }) => {
|
|
|
255
258
|
}] : [],
|
|
256
259
|
rowNumbers: false,
|
|
257
260
|
pinKey: auth.kind === "signed-in" ? pinnedStorageKey(auth.user.id, resource) : void 0,
|
|
258
|
-
|
|
261
|
+
scroll: "parent",
|
|
262
|
+
className: "flex-1"
|
|
259
263
|
}) : /* @__PURE__ */ jsx("div", { className: "flex-1 rounded-lg border bg-surface" });
|
|
260
264
|
const cardsList = {
|
|
261
265
|
...rows,
|
|
@@ -314,7 +318,10 @@ const LoadedList = ({ model, address }) => {
|
|
|
314
318
|
desktop ? desktopToolbar : phoneFilters,
|
|
315
319
|
desktop && layout === "panel" ? /* @__PURE__ */ jsxs("div", {
|
|
316
320
|
className: "flex min-h-0 flex-1 gap-4",
|
|
317
|
-
children: [
|
|
321
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
322
|
+
className: pageScroll ? "sticky top-[calc(var(--pb-sticky-top,0px)+1rem)] flex max-h-[calc(100dvh-var(--pb-sticky-top,0px)-var(--pb-sticky-bottom,0px)-2rem)] shrink-0 self-start" : "flex shrink-0",
|
|
323
|
+
children: panel
|
|
324
|
+
}), shown]
|
|
318
325
|
}) : shown
|
|
319
326
|
]
|
|
320
327
|
});
|
|
@@ -2,9 +2,10 @@ import { cn } from "../lib/cn.js";
|
|
|
2
2
|
import { useIsDesktop } from "../lib/use-media-query.js";
|
|
3
3
|
import { BottomBar } from "./bottom-bar.js";
|
|
4
4
|
import { Drawer } from "./drawer.js";
|
|
5
|
+
import { PageScrollContext, usePageInsets } from "./page-scroll.js";
|
|
5
6
|
import { Sidebar } from "./sidebar.js";
|
|
6
7
|
import { TopBar } from "./top-bar.js";
|
|
7
|
-
import { useEffect, useState } from "react";
|
|
8
|
+
import { useEffect, useRef, useState } from "react";
|
|
8
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
10
|
//#region src/app-shell/app-shell.tsx
|
|
10
11
|
const drawerId = "app-drawer";
|
|
@@ -15,8 +16,13 @@ const AppShell = ({ sidebarMode, activeItem, breadcrumb, user, workspace, action
|
|
|
15
16
|
if (desktop) setDrawerOpen(false);
|
|
16
17
|
}, [desktop]);
|
|
17
18
|
const tabs = !desktop && bottomBar && bottomBar.length > 0 ? bottomBar : void 0;
|
|
18
|
-
const
|
|
19
|
+
const [pageScroll, setPageScroll] = useState(false);
|
|
20
|
+
const topBarRef = useRef(null);
|
|
21
|
+
const bottomBarRef = useRef(null);
|
|
22
|
+
usePageInsets(pageScroll, topBarRef, bottomBarRef, Boolean(tabs));
|
|
23
|
+
const sidebar = (mode, sticky = false) => /* @__PURE__ */ jsx(Sidebar, {
|
|
19
24
|
mode,
|
|
25
|
+
sticky,
|
|
20
26
|
activeItem,
|
|
21
27
|
user,
|
|
22
28
|
workspace,
|
|
@@ -30,42 +36,52 @@ const AppShell = ({ sidebarMode, activeItem, breadcrumb, user, workspace, action
|
|
|
30
36
|
organizations,
|
|
31
37
|
roles
|
|
32
38
|
});
|
|
33
|
-
return /* @__PURE__ */
|
|
34
|
-
|
|
35
|
-
children:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
39
|
+
return /* @__PURE__ */ jsx(PageScrollContext.Provider, {
|
|
40
|
+
value: setPageScroll,
|
|
41
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
42
|
+
className: cn(pageScroll ? "flex min-h-full w-full overflow-x-clip bg-background" : "flex h-full min-h-0 w-full overflow-hidden bg-background", className),
|
|
43
|
+
children: [
|
|
44
|
+
desktop ? sidebar(sidebarMode, pageScroll) : /* @__PURE__ */ jsx(Drawer, {
|
|
45
|
+
id: drawerId,
|
|
46
|
+
open: drawerOpen,
|
|
47
|
+
onClose: () => setDrawerOpen(false),
|
|
48
|
+
children: sidebar("text-large")
|
|
49
|
+
}),
|
|
50
|
+
/* @__PURE__ */ jsxs("div", {
|
|
51
|
+
className: "flex min-w-0 flex-1 flex-col",
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ jsx(TopBar, {
|
|
54
|
+
ref: topBarRef,
|
|
55
|
+
sticky: pageScroll,
|
|
56
|
+
breadcrumb,
|
|
57
|
+
onNavigate,
|
|
58
|
+
actions,
|
|
59
|
+
onMenu: desktop || tabs ? void 0 : () => setDrawerOpen(true),
|
|
60
|
+
menuOpen: drawerOpen,
|
|
61
|
+
menuControls: drawerId,
|
|
62
|
+
search
|
|
63
|
+
}),
|
|
64
|
+
/* @__PURE__ */ jsx("main", {
|
|
65
|
+
className: pageScroll ? "flex flex-1 flex-col" : "flex min-h-0 flex-1 flex-col",
|
|
66
|
+
children
|
|
67
|
+
}),
|
|
68
|
+
tabs && /* @__PURE__ */ jsx(BottomBar, {
|
|
69
|
+
ref: bottomBarRef,
|
|
70
|
+
sticky: pageScroll,
|
|
71
|
+
tabs,
|
|
72
|
+
onNavigate,
|
|
73
|
+
onMore: () => setDrawerOpen(true),
|
|
74
|
+
moreOpen: drawerOpen,
|
|
75
|
+
moreControls: drawerId
|
|
76
|
+
})
|
|
77
|
+
]
|
|
78
|
+
}),
|
|
79
|
+
rightPanel && /* @__PURE__ */ jsx("div", {
|
|
80
|
+
className: pageScroll ? "sticky top-0 flex h-dvh shrink-0 self-start" : "flex shrink-0",
|
|
81
|
+
children: rightPanel
|
|
82
|
+
})
|
|
83
|
+
]
|
|
84
|
+
})
|
|
69
85
|
});
|
|
70
86
|
};
|
|
71
87
|
//#endregion
|
|
@@ -9,11 +9,12 @@ const tab = "flex min-h-[49px] min-w-0 flex-1 flex-col items-center justify-cent
|
|
|
9
9
|
* last, which opens the navigation drawer and is the current tab on screens the others do not hold. The padding below
|
|
10
10
|
* keeps the tabs clear of the iPhone home indicator.
|
|
11
11
|
*/
|
|
12
|
-
const BottomBar = ({ tabs, onNavigate, onMore, moreOpen, moreControls }) => {
|
|
12
|
+
const BottomBar = ({ tabs, onNavigate, onMore, moreOpen, moreControls, sticky, ref }) => {
|
|
13
13
|
const moreCurrent = !tabs.some((item) => item.active);
|
|
14
14
|
return /* @__PURE__ */ jsxs("nav", {
|
|
15
|
+
ref,
|
|
15
16
|
"aria-label": "Tabs",
|
|
16
|
-
className: "flex shrink-0 border-t bg-background pb-[env(safe-area-inset-bottom)] md:hidden",
|
|
17
|
+
className: cn("flex shrink-0 border-t bg-background pb-[env(safe-area-inset-bottom)] md:hidden", sticky && "sticky bottom-0 z-30"),
|
|
17
18
|
children: [tabs.map((item) => {
|
|
18
19
|
const Icon = item.icon;
|
|
19
20
|
return /* @__PURE__ */ jsxs("a", {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "react";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createContext, useContext, useLayoutEffect } from "react";
|
|
2
|
+
//#region src/app-shell/page-scroll.ts
|
|
3
|
+
/** Turns the shell's page scrolling on or off; pages use `usePageScroll`. */
|
|
4
|
+
const PageScrollContext = createContext(() => {});
|
|
5
|
+
/**
|
|
6
|
+
* While the calling page is shown and `on`, the document scrolls instead of a box of the screen's height: the top and
|
|
7
|
+
* bottom bars and the sidebar stick, and the page grows with its content, so the browser's own scrolling applies
|
|
8
|
+
* throughout, scrolling to the top included, as tapping the status bar on an iPhone does. Sticky content in the page
|
|
9
|
+
* stays clear of the bars with `--pb-sticky-top` and `--pb-sticky-bottom`.
|
|
10
|
+
*/
|
|
11
|
+
const usePageScroll = (on) => {
|
|
12
|
+
const set = useContext(PageScrollContext);
|
|
13
|
+
useLayoutEffect(() => {
|
|
14
|
+
set(on);
|
|
15
|
+
return () => set(false);
|
|
16
|
+
}, [set, on]);
|
|
17
|
+
};
|
|
18
|
+
const properties = [
|
|
19
|
+
"--pb-sticky-top",
|
|
20
|
+
"--pb-sticky-bottom",
|
|
21
|
+
"scroll-padding-top",
|
|
22
|
+
"scroll-padding-bottom"
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* While the page scrolls, says how tall its sticky bars are, on the root element: as `--pb-sticky-top` and
|
|
26
|
+
* `--pb-sticky-bottom` for content that sticks inside them, and as the page's scroll padding, which the browser keeps
|
|
27
|
+
* focused and scrolled-to content clear of, and which a virtual list reads as the part of the page it shows in.
|
|
28
|
+
*/
|
|
29
|
+
const usePageInsets = (on, top, bottom, bottomShown) => {
|
|
30
|
+
useLayoutEffect(() => {
|
|
31
|
+
if (!on) return;
|
|
32
|
+
const root = document.documentElement;
|
|
33
|
+
const update = () => {
|
|
34
|
+
const above = `${top.current?.offsetHeight ?? 0}px`;
|
|
35
|
+
const below = `${bottom.current?.offsetHeight ?? 0}px`;
|
|
36
|
+
root.style.setProperty("--pb-sticky-top", above);
|
|
37
|
+
root.style.setProperty("--pb-sticky-bottom", below);
|
|
38
|
+
root.style.setProperty("scroll-padding-top", above);
|
|
39
|
+
root.style.setProperty("scroll-padding-bottom", below);
|
|
40
|
+
};
|
|
41
|
+
const observer = new ResizeObserver(update);
|
|
42
|
+
for (const bar of [top.current, bottom.current]) if (bar) observer.observe(bar);
|
|
43
|
+
update();
|
|
44
|
+
return () => {
|
|
45
|
+
observer.disconnect();
|
|
46
|
+
for (const property of properties) root.style.removeProperty(property);
|
|
47
|
+
};
|
|
48
|
+
}, [
|
|
49
|
+
on,
|
|
50
|
+
top,
|
|
51
|
+
bottom,
|
|
52
|
+
bottomShown
|
|
53
|
+
]);
|
|
54
|
+
};
|
|
55
|
+
//#endregion
|
|
56
|
+
export { PageScrollContext, usePageInsets, usePageScroll };
|
|
@@ -8,13 +8,13 @@ import { SidebarGroup } from "./sidebar-group.js";
|
|
|
8
8
|
import { LogIn } from "lucide-react";
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
//#region src/app-shell/sidebar.tsx
|
|
11
|
-
const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, onNavigate, onSignOut, onSignIn, organizations, roles }) => {
|
|
11
|
+
const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, onNavigate, onSignOut, onSignIn, organizations, roles, sticky }) => {
|
|
12
12
|
const expanding = mode === "icon-expand";
|
|
13
13
|
const expanded = expanding && forceExpanded;
|
|
14
14
|
const inline = isTextMode(mode) || expanding;
|
|
15
15
|
return /* @__PURE__ */ jsx("nav", {
|
|
16
16
|
"aria-label": "Resources",
|
|
17
|
-
className: cn("relative z-30 shrink-0", sidebarWidth[mode]),
|
|
17
|
+
className: cn(sticky ? "sticky top-0 h-dvh self-start" : "relative", "z-30 shrink-0", sidebarWidth[mode]),
|
|
18
18
|
children: /* @__PURE__ */ jsxs("div", {
|
|
19
19
|
className: cn("group/side absolute inset-y-0 left-0 flex flex-col overflow-hidden border-r bg-sidebar", expanding ? "w-14 transition-[width,box-shadow] duration-150 hover:w-60 hover:shadow-pop focus-within:w-60 focus-within:shadow-pop" : "w-full", expanded && "w-60 shadow-pop"),
|
|
20
20
|
children: [
|
|
@@ -7,7 +7,7 @@ import { ChevronLeft, Menu, Search, X } from "lucide-react";
|
|
|
7
7
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/app-shell/top-bar.tsx
|
|
9
9
|
const iconButton = "inline-flex size-11 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted md:hidden";
|
|
10
|
-
const TopBar = ({ breadcrumb, onNavigate, actions, onMenu, menuOpen, menuControls, search }) => {
|
|
10
|
+
const TopBar = ({ breadcrumb, onNavigate, actions, onMenu, menuOpen, menuControls, search, sticky, ref }) => {
|
|
11
11
|
const [searchOpen, setSearchOpen] = useState(false);
|
|
12
12
|
const back = backTarget(breadcrumb);
|
|
13
13
|
const desktopSearch = useRef(null);
|
|
@@ -24,7 +24,8 @@ const TopBar = ({ breadcrumb, onNavigate, actions, onMenu, menuOpen, menuControl
|
|
|
24
24
|
return () => window.removeEventListener("keydown", onKey);
|
|
25
25
|
}, [search]);
|
|
26
26
|
return /* @__PURE__ */ jsxs("header", {
|
|
27
|
-
|
|
27
|
+
ref,
|
|
28
|
+
className: cn(sticky ? "sticky top-0 z-30" : "relative", "flex h-12 shrink-0 items-center gap-1 border-b bg-background px-2 md:gap-4 md:px-5"),
|
|
28
29
|
children: [
|
|
29
30
|
onMenu && /* @__PURE__ */ jsx("button", {
|
|
30
31
|
type: "button",
|