feat: Labeled Error-bound Input (#14530)

* Error Input Form created

* Adjusting styling

* testing complete

* Corrected component naming in Form

* testing complete

* Corrected component naming in Form

* Renamed stories file to appropriate name

* add image for alert

* Fixed test

* Switched from px to theme

* Adjusting LabeledErrorBoundInputProps

* validation now accepts a string

Co-authored-by: Elizabeth Thompson <eschutho@gmail.com>
This commit is contained in:
Lyndsi Kay Williams 2021-05-20 16:01:27 -05:00 committed by GitHub
parent 9729ffd7a1
commit e4103c272e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 232 additions and 1 deletions

View File

@ -40,6 +40,7 @@ const RequiredLabel = styled.label`
margin-bottom: ${({ theme }) => theme.gridUnit}px;
&::after {
display: inline-block;
margin-left: ${({ theme }) => theme.gridUnit}px;
color: ${({ theme }) => theme.colors.error.base};
font-size: ${({ theme }) => theme.typography.sizes.m}px;
content: '*';

View File

@ -0,0 +1,79 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { useState } from 'react';
import LabeledErrorBoundInput, {
LabeledErrorBoundInputProps,
} from './LabeledErrorBoundInput';
export default {
title: 'LabeledErrorBoundInput',
component: LabeledErrorBoundInput,
};
export const InteractiveLabeledErrorBoundInput = ({
name,
value,
placeholder,
type,
id,
}: LabeledErrorBoundInputProps) => {
const [currentValue, setCurrentValue] = useState(value);
const validateFunctionality: (value: any) => string = value => {
setCurrentValue(value.target.value);
if (value.target.value.includes('success')) {
return 'success';
}
return 'error';
};
return (
<LabeledErrorBoundInput
id={id}
name={name}
validationMethods={{ onChange: validateFunctionality }}
errorMessage={
currentValue === 'success' ? '' : 'Type success in the text bar'
}
helpText="This is a line of example help text"
value={currentValue}
// This must stay the same as name or form breaks
label={name}
placeholder={placeholder}
type={type}
required
/>
);
};
InteractiveLabeledErrorBoundInput.args = {
name: 'Username',
placeholder: 'Example placeholder text...',
id: 1,
};
InteractiveLabeledErrorBoundInput.argTypes = {
type: {
defaultValue: 'textbox',
control: {
type: 'select',
options: ['textbox', 'checkbox', 'radio'],
},
},
};

View File

@ -0,0 +1,61 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import LabeledErrorBoundInput from 'src/components/Form/LabeledErrorBoundInput';
const defaultProps = {
id: 1,
label: 'Username',
name: 'Username',
validationMethods: () => {},
errorMessage: '',
helpText: 'This is a line of example help text',
value: '',
placeholder: 'Example placeholder text...',
type: 'textbox',
};
describe('LabeledErrorBoundInput', () => {
it('renders a LabeledErrorBoundInput normally, without an error', () => {
render(<LabeledErrorBoundInput {...defaultProps} />);
const label = screen.getByText(/username/i);
const textboxInput = screen.getByRole('textbox');
const helperText = screen.getByText('This is a line of example help text');
expect(label).toBeVisible();
expect(textboxInput).toBeVisible();
expect(helperText).toBeVisible();
});
it('renders a LabeledErrorBoundInput with an error', () => {
// Pass an error into props, causing errorText to replace helperText
defaultProps.errorMessage = 'Example error message';
render(<LabeledErrorBoundInput {...defaultProps} />);
const label = screen.getByText(/username/i);
const textboxInput = screen.getByRole('textbox');
const errorText = screen.getByText(/example error message/i);
expect(label).toBeVisible();
expect(textboxInput).toBeVisible();
expect(errorText).toBeVisible();
});
});

View File

@ -0,0 +1,89 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { Input } from 'antd';
import { styled, css, SupersetTheme } from '@superset-ui/core';
import FormItem from './FormItem';
import FormLabel from './FormLabel';
export interface LabeledErrorBoundInputProps {
label?: string;
validationMethods:
| { onBlur: (value: any) => string }
| { onChange: (value: any) => string };
errorMessage: string | null;
helpText?: string;
required?: boolean;
id?: string;
[x: string]: any;
}
const StyledInput = styled(Input)`
margin: 8px 0;
`;
const alertIconStyles = (theme: SupersetTheme, hasError: boolean) => css`
.ant-form-item-children-icon {
display: none;
}
${hasError &&
`.ant-form-item-control-input-content {
position: relative;
&:after {
content: ' ';
display: inline-block;
background: ${theme.colors.error.base};
mask: url('/images/icons/error.svg');
mask-size: cover;
width: ${theme.gridUnit * 4}px;
height: ${theme.gridUnit * 4}px;
position: absolute;
right: 7px;
top: 15px;
}
}`}
`;
const LabeledErrorBoundInput = ({
label,
validationMethods,
errorMessage,
helpText,
required = false,
id,
...props
}: LabeledErrorBoundInputProps) => (
<>
<FormLabel htmlFor={id} required={required}>
{label}
</FormLabel>
<FormItem
css={(theme: SupersetTheme) => alertIconStyles(theme, !!errorMessage)}
validateTrigger={Object.keys(validationMethods)}
validateStatus={errorMessage ? 'error' : 'success'}
help={errorMessage || helpText}
hasFeedback={!!errorMessage}
>
<StyledInput {...props} {...validationMethods} />
</FormItem>
</>
);
export default LabeledErrorBoundInput;

View File

@ -19,5 +19,6 @@
import Form from './Form';
import FormItem from './FormItem';
import FormLabel from './FormLabel';
import LabeledErrorBoundInput from './LabeledErrorBoundInput';
export { Form, FormItem, FormLabel };
export { Form, FormItem, FormLabel, LabeledErrorBoundInput };