From 7194e54a56bbfbcab5b3d39d597fabfa4d49daf7 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Thu, 3 Oct 2024 13:38:57 +0200 Subject: [PATCH] feat: [DHIS2-17874] replace remaining Material UI components (#3794) * feat: remove snackbar * feat: change menu components * feat: remove paper * feat: remove grow * feat: change popover * feat: remove icon button * feat: remove icons * fix: type error for open delay * fix: after review changes * feat: change to icon button * fix: data test prop * fix: increase max width --- i18n/en.pot | 9 - .../FeedbackBar/FeedbackBar.component.js | 136 +++------- .../UserField/SearchSuggestion.component.js | 38 +-- .../IncompleteSelectionsMessage/index.js | 26 +- .../ActiveFilterButton.component.js | 14 +- .../FilterButtonMain.component.js | 58 ++--- .../FilterRestMenu.component.js | 239 ++++-------------- .../ListView/Menu/ListViewMenu.component.js | 73 +++--- .../ListView/types/listView.types.js | 3 +- .../withEndColumnMenu/RowMenu.component.js | 208 ++++----------- .../NetworkStatusBadge.component.js | 155 ------------ .../Section/ViewEventSection.component.js | 173 ++++++++----- .../components/Popper/Popper.component.js | 107 -------- .../Relationships/Relationships.component.js | 15 +- .../WidgetError/WidgetErrorHeader.js | 5 +- .../WidgetWarning/WidgetWarningHeader.js | 5 +- .../StageOverview/StageOverview.component.js | 22 +- .../hooks/useAddRelationship.js | 4 +- ...EventWorkingListsRowMenuSetup.component.js | 26 +- .../eventWorkingListsRowMenuSetup.types.js | 1 - .../Icons/DropdownChevron.component.js | 35 +++ src/core_modules/capture-ui/Icons/index.js | 1 + 22 files changed, 410 insertions(+), 943 deletions(-) delete mode 100644 src/core_modules/capture-core/components/NetworkStatusBadge/NetworkStatusBadge.component.js delete mode 100644 src/core_modules/capture-core/components/Popper/Popper.component.js create mode 100644 src/core_modules/capture-ui/Icons/DropdownChevron.component.js diff --git a/i18n/en.pot b/i18n/en.pot index 5d63ac1048..d299bcedcf 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -598,15 +598,6 @@ msgstr "Program doesn't exist" msgid "Selected program is invalid for selected organisation unit" msgstr "Selected program is invalid for selected organisation unit" -msgid "Online" -msgstr "Online" - -msgid "Offline" -msgstr "Offline" - -msgid "Syncing" -msgstr "Syncing" - msgid "Add note" msgstr "Add note" diff --git a/src/core_modules/capture-core/components/FeedbackBar/FeedbackBar.component.js b/src/core_modules/capture-core/components/FeedbackBar/FeedbackBar.component.js index f4406742cc..166a351e1d 100644 --- a/src/core_modules/capture-core/components/FeedbackBar/FeedbackBar.component.js +++ b/src/core_modules/capture-core/components/FeedbackBar/FeedbackBar.component.js @@ -1,11 +1,8 @@ // @flow -import * as React from 'react'; -import SnackBar from '@material-ui/core/Snackbar'; +import React, { type Node } from 'react'; import { withStyles } from '@material-ui/core/styles'; -import { IconButton } from 'capture-ui'; -import { IconCross24, Button, Modal, ModalTitle, ModalContent, ModalActions } from '@dhis2/ui'; +import { Button, Modal, ModalTitle, ModalContent, ModalActions, AlertStack, AlertBar } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; -import isDefined from 'd2-utilizr/lib/isDefined'; const styles = () => ({ closeButton: { @@ -17,110 +14,43 @@ const styles = () => ({ }); type Feedback = { - message: string | { title: string, content: string}, - action?: ?React.Node, - displayType?: ?string, + message: string | { title: string, content: string }, + action?: Node, + displayType?: 'alert' | 'dialog', }; type Props = { feedback: Feedback, onClose: () => void, - classes: Object, }; -class Index extends React.Component { - static defaultProps = { - feedback: {}, - }; - - constructor(props: Props) { - super(props); - this.handleClose = this.handleClose.bind(this); - } - static CLICKAWAY_KEY = 'clickaway'; - - static ANCHOR_ORIGIN = { - vertical: 'bottom', - horizontal: 'center', - }; - - handleClose = (event?: ?Object, reason?: ?string) => { - if (reason !== Index.CLICKAWAY_KEY) { - this.props.onClose(); - } - } - - getAction() { - const { feedback, classes } = this.props; - - return ( - <> - { - (() => { - if (!feedback.action) { - return null; - } - - return ( - - {feedback.action} - - ); - })() - } - - - - - ); - } - - render() { - const { feedback } = this.props; - const { message, displayType } = feedback; - const isSnackBarOpen = isDefined(message) && !displayType; - const isDialogOpen = isDefined(message) && displayType === 'dialog'; - return ( - - {message}} - action={this.getAction()} - /> - {isDialogOpen && ( - - - { - // $FlowFixMe[prop-missing] automated comment - isDialogOpen ? message && message.title : ''} - - - { - // $FlowFixMe[prop-missing] automated comment - isDialogOpen ? message && message.content : ''} - - - - - +const FeedbackBarComponentPlain = ({ feedback = {}, onClose }: Props) => { + const { message, displayType } = feedback; + const isAlertBarOpen = typeof message === 'string' && !displayType; + const isDialogOpen = typeof message === 'object' && displayType === 'dialog'; + + return ( + <> + + {isAlertBarOpen && ( + + {message} + )} - - ); - } -} -Index.displayName = 'FeedbackBar'; + + {isDialogOpen && ( + + {message?.title || ''} + {message?.content || ''} + + + + + )} + + ); +}; -export const FeedbackBarComponent = withStyles(styles)(Index); +export const FeedbackBarComponent = withStyles(styles)(FeedbackBarComponentPlain); diff --git a/src/core_modules/capture-core/components/FormFields/UserField/SearchSuggestion.component.js b/src/core_modules/capture-core/components/FormFields/UserField/SearchSuggestion.component.js index 754b3a464e..6ebff0dea1 100644 --- a/src/core_modules/capture-core/components/FormFields/UserField/SearchSuggestion.component.js +++ b/src/core_modules/capture-core/components/FormFields/UserField/SearchSuggestion.component.js @@ -1,7 +1,7 @@ // @flow import * as React from 'react'; import parse from 'autosuggest-highlight/parse'; -import MenuItem from '@material-ui/core/MenuItem'; +import { MenuItem } from '@dhis2/ui'; import classNames from 'classnames'; import { SearchContext } from './Search.context'; import defaultClasses from './searchSuggestion.module.css'; @@ -96,7 +96,6 @@ export const SearchSuggestion = (props: Props) => { onExitSearch(); } }, [onExitSearch, suggestionName, inputName]); - return (
{ onBlur={handleBlur} > -
- {parts.map((part, index) => (part.highlight ? ( - // eslint-disable-next-line react/no-array-index-key - - {part.text} - - ) : ( - // eslint-disable-next-line react/no-array-index-key - - {part.text} - - )))} -
-
+ active={isHighlighted} + label={( +
+ {parts.map((part, index) => (part.highlight ? ( + // eslint-disable-next-line react/no-array-index-key + + {part.text} + + ) : ( + // eslint-disable-next-line react/no-array-index-key + + {part.text} + + )))} +
+ )} + /> +
); }; diff --git a/src/core_modules/capture-core/components/IncompleteSelectionsMessage/index.js b/src/core_modules/capture-core/components/IncompleteSelectionsMessage/index.js index 6c206f72b6..411539110b 100644 --- a/src/core_modules/capture-core/components/IncompleteSelectionsMessage/index.js +++ b/src/core_modules/capture-core/components/IncompleteSelectionsMessage/index.js @@ -1,29 +1,23 @@ import React from 'react'; -import Paper from '@material-ui/core/Paper/Paper'; -import { withStyles } from '@material-ui/core'; import { colors } from '@dhis2/ui'; -const StyledPaper = withStyles({ - root: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - background: colors.grey200, - color: colors.grey700, - padding: '12px 16px', - }, -})(Paper); - const containerStyle = { display: 'flex', justifyContent: 'center', - width: '100%', +}; + +const messageBoxStyle = { + alignItems: 'center', + background: colors.grey200, + color: colors.grey700, + padding: '12px 16px', + borderRadius: '4px', }; export const IncompleteSelectionsMessage = ({ children, dataTest = 'informative-paper' }) => (
- +
{children} - +
); diff --git a/src/core_modules/capture-core/components/ListView/Filters/FilterButton/ActiveFilterButton.component.js b/src/core_modules/capture-core/components/ListView/Filters/FilterButton/ActiveFilterButton.component.js index f9fa85838f..fddd32eb76 100644 --- a/src/core_modules/capture-core/components/ListView/Filters/FilterButton/ActiveFilterButton.component.js +++ b/src/core_modules/capture-core/components/ListView/Filters/FilterButton/ActiveFilterButton.component.js @@ -1,16 +1,20 @@ // @flow import * as React from 'react'; import { withStyles } from '@material-ui/core/styles'; -import createSvgIcon from '@material-ui/icons/utils/createSvgIcon'; import { Tooltip, Button } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; import classNames from 'classnames'; -const ClearIcon = createSvgIcon( - +const ClearIcon = ({ className, ...props }) => ( + - , - 'CloseCircle', + ); const getStyles = (theme: Theme) => ({ diff --git a/src/core_modules/capture-core/components/ListView/Filters/FilterButton/FilterButtonMain.component.js b/src/core_modules/capture-core/components/ListView/Filters/FilterButton/FilterButtonMain.component.js index 8a1c83ddd1..c06d546ba2 100644 --- a/src/core_modules/capture-core/components/ListView/Filters/FilterButton/FilterButtonMain.component.js +++ b/src/core_modules/capture-core/components/ListView/Filters/FilterButton/FilterButtonMain.component.js @@ -1,9 +1,9 @@ // @flow import React, { Component } from 'react'; import { withStyles } from '@material-ui/core/styles'; -import Popover from '@material-ui/core/Popover'; -import { IconChevronDown16, IconChevronUp16, Button } from '@dhis2/ui'; +import { Button, Popover } from '@dhis2/ui'; import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; +import { ChevronDown, ChevronUp } from 'capture-ui/Icons'; import { ActiveFilterButton } from './ActiveFilterButton.component'; import { FilterSelectorContents } from '../Contents'; import type { UpdateFilter, ClearFilter, RemoveFilter } from '../../types'; @@ -11,8 +11,9 @@ import type { FilterData, Options } from '../../../FiltersForTypes'; const getStyles = (theme: Theme) => ({ icon: { - fontSize: theme.typography.pxToRem(20), - paddingLeft: theme.typography.pxToRem(5), + paddingLeft: theme.typography.pxToRem(12), + display: 'flex', + alignItems: 'center', }, inactiveFilterButton: { backgroundColor: '#f5f5f5', @@ -22,15 +23,6 @@ const getStyles = (theme: Theme) => ({ }, }); -const POPOVER_ANCHOR_ORIGIN = { - vertical: 'bottom', - horizontal: 'left', -}; -const POPOVER_TRANSFORM_ORIGIN = { - vertical: 'top', - horizontal: 'left', -}; - type Props = { itemId: string, type: string, @@ -142,11 +134,11 @@ class FilterButtonMainPlain extends Component { const arrowIconElement = selectorVisible ? ( - + ) : ( - + ); @@ -178,7 +170,7 @@ class FilterButtonMainPlain extends Component { > {title} - {selectorVisible ? : } + {selectorVisible ? : } @@ -199,22 +191,24 @@ class FilterButtonMainPlain extends Component { > {button} - - { - (() => { - if (selectorVisible) { - return this.renderSelectorContents(); - } - return null; - })() - } - + {(selectorVisible && isMounted) && ( + + { + (() => { + if (selectorVisible) { + return this.renderSelectorContents(); + } + return null; + })() + } + + )} ); } diff --git a/src/core_modules/capture-core/components/ListView/Filters/FilterRestMenu/FilterRestMenu.component.js b/src/core_modules/capture-core/components/ListView/Filters/FilterRestMenu/FilterRestMenu.component.js index 97ca1b48e8..598911c49c 100644 --- a/src/core_modules/capture-core/components/ListView/Filters/FilterRestMenu/FilterRestMenu.component.js +++ b/src/core_modules/capture-core/components/ListView/Filters/FilterRestMenu/FilterRestMenu.component.js @@ -1,203 +1,52 @@ // @flow -import React from 'react'; -import { withStyles } from '@material-ui/core/styles'; -import { Card, IconChevronDown16, IconChevronUp16, Button, Layer } from '@dhis2/ui'; - -import { Manager, Popper, Reference } from 'react-popper'; -import Grow from '@material-ui/core/Grow'; -import MenuList from '@material-ui/core/MenuList'; -import MenuItem from '@material-ui/core/MenuItem'; +import React, { useState, useCallback } from 'react'; +import { FlyoutMenu, MenuItem, DropdownButton } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; - -import type { Column } from '../../types'; - -const getStyles = (theme: Theme) => ({ - icon: { - fontSize: theme.typography.pxToRem(20), - paddingLeft: theme.typography.pxToRem(5), - }, - restMenuButton: { - backgroundColor: '#f5f5f5', - }, - restMenuButtonLabel: { - textTransform: 'none', - }, - menuPaper: { - maxHeight: 280, - overflowY: 'auto', - }, - menuItemRoot: { - padding: 6, - paddingLeft: 24, - paddingRight: 24, - fontSize: theme.typography.pxToRem(14), - }, - popperContainerHidden: { - display: 'none', - }, - popper: { - zIndex: 1, - }, -}); +import type { Column, FilterOnly } from '../../types'; type Props = { - columns: Array, + columns: Array, onItemSelected: (id: string) => void, - classes: { - icon: string, - restMenuButton: string, - restMenuButtonLabel: string, - menuPaper: string, - menuItemRoot: string, - popperContainerHidden: string, - }, -}; - -type State = { - filterSelectorOpen: boolean, }; -class FilterRestMenuPlain extends React.Component { - menuClasses: Object; - menuItemClasses: Object; - managerRef: any; - menuReferenceInstance: ?HTMLDivElement; - constructor(props: Props) { - super(props); - this.state = { - filterSelectorOpen: false, - }; - this.setClassesOnMount(); - } - - setClassesOnMount() { - const classes = this.props.classes; - this.menuClasses = { - paper: classes.menuPaper, - }; - - this.menuItemClasses = { - root: classes.menuItemRoot, - }; - } - closeMenu() { - this.setState({ - filterSelectorOpen: false, - }); - } - - toggleMenu() { - this.setState({ - filterSelectorOpen: !this.state.filterSelectorOpen, - }); - } - - handleMenuButtonClick = () => { - this.toggleMenu(); - } - - handleClickAway = (event: any) => { - if (this.menuReferenceInstance && this.menuReferenceInstance.contains(event.target)) { - return; - } - this.closeMenu(); - } - - handleItemSelected = (id: string) => { - this.closeMenu(); - this.props.onItemSelected(id); - } - - renderMenuItems() { - const columns = this.props.columns; - return columns - .map(column => ( - { this.handleItemSelected(column.id); }} - classes={this.menuItemClasses} +export const FilterRestMenu = ({ columns, onItemSelected }: Props) => { + const [filterSelectorOpen, setFilterSelectorOpen] = useState(false); + + const toggleMenu = useCallback(() => { + setFilterSelectorOpen(prevState => !prevState); + }, []); + + const handleItemSelected = useCallback((id: string) => { + setFilterSelectorOpen(false); + onItemSelected(id); + }, [onItemSelected]); + + const renderMenuItems = useCallback(() => ( + columns.map(column => ( + handleItemSelected(column.id)} + label={column.header} + /> + )) + ), [columns, handleItemSelected]); + + return ( + - {column.header} - - )); - } - - handleReferenceInstanceRetrieved = (instance) => { - this.managerRef(instance); - this.menuReferenceInstance = instance; - } - - render() { - const { classes } = this.props; - - return ( - - - { - ({ ref }) => { - this.managerRef = ref; - return ( -
- -
- ); - } - } -
- {this.state.filterSelectorOpen && - - - { - ({ ref, style, placement }) => ( -
- - - - {this.renderMenuItems()} - - - -
- ) - } -
-
- } -
- ); - } -} - -export const FilterRestMenu = withStyles(getStyles)(FilterRestMenuPlain); + {renderMenuItems()} + + } + > + {i18n.t('More filters')} + + ); +}; diff --git a/src/core_modules/capture-core/components/ListView/Menu/ListViewMenu.component.js b/src/core_modules/capture-core/components/ListView/Menu/ListViewMenu.component.js index 07611adc28..8acb2bcf59 100644 --- a/src/core_modules/capture-core/components/ListView/Menu/ListViewMenu.component.js +++ b/src/core_modules/capture-core/components/ListView/Menu/ListViewMenu.component.js @@ -1,11 +1,9 @@ // @flow -import React, { useCallback, memo, type ComponentType } from 'react'; +import React, { useState, useRef, memo, useCallback, type ComponentType } from 'react'; import { IconButton } from 'capture-ui'; -import { withStyles } from '@material-ui/core/styles'; -import { Divider, IconMore24, Card } from '@dhis2/ui'; -import { MenuList, MenuItem } from '@material-ui/core'; +import { MenuItem, Layer, Popper, IconMore24, FlyoutMenu, Divider } from '@dhis2/ui'; +import { withStyles } from '@material-ui/core'; -import { MenuPopper } from '../../Popper/Popper.component'; import type { Props } from './listViewMenu.types'; const getStyles = () => ({ @@ -31,16 +29,15 @@ const getStyles = () => ({ }); const ListViewMenuPlain = ({ customMenuContents = [], classes }: Props) => { - const renderPopperAction = useCallback((togglePopper: Function) => ( - - - - ), []); + const anchorRef = useRef(null); + const [actionsIsOpen, setActionsIsOpen] = useState(false); - const renderMenuItems = useCallback((togglePopper: Function) => + const toggle = () => { + setActionsIsOpen(prev => !prev); + }; + + + const renderMenuItems = useCallback(() => customMenuContents .map((content) => { if (content.subHeader) { @@ -70,40 +67,42 @@ const ListViewMenuPlain = ({ customMenuContents = [], classes }: Props) => { if (!content.clickHandler) { return; } - togglePopper(); + setActionsIsOpen(false); // $FlowFixMe Using exact types, in my book this should work. Please tell me what I'm missing. content.clickHandler(); }} // $FlowFixMe Using exact types, in my book this should work. Please tell me what I'm missing. disabled={!content.clickHandler} - > - { - // $FlowFixMe Using exact types, in my book this should work. Please tell me what I'm missing. - content.element} -
+ // $FlowFixMe Using exact types, in my book this should work. Please tell me what I'm missing. + label={content.element} + /> ); }) .flat(1), [customMenuContents, classes]); - const renderPopperContent = useCallback((togglePopper: Function) => ( - - - {renderMenuItems(togglePopper)} - - - ), [renderMenuItems]); - - if (!customMenuContents.length) { - return null; - } - return ( - +
+ + + + {actionsIsOpen && ( + setActionsIsOpen(false)} transparent> + + + {renderMenuItems()} + + + + )} +
); }; export const ListViewMenu: ComponentType<$Diff> = - memo<$Diff>(withStyles(getStyles)(ListViewMenuPlain)); + memo < $Diff>(withStyles(getStyles)(ListViewMenuPlain)); diff --git a/src/core_modules/capture-core/components/ListView/types/listView.types.js b/src/core_modules/capture-core/components/ListView/types/listView.types.js index 011d70d6fb..96f98f4c90 100644 --- a/src/core_modules/capture-core/components/ListView/types/listView.types.js +++ b/src/core_modules/capture-core/components/ListView/types/listView.types.js @@ -60,7 +60,8 @@ export type CustomMenuContents = Array; export type CustomRowMenuContent = {| key: string, clickHandler?: ?(rowData: DataSourceItem) => any, - element: React$Node, + icon: React$Node, + label: string, |}; export type CustomRowMenuContents = Array; diff --git a/src/core_modules/capture-core/components/ListView/withEndColumnMenu/RowMenu.component.js b/src/core_modules/capture-core/components/ListView/withEndColumnMenu/RowMenu.component.js index 1ba6d043c5..68d0706fce 100644 --- a/src/core_modules/capture-core/components/ListView/withEndColumnMenu/RowMenu.component.js +++ b/src/core_modules/capture-core/components/ListView/withEndColumnMenu/RowMenu.component.js @@ -1,160 +1,58 @@ // @flow -import * as React from 'react'; -import { Manager, Popper, Reference } from 'react-popper'; -import { Card, spacers, IconMore24, colors, Layer } from '@dhis2/ui'; -import Grow from '@material-ui/core/Grow'; -import MenuList from '@material-ui/core/MenuList'; -import MenuItem from '@material-ui/core/MenuItem'; -import withStyles from '@material-ui/core/styles/withStyles'; -import type { Props, State } from './rowMenu.types'; - -const styles = theme => ({ - deleteIcon: { - fill: theme.palette.error.main, - }, - menuList: { - padding: 0, - }, - popperContainer: { - zIndex: 100, - }, - iconButton: { - display: 'flex', - borderRadius: '50%', - border: 'none', - cursor: 'pointer', - background: 'transparent', - padding: spacers.dp12, - marginTop: `-${spacers.dp12}`, - marginBottom: `-${spacers.dp12}`, - color: colors.grey600, - '&:hover': { - background: colors.grey400, - }, - }, -}); - -class Index extends React.Component { - managerRef: any; - menuReferenceInstance: ?HTMLDivElement; - - constructor(props: Props) { - super(props); - this.state = { menuOpen: false }; - } - - handleReferenceInstanceRetrieved = (instance) => { - this.managerRef(instance); - this.menuReferenceInstance = instance; - } - - toggleMenu = (event: any) => { - this.setState({ - menuOpen: !this.state.menuOpen, - }); - event.stopPropagation(); - } - - closeMenu = () => { - this.setState({ - menuOpen: false, - }); - } - - handleClickAway = (event: any) => { - if (this.menuReferenceInstance && this.menuReferenceInstance.contains(event.target)) { - return; - } - this.closeMenu(); - } - - renderMenuItems = () => { - const { customRowMenuContents = [], row, classes } = this.props; - - const menuItems = customRowMenuContents - .map(content => ( - ) => { - if (!content.clickHandler) { - return; - } - this.closeMenu(); - // $FlowFixMe common flow, I checked this 4 lines up - content.clickHandler(row); - event.stopPropagation(); - }} - disabled={!content.clickHandler} - > - {content.element} - - )); - - return ( - - {menuItems} - - ); - } - - render() { - const { classes } = this.props; - return ( - - - { - ({ ref }) => { - this.managerRef = ref; - return ( -
- -
- ); - } - } -
- {this.state.menuOpen && - +import React, { useState, useRef } from 'react'; +import { IconButton } from 'capture-ui'; +import { MenuItem, Layer, Popper, IconMore24, FlyoutMenu } from '@dhis2/ui'; +import type { Props } from './rowMenu.types'; + +export const RowMenu = (props: Props) => { + const { customRowMenuContents = [], row } = props; + + const anchorRef = useRef(null); + const [actionsIsOpen, setActionsIsOpen] = useState(false); + + const toggle = () => { + setActionsIsOpen(prev => !prev); + }; + + const renderMenuItems = () => customRowMenuContents.map(content => ( + { + if (!content.clickHandler) { + return; + } + setActionsIsOpen(false); + // $FlowFixMe common flow, I checked this 4 lines up + content.clickHandler(row); + }} + disabled={!content.clickHandler} + label={content.label} + icon={content.icon} + /> + )); + + return ( +
+ + + + {actionsIsOpen && ( + setActionsIsOpen(false)} transparent> - { - ({ ref, style, placement }) => ( -
- - - {this.renderMenuItems()} - - -
- ) - } + + {renderMenuItems()} +
- } - - ); - } -} - -export const RowMenu = withStyles(styles)(Index); + )} +
+ ); +}; diff --git a/src/core_modules/capture-core/components/NetworkStatusBadge/NetworkStatusBadge.component.js b/src/core_modules/capture-core/components/NetworkStatusBadge/NetworkStatusBadge.component.js deleted file mode 100644 index 2cdd4be23f..0000000000 --- a/src/core_modules/capture-core/components/NetworkStatusBadge/NetworkStatusBadge.component.js +++ /dev/null @@ -1,155 +0,0 @@ -import { connect } from 'react-redux'; -import React, { PureComponent } from 'react'; -import { IconSynk16 } from '@dhis2/ui'; -import Grow from '@material-ui/core/Grow'; -import { withStyles } from '@material-ui/core/styles'; -import moment from 'moment'; -import i18n from '@dhis2/d2-i18n'; - -const styles = () => ({ - offlineIcon: { - backgroundColor: '#9e9e9e', - width: '8px', - height: '8px', - borderRadius: '4px', - display: 'inline-block', - marginRight: '6px', - }, - onlineIcon: { - backgroundColor: '#48a999', - width: '8px', - height: '8px', - borderRadius: '4px', - display: 'inline-block', - marginRight: '6px', - }, - badgeContainer: { - backgroundColor: '#16486e', - color: 'white', - borderRadius: '4px', - whiteSpace: 'nowrap', - }, - badgeSection: { - padding: '8px', - }, - badgeSeparator: { - borderRight: '1px solid #0b3b60', - }, - flex: { - display: 'flex', - flexWrap: 'nowrap', - alignItems: 'center', - justifyContent: 'space-between', - }, - icon: { - animationName: 'Sync-spin', - animationDuration: '1.5s', - animationIterationCount: 'infinite', - animationDelay: '250ms', - animationDirection: 'reverse', - width: '16px', - height: '16px', - }, - text: { - margin: 0, - padding: 0, - }, - smallText: { - fontSize: '14px', - }, - '@keyframes Sync-spin': { - from: { transform: 'rotate(0deg)' }, - to: { transform: 'rotate(360deg)' }, - }, -}); - -const RightSection = props => - (
- -

{ props.status ? i18n.t('Online') : i18n.t('Offline') }

-
); - -class LeftSection extends PureComponent { - constructor(props) { - super(props); - this.state = { offlineTimer: moment(Date.now()).fromNow() }; - } - - componentDidUpdate() { - if (this.props.statusTimer === 0) { - clearInterval(this.timer); - this.timer = null; - // todo (eslint) - // eslint-disable-next-line react/no-did-update-set-state - this.setState({ offlineTimer: moment(Date.now()).fromNow() }); - return; - } - - if (this.props.statusTimer > 0 && !this.timer) { - this.timer = setInterval(() => { - const t1 = moment(this.props.statusTimer).fromNow(); - this.setState({ offlineTimer: t1 }); - }, 60 * 1000); - } - } - - render() { - const props = this.props; - let content; - - if (props.status && props.syncList.length === 0) { - return null; - } - - if (props.status && props.syncList.length > 0) { - content = ( -
- - - -

{i18n.t('Syncing')}

-
- ); - } - - if (!props.status) { - content = {props.syncList.length} {i18n.t('offline events. Last synced:')} {this.state.offlineTimer}; - } - - const show = !!content; - - return ( - -
- {content} -
-
- ); - } -} - -const OnlineIcon = props => ; - -// eslint-disable-next-line react/no-multi-comp -class NetworkStatusBadgePlain extends PureComponent { - render() { - const status = this.props.offline || {}; - const classes = this.props.classes; - - const itemsToSync = status.outbox; - - return ( -
- - -
- ); - } -} - -const mapStateToProps = state => ({ - offline: state.offline, - networkStatus: state.networkStatus, -}); - -export const NetworkStatusBadge = connect(mapStateToProps)(withStyles(styles)(NetworkStatusBadgePlain)); diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/Section/ViewEventSection.component.js b/src/core_modules/capture-core/components/Pages/ViewEvent/Section/ViewEventSection.component.js index 36db5aebae..69eca570fc 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/Section/ViewEventSection.component.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/Section/ViewEventSection.component.js @@ -1,10 +1,11 @@ // @flow +import React, { useState, useEffect, useRef } from 'react'; +import { withStyles } from '@material-ui/core'; +import { IconButton } from 'capture-ui'; +import cx from 'classnames'; +import { IconChevronUp24, colors, spacersNum } from '@dhis2/ui'; -import * as React from 'react'; -import { withStyles, IconButton } from '@material-ui/core'; -import { IconChevronDown24, IconChevronUp24, colors } from '@dhis2/ui'; - -const getStyles = (theme: Theme) => ({ +const getStyles = theme => ({ container: { background: colors.white, border: '1px solid', @@ -17,86 +18,132 @@ const getStyles = (theme: Theme) => ({ justifyContent: 'space-between', padding: theme.typography.pxToRem(5), minHeight: theme.typography.pxToRem(42), - }, toggleCollapseButton: { padding: 4, }, - contentContainer: { + children: { padding: theme.typography.pxToRem(10), borderTop: `1px solid ${theme.palette.grey.blueGrey}`, + '&.open': { + animation: 'slidein 200ms normal forwards ease-in-out', + transformOrigin: '50% 0%', + }, + '&.close': { + animation: 'slideout 200ms normal forwards ease-in-out', + transformOrigin: '100% 0%', + }, + }, + toggleButton: { + margin: `0 0 0 ${spacersNum.dp4}px`, + height: '24px', + borderRadius: '3px', + color: colors.grey600, + '&:hover': { + background: colors.grey200, + color: colors.grey800, + }, + '&.open': { + animation: 'flipOpen 200ms normal forwards linear', + }, + '&.close': { + animation: 'flipClose 200ms normal forwards linear', + }, + '&.closeinit': { + transform: 'rotateX(180deg)', + }, + }, + '@keyframes slidein': { + from: { transform: 'scaleY(0)' }, + to: { transform: 'scaleY(1)' }, + }, + '@keyframes slideout': { + from: { transform: 'scaleY(1)' }, + to: { transform: 'scaleY(0)' }, + }, + '@keyframes flipOpen': { + from: { transform: 'rotateX(180deg)' }, + to: { transform: 'rotateX(0)' }, + }, + '@keyframes flipClose': { + from: { transform: 'rotateX(0)' }, + to: { transform: 'rotateX(180deg)' }, }, }); type Props = { - header: React.Element, - children: React.Element, + header: React$Node, + children: React$Node, collapsable?: ?boolean, collapsed?: ?boolean, classes: Object, -} - -type State = { - collapsed: ?boolean, -} +}; -class ViewEventSectionPlain extends React.Component { - constructor(props) { - super(props); - this.state = { - collapsed: this.props.collapsed || false, - }; - } +const ViewEventSectionPlain = ({ + header, + collapsable = false, + collapsed: propsCollapsed = false, + children, + classes, +}: Props) => { + const [collapsed, setCollapsed] = useState(propsCollapsed); + const [childrenVisible, setChildrenVisibility] = useState(!propsCollapsed); + const [animationsReady, setAnimationsReadyStatus] = useState(false); + const hideChildrenTimeoutRef = useRef(null); + const initialRenderRef = useRef(true); - UNSAFE_componentWillReceiveProps(nextProps: Props) { - if (nextProps.collapsed !== this.props.collapsed) { - this.setState({ - collapsed: nextProps.collapsed, - }); + useEffect(() => { + if (initialRenderRef.current) { + initialRenderRef.current = false; + setAnimationsReadyStatus(false); + return; } - } - toggleCollapse = () => { - this.setState({ - collapsed: !this.state.collapsed, - }); - } + setAnimationsReadyStatus(true); - renderCollapsable = () => { - const classes = this.props.classes; + if (!collapsed) { + setChildrenVisibility(true); + } else { + hideChildrenTimeoutRef.current = setTimeout(() => { + setChildrenVisibility(false); + }, 200); + } + }, [collapsed]); - return ( - - {this.state.collapsed ? - : - - } - - ); - } + const toggleCollapse = () => { + setCollapsed(prev => !prev); + }; - renderContent = () => { - const { children, classes } = this.props; - return ( -
- {children} + return ( +
+
+ {header} + {collapsable && ( + + + + )}
- ); - } - - render() { - const { header, collapsable, classes } = this.props; - return ( -
-
- {header} - {collapsable && this.renderCollapsable()} + {childrenVisible && ( +
+ {children}
- {!this.state.collapsed && this.renderContent()} -
- ); - } -} + )} +
+ ); +}; export const ViewEventSection = withStyles(getStyles)(ViewEventSectionPlain); diff --git a/src/core_modules/capture-core/components/Popper/Popper.component.js b/src/core_modules/capture-core/components/Popper/Popper.component.js deleted file mode 100644 index 832de14135..0000000000 --- a/src/core_modules/capture-core/components/Popper/Popper.component.js +++ /dev/null @@ -1,107 +0,0 @@ -// @flow -import * as React from 'react'; -import { Manager, Popper, Reference } from 'react-popper'; -import type { Placement } from '@popperjs/core/lib'; -import { Layer } from '@dhis2/ui'; -import Grow from '@material-ui/core/Grow'; - -type Props = { - getPopperAction: (togglePopper: () => void) => React.Node, - getPopperContent: (togglePopper: Function) => React.Node, - placement: Placement, - classes?: ?Object, -} - -type State = { - popperOpen: ?boolean, -} - -export class MenuPopper extends React.Component { - managerRef: any; - menuReferenceInstance: ?HTMLDivElement; - - static defaultProps = { - placement: 'bottom-end', - } - - constructor(props: Props) { - super(props); - this.state = { popperOpen: false }; - } - - handleReferenceInstanceRetrieved = (instance: any) => { - this.managerRef(instance); - this.menuReferenceInstance = instance; - } - - toggleMenu = (event?: any) => { - this.setState({ - popperOpen: !this.state.popperOpen, - }); - event && event.stopPropagation(); - } - - closeMenu = () => { - this.setState({ - popperOpen: false, - }); - } - - handleClickAway = (event: any) => { - if (this.menuReferenceInstance && this.menuReferenceInstance.contains(event.target)) { - return; - } - this.closeMenu(); - } - - render() { - const { classes, getPopperAction, getPopperContent } = this.props; - - return ( - - - { - ({ ref }) => { - this.managerRef = ref; - return ( -
- {getPopperAction(this.toggleMenu)} -
- ); - } - } -
- {this.state.popperOpen && - - - { - ({ ref, style, placement }) => ( -
- - - {getPopperContent(this.toggleMenu)} - - -
- ) - } -
-
} -
- ); - } -} diff --git a/src/core_modules/capture-core/components/Relationships/Relationships.component.js b/src/core_modules/capture-core/components/Relationships/Relationships.component.js index 5e4b38fbf1..a0fe19e867 100644 --- a/src/core_modules/capture-core/components/Relationships/Relationships.component.js +++ b/src/core_modules/capture-core/components/Relationships/Relationships.component.js @@ -3,8 +3,9 @@ import * as React from 'react'; import classNames from 'classnames'; import i18n from '@dhis2/d2-i18n'; -import { IconButton, withStyles } from '@material-ui/core'; -import { IconArrowRight16, IconCross24, Button } from '@dhis2/ui'; +import { withStyles } from '@material-ui/core'; +import { IconButton } from 'capture-ui'; +import { IconArrowRight16, IconDelete16, Button, colors } from '@dhis2/ui'; import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import type { RelationshipType } from '../../metaData'; import type { Relationship, Entity } from './relationships.types'; @@ -23,7 +24,7 @@ const getStyles = (theme: Theme) => ({ relationshipTypeName: { fontSize: 14, fontWeight: 600, - color: 'rgba(0,0,0,0.7)', + color: colors.grey700, }, relationshipsContainer: { }, @@ -84,6 +85,7 @@ type Props = { relationshipActions: string, relationshipHighlight: string, tooltip: string, + deleteButton: string, addButtonContainer: string, }, relationships: Array, @@ -145,12 +147,14 @@ class RelationshipsPlain extends React.Component { wrapperClassName={classes.tooltip} > { onRemoveRelationship(relationship.clientId); }} disabled={!canDelete} + secondary > - + +
@@ -203,6 +207,7 @@ class RelationshipsPlain extends React.Component { disabled={!canCreate} small={smallMainButton} dataTest="add-relationship-button" + secondary > {i18n.t('Add relationship')} diff --git a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetErrorHeader.js b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetErrorHeader.js index 1544fb4057..defa892c94 100644 --- a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetErrorHeader.js +++ b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetErrorHeader.js @@ -1,8 +1,7 @@ // @flow import React from 'react'; import { withStyles } from '@material-ui/core'; -import { ErrorRounded } from '@material-ui/icons'; -import { spacers, colors } from '@dhis2/ui'; +import { spacers, colors, IconErrorFilled24 } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; const styles = { @@ -20,7 +19,7 @@ const styles = { const WidgetErrorHeaderPlain = ({ classes }) => ( - +

{i18n.t('Error')}

); diff --git a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarningHeader.js b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarningHeader.js index 25eca6d680..9483be440f 100644 --- a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarningHeader.js +++ b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarningHeader.js @@ -1,7 +1,6 @@ // @flow -import { colors, spacers } from '@dhis2/ui'; -import { WarningRounded } from '@material-ui/icons'; +import { colors, spacers, IconWarningFilled24 } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; import React from 'react'; import { withStyles } from '@material-ui/core'; @@ -20,7 +19,7 @@ const styles = { const WidgetWarningHeaderPlain = ({ classes }) => ( - +

{i18n.t('Warning')}

); diff --git a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageOverview/StageOverview.component.js b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageOverview/StageOverview.component.js index c2e7731421..907e049840 100644 --- a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageOverview/StageOverview.component.js +++ b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageOverview/StageOverview.component.js @@ -76,7 +76,7 @@ const getLastUpdatedAt = (events, fromServerDate) => { <> {i18n.t('Last updated')}  - { moment(fromServerDate(updatedAt)).fromNow()} + {moment(fromServerDate(updatedAt)).fromNow()} ) @@ -111,15 +111,15 @@ export const StageOverviewPlain = ({ title, icon, description, events, classes }
{title}
- { description && - -
- -
-
+ {description && + +
+ +
+
}
@@ -141,7 +141,7 @@ export const StageOverviewPlain = ({ title, icon, description, events, classes }
{i18n.t('{{ scheduledEvents }} scheduled', { scheduledEvents })} - : null } + : null} {totalEvents > 0 &&
diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js index 3342810e0b..b57a46e4f0 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js @@ -23,7 +23,7 @@ export const useAddRelationship = ({ teiId, onMutate, onSuccess }: Props) => { const queryClient = useQueryClient(); const queryKey: string = useFeature(FEATURES.exportablePayload) ? 'relationships' : 'instances'; const dataEngine = useDataEngine(); - const { show: showSnackbar } = useAlert( + const { show: showAlert } = useAlert( i18n.t('An error occurred while adding the relationship'), { critical: true }, ); @@ -37,7 +37,7 @@ export const useAddRelationship = ({ teiId, onMutate, onSuccess }: Props) => { }), { onError: (_, requestData) => { - showSnackbar(); + showAlert(); const apiRelationshipId = requestData.clientRelationship.relationship; const apiResponse = queryClient.getQueryData([ReactQueryAppNamespace, 'relationships', teiId]); const apiRelationships = handleAPIResponse(REQUESTED_ENTITIES.relationships, apiResponse); diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/RowMenuSetup/EventWorkingListsRowMenuSetup.component.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/RowMenuSetup/EventWorkingListsRowMenuSetup.component.js index 7044885784..fadcccb965 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/RowMenuSetup/EventWorkingListsRowMenuSetup.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/RowMenuSetup/EventWorkingListsRowMenuSetup.component.js @@ -1,32 +1,19 @@ // @flow -import React, { useMemo, type ComponentType } from 'react'; +import React, { useMemo } from 'react'; import i18n from '@dhis2/d2-i18n'; -import { withStyles } from '@material-ui/core/styles'; import { IconDelete24, colors } from '@dhis2/ui'; import { EventWorkingListsUpdateTrigger } from '../UpdateTrigger'; import type { CustomRowMenuContents } from '../../WorkingListsBase'; import type { Props } from './eventWorkingListsRowMenuSetup.types'; -const getStyles = () => ({ - deleteContainer: { - display: 'flex', - }, -}); -export const EventWorkingListsRowMenuSetupPlain = ({ onDeleteEvent, classes, ...passOnProps }: Props) => { +export const EventWorkingListsRowMenuSetup = ({ onDeleteEvent, ...passOnProps }: Props) => { const customRowMenuContents: CustomRowMenuContents = useMemo(() => [{ key: 'deleteEventItem', clickHandler: ({ id }) => onDeleteEvent(id), - element: ( - - - {i18n.t('Delete event')} - - ), - }], [onDeleteEvent, classes.deleteContainer]); + icon: , + label: i18n.t('Delete event'), + }], [onDeleteEvent]); return ( ); }; - -export const EventWorkingListsRowMenuSetup: ComponentType<$Diff> = - withStyles(getStyles)(EventWorkingListsRowMenuSetupPlain); diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/RowMenuSetup/eventWorkingListsRowMenuSetup.types.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/RowMenuSetup/eventWorkingListsRowMenuSetup.types.js index 28331c6f0d..efd37756d4 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/RowMenuSetup/eventWorkingListsRowMenuSetup.types.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/RowMenuSetup/eventWorkingListsRowMenuSetup.types.js @@ -4,7 +4,6 @@ import type { CustomRowMenuContents } from '../../WorkingListsBase'; type ExtractedProps = $ReadOnly<{| onDeleteEvent: Function, - classes: Object, |}>; type RestProps = $Rest; diff --git a/src/core_modules/capture-ui/Icons/DropdownChevron.component.js b/src/core_modules/capture-ui/Icons/DropdownChevron.component.js new file mode 100644 index 0000000000..d02d85e2d5 --- /dev/null +++ b/src/core_modules/capture-ui/Icons/DropdownChevron.component.js @@ -0,0 +1,35 @@ +// @flow +import React from 'react'; + +type Props = { + className?: string, +}; + +export function ChevronDown({ className }: Props) { + return ( + + + + ); +} + +export function ChevronUp({ className }: Props) { + return ( + + + + ); +} diff --git a/src/core_modules/capture-ui/Icons/index.js b/src/core_modules/capture-ui/Icons/index.js index 1f4dfc1f82..0bada9e901 100644 --- a/src/core_modules/capture-ui/Icons/index.js +++ b/src/core_modules/capture-ui/Icons/index.js @@ -6,5 +6,6 @@ export { MultiSelectionUncheckedIcon } from './MultiSelectionUncheckedIcon.compo export { SingleSelectionCheckedIcon } from './SingleSelectionCheckedIcon.component'; export { SingleSelectionUncheckedIcon } from './SingleSelectionUncheckedIcon.component'; export { BookmarkAddIcon } from './BookmarkAddIcon.component'; +export { ChevronDown, ChevronUp } from './DropdownChevron.component'; export { ChevronIcon } from './ChevronIcon.component';