@artsy/palette-mobile 13.2.2 → 13.2.3
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.
|
@@ -4,7 +4,7 @@ import { EventEmitter } from "events";
|
|
|
4
4
|
import { TextInput, TextInputProps } from "react-native";
|
|
5
5
|
export declare const inputEvents: EventEmitter;
|
|
6
6
|
export declare const emitInputClearEvent: () => void;
|
|
7
|
-
export interface InputProps extends TextInputProps {
|
|
7
|
+
export interface InputProps extends Omit<TextInputProps, "placeholder"> {
|
|
8
8
|
addClearListener?: boolean;
|
|
9
9
|
/**
|
|
10
10
|
* We are applying some optimisations to make sure the UX is smooth
|
|
@@ -22,6 +22,27 @@ export interface InputProps extends TextInputProps {
|
|
|
22
22
|
onHintPress?: () => void;
|
|
23
23
|
onSelectTap?: () => void;
|
|
24
24
|
optional?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The placeholder can be an array of string, specifically for android, because of a bug.
|
|
27
|
+
* On ios, the longest string will always be picked, as ios can add ellipsis.
|
|
28
|
+
* On android, the longest string **that fits** will be picked, as android doesn't use ellipsis.
|
|
29
|
+
* The way to use it is to put the longest string first, and the shortest string last.
|
|
30
|
+
*
|
|
31
|
+
* Check `HACKS.md` for more info.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* const placeholders = [
|
|
35
|
+
* "Wow this is a great and very long placeholder",
|
|
36
|
+
* "Wow this is a great and long placeholder",
|
|
37
|
+
* "Wow this is a great placeholder",
|
|
38
|
+
* "Wow",
|
|
39
|
+
* ]
|
|
40
|
+
* ...
|
|
41
|
+
* <Input
|
|
42
|
+
* placeholder={placeholders}
|
|
43
|
+
* />
|
|
44
|
+
*/
|
|
45
|
+
placeholder?: string | string[];
|
|
25
46
|
required?: boolean;
|
|
26
47
|
selectComponentWidth?: number;
|
|
27
48
|
selectDisplayLabel?: string | undefined | null;
|
|
@@ -31,6 +31,8 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
31
31
|
const events_1 = require("events");
|
|
32
32
|
const palette_tokens_1 = require("@artsy/palette-tokens");
|
|
33
33
|
const theme_get_1 = __importDefault(require("@styled-system/theme-get"));
|
|
34
|
+
const isArray_1 = __importDefault(require("lodash/isArray"));
|
|
35
|
+
const isString_1 = __importDefault(require("lodash/isString"));
|
|
34
36
|
const react_1 = require("react");
|
|
35
37
|
const react_native_1 = require("react-native");
|
|
36
38
|
const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
|
|
@@ -62,6 +64,8 @@ exports.Input = (0, react_1.forwardRef)(({ addClearListener = false, defaultValu
|
|
|
62
64
|
const [delayedFocused, setDelayedFocused] = (0, react_1.useState)(false);
|
|
63
65
|
const [value, setValue] = (0, react_1.useState)(propValue ?? defaultValue);
|
|
64
66
|
const [showPassword, setShowPassword] = (0, react_1.useState)(!secureTextEntry);
|
|
67
|
+
const [inputWidth, setInputWidth] = (0, react_1.useState)(0);
|
|
68
|
+
const placeholderWidths = (0, react_1.useRef)([]);
|
|
65
69
|
const rightComponentRef = (0, react_1.useRef)(null);
|
|
66
70
|
const inputRef = (0, react_1.useRef)();
|
|
67
71
|
const variant = (0, helpers_1.getInputVariant)({
|
|
@@ -136,17 +140,19 @@ exports.Input = (0, react_1.forwardRef)(({ addClearListener = false, defaultValu
|
|
|
136
140
|
}
|
|
137
141
|
return leftComponentWidth;
|
|
138
142
|
}, [hasLeftComponent, leftComponentWidth, onSelectTap, selectComponentWidth]);
|
|
139
|
-
const styles = {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
143
|
+
const styles = (0, react_1.useMemo)(() => {
|
|
144
|
+
return {
|
|
145
|
+
fontFamily: fontFamily,
|
|
146
|
+
fontSize: parseInt(palette_tokens_1.THEME.textVariants["sm-display"].fontSize, 10),
|
|
147
|
+
minHeight: props.multiline ? exports.MULTILINE_INPUT_MIN_HEIGHT : exports.INPUT_MIN_HEIGHT,
|
|
148
|
+
maxHeight: props.multiline ? exports.MULTILINE_INPUT_MAX_HEIGHT : undefined,
|
|
149
|
+
height: props.multiline ? exports.MULTILINE_INPUT_MIN_HEIGHT : undefined,
|
|
150
|
+
borderWidth: 1,
|
|
151
|
+
paddingRight: rightComponentWidth + exports.HORIZONTAL_PADDING,
|
|
152
|
+
paddingLeft: textInputPaddingLeft,
|
|
153
|
+
...styleProp,
|
|
154
|
+
};
|
|
155
|
+
}, [fontFamily, styleProp, props.multiline, rightComponentWidth, textInputPaddingLeft]);
|
|
150
156
|
const labelStyles = (0, react_1.useMemo)(() => {
|
|
151
157
|
return {
|
|
152
158
|
// this is neeeded too make sure the label is on top of the input
|
|
@@ -210,9 +216,16 @@ exports.Input = (0, react_1.forwardRef)(({ addClearListener = false, defaultValu
|
|
|
210
216
|
}, children: [unit && ((0, jsx_runtime_1.jsx)(Text_1.Text, { color: disabled ? "black30" : "black60", variant: "sm-display", children: unit })), icon] }));
|
|
211
217
|
}
|
|
212
218
|
if (onSelectTap) {
|
|
213
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: onSelectTap, style:
|
|
219
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: onSelectTap, style: [
|
|
220
|
+
leftComponentSharedStyles,
|
|
221
|
+
{
|
|
222
|
+
width: selectComponentWidth,
|
|
223
|
+
},
|
|
224
|
+
], hitSlop: { top: 10, right: 10, bottom: 10, left: 10 }, children: (0, jsx_runtime_1.jsxs)(AnimatedFlex, { style: [
|
|
214
225
|
{
|
|
215
|
-
|
|
226
|
+
paddingHorizontal: exports.HORIZONTAL_PADDING,
|
|
227
|
+
height: exports.INPUT_MIN_HEIGHT,
|
|
228
|
+
alignItems: "center",
|
|
216
229
|
width: selectComponentWidth,
|
|
217
230
|
flexDirection: "row",
|
|
218
231
|
borderRightWidth: 1,
|
|
@@ -291,26 +304,68 @@ exports.Input = (0, react_1.forwardRef)(({ addClearListener = false, defaultValu
|
|
|
291
304
|
left: 10,
|
|
292
305
|
}, children: (0, jsx_runtime_1.jsx)(Text_1.Text, { underline: true, variant: "xs", color: "black60", children: hintText }) }) }));
|
|
293
306
|
}, [hintText, props.onHintPress, space]);
|
|
307
|
+
const getPlatformSpecificPlaceholder = (0, react_1.useCallback)(() => {
|
|
308
|
+
if (!placeholder) {
|
|
309
|
+
return "";
|
|
310
|
+
}
|
|
311
|
+
if (react_native_1.Platform.OS === "ios") {
|
|
312
|
+
return (0, isArray_1.default)(placeholder) ? placeholder[0] : placeholder;
|
|
313
|
+
}
|
|
314
|
+
// if it's android and we only have one string, return that string
|
|
315
|
+
if ((0, isString_1.default)(placeholder)) {
|
|
316
|
+
return placeholder;
|
|
317
|
+
}
|
|
318
|
+
// otherwise, find a placeholder that has longest width that fits in the inputtext
|
|
319
|
+
const longestFittingStringIndex = placeholderWidths.current.findIndex((placeholderWidth) => {
|
|
320
|
+
return placeholderWidth <= inputWidth;
|
|
321
|
+
});
|
|
322
|
+
if (longestFittingStringIndex > -1) {
|
|
323
|
+
return placeholder[longestFittingStringIndex];
|
|
324
|
+
}
|
|
325
|
+
// otherwise just return the shortest placeholder
|
|
326
|
+
return placeholder[placeholder.length - 1];
|
|
327
|
+
}, [inputWidth, placeholder]);
|
|
294
328
|
const getPlaceholder = (0, react_1.useCallback)(() => {
|
|
295
329
|
// Show placeholder always if there is no title
|
|
296
330
|
// This is because we won't have a title animation
|
|
297
331
|
if (!props.title) {
|
|
298
|
-
return
|
|
332
|
+
return getPlatformSpecificPlaceholder();
|
|
299
333
|
}
|
|
300
334
|
// On blur, we want to show the placeholder immediately
|
|
301
335
|
if (delayedFocused) {
|
|
302
|
-
return
|
|
336
|
+
return getPlatformSpecificPlaceholder();
|
|
303
337
|
}
|
|
304
338
|
// On focus, we want to show the placeholder after the title animation has finished
|
|
305
339
|
return "";
|
|
306
|
-
}, [delayedFocused, props.title
|
|
340
|
+
}, [delayedFocused, getPlatformSpecificPlaceholder, props.title]);
|
|
307
341
|
const renderAnimatedTitle = (0, react_1.useCallback)(() => {
|
|
308
342
|
if (!props.title) {
|
|
309
343
|
return null;
|
|
310
344
|
}
|
|
311
345
|
return ((0, jsx_runtime_1.jsx)(Flex_1.Flex, { flexDirection: "row", zIndex: 100, pointerEvents: "none", height: exports.LABEL_HEIGHT, children: (0, jsx_runtime_1.jsxs)(AnimatedText, { style: [labelStyles, labelAnimatedStyles], numberOfLines: 1, children: [" ", props.title, " "] }) }));
|
|
312
346
|
}, [labelStyles, labelAnimatedStyles, props.title]);
|
|
313
|
-
|
|
347
|
+
const renderAndroidPlaceholderMeasuringHack = (0, react_1.useCallback)(() => {
|
|
348
|
+
if (react_native_1.Platform.OS === "ios" || !(0, isArray_1.default)(placeholder)) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
// Do not render the hack if we have already measured the placeholder
|
|
352
|
+
if (placeholderWidths.current.length > 0) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
return ((0, jsx_runtime_1.jsx)(Flex_1.Flex, { style: {
|
|
356
|
+
position: "absolute",
|
|
357
|
+
top: -10000,
|
|
358
|
+
width: 10000,
|
|
359
|
+
alignItems: "baseline", // this is to make Texts get the smallest width they can get to fit the text
|
|
360
|
+
}, children: placeholder.map((placeholderString, index) => ((0, jsx_runtime_1.jsx)(Text_1.Text, { onLayout: (event) => {
|
|
361
|
+
placeholderWidths.current[index] = event.nativeEvent.layout.width;
|
|
362
|
+
}, numberOfLines: 1, style: {
|
|
363
|
+
...styles,
|
|
364
|
+
}, children: placeholderString }))) }));
|
|
365
|
+
}, [placeholder, styles]);
|
|
366
|
+
return ((0, jsx_runtime_1.jsxs)(Flex_1.Flex, { flexGrow: 1, children: [renderAndroidPlaceholderMeasuringHack(), renderHint(), renderAnimatedTitle(), (0, jsx_runtime_1.jsx)(AnimatedStyledInput, { value: value, onChangeText: handleChangeText, style: [styles, textInputAnimatedStyles], onFocus: handleFocus, onBlur: handleBlur, onLayout: (event) => {
|
|
367
|
+
setInputWidth(event.nativeEvent.layout.width);
|
|
368
|
+
}, scrollEnabled: false, editable: !disabled, textAlignVertical: props.multiline ? "top" : "center", ref: inputRef, placeholderTextColor: color("black60"), placeholder: getPlaceholder(), secureTextEntry: !showPassword, ...props }), renderRightComponent(), renderLeftComponent(), renderBottomComponent()] }));
|
|
314
369
|
});
|
|
315
370
|
const StyledInput = (0, styled_components_1.default)(react_native_1.TextInput) `
|
|
316
371
|
padding: ${exports.HORIZONTAL_PADDING}px;
|