feat: Implement SelectField

This commit is contained in:
Chi Vinh Le
2018-10-17 00:00:34 +02:00
parent e9c80fc02a
commit 70465ebd31
18 changed files with 381 additions and 17 deletions
+1 -13
View File
@@ -1,17 +1,5 @@
@font-face {
font-family: "Material Icons";
font-style: normal;
font-weight: 400;
src: local("Material Icons"), local("MaterialIcons-Regular"),
url(material-design-icons/iconfont/MaterialIcons-Regular.woff2)
format("woff2"),
url(material-design-icons/iconfont/MaterialIcons-Regular.woff)
format("woff"),
url(material-design-icons/iconfont/MaterialIcons-Regular.ttf)
format("truetype");
}
.root {
composes: icon from "talk-ui/shared/icon.css";
font-family: "Material Icons";
speak: none;
font-style: normal;
@@ -0,0 +1,14 @@
import React from "react";
import TestRenderer from "react-test-renderer";
import { PropTypesOf } from "talk-ui/types";
import OptGroup from "./OptGroup";
it("renders correctly", () => {
const props: PropTypesOf<typeof OptGroup> = {
label: "mamals",
children: <span />,
};
const renderer = TestRenderer.create(<OptGroup {...props} />);
expect(renderer.toJSON()).toMatchSnapshot();
});
@@ -0,0 +1,13 @@
import React from "react";
import { StatelessComponent } from "react";
export interface OptGroupProps {
label: string;
children?: React.ReactNode;
}
const OptionGroup: StatelessComponent<OptGroupProps> = props => {
return <optgroup {...props} />;
};
export default OptionGroup;
@@ -0,0 +1,16 @@
import React from "react";
import TestRenderer from "react-test-renderer";
import { PropTypesOf } from "talk-ui/types";
import Option from "./Option";
it("renders correctly", () => {
const props: PropTypesOf<typeof Option> = {
disabled: false,
hidden: false,
value: "apple",
children: "Apple",
};
const renderer = TestRenderer.create(<Option {...props} />);
expect(renderer.toJSON()).toMatchSnapshot();
});
@@ -0,0 +1,15 @@
import React from "react";
import { StatelessComponent } from "react";
export interface OptionProps {
value?: string;
disabled?: boolean;
hidden?: boolean;
children?: React.ReactNode;
}
const Option: StatelessComponent<OptionProps> = props => {
return <option {...props} />;
};
export default Option;
@@ -0,0 +1,65 @@
.root {
position: relative;
display: inline-block;
}
.iconWrapper {
position: absolute;
display: inline-flex;
justify-content: center;
align-items: center;
right: calc(0.5 * var(--spacing-unit));
height: 100%;
user-select: none;
pointer-events: none;
}
.keyboardFocus:focus {
outline-width: 3px;
outline-color: Highlight;
outline-color: -webkit-focus-ring-color;
outline-style: auto;
}
.select {
composes: menuItem from "talk-ui/shared/typography.css";
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
outline: none;
color: var(--palette-text-primary);
&::-moz-focus-inner {
border: 0;
}
&:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 #000;
}
&:disabled {
background-color: var(--palette-grey-lightest);
border-color: var(--palette-text-secondary);
color: var(--palette-text-secondary);
}
border: 1px solid var(--palette-text-primary);
border-radius: var(--round-corners);
background-color: var(--palette-common-white);
padding: calc(0.5 * var(--spacing-unit)) calc(2 * var(--spacing-unit))
calc(0.5 * var(--spacing-unit)) var(--spacing-unit);
&::-ms-expand {
display: none;
}
}
.fullWidth {
width: 100%;
}
.iconWrapperDisabled {
color: var(--palette-text-secondary);
}
@@ -0,0 +1,52 @@
---
name: SelectField
menu: UI Kit
---
import { Playground, PropsTable } from 'docz'
import SelectField from './SelectField.tsx'
import Option from './Option.tsx'
import OptGroup from './OptGroup.tsx'
import HorizontalGutter from '../HorizontalGutter'
import Container from "react-with-state-props"
# SelectField
## Basic Use
<Playground>
<Container
state={{ value: "blue" }}
render={props => (
<SelectField value={props.value} onChange={e => props.setValue(e.value)}>
<Option value="red">Red Pill</Option>
<Option value="blue">Blue Pill</Option>
</SelectField>
)}/>
</Playground>
## Groups
<Playground>
<Container
state={{ value: "dog" }}
render={props => (
<SelectField value={props.value} onChange={e => props.setValue(e.value)}>
<OptGroup label="mammals">
<Option value="dog">Dog</Option>
<Option value="cat">Cat</Option>
</OptGroup>
<OptGroup label="fish">
<Option value="salmon">Salmon</Option>
<Option value="tuna">Tuna</Option>
</OptGroup>
</SelectField>
)}/>
</Playground>
## Disabled
<Playground>
<SelectField disabled>
<Option value="red">Red Pill</Option>
<Option value="blue">Blue Pill</Option>
</SelectField>
</Playground>
@@ -0,0 +1,21 @@
import { noop } from "lodash";
import React from "react";
import TestRenderer from "react-test-renderer";
import { PropTypesOf } from "talk-ui/types";
import SelectField from "./SelectField";
it("renders correctly", () => {
const props: PropTypesOf<typeof SelectField> = {
id: "selectID",
autofocus: true,
name: "selectName",
value: "pie",
className: "customClassName",
fullWidth: true,
onChange: noop,
disabled: true,
};
const renderer = TestRenderer.create(<SelectField {...props} />);
expect(renderer.toJSON()).toMatchSnapshot();
});
@@ -0,0 +1,81 @@
import cn from "classnames";
import React, {
ChangeEvent,
EventHandler,
FocusEvent,
MouseEvent,
} from "react";
import { StatelessComponent } from "react";
import { withKeyboardFocus, withStyles } from "talk-ui/hocs";
import Icon from "../Icon";
import * as styles from "./SelectField.css";
export interface SelectFieldProps {
id?: string;
autofocus?: boolean;
/**
* Name
*/
name?: string;
/**
* Value that has been selected.
*/
value?: string;
/**
* Convenient prop to override the root styling.
*/
className?: string;
/**
* Override or extend the styles applied to the component.
*/
classes: typeof styles;
/*
* If set renders a full width button
*/
fullWidth?: boolean;
onChange?: EventHandler<ChangeEvent<HTMLSelectElement>>;
onFocus: EventHandler<FocusEvent<HTMLSelectElement>>;
onBlur: EventHandler<FocusEvent<HTMLSelectElement>>;
onMouseDown: EventHandler<MouseEvent<HTMLSelectElement>>;
disabled?: boolean;
keyboardFocus: boolean;
}
const SelectField: StatelessComponent<SelectFieldProps> = props => {
const {
className,
classes,
fullWidth,
keyboardFocus,
children,
disabled,
...rest
} = props;
const selectClassName = cn(classes.select, {
[classes.fullWidth]: fullWidth,
[classes.keyboardFocus]: keyboardFocus,
});
const iconWrapperClassName = cn(classes.iconWrapper, {
[classes.iconWrapperDisabled]: disabled,
});
return (
<span className={cn(classes.root, className)}>
<select className={selectClassName} disabled={disabled} {...rest}>
{children}
</select>
<span className={iconWrapperClassName} aria-hidden>
<Icon>expand_more</Icon>
</span>
</span>
);
};
const enhanced = withStyles(styles)(withKeyboardFocus(SelectField));
export default enhanced;
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<optgroup
label="mamals"
>
<span />
</optgroup>
`;
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<option
disabled={false}
hidden={false}
value="apple"
>
Apple
</option>
`;
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<span
className="SelectField-root customClassName"
>
<select
autofocus={true}
className="SelectField-select SelectField-fullWidth"
disabled={true}
id="selectID"
name="selectName"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
value="pie"
/>
<span
aria-hidden={true}
className="SelectField-iconWrapper SelectField-iconWrapperDisabled"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
expand_more
</span>
</span>
</span>
`;
@@ -0,0 +1,3 @@
export { default, default as SelectField } from "./SelectField";
export { default as Option } from "./Option";
export { default as OptGroup } from "./OptGroup";
@@ -7,6 +7,7 @@ menu: UI Kit
### Examples
import { Playground } from 'docz'
import { Flex, Step, StepBar } from './index'
<Playground>
+1
View File
@@ -23,3 +23,4 @@ export { default as AriaInfo } from "./AriaInfo";
export { default as Message, MessageIcon } from "./Message";
export { Tab, TabBar, TabContent, TabPane } from "./Tabs";
export { StepBar, Step } from "./Steps";
export { SelectField, Option, OptGroup } from "./SelectField";
@@ -63,7 +63,4 @@ const withKeyboardFocus = hoistStatics<InjectedProps>(
}
);
// TODO: workaround, add bug link.
export default withKeyboardFocus as <P extends Partial<InjectedProps>>(
BaseComponent: React.ComponentType<P>
) => React.ComponentType<P>;
export default withKeyboardFocus;
+37
View File
@@ -0,0 +1,37 @@
@font-face {
font-family: "Material Icons";
font-style: normal;
font-weight: 400;
src: local("Material Icons"), local("MaterialIcons-Regular"),
url(material-design-icons/iconfont/MaterialIcons-Regular.woff2)
format("woff2"),
url(material-design-icons/iconfont/MaterialIcons-Regular.woff)
format("woff"),
url(material-design-icons/iconfont/MaterialIcons-Regular.ttf)
format("truetype");
}
.icon {
font-family: "Material Icons";
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
overflow: hidden;
vertical-align: middle;
display: inline-block;
letter-spacing: 0;
/* Enable Ligatures */
font-feature-settings: "liga";
font-variant-ligatures: "discretionary-ligatures";
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Better Font Rendering */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
+9
View File
@@ -190,3 +190,12 @@
letter-spacing: calc(0.2em / 18);
color: var(--palette-text-primary);
}
.menuItem {
font-size: calc(16rem / var(--rem-base));
font-weight: var(--font-weight-regular);
font-family: var(--font-family-sans-serif);
line-height: calc(19em / 16);
letter-spacing: calc(0.2em / 16);
color: var(--palette-text-primary);
}