diff --git a/cypress/integration/ComponentCover/click_behavior.feature b/cypress/integration/ComponentCover/click_behavior.feature index 90d61e59ac..3302d97213 100644 --- a/cypress/integration/ComponentCover/click_behavior.feature +++ b/cypress/integration/ComponentCover/click_behavior.feature @@ -1,10 +1,5 @@ Feature: The ComponentCover has configurable click behaviour - Scenario: A non-blocking ComponentCover - Given a ComponentCover with pointerEvents none and a button below it is rendered - When the user clicks the button - Then the onClick handler of the button is called - Scenario: A blocking ComponentCover Given a ComponentCover with a button below it is rendered When the user clicks on the button coordinates diff --git a/cypress/integration/ComponentCover/click_behavior/index.js b/cypress/integration/ComponentCover/click_behavior/index.js index d51738a8cd..1a5a3f9eb5 100644 --- a/cypress/integration/ComponentCover/click_behavior/index.js +++ b/cypress/integration/ComponentCover/click_behavior/index.js @@ -1,12 +1,5 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps' -Given( - 'a ComponentCover with pointerEvents none and a button below it is rendered', - () => { - cy.visitStory('ComponentCover', 'Non Blocking') - } -) - Given('a ComponentCover with a button below it is rendered', () => { cy.visitStory('ComponentCover', 'Blocking') }) diff --git a/cypress/integration/FlyoutMenu/accepts_children.feature b/cypress/integration/FlyoutMenu/accepts_children.feature new file mode 100644 index 0000000000..7785383023 --- /dev/null +++ b/cypress/integration/FlyoutMenu/accepts_children.feature @@ -0,0 +1,5 @@ +Feature: The FlyoutMenu renders children + + Scenario: A FlyoutMenu with children + Given a FlyoutMenu with children is rendered + Then the children are visible diff --git a/cypress/integration/FlyoutMenu/accepts_children/index.js b/cypress/integration/FlyoutMenu/accepts_children/index.js new file mode 100644 index 0000000000..b3cceced65 --- /dev/null +++ b/cypress/integration/FlyoutMenu/accepts_children/index.js @@ -0,0 +1,10 @@ +import { Given, Then } from 'cypress-cucumber-preprocessor/steps' + +Given('a FlyoutMenu with children is rendered', () => { + cy.visitStory('FlyoutMenu', 'With Children') + cy.get('[data-test="dhis2-uicore-menu"]').should('be.visible') +}) + +Then('the children are visible', () => { + cy.contains('I am a child').should('be.visible') +}) diff --git a/cypress/integration/MenuItem/position.feature b/cypress/integration/FlyoutMenu/position.feature similarity index 83% rename from cypress/integration/MenuItem/position.feature rename to cypress/integration/FlyoutMenu/position.feature index 5d54e69812..afa14e33c4 100644 --- a/cypress/integration/MenuItem/position.feature +++ b/cypress/integration/FlyoutMenu/position.feature @@ -1,18 +1,18 @@ -Feature: Position of a MenuItem component +Feature: Position of a SubMenu component Scenario: Default rendering Given there is enough space to the right of the MenuItem to fit the SubMenu - When the user hovers over the MenuItem + When the user clicks on the MenuItem Then the right of the MenuItem is aligned with the left of the SubMenu And the top of the MenuItem is aligned with the top of the SubMenu wrapper Scenario: Flipped rendering when insufficient space to the right of Given there is not enough space to the right of the MenuItem to fit the SubMenu - When the user hovers over the MenuItem + When the user clicks on the MenuItem Then the left of the MenuItem is aligned with the right of the SubMenu And the top of the MenuItem is aligned with the top of the SubMenu wrapper Scenario: Shifting into view when insufficient space to the right of and above Given there is not enough space to the right or the left of the MenuItem to fit the SubMenu - When the user hovers over the MenuItem + When the user clicks on the MenuItem Then the SubMenu is rendered on top of the MenuItem And the top of the MenuItem is aligned with the top of the SubMenu wrapper diff --git a/cypress/integration/MenuItem/position/index.js b/cypress/integration/FlyoutMenu/position/index.js similarity index 84% rename from cypress/integration/MenuItem/position/index.js rename to cypress/integration/FlyoutMenu/position/index.js index 3c416629e4..cca8672989 100644 --- a/cypress/integration/MenuItem/position/index.js +++ b/cypress/integration/FlyoutMenu/position/index.js @@ -3,26 +3,26 @@ import { Given, Then, When } from 'cypress-cucumber-preprocessor/steps' Given( 'there is enough space to the right of the MenuItem to fit the SubMenu', () => { - cy.visitStory('MenuItem', 'Default position') + cy.visitStory('FlyoutMenu', 'Default Position') } ) Given( 'there is not enough space to the right of the MenuItem to fit the SubMenu', () => { - cy.visitStory('MenuItem', 'Flipped position') + cy.visitStory('FlyoutMenu', 'Flipped Position') } ) Given( 'there is not enough space to the right or the left of the MenuItem to fit the SubMenu', () => { - cy.visitStory('MenuItem', 'Shift into view') + cy.visitStory('FlyoutMenu', 'Shift Into View') } ) -When('the user hovers over the MenuItem', () => { - cy.get('[data-test="dhis2-uicore-menuitem"]').trigger('mouseover') +When('the user clicks on the MenuItem', () => { + cy.get('[data-test="dhis2-uicore-menuitem"]').click() }) Then( @@ -65,6 +65,6 @@ Then( function getMenuItemAndSubMenuRects() { return cy.getPositionsBySelectors( '[data-test="dhis2-uicore-menuitem"]', - '[data-test="submenu"]' + '[data-test="dhis2-uicore-popper"]' ) } diff --git a/cypress/integration/FlyoutMenu/toggles_submenus.feature b/cypress/integration/FlyoutMenu/toggles_submenus.feature new file mode 100644 index 0000000000..e176d1387f --- /dev/null +++ b/cypress/integration/FlyoutMenu/toggles_submenus.feature @@ -0,0 +1,9 @@ +Feature: The FlyoutMenu toggles SubMenus + + Scenario: a FlyoutMenu with two SubMenus + Given a FlyoutMenu with two SubMenus is rendered + When the user clicks the first SubMenu anchor + Then first SubMenu is visible + When the user clicks the second SubMenu anchor + Then second SubMenu is visible + And the first SubMenu is not visible diff --git a/cypress/integration/FlyoutMenu/toggles_submenus/index.js b/cypress/integration/FlyoutMenu/toggles_submenus/index.js new file mode 100644 index 0000000000..93e6dea417 --- /dev/null +++ b/cypress/integration/FlyoutMenu/toggles_submenus/index.js @@ -0,0 +1,26 @@ +import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps' + +Given('a FlyoutMenu with two SubMenus is rendered', () => { + cy.visitStory('FlyoutMenu', 'Toggles Sub Menus') + cy.get('[data-test="dhis2-uicore-menu"]').should('be.visible') +}) + +When('the user clicks the first SubMenu anchor', () => { + cy.contains('Item 1').click() +}) + +Then('first SubMenu is visible', () => { + cy.contains('SubMenu 1').should('be.visible') +}) + +When('the user clicks the second SubMenu anchor', () => { + cy.contains('Item 2').click() +}) + +Then('second SubMenu is visible', () => { + cy.contains('SubMenu 2').should('be.visible') +}) + +Then('the first SubMenu is not visible', () => { + cy.contains('SubMenu 1').should('not.be.visible') +}) diff --git a/cypress/integration/Layer/click_behavior.feature b/cypress/integration/Layer/click_behavior.feature index cef3686777..8bbbec12ac 100644 --- a/cypress/integration/Layer/click_behavior.feature +++ b/cypress/integration/Layer/click_behavior.feature @@ -1,10 +1,5 @@ Feature: The Layer has configurable click behaviour - Scenario: A non-blocking layer - Given a Layer with pointerEvents none and a button below it is rendered - When the user clicks the button - Then the onClick handler of the button is called - Scenario: A blocking layer Given a Layer with a button below it is rendered When the user clicks on the button coordinates diff --git a/cypress/integration/Layer/click_behavior/index.js b/cypress/integration/Layer/click_behavior/index.js index 8ed143bbe8..8e7f92afdb 100644 --- a/cypress/integration/Layer/click_behavior/index.js +++ b/cypress/integration/Layer/click_behavior/index.js @@ -1,12 +1,5 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps' -Given( - 'a Layer with pointerEvents none and a button below it is rendered', - () => { - cy.visitStory('Layer', 'Non Blocking') - } -) - Given('a Layer with a button below it is rendered', () => { cy.visitStory('Layer', 'Blocking') }) diff --git a/cypress/integration/Menu/accepts_children/index.js b/cypress/integration/Menu/accepts_children/index.js index afc9b251b4..6b7f951b7a 100644 --- a/cypress/integration/Menu/accepts_children/index.js +++ b/cypress/integration/Menu/accepts_children/index.js @@ -2,7 +2,7 @@ import { Given, Then } from 'cypress-cucumber-preprocessor/steps' Given('a Menu with children is rendered', () => { cy.visitStory('Menu', 'With children') - cy.get('[data-test="dhis2-uicore-menu"]').should('be.visible') + cy.get('[data-test="dhis2-uicore-menulist"]').should('be.visible') }) Then('the children are visible', () => { diff --git a/cypress/integration/MenuItem/accepts_href/index.js b/cypress/integration/MenuItem/accepts_href/index.js index ccdf236aec..f655b8d3ab 100644 --- a/cypress/integration/MenuItem/accepts_href/index.js +++ b/cypress/integration/MenuItem/accepts_href/index.js @@ -1,7 +1,7 @@ import { Given, Then } from 'cypress-cucumber-preprocessor/steps' Given('a MenuItem with href is rendered', () => { - cy.visitStory('MenuItem', 'With href') + cy.visitStory('MenuItem', 'With Href') }) Then('a link is rendered with the href', () => { diff --git a/cypress/integration/MenuItem/accepts_icon/index.js b/cypress/integration/MenuItem/accepts_icon/index.js index d01985879d..24221621f6 100644 --- a/cypress/integration/MenuItem/accepts_icon/index.js +++ b/cypress/integration/MenuItem/accepts_icon/index.js @@ -1,7 +1,7 @@ import { Given, Then } from 'cypress-cucumber-preprocessor/steps' Given('a MenuItem supplied with an icon is rendered', () => { - cy.visitStory('MenuItem', 'With icon') + cy.visitStory('MenuItem', 'With Icon') cy.get('[data-test="dhis2-uicore-menuitem"]').should('be.visible') }) diff --git a/cypress/integration/MenuItem/accepts_label.feature b/cypress/integration/MenuItem/accepts_label.feature index a902932447..2dd271d7ac 100644 --- a/cypress/integration/MenuItem/accepts_label.feature +++ b/cypress/integration/MenuItem/accepts_label.feature @@ -2,4 +2,4 @@ Feature: The MenuItem accepts a label prop Scenario: MenuItem renders supplied label Given a MenuItem supplied with a label is rendered - Then the label will be visible + Then the label is visible \ No newline at end of file diff --git a/cypress/integration/MenuItem/accepts_label/index.js b/cypress/integration/MenuItem/accepts_label/index.js index 1285c48e35..ec48d8d87f 100644 --- a/cypress/integration/MenuItem/accepts_label/index.js +++ b/cypress/integration/MenuItem/accepts_label/index.js @@ -1,10 +1,10 @@ import { Given, Then } from 'cypress-cucumber-preprocessor/steps' Given('a MenuItem supplied with a label is rendered', () => { - cy.visitStory('MenuItem', 'With label') + cy.visitStory('MenuItem', 'With Label') cy.get('[data-test="dhis2-uicore-menuitem"]').should('be.visible') }) -Then('the label will be visible', () => { - cy.contains('Label').should('be.visible') +Then('the label is visible', () => { + cy.contains('label').should('be.visible') }) diff --git a/cypress/integration/MenuItem/accepts_target/index.js b/cypress/integration/MenuItem/accepts_target/index.js index 69e2f2aa0d..841abfceca 100644 --- a/cypress/integration/MenuItem/accepts_target/index.js +++ b/cypress/integration/MenuItem/accepts_target/index.js @@ -1,7 +1,7 @@ import { Given, Then } from 'cypress-cucumber-preprocessor/steps' Given('a MenuItem with target is rendered', () => { - cy.visitStory('MenuItem', 'With target') + cy.visitStory('MenuItem', 'With Target') }) Then('a link is rendered with the target', () => { diff --git a/cypress/integration/MenuItem/is_clickable/index.js b/cypress/integration/MenuItem/is_clickable/index.js index 0dcd46fe8f..606bb415fd 100644 --- a/cypress/integration/MenuItem/is_clickable/index.js +++ b/cypress/integration/MenuItem/is_clickable/index.js @@ -1,7 +1,7 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps' Given('a MenuItem with onClick handler and value is rendered', () => { - cy.visitStory('MenuItem', 'With onClick and value') + cy.visitStory('MenuItem', 'With On Click And Value') }) When('the MenuItem is clicked', () => { diff --git a/cypress/integration/MenuList/accepts_children.feature b/cypress/integration/MenuList/accepts_children.feature deleted file mode 100644 index a07f814dea..0000000000 --- a/cypress/integration/MenuList/accepts_children.feature +++ /dev/null @@ -1,5 +0,0 @@ -Feature: The MenuList renders children - - Scenario: A MenuList with children - Given a MenuList with children is rendered - Then the children are visible diff --git a/cypress/integration/MenuList/accepts_children/index.js b/cypress/integration/MenuList/accepts_children/index.js deleted file mode 100644 index d08806e712..0000000000 --- a/cypress/integration/MenuList/accepts_children/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Given, Then } from 'cypress-cucumber-preprocessor/steps' - -Given('a MenuList with children is rendered', () => { - cy.visitStory('MenuList', 'With children') - cy.get('[data-test="dhis2-uicore-menulist"]').should('be.visible') -}) - -Then('the children are visible', () => { - cy.contains('I am a child').should('be.visible') -}) diff --git a/cypress/integration/MenuSectionHeader/accepts_label.feature b/cypress/integration/MenuSectionHeader/accepts_label.feature new file mode 100644 index 0000000000..8bf9674564 --- /dev/null +++ b/cypress/integration/MenuSectionHeader/accepts_label.feature @@ -0,0 +1,5 @@ +Feature: The MenuSectionHeader accepts a label prop + + Scenario: MenuSectionHeader renders supplied label + Given a MenuSectionHeader supplied with a label is rendered + Then the label is visible \ No newline at end of file diff --git a/cypress/integration/MenuSectionHeader/accepts_label/index.js b/cypress/integration/MenuSectionHeader/accepts_label/index.js new file mode 100644 index 0000000000..70bb3d779a --- /dev/null +++ b/cypress/integration/MenuSectionHeader/accepts_label/index.js @@ -0,0 +1,10 @@ +import { Given, Then } from 'cypress-cucumber-preprocessor/steps' + +Given('a MenuSectionHeader supplied with a label is rendered', () => { + cy.visitStory('MenuSectionHeader', 'With Label') + cy.get('[data-test="dhis2-uicore-menusectionheader"]').should('be.visible') +}) + +Then('the label is visible', () => { + cy.contains('label').should('be.visible') +}) diff --git a/packages/core/src/ComponentCover/ComponentCover.js b/packages/core/src/ComponentCover/ComponentCover.js index efe1c65dcb..9bbe122d1c 100644 --- a/packages/core/src/ComponentCover/ComponentCover.js +++ b/packages/core/src/ComponentCover/ComponentCover.js @@ -23,7 +23,6 @@ const ComponentCover = ({ className, dataTest, onClick, - pointerEvents, translucent, }) => (
( ) -export const NonBlocking = () => ( - <> - - - -) - export const Blocking = () => ( <> diff --git a/packages/core/src/ComponentCover/ComponentCover.stories.js b/packages/core/src/ComponentCover/ComponentCover.stories.js index 36f9479d09..e0fc4dcaf7 100644 --- a/packages/core/src/ComponentCover/ComponentCover.stories.js +++ b/packages/core/src/ComponentCover/ComponentCover.stories.js @@ -49,18 +49,6 @@ export const WithClickHandler = () => ( ) -export const NonBlocking = () => ( - <> - - -

Text behind the cover

-

- You can still select this text because the cover has pointer-event: - none. -

- -) - export const WithCenteredContentCircularLoader = () => ( <> diff --git a/packages/core/src/FlyoutMenu/FlyoutMenu.js b/packages/core/src/FlyoutMenu/FlyoutMenu.js new file mode 100644 index 0000000000..baa4ce209f --- /dev/null +++ b/packages/core/src/FlyoutMenu/FlyoutMenu.js @@ -0,0 +1,97 @@ +import React, { Children, cloneElement, isValidElement, useState } from 'react' +import propTypes from '@dhis2/prop-types' +import { spacers } from '@dhis2/ui-constants' +import { resolve } from 'styled-jsx/css' + +import { Card } from '../Card/Card.js' +import { Menu } from '../Menu/Menu.js' +;('') // TODO: https://github.com/jsdoc/jsdoc/issues/1718 + +/** + * @module + * @param {FlyoutMenu.PropTypes} + * @returns {React.Component} + * + * @example import { FlyoutMenu } from '@dhis2/ui' + * + * @see Specification: {@link https://github.com/dhis2/design-system/blob/master/molecules/menu.md|Design system} + * @see Live demo: {@link /demo/?path=/story/components-core-menu--default|Storybook} + */ +const FlyoutMenu = ({ + children, + className, + dataTest, + dense, + maxHeight, + maxWidth, +}) => { + const [openedSubMenu, setOpenedSubMenu] = useState(null) + const toggleSubMenu = index => { + const toggleValue = index === openedSubMenu ? null : index + setOpenedSubMenu(toggleValue) + } + + const cardCSS = resolve` + padding: ${spacers.dp4} 0; + max-height: ${maxHeight}; + overflow-y: auto; + ` + + return ( +
+ + + {Children.map(children, (child, index) => + isValidElement(child) + ? cloneElement(child, { + showSubMenu: openedSubMenu === index, + toggleSubMenu: toggleSubMenu.bind( + this, + index + ), + }) + : child + )} + + + + + {cardCSS.styles} +
+ ) +} + +FlyoutMenu.defaultProps = { + dataTest: 'dhis2-uicore-menu', + maxWidth: '380px', + maxHeight: 'auto', +} + +/** + * @typedef {Object} PropTypes + * @static + * + * @prop {Element} [children] + * @prop {string} [className] + * @prop {string} [dataTest='dhis2-uicore-menu'] + * @prop {boolean} [dense] + * @prop {string} [maxWidth='380px'] + * @prop {string} [maxHeight='auto'] + */ +FlyoutMenu.propTypes = { + children: Menu.propTypes.children, + className: propTypes.string, + dataTest: propTypes.string, + dense: propTypes.bool, + maxHeight: propTypes.string, + maxWidth: propTypes.string, +} + +export { FlyoutMenu } diff --git a/packages/core/src/FlyoutMenu/FlyoutMenu.stories.e2e.js b/packages/core/src/FlyoutMenu/FlyoutMenu.stories.e2e.js new file mode 100644 index 0000000000..6c32221baf --- /dev/null +++ b/packages/core/src/FlyoutMenu/FlyoutMenu.stories.e2e.js @@ -0,0 +1,56 @@ +import React from 'react' + +import { MenuItem } from '../MenuItem/MenuItem.js' +import { FlyoutMenu } from './FlyoutMenu.js' + +export default { + title: 'FlyoutMenu', + component: FlyoutMenu, +} + +const MenuItemSubMenuPositions = () => ( + + +
SubMenu 1
+
+
+) + +export const WithChildren = () => I am a child + +export const TogglesSubMenus = () => ( + + SubMenu 1 + SubMenu 2 + +) + +export const DefaultPosition = () => +export const FlippedPosition = () => ( +
+ + +
+) +export const ShiftIntoView = () => ( +
+ + +
+) diff --git a/packages/core/src/FlyoutMenu/FlyoutMenu.stories.js b/packages/core/src/FlyoutMenu/FlyoutMenu.stories.js new file mode 100644 index 0000000000..8a676beffc --- /dev/null +++ b/packages/core/src/FlyoutMenu/FlyoutMenu.stories.js @@ -0,0 +1,191 @@ +import React, { useState, useRef } from 'react' +import { ArrowDown } from '@dhis2/ui-icons' + +import { Layer } from '../Layer/Layer.js' +import { Popper } from '../Popper/Popper.js' +import { MenuItem } from '../MenuItem/MenuItem.js' +import { MenuDivider } from '../MenuDivider/MenuDivider.js' +import { MenuSectionHeader } from '../MenuSectionHeader/MenuSectionHeader.js' + +import { FlyoutMenu } from './FlyoutMenu.js' + +export default { + title: 'Components/Core/FlyoutMenu', + component: FlyoutMenu, +} + +export const Default = () => ( + + + + +) + +export const Dense = () => ( + + + + +) + +export const MaxHeight = () => ( + + + + + + + + + + + + +) + +export const MaxWidth = () => ( + <> + + + + +
+ + + + +
+ + + + + +) + +export const WithSubMenus = () => ( + + + + + + + + + + + + + + + + + + + + + +) + +export const WithVariousChildren = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + +) + +export const DropDownMenu = () => { + const ref = useRef() + const [open, setOpen] = useState(false) + const toggle = () => setOpen(!open) + + return ( + <> + + {open && ( + + + + + + )} + + ) +} + +export const WithCustomMenuItem = () => { + // You should not create custom components in the render cycle + // this is just for demo purposes + // eslint-disable-next-line react/prop-types + const PopupWindowMenuItem = ({ to, children, ...rest }) => { + const HEIGHT = 1000 + const WIDTH = 1400 + const centerY = (window.screen.height - HEIGHT) / 2 + const centerX = (window.screen.width - WIDTH) / 2 + + const onClick = () => + window.open( + to, + 'Popup', + [ + 'menubar=no', + 'location=no', + 'resizable=no', + 'scrollbars=no', + 'status=no', + `width=${WIDTH}`, + `height=${HEIGHT}`, + `top=${centerY}`, + `left=${centerX}`, + ].join() + ) + + return + } + + return ( + + + + A custom menu item (popup window) + + + ) +} diff --git a/packages/core/src/Layer/Layer.js b/packages/core/src/Layer/Layer.js index 7733b236a6..2bc22d2ec8 100644 --- a/packages/core/src/Layer/Layer.js +++ b/packages/core/src/Layer/Layer.js @@ -9,6 +9,8 @@ const LayerContext = createContext({ level: 0, }) +const useLayerContext = () => useContext(LayerContext) + const createClickHandler = onClick => event => { // don't respond to clicks that originated in the children if (onClick && event.target === event.currentTarget) { @@ -29,11 +31,10 @@ const Layer = ({ dataTest, level, onClick, - pointerEvents, position, translucent, }) => { - const parentLayer = useContext(LayerContext) + const parentLayer = useLayerContext() const [layerEl, setLayerEl] = useState(null) const nextLayer = { node: layerEl, @@ -59,7 +60,6 @@ const Layer = ({ - -
+ ) Menu.defaultProps = { - dataTest: 'dhis2-uicore-menu', - maxWidth: '380px', + dataTest: 'dhis2-uicore-menulist', } /** * @typedef {Object} PropTypes * @static * - * @prop {*} [children] + * @prop {Node} [children] * @prop {string} [className] - * @prop {string} [dataTest] - * @prop {string} [maxWidth] + * @prop {string} [dataTest='dhis2-uicore-menulist'] + * @prop {boolean} [dense] */ Menu.propTypes = { - children: MenuList.propTypes.children, + children: propTypes.node, className: propTypes.string, dataTest: propTypes.string, - maxWidth: propTypes.string, + dense: propTypes.bool, } export { Menu } diff --git a/packages/core/src/Menu/Menu.stories.e2e.js b/packages/core/src/Menu/Menu.stories.e2e.js index 7fae2a4c93..62efab1190 100644 --- a/packages/core/src/Menu/Menu.stories.e2e.js +++ b/packages/core/src/Menu/Menu.stories.e2e.js @@ -1,5 +1,5 @@ -import { storiesOf } from '@storybook/react' import React from 'react' +import { storiesOf } from '@storybook/react' import { Menu } from './Menu.js' storiesOf('Menu', module).add('With children', () => I am a child) diff --git a/packages/core/src/Menu/Menu.stories.js b/packages/core/src/Menu/Menu.stories.js index 071665baed..d34c9ff4d7 100644 --- a/packages/core/src/Menu/Menu.stories.js +++ b/packages/core/src/Menu/Menu.stories.js @@ -1,181 +1,50 @@ import React from 'react' -import { storiesOf } from '@storybook/react' -import { MenuItem, Divider, Switch } from '../index.js' +import { MenuItem } from '../MenuItem/MenuItem.js' import { Menu } from './Menu.js' -const Wrapper = fn =>
{fn()}
- -storiesOf('Components/Core/Menu', module) - .addDecorator(Wrapper) - - .add('Default', () => ( - - { - alert(`${evt.target.name}: ${evt.target.value}`) - }} - /> - } - /> - - { - alert(`this is ${val}`) - }} - /> - - - - - - - { - alert(`this is ${val}`) - }} - /> - - - - - - - - - { - alert(`this is ${val}`) - }} - /> - { - alert(`this is ${val}`) - }} - /> - { - alert(`this is ${val}`) - }} - /> - { - alert(`this is ${val}`) - }} - /> - - )) - - .add('Short item labels', () => ( - - - - - - - )) - - .add('Dense', () => ( - - { - alert('would need state, but would set to: ' + v) - }} - checked={false} - /> - } - /> - - { - alert(`this is ${val}`) - }} - /> - - - - - - - { - alert(`this is ${val}`) - }} - /> - - - - - - - - - { - alert(`this is ${val}`) - }} - /> - { - alert(`this is ${val}`) - }} - /> - { - alert(`this is ${val}`) - }} - /> - { - alert(`this is ${val}`) - }} - /> - - )) +export default { + title: 'Components/Core/Menu', + component: Menu, +} + +export const Default = () => ( + + + + +) + +export const Dense = () => ( + + + + +) + +export const SideBarMenu = () => ( +
+ +
+ Main content +
+
+) diff --git a/packages/core/src/MenuDivider/MenuDivider.js b/packages/core/src/MenuDivider/MenuDivider.js new file mode 100644 index 0000000000..d9d4dc0260 --- /dev/null +++ b/packages/core/src/MenuDivider/MenuDivider.js @@ -0,0 +1,51 @@ +import React from 'react' +import propTypes from '@dhis2/prop-types' +import { colors } from '@dhis2/ui-constants' + +import { Divider } from '../Divider/Divider.js' + +/** + * @module + * @param {MenuDivider.PropTypes} + * @returns {React.Component} + * + * @example import { MenuDivider } from '@dhis2/ui + * + * @see Specification: {@link https://github.com/dhis2/design-system/blob/master/molecules/menu.md|Design system} + * @see Live demo: {@link /demo/?path=/story/components-core-menudivider--default|Storybook} + */ +const MenuDivider = ({ className, dataTest, dense }) => ( +
  • + + + +
  • +) + +MenuDivider.defaultProps = { + dataTest: 'dhis2-uicore-menudivider', +} + +/** + * @typedef {Object} PropTypes + * @static + * + * @prop {string} [className] + * @prop {string} [dataTest='dhis2-uicore-menudivider'] + * @prop {boolean} [dense] + */ +MenuDivider.propTypes = { + className: propTypes.string, + dataTest: propTypes.string, + dense: propTypes.bool, +} + +export { MenuDivider } diff --git a/packages/core/src/MenuDivider/MenuDivider.stories.js b/packages/core/src/MenuDivider/MenuDivider.stories.js new file mode 100644 index 0000000000..bf32ce01e1 --- /dev/null +++ b/packages/core/src/MenuDivider/MenuDivider.stories.js @@ -0,0 +1,14 @@ +import React from 'react' + +import { Menu } from '../Menu/Menu.js' +import { MenuDivider } from './MenuDivider.js' + +export default { + title: 'Components/Core/MenuDivider', + component: MenuDivider, + decorators: [storyFn => {storyFn()}], +} + +export const Default = () => + +export const Dense = () => diff --git a/packages/core/src/MenuItem/MenuItem.js b/packages/core/src/MenuItem/MenuItem.js index f78ed4a59a..5f0ddf37fa 100644 --- a/packages/core/src/MenuItem/MenuItem.js +++ b/packages/core/src/MenuItem/MenuItem.js @@ -1,62 +1,98 @@ -import React from 'react' +import React, { useRef } from 'react' +import { createPortal } from 'react-dom' import propTypes from '@dhis2/prop-types' +import cx from 'classnames' +import { ChevronRight } from '@dhis2/ui-icons' -import { MenuItemContent } from './MenuItemContent/MenuItemContent.js' -import { SubMenu } from './SubMenu.js' +import { Popper } from '../Popper/Popper.js' +import { FlyoutMenu } from '../FlyoutMenu/FlyoutMenu.js' +import { useLayerContext } from '../Layer/Layer.js' +import styles from './MenuItem.styles.js' + +const createOnClickHandler = (onClick, toggleSubMenu, value) => evt => { + if (onClick || toggleSubMenu) { + evt.preventDefault() + evt.stopPropagation() + + onClick && onClick({ value }, evt) + toggleSubMenu && toggleSubMenu() + } +} /** * @module * @param {MenuItem.PropTypes} * @returns {React.Component} * - * @example import { MenuItem } from '@dhis2/ui-core' + * @example import { MenuItem } from '@dhis2/ui * * @see Specification: {@link https://github.com/dhis2/design-system/blob/master/molecules/menu.md|Design system} - * @see Live demo: {@link /demo/?path=/story/menu--default|Storybook} + * @see Live demo: {@link /demo/?path=/story/components-core-menulist--default|Storybook} */ const MenuItem = ({ href, - value, - label, - icon, + onClick, children, - active, + target, + icon, + className, destructive, disabled, dense, - onClick, - className, - target, + active, dataTest, + chevron, + value, + label, + showSubMenu, + toggleSubMenu, }) => { - return !children ? ( - - ) : ( - - {children} - + const menuItemRef = useRef() + const { node } = useLayerContext() + + return ( + <> +
  • + + {icon && {icon}} + + {label} + + {(chevron || children) && ( + + + + )} + + + +
  • + {children && + showSubMenu && + createPortal( + + {children} + , + node + )} + ) } @@ -68,32 +104,38 @@ MenuItem.defaultProps = { * @typedef {Object} PropTypes * @static * - * @prop {string|Node} [label] - * @prop {string} [value] - * @prop {string} [href] - * @prop {string} [target] - * @prop {function} [onClick] - Click handler called with `value` as the sole argument + * @prop {boolean} [active] + * @prop {boolean} [chevron] + * @prop {Node} [children] * @prop {string} [className] - * @prop {Element} [children] - * @prop {Element} [icon] + * @prop {string} [dataTest='dhis2-uicore-menuitem'] * @prop {boolean} [dense] - * @prop {boolean} [active] * @prop {boolean} [destructive] * @prop {boolean} [disabled] - * @prop {string} [dataTest] + * @prop {string} [href] + * @prop {Node} [icon] + * @prop {Node} [label] + * @prop {boolean} [showSubMenu] + * @prop {string} [target] + * @prop {function} [toggleSubMenu] + * @prop {string} [value] + * @prop {function} [onClick] - Click handler called with `value` in the payload */ MenuItem.propTypes = { active: propTypes.bool, - children: propTypes.element, + chevron: propTypes.bool, + children: propTypes.node, className: propTypes.string, dataTest: propTypes.string, dense: propTypes.bool, destructive: propTypes.bool, disabled: propTypes.bool, href: propTypes.string, - icon: propTypes.element, - label: propTypes.oneOfType([propTypes.string, propTypes.node]), + icon: propTypes.node, + label: propTypes.node, + showSubMenu: propTypes.bool, target: propTypes.string, + toggleSubMenu: propTypes.func, value: propTypes.string, onClick: propTypes.func, } diff --git a/packages/core/src/MenuItem/MenuItem.stories.e2e.js b/packages/core/src/MenuItem/MenuItem.stories.e2e.js index ab825bd853..06e773eca4 100644 --- a/packages/core/src/MenuItem/MenuItem.stories.e2e.js +++ b/packages/core/src/MenuItem/MenuItem.stories.e2e.js @@ -1,62 +1,28 @@ import React from 'react' -import { storiesOf } from '@storybook/react' + +import { Menu } from '../Menu/Menu.js' import { MenuItem } from './MenuItem.js' -import { Menu } from '../index.js' window.onClick = window.Cypress && window.Cypress.cy.stub() -const MenuItemPositions = () => ( - - - - - - - +export default { + title: 'MenuItem', + component: MenuItem, + decorators: [storyFn => {storyFn()}], +} + +export const WithLabel = () => + +export const WithOnClickAndValue = () => ( + ) -storiesOf('MenuItem', module) - .add('With onClick and value', () => ( - - )) - .add('With href', () => ) - .add('With target', () => ( - - )) - .add('With icon', () => ( - Icon} /> - )) - .add('With label', () => ) - .add('Default position', () => ) - .add('Flipped position', () => ( -
    - - -
    - )) - .add('Shift into view', () => ( -
    - - -
    - )) +export const WithHref = () => + +export const WithTarget = () => ( + +) + +export const WithIcon = () => ( + Icon} label="Menu item" /> +) diff --git a/packages/core/src/MenuItem/MenuItem.stories.js b/packages/core/src/MenuItem/MenuItem.stories.js index 742021c196..bceeb83190 100644 --- a/packages/core/src/MenuItem/MenuItem.stories.js +++ b/packages/core/src/MenuItem/MenuItem.stories.js @@ -1,63 +1,67 @@ -import { storiesOf } from '@storybook/react' -import React from 'react' +import React, { useState } from 'react' +import { resolve } from 'styled-jsx/css' -import { Menu } from '../index.js' +import { Menu } from '../Menu/Menu.js' import { MenuItem } from './MenuItem.js' +import { Apps } from '@dhis2/ui-icons' -window.onClick = window.Cypress && window.Cypress.cy.stub() - -const MenuItemPositions = () => ( - - - - - - - +export default { + title: 'Components/Core/MenuItem', + component: MenuItem, + decorators: [storyFn => {storyFn()}], +} + +export const Default = () => + +export const Active = () => + +export const Chevron = () => + +export const Dense = () => + +export const Destructive = () => + +export const Disabled = () => + +export const Link = () => ( + ) -storiesOf('Components/Core/MenuItem', module) - .add('With onClick and value', () => ( - - )) - .add('With href', () => ) - .add('With target', () => ( - - )) - .add('With icon', () => ( - Icon} /> - )) - .add('With label', () => ) - .add('Default position', () => ) - .add('Flipped position', () => ( -
    - - -
    - )) - .add('Shift into view', () => ( -
    - - -
    - )) +export const Icon = () => { + const { className, styles } = resolve` + fill: magenta; + ` + + return ( + <> + } label="Menu item" /> + } + label="Menu item - with custom icon fill" + /> + + {styles} + + ) +} + +export const OnClick = () => ( + { + console.log(payload.value, event.target) + }} + value="myValue" + label="Menu item" + /> +) + +export const ToggleMenuItem = () => { + const [on, setOn] = useState(false) + const toggleOn = () => setOn(!on) + const checkMarkStyle = { fontSize: '24px', lineHeight: '24px' } + const icon = on ? : + + return ( + + ) +} diff --git a/packages/core/src/MenuItem/MenuItem.styles.js b/packages/core/src/MenuItem/MenuItem.styles.js index 9b6e94997c..80f2b2f820 100644 --- a/packages/core/src/MenuItem/MenuItem.styles.js +++ b/packages/core/src/MenuItem/MenuItem.styles.js @@ -3,14 +3,23 @@ import { colors, spacers } from '@dhis2/ui-constants' export default css` li { - position: relative; + display: flex; + align-items: stretch; padding: 0; cursor: pointer; list-style: none; + transition: background-color 150ms ease-in-out; + background-color: ${colors.white}; + color: ${colors.grey900}; + fill: ${colors.grey900}; + font-size: 15px; + line-height: 18px; + user-select: none; } - li div.label:not(:first-child) { - margin-left: ${spacers.dp8}; + li.dense { + font-size: 14px; + line-height: 16px; } li:hover { @@ -22,70 +31,88 @@ export default css` background-color: ${colors.grey400}; } - .dense .link { - padding: ${spacers.dp8} ${spacers.dp12}; + li.destructive { + color: ${colors.red700}; + fill: ${colors.red600}; } - .dense .label { - font-size: 14px; - line-height: 16px; + li.destructive:hover { + background-color: ${colors.red050}; } - .dense .icon { - font-size: 20px; + li.destructive:active, + li.destructive.active { + background-color: ${colors.red100}; } - .disabled { + li.disabled { cursor: not-allowed; - pointer-events: none; - user-select: none; - } - - .disabled .icon, - .disabled .label { color: ${colors.grey500}; + fill: ${colors.grey500}; } - .destructive .label { - color: ${colors.red700}; + li.disabled:hover { + background-color: ${colors.white}; } - .destructive .icon { - color: ${colors.red600}; + a { + display: inline-flex; + flex-grow: 1; + align-items: center; + padding: 0 ${spacers.dp24}; + min-height: 48px; + text-decoration: none; + color: inherit; } - li.destructive:hover { - background-color: ${colors.red050}; + li.with-chevron a { + padding-right: ${spacers.dp8}; } - li.destructive:active { - background-color: ${colors.red100}; + li.dense a { + padding: 0 ${spacers.dp12}; + min-height: 32px; } - .link { - display: block; - height: 100%; - padding: 15px ${spacers.dp24}; - text-decoration: none; - display: flex; - flex-direction: row; - align-items: center; + li.with-chevron.dense a { + padding-right: ${spacers.dp4}; } .label { - color: ${colors.grey900}; - font-size: 15px; - line-height: 18px; - user-select: none; + flex-grow: 1; + padding: 15px 0; } - .icon { - box-sizing: border-box; + li.dense .label { + padding: 8px 0; + } + .icon { + flex-grow: 0; margin-right: ${spacers.dp16}; - color: #404040; - font-size: 24px; - pointer-events: none; - user-select: none; + width: 24px; + height: 24px; + } + + .chevron { + flex-grow: 0; + margin-left: ${spacers.dp48}; + } + + li.dense .icon { + margin-right: ${spacers.dp8}; + width: 16px; + height: 16px; + } + + li .icon > :global(svg) { + width: 24px; + height: 24px; + } + + li.dense .icon > :global(svg), + li .chevron > :global(svg) { + width: 16px; + height: 16px; } ` diff --git a/packages/core/src/MenuItem/MenuItemContent/MenuItemContent.js b/packages/core/src/MenuItem/MenuItemContent/MenuItemContent.js deleted file mode 100644 index 4fc52f98b2..0000000000 --- a/packages/core/src/MenuItem/MenuItemContent/MenuItemContent.js +++ /dev/null @@ -1,110 +0,0 @@ -import React, { forwardRef } from 'react' -import propTypes from '@dhis2/prop-types' -import cx from 'classnames' -import css from 'styled-jsx/css' - -import { ChevronRight } from '@dhis2/ui-icons' - -import styles from './MenuItemContent.styles.js' - -const subChevron = css.resolve` - svg { - margin: 0 -14px 0 auto; - width: 18px; - height: 18px; - pointer-events: none; - user-select: none; - } -` - -const createOnClickHandler = (onClick, value) => evt => { - if (onClick) { - evt.preventDefault() - evt.stopPropagation() - onClick({ value }, evt) - } -} - -const MenuItemContent = forwardRef( - ( - { - href, - onClick, - onMouseOver, - onMouseOut, - target, - icon, - label, - value, - className, - destructive, - disabled, - dense, - active, - dataTest, - showChevron, - }, - ref - ) => { - const isClickable = href || onClick - const LinkElement = isClickable ? 'a' : 'span' - const linkElementProps = isClickable - ? { - href, - onClick: createOnClickHandler(onClick, value), - } - : {} - - return ( -
  • - - {icon} -
    {label}
    - - {showChevron && ( - - )} - {subChevron.styles} -
    - - -
  • - ) - } -) -MenuItemContent.displayName = 'MenuItemContent' - -MenuItemContent.propTypes = { - active: propTypes.bool, - className: propTypes.string, - dataTest: propTypes.string, - dense: propTypes.bool, - destructive: propTypes.bool, - disabled: propTypes.bool, - href: propTypes.string, - icon: propTypes.element, - label: propTypes.oneOfType([propTypes.string, propTypes.node]), - showChevron: propTypes.bool, - target: propTypes.string, - value: propTypes.string, - onClick: propTypes.func, - onMouseOut: propTypes.func, - onMouseOver: propTypes.func, -} - -export { MenuItemContent } diff --git a/packages/core/src/MenuItem/MenuItemContent/MenuItemContent.styles.js b/packages/core/src/MenuItem/MenuItemContent/MenuItemContent.styles.js deleted file mode 100644 index 9b6e94997c..0000000000 --- a/packages/core/src/MenuItem/MenuItemContent/MenuItemContent.styles.js +++ /dev/null @@ -1,91 +0,0 @@ -import css from 'styled-jsx/css' -import { colors, spacers } from '@dhis2/ui-constants' - -export default css` - li { - position: relative; - padding: 0; - cursor: pointer; - list-style: none; - } - - li div.label:not(:first-child) { - margin-left: ${spacers.dp8}; - } - - li:hover { - background-color: ${colors.grey200}; - } - - li:active, - li.active { - background-color: ${colors.grey400}; - } - - .dense .link { - padding: ${spacers.dp8} ${spacers.dp12}; - } - - .dense .label { - font-size: 14px; - line-height: 16px; - } - - .dense .icon { - font-size: 20px; - } - - .disabled { - cursor: not-allowed; - pointer-events: none; - user-select: none; - } - - .disabled .icon, - .disabled .label { - color: ${colors.grey500}; - } - - .destructive .label { - color: ${colors.red700}; - } - - .destructive .icon { - color: ${colors.red600}; - } - - li.destructive:hover { - background-color: ${colors.red050}; - } - - li.destructive:active { - background-color: ${colors.red100}; - } - - .link { - display: block; - height: 100%; - padding: 15px ${spacers.dp24}; - text-decoration: none; - display: flex; - flex-direction: row; - align-items: center; - } - - .label { - color: ${colors.grey900}; - font-size: 15px; - line-height: 18px; - user-select: none; - } - - .icon { - box-sizing: border-box; - - margin-right: ${spacers.dp16}; - color: #404040; - font-size: 24px; - pointer-events: none; - user-select: none; - } -` diff --git a/packages/core/src/MenuItem/SubMenu.js b/packages/core/src/MenuItem/SubMenu.js deleted file mode 100644 index 9a9238f4d1..0000000000 --- a/packages/core/src/MenuItem/SubMenu.js +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useState, useRef } from 'react' - -import propTypes from '@dhis2/prop-types' - -import { Popper } from '../index.js' -import { MenuItemContent } from './MenuItemContent/MenuItemContent.js' - -const createMenuItemMouseOutHandler = (setOpen, popperRef) => event => { - if (!popperRef.current.contains(event.relatedTarget)) { - setOpen(false) - } -} -const createPopperMouseOutHandler = ( - setOpen, - menuItemRef, - popperRef -) => event => { - if ( - !menuItemRef.current.contains(event.relatedTarget) && - !popperRef.current.contains(event.relatedTarget) - ) { - setOpen(false) - } -} - -const SubMenu = ({ - children, - active, - className, - dataTest, - dense, - destructive, - disabled, - icon, - label, -}) => { - const menuItemRef = useRef() - const popperRef = useRef() - const [open, setOpen] = useState(false) - - return ( - <> - setOpen(true)} - onMouseOut={createMenuItemMouseOutHandler(setOpen, popperRef)} - ref={menuItemRef} - /> - {open && ( - -
    - {children} -
    -
    - )} - - ) -} - -SubMenu.propTypes = { - active: propTypes.bool, - children: propTypes.node, - className: propTypes.string, - dataTest: propTypes.string, - dense: propTypes.bool, - destructive: propTypes.bool, - disabled: propTypes.bool, - icon: propTypes.element, - label: propTypes.oneOfType([propTypes.string, propTypes.node]), -} - -export { SubMenu } diff --git a/packages/core/src/MenuList/MenuList.js b/packages/core/src/MenuList/MenuList.js deleted file mode 100644 index 6fc6218df7..0000000000 --- a/packages/core/src/MenuList/MenuList.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react' -import propTypes from '@dhis2/prop-types' - -/** - * @module - * @param {MenuList.PropTypes} - * @returns {React.Component} - * - * @example import { MenuList } from '@dhis2/ui-core' - * - * @see Specification: {@link https://github.com/dhis2/design-system/blob/master/molecules/menu.md|Design system} - * @see Live demo: {@link /demo/?path=/story/menulist--default|Storybook} - */ -const MenuList = ({ children, className, dataTest }) => ( -
      - {children} - - -
    -) - -MenuList.defaultProps = { - dataTest: 'dhis2-uicore-menulist', -} - -/** - * @typedef {Object} PropTypes - * @static - * - * @prop {Node} [children] - * @prop {string} [className] - * @prop {string} [dataTest] - */ -MenuList.propTypes = { - children: propTypes.node, - className: propTypes.string, - dataTest: propTypes.string, -} - -export { MenuList } diff --git a/packages/core/src/MenuList/MenuList.stories.e2e.js b/packages/core/src/MenuList/MenuList.stories.e2e.js deleted file mode 100644 index 1396ebb18d..0000000000 --- a/packages/core/src/MenuList/MenuList.stories.e2e.js +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react' -import { storiesOf } from '@storybook/react' -import { MenuList } from './MenuList.js' - -storiesOf('MenuList', module).add('With children', () => ( - I am a child -)) diff --git a/packages/core/src/MenuList/MenuList.stories.js b/packages/core/src/MenuList/MenuList.stories.js deleted file mode 100644 index 5b5fcef662..0000000000 --- a/packages/core/src/MenuList/MenuList.stories.js +++ /dev/null @@ -1,138 +0,0 @@ -import React from 'react' -import { storiesOf } from '@storybook/react' - -import { MenuItem, Divider, Switch } from '../index.js' -import { MenuList } from './MenuList.js' - -const Wrapper = fn =>
    {fn()}
    - -export const FolderOpen = () => ( - - - - - - - - - - - -) - -storiesOf('Components/Core/MenuList', module) - .addDecorator(Wrapper) - - .add('Default', () => ( - - { - alert(`${evt.target.name}: ${evt.target.value}`) - }} - /> - } - /> - - } - label="Three" - value="three" - onClick={val => { - alert(`this is ${val}`) - }} - /> - - - } - label="Two" - value="two" - onClick={val => { - alert(`this is ${val}`) - }} - /> - } - label="Three" - value="three" - onClick={val => { - alert(`this is ${val}`) - }} - /> - - )) - - .add('Dense', () => ( - - } - dense - label={ - { - alert('would need state, but would set to: ' + v) - }} - checked={false} - /> - } - /> - - } - dense - label="Three" - value="three" - onClick={val => { - alert(`this is ${val}`) - }} - /> - - - } - dense - label="Two" - value="two" - onClick={val => { - alert(`this is ${val}`) - }} - /> - } - dense - label="Three" - value="three" - onClick={val => { - alert(`this is ${val}`) - }} - /> - - )) diff --git a/packages/core/src/MenuSectionHeader/MenuSectionHeader.js b/packages/core/src/MenuSectionHeader/MenuSectionHeader.js new file mode 100644 index 0000000000..4caa0c6885 --- /dev/null +++ b/packages/core/src/MenuSectionHeader/MenuSectionHeader.js @@ -0,0 +1,78 @@ +import React from 'react' +import propTypes from '@dhis2/prop-types' +import cx from 'classnames' +import { colors, spacers } from '@dhis2/ui-constants' + +import { Divider } from '../Divider/Divider.js' + +/** + * @module + * @param {MenuSectionHeader.PropTypes} + * @returns {React.Component} + * + * @example import { MenuSectionHeader } from '@dhis2/ui + * + * @see Specification: {@link https://github.com/dhis2/design-system/blob/master/molecules/menu.md|Design system} + * @see Live demo: {@link /demo/?path=/story/components-core-menusectionheader--default|Storybook} + */ +const MenuSectionHeader = ({ + className, + dataTest, + dense, + hideDivider, + label, +}) => ( +
  • + {!hideDivider && } + +
    {label}
    + + +
  • +) + +MenuSectionHeader.defaultProps = { + dataTest: 'dhis2-uicore-menusectionheader', +} + +/** + * @typedef {Object} PropTypes + * @static + * + * @prop {string} [className] + * @prop {string} [dataTest='dhis2-uicore-menusectionheader'] + * @prop {boolean} [dense] + * @prop {boolean} [hideDivider] + * @prop {Node} [label] + */ +MenuSectionHeader.propTypes = { + className: propTypes.string, + dataTest: propTypes.string, + dense: propTypes.bool, + hideDivider: propTypes.bool, + label: propTypes.node, +} + +export { MenuSectionHeader } diff --git a/packages/core/src/MenuSectionHeader/MenuSectionHeader.stories.e2e.js b/packages/core/src/MenuSectionHeader/MenuSectionHeader.stories.e2e.js new file mode 100644 index 0000000000..4f5ffcaca3 --- /dev/null +++ b/packages/core/src/MenuSectionHeader/MenuSectionHeader.stories.e2e.js @@ -0,0 +1,12 @@ +import React from 'react' + +import { Menu } from '../Menu/Menu.js' +import { MenuSectionHeader } from './MenuSectionHeader.js' + +export default { + title: 'MenuSectionHeader', + component: MenuSectionHeader, + decorators: [storyFn => {storyFn()}], +} + +export const WithLabel = () => diff --git a/packages/core/src/MenuSectionHeader/MenuSectionHeader.stories.js b/packages/core/src/MenuSectionHeader/MenuSectionHeader.stories.js new file mode 100644 index 0000000000..f900f65caf --- /dev/null +++ b/packages/core/src/MenuSectionHeader/MenuSectionHeader.stories.js @@ -0,0 +1,18 @@ +import React from 'react' + +import { Menu } from '../Menu/Menu.js' +import { MenuSectionHeader } from './MenuSectionHeader.js' + +export default { + title: 'Components/Core/MenuSectionHeader', + component: MenuSectionHeader, + decorators: [storyFn => {storyFn()}], +} + +export const Default = () => + +export const Dense = () => + +export const WithoutDivider = () => ( + +) diff --git a/packages/core/src/Popper/Popper.js b/packages/core/src/Popper/Popper.js index f838068434..6da425fa64 100644 --- a/packages/core/src/Popper/Popper.js +++ b/packages/core/src/Popper/Popper.js @@ -84,6 +84,7 @@ class Popper extends Component { Popper.defaultProps = { dataTest: 'dhis2-uicore-popper', modifiers: [], + placement: 'auto', } /** diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 2e204d3e79..a8827e3ca6 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -21,15 +21,18 @@ export { FileInput } from './FileInput/FileInput.js' export { FileList } from './FileList/FileList.js' export { FileListItem } from './FileListItem/FileListItem.js' export { FileListPlaceholder } from './FileListPlaceholder/FileListPlaceholder.js' +export { FlyoutMenu } from './FlyoutMenu/FlyoutMenu.js' export { Help } from './Help/Help.js' export { Input } from './Input/Input.js' export { Label } from './Label/Label.js' +export { Layer, useLayerContext } from './Layer/Layer.js' export { Legend } from './Legend/Legend.js' export { LinearLoader } from './LinearLoader/LinearLoader.js' export { Logo, LogoIcon, LogoIconWhite, LogoWhite } from './Logo/Logo.js' export { Menu } from './Menu/Menu.js' +export { MenuDivider } from './MenuDivider/MenuDivider.js' export { MenuItem } from './MenuItem/MenuItem.js' -export { MenuList } from './MenuList/MenuList.js' +export { MenuSectionHeader } from './MenuSectionHeader/MenuSectionHeader.js' export { Modal } from './Modal/Modal.js' export { ModalActions } from './ModalActions/ModalActions.js' export { ModalContent } from './ModalContent/ModalContent.js' @@ -68,5 +71,4 @@ export { Tag } from './Tag/Tag.js' export { TextArea } from './TextArea/TextArea.js' export { Transfer } from './Transfer/Transfer.js' export { TransferOption } from './Transfer/TransferOption.js' -export { Layer } from './Layer/Layer.js' export { Tooltip } from './Tooltip/Tooltip.js'