Skip to content

Commit

Permalink
feat: progress bar
Browse files Browse the repository at this point in the history
  • Loading branch information
mikeldking committed Jun 19, 2024
1 parent dc346ca commit cd468d0
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 0 deletions.
31 changes: 31 additions & 0 deletions src/progress/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { DOMRef } from '@react-types/shared';
import React from 'react';
import { useProgressBar } from '@react-aria/progress';
import { ACProgressBarProps } from '../types';
import { classNames } from '../utils';
import { ProgressBarBase } from './ProgressBarBase';

function ProgressBar(props: ACProgressBarProps, ref: DOMRef<HTMLDivElement>) {
let { staticColor, ...otherProps } = props;
const { progressBarProps, labelProps } = useProgressBar(props);

return (
<ProgressBarBase
{...otherProps}
ref={ref}
barProps={progressBarProps}
labelProps={labelProps}
barClassName={classNames({
'ac-barloader--static-white': staticColor === 'white',
'ac-barloader--static-black': staticColor === 'black',
})}
/>
);
}

/**
* ProgressBars show the progression of a system operation: downloading, uploading, processing, etc., in a visual way.
* They can represent either determinate or indeterminate progress.
*/
let _ProgressBar = React.forwardRef(ProgressBar);
export { _ProgressBar as ProgressBar };
91 changes: 91 additions & 0 deletions src/progress/ProgressBarBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { clamp } from '@react-aria/utils';
import React, { CSSProperties, HTMLAttributes } from 'react';
import { DOMRef, ProgressBarProps, ACProgressBarBaseProps } from '../types';
import { classNames, useDOMRef, useStyleProps } from '../utils';
import { progressBarCSS } from './styles';
interface ProgressBarBaseProps
extends ACProgressBarBaseProps,
ProgressBarProps {
barClassName?: string;
barProps?: HTMLAttributes<HTMLDivElement>;
labelProps?: HTMLAttributes<HTMLLabelElement>;
}

// Base ProgressBar component shared with Meter.
function ProgressBarBase(
props: ProgressBarBaseProps,
ref: DOMRef<HTMLDivElement>
) {
let {
value = 0,
minValue = 0,
maxValue = 100,
size = 'L',
label,
barClassName,
showValueLabel = !!label,
labelPosition = 'top',
isIndeterminate = false,
barProps,
labelProps,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledby,
...otherProps
} = props;
let domRef = useDOMRef(ref);
let { styleProps } = useStyleProps(otherProps);

value = clamp(value, minValue, maxValue);

let barStyle: CSSProperties = {};
if (!isIndeterminate) {
let percentage = (value - minValue) / (maxValue - minValue);
barStyle.width = `${Math.round(percentage * 100)}%`;
}

// Ideally this should be in useProgressBar, but children
// are not supported in ProgressCircle which shares that hook...
if (!label && !ariaLabel && !ariaLabelledby) {
// eslint-disable-next-line no-console
console.warn(
'If you do not provide a visible label via children, you must specify an aria-label or aria-labelledby attribute for accessibility'
);
}
// use inline style for fit-content because cssnano is too smart for us and will strip out the -moz prefix in css files
return (
<div
{...barProps}
ref={domRef}
className={classNames(
'ac-barloader',
{
'ac-barloader--small': size === 'S',
'ac-barloader--large': size === 'L',
'ac-barloader--indeterminate': isIndeterminate,
'ac-barloader--sideLabel': labelPosition === 'side',
},
barClassName,
styleProps.className
)}
css={progressBarCSS}
style={{ minWidth: '-moz-fit-content', ...styleProps.style }}
>
{label && (
<span {...labelProps} className="ac-barloader-label">
{label}
</span>
)}
{showValueLabel && barProps && (
<div className={'ac-barloader-percentage'}>
{barProps['aria-valuetext']}
</div>
)}
<div className={'ac-barloader-track'}>
<div className={'ac-barloader-fill'} style={barStyle} />
</div>
</div>
);
}

let _ProgressBarBase = React.forwardRef(ProgressBarBase);
export { _ProgressBarBase as ProgressBarBase };
1 change: 1 addition & 0 deletions src/progress/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './ProgressCircle';
export * from './ProgressBar';
80 changes: 80 additions & 0 deletions src/progress/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,83 @@ export const progressCircleCSS = css`
}
}
`;

export const progressBarCSS = css`
--ac-barloader-large-border-radius: 3px;
--ac-barloader-track-color-default: var(--ac-global-color-grey-300);
&.ac-barloader {
--ac-barloader-large-track-fill-color: var(--ac-global-color-primary);
--ac-barloader-static-black-track-color: #00000040;
--ac-barloader-static-black-fill-color: var(
--ac-global-static-color-black-900
);
min-inline-size: var(--ac-global-dimension-static-size-600, 48px);
inline-size: var(--ac-global-dimension-size-2400);
vertical-align: top;
isolation: isolate;
flex-flow: wrap;
justify-content: space-between;
align-items: center;
display: inline-flex;
position: relative;
}
&.ac-barloader--static-white {
--mod-barloader-label-and-value-color: var(
--ac-global-static-color-white-900
);
--mod-barloader-fill-color: var(--ac-global-color-white-900);
}
&.ac-barloader--static-black {
--mod-barloader-label-and-value-color: var(
--ac-global-static-color-black-900
);
--mod-barloader-fill-color: var(--ac-global-static-color-black-900);
--mod-barloader-track-color: var(--ac-barloader-static-black-track-color);
}
.ac-barloader-label,
.ac-barloader-percentage {
color: var(
--mod-barloader-label-and-value-color,
var(--ac-global-text-color-900)
);
font-size: var(--spectrum-global-dimension-font-size-75);
font-weight: var(--spectrum-global-font-weight-regular);
line-height: var(--spectrum-global-font-line-height-small);
text-align: start;
text-align: start;
margin-bottom: var(--ac-global-dimension-size-115);
}
.ac-barloader-label {
flex: 1;
}
.ac-barloader-percentage {
align-self: flex-start;
margin-inline-start: var(--ac-global-dimension-size-150);
}
.ac-barloader-track {
background-color: var(
--mod-barloader-track-color,
var(--ac-barloader-track-color-default)
);
min-inline-size: var(--ac-global-dimension-static-size-600);
height: var(--ac-global-dimension-size-75);
border-radius: var(--ac-barloader-large-border-radius);
z-index: 1;
inline-size: 100%;
overflow: hidden;
}
.ac-barloader-fill {
background: var(--mod-barloader-fill-color, var(--ac-global-color-primary));
height: var(--ac-global-dimension-size-75);
border: none;
transition: width 1s;
}
`;
22 changes: 22 additions & 0 deletions src/provider/GlobalStyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ const staticCSS = css`
--ac-global-static-color-white-900: rgba(255, 255, 255, 0.9);
--ac-global-static-color-white-700: rgba(255, 255, 255, 0.7);
--ac-global-static-color-white-300: rgba(255, 255, 255, 0.3);
--ac-global-static-color-black-900: rgba(0, 0, 0, 0.9);
--ac-global-static-color-black-700: rgba(0, 0, 0, 0.7);
--ac-global-static-color-black-300: rgba(0, 0, 0, 0.3);
}
`;

Expand Down Expand Up @@ -132,6 +135,25 @@ const dimensionsCSS = css`
--ac-global-dimension-static-grid-columns: 12;
--ac-global-dimension-static-grid-fluid-width: 100%;
--ac-global-dimension-static-grid-fixed-max-width: 1280px;
/* Font sizing */
--ac-global-dimension-font-size-25: 10px;
--ac-global-dimension-font-size-50: 11px;
--ac-global-dimension-font-size-75: 12px;
--ac-global-dimension-font-size-100: 14px;
--ac-global-dimension-font-size-150: 15px;
--ac-global-dimension-font-size-200: 16px;
--ac-global-dimension-font-size-300: 18px;
--ac-global-dimension-font-size-400: 20px;
--ac-global-dimension-font-size-500: 22px;
--ac-global-dimension-font-size-600: 25px;
--ac-global-dimension-font-size-700: 28px;
--ac-global-dimension-font-size-800: 32px;
--ac-global-dimension-font-size-900: 36px;
--ac-global-dimension-font-size-1000: 40px;
--ac-global-dimension-font-size-1100: 45px;
--ac-global-dimension-font-size-1200: 50px;
--ac-global-dimension-font-size-1300: 60px;
}
`;

Expand Down
58 changes: 58 additions & 0 deletions src/types/progress.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { ReactNode } from 'react';
import { AriaLabelingProps, DOMProps } from './dom';
import { LabelPosition } from './labelable';
import { StyleProps } from './style';

export interface ProgressBaseProps {
/**
* The current value (controlled).
Expand All @@ -15,3 +20,56 @@ export interface ProgressBaseProps {
*/
maxValue?: number;
}

export interface ProgressBarBaseProps extends ProgressBaseProps {
/** The content to display as the label. */
label?: ReactNode;
/**
* The display format of the value label.
* @default {style: 'percent'}
*/
formatOptions?: Intl.NumberFormatOptions;
/** The content to display as the value's label (e.g. 1 of 4). */
valueLabel?: ReactNode;
}

export interface AriaProgressBarBaseProps
extends ProgressBarBaseProps,
DOMProps,
AriaLabelingProps {}

export interface ProgressBarProps extends ProgressBarBaseProps {
/**
* Whether presentation is indeterminate when progress isn't known.
*/
isIndeterminate?: boolean;
}

export interface AriaProgressBarProps
extends ProgressBarProps,
DOMProps,
AriaLabelingProps {}

export interface ACProgressBarBaseProps
extends AriaProgressBarBaseProps,
StyleProps {
/**
* How thick the bar should be.
* @default 'L'
*/
size?: 'S' | 'L';
/**
* The label's overall position relative to the element it is labeling.
* @default 'top'
*/
labelPosition?: LabelPosition;
/** Whether the value's label is displayed. True by default if there's a label, false by default if not. */
showValueLabel?: boolean;
}

export interface ACProgressBarProps
extends ACProgressBarBaseProps,
ProgressBarProps {
/** The static color style to apply. Useful when the button appears over a color background. */
staticColor?: 'white' | 'black';
}

0 comments on commit cd468d0

Please sign in to comment.