Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: progress bar UI improvements #1436

Merged
merged 9 commits into from
Jul 26, 2024
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;
}
}