Files
ds-chat/frontend/src/components/MessageList.css
T
ksmithandClaude Sonnet 5.5 0065b4ced3 Add an add-reaction button to the reaction row (#78)
Adding to a message that already had reactions meant going back up to
the hover row's 🙂. A "+🙂" button now sits at the end of the row of
reaction pills, always visible once the message has at least one
reaction, and opens the same emoji picker.

The picker anchors to the left edge of the pill row rather than the
button: the button moves right with every added reaction, and a picker
hanging off it ran past the edge of a phone screen. Both buttons share
the picker's open state, so only one picker is ever open at a time.

MessageList had no tests; this adds a few covering the new button.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:49:44 -06:00

455 lines
8.4 KiB
CSS

.message-list {
flex: 1;
overflow-y: auto;
padding: var(--sp-4);
display: flex;
flex-direction: column;
}
.message-row {
position: relative;
display: flex;
gap: var(--sp-2);
padding: 1px var(--sp-2);
border-radius: 6px;
}
.message-row-start {
margin-top: 10px;
}
.message-row:hover {
background: var(--ds-surface);
}
.message-avatar-slot {
width: 36px;
flex: none;
}
.message-content {
flex: 1;
min-width: 0;
}
.message-header {
display: flex;
align-items: baseline;
gap: 8px;
margin-bottom: 2px;
}
.message-author {
/* Lets a very long display name shrink/wrap instead of pushing the
(now longer, e.g. "Yesterday 4:04 PM") timestamp out of the row. */
min-width: 0;
font-size: 0.86rem;
font-weight: 700;
color: var(--ds-text);
}
.message-time {
flex: none;
white-space: nowrap;
font-size: 0.68rem;
color: var(--ds-muted);
font-family: var(--mono);
}
.message-image {
display: block;
/* #71: rem, not px -- scales with the text-size setting (see
lib/theme.ts's applyTextScale), same as every other size in this app.
min(...) still caps against the viewport in absolute px, since a
percentage-of-viewport constraint isn't something a root font-size
change should affect. */
max-width: min(20rem, 100%);
max-height: 15rem;
object-fit: contain;
border-radius: var(--radius);
border: 1px solid var(--ds-border);
cursor: zoom-in;
margin-bottom: 4px;
}
.message-video-wrap {
position: relative;
display: inline-block;
max-width: min(20rem, 100%);
margin-bottom: 4px;
}
.message-video {
display: block;
width: 100%;
max-height: 15rem;
border-radius: var(--radius);
border: 1px solid var(--ds-border);
background: var(--ds-void);
}
.message-video-expand {
position: absolute;
top: 6px;
right: 6px;
display: flex;
align-items: center;
justify-content: center;
padding: 5px;
background: rgba(0, 0, 0, 0.6);
border: none;
border-radius: 6px;
color: white;
cursor: pointer;
opacity: 0;
transition: opacity 0.15s ease;
}
.message-video-wrap:hover .message-video-expand,
.message-video-expand:focus-visible {
opacity: 1;
}
.message-file-attachment {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--ds-surface-2);
border: 1px solid var(--ds-border);
border-radius: var(--radius);
padding: 8px 12px;
margin-bottom: 4px;
color: var(--ds-text);
text-decoration: none;
max-width: min(20rem, 100%);
}
.message-file-attachment:hover {
border-color: var(--ds-accent);
}
.message-file-attachment svg {
flex: none;
color: var(--ds-muted);
}
.message-file-info {
display: flex;
flex-direction: column;
min-width: 0;
}
.message-file-name {
font-size: 0.85rem;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.message-file-size {
font-size: 0.72rem;
color: var(--ds-muted);
font-family: var(--mono);
}
.message-text {
font-size: 0.88rem;
line-height: 1.45;
color: var(--ds-text);
word-break: break-word;
}
.message-text p {
margin: 0 0 0.4em;
}
.message-text p:last-child {
margin-bottom: 0;
}
.message-mention {
background: color-mix(in srgb, var(--ds-highlight) 18%, transparent);
color: var(--ds-highlight);
border-radius: 5px;
padding: 0 4px;
font-weight: 700;
}
.message-room-reference {
background: color-mix(in srgb, var(--ds-accent) 18%, transparent);
color: var(--ds-accent);
border-radius: 5px;
padding: 0 4px;
font-weight: 700;
text-decoration: none;
}
.message-room-reference:hover {
text-decoration: underline;
}
.message-text code {
background: var(--ds-surface-2);
padding: 1px 5px;
border-radius: 5px;
font-family: var(--mono);
font-size: 0.85em;
}
.message-text pre {
background: var(--ds-surface-2);
padding: 8px 10px;
border-radius: 8px;
overflow-x: auto;
margin: 0.3em 0;
}
.message-text pre code {
background: none;
padding: 0;
font-size: 0.82em;
}
.message-text blockquote {
border-left: 3px solid var(--ds-border);
margin: 0.3em 0;
padding: 0 0.7em;
color: var(--ds-muted);
}
.message-text ul,
.message-text ol {
margin: 0.2em 0;
padding-left: 1.3em;
}
.message-text a {
color: var(--ds-accent);
word-break: break-all;
}
.message-text h1,
.message-text h2,
.message-text h3,
.message-text h4,
.message-text h5,
.message-text h6 {
font-weight: 700;
margin: 0.3em 0;
line-height: 1.3;
}
/* Capped well below normal HTML heading sizes so a heading in chat doesn't
dwarf the message row, but each level still steps down from the last so
the hierarchy stays visible. */
.message-text h1 {
font-size: 1.3em;
}
.message-text h2 {
font-size: 1.2em;
}
.message-text h3 {
font-size: 1.1em;
}
.message-text h4 {
font-size: 1.02em;
}
.message-text h5 {
font-size: 0.95em;
}
.message-text h6 {
font-size: 0.88em;
color: var(--ds-muted);
}
.message-text table {
border-collapse: collapse;
margin: 0.3em 0;
font-size: 0.85em;
max-width: 100%;
overflow-x: auto;
}
.message-text th,
.message-text td {
border: 1px solid var(--ds-border);
padding: 4px 8px;
text-align: left;
}
.message-text th {
background: var(--ds-surface-2);
font-weight: 700;
}
.message-text mark {
background: color-mix(in srgb, var(--ds-accent) 35%, transparent);
color: var(--ds-text);
padding: 0 2px;
border-radius: 3px;
}
.message-text li:has(> input[type='checkbox']) {
list-style: none;
margin-left: -1.3em;
}
.message-text input[type='checkbox'] {
accent-color: var(--ds-accent);
margin-right: 4px;
vertical-align: middle;
}
.message-text footer {
margin-top: 0.4em;
padding-top: 0.3em;
border-top: 1px solid var(--ds-border);
font-size: 0.8em;
color: var(--ds-muted);
}
.message-text footer div {
margin: 2px 0;
}
.message-edited {
font-style: italic;
color: var(--ds-muted);
font-size: 0.78rem;
}
.message-row-actions {
display: none;
position: absolute;
top: 2px;
right: var(--sp-2);
align-items: center;
gap: 4px;
}
.message-row:hover .message-row-actions {
display: flex;
}
.message-edit-link {
background: var(--ds-surface-2);
border: 1px solid var(--ds-border);
color: var(--ds-muted);
font-size: 0.68rem;
cursor: pointer;
padding: 2px 8px;
border-radius: 6px;
}
.message-edit-link:hover {
color: var(--ds-text);
border-color: var(--ds-accent);
}
.message-delete-link {
background: var(--ds-surface-2);
border: 1px solid var(--ds-border);
color: var(--ds-muted);
font-size: 0.68rem;
cursor: pointer;
padding: 2px 8px;
border-radius: 6px;
}
.message-delete-link:hover {
color: var(--ds-danger);
border-color: var(--ds-danger);
}
.message-deleted-text {
color: var(--ds-muted);
}
.message-reaction-wrap {
position: relative;
}
.message-reaction-trigger {
background: var(--ds-surface-2);
border: 1px solid var(--ds-border);
color: var(--ds-muted);
font-size: 0.78rem;
line-height: 1;
cursor: pointer;
padding: 2px 6px;
border-radius: 6px;
}
.message-reaction-trigger:hover {
border-color: var(--ds-accent);
}
.message-reaction-pills {
position: relative;
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 4px;
}
.message-reaction-pill {
display: flex;
align-items: center;
gap: 4px;
background: var(--ds-surface-2);
border: 1px solid var(--ds-border);
border-radius: var(--radius-pill);
color: var(--ds-muted);
font-size: 0.76rem;
padding: 2px 8px;
cursor: pointer;
}
.message-reaction-pill:hover {
border-color: var(--ds-accent);
}
.message-reaction-pill-mine {
border-color: var(--ds-accent);
color: var(--ds-text);
}
.message-edit-input {
background: var(--ds-surface-2);
border: 1px solid var(--ds-accent);
border-radius: 6px;
padding: 6px 10px;
font-size: 0.88rem;
color: var(--ds-text);
font-family: var(--sans);
width: 100%;
resize: vertical;
}
/* The picker anchors to the row of pills, not to this button: the button's
position moves right with every added reaction, and a picker hanging
off it ran past the edge of a phone screen. (No `position` here on
purpose -- .message-reaction-wrap's relative would re-anchor it.) */
.message-reaction-add-wrap {
display: contents;
}
/* #78: always visible once a message has reactions, so adding to the pile
doesn't need the hover-row button. Same shape as the pills it sits
beside, kept quieter so it doesn't read as another reaction. */
.message-reaction-add {
gap: 2px;
opacity: 0.8;
}
.message-reaction-add:hover,
.message-reaction-add:focus-visible {
opacity: 1;
}