Skip to content

Commit

Permalink
feat: Add validationState to Progress, to make the bar red or green (#…
Browse files Browse the repository at this point in the history
…25253)

Add a `validationState` prop to Progress, which can make the bar red, orange, or green
  • Loading branch information
behowell authored Oct 24, 2022
1 parent a560d5f commit f9436f5
Show file tree
Hide file tree
Showing 16 changed files with 103 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const AllFields = (
<CheckboxField label="Checkbox" {...props} />
<ComboboxField label="Combo box field" {...props} />
<InputField label="Input field" {...props} />
<ProgressField label="Progress field" {...props} />
<ProgressField label="Progress field" value={0.5} {...props} />
<RadioGroupField label="Radio group field" {...props}>
<Radio label="Option one" />
<Radio label="Option two" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ storiesOf('Progress converged', module)
includeHighContrast: true,
includeRtl: true,
})
.addStory('Determinate with thickness large', () => <Progress value={0.5} thickness="large" />);
.addStory('Determinate with thickness large', () => <Progress value={0.5} thickness="large" />)
.addStory('Error', () => <Progress value={0.5} validationState="error" />)
.addStory('Warning', () => <Progress value={0.5} validationState="warning" />)
.addStory('Success', () => <Progress value={0.5} validationState="success" />);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "feat: Add support for validationState to ProgressField",
"packageName": "@fluentui/react-field",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "feat: Add validationState to Progress, to make the bar red or green",
"packageName": "@fluentui/react-progress",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export type FieldConfig<T extends FieldComponent> = {
component: T;
classNames: SlotClassNames<FieldSlots<T>>;
labelConnection?: 'htmlFor' | 'aria-labelledby';
ariaInvalidOnError?: boolean;
};

// @public
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ export type FieldConfig<T extends FieldComponent> = {
* @default htmlFor
*/
labelConnection?: 'htmlFor' | 'aria-labelledby';

/**
* Should the aria-invalid and aria-errormessage attributes be set when validationState="error".
*
* @default true
*/
ariaInvalidOnError?: boolean;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const useField_unstable = <T extends FieldComponent>(
): FieldState<T> => {
const [fieldProps, controlProps] = getPartitionedFieldProps(props);
const { orientation = 'vertical', validationState } = fieldProps;
const { labelConnection = 'htmlFor' } = params;
const { labelConnection = 'htmlFor', ariaInvalidOnError = true } = params;

const baseId = useId('field-');

Expand Down Expand Up @@ -118,7 +118,7 @@ export const useField_unstable = <T extends FieldComponent>(
control['aria-labelledby'] ??= label.id;
}

if (validationState === 'error') {
if (validationState === 'error' && ariaInvalidOnError) {
control['aria-invalid'] ??= true;
if (validationMessage) {
control['aria-errormessage'] ??= validationMessage.id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('ProgressField', () => {
displayName: 'ProgressField',
});

// Most functionality is tested by Field.test.tsx, and RadioGroup's tests
// Most functionality is tested by Field.test.tsx and Progress.test.tsx

it('uses aria-labelledby for the label', () => {
const result = render(<ProgressField label="Test label" />);
Expand All @@ -21,4 +21,15 @@ describe('ProgressField', () => {
expect(progress.getAttribute('aria-labelledby')).toBe(label.id);
expect(label.htmlFor).toBeFalsy();
});

it('uses aria-describedby on error, instead of aria-errormessage ', () => {
const result = render(<ProgressField label="Test label" validationState="error" validationMessage="Test error" />);

const progress = result.getByRole('progressbar');
const message = result.getByText('Test error') as HTMLLabelElement;

expect(message.id).toBeTruthy();
expect(progress.getAttribute('aria-describedby')).toBe(message.id);
expect(progress.getAttribute('aria-invalid')).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export const ProgressField: ForwardRefComponent<ProgressFieldProps> = React.forw
component: Progress,
classNames: progressFieldClassNames,
labelConnection: 'aria-labelledby',
ariaInvalidOnError: false,
});
state.control.validationState = state.validationState;
useFieldStyles_unstable(state);
return renderField_unstable(state);
});
Expand Down
3 changes: 3 additions & 0 deletions packages/react-components/react-progress/Spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ function App() {
- The default Progress that animates indefinitely
- Determinate Progress
- The determinate form of the Progress component that incrementally loads from 0% to 100%
- Error/success
- The validationState prop can be set to "error", "warning", or "success" to make the bar red, orange, or green, respectively.
- The prop name was chosen to align with the Field prop of the same name, allowing ProgressField to have the same API as other fields.

#### Adding Label and Description with ProgressField

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type ProgressProps = Omit<ComponentProps<ProgressSlots>, 'size'> & {
value?: number;
max?: number;
thickness?: 'medium' | 'large';
validationState?: 'success' | 'warning' | 'error';
};

// @public (undocumented)
Expand All @@ -32,7 +33,7 @@ export type ProgressSlots = {
};

// @public
export type ProgressState = ComponentState<ProgressSlots> & Required<Pick<ProgressProps, 'max' | 'shape' | 'thickness'>> & Pick<ProgressProps, 'value'>;
export type ProgressState = ComponentState<ProgressSlots> & Required<Pick<ProgressProps, 'max' | 'shape' | 'thickness'>> & Pick<ProgressProps, 'value' | 'validationState'>;

// @public
export const renderProgress_unstable: (state: ProgressState) => JSX.Element;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,16 @@ export type ProgressProps = Omit<ComponentProps<ProgressSlots>, 'size'> & {
* @default 'medium'
*/
thickness?: 'medium' | 'large';

/**
* The status of the progress bar. Changes the color of the bar.
*/
validationState?: 'success' | 'warning' | 'error';
};

/**
* State used in rendering Progress
*/
export type ProgressState = ComponentState<ProgressSlots> &
Required<Pick<ProgressProps, 'max' | 'shape' | 'thickness'>> &
Pick<ProgressProps, 'value'>;
Pick<ProgressProps, 'value' | 'validationState'>;
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { ProgressProps, ProgressState } from './Progress.types';
*/
export const useProgress_unstable = (props: ProgressProps, ref: React.Ref<HTMLElement>): ProgressState => {
// Props
const { max = 1.0, shape = 'rounded', thickness = 'medium', value } = props;
const { max = 1.0, shape = 'rounded', thickness = 'medium', validationState, value } = props;

const root = getNativeElementProps('div', {
ref,
Expand All @@ -33,6 +33,7 @@ export const useProgress_unstable = (props: ProgressProps, ref: React.Ref<HTMLEl
shape,
thickness,
value,
validationState,
components: {
root: 'div',
bar: 'div',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const useBarStyles = makeStyles({
backgroundImage: `linear-gradient(
to right,
${tokens.colorNeutralBackground6} 0%,
${tokens.colorCompoundBrandBackground} 50%,
${tokens.colorTransparentBackground} 50%,
${tokens.colorNeutralBackground6} 100%
)`,
animationName: indeterminateProgress,
Expand All @@ -103,13 +103,23 @@ const useBarStyles = makeStyles({
rtl: {
animationName: indeterminateProgressRTL,
},

error: {
backgroundColor: tokens.colorPaletteRedForeground1,
},
warning: {
backgroundColor: tokens.colorPaletteDarkOrangeForeground1,
},
success: {
backgroundColor: tokens.colorPaletteGreenForeground1,
},
});

/**
* Apply styling to the Progress slots based on the state
*/
export const useProgressStyles_unstable = (state: ProgressState): ProgressState => {
const { max, shape, thickness, value } = state;
const { max, shape, thickness, validationState, value } = state;
const rootStyles = useRootStyles();
const barStyles = useBarStyles();
const { dir } = useFluent();
Expand All @@ -130,6 +140,7 @@ export const useProgressStyles_unstable = (state: ProgressState): ProgressState
value === undefined && dir === 'rtl' && barStyles.rtl,
barStyles[thickness],
value !== undefined && value > ZERO_THRESHOLD && barStyles.nonZeroDeterminate,
validationState && barStyles[validationState],
state.bar.className,
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import { makeStyles } from '@fluentui/react-components';
import { Progress } from '@fluentui/react-progress';

const useStyles = makeStyles({
container: {
display: 'flex',
flexDirection: 'column',
rowGap: '20px',
},
});

export const ValidationState = () => {
const styles = useStyles();
return (
<div className={styles.container}>
<Progress value={0.75} validationState="error" />
<Progress value={0.95} validationState="warning" />
<Progress value={1} validationState="success" />
</div>
);
};

ValidationState.parameters = {
docs: {
name: 'Validation State',
description: {
story:
'The `validationState` prop can be used to indicate an `"error"` state (red), `"warning"` state (orange), ' +
'or `"success"` state (green).',
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { Default } from './ProgressDefault.stories';
export { Shape } from './ProgressShape.stories';
export { Thickness } from './ProgressBarThickness.stories';
export { Indeterminate } from './ProgressIndeterminate.stories';
export { ValidationState } from './ProgressValidationState.stories';
export { Max } from './ProgressMax.stories';

export default {
Expand Down

0 comments on commit f9436f5

Please sign in to comment.