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
145 changes: 145 additions & 0 deletions packages/demo/src/components/demo/bar-graph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { BarGraph, BarGraphSegment } from "@eqtylab/equality";

export function BarGraphDemo() {
return (
<div style={{ margin: "1rem 0 2rem" }}>
<BarGraph>
<BarGraphSegment
value={12}
color="var(--color-brand-green)"
label="Done"
tooltip="12 tasks done"
/>
<BarGraphSegment
value={3}
color="var(--color-brand-yellow)"
label="In progress"
tooltip="3 tasks in progress"
/>
<BarGraphSegment
value={28}
color="var(--color-greyscale-600)"
label="Remaining"
tooltip="28 tasks remaining"
/>
</BarGraph>
</div>
);
}

const controlsSegments = () => [
<BarGraphSegment
key="success"
value={12}
color="var(--color-brand-green)"
label="Success"
tooltip="12 controls succeeded"
/>,
<BarGraphSegment
key="pending"
value={190}
color="var(--color-brand-yellow)"
label="Pending"
tooltip="190 controls pending"
/>,
<BarGraphSegment
key="failure"
value={1}
color="var(--color-brand-red)"
label="Failure"
tooltip="1 control failed"
/>,
];

export function BarGraphSizeSmDemo() {
return (
<div style={{ margin: "1rem 0 2rem" }}>
<BarGraph size="sm">{controlsSegments()}</BarGraph>
</div>
);
}

export function BarGraphSizeMdDemo() {
return (
<div style={{ margin: "1rem 0 2rem" }}>
<BarGraph size="md">{controlsSegments()}</BarGraph>
</div>
);
}

export function BarGraphSizeLgDemo() {
return (
<div style={{ margin: "1rem 0 2rem" }}>
<BarGraph size="lg">{controlsSegments()}</BarGraph>
</div>
);
}

export function BarGraphWithLabelsDemo() {
return (
<div style={{ margin: "1rem 0 2rem" }}>
<BarGraph showLabels>
<BarGraphSegment
value={12}
color="var(--color-brand-green)"
label="Done"
tooltip="12 tasks done"
/>
<BarGraphSegment
value={3}
color="var(--color-brand-yellow)"
label="In progress"
tooltip="3 tasks in progress"
/>
<BarGraphSegment
value={28}
color="var(--color-greyscale-600)"
label="Remaining"
tooltip="28 tasks remaining"
/>
</BarGraph>
</div>
);
}

export function BarGraphRichTooltipDemo() {
return (
<div style={{ margin: "1rem 0 2rem" }}>
<BarGraph showLabels>
<BarGraphSegment
value={10}
color="var(--color-brand-green)"
label="10"
tooltip={
<div>
<strong>Success</strong>
<p>10 controls</p>
</div>
}
/>
<BarGraphSegment
value={290}
color="var(--color-brand-yellow)"
label="290"
tooltip={
<div>
<strong>Pending</strong>
<p>290 controls</p>
</div>
}
/>
<BarGraphSegment
value={8}
color="var(--color-brand-red)"
label="8"
tooltip={
<div>
<strong>Failure</strong>
<p>8 controls</p>
</div>
}
/>
</BarGraph>
</div>
);
}
114 changes: 114 additions & 0 deletions packages/demo/src/content/components/bar-graph.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
layout: "@demo/layouts/mdx-layout.astro"
heading: "Bar Graph"
description: "Single line segmented bar graph with segment tooltips"
---

import {
BarGraphDemo,
BarGraphSizeSmDemo,
BarGraphSizeMdDemo,
BarGraphSizeLgDemo,
BarGraphWithLabelsDemo,
BarGraphRichTooltipDemo,
} from "@demo/components/demo/bar-graph";

## Overview

`BarGraph` shows a single horizontal bar split into colored segments. Use it to break a total down into its parts, such as controls status.

Add a `BarGraphSegment` for each part. Widths come from each segment's `value` as a share of the total (`value / sum of all values`), so you pass raw numbers rather than percentages. Children that aren't a `BarGraphSegment` are ignored.

Segments are focusable and show a [Tooltip](tooltip) on hover and focus, so they work by keyboard alone. Each segment is exposed individually to screen readers, with its `tooltip` as the accessible name — there's no single label for the bar as a whole, so screen reader users read it one segment at a time. Write each `tooltip` to describe its segment on its own.

## Usage

Import the component:

```tsx
import { BarGraph, BarGraphSegment } from "@eqtylab/equality";
```

Basic usage with required properties:

```tsx
<BarGraph>
<BarGraphSegment
value={12}
color="var(--color-brand-green)"
label="Done"
tooltip="12 tasks done"
/>
<BarGraphSegment
value={3}
color="var(--color-brand-yellow)"
label="In progress"
tooltip="3 tasks in progress"
/>
<BarGraphSegment
value={28}
color="var(--color-greyscale-600)"
label="Remaining"
tooltip="28 tasks remaining"
/>
</BarGraph>
```

## Variants

### Default

There's no visible legend by default — each segment's accessible name comes from its tooltip. Hover or focus a segment to reveal it.

<BarGraphDemo client:only="react" />

### Sizes

Use `size` to control the bar's height. `sm` is a thin pill; the default `md` and `lg` are bolder and use rounded rectangles instead of a full pill. Note the small `Failure` segment, which is floored at 2px so it stays visible and focusable at every size.

#### Small

<BarGraphSizeSmDemo client:only="react" />

#### Medium (default)

<BarGraphSizeMdDemo client:only="react" />

#### Large

<BarGraphSizeLgDemo client:only="react" />

```tsx
<BarGraph size="lg">{/* segments */}</BarGraph>
```

### With visible labels

Set `showLabels` to render a legend beneath the bar. Note that this legend will not be read aloud by screen readers, content inside it should also be exposed in the section tooltip.

<BarGraphWithLabelsDemo client:only="react" />

### Rich tooltip content

The `tooltip` prop accepts a string or any `ReactNode`, so segments can show formatted content.

<BarGraphRichTooltipDemo client:only="react" />

## Props

### `BarGraph`

| Name | Description | Type | Default | Required |
| ------------ | ------------------------------------------------------------------------ | ----------------- | ------- | -------- |
| `size` | Bar height. `md` and `lg` use rounded rectangles instead of a full pill. | `sm`, `md`, `lg` | `md` | ❌ |
| `showLabels` | Render a visible legend (color swatch + label) beneath the bar. | `boolean` | `false` | ❌ |
| `children` | One or more segments to render. | `BarGraphSegment` | — | ✅ |

### `BarGraphSegment`

| Name | Description | Type | Default | Required |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- | ------- | -------- |
| `value` | Sets the segment's width as a share of the total (`value / sum of all values`). Every segment is floored at 2px so small and zero values stay visible. | `number` | — | ✅ |
| `color` | Any valid CSS color (e.g. a token like `var(--color-brand-green)` or `#10b981`). | `string` | — | ✅ |
| `label` | Visible legend label, shown when `showLabels` is set. Not announced to screen readers. | `string` | — | ✅ |
| `tooltip` | Tooltip content shown on hover and keyboard focus; also the segment's accessible name, so it must describe the segment on its own. | `string \| ReactNode` | — | ✅ |
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@eqtylab/equality",
"description": "EQTYLab's component and token-based design system",
"homepage": "https://equality.eqtylab.io/",
"version": "1.8.2",
"version": "1.9.0",
"license": "Apache-2.0",
"keywords": [
"component library",
Expand Down
52 changes: 52 additions & 0 deletions packages/ui/src/components/bar-graph/bar-graph.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@reference '../../theme/theme.module.css';

.bar-graph {
@apply flex w-full flex-col gap-2;
}

.bar {
@apply bg-greyscale-800;
@apply flex w-full gap-0.5 overflow-hidden;
}

/* Size Variants */

.size--sm {
@apply h-2 rounded-full;
}

.size--md {
@apply h-5 rounded-sm;
}

.size--lg {
@apply h-8 rounded-sm;
}

.segment {
@apply h-full;
flex: 1 1 0%;
@apply outline-none;
@apply focus-visible:ring-focus-ring focus-visible:ring-2 focus-visible:ring-inset;
}

/* Screen-reader-only text that backs each segment's accessible name. */
.visually-hidden {
@apply sr-only;
}

.legend {
@apply m-0 flex list-none flex-wrap gap-x-4 gap-y-1 p-0;
}

.legend-item {
@apply flex items-center gap-1.5;
}

.legend-swatch {
@apply size-2.5 shrink-0 rounded-full;
}

.legend-label {
@apply text-text-primary text-xs;
}
Loading
Loading