From d35e519d2d9156df6bcef453a413ce6dd40ea93a Mon Sep 17 00:00:00 2001 From: Navin Date: Thu, 3 Sep 2020 13:06:49 +0530 Subject: [PATCH] =?UTF-8?q?feat(progress):=20=E2=9C=A8=20=20add=20circular?= =?UTF-8?q?=20progress=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/progress/Progress.ts | 31 +++- src/progress/ProgressBar.ts | 65 ------- src/progress/ProgressState.ts | 9 - src/progress/__keys.ts | 3 +- src/progress/index.ts | 3 +- .../stories/CircularProgress.stories.tsx | 161 ++++++++++++++++++ ...stories.tsx => LinearProgress.stories.tsx} | 73 ++++---- 7 files changed, 229 insertions(+), 116 deletions(-) delete mode 100644 src/progress/ProgressBar.ts create mode 100644 src/progress/stories/CircularProgress.stories.tsx rename src/progress/stories/{Progress.stories.tsx => LinearProgress.stories.tsx} (78%) diff --git a/src/progress/Progress.ts b/src/progress/Progress.ts index a76f58ce1..baed8a739 100644 --- a/src/progress/Progress.ts +++ b/src/progress/Progress.ts @@ -4,27 +4,48 @@ * We improved the Progress Component [Progress](https://github.com/chakra-ui/chakra-ui/tree/develop/packages/progress) * to work with Reakit System */ +import { isFunction } from "@chakra-ui/utils"; import { BoxHTMLProps, BoxOptions, useBox } from "reakit"; -import { createComponent, createHook } from "reakit-system"; +import { createHook, createComponent } from "reakit-system"; import { PROGRESS_KEYS } from "./__keys"; import { ProgressStateReturn } from "./ProgressState"; -export type ProgressOptions = BoxOptions & ProgressStateReturn; +export type ProgressOptions = BoxOptions & + ProgressStateReturn & { + /** + * Function that returns the `aria-valuetext` for screen readers. + * It's mostly used to generate a more human-readable + * representation of the value for assistive technologies + */ + getAriaValueText?(value: number, percent: number): string; + }; export type ProgressHTMLProps = BoxHTMLProps; export type ProgressProps = ProgressOptions & ProgressHTMLProps; -export const useProgress = createHook({ +const useProgress = createHook({ name: "Progress", compose: useBox, keys: PROGRESS_KEYS, - useProps(options, { style: htmlStyle, ...htmlProps }) { + useProps(options, { "aria-valuetext": ariaValueText, ...htmlProps }) { + const { isIndeterminate, value, max, min, percent } = options; + return { ...htmlProps, - style: { ...htmlStyle, overflow: "hidden", position: "relative" }, + role: "progressbar", + "data-indeterminate": isIndeterminate ? "" : undefined, + "aria-valuemax": max, + "aria-valuemin": min, + "aria-valuenow": isIndeterminate ? undefined : value, + "aria-valuetext": + value == null || percent == null + ? undefined + : isFunction(options.getAriaValueText) + ? options.getAriaValueText(value, percent) + : ariaValueText, }; }, }); diff --git a/src/progress/ProgressBar.ts b/src/progress/ProgressBar.ts deleted file mode 100644 index d5c345b02..000000000 --- a/src/progress/ProgressBar.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * All credit goes to [Segun Adebayo](https://github.com/segunadebayo) for - * creating an Awesome Library [Chakra UI](https://github.com/chakra-ui/chakra-ui/) - * We improved the Progress Component [Progress](https://github.com/chakra-ui/chakra-ui/tree/develop/packages/progress) - * to work with Reakit System - */ -import { isFunction } from "@chakra-ui/utils"; -import { BoxHTMLProps, BoxOptions, useBox } from "reakit"; -import { createHook, createComponent } from "reakit-system"; - -import { PROGRESS_BAR_KEYS } from "./__keys"; -import { ProgressStateReturn } from "./ProgressState"; - -export type ProgressBarOptions = BoxOptions & - ProgressStateReturn & { - /** - * Function that returns the `aria-valuetext` for screen readers. - * It's mostly used to generate a more human-readable - * representation of the value for assistive technologies - */ - getAriaValueText?(value: number, percent: number): string; - }; - -export type ProgressBarHTMLProps = BoxHTMLProps; - -export type ProgressBarProps = ProgressBarOptions & ProgressBarHTMLProps; - -const useProgressBar = createHook({ - name: "ProgressBar", - compose: useBox, - keys: PROGRESS_BAR_KEYS, - - useProps( - options, - { style: htmlStyle, "aria-valuetext": ariaValueText, ...htmlProps }, - ) { - const { isIndeterminate, value, max, min, percent } = options; - - return { - ...htmlProps, - style: { - ...htmlStyle, - width: percent != null ? `${percent}%` : undefined, - height: "100%", - }, - role: "progressbar", - "data-indeterminate": isIndeterminate ? "" : undefined, - "aria-valuemax": max, - "aria-valuemin": min, - "aria-valuenow": isIndeterminate ? undefined : value, - "aria-valuetext": - value == null || percent == null - ? undefined - : isFunction(options.getAriaValueText) - ? options.getAriaValueText(value, percent) - : ariaValueText, - }; - }, -}); - -export const ProgressBar = createComponent({ - as: "div", - memo: true, - useHook: useProgressBar, -}); diff --git a/src/progress/ProgressState.ts b/src/progress/ProgressState.ts index 52a3c9c23..6fa913994 100644 --- a/src/progress/ProgressState.ts +++ b/src/progress/ProgressState.ts @@ -22,15 +22,6 @@ export interface UseProgressProps { max?: number; } -/** - * Progress (Linear) - * - * Progress is used to display the progress status for a task that takes a long - * time or consists of several steps. - * - * It includes accessible attributes to help assistive technologies understand - * and speak the progress values. - */ export const useProgressState = (props: UseProgressProps = {}) => { const { value, min = 0, max = 100 } = props; diff --git a/src/progress/__keys.ts b/src/progress/__keys.ts index 8f88192a9..9b7ca2060 100644 --- a/src/progress/__keys.ts +++ b/src/progress/__keys.ts @@ -6,5 +6,4 @@ const PROGRESS_STATE_KEYS = [ "percent", ] as const; -export const PROGRESS_KEYS = PROGRESS_STATE_KEYS; -export const PROGRESS_BAR_KEYS = [...PROGRESS_STATE_KEYS, "getAriaValueText"]; +export const PROGRESS_KEYS = [...PROGRESS_STATE_KEYS, "getAriaValueText"]; diff --git a/src/progress/index.ts b/src/progress/index.ts index d8a3c0502..bc295f7cb 100644 --- a/src/progress/index.ts +++ b/src/progress/index.ts @@ -1,3 +1,2 @@ -export * from "./ProgressState"; export * from "./Progress"; -export * from "./ProgressBar"; +export * from "./ProgressState"; diff --git a/src/progress/stories/CircularProgress.stories.tsx b/src/progress/stories/CircularProgress.stories.tsx new file mode 100644 index 000000000..4c39ea4bc --- /dev/null +++ b/src/progress/stories/CircularProgress.stories.tsx @@ -0,0 +1,161 @@ +import React from "react"; +import { Meta } from "@storybook/react"; +import { css, keyframes } from "emotion"; +import { isUndefined } from "@chakra-ui/utils"; + +import { Progress } from "../Progress"; +import { useProgressState } from "../ProgressState"; + +export default { + title: "Component/Progress/Circular", +} as Meta; + +const spin = keyframes({ + "0%": { + strokeDasharray: "1, 400", + strokeDashoffset: "0", + }, + "50%": { + strokeDasharray: "400, 400", + strokeDashoffset: "-100", + }, + "100%": { + strokeDasharray: "400, 400", + strokeDashoffset: "-260", + }, +}); + +const rotate = keyframes({ + "0%": { + transform: "rotate(0deg)", + }, + "100%": { + transform: "rotate(360deg)", + }, +}); + +const CircularProgress = (props: any) => { + const { + size = "48px", + value, + capIsRound, + thickness = "10px", + color = "#0078d4", + trackColor = "#edebe9", + label = false, + } = props; + + const progress = useProgressState({ value }); + + const determinant = progress.isIndeterminate + ? undefined + : (progress.percent ?? 0) * 2.64; + + const strokeDasharray = isUndefined(determinant) + ? undefined + : `${determinant} ${264 - determinant}`; + + const indicatorStyles = progress.isIndeterminate + ? css({ + animation: `${spin} 1.5s linear infinite`, + }) + : css({ + strokeDashoffset: 66, + strokeDasharray, + transition: `stroke-dasharray 0.6s ease 0s, stroke 0.6s ease`, + }); + + const rootStyles = css({ + display: "inline-block", + position: "relative", + verticalAlign: "middle", + }); + + const svgStyles = css({ + width: size, + height: size, + animation: progress.isIndeterminate + ? `${rotate} 2s linear infinite` + : undefined, + }); + + const labelStyles = css({ + fontSize: "14px", + top: "50%", + left: "50%", + width: "100%", + textAlign: "center", + position: "absolute", + transform: "translate(-50%, -50%)", + }); + + return ( + + + + + + {label &&
{`${progress.value}%`}
} +
+ ); +}; + +export const Default = () => { + const [value, setValue] = React.useState(0); + + React.useEffect(() => { + const clearId = setInterval(() => { + setValue(prevValue => prevValue + 5); + }, 500); + + if (value === 100) { + clearInterval(clearId); + } + + return () => { + clearInterval(clearId); + }; + }, [value]); + + return ; +}; + +export const WithLabel = () => { + const [value, setValue] = React.useState(0); + + React.useEffect(() => { + const clearId = setInterval(() => { + setValue(prevValue => prevValue + 5); + }, 500); + + if (value === 100) { + clearInterval(clearId); + } + + return () => { + clearInterval(clearId); + }; + }, [value]); + + return ; +}; + +export const IsIndeterminate = () => { + return ; +}; diff --git a/src/progress/stories/Progress.stories.tsx b/src/progress/stories/LinearProgress.stories.tsx similarity index 78% rename from src/progress/stories/Progress.stories.tsx rename to src/progress/stories/LinearProgress.stories.tsx index 381a7ada8..262a51615 100644 --- a/src/progress/stories/Progress.stories.tsx +++ b/src/progress/stories/LinearProgress.stories.tsx @@ -1,19 +1,20 @@ import { Meta } from "@storybook/react"; import React, { CSSProperties } from "react"; +import { css, keyframes, cx } from "emotion"; import { Progress } from "../Progress"; -import { css, keyframes } from "emotion"; -import { ProgressBar } from "../ProgressBar"; import { useProgressState } from "../ProgressState"; export default { - title: "Component/Progress", + title: "Component/Progress/Linear", } as Meta; const progressStyle: CSSProperties = { background: "rgb(237, 242, 247)", height: "0.75rem", width: "400px", + overflow: "hidden", + position: "relative", }; const labelStyles: CSSProperties = { @@ -28,9 +29,13 @@ const labelStyles: CSSProperties = { lineHeight: 1, }; -const progressBarStyle: CSSProperties = { - transition: "all 0.3s", - backgroundColor: "#3182ce", +const progressBarStyle = (percent: any) => { + return css({ + transition: "all 0.3s", + backgroundColor: "#3182ce", + width: percent != null ? `${percent}%` : undefined, + height: "100%", + }); }; function generateStripe(size = "1rem", color = "rgba(255, 255, 255, 0.15)") { @@ -49,11 +54,6 @@ function generateStripe(size = "1rem", color = "rgba(255, 255, 255, 0.15)") { }; } -const stripe = keyframes({ - from: { backgroundPosition: "1rem 0" }, - to: { backgroundPosition: "0 0" }, -}); - export const Default = () => { const [value, setValue] = React.useState(0); @@ -74,9 +74,9 @@ export const Default = () => { const progress = useProgressState({ value }); return ( - - - +
+ +
); }; @@ -100,10 +100,10 @@ export const WithLabel = () => { const progress = useProgressState({ value }); return ( - +
{progress.value}
- - + +
); }; @@ -131,13 +131,12 @@ export const WithStripe = () => { }); return ( - - + - + ); }; @@ -160,19 +159,23 @@ export const WithAnimatedStripe = () => { const progress = useProgressState({ value }); + const stripe = keyframes({ + from: { backgroundPosition: "1rem 0" }, + to: { backgroundPosition: "0 0" }, + }); + const stripStyles = css({ ...generateStripe(), animation: `${stripe} 1s linear infinite`, }); return ( - - + - + ); }; @@ -189,6 +192,8 @@ export const WhenIsIndeterminate = () => { position: "absolute", willChange: "left", minWidth: "50%", + width: "100%", + height: "100%", backgroundImage: "linear-gradient( to right, transparent 0%, #D53F8C 50%, transparent 100% )", animation: `${progressAnim} 1s ease infinite normal none running`, @@ -196,13 +201,13 @@ export const WhenIsIndeterminate = () => { }); return ( - - + - + ); }; @@ -219,18 +224,20 @@ export const WhenIsIndeterminateStripe = () => { position: "absolute", willChange: "left", minWidth: "50%", + width: "100%", + height: "100%", ...generateStripe(), animation: `${progressAnim} 1s ease infinite normal none running`, }), }); return ( - - + - + ); };