-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(inputsearch): add new input search component (#134)
* feat(inputsearch): add new input search component * feat(inputsearch): add index and stories * style(inputsearch): add intent states and user states * feat(inputsearch): add stories * test(inputsearch): add tests, onsubmit fuction todo * test(inputsearch): add tests, make input controllable, pass submit event on click and enter * chore(inputsearch): lint errors * refactor(menuselect): change input to sds InputSearch to standardize styling * fix(tabs): add delay to chromatic snapshot capture to prevent micro changes in chromatic * fix(tooltip): add delay to tooltip placement story to prevent baseline bugs
- Loading branch information
Showing
11 changed files
with
503 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`<InputSearch /> Test story renders snapshot 1`] = ` | ||
<label | ||
class="css-1m92ss4" | ||
for="test-round" | ||
> | ||
Round Search | ||
</label> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import { action } from "@storybook/addon-actions"; | ||
import { Args, Story } from "@storybook/react"; | ||
import React from "react"; | ||
import InputSearch from "./index"; | ||
|
||
const Demo = (props: Args): JSX.Element => { | ||
const { id, placeholder, label, disabled, sdsStyle, sdsStage, intent } = | ||
props; | ||
const handleSubmit = (value: string) => { | ||
console.log(value); | ||
}; | ||
return ( | ||
<InputSearch | ||
id={id} | ||
placeholder={placeholder} | ||
label={label} | ||
disabled={disabled} | ||
sdsStyle={sdsStyle} | ||
sdsStage={sdsStage} | ||
intent={intent} | ||
handleSubmit={handleSubmit} | ||
/> | ||
); | ||
}; | ||
|
||
export default { | ||
argTypes: { | ||
disabled: { | ||
control: { type: "boolean" }, | ||
}, | ||
id: { | ||
control: { type: "text" }, | ||
required: true, | ||
}, | ||
intent: { | ||
control: { type: "radio" }, | ||
options: ["default", "error", "warning"], | ||
}, | ||
label: { | ||
control: { type: "text" }, | ||
required: true, | ||
}, | ||
placeholder: { | ||
control: { type: "text" }, | ||
}, | ||
sdsStage: { | ||
control: { type: "radio" }, | ||
options: ["default", "userInput"], | ||
}, | ||
sdsStyle: { | ||
control: { type: "radio" }, | ||
options: ["rounded", "square"], | ||
}, | ||
}, | ||
component: Demo, | ||
title: "InputSearch", | ||
}; | ||
|
||
const Template: Story = (args) => <Demo {...args} />; | ||
|
||
export const Default = Template.bind({}); | ||
|
||
Default.args = { | ||
disabled: false, | ||
id: "Test", | ||
label: "Search", | ||
placeholder: "Search", | ||
}; | ||
|
||
Default.parameters = { | ||
snapshot: { | ||
skip: true, | ||
}, | ||
}; | ||
|
||
const RoundLivePreviewDemo = (props: Args): JSX.Element => { | ||
return ( | ||
<InputSearch | ||
{...props} | ||
id="squareSearchPreview" | ||
label="Search" | ||
sdsStyle="rounded" | ||
placeholder="Search" | ||
handleSubmit={action("onSubmit")} | ||
/> | ||
); | ||
}; | ||
|
||
const RoundLivePreviewTemplate: Story = (args) => ( | ||
<RoundLivePreviewDemo {...args} /> | ||
); | ||
|
||
export const RoundLivePreview = RoundLivePreviewTemplate.bind({}); | ||
|
||
RoundLivePreview.parameters = { | ||
snapshot: { | ||
skip: true, | ||
}, | ||
}; | ||
|
||
const SquareLivePreviewDemo = (props: Args): JSX.Element => { | ||
return ( | ||
<InputSearch | ||
{...props} | ||
id="squareSearchPreview" | ||
label="Search" | ||
sdsStyle="square" | ||
placeholder="Search" | ||
handleSubmit={action("onSubmit")} | ||
/> | ||
); | ||
}; | ||
|
||
const SquareLivePreviewTemplate: Story = (args) => ( | ||
<SquareLivePreviewDemo {...args} /> | ||
); | ||
|
||
export const SquareLivePreview = SquareLivePreviewTemplate.bind({}); | ||
|
||
SquareLivePreview.parameters = { | ||
snapshot: { | ||
skip: true, | ||
}, | ||
}; | ||
|
||
const TestDemo = (props: Args): JSX.Element => { | ||
return ( | ||
<> | ||
<InputSearch | ||
id="test-round" | ||
sdsStyle="rounded" | ||
label="Round Search" | ||
placeholder="Search" | ||
data-testid="inputSearchRound" | ||
handleSubmit={action("onSubmit")} | ||
{...props} | ||
/> | ||
<InputSearch | ||
id="test-square" | ||
sdsStyle="square" | ||
label="Square Search" | ||
placeholder="Search" | ||
data-testid="inputSearchSquare" | ||
handleSubmit={action("onSubmit")} | ||
{...props} | ||
/> | ||
{/* @ts-expect-error testing fail state */} | ||
<InputSearch | ||
sdsStyle="square" | ||
placeholder="Search" | ||
data-testid="inputSearchFail" | ||
handleSubmit={action("onSubmit")} | ||
/> | ||
</> | ||
); | ||
}; | ||
|
||
const TestTemplate: Story = (args) => <TestDemo {...args} />; | ||
|
||
export const Test = TestTemplate.bind({}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { generateSnapshots } from "@chanzuckerberg/story-utils"; | ||
import { composeStory } from "@storybook/testing-react"; | ||
import { fireEvent, render, screen } from "@testing-library/react"; | ||
import React from "react"; | ||
import * as snapshotTestStoryFile from "./index.stories"; | ||
import Meta, { Test as TestStory } from "./index.stories"; | ||
|
||
// Returns a component that already contain all decorators from story level, meta level and global level. | ||
const Test = composeStory(TestStory, Meta); | ||
|
||
describe("<InputSearch />", () => { | ||
generateSnapshots(snapshotTestStoryFile); | ||
|
||
it("renders inputSearch component", () => { | ||
render(<Test {...Test.args} />); | ||
const inputSearchRoundElement = screen.getByTestId("inputSearchRound"); | ||
expect(inputSearchRoundElement).not.toBeNull(); | ||
const inputSearchSquareElement = screen.getByTestId("inputSearchSquare"); | ||
expect(inputSearchSquareElement).not.toBeNull(); | ||
}); | ||
|
||
it("component fails if missing id or label prop", () => { | ||
render(<Test />); | ||
const inputTextElement = screen.queryByTestId("inputSearchFail"); | ||
expect(inputTextElement).toBeNull(); | ||
}); | ||
|
||
it("no label is rendered, but component is still accessible", () => { | ||
render(<Test />); | ||
const inputSearchElement = screen.getByLabelText("Round Search"); | ||
expect(inputSearchElement).not.toBeNull(); | ||
}); | ||
|
||
it("input value updates on change", () => { | ||
render(<Test />); | ||
const input = screen.getByLabelText("Round Search") as HTMLInputElement; | ||
fireEvent.change(input, { target: { value: "apple" } }); | ||
expect(input.value).toBe("apple"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { | ||
InputAdornment, | ||
TextFieldProps as RawTextFieldSearchProps, | ||
} from "@material-ui/core"; | ||
import React, { forwardRef, useState } from "react"; | ||
import Icon from "../Icon"; | ||
import IconButton from "../IconButton"; | ||
import { ExtraProps, StyledLabel, StyledSearchBase } from "./style"; | ||
|
||
export interface AccessibleInputSearchProps { | ||
label: string; | ||
placeholder?: string; | ||
id: string; | ||
handleSubmit?: (value: string) => void; | ||
} | ||
|
||
export type InputSearchProps = RawTextFieldSearchProps & | ||
AccessibleInputSearchProps & | ||
ExtraProps; | ||
|
||
const InputSearch = forwardRef<HTMLInputElement, InputSearchProps>( | ||
function InputSearch(props, ref): JSX.Element { | ||
const { | ||
label, | ||
id, | ||
placeholder, | ||
sdsStyle = "square", | ||
intent = "default", | ||
handleSubmit, | ||
...rest | ||
} = props; | ||
|
||
const [hasValue, setHasValue] = useState<boolean>(false); | ||
const [value, setValue] = useState<string>(""); | ||
|
||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
if (event.target.value) { | ||
setHasValue(true); | ||
} else { | ||
setHasValue(false); | ||
} | ||
setValue(event.target.value); | ||
}; | ||
|
||
const localHandleSubmit = () => { | ||
if (handleSubmit) handleSubmit(value); | ||
}; | ||
|
||
const handleKeyPress = (event: React.KeyboardEvent) => { | ||
if (event.key === "Enter") { | ||
event.preventDefault(); | ||
if (handleSubmit) handleSubmit(value); | ||
} | ||
}; | ||
|
||
if (!id || !label) { | ||
// eslint-disable-next-line no-console | ||
console.error( | ||
`Error: czifui component InputText requires id and label props for accessibility.` | ||
); | ||
return <></>; | ||
} | ||
|
||
const inputProps = { | ||
"aria-label": `${label}`, | ||
role: "search", | ||
}; | ||
|
||
return ( | ||
<> | ||
<StyledLabel htmlFor={id}>{label}</StyledLabel> | ||
<StyledSearchBase | ||
ref={ref} | ||
// passed to mui Input | ||
InputProps={{ | ||
endAdornment: ( | ||
<InputAdornment position="end"> | ||
<IconButton | ||
onClick={localHandleSubmit} | ||
data-testId="searchButton" | ||
sdsType="secondary" | ||
> | ||
<Icon sdsIcon="search" sdsSize="s" sdsType="interactive" /> | ||
</IconButton> | ||
</InputAdornment> | ||
), | ||
// passed to html input | ||
inputProps: inputProps, | ||
}} | ||
type="text" | ||
id={id} | ||
variant="outlined" | ||
size="small" | ||
placeholder={placeholder} | ||
value={value} | ||
sdsStyle={sdsStyle} | ||
sdsStage={hasValue ? "userInput" : "default"} | ||
onChange={handleChange} | ||
onKeyPress={handleKeyPress} | ||
intent={intent} | ||
{...rest} | ||
/> | ||
</> | ||
); | ||
} | ||
); | ||
|
||
export default InputSearch; |
Oops, something went wrong.