@hero-design/rn-work-uikit 1.4.0 → 1.6.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.
- package/CHANGELOG.md +27 -0
- package/lib/index.js +870 -639
- package/package.json +2 -2
- package/src/components/DatePicker/__tests__/__snapshots__/index.spec.tsx.snap +36 -3
- package/src/components/FormGroup/__tests__/__snapshots__/index.spec.tsx.snap +26 -3
- package/src/components/FormGroup/__tests__/index.spec.tsx +143 -3
- package/src/components/FormGroup/index.tsx +16 -7
- package/src/components/RichTextEditor/RichTextEditor.tsx +15 -24
- package/src/components/RichTextEditor/RichTextEditorInput.tsx +17 -11
- package/src/components/RichTextEditor/StyledRichTextEditor.tsx +10 -7
- package/src/components/RichTextEditor/__tests__/RichTextEditor.spec.tsx +81 -0
- package/src/components/RichTextEditor/index.tsx +2 -4
- package/src/components/RichTextEditor/types.ts +12 -1
- package/src/components/Select/__tests__/__snapshots__/index.spec.tsx.snap +24 -2
- package/src/components/Select/index.tsx +11 -10
- package/src/components/TextInput/Group/__tests__/__snapshots__/index.spec.tsx.snap +3 -3
- package/src/components/TextInput/Group/index.tsx +6 -1
- package/src/components/TextInput/StyledTextInput.tsx +3 -3
- package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +17 -17
- package/src/components/TextInput/index.tsx +2 -2
- package/src/components/TextInput/types.ts +1 -1
- package/src/index.ts +2 -1
- package/src/utils/hooks.ts +10 -0
- package/stats/1.6.0/rn-work-uikit-stats.html +4844 -0
- package/testUtils/setup.tsx +19 -1
- package/src/components/RichTextEditor/__mocks__/hero-editor.js +0 -3
- package/stats/1.4.0/rn-work-uikit-stats.html +0 -4844
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hero-design/rn-work-uikit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@emotion/native": "^11.9.3",
|
|
24
24
|
"@emotion/primitives-core": "11.0.0",
|
|
25
25
|
"@emotion/react": "^11.9.3",
|
|
26
|
-
"@hero-design/rn": "^8.103.
|
|
26
|
+
"@hero-design/rn": "^8.103.7",
|
|
27
27
|
"hero-editor": "^1.15.5"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
@@ -4,6 +4,17 @@ exports[`DatePicker renders correctly (snapshot) 1`] = `
|
|
|
4
4
|
<TouchableOpacity
|
|
5
5
|
disabled={false}
|
|
6
6
|
onPress={[Function]}
|
|
7
|
+
style={
|
|
8
|
+
[
|
|
9
|
+
[
|
|
10
|
+
{},
|
|
11
|
+
],
|
|
12
|
+
undefined,
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
testID="date-picker-ios-touchable-opacity"
|
|
16
|
+
themeGroupStyleEnabled={false}
|
|
17
|
+
themeHasError={false}
|
|
7
18
|
>
|
|
8
19
|
<View
|
|
9
20
|
pointerEvents="none"
|
|
@@ -32,8 +43,8 @@ exports[`DatePicker renders correctly (snapshot) 1`] = `
|
|
|
32
43
|
]
|
|
33
44
|
}
|
|
34
45
|
themeFocused={false}
|
|
46
|
+
themeGroupStyleEnabled={false}
|
|
35
47
|
themeHasError={false}
|
|
36
|
-
themeUseGroupStyleEnabled={false}
|
|
37
48
|
>
|
|
38
49
|
<View
|
|
39
50
|
pointerEvents="none"
|
|
@@ -643,6 +654,17 @@ exports[`Dialog renders DatePickerAndroid when OS is android 1`] = `
|
|
|
643
654
|
<TouchableOpacity
|
|
644
655
|
disabled={false}
|
|
645
656
|
onPress={[Function]}
|
|
657
|
+
style={
|
|
658
|
+
[
|
|
659
|
+
[
|
|
660
|
+
{},
|
|
661
|
+
],
|
|
662
|
+
undefined,
|
|
663
|
+
]
|
|
664
|
+
}
|
|
665
|
+
testID="date-picker-android-touchable-opacity"
|
|
666
|
+
themeGroupStyleEnabled={false}
|
|
667
|
+
themeHasError={false}
|
|
646
668
|
>
|
|
647
669
|
<View
|
|
648
670
|
pointerEvents="none"
|
|
@@ -671,8 +693,8 @@ exports[`Dialog renders DatePickerAndroid when OS is android 1`] = `
|
|
|
671
693
|
]
|
|
672
694
|
}
|
|
673
695
|
themeFocused={false}
|
|
696
|
+
themeGroupStyleEnabled={false}
|
|
674
697
|
themeHasError={false}
|
|
675
|
-
themeUseGroupStyleEnabled={false}
|
|
676
698
|
>
|
|
677
699
|
<View
|
|
678
700
|
pointerEvents="none"
|
|
@@ -966,6 +988,17 @@ exports[`Dialog renders DatePickerIOS when OS is iOS 1`] = `
|
|
|
966
988
|
<TouchableOpacity
|
|
967
989
|
disabled={false}
|
|
968
990
|
onPress={[Function]}
|
|
991
|
+
style={
|
|
992
|
+
[
|
|
993
|
+
[
|
|
994
|
+
{},
|
|
995
|
+
],
|
|
996
|
+
undefined,
|
|
997
|
+
]
|
|
998
|
+
}
|
|
999
|
+
testID="date-picker-ios-touchable-opacity"
|
|
1000
|
+
themeGroupStyleEnabled={false}
|
|
1001
|
+
themeHasError={false}
|
|
969
1002
|
>
|
|
970
1003
|
<View
|
|
971
1004
|
pointerEvents="none"
|
|
@@ -994,8 +1027,8 @@ exports[`Dialog renders DatePickerIOS when OS is iOS 1`] = `
|
|
|
994
1027
|
]
|
|
995
1028
|
}
|
|
996
1029
|
themeFocused={false}
|
|
1030
|
+
themeGroupStyleEnabled={false}
|
|
997
1031
|
themeHasError={false}
|
|
998
|
-
themeUseGroupStyleEnabled={false}
|
|
999
1032
|
>
|
|
1000
1033
|
<View
|
|
1001
1034
|
pointerEvents="none"
|
|
@@ -37,8 +37,8 @@ exports[`FormGroup should render: xxx 1`] = `
|
|
|
37
37
|
]
|
|
38
38
|
}
|
|
39
39
|
themeFocused={false}
|
|
40
|
+
themeGroupStyleEnabled={true}
|
|
40
41
|
themeHasError={false}
|
|
41
|
-
themeUseGroupStyleEnabled={true}
|
|
42
42
|
>
|
|
43
43
|
<View
|
|
44
44
|
pointerEvents="none"
|
|
@@ -199,6 +199,14 @@ exports[`FormGroup should render: xxx 1`] = `
|
|
|
199
199
|
}
|
|
200
200
|
allowFontScaling={false}
|
|
201
201
|
editable={true}
|
|
202
|
+
inputProps={
|
|
203
|
+
{
|
|
204
|
+
"textStyle": {
|
|
205
|
+
"borderBottomLeftRadius": 0,
|
|
206
|
+
"borderBottomRightRadius": 0,
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
}
|
|
202
210
|
onBlur={[Function]}
|
|
203
211
|
onChangeText={[Function]}
|
|
204
212
|
onFocus={[Function]}
|
|
@@ -290,8 +298,8 @@ exports[`FormGroup should render: xxx 1`] = `
|
|
|
290
298
|
}
|
|
291
299
|
testID="text-input-2"
|
|
292
300
|
themeFocused={false}
|
|
301
|
+
themeGroupStyleEnabled={true}
|
|
293
302
|
themeHasError={true}
|
|
294
|
-
themeUseGroupStyleEnabled={true}
|
|
295
303
|
>
|
|
296
304
|
<View
|
|
297
305
|
pointerEvents="none"
|
|
@@ -480,6 +488,13 @@ exports[`FormGroup should render: xxx 1`] = `
|
|
|
480
488
|
}
|
|
481
489
|
allowFontScaling={false}
|
|
482
490
|
editable={true}
|
|
491
|
+
inputProps={
|
|
492
|
+
{
|
|
493
|
+
"textStyle": {
|
|
494
|
+
"borderRadius": 0,
|
|
495
|
+
},
|
|
496
|
+
}
|
|
497
|
+
}
|
|
483
498
|
onBlur={[Function]}
|
|
484
499
|
onChangeText={[Function]}
|
|
485
500
|
onFocus={[Function]}
|
|
@@ -650,8 +665,8 @@ exports[`FormGroup should render: xxx 1`] = `
|
|
|
650
665
|
]
|
|
651
666
|
}
|
|
652
667
|
themeFocused={false}
|
|
668
|
+
themeGroupStyleEnabled={true}
|
|
653
669
|
themeHasError={false}
|
|
654
|
-
themeUseGroupStyleEnabled={true}
|
|
655
670
|
>
|
|
656
671
|
<View
|
|
657
672
|
pointerEvents="none"
|
|
@@ -812,6 +827,14 @@ exports[`FormGroup should render: xxx 1`] = `
|
|
|
812
827
|
}
|
|
813
828
|
allowFontScaling={false}
|
|
814
829
|
editable={true}
|
|
830
|
+
inputProps={
|
|
831
|
+
{
|
|
832
|
+
"textStyle": {
|
|
833
|
+
"borderTopLeftRadius": 0,
|
|
834
|
+
"borderTopRightRadius": 0,
|
|
835
|
+
},
|
|
836
|
+
}
|
|
837
|
+
}
|
|
815
838
|
onBlur={[Function]}
|
|
816
839
|
onChangeText={[Function]}
|
|
817
840
|
onFocus={[Function]}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { within } from '@testing-library/react-native';
|
|
3
|
-
import renderWithTheme from '../../../../testUtils/renderWithTheme';
|
|
4
|
-
import FormGroup from '..';
|
|
5
|
-
import TextInput from '../../TextInput';
|
|
6
3
|
import theme from '../../../theme';
|
|
4
|
+
import TextInput from '../../TextInput';
|
|
5
|
+
import Select from '../../Select';
|
|
6
|
+
import DatePicker from '../../DatePicker';
|
|
7
|
+
import TimePicker from '../../TimePicker';
|
|
8
|
+
import RichTextEditor from '../../RichTextEditor';
|
|
9
|
+
import FormGroup from '..';
|
|
10
|
+
import renderWithTheme from '../../../../testUtils/renderWithTheme';
|
|
11
|
+
import { noop } from '../../../utils/functions';
|
|
7
12
|
|
|
8
13
|
describe('FormGroup', () => {
|
|
9
14
|
it('should render', () => {
|
|
@@ -176,4 +181,139 @@ describe('FormGroup', () => {
|
|
|
176
181
|
);
|
|
177
182
|
expect(borderKeys).toHaveLength(0);
|
|
178
183
|
});
|
|
184
|
+
|
|
185
|
+
it('renders all component types (TextInput, Select, Select.Multi, DatePicker, TimePicker, RichTextEditor) with correct styles', () => {
|
|
186
|
+
const { getByTestId } = renderWithTheme(
|
|
187
|
+
<FormGroup>
|
|
188
|
+
<TextInput
|
|
189
|
+
label="Text Input"
|
|
190
|
+
value="Text Input"
|
|
191
|
+
testID="mixed-text-input"
|
|
192
|
+
/>
|
|
193
|
+
<DatePicker
|
|
194
|
+
label="Date Picker"
|
|
195
|
+
value={new Date('1995-12-17T00:00:00.000Z')}
|
|
196
|
+
testID="mixed-date-picker"
|
|
197
|
+
onChange={noop}
|
|
198
|
+
confirmLabel="Confirm"
|
|
199
|
+
/>
|
|
200
|
+
<TimePicker
|
|
201
|
+
label="Time Picker"
|
|
202
|
+
value={new Date('December 17, 1995 03:24:00')}
|
|
203
|
+
testID="mixed-time-picker"
|
|
204
|
+
onChange={noop}
|
|
205
|
+
confirmLabel="Confirm"
|
|
206
|
+
/>
|
|
207
|
+
<RichTextEditor
|
|
208
|
+
label="Rich Text Editor"
|
|
209
|
+
value={[{ type: 'paragraph', children: [{ text: 'Content' }] }]}
|
|
210
|
+
onChange={noop}
|
|
211
|
+
name="mixed-rich-text-editor"
|
|
212
|
+
testID="mixed-rich-text-editor"
|
|
213
|
+
/>
|
|
214
|
+
<Select
|
|
215
|
+
label="Single Select"
|
|
216
|
+
value="option1"
|
|
217
|
+
testID="mixed-select"
|
|
218
|
+
onConfirm={noop}
|
|
219
|
+
options={[
|
|
220
|
+
{ text: 'Option 1', value: 'option1' },
|
|
221
|
+
{ text: 'Option 2', value: 'option2' },
|
|
222
|
+
]}
|
|
223
|
+
/>
|
|
224
|
+
<Select.Multi
|
|
225
|
+
label="Multi Select"
|
|
226
|
+
value={['option1', 'option2']}
|
|
227
|
+
testID="mixed-select-multi"
|
|
228
|
+
onConfirm={noop}
|
|
229
|
+
footerLabel="Confirm"
|
|
230
|
+
options={[
|
|
231
|
+
{ text: 'Option 1', value: 'option1' },
|
|
232
|
+
{ text: 'Option 2', value: 'option2' },
|
|
233
|
+
{ text: 'Option 3', value: 'option3' },
|
|
234
|
+
]}
|
|
235
|
+
/>
|
|
236
|
+
</FormGroup>
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// TextInput should have top border radius removed (first component)
|
|
240
|
+
expect(
|
|
241
|
+
within(getByTestId('mixed-text-input'))
|
|
242
|
+
.getByTestId('text-input-border')
|
|
243
|
+
.props.style.flat()
|
|
244
|
+
).toEqual(
|
|
245
|
+
expect.arrayContaining([
|
|
246
|
+
expect.objectContaining({
|
|
247
|
+
borderBottomLeftRadius: 0,
|
|
248
|
+
borderBottomRightRadius: 0,
|
|
249
|
+
}),
|
|
250
|
+
])
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// DatePicker should have no border radius (middle component)
|
|
254
|
+
expect(
|
|
255
|
+
within(getByTestId('mixed-date-picker'))
|
|
256
|
+
.getByTestId('text-input-border')
|
|
257
|
+
.props.style.flat()
|
|
258
|
+
).toEqual(
|
|
259
|
+
expect.arrayContaining([
|
|
260
|
+
expect.objectContaining({
|
|
261
|
+
borderRadius: 0,
|
|
262
|
+
}),
|
|
263
|
+
])
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// TimePicker should have no border radius (middle component)
|
|
267
|
+
expect(
|
|
268
|
+
within(getByTestId('mixed-time-picker'))
|
|
269
|
+
.getByTestId('text-input-border')
|
|
270
|
+
.props.style.flat()
|
|
271
|
+
).toEqual(
|
|
272
|
+
expect.arrayContaining([
|
|
273
|
+
expect.objectContaining({
|
|
274
|
+
borderRadius: 0,
|
|
275
|
+
}),
|
|
276
|
+
])
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// RichTextEditor should have no border radius (middle component)
|
|
280
|
+
expect(
|
|
281
|
+
within(getByTestId('mixed-rich-text-editor'))
|
|
282
|
+
.getByTestId('text-input-border')
|
|
283
|
+
.props.style.flat()
|
|
284
|
+
).toEqual(
|
|
285
|
+
expect.arrayContaining([
|
|
286
|
+
expect.objectContaining({
|
|
287
|
+
borderRadius: 0,
|
|
288
|
+
}),
|
|
289
|
+
])
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
// Select should have no border radius (middle component)
|
|
293
|
+
expect(
|
|
294
|
+
within(getByTestId('mixed-select'))
|
|
295
|
+
.getByTestId('text-input-border')
|
|
296
|
+
.props.style.flat()
|
|
297
|
+
).toEqual(
|
|
298
|
+
expect.arrayContaining([
|
|
299
|
+
expect.objectContaining({
|
|
300
|
+
borderRadius: 0,
|
|
301
|
+
}),
|
|
302
|
+
])
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Select.Multi should have bottom border radius removed (last component)
|
|
306
|
+
expect(
|
|
307
|
+
within(getByTestId('mixed-select-multi'))
|
|
308
|
+
.getByTestId('text-input-border')
|
|
309
|
+
.props.style.flat()
|
|
310
|
+
).toEqual(
|
|
311
|
+
expect.arrayContaining([
|
|
312
|
+
expect.objectContaining({
|
|
313
|
+
borderTopLeftRadius: 0,
|
|
314
|
+
borderTopRightRadius: 0,
|
|
315
|
+
}),
|
|
316
|
+
])
|
|
317
|
+
);
|
|
318
|
+
});
|
|
179
319
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { ReactNode, useMemo } from 'react';
|
|
1
|
+
import React, { ReactElement, ReactNode, useMemo } from 'react';
|
|
2
2
|
import { StyleProp, StyleSheet, ViewProps, ViewStyle } from 'react-native';
|
|
3
3
|
import { Box, useTheme } from '@hero-design/rn';
|
|
4
4
|
import { generateBorderStyle, generateMarginStyle } from './utils';
|
|
@@ -6,7 +6,7 @@ import { generateBorderStyle, generateMarginStyle } from './utils';
|
|
|
6
6
|
export interface FormGroupProps extends ViewProps {
|
|
7
7
|
/**
|
|
8
8
|
* The children of the FormGroup. In order for the group styling to work,
|
|
9
|
-
* they must be either HD form components (TextInput, Select, Pickers,...) or enhanced HD
|
|
9
|
+
* they must be either HD form components (TextInput, Select, Pickers,...) or enhanced HD input components
|
|
10
10
|
* that supports the corresponding interface.
|
|
11
11
|
*
|
|
12
12
|
* Example:
|
|
@@ -33,15 +33,15 @@ export interface FormGroupProps extends ViewProps {
|
|
|
33
33
|
const FormGroup = ({ children, style, testID, ...props }: FormGroupProps) => {
|
|
34
34
|
const theme = useTheme();
|
|
35
35
|
const childrenArray = React.Children.toArray(children).filter(
|
|
36
|
-
React.isValidElement
|
|
36
|
+
(child): child is ReactElement => React.isValidElement(child)
|
|
37
37
|
);
|
|
38
38
|
|
|
39
39
|
// If there are multiple children, inject styles to group them together.
|
|
40
40
|
const groupedChildren = useMemo(
|
|
41
41
|
() =>
|
|
42
42
|
childrenArray.map((child, index) => {
|
|
43
|
-
const rawChildStyle =
|
|
44
|
-
const rawChildTextStyle =
|
|
43
|
+
const rawChildStyle = child.props.style;
|
|
44
|
+
const rawChildTextStyle = child.props.textStyle;
|
|
45
45
|
|
|
46
46
|
// Handle array styles by flattening them first
|
|
47
47
|
const childStyle = StyleSheet.flatten(rawChildStyle);
|
|
@@ -77,11 +77,20 @@ const FormGroup = ({ children, style, testID, ...props }: FormGroupProps) => {
|
|
|
77
77
|
}),
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
return React.cloneElement(child
|
|
80
|
+
return React.cloneElement(child, {
|
|
81
81
|
style: mergedStyle,
|
|
82
82
|
textStyle: mergedTextStyle,
|
|
83
83
|
// Internal text input prop to allow for different styling
|
|
84
|
-
|
|
84
|
+
groupStyleEnabled: true,
|
|
85
|
+
|
|
86
|
+
// For HD components that uses FormGroup
|
|
87
|
+
inputProps: {
|
|
88
|
+
...child.props.inputProps,
|
|
89
|
+
textStyle: {
|
|
90
|
+
...child.props.inputProps?.textStyle,
|
|
91
|
+
...mergedTextStyle,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
85
94
|
});
|
|
86
95
|
}),
|
|
87
96
|
[childrenArray, theme]
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import type { ComponentType, ReactElement } from 'react';
|
|
1
2
|
import React, { useMemo } from 'react';
|
|
2
|
-
import type { ComponentType, ReactElement, Ref } from 'react';
|
|
3
|
-
import type { StyleProp, ViewStyle } from 'react-native';
|
|
4
3
|
|
|
5
4
|
import { plainSerializer } from 'hero-editor';
|
|
6
|
-
import
|
|
5
|
+
import { InternalTextInput } from '../TextInput';
|
|
7
6
|
import RichTextEditorInput from './RichTextEditorInput';
|
|
8
|
-
import type { EditorValue, TextUnit } from './types';
|
|
9
7
|
import { StyledWrapper } from './StyledRichTextEditor';
|
|
8
|
+
import type {
|
|
9
|
+
EditorValue,
|
|
10
|
+
InternalRichTextEditorProps,
|
|
11
|
+
TextUnit,
|
|
12
|
+
} from './types';
|
|
10
13
|
|
|
11
14
|
export interface RichTextEditorRef {
|
|
12
15
|
requestBlur: VoidFunction;
|
|
@@ -15,22 +18,6 @@ export interface RichTextEditorRef {
|
|
|
15
18
|
setReadOnly: (readOnly: boolean) => void;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
export interface RichTextEditorProps {
|
|
19
|
-
autoFocus?: boolean;
|
|
20
|
-
error?: string;
|
|
21
|
-
value?: EditorValue;
|
|
22
|
-
name: string;
|
|
23
|
-
onChange: (data: EditorValue) => void;
|
|
24
|
-
onCursorChange?: (params: { position: { top: number } }) => void;
|
|
25
|
-
placeholder?: string;
|
|
26
|
-
style?: StyleProp<ViewStyle>;
|
|
27
|
-
label: string;
|
|
28
|
-
helpText?: string;
|
|
29
|
-
required?: boolean;
|
|
30
|
-
testID?: string;
|
|
31
|
-
forwardedRef?: Ref<RichTextEditorRef>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
21
|
const defaultValue: EditorValue = [
|
|
35
22
|
{
|
|
36
23
|
type: 'paragraph',
|
|
@@ -38,7 +25,7 @@ const defaultValue: EditorValue = [
|
|
|
38
25
|
},
|
|
39
26
|
];
|
|
40
27
|
|
|
41
|
-
const RichTextEditor: ComponentType<
|
|
28
|
+
const RichTextEditor: ComponentType<InternalRichTextEditorProps> = ({
|
|
42
29
|
autoFocus = true,
|
|
43
30
|
name,
|
|
44
31
|
placeholder = '',
|
|
@@ -46,18 +33,20 @@ const RichTextEditor: ComponentType<RichTextEditorProps> = ({
|
|
|
46
33
|
onCursorChange,
|
|
47
34
|
error = '',
|
|
48
35
|
style = {},
|
|
36
|
+
textStyle,
|
|
49
37
|
label,
|
|
50
38
|
helpText,
|
|
51
39
|
required,
|
|
52
40
|
testID,
|
|
53
41
|
forwardedRef,
|
|
54
42
|
value = defaultValue,
|
|
55
|
-
|
|
43
|
+
groupStyleEnabled = false,
|
|
44
|
+
}: InternalRichTextEditorProps): ReactElement => {
|
|
56
45
|
const plain = useMemo(() => plainSerializer(value), [value]);
|
|
57
46
|
|
|
58
47
|
return (
|
|
59
|
-
<StyledWrapper>
|
|
60
|
-
<
|
|
48
|
+
<StyledWrapper groupStyleEnabled={groupStyleEnabled}>
|
|
49
|
+
<InternalTextInput
|
|
61
50
|
autoFocus={autoFocus}
|
|
62
51
|
error={error}
|
|
63
52
|
label={label}
|
|
@@ -66,7 +55,9 @@ const RichTextEditor: ComponentType<RichTextEditorProps> = ({
|
|
|
66
55
|
placeholder={placeholder}
|
|
67
56
|
value={plain}
|
|
68
57
|
style={style}
|
|
58
|
+
textStyle={textStyle}
|
|
69
59
|
helpText={helpText}
|
|
60
|
+
groupStyleEnabled={groupStyleEnabled}
|
|
70
61
|
renderInputValue={(inputProps, ref) => (
|
|
71
62
|
<RichTextEditorInput
|
|
72
63
|
{...inputProps}
|
|
@@ -11,6 +11,7 @@ import React, {
|
|
|
11
11
|
import type { Ref } from 'react';
|
|
12
12
|
import { WebView } from 'react-native-webview';
|
|
13
13
|
|
|
14
|
+
import { View } from 'react-native';
|
|
14
15
|
import { isAndroid } from '../../utils/helpers';
|
|
15
16
|
import { emitter } from './EditorEvent';
|
|
16
17
|
import * as Events from './utils/events';
|
|
@@ -268,17 +269,22 @@ const RichTextEditorInput = forwardRef<
|
|
|
268
269
|
);
|
|
269
270
|
|
|
270
271
|
return (
|
|
271
|
-
<
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
272
|
+
<View
|
|
273
|
+
style={{
|
|
274
|
+
height: webviewHeight,
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
<StyledWebView
|
|
278
|
+
ref={webview}
|
|
279
|
+
testID="webview"
|
|
280
|
+
originWhitelist={['*']}
|
|
281
|
+
source={{ html }}
|
|
282
|
+
onMessage={onMessage}
|
|
283
|
+
scrollEnabled={false}
|
|
284
|
+
hideKeyboardAccessoryView
|
|
285
|
+
keyboardDisplayRequiresUserAction={false}
|
|
286
|
+
/>
|
|
287
|
+
</View>
|
|
282
288
|
);
|
|
283
289
|
}
|
|
284
290
|
);
|
|
@@ -2,14 +2,17 @@ import { styled } from '@hero-design/rn';
|
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
import { WebView } from 'react-native-webview';
|
|
4
4
|
|
|
5
|
-
export const StyledWrapper = styled(View)
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export const StyledWrapper = styled(View)<{ groupStyleEnabled?: boolean }>(
|
|
6
|
+
({ theme, groupStyleEnabled }) => ({
|
|
7
|
+
...(groupStyleEnabled
|
|
8
|
+
? {}
|
|
9
|
+
: {
|
|
10
|
+
marginBottom: theme.__hd__.richTextEditor.space.wrapperMarginBottom,
|
|
11
|
+
}),
|
|
12
|
+
})
|
|
13
|
+
);
|
|
8
14
|
|
|
9
|
-
export const StyledWebView = styled(WebView)
|
|
10
|
-
height: number | undefined;
|
|
11
|
-
}>(({ height, theme }) => ({
|
|
12
|
-
height,
|
|
15
|
+
export const StyledWebView = styled(WebView)(({ theme }) => ({
|
|
13
16
|
minHeight: theme.__hd__.richTextEditor.sizes.editorMinHeight,
|
|
14
17
|
backgroundColor: 'transparent',
|
|
15
18
|
textAlignVertical: 'center',
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import RichTextEditor from '../RichTextEditor';
|
|
3
|
+
import renderWithTheme from '../../../../testUtils/renderWithTheme';
|
|
4
|
+
|
|
5
|
+
const initialValue = [
|
|
6
|
+
{ type: 'paragraph', children: [{ text: 'Hello world' }] },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
describe('RichTextEditor zIndex', () => {
|
|
10
|
+
it('applies correct zIndex logic when groupStyleEnabled is true', () => {
|
|
11
|
+
const { getByTestId } = renderWithTheme(
|
|
12
|
+
<RichTextEditor
|
|
13
|
+
label="Test Editor"
|
|
14
|
+
value={initialValue}
|
|
15
|
+
onChange={jest.fn()}
|
|
16
|
+
name="test-editor"
|
|
17
|
+
testID="rich-text-editor"
|
|
18
|
+
groupStyleEnabled
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const container = getByTestId('rich-text-editor');
|
|
23
|
+
|
|
24
|
+
// Initially should have zIndex 0 (normal state)
|
|
25
|
+
expect(container.props.style.flat()).toEqual(
|
|
26
|
+
expect.arrayContaining([
|
|
27
|
+
expect.objectContaining({
|
|
28
|
+
zIndex: 0,
|
|
29
|
+
}),
|
|
30
|
+
])
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('applies correct zIndex when has error', () => {
|
|
35
|
+
const { getByTestId } = renderWithTheme(
|
|
36
|
+
<RichTextEditor
|
|
37
|
+
label="Test Editor"
|
|
38
|
+
value={initialValue}
|
|
39
|
+
onChange={jest.fn()}
|
|
40
|
+
name="test-editor"
|
|
41
|
+
testID="rich-text-editor"
|
|
42
|
+
error="This is an error"
|
|
43
|
+
groupStyleEnabled
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const container = getByTestId('rich-text-editor');
|
|
48
|
+
|
|
49
|
+
// Should have zIndex 1 when has error (medium priority)
|
|
50
|
+
expect(container.props.style.flat()).toEqual(
|
|
51
|
+
expect.arrayContaining([
|
|
52
|
+
expect.objectContaining({
|
|
53
|
+
zIndex: 1,
|
|
54
|
+
}),
|
|
55
|
+
])
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('does not apply zIndex when groupStyleEnabled is false', () => {
|
|
60
|
+
const { getByTestId } = renderWithTheme(
|
|
61
|
+
<RichTextEditor
|
|
62
|
+
label="Test Editor"
|
|
63
|
+
value={initialValue}
|
|
64
|
+
onChange={jest.fn()}
|
|
65
|
+
name="test-editor"
|
|
66
|
+
testID="rich-text-editor"
|
|
67
|
+
groupStyleEnabled={false}
|
|
68
|
+
error="This is an error"
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const container = getByTestId('rich-text-editor');
|
|
73
|
+
|
|
74
|
+
// Should not have zIndex applied when groupStyleEnabled is false
|
|
75
|
+
const containerStyle = container.props.style.flat();
|
|
76
|
+
const hasZIndex = containerStyle.some(
|
|
77
|
+
(style: Record<string, unknown>) => style && style.zIndex !== undefined
|
|
78
|
+
);
|
|
79
|
+
expect(hasZIndex).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react';
|
|
2
|
-
import RichTextEditor, {
|
|
3
|
-
RichTextEditorProps,
|
|
4
|
-
RichTextEditorRef,
|
|
5
|
-
} from './RichTextEditor';
|
|
2
|
+
import RichTextEditor, { RichTextEditorRef } from './RichTextEditor';
|
|
6
3
|
import MentionList from './MentionList';
|
|
4
|
+
import type { RichTextEditorProps } from './types';
|
|
7
5
|
|
|
8
6
|
import Toolbar from './EditorToolbar';
|
|
9
7
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { StyleProp, ViewStyle } from 'react-native';
|
|
1
|
+
import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
2
2
|
|
|
3
3
|
import { Ref } from 'react';
|
|
4
4
|
import type { TextInputProps } from '../TextInput';
|
|
@@ -83,5 +83,16 @@ export interface RichTextEditorProps {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export interface InternalRichTextEditorProps extends RichTextEditorProps {
|
|
86
|
+
/**
|
|
87
|
+
* Input text style
|
|
88
|
+
*/
|
|
89
|
+
textStyle?: StyleProp<TextStyle>;
|
|
90
|
+
/**
|
|
91
|
+
* Whether the component is used within a FormGroup
|
|
92
|
+
*/
|
|
93
|
+
groupStyleEnabled?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Custom TextInput component to be used instead of the default one
|
|
96
|
+
*/
|
|
86
97
|
TextInputComponent?: React.ComponentType<TextInputProps>;
|
|
87
98
|
}
|