Skip to content

Commit d45412b

Browse files
committed
Add new UI components and minor card padding adjustments
Introduce new UI components including Tabs, Collapsible, Markdown, SearchFilter, Sonner, Switch, Carousel, Icons, FloatingActionButton, Spinner, and CodeBlock. Also, adjust padding in the Card component to maintain consistent spacing. Took 27 seconds
1 parent 3ae7964 commit d45412b

File tree

12 files changed

+694
-3
lines changed

12 files changed

+694
-3
lines changed

components/ui/SearchFilter.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use client'
2+
3+
import { Input } from "@/components/ui/input"
4+
import { useState } from "react"
5+
6+
type SearchFilterProps = {
7+
placeholder: string
8+
onSearch: (query: string) => void
9+
}
10+
11+
export default function SearchFilter({ placeholder, onSearch }: SearchFilterProps) {
12+
const [query, setQuery] = useState('')
13+
14+
const handleSearch = (value: string) => {
15+
setQuery(value)
16+
onSearch(value)
17+
}
18+
19+
return (
20+
<div className="w-full max-w-sm mb-8">
21+
<Input
22+
type="search"
23+
placeholder={placeholder}
24+
value={query}
25+
onChange={(e) => handleSearch(e.target.value)}
26+
className="w-full"
27+
/>
28+
</div>
29+
)
30+
}

components/ui/card.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const CardHeader = React.forwardRef<
2323
>(({ className, ...props }, ref) => (
2424
<div
2525
ref={ref}
26-
className={cn("flex flex-col space-y-1.5 p-6", className)}
26+
className={cn("flex flex-col space-y-1.5 p-4", className)}
2727
{...props}
2828
/>
2929
))
@@ -60,7 +60,7 @@ const CardContent = React.forwardRef<
6060
HTMLDivElement,
6161
React.HTMLAttributes<HTMLDivElement>
6262
>(({ className, ...props }, ref) => (
63-
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
63+
<div ref={ref} className={cn("p-4 pt-0", className)} {...props} />
6464
))
6565
CardContent.displayName = "CardContent"
6666

@@ -70,7 +70,7 @@ const CardFooter = React.forwardRef<
7070
>(({ className, ...props }, ref) => (
7171
<div
7272
ref={ref}
73-
className={cn("flex items-center p-6 pt-0", className)}
73+
className={cn("flex items-center p-4 pt-0", className)}
7474
{...props}
7575
/>
7676
))

components/ui/carousel.tsx

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
'use client'
2+
3+
import * as React from 'react'
4+
import useEmblaCarousel, {
5+
type UseEmblaCarouselType
6+
} from 'embla-carousel-react'
7+
import { ArrowLeft, ArrowRight } from 'lucide-react'
8+
9+
import { cn } from '@/lib/utils'
10+
import { Button } from '@/components/ui/button'
11+
12+
type CarouselApi = UseEmblaCarouselType[1]
13+
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
14+
type CarouselOptions = UseCarouselParameters[0]
15+
type CarouselPlugin = UseCarouselParameters[1]
16+
17+
type CarouselProps = {
18+
opts?: CarouselOptions
19+
plugins?: CarouselPlugin
20+
orientation?: 'horizontal' | 'vertical'
21+
setApi?: (api: CarouselApi) => void
22+
}
23+
24+
type CarouselContextProps = {
25+
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
26+
api: ReturnType<typeof useEmblaCarousel>[1]
27+
scrollPrev: () => void
28+
scrollNext: () => void
29+
canScrollPrev: boolean
30+
canScrollNext: boolean
31+
} & CarouselProps
32+
33+
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
34+
35+
function useCarousel() {
36+
const context = React.useContext(CarouselContext)
37+
38+
if (!context) {
39+
throw new Error('useCarousel must be used within a <Carousel />')
40+
}
41+
42+
return context
43+
}
44+
45+
const Carousel = React.forwardRef<
46+
HTMLDivElement,
47+
React.HTMLAttributes<HTMLDivElement> & CarouselProps
48+
>(
49+
(
50+
{
51+
orientation = 'horizontal',
52+
opts,
53+
setApi,
54+
plugins,
55+
className,
56+
children,
57+
...props
58+
},
59+
ref
60+
) => {
61+
const [carouselRef, api] = useEmblaCarousel(
62+
{
63+
...opts,
64+
axis: orientation === 'horizontal' ? 'x' : 'y'
65+
},
66+
plugins
67+
)
68+
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
69+
const [canScrollNext, setCanScrollNext] = React.useState(false)
70+
71+
const onSelect = React.useCallback((api: CarouselApi) => {
72+
if (!api) {
73+
return
74+
}
75+
76+
setCanScrollPrev(api.canScrollPrev())
77+
setCanScrollNext(api.canScrollNext())
78+
}, [])
79+
80+
const scrollPrev = React.useCallback(() => {
81+
api?.scrollPrev()
82+
}, [api])
83+
84+
const scrollNext = React.useCallback(() => {
85+
api?.scrollNext()
86+
}, [api])
87+
88+
const handleKeyDown = React.useCallback(
89+
(event: React.KeyboardEvent<HTMLDivElement>) => {
90+
if (event.key === 'ArrowLeft') {
91+
event.preventDefault()
92+
scrollPrev()
93+
} else if (event.key === 'ArrowRight') {
94+
event.preventDefault()
95+
scrollNext()
96+
}
97+
},
98+
[scrollPrev, scrollNext]
99+
)
100+
101+
React.useEffect(() => {
102+
if (!api || !setApi) {
103+
return
104+
}
105+
106+
setApi(api)
107+
}, [api, setApi])
108+
109+
React.useEffect(() => {
110+
if (!api) {
111+
return
112+
}
113+
114+
onSelect(api)
115+
api.on('reInit', onSelect)
116+
api.on('select', onSelect)
117+
118+
return () => {
119+
api?.off('select', onSelect)
120+
}
121+
}, [api, onSelect])
122+
123+
return (
124+
<CarouselContext.Provider
125+
value={{
126+
carouselRef,
127+
api: api,
128+
opts,
129+
orientation:
130+
orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'),
131+
scrollPrev,
132+
scrollNext,
133+
canScrollPrev,
134+
canScrollNext
135+
}}
136+
>
137+
<div
138+
ref={ref}
139+
onKeyDownCapture={handleKeyDown}
140+
className={cn('relative', className)}
141+
role="region"
142+
aria-roledescription="carousel"
143+
{...props}
144+
>
145+
{children}
146+
</div>
147+
</CarouselContext.Provider>
148+
)
149+
}
150+
)
151+
Carousel.displayName = 'Carousel'
152+
153+
const CarouselContent = React.forwardRef<
154+
HTMLDivElement,
155+
React.HTMLAttributes<HTMLDivElement>
156+
>(({ className, ...props }, ref) => {
157+
const { carouselRef, orientation } = useCarousel()
158+
159+
return (
160+
<div ref={carouselRef} className="overflow-hidden">
161+
<div
162+
ref={ref}
163+
className={cn(
164+
'flex',
165+
orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',
166+
className
167+
)}
168+
{...props}
169+
/>
170+
</div>
171+
)
172+
})
173+
CarouselContent.displayName = 'CarouselContent'
174+
175+
const CarouselItem = React.forwardRef<
176+
HTMLDivElement,
177+
React.HTMLAttributes<HTMLDivElement>
178+
>(({ className, ...props }, ref) => {
179+
const { orientation } = useCarousel()
180+
181+
return (
182+
<div
183+
ref={ref}
184+
role="group"
185+
aria-roledescription="slide"
186+
className={cn(
187+
'min-w-0 shrink-0 grow-0 basis-full',
188+
orientation === 'horizontal' ? 'pl-4' : 'pt-4',
189+
className
190+
)}
191+
{...props}
192+
/>
193+
)
194+
})
195+
CarouselItem.displayName = 'CarouselItem'
196+
197+
const CarouselPrevious = React.forwardRef<
198+
HTMLButtonElement,
199+
React.ComponentProps<typeof Button>
200+
>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
201+
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
202+
203+
return (
204+
<Button
205+
ref={ref}
206+
variant={variant}
207+
size={size}
208+
className={cn(
209+
'absolute h-8 w-8 rounded-full',
210+
orientation === 'horizontal'
211+
? '-left-12 top-1/2 -translate-y-1/2'
212+
: '-top-12 left-1/2 -translate-x-1/2 rotate-90',
213+
className
214+
)}
215+
disabled={!canScrollPrev}
216+
onClick={scrollPrev}
217+
{...props}
218+
>
219+
<ArrowLeft className="h-4 w-4" />
220+
<span className="sr-only">Previous slide</span>
221+
</Button>
222+
)
223+
})
224+
CarouselPrevious.displayName = 'CarouselPrevious'
225+
226+
const CarouselNext = React.forwardRef<
227+
HTMLButtonElement,
228+
React.ComponentProps<typeof Button>
229+
>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
230+
const { orientation, scrollNext, canScrollNext } = useCarousel()
231+
232+
return (
233+
<Button
234+
ref={ref}
235+
variant={variant}
236+
size={size}
237+
className={cn(
238+
'absolute h-8 w-8 rounded-full',
239+
orientation === 'horizontal'
240+
? '-right-12 top-1/2 -translate-y-1/2'
241+
: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',
242+
className
243+
)}
244+
disabled={!canScrollNext}
245+
onClick={scrollNext}
246+
{...props}
247+
>
248+
<ArrowRight className="h-4 w-4" />
249+
<span className="sr-only">Next slide</span>
250+
</Button>
251+
)
252+
})
253+
CarouselNext.displayName = 'CarouselNext'
254+
255+
export {
256+
type CarouselApi,
257+
Carousel,
258+
CarouselContent,
259+
CarouselItem,
260+
CarouselPrevious,
261+
CarouselNext
262+
}

0 commit comments

Comments
 (0)