CI / Build & Quality Checks (pull_request) Successful in 1m52s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 8s
CI / Playwright smoke (e2e) (pull_request) Successful in 10m59s
DND was sent as presence unavailable with the literal status text "dnd": it replaced your status, and other clients showed "dnd". Where the server supports MSC3026 (busy_presence), DND now sends presence org.matrix.msc3026.busy and leaves the status alone (Synapse keeps busy across /sync set_presence=online). Busy renders as red "Do Not Disturb" with the real status line; the legacy unavailable + "dnd" encoding is still recognised and still used on servers without it. Status saves / clears no longer downgrade busy. The SDK rejects non-spec presences, so busy goes through a direct authed PUT. Fixes #283 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
import {
|
|
as,
|
|
Badge,
|
|
Box,
|
|
color,
|
|
ContainerColor,
|
|
MainColor,
|
|
Text,
|
|
Tooltip,
|
|
TooltipProvider,
|
|
toRem,
|
|
} from 'folds';
|
|
import React, { ReactNode } from 'react';
|
|
import * as css from './styles.css';
|
|
import { Presence, usePresenceLabel } from '../../hooks/useUserPresence';
|
|
import { isDndPresence } from '../../utils/busyPresence';
|
|
|
|
const PresenceToColor: Record<Presence, MainColor> = {
|
|
[Presence.Online]: 'Success',
|
|
[Presence.Unavailable]: 'Warning',
|
|
[Presence.Offline]: 'Secondary',
|
|
[Presence.Busy]: 'Critical',
|
|
};
|
|
|
|
type PresenceBadgeProps = {
|
|
presence: Presence;
|
|
status?: string;
|
|
size?: '200' | '300' | '400' | '500';
|
|
};
|
|
export function PresenceBadge({ presence, status, size }: PresenceBadgeProps) {
|
|
const label = usePresenceLabel();
|
|
// DND is encoded as unavailable + status_msg 'dnd'; render it red/"Do Not
|
|
// Disturb" to match PresenceRingAvatar and the settings picker (which both
|
|
// special-case 'dnd' → Critical) — the badge was the lone outlier showing a
|
|
// yellow "Idle". The 'dnd' sentinel isn't surfaced as a status line.
|
|
// [Gitea #283] or MSC3026 busy, which keeps a real status line.
|
|
const isDnd = isDndPresence(presence, status);
|
|
const legacyDnd = isDnd && status === 'dnd';
|
|
const displayLabel = isDnd ? 'Do Not Disturb' : label[presence];
|
|
const displayStatus = legacyDnd ? undefined : status;
|
|
const ariaLabel = displayStatus ? `${displayLabel} — ${displayStatus}` : displayLabel;
|
|
|
|
return (
|
|
<TooltipProvider
|
|
position="Right"
|
|
align="Center"
|
|
offset={4}
|
|
delay={200}
|
|
tooltip={
|
|
<Tooltip>
|
|
<Box style={{ maxWidth: toRem(250) }} alignItems="Baseline" gap="100">
|
|
<Text size="L400">{displayLabel}</Text>
|
|
{displayStatus && <Text size="T200">•</Text>}
|
|
{displayStatus && <Text size="T200">{displayStatus}</Text>}
|
|
</Box>
|
|
</Tooltip>
|
|
}
|
|
>
|
|
{(triggerRef) => (
|
|
<Badge
|
|
aria-label={ariaLabel}
|
|
ref={triggerRef}
|
|
size={size}
|
|
variant={isDnd ? 'Critical' : PresenceToColor[presence]}
|
|
fill={presence === Presence.Offline ? 'Soft' : 'Solid'}
|
|
radii="Pill"
|
|
/>
|
|
)}
|
|
</TooltipProvider>
|
|
);
|
|
}
|
|
|
|
type AvatarPresenceProps = {
|
|
badge: ReactNode;
|
|
variant?: ContainerColor;
|
|
};
|
|
export const AvatarPresence = as<'div', AvatarPresenceProps>(
|
|
({ as: AsAvatarPresence, badge, variant = 'Surface', children, ...props }, ref) => (
|
|
<Box as={AsAvatarPresence} className={css.AvatarPresence} {...props} ref={ref}>
|
|
{badge && (
|
|
<div
|
|
className={css.AvatarPresenceBadge}
|
|
style={{ backgroundColor: color[variant].Container }}
|
|
>
|
|
{badge}
|
|
</div>
|
|
)}
|
|
{children}
|
|
</Box>
|
|
),
|
|
);
|