@aglyn/shared-ui-next 1.0.0-beta.143

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.
@@ -0,0 +1,292 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
+ import { AppLink, CardDisplay, GridItems, MdiIcon, mdiLockOutline } from "@aglyn/shared-ui-jsx";
20
+ import { TabContext, TabList, TabPanel } from "@mui/lab";
21
+ import { Box, Tab, Tabs, useMediaQuery, useTheme } from "@mui/material";
22
+ import { usePathname } from "next/navigation";
23
+ import { useCallback, useEffect, useMemo, useState } from "react";
24
+ import { useTabParam } from "../hooks/use-tab-param.js";
25
+ /**
26
+ * The rail's own shape, shared by BOTH modes below (AGL-2501).
27
+ *
28
+ * `HubTabs` and `HubSections` are two ways of choosing a section and one way
29
+ * of DRAWING that choice. Duplicating the card, the orientation switch and the
30
+ * tab `sx` is how they drift into looking like two different products, which
31
+ * is exactly what happened the first time this rail was rebuilt by hand.
32
+ */ function useRailLayout() {
33
+ const theme = useTheme();
34
+ const stacked = useMediaQuery(theme.breakpoints.down('sm'));
35
+ return {
36
+ stacked,
37
+ /** Props every rail passes to its `Tabs`/`TabList`. */ tabsProps: {
38
+ orientation: stacked ? 'horizontal' : 'vertical',
39
+ variant: stacked ? 'scrollable' : 'standard',
40
+ allowScrollButtonsMobile: true,
41
+ textColor: 'primary',
42
+ indicatorColor: 'primary',
43
+ sx: {
44
+ ['.MuiTab-root']: {
45
+ alignItems: stacked ? 'center' : 'start',
46
+ maxWidth: 'unset',
47
+ textTransform: 'none'
48
+ }
49
+ }
50
+ }
51
+ };
52
+ }
53
+ /**
54
+ * Hub tab strip (AGL-354/382): the host-setup two-column pattern as a
55
+ * shared component — a left "Navigation" CardDisplay with a vertical
56
+ * TabList, content on the right. Collapses to horizontal tabs on small
57
+ * screens. The active tab mirrors into the `?tab=` query param (shallow
58
+ * replace) so hub views deep-link and survive back/forward; panels are
59
+ * kept mounted so content and its data subscriptions are always present
60
+ * (unless `lazy`, which defers un-visited panels — see the prop).
61
+ */ export function HubTabs(props) {
62
+ const { tabs, navHeader = 'Navigation', lazy = false } = props;
63
+ const { tabsProps } = useRailLayout();
64
+ /*
65
+ * The SHARED resolver, not a second reading of the same parameter
66
+ * (AGL-2486). This rail used to hold the incoming id in `useState`, which
67
+ * reads it once and then stops: back and forward are navigations between
68
+ * two states of one mounted page, and a link into another section of a page
69
+ * already open changes the parameter without remounting anything. Either
70
+ * one left the rail on the old tab while the URL named a different one.
71
+ *
72
+ * `ids` is the tabs that exist right now, so an id naming a tab this hub
73
+ * does not render falls back to the first rather than selecting a panel
74
+ * nothing draws — which matters because several hubs build their tab list
75
+ * from entitlements and render a different set per org.
76
+ */ const tabIds = useMemo(()=>tabs.map((item)=>item.id), [
77
+ tabs
78
+ ]);
79
+ const { tab, onTabChange } = useTabParam({
80
+ ids: tabIds
81
+ });
82
+ // Which tabs have ever been active — the mount set when `lazy`. Seeded with
83
+ // the tab resolved for first paint so it (and only it) mounts.
84
+ const [activated, setActivated] = useState(()=>new Set(tab ? [
85
+ tab
86
+ ] : []));
87
+ const handleChange = useCallback((event, value)=>{
88
+ setActivated((prev)=>prev.has(value) ? prev : new Set(prev).add(value));
89
+ onTabChange(event, value);
90
+ }, [
91
+ onTabChange
92
+ ]);
93
+ /*
94
+ * A panel reached by URL rather than by click has to stay in the mount set
95
+ * too. `handleChange` is the only thing that grows the set, and it does not
96
+ * fire when back/forward or an in-app link moves the parameter — so without
97
+ * this, a `lazy` hub would drop such a panel again the moment the reader
98
+ * moved on, and every return to it would remount and re-subscribe.
99
+ *
100
+ * The panel itself does not wait for this effect: the render below mounts
101
+ * the ACTIVE tab unconditionally, so there is no frame in which the rail
102
+ * shows a selected tab over an empty panel.
103
+ */ useEffect(()=>{
104
+ if (!lazy || !tab) return;
105
+ setActivated((prev)=>prev.has(tab) ? prev : new Set(prev).add(tab));
106
+ }, [
107
+ lazy,
108
+ tab
109
+ ]);
110
+ return /*#__PURE__*/ _jsx(TabContext, {
111
+ value: tab,
112
+ children: /*#__PURE__*/ _jsx(GridItems, {
113
+ // A navigation column beside its content, not a set of cards.
114
+ masonry: false,
115
+ spacing: 3,
116
+ items: [
117
+ {
118
+ size: {
119
+ xs: 12,
120
+ sm: 3
121
+ },
122
+ children: /*#__PURE__*/ _jsx(CardDisplay, {
123
+ header: navHeader,
124
+ children: /*#__PURE__*/ _jsx(TabList, _extends({}, tabsProps, {
125
+ onChange: handleChange,
126
+ children: tabs.map((item)=>/*#__PURE__*/ _jsx(Tab, {
127
+ value: item.id,
128
+ label: item.label
129
+ }, item.id))
130
+ }))
131
+ })
132
+ },
133
+ {
134
+ size: {
135
+ xs: 12,
136
+ sm: 9
137
+ },
138
+ children: /*#__PURE__*/ _jsx(_Fragment, {
139
+ children: tabs.map((item)=>/*#__PURE__*/ _jsx(TabPanel, {
140
+ value: item.id,
141
+ keepMounted: true,
142
+ sx: {
143
+ padding: 'unset'
144
+ },
145
+ children: !lazy || item.id === tab || activated.has(item.id) ? item.content : null
146
+ }, item.id))
147
+ })
148
+ }
149
+ ]
150
+ })
151
+ });
152
+ }
153
+ HubTabs.displayName = 'HubTabs';
154
+ export default HubTabs;
155
+ /**
156
+ * The same rail, choosing a section by ROUTE rather than by panel (AGL-2501).
157
+ *
158
+ * ## Why this exists beside `HubTabs`
159
+ *
160
+ * `HubTabs` renders every panel and keeps them mounted — `keepMounted`, and
161
+ * `lazy` is off by default and passed by nobody. That is deliberate for a hub
162
+ * whose panels are cheap and want live subscriptions, and it is the wrong
163
+ * default for a settings page: opening "General" mounts the API-keys card, the
164
+ * SSO card and the data-export card, and every one of their reads runs.
165
+ *
166
+ * The code is the other half. A tabbed page imports every panel's module
167
+ * statically, so a reader who only renames their workspace still downloads the
168
+ * delete-org dialog.
169
+ *
170
+ * Sections as routes fix both at the framework level rather than by hand: Next
171
+ * mounts one page and code-splits per route, so an unopened section costs
172
+ * neither a read nor a byte. Three things come free that the tab version faked
173
+ * or did without — a section is linkable, the back button walks sections, and
174
+ * the active state is a fact about the URL rather than state kept in sync with
175
+ * it.
176
+ *
177
+ * ## Active state
178
+ *
179
+ * By PREFIX, so a section stays selected on its own deeper routes, longest
180
+ * match first so a nested section beats its parent. The separator boundary is
181
+ * what stops `/settings` claiming `/settings-export`.
182
+ */ /**
183
+ * The section the current URL is inside, or `null` when none matches.
184
+ *
185
+ * Exported because the RAIL is not the only thing that has to know. A hub's
186
+ * breadcrumb ends at the hub — "Site / Admin" — while the reader is looking at
187
+ * Plugins, so the trail names every level except the one they are on. Feeding
188
+ * both from one resolver is what stops the two disagreeing: a section added to
189
+ * the rail is in the breadcrumb by construction, rather than by somebody
190
+ * remembering a second list.
191
+ *
192
+ * Matching is by PREFIX so a section stays selected on its own deeper routes,
193
+ * longest match first so a nested section beats its parent, and on a separator
194
+ * boundary so `/settings` cannot claim `/settings-export`.
195
+ */ export function useActiveSection(sections) {
196
+ const pathname = usePathname();
197
+ return useMemo(()=>{
198
+ var _filter_sort_;
199
+ const onPath = (href)=>pathname === href || pathname.startsWith(`${href}/`);
200
+ return (_filter_sort_ = [
201
+ ...sections
202
+ ].filter((section)=>section.visible !== false && onPath(section.href)).sort((a, b)=>b.href.length - a.href.length)[0]) != null ? _filter_sort_ : null;
203
+ }, [
204
+ pathname,
205
+ sections
206
+ ]);
207
+ }
208
+ /**
209
+ * A locked section's label: its name, then a lock, as one inline run
210
+ * (AGL-2783).
211
+ *
212
+ * The lock lives inside the label rather than in `Tab`'s `icon` slot, because
213
+ * MUI lays out a tab that has an icon as a different row. A tab with both an
214
+ * icon and a label gets `MuiTab-labelIcon` — a 72px minimum height against a
215
+ * plain tab's 48px, with its own vertical padding — and an icon beside the
216
+ * label turns the tab from a column into a row, where the rail's
217
+ * `alignItems: 'start'` stops meaning left and starts meaning top. Drawn that
218
+ * way, a locked section is taller than its neighbors, centered in its row,
219
+ * and its label is pinned to the row's top edge. Inside the label, a locked
220
+ * tab is the same tab as an unlocked one: same classes, same height, same
221
+ * alignment, on the vertical rail and the stacked one alike.
222
+ *
223
+ * The lock is named through `titleAccess`, which gives the svg `role="img"`
224
+ * and a `<title>`, so its words reach the tab's accessible name (AGL-2794).
225
+ * An `aria-label` cannot: MUI's `SvgIcon` renders any icon without a title
226
+ * `aria-hidden`, and a label on a hidden element is never read, so the tab
227
+ * would announce itself exactly like a section the plan includes.
228
+ */ function LockedSectionLabel(props) {
229
+ return /*#__PURE__*/ _jsxs(Box, {
230
+ component: "span",
231
+ sx: {
232
+ display: 'inline-flex',
233
+ alignItems: 'center',
234
+ gap: 1
235
+ },
236
+ children: [
237
+ props.label,
238
+ /*#__PURE__*/ _jsx(MdiIcon, {
239
+ path: mdiLockOutline.path,
240
+ titleAccess: "Not included in your plan"
241
+ })
242
+ ]
243
+ });
244
+ }
245
+ export function HubSections(props) {
246
+ var _ref;
247
+ var _useActiveSection;
248
+ const { sections, children, navHeader = 'Navigation' } = props;
249
+ const { tabsProps } = useRailLayout();
250
+ const shown = useMemo(()=>sections.filter((section)=>section.visible !== false), [
251
+ sections
252
+ ]);
253
+ const activeHref = (_ref = (_useActiveSection = useActiveSection(sections)) == null ? void 0 : _useActiveSection.href) != null ? _ref : null;
254
+ return /*#__PURE__*/ _jsx(GridItems, {
255
+ // The same navigation-beside-content split as above.
256
+ masonry: false,
257
+ spacing: 3,
258
+ items: [
259
+ {
260
+ size: {
261
+ xs: 12,
262
+ sm: 3
263
+ },
264
+ children: /*#__PURE__*/ _jsx(CardDisplay, {
265
+ header: navHeader,
266
+ children: /*#__PURE__*/ _jsx(Tabs, _extends({}, tabsProps, {
267
+ value: activeHref != null ? activeHref : false,
268
+ children: shown.map((section)=>/*#__PURE__*/ _jsx(Tab, {
269
+ value: section.href,
270
+ label: section.locked ? /*#__PURE__*/ _jsx(LockedSectionLabel, {
271
+ label: section.label
272
+ }) : section.label,
273
+ component: AppLink,
274
+ href: section.href,
275
+ "aria-current": section.href === activeHref ? 'page' : undefined
276
+ }, section.href))
277
+ }))
278
+ })
279
+ },
280
+ {
281
+ size: {
282
+ xs: 12,
283
+ sm: 9
284
+ },
285
+ children
286
+ }
287
+ ]
288
+ });
289
+ }
290
+ HubSections.displayName = 'HubSections';
291
+
292
+ //# sourceMappingURL=hub-tabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/next/src/lib/components/hub-tabs.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n AppLink,\n CardDisplay,\n GridItems,\n MdiIcon,\n mdiLockOutline,\n} from '@aglyn/shared-ui-jsx'\nimport { TabContext, TabList, TabPanel } from '@mui/lab'\nimport { Box, Tab, Tabs, useMediaQuery, useTheme } from '@mui/material'\nimport { usePathname } from 'next/navigation'\nimport {\n type ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from 'react'\nimport { useTabParam } from '../hooks/use-tab-param'\n\n/**\n * The rail's own shape, shared by BOTH modes below (AGL-2501).\n *\n * `HubTabs` and `HubSections` are two ways of choosing a section and one way\n * of DRAWING that choice. Duplicating the card, the orientation switch and the\n * tab `sx` is how they drift into looking like two different products, which\n * is exactly what happened the first time this rail was rebuilt by hand.\n */\nfunction useRailLayout() {\n const theme = useTheme()\n const stacked = useMediaQuery(theme.breakpoints.down('sm'))\n return {\n stacked,\n /** Props every rail passes to its `Tabs`/`TabList`. */\n tabsProps: {\n orientation: stacked ? ('horizontal' as const) : ('vertical' as const),\n variant: stacked ? ('scrollable' as const) : ('standard' as const),\n allowScrollButtonsMobile: true,\n textColor: 'primary' as const,\n indicatorColor: 'primary' as const,\n sx: {\n ['.MuiTab-root']: {\n alignItems: stacked ? 'center' : 'start',\n maxWidth: 'unset',\n textTransform: 'none',\n },\n },\n },\n }\n}\n\nexport interface HubTab {\n id: string\n label: string\n content: ReactNode\n}\n\nexport interface HubTabsProps {\n tabs: HubTab[]\n /** Left nav card header (defaults to \"Navigation\"). */\n navHeader?: string\n /**\n * Defer mounting a panel's content until its tab is first activated, then\n * keep it mounted (AGL-785). Off by default so the standard behavior —\n * every panel mounted up front, subscriptions always live — is unchanged.\n * Opt in when the tabs host several data-heavy panels whose subscriptions\n * would otherwise all settle at once on load: mounting only the active\n * panel keeps that first-paint re-render burst small enough not to trip\n * React's nested-update limit. Panels stay mounted once visited, so\n * switching back is still instant.\n */\n lazy?: boolean\n}\n\n/**\n * Hub tab strip (AGL-354/382): the host-setup two-column pattern as a\n * shared component — a left \"Navigation\" CardDisplay with a vertical\n * TabList, content on the right. Collapses to horizontal tabs on small\n * screens. The active tab mirrors into the `?tab=` query param (shallow\n * replace) so hub views deep-link and survive back/forward; panels are\n * kept mounted so content and its data subscriptions are always present\n * (unless `lazy`, which defers un-visited panels — see the prop).\n */\nexport function HubTabs(props: HubTabsProps) {\n const { tabs, navHeader = 'Navigation', lazy = false } = props\n const { tabsProps } = useRailLayout()\n /*\n * The SHARED resolver, not a second reading of the same parameter\n * (AGL-2486). This rail used to hold the incoming id in `useState`, which\n * reads it once and then stops: back and forward are navigations between\n * two states of one mounted page, and a link into another section of a page\n * already open changes the parameter without remounting anything. Either\n * one left the rail on the old tab while the URL named a different one.\n *\n * `ids` is the tabs that exist right now, so an id naming a tab this hub\n * does not render falls back to the first rather than selecting a panel\n * nothing draws — which matters because several hubs build their tab list\n * from entitlements and render a different set per org.\n */\n const tabIds = useMemo(() => tabs.map((item) => item.id), [tabs])\n const { tab, onTabChange } = useTabParam({ ids: tabIds })\n // Which tabs have ever been active — the mount set when `lazy`. Seeded with\n // the tab resolved for first paint so it (and only it) mounts.\n const [activated, setActivated] = useState<Set<string>>(\n () => new Set(tab ? [tab] : []),\n )\n\n const handleChange = useCallback(\n (event: unknown, value: string) => {\n setActivated((prev) => (prev.has(value) ? prev : new Set(prev).add(value)))\n onTabChange(event, value)\n },\n [onTabChange],\n )\n\n /*\n * A panel reached by URL rather than by click has to stay in the mount set\n * too. `handleChange` is the only thing that grows the set, and it does not\n * fire when back/forward or an in-app link moves the parameter — so without\n * this, a `lazy` hub would drop such a panel again the moment the reader\n * moved on, and every return to it would remount and re-subscribe.\n *\n * The panel itself does not wait for this effect: the render below mounts\n * the ACTIVE tab unconditionally, so there is no frame in which the rail\n * shows a selected tab over an empty panel.\n */\n useEffect(() => {\n if (!lazy || !tab) return\n setActivated((prev) => (prev.has(tab) ? prev : new Set(prev).add(tab)))\n }, [lazy, tab])\n\n return (\n <TabContext value={tab}>\n <GridItems\n // A navigation column beside its content, not a set of cards.\n masonry={false}\n spacing={3}\n items={[\n {\n size: { xs: 12, sm: 3 },\n children: (\n <CardDisplay header={navHeader}>\n <TabList {...tabsProps} onChange={handleChange}>\n {tabs.map((item) => (\n <Tab key={item.id} value={item.id} label={item.label} />\n ))}\n </TabList>\n </CardDisplay>\n ),\n },\n {\n size: { xs: 12, sm: 9 },\n children: (\n <>\n {tabs.map((item) => (\n <TabPanel\n key={item.id}\n value={item.id}\n keepMounted\n sx={{ padding: 'unset' }}\n >\n {!lazy || item.id === tab || activated.has(item.id)\n ? item.content\n : null}\n </TabPanel>\n ))}\n </>\n ),\n },\n ]}\n />\n </TabContext>\n )\n}\nHubTabs.displayName = 'HubTabs'\n\nexport default HubTabs\n\n\nexport interface HubSection {\n /** Route this section lives at. What the rail links to. */\n href: string\n label: string\n /** Hidden entirely when false — a release or a role gate. */\n visible?: boolean\n /**\n * Shown and linked, with a lock — an entitlement the org's plan does not\n * carry (AGL-2611). Not hidden, deliberately: the page behind the link is\n * the shell's upgrade notice, which is the way to buy it, and a rail that\n * hid the section would hide the reason to upgrade.\n */\n locked?: boolean\n}\n\nexport interface HubSectionsProps {\n sections: readonly HubSection[]\n /** The active section's page, rendered beside the rail. */\n children: ReactNode\n /** Left nav card header (defaults to \"Navigation\"). */\n navHeader?: string\n}\n\n/**\n * The same rail, choosing a section by ROUTE rather than by panel (AGL-2501).\n *\n * ## Why this exists beside `HubTabs`\n *\n * `HubTabs` renders every panel and keeps them mounted — `keepMounted`, and\n * `lazy` is off by default and passed by nobody. That is deliberate for a hub\n * whose panels are cheap and want live subscriptions, and it is the wrong\n * default for a settings page: opening \"General\" mounts the API-keys card, the\n * SSO card and the data-export card, and every one of their reads runs.\n *\n * The code is the other half. A tabbed page imports every panel's module\n * statically, so a reader who only renames their workspace still downloads the\n * delete-org dialog.\n *\n * Sections as routes fix both at the framework level rather than by hand: Next\n * mounts one page and code-splits per route, so an unopened section costs\n * neither a read nor a byte. Three things come free that the tab version faked\n * or did without — a section is linkable, the back button walks sections, and\n * the active state is a fact about the URL rather than state kept in sync with\n * it.\n *\n * ## Active state\n *\n * By PREFIX, so a section stays selected on its own deeper routes, longest\n * match first so a nested section beats its parent. The separator boundary is\n * what stops `/settings` claiming `/settings-export`.\n */\n/**\n * The section the current URL is inside, or `null` when none matches.\n *\n * Exported because the RAIL is not the only thing that has to know. A hub's\n * breadcrumb ends at the hub — \"Site / Admin\" — while the reader is looking at\n * Plugins, so the trail names every level except the one they are on. Feeding\n * both from one resolver is what stops the two disagreeing: a section added to\n * the rail is in the breadcrumb by construction, rather than by somebody\n * remembering a second list.\n *\n * Matching is by PREFIX so a section stays selected on its own deeper routes,\n * longest match first so a nested section beats its parent, and on a separator\n * boundary so `/settings` cannot claim `/settings-export`.\n */\nexport function useActiveSection(\n sections: readonly HubSection[],\n): HubSection | null {\n const pathname = usePathname()\n return useMemo(() => {\n const onPath = (href: string) =>\n pathname === href || pathname.startsWith(`${href}/`)\n return (\n [...sections]\n .filter((section) => section.visible !== false && onPath(section.href))\n .sort((a, b) => b.href.length - a.href.length)[0] ?? null\n )\n }, [pathname, sections])\n}\n\n/**\n * A locked section's label: its name, then a lock, as one inline run\n * (AGL-2783).\n *\n * The lock lives inside the label rather than in `Tab`'s `icon` slot, because\n * MUI lays out a tab that has an icon as a different row. A tab with both an\n * icon and a label gets `MuiTab-labelIcon` — a 72px minimum height against a\n * plain tab's 48px, with its own vertical padding — and an icon beside the\n * label turns the tab from a column into a row, where the rail's\n * `alignItems: 'start'` stops meaning left and starts meaning top. Drawn that\n * way, a locked section is taller than its neighbors, centered in its row,\n * and its label is pinned to the row's top edge. Inside the label, a locked\n * tab is the same tab as an unlocked one: same classes, same height, same\n * alignment, on the vertical rail and the stacked one alike.\n *\n * The lock is named through `titleAccess`, which gives the svg `role=\"img\"`\n * and a `<title>`, so its words reach the tab's accessible name (AGL-2794).\n * An `aria-label` cannot: MUI's `SvgIcon` renders any icon without a title\n * `aria-hidden`, and a label on a hidden element is never read, so the tab\n * would announce itself exactly like a section the plan includes.\n */\nfunction LockedSectionLabel(props: { label: string }) {\n return (\n <Box\n component=\"span\"\n sx={{ display: 'inline-flex', alignItems: 'center', gap: 1 }}\n >\n {props.label}\n <MdiIcon\n path={mdiLockOutline.path}\n titleAccess=\"Not included in your plan\"\n />\n </Box>\n )\n}\n\nexport function HubSections(props: HubSectionsProps) {\n const { sections, children, navHeader = 'Navigation' } = props\n const { tabsProps } = useRailLayout()\n const shown = useMemo(\n () => sections.filter((section) => section.visible !== false),\n [sections],\n )\n const activeHref = useActiveSection(sections)?.href ?? null\n\n return (\n <GridItems\n // The same navigation-beside-content split as above.\n masonry={false}\n spacing={3}\n items={[\n {\n size: { xs: 12, sm: 3 },\n children: (\n <CardDisplay header={navHeader}>\n {/*\n * `Tabs`, not `TabList`: there is no `TabContext` here because\n * there are no panels to bind to — the content is a routed\n * page. `false` when nothing matches, because a `value` MUI\n * cannot find warns on every render and parks the indicator on\n * whichever tab happens to be first.\n */}\n <Tabs {...tabsProps} value={activeHref ?? false}>\n {shown.map((section) => (\n <Tab\n key={section.href}\n value={section.href}\n label={\n section.locked ? (\n <LockedSectionLabel label={section.label} />\n ) : (\n section.label\n )\n }\n component={AppLink}\n href={section.href}\n aria-current={\n section.href === activeHref ? 'page' : undefined\n }\n />\n ))}\n </Tabs>\n </CardDisplay>\n ),\n },\n { size: { xs: 12, sm: 9 }, children },\n ]}\n />\n )\n}\nHubSections.displayName = 'HubSections'\n"],"names":["AppLink","CardDisplay","GridItems","MdiIcon","mdiLockOutline","TabContext","TabList","TabPanel","Box","Tab","Tabs","useMediaQuery","useTheme","usePathname","useCallback","useEffect","useMemo","useState","useTabParam","useRailLayout","theme","stacked","breakpoints","down","tabsProps","orientation","variant","allowScrollButtonsMobile","textColor","indicatorColor","sx","alignItems","maxWidth","textTransform","HubTabs","props","tabs","navHeader","lazy","tabIds","map","item","id","tab","onTabChange","ids","activated","setActivated","Set","handleChange","event","value","prev","has","add","masonry","spacing","items","size","xs","sm","children","header","onChange","label","keepMounted","padding","content","displayName","useActiveSection","sections","pathname","onPath","href","startsWith","filter","section","visible","sort","a","b","length","LockedSectionLabel","component","display","gap","path","titleAccess","HubSections","shown","activeHref","locked","aria-current","undefined"],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { ComponentProps } from 'react';
18
+ interface NextEmotionImageProps extends ComponentProps<typeof NextEmotionImage> {
19
+ }
20
+ declare const NextEmotionImage: import("@emotion/styled").StyledComponent<Pick<Omit<import("react").DetailedHTMLProps<import("react").ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "ref" | "height" | "width" | "loading" | "src" | "alt" | "srcSet"> & {
21
+ src: string | import("next/dist/shared/lib/get-img-props").StaticImport;
22
+ alt: string;
23
+ width?: number | `${number}`;
24
+ height?: number | `${number}`;
25
+ fill?: boolean;
26
+ loader?: import("next/image").ImageLoader;
27
+ quality?: number | `${number}`;
28
+ preload?: boolean;
29
+ priority?: boolean;
30
+ loading?: "eager" | "lazy" | undefined;
31
+ placeholder?: import("next/dist/shared/lib/get-img-props").PlaceholderValue;
32
+ blurDataURL?: string;
33
+ unoptimized?: boolean;
34
+ overrideSrc?: string;
35
+ onLoadingComplete?: import("next/dist/shared/lib/get-img-props").OnLoadingComplete;
36
+ layout?: string;
37
+ objectFit?: string;
38
+ objectPosition?: string;
39
+ lazyBoundary?: string;
40
+ lazyRoot?: string;
41
+ } & import("react").RefAttributes<HTMLImageElement>, "onChange" | "fill" | "children" | "slot" | "style" | "title" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "autoCapitalize" | "autoFocus" | "className" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "enterKeyHint" | "hidden" | "id" | "lang" | "nonce" | "spellCheck" | "tabIndex" | "translate" | "radioGroup" | "role" | "about" | "content" | "datatype" | "inlist" | "prefix" | "property" | "rel" | "resource" | "rev" | "typeof" | "vocab" | "autoCorrect" | "autoSave" | "color" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "popover" | "popoverTargetAction" | "popoverTarget" | "inert" | "inputMode" | "is" | "exportparts" | "part" | "tw" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-braillelabel" | "aria-brailleroledescription" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colindextext" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-description" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowindextext" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onScrollEnd" | "onScrollEndCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onToggle" | "onBeforeToggle" | "onTransitionCancel" | "onTransitionCancelCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "onTransitionRun" | "onTransitionRunCapture" | "onTransitionStart" | "onTransitionStartCapture" | "height" | "objectFit" | "objectPosition" | "width" | "layout" | "referrerPolicy" | "loading" | "src" | "alt" | "crossOrigin" | "decoding" | "fetchPriority" | "sizes" | "useMap" | "loader" | "quality" | "preload" | "priority" | "placeholder" | "blurDataURL" | "unoptimized" | "overrideSrc" | "onLoadingComplete" | "lazyBoundary" | "lazyRoot" | keyof import("react").RefAttributes<HTMLImageElement>> & import("@mui/system").MUIStyledCommonProps<import("@mui/material").Theme> & Omit<import("react").DetailedHTMLProps<import("react").ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "ref" | "height" | "width" | "loading" | "src" | "alt" | "srcSet"> & {
42
+ src: string | import("next/dist/shared/lib/get-img-props").StaticImport;
43
+ alt: string;
44
+ width?: number | `${number}`;
45
+ height?: number | `${number}`;
46
+ fill?: boolean;
47
+ loader?: import("next/image").ImageLoader;
48
+ quality?: number | `${number}`;
49
+ preload?: boolean;
50
+ priority?: boolean;
51
+ loading?: "lazy" | "eager";
52
+ placeholder?: import("next/dist/shared/lib/get-img-props").PlaceholderValue;
53
+ blurDataURL?: string;
54
+ unoptimized?: boolean;
55
+ overrideSrc?: string;
56
+ onLoadingComplete?: import("next/dist/shared/lib/get-img-props").OnLoadingComplete;
57
+ layout?: string;
58
+ objectFit?: string;
59
+ objectPosition?: string;
60
+ lazyBoundary?: string;
61
+ lazyRoot?: string;
62
+ }, {}, {}>;
63
+ interface ShimmerProps {
64
+ width?: number | string;
65
+ height?: number | string;
66
+ }
67
+ export interface ImageProps extends NextEmotionImageProps {
68
+ disableShimmer?: boolean;
69
+ }
70
+ export declare function Image(props: ImageProps): JSX.Element;
71
+ export declare namespace Image {
72
+ var shimmerToBase64: (shimmer?: string) => string;
73
+ var shimmer: (props: ShimmerProps) => string;
74
+ var displayName: string;
75
+ var aglyn: boolean;
76
+ }
77
+ export default Image;
@@ -0,0 +1,75 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ import { jsx as _jsx } from "react/jsx-runtime";
4
+ /**
5
+ * @license
6
+ * Copyright 2026 Aglyn LLC
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */ import { styled } from "@aglyn/shared-ui-theme";
20
+ import { base64IsomorphicEncode } from "@aglyn/shared-util-tools";
21
+ import NextImageRaw from "next/image";
22
+ import { useMemo } from "react";
23
+ const NextEmotionImage = styled(NextImageRaw, {
24
+ name: 'NextEmotionImage'
25
+ })({});
26
+ function shimmer(props) {
27
+ const { width: w, height: h } = props;
28
+ return `<svg width="${w}" height="${h}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
29
+ <defs>
30
+ <linearGradient id="g">
31
+ <stop stop-color="#333" offset="20%" />
32
+ <stop stop-color="#222" offset="50%" />
33
+ <stop stop-color="#333" offset="70%" />
34
+ </linearGradient>
35
+ </defs>
36
+ <rect width="${w}" height="${h}" fill="#333" />
37
+ <rect id="r" width="${w}" height="${h}" fill="url(#g)" />
38
+ <animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
39
+ </svg>`;
40
+ }
41
+ function shimmerToBase64(shimmer) {
42
+ return `data:image/svg+xml;base64,${base64IsomorphicEncode(shimmer)}`;
43
+ }
44
+ export function Image(props) {
45
+ const { width = 100, height = 100, disableShimmer = false, placeholder = 'blur' } = props, rest = _object_without_properties_loose(props, [
46
+ "width",
47
+ "height",
48
+ "disableShimmer",
49
+ "placeholder"
50
+ ]);
51
+ const blurDataURL = useMemo(()=>{
52
+ if (disableShimmer) return undefined;
53
+ return Image.shimmerToBase64(Image.shimmer({
54
+ width,
55
+ height
56
+ }));
57
+ }, [
58
+ disableShimmer,
59
+ width,
60
+ height
61
+ ]);
62
+ return /*#__PURE__*/ _jsx(NextEmotionImage, _extends({
63
+ width: width,
64
+ height: height,
65
+ blurDataURL: blurDataURL,
66
+ placeholder: placeholder
67
+ }, rest));
68
+ }
69
+ Image.shimmerToBase64 = shimmerToBase64;
70
+ Image.shimmer = shimmer;
71
+ Image.displayName = 'Image';
72
+ Image.aglyn = true;
73
+ export default Image;
74
+
75
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/next/src/lib/components/image.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { styled } from '@aglyn/shared-ui-theme'\nimport { base64IsomorphicEncode } from '@aglyn/shared-util-tools'\nimport NextImageRaw, { type ImageProps as NextImageProps } from 'next/image'\nimport type { ComponentProps } from 'react'\nimport { useMemo } from 'react'\n\ninterface NextEmotionImageProps\n extends ComponentProps<typeof NextEmotionImage> {}\n\nconst NextEmotionImage = styled(NextImageRaw, {\n name: 'NextEmotionImage',\n})<NextImageProps>({})\n\ninterface ShimmerProps {\n width?: number | string\n height?: number | string\n}\n\nfunction shimmer(props: ShimmerProps) {\n const { width: w, height: h } = props\n return `<svg width=\"${w}\" height=\"${h}\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n <defs>\n <linearGradient id=\"g\">\n <stop stop-color=\"#333\" offset=\"20%\" />\n <stop stop-color=\"#222\" offset=\"50%\" />\n <stop stop-color=\"#333\" offset=\"70%\" />\n </linearGradient>\n </defs>\n <rect width=\"${w}\" height=\"${h}\" fill=\"#333\" />\n <rect id=\"r\" width=\"${w}\" height=\"${h}\" fill=\"url(#g)\" />\n <animate xlink:href=\"#r\" attributeName=\"x\" from=\"-${w}\" to=\"${w}\" dur=\"1s\" repeatCount=\"indefinite\" />\n</svg>`\n}\n\nfunction shimmerToBase64(shimmer?: string) {\n return `data:image/svg+xml;base64,${base64IsomorphicEncode(shimmer)}`\n}\n\nexport interface ImageProps extends NextEmotionImageProps {\n disableShimmer?: boolean\n}\n\nexport function Image(props: ImageProps): JSX.Element {\n const {\n width = 100,\n height = 100,\n disableShimmer = false,\n placeholder = 'blur',\n ...rest\n } = props\n\n const blurDataURL = useMemo(() => {\n if (disableShimmer) return undefined\n return Image.shimmerToBase64(Image.shimmer({ width, height }))\n }, [disableShimmer, width, height])\n\n return (\n <NextEmotionImage\n width={width}\n height={height}\n blurDataURL={blurDataURL}\n placeholder={placeholder}\n {...rest}\n />\n )\n}\nImage.shimmerToBase64 = shimmerToBase64\nImage.shimmer = shimmer\nImage.displayName = 'Image'\nImage.aglyn = true\n\nexport default Image\n"],"names":["styled","base64IsomorphicEncode","NextImageRaw","useMemo","NextEmotionImage","name","shimmer","props","width","w","height","h","shimmerToBase64","Image","disableShimmer","placeholder","rest","blurDataURL","undefined","displayName","aglyn"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,MAAM,QAAQ,yBAAwB;AAC/C,SAASC,sBAAsB,QAAQ,2BAA0B;AACjE,OAAOC,kBAAyD,aAAY;AAE5E,SAASC,OAAO,QAAQ,QAAO;AAK/B,MAAMC,mBAAmBJ,OAAOE,cAAc;IAC5CG,MAAM;AACR,GAAmB,CAAC;AAOpB,SAASC,QAAQC,KAAmB;IAClC,MAAM,EAAEC,OAAOC,CAAC,EAAEC,QAAQC,CAAC,EAAE,GAAGJ;IAChC,OAAO,CAAC,YAAY,EAAEE,EAAE,UAAU,EAAEE,EAAE;;;;;;;;eAQzB,EAAEF,EAAE,UAAU,EAAEE,EAAE;sBACX,EAAEF,EAAE,UAAU,EAAEE,EAAE;oDACY,EAAEF,EAAE,MAAM,EAAEA,EAAE;MAC5D,CAAC;AACP;AAEA,SAASG,gBAAgBN,OAAgB;IACvC,OAAO,CAAC,0BAA0B,EAAEL,uBAAuBK,UAAU;AACvE;AAMA,OAAO,SAASO,MAAMN,KAAiB;IACrC,MAAM,EACJC,QAAQ,GAAG,EACXE,SAAS,GAAG,EACZI,iBAAiB,KAAK,EACtBC,cAAc,MAAM,EAErB,GAAGR,OADCS,wCACDT;;;;;;IAEJ,MAAMU,cAAcd,QAAQ;QAC1B,IAAIW,gBAAgB,OAAOI;QAC3B,OAAOL,MAAMD,eAAe,CAACC,MAAMP,OAAO,CAAC;YAAEE;YAAOE;QAAO;IAC7D,GAAG;QAACI;QAAgBN;QAAOE;KAAO;IAElC,qBACE,KAACN;QACCI,OAAOA;QACPE,QAAQA;QACRO,aAAaA;QACbF,aAAaA;OACTC;AAGV;AACAH,MAAMD,eAAe,GAAGA;AACxBC,MAAMP,OAAO,GAAGA;AAChBO,MAAMM,WAAW,GAAG;AACpBN,MAAMO,KAAK,GAAG;AAEd,eAAeP,MAAK"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2023 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { NextPage } from 'next';
18
+ import type { AppProps as NextAppProps } from 'next/app';
19
+ type AnyProps = Partial<Record<string, unknown>>;
20
+ type EmptyObj = Partial<Record<never, unknown>>;
21
+ export interface NextPageGetLayoutFn {
22
+ (page: JSX.Element, props: NextAppWithLayoutProps): JSX.Element;
23
+ }
24
+ export interface NextPageLayoutObject<P = EmptyObj> {
25
+ Component: JSX.ElementType<P>;
26
+ props?: P;
27
+ }
28
+ export interface NextPageMemberLayout {
29
+ layouts?: NextPageLayoutObject<any>[];
30
+ layout?: NextPageGetLayoutFn;
31
+ }
32
+ export type NextPageWithLayout<Props = AnyProps, InitialProps = Props> = NextPage<Props, InitialProps> & NextPageMemberLayout;
33
+ export interface NextAppWithLayoutProps<Props = AnyProps, InitialProps = Props> extends NextAppProps<Props> {
34
+ Component: NextPageWithLayout<Props, InitialProps>;
35
+ }
36
+ export interface PageDecoratedProps<Props, InitialProps> extends NextAppWithLayoutProps<Props, InitialProps> {
37
+ }
38
+ /**
39
+ * Decorate next page with defined layout
40
+ * Uses the layout defined at the page level, if available
41
+ */
42
+ export declare function PageDecorated<Props, InitialProps>(props: PageDecoratedProps<Props, InitialProps>): JSX.Element;
43
+ export declare namespace PageDecorated {
44
+ var displayName: string;
45
+ }
46
+ export default PageDecorated;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2023 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ import { jsx as _jsx } from "react/jsx-runtime";
18
+ function GetLayout(page, initialProps) {
19
+ return page;
20
+ }
21
+ function getNextPageLayout(props) {
22
+ const { Component } = props;
23
+ const { layout, layouts } = Component;
24
+ if (layout) return layout;
25
+ if (Array.isArray(layouts)) {
26
+ return layouts.reduce((page, layout)=>{
27
+ const { Component, props } = layout;
28
+ return (innerPage, initialProps)=>{
29
+ return page(/*#__PURE__*/ _jsx(Component, _extends({}, props, {
30
+ children: innerPage
31
+ })), initialProps);
32
+ };
33
+ }, GetLayout);
34
+ }
35
+ return GetLayout;
36
+ }
37
+ /**
38
+ * Decorate next page with defined layout
39
+ * Uses the layout defined at the page level, if available
40
+ */ export function PageDecorated(props) {
41
+ const Component = props.Component;
42
+ return getNextPageLayout(props)(/*#__PURE__*/ _jsx(Component, _extends({}, props.pageProps)), props);
43
+ }
44
+ PageDecorated.displayName = 'PageDecorated';
45
+ export default PageDecorated;
46
+
47
+ //# sourceMappingURL=page-decorated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/next/src/lib/components/page-decorated.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2023 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { NextPage } from 'next'\nimport type { AppProps as NextAppProps } from 'next/app'\n\ntype AnyProps = Partial<Record<string, unknown>>\ntype EmptyObj = Partial<Record<never, unknown>>\n\nexport interface NextPageGetLayoutFn {\n (page: JSX.Element, props: NextAppWithLayoutProps): JSX.Element\n}\n\nexport interface NextPageLayoutObject<P = EmptyObj> {\n Component: JSX.ElementType<P>\n props?: P\n}\n\nexport interface NextPageMemberLayout {\n layouts?: NextPageLayoutObject<any>[]\n layout?: NextPageGetLayoutFn\n}\n\nexport type NextPageWithLayout<\n Props = AnyProps,\n InitialProps = Props,\n> = NextPage<Props, InitialProps> & NextPageMemberLayout\n\nexport interface NextAppWithLayoutProps<Props = AnyProps, InitialProps = Props>\n extends NextAppProps<Props> {\n Component: NextPageWithLayout<Props, InitialProps>\n}\n\nfunction GetLayout(page: JSX.Element, initialProps?: NextAppWithLayoutProps) {\n return page\n}\n\nfunction getNextPageLayout<Props, InitialProps>(\n props: NextAppWithLayoutProps<Props, InitialProps>,\n): NextPageGetLayoutFn {\n const { Component } = props\n const { layout, layouts } = Component\n\n if (layout) return layout\n\n if (Array.isArray(layouts)) {\n return layouts.reduce((page, layout) => {\n const { Component, props } = layout\n return (innerPage, initialProps) => {\n return page(<Component {...props} children={innerPage} />, initialProps)\n }\n }, GetLayout)\n }\n\n return GetLayout\n}\n\nexport interface PageDecoratedProps<Props, InitialProps>\n extends NextAppWithLayoutProps<Props, InitialProps> {}\n\n/**\n * Decorate next page with defined layout\n * Uses the layout defined at the page level, if available\n */\nexport function PageDecorated<Props, InitialProps>(\n props: PageDecoratedProps<Props, InitialProps>,\n) {\n const Component = props.Component\n return getNextPageLayout(props)(<Component {...props.pageProps} />, props as NextAppWithLayoutProps)\n}\n\nPageDecorated.displayName = 'PageDecorated'\nexport default PageDecorated\n"],"names":["GetLayout","page","initialProps","getNextPageLayout","props","Component","layout","layouts","Array","isArray","reduce","innerPage","children","PageDecorated","pageProps","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;;AAgCD,SAASA,UAAUC,IAAiB,EAAEC,YAAqC;IACzE,OAAOD;AACT;AAEA,SAASE,kBACPC,KAAkD;IAElD,MAAM,EAAEC,SAAS,EAAE,GAAGD;IACtB,MAAM,EAAEE,MAAM,EAAEC,OAAO,EAAE,GAAGF;IAE5B,IAAIC,QAAQ,OAAOA;IAEnB,IAAIE,MAAMC,OAAO,CAACF,UAAU;QAC1B,OAAOA,QAAQG,MAAM,CAAC,CAACT,MAAMK;YAC3B,MAAM,EAAED,SAAS,EAAED,KAAK,EAAE,GAAGE;YAC7B,OAAO,CAACK,WAAWT;gBACjB,OAAOD,mBAAK,KAACI,wBAAcD;oBAAOQ,UAAUD;qBAAeT;YAC7D;QACF,GAAGF;IACL;IAEA,OAAOA;AACT;AAKA;;;CAGC,GACD,OAAO,SAASa,cACdT,KAA8C;IAE9C,MAAMC,YAAYD,MAAMC,SAAS;IACjC,OAAOF,kBAAkBC,qBAAO,KAACC,wBAAcD,MAAMU,SAAS,IAAMV;AACtE;AAEAS,cAAcE,WAAW,GAAG;AAC5B,eAAeF,cAAa"}