From 84043a32e7a4c2de0e49556293a05b1384a535ec Mon Sep 17 00:00:00 2001 From: Ben McKernan Date: Sat, 23 Feb 2019 01:47:27 +0100 Subject: [PATCH 01/22] fix(tab): Set initial tabIndex state to -1 (#690) (#691) --- packages/tab/index.tsx | 1 + test/unit/tab/index.test.tsx | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/packages/tab/index.tsx b/packages/tab/index.tsx index 767062dfa..0d63cf797 100644 --- a/packages/tab/index.tsx +++ b/packages/tab/index.tsx @@ -72,6 +72,7 @@ export default class Tab extends React.Component { 'aria-selected': false, 'activateIndicator': false, 'previousIndicatorClientRect': this.props.previousIndicatorClientRect, + 'tabIndex': -1, }; componentDidMount() { diff --git a/test/unit/tab/index.test.tsx b/test/unit/tab/index.test.tsx index 6ecfc32c1..5787a66d5 100644 --- a/test/unit/tab/index.test.tsx +++ b/test/unit/tab/index.test.tsx @@ -29,6 +29,16 @@ test('adds the active class if props.active is true on mount', () => { assert.isTrue(wrapper.hasClass('mdc-tab--active')); }); +test('sets the tabIndex to 0 if props.active is true on mount', () => { + const wrapper = shallow(); + assert.equal(wrapper.prop('tabIndex'), 0); +}); + +test('sets the tabIndex to -1 if props.active is false on mount', () => { + const wrapper = shallow(); + assert.equal(wrapper.prop('tabIndex'), -1); +}); + test('adds a class from state.classList', () => { const wrapper = shallow(); wrapper.setState({classList: new Set(['test-class'])}); From 66d36b69d0a9aed1bd8c04488233f0fd973504d3 Mon Sep 17 00:00:00 2001 From: Eli Gao Date: Sun, 24 Feb 2019 01:57:10 +0800 Subject: [PATCH 02/22] docs(text-field): fix duplicated "from" in example (#687) --- packages/text-field/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/text-field/README.md b/packages/text-field/README.md index d8f6e31e6..34c3bc114 100644 --- a/packages/text-field/README.md +++ b/packages/text-field/README.md @@ -29,7 +29,7 @@ React Text Field accepts one child element which is the input element. For ease ```js import React from 'react'; import TextField, {HelperText, Input} from '@material/react-text-field'; -import MaterialIcon from from '@material/react-material-icon'; +import MaterialIcon from '@material/react-material-icon'; class MyApp extends React.Component { state = {value: 'Woof'}; From 84b6395369ec54cabb03945ec3b42da039388724 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 27 Feb 2019 12:32:25 -0500 Subject: [PATCH 03/22] feat(top-app-bar): add children components for composition --- packages/top-app-bar/Icon.tsx | 54 +++++ packages/top-app-bar/README.md | 93 ++++++-- packages/top-app-bar/Row.tsx | 47 ++++ packages/top-app-bar/Section.tsx | 55 +++++ packages/top-app-bar/Title.tsx | 47 ++++ packages/top-app-bar/constants.ts | 39 ++++ packages/top-app-bar/index.tsx | 172 ++++++++++---- packages/top-app-bar/package.json | 3 +- .../drawer/DrawerAboveTopAppBar.tsx | 15 +- .../drawer/DrawerBelowTopAppBar.tsx | 13 +- test/screenshot/drawer/DrawerTest.tsx | 2 + test/screenshot/golden.json | 7 +- test/screenshot/top-app-bar/actionItems.ts | 10 + test/screenshot/top-app-bar/dense.tsx | 35 ++- test/screenshot/top-app-bar/fixed.tsx | 34 ++- test/screenshot/top-app-bar/index.scss | 14 ++ test/screenshot/top-app-bar/index.tsx | 9 + test/screenshot/top-app-bar/prominent.tsx | 35 ++- .../screenshot/top-app-bar/prominentDense.tsx | 35 +-- .../top-app-bar/prominentToShortCollapsed.tsx | 51 ++-- test/screenshot/top-app-bar/short.tsx | 34 ++- .../screenshot/top-app-bar/shortCollapsed.tsx | 32 ++- test/screenshot/top-app-bar/standard.tsx | 33 ++- .../top-app-bar/standardNoActionItems.tsx | 24 +- .../standardWithNavigationIconElement.tsx | 33 ++- test/screenshot/top-app-bar/twoRows.tsx | 61 +++++ test/screenshot/top-app-bar/variants.tsx | 2 + test/unit/top-app-bar/Icon.test.tsx | 144 ++++++++++++ test/unit/top-app-bar/Row.test.tsx | 41 ++++ test/unit/top-app-bar/Section.test.tsx | 53 +++++ test/unit/top-app-bar/Title.test.tsx | 41 ++++ test/unit/top-app-bar/index.test.tsx | 220 ++++++++++++------ 32 files changed, 1234 insertions(+), 254 deletions(-) create mode 100644 packages/top-app-bar/Icon.tsx create mode 100644 packages/top-app-bar/Row.tsx create mode 100644 packages/top-app-bar/Section.tsx create mode 100644 packages/top-app-bar/Title.tsx create mode 100644 packages/top-app-bar/constants.ts create mode 100644 test/screenshot/top-app-bar/actionItems.ts create mode 100644 test/screenshot/top-app-bar/twoRows.tsx create mode 100644 test/unit/top-app-bar/Icon.test.tsx create mode 100644 test/unit/top-app-bar/Row.test.tsx create mode 100644 test/unit/top-app-bar/Section.test.tsx create mode 100644 test/unit/top-app-bar/Title.test.tsx diff --git a/packages/top-app-bar/Icon.tsx b/packages/top-app-bar/Icon.tsx new file mode 100644 index 000000000..053f75f78 --- /dev/null +++ b/packages/top-app-bar/Icon.tsx @@ -0,0 +1,54 @@ +// 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 * as React from 'react'; +import * as classnames from 'classnames'; +import {cssClasses} from './constants'; + + +export interface IconProps extends React.HTMLProps { + actionItem?: boolean; + className?: string; + children: React.ReactElement; + navIcon?: boolean; +} + + +const Icon: (props: IconProps ) => + React.ReactElement = ({ + /* eslint-disable react/prop-types */ + actionItem = false, + navIcon = false, + className, + children, + ...otherProps + /* eslint-enable react/prop-types */ + }) => + React.cloneElement(children, { + ...otherProps, + className: classnames(className, children.props.className, { + [cssClasses.ACTION_ITEM]: actionItem, + [cssClasses.NAV_ICON]: navIcon, + }), + }); + +export default Icon; diff --git a/packages/top-app-bar/README.md b/packages/top-app-bar/README.md index 31595dd77..2f8a23f12 100644 --- a/packages/top-app-bar/README.md +++ b/packages/top-app-bar/README.md @@ -26,20 +26,38 @@ import '@material/react-material-icon/dist/material-icon.css'; ### Javascript Instantiation ```js -import TopAppBar, {TopAppBarFixedAdjust} from '@material/react-top-app-bar'; +import TopAppBar, { + TopAppBarFixedAdjust, + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '@material/react-top-app-bar'; import MaterialIcon from '@material/react-material-icon'; const MyComponent = () => { return (
- console.log('click')} - />} - actionItems={[]} - /> + + + + + console.log('click')}/> + + Miami, FL + + + + console.log('print')} + /> + + + + My exciting content! @@ -56,17 +74,55 @@ Use the `` component to give your content top-padding, s Prop Name | Type | Description --- | --- | --- -actionItems | Array | Accepts an array of elements that should be rendered to the opposite side of the title. Note that a single action item should also be passed as an array. className | String | Classes to be applied to the root element. -title | String | The title of the Top App Bar. -navigationIcon | Element | Appears adjacent to the title. This acts as the main action of the Top App Bar. short | Boolean | Enables short variant. shortCollapsed | Boolean | Enables short collapsed variant. prominent | Boolean | Enables prominent variant. fixed | Boolean | Enables fixed variant. dense | Boolean | Enables dense variant. +scrollTarget | React.RefObject | Sets scroll target to different DOM node (default is `window`) +tag | String | Customizes the `TopAppBar` HTML tag. (default: `
`) +> NOTES: As per design guidelines, prominent and dense variants should not be used with short or short collapsed. Additionally, dense variants should only be used on desktop. Additionally short top-app-bars should be used with no more than 1 action item. + +#### Deprecated TopAppBar Props + +The following props are deprecated since v0.11.0 and are scheduled for removal in v0.13.0. +They will still render as expected until v0.13.0, but will output a warning to the console. + +Prop Name | Type | Description +--- | --- | --- +actionItems | Array | Accepts an array of elements that should be rendered to the opposite side of the title. Note that a single action item should also be passed as an array. +navigationIcon | Element | Appears adjacent to the title. This acts as the main action of the Top App Bar. +title | String | The Title of the Top App Bar. + +### TopAppBarRow +Prop Name | Type | Description +--- | --- | --- +className | String | Classes to be applied to the root element. +tag | String | Customizes the `TopAppBarRow` tag. (default: `
`) + +### TopAppBarSection +Prop Name | Type | Description +--- | --- | --- +align | Sring ('start' or 'end') | optional property tha aligns section content to either start or end of section +className | String | Classes to be applied to the root element. +tag | String | Customizes the `TopAppBarSection` tag. (default: `
`) +> Note: if section contains action items it is reccomended to add property role='toolbar' for a11y purposes + +### TopAppBarTitle +Prop Name | Type | Description +--- | --- | --- +className | String | Classes to be applied to the root element. +tag | String | Customizes the `TopAppBarTitle` tag. (default: ``) -> NOTES: As per design guidelines, prominent and dense variants should not be used with short or short collapsed. Additionally, dense variants should only be used on desktop. +### TopAppBarIcon +Prop Name | Type | Description +--- | --- | --- +className | String | Classes to be applied to the root element. +actionItem | Boolean | applies action-item class to icon +navIcon | Boolean | applies nav-icon class to icon +children | React.ReactElement | can be any icon. Material Icons are reccomended +> Notes: (1) consider adding `aria-label` to actionItem's. (2) you may need to manually add ripple or tabindex to icon. (3) Short top-app-bars shold be used with no more than 1 action item. ### TopAppBarFixedAdjust @@ -89,8 +145,9 @@ Use of [Material Icon's](../material-icon/README.md) for Action Items and Naviga The navigation icon can be a ``, ``, ``, ``, ``, etc., but again must be wrapped with the `withRipple HOC`. ```js - menu} /> + + menu + ``` If you decide to use a React Component please see [Integrating with Components](./../../docs/guidelines.md#integrating-with-components). @@ -100,13 +157,13 @@ If you decide to use a React Component please see [Integrating with Components]( Similar to the [navigation icon](#navigation-icon), it can be ``, ``, ``, ``, ``, etc., and must be wrapped with the `withRipple HOC`. ```js - bookmark]} /> + + bookmark ``` If you decide to use a React Component please see [Integrating with Components](./../../docs/guidelines.md#integrating-with-components). -> NOTE: `actionItems` prop is expecting an array of elements. ## Sass Mixins diff --git a/packages/top-app-bar/Row.tsx b/packages/top-app-bar/Row.tsx new file mode 100644 index 000000000..8cb4a7bc4 --- /dev/null +++ b/packages/top-app-bar/Row.tsx @@ -0,0 +1,47 @@ +// 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 * as React from 'react'; +import * as classnames from 'classnames'; +import {cssClasses} from './constants'; + +export interface RowProps extends React.HTMLProps { + className?: string; + tag?: string; +}; + +const Row: (props: RowProps) => + React.ReactElement = ({ + /* eslint-disable react/prop-types */ + children, + className, + tag: Tag = 'div', + ...otherProps + /* eslint-enable react/prop-types */ + }) => ( + // @ts-ignore https://github.com/Microsoft/TypeScript/issues/28892 + + {children} + + ); + +export default Row; diff --git a/packages/top-app-bar/Section.tsx b/packages/top-app-bar/Section.tsx new file mode 100644 index 000000000..9e6dc34f5 --- /dev/null +++ b/packages/top-app-bar/Section.tsx @@ -0,0 +1,55 @@ +// 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 * as React from 'react'; +import * as classnames from 'classnames'; +import {cssClasses} from './constants'; + +export interface SectionProps extends React.HTMLProps { + align?: 'start' | 'end'; + className?: string; + tag?: string; +}; + +const Section: (props: SectionProps) => + React.ReactElement = ({ + /* eslint-disable react/prop-types */ + align, + className, + children, + tag: Tag = 'section', + ...otherProps + /* eslint-enable react/prop-types */ + }) => ( + // @ts-ignore https://github.com/Microsoft/TypeScript/issues/28892 + + {children} + + ); + +export default Section; diff --git a/packages/top-app-bar/Title.tsx b/packages/top-app-bar/Title.tsx new file mode 100644 index 000000000..1d8885404 --- /dev/null +++ b/packages/top-app-bar/Title.tsx @@ -0,0 +1,47 @@ +// 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 * as React from 'react'; +import * as classnames from 'classnames'; +import {cssClasses} from './constants'; + +export interface TitleProps extends React.HTMLProps { + className?: string; + tag?: string; +}; + +const Title: (props: TitleProps) => + React.ReactElement = ({ + /* eslint-disable react/prop-types */ + children, + className, + tag: Tag = 'span', + ...otherProps + /* eslint-enable react/prop-types */ + }) => ( + // @ts-ignore https://github.com/Microsoft/TypeScript/issues/28892 + + {children} + + ); + +export default Title; diff --git a/packages/top-app-bar/constants.ts b/packages/top-app-bar/constants.ts new file mode 100644 index 000000000..2c59f0fe1 --- /dev/null +++ b/packages/top-app-bar/constants.ts @@ -0,0 +1,39 @@ +// 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 +const BASE = 'mdc-top-app-bar'; +const SECTION = `${BASE}__section`; +const cssClasses = { + BASE, + ROW: `${BASE}__row`, + SECTION, + SECTION_START: `${SECTION}--align-start`, + SECTION_END: `${SECTION}--align-end`, + FIXED: `${BASE}--fixed`, + SHORT: `${BASE}--short`, + SHORT_COLLAPSED: `${BASE}--short-collapsed`, + PROMINENT: `${BASE}--prominent`, + DENSE: `${BASE}--dense`, + TITLE: `${BASE}__title`, + ACTION_ITEM: `${BASE}__action-item`, + NAV_ICON: `${BASE}__navigation-icon`, +}; + +export {cssClasses}; diff --git a/packages/top-app-bar/index.tsx b/packages/top-app-bar/index.tsx index 1002624aa..887f0c8a9 100644 --- a/packages/top-app-bar/index.tsx +++ b/packages/top-app-bar/index.tsx @@ -23,6 +23,11 @@ import * as React from 'react'; import * as classnames from 'classnames'; import TopAppBarFixedAdjust, {TopAppbarFixedAdjustProps} from './FixedAdjust'; +import TopAppBarSection from './Section'; +import TopAppBarRow from './Row'; +import TopAppBarTitle from './Title'; +import TopAppBarIcon from './Icon'; +import {cssClasses} from './constants'; import { MDCFixedTopAppBarFoundation, MDCTopAppBarFoundation, @@ -33,29 +38,37 @@ import { export type MDCTopAppBarFoundationTypes = MDCFixedTopAppBarFoundation | MDCTopAppBarFoundation | MDCShortTopAppBarFoundation; -export interface TopAppBarProps { +/** + * @deprecated since 0.11.0. Will be deleted in 0.13.0 + */ +interface DeprecatedProps { actionItems?: React.ReactElement[]; + navigationIcon?: React.ReactElement; + title?: string; +} + +export interface TopAppBarProps extends React.HTMLProps, DeprecatedProps { className?: string; dense?: boolean; fixed?: boolean; - navigationIcon?: React.ReactElement; prominent?: boolean; short?: boolean; shortCollapsed?: boolean; style?: React.CSSProperties; - title?: React.ReactNode; + scrollTarget?: React.RefObject; + tag?: string; } interface TopAppBarState { classList: Set; style: React.CSSProperties; + scrollTarget?: React.RefObject; } -type Props = TopAppBarProps & React.HTMLProps; export type VariantType = 'dense' | 'fixed' | 'prominent' | 'short' | 'shortCollapsed'; -export default class TopAppBar extends React.Component< - Props, +class TopAppBar extends React.Component< + TopAppBarProps, TopAppBarState > { topAppBarElement: React.RefObject = React.createRef(); @@ -66,7 +79,7 @@ export default class TopAppBar extends React.Component< style: {}, }; - static defaultProps: Partial = { + static defaultProps: Partial> = { className: '', dense: false, fixed: false, @@ -74,7 +87,7 @@ export default class TopAppBar extends React.Component< short: false, shortCollapsed: false, style: {}, - title: '', + tag: 'header', }; get classes() { @@ -87,30 +100,43 @@ export default class TopAppBar extends React.Component< short, shortCollapsed, } = this.props; - return classnames('mdc-top-app-bar', Array.from(classList), className, { - 'mdc-top-app-bar--fixed': fixed, - 'mdc-top-app-bar--short': shortCollapsed || short, - 'mdc-top-app-bar--short-collapsed': shortCollapsed, - 'mdc-top-app-bar--prominent': prominent, - 'mdc-top-app-bar--dense': dense, + return classnames(cssClasses.BASE, Array.from(classList), className, { + [cssClasses.FIXED]: fixed, + [cssClasses.SHORT]: shortCollapsed || short, + [cssClasses.SHORT_COLLAPSED]: shortCollapsed, + [cssClasses.PROMINENT]: prominent, + [cssClasses.DENSE]: dense, }); } componentDidMount() { this.initializeFoundation(); + if (this.props.scrollTarget) { + this.setState({scrollTarget: this.props.scrollTarget}); + } } componentWillUnmount() { this.foundation.destroy(); } - componentDidUpdate(prevProps: Props) { + componentDidUpdate(prevProps: TopAppBarProps, prevState: TopAppBarState) { const foundationChanged = ['short', 'shortCollapsed', 'fixed'].some( (variant: string) => this.props[variant as VariantType] !== prevProps[variant as VariantType] ); if (foundationChanged) { - this.initializeFoundation(); + // foundation.destroy() does not remove old variant className(s) + this.setState({classList: new Set()}, this.initializeFoundation); + } + + if (prevProps.scrollTarget !== this.props.scrollTarget) { + this.foundation.destroyScrollHandler(); + this.setState({scrollTarget: this.props.scrollTarget}); + } + + if (prevState.scrollTarget !== this.state.scrollTarget) { + this.foundation.initScrollHandler(); } } @@ -126,10 +152,14 @@ export default class TopAppBar extends React.Component< } else { this.foundation = new MDCTopAppBarFoundation(this.adapter); } + this.foundation.init(); }; - addClassesToElement(classes: string, element: React.ReactElement) { + /** + * @deprecated since 0.11.0. Will be deleted in 0.13.0 + */ + addClassesToElement/* istanbul ignore next */(classes: string, element: React.ReactElement) { const updatedProps = { className: classnames(classes, element.props.className), }; @@ -142,7 +172,6 @@ export default class TopAppBar extends React.Component< }; get adapter() { - const {actionItems} = this.props; return { addClass: (className: string) => this.setState({classList: this.state.classList.add(className)}), @@ -163,47 +192,97 @@ export default class TopAppBar extends React.Component< } return 0; }, - registerScrollHandler: (handler: EventListener) => - window.addEventListener('scroll', handler), - deregisterScrollHandler: (handler: EventListener) => - window.removeEventListener('scroll', handler), - getViewportScrollY: () => window.pageYOffset, - getTotalActionItems: () => !!(actionItems && actionItems.length), + registerScrollHandler: (handler: EventListener) => { + if (this.state.scrollTarget && this.state.scrollTarget.current) { + this.state.scrollTarget.current.addEventListener('scroll', handler); + } else { + window.addEventListener('scroll', handler); + } + }, + deregisterScrollHandler: (handler: EventListener) => { + if (this.state.scrollTarget && this.state.scrollTarget.current) { + this.state.scrollTarget.current.removeEventListener('scroll', handler); + } else { + window.removeEventListener('scroll', handler); + } + }, + getViewportScrollY: () => { + return (this.state.scrollTarget && this.state.scrollTarget.current) + ? this.state.scrollTarget.current.offsetTop + : window.pageYOffset; + }, + getTotalActionItems: () => { + if (this.topAppBarElement && this.topAppBarElement.current) { + const actionItems = this.topAppBarElement.current.querySelectorAll( + `.${cssClasses.ACTION_ITEM}`); + return actionItems.length; + } + return 0; + }, }; } render() { const { /* eslint-disable no-unused-vars */ - actionItems, + children, className, dense, fixed, - title, - navigationIcon, short, shortCollapsed, prominent, - /* eslint-enable no-unused-vars */ + scrollTarget, + tag: Tag, + actionItems, + navigationIcon, + title, ...otherProps + /* eslint-enable no-unused-vars */ } = this.props; + /** + * @deprecated since 0.11.0. Will be deleted in 0.13.0 + */ + /* istanbul ignore if */ + if (actionItems || navigationIcon || title) { + // TODO(mgr34): remove all deprecated statements and istanbul ignore's for v0.13.0 + const warning = 'actionItems, navigationIcon, and title are deprecated ' + + 'since v0.11.0 and will be removed in v0.13.0. Please refer to ' + + 'https://github.com/material-components/material-components-web-react' + + '/blob/master/packages/top-app-bar/README.md'; + console.warn(warning); + return ( + // @ts-ignore Tag does not have any construct https://github.com/Microsoft/TypeScript/issues/28892 + +
+ {this.renderTitleAndNavigationSection()} + {this.renderActionItems()} +
+
+ ); + } + return ( -
-
- {this.renderTitleAndNavigationSection()} - {this.renderActionItems()} -
-
+ >{children} ); } - renderTitleAndNavigationSection() { + /** + * @deprecated since 0.11.0. Will be deleted in 0.13.0 + */ + renderTitleAndNavigationSection/* istanbul ignore next */() { const {title} = this.props; const classes = 'mdc-top-app-bar__section mdc-top-app-bar__section--align-start'; @@ -215,7 +294,10 @@ export default class TopAppBar extends React.Component< ); } - renderNavigationIcon() { + /** + * @deprecated since 0.11.0. Will be deleted in 0.13.0 + */ + renderNavigationIcon/* istanbul ignore next */() { const {navigationIcon} = this.props; if (!navigationIcon) { return; @@ -226,7 +308,10 @@ export default class TopAppBar extends React.Component< ); } - renderActionItems() { + /** + * @deprecated since 0.11.0. Will be deleted in 0.13.0 + */ + renderActionItems/* istanbul ignore next */() { const {actionItems} = this.props; if (!actionItems) { return; @@ -248,4 +333,13 @@ export default class TopAppBar extends React.Component< } } -export {TopAppBarFixedAdjust, TopAppbarFixedAdjustProps}; +export default TopAppBar; + +export { + TopAppBarFixedAdjust, + TopAppbarFixedAdjustProps, + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +}; diff --git a/packages/top-app-bar/package.json b/packages/top-app-bar/package.json index 79e6e248f..fbe454132 100644 --- a/packages/top-app-bar/package.json +++ b/packages/top-app-bar/package.json @@ -13,7 +13,8 @@ "topappbar" ], "dependencies": { - "@material/top-app-bar": "^0.41.0", + "@material/react-ripple": "^0.9.0", + "@material/top-app-bar": "^0.44.0", "classnames": "^2.2.5", "react": "^16.4.2" }, diff --git a/test/screenshot/drawer/DrawerAboveTopAppBar.tsx b/test/screenshot/drawer/DrawerAboveTopAppBar.tsx index 29838644c..7d6601805 100644 --- a/test/screenshot/drawer/DrawerAboveTopAppBar.tsx +++ b/test/screenshot/drawer/DrawerAboveTopAppBar.tsx @@ -1,7 +1,11 @@ import * as React from 'react'; import '../../../packages/drawer/index.scss'; import './index.scss'; -import TopAppBar from '../../../packages/top-app-bar/index'; +import TopAppBar, { + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar/index'; import Drawer, { DrawerAppContent, DrawerHeader, @@ -54,7 +58,14 @@ const DrawerScreenshotTest: React.FunctionComponent = - + + + + {renderNavigationIcon()} + {title} + + +
{[0, 1, 2, 3, 4, 5].map(renderLoremIpsum)}
diff --git a/test/screenshot/drawer/DrawerBelowTopAppBar.tsx b/test/screenshot/drawer/DrawerBelowTopAppBar.tsx index 1331eb25c..5146fb4e7 100644 --- a/test/screenshot/drawer/DrawerBelowTopAppBar.tsx +++ b/test/screenshot/drawer/DrawerBelowTopAppBar.tsx @@ -2,6 +2,9 @@ import * as React from 'react'; import '../../../packages/drawer/index.scss'; import './index.scss'; import TopAppBar, { + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, TopAppBarFixedAdjust, } from '../../../packages/top-app-bar/index'; import Drawer, { @@ -37,8 +40,14 @@ const DrawerScreenshotTest: React.FunctionComponent = }) => { return ( - - + + + + {renderNavigationIcon()} + {title} + + + diff --git a/test/screenshot/drawer/DrawerTest.tsx b/test/screenshot/drawer/DrawerTest.tsx index ef5ac70c0..2b2085528 100644 --- a/test/screenshot/drawer/DrawerTest.tsx +++ b/test/screenshot/drawer/DrawerTest.tsx @@ -55,10 +55,12 @@ class DrawerScreenshotTest extends React.Component< if (this.props.hideNavigationIcon) return; // eslint-disable-line react/prop-types return ( this.setState({open: !this.state.open})} onKeyDown={() => this.setState({open: !this.state.open})} icon='menu' + hasRipple /> ); }; diff --git a/test/screenshot/golden.json b/test/screenshot/golden.json index 383c5e6e0..69529c0ae 100644 --- a/test/screenshot/golden.json +++ b/test/screenshot/golden.json @@ -30,14 +30,15 @@ "text-field/refTest": "8a9597a622fb5735dc4f29c918338940d2a28122e543486c02543151bd77751c", "top-app-bar/fixed": "7a2dd6318d62ac2eabd66f1b28100db7c15840ccb753660065fa9524db6435d6", "top-app-bar/prominent": "2506ed2dd5f370c7bab69315d2daebd58b443d2b9e32bbaec762e40a8736309b", - "top-app-bar/short": "4d9b86955a026da5bfebeb13389022b9360e779dbd6c23c8761e7bb78e14d6fd", - "top-app-bar/shortCollapsed": "d53239e0f8cffd81010e30c5dc9563cc323b3945328dcc4460486e5d0d1131db", + "top-app-bar/short": "90dba9623f16d58cfc4a24b2a3ab652c7e0cc6d5ccfd030566a170a55d6bce0c", + "top-app-bar/shortCollapsed": "a711abff27747da6c91a09525a2a17ae889ce1a0ff81d513b0b3b067b66abac3", "top-app-bar/standard": "7a2dd6318d62ac2eabd66f1b28100db7c15840ccb753660065fa9524db6435d6", "top-app-bar/standardNoActionItems": "6d361edb994cafcc6ac720336d12ee7d7114745993e16abd6e6b00f078424ff2", "top-app-bar/standardWithNavigationIconElement": "95afd559c35dede805e4d4b51ad1fabd82b4117c358a8679e3166b88e059bf68", - "top-app-bar/prominentToShortCollapsed": "ce23a6060af19f93ee0255e45ebe51c7243ff12c14b2c09d070501ea7806e316", + "top-app-bar/prominentToShortCollapsed": "203d617f5358eaa406fafbb41b3adce69bd4c3939791dbf85738b8e49e93dfa6", "top-app-bar/dense": "e273e6c777f168248f5757c1f00a74206f4cce51c484d91cc7d36dc16de7d0de", "top-app-bar/prominentDense": "cc8af934f9187ffd8f250834ef7c73e5c53c5ace10126bb855f74878ba125149", + "top-app-bar/twoRows": "a7c025c8c04123d377f53a3e3e888eb4de78439b9dcd76c306e67712934f9ed1", "dialog/alert": "e0c9b5fd4468c5fff9902b4e8e79c488b0fe8300b08a9620f32ed380b5c2af79", "dialog/simple": "2b72930b7711c258d4fead83055ec02b10edebda722901e7ed3ecb7365827dd1", "dialog/confirmation": "0d548366130de2aeb85d993a0e9d2030840db0ba10bc49b2721ae05cbd42b058", diff --git a/test/screenshot/top-app-bar/actionItems.ts b/test/screenshot/top-app-bar/actionItems.ts new file mode 100644 index 000000000..3e9695400 --- /dev/null +++ b/test/screenshot/top-app-bar/actionItems.ts @@ -0,0 +1,10 @@ +export interface actionItem { + icon: string; + label: string; +} + +const actionItems: actionItem[] = [ + {icon: 'bookmark', label: 'Add to bookmarks'}, +]; + +export {actionItems}; diff --git a/test/screenshot/top-app-bar/dense.tsx b/test/screenshot/top-app-bar/dense.tsx index 9d7421335..7f39935d0 100644 --- a/test/screenshot/top-app-bar/dense.tsx +++ b/test/screenshot/top-app-bar/dense.tsx @@ -1,22 +1,33 @@ import * as React from 'react'; -import TopAppBar from '../../../packages/top-app-bar'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar'; import MaterialIcon from '../../../packages/material-icon'; import MainTopAppBarContent from './mainContent'; +import {actionItems} from './actionItems'; +import {mapActionItem} from './index'; + +const title: string = 'Miami, FL'; const TopAppBarDenseScreenshotTest: React.FunctionComponent = () => { return (
- console.log('dense click')} - /> - } - actionItems={[]} - /> + + + + + + + {title} + + + {actionItems.map(mapActionItem)} + + +
); diff --git a/test/screenshot/top-app-bar/fixed.tsx b/test/screenshot/top-app-bar/fixed.tsx index daec6d033..2d0f87b98 100644 --- a/test/screenshot/top-app-bar/fixed.tsx +++ b/test/screenshot/top-app-bar/fixed.tsx @@ -1,22 +1,32 @@ import * as React from 'react'; -import TopAppBar from '../../../packages/top-app-bar'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar'; import MaterialIcon from '../../../packages/material-icon'; import MainTopAppBarContent from './mainContent'; +import {actionItems} from './actionItems'; +import {mapActionItem} from './index'; +const title: string = 'Miami, FL'; const TopAppBarFixedScreenshotTest: React.FunctionComponent = () => { return (
- console.log('fixed click')} - /> - } - actionItems={[]} - /> + + + + + + + {title} + + + {actionItems.map(mapActionItem)} + + +
); diff --git a/test/screenshot/top-app-bar/index.scss b/test/screenshot/top-app-bar/index.scss index a477b6816..ce560b798 100644 --- a/test/screenshot/top-app-bar/index.scss +++ b/test/screenshot/top-app-bar/index.scss @@ -1,3 +1,17 @@ .top-app-bar-container { height: 200vh; } + +.top-app-bar--tabs { + .mdc-tab { + .mdc-tab-indicator > .mdc-tab-indicator__content--underline { + background-color: var(--mdc-theme-on-primary, #fff); + } + span.mdc-tab__text-label { color: var(--mdc-theme-on-primary, #fff); } + div.mdc-tab__ripple { + &:before,&:after { + background-color: var(--mdc-theme-on-primary, #fff); + } + } + } +} diff --git a/test/screenshot/top-app-bar/index.tsx b/test/screenshot/top-app-bar/index.tsx index a06948f71..a15e3d35f 100644 --- a/test/screenshot/top-app-bar/index.tsx +++ b/test/screenshot/top-app-bar/index.tsx @@ -1,10 +1,18 @@ import * as React from 'react'; import {Link} from 'react-router-dom'; import topAppBarVariants from './variants'; +import {TopAppBarIcon} from '../../../packages/top-app-bar'; +import MaterialIcon from '../../../packages/material-icon'; import '../../../packages/top-app-bar/index.scss'; import '../../../packages/material-icon/index.scss'; import './index.scss'; +import {actionItem} from './actionItems'; +const mapActionItem = ({label, icon}: actionItem) => ( + + + +); const TopAppBarHomePage: React.FunctionComponent = () => { return (
@@ -18,3 +26,4 @@ const TopAppBarHomePage: React.FunctionComponent = () => { }; export default TopAppBarHomePage; +export {mapActionItem}; diff --git a/test/screenshot/top-app-bar/prominent.tsx b/test/screenshot/top-app-bar/prominent.tsx index 88b791ece..fd171caad 100644 --- a/test/screenshot/top-app-bar/prominent.tsx +++ b/test/screenshot/top-app-bar/prominent.tsx @@ -1,22 +1,33 @@ import * as React from 'react'; -import TopAppBar from '../../../packages/top-app-bar'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar'; import MaterialIcon from '../../../packages/material-icon'; import MainTopAppBarContent from './mainContent'; +import {mapActionItem} from './index'; +import {actionItems} from './actionItems'; + +const title: string = 'Miami, FL'; const TopAppBarProminentScreenshotTest: React.FunctionComponent = () => { return (
- console.log('prominent click')} - /> - } - actionItems={[]} - /> + + + + + + + {title} + + + {actionItems.map(mapActionItem)} + + +
); diff --git a/test/screenshot/top-app-bar/prominentDense.tsx b/test/screenshot/top-app-bar/prominentDense.tsx index 1c4f9afdb..80f2e244a 100644 --- a/test/screenshot/top-app-bar/prominentDense.tsx +++ b/test/screenshot/top-app-bar/prominentDense.tsx @@ -1,23 +1,32 @@ import * as React from 'react'; -import TopAppBar from '../../../packages/top-app-bar'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar'; import MaterialIcon from '../../../packages/material-icon'; import MainTopAppBarContent from './mainContent'; +import {mapActionItem} from './index'; +import {actionItems} from './actionItems'; +const title: string = 'Miami, FL'; const TopAppBarProminentDenseScreenshotTest: React.FunctionComponent = () => { return (
- console.log('prominent dense click')} - /> - } - actionItems={[]} - /> + + + + + + + {title} + + + {actionItems.map(mapActionItem)} + + +
); diff --git a/test/screenshot/top-app-bar/prominentToShortCollapsed.tsx b/test/screenshot/top-app-bar/prominentToShortCollapsed.tsx index ba247a497..3865cf3b9 100644 --- a/test/screenshot/top-app-bar/prominentToShortCollapsed.tsx +++ b/test/screenshot/top-app-bar/prominentToShortCollapsed.tsx @@ -1,12 +1,20 @@ import * as React from 'react'; -import TopAppBar from '../../../packages/top-app-bar'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar'; import MaterialIcon from '../../../packages/material-icon'; import MainTopAppBarContent from './mainContent'; +import {mapActionItem} from './index'; +import {actionItems} from './actionItems'; interface TopAppBarProminentToShortCollapsedScreenshotTestState { isPhone: boolean; } +const title: string = 'Miami, FL'; class TopAppBarProminentToShortCollapsedScreenshotTest extends React.Component< {}, TopAppBarProminentToShortCollapsedScreenshotTestState @@ -33,30 +41,27 @@ class TopAppBarProminentToShortCollapsedScreenshotTest extends React.Component< }; render() { - if (this.state.isPhone) { - return ( -
- console.log('click')} /> - } - /> - -
- ); - } - + const {isPhone} = this.state; return (
- console.log('click')} /> - } - /> - + + + + + + + {title} + + + {isPhone + ? ( + + ) + : actionItems.map(mapActionItem)} + + + +
); } diff --git a/test/screenshot/top-app-bar/short.tsx b/test/screenshot/top-app-bar/short.tsx index b932eb9fb..670a8ac75 100644 --- a/test/screenshot/top-app-bar/short.tsx +++ b/test/screenshot/top-app-bar/short.tsx @@ -1,22 +1,32 @@ import * as React from 'react'; -import TopAppBar from '../../../packages/top-app-bar'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar'; import MaterialIcon from '../../../packages/material-icon'; import MainTopAppBarContent from './mainContent'; +const title: string = 'Miami, FL'; const TopAppBarShortScreenshotTest: React.FunctionComponent = () => { return (
- console.log('short click')} - /> - } - actionItems={[]} - /> + + + + + + + {title} + + + + + + + +
); diff --git a/test/screenshot/top-app-bar/shortCollapsed.tsx b/test/screenshot/top-app-bar/shortCollapsed.tsx index 2ba375ea0..43192c5fe 100644 --- a/test/screenshot/top-app-bar/shortCollapsed.tsx +++ b/test/screenshot/top-app-bar/shortCollapsed.tsx @@ -1,20 +1,32 @@ import * as React from 'react'; -import TopAppBar from '../../../packages/top-app-bar'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar'; import MaterialIcon from '../../../packages/material-icon'; import MainTopAppBarContent from './mainContent'; +const title: string = 'Miami, FL'; const TopAppBarShortCollapsedScreenshotTest: React.FunctionComponent = () => { return (
- console.log('shortCollapsed click')} - /> - } - /> + + + + + + + {title} + + + + + + + +
); diff --git a/test/screenshot/top-app-bar/standard.tsx b/test/screenshot/top-app-bar/standard.tsx index 5054a0db4..83869fb1e 100644 --- a/test/screenshot/top-app-bar/standard.tsx +++ b/test/screenshot/top-app-bar/standard.tsx @@ -1,21 +1,32 @@ import * as React from 'react'; -import TopAppBar from '../../../packages/top-app-bar'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar'; import MaterialIcon from '../../../packages/material-icon'; import MainTopAppBarContent from './mainContent'; +import {actionItems} from './actionItems'; +import {mapActionItem} from './index'; +const title: string = 'Miami, FL'; const TopAppBarStandardScreenshotTest: React.FunctionComponent = () => { return (
- console.log('standard click')} - /> - } - actionItems={[]} - /> + + + + + + + {title} + + + {actionItems.map(mapActionItem)} + + +
); diff --git a/test/screenshot/top-app-bar/standardNoActionItems.tsx b/test/screenshot/top-app-bar/standardNoActionItems.tsx index 4392e3514..0396aa172 100644 --- a/test/screenshot/top-app-bar/standardNoActionItems.tsx +++ b/test/screenshot/top-app-bar/standardNoActionItems.tsx @@ -1,17 +1,27 @@ import * as React from 'react'; -import TopAppBar from '../../../packages/top-app-bar'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar'; import MaterialIcon from '../../../packages/material-icon'; import MainTopAppBarContent from './mainContent'; +const title: string = 'Miami, FL'; const TopAppBarStandardNoActionItemsScreenshotTest: React.FunctionComponent = () => { return (
- console.log('click')} /> - } - /> + + + + + + + {title} + + +
); diff --git a/test/screenshot/top-app-bar/standardWithNavigationIconElement.tsx b/test/screenshot/top-app-bar/standardWithNavigationIconElement.tsx index 47f6d20ea..55c01d33f 100644 --- a/test/screenshot/top-app-bar/standardWithNavigationIconElement.tsx +++ b/test/screenshot/top-app-bar/standardWithNavigationIconElement.tsx @@ -1,8 +1,14 @@ import * as React from 'react'; -import TopAppBar from '../../../packages/top-app-bar'; -import MaterialIcon from '../../../packages/material-icon'; -import {withRipple, InjectedProps} from '../../../packages/ripple'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar'; import MainTopAppBarContent from './mainContent'; +import {mapActionItem} from './index'; +import {actionItems} from './actionItems'; +import {withRipple, InjectedProps} from '../../../packages/ripple'; interface RippleProps extends InjectedProps { hasRipple?: boolean; @@ -43,16 +49,23 @@ const NavigationIcon: React.FunctionComponent = ({ ); const NavigationIconWithRipple = withRipple(NavigationIcon); - +const title: string = 'Miami, FL'; const TopAppBarStandardWithNavigationIconElementScreenshotTest: React.FunctionComponent = () => { return (
- } - actionItems={[]} - /> + + + + + + + {title} + + + {actionItems.map(mapActionItem)} + + +
); diff --git a/test/screenshot/top-app-bar/twoRows.tsx b/test/screenshot/top-app-bar/twoRows.tsx new file mode 100644 index 000000000..e63355410 --- /dev/null +++ b/test/screenshot/top-app-bar/twoRows.tsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar'; +import Tab from '../../../packages/tab'; +import TabBar from '../../../packages/tab-bar'; +import MaterialIcon from '../../../packages/material-icon'; +import MainTopAppBarContent from './mainContent'; +import {actionItems} from './actionItems'; +import {mapActionItem} from './index'; + +const title: string = 'Miami, FL'; +class TopAppBarTwoRows extends React.Component<{}, {activeIndex: number}> { + state = {activeIndex: 0}; + + handleActiveIndexUpdate = (activeIndex: number) => this.setState({activeIndex}); + + render() { + return ( +
+ + + + + + + {title} + + + {actionItems.map(mapActionItem)} + + + + + + + One + + + Two + + + Three + + + + + + +
+ ); + } +}; + +export default TopAppBarTwoRows; diff --git a/test/screenshot/top-app-bar/variants.tsx b/test/screenshot/top-app-bar/variants.tsx index f2776927b..d58404fec 100644 --- a/test/screenshot/top-app-bar/variants.tsx +++ b/test/screenshot/top-app-bar/variants.tsx @@ -9,4 +9,6 @@ export default [ 'standardNoActionItems', 'standardWithNavigationIconElement', 'prominentToShortCollapsed', + 'twoRows', ]; + diff --git a/test/unit/top-app-bar/Icon.test.tsx b/test/unit/top-app-bar/Icon.test.tsx new file mode 100644 index 000000000..9e276bb5b --- /dev/null +++ b/test/unit/top-app-bar/Icon.test.tsx @@ -0,0 +1,144 @@ +import * as React from 'react'; +import {assert} from 'chai'; +import {shallow} from 'enzyme'; +import {TopAppBarIcon} from '../../../packages/top-app-bar/index'; +import MaterialIcon from '../../../packages/material-icon/index'; +import {cssClasses} from '../../../packages/top-app-bar/constants'; +import {withRipple, InjectedProps} from '../../../packages/ripple/index'; + +suite('TopAppBarIcon'); + +interface RippleProps extends InjectedProps { + hasRipple?: boolean; + className: string; +} + +type DivRippleProps = RippleProps & React.HTMLProps; +type ActionItemRippleProps = RippleProps & React.HTMLProps; +type SVGRippleProps = RippleProps & React.HTMLProps; + +const NavigationIcon: React.FunctionComponent = ({ + /* eslint-disable react/prop-types */ + initRipple, + hasRipple, + unbounded, + className, + /* eslint-enable react/prop-types */ + ...otherProps +}) => ( +
+); + +const RippledNavigationIcon = withRipple, HTMLDivElement>(NavigationIcon); + +const ActionItem: React.FunctionComponent = ({ + /* eslint-disable react/prop-types */ + initRipple, + hasRipple, + unbounded, + className, + ref, + /* eslint-enable react/prop-types */ + ...otherProps +}) => ( + +); + +const RippledActionItem = withRipple, HTMLAnchorElement>(ActionItem); + +const SVGNavigationIcon: React.FunctionComponent = ({ + /* eslint-disable react/prop-types */ + hasRipple, + initRipple, + unbounded, + /* eslint-enable react/prop-types */ + ...otherProps +}) => ( + console.log('navigation icon click')} + ref={initRipple} + {...otherProps} + width='24px' + height='24px' + xmlns='http://www.w3.org/2000/svg' + className='material-icons' + viewBox='0 0 24 24' + fill='#fff' + > + + + +); + +const RippledSVGNavigationIcon = withRipple, SVGSVGElement>(SVGNavigationIcon); + +test('props.actionItem add actionItem class', () => { + const wrapper = shallow( + + ); + assert.isTrue(wrapper.hasClass(cssClasses.ACTION_ITEM)); +}); + +test('actionItem can be rendered as a custom component', () => { + const wrapper = shallow( + + ); + assert.isTrue(wrapper.hasClass(cssClasses.ACTION_ITEM)); +}); + +test('props.navIcon add navIcon class', () => { + const wrapper = shallow( + + ); + assert.isTrue(wrapper.hasClass(cssClasses.NAV_ICON)); +}); + +test('navIcon can be rendered as a custom component', () => { + const wrapper = shallow( + + ); + assert.isTrue(wrapper.hasClass(cssClasses.NAV_ICON)); +}); + +test('navIcon can be rendered as a custom SVG component', () => { + const wrapper = shallow( + + ); + assert.isTrue(wrapper.hasClass(cssClasses.NAV_ICON)); +}); + +test('props.className adds classes', () => { + const wrapper = shallow( + + + ); + assert.isTrue(wrapper.hasClass('test-class')); + assert.isTrue(wrapper.hasClass(cssClasses.NAV_ICON)); +}); + +test('children are added correctly', () => { + const wrapper = shallow( + + menu + + ); + + assert.equal(wrapper.find('i').length, 1); +}); + diff --git a/test/unit/top-app-bar/Row.test.tsx b/test/unit/top-app-bar/Row.test.tsx new file mode 100644 index 000000000..90297fc75 --- /dev/null +++ b/test/unit/top-app-bar/Row.test.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import {assert} from 'chai'; +import {shallow} from 'enzyme'; +import {TopAppBarRow} from '../../../packages/top-app-bar/index'; +import {cssClasses} from '../../../packages/top-app-bar/constants'; + +suite('TopAppBarRow'); + + +test('renders a TopAppBarRow with default tag', () => { + const wrapper = shallow(test); + assert.equal(wrapper.type(), 'div'); +}); + +test('renders a TopAppBarRow with custom tag', () => { + const wrapper = shallow(); + assert.equal(wrapper.type(), 'section'); +}); + +test('renders a TopAppBarRow with the default className', () => { + const wrapper = shallow(); + assert.isTrue(wrapper.hasClass(cssClasses.ROW)); +}); + +test('props.className adds classes', () => { + const wrapper = shallow(); + assert.isTrue(wrapper.hasClass('test-class')); + assert.isTrue(wrapper.hasClass(cssClasses.ROW)); +}); + +test('children are added correctly', () => { + const wrapper = shallow( + +

moewkay

+ meowkay +
+ ); + + assert.equal(wrapper.find('p').length, 1); + assert.equal(wrapper.find('span').length, 1); +}); diff --git a/test/unit/top-app-bar/Section.test.tsx b/test/unit/top-app-bar/Section.test.tsx new file mode 100644 index 000000000..474a5a278 --- /dev/null +++ b/test/unit/top-app-bar/Section.test.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import {assert} from 'chai'; +import {shallow} from 'enzyme'; +import {TopAppBarSection} from '../../../packages/top-app-bar/index'; +import {cssClasses} from '../../../packages/top-app-bar/constants'; + +suite('TopAppBarSection'); + + +test('renders a TopAppBarSection with default tag', () => { + const wrapper = shallow(test); + assert.equal(wrapper.type(), 'section'); +}); + +test('renders a TopAppBarSection with custom tag', () => { + const wrapper = shallow(); + assert.equal(wrapper.type(), 'div'); +}); + +test('redners a TopAppBarSection with the default className', () => { + const wrapper = shallow(); + assert.isTrue(wrapper.hasClass(cssClasses.SECTION)); +}); + +test('props.className adds classes', () => { + const wrapper = shallow(); + assert.isTrue(wrapper.hasClass('test-class')); + assert.isTrue(wrapper.hasClass(cssClasses.SECTION)); +}); + +test('props.align start adds proper start alignment class', () => { + const wrapper = shallow(); + assert.isTrue(wrapper.hasClass(cssClasses.SECTION)); + assert.isTrue(wrapper.hasClass(cssClasses.SECTION_START)); +}); + +test('props.align end adds proper end alignment class', () => { + const wrapper = shallow(); + assert.isTrue(wrapper.hasClass(cssClasses.SECTION)); + assert.isTrue(wrapper.hasClass(cssClasses.SECTION_END)); +}); + +test('children are added correctly', () => { + const wrapper = shallow( + +

moewkay

+ meowkay +
+ ); + + assert.equal(wrapper.find('p').length, 1); + assert.equal(wrapper.find('span').length, 1); +}); diff --git a/test/unit/top-app-bar/Title.test.tsx b/test/unit/top-app-bar/Title.test.tsx new file mode 100644 index 000000000..dad754a66 --- /dev/null +++ b/test/unit/top-app-bar/Title.test.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import {assert} from 'chai'; +import {shallow} from 'enzyme'; +import {TopAppBarTitle} from '../../../packages/top-app-bar/index'; +import {cssClasses} from '../../../packages/top-app-bar/constants'; + +suite('TopAppBarTitle'); + + +test('renders a TopAppBarTitle with default tag', () => { + const wrapper = shallow(test); + assert.equal(wrapper.type(), 'span'); +}); + +test('renders a TopAppBarTitle with custom tag', () => { + const wrapper = shallow(); + assert.equal(wrapper.type(), 'h2'); +}); + +test('redners a TopAppBarTitle with the default className', () => { + const wrapper = shallow(); + assert.isTrue(wrapper.hasClass(cssClasses.TITLE)); +}); + +test('props.className adds classes', () => { + const wrapper = shallow(); + assert.isTrue(wrapper.hasClass('test-class')); + assert.isTrue(wrapper.hasClass(cssClasses.TITLE)); +}); + +test('children are added correctly', () => { + const wrapper = shallow( + +

moewkay

+ meowkay +
+ ); + + assert.equal(wrapper.find('p').length, 1); + assert.equal(wrapper.find('strong').length, 1); +}); diff --git a/test/unit/top-app-bar/index.test.tsx b/test/unit/top-app-bar/index.test.tsx index 1c15d940d..2e7c6947d 100644 --- a/test/unit/top-app-bar/index.test.tsx +++ b/test/unit/top-app-bar/index.test.tsx @@ -2,38 +2,60 @@ import * as React from 'react'; import {assert} from 'chai'; import {mount, shallow} from 'enzyme'; import * as td from 'testdouble'; -import TopAppBar from '../../../packages/top-app-bar/index'; +import TopAppBar, { + TopAppBarIcon, + TopAppBarRow, + TopAppBarSection, + TopAppBarTitle, +} from '../../../packages/top-app-bar/index'; import {withRipple, InjectedProps} from '../../../packages/ripple/index'; import {coerceForTesting} from '../helpers/types'; suite('TopAppBar'); +interface ScrollState { + scrollRef: React.RefObject; + withRef: boolean; +} +interface ScrollProps { + withRef: boolean; +} + +class TopAppBarWithScroll extends React.Component { + state: ScrollState = { + scrollRef: React.createRef(), + withRef: this.props.withRef, + }; + + static defaultProps: ScrollProps = { + withRef: false, + }; + + + withRef = () => this.setState({withRef: !this.state.withRef}) + + render() { + const {withRef, scrollRef} = this.state; + return ( +
+ + + Scroll + + +
Scroll Target
+
+ ); + } +} + interface RippleProps extends InjectedProps { hasRipple?: boolean; className: string; } -type DivRippleProps = RippleProps & React.HTMLProps; type ActionItemRippleProps = RippleProps & React.HTMLProps; -const NavigationIcon: React.FunctionComponent = ({ - /* eslint-disable react/prop-types */ - initRipple, - hasRipple, - unbounded, - className, - /* eslint-enable react/prop-types */ - ...otherProps -}) => ( -
-); - -const RippledNavigationIcon = withRipple, HTMLDivElement>(NavigationIcon); - const ActionItem: React.FunctionComponent = ({ /* eslint-disable react/prop-types */ initRipple, @@ -54,6 +76,16 @@ const ActionItem: React.FunctionComponent = ({ const RippledActionItem = withRipple, HTMLAnchorElement>(ActionItem); +test('renders a TopAppBar with default tag', () => { + const wrapper = shallow(); + assert.equal(wrapper.type(), 'header'); +}); + +test('renders a TopAppBar with custom tag', () => { + const wrapper = shallow(); + assert.equal(wrapper.type(), 'div'); +}); + test('classNames adds classes', () => { const wrapper = shallow(); assert.isTrue(wrapper.hasClass('test-class-name')); @@ -98,48 +130,49 @@ test('has correct prominent dense class', () => { assert.isTrue(wrapper.hasClass('mdc-top-app-bar--prominent')); }); -test('navigationIcon is rendered with navigation icon class', () => { - const wrapper = mount( - } /> - ); - assert.isTrue( - wrapper - .find('.test-top-app-bar-nav-icon') - .hasClass('mdc-top-app-bar__navigation-icon') - ); +test('top app bar style should be set by state', () => { + const wrapper = mount(); + wrapper.setState({style: {color: 'blue'}}); + assert.equal(coerceForTesting(wrapper.getDOMNode()).style.color, 'blue'); }); -test('navigationIcon is rendered as custom component that accepts a className prop', () => { - const wrapper = mount( - } /> - ); - const navigationIcon = wrapper.find(RippledNavigationIcon); - assert.isTrue(navigationIcon.hasClass('mdc-top-app-bar__navigation-icon')); -}); +test('#componetDIdMount will set state scrollTarget if prop.scrollTarget exists', () => { + const wrapper = mount(); + const topAppBar: TopAppBar = coerceForTesting(wrapper.find('TopAppBar').instance()); -test('actionItems are rendered with action item class', () => { - const wrapper = mount( - ]} /> - ); - assert.isTrue( - wrapper.find('.test-action-icon-1').hasClass('mdc-top-app-bar__action-item') - ); + assert.isDefined(topAppBar.state.scrollTarget); + assert.strictEqual(topAppBar.state.scrollTarget, wrapper.state().scrollRef); }); -test('actionItems are rendered as a custom component that accepts a className prop', () => { - const wrapper = mount( - ]} /> - ); - const actionItem = wrapper.find(RippledActionItem); - assert.isTrue(actionItem.hasClass('mdc-top-app-bar__action-item')); +test('Updating props.scrollTarget will set state scrollTarget', () => { + const wrapper = mount(); + const topAppBar: TopAppBar = coerceForTesting(wrapper.find('TopAppBar').instance()); + assert.isUndefined(topAppBar.state.scrollTarget); + wrapper.instance().withRef(); + + assert.strictEqual(topAppBar.state.scrollTarget, wrapper.state().scrollRef); }); -test('top app bar style should be set by state', () => { - const wrapper = mount(); - wrapper.setState({style: {color: 'blue'}}); - assert.equal(coerceForTesting(wrapper.getDOMNode()).style.color, 'blue'); + +test('Updating scrollTarget prop will call foundation method destroyScrollHandler', () => { + const wrapper = mount(); + const topAppBar: TopAppBar = coerceForTesting(wrapper.find('TopAppBar').instance()); + const foundation = topAppBar.foundation; + foundation.destroyScrollHandler = td.func(); + wrapper.instance().withRef(); + + td.verify(foundation.destroyScrollHandler(), {times: 1}); }); +test('Updating scrollTarget prop will call foundation method initScrollHandler', () => { + const wrapper = mount(); + const topAppBar: TopAppBar = coerceForTesting(wrapper.find('TopAppBar').instance()); + const foundation = topAppBar.foundation; + foundation.initScrollHandler = td.func(); + wrapper.instance().withRef(); + + td.verify(foundation.initScrollHandler(), {times: 1}); +}); test('#adapter.addClass adds a class to state', () => { const wrapper = shallow(); wrapper.instance().adapter.addClass('test-class-1'); @@ -168,9 +201,22 @@ test('#adapter.registerScrollHandler triggers handler on window scroll', () => { window.dispatchEvent(event); td.verify(testHandler(event), {times: 1}); }); + +test('#adapter.registerScrollHandler triggers handler on scrollTarget scroll', () => { + const wrapper = mount(); + const topAppBar: TopAppBar = coerceForTesting(wrapper.find('TopAppBar').instance()); + const testHandler = coerceForTesting(td.func()); + topAppBar.adapter.registerScrollHandler(testHandler); + const event = new Event('scroll'); + const scrollRef = wrapper.instance().state.scrollRef.current; + scrollRef!.dispatchEvent(event); + + td.verify(testHandler(event), {times: 1}); +}); + test( '#adapter.deregisterScrollHandler does not trigger handler ' + - 'after registering scroll handler', + 'after deregistering scroll handler on window', () => { const wrapper = shallow(); const testHandler = coerceForTesting(td.func()); @@ -181,23 +227,58 @@ test( td.verify(testHandler(event), {times: 0}); } ); + test( - '#adapter.getTotalActionItems returns true with one actionItem ' + 'passed', - () => { - const wrapper = shallow( - ]} /> - ); - assert.isTrue(wrapper.instance().adapter.getTotalActionItems()); - } -); -test( - '#adapter.getTotalActionItems returns false with no actionItem ' + 'passed', + '#adapter.deregisterScrollHandler does not trigger handler ' + + 'after deregistering scroll handler on scrollTarget', () => { - const wrapper = shallow(); - assert.isFalse(wrapper.instance().adapter.getTotalActionItems()); + const wrapper = mount(); + const topAppBar: TopAppBar = coerceForTesting(wrapper.find('TopAppBar').instance()); + const testHandler = coerceForTesting(td.func()); + topAppBar.adapter.registerScrollHandler(testHandler); + const event = new Event('scroll'); + topAppBar.adapter.deregisterScrollHandler(testHandler); + const scrollRef = wrapper.instance().state.scrollRef.current; + scrollRef!.dispatchEvent(event); + + td.verify(testHandler(event), {times: 0}); } ); + +test('#adapter.getTotalActionItems returns one with one actionItem passed', () => { + const wrapper = mount( + + + + + + + + ); + assert.strictEqual(wrapper.instance().adapter.getTotalActionItems(), 1); +}); + +test('#adapter.getTotalActionItems returns three with three actionItems passed', () => { + const wrapper = mount( + + + + + + + + + + ); + assert.strictEqual(wrapper.instance().adapter.getTotalActionItems(), 3); +}); + +test('#adapter.getTotalActionItems returns zero with no actionItem passed', () => { + const wrapper = shallow(); + assert.strictEqual(wrapper.instance().adapter.getTotalActionItems(), 0); +}); + test('#adapter.setStyle should update state', () => { const wrapper = shallow(); wrapper.instance().adapter.setStyle('display', 'block'); @@ -210,7 +291,12 @@ test('#adapter.getTopAppBarHeight should return clientHeight', () => { // https://github.com/airbnb/enzyme/issues/1525 document.body.append(div); const options = {attachTo: div}; - const wrapper = mount(, options); + const wrapper = mount( + + + Test + + , options); const topAppBarHeight = wrapper.instance().adapter.getTopAppBarHeight(); const realDOMHeight = wrapper.getDOMNode().clientHeight; assert.equal(topAppBarHeight, realDOMHeight); From 5174677bee2519ac3b5668616a6ac996daaa6972 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 1 Mar 2019 09:43:25 -0800 Subject: [PATCH 04/22] fix(chips): update classnames version to 2.2.6 (#702) --- packages/chips/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/chips/package.json b/packages/chips/package.json index c06cffd6f..6049472c6 100644 --- a/packages/chips/package.json +++ b/packages/chips/package.json @@ -21,7 +21,7 @@ "dependencies": { "@material/chips": "^0.41.0", "@material/react-ripple": "^0.10.0", - "classnames": "^2.2.5", + "classnames": "^2.2.6", "react": "^16.4.2" }, "publishConfig": { From f7f3d72c31da8fc74a851148f9645f3a88b3e80c Mon Sep 17 00:00:00 2001 From: Julien Blatecky Date: Fri, 1 Mar 2019 21:44:29 +0100 Subject: [PATCH 05/22] fix(ripple): Use mdc-dom.matches instead of `getMatchesProperty()` (#706) --- packages/ripple/index.tsx | 19 ++++--------------- packages/ripple/package.json | 1 + packages/tab-scroller/index.tsx | 12 ++++-------- packages/tab-scroller/package.json | 1 + 4 files changed, 10 insertions(+), 23 deletions(-) diff --git a/packages/ripple/index.tsx b/packages/ripple/index.tsx index ccb54bd67..9a0156a10 100644 --- a/packages/ripple/index.tsx +++ b/packages/ripple/index.tsx @@ -25,9 +25,8 @@ import {Subtract} from 'utility-types'; // eslint-disable-line no-unused-vars // @ts-ignore no mdc .d.ts file import {MDCRippleFoundation, MDCRippleAdapter, util} from '@material/ripple/dist/mdc.ripple'; - -const HTMLElementShim: any = typeof HTMLElement === 'undefined' ? {} : HTMLElement; -const MATCHES = util.getMatchesProperty(HTMLElementShim.prototype); +// @ts-ignore no mdc .d.ts file +import {matches} from '@material/dom/ponyfill'; export interface RippledComponentProps { unbounded?: boolean; @@ -55,10 +54,6 @@ export interface InjectedProps extends RippledComponentProps initRipple: React.Ref | ((surface: S | null, activator?: A | null) => void); } -function isElement(element: any): element is Element { - return element[MATCHES as 'matches'] !== undefined; -} - type ActivateEventTypes = React.MouseEvent | React.TouchEvent | React.KeyboardEvent | React.FocusEvent; @@ -158,16 +153,10 @@ export function withRipple < isUnbounded: () => this.props.unbounded, isSurfaceActive: () => { if (activator) { - if (isElement(activator)) { - return activator[MATCHES as 'matches'](':active'); - } - return false; + return matches(activator, ':active'); } - if (isElement(surface)) { - return surface[MATCHES as 'matches'](':active'); - } - return false; + return matches(surface, ':active'); }, isSurfaceDisabled: () => this.props.disabled, addClass: (className: string) => { diff --git a/packages/ripple/package.json b/packages/ripple/package.json index 0a90a5b45..2e15c9653 100644 --- a/packages/ripple/package.json +++ b/packages/ripple/package.json @@ -16,6 +16,7 @@ "url": "https://github.com/material-components/material-components-web-react.git" }, "dependencies": { + "@material/dom": "^0.41.0", "@material/ripple": "^0.41.0", "classnames": "^2.2.5", "react": "^16.4.2", diff --git a/packages/tab-scroller/index.tsx b/packages/tab-scroller/index.tsx index 05646d9db..b7c559b22 100644 --- a/packages/tab-scroller/index.tsx +++ b/packages/tab-scroller/index.tsx @@ -27,6 +27,8 @@ import { util, // @ts-ignore no .d.ts file } from '@material/tab-scroller/dist/mdc.tabScroller'; +// @ts-ignore no mdc .d.ts file +import {matches} from '@material/dom/ponyfill'; const convertDashToCamelCase = (propName: string) => propName.replace(/-(\w)/g, (_, v) => v.toUpperCase()); @@ -47,12 +49,6 @@ interface TabScrollerState { type ScrollerElementNames = 'scrollAreaStyleProperty' | 'scrollContentStyleProperty'; -const MATCHES = util.getMatchesProperty(HTMLElement.prototype); - -function isElement(element: any): element is Element { - return element[MATCHES as 'matches'] !== undefined; -} - export default class TabScroller extends React.Component< TabScrollerProps, TabScrollerState @@ -120,8 +116,8 @@ export default class TabScroller extends React.Component< get adapter() { return { eventTargetMatchesSelector: (evtTarget: HTMLDivElement, selector: string) => { - if (selector && isElement(evtTarget)) { - return evtTarget[MATCHES as 'matches'](selector); + if (selector) { + return matches(evtTarget, selector); } return false; }, diff --git a/packages/tab-scroller/package.json b/packages/tab-scroller/package.json index 071321b6c..39800a17b 100644 --- a/packages/tab-scroller/package.json +++ b/packages/tab-scroller/package.json @@ -18,6 +18,7 @@ "url": "https://github.com/material-components/material-components-web-react.git" }, "dependencies": { + "@material/dom": "^0.41.0", "@material/tab-scroller": "^0.41.0", "classnames": "^2.2.5", "react": "^16.4.2" From 30e2caf7e8226671521c7aec0039d9bb7d4d1be0 Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Sat, 2 Mar 2019 19:34:42 +0200 Subject: [PATCH 06/22] docs(layout-grid): typo regarding the number of rows in mobile and tablet in readme (#704) --- packages/layout-grid/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/layout-grid/README.md b/packages/layout-grid/README.md index 76dce874f..0eba7259b 100644 --- a/packages/layout-grid/README.md +++ b/packages/layout-grid/README.md @@ -107,8 +107,8 @@ className | String | Classes to be applied to the root element columns | Number (1-12) | The width of the cell on all devices desktopColumns | Number (1-12) | The width of the cell on desktop order | Number (1-12) | The order that the cell is displayed in -phoneColumns | Number (1-8) | The width of the cell on phones -tabletColumns | Number (1-4) | The width of the cell on tablets +phoneColumns | Number (1-4) | The width of the cell on phones +tabletColumns | Number (1-8) | The width of the cell on tablets tag | String | The tag type to render (default `'div'`) ## Sass Mixins From 33b29af671547e56879054cab1f2f17a70db9087 Mon Sep 17 00:00:00 2001 From: Lucas Cordeiro Date: Wed, 6 Mar 2019 15:00:16 -0300 Subject: [PATCH 07/22] docs(dialog): Fix import in Dialog readme (#712) --- packages/dialog/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dialog/README.md b/packages/dialog/README.md index 97ebd6371..5a587234f 100644 --- a/packages/dialog/README.md +++ b/packages/dialog/README.md @@ -106,7 +106,7 @@ The Simple Dialog contains a list of potential actions. It does not contain butt ```js import React, {Component} from 'react'; import Dialog, {DialogTitle, DialogContent} from '@material/react-dialog'; -import List, {ListItem, ListItemGraphic, ListItemText} from '@material/react-dialog'; +import List, {ListItem, ListItemGraphic, ListItemText} from '@material/react-list'; import MaterialIcon from '@material/react-material-icon'; class Simple extends Component { From 152f37d67617d33a122a1271110e99729b483074 Mon Sep 17 00:00:00 2001 From: Lucas Cordeiro Date: Wed, 6 Mar 2019 15:01:06 -0300 Subject: [PATCH 08/22] fix(snackbar): Add missing mdcw snackbar dependency (#714) --- packages/snackbar/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/snackbar/package.json b/packages/snackbar/package.json index b7d08e291..af861b47e 100644 --- a/packages/snackbar/package.json +++ b/packages/snackbar/package.json @@ -17,6 +17,7 @@ "url": "https://github.com/material-components/material-components-web-react.git" }, "dependencies": { + "@material/snackbar": "^0.43.0", "classnames": "^2.2.5", "react": "^16.4.2" }, From 1827385da633c32f79ace47b0eb9c236068cbeb4 Mon Sep 17 00:00:00 2001 From: Nickie Archua Date: Thu, 7 Mar 2019 04:00:04 +0800 Subject: [PATCH 09/22] docs(snackbar): Incorrect snackbar import (#715) --- packages/snackbar/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/snackbar/README.md b/packages/snackbar/README.md index dc4246dab..b12a24333 100644 --- a/packages/snackbar/README.md +++ b/packages/snackbar/README.md @@ -25,7 +25,7 @@ import '@material/react-snackbar/dist/snackbar.css'; ### Javascript Instantiation ```js import React from 'react'; -import Snackbar from '@material/react-snackbar'; +import {Snackbar} from '@material/react-snackbar'; class MyApp extends React.Component { render() { @@ -59,7 +59,7 @@ If you need to get the `timeoutMs`, `closeOnEscape`, or `open` value, then you c ```js import React from 'react'; -import Snackbar from '@material/react-snackbar'; +import {Snackbar} from '@material/react-snackbar'; class MyApp extends React.Component { getSnackbarInfo = (snackbar) => { if (!snackbar) return; From 9665f7fc531a196be74c188856f1c1866ca14f13 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 8 Mar 2019 11:20:11 -0500 Subject: [PATCH 10/22] fix(dialog): should extend HTMLElement (#723) --- packages/dialog/DialogContent.tsx | 2 +- packages/dialog/DialogFooter.tsx | 2 +- packages/dialog/DialogTitle.tsx | 2 +- packages/dialog/index.tsx | 2 +- test/unit/dialog/index.test.tsx | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/dialog/DialogContent.tsx b/packages/dialog/DialogContent.tsx index 922a9b423..bd2f0cbc7 100644 --- a/packages/dialog/DialogContent.tsx +++ b/packages/dialog/DialogContent.tsx @@ -30,7 +30,7 @@ export interface DialogContentProps extends React.HTMLProps { id?: string, }; -const DialogContent: (props: DialogContentProps) => +const DialogContent: (props: DialogContentProps) => React.ReactElement = ({ /* eslint-disable react/prop-types */ className = '', diff --git a/packages/dialog/DialogFooter.tsx b/packages/dialog/DialogFooter.tsx index 34ed5c777..1e1169c37 100644 --- a/packages/dialog/DialogFooter.tsx +++ b/packages/dialog/DialogFooter.tsx @@ -30,7 +30,7 @@ export interface DialogFooterProps extends React.HTMLProps { }; -const DialogFooter: (props: DialogFooterProps) => +const DialogFooter: (props: DialogFooterProps) => React.ReactElement = ({ /* eslint-disable react/prop-types */ className = '', diff --git a/packages/dialog/DialogTitle.tsx b/packages/dialog/DialogTitle.tsx index 2f249a670..4b6ca7cf8 100644 --- a/packages/dialog/DialogTitle.tsx +++ b/packages/dialog/DialogTitle.tsx @@ -30,7 +30,7 @@ export interface DialogTitleProps extends React.HTMLProps { id?: string, }; -const DialogTitle: (props: DialogTitleProps) => +const DialogTitle: (props: DialogTitleProps) => React.ReactElement = ({ /* eslint-disable react/prop-types */ className = '', diff --git a/packages/dialog/index.tsx b/packages/dialog/index.tsx index 9c44e48fc..e219eb894 100644 --- a/packages/dialog/index.tsx +++ b/packages/dialog/index.tsx @@ -84,7 +84,7 @@ function isDialogContent(element: any): element is DialogContent { return element.type === DialogContent; } -class Dialog extends React.Component< +class Dialog extends React.Component< DialogProps, DialogState > { diff --git a/test/unit/dialog/index.test.tsx b/test/unit/dialog/index.test.tsx index 821e10976..3f0e472d0 100644 --- a/test/unit/dialog/index.test.tsx +++ b/test/unit/dialog/index.test.tsx @@ -5,7 +5,7 @@ import * as td from 'testdouble'; import {shallow, mount} from 'enzyme'; // @ts-ignore import Dialog, { - ChildTypes, DialogProps, DialogTitle, DialogContent, DialogFooter, DialogButton, + ChildTypes, DialogTitle, DialogContent, DialogFooter, DialogButton, } from '../../../packages/dialog'; // @ts-ignore no mdc .d.ts file import {util} from '@material/dialog/dist/mdc.dialog'; @@ -227,7 +227,7 @@ test('#adapter.isContentScrollable returns false when there is no content', () = }); test('#adapter.isContentScrollable returns the value of util.isScrollable', () => { - const wrapper = mount>, {classList: Set}>( + const wrapper = mount(

meowkay

); const content = wrapper.instance().content; From 8f7866061ac8e9184917cc1ce8b7fef8bbf7807d Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 8 Mar 2019 12:38:30 -0500 Subject: [PATCH 11/22] fix(select): prop value should be string only (#725) --- packages/select/NativeControl.tsx | 2 ++ packages/select/README.md | 2 ++ packages/select/index.tsx | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/select/NativeControl.tsx b/packages/select/NativeControl.tsx index 94f800d80..4b18e9282 100644 --- a/packages/select/NativeControl.tsx +++ b/packages/select/NativeControl.tsx @@ -31,6 +31,7 @@ export interface NativeControlProps extends React.HTMLProps { foundation: MDCSelectFoundation; setRippleCenter: (lineRippleCenter: number) => void; handleDisabled: (disabled: boolean) => void; + value: string; } export default class NativeControl extends React.Component< @@ -49,6 +50,7 @@ export default class NativeControl extends React.Component< }, setRippleCenter: () => {}, handleDisabled: () => {}, + value: '', }; diff --git a/packages/select/README.md b/packages/select/README.md index 779c5840e..6ea1f3d4c 100644 --- a/packages/select/README.md +++ b/packages/select/README.md @@ -76,6 +76,7 @@ class MyApp extends React.Component { ` elements passed as `this.props.children`. If its an array of strings, then the string value will be used as the `label` and `value` of the `