mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-02-23 13:43:07 +01:00
refactor explore ui
This commit is contained in:
parent
a6204f8fb4
commit
958f14712c
6 changed files with 396 additions and 268 deletions
|
@ -1 +0,0 @@
|
|||
export * from './Content';
|
|
@ -1,34 +1,45 @@
|
|||
import React, { ComponentProps, ReactNode } from 'react';
|
||||
import { Box, Scroll, Text, as } from 'folds';
|
||||
import { Box, Header, Text, as } from 'folds';
|
||||
import classNames from 'classnames';
|
||||
import { ContainerColor } from '../../styles/ContainerColor.css';
|
||||
import * as css from './style.css';
|
||||
|
||||
export const Content = as<'div'>(({ className, children, ...props }, ref) => (
|
||||
export const Page = as<'div'>(({ className, ...props }, ref) => (
|
||||
<Box
|
||||
grow="Yes"
|
||||
direction="Column"
|
||||
className={classNames(ContainerColor({ variant: 'Surface' }), className)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<Scroll hideTrack>
|
||||
<div className={css.Content}>{children}</div>
|
||||
</Scroll>
|
||||
</Box>
|
||||
/>
|
||||
));
|
||||
|
||||
export const ContentHeroSection = as<'div', ComponentProps<typeof Box>>(
|
||||
export const PageHeader = as<'div'>(({ className, ...props }, ref) => (
|
||||
<Header
|
||||
as="header"
|
||||
size="600"
|
||||
className={classNames(css.PageHeader, className)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
));
|
||||
|
||||
export const PageContent = as<'div'>(({ className, ...props }, ref) => (
|
||||
<div className={classNames(css.PageContent, className)} {...props} ref={ref} />
|
||||
));
|
||||
|
||||
export const PageHeroSection = as<'div', ComponentProps<typeof Box>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<Box
|
||||
direction="Column"
|
||||
className={classNames(css.ContentHeroSection, className)}
|
||||
className={classNames(css.PageHeroSection, className)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
export function ContentHero({
|
||||
export function PageHero({
|
||||
icon,
|
||||
title,
|
||||
subTitle,
|
||||
|
@ -54,6 +65,6 @@ export function ContentHero({
|
|||
);
|
||||
}
|
||||
|
||||
export const ContentBody = as<'div'>(({ className, ...props }, ref) => (
|
||||
<div className={classNames(css.ContentBody, className)} {...props} ref={ref} />
|
||||
export const PageContentCenter = as<'div'>(({ className, ...props }, ref) => (
|
||||
<div className={classNames(css.PageContentCenter, className)} {...props} ref={ref} />
|
||||
));
|
1
src/app/components/page/index.tsx
Normal file
1
src/app/components/page/index.tsx
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './Page';
|
|
@ -1,16 +1,23 @@
|
|||
import { style } from '@vanilla-extract/css';
|
||||
import { DefaultReset, config, toRem } from 'folds';
|
||||
|
||||
export const Content = style([
|
||||
export const PageHeader = style({
|
||||
paddingLeft: config.space.S400,
|
||||
paddingRight: config.space.S200,
|
||||
borderBottomWidth: config.borderWidth.B300,
|
||||
});
|
||||
|
||||
export const PageContent = style([
|
||||
DefaultReset,
|
||||
{
|
||||
paddingLeft: config.space.S500,
|
||||
paddingRight: config.space.S100,
|
||||
paddingTop: config.space.S400,
|
||||
paddingLeft: config.space.S400,
|
||||
paddingRight: 0,
|
||||
paddingBottom: toRem(100),
|
||||
},
|
||||
]);
|
||||
|
||||
export const ContentHeroSection = style([
|
||||
export const PageHeroSection = style([
|
||||
DefaultReset,
|
||||
{
|
||||
padding: '40px 0',
|
||||
|
@ -20,7 +27,7 @@ export const ContentHeroSection = style([
|
|||
},
|
||||
]);
|
||||
|
||||
export const ContentBody = style([
|
||||
export const PageContentCenter = style([
|
||||
DefaultReset,
|
||||
{
|
||||
maxWidth: toRem(964),
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { Box, Icon, Icons, Text } from 'folds';
|
||||
import { Box, Icon, Icons, Scroll, Text } from 'folds';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useClientConfig } from '../../../hooks/useClientConfig';
|
||||
|
@ -12,7 +12,13 @@ import {
|
|||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
||||
import { RoomSummaryLoader } from '../../../components/RoomSummaryLoader';
|
||||
import { Content, ContentBody, ContentHero, ContentHeroSection } from '../../../components/content';
|
||||
import {
|
||||
Page,
|
||||
PageContent,
|
||||
PageContentCenter,
|
||||
PageHero,
|
||||
PageHeroSection,
|
||||
} from '../../../components/page';
|
||||
import { RoomTopicViewer } from '../../../components/room-topic-viewer';
|
||||
import { getHomeRoomPath, getSpacePath, getSpaceRoomPath } from '../../pathUtils';
|
||||
import { getOrphanParents } from '../../../utils/room';
|
||||
|
@ -63,78 +69,84 @@ export function FeaturedRooms() {
|
|||
);
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<Box direction="Column" gap="200">
|
||||
<ContentHeroSection>
|
||||
<ContentHero
|
||||
icon={<Icon size="600" src={Icons.Bulb} />}
|
||||
title="Featured by Client"
|
||||
subTitle="Find and explore public rooms and spaces featured by client provider."
|
||||
/>
|
||||
</ContentHeroSection>
|
||||
<ContentBody>
|
||||
<Box direction="Column" gap="700">
|
||||
{spaces && spaces.length > 0 && (
|
||||
<Box direction="Column" gap="400">
|
||||
<Text size="H4">Featured Spaces</Text>
|
||||
<RoomCardGrid>
|
||||
{spaces.map((roomIdOrAlias) => (
|
||||
<RoomSummaryLoader key={roomIdOrAlias} roomIdOrAlias={roomIdOrAlias}>
|
||||
{(roomSummary) => (
|
||||
<RoomCard
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
joinedRoomId={joinedRoomId(roomIdOrAlias)}
|
||||
avatarUrl={roomSummary?.avatar_url}
|
||||
name={roomSummary?.name}
|
||||
topic={roomSummary?.topic}
|
||||
memberCount={roomSummary?.num_joined_members}
|
||||
onView={navigateSpace}
|
||||
renderTopicViewer={(name, topic, requestClose) => (
|
||||
<RoomTopicViewer
|
||||
name={name}
|
||||
topic={topic}
|
||||
requestClose={requestClose}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</RoomSummaryLoader>
|
||||
))}
|
||||
</RoomCardGrid>
|
||||
<Page>
|
||||
<Box grow="Yes">
|
||||
<Scroll hideTrack visibility="Hover">
|
||||
<PageContent>
|
||||
<PageContentCenter>
|
||||
<Box direction="Column" gap="200">
|
||||
<PageHeroSection>
|
||||
<PageHero
|
||||
icon={<Icon size="600" src={Icons.Bulb} />}
|
||||
title="Featured by Client"
|
||||
subTitle="Find and explore public rooms and spaces featured by client provider."
|
||||
/>
|
||||
</PageHeroSection>
|
||||
<Box direction="Column" gap="700">
|
||||
{spaces && spaces.length > 0 && (
|
||||
<Box direction="Column" gap="400">
|
||||
<Text size="H4">Featured Spaces</Text>
|
||||
<RoomCardGrid>
|
||||
{spaces.map((roomIdOrAlias) => (
|
||||
<RoomSummaryLoader key={roomIdOrAlias} roomIdOrAlias={roomIdOrAlias}>
|
||||
{(roomSummary) => (
|
||||
<RoomCard
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
joinedRoomId={joinedRoomId(roomIdOrAlias)}
|
||||
avatarUrl={roomSummary?.avatar_url}
|
||||
name={roomSummary?.name}
|
||||
topic={roomSummary?.topic}
|
||||
memberCount={roomSummary?.num_joined_members}
|
||||
onView={navigateSpace}
|
||||
renderTopicViewer={(name, topic, requestClose) => (
|
||||
<RoomTopicViewer
|
||||
name={name}
|
||||
topic={topic}
|
||||
requestClose={requestClose}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</RoomSummaryLoader>
|
||||
))}
|
||||
</RoomCardGrid>
|
||||
</Box>
|
||||
)}
|
||||
{rooms && rooms.length > 0 && (
|
||||
<Box direction="Column" gap="400">
|
||||
<Text size="H4">Featured Rooms</Text>
|
||||
<RoomCardGrid>
|
||||
{rooms.map((roomIdOrAlias) => (
|
||||
<RoomSummaryLoader key={roomIdOrAlias} roomIdOrAlias={roomIdOrAlias}>
|
||||
{(roomSummary) => (
|
||||
<RoomCard
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
joinedRoomId={joinedRoomId(roomIdOrAlias)}
|
||||
avatarUrl={roomSummary?.avatar_url}
|
||||
name={roomSummary?.name}
|
||||
topic={roomSummary?.topic}
|
||||
memberCount={roomSummary?.num_joined_members}
|
||||
onView={navigateRoom}
|
||||
renderTopicViewer={(name, topic, requestClose) => (
|
||||
<RoomTopicViewer
|
||||
name={name}
|
||||
topic={topic}
|
||||
requestClose={requestClose}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</RoomSummaryLoader>
|
||||
))}
|
||||
</RoomCardGrid>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{rooms && rooms.length > 0 && (
|
||||
<Box direction="Column" gap="400">
|
||||
<Text size="H4">Featured Rooms</Text>
|
||||
<RoomCardGrid>
|
||||
{rooms.map((roomIdOrAlias) => (
|
||||
<RoomSummaryLoader key={roomIdOrAlias} roomIdOrAlias={roomIdOrAlias}>
|
||||
{(roomSummary) => (
|
||||
<RoomCard
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
joinedRoomId={joinedRoomId(roomIdOrAlias)}
|
||||
avatarUrl={roomSummary?.avatar_url}
|
||||
name={roomSummary?.name}
|
||||
topic={roomSummary?.topic}
|
||||
memberCount={roomSummary?.num_joined_members}
|
||||
onView={navigateRoom}
|
||||
renderTopicViewer={(name, topic, requestClose) => (
|
||||
<RoomTopicViewer
|
||||
name={name}
|
||||
topic={topic}
|
||||
requestClose={requestClose}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</RoomSummaryLoader>
|
||||
))}
|
||||
</RoomCardGrid>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</ContentBody>
|
||||
</PageContentCenter>
|
||||
</PageContent>
|
||||
</Scroll>
|
||||
</Box>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,12 +7,25 @@ import React, {
|
|||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Box, Button, Chip, Icon, Icons, Input, Menu, PopOut, Spinner, Text, config } from 'folds';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Chip,
|
||||
Icon,
|
||||
Icons,
|
||||
Input,
|
||||
Menu,
|
||||
PopOut,
|
||||
Scroll,
|
||||
Spinner,
|
||||
Text,
|
||||
config,
|
||||
} from 'folds';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { MatrixClient, Method, RoomType } from 'matrix-js-sdk';
|
||||
import { Content, ContentHeroSection, ContentHero, ContentBody } from '../../../components/content';
|
||||
import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { RoomTopicViewer } from '../../../components/room-topic-viewer';
|
||||
import { RoomCard, RoomCardGrid } from '../../../components/room-card';
|
||||
|
@ -57,16 +70,14 @@ export function PublicRooms() {
|
|||
const [searchParams] = useSearchParams();
|
||||
const serverSearchParams = getServerSearchParams(searchParams);
|
||||
const isSearch = serverSearchParams.term;
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const navigate = useNavigate();
|
||||
const roomTypeFilters = useRoomTypeFilters();
|
||||
const [openLimit, setOpenLimit] = useState(false);
|
||||
|
||||
const resetScroll = useCallback(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
const scroll = container.children[0];
|
||||
const scroll = scrollRef.current;
|
||||
if (scroll) scroll.scrollTop = 0;
|
||||
}, []);
|
||||
|
||||
|
@ -165,191 +176,278 @@ export function PublicRooms() {
|
|||
});
|
||||
};
|
||||
|
||||
const setLimit = (limit: string) => {
|
||||
setOpenLimit(false);
|
||||
explore({ limit });
|
||||
};
|
||||
|
||||
const handleLimitSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
|
||||
evt.preventDefault();
|
||||
const limitInput = evt.currentTarget.limitInput as HTMLInputElement;
|
||||
if (!limitInput) return;
|
||||
const limit = limitInput.value.trim();
|
||||
if (!limit) return;
|
||||
explore({ limit });
|
||||
setLimit(limit);
|
||||
};
|
||||
|
||||
return (
|
||||
<Content ref={containerRef}>
|
||||
<Box direction="Column" gap="200">
|
||||
<ContentHeroSection direction="Column" gap="400">
|
||||
<ContentHero
|
||||
icon={<Icon size="600" src={Icons.Category} />}
|
||||
title={server}
|
||||
subTitle={`Find and explore public rooms and spaces on ${server} server.`}
|
||||
/>
|
||||
</ContentHeroSection>
|
||||
<ContentBody>
|
||||
<Box direction="Column" gap="700">
|
||||
<Box direction="Column" gap="400">
|
||||
<Box direction="Column" gap="300">
|
||||
{isSearch ? (
|
||||
<Text size="H4">{`Public Communities for "${serverSearchParams.term}"`}</Text>
|
||||
) : (
|
||||
<Text size="H4">Public Community</Text>
|
||||
)}
|
||||
<form onSubmit={handleSearchSubmit}>
|
||||
<Input
|
||||
ref={searchInputRef}
|
||||
name="searchInput"
|
||||
size="500"
|
||||
variant="Background"
|
||||
placeholder="Search"
|
||||
before={
|
||||
isSearch && isLoading ? (
|
||||
<Spinner variant="Secondary" size="200" />
|
||||
) : (
|
||||
<Icon size="200" src={Icons.Search} />
|
||||
)
|
||||
}
|
||||
after={
|
||||
isSearch && (
|
||||
<Chip
|
||||
type="button"
|
||||
variant="Secondary"
|
||||
size="400"
|
||||
radii="Pill"
|
||||
aria-pressed
|
||||
after={<Icon size="50" src={Icons.Cross} />}
|
||||
onClick={handleSearchClear}
|
||||
>
|
||||
<Text size="B300">Clear</Text>
|
||||
</Chip>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</form>
|
||||
<Box gap="200">
|
||||
{roomTypeFilters.map((filter) => (
|
||||
<Chip
|
||||
key={filter.title}
|
||||
onClick={handleRoomFilterClick}
|
||||
data-room-filter={filter.value}
|
||||
variant={filter.value === serverSearchParams.type ? 'Success' : 'Surface'}
|
||||
aria-pressed={filter.value === serverSearchParams.type}
|
||||
<Page>
|
||||
<PageHeader>
|
||||
{isSearch ? (
|
||||
<>
|
||||
<Box grow="Yes" basis="No">
|
||||
<Chip
|
||||
size="500"
|
||||
variant="Surface"
|
||||
radii="Pill"
|
||||
before={<Icon size="100" src={Icons.ArrowLeft} />}
|
||||
onClick={handleSearchClear}
|
||||
>
|
||||
<Text size="T300">{server}</Text>
|
||||
</Chip>
|
||||
</Box>
|
||||
|
||||
<Box grow="No" justifyContent="Center" alignItems="Center" gap="200">
|
||||
<Icon size="400" src={Icons.Search} />
|
||||
<Text size="H3" truncate>
|
||||
Search
|
||||
</Text>
|
||||
</Box>
|
||||
<Box grow="Yes" />
|
||||
</>
|
||||
) : (
|
||||
<Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
|
||||
<Icon size="400" src={Icons.Category} />
|
||||
<Text size="H3" truncate>
|
||||
{server}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</PageHeader>
|
||||
<Box grow="Yes">
|
||||
<Scroll ref={scrollRef} hideTrack visibility="Hover">
|
||||
<PageContent>
|
||||
<PageContentCenter>
|
||||
<Box direction="Column" gap="200">
|
||||
<Box direction="Column" gap="500">
|
||||
<Box as="form" direction="Column" gap="100" onSubmit={handleSearchSubmit}>
|
||||
<span data-spacing-node />
|
||||
<Text size="L400">Search</Text>
|
||||
<Input
|
||||
ref={searchInputRef}
|
||||
style={{ paddingRight: config.space.S300 }}
|
||||
name="searchInput"
|
||||
size="500"
|
||||
variant="Background"
|
||||
placeholder="Search for keyword"
|
||||
before={
|
||||
filter.value === serverSearchParams.type && (
|
||||
<Icon size="100" src={Icons.Check} />
|
||||
isSearch && isLoading ? (
|
||||
<Spinner variant="Secondary" size="200" />
|
||||
) : (
|
||||
<Icon size="200" src={Icons.Search} />
|
||||
)
|
||||
}
|
||||
outlined
|
||||
>
|
||||
<Text size="T200">{filter.title}</Text>
|
||||
</Chip>
|
||||
))}
|
||||
after={
|
||||
isSearch ? (
|
||||
<Chip
|
||||
type="button"
|
||||
variant="Secondary"
|
||||
size="400"
|
||||
radii="Pill"
|
||||
outlined
|
||||
after={<Icon size="50" src={Icons.Cross} />}
|
||||
onClick={handleSearchClear}
|
||||
>
|
||||
<Text size="B300">Clear</Text>
|
||||
</Chip>
|
||||
) : (
|
||||
<Chip type="submit" variant="Primary" size="400" radii="Pill" outlined>
|
||||
<Text size="B300">Enter</Text>
|
||||
</Chip>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Box direction="Column" gap="400">
|
||||
<Box direction="Column" gap="300">
|
||||
{isSearch ? (
|
||||
<Text size="H4">{`Public Communities for "${serverSearchParams.term}"`}</Text>
|
||||
) : (
|
||||
<Text size="H4">Public Communities</Text>
|
||||
)}
|
||||
<Box gap="200">
|
||||
{roomTypeFilters.map((filter) => (
|
||||
<Chip
|
||||
key={filter.title}
|
||||
onClick={handleRoomFilterClick}
|
||||
data-room-filter={filter.value}
|
||||
variant={
|
||||
filter.value === serverSearchParams.type ? 'Success' : 'Surface'
|
||||
}
|
||||
aria-pressed={filter.value === serverSearchParams.type}
|
||||
before={
|
||||
filter.value === serverSearchParams.type && (
|
||||
<Icon size="100" src={Icons.Check} />
|
||||
)
|
||||
}
|
||||
outlined
|
||||
>
|
||||
<Text size="T200">{filter.title}</Text>
|
||||
</Chip>
|
||||
))}
|
||||
<Box grow="Yes" data-spacing-node />
|
||||
<PopOut
|
||||
open={openLimit}
|
||||
align="End"
|
||||
position="Bottom"
|
||||
content={
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: () => setOpenLimit(false),
|
||||
clickOutsideDeactivates: true,
|
||||
}}
|
||||
>
|
||||
<Menu variant="Surface">
|
||||
<Box
|
||||
direction="Column"
|
||||
gap="300"
|
||||
style={{ padding: config.space.S200 }}
|
||||
>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Presets</Text>
|
||||
<Box gap="100" wrap="Wrap">
|
||||
<Chip
|
||||
variant="SurfaceVariant"
|
||||
onClick={() => setLimit('24')}
|
||||
radii="Pill"
|
||||
>
|
||||
<Text size="T200">24</Text>
|
||||
</Chip>
|
||||
<Chip
|
||||
variant="SurfaceVariant"
|
||||
onClick={() => setLimit('48')}
|
||||
radii="Pill"
|
||||
>
|
||||
<Text size="T200">48</Text>
|
||||
</Chip>
|
||||
<Chip
|
||||
variant="SurfaceVariant"
|
||||
onClick={() => setLimit('96')}
|
||||
radii="Pill"
|
||||
>
|
||||
<Text size="T200">96</Text>
|
||||
</Chip>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
as="form"
|
||||
onSubmit={handleLimitSubmit}
|
||||
direction="Column"
|
||||
gap="200"
|
||||
>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Custom Limit</Text>
|
||||
<Input
|
||||
name="limitInput"
|
||||
size="300"
|
||||
variant="Background"
|
||||
defaultValue={
|
||||
serverSearchParams.limit ?? FALLBACK_ROOMS_LIMIT
|
||||
}
|
||||
min={1}
|
||||
step={1}
|
||||
outlined
|
||||
type="number"
|
||||
radii="400"
|
||||
aria-label="Per Page Item Limit"
|
||||
/>
|
||||
</Box>
|
||||
<Button type="submit" size="300" variant="Primary" radii="400">
|
||||
<Text size="B300">Change Limit</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Menu>
|
||||
</FocusTrap>
|
||||
}
|
||||
>
|
||||
{(anchorRef) => (
|
||||
<Chip
|
||||
ref={anchorRef}
|
||||
onClick={() => setOpenLimit(!openLimit)}
|
||||
aria-pressed={openLimit}
|
||||
radii="Pill"
|
||||
size="400"
|
||||
variant="SurfaceVariant"
|
||||
after={<Icon size="100" src={Icons.ChevronBottom} />}
|
||||
>
|
||||
<Text size="T200" truncate>{`Page Limit: ${
|
||||
serverSearchParams.limit ?? FALLBACK_ROOMS_LIMIT
|
||||
}`}</Text>
|
||||
</Chip>
|
||||
)}
|
||||
</PopOut>
|
||||
</Box>
|
||||
</Box>
|
||||
{isLoading && !error && <Text>loading...</Text>}
|
||||
{error && <Text>{error.message}</Text>}
|
||||
<RoomCardGrid>
|
||||
{data?.chunk.map((chunkRoom) => (
|
||||
<RoomCard
|
||||
key={chunkRoom.room_id}
|
||||
roomIdOrAlias={chunkRoom.canonical_alias ?? chunkRoom.room_id}
|
||||
joinedRoomId={mx.getRoom(chunkRoom.room_id)?.roomId}
|
||||
avatarUrl={chunkRoom.avatar_url}
|
||||
name={chunkRoom.name}
|
||||
topic={chunkRoom.topic}
|
||||
memberCount={chunkRoom.num_joined_members}
|
||||
roomType={chunkRoom.room_type}
|
||||
renderTopicViewer={(name, topic, requestClose) => (
|
||||
<RoomTopicViewer
|
||||
name={name}
|
||||
topic={topic}
|
||||
requestClose={requestClose}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</RoomCardGrid>
|
||||
{data && (
|
||||
<>
|
||||
<span data-spacing-node />
|
||||
<Box justifyContent="Center" gap="200">
|
||||
<Button
|
||||
onClick={paginateBack}
|
||||
size="300"
|
||||
fill="Soft"
|
||||
disabled={!data.prev_batch}
|
||||
>
|
||||
<Text size="B300" truncate>
|
||||
Previous Page
|
||||
</Text>
|
||||
</Button>
|
||||
<Box data-spacing-node grow="Yes" />
|
||||
<Button
|
||||
onClick={paginateFront}
|
||||
size="300"
|
||||
fill="Solid"
|
||||
disabled={!data.next_batch}
|
||||
>
|
||||
<Text size="B300" truncate>
|
||||
Next Page
|
||||
</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{isLoading && !error && <Text>loading...</Text>}
|
||||
{error && <Text>{error.message}</Text>}
|
||||
<RoomCardGrid>
|
||||
{data?.chunk.map((chunkRoom) => (
|
||||
<RoomCard
|
||||
key={chunkRoom.room_id}
|
||||
roomIdOrAlias={chunkRoom.canonical_alias ?? chunkRoom.room_id}
|
||||
joinedRoomId={mx.getRoom(chunkRoom.room_id)?.roomId}
|
||||
avatarUrl={chunkRoom.avatar_url}
|
||||
name={chunkRoom.name}
|
||||
topic={chunkRoom.topic}
|
||||
memberCount={chunkRoom.num_joined_members}
|
||||
roomType={chunkRoom.room_type}
|
||||
renderTopicViewer={(name, topic, requestClose) => (
|
||||
<RoomTopicViewer name={name} topic={topic} requestClose={requestClose} />
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</RoomCardGrid>
|
||||
{data && (
|
||||
<>
|
||||
<span data-spacing-node />
|
||||
<Box justifyContent="Center" gap="200">
|
||||
<PopOut
|
||||
open={openLimit}
|
||||
align="Center"
|
||||
position="Top"
|
||||
content={
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: () => setOpenLimit(false),
|
||||
clickOutsideDeactivates: true,
|
||||
}}
|
||||
>
|
||||
<Menu variant="Surface">
|
||||
<Box
|
||||
as="form"
|
||||
onSubmit={handleLimitSubmit}
|
||||
style={{ padding: config.space.S200 }}
|
||||
direction="Column"
|
||||
gap="200"
|
||||
>
|
||||
<Input
|
||||
name="limitInput"
|
||||
size="300"
|
||||
variant="Background"
|
||||
defaultValue={serverSearchParams.limit ?? FALLBACK_ROOMS_LIMIT}
|
||||
min={1}
|
||||
step={1}
|
||||
outlined
|
||||
type="number"
|
||||
radii="300"
|
||||
aria-label="Per Page Item Limit"
|
||||
/>
|
||||
<Button type="submit" size="300" variant="Primary" radii="300">
|
||||
<Text size="B300">Change Limit</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Menu>
|
||||
</FocusTrap>
|
||||
}
|
||||
>
|
||||
{(anchorRef) => (
|
||||
<Chip
|
||||
ref={anchorRef}
|
||||
onClick={() => setOpenLimit(!openLimit)}
|
||||
aria-pressed={openLimit}
|
||||
radii="Pill"
|
||||
size="500"
|
||||
variant="SurfaceVariant"
|
||||
after={<Icon size="200" src={Icons.ChevronBottom} />}
|
||||
>
|
||||
<Text size="B300" truncate>{`Page Limit: ${data.chunk.length}`}</Text>
|
||||
</Chip>
|
||||
)}
|
||||
</PopOut>
|
||||
|
||||
<Box data-spacing-node grow="Yes" />
|
||||
<Button
|
||||
onClick={paginateBack}
|
||||
size="300"
|
||||
fill="Soft"
|
||||
disabled={!data.prev_batch}
|
||||
>
|
||||
<Text size="B300" truncate>
|
||||
Previous Page
|
||||
</Text>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={paginateFront}
|
||||
size="300"
|
||||
fill="Solid"
|
||||
disabled={!data.next_batch}
|
||||
>
|
||||
<Text size="B300" truncate>
|
||||
Next Page
|
||||
</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</ContentBody>
|
||||
</PageContentCenter>
|
||||
</PageContent>
|
||||
</Scroll>
|
||||
</Box>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue