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
20 changes: 20 additions & 0 deletions app/docs/chevron-steps/chevron-steps-basic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import ChevronSteps from '@/components/core/chevron-steps';

export function ChevronStepsBasic() {
return (
<div className='w-full px-6'>
<ChevronSteps
steps={[
{ label: 'Step 1' },
{ label: 'Step 2 some words' },
{ label: 'Step 3' },
{ label: 'Step 4' },
]}
current={0}
variant='brand'
tailWidth={20}
className='mx-auto max-w-4xl'
/>
</div>
);
}
16 changes: 16 additions & 0 deletions app/docs/chevron-steps/chevron-steps-neutral.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import ChevronSteps from '@/components/core/chevron-steps';

export function ChevronStepsNeutral() {
return (
<div className='w-full px-6'>
<ChevronSteps
steps={[{ label: 'Plan' }, { label: 'Build' }, { label: 'Ship' }]}
current={1}
variant='neutral'
size='lg'
tailWidth={22}
className='mx-auto max-w-2xl'
/>
</div>
);
}
38 changes: 38 additions & 0 deletions app/docs/chevron-steps/chevron-steps-progress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';
import * as React from 'react';
import ChevronSteps from '@/components/core/chevron-steps';

export function ChevronStepsProgress() {
const [idx, setIdx] = React.useState(2);
return (
<div className='w-full px-6'>
<ChevronSteps
steps={[
{ label: 'Account' },
{ label: 'Details' },
{ label: 'Verification' },
{ label: 'Done' },
]}
current={idx}
onStepClick={setIdx}
variant='brand'
size='md'
className='mx-auto max-w-3xl'
/>
<div className='mt-4 flex justify-center gap-2'>
<button
className='rounded-md border px-3 py-1.5 text-sm'
onClick={() => setIdx((v) => Math.max(0, v - 1))}
>
Prev
</button>
<button
className='rounded-md border px-3 py-1.5 text-sm'
onClick={() => setIdx((v) => Math.min(3, v + 1))}
>
Next
</button>
</div>
</div>
);
}
74 changes: 74 additions & 0 deletions app/docs/chevron-steps/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
export const metadata = {
title: 'Chevron Steps – Shadcn-Extras',
description:
'Arrowed step progress bar with clickable segments. Accessible, responsive, and fully customizable.',
};

import ComponentCodePreview from '@/components/website/component-code-preview';
import { ChevronStepsBasic } from './chevron-steps-basic';
import { ChevronStepsProgress } from './chevron-steps-progress';
import { ChevronStepsNeutral } from './chevron-steps-neutral';

# Chevron Steps

A clean chevron-style stepper with arrow tails. Great for multi-step forms and wizards.
Keyboard and screen-reader friendly, responsive, and customizable via props.

## Examples

### Brand

<ComponentCodePreview
component={<ChevronStepsBasic />}
filePath='app/docs/chevron-steps/chevron-steps-basic.tsx'
/>

### Clickable progress

<ComponentCodePreview
component={<ChevronStepsProgress />}
filePath='app/docs/chevron-steps/chevron-steps-progress.tsx'
/>

### Neutral, large

<ComponentCodePreview
component={<ChevronStepsNeutral />}
filePath='app/docs/chevron-steps/chevron-steps-neutral.tsx'
/>

## Installation

<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>

<TabsContent value='cli'>
<InstallationCli value='chevron-steps' />
</TabsContent>

<TabsContent value="manual">
<Steps className="space-y-6">
<Step>Copy the component into <code>components/core/chevron-steps.tsx</code>.</Step>
<CodeBlock filePath="components/core/chevron-steps.tsx" />
<Step>Use with your step data. Control the active index with the <code>current</code> prop.</Step>
</Steps>
</TabsContent>
</Tabs>

## Props

| Prop | Type | Default | Description |
| :------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | :-------- | :----------------------------------------------- |
| `steps` | `Array<{ id?: string\|number; label: string; description?: string; className?: string; disabled?: boolean }>` | — | Steps in order. |
| `current` | `number` | `0` | Zero-based active step index. |
| `onStepClick` | `(index, step) => void` | — | Called when a step is clicked (if not disabled). |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Height & text size. |
| `variant` | `'brand' \| 'neutral'` | `'brand'` | Color scheme. |
| `tailWidth` | `number` | `18` | Arrow head width (px). |
| `radius` | `'md' \| 'lg' \| 'xl' \| '2xl'` | `'2xl'` | Outer roundness. |
| `scrollable` | `boolean` | `true` | Enables horizontal scroll on small screens. |
| `className` | `string` | — | Root classes. |
| `stepClassName` / `stepActiveClassName` / `stepCompletedClassName` / `stepUpcomingClassName` | `string` | — | Slot overrides. |
20 changes: 13 additions & 7 deletions app/docs/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,21 @@ export const NAVIGATION: NavigationGroup[] = [
{
name: 'Core Components',
children: [
{ name: 'Timeline Rail', href: '/docs/timeline-rail', isNew: true },
{ name: 'Testimonial', href: '/docs/testimonial', isUpdated: true },
{ name: 'Timeline Rail', href: '/docs/timeline-rail' },
{ name: 'Testimonial', href: '/docs/testimonial' },
{ name: 'Chevron Steps', href: '/docs/chevron-steps', isNew: true },
{
name: 'Text Circle Scroll',
href: '/docs/text-circle-scroll',
isNew: true,
},
],
},
{
name: 'Pricing Card',
children: [
{ name: 'Pricing Card One', href: '/docs/pricing-card-one', isNew: true },
{ name: 'Pricing Card Two', href: '/docs/pricing-card-two', isNew: true },
{ name: 'Pricing Card One', href: '/docs/pricing-card-one' },
{ name: 'Pricing Card Two', href: '/docs/pricing-card-two' },
],
},
{
Expand All @@ -48,7 +54,7 @@ export const NAVIGATION: NavigationGroup[] = [
{
name: 'Leaderboard Card',
href: '/docs/leaderboard-card',
isNew: true,
isUpdated: true,
},
],
},
Expand Down Expand Up @@ -78,8 +84,8 @@ export const NAVIGATION: NavigationGroup[] = [
href: '/docs/blog-card-one',
},
{ name: 'Blog Card Two', href: '/docs/blog-card-two' },
{ name: 'Blog Card Three', href: '/docs/blog-card-three', isNew: true },
{ name: 'Blog Card Four', href: '/docs/blog-card-four', isNew: true },
{ name: 'Blog Card Three', href: '/docs/blog-card-three' },
{ name: 'Blog Card Four', href: '/docs/blog-card-four' },
],
},
];
74 changes: 74 additions & 0 deletions app/docs/text-circle-scroll/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
export const metadata = {
title: 'Text Circle Scroll – Shadcn-Extras',
description:
'Lay text (or any nodes) around a circle and rotate it with scroll and/or auto-spin. Fully customizable and accessible.',
};

import ComponentCodePreview from '@/components/website/component-code-preview';
import { TextCircleBasic } from './text-circle-basic';
import { TextCircleAuto } from './text-circle-auto';
import { TextCircleNodes } from './text-circle-nodes';

# Text Circle Scroll

Arrange any items around a circle and rotate the ring based on **scroll progress** (with spring), **auto-spin**, or a **combination of both**.

## Examples

### Scroll-driven

<ComponentCodePreview
component={<TextCircleBasic />}
filePath='app/docs/text-circle-scroll/text-circle-basic.tsx'
/>

### Auto-spin (no scroll)

<ComponentCodePreview
component={<TextCircleAuto />}
filePath='app/docs/text-circle-scroll/text-circle-auto.tsx'
/>

### Using custom nodes

<ComponentCodePreview
component={<TextCircleNodes />}
filePath='app/docs/text-circle-scroll/text-circle-nodes.tsx'
/>

## Installation

<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>

<TabsContent value='cli'>
<InstallationCli value='text-circle-scroll' />
</TabsContent>

<TabsContent value="manual">
<Steps className="space-y-6">
<Step>Copy the component into <code>components/core/text-circle-scroll.tsx</code>.</Step>
<CodeBlock filePath="components/core/text-circle-scroll.tsx" />
<Step>Pass your items, radius, and behaviors (scroll/auto-spin) via props.</Step>
</Steps>
</TabsContent>
</Tabs>

## Props

| Prop | Type | Default | Description |
| ----------------------------------------------------------------- | ------------------------- | ------- | ---------------------------------------- |
| `items` | `(string \| ReactNode)[]` | — | Items around the ring in order. |
| `radius` | `number` | `110` | Radius in px (center → baseline). |
| `innerGap` | `number` | `90` | Visual hole size (px). |
| `startAngle` | `number` | `-90` | First item angle in degrees. |
| `clockwise` | `boolean` | `true` | Direction of item order. |
| `rotateOnScroll` | `boolean` | `true` | Attach rotation to scroll. |
| `scrollDegrees` | `number` | `360` | Total degrees across viewport scrolling. |
| `autoSpinDegPerSec` | `number` | `0` | Constant rotation (deg/s). |
| `springStiffness` | `number` | `120` | Spring stiffness for scroll rotation. |
| `height` | `number \| string` | `320` | Outer container height. |
| `className` / `ringClassName` / `itemClassName` / `textClassName` | `string` | — | Slot overrides. |
27 changes: 27 additions & 0 deletions app/docs/text-circle-scroll/text-circle-auto.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import TextCircleScroll from '@/components/core/text-circle-scroll';

export function TextCircleAuto() {
const words = Array.from(
{ length: 16 },
(_, i) =>
(['Aurora', 'Lullaby', 'Labyrinth', 'Idyllic', 'Felicity'] as const)[
i % 5
]
);

return (
<div className='min-h-[380px] rounded-xl bg-amber-50/50 p-10 dark:bg-amber-950/20'>
<TextCircleScroll
items={words}
radius={120}
innerGap={80}
startAngle={-60}
clockwise={false}
rotateOnScroll={false}
autoSpinDegPerSec={18}
className='mx-auto max-w-xl'
textClassName='text-[17px] tracking-wide text-zinc-700 dark:text-zinc-100'
/>
</div>
);
}
33 changes: 33 additions & 0 deletions app/docs/text-circle-scroll/text-circle-basic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import TextCircleScroll from '@/components/core/text-circle-scroll';

const WORDS = [
'Bungalow',
'Aurora',
'Lullaby',
'Labyrinth',
'Idyllic',
'Felicity',
'Demure',
'Chatoyant',
'TheseDays',
'Demure',
'Aurora',
'Bungalow',
];

export function TextCircleBasic() {
return (
<div className='min-h-[420px] rounded-xl bg-zinc-100/70 p-10 dark:bg-zinc-900/40'>
<TextCircleScroll
items={WORDS}
radius={110}
innerGap={90}
startAngle={-90}
rotateOnScroll
scrollDegrees={300}
className='mx-auto max-w-xl'
textClassName='text-[18px] font-serif text-zinc-800 dark:text-zinc-100'
/>
</div>
);
}
46 changes: 46 additions & 0 deletions app/docs/text-circle-scroll/text-circle-nodes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import TextCircleScroll from '@/components/core/text-circle-scroll';

export function TextCircleNodes() {
const items = [
<span key={1} className='font-semibold'>
Design
</span>,
<span key={2} className='opacity-70'>
Motion
</span>,
<span key={3} className='italic'>
UI
</span>,
<span key={4} className='opacity-70'>
DX
</span>,
<span key={5} className='font-semibold'>
A11y
</span>,
<span key={6} className='opacity-70'>
Next.js
</span>,
<span key={7} className='font-semibold'>
Tailwind
</span>,
<span key={8} className='opacity-70'>
Framer
</span>,
];

return (
<div className='min-h-[380px] rounded-xl bg-sky-50/60 p-10 dark:bg-sky-950/20'>
<TextCircleScroll
items={items}
radius={95}
innerGap={70}
startAngle={-90}
rotateOnScroll
autoSpinDegPerSec={10}
scrollDegrees={180}
className='mx-auto max-w-md'
textClassName='text-[14px] uppercase tracking-[0.18em] text-sky-700 dark:text-sky-200'
/>
</div>
);
}
4 changes: 2 additions & 2 deletions cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading