@hero-design/rn-work-uikit 1.1.0 → 1.2.0-alpha.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/.cursorrules +57 -0
- package/CHANGELOG.md +6 -0
- package/DEVELOPMENT.md +118 -0
- package/eslint.config.js +20 -0
- package/lib/index.js +871 -5
- package/package.json +4 -1
- package/src/__tests__/__snapshots__/index.spec.tsx.snap +90 -115
- package/src/__tests__/theme-export-override.spec.ts +6 -0
- package/src/components/TextInput/ErrorOrHelpText.tsx +58 -0
- package/src/components/TextInput/FloatingLabel.tsx +120 -0
- package/src/components/TextInput/InputComponent.tsx +61 -0
- package/src/components/TextInput/InputRow.tsx +103 -0
- package/src/components/TextInput/MaxLengthMessage.tsx +66 -0
- package/src/components/TextInput/PrefixComponent.tsx +77 -0
- package/src/components/TextInput/StyledTextInput.tsx +134 -0
- package/src/components/TextInput/SuffixComponent.tsx +73 -0
- package/src/components/TextInput/__tests__/ErrorOrHelpText.spec.tsx +20 -0
- package/src/components/TextInput/__tests__/FloatingLabel.spec.tsx +203 -0
- package/src/components/TextInput/__tests__/InputComponent.spec.tsx +39 -0
- package/src/components/TextInput/__tests__/InputRow.spec.tsx +275 -0
- package/src/components/TextInput/__tests__/MaxLengthMessage.spec.tsx +17 -0
- package/src/components/TextInput/__tests__/PrefixComponent.spec.tsx +14 -0
- package/src/components/TextInput/__tests__/StyledTextInput.spec.tsx +114 -0
- package/src/components/TextInput/__tests__/SuffixComponent.spec.tsx +20 -0
- package/src/components/TextInput/__tests__/__snapshots__/StyledTextInput.spec.tsx.snap +571 -0
- package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +5671 -0
- package/src/components/TextInput/__tests__/getState.spec.tsx +89 -0
- package/src/components/TextInput/__tests__/index.spec.tsx +699 -0
- package/src/components/TextInput/constants.ts +1 -0
- package/src/components/TextInput/index.tsx +327 -0
- package/src/components/TextInput/types.ts +95 -0
- package/src/emotion.d.ts +15 -0
- package/src/index.ts +3 -0
- package/src/jest.d.ts +24 -0
- package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +15 -8
- package/src/theme/components/textInput.ts +33 -0
- package/src/utils/__tests__/helpers.spec.ts +92 -0
- package/src/utils/helpers.ts +113 -0
- package/testUtils/renderWithTheme.tsx +6 -3
- package/stats/1.1.0/rn-work-uikit-stats.html +0 -4842
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import React, { forwardRef, useCallback } from 'react';
|
|
2
|
+
import { StyleSheet, TextInput as RNTextInput } from 'react-native';
|
|
3
|
+
import type {
|
|
4
|
+
TextInputProps as NativeTextInputProps,
|
|
5
|
+
StyleProp,
|
|
6
|
+
ViewStyle,
|
|
7
|
+
NativeSyntheticEvent,
|
|
8
|
+
TextInputFocusEventData,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import {
|
|
11
|
+
StyledInputWrapper,
|
|
12
|
+
StyledContainer,
|
|
13
|
+
StyledBorder,
|
|
14
|
+
StyledBottomContainer,
|
|
15
|
+
StyledSuffixContainer,
|
|
16
|
+
} from './StyledTextInput';
|
|
17
|
+
import { useTheme } from '../../theme';
|
|
18
|
+
import type { State } from './StyledTextInput';
|
|
19
|
+
import ErrorOrHelpText from './ErrorOrHelpText';
|
|
20
|
+
import SuffixComponent from './SuffixComponent';
|
|
21
|
+
import MaxLengthMessage from './MaxLengthMessage';
|
|
22
|
+
import FloatingLabel from './FloatingLabel';
|
|
23
|
+
import InputRow from './InputRow';
|
|
24
|
+
import type { TextInputHandles, TextInputProps } from './types';
|
|
25
|
+
|
|
26
|
+
export type {
|
|
27
|
+
TextInputHandles,
|
|
28
|
+
TextInputVariant,
|
|
29
|
+
TextInputProps,
|
|
30
|
+
} from './types';
|
|
31
|
+
|
|
32
|
+
export const getState = ({
|
|
33
|
+
disabled,
|
|
34
|
+
error,
|
|
35
|
+
editable,
|
|
36
|
+
loading,
|
|
37
|
+
isEmptyValue,
|
|
38
|
+
}: {
|
|
39
|
+
disabled?: boolean;
|
|
40
|
+
error?: string;
|
|
41
|
+
editable?: boolean;
|
|
42
|
+
loading: boolean;
|
|
43
|
+
isEmptyValue?: boolean;
|
|
44
|
+
}): State => {
|
|
45
|
+
if (disabled) {
|
|
46
|
+
return 'disabled';
|
|
47
|
+
}
|
|
48
|
+
if (error) {
|
|
49
|
+
return 'error';
|
|
50
|
+
}
|
|
51
|
+
if (!editable || loading) {
|
|
52
|
+
return 'readonly';
|
|
53
|
+
}
|
|
54
|
+
if (!isEmptyValue) {
|
|
55
|
+
return 'filled';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return 'default';
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Fix issue: Placeholder is not shown on iOS when multiline is true
|
|
62
|
+
// https://github.com/callstack/react-native-paper/pull/3331
|
|
63
|
+
const EMPTY_PLACEHOLDER_VALUE = ' ';
|
|
64
|
+
|
|
65
|
+
// Simplified style extraction functions
|
|
66
|
+
const extractBackgroundColor = (style: StyleProp<ViewStyle>) => {
|
|
67
|
+
const flattened = StyleSheet.flatten(style);
|
|
68
|
+
return flattened?.backgroundColor;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const extractBorderStyles = (style: StyleProp<ViewStyle>) => {
|
|
72
|
+
const flattened = StyleSheet.flatten(style);
|
|
73
|
+
if (!flattened) return {};
|
|
74
|
+
|
|
75
|
+
const borderKeys = Object.keys(flattened).filter((key) =>
|
|
76
|
+
key.startsWith('border')
|
|
77
|
+
);
|
|
78
|
+
const borderStyles: Record<string, unknown> = {};
|
|
79
|
+
|
|
80
|
+
borderKeys.forEach((key) => {
|
|
81
|
+
borderStyles[key] = flattened[key as keyof typeof flattened];
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return borderStyles;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* TextInput Layout Structure:
|
|
89
|
+
*
|
|
90
|
+
* ┌─────────────────────────────────────────────────────────────────────────┐
|
|
91
|
+
* │ StyledContainer (with StyledBorder overlay) │
|
|
92
|
+
* │ ┌─────────────────────────────────────────────────────────┐ │
|
|
93
|
+
* │ │ StyledInputWrapper │ │
|
|
94
|
+
* │ │ ┌─────────────────────────────────────────────────┐ │ │
|
|
95
|
+
* │ │ │ FloatingLabel (Optional) │ │ │
|
|
96
|
+
* │ │ │ "Label" or animated position │ │ ┌──────┐ │
|
|
97
|
+
* │ │ └─────────────────────────────────────────────────┘ │ │Suffix│ │
|
|
98
|
+
* │ │ │ │ Icon │ │
|
|
99
|
+
* │ │ ┌─────────────────────────────────────────────────┐ │ │ or │ │
|
|
100
|
+
* │ │ │ InputRow Component │ │ │Custom│ │
|
|
101
|
+
* │ │ │ ┌──────────┐ ┌─────────────────────────────┐ │ │ │(V- │ │
|
|
102
|
+
* │ │ │ │ Prefix │ │ Input Field │ │ │ │Center│ │
|
|
103
|
+
* │ │ │ │(Animated)│ │ (Animated TextInput) │ │ │ │ ed) │ │
|
|
104
|
+
* │ │ │ └──────────┘ └─────────────────────────────┘ │ │ └──────┘ │
|
|
105
|
+
* │ │ └─────────────────────────────────────────────────┘ │ │
|
|
106
|
+
* │ │ │ │
|
|
107
|
+
* │ │ ┌─────────────────────────────────────────────────┐ │ │
|
|
108
|
+
* │ │ │ StyledBottomContainer │ │ │
|
|
109
|
+
* │ │ │ ┌─────────────────┐ ┌─────────────────────┐ │ │ │
|
|
110
|
+
* │ │ │ │ Error/Help Text │ │ Character Count │ │ │ │
|
|
111
|
+
* │ │ │ │ (flex: 4) │ │ (flex: 1) │ │ │ │
|
|
112
|
+
* │ │ │ └─────────────────┘ └─────────────────────┘ │ │ │
|
|
113
|
+
* │ │ └─────────────────────────────────────────────────┘ │ │
|
|
114
|
+
* │ └─────────────────────────────────────────────────────────┘ │
|
|
115
|
+
* └─────────────────────────────────────────────────────────────────────────┘
|
|
116
|
+
*
|
|
117
|
+
* Note: StyledBorder uses StyleSheet.absoluteFillObject to overlay the entire
|
|
118
|
+
* StyledContainer, providing the border and background styling.
|
|
119
|
+
*/
|
|
120
|
+
const TextInput = forwardRef<TextInputHandles, TextInputProps>(
|
|
121
|
+
(
|
|
122
|
+
{
|
|
123
|
+
label,
|
|
124
|
+
prefix,
|
|
125
|
+
suffix,
|
|
126
|
+
style,
|
|
127
|
+
textStyle,
|
|
128
|
+
testID,
|
|
129
|
+
accessibilityLabelledBy,
|
|
130
|
+
error,
|
|
131
|
+
required,
|
|
132
|
+
editable = true,
|
|
133
|
+
disabled = false,
|
|
134
|
+
loading = false,
|
|
135
|
+
maxLength,
|
|
136
|
+
hideCharacterCount = false,
|
|
137
|
+
helpText,
|
|
138
|
+
value,
|
|
139
|
+
defaultValue,
|
|
140
|
+
renderInputValue,
|
|
141
|
+
allowFontScaling = false,
|
|
142
|
+
variant = 'text',
|
|
143
|
+
...nativeProps
|
|
144
|
+
}: TextInputProps,
|
|
145
|
+
ref?: React.Ref<TextInputHandles>
|
|
146
|
+
) => {
|
|
147
|
+
// Inline the simple getDisplayText function
|
|
148
|
+
const displayText = (value !== undefined ? value : defaultValue) ?? '';
|
|
149
|
+
const isEmptyValue = displayText.length === 0;
|
|
150
|
+
|
|
151
|
+
const [isFocused, setIsFocused] = React.useState(false);
|
|
152
|
+
|
|
153
|
+
const state = getState({
|
|
154
|
+
disabled,
|
|
155
|
+
error,
|
|
156
|
+
editable,
|
|
157
|
+
loading,
|
|
158
|
+
isEmptyValue,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const theme = useTheme();
|
|
162
|
+
|
|
163
|
+
const innerTextInput = React.useRef<RNTextInput | null>(null);
|
|
164
|
+
React.useImperativeHandle(
|
|
165
|
+
ref,
|
|
166
|
+
() => ({
|
|
167
|
+
// we don't expose this method, it's for testing https://medium.com/developer-rants/how-to-test-useref-without-mocking-useref-699165f4994e
|
|
168
|
+
getNativeTextInputRef: () => innerTextInput.current,
|
|
169
|
+
focus: () => {
|
|
170
|
+
innerTextInput.current?.focus();
|
|
171
|
+
},
|
|
172
|
+
clear: () => innerTextInput.current?.clear(),
|
|
173
|
+
setNativeProps: (args: NativeTextInputProps) =>
|
|
174
|
+
innerTextInput.current?.setNativeProps(args),
|
|
175
|
+
isFocused: () => innerTextInput.current?.isFocused() || false,
|
|
176
|
+
blur: () => innerTextInput.current?.blur(),
|
|
177
|
+
}),
|
|
178
|
+
[innerTextInput]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Simplified style extraction
|
|
182
|
+
const borderStyles = extractBorderStyles(textStyle);
|
|
183
|
+
const customBackgroundColor = extractBackgroundColor(style);
|
|
184
|
+
const backgroundColor =
|
|
185
|
+
customBackgroundColor ??
|
|
186
|
+
theme.__hd__.textInput.colors.containerBackground;
|
|
187
|
+
|
|
188
|
+
// Simplified callback functions (removed unnecessary memoization for simple cases)
|
|
189
|
+
const handleFocus = useCallback(
|
|
190
|
+
(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
191
|
+
setIsFocused(true);
|
|
192
|
+
nativeProps.onFocus?.(event);
|
|
193
|
+
},
|
|
194
|
+
[nativeProps]
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const handleBlur = useCallback(
|
|
198
|
+
(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
199
|
+
setIsFocused(false);
|
|
200
|
+
nativeProps.onBlur?.(event);
|
|
201
|
+
},
|
|
202
|
+
[nativeProps]
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const handleChangeText = useCallback(
|
|
206
|
+
(text: string) => {
|
|
207
|
+
nativeProps.onChangeText?.(text);
|
|
208
|
+
},
|
|
209
|
+
[nativeProps]
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// Simplified callbacks - these don't need memoization as they're stable
|
|
213
|
+
const handleContainerPress = () => {
|
|
214
|
+
innerTextInput.current?.focus();
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Create text input style without border properties
|
|
218
|
+
const textInputStyle = textStyle
|
|
219
|
+
? { ...StyleSheet.flatten(textStyle) }
|
|
220
|
+
: {};
|
|
221
|
+
Object.keys(borderStyles).forEach((key) => {
|
|
222
|
+
delete textInputStyle[key as keyof typeof textInputStyle];
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const nativeInputProps: NativeTextInputProps = {
|
|
226
|
+
style: StyleSheet.flatten([
|
|
227
|
+
{
|
|
228
|
+
backgroundColor,
|
|
229
|
+
color: theme.__hd__.textInput.colors.text,
|
|
230
|
+
},
|
|
231
|
+
textInputStyle,
|
|
232
|
+
]),
|
|
233
|
+
testID: 'text-input',
|
|
234
|
+
accessibilityState: {
|
|
235
|
+
disabled: state === 'disabled' || state === 'readonly',
|
|
236
|
+
},
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
238
|
+
// @ts-ignore
|
|
239
|
+
accessibilityLabelledBy,
|
|
240
|
+
allowFontScaling,
|
|
241
|
+
...nativeProps,
|
|
242
|
+
onFocus: handleFocus,
|
|
243
|
+
onBlur: handleBlur,
|
|
244
|
+
onChangeText: handleChangeText,
|
|
245
|
+
editable,
|
|
246
|
+
maxLength,
|
|
247
|
+
value,
|
|
248
|
+
defaultValue,
|
|
249
|
+
placeholder:
|
|
250
|
+
isFocused || label === undefined
|
|
251
|
+
? nativeProps.placeholder
|
|
252
|
+
: EMPTY_PLACEHOLDER_VALUE,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Create container style without background color
|
|
256
|
+
const containerStyle = style ? { ...StyleSheet.flatten(style) } : {};
|
|
257
|
+
if (customBackgroundColor) {
|
|
258
|
+
delete containerStyle.backgroundColor;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const isDisabledOrReadonly = state === 'disabled' || state === 'readonly';
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<StyledContainer
|
|
265
|
+
style={containerStyle}
|
|
266
|
+
onPress={handleContainerPress}
|
|
267
|
+
disabled={isDisabledOrReadonly}
|
|
268
|
+
accessibilityState={{
|
|
269
|
+
disabled: isDisabledOrReadonly,
|
|
270
|
+
}}
|
|
271
|
+
testID={testID}
|
|
272
|
+
>
|
|
273
|
+
{/*
|
|
274
|
+
StyledBorder: Absolute positioned overlay covering entire container
|
|
275
|
+
- Uses StyleSheet.absoluteFillObject to cover full StyledContainer
|
|
276
|
+
- Provides border styling and background color
|
|
277
|
+
- pointerEvents="none" to allow interaction with underlying components
|
|
278
|
+
*/}
|
|
279
|
+
<StyledBorder
|
|
280
|
+
themeFocused={isFocused}
|
|
281
|
+
themeState={state}
|
|
282
|
+
testID="text-input-border"
|
|
283
|
+
pointerEvents="none"
|
|
284
|
+
style={[{ backgroundColor }, borderStyles]}
|
|
285
|
+
/>
|
|
286
|
+
<StyledInputWrapper>
|
|
287
|
+
{!!label && (
|
|
288
|
+
<FloatingLabel
|
|
289
|
+
label={label}
|
|
290
|
+
variant={variant}
|
|
291
|
+
state={state}
|
|
292
|
+
isFocused={isFocused}
|
|
293
|
+
required={required}
|
|
294
|
+
accessibilityLabelledBy={accessibilityLabelledBy}
|
|
295
|
+
isEmptyValue={isEmptyValue}
|
|
296
|
+
/>
|
|
297
|
+
)}
|
|
298
|
+
<InputRow
|
|
299
|
+
state={state}
|
|
300
|
+
isFocused={isFocused}
|
|
301
|
+
prefix={prefix}
|
|
302
|
+
variant={variant}
|
|
303
|
+
nativeInputProps={nativeInputProps}
|
|
304
|
+
renderInputValue={renderInputValue}
|
|
305
|
+
ref={innerTextInput}
|
|
306
|
+
isEmptyValue={isEmptyValue}
|
|
307
|
+
/>
|
|
308
|
+
|
|
309
|
+
<StyledBottomContainer>
|
|
310
|
+
<ErrorOrHelpText error={error} helpText={helpText} />
|
|
311
|
+
<MaxLengthMessage
|
|
312
|
+
state={state}
|
|
313
|
+
currentLength={displayText.length}
|
|
314
|
+
maxLength={maxLength}
|
|
315
|
+
hideCharacterCount={hideCharacterCount}
|
|
316
|
+
/>
|
|
317
|
+
</StyledBottomContainer>
|
|
318
|
+
</StyledInputWrapper>
|
|
319
|
+
<StyledSuffixContainer>
|
|
320
|
+
<SuffixComponent state={state} loading={loading} suffix={suffix} />
|
|
321
|
+
</StyledSuffixContainer>
|
|
322
|
+
</StyledContainer>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
export default TextInput;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TextInputProps as NativeTextInputProps,
|
|
3
|
+
StyleProp,
|
|
4
|
+
ViewStyle,
|
|
5
|
+
TextStyle,
|
|
6
|
+
TextInput as RNTextInput,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import type { IconName } from '@hero-design/rn';
|
|
9
|
+
|
|
10
|
+
export type TextInputHandles = Pick<
|
|
11
|
+
RNTextInput,
|
|
12
|
+
'focus' | 'clear' | 'blur' | 'isFocused' | 'setNativeProps'
|
|
13
|
+
>;
|
|
14
|
+
|
|
15
|
+
export type TextInputVariant = 'text' | 'textarea';
|
|
16
|
+
|
|
17
|
+
export interface TextInputProps extends NativeTextInputProps {
|
|
18
|
+
/**
|
|
19
|
+
* Field label.
|
|
20
|
+
*/
|
|
21
|
+
label?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Name of Icon or ReactElement to render on the left side of the input, before the user's cursor.
|
|
24
|
+
*/
|
|
25
|
+
prefix?: IconName | React.ReactElement;
|
|
26
|
+
/**
|
|
27
|
+
* Name of Icon or ReactElement to render on the right side of the input.
|
|
28
|
+
*/
|
|
29
|
+
suffix?: IconName | React.ReactElement;
|
|
30
|
+
/**
|
|
31
|
+
* Additional wrapper style.
|
|
32
|
+
*/
|
|
33
|
+
style?: StyleProp<ViewStyle>;
|
|
34
|
+
/**
|
|
35
|
+
* Input text style.
|
|
36
|
+
*/
|
|
37
|
+
textStyle?: StyleProp<TextStyle>;
|
|
38
|
+
/**
|
|
39
|
+
* Testing id of the component.
|
|
40
|
+
*/
|
|
41
|
+
testID?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Accessibility label for the input (Android).
|
|
44
|
+
*/
|
|
45
|
+
accessibilityLabelledBy?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Error message to display.
|
|
48
|
+
*/
|
|
49
|
+
error?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the input is required. When false, "(Optional)" will be appended to the label.
|
|
52
|
+
*/
|
|
53
|
+
required?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Placeholder text to display.
|
|
56
|
+
* */
|
|
57
|
+
placeholder?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the input is editable.
|
|
60
|
+
* */
|
|
61
|
+
editable?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Whether the input is disabled.
|
|
64
|
+
*/
|
|
65
|
+
disabled?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Whether the input is loading.
|
|
68
|
+
*/
|
|
69
|
+
loading?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* The max length of the input.
|
|
72
|
+
* If the max length is set, the input will display the current length and the max length.
|
|
73
|
+
* */
|
|
74
|
+
maxLength?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Whether to hide the character count.
|
|
77
|
+
* */
|
|
78
|
+
hideCharacterCount?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* The helper text to display.
|
|
81
|
+
*/
|
|
82
|
+
helpText?: string;
|
|
83
|
+
/**
|
|
84
|
+
* Customise input value renderer
|
|
85
|
+
*/
|
|
86
|
+
renderInputValue?: (inputProps: NativeTextInputProps) => React.ReactNode;
|
|
87
|
+
/**
|
|
88
|
+
* Component ref.
|
|
89
|
+
*/
|
|
90
|
+
ref?: React.Ref<TextInputHandles>;
|
|
91
|
+
/**
|
|
92
|
+
* Component variant.
|
|
93
|
+
*/
|
|
94
|
+
variant?: TextInputVariant;
|
|
95
|
+
}
|
package/src/emotion.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import '@emotion/react';
|
|
2
|
+
import '@emotion/native';
|
|
3
|
+
import type { Theme as WorkTheme } from './theme';
|
|
4
|
+
|
|
5
|
+
// Augment the emotion theme for both @emotion/react and @emotion/native
|
|
6
|
+
declare module '@emotion/react' {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
8
|
+
export interface Theme extends WorkTheme {}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// This should make styled components automatically get the Theme type
|
|
12
|
+
declare module '@emotion/native' {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
14
|
+
export interface Theme extends WorkTheme {}
|
|
15
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// Re-export everything from @hero-design/rn except theme exports we want to override
|
|
2
|
+
import TextInput from './components/TextInput';
|
|
3
|
+
|
|
2
4
|
export * from '@hero-design/rn';
|
|
3
5
|
|
|
4
6
|
// Override theme exports with work-specific versions
|
|
@@ -12,3 +14,4 @@ export {
|
|
|
12
14
|
} from './theme';
|
|
13
15
|
|
|
14
16
|
export { default as theme } from './theme';
|
|
17
|
+
export { TextInput };
|
package/src/jest.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
namespace jest {
|
|
3
|
+
interface Matchers<R> {
|
|
4
|
+
toHaveProp(prop: string, value?: unknown): R;
|
|
5
|
+
toHaveStyle(style: Record<string, unknown>): R;
|
|
6
|
+
toBeDisabled(): R;
|
|
7
|
+
toBeEnabled(): R;
|
|
8
|
+
toBeVisible(): R;
|
|
9
|
+
toBeEmptyElement(): R;
|
|
10
|
+
toHaveTextContent(text: string | RegExp): R;
|
|
11
|
+
toHaveDisplayValue(value: string | RegExp): R;
|
|
12
|
+
toBeSelected(): R;
|
|
13
|
+
toBePartiallyChecked(): R;
|
|
14
|
+
toBeChecked(): R;
|
|
15
|
+
toHaveA11yLabel(label: string | RegExp): R;
|
|
16
|
+
toHaveA11yHint(hint: string | RegExp): R;
|
|
17
|
+
toHaveA11yRole(role: string): R;
|
|
18
|
+
toHaveA11yState(state: Record<string, boolean | string>): R;
|
|
19
|
+
toHaveA11yValue(value: Record<string, unknown>): R;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {};
|
|
@@ -1317,7 +1317,7 @@ exports[`theme returns correct theme object 1`] = `
|
|
|
1317
1317
|
"borderWidths": {
|
|
1318
1318
|
"container": {
|
|
1319
1319
|
"focused": 2,
|
|
1320
|
-
"normal":
|
|
1320
|
+
"normal": 2,
|
|
1321
1321
|
},
|
|
1322
1322
|
},
|
|
1323
1323
|
"colors": {
|
|
@@ -1329,20 +1329,22 @@ exports[`theme returns correct theme object 1`] = `
|
|
|
1329
1329
|
"readonly": "#808f91",
|
|
1330
1330
|
},
|
|
1331
1331
|
"borders": {
|
|
1332
|
-
"default": "#
|
|
1332
|
+
"default": "#e8e9ea",
|
|
1333
1333
|
"disabled": "#bfc1c5",
|
|
1334
1334
|
"error": "#cb300a",
|
|
1335
|
-
"filled": "#
|
|
1336
|
-
"
|
|
1335
|
+
"filled": "#e8e9ea",
|
|
1336
|
+
"focused": "#001f23",
|
|
1337
|
+
"readonly": "#bfc1c5",
|
|
1337
1338
|
},
|
|
1338
1339
|
"containerBackground": "#ffffff",
|
|
1339
1340
|
"error": "#cb300a",
|
|
1340
1341
|
"labelBackground": "#ffffff",
|
|
1341
1342
|
"labels": {
|
|
1342
|
-
"default": "#
|
|
1343
|
+
"default": "#808f91",
|
|
1343
1344
|
"disabled": "#bfc1c5",
|
|
1344
|
-
"error": "#
|
|
1345
|
-
"filled": "#
|
|
1345
|
+
"error": "#cb300a",
|
|
1346
|
+
"filled": "#808f91",
|
|
1347
|
+
"focused": "#001f23",
|
|
1346
1348
|
"readonly": "#808f91",
|
|
1347
1349
|
},
|
|
1348
1350
|
"labelsInsideTextInput": {
|
|
@@ -1357,6 +1359,7 @@ exports[`theme returns correct theme object 1`] = `
|
|
|
1357
1359
|
"disabled": "#bfc1c5",
|
|
1358
1360
|
"error": "#cb300a",
|
|
1359
1361
|
"filled": "#001f23",
|
|
1362
|
+
"focused": "#001f23",
|
|
1360
1363
|
"readonly": "#808f91",
|
|
1361
1364
|
},
|
|
1362
1365
|
"placeholder": "#4d6265",
|
|
@@ -1380,8 +1383,10 @@ exports[`theme returns correct theme object 1`] = `
|
|
|
1380
1383
|
"container": 8,
|
|
1381
1384
|
},
|
|
1382
1385
|
"sizes": {
|
|
1383
|
-
"
|
|
1386
|
+
"containerMinHeight": 54.92307692307692,
|
|
1387
|
+
"errorAndHelpTextContainerHeight": 0,
|
|
1384
1388
|
"textInputMaxHeight": 141.23076923076923,
|
|
1389
|
+
"textInputMinHeight": 23.53846153846154,
|
|
1385
1390
|
"textareaHeight": 141.23076923076923,
|
|
1386
1391
|
},
|
|
1387
1392
|
"space": {
|
|
@@ -1392,11 +1397,13 @@ exports[`theme returns correct theme object 1`] = `
|
|
|
1392
1397
|
"errorContainerMarginRight": 3.9230769230769234,
|
|
1393
1398
|
"errorMarginLeft": 3.9230769230769234,
|
|
1394
1399
|
"inputHorizontalMargin": 7.846153846153847,
|
|
1400
|
+
"inputWrapperMarginVertical": 7.846153846153847,
|
|
1395
1401
|
"labelHorizontalPadding": 3.9230769230769234,
|
|
1396
1402
|
"labelInsideTextInputMarginTop": -1.9615384615384617,
|
|
1397
1403
|
"labelLeft": 31.384615384615387,
|
|
1398
1404
|
"labelPaddingBottom": 7.846153846153847,
|
|
1399
1405
|
"labelTop": 10.461538461538462,
|
|
1406
|
+
"prefixAndInputContainerGap": 3.9230769230769234,
|
|
1400
1407
|
},
|
|
1401
1408
|
},
|
|
1402
1409
|
"timePicker": {
|
|
@@ -8,12 +8,38 @@ const getTextInputTheme = (theme: Theme) => {
|
|
|
8
8
|
...baseTextInputTheme.colors,
|
|
9
9
|
// Example: override specific colors
|
|
10
10
|
// text: theme.colors.customTextColor,
|
|
11
|
+
borders: {
|
|
12
|
+
default: theme.colors.secondaryOutline,
|
|
13
|
+
error: theme.colors.onErrorSurface,
|
|
14
|
+
disabled: theme.colors.disabledOutline,
|
|
15
|
+
readonly: theme.colors.disabledOutline,
|
|
16
|
+
filled: theme.colors.secondaryOutline,
|
|
17
|
+
focused: theme.colors.primaryOutline,
|
|
18
|
+
},
|
|
19
|
+
labels: {
|
|
20
|
+
default: theme.colors.inactiveOnDefaultGlobalSurface,
|
|
21
|
+
error: theme.colors.onErrorSurface,
|
|
22
|
+
disabled: theme.colors.disabledOnDefaultGlobalSurface,
|
|
23
|
+
readonly: theme.colors.inactiveOnDefaultGlobalSurface,
|
|
24
|
+
filled: theme.colors.inactiveOnDefaultGlobalSurface,
|
|
25
|
+
focused: theme.colors.onDefaultGlobalSurface,
|
|
26
|
+
},
|
|
27
|
+
maxLengthLabels: {
|
|
28
|
+
default: theme.colors.onDefaultGlobalSurface,
|
|
29
|
+
error: theme.colors.onErrorSurface,
|
|
30
|
+
disabled: theme.colors.disabledOnDefaultGlobalSurface,
|
|
31
|
+
readonly: theme.colors.inactiveOnDefaultGlobalSurface,
|
|
32
|
+
filled: theme.colors.onDefaultGlobalSurface,
|
|
33
|
+
focused: theme.colors.onDefaultGlobalSurface,
|
|
34
|
+
},
|
|
11
35
|
};
|
|
12
36
|
|
|
13
37
|
const space = {
|
|
14
38
|
...baseTextInputTheme.space,
|
|
15
39
|
// Example: override specific spacing
|
|
16
40
|
// containerPadding: theme.space.large,
|
|
41
|
+
inputWrapperMarginVertical: theme.space.small,
|
|
42
|
+
prefixAndInputContainerGap: theme.space.xsmall,
|
|
17
43
|
};
|
|
18
44
|
|
|
19
45
|
const fonts = {
|
|
@@ -30,6 +56,10 @@ const getTextInputTheme = (theme: Theme) => {
|
|
|
30
56
|
|
|
31
57
|
const borderWidths = {
|
|
32
58
|
...baseTextInputTheme.borderWidths,
|
|
59
|
+
container: {
|
|
60
|
+
...baseTextInputTheme.borderWidths.container,
|
|
61
|
+
normal: theme.borderWidths.medium,
|
|
62
|
+
},
|
|
33
63
|
};
|
|
34
64
|
|
|
35
65
|
const radii = {
|
|
@@ -38,6 +68,9 @@ const getTextInputTheme = (theme: Theme) => {
|
|
|
38
68
|
|
|
39
69
|
const sizes = {
|
|
40
70
|
...baseTextInputTheme.sizes,
|
|
71
|
+
containerMinHeight: theme.sizes.xxxxlarge,
|
|
72
|
+
errorAndHelpTextContainerHeight: 0,
|
|
73
|
+
textInputMinHeight: baseTextInputTheme.fontSizes.text + theme.space.small,
|
|
41
74
|
};
|
|
42
75
|
|
|
43
76
|
const lineHeights = {
|