@unisim/sdk 0.18.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -107
- package/dist/ChangelogMenu.d.ts.map +1 -1
- package/dist/ChangelogMenu.js +16 -50
- package/dist/ChangelogMenu.js.map +1 -1
- package/dist/CompanyMenu.d.ts.map +1 -1
- package/dist/CompanyMenu.js +14 -37
- package/dist/CompanyMenu.js.map +1 -1
- package/dist/DropdownSurface.d.ts +49 -0
- package/dist/DropdownSurface.d.ts.map +1 -0
- package/dist/DropdownSurface.js +180 -0
- package/dist/DropdownSurface.js.map +1 -0
- package/dist/KnowledgeBaseMenu.d.ts.map +1 -1
- package/dist/KnowledgeBaseMenu.js +16 -39
- package/dist/KnowledgeBaseMenu.js.map +1 -1
- package/dist/SuiteSwitcher.d.ts +13 -1
- package/dist/SuiteSwitcher.d.ts.map +1 -1
- package/dist/SuiteSwitcher.js +33 -63
- package/dist/SuiteSwitcher.js.map +1 -1
- package/dist/UniversalAppsNavBar.d.ts +4 -3
- package/dist/UniversalAppsNavBar.d.ts.map +1 -1
- package/dist/UniversalAppsNavBar.js +37 -4
- package/dist/UniversalAppsNavBar.js.map +1 -1
- package/dist/UniversalNavBar.js +6 -1
- package/dist/UniversalNavBar.js.map +1 -1
- package/dist/UserProfile.d.ts.map +1 -1
- package/dist/UserProfile.js +14 -37
- package/dist/UserProfile.js.map +1 -1
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +7 -0
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/provider.js +1 -1
- package/dist/provider.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +63 -61
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useLayoutEffect, useRef, useState, } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
// Shared dropdown surface for the universal nav bar menus.
|
|
7
|
+
//
|
|
8
|
+
// Every nav dropdown (CompanyMenu, KnowledgeBaseMenu, ChangelogMenu,
|
|
9
|
+
// UserProfile, SuiteSwitcher) renders its panel through this component so the
|
|
10
|
+
// whole suite gets two guarantees for free:
|
|
11
|
+
//
|
|
12
|
+
// 1. Always on top — the panel is portaled to <body> and painted at a
|
|
13
|
+
// z-index above any reasonable host-app layer. The old approach anchored
|
|
14
|
+
// the menu with `position:absolute` inside the navbar's own DOM, which
|
|
15
|
+
// meant any ancestor that created a stacking context (a `transform`, a
|
|
16
|
+
// sticky header, a modal root, an `overflow:hidden` wrapper) could clip
|
|
17
|
+
// or paint over an open dropdown. A body portal escapes every ancestor
|
|
18
|
+
// stacking/overflow context, so nav dropdowns reliably sit in front of —
|
|
19
|
+
// and are never clipped by — page content.
|
|
20
|
+
//
|
|
21
|
+
// 2. Never off-screen — the panel's position is computed from the trigger's
|
|
22
|
+
// live viewport rect each time it opens (and on scroll / resize / async
|
|
23
|
+
// content growth). It's clamped to stay fully inside the viewport, flips
|
|
24
|
+
// above the trigger when there's no room below, and — when it's simply
|
|
25
|
+
// larger than the viewport — centres on the overflowing axis and leans on
|
|
26
|
+
// its own max-height/scroll rather than spilling off the edge.
|
|
27
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Stacking layer for portaled nav overlays. Deliberately near the top of the
|
|
30
|
+
* 32-bit range so dropdowns beat host content (including most modal/toast
|
|
31
|
+
* layers) while leaving a little headroom for a host that genuinely must
|
|
32
|
+
* layer something above the navigation.
|
|
33
|
+
*/
|
|
34
|
+
export const NAV_OVERLAY_Z_INDEX = 2_147_483_000;
|
|
35
|
+
function clamp(value, lo, hi) {
|
|
36
|
+
// Guard the degenerate slot (hi < lo, i.e. panel bigger than the gap): pin to
|
|
37
|
+
// the low bound rather than returning an inverted range.
|
|
38
|
+
if (hi < lo)
|
|
39
|
+
return lo;
|
|
40
|
+
return Math.min(Math.max(value, lo), hi);
|
|
41
|
+
}
|
|
42
|
+
// useLayoutEffect warns when run during SSR; fall back to useEffect on the
|
|
43
|
+
// server so the positioning pass stays warning-free. On the client we want the
|
|
44
|
+
// layout variant so the panel is positioned before the browser paints.
|
|
45
|
+
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
|
46
|
+
export function DropdownSurface({ open, anchorRef, align = 'left', gap = 8, margin = 8, arrow = false, surfaceStyle, role = 'menu', className, onMouseEnter, onMouseLeave, onRequestClose, children, }) {
|
|
47
|
+
const menuRef = useRef(null);
|
|
48
|
+
const [mounted, setMounted] = useState(false);
|
|
49
|
+
const [pos, setPos] = useState(null);
|
|
50
|
+
// The portal target (document.body) only exists on the client; defer the
|
|
51
|
+
// portal until after mount so SSR output and the first client render agree.
|
|
52
|
+
useEffect(() => setMounted(true), []);
|
|
53
|
+
// Position the panel from the trigger's live viewport rect, and keep it glued
|
|
54
|
+
// there while open.
|
|
55
|
+
useIsomorphicLayoutEffect(() => {
|
|
56
|
+
if (!open || !mounted)
|
|
57
|
+
return;
|
|
58
|
+
function compute() {
|
|
59
|
+
const anchor = anchorRef.current;
|
|
60
|
+
const menu = menuRef.current;
|
|
61
|
+
if (!anchor || !menu)
|
|
62
|
+
return;
|
|
63
|
+
const a = anchor.getBoundingClientRect();
|
|
64
|
+
const vw = document.documentElement.clientWidth;
|
|
65
|
+
const vh = document.documentElement.clientHeight;
|
|
66
|
+
const mw = menu.offsetWidth;
|
|
67
|
+
const mh = menu.offsetHeight;
|
|
68
|
+
// ── Horizontal ──────────────────────────────────────────────────────
|
|
69
|
+
let left = align === 'right' ? a.right - mw : a.left;
|
|
70
|
+
if (mw > vw - margin * 2) {
|
|
71
|
+
// Wider than the viewport will ever allow — centre it and let it ride.
|
|
72
|
+
left = Math.max(margin, (vw - mw) / 2);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
left = clamp(left, margin, vw - mw - margin);
|
|
76
|
+
}
|
|
77
|
+
// ── Vertical ────────────────────────────────────────────────────────
|
|
78
|
+
let top = a.bottom + gap;
|
|
79
|
+
let above = false;
|
|
80
|
+
if (top + mh > vh - margin) {
|
|
81
|
+
const aboveTop = a.top - gap - mh;
|
|
82
|
+
if (aboveTop >= margin) {
|
|
83
|
+
// Room above but not below — flip up.
|
|
84
|
+
top = aboveTop;
|
|
85
|
+
above = true;
|
|
86
|
+
}
|
|
87
|
+
else if (mh > vh - margin * 2) {
|
|
88
|
+
// Taller than the viewport — pin near the top and let the surface's
|
|
89
|
+
// own max-height/overflow scroll the rest.
|
|
90
|
+
top = margin;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Fits, but only by hugging the bottom edge.
|
|
94
|
+
top = vh - mh - margin;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const anchorCentre = a.left + a.width / 2;
|
|
98
|
+
const arrowLeft = clamp(anchorCentre - left, 16, mw - 16);
|
|
99
|
+
setPos({ left, top, arrowLeft, above });
|
|
100
|
+
}
|
|
101
|
+
compute();
|
|
102
|
+
// Keep it tracking the trigger. `scroll` is captured so nested scroll
|
|
103
|
+
// containers also trigger a recompute; the ResizeObserver catches async
|
|
104
|
+
// content growth (e.g. the changelog feed arriving after open).
|
|
105
|
+
const onReflow = () => compute();
|
|
106
|
+
window.addEventListener('scroll', onReflow, true);
|
|
107
|
+
window.addEventListener('resize', onReflow);
|
|
108
|
+
const ro = new ResizeObserver(onReflow);
|
|
109
|
+
if (menuRef.current)
|
|
110
|
+
ro.observe(menuRef.current);
|
|
111
|
+
if (anchorRef.current)
|
|
112
|
+
ro.observe(anchorRef.current);
|
|
113
|
+
return () => {
|
|
114
|
+
window.removeEventListener('scroll', onReflow, true);
|
|
115
|
+
window.removeEventListener('resize', onReflow);
|
|
116
|
+
ro.disconnect();
|
|
117
|
+
};
|
|
118
|
+
}, [open, mounted, align, gap, margin, anchorRef]);
|
|
119
|
+
// Outside-click + Escape, centralised. The trigger and the portaled panel
|
|
120
|
+
// both count as "inside" so neither dismisses the menu.
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!open || !onRequestClose)
|
|
123
|
+
return;
|
|
124
|
+
function onDown(e) {
|
|
125
|
+
const target = e.target;
|
|
126
|
+
if (anchorRef.current?.contains(target))
|
|
127
|
+
return;
|
|
128
|
+
if (menuRef.current?.contains(target))
|
|
129
|
+
return;
|
|
130
|
+
onRequestClose();
|
|
131
|
+
}
|
|
132
|
+
function onKey(e) {
|
|
133
|
+
if (e.key === 'Escape')
|
|
134
|
+
onRequestClose();
|
|
135
|
+
}
|
|
136
|
+
document.addEventListener('mousedown', onDown);
|
|
137
|
+
document.addEventListener('keydown', onKey);
|
|
138
|
+
return () => {
|
|
139
|
+
document.removeEventListener('mousedown', onDown);
|
|
140
|
+
document.removeEventListener('keydown', onKey);
|
|
141
|
+
};
|
|
142
|
+
}, [open, onRequestClose, anchorRef]);
|
|
143
|
+
if (!mounted)
|
|
144
|
+
return null;
|
|
145
|
+
const positioned = open && pos !== null;
|
|
146
|
+
const style = {
|
|
147
|
+
...surfaceStyle,
|
|
148
|
+
position: 'fixed',
|
|
149
|
+
left: pos?.left ?? -9999,
|
|
150
|
+
top: pos?.top ?? -9999,
|
|
151
|
+
// Defeat any leftover edge anchoring from surfaceStyle so only left/top win.
|
|
152
|
+
right: 'auto',
|
|
153
|
+
bottom: 'auto',
|
|
154
|
+
margin: 0,
|
|
155
|
+
zIndex: NAV_OVERLAY_Z_INDEX,
|
|
156
|
+
opacity: positioned ? 1 : 0,
|
|
157
|
+
visibility: positioned ? 'visible' : 'hidden',
|
|
158
|
+
transform: positioned ? 'translateY(0)' : `translateY(${pos?.above ? 4 : -4}px)`,
|
|
159
|
+
transition: 'opacity 150ms cubic-bezier(0.16,1,0.3,1), transform 150ms cubic-bezier(0.16,1,0.3,1), visibility 150ms',
|
|
160
|
+
pointerEvents: open ? 'auto' : 'none',
|
|
161
|
+
};
|
|
162
|
+
return createPortal(_jsxs("div", { ref: menuRef, role: role, className: className, style: style, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, children: [arrow && pos && !pos.above && _jsx("span", { style: arrowStyle(pos.arrowLeft), "aria-hidden": true }), children] }), document.body);
|
|
163
|
+
}
|
|
164
|
+
// Caret that points from the panel up at its trigger. Matches the white square
|
|
165
|
+
// rotated 45° used by the old SuiteSwitcher / ChangelogMenu arrows; only shown
|
|
166
|
+
// when the panel sits below the trigger (the normal case for a top navbar).
|
|
167
|
+
function arrowStyle(arrowLeft) {
|
|
168
|
+
return {
|
|
169
|
+
position: 'absolute',
|
|
170
|
+
top: -6,
|
|
171
|
+
left: arrowLeft - 6,
|
|
172
|
+
width: 12,
|
|
173
|
+
height: 12,
|
|
174
|
+
background: '#ffffff',
|
|
175
|
+
borderLeft: '1px solid #e2e8f0',
|
|
176
|
+
borderTop: '1px solid #e2e8f0',
|
|
177
|
+
transform: 'rotate(45deg)',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=DropdownSurface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DropdownSurface.js","sourceRoot":"","sources":["../src/DropdownSurface.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnowledgeBaseMenu.d.ts","sourceRoot":"","sources":["../src/KnowledgeBaseMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"KnowledgeBaseMenu.d.ts","sourceRoot":"","sources":["../src/KnowledgeBaseMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,OAAO,CAAA;AASd,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAS,MAAM,CAAA;IACjB,sEAAsE;IACtE,IAAI,EAAO,MAAM,CAAA;IACjB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,IAAI,EAAO,MAAM,CAAA;IACjB,gFAAgF;IAChF,KAAK,CAAC,EAAK,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,YAAY,CAAC,EAAK,iBAAiB,EAAE,CAAA;IACrC,uDAAuD;IACvD,aAAa,CAAC,EAAI,MAAM,CAAA;IACxB,uFAAuF;IACvF,YAAY,CAAC,EAAK,MAAM,CAAA;IACxB;;;;;OAKG;IACH,YAAY,CAAC,EAAK,iBAAiB,EAAE,CAAA;IACrC,uDAAuD;IACvD,aAAa,CAAC,EAAI,MAAM,CAAA;IACxB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,0EAA0E;IAC1E,YAAY,CAAC,EAAK,aAAa,CAAA;IAC/B,KAAK,CAAC,EAAY,aAAa,CAAA;IAC/B,SAAS,CAAC,EAAQ,MAAM,CAAA;CACzB;AASD;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,iBAAiB,EAQpD,CAAA;AA2BD,wBAAgB,iBAAiB,CAAC,EAChC,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,KAAK,EACL,SAAS,GACV,EAAE,sBAAsB,2CA0FxB"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useRef,
|
|
3
|
+
import { useState, useRef, } from 'react';
|
|
4
4
|
import { t } from './i18n.js';
|
|
5
5
|
import { useLanguage } from './provider.js';
|
|
6
|
+
import { DropdownSurface } from './DropdownSurface.js';
|
|
6
7
|
/**
|
|
7
8
|
* Cap on items shown per section in the dropdown. Anything beyond this is
|
|
8
9
|
* surfaced via the "View all" row, so the menu stays compact regardless of
|
|
@@ -63,22 +64,6 @@ export function KnowledgeBaseMenu({ productItems, productKBHref, productLabel, c
|
|
|
63
64
|
clearTimeout(closeTimer.current);
|
|
64
65
|
closeTimer.current = setTimeout(() => setOpen(false), 220);
|
|
65
66
|
}
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
function onDown(e) {
|
|
68
|
-
if (wrapRef.current && !wrapRef.current.contains(e.target))
|
|
69
|
-
setOpen(false);
|
|
70
|
-
}
|
|
71
|
-
function onKey(e) {
|
|
72
|
-
if (e.key === 'Escape')
|
|
73
|
-
setOpen(false);
|
|
74
|
-
}
|
|
75
|
-
document.addEventListener('mousedown', onDown);
|
|
76
|
-
document.addEventListener('keydown', onKey);
|
|
77
|
-
return () => {
|
|
78
|
-
document.removeEventListener('mousedown', onDown);
|
|
79
|
-
document.removeEventListener('keydown', onKey);
|
|
80
|
-
};
|
|
81
|
-
}, []);
|
|
82
67
|
const visibleProductItems = productItems?.slice(0, MAX_ITEMS) ?? [];
|
|
83
68
|
// Fall back to the suite-wide default ("About Universal Simulation") when
|
|
84
69
|
// the consumer doesn't pass anything explicit. Pass `companyItems={[]}` to
|
|
@@ -87,7 +72,7 @@ export function KnowledgeBaseMenu({ productItems, productKBHref, productLabel, c
|
|
|
87
72
|
const visibleCompanyItems = effectiveCompanyItems.slice(0, MAX_ITEMS);
|
|
88
73
|
const hasProductItems = visibleProductItems.length > 0;
|
|
89
74
|
const hasCompanyItems = visibleCompanyItems.length > 0;
|
|
90
|
-
return (_jsxs("div", { ref: wrapRef, className: className, style: { position: 'relative', display: 'inline-block', ...style }, onMouseEnter: openNow, onMouseLeave: closeSoon, children: [_jsxs("button", { type: "button", "aria-label": t(language, 'kb.label'), "aria-expanded": open, "aria-haspopup": "true", onClick: () => setOpen((v) => !v), onFocus: openNow, className: triggerClassName, style: triggerClassName ? undefined : { ...defaultTriggerStyle, ...triggerStyle }, children: [t(language, 'kb.label'), _jsx(IconChevron, {})] }), _jsxs("
|
|
75
|
+
return (_jsxs("div", { ref: wrapRef, className: className, style: { position: 'relative', display: 'inline-block', ...style }, onMouseEnter: openNow, onMouseLeave: closeSoon, children: [_jsxs("button", { type: "button", "aria-label": t(language, 'kb.label'), "aria-expanded": open, "aria-haspopup": "true", onClick: () => setOpen((v) => !v), onFocus: openNow, className: triggerClassName, style: triggerClassName ? undefined : { ...defaultTriggerStyle, ...triggerStyle }, children: [t(language, 'kb.label'), _jsx(IconChevron, {})] }), _jsxs(DropdownSurface, { open: open, anchorRef: wrapRef, align: "left", role: "menu", surfaceStyle: menuSurfaceStyle, onMouseEnter: openNow, onMouseLeave: closeSoon, onRequestClose: () => setOpen(false), children: [_jsx(SectionHeader, { label: resolvedProductLabel }), hasProductItems ? (_jsxs(_Fragment, { children: [_jsx("div", { style: listStyle, children: visibleProductItems.map((item) => (_jsx(KBLink, { item: item }, item.id))) }), _jsx(SeeAllRow, { href: productKBHref ?? '#', label: t(language, 'kb.view_all') })] })) : (_jsx(EmptyState, { text: t(language, 'kb.no_articles') })), _jsx(Divider, {}), _jsx(SectionHeader, { label: t(language, 'kb.company_knowledge') }), hasCompanyItems ? (_jsxs(_Fragment, { children: [_jsx("div", { style: listStyle, children: visibleCompanyItems.map((item) => (_jsx(KBLink, { item: item }, item.id))) }), _jsx(SeeAllRow, { href: companyKBHref ?? '#', label: t(language, 'kb.view_all') })] })) : (_jsx(EmptyState, { text: t(language, 'kb.company_coming_soon') }))] })] }));
|
|
91
76
|
}
|
|
92
77
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
93
78
|
// Subcomponents
|
|
@@ -133,27 +118,19 @@ const defaultTriggerStyle = {
|
|
|
133
118
|
cursor: 'pointer',
|
|
134
119
|
fontFamily: 'inherit',
|
|
135
120
|
};
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
transform: open ? 'translateY(0)' : 'translateY(-4px)',
|
|
150
|
-
transition: 'opacity 150ms cubic-bezier(0.16,1,0.3,1), transform 150ms cubic-bezier(0.16,1,0.3,1), visibility 150ms',
|
|
151
|
-
zIndex: 50,
|
|
152
|
-
color: '#111827',
|
|
153
|
-
maxHeight: '70vh',
|
|
154
|
-
overflowY: 'auto',
|
|
155
|
-
};
|
|
156
|
-
}
|
|
121
|
+
// Visual styling only — positioning, stacking and the open/close transition
|
|
122
|
+
// are owned by <DropdownSurface />.
|
|
123
|
+
const menuSurfaceStyle = {
|
|
124
|
+
minWidth: 280,
|
|
125
|
+
background: '#ffffff',
|
|
126
|
+
border: '1px solid #e5e7eb',
|
|
127
|
+
borderRadius: 12,
|
|
128
|
+
boxShadow: '0 16px 32px -8px rgba(15,23,42,0.16), 0 4px 8px -2px rgba(15,23,42,0.08)',
|
|
129
|
+
padding: '6px 0',
|
|
130
|
+
color: '#111827',
|
|
131
|
+
maxHeight: '70vh',
|
|
132
|
+
overflowY: 'auto',
|
|
133
|
+
};
|
|
157
134
|
const sectionHeaderStyle = {
|
|
158
135
|
padding: '8px 14px 4px',
|
|
159
136
|
fontSize: 10,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnowledgeBaseMenu.js","sourceRoot":"","sources":["../src/KnowledgeBaseMenu.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EACL,QAAQ,EACR,MAAM,
|
|
1
|
+
{"version":3,"file":"KnowledgeBaseMenu.js","sourceRoot":"","sources":["../src/KnowledgeBaseMenu.tsx"],"names":[],"mappings":"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"}
|
package/dist/SuiteSwitcher.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode, type CSSProperties } from 'react';
|
|
2
|
-
export type SuiteProductId = 'ergo_assess' | 'cyber_assess' | 'workplace_assess' | 'pdf' | 'webinar' | 'images' | 'exports' | '
|
|
2
|
+
export type SuiteProductId = 'ergo_assess' | 'cyber_assess' | 'workplace_assess' | 'pdf' | 'webinar' | 'images' | 'exports' | 'blackbook' | 'qr' | (string & {});
|
|
3
3
|
export interface SuiteProduct {
|
|
4
4
|
id: SuiteProductId;
|
|
5
5
|
name: string;
|
|
@@ -21,6 +21,18 @@ export interface SuiteProduct {
|
|
|
21
21
|
* roadmap without being able to navigate to a half-built page).
|
|
22
22
|
*/
|
|
23
23
|
comingSoon?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Audience category used to keep distinct product families from mixing in
|
|
26
|
+
* the switcher. When the *current* product declares a category, the dropdown
|
|
27
|
+
* only lists products sharing that category — so an `everyday` app (PDF,
|
|
28
|
+
* Images, QR) never surfaces a `business` app (Exports, Blackbook) and vice
|
|
29
|
+
* versa. The portal (rendered separately) still shows everything, so it
|
|
30
|
+
* stays the one place users can cross between families.
|
|
31
|
+
*
|
|
32
|
+
* Leave undefined to opt a product out of filtering: an uncategorised
|
|
33
|
+
* current product shows the full list (the Assess suite relies on this).
|
|
34
|
+
*/
|
|
35
|
+
category?: 'everyday' | 'business' | (string & {});
|
|
24
36
|
}
|
|
25
37
|
export interface SuiteSwitcherProps {
|
|
26
38
|
/** Which product is currently being viewed — gets the "You're here" badge. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SuiteSwitcher.d.ts","sourceRoot":"","sources":["../src/SuiteSwitcher.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"SuiteSwitcher.d.ts","sourceRoot":"","sources":["../src/SuiteSwitcher.tsx"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,OAAO,CAAA;AASd,MAAM,MAAM,cAAc,GACtB,aAAa,GACb,cAAc,GACd,kBAAkB,GAClB,KAAK,GACL,SAAS,GACT,QAAQ,GACR,SAAS,GACT,WAAW,GACX,IAAI,GACJ,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEjB,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,cAAc,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAA;IACZ,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,KAAK,EAAE,SAAS,CAAA;IAChB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;CACnD;AAED,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,OAAO,EAAE,cAAc,CAAA;IACvB;;;OAGG;IACH,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAA;IACzB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACxB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAChD;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uEAAuE;IACvE,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAeD,wBAAgB,MAAM,CAAC,EAAE,KAAU,EAAE,MAAW,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,2CAoBtF;AAqED,eAAO,MAAM,sBAAsB,EAAE,YAAY,EA6BhD,CAAA;AAmFD,eAAO,MAAM,+BAA+B,EAAE,YAAY,EAmCzD,CAAA;AAgBD,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,QAAiC,EACjC,OAA8B,EAC9B,SAAS,EACT,QAAQ,EACR,KAAe,EACf,IAAI,EACJ,UAA0C,EAC1C,WAAW,EACX,UAAU,EACV,KAAK,EACL,SAAS,GACV,EAAE,kBAAkB,2CA4LpB"}
|
package/dist/SuiteSwitcher.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useRef,
|
|
3
|
+
import { useState, useRef, } from 'react';
|
|
4
4
|
import { t } from './i18n.js';
|
|
5
5
|
import { useLanguage } from './provider.js';
|
|
6
|
+
import { DropdownSurface } from './DropdownSurface.js';
|
|
6
7
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
7
8
|
// Default product catalogue
|
|
8
9
|
//
|
|
@@ -46,7 +47,6 @@ export const DEFAULT_SUITE_PRODUCTS = [
|
|
|
46
47
|
href: 'https://assess.unisim.co.uk/cyber',
|
|
47
48
|
glyph: CYBER_GLYPH,
|
|
48
49
|
flag: _jsx(UKFlag, {}),
|
|
49
|
-
comingSoon: true,
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
// Product key stays `workplace_assess` (infrastructure ID, used by the
|
|
@@ -59,7 +59,6 @@ export const DEFAULT_SUITE_PRODUCTS = [
|
|
|
59
59
|
href: 'https://assess.unisim.co.uk/wfh',
|
|
60
60
|
glyph: WORKPLACE_GLYPH,
|
|
61
61
|
flag: _jsx(UKFlag, {}),
|
|
62
|
-
comingSoon: true,
|
|
63
62
|
},
|
|
64
63
|
];
|
|
65
64
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
@@ -84,20 +83,17 @@ export const DEFAULT_UNIVERSAL_APPS_PRODUCTS = [
|
|
|
84
83
|
desc: 'View, edit, sign & redact PDFs locally',
|
|
85
84
|
href: 'https://opensource.unisim.co.uk/pdf',
|
|
86
85
|
glyph: PDF_GLYPH,
|
|
86
|
+
category: 'everyday',
|
|
87
87
|
},
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
name: 'Universal Webinar',
|
|
91
|
-
desc: 'Lightweight live Q&A with chat + reactions',
|
|
92
|
-
href: 'https://opensource.unisim.co.uk/webinar',
|
|
93
|
-
glyph: WEBINAR_GLYPH,
|
|
94
|
-
},
|
|
88
|
+
// Universal Webinar is intentionally omitted from the dropdown for now —
|
|
89
|
+
// WEBINAR_GLYPH above is kept so re-adding the entry is a one-liner.
|
|
95
90
|
{
|
|
96
91
|
id: 'images',
|
|
97
92
|
name: 'Universal Images',
|
|
98
93
|
desc: 'Drag, resize & optimise images in-browser',
|
|
99
94
|
href: 'https://opensource.unisim.co.uk/images',
|
|
100
95
|
glyph: IMAGES_GLYPH,
|
|
96
|
+
category: 'everyday',
|
|
101
97
|
},
|
|
102
98
|
{
|
|
103
99
|
id: 'exports',
|
|
@@ -105,6 +101,7 @@ export const DEFAULT_UNIVERSAL_APPS_PRODUCTS = [
|
|
|
105
101
|
desc: 'Compose, schedule & deliver data exports',
|
|
106
102
|
href: 'https://opensource.unisim.co.uk/exports',
|
|
107
103
|
glyph: EXPORTS_GLYPH,
|
|
104
|
+
category: 'business',
|
|
108
105
|
},
|
|
109
106
|
{
|
|
110
107
|
id: 'qr',
|
|
@@ -112,13 +109,14 @@ export const DEFAULT_UNIVERSAL_APPS_PRODUCTS = [
|
|
|
112
109
|
desc: 'Design branded, styled QR codes in-browser',
|
|
113
110
|
href: 'https://opensource.unisim.co.uk/qr',
|
|
114
111
|
glyph: QR_GLYPH,
|
|
112
|
+
category: 'everyday',
|
|
115
113
|
},
|
|
116
114
|
];
|
|
117
115
|
// Inline UNI·SIM monogram so the SDK has zero asset dependencies.
|
|
118
116
|
const DEFAULT_TRIGGER_ICON = 'data:image/svg+xml;utf8,' +
|
|
119
|
-
encodeURIComponent(`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'>
|
|
120
|
-
<rect width='32' height='32' rx='8' fill='#e05504'/>
|
|
121
|
-
<text x='16' y='21' text-anchor='middle' font-family='-apple-system,Segoe UI,Helvetica,Arial,sans-serif' font-weight='800' font-size='13' fill='white' letter-spacing='-0.5'>US</text>
|
|
117
|
+
encodeURIComponent(`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'>
|
|
118
|
+
<rect width='32' height='32' rx='8' fill='#e05504'/>
|
|
119
|
+
<text x='16' y='21' text-anchor='middle' font-family='-apple-system,Segoe UI,Helvetica,Arial,sans-serif' font-weight='800' font-size='13' fill='white' letter-spacing='-0.5'>US</text>
|
|
122
120
|
</svg>`);
|
|
123
121
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
124
122
|
// Component
|
|
@@ -134,6 +132,16 @@ export function SuiteSwitcher({ current, products = DEFAULT_SUITE_PRODUCTS, icon
|
|
|
134
132
|
title: t(language, 'suite.portal_title_assess'),
|
|
135
133
|
};
|
|
136
134
|
const [open, setOpen] = useState(false);
|
|
135
|
+
// Keep product families from mixing: when the current product declares a
|
|
136
|
+
// category, only list products in that same category (the current product
|
|
137
|
+
// always stays, so its "You're here" row never disappears). An uncategorised
|
|
138
|
+
// current product — e.g. the Assess suite — shows the full list unchanged.
|
|
139
|
+
// The portal row above is rendered separately and always lists everything,
|
|
140
|
+
// so it remains the one crossover point between families.
|
|
141
|
+
const currentCategory = products.find((p) => p.id === current)?.category;
|
|
142
|
+
const visibleProducts = currentCategory
|
|
143
|
+
? products.filter((p) => p.category === currentCategory || p.id === current)
|
|
144
|
+
: products;
|
|
137
145
|
const wrapRef = useRef(null);
|
|
138
146
|
const closeTimer = useRef(null);
|
|
139
147
|
function openNow() {
|
|
@@ -146,23 +154,6 @@ export function SuiteSwitcher({ current, products = DEFAULT_SUITE_PRODUCTS, icon
|
|
|
146
154
|
clearTimeout(closeTimer.current);
|
|
147
155
|
closeTimer.current = setTimeout(() => setOpen(false), 220);
|
|
148
156
|
}
|
|
149
|
-
// close on outside click / escape
|
|
150
|
-
useEffect(() => {
|
|
151
|
-
function onDown(e) {
|
|
152
|
-
if (wrapRef.current && !wrapRef.current.contains(e.target))
|
|
153
|
-
setOpen(false);
|
|
154
|
-
}
|
|
155
|
-
function onKey(e) {
|
|
156
|
-
if (e.key === 'Escape')
|
|
157
|
-
setOpen(false);
|
|
158
|
-
}
|
|
159
|
-
document.addEventListener('mousedown', onDown);
|
|
160
|
-
document.addEventListener('keydown', onKey);
|
|
161
|
-
return () => {
|
|
162
|
-
document.removeEventListener('mousedown', onDown);
|
|
163
|
-
document.removeEventListener('keydown', onKey);
|
|
164
|
-
};
|
|
165
|
-
}, []);
|
|
166
157
|
// Toggle on click — but only if the click hit the wrapper itself or a
|
|
167
158
|
// non-interactive descendant. Lets a child <a> still navigate without us
|
|
168
159
|
// swallowing the click.
|
|
@@ -172,7 +163,7 @@ export function SuiteSwitcher({ current, products = DEFAULT_SUITE_PRODUCTS, icon
|
|
|
172
163
|
return;
|
|
173
164
|
setOpen((v) => !v);
|
|
174
165
|
}
|
|
175
|
-
return (_jsxs("div", { ref: wrapRef, className: className, style: { position: 'relative', display: 'inline-block', ...style }, onMouseEnter: openNow, onMouseLeave: closeSoon, onFocusCapture: openNow, children: [children ? (_jsxs("div", { "aria-haspopup": "true", "aria-expanded": open, onClick: onWrapperClick, style: { cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 6 }, children: [children, _jsx(SwitcherChevron, { open: open })] })) : (_jsx("button", { type: "button", "aria-label": resolvedAriaLabel, "aria-expanded": open, "aria-haspopup": "true", onClick: () => setOpen((v) => !v), onFocus: openNow, style: triggerStyle(open), children: _jsx("img", { src: iconSrc, alt: "", "aria-hidden": true, style: { width: 28, height: 28, display: 'block' } }) })), _jsxs(
|
|
166
|
+
return (_jsxs("div", { ref: wrapRef, className: className, style: { position: 'relative', display: 'inline-block', ...style }, onMouseEnter: openNow, onMouseLeave: closeSoon, onFocusCapture: openNow, children: [children ? (_jsxs("div", { "aria-haspopup": "true", "aria-expanded": open, onClick: onWrapperClick, style: { cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 6 }, children: [children, _jsx(SwitcherChevron, { open: open })] })) : (_jsx("button", { type: "button", "aria-label": resolvedAriaLabel, "aria-expanded": open, "aria-haspopup": "true", onClick: () => setOpen((v) => !v), onFocus: openNow, style: triggerStyle(open), children: _jsx("img", { src: iconSrc, alt: "", "aria-hidden": true, style: { width: 28, height: 28, display: 'block' } }) })), _jsxs(DropdownSurface, { open: open, anchorRef: wrapRef, align: align, arrow: true, role: "menu", surfaceStyle: menuSurfaceStyle, onMouseEnter: openNow, onMouseLeave: closeSoon, onRequestClose: () => setOpen(false), children: [_jsxs("a", { role: "menuitem", href: portalHref, style: portalItemStyle, onMouseEnter: (e) => { e.currentTarget.style.background = '#fff7ed'; }, onMouseLeave: (e) => { e.currentTarget.style.background = 'transparent'; }, children: [_jsx("span", { style: glyphStyle, children: UNISIM_GLYPH }), _jsxs("span", { style: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 1 }, children: [_jsx("span", { style: portalEyebrowStyle, children: resolvedPortalLabel.eyebrow }), _jsx("span", { style: portalTitleStyle, children: resolvedPortalLabel.title })] }), chip && _jsx("div", { style: { flexShrink: 0 }, children: chip })] }), _jsx("div", { style: portalDividerStyle, "aria-hidden": true }), visibleProducts.map((p) => {
|
|
176
167
|
const isCurrent = p.id === current;
|
|
177
168
|
const isComingSoon = p.comingSoon === true;
|
|
178
169
|
const isDisabled = isCurrent || isComingSoon;
|
|
@@ -228,38 +219,17 @@ function triggerStyle(open) {
|
|
|
228
219
|
boxShadow: open ? '0 0 0 4px rgba(224,85,4,0.08)' : 'none',
|
|
229
220
|
};
|
|
230
221
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
opacity: open ? 1 : 0,
|
|
243
|
-
visibility: open ? 'visible' : 'hidden',
|
|
244
|
-
transform: open ? 'translateY(0)' : 'translateY(-4px)',
|
|
245
|
-
transition: 'opacity 150ms cubic-bezier(0.16,1,0.3,1), transform 150ms cubic-bezier(0.16,1,0.3,1), visibility 150ms',
|
|
246
|
-
zIndex: 50,
|
|
247
|
-
color: '#0f172a',
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
function menuArrowStyle(align) {
|
|
251
|
-
return {
|
|
252
|
-
position: 'absolute',
|
|
253
|
-
top: -6,
|
|
254
|
-
[align === 'right' ? 'right' : 'left']: 14,
|
|
255
|
-
width: 12,
|
|
256
|
-
height: 12,
|
|
257
|
-
background: '#ffffff',
|
|
258
|
-
borderLeft: '1px solid #e2e8f0',
|
|
259
|
-
borderTop: '1px solid #e2e8f0',
|
|
260
|
-
transform: 'rotate(45deg)',
|
|
261
|
-
};
|
|
262
|
-
}
|
|
222
|
+
// Visual styling only — positioning, stacking, the caret and the open/close
|
|
223
|
+
// transition are owned by <DropdownSurface />.
|
|
224
|
+
const menuSurfaceStyle = {
|
|
225
|
+
width: 320,
|
|
226
|
+
background: '#ffffff',
|
|
227
|
+
border: '1px solid #e2e8f0',
|
|
228
|
+
borderRadius: 16,
|
|
229
|
+
boxShadow: '0 16px 32px -8px rgba(15,23,42,0.16), 0 4px 8px -2px rgba(15,23,42,0.08)',
|
|
230
|
+
padding: 12,
|
|
231
|
+
color: '#0f172a',
|
|
232
|
+
};
|
|
263
233
|
// Small ▾ chevron rendered next to a custom trigger so it visually reads as
|
|
264
234
|
// a dropdown. Rotates 180° when open.
|
|
265
235
|
function SwitcherChevron({ open }) {
|