saagie-ui
Version:
Saagie UI from Saagie Design System
252 lines (236 loc) • 7.42 kB
JavaScript
/* eslint-env browser */
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { Manager, Reference, Popper } from 'react-popper';
import classnames from 'classnames';
import { ClickAwayListener } from '../../core/helpers/clickAwayListener/ClickAwayListener';
import { dropUnwantedProps, modifierCSS } from '../../helpers';
import { Icon } from '../../core/atoms/icon/Icon';
import { TechnologyBadge } from '../../core/atoms/technologyBadge/TechnologyBadge';
import { Badge } from '../../core/atoms/badge/Badge';
import { useMapInteractionContext } from '../mapInteraction/MapInteractionContext';
import { useDragContainerContext } from './PipelineDragContainer';
const propTypes = {
children: PropTypes.node,
/**
* The component used for the root node.
* Either a string to use a DOM element or a component.
*/
tag: PropTypes.elementType,
defaultClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
defaultPlacement: PropTypes.string,
className: PropTypes.string,
category: PropTypes.string,
name: PropTypes.string.isRequired,
technology: PropTypes.string,
technologyIcon: PropTypes.string,
technologyIconUrl: PropTypes.string,
message: PropTypes.node,
isFocus: PropTypes.bool,
onFocus: PropTypes.func,
status: PropTypes.string,
statusInfos: PropTypes.string,
defaultOffset: PropTypes.shape({
top: PropTypes.number,
left: PropTypes.number,
}),
popperClassName: PropTypes.string,
context: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
version: PropTypes.string,
autoClosePopper: PropTypes.bool,
};
const defaultProps = {
children: '',
tag: 'div',
defaultClassName: 'sui-prj-pipeline-job',
defaultPlacement: 'top',
defaultOffset: { top: 0, left: 0 },
className: '',
category: '',
technology: '',
technologyIcon: '',
technologyIconUrl: null,
message: '',
isFocus: false,
onFocus: () => {},
status: '',
statusInfos: '',
popperClassName: 'sui-prj-pipeline-job__popper',
context: false,
version: '',
autoClosePopper: false,
};
export const PipelineJob = ({
isFocus: isFocusProp,
onFocus: onFocusProp,
children,
defaultPlacement,
defaultOffset,
popperClassName,
autoClosePopper,
tag: Tag,
defaultClassName,
className,
category,
name,
technology,
technologyIcon,
technologyIconUrl,
message,
status,
statusInfos,
version,
context,
...props
}) => {
const { isDragging: isDraggingItems } = useDragContainerContext();
const { isDragging: isDraggingMap, scale: scaleMap } = useMapInteractionContext();
const [isFocus, setIsFocus] = useState(isFocusProp);
const onFocusRef = useRef();
onFocusRef.current = onFocusProp;
const handleFocus = (val) => {
setIsFocus(val);
onFocusRef.current(val);
};
useEffect(() => {
setIsFocus(isFocusProp);
}, [isFocusProp]);
useEffect(() => {
handleFocus(false);
}, [isDraggingItems, isDraggingMap, scaleMap]);
const renderMainElement = (ref) => {
const attibutes = dropUnwantedProps(props, ['isFocus', 'autoClosePopper']);
const classes = classnames(
defaultClassName,
className,
modifierCSS(status),
{
'as--focus': isFocus,
'as--dragging': isDraggingItems,
}
);
const getIconByStatus = (currentStatus) => {
switch (currentStatus) {
case 'awaiting':
return <Icon name="clock" />;
case 'requested':
case 'queued':
return (
<svg className="sui-prj-slidein-animation" height="10" width="10">
<circle cx="5" cy="5" r="2" />
</svg>
);
case 'running':
return <Icon name="spinner" className="sui-prj-spiner-animation" />;
case 'succeeded':
return <Icon name="fa-check" />;
case 'failed':
return <Icon name="fa-exclamation" />;
case 'out_of_memory':
return <Icon name="out-of-memory" />;
case 'skipped':
return <Icon name="fa-long-arrow-right" />;
case 'stopping':
return <Icon className="sui-prj-pulse-opacity-animation" name="fa-stop" />;
case 'stopped':
return <Icon name="fa-stop" />;
case 'unknown':
return <Icon name="fa-question" />;
default:
return (
<svg height="10" width="10">
<circle cx="5" cy="5" r="5" />
</svg>
);
}
};
return (
<Tag
className={classes}
{...attibutes}
tabIndex="0"
role="button"
onKeyDown={(e) => { if (['Enter', ' '].includes(e.key)) { handleFocus(true); } }}
onClick={() => handleFocus(true)}
>
{ status && (
<div className="sui-prj-pipeline-job__status">
<i className="sui-prj-pipeline-job__anchor">
{ getIconByStatus(status) }
</i>
{status.replaceAll('_', ' ')}
{ statusInfos && (
<div className="sui-prj-pipeline-job__info">{statusInfos}</div>
)}
</div>
)}
{message}
<div className="sui-prj-pipeline-job__content" ref={ref || null}>
<div className="sui-prj-pipeline-job__technology">
<TechnologyBadge
shadow="none"
size="sm"
icon={technologyIcon || technology}
iconUrl={technologyIconUrl}
label={technology}
/>
</div>
<div className="sui-prj-pipeline-job__details">
<div className="sui-g-grid as--no-wrap as--gutter-xs">
<div className="sui-g-grid__item">
<div className="sui-prj-pipeline-job__name">
{name}
</div>
</div>
<div className="sui-g-grid__item as--push">
{version && (
<div className="sui-prj-pipeline-job__version">
<Badge position="end" size="xs">{version}</Badge>
</div>
)}
</div>
</div>
<div className="sui-prj-pipeline-job__type">
{category}
</div>
</div>
</div>
</Tag>
);
};
const formattedOffset = { offset: { offset: `${defaultOffset.left}, ${defaultOffset.top}` } };
if (!children) {
renderMainElement();
}
return (
<Manager>
<Reference>
{({ ref }) => renderMainElement(ref)}
</Reference>
{isFocus
? ReactDOM.createPortal(
<Popper placement={defaultPlacement} modifiers={formattedOffset}>
{({ placement, ref, style }) => (
<ClickAwayListener onClickOutside={() => handleFocus(false)} ref={ref}>
<div
style={style}
data-placement={placement}
className={popperClassName}
onClick={autoClosePopper ? handleFocus(false) : null}
onKeyDown={autoClosePopper ? handleFocus(false) : null}
role="presentation"
>
{children}
</div>
</ClickAwayListener>
)}
</Popper>,
document.body
)
: ''}
</Manager>
);
};
PipelineJob.propTypes = propTypes;
PipelineJob.defaultProps = defaultProps;