@pie-lib/text-select
Version:
Some react components for text selection
226 lines (209 loc) • 6.22 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import classNames from 'classnames';
import Check from '@material-ui/icons/Check';
import Close from '@material-ui/icons/Close';
import { color } from '@pie-lib/render-ui';
// we need to use a larger line height for the token to be more readable
const LINE_HEIGHT_MULTIPLIER = 3.2;
// we need a bit more space for correctness indicators
const CORRECTNESS_LINE_HEIGHT_MULTIPLIER = 3.4;
const CORRECTNESS_PADDING = 2;
const Wrapper = ({ useWrapper, children, classNameContainer, iconClass, Icon }) =>
useWrapper ? (
<span className={classNameContainer}>
{children}
<Icon className={iconClass} />
</span>
) : (
children
);
Wrapper.propTypes = {
useWrapper: PropTypes.bool,
classNameContainer: PropTypes.string,
iconClass: PropTypes.string,
Icon: PropTypes.func,
children: PropTypes.element,
};
export const TokenTypes = {
text: PropTypes.string,
selectable: PropTypes.bool,
};
export class Token extends React.Component {
static rootClassName = 'tokenRootClass';
static propTypes = {
...TokenTypes,
classes: PropTypes.object.isRequired,
text: PropTypes.string.isRequired,
className: PropTypes.string,
disabled: PropTypes.bool,
highlight: PropTypes.bool,
correct: PropTypes.bool,
};
static defaultProps = {
selectable: false,
text: '',
};
getClassAndIconConfig = () => {
const {
selectable,
selected,
classes,
className: classNameProp,
disabled,
highlight,
correct,
animationsDisabled,
isMissing,
} = this.props;
const isTouchEnabled = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
const baseClassName = Token.rootClassName;
let classNameContainer;
let Icon;
let iconClass;
if (correct === undefined && selected && disabled) {
return {
className: classNames(classes.token, classes.selected, classes.disabledBlack),
};
}
if (correct !== undefined) {
const isCorrect = correct === true;
return {
className: classNames(baseClassName, classes.custom),
classNameContainer: classNames(isCorrect ? classes.correct : classes.incorrect, classes.commonTokenStyle),
Icon: isCorrect ? Check : Close,
iconClass: classNames(
classes.correctnessIndicatorIcon,
isCorrect ? classes.correctIcon : classes.incorrectIcon,
),
};
}
if (isMissing) {
return {
className: classNames(baseClassName, classes.custom, classes.missing, classes.commonTokenStyle),
classNameContainer: classes.commonTokenStyle,
Icon: Close,
iconClass: classNames(classes.correctnessIndicatorIcon, classes.incorrectIcon),
};
}
return {
className: classNames(
baseClassName,
classes.token,
disabled && classes.disabled,
selectable && !disabled && !isTouchEnabled && classes.selectable,
selected && !disabled && classes.selected,
selected && disabled && classes.disabledAndSelected,
highlight && selectable && !disabled && !selected && classes.highlight,
animationsDisabled && classes.print,
classNameProp,
),
classNameContainer,
Icon,
iconClass,
};
};
render() {
const { text, index, correct, isMissing } = this.props;
const { className, classNameContainer, Icon, iconClass } = this.getClassAndIconConfig();
return (
<Wrapper
useWrapper={correct !== undefined || isMissing}
classNameContainer={classNameContainer}
iconClass={iconClass}
Icon={Icon}
>
<span
className={className}
dangerouslySetInnerHTML={{ __html: (text || '').replace(/\n/g, '<br>') }}
data-indexkey={index}
/>
</Wrapper>
);
}
}
export default withStyles((theme) => {
return {
token: {
cursor: 'pointer',
textIndent: 0,
},
disabled: {
cursor: 'inherit',
color: color.disabled(),
},
disabledBlack: {
cursor: 'inherit',
},
disabledAndSelected: {
backgroundColor: color.blueGrey100(),
},
selectable: {
[theme.breakpoints.up(769)]: {
'&:hover': {
backgroundColor: color.blueGrey300(),
color: theme.palette.common.black,
'& > *': {
backgroundColor: color.blueGrey300(),
},
},
},
},
selected: {
backgroundColor: color.blueGrey100(),
color: theme.palette.common.black,
lineHeight: `${theme.spacing.unit * LINE_HEIGHT_MULTIPLIER}px`,
border: `solid 2px ${color.blueGrey900()}`,
borderRadius: '4px',
'& > *': {
backgroundColor: color.blueGrey100(),
},
},
highlight: {
border: `dashed 2px ${color.blueGrey600()}`,
borderRadius: '4px',
lineHeight: `${theme.spacing.unit * LINE_HEIGHT_MULTIPLIER}px`,
},
print: {
border: `dashed 2px ${color.blueGrey600()}`,
borderRadius: '4px',
lineHeight: `${theme.spacing.unit * LINE_HEIGHT_MULTIPLIER}px`,
color: color.text(),
},
custom: {
display: 'initial',
},
commonTokenStyle: {
position: 'relative',
borderRadius: '4px',
color: theme.palette.common.black,
lineHeight: `${theme.spacing.unit * CORRECTNESS_LINE_HEIGHT_MULTIPLIER + CORRECTNESS_PADDING}px`,
padding: `${CORRECTNESS_PADDING}px`,
},
correct: {
border: `${color.correctTertiary()} solid 2px`,
},
incorrect: {
border: `${color.incorrectWithIcon()} solid 2px`,
},
missing: {
border: `${color.incorrectWithIcon()} dashed 2px`,
},
incorrectIcon: {
backgroundColor: color.incorrectWithIcon(),
},
correctIcon: {
backgroundColor: color.correctTertiary(),
},
correctnessIndicatorIcon: {
color: color.white(),
position: 'absolute',
top: '-8px',
left: '-8px',
borderRadius: '50%',
fontSize: '12px',
padding: '2px',
},
};
})(Token);