Add a chip and setting for user timezones

This commit is contained in:
Ginger 2025-09-15 13:46:27 -04:00
parent 3c1aa0e699
commit c3901804c0
No known key found for this signature in database
4 changed files with 289 additions and 15 deletions

View file

@ -19,6 +19,9 @@ import {
Box,
Scroll,
Avatar,
TooltipProvider,
Tooltip,
Badge,
} from 'folds';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { getMxIdServer } from '../../utils/matrix';
@ -41,6 +44,7 @@ import { useTimeoutToggle } from '../../hooks/useTimeoutToggle';
import { useIgnoredUsers } from '../../hooks/useIgnoredUsers';
import { CutoutCard } from '../cutout-card';
import { SettingTile } from '../setting-tile';
import { useInterval } from '../../hooks/useInterval';
export function ServerChip({ server }: { server: string }) {
const mx = useMatrixClient();
@ -512,3 +516,66 @@ export function OptionsChip({ userId }: { userId: string }) {
</PopOut>
);
}
export function TimezoneChip({ timezone }: { timezone: string }) {
const shortFormat = useMemo(
() =>
new Intl.DateTimeFormat(undefined, {
dateStyle: undefined,
timeStyle: 'short',
timeZone: timezone,
}),
[timezone]
);
const longFormat = useMemo(
() =>
new Intl.DateTimeFormat(undefined, {
dateStyle: 'long',
timeStyle: 'short',
timeZone: timezone,
}),
[timezone]
);
const [shortTime, setShortTime] = useState(shortFormat.format());
const [longTime, setLongTime] = useState(longFormat.format());
useInterval(() => {
setShortTime(shortFormat.format());
setLongTime(longFormat.format());
}, 1000);
return (
<TooltipProvider
position="Top"
offset={5}
align="Center"
tooltip={
<Tooltip variant='SurfaceVariant' style={{ maxWidth: toRem(280) }}>
<Box direction="Column" alignItems='Start' gap="100">
<Box gap="100">
<Text size="L400">Timezone:</Text>
<Badge size="400" variant="Primary">
<Text size="T200">{timezone}</Text>
</Badge>
</Box>
<Text size="T200">{longTime}</Text>
</Box>
</Tooltip>
}
>
{(triggerRef) => (
<Chip
ref={triggerRef}
variant="SurfaceVariant"
radii="Pill"
style={{ cursor: "initial" }}
before={<Icon size="50" src={Icons.RecentClock} />}
>
<Text size="B300" truncate>
{shortTime}
</Text>
</Chip>
)}
</TooltipProvider>
);
}

View file

@ -21,7 +21,7 @@ import { UserPresence } from '../../hooks/useUserPresence';
import { AvatarPresence, PresenceBadge } from '../presence';
import { ImageViewer } from '../image-viewer';
import { stopPropagation } from '../../utils/keyboard';
import { extendedProfileFields } from '../../hooks/useExtendedProfile';
import { ExtendedProfile } from '../../hooks/useExtendedProfile';
type UserHeroProps = {
userId: string;
@ -96,7 +96,7 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
type UserHeroNameProps = {
displayName?: string;
userId: string;
extendedProfile?: extendedProfileFields;
extendedProfile?: ExtendedProfile;
};
export function UserHeroName({ displayName, userId, extendedProfile }: UserHeroNameProps) {
const username = getMxIdLocalPart(userId);

View file

@ -1,5 +1,5 @@
import { Box, Button, config, Icon, Icons, Text } from 'folds';
import React, { useEffect } from 'react';
import React, { useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { UserHero, UserHeroName } from './UserHero';
import { getMxIdServer, mxcUrlToHttp } from '../../utils/matrix';
@ -9,7 +9,7 @@ import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { usePowerLevels } from '../../hooks/usePowerLevels';
import { useRoom } from '../../hooks/useRoom';
import { useUserPresence } from '../../hooks/useUserPresence';
import { IgnoredUserAlert, MutualRoomsChip, OptionsChip, ServerChip, ShareChip } from './UserChips';
import { IgnoredUserAlert, MutualRoomsChip, OptionsChip, ServerChip, ShareChip, TimezoneChip } from './UserChips';
import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile';
import { PowerChip } from './PowerChip';
import { UserInviteAlert, UserBanAlert, UserModeration, UserKickAlert } from './UserModeration';
@ -60,6 +60,16 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
const avatarUrl = (avatarMxc && mxcUrlToHttp(mx, avatarMxc, useAuthentication)) ?? undefined;
const [extendedProfileState, refreshExtendedProfile] = useExtendedProfile(userId);
const extendedProfile = extendedProfileState.status === AsyncStatus.Success ? extendedProfileState.data : undefined;
const timezone = useMemo(() => {
// @ts-expect-error Intl.supportedValuesOf isn't in the types yet
const supportedTimezones = Intl.supportedValuesOf('timeZone') as string[];
const profileTimezone = extendedProfile?.['us.cloke.msc4175.tz'];
if (profileTimezone && supportedTimezones.includes(profileTimezone)) {
return profileTimezone;
}
return undefined;
}, [extendedProfile]);
const presence = useUserPresence(userId);
@ -107,6 +117,7 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
{creator ? <CreatorChip /> : <PowerChip userId={userId} />}
{userId !== myUserId && <MutualRoomsChip userId={userId} />}
{userId !== myUserId && <OptionsChip userId={userId} />}
{timezone && <TimezoneChip timezone={timezone} />}
</Box>
</Box>
{ignored && <IgnoredUserAlert />}