@visns-studio/visns-components 6.32.3 → 6.34.3
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 +483 -0
- package/package.json +1 -1
- package/src/components/DataGrid.jsx +44 -2
- package/src/components/Field.jsx +32 -0
- package/src/components/Form.jsx +39 -2
- package/src/components/Navigation.jsx +274 -15
- package/src/components/Notification.jsx +13 -2
- package/src/components/TableFilter.jsx +14 -2
- package/src/components/auth/Login.jsx +11 -4
- package/src/components/auth/PasskeyEnrolPrompt.jsx +269 -0
- package/src/components/auth/Profile.jsx +422 -26
- package/src/components/auth/Reset.jsx +1 -1
- package/src/components/auth/TwoFactorAuth.jsx +1 -1
- package/src/components/auth/Verify.jsx +2 -2
- package/src/components/auth/authEndpoints.js +43 -0
- package/src/components/auth/passkeyClient.js +102 -0
- package/src/components/auth/passkeyPrompt.js +408 -0
- package/src/components/auth/profileLayout.js +45 -0
- package/src/components/controls/DataGridSearch.jsx +16 -2
- package/src/components/controls/DataGridSortSheet.jsx +2 -0
- package/src/components/emailCampaigns/BlockEditor.jsx +486 -0
- package/src/components/emailCampaigns/CampaignEditor.jsx +614 -0
- package/src/components/emailCampaigns/CampaignReport.jsx +98 -0
- package/src/components/emailCampaigns/EmailCampaigns.jsx +422 -0
- package/src/components/emailCampaigns/EmailLists.jsx +505 -0
- package/src/components/emailCampaigns/emailCampaignApi.js +135 -0
- package/src/components/generic/ActionButtons.jsx +1 -1
- package/src/components/generic/GenericAuth.jsx +197 -21
- package/src/components/generic/GenericDetail.jsx +95 -3
- package/src/components/generic/GenericMain.jsx +7 -0
- package/src/components/generic/StandardModal.jsx +71 -158
- package/src/components/sms/SmsCampaigns.jsx +2517 -0
- package/src/components/sms/SmsInbox.jsx +20 -5
- package/src/components/sms/SmsLineSettings.jsx +2 -5
- package/src/components/sms/SmsThreadPanel.jsx +20 -0
- package/src/components/sms/smsEndpoints.js +23 -0
- package/src/components/styles/DataGrid.module.scss +22 -0
- package/src/components/styles/EmailCampaigns.module.scss +922 -0
- package/src/components/styles/Form.module.scss +100 -2
- package/src/components/styles/Navigation.module.scss +322 -5
- package/src/components/styles/Notification.module.scss +140 -8
- package/src/components/styles/PasskeyEnrolPrompt.module.scss +105 -0
- package/src/components/styles/Profile.module.scss +157 -0
- package/src/components/styles/Sms.module.scss +484 -4
- package/src/components/styles/StandardModal.module.scss +273 -0
- package/src/components/styles/Vault.module.scss +5 -3
- package/src/components/styles/global.css +38 -7
- package/src/components/utils/navCollapsed.js +201 -0
- package/src/components/utils/rowActionSettings.js +76 -0
- package/src/components/utils/usePasskeysEnabled.js +84 -0
- package/src/components/utils/useShellLayout.js +131 -0
- package/src/index.js +107 -1
- package/src/utils/rememberTab.js +70 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
@use 'controls' as *;
|
|
2
|
+
|
|
3
|
+
/* ============================================================================
|
|
4
|
+
PasskeyEnrolPrompt — the offer made once, after signing in
|
|
5
|
+
----------------------------------------------------------------------------
|
|
6
|
+
Inside a StandardModal at `size="small"`, so the panel's width, its phone
|
|
7
|
+
geometry and its z-index all come from StandardModal.module.scss and none of
|
|
8
|
+
it is restated here. What is here is the inside of the panel: a line of
|
|
9
|
+
explanation and a row of three buttons whose weights say what they are.
|
|
10
|
+
|
|
11
|
+
The three are deliberately NOT three equal buttons. "Add passkey" is the
|
|
12
|
+
thing being offered; "Not now" is the way out anybody can take without
|
|
13
|
+
thinking about it; "Don't ask again" is quieter still and reads as a link,
|
|
14
|
+
because a permanent decision should not be the easiest button on the screen
|
|
15
|
+
to hit by accident.
|
|
16
|
+
|
|
17
|
+
Every measurement is `var(--hook, <value>)` so an app can retune it from
|
|
18
|
+
`:root` without forking the component, the same way Profile.module.scss
|
|
19
|
+
does.
|
|
20
|
+
========================================================================= */
|
|
21
|
+
|
|
22
|
+
.body {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
gap: var(--spacing-md, 1rem);
|
|
26
|
+
padding: var(--spacing-md, 1rem) 0 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* The mark. A key in a tinted disc — enough to say "this is about signing in"
|
|
30
|
+
without an illustration nobody asked to download. */
|
|
31
|
+
.mark {
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
/* A column flex parent stretches its children; without this the disc
|
|
36
|
+
becomes a lozenge the width of the panel. */
|
|
37
|
+
align-self: flex-start;
|
|
38
|
+
width: 2.75rem;
|
|
39
|
+
height: 2.75rem;
|
|
40
|
+
border-radius: 50%;
|
|
41
|
+
background: var(--primary-color-light, rgba(0, 0, 0, 0.06));
|
|
42
|
+
color: var(--primary-color, #1b3a57);
|
|
43
|
+
flex: 0 0 auto;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.text {
|
|
47
|
+
margin: 0;
|
|
48
|
+
font-size: var(--font-size-sm, 0.875rem);
|
|
49
|
+
line-height: 1.55;
|
|
50
|
+
color: var(--text-color, #333);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* The row of choices. It wraps rather than scrolls: on a 320px phone three
|
|
54
|
+
buttons do not fit side by side, and a row that overflows puts the way out
|
|
55
|
+
off the edge of the sheet. */
|
|
56
|
+
.actions {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-wrap: wrap;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
61
|
+
padding-top: var(--spacing-sm, 0.5rem);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.primary {
|
|
65
|
+
@include button-primary;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.later {
|
|
69
|
+
@include button-secondary;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Pushed to the far end of the row, and styled as text: the permanent answer
|
|
73
|
+
should be available, findable, and never the thing a thumb lands on. */
|
|
74
|
+
.never {
|
|
75
|
+
@include button-base;
|
|
76
|
+
|
|
77
|
+
margin-left: auto;
|
|
78
|
+
background: none;
|
|
79
|
+
border-color: transparent;
|
|
80
|
+
color: var(--muted-color, #6b7280);
|
|
81
|
+
text-decoration: underline;
|
|
82
|
+
text-underline-offset: 0.2em;
|
|
83
|
+
|
|
84
|
+
&:hover:not(:disabled) {
|
|
85
|
+
background: none;
|
|
86
|
+
color: var(--text-color, #333);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Phones: one full-width button per line, in the order they are read, and the
|
|
91
|
+
quiet one loses its auto margin — `margin-left: auto` in a column pushes it
|
|
92
|
+
to the right edge of a stack, which is where nothing else is. */
|
|
93
|
+
@media (max-width: 640px) {
|
|
94
|
+
.actions {
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
align-items: stretch;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.primary,
|
|
100
|
+
.later,
|
|
101
|
+
.never {
|
|
102
|
+
width: 100%;
|
|
103
|
+
margin-left: 0;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -580,6 +580,44 @@
|
|
|
580
580
|
}
|
|
581
581
|
}
|
|
582
582
|
|
|
583
|
+
/* The passkey list. A stack of rows rather than a table: each row is a name,
|
|
584
|
+
a quiet line of context and the one control that acts on it, and at phone
|
|
585
|
+
width a table of three columns is a table that scrolls sideways. The row
|
|
586
|
+
wraps instead, so the Remove button drops under the name rather than off
|
|
587
|
+
the edge. */
|
|
588
|
+
.passkeyList {
|
|
589
|
+
display: flex;
|
|
590
|
+
flex-direction: column;
|
|
591
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
592
|
+
width: 100%;
|
|
593
|
+
margin: 0;
|
|
594
|
+
padding: 0;
|
|
595
|
+
list-style: none;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.passkeyItem {
|
|
599
|
+
display: flex;
|
|
600
|
+
flex-wrap: wrap;
|
|
601
|
+
align-items: center;
|
|
602
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
603
|
+
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
|
|
604
|
+
border: 1px solid var(--border-color, #e1e1e1);
|
|
605
|
+
border-radius: var(--radius-sm, 8px);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.passkeyName {
|
|
609
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
610
|
+
/* Takes the slack so the button sits at the far edge of the row. */
|
|
611
|
+
flex: 1 1 auto;
|
|
612
|
+
min-width: 0;
|
|
613
|
+
overflow-wrap: anywhere;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.passkeyMeta {
|
|
617
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
618
|
+
color: var(--muted-color, #6b7280);
|
|
619
|
+
}
|
|
620
|
+
|
|
583
621
|
/* The setup walkthrough. It used to be a bordered, shadowed card inside the
|
|
584
622
|
panel card, carrying a heading that repeated the heading above it. The
|
|
585
623
|
section owns the title and the separation now, so this is only the steps. */
|
|
@@ -902,3 +940,122 @@
|
|
|
902
940
|
color: rgba(var(--paragraph-color-rgb, 43, 43, 43), 0.65);
|
|
903
941
|
}
|
|
904
942
|
}
|
|
943
|
+
|
|
944
|
+
/* ============================================================================
|
|
945
|
+
* Tabs as a strip
|
|
946
|
+
* ----------------------------------------------------------------------------
|
|
947
|
+
* The rail above is the layout this screen has always had, and on a top-bar
|
|
948
|
+
* app it is still the one it gets. In a SIDEBAR app the page already has a
|
|
949
|
+
* vertical navigation column two centimetres to the left, and a second one
|
|
950
|
+
* beside it spends a fifth of the content width to show two words. Profile
|
|
951
|
+
* asks for the strip there (see profileLayout.js for the rule, Profile.jsx
|
|
952
|
+
* for where the classes below are applied) and for the same three shapes
|
|
953
|
+
* GenericIndex and GenericDetail use for it, so the three pages agree:
|
|
954
|
+
*
|
|
955
|
+
* `.crmtitle--flat` the page title as type on the page ground.
|
|
956
|
+
* `.grid__subrow--stacked` one column instead of rail + panel.
|
|
957
|
+
* `.grid__subnav--underline` the rail's card undone; TableFilter's
|
|
958
|
+
* `variant="underline"` draws what is inside.
|
|
959
|
+
*
|
|
960
|
+
* Appended rather than merged into the rules above, and every selector here
|
|
961
|
+
* carries a modifier class: with none of them applied — which is every
|
|
962
|
+
* `layout="full"` app, and any app that passes `tabLayout="rail"` — not one
|
|
963
|
+
* declaration in this block matches anything.
|
|
964
|
+
* ==========================================================================*/
|
|
965
|
+
|
|
966
|
+
/* `.crmtitle` is applied to the same element as `.grid__full`, so this has to
|
|
967
|
+
undo that card's surface, border, radius, shadow and 2px padding as well as
|
|
968
|
+
the title card's own. Matched to `.crmtitle` in GenericIndex.module.scss —
|
|
969
|
+
the same inset, the same type size — so the account screen sits on the same
|
|
970
|
+
baseline as every index page. */
|
|
971
|
+
.crmtitle--flat {
|
|
972
|
+
padding: 0 0.25rem 0.85rem;
|
|
973
|
+
margin: 8px 0 0;
|
|
974
|
+
background: none;
|
|
975
|
+
border: 0;
|
|
976
|
+
border-radius: 0;
|
|
977
|
+
box-shadow: none;
|
|
978
|
+
|
|
979
|
+
h1 {
|
|
980
|
+
font-size: 1.375rem;
|
|
981
|
+
font-weight: 700;
|
|
982
|
+
letter-spacing: -0.01em;
|
|
983
|
+
line-height: 1.25;
|
|
984
|
+
color: var(--heading-color, var(--primary-color, #1b3933));
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/* Two grid tracks become one flow column: `grid-template-columns: none` is
|
|
989
|
+
what hands the rail's 14rem back to the panel rather than leaving a hole
|
|
990
|
+
beside it. */
|
|
991
|
+
.grid__subrow--stacked {
|
|
992
|
+
display: flex;
|
|
993
|
+
flex-direction: column;
|
|
994
|
+
flex-wrap: nowrap;
|
|
995
|
+
grid-template-columns: none;
|
|
996
|
+
align-items: stretch;
|
|
997
|
+
gap: 0;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.grid__subrow--stacked > .grid__subnav,
|
|
1001
|
+
.grid__subrow--stacked > .grid__subcontent {
|
|
1002
|
+
flex: 0 0 auto;
|
|
1003
|
+
width: 100%;
|
|
1004
|
+
/* Without this the strip's min-content width floors the flex item, so a
|
|
1005
|
+
row of tabs widens the page instead of scrolling inside it. */
|
|
1006
|
+
min-width: 0;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/* The strip and the card it labels are one object: no gap, and the card's top
|
|
1010
|
+
corners squared off under it. The `8px 0` margin `.grid__subcontent` carries
|
|
1011
|
+
for the rail layout would put a hairline gap between the two. */
|
|
1012
|
+
.grid__subrow--stacked > .grid__subcontent {
|
|
1013
|
+
margin: 0;
|
|
1014
|
+
border-top-left-radius: 0;
|
|
1015
|
+
border-top-right-radius: 0;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
.grid__subnav--underline {
|
|
1019
|
+
/* Not a card. */
|
|
1020
|
+
padding: 0;
|
|
1021
|
+
margin: 0;
|
|
1022
|
+
background: none;
|
|
1023
|
+
border: none;
|
|
1024
|
+
border-radius: 0;
|
|
1025
|
+
box-shadow: none;
|
|
1026
|
+
/* The one hairline under the whole strip; the selected tab's accent edge
|
|
1027
|
+
sits on top of it, and the content card below continues it. */
|
|
1028
|
+
border-bottom: 1px solid var(--border-color, #e1e1e1);
|
|
1029
|
+
display: block;
|
|
1030
|
+
min-width: 0;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/* `.grid__subnav > div` above only zeroes a margin; the strip needs the
|
|
1034
|
+
wrapper to stay a plain block, which is what TableFilter's own stylesheet
|
|
1035
|
+
expects around its scroller. */
|
|
1036
|
+
.grid__subnav--underline > div {
|
|
1037
|
+
display: block;
|
|
1038
|
+
min-width: 0;
|
|
1039
|
+
margin-bottom: 0;
|
|
1040
|
+
padding: 0;
|
|
1041
|
+
background: none;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/* The panel header named the tab — "Overview" under a rail whose selected
|
|
1045
|
+
item also said "Overview". Beside a strip that is the same word twice, half
|
|
1046
|
+
a centimetre apart, so the strip is the only place the tab is named.
|
|
1047
|
+
|
|
1048
|
+
Hidden rather than dropped from the markup: the rail layout renders this
|
|
1049
|
+
element and must keep rendering it, the heading is the same string the
|
|
1050
|
+
selected tab already publishes (with real tab semantics — see TableFilter's
|
|
1051
|
+
underline variant), and one class in one place is easier to be sure about
|
|
1052
|
+
than a branch inside a memoised renderer with sixteen dependencies. */
|
|
1053
|
+
.grid__subrow--stacked .gridtxt__header {
|
|
1054
|
+
display: none;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/* With the header gone the sections would start hard against the card's top
|
|
1058
|
+
edge. The header's own vertical padding, given back to the body. */
|
|
1059
|
+
.grid__subrow--stacked .form {
|
|
1060
|
+
padding-top: var(--spacing-md, 1rem);
|
|
1061
|
+
}
|