diff --git a/.travis.yml b/.travis.yml index 652a7e0a41d..d0716ba9391 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ script: - | if [ ${TRAVIS_NODE_VERSION} = "10" ] then - cat ${TRAVIS_BUILD_DIR}/coverage/lcov.info | ${TRAVIS_BUILD_DIR}/node_modules/.bin/coveralls + cat ${TRAVIS_BUILD_DIR}/coverage/lcov.info | ${TRAVIS_BUILD_DIR}/node_modules/.bin/coveralls || exit 0 fi jobs: diff --git a/package.json b/package.json index bacb4dd03ad..ec67bf4c188 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,10 @@ "build": "node ./scripts/rollup/build.js", "build:storybook": "build-storybook -c packages/docs/.storybook -o .out", "pretest": "node scripts/test/clean.js", - "test": "npm-run-all --sequential test:jest test:karma test:combineCoverage", - "test:coveralls": "npm run test && cat ./coverage/lcov.info | coveralls", + "test": "npm-run-all --sequential test:jest test:karma", "test:karma": "karma start ./config/karma.conf.js --coverage", "test:jest": "jest --config=config/jest.config.js --coverage", - "test:combineCoverage": "nyc report -r text -r lcov", + "posttest": "nyc report -r text -r lcov || exit 0", "clean": "lerna clean --yes && node scripts/clean.js", "postversion": "node ./scripts/postversion/index.js", "prettier:all": "prettier --write --config ./prettier.config.js \"packages/**/*.{ts,tsx}\"" diff --git a/packages/main/__karma_snapshots__/AnalyticalCard.md b/packages/main/__karma_snapshots__/AnalyticalCard.md index d4716f9b2e4..3e325c27b95 100644 --- a/packages/main/__karma_snapshots__/AnalyticalCard.md +++ b/packages/main/__karma_snapshots__/AnalyticalCard.md @@ -6,83 +6,86 @@ - - -
- - -
-
-
- + +
+ +
+
+
+ +
+
Title - -
- Subtitle
+ + +
+ +
-
-
-
-
+ +
+ Subtitle +
+
+ +
+ +
+
+
Value
-
-
-
+
+
+
Unit
-
-
-
+ +
+ Description
- - -
- - - I'm a content! - - +
+ +
+ + + I'm a content! + +
- - +
+ diff --git a/packages/main/src/components/AnalyticalCard/AnalyticalCard.jss.ts b/packages/main/src/components/AnalyticalCard/AnalyticalCard.jss.ts index ce31849c1cb..40d4e109b1a 100644 --- a/packages/main/src/components/AnalyticalCard/AnalyticalCard.jss.ts +++ b/packages/main/src/components/AnalyticalCard/AnalyticalCard.jss.ts @@ -1,18 +1,20 @@ -import { fonts } from '@ui5/webcomponents-react-base'; +import { fonts, spacing } from '@ui5/webcomponents-react-base'; import { JSSTheme } from '../../interfaces/JSSTheme'; -const styles = ({ theme, contentDensity, parameters }: JSSTheme) => ({ +const styles = ({ parameters }: JSSTheme) => ({ card: { backgroundColor: parameters.sapUiTileBackground, - border: `1px solid ${parameters.sapUiTileBorderColor}`, - boxShadow: '0 0 0 1px rgba(0,0,0,0.15)', + // TODO There is a border mentioned in the specs, but this one looks weird. + // border: `0.625rem solid ${parameters.sapUiTileBorderColor}`, + boxShadow: parameters.sapUiShadowLevel0, borderRadius: '0.25rem', textAlign: 'start', overflow: 'hidden', position: 'relative', - width: '20rem', - fontFamily: fonts.sapUiFontFamily - } + fontFamily: fonts.sapUiFontFamily, + boxSizing: 'border-box' + }, + content: spacing.sapUiContentPadding }); export default styles; diff --git a/packages/main/src/components/AnalyticalCard/AnalyticalCard.karma.tsx b/packages/main/src/components/AnalyticalCard/AnalyticalCard.karma.tsx index fd554f38e9b..14e2f307d2d 100644 --- a/packages/main/src/components/AnalyticalCard/AnalyticalCard.karma.tsx +++ b/packages/main/src/components/AnalyticalCard/AnalyticalCard.karma.tsx @@ -10,30 +10,27 @@ import { ValueState } from '../../lib/ValueState'; use(matchSnapshot); -const renderHeader = () => { - return ( - - ); -}; +const Header = ( + +); // TODO Add more tests describe('Analytical Card', () => { it('Render without Crashing', () => { const wrapper = mountThemedComponent( - + I'm a content! ); diff --git a/packages/main/src/components/AnalyticalCard/demo.stories.tsx b/packages/main/src/components/AnalyticalCard/demo.stories.tsx index 9987430ed60..3b7f8718408 100644 --- a/packages/main/src/components/AnalyticalCard/demo.stories.tsx +++ b/packages/main/src/components/AnalyticalCard/demo.stories.tsx @@ -1,5 +1,5 @@ import { action } from '@storybook/addon-actions'; -import { boolean, select } from '@storybook/addon-knobs'; +import { boolean, select, text } from '@storybook/addon-knobs'; import { storiesOf } from '@storybook/react'; import { LineChart } from '@ui5/webcomponents-react-charts/lib/LineChart'; import React from 'react'; @@ -10,23 +10,26 @@ import { ValueState } from '../../lib/ValueState'; storiesOf('Components | Analytical Card', module).add('default', () => ( ( + width={text('width', '20rem')} + header={ - )} + } > void; - loading?: boolean; - description?: string; -} - -interface AnalyticalCardHeaderInternalProps extends AnalyticalCardHeaderPropTypes, ClassProps {} - -interface AnalyticalCardHeaderState { - helpVisible: boolean; -} - -@withStyles(styles) -export class AnalyticalCardHeader extends PureComponent { - static defaultProps = { - title: null, - subTitle: null, - arrowIndicator: DeviationIndicator.None, - showIndicator: true, - indicatorState: ValueState.None, - value: null, - unit: null, - valueState: ValueState.None, - target: null, - deviation: null, - onHeaderPress: null, - loading: false, - description: null - }; - - state = { - helpVisible: false - }; - - private handleHelpPress = (e) => { - e.stopPropagation(); - this.setState({ helpVisible: true }); - }; - - private onCloseHelp = () => { - this.setState({ helpVisible: false }); - }; - - private onClick = (e) => { - if (this.props.onHeaderPress) { - this.props.onHeaderPress(Event.of(this, e)); - } - }; - - private getIndicatorIcon = () => { - const { indicatorState, arrowIndicator, classes } = this.props as AnalyticalCardHeaderInternalProps; - - const arrowClasses = StyleClassHelper.of(classes.arrowIndicatorShape); - - switch (arrowIndicator) { - case DeviationIndicator.Up: - arrowClasses.put(classes.arrowUp); - break; - case DeviationIndicator.Down: - arrowClasses.put(classes.arrowDown); - break; - default: - arrowClasses.put(classes.arrowRight); - break; - } - - switch (indicatorState) { - case ValueState.Success: - arrowClasses.put(classes.good); - break; - case ValueState.Error: - arrowClasses.put(classes.error); - break; - case ValueState.Warning: - arrowClasses.put(classes.critical); - break; - default: - arrowClasses.put(classes.none); - - break; - } - return
; - }; - - render() { - const { - title, - subTitle, - value, - unit, - target, - deviation, - valueState, - onHeaderPress, - loading, - classes, - showIndicator, - tooltip, - className, - description, - style, - innerRef - } = this.props as AnalyticalCardHeaderInternalProps; - - const headerClasses = StyleClassHelper.of(classes.cardHeader); - if (onHeaderPress) { - headerClasses.put(classes.cardHeaderClickable); - } - - const valueAndUnitClasses = StyleClassHelper.of(classes.valueAndUnit); - if (valueState === ValueState.Error) { - valueAndUnitClasses.put(classes.error); - } - if (valueState === ValueState.Warning) { - valueAndUnitClasses.put(classes.critical); - } - if (valueState === ValueState.Success) { - valueAndUnitClasses.put(classes.good); - } - - if (className) { - headerClasses.put(className); - } - const shouldRenderContent = [value, unit, deviation, target].some((v) => v !== null) || loading === true; - - return ( -
-
-
- {title} -
{subTitle}
-
- {shouldRenderContent && ( -
-
-
-
{value}
-
- {showIndicator && this.getIndicatorIcon()} -
{unit}
-
- {loading && ( - - - - )} -
-
- -
- {target !== null && ( -
- -
- {target} -
-
- )} - {deviation !== null && ( -
- -
- {deviation} -
-
- )} -
-
- )} -
{description}
-
-
- ); - } -} diff --git a/packages/main/src/components/AnalyticalCard/index.tsx b/packages/main/src/components/AnalyticalCard/index.tsx index 292c77e01de..04d0bfcc57a 100644 --- a/packages/main/src/components/AnalyticalCard/index.tsx +++ b/packages/main/src/components/AnalyticalCard/index.tsx @@ -1,43 +1,48 @@ -import { StyleClassHelper, withStyles } from '@ui5/webcomponents-react-base'; -import React, { PureComponent, ReactNode, ReactNodeArray } from 'react'; -import { ClassProps } from '../../interfaces/ClassProps'; +import { StyleClassHelper } from '@ui5/webcomponents-react-base'; +import React, { CSSProperties, FC, forwardRef, ReactNode, ReactNodeArray, Ref, useMemo } from 'react'; +import { createUseStyles } from 'react-jss'; import { CommonProps } from '../../interfaces/CommonProps'; import { JSSTheme } from '../../interfaces/JSSTheme'; -import { ContentDensity } from '../../lib/ContentDensity'; -import { Themes } from '../../lib/Themes'; + import styles from './AnalyticalCard.jss'; export interface AnalyticalCardTypes extends CommonProps { /** - * Render Function for Header Content - * This function will pass two parameters: theme and Content Density. - * Expect to return a CardHeader. + * The Card header Component, using the AnalyticalCardHeader is recommended. */ - renderHeader: (theme: Themes, contentDensity: ContentDensity) => JSX.Element; + header?: ReactNode; /** * Expected one or more React Components */ - children?: ReactNode | ReactNodeArray; + children: ReactNode | ReactNodeArray; + width?: CSSProperties['width']; } -export interface AnalyticalCardPropsInternal extends AnalyticalCardTypes, ClassProps { - theme?: JSSTheme; -} +const useStyles = createUseStyles>(styles, { name: 'AnalyticalCard' }); -@withStyles(styles) -export class AnalyticalCard extends PureComponent { - render() { - const { renderHeader, children, classes, theme, style, className, tooltip, innerRef } = this - .props as AnalyticalCardPropsInternal; +export const AnalyticalCard: FC = forwardRef( + (props: AnalyticalCardTypes, ref: Ref) => { + const { children, style, className, tooltip, header, width } = props; + const classes = useStyles(); const classNameString = StyleClassHelper.of(classes.card); if (className) { classNameString.put(className); } + + const analyticalCardStyles = useMemo(() => { + return { + width, + ...style + }; + }, [style, width]); return ( -
- {renderHeader(theme.theme, theme.contentDensity)} -
{children}
+
+ {header} +
{children}
); } -} +); + +AnalyticalCard.displayName = 'AnalyticalCard'; +AnalyticalCard.defaultProps = { width: '20rem', header: null }; diff --git a/packages/main/src/components/AnalyticalCard/header/AnalyticalCardHeader.jss.ts b/packages/main/src/components/AnalyticalCardHeader/AnalyticalCardHeader.jss.ts similarity index 53% rename from packages/main/src/components/AnalyticalCard/header/AnalyticalCardHeader.jss.ts rename to packages/main/src/components/AnalyticalCardHeader/AnalyticalCardHeader.jss.ts index 02a985ec2b0..1615c01e398 100644 --- a/packages/main/src/components/AnalyticalCard/header/AnalyticalCardHeader.jss.ts +++ b/packages/main/src/components/AnalyticalCardHeader/AnalyticalCardHeader.jss.ts @@ -1,7 +1,7 @@ import { fonts } from '@ui5/webcomponents-react-base'; -import { JSSTheme } from '../../../interfaces/JSSTheme'; +import { JSSTheme } from '../../interfaces/JSSTheme'; -const styles = ({ theme, contentDensity, parameters }: JSSTheme) => ({ +const styles = ({ parameters }: JSSTheme) => ({ helpText: { fontFamily: fonts.sapUiFontFamily, fontSize: fonts.sapMFontMediumSize, @@ -12,12 +12,14 @@ const styles = ({ theme, contentDensity, parameters }: JSSTheme) => ({ paddingTop: '1rem', paddingBottom: '1rem', outlineOffset: '-0.125rem', - boxSizing: 'border-box', - borderBottom: `1px solid ${parameters.sapUiListBorderColor}`, + borderBottom: `0.0625rem solid ${parameters.sapUiTileBackgroundDarken20}`, backgroundColor: parameters.sapUiTileBackground, fontFamily: fonts.sapUiFontHeaderFamily, '&:hover': { - backgroundColor: parameters.sapUiTileBackgroundDarken20 + backgroundColor: parameters.sapUiListHoverBackground // TODO sapUiTileHoverBackground '#fafafa' + }, + '&:active': { + backgroundColor: parameters.sapUiListHoverBackground // TODO sapUiTileHoverBackground '#fafafa' } }, arrowIndicatorShape: { @@ -39,7 +41,6 @@ const styles = ({ theme, contentDensity, parameters }: JSSTheme) => ({ borderTop: '8px solid transparent', borderBottom: '8px solid transparent' }, - cardHeaderClickable: { cursor: 'pointer' }, @@ -48,16 +49,23 @@ const styles = ({ theme, contentDensity, parameters }: JSSTheme) => ({ marginRight: '1rem', position: 'relative' }, - headerText: { + headerTitles: { overflow: 'hidden', + textAlign: 'left', + whiteSpace: 'normal', + wordWrap: 'break-word' + }, + headerText: { fontFamily: fonts.sapUiFontHeaderFamily, fontWeight: fonts.sapUiFontHeaderWeight, fontSize: fonts.sapMFontHeader5Size, color: parameters.sapUiTileTitleTextColor, - textAlign: 'left', - whiteSpace: 'normal', - wordWrap: 'break-word', - textOverflow: 'ellipsis' + overflow: 'hidden', + display: '-webkit-box', + lineHeight: '18px', + maxHeight: '54px' /* height * number of lines */, + WebkitLineClamp: '3' /* number of lines to show */, + WebkitBoxOrient: 'vertical' }, subHeaderText: { overflow: 'hidden', @@ -70,7 +78,27 @@ const styles = ({ theme, contentDensity, parameters }: JSSTheme) => ({ wordWrap: 'break-word', textOverflow: 'ellipsis', marginTop: '0.5rem', - width: '100%' + width: '100%', + display: '-webkit-box', + lineHeight: '16px', + maxHeight: '32px', + WebkitLineClamp: '2', + WebkitBoxOrient: 'vertical' + }, + counter: { + fontSize: fonts.sapMFontSmallSize, + margin: '0.188rem 0 0 1rem', + lineHeight: 'normal', + textAlign: 'right' + }, + currency: { + fontFamily: parameters.sapUiFontFamily, + fontSize: parameters.sapMFontMediumSize, + fontWeight: 'normal', + color: parameters.sapUiTileTextColor, + overflow: 'hidden', + marginLeft: '0.25rem', + textAlign: 'right' }, helpIcon: { position: 'absolute', @@ -79,26 +107,23 @@ const styles = ({ theme, contentDensity, parameters }: JSSTheme) => ({ }, kpiContent: { fontWeight: 'normal', - display: 'flex', - justifyContent: 'space-between', - alignItems: 'flex-end', - marginTop: '10px', - color: parameters.sapUiTileTextColor - }, - leftContent: { - display: 'flex', - alignItems: 'baseline' + marginTop: '0.5rem', + color: parameters.sapUiTileTextColor, + width: '100%', + boxSizing: 'border-box' }, valueAndUnit: { display: 'flex', alignItems: 'end', - marginLeft: '0.25rem', color: parameters.sapUiNeutralText }, value: { - display: 'flex', - alignItems: 'flex-end', - fontSize: '2rem' + fontSize: '2rem', + maxWidth: '135px', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + display: 'block' }, unit: { marginLeft: '0.25rem', @@ -112,12 +137,38 @@ const styles = ({ theme, contentDensity, parameters }: JSSTheme) => ({ justifyContent: 'flex-end' }, targetAndDeviation: { + width: '60%', + paddingBottom: '0.25rem', textAlign: 'right', - fontSize: fonts.sapMFontSmallSize, - display: 'flex' + fontSize: parameters.sapMFontSmallSize, + color: parameters.sapUiTileTextColor + }, + targetAndDeviationColumn: { + maxWidth: '45%', + marginLeft: '1rem' }, - deviation: { - marginLeft: '0.5rem' + targetAndDeviationValue: { + color: parameters.sapUiTileTitleTextColor, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis' + }, + description: { + fontFamily: parameters.sapUiFontFamily, + fontSize: parameters.sapMFontSmallSize, + fontWeight: 'normal', + color: parameters.sapUiTileTextColor, + whiteSpace: 'normal', + overflow: 'hidden', + textAlign: 'left', + textOverflow: 'ellipsis', + marginTop: '0.25rem', + width: '100%', + display: '-webkit-box', + lineHeight: '14px', + maxHeight: '14px' /* height * number of lines */, + WebkitLineClamp: '1' /* number of lines to show */, + WebkitBoxOrient: 'vertical' }, good: { color: parameters.sapUiPositiveText diff --git a/packages/main/src/components/AnalyticalCardHeader/index.tsx b/packages/main/src/components/AnalyticalCardHeader/index.tsx new file mode 100644 index 00000000000..9072f9ae7a7 --- /dev/null +++ b/packages/main/src/components/AnalyticalCardHeader/index.tsx @@ -0,0 +1,200 @@ +import { Event, StyleClassHelper } from '@ui5/webcomponents-react-base'; +import React, { FC, forwardRef, Ref, useCallback, useMemo } from 'react'; +import { CommonProps } from '../../interfaces/CommonProps'; +import { DeviationIndicator } from '@ui5/webcomponents-react/lib/DeviationIndicator'; +import { ObjectStatus } from '@ui5/webcomponents-react/lib/ObjectStatus'; +import { ValueState } from '@ui5/webcomponents-react/lib/ValueState'; +import { FlexBox } from '@ui5/webcomponents-react/lib/FlexBox'; +import { FlexBoxAlignItems } from '@ui5/webcomponents-react/lib/FlexBoxAlignItems'; +import { FlexBoxDirection } from '@ui5/webcomponents-react/lib/FlexBoxDirection'; +import { FlexBoxJustifyContent } from '@ui5/webcomponents-react/lib/FlexBoxJustifyContent'; +import { FlexBoxWrap } from '@ui5/webcomponents-react/lib/FlexBoxWrap'; +import styles from './AnalyticalCardHeader.jss'; +import { JSSTheme } from '../../interfaces/JSSTheme'; +import { createUseStyles } from 'react-jss'; + +export interface AnalyticalCardHeaderPropTypes extends CommonProps { + title?: string; + subTitle?: string; + arrowIndicator?: DeviationIndicator; + showIndicator?: boolean; + indicatorState?: ValueState; + value?: string; + unit?: string; + valueState?: ValueState; + target?: string; + deviation?: string; + onHeaderPress?: (event: Event) => void; + description?: string; + counter?: string; + counterState?: ValueState; + currency?: string; +} + +const useStyles = createUseStyles>(styles, { + name: 'AnalyticalCardHeader' +}); + +export const AnalyticalCardHeader: FC = forwardRef( + (props: AnalyticalCardHeaderPropTypes, ref: Ref) => { + const { + title, + subTitle, + value, + unit, + target, + deviation, + valueState, + onHeaderPress, + showIndicator, + tooltip, + className, + description, + counter, + counterState, + currency, + indicatorState, + arrowIndicator, + style + } = props; + const classes = useStyles(props); + const onClick = useCallback( + (e) => { + if (onHeaderPress) { + onHeaderPress(Event.of(null, e)); + } + }, + [onHeaderPress] + ); + const indicatorIcon = useMemo(() => { + const arrowClasses = StyleClassHelper.of(classes.arrowIndicatorShape); + switch (arrowIndicator) { + case DeviationIndicator.Up: + arrowClasses.put(classes.arrowUp); + break; + case DeviationIndicator.Down: + arrowClasses.put(classes.arrowDown); + break; + default: + arrowClasses.put(classes.arrowRight); + break; + } + + switch (indicatorState) { + case ValueState.Success: + arrowClasses.put(classes.good); + break; + case ValueState.Error: + arrowClasses.put(classes.error); + break; + case ValueState.Warning: + arrowClasses.put(classes.critical); + break; + default: + arrowClasses.put(classes.none); + + break; + } + return
; + }, [arrowIndicator, indicatorState, classes]); + + const headerClasses = StyleClassHelper.of(classes.cardHeader); + if (onHeaderPress) { + headerClasses.put(classes.cardHeaderClickable); + } + + const valueAndUnitClasses = StyleClassHelper.of(classes.valueAndUnit); + if (valueState === ValueState.Error) { + valueAndUnitClasses.put(classes.error); + } + if (valueState === ValueState.Warning) { + valueAndUnitClasses.put(classes.critical); + } + if (valueState === ValueState.Success) { + valueAndUnitClasses.put(classes.good); + } + + if (className) { + headerClasses.put(className); + } + const shouldRenderContent = [value, unit, deviation, target].some((v) => v !== null); + return ( +
+
+
+ +
{title}
+ + {counter} + +
+
+ {subTitle} + {currency && ` | ${currency}`} +
+
+ {shouldRenderContent && ( + + +
+
{value}
+
+ {showIndicator && indicatorIcon} +
{unit}
+
+
+
+ + {target !== null && ( + + Target + {target} + + )} + {deviation !== null && ( + + Deviation + {deviation} + + )} + +
+ )} +
{description}
+
+
+ ); + } +); + +AnalyticalCardHeader.displayName = 'AnalyticalCardHeader'; + +AnalyticalCardHeader.defaultProps = { + title: null, + subTitle: null, + arrowIndicator: DeviationIndicator.None, + showIndicator: true, + indicatorState: ValueState.None, + value: null, + unit: null, + valueState: ValueState.None, + target: null, + deviation: null, + onHeaderPress: null, + description: null, + counter: null, + counterState: ValueState.None, + currency: null +}; diff --git a/packages/main/src/lib/AnalyticalCardHeader.ts b/packages/main/src/lib/AnalyticalCardHeader.ts index f6e787958f6..f2e46a057cc 100644 --- a/packages/main/src/lib/AnalyticalCardHeader.ts +++ b/packages/main/src/lib/AnalyticalCardHeader.ts @@ -1,3 +1,3 @@ -import { AnalyticalCardHeader } from '../components/AnalyticalCard/header/AnalyticalCardHeader'; +import { AnalyticalCardHeader } from '../components/AnalyticalCardHeader'; export { AnalyticalCardHeader };