@hero-design/rn 7.12.1 → 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 (185) 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 +35840 -16325
  6. package/lib/assets/fonts/hero-icons.ttf +0 -0
  7. package/lib/index.js +35847 -16327
  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/Button.tsx +64 -60
  21. package/src/components/Button/IconButton.tsx +1 -1
  22. package/src/components/Button/StyledButton.tsx +4 -6
  23. package/src/components/Button/__tests__/StyledButton.spec.tsx +11 -4
  24. package/src/components/Button/__tests__/__snapshots__/StyledButton.spec.tsx.snap +312 -78
  25. package/src/components/Calendar/CalendarRowItem.tsx +54 -0
  26. package/src/components/Calendar/StyledCalendar.tsx +76 -0
  27. package/src/components/Calendar/__tests__/CalendarRowItem.spec.tsx +76 -0
  28. package/src/components/Calendar/__tests__/__snapshots__/CalendarRowItem.spec.tsx.snap +411 -0
  29. package/src/components/Calendar/__tests__/helper.spec.ts +50 -0
  30. package/src/components/Calendar/__tests__/index.spec.tsx +99 -0
  31. package/src/components/Calendar/helpers.ts +29 -0
  32. package/src/components/Calendar/index.tsx +217 -0
  33. package/src/components/Collapse/index.tsx +13 -15
  34. package/src/components/ContentNavigator/index.tsx +6 -0
  35. package/src/components/DatePicker/DatePickerAndroid.tsx +59 -0
  36. package/src/components/DatePicker/DatePickerIOS.tsx +87 -0
  37. package/src/components/DatePicker/StyledDatePicker.tsx +8 -0
  38. package/src/components/DatePicker/__tests__/DatePicker.spec.tsx +34 -0
  39. package/src/components/DatePicker/__tests__/DatePickerAndroid.spec.tsx +39 -0
  40. package/src/components/DatePicker/__tests__/DatePickerIOS.spec.tsx +46 -0
  41. package/src/components/DatePicker/__tests__/__snapshots__/DatePickerAndroid.spec.tsx.snap +199 -0
  42. package/src/components/DatePicker/__tests__/__snapshots__/DatePickerIOS.spec.tsx.snap +513 -0
  43. package/src/components/DatePicker/index.tsx +15 -0
  44. package/src/components/DatePicker/types.ts +49 -0
  45. package/src/components/Empty/StyledEmpty.tsx +47 -0
  46. package/src/components/Empty/__tests__/__snapshots__/index.spec.tsx.snap +66 -0
  47. package/src/components/Empty/__tests__/index.spec.tsx +17 -0
  48. package/src/components/Empty/index.tsx +53 -0
  49. package/src/components/FAB/ActionGroup/ActionItem.tsx +6 -2
  50. package/src/components/FAB/ActionGroup/StyledActionGroup.tsx +1 -0
  51. package/src/components/FAB/ActionGroup/StyledActionItem.tsx +7 -1
  52. package/src/components/FAB/ActionGroup/__tests__/__snapshots__/index.spec.tsx.snap +84 -22
  53. package/src/components/FAB/ActionGroup/index.tsx +8 -1
  54. package/src/components/Icon/HeroIcon/selection.json +1 -1
  55. package/src/components/Icon/IconList.ts +13 -0
  56. package/src/components/List/BasicListItem.tsx +44 -34
  57. package/src/components/List/ListItem.tsx +67 -58
  58. package/src/components/List/StyledBasicListItem.tsx +2 -3
  59. package/src/components/List/StyledListItem.tsx +2 -2
  60. package/src/components/List/__tests__/StyledBasicListItem.spec.tsx +5 -2
  61. package/src/components/List/__tests__/StyledListItem.spec.tsx +4 -1
  62. package/src/components/List/__tests__/__snapshots__/BasicListItem.spec.tsx.snap +15 -10
  63. package/src/components/List/__tests__/__snapshots__/ListItem.spec.tsx.snap +52 -32
  64. package/src/components/List/__tests__/__snapshots__/StyledBasicListItem.spec.tsx.snap +128 -48
  65. package/src/components/List/__tests__/__snapshots__/StyledListItem.spec.tsx.snap +132 -52
  66. package/src/components/RichTextEditor/EditorEvent.ts +7 -0
  67. package/src/components/RichTextEditor/EditorToolbar.tsx +220 -0
  68. package/src/components/RichTextEditor/MentionList.tsx +69 -0
  69. package/src/components/RichTextEditor/RichTextEditor.tsx +396 -0
  70. package/src/components/RichTextEditor/StyledRichTextEditor.ts +20 -0
  71. package/src/components/RichTextEditor/StyledToolbar.ts +32 -0
  72. package/src/components/RichTextEditor/__tests__/EditorToolbar.spec.tsx +130 -0
  73. package/src/components/RichTextEditor/__tests__/MentionList.spec.tsx +109 -0
  74. package/src/components/RichTextEditor/__tests__/RichTextEditor.spec.tsx +245 -0
  75. package/src/components/RichTextEditor/__tests__/__snapshots__/EditorToolbar.spec.tsx.snap +324 -0
  76. package/src/components/RichTextEditor/__tests__/__snapshots__/MentionList.spec.tsx.snap +45 -0
  77. package/src/components/RichTextEditor/__tests__/__snapshots__/RichTextEditor.spec.tsx.snap +526 -0
  78. package/src/components/RichTextEditor/constants.ts +20 -0
  79. package/src/components/RichTextEditor/hero-editor.d.ts +8 -0
  80. package/src/components/RichTextEditor/index.tsx +8 -0
  81. package/src/components/RichTextEditor/utils/events.ts +31 -0
  82. package/src/components/RichTextEditor/utils/rnWebView.ts +19 -0
  83. package/src/components/SectionHeading/__tests__/__snapshots__/index.spec.tsx.snap +77 -0
  84. package/src/components/SectionHeading/__tests__/index.spec.tsx +14 -0
  85. package/src/components/SectionHeading/index.tsx +16 -9
  86. package/src/components/Tag/StyledTag.tsx +12 -2
  87. package/src/components/Tag/__tests__/Tag.spec.tsx +35 -8
  88. package/src/components/Tag/__tests__/__snapshots__/Tag.spec.tsx.snap +118 -4
  89. package/src/components/Tag/index.tsx +9 -2
  90. package/src/components/TextInput/__tests__/__snapshots__/StyledTextInput.spec.tsx.snap +1 -0
  91. package/src/components/TimePicker/TimePickerIOS.tsx +1 -1
  92. package/src/components/TimePicker/types.ts +1 -1
  93. package/src/components/Typography/Text/StyledText.tsx +2 -1
  94. package/src/components/Typography/Text/__tests__/StyledText.spec.tsx +1 -0
  95. package/src/components/Typography/Text/__tests__/__snapshots__/StyledText.spec.tsx.snap +22 -0
  96. package/src/components/Typography/Text/index.tsx +2 -1
  97. package/src/index.ts +10 -0
  98. package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +119 -4
  99. package/src/theme/components/accordion.ts +19 -0
  100. package/src/theme/components/button.ts +12 -0
  101. package/src/theme/components/calendar.ts +34 -0
  102. package/src/theme/components/card.ts +1 -1
  103. package/src/theme/components/datePicker.ts +11 -0
  104. package/src/theme/components/empty.ts +38 -0
  105. package/src/theme/components/fab.ts +4 -3
  106. package/src/theme/components/list.ts +1 -0
  107. package/src/theme/components/pinInput.ts +1 -1
  108. package/src/theme/components/richTextEditor.ts +34 -0
  109. package/src/theme/components/tag.ts +8 -2
  110. package/src/theme/components/typography.ts +1 -0
  111. package/src/theme/global/borders.ts +6 -6
  112. package/src/theme/global/colors.ts +5 -1
  113. package/src/theme/index.ts +15 -0
  114. package/testUtils/setup.tsx +17 -0
  115. package/types/components/Accordion/AccordionItem.d.ts +14 -0
  116. package/types/components/Accordion/StyledAccordion.d.ts +32 -0
  117. package/types/components/Accordion/__tests__/AccordionItem.spec.d.ts +1 -0
  118. package/types/components/Accordion/__tests__/StyledAccordion.spec.d.ts +1 -0
  119. package/types/components/Accordion/__tests__/index.spec.d.ts +1 -0
  120. package/types/components/Accordion/index.d.ts +38 -0
  121. package/types/components/Accordion/utils.d.ts +1 -0
  122. package/types/components/Button/IconButton.d.ts +1 -1
  123. package/types/components/Button/StyledButton.d.ts +3 -3
  124. package/types/components/Calendar/CalendarRowItem.d.ts +10 -0
  125. package/types/components/Calendar/StyledCalendar.d.ts +54 -0
  126. package/types/components/Calendar/__tests__/CalendarRowItem.spec.d.ts +1 -0
  127. package/types/components/Calendar/__tests__/helper.spec.d.ts +1 -0
  128. package/types/components/Calendar/__tests__/index.spec.d.ts +1 -0
  129. package/types/components/Calendar/helpers.d.ts +3 -0
  130. package/types/components/Calendar/index.d.ts +40 -0
  131. package/types/components/Collapse/index.d.ts +1 -1
  132. package/types/components/ContentNavigator/index.d.ts +5 -1
  133. package/types/components/DatePicker/DatePickerAndroid.d.ts +3 -0
  134. package/types/components/DatePicker/DatePickerIOS.d.ts +3 -0
  135. package/types/components/DatePicker/StyledDatePicker.d.ts +8 -0
  136. package/types/components/DatePicker/__tests__/DatePicker.spec.d.ts +1 -0
  137. package/types/components/DatePicker/__tests__/DatePickerAndroid.spec.d.ts +1 -0
  138. package/types/components/DatePicker/__tests__/DatePickerIOS.spec.d.ts +1 -0
  139. package/types/components/DatePicker/index.d.ts +3 -0
  140. package/types/components/DatePicker/types.d.ts +48 -0
  141. package/types/components/Empty/StyledEmpty.d.ts +31 -0
  142. package/types/components/Empty/__tests__/index.spec.d.ts +1 -0
  143. package/types/components/Empty/index.d.ts +26 -0
  144. package/types/components/FAB/ActionGroup/StyledActionItem.d.ts +6 -1
  145. package/types/components/FAB/ActionGroup/index.d.ts +6 -1
  146. package/types/components/FAB/index.d.ts +1 -1
  147. package/types/components/Icon/IconList.d.ts +1 -1
  148. package/types/components/Icon/utils.d.ts +1 -1
  149. package/types/components/List/StyledBasicListItem.d.ts +3 -3
  150. package/types/components/List/StyledListItem.d.ts +3 -3
  151. package/types/components/RichTextEditor/EditorEvent.d.ts +3 -0
  152. package/types/components/RichTextEditor/EditorToolbar.d.ts +17 -0
  153. package/types/components/RichTextEditor/MentionList.d.ts +12 -0
  154. package/types/components/RichTextEditor/RichTextEditor.d.ts +65 -0
  155. package/types/components/RichTextEditor/StyledRichTextEditor.d.ts +16 -0
  156. package/types/components/RichTextEditor/StyledToolbar.d.ts +21 -0
  157. package/types/components/RichTextEditor/__tests__/EditorToolbar.spec.d.ts +1 -0
  158. package/types/components/RichTextEditor/__tests__/MentionList.spec.d.ts +1 -0
  159. package/types/components/RichTextEditor/__tests__/RichTextEditor.spec.d.ts +1 -0
  160. package/types/components/RichTextEditor/constants.d.ts +19 -0
  161. package/types/components/RichTextEditor/index.d.ts +5 -0
  162. package/types/components/RichTextEditor/utils/events.d.ts +8 -0
  163. package/types/components/RichTextEditor/utils/rnWebView.d.ts +7 -0
  164. package/types/components/SectionHeading/index.d.ts +2 -2
  165. package/types/components/Select/MultiSelect/OptionList.d.ts +1 -1
  166. package/types/components/Select/SingleSelect/OptionList.d.ts +1 -1
  167. package/types/components/Tag/StyledTag.d.ts +1 -1
  168. package/types/components/Tag/index.d.ts +1 -1
  169. package/types/components/TimePicker/types.d.ts +1 -1
  170. package/types/components/Typography/Text/StyledText.d.ts +1 -1
  171. package/types/components/Typography/Text/index.d.ts +1 -1
  172. package/types/index.d.ts +6 -1
  173. package/types/theme/components/accordion.d.ts +13 -0
  174. package/types/theme/components/button.d.ts +12 -0
  175. package/types/theme/components/calendar.d.ts +26 -0
  176. package/types/theme/components/datePicker.d.ts +6 -0
  177. package/types/theme/components/empty.d.ts +28 -0
  178. package/types/theme/components/fab.d.ts +1 -0
  179. package/types/theme/components/list.d.ts +1 -0
  180. package/types/theme/components/richTextEditor.d.ts +26 -0
  181. package/types/theme/components/tag.d.ts +8 -2
  182. package/types/theme/components/typography.d.ts +1 -0
  183. package/types/theme/global/colors.d.ts +5 -1
  184. package/types/theme/global/index.d.ts +5 -1
  185. package/types/theme/index.d.ts +10 -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
+ });