mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-04 22:40:29 +03:00
add presence component
This commit is contained in:
parent
2e69106283
commit
507f41cb8b
4 changed files with 161 additions and 0 deletions
80
src/app/components/presence/Presence.tsx
Normal file
80
src/app/components/presence/Presence.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import {
|
||||||
|
as,
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
|
color,
|
||||||
|
ContainerColor,
|
||||||
|
MainColor,
|
||||||
|
Text,
|
||||||
|
Tooltip,
|
||||||
|
TooltipProvider,
|
||||||
|
toRem,
|
||||||
|
} from 'folds';
|
||||||
|
import React, { ReactNode, useId } from 'react';
|
||||||
|
import * as css from './styles.css';
|
||||||
|
import { Presence, usePresenceLabel } from '../../hooks/useUserPresence';
|
||||||
|
|
||||||
|
const PresenceToColor: Record<Presence, MainColor> = {
|
||||||
|
[Presence.Online]: 'Success',
|
||||||
|
[Presence.Unavailable]: 'Warning',
|
||||||
|
[Presence.Offline]: 'Secondary',
|
||||||
|
};
|
||||||
|
|
||||||
|
type PresenceBadgeProps = {
|
||||||
|
presence: Presence;
|
||||||
|
status?: string;
|
||||||
|
size?: '200' | '300' | '400' | '500';
|
||||||
|
};
|
||||||
|
export function PresenceBadge({ presence, status, size }: PresenceBadgeProps) {
|
||||||
|
const label = usePresenceLabel();
|
||||||
|
const badgeLabelId = useId();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TooltipProvider
|
||||||
|
position="Right"
|
||||||
|
align="Center"
|
||||||
|
offset={4}
|
||||||
|
delay={200}
|
||||||
|
tooltip={
|
||||||
|
<Tooltip id={badgeLabelId}>
|
||||||
|
<Box style={{ maxWidth: toRem(250) }} alignItems="Baseline" gap="100">
|
||||||
|
<Text size="L400">{label[presence]}</Text>
|
||||||
|
{status && <Text size="T200">•</Text>}
|
||||||
|
{status && <Text size="T200">{status}</Text>}
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(triggerRef) => (
|
||||||
|
<Badge
|
||||||
|
aria-labelledby={badgeLabelId}
|
||||||
|
ref={triggerRef}
|
||||||
|
size={size}
|
||||||
|
variant={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>
|
||||||
|
)
|
||||||
|
);
|
||||||
1
src/app/components/presence/index.ts
Normal file
1
src/app/components/presence/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './Presence';
|
||||||
22
src/app/components/presence/styles.css.ts
Normal file
22
src/app/components/presence/styles.css.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
import { config } from 'folds';
|
||||||
|
|
||||||
|
export const AvatarPresence = style({
|
||||||
|
display: 'flex',
|
||||||
|
position: 'relative',
|
||||||
|
flexShrink: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AvatarPresenceBadge = style({
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
transform: 'translate(25%, 25%)',
|
||||||
|
zIndex: 1,
|
||||||
|
|
||||||
|
display: 'flex',
|
||||||
|
padding: config.borderWidth.B600,
|
||||||
|
backgroundColor: 'inherit',
|
||||||
|
borderRadius: config.radii.Pill,
|
||||||
|
overflow: 'hidden',
|
||||||
|
});
|
||||||
58
src/app/hooks/useUserPresence.ts
Normal file
58
src/app/hooks/useUserPresence.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { User, UserEvent, UserEventHandlerMap } from 'matrix-js-sdk';
|
||||||
|
import { useMatrixClient } from './useMatrixClient';
|
||||||
|
|
||||||
|
export enum Presence {
|
||||||
|
Online = 'online',
|
||||||
|
Unavailable = 'unavailable',
|
||||||
|
Offline = 'offline',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserPresence = {
|
||||||
|
presence: Presence;
|
||||||
|
status?: string;
|
||||||
|
active: boolean;
|
||||||
|
lastActiveTs?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserPresence = (user: User): UserPresence => ({
|
||||||
|
presence: user.presence as Presence,
|
||||||
|
status: user.presenceStatusMsg,
|
||||||
|
active: user.currentlyActive,
|
||||||
|
lastActiveTs: user.getLastActiveTs(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useUserPresence = (userId: string): UserPresence | undefined => {
|
||||||
|
const mx = useMatrixClient();
|
||||||
|
const user = mx.getUser(userId);
|
||||||
|
|
||||||
|
const [presence, setPresence] = useState(() => (user ? getUserPresence(user) : undefined));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updatePresence: UserEventHandlerMap[UserEvent.Presence] = (event, u) => {
|
||||||
|
if (u.userId === user?.userId) {
|
||||||
|
setPresence(getUserPresence(user));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
user?.on(UserEvent.Presence, updatePresence);
|
||||||
|
user?.on(UserEvent.CurrentlyActive, updatePresence);
|
||||||
|
user?.on(UserEvent.LastPresenceTs, updatePresence);
|
||||||
|
return () => {
|
||||||
|
user?.removeListener(UserEvent.Presence, updatePresence);
|
||||||
|
user?.removeListener(UserEvent.CurrentlyActive, updatePresence);
|
||||||
|
user?.removeListener(UserEvent.LastPresenceTs, updatePresence);
|
||||||
|
};
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
return presence;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePresenceLabel = (): Record<Presence, string> =>
|
||||||
|
useMemo(
|
||||||
|
() => ({
|
||||||
|
[Presence.Online]: 'Active',
|
||||||
|
[Presence.Unavailable]: 'Busy',
|
||||||
|
[Presence.Offline]: 'Away',
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue