diff --git a/package.json b/package.json
index e5fd7650c..542143ebb 100644
--- a/package.json
+++ b/package.json
@@ -82,7 +82,7 @@
"@material/notched-outline": "^1.1.1",
"@material/radio": "^1.1.0",
"@material/ripple": "^1.0.0",
- "@material/select": "^0.40.1",
+ "@material/select": "^1.1.1",
"@material/snackbar": "^1.0.0",
"@material/switch": "^1.0.0",
"@material/tab": "^1.0.0",
diff --git a/packages/select/icon/README.md b/packages/select/icon/README.md
new file mode 100644
index 000000000..be00e3190
--- /dev/null
+++ b/packages/select/icon/README.md
@@ -0,0 +1,23 @@
+# React Select Icon
+
+MDC React Select Icon is a React Component which uses MDC [MDC Select Icon](https://github.com/material-components/material-components-web/tree/master/packages/mdc-select/icon/)'s CSS and foundation JavaScript.
+
+## Usage
+
+```js
+import {SelectIcon} from '@material/react-select/icon/index';
+
+const MyComponent = () => {
+ return (
+
+ favorite
+
+ );
+}
+```
+
+## Props
+
+Prop Name | Type | Description
+--- | --- | ---
+tag | string (keyof React.ReactHTML) | Sets the element tag. Defaults to i which becomes``.
diff --git a/packages/select/icon/index.tsx b/packages/select/icon/index.tsx
new file mode 100644
index 000000000..78e2f88e4
--- /dev/null
+++ b/packages/select/icon/index.tsx
@@ -0,0 +1,120 @@
+// The MIT License
+//
+// Copyright (c) 2019 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import React from 'react';
+import classnames from 'classnames';
+import {MDCSelectIconAdapter} from '@material/select/icon/adapter';
+import {MDCSelectIconFoundation} from '@material/select/icon/foundation';
+
+export interface SelectIconProps extends React.HTMLProps {
+ setIconFoundation?: (foundation?: MDCSelectIconFoundation) => void;
+ tag?: keyof React.ReactHTML;
+}
+
+interface ElementAttributes {
+ 'tabindex'?: number;
+ role?: string;
+};
+
+interface SelectIconState extends ElementAttributes {};
+
+export class SelectIcon extends React.Component {
+ foundation?: MDCSelectIconFoundation;
+
+ state: SelectIconState = {
+ 'tabindex': undefined,
+ 'role': undefined,
+ };
+
+ static defaultProps = {
+ tag: 'i',
+ };
+
+ componentDidMount() {
+ const {setIconFoundation} = this.props;
+ this.foundation = new MDCSelectIconFoundation(this.adapter);
+ this.foundation.init();
+ setIconFoundation && setIconFoundation(this.foundation);
+ }
+
+ componentWillUnmount() {
+ const {setIconFoundation} = this.props;
+ if (this.foundation) {
+ this.foundation.destroy();
+ setIconFoundation && setIconFoundation(undefined);
+ }
+ }
+
+ get adapter(): MDCSelectIconAdapter {
+ return {
+ getAttr: (attr: keyof ElementAttributes) => {
+ if (this.state[attr] !== undefined) {
+ return (this.state[attr] as ElementAttributes[keyof ElementAttributes])!.toString();
+ }
+ const reactAttr = attr === 'tabindex' ? 'tabIndex' : attr;
+ if (this.props[reactAttr] !== undefined) {
+ return (this.props[reactAttr])!.toString();
+ }
+ return null;
+ },
+ setAttr: (attr: keyof ElementAttributes, value: ElementAttributes[keyof ElementAttributes]) => {
+ this.setState((prevState) => ({
+ ...prevState,
+ [attr]: value,
+ }));
+ },
+ removeAttr: (attr: keyof ElementAttributes) => {
+ this.setState((prevState) => ({...prevState, [attr]: null}));
+ },
+ setContent: () => {
+ // not implmenting because developer should would never call `setContent()`
+ },
+ // the adapter methods below are effectively useless since React
+ // handles events and width differently
+ registerInteractionHandler: () => undefined,
+ deregisterInteractionHandler: () => undefined,
+ notifyIconAction: () => undefined,
+ };
+ }
+
+ render() {
+ const {
+ tag: Tag,
+ setIconFoundation, // eslint-disable-line no-unused-vars
+ children,
+ className,
+ ...otherProps
+ } = this.props;
+ const {tabindex: tabIndex, role} = this.state;
+ return (
+ // @ts-ignore https://github.com/Microsoft/TypeScript/issues/28892
+
+ {children}
+
+ );
+ }
+}
diff --git a/packages/select/package.json b/packages/select/package.json
index c9a01731d..17283de20 100644
--- a/packages/select/package.json
+++ b/packages/select/package.json
@@ -19,7 +19,7 @@
"@material/react-floating-label": "^0.11.0",
"@material/react-line-ripple": "^0.11.0",
"@material/react-notched-outline": "^0.11.0",
- "@material/select": "^0.40.1",
+ "@material/select": "^1.1.1",
"classnames": "^2.2.6",
"react": "^16.4.2"
},
diff --git a/test/screenshot/golden.json b/test/screenshot/golden.json
index 7e905c010..e9140580f 100644
--- a/test/screenshot/golden.json
+++ b/test/screenshot/golden.json
@@ -27,7 +27,6 @@
"text-field/textArea": "b4c009f5f7637f7103380a2dd93bd6387d1ccc22d031323e6f0472949ce35881",
"text-field/standard": "61cf0ebade2a09263d3a015c26cde28b3b3e67ab9d9bb4c494ac4823b9e8000b",
"text-field/fullWidth": "26fa1e96054939384efb6427d93967bdbbc05ecc00bf7e4f13ab17cbe3e367fb",
- "text-field/outlined": "91e95a9bfb4e3f75ba9bb6a7ccf6a379b944d4960aaffd4ca2e4026a3f3daa71",
"text-field/refTest": "742fe55ba0f3ca11c74beef5ea9737e2eaec37d9c8524552f3b06c6cb25f4157",
"top-app-bar/fixed": "7a2dd6318d62ac2eabd66f1b28100db7c15840ccb753660065fa9524db6435d6",
"top-app-bar/prominent": "2506ed2dd5f370c7bab69315d2daebd58b443d2b9e32bbaec762e40a8736309b",
diff --git a/test/screenshot/text-field/variants.tsx b/test/screenshot/text-field/variants.tsx
index dbf0457ac..aaabf4033 100644
--- a/test/screenshot/text-field/variants.tsx
+++ b/test/screenshot/text-field/variants.tsx
@@ -1,7 +1,6 @@
export default [
'standard',
'fullWidth',
- 'outlined',
'textArea',
'refTest',
];
diff --git a/test/unit/select/icon/index.test.tsx b/test/unit/select/icon/index.test.tsx
new file mode 100644
index 000000000..e104d018d
--- /dev/null
+++ b/test/unit/select/icon/index.test.tsx
@@ -0,0 +1,79 @@
+import * as React from 'react';
+import * as td from 'testdouble';
+import {assert} from 'chai';
+import {shallow, mount} from 'enzyme';
+import {SelectIcon} from '../../../../packages/select/icon/index';
+import {MDCSelectIconFoundation} from '@material/select';
+
+suite('Select Icon');
+
+test('renders with mdc-select-helper-text class', () => {
+ const wrapper = shallow();
+ assert.isTrue(wrapper.hasClass('mdc-select__icon'));
+});
+
+test('renders with a test class name', () => {
+ const wrapper = shallow();
+ assert.isTrue(wrapper.hasClass('test-class'));
+});
+
+test('calls setIconFoundation with foundation', () => {
+ const setIconFoundation = td.func<(foundation?: MDCSelectIconFoundation) => void>();
+ shallow();
+ // TODO: change Object to MDCSelectHelperTextFoundation in PR 823
+ td.verify(setIconFoundation(td.matchers.isA(Object)), {times: 1});
+});
+
+test('#componentWillUnmount destroys foundation', () => {
+ const wrapper = mount();
+ const foundation = wrapper.instance().foundation!;
+ foundation.destroy = td.func<() => void>();
+ wrapper.unmount();
+ td.verify(foundation.destroy(), {times: 1});
+});
+
+test('#adapter.setAttr should update state', () => {
+ const wrapper = shallow();
+ wrapper.instance().adapter.setAttr('role', 'menu');
+ assert.equal(wrapper.state().role, 'menu');
+});
+
+test('#adapter.removeAttr should update state', () => {
+ const wrapper = shallow();
+ wrapper.setState({role: 'menu'});
+ wrapper.instance().adapter.removeAttr('role');
+ assert.equal(wrapper.state().role, null);
+});
+
+test('renders with tabindex from state.tabindex', () => {
+ const wrapper = mount();
+ wrapper.setState({'tabindex': 1});
+ assert.equal(wrapper.getDOMNode().getAttribute('tabindex'), '1');
+});
+
+test('#adapter.getAttr returns the correct value of role', () => {
+ const wrapper = mount();
+ assert.equal(wrapper.instance().adapter.getAttr('role'), 'menu');
+});
+
+test('#adapter.getAttr returns the correct value of tabindex', () => {
+ const wrapper = mount();
+ assert.equal(wrapper.instance().adapter.getAttr('tabindex'), '1');
+});
+
+test('#adapter.getAttr returns the correct value of role if it exists on state.role', () => {
+ const wrapper = mount();
+ wrapper.setState({role: 'menu'});
+ assert.equal(wrapper.instance().adapter.getAttr('role'), 'menu');
+});
+
+test('renders with role from state.role', () => {
+ const wrapper = mount();
+ wrapper.setState({'role': 'true'});
+ assert.equal(wrapper.getDOMNode().getAttribute('role'), 'true');
+});
+
+test('renders children', () => {
+ const wrapper = mount(MEOW);
+ assert.equal(wrapper.text(), 'MEOW');
+});