Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat #186 - MultipleChoice component, Refac ShortcutMicrocopy #201

Merged
merged 30 commits into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1bef0d0
feat #186 - Add TimeInput component
sam-dassana Jan 19, 2021
a3a6f0b
feat #186 - Fix bug 0 is valid val
sam-dassana Jan 19, 2021
97a0a1f
feat #186 - Add MultipleChoice comp
sam-dassana Jan 19, 2021
df42dff
feat #186 - Add styles for MultipleChoice
sam-dassana Jan 19, 2021
18f760c
feat #186 - Add tooltip, hover bg color
sam-dassana Jan 19, 2021
a4e08b0
feat #186 - Update failing snapshot tests
sam-dassana Jan 19, 2021
2bdf679
feat #186 - Fix TimeInput type
sam-dassana Jan 20, 2021
eec35e1
feat #186 - Add MultiSelect defaultSelected prop
sam-dassana Jan 20, 2021
2e63b34
feat #186 - Add MultipleChoice to Form
sam-dassana Jan 20, 2021
a9704d0
feat #186 - Add loading state to Multiple Choice
sam-dassana Jan 20, 2021
233ba78
feat #186 - Fix bug event propagation
sam-dassana Jan 21, 2021
1743ffd
feat #186 - Throw error if MCQ > 26 items
sam-dassana Jan 21, 2021
77f50c9
feat #186 - Delete TimeInput comp
sam-dassana Jan 22, 2021
68e948f
feat #186 - Refac/Redo ShortcutMicrocopy
sam-dassana Jan 23, 2021
cb9324a
feat #186 - Update Modal styles
sam-dassana Jan 25, 2021
f91d987
feat #186 - Allow tabbing through items
sam-dassana Jan 25, 2021
959017d
feat #186 - Add prop getEventTarget
sam-dassana Jan 25, 2021
953fa06
feat #186 - Refac to allow for mode='single'
sam-dassana Jan 25, 2021
c6824a0
feat #186 - Fix FormMultipleChoice
sam-dassana Jan 25, 2021
cbd5cb5
feat #186 - Add prop to allow single column
sam-dassana Jan 25, 2021
de6b53b
feat #186 - Navigate through items with Arrow keys
sam-dassana Jan 26, 2021
5eb3538
feat #186 - Fix bug: tooltip not aligned to item
sam-dassana Jan 26, 2021
8ad447b
feat #186 - Fix item text overflow bug
sam-dassana Jan 26, 2021
6006aa9
feat #186 - Tooltip opens onFocus and onHover
sam-dassana Jan 26, 2021
e4060f7
feat #186 - Update storybook controls
sam-dassana Jan 26, 2021
01a9fea
feat #186 - Fix bug: focused classes not applying
sam-dassana Jan 26, 2021
1d639f4
feat #186 - Fix bug: out of boundary key
sam-dassana Jan 26, 2021
968acfb
feat #186 - Fix bug: ChipInput - duplicate vals being added with addon
sam-dassana Jan 26, 2021
cf65dc2
feat #186 - Address PR comments
sam-dassana Jan 27, 2021
f0c9123
feat #186 - Fix CISO typo, update v, update validate fn
sam-dassana Jan 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,106 changes: 937 additions & 169 deletions src/__snapshots__/storybook.test.ts.snap

Large diffs are not rendered by default.

47 changes: 38 additions & 9 deletions src/components/ChipInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
getInitialValues,
getInputValue,
getTagDeletionProps,
shortcutMicrocopyWidth,
useStyles
} from './utils'
import { Input, InputProps } from 'components/Input'
Expand All @@ -17,6 +16,8 @@ import React, {
FC,
KeyboardEvent,
useEffect,
useLayoutEffect,
useRef,
useState
} from 'react'

Expand Down Expand Up @@ -50,22 +51,27 @@ export const ChipInput: FC<ChipInputProps> = ({
fullWidth,
inputRef,
loading = false,
onBlur,
onFocus,
onChange,
placeholder,
undeleteableValues = [],
validate,
values
}: ChipInputProps) => {
const shortcutMicrocopyRef = useRef<HTMLDivElement>(null)

const [addedValues, setAddedValues] = useState<string[]>(
getInitialValues(values, defaultValues)
)
const [inputValue, setInputValue] = useState('')
const [isInvalidValue, setIsInvalidValue] = useState(false)
const [localError, setLocalError] = useState(false)
const [localErrorMsg, setLocalErrorMsg] = useState('')
const [shortcutMicrocopyWidth, setShortcutMicrocopyWidth] = useState(101)
const [showShortcutMicrocopy, setShowShortcutMicrocopy] = useState(false)

const componentClasses = useStyles({ fullWidth })
const componentClasses = useStyles({ fullWidth, shortcutMicrocopyWidth })

const addInputValue = () => {
const newValues = [
Expand All @@ -88,6 +94,12 @@ export const ChipInput: FC<ChipInputProps> = ({
if (onChange) onChange(newValues)
}

const onInputBlur = (event: ChangeEvent<HTMLInputElement>) => {
setShowShortcutMicrocopy(false)

if (onBlur) onBlur(event)
}

const onInputChange = (event: ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value.toLowerCase())

Expand All @@ -97,6 +109,12 @@ export const ChipInput: FC<ChipInputProps> = ({
if (clearErrros) clearErrros()
}

const onInputFocus = () => {
setShowShortcutMicrocopy(true)

if (onFocus) onFocus()
}

const onKeyDown = (e: KeyboardEvent<Element>) => {
if (e.key === 'Enter') {
e.preventDefault()
Expand Down Expand Up @@ -143,10 +161,19 @@ export const ChipInput: FC<ChipInputProps> = ({
))

useEffect(() => {
!inputValue || addedValues.includes(inputValue) || localError || error
const isDuplicate = addedValues.includes(
getInputValue(inputValue, addonBefore, addonAfter)
)

!inputValue || isDuplicate || localError || error
? setIsInvalidValue(true)
: setIsInvalidValue(false)
}, [addedValues, inputValue, localError, error])
}, [addonBefore, addonAfter, addedValues, inputValue, localError, error])

useLayoutEffect(() => {
if (shortcutMicrocopyRef.current)
setShortcutMicrocopyWidth(shortcutMicrocopyRef.current.scrollWidth)
}, [])

if (values && !onChange)
throw new Error('Controlled chip inputs require an onChange prop')
Expand All @@ -164,16 +191,18 @@ export const ChipInput: FC<ChipInputProps> = ({
fullWidth={fullWidth}
inputRef={inputRef}
loading={loading}
onBlur={onInputBlur}
onChange={onInputChange}
onFocus={onFocus}
onFocus={onInputFocus}
onKeyDown={onKeyDown}
placeholder={placeholder}
value={inputValue}
/>
<ShortcutMicrocopy
loading={loading}
width={shortcutMicrocopyWidth}
/>
{showShortcutMicrocopy && (
<ShortcutMicrocopy
shortcutMicrocopyRef={shortcutMicrocopyRef}
/>
)}
</div>
<div className={componentClasses.tagsWrapper}>
{loading ? renderSkeletenTags() : renderTags()}
Expand Down
6 changes: 2 additions & 4 deletions src/components/ChipInput/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ export const getTagDeletionProps: GetTagDeletionProps = (

// -----------------------------------

export const shortcutMicrocopyWidth = 82

export const useStyles = createUseStyles({
skeleton: { marginLeft: spacing.m },
tag: {
Expand All @@ -62,9 +60,9 @@ export const useStyles = createUseStyles({
tagsWrapper: {
display: 'flex',
flexWrap: 'wrap',
width: ({ fullWidth }) =>
width: ({ fullWidth, shortcutMicrocopyWidth }) =>
fullWidth
? `calc( 100% - ${shortcutMicrocopyWidth + spacing.m}px)`
? `calc( 100% - ${shortcutMicrocopyWidth}px)`
: defaultFieldWidth
},
wrapper: {
Expand Down
2 changes: 2 additions & 0 deletions src/components/Form/Form.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ export default {
interface UserModel {
firstName: string
lastName?: string
timeInput?: number
}

const Template: Story<FormProps<UserModel>> = (args: FormProps<UserModel>) => (
<Form
{...args}
initialValues={{
cloudType: 'azure',
defaultCheckedKeys: [5],
domains: ['@lorem.com'],
firstName: 'First Name'
}}
Expand Down
19 changes: 8 additions & 11 deletions src/components/Form/FormChipInput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Input as AntDInput } from 'antd'
import { BaseFieldProps } from '../types'
import cn from 'classnames'
import FieldLabel from '../FieldLabel'
import { getFormFieldDataTag } from '../utils'
import { ChipInput, ChipInputProps } from 'components/ChipInput'
import { Controller, useFormContext } from 'react-hook-form'
import FieldContext, { FieldContextProps } from '../FieldContext'
import { getFormFieldDataTag, renderFieldLabel } from '../utils'
import React, { FC, useContext, useEffect, useRef } from 'react'

export interface FormChipInputProps
Expand Down Expand Up @@ -48,15 +47,13 @@ const FormChipInput: FC<FormChipInputProps> = ({

return (
<div className={cn(containerClasses)}>
{label && (
<FieldLabel
fullWidth={fullWidth}
label={label}
loading={loading}
required={required}
skeletonWidth={labelSkeletonWidth}
/>
)}
{renderFieldLabel({
fullWidth,
label,
loading,
required,
skeletonWidth: labelSkeletonWidth
})}
<Controller
control={control}
name={name}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { multipleChoiceItems } from 'components/MultipleChoice/fixtures'
import React from 'react'
import { Form, FormProps } from '../index'
import { Meta, Story } from '@storybook/react/types-6-0'

export default {
argTypes: {
initialValues: { control: { disable: true } },
onSubmit: { control: { disable: true } }
},
component: Form,
parameters: {
storyshots: { disable: true }
},
title: 'Form/MultipleChoice'
} as Meta

interface MultipleProps {
roles?: string[]
}

const MultipleTemplate: Story<FormProps<MultipleProps>> = (
args: FormProps<MultipleProps>
) => (
<Form
{...args}
initialValues={{
roles: ['sr-leadership', 'devops']
}}
>
<Form.MultipleChoice
items={multipleChoiceItems}
mode='multiple'
name='multipleChoice'
required
/>
<Form.SubmitButton>Submit</Form.SubmitButton>
</Form>
)

export const Multiple = MultipleTemplate.bind({})

interface SingleProps {
role?: string
}

const SingleTemplate: Story<FormProps<SingleProps>> = (
args: FormProps<SingleProps>
) => (
<Form
{...args}
initialValues={{
role: 'sr-leadership'
}}
>
<Form.MultipleChoice
items={multipleChoiceItems}
mode='single'
name='multipleChoice'
required
/>
</Form>
)

export const Single = SingleTemplate.bind({})
54 changes: 54 additions & 0 deletions src/components/Form/FormMultipleChoice/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { BaseFieldProps } from '../types'
import { Controller, useFormContext } from 'react-hook-form'
import FieldContext, { FieldContextProps } from '../FieldContext'
import { getFormFieldDataTag, renderFieldLabel } from '../utils'
import { MultipleChoice, MultipleChoiceProps } from 'components/MultipleChoice'
import React, { FC, useContext } from 'react'

export interface FormMultipleChoiceProps
extends BaseFieldProps,
Omit<MultipleChoiceProps, 'onChange' | 'value'> {}

const FormMultipleChoice: FC<FormMultipleChoiceProps> = ({
label,
labelSkeletonWidth,
name,
required,
rules = {},
...rest
}: FormMultipleChoiceProps) => {
const { control } = useFormContext()
const { loading } = useContext<FieldContextProps>(FieldContext)

if (required) {
rules.required = true
}

return (
<div>
{renderFieldLabel({
fullWidth: true,
label,
loading,
required,
skeletonWidth: labelSkeletonWidth
})}
<Controller
control={control}
name={name}
render={({ onChange, value }) => (
<MultipleChoice
dataTag={getFormFieldDataTag(name)}
loading={loading}
onChange={onChange}
value={value}
{...rest}
/>
)}
rules={rules}
/>
</div>
)
}

export default FormMultipleChoice
2 changes: 2 additions & 0 deletions src/components/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createUseStyles } from 'react-jss'
import FieldContext from './FieldContext'
import FormChipInput from './FormChipInput'
import FormInput from './FormInput'
import FormMultipleChoice from './FormMultipleChoice'
import FormRadioGroup from './FormRadioGroup'
import FormSelect from './FormSelect'
import FormSubmitButton from './FormSubmitButton'
Expand Down Expand Up @@ -70,6 +71,7 @@ export function Form<Model>({
Form.SubmitButton = FormSubmitButton
Form.ChipInput = FormChipInput
Form.Input = FormInput
Form.MultipleChoice = FormMultipleChoice
Form.RadioGroup = FormRadioGroup
Form.Select = FormSelect
Form.Toggle = FormToggle
Expand Down
1 change: 0 additions & 1 deletion src/components/Form/utils.ts

This file was deleted.

8 changes: 8 additions & 0 deletions src/components/Form/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react'
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)} />
}
2 changes: 2 additions & 0 deletions src/components/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const Input: FC<InputProps> = (props: InputProps) => {
dataTag,
disabled = false,
inputRef,
onBlur = noop,
onChange,
onFocus = noop,
onKeyDown = noop,
Expand Down Expand Up @@ -112,6 +113,7 @@ export const Input: FC<InputProps> = (props: InputProps) => {
addonBefore={addonBefore}
className={cn(componentClasses.container, inputClasses)}
disabled={disabled}
onBlur={onBlur}
onFocus={onFocus}
onKeyDown={onKeyDown}
placeholder={placeholder}
Expand Down
20 changes: 14 additions & 6 deletions src/components/Modal/Modal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@ import { Button } from '../Button'
import { Emitter } from '@dassana-io/web-utils'
import { ModalConfig } from './utils'
import React from 'react'
import { SbTheme } from '../../../.storybook/preview'
import { useTheme } from 'react-jss'
import { Meta, Story } from '@storybook/react/types-6-0'
import { ModalProvider, useModal } from './index'

const mockEmitter = new Emitter()

export default {
decorators: [
Story => (
<ModalProvider emitter={mockEmitter}>
<Story />
</ModalProvider>
)
Story => {
const theme: SbTheme = useTheme()

return (
<ModalProvider
emitter={mockEmitter}
popupContainerSelector={`.${theme.type}`}
>
<Story />
</ModalProvider>
)
}
],
title: 'Modal'
} as Meta
Expand Down
Loading