Refactor ui a bit

This commit is contained in:
dominik-zeglen 2019-10-14 13:57:08 +02:00
parent 47dc872eeb
commit 0bf7594ce0
6 changed files with 497 additions and 156 deletions

View file

@ -2,28 +2,16 @@ import { storiesOf } from "@storybook/react";
import React from "react";
import Form from "@saleor/components/Form";
import { maybe } from "@saleor/misc";
import CardDecorator from "@saleor/storybook/CardDecorator";
import Decorator from "@saleor/storybook/Decorator";
import { ChoiceProvider } from "@saleor/storybook/mock";
import { countries } from "./fixtures";
import SingleAutocompleteSelectField, {
SingleAutocompleteSelectFieldProps
} from "@saleor/components/SingleAutocompleteSelectField";
import { maybe } from "@saleor/misc";
import CardDecorator from "../../CardDecorator";
import Decorator from "../../Decorator";
import { ChoiceProvider } from "../../mock";
} from "./SingleAutocompleteSelectField";
const suggestions = [
"Afghanistan",
"Burundi",
"Comoros",
"Egypt",
"Equatorial Guinea",
"Greenland",
"Isle of Man",
"Israel",
"Italy",
"United States",
"Wallis and Futuna",
"Zimbabwe"
].map(c => ({ label: c, value: c.toLocaleLowerCase().replace(/\s+/, "_") }));
const suggestions = countries.map(c => ({ label: c.name, value: c.code }));
const props: SingleAutocompleteSelectFieldProps = {
choices: undefined,

View file

@ -1,50 +1,26 @@
import { Omit } from "@material-ui/core";
import CircularProgress from "@material-ui/core/CircularProgress";
import { InputProps } from "@material-ui/core/Input";
import MenuItem from "@material-ui/core/MenuItem";
import Paper from "@material-ui/core/Paper";
import {
createStyles,
Theme,
withStyles,
WithStyles
} from "@material-ui/core/styles";
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import Downshift from "downshift";
import React from "react";
import { FormattedMessage } from "react-intl";
import { compareTwoStrings } from "string-similarity";
import SingleAutocompleteSelectFieldContent, {
SingleAutocompleteChoiceType
} from "./SingleAutocompleteSelectFieldContent";
import useStateFromProps from "@saleor/hooks/useStateFromProps";
import ArrowDropdownIcon from "../../icons/ArrowDropdown";
import Debounce, { DebounceProps } from "../Debounce";
const styles = (theme: Theme) =>
createStyles({
const styles = createStyles({
container: {
flexGrow: 1,
position: "relative"
},
menuItem: {
height: "auto",
whiteSpace: "normal"
},
paper: {
borderRadius: 4,
left: 0,
marginTop: theme.spacing.unit,
padding: 8,
position: "absolute",
right: 0,
zIndex: 22
}
});
export interface SingleAutocompleteChoiceType {
label: string;
value: any;
}
export interface SingleAutocompleteSelectFieldProps {
error?: boolean;
name: string;
@ -97,6 +73,7 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
...props
}: SingleAutocompleteSelectFieldProps & WithStyles<typeof styles>) => {
const [prevDisplayValue] = useStateFromProps(displayValue);
const handleChange = item =>
onChange({
target: {
@ -131,11 +108,20 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
choices && selectedItem
? choices.filter(c => c.value === selectedItem).length === 0
: false;
const hasInputValueChanged = prevDisplayValue !== displayValue;
if (prevDisplayValue !== displayValue) {
if (hasInputValueChanged) {
reset({ inputValue: displayValue });
}
const displayCustomValue =
inputValue.length > 0 &&
allowCustomValues &&
!choices.find(
choice =>
choice.label.toLowerCase() === inputValue.toLowerCase()
);
return (
<div className={classes.container} {...props}>
<TextField
@ -165,82 +151,16 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
fullWidth={true}
/>
{isOpen && (!!inputValue || !!choices.length) && (
<Paper className={classes.paper} square>
{choices.length > 0 || allowCustomValues ? (
<>
{emptyOption && (
<MenuItem
className={classes.menuItem}
component="div"
{...getItemProps({
item: ""
})}
data-tc="singleautocomplete-select-option"
>
<Typography color="textSecondary">
<FormattedMessage defaultMessage="None" />
</Typography>
</MenuItem>
)}
{choices.map((suggestion, index) => {
const choiceIndex = index + (emptyOption ? 1 : 0);
return (
<MenuItem
className={classes.menuItem}
key={JSON.stringify(suggestion)}
selected={
highlightedIndex === choiceIndex ||
selectedItem === suggestion.value
}
component="div"
{...getItemProps({
index: choiceIndex,
item: suggestion.value
})}
data-tc="singleautocomplete-select-option"
>
{suggestion.label}
</MenuItem>
);
})}
{allowCustomValues &&
!!inputValue &&
!choices.find(
choice =>
choice.label.toLowerCase() ===
inputValue.toLowerCase()
) && (
<MenuItem
className={classes.menuItem}
key={"customValue"}
selected={isCustomValueSelected}
component="div"
{...getItemProps({
item: inputValue
})}
data-tc="singleautocomplete-select-option"
>
<FormattedMessage
defaultMessage="Add new value: {value}"
description="add custom select input option"
values={{
value: inputValue
}}
<SingleAutocompleteSelectFieldContent
choices={choices}
displayCustomValue={displayCustomValue}
emptyOption={emptyOption}
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
inputValue={inputValue}
isCustomValueSelected={isCustomValueSelected}
selectedItem={selectedItem}
/>
</MenuItem>
)}
</>
) : (
<MenuItem
disabled={true}
component="div"
data-tc="singleautocomplete-select-no-options"
>
<FormattedMessage defaultMessage="No results found" />
</MenuItem>
)}
</Paper>
)}
</div>
);

View file

@ -0,0 +1,197 @@
import MenuItem from "@material-ui/core/MenuItem";
import Paper from "@material-ui/core/Paper";
import { Theme } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import { makeStyles } from "@material-ui/styles";
import classNames from "classnames";
import { GetItemPropsOptions } from "downshift";
import React from "react";
import { FormattedMessage } from "react-intl";
import useElementScroll from "@saleor/hooks/useElementScroll";
import Hr from "../Hr";
const menuItemHeight = 46;
const maxMenuItems = 5;
export interface SingleAutocompleteChoiceType {
label: string;
value: any;
}
interface SingleAutocompleteSelectFieldContentProps {
choices: SingleAutocompleteChoiceType[];
displayCustomValue: boolean;
emptyOption: boolean;
getItemProps: (options: GetItemPropsOptions) => void;
highlightedIndex: number;
inputValue: string;
isCustomValueSelected: boolean;
selectedItem: any;
}
const useStyles = makeStyles(
(theme: Theme) => ({
content: {
maxHeight: menuItemHeight * maxMenuItems + theme.spacing.unit * 2,
overflow: "scroll",
padding: 8
},
hr: {
margin: `${theme.spacing.unit}px 0`
},
menuItem: {
height: "auto",
whiteSpace: "normal"
},
root: {
borderRadius: 4,
left: 0,
marginTop: theme.spacing.unit,
position: "absolute",
right: 0,
zIndex: 22
},
shadow: {
boxShadow: `0px -5px 10px 0px ${theme.palette.grey[800]}`
},
shadowLine: {
boxShadow: `0px 0px 0px 0px ${theme.palette.grey[50]}`,
height: 1,
transition: theme.transitions.duration.short + "ms"
}
}),
{
name: "SingleAutocompleteSelectFieldContent"
}
);
function getChoiceIndex(
index: number,
emptyValue: boolean,
customValue: boolean
) {
let choiceIndex = index;
if (emptyValue) {
choiceIndex += 1;
}
if (customValue) {
choiceIndex += 2;
}
return choiceIndex;
}
const SingleAutocompleteSelectFieldContent: React.FC<
SingleAutocompleteSelectFieldContentProps
> = props => {
const {
choices,
displayCustomValue,
emptyOption,
getItemProps,
highlightedIndex,
inputValue,
isCustomValueSelected,
selectedItem
} = props;
const classes = useStyles(props);
const anchor = React.useRef<HTMLDivElement>();
const scrollPosition = useElementScroll(anchor);
const dropShadow = anchor.current
? scrollPosition.y + anchor.current.clientHeight <
anchor.current.scrollHeight
: false;
return (
<Paper className={classes.root} square>
<div className={classes.content} ref={anchor}>
{choices.length > 0 || displayCustomValue ? (
<>
{emptyOption && (
<MenuItem
className={classes.menuItem}
component="div"
{...getItemProps({
item: ""
})}
data-tc="singleautocomplete-select-option"
>
<Typography color="textSecondary">
<FormattedMessage defaultMessage="None" />
</Typography>
</MenuItem>
)}
{displayCustomValue && (
<MenuItem
className={classes.menuItem}
key={"customValue"}
selected={isCustomValueSelected}
component="div"
{...getItemProps({
item: inputValue
})}
data-tc="singleautocomplete-select-option"
>
<FormattedMessage
defaultMessage="Add new value: {value}"
description="add custom select input option"
values={{
value: inputValue
}}
/>
</MenuItem>
)}
{choices.length > 0 && displayCustomValue && (
<Hr className={classes.hr} />
)}
{choices.map((suggestion, index) => {
const choiceIndex = getChoiceIndex(
index,
emptyOption,
displayCustomValue
);
return (
<MenuItem
className={classes.menuItem}
key={JSON.stringify(suggestion)}
selected={
highlightedIndex === choiceIndex ||
selectedItem === suggestion.value
}
component="div"
{...getItemProps({
index: choiceIndex,
item: suggestion.value
})}
data-tc="singleautocomplete-select-option"
>
{suggestion.label}
</MenuItem>
);
})}
</>
) : (
<MenuItem
disabled={true}
component="div"
data-tc="singleautocomplete-select-no-options"
>
<FormattedMessage defaultMessage="No results found" />
</MenuItem>
)}
</div>
<div
className={classNames(classes.shadowLine, {
[classes.shadow]: dropShadow
})}
/>
</Paper>
);
};
SingleAutocompleteSelectFieldContent.displayName =
"SingleAutocompleteSelectFieldContent";
export default SingleAutocompleteSelectFieldContent;

View file

@ -0,0 +1,245 @@
export const countries = [
{ name: "Afghanistan", code: "AF" },
{ name: "Åland Islands", code: "AX" },
{ name: "Albania", code: "AL" },
{ name: "Algeria", code: "DZ" },
{ name: "American Samoa", code: "AS" },
{ name: "AndorrA", code: "AD" },
{ name: "Angola", code: "AO" },
{ name: "Anguilla", code: "AI" },
{ name: "Antarctica", code: "AQ" },
{ name: "Antigua and Barbuda", code: "AG" },
{ name: "Argentina", code: "AR" },
{ name: "Armenia", code: "AM" },
{ name: "Aruba", code: "AW" },
{ name: "Australia", code: "AU" },
{ name: "Austria", code: "AT" },
{ name: "Azerbaijan", code: "AZ" },
{ name: "Bahamas", code: "BS" },
{ name: "Bahrain", code: "BH" },
{ name: "Bangladesh", code: "BD" },
{ name: "Barbados", code: "BB" },
{ name: "Belarus", code: "BY" },
{ name: "Belgium", code: "BE" },
{ name: "Belize", code: "BZ" },
{ name: "Benin", code: "BJ" },
{ name: "Bermuda", code: "BM" },
{ name: "Bhutan", code: "BT" },
{ name: "Bolivia", code: "BO" },
{ name: "Bosnia and Herzegovina", code: "BA" },
{ name: "Botswana", code: "BW" },
{ name: "Bouvet Island", code: "BV" },
{ name: "Brazil", code: "BR" },
{ name: "British Indian Ocean Territory", code: "IO" },
{ name: "Brunei Darussalam", code: "BN" },
{ name: "Bulgaria", code: "BG" },
{ name: "Burkina Faso", code: "BF" },
{ name: "Burundi", code: "BI" },
{ name: "Cambodia", code: "KH" },
{ name: "Cameroon", code: "CM" },
{ name: "Canada", code: "CA" },
{ name: "Cape Verde", code: "CV" },
{ name: "Cayman Islands", code: "KY" },
{ name: "Central African Republic", code: "CF" },
{ name: "Chad", code: "TD" },
{ name: "Chile", code: "CL" },
{ name: "China", code: "CN" },
{ name: "Christmas Island", code: "CX" },
{ name: "Cocos (Keeling) Islands", code: "CC" },
{ name: "Colombia", code: "CO" },
{ name: "Comoros", code: "KM" },
{ name: "Congo", code: "CG" },
{ name: "Congo, The Democratic Republic of the", code: "CD" },
{ name: "Cook Islands", code: "CK" },
{ name: "Costa Rica", code: "CR" },
{ name: "Cote D'Ivoire", code: "CI" },
{ name: "Croatia", code: "HR" },
{ name: "Cuba", code: "CU" },
{ name: "Cyprus", code: "CY" },
{ name: "Czech Republic", code: "CZ" },
{ name: "Denmark", code: "DK" },
{ name: "Djibouti", code: "DJ" },
{ name: "Dominica", code: "DM" },
{ name: "Dominican Republic", code: "DO" },
{ name: "Ecuador", code: "EC" },
{ name: "Egypt", code: "EG" },
{ name: "El Salvador", code: "SV" },
{ name: "Equatorial Guinea", code: "GQ" },
{ name: "Eritrea", code: "ER" },
{ name: "Estonia", code: "EE" },
{ name: "Ethiopia", code: "ET" },
{ name: "Falkland Islands (Malvinas)", code: "FK" },
{ name: "Faroe Islands", code: "FO" },
{ name: "Fiji", code: "FJ" },
{ name: "Finland", code: "FI" },
{ name: "France", code: "FR" },
{ name: "French Guiana", code: "GF" },
{ name: "French Polynesia", code: "PF" },
{ name: "French Southern Territories", code: "TF" },
{ name: "Gabon", code: "GA" },
{ name: "Gambia", code: "GM" },
{ name: "Georgia", code: "GE" },
{ name: "Germany", code: "DE" },
{ name: "Ghana", code: "GH" },
{ name: "Gibraltar", code: "GI" },
{ name: "Greece", code: "GR" },
{ name: "Greenland", code: "GL" },
{ name: "Grenada", code: "GD" },
{ name: "Guadeloupe", code: "GP" },
{ name: "Guam", code: "GU" },
{ name: "Guatemala", code: "GT" },
{ name: "Guernsey", code: "GG" },
{ name: "Guinea", code: "GN" },
{ name: "Guinea-Bissau", code: "GW" },
{ name: "Guyana", code: "GY" },
{ name: "Haiti", code: "HT" },
{ name: "Heard Island and Mcdonald Islands", code: "HM" },
{ name: "Holy See (Vatican City State)", code: "VA" },
{ name: "Honduras", code: "HN" },
{ name: "Hong Kong", code: "HK" },
{ name: "Hungary", code: "HU" },
{ name: "Iceland", code: "IS" },
{ name: "India", code: "IN" },
{ name: "Indonesia", code: "ID" },
{ name: "Iran, Islamic Republic Of", code: "IR" },
{ name: "Iraq", code: "IQ" },
{ name: "Ireland", code: "IE" },
{ name: "Isle of Man", code: "IM" },
{ name: "Israel", code: "IL" },
{ name: "Italy", code: "IT" },
{ name: "Jamaica", code: "JM" },
{ name: "Japan", code: "JP" },
{ name: "Jersey", code: "JE" },
{ name: "Jordan", code: "JO" },
{ name: "Kazakhstan", code: "KZ" },
{ name: "Kenya", code: "KE" },
{ name: "Kiribati", code: "KI" },
{ name: "Korea, Democratic People'S Republic of", code: "KP" },
{ name: "Korea, Republic of", code: "KR" },
{ name: "Kuwait", code: "KW" },
{ name: "Kyrgyzstan", code: "KG" },
{ name: "Lao People'S Democratic Republic", code: "LA" },
{ name: "Latvia", code: "LV" },
{ name: "Lebanon", code: "LB" },
{ name: "Lesotho", code: "LS" },
{ name: "Liberia", code: "LR" },
{ name: "Libyan Arab Jamahiriya", code: "LY" },
{ name: "Liechtenstein", code: "LI" },
{ name: "Lithuania", code: "LT" },
{ name: "Luxembourg", code: "LU" },
{ name: "Macao", code: "MO" },
{ name: "Macedonia, The Former Yugoslav Republic of", code: "MK" },
{ name: "Madagascar", code: "MG" },
{ name: "Malawi", code: "MW" },
{ name: "Malaysia", code: "MY" },
{ name: "Maldives", code: "MV" },
{ name: "Mali", code: "ML" },
{ name: "Malta", code: "MT" },
{ name: "Marshall Islands", code: "MH" },
{ name: "Martinique", code: "MQ" },
{ name: "Mauritania", code: "MR" },
{ name: "Mauritius", code: "MU" },
{ name: "Mayotte", code: "YT" },
{ name: "Mexico", code: "MX" },
{ name: "Micronesia, Federated States of", code: "FM" },
{ name: "Moldova, Republic of", code: "MD" },
{ name: "Monaco", code: "MC" },
{ name: "Mongolia", code: "MN" },
{ name: "Montserrat", code: "MS" },
{ name: "Morocco", code: "MA" },
{ name: "Mozambique", code: "MZ" },
{ name: "Myanmar", code: "MM" },
{ name: "Namibia", code: "NA" },
{ name: "Nauru", code: "NR" },
{ name: "Nepal", code: "NP" },
{ name: "Netherlands", code: "NL" },
{ name: "Netherlands Antilles", code: "AN" },
{ name: "New Caledonia", code: "NC" },
{ name: "New Zealand", code: "NZ" },
{ name: "Nicaragua", code: "NI" },
{ name: "Niger", code: "NE" },
{ name: "Nigeria", code: "NG" },
{ name: "Niue", code: "NU" },
{ name: "Norfolk Island", code: "NF" },
{ name: "Northern Mariana Islands", code: "MP" },
{ name: "Norway", code: "NO" },
{ name: "Oman", code: "OM" },
{ name: "Pakistan", code: "PK" },
{ name: "Palau", code: "PW" },
{ name: "Palestinian Territory, Occupied", code: "PS" },
{ name: "Panama", code: "PA" },
{ name: "Papua New Guinea", code: "PG" },
{ name: "Paraguay", code: "PY" },
{ name: "Peru", code: "PE" },
{ name: "Philippines", code: "PH" },
{ name: "Pitcairn", code: "PN" },
{ name: "Poland", code: "PL" },
{ name: "Portugal", code: "PT" },
{ name: "Puerto Rico", code: "PR" },
{ name: "Qatar", code: "QA" },
{ name: "Reunion", code: "RE" },
{ name: "Romania", code: "RO" },
{ name: "Russian Federation", code: "RU" },
{ name: "RWANDA", code: "RW" },
{ name: "Saint Helena", code: "SH" },
{ name: "Saint Kitts and Nevis", code: "KN" },
{ name: "Saint Lucia", code: "LC" },
{ name: "Saint Pierre and Miquelon", code: "PM" },
{ name: "Saint Vincent and the Grenadines", code: "VC" },
{ name: "Samoa", code: "WS" },
{ name: "San Marino", code: "SM" },
{ name: "Sao Tome and Principe", code: "ST" },
{ name: "Saudi Arabia", code: "SA" },
{ name: "Senegal", code: "SN" },
{ name: "Serbia and Montenegro", code: "CS" },
{ name: "Seychelles", code: "SC" },
{ name: "Sierra Leone", code: "SL" },
{ name: "Singapore", code: "SG" },
{ name: "Slovakia", code: "SK" },
{ name: "Slovenia", code: "SI" },
{ name: "Solomon Islands", code: "SB" },
{ name: "Somalia", code: "SO" },
{ name: "South Africa", code: "ZA" },
{ name: "South Georgia and the South Sandwich Islands", code: "GS" },
{ name: "Spain", code: "ES" },
{ name: "Sri Lanka", code: "LK" },
{ name: "Sudan", code: "SD" },
{ name: "Suriname", code: "SR" },
{ name: "Svalbard and Jan Mayen", code: "SJ" },
{ name: "Swaziland", code: "SZ" },
{ name: "Sweden", code: "SE" },
{ name: "Switzerland", code: "CH" },
{ name: "Syrian Arab Republic", code: "SY" },
{ name: "Taiwan, Province of China", code: "TW" },
{ name: "Tajikistan", code: "TJ" },
{ name: "Tanzania, United Republic of", code: "TZ" },
{ name: "Thailand", code: "TH" },
{ name: "Timor-Leste", code: "TL" },
{ name: "Togo", code: "TG" },
{ name: "Tokelau", code: "TK" },
{ name: "Tonga", code: "TO" },
{ name: "Trinidad and Tobago", code: "TT" },
{ name: "Tunisia", code: "TN" },
{ name: "Turkey", code: "TR" },
{ name: "Turkmenistan", code: "TM" },
{ name: "Turks and Caicos Islands", code: "TC" },
{ name: "Tuvalu", code: "TV" },
{ name: "Uganda", code: "UG" },
{ name: "Ukraine", code: "UA" },
{ name: "United Arab Emirates", code: "AE" },
{ name: "United Kingdom", code: "GB" },
{ name: "United States", code: "US" },
{ name: "United States Minor Outlying Islands", code: "UM" },
{ name: "Uruguay", code: "UY" },
{ name: "Uzbekistan", code: "UZ" },
{ name: "Vanuatu", code: "VU" },
{ name: "Venezuela", code: "VE" },
{ name: "Viet Nam", code: "VN" },
{ name: "Virgin Islands, British", code: "VG" },
{ name: "Virgin Islands, U.S.", code: "VI" },
{ name: "Wallis and Futuna", code: "WF" },
{ name: "Western Sahara", code: "EH" },
{ name: "Yemen", code: "YE" },
{ name: "Zambia", code: "ZM" },
{ name: "Zimbabwe", code: "ZW" }
];

View file

@ -39,7 +39,6 @@ function loadStories() {
require("./stories/components/RichTextEditor");
require("./stories/components/SaveButtonBar");
require("./stories/components/SaveFilterTabDialog");
require("./stories/components/SingleAutocompleteSelectField");
require("./stories/components/SingleSelectField");
require("./stories/components/Skeleton");
require("./stories/components/StatusLabel");

View file

@ -1,16 +1,14 @@
import React from "react";
interface ChoiceProviderProps {
children: ((
props: {
children: (props: {
choices: Array<{
label: string;
value: string;
}>;
loading: boolean;
fetchChoices(value: string);
}
) => React.ReactElement<any>);
}) => React.ReactElement<any>;
choices: Array<{
label: string;
value: string;
@ -31,7 +29,7 @@ export class ChoiceProvider extends React.Component<
> {
state = { choices: [], loading: false, timeout: null };
handleChange = inputValue => {
handleChange = (inputValue: string) => {
if (this.state.loading) {
clearTimeout(this.state.timeout);
}
@ -42,22 +40,16 @@ export class ChoiceProvider extends React.Component<
});
};
fetchChoices = inputValue => {
let count = 0;
fetchChoices = (inputValue: string) => {
this.setState({
choices: this.props.choices.filter(suggestion => {
const keep =
(!inputValue ||
choices: this.props.choices
.filter(
suggestion =>
!inputValue ||
suggestion.label.toLowerCase().indexOf(inputValue.toLowerCase()) !==
-1) &&
count < 5;
if (keep) {
count += 1;
}
return keep;
}),
-1
)
.slice(0, 10),
loading: false,
timeout: null
});