Skip to content

Commit

Permalink
add collections screens and collection controls to thread list
Browse files Browse the repository at this point in the history
  • Loading branch information
Southclaws committed Aug 5, 2023
1 parent fcbe399 commit 0daa60a
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 31 deletions.
32 changes: 32 additions & 0 deletions web/src/components/Action/Action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {
ArrowLeftIcon,
Bars3Icon,
BellIcon,
BookmarkIcon,
CloudArrowUpIcon,
EllipsisHorizontalIcon,
HomeIcon,
PlusIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
import { BookmarkIcon as BookmarkSolidIcon } from "@heroicons/react/24/solid";
import { MouseEvent, MouseEventHandler, useCallback } from "react";

import { LoginIcon } from "../graphics/LoginIcon";
Expand Down Expand Up @@ -250,3 +252,33 @@ export const More = forwardRef(
);
}
);

export const Bookmark = forwardRef(
({ "aria-label": al, ...props }: WithOptionalARIALabel, ref) => {
return (
<ActionButton
ref={ref}
size="sm"
title="Save to collection"
aria-label={al ?? "save"}
{...props}
icon={<BookmarkIcon width="1.4em" />}
/>
);
}
);

export const BookmarkSolid = forwardRef(
({ "aria-label": al, ...props }: WithOptionalARIALabel, ref) => {
return (
<ActionButton
ref={ref}
size="sm"
title="Save to collection"
aria-label={al ?? "save"}
{...props}
icon={<BookmarkSolidIcon width="1.4em" />}
/>
);
}
);
59 changes: 59 additions & 0 deletions web/src/components/CollectionMenu/CollectionMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
Menu,
MenuButton,
MenuDivider,
MenuGroup,
MenuItem,
MenuList,
} from "@chakra-ui/react";
import { MinusIcon, PlusIcon } from "@heroicons/react/24/solid";

import { Bookmark, BookmarkSolid } from "../Action/Action";

import { Props, useCollectionMenu } from "./useCollectionMenu";

export function CollectionMenu(props: Props) {
const { collections, isAlreadySaved, onSelect } = useCollectionMenu(props);

if (!collections) return null;

return (
<Menu
preventOverflow={true}
modifiers={[
{
name: "preventOverflow",
options: {
altAxis: true,
padding: { bottom: 82 },
},
},
]}
>
<MenuButton
title="Add to collections"
as={isAlreadySaved ? BookmarkSolid : Bookmark}
/>
<MenuList title="Add to collections">
<MenuGroup title="Add to collections">
<MenuDivider />
{collections.map((c) => (
<MenuItem
key={c.id}
icon={
c.hasPost ? (
<MinusIcon width="1.4em" />
) : (
<PlusIcon width="1.4em" />
)
}
onClick={onSelect(c)}
>
{c.name}
</MenuItem>
))}
</MenuGroup>
</MenuList>
</Menu>
);
}
58 changes: 58 additions & 0 deletions web/src/components/CollectionMenu/useCollectionMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useToast } from "@chakra-ui/react";
import { mutate } from "swr";

import {
collectionAddPost,
collectionRemovePost,
useCollectionList,
} from "src/api/openapi/collections";
import { ThreadReference } from "src/api/openapi/schemas";
import { getThreadListKey } from "src/api/openapi/threads";
import { useSession } from "src/auth";

export type Props = {
thread: ThreadReference;
};

type CollectionState = {
id: string;
name: string;
hasPost: boolean;
};

export function useCollectionMenu(props: Props) {
const account = useSession();
const toast = useToast();
const collectionList = useCollectionList();

const postCollections = new Set(props.thread.collections.map((c) => c.id));
const isAlreadySaved = Boolean(
props.thread.collections.filter((c) => c.owner.id === account?.id).length
);

const collections: CollectionState[] =
collectionList.data?.collections.map((c) => ({
id: c.id,
name: c.name,
hasPost: postCollections.has(c.id),
})) ?? [];

const onSelect = (c: CollectionState) => async () => {
if (postCollections.has(c.id)) {
await collectionRemovePost(c.id, props.thread.id);
toast({ title: `Removed from ${c.name}` });
} else {
await collectionAddPost(c.id, props.thread.id);
toast({ title: `Added to ${c.name}` });
}
console.log(getThreadListKey());
await mutate(getThreadListKey({}));
};

return {
error: collectionList.error,
collections: collections,
isAlreadySaved,
onSelect,
};
}
4 changes: 2 additions & 2 deletions web/src/screens/collection/components/Collection.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Heading, Text } from "@chakra-ui/react";

import { Collection, CollectionWithItems } from "src/api/openapi/schemas";
import { ThreadList } from "src/screens/home/components/ThreadList";
import { Byline } from "src/screens/thread/components/Byline";
import { CollectionItemList } from "./CollectionItemList";

export function Collection(props: CollectionWithItems) {
return (
Expand All @@ -16,7 +16,7 @@ export function Collection(props: CollectionWithItems) {
/>
<Text>{props.description}</Text>

<ThreadList threads={props.items} />
<CollectionItemList items={props.items} />
</>
);
}
37 changes: 37 additions & 0 deletions web/src/screens/collection/components/CollectionItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Flex, Heading, LinkBox, LinkOverlay, Text } from "@chakra-ui/react";
import NextLink from "next/link";

import { CollectionItem } from "src/api/openapi/schemas";
import { Byline } from "src/screens/thread/components/Byline";

export function CollectionItem(props: { item: CollectionItem }) {
const permalink = `/t/${props.item.slug}`;

return (
<Flex as="section" flexDir="column" py={2} width="full" gap={2}>
<LinkBox as="article">
<Flex justifyContent="space-between">
<Heading size="sm">
<LinkOverlay as={NextLink} href={permalink}>
{props.item.title}
</LinkOverlay>
</Heading>
</Flex>

<Text noOfLines={3}>{props.item.short}</Text>
</LinkBox>

<Flex justifyContent="space-between">
<Byline
href={permalink}
author={props.item.author.handle}
time={new Date(props.item.createdAt)}
updated={new Date(props.item.updatedAt)}
// more={<ThreadMenu {...props.item} />}
/>

{/* Tags list */}
</Flex>
</Flex>
);
}
21 changes: 21 additions & 0 deletions web/src/screens/collection/components/CollectionItemList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Divider, List } from "@chakra-ui/react";
import { Fragment } from "react";

import { CollectionItem as CollectionItemSchema } from "src/api/openapi/schemas";

import { CollectionItem } from "./CollectionItem";

type Props = { items: CollectionItemSchema[] };

export function CollectionItemList(props: Props) {
return (
<List width="full" display="flex" flexDirection="column">
{props.items.map((t) => (
<Fragment key={t.id}>
<Divider />
<CollectionItem key={t.id} item={t} />
</Fragment>
))}
</List>
);
}
20 changes: 17 additions & 3 deletions web/src/screens/home/components/ThreadListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { Flex, Heading, LinkBox, LinkOverlay, Text } from "@chakra-ui/react";
import {
Flex,
HStack,
Heading,
LinkBox,
LinkOverlay,
Text,
} from "@chakra-ui/react";
import NextLink from "next/link";

import { ThreadReference } from "src/api/openapi/schemas";
import { CollectionMenu } from "src/components/CollectionMenu/CollectionMenu";
import { Byline } from "src/screens/thread/components/Byline";

import { ThreadMenu } from "./ThreadMenu/ThreadMenu";
import NextLink from "next/link";

export function ThreadListItem(props: { thread: ThreadReference }) {
const permalink = `/t/${props.thread.slug}`;
Expand All @@ -27,10 +37,14 @@ export function ThreadListItem(props: { thread: ThreadReference }) {
author={props.thread.author.handle}
time={new Date(props.thread.createdAt)}
updated={new Date(props.thread.updatedAt)}
more={<ThreadMenu {...props.thread} />}
/>

{/* Tags list */}

<HStack>
<CollectionMenu thread={props.thread} />
<ThreadMenu {...props.thread} />
</HStack>
</Flex>
</Flex>
);
Expand Down
2 changes: 1 addition & 1 deletion web/src/screens/profile/components/Content/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ export function Content(props: PublicProfile) {
<VStack alignItems="start" w="full">
<Tabs width="full" variant="soft-rounded">
<TabList>
<Tab>Threads</Tab>
<Tab>Posts</Tab>
<Tab>Replies</Tab>
<Tab>Collections</Tab>
</TabList>
<TabPanels>
Expand Down
47 changes: 22 additions & 25 deletions web/src/screens/thread/components/Byline.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Flex, HStack, Text } from "@chakra-ui/react";
import { differenceInSeconds, formatDistanceToNow } from "date-fns";

import { ProfileReference } from "src/components/ProfileReference/ProfileReference";
import { Timestamp } from "src/components/Timestamp";
import { formatDistanceDefaults } from "src/utils/date";
Expand All @@ -20,32 +21,28 @@ export function Byline(props: Props) {
: undefined;

return (
<HStack w="full" justifyContent="space-between">
<Flex
alignItems={{
// base: "start",
md: "center",
}}
gap={1}
fontSize="sm"
color="blackAlpha.700"
flexDir={{
// base: "column",
md: "row",
}}
>
<ProfileReference handle={props.author} />

<HStack>
<Text as="span"></Text>
</HStack>
<Flex
alignItems={{
// base: "start",
md: "center",
}}
gap={1}
fontSize="sm"
color="blackAlpha.700"
flexDir={{
// base: "column",
md: "row",
}}
>
<ProfileReference handle={props.author} />

<HStack>
<Timestamp created={created} updated={updated} href={props.href} />
</HStack>
</Flex>
<HStack>
<Text as="span"></Text>
</HStack>

{props.more}
</HStack>
<HStack>
<Timestamp created={created} updated={updated} href={props.href} />
</HStack>
</Flex>
);
}

1 comment on commit 0daa60a

@vercel
Copy link

@vercel vercel bot commented on 0daa60a Aug 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.