@hero-design/rn-work-uikit 1.3.1 → 1.5.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 (44) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/lib/index.js +19933 -735
  3. package/package.json +3 -2
  4. package/src/components/DatePicker/__tests__/__snapshots__/index.spec.tsx.snap +3 -3
  5. package/src/components/FormGroup/__tests__/__snapshots__/index.spec.tsx.snap +903 -0
  6. package/src/components/FormGroup/__tests__/index.spec.tsx +306 -0
  7. package/src/components/FormGroup/__tests__/utils.spec.ts +73 -0
  8. package/src/components/FormGroup/index.tsx +106 -0
  9. package/src/components/FormGroup/utils.ts +67 -0
  10. package/src/components/RichTextEditor/EditorEvent.ts +7 -0
  11. package/src/components/RichTextEditor/EditorToolbar.tsx +216 -0
  12. package/src/components/RichTextEditor/MentionList.tsx +99 -0
  13. package/src/components/RichTextEditor/RichTextEditor.tsx +88 -0
  14. package/src/components/RichTextEditor/RichTextEditorInput.tsx +292 -0
  15. package/src/components/RichTextEditor/StyledRichTextEditor.tsx +15 -0
  16. package/src/components/RichTextEditor/StyledToolbar.ts +32 -0
  17. package/src/components/RichTextEditor/__mocks__/hero-editor.js +3 -0
  18. package/src/components/RichTextEditor/__mocks__/heroEditorApp.ts +2 -0
  19. package/src/components/RichTextEditor/__tests__/EditorToolbar.spec.tsx +144 -0
  20. package/src/components/RichTextEditor/__tests__/MentionList.spec.tsx +105 -0
  21. package/src/components/RichTextEditor/__tests__/RichTextEditorInput.spec.tsx +136 -0
  22. package/src/components/RichTextEditor/__tests__/__snapshots__/EditorToolbar.spec.tsx.snap +414 -0
  23. package/src/components/RichTextEditor/__tests__/__snapshots__/MentionList.spec.tsx.snap +13 -0
  24. package/src/components/RichTextEditor/constants.ts +9 -0
  25. package/src/{hero-editor.d.ts → components/RichTextEditor/hero-editor.d.ts} +6 -0
  26. package/src/components/RichTextEditor/heroEditorApp.ts +3 -0
  27. package/src/components/RichTextEditor/index.tsx +20 -0
  28. package/src/components/RichTextEditor/types.ts +87 -0
  29. package/src/components/RichTextEditor/utils/events.ts +31 -0
  30. package/src/components/RichTextEditor/utils/rnWebView.tsx +30 -0
  31. package/src/components/Select/__tests__/__snapshots__/index.spec.tsx.snap +24 -2
  32. package/src/components/Select/index.tsx +11 -10
  33. package/src/components/TextInput/Group/__tests__/__snapshots__/index.spec.tsx.snap +3 -3
  34. package/src/components/TextInput/Group/index.tsx +6 -1
  35. package/src/components/TextInput/InputComponent.tsx +59 -18
  36. package/src/components/TextInput/InputRow.tsx +13 -7
  37. package/src/components/TextInput/StyledTextInput.tsx +3 -3
  38. package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +17 -17
  39. package/src/components/TextInput/index.tsx +22 -13
  40. package/src/components/TextInput/types.ts +30 -5
  41. package/src/index.ts +3 -1
  42. package/src/utils/hooks.ts +10 -0
  43. package/stats/1.5.0/rn-work-uikit-stats.html +4844 -0
  44. package/stats/1.3.1/rn-work-uikit-stats.html +0 -4844
@@ -0,0 +1,306 @@
1
+ import React from 'react';
2
+ import { within } from '@testing-library/react-native';
3
+ import theme from '../../../theme';
4
+ import TextInput from '../../TextInput';
5
+ import Select from '../../Select';
6
+ import FormGroup from '..';
7
+ import renderWithTheme from '../../../../testUtils/renderWithTheme';
8
+ import { noop } from '../../../utils/functions';
9
+
10
+ describe('FormGroup', () => {
11
+ it('should render', () => {
12
+ const { getByText, getByTestId, toJSON } = renderWithTheme(
13
+ <FormGroup>
14
+ <TextInput label="Text Input 1" value="Text Input 1" required />
15
+ <TextInput
16
+ label="Text Input 2"
17
+ value="Text Input 2"
18
+ error="This is an error"
19
+ testID="text-input-2"
20
+ />
21
+ <TextInput label="Text Input 3" value="Text Input 3" required />
22
+ </FormGroup>
23
+ );
24
+
25
+ expect(toJSON()).toMatchSnapshot('xxx');
26
+
27
+ expect(getByText('Text Input 1')).toBeVisible();
28
+ expect(getByText('Text Input 2', { exact: false })).toBeVisible();
29
+ expect(
30
+ within(getByTestId('text-input-2')).getByText('(Optional)', {
31
+ exact: false,
32
+ })
33
+ ).toBeVisible();
34
+ expect(getByText('This is an error')).toBeVisible();
35
+ expect(getByText('Text Input 3')).toBeVisible();
36
+ });
37
+
38
+ it('renders with correct border styling', () => {
39
+ const { getByTestId } = renderWithTheme(
40
+ <FormGroup>
41
+ <TextInput value="Text Input 1" testID="text-input-1" />
42
+ <TextInput value="Text Input 2" testID="text-input-2" />
43
+ <TextInput value="Text Input 3" testID="text-input-3" />
44
+ </FormGroup>
45
+ );
46
+
47
+ expect(
48
+ within(getByTestId('text-input-1'))
49
+ .getByTestId('text-input-border')
50
+ .props.style.flat()
51
+ ).toEqual(
52
+ expect.arrayContaining([
53
+ expect.objectContaining({
54
+ borderBottomLeftRadius: 0,
55
+ borderBottomRightRadius: 0,
56
+ }),
57
+ ])
58
+ );
59
+
60
+ expect(
61
+ within(getByTestId('text-input-2'))
62
+ .getByTestId('text-input-border')
63
+ .props.style.flat()
64
+ ).toEqual(
65
+ expect.arrayContaining([
66
+ expect.objectContaining({
67
+ borderRadius: 0,
68
+ }),
69
+ ])
70
+ );
71
+
72
+ expect(
73
+ within(getByTestId('text-input-3'))
74
+ .getByTestId('text-input-border')
75
+ .props.style.flat()
76
+ ).toEqual(
77
+ expect.arrayContaining([
78
+ expect.objectContaining({
79
+ borderTopLeftRadius: 0,
80
+ borderTopRightRadius: 0,
81
+ }),
82
+ ])
83
+ );
84
+ });
85
+
86
+ it('merges with the children styles in correct order', () => {
87
+ const { getByTestId } = renderWithTheme(
88
+ <FormGroup>
89
+ <TextInput
90
+ value="Text Input 1"
91
+ testID="text-input-1"
92
+ textStyle={{
93
+ borderColor: '#ffffff',
94
+ borderWidth: 1,
95
+ }}
96
+ />
97
+ <TextInput
98
+ value="Text Input 2"
99
+ testID="text-input-2"
100
+ style={{ width: 300 }}
101
+ />
102
+ </FormGroup>
103
+ );
104
+
105
+ // Merging text styles
106
+ expect(
107
+ within(getByTestId('text-input-1'))
108
+ .getByTestId('text-input-border')
109
+ .props.style.flat()
110
+ ).toEqual(
111
+ expect.arrayContaining([
112
+ expect.objectContaining({
113
+ // Passed style
114
+ borderWidth: 1,
115
+ borderColor: '#ffffff',
116
+ // Injected style
117
+ borderBottomLeftRadius: 0,
118
+ borderBottomRightRadius: 0,
119
+ }),
120
+ ])
121
+ );
122
+
123
+ // Merging container styles
124
+ expect(getByTestId('text-input-2').props.style.flat()).toEqual(
125
+ expect.arrayContaining([
126
+ expect.objectContaining({
127
+ // Passed style
128
+ width: 300,
129
+ // Injected style
130
+ marginTop: -theme.__hd__.textInput.borderWidths.container.normal,
131
+ }),
132
+ ])
133
+ );
134
+
135
+ expect(
136
+ within(getByTestId('text-input-2'))
137
+ .getByTestId('text-input-border')
138
+ .props.style.flat()
139
+ ).toEqual(
140
+ expect.arrayContaining([
141
+ expect.objectContaining({
142
+ // Injected style
143
+ borderTopLeftRadius: 0,
144
+ borderTopRightRadius: 0,
145
+ }),
146
+ ])
147
+ );
148
+ });
149
+
150
+ it('does not alter the children styles if there is only one child', () => {
151
+ const { getByTestId } = renderWithTheme(
152
+ <FormGroup>
153
+ <TextInput
154
+ value="Text Input 1"
155
+ testID="text-input-1"
156
+ style={{
157
+ marginTop: theme.space.medium,
158
+ }}
159
+ />
160
+ </FormGroup>
161
+ );
162
+
163
+ // Container style is not injected
164
+ expect(getByTestId('text-input-1').props.style.flat()).toEqual(
165
+ expect.arrayContaining([
166
+ expect.objectContaining({
167
+ // Passed style instead of injected style
168
+ marginTop: theme.space.medium,
169
+ }),
170
+ ])
171
+ );
172
+
173
+ // Border style is not injected
174
+ const internalBorderStyle =
175
+ getByTestId('text-input-border').props.style.flat();
176
+ const borderKeys = Object.keys(internalBorderStyle).filter((key) =>
177
+ key.startsWith('border')
178
+ );
179
+ expect(borderKeys).toHaveLength(0);
180
+ });
181
+
182
+ it('renders enhanced TextInput/Select components with correct styles', () => {
183
+ const { getByTestId } = renderWithTheme(
184
+ <FormGroup>
185
+ <TextInput
186
+ label="Enhanced Text Input"
187
+ value="Enhanced Text Input"
188
+ testID="enhanced-text-input"
189
+ />
190
+ <Select
191
+ label="Enhanced Select"
192
+ value="option1"
193
+ testID="enhanced-select"
194
+ onConfirm={noop}
195
+ options={[
196
+ { text: 'Option 1', value: 'option1' },
197
+ { text: 'Option 2', value: 'option2' },
198
+ ]}
199
+ />
200
+ </FormGroup>
201
+ );
202
+
203
+ // Enhanced TextInput should have correct border styling
204
+ expect(
205
+ within(getByTestId('enhanced-text-input'))
206
+ .getByTestId('text-input-border')
207
+ .props.style.flat()
208
+ ).toEqual(
209
+ expect.arrayContaining([
210
+ expect.objectContaining({
211
+ borderBottomLeftRadius: 0,
212
+ borderBottomRightRadius: 0,
213
+ }),
214
+ ])
215
+ );
216
+
217
+ // Enhanced Select should have correct border styling
218
+ expect(
219
+ within(getByTestId('enhanced-select'))
220
+ .getByTestId('text-input-border')
221
+ .props.style.flat()
222
+ ).toEqual(
223
+ expect.arrayContaining([
224
+ expect.objectContaining({
225
+ borderTopLeftRadius: 0,
226
+ borderTopRightRadius: 0,
227
+ }),
228
+ ])
229
+ );
230
+ });
231
+
232
+ it('renders mixed components (TextInput, Select, Select.Multi) with correct styles', () => {
233
+ const { getByTestId } = renderWithTheme(
234
+ <FormGroup>
235
+ <TextInput
236
+ label="Text Input"
237
+ value="Text Input"
238
+ testID="mixed-text-input"
239
+ />
240
+ <Select
241
+ label="Single Select"
242
+ value="option1"
243
+ testID="mixed-select"
244
+ onConfirm={noop}
245
+ options={[
246
+ { text: 'Option 1', value: 'option1' },
247
+ { text: 'Option 2', value: 'option2' },
248
+ ]}
249
+ />
250
+ <Select.Multi
251
+ label="Multi Select"
252
+ value={['option1', 'option2']}
253
+ testID="mixed-select-multi"
254
+ onConfirm={noop}
255
+ footerLabel="Confirm"
256
+ options={[
257
+ { text: 'Option 1', value: 'option1' },
258
+ { text: 'Option 2', value: 'option2' },
259
+ { text: 'Option 3', value: 'option3' },
260
+ ]}
261
+ />
262
+ </FormGroup>
263
+ );
264
+
265
+ // TextInput should have top border radius removed
266
+ expect(
267
+ within(getByTestId('mixed-text-input'))
268
+ .getByTestId('text-input-border')
269
+ .props.style.flat()
270
+ ).toEqual(
271
+ expect.arrayContaining([
272
+ expect.objectContaining({
273
+ borderBottomLeftRadius: 0,
274
+ borderBottomRightRadius: 0,
275
+ }),
276
+ ])
277
+ );
278
+
279
+ // Select should have no border radius (middle component)
280
+ expect(
281
+ within(getByTestId('mixed-select'))
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.Multi should have bottom border radius removed
293
+ expect(
294
+ within(getByTestId('mixed-select-multi'))
295
+ .getByTestId('text-input-border')
296
+ .props.style.flat()
297
+ ).toEqual(
298
+ expect.arrayContaining([
299
+ expect.objectContaining({
300
+ borderTopLeftRadius: 0,
301
+ borderTopRightRadius: 0,
302
+ }),
303
+ ])
304
+ );
305
+ });
306
+ });
@@ -0,0 +1,73 @@
1
+ import theme from '../../../theme';
2
+ import { generateBorderStyle, generateMarginStyle } from '../utils';
3
+
4
+ describe('utils', () => {
5
+ describe('generateBorderStyle', () => {
6
+ it('should generate the correct border style for the first child', () => {
7
+ const borderStyle = generateBorderStyle({ index: 0, length: 3 });
8
+ expect(borderStyle).toEqual({
9
+ borderBottomLeftRadius: 0,
10
+ borderBottomRightRadius: 0,
11
+ });
12
+ });
13
+
14
+ it('should generate the correct border style for the last child', () => {
15
+ const borderStyle = generateBorderStyle({ index: 2, length: 3 });
16
+ expect(borderStyle).toEqual({
17
+ borderTopLeftRadius: 0,
18
+ borderTopRightRadius: 0,
19
+ });
20
+ });
21
+
22
+ it('should generate the correct border style for the middle child', () => {
23
+ const borderStyle = generateBorderStyle({ index: 1, length: 3 });
24
+ expect(borderStyle).toEqual({
25
+ borderRadius: 0,
26
+ });
27
+ });
28
+ });
29
+
30
+ describe('generateMarginStyle', () => {
31
+ it('should generate the correct margin style for the first child', () => {
32
+ const marginStyle = generateMarginStyle({
33
+ index: 0,
34
+ length: 3,
35
+ theme,
36
+ });
37
+ expect(marginStyle).toEqual({
38
+ marginTop: 0,
39
+ });
40
+ });
41
+
42
+ it('should generate the correct margin style for the last child', () => {
43
+ const marginStyle = generateMarginStyle({
44
+ index: 2,
45
+ length: 3,
46
+ theme,
47
+ });
48
+ expect(marginStyle).toEqual({
49
+ marginTop: -theme.__hd__.textInput.borderWidths.container.normal,
50
+ });
51
+ });
52
+
53
+ it('should generate the correct margin style for the middle child', () => {
54
+ const marginStyle = generateMarginStyle({
55
+ index: 1,
56
+ length: 3,
57
+ theme,
58
+ });
59
+ expect(marginStyle).toEqual({
60
+ marginTop: -theme.__hd__.textInput.borderWidths.container.normal,
61
+ });
62
+ });
63
+
64
+ it('should generate the correct margin style for a single child', () => {
65
+ const marginStyle = generateMarginStyle({
66
+ index: 0,
67
+ length: 1,
68
+ theme,
69
+ });
70
+ expect(marginStyle).toEqual({});
71
+ });
72
+ });
73
+ });
@@ -0,0 +1,106 @@
1
+ import React, { ReactElement, ReactNode, useMemo } from 'react';
2
+ import { StyleProp, StyleSheet, ViewProps, ViewStyle } from 'react-native';
3
+ import { Box, useTheme } from '@hero-design/rn';
4
+ import { generateBorderStyle, generateMarginStyle } from './utils';
5
+
6
+ export interface FormGroupProps extends ViewProps {
7
+ /**
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 input components
10
+ * that supports the corresponding interface.
11
+ *
12
+ * Example:
13
+ * const EnhancedTextInput = (props: TextInputProps) => {
14
+ * return <TextInput {...props} />;
15
+ * };
16
+ *
17
+ * <FormGroup>
18
+ * <Select ... />
19
+ * <EnhancedTextInput ... />
20
+ * </FormGroup>
21
+ */
22
+ children: ReactNode;
23
+ /**
24
+ * The style of the FormGroup.
25
+ */
26
+ style?: StyleProp<ViewStyle>;
27
+ /**
28
+ * The testID of the FormGroup.
29
+ */
30
+ testID?: string;
31
+ }
32
+
33
+ const FormGroup = ({ children, style, testID, ...props }: FormGroupProps) => {
34
+ const theme = useTheme();
35
+ const childrenArray = React.Children.toArray(children).filter(
36
+ (child): child is ReactElement => React.isValidElement(child)
37
+ );
38
+
39
+ // If there are multiple children, inject styles to group them together.
40
+ const groupedChildren = useMemo(
41
+ () =>
42
+ childrenArray.map((child, index) => {
43
+ const rawChildStyle = child.props.style;
44
+ const rawChildTextStyle = child.props.textStyle;
45
+
46
+ // Handle array styles by flattening them first
47
+ const childStyle = StyleSheet.flatten(rawChildStyle);
48
+ const childTextStyle = StyleSheet.flatten(rawChildTextStyle);
49
+
50
+ /**
51
+ * Merge the child style with the group injected style.
52
+ * Order of precedence:
53
+ * 1. Child style.
54
+ * 2. Group injected style.
55
+ */
56
+ const mergedStyle = {
57
+ ...childStyle,
58
+ ...generateMarginStyle({
59
+ index,
60
+ length: childrenArray.length,
61
+ theme,
62
+ }),
63
+ };
64
+
65
+ /**
66
+ * Merge the child text style with the group text style.
67
+ * Order of precedence:
68
+ * 1. Group text style through textStyle prop.
69
+ * 2. Child text style.
70
+ * 3. Group injected border style.
71
+ */
72
+ const mergedTextStyle = {
73
+ ...childTextStyle,
74
+ ...generateBorderStyle({
75
+ index,
76
+ length: childrenArray.length,
77
+ }),
78
+ };
79
+
80
+ return React.cloneElement(child, {
81
+ style: mergedStyle,
82
+ textStyle: mergedTextStyle,
83
+ // Internal text input prop to allow for different styling
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
+ },
94
+ });
95
+ }),
96
+ [childrenArray, theme]
97
+ );
98
+
99
+ return (
100
+ <Box style={style} testID={testID} {...props}>
101
+ {groupedChildren}
102
+ </Box>
103
+ );
104
+ };
105
+
106
+ export default FormGroup;
@@ -0,0 +1,67 @@
1
+ import { CSSProperties } from 'react';
2
+ import { Theme } from '@hero-design/rn';
3
+
4
+ /**
5
+ * Generates the border style for the TextInputGroup.
6
+ * @param index - The index of the TextInput.
7
+ * @param length - The length of the TextInputGroup.
8
+ * @returns The border style for the TextInputGroup.
9
+ */
10
+ const generateBorderStyle = ({
11
+ index,
12
+ length,
13
+ }: {
14
+ index: number;
15
+ length: number;
16
+ }): CSSProperties => {
17
+ const isFirst = index === 0;
18
+ const isLast = index === length - 1;
19
+
20
+ if (length === 1) {
21
+ return {};
22
+ }
23
+
24
+ if (isFirst) {
25
+ return {
26
+ borderBottomLeftRadius: 0,
27
+ borderBottomRightRadius: 0,
28
+ };
29
+ }
30
+
31
+ if (isLast) {
32
+ return {
33
+ borderTopLeftRadius: 0,
34
+ borderTopRightRadius: 0,
35
+ };
36
+ }
37
+
38
+ return {
39
+ borderRadius: 0,
40
+ };
41
+ };
42
+
43
+ const generateMarginStyle = ({
44
+ index,
45
+ length,
46
+ theme,
47
+ }: {
48
+ index: number;
49
+ length: number;
50
+ theme: Theme;
51
+ }) => {
52
+ if (length === 1) {
53
+ return {};
54
+ }
55
+
56
+ if (index === 0) {
57
+ return {
58
+ marginTop: 0,
59
+ };
60
+ }
61
+
62
+ return {
63
+ marginTop: -theme.__hd__.textInput.borderWidths.container.normal,
64
+ };
65
+ };
66
+
67
+ export { generateBorderStyle, generateMarginStyle };
@@ -0,0 +1,7 @@
1
+ import { EventEmitter } from 'events';
2
+
3
+ const emitter = new EventEmitter();
4
+
5
+ emitter.setMaxListeners(20);
6
+
7
+ export { emitter };