mirror of https://github.com/apache/superset.git
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:
parent
9729ffd7a1
commit
e4103c272e
|
@ -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: '*';
|
||||
|
|
|
@ -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'],
|
||||
},
|
||||
},
|
||||
};
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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;
|
|
@ -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 };
|
||||
|
|
Loading…
Reference in New Issue