Skip to content

Commit

Permalink
feat(component): searchfield component with icons and center eye icon…
Browse files Browse the repository at this point in the history
… in passwordfield
  • Loading branch information
Lahuen Garcia authored and benfurber committed Oct 2, 2024
1 parent 90b8efc commit 13680ce
Show file tree
Hide file tree
Showing 18 changed files with 398 additions and 195 deletions.
2 changes: 1 addition & 1 deletion packages/components/assets/icons/cross-close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 1 addition & 14 deletions packages/components/assets/icons/eye.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 49 additions & 23 deletions packages/components/src/FieldInput/FieldInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'react'
import { Flex, Input, Text } from 'theme-ui'
import { Box, Flex, Input, Text } from 'theme-ui'

import { CharacterCount } from '../CharacterCount/CharacterCount'

Expand All @@ -14,6 +14,7 @@ export interface Props extends FieldProps {
showCharacterCount?: boolean
'data-cy'?: string
customOnBlur?: (event: any) => void
endAdornment?: any
}

type InputModifiers = {
Expand Down Expand Up @@ -44,37 +45,62 @@ export const FieldInput = ({
showCharacterCount,
minLength,
maxLength,
endAdornment,
...rest
}: Props) => {
const [curLength, setLength] = useState<number>(input?.value?.length ?? 0)

const InputElement = (
<Input
disabled={disabled}
variant={meta?.error && meta?.touched ? 'textareaError' : 'textarea'}
{...input}
{...rest}
minLength={minLength}
maxLength={maxLength}
onBlur={(e) => {
if (modifiers) {
e.target.value = processInputModifiers(e.target.value, modifiers)
input.onChange(e)
}
if (customOnBlur) {
customOnBlur(e)
}
input.onBlur()
}}
onChange={(ev) => {
showCharacterCount && setLength(ev.target.value.length)
input.onChange(ev)
}}
/>
)

return (
<Flex sx={{ flexDirection: 'column', flex: 1, gap: 1 }}>
{meta.error && meta.touched && (
<Text sx={{ fontSize: 1, color: 'error' }}>{meta.error}</Text>
)}
<Input
disabled={disabled}
variant={meta?.error && meta?.touched ? 'textareaError' : 'textarea'}
{...input}
{...rest}
minLength={minLength}
maxLength={maxLength}
onBlur={(e) => {
if (modifiers) {
e.target.value = processInputModifiers(e.target.value, modifiers)
input.onChange(e)
}
if (customOnBlur) {
customOnBlur(e)
}
input.onBlur()
}}
onChange={(ev) => {
showCharacterCount && setLength(ev.target.value.length)
input.onChange(ev)
}}
/>
{endAdornment ? (
<Box
style={{
display: 'flex',
alignItems: 'center',
position: 'relative',
}}
>
{InputElement}
<Box
sx={{
position: 'absolute',
right: 2,
}}
>
{endAdornment}
</Box>
</Box>
) : (
InputElement
)}
{showCharacterCount && maxLength && (
<CharacterCount
currentSize={curLength}
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export const glyphs: IGlyphs = {
view: iconMap.view,
volunteer: iconMap.volunteer,
website: iconMap.website,
search: iconMap.search,
}

export type Props = IProps & VerticalAlignProps & SpaceProps
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/Icon/svgs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import bazarSVG from '../../assets/icons/icon-bazar.svg'
import commentSVG from '../../assets/icons/icon-comment.svg'
import discordSVG from '../../assets/icons/icon-discord.svg'
import emailOutlineSVG from '../../assets/icons/icon-email.svg'
import searchSVG from '../../assets/icons/icon-search.svg'
import socialMediaSVG from '../../assets/icons/icon-social-media.svg'
import starActiveSVG from '../../assets/icons/icon-star-active.svg'
import starSVG from '../../assets/icons/icon-star-default.svg'
Expand Down Expand Up @@ -89,4 +90,5 @@ export const iconMap = {
view: <ImageIcon src={viewSVG} />,
volunteer: <ImageIcon src={volunteerSVG} />,
website: <ImageIcon src={websiteSVG} />,
search: <ImageIcon src={searchSVG} />,
}
1 change: 1 addition & 0 deletions packages/components/src/Icon/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,6 @@ export type availableGlyphs =
| 'view'
| 'volunteer'
| 'website'
| 'search'

export type IGlyphs = { [k in availableGlyphs]: JSX.Element }
29 changes: 17 additions & 12 deletions packages/components/src/OsmGeocoding/OsmGeocoding.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from 'react'
import { Input } from 'theme-ui'
import { useDebouncedCallback } from 'use-debounce'

import { SearchField } from '../SearchField/SearchField'
import { OsmGeocodingLoader } from './OsmGeocodingLoader'
import { OsmGeocodingResultsList } from './OsmGeocodingResultsList'

Expand Down Expand Up @@ -94,16 +94,26 @@ export const OsmGeocoding = ({
ref={mainContainerRef}
style={{ width: '100%' }}
>
<Input
<SearchField
autoComplete="off"
type="search"
name="geocoding"
id="geocoding"
data-cy="osm-geocoding-input"
placeholder={placeholder}
dataCy="osm-geocoding-input"
placeHolder={placeholder}
value={searchValue}
style={{
width: '100%',
onChange={(value: string) => {
setQueryLocationService(true)
setSearchValue(value)
}}
onClickDelete={() => {
setSearchValue('')
setQueryLocationService(false)
}}
onClickSearch={() => {
setQueryLocationService(true)
setSearchValue(searchValue)
}}
additionalStyle={{
background: 'white',
fontFamily: 'Varela Round',
fontSize: '14px',
Expand All @@ -114,11 +124,6 @@ export const OsmGeocoding = ({
showResultsListing || showLoader ? '5px 5px 0 0' : '5px',
marginBottom: 0,
}}
onClick={() => setShowResults(true)}
onChange={(event) => {
setQueryLocationService(true)
setSearchValue(event.target.value)
}}
/>
{showLoader && <OsmGeocodingLoader />}
{showResultsListing && (
Expand Down
25 changes: 25 additions & 0 deletions packages/components/src/SearchField/SearchField.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useState } from 'react'

import { SearchField } from './SearchField'

import type { Meta, StoryFn } from '@storybook/react'

export default {
title: 'Forms/SearchField',
component: SearchField,
} as Meta<typeof SearchField>

export const Default: StoryFn<typeof SearchField> = () => {
const [searchValue, setSearchValue] = useState<string>('')

return (
<SearchField
dataCy="default-search-box"
placeHolder="Default search"
value={searchValue}
onChange={(value: string) => setSearchValue(value)}
onClickDelete={() => setSearchValue('')}
onClickSearch={() => {}}
/>
)
}
96 changes: 96 additions & 0 deletions packages/components/src/SearchField/SearchField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Box, Input } from 'theme-ui'

import { Icon } from '../Icon/Icon'

import type { ThemeUIStyleObject } from 'theme-ui'

export type Props = {
autoComplete?: string
name?: string
id?: string
dataCy: string
placeHolder: string
value: string
onChange: (value: string) => void
onClickDelete: () => void
onClickSearch: () => void
additionalStyle?: ThemeUIStyleObject
}

export const SearchField = (props: Props) => {
const {
autoComplete = 'on',
name = 'rand-name',
id = 'rand-id',
dataCy,
placeHolder,
value,
onChange,
onClickDelete,
onClickSearch,
additionalStyle = {},
} = props

return (
<Box
sx={{
position: 'relative',
width: '100%',
display: 'flex',
alignItems: 'center',
}}
>
<Input
autoComplete={autoComplete}
name={name}
id={id}
variant="inputOutline"
type="search"
data-cy={dataCy}
placeholder={placeHolder}
value={value}
onChange={(e) => onChange(e.target.value)}
sx={{
paddingRight: 11,
'::-webkit-search-cancel-button': {
display: 'none',
},
'::-ms-clear': {
display: 'none',
},
...additionalStyle,
}}
/>
<Box
sx={{
right: 2,
position: 'absolute',
display: 'flex',
alignItems: 'center',
}}
>
{value && (
<Icon
sx={{
display: 'flex',
alignItems: 'center',
marginRight: 1,
}}
glyph="close"
onClick={onClickDelete}
size="17"
/>
)}
<Icon
sx={{
display: 'flex',
alignItems: 'center',
}}
glyph="search"
onClick={onClickSearch}
size="19"
/>
</Box>
</Box>
)
}
3 changes: 1 addition & 2 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export { OsmGeocoding } from './OsmGeocoding/OsmGeocoding'
export { PinProfile } from './PinProfile/PinProfile'
export { ProfileLink } from './ProfileLink/ProfileLink'
export { ResearchEditorOverview } from './ResearchEditorOverview/ResearchEditorOverview'
export { SearchField } from './SearchField/SearchField'
export { Select } from './Select/Select'
export { SettingsFormWrapper } from './SettingsFormWrapper/SettingsFormWrapper'
export { SiteFooter } from './SiteFooter/SiteFooter'
Expand All @@ -69,7 +70,5 @@ export { UserEngagementWrapper } from './UserEngagementWrapper/UserEngagementWra
export { Username } from './Username/Username'
export { UserStatistics } from './UserStatistics/UserStatistics'
export { VideoPlayer } from './VideoPlayer/VideoPlayer'

// export { IImageGalleryItem } from './ImageGallery/ImageGallery'
export type { availableGlyphs } from './Icon/types'
export type { ITab } from './SettingsFormWrapper/SettingsFormTab'
15 changes: 9 additions & 6 deletions packages/cypress/src/integration/map.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ describe('[Map]', () => {
cy.step('New map shows the cards')
cy.get('[data-cy="welome-header"]').should('be.visible')
cy.get('[data-cy="CardList-desktop"]').should('be.visible')
// Should be 'x results in view' - reduction in coverage until temp API removed
cy.get('[data-cy="list-results"]').contains('results in view')
cy.get('[data-cy="list-results"]').contains('52 results in view')

cy.step('Map filters can be used')
cy.get('[data-cy=FilterList]')
Expand All @@ -39,8 +38,7 @@ describe('[Map]', () => {
// Reduction in coverage until temp API removed
// cy.get('[data-cy="list-results"]').contains('6 results in view')
cy.get('[data-cy=MapListFilter-active]').first().click()
// Reduction in coverage until temp API removed
// cy.get('[data-cy="list-results"]').contains('52 results in view')
cy.get('[data-cy="list-results"]').contains('52 results in view')

cy.step('As the user moves in the list updates')
for (let i = 0; i < 9; i++) {
Expand All @@ -66,14 +64,17 @@ describe('[Map]', () => {
cy.url().should('include', `#${userId}`)

cy.step('New map pins can be hidden with the cross button')
cy.get('[data-cy=PinProfile]').should('be.visible')
cy.get('[data-cy=PinProfileCloseButton]').click()
cy.get('[data-cy=PinProfile]').should('not.exist')
cy.url().should('not.include', `#${userId}`)
cy.get('[data-cy=PinProfile]').should('not.exist')

cy.step('New map pins can be hidden by clicking the map')
cy.get(`[data-cy=pin-${userId}]`).click()
cy.url().should('include', `#${userId}`)
cy.get('[data-cy=PinProfile]').should('be.visible')
cy.get('.markercluster-map').click(0, 0)
cy.get('.markercluster-map').click(10, 10)
cy.url().should('not.include', `#${userId}`)
cy.get('[data-cy=PinProfile]').should('not.exist')

cy.step('Mobile list view can be shown')
Expand Down Expand Up @@ -101,8 +102,10 @@ describe('[Map]', () => {
cy.step('The whole map can be searched')
cy.get('[data-cy="ShowMobileListButton"]').click()
cy.get('[data-cy=osm-geocoding]').last().click().type('london')
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000) // Needed for location response
cy.contains('London, Greater London, England, United Kingdom').click()
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000) // Needed for animation
cy.get('.icon-cluster-text').contains('3')
})
Expand Down
Loading

0 comments on commit 13680ce

Please sign in to comment.