@umodeapporg/ui 0.9.0 → 0.9.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/dist/components/Navigation/PageTitle.d.ts +11 -7
- package/dist/components/Navigation/PageTitle.js +132 -132
- package/dist/components/Navigation/PageTitle.js.map +1 -1
- package/dist/components/Navigation/ReactRouterNavigationAdapter.js +4 -3
- package/dist/components/Navigation/ReactRouterNavigationAdapter.js.map +1 -1
- package/dist/components/Navigation/UiNavigationContext.js +3 -2
- package/dist/components/Navigation/UiNavigationContext.js.map +1 -1
- package/dist/components/Shell/AppShell.js +8 -2
- package/dist/components/Shell/AppShell.js.map +1 -1
- package/package.json +1 -1
|
@@ -46,14 +46,18 @@ interface IPageTitleProps {
|
|
|
46
46
|
*/
|
|
47
47
|
titleMediaSize?: 'sm' | 'md' | 'lg';
|
|
48
48
|
/**
|
|
49
|
-
* The header
|
|
50
|
-
* headline and the `subtitleSlot`
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* a header that scrolls away does not need to be smaller.
|
|
49
|
+
* The header pins itself to the top of its scroll container and, once pinned, shrinks: the
|
|
50
|
+
* breadcrumb, the eyebrow, the headline and the `subtitleSlot` go, the media drops one size (the
|
|
51
|
+
* record's picture stays, at the icon chip's size) and the title takes its compact type. The back
|
|
52
|
+
* button and the `slot` never move.
|
|
54
53
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
* EXPANDED, IT RENDERS EXACTLY WHAT IT RENDERED BEFORE THIS EXISTED: no wrapper node, no extra
|
|
55
|
+
* element, no background. That is deliberate. The first version added a sentinel node as a sibling
|
|
56
|
+
* of the header, and since almost every page wraps it in a `flex flex-col gap-y-*`, that node
|
|
57
|
+
* became a flex item and every page grew a stray gap.
|
|
58
|
+
*
|
|
59
|
+
* On by default. Turn it off for a header that must keep its full identity on screen, or one that
|
|
60
|
+
* is not at the top of a scrolling page (inside a card, a dialog or a print sheet).
|
|
57
61
|
*/
|
|
58
62
|
collapseOnScroll?: boolean;
|
|
59
63
|
/**
|
|
@@ -4,7 +4,6 @@ import classNames from "classnames";
|
|
|
4
4
|
import { useTranslation } from "react-i18next";
|
|
5
5
|
import { twMerge } from "tailwind-merge";
|
|
6
6
|
import ControlledButton from "../Form/ControlledButton.js";
|
|
7
|
-
import ControlledCollapse from "../UI/ControlledCollapse.js";
|
|
8
7
|
import ControlledTooltip from "../UI/ControlledTooltip.js";
|
|
9
8
|
import { capitalizeAllLetters } from "../../helpers/stringHelpers.js";
|
|
10
9
|
import { MaterialSymbol } from "../UI/MaterialSymbol/index.js";
|
|
@@ -21,6 +20,7 @@ const MEDIA_ICON_SIZE = { sm: 18, md: 22, lg: 34 };
|
|
|
21
20
|
const COLLAPSED_MEDIA_SIZE = { sm: "sm", md: "sm", lg: "md" };
|
|
22
21
|
const COLLAPSE_AT_SCROLL = 48;
|
|
23
22
|
const EXPAND_AT_SCROLL = 16;
|
|
23
|
+
const PINNED_TOLERANCE_PX = 1;
|
|
24
24
|
const findScrollParent = (node) => {
|
|
25
25
|
let current = node?.parentElement ?? null;
|
|
26
26
|
while (current) {
|
|
@@ -92,7 +92,7 @@ const PageTitle = ({
|
|
|
92
92
|
const resolvedTitleIcon = titleIcon ?? (autoIcon ? menuTrail[menuTrail.length - 1]?.icon : void 0);
|
|
93
93
|
const [failedTitleImageSrc, setFailedTitleImageSrc] = useState(null);
|
|
94
94
|
const rendersTitleImage = Boolean(titleImage?.src) && titleImage?.src !== failedTitleImageSrc;
|
|
95
|
-
const
|
|
95
|
+
const rootRef = useRef(null);
|
|
96
96
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
97
97
|
const collapses = collapseOnScroll && !canFloat;
|
|
98
98
|
useEffect(() => {
|
|
@@ -100,24 +100,33 @@ const PageTitle = ({
|
|
|
100
100
|
setIsCollapsed(false);
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
|
-
const
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
const root = rootRef.current;
|
|
104
|
+
if (!root) return;
|
|
105
|
+
const container = collapseScrollRef?.current ?? findScrollParent(root);
|
|
106
|
+
if (!container) return;
|
|
107
|
+
const scrollsUnderTheHeader = container.contains(root);
|
|
108
|
+
const containerPaddingTop = Number.parseFloat(getComputedStyle(container).paddingTop) || 0;
|
|
109
|
+
const stickyTop = Number.parseFloat(getComputedStyle(root).top) || 0;
|
|
110
|
+
let frame = 0;
|
|
111
|
+
const evaluate = () => {
|
|
112
|
+
frame = 0;
|
|
113
|
+
if (!scrollsUnderTheHeader) {
|
|
114
|
+
const offset = container.scrollTop;
|
|
107
115
|
setIsCollapsed((previous) => previous ? offset > EXPAND_AT_SCROLL : offset >= COLLAPSE_AT_SCROLL);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const pinnedLine = container.getBoundingClientRect().top + containerPaddingTop + stickyTop;
|
|
119
|
+
setIsCollapsed(root.getBoundingClientRect().top <= pinnedLine + PINNED_TOLERANCE_PX);
|
|
120
|
+
};
|
|
121
|
+
const handleScroll = () => {
|
|
122
|
+
if (!frame) frame = requestAnimationFrame(evaluate);
|
|
123
|
+
};
|
|
124
|
+
evaluate();
|
|
125
|
+
container.addEventListener("scroll", handleScroll, { passive: true });
|
|
126
|
+
return () => {
|
|
127
|
+
container.removeEventListener("scroll", handleScroll);
|
|
128
|
+
if (frame) cancelAnimationFrame(frame);
|
|
129
|
+
};
|
|
121
130
|
}, [collapses, collapseScrollRef]);
|
|
122
131
|
const isCompactLayout = compact || isCollapsed;
|
|
123
132
|
const resolvedMediaSize = titleMediaSize ?? (compact ? "sm" : "md");
|
|
@@ -126,125 +135,116 @@ const PageTitle = ({
|
|
|
126
135
|
const mediaIconSize = MEDIA_ICON_SIZE[mediaSize];
|
|
127
136
|
const mediaTransitionClassName = "transition-[width,height] duration-base ease-enter";
|
|
128
137
|
const collapseValue = useMemo(() => ({ isCollapsed }), [isCollapsed]);
|
|
129
|
-
return /* @__PURE__ */
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
style: {
|
|
146
|
-
width: canFloat ? isFirefox() ? "-moz-available" : "-webkit-fill-available" : "100%"
|
|
138
|
+
return /* @__PURE__ */ jsx(PageTitleCollapseProvider, { value: collapseValue, children: /* @__PURE__ */ jsxs(
|
|
139
|
+
"div",
|
|
140
|
+
{
|
|
141
|
+
ref: rootRef,
|
|
142
|
+
className: classNames(
|
|
143
|
+
"flex flex-col",
|
|
144
|
+
{
|
|
145
|
+
"zIndexMax fixed top-16 mr-7": canFloat,
|
|
146
|
+
// Pins under the app header, which publishes its own height; hosts without the shell stick
|
|
147
|
+
// at the top of their own scroll container. `sticky` is inert until it pins, so this costs
|
|
148
|
+
// nothing on a page that does not scroll.
|
|
149
|
+
"sticky top-[var(--page-header-sticky-top,0px)] z-30": collapses,
|
|
150
|
+
// The page's own background, and nothing else. Pinned, the header has to be opaque or the
|
|
151
|
+
// content slides visibly through the title; but it is not a PANEL, so no glass, no border
|
|
152
|
+
// and no shadow, which read as a second surface appearing out of nowhere mid-scroll.
|
|
153
|
+
"bg-canvas": collapses && isCollapsed
|
|
147
154
|
},
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"flex shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-interactive duration-base ease-enter hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border active:scale-90 disabled:pointer-events-none disabled:text-foreground-faint",
|
|
169
|
-
isCompactLayout ? "size-8" : "size-9"
|
|
170
|
-
),
|
|
171
|
-
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "arrow_back", size: isCompactLayout ? 18 : 20 })
|
|
172
|
-
}
|
|
173
|
-
) }),
|
|
174
|
-
rendersTitleImage && titleImage ? /* @__PURE__ */ jsx(
|
|
175
|
-
"img",
|
|
176
|
-
{
|
|
177
|
-
src: titleImage.src,
|
|
178
|
-
alt: titleImage.alt,
|
|
179
|
-
onError: () => setFailedTitleImageSrc(titleImage.src),
|
|
180
|
-
className: twMerge(
|
|
181
|
-
"shrink-0 border border-border object-cover",
|
|
182
|
-
mediaTransitionClassName,
|
|
183
|
-
mediaBoxClassName,
|
|
184
|
-
titleImage.className
|
|
185
|
-
)
|
|
186
|
-
}
|
|
187
|
-
) : resolvedTitleIcon && /* @__PURE__ */ jsx(
|
|
188
|
-
"span",
|
|
189
|
-
{
|
|
190
|
-
className: twMerge(
|
|
191
|
-
"flex shrink-0 items-center justify-center",
|
|
192
|
-
mediaTransitionClassName,
|
|
193
|
-
mediaBoxClassName,
|
|
194
|
-
titleIconClassName ?? "bg-brand-soft text-brand-emphasis"
|
|
195
|
-
),
|
|
196
|
-
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: resolvedTitleIcon, size: mediaIconSize, fill: true, className: "text-inherit" })
|
|
197
|
-
}
|
|
198
|
-
),
|
|
199
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
200
|
-
eyebrow && /* @__PURE__ */ jsx(ControlledCollapse, { open: !isCollapsed, children: /* @__PURE__ */ jsx("span", { className: "mb-0.5 block text-xs font-semibold uppercase tracking-widest text-foreground-faint", children: eyebrow }) }),
|
|
201
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
202
|
-
titleContent ?? /* @__PURE__ */ jsx(
|
|
203
|
-
"span",
|
|
204
|
-
{
|
|
205
|
-
className: classNames(
|
|
206
|
-
"min-w-0 truncate",
|
|
207
|
-
isCompactLayout ? "text-sm font-semibold text-foreground" : "text-lg font-bold tracking-tight text-foreground laptop:text-xl",
|
|
208
|
-
titleClassName
|
|
209
|
-
),
|
|
210
|
-
children: capitalizeTitle ? capitalizeAllLetters(title) : title
|
|
211
|
-
}
|
|
212
|
-
),
|
|
213
|
-
titleSlot
|
|
214
|
-
] }),
|
|
215
|
-
headline && /* @__PURE__ */ jsx(ControlledCollapse, { open: !isCollapsed, children: /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-xs text-foreground-subtle", children: headline }) }),
|
|
216
|
-
subtitleSlot && /* @__PURE__ */ jsx(ControlledCollapse, { open: !isCollapsed, children: /* @__PURE__ */ jsx("div", { className: "mt-1 flex min-w-0 flex-col gap-1", children: subtitleSlot }) })
|
|
217
|
-
] })
|
|
218
|
-
] }),
|
|
219
|
-
slot && /* @__PURE__ */ jsx(
|
|
220
|
-
"div",
|
|
155
|
+
className
|
|
156
|
+
),
|
|
157
|
+
style: {
|
|
158
|
+
width: canFloat ? isFirefox() ? "-moz-available" : "-webkit-fill-available" : "100%"
|
|
159
|
+
},
|
|
160
|
+
children: [
|
|
161
|
+
resolvedBreadcrumb && !isCollapsed && /* @__PURE__ */ jsx("div", { className: "mb-2 shrink-0", children: resolvedBreadcrumb }),
|
|
162
|
+
/* @__PURE__ */ jsxs(
|
|
163
|
+
"div",
|
|
164
|
+
{
|
|
165
|
+
className: classNames("flex w-full flex-wrap justify-between gap-x-4 gap-y-2", {
|
|
166
|
+
"items-start": slotAlign === "start",
|
|
167
|
+
"items-center": slotAlign !== "start",
|
|
168
|
+
"bg-surface py-5": canFloat
|
|
169
|
+
}),
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ jsxs("div", { className: classNames("flex min-w-0 flex-1 basis-60 items-center", isCompactLayout ? "gap-2" : "gap-3"), children: [
|
|
172
|
+
leadingSlot,
|
|
173
|
+
canBack && /* @__PURE__ */ jsx(ControlledTooltip, { content: t("actions.back"), children: /* @__PURE__ */ jsx(
|
|
174
|
+
ControlledButton,
|
|
221
175
|
{
|
|
176
|
+
defaultHtmlButton: true,
|
|
177
|
+
onClick: handleBack,
|
|
178
|
+
disabled: disableBack,
|
|
222
179
|
className: classNames(
|
|
223
|
-
"
|
|
224
|
-
|
|
180
|
+
"flex shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-interactive duration-base ease-enter hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border active:scale-90 disabled:pointer-events-none disabled:text-foreground-faint",
|
|
181
|
+
isCompactLayout ? "size-8" : "size-9"
|
|
225
182
|
),
|
|
226
|
-
children:
|
|
183
|
+
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "arrow_back", size: isCompactLayout ? 18 : 20 })
|
|
184
|
+
}
|
|
185
|
+
) }),
|
|
186
|
+
rendersTitleImage && titleImage ? /* @__PURE__ */ jsx(
|
|
187
|
+
"img",
|
|
188
|
+
{
|
|
189
|
+
src: titleImage.src,
|
|
190
|
+
alt: titleImage.alt,
|
|
191
|
+
onError: () => setFailedTitleImageSrc(titleImage.src),
|
|
192
|
+
className: twMerge(
|
|
193
|
+
"shrink-0 border border-border object-cover",
|
|
194
|
+
mediaTransitionClassName,
|
|
195
|
+
mediaBoxClassName,
|
|
196
|
+
titleImage.className
|
|
197
|
+
)
|
|
227
198
|
}
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
199
|
+
) : resolvedTitleIcon && /* @__PURE__ */ jsx(
|
|
200
|
+
"span",
|
|
201
|
+
{
|
|
202
|
+
className: twMerge(
|
|
203
|
+
"flex shrink-0 items-center justify-center",
|
|
204
|
+
mediaTransitionClassName,
|
|
205
|
+
mediaBoxClassName,
|
|
206
|
+
titleIconClassName ?? "bg-brand-soft text-brand-emphasis"
|
|
207
|
+
),
|
|
208
|
+
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: resolvedTitleIcon, size: mediaIconSize, fill: true, className: "text-inherit" })
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
212
|
+
eyebrow && !isCollapsed && /* @__PURE__ */ jsx("span", { className: "mb-0.5 text-xs font-semibold uppercase tracking-widest text-foreground-faint", children: eyebrow }),
|
|
213
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
214
|
+
titleContent ?? /* @__PURE__ */ jsx(
|
|
215
|
+
"span",
|
|
216
|
+
{
|
|
217
|
+
className: classNames(
|
|
218
|
+
"min-w-0 truncate",
|
|
219
|
+
isCompactLayout ? "text-sm font-semibold text-foreground" : "text-lg font-bold tracking-tight text-foreground laptop:text-xl",
|
|
220
|
+
titleClassName
|
|
221
|
+
),
|
|
222
|
+
children: capitalizeTitle ? capitalizeAllLetters(title) : title
|
|
223
|
+
}
|
|
224
|
+
),
|
|
225
|
+
titleSlot
|
|
226
|
+
] }),
|
|
227
|
+
headline && !isCollapsed && /* @__PURE__ */ jsx("span", { className: "mt-0.5 text-xs text-foreground-subtle", children: headline }),
|
|
228
|
+
subtitleSlot && !isCollapsed && /* @__PURE__ */ jsx("div", { className: "mt-1 flex min-w-0 flex-col gap-1", children: subtitleSlot })
|
|
229
|
+
] })
|
|
230
|
+
] }),
|
|
231
|
+
slot && /* @__PURE__ */ jsx(
|
|
232
|
+
"div",
|
|
233
|
+
{
|
|
234
|
+
className: classNames(
|
|
235
|
+
"ml-auto flex shrink-0 flex-wrap justify-end gap-2",
|
|
236
|
+
slotAlign === "start" ? "items-start" : "items-center"
|
|
237
|
+
),
|
|
238
|
+
children: slot
|
|
239
|
+
}
|
|
241
240
|
)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
),
|
|
244
|
+
showDivider && !canFloat && (title.length > 0 || Boolean(titleContent)) && /* @__PURE__ */ jsx("hr", { className: classNames("border-border-subtle", isCompactLayout ? "mb-2 mt-2" : "mb-2 mt-3") })
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
) });
|
|
248
248
|
};
|
|
249
249
|
export {
|
|
250
250
|
PageTitle as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageTitle.js","sources":["../../../src/components/Navigation/PageTitle.tsx"],"sourcesContent":["import { ReactNode, RefObject, useEffect, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledCollapse from '@/components/UI/ControlledCollapse';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { capitalizeAllLetters } from '@/helpers/stringHelpers';\n\nimport { MaterialSymbol, SymbolCodepoints } from '../UI/MaterialSymbol';\n\nimport ControlledBreadcrumb from './ControlledBreadcrumb';\nimport { PageTitleCollapseProvider } from './PageTitleCollapseContext';\nimport { usePageTitleNavigation } from './PageTitleNavigationContext';\nimport { useUiNavigation } from './UiNavigationContext';\n\nexport interface IPageTitleImage {\n src: string;\n /** The image REPLACES the icon chip, so it carries the accessible name of what it depicts. */\n alt: string;\n /** Escape hatch for shape and size, e.g. `rounded-full` for an avatar. Merged last, so it wins. */\n className?: string;\n}\n\nconst MEDIA_BOX_CLASSNAME: Record<'sm' | 'md' | 'lg', string> = {\n sm: 'size-8 rounded-lg',\n md: 'size-10 rounded-xl',\n lg: 'size-20 rounded-xl',\n};\n\nconst MEDIA_ICON_SIZE: Record<'sm' | 'md' | 'lg', number> = { sm: 18, md: 22, lg: 34 };\n\n/** One step down, so a record cover stays recognisable in the collapsed header instead of vanishing. */\nconst COLLAPSED_MEDIA_SIZE: Record<'sm' | 'md' | 'lg', 'sm' | 'md' | 'lg'> = { sm: 'sm', md: 'sm', lg: 'md' };\n\n/**\n * Thresholds for the sibling-container case, in px. They differ on purpose: collapsing and expanding\n * at the same position makes the header flicker for a whole gesture when a scroll lands on it.\n */\nconst COLLAPSE_AT_SCROLL = 48;\nconst EXPAND_AT_SCROLL = 16;\n\nconst findScrollParent = (node: HTMLElement | null): HTMLElement | null => {\n let current = node?.parentElement ?? null;\n while (current) {\n const { overflowY } = getComputedStyle(current);\n if (overflowY === 'auto' || overflowY === 'scroll') return current;\n current = current.parentElement;\n }\n return null;\n};\n\ninterface IPageTitleProps {\n title?: string;\n /** Substitui o span do título por conteúdo rico (ex.: nome editável inline da ficha técnica). */\n titleContent?: ReactNode;\n /** Rótulo pequeno em caixa alta acima do título (ex.: \"FICHA TÉCNICA\"). */\n eyebrow?: string;\n /** Conteúdo abaixo da linha do título, dentro da coluna de texto (tags, steppers). */\n subtitleSlot?: ReactNode;\n slot?: ReactNode;\n /**\n * Where the `slot` sits against the text column. `center` (default) is right for a one-line title with\n * a couple of buttons; `start` pins it to the TOP of the header, which is what a tall identification\n * block needs so its actions line up with the eyebrow instead of floating at the middle.\n */\n slotAlign?: 'center' | 'start';\n /** Conteúdo renderizado à ESQUERDA, imediatamente ao lado do título (o `slot` fica à direita). */\n titleSlot?: ReactNode;\n /** Conteúdo renderizado ANTES do ícone/título (ex.: botão voltar customizado, alvo de drop). */\n leadingSlot?: ReactNode;\n /** Trilha custom acima do título (use `ControlledBreadcrumb`). Sem ela, a trilha é derivada do menu. */\n breadcrumb?: ReactNode;\n /** Deriva a trilha automaticamente da árvore de navegação quando `breadcrumb` não é passado. */\n autoBreadcrumb?: boolean;\n /** Ícone em chip à esquerda do título. Sem ele, herda o ícone do item de menu da rota atual. */\n titleIcon?: SymbolCodepoints;\n titleIconClassName?: string;\n /**\n * An image in the icon chip's place, at the same size and radius (e.g. the product cover on a\n * datasheet header). A src that fails to load falls back to the icon instead of leaving a broken\n * image in the header.\n */\n titleImage?: IPageTitleImage;\n /**\n * Size of the leading media (icon chip or `titleImage`). `md` is the 2.5rem chip every page uses;\n * `lg` is the 5rem cover of a RECORD header, where the image identifies the record itself (a product\n * datasheet). `compact` already implies `sm`, so pass this only to go bigger.\n */\n titleMediaSize?: 'sm' | 'md' | 'lg';\n /**\n * The header shrinks once the page under it has been scrolled: the breadcrumb, the eyebrow, the\n * headline and the `subtitleSlot` collapse away, the media drops one size and the title takes its\n * compact type, while the back button and the `slot` stay exactly where they were. The header also\n * pins itself to the top of its scroll container, which is what makes shrinking it worth anything:\n * a header that scrolls away does not need to be smaller.\n *\n * On by default. Turn it off for a header that must keep its full identity on screen, and for one\n * that is not at the top of a scrolling page (inside a card, a dialog or a print sheet).\n */\n collapseOnScroll?: boolean;\n /**\n * The element whose scrolling drives the collapse. Only needed when the page's content scrolls in a\n * container that is NOT an ancestor of the header, which is what a split layout does (a fixed header\n * over an independently scrolling body). Left out, the header finds its own scrollable ancestor.\n */\n collapseScrollRef?: RefObject<HTMLElement | null>;\n /** Deriva o ícone automaticamente do menu quando `titleIcon` não é passado. */\n autoIcon?: boolean;\n /** Variante compacta: título menor, para headers de navegação interna (ex.: pastas). */\n compact?: boolean;\n /** Desligue para títulos digitados pelo usuário (nomes de pastas etc.), que não podem ter a caixa alterada. */\n capitalizeTitle?: boolean;\n /** Desligue quando o header é seguido imediatamente por tabs ou outra divisão própria. */\n showDivider?: boolean;\n canBack?: boolean;\n canBackTo?: string;\n canFloat?: boolean;\n headline?: string;\n className?: string;\n titleClassName?: string;\n onBackClick?: () => void;\n disableBack?: boolean;\n}\n\nconst PageTitle: React.FC<IPageTitleProps> = ({\n canBack = false,\n canFloat = false,\n canBackTo,\n title = '',\n titleContent,\n eyebrow,\n subtitleSlot,\n headline,\n slot,\n slotAlign = 'center',\n titleSlot,\n leadingSlot,\n breadcrumb,\n autoBreadcrumb = true,\n titleIcon,\n titleIconClassName,\n titleImage,\n titleMediaSize,\n collapseOnScroll = true,\n collapseScrollRef,\n autoIcon = true,\n compact = false,\n capitalizeTitle = true,\n showDivider = true,\n className,\n titleClassName,\n onBackClick,\n disableBack = false,\n}) => {\n const { t } = useTranslation('ui');\n const { navigate, goBack, location } = useUiNavigation();\n\n const isFirefox = () => typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().includes('firefox');\n\n const handleBack = () => {\n if (disableBack) return;\n\n if (onBackClick) {\n onBackClick();\n } else if (canBackTo) {\n navigate(canBackTo);\n } else {\n goBack();\n }\n };\n\n const { resolveTrail } = usePageTitleNavigation();\n\n const menuTrail = useMemo(() => {\n const needsTrail = (autoBreadcrumb && !breadcrumb && !canFloat) || (autoIcon && !titleIcon && !titleImage);\n return needsTrail ? resolveTrail(location.pathname) : [];\n }, [autoBreadcrumb, breadcrumb, canFloat, autoIcon, titleIcon, titleImage, location.pathname, resolveTrail]);\n\n const resolvedBreadcrumb =\n breadcrumb ??\n (autoBreadcrumb && !canFloat && menuTrail.length >= 2 ? (\n <ControlledBreadcrumb\n ariaLabel={t('pageTitle.breadcrumbLabel')}\n items={menuTrail.map((item, index) => ({\n key: item.key,\n label: item.label,\n to: index < menuTrail.length - 1 ? item.to : undefined,\n }))}\n />\n ) : undefined);\n\n const resolvedTitleIcon = titleIcon ?? (autoIcon ? menuTrail[menuTrail.length - 1]?.icon : undefined);\n\n // Holds the src that failed rather than a boolean, so pointing at another image retries on its own.\n const [failedTitleImageSrc, setFailedTitleImageSrc] = useState<string | null>(null);\n const rendersTitleImage = Boolean(titleImage?.src) && titleImage?.src !== failedTitleImageSrc;\n\n const sentinelRef = useRef<HTMLDivElement>(null);\n const [isCollapsed, setIsCollapsed] = useState(false);\n // A floating header is already pinned by its own mechanism, and collapsing it would fight it.\n const collapses = collapseOnScroll && !canFloat;\n\n useEffect(() => {\n if (!collapses) {\n setIsCollapsed(false);\n return;\n }\n\n // Sibling container: the header does not move with the content, so its own position says\n // nothing and the scroll offset is the only signal.\n const siblingContainer = collapseScrollRef?.current;\n if (siblingContainer) {\n const handleScroll = () => {\n const offset = siblingContainer.scrollTop;\n setIsCollapsed((previous) => (previous ? offset > EXPAND_AT_SCROLL : offset >= COLLAPSE_AT_SCROLL));\n };\n handleScroll();\n siblingContainer.addEventListener('scroll', handleScroll, { passive: true });\n return () => siblingContainer.removeEventListener('scroll', handleScroll);\n }\n\n // Ancestor container: the sentinel sits ABOVE the sticky header, in the normal flow, so it\n // leaves the scrollport exactly when the header pins itself. Watching the header instead would\n // never fire, since a pinned element stays in view by definition.\n const sentinel = sentinelRef.current;\n if (!sentinel || typeof IntersectionObserver === 'undefined') return;\n\n const observer = new IntersectionObserver(([entry]) => setIsCollapsed(!entry.isIntersecting), {\n root: findScrollParent(sentinel),\n threshold: 0,\n });\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [collapses, collapseScrollRef]);\n\n const isCompactLayout = compact || isCollapsed;\n const resolvedMediaSize = titleMediaSize ?? (compact ? 'sm' : 'md');\n const mediaSize = isCollapsed ? COLLAPSED_MEDIA_SIZE[resolvedMediaSize] : resolvedMediaSize;\n const mediaBoxClassName = MEDIA_BOX_CLASSNAME[mediaSize];\n const mediaIconSize = MEDIA_ICON_SIZE[mediaSize];\n const mediaTransitionClassName = 'transition-[width,height] duration-base ease-enter';\n const collapseValue = useMemo(() => ({ isCollapsed }), [isCollapsed]);\n\n return (\n <PageTitleCollapseProvider value={collapseValue}>\n {/* Sits in the normal flow, right above the pinned header, so it is the thing that scrolls\n out of view when the header pins itself. Zero height: it must not add rhythm. */}\n {collapses && <div ref={sentinelRef} aria-hidden className=\"-mt-px h-px shrink-0\" />}\n\n <div\n className={classNames(\n 'flex flex-col',\n {\n 'zIndexMax fixed top-16 mr-7': canFloat,\n // Pins under the app header, which publishes its own height; hosts without the shell\n // stick at the top of their scroll container.\n 'sticky top-[var(--page-header-sticky-top,0px)] z-30': collapses,\n 'shadow-elevation1 glass-subtle': collapses && isCollapsed,\n },\n className,\n )}\n style={{\n width: canFloat ? (isFirefox() ? '-moz-available' : '-webkit-fill-available') : '100%',\n }}\n >\n {resolvedBreadcrumb && (\n <ControlledCollapse open={!isCollapsed} className=\"shrink-0\">\n <div className=\"mb-2\">{resolvedBreadcrumb}</div>\n </ControlledCollapse>\n )}\n\n <div\n className={classNames('flex w-full flex-wrap justify-between gap-x-4 gap-y-2', {\n 'items-start': slotAlign === 'start',\n 'items-center': slotAlign !== 'start',\n 'bg-surface py-5': canFloat,\n })}\n >\n <div className={classNames('flex min-w-0 flex-1 basis-60 items-center', isCompactLayout ? 'gap-2' : 'gap-3')}>\n {leadingSlot}\n\n {canBack && (\n <ControlledTooltip content={t('actions.back')}>\n <ControlledButton\n defaultHtmlButton\n onClick={handleBack}\n disabled={disableBack}\n className={classNames(\n 'flex shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-interactive duration-base ease-enter hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border active:scale-90 disabled:pointer-events-none disabled:text-foreground-faint',\n isCompactLayout ? 'size-8' : 'size-9',\n )}\n >\n <MaterialSymbol icon=\"arrow_back\" size={isCompactLayout ? 18 : 20} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n {rendersTitleImage && titleImage ? (\n <img\n src={titleImage.src}\n alt={titleImage.alt}\n onError={() => setFailedTitleImageSrc(titleImage.src)}\n className={twMerge(\n 'shrink-0 border border-border object-cover',\n mediaTransitionClassName,\n mediaBoxClassName,\n titleImage.className,\n )}\n />\n ) : (\n resolvedTitleIcon && (\n <span\n className={twMerge(\n 'flex shrink-0 items-center justify-center',\n mediaTransitionClassName,\n mediaBoxClassName,\n titleIconClassName ?? 'bg-brand-soft text-brand-emphasis',\n )}\n >\n <MaterialSymbol icon={resolvedTitleIcon} size={mediaIconSize} fill className=\"text-inherit\" />\n </span>\n )\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {eyebrow && (\n <ControlledCollapse open={!isCollapsed}>\n <span className=\"mb-0.5 block text-xs font-semibold uppercase tracking-widest text-foreground-faint\">\n {eyebrow}\n </span>\n </ControlledCollapse>\n )}\n\n <div className=\"flex min-w-0 items-center gap-2\">\n {titleContent ?? (\n <span\n className={classNames(\n 'min-w-0 truncate',\n isCompactLayout\n ? 'text-sm font-semibold text-foreground'\n : 'text-lg font-bold tracking-tight text-foreground laptop:text-xl',\n titleClassName,\n )}\n >\n {capitalizeTitle ? capitalizeAllLetters(title) : title}\n </span>\n )}\n\n {titleSlot}\n </div>\n\n {headline && (\n <ControlledCollapse open={!isCollapsed}>\n <span className=\"mt-0.5 block text-xs text-foreground-subtle\">{headline}</span>\n </ControlledCollapse>\n )}\n\n {subtitleSlot && (\n <ControlledCollapse open={!isCollapsed}>\n <div className=\"mt-1 flex min-w-0 flex-col gap-1\">{subtitleSlot}</div>\n </ControlledCollapse>\n )}\n </div>\n </div>\n\n {slot && (\n <div\n className={classNames(\n 'ml-auto flex shrink-0 flex-wrap justify-end gap-2',\n slotAlign === 'start' ? 'items-start' : 'items-center',\n )}\n >\n {slot}\n </div>\n )}\n </div>\n {showDivider && !canFloat && (title.length > 0 || Boolean(titleContent)) && (\n // Pinned, the shadow already separates the header from what slides under it, and a rule on\n // top of it reads as a seam.\n <hr\n className={classNames(\n 'border-border-subtle transition-opacity duration-base',\n isCompactLayout ? 'mb-2 mt-2' : 'mb-2 mt-3',\n isCollapsed && 'opacity-0',\n )}\n />\n )}\n </div>\n </PageTitleCollapseProvider>\n );\n};\n\nexport default PageTitle;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PageTitle.js","sources":["../../../src/components/Navigation/PageTitle.tsx"],"sourcesContent":["import { ReactNode, RefObject, useEffect, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { capitalizeAllLetters } from '@/helpers/stringHelpers';\n\nimport { MaterialSymbol, SymbolCodepoints } from '../UI/MaterialSymbol';\n\nimport ControlledBreadcrumb from './ControlledBreadcrumb';\nimport { PageTitleCollapseProvider } from './PageTitleCollapseContext';\nimport { usePageTitleNavigation } from './PageTitleNavigationContext';\nimport { useUiNavigation } from './UiNavigationContext';\n\nexport interface IPageTitleImage {\n src: string;\n /** The image REPLACES the icon chip, so it carries the accessible name of what it depicts. */\n alt: string;\n /** Escape hatch for shape and size, e.g. `rounded-full` for an avatar. Merged last, so it wins. */\n className?: string;\n}\n\nconst MEDIA_BOX_CLASSNAME: Record<'sm' | 'md' | 'lg', string> = {\n sm: 'size-8 rounded-lg',\n md: 'size-10 rounded-xl',\n lg: 'size-20 rounded-xl',\n};\n\nconst MEDIA_ICON_SIZE: Record<'sm' | 'md' | 'lg', number> = { sm: 18, md: 22, lg: 34 };\n\n/** One step down, so a record cover stays recognisable in the collapsed header instead of vanishing. */\nconst COLLAPSED_MEDIA_SIZE: Record<'sm' | 'md' | 'lg', 'sm' | 'md' | 'lg'> = { sm: 'sm', md: 'sm', lg: 'md' };\n\n/**\n * Thresholds for the sibling-container case, in px. They differ on purpose: collapsing and expanding\n * at the same position makes the header flicker for a whole gesture when a scroll lands on it.\n */\nconst COLLAPSE_AT_SCROLL = 48;\nconst EXPAND_AT_SCROLL = 16;\n\n/** Tolerance for the pinned test: sub-pixel layout would otherwise flip the state on its own. */\nconst PINNED_TOLERANCE_PX = 1;\n\nconst findScrollParent = (node: HTMLElement | null): HTMLElement | null => {\n let current = node?.parentElement ?? null;\n while (current) {\n const { overflowY } = getComputedStyle(current);\n if (overflowY === 'auto' || overflowY === 'scroll') return current;\n current = current.parentElement;\n }\n return null;\n};\n\ninterface IPageTitleProps {\n title?: string;\n /** Substitui o span do título por conteúdo rico (ex.: nome editável inline da ficha técnica). */\n titleContent?: ReactNode;\n /** Rótulo pequeno em caixa alta acima do título (ex.: \"FICHA TÉCNICA\"). */\n eyebrow?: string;\n /** Conteúdo abaixo da linha do título, dentro da coluna de texto (tags, steppers). */\n subtitleSlot?: ReactNode;\n slot?: ReactNode;\n /**\n * Where the `slot` sits against the text column. `center` (default) is right for a one-line title with\n * a couple of buttons; `start` pins it to the TOP of the header, which is what a tall identification\n * block needs so its actions line up with the eyebrow instead of floating at the middle.\n */\n slotAlign?: 'center' | 'start';\n /** Conteúdo renderizado à ESQUERDA, imediatamente ao lado do título (o `slot` fica à direita). */\n titleSlot?: ReactNode;\n /** Conteúdo renderizado ANTES do ícone/título (ex.: botão voltar customizado, alvo de drop). */\n leadingSlot?: ReactNode;\n /** Trilha custom acima do título (use `ControlledBreadcrumb`). Sem ela, a trilha é derivada do menu. */\n breadcrumb?: ReactNode;\n /** Deriva a trilha automaticamente da árvore de navegação quando `breadcrumb` não é passado. */\n autoBreadcrumb?: boolean;\n /** Ícone em chip à esquerda do título. Sem ele, herda o ícone do item de menu da rota atual. */\n titleIcon?: SymbolCodepoints;\n titleIconClassName?: string;\n /**\n * An image in the icon chip's place, at the same size and radius (e.g. the product cover on a\n * datasheet header). A src that fails to load falls back to the icon instead of leaving a broken\n * image in the header.\n */\n titleImage?: IPageTitleImage;\n /**\n * Size of the leading media (icon chip or `titleImage`). `md` is the 2.5rem chip every page uses;\n * `lg` is the 5rem cover of a RECORD header, where the image identifies the record itself (a product\n * datasheet). `compact` already implies `sm`, so pass this only to go bigger.\n */\n titleMediaSize?: 'sm' | 'md' | 'lg';\n /**\n * The header pins itself to the top of its scroll container and, once pinned, shrinks: the\n * breadcrumb, the eyebrow, the headline and the `subtitleSlot` go, the media drops one size (the\n * record's picture stays, at the icon chip's size) and the title takes its compact type. The back\n * button and the `slot` never move.\n *\n * EXPANDED, IT RENDERS EXACTLY WHAT IT RENDERED BEFORE THIS EXISTED: no wrapper node, no extra\n * element, no background. That is deliberate. The first version added a sentinel node as a sibling\n * of the header, and since almost every page wraps it in a `flex flex-col gap-y-*`, that node\n * became a flex item and every page grew a stray gap.\n *\n * On by default. Turn it off for a header that must keep its full identity on screen, or one that\n * is not at the top of a scrolling page (inside a card, a dialog or a print sheet).\n */\n collapseOnScroll?: boolean;\n /**\n * The element whose scrolling drives the collapse. Only needed when the page's content scrolls in a\n * container that is NOT an ancestor of the header, which is what a split layout does (a fixed header\n * over an independently scrolling body). Left out, the header finds its own scrollable ancestor.\n */\n collapseScrollRef?: RefObject<HTMLElement | null>;\n /** Deriva o ícone automaticamente do menu quando `titleIcon` não é passado. */\n autoIcon?: boolean;\n /** Variante compacta: título menor, para headers de navegação interna (ex.: pastas). */\n compact?: boolean;\n /** Desligue para títulos digitados pelo usuário (nomes de pastas etc.), que não podem ter a caixa alterada. */\n capitalizeTitle?: boolean;\n /** Desligue quando o header é seguido imediatamente por tabs ou outra divisão própria. */\n showDivider?: boolean;\n canBack?: boolean;\n canBackTo?: string;\n canFloat?: boolean;\n headline?: string;\n className?: string;\n titleClassName?: string;\n onBackClick?: () => void;\n disableBack?: boolean;\n}\n\nconst PageTitle: React.FC<IPageTitleProps> = ({\n canBack = false,\n canFloat = false,\n canBackTo,\n title = '',\n titleContent,\n eyebrow,\n subtitleSlot,\n headline,\n slot,\n slotAlign = 'center',\n titleSlot,\n leadingSlot,\n breadcrumb,\n autoBreadcrumb = true,\n titleIcon,\n titleIconClassName,\n titleImage,\n titleMediaSize,\n collapseOnScroll = true,\n collapseScrollRef,\n autoIcon = true,\n compact = false,\n capitalizeTitle = true,\n showDivider = true,\n className,\n titleClassName,\n onBackClick,\n disableBack = false,\n}) => {\n const { t } = useTranslation('ui');\n const { navigate, goBack, location } = useUiNavigation();\n\n const isFirefox = () => typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().includes('firefox');\n\n const handleBack = () => {\n if (disableBack) return;\n\n if (onBackClick) {\n onBackClick();\n } else if (canBackTo) {\n navigate(canBackTo);\n } else {\n goBack();\n }\n };\n\n const { resolveTrail } = usePageTitleNavigation();\n\n const menuTrail = useMemo(() => {\n const needsTrail = (autoBreadcrumb && !breadcrumb && !canFloat) || (autoIcon && !titleIcon && !titleImage);\n return needsTrail ? resolveTrail(location.pathname) : [];\n }, [autoBreadcrumb, breadcrumb, canFloat, autoIcon, titleIcon, titleImage, location.pathname, resolveTrail]);\n\n const resolvedBreadcrumb =\n breadcrumb ??\n (autoBreadcrumb && !canFloat && menuTrail.length >= 2 ? (\n <ControlledBreadcrumb\n ariaLabel={t('pageTitle.breadcrumbLabel')}\n items={menuTrail.map((item, index) => ({\n key: item.key,\n label: item.label,\n to: index < menuTrail.length - 1 ? item.to : undefined,\n }))}\n />\n ) : undefined);\n\n const resolvedTitleIcon = titleIcon ?? (autoIcon ? menuTrail[menuTrail.length - 1]?.icon : undefined);\n\n // Holds the src that failed rather than a boolean, so pointing at another image retries on its own.\n const [failedTitleImageSrc, setFailedTitleImageSrc] = useState<string | null>(null);\n const rendersTitleImage = Boolean(titleImage?.src) && titleImage?.src !== failedTitleImageSrc;\n\n const rootRef = useRef<HTMLDivElement>(null);\n const [isCollapsed, setIsCollapsed] = useState(false);\n // A floating header is already pinned by its own mechanism, and collapsing it would fight it.\n const collapses = collapseOnScroll && !canFloat;\n\n useEffect(() => {\n if (!collapses) {\n setIsCollapsed(false);\n return;\n }\n\n const root = rootRef.current;\n if (!root) return;\n\n const container = collapseScrollRef?.current ?? findScrollParent(root);\n if (!container) return;\n\n // Two shapes, one listener. When the container is an ANCESTOR the header travels with the page,\n // so being pinned is a question about its position; when it is a SIBLING (a split layout, where\n // the header never moves) its position says nothing and the scroll offset is all there is.\n const scrollsUnderTheHeader = container.contains(root);\n\n // Read once: both are static for the life of the listener, and reading them per frame would mean\n // a style recalculation on every scroll tick. A sticky offset is measured from the container's\n // CONTENT box, so the pinned line is its padding plus that offset, never the border box alone.\n const containerPaddingTop = Number.parseFloat(getComputedStyle(container).paddingTop) || 0;\n const stickyTop = Number.parseFloat(getComputedStyle(root).top) || 0;\n let frame = 0;\n\n const evaluate = () => {\n frame = 0;\n\n if (!scrollsUnderTheHeader) {\n const offset = container.scrollTop;\n setIsCollapsed((previous) => (previous ? offset > EXPAND_AT_SCROLL : offset >= COLLAPSE_AT_SCROLL));\n return;\n }\n\n const pinnedLine = container.getBoundingClientRect().top + containerPaddingTop + stickyTop;\n setIsCollapsed(root.getBoundingClientRect().top <= pinnedLine + PINNED_TOLERANCE_PX);\n };\n\n const handleScroll = () => {\n if (!frame) frame = requestAnimationFrame(evaluate);\n };\n\n evaluate();\n container.addEventListener('scroll', handleScroll, { passive: true });\n return () => {\n container.removeEventListener('scroll', handleScroll);\n if (frame) cancelAnimationFrame(frame);\n };\n }, [collapses, collapseScrollRef]);\n\n const isCompactLayout = compact || isCollapsed;\n const resolvedMediaSize = titleMediaSize ?? (compact ? 'sm' : 'md');\n const mediaSize = isCollapsed ? COLLAPSED_MEDIA_SIZE[resolvedMediaSize] : resolvedMediaSize;\n const mediaBoxClassName = MEDIA_BOX_CLASSNAME[mediaSize];\n const mediaIconSize = MEDIA_ICON_SIZE[mediaSize];\n const mediaTransitionClassName = 'transition-[width,height] duration-base ease-enter';\n const collapseValue = useMemo(() => ({ isCollapsed }), [isCollapsed]);\n\n return (\n <PageTitleCollapseProvider value={collapseValue}>\n <div\n ref={rootRef}\n className={classNames(\n 'flex flex-col',\n {\n 'zIndexMax fixed top-16 mr-7': canFloat,\n // Pins under the app header, which publishes its own height; hosts without the shell stick\n // at the top of their own scroll container. `sticky` is inert until it pins, so this costs\n // nothing on a page that does not scroll.\n 'sticky top-[var(--page-header-sticky-top,0px)] z-30': collapses,\n // The page's own background, and nothing else. Pinned, the header has to be opaque or the\n // content slides visibly through the title; but it is not a PANEL, so no glass, no border\n // and no shadow, which read as a second surface appearing out of nowhere mid-scroll.\n 'bg-canvas': collapses && isCollapsed,\n },\n className,\n )}\n style={{\n width: canFloat ? (isFirefox() ? '-moz-available' : '-webkit-fill-available') : '100%',\n }}\n >\n {resolvedBreadcrumb && !isCollapsed && <div className=\"mb-2 shrink-0\">{resolvedBreadcrumb}</div>}\n\n <div\n className={classNames('flex w-full flex-wrap justify-between gap-x-4 gap-y-2', {\n 'items-start': slotAlign === 'start',\n 'items-center': slotAlign !== 'start',\n 'bg-surface py-5': canFloat,\n })}\n >\n <div className={classNames('flex min-w-0 flex-1 basis-60 items-center', isCompactLayout ? 'gap-2' : 'gap-3')}>\n {leadingSlot}\n\n {canBack && (\n <ControlledTooltip content={t('actions.back')}>\n <ControlledButton\n defaultHtmlButton\n onClick={handleBack}\n disabled={disableBack}\n className={classNames(\n 'flex shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-interactive duration-base ease-enter hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border active:scale-90 disabled:pointer-events-none disabled:text-foreground-faint',\n isCompactLayout ? 'size-8' : 'size-9',\n )}\n >\n <MaterialSymbol icon=\"arrow_back\" size={isCompactLayout ? 18 : 20} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n {rendersTitleImage && titleImage ? (\n <img\n src={titleImage.src}\n alt={titleImage.alt}\n onError={() => setFailedTitleImageSrc(titleImage.src)}\n className={twMerge(\n 'shrink-0 border border-border object-cover',\n mediaTransitionClassName,\n mediaBoxClassName,\n titleImage.className,\n )}\n />\n ) : (\n resolvedTitleIcon && (\n <span\n className={twMerge(\n 'flex shrink-0 items-center justify-center',\n mediaTransitionClassName,\n mediaBoxClassName,\n titleIconClassName ?? 'bg-brand-soft text-brand-emphasis',\n )}\n >\n <MaterialSymbol icon={resolvedTitleIcon} size={mediaIconSize} fill className=\"text-inherit\" />\n </span>\n )\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {eyebrow && !isCollapsed && (\n <span className=\"mb-0.5 text-xs font-semibold uppercase tracking-widest text-foreground-faint\">\n {eyebrow}\n </span>\n )}\n\n <div className=\"flex min-w-0 items-center gap-2\">\n {titleContent ?? (\n <span\n className={classNames(\n 'min-w-0 truncate',\n isCompactLayout\n ? 'text-sm font-semibold text-foreground'\n : 'text-lg font-bold tracking-tight text-foreground laptop:text-xl',\n titleClassName,\n )}\n >\n {capitalizeTitle ? capitalizeAllLetters(title) : title}\n </span>\n )}\n\n {titleSlot}\n </div>\n\n {headline && !isCollapsed && <span className=\"mt-0.5 text-xs text-foreground-subtle\">{headline}</span>}\n\n {subtitleSlot && !isCollapsed && <div className=\"mt-1 flex min-w-0 flex-col gap-1\">{subtitleSlot}</div>}\n </div>\n </div>\n\n {slot && (\n <div\n className={classNames(\n 'ml-auto flex shrink-0 flex-wrap justify-end gap-2',\n slotAlign === 'start' ? 'items-start' : 'items-center',\n )}\n >\n {slot}\n </div>\n )}\n </div>\n {showDivider && !canFloat && (title.length > 0 || Boolean(titleContent)) && (\n <hr className={classNames('border-border-subtle', isCompactLayout ? 'mb-2 mt-2' : 'mb-2 mt-3')} />\n )}\n </div>\n </PageTitleCollapseProvider>\n );\n};\n\nexport default PageTitle;\n"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
-
import { useLocation, useNavigate, useSearchParams
|
|
2
|
+
import { forwardRef, useMemo } from "react";
|
|
3
|
+
import { Link, useLocation, useNavigate, useSearchParams } from "react-router-dom";
|
|
4
4
|
import { UiNavigationProvider } from "./UiNavigationContext.js";
|
|
5
|
-
const RouterLink = ({ to, children, ...rest }) => /* @__PURE__ */ jsx(Link, { to, ...rest, children });
|
|
5
|
+
const RouterLink = forwardRef(({ to, children, ...rest }, ref) => /* @__PURE__ */ jsx(Link, { ref, to, ...rest, children }));
|
|
6
|
+
RouterLink.displayName = "RouterLink";
|
|
6
7
|
const UiReactRouterNavigationProvider = ({ children }) => {
|
|
7
8
|
const location = useLocation();
|
|
8
9
|
const navigate = useNavigate();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactRouterNavigationAdapter.js","sources":["../../../src/components/Navigation/ReactRouterNavigationAdapter.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { Link as ReactRouterDomLink, useLocation, useNavigate, useSearchParams } from 'react-router-dom';\n\nimport { IUiLinkProps, IUiNavigationApi, UiNavigationProvider } from './UiNavigationContext';\n\nconst RouterLink
|
|
1
|
+
{"version":3,"file":"ReactRouterNavigationAdapter.js","sources":["../../../src/components/Navigation/ReactRouterNavigationAdapter.tsx"],"sourcesContent":["import { forwardRef, useMemo } from 'react';\nimport { Link as ReactRouterDomLink, useLocation, useNavigate, useSearchParams } from 'react-router-dom';\n\nimport { IUiLinkProps, IUiNavigationApi, UiNavigationProvider } from './UiNavigationContext';\n\n/**\n * Forwards its ref because a link is a legitimate tooltip and menu trigger, and both of those clone\n * their child to inject one. As a plain function component it warned on every render of the app\n * header (\"Function components cannot be given refs\") and silently lost the anchor the surface\n * positions against.\n */\nconst RouterLink = forwardRef<HTMLAnchorElement, IUiLinkProps>(({ to, children, ...rest }, ref) => (\n <ReactRouterDomLink ref={ref} to={to} {...rest}>\n {children}\n </ReactRouterDomLink>\n));\n\nRouterLink.displayName = 'RouterLink';\n\n/**\n * Binds the navigation port to react-router. The ONLY module in the package that imports\n * `react-router-dom` (an optional peer): hosts without it simply never import this file.\n */\nexport const UiReactRouterNavigationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {\n const location = useLocation();\n const navigate = useNavigate();\n const [searchParams, setSearchParams] = useSearchParams();\n\n const value = useMemo<IUiNavigationApi>(\n () => ({\n location: { pathname: location.pathname, search: location.search, state: location.state },\n searchParams,\n navigate: (to, options) => navigate(to, options),\n goBack: () => navigate(-1),\n setSearchParams: (updater, options) => setSearchParams(updater, options),\n Link: RouterLink,\n }),\n [location.pathname, location.search, location.state, searchParams, navigate, setSearchParams],\n );\n\n return <UiNavigationProvider value={value}>{children}</UiNavigationProvider>;\n};\n"],"names":["ReactRouterDomLink"],"mappings":";;;;AAWA,MAAM,aAAa,WAA4C,CAAC,EAAE,IAAI,UAAU,GAAG,QAAQ,4BACxFA,MAAA,EAAmB,KAAU,IAAS,GAAG,MACvC,UACH,CACD;AAED,WAAW,cAAc;AAMlB,MAAM,kCAA2E,CAAC,EAAE,eAAe;AACxG,QAAM,WAAW,YAAA;AACjB,QAAM,WAAW,YAAA;AACjB,QAAM,CAAC,cAAc,eAAe,IAAI,gBAAA;AAExC,QAAM,QAAQ;AAAA,IACZ,OAAO;AAAA,MACL,UAAU,EAAE,UAAU,SAAS,UAAU,QAAQ,SAAS,QAAQ,OAAO,SAAS,MAAA;AAAA,MAClF;AAAA,MACA,UAAU,CAAC,IAAI,YAAY,SAAS,IAAI,OAAO;AAAA,MAC/C,QAAQ,MAAM,SAAS,EAAE;AAAA,MACzB,iBAAiB,CAAC,SAAS,YAAY,gBAAgB,SAAS,OAAO;AAAA,MACvE,MAAM;AAAA,IAAA;AAAA,IAER,CAAC,SAAS,UAAU,SAAS,QAAQ,SAAS,OAAO,cAAc,UAAU,eAAe;AAAA,EAAA;AAG9F,SAAO,oBAAC,sBAAA,EAAqB,OAAe,SAAA,CAAS;AACvD;"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useMemo } from "react";
|
|
3
|
-
const FallbackLink = ({ to, children, ...rest }) => /* @__PURE__ */ jsx("a", { href: to, ...rest, children });
|
|
2
|
+
import { forwardRef, createContext, useContext, useMemo } from "react";
|
|
3
|
+
const FallbackLink = forwardRef(({ to, children, ...rest }, ref) => /* @__PURE__ */ jsx("a", { ref, href: to, ...rest, children }));
|
|
4
|
+
FallbackLink.displayName = "FallbackLink";
|
|
4
5
|
const createFallbackNavigation = () => {
|
|
5
6
|
const hasWindow = typeof window !== "undefined";
|
|
6
7
|
const currentSearch = hasWindow ? window.location.search : "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UiNavigationContext.js","sources":["../../../src/components/Navigation/UiNavigationContext.tsx"],"sourcesContent":["import { AnchorHTMLAttributes, createContext, ReactNode, useContext, useMemo } from 'react';\n\nexport interface IUiNavigationLocation {\n pathname: string;\n search: string;\n /** Ephemeral navigation state (react-router). Hosts without it always expose undefined. */\n state?: unknown;\n}\n\nexport interface IUiLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {\n to: string;\n children?: ReactNode;\n}\n\nexport type TUiSearchParamsSetter = (\n updater: (previousParams: URLSearchParams) => URLSearchParams,\n options?: { replace?: boolean },\n) => void;\n\n/**\n * The navigation port: everything the components need from a router, and nothing else.\n * The host decides who fulfils it: `UiReactRouterNavigationProvider` (react-router apps),\n * a hand-rolled provider on `next/navigation` (Next.js), or nothing at all, in which case\n * a window-based fallback keeps links and URL sync working with full page loads.\n */\nexport interface IUiNavigationApi {\n location: IUiNavigationLocation;\n searchParams: URLSearchParams;\n navigate: (to: string, options?: { replace?: boolean; state?: unknown }) => void;\n goBack: () => void;\n setSearchParams: TUiSearchParamsSetter;\n Link: React.ComponentType<IUiLinkProps>;\n}\n\nconst FallbackLink
|
|
1
|
+
{"version":3,"file":"UiNavigationContext.js","sources":["../../../src/components/Navigation/UiNavigationContext.tsx"],"sourcesContent":["import { AnchorHTMLAttributes, createContext, forwardRef, ReactNode, useContext, useMemo } from 'react';\n\nexport interface IUiNavigationLocation {\n pathname: string;\n search: string;\n /** Ephemeral navigation state (react-router). Hosts without it always expose undefined. */\n state?: unknown;\n}\n\nexport interface IUiLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {\n to: string;\n children?: ReactNode;\n}\n\nexport type TUiSearchParamsSetter = (\n updater: (previousParams: URLSearchParams) => URLSearchParams,\n options?: { replace?: boolean },\n) => void;\n\n/**\n * The navigation port: everything the components need from a router, and nothing else.\n * The host decides who fulfils it: `UiReactRouterNavigationProvider` (react-router apps),\n * a hand-rolled provider on `next/navigation` (Next.js), or nothing at all, in which case\n * a window-based fallback keeps links and URL sync working with full page loads.\n */\nexport interface IUiNavigationApi {\n location: IUiNavigationLocation;\n searchParams: URLSearchParams;\n navigate: (to: string, options?: { replace?: boolean; state?: unknown }) => void;\n goBack: () => void;\n setSearchParams: TUiSearchParamsSetter;\n Link: React.ComponentType<IUiLinkProps>;\n}\n\n/** Forwards its ref for the same reason `RouterLink` does: a link is a tooltip and menu trigger. */\nconst FallbackLink = forwardRef<HTMLAnchorElement, IUiLinkProps>(({ to, children, ...rest }, ref) => (\n <a ref={ref} href={to} {...rest}>\n {children}\n </a>\n));\n\nFallbackLink.displayName = 'FallbackLink';\n\nconst createFallbackNavigation = (): IUiNavigationApi => {\n const hasWindow = typeof window !== 'undefined';\n const currentSearch = hasWindow ? window.location.search : '';\n\n return {\n location: {\n pathname: hasWindow ? window.location.pathname : '/',\n search: currentSearch,\n state: undefined,\n },\n searchParams: new URLSearchParams(currentSearch),\n navigate: (to, options) => {\n if (!hasWindow) return;\n if (options?.replace) window.location.replace(to);\n else window.location.assign(to);\n },\n goBack: () => {\n if (hasWindow) window.history.back();\n },\n setSearchParams: (updater, options) => {\n if (!hasWindow) return;\n const nextParams = updater(new URLSearchParams(window.location.search));\n const query = nextParams.toString();\n const nextUrl = `${window.location.pathname}${query ? `?${query}` : ''}${window.location.hash}`;\n if (options?.replace) window.history.replaceState(window.history.state, '', nextUrl);\n else window.history.pushState(window.history.state, '', nextUrl);\n },\n Link: FallbackLink,\n };\n};\n\nconst UiNavigationContext = createContext<IUiNavigationApi | null>(null);\n\ninterface IUiNavigationProviderProps {\n value: IUiNavigationApi;\n children: ReactNode;\n}\n\nexport const UiNavigationProvider: React.FC<IUiNavigationProviderProps> = ({ value, children }) => (\n <UiNavigationContext.Provider value={value}>{children}</UiNavigationContext.Provider>\n);\n\nexport const useUiNavigation = (): IUiNavigationApi => {\n const fromContext = useContext(UiNavigationContext);\n return useMemo(() => fromContext ?? createFallbackNavigation(), [fromContext]);\n};\n"],"names":[],"mappings":";;AAmCA,MAAM,eAAe,WAA4C,CAAC,EAAE,IAAI,UAAU,GAAG,KAAA,GAAQ,QAC3F,oBAAC,OAAE,KAAU,MAAM,IAAK,GAAG,MACxB,UACH,CACD;AAED,aAAa,cAAc;AAE3B,MAAM,2BAA2B,MAAwB;AACvD,QAAM,YAAY,OAAO,WAAW;AACpC,QAAM,gBAAgB,YAAY,OAAO,SAAS,SAAS;AAE3D,SAAO;AAAA,IACL,UAAU;AAAA,MACR,UAAU,YAAY,OAAO,SAAS,WAAW;AAAA,MACjD,QAAQ;AAAA,MACR,OAAO;AAAA,IAAA;AAAA,IAET,cAAc,IAAI,gBAAgB,aAAa;AAAA,IAC/C,UAAU,CAAC,IAAI,YAAY;AACzB,UAAI,CAAC,UAAW;AAChB,UAAI,SAAS,QAAS,QAAO,SAAS,QAAQ,EAAE;AAAA,UAC3C,QAAO,SAAS,OAAO,EAAE;AAAA,IAChC;AAAA,IACA,QAAQ,MAAM;AACZ,UAAI,UAAW,QAAO,QAAQ,KAAA;AAAA,IAChC;AAAA,IACA,iBAAiB,CAAC,SAAS,YAAY;AACrC,UAAI,CAAC,UAAW;AAChB,YAAM,aAAa,QAAQ,IAAI,gBAAgB,OAAO,SAAS,MAAM,CAAC;AACtE,YAAM,QAAQ,WAAW,SAAA;AACzB,YAAM,UAAU,GAAG,OAAO,SAAS,QAAQ,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE,GAAG,OAAO,SAAS,IAAI;AAC7F,UAAI,SAAS,QAAS,QAAO,QAAQ,aAAa,OAAO,QAAQ,OAAO,IAAI,OAAO;AAAA,kBACvE,QAAQ,UAAU,OAAO,QAAQ,OAAO,IAAI,OAAO;AAAA,IACjE;AAAA,IACA,MAAM;AAAA,EAAA;AAEV;AAEA,MAAM,sBAAsB,cAAuC,IAAI;AAOhE,MAAM,uBAA6D,CAAC,EAAE,OAAO,SAAA,MAClF,oBAAC,oBAAoB,UAApB,EAA6B,OAAe,SAAA,CAAS;AAGjD,MAAM,kBAAkB,MAAwB;AACrD,QAAM,cAAc,WAAW,mBAAmB;AAClD,SAAO,QAAQ,MAAM,eAAe,4BAA4B,CAAC,WAAW,CAAC;AAC/E;"}
|
|
@@ -84,8 +84,14 @@ const AppShell = ({
|
|
|
84
84
|
{
|
|
85
85
|
id: "scrollableDiv",
|
|
86
86
|
ref: scrollRef,
|
|
87
|
-
className: "z-30 h-screen w-full px-4
|
|
88
|
-
style: {
|
|
87
|
+
className: "z-30 h-screen w-full px-4 tablet:px-6",
|
|
88
|
+
style: {
|
|
89
|
+
overflow: "auto",
|
|
90
|
+
paddingTop: "var(--page-scroll-padding-top)",
|
|
91
|
+
["--page-scroll-padding-top"]: "6rem",
|
|
92
|
+
["--app-header-height"]: "4rem",
|
|
93
|
+
["--page-header-sticky-top"]: "calc(var(--app-header-height) - var(--page-scroll-padding-top))"
|
|
94
|
+
},
|
|
89
95
|
children
|
|
90
96
|
}
|
|
91
97
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppShell.js","sources":["../../../src/components/Shell/AppShell.tsx"],"sourcesContent":["import { ReactNode, useEffect, useRef } from 'react';\nimport classNames from 'classnames';\n\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\n\nimport AppHeader from './AppHeader';\nimport AppSidebar from './AppSidebar';\nimport { ISidebarMenuItem } from './SidebarMenu';\nimport { useSidebarState } from './useSidebarState';\n\nimport '../../styles/sidebar.css';\n\ninterface IAppShellProps {\n items: Array<ISidebarMenuItem>;\n homePath: string;\n logo?: ReactNode;\n toolsSlot?: ReactNode;\n controlsSlot?: ReactNode;\n userMenuSlot?: ReactNode;\n /** Sidebar footer sign-out row; hidden when absent. */\n onLogout?: () => void;\n children: ReactNode;\n}\n\n/**\n * The uMode application chrome: fixed glass header, rail/dock/hover sidebar (pin persisted\n * through uiPreferences, Ctrl+\\ shortcut) and the scroll container every page renders into.\n * Everything app-shaped (menu items, tools, account surface, logout) arrives as props/slots.\n */\nconst AppShell: React.FC<IAppShellProps> = ({\n items,\n homePath,\n logo,\n toolsSlot,\n controlsSlot,\n userMenuSlot,\n onLogout,\n children,\n}) => {\n const { location } = useUiNavigation();\n const scrollRef = useRef<HTMLDivElement>(null);\n\n const {\n isMobile,\n isVisible,\n isDocked,\n isOverlayOnMobile,\n showRail,\n toggleLabelKey,\n togglePrimary,\n onHoverEnter,\n onHoverLeave,\n closeMobileDrawer,\n } = useSidebarState();\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: 0, behavior: 'smooth' });\n }, [location.pathname]);\n\n return (\n <div className=\"h-screen w-full bg-surface\">\n <AppHeader\n toggleSidebar={togglePrimary}\n isSidebarOpen={isDocked}\n toggleLabelKey={toggleLabelKey}\n homePath={homePath}\n logo={logo}\n toolsSlot={toolsSlot}\n controlsSlot={controlsSlot}\n userMenuSlot={userMenuSlot}\n onHoverEnter={onHoverEnter}\n onHoverLeave={onHoverLeave}\n />\n\n <main\n className={classNames('sidebar-content flex h-screen w-full', {\n 'is-rail': !isMobile && !isDocked,\n 'is-mobile': isMobile,\n })}\n >\n {isOverlayOnMobile && (\n <div\n aria-hidden\n className=\"fixed inset-0 z-40 bg-scrim motion-reduce:transition-none\"\n onClick={closeMobileDrawer}\n />\n )}\n\n <AppSidebar\n items={items}\n isMobile={isMobile}\n isVisible={isVisible}\n collapsed={showRail}\n isFloating={!isMobile && !showRail && !isDocked}\n onHoverEnter={onHoverEnter}\n onHoverLeave={onHoverLeave}\n onNavigate={isMobile ? closeMobileDrawer : undefined}\n onLogout={onLogout}\n />\n\n <div\n id=\"scrollableDiv\"\n ref={scrollRef}\n className=\"z-30 h-screen w-full px-4
|
|
1
|
+
{"version":3,"file":"AppShell.js","sources":["../../../src/components/Shell/AppShell.tsx"],"sourcesContent":["import { ReactNode, useEffect, useRef } from 'react';\nimport classNames from 'classnames';\n\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\n\nimport AppHeader from './AppHeader';\nimport AppSidebar from './AppSidebar';\nimport { ISidebarMenuItem } from './SidebarMenu';\nimport { useSidebarState } from './useSidebarState';\n\nimport '../../styles/sidebar.css';\n\ninterface IAppShellProps {\n items: Array<ISidebarMenuItem>;\n homePath: string;\n logo?: ReactNode;\n toolsSlot?: ReactNode;\n controlsSlot?: ReactNode;\n userMenuSlot?: ReactNode;\n /** Sidebar footer sign-out row; hidden when absent. */\n onLogout?: () => void;\n children: ReactNode;\n}\n\n/**\n * The uMode application chrome: fixed glass header, rail/dock/hover sidebar (pin persisted\n * through uiPreferences, Ctrl+\\ shortcut) and the scroll container every page renders into.\n * Everything app-shaped (menu items, tools, account surface, logout) arrives as props/slots.\n */\nconst AppShell: React.FC<IAppShellProps> = ({\n items,\n homePath,\n logo,\n toolsSlot,\n controlsSlot,\n userMenuSlot,\n onLogout,\n children,\n}) => {\n const { location } = useUiNavigation();\n const scrollRef = useRef<HTMLDivElement>(null);\n\n const {\n isMobile,\n isVisible,\n isDocked,\n isOverlayOnMobile,\n showRail,\n toggleLabelKey,\n togglePrimary,\n onHoverEnter,\n onHoverLeave,\n closeMobileDrawer,\n } = useSidebarState();\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: 0, behavior: 'smooth' });\n }, [location.pathname]);\n\n return (\n <div className=\"h-screen w-full bg-surface\">\n <AppHeader\n toggleSidebar={togglePrimary}\n isSidebarOpen={isDocked}\n toggleLabelKey={toggleLabelKey}\n homePath={homePath}\n logo={logo}\n toolsSlot={toolsSlot}\n controlsSlot={controlsSlot}\n userMenuSlot={userMenuSlot}\n onHoverEnter={onHoverEnter}\n onHoverLeave={onHoverLeave}\n />\n\n <main\n className={classNames('sidebar-content flex h-screen w-full', {\n 'is-rail': !isMobile && !isDocked,\n 'is-mobile': isMobile,\n })}\n >\n {isOverlayOnMobile && (\n <div\n aria-hidden\n className=\"fixed inset-0 z-40 bg-scrim motion-reduce:transition-none\"\n onClick={closeMobileDrawer}\n />\n )}\n\n <AppSidebar\n items={items}\n isMobile={isMobile}\n isVisible={isVisible}\n collapsed={showRail}\n isFloating={!isMobile && !showRail && !isDocked}\n onHoverEnter={onHoverEnter}\n onHoverLeave={onHoverLeave}\n onNavigate={isMobile ? closeMobileDrawer : undefined}\n onLogout={onLogout}\n />\n\n <div\n id=\"scrollableDiv\"\n ref={scrollRef}\n className=\"z-30 h-screen w-full px-4 tablet:px-6\"\n /*\n * Where a PageTitle pins itself when the page scrolls. A sticky offset is measured from this\n * container's CONTENT box, so the padding that clears the fixed app header has to be\n * subtracted, or the header ends up pushed DOWN by the app header's height and covers the\n * content below it before the page is even scrolled. The padding moves into the same object\n * as the two numbers it depends on, so the relation between them stays visible.\n */\n style={{\n overflow: 'auto',\n paddingTop: 'var(--page-scroll-padding-top)',\n ['--page-scroll-padding-top' as string]: '6rem',\n ['--app-header-height' as string]: '4rem',\n ['--page-header-sticky-top' as string]: 'calc(var(--app-header-height) - var(--page-scroll-padding-top))',\n }}\n >\n {children}\n </div>\n </main>\n </div>\n );\n};\n\nexport default AppShell;\n"],"names":[],"mappings":";;;;;;;;AA6BA,MAAM,WAAqC,CAAC;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,SAAA,IAAa,gBAAA;AACrB,QAAM,YAAY,OAAuB,IAAI;AAE7C,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE,gBAAA;AAEJ,YAAU,MAAM;AACd,cAAU,SAAS,SAAS,EAAE,KAAK,GAAG,UAAU,UAAU;AAAA,EAC5D,GAAG,CAAC,SAAS,QAAQ,CAAC;AAEtB,SACE,qBAAC,OAAA,EAAI,WAAU,8BACb,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAe;AAAA,QACf,eAAe;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAAA;AAAA,IAGF;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,WAAW,wCAAwC;AAAA,UAC5D,WAAW,CAAC,YAAY,CAAC;AAAA,UACzB,aAAa;AAAA,QAAA,CACd;AAAA,QAEA,UAAA;AAAA,UAAA,qBACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,eAAW;AAAA,cACX,WAAU;AAAA,cACV,SAAS;AAAA,YAAA;AAAA,UAAA;AAAA,UAIb;AAAA,YAAC;AAAA,YAAA;AAAA,cACC;AAAA,cACA;AAAA,cACA;AAAA,cACA,WAAW;AAAA,cACX,YAAY,CAAC,YAAY,CAAC,YAAY,CAAC;AAAA,cACvC;AAAA,cACA;AAAA,cACA,YAAY,WAAW,oBAAoB;AAAA,cAC3C;AAAA,YAAA;AAAA,UAAA;AAAA,UAGF;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAG;AAAA,cACH,KAAK;AAAA,cACL,WAAU;AAAA,cAQV,OAAO;AAAA,gBACL,UAAU;AAAA,gBACV,YAAY;AAAA,gBACZ,CAAC,2BAAqC,GAAG;AAAA,gBACzC,CAAC,qBAA+B,GAAG;AAAA,gBACnC,CAAC,0BAAoC,GAAG;AAAA,cAAA;AAAA,cAGzC;AAAA,YAAA;AAAA,UAAA;AAAA,QACH;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,GACF;AAEJ;"}
|