Skip to content

Commit

Permalink
Merge pull request #574 from tudi2d/542-improve-form-validation
Browse files Browse the repository at this point in the history
#542 - Show form validation to the user
  • Loading branch information
BenGamma authored Sep 12, 2019
2 parents 6a953ff + ad84d70 commit e49ba4f
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 76 deletions.
43 changes: 25 additions & 18 deletions src/components/Form/Fields.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import * as React from 'react'
import { TextAreaStyled, Input, TextAreaDisabled } from './elements'
import {
TextAreaStyled,
Input,
TextAreaDisabled,
ErrorMessage,
} from './elements'
import { FieldRenderProps } from 'react-final-form'
import Calendar, { CalendarProps } from 'react-calendar'

Expand All @@ -17,9 +22,10 @@ type YearPickerProps = IFieldProps & CalendarProps

export const InputField = ({ input, meta, ...rest }: IFieldProps) => (
<>
<Input {...input} {...rest} />
{/* CC - removed clunky error reporting until better solution implemented */}
{/* {meta.error && meta.touched && <span>{meta.error}</span>} */}
<Input invalid={meta.error && meta.touched} {...input} {...rest} />
{meta.error && meta.touched ? (
<ErrorMessage>{meta.error}</ErrorMessage>
) : null}
</>
)

Expand All @@ -28,18 +34,20 @@ export const TextAreaField = ({
meta,
disabled,
...rest
}: IFieldProps) => (
<>
{disabled ? (
// want disabled textarea to just render as styled div to remove scrollbars
<TextAreaDisabled>{input.value}</TextAreaDisabled>
) : (
<TextAreaStyled {...input} {...rest} />
)}

{/* {meta.error && meta.touched && <span>{meta.error}</span>} */}
</>
)
}: IFieldProps) =>
disabled ? (
// want disabled textarea to just render as styled div to remove scrollbars
<TextAreaDisabled>{input.value}</TextAreaDisabled>
) : (
<>
<TextAreaStyled
invalid={meta.error && meta.touched}
{...input}
{...rest}
/>
{meta.error && meta.touched && <ErrorMessage>{meta.error}</ErrorMessage>}
</>
)

export const YearPicker = ({ input, meta, ...rest }: YearPickerProps) => (
<>
Expand All @@ -53,7 +61,6 @@ export const YearPicker = ({ input, meta, ...rest }: YearPickerProps) => (
input.onChange(v)
}}
/>

{/* {meta.error && meta.touched && <span>{meta.error}</span>} */}
{/* meta.error && meta.touched && <ErrorMessage>{meta.error}</ErrorMessage> */}
</>
)
27 changes: 15 additions & 12 deletions src/components/Form/ImageInput.field.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import React from 'react'
import { ImageInput } from '../ImageInput/ImageInput'
import { IFieldProps } from './Fields'
import { FieldContainer } from './elements'
import { FieldContainer, ErrorMessage } from './elements'

export const ImageInputField = ({ input, meta, ...rest }: IFieldProps) => (
<FieldContainer>
<ImageInput
{...rest}
// as validation happens on blur also want to artificially trigger when values change
// (no native blur event)
onFilesChange={files => {
input.onChange(files)
input.onBlur()
}}
/>
</FieldContainer>
<>
<FieldContainer invalid={meta.touched && meta.error}>
<ImageInput
{...rest}
// as validation happens on blur also want to artificially trigger when values change
// (no native blur event)
onFilesChange={files => {
input.onChange(files)
input.onBlur()
}}
/>
</FieldContainer>
{meta.error && meta.touched && <ErrorMessage>{meta.error}</ErrorMessage>}
</>
)
1 change: 1 addition & 0 deletions src/components/Form/LocationSearch.field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ export const LocationSearchField = ({
}
input.onBlur()
}}
placeholder="Search for a location *"
/>
)
41 changes: 22 additions & 19 deletions src/components/Form/Select.field.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as React from 'react'
import ReactFlagsSelect from 'react-flags-select'
import Select from 'react-select'
import { IFieldProps } from './Fields'
import { Styles } from 'react-select/lib/styles'
import { Props as SelectProps } from 'react-select/lib/Select'
import { FieldContainer } from './elements'
import { Styles } from 'react-select/lib/styles'
import theme from 'src/themes/styled.theme'
import ReactFlagsSelect from 'react-flags-select'
import { getCountryName } from 'src/utils/helpers'
import { FlexContainer } from '../Layout/FlexContainer'
import { ErrorMessage, FieldContainer } from './elements'
import { IFieldProps } from './Fields'

interface ISelectOption {
value: string
Expand Down Expand Up @@ -63,19 +64,22 @@ const defaultProps: Partial<ISelectFieldProps> = {
}
export const SelectField = ({ input, meta, ...rest }: ISelectFieldProps) => (
// note, we first use a div container so that default styles can be applied
<FieldContainer style={rest.style}>
<Select
styles={SelectStyles}
onChange={v => {
input.onChange(getValueFromSelect(v))
}}
onBlur={input.onBlur}
onFocus={input.onFocus}
value={getValueForSelect(rest.options, input.value)}
{...defaultProps}
{...rest}
/>
</FieldContainer>
<FlexContainer p={0} flexWrap="wrap">
<FieldContainer invalid={meta.error && meta.touched} style={rest.style}>
<Select
styles={SelectStyles}
onChange={v => {
input.onChange(getValueFromSelect(v))
}}
onBlur={input.onBlur}
onFocus={input.onFocus}
value={getValueForSelect(rest.options, input.value)}
{...defaultProps}
{...rest}
/>
</FieldContainer>
{meta.error && meta.touched && <ErrorMessage>{meta.error}</ErrorMessage>}
</FlexContainer>
)

export const FlagSelector = ({ input, meta, ...rest }: ISelectFieldProps) => (
Expand All @@ -89,7 +93,6 @@ export const FlagSelector = ({ input, meta, ...rest }: ISelectFieldProps) => (
{...defaultProps}
{...rest}
/>

{/* {meta.error && meta.touched && <span>{meta.error}</span>} */}
{/* meta.error && meta.touched && <ErrorMessage>{meta.error}</ErrorMessage> */}
</>
)
21 changes: 16 additions & 5 deletions src/components/Form/elements.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import styled, { css } from 'styled-components'
import theme from 'src/themes/styled.theme'

export const inputStyles = css`
border: 1px solid ${theme.colors.black};
interface IFormElement {
invalid?: boolean
}

export const inputStyles = ({ invalid }: IFormElement) => css`
border: 1px solid ${invalid ? theme.colors.error : theme.colors.black};
border-radius: 4px;
font-size: ${theme.fontSizes[2] + 'px'};
background: white;
Expand All @@ -16,12 +20,12 @@ export const inputStyles = css`
}
`

export const Input = styled.input`
export const Input = styled.input<IFormElement>`
${inputStyles};
height: 40px;
`

export const TextAreaStyled = styled.textarea`
export const TextAreaStyled = styled.textarea<IFormElement>`
${inputStyles};
height: 150px;
font-family: inherit;
Expand All @@ -32,8 +36,15 @@ export const TextAreaDisabled = styled.div`
`

// generic container used for some custom component fields
export const FieldContainer = styled.div`
export const FieldContainer = styled.div<IFormElement>`
${inputStyles};
border: 'none';
padding: 0;
`
export const ErrorMessage = styled.span`
position: relative;
bottom: ${theme.space[2]}px;
color: ${theme.colors.error};
font-size: ${theme.fontSizes[0]}px;
height: ${theme.space[0]};
`
4 changes: 2 additions & 2 deletions src/pages/Events/Content/EventsCreate/EventsCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class EventsCreate extends React.Component<IProps, IState> {
validate={value => this.validateTitle(value)}
validateFields={[]}
component={InputField}
placeholder="Title of your event"
placeholder="Title of your event *"
/>
<Field
name="tags"
Expand All @@ -117,7 +117,7 @@ export class EventsCreate extends React.Component<IProps, IState> {
validateFields={[]}
validate={value => this.validateUrl(value)}
component={InputField}
placeholder="URL to offsite link (Facebook, Meetup, etc)"
placeholder="URL to offsite link (Facebook, Meetup, etc) *"
/>
</BoxContainer>
</form>
Expand Down
46 changes: 26 additions & 20 deletions src/pages/Howto/Content/CreateHowto/Step/Step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,30 +65,36 @@ class Step extends Component<IProps, IState> {
<Field
name={`${step}.title`}
component={InputField}
placeholder={`Title of Step ${index + 1}`}
placeholder={`Title of Step ${index + 1} *`}
validate={required}
validateFields={[]}
/>
{/* Left */}
<FlexContainer p={0} pr={2} flexDirection="column" flex={1}>
<Field
name={`${step}.text`}
placeholder="Describe this step"
component={TextAreaField}
style={{ resize: 'vertical', height: '100%' }}
validate={required}
validateFields={[]}
/>
<FlexContainer p={0} flexWrap="wrap">
{/* Left */}
<FlexContainer p={0} pr={2} flexDirection="column" flex={1}>
<Field
name={`${step}.text`}
placeholder="Describe this step *"
component={TextAreaField}
style={{ resize: 'vertical', height: '100%' }}
validate={required}
validateFields={[]}
/>
</FlexContainer>
{/* right */}
<BoxContainer p={0} width={[1, '305px', null]}>
<Field
name={`${step}.images`}
component={ImageInputField}
multi
/>
<Field
name={`${step}.caption`}
component={InputField}
placeholder="Insert Caption"
/>
</BoxContainer>
</FlexContainer>
{/* right */}
<BoxContainer p={0} width={[1, '305px', null]}>
<Field name={`${step}.images`} component={ImageInputField} multi />
<Field
name={`${step}.caption`}
component={InputField}
placeholder="Insert Caption"
/>
</BoxContainer>
</FlexContainer>
</BoxContainer>
)
Expand Down

0 comments on commit e49ba4f

Please sign in to comment.