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
100 changes: 100 additions & 0 deletions packages/demo/src/components/demo/progress-indicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { useState } from "react";
import { ProgressIndicator, ProgressIndicatorStep } from "@eqtylab/equality";

/* Default horizontal layout with a mix of statuses. */
export function ProgressIndicatorDemo({
layout = "horizontal",
}: {
layout?: "horizontal" | "vertical";
}) {
return (
<div style={{ margin: "2rem 0" }}>
<ProgressIndicator
layout={layout}
currentIndex={2}
aria-label="Progress Indicator"
className="my-4"
>
<ProgressIndicatorStep
name="Success"
description="Completed"
status="success"
/>

<ProgressIndicatorStep
name="Danger"
description="Action required"
status="danger"
/>
<ProgressIndicatorStep
name="Editing"
description="In progress"
status="editing"
/>
<ProgressIndicatorStep
name="Info"
description="Read only step"
status="info"
/>
<ProgressIndicatorStep name="Neutral" status="neutral" />
<ProgressIndicatorStep
name="Empty"
description="Not yet edited"
status="empty"
/>
</ProgressIndicator>
</div>
);
}

const STEPS = ["Getting Started", "Policy Details", "Controls", "Review"];

/* Interactive example: clicking a step makes it the current step. */
export function ProgressIndicatorInteractiveDemo() {
const [active, setActive] = useState(1);

return (
<div style={{ margin: "2rem 0" }}>
<ProgressIndicator
aria-label="Policy creation steps"
currentIndex={active}
>
{STEPS.map((name, index) => (
<ProgressIndicatorStep
key={name}
name={name}
status={
index < active
? "success"
: index === active
? "editing"
: "neutral"
}
onClick={() => setActive(index)}
/>
))}
</ProgressIndicator>
</div>
);
}

/* Link example: each step navigates via an href. */
export function ProgressIndicatorLinkDemo() {
return (
<div style={{ margin: "2rem 0" }}>
<ProgressIndicator aria-label="Documentation progress" currentIndex={1}>
<ProgressIndicatorStep
name="Introduction"
status="success"
href="#introduction"
/>
<ProgressIndicatorStep
name="Installation"
status="editing"
href="#installation"
/>
<ProgressIndicatorStep name="Usage" status="neutral" href="#usage" />
</ProgressIndicator>
</div>
);
}
11 changes: 5 additions & 6 deletions packages/demo/src/components/ui/nav-list.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ interface Props {
const { label, list } = Astro.props;
const currentPath = Astro.url.pathname;

const isActive = (href: string) => {
if (href === "/") {
return currentPath === "/";
}
return currentPath.startsWith(href);
};
const stripTrailingSlash = (path: string) =>
path.length > 1 ? path.replace(/\/$/, "") : path;

const isActive = (href: string) =>
stripTrailingSlash(currentPath) === stripTrailingSlash(href);
---

<div class="space-y-3">
Expand Down
158 changes: 158 additions & 0 deletions packages/demo/src/content/components/progress-indicator.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
layout: "@demo/layouts/mdx-layout.astro"
heading: "Progress Indicator"
description: "Communicates a user's progress through a sequence of steps"
---

import {
ProgressIndicatorDemo,
ProgressIndicatorInteractiveDemo,
ProgressIndicatorLinkDemo,
} from "@demo/components/demo/progress-indicator";

## Overview

The Progress Indicator communicates a user's position within a multi-step process such as policy or indicator creation. It renders a sequence of steps along a track, with the current step highlighted and each step's state conveyed through a status icon.

It is composed of two components: `ProgressIndicator` (the parent that draws the track and arranges the steps) and `ProgressIndicatorStep` (each individual step). The parent takes a `currentIndex` and positions the track's filled segment over that step; each step derives its own active state by comparing its index to it.

Progress Indicator should _not_ be used with fewer than three steps.

## Usage

Import the components:

```ts
import { ProgressIndicator, ProgressIndicatorStep } from "@eqtylab/equality";
```

Basic usage with required properties:

```tsx
<ProgressIndicator aria-label="Account setup progress" currentIndex={1}>
<ProgressIndicatorStep name="Account" status="success" />
<ProgressIndicatorStep name="Verification" status="editing" />
<ProgressIndicatorStep name="Confirm" status="neutral" />
</ProgressIndicator>
```

## Variants

### Horizontal (default)

The default layout arranges steps in a row. The example below mixes every status so each icon is visible. The step at `currentIndex` (`Verification`) is marked with `aria-current="step"` and the track fills its segment.

<ProgressIndicatorDemo client:only="react" />

```tsx
<ProgressIndicator aria-label="Progress Indicator" currentIndex={2}>
<ProgressIndicatorStep
name="Success"
description="Completed"
status="success"
/>
<ProgressIndicatorStep
name="Danger"
description="Action required"
status="danger"
/>
<ProgressIndicatorStep
name="Editing"
description="In progress"
status="editing"
/>
<ProgressIndicatorStep name="Info" description="Optional" status="info" />
<ProgressIndicatorStep name="Neutral" status="neutral" />
<ProgressIndicatorStep name="Empty" status="empty" />
</ProgressIndicator>
```

### Vertical

Set `layout="vertical"` to stack the steps. The track and fill re-orient automatically.

<ProgressIndicatorDemo layout="vertical" client:only="react" />

```tsx
<ProgressIndicator
layout="vertical"
currentIndex={2}
aria-label="Progress Indicator"
>
<ProgressIndicatorStep
name="Success"
description="Completed"
status="success"
/>
{/* …remaining steps… */}
</ProgressIndicator>
```

### Interactive (onClick)

When a step is given an `onClick` handler it renders as a `<button>`, making it focusable and keyboard-operable.

<ProgressIndicatorInteractiveDemo client:only="react" />

```tsx
const [active, setActive] = useState(1);

<ProgressIndicator aria-label="Policy creation steps" currentIndex={active}>
{steps.map((name, index) => (
<ProgressIndicatorStep
key={name}
name={name}
status={
index < active ? "success" : index === active ? "editing" : "neutral"
}
onClick={() => setActive(index)}
/>
))}
</ProgressIndicator>;
```

### Link (href)

When a step is given an `href` it renders as an `<a>`.

<ProgressIndicatorLinkDemo client:only="react" />

```tsx
<ProgressIndicator aria-label="Documentation progress" currentIndex={1}>
<ProgressIndicatorStep
name="Introduction"
status="success"
href="#introduction"
/>
<ProgressIndicatorStep
name="Installation"
status="editing"
href="#installation"
/>
<ProgressIndicatorStep name="Usage" status="neutral" href="#usage" />
</ProgressIndicator>
```

If a step has neither `href` nor `onClick`, it renders as a non-interactive `<div>` that is not focusable.

## Props

### ProgressIndicator

| Name | Description | Type | Default | Required |
| -------------- | ---------------------------------------------- | ------------------------------------- | ------------ | -------- |
| `layout` | Orientation of the steps | `horizontal`, `vertical` | `horizontal` | ❌ |
| `currentIndex` | Array index of the current (active) step | `number` | 0 | ✅ |
| `elevation` | Elevation applied to each step's marker icon | `sunken`, `base`, `raised`, `overlay` | `raised` | ❌ |
| `aria-label` | Accessible label for the step list | `string` | `"Progress"` | ❌ |
| `children` | One or more `ProgressIndicatorStep` components | `ReactNode` | — | ✅ |

### ProgressIndicatorStep

| Name | Description | Type | Default | Required |
| ------------- | ------------------------------------- | ---------------------------------------------------------- | --------- | -------- |
| `name` | The step label | `string` | — | ✅ |
| `description` | Secondary text shown beneath the name | `string` | — | ❌ |
| `status` | Controls the step's icon and colour | `editing`, `empty`, `neutral`, `success`, `info`, `danger` | `neutral` | ❌ |
| `href` | Renders the step as an `<a>` | `string` | — | ❌ |
| `onClick` | Renders the step as a `<button>` | `(e: MouseEvent) => void` | — | ❌ |
1 change: 1 addition & 0 deletions packages/ui/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export * from './pagination/pagination';
export * from './panel-label/panel-label';
export * from './popover/popover';
export * from './progress/progress';
export * from './progress-indicator/progress-indicator';
export * from './radial-graph/radial-graph';
export * from './radio-dropdown/radio-dropdown';
export * from './radio-group/radio-group';
Expand Down
Loading
Loading