1
|
|
|
import React, { ReactElement } from "react"; |
2
|
|
|
import { FormattedMessage, useIntl, defineMessages } from "react-intl"; |
3
|
|
|
import { inputMessages } from "./Form/Messages"; |
4
|
|
|
import WordCounter from "./WordCounter/WordCounter"; |
5
|
|
|
import SimpleWordCounter from "./WordCounter/SimpleWordCounter"; |
6
|
|
|
|
7
|
|
|
export interface TextAreaProps { |
8
|
|
|
/** HTML id of the input element */ |
9
|
|
|
id: string; |
10
|
|
|
/** HTML name of the input element */ |
11
|
|
|
name: string; |
12
|
|
|
/** Holds text for label associated with input element */ |
13
|
|
|
label: string; |
14
|
|
|
/** Custom class for the wrapper div */ |
15
|
|
|
className?: string; |
16
|
|
|
/** boolean indicating if input must have a value, or not */ |
17
|
|
|
required?: boolean; |
18
|
|
|
/** Holds message for right hand side, after required warning */ |
19
|
|
|
rightMessage?: string | ReactElement; |
20
|
|
|
/** Boolean that sets the select input to invalid */ |
21
|
|
|
invalid?: boolean | null; |
22
|
|
|
/** Let's you specify example text that appears in input element when empty */ |
23
|
|
|
placeholder?: string; |
24
|
|
|
/** Minimum length of characters the text value can be */ |
25
|
|
|
minLength?: number; |
26
|
|
|
/** Maximum length of characters the text value can be */ |
27
|
|
|
maxLength?: number; |
28
|
|
|
/** The value of the input */ |
29
|
|
|
value?: string | number | string[]; |
30
|
|
|
/** Error text that appers underneath if error occurs (eg. required) */ |
31
|
|
|
errorText?: string; |
32
|
|
|
/** data-clone-grid-item value: https://designwithclone.ca/#flexbox-grid */ |
33
|
|
|
grid?: string; |
34
|
|
|
/** Event listener which fires when a change event occurs (varies on input type) */ |
35
|
|
|
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void; |
36
|
|
|
/** Event listener which fires when a input loses focus */ |
37
|
|
|
onBlur?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void; |
38
|
|
|
/** The maximum word count. If set, adds a word counter. */ |
39
|
|
|
wordLimit?: number; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
const messages = defineMessages({ |
43
|
|
|
underLimit: { |
44
|
|
|
id: "textArea.wordsUnderLimit", |
45
|
|
|
defaultMessage: "words left", |
46
|
|
|
description: |
47
|
|
|
"Message displayed on word counter when user is under/matching the limit.", |
48
|
|
|
}, |
49
|
|
|
overLimit: { |
50
|
|
|
id: "textArea.wordsOverLimit", |
51
|
|
|
defaultMessage: "words over limit", |
52
|
|
|
description: |
53
|
|
|
"Message displayed on word counter when user passes the limit.", |
54
|
|
|
}, |
55
|
|
|
}); |
56
|
|
|
|
57
|
|
|
const TextArea: React.FunctionComponent<TextAreaProps> = ({ |
58
|
|
|
id, |
59
|
|
|
name, |
60
|
|
|
label, |
61
|
|
|
className, |
62
|
|
|
required, |
63
|
|
|
rightMessage, |
64
|
|
|
invalid, |
65
|
|
|
placeholder, |
66
|
|
|
value, |
67
|
|
|
errorText, |
68
|
|
|
grid, |
69
|
|
|
minLength, |
70
|
|
|
maxLength, |
71
|
|
|
onChange, |
72
|
|
|
onBlur, |
73
|
|
|
wordLimit, |
74
|
|
|
}): React.ReactElement => { |
75
|
|
|
const intl = useIntl(); |
76
|
|
|
const valueString = typeof value === "string" ? value : ""; |
77
|
|
|
const wordCounter = wordLimit ? ( |
78
|
|
|
<span data-c-color="black"> |
79
|
|
|
<SimpleWordCounter |
80
|
|
|
wordLimit={wordLimit} |
81
|
|
|
value={valueString} |
82
|
|
|
absoluteValue |
83
|
|
|
beforeText="( " |
84
|
|
|
underMaxMessage={`${intl.formatMessage(messages.underLimit)} )`} |
85
|
|
|
overMaxMessage={`${intl.formatMessage(messages.overLimit)} )`} |
86
|
|
|
/> |
87
|
|
|
</span> |
88
|
|
|
) : null; |
89
|
|
|
|
90
|
|
|
return ( |
91
|
|
|
<div |
92
|
|
|
className={className} |
93
|
|
|
data-c-grid-item={grid} |
94
|
|
|
data-c-input="textarea" |
95
|
|
|
data-c-required={required || null} |
96
|
|
|
data-c-invalid={invalid || null} |
97
|
|
|
> |
98
|
|
|
<label htmlFor={id}>{label}</label> |
99
|
|
|
{required && ( |
100
|
|
|
<span> |
101
|
|
|
<FormattedMessage {...inputMessages.required} /> {rightMessage} |
102
|
|
|
{wordCounter} |
103
|
|
|
</span> |
104
|
|
|
)} |
105
|
|
|
{/** rightMessage and wordCounter are repeated because if this input is not required, |
106
|
|
|
* the previous span will not appear - and it is required, they must be part of the |
107
|
|
|
* previous span to appear in the right place. |
108
|
|
|
*/} |
109
|
|
|
{!required && ( |
110
|
|
|
<span style={{ display: "block" }}> |
111
|
|
|
{rightMessage} |
112
|
|
|
{wordCounter} |
113
|
|
|
</span> |
114
|
|
|
)} |
115
|
|
|
<div> |
116
|
|
|
<textarea |
117
|
|
|
id={id} |
118
|
|
|
name={name} |
119
|
|
|
required={required} |
120
|
|
|
placeholder={placeholder} |
121
|
|
|
minLength={minLength} |
122
|
|
|
maxLength={maxLength} |
123
|
|
|
onChange={onChange} |
124
|
|
|
onBlur={onBlur} |
125
|
|
|
value={value} |
126
|
|
|
/> |
127
|
|
|
</div> |
128
|
|
|
<span>{errorText || <FormattedMessage {...inputMessages.error} />}</span> |
129
|
|
|
</div> |
130
|
|
|
); |
131
|
|
|
}; |
132
|
|
|
|
133
|
|
|
export default TextArea; |
134
|
|
|
|