feat: presence avatar border rings (P5-18) + room emoji prefix support (P5-6)
P5-18: PresenceRingAvatar wrapper component applies a 2px box-shadow ring to user avatars — green (online), yellow (idle/unavailable), red (DND via status_msg='dnd'), no ring (offline). Applied to: message timeline sender avatars, members drawer (members + knock requests), @mention autocomplete, and inbox notification senders. P5-6: Leading emoji in room names renders at 1.15× in the sidebar via Unicode emoji regex detection in RoomNavItem. Emoji picker (EmojiBoard in PopOut) added to all three room-name inputs: Create Room dialog (converted to controlled input), Room Settings name field (shown only when canEditName), and the "Rename for me" local rename dialog. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,13 +6,17 @@ import {
|
||||
color,
|
||||
config,
|
||||
Icon,
|
||||
IconButton,
|
||||
Icons,
|
||||
Input,
|
||||
PopOut,
|
||||
RectCords,
|
||||
Spinner,
|
||||
Text,
|
||||
TextArea,
|
||||
} from 'folds';
|
||||
import React, { FormEventHandler, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { EmojiBoard } from '../../../components/emoji-board';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import Linkify from 'linkify-react';
|
||||
import classNames from 'classnames';
|
||||
@@ -113,6 +117,14 @@ export function RoomProfileEdit({
|
||||
? (mxcUrlToHttp(mx, roomAvatar, useAuthentication) ?? undefined)
|
||||
: undefined;
|
||||
|
||||
const [nameValue, setNameValue] = useState(name);
|
||||
const [emojiAnchor, setEmojiAnchor] = useState<RectCords>();
|
||||
|
||||
const handleEmojiSelect = useCallback((unicode: string) => {
|
||||
setNameValue((prev) => unicode + prev);
|
||||
setEmojiAnchor(undefined);
|
||||
}, []);
|
||||
|
||||
const topicRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [imageFile, setImageFile] = useState<File>();
|
||||
const avatarFileUrl = useObjectURL(imageFile);
|
||||
@@ -159,11 +171,10 @@ export function RoomProfileEdit({
|
||||
if (uploadingAvatar) return;
|
||||
|
||||
const target = evt.target as HTMLFormElement | undefined;
|
||||
const nameInput = target?.nameInput as HTMLInputElement | undefined;
|
||||
const topicTextArea = target?.topicTextArea as HTMLTextAreaElement | undefined;
|
||||
if (!nameInput || !topicTextArea) return;
|
||||
if (!topicTextArea) return;
|
||||
|
||||
const roomName = nameInput.value.trim();
|
||||
const roomName = nameValue.trim();
|
||||
const roomTopic = topicTextArea.value.trim();
|
||||
|
||||
if (roomAvatar === avatar && roomName === name && roomTopic === topic) {
|
||||
@@ -256,13 +267,52 @@ export function RoomProfileEdit({
|
||||
</Box>
|
||||
<Box direction="Inherit" gap="100">
|
||||
<Text size="L400">Name</Text>
|
||||
<Input
|
||||
name="nameInput"
|
||||
defaultValue={name}
|
||||
variant="Secondary"
|
||||
radii="300"
|
||||
readOnly={!canEditName || submitting}
|
||||
/>
|
||||
<Box direction="Row" gap="100" alignItems="Center">
|
||||
{canEditName && !submitting && (
|
||||
<PopOut
|
||||
anchor={emojiAnchor}
|
||||
position="Top"
|
||||
align="End"
|
||||
content={
|
||||
<EmojiBoard
|
||||
imagePackRooms={[]}
|
||||
returnFocusOnDeactivate={false}
|
||||
onEmojiSelect={handleEmojiSelect}
|
||||
requestClose={() => setEmojiAnchor(undefined)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
type="button"
|
||||
size="400"
|
||||
radii="400"
|
||||
variant="Surface"
|
||||
fill="Soft"
|
||||
outlined
|
||||
aria-label="Insert emoji"
|
||||
aria-expanded={!!emojiAnchor}
|
||||
aria-haspopup="dialog"
|
||||
onClick={(evt: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const rect = evt.currentTarget.getBoundingClientRect();
|
||||
setEmojiAnchor((prev) => (prev ? undefined : rect));
|
||||
}}
|
||||
>
|
||||
<Icon src={Icons.Smile} size="400" />
|
||||
</IconButton>
|
||||
</PopOut>
|
||||
)}
|
||||
<Box grow="Yes">
|
||||
<Input
|
||||
name="nameInput"
|
||||
value={nameValue}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setNameValue(e.target.value)}
|
||||
variant="Secondary"
|
||||
radii="300"
|
||||
readOnly={!canEditName || submitting}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box direction="Inherit" gap="100">
|
||||
<Box alignItems="Center" justifyContent="SpaceBetween">
|
||||
|
||||
Reference in New Issue
Block a user