Skip to content

Commit

Permalink
Migrate ProgressBar from kaizen-legacy (#4254)
Browse files Browse the repository at this point in the history
* Migrate ProgressBar from @kaizen/legacy
* Update folder architecture
* Move Label into subcomponents
* Move calculatePercentage into util
* Update documentation, stories and add basic tests
  • Loading branch information
mcwinter07 authored Nov 2, 2023
1 parent b2cc312 commit 53c59af
Show file tree
Hide file tree
Showing 14 changed files with 484 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/stale-trains-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kaizen/components": minor
---

Migrate ProgressBar from kaizen-legacy
88 changes: 88 additions & 0 deletions packages/components/src/ProgressBar/ProgressBar.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
@import "~@kaizen/design-tokens/sass/color";
@import "~@kaizen/design-tokens/sass/spacing";

$height: 10px;

@mixin animation-background($color) {
background: linear-gradient(90deg, transparent, #{$color} 75%, transparent);
}

.subtext {
color: $color-purple-800;
padding-top: $spacing-6;
text-align: center;
opacity: 80%;
}

.progressBackground {
width: 100%;
background: $color-gray-300;
border-radius: $height;
height: $height;
overflow: hidden;
position: relative;
}

@keyframes pulse {
0% {
transform: translateX(-100%);
}

100% {
transform: translateX(200%);
}
}

.progress {
position: absolute;
inset: 0;
border-radius: $height;
overflow: hidden;
transition: transform 200ms ease;
}

.positive {
composes: progress;
background: $color-green-400;

&::after {
@include animation-background($color-green-300);
}
}

.informative {
composes: progress;
background: $color-blue-400;

&::after {
@include animation-background($color-blue-300);
}
}

.cautionary {
composes: progress;
background: $color-yellow-400;

&::after {
@include animation-background($color-yellow-300);
}
}

.negative {
composes: progress;
background: $color-red-400;
}

.isAnimating {
&::after {
opacity: 100%;
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 50%;
animation: pulse 2s infinite;
transition: opacity 0.2s;
}
}
25 changes: 25 additions & 0 deletions packages/components/src/ProgressBar/ProgressBar.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react"
import { render, screen } from "@testing-library/react"
import { ProgressBar } from "./ProgressBar"

describe("<ProgressBar />", () => {
it("has an accessible progress value expressed as a percentage", () => {
const expectedAccessiblePercent: string = "60"

render(
<ProgressBar
value={3}
max={5}
label=""
isReversed={false}
isAnimating={false}
mood="positive"
data-testid="id--progress-bar"
/>
)
expect(screen.getByTestId("id--progress-bar")).toHaveAttribute(
"aria-valuenow",
expectedAccessiblePercent
)
})
})
74 changes: 74 additions & 0 deletions packages/components/src/ProgressBar/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { HTMLAttributes } from "react"
import classnames from "classnames"
import { Heading } from "~components/Heading"
import { OverrideClassName } from "~types/OverrideClassName"
import { Label } from "./subcomponents/Label"
import { calculatePercentage } from "./utils/calculatePercentage"
import styles from "./ProgressBar.module.scss"

export type ProgressBarProps = {
/** A value that represents completed progress */
value: number
/** A value that sets the maximum progress that can be achieved */
max: number
/** Adds an animated state to indicate loading progress */
isAnimating: boolean
mood: Mood
subtext?: string
label?: string
isReversed: boolean
} & OverrideClassName<HTMLAttributes<HTMLDivElement>>

type Mood = "positive" | "informative" | "negative" | "cautionary"

/**
* {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3081896891/Progress+Bar Guidance} |
* {@link https://cultureamp.design/?path=/docs/components-progress-bar--docs Storybook}
*/
export const ProgressBar = ({
value,
max,
isAnimating,
mood,
subtext,
label,
classNameOverride,
isReversed = false,
...restProps
}: ProgressBarProps): JSX.Element => {
const percentage = calculatePercentage({ value, max })
return (
<div
role="progressbar"
aria-valuenow={percentage}
aria-valuemin={0}
aria-valuemax={100}
className={classNameOverride}
{...restProps}
>
{label && <Label content={label} isReversed={isReversed} />}
<div className={styles.progressBackground}>
<div
className={classnames(
styles[mood],
isAnimating && styles.isAnimating
)}
style={{ transform: `translateX(-${100 - percentage}%` }}
/>
</div>
{subtext && (
<div className={styles.subtext}>
<Heading
variant="heading-6"
tag="p"
color={isReversed ? "white" : "dark"}
>
{subtext}
</Heading>
</div>
)}
</div>
)
}

ProgressBar.displayName = "ProgressBar"
56 changes: 56 additions & 0 deletions packages/components/src/ProgressBar/_docs/ProgressBar.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Canvas, Controls, DocsStory, Meta } from "@storybook/blocks"
import { ResourceLinks, KaioNotification, Installation } from "~storybook/components"
import * as ProgressBarStickersheetStories from "./ProgressBar.stickersheet.stories"
import * as ProgressBarStories from "./ProgressBar.stories"

<Meta of={ProgressBarStories} />

# ProgressBar

<ResourceLinks
sourceCode="https://github.com/cultureamp/kaizen-design-system/tree/main/packages/components/src/ProgressBar"
figma="https://www.figma.com/file/eZKEE5kXbEMY3lx84oz8iN/%F0%9F%92%9C-UI-Kit%3A-Heart?type=design&node-id=1929%3A20889&mode=design&t=mSqG0g2n5Iquqx6m-1"
designGuidelines="https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3081896891/Progress+Bar"
className="!mb-8"
/>

<KaioNotification />

<Installation
installCommand="yarn add @kaizen/components"
importStatement='import { ProgressBar } from "@kaizen/components"'
/>

## Overview

Progress bars show continuous progress through a process, such as a percentage value. They show how much progress is complete and how much remains.

<Canvas of={ProgressBarStories.Playground} />
<Controls of={ProgressBarStories.Playground} />

## API

### isAnimating

Adds an animated state that indicates system processes, such as downloading, uploading, or processing.

<Canvas of={ProgressBarStories.IsAnimating} />

### Moods

<Canvas of={ProgressBarStickersheetStories.StickerSheetDefault} />

### Value and max

While `value` and `max` can be used to represent progress as either a percentage or fraction, screen readers will always announce this as a calculated percentage.

<Canvas of={ProgressBarStories.ValueAndMax} />

<DocsStory of={ProgressBarStories.Reversed} />

### Accessible context

Due to the optional `label` prop, the progress bar does not have an accessible name. You can provide context for assistive technologies with aria attributes such as `aria-label` or `aria-labelledby`.


<Canvas of={ProgressBarStories.AccessibleName} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from "react"
import { Meta } from "@storybook/react"
import {
StickerSheet,
StickerSheetStory,
} from "~storybook/components/StickerSheet"
import { ProgressBar } from "../index"

export default {
title: "Components/ProgressBar",
parameters: {
chromatic: { disable: false, pauseAnimationAtEnd: true },
controls: { disable: true },
a11y: {
config: {
rules: [
{
// `label` is an optional prop so this has no accessible by default. consumers can pass in `aria-labelledby` or `aria-label` which can provide an accessible description pending a refactor.
id: "aria-progressbar-name",
enabled: false,
},
],
},
},
},
} satisfies Meta

const StickerSheetTemplate: StickerSheetStory = {
render: ({ isReversed = false }) => (
<StickerSheet className="w-full" isReversed={isReversed}>
<StickerSheet.Body>
<StickerSheet.Row rowTitle="Positive" rowTitleWidth="100px">
<ProgressBar
value={25}
max={100}
mood="positive"
isAnimating={false}
label="25%"
subtext="Subtext"
isReversed={isReversed}
/>
</StickerSheet.Row>
<StickerSheet.Row rowTitle="Informative">
<ProgressBar
value={25}
max={100}
mood="informative"
isAnimating={false}
label="25%"
subtext="Subtext"
isReversed={isReversed}
/>
</StickerSheet.Row>
<StickerSheet.Row rowTitle="Negative">
<ProgressBar
value={25}
max={100}
mood="negative"
isAnimating={false}
label="25%"
subtext="Subtext"
isReversed={isReversed}
/>
</StickerSheet.Row>
<StickerSheet.Row rowTitle="Cautionary">
<ProgressBar
value={25}
max={100}
mood="cautionary"
isAnimating={false}
label="25%"
subtext="Subtext"
isReversed={isReversed}
/>
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
),
}

export const StickerSheetDefault: StickerSheetStory = {
...StickerSheetTemplate,
name: "Sticker Sheet (Default)",
}

export const StickerSheetReversed: StickerSheetStory = {
...StickerSheetTemplate,
name: "Sticker Sheet (Reversed)",
parameters: {
backgrounds: { default: "Purple 700" },
},
args: { isReversed: true },
}

export const StickerSheetRTL: StickerSheetStory = {
...StickerSheetTemplate,
name: "Sticker Sheet (RTL)",
parameters: {
textDirection: "rtl",
},
}
Loading

0 comments on commit 53c59af

Please sign in to comment.