Merge remote-tracking branch 'origin/develop' into group-lookup
This commit is contained in:
@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import Link from 'soapbox/components/link';
|
||||
import { HStack, Stack, Text } from 'soapbox/components/ui';
|
||||
import { usePopularTags } from 'soapbox/hooks/api';
|
||||
|
||||
import TagListItem from './tag-list-item';
|
||||
|
||||
const PopularTags = () => {
|
||||
const { tags, isFetched, isError } = usePopularTags();
|
||||
const isEmpty = (isFetched && tags.length === 0) || isError;
|
||||
|
||||
return (
|
||||
<Stack space={4}>
|
||||
<HStack alignItems='center' justifyContent='between'>
|
||||
<Text size='xl' weight='bold'>
|
||||
<FormattedMessage
|
||||
id='groups.discover.tags.title'
|
||||
defaultMessage='Browse Topics'
|
||||
/>
|
||||
</Text>
|
||||
|
||||
<Link to='/groups/tags'>
|
||||
<Text tag='span' weight='medium' size='sm' theme='inherit'>
|
||||
<FormattedMessage
|
||||
id='groups.discover.tags.show_more'
|
||||
defaultMessage='Show More'
|
||||
/>
|
||||
</Text>
|
||||
</Link>
|
||||
</HStack>
|
||||
|
||||
{isEmpty ? (
|
||||
<Text theme='muted'>
|
||||
<FormattedMessage
|
||||
id='groups.discover.tags.empty'
|
||||
defaultMessage='Unable to fetch popular topics at this time. Please check back later.'
|
||||
/>
|
||||
</Text>
|
||||
) : (
|
||||
<Stack space={4}>
|
||||
{tags.slice(0, 10).map((tag) => (
|
||||
<TagListItem key={tag.id} tag={tag} />
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopularTags;
|
||||
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Stack, Text } from 'soapbox/components/ui';
|
||||
|
||||
import type { GroupTag } from 'soapbox/schemas';
|
||||
|
||||
interface ITagListItem {
|
||||
tag: GroupTag
|
||||
}
|
||||
|
||||
const TagListItem = (props: ITagListItem) => {
|
||||
const { tag } = props;
|
||||
|
||||
return (
|
||||
<Link to={`/groups/discover/tags/${tag.id}`} className='group'>
|
||||
<Stack>
|
||||
<Text
|
||||
weight='bold'
|
||||
className='group-hover:text-primary-600 group-hover:underline dark:group-hover:text-accent-blue'
|
||||
>
|
||||
#{tag.name}
|
||||
</Text>
|
||||
|
||||
<Text size='sm' theme='muted' weight='medium'>
|
||||
<FormattedMessage
|
||||
id='groups.discovery.tags.no_of_groups'
|
||||
defaultMessage='Number of groups'
|
||||
/>
|
||||
:{' '}
|
||||
{tag.uses}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagListItem;
|
||||
@ -4,6 +4,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
import { HStack, Icon, IconButton, Input, Stack } from 'soapbox/components/ui';
|
||||
|
||||
import PopularGroups from './components/discover/popular-groups';
|
||||
import PopularTags from './components/discover/popular-tags';
|
||||
import Search from './components/discover/search/search';
|
||||
import SuggestedGroups from './components/discover/suggested-groups';
|
||||
import TabBar, { TabItems } from './components/tab-bar';
|
||||
@ -71,6 +72,7 @@ const Discover: React.FC = () => {
|
||||
<>
|
||||
<PopularGroups />
|
||||
<SuggestedGroups />
|
||||
<PopularTags />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
117
app/soapbox/features/groups/tag.tsx
Normal file
117
app/soapbox/features/groups/tag.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Components, Virtuoso, VirtuosoGrid } from 'react-virtuoso';
|
||||
|
||||
import { Column, HStack, Icon } from 'soapbox/components/ui';
|
||||
import { useGroupTag, useGroupsFromTag } from 'soapbox/hooks/api';
|
||||
|
||||
import GroupGridItem from './components/discover/group-grid-item';
|
||||
import GroupListItem from './components/discover/group-list-item';
|
||||
|
||||
import type { Group } from 'soapbox/schemas';
|
||||
|
||||
enum Layout {
|
||||
LIST = 'LIST',
|
||||
GRID = 'GRID'
|
||||
}
|
||||
|
||||
const GridList: Components['List'] = React.forwardRef((props, ref) => {
|
||||
const { context, ...rest } = props;
|
||||
return <div ref={ref} {...rest} className='flex flex-wrap' />;
|
||||
});
|
||||
|
||||
interface ITag {
|
||||
params: { id: string }
|
||||
}
|
||||
|
||||
const Tag: React.FC<ITag> = (props) => {
|
||||
const tagId = props.params.id;
|
||||
|
||||
const [layout, setLayout] = useState<Layout>(Layout.LIST);
|
||||
|
||||
const { tag, isLoading } = useGroupTag(tagId);
|
||||
const { groups, hasNextPage, fetchNextPage } = useGroupsFromTag(tagId);
|
||||
|
||||
const handleLoadMore = () => {
|
||||
if (hasNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
};
|
||||
|
||||
const renderGroupList = useCallback((group: Group, index: number) => (
|
||||
<div
|
||||
className={
|
||||
clsx({
|
||||
'pt-4': index !== 0,
|
||||
})
|
||||
}
|
||||
>
|
||||
<GroupListItem group={group} withJoinAction />
|
||||
</div>
|
||||
), []);
|
||||
|
||||
const renderGroupGrid = useCallback((group: Group, index: number) => (
|
||||
<div className='pb-4'>
|
||||
<GroupGridItem group={group} />
|
||||
</div>
|
||||
), []);
|
||||
|
||||
if (isLoading || !tag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Column
|
||||
label={`#${tag.name}`}
|
||||
action={
|
||||
<HStack alignItems='center'>
|
||||
<button onClick={() => setLayout(Layout.LIST)}>
|
||||
<Icon
|
||||
src={require('@tabler/icons/layout-list.svg')}
|
||||
className={
|
||||
clsx('h-5 w-5 text-gray-600', {
|
||||
'text-primary-600': layout === Layout.LIST,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<button onClick={() => setLayout(Layout.GRID)}>
|
||||
<Icon
|
||||
src={require('@tabler/icons/layout-grid.svg')}
|
||||
className={
|
||||
clsx('h-5 w-5 text-gray-600', {
|
||||
'text-primary-600': layout === Layout.GRID,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
</HStack>
|
||||
}
|
||||
>
|
||||
{layout === Layout.LIST ? (
|
||||
<Virtuoso
|
||||
useWindowScroll
|
||||
data={groups}
|
||||
itemContent={(index, group) => renderGroupList(group, index)}
|
||||
endReached={handleLoadMore}
|
||||
/>
|
||||
) : (
|
||||
<VirtuosoGrid
|
||||
useWindowScroll
|
||||
data={groups}
|
||||
itemContent={(index, group) => renderGroupGrid(group, index)}
|
||||
components={{
|
||||
Item: (props) => (
|
||||
<div {...props} className='w-1/2 flex-none' />
|
||||
),
|
||||
List: GridList,
|
||||
}}
|
||||
endReached={handleLoadMore}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tag;
|
||||
62
app/soapbox/features/groups/tags.tsx
Normal file
62
app/soapbox/features/groups/tags.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
|
||||
import { Column, Text } from 'soapbox/components/ui';
|
||||
import { usePopularTags } from 'soapbox/hooks/api';
|
||||
|
||||
import TagListItem from './components/discover/tag-list-item';
|
||||
|
||||
import type { GroupTag } from 'soapbox/schemas';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'groups.tags.title', defaultMessage: 'Browse Topics' },
|
||||
});
|
||||
|
||||
const Tags: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { tags, isFetched, isError, hasNextPage, fetchNextPage } = usePopularTags();
|
||||
const isEmpty = (isFetched && tags.length === 0) || isError;
|
||||
|
||||
const handleLoadMore = () => {
|
||||
if (hasNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
};
|
||||
|
||||
const renderItem = (index: number, tag: GroupTag) => (
|
||||
<div
|
||||
className={
|
||||
clsx({
|
||||
'pt-4': index !== 0,
|
||||
})
|
||||
}
|
||||
>
|
||||
<TagListItem key={tag.id} tag={tag} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.title)}>
|
||||
{isEmpty ? (
|
||||
<Text theme='muted'>
|
||||
<FormattedMessage
|
||||
id='groups.discover.tags.empty'
|
||||
defaultMessage='Unable to fetch popular topics at this time. Please check back later.'
|
||||
/>
|
||||
</Text>
|
||||
) : (
|
||||
<Virtuoso
|
||||
useWindowScroll
|
||||
data={tags}
|
||||
itemContent={renderItem}
|
||||
endReached={handleLoadMore}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tags;
|
||||
Reference in New Issue
Block a user