Redesign space/room creation panel (#2408)
* add new create room * rename create room modal file * default restrict access for space children in room create modal * move create room kind selector to components * add radii variant to sequence card component * more more reusable create room logic to components * add create space * update address input description * add new space modal * fix add room button visible on left room in space lobby
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
||||
_ROOM_PATH,
|
||||
_SEARCH_PATH,
|
||||
_SERVER_PATH,
|
||||
CREATE_PATH,
|
||||
} from './paths';
|
||||
import { isAuthenticated } from '../../client/state/auth';
|
||||
import {
|
||||
@@ -61,6 +62,10 @@ import { AutoRestoreBackupOnVerification } from '../components/BackupRestore';
|
||||
import { RoomSettingsRenderer } from '../features/room-settings';
|
||||
import { ClientRoomsNotificationPreferences } from './client/ClientRoomsNotificationPreferences';
|
||||
import { SpaceSettingsRenderer } from '../features/space-settings';
|
||||
import { CreateRoomModalRenderer } from '../features/create-room';
|
||||
import { HomeCreateRoom } from './client/home/CreateRoom';
|
||||
import { Create } from './client/create';
|
||||
import { CreateSpaceModalRenderer } from '../features/create-space';
|
||||
|
||||
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
|
||||
const { hashRouter } = clientConfig;
|
||||
@@ -125,6 +130,8 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||
>
|
||||
<Outlet />
|
||||
</ClientLayout>
|
||||
<CreateRoomModalRenderer />
|
||||
<CreateSpaceModalRenderer />
|
||||
<RoomSettingsRenderer />
|
||||
<SpaceSettingsRenderer />
|
||||
<ReceiveSelfDeviceVerification />
|
||||
@@ -152,7 +159,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||
}
|
||||
>
|
||||
{mobile ? null : <Route index element={<WelcomePage />} />}
|
||||
<Route path={_CREATE_PATH} element={<p>create</p>} />
|
||||
<Route path={_CREATE_PATH} element={<HomeCreateRoom />} />
|
||||
<Route path={_JOIN_PATH} element={<p>join</p>} />
|
||||
<Route path={_SEARCH_PATH} element={<HomeSearch />} />
|
||||
<Route
|
||||
@@ -253,6 +260,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||
<Route path={_FEATURED_PATH} element={<FeaturedRooms />} />
|
||||
<Route path={_SERVER_PATH} element={<PublicRooms />} />
|
||||
</Route>
|
||||
<Route path={CREATE_PATH} element={<Create />} />
|
||||
<Route
|
||||
path={INBOX_PATH}
|
||||
element={
|
||||
|
||||
@@ -19,7 +19,8 @@ import {
|
||||
SettingsTab,
|
||||
UnverifiedTab,
|
||||
} from './sidebar';
|
||||
import { openCreateRoom, openSearch } from '../../../client/action/navigation';
|
||||
import { openSearch } from '../../../client/action/navigation';
|
||||
import { CreateTab } from './sidebar/CreateTab';
|
||||
|
||||
export function SidebarNav() {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
@@ -37,20 +38,7 @@ export function SidebarNav() {
|
||||
<SidebarStackSeparator />
|
||||
<SidebarStack>
|
||||
<ExploreTab />
|
||||
<SidebarItem>
|
||||
<SidebarItemTooltip tooltip="Create Space">
|
||||
{(triggerRef) => (
|
||||
<SidebarAvatar
|
||||
as="button"
|
||||
ref={triggerRef}
|
||||
outlined
|
||||
onClick={() => openCreateRoom(true)}
|
||||
>
|
||||
<Icon src={Icons.Plus} />
|
||||
</SidebarAvatar>
|
||||
)}
|
||||
</SidebarItemTooltip>
|
||||
</SidebarItem>
|
||||
<CreateTab />
|
||||
</SidebarStack>
|
||||
</Scroll>
|
||||
}
|
||||
|
||||
38
src/app/pages/client/create/Create.tsx
Normal file
38
src/app/pages/client/create/Create.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Box, Icon, Icons, Scroll } from 'folds';
|
||||
import {
|
||||
Page,
|
||||
PageContent,
|
||||
PageContentCenter,
|
||||
PageHero,
|
||||
PageHeroSection,
|
||||
} from '../../../components/page';
|
||||
import { CreateSpaceForm } from '../../../features/create-space';
|
||||
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
|
||||
|
||||
export function Create() {
|
||||
const { navigateSpace } = useRoomNavigate();
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Box grow="Yes">
|
||||
<Scroll hideTrack visibility="Hover">
|
||||
<PageContent>
|
||||
<PageContentCenter>
|
||||
<PageHeroSection>
|
||||
<Box direction="Column" gap="700">
|
||||
<PageHero
|
||||
icon={<Icon size="600" src={Icons.Space} />}
|
||||
title="Create Space"
|
||||
subTitle="Build a space for your community."
|
||||
/>
|
||||
<CreateSpaceForm onCreate={navigateSpace} />
|
||||
</Box>
|
||||
</PageHeroSection>
|
||||
</PageContentCenter>
|
||||
</PageContent>
|
||||
</Scroll>
|
||||
</Box>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
1
src/app/pages/client/create/index.ts
Normal file
1
src/app/pages/client/create/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './Create';
|
||||
56
src/app/pages/client/home/CreateRoom.tsx
Normal file
56
src/app/pages/client/home/CreateRoom.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import { Box, Icon, Icons, Scroll, IconButton } from 'folds';
|
||||
import {
|
||||
Page,
|
||||
PageContent,
|
||||
PageContentCenter,
|
||||
PageHeader,
|
||||
PageHero,
|
||||
PageHeroSection,
|
||||
} from '../../../components/page';
|
||||
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
||||
import { BackRouteHandler } from '../../../components/BackRouteHandler';
|
||||
import { CreateRoomForm } from '../../../features/create-room';
|
||||
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
|
||||
|
||||
export function HomeCreateRoom() {
|
||||
const screenSize = useScreenSizeContext();
|
||||
|
||||
const { navigateRoom } = useRoomNavigate();
|
||||
|
||||
return (
|
||||
<Page>
|
||||
{screenSize === ScreenSize.Mobile && (
|
||||
<PageHeader balance outlined={false}>
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<BackRouteHandler>
|
||||
{(onBack) => (
|
||||
<IconButton onClick={onBack}>
|
||||
<Icon src={Icons.ArrowLeft} />
|
||||
</IconButton>
|
||||
)}
|
||||
</BackRouteHandler>
|
||||
</Box>
|
||||
</PageHeader>
|
||||
)}
|
||||
<Box grow="Yes">
|
||||
<Scroll hideTrack visibility="Hover">
|
||||
<PageContent>
|
||||
<PageContentCenter>
|
||||
<PageHeroSection>
|
||||
<Box direction="Column" gap="700">
|
||||
<PageHero
|
||||
icon={<Icon size="600" src={Icons.Hash} />}
|
||||
title="Create Room"
|
||||
subTitle="Build a Room for Real-Time Conversations"
|
||||
/>
|
||||
<CreateRoomForm onCreate={navigateRoom} />
|
||||
</Box>
|
||||
</PageHeroSection>
|
||||
</PageContentCenter>
|
||||
</PageContent>
|
||||
</Scroll>
|
||||
</Box>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -29,10 +29,18 @@ import {
|
||||
NavItemContent,
|
||||
NavLink,
|
||||
} from '../../../components/nav';
|
||||
import { getExplorePath, getHomeRoomPath, getHomeSearchPath } from '../../pathUtils';
|
||||
import {
|
||||
getExplorePath,
|
||||
getHomeCreatePath,
|
||||
getHomeRoomPath,
|
||||
getHomeSearchPath,
|
||||
} from '../../pathUtils';
|
||||
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
|
||||
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
|
||||
import { useHomeSearchSelected } from '../../../hooks/router/useHomeSelected';
|
||||
import {
|
||||
useHomeCreateSelected,
|
||||
useHomeSearchSelected,
|
||||
} from '../../../hooks/router/useHomeSelected';
|
||||
import { useHomeRooms } from './useHomeRooms';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { VirtualTile } from '../../../components/virtualizer';
|
||||
@@ -41,7 +49,7 @@ import { makeNavCategoryId } from '../../../state/closedNavCategories';
|
||||
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
|
||||
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
|
||||
import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper';
|
||||
import { openCreateRoom, openJoinAlias } from '../../../../client/action/navigation';
|
||||
import { openJoinAlias } from '../../../../client/action/navigation';
|
||||
import { PageNav, PageNavHeader, PageNavContent } from '../../../components/page';
|
||||
import { useRoomsUnread } from '../../../state/hooks/unread';
|
||||
import { markAsRead } from '../../../../client/action/notifications';
|
||||
@@ -174,7 +182,7 @@ function HomeEmpty() {
|
||||
}
|
||||
options={
|
||||
<>
|
||||
<Button onClick={() => openCreateRoom()} variant="Secondary" size="300">
|
||||
<Button onClick={() => navigate(getHomeCreatePath())} variant="Secondary" size="300">
|
||||
<Text size="B300" truncate>
|
||||
Create Room
|
||||
</Text>
|
||||
@@ -204,8 +212,10 @@ export function Home() {
|
||||
const rooms = useHomeRooms();
|
||||
const notificationPreferences = useRoomsNotificationPreferencesContext();
|
||||
const roomToUnread = useAtomValue(roomToUnreadAtom);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const selectedRoomId = useSelectedRoom();
|
||||
const createRoomSelected = useHomeCreateSelected();
|
||||
const searchSelected = useHomeSearchSelected();
|
||||
const noRoomToDisplay = rooms.length === 0;
|
||||
const [closedCategories, setClosedCategories] = useAtom(useClosedNavCategoriesAtom());
|
||||
@@ -242,8 +252,8 @@ export function Home() {
|
||||
<PageNavContent scrollRef={scrollRef}>
|
||||
<Box direction="Column" gap="300">
|
||||
<NavCategory>
|
||||
<NavItem variant="Background" radii="400">
|
||||
<NavButton onClick={() => openCreateRoom()}>
|
||||
<NavItem variant="Background" radii="400" aria-selected={createRoomSelected}>
|
||||
<NavButton onClick={() => navigate(getHomeCreatePath())}>
|
||||
<NavItemContent>
|
||||
<Box as="span" grow="Yes" alignItems="Center" gap="200">
|
||||
<Avatar size="200" radii="400">
|
||||
|
||||
111
src/app/pages/client/sidebar/CreateTab.tsx
Normal file
111
src/app/pages/client/sidebar/CreateTab.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import React, { MouseEventHandler, useState } from 'react';
|
||||
import { Box, config, Icon, Icons, Menu, PopOut, RectCords, Text } from 'folds';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { SidebarAvatar, SidebarItem, SidebarItemTooltip } from '../../../components/sidebar';
|
||||
import { stopPropagation } from '../../../utils/keyboard';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
import { SettingTile } from '../../../components/setting-tile';
|
||||
import { ContainerColor } from '../../../styles/ContainerColor.css';
|
||||
import { openJoinAlias } from '../../../../client/action/navigation';
|
||||
import { getCreatePath } from '../../pathUtils';
|
||||
import { useCreateSelected } from '../../../hooks/router/useCreateSelected';
|
||||
|
||||
export function CreateTab() {
|
||||
const createSelected = useCreateSelected();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [menuCords, setMenuCords] = useState<RectCords>();
|
||||
|
||||
const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
setMenuCords(menuCords ? undefined : evt.currentTarget.getBoundingClientRect());
|
||||
};
|
||||
|
||||
const handleCreateSpace = () => {
|
||||
navigate(getCreatePath());
|
||||
setMenuCords(undefined);
|
||||
};
|
||||
|
||||
const handleJoinWithAddress = () => {
|
||||
openJoinAlias();
|
||||
setMenuCords(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<SidebarItem active={createSelected}>
|
||||
<SidebarItemTooltip tooltip="Add Space">
|
||||
{(triggerRef) => (
|
||||
<PopOut
|
||||
anchor={menuCords}
|
||||
position="Right"
|
||||
align="Center"
|
||||
content={
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
returnFocusOnDeactivate: false,
|
||||
initialFocus: false,
|
||||
onDeactivate: () => setMenuCords(undefined),
|
||||
clickOutsideDeactivates: true,
|
||||
isKeyForward: (evt: KeyboardEvent) =>
|
||||
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
|
||||
isKeyBackward: (evt: KeyboardEvent) =>
|
||||
evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Menu>
|
||||
<Box direction="Column">
|
||||
<SequenceCard
|
||||
style={{ padding: config.space.S300 }}
|
||||
variant="Surface"
|
||||
direction="Column"
|
||||
gap="100"
|
||||
radii="0"
|
||||
as="button"
|
||||
type="button"
|
||||
onClick={handleCreateSpace}
|
||||
>
|
||||
<SettingTile before={<Icon size="400" src={Icons.Space} />}>
|
||||
<Text size="H6">Create Space</Text>
|
||||
<Text size="T300" priority="300">
|
||||
Build a space for your community.
|
||||
</Text>
|
||||
</SettingTile>
|
||||
</SequenceCard>
|
||||
<SequenceCard
|
||||
style={{ padding: config.space.S300 }}
|
||||
variant="Surface"
|
||||
direction="Column"
|
||||
gap="100"
|
||||
radii="0"
|
||||
as="button"
|
||||
type="button"
|
||||
onClick={handleJoinWithAddress}
|
||||
>
|
||||
<SettingTile before={<Icon size="400" src={Icons.Link} />}>
|
||||
<Text size="H6">Join with Address</Text>
|
||||
<Text size="T300" priority="300">
|
||||
Become a part of existing community.
|
||||
</Text>
|
||||
</SettingTile>
|
||||
</SequenceCard>
|
||||
</Box>
|
||||
</Menu>
|
||||
</FocusTrap>
|
||||
}
|
||||
>
|
||||
<SidebarAvatar
|
||||
className={menuCords ? ContainerColor({ variant: 'Surface' }) : undefined}
|
||||
as="button"
|
||||
ref={triggerRef}
|
||||
outlined
|
||||
onClick={handleMenu}
|
||||
>
|
||||
<Icon src={Icons.Plus} />
|
||||
</SidebarAvatar>
|
||||
</PopOut>
|
||||
)}
|
||||
</SidebarItemTooltip>
|
||||
</SidebarItem>
|
||||
);
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
SPACE_PATH,
|
||||
SPACE_ROOM_PATH,
|
||||
SPACE_SEARCH_PATH,
|
||||
CREATE_PATH,
|
||||
} from './paths';
|
||||
import { trimLeadingSlash, trimTrailingSlash } from '../utils/common';
|
||||
import { HashRouterConfig } from '../hooks/useClientConfig';
|
||||
@@ -152,6 +153,8 @@ export const getExploreServerPath = (server: string): string => {
|
||||
return generatePath(EXPLORE_SERVER_PATH, params);
|
||||
};
|
||||
|
||||
export const getCreatePath = (): string => CREATE_PATH;
|
||||
|
||||
export const getInboxPath = (): string => INBOX_PATH;
|
||||
export const getInboxNotificationsPath = (): string => INBOX_NOTIFICATIONS_PATH;
|
||||
export const getInboxInvitesPath = (): string => INBOX_INVITES_PATH;
|
||||
|
||||
@@ -74,6 +74,8 @@ export type ExploreServerPathSearchParams = {
|
||||
};
|
||||
export const EXPLORE_SERVER_PATH = `/explore/${_SERVER_PATH}`;
|
||||
|
||||
export const CREATE_PATH = '/create';
|
||||
|
||||
export const _NOTIFICATIONS_PATH = 'notifications/';
|
||||
export const _INVITES_PATH = 'invites/';
|
||||
export const INBOX_PATH = '/inbox/';
|
||||
|
||||
Reference in New Issue
Block a user