diff --git a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap index 8a4f856e8..52c9a86c2 100644 --- a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap +++ b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap @@ -175,11 +175,11 @@ Array [ "keys": Array [ Object { "key": "curiosity-inventory.header", - "match": "translate('curiosity-inventory.header', { context: key })", + "match": "translate('curiosity-inventory.header', { context: id })", }, Object { "key": "curiosity-inventory.header", - "match": "translate('curiosity-inventory.header', { context: id })", + "match": "translate('curiosity-inventory.header', { context: key })", }, ], }, diff --git a/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHelpers.test.js.snap b/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHelpers.test.js.snap index 8acbc42d4..0666d0567 100644 --- a/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHelpers.test.js.snap +++ b/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHelpers.test.js.snap @@ -1,5 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`InventoryListHelpers applyConfigProperty should determine if filter property is valid and return it or undefined: prop invalid, null 1`] = `undefined`; + +exports[`InventoryListHelpers applyConfigProperty should determine if filter property is valid and return it or undefined: prop valid, function 1`] = `"ipsum"`; + +exports[`InventoryListHelpers applyConfigProperty should determine if filter property is valid and return it or undefined: prop valid, number 1`] = `1000`; + +exports[`InventoryListHelpers applyConfigProperty should determine if filter property is valid and return it or undefined: prop valid, string 1`] = `"hello world"`; + exports[`InventoryListHelpers applySortFilters should apply and return updated filters for table sorting: NOT sortable 1`] = ` Object { "id": "lorem", @@ -138,6 +146,35 @@ Object { } `; +exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom and generated transforms 1`] = ` +Object { + "cells": Array [ + Object { + "title": "ipsum", + }, + ], + "columnHeaders": Array [ + Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})", + "transforms": Array [ + [Function], + [Function], + ], + }, + ], + "data": Object { + "dolor": Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"dolor\\"})", + "value": "sit", + }, + "lorem": Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})", + "value": "ipsum", + }, + }, +} +`; + exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom callback data 1`] = ` Object { "cells": Array [ @@ -193,6 +230,82 @@ Object { } `; +exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom cell tooltips 1`] = ` +Object { + "cells": Array [ + Object { + "title": + object, body, lorem + , + }, + ], + "columnHeaders": Array [ + Object { + "title": + object, header, lorem + , + "transforms": Array [], + }, + ], + "data": Object { + "dolor": Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"dolor\\"})", + "value": "sit", + }, + "lorem": Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})", + "value": "ipsum", + }, + }, +} +`; + +exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom cell, hide cell and column header if data is missing 1`] = ` +Object { + "cells": Array [], + "columnHeaders": Array [], + "data": Object {}, +} +`; + +exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom cell, use an empty cell if data is missing 1`] = ` +Object { + "cells": Array [ + Object { + "title": "", + }, + ], + "columnHeaders": Array [ + Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"missing\\"})", + "transforms": Array [], + }, + ], + "data": Object { + "hello": Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"hello\\"})", + "value": "world", + }, + }, +} +`; + exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom header data 1`] = ` Object { "cells": Array [ @@ -222,6 +335,63 @@ Object { } `; +exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom sort 1`] = ` +Object { + "cells": Array [ + Object { + "title": "ipsum", + }, + ], + "columnHeaders": Array [ + Object { + "onSort": [Function], + "sortActive": undefined, + "sortDirection": undefined, + "title": "header, lorem sort", + "transforms": Array [], + }, + ], + "data": Object { + "dolor": Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"dolor\\"})", + "value": "sit", + }, + "lorem": Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})", + "value": "ipsum", + }, + }, +} +`; + +exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom transforms 1`] = ` +Object { + "cells": Array [ + Object { + "title": "ipsum", + }, + ], + "columnHeaders": Array [ + Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})", + "transforms": Array [ + [Function], + ], + }, + ], + "data": Object { + "dolor": Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"dolor\\"})", + "value": "sit", + }, + "lorem": Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})", + "value": "ipsum", + }, + }, +} +`; + exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: filtered data 1`] = ` Object { "cells": Array [ @@ -248,8 +418,38 @@ Object { } `; +exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: generated transforms 1`] = ` +Object { + "cells": Array [ + Object { + "title": "ipsum", + }, + ], + "columnHeaders": Array [ + Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})", + "transforms": Array [ + [Function], + ], + }, + ], + "data": Object { + "dolor": Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"dolor\\"})", + "value": "sit", + }, + "lorem": Object { + "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})", + "value": "ipsum", + }, + }, +} +`; + exports[`InventoryListHelpers should have specific functions: inventoryListHelpers 1`] = ` Object { + "applyConfigProperty": [Function], + "applyHeaderRowCellFilters": [Function], "applySortFilters": [Function], "applyWrappableFilters": [Function], "parseInventoryFilters": [Function], diff --git a/src/components/inventoryCard/__tests__/inventoryCardHelpers.test.js b/src/components/inventoryCard/__tests__/inventoryCardHelpers.test.js index 27ba89c9e..d559d0778 100644 --- a/src/components/inventoryCard/__tests__/inventoryCardHelpers.test.js +++ b/src/components/inventoryCard/__tests__/inventoryCardHelpers.test.js @@ -1,5 +1,6 @@ import { inventoryCardHelpers, + applyConfigProperty, applySortFilters, applyWrappableFilters, parseInventoryFilters, @@ -51,6 +52,70 @@ describe('InventoryListHelpers', () => { expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('custom header data'); filters[0] = { + id: 'lorem', + header: { + title: 'header, lorem sort' + }, + onSort: () => {} + }; + + expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('custom sort'); + + filters[0] = { + id: 'lorem', + transforms: [() => {}] + }; + + expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('custom transforms'); + + filters[0] = { + id: 'lorem', + transforms: [() => {}], + cellWidth: 200 + }; + + expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('custom and generated transforms'); + + filters[0] = { + id: 'lorem', + cellWidth: 200 + }; + + expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('generated transforms'); + + filters[0] = { + id: 'missing' + }; + + expect(parseRowCellsListData({ filters, cellData: { hello: 'world' } })).toMatchSnapshot( + 'custom cell, use an empty cell if data is missing' + ); + + filters[0] = { + id: 'missing', + showEmptyCell: false + }; + + expect(parseRowCellsListData({ filters, cellData: {} })).toMatchSnapshot( + 'custom cell, hide cell and column header if data is missing' + ); + + filters[0] = { + id: 'lorem', + header: { + title: 'object, header, lorem', + tooltip: 'tooltip header content' + }, + cell: { + title: 'object, body, lorem', + tooltip: 'tooltip body content' + } + }; + + expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('custom cell tooltips'); + + filters[0] = { + id: 'lorem', header: ({ lorem, dolor }) => `${lorem.title}/${dolor.title}`, cell: ({ lorem, dolor }) => `${lorem.value}/${dolor.value}` }; @@ -72,6 +137,26 @@ describe('InventoryListHelpers', () => { expect(parseInventoryFilters({ filters, onSort: () => {} })).toMatchSnapshot('sortable'); }); + it('applyConfigProperty should determine if filter property is valid and return it or undefined', () => { + const filter = { + title: 'hello world' + }; + + expect(applyConfigProperty(filter.title, { params: { lorem: 'ipsum' } })).toMatchSnapshot('prop valid, string'); + + filter.title = 1000; + + expect(applyConfigProperty(filter.title, { params: { lorem: 'ipsum' } })).toMatchSnapshot('prop valid, number'); + + filter.title = ({ lorem }) => lorem; + + expect(applyConfigProperty(filter.title, { params: { lorem: 'ipsum' } })).toMatchSnapshot('prop valid, function'); + + filter.title = null; + + expect(applyConfigProperty(filter.title, { params: { lorem: 'ipsum' } })).toMatchSnapshot('prop invalid, null'); + }); + it('applySortFilters should apply and return updated filters for table sorting', () => { const filter = { id: 'lorem', diff --git a/src/components/inventoryCard/inventoryCardHelpers.js b/src/components/inventoryCard/inventoryCardHelpers.js index 0ef0cf6b1..a8504489f 100644 --- a/src/components/inventoryCard/inventoryCardHelpers.js +++ b/src/components/inventoryCard/inventoryCardHelpers.js @@ -1,6 +1,8 @@ import React from 'react'; import { cellWidth as PfCellWidth, SortByDirection, wrappable } from '@patternfly/react-table'; import _camelCase from 'lodash/camelCase'; +import _isPlainObject from 'lodash/isPlainObject'; +import { Tooltip } from '../tooltip/tooltip'; import { translate } from '../i18n/i18n'; import { RHSM_API_QUERY_SORT_DIRECTION_TYPES as SORT_DIRECTION_TYPES, @@ -9,16 +11,150 @@ import { import { helpers } from '../../common'; /** - * Apply sort filter to filters. + * ToDo: review setting up a transformed cell cache for already transformed cells. + * - review using a simple state and key memoized component + * - review using lru cache in a inventoryCardContext custom hook + */ + +/** + * Apply product inventory config properties consistently. + * + * @param {Function|string|number} prop + * @param {object} options + * @param {*[]|*} options.params + * @returns {React.ReactNode} + */ +const applyConfigProperty = (prop, { params = [] } = {}) => { + let updatedProp = prop; + + if (typeof prop === 'function') { + updatedProp = prop(...((Array.isArray(params) && params) || [params])); + } + + if (typeof updatedProp === 'string' || typeof updatedProp === 'number' || React.isValidElement(updatedProp)) { + return updatedProp; + } + + return undefined; +}; + +/** + * Generate header and row cell configuration from filters. + * + * @param {Array<{id: string, cell:React.ReactNode|{ title: string }, cellWidth: number, + * header:React.ReactNode|{ title: string }, onSort: Function, showEmptyCell: boolean, + * sortId: string, sortActive: boolean, sortDirection: string, + * transforms: Array}>} filters + * @param {object} cellData + * @param {object} session + * @returns {{bodyCells: { title: React.ReactNode }[], headerCells: { title: React.ReactNode }[]}} + */ +const applyHeaderRowCellFilters = (filters = [], cellData = {}, session = {}) => { + const headerCells = []; + const bodyCells = []; + + filters.forEach( + ({ id, cell, cellWidth, header, onSort, showEmptyCell = true, sortId, sortActive, sortDirection, transforms }) => { + const headerCellUpdated = { title: translate('curiosity-inventory.header', { context: id }), transforms: [] }; + const bodyCellUpdated = { title: '' }; + + // set filtered base header and body cells, or if filter doesn't exist skip + if (cellData[id]) { + headerCellUpdated.title = cellData[id]?.title ?? id; + bodyCellUpdated.title = cellData[id]?.value ?? ''; + } else { + if (helpers.DEV_MODE || helpers.REVIEW_MODE) { + console.warn(`Warning: Filter "${id}" not found in "table row" response data.`, cellData); + } + if (showEmptyCell === false) { + return; + } + } + + // set header cell title + if (header) { + const updatedHeaderCellTitle = applyConfigProperty(header, { params: [{ ...cellData }, { ...session }] }); + if (updatedHeaderCellTitle) { + headerCellUpdated.title = updatedHeaderCellTitle; + } else if (_isPlainObject(header)) { + Object.assign(headerCellUpdated, { ...header }); + } + + // set header cell tooltip + if (header.tooltip && headerCellUpdated.title) { + const updatedHeaderCellTooltip = applyConfigProperty(header.tooltip, { + params: [{ ...cellData }, { ...session }] + }); + if (updatedHeaderCellTooltip) { + headerCellUpdated.title = {headerCellUpdated.title}; + } + + delete headerCellUpdated.tooltip; + } + } + + // set header cell transforms + if (Array.isArray(headerCellUpdated.transforms)) { + if (Array.isArray(transforms)) { + headerCellUpdated.transforms = headerCellUpdated.transforms.concat([...transforms]); + } + + if (typeof cellWidth === 'number') { + headerCellUpdated.transforms.push(PfCellWidth(cellWidth)); + } + } + + // set header cell onSort + if (typeof onSort === 'function') { + headerCellUpdated.onSort = obj => onSort({ ...cellData }, { ...obj, id: sortId || id }); + headerCellUpdated.sortActive = sortActive; + headerCellUpdated.sortDirection = sortDirection; + } + + // set body cell title + if (cell) { + const updatedBodyCellTitle = applyConfigProperty(cell, { params: [{ ...cellData }, { ...session }] }); + if (updatedBodyCellTitle) { + bodyCellUpdated.title = updatedBodyCellTitle; + } else if (_isPlainObject(cell)) { + Object.assign(bodyCellUpdated, { ...cell }); + } + + // set body cell tooltip + if (cell.tooltip && bodyCellUpdated.title) { + const updatedBodyCellTooltip = applyConfigProperty(cell.tooltip, { + params: [{ ...cellData }, { ...session }] + }); + if (updatedBodyCellTooltip) { + bodyCellUpdated.title = {bodyCellUpdated.title}; + } + + delete bodyCellUpdated.tooltip; + } + } + + headerCells.push(headerCellUpdated); + bodyCells.push(bodyCellUpdated); + } + ); + + return { + headerCells, + bodyCells + }; +}; + +/** + * Shallow clone filter, and apply a column sort filter. * * @param {object} params - * @param {{ onSort: Function, sortActive: boolean, sortDirection: string, isSortDefault: boolean, - * sortDefaultInitialDirection: string }} params.filter + * @param {{onSort: Function, sortActive: boolean, sortDirection: string, isSortDefault: boolean, + * sortDefaultInitialDirection: string}} params.filter * @param {Function} params.onSort * @param {object} params.query - * @returns {object} + * @returns {{}} */ -const applySortFilters = ({ filter = {}, onSort, query = {} }) => { +const applySortFilters = ({ filter = {}, onSort, query = {} } = {}) => { const { id, sortId } = filter; const updatedId = sortId || id; const updatedFilter = { ...filter }; @@ -66,7 +202,14 @@ const applySortFilters = ({ filter = {}, onSort, query = {} }) => { return updatedFilter; }; -const applyWrappableFilters = ({ filter = {} }) => { +/** + * Shallow clone and apply a consistent PF "wrappable" transformation config allowing column content to wrap. + * + * @param {object} params + * @param {object} params.filter + * @returns {{}} + */ +const applyWrappableFilters = ({ filter = {} } = {}) => { const updatedFilter = { ...filter }; if (Array.isArray(updatedFilter.transforms)) { @@ -79,15 +222,18 @@ const applyWrappableFilters = ({ filter = {} }) => { }; /** - * Apply additional properties to filters. + * Shallow clone and apply, sequence specific, additional properties to filters. * * @param {object} params - * @param {Array} params.filters + * @param {Array<{id: string, cell:*, cellWidth: number, header:*, onSort: Function, + * showEmptyCell: boolean, sortId: string, sortActive: boolean, + * sortDirection: string, transforms: Array, isSortDefault: boolean, + * sortDefaultInitialDirection: string}>} params.filters * @param {Function} params.onSort * @param {object} params.query - * @returns {Array} + * @returns {*[]} */ -const parseInventoryFilters = ({ filters = [], onSort, query = {} }) => +const parseInventoryFilters = ({ filters = [], onSort, query = {} } = {}) => [...filters].map(filter => { const updatedFilter = { ...filter }; @@ -106,105 +252,39 @@ const parseInventoryFilters = ({ filters = [], onSort, query = {} }) => * Parse and return formatted/filtered table cells, and apply table filters. * * @param {object} params - * @param {Array} params.filters + * @param {Array<{id: string, cell:React.ReactNode|{ title: string }, cellWidth: number, + * header:React.ReactNode|{ title: string }, onSort: Function, showEmptyCell: boolean, + * sortId: string, sortActive: boolean, sortDirection: string, + * transforms: Array}>} params.filters * @param {object} params.cellData * @param {object} params.session - * @returns {{columnHeaders: Array, cells: Array, data: object}} + * @returns {{columnHeaders: { title: React.ReactNode }[], cells: { title: React.ReactNode }[], data: {}}} */ -const parseRowCellsListData = ({ filters = [], cellData = {}, session = {} }) => { +const parseRowCellsListData = ({ filters = [], cellData = {}, session = {} } = {}) => { const updatedColumnHeaders = []; const updatedCells = []; const allCells = {}; // Apply basic translation and value - Object.entries(cellData).forEach(([key, value]) => { + Object.entries(cellData).forEach(([key, value = '']) => { allCells[key] = { title: translate('curiosity-inventory.header', { context: key }), value }; updatedColumnHeaders.push(allCells[key].title); - updatedCells.push(value); + updatedCells.push(value || '...'); }); // Apply filters to header and cell values - if (filters?.length) { + if (filters?.length && Object.keys(allCells).length) { updatedColumnHeaders.length = 0; updatedCells.length = 0; - filters.forEach(({ id, cell, cellWidth, header, onSort, sortId, sortActive, sortDirection, transforms }) => { - let headerUpdated; - let cellUpdated; - - if (allCells[id]) { - headerUpdated = allCells[id]?.title ?? id; - cellUpdated = allCells[id]?.value ?? ''; - } else if (id) { - headerUpdated = translate('curiosity-inventory.header', { context: id }); - cellUpdated = ''; - } - - // set table header cell filter params - if (header) { - headerUpdated = (typeof header === 'function' && header({ ...allCells })) || header; - } - - if ( - typeof headerUpdated === 'string' || - typeof headerUpdated === 'number' || - React.isValidElement(headerUpdated) - ) { - headerUpdated = { - title: headerUpdated - }; - } - - if (headerUpdated) { - headerUpdated.transforms = []; - - if (Array.isArray(transforms)) { - headerUpdated.transforms = headerUpdated.transforms.concat([...transforms]); - } - - if (typeof cellWidth === 'number') { - headerUpdated.transforms.push(PfCellWidth(cellWidth)); - } - } - - if (typeof onSort === 'function') { - headerUpdated = { - ...headerUpdated, - onSort: obj => onSort({ ...allCells }, { ...obj, id: sortId || id }), - sortActive, - sortDirection - }; - } - - // set table row cell filter params - if (cell) { - cellUpdated = typeof cell === 'function' ? cell({ ...allCells }, { ...session }) : cell; - } - - if (typeof cellUpdated === 'string' || typeof cellUpdated === 'number' || React.isValidElement(cellUpdated)) { - cellUpdated = { - title: cellUpdated - }; - } else if (!cellUpdated?.title) { - if (helpers.DEV_MODE || helpers.REVIEW_MODE) { - console.error( - `PF table throws an error when cell values don't conform to what it is expecting, or align exactly to column headers. - \n\nSee cell ID=${id} with VALUE=${cellUpdated}` - ); - } - - cellUpdated = { - title: '' - }; - } + const { headerCells = [], bodyCells = [] } = applyHeaderRowCellFilters(filters, allCells, session); - updatedColumnHeaders.push(headerUpdated); - updatedCells.push(cellUpdated); - }); + updatedColumnHeaders.push(...headerCells); + updatedCells.push(...bodyCells); } return { @@ -215,6 +295,8 @@ const parseRowCellsListData = ({ filters = [], cellData = {}, session = {} }) => }; const inventoryCardHelpers = { + applyConfigProperty, + applyHeaderRowCellFilters, applySortFilters, applyWrappableFilters, parseInventoryFilters, @@ -224,6 +306,8 @@ const inventoryCardHelpers = { export { inventoryCardHelpers as default, inventoryCardHelpers, + applyConfigProperty, + applyHeaderRowCellFilters, applySortFilters, applyWrappableFilters, parseInventoryFilters, diff --git a/src/components/tooltip/__tests__/__snapshots__/tooltip.test.js.snap b/src/components/tooltip/__tests__/__snapshots__/tooltip.test.js.snap new file mode 100644 index 000000000..53286cd2a --- /dev/null +++ b/src/components/tooltip/__tests__/__snapshots__/tooltip.test.js.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Tooltip Component should allow custom properties: custom, content 1`] = ` + + tooltip content + + } + distance={5} + enableFlip={false} + entryDelay={100} + exitDelay={0} + position="top" +> +
+ content +
+
+`; + +exports[`Tooltip Component should render a basic component: basic 1`] = ` + + ... +

+ } + distance={5} + enableFlip={false} + entryDelay={100} + exitDelay={0} + position="top" +> + + hello world + +
+`; diff --git a/src/components/tooltip/__tests__/tooltip.test.js b/src/components/tooltip/__tests__/tooltip.test.js new file mode 100644 index 000000000..98b1ad5db --- /dev/null +++ b/src/components/tooltip/__tests__/tooltip.test.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { Tooltip } from '../tooltip'; + +describe('Tooltip Component', () => { + it('should render a basic component', () => { + const props = {}; + + const component = shallow(hello world); + expect(component).toMatchSnapshot('basic'); + }); + + it('should allow custom properties', () => { + const props = { + content: tooltip content, + isNoWrap: true + }; + + const component = shallow( + +
content
+
+ ); + expect(component).toMatchSnapshot('custom, content'); + }); +}); diff --git a/src/components/tooltip/tooltip.js b/src/components/tooltip/tooltip.js new file mode 100755 index 000000000..caa18faba --- /dev/null +++ b/src/components/tooltip/tooltip.js @@ -0,0 +1,56 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tooltip as PfTooltip, TooltipProps, TooltipPosition } from '@patternfly/react-core'; + +/** + * PF tooltip wrapper component. + * + * @param {object} props + * @param {React.ReactNode} props.children + * @param {React.ReactNode} props.content + * @param {boolean} props.isNoWrap + * @param {TooltipProps} props.props + * @returns {React.ReactNode} + */ +const Tooltip = ({ children, content, isNoWrap, ...props }) => ( + {content || ''}

} + {...props} + > + {(React.isValidElement(children) && children) || {children}} +
+); + +/** + * Prop types. + * + * @type {{children: React.ReactNode, content: React.ReactNode}} + */ +Tooltip.propTypes = { + children: PropTypes.node.isRequired, + content: PropTypes.node, + distance: PropTypes.number, + enableFlip: PropTypes.bool, + entryDelay: PropTypes.number, + exitDelay: PropTypes.number, + isNoWrap: PropTypes.bool, + position: PropTypes.string +}; + +/** + * Default props. + * + * @type {{content: string}} + */ +Tooltip.defaultProps = { + content: '...', + distance: 5, + enableFlip: false, + entryDelay: 100, + exitDelay: 0, + isNoWrap: false, + position: TooltipPosition.top +}; + +export { Tooltip as default, Tooltip }; diff --git a/src/config/__tests__/__snapshots__/product.rhosak.test.js.snap b/src/config/__tests__/__snapshots__/product.rhosak.test.js.snap index 2df0e7d9d..031c87296 100644 --- a/src/config/__tests__/__snapshots__/product.rhosak.test.js.snap +++ b/src/config/__tests__/__snapshots__/product.rhosak.test.js.snap @@ -233,7 +233,7 @@ Object { "title": "", }, Object { - "title": "", + "title": "2022-01-01T00:00:00.000Z", }, ], "columnHeaders": Array [ diff --git a/src/styles/_tooltip.scss b/src/styles/_tooltip.scss new file mode 100644 index 000000000..f159b97ba --- /dev/null +++ b/src/styles/_tooltip.scss @@ -0,0 +1,8 @@ +html > .curiosity-tooltip, +.curiosity-tooltip { + &__nowrap { + white-space: nowrap; + } + + &-children {} +} diff --git a/src/styles/index.scss b/src/styles/index.scss index 0d50c2eef..0b6347105 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -5,6 +5,7 @@ // App @import 'page-layout'; @import 'fade'; +@import 'tooltip'; @import 'product-view'; @import 'optin'; @import 'usage-graph'; diff --git a/tests/__snapshots__/code.test.js.snap b/tests/__snapshots__/code.test.js.snap index baa76ace8..4e43703f3 100644 --- a/tests/__snapshots__/code.test.js.snap +++ b/tests/__snapshots__/code.test.js.snap @@ -3,7 +3,7 @@ exports[`General code checks should only have specific console.[warn|log|info|error] methods: console methods 1`] = ` Array [ "components/inventoryCard/inventoryCardContext.js:127: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);", - "components/inventoryCard/inventoryCardHelpers.js:194: console.error(", + "components/inventoryCard/inventoryCardHelpers.js:67: console.warn(\`Warning: Filter \\"\${id}\\" not found in \\"table row\\" response data.\`, cellData);", "components/inventoryCard/inventoryList.deprecated.js:62: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);", "components/inventoryCardSubscriptions/inventoryCardSubscriptionsContext.js:127: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);", "redux/common/reduxHelpers.js:282: console.error(\`Error: Property \${prop} does not exist within the passed state.\`, state);", diff --git a/tests/__snapshots__/dist.test.js.snap b/tests/__snapshots__/dist.test.js.snap index 71f0dcc12..69fe83a4a 100644 --- a/tests/__snapshots__/dist.test.js.snap +++ b/tests/__snapshots__/dist.test.js.snap @@ -44,6 +44,7 @@ Array [ "./dist/js/384*js", "./dist/js/3935*js", "./dist/js/3935*txt", + "./dist/js/3969*js", "./dist/js/4004*js", "./dist/js/4024*js", "./dist/js/4044*js", @@ -154,6 +155,7 @@ Array [ "./dist/sourcemaps/361*map", "./dist/sourcemaps/384*map", "./dist/sourcemaps/3935*map", + "./dist/sourcemaps/3969*map", "./dist/sourcemaps/4004*map", "./dist/sourcemaps/4024*map", "./dist/sourcemaps/4044*map",