@prosperitainova/dumbo-react-native
Version:
Dumbo for React Native Library
440 lines (437 loc) • 13.6 kB
JavaScript
"use strict";
import React from 'react';
import { View, TextInput as ReactTextInput, Pressable, Platform } from 'react-native';
import { createIcon, pressableFeedbackStyle, styleReferenceBreaker } from '../../helpers';
import { getColor } from '../../styles/colors';
import { Button } from '../Button';
import { Text } from '../Text';
import ViewIcon from '@carbon/icons/es/view/20';
import ViewOffIcon from '@carbon/icons/es/view--off/20';
import SubtractIcon from '@carbon/icons/es/subtract/20';
import CalendarIcon from '@carbon/icons/es/calendar/20';
import WarningIcon from '@carbon/icons/es/warning--alt--filled/20';
import ErrorIcon from '@carbon/icons/es/warning--filled/20';
import AddIcon from '@carbon/icons/es/add/20';
import { defaultText } from '../../constants/defaultText';
import { BodyCompact02, Body02 } from '../../styles/typography';
import { Link } from '../Link';
/** Props for the internal base text input */
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
* Get the base styling for text inputs
*
* @param light - Indicate that light variant should be used
* @param hasLabelLink - Indicates that item has label with link
* @param fullBleed - Indicates that it should be full bleed style
*
* @returns React style item
*/
export const getTextInputStyle = (light, hasLabelLink, fullBleed) => {
// React Native on iOS
const baseTextBox = {
...BodyCompact02(),
height: 48,
backgroundColor: getColor('field01'),
borderColor: getColor('field01'),
color: getColor('textPrimary'),
borderBottomColor: getColor('borderStrong02'),
borderWidth: 2,
borderBottomWidth: 1,
paddingRight: 16,
paddingLeft: 18
};
if (light) {
baseTextBox.backgroundColor = getColor('field02');
baseTextBox.borderColor = getColor('field02');
}
if (Platform.OS === 'ios') {
// https://github.com/facebook/react-native/issues/29068
// This seems to hide it but very hacky.
baseTextBox.paddingBottom = 2;
}
if (fullBleed) {
baseTextBox.backgroundColor = 'transparent';
baseTextBox.borderColor = undefined;
baseTextBox.borderWidth = undefined;
baseTextBox.borderBottomWidth = undefined;
baseTextBox.paddingLeft = 0;
baseTextBox.paddingRight = 0;
}
return {
wrapper: {
paddingTop: hasLabelLink ? undefined : 22
},
labelWrapper: {
flexDirection: 'row',
flexWrap: 'wrap'
},
label: {
color: getColor('textSecondary'),
flex: 1,
paddingTop: hasLabelLink ? 30 : undefined,
marginBottom: fullBleed ? 5 : 8
},
helperText: {
color: getColor('textHelper'),
marginTop: 8,
marginBottom: fullBleed ? 20 : undefined
},
errorText: {
color: getColor('textError'),
marginTop: 8,
marginBottom: fullBleed ? 20 : undefined
},
warningText: {
marginTop: 8,
marginBottom: fullBleed ? 20 : undefined
},
textBox: baseTextBox,
textBoxDisabled: {
...baseTextBox,
color: getColor('textDisabled'),
borderBottomColor: 'transparent'
},
textBoxActive: {
...baseTextBox,
borderStyle: 'solid',
borderColor: getColor('focus'),
borderBottomColor: getColor('focus'),
paddingRight: 14,
borderBottomWidth: 2
},
textBoxError: {
...baseTextBox,
borderStyle: 'solid',
borderColor: getColor('supportError'),
borderBottomColor: getColor('supportError'),
paddingRight: 14,
borderBottomWidth: 2
},
textBoxWrapper: {
position: 'relative'
},
passwordRevealButton: {
position: 'absolute',
top: 0,
right: 0
},
dateIcon: {
position: 'absolute',
padding: 13,
top: 0,
right: 0
},
errorIcon: {
position: 'absolute',
padding: 13,
top: fullBleed ? '100%' : 0,
right: 0
},
numberActions: {
position: 'absolute',
top: 0,
right: 0,
flexDirection: 'row'
},
numberActionsDivider: {
backgroundColor: getColor('layer02'),
width: 1,
height: 20,
marginTop: 14
},
numberActionsButton: {
padding: 14
},
labelLink: {
paddingBottom: 0,
paddingTop: 30
}
};
};
/**
* @ignore
* This is the base system for text input.
* This allows a shared code base for all text input systems and validation rules
* This component is not exported. It is used by `TextInput`, `TextArea` and `PasswordInput`.
*/
export class BaseTextInput extends React.Component {
state = {
dirty: false,
hasFocus: false,
revealPassword: false
};
get styles() {
const {
light,
labelLink,
fullBleedCallback
} = this.props;
return getTextInputStyle(light, !!labelLink, !!fullBleedCallback);
}
onFocus = event => {
const {
onFocus
} = this.props;
if (typeof onFocus === 'function') {
onFocus(event);
}
this.setState({
hasFocus: true
});
};
onBlur = event => {
const {
onBlur
} = this.props;
if (typeof onBlur === 'function') {
onBlur(event);
}
this.setState({
hasFocus: false
});
};
onChange = value => {
const {
onChangeText,
type,
numberRules
} = this.props;
if (type === 'number' && value) {
if (Number.isNaN(Number(value))) {
value = String(numberRules?.min || 0);
}
const invalidMin = typeof numberRules?.min === 'number' ? numberRules.min >= Number(value) : false;
const invalidMax = typeof numberRules?.max === 'number' ? numberRules.max <= Number(value) : false;
if (invalidMin) {
value = String(numberRules?.min || 0);
}
if (invalidMax) {
value = String(numberRules?.max || 0);
}
}
if (typeof onChangeText === 'function') {
onChangeText(value);
}
this.setState({
dirty: true
});
};
get passwordReveal() {
const {
revealPassword
} = this.state;
const {
togglePasswordText,
disabled
} = this.props;
return /*#__PURE__*/_jsx(Button, {
overrideColor: disabled ? getColor('iconDisabled') : getColor('iconSecondary'),
disabled: disabled,
style: this.styles.passwordRevealButton,
iconOnlyMode: true,
kind: "ghost",
icon: revealPassword ? ViewOffIcon : ViewIcon,
text: togglePasswordText || defaultText.passwordRevealButton,
onPress: () => this.setState({
revealPassword: !revealPassword
})
});
}
get dateIcon() {
const {
disabled
} = this.props;
return /*#__PURE__*/_jsx(View, {
style: this.styles.dateIcon,
children: createIcon(CalendarIcon, 20, 20, disabled ? getColor('iconDisabled') : getColor('iconSecondary'))
});
}
get baseErrorWarningStyle() {
const {
type
} = this.props;
const errorIconStyle = styleReferenceBreaker(this.styles.errorIcon);
if (type === 'password' || type === 'date') {
errorIconStyle.right = 48;
errorIconStyle.paddingRight = 0;
errorIconStyle.paddingLeft = 0;
} else if (type === 'number') {
errorIconStyle.right = 97;
errorIconStyle.paddingRight = 0;
errorIconStyle.paddingLeft = 0;
}
return errorIconStyle;
}
get errorIndicator() {
return /*#__PURE__*/_jsx(View, {
style: this.baseErrorWarningStyle,
children: createIcon(ErrorIcon, 20, 20, getColor('supportError'))
});
}
get warningIndicator() {
return /*#__PURE__*/_jsx(View, {
style: this.baseErrorWarningStyle,
children: createIcon(WarningIcon, 20, 20, getColor('supportWarning'))
});
}
incrementNumber = () => {
const {
value
} = this.props;
const valueNumber = Number.isNaN(Number(value)) ? 0 : Number(value);
this.onChange(String(valueNumber + 1));
};
decrementNumber = () => {
const {
value
} = this.props;
const valueNumber = Number.isNaN(Number(value)) ? 0 : Number(value);
this.onChange(String(valueNumber - 1));
};
get numberActions() {
const {
numberRules,
value,
disabled,
incrementNumberText,
decrementNumberText
} = this.props;
const valueNumber = Number.isNaN(Number(value)) ? 0 : Number(value);
const disableMin = typeof numberRules?.min === 'number' ? numberRules.min >= valueNumber : false;
const disableMax = typeof numberRules?.max === 'number' ? numberRules.max <= valueNumber : false;
const getStateStyle = state => {
return state.pressed ? {
backgroundColor: getColor('layerActive01')
} : undefined;
};
const getPressable = (onPress, pressableDisabled, icon, text) => {
const finalDisabled = pressableDisabled || disabled || false;
return /*#__PURE__*/_jsx(Pressable, {
style: state => pressableFeedbackStyle(state, this.styles.numberActionsButton, getStateStyle),
onPress: onPress,
disabled: finalDisabled,
accessibilityLabel: text,
accessibilityHint: value,
children: createIcon(icon, 20, 20, finalDisabled ? getColor('iconDisabled') : getColor('iconPrimary'))
});
};
return /*#__PURE__*/_jsxs(View, {
style: this.styles.numberActions,
children: [getPressable(this.decrementNumber, disableMin, SubtractIcon, decrementNumberText || defaultText.decrementNumber), /*#__PURE__*/_jsx(View, {
style: this.styles.numberActionsDivider
}), getPressable(this.incrementNumber, disableMax, AddIcon, incrementNumberText || defaultText.incrementNumber)]
});
}
render() {
const {
label,
helperText,
getErrorText,
value,
autoCorrect,
autoCapitalize,
placeholder,
maxLength,
onSubmitEditing,
componentProps,
style,
required,
disabled,
isInvalid,
type,
textAreaMinHeight,
labelBreakMode,
labelLink,
fullBleedCallback,
warningText
} = this.props;
const {
hasFocus,
dirty,
revealPassword
} = this.state;
const password = type === 'password';
const textArea = type === 'text-area';
const date = type === 'date';
const number = type === 'number';
let textBoxStyle = styleReferenceBreaker(this.styles.textBox);
const error = !!(required && dirty && !value) || dirty && typeof isInvalid === 'function' && isInvalid(value);
const fullBleedMode = typeof fullBleedCallback === 'function';
if (fullBleedMode) {
setTimeout(() => {
fullBleedCallback(hasFocus, error);
});
}
if (disabled) {
textBoxStyle = styleReferenceBreaker(this.styles.textBoxDisabled);
} else if (error && !fullBleedMode) {
textBoxStyle = styleReferenceBreaker(this.styles.textBoxError);
} else if (hasFocus && !fullBleedMode) {
textBoxStyle = styleReferenceBreaker(this.styles.textBoxActive);
}
if (textArea) {
textBoxStyle.height = textAreaMinHeight || 144;
textBoxStyle.paddingTop = 12;
textBoxStyle.paddingBottom = 12;
textBoxStyle = styleReferenceBreaker(textBoxStyle, Body02());
} else if (password || date) {
textBoxStyle.paddingRight = 50;
} else if (number) {
textBoxStyle.paddingRight = 100;
}
if (error) {
textBoxStyle.paddingRight = (Number(textBoxStyle.paddingRight) || 0) + 25;
}
return /*#__PURE__*/_jsxs(View, {
style: styleReferenceBreaker(this.styles.wrapper, style),
children: [!!(label || labelLink) && /*#__PURE__*/_jsxs(View, {
style: this.styles.labelWrapper,
children: [!!label && /*#__PURE__*/_jsx(Text, {
style: this.styles.label,
type: "label-02",
text: label,
breakMode: labelBreakMode
}), !!labelLink && /*#__PURE__*/_jsx(Link, {
...labelLink,
style: this.styles.labelLink,
textType: "label-02"
})]
}), /*#__PURE__*/_jsxs(View, {
style: this.styles.textBoxWrapper,
accessible: password,
children: [/*#__PURE__*/_jsx(ReactTextInput, {
editable: !disabled,
accessibilityLabel: helperText ? `${label} - ${helperText}` : label,
secureTextEntry: revealPassword ? false : password,
autoCapitalize: autoCapitalize,
style: textBoxStyle,
value: value,
onSubmitEditing: onSubmitEditing,
onChangeText: this.onChange,
autoCorrect: autoCorrect,
placeholder: placeholder,
placeholderTextColor: getColor('textPlaceholder'),
onBlur: this.onBlur,
onFocus: this.onFocus,
maxLength: maxLength,
textAlignVertical: "top",
multiline: textArea,
...(componentProps || {})
}), error && this.errorIndicator, !!(warningText && !error) && this.warningIndicator, password && this.passwordReveal, date && this.dateIcon, number && this.numberActions]
}), !!(helperText && !error && !warningText) && /*#__PURE__*/_jsx(Text, {
style: this.styles.helperText,
type: "helper-text-02",
text: helperText
}), !!(typeof getErrorText === 'function' && error) && /*#__PURE__*/_jsx(Text, {
style: this.styles.errorText,
type: "helper-text-02",
text: getErrorText(value)
}), !!(warningText && !error) && /*#__PURE__*/_jsx(Text, {
style: this.styles.warningText,
type: "helper-text-02",
text: warningText
})]
});
}
}
//# sourceMappingURL=index.js.map