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
78 changes: 78 additions & 0 deletions apps/web/content/docs/zen/carousel.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
title: Carousel
description: A slide track with previous and next controls, built on Embla Carousel.
---

Carousel wraps [embla-carousel-react](https://www.embla-carousel.com/) in a
track, a slide and two icon controls. Each slide takes the full width of the
track, and the controls scroll the track one slide at a time. Arrow keys work
when the track has focus.

It is an Authored component: shadcn/ui owns its structure and its design comes
from the nearest Port, because coss/ui ships no carousel. The controls inherit
their surface, focus ring and disabled treatment from `Button`, and a slide
inherits its sizing from `AspectRatio`.

## Installation

```bash
npx shadcn@latest add @kosori/carousel
```

## Example

<ComponentPreview name='carousel' />

## Usage

```tsx
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel';

const CarouselDemo = () => (
<Carousel className='w-full max-w-sm'>
<CarouselContent>
<CarouselItem>Slide one</CarouselItem>
<CarouselItem>Slide two</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
```

The previous and next controls sit outside the track, so leave room for them:
the Example pads its container by `px-12`.

## Orientation

`orientation` switches the scroll axis. The controls rotate and move above and
below the track, so a vertical carousel needs vertical room instead:

```tsx
<Carousel className='h-64 w-full max-w-xs' orientation='vertical'>
<CarouselContent>
<CarouselItem>Slide one</CarouselItem>
<CarouselItem>Slide two</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
```

## API

| Prop | Type | Default | Purpose |
| --- | --- | --- | --- |
| `opts` | Embla options | `undefined` | Forwarded to Embla Carousel |
| `orientation` | `horizontal`, `vertical` | `horizontal` | Scroll axis |
| `plugins` | Embla plugins | `undefined` | Forwarded to Embla Carousel |
| `setApi` | `(api: CarouselApi) => void` | `undefined` | Receives the Embla API |

`useCarousel` reads the nearest carousel's API and scroll state, and throws
outside a `Carousel`.
63 changes: 63 additions & 0 deletions apps/web/content/docs/zen/chart.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
title: Chart
description: Composes Recharts into a responsive container with themed tooltips and legends.
---

Chart is an Authored component: coss/ui has no counterpart, so it takes
shadcn/ui's Structure and wraps [Recharts](https://recharts.org) rather than
copying an upstream file. The container, tooltip and legend reuse kosori's
design vocabulary: the tooltip surface comes from Popover, muted labels use
`text-muted-foreground`, and series colours come from the `--chart-1` to
`--chart-5` Tokens through `ChartStyle`.

## Installation

```bash
npx shadcn@latest add @kosori/chart
```

## Example

<ComponentPreview name='chart' />

## Usage

```tsx
'use client';

import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts';
import {
type ChartConfig,
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
} from '@/components/ui/chart';

const chartConfig = {
desktop: { label: 'Desktop', color: 'var(--chart-1)' },
} satisfies ChartConfig;

const data = [
{ desktop: 186, month: 'January' },
{ desktop: 305, month: 'February' },
];

const ChartDemo = () => (
<ChartContainer className='w-full' config={chartConfig}>
<BarChart accessibilityLayer data={data}>
<CartesianGrid vertical={false} />
<XAxis dataKey='month' tickLine={false} axisLine={false} />
<ChartTooltip content={<ChartTooltipContent />} />
<ChartLegend content={<ChartLegendContent />} />
<Bar dataKey='desktop' fill='var(--color-desktop)' radius={4} />
</BarChart>
</ChartContainer>
);
```

A `ChartConfig` entry labels a series and sets its colour. `ChartStyle` writes
those colours as custom properties on the container, so the same config works in
light and dark without a second definition. Pass `theme` instead of `color` when
a series needs a different value per scheme.
2 changes: 2 additions & 0 deletions apps/web/content/docs/zen/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"button-group",
"calendar",
"card",
"carousel",
"chart",
"checkbox",
"collapsible",
"combobox",
Expand Down
4 changes: 4 additions & 0 deletions apps/web/lib/registry/examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import ButtonExample from '@/registry/zen/examples/button-example';
import ButtonGroupExample from '@/registry/zen/examples/button-group-example';
import CalendarExample from '@/registry/zen/examples/calendar-example';
import CardExample from '@/registry/zen/examples/card-example';
import CarouselExample from '@/registry/zen/examples/carousel-example';
import ChartExample from '@/registry/zen/examples/chart-example';
import CheckboxExample from '@/registry/zen/examples/checkbox-example';
import CollapsibleExample from '@/registry/zen/examples/collapsible-example';
import ComboboxExample from '@/registry/zen/examples/combobox-example';
Expand Down Expand Up @@ -90,6 +92,8 @@ const zenExampleComponents = {
'button-group': ButtonGroupExample,
calendar: CalendarExample,
card: CardExample,
carousel: CarouselExample,
chart: ChartExample,
checkbox: CheckboxExample,
collapsible: CollapsibleExample,
combobox: ComboboxExample,
Expand Down
2 changes: 0 additions & 2 deletions apps/web/lib/registry/pending.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
export const PENDING_COMPONENTS = [
'attachment',
'bubble',
'carousel',
'chart',
'code-block',
'marker',
'message',
Expand Down
3 changes: 3 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@
"@tailwindcss/postcss": "4.3.3",
"class-variance-authority": "^0.7.1",
"cn": "^0.5.0",
"embla-carousel-react": "^8.6.0",
"fumadocs-core": "16.17.5",
"fumadocs-mdx": "15.4.7",
"fumadocs-ui": "npm:@fumadocs/base-ui@16.17.5",
"next": "16.3.8",
"postcss": "8.5.30",
"react": "19.2.8",
"react-dom": "19.2.8",
"react-is": "^19.3.1",
"react-resizable-panels": "4.14.8",
"recharts": "^3.10.1",
"shadcn": "^4.22.0",
"tailwindcss": "4.3.3",
"tw-animate-css": "^1.4.0"
Expand Down
66 changes: 66 additions & 0 deletions apps/web/registry/zen/examples/carousel-example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { AspectRatio } from '@/components/ui/aspect-ratio';
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel';

const SLIDES = [
{
className:
'overflow-hidden rounded-xl border bg-linear-to-br from-primary/16 to-background',
description: 'The first slide of the default carousel.',
title: 'One',
},
{
className:
'overflow-hidden rounded-xl border bg-linear-to-br from-info/16 to-background',
description: 'Slides scroll with the previous and next controls.',
title: 'Two',
},
{
className:
'overflow-hidden rounded-xl border bg-linear-to-br from-success/16 to-background',
description: 'The track also responds to the arrow keys.',
title: 'Three',
},
{
className:
'overflow-hidden rounded-xl border bg-linear-to-br from-warning/16 to-background',
description: 'Every slide shares the same width.',
title: 'Four',
},
{
className:
'overflow-hidden rounded-xl border bg-linear-to-br from-destructive/16 to-background',
description: 'The last slide disables the next control.',
title: 'Five',
},
];

const CarouselExample = () => (
<div className='w-full max-w-md px-12'>
<Carousel>
<CarouselContent>
{SLIDES.map((slide) => (
<CarouselItem key={slide.title}>
<AspectRatio className={slide.className} ratio={16 / 9}>
<div className='flex size-full flex-col justify-end gap-1 p-5'>
<span className='font-medium text-lg'>{slide.title}</span>
<span className='text-muted-foreground text-sm'>
{slide.description}
</span>
</div>
</AspectRatio>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
);

export default CarouselExample;
54 changes: 54 additions & 0 deletions apps/web/registry/zen/examples/chart-example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use client';

import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts';
import {
type ChartConfig,
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
} from '@/components/ui/chart';

const chartData = [
{ desktop: 186, mobile: 80, month: 'January' },
{ desktop: 305, mobile: 200, month: 'February' },
{ desktop: 237, mobile: 120, month: 'March' },
{ desktop: 73, mobile: 190, month: 'April' },
{ desktop: 209, mobile: 130, month: 'May' },
{ desktop: 214, mobile: 140, month: 'June' },
];

const formatMonth = (value: string) => value.slice(0, 3);

const chartConfig = {
desktop: {
color: 'var(--chart-1)',
label: 'Desktop',
},
mobile: {
color: 'var(--chart-2)',
label: 'Mobile',
},
} satisfies ChartConfig;

const ChartExample = () => (
<ChartContainer className='w-full' config={chartConfig}>
<BarChart accessibilityLayer data={chartData}>
<CartesianGrid vertical={false} />
<XAxis
axisLine={false}
dataKey='month'
tickFormatter={formatMonth}
tickLine={false}
tickMargin={10}
/>
<ChartTooltip content={<ChartTooltipContent />} />
<ChartLegend content={<ChartLegendContent />} />
<Bar dataKey='desktop' fill='var(--color-desktop)' radius={4} />
<Bar dataKey='mobile' fill='var(--color-mobile)' radius={4} />
</BarChart>
</ChartContainer>
);

export default ChartExample;
51 changes: 51 additions & 0 deletions apps/web/registry/zen/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,57 @@
}
]
},
{
"name": "carousel",
"type": "registry:ui",
"dependencies": ["@tabler/icons-react", "cn", "embla-carousel-react"],
"registryDependencies": ["@kosori/button"],
"files": [
{
"path": "registry/zen/ui/carousel.tsx",
"type": "registry:ui"
}
],
"meta": {
"authored": true
}
},
{
"name": "carousel-example",
"type": "registry:example",
"registryDependencies": ["@kosori/aspect-ratio", "@kosori/carousel"],
"files": [
{
"path": "registry/zen/examples/carousel-example.tsx",
"type": "registry:example"
}
]
},
{
"name": "chart",
"type": "registry:ui",
"dependencies": ["cn", "react-is", "recharts"],
"files": [
{
"path": "registry/zen/ui/chart.tsx",
"type": "registry:ui"
}
],
"meta": {
"authored": true
}
},
{
"name": "chart-example",
"type": "registry:example",
"registryDependencies": ["@kosori/chart"],
"files": [
{
"path": "registry/zen/examples/chart-example.tsx",
"type": "registry:example"
}
]
},
{
"name": "checkbox",
"type": "registry:ui",
Expand Down
Loading
Loading