@visns-studio/visns-components 6.24.4 → 6.26.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 +4 -2
- package/src/components/Autocomplete.jsx +189 -119
- package/src/components/DataGrid.jsx +472 -31
- package/src/components/Navigation.jsx +475 -51
- package/src/components/auth/ClientAuthFrame.jsx +5 -0
- package/src/components/auth/ClientAuthScreen.jsx +29 -0
- package/src/components/callQueue/CallQueueDiagnostics.jsx +1043 -0
- package/src/components/callQueue/CallQueuePop.jsx +713 -90
- package/src/components/callQueue/CallQueueSettings.jsx +308 -146
- package/src/components/callQueue/callPopStatus.js +236 -0
- package/src/components/callQueue/callQueueHelpers.js +284 -2
- package/src/components/columns/ColumnRenderers.jsx +3 -46
- package/src/components/columns/StackedRow.jsx +186 -0
- package/src/components/controls/DataGridSearch.jsx +110 -2
- package/src/components/controls/DataGridSortSheet.jsx +155 -0
- package/src/components/generic/GenericAuth.jsx +50 -18
- package/src/components/generic/GenericDashboard.jsx +20 -1
- package/src/components/generic/GenericDetail.jsx +446 -259
- package/src/components/mapboxSearchBox.js +640 -0
- package/src/components/navBadges.js +63 -1
- package/src/components/navDrawer.js +147 -0
- package/src/components/sms/SmsThreadPanel.jsx +34 -6
- package/src/components/sms/smsHelpers.js +15 -0
- package/src/components/styles/CallQueueDiagnostics.module.scss +398 -0
- package/src/components/styles/CallQueuePop.module.scss +29 -0
- package/src/components/styles/CallQueueSettings.module.scss +93 -0
- package/src/components/styles/ClientAuth.module.scss +39 -0
- package/src/components/styles/DataGrid.module.scss +158 -5
- package/src/components/styles/Field.module.scss +52 -1
- package/src/components/styles/Form.module.scss +82 -0
- package/src/components/styles/GenericClientPortal.module.scss +72 -20
- package/src/components/styles/GenericDashboard.module.scss +50 -0
- package/src/components/styles/GenericDetail.module.scss +63 -1
- package/src/components/styles/GenericDynamic.module.scss +23 -0
- package/src/components/styles/GenericFormBuilder.module.scss +11 -0
- package/src/components/styles/GenericIndex.module.scss +6 -1
- package/src/components/styles/Navigation.module.scss +460 -7
- package/src/components/styles/Sms.module.scss +92 -0
- package/src/components/styles/StackedRow.module.scss +182 -0
- package/src/components/styles/TicketConversation.module.scss +76 -0
- package/src/components/styles/Vault.module.scss +192 -0
- package/src/components/styles/density.css +10 -0
- package/src/components/styles/global-datagrid.css +163 -0
- package/src/components/styles/global.css +20 -0
- package/src/components/tickets/TicketConversation.jsx +13 -8
- package/src/components/utils/ConfirmDialog.js +22 -3
- package/src/components/utils/cardLayout.js +666 -0
- package/src/components/utils/contactChannels.js +130 -0
- package/src/components/utils/editPlacement.js +95 -0
- package/src/components/utils/useDensity.js +303 -7
- package/src/index.js +42 -0
|
@@ -2929,6 +2929,17 @@ select:not(:placeholder-shown) + .fi__span {
|
|
|
2929
2929
|
opacity: 1;
|
|
2930
2930
|
}
|
|
2931
2931
|
|
|
2932
|
+
/* No pointer means no hover, and a hidden control that is only revealed by an
|
|
2933
|
+
event the device cannot generate is not hidden, it is GONE — on a tablet
|
|
2934
|
+
these rows had no edit and no delete at all. `focus-within` above already
|
|
2935
|
+
covers the keyboard; this covers the finger. The same pattern, and the same
|
|
2936
|
+
sentence, as `_associationManager.scss`'s `.actions`. */
|
|
2937
|
+
@media (hover: none), (pointer: coarse) {
|
|
2938
|
+
.cActions {
|
|
2939
|
+
opacity: 1;
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2932
2943
|
.cDelete:hover {
|
|
2933
2944
|
color: var(--secondary-color) !important;
|
|
2934
2945
|
background: rgba(var(--secondary-rgb, 60, 191, 125), 0.1) !important;
|
|
@@ -1208,10 +1208,15 @@
|
|
|
1208
1208
|
z-index: 1000;
|
|
1209
1209
|
}
|
|
1210
1210
|
|
|
1211
|
+
/* See DataGrid.module.scss's `.modalWide` for the whole note. Short version:
|
|
1212
|
+
`min-width` outranks both `width` and `max-width`, so a flat 600px floor put
|
|
1213
|
+
210px of the dialog — its right edge and its close button — off the side of
|
|
1214
|
+
a 390px phone. 90vw is the width this rule already asks for, so the floor
|
|
1215
|
+
can never outrank the line above it. */
|
|
1211
1216
|
.modalWide {
|
|
1212
1217
|
width: 90vw;
|
|
1213
1218
|
max-width: var(--detail-max-width, 1400px);
|
|
1214
|
-
min-width: 600px;
|
|
1219
|
+
min-width: min(600px, 90vw);
|
|
1215
1220
|
}
|
|
1216
1221
|
|
|
1217
1222
|
.modal {
|
|
@@ -54,6 +54,32 @@
|
|
|
54
54
|
tallest thing in it happens to measure. Total
|
|
55
55
|
bar height is this plus --nav-bar-padding: 52px
|
|
56
56
|
and 4px gives the 60px bar. default: 50px
|
|
57
|
+
--nav-header-height the FULL height of the header bar, border
|
|
58
|
+
included — what the ≤1024px drawer and its
|
|
59
|
+
backdrop hang off, so that they start exactly
|
|
60
|
+
where the bar stops.
|
|
61
|
+
The default composes the bar's own box:
|
|
62
|
+
--nav-logo-height + twice --spacing-sm + the
|
|
63
|
+
1px bottom border, which is exact for every
|
|
64
|
+
project that leaves --nav-bar-padding alone.
|
|
65
|
+
A PROJECT THAT OVERRIDES --nav-bar-padding
|
|
66
|
+
MUST SET THIS TOO — nothing can read a
|
|
67
|
+
shorthand's vertical half back out of it, and
|
|
68
|
+
the failure is a drawer overlapping the bar
|
|
69
|
+
or floating below it.
|
|
70
|
+
default: calc(var(--nav-logo-height, 50px) +
|
|
71
|
+
2 * var(--spacing-sm, 0.5rem) + 1px)
|
|
72
|
+
--nav-logo-width-sm the cap on the logo's width below 640px. The
|
|
73
|
+
logo keeps --logo-width wherever that is
|
|
74
|
+
already narrower — this only ever shrinks.
|
|
75
|
+
default: 92px
|
|
76
|
+
--nav-badge-warn the ground of a `tone: 'warn'` count pill. The
|
|
77
|
+
only two hooks here that are colour rather than
|
|
78
|
+
--nav-badge-danger density, and they are hooks at all because the
|
|
79
|
+
severity grounds are stated as literals — see
|
|
80
|
+
the contrast note at .nav-badge-warn for why
|
|
81
|
+
they cannot simply read the app's tokens.
|
|
82
|
+
defaults: #d9902a (dark ink) and #c1272d (white ink)
|
|
57
83
|
|
|
58
84
|
Two deliberate omissions: the icon-to-label gap inside a nav item and the
|
|
59
85
|
padding on the chips themselves are internal alignment, not density — the
|
|
@@ -269,6 +295,15 @@
|
|
|
269
295
|
transform: rotate(180deg);
|
|
270
296
|
}
|
|
271
297
|
|
|
298
|
+
/* The same caret, turned by the state a TOUCH reader's tap put on the
|
|
299
|
+
button rather than by a hover the device cannot produce. The
|
|
300
|
+
attribute is only ever rendered on a coarse pointer — see the group
|
|
301
|
+
button in Navigation.jsx — so this cannot fire on a desktop. */
|
|
302
|
+
&:has(> button[aria-expanded='true']) > button::after {
|
|
303
|
+
opacity: 1;
|
|
304
|
+
transform: rotate(180deg);
|
|
305
|
+
}
|
|
306
|
+
|
|
272
307
|
&.nav-item {
|
|
273
308
|
a,
|
|
274
309
|
span,
|
|
@@ -434,9 +469,30 @@
|
|
|
434
469
|
the group heading, the rows appear, tab again and focus moves
|
|
435
470
|
into them — and the menu stays open because focus is still
|
|
436
471
|
inside the item. Without this the dropdown was hover-only and
|
|
437
|
-
every page inside a group was unreachable without a mouse.
|
|
438
|
-
|
|
439
|
-
|
|
472
|
+
every page inside a group was unreachable without a mouse.
|
|
473
|
+
|
|
474
|
+
FENCED BEHIND `hover: hover`, which is every mouse and every
|
|
475
|
+
trackpad and therefore leaves the desktop bar byte-identical.
|
|
476
|
+
What it excludes is the touch screen, where a "hover" is a
|
|
477
|
+
sticky artefact of the last tap that then refuses to end, and
|
|
478
|
+
`:focus-within` fights the button's own blur-on-click. Those
|
|
479
|
+
devices get the rule below instead — one mechanism at a time,
|
|
480
|
+
so the menu's open state is never two disagreeing answers. */
|
|
481
|
+
@media (hover: hover) {
|
|
482
|
+
&:hover > ul,
|
|
483
|
+
&:focus-within > ul {
|
|
484
|
+
display: block;
|
|
485
|
+
opacity: 1;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/* Touch: the open state is React's, published on the group button
|
|
490
|
+
as `aria-expanded` (Navigation.jsx sets the attribute ONLY on a
|
|
491
|
+
coarse pointer, which is why this needs no media query of its
|
|
492
|
+
own and cannot affect a mouse). A keyboard on a tablet lands
|
|
493
|
+
here too — Enter fires the same click handler — so nothing is
|
|
494
|
+
left unreachable by fencing the hover path above. */
|
|
495
|
+
> button[aria-expanded='true'] + ul {
|
|
440
496
|
display: block;
|
|
441
497
|
opacity: 1;
|
|
442
498
|
}
|
|
@@ -560,9 +616,17 @@
|
|
|
560
616
|
}
|
|
561
617
|
}
|
|
562
618
|
|
|
563
|
-
/* Same keyboard path
|
|
564
|
-
|
|
565
|
-
|
|
619
|
+
/* Same keyboard path, and the same touch/hover split, as
|
|
620
|
+
`.nav-item` above — see the long note there. */
|
|
621
|
+
@media (hover: hover) {
|
|
622
|
+
&:hover > ul,
|
|
623
|
+
&:focus-within > ul {
|
|
624
|
+
display: block;
|
|
625
|
+
opacity: 1;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
> button[aria-expanded='true'] + ul {
|
|
566
630
|
display: block;
|
|
567
631
|
opacity: 1;
|
|
568
632
|
}
|
|
@@ -616,6 +680,48 @@
|
|
|
616
680
|
font-variant-numeric: tabular-nums;
|
|
617
681
|
}
|
|
618
682
|
|
|
683
|
+
/* ---------------------------------------------------------------------------
|
|
684
|
+
Tones — how BAD the number is, not what it counts.
|
|
685
|
+
---------------------------------------------------------------------------
|
|
686
|
+
`ok` is the block above and has no class of its own; a consumer that
|
|
687
|
+
publishes a plain number keeps exactly the green pill it has always had.
|
|
688
|
+
These two are modifiers written after it, so they win on source order at the
|
|
689
|
+
same specificity — deliberately, because anything heavier here would have to
|
|
690
|
+
be restated in the `.hactions` block below, which already positions this
|
|
691
|
+
same element.
|
|
692
|
+
|
|
693
|
+
BOTH grounds are hard-coded rather than reaching for --secondary-color the
|
|
694
|
+
way the ok pill and the bell do. In this house that token IS the green: a
|
|
695
|
+
"danger" pill written as `var(--secondary-color, #c1272d)` would resolve to
|
|
696
|
+
the same green as the default one in every project that declares the token,
|
|
697
|
+
and the fallback crimson would only ever appear in projects that do not —
|
|
698
|
+
which is precisely backwards. The public `--vs-warn` / `--vs-danger` hooks
|
|
699
|
+
from _surface.scss are honoured first so a project that has already stated
|
|
700
|
+
its palette is not overridden here.
|
|
701
|
+
|
|
702
|
+
Contrast, because the green pill shipped illegible once and a 10px digit has
|
|
703
|
+
no margin for a guess (all figures against the house ink #1b3933 and #fff):
|
|
704
|
+
|
|
705
|
+
amber #c8811b vs ink 3.94:1 vs white 3.18:1 — neither reaches 4.5
|
|
706
|
+
amber #d9902a vs ink 4.75:1 vs white 2.63:1 — ink clears it
|
|
707
|
+
crimson #c1272d vs ink 2.14:1 vs white 5.84:1 — white clears it
|
|
708
|
+
|
|
709
|
+
So warn is a step lighter than the library's #c8811b — the same hue, lifted
|
|
710
|
+
until the ink the ok pill already uses is legible on it, which also keeps
|
|
711
|
+
one ink across ok and warn so only the GROUND moves between them. Danger is
|
|
712
|
+
dark enough that the same ink is hopeless and takes white, which is what
|
|
713
|
+
every other crimson badge in the house does anyway.
|
|
714
|
+
------------------------------------------------------------------------- */
|
|
715
|
+
.nav-badge-warn {
|
|
716
|
+
background-color: var(--nav-badge-warn, var(--vs-warn, #d9902a));
|
|
717
|
+
color: var(--primary-color, #1b3933);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.nav-badge-danger {
|
|
721
|
+
background-color: var(--nav-badge-danger, var(--vs-danger, #c1272d));
|
|
722
|
+
color: #fff;
|
|
723
|
+
}
|
|
724
|
+
|
|
619
725
|
/* In a dropdown row the label is a block-level link, so the pill is pushed to
|
|
620
726
|
the row's trailing edge rather than butted against the text. Both themed
|
|
621
727
|
selectors are spelled out to match the specificity of the blocks above —
|
|
@@ -631,6 +737,15 @@
|
|
|
631
737
|
|
|
632
738
|
.hactions {
|
|
633
739
|
width: max-content;
|
|
740
|
+
/* A grid item's automatic minimum size is its CONTENT's min-content width,
|
|
741
|
+
not zero — so a cluster of nine 34px chips at `width: max-content` set a
|
|
742
|
+
floor of roughly 360px on this column, the bar's min-content width came
|
|
743
|
+
out around 530px, and on a 390px phone the browser resolved that by
|
|
744
|
+
collapsing the navigation column to nothing and letting the whole page
|
|
745
|
+
pan sideways. `min-width: 0` lets the column give way instead; the
|
|
746
|
+
cluster's own `overflow-x` below is what it gives way INTO.
|
|
747
|
+
Costs nothing above the breakpoint, where the bar has room to spare. */
|
|
748
|
+
min-width: 0;
|
|
634
749
|
justify-self: end;
|
|
635
750
|
position: relative;
|
|
636
751
|
z-index: 1000; // Higher z-index to stay above sticky header
|
|
@@ -908,6 +1023,8 @@
|
|
|
908
1023
|
|
|
909
1024
|
.hactions-alternate {
|
|
910
1025
|
width: max-content;
|
|
1026
|
+
/* Same reason as `.hactions` — see the note there. */
|
|
1027
|
+
min-width: 0;
|
|
911
1028
|
justify-self: end;
|
|
912
1029
|
position: relative;
|
|
913
1030
|
z-index: 1000; // Higher z-index to stay above sticky header
|
|
@@ -1355,6 +1472,20 @@
|
|
|
1355
1472
|
margin: 0;
|
|
1356
1473
|
line-height: 1.25;
|
|
1357
1474
|
text-align: left;
|
|
1475
|
+
/* STATED, not left at `normal`. A mounted component's trigger was
|
|
1476
|
+
a round 36px chip before it was a row, and a chip centres its
|
|
1477
|
+
lone icon — `justify-content: center` is in Sms.module.scss,
|
|
1478
|
+
Vault.module.scss and Notification.module.scss alike. Left
|
|
1479
|
+
unset here, that centring survives the row shell and the
|
|
1480
|
+
action reads as an icon floating in the middle of a full-width
|
|
1481
|
+
row with the labelled rows above and below it left-aligned.
|
|
1482
|
+
The row grammar belongs to the row, so the row states it. */
|
|
1483
|
+
justify-content: flex-start;
|
|
1484
|
+
/* Same story, same chips: a 36/40px SQUARE is the other half of
|
|
1485
|
+
being a chip, and a fixed height in a list of rows sized by
|
|
1486
|
+
their own padding is a row that is visibly shorter than its
|
|
1487
|
+
neighbours. A row is as tall as the padding the rail gives it. */
|
|
1488
|
+
height: auto;
|
|
1358
1489
|
text-decoration: none;
|
|
1359
1490
|
color: var(--tertiary-color);
|
|
1360
1491
|
background: var(--nav-bg);
|
|
@@ -1399,7 +1530,15 @@
|
|
|
1399
1530
|
// trigger wears it. Empty by default, so a component that already
|
|
1400
1531
|
// labels itself is untouched. (The notification bell sets its own
|
|
1401
1532
|
// fallback text — see Notification.module.scss.)
|
|
1402
|
-
|
|
1533
|
+
//
|
|
1534
|
+
// BOTH TRIGGER ELEMENTS, and that is the fix rather than the tidy-up:
|
|
1535
|
+
// this read `> li > div > a` alone, so the account switcher — a link —
|
|
1536
|
+
// wore its label and every component whose trigger is a <button>
|
|
1537
|
+
// (SmsInboxBadge, VaultQuickSearch) rendered in the drawer as a bare
|
|
1538
|
+
// icon with no text beside it at all. The row shell above has always
|
|
1539
|
+
// matched buttons; only the label did not.
|
|
1540
|
+
> li > div > a::after,
|
|
1541
|
+
> li > div > button::after {
|
|
1403
1542
|
content: var(--nav-action-label, '');
|
|
1404
1543
|
flex: 1 1 auto;
|
|
1405
1544
|
min-width: 0;
|
|
@@ -1574,6 +1713,14 @@
|
|
|
1574
1713
|
.rail-actions {
|
|
1575
1714
|
padding: 0.35rem 0 calc(0.35rem + env(safe-area-inset-bottom, 0px));
|
|
1576
1715
|
|
|
1716
|
+
/* The footer casts a shadow UP over the list it footers, so the band
|
|
1717
|
+
above it reads as passing underneath rather than stopping at it.
|
|
1718
|
+
The hairline border alone drew a floor: a list clipped at a row
|
|
1719
|
+
boundary and a list that genuinely ends look identical against it,
|
|
1720
|
+
which is most of why a menu with six more entries below the fold
|
|
1721
|
+
read as a menu with six entries. */
|
|
1722
|
+
box-shadow: 0 -10px 12px -10px rgba(0, 0, 0, 0.55);
|
|
1723
|
+
|
|
1577
1724
|
> ul > li {
|
|
1578
1725
|
padding: 0.2rem 0.65rem;
|
|
1579
1726
|
}
|
|
@@ -1994,3 +2141,309 @@
|
|
|
1994
2141
|
}
|
|
1995
2142
|
}
|
|
1996
2143
|
}
|
|
2144
|
+
|
|
2145
|
+
/* ============================================================================
|
|
2146
|
+
The header layout on a phone — the drawer (≤1024px)
|
|
2147
|
+
----------------------------------------------------------------------------
|
|
2148
|
+
The sliding panel, the backdrop, the hamburger and the body scroll lock were
|
|
2149
|
+
all written for the SIDEBAR layouts. The one-line header — the layout most of
|
|
2150
|
+
these applications actually run — had none of it: below 1024px its answer to
|
|
2151
|
+
a narrow screen was a horizontally-scrolling nav strip with the scrollbar
|
|
2152
|
+
hidden, which is a menu you have to know is there to find, next to an action
|
|
2153
|
+
cluster whose min-content width alone exceeded a 390px phone.
|
|
2154
|
+
|
|
2155
|
+
Nothing here is a second implementation of any of that. The panel IS
|
|
2156
|
+
`.aside--cms`, the open state IS `.aside--cms--open`, the backdrop IS
|
|
2157
|
+
`.drawer-backdrop`, the action footer IS `.rail-actions`, and the scroll lock
|
|
2158
|
+
is the effect that was already keyed on the same `isOpen`. What this block
|
|
2159
|
+
adds is the two things the header layout genuinely does not share: the anchor
|
|
2160
|
+
(this drawer hangs off the bottom of the BAR, not off a 50px content header)
|
|
2161
|
+
and the flat row list that replaces dropdowns nobody can open with a finger.
|
|
2162
|
+
========================================================================= */
|
|
2163
|
+
|
|
2164
|
+
/* The bar's full outside height, composed rather than guessed — see the
|
|
2165
|
+
`--nav-header-height` entry in this file's density-hook header, including the
|
|
2166
|
+
one case a project has to state it by hand. Written as repeated additions
|
|
2167
|
+
because a multiplication of a custom property is one more thing that can go
|
|
2168
|
+
invalid at computed-value time for no gain. */
|
|
2169
|
+
$drawer-top: var(
|
|
2170
|
+
--nav-header-height,
|
|
2171
|
+
calc(
|
|
2172
|
+
var(--nav-logo-height, 50px) + var(--spacing-sm, 0.5rem) +
|
|
2173
|
+
var(--spacing-sm, 0.5rem) + 1px
|
|
2174
|
+
)
|
|
2175
|
+
);
|
|
2176
|
+
|
|
2177
|
+
@media (max-width: 1024px) {
|
|
2178
|
+
/* The strip goes; the drawer replaces it. `> .navwrap` and not a bare
|
|
2179
|
+
`.navwrap`: the sidebar layouts' own nav list wears the same class and
|
|
2180
|
+
is the CONTENTS of a drawer rather than an alternative to one. */
|
|
2181
|
+
.hwrap > .navwrap {
|
|
2182
|
+
display: none;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
/* The hamburger. Every span, every offset and every transform that folds
|
|
2186
|
+
the six bars into an X is `.mobmenu`'s, unchanged — what differs is
|
|
2187
|
+
placement: the content header pins its copy absolutely into a corner,
|
|
2188
|
+
and this one is an item in the bar's grid.
|
|
2189
|
+
|
|
2190
|
+
The scale is the other change. `.mobmenu` draws inside a 60x45 box and
|
|
2191
|
+
then scales the whole button to 0.4, which is a 24x18 target — under any
|
|
2192
|
+
touch guideline — while still reserving all 60px of bar width, because a
|
|
2193
|
+
transform does not change a layout box. 0.55 puts the DRAWN mark at
|
|
2194
|
+
33x25: the box is the same order as the 34px chips at the far end, and —
|
|
2195
|
+
the part 0.75 got wrong — the bars themselves thin from ~4px to ~3px and
|
|
2196
|
+
stop out-weighing every glyph in the bar. (The chips draw ~18px icons
|
|
2197
|
+
inside their boxes; a hamburger whose bars span the full box will always
|
|
2198
|
+
read a step heavier than its box-for-box equal, so it sits a step BELOW
|
|
2199
|
+
the chips' box size, not at it.) Hit-testing follows the transform, so
|
|
2200
|
+
this is also the tap target — 33x25 is the same order as the chips, and
|
|
2201
|
+
the bar around it is dead space, so a near miss lands on nothing. The
|
|
2202
|
+
negative margins hand back the slack the untransformed box still holds
|
|
2203
|
+
in layout: half of (60 - 33) across and half of (45 - 24.75) down. */
|
|
2204
|
+
.hwrap .hmenu {
|
|
2205
|
+
position: relative;
|
|
2206
|
+
top: auto;
|
|
2207
|
+
left: auto;
|
|
2208
|
+
flex: none;
|
|
2209
|
+
justify-self: start;
|
|
2210
|
+
transform: scale(0.55);
|
|
2211
|
+
margin: -10px -13.5px;
|
|
2212
|
+
z-index: auto;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
/* THE DRAWER. The transform, the transition curve, the width, the z-index
|
|
2216
|
+
and the shadow all come from `.aside--cms` in the media block above; this
|
|
2217
|
+
re-anchors it and gives it the column the header layout has no `.awrap`
|
|
2218
|
+
to provide. */
|
|
2219
|
+
.hwrap .aside--drawer {
|
|
2220
|
+
top: #{$drawer-top};
|
|
2221
|
+
left: 0;
|
|
2222
|
+
height: calc(100% - #{$drawer-top});
|
|
2223
|
+
display: flex;
|
|
2224
|
+
flex-direction: column;
|
|
2225
|
+
/* `.aside--cms` reads `--nav-bg` with no fallback, which is fine for
|
|
2226
|
+
the layouts that ship with a rail and declare it. A header-layout
|
|
2227
|
+
project may never have needed one, and a `var()` that resolves to
|
|
2228
|
+
nothing takes the whole declaration with it — a transparent drawer
|
|
2229
|
+
over the page. */
|
|
2230
|
+
background: var(--nav-bg, var(--primary-color, #1b3933));
|
|
2231
|
+
/* The panels the action rows open — the notification list, the account
|
|
2232
|
+
switcher — are absolutely positioned inside the drawer and would be
|
|
2233
|
+
cut off by `.aside--cms`'s own clip. The nav list below keeps its own
|
|
2234
|
+
scroll container, so nothing here depended on that clip. */
|
|
2235
|
+
overflow: visible;
|
|
2236
|
+
|
|
2237
|
+
/* OFF-SCREEN IS NOT ENOUGH. A translated panel is still in the tab
|
|
2238
|
+
order and still in the accessibility tree, so a closed drawer parks
|
|
2239
|
+
the entire menu between the hamburger and the page for anyone using a
|
|
2240
|
+
keyboard or a screen reader. `visibility` removes it from both, and
|
|
2241
|
+
the delay is what keeps the CLOSING slide visible: the panel stays
|
|
2242
|
+
painted for exactly the length of the transform and goes at the end
|
|
2243
|
+
of it. */
|
|
2244
|
+
visibility: hidden;
|
|
2245
|
+
transition:
|
|
2246
|
+
transform 0.55s cubic-bezier(0.25, 0.8, 0.25, 1),
|
|
2247
|
+
visibility 0s linear 0.55s;
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
.hwrap .aside--drawer.aside--cms--open {
|
|
2251
|
+
visibility: visible;
|
|
2252
|
+
transition:
|
|
2253
|
+
transform 0.55s cubic-bezier(0.25, 0.8, 0.25, 1),
|
|
2254
|
+
visibility 0s;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
/* The backdrop starts where the drawer does, so the bar above it stays
|
|
2258
|
+
tappable — which is what lets the hamburger close what it opened. */
|
|
2259
|
+
.hwrap .drawer-backdrop {
|
|
2260
|
+
top: #{$drawer-top};
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
/* The hairline before the LAST chip says sign-out is a different kind of
|
|
2264
|
+
action from the eight before it. Below this width sign-out is a drawer
|
|
2265
|
+
row and the last chip is the ticket count, so the divider would be
|
|
2266
|
+
separating nothing from nothing. */
|
|
2267
|
+
.hwrap .hactions > ul > li:last-child,
|
|
2268
|
+
.hwrap .hactions-alternate > ul > li:last-child {
|
|
2269
|
+
margin-left: var(--nav-action-gap, 2px);
|
|
2270
|
+
|
|
2271
|
+
&::before {
|
|
2272
|
+
content: none;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
/* ---------------------------------------------------------------------- *
|
|
2277
|
+
* the drawer's rows
|
|
2278
|
+
* ---------------------------------------------------------------------- *
|
|
2279
|
+
* The band between the top of the drawer and the actions pinned under it,
|
|
2280
|
+
* and the only part of the drawer that scrolls.
|
|
2281
|
+
*/
|
|
2282
|
+
.drawer-nav {
|
|
2283
|
+
flex: 1 1 auto;
|
|
2284
|
+
/* Or the list refuses to shrink below its own content and pushes the
|
|
2285
|
+
action footer off the bottom of the panel. */
|
|
2286
|
+
min-height: 0;
|
|
2287
|
+
overflow-x: hidden;
|
|
2288
|
+
overflow-y: auto;
|
|
2289
|
+
padding: var(--padding, 1rem) 0;
|
|
2290
|
+
|
|
2291
|
+
/* THE SCROLLBAR IS THE AFFORDANCE, and it was hidden.
|
|
2292
|
+
`scrollbar-width: none` + `::-webkit-scrollbar { display: none }`
|
|
2293
|
+
is the right call for the horizontal strip this file hides it on —
|
|
2294
|
+
a 3px bar under a row of chips is noise. Here it is the only thing
|
|
2295
|
+
on screen that says the menu continues below the fold: the band
|
|
2296
|
+
stops at a full row and the pinned actions start immediately under
|
|
2297
|
+
it, so a truncated list and a complete one look identical. Safari
|
|
2298
|
+
on iOS honours `scrollbar-width`, so hiding it took the flashing
|
|
2299
|
+
overlay indicator away too — the list scrolled and said nothing
|
|
2300
|
+
about it, on the one device this drawer exists for.
|
|
2301
|
+
Thin and low-contrast rather than back to the platform default. */
|
|
2302
|
+
scrollbar-width: thin;
|
|
2303
|
+
scrollbar-color: rgba(var(--tertiary-color-rgb, 255, 255, 255), 0.28)
|
|
2304
|
+
transparent;
|
|
2305
|
+
|
|
2306
|
+
&::-webkit-scrollbar {
|
|
2307
|
+
width: 3px;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
&::-webkit-scrollbar-thumb {
|
|
2311
|
+
background: rgba(var(--tertiary-color-rgb, 255, 255, 255), 0.28);
|
|
2312
|
+
border-radius: 3px;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
/* The page behind the drawer is frozen by a scroll lock on <body>,
|
|
2316
|
+
which iOS Safari does not honour for the viewport scroller. Without
|
|
2317
|
+
this, a swipe that reaches either end of the list — and on first
|
|
2318
|
+
open the list IS at its top — chains into the page underneath and
|
|
2319
|
+
the drawer reads as a menu that will not move. */
|
|
2320
|
+
overscroll-behavior: contain;
|
|
2321
|
+
-webkit-overflow-scrolling: touch;
|
|
2322
|
+
|
|
2323
|
+
> ul {
|
|
2324
|
+
margin: 0;
|
|
2325
|
+
padding: 0;
|
|
2326
|
+
list-style: none;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
/* The same gutter `.app-nav .nav-item` and `.rail-actions > ul > li`
|
|
2330
|
+
use at this width, so the three bands of the drawer line up. */
|
|
2331
|
+
> ul > li {
|
|
2332
|
+
width: 100%;
|
|
2333
|
+
padding: 0.2rem 0.65rem;
|
|
2334
|
+
box-sizing: border-box;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
/* A row: full width, one line, a finger-sized target. The pill is
|
|
2338
|
+
pushed to the trailing edge rather than butted against the label —
|
|
2339
|
+
the labels here are of wildly different lengths and an inline badge
|
|
2340
|
+
would land in a different place on every line.
|
|
2341
|
+
|
|
2342
|
+
THERE IS NO HOVER RULE ANYWHERE IN THIS DRAWER, deliberately. On the
|
|
2343
|
+
devices it exists for, `:hover` is whatever was tapped last and stays
|
|
2344
|
+
lit until something else is tapped — a row that looks selected
|
|
2345
|
+
because you scrolled past it. The current page is marked by `.active`
|
|
2346
|
+
below, which is a fact rather than an artefact.
|
|
2347
|
+
|
|
2348
|
+
`font-size` is stated rather than inherited for the reason every list
|
|
2349
|
+
in this house states it: global.css carries
|
|
2350
|
+
`ul { font-size: clamp(1rem, 2vw, 1.25rem) }` and an element selector
|
|
2351
|
+
beats a class. */
|
|
2352
|
+
a {
|
|
2353
|
+
display: flex;
|
|
2354
|
+
flex-wrap: nowrap;
|
|
2355
|
+
align-items: center;
|
|
2356
|
+
justify-content: space-between;
|
|
2357
|
+
gap: 0.5rem;
|
|
2358
|
+
width: 100%;
|
|
2359
|
+
box-sizing: border-box;
|
|
2360
|
+
padding: 0.85rem 1rem;
|
|
2361
|
+
line-height: 1.3;
|
|
2362
|
+
color: var(--tertiary-color, #fff);
|
|
2363
|
+
font-size: 1rem;
|
|
2364
|
+
font-weight: var(--font-weight-medium, 500);
|
|
2365
|
+
text-decoration: none;
|
|
2366
|
+
border-radius: var(--br, 5px);
|
|
2367
|
+
|
|
2368
|
+
&:focus-visible {
|
|
2369
|
+
outline: 2px solid
|
|
2370
|
+
var(--nav-active-bar-color, var(--highlight-color, #fff));
|
|
2371
|
+
outline-offset: -2px;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
a.active {
|
|
2376
|
+
background: rgba(var(--tertiary-color-rgb, 255, 255, 255), 0.12);
|
|
2377
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
/* A group's NAME, and not a control: it has no page of its own, and it has
|
|
2382
|
+
nothing left to disclose because its rows are already open beneath it. So
|
|
2383
|
+
it takes the section-label voice — small, quiet, uppercase — and no tap
|
|
2384
|
+
target at all. A <button> that opens a dropdown is precisely what does
|
|
2385
|
+
not work on a touch screen, and is the reason this drawer exists. */
|
|
2386
|
+
.drawer-heading {
|
|
2387
|
+
display: block;
|
|
2388
|
+
padding: 1rem 1rem 0.35rem;
|
|
2389
|
+
color: rgba(var(--tertiary-color-rgb, 255, 255, 255), 0.5);
|
|
2390
|
+
font-size: 0.72rem;
|
|
2391
|
+
font-weight: 600;
|
|
2392
|
+
letter-spacing: 0.06em;
|
|
2393
|
+
text-transform: uppercase;
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
/* Indented under the heading that names it, with a rule down the left so
|
|
2397
|
+
the grouping survives being scrolled past its own heading. */
|
|
2398
|
+
.drawer-child > a {
|
|
2399
|
+
padding-left: 1.75rem;
|
|
2400
|
+
border-left: 2px solid
|
|
2401
|
+
rgba(var(--tertiary-color-rgb, 255, 255, 255), 0.12);
|
|
2402
|
+
border-radius: 0 var(--br, 5px) var(--br, 5px) 0;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
/* ----------------------------------------------------------------------------
|
|
2407
|
+
A drawer shorter than its own footer
|
|
2408
|
+
----------------------------------------------------------------------------
|
|
2409
|
+
The pinned footer is `flex: 0 0 auto` — it takes its content height and the
|
|
2410
|
+
list takes what is left, which is the rail's arrangement and the right one
|
|
2411
|
+
while there IS anything left. A phone in LANDSCAPE is 393px tall: the panel
|
|
2412
|
+
is that minus the bar, and six labelled action rows are taller than all of
|
|
2413
|
+
it. The list is then squeezed to zero and the navigation — the entire reason
|
|
2414
|
+
the drawer exists — is not reachable by any gesture.
|
|
2415
|
+
|
|
2416
|
+
So below this height the drawer stops being two bands and becomes one
|
|
2417
|
+
scroller: the list and the actions scroll together, everything is reachable,
|
|
2418
|
+
and the footer is simply the end of the list. The cost is that the panels
|
|
2419
|
+
those action rows open are clipped by the new scroll container, and it is
|
|
2420
|
+
the right cost — a 350px-tall popover has nowhere to open on a 393px screen
|
|
2421
|
+
whether or not anything clips it.
|
|
2422
|
+
------------------------------------------------------------------------- */
|
|
2423
|
+
@media (max-width: 1024px) and (max-height: 600px) {
|
|
2424
|
+
.hwrap .aside--drawer {
|
|
2425
|
+
overflow-y: auto;
|
|
2426
|
+
overscroll-behavior: contain;
|
|
2427
|
+
-webkit-overflow-scrolling: touch;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
.drawer-nav {
|
|
2431
|
+
flex: 0 0 auto;
|
|
2432
|
+
overflow: visible;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
/* The mark, on a phone. 130px of logo beside a hamburger and three chips is the
|
|
2437
|
+
difference between the bar fitting a 390px screen and not, and a mark is the
|
|
2438
|
+
one thing in the bar that is still recognisable at two thirds the size.
|
|
2439
|
+
`min()` so this can only ever SHRINK: a project whose --logo-width is already
|
|
2440
|
+
narrower than the cap keeps its own figure. The logo's own gutter tightens
|
|
2441
|
+
with it — a hairline with 16px either side reads as a gap, not a divider,
|
|
2442
|
+
once everything around it has closed up. */
|
|
2443
|
+
@media (max-width: 640px) {
|
|
2444
|
+
.hwrap .logo {
|
|
2445
|
+
width: min(var(--logo-width, 130px), var(--nav-logo-width-sm, 92px));
|
|
2446
|
+
margin-right: var(--spacing-sm, 0.5rem);
|
|
2447
|
+
padding-right: var(--spacing-sm, 0.5rem);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
@@ -967,6 +967,77 @@ $sms-mono: 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
|
|
|
967
967
|
font-size: 0.75rem; // 12px at a 16px root
|
|
968
968
|
}
|
|
969
969
|
|
|
970
|
+
// ---------------------------------------------------------------------------
|
|
971
|
+
// Mounted in the sidebar rail, and in the mobile drawer
|
|
972
|
+
// ---------------------------------------------------------------------------
|
|
973
|
+
// `actionsPlacement: 'sidebar'` — and, below 1024px, the header layout's
|
|
974
|
+
// drawer — draws the account actions as full-width rows instead of chips. The
|
|
975
|
+
// block that holds them advertises itself with `data-nav-actions="sidebar"`
|
|
976
|
+
// (Navigation.jsx / Navigation.module.scss `.rail-actions`), which is all this
|
|
977
|
+
// file needs to know. Notification.module.scss carries the same block for the
|
|
978
|
+
// bell; this is the copy for the inbox badge, and it exists because a chip and
|
|
979
|
+
// a row are two different things in three places.
|
|
980
|
+
//
|
|
981
|
+
// The row's box — full width, the rail's padding, left-aligned, and the LABEL
|
|
982
|
+
// beside the icon — all come from `.rail-actions`. The trigger publishes no
|
|
983
|
+
// label of its own, so the row publishes one as `--nav-action-label` and the
|
|
984
|
+
// rail hangs it off the trigger's `::after`. Only what this file would
|
|
985
|
+
// otherwise force back is restated here.
|
|
986
|
+
[data-nav-actions='sidebar'] {
|
|
987
|
+
// The chip's lift and scale read as a stray button in a list of rows.
|
|
988
|
+
// The focus ring is left alone — it is the only keyboard affordance the
|
|
989
|
+
// row has.
|
|
990
|
+
.trigger {
|
|
991
|
+
&:hover,
|
|
992
|
+
&:focus,
|
|
993
|
+
&:active {
|
|
994
|
+
transform: none;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
&:hover {
|
|
998
|
+
background-color: rgba(var(--tertiary-color-rgb, 255, 255, 255), 0.05);
|
|
999
|
+
color: var(--secondary-color);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Was pinned to the corner of a round 40px chip, which on a full-width
|
|
1004
|
+
// row is the row's top-right corner, floating clear of the count it
|
|
1005
|
+
// belongs to. At the end of the line is where the eye lands after the
|
|
1006
|
+
// label.
|
|
1007
|
+
.badge {
|
|
1008
|
+
top: 50%;
|
|
1009
|
+
right: 14px;
|
|
1010
|
+
transform: translateY(-50%);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// `top: 100%` drops the popover below the row. These rows are pinned to
|
|
1014
|
+
// the FOOT of the rail, so that is below the foot of the viewport.
|
|
1015
|
+
// Beside the rail, bottom-aligned with the row that opened it.
|
|
1016
|
+
.popoverWrapper {
|
|
1017
|
+
top: auto;
|
|
1018
|
+
right: auto;
|
|
1019
|
+
bottom: 0;
|
|
1020
|
+
left: calc(100% + 10px);
|
|
1021
|
+
margin-top: 0;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
@media (max-width: 1024px) {
|
|
1025
|
+
// Inside the drawer there is nothing to the right, so the panel
|
|
1026
|
+
// opens upward from the row.
|
|
1027
|
+
.popoverWrapper {
|
|
1028
|
+
left: 0;
|
|
1029
|
+
bottom: calc(100% + 8px);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// Keeps the panel inside a phone's viewport when the list is long —
|
|
1033
|
+
// it grows from the foot of the drawer towards the header, and 360px
|
|
1034
|
+
// of rows plus a head and a footer is taller than the room above.
|
|
1035
|
+
.rows {
|
|
1036
|
+
max-height: 40vh;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
970
1041
|
// ---------------------------------------------------------------------------
|
|
971
1042
|
// SmsInbox — the page
|
|
972
1043
|
// ---------------------------------------------------------------------------
|
|
@@ -1703,6 +1774,27 @@ $sms-mono: 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
|
|
|
1703
1774
|
background: var(--s-surface);
|
|
1704
1775
|
}
|
|
1705
1776
|
|
|
1777
|
+
// Stands exactly where the composer does on a receive-only thread - one from an
|
|
1778
|
+
// alphanumeric sender ID, which has no handset behind it to answer. The pane
|
|
1779
|
+
// keeps its footer and its height; what changes is that the footer explains
|
|
1780
|
+
// itself instead of offering a control that could not work.
|
|
1781
|
+
.receiveOnly {
|
|
1782
|
+
box-sizing: border-box;
|
|
1783
|
+
padding: 0.6rem 0.7rem 0.7rem;
|
|
1784
|
+
border-top: 1px solid var(--s-line);
|
|
1785
|
+
background: var(--s-surface);
|
|
1786
|
+
|
|
1787
|
+
.banner {
|
|
1788
|
+
// The sentence wraps on a narrow pane, so the icon sits with the first
|
|
1789
|
+
// line rather than floating in the middle of a two-line block.
|
|
1790
|
+
align-items: flex-start;
|
|
1791
|
+
|
|
1792
|
+
svg {
|
|
1793
|
+
margin-top: 0.15rem;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1706
1798
|
// Stands exactly where the composer does while the timeline is being picked
|
|
1707
1799
|
// over, so the pane keeps its height and its one footer.
|
|
1708
1800
|
.selectBar {
|