@atlaskit/progress-indicator
Version:
A progress indicator shows the user where they are along the steps of a journey.
145 lines (140 loc) • 4.17 kB
JavaScript
import React, { useCallback, useEffect, useRef } from 'react';
import { bind } from 'bind-event-listener';
import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next/usePlatformLeafEventHandler';
import noop from '@atlaskit/ds-lib/noop';
import { Box, Inline } from '@atlaskit/primitives/compiled';
import { ButtonIndicator } from './button-indicator';
import { PresentationalIndicator } from './presentational-indicator';
const packageName = "@atlaskit/progress-indicator";
const packageVersion = "13.4.2";
const progressIndicatorGapMap = {
comfortable: {
default: 'space.100',
large: 'space.150'
},
cozy: {
default: 'space.075',
large: 'space.100'
},
compact: {
default: 'space.050',
large: 'space.075'
}
};
const sizes = {
default: 8,
large: 12
};
const varDotsSize = '--ds-dots-size';
const varDotsMargin = '--ds-dots-margin';
/**
* __ProgressDots__
*
* A progress indicator shows the user where they are along the steps of a journey.
*/
const ProgressDots = ({
appearance = 'default',
ariaControls = 'panel',
ariaLabel = 'tab',
getAriaLabel,
size = 'default',
// NOTE: `spacing` is a reserved HTML attribute and will be added to the
// element, replaced with `gutter`.
spacing: gutter = 'comfortable',
selectedIndex,
testId,
values,
onSelect
}) => {
const tablistRef = useRef(null);
const onSelectWithAnalytics = usePlatformLeafEventHandler({
fn: onSelect || noop,
action: 'selected',
componentName: 'progressIndicator',
packageName,
packageVersion
});
const gap = progressIndicatorGapMap[gutter][size];
const handleKeyDown = useCallback(event => {
const indicators = Array.from(tablistRef.current.children);
// bail if the target isn't an indicator
if (!indicators.includes(event.target)) {
return;
}
// bail if not valid arrow key
const isLeft = event.key === 'ArrowLeft';
const isRight = event.key === 'ArrowRight';
if (!isLeft && !isRight) {
return;
}
// bail if at either end of the values
const isAlpha = isLeft && selectedIndex === 0;
const isOmega = isRight && selectedIndex === values.length - 1;
if (isAlpha || isOmega) {
return;
}
const index = isLeft ? selectedIndex - 1 : selectedIndex + 1;
// call the consumer's select method and focus the applicable indicator
if (onSelect) {
onSelectWithAnalytics({
event: event,
index
});
}
if (typeof indicators[index].focus === 'function') {
indicators[index].focus();
}
}, [onSelectWithAnalytics, selectedIndex, values, onSelect]);
useEffect(() => {
if (!onSelect) {
return noop;
}
return bind(document, {
type: 'keydown',
listener: handleKeyDown,
options: {
capture: false
}
});
}, [onSelect, handleKeyDown]);
return /*#__PURE__*/React.createElement(Box, {
style: {
[varDotsSize]: `${sizes[size]}px`,
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
[varDotsMargin]: gap
},
role: onSelect && 'tablist'
}, /*#__PURE__*/React.createElement(Inline, {
testId: testId,
ref: r => {
tablistRef.current = r;
},
alignInline: "center",
space: gap
}, values.map((_, index) => {
const isSelected = selectedIndex === index;
const tabId = `${ariaLabel}${index}`;
const panelId = `${ariaControls}${index}`;
const label = getAriaLabel ? getAriaLabel(index) : tabId;
const indicatorTestId = testId && `${testId}-ind-${index}`;
return onSelect ? /*#__PURE__*/React.createElement(ButtonIndicator, {
key: index,
testId: indicatorTestId,
appearance: appearance,
isSelected: isSelected,
tabId: tabId,
panelId: panelId,
label: label,
onClick: event => onSelectWithAnalytics({
event,
index
})
}) : /*#__PURE__*/React.createElement(PresentationalIndicator, {
key: index,
testId: indicatorTestId,
appearance: appearance,
isSelected: isSelected
});
})));
};
export default ProgressDots;