Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64536563a8 | |||
| 0dbe5a00d6 | |||
| 970f3a818b | |||
| abe3ef8cdb |
@@ -0,0 +1,80 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { useEffect, useState } from "react";
|
||||
import LinearProgress from "./LinearProgress";
|
||||
|
||||
const meta: Meta<typeof LinearProgress> = {
|
||||
title: "Components/LinearProgress",
|
||||
component: LinearProgress,
|
||||
args: {
|
||||
variant: "determinate",
|
||||
value: 40,
|
||||
},
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: "inline-radio",
|
||||
options: ["determinate", "indeterminate"],
|
||||
},
|
||||
value: {
|
||||
control: { type: "range", min: 0, max: 100, step: 1 },
|
||||
if: { arg: "variant", eq: "determinate" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof LinearProgress>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Indeterminate: Story = {
|
||||
args: {
|
||||
variant: "indeterminate",
|
||||
value: 0,
|
||||
},
|
||||
parameters: {
|
||||
chromatic: { disable: true },
|
||||
},
|
||||
};
|
||||
|
||||
export const Determinate: Story = {
|
||||
args: {
|
||||
variant: "determinate",
|
||||
value: 62,
|
||||
},
|
||||
};
|
||||
|
||||
export const DeterminateSamples: Story = {
|
||||
render: () => (
|
||||
<div className="flex w-full max-w-md flex-col gap-4">
|
||||
{([0, 25, 50, 75, 100] as const).map((value) => (
|
||||
<div key={value} className="flex flex-col gap-1">
|
||||
<span className="text-content-secondary text-xs">{value}%</span>
|
||||
<LinearProgress variant="determinate" value={value} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const ControlledDeterminate: Story = {
|
||||
render: function ControlledDeterminateRender() {
|
||||
const [value, setValue] = useState(0);
|
||||
useEffect(() => {
|
||||
const id = window.setInterval(() => {
|
||||
setValue((previous) => (previous >= 100 ? 0 : previous + 2));
|
||||
}, 120);
|
||||
return () => window.clearInterval(id);
|
||||
}, []);
|
||||
return (
|
||||
<div className="flex w-full max-w-md flex-col gap-2">
|
||||
<span className="text-content-secondary text-xs tabular-nums">
|
||||
{value}%
|
||||
</span>
|
||||
<LinearProgress variant="determinate" value={value} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
parameters: {
|
||||
chromatic: { disable: true },
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import type React from "react";
|
||||
import type { FC } from "react";
|
||||
import { cn } from "#/utils/cn";
|
||||
|
||||
type LinearProgressProps = React.ComponentProps<"div"> & {
|
||||
value: number;
|
||||
variant: "determinate" | "indeterminate";
|
||||
};
|
||||
|
||||
const LinearProgress: FC<LinearProgressProps> = ({
|
||||
value,
|
||||
className,
|
||||
variant,
|
||||
...props
|
||||
}) => {
|
||||
const isDeterminate = variant === "determinate";
|
||||
|
||||
return (
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
{...(isDeterminate ? { "aria-valuenow": Math.round(value) } : {})}
|
||||
className={cn(
|
||||
"w-full h-1 bg-surface-sky rounded-full relative",
|
||||
"overflow-hidden block",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{!isDeterminate ? (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-y-0 w-auto origin-left rounded-full bg-highlight-sky",
|
||||
"animate-bar-indeterminate",
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-y-0 w-auto origin-left rounded-full bg-highlight-sky",
|
||||
"animate-bar-indeterminate-2",
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className="h-full rounded-full bg-highlight-sky"
|
||||
style={{ width: `${value}%` }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LinearProgress;
|
||||
@@ -1,6 +1,3 @@
|
||||
import { css } from "@emotion/css";
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
import dayjs, { type Dayjs } from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import capitalize from "lodash/capitalize";
|
||||
@@ -10,6 +7,7 @@ import type {
|
||||
TransitionStats,
|
||||
Workspace,
|
||||
} from "#/api/typesGenerated";
|
||||
import LinearProgress from "#/components/LinearProgress/LinearProgress";
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
@@ -124,10 +122,13 @@ export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div css={styles.stack}>
|
||||
<div className="px-0.5">
|
||||
{variant === "task" && (
|
||||
<div className="mb-1 text-center">
|
||||
<div css={styles.label} data-chromatic="ignore">
|
||||
<div
|
||||
className="block text-xs font-semibold text-content-secondary"
|
||||
data-chromatic="ignore"
|
||||
>
|
||||
{progressText}
|
||||
</div>
|
||||
</div>
|
||||
@@ -144,22 +145,16 @@ export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
|
||||
? "determinate"
|
||||
: "indeterminate"
|
||||
}
|
||||
classes={{
|
||||
// If a transition is set, there is a moment on new load where the bar
|
||||
// accelerates to progressValue and then rapidly decelerates, which is
|
||||
// not indicative of true progress.
|
||||
bar: classNames.bar,
|
||||
// With the "task" variant, the progress bar is fullscreen, so remove
|
||||
// the border radius.
|
||||
root: variant === "task" ? classNames.root : undefined,
|
||||
}}
|
||||
/>
|
||||
{variant !== "task" && (
|
||||
<div className="flex mt-1 justify-between">
|
||||
<div css={styles.label}>
|
||||
<div className="flex mt-2.5 justify-between">
|
||||
<div className="block text-xs font-semibold text-content-secondary">
|
||||
{capitalize(workspace.latest_build.status)} workspace...
|
||||
</div>
|
||||
<div css={styles.label} data-chromatic="ignore">
|
||||
<div
|
||||
className="block text-xs font-semibold text-content-secondary"
|
||||
data-chromatic="ignore"
|
||||
>
|
||||
{progressText}
|
||||
</div>
|
||||
</div>
|
||||
@@ -167,25 +162,3 @@ export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const classNames = {
|
||||
bar: css`
|
||||
transition: none;
|
||||
`,
|
||||
root: css`
|
||||
border-radius: 0;
|
||||
`,
|
||||
};
|
||||
|
||||
const styles = {
|
||||
stack: {
|
||||
paddingLeft: 2,
|
||||
paddingRight: 2,
|
||||
},
|
||||
label: (theme) => ({
|
||||
fontSize: 12,
|
||||
display: "block",
|
||||
fontWeight: 600,
|
||||
color: theme.palette.text.secondary,
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -124,12 +124,46 @@ module.exports = {
|
||||
"30%": { left: "0%", width: "40%" },
|
||||
"100%": { left: "100%", width: "0%" },
|
||||
},
|
||||
// Matches MUI LinearProgress bar1/bar2 indeterminate keyframes; two
|
||||
// staggered bars are required so one is visible while the other resets.
|
||||
"bar-indeterminate": {
|
||||
"0%": {
|
||||
left: "-35%",
|
||||
right: "100%",
|
||||
},
|
||||
"60%": {
|
||||
left: "100%",
|
||||
right: "-90%",
|
||||
},
|
||||
"100%": {
|
||||
left: "100%",
|
||||
right: "-90%",
|
||||
},
|
||||
},
|
||||
"bar-indeterminate-2": {
|
||||
"0%": {
|
||||
left: "-200%",
|
||||
right: "100%",
|
||||
},
|
||||
"60%": {
|
||||
left: "107%",
|
||||
right: "-8%",
|
||||
},
|
||||
"100%": {
|
||||
left: "107%",
|
||||
right: "-8%",
|
||||
},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
loading: "loading 2s ease-in-out infinite alternate",
|
||||
"caret-scan": "caret-scan 3s ease-in-out infinite",
|
||||
"spin-once": "spin 1s cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
"zip-right": "zip-right 1s cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
"bar-indeterminate":
|
||||
"bar-indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite",
|
||||
"bar-indeterminate-2":
|
||||
"bar-indeterminate-2 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -182,7 +182,6 @@ export default defineConfig({
|
||||
"@mui/material/FormLabel",
|
||||
"@mui/material/InputAdornment",
|
||||
"@mui/material/InputBase",
|
||||
"@mui/material/LinearProgress",
|
||||
"@mui/material/Link",
|
||||
"@mui/material/List",
|
||||
"@mui/material/ListItem",
|
||||
|
||||
Reference in New Issue
Block a user