* FEATURE-3357 : Rate Widget -- Create the first MVP of rate widget * FEATURE-3357 : Rate Widget -- Change the widget name into rating -- Change the widget icon -- Fix the overflow issue in case max count is big -- Fix the issue in case default rate is zero -- Add validations for maxCount and defaultRate * FEATURE-3357 : Rate Widget -- Fix an issue : Stars is cut off if maxCount is greater than 20 -- Add test cases for two validation types, RATE_DEFAULT_RATE and RATE_MAX_COUNT * FEATURE-3357 : Rate Widget -- Add expected data type for tooltip field * FEATURE-3357 : Rate Widget -- Expose maxCount * FEATURE-3357 : Rate Widget -- Change contents of isAllowHalf property -- Adjust alignment of stars dynamically -- Decrease default widget width * FEATURE-3357 : Rate Widget -- Remove a unnecessary comment block
146 lines
3.7 KiB
TypeScript
146 lines
3.7 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
|
import { Icon, Position } from "@blueprintjs/core";
|
|
import { IconNames } from "@blueprintjs/icons";
|
|
import styled from "styled-components";
|
|
import Rating from "react-rating";
|
|
import _ from "lodash";
|
|
|
|
import { ComponentProps } from "components/designSystems/appsmith/BaseComponent";
|
|
import { RateSize, RATE_SIZES } from "constants/WidgetConstants";
|
|
import TooltipComponent from "components/ads/Tooltip";
|
|
|
|
/*
|
|
Note:
|
|
-webkit-line-clamp may seem like a wierd way to doing this
|
|
however, it is getting more and more useful with more browser support.
|
|
It suffices for our target browsers
|
|
More info: https://css-tricks.com/line-clampin/
|
|
*/
|
|
|
|
interface RateContainerProps {
|
|
scrollable: boolean;
|
|
}
|
|
|
|
export const RateContainer = styled.div<RateContainerProps>`
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: center;
|
|
align-content: flex-start;
|
|
overflow: auto;
|
|
|
|
> span {
|
|
align-self: ${(props) => (props.scrollable ? "flex-start" : "center")};
|
|
}
|
|
`;
|
|
|
|
export const Star = styled(Icon)`
|
|
padding: ${(props) =>
|
|
props.iconSize === 12 ? 2.92 : props.iconSize === 16 ? 4.37 : 4.93}px;
|
|
`;
|
|
|
|
export interface RateComponentProps extends ComponentProps {
|
|
value: number;
|
|
isLoading: boolean;
|
|
maxCount: number;
|
|
size: RateSize;
|
|
onValueChanged: (value: number) => void;
|
|
tooltips?: Array<string>;
|
|
activeColor?: string;
|
|
inactiveColor?: string;
|
|
isAllowHalf?: boolean;
|
|
readonly?: boolean;
|
|
leftColumn?: number;
|
|
rightColumn?: number;
|
|
topRow?: number;
|
|
bottomRow?: number;
|
|
}
|
|
|
|
function renderStarsWithTooltip(props: RateComponentProps) {
|
|
const rateTooltips = props.tooltips || [];
|
|
const rateTooltipsCount = rateTooltips.length;
|
|
const deltaCount = props.maxCount - rateTooltipsCount;
|
|
if (rateTooltipsCount === 0) {
|
|
return (
|
|
<Star
|
|
color={props.activeColor}
|
|
icon={IconNames.STAR}
|
|
iconSize={RATE_SIZES[props.size]}
|
|
/>
|
|
);
|
|
}
|
|
const starWithTooltip = rateTooltips.map((tooltip) => (
|
|
<TooltipComponent content={tooltip} key={tooltip} position={Position.TOP}>
|
|
<Star
|
|
color={props.activeColor}
|
|
icon={IconNames.STAR}
|
|
iconSize={RATE_SIZES[props.size]}
|
|
/>
|
|
</TooltipComponent>
|
|
));
|
|
const starWithoutTooltip = _.times(deltaCount, (num: number) => (
|
|
<Star
|
|
color={props.activeColor}
|
|
icon={IconNames.STAR}
|
|
iconSize={RATE_SIZES[props.size]}
|
|
key={num}
|
|
/>
|
|
));
|
|
|
|
return _.concat(starWithTooltip, starWithoutTooltip);
|
|
}
|
|
|
|
function RateComponent(props: RateComponentProps) {
|
|
const rateContainerRef = React.createRef<HTMLDivElement>();
|
|
|
|
const {
|
|
bottomRow,
|
|
inactiveColor,
|
|
isAllowHalf,
|
|
leftColumn,
|
|
maxCount,
|
|
onValueChanged,
|
|
readonly,
|
|
rightColumn,
|
|
size,
|
|
topRow,
|
|
value,
|
|
} = props;
|
|
|
|
const [scrollable, setScrollable] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const rateContainerElement = rateContainerRef.current;
|
|
if (
|
|
rateContainerElement &&
|
|
rateContainerElement.scrollHeight > rateContainerElement.clientHeight
|
|
) {
|
|
setScrollable(true);
|
|
} else {
|
|
setScrollable(false);
|
|
}
|
|
}, [leftColumn, rightColumn, topRow, bottomRow, maxCount, size]);
|
|
|
|
return (
|
|
<RateContainer ref={rateContainerRef} scrollable={scrollable}>
|
|
<Rating
|
|
emptySymbol={
|
|
<Star
|
|
color={inactiveColor}
|
|
icon={IconNames.STAR}
|
|
iconSize={RATE_SIZES[size]}
|
|
/>
|
|
}
|
|
fractions={isAllowHalf ? 2 : 1}
|
|
fullSymbol={renderStarsWithTooltip(props)}
|
|
initialRating={value}
|
|
onChange={onValueChanged}
|
|
readonly={readonly}
|
|
stop={maxCount}
|
|
/>
|
|
</RateContainer>
|
|
);
|
|
}
|
|
|
|
export default RateComponent;
|