Skip to content

Commit

Permalink
feat: progress bar UI improvements (#1436)
Browse files Browse the repository at this point in the history
Co-authored-by: Derek Roberts <[email protected]>
Co-authored-by: mgaseta <[email protected]>
  • Loading branch information
3 people authored Jul 26, 2024
1 parent dc6159c commit 65e2473
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useState } from 'react';
import prefix from '../../styles/classPrefix';
import { SparProgressStepProps } from './SparProgressStep';

export interface SparProgressIndicatorProps
extends Omit<React.HTMLAttributes<HTMLUListElement>, 'onChange'> {
children?: React.ReactNode;
className?: string;
currentIndex?: number;
onChange?: (data: number) => void;
spaceEqually?: boolean;
vertical?: boolean;
}

/**
* Create a progress bar modified with custom icons.
*
* @param {SparProgressIndicatorProps} param0 Progress bar indicator parameters.
* @returns {JSX.Element} custom element
*/
function SparProgressIndicator({
children,
className: customClassName,
currentIndex: controlledIndex = 0,
onChange,
spaceEqually,
vertical
}: SparProgressIndicatorProps): JSX.Element {
const [currentIndex, setCurrentIndex] = useState(controlledIndex);
const [prevControlledIndex, setPrevControlledIndex] = useState(controlledIndex);

const getClassName = (): string => {
let classNames = `${prefix}--progress `;
if (vertical) {
classNames += `${prefix}--progress--vertical `;
}
if (spaceEqually && !vertical) {
classNames += `${prefix}--progress--space-equal `;
}
if (customClassName) {
classNames += customClassName;
}
return classNames;
};

if (controlledIndex !== prevControlledIndex) {
setCurrentIndex(controlledIndex);
setPrevControlledIndex(controlledIndex);
}

return (
<ul className={getClassName()}>
{React.Children.map(children, (child, index) => {
if (!React.isValidElement<SparProgressStepProps>(child)) {
return null;
}

// only setup click handlers if onChange event is passed
const onClick = onChange ? () => onChange(index) : undefined;
if (index === currentIndex) {
return React.cloneElement(child, {
complete: child.props.complete,
current: child.props.complete,
onClick
});
}
if (index < currentIndex) {
return React.cloneElement(child, {
complete: true,
onClick
});
}
if (index > currentIndex) {
return React.cloneElement(child, {
complete: child.props.complete,
onClick
});
}
return null;
})}
</ul>
);
}

export { SparProgressIndicator };
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react';
import prefix from '../../styles/classPrefix';
import { SparSVGIcon } from './SparSVGIcon';

export interface SparProgressStepProps {
className?: string;
complete?: boolean;
current?: boolean;
description?: string;
disabled?: boolean;
invalid?: boolean;
label: string;
onClick?: (
event:
| React.KeyboardEvent<HTMLButtonElement>
| React.MouseEvent<HTMLButtonElement>
) => void;
secondaryLabel?: string;
}

/**
* Create a custom progress step.
*
* @param {SparProgressStepProps} props parameters of the component.
* @returns {JSX.Element} custom element
*/
const SparProgressStep = ({
className,
complete,
current,
description,
disabled,
invalid,
label,
onClick,
secondaryLabel
}: SparProgressStepProps): JSX.Element => {
const getClassName = (): string => {
const classNames: Array<string> = [`${prefix}--progress-step`];
classNames.push(current ? `${prefix}--progress-step--current` : '');
classNames.push(complete ? `${prefix}--progress-step--complete` : '');
classNames.push(!complete && !current ? `${prefix}--progress-step--incomplete` : '');
classNames.push(disabled ? `${prefix}--progress-step--disabled` : '');
if (className) {
classNames.push(className);
}
return classNames.join(' ');
};

const getButtonClassName = (): string => {
const classNames: Array<string> = [`${prefix}--progress-step-button`];
classNames.push(!onClick || current ? `${prefix}--progress-step-button--unclickable` : '');
return classNames.join(' ');
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
if ((e.key === 'Enter' || e.key === 'Space') && onClick) {
onClick(e);
}
};

let message = 'Incomplete';

if (current) {
message = 'Current';
}

if (complete) {
message = 'Complete';
}

if (invalid) {
message = 'Invalid';
}

return (
<li className={getClassName()}>
<button
type="button"
className={getButtonClassName()}
disabled={disabled}
aria-disabled={disabled}
tabIndex={!current && onClick && !disabled ? 0 : -1}
onClick={!current ? onClick : undefined}
onKeyDown={handleKeyDown}
title={label}
>
<SparSVGIcon
complete={complete}
current={current}
description={description}
invalid={invalid}
svgPrefix={prefix}
/>
<div className={`${prefix}--progress-text`}>
<p className={`${prefix}--progress-label`}>
{label}
</p>

{secondaryLabel !== null && secondaryLabel !== undefined ? (
<p className={`${prefix}--progress-optional`}>
{secondaryLabel}
</p>
) : null}
</div>
<span className={`${prefix}--assistive-text`}>{message}</span>
<span className={`${prefix}--progress-line`} />
</button>
</li>
);
};

export { SparProgressStep };
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import {
CheckmarkFilled,
Warning,
CircleDash,
Incomplete,
CircleFilled
} from '@carbon/icons-react';

export interface SparSVGIconProps {
complete?: boolean;
current?: boolean;
description?: string;
invalid?: boolean;
svgPrefix: string;
}

const SparSVGIcon = ({
complete,
current,
description,
invalid,
svgPrefix
}: SparSVGIconProps): JSX.Element => {
if (invalid) {
return (
<Warning className={`${svgPrefix}--progress__warning`}>
<title>{description}</title>
</Warning>
);
}
if (current && !complete) {
return (
<Incomplete>
<title>{description}</title>
</Incomplete>
);
}
if (current && complete) {
return (
<CircleFilled>
<title>{description}</title>
</CircleFilled>
);
}
if (complete && !current) {
return (
<CheckmarkFilled>
<title>{description}</title>
</CheckmarkFilled>
);
}
return (
<CircleDash>
<title>{description}</title>
</CircleDash>
);
};

export { SparSVGIcon };
27 changes: 12 additions & 15 deletions frontend/src/components/SeedlotRegistrationProgress/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React from 'react';

import {
ProgressIndicator,
ProgressStep
} from '@carbon/react';

import { SparProgressIndicator } from './SparProgressIndicator';
import { SparProgressStep } from './SparProgressStep';
import { ProgressIndicatorConfig } from '../../views/Seedlot/ContextContainerClassA/definitions';
import { MEDIUM_SCREEN_WIDTH } from '../../shared-constants/shared-constants';
import useWindowSize from '../../hooks/UseWindowSize';
Expand All @@ -13,7 +10,7 @@ import './styles.scss';

interface SeedlotRegistrationProgressProps {
progressStatus: ProgressIndicatorConfig;
interactFunction?: Function;
interactFunction?: (data: number) => void;
}

const SeedlotRegistrationProgress = ({
Expand All @@ -22,57 +19,57 @@ const SeedlotRegistrationProgress = ({
}: SeedlotRegistrationProgressProps) => {
const widowSize = useWindowSize();
return (
<ProgressIndicator
<SparProgressIndicator
// Needs to feed it a -1 value otherwise step 1 will stuck at current
className="spar-seedlot-reg-progress-bar"
currentIndex={-1}
spaceEqually
onChange={interactFunction ?? null}
onChange={interactFunction ?? undefined}
vertical={widowSize.innerWidth < MEDIUM_SCREEN_WIDTH}
>
<ProgressStep
<SparProgressStep
label="Collection"
secondaryLabel="Step 1"
complete={progressStatus.collection.isComplete}
current={progressStatus.collection.isCurrent}
invalid={progressStatus.collection.isInvalid}
/>
<ProgressStep
<SparProgressStep
label="Ownership"
secondaryLabel="Step 2"
complete={progressStatus.ownership.isComplete}
current={progressStatus.ownership.isCurrent}
invalid={progressStatus.ownership.isInvalid}
/>
<ProgressStep
<SparProgressStep
label="Interim storage"
secondaryLabel="Step 3"
complete={progressStatus.interim.isComplete}
current={progressStatus.interim.isCurrent}
invalid={progressStatus.interim.isInvalid}
/>
<ProgressStep
<SparProgressStep
label="Orchard"
secondaryLabel="Step 4"
complete={progressStatus.orchard.isComplete}
current={progressStatus.orchard.isCurrent}
invalid={progressStatus.orchard.isInvalid}
/>
<ProgressStep
<SparProgressStep
label="Parent tree and SMP"
secondaryLabel="Step 5"
complete={progressStatus.parent.isComplete}
current={progressStatus.parent.isCurrent}
invalid={progressStatus.parent.isInvalid}
/>
<ProgressStep
<SparProgressStep
label="Extraction and storage"
secondaryLabel="Step 6"
complete={progressStatus.extraction.isComplete}
current={progressStatus.extraction.isCurrent}
invalid={progressStatus.extraction.isInvalid}
/>
</ProgressIndicator>
</SparProgressIndicator>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@
.#{vars.$bcgov-prefix}--progress-step {
min-inline-size: unset;
}

.#{vars.$bcgov-prefix}--progress-step-button--unclickable .#{vars.$bcgov-prefix}--progress-label {
font-weight: 700;
}
}

0 comments on commit 65e2473

Please sign in to comment.