chats 0.4.2 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +247 -0
- data/README.md +95 -8
- data/app/assets/stylesheets/chats.css +596 -0
- data/app/controllers/chats/conversations_controller.rb +71 -5
- data/app/helpers/chats/engine_helper.rb +75 -2
- data/app/javascript/chats/composer_controller.js +14 -1
- data/app/javascript/chats/dock_controller.js +1070 -0
- data/app/javascript/chats/thread_controller.js +72 -6
- data/app/views/chats/conversations/_conversation_row.html.erb +13 -2
- data/app/views/chats/conversations/_group.html.erb +6 -2
- data/app/views/chats/conversations/_locked_composer.html.erb +8 -3
- data/app/views/chats/conversations/_thread.html.erb +172 -0
- data/app/views/chats/conversations/dock.html.erb +40 -0
- data/app/views/chats/conversations/index.html.erb +2 -1
- data/app/views/chats/conversations/show.html.erb +11 -118
- data/app/views/chats/conversations/window.html.erb +26 -0
- data/app/views/chats/dock/_badge.html.erb +12 -0
- data/app/views/chats/dock/_dock.html.erb +130 -0
- data/app/views/chats/dock/_signal.html.erb +26 -0
- data/app/views/chats/dock/_update.turbo_stream.erb +10 -0
- data/app/views/chats/dock/_window.html.erb +23 -0
- data/app/views/chats/dock/_window_header.html.erb +75 -0
- data/app/views/chats/dock/_window_shell.html.erb +17 -0
- data/app/views/chats/messages/_composer.html.erb +11 -1
- data/app/views/chats/shared/_unread_badge.html.erb +4 -1
- data/config/importmap.rb +1 -0
- data/config/locales/en.yml +17 -0
- data/config/locales/es.yml +17 -0
- data/config/routes.rb +5 -0
- data/docs/screenshots/dock-bar.png +0 -0
- data/docs/screenshots/dock-desktop.png +0 -0
- data/docs/screenshots/dock-phone-button.png +0 -0
- data/docs/screenshots/dock-phone-panel.png +0 -0
- data/docs/screenshots/dock-phone-window.png +0 -0
- data/gemfiles/rails_7.1.gemfile +2 -0
- data/gemfiles/rails_7.2.gemfile +2 -0
- data/gemfiles/rails_8.1.gemfile +2 -0
- data/lib/chats/broadcasts.rb +76 -10
- data/lib/chats/inbox.rb +68 -30
- data/lib/chats/models/concerns/messager.rb +10 -2
- data/lib/chats/models/conversation.rb +19 -0
- data/lib/chats/version.rb +1 -1
- data/lib/generators/chats/templates/initializer.rb +1 -0
- data/lib/generators/chats/views_generator.rb +3 -2
- metadata +18 -2
|
@@ -32,6 +32,12 @@
|
|
|
32
32
|
--chats-bubble-text: #111827;
|
|
33
33
|
--chats-radius: 1.1rem;
|
|
34
34
|
--chats-max-width: 42rem;
|
|
35
|
+
|
|
36
|
+
/* Safe areas, re-pointable by a host with its own source of truth. */
|
|
37
|
+
--chats-safe-top: env(safe-area-inset-top, 0px);
|
|
38
|
+
--chats-safe-right: env(safe-area-inset-right, 0px);
|
|
39
|
+
--chats-safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
40
|
+
--chats-safe-left: env(safe-area-inset-left, 0px);
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
/* --- Shell ----------------------------------------------------------------- */
|
|
@@ -996,3 +1002,593 @@ turbo-frame[id^="chats_page_"] {
|
|
|
996
1002
|
font-size: 0.85rem;
|
|
997
1003
|
color: var(--chats-text-muted);
|
|
998
1004
|
}
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
/* ---------------------------------------------------------------------------
|
|
1008
|
+
0.5.0 — the messaging dock (chats_dock)
|
|
1009
|
+
---------------------------------------------------------------------------
|
|
1010
|
+
|
|
1011
|
+
Windows to the left, the recent-chats panel at the right edge, flush with
|
|
1012
|
+
the bottom of the viewport. One element (`.chats-dock`, permanent across
|
|
1013
|
+
Turbo visits) rendered by `chats_dock`; the chats--dock controller owns
|
|
1014
|
+
the state classes:
|
|
1015
|
+
|
|
1016
|
+
.chats-dock[data-mode] "dock" or "sheet", set once JS runs (the
|
|
1017
|
+
layout itself comes from the media query
|
|
1018
|
+
at the end, so it holds before and without)
|
|
1019
|
+
.chats-dock__panel[open] the recent list shows (a <details>)
|
|
1020
|
+
.chats-window--minimized folded to its header
|
|
1021
|
+
.chats-window--unread something arrived the viewer hasn't seen
|
|
1022
|
+
.chats-window--front the one window a phone shows
|
|
1023
|
+
.chats-window--entering its first 200ms; --popped: opened by a message
|
|
1024
|
+
|
|
1025
|
+
Derived variables live on :where(.chats-dock), NOT :root: a custom
|
|
1026
|
+
property that references another resolves where it is declared, so a host
|
|
1027
|
+
re-pointing --chats-bg on .chats-dock would otherwise leave --chats-dock-bg
|
|
1028
|
+
frozen at the :root value. :where() keeps them at zero specificity.
|
|
1029
|
+
|
|
1030
|
+
Never a lasting transform, filter, backdrop-filter, perspective or contain
|
|
1031
|
+
on the dock or a window: each makes the window the containing block of the
|
|
1032
|
+
thread's position: fixed popup and photo preview, which would then open
|
|
1033
|
+
inside a 336px box. Entrance animations end at `transform: none`, and
|
|
1034
|
+
minimizing changes a height, not a transform. */
|
|
1035
|
+
|
|
1036
|
+
:where(.chats-dock) {
|
|
1037
|
+
--chats-dock-z: 40; /* above the page, under host modals */
|
|
1038
|
+
--chats-dock-offset: 0px; /* bottom clearance (a cookie banner…) */
|
|
1039
|
+
--chats-dock-inset: 16px; /* from the right edge */
|
|
1040
|
+
--chats-dock-gap: 8px;
|
|
1041
|
+
--chats-dock-bar-height: 48px; /* the bar, every window header */
|
|
1042
|
+
--chats-dock-panel-width: 288px;
|
|
1043
|
+
--chats-dock-panel-height: 560px;
|
|
1044
|
+
--chats-dock-window-width: 336px;
|
|
1045
|
+
--chats-dock-window-height: 480px;
|
|
1046
|
+
--chats-dock-squeezed-width: 104px; /* a window folded to make room */
|
|
1047
|
+
--chats-dock-top-clearance: 24px;
|
|
1048
|
+
--chats-dock-radius: 12px;
|
|
1049
|
+
--chats-dock-bg: var(--chats-bg);
|
|
1050
|
+
--chats-dock-header-bg: var(--chats-bg);
|
|
1051
|
+
/* Not --chats-surface: muted text on it is 4.39:1; on this, 4.63:1. */
|
|
1052
|
+
--chats-dock-hover: #f9fafb;
|
|
1053
|
+
--chats-dock-edge: rgba(17, 24, 39, 0.12);
|
|
1054
|
+
--chats-dock-shadow: 0 12px 40px rgba(17, 24, 39, 0.16), 0 2px 6px rgba(17, 24, 39, 0.08);
|
|
1055
|
+
--chats-dock-unread-bg: var(--chats-accent);
|
|
1056
|
+
--chats-dock-unread-text: var(--chats-accent-contrast);
|
|
1057
|
+
--chats-dock-focus: var(--chats-accent);
|
|
1058
|
+
/* --chats-dock-title-font: unset on purpose; titles inherit the host font. */
|
|
1059
|
+
--chats-dock-launcher-size: 56px;
|
|
1060
|
+
--chats-dock-sheet-header-height: 56px;
|
|
1061
|
+
--chats-dock-duration: 200ms;
|
|
1062
|
+
--chats-dock-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/* --- The dock ----------------------------------------------------------------- */
|
|
1066
|
+
|
|
1067
|
+
.chats-dock {
|
|
1068
|
+
position: fixed;
|
|
1069
|
+
z-index: var(--chats-dock-z);
|
|
1070
|
+
right: calc(var(--chats-dock-inset) + var(--chats-safe-right));
|
|
1071
|
+
bottom: var(--chats-dock-offset);
|
|
1072
|
+
display: flex;
|
|
1073
|
+
align-items: flex-end;
|
|
1074
|
+
gap: var(--chats-dock-gap);
|
|
1075
|
+
color: var(--chats-text);
|
|
1076
|
+
font-size: 0.875rem;
|
|
1077
|
+
line-height: 1.4;
|
|
1078
|
+
pointer-events: none; /* the gaps between surfaces never eat page clicks */
|
|
1079
|
+
}
|
|
1080
|
+
/* Fixed-size surfaces with padding: never trust the host to have a reset. */
|
|
1081
|
+
.chats-dock,
|
|
1082
|
+
.chats-dock *,
|
|
1083
|
+
.chats-dock *::before,
|
|
1084
|
+
.chats-dock *::after { box-sizing: border-box; }
|
|
1085
|
+
.chats-dock[hidden] { display: none; }
|
|
1086
|
+
.chats-dock__windows {
|
|
1087
|
+
display: flex;
|
|
1088
|
+
align-items: flex-end;
|
|
1089
|
+
gap: var(--chats-dock-gap);
|
|
1090
|
+
}
|
|
1091
|
+
.chats-window,
|
|
1092
|
+
.chats-dock__panel { pointer-events: auto; }
|
|
1093
|
+
|
|
1094
|
+
/* Inset: every surface clips (overflow: hidden), so an outside ring is cut. */
|
|
1095
|
+
.chats-dock :focus-visible {
|
|
1096
|
+
outline: 2px solid var(--chats-dock-focus);
|
|
1097
|
+
outline-offset: -2px;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/* --- Surfaces (panel and windows share one skin) -------------------------------- */
|
|
1101
|
+
|
|
1102
|
+
.chats-window,
|
|
1103
|
+
.chats-dock__panel {
|
|
1104
|
+
background: var(--chats-dock-bg);
|
|
1105
|
+
border: 1px solid var(--chats-dock-edge);
|
|
1106
|
+
border-bottom: 0;
|
|
1107
|
+
border-radius: var(--chats-dock-radius) var(--chats-dock-radius) 0 0;
|
|
1108
|
+
box-shadow: var(--chats-dock-shadow);
|
|
1109
|
+
overflow: hidden;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
.chats-dock__panel {
|
|
1113
|
+
position: relative;
|
|
1114
|
+
width: var(--chats-dock-panel-width);
|
|
1115
|
+
padding-bottom: var(--chats-safe-bottom);
|
|
1116
|
+
}
|
|
1117
|
+
.chats-dock__panel[open] {
|
|
1118
|
+
height: min(var(--chats-dock-panel-height),
|
|
1119
|
+
calc(100dvh - var(--chats-dock-offset) - var(--chats-dock-top-clearance)));
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
.chats-window {
|
|
1123
|
+
display: flex;
|
|
1124
|
+
flex-direction: column;
|
|
1125
|
+
flex-shrink: 0;
|
|
1126
|
+
width: var(--chats-dock-window-width);
|
|
1127
|
+
height: min(var(--chats-dock-window-height),
|
|
1128
|
+
calc(100dvh - var(--chats-dock-offset) - var(--chats-dock-top-clearance)));
|
|
1129
|
+
}
|
|
1130
|
+
/* Folded to its header (and the home-indicator strip). A height, not a
|
|
1131
|
+
transform: the thread keeps its box and its scroll position, and its
|
|
1132
|
+
zero-height scroller is how the thread controller knows not to read. */
|
|
1133
|
+
.chats-window--minimized { height: calc(var(--chats-dock-bar-height) + var(--chats-safe-bottom)); }
|
|
1134
|
+
/* Folded to make room (it was in use when another opened, and the row is
|
|
1135
|
+
full): the width the controller budgets for it, the avatar (which expands
|
|
1136
|
+
it) and ×. The name stays in the title's tooltip. */
|
|
1137
|
+
.chats-window--squeezed { width: var(--chats-dock-squeezed-width); }
|
|
1138
|
+
.chats-window--squeezed :is(.chats-window__identity, .chats-window__unread, .chats-window__menu,
|
|
1139
|
+
.chats-window__expand, .chats-window__minimize) {
|
|
1140
|
+
display: none;
|
|
1141
|
+
}
|
|
1142
|
+
/* Nothing of the thread keeps a height while folded, padding included, so
|
|
1143
|
+
the geometry says what the state says: there's nothing to read. */
|
|
1144
|
+
.chats-window--minimized .chats-thread--window .chats-thread__scroll { padding-top: 0; padding-bottom: 0; }
|
|
1145
|
+
.chats-window__card {
|
|
1146
|
+
display: flex;
|
|
1147
|
+
flex-direction: column;
|
|
1148
|
+
flex: 1;
|
|
1149
|
+
min-height: 0;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/* --- The bar (the panel's <summary>) and window headers -------------------------- */
|
|
1153
|
+
|
|
1154
|
+
.chats-dock__bar,
|
|
1155
|
+
.chats-window__header {
|
|
1156
|
+
flex: 0 0 var(--chats-dock-bar-height);
|
|
1157
|
+
height: var(--chats-dock-bar-height);
|
|
1158
|
+
display: flex;
|
|
1159
|
+
align-items: center;
|
|
1160
|
+
background: var(--chats-dock-header-bg);
|
|
1161
|
+
border-bottom: 1px solid var(--chats-dock-edge);
|
|
1162
|
+
}
|
|
1163
|
+
.chats-dock__toggle {
|
|
1164
|
+
gap: 10px;
|
|
1165
|
+
color: inherit;
|
|
1166
|
+
padding: 0 8px 0 12px;
|
|
1167
|
+
list-style: none;
|
|
1168
|
+
cursor: pointer;
|
|
1169
|
+
user-select: none;
|
|
1170
|
+
}
|
|
1171
|
+
.chats-dock__toggle::-webkit-details-marker { display: none; }
|
|
1172
|
+
.chats-dock__panel:not([open]) .chats-dock__toggle { border-bottom-color: transparent; }
|
|
1173
|
+
.chats-dock__toggle .chats-avatar { width: 32px; height: 32px; font-size: 0.8rem; }
|
|
1174
|
+
.chats-dock__icon { display: none; width: 26px; height: 26px; flex-shrink: 0; }
|
|
1175
|
+
.chats-dock__title {
|
|
1176
|
+
flex: 1;
|
|
1177
|
+
min-width: 0;
|
|
1178
|
+
font-family: var(--chats-dock-title-font);
|
|
1179
|
+
font-size: 0.9375rem;
|
|
1180
|
+
font-weight: 700;
|
|
1181
|
+
white-space: nowrap;
|
|
1182
|
+
overflow: hidden;
|
|
1183
|
+
text-overflow: ellipsis;
|
|
1184
|
+
}
|
|
1185
|
+
.chats-dock__chevron,
|
|
1186
|
+
.chats-dock__close-glyph {
|
|
1187
|
+
flex: 0 0 32px;
|
|
1188
|
+
width: 32px;
|
|
1189
|
+
height: 32px;
|
|
1190
|
+
display: inline-grid;
|
|
1191
|
+
place-items: center;
|
|
1192
|
+
border-radius: 8px;
|
|
1193
|
+
color: var(--chats-text-muted);
|
|
1194
|
+
}
|
|
1195
|
+
.chats-dock__chevron svg,
|
|
1196
|
+
.chats-dock__close-glyph svg {
|
|
1197
|
+
width: 18px;
|
|
1198
|
+
height: 18px;
|
|
1199
|
+
transition: transform var(--chats-dock-duration) var(--chats-dock-ease);
|
|
1200
|
+
}
|
|
1201
|
+
.chats-dock__toggle:hover .chats-dock__chevron { background: var(--chats-dock-hover); color: var(--chats-text); }
|
|
1202
|
+
.chats-dock__panel[open] .chats-dock__chevron svg { transform: rotate(180deg); }
|
|
1203
|
+
.chats-dock__close-glyph { display: none; }
|
|
1204
|
+
|
|
1205
|
+
/* Badges in the dock: one size, a hair smaller than the inbox's. */
|
|
1206
|
+
.chats-dock .chats-badge {
|
|
1207
|
+
min-width: 1.25rem;
|
|
1208
|
+
height: 1.25rem;
|
|
1209
|
+
padding: 0 0.3rem;
|
|
1210
|
+
font-size: 0.6875rem;
|
|
1211
|
+
font-variant-numeric: tabular-nums;
|
|
1212
|
+
}
|
|
1213
|
+
.chats-dock .chats-badge[hidden] { display: none; }
|
|
1214
|
+
|
|
1215
|
+
/* --- The panel body -------------------------------------------------------------- */
|
|
1216
|
+
|
|
1217
|
+
.chats-dock__body {
|
|
1218
|
+
/* Positioned, not a flex item: a <details>' content sits in a UA slot box,
|
|
1219
|
+
so flex sizing from the panel never reaches it. */
|
|
1220
|
+
position: absolute;
|
|
1221
|
+
inset: var(--chats-dock-bar-height) 0 var(--chats-safe-bottom) 0;
|
|
1222
|
+
display: flex;
|
|
1223
|
+
flex-direction: column;
|
|
1224
|
+
}
|
|
1225
|
+
.chats-dock__panel[open] .chats-dock__body {
|
|
1226
|
+
animation: chats-dock-rise var(--chats-dock-duration) var(--chats-dock-ease);
|
|
1227
|
+
}
|
|
1228
|
+
.chats-dock__search { padding: 8px 12px 4px; }
|
|
1229
|
+
.chats-dock__search .chats-search__input { padding: 0.45rem 0.8rem; font-size: 0.8125rem; }
|
|
1230
|
+
.chats-dock__results {
|
|
1231
|
+
display: block;
|
|
1232
|
+
flex: 1;
|
|
1233
|
+
min-height: 0;
|
|
1234
|
+
overflow-y: auto;
|
|
1235
|
+
overscroll-behavior: contain;
|
|
1236
|
+
}
|
|
1237
|
+
.chats-dock__list { list-style: none; margin: 0; padding: 4px 0; }
|
|
1238
|
+
.chats-dock__list .chats-row__link { padding: 9px 12px; gap: 10px; }
|
|
1239
|
+
.chats-dock__list .chats-row__link:hover { background: var(--chats-dock-hover); }
|
|
1240
|
+
.chats-dock__list .chats-avatar { width: 40px; height: 40px; font-size: 0.85rem; }
|
|
1241
|
+
.chats-dock__list .chats-row__title { font-size: 0.875rem; }
|
|
1242
|
+
.chats-dock__list .chats-row__subject { font-size: 0.6875rem; }
|
|
1243
|
+
.chats-dock__list .chats-row__preview { font-size: 0.8125rem; }
|
|
1244
|
+
.chats-dock__list .chats-row__time { font-size: 0.6875rem; }
|
|
1245
|
+
/* A conversation that is open in a window right now. */
|
|
1246
|
+
.chats-dock__list .chats-row__link[aria-current="true"] { box-shadow: inset 3px 0 0 var(--chats-accent); }
|
|
1247
|
+
.chats-row__muted { flex-shrink: 0; width: 14px; height: 14px; color: var(--chats-text-muted); }
|
|
1248
|
+
|
|
1249
|
+
.chats-dock__see-all {
|
|
1250
|
+
flex: 0 0 44px;
|
|
1251
|
+
display: flex;
|
|
1252
|
+
align-items: center;
|
|
1253
|
+
justify-content: center;
|
|
1254
|
+
border-top: 1px solid var(--chats-dock-edge);
|
|
1255
|
+
color: var(--chats-text);
|
|
1256
|
+
font-size: 0.8125rem;
|
|
1257
|
+
font-weight: 650;
|
|
1258
|
+
text-decoration: none;
|
|
1259
|
+
}
|
|
1260
|
+
.chats-dock__see-all:hover { background: var(--chats-dock-hover); text-decoration: underline; }
|
|
1261
|
+
|
|
1262
|
+
.chats-dock .chats-empty { padding: 40px 20px; }
|
|
1263
|
+
.chats-dock .chats-empty__icon { font-size: 2rem; margin-bottom: 0.5rem; }
|
|
1264
|
+
.chats-dock .chats-empty__title { font-size: 0.9375rem; }
|
|
1265
|
+
.chats-dock .chats-empty__hint { font-size: 0.8125rem; }
|
|
1266
|
+
|
|
1267
|
+
/* The very first load: four still rows (no shimmer). */
|
|
1268
|
+
.chats-dock__skeleton { display: flex; gap: 10px; align-items: center; padding: 9px 12px; }
|
|
1269
|
+
.chats-dock__skeleton::before {
|
|
1270
|
+
content: "";
|
|
1271
|
+
width: 40px;
|
|
1272
|
+
height: 40px;
|
|
1273
|
+
border-radius: 999px;
|
|
1274
|
+
background: var(--chats-surface);
|
|
1275
|
+
flex-shrink: 0;
|
|
1276
|
+
}
|
|
1277
|
+
.chats-dock__skeleton span { flex: 1; display: flex; flex-direction: column; gap: 6px; }
|
|
1278
|
+
.chats-dock__skeleton span::before,
|
|
1279
|
+
.chats-dock__skeleton span::after { content: ""; height: 10px; border-radius: 4px; background: var(--chats-surface); }
|
|
1280
|
+
.chats-dock__skeleton span::before { width: 45%; }
|
|
1281
|
+
.chats-dock__skeleton span::after { width: 80%; }
|
|
1282
|
+
|
|
1283
|
+
/* --- Windows --------------------------------------------------------------------- */
|
|
1284
|
+
|
|
1285
|
+
.chats-window__header { padding-right: 4px; position: relative; }
|
|
1286
|
+
.chats-window__title {
|
|
1287
|
+
flex: 1;
|
|
1288
|
+
min-width: 0;
|
|
1289
|
+
height: 100%;
|
|
1290
|
+
display: flex;
|
|
1291
|
+
align-items: center;
|
|
1292
|
+
gap: 8px;
|
|
1293
|
+
padding: 0 4px 0 8px;
|
|
1294
|
+
border: 0;
|
|
1295
|
+
background: none;
|
|
1296
|
+
color: inherit;
|
|
1297
|
+
font: inherit;
|
|
1298
|
+
text-align: left;
|
|
1299
|
+
cursor: pointer;
|
|
1300
|
+
}
|
|
1301
|
+
.chats-window__title .chats-avatar { width: 32px; height: 32px; font-size: 0.8rem; }
|
|
1302
|
+
.chats-window__identity { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
1303
|
+
.chats-window__name-row { display: flex; align-items: center; gap: 4px; min-width: 0; }
|
|
1304
|
+
.chats-window__name {
|
|
1305
|
+
font-family: var(--chats-dock-title-font);
|
|
1306
|
+
font-size: 0.875rem;
|
|
1307
|
+
font-weight: 700;
|
|
1308
|
+
line-height: 1.25;
|
|
1309
|
+
white-space: nowrap;
|
|
1310
|
+
overflow: hidden;
|
|
1311
|
+
text-overflow: ellipsis;
|
|
1312
|
+
}
|
|
1313
|
+
.chats-window__subject {
|
|
1314
|
+
font-size: 0.75rem;
|
|
1315
|
+
line-height: 1.25;
|
|
1316
|
+
color: var(--chats-text-muted);
|
|
1317
|
+
white-space: nowrap;
|
|
1318
|
+
overflow: hidden;
|
|
1319
|
+
text-overflow: ellipsis;
|
|
1320
|
+
}
|
|
1321
|
+
.chats-window__title .chats-badge { margin-left: 2px; }
|
|
1322
|
+
.chats-window__icon {
|
|
1323
|
+
flex: 0 0 32px;
|
|
1324
|
+
width: 32px;
|
|
1325
|
+
height: 32px;
|
|
1326
|
+
display: inline-grid;
|
|
1327
|
+
place-items: center;
|
|
1328
|
+
padding: 0;
|
|
1329
|
+
border: 0;
|
|
1330
|
+
border-radius: 8px;
|
|
1331
|
+
background: none;
|
|
1332
|
+
color: var(--chats-text-muted);
|
|
1333
|
+
font: inherit;
|
|
1334
|
+
font-size: 1.1rem;
|
|
1335
|
+
line-height: 1;
|
|
1336
|
+
cursor: pointer;
|
|
1337
|
+
text-decoration: none;
|
|
1338
|
+
list-style: none;
|
|
1339
|
+
}
|
|
1340
|
+
.chats-window__icon::-webkit-details-marker { display: none; }
|
|
1341
|
+
.chats-window__icon svg { width: 18px; height: 18px; }
|
|
1342
|
+
.chats-window__icon:hover { background: var(--chats-dock-hover); color: var(--chats-text); }
|
|
1343
|
+
.chats-window__back { display: none; }
|
|
1344
|
+
/* The host's thread actions (conversation_header_actions). */
|
|
1345
|
+
.chats-window__menu { flex: 0 0 auto; }
|
|
1346
|
+
.chats-window__menu .chats-menu__panel { top: calc(100% - 4px); right: 4px; z-index: 30; }
|
|
1347
|
+
/* Touch in dock mode (a tablet): the same 48px header, fatter targets. */
|
|
1348
|
+
@media (pointer: coarse) {
|
|
1349
|
+
.chats-dock[data-mode="dock"] .chats-window__icon { flex-basis: 40px; width: 40px; height: 40px; }
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
/* Focused: a 2px accent line along the top of the header. */
|
|
1353
|
+
.chats-window:focus-within .chats-window__header { box-shadow: inset 0 2px 0 var(--chats-dock-focus); }
|
|
1354
|
+
|
|
1355
|
+
/* Waiting to be seen (popped open, or minimized when something arrived): the
|
|
1356
|
+
header becomes the accent pair, the one every host already tunes. */
|
|
1357
|
+
.chats-window--unread .chats-window__header {
|
|
1358
|
+
background: var(--chats-dock-unread-bg);
|
|
1359
|
+
color: var(--chats-dock-unread-text);
|
|
1360
|
+
border-bottom-color: transparent;
|
|
1361
|
+
box-shadow: none;
|
|
1362
|
+
}
|
|
1363
|
+
.chats-window--unread :is(.chats-window__subject, .chats-window__icon, .chats-verified) { color: inherit; }
|
|
1364
|
+
.chats-window--unread .chats-window__icon:hover { background: color-mix(in srgb, currentColor 16%, transparent); color: inherit; }
|
|
1365
|
+
.chats-window--unread .chats-window__header .chats-badge {
|
|
1366
|
+
background: var(--chats-dock-unread-text);
|
|
1367
|
+
color: var(--chats-dock-unread-bg);
|
|
1368
|
+
}
|
|
1369
|
+
.chats-window--unread .chats-window__header :focus-visible { outline-color: var(--chats-dock-unread-text); }
|
|
1370
|
+
|
|
1371
|
+
/* The thread, inside a window: the full page's body, compacted. Rooted at
|
|
1372
|
+
.chats-thread--window, never .chats (the full-page shell). */
|
|
1373
|
+
.chats-thread--window {
|
|
1374
|
+
--chats-radius: 0.95rem;
|
|
1375
|
+
flex: 1;
|
|
1376
|
+
min-height: 0;
|
|
1377
|
+
height: auto;
|
|
1378
|
+
display: flex;
|
|
1379
|
+
flex-direction: column;
|
|
1380
|
+
overflow: hidden;
|
|
1381
|
+
background: var(--chats-dock-bg);
|
|
1382
|
+
}
|
|
1383
|
+
.chats-thread--window .chats-thread__scroll { padding: 10px 12px 6px; }
|
|
1384
|
+
/* A short thread sits on the composer: the eye is at the bottom. */
|
|
1385
|
+
.chats-thread--window .chats-thread__scroll > :first-child { margin-top: auto; }
|
|
1386
|
+
.chats-thread--window .chats-day-separator,
|
|
1387
|
+
.chats-thread--window .chats-thread__unread-line { margin: 0.5rem 0; }
|
|
1388
|
+
.chats-thread--window .chats-message { max-width: 88%; }
|
|
1389
|
+
.chats-thread--window .chats-message__avatar { width: 1.6rem; height: 1.6rem; font-size: 0.65rem; }
|
|
1390
|
+
.chats-thread--window .chats-message__bubble { padding: 0.4rem 0.7rem; font-size: 0.875rem; line-height: 1.35; }
|
|
1391
|
+
.chats-thread--window .chats-message__bubble > .chats-message__action { font-size: 0.8125rem; padding: 0.45rem 0.8rem; }
|
|
1392
|
+
.chats-thread--window .chats-message__image { max-width: 11rem; max-height: 11rem; }
|
|
1393
|
+
.chats-thread--window .chats-typing { font-size: 0.75rem; padding: 0.3rem 0.25rem; }
|
|
1394
|
+
.chats-thread--window .chats-composer { padding: 8px 10px calc(8px + var(--chats-safe-bottom)); }
|
|
1395
|
+
.chats-thread--window .chats-composer__actions { margin-bottom: 6px; }
|
|
1396
|
+
.chats-thread--window :where(.chats-composer__actions) :where(a, button) { font-size: 0.75rem; padding: 0.25rem 0.7rem; }
|
|
1397
|
+
.chats-thread--window .chats-composer__bar { gap: 8px; }
|
|
1398
|
+
.chats-thread--window .chats-composer__input { font-size: 0.875rem; padding: 0.45rem 0.75rem; max-height: 7.5rem; }
|
|
1399
|
+
.chats-thread--window .chats-composer__send { width: 2.25rem; height: 2.25rem; font-size: 0.95rem; }
|
|
1400
|
+
.chats-thread--window .chats-composer--locked { padding: 12px 14px calc(12px + var(--chats-safe-bottom)); }
|
|
1401
|
+
.chats-thread--window .chats-composer__locked-notice { font-size: 0.8125rem; }
|
|
1402
|
+
|
|
1403
|
+
/* Before anybody wrote (opened from a Message button). */
|
|
1404
|
+
.chats-window__hello {
|
|
1405
|
+
margin: auto 0;
|
|
1406
|
+
padding: 24px 16px;
|
|
1407
|
+
text-align: center;
|
|
1408
|
+
color: var(--chats-text-muted);
|
|
1409
|
+
font-size: 0.8125rem;
|
|
1410
|
+
}
|
|
1411
|
+
.chats-window__hello .chats-avatar { display: flex; width: 56px; height: 56px; font-size: 1.2rem; margin: 0 auto 10px; }
|
|
1412
|
+
.chats-window__hello strong { display: block; color: var(--chats-text); font-size: 0.9375rem; margin-bottom: 2px; }
|
|
1413
|
+
.chats-thread__scroll:has(.chats-message) .chats-window__hello { display: none; }
|
|
1414
|
+
|
|
1415
|
+
/* Loading, and failure. */
|
|
1416
|
+
.chats-window__status {
|
|
1417
|
+
margin: auto;
|
|
1418
|
+
padding: 24px 16px;
|
|
1419
|
+
text-align: center;
|
|
1420
|
+
color: var(--chats-text-muted);
|
|
1421
|
+
font-size: 0.8125rem;
|
|
1422
|
+
}
|
|
1423
|
+
.chats-window__status p { margin: 0; }
|
|
1424
|
+
.chats-window__retry {
|
|
1425
|
+
margin-top: 10px;
|
|
1426
|
+
padding: 0.35rem 0.9rem;
|
|
1427
|
+
border: 1px solid var(--chats-border);
|
|
1428
|
+
border-radius: 999px;
|
|
1429
|
+
background: var(--chats-surface);
|
|
1430
|
+
color: var(--chats-text);
|
|
1431
|
+
font: inherit;
|
|
1432
|
+
font-size: 0.8125rem;
|
|
1433
|
+
font-weight: 600;
|
|
1434
|
+
cursor: pointer;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/* --- Motion ---------------------------------------------------------------------- */
|
|
1438
|
+
|
|
1439
|
+
@keyframes chats-dock-rise { from { transform: translateY(16px); opacity: 0; } }
|
|
1440
|
+
@keyframes chats-dock-sheet-up { from { transform: translateY(100%); } }
|
|
1441
|
+
@keyframes chats-dock-push { from { transform: translateX(100%); } }
|
|
1442
|
+
@keyframes chats-dock-bump { 50% { transform: scale(1.2); } }
|
|
1443
|
+
@keyframes chats-dock-flash { 50% { filter: brightness(1.18); } }
|
|
1444
|
+
|
|
1445
|
+
.chats-window--entering { animation: chats-dock-rise var(--chats-dock-duration) var(--chats-dock-ease); }
|
|
1446
|
+
.chats-window--popped .chats-window__header { animation: chats-dock-flash 600ms ease-in-out 2; }
|
|
1447
|
+
.chats-dock .chats-badge--bumped { animation: chats-dock-bump 160ms ease-out; }
|
|
1448
|
+
|
|
1449
|
+
/* --- Phones: the sheet ------------------------------------------------------------
|
|
1450
|
+
Under 700px wide or 500px tall (one window beside the panel needs 656px;
|
|
1451
|
+
a 480px window needs the height). A media query, not a class, so a phone
|
|
1452
|
+
gets this layout before the controller connects and without JavaScript —
|
|
1453
|
+
the panel is a <details>, so the closed bar is the launcher and opening it
|
|
1454
|
+
is the sheet. The controller reads the same query for its behaviour. */
|
|
1455
|
+
|
|
1456
|
+
@media (max-width: 699.98px), (max-height: 499.98px) {
|
|
1457
|
+
.chats-dock {
|
|
1458
|
+
inset: auto 0 0 auto;
|
|
1459
|
+
right: 0;
|
|
1460
|
+
display: block;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
/* Closed: the round launcher, bottom-right above the home indicator. */
|
|
1464
|
+
.chats-dock__panel:not([open]) {
|
|
1465
|
+
position: fixed;
|
|
1466
|
+
right: calc(16px + var(--chats-safe-right));
|
|
1467
|
+
bottom: calc(16px + var(--chats-dock-offset) + var(--chats-safe-bottom));
|
|
1468
|
+
width: var(--chats-dock-launcher-size);
|
|
1469
|
+
height: var(--chats-dock-launcher-size);
|
|
1470
|
+
padding: 0;
|
|
1471
|
+
border: 0;
|
|
1472
|
+
border-radius: 999px;
|
|
1473
|
+
background: var(--chats-accent);
|
|
1474
|
+
color: var(--chats-accent-contrast);
|
|
1475
|
+
box-shadow: 0 6px 20px rgba(17, 24, 39, 0.28);
|
|
1476
|
+
overflow: visible;
|
|
1477
|
+
}
|
|
1478
|
+
.chats-dock__panel:not([open]) .chats-dock__toggle {
|
|
1479
|
+
position: relative;
|
|
1480
|
+
justify-content: center;
|
|
1481
|
+
width: 100%;
|
|
1482
|
+
height: 100%;
|
|
1483
|
+
padding: 0;
|
|
1484
|
+
border: 0;
|
|
1485
|
+
border-radius: 999px;
|
|
1486
|
+
background: none;
|
|
1487
|
+
}
|
|
1488
|
+
.chats-dock__panel:not([open]) :is(.chats-avatar, .chats-dock__chevron) { display: none; }
|
|
1489
|
+
.chats-dock__panel:not([open]) .chats-dock__title {
|
|
1490
|
+
/* Still read out ("Messages, 3 unread"), just not shown. */
|
|
1491
|
+
position: absolute;
|
|
1492
|
+
width: 1px;
|
|
1493
|
+
height: 1px;
|
|
1494
|
+
overflow: hidden;
|
|
1495
|
+
clip: rect(0, 0, 0, 0);
|
|
1496
|
+
}
|
|
1497
|
+
.chats-dock__panel:not([open]) .chats-dock__icon { display: block; }
|
|
1498
|
+
.chats-dock__panel:not([open]) .chats-badge {
|
|
1499
|
+
position: absolute;
|
|
1500
|
+
top: -2px;
|
|
1501
|
+
right: -2px;
|
|
1502
|
+
/* accent-on-accent would vanish: the launcher's badge is the inverse pair */
|
|
1503
|
+
background: var(--chats-accent-contrast);
|
|
1504
|
+
color: var(--chats-accent);
|
|
1505
|
+
box-shadow: 0 0 0 2px var(--chats-accent);
|
|
1506
|
+
}
|
|
1507
|
+
.chats-dock--no-launcher .chats-dock__panel:not([open]) { display: none; }
|
|
1508
|
+
/* Anything full-screen up: the launcher would only cover it. */
|
|
1509
|
+
.chats-dock:has(.chats-window--front) .chats-dock__panel:not([open]) { display: none; }
|
|
1510
|
+
|
|
1511
|
+
/* Open: the full-screen sheet; a window sits on top of the list. */
|
|
1512
|
+
.chats-dock__panel[open],
|
|
1513
|
+
.chats-window {
|
|
1514
|
+
position: fixed;
|
|
1515
|
+
left: 0;
|
|
1516
|
+
right: 0;
|
|
1517
|
+
top: var(--chats-vv-top, 0px);
|
|
1518
|
+
width: 100%;
|
|
1519
|
+
height: var(--chats-vvh, 100dvh);
|
|
1520
|
+
border: 0;
|
|
1521
|
+
border-radius: 0;
|
|
1522
|
+
box-shadow: none;
|
|
1523
|
+
padding: var(--chats-safe-top) var(--chats-safe-right) 0 var(--chats-safe-left);
|
|
1524
|
+
}
|
|
1525
|
+
.chats-dock__panel[open] {
|
|
1526
|
+
z-index: 1;
|
|
1527
|
+
padding-bottom: var(--chats-safe-bottom);
|
|
1528
|
+
animation: chats-dock-sheet-up 260ms cubic-bezier(0.32, 0.72, 0, 1);
|
|
1529
|
+
}
|
|
1530
|
+
.chats-dock__panel[open] .chats-dock__body { animation: none; }
|
|
1531
|
+
.chats-window { z-index: 2; }
|
|
1532
|
+
.chats-window:not(.chats-window--front) { display: none; }
|
|
1533
|
+
.chats-window--front.chats-window--entering { animation: chats-dock-push 260ms cubic-bezier(0.32, 0.72, 0, 1); }
|
|
1534
|
+
|
|
1535
|
+
.chats-dock__body {
|
|
1536
|
+
inset: calc(var(--chats-safe-top) + var(--chats-dock-sheet-header-height)) var(--chats-safe-right)
|
|
1537
|
+
var(--chats-safe-bottom) var(--chats-safe-left);
|
|
1538
|
+
}
|
|
1539
|
+
.chats-dock__panel[open] .chats-dock__bar,
|
|
1540
|
+
.chats-window__header {
|
|
1541
|
+
flex-basis: var(--chats-dock-sheet-header-height);
|
|
1542
|
+
height: var(--chats-dock-sheet-header-height);
|
|
1543
|
+
box-shadow: none;
|
|
1544
|
+
}
|
|
1545
|
+
.chats-dock__panel[open] .chats-dock__toggle { padding: 0 6px 0 16px; }
|
|
1546
|
+
.chats-dock__panel[open] .chats-dock__title { font-size: 1.125rem; font-weight: 800; }
|
|
1547
|
+
.chats-dock__panel[open] :is(.chats-dock__toggle .chats-avatar, .chats-dock__chevron) { display: none; }
|
|
1548
|
+
.chats-dock__panel[open] .chats-dock__close-glyph { display: inline-grid; flex-basis: 44px; width: 44px; height: 44px; }
|
|
1549
|
+
.chats-dock__panel[open] .chats-dock__close-glyph svg { width: 22px; height: 22px; }
|
|
1550
|
+
.chats-dock__search { padding: 8px 16px; }
|
|
1551
|
+
/* 16px or iOS Safari zooms the page on focus. */
|
|
1552
|
+
.chats-dock__search .chats-search__input { font-size: 16px; padding: 0.55rem 0.9rem; }
|
|
1553
|
+
.chats-dock__list .chats-row__link { padding: 10px 16px; gap: 12px; }
|
|
1554
|
+
.chats-dock__list .chats-avatar { width: 48px; height: 48px; }
|
|
1555
|
+
.chats-dock__list .chats-row__title { font-size: 1rem; }
|
|
1556
|
+
.chats-dock__list .chats-row__preview { font-size: 0.875rem; }
|
|
1557
|
+
.chats-dock__list .chats-row__time { font-size: 0.75rem; }
|
|
1558
|
+
.chats-dock__see-all { flex-basis: 52px; font-size: 0.9375rem; }
|
|
1559
|
+
|
|
1560
|
+
.chats-window__back { display: inline-grid; }
|
|
1561
|
+
.chats-window__header { padding: 0 4px; }
|
|
1562
|
+
.chats-window__header :is(.chats-window__minimize, .chats-window__close) { display: none; }
|
|
1563
|
+
.chats-window__icon { flex-basis: 44px; width: 44px; height: 44px; }
|
|
1564
|
+
.chats-window__icon svg { width: 22px; height: 22px; }
|
|
1565
|
+
.chats-window__title { cursor: default; padding-left: 2px; }
|
|
1566
|
+
.chats-window__title .chats-avatar { width: 36px; height: 36px; }
|
|
1567
|
+
.chats-window__name { font-size: 1rem; }
|
|
1568
|
+
/* Full screen: the conversation is in front of you, nothing to flag. */
|
|
1569
|
+
.chats-window--unread .chats-window__header {
|
|
1570
|
+
background: var(--chats-dock-header-bg);
|
|
1571
|
+
color: inherit;
|
|
1572
|
+
border-bottom-color: var(--chats-dock-edge);
|
|
1573
|
+
}
|
|
1574
|
+
.chats-thread--window .chats-thread__scroll { padding: 12px 14px 8px; }
|
|
1575
|
+
.chats-thread--window .chats-message__bubble { font-size: 1rem; padding: 0.5rem 0.8rem; }
|
|
1576
|
+
.chats-thread--window .chats-composer { padding: 8px 12px calc(8px + var(--chats-safe-bottom)); }
|
|
1577
|
+
.chats-thread--window .chats-composer__input { font-size: 16px; padding: 0.55rem 0.9rem; }
|
|
1578
|
+
.chats-thread--window .chats-composer__send { width: 2.75rem; height: 2.75rem; }
|
|
1579
|
+
/* The keyboard is up: the home-indicator strip is behind it. */
|
|
1580
|
+
.chats-dock--keyboard .chats-thread--window .chats-composer { padding-bottom: 8px; }
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
html.chats-dock-sheet-open { overflow: hidden; }
|
|
1584
|
+
|
|
1585
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1586
|
+
.chats-dock,
|
|
1587
|
+
.chats-dock *,
|
|
1588
|
+
.chats-dock *::before,
|
|
1589
|
+
.chats-dock *::after {
|
|
1590
|
+
animation: none !important;
|
|
1591
|
+
transition-duration: 0s !important;
|
|
1592
|
+
transition-delay: 0s !important;
|
|
1593
|
+
}
|
|
1594
|
+
}
|