@visns-studio/visns-components 6.4.4 → 6.5.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/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.5.0",
|
|
95
95
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
96
96
|
"main": "src/index.js",
|
|
97
97
|
"files": [
|
|
@@ -50,6 +50,32 @@ const NAV_ICONS = {
|
|
|
50
50
|
users: Users,
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
// The icons an account action can wear, named by a settings entry's `icon`
|
|
54
|
+
// key. Module scope because two surfaces read it now: the header's action
|
|
55
|
+
// chips and — when a project opts in — the sidebar's pinned action rows.
|
|
56
|
+
const SETTING_ICONS = {
|
|
57
|
+
bug: Bug,
|
|
58
|
+
calendar: Calendar,
|
|
59
|
+
draft: FileText,
|
|
60
|
+
image: Image,
|
|
61
|
+
peopleGroup: Users,
|
|
62
|
+
person: User,
|
|
63
|
+
phone: Phone,
|
|
64
|
+
shield: Shield,
|
|
65
|
+
signOut: LogOut,
|
|
66
|
+
settings: Settings,
|
|
67
|
+
search: Search,
|
|
68
|
+
userSearch: UserSearch,
|
|
69
|
+
clipboardCheck: ClipboardCheck,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// A rail row carries a visible label. In the header these actions were icon
|
|
73
|
+
// chips with a tooltip, so a settings entry is allowed to have no label of
|
|
74
|
+
// its own — the bell in particular usually doesn't.
|
|
75
|
+
const RAIL_ACTION_LABELS = {
|
|
76
|
+
notifications: 'Notifications',
|
|
77
|
+
};
|
|
78
|
+
|
|
53
79
|
const SearchComponent = ({
|
|
54
80
|
navigate,
|
|
55
81
|
search,
|
|
@@ -270,21 +296,7 @@ function Navigation({
|
|
|
270
296
|
};
|
|
271
297
|
|
|
272
298
|
const renderSetting = (n) => {
|
|
273
|
-
const iconComponents =
|
|
274
|
-
bug: Bug,
|
|
275
|
-
calendar: Calendar,
|
|
276
|
-
draft: FileText,
|
|
277
|
-
image: Image,
|
|
278
|
-
peopleGroup: Users,
|
|
279
|
-
person: User,
|
|
280
|
-
phone: Phone,
|
|
281
|
-
shield: Shield,
|
|
282
|
-
signOut: LogOut,
|
|
283
|
-
settings: Settings,
|
|
284
|
-
search: Search,
|
|
285
|
-
userSearch: UserSearch,
|
|
286
|
-
clipboardCheck: ClipboardCheck,
|
|
287
|
-
};
|
|
299
|
+
const iconComponents = SETTING_ICONS;
|
|
288
300
|
|
|
289
301
|
// Check if the setting's URL matches the current page
|
|
290
302
|
const isActive = n.url && n.url === `/${currentPage}`;
|
|
@@ -356,6 +368,93 @@ function Navigation({
|
|
|
356
368
|
}
|
|
357
369
|
};
|
|
358
370
|
|
|
371
|
+
/**
|
|
372
|
+
* The same account actions, drawn as rows for the sidebar rail.
|
|
373
|
+
*
|
|
374
|
+
* Opt-in only — see `actionsInSidebar` below. Every action keeps the
|
|
375
|
+
* behaviour it had in the header: the same logout handler, the same
|
|
376
|
+
* links, the same mounted components (notifications, account switcher),
|
|
377
|
+
* so nothing here re-implements a chip. What changes is the shape: a
|
|
378
|
+
* full-width row wearing the rail's own language, icon then label, since
|
|
379
|
+
* a 36px chip in a 216px column reads as a stray button.
|
|
380
|
+
*
|
|
381
|
+
* Components mounted here draw their own trigger and can't be handed a
|
|
382
|
+
* label, so the row publishes one as `--nav-action-label` for their
|
|
383
|
+
* stylesheet to place. See Notification.module.scss / SwitchAccount.
|
|
384
|
+
*/
|
|
385
|
+
const renderRailSetting = (n) => {
|
|
386
|
+
const isActive = n.url && n.url === `/${currentPage}`;
|
|
387
|
+
const activeClass = isActive ? styles.active : '';
|
|
388
|
+
const label = n.label || RAIL_ACTION_LABELS[n.id] || '';
|
|
389
|
+
// A CSS string, quoted and escaped, for `content`.
|
|
390
|
+
const labelVar = { '--nav-action-label': JSON.stringify(label) };
|
|
391
|
+
const IconComponent = SETTING_ICONS[n.icon];
|
|
392
|
+
|
|
393
|
+
if (n.id === 'notifications') {
|
|
394
|
+
return (
|
|
395
|
+
<li style={labelVar}>
|
|
396
|
+
<Notification setSystemAuth={setSystemAuth} />
|
|
397
|
+
</li>
|
|
398
|
+
);
|
|
399
|
+
} else if (n.id === 'businessCardOcr') {
|
|
400
|
+
return (
|
|
401
|
+
<li>
|
|
402
|
+
<button
|
|
403
|
+
onClick={() => setShowBusinessCardOcr(true)}
|
|
404
|
+
className={activeClass}
|
|
405
|
+
type="button"
|
|
406
|
+
>
|
|
407
|
+
<CreditCard size={19} strokeWidth={2} />
|
|
408
|
+
<span>{label}</span>
|
|
409
|
+
</button>
|
|
410
|
+
</li>
|
|
411
|
+
);
|
|
412
|
+
} else if (n.id === 'switchAccount') {
|
|
413
|
+
return (
|
|
414
|
+
<li style={labelVar}>
|
|
415
|
+
<SwitchAccount
|
|
416
|
+
setSystemAuth={setSystemAuth}
|
|
417
|
+
setting={n}
|
|
418
|
+
userProfile={userProfile}
|
|
419
|
+
/>
|
|
420
|
+
</li>
|
|
421
|
+
);
|
|
422
|
+
} else if (n.id === 'logout') {
|
|
423
|
+
return (
|
|
424
|
+
<li>
|
|
425
|
+
<button
|
|
426
|
+
onClick={handleLogout}
|
|
427
|
+
className={activeClass}
|
|
428
|
+
type="button"
|
|
429
|
+
>
|
|
430
|
+
{IconComponent && (
|
|
431
|
+
<IconComponent size={19} strokeWidth={2} />
|
|
432
|
+
)}
|
|
433
|
+
<span>{label}</span>
|
|
434
|
+
</button>
|
|
435
|
+
</li>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return (
|
|
440
|
+
<li>
|
|
441
|
+
<Link
|
|
442
|
+
to={n.url}
|
|
443
|
+
className={`${activeClass}`}
|
|
444
|
+
target={n.target ? n.target : '_self'}
|
|
445
|
+
// The rail is the mobile drawer below 1024px; navigating
|
|
446
|
+
// from it has to close the drawer, as the nav list does.
|
|
447
|
+
onClick={() => setOpen(false)}
|
|
448
|
+
>
|
|
449
|
+
{IconComponent && (
|
|
450
|
+
<IconComponent size={19} strokeWidth={2} />
|
|
451
|
+
)}
|
|
452
|
+
<span>{label}</span>
|
|
453
|
+
</Link>
|
|
454
|
+
</li>
|
|
455
|
+
);
|
|
456
|
+
};
|
|
457
|
+
|
|
359
458
|
const [, setActiveDropdown] = useState(null); // We only need the setter
|
|
360
459
|
const navWrapRef = useRef(null);
|
|
361
460
|
|
|
@@ -704,14 +803,60 @@ function Navigation({
|
|
|
704
803
|
};
|
|
705
804
|
}, [debugIcons]);
|
|
706
805
|
|
|
806
|
+
/**
|
|
807
|
+
* `navConfig.actionsPlacement === 'sidebar'` moves the account actions
|
|
808
|
+
* out of the 50px content header and pins them to the bottom of the
|
|
809
|
+
* rail, which is otherwise empty space, so the page gets the whole
|
|
810
|
+
* height of the glass. Every project that publishes nothing — or
|
|
811
|
+
* 'header' — renders exactly what it rendered before.
|
|
812
|
+
*/
|
|
813
|
+
const actionsInSidebar =
|
|
814
|
+
(layout === 'simple' || layout === 'cms') &&
|
|
815
|
+
navConfig.actionsPlacement === 'sidebar';
|
|
816
|
+
const searchEnabled = navConfig.search?.enable === true;
|
|
817
|
+
/**
|
|
818
|
+
* With the actions gone the header holds a search field the project may
|
|
819
|
+
* not have enabled and a hamburger that only exists below 1024px. In
|
|
820
|
+
* that case it stays in the markup — the drawer is anchored under it and
|
|
821
|
+
* offsets itself by its 50px — but desktop drops it, and the main
|
|
822
|
+
* container starts at the top of the viewport.
|
|
823
|
+
*/
|
|
824
|
+
const headerIsMobileOnly = actionsInSidebar && !searchEnabled;
|
|
825
|
+
|
|
826
|
+
// Built only when a project asks for it, so the default path does no
|
|
827
|
+
// work it then throws away.
|
|
828
|
+
const railActions = !actionsInSidebar ? null : (
|
|
829
|
+
<div
|
|
830
|
+
className={styles['rail-actions']}
|
|
831
|
+
/* Read by the stylesheets of the components mounted in here —
|
|
832
|
+
the notification panel and the account switcher list both
|
|
833
|
+
anchor to a header chip by default, which from a rail at the
|
|
834
|
+
left edge would open off-screen. */
|
|
835
|
+
data-nav-actions="sidebar"
|
|
836
|
+
ref={actionItemsRef}
|
|
837
|
+
>
|
|
838
|
+
<ul>
|
|
839
|
+
{navData.settings.map((setting, settingKey) =>
|
|
840
|
+
setting.hidden !== true && setting.permission === true ? (
|
|
841
|
+
<React.Fragment key={`rail-setting-${settingKey}`}>
|
|
842
|
+
{renderRailSetting(setting)}
|
|
843
|
+
</React.Fragment>
|
|
844
|
+
) : null
|
|
845
|
+
)}
|
|
846
|
+
</ul>
|
|
847
|
+
</div>
|
|
848
|
+
);
|
|
849
|
+
|
|
707
850
|
return layout === 'simple' || layout === 'cms' ? (
|
|
708
851
|
<div className={styles.cwrap}>
|
|
709
852
|
<aside
|
|
710
|
-
className={
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
853
|
+
className={[
|
|
854
|
+
styles['aside--cms'],
|
|
855
|
+
isOpen ? styles['aside--cms--open'] : '',
|
|
856
|
+
actionsInSidebar ? styles['aside--cms--rail'] : '',
|
|
857
|
+
]
|
|
858
|
+
.filter(Boolean)
|
|
859
|
+
.join(' ')}
|
|
715
860
|
>
|
|
716
861
|
<div className={styles.logo}>
|
|
717
862
|
<Link to="/">
|
|
@@ -733,6 +878,7 @@ function Navigation({
|
|
|
733
878
|
</ul>
|
|
734
879
|
</nav>
|
|
735
880
|
</div>
|
|
881
|
+
{railActions}
|
|
736
882
|
</aside>
|
|
737
883
|
{isOpen ? (
|
|
738
884
|
<button
|
|
@@ -742,8 +888,20 @@ function Navigation({
|
|
|
742
888
|
onClick={() => setOpen(false)}
|
|
743
889
|
/>
|
|
744
890
|
) : null}
|
|
745
|
-
<main
|
|
746
|
-
|
|
891
|
+
<main
|
|
892
|
+
className={
|
|
893
|
+
headerIsMobileOnly
|
|
894
|
+
? `${styles.content} ${styles['content--full']}`
|
|
895
|
+
: styles.content
|
|
896
|
+
}
|
|
897
|
+
>
|
|
898
|
+
<div
|
|
899
|
+
className={
|
|
900
|
+
headerIsMobileOnly
|
|
901
|
+
? `${styles['content-header']} ${styles['content-header--mobile']}`
|
|
902
|
+
: styles['content-header']
|
|
903
|
+
}
|
|
904
|
+
>
|
|
747
905
|
{navConfig.search?.enable === true && (
|
|
748
906
|
<SearchComponent
|
|
749
907
|
navigate={navigate}
|
|
@@ -774,25 +932,27 @@ function Navigation({
|
|
|
774
932
|
<span></span>
|
|
775
933
|
</button>
|
|
776
934
|
</div>
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
setting
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
935
|
+
{actionsInSidebar ? null : (
|
|
936
|
+
<div
|
|
937
|
+
className={`${styles['hactions-alternate']} ${
|
|
938
|
+
debugIcons ? styles['debug-icons'] : ''
|
|
939
|
+
}`}
|
|
940
|
+
ref={actionItemsRef}
|
|
941
|
+
>
|
|
942
|
+
<ul>
|
|
943
|
+
{navData.settings.map((setting, settingKey) =>
|
|
944
|
+
setting.hidden !== true &&
|
|
945
|
+
setting.permission === true ? (
|
|
946
|
+
<React.Fragment
|
|
947
|
+
key={`setting-${settingKey}`}
|
|
948
|
+
>
|
|
949
|
+
{renderSetting(setting)}
|
|
950
|
+
</React.Fragment>
|
|
951
|
+
) : null
|
|
952
|
+
)}
|
|
953
|
+
</ul>
|
|
954
|
+
</div>
|
|
955
|
+
)}
|
|
796
956
|
</div>
|
|
797
957
|
{children}
|
|
798
958
|
</main>
|
|
@@ -870,6 +870,140 @@
|
|
|
870
870
|
}
|
|
871
871
|
}
|
|
872
872
|
|
|
873
|
+
/* ----------------------------------------------------------------------------
|
|
874
|
+
Sidebar-owned account actions — opt-in
|
|
875
|
+
----------------------------------------------------------------------------
|
|
876
|
+
`navConfig.actionsPlacement: 'sidebar'` moves the notification bell, the
|
|
877
|
+
profile link, the logout button and anything else a project mounts as a
|
|
878
|
+
"setting" out of the 50px content header and pins them to the bottom of the
|
|
879
|
+
rail, which was empty space. The page then gets the full height of the
|
|
880
|
+
glass. Nothing below applies unless the consumer publishes that key.
|
|
881
|
+
------------------------------------------------------------------------- */
|
|
882
|
+
.aside--cms--rail {
|
|
883
|
+
/* Three bands: the logo plate, the nav list that takes what's left and
|
|
884
|
+
scrolls, and the actions pinned under it. */
|
|
885
|
+
display: flex;
|
|
886
|
+
flex-direction: column;
|
|
887
|
+
|
|
888
|
+
/* The rail clips itself so a long nav list can't spill; the panels that
|
|
889
|
+
open from the action rows — the notification list, the account
|
|
890
|
+
switcher — would be cut off by that. The nav list keeps its own
|
|
891
|
+
scroll container, so nothing else depended on this. */
|
|
892
|
+
overflow: visible;
|
|
893
|
+
|
|
894
|
+
.awrap {
|
|
895
|
+
/* `min-height: 0` or the nav list refuses to shrink below its
|
|
896
|
+
content and pushes the actions off the bottom of the rail. */
|
|
897
|
+
flex: 1 1 auto;
|
|
898
|
+
min-height: 0;
|
|
899
|
+
display: flex;
|
|
900
|
+
flex-direction: column;
|
|
901
|
+
|
|
902
|
+
.navwrap {
|
|
903
|
+
flex: 1 1 auto;
|
|
904
|
+
min-height: 0;
|
|
905
|
+
/* The fixed `calc(100vh - 50px)` assumed the rail's only other
|
|
906
|
+
band was the logo plate. The column measures it now. */
|
|
907
|
+
max-height: none;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.rail-actions {
|
|
913
|
+
flex: 0 0 auto;
|
|
914
|
+
position: relative;
|
|
915
|
+
/* Above the content header's sticky 995 so a panel opening from a row
|
|
916
|
+
is never painted underneath it. */
|
|
917
|
+
z-index: 996;
|
|
918
|
+
/* Reads as a footer to the list rather than one more entry in it. */
|
|
919
|
+
border-top: 1px solid rgba(var(--tertiary-color-rgb), 0.14);
|
|
920
|
+
padding: var(--padding) 0;
|
|
921
|
+
|
|
922
|
+
> ul {
|
|
923
|
+
list-style: none;
|
|
924
|
+
margin: 0;
|
|
925
|
+
padding: 0;
|
|
926
|
+
display: flex;
|
|
927
|
+
flex-direction: column;
|
|
928
|
+
width: 100%;
|
|
929
|
+
|
|
930
|
+
> li {
|
|
931
|
+
/* Same gutter as `.app-nav .nav-item`, so the rows line up with
|
|
932
|
+
the navigation above them to the pixel. */
|
|
933
|
+
width: 100%;
|
|
934
|
+
padding: 0.15rem 0.45rem;
|
|
935
|
+
box-sizing: border-box;
|
|
936
|
+
/* The positioning context for the panels a mounted component
|
|
937
|
+
opens — they anchor to the nearest positioned ancestor. */
|
|
938
|
+
position: relative;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// A row wears the rail's own language: the nav item's padding,
|
|
942
|
+
// radius and hover tint. Depth-limited on purpose — the panels these
|
|
943
|
+
// components open live deeper inside the same `li` and must keep
|
|
944
|
+
// their own styling.
|
|
945
|
+
> li > a,
|
|
946
|
+
> li > button,
|
|
947
|
+
> li > div > a,
|
|
948
|
+
> li > div > button {
|
|
949
|
+
width: 100%;
|
|
950
|
+
display: flex;
|
|
951
|
+
flex-wrap: nowrap;
|
|
952
|
+
align-items: center;
|
|
953
|
+
gap: 0.55rem;
|
|
954
|
+
box-sizing: border-box;
|
|
955
|
+
padding: 0.5rem 1rem;
|
|
956
|
+
margin: 0;
|
|
957
|
+
line-height: 1.25;
|
|
958
|
+
text-align: left;
|
|
959
|
+
text-decoration: none;
|
|
960
|
+
color: var(--tertiary-color);
|
|
961
|
+
background: var(--nav-bg);
|
|
962
|
+
border: none;
|
|
963
|
+
outline: none;
|
|
964
|
+
border-radius: var(--br);
|
|
965
|
+
font: inherit;
|
|
966
|
+
cursor: pointer;
|
|
967
|
+
appearance: none;
|
|
968
|
+
transition: all 0.65s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
969
|
+
|
|
970
|
+
svg {
|
|
971
|
+
flex: 0 0 auto;
|
|
972
|
+
color: var(--secondary-color);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
> li > a:hover,
|
|
977
|
+
> li > button:hover,
|
|
978
|
+
> li > div > a:hover,
|
|
979
|
+
> li > div > button:hover,
|
|
980
|
+
> li > a.active {
|
|
981
|
+
background: rgba(var(--tertiary-color-rgb), 0.05);
|
|
982
|
+
color: var(--secondary-color);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Components mounted here wrap their trigger in a div of their own
|
|
986
|
+
// (`.notificationContainer`, the switcher's wrapper). Left as an
|
|
987
|
+
// inline-ish block it would collapse the row.
|
|
988
|
+
> li > div {
|
|
989
|
+
width: 100%;
|
|
990
|
+
position: relative;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// A mounted component draws its own trigger and takes no label from
|
|
994
|
+
// us, so the row publishes one as `--nav-action-label` and the
|
|
995
|
+
// trigger wears it. Empty by default, so a component that already
|
|
996
|
+
// labels itself is untouched. (The notification bell sets its own
|
|
997
|
+
// fallback text — see Notification.module.scss.)
|
|
998
|
+
> li > div > a::after {
|
|
999
|
+
content: var(--nav-action-label, '');
|
|
1000
|
+
flex: 1 1 auto;
|
|
1001
|
+
min-width: 0;
|
|
1002
|
+
text-align: left;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
873
1007
|
.content {
|
|
874
1008
|
margin-left: var(--sidebar-width);
|
|
875
1009
|
flex-grow: 1;
|
|
@@ -952,6 +1086,21 @@
|
|
|
952
1086
|
}
|
|
953
1087
|
}
|
|
954
1088
|
|
|
1089
|
+
/* With the actions in the rail and no search field configured, the only
|
|
1090
|
+
thing left in the header is the drawer's hamburger — which exists below
|
|
1091
|
+
1024px and nowhere else. The bar stays in the markup so the drawer keeps
|
|
1092
|
+
the 50px anchor it offsets itself by, and desktop simply drops it. */
|
|
1093
|
+
.content .content-header--mobile {
|
|
1094
|
+
display: none;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/* Nothing sits above the page any more, so the main container is the
|
|
1098
|
+
viewport. Without this it is only as tall as its content and the page
|
|
1099
|
+
background stops short on a short screen. */
|
|
1100
|
+
.content--full {
|
|
1101
|
+
min-height: 100vh;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
955
1104
|
/* Rendered only while the mobile drawer is open; the phone block below
|
|
956
1105
|
displays it. Desktop never opens the drawer, but the guard costs nothing. */
|
|
957
1106
|
.drawer-backdrop {
|
|
@@ -1016,6 +1165,41 @@
|
|
|
1016
1165
|
}
|
|
1017
1166
|
}
|
|
1018
1167
|
|
|
1168
|
+
/* The account actions sit at the foot of the drawer, above the home
|
|
1169
|
+
indicator, at the same touch size as the nav rows above them. */
|
|
1170
|
+
.rail-actions {
|
|
1171
|
+
padding: 0.35rem 0 calc(0.35rem + env(safe-area-inset-bottom, 0px));
|
|
1172
|
+
|
|
1173
|
+
> ul > li {
|
|
1174
|
+
padding: 0.2rem 0.65rem;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// Depth-limited: the panels these rows open live deeper in the same
|
|
1178
|
+
// `li` and size their own icons.
|
|
1179
|
+
> ul > li > a,
|
|
1180
|
+
> ul > li > button,
|
|
1181
|
+
> ul > li > div > a,
|
|
1182
|
+
> ul > li > div > button {
|
|
1183
|
+
padding: 0.85rem 1rem;
|
|
1184
|
+
font-size: 1rem;
|
|
1185
|
+
line-height: 1.3;
|
|
1186
|
+
|
|
1187
|
+
> svg {
|
|
1188
|
+
width: 20px;
|
|
1189
|
+
height: 20px;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
.content .content-header--mobile {
|
|
1195
|
+
display: flex;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
.content--full {
|
|
1199
|
+
/* The 50px bar is still there below this width. */
|
|
1200
|
+
min-height: calc(100vh - 50px);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1019
1203
|
.drawer-backdrop {
|
|
1020
1204
|
display: block;
|
|
1021
1205
|
position: fixed;
|
|
@@ -448,3 +448,86 @@
|
|
|
448
448
|
width: 300px;
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
|
+
|
|
452
|
+
/* ----------------------------------------------------------------------------
|
|
453
|
+
Mounted in the sidebar rail
|
|
454
|
+
----------------------------------------------------------------------------
|
|
455
|
+
A project can opt its navigation into `actionsPlacement: 'sidebar'`, which
|
|
456
|
+
pins the account actions to the bottom of the rail instead of the content
|
|
457
|
+
header. The rail's block advertises itself with `data-nav-actions="sidebar"`
|
|
458
|
+
(see Navigation.jsx / Navigation.module.scss `.rail-actions`), which is all
|
|
459
|
+
this file needs to know.
|
|
460
|
+
|
|
461
|
+
Two things change. The trigger is a full-width row rather than a 36px chip,
|
|
462
|
+
so it carries a visible label — published by the row as `--nav-action-label`
|
|
463
|
+
because the component draws its own trigger and takes no label of its own.
|
|
464
|
+
And the panel anchors to the row's right edge: `top: 100%; right: 0` would
|
|
465
|
+
drop it below the foot of the viewport and off the left of the screen.
|
|
466
|
+
|
|
467
|
+
The row's own padding, colour and radius come from `.rail-actions`; only
|
|
468
|
+
what this file would otherwise force back is restated here.
|
|
469
|
+
------------------------------------------------------------------------- */
|
|
470
|
+
[data-nav-actions='sidebar'] {
|
|
471
|
+
.notificationTrigger {
|
|
472
|
+
justify-content: flex-start;
|
|
473
|
+
/* The chip was a fixed 40px square; a row is as tall as the padding
|
|
474
|
+
the rail gives it, which is what the rows beside it measure. */
|
|
475
|
+
height: auto;
|
|
476
|
+
|
|
477
|
+
// The label. `content` reads the row's custom property; the fallback
|
|
478
|
+
// covers a settings entry that never carried a label — in the header
|
|
479
|
+
// the bell was an icon with a tooltip, so most don't.
|
|
480
|
+
&::after {
|
|
481
|
+
content: var(--nav-action-label, 'Notifications');
|
|
482
|
+
flex: 1 1 auto;
|
|
483
|
+
min-width: 0;
|
|
484
|
+
text-align: left;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// The chip's lift read as a stray button in a list of rows. The
|
|
488
|
+
// focus ring is left alone — it is the only keyboard affordance
|
|
489
|
+
// these rows have.
|
|
490
|
+
&:hover,
|
|
491
|
+
&:focus,
|
|
492
|
+
&:active {
|
|
493
|
+
transform: none;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
&:hover {
|
|
497
|
+
background-color: rgba(var(--tertiary-color-rgb), 0.05);
|
|
498
|
+
color: var(--secondary-color);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Was pinned to the corner of a round chip. On a wide row it belongs at
|
|
503
|
+
// the end of the line, where the eye lands after the label.
|
|
504
|
+
.badge {
|
|
505
|
+
top: 50%;
|
|
506
|
+
right: 14px;
|
|
507
|
+
transform: translateY(-50%);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Beside the rail, bottom-aligned with the row that opened it.
|
|
511
|
+
.dropdownWrapper {
|
|
512
|
+
top: auto;
|
|
513
|
+
right: auto;
|
|
514
|
+
bottom: 0;
|
|
515
|
+
left: calc(100% + 10px);
|
|
516
|
+
margin-top: 0;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
@media (max-width: 1024px) {
|
|
520
|
+
// Inside the drawer there is nothing to the right, so the panel
|
|
521
|
+
// opens upward from the row.
|
|
522
|
+
.dropdownWrapper {
|
|
523
|
+
left: 0;
|
|
524
|
+
bottom: calc(100% + 8px);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Keeps the panel inside a phone's viewport when the list is long —
|
|
528
|
+
// it grows from the foot of the drawer towards the header.
|
|
529
|
+
.notiscrollwrap {
|
|
530
|
+
max-height: 40vh;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
@@ -24,6 +24,28 @@
|
|
|
24
24
|
z-index: 999;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/* Mounted in the sidebar rail — `navConfig.actionsPlacement: 'sidebar'`.
|
|
28
|
+
The list is sized and shunted 470px left to hang off a chip at the top
|
|
29
|
+
right of the header; from a rail at the left edge that puts it off-screen.
|
|
30
|
+
The rail's action block advertises itself with `data-nav-actions`, which is
|
|
31
|
+
all this file needs to know. See Navigation.module.scss `.rail-actions`. */
|
|
32
|
+
[data-nav-actions='sidebar'] .notibox {
|
|
33
|
+
width: min(550px, calc(100vw - var(--sidebar-width) - 24px));
|
|
34
|
+
max-height: 60vh;
|
|
35
|
+
transform: none;
|
|
36
|
+
left: calc(100% + 10px);
|
|
37
|
+
bottom: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@media (max-width: 1024px) {
|
|
41
|
+
/* Inside the drawer there is nothing to the right of the row. */
|
|
42
|
+
[data-nav-actions='sidebar'] .notibox {
|
|
43
|
+
width: min(550px, calc(100vw - 24px));
|
|
44
|
+
left: 0;
|
|
45
|
+
bottom: calc(100% + 8px);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
.notibox-item {
|
|
28
50
|
width: 100%;
|
|
29
51
|
display: block;
|