diff --git a/packages/components/assets/icons/cross-close.svg b/packages/components/assets/icons/cross-close.svg
index f519fe135c..ae5eca3f4b 100644
--- a/packages/components/assets/icons/cross-close.svg
+++ b/packages/components/assets/icons/cross-close.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/packages/components/assets/icons/eye.svg b/packages/components/assets/icons/eye.svg
index 6d0729f94e..7b3dd19111 100644
--- a/packages/components/assets/icons/eye.svg
+++ b/packages/components/assets/icons/eye.svg
@@ -1,14 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/packages/components/src/FieldInput/FieldInput.tsx b/packages/components/src/FieldInput/FieldInput.tsx
index 2cd156ded2..3602afc967 100644
--- a/packages/components/src/FieldInput/FieldInput.tsx
+++ b/packages/components/src/FieldInput/FieldInput.tsx
@@ -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'
@@ -14,6 +14,7 @@ export interface Props extends FieldProps {
showCharacterCount?: boolean
'data-cy'?: string
customOnBlur?: (event: any) => void
+ endAdornment?: any
}
type InputModifiers = {
@@ -44,37 +45,62 @@ export const FieldInput = ({
showCharacterCount,
minLength,
maxLength,
+ endAdornment,
...rest
}: Props) => {
const [curLength, setLength] = useState(input?.value?.length ?? 0)
+ const InputElement = (
+ {
+ 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 (
{meta.error && meta.touched && (
{meta.error}
)}
- {
- 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 ? (
+
+ {InputElement}
+
+ {endAdornment}
+
+
+ ) : (
+ InputElement
+ )}
{showCharacterCount && maxLength && (
,
volunteer: ,
website: ,
+ search: ,
}
diff --git a/packages/components/src/Icon/types.ts b/packages/components/src/Icon/types.ts
index eb7e8ef070..bbef14adfc 100644
--- a/packages/components/src/Icon/types.ts
+++ b/packages/components/src/Icon/types.ts
@@ -68,5 +68,6 @@ export type availableGlyphs =
| 'view'
| 'volunteer'
| 'website'
+ | 'search'
export type IGlyphs = { [k in availableGlyphs]: JSX.Element }
diff --git a/packages/components/src/OsmGeocoding/OsmGeocoding.tsx b/packages/components/src/OsmGeocoding/OsmGeocoding.tsx
index 7fc285106b..949415b241 100644
--- a/packages/components/src/OsmGeocoding/OsmGeocoding.tsx
+++ b/packages/components/src/OsmGeocoding/OsmGeocoding.tsx
@@ -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'
@@ -94,16 +94,26 @@ export const OsmGeocoding = ({
ref={mainContainerRef}
style={{ width: '100%' }}
>
- {
+ setQueryLocationService(true)
+ setSearchValue(value)
+ }}
+ onClickDelete={() => {
+ setSearchValue('')
+ setQueryLocationService(false)
+ }}
+ onClickSearch={() => {
+ setQueryLocationService(true)
+ setSearchValue(searchValue)
+ }}
+ additionalStyle={{
background: 'white',
fontFamily: 'Varela Round',
fontSize: '14px',
@@ -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 && }
{showResultsListing && (
diff --git a/packages/components/src/SearchField/SearchField.stories.tsx b/packages/components/src/SearchField/SearchField.stories.tsx
new file mode 100644
index 0000000000..98e1270b2b
--- /dev/null
+++ b/packages/components/src/SearchField/SearchField.stories.tsx
@@ -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
+
+export const Default: StoryFn = () => {
+ const [searchValue, setSearchValue] = useState('')
+
+ return (
+ setSearchValue(value)}
+ onClickDelete={() => setSearchValue('')}
+ onClickSearch={() => {}}
+ />
+ )
+}
diff --git a/packages/components/src/SearchField/SearchField.tsx b/packages/components/src/SearchField/SearchField.tsx
new file mode 100644
index 0000000000..49e7101f21
--- /dev/null
+++ b/packages/components/src/SearchField/SearchField.tsx
@@ -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 (
+
+ onChange(e.target.value)}
+ sx={{
+ paddingRight: 11,
+ '::-webkit-search-cancel-button': {
+ display: 'none',
+ },
+ '::-ms-clear': {
+ display: 'none',
+ },
+ ...additionalStyle,
+ }}
+ />
+
+ {value && (
+
+ )}
+
+
+
+ )
+}
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index daac4d9d34..989f8c3066 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -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'
@@ -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'
diff --git a/src/assets/icons/cross-close.svg b/src/assets/icons/cross-close.svg
index f519fe135c..ff2033b1bb 100644
--- a/src/assets/icons/cross-close.svg
+++ b/src/assets/icons/cross-close.svg
@@ -1 +1,4 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/assets/icons/icon-search.svg b/src/assets/icons/icon-search.svg
index dab0ec1793..4c8bd884a9 100644
--- a/src/assets/icons/icon-search.svg
+++ b/src/assets/icons/icon-search.svg
@@ -1 +1,10 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/common/Form/PasswordField.tsx b/src/common/Form/PasswordField.tsx
index 49339b9697..0a84d787a6 100644
--- a/src/common/Form/PasswordField.tsx
+++ b/src/common/Form/PasswordField.tsx
@@ -1,34 +1,32 @@
import { useState } from 'react'
import { Field } from 'react-final-form'
import { Icon } from 'oa-components'
-import { Box } from 'theme-ui'
export const PasswordField = ({ name, component, ...rest }) => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false)
return (
-
-
-
+ setIsPasswordVisible(!isPasswordVisible)}
size="25"
- >
-
-
+ />
+ }
+ required
+ />
)
}
diff --git a/src/pages/Howto/Content/HowtoList/HowtoFilterHeader.tsx b/src/pages/Howto/Content/HowtoList/HowtoFilterHeader.tsx
index 12e85d1738..cd63ec0e4b 100644
--- a/src/pages/Howto/Content/HowtoList/HowtoFilterHeader.tsx
+++ b/src/pages/Howto/Content/HowtoList/HowtoFilterHeader.tsx
@@ -1,9 +1,9 @@
import { useCallback, useEffect, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import debounce from 'debounce'
-import { Select } from 'oa-components'
+import { SearchField, Select } from 'oa-components'
import { FieldContainer } from 'src/common/Form/FieldContainer'
-import { Flex, Input } from 'theme-ui'
+import { Flex } from 'theme-ui'
import { CategoriesSelectV2 } from '../../../common/Category/CategoriesSelectV2'
import { howtoService, HowtosSearchParams } from '../../howto.service'
@@ -15,6 +15,7 @@ import type { HowtoSortOption } from './HowtoSortOptions'
export const HowtoFilterHeader = () => {
const [categories, setCategories] = useState([])
+ const [searchString, setSearchString] = useState('')
const [searchParams, setSearchParams] = useSearchParams()
const categoryParam = searchParams.get(HowtosSearchParams.category)
@@ -23,11 +24,17 @@ export const HowtoFilterHeader = () => {
const sort = searchParams.get(HowtosSearchParams.sort) as HowtoSortOption
const _inputStyle = {
- width: ['100%', '100%', '200px'],
+ width: ['100%', '100%', '230px'],
mr: [0, 0, 2],
mb: [3, 3, 0],
}
+ useEffect(() => {
+ if (q && q.length > 0) {
+ setSearchString(q)
+ }
+ }, [q])
+
useEffect(() => {
const initCategories = async () => {
const categories = (await howtoService.getHowtoCategories()) || []
@@ -57,22 +64,26 @@ export const HowtoFilterHeader = () => {
const onSearchInputChange = useCallback(
debounce((value: string) => {
- const params = new URLSearchParams(searchParams.toString())
- params.set(HowtosSearchParams.q, value)
-
- if (value.length > 0 && sort !== 'MostRelevant') {
- params.set(HowtosSearchParams.sort, 'MostRelevant')
- }
-
- if (value.length === 0 || !value) {
- params.set(HowtosSearchParams.sort, 'Newest')
- }
-
- setSearchParams(params)
+ searchValue(value)
}, 500),
[searchParams],
)
+ const searchValue = (value: string) => {
+ const params = new URLSearchParams(searchParams.toString())
+ params.set(HowtosSearchParams.q, value)
+
+ if (value.length > 0 && sort !== 'MostRelevant') {
+ params.set(HowtosSearchParams.sort, 'MostRelevant')
+ }
+
+ if (value.length === 0 || !value) {
+ params.set(HowtosSearchParams.sort, 'Newest')
+ }
+
+ setSearchParams(params)
+ }
+
return (
{
- onSearchInputChange(e.target.value)}
+ {
+ setSearchString(value)
+ onSearchInputChange(value)
+ }}
+ onClickDelete={() => {
+ setSearchString('')
+ searchValue('')
+ }}
+ onClickSearch={() => searchValue(searchString)}
/>
diff --git a/src/pages/Question/QuestionFilterHeader.tsx b/src/pages/Question/QuestionFilterHeader.tsx
index 798326293c..7aa1ba23c7 100644
--- a/src/pages/Question/QuestionFilterHeader.tsx
+++ b/src/pages/Question/QuestionFilterHeader.tsx
@@ -1,13 +1,13 @@
import { useCallback, useEffect, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import debounce from 'debounce'
-import { Select } from 'oa-components'
+import { SearchField, Select } from 'oa-components'
import { FieldContainer } from 'src/common/Form/FieldContainer'
import {
QuestionSearchParams,
questionService,
} from 'src/pages/Question/question.service'
-import { Flex, Input } from 'theme-ui'
+import { Flex } from 'theme-ui'
import { CategoriesSelectV2 } from '../common/Category/CategoriesSelectV2'
import { listing } from './labels'
@@ -18,6 +18,7 @@ import type { QuestionSortOption } from './QuestionSortOptions'
export const QuestionFilterHeader = () => {
const [categories, setCategories] = useState([])
+ const [searchString, setSearchString] = useState('')
const [searchParams, setSearchParams] = useSearchParams()
const categoryParam = searchParams.get(QuestionSearchParams.category)
@@ -26,7 +27,7 @@ export const QuestionFilterHeader = () => {
const sort = searchParams.get(QuestionSearchParams.sort) as QuestionSortOption
const _inputStyle = {
- width: ['100%', '100%', '200px'],
+ width: ['100%', '100%', '230px'],
mr: [0, 0, 2],
mb: [3, 3, 0],
}
@@ -59,22 +60,26 @@ export const QuestionFilterHeader = () => {
const onSearchInputChange = useCallback(
debounce((value: string) => {
- const params = new URLSearchParams(searchParams.toString())
- params.set('q', value)
-
- if (value.length > 0 && sort !== 'MostRelevant') {
- params.set('sort', 'MostRelevant')
- }
-
- if (value.length === 0 || !value) {
- params.set('sort', 'Newest')
- }
-
- setSearchParams(params)
+ searchValue(value)
}, 500),
[searchParams],
)
+ const searchValue = (value: string) => {
+ const params = new URLSearchParams(searchParams.toString())
+ params.set('q', value)
+
+ if (value.length > 0 && sort !== 'MostRelevant') {
+ params.set('sort', 'MostRelevant')
+ }
+
+ if (value.length === 0 || !value) {
+ params.set('sort', 'Newest')
+ }
+
+ setSearchParams(params)
+ }
+
return (
{
- onSearchInputChange(e.target.value)}
+ {
+ setSearchString(value)
+ onSearchInputChange(value)
+ }}
+ onClickDelete={() => {
+ setSearchString('')
+ searchValue('')
+ }}
+ onClickSearch={() => searchValue(searchString)}
/>
diff --git a/src/pages/Research/Content/ResearchFilterHeader.tsx b/src/pages/Research/Content/ResearchFilterHeader.tsx
index cb7ea7dcfd..44a98e3f37 100644
--- a/src/pages/Research/Content/ResearchFilterHeader.tsx
+++ b/src/pages/Research/Content/ResearchFilterHeader.tsx
@@ -1,10 +1,10 @@
import { useCallback, useEffect, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import debounce from 'debounce'
-import { Select } from 'oa-components'
+import { SearchField, Select } from 'oa-components'
import { ResearchStatus } from 'oa-shared'
import { FieldContainer } from 'src/common/Form/FieldContainer'
-import { Flex, Input } from 'theme-ui'
+import { Flex } from 'theme-ui'
import { CategoriesSelectV2 } from '../../common/Category/CategoriesSelectV2'
import { listing } from '../labels'
@@ -25,8 +25,9 @@ const researchStatusOptions = [
export const ResearchFilterHeader = () => {
const [categories, setCategories] = useState([])
- const [searchParams, setSearchParams] = useSearchParams()
+ const [searchString, setSearchString] = useState('')
+ const [searchParams, setSearchParams] = useSearchParams()
const categoryParam = searchParams.get(ResearchSearchParams.category)
const category = categories?.find((x) => x.value === categoryParam) ?? null
const q = searchParams.get(ResearchSearchParams.q)
@@ -36,7 +37,7 @@ export const ResearchFilterHeader = () => {
// TODO: create a library component for this
const _inputStyle = {
- width: ['100%', '100%', '200px'],
+ width: ['100%', '100%', '230px'],
mr: [0, 0, 2],
mb: [3, 3, 0],
}
@@ -69,22 +70,26 @@ export const ResearchFilterHeader = () => {
const onSearchInputChange = useCallback(
debounce((value: string) => {
- const params = new URLSearchParams(searchParams.toString())
- params.set(ResearchSearchParams.q, value)
-
- if (value.length > 0 && sort !== 'MostRelevant') {
- params.set(ResearchSearchParams.sort, 'MostRelevant')
- }
-
- if (value.length === 0 || !value) {
- params.set(ResearchSearchParams.sort, 'LatestUpdated')
- }
-
- setSearchParams(params)
+ searchValue(value)
}, 1000),
[searchParams],
)
+ const searchValue = (value: string) => {
+ const params = new URLSearchParams(searchParams.toString())
+ params.set(ResearchSearchParams.q, value)
+
+ if (value.length > 0 && sort !== 'MostRelevant') {
+ params.set(ResearchSearchParams.sort, 'MostRelevant')
+ }
+
+ if (value.length === 0 || !value) {
+ params.set(ResearchSearchParams.sort, 'LatestUpdated')
+ }
+
+ setSearchParams(params)
+ }
+
return (
{
{/* Text search */}
- onSearchInputChange(e.target.value)}
+ {
+ setSearchString(value)
+ onSearchInputChange(value)
+ }}
+ onClickDelete={() => {
+ setSearchString('')
+ searchValue('')
+ }}
+ onClickSearch={() => searchValue(searchString)}
/>