@carlonicora/nextjs-jsonapi 1.135.1 → 1.136.0
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/{BlockNoteEditor-FAPCNKXZ.mjs → BlockNoteEditor-JVZ2HDAU.mjs} +2 -2
- package/dist/{BlockNoteEditor-PYRHKBF3.js → BlockNoteEditor-MNCHLJO4.js} +9 -9
- package/dist/{BlockNoteEditor-PYRHKBF3.js.map → BlockNoteEditor-MNCHLJO4.js.map} +1 -1
- package/dist/billing/index.js +310 -310
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-EF2HVXOV.mjs → chunk-2KXPQHZU.mjs} +235 -191
- package/dist/chunk-2KXPQHZU.mjs.map +1 -0
- package/dist/{chunk-DNWYHP4V.js → chunk-3NVJEXUT.js} +343 -299
- package/dist/chunk-3NVJEXUT.js.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.d.mts +52 -3
- package/dist/components/index.d.ts +52 -3
- package/dist/components/index.js +4 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +3 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/features/help/index.js +31 -31
- package/dist/features/help/index.mjs +1 -1
- package/package.json +1 -1
- package/src/components/containers/RoundPageContainer.tsx +93 -14
- package/src/components/containers/RoundPageContainerTitle.tsx +19 -9
- package/dist/chunk-DNWYHP4V.js.map +0 -1
- package/dist/chunk-EF2HVXOV.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-FAPCNKXZ.mjs.map → BlockNoteEditor-JVZ2HDAU.mjs.map} +0 -0
|
@@ -22,7 +22,7 @@ var _chunkYBZVWLPHjs = require('../../chunk-YBZVWLPH.js');
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
var
|
|
25
|
+
var _chunk3NVJEXUTjs = require('../../chunk-3NVJEXUT.js');
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
var _chunkC2LEIBUKjs = require('../../chunk-C2LEIBUK.js');
|
|
@@ -74,29 +74,29 @@ var _lucidereact = require('lucide-react');
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
function HelpAssistantSheet({ open, onOpenChange }) {
|
|
77
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
78
|
-
|
|
77
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.Sheet, { open, onOpenChange, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
78
|
+
_chunk3NVJEXUTjs.SheetContent,
|
|
79
79
|
{
|
|
80
80
|
side: "right",
|
|
81
81
|
className: "flex w-full flex-col data-[side=right]:sm:max-w-2xl data-[side=right]:lg:max-w-3xl",
|
|
82
|
-
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
82
|
+
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.AssistantProvider, { manageUrl: false, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, HelpAssistantSheetBody, {}) })
|
|
83
83
|
}
|
|
84
84
|
) });
|
|
85
85
|
}
|
|
86
86
|
_chunk7QVYU63Ejs.__name.call(void 0, HelpAssistantSheet, "HelpAssistantSheet");
|
|
87
87
|
function HelpAssistantSheetBody() {
|
|
88
88
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
89
|
-
const ctx =
|
|
89
|
+
const ctx = _chunk3NVJEXUTjs.useAssistantContext.call(void 0, );
|
|
90
90
|
const showThread = !!ctx.assistant || ctx.sending || ctx.messages.length > 0;
|
|
91
91
|
const send = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, (content) => ctx.sendMessage(content, { howToMode: true }), "send");
|
|
92
92
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
93
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
94
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
95
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
93
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk3NVJEXUTjs.SheetHeader, { children: [
|
|
94
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.SheetTitle, { children: t("help.askAi.sheet.title") }),
|
|
95
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.SheetDescription, { children: t("help.askAi.sheet.subtitle") })
|
|
96
96
|
] }),
|
|
97
|
-
!showThread ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
97
|
+
!showThread ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.AssistantEmptyState, { onSend: send }) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
98
98
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
99
|
-
|
|
99
|
+
_chunk3NVJEXUTjs.AssistantThread,
|
|
100
100
|
{
|
|
101
101
|
messages: ctx.messages,
|
|
102
102
|
sending: ctx.sending,
|
|
@@ -106,7 +106,7 @@ function HelpAssistantSheetBody() {
|
|
|
106
106
|
onRetry: ctx.retrySend
|
|
107
107
|
}
|
|
108
108
|
),
|
|
109
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
109
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.AssistantComposer, { onSend: send, disabled: ctx.sending })
|
|
110
110
|
] })
|
|
111
111
|
] });
|
|
112
112
|
}
|
|
@@ -116,20 +116,20 @@ _chunk7QVYU63Ejs.__name.call(void 0, HelpAssistantSheetBody, "HelpAssistantSheet
|
|
|
116
116
|
|
|
117
117
|
function HelpAskAi() {
|
|
118
118
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
119
|
-
const { currentUser } =
|
|
119
|
+
const { currentUser } = _chunk3NVJEXUTjs.useCurrentUserContext.call(void 0, );
|
|
120
120
|
const [open, setOpen] = _react.useState.call(void 0, false);
|
|
121
121
|
const disabled = !currentUser;
|
|
122
122
|
if (disabled) {
|
|
123
|
-
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
124
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
123
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk3NVJEXUTjs.Tooltip, { children: [
|
|
124
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk3NVJEXUTjs.TooltipTrigger, { render: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.Button, { variant: "outline", size: "sm", disabled: true }), children: [
|
|
125
125
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.SparklesIcon, { className: "h-4 w-4" }),
|
|
126
126
|
t("help.askAi.button")
|
|
127
127
|
] }),
|
|
128
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
128
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.TooltipContent, { children: t("help.askAi.loginTooltip") })
|
|
129
129
|
] });
|
|
130
130
|
}
|
|
131
131
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
132
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
132
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk3NVJEXUTjs.Button, { variant: "outline", size: "sm", onClick: () => setOpen(true), children: [
|
|
133
133
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.SparklesIcon, { className: "h-4 w-4" }),
|
|
134
134
|
t("help.askAi.button")
|
|
135
135
|
] }),
|
|
@@ -142,7 +142,7 @@ _chunk7QVYU63Ejs.__name.call(void 0, HelpAskAi, "HelpAskAi");
|
|
|
142
142
|
|
|
143
143
|
function HelpHeader() {
|
|
144
144
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
145
|
-
const { currentUser } =
|
|
145
|
+
const { currentUser } = _chunk3NVJEXUTjs.useCurrentUserContext.call(void 0, );
|
|
146
146
|
const { brand } = useHelp();
|
|
147
147
|
const logo = _optionalChain([brand, 'optionalAccess', _ => _.logo]);
|
|
148
148
|
const label = _nullishCoalesce(_optionalChain([brand, 'optionalAccess', _2 => _2.label]), () => ( "Help"));
|
|
@@ -158,7 +158,7 @@ function HelpHeader() {
|
|
|
158
158
|
] }),
|
|
159
159
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2", children: [
|
|
160
160
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, HelpAskAi, {}),
|
|
161
|
-
currentUser ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
161
|
+
currentUser ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.Button, { render: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _link2.default, { href: appHref }), nativeButton: false, variant: "outline", size: "sm", children: t("help.header.openApp") }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.Button, { render: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _link2.default, { href: "/login" }), nativeButton: false, variant: "outline", size: "sm", children: t("help.header.login") })
|
|
162
162
|
] })
|
|
163
163
|
] });
|
|
164
164
|
}
|
|
@@ -200,7 +200,7 @@ _chunk7QVYU63Ejs.__name.call(void 0, useHelpFilter, "useHelpFilter");
|
|
|
200
200
|
|
|
201
201
|
function HelpSideNav() {
|
|
202
202
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
203
|
-
const generateUrl =
|
|
203
|
+
const generateUrl = _chunk3NVJEXUTjs.usePageUrlGenerator.call(void 0, );
|
|
204
204
|
const pathname = _navigation.usePathname.call(void 0, );
|
|
205
205
|
const [articles, setArticles] = _react.useState.call(void 0, []);
|
|
206
206
|
_react.useEffect.call(void 0, () => {
|
|
@@ -281,7 +281,7 @@ _chunk7QVYU63Ejs.__name.call(void 0, HelpSideNav, "HelpSideNav");
|
|
|
281
281
|
function HelpArticleBody(props) {
|
|
282
282
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
283
283
|
const format = _nextintl.useFormatter.call(void 0, );
|
|
284
|
-
const generateUrl =
|
|
284
|
+
const generateUrl = _chunk3NVJEXUTjs.usePageUrlGenerator.call(void 0, );
|
|
285
285
|
const { howToType, title, summary, updatedAt, prev, next, children } = props;
|
|
286
286
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "article", { className: "prose dark:prose-invert max-w-none", children: [
|
|
287
287
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "nav", { className: "text-muted-foreground mb-2 text-xs", children: [
|
|
@@ -338,7 +338,7 @@ function HelpTOC({ headings }) {
|
|
|
338
338
|
}, [headings]);
|
|
339
339
|
if (headings.length === 0) return null;
|
|
340
340
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "nav", { "aria-label": t("help.toc.title"), className: "hidden lg:block", children: [
|
|
341
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
341
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.MicroLabel, { className: "mb-2", children: t("help.toc.title") }),
|
|
342
342
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "ul", { className: "space-y-1 text-sm", children: headings.map((h) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "li", { style: { paddingLeft: `${(h.depth - 2) * 0.75}rem` }, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
343
343
|
"a",
|
|
344
344
|
{
|
|
@@ -359,7 +359,7 @@ _chunk7QVYU63Ejs.__name.call(void 0, HelpTOC, "HelpTOC");
|
|
|
359
359
|
|
|
360
360
|
function HelpHint({ contextKey }) {
|
|
361
361
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
362
|
-
const generateUrl =
|
|
362
|
+
const generateUrl = _chunk3NVJEXUTjs.usePageUrlGenerator.call(void 0, );
|
|
363
363
|
const [open, setOpen] = _react.useState.call(void 0, false);
|
|
364
364
|
const [picked, setPicked] = _react.useState.call(void 0, null);
|
|
365
365
|
const [articles, setArticles] = _react.useState.call(void 0, []);
|
|
@@ -377,7 +377,7 @@ function HelpHint({ contextKey }) {
|
|
|
377
377
|
if (matches.length === 0) return null;
|
|
378
378
|
const active = _nullishCoalesce(picked, () => ( (matches.length === 1 ? matches[0] : null)));
|
|
379
379
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
380
|
-
|
|
380
|
+
_chunk3NVJEXUTjs.Sheet,
|
|
381
381
|
{
|
|
382
382
|
open,
|
|
383
383
|
onOpenChange: (o) => {
|
|
@@ -385,11 +385,11 @@ function HelpHint({ contextKey }) {
|
|
|
385
385
|
if (!o) setPicked(null);
|
|
386
386
|
},
|
|
387
387
|
children: [
|
|
388
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
389
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
390
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
391
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
392
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
388
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.SheetTrigger, { render: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.Button, { variant: "ghost", size: "icon-sm", "aria-label": t("help.hint.trigger") }), children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.HelpCircleIcon, { className: "h-4 w-4" }) }),
|
|
389
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk3NVJEXUTjs.SheetContent, { side: "right", className: "w-full max-w-md sm:max-w-lg", children: [
|
|
390
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk3NVJEXUTjs.SheetHeader, { children: [
|
|
391
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.SheetTitle, { children: active ? active.name : t("help.hint.pickArticle") }),
|
|
392
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.SheetDescription, { children: _nullishCoalesce(_optionalChain([active, 'optionalAccess', _4 => _4.summary]), () => ( "")) })
|
|
393
393
|
] }),
|
|
394
394
|
active ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "mt-4 space-y-3", children: [
|
|
395
395
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-muted-foreground text-sm", children: active.summary }),
|
|
@@ -421,7 +421,7 @@ _chunk7QVYU63Ejs.__name.call(void 0, HelpHint, "HelpHint");
|
|
|
421
421
|
|
|
422
422
|
function HelpSearchResultRow({ result, onSelect }) {
|
|
423
423
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
424
|
-
const generateUrl =
|
|
424
|
+
const generateUrl = _chunk3NVJEXUTjs.usePageUrlGenerator.call(void 0, );
|
|
425
425
|
const [article, setArticle] = _react.useState.call(void 0, null);
|
|
426
426
|
_react.useEffect.call(void 0, () => {
|
|
427
427
|
let active = true;
|
|
@@ -432,7 +432,7 @@ function HelpSearchResultRow({ result, onSelect }) {
|
|
|
432
432
|
}, [result.id]);
|
|
433
433
|
if (!article) {
|
|
434
434
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
435
|
-
|
|
435
|
+
_chunk3NVJEXUTjs.CommandItem,
|
|
436
436
|
{
|
|
437
437
|
value: result.id,
|
|
438
438
|
disabled: true,
|
|
@@ -441,7 +441,7 @@ function HelpSearchResultRow({ result, onSelect }) {
|
|
|
441
441
|
}
|
|
442
442
|
);
|
|
443
443
|
}
|
|
444
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
444
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3NVJEXUTjs.CommandItem, { value: result.id, onSelect, className: "cursor-pointer p-0", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
445
445
|
_link2.default,
|
|
446
446
|
{
|
|
447
447
|
href: generateUrl({ page: `/help/${article.howToType}/${article.slug}` }),
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
TabsTrigger,
|
|
19
19
|
} from "@/components";
|
|
20
20
|
import { partitionTabs, Tab } from "@/components/containers";
|
|
21
|
-
import { RoundPageContainerTitle } from "@/components/containers/RoundPageContainerTitle";
|
|
21
|
+
import { HEADER_ROW_MIN_H, RoundPageContainerTitle } from "@/components/containers/RoundPageContainerTitle";
|
|
22
22
|
import { Header, MobileNavigationBar } from "@/components/navigations";
|
|
23
23
|
import { useHeaderChildren, useHeaderLeftContent, useHeaderLogo, useHeaderMobileChildren } from "@/contexts";
|
|
24
24
|
import { useUrlRewriter } from "@/hooks";
|
|
@@ -55,6 +55,44 @@ type RoundPageContainerProps = {
|
|
|
55
55
|
* ancestors.
|
|
56
56
|
*/
|
|
57
57
|
onSectionChange?: (section: string) => void;
|
|
58
|
+
/**
|
|
59
|
+
* `data-testid` for the page shell. Applied to the outermost content wrapper
|
|
60
|
+
* of BOTH return branches — the pre-hydration one and the main one — so an
|
|
61
|
+
* e2e suite gating page-readiness on the testid can attach before hydration
|
|
62
|
+
* completes rather than racing it.
|
|
63
|
+
*/
|
|
64
|
+
testId?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Initial state of the `details` panel before the persisted preference (the
|
|
67
|
+
* `round_page_details_state` cookie) is read. Defaults to `false` — the panel
|
|
68
|
+
* starts collapsed, which suits an informational aside.
|
|
69
|
+
*
|
|
70
|
+
* Pass `true` when `details` holds PRIMARY navigation rather than an aside
|
|
71
|
+
* (e.g. the chat / conversation list), where a collapsed-by-default panel
|
|
72
|
+
* would hide the page's main affordance behind a toggle. The stored
|
|
73
|
+
* preference still wins in both directions once the user sets one.
|
|
74
|
+
*
|
|
75
|
+
* Requires the title bar to be rendered (`!fullWidth || forceHeader`) —
|
|
76
|
+
* that is where the toggle lives, so a `fullWidth` caller passing `details`
|
|
77
|
+
* without `forceHeader` leaves the panel unreachable.
|
|
78
|
+
*/
|
|
79
|
+
defaultDetailsOpen?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Heading for the `details` panel. Rendered as a fixed header above the
|
|
82
|
+
* panel's scroll area, used as the mobile `Sheet` title (replacing the old
|
|
83
|
+
* hardcoded "Details"), and woven into the toggle's tooltip — so the control
|
|
84
|
+
* reads "Show conversations" rather than an unlabelled icon.
|
|
85
|
+
*
|
|
86
|
+
* Strongly recommended whenever `details` holds primary navigation: without
|
|
87
|
+
* it the panel is an unlabelled column and the toggle is unguessable.
|
|
88
|
+
*/
|
|
89
|
+
detailsTitle?: ReactNode;
|
|
90
|
+
/**
|
|
91
|
+
* Icon for the `details` toggle. Defaults to an info glyph, which suits an
|
|
92
|
+
* informational aside. Pass a panel/list glyph when `details` holds primary
|
|
93
|
+
* navigation — an "info" icon actively misdescribes a conversation list.
|
|
94
|
+
*/
|
|
95
|
+
detailsIcon?: ReactNode;
|
|
58
96
|
};
|
|
59
97
|
|
|
60
98
|
// Rail trigger class: override the horizontal TabsTrigger defaults for a
|
|
@@ -82,28 +120,47 @@ export function RoundPageContainer({
|
|
|
82
120
|
header,
|
|
83
121
|
layout = "tabs",
|
|
84
122
|
onSectionChange,
|
|
123
|
+
testId,
|
|
124
|
+
defaultDetailsOpen = false,
|
|
125
|
+
detailsTitle,
|
|
126
|
+
detailsIcon,
|
|
85
127
|
}: RoundPageContainerProps) {
|
|
86
128
|
const headerChildren = useHeaderChildren();
|
|
87
129
|
const headerLeftContent = useHeaderLeftContent();
|
|
88
130
|
const headerLogo = useHeaderLogo();
|
|
89
131
|
const headerMobileChildren = useHeaderMobileChildren();
|
|
90
|
-
const [showDetails, setShowDetailsState] = useState(
|
|
132
|
+
const [showDetails, setShowDetailsState] = useState(defaultDetailsOpen);
|
|
91
133
|
const isMobile = useIsMobile();
|
|
92
134
|
const [mounted, setMounted] = useState(false);
|
|
93
135
|
|
|
94
136
|
useEffect(() => {
|
|
95
|
-
const match = document.cookie.split("; ").find((row) => row.startsWith(`${
|
|
96
|
-
|
|
137
|
+
const match = document.cookie.split("; ").find((row) => row.startsWith(`${detailsCookieName}=`));
|
|
138
|
+
const stored = match?.split("=")[1];
|
|
139
|
+
// Apply the stored preference in BOTH directions. Previously this only ever
|
|
140
|
+
// opened the panel, which was harmless while the initial state was always
|
|
141
|
+
// `false` — but with `defaultDetailsOpen` a caller can start open, and a user
|
|
142
|
+
// who explicitly collapsed the panel must stay collapsed on the next load.
|
|
143
|
+
if (stored === "true") setShowDetailsState(true);
|
|
144
|
+
else if (stored === "false") setShowDetailsState(false);
|
|
97
145
|
}, []);
|
|
98
146
|
|
|
99
147
|
useEffect(() => {
|
|
100
148
|
setMounted(true);
|
|
101
149
|
}, []);
|
|
102
150
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
151
|
+
// Scope the persisted preference PER MODULE. A single global cookie meant
|
|
152
|
+
// collapsing an informational aside on one page silently collapsed a primary
|
|
153
|
+
// navigation panel on another — so a page declaring `defaultDetailsOpen`
|
|
154
|
+
// could still load collapsed because of an unrelated page's cookie.
|
|
155
|
+
const detailsCookieName = module?.name ? `${DETAILS_COOKIE_NAME}_${module.name}` : DETAILS_COOKIE_NAME;
|
|
156
|
+
|
|
157
|
+
const setShowDetails = useCallback(
|
|
158
|
+
(value: boolean) => {
|
|
159
|
+
setShowDetailsState(value);
|
|
160
|
+
document.cookie = `${detailsCookieName}=${value}; path=/; max-age=${DETAILS_COOKIE_MAX_AGE}`;
|
|
161
|
+
},
|
|
162
|
+
[detailsCookieName],
|
|
163
|
+
);
|
|
107
164
|
|
|
108
165
|
const searchParams = useSearchParams();
|
|
109
166
|
const section = searchParams.get("section");
|
|
@@ -163,7 +220,10 @@ export function RoundPageContainer({
|
|
|
163
220
|
>
|
|
164
221
|
{headerChildren}
|
|
165
222
|
</Header>
|
|
166
|
-
<div
|
|
223
|
+
<div
|
|
224
|
+
data-testid={testId}
|
|
225
|
+
className={cn("flex h-[calc(100vh-3rem)] w-full flex-col", isMobile ? "gap-1 p-1 pt-0" : "p-2 pt-0 pl-0")}
|
|
226
|
+
>
|
|
167
227
|
{/* `min-h-0 flex-1`, NOT `h-full`: the bar below is an in-flow sibling in
|
|
168
228
|
this fixed-height flex column. `h-full` would resolve to 100% of the
|
|
169
229
|
wrapper, leaving no room and pushing the bar below the fold (visible
|
|
@@ -187,7 +247,10 @@ export function RoundPageContainer({
|
|
|
187
247
|
>
|
|
188
248
|
{headerChildren}
|
|
189
249
|
</Header>
|
|
190
|
-
<div
|
|
250
|
+
<div
|
|
251
|
+
data-testid={testId}
|
|
252
|
+
className={cn(`flex h-[calc(100vh-3rem)] w-full flex-col`, isMobile ? "gap-1 p-1 pt-0" : "p-2 pt-0 pl-0")}
|
|
253
|
+
>
|
|
191
254
|
{/* `min-h-0 flex-1`, NOT `h-full`: MobileNavigationBar below is an in-flow
|
|
192
255
|
sibling in this fixed-height flex column. `h-full` resolves to 100% of
|
|
193
256
|
the wrapper, leaving the bar no room and pushing it below the fold —
|
|
@@ -198,6 +261,8 @@ export function RoundPageContainer({
|
|
|
198
261
|
<RoundPageContainerTitle
|
|
199
262
|
module={module}
|
|
200
263
|
details={details}
|
|
264
|
+
detailsTitle={detailsTitle}
|
|
265
|
+
detailsIcon={detailsIcon}
|
|
201
266
|
showDetails={showDetails}
|
|
202
267
|
setShowDetails={setShowDetails}
|
|
203
268
|
fullWidth={fullWidth}
|
|
@@ -402,7 +467,7 @@ export function RoundPageContainer({
|
|
|
402
467
|
<Sheet open={showDetails} onOpenChange={setShowDetails}>
|
|
403
468
|
<SheetContent side="right">
|
|
404
469
|
<SheetHeader>
|
|
405
|
-
<SheetTitle>Details</SheetTitle>
|
|
470
|
+
<SheetTitle>{detailsTitle ?? "Details"}</SheetTitle>
|
|
406
471
|
</SheetHeader>
|
|
407
472
|
<div className="overflow-y-auto p-6 pt-0">{details}</div>
|
|
408
473
|
</SheetContent>
|
|
@@ -410,11 +475,25 @@ export function RoundPageContainer({
|
|
|
410
475
|
) : (
|
|
411
476
|
<div
|
|
412
477
|
className={cn(
|
|
413
|
-
|
|
414
|
-
|
|
478
|
+
// `flex-col` + a separate scroll body below: the header must stay
|
|
479
|
+
// put while the panel content scrolls. Scrolling on this element
|
|
480
|
+
// (as it did before) would carry the header away with the content.
|
|
481
|
+
"flex h-full shrink-0 flex-col overflow-hidden transition-all duration-300 ease-in-out",
|
|
482
|
+
showDetails ? "w-96 border-l opacity-100" : "ml-0 w-0 border-l-0 opacity-0",
|
|
415
483
|
)}
|
|
416
484
|
>
|
|
417
|
-
{
|
|
485
|
+
{detailsTitle && (
|
|
486
|
+
// Mirrors RoundPageContainerTitle's structure exactly — border on an
|
|
487
|
+
// OUTER wrapper, height floor on the INNER row. Putting the border
|
|
488
|
+
// inside the measured row instead leaves this header 1px short of
|
|
489
|
+
// the page title bar, since `box-sizing: border-box` absorbs it.
|
|
490
|
+
<div className="shrink-0 border-b">
|
|
491
|
+
<div className={cn("flex items-center p-4", HEADER_ROW_MIN_H)}>
|
|
492
|
+
<span className="text-primary truncate text-sm font-semibold">{detailsTitle}</span>
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
)}
|
|
496
|
+
<div className="min-h-0 flex-1 overflow-y-auto p-4">{details}</div>
|
|
418
497
|
</div>
|
|
419
498
|
))}
|
|
420
499
|
</div>
|
|
@@ -7,9 +7,21 @@ import { cn, useIsMobile } from "@/utils";
|
|
|
7
7
|
import { InfoIcon } from "lucide-react";
|
|
8
8
|
import { ReactNode } from "react";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Shared height floor for the card's top row. Applied BOTH here and to the
|
|
12
|
+
* `details` panel header in RoundPageContainer, so the two line up across the
|
|
13
|
+
* card's top edge. Expressed in px on purpose: the app scales its root
|
|
14
|
+
* font-size (14px), so a rem value would drift between the two rows.
|
|
15
|
+
*/
|
|
16
|
+
export const HEADER_ROW_MIN_H = "min-h-[53px]";
|
|
17
|
+
|
|
10
18
|
type RoundPageContainerTitleProps = {
|
|
11
19
|
module?: ModuleWithPermissions;
|
|
12
20
|
details?: ReactNode;
|
|
21
|
+
/** Names what the toggle reveals, so the control is not an unlabelled icon. */
|
|
22
|
+
detailsTitle?: ReactNode;
|
|
23
|
+
/** Overrides the default info glyph — see `RoundPageContainer.detailsIcon`. */
|
|
24
|
+
detailsIcon?: ReactNode;
|
|
13
25
|
showDetails: boolean;
|
|
14
26
|
setShowDetails: (show: boolean) => void;
|
|
15
27
|
fullWidth?: boolean;
|
|
@@ -18,6 +30,8 @@ type RoundPageContainerTitleProps = {
|
|
|
18
30
|
export function RoundPageContainerTitle({
|
|
19
31
|
module,
|
|
20
32
|
details,
|
|
33
|
+
detailsTitle,
|
|
34
|
+
detailsIcon,
|
|
21
35
|
showDetails,
|
|
22
36
|
setShowDetails,
|
|
23
37
|
}: RoundPageContainerTitleProps) {
|
|
@@ -26,7 +40,7 @@ export function RoundPageContainerTitle({
|
|
|
26
40
|
|
|
27
41
|
return (
|
|
28
42
|
<div className="flex w-full flex-col border-b">
|
|
29
|
-
<div className={cn(`flex w-full flex-row items-center gap-x-2 p-4 justify-between
|
|
43
|
+
<div className={cn(`flex w-full flex-row items-center gap-x-2 p-4 justify-between`, HEADER_ROW_MIN_H)}>
|
|
30
44
|
{!isMobile ? (
|
|
31
45
|
<div className="flex w-full gap-x-4">
|
|
32
46
|
<div className={"text-muted-foreground flex items-center gap-x-2 text-lg font-light whitespace-nowrap"}>
|
|
@@ -56,16 +70,12 @@ export function RoundPageContainerTitle({
|
|
|
56
70
|
onClick={() => setShowDetails(!showDetails)}
|
|
57
71
|
className={cn(`cursor-pointer`)}
|
|
58
72
|
>
|
|
59
|
-
{
|
|
60
|
-
<InfoIcon />
|
|
61
|
-
{/* {showDetails ? (
|
|
62
|
-
<PanelRightCloseIcon className="text-muted-foreground" />
|
|
63
|
-
) : (
|
|
64
|
-
<PanelRightOpenIcon className="text-accent" />
|
|
65
|
-
)} */}
|
|
73
|
+
{detailsIcon ?? <InfoIcon />}
|
|
66
74
|
</Button>
|
|
67
75
|
</TooltipTrigger>
|
|
68
|
-
<TooltipContent>
|
|
76
|
+
<TooltipContent>
|
|
77
|
+
{showDetails ? "Hide" : "Show"} {detailsTitle ?? "details"}
|
|
78
|
+
</TooltipContent>
|
|
69
79
|
</Tooltip>
|
|
70
80
|
)}
|
|
71
81
|
</div>
|