-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(clickable): add clickable component
This adds a clickable component which adds click specific attributes to a child element and is using a11y best practices. Close DCOS-22331
- Loading branch information
Poltergeist
committed
May 4, 2018
1 parent
b6a21ba
commit a5ddbf3
Showing
6 changed files
with
171 additions
and
0 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,3 @@ | ||
# Clickable Name | ||
|
||
Clickable documentation |
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,50 @@ | ||
import * as React from "react"; | ||
|
||
import { cx } from "emotion"; | ||
import { outline } from "../style"; | ||
|
||
export interface IClickableProps { | ||
/** | ||
* Children should be a HTML element. | ||
*/ | ||
children: React.ReactElement<HTMLElement>; | ||
/** | ||
* Action is a event handler for the onClick and onKeypress events | ||
*/ | ||
action: (event?: React.SyntheticEvent<HTMLElement>) => void; | ||
/** | ||
* The tabIndex is passed down and is the same as the native tabIndex | ||
*/ | ||
tabIndex?: number | string; | ||
} | ||
|
||
class Clickable extends React.PureComponent<IClickableProps, {}> { | ||
public static defaultProps: Partial<IClickableProps> = { | ||
tabIndex: -1 | ||
}; | ||
|
||
constructor(props: IClickableProps) { | ||
super(props); | ||
this.handleKeyPress = this.handleKeyPress.bind(this); | ||
} | ||
|
||
public render() { | ||
const { children, action, tabIndex } = this.props; | ||
const { className = "" } = children.props; | ||
|
||
return React.cloneElement(React.Children.only(children), { | ||
onClick: action, | ||
className: cx(className, outline), | ||
tabIndex, | ||
onKeyPress: this.handleKeyPress | ||
}); | ||
} | ||
|
||
public handleKeyPress(event: React.KeyboardEvent<HTMLElement>): void { | ||
if (event.key === " " || event.key === "Enter") { | ||
this.props.action(event); | ||
} | ||
} | ||
} | ||
|
||
export default Clickable; |
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 @@ | ||
export { default as Clickable } from "./clickable"; |
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,15 @@ | ||
import React from "react"; | ||
import { storiesOf } from "@storybook/react"; | ||
import { withReadme } from "storybook-readme"; | ||
import Clickable from "../components/clickable"; | ||
import { action } from "@storybook/addon-actions"; | ||
|
||
const readme = require("../README.md"); | ||
|
||
storiesOf("Clickable", module) | ||
.addDecorator(withReadme([readme])) | ||
.addWithInfo("default", () => ( | ||
<Clickable action={action("action trigger")} tabIndex="0"> | ||
<span>Click me!</span> | ||
</Clickable> | ||
)); |
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 @@ | ||
import { css } from "emotion"; | ||
import { coreColors } from "../shared/styles/color"; | ||
|
||
const { white } = coreColors(); | ||
export const outline = css` | ||
&:focus { | ||
box-shadow: 0 0 0 1px ${white}, 0 0 0 3px currentColor; | ||
outline: 0; | ||
} | ||
`; |
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,92 @@ | ||
import React from "react"; | ||
|
||
import { shallow } from "enzyme"; | ||
import Clickable from "../components/clickable"; | ||
|
||
describe("Clickable", () => { | ||
it("has onClick function", () => { | ||
const action = jest.fn(); | ||
const wrapper = shallow( | ||
<Clickable action={action}> | ||
<span>onClick</span> | ||
</Clickable> | ||
); | ||
wrapper.simulate("click"); | ||
expect(action).toHaveBeenCalled(); | ||
}); | ||
|
||
it("has onKeyPress function and reacts on space", () => { | ||
const action = jest.fn(); | ||
const wrapper = shallow( | ||
<Clickable action={action}> | ||
<span>onKeyPress</span> | ||
</Clickable> | ||
); | ||
wrapper.simulate("keyPress", { | ||
key: " ", | ||
keyCode: 32, | ||
which: 32 | ||
}); | ||
expect(action).toHaveBeenCalled(); | ||
}); | ||
|
||
it("has onKeyPress function and reacts on Enter", () => { | ||
const action = jest.fn(); | ||
const wrapper = shallow( | ||
<Clickable action={action}> | ||
<span>onKeyPress</span> | ||
</Clickable> | ||
); | ||
wrapper.simulate("keyPress", { | ||
key: "Enter", | ||
keyCode: 13, | ||
which: 13 | ||
}); | ||
expect(action).toHaveBeenCalled(); | ||
}); | ||
it("does not react on e keypress", () => { | ||
const action = jest.fn(); | ||
const wrapper = shallow( | ||
<Clickable action={action}> | ||
<span>onKeyPress</span> | ||
</Clickable> | ||
); | ||
wrapper.simulate("keyPress", { | ||
key: "e", | ||
keyCode: 69, | ||
which: 69 | ||
}); | ||
expect(action).not.toHaveBeenCalled(); | ||
}); | ||
describe("tabIndex", () => { | ||
it("default value", () => { | ||
const action = jest.fn(); | ||
const wrapper = shallow( | ||
<Clickable action={action}> | ||
<span>default tabIndex</span> | ||
</Clickable> | ||
); | ||
expect( | ||
wrapper | ||
.find("span") | ||
.props() | ||
.tabIndex.toString() | ||
).toEqual("-1"); | ||
}); | ||
|
||
it("takes 10 as a value", () => { | ||
const action = jest.fn(); | ||
const wrapper = shallow( | ||
<Clickable action={action} tabIndex="10"> | ||
<span>default tabIndex</span> | ||
</Clickable> | ||
); | ||
expect( | ||
wrapper | ||
.find("span") | ||
.props() | ||
.tabIndex.toString() | ||
).toEqual("10"); | ||
}); | ||
}); | ||
}); |