@visns-studio/visns-components 6.3.18 → 6.4.1
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/package.json
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
92
92
|
},
|
|
93
93
|
"name": "@visns-studio/visns-components",
|
|
94
|
-
"version": "6.
|
|
94
|
+
"version": "6.4.1",
|
|
95
95
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
96
96
|
"main": "src/index.js",
|
|
97
97
|
"files": [
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
|
2
2
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
|
3
3
|
import {
|
|
4
|
+
BarChart3,
|
|
5
|
+
Briefcase,
|
|
4
6
|
Bug,
|
|
7
|
+
Building2,
|
|
5
8
|
Calendar,
|
|
6
9
|
CreditCard,
|
|
7
10
|
FileText,
|
|
11
|
+
Home,
|
|
12
|
+
LayoutDashboard,
|
|
13
|
+
MapPin,
|
|
8
14
|
Users,
|
|
9
15
|
User,
|
|
10
16
|
Search,
|
|
@@ -22,6 +28,28 @@ import SwitchAccount from './SwitchAccount';
|
|
|
22
28
|
import BusinessCardOcr from './BusinessCardOcr';
|
|
23
29
|
import styles from './styles/Navigation.module.scss';
|
|
24
30
|
|
|
31
|
+
// Opt-in icons for the sidebar (simple/cms) layouts: a nav entry names one
|
|
32
|
+
// via its `icon` key, falling back to its `id`. Unknown names render
|
|
33
|
+
// nothing, and the header layout never renders these, so existing nav
|
|
34
|
+
// configs are unaffected.
|
|
35
|
+
const NAV_ICONS = {
|
|
36
|
+
billing: CreditCard,
|
|
37
|
+
calendar: Calendar,
|
|
38
|
+
clients: Building2,
|
|
39
|
+
contacts: Users,
|
|
40
|
+
customers: Building2,
|
|
41
|
+
dashboard: LayoutDashboard,
|
|
42
|
+
files: FileText,
|
|
43
|
+
home: Home,
|
|
44
|
+
jobs: Briefcase,
|
|
45
|
+
phone: Phone,
|
|
46
|
+
reports: BarChart3,
|
|
47
|
+
safety: Shield,
|
|
48
|
+
settings: Settings,
|
|
49
|
+
sites: MapPin,
|
|
50
|
+
users: Users,
|
|
51
|
+
};
|
|
52
|
+
|
|
25
53
|
const SearchComponent = ({
|
|
26
54
|
navigate,
|
|
27
55
|
search,
|
|
@@ -199,6 +227,19 @@ function Navigation({
|
|
|
199
227
|
const navigate = useNavigate();
|
|
200
228
|
const currentPage = location.pathname.substr(1) || 'dashboard';
|
|
201
229
|
const [isOpen, setOpen] = useState(false);
|
|
230
|
+
|
|
231
|
+
// The mobile drawer is a full-viewport fixed overlay; without a scroll
|
|
232
|
+
// lock the page behind it keeps scrolling under the user's thumb.
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (isOpen) {
|
|
235
|
+
const previousOverflow = document.body.style.overflow;
|
|
236
|
+
document.body.style.overflow = 'hidden';
|
|
237
|
+
return () => {
|
|
238
|
+
document.body.style.overflow = previousOverflow;
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return undefined;
|
|
242
|
+
}, [isOpen]);
|
|
202
243
|
const [isScrolled, setIsScrolled] = useState(false);
|
|
203
244
|
const [navData, setNavData] = useState(navConfig);
|
|
204
245
|
const [showBusinessCardOcr, setShowBusinessCardOcr] = useState(false);
|
|
@@ -320,6 +361,10 @@ function Navigation({
|
|
|
320
361
|
|
|
321
362
|
const renderNav = (n) => {
|
|
322
363
|
let navItemClasses;
|
|
364
|
+
const NavItemIcon =
|
|
365
|
+
layout === 'simple' || layout === 'cms'
|
|
366
|
+
? NAV_ICONS[n.icon || n.id]
|
|
367
|
+
: undefined;
|
|
323
368
|
|
|
324
369
|
if (n.class.includes('active')) {
|
|
325
370
|
navItemClasses =
|
|
@@ -372,6 +417,7 @@ function Navigation({
|
|
|
372
417
|
? styles.active
|
|
373
418
|
: ''
|
|
374
419
|
}
|
|
420
|
+
onClick={() => setOpen(false)}
|
|
375
421
|
>
|
|
376
422
|
{child.label}
|
|
377
423
|
</Link>
|
|
@@ -391,7 +437,12 @@ function Navigation({
|
|
|
391
437
|
onMouseLeave={handleMouseLeave}
|
|
392
438
|
>
|
|
393
439
|
{n.url ? (
|
|
394
|
-
|
|
440
|
+
// Close the mobile drawer when navigating; no-op for the
|
|
441
|
+
// header layout where the drawer state is never opened.
|
|
442
|
+
<Link to={n.url} onClick={() => setOpen(false)}>
|
|
443
|
+
{NavItemIcon ? <NavItemIcon size={18} /> : null}
|
|
444
|
+
{n.label}
|
|
445
|
+
</Link>
|
|
395
446
|
) : (
|
|
396
447
|
<span title={n.label}>{n.label}</span>
|
|
397
448
|
)}
|
|
@@ -22,10 +22,54 @@ import TwoFactorAuth from '../auth/TwoFactorAuth';
|
|
|
22
22
|
import ClientLogin from '../auth/ClientLogin';
|
|
23
23
|
import ClientOTPVerify from '../auth/ClientOTPVerify';
|
|
24
24
|
|
|
25
|
+
// Shown while the profile check is in flight. Rendering null there leaves a
|
|
26
|
+
// blank white page between the sign-in redirect and the first authenticated
|
|
27
|
+
// paint. Self-contained: SMIL animation, no stylesheet dependency, and the
|
|
28
|
+
// consuming app's design tokens with neutral fallbacks.
|
|
29
|
+
const AuthLoading = () => (
|
|
30
|
+
<div
|
|
31
|
+
role="status"
|
|
32
|
+
aria-label="Loading"
|
|
33
|
+
style={{
|
|
34
|
+
position: 'fixed',
|
|
35
|
+
inset: 0,
|
|
36
|
+
display: 'flex',
|
|
37
|
+
alignItems: 'center',
|
|
38
|
+
justifyContent: 'center',
|
|
39
|
+
background: 'var(--bg-color, #f4f4f5)',
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<svg width="42" height="42" viewBox="0 0 42 42" fill="none">
|
|
43
|
+
<circle
|
|
44
|
+
cx="21"
|
|
45
|
+
cy="21"
|
|
46
|
+
r="18"
|
|
47
|
+
stroke="var(--border-color, #dfe3eb)"
|
|
48
|
+
strokeWidth="4"
|
|
49
|
+
/>
|
|
50
|
+
<path
|
|
51
|
+
d="M39 21a18 18 0 0 0-18-18"
|
|
52
|
+
stroke="var(--primary-color, #52525b)"
|
|
53
|
+
strokeWidth="4"
|
|
54
|
+
strokeLinecap="round"
|
|
55
|
+
>
|
|
56
|
+
<animateTransform
|
|
57
|
+
attributeName="transform"
|
|
58
|
+
type="rotate"
|
|
59
|
+
from="0 21 21"
|
|
60
|
+
to="360 21 21"
|
|
61
|
+
dur="0.9s"
|
|
62
|
+
repeatCount="indefinite"
|
|
63
|
+
/>
|
|
64
|
+
</path>
|
|
65
|
+
</svg>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
|
|
25
69
|
const ProtectedRoute = ({ user, loading, redirectPath = '/login' }) => {
|
|
26
70
|
const location = useLocation();
|
|
27
71
|
|
|
28
|
-
return loading ?
|
|
72
|
+
return loading ? <AuthLoading /> : !user || !user.id ? (
|
|
29
73
|
<Navigate
|
|
30
74
|
to={redirectPath}
|
|
31
75
|
replace
|
|
@@ -43,7 +87,7 @@ const ClientProtectedRoute = ({
|
|
|
43
87
|
}) => {
|
|
44
88
|
const location = useLocation();
|
|
45
89
|
|
|
46
|
-
return loading ?
|
|
90
|
+
return loading ? <AuthLoading /> : !clientUser || !clientUser.id ? (
|
|
47
91
|
<Navigate
|
|
48
92
|
to={redirectPath}
|
|
49
93
|
replace
|
|
@@ -971,10 +971,22 @@
|
|
|
971
971
|
|
|
972
972
|
.aside--cms {
|
|
973
973
|
width: 100%;
|
|
974
|
+
/* The content header is sticky at z-index 995, so the drawer sits
|
|
975
|
+
below it: start the drawer under the 50px header instead of
|
|
976
|
+
letting the header paint over the drawer's top row. This also
|
|
977
|
+
keeps the hamburger tappable while the drawer is open. */
|
|
978
|
+
top: 50px;
|
|
979
|
+
height: calc(100% - 50px);
|
|
974
980
|
transform: translateX(-2000px);
|
|
975
981
|
transition: all 0.55s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
976
982
|
will-change: transform;
|
|
977
983
|
z-index: 600;
|
|
984
|
+
|
|
985
|
+
/* The header already anchors the screen; the drawer's own logo
|
|
986
|
+
plate would render as a second 50px brand row. */
|
|
987
|
+
.logo {
|
|
988
|
+
display: none;
|
|
989
|
+
}
|
|
978
990
|
}
|
|
979
991
|
|
|
980
992
|
.aside--cms--open {
|