@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.3.18",
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
- <Link to={n.url}>{n.label}</Link>
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 ? null : !user || !user.id ? (
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 ? null : !clientUser || !clientUser.id ? (
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 {