Skip to content

Commit

Permalink
feat: Make search component extendable (#2230)
Browse files Browse the repository at this point in the history
* add base SearchField component

* update SearchField to have correct props, structure

* incorporated SearchField into Search component

* create base storybook story for SearchField component

* add unit test for SearchField

* moved SearchField into new shared Search directory

* remove inline Label addition in Search component

* created SearchButton component

* incorporate SearchButton into Search component

* keep track of button size in SearchButton component

* add stories for SearchButton

* add test for SearchButton

* pass size fields and styling into SearchField and SearchButton components

* Add test for passing usa-search--big class when isBig is set on SearchField

* Add addtional test to Search

* update import for Search in index.ts

* fix additional import errors for Search

* update import for Search in Header story

* add test for passing input props to searchField component

* revert changes to inputref

* Add @pearl-truss as a contributor

* push with updated ssh key
  • Loading branch information
pearl-truss authored Jan 26, 2023
1 parent 2905d34 commit 9bd9137
Show file tree
Hide file tree
Showing 16 changed files with 322 additions and 49 deletions.
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,15 @@
"contributions": [
"code"
]
},
{
"login": "pearl-truss",
"name": "pearl-truss",
"avatar_url": "https://avatars.githubusercontent.com/u/67110378?v=4",
"profile": "https://github.com/pearl-truss",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# @trussworks/react-uswds

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-23-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->

[![npm version](https://img.shields.io/npm/v/@trussworks/react-uswds)](https://www.npmjs.com/package/@trussworks/react-uswds)
Expand Down Expand Up @@ -156,6 +156,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jcbcapps"><img src="https://avatars.githubusercontent.com/u/99674188?v=4?s=100" width="100px;" alt="Jacob Capps"/><br /><sub><b>Jacob Capps</b></sub></a><br /><a href="https://github.com/trussworks/react-uswds/commits?author=jcbcapps" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pearl-truss"><img src="https://avatars.githubusercontent.com/u/67110378?v=4?s=100" width="100px;" alt="pearl-truss"/><br /><sub><b>pearl-truss</b></sub></a><br /><a href="https://github.com/trussworks/react-uswds/commits?author=pearl-truss" title="Code">💻</a></td>
</tr>
</tbody>
</table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import { Search } from './Search'

export default {
title: 'Components/Search',
title: 'Components/Search/Search',
component: Search,
parameters: {
docs: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,16 @@ describe('Search component', () => {
expect(queryByRole('button')).not.toHaveTextContent('Search')
})

describe('renders size classes', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it.each([
['big', 'usa-search--big'],
['small', 'usa-search--small'],
])('when size is %s should include class %s', (sizeString, uswdsClass) => {
const size = sizeString as 'big' | 'small'
const mockSubmit = jest.fn()
const { container } = render(<Search onSubmit={mockSubmit} size={size} />)
expect(container.querySelector('form')).toHaveClass(uswdsClass)
})
it('adds small class when size prop is small', () => {
const mockSubmit = jest.fn()
const { container } = render(<Search onSubmit={mockSubmit} size="small" />)
expect(container.querySelector('div.usa-search--small button')).toBeInTheDocument()
})

it('adds big class when size prop is big', () => {
const mockSubmit = jest.fn()
const { container } = render(<Search onSubmit={mockSubmit} size="big" />)
expect(container.querySelector('div.usa-search--big button')).toBeInTheDocument()
expect(container.querySelector('div.usa-search--big input')).toBeInTheDocument()
})
})
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React from 'react'
import classnames from 'classnames'

import searchImg from '@uswds/uswds/src/img/usa-icons-bg/search--white.svg'

import { Button } from '../Button/Button'
import { Form, OptionalFormProps } from '../forms/Form/Form'
import { Label } from '../forms/Label/Label'
import { TextInput } from '../forms/TextInput/TextInput'
import { Form, OptionalFormProps } from '../../forms/Form/Form'
import { SearchField } from '../SearchField/SearchField'
import { SearchButton } from '../SearchButton/SearchButton'
import { OptionalTextInputProps } from '../../forms/TextInput/TextInput'

type SearchLocalization = {
buttonText: string
Expand All @@ -21,6 +19,7 @@ type SearchInputProps = {
placeholder?: string
label?: React.ReactNode
i18n?: SearchLocalization
inputProps?: OptionalTextInputProps
}

export const Search = ({
Expand All @@ -32,18 +31,12 @@ export const Search = ({
label = 'Search',
inputId = 'search-field',
i18n,
inputProps,
...formProps
}: SearchInputProps & OptionalFormProps): React.ReactElement => {
const buttonText = i18n?.buttonText || 'Search'

const isBig = size === 'big'
const isSmall = size === 'small'
const classes = classnames(
'usa-search',
{
'usa-search--small': isSmall,
'usa-search--big': isBig,
},
className
)

Expand All @@ -54,26 +47,16 @@ export const Search = ({
role="search"
search={true}
{...formProps}>
<Label srOnly={true} htmlFor={inputId}>
{label}
</Label>
<TextInput
id={inputId}
type="search"
name={inputName}
<SearchField
{...inputProps}
isBig={size == 'big'}
inputId={inputId}
placeholder={placeholder}
name={inputName}
label={label}
defaultValue={formProps.defaultValue}
/>
<Button type="submit">
{!isSmall && (
<span className="usa-search__submit-text">{buttonText}</span>
)}
<img
src={searchImg}
className="usa-search__submit-icon"
alt={buttonText}
/>
</Button>
<SearchButton size={size} i18n={i18n} />
</Form>
)
}
Expand Down
46 changes: 46 additions & 0 deletions src/components/Search/SearchButton/SearchButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import { SearchButton } from './SearchButton'

export default {
title: 'Components/Search/SearchButton',
component: SearchButton,
parameters: {
docs: {
description: {
component: `
### USWDS 2.0 Search component
Source: https://designsystem.digital.gov/components/search/
`,
},
},
},
}

const sampleLocalization = {
buttonText: 'Buscar',
}

export const defaultSearchButton = (): React.ReactElement => (
<SearchButton />
)

export const bigSearchButton = (): React.ReactElement => (
<SearchButton size="big" />
)

export const smallSearch = (): React.ReactElement => (
<SearchButton size="small" />
)

export const defaultSpanishSearchButton = (): React.ReactElement => (
<SearchButton i18n={sampleLocalization} />
)

export const bigSpanishSearchButton = (): React.ReactElement => (
<SearchButton size="big" i18n={sampleLocalization} />
)

export const smallSpanishSearch = (): React.ReactElement => (
<SearchButton size="small" i18n={sampleLocalization} />
)
46 changes: 46 additions & 0 deletions src/components/Search/SearchButton/SearchButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import { render } from '@testing-library/react'
import { SearchButton } from './SearchButton'

const sampleLocalization = {
buttonText: 'Buscar',
}

describe('SearchButton component', () => {
it('renders without errors', () => {
const { queryByRole } = render(
<SearchButton />
)
expect(queryByRole('button')).toHaveTextContent('Search')
})

it('does not render button text when small', () => {
const { queryByRole } = render(
<SearchButton size="small" />
)

expect(queryByRole('button')).not.toHaveTextContent('Search')
})

it('internationalization', () => {
const { queryByText } = render(
<SearchButton i18n={sampleLocalization} />
)

expect(queryByText('Buscar')).toBeInTheDocument()
})

describe('renders size classes', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it.each([
['big', 'usa-search--big'],
['small', 'usa-search--small'],
])('when size is %s should include class %s', (sizeString, uswdsClass) => {
const size = sizeString as 'big' | 'small'
const { container } = render(<SearchButton size={size} />)
expect(container.querySelector('div')).toHaveClass(uswdsClass)
})
})
})
50 changes: 50 additions & 0 deletions src/components/Search/SearchButton/SearchButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react'
import classnames from 'classnames'

import searchImg from '@uswds/uswds/src/img/usa-icons-bg/search--white.svg'

import { Button } from '../../Button/Button'

type SearchLocalization = {
buttonText: string
}

type SearchButtonProps = {
size?: 'big' | 'small'
className?: string
i18n?: SearchLocalization
}

export const SearchButton = ({
size,
className,
i18n
}: SearchButtonProps): React.ReactElement => {
const buttonText = i18n?.buttonText || 'Search'
const isSmall = size === 'small'
const isBig = size === 'big'

const classes = classnames(
{
'usa-search--small': isSmall,
'usa-search--big': isBig,
},
className
)
return (
<div className={classes}>
<Button type="submit">
{!isSmall && (
<span className="usa-search__submit-text">{buttonText}</span>
)}
<img
src={searchImg}
className="usa-search__submit-icon"
alt={buttonText}
/>
</Button>
</div>
)
}

export default SearchButton
27 changes: 27 additions & 0 deletions src/components/Search/SearchField/SearchField.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react'
import { SearchField } from './SearchField'

export default {
title: 'Components/Search/SearchField',
component: SearchField,
parameters: {
docs: {
description: {
component: `
### USWDS 2.0 Search Field component
Source: https://designsystem.digital.gov/components/search/
`,
},
},
},
}


export const defaultSearchField = (): React.ReactElement => (
<SearchField placeholder='Search...' />
)

export const bigSearchField = (): React.ReactElement => (
<SearchField placeholder='Type something here...' isBig />
)
58 changes: 58 additions & 0 deletions src/components/Search/SearchField/SearchField.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react'
import { render } from '@testing-library/react'
import { SearchField } from './SearchField'

describe('SearchField component', () => {
it('renders without errors', () => {
const { queryByTestId } = render(
<SearchField />
)
expect(queryByTestId('textInput')).toBeInTheDocument()
})

it('renders a placeholder', () => {
const placeholder = 'SearchFieldhere'
const { queryByTestId } = render(
<SearchField placeholder={placeholder} />
)
expect(queryByTestId('textInput')).toHaveAttribute(
'placeholder',
placeholder
)
})

it('renders a default value', () => {
const defaultValue = 'SearchFieldhere'
const { queryByTestId } = render(
<SearchField defaultValue={defaultValue} />
)
expect(queryByTestId('textInput')).toHaveAttribute(
'value',
defaultValue
)
})

it('passes input props', () => {
const { getByTestId } = render(
<SearchField inputProps={{ required: true, minLength: 6 }} />
)
const input = getByTestId('textInput')

expect(input).toHaveAttribute('required')
expect(input).toHaveAttribute('minLength', "6")
})

it('renders a label', () => {
const { queryByLabelText } = render(
<SearchField label="Buscar" />
)

expect(queryByLabelText('Buscar')).toBeInTheDocument()
})

it('adds big class when isBig is true', () => {
const uswdsClass = 'usa-search--big'
const { container } = render(<SearchField isBig />)
expect(container.querySelector('div')).toHaveClass(uswdsClass)
})
})
Loading

0 comments on commit 9bd9137

Please sign in to comment.