[MM-63021] Modify input to have min/max length validation work the same as the validation around required, replace Create Team input with Input component (#31406)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
548a47ae56
Коммит
b99a22f175
@@ -26,15 +26,14 @@ exports[`/components/create_team/components/display_name should match snapshot 1
|
|||||||
<div
|
<div
|
||||||
className="col-sm-9"
|
className="col-sm-9"
|
||||||
>
|
>
|
||||||
<input
|
<ForwardRef
|
||||||
aria-describedby="teamNameInputError"
|
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="form-control"
|
|
||||||
id="teamNameInput"
|
id="teamNameInput"
|
||||||
maxLength={128}
|
maxLength={64}
|
||||||
|
minLength={2}
|
||||||
|
name="teamNameInput"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onFocus={[Function]}
|
required={true}
|
||||||
placeholder=""
|
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
type="text"
|
type="text"
|
||||||
value="test-team"
|
value="test-team"
|
||||||
@@ -50,6 +49,7 @@ exports[`/components/create_team/components/display_name should match snapshot 1
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary mt-8"
|
className="btn btn-primary mt-8"
|
||||||
|
disabled={false}
|
||||||
id="teamNameNextButton"
|
id="teamNameNextButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@@ -4,12 +4,10 @@
|
|||||||
import {shallow} from 'enzyme';
|
import {shallow} from 'enzyme';
|
||||||
import type {ReactWrapper} from 'enzyme';
|
import type {ReactWrapper} from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
import DisplayName from 'components/create_team/components/display_name';
|
import DisplayName from 'components/create_team/components/display_name';
|
||||||
|
|
||||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||||
import Constants from 'utils/constants';
|
|
||||||
import {cleanUpUrlable} from 'utils/url';
|
import {cleanUpUrlable} from 'utils/url';
|
||||||
|
|
||||||
jest.mock('images/logo.png', () => 'logo.png');
|
jest.mock('images/logo.png', () => 'logo.png');
|
||||||
@@ -73,115 +71,4 @@ describe('/components/create_team/components/display_name', () => {
|
|||||||
expect(wrapper.prop('updateParent')).toHaveBeenCalledWith(defaultProps.state);
|
expect(wrapper.prop('updateParent')).toHaveBeenCalledWith(defaultProps.state);
|
||||||
expect(wrapper.prop('updateParent').mock.calls[0][0]).toEqual(newState);
|
expect(wrapper.prop('updateParent').mock.calls[0][0]).toEqual(newState);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should display isRequired error', () => {
|
|
||||||
const wrapper = mountWithIntl(<DisplayName {...defaultProps}/>);
|
|
||||||
(wrapper.find('.form-control') as unknown as ReactWrapper<any, any, HTMLInputElement>).instance().value = '';
|
|
||||||
wrapper.find('.form-control').simulate('change');
|
|
||||||
|
|
||||||
wrapper.find('button').simulate('click', {
|
|
||||||
preventDefault: () => jest.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.state('nameError')).toEqual(
|
|
||||||
<FormattedMessage
|
|
||||||
id='create_team.display_name.required'
|
|
||||||
defaultMessage='This field is required'
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should display isRequired error for null team in props', () => {
|
|
||||||
const nullTeamProps = {
|
|
||||||
updateParent: jest.fn(),
|
|
||||||
state: {
|
|
||||||
wizard: 'display_name',
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
trackEvent: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = mountWithIntl(<DisplayName {...nullTeamProps}/>);
|
|
||||||
|
|
||||||
(wrapper.find('.form-control') as unknown as ReactWrapper<any, any, HTMLInputElement>).instance().value = '';
|
|
||||||
wrapper.find('.form-control').simulate('change');
|
|
||||||
|
|
||||||
wrapper.find('button').simulate('click', {
|
|
||||||
preventDefault: () => jest.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.state('nameError')).toEqual(
|
|
||||||
<FormattedMessage
|
|
||||||
id='create_team.display_name.required'
|
|
||||||
defaultMessage='This field is required'
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should display isRequired error for empty team in props', () => {
|
|
||||||
const nullTeamProps = {
|
|
||||||
updateParent: jest.fn(),
|
|
||||||
state: {
|
|
||||||
team: {},
|
|
||||||
wizard: 'display_name',
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
trackEvent: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = mountWithIntl(<DisplayName {...nullTeamProps}/>);
|
|
||||||
|
|
||||||
(wrapper.find('.form-control') as unknown as ReactWrapper<any, any, HTMLInputElement>).instance().value = '';
|
|
||||||
wrapper.find('.form-control').simulate('change');
|
|
||||||
|
|
||||||
wrapper.find('button').simulate('click', {
|
|
||||||
preventDefault: () => jest.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.state('nameError')).toEqual(
|
|
||||||
<FormattedMessage
|
|
||||||
id='create_team.display_name.required'
|
|
||||||
defaultMessage='This field is required'
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should display charLength error', () => {
|
|
||||||
const wrapper = mountWithIntl(<DisplayName {...defaultProps}/>);
|
|
||||||
const input = (wrapper.find('.form-control') as unknown as ReactWrapper<any, any, HTMLInputElement>).instance();
|
|
||||||
input.value = 'a'.repeat(Constants.MAX_TEAMNAME_LENGTH + 1);
|
|
||||||
wrapper.find('.form-control').simulate('change');
|
|
||||||
|
|
||||||
wrapper.find('button').simulate('click', {
|
|
||||||
preventDefault: () => jest.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.state('nameError')).toEqual(
|
|
||||||
<FormattedMessage
|
|
||||||
id='create_team.display_name.charLength'
|
|
||||||
defaultMessage='Name must be {min} or more characters up to a maximum of {max}. You can add a longer team description later.'
|
|
||||||
values={{
|
|
||||||
min: Constants.MIN_TEAMNAME_LENGTH,
|
|
||||||
max: Constants.MAX_TEAMNAME_LENGTH,
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should focus input when validation error occurs', () => {
|
|
||||||
const wrapper = mountWithIntl(<DisplayName {...defaultProps}/>);
|
|
||||||
const input = wrapper.find('.form-control').getDOMNode() as HTMLInputElement;
|
|
||||||
const focusSpy = jest.spyOn(input, 'focus');
|
|
||||||
|
|
||||||
// Trigger validation error
|
|
||||||
input.value = '';
|
|
||||||
wrapper.find('.form-control').simulate('change');
|
|
||||||
wrapper.find('button').simulate('click', {
|
|
||||||
preventDefault: () => jest.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(focusSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import type {Team} from '@mattermost/types/teams';
|
|||||||
|
|
||||||
import {trackEvent} from 'actions/telemetry_actions.jsx';
|
import {trackEvent} from 'actions/telemetry_actions.jsx';
|
||||||
|
|
||||||
|
import Input from 'components/widgets/inputs/input/input';
|
||||||
|
|
||||||
import logoImage from 'images/logo.png';
|
import logoImage from 'images/logo.png';
|
||||||
import Constants from 'utils/constants';
|
import Constants from 'utils/constants';
|
||||||
import {cleanUpUrlable} from 'utils/url';
|
import {cleanUpUrlable} from 'utils/url';
|
||||||
@@ -32,15 +34,11 @@ type Props = {
|
|||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
teamDisplayName: string;
|
teamDisplayName: string;
|
||||||
nameError?: React.ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TeamSignupDisplayNamePage extends React.PureComponent<Props, State> {
|
export default class TeamSignupDisplayNamePage extends React.PureComponent<Props, State> {
|
||||||
teamNameInput: React.RefObject<HTMLInputElement>;
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.teamNameInput = React.createRef();
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
teamDisplayName: this.props.state.team?.display_name || '',
|
teamDisplayName: this.props.state.team?.display_name || '',
|
||||||
@@ -51,33 +49,18 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
|||||||
trackEvent('signup', 'signup_team_01_name');
|
trackEvent('signup', 'signup_team_01_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isValidTeamName = (): boolean => {
|
||||||
|
return this.state.teamDisplayName.length >= Constants.MIN_TEAMNAME_LENGTH && this.state.teamDisplayName.length <= Constants.MAX_TEAMNAME_LENGTH;
|
||||||
|
};
|
||||||
|
|
||||||
submitNext = (e: React.MouseEvent): void => {
|
submitNext = (e: React.MouseEvent): void => {
|
||||||
|
if (!this.isValidTeamName()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
trackEvent('display_name', 'click_next');
|
trackEvent('display_name', 'click_next');
|
||||||
const displayName = this.state.teamDisplayName.trim();
|
const displayName = this.state.teamDisplayName.trim();
|
||||||
if (!displayName) {
|
|
||||||
this.setState({nameError: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='create_team.display_name.required'
|
|
||||||
defaultMessage='This field is required'
|
|
||||||
/>),
|
|
||||||
});
|
|
||||||
this.teamNameInput.current?.focus();
|
|
||||||
return;
|
|
||||||
} else if (displayName.length < Constants.MIN_TEAMNAME_LENGTH || displayName.length > Constants.MAX_TEAMNAME_LENGTH) {
|
|
||||||
this.setState({nameError: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='create_team.display_name.charLength'
|
|
||||||
defaultMessage='Name must be {min} or more characters up to a maximum of {max}. You can add a longer team description later.'
|
|
||||||
values={{
|
|
||||||
min: Constants.MIN_TEAMNAME_LENGTH,
|
|
||||||
max: Constants.MAX_TEAMNAME_LENGTH,
|
|
||||||
}}
|
|
||||||
/>),
|
|
||||||
});
|
|
||||||
this.teamNameInput.current?.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newState = this.props.state;
|
const newState = this.props.state;
|
||||||
newState.wizard = 'team_url';
|
newState.wizard = 'team_url';
|
||||||
@@ -86,31 +69,11 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
|||||||
this.props.updateParent(newState);
|
this.props.updateParent(newState);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleFocus = (e: React.FocusEvent<HTMLInputElement>): void => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.currentTarget.select();
|
|
||||||
};
|
|
||||||
|
|
||||||
handleDisplayNameChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
handleDisplayNameChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
this.setState({teamDisplayName: e.target.value});
|
this.setState({teamDisplayName: e.target.value});
|
||||||
};
|
};
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
let nameError = null;
|
|
||||||
let nameDivClass = 'form-group';
|
|
||||||
if (this.state.nameError) {
|
|
||||||
nameError = (
|
|
||||||
<label
|
|
||||||
role='alert'
|
|
||||||
className='control-label'
|
|
||||||
id='teamNameInputError'
|
|
||||||
>
|
|
||||||
{this.state.nameError}
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
nameDivClass += ' has-error';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form>
|
<form>
|
||||||
@@ -126,26 +89,23 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
|||||||
defaultMessage='Team Name'
|
defaultMessage='Team Name'
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className={nameDivClass}>
|
<div className='form-group'>
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<div className='col-sm-9'>
|
<div className='col-sm-9'>
|
||||||
<input
|
<Input
|
||||||
id='teamNameInput'
|
id='teamNameInput'
|
||||||
|
name='teamNameInput'
|
||||||
type='text'
|
type='text'
|
||||||
ref={this.teamNameInput}
|
|
||||||
className='form-control'
|
|
||||||
placeholder=''
|
|
||||||
maxLength={128}
|
|
||||||
value={this.state.teamDisplayName}
|
value={this.state.teamDisplayName}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
onFocus={this.handleFocus}
|
|
||||||
onChange={this.handleDisplayNameChange}
|
onChange={this.handleDisplayNameChange}
|
||||||
|
required={true}
|
||||||
|
maxLength={Constants.MAX_TEAMNAME_LENGTH}
|
||||||
|
minLength={Constants.MIN_TEAMNAME_LENGTH}
|
||||||
spellCheck='false'
|
spellCheck='false'
|
||||||
aria-describedby='teamNameInputError'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{nameError}
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -158,6 +118,7 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
|||||||
type='submit'
|
type='submit'
|
||||||
className='btn btn-primary mt-8'
|
className='btn btn-primary mt-8'
|
||||||
onClick={this.submitNext}
|
onClick={this.submitNext}
|
||||||
|
disabled={!this.isValidTeamName()}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='create_team.display_name.next'
|
id='create_team.display_name.next'
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ Object {
|
|||||||
class="Input_wrapper"
|
class="Input_wrapper"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
|
||||||
aria-label="Name"
|
aria-label="Name"
|
||||||
class="Input form-control large"
|
class="Input form-control large"
|
||||||
id="input_name"
|
id="input_name"
|
||||||
@@ -121,6 +120,8 @@ Object {
|
|||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label=""
|
||||||
class="icon error icon-alert-circle-outline"
|
class="icon error icon-alert-circle-outline"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
@@ -141,7 +142,6 @@ Object {
|
|||||||
class="Input_wrapper"
|
class="Input_wrapper"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
|
||||||
aria-label="Company Name"
|
aria-label="Company Name"
|
||||||
class="Input form-control large"
|
class="Input form-control large"
|
||||||
id="input_company_name"
|
id="input_company_name"
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ exports[`components/widgets/inputs/Input should match snapshot 1`] = `
|
|||||||
class="Input_wrapper"
|
class="Input_wrapper"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
|
||||||
class="Input form-control medium"
|
class="Input form-control medium"
|
||||||
id="input_"
|
id="input_"
|
||||||
value=""
|
value=""
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ describe('components/widgets/inputs/Input', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('minLength validation', () => {
|
describe('minLength validation', () => {
|
||||||
test('should show error styling when input is empty with minLength set', () => {
|
test('should show error styling when input is empty with minLength set', async () => {
|
||||||
renderWithContext(
|
renderWithContext(
|
||||||
<Input
|
<Input
|
||||||
value={''}
|
value={''}
|
||||||
@@ -69,6 +69,13 @@ describe('components/widgets/inputs/Input', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Find the input and blur it to trigger validation
|
||||||
|
const inputElement = screen.getByRole('textbox');
|
||||||
|
await act(async () => {
|
||||||
|
inputElement.focus();
|
||||||
|
inputElement.blur();
|
||||||
|
});
|
||||||
|
|
||||||
// Check for error styling
|
// Check for error styling
|
||||||
const fieldset = screen.getByRole('group');
|
const fieldset = screen.getByRole('group');
|
||||||
expect(fieldset).toHaveClass('Input_fieldset___error');
|
expect(fieldset).toHaveClass('Input_fieldset___error');
|
||||||
@@ -96,6 +103,8 @@ describe('components/widgets/inputs/Input', () => {
|
|||||||
|
|
||||||
// Then type the new value
|
// Then type the new value
|
||||||
userEvent.type(inputElement, 'a');
|
userEvent.type(inputElement, 'a');
|
||||||
|
|
||||||
|
inputElement.blur();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for error styling
|
// Check for error styling
|
||||||
@@ -142,11 +151,12 @@ describe('components/widgets/inputs/Input', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// With 6 characters and limit of 5, there should be an error
|
// Find the input and blur it to trigger validation
|
||||||
|
const inputElement = screen.getByRole('textbox');
|
||||||
// Check for the -X indicator
|
await act(async () => {
|
||||||
const indicator = screen.getByText('-1');
|
inputElement.focus();
|
||||||
expect(indicator).toBeInTheDocument();
|
inputElement.blur();
|
||||||
|
});
|
||||||
|
|
||||||
// Check for error styling
|
// Check for error styling
|
||||||
const fieldset = screen.getByRole('group');
|
const fieldset = screen.getByRole('group');
|
||||||
@@ -164,10 +174,6 @@ describe('components/widgets/inputs/Input', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// With exactly 5 characters and limit of 5, there should be no error
|
|
||||||
// Check that the -X indicator is not present
|
|
||||||
expect(screen.queryByText(/-\d+/)).not.toBeInTheDocument();
|
|
||||||
|
|
||||||
// Check that error message is not present
|
// Check that error message is not present
|
||||||
expect(screen.queryByText(/Must be no more than 5 characters/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/Must be no more than 5 characters/i)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -251,20 +257,5 @@ describe('components/widgets/inputs/Input', () => {
|
|||||||
// Check that minLength error message is not present
|
// Check that minLength error message is not present
|
||||||
expect(screen.queryByText(/Must be at least 2 characters/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/Must be at least 2 characters/i)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show both minLength indicator and limit indicator when applicable', () => {
|
|
||||||
renderWithContext(
|
|
||||||
<Input
|
|
||||||
value={'abc'}
|
|
||||||
minLength={5}
|
|
||||||
limit={10}
|
|
||||||
showMinLengthIndicator={true}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check for the +X indicator for minLength
|
|
||||||
const indicator = screen.getByText('+2');
|
|
||||||
expect(indicator).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
|
|||||||
inputClassName?: string;
|
inputClassName?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
minLength?: number;
|
minLength?: number;
|
||||||
showMinLengthIndicator?: boolean;
|
|
||||||
useLegend?: boolean;
|
useLegend?: boolean;
|
||||||
customMessage?: CustomMessageInputType;
|
customMessage?: CustomMessageInputType;
|
||||||
inputSize?: SIZE;
|
inputSize?: SIZE;
|
||||||
@@ -65,7 +64,6 @@ const Input = React.forwardRef((
|
|||||||
inputClassName,
|
inputClassName,
|
||||||
limit,
|
limit,
|
||||||
minLength,
|
minLength,
|
||||||
showMinLengthIndicator = false,
|
|
||||||
customMessage,
|
customMessage,
|
||||||
maxLength,
|
maxLength,
|
||||||
inputSize = SIZE.MEDIUM,
|
inputSize = SIZE.MEDIUM,
|
||||||
@@ -157,6 +155,7 @@ const Input = React.forwardRef((
|
|||||||
const validationError = validate(value);
|
const validationError = validate(value);
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
setCustomInputLabel(validationError);
|
setCustomInputLabel(validationError);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,32 +164,35 @@ const Input = React.forwardRef((
|
|||||||
if (required && (value === null || value === '')) {
|
if (required && (value === null || value === '')) {
|
||||||
const validationErrorMsg = formatMessage({id: 'widget.input.required', defaultMessage: 'This field is required'});
|
const validationErrorMsg = formatMessage({id: 'widget.input.required', defaultMessage: 'This field is required'});
|
||||||
setCustomInputLabel({type: ItemStatus.ERROR, value: validationErrorMsg});
|
setCustomInputLabel({type: ItemStatus.ERROR, value: validationErrorMsg});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const limitExceeded = limit && value && !Array.isArray(value) ? value.toString().length - limit : 0;
|
||||||
|
const minLengthNotMet = minLength && value !== undefined && !Array.isArray(value) ? minLength - value.toString().length : (minLength || 0);
|
||||||
|
|
||||||
|
// Show min length error even when the input is empty (to match existing behavior in tests)
|
||||||
|
// Generate derived error messages
|
||||||
|
if (limitExceeded > 0) {
|
||||||
|
setCustomInputLabel({
|
||||||
|
type: ItemStatus.ERROR,
|
||||||
|
value: formatMessage(
|
||||||
|
{id: 'widget.input.max_length', defaultMessage: 'Must be no more than {limit} characters'},
|
||||||
|
{limit},
|
||||||
|
)});
|
||||||
|
} else if (minLengthNotMet > 0) {
|
||||||
|
setCustomInputLabel({
|
||||||
|
type: ItemStatus.ERROR,
|
||||||
|
value: formatMessage(
|
||||||
|
{id: 'widget.input.min_length', defaultMessage: 'Must be at least {minLength} characters'},
|
||||||
|
{minLength},
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showLegend = Boolean(focused || value);
|
const showLegend = Boolean(focused || value);
|
||||||
const error = customInputLabel?.type === ItemStatus.ERROR;
|
const error = customInputLabel?.type === ItemStatus.ERROR;
|
||||||
const warning = customInputLabel?.type === ItemStatus.WARNING;
|
const warning = customInputLabel?.type === ItemStatus.WARNING;
|
||||||
const limitExceeded = limit && value && !Array.isArray(value) ? value.toString().length - limit : 0;
|
|
||||||
const minLengthNotMet = minLength && value !== undefined && !Array.isArray(value) ? minLength - value.toString().length : (minLength || 0);
|
|
||||||
|
|
||||||
// Show min length error even when the input is empty (to match existing behavior in tests)
|
|
||||||
const isMinLengthError = minLengthNotMet > 0;
|
|
||||||
const isMaxLengthError = limitExceeded > 0;
|
|
||||||
|
|
||||||
// Generate derived error messages
|
|
||||||
let derivedErrorMessage: React.ReactNode | null = null;
|
|
||||||
if (isMaxLengthError && !customInputLabel) {
|
|
||||||
derivedErrorMessage = formatMessage(
|
|
||||||
{id: 'widget.input.max_length', defaultMessage: 'Must be no more than {limit} characters'},
|
|
||||||
{limit},
|
|
||||||
);
|
|
||||||
} else if (isMinLengthError && !customInputLabel) {
|
|
||||||
derivedErrorMessage = formatMessage(
|
|
||||||
{id: 'widget.input.min_length', defaultMessage: 'Must be at least {minLength} characters'},
|
|
||||||
{minLength},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearButton = value && clearable ? (
|
const clearButton = value && clearable ? (
|
||||||
<div
|
<div
|
||||||
@@ -220,7 +222,7 @@ const Input = React.forwardRef((
|
|||||||
placeholder={placeholderValue}
|
placeholder={placeholderValue}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
aria-describedby={customInputLabel ? errorId : undefined}
|
aria-describedby={customInputLabel ? errorId : undefined}
|
||||||
aria-invalid={error || hasError || limitExceeded > 0}
|
aria-invalid={error || hasError}
|
||||||
rows={3}
|
rows={3}
|
||||||
name={name}
|
name={name}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -240,7 +242,7 @@ const Input = React.forwardRef((
|
|||||||
placeholder={placeholderValue}
|
placeholder={placeholderValue}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
aria-describedby={customInputLabel ? errorId : undefined}
|
aria-describedby={customInputLabel ? errorId : undefined}
|
||||||
aria-invalid={error || hasError || limitExceeded > 0}
|
aria-invalid={error || hasError}
|
||||||
name={name}
|
name={name}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
@@ -256,7 +258,7 @@ const Input = React.forwardRef((
|
|||||||
<div className={classNames('Input_container', containerClassName, {disabled})}>
|
<div className={classNames('Input_container', containerClassName, {disabled})}>
|
||||||
<fieldset
|
<fieldset
|
||||||
className={classNames('Input_fieldset', className, {
|
className={classNames('Input_fieldset', className, {
|
||||||
Input_fieldset___error: hasError || limitExceeded > 0 || isMinLengthError || customInputLabel?.type === 'error',
|
Input_fieldset___error: hasError || customInputLabel?.type === 'error',
|
||||||
Input_fieldset___legend: showLegend,
|
Input_fieldset___legend: showLegend,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@@ -269,23 +271,13 @@ const Input = React.forwardRef((
|
|||||||
{inputPrefix}
|
{inputPrefix}
|
||||||
{textPrefix && <span>{textPrefix}</span>}
|
{textPrefix && <span>{textPrefix}</span>}
|
||||||
{generateInput()}
|
{generateInput()}
|
||||||
{limitExceeded > 0 && (
|
|
||||||
<span className='Input_limit-exceeded'>
|
|
||||||
{'-'}{limitExceeded}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{Boolean(isMinLengthError && showMinLengthIndicator) && (
|
|
||||||
<span className='Input_limit-exceeded'>
|
|
||||||
{'+'}{minLengthNotMet}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{inputSuffix}
|
{inputSuffix}
|
||||||
{clearButton}
|
{clearButton}
|
||||||
</div>
|
</div>
|
||||||
{addon}
|
{addon}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{/* Display custom or derived error messages */}
|
{/* Display custom or derived error messages */}
|
||||||
{(customInputLabel || derivedErrorMessage) && (
|
{customInputLabel && (
|
||||||
<div
|
<div
|
||||||
className={`Input___customMessage Input___${customInputLabel?.type || 'error'}`}
|
className={`Input___customMessage Input___${customInputLabel?.type || 'error'}`}
|
||||||
id={errorId}
|
id={errorId}
|
||||||
@@ -298,8 +290,10 @@ const Input = React.forwardRef((
|
|||||||
'icon-information-outline': (customInputLabel?.type || 'error') === ItemStatus.INFO,
|
'icon-information-outline': (customInputLabel?.type || 'error') === ItemStatus.INFO,
|
||||||
'icon-check': (customInputLabel?.type || 'error') === ItemStatus.SUCCESS,
|
'icon-check': (customInputLabel?.type || 'error') === ItemStatus.SUCCESS,
|
||||||
})}
|
})}
|
||||||
|
aria-hidden={Boolean(customInputLabel.value)}
|
||||||
|
aria-label={customInputLabel.value ? '' : customInputLabel.type || 'error'}
|
||||||
/>
|
/>
|
||||||
<span>{customInputLabel?.value || derivedErrorMessage}</span>
|
<span>{customInputLabel?.value}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3838,10 +3838,8 @@
|
|||||||
"create_post.write": "Write to {channelDisplayName}",
|
"create_post.write": "Write to {channelDisplayName}",
|
||||||
"create_team.createTeamRestricted.message": "Your workspace plan has reached the limit on the number of teams. Create unlimited teams with a free 30-day trial. Contact your System Administrator.",
|
"create_team.createTeamRestricted.message": "Your workspace plan has reached the limit on the number of teams. Create unlimited teams with a free 30-day trial. Contact your System Administrator.",
|
||||||
"create_team.createTeamRestricted.title": "Professional feature",
|
"create_team.createTeamRestricted.title": "Professional feature",
|
||||||
"create_team.display_name.charLength": "Name must be {min} or more characters up to a maximum of {max}. You can add a longer team description later.",
|
|
||||||
"create_team.display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.",
|
"create_team.display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.",
|
||||||
"create_team.display_name.next": "Next",
|
"create_team.display_name.next": "Next",
|
||||||
"create_team.display_name.required": "This field is required",
|
|
||||||
"create_team.display_name.teamName": "Team Name",
|
"create_team.display_name.teamName": "Team Name",
|
||||||
"create_team.pageTitle": "Create a team - {siteName}",
|
"create_team.pageTitle": "Create a team - {siteName}",
|
||||||
"create_team.team_url.back": "Back to previous step",
|
"create_team.team_url.back": "Back to previous step",
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user