@byline/host-tanstack-start 2.2.6 → 2.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin-shell/chrome/admin-app-bar_module.css +3 -0
- package/dist/admin-shell/chrome/breadcrumbs/breadcrumbs.d.ts +0 -7
- package/dist/admin-shell/chrome/breadcrumbs/breadcrumbs.js +202 -54
- package/dist/admin-shell/chrome/breadcrumbs/breadcrumbs.module.js +5 -1
- package/dist/admin-shell/chrome/breadcrumbs/breadcrumbs_module.css +97 -1
- package/dist/admin-shell/collections/preview-link.js +2 -2
- package/package.json +7 -7
- package/src/admin-shell/chrome/admin-app-bar.module.css +3 -0
- package/src/admin-shell/chrome/breadcrumbs/breadcrumbs.module.css +91 -7
- package/src/admin-shell/chrome/breadcrumbs/breadcrumbs.tsx +217 -56
- package/src/admin-shell/collections/preview-link.tsx +7 -9
|
@@ -20,12 +20,15 @@
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
:is(.left-wd7pIB, .byline-admin-app-bar-left) {
|
|
23
|
+
flex: auto;
|
|
23
24
|
align-items: center;
|
|
24
25
|
gap: 1rem;
|
|
26
|
+
min-width: 0;
|
|
25
27
|
display: flex;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
:is(.right-w1uns3, .byline-admin-app-bar-right) {
|
|
31
|
+
flex: none;
|
|
29
32
|
align-items: center;
|
|
30
33
|
gap: 1rem;
|
|
31
34
|
font-size: .875rem;
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) Infonomic Company Limited
|
|
7
|
-
*/
|
|
8
1
|
import type { Breadcrumb } from './@types.js';
|
|
9
2
|
export declare function Breadcrumbs({ breadcrumbs, className, homeLabel, homePath, }: {
|
|
10
3
|
breadcrumbs: Breadcrumb[];
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Dropdown, EllipsisIcon } from "@byline/ui/react";
|
|
2
5
|
import classnames from "classnames";
|
|
3
6
|
import { Link } from "../loose-router.js";
|
|
4
7
|
import breadcrumbs_module from "./breadcrumbs.module.js";
|
|
8
|
+
const useIsoLayoutEffect = "u" > typeof window ? useLayoutEffect : useEffect;
|
|
9
|
+
const MAX_LABEL_LENGTH = 20;
|
|
5
10
|
function truncate(str, length, useWordBoundary = true, useSuffix = true) {
|
|
6
11
|
if (null == str || str.length <= length) return str;
|
|
7
12
|
const subString = str.slice(0, length - 2);
|
|
@@ -9,69 +14,212 @@ function truncate(str, length, useWordBoundary = true, useSuffix = true) {
|
|
|
9
14
|
return useSuffix ? `${truncated}...` : truncated;
|
|
10
15
|
}
|
|
11
16
|
function Breadcrumbs({ breadcrumbs, className, homeLabel = 'Home', homePath = '/' }) {
|
|
12
|
-
|
|
17
|
+
const navRef = useRef(null);
|
|
18
|
+
const measureRef = useRef(null);
|
|
19
|
+
const [visibleIndices, setVisibleIndices] = useState(()=>breadcrumbs.map((_, i)=>i));
|
|
20
|
+
useIsoLayoutEffect(()=>{
|
|
21
|
+
setVisibleIndices(breadcrumbs.map((_, i)=>i));
|
|
22
|
+
}, [
|
|
23
|
+
breadcrumbs
|
|
24
|
+
]);
|
|
25
|
+
useIsoLayoutEffect(()=>{
|
|
26
|
+
const nav = navRef.current;
|
|
27
|
+
const measure = measureRef.current;
|
|
28
|
+
if (!nav || !measure) return;
|
|
29
|
+
const compute = ()=>{
|
|
30
|
+
const children = Array.from(measure.children);
|
|
31
|
+
if (children.length !== breadcrumbs.length + 2) return;
|
|
32
|
+
const containerWidth = nav.clientWidth;
|
|
33
|
+
const homeWidth = children[0].offsetWidth;
|
|
34
|
+
const triggerWidth = children[children.length - 1].offsetWidth;
|
|
35
|
+
const itemWidths = children.slice(1, -1).map((el)=>el.offsetWidth);
|
|
36
|
+
const gap = Number.parseFloat(getComputedStyle(measure).gap) || 4;
|
|
37
|
+
const n = itemWidths.length;
|
|
38
|
+
let next;
|
|
39
|
+
const allTotal = homeWidth + itemWidths.reduce((a, b)=>a + b, 0) + n * gap;
|
|
40
|
+
if (n <= 2 || allTotal <= containerWidth) next = itemWidths.map((_, i)=>i);
|
|
41
|
+
else {
|
|
42
|
+
const visible = new Set([
|
|
43
|
+
0,
|
|
44
|
+
n - 1
|
|
45
|
+
]);
|
|
46
|
+
const baselineGaps = 4 * gap;
|
|
47
|
+
let used = homeWidth + itemWidths[0] + triggerWidth + itemWidths[n - 1] + baselineGaps;
|
|
48
|
+
if (used > containerWidth) {
|
|
49
|
+
visible.delete(0);
|
|
50
|
+
used = homeWidth + triggerWidth + itemWidths[n - 1] + 3 * gap;
|
|
51
|
+
}
|
|
52
|
+
for(let i = n - 2; i >= 1; i--){
|
|
53
|
+
if (visible.has(i)) continue;
|
|
54
|
+
const cost = itemWidths[i] + gap;
|
|
55
|
+
if (used + cost > containerWidth) break;
|
|
56
|
+
used += cost;
|
|
57
|
+
visible.add(i);
|
|
58
|
+
}
|
|
59
|
+
next = [
|
|
60
|
+
...visible
|
|
61
|
+
].sort((a, b)=>a - b);
|
|
62
|
+
}
|
|
63
|
+
setVisibleIndices((prev)=>{
|
|
64
|
+
if (prev.length === next.length && prev.every((v, i)=>v === next[i])) return prev;
|
|
65
|
+
return next;
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
compute();
|
|
69
|
+
const ro = new ResizeObserver(compute);
|
|
70
|
+
ro.observe(nav);
|
|
71
|
+
return ()=>ro.disconnect();
|
|
72
|
+
}, [
|
|
73
|
+
breadcrumbs
|
|
74
|
+
]);
|
|
75
|
+
const overflowed = useMemo(()=>{
|
|
76
|
+
const visible = new Set(visibleIndices);
|
|
77
|
+
return breadcrumbs.filter((_, i)=>!visible.has(i));
|
|
78
|
+
}, [
|
|
79
|
+
breadcrumbs,
|
|
80
|
+
visibleIndices
|
|
81
|
+
]);
|
|
82
|
+
const visibleSet = new Set(visibleIndices);
|
|
83
|
+
const lastIndex = breadcrumbs.length - 1;
|
|
84
|
+
let overflowEmitted = false;
|
|
85
|
+
const rendered = [];
|
|
86
|
+
for(let i = 0; i < breadcrumbs.length; i++)if (visibleSet.has(i)) rendered.push(renderBreadcrumb(breadcrumbs[i], i === lastIndex));
|
|
87
|
+
else if (!overflowEmitted) {
|
|
88
|
+
overflowEmitted = true;
|
|
89
|
+
rendered.push(/*#__PURE__*/ jsx(OverflowDropdown, {
|
|
90
|
+
items: overflowed
|
|
91
|
+
}, "__overflow__"));
|
|
92
|
+
}
|
|
93
|
+
return /*#__PURE__*/ jsxs("nav", {
|
|
94
|
+
ref: navRef,
|
|
13
95
|
"aria-label": "Breadcrumb",
|
|
14
96
|
className: classnames('byline-breadcrumbs', breadcrumbs_module.nav, className),
|
|
15
|
-
children:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
97
|
+
children: [
|
|
98
|
+
/*#__PURE__*/ jsxs("ul", {
|
|
99
|
+
className: classnames('byline-breadcrumbs-list', breadcrumbs_module.list),
|
|
100
|
+
children: [
|
|
101
|
+
/*#__PURE__*/ jsx(HomeItem, {
|
|
102
|
+
homePath: homePath,
|
|
103
|
+
homeLabel: homeLabel
|
|
104
|
+
}),
|
|
105
|
+
rendered
|
|
106
|
+
]
|
|
107
|
+
}),
|
|
108
|
+
/*#__PURE__*/ jsxs("ul", {
|
|
109
|
+
ref: measureRef,
|
|
110
|
+
"aria-hidden": true,
|
|
111
|
+
className: classnames(breadcrumbs_module.list, breadcrumbs_module.measure),
|
|
112
|
+
children: [
|
|
113
|
+
/*#__PURE__*/ jsx(HomeItem, {
|
|
114
|
+
homePath: homePath,
|
|
115
|
+
homeLabel: homeLabel
|
|
116
|
+
}),
|
|
117
|
+
breadcrumbs.map((b, i)=>renderBreadcrumb(b, i === lastIndex)),
|
|
118
|
+
/*#__PURE__*/ jsxs("li", {
|
|
119
|
+
className: classnames('byline-breadcrumbs-item', breadcrumbs_module.item),
|
|
23
120
|
children: [
|
|
24
|
-
/*#__PURE__*/ jsx(
|
|
25
|
-
|
|
26
|
-
className: classnames('byline-breadcrumbs-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
30
|
-
children: /*#__PURE__*/ jsx("path", {
|
|
31
|
-
d: "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"
|
|
121
|
+
/*#__PURE__*/ jsx(ChevronIcon, {}),
|
|
122
|
+
/*#__PURE__*/ jsx("span", {
|
|
123
|
+
className: classnames('byline-breadcrumbs-overflow-trigger', breadcrumbs_module.overflowTrigger),
|
|
124
|
+
children: /*#__PURE__*/ jsx(EllipsisIcon, {
|
|
125
|
+
className: classnames('byline-breadcrumbs-overflow-icon', breadcrumbs_module.overflowIcon)
|
|
32
126
|
})
|
|
33
|
-
})
|
|
34
|
-
homeLabel
|
|
127
|
+
})
|
|
35
128
|
]
|
|
36
129
|
})
|
|
130
|
+
]
|
|
131
|
+
})
|
|
132
|
+
]
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function renderBreadcrumb(breadcrumb, isLeaf) {
|
|
136
|
+
return /*#__PURE__*/ jsxs("li", {
|
|
137
|
+
"aria-current": isLeaf ? 'page' : void 0,
|
|
138
|
+
className: classnames('byline-breadcrumbs-item', breadcrumbs_module.item),
|
|
139
|
+
children: [
|
|
140
|
+
/*#__PURE__*/ jsx(ChevronIcon, {
|
|
141
|
+
isLeaf: isLeaf
|
|
142
|
+
}),
|
|
143
|
+
isLeaf ? /*#__PURE__*/ jsx("span", {
|
|
144
|
+
className: classnames('byline-breadcrumbs-leaf', breadcrumbs_module.leaf),
|
|
145
|
+
children: truncate(breadcrumb.label, MAX_LABEL_LENGTH, true)
|
|
146
|
+
}) : /*#__PURE__*/ jsx(Link, {
|
|
147
|
+
to: breadcrumb.href,
|
|
148
|
+
className: classnames('byline-breadcrumbs-link', breadcrumbs_module.link),
|
|
149
|
+
children: truncate(breadcrumb.label, MAX_LABEL_LENGTH, true)
|
|
150
|
+
})
|
|
151
|
+
]
|
|
152
|
+
}, breadcrumb.href);
|
|
153
|
+
}
|
|
154
|
+
function HomeItem({ homePath, homeLabel }) {
|
|
155
|
+
return /*#__PURE__*/ jsx("li", {
|
|
156
|
+
className: classnames('byline-breadcrumbs-item', breadcrumbs_module.item),
|
|
157
|
+
children: /*#__PURE__*/ jsxs(Link, {
|
|
158
|
+
to: homePath,
|
|
159
|
+
className: classnames('byline-breadcrumbs-link', breadcrumbs_module.link),
|
|
160
|
+
children: [
|
|
161
|
+
/*#__PURE__*/ jsx("svg", {
|
|
162
|
+
role: "presentation",
|
|
163
|
+
className: classnames('byline-breadcrumbs-home-icon', breadcrumbs_module.homeIcon),
|
|
164
|
+
fill: "currentColor",
|
|
165
|
+
viewBox: "0 0 20 20",
|
|
166
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
167
|
+
children: /*#__PURE__*/ jsx("path", {
|
|
168
|
+
d: "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"
|
|
169
|
+
})
|
|
37
170
|
}),
|
|
38
|
-
|
|
39
|
-
const isLeaf = index === breadcrumbs.length - 1;
|
|
40
|
-
return /*#__PURE__*/ jsx("li", {
|
|
41
|
-
"aria-current": isLeaf ? 'page' : void 0,
|
|
42
|
-
className: classnames('byline-breadcrumbs-item', breadcrumbs_module.item),
|
|
43
|
-
children: /*#__PURE__*/ jsxs("div", {
|
|
44
|
-
className: classnames('byline-breadcrumbs-item-row', breadcrumbs_module.item),
|
|
45
|
-
children: [
|
|
46
|
-
/*#__PURE__*/ jsx("svg", {
|
|
47
|
-
role: "presentation",
|
|
48
|
-
className: classnames('byline-breadcrumbs-chevron', breadcrumbs_module.chevron, {
|
|
49
|
-
'byline-breadcrumbs-chevron-current': isLeaf,
|
|
50
|
-
[breadcrumbs_module.chevronCurrent]: isLeaf
|
|
51
|
-
}),
|
|
52
|
-
fill: "currentColor",
|
|
53
|
-
viewBox: "0 0 20 20",
|
|
54
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
55
|
-
children: /*#__PURE__*/ jsx("path", {
|
|
56
|
-
fillRule: "evenodd",
|
|
57
|
-
d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z",
|
|
58
|
-
clipRule: "evenodd"
|
|
59
|
-
})
|
|
60
|
-
}),
|
|
61
|
-
isLeaf ? /*#__PURE__*/ jsx("span", {
|
|
62
|
-
className: classnames('byline-breadcrumbs-leaf', breadcrumbs_module.leaf),
|
|
63
|
-
children: truncate(breadcrumb.label, 20, true)
|
|
64
|
-
}) : /*#__PURE__*/ jsx(Link, {
|
|
65
|
-
to: breadcrumb.href,
|
|
66
|
-
className: classnames('byline-breadcrumbs-link', breadcrumbs_module.link),
|
|
67
|
-
children: truncate(breadcrumb.label, 20, true)
|
|
68
|
-
})
|
|
69
|
-
]
|
|
70
|
-
})
|
|
71
|
-
}, breadcrumb.href);
|
|
72
|
-
})
|
|
171
|
+
homeLabel
|
|
73
172
|
]
|
|
74
173
|
})
|
|
75
174
|
});
|
|
76
175
|
}
|
|
176
|
+
function ChevronIcon({ isLeaf = false }) {
|
|
177
|
+
return /*#__PURE__*/ jsx("svg", {
|
|
178
|
+
role: "presentation",
|
|
179
|
+
className: classnames('byline-breadcrumbs-chevron', breadcrumbs_module.chevron, {
|
|
180
|
+
'byline-breadcrumbs-chevron-current': isLeaf,
|
|
181
|
+
[breadcrumbs_module.chevronCurrent]: isLeaf
|
|
182
|
+
}),
|
|
183
|
+
fill: "currentColor",
|
|
184
|
+
viewBox: "0 0 20 20",
|
|
185
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
186
|
+
children: /*#__PURE__*/ jsx("path", {
|
|
187
|
+
fillRule: "evenodd",
|
|
188
|
+
d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z",
|
|
189
|
+
clipRule: "evenodd"
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function OverflowDropdown({ items }) {
|
|
194
|
+
return /*#__PURE__*/ jsxs("li", {
|
|
195
|
+
className: classnames('byline-breadcrumbs-item', breadcrumbs_module.item),
|
|
196
|
+
children: [
|
|
197
|
+
/*#__PURE__*/ jsx(ChevronIcon, {}),
|
|
198
|
+
/*#__PURE__*/ jsxs(Dropdown.Root, {
|
|
199
|
+
children: [
|
|
200
|
+
/*#__PURE__*/ jsx(Dropdown.Trigger, {
|
|
201
|
+
"aria-label": "Show hidden breadcrumbs",
|
|
202
|
+
className: classnames('byline-breadcrumbs-overflow-trigger', breadcrumbs_module.overflowTrigger),
|
|
203
|
+
children: /*#__PURE__*/ jsx(EllipsisIcon, {
|
|
204
|
+
className: classnames('byline-breadcrumbs-overflow-icon', breadcrumbs_module.overflowIcon)
|
|
205
|
+
})
|
|
206
|
+
}),
|
|
207
|
+
/*#__PURE__*/ jsx(Dropdown.Portal, {
|
|
208
|
+
children: /*#__PURE__*/ jsx(Dropdown.Content, {
|
|
209
|
+
sideOffset: 5,
|
|
210
|
+
align: "start",
|
|
211
|
+
children: items.map((item)=>/*#__PURE__*/ jsx(Dropdown.Item, {
|
|
212
|
+
className: classnames('byline-breadcrumbs-overflow-item', breadcrumbs_module.overflowItem),
|
|
213
|
+
render: /*#__PURE__*/ jsx(Link, {
|
|
214
|
+
to: item.href
|
|
215
|
+
}),
|
|
216
|
+
children: item.label
|
|
217
|
+
}, item.href))
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
]
|
|
221
|
+
})
|
|
222
|
+
]
|
|
223
|
+
});
|
|
224
|
+
}
|
|
77
225
|
export { Breadcrumbs };
|
|
@@ -2,11 +2,15 @@ import "./breadcrumbs_module.css";
|
|
|
2
2
|
const breadcrumbs_module = {
|
|
3
3
|
nav: "nav-v78C9V",
|
|
4
4
|
list: "list-YJoq_f",
|
|
5
|
+
measure: "measure-lRWlDd",
|
|
5
6
|
item: "item-BqSVfE",
|
|
6
7
|
link: "link-g18cLE",
|
|
7
8
|
leaf: "leaf-X3Q13H",
|
|
8
9
|
homeIcon: "homeIcon-yUKJfw",
|
|
9
10
|
chevron: "chevron-VWUUs9",
|
|
10
|
-
chevronCurrent: "chevronCurrent-vgprvC"
|
|
11
|
+
chevronCurrent: "chevronCurrent-vgprvC",
|
|
12
|
+
overflowTrigger: "overflowTrigger-Y8XJnz",
|
|
13
|
+
overflowIcon: "overflowIcon-FMJ1GK",
|
|
14
|
+
overflowItem: "overflowItem-zVrxKo"
|
|
11
15
|
};
|
|
12
16
|
export default breadcrumbs_module;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
:is(.nav-v78C9V, .byline-breadcrumbs) {
|
|
2
|
+
flex: auto;
|
|
3
|
+
min-width: 0;
|
|
2
4
|
display: flex;
|
|
5
|
+
position: relative;
|
|
6
|
+
overflow: hidden;
|
|
3
7
|
}
|
|
4
8
|
|
|
5
9
|
:is(.list-YJoq_f, .byline-breadcrumbs-list) {
|
|
6
|
-
|
|
10
|
+
white-space: nowrap;
|
|
11
|
+
flex-wrap: nowrap;
|
|
7
12
|
align-items: center;
|
|
8
13
|
gap: .25rem;
|
|
9
14
|
margin: 0;
|
|
@@ -12,7 +17,16 @@
|
|
|
12
17
|
display: inline-flex;
|
|
13
18
|
}
|
|
14
19
|
|
|
20
|
+
.measure-lRWlDd {
|
|
21
|
+
visibility: hidden;
|
|
22
|
+
pointer-events: none;
|
|
23
|
+
position: absolute;
|
|
24
|
+
top: 0;
|
|
25
|
+
left: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
15
28
|
:is(.item-BqSVfE, .byline-breadcrumbs-item) {
|
|
29
|
+
flex: none;
|
|
16
30
|
align-items: center;
|
|
17
31
|
margin: 0;
|
|
18
32
|
padding: 0;
|
|
@@ -79,3 +93,85 @@
|
|
|
79
93
|
color: var(--gray-600);
|
|
80
94
|
}
|
|
81
95
|
|
|
96
|
+
:is(.overflowTrigger-Y8XJnz, .byline-breadcrumbs-overflow-trigger) {
|
|
97
|
+
color: var(--gray-700);
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
font: inherit;
|
|
100
|
+
background: none;
|
|
101
|
+
border: 0;
|
|
102
|
+
border-radius: .25rem;
|
|
103
|
+
justify-content: center;
|
|
104
|
+
align-items: center;
|
|
105
|
+
margin: 0;
|
|
106
|
+
padding: 0 .125rem;
|
|
107
|
+
line-height: 1;
|
|
108
|
+
display: inline-flex;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.overflowTrigger-Y8XJnz:hover {
|
|
112
|
+
background-color: var(--gray-100);
|
|
113
|
+
color: var(--gray-900);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.byline-breadcrumbs-overflow-trigger:hover {
|
|
117
|
+
background-color: var(--gray-100);
|
|
118
|
+
color: var(--gray-900);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.overflowTrigger-Y8XJnz:focus-visible {
|
|
122
|
+
outline: 2px solid var(--gray-400);
|
|
123
|
+
outline-offset: 1px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.byline-breadcrumbs-overflow-trigger:focus-visible {
|
|
127
|
+
outline: 2px solid var(--gray-400);
|
|
128
|
+
outline-offset: 1px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
:is(:is([data-theme="dark"], .dark) .overflowTrigger-Y8XJnz, :is([data-theme="dark"], .dark) .byline-breadcrumbs-overflow-trigger) {
|
|
132
|
+
color: var(--gray-400);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
:is([data-theme="dark"], .dark) .overflowTrigger-Y8XJnz:hover {
|
|
136
|
+
background-color: var(--gray-800);
|
|
137
|
+
color: #fff;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
:is([data-theme="dark"], .dark) .byline-breadcrumbs-overflow-trigger:hover {
|
|
141
|
+
background-color: var(--gray-800);
|
|
142
|
+
color: #fff;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
:is(.overflowIcon-FMJ1GK, .byline-breadcrumbs-overflow-icon) {
|
|
146
|
+
width: 1rem;
|
|
147
|
+
height: 1rem;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
:is(.overflowItem-zVrxKo, .byline-breadcrumbs-overflow-item) {
|
|
151
|
+
color: var(--gray-900);
|
|
152
|
+
cursor: pointer;
|
|
153
|
+
padding: .375rem .625rem;
|
|
154
|
+
font-size: .875rem;
|
|
155
|
+
text-decoration: none;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.overflowItem-zVrxKo:hover {
|
|
159
|
+
color: var(--gray-900);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.byline-breadcrumbs-overflow-item:hover {
|
|
163
|
+
color: var(--gray-900);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
:is(:is([data-theme="dark"], .dark) .overflowItem-zVrxKo, :is([data-theme="dark"], .dark) .byline-breadcrumbs-overflow-item) {
|
|
167
|
+
color: var(--gray-300);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
:is([data-theme="dark"], .dark) .overflowItem-zVrxKo:hover {
|
|
171
|
+
color: #fff;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
:is([data-theme="dark"], .dark) .byline-breadcrumbs-overflow-item:hover {
|
|
175
|
+
color: #fff;
|
|
176
|
+
}
|
|
177
|
+
|
|
@@ -20,7 +20,7 @@ const PreviewLink = ({ collectionPath, doc, adminConfig, locale, className })=>{
|
|
|
20
20
|
setBusy(true);
|
|
21
21
|
try {
|
|
22
22
|
await enablePreviewModeFn();
|
|
23
|
-
window.
|
|
23
|
+
window.location.assign(url);
|
|
24
24
|
} catch (err) {
|
|
25
25
|
toastManager.add({
|
|
26
26
|
title: 'Preview',
|
|
@@ -42,7 +42,7 @@ const PreviewLink = ({ collectionPath, doc, adminConfig, locale, className })=>{
|
|
|
42
42
|
variant: "text",
|
|
43
43
|
disabled: busy,
|
|
44
44
|
onClick: handleClick,
|
|
45
|
-
"aria-label": "Open preview
|
|
45
|
+
"aria-label": "Open preview",
|
|
46
46
|
title: "Preview",
|
|
47
47
|
children: /*#__PURE__*/ jsx(ExternalLinkIcon, {
|
|
48
48
|
width: "20px",
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "2.2.
|
|
6
|
+
"version": "2.2.8",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20.9.0"
|
|
9
9
|
},
|
|
@@ -107,12 +107,12 @@
|
|
|
107
107
|
"react-swipeable": "^7.0.2",
|
|
108
108
|
"uuid": "^14.0.0",
|
|
109
109
|
"zod": "^4.4.3",
|
|
110
|
-
"@byline/
|
|
111
|
-
"@byline/
|
|
112
|
-
"@byline/core": "2.2.
|
|
113
|
-
"@byline/
|
|
114
|
-
"@byline/
|
|
115
|
-
"@byline/
|
|
110
|
+
"@byline/admin": "2.2.8",
|
|
111
|
+
"@byline/ai": "2.2.8",
|
|
112
|
+
"@byline/core": "2.2.8",
|
|
113
|
+
"@byline/auth": "2.2.8",
|
|
114
|
+
"@byline/ui": "2.2.8",
|
|
115
|
+
"@byline/client": "2.2.8"
|
|
116
116
|
},
|
|
117
117
|
"peerDependencies": {
|
|
118
118
|
"@tanstack/react-router": "^1.167.0",
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
display: flex;
|
|
38
38
|
align-items: center;
|
|
39
39
|
gap: 1rem;
|
|
40
|
+
flex: 1 1 auto;
|
|
41
|
+
min-width: 0;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
.right,
|
|
@@ -44,6 +46,7 @@
|
|
|
44
46
|
display: flex;
|
|
45
47
|
align-items: center;
|
|
46
48
|
gap: 1rem;
|
|
49
|
+
flex: 0 0 auto;
|
|
47
50
|
font-size: 0.875rem;
|
|
48
51
|
font-weight: 400;
|
|
49
52
|
}
|
|
@@ -2,23 +2,31 @@
|
|
|
2
2
|
* Breadcrumbs — admin app-bar trail.
|
|
3
3
|
*
|
|
4
4
|
* Override handles:
|
|
5
|
-
* .byline-breadcrumbs
|
|
6
|
-
* .byline-breadcrumbs-list
|
|
7
|
-
* .byline-breadcrumbs-item
|
|
8
|
-
* .byline-breadcrumbs-link
|
|
9
|
-
* .byline-breadcrumbs-leaf
|
|
10
|
-
* .byline-breadcrumbs-icon
|
|
5
|
+
* .byline-breadcrumbs — outer <nav>
|
|
6
|
+
* .byline-breadcrumbs-list — inline <ul>
|
|
7
|
+
* .byline-breadcrumbs-item — each <li>
|
|
8
|
+
* .byline-breadcrumbs-link — clickable home + intermediate links
|
|
9
|
+
* .byline-breadcrumbs-leaf — current-page (final) span
|
|
10
|
+
* .byline-breadcrumbs-icon — chevron / home icons
|
|
11
|
+
* .byline-breadcrumbs-overflow-trigger — "…" button shown when items collapse
|
|
12
|
+
* .byline-breadcrumbs-overflow-icon — ellipsis icon inside the trigger
|
|
13
|
+
* .byline-breadcrumbs-overflow-item — each link inside the overflow dropdown
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
.nav,
|
|
14
17
|
:global(.byline-breadcrumbs) {
|
|
15
18
|
display: flex;
|
|
19
|
+
position: relative;
|
|
20
|
+
min-width: 0;
|
|
21
|
+
flex: 1 1 auto;
|
|
22
|
+
overflow: hidden;
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
.list,
|
|
19
26
|
:global(.byline-breadcrumbs-list) {
|
|
20
27
|
display: inline-flex;
|
|
21
|
-
flex-wrap:
|
|
28
|
+
flex-wrap: nowrap;
|
|
29
|
+
white-space: nowrap;
|
|
22
30
|
align-items: center;
|
|
23
31
|
list-style: none;
|
|
24
32
|
margin: 0;
|
|
@@ -26,12 +34,24 @@
|
|
|
26
34
|
gap: 0.25rem;
|
|
27
35
|
}
|
|
28
36
|
|
|
37
|
+
/* Hidden measurement layer: mirrors all items so we can read their widths
|
|
38
|
+
without affecting layout. Positioned absolutely with visibility hidden so
|
|
39
|
+
it takes up no flow space and isn't focusable / clickable. */
|
|
40
|
+
.measure {
|
|
41
|
+
position: absolute;
|
|
42
|
+
top: 0;
|
|
43
|
+
left: 0;
|
|
44
|
+
visibility: hidden;
|
|
45
|
+
pointer-events: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
.item,
|
|
30
49
|
:global(.byline-breadcrumbs-item) {
|
|
31
50
|
display: inline-flex;
|
|
32
51
|
align-items: center;
|
|
33
52
|
margin: 0;
|
|
34
53
|
padding: 0;
|
|
54
|
+
flex: 0 0 auto;
|
|
35
55
|
}
|
|
36
56
|
|
|
37
57
|
.link,
|
|
@@ -91,3 +111,67 @@
|
|
|
91
111
|
:is([data-theme="dark"], :global(.dark)) :global(.byline-breadcrumbs-chevron-current) {
|
|
92
112
|
color: var(--gray-600);
|
|
93
113
|
}
|
|
114
|
+
|
|
115
|
+
.overflowTrigger,
|
|
116
|
+
:global(.byline-breadcrumbs-overflow-trigger) {
|
|
117
|
+
display: inline-flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
justify-content: center;
|
|
120
|
+
margin: 0;
|
|
121
|
+
padding: 0 0.125rem;
|
|
122
|
+
background: transparent;
|
|
123
|
+
border: 0;
|
|
124
|
+
border-radius: 0.25rem;
|
|
125
|
+
color: var(--gray-700);
|
|
126
|
+
cursor: pointer;
|
|
127
|
+
font: inherit;
|
|
128
|
+
line-height: 1;
|
|
129
|
+
}
|
|
130
|
+
.overflowTrigger:hover,
|
|
131
|
+
:global(.byline-breadcrumbs-overflow-trigger:hover) {
|
|
132
|
+
background-color: var(--gray-100);
|
|
133
|
+
color: var(--gray-900);
|
|
134
|
+
}
|
|
135
|
+
.overflowTrigger:focus-visible,
|
|
136
|
+
:global(.byline-breadcrumbs-overflow-trigger:focus-visible) {
|
|
137
|
+
outline: 2px solid var(--gray-400);
|
|
138
|
+
outline-offset: 1px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
:is([data-theme="dark"], :global(.dark)) .overflowTrigger,
|
|
142
|
+
:is([data-theme="dark"], :global(.dark)) :global(.byline-breadcrumbs-overflow-trigger) {
|
|
143
|
+
color: var(--gray-400);
|
|
144
|
+
}
|
|
145
|
+
:is([data-theme="dark"], :global(.dark)) .overflowTrigger:hover,
|
|
146
|
+
:is([data-theme="dark"], :global(.dark)) :global(.byline-breadcrumbs-overflow-trigger:hover) {
|
|
147
|
+
background-color: var(--gray-800);
|
|
148
|
+
color: #ffffff;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.overflowIcon,
|
|
152
|
+
:global(.byline-breadcrumbs-overflow-icon) {
|
|
153
|
+
width: 1rem;
|
|
154
|
+
height: 1rem;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.overflowItem,
|
|
158
|
+
:global(.byline-breadcrumbs-overflow-item) {
|
|
159
|
+
padding: 0.375rem 0.625rem;
|
|
160
|
+
font-size: 0.875rem;
|
|
161
|
+
color: var(--gray-900);
|
|
162
|
+
text-decoration: none;
|
|
163
|
+
cursor: pointer;
|
|
164
|
+
}
|
|
165
|
+
.overflowItem:hover,
|
|
166
|
+
:global(.byline-breadcrumbs-overflow-item:hover) {
|
|
167
|
+
color: var(--gray-900);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
:is([data-theme="dark"], :global(.dark)) .overflowItem,
|
|
171
|
+
:is([data-theme="dark"], :global(.dark)) :global(.byline-breadcrumbs-overflow-item) {
|
|
172
|
+
color: var(--gray-300);
|
|
173
|
+
}
|
|
174
|
+
:is([data-theme="dark"], :global(.dark)) .overflowItem:hover,
|
|
175
|
+
:is([data-theme="dark"], :global(.dark)) :global(.byline-breadcrumbs-overflow-item:hover) {
|
|
176
|
+
color: #ffffff;
|
|
177
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
5
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -6,12 +8,19 @@
|
|
|
6
8
|
* Copyright (c) Infonomic Company Limited
|
|
7
9
|
*/
|
|
8
10
|
|
|
11
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
12
|
+
|
|
13
|
+
import { Dropdown, EllipsisIcon } from '@byline/ui/react'
|
|
9
14
|
import cx from 'classnames'
|
|
10
15
|
|
|
11
16
|
import { Link } from '../loose-router.js'
|
|
12
17
|
import styles from './breadcrumbs.module.css'
|
|
13
18
|
import type { Breadcrumb } from './@types.js'
|
|
14
19
|
|
|
20
|
+
const useIsoLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect
|
|
21
|
+
|
|
22
|
+
const MAX_LABEL_LENGTH = 20
|
|
23
|
+
|
|
15
24
|
function truncate(str: string, length: number, useWordBoundary = true, useSuffix = true): string {
|
|
16
25
|
if (str == null || str.length <= length) return str
|
|
17
26
|
const subString = str.slice(0, length - 2)
|
|
@@ -30,67 +39,219 @@ export function Breadcrumbs({
|
|
|
30
39
|
homeLabel?: string
|
|
31
40
|
homePath?: string
|
|
32
41
|
}): React.JSX.Element {
|
|
42
|
+
const navRef = useRef<HTMLElement | null>(null)
|
|
43
|
+
const measureRef = useRef<HTMLUListElement | null>(null)
|
|
44
|
+
// visibleIndices = indices into breadcrumbs[] that are shown inline.
|
|
45
|
+
// Anything missing from this set is rolled into the overflow dropdown.
|
|
46
|
+
// Default to "all visible" so SSR and pre-measurement paints look right.
|
|
47
|
+
const [visibleIndices, setVisibleIndices] = useState<number[]>(() => breadcrumbs.map((_, i) => i))
|
|
48
|
+
|
|
49
|
+
// Reset visibility when breadcrumbs change; the measurement effect below
|
|
50
|
+
// will collapse again on the next layout tick if needed.
|
|
51
|
+
useIsoLayoutEffect(() => {
|
|
52
|
+
setVisibleIndices(breadcrumbs.map((_, i) => i))
|
|
53
|
+
}, [breadcrumbs])
|
|
54
|
+
|
|
55
|
+
useIsoLayoutEffect(() => {
|
|
56
|
+
const nav = navRef.current
|
|
57
|
+
const measure = measureRef.current
|
|
58
|
+
if (!nav || !measure) return
|
|
59
|
+
|
|
60
|
+
const compute = () => {
|
|
61
|
+
// measurement layer order: [home, ...breadcrumbs..., overflow-trigger]
|
|
62
|
+
const children = Array.from(measure.children) as HTMLElement[]
|
|
63
|
+
if (children.length !== breadcrumbs.length + 2) return
|
|
64
|
+
|
|
65
|
+
const containerWidth = nav.clientWidth
|
|
66
|
+
const homeWidth = children[0].offsetWidth
|
|
67
|
+
const triggerWidth = children[children.length - 1].offsetWidth
|
|
68
|
+
const itemWidths = children.slice(1, -1).map((el) => el.offsetWidth)
|
|
69
|
+
const gap = Number.parseFloat(getComputedStyle(measure).gap) || 4
|
|
70
|
+
|
|
71
|
+
const n = itemWidths.length
|
|
72
|
+
let next: number[]
|
|
73
|
+
|
|
74
|
+
// n <= 1: just Home + (optional) leaf — never collapse.
|
|
75
|
+
// n === 2: per design, two-segment trails (Home > Dashboard > Leaf) never collapse.
|
|
76
|
+
const allTotal = homeWidth + itemWidths.reduce((a, b) => a + b, 0) + n * gap
|
|
77
|
+
if (n <= 2 || allTotal <= containerWidth) {
|
|
78
|
+
next = itemWidths.map((_, i) => i)
|
|
79
|
+
} else {
|
|
80
|
+
// Always preserve Dashboard (idx 0) and Leaf (idx n-1).
|
|
81
|
+
// Then add middle items greedily from leaf-adjacent backward.
|
|
82
|
+
const visible = new Set<number>([0, n - 1])
|
|
83
|
+
// Gap accounting: between home/dashboard, dashboard/trigger,
|
|
84
|
+
// trigger/leaf, plus the leading gap before home.
|
|
85
|
+
const baselineGaps = 4 * gap
|
|
86
|
+
let used = homeWidth + itemWidths[0] + triggerWidth + itemWidths[n - 1] + baselineGaps
|
|
87
|
+
|
|
88
|
+
if (used > containerWidth) {
|
|
89
|
+
// Even Home + Dashboard + … + Leaf doesn't fit. Push Dashboard
|
|
90
|
+
// into the overflow too; keep Home + … + Leaf as the minimum.
|
|
91
|
+
visible.delete(0)
|
|
92
|
+
used = homeWidth + triggerWidth + itemWidths[n - 1] + 3 * gap
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (let i = n - 2; i >= 1; i--) {
|
|
96
|
+
if (visible.has(i)) continue
|
|
97
|
+
const cost = itemWidths[i] + gap
|
|
98
|
+
if (used + cost > containerWidth) break
|
|
99
|
+
used += cost
|
|
100
|
+
visible.add(i)
|
|
101
|
+
}
|
|
102
|
+
next = [...visible].sort((a, b) => a - b)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setVisibleIndices((prev) => {
|
|
106
|
+
if (prev.length === next.length && prev.every((v, i) => v === next[i])) return prev
|
|
107
|
+
return next
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
compute()
|
|
112
|
+
const ro = new ResizeObserver(compute)
|
|
113
|
+
ro.observe(nav)
|
|
114
|
+
return () => ro.disconnect()
|
|
115
|
+
}, [breadcrumbs])
|
|
116
|
+
|
|
117
|
+
const overflowed = useMemo(() => {
|
|
118
|
+
const visible = new Set(visibleIndices)
|
|
119
|
+
return breadcrumbs.filter((_, i) => !visible.has(i))
|
|
120
|
+
}, [breadcrumbs, visibleIndices])
|
|
121
|
+
|
|
122
|
+
// Walk source order, emitting visible items inline; the first time we hit
|
|
123
|
+
// an overflowed index, drop the dropdown trigger in its place.
|
|
124
|
+
const visibleSet = new Set(visibleIndices)
|
|
125
|
+
const lastIndex = breadcrumbs.length - 1
|
|
126
|
+
let overflowEmitted = false
|
|
127
|
+
const rendered: React.ReactNode[] = []
|
|
128
|
+
for (let i = 0; i < breadcrumbs.length; i++) {
|
|
129
|
+
if (visibleSet.has(i)) {
|
|
130
|
+
rendered.push(renderBreadcrumb(breadcrumbs[i], i === lastIndex))
|
|
131
|
+
} else if (!overflowEmitted) {
|
|
132
|
+
overflowEmitted = true
|
|
133
|
+
rendered.push(<OverflowDropdown key="__overflow__" items={overflowed} />)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
33
137
|
return (
|
|
34
|
-
<nav
|
|
138
|
+
<nav
|
|
139
|
+
ref={navRef}
|
|
140
|
+
aria-label="Breadcrumb"
|
|
141
|
+
className={cx('byline-breadcrumbs', styles.nav, className)}
|
|
142
|
+
>
|
|
35
143
|
<ul className={cx('byline-breadcrumbs-list', styles.list)}>
|
|
144
|
+
<HomeItem homePath={homePath} homeLabel={homeLabel} />
|
|
145
|
+
{rendered}
|
|
146
|
+
</ul>
|
|
147
|
+
{/* Hidden measurement layer — always renders every item plus the
|
|
148
|
+
overflow trigger placeholder so we can read accurate widths. */}
|
|
149
|
+
<ul ref={measureRef} aria-hidden className={cx(styles.list, styles.measure)}>
|
|
150
|
+
<HomeItem homePath={homePath} homeLabel={homeLabel} />
|
|
151
|
+
{breadcrumbs.map((b, i) => renderBreadcrumb(b, i === lastIndex))}
|
|
36
152
|
<li className={cx('byline-breadcrumbs-item', styles.item)}>
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
fill="currentColor"
|
|
42
|
-
viewBox="0 0 20 20"
|
|
43
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
44
|
-
>
|
|
45
|
-
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" />
|
|
46
|
-
</svg>
|
|
47
|
-
{homeLabel}
|
|
48
|
-
</Link>
|
|
153
|
+
<ChevronIcon />
|
|
154
|
+
<span className={cx('byline-breadcrumbs-overflow-trigger', styles.overflowTrigger)}>
|
|
155
|
+
<EllipsisIcon className={cx('byline-breadcrumbs-overflow-icon', styles.overflowIcon)} />
|
|
156
|
+
</span>
|
|
49
157
|
</li>
|
|
50
|
-
{breadcrumbs != null &&
|
|
51
|
-
breadcrumbs.length > 0 &&
|
|
52
|
-
breadcrumbs.map((breadcrumb, index) => {
|
|
53
|
-
const isLeaf = index === breadcrumbs.length - 1
|
|
54
|
-
return (
|
|
55
|
-
<li
|
|
56
|
-
key={breadcrumb.href}
|
|
57
|
-
aria-current={isLeaf ? 'page' : undefined}
|
|
58
|
-
className={cx('byline-breadcrumbs-item', styles.item)}
|
|
59
|
-
>
|
|
60
|
-
<div className={cx('byline-breadcrumbs-item-row', styles.item)}>
|
|
61
|
-
<svg
|
|
62
|
-
role="presentation"
|
|
63
|
-
className={cx('byline-breadcrumbs-chevron', styles.chevron, {
|
|
64
|
-
'byline-breadcrumbs-chevron-current': isLeaf,
|
|
65
|
-
[styles.chevronCurrent]: isLeaf,
|
|
66
|
-
})}
|
|
67
|
-
fill="currentColor"
|
|
68
|
-
viewBox="0 0 20 20"
|
|
69
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
70
|
-
>
|
|
71
|
-
<path
|
|
72
|
-
fillRule="evenodd"
|
|
73
|
-
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
|
74
|
-
clipRule="evenodd"
|
|
75
|
-
/>
|
|
76
|
-
</svg>
|
|
77
|
-
{isLeaf ? (
|
|
78
|
-
<span className={cx('byline-breadcrumbs-leaf', styles.leaf)}>
|
|
79
|
-
{truncate(breadcrumb.label, 20, true)}
|
|
80
|
-
</span>
|
|
81
|
-
) : (
|
|
82
|
-
<Link
|
|
83
|
-
to={breadcrumb.href as string}
|
|
84
|
-
className={cx('byline-breadcrumbs-link', styles.link)}
|
|
85
|
-
>
|
|
86
|
-
{truncate(breadcrumb.label, 20, true)}
|
|
87
|
-
</Link>
|
|
88
|
-
)}
|
|
89
|
-
</div>
|
|
90
|
-
</li>
|
|
91
|
-
)
|
|
92
|
-
})}
|
|
93
158
|
</ul>
|
|
94
159
|
</nav>
|
|
95
160
|
)
|
|
96
161
|
}
|
|
162
|
+
|
|
163
|
+
function renderBreadcrumb(breadcrumb: Breadcrumb, isLeaf: boolean): React.JSX.Element {
|
|
164
|
+
return (
|
|
165
|
+
<li
|
|
166
|
+
key={breadcrumb.href}
|
|
167
|
+
aria-current={isLeaf ? 'page' : undefined}
|
|
168
|
+
className={cx('byline-breadcrumbs-item', styles.item)}
|
|
169
|
+
>
|
|
170
|
+
<ChevronIcon isLeaf={isLeaf} />
|
|
171
|
+
{isLeaf ? (
|
|
172
|
+
<span className={cx('byline-breadcrumbs-leaf', styles.leaf)}>
|
|
173
|
+
{truncate(breadcrumb.label, MAX_LABEL_LENGTH, true)}
|
|
174
|
+
</span>
|
|
175
|
+
) : (
|
|
176
|
+
<Link to={breadcrumb.href as string} className={cx('byline-breadcrumbs-link', styles.link)}>
|
|
177
|
+
{truncate(breadcrumb.label, MAX_LABEL_LENGTH, true)}
|
|
178
|
+
</Link>
|
|
179
|
+
)}
|
|
180
|
+
</li>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function HomeItem({
|
|
185
|
+
homePath,
|
|
186
|
+
homeLabel,
|
|
187
|
+
}: {
|
|
188
|
+
homePath: string
|
|
189
|
+
homeLabel: string
|
|
190
|
+
}): React.JSX.Element {
|
|
191
|
+
return (
|
|
192
|
+
<li className={cx('byline-breadcrumbs-item', styles.item)}>
|
|
193
|
+
<Link to={homePath as string} className={cx('byline-breadcrumbs-link', styles.link)}>
|
|
194
|
+
<svg
|
|
195
|
+
role="presentation"
|
|
196
|
+
className={cx('byline-breadcrumbs-home-icon', styles.homeIcon)}
|
|
197
|
+
fill="currentColor"
|
|
198
|
+
viewBox="0 0 20 20"
|
|
199
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
200
|
+
>
|
|
201
|
+
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" />
|
|
202
|
+
</svg>
|
|
203
|
+
{homeLabel}
|
|
204
|
+
</Link>
|
|
205
|
+
</li>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function ChevronIcon({ isLeaf = false }: { isLeaf?: boolean }): React.JSX.Element {
|
|
210
|
+
return (
|
|
211
|
+
<svg
|
|
212
|
+
role="presentation"
|
|
213
|
+
className={cx('byline-breadcrumbs-chevron', styles.chevron, {
|
|
214
|
+
'byline-breadcrumbs-chevron-current': isLeaf,
|
|
215
|
+
[styles.chevronCurrent]: isLeaf,
|
|
216
|
+
})}
|
|
217
|
+
fill="currentColor"
|
|
218
|
+
viewBox="0 0 20 20"
|
|
219
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
220
|
+
>
|
|
221
|
+
<path
|
|
222
|
+
fillRule="evenodd"
|
|
223
|
+
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
|
224
|
+
clipRule="evenodd"
|
|
225
|
+
/>
|
|
226
|
+
</svg>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function OverflowDropdown({ items }: { items: Breadcrumb[] }): React.JSX.Element {
|
|
231
|
+
return (
|
|
232
|
+
<li className={cx('byline-breadcrumbs-item', styles.item)}>
|
|
233
|
+
<ChevronIcon />
|
|
234
|
+
<Dropdown.Root>
|
|
235
|
+
<Dropdown.Trigger
|
|
236
|
+
aria-label="Show hidden breadcrumbs"
|
|
237
|
+
className={cx('byline-breadcrumbs-overflow-trigger', styles.overflowTrigger)}
|
|
238
|
+
>
|
|
239
|
+
<EllipsisIcon className={cx('byline-breadcrumbs-overflow-icon', styles.overflowIcon)} />
|
|
240
|
+
</Dropdown.Trigger>
|
|
241
|
+
<Dropdown.Portal>
|
|
242
|
+
<Dropdown.Content sideOffset={5} align="start">
|
|
243
|
+
{items.map((item) => (
|
|
244
|
+
<Dropdown.Item
|
|
245
|
+
key={item.href}
|
|
246
|
+
className={cx('byline-breadcrumbs-overflow-item', styles.overflowItem)}
|
|
247
|
+
render={<Link to={item.href as string} />}
|
|
248
|
+
>
|
|
249
|
+
{item.label}
|
|
250
|
+
</Dropdown.Item>
|
|
251
|
+
))}
|
|
252
|
+
</Dropdown.Content>
|
|
253
|
+
</Dropdown.Portal>
|
|
254
|
+
</Dropdown.Root>
|
|
255
|
+
</li>
|
|
256
|
+
)
|
|
257
|
+
}
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* on the admin's session — so the front-end host's viewer client
|
|
15
15
|
* starts surfacing draft versions for this admin's subsequent
|
|
16
16
|
* requests.
|
|
17
|
-
* 2.
|
|
18
|
-
* `window.
|
|
17
|
+
* 2. Navigates the current tab to the document's preview URL via
|
|
18
|
+
* `window.location.assign(url)`.
|
|
19
19
|
*
|
|
20
20
|
* The preview URL comes from `CollectionAdminConfig.preview.url(doc, ctx)`
|
|
21
21
|
* when configured; otherwise it falls back to the conventional
|
|
@@ -101,13 +101,11 @@ export const PreviewLink = ({
|
|
|
101
101
|
if (busy) return
|
|
102
102
|
setBusy(true)
|
|
103
103
|
try {
|
|
104
|
-
// Enable preview mode for the admin's browser session before
|
|
105
|
-
//
|
|
106
|
-
// on subsequent requests and elevates the read context.
|
|
104
|
+
// Enable preview mode for the admin's browser session before
|
|
105
|
+
// navigating. The viewer client on the front-end host reads the
|
|
106
|
+
// cookie on subsequent requests and elevates the read context.
|
|
107
107
|
await enablePreviewModeFn()
|
|
108
|
-
|
|
109
|
-
// the admin window via `window.opener`.
|
|
110
|
-
window.open(url, '_blank', 'noopener,noreferrer')
|
|
108
|
+
window.location.assign(url)
|
|
111
109
|
} catch (err) {
|
|
112
110
|
toastManager.add({
|
|
113
111
|
title: 'Preview',
|
|
@@ -131,7 +129,7 @@ export const PreviewLink = ({
|
|
|
131
129
|
variant="text"
|
|
132
130
|
disabled={busy}
|
|
133
131
|
onClick={handleClick}
|
|
134
|
-
aria-label="Open preview
|
|
132
|
+
aria-label="Open preview"
|
|
135
133
|
title="Preview"
|
|
136
134
|
>
|
|
137
135
|
<ExternalLinkIcon width="20px" height="20px" className="byline-preview-link-icon" />
|