Skip to content

Commit ddc857c

Browse files
carousel
1 parent 0938849 commit ddc857c

File tree

7 files changed

+421
-18
lines changed

7 files changed

+421
-18
lines changed

package-lock.json

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"@radix-ui/react-slot": "^1.2.3",
1313
"class-variance-authority": "^0.7.1",
1414
"clsx": "^2.1.1",
15+
"embla-carousel-react": "^8.6.0",
1516
"fetch-loading": "^0.0.7",
17+
"lucide-react": "^0.543.0",
1618
"next": "15.5.2",
1719
"react": "19.1.0",
1820
"react-dom": "19.1.0",

src/app/globals.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ html {
3939
@apply text-base sm:text-lg lg:text-xl;
4040
}
4141

42+
.text-very-large {
43+
@apply text-xl sm:text-2xl lg:text-3xl;
44+
}
45+
4246
@theme inline {
4347
--radius-sm: calc(var(--radius) - 4px);
4448
--radius-md: calc(var(--radius) - 2px);

src/app/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export default function Home() {
1414
<div className="min-h-svh flex flex-col items-center justify-start bg-gradient-to-b from-stone-700 to-stone-800">
1515
<Navigation />
1616
<p>Hero Section</p>
17-
<p>Carousel with Top 10 Posts</p>
1817
<Feed />
1918
<Footer />
2019
</div>

src/components/feed/Feed.tsx

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ import {
1111
CardHeader,
1212
CardTitle,
1313
} from '@/components/ui/card'
14+
import {
15+
Carousel,
16+
CarouselContent,
17+
CarouselItem,
18+
CarouselNext,
19+
CarouselPrevious,
20+
} from '@/components/ui/carousel'
1421
import fetchFeedItems from '@/api/fetchFeedItems'
1522
import { FeedItemsType } from '@/types/types'
1623
import { getItemFromSessionStorage } from '@/utils/getItemFromSessionStorage'
@@ -23,7 +30,9 @@ const Feed = () => {
2330
const [isLoading, setIsLoading] = useState(false)
2431

2532
const loadMoreItems = useCallback(async () => {
26-
if (isLoading) return
33+
if (isLoading) {
34+
return
35+
}
2736
setIsLoading(true)
2837
await fetchFeedItems(nextPage, PAGE_SIZE, setFeedItems)
2938
setIsLoading(false)
@@ -61,22 +70,72 @@ const Feed = () => {
6170
drop-shadow-stone-900 drop-shadow-sm"
6271
>
6372
{feedItems.length > 0 ? (
64-
<div className="grid grid-cols-[repeat(auto-fit,minmax(min(100%,448px),1fr))] grid-flow-row-dense w-full gap-3 sm:gap-4 lg:gap-6">
65-
{feedItems.map((item) => (
66-
<Card key={item.id}>
67-
<CardHeader>
68-
<CardTitle>{item.name}</CardTitle>
69-
<CardDescription>{item.email}</CardDescription>
70-
</CardHeader>
71-
<CardContent>
72-
<p>{item.body}</p>
73-
</CardContent>
74-
<CardFooter>
75-
<Badge variant="outline">Post #{item.id}</Badge>
76-
</CardFooter>
77-
</Card>
78-
))}
79-
</div>
73+
<>
74+
<div className="w-full flex flex-col items-center mb-4 px-16 py-6 bg-stone-500 rounded-lg shadow-md shadow-stone-600">
75+
<div className="w-full flex flex-col gap-4 max-w-3xl">
76+
<h2 className="self-start text-very-large font-semibold text-zinc-100 underline">
77+
Top 10 Posts of the Week
78+
</h2>
79+
<div className="w-full">
80+
<Carousel>
81+
<CarouselContent>
82+
{feedItems.slice(0, 10).map((item) => (
83+
<CarouselItem key={item.id}>
84+
<Card>
85+
<CardHeader>
86+
<CardTitle>
87+
{item.name}
88+
</CardTitle>
89+
<CardDescription>
90+
{item.email}
91+
</CardDescription>
92+
</CardHeader>
93+
<CardContent>
94+
<p>{item.body}</p>
95+
</CardContent>
96+
<CardFooter>
97+
<Badge variant="outline">
98+
Post #{item.id}
99+
</Badge>
100+
</CardFooter>
101+
</Card>
102+
</CarouselItem>
103+
))}
104+
</CarouselContent>
105+
<CarouselPrevious />
106+
<CarouselNext />
107+
</Carousel>
108+
</div>
109+
</div>
110+
</div>
111+
{feedItems.length > 10 && (
112+
<>
113+
<h2 className="self-start text-very-large font-semibold underline px-4">
114+
More Hot Posts
115+
</h2>
116+
<div className="grid grid-cols-[repeat(auto-fit,minmax(min(100%,448px),1fr))] grid-flow-row-dense w-full gap-3 sm:gap-4 lg:gap-6">
117+
{feedItems.slice(10).map((item) => (
118+
<Card key={item.id}>
119+
<CardHeader>
120+
<CardTitle>{item.name}</CardTitle>
121+
<CardDescription>
122+
{item.email}
123+
</CardDescription>
124+
</CardHeader>
125+
<CardContent>
126+
<p>{item.body}</p>
127+
</CardContent>
128+
<CardFooter>
129+
<Badge variant="outline">
130+
Post #{item.id}
131+
</Badge>
132+
</CardFooter>
133+
</Card>
134+
))}
135+
</div>
136+
</>
137+
)}
138+
</>
80139
) : null}
81140

82141
{isLoading && (

src/components/ui/button.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as React from 'react'
2+
import { Slot } from '@radix-ui/react-slot'
3+
import { cva, type VariantProps } from 'class-variance-authority'
4+
5+
import { cn } from '@/lib/utils'
6+
7+
const buttonVariants = cva(
8+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9+
{
10+
variants: {
11+
variant: {
12+
default:
13+
'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
14+
destructive:
15+
'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
16+
outline:
17+
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
18+
secondary:
19+
'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
20+
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
21+
link: 'text-primary underline-offset-4 hover:underline',
22+
},
23+
size: {
24+
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
25+
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
26+
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
27+
icon: 'size-9',
28+
},
29+
},
30+
defaultVariants: {
31+
variant: 'default',
32+
size: 'default',
33+
},
34+
}
35+
)
36+
37+
function Button({
38+
className,
39+
variant,
40+
size,
41+
asChild = false,
42+
...props
43+
}: React.ComponentProps<'button'> &
44+
VariantProps<typeof buttonVariants> & {
45+
asChild?: boolean
46+
}) {
47+
const Comp = asChild ? Slot : 'button'
48+
49+
return (
50+
<Comp
51+
data-slot="button"
52+
className={cn(buttonVariants({ variant, size, className }))}
53+
{...props}
54+
/>
55+
)
56+
}
57+
58+
export { Button, buttonVariants }

0 commit comments

Comments
 (0)