@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.
- package/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +45 -0
- package/src/index.d.ts +17 -0
- package/src/index.js +18 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/hub-tabs.d.ts +104 -0
- package/src/lib/components/hub-tabs.js +292 -0
- package/src/lib/components/hub-tabs.js.map +1 -0
- package/src/lib/components/image.d.ts +77 -0
- package/src/lib/components/image.js +75 -0
- package/src/lib/components/image.js.map +1 -0
- package/src/lib/components/page-decorated.d.ts +46 -0
- package/src/lib/components/page-decorated.js +47 -0
- package/src/lib/components/page-decorated.js.map +1 -0
- package/src/lib/contexts/next-page-title-provider.d.ts +40 -0
- package/src/lib/contexts/next-page-title-provider.js +81 -0
- package/src/lib/contexts/next-page-title-provider.js.map +1 -0
- package/src/lib/hooks/use-tab-param.d.ts +58 -0
- package/src/lib/hooks/use-tab-param.js +70 -0
- package/src/lib/hooks/use-tab-param.js.map +1 -0
- package/src/lib/next.d.ts +20 -0
- package/src/lib/next.js +26 -0
- package/src/lib/next.js.map +1 -0
|
@@ -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"}
|