Add link with choices component
This commit is contained in:
parent
92d16effb3
commit
ccc51b1243
3 changed files with 185 additions and 0 deletions
26
src/components/LinkChoice/LinkChoice.stories.tsx
Normal file
26
src/components/LinkChoice/LinkChoice.stories.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import { countries } from "@saleor/fixtures";
|
||||
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import Form from "../Form";
|
||||
import LinkChoice, { LinkChoiceProps } from "./LinkChoice";
|
||||
|
||||
const suggestions = countries.map(c => ({ label: c.name, value: c.code }));
|
||||
|
||||
const props: Omit<LinkChoiceProps, "value" | "onChange"> = {
|
||||
choices: suggestions.slice(0, 10),
|
||||
name: "country"
|
||||
};
|
||||
|
||||
storiesOf("Generics / Link with choices", module)
|
||||
.addDecorator(CardDecorator)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => (
|
||||
<Form initial={{ country: suggestions[1].value }}>
|
||||
{({ change, data }) => (
|
||||
<LinkChoice {...props} value={data.country} onChange={change} />
|
||||
)}
|
||||
</Form>
|
||||
));
|
157
src/components/LinkChoice/LinkChoice.tsx
Normal file
157
src/components/LinkChoice/LinkChoice.tsx
Normal file
|
@ -0,0 +1,157 @@
|
|||
import React from "react";
|
||||
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
import Popper from "@material-ui/core/Popper";
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import classNames from "classnames";
|
||||
import { codes } from "keycode";
|
||||
|
||||
import ArrowDropdown from "@saleor/icons/ArrowDropdown";
|
||||
import { FormChange } from "@saleor/hooks/useForm";
|
||||
import { SingleAutocompleteChoiceType } from "../SingleAutocompleteSelectField";
|
||||
import Link from "../Link";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
arrow: {
|
||||
position: "relative",
|
||||
top: 6,
|
||||
transition: theme.transitions.duration.short + "ms"
|
||||
},
|
||||
highlighted: {
|
||||
background: theme.palette.background.default
|
||||
},
|
||||
menuItem: {
|
||||
"&:not(:last-of-type)": {
|
||||
marginBottom: theme.spacing()
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing()
|
||||
},
|
||||
popper: {
|
||||
boxShadow: `0px 5px 10px 0 ${fade(theme.palette.common.black, 0.05)}`,
|
||||
marginTop: theme.spacing(1),
|
||||
zIndex: 2
|
||||
},
|
||||
root: {
|
||||
"&:focus": {
|
||||
textDecoration: "underline"
|
||||
},
|
||||
outline: 0,
|
||||
position: "relative"
|
||||
},
|
||||
rotate: {
|
||||
transform: "rotate(180deg)"
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: "LinkChoice"
|
||||
}
|
||||
);
|
||||
|
||||
export interface LinkChoiceProps {
|
||||
choices: SingleAutocompleteChoiceType[];
|
||||
name: "country";
|
||||
value: string;
|
||||
onChange: FormChange;
|
||||
}
|
||||
|
||||
const LinkChoice: React.FC<LinkChoiceProps> = ({
|
||||
choices,
|
||||
name,
|
||||
value,
|
||||
onChange
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const anchor = React.useRef<HTMLInputElement>(null);
|
||||
const current = choices.find(c => c.value === value);
|
||||
const [highlightedIndex, setHighlightedIndex] = React.useState(0);
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
setOpen(false);
|
||||
onChange({
|
||||
target: {
|
||||
name,
|
||||
value
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyPress = (event: React.KeyboardEvent<HTMLSpanElement>) => {
|
||||
switch (event.keyCode) {
|
||||
case codes.down:
|
||||
setHighlightedIndex(
|
||||
highlightedIndex => (highlightedIndex + 1) % choices.length
|
||||
);
|
||||
break;
|
||||
case codes.up:
|
||||
setHighlightedIndex(highlightedIndex =>
|
||||
highlightedIndex === 0
|
||||
? choices.length - 1
|
||||
: (highlightedIndex - 1) % choices.length
|
||||
);
|
||||
break;
|
||||
case codes.enter:
|
||||
if (open) {
|
||||
handleChange(choices[highlightedIndex].value);
|
||||
} else {
|
||||
setOpen(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
className={classes.root}
|
||||
ref={anchor}
|
||||
onKeyDown={handleKeyPress}
|
||||
tabIndex={0}
|
||||
>
|
||||
<Link onClick={() => setOpen(open => !open)}>{current.label}</Link>
|
||||
<ArrowDropdown
|
||||
className={classNames(classes.arrow, {
|
||||
[classes.rotate]: open
|
||||
})}
|
||||
color="primary"
|
||||
/>
|
||||
|
||||
<Popper
|
||||
className={classes.popper}
|
||||
open={open}
|
||||
anchorEl={anchor.current}
|
||||
transition
|
||||
disablePortal
|
||||
placement="bottom-start"
|
||||
>
|
||||
<ClickAwayListener
|
||||
onClickAway={() => setOpen(false)}
|
||||
mouseEvent="onClick"
|
||||
>
|
||||
<Paper className={classes.paper}>
|
||||
{choices.map((choice, choiceIndex) => (
|
||||
<MenuItem
|
||||
className={classNames(classes.menuItem, {
|
||||
[classes.highlighted]: highlightedIndex === choiceIndex
|
||||
})}
|
||||
selected={choice.value === value}
|
||||
key={choice.value}
|
||||
onClick={() => handleChange(choice.value)}
|
||||
data-tc="select-option"
|
||||
>
|
||||
{choice.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Paper>
|
||||
</ClickAwayListener>
|
||||
</Popper>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
LinkChoice.displayName = "LinkChoice";
|
||||
export default LinkChoice;
|
2
src/components/LinkChoice/index.ts
Normal file
2
src/components/LinkChoice/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./LinkChoice";
|
||||
export * from "./LinkChoice";
|
Loading…
Reference in a new issue