@octopusdeploy/design-system-components
Version:
The design systems component library.
216 lines (215 loc) • 10.3 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Text = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const css_1 = require("@emotion/css");
const TextField_1 = __importDefault(require("@mui/material/TextField"));
const design_system_tokens_1 = require("@octopusdeploy/design-system-tokens");
const react_1 = __importDefault(require("react"));
const uuid_1 = require("uuid");
/* @deprecated
* Deprecated: 15/09/2025
* This component is deprecated.
* As part of our effort to improve form usability and accessibility, please migrate to TextField where feasible.
* We recognize TextField has different styling; if that prevents an immediate swap, you may continue using this
* component until all the new form components are in place.
*/
class Text extends react_1.default.Component {
static defaultProps = {
type: "text",
autoComplete: "off",
autoFocus: false,
applyMaxWidth: false,
multiline: false,
showBorder: false,
maxRows: 30,
maxLength: undefined,
textInputRef: (input) => {
/* Do nothing */
},
};
textFieldInput = undefined;
genericName;
uniqueId;
constructor(props) {
super(props);
this.state = {
showExternalError: true,
};
this.genericName = props.generateUniqueName ? `input-${(0, uuid_1.v4)()}` : typeof props.label === "string" ? props.label : props.placeholder ? props.placeholder : `input-${(0, uuid_1.v4)()}`;
this.uniqueId = `${props.label}-${(0, uuid_1.v4)()}`;
}
// eslint-disable-next-line react/no-unsafe
UNSAFE_componentWillMount() {
if (this.props.textInputRef) {
this.props.textInputRef(this);
}
}
componentWillUnmount() {
if (this.props.textInputRef) {
this.props.textInputRef(null);
}
}
// eslint-disable-next-line react/no-unsafe
UNSAFE_componentWillReceiveProps(nextProps) {
// Originally the validate function was called in onChange, but the related setState call caused issue when the Text field is used in Drawer (react-reverse-portal).
// The issue is that the setState triggers re-rendering with stale props from the parent component, which causes the value to reset to the old value, and can cause the cursor to jump to the end of the input field.
// Moving the validate function to lifecycle method so that no re-rendering is triggered before the prop is updated.
if (nextProps.value !== this.props.value) {
const internalError = nextProps.validate ? nextProps.validate(nextProps.value ?? "") : undefined;
if (internalError !== this.state.error || this.state.showExternalError) {
this.setState({ error: internalError, showExternalError: false });
}
}
const isNewExternalErrorAvailable = nextProps.error !== this.props.error;
if (isNewExternalErrorAvailable) {
this.setState({ showExternalError: true });
}
}
getSelection = () => {
const input = this.textFieldInput;
return { start: input?.selectionStart ?? null, end: input?.selectionEnd ?? null };
};
setValueAndSelection = (selection, value) => {
const input = this.textFieldInput;
if (!input)
return;
input.value = value;
input.selectionStart = selection.start;
input.selectionEnd = selection.end;
this.callValidateAndChange(input.value);
};
isFocused() {
return this.textFieldInput !== undefined && this.textFieldInput === document.activeElement;
}
select() {
if (this.textFieldInput) {
this.textFieldInput.select();
}
}
focus() {
if (this.textFieldInput) {
this.textFieldInput.focus();
}
}
blur() {
if (this.textFieldInput) {
this.textFieldInput.blur();
}
}
insertAtCursor(value) {
if (!this.textFieldInput) {
return;
}
const input = this.textFieldInput;
if (input.selectionStart || input.selectionStart === 0) {
const startPos = input.selectionStart;
const endPos = input.selectionEnd;
if (endPos !== null) {
input.value = input.value.substring(0, startPos) + value + input.value.substring(endPos, input.value.length);
}
input.selectionStart = startPos + value.length;
input.selectionEnd = startPos + value.length;
}
else {
input.value += value;
}
this.callValidateAndChange(input.value);
}
handleChange = (event) => {
event.preventDefault();
event.stopPropagation();
const value = event.currentTarget.value;
this.callValidateAndChange(value);
};
callValidateAndChange = (value) => {
// Originally the props.validate function was called here,
// but it has delibrately been moved to UNSAFE_componentWillReceiveProps to avoid the issue of re-rendering with stale props when used in Drawer (react-reverse-portal).
if (this.props.validate && this.props.onValidate) {
this.props.onValidate(this.props.validate(value));
}
if (this.props.onChange) {
this.props.onChange(value);
}
};
handleKeyPress = (event) => {
if (this.props.onKeyPress) {
this.props.onKeyPress(event);
}
};
handleKeyDown = (event) => {
if (this.props.onKeyDown) {
this.props.onKeyDown(event);
}
};
render() {
const { id, validate, error, onChange, onValidate, value, label, min, max, name, maxLength, placeholder, hideUnderline, warning, monoSpacedFont, onKeyPress, minRows, multiline, applyMaxWidth, showBorder, textInputRef, inputContainerRef, showValueAsTitleAttribute, autoComplete, type, usePlaceholderAsLabel, accessibleName, customMargins, generateUniqueName, helperText, margin, inputProps, ...otherProps } = this.props;
const err = this.state.error || (this.state.showExternalError && error);
const errorText = err || warning;
const val = value ? value : "";
// Play carefully here. There's some interesting combinations happening with SensitiveInput label/placeholder etc. We have tests around this.
const inputLabel = usePlaceholderAsLabel && placeholder && !val ? placeholder : label;
const commonStyle = { margin: customMargins ? customMargins : !inputLabel ? "1rem 0" : "0 0 1rem 0", pointerEvents: "auto" }; // This allows us to match the padding/alignment of our other select components (AutoComplete, Select, MultiSelect), so we get consistent alignments.
const widthStyle = applyMaxWidth ? { maxWidth: "100%" } : {};
const inputId = id || this.uniqueId;
const getHelpText = () => {
if (errorText)
return errorText;
if (helperText)
return helperText;
return null;
};
const typeComplete = type === "password" && autoComplete === "off" ? "new-password" : autoComplete;
return ((0, jsx_runtime_1.jsx)("div", { className: (0, css_1.cx)(styles.container, { [styles.border]: showBorder }), children: (0, jsx_runtime_1.jsx)(TextField_1.default, { ref: inputContainerRef, id: inputId, className: (0, css_1.cx)(styles.text, { [styles.noUnderline]: hideUnderline }), inputRef: (textFieldInput) => {
this.textFieldInput = textFieldInput;
}, variant: "standard", type: type, autoComplete: typeComplete, autoCorrect: autoComplete, placeholder: placeholder, title: showValueAsTitleAttribute ? val : undefined, name: name || this.genericName, value: val, label: inputLabel, onChange: this.handleChange, error: !!errorText, helperText: getHelpText(), FormHelperTextProps: { component: "div" }, inputProps: { min: min, max: max, "aria-label": accessibleName }, InputProps: {
...this.props.inputProps,
classes: {
...this.props.inputProps?.classes,
input: (0, css_1.cx)(this.props.inputProps?.classes?.input, { [styles.monospacedText]: monoSpacedFont }),
},
}, minRows: multiline ? minRows || 3 : minRows, multiline: multiline, onKeyPress: this.handleKeyPress, onKeyDown: this.handleKeyDown, style: { ...widthStyle, ...commonStyle }, margin: margin, ...otherProps }) }));
}
}
exports.Text = Text;
const muiInputClasses = {
input: "MuiInputBase-input",
};
const styles = {
container: (0, css_1.css)({
width: "100%",
}),
border: (0, css_1.css)({
padding: `0 ${design_system_tokens_1.space[8]} 0 ${design_system_tokens_1.space[8]}`,
border: `${design_system_tokens_1.borderWidth[1]} solid ${design_system_tokens_1.themeTokens.color.border.primary}`,
marginBottom: design_system_tokens_1.space[8],
}),
text: (0, css_1.css)({
verticalAlign: "baseline !important",
width: "100% !important",
minWidth: "0 !important",
}),
monospacedText: (0, css_1.css)({
[`&.${muiInputClasses.input}`]: {
font: design_system_tokens_1.text.code.regular.medium,
},
}),
noUnderline: (0, css_1.css)({
borderBottom: 0,
"div::before": {
// This isn't great because it relies on the structure of MUI's TextField component, but it's the only way I could find to override this here.
borderBottom: 0,
"div::before": {
// This isn't great because it relies on the structure of MUI's TextField component, but it's the only way I could find to override this here.
borderBottom: 0,
},
"div::after": {
// This isn't great because it relies on the structure of MUI's TextField component, but it's the only way I could find to override this here.
borderBottom: 0,
},
},
}),
};