diff --git a/.changeset/early-frogs-applaud.md b/.changeset/early-frogs-applaud.md new file mode 100644 index 0000000000..23ad016069 --- /dev/null +++ b/.changeset/early-frogs-applaud.md @@ -0,0 +1,14 @@ +--- +"@twilio-paste/progress-bar": minor +"@twilio-paste/core": minor +--- + +[ProgressBar] Added new features and updated the design. + +The ProgressBarLabel component has the following new props: +- `valueLabel` which displays a plain text value on the right side of the label. +- `disabled` which applies disabled styling on the label. + +The ProgressBar component has updated styling and the following new props: +- `hasError` which displays error styling. +- `disabled` which displays disabled styling. diff --git a/packages/paste-core/components/progress-bar/package.json b/packages/paste-core/components/progress-bar/package.json index 1840f1cd55..266cabff57 100644 --- a/packages/paste-core/components/progress-bar/package.json +++ b/packages/paste-core/components/progress-bar/package.json @@ -14,9 +14,7 @@ "publishConfig": { "access": "public" }, - "files": [ - "dist" - ], + "files": ["dist"], "scripts": { "build": "yarn clean && NODE_ENV=production node build.js && tsc", "build:js": "NODE_ENV=development node build.js", @@ -34,6 +32,7 @@ "@twilio-paste/media-object": "^10.0.0", "@twilio-paste/react-spectrum-library": "^2.0.0", "@twilio-paste/screen-reader-only": "^13.0.0", + "@twilio-paste/skeleton-loader": "^6.1.0", "@twilio-paste/style-props": "^9.0.0", "@twilio-paste/styling-library": "^3.0.0", "@twilio-paste/text": "^10.0.0", @@ -56,6 +55,7 @@ "@twilio-paste/media-object": "^10.0.0", "@twilio-paste/react-spectrum-library": "^2.0.0", "@twilio-paste/screen-reader-only": "^13.0.0", + "@twilio-paste/skeleton-loader": "^6.1.0", "@twilio-paste/style-props": "^9.0.0", "@twilio-paste/styling-library": "^3.0.0", "@twilio-paste/text": "^10.0.0", diff --git a/packages/paste-core/components/progress-bar/src/ProgressBar.tsx b/packages/paste-core/components/progress-bar/src/ProgressBar.tsx index c3185f05e5..c9b5b3af12 100644 --- a/packages/paste-core/components/progress-bar/src/ProgressBar.tsx +++ b/packages/paste-core/components/progress-bar/src/ProgressBar.tsx @@ -1,21 +1,13 @@ import { animated, useSpring } from "@twilio-paste/animation-library"; import { Box, type BoxProps } from "@twilio-paste/box"; import { useProgressBar } from "@twilio-paste/react-spectrum-library"; -import { keyframes } from "@twilio-paste/styling-library"; +import { SkeletonLoader } from "@twilio-paste/skeleton-loader"; import type { HTMLPasteProps } from "@twilio-paste/types"; import * as React from "react"; import { LABEL_SUFFIX } from "./constants"; const AnimatedBox = animated(Box); -const IndeterminateKeyframes = keyframes` - from { - left: -10%; - } - to { - left: 105%; - } -`; export interface ProgressBarProps extends HTMLPasteProps<"progress"> { element?: BoxProps["element"]; @@ -24,6 +16,9 @@ export interface ProgressBarProps extends HTMLPasteProps<"progress"> { "aria-describedby"?: string; "aria-labelledby"?: string; value?: number; + /** + * Screen reader only: used to describe the current value of the progress bar in plain text. + */ valueLabel?: string; /** * Minimum value of the progress bar is always set to 0 per the spec. @@ -31,6 +26,8 @@ export interface ProgressBarProps extends HTMLPasteProps<"progress"> { minValue?: never; maxValue?: number; isIndeterminate?: boolean; + hasError?: boolean; + disabled?: boolean; /** * Used to adjust how the numbers are rendered and interpreted. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat \ @@ -39,7 +36,15 @@ export interface ProgressBarProps extends HTMLPasteProps<"progress"> { } export const ProgressBar = React.forwardRef((props, ref) => { - const { element = "PROGRESS_BAR", id, value = 0, maxValue = 100, isIndeterminate = false } = props; + const { + element = "PROGRESS_BAR", + id, + value = 0, + maxValue = 100, + disabled = false, + hasError = false, + isIndeterminate = false, + } = props; /* * Since ProgressBar isn't a form element, we cannot use htmlFor from the regular label * so we create a ProgressBarLabel component that behaves like a regular form Label @@ -57,19 +62,23 @@ export const ProgressBar = React.forwardRef((p }); const springConfig = React.useMemo(() => { - if (!isIndeterminate) { - const clampedValue = Math.min(Math.max(value, 0), maxValue); - const percentage = Math.round((clampedValue / maxValue) * 100); - return { width: `${percentage}%`, config: { tension: 280, friction: 60 } }; + if (isIndeterminate) { + return {}; } - - return { - width: "15%", - }; + const clampedValue = Math.min(Math.max(value, 0), maxValue); + const percentage = Math.round((clampedValue / maxValue) * 100); + return { width: `${percentage}%`, config: { tension: 280, friction: 60 } }; }, [isIndeterminate, value, maxValue]); const style = useSpring(springConfig); + let barColor: BoxProps["backgroundColor"] = "colorBackgroundPrimary"; + if (hasError) { + barColor = "colorBackgroundError"; + } else if (disabled) { + barColor = "colorBackgroundStronger"; + } + return ( ((p element={element} id={id} aria-labelledby={labelledBy} - height="20px" + height="8px" width="100%" - backgroundColor="colorBackgroundWeak" + backgroundColor={disabled ? "colorBackground" : "colorBackgroundStrong"} borderRadius="borderRadiusPill" position="relative" overflow="hidden" > - + {isIndeterminate ? ( + + ) : ( + + )} ); }); diff --git a/packages/paste-core/components/progress-bar/src/ProgressBarLabel.tsx b/packages/paste-core/components/progress-bar/src/ProgressBarLabel.tsx index 268c5ac788..621bfaffba 100644 --- a/packages/paste-core/components/progress-bar/src/ProgressBarLabel.tsx +++ b/packages/paste-core/components/progress-bar/src/ProgressBarLabel.tsx @@ -1,5 +1,6 @@ -import { type BoxProps } from "@twilio-paste/box"; -import { Label } from "@twilio-paste/label"; +import { Box, type BoxProps } from "@twilio-paste/box"; +import { Label, type LabelProps } from "@twilio-paste/label"; +import { Text } from "@twilio-paste/text"; import type { HTMLPasteProps } from "@twilio-paste/types"; import * as React from "react"; @@ -9,14 +10,48 @@ export interface ProgressBarLabelProps extends HTMLPasteProps<"div"> { element?: BoxProps["element"]; children: string; htmlFor: string; + /** + * Displays value text on the right side of the label. + */ + valueLabel?: string; + disabled?: LabelProps["disabled"]; } export const ProgressBarLabel = React.forwardRef( - ({ element = "PROGRESS_BAR_LABEL", children, htmlFor, ...labelProps }, ref) => { + ({ element = "PROGRESS_BAR_LABEL", children, htmlFor, valueLabel, disabled, ...labelProps }, ref) => { return ( - + + + {valueLabel && ( + + )} + ); }, ); diff --git a/packages/paste-core/components/progress-bar/stories/index.stories.tsx b/packages/paste-core/components/progress-bar/stories/index.stories.tsx index b2d22f16e5..958d6a81fe 100644 --- a/packages/paste-core/components/progress-bar/stories/index.stories.tsx +++ b/packages/paste-core/components/progress-bar/stories/index.stories.tsx @@ -1,8 +1,8 @@ +import { Anchor } from "@twilio-paste/anchor"; import { Box } from "@twilio-paste/box"; import { Button } from "@twilio-paste/button"; import { Form, FormControl } from "@twilio-paste/form"; import { HelpText } from "@twilio-paste/help-text"; -import { Label } from "@twilio-paste/label"; import { useUID } from "@twilio-paste/uid-library"; import * as React from "react"; @@ -18,7 +18,7 @@ const NumberFormatter = new Intl.NumberFormat("en-US"); export const Default = (): React.ReactNode => { const progressBarId = useUID(); const helpTextId = useUID(); - const [value, setValue] = React.useState(0); + const [value, setValue] = React.useState(5); const [rerun, setRerun] = React.useState(1); // Randomly updates the value of the progress bar to simulate a real progress bar @@ -31,7 +31,16 @@ export const Default = (): React.ReactNode => { setRerun(0); return 100; } - return previousValue + Math.random() * 12; + const newValue = previousValue + Math.random() * 12; + + /* + * intentionally causes another loop for UX so that the + * submit button is not enabled until the progress fills cleanly + */ + if (newValue > 100) { + return 100; + } + return newValue; }); }, 600); } @@ -42,8 +51,15 @@ export const Default = (): React.ReactNode => {
- Downloading more ram - + + Downloading more ram + + Increasing your ram helps your computer run faster. {NumberFormatter.format(value)}% complete. @@ -80,6 +96,50 @@ export const Indeterminate = (): React.ReactNode => { ); }; +export const ErrorStory = (): React.ReactNode => { + const progressBarId = useUID(); + const helpTextId = useUID(); + + return ( + + + + + mtn_sunrise.png + + + + Upload failed. Retry upload + + + + + ); +}; +ErrorStory.displayName = "Error"; + +export const DisabledStory = (): React.ReactNode => { + const progressBarId = useUID(); + const helpTextId = useUID(); + + return ( + +
+ + + Campaign registration + + + + Your profile is in review. You will receive an email about your application status in 3-5 business days. + + +
+
+ ); +}; +DisabledStory.displayName = "Disabled"; + export const LabelingApproaches = (): React.ReactNode => { const labelId = useUID(); @@ -105,7 +165,9 @@ export const CustomRange = (): React.ReactNode => {
- Friends coming to your birthday + + Friends coming to your birthday +
@@ -120,7 +182,9 @@ export const CustomValueText = (): React.ReactNode => {
- Friends coming to your birthday + + Friends coming to your birthday +