@hero-design/rn-work-uikit 1.3.1 → 1.4.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 (36) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/lib/index.js +19228 -186
  3. package/package.json +3 -2
  4. package/src/components/FormGroup/__tests__/__snapshots__/index.spec.tsx.snap +880 -0
  5. package/src/components/FormGroup/__tests__/index.spec.tsx +179 -0
  6. package/src/components/FormGroup/__tests__/utils.spec.ts +73 -0
  7. package/src/components/FormGroup/index.tsx +97 -0
  8. package/src/components/FormGroup/utils.ts +67 -0
  9. package/src/components/RichTextEditor/EditorEvent.ts +7 -0
  10. package/src/components/RichTextEditor/EditorToolbar.tsx +216 -0
  11. package/src/components/RichTextEditor/MentionList.tsx +99 -0
  12. package/src/components/RichTextEditor/RichTextEditor.tsx +88 -0
  13. package/src/components/RichTextEditor/RichTextEditorInput.tsx +286 -0
  14. package/src/components/RichTextEditor/StyledRichTextEditor.tsx +18 -0
  15. package/src/components/RichTextEditor/StyledToolbar.ts +32 -0
  16. package/src/components/RichTextEditor/__mocks__/hero-editor.js +3 -0
  17. package/src/components/RichTextEditor/__mocks__/heroEditorApp.ts +2 -0
  18. package/src/components/RichTextEditor/__tests__/EditorToolbar.spec.tsx +144 -0
  19. package/src/components/RichTextEditor/__tests__/MentionList.spec.tsx +105 -0
  20. package/src/components/RichTextEditor/__tests__/RichTextEditorInput.spec.tsx +136 -0
  21. package/src/components/RichTextEditor/__tests__/__snapshots__/EditorToolbar.spec.tsx.snap +414 -0
  22. package/src/components/RichTextEditor/__tests__/__snapshots__/MentionList.spec.tsx.snap +13 -0
  23. package/src/components/RichTextEditor/constants.ts +9 -0
  24. package/src/{hero-editor.d.ts → components/RichTextEditor/hero-editor.d.ts} +6 -0
  25. package/src/components/RichTextEditor/heroEditorApp.ts +3 -0
  26. package/src/components/RichTextEditor/index.tsx +20 -0
  27. package/src/components/RichTextEditor/types.ts +87 -0
  28. package/src/components/RichTextEditor/utils/events.ts +31 -0
  29. package/src/components/RichTextEditor/utils/rnWebView.tsx +30 -0
  30. package/src/components/TextInput/InputComponent.tsx +59 -18
  31. package/src/components/TextInput/InputRow.tsx +13 -7
  32. package/src/components/TextInput/index.tsx +20 -11
  33. package/src/components/TextInput/types.ts +29 -4
  34. package/src/index.ts +2 -1
  35. package/stats/1.4.0/rn-work-uikit-stats.html +4844 -0
  36. package/stats/1.3.1/rn-work-uikit-stats.html +0 -4844
@@ -0,0 +1,87 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+
3
+ import { Ref } from 'react';
4
+ import type { TextInputProps } from '../TextInput';
5
+
6
+ export type ToolbarButtonName =
7
+ | 'bold'
8
+ | 'italic'
9
+ | 'underline'
10
+ | 'bulletedList'
11
+ | 'numberedList'
12
+ | 'headingOne'
13
+ | 'headingTwo'
14
+ | '|';
15
+
16
+ export type TextUnit = 'character' | 'word' | 'line' | 'block';
17
+
18
+ export interface RichTextEditorRef {
19
+ requestBlur: VoidFunction;
20
+ insertNodes: (nodes: Record<string, unknown>[]) => void;
21
+ deleteBackward: (unit?: TextUnit) => void;
22
+ setReadOnly: (readOnly: boolean) => void;
23
+ }
24
+
25
+ export type EditorValue = {
26
+ type: string;
27
+ children: unknown;
28
+ }[];
29
+
30
+ export interface RichTextEditorProps {
31
+ /**
32
+ * If true, the editor will be focused when the user enters the screen
33
+ */
34
+ autoFocus?: boolean;
35
+ /**
36
+ * Error message
37
+ */
38
+ error?: string;
39
+ /**
40
+ * Field value
41
+ */
42
+ value?: EditorValue;
43
+ /**
44
+ * Unique name used to communicate with webview
45
+ */
46
+ name: string;
47
+ /**
48
+ * Callback function called when the field value changed
49
+ */
50
+ onChange: (data: EditorValue) => void;
51
+ /**
52
+ * Callback function called when the cursor position changed
53
+ */
54
+ onCursorChange?: (params: { position: { top: number } }) => void;
55
+ /**
56
+ * Field placeholder
57
+ */
58
+ placeholder?: string;
59
+ /**
60
+ * Additional styles
61
+ */
62
+ style?: StyleProp<ViewStyle>;
63
+ /**
64
+ * Field label
65
+ */
66
+ label: string;
67
+ /**
68
+ * Field helper text
69
+ */
70
+ helpText?: string;
71
+ /**
72
+ * Whether the input is required, if true, an asterisk will be appended to the label.
73
+ * */
74
+ required?: boolean;
75
+ /**
76
+ * Testing ID of the component
77
+ */
78
+ testID?: string;
79
+ /**
80
+ * Imperative ref to expose the component method
81
+ */
82
+ forwardedRef?: Ref<RichTextEditorRef>;
83
+ }
84
+
85
+ export interface InternalRichTextEditorProps extends RichTextEditorProps {
86
+ TextInputComponent?: React.ComponentType<TextInputProps>;
87
+ }
@@ -0,0 +1,31 @@
1
+ import { EventEmitter } from 'events';
2
+
3
+ export interface Listener<T> {
4
+ (data: T): void;
5
+ }
6
+
7
+ export const off = <T>(
8
+ emitter: EventEmitter,
9
+ eventName: string,
10
+ listener: Listener<T>
11
+ ) => {
12
+ emitter.off(eventName, listener);
13
+ };
14
+
15
+ export const emit = <T>(emitter: EventEmitter, eventName: string, data: T) =>
16
+ emitter.emit(eventName, data);
17
+
18
+ export const setMaxListeners = (emitter: EventEmitter, n: number) => {
19
+ emitter.setMaxListeners(n);
20
+ };
21
+
22
+ export const on = <T>(
23
+ emitter: EventEmitter,
24
+ eventName: string,
25
+ listener: Listener<T>
26
+ ) => {
27
+ emitter.on(eventName, listener);
28
+ return () => {
29
+ emitter.off(eventName, listener);
30
+ };
31
+ };
@@ -0,0 +1,30 @@
1
+ import WebView from 'react-native-webview';
2
+
3
+ export const requestBlurEditor = (element: WebView): void => {
4
+ element.injectJavaScript(
5
+ '\n window.document.activeElement && window.document.activeElement.blur();\n '
6
+ );
7
+ };
8
+
9
+ export const requestFocusEditor = (element: WebView): void => {
10
+ element.injectJavaScript(
11
+ `
12
+ \n
13
+ var el = window.document.querySelector('[role="textbox"]');
14
+ if (el) el.focus();
15
+ \n
16
+ `
17
+ );
18
+ };
19
+
20
+ export interface WebViewEventMessage {
21
+ type: string;
22
+ data: unknown;
23
+ }
24
+ export const postMessage = (
25
+ element: WebView,
26
+ message: WebViewEventMessage
27
+ ): void => {
28
+ const messageString = JSON.stringify(message);
29
+ element.injectJavaScript(`window.postMessage(${messageString}, '*');`);
30
+ };
@@ -1,9 +1,52 @@
1
- import React from 'react';
2
- import { TextInput as RNTextInput } from 'react-native';
3
- import type { TextInputProps as NativeTextInputProps } from 'react-native';
1
+ import React, { useImperativeHandle, useRef } from 'react';
2
+ import {
3
+ TextInput as RNTextInput,
4
+ TextInputProps as RNTextInputProps,
5
+ } from 'react-native';
4
6
  import { StyledTextInput } from './StyledTextInput';
5
7
  import { useTheme } from '../../theme';
6
- import type { TextInputVariant } from './types';
8
+ import type {
9
+ NativeTextInputProps,
10
+ TextInputRef,
11
+ TextInputVariant,
12
+ } from './types';
13
+
14
+ interface DefaultTextInputProps {
15
+ variant: TextInputVariant;
16
+ /** Native TextInput props */
17
+ nativeInputProps: NativeTextInputProps;
18
+ }
19
+
20
+ const DefaultTextInput = React.forwardRef<TextInputRef, DefaultTextInputProps>(
21
+ ({ variant, nativeInputProps }, ref) => {
22
+ const theme = useTheme();
23
+ const inputRef = useRef<RNTextInput>(null);
24
+ useImperativeHandle(
25
+ ref,
26
+ () => ({
27
+ focus: () => inputRef.current?.focus(),
28
+ blur: () => inputRef.current?.blur(),
29
+ clear: () => inputRef.current?.clear(),
30
+ isFocused: () => inputRef.current?.isFocused() ?? false,
31
+ setNativeProps: (props: RNTextInputProps) => {
32
+ inputRef.current?.setNativeProps(props);
33
+ },
34
+ }),
35
+ []
36
+ );
37
+
38
+ // Default styled input with theme and variant support
39
+ return (
40
+ <StyledTextInput
41
+ {...nativeInputProps}
42
+ themeVariant={variant}
43
+ multiline={variant === 'textarea' || nativeInputProps.multiline}
44
+ ref={inputRef}
45
+ placeholderTextColor={theme.__hd__.textInput.colors.placeholder}
46
+ />
47
+ );
48
+ }
49
+ );
7
50
 
8
51
  export interface InputComponentProps {
9
52
  /** Input type ('text' or 'textarea') */
@@ -11,7 +54,10 @@ export interface InputComponentProps {
11
54
  /** All props passed to the underlying TextInput */
12
55
  nativeInputProps: NativeTextInputProps;
13
56
  /** Optional custom input renderer function */
14
- renderInputValue?: (inputProps: NativeTextInputProps) => React.ReactNode;
57
+ renderInputValue?: (
58
+ inputProps: NativeTextInputProps,
59
+ ref?: React.ForwardedRef<TextInputRef>
60
+ ) => React.ReactNode;
15
61
  }
16
62
 
17
63
  /**
@@ -36,21 +82,16 @@ export interface InputComponentProps {
36
82
  *
37
83
  * @param props - The component props (see InputComponentProps interface for details)
38
84
  */
39
- const InputComponent = React.forwardRef<RNTextInput, InputComponentProps>(
85
+ const InputComponent = React.forwardRef<TextInputRef, InputComponentProps>(
40
86
  ({ variant, nativeInputProps, renderInputValue }, ref) => {
41
- const theme = useTheme();
42
-
43
- return renderInputValue ? (
44
- // Custom input renderer provided
45
- <>{renderInputValue(nativeInputProps)}</>
46
- ) : (
47
- // Default styled input with theme and variant support
48
- <StyledTextInput
49
- {...nativeInputProps}
50
- themeVariant={variant}
51
- multiline={variant === 'textarea' || nativeInputProps.multiline}
87
+ if (renderInputValue) {
88
+ return <>{renderInputValue({ ...nativeInputProps }, ref)}</>;
89
+ }
90
+ return (
91
+ <DefaultTextInput
92
+ variant={variant}
93
+ nativeInputProps={nativeInputProps}
52
94
  ref={ref}
53
- placeholderTextColor={theme.__hd__.textInput.colors.placeholder}
54
95
  />
55
96
  );
56
97
  }
@@ -1,8 +1,7 @@
1
1
  import React, { useCallback, useState } from 'react';
2
- import { TextInput as RNTextInput, View } from 'react-native';
2
+ import { View } from 'react-native';
3
3
  import type {
4
4
  NativeSyntheticEvent,
5
- TextInputProps as NativeTextInputProps,
6
5
  TextInputFocusEventData,
7
6
  } from 'react-native';
8
7
  import { IconName } from '@hero-design/rn';
@@ -10,7 +9,11 @@ import { StyledInputRow } from './StyledTextInput';
10
9
  import PrefixComponent from './PrefixComponent';
11
10
  import InputComponent from './InputComponent';
12
11
  import type { State } from './StyledTextInput';
13
- import type { TextInputVariant } from './types';
12
+ import type {
13
+ NativeTextInputProps,
14
+ TextInputRef,
15
+ TextInputVariant,
16
+ } from './types';
14
17
 
15
18
  interface InputRowProps {
16
19
  /** Current state of the input (focused, error, disabled, etc.) */
@@ -22,7 +25,10 @@ interface InputRowProps {
22
25
  /** Native TextInput props passed to the input component */
23
26
  nativeInputProps: NativeTextInputProps;
24
27
  /** Optional custom render function for input value */
25
- renderInputValue?: (inputProps: NativeTextInputProps) => React.ReactNode;
28
+ renderInputValue?: (
29
+ inputProps: NativeTextInputProps,
30
+ ref?: React.ForwardedRef<TextInputRef>
31
+ ) => React.ReactNode;
26
32
  /** Whether the input value is empty */
27
33
  isEmptyValue: boolean;
28
34
  /** Whether the input should be visible when not focused and empty. Defaults to true. */
@@ -52,7 +58,7 @@ interface InputRowProps {
52
58
  * 1. Conditionally visible prefix component (icon or custom element)
53
59
  * 2. Conditionally visible input component (TextInput or custom rendered input)
54
60
  */
55
- const InputRow = React.forwardRef<RNTextInput, InputRowProps>(
61
+ const InputRow = React.forwardRef<TextInputRef, InputRowProps>(
56
62
  (
57
63
  {
58
64
  state,
@@ -75,7 +81,7 @@ const InputRow = React.forwardRef<RNTextInput, InputRowProps>(
75
81
 
76
82
  // Simplified callback functions (removed unnecessary memoization for simple cases)
77
83
  const handleFocus = useCallback(
78
- (event: NativeSyntheticEvent<TextInputFocusEventData>) => {
84
+ (event?: NativeSyntheticEvent<TextInputFocusEventData>) => {
79
85
  setIsFocused(true);
80
86
  nativeInputProps.onFocus?.(event);
81
87
  },
@@ -83,7 +89,7 @@ const InputRow = React.forwardRef<RNTextInput, InputRowProps>(
83
89
  );
84
90
 
85
91
  const handleBlur = useCallback(
86
- (event: NativeSyntheticEvent<TextInputFocusEventData>) => {
92
+ (event?: NativeSyntheticEvent<TextInputFocusEventData>) => {
87
93
  setIsFocused(false);
88
94
  nativeInputProps.onBlur?.(event);
89
95
  },
@@ -1,11 +1,11 @@
1
1
  import React, { forwardRef, useCallback } from 'react';
2
- import { StyleSheet, TextInput as RNTextInput } from 'react-native';
2
+ import { StyleSheet } from 'react-native';
3
3
  import type {
4
- TextInputProps as NativeTextInputProps,
5
4
  StyleProp,
6
5
  ViewStyle,
7
6
  NativeSyntheticEvent,
8
7
  TextInputFocusEventData,
8
+ ColorValue,
9
9
  } from 'react-native';
10
10
  import {
11
11
  StyledInputWrapper,
@@ -23,8 +23,10 @@ import FloatingLabel from './FloatingLabel';
23
23
  import InputRow from './InputRow';
24
24
  import type {
25
25
  InternalTextInputProps,
26
+ NativeTextInputProps,
26
27
  TextInputHandles,
27
28
  TextInputProps,
29
+ TextInputRef,
28
30
  } from './types';
29
31
  import Group from './Group';
30
32
 
@@ -89,6 +91,17 @@ const extractBorderStyles = (style: StyleProp<ViewStyle>) => {
89
91
  return borderStyles;
90
92
  };
91
93
 
94
+ // Create container style without background color using a helper function
95
+ const getContainerStyle = (
96
+ style: StyleProp<ViewStyle>,
97
+ customBackgroundColor?: ColorValue
98
+ ) => {
99
+ const flattenedStyle = style ? { ...StyleSheet.flatten(style) } : {};
100
+ if (customBackgroundColor) {
101
+ delete flattenedStyle.backgroundColor;
102
+ }
103
+ return flattenedStyle;
104
+ };
92
105
  /**
93
106
  * TextInput Layout Structure:
94
107
  *
@@ -171,7 +184,7 @@ export const InternalTextInput = forwardRef<
171
184
 
172
185
  const theme = useTheme();
173
186
 
174
- const innerTextInput = React.useRef<RNTextInput | null>(null);
187
+ const innerTextInput = React.useRef<TextInputRef | null>(null);
175
188
  React.useImperativeHandle(
176
189
  ref,
177
190
  () => ({
@@ -182,7 +195,7 @@ export const InternalTextInput = forwardRef<
182
195
  },
183
196
  clear: () => innerTextInput.current?.clear(),
184
197
  setNativeProps: (args: NativeTextInputProps) =>
185
- innerTextInput.current?.setNativeProps(args),
198
+ innerTextInput.current?.setNativeProps?.(args),
186
199
  isFocused: () => innerTextInput.current?.isFocused() || false,
187
200
  blur: () => innerTextInput.current?.blur(),
188
201
  }),
@@ -198,7 +211,7 @@ export const InternalTextInput = forwardRef<
198
211
 
199
212
  // Simplified callback functions (removed unnecessary memoization for simple cases)
200
213
  const handleFocus = useCallback(
201
- (event: NativeSyntheticEvent<TextInputFocusEventData>) => {
214
+ (event?: NativeSyntheticEvent<TextInputFocusEventData>) => {
202
215
  setIsFocused(true);
203
216
  onFocus?.(event);
204
217
  },
@@ -206,7 +219,7 @@ export const InternalTextInput = forwardRef<
206
219
  );
207
220
 
208
221
  const handleBlur = useCallback(
209
- (event: NativeSyntheticEvent<TextInputFocusEventData>) => {
222
+ (event?: NativeSyntheticEvent<TextInputFocusEventData>) => {
210
223
  setIsFocused(false);
211
224
  onBlur?.(event);
212
225
  },
@@ -265,11 +278,7 @@ export const InternalTextInput = forwardRef<
265
278
  accessibilityLabel,
266
279
  };
267
280
 
268
- // Create container style without background color
269
- const containerStyle = style ? { ...StyleSheet.flatten(style) } : {};
270
- if (customBackgroundColor) {
271
- delete containerStyle.backgroundColor;
272
- }
281
+ const containerStyle = getContainerStyle(style, customBackgroundColor);
273
282
 
274
283
  const isDisabledOrReadonly = state === 'disabled' || state === 'readonly';
275
284
 
@@ -1,9 +1,11 @@
1
1
  import type {
2
- TextInputProps as NativeTextInputProps,
2
+ TextInputProps as RNTextInputProps,
3
3
  StyleProp,
4
4
  ViewStyle,
5
5
  TextStyle,
6
6
  TextInput as RNTextInput,
7
+ NativeSyntheticEvent,
8
+ TextInputFocusEventData,
7
9
  } from 'react-native';
8
10
  import type { IconName } from '@hero-design/rn';
9
11
 
@@ -14,7 +16,27 @@ export type TextInputHandles = Pick<
14
16
 
15
17
  export type TextInputVariant = 'text' | 'textarea';
16
18
 
17
- export interface TextInputProps extends NativeTextInputProps {
19
+ export type NativeTextInputProps = Omit<
20
+ RNTextInputProps,
21
+ 'onFocus' | 'onBlur'
22
+ > & {
23
+ onFocus?: (
24
+ event?: NativeSyntheticEvent<TextInputFocusEventData>
25
+ ) => void | undefined;
26
+ onBlur?: (
27
+ event?: NativeSyntheticEvent<TextInputFocusEventData>
28
+ ) => void | undefined;
29
+ };
30
+
31
+ export interface TextInputRef {
32
+ focus: () => void;
33
+ blur: () => void;
34
+ clear: () => void;
35
+ isFocused: () => boolean;
36
+ setNativeProps?: (props: RNTextInputProps) => void;
37
+ }
38
+
39
+ export type TextInputProps = NativeTextInputProps & {
18
40
  /**
19
41
  * Field label.
20
42
  */
@@ -83,7 +105,10 @@ export interface TextInputProps extends NativeTextInputProps {
83
105
  /**
84
106
  * Customise input value renderer
85
107
  */
86
- renderInputValue?: (inputProps: NativeTextInputProps) => React.ReactNode;
108
+ renderInputValue?: (
109
+ inputProps: NativeTextInputProps,
110
+ ref?: React.ForwardedRef<TextInputRef>
111
+ ) => React.ReactNode;
87
112
  /**
88
113
  * Component ref.
89
114
  */
@@ -92,7 +117,7 @@ export interface TextInputProps extends NativeTextInputProps {
92
117
  * Component variant.
93
118
  */
94
119
  variant?: TextInputVariant;
95
- }
120
+ };
96
121
 
97
122
  export interface InternalTextInputProps extends TextInputProps {
98
123
  /**
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ import TextInput from './components/TextInput';
3
3
  import Select from './components/Select';
4
4
  import DatePicker from './components/DatePicker';
5
5
  import TimePicker from './components/TimePicker';
6
+ import RichTextEditor from './components/RichTextEditor';
6
7
 
7
8
  export * from '@hero-design/rn';
8
9
 
@@ -17,4 +18,4 @@ export {
17
18
  } from './theme';
18
19
 
19
20
  export { default as theme } from './theme';
20
- export { TextInput, Select, DatePicker, TimePicker };
21
+ export { TextInput, Select, DatePicker, TimePicker, RichTextEditor };