Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/components/nav/animated-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const PLAY: Record<string, Play> = {
'fold-horizontal': squeeze,
'flip-horizontal': flip,
'gallery-horizontal': flip,
'gallery-horizontal-end': flip,
cylinder: flip,
images: flip,
'image-plus': flip,
Expand Down
171 changes: 171 additions & 0 deletions apps/docs/content/2.block/arc-carousel.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
---
title: Arc Carousel
description: A showcase carousel whose cards hang on a downward arc and swing round it as you step.
icon: gallery-horizontal-end
---

A showcase for a handful of pieces, laid out on a curve. The cards **hang on a
downward arc**, each one further from the centre sitting a little lower and
**turned a little more**, three to a side. **Step** and the whole arc swings
round: every card glides to its neighbour's slot on a slow, weighted curve,
while the one arriving in the middle **eases up in size** and takes a **lime
outline**. A card that wraps past the edge **keeps travelling off-screen**
while it comes back in **from the far side**, so the loop never snaps.
**Swipe**, **drag**, use the **arrow keys**, or **click a side card** to bring
it to the centre. Plain CSS transitions, no animation library.

::component-preview{name="arc-carousel" size="xl"}
::

## Installation

::tabs
:::tabs-item{label="CLI" icon="i-lucide-terminal"}

::install-tabs{item="arc-carousel"}
::

:::
:::tabs-item{label="Manual" icon="i-lucide-clipboard"}

::component-source{name="arc-carousel"}
::

:::
::

## Usage

::tabs
:::tabs-item{label="React" icon="i-lucide-atom"}

```tsx [app/page.tsx]
import ArcCarousel from '@/components/tweenui/arc-carousel';

export default function Page() {
return <ArcCarousel />;
}
```

:::
::

It ships with eight sample projects. Pass your own as `items`, and point the
button wherever the full list lives:

```tsx [app/page.tsx]
import ArcCarousel from '@/components/tweenui/arc-carousel';

export default function Page() {
return (
<ArcCarousel
badge="Just in"
title="New this season."
description="Fresh drops from the workshop, restocked every Friday."
cta={{ label: 'Shop the collection', href: '/shop' }}
items={[
{
title: 'Field Jacket',
tag: 'Outerwear',
meta: '$240',
image: '/shop/jacket.jpg',
href: '/shop/jacket',
},
{
title: 'Day Pack',
tag: 'Bags',
meta: '$120',
image: '/shop/pack.jpg',
href: '/shop/pack',
},
// …
]}
/>
);
}
```

The colors are the Tween UI theme tokens, used as Tailwind classes rather than
raw hex: `tween-ink-deep` (`#03110f`) for the stage and the edge fades,
`tween-ink` (`#071d1b`) under each photo, a `tween-teal` (`#045f64`) glow
behind the arc, `tween-teal-soft` (`#9fd4d6`) for the supporting text and
`tween-lime` (`#c6f56f`) on the badge, the centre card's outline and arrow,
and the button. The CLI adds them to your `@theme`; with a manual install,
copy the CSS block from the Manual tab into your global stylesheet. Change a
token there and the block follows.

### Autoplay

Autoplay is on by default: a new card takes the centre every 5 seconds, and a
pause / play button sits beside the arrows with a lime ring filling up as the
current card's time runs out. Set the pace with `interval`, or turn it off with
`autoplay={false}`, which also drops the button:

```tsx [app/page.tsx]
<ArcCarousel interval={4000} /> // a new card every 4s
<ArcCarousel autoplay={false} /> // steps only when the viewer does
```

## Props

| Prop | Type | Default | Description |
| ------------- | ----------------------------------------- | ------------------------------------ | -------------------------------------------------- |
| `items` | `ArcCarouselItem[]` | Eight sample projects | The cards on the arc. |
| `badge` | `string` | `'Featured work'` | Pill above the title. Pass `''` to drop it. |
| `title` | `string` | `'Projects we shipped this season.'` | Heading, and the carousel's accessible name. |
| `description` | `string` | How to browse | Line under the heading. Pass `''` to drop it. |
| `cta` | `{ label: string; href: string } \| null` | `View all projects` | Button between the arrows. Pass `null` to drop it. |
| `autoplay` | `boolean` | `true` | Step to the next card on its own. |
| `interval` | `number` | `5000` | Time each card holds the centre, in ms. |

Each item is `{ title, image, tag?, meta?, href? }`. The `tag` is the chip on
the top left of the card and `meta` the line under the title. With an `href`
the centre card is a link and shows a round arrow. Extends native `<section>`
attributes (`className`, `style`, …).

## Behavior

- **One card size, one arc.** Every card is 312 × 430px and the slots are
fixed offsets from the centre, so the curve keeps its shape whatever the
content. Below 1024px of container width the whole arc scales to 80%, and
below 768px to 62%, so it fits a phone without relaying out.
- **Wraps without a jump.** When a card has to move from one end of the loop
to the other, a copy of it carries on off the near edge and fades, while
the card itself is placed past the far edge and slides in. The copy is
removed once its move has finished.
- **Side cards step, the centre card opens.** A click on a side card moves
the arc one step toward it instead of following its link, and a swipe or
drag of more than 50px sideways steps it the other way and swallows the
click that ends it. Vertical swipes still scroll the page.
- **The centre card steps forward.** Side cards sit under a light shade of
the stage color; the centre card loses it, grows 5%, takes a lime outline
and turns its arrow lime, so the eye lands on it first.
- **The edges fade, the arc does not stop.** The soft fades on both sides sit
above the cards, so the outer ones run out into the background instead of
being cut off by the edge of the section.
- **Light on the browser.** Cards move on `transform` and `opacity` only,
with no blur. Photos come at 1x and 2x with fixed dimensions; the five
nearest the centre load first and the rest lazily.
- **Autoplay waits for you.** It holds while the mouse rests on the cards,
while the cards have keyboard focus and while the block is off-screen, and
picks up where it left off. Stepping by hand gives the new card a full
interval. A backgrounded tab never makes it skip ahead on return.
- **It is in place before it runs.** The first layout is written into the
markup, so the arc renders already curved and nothing flashes into place
on load.

## Accessibility

::note
The carousel is a focusable group with `aria-roledescription="carousel"`,
named by the title, and the left and right arrow keys step it while it has
focus. Each card is a slide labelled `n of total: title`; cards off the
centre are hidden from screen readers and out of the tab order, so only the
card you are looking at can be reached. The current card is announced
politely after every step. The arrows are real buttons with labels and every
control has a visible lime focus ring. With autoplay on there is always a
labelled pause button, and the announcement goes quiet while the carousel plays
itself, so a screen reader is not interrupted every few seconds. Under
`prefers-reduced-motion: reduce` autoplay starts paused, the cards fade
between slots instead of travelling, and the centre card does not grow.
::
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
27 changes: 27 additions & 0 deletions apps/docs/public/r/arc-carousel-demo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "arc-carousel-demo",
"type": "registry:example",
"title": "Arc Carousel Demo",
"description": "Usage examples for Arc Carousel.",
"registryDependencies": [
"https://tween-ui.vercel.app/r/arc-carousel.json"
],
"files": [
{
"path": "examples/arc-carousel-demo.tsx",
"type": "registry:example",
"content": "import ArcCarousel from '@/components/tweenui/arc-carousel';\n\nexport default function Page() {\n return <ArcCarousel />;\n}"
},
{
"path": "examples/arc-carousel-demo-2.tsx",
"type": "registry:example",
"content": "import ArcCarousel from '@/components/tweenui/arc-carousel';\n\nexport default function Page() {\n return (\n <ArcCarousel\n badge=\"Just in\"\n title=\"New this season.\"\n description=\"Fresh drops from the workshop, restocked every Friday.\"\n cta={{ label: 'Shop the collection', href: '/shop' }}\n items={[\n {\n title: 'Field Jacket',\n tag: 'Outerwear',\n meta: '$240',\n image: '/shop/jacket.jpg',\n href: '/shop/jacket',\n },\n {\n title: 'Day Pack',\n tag: 'Bags',\n meta: '$120',\n image: '/shop/pack.jpg',\n href: '/shop/pack',\n },\n // …\n ]}\n />\n );\n}"
},
{
"path": "examples/arc-carousel-demo-3.tsx",
"type": "registry:example",
"content": "<ArcCarousel interval={4000} /> // a new card every 4s\n<ArcCarousel autoplay={false} /> // steps only when the viewer does"
}
]
}
28 changes: 28 additions & 0 deletions apps/docs/public/r/arc-carousel.json

Large diffs are not rendered by default.

Loading
Loading