PromucFlow_constructor/app/client/src/widgets/TextWidget/component/index.tsx
Valera cd02e171cc
fix: change word truncate behavior in show more modal (#20747)
## Description
Swapping `word-break: break-all;` to `overflow-wrap: break-word;` in the
"Show more" modal.

Before

![2023-02-17_10-54-25](https://user-images.githubusercontent.com/11555074/219649645-e9a83f85-9ef5-4ee6-9a98-22971f374a8a.png)
After
![2023-02-17_10-54-25
2](https://user-images.githubusercontent.com/11555074/219649649-3d4d8079-e52e-4cd2-ab30-7051027bf8a5.png)

Fixes https://github.com/appsmithorg/appsmith/issues/20413

## Type of change
Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?
Manual

## Checklist:
### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


### QA activity:
- [ ] Test plan has been approved by relevant developers
- [ ] Test plan has been peer reviewed by QA
- [ ] Cypress test cases have been added and approved by either SDET or
manual QA
- [ ] Organized project review call with relevant stakeholders after
Round 1/2 of QA
- [ ] Added Test Plan Approved label after reveiwing all Cypress test
2023-02-20 14:20:22 +05:30

378 lines
9.7 KiB
TypeScript

import * as React from "react";
import { Text } from "@blueprintjs/core";
import styled from "styled-components";
import { ComponentProps } from "widgets/BaseComponent";
import Interweave from "interweave";
import { UrlMatcher, EmailMatcher } from "interweave-autolink";
import {
DEFAULT_FONT_SIZE,
FontStyleTypes,
TextSize,
} from "constants/WidgetConstants";
import { Icon, IconSize } from "design-system-old";
import { get } from "lodash";
import equal from "fast-deep-equal/es6";
import ModalComponent from "components/designSystems/appsmith/ModalComponent";
import { Color, Colors } from "constants/Colors";
import FontLoader from "./FontLoader";
import { fontSizeUtility } from "widgets/WidgetUtils";
import { OverflowTypes } from "../constants";
import LinkFilter from "./filters/LinkFilter";
export type TextAlign = "LEFT" | "CENTER" | "RIGHT" | "JUSTIFY";
const ELLIPSIS_HEIGHT = 15;
export const TextContainer = styled.div`
& {
height: 100%;
width: 100%;
position: relative;
}
ul {
list-style-type: disc;
list-style-position: inside;
}
ol {
list-style-type: decimal;
list-style-position: inside;
}
ul ul,
ol ul {
list-style-type: circle;
list-style-position: inside;
margin-left: 15px;
}
ol ol,
ul ol {
list-style-type: lower-latin;
list-style-position: inside;
margin-left: 15px;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
h2 {
font-size: 1.5em;
margin: 0.75em 0;
}
h3 {
font-size: 1.17em;
margin: 0.83em 0;
}
h5 {
font-size: 0.83em;
margin: 1.5em 0;
}
h6 {
font-size: 0.75em;
margin: 1.67em 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: bold;
}
a {
color: #106ba3;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
`;
const StyledIcon = styled(Icon)<{ backgroundColor?: string }>`
cursor: pointer;
bottom: 0;
left: 0;
right: 0;
height: ${ELLIPSIS_HEIGHT}px;
background: ${(props) =>
props.backgroundColor ? props.backgroundColor : "transparent"};
`;
type StyledTextProps = React.PropsWithChildren<{
overflow: OverflowTypes;
isTruncated: boolean;
textAlign: string;
backgroundColor?: string;
textColor?: string;
fontStyle?: string;
fontSize?: TextSize;
}>;
export const StyledText = styled(Text)<StyledTextProps>`
height: ${(props) =>
props.overflow === OverflowTypes.TRUNCATE
? `calc(100% - ${ELLIPSIS_HEIGHT}px)`
: "100%"};
overflow-x: hidden;
overflow-y: ${(props) =>
props.overflow !== OverflowTypes.SCROLL ||
props.overflow === OverflowTypes.TRUNCATE.valueOf()
? "hidden"
: "auto"};
text-overflow: ellipsis;
&& div {
display: block;
width: 100%;
}
display: flex;
width: 100%;
justify-content: flex-start;
flex-direction: ${(props) => (props.isTruncated ? "column" : "unset")};
align-items: ${(props) =>
props.overflow === OverflowTypes.SCROLL ||
props.overflow === OverflowTypes.TRUNCATE
? "flex-start"
: "center"};
background: ${(props) => props?.backgroundColor};
color: ${(props) => props?.textColor};
font-style: ${(props) =>
props?.fontStyle?.includes(FontStyleTypes.ITALIC) ? "italic" : ""};
text-decoration: ${(props) =>
props?.fontStyle?.includes(FontStyleTypes.UNDERLINE) ? "underline" : ""};
font-weight: ${(props) =>
props?.fontStyle?.includes(FontStyleTypes.BOLD) ? "bold" : "normal"};
font-size: ${({ fontSize }) =>
fontSizeUtility(fontSize) || DEFAULT_FONT_SIZE};
word-break: break-word;
span {
width: 100%;
line-height: 1.2;
white-space: pre-wrap;
text-align: ${(props) => props.textAlign.toLowerCase()};
}
`;
const ModalContent = styled.div<{
backgroundColor?: string;
}>`
background: ${(props) => props?.backgroundColor || Colors.WHITE};
padding: 24px;
padding-top: 16px;
`;
const Heading = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
.title {
font-weight: 500;
font-size: 20px;
line-height: 24px;
letter-spacing: -0.24px;
color: ${Colors.GREY_10};
}
.icon > svg > path {
stroke: ${Colors.GREY_9};
}
`;
const Content = styled.div<{
fontSize?: TextSize;
fontStyle?: string;
textAlign: string;
textColor?: string;
}>`
padding-top: 16px;
color: ${(props) => props?.textColor};
max-height: 70vh;
overflow: auto;
overflow-wrap: break-word;
text-align: ${(props) => props.textAlign.toLowerCase()};
font-style: ${(props) =>
props?.fontStyle?.includes(FontStyleTypes.ITALIC) ? "italic" : ""};
text-decoration: ${(props) =>
props?.fontStyle?.includes(FontStyleTypes.UNDERLINE) ? "underline" : ""};
font-weight: ${(props) =>
props?.fontStyle?.includes(FontStyleTypes.BOLD) ? "bold" : "normal"};
font-size: ${({ fontSize }) =>
fontSizeUtility(fontSize) || DEFAULT_FONT_SIZE};
`;
export interface TextComponentProps extends ComponentProps {
accentColor: string;
text?: string;
textAlign: TextAlign;
ellipsize?: boolean;
fontSize?: TextSize;
fontFamily: string;
isLoading: boolean;
backgroundColor?: string;
textColor?: string;
fontStyle?: string;
disableLink: boolean;
truncateButtonColor?: string;
borderColor?: Color;
borderWidth?: number;
overflow: OverflowTypes;
// helpers to detect and re-calculate content width
bottomRow?: number;
leftColumn?: number;
rightColumn?: number;
topRow?: number;
}
type State = {
isTruncated: boolean;
showModal: boolean;
};
type TextRef = React.Ref<Text> | undefined;
class TextComponent extends React.Component<TextComponentProps, State> {
state = {
isTruncated: false,
showModal: false,
};
textRef = React.createRef() as TextRef;
getTruncate = (element: any) => {
const { isTruncated } = this.state;
// add ELLIPSIS_HEIGHT and check content content is overflowing or not
return (
element.scrollHeight >
element.offsetHeight + (isTruncated ? ELLIPSIS_HEIGHT : 0)
);
};
componentDidMount = () => {
const textRef = get(this.textRef, "current.textRef");
if (textRef && this.props.overflow === OverflowTypes.TRUNCATE) {
const isTruncated = this.getTruncate(textRef);
this.setState({ isTruncated });
}
};
componentDidUpdate = (prevProps: TextComponentProps) => {
if (!equal(prevProps, this.props)) {
if (this.props.overflow === OverflowTypes.TRUNCATE) {
const textRef = get(this.textRef, "current.textRef");
if (textRef) {
const isTruncated = this.getTruncate(textRef);
this.setState({ isTruncated });
}
} else if (
prevProps.overflow === OverflowTypes.TRUNCATE &&
this.props.overflow !== OverflowTypes.TRUNCATE.valueOf()
) {
this.setState({ isTruncated: false });
}
}
};
handleModelOpen = () => {
this.setState({ showModal: true });
};
handleModelClose = () => {
this.setState({ showModal: false });
};
render() {
const {
accentColor,
backgroundColor,
disableLink,
ellipsize,
fontSize,
fontStyle,
overflow,
text,
textAlign,
textColor,
truncateButtonColor,
} = this.props;
return (
<>
<FontLoader fontFamily={this.props.fontFamily}>
<TextContainer>
<StyledText
backgroundColor={backgroundColor}
className={this.props.isLoading ? "bp3-skeleton" : "bp3-ui-text"}
ellipsize={ellipsize}
fontSize={fontSize}
fontStyle={fontStyle}
isTruncated={this.state.isTruncated}
overflow={overflow}
ref={this.textRef}
textAlign={textAlign}
textColor={textColor}
>
<Interweave
content={text}
filters={[new LinkFilter()]}
matchers={
disableLink
? []
: [new EmailMatcher("email"), new UrlMatcher("url")]
}
newWindow
/>
</StyledText>
{this.state.isTruncated && (
<StyledIcon
backgroundColor={backgroundColor}
className="t--widget-textwidget-truncate"
fillColor={truncateButtonColor || accentColor}
name="context-menu"
onClick={this.handleModelOpen}
size={IconSize.XXXL}
/>
)}
</TextContainer>
</FontLoader>
<ModalComponent
canEscapeKeyClose
canOutsideClickClose
className="t--widget-textwidget-truncate-modal"
hasBackDrop
isOpen={this.state.showModal}
onClose={this.handleModelClose}
overlayClassName="text-widget-truncate"
scrollContents
width={500}
>
<ModalContent backgroundColor={backgroundColor}>
<Heading>
<div className="title">Show More</div>
<Icon
className="icon"
name="cross"
onClick={this.handleModelClose}
size={IconSize.MEDIUM}
/>
</Heading>
<Content
fontSize={fontSize}
fontStyle={fontStyle}
textAlign={textAlign}
textColor={textColor}
>
<Interweave
content={text}
filters={[new LinkFilter()]}
matchers={
disableLink
? []
: [new EmailMatcher("email"), new UrlMatcher("url")]
}
newWindow
/>
</Content>
</ModalContent>
</ModalComponent>
</>
);
}
}
export default TextComponent;