@visns-studio/visns-components 6.17.2 → 6.20.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.
Files changed (83) hide show
  1. package/package.json +10 -7
  2. package/src/components/AssociationManager.jsx +344 -177
  3. package/src/components/AsyncSelect.jsx +2 -2
  4. package/src/components/Autocomplete.jsx +57 -12
  5. package/src/components/Breadcrumb.jsx +23 -4
  6. package/src/components/ClientAssociationManager.jsx +321 -145
  7. package/src/components/DataGrid.jsx +625 -20
  8. package/src/components/DropZone.jsx +4 -1
  9. package/src/components/Form.jsx +233 -6
  10. package/src/components/MultiSelect.jsx +2 -2
  11. package/src/components/Navigation.jsx +39 -23
  12. package/src/components/TableFilter.jsx +172 -12
  13. package/src/components/UniversalSearch.jsx +390 -0
  14. package/src/components/auth/Login.jsx +164 -8
  15. package/src/components/auth/Profile.jsx +209 -22
  16. package/src/components/auth/authEndpoints.js +4 -0
  17. package/src/components/auth/webauthn.js +194 -0
  18. package/src/components/columns/ColumnRenderers.jsx +6 -1
  19. package/src/components/controls/DataGridSearch.jsx +11 -5
  20. package/src/components/generic/GenericAuth.jsx +6 -0
  21. package/src/components/generic/GenericDashboard.jsx +93 -36
  22. package/src/components/generic/GenericDetail.jsx +146 -61
  23. package/src/components/generic/GenericIndex.jsx +134 -38
  24. package/src/components/generic/GenericSettings.jsx +220 -0
  25. package/src/components/generic/StandardModal.jsx +165 -8
  26. package/src/components/generic/dashboardChartDefaults.js +230 -0
  27. package/src/components/generic/shared/indexLayout.js +38 -0
  28. package/src/components/settings/IntegrationsManager.jsx +474 -0
  29. package/src/components/sms/SmsLineSettings.jsx +16 -12
  30. package/src/components/styles/AssociationManager.module.scss +4 -363
  31. package/src/components/styles/AuditLog.module.scss +6 -6
  32. package/src/components/styles/AuditLogs.module.scss +3 -3
  33. package/src/components/styles/Autocomplete.module.scss +7 -7
  34. package/src/components/styles/BusinessCardOcr.module.scss +6 -1
  35. package/src/components/styles/CallQueueSettings.module.scss +4 -1
  36. package/src/components/styles/CameraPlacement.module.scss +19 -19
  37. package/src/components/styles/CategorizedDropZone.module.scss +6 -1
  38. package/src/components/styles/ClientAssociationManager.module.scss +5 -289
  39. package/src/components/styles/ClientAuth.module.scss +1 -1
  40. package/src/components/styles/DataGrid.module.scss +455 -8
  41. package/src/components/styles/Field.module.scss +32 -24
  42. package/src/components/styles/Form.module.scss +123 -3
  43. package/src/components/styles/GenericDashboard.module.scss +612 -825
  44. package/src/components/styles/GenericDetail.module.scss +406 -61
  45. package/src/components/styles/GenericDynamic.module.scss +25 -25
  46. package/src/components/styles/GenericEditableTable.module.scss +33 -33
  47. package/src/components/styles/GenericFormBuilder.module.scss +119 -119
  48. package/src/components/styles/GenericIndex.module.scss +483 -38
  49. package/src/components/styles/GenericMain.module.scss +6 -3
  50. package/src/components/styles/GenericQuote.module.scss +399 -81
  51. package/src/components/styles/GenericReport.module.scss +8 -8
  52. package/src/components/styles/GenericSettings.module.scss +209 -0
  53. package/src/components/styles/GenericSort.module.scss +9 -9
  54. package/src/components/styles/Integrations.module.scss +321 -0
  55. package/src/components/styles/List.module.scss +4 -4
  56. package/src/components/styles/Login.module.scss +57 -0
  57. package/src/components/styles/MultiSelect.module.scss +4 -4
  58. package/src/components/styles/Navigation.module.scss +12 -12
  59. package/src/components/styles/Notification.module.scss +12 -8
  60. package/src/components/styles/NotificationList.module.scss +5 -5
  61. package/src/components/styles/Profile.module.scss +5 -5
  62. package/src/components/styles/QrCode.module.scss +4 -4
  63. package/src/components/styles/QuickAction.module.scss +20 -20
  64. package/src/components/styles/SortableQuoteItems.module.scss +4 -4
  65. package/src/components/styles/TableFilter.module.scss +128 -17
  66. package/src/components/styles/TicketConversation.module.scss +497 -0
  67. package/src/components/styles/UniversalSearch.module.scss +229 -0
  68. package/src/components/styles/Vault.module.scss +413 -0
  69. package/src/components/styles/_associationManager.scss +467 -0
  70. package/src/components/styles/_authBrandPanel.scss +15 -9
  71. package/src/components/styles/_controls.scss +43 -39
  72. package/src/components/styles/global-datagrid.css +196 -0
  73. package/src/components/styles/global.css +168 -17
  74. package/src/components/tickets/TicketConversation.jsx +455 -0
  75. package/src/components/utils/rowActionSettings.js +117 -0
  76. package/src/components/vault/VaultClientPicker.jsx +183 -0
  77. package/src/components/vault/VaultEntryForm.jsx +23 -0
  78. package/src/components/vault/VaultManager.jsx +382 -34
  79. package/src/components/vault/VaultShareModal.jsx +454 -0
  80. package/src/components/vault/vaultEndpoints.js +16 -2
  81. package/src/components/vault/vaultShares.js +247 -0
  82. package/src/index.js +35 -0
  83. package/src/utils/fetchUtil.js +38 -14
@@ -0,0 +1,229 @@
1
+ // ============================================================================
2
+ // Universal search — the ⌘K palette
3
+ // ----------------------------------------------------------------------------
4
+ // Mounts inside Navigation's `.hactions > ul`, whose chip rules reach every
5
+ // descendant `a` and `button` and hand them white text on a fixed 36px box.
6
+ // On a white panel that renders as blank boxes, so — exactly as Vault and Sms
7
+ // do — nothing here inherits anything it actually needs. Every token carries a
8
+ // fallback, because a var() on an undefined property with no fallback is
9
+ // invalid at computed-value time and the whole declaration is dropped.
10
+ // ============================================================================
11
+
12
+ .wrap {
13
+ --us-primary: var(--primary-color, #1b3933);
14
+ --us-ink: var(--paragraph-color, #2b2b2b);
15
+ --us-surface: var(--background-color, #fff);
16
+ --us-line: color-mix(in srgb, var(--us-ink) 14%, transparent);
17
+ --us-muted: color-mix(in srgb, var(--us-ink) 66%, transparent);
18
+ --us-quiet: color-mix(in srgb, var(--us-ink) 4%, var(--us-surface));
19
+ --us-hover: color-mix(in srgb, var(--us-primary) 8%, var(--us-surface));
20
+ --us-motion: var(--speed, 160ms) var(--ease, cubic-bezier(0.4, 0, 0.2, 1));
21
+
22
+ position: relative;
23
+ display: flex;
24
+ align-items: center;
25
+ }
26
+
27
+ // The same 40px chip as Notification and VaultQuickSearch, so the counts and
28
+ // controls in the header read as one row rather than three treatments.
29
+ .trigger {
30
+ display: inline-flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ width: var(--nav-action-size, 34px);
34
+ height: var(--nav-action-size, 34px);
35
+ padding: 0;
36
+ border: 0;
37
+ border-radius: var(--nav-chip-radius, var(--radius-pill, 999px));
38
+ background: transparent;
39
+ color: inherit;
40
+ cursor: pointer;
41
+ transition: background var(--us-motion);
42
+ }
43
+
44
+ .trigger:hover { background: rgba(255, 255, 255, 0.14); }
45
+
46
+ .trigger:focus-visible {
47
+ outline: 2px solid var(--highlight-color, #3cbf7d);
48
+ outline-offset: 2px;
49
+ }
50
+
51
+ .overlay {
52
+ position: absolute;
53
+ top: calc(100% + 0.5rem);
54
+ right: 0;
55
+ z-index: 1200;
56
+ }
57
+
58
+ .panel {
59
+ width: min(620px, calc(100vw - 2rem));
60
+ display: flex;
61
+ flex-direction: column;
62
+ border: 1px solid var(--us-line);
63
+ border-radius: 14px;
64
+ background: var(--us-surface);
65
+ color: var(--us-ink);
66
+ // Two stops: a tight contact shadow plus a wide ambient one. One large
67
+ // blur reads as a smudge rather than a raised surface.
68
+ box-shadow:
69
+ 0 2px 4px color-mix(in srgb, var(--us-primary) 10%, transparent),
70
+ 0 24px 48px -20px color-mix(in srgb, var(--us-primary) 40%, transparent);
71
+ overflow: hidden;
72
+ }
73
+
74
+ // -- the field ---------------------------------------------------------------
75
+
76
+ .field {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 0.55rem;
80
+ padding: 0.7rem 0.85rem;
81
+ border-bottom: 1px solid var(--us-line);
82
+ }
83
+
84
+ .fieldIcon { flex: 0 0 auto; color: var(--us-muted); }
85
+
86
+ .input {
87
+ flex: 1 1 auto;
88
+ min-width: 0;
89
+ height: auto;
90
+ padding: 0;
91
+ border: 0;
92
+ border-radius: 0;
93
+ background: none;
94
+ color: var(--us-ink);
95
+ font: inherit;
96
+ font-size: 0.9375rem;
97
+ line-height: 1.4;
98
+ }
99
+
100
+ .input:focus { outline: none; } // the panel itself is the focus surface
101
+ .input::placeholder { color: var(--us-muted); }
102
+
103
+ // -- results -----------------------------------------------------------------
104
+
105
+ .results {
106
+ // Scrolls inside the panel; a long result set must never make the page
107
+ // scroll behind the overlay.
108
+ max-height: min(52vh, 26rem);
109
+ overflow-y: auto;
110
+ padding: 0.35rem 0;
111
+ }
112
+
113
+ .state {
114
+ margin: 0;
115
+ padding: 1.1rem 0.9rem;
116
+ color: var(--us-muted);
117
+ font-size: 0.8125rem;
118
+ text-align: center;
119
+ }
120
+
121
+ .group { padding: 0.25rem 0; }
122
+
123
+ .groupLabel {
124
+ margin: 0;
125
+ padding: 0.4rem 0.9rem 0.25rem;
126
+ color: var(--us-muted);
127
+ font-size: 0.6875rem;
128
+ font-weight: 600;
129
+ letter-spacing: 0.05em;
130
+ text-transform: uppercase;
131
+ }
132
+
133
+ .rows {
134
+ margin: 0;
135
+ padding: 0;
136
+ list-style: none;
137
+ }
138
+
139
+ // `> li` reset restated explicitly: the host's own `li` rules are 0,1,2 and no
140
+ // single module class can outrank them. Same note as the Sms and Vault lists.
141
+ .rows > li {
142
+ margin: 0;
143
+ padding: 0;
144
+ border: 0;
145
+ background: none;
146
+ list-style: none;
147
+ }
148
+
149
+ .row {
150
+ display: grid;
151
+ // A fixed final track for the ↵ hint, so a row does not reflow when it
152
+ // becomes the active one.
153
+ grid-template-columns: minmax(0, 1fr) auto;
154
+ align-items: baseline;
155
+ gap: 0.15rem 0.6rem;
156
+ width: 100%;
157
+ height: auto;
158
+ min-height: 0;
159
+ padding: 0.5rem 0.9rem;
160
+ border: 0;
161
+ border-radius: 0;
162
+ background: none;
163
+ color: var(--us-ink);
164
+ font: inherit;
165
+ text-align: left;
166
+ cursor: pointer;
167
+ transition: background var(--us-motion);
168
+ }
169
+
170
+ .rowActive { background: var(--us-hover); }
171
+
172
+ .rowTitle {
173
+ grid-column: 1;
174
+ min-width: 0;
175
+ overflow: hidden;
176
+ text-overflow: ellipsis;
177
+ white-space: nowrap;
178
+ font-size: 0.875rem;
179
+ font-weight: 500;
180
+ }
181
+
182
+ .rowSub {
183
+ grid-column: 1;
184
+ min-width: 0;
185
+ overflow: hidden;
186
+ text-overflow: ellipsis;
187
+ white-space: nowrap;
188
+ color: var(--us-muted);
189
+ font-size: 0.75rem;
190
+ }
191
+
192
+ .rowEnter {
193
+ grid-column: 2;
194
+ grid-row: 1 / span 2;
195
+ align-self: center;
196
+ color: var(--us-muted);
197
+ }
198
+
199
+ // -- footer ------------------------------------------------------------------
200
+
201
+ .foot {
202
+ display: flex;
203
+ gap: 1rem;
204
+ padding: 0.5rem 0.9rem;
205
+ border-top: 1px solid var(--us-line);
206
+ background: var(--us-quiet);
207
+ color: var(--us-muted);
208
+ font-size: 0.6875rem;
209
+ }
210
+
211
+ .kbd {
212
+ display: inline-flex;
213
+ align-items: center;
214
+ padding: 0 0.3rem;
215
+ min-width: 1.1rem;
216
+ height: 1.1rem;
217
+ border: 1px solid var(--us-line);
218
+ border-radius: 4px;
219
+ background: var(--us-surface);
220
+ color: var(--us-muted);
221
+ font-family: inherit;
222
+ font-size: 0.6875rem;
223
+ line-height: 1;
224
+ }
225
+
226
+ @media (prefers-reduced-motion: reduce) {
227
+ .trigger,
228
+ .row { transition: none; }
229
+ }
@@ -482,6 +482,16 @@ $vault-tick: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewB
482
482
  cursor: pointer;
483
483
  }
484
484
 
485
+ // A select that is currently narrowing the list. Without it a filter picked
486
+ // from a row's chip is a change the user did not make in the toolbar and can
487
+ // easily fail to notice — and then the vault looks half empty.
488
+ .selectActive {
489
+ border-color: color-mix(in srgb, var(--v-primary) 45%, transparent);
490
+ background: var(--v-tint);
491
+ color: var(--v-primary);
492
+ font-weight: 600;
493
+ }
494
+
485
495
  // Every checkbox and radio in the vault carries this, so the box survives
486
496
  // being mounted anywhere — see `vault-check`. The descendant selectors below
487
497
  // repeat it only as a safety net for markup that forgets the class.
@@ -1981,3 +1991,406 @@ input.checkControl {
1981
1991
  font-size: 0.75rem;
1982
1992
  white-space: nowrap;
1983
1993
  }
1994
+
1995
+ // ---------------------------------------------------------------------------
1996
+ // Client attachment
1997
+ //
1998
+ // A vault entry can belong to a client. On the form that is a typeahead; on
1999
+ // the list it is one line under the title, so the association is visible
2000
+ // without opening the entry.
2001
+ // ---------------------------------------------------------------------------
2002
+
2003
+ .clientPicker {
2004
+ position: relative;
2005
+ }
2006
+
2007
+ .clientSpinner {
2008
+ position: absolute;
2009
+ top: 50%;
2010
+ right: 0.55rem;
2011
+ color: var(--v-muted);
2012
+ transform: translateY(-50%);
2013
+ animation: vault-client-spin 0.9s linear infinite;
2014
+ pointer-events: none;
2015
+ }
2016
+
2017
+ @keyframes vault-client-spin {
2018
+ to {
2019
+ transform: translateY(-50%) rotate(360deg);
2020
+ }
2021
+ }
2022
+
2023
+ .clientResults {
2024
+ position: absolute;
2025
+ z-index: 30;
2026
+ top: calc(100% + 4px);
2027
+ right: 0;
2028
+ left: 0;
2029
+ // Roughly seven rows. Enough to scan, short enough that the field below
2030
+ // it is still reachable without scrolling the modal.
2031
+ max-height: 15rem;
2032
+ margin: 0;
2033
+ padding: 0.25rem;
2034
+ overflow-y: auto;
2035
+ border: 1px solid var(--v-line);
2036
+ border-radius: var(--v-btn-br);
2037
+ background: var(--v-background);
2038
+ box-shadow: 0 12px 28px -12px rgb(0 0 0 / 35%);
2039
+ list-style: none;
2040
+ }
2041
+
2042
+ .clientResult,
2043
+ .clientResultActive {
2044
+ @include vault-reset;
2045
+ @include vault-focus;
2046
+
2047
+ display: flex;
2048
+ gap: 0.45rem;
2049
+ align-items: center;
2050
+ width: 100%;
2051
+ padding: 0.4rem 0.5rem;
2052
+ border-radius: var(--v-btn-br);
2053
+ color: var(--v-paragraph);
2054
+ font-size: 0.85rem;
2055
+ text-align: left;
2056
+ cursor: pointer;
2057
+
2058
+ svg {
2059
+ flex-shrink: 0;
2060
+ color: var(--v-muted);
2061
+ }
2062
+ }
2063
+
2064
+ .clientResultActive {
2065
+ background: var(--v-tint);
2066
+ color: var(--v-primary);
2067
+
2068
+ svg {
2069
+ color: currentcolor;
2070
+ }
2071
+ }
2072
+
2073
+ .clientEmpty {
2074
+ padding: 0.5rem;
2075
+ color: var(--v-muted);
2076
+ font-size: 0.82rem;
2077
+ }
2078
+
2079
+ .clientChosen {
2080
+ display: inline-flex;
2081
+ gap: 0.4rem;
2082
+ align-items: center;
2083
+ max-width: 100%;
2084
+ padding: 0.35rem 0.4rem 0.35rem 0.55rem;
2085
+ border: 1px solid var(--v-line);
2086
+ border-radius: 999px;
2087
+ background: var(--v-tint);
2088
+ color: var(--v-primary);
2089
+ font-size: 0.83rem;
2090
+ font-weight: 600;
2091
+ }
2092
+
2093
+ .clientChosenLabel {
2094
+ overflow: hidden;
2095
+ text-overflow: ellipsis;
2096
+ white-space: nowrap;
2097
+ }
2098
+
2099
+ .clientClear {
2100
+ @include vault-reset;
2101
+ @include vault-focus;
2102
+
2103
+ display: inline-flex;
2104
+ flex-shrink: 0;
2105
+ align-items: center;
2106
+ justify-content: center;
2107
+ width: 1.1rem;
2108
+ height: 1.1rem;
2109
+ border-radius: 999px;
2110
+ color: currentcolor;
2111
+ cursor: pointer;
2112
+ opacity: 0.7;
2113
+
2114
+ &:hover {
2115
+ background: color-mix(in srgb, currentcolor 18%, transparent);
2116
+ opacity: 1;
2117
+ }
2118
+ }
2119
+
2120
+ // The client a credential belongs to, beside the title.
2121
+ //
2122
+ // A chip rather than loose grey text. Once the importer's duplicated
2123
+ // "Client — Title" prefix is stripped (vault:strip-client-titles) this is the
2124
+ // ONLY place the client is named on the row, so it has to be legible on its
2125
+ // own — but it must still read as secondary to the title, which is why it
2126
+ // takes the flattest surface in the palette (`--v-surface` is 4% of the body
2127
+ // colour) and the muted text colour rather than a tint of the brand.
2128
+ //
2129
+ // The icon stays: it is what separates this chip at a glance from the tag
2130
+ // pills sitting on the line below, which are the same size and shape.
2131
+ //
2132
+ // Inline, not block: the title cell is `white-space: nowrap` in fitted mode
2133
+ // and the chip is meant to sit on the title's line, with the tags taking the
2134
+ // second. `margin-left` is the whole fix for the run-on look.
2135
+ .clientUnderTitle {
2136
+ display: inline-flex;
2137
+ gap: 0.3rem;
2138
+ align-items: center;
2139
+ max-width: 100%;
2140
+ margin-left: 0.45rem;
2141
+ padding: 0.08rem 0.45rem;
2142
+ border: 1px solid var(--v-line);
2143
+ border-radius: 999px;
2144
+ background: var(--v-surface);
2145
+ color: var(--v-muted);
2146
+ font-size: 0.72rem;
2147
+ font-weight: 600;
2148
+ line-height: 1.5;
2149
+ white-space: nowrap;
2150
+ vertical-align: middle;
2151
+
2152
+ svg {
2153
+ flex-shrink: 0;
2154
+ // The glyph is decoration next to a name that is already the label;
2155
+ // at full strength it competes with the text it introduces.
2156
+ opacity: 0.75;
2157
+ }
2158
+
2159
+ // The name itself. Own ellipsis rather than the cell's: the cell clips
2160
+ // everything after the overflow point, which on a long client name would
2161
+ // take the chip's right-hand border with it and leave a shape that reads
2162
+ // as a rendering fault.
2163
+ .clientChipLabel {
2164
+ display: block;
2165
+ overflow: hidden;
2166
+ text-overflow: ellipsis;
2167
+ white-space: nowrap;
2168
+ // Roughly 22 characters. Past that the client is identified well
2169
+ // enough and the title has the better claim on the row.
2170
+ max-width: 11rem;
2171
+ }
2172
+
2173
+ a {
2174
+ color: inherit;
2175
+ text-decoration: none;
2176
+
2177
+ &:hover,
2178
+ &:focus-visible {
2179
+ text-decoration: underline;
2180
+ }
2181
+ }
2182
+
2183
+ // The name, as the control that filters the list to this client. Reset to
2184
+ // nothing and told to inherit: it has to be indistinguishable from the
2185
+ // plain-text version of the same chip on a page that cannot filter, or the
2186
+ // row grows a button-shaped thing in the middle of a title cell.
2187
+ .clientChipButton {
2188
+ @include vault-reset;
2189
+ @include vault-focus;
2190
+
2191
+ display: block;
2192
+ max-width: 11rem;
2193
+ padding: 0;
2194
+ border: 0;
2195
+ background: none;
2196
+ color: inherit;
2197
+ font: inherit;
2198
+ cursor: pointer;
2199
+
2200
+ &:hover .clientChipLabel,
2201
+ &:focus-visible .clientChipLabel {
2202
+ text-decoration: underline;
2203
+ }
2204
+ }
2205
+
2206
+ // The link to the client's own record, kept alongside the filter button.
2207
+ // Icon-only and quiet: filtering is the common intent from a row, opening
2208
+ // the client is the occasional one.
2209
+ .clientChipLink {
2210
+ display: inline-flex;
2211
+ flex-shrink: 0;
2212
+ align-items: center;
2213
+ opacity: 0.6;
2214
+
2215
+ &:hover,
2216
+ &:focus-visible {
2217
+ opacity: 1;
2218
+ }
2219
+
2220
+ svg {
2221
+ opacity: 1;
2222
+ }
2223
+ }
2224
+
2225
+ // The whole chip lifts on hover, so the target reads as the chip rather
2226
+ // than as the few pixels of text inside it.
2227
+ &:has(a:hover),
2228
+ &:has(a:focus-visible),
2229
+ &:has(button:hover),
2230
+ &:has(button:focus-visible) {
2231
+ border-color: color-mix(in srgb, var(--v-primary) 35%, transparent);
2232
+ background: var(--v-tint);
2233
+ color: var(--v-primary);
2234
+ }
2235
+ }
2236
+
2237
+ // The client this list is currently filtered to. Every visible row carries the
2238
+ // same chip once the filter is on, so this is not a highlight so much as a
2239
+ // statement of why these rows are the ones on screen.
2240
+ .clientUnderTitleActive {
2241
+ border-color: color-mix(in srgb, var(--v-primary) 45%, transparent);
2242
+ background: var(--v-tint);
2243
+ color: var(--v-primary);
2244
+ }
2245
+
2246
+ // ---------------------------------------------------------------------------
2247
+ // Share links
2248
+ //
2249
+ // The dialog behind the Share action: choose what to send, get a URL once, and
2250
+ // see what has already gone out. Everything here lives inside `.form`, which
2251
+ // already carries the token block, so these rules only add what the form's own
2252
+ // controls do not.
2253
+ // ---------------------------------------------------------------------------
2254
+
2255
+ .shareFields {
2256
+ display: flex;
2257
+ flex-direction: column;
2258
+ gap: 0.35rem;
2259
+
2260
+ // The hint under a field name — "the current code, not the secret", or the
2261
+ // reason a box is greyed out. Own line, so a long one does not push the
2262
+ // checkbox off centre.
2263
+ .shareHint {
2264
+ display: block;
2265
+ color: var(--v-muted);
2266
+ font-size: 0.72rem;
2267
+ font-style: normal;
2268
+ }
2269
+ }
2270
+
2271
+ // The one-time URL, and the sentence that makes it clear it IS one time.
2272
+ .shareResult {
2273
+ display: flex;
2274
+ flex-direction: column;
2275
+ gap: 0.7rem;
2276
+ padding: 0.85rem;
2277
+ border: 1px solid color-mix(in srgb, var(--v-primary) 35%, transparent);
2278
+ border-radius: var(--v-br);
2279
+ background: var(--v-tint);
2280
+ }
2281
+
2282
+ .shareResultHead {
2283
+ display: flex;
2284
+ gap: 0.35rem;
2285
+ align-items: center;
2286
+ margin: 0;
2287
+ color: var(--v-primary);
2288
+ font-size: 0.82rem;
2289
+ font-weight: 700;
2290
+ }
2291
+
2292
+ // Deliberately loud. The link is the credential, and the two things a person
2293
+ // has to take from this dialog — it will not be shown again, and anyone with
2294
+ // it can read the password — are both easy to skim past in body text.
2295
+ .shareWarning {
2296
+ display: flex;
2297
+ gap: 0.45rem;
2298
+ align-items: flex-start;
2299
+ margin: 0;
2300
+ color: var(--v-paragraph);
2301
+ font-size: 0.78rem;
2302
+ line-height: 1.45;
2303
+
2304
+ svg {
2305
+ flex-shrink: 0;
2306
+ margin-top: 0.15rem;
2307
+ color: var(--v-bad);
2308
+ }
2309
+
2310
+ strong {
2311
+ font-weight: 700;
2312
+ }
2313
+ }
2314
+
2315
+ .shareList {
2316
+ display: flex;
2317
+ flex-direction: column;
2318
+ gap: 0.4rem;
2319
+ padding-top: 0.7rem;
2320
+ border-top: 1px solid var(--v-line);
2321
+
2322
+ > p:first-child {
2323
+ display: flex;
2324
+ gap: 0.35rem;
2325
+ align-items: center;
2326
+ margin: 0 0 0.2rem;
2327
+ }
2328
+ }
2329
+
2330
+ .shareRow {
2331
+ display: flex;
2332
+ flex-wrap: wrap;
2333
+ gap: 0.4rem 0.6rem;
2334
+ align-items: center;
2335
+ padding: 0.5rem 0.6rem;
2336
+ border: 1px solid var(--v-line);
2337
+ border-radius: var(--v-br);
2338
+ background: var(--v-surface);
2339
+ font-size: 0.79rem;
2340
+
2341
+ // The revoke button is pushed to the far end rather than floated, so a row
2342
+ // without one simply has nothing there instead of a gap in the middle.
2343
+ > button:last-child {
2344
+ margin-left: auto;
2345
+ }
2346
+ }
2347
+
2348
+ .shareRowMain {
2349
+ display: flex;
2350
+ flex-wrap: wrap;
2351
+ gap: 0.4rem;
2352
+ align-items: center;
2353
+ min-width: 0;
2354
+ }
2355
+
2356
+ .shareRowFields {
2357
+ overflow: hidden;
2358
+ color: var(--v-paragraph);
2359
+ font-weight: 600;
2360
+ text-overflow: ellipsis;
2361
+ white-space: nowrap;
2362
+ }
2363
+
2364
+ .shareRowMeta {
2365
+ display: flex;
2366
+ flex-wrap: wrap;
2367
+ gap: 0.15rem 0.5rem;
2368
+ align-items: center;
2369
+ color: var(--v-muted);
2370
+ font-size: 0.74rem;
2371
+
2372
+ // Interpuncts between the facts rather than commas: they are three
2373
+ // independent readings, not a list.
2374
+ > span + span::before {
2375
+ content: '·';
2376
+ margin-right: 0.5rem;
2377
+ }
2378
+ }
2379
+
2380
+ // Green for a link that still opens, red for one somebody deliberately closed,
2381
+ // neutral grey for one that simply ran out — an expired link is not an
2382
+ // incident, and colouring it as one trains people to ignore the colour.
2383
+ .shareStatusActive {
2384
+ background: color-mix(in srgb, var(--v-ok) 15%, var(--v-background));
2385
+ color: var(--v-ok);
2386
+ }
2387
+
2388
+ .shareStatusRevoked {
2389
+ background: color-mix(in srgb, var(--v-bad) 15%, var(--v-background));
2390
+ color: var(--v-bad);
2391
+ }
2392
+
2393
+ .shareStatusClosed {
2394
+ background: color-mix(in srgb, var(--v-paragraph) 12%, var(--v-background));
2395
+ color: var(--v-muted);
2396
+ }