Carousel 轮播
在有限视口中逐项浏览横向或纵向内容。
01
每日推荐
02
私人雷达
03
新歌速递
import * as React from "react"
import { Card, CardContent } from "@scopify/ui/shadcn/components/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@scopify/ui/shadcn/components/carousel"
export default function CarouselDemo() {
return (
<Carousel className="w-full max-w-[12rem] sm:max-w-xs">
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
)
}
About
The carousel component is built using the Embla Carousel library.
Scopify 说明
Installation
npx shadcn@latest add carouselUsage
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@scopify/ui/shadcn/components/carousel"<Carousel>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>Composition
Use the following composition to build a Carousel:
Carousel
├── CarouselContent
│ ├── CarouselItem
│ └── CarouselItem
├── CarouselPrevious
└── CarouselNextSizes
To set the size of the items, you can use the basis utility class on the <CarouselItem />.
01
每日推荐
02
私人雷达
03
新歌速递
import * as React from "react"
import { Card, CardContent } from "@scopify/ui/shadcn/components/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@scopify/ui/shadcn/components/carousel"
export default function CarouselSize() {
return (
<Carousel
opts={{
align: "start",
}}
className="w-full max-w-[12rem] sm:max-w-xs md:max-w-sm"
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="basis-1/2 lg:basis-1/3">
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-3xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
)
}
// 33% of the carousel width.
<Carousel>
<CarouselContent>
<CarouselItem className="basis-1/3">...</CarouselItem>
<CarouselItem className="basis-1/3">...</CarouselItem>
<CarouselItem className="basis-1/3">...</CarouselItem>
</CarouselContent>
</Carousel>// 50% on small screens and 33% on larger screens.
<Carousel>
<CarouselContent>
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
</CarouselContent>
</Carousel>Spacing
To set the spacing between the items, we use a pl-[VALUE] utility on the <CarouselItem /> and a negative -ml-[VALUE] on the <CarouselContent />.
01
每日推荐
02
私人雷达
03
新歌速递
import * as React from "react"
import { Card, CardContent } from "@scopify/ui/shadcn/components/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@scopify/ui/shadcn/components/carousel"
export default function CarouselSpacing() {
return (
<Carousel className="w-full max-w-[12rem] sm:max-w-xs md:max-w-sm">
<CarouselContent className="-ml-1">
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="basis-1/2 pl-1 lg:basis-1/3">
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-2xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
)
}
<Carousel>
<CarouselContent className="-ml-4">
<CarouselItem className="pl-4">...</CarouselItem>
<CarouselItem className="pl-4">...</CarouselItem>
<CarouselItem className="pl-4">...</CarouselItem>
</CarouselContent>
</Carousel><Carousel>
<CarouselContent className="-ml-2 md:-ml-4">
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
</CarouselContent>
</Carousel>Orientation
Use the orientation prop to set the orientation of the carousel.
01
每日推荐
02
私人雷达
03
新歌速递
import * as React from "react"
import { Card, CardContent } from "@scopify/ui/shadcn/components/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@scopify/ui/shadcn/components/carousel"
export default function CarouselOrientation() {
return (
<Carousel
opts={{
align: "start",
}}
orientation="vertical"
className="w-full max-w-xs"
>
<CarouselContent className="-mt-1 h-[270px]">
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="basis-1/2 pt-1">
<div className="p-1">
<Card>
<CardContent className="flex items-center justify-center p-6">
<span className="text-3xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
)
}
<Carousel orientation="vertical | horizontal">
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>Options
You can pass options to the carousel using the opts prop. See the Embla Carousel docs for more information.
<Carousel
opts={{
align: "start",
loop: true,
}}
>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>API
Use a state and the setApi props to get an instance of the carousel API.
01
每日推荐
02
私人雷达
03
新歌速递
"use client"
import * as React from "react"
import { Card, CardContent } from "@scopify/ui/shadcn/components/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
type CarouselApi,
} from "@scopify/ui/shadcn/components/carousel"
export default function CarouselDApiDemo() {
const [api, setApi] = React.useState<CarouselApi>()
const [current, setCurrent] = React.useState(0)
const [count, setCount] = React.useState(0)
React.useEffect(() => {
if (!api) {
return
}
setCount(api.scrollSnapList().length)
setCurrent(api.selectedScrollSnap() + 1)
api.on("select", () => {
setCurrent(api.selectedScrollSnap() + 1)
})
}, [api])
return (
<div className="mx-auto max-w-[10rem] sm:max-w-xs">
<Carousel setApi={setApi} className="w-full max-w-xs">
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<Card className="m-px">
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<div className="py-2 text-center text-sm text-muted-foreground">
Slide {current} of {count}
</div>
</div>
)
}
import { type CarouselApi } from "@scopify/ui/shadcn/components/carousel"
export function Example() {
const [api, setApi] = React.useState<CarouselApi>()
const [current, setCurrent] = React.useState(0)
const [count, setCount] = React.useState(0)
React.useEffect(() => {
if (!api) {
return
}
setCount(api.scrollSnapList().length)
setCurrent(api.selectedScrollSnap() + 1)
api.on("select", () => {
setCurrent(api.selectedScrollSnap() + 1)
})
}, [api])
return (
<Carousel setApi={setApi}>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
)
}Events
You can listen to events using the api instance from setApi.
import { type CarouselApi } from "@scopify/ui/shadcn/components/carousel"
export function Example() {
const [api, setApi] = React.useState<CarouselApi>()
React.useEffect(() => {
if (!api) {
return
}
api.on("select", () => {
// Do something on select.
})
}, [api])
return (
<Carousel setApi={setApi}>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
)
}See the Embla Carousel docs for more information on using events.
Plugins
You can use the plugins prop to add plugins to the carousel.
import Autoplay from "embla-carousel-autoplay"
export function Example() {
return (
<Carousel
plugins={[
Autoplay({
delay: 2000,
}),
]}
>
// ...
</Carousel>
)
}01
每日推荐
02
私人雷达
03
新歌速递
"use client"
import * as React from "react"
import Autoplay from "embla-carousel-autoplay"
import { Card, CardContent } from "@scopify/ui/shadcn/components/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@scopify/ui/shadcn/components/carousel"
export default function CarouselPlugin() {
const plugin = React.useRef(
Autoplay({ delay: 2000, stopOnInteraction: true })
)
return (
<Carousel
plugins={[plugin.current]}
className="w-full max-w-[10rem] sm:max-w-xs"
onMouseEnter={plugin.current.stop}
onMouseLeave={plugin.current.reset}
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
)
}
RTL
To enable RTL support in shadcn/ui, see the RTL configuration guide.
01
每日推荐
02
私人雷达
03
新歌速递
"use client"
import {
useTranslation,
type Translations,
} from "@/components/language-selector"
import { Card, CardContent } from "@/styles/radix-nova/ui-rtl/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/styles/radix-nova/ui-rtl/carousel"
const translations: Translations = {
en: {
dir: "ltr",
values: {},
},
ar: {
dir: "rtl",
values: {},
},
he: {
dir: "rtl",
values: {},
},
}
function toArabicNumerals(num: number): string {
const arabicNumerals = ["٠", "١", "٢", "٣", "٤", "٥", "٦", "٧", "٨", "٩"]
return num
.toString()
.split("")
.map((digit) => arabicNumerals[parseInt(digit, 10)])
.join("")
}
export function CarouselRtl() {
const { dir, language } = useTranslation(translations, "ar")
const formatNumber = (num: number): string => {
if (language === "ar") {
return toArabicNumerals(num)
}
return num.toString()
}
return (
<Carousel
dir={dir}
className="w-full max-w-[12rem] sm:max-w-xs"
opts={{
direction: dir,
}}
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<div className="p-1">
<Card dir={dir}>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">
{formatNumber(index + 1)}
</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
)
}
When localizing the carousel for RTL languages, you need to set the direction option in the opts prop to match the text direction. This ensures the carousel scrolls in the correct direction.
<Carousel
dir={dir}
opts={{
direction: dir,
}}
>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
<CarouselPrevious className="rtl:rotate-180" />
<CarouselNext className="rtl:rotate-180" />
</Carousel>The direction option accepts "ltr" or "rtl" and should match the dir prop value. You may also want to rotate the navigation buttons using the rtl:rotate-180 class to ensure they point in the correct direction.
API Reference
See the Embla Carousel docs for more information on props and plugins.