loveship/components/inputs/SliderRating.tsx (180 lines of code) (raw):

import React, { useState, useRef } from 'react'; import cx from 'classnames'; import { BaseRating, IconContainer } from '@epam/uui-components'; import { Icon, IEditable, IHasRawProps } from '@epam/uui-core'; import { ReactComponent as LineGrayIcon } from '../icons/slider-rating/line_gray_icon.svg'; import { ReactComponent as LineRedIcon } from '../icons/slider-rating/line_red_icon.svg'; import { ReactComponent as LineYellowIcon } from '../icons/slider-rating/line_yellow_icon.svg'; import { ReactComponent as LineBlueIcon } from '../icons/slider-rating/line_blue_icon.svg'; import { ReactComponent as LineVioletIcon } from '../icons/slider-rating/line_violet_icon.svg'; import { ReactComponent as ActiveMarkRedIcon } from '../icons/slider-rating/active_mark_red_icon.svg'; import { ReactComponent as ActiveMarkYellowIcon } from '../icons/slider-rating/active_mark_yellow_icon.svg'; import { ReactComponent as ActiveMarkGreenIcon } from '../icons/slider-rating/active_mark_green_icon.svg'; import { ReactComponent as ActiveMarkBlueIcon } from '../icons/slider-rating/active_mark_blue_icon.svg'; import { ReactComponent as ActiveMarkVioletIcon } from '../icons/slider-rating/active_mark_violet_icon.svg'; import { ReactComponent as NaIcon } from '../icons/slider-rating/na_icon.svg'; import { ReactComponent as NaActiveIcon } from '../icons/slider-rating/na_active_icon.svg'; import { Tooltip } from '../overlays'; import { i18n } from '../../i18n'; import css from './SliderRating.module.scss'; const defaultSize = '18'; export interface SliderRatingProps<TValue> extends IEditable<TValue>, IHasRawProps<React.HTMLAttributes<HTMLDivElement>> { /** * Enables to pass your custom Tooltip component instead of default. */ renderTooltip?: (value: TValue) => React.ReactNode; /** * Defines start point of component. * @default '1' */ from?: 1 | 2; /** * Defines is NotAvailable showing. * @default false */ withoutNa?: boolean; /** * Defines component size. * @default '18' */ size?: '18' | '24'; /** * Enables to pass your ScaleIcon component instead of default. */ getScaleIcon?: (value: number) => Icon; /** * Icon click handler. */ getHandlerIcon?: (value: number) => Icon; /* * Defines Tooltip color. */ tooltipColor?: 'white' | 'fire' | 'gray'; } const maxValue = 5; export class SliderRating extends React.Component<SliderRatingProps<number>> { handlerWidth: number; getScaleIcon = (rating: number) => { switch (rating) { case 1: return LineRedIcon; case 2: return LineYellowIcon; case 3: return LineGrayIcon; case 4: return LineBlueIcon; case 5: return LineVioletIcon; default: return LineGrayIcon; } }; getHandlerIcon = (rating: number) => { switch (rating) { case 1: return ActiveMarkRedIcon; case 2: return ActiveMarkYellowIcon; case 3: return ActiveMarkGreenIcon; case 4: return ActiveMarkBlueIcon; case 5: return ActiveMarkVioletIcon; } }; getLeftHandlerIconPosition = (rating: number, from: number, stepWidth: number) => { const left = !!rating ? (rating - from) * stepWidth - this.handlerWidth / 2 : 0; if (rating && rating === from) { return left + 2; } else if (rating && rating === maxValue) { return left - 2; } return left; }; renderTooltipBox(rating: number) { const tooltipContent = this.props.renderTooltip ? this.props.renderTooltip(rating) : `${rating}`; return <TooltipBox tooltipColor={ this.props.tooltipColor } content={ tooltipContent } size={ this.props.size || '18' } />; } renderRating = (sliderRating: number, markWidth: number, numberOfMarks: number) => { const rating = sliderRating || 0; const from = this.props.from || 1; const stepWidth = (markWidth * numberOfMarks) / (numberOfMarks - 1); const left = this.getLeftHandlerIconPosition(rating, from, stepWidth); const size = this.props.size || defaultSize; return ( <> <div className={ cx(css.scale, css[`size-${size}`], from === 2 && css.shortScale) }> <IconContainer cx={ css.scaleIcon } icon={ this.props.getScaleIcon ? this.props.getScaleIcon(rating) : this.getScaleIcon(rating) } /> </div> {this.renderTooltipBox(rating)} <div className={ cx(css.handler, css[`size-${size}`], !rating && css.hidden) } style={ { left: left } } ref={ (handler) => { this.handlerWidth = handler && handler.offsetWidth; } } > <Tooltip color={ this.props.tooltipColor } cx={ css.tooltip } content={ this.props.renderTooltip ? this.props.renderTooltip(rating) : `${rating}` }> <IconContainer cx={ css.handlerIcon } icon={ this.props.getHandlerIcon ? this.props.getHandlerIcon(rating) : this.getHandlerIcon(rating) } /> </Tooltip> </div> </> ); }; renderNa() { const isReadonly = this.props.isReadonly || this.props.isDisabled; const size = this.props.size || defaultSize; if (isReadonly && this.props.value !== 0) { return <IconContainer cx={ cx(css.naIcon, css[`size-${size}`], css.disabled) } icon={ NaIcon } />; } else { return ( <Tooltip color={ this.props.tooltipColor } content={ this.props.renderTooltip ? this.props.renderTooltip(0) : i18n.sliderRating.notAvailableMessage }> <IconContainer cx={ cx(css.naIcon, css[`size-${size}`], isReadonly && css.disabled) } icon={ this.props.value === 0 ? NaActiveIcon : NaIcon } onClick={ !isReadonly && (() => this.props.onValueChange(0)) } /> </Tooltip> ); } } render() { return ( <div className={ css.container } { ...this.props.rawProps }> <BaseRating from={ this.props.from || 1 } to={ maxValue } step={ 1 } renderRating={ this.renderRating } { ...this.props } value={ this.props.value === 0 ? null : this.props.value } cx={ css.baseRatingContainer } /> {!this.props.withoutNa && <div className={ css.naIconContainer }>{this.renderNa()}</div>} </div> ); } } type TooltipBoxProps = { size: string; tooltipColor: 'white' | 'fire' | 'gray'; content: React.ReactNode | string; }; function TooltipBox(props: TooltipBoxProps) { const { content, size, tooltipColor } = props; const tooltipBoxRef = useRef<HTMLDivElement>(null); const [left, setLeft] = useState<number>(0); const topPosition = tooltipBoxRef.current?.getBoundingClientRect().y || 0; return ( <div className={ css.tooltipsBox } ref={ tooltipBoxRef } onMouseMove={ (event: React.MouseEvent) => setLeft(event.clientX) }> <Tooltip color={ tooltipColor } placement="top" content={ content } cx={ css.tooltip }> <div className={ css.tooltipsBoxItem } style={ { left: `${left - 1}px`, top: `${topPosition}px`, height: `${size}px`, } } /> </Tooltip> <Tooltip color={ tooltipColor } placement="top" content={ content } cx={ css.tooltip }> <div className={ css.tooltipsBoxItem } style={ { left: `${left}px`, top: `${topPosition}px`, height: `${size}px`, } } /> </Tooltip> <Tooltip color={ tooltipColor } placement="top" content={ content } cx={ css.tooltip }> <div className={ css.tooltipsBoxItem } style={ { left: `${left + 1}px`, top: `${topPosition}px`, height: `${size}px`, } } /> </Tooltip> </div> ); }