Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an indeterminate linear progress bar #3429

Merged
merged 45 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b093497
add indeterminate example
JoanaMMoreira May 14, 2024
e36d22d
add animations
JoanaMMoreira May 14, 2024
8cb818f
add default props
JoanaMMoreira May 15, 2024
648f16b
tweak animation
JoanaMMoreira May 15, 2024
d0a310d
add temp examples for design review
JoanaMMoreira May 15, 2024
9abf06f
add example
JoanaMMoreira May 16, 2024
4c1c29c
add ease in ease out example
JoanaMMoreira May 16, 2024
beb7c1b
amend keyframes
JoanaMMoreira May 17, 2024
dd048ec
Revert "add temp examples for design review"
JoanaMMoreira May 21, 2024
cc732df
change line width and animation speed
JoanaMMoreira May 21, 2024
e325b7f
remove aria value now when indeterminate
JoanaMMoreira May 22, 2024
cdc9f11
add QA story
JoanaMMoreira May 22, 2024
266f322
add changeset
JoanaMMoreira May 22, 2024
2272e10
update changeset
JoanaMMoreira May 23, 2024
ca31655
add example to site
JoanaMMoreira May 23, 2024
4ba1bfa
update changeset
JoanaMMoreira May 28, 2024
faec1ef
add separate site example
JoanaMMoreira May 28, 2024
ae83683
add indetermiante to determinate example
JoanaMMoreira May 30, 2024
7c72e69
add progressing value
JoanaMMoreira May 30, 2024
395f1ac
display label when indeterminate
JoanaMMoreira May 31, 2024
b21dc5f
add dialog example
JoanaMMoreira Jun 3, 2024
cc4216a
update cypress test
JoanaMMoreira Jun 3, 2024
ae0072b
remove animation on QA stories
JoanaMMoreira Jun 4, 2024
ce5ed8b
add space to linear progress label
JoanaMMoreira Jun 4, 2024
e5af792
add toast example to site
JoanaMMoreira Jun 10, 2024
34bf12f
add indeterminate bar width var
JoanaMMoreira Jun 19, 2024
69a9b1d
change animation to ease in out
JoanaMMoreira Jun 21, 2024
1f7bbd8
Remove site example change state
origami-z Jun 24, 2024
5c943a1
Remove variant prop, make it depends on `value`
origami-z Jun 24, 2024
3b8036a
Fix typecheck
origami-z Jun 24, 2024
516763e
Indeterminate with buffer value
origami-z Jun 28, 2024
526c94d
Fix indeterminate with buffer
origami-z Jun 28, 2024
66a3b40
Update progress site example
origami-z Jul 1, 2024
8f0b7c2
Remove `noAnimation`
origami-z Jul 1, 2024
855ee22
Update to use transform
origami-z Jul 3, 2024
d6c3c42
Update example and site
origami-z Jul 3, 2024
7d90748
Update changeset
origami-z Jul 3, 2024
ebf3b88
Update min/max site heading
origami-z Jul 3, 2024
0db72ed
Update buffer doc
origami-z Jul 3, 2024
0c22b4d
Update props jsdoc
origami-z Jul 3, 2024
d1f985a
Add back noAnimation css
origami-z Jul 3, 2024
9097191
Apply suggestions from code review
origami-z Jul 3, 2024
2474bc7
Update examples.mdx
navkaur76 Jul 3, 2024
607c7dc
Update .changeset/four-bugs-reply.md
origami-z Jul 5, 2024
2533ded
Prettier
origami-z Jul 8, 2024
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
9 changes: 9 additions & 0 deletions .changeset/four-bugs-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@salt-ds/core": minor
---

Updated `LinearProgress` to display a moving line to represent an unspecified wait time, when `value` is `undefined`.

`<LinearProgress />`
origami-z marked this conversation as resolved.
Show resolved Hide resolved

_Note_: `value` and `bufferValue` are no longer default to `0`. Previously above code would render a 0% progress bar, which was not a good reflection of intent. You can still achieve it by passing in `value={0}`.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { composeStories } from "@storybook/react";
import * as linearProgressStories from "@stories/progress/linear-progress.stories";

const composedStories = composeStories(linearProgressStories);
const { Default } = composedStories;
const { Default, Indeterminate } = composedStories;
describe("GIVEN a LinearProgress", () => {
it("SHOULD render progress bar with correct value with correct value and percentage", () => {
cy.mount(<Default value={50} />);
Expand All @@ -18,4 +18,18 @@ describe("GIVEN a LinearProgress", () => {
cy.findByRole("progressbar").contains("75 %");
cy.findByRole("progressbar").should("not.contain.text", "0"); // test regression #3202
});

it("SHOULD render progress bar with bufferValue provided", () => {
cy.mount(<Default bufferValue={50} />);
cy.findByRole("progressbar")
.get(".saltLinearProgress-buffer")
.should("exist");
});

it("SHOULD render indeterminate progress bar", () => {
cy.mount(<Indeterminate />);
cy.findByRole("progressbar").should("have.attr", "aria-valuemax", "100");
cy.findByRole("progressbar").should("have.attr", "aria-valuemin", "0");
cy.findByRole("progressbar").should("not.have.attr", "aria-valuenow");
});
});
23 changes: 23 additions & 0 deletions packages/core/src/progress/LinearProgress/LinearProgress.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,26 @@
white-space: nowrap;
padding-left: var(--salt-spacing-100);
}

.saltLinearProgress-indeterminate.saltLinearProgress-bar {
position: absolute;
left: 0px;
bottom: 0px;
top: 0px;
transform-origin: left center;
width: 66%;
animation: 1.8s ease-in-out infinite salt-indeterminate-progress-bar;
}

@keyframes salt-indeterminate-progress-bar {
0% {
transform: translateX(-100%);
}
60% {
/* 155% is slightly more than moving the bar off screen (with width of 66%) */
transform: translateX(155%);
}
100% {
transform: translateX(200%);
}
}
43 changes: 27 additions & 16 deletions packages/core/src/progress/LinearProgress/LinearProgress.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentPropsWithoutRef, CSSProperties, forwardRef } from "react";
import { ComponentPropsWithoutRef, forwardRef } from "react";
import { clsx } from "clsx";
import { makePrefixer } from "../../utils";
import { Text } from "../../text";
Expand All @@ -12,7 +12,8 @@ const withBaseName = makePrefixer("saltLinearProgress");
export interface LinearProgressProps extends ComponentPropsWithoutRef<"div"> {
/**
* The value of the buffer indicator.
* Value between 0 and max.
* Value between `min` and `max`.
* When no `value` and `bufferValue` is passed in, show as indeterminate state.
*/
bufferValue?: number;
/**
Expand All @@ -31,7 +32,8 @@ export interface LinearProgressProps extends ComponentPropsWithoutRef<"div"> {
min?: number;
/**
* The value of the progress indicator.
* Value between 0 and max.
* Value between `min` and `max`.
* When no `value` and `bufferValue` is passed in, show as indeterminate state.
*/
value?: number;
}
Expand All @@ -43,8 +45,8 @@ export const LinearProgress = forwardRef<HTMLDivElement, LinearProgressProps>(
hideLabel = false,
max = 100,
min = 0,
value = 0,
bufferValue = 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this considered a breaking change ?
Should we leave this as 0 and opt into indeterminate ?
At some point a breaking change release we could make this the default ?

value,
bufferValue,
joshwooding marked this conversation as resolved.
Show resolved Hide resolved
...rest
},
ref
Expand All @@ -56,13 +58,17 @@ export const LinearProgress = forwardRef<HTMLDivElement, LinearProgressProps>(
window: targetWindow,
});

const progress = ((value - min) / (max - min)) * 100;
const buffer = ((bufferValue - min) / (max - min)) * 100;
const barStyle: CSSProperties = {};
const bufferStyle: CSSProperties = {};

barStyle.width = `${progress}%`;
bufferStyle.width = `${buffer}%`;
const isIndeterminate = value === undefined && bufferValue === undefined;
const progress =
value === undefined ? 0 : ((value - min) / (max - min)) * 100;
const buffer =
bufferValue === undefined ? 0 : ((bufferValue - min) / (max - min)) * 100;
const barStyle = {
width: isIndeterminate ? undefined : `${progress}%`,
};
const bufferStyle = {
width: `${buffer}%`,
};

return (
<div
Expand All @@ -71,19 +77,24 @@ export const LinearProgress = forwardRef<HTMLDivElement, LinearProgressProps>(
role="progressbar"
aria-valuemax={max}
aria-valuemin={min}
aria-valuenow={Math.round(value)}
aria-valuenow={value === undefined ? undefined : Math.round(value)}
{...rest}
>
<div className={withBaseName("barContainer")}>
<div className={withBaseName("bar")} style={barStyle} />
{bufferValue > 0 ? (
<div
className={clsx(withBaseName("bar"), {
[withBaseName("indeterminate")]: isIndeterminate,
})}
style={barStyle}
/>
{bufferValue && bufferValue > 0 ? (
<div className={withBaseName("buffer")} style={bufferStyle} />
) : null}
<div className={withBaseName("track")} />
</div>
{!hideLabel && (
<Text styleAs="h2" className={withBaseName("progressLabel")}>
{`${Math.round(progress)} %`}
{isIndeterminate ? `— %` : `${Math.round(progress)} %`}
</Text>
)}
</div>
Expand Down
8 changes: 5 additions & 3 deletions packages/core/stories/progress/linear-progress.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Meta, StoryFn } from "@storybook/react";
import {
Button,
FlowLayout,
StackLayout,
CircularProgress,
FlowLayout,
LinearProgress,
StackLayout,
} from "@salt-ds/core";
import { Meta, StoryFn } from "@storybook/react";
import { useProgressingValue } from "./useProgressingValue";

import "./progress.stories.css";
Expand Down Expand Up @@ -91,3 +91,5 @@ export const ProgressingValue: StoryFn<typeof LinearProgress> = () => (
export const ProgressingBufferValue: StoryFn<typeof LinearProgress> = () => (
<ProgressBufferWithControls ProgressComponent={LinearProgress} />
);

export const Indeterminate = Default.bind({});
3 changes: 3 additions & 0 deletions packages/core/stories/progress/progress.qa.stories.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.noAnimation.saltLinearProgress .saltLinearProgress-indeterminate.saltLinearProgress-bar {
animation: none;
}
8 changes: 8 additions & 0 deletions packages/core/stories/progress/progress.qa.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Meta, StoryFn } from "@storybook/react";
import { CircularProgress, LinearProgress } from "@salt-ds/core";
import { QAContainer, QAContainerProps } from "docs/components";

import "./progress.qa.stories.css";

export default {
title: "Core/Progress/Progress QA",
component: CircularProgress,
Expand Down Expand Up @@ -35,6 +37,12 @@ export const ExamplesGrid: StoryFn<QAContainerProps> = (props) => {
hideLabel
/>
<CircularProgress aria-label="Download" value={38} hideLabel />
<LinearProgress
aria-label="Download"
// Chromatic doesn't work https://www.chromatic.com/docs/animations/#css-animations
className="noAnimation"
joshwooding marked this conversation as resolved.
Show resolved Hide resolved
style={{ padding: "50px" }}
/>
</QAContainer>
);
};
Expand Down
31 changes: 11 additions & 20 deletions site/docs/components/progress/examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,47 +26,38 @@ Use linear or circular depending on the context, layout and functionality of an

</LivePreview>

<LivePreview componentName="progress" exampleName="HiddenLabel" displayName="Hide label">
<LivePreview componentName="progress" exampleName="LinearIndeterminate" displayName="Indeterminate linear progress">

## Hide label
## Indeterminate linear progress

Use the `hideLabel` prop to display the progress without a label.
Use an indeterminate linear progress to indicate ongoing progress until a determinate value can be specified.

</LivePreview>

<LivePreview componentName="progress" exampleName="WithMaxVal" displayName="With maximum value">
<LivePreview componentName="progress" exampleName="WithMinVal" displayName="Min and max values">

## With maximum value
## Min and max values

Specify the maximum value of the progress indicator. The percentage progress will be calculated using this value. The default value is 100.
Specify the `min` and `max` values of the progress indicator. The default range is 0 - 100.

</LivePreview>
<LivePreview componentName="progress" exampleName="WithBuffer" displayName="With buffer">

<LivePreview componentName="progress" exampleName="WithMinVal" displayName="With minimum value">

## With minimum value
## With buffer

Specify the minimum value of the progress indicator. The percentage progress will be calculated using this value. The default value is 0.
Specify a buffer value to indicate loading state. The buffer is a pending value so will not affect the progress label.

</LivePreview>

<LivePreview componentName="progress" exampleName="WithProgVal" displayName="With progressing value">

## With progressing value

Dynamically represent a progressing value in the progress indicator.

</LivePreview>
<LivePreview componentName="progress" exampleName="WithBuffer" displayName="With buffer">

## With buffer

Specify a buffer value to indicate loading state. The buffer does not have a label and will not affect the progress label.

</LivePreview>
<LivePreview componentName="progress" exampleName="WithProgBufferVal" displayName="With progressing buffer value">
<LivePreview componentName="progress" exampleName="WithProgBufferVal" displayName="With progressing buffer">

## With progressing buffer value
## With progressing buffer

Dynamically represent a progressing buffer value in the progress indicator.

Expand Down
39 changes: 10 additions & 29 deletions site/src/examples/progress/HiddenLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,20 @@
import { ReactElement, useState } from "react";
import {
FlexItem,
FlexLayout,
FlowLayout,
RadioButton,
RadioButtonGroup,
CircularProgress,
FlexItem,
LinearProgress,
StackLayout,
} from "@salt-ds/core";
import { ReactElement } from "react";

export const HiddenLabel = (): ReactElement => {
const [selectedType, setSelectedType] = useState("circular");

return (
<FlexLayout direction="column" style={{ height: "100%" }}>
<FlowLayout justify="center" gap={1}>
<RadioButtonGroup
direction="horizontal"
value={selectedType}
aria-label="Progress type control"
onChange={(e) => setSelectedType(e.target.value)}
>
<RadioButton label="Circular" value="circular" />
<RadioButton label="Linear" value="linear" />
</RadioButtonGroup>
</FlowLayout>

<FlexItem style={{ margin: "auto" }}>
{selectedType === "circular" && (
<CircularProgress aria-label="Download" value={38} hideLabel />
)}
{selectedType === "linear" && (
<LinearProgress aria-label="Download" value={38} hideLabel />
)}
<StackLayout align="center">
<FlexItem>
<CircularProgress aria-label="Download" value={38} hideLabel />
</FlexItem>
<FlexItem>
<LinearProgress aria-label="Download" value={38} hideLabel />
</FlexItem>
</FlexLayout>
</StackLayout>
);
};
28 changes: 28 additions & 0 deletions site/src/examples/progress/LinearIndeterminate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
Button,
LinearProgress,
Text,
Toast,
ToastContent,
} from "@salt-ds/core";
import { CloseIcon } from "@salt-ds/icons";
import { ReactElement } from "react";

export const LinearIndeterminate = (): ReactElement => {
return (
<Toast status="info">
<ToastContent>
<div>
<Text>
<strong>File uploading</strong>
</Text>
<Text>File upload to shared drive in progress.</Text>
<LinearProgress aria-label="Download" />
</div>
</ToastContent>
<Button variant="secondary" aria-label="Dismiss">
<CloseIcon aria-hidden />
</Button>
</Toast>
);
};
38 changes: 10 additions & 28 deletions site/src/examples/progress/WithBuffer.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,23 @@
import { ReactElement, useState } from "react";
import {
CircularProgress,
FlexItem,
FlexLayout,
FlowLayout,
H3,
RadioButton,
RadioButtonGroup,
CircularProgress,
LinearProgress,
StackLayout,
} from "@salt-ds/core";
import { ReactElement } from "react";

export const WithBuffer = (): ReactElement => {
const [selectedType, setSelectedType] = useState("circular");

return (
<FlexLayout direction="column" align="center" style={{ height: "100%" }}>
<StackLayout align="center">
<H3> value = 38, buffer value = 60</H3>
<FlowLayout justify="center" gap={1}>
<RadioButtonGroup
direction="horizontal"
value={selectedType}
aria-label="Progress type control"
onChange={(e) => setSelectedType(e.target.value)}
>
<RadioButton label="Circular" value="circular" />
<RadioButton label="Linear" value="linear" />
</RadioButtonGroup>
</FlowLayout>

<FlexItem style={{ margin: "auto" }}>
{selectedType === "circular" && (
<CircularProgress aria-label="Download" value={38} bufferValue={60} />
)}
{selectedType === "linear" && (
<LinearProgress aria-label="Download" value={38} bufferValue={60} />
)}
<FlexItem>
<CircularProgress aria-label="Download" value={38} bufferValue={60} />
</FlexItem>
<FlexItem>
<LinearProgress aria-label="Download" value={38} bufferValue={60} />
</FlexItem>
</FlexLayout>
</StackLayout>
);
};
Loading
Loading