@visns-studio/visns-components 6.20.1 → 6.23.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 +1 -1
- package/src/components/DataGrid.jsx +15 -2
- package/src/components/Navigation.jsx +10 -1
- package/src/components/columns/SelectFilterEditor.jsx +372 -0
- package/src/components/controls/DataGridSearch.jsx +6 -1
- package/src/components/layout/AnchoredMenu.jsx +131 -0
- package/src/components/layout/BandedList.jsx +158 -0
- package/src/components/layout/FactsBand.jsx +150 -0
- package/src/components/layout/PageHeader.jsx +90 -0
- package/src/components/layout/ScrollRegion.jsx +53 -0
- package/src/components/layout/StatStrip.jsx +170 -0
- package/src/components/layout/useContainedHeight.js +82 -0
- package/src/components/navigation/TabStrip.jsx +156 -0
- package/src/components/phone/ZoomPhoneBadge.jsx +625 -0
- package/src/components/phone/phonePresenceEndpoints.js +34 -0
- package/src/components/phone/phonePresenceHelpers.js +298 -0
- package/src/components/phone/useZoomPhoneLive.js +165 -0
- package/src/components/sms/SmsClientConversations.jsx +8 -0
- package/src/components/sms/SmsInbox.jsx +11 -0
- package/src/components/sms/SmsThreadPanel.jsx +258 -29
- package/src/components/sms/smsHelpers.js +95 -0
- package/src/components/styles/AnchoredMenu.module.scss +43 -0
- package/src/components/styles/BandedList.module.scss +278 -0
- package/src/components/styles/DataGrid.module.scss +10 -0
- package/src/components/styles/FactsBand.module.scss +108 -0
- package/src/components/styles/GenericDetail.module.scss +12 -2
- package/src/components/styles/PageHeader.module.scss +98 -0
- package/src/components/styles/ScrollRegion.module.scss +20 -0
- package/src/components/styles/Sms.module.scss +131 -0
- package/src/components/styles/StatStrip.module.scss +183 -0
- package/src/components/styles/TabStrip.module.scss +96 -0
- package/src/components/styles/Vault.module.scss +46 -0
- package/src/components/styles/ZoomPhone.module.scss +550 -0
- package/src/components/styles/_surface.scss +49 -0
- package/src/components/styles/global-datagrid.css +23 -0
- package/src/components/vault/VaultAccessLog.jsx +24 -0
- package/src/components/vault/VaultShareModal.jsx +126 -5
- package/src/components/vault/vaultShares.js +67 -3
- package/src/index.js +88 -2
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
@use 'surface' as *;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BandedList — sections with a heading, a count, and an urgency on the edge.
|
|
5
|
+
*
|
|
6
|
+
* THE ONE IDEA THIS FILE CARRIES is that urgency is a property of the SECTION,
|
|
7
|
+
* not of the row. Each band gets a coloured left edge and a heading that says
|
|
8
|
+
* what its contents mean; the rows inside stay quiet and uniform. The
|
|
9
|
+
* alternative — colouring every overdue row red — turns a book with forty lapsed
|
|
10
|
+
* renewals into a wall of alarm nobody can read.
|
|
11
|
+
*
|
|
12
|
+
* The rows themselves are the host's. This draws the scaffolding and nothing
|
|
13
|
+
* else: a support block card, a subscription line and a client row have almost
|
|
14
|
+
* nothing in common, and a component that tried to render all three would be a
|
|
15
|
+
* config format rather than a primitive.
|
|
16
|
+
*
|
|
17
|
+
* Three variants, one per shape the CRM's screens actually draw:
|
|
18
|
+
*
|
|
19
|
+
* `card` a card per band, urgency on its left border. For bands that are
|
|
20
|
+
* independent of each other — this week's renewals and last month's.
|
|
21
|
+
* `plain` no card at all: a bare heading over whatever follows. For a page
|
|
22
|
+
* whose rows are already cards and would otherwise be cards inside
|
|
23
|
+
* cards.
|
|
24
|
+
* `flush` no card of its own, sitting inside ONE shared card with its
|
|
25
|
+
* siblings, heading sticky. For a continuous list broken up by
|
|
26
|
+
* dividers — the A–Z tabs in a filing drawer.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/* -------------------------------------------------------------------------- */
|
|
30
|
+
/* the container */
|
|
31
|
+
/* -------------------------------------------------------------------------- */
|
|
32
|
+
|
|
33
|
+
.board {
|
|
34
|
+
@include surface-tokens;
|
|
35
|
+
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
gap: var(--spacing-md, 1rem);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ONE card holding every band, rather than a card per band — the container for
|
|
42
|
+
`flush`. A drawer of clients is one drawer; breaking it into twenty-six panels
|
|
43
|
+
would put a shadow and a radius between D and E for no reason. */
|
|
44
|
+
.boardCard {
|
|
45
|
+
display: block;
|
|
46
|
+
background: var(--pr-surface);
|
|
47
|
+
border: 1px solid var(--pr-line);
|
|
48
|
+
border-radius: var(--pr-radius);
|
|
49
|
+
box-shadow: var(--pr-shadow);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* -------------------------------------------------------------------------- */
|
|
53
|
+
/* contained scrolling */
|
|
54
|
+
/* -------------------------------------------------------------------------- */
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The rows scroll, the page does not.
|
|
58
|
+
*
|
|
59
|
+
* A list of four hundred clients scrolls the whole document, which takes the
|
|
60
|
+
* summary strip, the search box and the view tabs off the screen — so finding a
|
|
61
|
+
* name and then filtering it means scrolling back up to controls that used to be
|
|
62
|
+
* in front of you. The rows get their own scroll region instead and the chrome
|
|
63
|
+
* above them stays put.
|
|
64
|
+
*
|
|
65
|
+
* The height is set INLINE by `useContainedHeight`, measured from where the
|
|
66
|
+
* region actually starts rather than guessed from a stack of constants — the
|
|
67
|
+
* chrome above it changes height (a summary that has not loaded, a truncation
|
|
68
|
+
* notice that appears at a hundred rows) and a hard-coded offset is wrong on
|
|
69
|
+
* half the states of the page.
|
|
70
|
+
*
|
|
71
|
+
* `overscroll-behavior` is deliberately LEFT ALONE. Trapping the scroll at the
|
|
72
|
+
* end of the list is the behaviour that makes a long page feel broken on an
|
|
73
|
+
* iPad; when this region is done scrolling, the page should carry on.
|
|
74
|
+
*/
|
|
75
|
+
.scroll {
|
|
76
|
+
overflow: auto;
|
|
77
|
+
/* Narrow screens scroll their overflow HERE rather than tipping the whole
|
|
78
|
+
document sideways. */
|
|
79
|
+
overflow-x: auto;
|
|
80
|
+
-webkit-overflow-scrolling: touch;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* -------------------------------------------------------------------------- */
|
|
84
|
+
/* one band */
|
|
85
|
+
/* -------------------------------------------------------------------------- */
|
|
86
|
+
|
|
87
|
+
.band {
|
|
88
|
+
@include surface-tokens;
|
|
89
|
+
|
|
90
|
+
display: block;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* NOT `overflow: hidden`, however much the rounded corners want it.
|
|
94
|
+
*
|
|
95
|
+
* A row inside a band carries a `⋯` menu, and a clipping ancestor eats it — on
|
|
96
|
+
* the last row of a band the popover was cut off entirely and the menu looked
|
|
97
|
+
* like a button that did nothing. Corners are handled by rounding the heading
|
|
98
|
+
* and the last row instead (see `.head` below, and the host's own last-row
|
|
99
|
+
* rule), which costs two rules and keeps every popover visible. */
|
|
100
|
+
.card {
|
|
101
|
+
background: var(--pr-surface);
|
|
102
|
+
border: 1px solid var(--pr-line);
|
|
103
|
+
/* Drawn at full width in the line colour and recoloured by the tone, so a
|
|
104
|
+
band with no urgency is not 3px narrower than one that has some. */
|
|
105
|
+
border-left: 3px solid var(--pr-line);
|
|
106
|
+
border-radius: var(--pr-radius);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.plain {
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
gap: 0.6rem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.toneDanger.card {
|
|
116
|
+
border-left-color: var(--pr-danger);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.toneWarn.card {
|
|
120
|
+
border-left-color: var(--pr-warn);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.toneNeutral.card {
|
|
124
|
+
border-left-color: var(--pr-accent);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.toneQuiet.card {
|
|
128
|
+
border-left-color: color-mix(in srgb, var(--pr-ink) 18%, transparent);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* -------------------------------------------------------------------------- */
|
|
132
|
+
/* the heading */
|
|
133
|
+
/* -------------------------------------------------------------------------- */
|
|
134
|
+
|
|
135
|
+
.head {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-wrap: wrap;
|
|
138
|
+
gap: 0.5rem;
|
|
139
|
+
align-items: baseline;
|
|
140
|
+
justify-content: space-between;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.card > .head,
|
|
144
|
+
.flush > .head {
|
|
145
|
+
padding: 0.7rem var(--spacing-md, 1rem);
|
|
146
|
+
background: var(--pr-quiet);
|
|
147
|
+
border-bottom: 1px solid var(--pr-line);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* The corner the card no longer clips for itself. Inset by the border width, so
|
|
151
|
+
the tinted heading meets the border rather than showing a hairline of page
|
|
152
|
+
behind it. */
|
|
153
|
+
.card > .head {
|
|
154
|
+
border-radius: calc(var(--pr-radius) - 1px) calc(var(--pr-radius) - 1px) 0 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.boardCard > .flush:first-child > .head {
|
|
158
|
+
border-radius: calc(var(--pr-radius) - 1px) calc(var(--pr-radius) - 1px) 0 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Flush bands share one card, so the divider between two of them is a rule on
|
|
162
|
+
the second one's heading — there is no card edge to do it for them. */
|
|
163
|
+
.flush + .flush > .head {
|
|
164
|
+
border-top: 1px solid var(--pr-line);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* A `plain` heading is type on the page ground: no slab, no rule, and the
|
|
168
|
+
right-hand note pushed out by the count rather than by a space-between that
|
|
169
|
+
has nothing to push against. */
|
|
170
|
+
.plain > .head {
|
|
171
|
+
gap: 0.5rem;
|
|
172
|
+
padding: 0 0.25rem;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* Sticky is opt-in, and it is the whole point of a flush band: the heading's
|
|
176
|
+
job is to tell you where you are once you have scrolled past the top of its
|
|
177
|
+
group. */
|
|
178
|
+
.headSticky {
|
|
179
|
+
position: sticky;
|
|
180
|
+
top: 0;
|
|
181
|
+
z-index: 1;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* A sticky heading has to be OPAQUE or the rows read straight through it, and
|
|
185
|
+
`--pr-quiet` is usually a translucent mix — it is a tint meant to be laid over
|
|
186
|
+
a surface, not a colour. Layering it over `--pr-surface` as two backgrounds
|
|
187
|
+
composites the same shade with nothing showing through. */
|
|
188
|
+
.card > .headSticky,
|
|
189
|
+
.flush > .headSticky {
|
|
190
|
+
background:
|
|
191
|
+
linear-gradient(var(--pr-quiet), var(--pr-quiet)),
|
|
192
|
+
var(--pr-surface);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* A plain heading has no slab of its own, so it borrows the PAGE ground — not
|
|
196
|
+
the card surface, which would draw a white bar across a warm page. */
|
|
197
|
+
.plain > .headSticky {
|
|
198
|
+
padding: 0.35rem 0.25rem;
|
|
199
|
+
background: var(--pr-page);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.flush > .head {
|
|
203
|
+
padding: 0.4rem var(--spacing-md, 1rem);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.title {
|
|
207
|
+
display: flex;
|
|
208
|
+
gap: 0.5rem;
|
|
209
|
+
align-items: center;
|
|
210
|
+
margin: 0;
|
|
211
|
+
color: var(--pr-heading);
|
|
212
|
+
font-size: 0.95rem;
|
|
213
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* A plain heading is small caps on the page ground, and it takes the page's own
|
|
217
|
+
ink rather than the heading colour — a run of five of them in the brand green
|
|
218
|
+
reads as five separate pages. */
|
|
219
|
+
.plain > .head > .title {
|
|
220
|
+
gap: 0.5rem;
|
|
221
|
+
align-items: baseline;
|
|
222
|
+
color: inherit;
|
|
223
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
224
|
+
font-weight: 700;
|
|
225
|
+
letter-spacing: 0.02em;
|
|
226
|
+
text-transform: uppercase;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.flush > .head > .title {
|
|
230
|
+
align-items: baseline;
|
|
231
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
232
|
+
font-weight: 700;
|
|
233
|
+
letter-spacing: 0.04em;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* A pill in a card heading, a bare number everywhere else — a pill on the page
|
|
237
|
+
ground reads as a badge that means something, and it does not. */
|
|
238
|
+
.count {
|
|
239
|
+
padding: 0 0.45rem;
|
|
240
|
+
color: var(--pr-muted);
|
|
241
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
242
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
243
|
+
font-variant-numeric: tabular-nums;
|
|
244
|
+
background: var(--pr-surface);
|
|
245
|
+
border: 1px solid var(--pr-line);
|
|
246
|
+
border-radius: var(--radius-pill, 999px);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.plain > .head > .title > .count,
|
|
250
|
+
.flush > .head > .title > .count {
|
|
251
|
+
padding: 0;
|
|
252
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
253
|
+
font-weight: 400;
|
|
254
|
+
background: none;
|
|
255
|
+
border: 0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.flush > .head > .title > .count {
|
|
259
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* Whatever the band wants to say about itself on the right — a subtotal, a
|
|
263
|
+
sentence explaining what its dates mean. */
|
|
264
|
+
.meta {
|
|
265
|
+
display: flex;
|
|
266
|
+
gap: 0.6rem;
|
|
267
|
+
align-items: baseline;
|
|
268
|
+
color: var(--pr-muted);
|
|
269
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.plain > .head > .meta {
|
|
273
|
+
margin-left: auto;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.note {
|
|
277
|
+
color: var(--pr-muted);
|
|
278
|
+
}
|
|
@@ -697,6 +697,16 @@
|
|
|
697
697
|
width: 100%;
|
|
698
698
|
font-size: 0.9rem;
|
|
699
699
|
box-sizing: border-box;
|
|
700
|
+
// The input is type="search" (to sidestep host apps' global text-input
|
|
701
|
+
// rules), so strip WebKit's search decorations to keep it looking like
|
|
702
|
+
// the plain field it always was.
|
|
703
|
+
appearance: none;
|
|
704
|
+
-webkit-appearance: none;
|
|
705
|
+
|
|
706
|
+
&::-webkit-search-decoration,
|
|
707
|
+
&::-webkit-search-cancel-button {
|
|
708
|
+
-webkit-appearance: none;
|
|
709
|
+
}
|
|
700
710
|
|
|
701
711
|
&:focus {
|
|
702
712
|
outline: none;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
@use 'surface' as *;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FactsBand — the persistent strip of key facts under a record's title.
|
|
5
|
+
*
|
|
6
|
+
* The same hairline grid StatStrip draws in its `joined` variant, at a tighter
|
|
7
|
+
* density and carrying two kinds of cell rather than one: a FIGURE (a count,
|
|
8
|
+
* read at a glance) and a VALUE (a name, an address, a phone number — read as
|
|
9
|
+
* text, and clipped rather than wrapped, because the band's whole job is to keep
|
|
10
|
+
* a fixed height while the body under it changes).
|
|
11
|
+
*
|
|
12
|
+
* It is a separate module from StatStrip on purpose. A stat strip describes a
|
|
13
|
+
* SET and sits above a list; a facts band describes ONE RECORD and sits under
|
|
14
|
+
* its title, where it has to survive the tabs beneath it being switched. Merging
|
|
15
|
+
* them would mean one component with a density prop that changed what its slots
|
|
16
|
+
* meant, which is the kind of parameter that quietly grows a third value.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
.band {
|
|
20
|
+
@include surface-tokens;
|
|
21
|
+
|
|
22
|
+
display: grid;
|
|
23
|
+
grid-template-columns: var(
|
|
24
|
+
--vs-facts-columns,
|
|
25
|
+
repeat(auto-fit, minmax(var(--vs-facts-min, 11rem), 1fr))
|
|
26
|
+
);
|
|
27
|
+
gap: 1px;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
30
|
+
background: var(--pr-line);
|
|
31
|
+
border: 1px solid var(--pr-line);
|
|
32
|
+
border-radius: var(--pr-radius);
|
|
33
|
+
box-shadow: var(--pr-shadow);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.cell {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
gap: 0.15rem;
|
|
40
|
+
min-width: 0;
|
|
41
|
+
padding: 0.7rem var(--spacing-md, 1rem);
|
|
42
|
+
background: var(--pr-surface);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.label {
|
|
46
|
+
color: var(--pr-muted);
|
|
47
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
48
|
+
font-weight: 600;
|
|
49
|
+
letter-spacing: 0.02em;
|
|
50
|
+
text-transform: uppercase;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Text, clipped. A band that wraps is a band whose height moves, and the tab
|
|
54
|
+
strip under it moves with it every time a record loads. */
|
|
55
|
+
.value {
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
color: var(--pr-heading);
|
|
58
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
59
|
+
text-decoration: none;
|
|
60
|
+
text-overflow: ellipsis;
|
|
61
|
+
white-space: nowrap;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* `href` turns the value into a `tel:` / `mailto:`, so it has to be obviously
|
|
65
|
+
tappable without looking like a stray link in a header. */
|
|
66
|
+
a.value:hover {
|
|
67
|
+
text-decoration: underline;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* The counts read as figures rather than sentences, so a glance across the band
|
|
71
|
+
lands on the numbers. */
|
|
72
|
+
.figure {
|
|
73
|
+
font-size: 1.2rem;
|
|
74
|
+
font-weight: 700;
|
|
75
|
+
font-variant-numeric: tabular-nums;
|
|
76
|
+
line-height: 1.2;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.note {
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
color: var(--pr-muted);
|
|
82
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
83
|
+
text-overflow: ellipsis;
|
|
84
|
+
white-space: nowrap;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.toneAccent {
|
|
88
|
+
color: var(--pr-accent);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.toneWarn {
|
|
92
|
+
color: var(--pr-warn);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.toneDanger {
|
|
96
|
+
color: var(--pr-danger);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* The band is one row of a card, so a failure to load must not leave a hole in
|
|
100
|
+
the page where the record's facts were. Full width, same ground, one
|
|
101
|
+
sentence. */
|
|
102
|
+
.state {
|
|
103
|
+
grid-column: 1 / -1;
|
|
104
|
+
padding: 0.7rem var(--spacing-md, 1rem);
|
|
105
|
+
color: var(--pr-muted);
|
|
106
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
107
|
+
background: var(--pr-surface);
|
|
108
|
+
}
|
|
@@ -1362,8 +1362,12 @@
|
|
|
1362
1362
|
/* It stays pinned bottom-right — every detail page in every project is laid
|
|
1363
1363
|
out around that — but it had no ground of its own, so on a short page the
|
|
1364
1364
|
buttons floated over the page background with nothing behind them. A pill
|
|
1365
|
-
with the same elevation as the panels gives them somewhere to sit.
|
|
1366
|
-
|
|
1365
|
+
with the same elevation as the panels gives them somewhere to sit.
|
|
1366
|
+
|
|
1367
|
+
ONLY when there is more than one of them: a tray around a single solid
|
|
1368
|
+
button reads as a white oval drawn around it, not as ground. A lone
|
|
1369
|
+
button carries its own elevation instead. */
|
|
1370
|
+
.polActions:has(> :nth-child(2)) {
|
|
1367
1371
|
padding: 0.4rem;
|
|
1368
1372
|
border: 1px solid var(--gd-line);
|
|
1369
1373
|
border-radius: var(--radius-pill, 999px);
|
|
@@ -1374,6 +1378,12 @@
|
|
|
1374
1378
|
0 16px 32px -14px color-mix(in srgb, var(--primary-color, #1b3933) 30%, transparent);
|
|
1375
1379
|
}
|
|
1376
1380
|
|
|
1381
|
+
.polActions:not(:has(> :nth-child(2))) > * {
|
|
1382
|
+
box-shadow:
|
|
1383
|
+
0 2px 4px color-mix(in srgb, var(--primary-color, #1b3933) 12%, transparent),
|
|
1384
|
+
0 12px 24px -12px color-mix(in srgb, var(--primary-color, #1b3933) 35%, transparent);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1377
1387
|
/* -- states --------------------------------------------------------------- */
|
|
1378
1388
|
|
|
1379
1389
|
.noDataMessage {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
@use 'surface' as *;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PageHeader — the identity row at the top of a screen.
|
|
5
|
+
*
|
|
6
|
+
* Whatever names the page on the left, whatever acts on it hard right, and
|
|
7
|
+
* nothing behind either: the shape GenericDetail's `.crmtitle` draws, restated
|
|
8
|
+
* as a component so the six screens that had each written it out by hand stop
|
|
9
|
+
* drifting apart by a tenth of a rem at a time.
|
|
10
|
+
*
|
|
11
|
+
* The left half is deliberately a slot. Some of these pages lead with a
|
|
12
|
+
* breadcrumb, some with a back link over a title, some with a title and a line
|
|
13
|
+
* of explanation — and the component's job is the ROW, not a decision about
|
|
14
|
+
* which of those a page should use.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
.header {
|
|
18
|
+
@include surface-tokens;
|
|
19
|
+
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-wrap: wrap;
|
|
22
|
+
gap: var(--spacing-md, 1rem);
|
|
23
|
+
justify-content: space-between;
|
|
24
|
+
|
|
25
|
+
/* Breadcrumb wraps its trail in a <nav>; without this the flex item takes
|
|
26
|
+
its content's minimum width and pushes the actions off the row. */
|
|
27
|
+
nav {
|
|
28
|
+
min-width: 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.alignCenter {
|
|
33
|
+
align-items: center;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.alignStart {
|
|
37
|
+
align-items: flex-start;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.alignEnd {
|
|
41
|
+
align-items: flex-end;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* The quarter-rem of air the breadcrumb-led screens sit in, so the trail is not
|
|
45
|
+
flush against the page gutter. Opt-in: the title-led screens do not want it. */
|
|
46
|
+
.gutter {
|
|
47
|
+
padding: 0 0.25rem 0.25rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Opt-in, and it needs a ground of its own — a transparent sticky header lets
|
|
51
|
+
the rows scroll through the type underneath it. */
|
|
52
|
+
.sticky {
|
|
53
|
+
position: sticky;
|
|
54
|
+
top: 0;
|
|
55
|
+
z-index: 2;
|
|
56
|
+
background: var(--pr-surface);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.main {
|
|
60
|
+
min-width: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.title {
|
|
64
|
+
margin: 0;
|
|
65
|
+
padding: 0;
|
|
66
|
+
color: var(--pr-heading);
|
|
67
|
+
font-size: var(--vs-header-title-size, var(--page-header-title-size, 1.25rem));
|
|
68
|
+
font-weight: var(--font-weight-bold, 700);
|
|
69
|
+
line-height: 1.2;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.subtitle {
|
|
73
|
+
margin: 0.2rem 0 0;
|
|
74
|
+
/* Wide enough for a sentence, narrow enough that it stays one. */
|
|
75
|
+
max-width: 62ch;
|
|
76
|
+
color: var(--pr-muted);
|
|
77
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.actions {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-wrap: wrap;
|
|
83
|
+
gap: 0.5rem;
|
|
84
|
+
align-items: center;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* -------------------------------------------------------------------------- */
|
|
88
|
+
/* narrow */
|
|
89
|
+
/* -------------------------------------------------------------------------- */
|
|
90
|
+
|
|
91
|
+
/* The tablet break. Actions under the title rather than squeezed beside it —
|
|
92
|
+
this app is driven on an iPad in portrait as much as at a desk. */
|
|
93
|
+
@media (max-width: 820px) {
|
|
94
|
+
.header {
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
align-items: stretch;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScrollRegion — the list scrolls, the page stays.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately almost empty. The height is inline (measured, see
|
|
5
|
+
* `useContainedHeight`) and the region must not impose a background, a border or
|
|
6
|
+
* a radius: it is a scroll box around whatever the host already drew, and giving
|
|
7
|
+
* it a surface of its own would put a second card edge around a card.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.region {
|
|
11
|
+
overflow: auto;
|
|
12
|
+
/* Narrow screens scroll their overflow HERE rather than tipping the whole
|
|
13
|
+
document sideways. */
|
|
14
|
+
overflow-x: auto;
|
|
15
|
+
/* Momentum on iOS Safari. A no-op everywhere it is not needed. */
|
|
16
|
+
-webkit-overflow-scrolling: touch;
|
|
17
|
+
/* `overscroll-behavior` is left at its default on purpose: chaining to the
|
|
18
|
+
page when the list is finished is the native feel, and containing it is
|
|
19
|
+
what makes a long page seem stuck. */
|
|
20
|
+
}
|
|
@@ -234,6 +234,15 @@ $sms-mono: 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
// A ghost button that is currently ON — the Select toggle while the timeline is
|
|
238
|
+
// in picking mode. Tinted rather than filled: it is a mode, not the pane's
|
|
239
|
+
// primary action.
|
|
240
|
+
.ghostButtonOn {
|
|
241
|
+
border-color: var(--s-primary);
|
|
242
|
+
background: var(--s-tint);
|
|
243
|
+
color: var(--s-primary);
|
|
244
|
+
}
|
|
245
|
+
|
|
237
246
|
.iconButton {
|
|
238
247
|
@include sms-reset;
|
|
239
248
|
@include sms-focus;
|
|
@@ -1521,6 +1530,101 @@ $sms-mono: 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
|
|
|
1521
1530
|
font-weight: 600;
|
|
1522
1531
|
}
|
|
1523
1532
|
|
|
1533
|
+
// --------------------------------------------------- picking and annotations
|
|
1534
|
+
//
|
|
1535
|
+
// Both are off unless the host asked for them: `.pickRow` only ever wraps a
|
|
1536
|
+
// bubble while the panel is in Select mode, and `.annotations` renders only
|
|
1537
|
+
// where the host supplied chips.
|
|
1538
|
+
|
|
1539
|
+
// The whole row is the hit target while picking. The checkbox sits on the
|
|
1540
|
+
// bubble's own side of the pane so the ticks line up down one margin rather
|
|
1541
|
+
// than zig-zagging with the conversation.
|
|
1542
|
+
.pickRow {
|
|
1543
|
+
display: flex;
|
|
1544
|
+
gap: 0.5rem;
|
|
1545
|
+
align-items: center;
|
|
1546
|
+
max-width: 100%;
|
|
1547
|
+
padding: 0.15rem 0.25rem;
|
|
1548
|
+
border: 1px solid transparent;
|
|
1549
|
+
border-radius: 14px;
|
|
1550
|
+
cursor: pointer;
|
|
1551
|
+
|
|
1552
|
+
&:hover {
|
|
1553
|
+
background: var(--s-hover);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
.bubbleOut .pickRow {
|
|
1558
|
+
flex-direction: row-reverse;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
.pickRowOn {
|
|
1562
|
+
border-color: color-mix(in srgb, var(--s-primary) 45%, transparent);
|
|
1563
|
+
background: var(--s-tint);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// A message that has not reached the server yet — nothing to file it against.
|
|
1567
|
+
.pickRowOff {
|
|
1568
|
+
opacity: 0.5;
|
|
1569
|
+
cursor: default;
|
|
1570
|
+
|
|
1571
|
+
&:hover {
|
|
1572
|
+
background: none;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
.pickBox {
|
|
1577
|
+
flex-shrink: 0;
|
|
1578
|
+
width: 15px;
|
|
1579
|
+
height: 15px;
|
|
1580
|
+
margin: 0;
|
|
1581
|
+
accent-color: var(--s-primary);
|
|
1582
|
+
cursor: inherit;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// Host-supplied chips under a bubble, on the bubble's own side.
|
|
1586
|
+
.annotations {
|
|
1587
|
+
display: flex;
|
|
1588
|
+
flex-wrap: wrap;
|
|
1589
|
+
gap: 0.25rem;
|
|
1590
|
+
max-width: min(60ch, 64%);
|
|
1591
|
+
margin-top: 0.25rem;
|
|
1592
|
+
padding: 0 0.25rem;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
.bubbleOut .annotations {
|
|
1596
|
+
justify-content: flex-end;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
.annotation {
|
|
1600
|
+
@include sms-reset;
|
|
1601
|
+
|
|
1602
|
+
display: inline-flex;
|
|
1603
|
+
gap: 0.25rem;
|
|
1604
|
+
align-items: center;
|
|
1605
|
+
max-width: 100%;
|
|
1606
|
+
padding: 0.1rem 0.5rem;
|
|
1607
|
+
border: 1px solid color-mix(in srgb, var(--s-primary) 30%, transparent);
|
|
1608
|
+
border-radius: 999px;
|
|
1609
|
+
background: var(--s-tint);
|
|
1610
|
+
color: var(--s-primary);
|
|
1611
|
+
font-size: 0.7rem;
|
|
1612
|
+
font-weight: 600;
|
|
1613
|
+
text-align: left;
|
|
1614
|
+
overflow-wrap: anywhere;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
.annotationAction {
|
|
1618
|
+
@include sms-focus;
|
|
1619
|
+
|
|
1620
|
+
cursor: pointer;
|
|
1621
|
+
|
|
1622
|
+
&:hover {
|
|
1623
|
+
border-color: var(--s-primary);
|
|
1624
|
+
background: color-mix(in srgb, var(--s-primary) 16%, var(--s-background));
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1524
1628
|
.retryButton {
|
|
1525
1629
|
@include sms-reset;
|
|
1526
1630
|
@include sms-focus;
|
|
@@ -1599,6 +1703,33 @@ $sms-mono: 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
|
|
|
1599
1703
|
background: var(--s-surface);
|
|
1600
1704
|
}
|
|
1601
1705
|
|
|
1706
|
+
// Stands exactly where the composer does while the timeline is being picked
|
|
1707
|
+
// over, so the pane keeps its height and its one footer.
|
|
1708
|
+
.selectBar {
|
|
1709
|
+
display: flex;
|
|
1710
|
+
flex-wrap: wrap;
|
|
1711
|
+
gap: 0.5rem 0.75rem;
|
|
1712
|
+
align-items: center;
|
|
1713
|
+
justify-content: space-between;
|
|
1714
|
+
box-sizing: border-box;
|
|
1715
|
+
padding: 0.6rem 0.7rem 0.7rem;
|
|
1716
|
+
border-top: 1px solid var(--s-line);
|
|
1717
|
+
background: var(--s-surface);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
.selectCount {
|
|
1721
|
+
color: var(--s-paragraph);
|
|
1722
|
+
font-size: 0.8rem;
|
|
1723
|
+
font-weight: 600;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
.selectActions {
|
|
1727
|
+
display: inline-flex;
|
|
1728
|
+
gap: 0.5rem;
|
|
1729
|
+
align-items: center;
|
|
1730
|
+
margin-left: auto;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1602
1733
|
// One card: the text on top, every control in a bar along its bottom edge.
|
|
1603
1734
|
// Buttons beside a textarea line up with nothing once the box grows; a bar
|
|
1604
1735
|
// inside the same border always reads as part of the message being written.
|