Skip to content

Commit

Permalink
feat #218 - Add MultiSelect to Form, fix MS bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
sam-dassana committed Feb 19, 2021
1 parent 6fd334e commit 9ee21aa
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 6 deletions.
14 changes: 14 additions & 0 deletions src/components/Form/Form.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface UserModel {
firstName: string
isProduction?: boolean
lastName?: string
persona?: string[]
timeInput?: number
severity?: string
timezone?: string
Expand All @@ -41,6 +42,7 @@ const Template: Story<FormProps<UserModel>> = (args: FormProps<UserModel>) => (
defaultCheckedKeys: [5],
domains: ['@lorem.com'],
firstName: 'First Name',
persona: ['other'],
timezone: 'Asia/Kathmandu',
workStart: 9
}}
Expand All @@ -52,6 +54,18 @@ const Template: Story<FormProps<UserModel>> = (args: FormProps<UserModel>) => (
name='cloudType'
options={iconOptions}
/>
<Form.MultiSelect
label='Persona'
name='persona'
options={[
{ text: 'CISO', value: 'ciso' },
{ text: 'SecOps', value: 'sec-ops' },
{ text: 'DevOps', value: 'dev-ops' },
{ text: 'Compliance', value: 'compliance' },
{ text: 'other', value: 'other' }
]}
required
/>
<Form.ChipInput
addonBefore='@'
label='Domains'
Expand Down
72 changes: 72 additions & 0 deletions src/components/Form/FormMultiSelect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { BaseFieldProps } from '../types'
import cn from 'classnames'
import FieldError from '../FieldError'
import FieldLabel from '../FieldLabel'
import { Controller, useFormContext } from 'react-hook-form'
import FieldContext, { FieldContextProps } from '../FieldContext'
import { getFormFieldDataTag, getRulesForArrVals } from '../utils'
import { MultiSelect, MultiSelectProps } from 'components/Select/MultiSelect'
import React, { FC, useContext } from 'react'

export interface FormMultiSelectProps
extends BaseFieldProps,
Omit<MultiSelectProps, 'defaultValues' | 'onChange' | 'values'> {}

const FormMultiSelect: FC<FormMultiSelectProps> = ({
containerClasses = [],
fieldErrorClasses = [],
fullWidth = false,
label,
labelSkeletonWidth,
name,
required,
rules = {},
...rest
}: FormMultiSelectProps) => {
const { clearErrors, control, errors } = useFormContext()
const { loading } = useContext<FieldContextProps>(FieldContext)

const errorMsg = errors[name] ? errors[name].message : ''

const onFocus = () => {
if (errors[name]) clearErrors(name)
}

return (
<div className={cn(containerClasses)}>
{label && (
<FieldLabel
fullWidth={fullWidth}
label={label}
loading={loading}
required={required}
skeletonWidth={labelSkeletonWidth}
/>
)}
<Controller
control={control}
name={name}
render={({ onChange, value }) => (
<MultiSelect
dataTag={getFormFieldDataTag(name)}
error={errors[name]}
fullWidth={fullWidth}
loading={loading}
onChange={onChange}
onFocus={onFocus}
values={value}
{...rest}
/>
)}
rules={getRulesForArrVals({ required, rules })}
/>
<FieldError
classes={fieldErrorClasses}
error={errorMsg}
fullWidth={fullWidth}
/>
</div>
)
}

export default FormMultiSelect
8 changes: 6 additions & 2 deletions src/components/Form/FormMultipleChoice/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { BaseFieldProps } from '../types'
import { Controller, useFormContext } from 'react-hook-form'
import FieldContext, { FieldContextProps } from '../FieldContext'
import { getFormFieldDataTag, renderFieldLabel } from '../utils'
import {
getFormFieldDataTag,
getRulesForArrVals,
renderFieldLabel
} from '../utils'
import { MultipleChoice, MultipleChoiceProps } from 'components/MultipleChoice'
import React, { FC, useContext } from 'react'

Expand Down Expand Up @@ -45,7 +49,7 @@ const FormMultipleChoice: FC<FormMultipleChoiceProps> = ({
{...rest}
/>
)}
rules={rules}
rules={getRulesForArrVals({ required, rules })}
/>
</div>
)
Expand Down
2 changes: 2 additions & 0 deletions src/components/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import FieldContext from './FieldContext'
import FormChipInput from './FormChipInput'
import FormInput from './FormInput'
import FormMultipleChoice from './FormMultipleChoice'
import FormMultiSelect from './FormMultiSelect'
import FormRadioGroup from './FormRadioGroup'
import FormSelect from './FormSelect'
import FormSubmitButton from './FormSubmitButton'
Expand Down Expand Up @@ -74,6 +75,7 @@ Form.SubmitButton = FormSubmitButton
Form.ChipInput = FormChipInput
Form.Input = FormInput
Form.MultipleChoice = FormMultipleChoice
Form.MultiSelect = FormMultiSelect
Form.RadioGroup = FormRadioGroup
Form.Select = FormSelect
Form.TimeInput = FormTimeInput
Expand Down
34 changes: 34 additions & 0 deletions src/components/Form/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
import cloneDeep from 'lodash/cloneDeep'
import React from 'react'
import { ValidationRules } from 'react-hook-form'
import FieldLabel, { FieldLabelProps } from './FieldLabel'

export const getFormFieldDataTag = (tag: string): string => `field-${tag}`

export const renderFieldLabel = (props: Partial<FieldLabelProps>) => {
if (props.label) return <FieldLabel {...(props as FieldLabelProps)} />
}

interface Params {
rules?: ValidationRules
required?: boolean
}
export const getRulesForArrVals = ({
rules = {},
required = false
}: Params): ValidationRules => {
const newRules = cloneDeep(rules)

if (required) {
newRules.required = true

const required = (values: string[]) => values.length > 0 || ''

if (!('validate' in newRules)) {
newRules.validate = required
} else {
newRules.validate =
typeof newRules.validate === 'object'
? { ...newRules.validate, required }
: {
required,
validate: newRules.validate!
// eslint-disable-next-line no-mixed-spaces-and-tabs
}
}
}

return newRules
}
4 changes: 4 additions & 0 deletions src/components/Select/BaseSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ export const BaseSelect: FC<BaseSelectProps> = (props: BaseSelectProps) => {
defaultOpen = false,
disabled = false,
error = false,
focused = false,
loading = false,
onBlur,
onFocus,
options,
optionsConfig = {},
placeholder = '',
Expand Down Expand Up @@ -122,11 +124,13 @@ export const BaseSelect: FC<BaseSelectProps> = (props: BaseSelectProps) => {
) : (
<div className={componentClasses.container}>
<AntDSelect
autoFocus={focused}
className={inputClasses}
defaultOpen={defaultOpen}
disabled={disabled}
dropdownClassName={componentClasses.dropdown}
notFoundContent={<NoContentFound />}
onFocus={onFocus}
placeholder={placeholder}
showArrow
showSearch={showSearch}
Expand Down
3 changes: 3 additions & 0 deletions src/components/Select/MultiSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const MultiSelect: FC<MultiSelectProps> = (props: MultiSelectProps) => {
defaultValues = [],
disabled = false,
error = false,
focused = false,
fullWidth = false,
loading = false,
maxTagCount,
Expand Down Expand Up @@ -104,13 +105,15 @@ export const MultiSelect: FC<MultiSelectProps> = (props: MultiSelectProps) => {

return (
<BaseSelect
{...props}
classes={classes}
dataTag={dataTag}
defaultOpen={defaultOpen}
defaultValue={defaultValues}
disabled={disabled}
dropdownRender={dropdownRender}
error={error}
focused={focused}
fullWidth={fullWidth}
loading={loading}
localValues={localValues}
Expand Down
2 changes: 2 additions & 0 deletions src/components/Select/MultiSelect/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface MultiSelectProps
* Default values for select component. Without this, the select dropdown will be blank until an option is selected. Gets overwritten by values if both are provided
*/
defaultValues?: string[]
focused?: boolean
/**
* The number after which to show "& 'x' more" for selected tags. Setting it to 0 will always show all selected tags in the input
* @default 2
Expand All @@ -16,6 +17,7 @@ export interface MultiSelectProps
* Array of options to be rendered in the dropdown
*/
onChange?: (values: string[]) => void
onFocus?: () => void
onSearch?: (value: string) => void
/**
* Only valid if showSearch is true and and onSearch is not passed. By default options are only filtered by text. To filter by other keys, pass an array of keys to filter. Eg. ['value']
Expand Down
8 changes: 4 additions & 4 deletions src/components/Select/MultiSelect/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ export const useStyles = createUseStyles({
checkbox: { marginRight: spacing.s },
container: ({ fullWidth, matchSelectedContentWidth }) => ({
'& .ant-select': {
'&$error > .ant-select-selector': {
border: `1px solid ${themedStyles[light].error.borderColor}`
},
'&.ant-select-multiple': {
'&$error > .ant-select-selector': {
border: `1px solid ${themedStyles[light].error.borderColor}`
},
'&.ant-select-disabled': generateThemedDisabledStyles(light),
...generateThemedSelectStyles(light),
...generateThemedTagStyles(light),
Expand All @@ -106,7 +106,7 @@ export const useStyles = createUseStyles({
display: 'flex',
flexWrap: matchSelectedContentWidth ? 'nowrap' : 'wrap'
},
'&:after': { display: 'none' },
'&:after': { width: 0 },
...generateThemedInputStyles(light),
borderRadius
},
Expand Down
3 changes: 3 additions & 0 deletions src/components/Select/SingleSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const Select: FC<SelectProps> = (props: SelectProps) => {
defaultValue,
disabled = false,
error = false,
focused = false,
fullWidth = false,
loading = false,
matchSelectedContentWidth = false,
Expand Down Expand Up @@ -39,12 +40,14 @@ export const Select: FC<SelectProps> = (props: SelectProps) => {

return (
<BaseSelect
{...props}
classes={classes}
dataTag={dataTag}
defaultOpen={defaultOpen}
defaultValue={defaultValue}
disabled={disabled}
error={error}
focused={focused}
fullWidth={fullWidth}
loading={loading}
matchSelectedContentWidth={matchSelectedContentWidth}
Expand Down
2 changes: 2 additions & 0 deletions src/components/Select/SingleSelect/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface SelectProps extends BaseFormElementProps {
* @default false
*/
defaultOpen?: boolean
focused?: boolean
/**
* Sets the width of the select to be same as the selected content width. Can be false or a number which will be used as the minimum width
*/
Expand All @@ -29,6 +30,7 @@ export interface SelectProps extends BaseFormElementProps {
* Selector of HTML element inside which to render the popup/dropdown
*/
popupContainerSelector?: string
onFocus?: () => void
/**
* Array of options to be rendered in the dropdown
*/
Expand Down

0 comments on commit 9ee21aa

Please sign in to comment.