--- /dev/null
+import { ReactNode, useCallback } from 'react';
+import { matchPath, useLocation, useNavigate } from 'react-router-dom';
+import {
+ getDirectPath,
+ getExplorePath,
+ getHomePath,
+ getInboxPath,
+ getSpacePath,
+} from '../pages/pathUtils';
+import { DIRECT_PATH, EXPLORE_PATH, HOME_PATH, INBOX_PATH, SPACE_PATH } from '../pages/paths';
+
+type BackRouteHandlerProps = {
+ children: (onBack: () => void) => ReactNode;
+};
+export function BackRouteHandler({ children }: BackRouteHandlerProps) {
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const goBack = useCallback(() => {
+ if (
+ matchPath(
+ {
+ path: HOME_PATH,
+ caseSensitive: true,
+ end: false,
+ },
+ location.pathname
+ )
+ ) {
+ navigate(getHomePath());
+ return;
+ }
+ if (
+ matchPath(
+ {
+ path: DIRECT_PATH,
+ caseSensitive: true,
+ end: false,
+ },
+ location.pathname
+ )
+ ) {
+ navigate(getDirectPath());
+ return;
+ }
+ const spaceMatch = matchPath(
+ {
+ path: SPACE_PATH,
+ caseSensitive: true,
+ end: false,
+ },
+ location.pathname
+ );
+ if (spaceMatch?.params.spaceIdOrAlias) {
+ navigate(getSpacePath(spaceMatch.params.spaceIdOrAlias));
+ return;
+ }
+ if (
+ matchPath(
+ {
+ path: EXPLORE_PATH,
+ caseSensitive: true,
+ end: false,
+ },
+ location.pathname
+ )
+ ) {
+ navigate(getExplorePath());
+ return;
+ }
+ if (
+ matchPath(
+ {
+ path: INBOX_PATH,
+ caseSensitive: true,
+ end: false,
+ },
+ location.pathname
+ )
+ ) {
+ navigate(getInboxPath());
+ }
+ }, [navigate, location]);
+
+ return children(goBack);
+}
/>
));
-export const PageHeader = as<'div'>(({ className, ...props }, ref) => (
- <Header
- as="header"
- size="600"
- className={classNames(css.PageHeader, className)}
- {...props}
- ref={ref}
- />
-));
+export const PageHeader = as<'div', css.PageHeaderVariants>(
+ ({ className, balance, ...props }, ref) => (
+ <Header
+ as="header"
+ size="600"
+ className={classNames(css.PageHeader({ balance }), className)}
+ {...props}
+ ref={ref}
+ />
+ )
+);
export const PageContent = as<'div'>(({ className, ...props }, ref) => (
<div className={classNames(css.PageContent, className)} {...props} ref={ref} />
import { style } from '@vanilla-extract/css';
+import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
import { DefaultReset, color, config, toRem } from 'folds';
export const PageNav = style({
paddingBottom: config.space.S700,
});
-export const PageHeader = style({
- paddingLeft: config.space.S400,
- paddingRight: config.space.S200,
- borderBottomWidth: config.borderWidth.B300,
+export const PageHeader = recipe({
+ base: {
+ paddingLeft: config.space.S400,
+ paddingRight: config.space.S200,
+ borderBottomWidth: config.borderWidth.B300,
+ },
+ variants: {
+ balance: {
+ true: {
+ paddingLeft: config.space.S200,
+ },
+ },
+ },
});
+export type PageHeaderVariants = RecipeVariants<typeof PageHeader>;
export const PageContent = style([
DefaultReset,
import React from 'react';
-import { Box, Scroll, Text, toRem } from 'folds';
+import { Box, Icon, IconButton, Icons, Scroll, Text, toRem } from 'folds';
import { useAtomValue } from 'jotai';
import { RoomCard } from '../../components/room-card';
import { RoomTopicViewer } from '../../components/room-topic-viewer';
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { allRoomsAtom } from '../../state/room-list/roomList';
+import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../components/BackRouteHandler';
type JoinBeforeNavigateProps = { roomIdOrAlias: string; eventId?: string; viaServers?: string[] };
export function JoinBeforeNavigate({
const mx = useMatrixClient();
const allRooms = useAtomValue(allRoomsAtom);
const { navigateRoom, navigateSpace } = useRoomNavigate();
+ const screenSize = useScreenSizeContext();
const handleView = (roomId: string) => {
if (mx.getRoom(roomId)?.isSpaceRoom()) {
return (
<Page>
- <PageHeader>
- <Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
- <Text size="H3" truncate>
- {roomIdOrAlias}
- </Text>
+ <PageHeader balance>
+ <Box grow="Yes" gap="200">
+ <Box shrink="No">
+ {screenSize === ScreenSize.Mobile && (
+ <BackRouteHandler>
+ {(onBack) => (
+ <IconButton onClick={onBack}>
+ <Icon src={Icons.ArrowLeft} />
+ </IconButton>
+ )}
+ </BackRouteHandler>
+ )}
+ </Box>
+ <Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
+ <Text size="H3" truncate>
+ {roomIdOrAlias}
+ </Text>
+ </Box>
</Box>
</PageHeader>
<Box grow="Yes">
import { UseStateProvider } from '../../components/UseStateProvider';
import { LeaveSpacePrompt } from '../../components/leave-space-prompt';
import { stopPropagation } from '../../utils/keyboard';
+import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../components/BackRouteHandler';
type LobbyMenuProps = {
roomId: string;
const space = useSpace();
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
+ const screenSize = useScreenSizeContext();
const name = useRoomName(space);
const avatarMxc = useRoomAvatar(space);
};
return (
- <PageHeader className={showProfile ? undefined : css.Header}>
+ <PageHeader className={showProfile ? undefined : css.Header} balance>
<Box grow="Yes" alignItems="Center" gap="200">
- <Box grow="Yes" basis="No" />
- <Box justifyContent="Center" alignItems="Center" gap="300">
- {showProfile && (
- <>
- <Avatar size="300">
- <RoomAvatar
- roomId={space.roomId}
- src={avatarUrl}
- alt={name}
- renderFallback={() => <Text size="H4">{nameInitials(name)}</Text>}
- />
- </Avatar>
- <Text size="H3" truncate>
- {name}
- </Text>
- </>
+ {screenSize === ScreenSize.Mobile ? (
+ <>
+ <Box shrink="No">
+ <BackRouteHandler>
+ {(onBack) => (
+ <IconButton onClick={onBack}>
+ <Icon src={Icons.ArrowLeft} />
+ </IconButton>
+ )}
+ </BackRouteHandler>
+ </Box>
+ <Box grow="Yes" justifyContent="Center">
+ {showProfile && (
+ <Text size="H3" truncate>
+ {name}
+ </Text>
+ )}
+ </Box>
+ </>
+ ) : (
+ <>
+ <Box grow="Yes" basis="No" />
+ <Box justifyContent="Center" alignItems="Center" gap="300">
+ {showProfile && (
+ <>
+ <Avatar size="300">
+ <RoomAvatar
+ roomId={space.roomId}
+ src={avatarUrl}
+ alt={name}
+ renderFallback={() => <Text size="H4">{nameInitials(name)}</Text>}
+ />
+ </Avatar>
+ <Text size="H3" truncate>
+ {name}
+ </Text>
+ </>
+ )}
+ </Box>
+ </>
+ )}
+ <Box
+ shrink="No"
+ grow={screenSize === ScreenSize.Mobile ? 'No' : 'Yes'}
+ basis={screenSize === ScreenSize.Mobile ? 'Yes' : 'No'}
+ justifyContent="End"
+ >
+ {screenSize !== ScreenSize.Mobile && (
+ <TooltipProvider
+ position="Bottom"
+ offset={4}
+ tooltip={
+ <Tooltip>
+ <Text>Members</Text>
+ </Tooltip>
+ }
+ >
+ {(triggerRef) => (
+ <IconButton ref={triggerRef} onClick={() => setPeopleDrawer((drawer) => !drawer)}>
+ <Icon size="400" src={Icons.User} />
+ </IconButton>
+ )}
+ </TooltipProvider>
)}
- </Box>
- <Box shrink="No" grow="Yes" basis="No" justifyContent="End">
- <TooltipProvider
- position="Bottom"
- offset={4}
- tooltip={
- <Tooltip>
- <Text>Members</Text>
- </Tooltip>
- }
- >
- {(triggerRef) => (
- <IconButton ref={triggerRef} onClick={() => setPeopleDrawer((drawer) => !drawer)}>
- <Icon size="400" src={Icons.User} />
- </IconButton>
- )}
- </TooltipProvider>
<TooltipProvider
position="Bottom"
align="End"
import { stopPropagation } from '../../utils/keyboard';
import { getMatrixToRoom } from '../../plugins/matrix-to';
import { getViaServers } from '../../plugins/via-servers';
+import { BackRouteHandler } from '../../components/BackRouteHandler';
type RoomMenuProps = {
room: Room;
};
return (
- <PageHeader>
+ <PageHeader balance={screenSize === ScreenSize.Mobile}>
<Box grow="Yes" gap="300">
+ {screenSize === ScreenSize.Mobile && (
+ <BackRouteHandler>
+ {(onBack) => (
+ <Box shrink="No" alignItems="Center">
+ <IconButton onClick={onBack}>
+ <Icon src={Icons.ArrowLeft} />
+ </IconButton>
+ </Box>
+ )}
+ </BackRouteHandler>
+ )}
<Box grow="Yes" alignItems="Center" gap="300">
- <Avatar size="300">
- <RoomAvatar
- roomId={room.roomId}
- src={avatarUrl}
- alt={name}
- renderFallback={() => (
- <RoomIcon size="200" joinRule={room.getJoinRule() ?? JoinRule.Restricted} filled />
- )}
- />
- </Avatar>
+ {screenSize !== ScreenSize.Mobile && (
+ <Avatar size="300">
+ <RoomAvatar
+ roomId={room.roomId}
+ src={avatarUrl}
+ alt={name}
+ renderFallback={() => (
+ <RoomIcon
+ size="200"
+ joinRule={room.getJoinRule() ?? JoinRule.Restricted}
+ filled
+ />
+ )}
+ />
+ </Avatar>
+ )}
<Box direction="Column">
<Text size={topic ? 'H5' : 'H3'} truncate>
{name}
import React from 'react';
-import { Box, Icon, Icons, Scroll, Text } from 'folds';
+import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds';
import { useAtomValue } from 'jotai';
import { useClientConfig } from '../../../hooks/useClientConfig';
import { RoomCard, RoomCardGrid } from '../../../components/room-card';
Page,
PageContent,
PageContentCenter,
+ PageHeader,
PageHero,
PageHeroSection,
} from '../../../components/page';
import { RoomTopicViewer } from '../../../components/room-topic-viewer';
import * as css from './style.css';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
export function FeaturedRooms() {
const { featuredCommunities } = useClientConfig();
const { rooms, spaces } = featuredCommunities ?? {};
const allRooms = useAtomValue(allRoomsAtom);
+ const screenSize = useScreenSizeContext();
const { navigateSpace, navigateRoom } = useRoomNavigate();
return (
<Page>
+ {screenSize === ScreenSize.Mobile && (
+ <PageHeader>
+ <Box shrink="No">
+ <BackRouteHandler>
+ {(onBack) => (
+ <IconButton onClick={onBack}>
+ <Icon src={Icons.ArrowLeft} />
+ </IconButton>
+ )}
+ </BackRouteHandler>
+ </Box>
+ </PageHeader>
+ )}
<Box grow="Yes">
<Scroll hideTrack visibility="Hover">
<PageContent>
Button,
Chip,
Icon,
+ IconButton,
Icons,
Input,
Line,
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { getMxIdServer } from '../../../utils/matrix';
import { stopPropagation } from '../../../utils/keyboard';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
const useServerSearchParams = (searchParams: URLSearchParams): ExploreServerPathSearchParams =>
useMemo(
const userServer = userId && getMxIdServer(userId);
const allRooms = useAtomValue(allRoomsAtom);
const { navigateSpace, navigateRoom } = useRoomNavigate();
+ const screenSize = useScreenSizeContext();
const [searchParams] = useSearchParams();
const serverSearchParams = useServerSearchParams(searchParams);
return (
<Page>
- <PageHeader>
+ <PageHeader balance>
{isSearch ? (
<>
<Box grow="Yes" basis="No">
</Box>
<Box grow="No" justifyContent="Center" alignItems="Center" gap="200">
- <Icon size="400" src={Icons.Search} />
+ {screenSize !== ScreenSize.Mobile && <Icon size="400" src={Icons.Search} />}
<Text size="H3" truncate>
Search
</Text>
</Box>
- <Box grow="Yes" />
+ <Box grow="Yes" basis="No" />
</>
) : (
- <Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
- <Icon size="400" src={Icons.Category} />
- <Text size="H3" truncate>
- {server}
- </Text>
- </Box>
+ <>
+ <Box grow="Yes" basis="No">
+ {screenSize === ScreenSize.Mobile && (
+ <BackRouteHandler>
+ {(onBack) => (
+ <IconButton onClick={onBack}>
+ <Icon src={Icons.ArrowLeft} />
+ </IconButton>
+ )}
+ </BackRouteHandler>
+ )}
+ </Box>
+ <Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
+ {screenSize !== ScreenSize.Mobile && <Icon size="400" src={Icons.Category} />}
+ <Text size="H3" truncate>
+ {server}
+ </Text>
+ </Box>
+ <Box grow="Yes" basis="No" />
+ </>
)}
</PageHeader>
<Box grow="Yes">
import React, { useRef } from 'react';
-import { Box, Icon, Icons, Text, Scroll } from 'folds';
+import { Box, Icon, Icons, Text, Scroll, IconButton } from 'folds';
import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page';
import { MessageSearch } from '../../../features/message-search';
import { useHomeRooms } from './useHomeRooms';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
export function HomeSearch() {
const scrollRef = useRef<HTMLDivElement>(null);
const rooms = useHomeRooms();
+ const screenSize = useScreenSizeContext();
return (
<Page>
- <PageHeader>
- <Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
- <Icon size="400" src={Icons.Search} />
- <Text size="H3" truncate>
- Message Search
- </Text>
+ <PageHeader balance>
+ <Box grow="Yes" alignItems="Center" gap="200">
+ <Box grow="Yes" basis="No">
+ {screenSize === ScreenSize.Mobile && (
+ <BackRouteHandler>
+ {(onBack) => (
+ <IconButton onClick={onBack}>
+ <Icon src={Icons.ArrowLeft} />
+ </IconButton>
+ )}
+ </BackRouteHandler>
+ )}
+ </Box>
+ <Box justifyContent="Center" alignItems="Center" gap="200">
+ {screenSize !== ScreenSize.Mobile && <Icon size="400" src={Icons.Search} />}
+ <Text size="H3" truncate>
+ Message Search
+ </Text>
+ </Box>
+ <Box grow="Yes" basis="No" />
</Box>
</PageHeader>
<Box style={{ position: 'relative' }} grow="Yes">
Box,
Button,
Icon,
+ IconButton,
Icons,
Overlay,
OverlayBackdrop,
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { useRoomTopic } from '../../../hooks/useRoomMeta';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
const COMPACT_CARD_WIDTH = 548;
useCallback(() => containerRef.current, []),
useCallback((width) => setCompact(width <= COMPACT_CARD_WIDTH), [])
);
+ const screenSize = useScreenSizeContext();
const { navigateRoom, navigateSpace } = useRoomNavigate();
return (
<Page>
- <PageHeader>
- <Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
- <Icon size="400" src={Icons.Mail} />
- <Text size="H3" truncate>
- Invitations
- </Text>
+ <PageHeader balance>
+ <Box grow="Yes" gap="200">
+ <Box grow="Yes" basis="No">
+ {screenSize === ScreenSize.Mobile && (
+ <BackRouteHandler>
+ {(onBack) => (
+ <IconButton onClick={onBack}>
+ <Icon src={Icons.ArrowLeft} />
+ </IconButton>
+ )}
+ </BackRouteHandler>
+ )}
+ </Box>
+ <Box alignItems="Center" gap="200">
+ {screenSize !== ScreenSize.Mobile && <Icon size="400" src={Icons.Mail} />}
+ <Text size="H3" truncate>
+ Invitations
+ </Text>
+ </Box>
+ <Box grow="Yes" basis="No" />
</Box>
</PageHeader>
<Box grow="Yes">
import { EncryptedContent } from '../../../features/room/message';
import { useMentionClickHandler } from '../../../hooks/useMentionClickHandler';
import { useSpoilerClickHandler } from '../../../hooks/useSpoilerClickHandler';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
type RoomNotificationsGroup = {
roomId: string;
const mx = useMatrixClient();
const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
+ const screenSize = useScreenSizeContext();
const { navigateRoom } = useRoomNavigate();
const [searchParams, setSearchParams] = useSearchParams();
return (
<Page>
- <PageHeader>
- <Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
- <Icon size="400" src={Icons.Message} />
- <Text size="H3" truncate>
- Notification Messages
- </Text>
+ <PageHeader balance>
+ <Box grow="Yes" gap="200">
+ <Box grow="Yes" basis="No">
+ {screenSize === ScreenSize.Mobile && (
+ <BackRouteHandler>
+ {(onBack) => (
+ <IconButton onClick={onBack}>
+ <Icon src={Icons.ArrowLeft} />
+ </IconButton>
+ )}
+ </BackRouteHandler>
+ )}
+ </Box>
+ <Box alignItems="Center" gap="200">
+ {screenSize !== ScreenSize.Mobile && <Icon size="400" src={Icons.Message} />}
+ <Text size="H3" truncate>
+ Notification Messages
+ </Text>
+ </Box>
+ <Box grow="Yes" basis="No" />
</Box>
</PageHeader>
import React, { useRef } from 'react';
-import { Box, Icon, Icons, Text, Scroll } from 'folds';
+import { Box, Icon, Icons, Text, Scroll, IconButton } from 'folds';
import { useAtomValue } from 'jotai';
import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page';
import { MessageSearch } from '../../../features/message-search';
import { mDirectAtom } from '../../../state/mDirectList';
import { roomToParentsAtom } from '../../../state/room/roomToParents';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
export function SpaceSearch() {
const mx = useMatrixClient();
const scrollRef = useRef<HTMLDivElement>(null);
const space = useSpace();
+ const screenSize = useScreenSizeContext();
const mDirects = useAtomValue(mDirectAtom);
const roomToParents = useAtomValue(roomToParentsAtom);
return (
<Page>
- <PageHeader>
- <Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
- <Icon size="400" src={Icons.Search} />
- <Text size="H3" truncate>
- Message Search
- </Text>
+ <PageHeader balance>
+ <Box grow="Yes" alignItems="Center" gap="200">
+ <Box grow="Yes" basis="No">
+ {screenSize === ScreenSize.Mobile && (
+ <BackRouteHandler>
+ {(onBack) => (
+ <IconButton onClick={onBack}>
+ <Icon src={Icons.ArrowLeft} />
+ </IconButton>
+ )}
+ </BackRouteHandler>
+ )}
+ </Box>
+ <Box justifyContent="Center" alignItems="Center" gap="200">
+ {screenSize !== ScreenSize.Mobile && <Icon size="400" src={Icons.Search} />}
+ <Text size="H3" truncate>
+ Message Search
+ </Text>
+ </Box>
+ <Box grow="Yes" basis="No" />
</Box>
</PageHeader>
<Box style={{ position: 'relative' }} grow="Yes">