Skip to content

Commit

Permalink
[EN-6218] feat(textarea): add textarea line limit
Browse files Browse the repository at this point in the history
  • Loading branch information
emile-bex committed Jul 20, 2023
1 parent 337b2a3 commit 0456fce
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 30 deletions.
1 change: 1 addition & 0 deletions src/components/forms/GenericField.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ export const GenericField = ({
<TextAreaNew
id={`${formId}-${data.id}`}
name={data.name}
maxLines={data.maxLines}
onChange={onChangeCustom}
value={value}
valid={getValid(data.name)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/forms/schema/formAddExternalOpportunity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const formAddExternalOpportunityCandidate = {
component: 'heading',
},
{
component: 'fieldgroup',
component: 'fieldgroup-new',
fields: [
{
id: 'recruiterFirstName',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import _ from 'lodash';
import moment from 'moment';
import React, {
/* memo, */ useCallback,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import styled from 'styled-components';
import { BREAKPOINTS } from 'src/constants/styles';

export const StyledBackground = styled.section`
&.top-banner {
Expand Down
2 changes: 1 addition & 1 deletion src/components/utils/Inputs/CheckBox/CheckBox.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const StyledCheckbox = styled.div`
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-start;
height: 16px;
position: relative;
cursor: pointer;
Expand Down
62 changes: 46 additions & 16 deletions src/components/utils/Inputs/TextArea/TextArea.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { COLORS } from 'src/constants/styles';
export const StyledTextAreaContainer = styled.div`
max-width: 100%;
background-color: ${COLORS.white};
overflow-x: auto;
border-radius: 5px;
/* div.label {
border-bottom: solid 2px ${COLORS.gray};
Expand All @@ -15,22 +17,50 @@ export const StyledTextAreaContainer = styled.div`
color: ${COLORS.darkGray};
margin-bottom: 8px;
} */
textarea {
width: 100%;
padding-bottom: 12px;
box-sizing: border-box;
border: none;
resize: vertical;
border-bottom: solid 2px ${COLORS.gray};
margin-bottom: 30px;
`;

export const StyledTextArea = styled.textarea`
width: ${({ hasLineLimit }) => {
// Hard code the width in case the width of the modal changes
return hasLineLimit ? '655px' : '100%';
}};
padding-bottom: 12px;
box-sizing: border-box;
border: none;
resize: none;
border-bottom: solid 2px ${COLORS.gray};
font-family: Poppins, sans-serif;
// need to fix line height to calculate number of lines
line-height: 17px;
&::placeholder {
font-style: italic;
color: ${COLORS.darkGray};
font-family: Poppins, sans-serif;
&::placeholder {
font-style: italic;
color: ${COLORS.darkGray};
font-family: Poppins, sans-serif;
}
:focus-visible {
outline: none;
}
}
:focus-visible {
outline: none;
}
`;

export const StyledTextAreaScrollContainer = styled.div`
overflow-x: auto;
width: ${({ isMobile, hasLineLimit }) => {
return isMobile && hasLineLimit ? '300px' : '100%';
}};
margin-bottom: 30px;
`;

export const StyledAnnotations = styled.div`
display: flex;
justify-content: space-between;
`;

export const StyledLineLimit = styled.div`
color: ${({ warning }) => {
return warning ? COLORS.noRed : COLORS.darkGray;
}};
font-size: 12px;
text-align: right;
align-self: flex-end;
transform: translate(0, -30px);
`;
56 changes: 46 additions & 10 deletions src/components/utils/Inputs/TextArea/TextArea.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import React from 'react';
import { FormValidatorErrorMessage } from 'src/components/forms/FormValidatorErrorMessage';
import { StyledTextAreaContainer } from './TextArea.styles';
import { useIsMobile } from 'src/hooks/utils';
import {
StyledAnnotations,
StyledLineLimit,
StyledTextArea,
StyledTextAreaContainer,
StyledTextAreaScrollContainer,
} from './TextArea.styles';
import { useLineLimit } from './useLineLimit';

interface TextAreaProps {
title: string;
Expand All @@ -13,6 +21,7 @@ interface TextAreaProps {
isInvalid: boolean;
message: string;
};
maxLines?: number;
}

export function TextArea({
Expand All @@ -23,22 +32,49 @@ export function TextArea({
value,
hidden,
valid,
maxLines,
}: TextAreaProps) {
const isMobile = useIsMobile();

const { textAreaRef, remainingLines } = useLineLimit(
value,
name,
onChange,
maxLines
);

if (hidden) {
return null;
}

return (
<StyledTextAreaContainer>
<textarea
name={name}
id={id}
rows={5}
placeholder={title}
onChange={onChange}
value={value}
/>
<FormValidatorErrorMessage validObj={valid} newInput />
<StyledTextAreaScrollContainer
isMobile={isMobile}
hasLineLimit={!!maxLines}
>
<StyledTextArea
isMobile={isMobile}
hasLineLimit={!!maxLines}
ref={textAreaRef}
name={name}
id={id}
rows={5}
placeholder={title}
onChange={onChange}
value={value}
/>
</StyledTextAreaScrollContainer>
<StyledAnnotations>
<div>
<FormValidatorErrorMessage validObj={valid} newInput />
</div>
{maxLines && (
<StyledLineLimit warning={remainingLines === 0}>
{remainingLines} ligne(s) restante(s)
</StyledLineLimit>
)}
</StyledAnnotations>
</StyledTextAreaContainer>
);
}
92 changes: 92 additions & 0 deletions src/components/utils/Inputs/TextArea/useLineLimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import UIkit from 'uikit';
import { usePrevious } from 'src/hooks/utils';

export function useLineLimit(
value: string,
name: string,
onChange: (arg: { target: { value: string; name: string } }) => void,
maxLines?: number
) {
const prevValue: string = usePrevious(value);

const ref = useRef<HTMLTextAreaElement>();

const [numberOfLines, setNumberOfLines] = useState(0);

const calculateContentHeight = useCallback((ta, scanAmount) => {
const origHeight = ta.style.height;
let height = ta.offsetHeight;
const { scrollHeight } = ta;
const { overflow } = ta.style;
/// only bother if the ta is bigger than content
if (height >= scrollHeight) {
/// check that our browser supports changing dimension
/// calculations mid-way through a function call...
ta.style.height = `${height + scanAmount}px`;
/// because the scrollbar can cause calculation problems
ta.style.overflow = 'hidden';
/// by checking that scrollHeight has updated
if (scrollHeight < ta.scrollHeight) {
/// now try and scan the ta's height downwards
/// until scrollHeight becomes larger than height
while (ta.offsetHeight >= ta.scrollHeight) {
ta.style.height = `${(height -= scanAmount)}px`;
}
/// be more specific to get the exact height
while (ta.offsetHeight < ta.scrollHeight) {
ta.style.height = `${(height += 1)}px`;
}
/// reset the ta back to it's original height
ta.style.height = origHeight;
/// put the overflow back
ta.style.overflow = overflow;
return height;
}
} else {
return scrollHeight;
}
}, []);

useEffect(() => {
if (maxLines && ref && ref.current && value !== prevValue) {
const ta = ref.current;

const style = window.getComputedStyle
? window.getComputedStyle(ta)
: ta.style;

const taLineHeight = parseInt(style.lineHeight, 10);
const taPaddingBottom = parseInt(style.paddingBottom, 10);
const taPaddingTop = parseInt(style.paddingTop, 10);
const taHeight = calculateContentHeight(ta, taLineHeight);

const nbOfLines = Math.ceil(
(taHeight - taPaddingBottom - taPaddingTop) / taLineHeight
);

if (nbOfLines > maxLines) {
if (!prevValue || value.length - prevValue.length > 1) {
UIkit.notification(
`Le texte que vous avez essayé de coller dépasse la limite des ${maxLines} lignes.`,
'danger'
);
}

onChange({
target: {
value: prevValue || '',
name,
},
});
} else {
setNumberOfLines(!value ? 0 : nbOfLines);
}
}
}, [calculateContentHeight, maxLines, name, onChange, prevValue, value]);

const remainingLines =
maxLines - numberOfLines < 0 ? 0 : maxLines - numberOfLines;

return { remainingLines, textAreaRef: ref };
}

0 comments on commit 0456fce

Please sign in to comment.