Skip to content

Commit

Permalink
feat: Add auto complete component with comboBox and freeSolo modes (#116
Browse files Browse the repository at this point in the history
)

closes #9
  • Loading branch information
PuBHARGAVI committed Oct 23, 2023
1 parent e5b4286 commit 42b1325
Show file tree
Hide file tree
Showing 7 changed files with 485 additions and 3 deletions.
146 changes: 146 additions & 0 deletions src/packages/AutoComplete/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React, { useRef, useState } from 'react'
import { useOnClickOutside, useOnKeypress } from '../../hooks'
import Paper from '../Paper'
import TextField from '../TextField'

import Tag from '../Tag'
import * as S from './styles'

export type MockData = {
id: number
name: string
}

export type AutoCompleteProps = {
label: string
freeSolo: boolean
fieldPlaceholder: string
options: MockData[]
}

export const AutoComplete = ({
label,
freeSolo,
fieldPlaceholder,
options
}: AutoCompleteProps) => {
const ref = useRef<HTMLDivElement>(null)

const [textFieldValue, setTextFieldValue] = useState('')
const [showOptions, setShowOptions] = useState(false)
const [selectedOption, setSelectedOption] = useState('')
const [filteredSuggestions, setFilteredSuggestions] = useState<MockData[]>([
...options
])

const handleOptionClick = (option: string) => {
setTextFieldValue(option)
setSelectedOption(option)
setShowOptions(false)
setFilteredSuggestions(options)
}

const optionsFilter = (inputValue: string) => {
return options.filter((option: MockData) =>
option.name.toLowerCase().includes(inputValue.toLowerCase())
)
}

const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
showOptions === false ? setShowOptions(true) : null
const currentInputValue = event.target.value
setTextFieldValue(currentInputValue)
const filteredOptions = optionsFilter(currentInputValue)
filteredOptions.length === 0 && freeSolo
? (setShowOptions(false), setFilteredSuggestions(filteredOptions))
: setFilteredSuggestions(filteredOptions)
currentInputValue === '' && selectedOption !== ''
? setSelectedOption('')
: null
}

const resetOptions = () => setFilteredSuggestions(options)

const setTheFields = () => {
freeSolo === false
? selectedOption === ''
? (setTextFieldValue(''), resetOptions())
: (setTextFieldValue(selectedOption), resetOptions())
: null

setShowOptions(false)
}

useOnClickOutside(ref, () => {
setTheFields()
})

useOnKeypress('Escape', () => {
setTheFields()
})

const handleOnClick = () => {
if (filteredSuggestions.length === 0) {
setShowOptions(false)
} else if (textFieldValue === '') {
setShowOptions(!showOptions)
} else if (selectedOption !== '') {
setShowOptions(true)
}
}

return (
<S.AutoComplete ref={ref} data-testid="auto-complete">
<>
<TextField
data-testid="auto-complete-input"
label={label}
value={textFieldValue}
placeholder={fieldPlaceholder}
onChange={onInputChange}
onClick={handleOnClick}
style={{ marginRight: 10 }}
/>
{freeSolo === false && (
<S.AutoCompleteArrow
data-testid="arrow-toggle"
direction={showOptions ? 'up' : 'down'}
show={freeSolo === false ? true : false}
onClick={() => setShowOptions(!showOptions)}
/>
)}
</>

{showOptions && (
<Paper data-testid="options" active={showOptions} placement="bottom">
<div
style={{
display: 'grid',
gridGap: '0.5rem',
gridTemplateColumns: '1fr',
padding: '0.5rem',
placeItems: 'center'
}}
>
{filteredSuggestions.length > 0 ? (
filteredSuggestions.map((option) => (
<Tag
key={option.id}
value={option.name}
onClick={() => handleOptionClick(option.name)}
isPointer
isDarkBgOnHover={true}
isDarkBg={option.name === selectedOption}
/>
))
) : (
<S.NoOptionsWrapper>{'No Options'}</S.NoOptionsWrapper>
)}
</div>
</Paper>
)}
</S.AutoComplete>
)
}

export default AutoComplete
18 changes: 18 additions & 0 deletions src/packages/AutoComplete/mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const mock = [
{
id: 1,
name: 'New York'
},
{
id: 2,
name: 'Los Angeles'
},
{
id: 3,
name: 'Chicago'
},
{
id: 4,
name: 'Houston'
}
]
29 changes: 29 additions & 0 deletions src/packages/AutoComplete/stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Meta, StoryFn } from '@storybook/react'
import React from 'react'

import AutoComplete, { AutoCompleteProps } from '.'
import { mock } from './mock'

export default {
title: 'AutoComplete',
component: AutoComplete,
args: {
label: 'where',
fieldPlaceholder: 'Enter the Input',
freeSolo: false,
options: mock
},
argTypes: {
fieldValue: {
control: false
}
}
} as Meta

export const ComboBox: StoryFn<AutoCompleteProps> = (args) => {
return <AutoComplete {...args} freeSolo={false} />
}

export const FreeSolo: StoryFn<AutoCompleteProps> = (args) => {
return <AutoComplete {...args} freeSolo />
}
56 changes: 56 additions & 0 deletions src/packages/AutoComplete/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { BaseHTMLAttributes } from 'react'
import styled, { css } from 'styled-components'
import { Select } from '../Select/styles'

export type NoOptionsProps = {
size?: 'xsmall' | 'small' | 'medium'
} & BaseHTMLAttributes<HTMLDivElement>

export const AutoComplete = styled(Select)`
${() => css`
display: flex;
flex-direction: row;
align-items: center;
`}
`

export const NoOptionsWrapper = styled.div<Pick<NoOptionsProps, 'size'>>`
${({ theme, size = 'small' }) => css`
padding: ${theme.spacings.mini} ${theme.spacings.xxsmall};
color: ${theme.colors.neutral.darkest};
width: fit-content;
height: fit-content;
border-radius: ${theme.border.radius};
font-size: ${theme.font.sizes[size]};
font-weight: ${theme.font.weights.regular};
`}
`

const figureArrowModifiers = {
up: css`
transform: rotate(-45deg);
`,
down: css`
transform: rotate(135deg);
`
}

export const AutoCompleteArrow = styled.div<{
direction: 'up' | 'down'
show: boolean
}>`
${({ theme, direction, show }) => css`
:before {
position: relative;
content: '';
display: block;
width: 1rem;
height: 1rem;
border-right: 2px solid ${theme.colors.secondary.medium};
border-top: 2px solid ${theme.colors.secondary.medium};
visibility: ${!show ? 'hidden' : 'visible'};
${figureArrowModifiers[direction]}
top: ${direction === 'up' ? '-0.2rem' : 'unset'};
}
`}
`
Loading

0 comments on commit 42b1325

Please sign in to comment.