Building Blocks for the Web
Clean, modern building blocks. Copy and paste into your apps. Works with all React frameworks. Open Source. Free forever.
app/post-feed/page.tsx
"use client";
import * as React from "react";
import {
QueryClient,
QueryClientProvider,
useInfiniteQuery,
} from "@tanstack/react-query";
import { PostResolver } from "@/components/posts-resolver";
import { POSTS_STREAM } from "@/registry/default/blocks/social-content/post-feed/mock-data";
import { Button } from "@/components/ui/button";
import { FeedCTA } from "@/components/ui/feed-cta";
import {
InfiniteScroll,
InfiniteScrollList,
} from "@/components/ui/infinite-scroll";
import { Stack } from "@/components/ui/stack";
const queryClient = new QueryClient();
async function fetchPostsStream(
limit: number,
offset: number = 0
): Promise<{ ids: string[]; nextOffset: number }> {
const startIndex = offset * limit;
const endIndex = Math.min(startIndex + limit, POSTS_STREAM.length);
const ids = POSTS_STREAM.slice(startIndex, endIndex).map((post) => post.id);
return {
ids,
nextOffset: endIndex < POSTS_STREAM.length ? offset + 1 : -1,
};
}
function Posts() {
const {
status,
data,
error,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
} = useInfiniteQuery({
queryKey: ["post-ids"],
queryFn: (ctx) => fetchPostsStream(10, ctx.pageParam),
getNextPageParam: (lastGroup) =>
lastGroup.nextOffset === -1 ? undefined : lastGroup.nextOffset,
initialPageParam: 0,
});
const postIds = data ? data.pages.flatMap((d) => d.ids) : [];
return (
<Stack
spacing={4}
className="p-4 **:data-[slot=infinite-scroll-container]:h-fit h-screen"
>
<FeedCTA
profileDID="did:pkh:eip155:11155111:0x1a4b3c567890abcdeffedcba1234567890abcdef"
avatarSrc="https://github.com/akashaorg.png"
cta="From your mind to the world. "
onClick={() => {
console.log("Show beam editor");
}}
>
<Button
size="sm"
onClick={() => {
console.log("Show beam editor");
}}
>
Post Something
</Button>
</FeedCTA>
{status === "pending" ? (
<p>Loading...</p>
) : status === "error" ? (
<span>Error: {error.message}</span>
) : (
<InfiniteScroll
count={postIds.length}
estimatedHeight={220}
overScan={5}
gap={16}
loading={isFetchingNextPage}
hasNextPage={hasNextPage}
onLoadMore={() => {
fetchNextPage();
}}
loadingIndicator="Loading ..."
scrollElementType="element"
>
<InfiniteScrollList>
{(index) => {
const postId = postIds[index];
return <PostResolver key={postId} postId={postId} />;
}}
</InfiniteScrollList>
</InfiniteScroll>
)}
</Stack>
);
}
export default function Page() {
return (
<QueryClientProvider client={queryClient}>
<Posts />
</QueryClientProvider>
);
}
app/post/page.tsx
"use client";
import * as React from "react";
import {
QueryClient,
QueryClientProvider,
useInfiniteQuery,
} from "@tanstack/react-query";
import { Ellipsis } from "lucide-react";
import { cn } from "@/lib/utils";
import { PostCard } from "@/registry/default/blocks/social-content/post-card";
import {
POST,
REPLIES_STREAM,
} from "@/registry/default/blocks/social-content/post-page/mock-data";
import { ReplyEditor } from "@/registry/default/blocks/social-content/reply-editor";
import { ReplyResolver } from "@/registry/default/blocks/social-content/reply-resolver";
import { Card } from "@/components/ui/card";
import {
InfiniteScroll,
InfiniteScrollList,
} from "@/components/ui/infinite-scroll";
const queryClient = new QueryClient();
async function fetchRepliesStream(
limit: number,
offset: number = 0
): Promise<{ ids: string[]; nextOffset: number }> {
const startIndex = offset * limit;
const endIndex = Math.min(startIndex + limit, REPLIES_STREAM.length);
const ids = REPLIES_STREAM.slice(startIndex, endIndex).map((post) => post.id);
return {
ids,
nextOffset: endIndex < REPLIES_STREAM.length ? offset + 1 : -1,
};
}
function Post() {
const { content, ...postProps } = POST;
const {
status,
data,
error,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
} = useInfiniteQuery({
queryKey: ["post-ids"],
queryFn: (ctx) => fetchRepliesStream(10, ctx.pageParam),
getNextPageParam: (lastGroup) =>
lastGroup.nextOffset === -1 ? undefined : lastGroup.nextOffset,
initialPageParam: 0,
});
const replyIds = data ? data.pages.flatMap((d) => d.ids) : [];
return (
<div className="p-4 h-full">
<PostCard
{...postProps}
onRepliesClick={() => {
console.log("Not implemented");
}}
className="rounded-b-none"
menu={{
trigger: (
<Ellipsis size={20} className="text-primary hover:text-muted" />
),
items: [
{ content: "Flag", onClick: () => console.log("flag") },
{
content: "Delete",
className: "text-destructive",
onClick: () => console.log("delete"),
},
{ content: "Edit", onClick: () => console.log("edit") },
],
}}
>
{content}
</PostCard>
<Card className="border-y-0 p-2 rounded-none">
<ReplyEditor
avatarSrc={"https://github.com/akashaorg.png"}
onReplyClick={() => {
console.log("Not implemented");
}}
/>
</Card>
{status === "pending" ? (
<p>Loading...</p>
) : status === "error" ? (
<span>Error: {error.message}</span>
) : (
<InfiniteScroll
count={replyIds.length}
estimatedHeight={220}
overScan={5}
gap={0}
loading={isFetchingNextPage}
hasNextPage={hasNextPage}
onLoadMore={() => {
fetchNextPage();
}}
loadingIndicator="Loading ..."
scrollElementType="window"
>
<InfiniteScrollList>
{(index) => {
const replyId = replyIds[index];
return (
<ReplyResolver
key={replyId}
replyId={replyId}
className={cn(
index === replyIds.length - 1
? "rounded-t-none"
: "rounded-none border-b-0"
)}
/>
);
}}
</InfiniteScrollList>
</InfiniteScroll>
)}
</div>
);
}
export default function Page() {
return (
<QueryClientProvider client={queryClient}>
<Post />
</QueryClientProvider>
);
}