From a4d599b64b1620779f0435d1c07ac5bbc4156528 Mon Sep 17 00:00:00 2001 From: marcJV Date: Fri, 30 Oct 2020 10:04:34 -0500 Subject: [PATCH] feat(dropdown): added footer to icondropdown --- src/components/Dropdown/Dropdown.story.jsx | 58 ++-- .../Dropdown/DropdownWithIcon.test.jsx | 123 ++++++++ .../__snapshots__/Dropdown.story.storyshot | 124 ++++++++ src/components/IconDropdown/IconDropdown.jsx | 114 +++++--- .../IconDropdown/IconDropdown.story.jsx | 62 ++-- .../IconDropdown/IconDropdown.test.jsx | 68 ++--- .../IconDropdown.story.storyshot | 266 +++++------------- .../IconDropdown/_icon-dropdown.scss | 76 +++-- .../__snapshots__/publicAPI.test.js.snap | 20 +- 9 files changed, 541 insertions(+), 370 deletions(-) create mode 100644 src/components/Dropdown/DropdownWithIcon.test.jsx create mode 100644 src/components/Dropdown/__snapshots__/Dropdown.story.storyshot diff --git a/src/components/Dropdown/Dropdown.story.jsx b/src/components/Dropdown/Dropdown.story.jsx index e16fb7eb3e..b9f24a8bd1 100644 --- a/src/components/Dropdown/Dropdown.story.jsx +++ b/src/components/Dropdown/Dropdown.story.jsx @@ -87,39 +87,27 @@ const props = () => ({ ), }); -storiesOf('Watson IoT/Dropdown', module) - .add('with icons and labels', () => { - return React.createElement(() => { - const [selectedViewId, setSelectedViewId] = useState(items[1].id); +storiesOf('Watson IoT/Dropdown', module).add('with icons and labels', () => { + return React.createElement(() => { + const [selectedViewId, setSelectedViewId] = useState(items[1].id); - const renderFooter = (item) => { - return
{item.title}
; - }; - - const itemsWithFooter = items.map((item) => { - return { - ...item, - footer: renderFooter(item), - }; - }); - - return ( -
- { - setSelectedViewId(viewItem.id); - action('onChangeView')(viewItem); - }, - }} - /> -
- ); - }); - }); \ No newline at end of file + return ( +
+ { + setSelectedViewId(viewItem.id); + action('onChangeView')(viewItem); + }, + }} + /> +
+ ); + }); +}); diff --git a/src/components/Dropdown/DropdownWithIcon.test.jsx b/src/components/Dropdown/DropdownWithIcon.test.jsx new file mode 100644 index 0000000000..8d74ac0941 --- /dev/null +++ b/src/components/Dropdown/DropdownWithIcon.test.jsx @@ -0,0 +1,123 @@ +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; + +import { items } from '../IconDropdown/IconDropdown.story'; + +import Dropdown from './DropdownWithIcon'; + +const iconDropdownProps = { + id: 'icon-dropdown-1', + label: 'Icon Dropdown menu options', + items, +}; + +const itemsWithoutIcons = [ + { + id: 'option-0', + text: 'Option 0', + }, + { + id: 'option-1', + text: 'Option 1', + }, + { + id: 'option-2', + text: 'Option 2', + }, +]; + +describe('Dropdown with Icon and Labels', () => { + it('Renders default', () => { + render( + {}, + }} + /> + ); + + const renderedLabel = screen.queryByText(iconDropdownProps.label); + + expect(renderedLabel).toBeDefined(); + }); + + it('Renders selected item', () => { + render( + {}, + }} + /> + ); + + const selectedItem = screen.queryByText(items[0].text); + + expect(selectedItem).toBeDefined(); + }); + + it('Renders in dropdown', () => { + const label = 'Icon Dropdown menu options'; + + render( + {}, + }} + /> + ); + + const renderedLabel = screen.queryByText(label); + + fireEvent.click(renderedLabel); + + expect(screen.queryByTestId(`dropdown-button__${items[3].id}`)).toBeNull(); + expect(screen.queryByTestId(items[3].text)).toBeDefined(); + + expect(screen.queryByTestId(`dropdown-button__${items[5].id}`)).toBeNull(); + expect(screen.queryByTestId(items[5].text)).toBeDefined(); + }); + + it('handles callback', () => { + let selectedItem = null; + + render( + { + selectedItem = item; + }, + }} + /> + ); + + const itemToSelect = items[3]; + + fireEvent.click(screen.getByText(iconDropdownProps.label)); + fireEvent.click(screen.getAllByText(itemToSelect.text)[0]); + + expect(selectedItem.id).toEqual(itemToSelect.id); + }); + + it('custom render', () => { + render( + 'test'} + actions={{ + onChangeView: () => {}, + }} + /> + ); + + fireEvent.click(screen.getByText(iconDropdownProps.label)); + + expect(screen.getAllByText('test').length).toEqual(items.length); + }); +}); diff --git a/src/components/Dropdown/__snapshots__/Dropdown.story.storyshot b/src/components/Dropdown/__snapshots__/Dropdown.story.storyshot new file mode 100644 index 0000000000..a95714b2b5 --- /dev/null +++ b/src/components/Dropdown/__snapshots__/Dropdown.story.storyshot @@ -0,0 +1,124 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/Dropdown with icons and labels 1`] = ` +
+
+
+
+ +
+ +
+
+
+ This is some helper text. +
+
+
+
+ +
+`; diff --git a/src/components/IconDropdown/IconDropdown.jsx b/src/components/IconDropdown/IconDropdown.jsx index f26f01310d..f0db082e0b 100644 --- a/src/components/IconDropdown/IconDropdown.jsx +++ b/src/components/IconDropdown/IconDropdown.jsx @@ -1,20 +1,17 @@ -import React, {useEffect} from 'react'; +import React, { useRef, useState } from 'react'; import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import ReactDOM, {createPortal} from 'react-dom'; - import { settings } from '../../constants/Settings'; import { Button } from '../../index'; import { Dropdown } from './index'; -import { itemPropTypes } from '../List/List'; -const { iotPrefix } = settings; +const { iotPrefix, prefix } = settings; const propTypes = { - itemToString: PropTypes.func, - hasFooter: PropTypes.node, + disabled: PropTypes.bool, + light: PropTypes.node, + columnCount: PropTypes.number, items: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, @@ -26,31 +23,58 @@ const propTypes = { }; const defaultPropTypes = { - itemToString: null, - hasFooter: false, + disabled: false, + columnCount: 4, }; const IconDropdown = ({ + columnCount, selectedViewId, items, - hasFooter, - itemToString, + light, + disabled, actions: { onChangeView, ...otherActions }, ...other }) => { + const [isOpen, setIsOpen] = useState(false); + const [highlightedIndex, setHighlightedIndex] = useState(-1); + const dropdownRef = useRef(null); + const onSelectionChange = (changes) => { const { selectedItem } = changes; - onChangeView(selectedItem); }; + const helperTextHeight = + document.body.getElementsByClassName(`${prefix}--form__helper-text`)[0] + ?.clientHeight + 4 ?? 0; + const widthStyle = `${columnCount * 48}px`; + const heightStyle = `${Math.ceil(items.length / columnCount) * 48}px`; + const renderFooter = () => { const selectedItem = items.filter((item) => item.id === selectedViewId); - - return selectedItem?.footer; + const highlightedItem = + highlightedIndex >= 0 && highlightedIndex < items.length + ? items[highlightedIndex] + : null; + return ( +
+
+ {highlightedItem !== null + ? highlightedItem?.footer + : selectedItem[0]?.footer} +
+
+ ); }; - const renderButtonsOnly = (item) => { + const itemToString = (item) => { return ( <> + Dropdown label +
-
-
- This is some helper text. -
-
- - - - -`; - -exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/IconDropdown with icons only 1`] = ` -
-
-
-
- -
- + + + + Open menu + + +
+ +
+
-
-
- This is some helper text. + className="bx--form__helper-text" + > + This is some helper text. +
diff --git a/src/components/IconDropdown/_icon-dropdown.scss b/src/components/IconDropdown/_icon-dropdown.scss index 99a1a6fd00..1f0df9c7d8 100644 --- a/src/components/IconDropdown/_icon-dropdown.scss +++ b/src/components/IconDropdown/_icon-dropdown.scss @@ -6,32 +6,54 @@ padding: 0; } -.#{$iot-prefix}--dropdown__selection-buttons { - width: 180px; +.#{$iot-prefix}--dropdown__footer { + @include box-shadow(); + + background-color: $ui-01; + z-index: z('dropdown'); + width: 100%; + + &-content { + padding: $spacing-03; + } +} +.#{$iot-prefix}--dropdown__selection-buttons { > .#{$prefix}--list-box__field { - padding: 0 $spacing-09 0 $spacing-03; + padding: 0 $spacing-09 0 $spacing-05; } > .#{$prefix}--list-box__menu { - padding: $spacing-01; + outline-style: none; + border: none; + transition: initial; + box-shadow: none; > .#{$prefix}--list-box__menu-item--active { background: transparent; > .#{$prefix}--list-box__menu-item__option { - > .#{$iot-prefix}--dropdown__image-button { - border: 1px $focus solid; + > .#{$iot-prefix}--dropdown__image-button.bx--btn.bx--btn--icon-only.bx--tooltip__trigger:hover::before { + opacity: 0; } } } - > .#{$prefix}--list-box__menu-item--highlighted { - background: transparent; + > .#{$prefix}--list-box__menu-item.#{$prefix}--list-box__menu-item--highlighted { + &:hover { + > .bx--list-box__menu-item__option { + > .#{$iot-prefix}--dropdown__image-button { + border-color: transparent; + } + } + } - > div > button { - border: 2px $focus solid; + > .bx--list-box__menu-item__option { + > .#{$iot-prefix}--dropdown__image-button { + border-color: $focus; + } } + background: transparent; } > .#{$prefix}--list-box__menu-item { @@ -40,6 +62,11 @@ > .#{$prefix}--list-box__menu-item__option { overflow: visible; + border: none; + + &:hover { + background: $hover-ui; + } > .#{$iot-prefix}--dropdown__selected-icon-label { display: none; @@ -49,14 +76,32 @@ display: none; } - padding: 0; - margin: $spacing-02; height: min-content; + padding: 0; + margin: 0; display: flex; align-items: center; justify-items: center; - border: none; + + > .#{$iot-prefix}--dropdown__image-button { + border-width: 1px; + border-style: solid; + border-color: transparent $ui-03 $ui-03 transparent; + color: transparent; + + &:hover { + color: transparent; + } + + &--bottom { + padding-bottom: $spacing-03; + } + + > .bx--assistive-text { + display: none; + } + } } } } @@ -117,8 +162,3 @@ } } } - -.#{$iot-prefix}--dropdown__image-button { - background-color: $ui-02; - border: 2px transparent solid; -} diff --git a/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap b/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap index ec0612c316..adec1c9a56 100644 --- a/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap +++ b/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap @@ -6004,25 +6004,24 @@ Map { }, "IconDropdown" => Object { "defaultProps": Object { - "footer": null, - "hasIconOnly": false, - "itemToString": null, + "columnCount": 4, + "disabled": false, }, "propTypes": Object { - "footer": Object { - "type": "node", + "columnCount": Object { + "type": "number", }, - "hasIconsOnly": Object { + "disabled": Object { "type": "bool", }, - "itemToString": Object { - "type": "func", - }, "items": Object { "args": Array [ Object { "args": Array [ Object { + "footer": Object { + "type": "node", + }, "icon": Object { "args": Array [ Array [ @@ -6053,6 +6052,9 @@ Map { "isRequired": true, "type": "arrayOf", }, + "light": Object { + "type": "node", + }, }, }, "baseTableReducer" => Object {},