@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.
Files changed (53) hide show
  1. package/README.md +483 -0
  2. package/package.json +1 -1
  3. package/src/components/DataGrid.jsx +44 -2
  4. package/src/components/Field.jsx +32 -0
  5. package/src/components/Form.jsx +39 -2
  6. package/src/components/Navigation.jsx +274 -15
  7. package/src/components/Notification.jsx +13 -2
  8. package/src/components/TableFilter.jsx +14 -2
  9. package/src/components/auth/Login.jsx +11 -4
  10. package/src/components/auth/PasskeyEnrolPrompt.jsx +269 -0
  11. package/src/components/auth/Profile.jsx +422 -26
  12. package/src/components/auth/Reset.jsx +1 -1
  13. package/src/components/auth/TwoFactorAuth.jsx +1 -1
  14. package/src/components/auth/Verify.jsx +2 -2
  15. package/src/components/auth/authEndpoints.js +43 -0
  16. package/src/components/auth/passkeyClient.js +102 -0
  17. package/src/components/auth/passkeyPrompt.js +408 -0
  18. package/src/components/auth/profileLayout.js +45 -0
  19. package/src/components/controls/DataGridSearch.jsx +16 -2
  20. package/src/components/controls/DataGridSortSheet.jsx +2 -0
  21. package/src/components/emailCampaigns/BlockEditor.jsx +486 -0
  22. package/src/components/emailCampaigns/CampaignEditor.jsx +614 -0
  23. package/src/components/emailCampaigns/CampaignReport.jsx +98 -0
  24. package/src/components/emailCampaigns/EmailCampaigns.jsx +422 -0
  25. package/src/components/emailCampaigns/EmailLists.jsx +505 -0
  26. package/src/components/emailCampaigns/emailCampaignApi.js +135 -0
  27. package/src/components/generic/ActionButtons.jsx +1 -1
  28. package/src/components/generic/GenericAuth.jsx +197 -21
  29. package/src/components/generic/GenericDetail.jsx +95 -3
  30. package/src/components/generic/GenericMain.jsx +7 -0
  31. package/src/components/generic/StandardModal.jsx +71 -158
  32. package/src/components/sms/SmsCampaigns.jsx +2517 -0
  33. package/src/components/sms/SmsInbox.jsx +20 -5
  34. package/src/components/sms/SmsLineSettings.jsx +2 -5
  35. package/src/components/sms/SmsThreadPanel.jsx +20 -0
  36. package/src/components/sms/smsEndpoints.js +23 -0
  37. package/src/components/styles/DataGrid.module.scss +22 -0
  38. package/src/components/styles/EmailCampaigns.module.scss +922 -0
  39. package/src/components/styles/Form.module.scss +100 -2
  40. package/src/components/styles/Navigation.module.scss +322 -5
  41. package/src/components/styles/Notification.module.scss +140 -8
  42. package/src/components/styles/PasskeyEnrolPrompt.module.scss +105 -0
  43. package/src/components/styles/Profile.module.scss +157 -0
  44. package/src/components/styles/Sms.module.scss +484 -4
  45. package/src/components/styles/StandardModal.module.scss +273 -0
  46. package/src/components/styles/Vault.module.scss +5 -3
  47. package/src/components/styles/global.css +38 -7
  48. package/src/components/utils/navCollapsed.js +201 -0
  49. package/src/components/utils/rowActionSettings.js +76 -0
  50. package/src/components/utils/usePasskeysEnabled.js +84 -0
  51. package/src/components/utils/useShellLayout.js +131 -0
  52. package/src/index.js +107 -1
  53. 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
+ }