@hero-design/rn 7.13.0 → 7.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/.eslintrc.json +3 -1
  2. package/.turbo/turbo-build.log +3 -2
  3. package/assets/fonts/hero-icons.ttf +0 -0
  4. package/babel.config.js +16 -0
  5. package/es/index.js +26957 -7622
  6. package/lib/assets/fonts/hero-icons.ttf +0 -0
  7. package/lib/index.js +26963 -7624
  8. package/package.json +9 -4
  9. package/rollup.config.js +1 -0
  10. package/src/components/Accordion/AccordionItem.tsx +50 -0
  11. package/src/components/Accordion/StyledAccordion.tsx +29 -0
  12. package/src/components/Accordion/__tests__/AccordionItem.spec.tsx +56 -0
  13. package/src/components/Accordion/__tests__/StyledAccordion.spec.tsx +17 -0
  14. package/src/components/Accordion/__tests__/__snapshots__/AccordionItem.spec.tsx.snap +529 -0
  15. package/src/components/Accordion/__tests__/__snapshots__/StyledAccordion.spec.tsx.snap +33 -0
  16. package/src/components/Accordion/__tests__/__snapshots__/index.spec.tsx.snap +822 -0
  17. package/src/components/Accordion/__tests__/index.spec.tsx +54 -0
  18. package/src/components/Accordion/index.tsx +82 -0
  19. package/src/components/Accordion/utils.tsx +11 -0
  20. package/src/components/Button/IconButton.tsx +1 -1
  21. package/src/components/Calendar/CalendarRowItem.tsx +54 -0
  22. package/src/components/Calendar/StyledCalendar.tsx +76 -0
  23. package/src/components/Calendar/__tests__/CalendarRowItem.spec.tsx +76 -0
  24. package/src/components/Calendar/__tests__/__snapshots__/CalendarRowItem.spec.tsx.snap +411 -0
  25. package/src/components/Calendar/__tests__/helper.spec.ts +50 -0
  26. package/src/components/Calendar/__tests__/index.spec.tsx +99 -0
  27. package/src/components/Calendar/helpers.ts +29 -0
  28. package/src/components/Calendar/index.tsx +217 -0
  29. package/src/components/Collapse/index.tsx +13 -15
  30. package/src/components/ContentNavigator/index.tsx +6 -0
  31. package/src/components/Empty/StyledEmpty.tsx +47 -0
  32. package/src/components/Empty/__tests__/__snapshots__/index.spec.tsx.snap +66 -0
  33. package/src/components/Empty/__tests__/index.spec.tsx +17 -0
  34. package/src/components/Empty/index.tsx +53 -0
  35. package/src/components/FAB/ActionGroup/ActionItem.tsx +6 -2
  36. package/src/components/FAB/ActionGroup/StyledActionGroup.tsx +1 -0
  37. package/src/components/FAB/ActionGroup/StyledActionItem.tsx +7 -1
  38. package/src/components/FAB/ActionGroup/__tests__/__snapshots__/index.spec.tsx.snap +84 -22
  39. package/src/components/FAB/ActionGroup/index.tsx +8 -1
  40. package/src/components/Icon/HeroIcon/selection.json +1 -1
  41. package/src/components/Icon/IconList.ts +13 -0
  42. package/src/components/List/BasicListItem.tsx +44 -34
  43. package/src/components/List/ListItem.tsx +67 -58
  44. package/src/components/List/StyledBasicListItem.tsx +2 -3
  45. package/src/components/List/StyledListItem.tsx +2 -2
  46. package/src/components/List/__tests__/StyledBasicListItem.spec.tsx +5 -2
  47. package/src/components/List/__tests__/StyledListItem.spec.tsx +4 -1
  48. package/src/components/List/__tests__/__snapshots__/BasicListItem.spec.tsx.snap +15 -10
  49. package/src/components/List/__tests__/__snapshots__/ListItem.spec.tsx.snap +52 -32
  50. package/src/components/List/__tests__/__snapshots__/StyledBasicListItem.spec.tsx.snap +128 -48
  51. package/src/components/List/__tests__/__snapshots__/StyledListItem.spec.tsx.snap +132 -52
  52. package/src/components/RichTextEditor/EditorEvent.ts +7 -0
  53. package/src/components/RichTextEditor/EditorToolbar.tsx +220 -0
  54. package/src/components/RichTextEditor/MentionList.tsx +69 -0
  55. package/src/components/RichTextEditor/RichTextEditor.tsx +396 -0
  56. package/src/components/RichTextEditor/StyledRichTextEditor.ts +20 -0
  57. package/src/components/RichTextEditor/StyledToolbar.ts +32 -0
  58. package/src/components/RichTextEditor/__tests__/EditorToolbar.spec.tsx +130 -0
  59. package/src/components/RichTextEditor/__tests__/MentionList.spec.tsx +109 -0
  60. package/src/components/RichTextEditor/__tests__/RichTextEditor.spec.tsx +245 -0
  61. package/src/components/RichTextEditor/__tests__/__snapshots__/EditorToolbar.spec.tsx.snap +324 -0
  62. package/src/components/RichTextEditor/__tests__/__snapshots__/MentionList.spec.tsx.snap +45 -0
  63. package/src/components/RichTextEditor/__tests__/__snapshots__/RichTextEditor.spec.tsx.snap +526 -0
  64. package/src/components/RichTextEditor/constants.ts +20 -0
  65. package/src/components/RichTextEditor/hero-editor.d.ts +8 -0
  66. package/src/components/RichTextEditor/index.tsx +8 -0
  67. package/src/components/RichTextEditor/utils/events.ts +31 -0
  68. package/src/components/RichTextEditor/utils/rnWebView.ts +19 -0
  69. package/src/components/SectionHeading/__tests__/__snapshots__/index.spec.tsx.snap +77 -0
  70. package/src/components/SectionHeading/__tests__/index.spec.tsx +14 -0
  71. package/src/components/SectionHeading/index.tsx +16 -9
  72. package/src/components/Tag/StyledTag.tsx +12 -2
  73. package/src/components/Tag/__tests__/Tag.spec.tsx +35 -8
  74. package/src/components/Tag/__tests__/__snapshots__/Tag.spec.tsx.snap +118 -4
  75. package/src/components/Tag/index.tsx +9 -2
  76. package/src/components/TextInput/__tests__/__snapshots__/StyledTextInput.spec.tsx.snap +1 -0
  77. package/src/components/Typography/Text/StyledText.tsx +2 -1
  78. package/src/components/Typography/Text/__tests__/StyledText.spec.tsx +1 -0
  79. package/src/components/Typography/Text/__tests__/__snapshots__/StyledText.spec.tsx.snap +22 -0
  80. package/src/components/Typography/Text/index.tsx +2 -1
  81. package/src/index.ts +8 -0
  82. package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +101 -4
  83. package/src/theme/components/accordion.ts +19 -0
  84. package/src/theme/components/calendar.ts +34 -0
  85. package/src/theme/components/card.ts +1 -1
  86. package/src/theme/components/empty.ts +38 -0
  87. package/src/theme/components/fab.ts +4 -3
  88. package/src/theme/components/list.ts +1 -0
  89. package/src/theme/components/pinInput.ts +1 -1
  90. package/src/theme/components/richTextEditor.ts +34 -0
  91. package/src/theme/components/tag.ts +8 -2
  92. package/src/theme/components/typography.ts +1 -0
  93. package/src/theme/global/borders.ts +6 -6
  94. package/src/theme/global/colors.ts +4 -1
  95. package/src/theme/index.ts +12 -0
  96. package/testUtils/setup.tsx +17 -0
  97. package/types/components/Accordion/AccordionItem.d.ts +14 -0
  98. package/types/components/Accordion/StyledAccordion.d.ts +32 -0
  99. package/types/components/Accordion/__tests__/AccordionItem.spec.d.ts +1 -0
  100. package/types/components/Accordion/__tests__/StyledAccordion.spec.d.ts +1 -0
  101. package/types/components/Accordion/__tests__/index.spec.d.ts +1 -0
  102. package/types/components/Accordion/index.d.ts +38 -0
  103. package/types/components/Accordion/utils.d.ts +1 -0
  104. package/types/components/Button/IconButton.d.ts +1 -1
  105. package/types/components/Calendar/CalendarRowItem.d.ts +10 -0
  106. package/types/components/Calendar/StyledCalendar.d.ts +54 -0
  107. package/types/components/Calendar/__tests__/CalendarRowItem.spec.d.ts +1 -0
  108. package/types/components/Calendar/__tests__/helper.spec.d.ts +1 -0
  109. package/types/components/Calendar/__tests__/index.spec.d.ts +1 -0
  110. package/types/components/Calendar/helpers.d.ts +3 -0
  111. package/types/components/Calendar/index.d.ts +40 -0
  112. package/types/components/Collapse/index.d.ts +1 -1
  113. package/types/components/ContentNavigator/index.d.ts +5 -1
  114. package/types/components/Empty/StyledEmpty.d.ts +31 -0
  115. package/types/components/Empty/__tests__/index.spec.d.ts +1 -0
  116. package/types/components/Empty/index.d.ts +26 -0
  117. package/types/components/FAB/ActionGroup/StyledActionItem.d.ts +6 -1
  118. package/types/components/FAB/ActionGroup/index.d.ts +6 -1
  119. package/types/components/FAB/index.d.ts +1 -1
  120. package/types/components/Icon/IconList.d.ts +1 -1
  121. package/types/components/Icon/utils.d.ts +1 -1
  122. package/types/components/List/StyledBasicListItem.d.ts +3 -3
  123. package/types/components/List/StyledListItem.d.ts +3 -3
  124. package/types/components/RichTextEditor/EditorEvent.d.ts +3 -0
  125. package/types/components/RichTextEditor/EditorToolbar.d.ts +17 -0
  126. package/types/components/RichTextEditor/MentionList.d.ts +12 -0
  127. package/types/components/RichTextEditor/RichTextEditor.d.ts +65 -0
  128. package/types/components/RichTextEditor/StyledRichTextEditor.d.ts +16 -0
  129. package/types/components/RichTextEditor/StyledToolbar.d.ts +21 -0
  130. package/types/components/RichTextEditor/__tests__/EditorToolbar.spec.d.ts +1 -0
  131. package/types/components/RichTextEditor/__tests__/MentionList.spec.d.ts +1 -0
  132. package/types/components/RichTextEditor/__tests__/RichTextEditor.spec.d.ts +1 -0
  133. package/types/components/RichTextEditor/constants.d.ts +19 -0
  134. package/types/components/RichTextEditor/index.d.ts +5 -0
  135. package/types/components/RichTextEditor/utils/events.d.ts +8 -0
  136. package/types/components/RichTextEditor/utils/rnWebView.d.ts +7 -0
  137. package/types/components/SectionHeading/index.d.ts +2 -2
  138. package/types/components/Select/MultiSelect/OptionList.d.ts +1 -1
  139. package/types/components/Select/SingleSelect/OptionList.d.ts +1 -1
  140. package/types/components/Tag/StyledTag.d.ts +1 -1
  141. package/types/components/Tag/index.d.ts +1 -1
  142. package/types/components/Typography/Text/StyledText.d.ts +1 -1
  143. package/types/components/Typography/Text/index.d.ts +1 -1
  144. package/types/index.d.ts +5 -1
  145. package/types/theme/components/accordion.d.ts +13 -0
  146. package/types/theme/components/calendar.d.ts +26 -0
  147. package/types/theme/components/empty.d.ts +28 -0
  148. package/types/theme/components/fab.d.ts +1 -0
  149. package/types/theme/components/list.d.ts +1 -0
  150. package/types/theme/components/richTextEditor.d.ts +26 -0
  151. package/types/theme/components/tag.d.ts +8 -2
  152. package/types/theme/components/typography.d.ts +1 -0
  153. package/types/theme/global/colors.d.ts +4 -1
  154. package/types/theme/global/index.d.ts +4 -1
  155. package/types/theme/index.d.ts +8 -0
@@ -0,0 +1,69 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { View } from 'react-native';
3
+ import { emitter } from './EditorEvent';
4
+ import * as Events from './utils/events';
5
+
6
+ const isEmptyString = (s: string | null): boolean => !s || s.length === 0;
7
+
8
+ export interface MentionListProps {
9
+ /**
10
+ * Unique name used to communicate with webview, should be the same with the RichTextEditor component it used with
11
+ */
12
+ name: string;
13
+ /**
14
+ * Function used to render mention options
15
+ */
16
+ render: (
17
+ searchText: string,
18
+ onSelect: (id: string, name: string) => void
19
+ ) => JSX.Element;
20
+ }
21
+
22
+ const MentionList = ({ name: eventPrefix, render }: MentionListProps) => {
23
+ const [search, setSearch] = useState<string>('');
24
+ const [target, setTarget] = useState<string | null>(null);
25
+ const normalizeEventName = useCallback(
26
+ (event: string) => `${eventPrefix}/${event}`,
27
+ [eventPrefix]
28
+ );
29
+ useEffect(() => {
30
+ const removeMentionSearchListener = Events.on(
31
+ emitter,
32
+ normalizeEventName('mention-search'),
33
+ ({
34
+ search: nextSearch,
35
+ target: nextTarget,
36
+ }: {
37
+ search?: string;
38
+ target?: string;
39
+ }) => {
40
+ setSearch(nextSearch || '');
41
+ setTarget(nextTarget || null);
42
+ }
43
+ );
44
+
45
+ return () => {
46
+ removeMentionSearchListener();
47
+ };
48
+ }, []);
49
+
50
+ if (isEmptyString(search)) {
51
+ return null;
52
+ }
53
+
54
+ return (
55
+ <View>
56
+ {render(search, (id, name) => {
57
+ const data = {
58
+ id,
59
+ name,
60
+ target,
61
+ };
62
+
63
+ Events.emit(emitter, normalizeEventName('mention-apply'), data);
64
+ })}
65
+ </View>
66
+ );
67
+ };
68
+
69
+ export default MentionList;
@@ -0,0 +1,396 @@
1
+ import { useTheme } from '@emotion/react';
2
+ import { isEmptyContent } from 'hero-editor';
3
+ import * as heroEditorApp from 'hero-editor/dist/app.js';
4
+ import React, {
5
+ ComponentType,
6
+ forwardRef,
7
+ ReactElement,
8
+ Ref,
9
+ useCallback,
10
+ useEffect,
11
+ useImperativeHandle,
12
+ useMemo,
13
+ useRef,
14
+ useState,
15
+ } from 'react';
16
+
17
+ import {
18
+ Keyboard,
19
+ StyleProp,
20
+ TouchableWithoutFeedback,
21
+ ViewStyle,
22
+ } from 'react-native';
23
+ import { WebView } from 'react-native-webview';
24
+ import { isAndroid } from '../../utils/helpers';
25
+ import Icon from '../Icon';
26
+ import {
27
+ StyledAsteriskLabel,
28
+ StyledAsteriskLabelInsideTextInput,
29
+ StyledBorderBackDrop,
30
+ StyledContainer,
31
+ StyledError,
32
+ StyledErrorAndHelpTextContainer,
33
+ StyledErrorAndMaxLengthContainer,
34
+ StyledErrorContainer,
35
+ StyledHelperText,
36
+ StyledLabel,
37
+ StyledLabelContainer,
38
+ StyledLabelContainerInsideTextInput,
39
+ StyledLabelInsideTextInput,
40
+ StyledTextInputAndLabelContainer,
41
+ StyledTextInputContainer,
42
+ } from '../TextInput/StyledTextInput';
43
+ import { ToolbarEvents } from './constants';
44
+ import { emitter } from './EditorEvent';
45
+ import { StyledWebView } from './StyledRichTextEditor';
46
+ import * as Events from './utils/events';
47
+ import {
48
+ postMessage,
49
+ requestBlurEditor,
50
+ WebViewEventMessage,
51
+ } from './utils/rnWebView';
52
+
53
+ export interface RichTextEditorRef {
54
+ requestBlur: VoidFunction;
55
+ }
56
+
57
+ export type EditorValue = {
58
+ type: string;
59
+ children: any;
60
+ }[];
61
+
62
+ export interface RichTextEditorProps {
63
+ /**
64
+ * Error message
65
+ */
66
+ error?: string;
67
+ /**
68
+ * Field value
69
+ */
70
+ value?: EditorValue;
71
+ /**
72
+ * Unique name used to communicate with webview
73
+ */
74
+ name: string;
75
+ /**
76
+ * Callback function called when the field value changed
77
+ */
78
+ onChange: (data: EditorValue) => void;
79
+ /**
80
+ * Callback function called when the cursor position changed
81
+ */
82
+ onCursorChange?: (params: { position: { top: number } }) => void;
83
+ /**
84
+ * Field placeholder
85
+ */
86
+ placeholder?: string;
87
+ /**
88
+ * Additional styles
89
+ */
90
+ style?: StyleProp<ViewStyle>;
91
+ /**
92
+ * Field label
93
+ */
94
+ label: string;
95
+ /**
96
+ * Field helper text
97
+ */
98
+ helpText?: string;
99
+ /**
100
+ * Whether the input is required, if true, an asterisk will be appended to the label.
101
+ * */
102
+ required?: boolean;
103
+ /**
104
+ * Testing ID of the component
105
+ */
106
+ testID?: string;
107
+ /**
108
+ * Imperative ref to expose the component method
109
+ */
110
+ forwardedRef?: Ref<RichTextEditorRef>;
111
+ }
112
+
113
+ const noop = () => {};
114
+ const defaultValue: EditorValue = [
115
+ {
116
+ type: 'paragraph',
117
+ children: [{ text: '' }],
118
+ },
119
+ ];
120
+
121
+ const RichTextEditor: ComponentType<RichTextEditorProps> = ({
122
+ name,
123
+ placeholder = '',
124
+ onChange = noop,
125
+ onCursorChange = noop,
126
+ error = '',
127
+ style = {},
128
+ label,
129
+ helpText,
130
+ required,
131
+ testID,
132
+ forwardedRef,
133
+ value = defaultValue,
134
+ }: RichTextEditorProps): ReactElement => {
135
+ const theme = useTheme();
136
+
137
+ const webview = useRef<WebView | null>(null);
138
+ const [webviewHeight, setWebviewHeight] = useState<number | undefined>();
139
+
140
+ const [isFocused, setIsFocused] = useState(false);
141
+ const isEmptyValue = useMemo(() => isEmptyContent(value), [value]);
142
+ const variant = useMemo(() => {
143
+ if (error) {
144
+ return 'error';
145
+ }
146
+ if (isFocused) {
147
+ return 'focused';
148
+ }
149
+ if (isEmptyValue) {
150
+ return 'filled';
151
+ }
152
+ return 'default';
153
+ }, [isFocused, error, isEmptyValue]);
154
+
155
+ const normalizeEventName = (event: string) => `${name}/${event}`;
156
+ const postMessageToWebview = (message: WebViewEventMessage) => {
157
+ if (webview && webview.current) {
158
+ postMessage(webview.current, message);
159
+ }
160
+ };
161
+
162
+ useEffect(() => {
163
+ const removeFocusListener = Events.on(
164
+ emitter,
165
+ normalizeEventName('editor-focus'),
166
+ () => setIsFocused(true)
167
+ );
168
+
169
+ const removeBlurListener = Events.on(
170
+ emitter,
171
+ normalizeEventName('editor-blur'),
172
+ () => setIsFocused(false)
173
+ );
174
+
175
+ return () => {
176
+ removeFocusListener();
177
+ removeBlurListener();
178
+ };
179
+ }, []);
180
+
181
+ const html = useMemo(() => {
182
+ const initialValueString = JSON.stringify(value);
183
+
184
+ return `
185
+ <!DOCTYPE html>
186
+ <html>
187
+ <head>
188
+ <meta charset="utf-8">
189
+ <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
190
+ <style>
191
+ body {
192
+ margin: 0;
193
+ }
194
+ </style>
195
+ </head>
196
+ <body>
197
+ <div id="root"></div>
198
+ <script>
199
+ window.__editorConfigs = {
200
+ placeholder: "${placeholder}",
201
+ initialValue: ${initialValueString},
202
+ isAndroid: ${isAndroid ? 'true' : 'false'},
203
+ autoFocus: true,
204
+ style: {
205
+ padding: '0 !important',
206
+ fontSize: ${theme.__hd__.richTextEditor.fontSizes.editor}
207
+ }
208
+ };
209
+ ${heroEditorApp}
210
+ </script>
211
+ </body>
212
+ </html>
213
+ `;
214
+ }, []);
215
+
216
+ const requestBlur = useCallback(() => {
217
+ if (webview.current) {
218
+ requestBlurEditor(webview.current);
219
+ Keyboard.dismiss();
220
+ }
221
+ }, []);
222
+
223
+ useImperativeHandle(forwardedRef, () => ({ requestBlur }), [requestBlur]);
224
+
225
+ /* Forward events from Toolbar and MentionList to webview */
226
+ useEffect(() => {
227
+ const toolbarEventListenerRemovers = Object.values(ToolbarEvents).map(
228
+ eventName =>
229
+ Events.on(emitter, normalizeEventName(eventName), data => {
230
+ postMessageToWebview({
231
+ type: `@hero-editor/webview/${eventName}`,
232
+ data,
233
+ });
234
+ })
235
+ );
236
+
237
+ const removeMentionApplyListener = Events.on(
238
+ emitter,
239
+ normalizeEventName('mention-apply'),
240
+ data =>
241
+ postMessageToWebview({
242
+ type: '@hero-editor/webview/mention-apply',
243
+ data,
244
+ })
245
+ );
246
+
247
+ return () => {
248
+ removeMentionApplyListener();
249
+ toolbarEventListenerRemovers.forEach(remover => remover());
250
+ };
251
+ }, []);
252
+
253
+ const handleEditorLayoutEvent = useCallback((messageData: any) => {
254
+ const editorLayout = messageData
255
+ ? {
256
+ width: Number(messageData.width),
257
+ height: Number(messageData.height),
258
+ }
259
+ : undefined;
260
+
261
+ if (editorLayout) {
262
+ setWebviewHeight(editorLayout.height);
263
+ }
264
+ }, []);
265
+
266
+ /* Handle events from webview */
267
+ const onMessage = useCallback(
268
+ (event?: { nativeEvent?: { data?: string } }) => {
269
+ const message = event?.nativeEvent?.data
270
+ ? JSON.parse(event?.nativeEvent?.data)
271
+ : undefined;
272
+
273
+ const messageType = message?.type;
274
+
275
+ const messageData = message?.data;
276
+
277
+ switch (messageType) {
278
+ case '@hero-editor/webview/editor-focus':
279
+ Events.emit(emitter, normalizeEventName('editor-focus'), undefined);
280
+
281
+ break;
282
+ case '@hero-editor/webview/editor-blur':
283
+ Events.emit(emitter, normalizeEventName('editor-blur'), undefined);
284
+
285
+ break;
286
+ case '@hero-editor/webview/mention-search':
287
+ Events.emit(
288
+ emitter,
289
+ normalizeEventName('mention-search'),
290
+ messageData
291
+ );
292
+
293
+ break;
294
+ case '@hero-editor/webview/editor-change':
295
+ if (messageData) {
296
+ onChange(messageData.value);
297
+ }
298
+
299
+ break;
300
+ case '@hero-editor/webview/cursor-change':
301
+ onCursorChange(messageData);
302
+
303
+ break;
304
+
305
+ case '@hero-editor/webview/editor-layout':
306
+ handleEditorLayoutEvent(messageData);
307
+ break;
308
+
309
+ default:
310
+ break;
311
+ }
312
+ },
313
+ []
314
+ );
315
+
316
+ return (
317
+ <StyledContainer style={style} testID={testID}>
318
+ <StyledTextInputContainer>
319
+ <StyledBorderBackDrop themeVariant={variant} />
320
+ {(isFocused || (label && !isEmptyValue)) && (
321
+ <StyledLabelContainer pointerEvents="none">
322
+ {required && (
323
+ <StyledAsteriskLabel themeVariant={variant} fontSize="small">
324
+ *
325
+ </StyledAsteriskLabel>
326
+ )}
327
+ {!!label && (
328
+ <StyledLabel
329
+ testID="input-label"
330
+ fontSize="small"
331
+ themeVariant={variant}
332
+ >
333
+ {label}
334
+ </StyledLabel>
335
+ )}
336
+ </StyledLabelContainer>
337
+ )}
338
+ <StyledTextInputAndLabelContainer>
339
+ {!isFocused && isEmptyValue && (
340
+ <StyledLabelContainerInsideTextInput pointerEvents="none">
341
+ {required && (
342
+ <StyledAsteriskLabelInsideTextInput themeVariant={variant}>
343
+ *
344
+ </StyledAsteriskLabelInsideTextInput>
345
+ )}
346
+ {!!label && (
347
+ <StyledLabelInsideTextInput
348
+ testID="input-label"
349
+ fontSize="medium"
350
+ themeVariant={variant}
351
+ >
352
+ {label}
353
+ </StyledLabelInsideTextInput>
354
+ )}
355
+ </StyledLabelContainerInsideTextInput>
356
+ )}
357
+ <TouchableWithoutFeedback onPress={e => e.stopPropagation()}>
358
+ <StyledWebView
359
+ ref={webview}
360
+ testID="webview"
361
+ style={style}
362
+ originWhitelist={['*']}
363
+ source={{ html }}
364
+ onMessage={onMessage}
365
+ scrollEnabled={false}
366
+ hideKeyboardAccessoryView
367
+ keyboardDisplayRequiresUserAction={false}
368
+ height={webviewHeight}
369
+ />
370
+ </TouchableWithoutFeedback>
371
+ </StyledTextInputAndLabelContainer>
372
+ </StyledTextInputContainer>
373
+ <StyledErrorAndHelpTextContainer>
374
+ <StyledErrorAndMaxLengthContainer>
375
+ {!!error && (
376
+ <StyledErrorContainer>
377
+ <Icon
378
+ testID="input-error-icon"
379
+ icon="circle-info"
380
+ size="xsmall"
381
+ intent="danger"
382
+ />
383
+ <StyledError testID="input-error-message">{error}</StyledError>
384
+ </StyledErrorContainer>
385
+ )}
386
+ </StyledErrorAndMaxLengthContainer>
387
+ {!!helpText && <StyledHelperText>{helpText}</StyledHelperText>}
388
+ </StyledErrorAndHelpTextContainer>
389
+ </StyledContainer>
390
+ );
391
+ };
392
+
393
+ export default forwardRef<
394
+ RichTextEditorRef,
395
+ Omit<RichTextEditorProps, 'forwardedRef'>
396
+ >((props, ref) => <RichTextEditor {...props} forwardedRef={ref} />);
@@ -0,0 +1,20 @@
1
+ import styled from '@emotion/native';
2
+ import { View } from 'react-native';
3
+ import { WebView } from 'react-native-webview';
4
+
5
+ export const StyledWrapper = styled(View)(({ theme }) => ({
6
+ marginBottom: theme.__hd__.richTextEditor.space.wrapperMarginBottom,
7
+ }));
8
+
9
+ export const StyledWebView = styled(WebView)<{
10
+ height: number | undefined;
11
+ }>(({ height, theme }) => ({
12
+ height,
13
+ minHeight: theme.__hd__.richTextEditor.sizes.editorMinHeight,
14
+ backgroundColor: 'transparent',
15
+ textAlignVertical: 'center',
16
+ fontSize: theme.__hd__.textInput.fontSizes.text,
17
+ alignSelf: 'stretch',
18
+ flexGrow: 2,
19
+ marginHorizontal: theme.__hd__.textInput.space.inputHorizontalMargin,
20
+ }));
@@ -0,0 +1,32 @@
1
+ import styled from '@emotion/native';
2
+ import { StyleSheet, TouchableOpacity, View } from 'react-native';
3
+
4
+ export const StyledToolbarButton = styled(TouchableOpacity)<{
5
+ selected: boolean;
6
+ }>(({ theme, selected }) => ({
7
+ width: theme.__hd__.richTextEditor.sizes.toolbarButtonSize,
8
+ height: theme.__hd__.richTextEditor.sizes.toolbarButtonSize,
9
+ alignItems: 'center',
10
+ justifyContent: 'center',
11
+ backgroundColor: selected
12
+ ? theme.__hd__.richTextEditor.colors.toolbarButtonSelectedBackground
13
+ : undefined,
14
+ }));
15
+
16
+ export const StyledToolbar = styled(View)(({ theme }) => ({
17
+ flexDirection: 'row',
18
+ alignItems: 'center',
19
+ borderTopWidth: StyleSheet.hairlineWidth,
20
+ borderTopColor: theme.__hd__.richTextEditor.colors.toolbarBorderColor,
21
+ backgroundColor: theme.__hd__.richTextEditor.colors.toolbarBackgroundColor,
22
+ paddingHorizontal: theme.__hd__.richTextEditor.space.toolbarHorizontalPadding,
23
+ }));
24
+
25
+ export const StyledSeparator = styled(View)(({ theme }) => ({
26
+ width: theme.__hd__.richTextEditor.sizes.toolbarSeparatorWidth,
27
+ height: theme.__hd__.richTextEditor.sizes.toolbarSeparatorHeight,
28
+ flexDirection: 'row',
29
+ alignItems: 'center',
30
+ marginHorizontal: theme.space.small,
31
+ backgroundColor: theme.colors.outline,
32
+ }));
@@ -0,0 +1,130 @@
1
+ import React from 'react';
2
+ import {
3
+ act,
4
+ fireEvent,
5
+ RenderAPI,
6
+ waitFor,
7
+ } from '@testing-library/react-native';
8
+ import renderWithTheme from '../../../testHelpers/renderWithTheme';
9
+ import EditorToolbar from '../EditorToolbar';
10
+ import { emitter as editorEventEmmitor } from '../EditorEvent';
11
+ import { theme } from '../../../index';
12
+ import * as Events from '../utils/events';
13
+
14
+ describe('EditorToolbar', () => {
15
+ it('should not render toolbar when the editor is not focused', () => {
16
+ const wrapper = renderWithTheme(<EditorToolbar name="toolbar" />);
17
+ expect(wrapper.toJSON()).toBeNull();
18
+ });
19
+
20
+ describe('when the editor is focused', () => {
21
+ let wrapper: RenderAPI;
22
+
23
+ beforeEach(async () => {
24
+ wrapper = renderWithTheme(
25
+ <EditorToolbar name="toolbar" testID="toolbar" />
26
+ );
27
+ act(() => {
28
+ editorEventEmmitor.emit('toolbar/editor-focus');
29
+ });
30
+ await waitFor(() => wrapper.getByTestId('toolbar'));
31
+ });
32
+
33
+ it('should render toolbar', async () => {
34
+ expect(wrapper.toJSON()).toMatchSnapshot();
35
+
36
+ [
37
+ 'format-bold',
38
+ 'format-italic',
39
+ 'format-underlined',
40
+ 'format-list-bulleted',
41
+ 'format-list-numbered',
42
+ 'format-heading1',
43
+ 'format-heading2',
44
+ ].forEach(testId => {
45
+ expect(wrapper.queryAllByTestId(testId)).toHaveLength(1);
46
+ });
47
+ });
48
+
49
+ it('should hide toolbar when blur', async () => {
50
+ act(() => {
51
+ editorEventEmmitor.emit('toolbar/editor-blur');
52
+ });
53
+ await waitFor(() => wrapper.queryAllByTestId('toolbar').length === 0);
54
+ expect(wrapper.queryAllByTestId('toolbar')).toHaveLength(0);
55
+ });
56
+
57
+ describe("should change button's background color when pressing", () => {
58
+ it('should send event and highlight buttons correctly', () => {
59
+ const emmitedEvents: string[] = [];
60
+
61
+ const eventNameAndTestIDArray = [
62
+ {
63
+ eventName: 'toolbar/bold',
64
+ testID: 'format-bold',
65
+ },
66
+ {
67
+ eventName: 'toolbar/italic',
68
+ testID: 'format-italic',
69
+ },
70
+ {
71
+ eventName: 'toolbar/underline',
72
+ testID: 'format-underlined',
73
+ },
74
+ {
75
+ eventName: 'toolbar/bulleted-list',
76
+ testID: 'format-list-bulleted',
77
+ },
78
+ {
79
+ eventName: 'toolbar/numbered-list',
80
+ testID: 'format-list-numbered',
81
+ },
82
+ {
83
+ eventName: 'toolbar/heading-one',
84
+ testID: 'format-heading1',
85
+ },
86
+ {
87
+ eventName: 'toolbar/heading-two',
88
+ testID: 'format-heading2',
89
+ },
90
+ ];
91
+
92
+ eventNameAndTestIDArray.forEach(({ eventName, testID }) => {
93
+ Events.on(editorEventEmmitor, eventName, () => {
94
+ emmitedEvents.push(testID);
95
+ });
96
+
97
+ expect(wrapper.getByTestId(testID)).toHaveStyle({
98
+ backgroundColor: undefined,
99
+ });
100
+
101
+ fireEvent(wrapper.getByTestId(testID), 'press');
102
+ expect(wrapper.getByTestId(testID)).toHaveStyle({
103
+ backgroundColor: theme.colors.outline,
104
+ });
105
+ });
106
+
107
+ const standaloneButtonTestIDs = [
108
+ 'format-list-bulleted',
109
+ 'format-list-numbered',
110
+ 'format-heading1',
111
+ ];
112
+ standaloneButtonTestIDs.forEach(testID => {
113
+ expect(wrapper.getByTestId(testID)).toHaveStyle({
114
+ backgroundColor: undefined,
115
+ });
116
+ });
117
+
118
+ expect(emmitedEvents).toMatchObject([
119
+ 'format-bold',
120
+ 'format-italic',
121
+ 'format-underlined',
122
+ 'format-list-bulleted',
123
+ 'format-list-numbered',
124
+ 'format-heading1',
125
+ 'format-heading2',
126
+ ]);
127
+ });
128
+ });
129
+ });
130
+ });