From 519b816afaa21f8c71306d6e76ee74fc8c63b184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dro=C5=84?= Date: Wed, 2 Feb 2022 09:48:12 +0100 Subject: [PATCH] Add hover tooltip on sorting disabled columns (#1811) * Add hover tooltip on sorting disabled columns in lists (#1784) * Update macaw * Add TooltipTableCellHeader component * Add filterDependency type to lists * Add tooltip to conditional columns in Voucher List * Add tooltip to conditional columns in Sale List * Add tooltip to conditional columns in Collection List * Add tooltip to conditional columns in Product List * Update snapshots * Improve component clarity * Change TableCellHeader to React.forwardRef component * Change TooltipTableCellHeader to use TableCellHeader * Remove RefTableCellHeader * Bump macaw * Update snapshots * Remove merge conflict leftovers * Add tooltip header to gift card list * Refactor gift card list tooltip Co-authored-by: Wojciech --- locale/defaultMessages.json | 3 + .../CollectionList/CollectionList.tsx | 15 ++- .../CollectionListPage/CollectionListPage.tsx | 4 + .../TableCellHeader/TableCellHeader.tsx | 107 +++++++++--------- .../TooltipTableCellHeader.tsx | 30 +++++ .../TooltipTableCellHeader/index.ts | 2 + .../TooltipTableCellHeader/messages.ts | 8 ++ .../components/SaleList/SaleList.tsx | 15 ++- .../components/SaleListPage/SaleListPage.tsx | 5 +- .../components/VoucherList/VoucherList.tsx | 22 +++- .../VoucherListPage/VoucherListPage.tsx | 5 +- .../GiftCardListSearchAndFilters/filters.ts | 2 +- .../GiftCardsListTableHeader.tsx | 61 ++++++---- .../components/ProductList/ProductList.tsx | 24 +++- .../ProductListPage/ProductListPage.tsx | 4 + src/types.ts | 3 +- 16 files changed, 211 insertions(+), 99 deletions(-) create mode 100644 src/components/TooltipTableCellHeader/TooltipTableCellHeader.tsx create mode 100644 src/components/TooltipTableCellHeader/index.ts create mode 100644 src/components/TooltipTableCellHeader/messages.ts diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 6adfb8d29..e20665750 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -2501,6 +2501,9 @@ "src_dot_components_dot_Timeline_dot_3028189627": { "string": "Leave your note here..." }, + "src_dot_components_dot_TooltipTableCellHeader_dot_noFilterSelected": { + "string": "Sorting by this column requires active filter: {filterName}" + }, "src_dot_components_dot_UserChip_dot_21332146": { "context": "button", "string": "Log out" diff --git a/src/collections/components/CollectionList/CollectionList.tsx b/src/collections/components/CollectionList/CollectionList.tsx index f25a2986c..a1992d79c 100644 --- a/src/collections/components/CollectionList/CollectionList.tsx +++ b/src/collections/components/CollectionList/CollectionList.tsx @@ -9,12 +9,14 @@ import Skeleton from "@saleor/components/Skeleton"; import TableCellHeader from "@saleor/components/TableCellHeader"; import TableHead from "@saleor/components/TableHead"; import TablePagination from "@saleor/components/TablePagination"; +import TooltipTableCellHeader from "@saleor/components/TooltipTableCellHeader"; +import { commonTooltipMessages } from "@saleor/components/TooltipTableCellHeader/messages"; import { makeStyles } from "@saleor/macaw-ui"; import { maybe, renderCollection } from "@saleor/misc"; import { ChannelProps, ListActions, ListProps, SortPage } from "@saleor/types"; import { getArrowDirection } from "@saleor/utils/sort"; import React from "react"; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; import { CollectionList_collections_edges_node } from "../../types/CollectionList"; import { messages } from "./messages"; @@ -73,10 +75,12 @@ const CollectionList: React.FC = props => { selectedChannelId, toggle, toggleAll, - toolbar + toolbar, + filterDependency } = props; const classes = useStyles(props); + const intl = useIntl(); return ( @@ -111,7 +115,7 @@ const CollectionList: React.FC = props => { > - = props => { !!selectedChannelId ) } + tooltip={intl.formatMessage(commonTooltipMessages.noFilterSelected, { + filterName: filterDependency.label + })} > - + diff --git a/src/collections/components/CollectionListPage/CollectionListPage.tsx b/src/collections/components/CollectionListPage/CollectionListPage.tsx index ae90b26d2..90f72f230 100644 --- a/src/collections/components/CollectionListPage/CollectionListPage.tsx +++ b/src/collections/components/CollectionListPage/CollectionListPage.tsx @@ -1,6 +1,7 @@ import { Card } from "@material-ui/core"; import { CollectionListUrlSortField } from "@saleor/collections/urls"; import { Container } from "@saleor/components/Container"; +import { getByName } from "@saleor/components/Filter/utils"; import FilterBar from "@saleor/components/FilterBar"; import PageHeader from "@saleor/components/PageHeader"; import { sectionNames } from "@saleor/intl"; @@ -57,6 +58,8 @@ const CollectionListPage: React.FC = ({ const intl = useIntl(); const filterStructure = createFilterStructure(intl, filterOpts); + const filterDependency = filterStructure.find(getByName("channel")); + return ( @@ -97,6 +100,7 @@ const CollectionListPage: React.FC = ({ disabled={disabled} channelsCount={channelsCount} selectedChannelId={selectedChannelId} + filterDependency={filterDependency} {...listProps} /> diff --git a/src/components/TableCellHeader/TableCellHeader.tsx b/src/components/TableCellHeader/TableCellHeader.tsx index 7958b511c..d7f896db6 100644 --- a/src/components/TableCellHeader/TableCellHeader.tsx +++ b/src/components/TableCellHeader/TableCellHeader.tsx @@ -63,61 +63,64 @@ export interface TableCellHeaderProps extends TableCellProps { disabled?: boolean; } -const TableCellHeader: React.FC = props => { - const classes = useStyles(props); - const { - arrowPosition, - children, - className, - direction, - textAlign, - disabled = false, - onClick, - title, - ...rest - } = props; +const TableCellHeader = React.forwardRef( + (props, ref) => { + const classes = useStyles(props); + const { + arrowPosition, + children, + className, + direction, + textAlign, + disabled = false, + onClick, + title, + ...rest + } = props; - return ( - { - if (disabled || !onClick) { - e.preventDefault(); - } else { - onClick(e); - } - }} - className={classNames(classes.root, className, { - [classes.disabled]: disabled, - [classes.notSortable]: !onClick - })} - > -
{ + if (disabled || !onClick) { + e.preventDefault(); + } else { + onClick(e); + } + }} + className={classNames(classes.root, className, { + [classes.disabled]: disabled, + [classes.notSortable]: !onClick })} > - {!!direction && arrowPosition === "left" && ( - - )} -
{children}
- {!!direction && arrowPosition === "right" && ( - - )} -
-
- ); -}; +
+ {!!direction && arrowPosition === "left" && ( + + )} +
{children}
+ {!!direction && arrowPosition === "right" && ( + + )} +
+ + ); + } +); TableCellHeader.displayName = "TableCellHeader"; TableCellHeader.defaultProps = { diff --git a/src/components/TooltipTableCellHeader/TooltipTableCellHeader.tsx b/src/components/TooltipTableCellHeader/TooltipTableCellHeader.tsx new file mode 100644 index 000000000..73c40e89f --- /dev/null +++ b/src/components/TooltipTableCellHeader/TooltipTableCellHeader.tsx @@ -0,0 +1,30 @@ +import { Tooltip } from "@saleor/macaw-ui"; +import React from "react"; + +import TableCellHeader, { TableCellHeaderProps } from "../TableCellHeader"; + +interface TooltipTableCellHeaderProps extends TableCellHeaderProps { + tooltip?: string | React.ReactNodeArray; +} + +export const TooltipTableCellHeader: React.FC = props => { + const { children, tooltip, disabled, ...rest } = props; + + const tooltipDisabled = () => { + if (!tooltip) { + return true; + } + return !disabled; + }; + + return ( + + + {children} + + + ); +}; + +TooltipTableCellHeader.displayName = "TooltipTableCellHeader"; +export default TooltipTableCellHeader; diff --git a/src/components/TooltipTableCellHeader/index.ts b/src/components/TooltipTableCellHeader/index.ts new file mode 100644 index 000000000..823c8b0bf --- /dev/null +++ b/src/components/TooltipTableCellHeader/index.ts @@ -0,0 +1,2 @@ +export { default } from "./TooltipTableCellHeader"; +export * from "./TooltipTableCellHeader"; diff --git a/src/components/TooltipTableCellHeader/messages.ts b/src/components/TooltipTableCellHeader/messages.ts new file mode 100644 index 000000000..61a7648b7 --- /dev/null +++ b/src/components/TooltipTableCellHeader/messages.ts @@ -0,0 +1,8 @@ +import { defineMessages } from "react-intl"; + +export const commonTooltipMessages = defineMessages({ + noFilterSelected: { + defaultMessage: + "Sorting by this column requires active filter: {filterName}" + } +}); diff --git a/src/discounts/components/SaleList/SaleList.tsx b/src/discounts/components/SaleList/SaleList.tsx index 452ffcd94..1aaae71a6 100644 --- a/src/discounts/components/SaleList/SaleList.tsx +++ b/src/discounts/components/SaleList/SaleList.tsx @@ -8,6 +8,8 @@ import Skeleton from "@saleor/components/Skeleton"; import TableCellHeader from "@saleor/components/TableCellHeader"; import TableHead from "@saleor/components/TableHead"; import TablePagination from "@saleor/components/TablePagination"; +import TooltipTableCellHeader from "@saleor/components/TooltipTableCellHeader"; +import { commonTooltipMessages } from "@saleor/components/TooltipTableCellHeader/messages"; import { SaleListUrlSortField } from "@saleor/discounts/urls"; import { canBeSorted } from "@saleor/discounts/views/SaleList/sort"; import { makeStyles } from "@saleor/macaw-ui"; @@ -17,7 +19,7 @@ import { SaleType } from "@saleor/types/globalTypes"; import { getArrowDirection } from "@saleor/utils/sort"; import classNames from "classnames"; import React from "react"; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; import { SaleList_sales_edges_node } from "../../types/SaleList"; @@ -83,10 +85,12 @@ const SaleList: React.FC = props => { sort, toggle, toggleAll, - toolbar + toolbar, + filterDependency } = props; const classes = useStyles(props); + const intl = useIntl(); const numberOfColumns = sales?.length === 0 ? 4 : 5; return ( @@ -138,7 +142,7 @@ const SaleList: React.FC = props => { > - = props => { disabled={ !canBeSorted(SaleListUrlSortField.value, !!selectedChannelId) } + tooltip={intl.formatMessage(commonTooltipMessages.noFilterSelected, { + filterName: filterDependency.label + })} className={classes.colValue} > - + diff --git a/src/discounts/components/SaleListPage/SaleListPage.tsx b/src/discounts/components/SaleListPage/SaleListPage.tsx index 8341e66fc..8068a3f41 100644 --- a/src/discounts/components/SaleListPage/SaleListPage.tsx +++ b/src/discounts/components/SaleListPage/SaleListPage.tsx @@ -1,5 +1,6 @@ import { Card } from "@material-ui/core"; import Container from "@saleor/components/Container"; +import { getByName } from "@saleor/components/Filter/utils"; import FilterBar from "@saleor/components/FilterBar"; import PageHeader from "@saleor/components/PageHeader"; import { SaleListUrlSortField } from "@saleor/discounts/urls"; @@ -51,6 +52,8 @@ const SaleListPage: React.FC = ({ const intl = useIntl(); const structure = createFilterStructure(intl, filterOpts); + const filterDependency = structure.find(getByName("channel")); + return ( @@ -78,7 +81,7 @@ const SaleListPage: React.FC = ({ onTabDelete={onTabDelete} onTabSave={onTabSave} /> - + ); diff --git a/src/discounts/components/VoucherList/VoucherList.tsx b/src/discounts/components/VoucherList/VoucherList.tsx index f1243616b..917eeab38 100644 --- a/src/discounts/components/VoucherList/VoucherList.tsx +++ b/src/discounts/components/VoucherList/VoucherList.tsx @@ -8,6 +8,8 @@ import Skeleton from "@saleor/components/Skeleton"; import TableCellHeader from "@saleor/components/TableCellHeader"; import TableHead from "@saleor/components/TableHead"; import TablePagination from "@saleor/components/TablePagination"; +import TooltipTableCellHeader from "@saleor/components/TooltipTableCellHeader"; +import { commonTooltipMessages } from "@saleor/components/TooltipTableCellHeader/messages"; import { VoucherListUrlSortField } from "@saleor/discounts/urls"; import { canBeSorted } from "@saleor/discounts/views/VoucherList/sort"; import { makeStyles } from "@saleor/macaw-ui"; @@ -18,7 +20,7 @@ import { getArrowDirection } from "@saleor/utils/sort"; import { getFooterColSpanWithBulkActions } from "@saleor/utils/tables"; import classNames from "classnames"; import React from "react"; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; import { VoucherList_vouchers_edges_node } from "../../types/VoucherList"; @@ -101,10 +103,12 @@ const VoucherList: React.FC = props => { sort, toggle, toggleAll, - toolbar + toolbar, + filterDependency } = props; const classes = useStyles(props); + const intl = useIntl(); return ( @@ -128,7 +132,7 @@ const VoucherList: React.FC = props => { > - = props => { !canBeSorted(VoucherListUrlSortField.minSpent, !!selectedChannelId) } className={classes.colMinSpent} + tooltip={intl.formatMessage(commonTooltipMessages.noFilterSelected, { + filterName: filterDependency.label + })} > - + = props => { description="voucher is active until date" /> - = props => { !canBeSorted(VoucherListUrlSortField.minSpent, !!selectedChannelId) } className={classes.colValue} + tooltip={intl.formatMessage(commonTooltipMessages.noFilterSelected, { + filterName: filterDependency.label + })} > - + = ({ const intl = useIntl(); const structure = createFilterStructure(intl, filterOpts); + const filterDependency = structure.find(getByName("channel")); + return ( @@ -80,7 +83,7 @@ const VoucherListPage: React.FC = ({ onTabDelete={onTabDelete} onTabSave={onTabSave} /> - + ); diff --git a/src/giftCards/GiftCardsList/GiftCardListSearchAndFilters/filters.ts b/src/giftCards/GiftCardsList/GiftCardListSearchAndFilters/filters.ts index 810c6671d..aa4c3c4be 100644 --- a/src/giftCards/GiftCardsList/GiftCardListSearchAndFilters/filters.ts +++ b/src/giftCards/GiftCardsList/GiftCardListSearchAndFilters/filters.ts @@ -164,7 +164,7 @@ export function getFilterQueryParam( } } -const messages = defineMessages({ +export const messages = defineMessages({ balanceAmountLabel: { defaultMessage: "Amount", description: "amount filter label" diff --git a/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTableHeader/GiftCardsListTableHeader.tsx b/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTableHeader/GiftCardsListTableHeader.tsx index ff127a820..d12133624 100644 --- a/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTableHeader/GiftCardsListTableHeader.tsx +++ b/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTableHeader/GiftCardsListTableHeader.tsx @@ -5,6 +5,8 @@ import TableCellHeader, { TableCellHeaderProps } from "@saleor/components/TableCellHeader"; import TableHead from "@saleor/components/TableHead"; +import TooltipTableCellHeader from "@saleor/components/TooltipTableCellHeader"; +import { commonTooltipMessages } from "@saleor/components/TooltipTableCellHeader/messages"; import Label, { LabelSizes } from "@saleor/orders/components/OrderHistory/Label"; @@ -12,6 +14,7 @@ import { getArrowDirection } from "@saleor/utils/sort"; import React from "react"; import { MessageDescriptor, useIntl } from "react-intl"; +import { messages as filterLabels } from "../../GiftCardListSearchAndFilters/filters"; import { giftCardsListTableMessages as messages } from "../../messages"; import useGiftCardListDialogs from "../../providers/GiftCardListDialogsProvider/hooks/useGiftCardListDialogs"; import useGiftCardListSort from "../../providers/GiftCardListDialogsProvider/hooks/useGiftCardListSort"; @@ -27,7 +30,6 @@ interface HeaderItem { options?: TableCellHeaderProps; onClick?: () => void; direction?: TableCellHeaderArrowDirection; - canBeSorted?: boolean; } interface GiftCardsListTableHeaderProps { @@ -68,19 +70,28 @@ const GiftCardsListTableHeader: React.FC = ({ title: messages.giftCardsTableColumnCustomerTitle, onClick: () => onSort(GiftCardUrlSortField.usedBy), direction: getDirection(GiftCardUrlSortField.usedBy) - }, - { - title: messages.giftCardsTableColumnBalanceTitle, - options: { - className: classes.colBalance, - textAlign: "right" - }, - onClick: () => onSort(GiftCardUrlSortField.balance), - direction: getDirection(GiftCardUrlSortField.balance), - canBeSorted: canBeSorted(GiftCardUrlSortField.balance, isCurrencySelected) } ]; + const headerTooltipItem: HeaderItem & { + disabled: boolean; + tooltip: string | React.ReactNodeArray; + } = { + title: messages.giftCardsTableColumnBalanceTitle, + options: { + className: classes.colBalance, + textAlign: "right" + }, + onClick: () => onSort(GiftCardUrlSortField.balance), + direction: getDirection(GiftCardUrlSortField.balance), + disabled: !canBeSorted(GiftCardUrlSortField.balance, isCurrencySelected), + tooltip: intl.formatMessage(commonTooltipMessages.noFilterSelected, { + filterName: {filterLabels.currencyLabel.defaultMessage} + }) + }; + + const { title, ...headerTooltipItemProps } = headerTooltipItem; + return ( <> @@ -105,18 +116,22 @@ const GiftCardsListTableHeader: React.FC = ({ } > - {headerItems.map( - ({ title, options, onClick, direction, canBeSorted = true }) => ( - - - ) - )} + {headerItems.map(({ title, options, onClick, direction }) => ( + + + ))} + + diff --git a/src/products/components/ProductList/ProductList.tsx b/src/products/components/ProductList/ProductList.tsx index a7f98c49f..70cd163d2 100644 --- a/src/products/components/ProductList/ProductList.tsx +++ b/src/products/components/ProductList/ProductList.tsx @@ -17,6 +17,8 @@ import { AVATAR_MARGIN } from "@saleor/components/TableCellAvatar/Avatar"; import TableCellHeader from "@saleor/components/TableCellHeader"; import TableHead from "@saleor/components/TableHead"; import TablePagination from "@saleor/components/TablePagination"; +import TooltipTableCellHeader from "@saleor/components/TooltipTableCellHeader"; +import { commonTooltipMessages } from "@saleor/components/TooltipTableCellHeader/messages"; import { ProductListColumns } from "@saleor/config"; import { makeStyles } from "@saleor/macaw-ui"; import { maybe, renderCollection } from "@saleor/misc"; @@ -35,7 +37,7 @@ import TDisplayColumn, { import { getArrowDirection } from "@saleor/utils/sort"; import classNames from "classnames"; import React from "react"; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; import { columnsMessages, messages } from "./messages"; @@ -137,10 +139,12 @@ export const ProductList: React.FC = props => { onUpdateListSettings, onRowClick, onSort, - selectedChannelId + selectedChannelId, + filterDependency } = props; const classes = useStyles(props); + const intl = useIntl(); const gridAttributesFromSettings = settings.columns.filter( isAttributeColumnValue ); @@ -215,7 +219,7 @@ export const ProductList: React.FC = props => { column="availability" displayColumns={settings.columns} > - = props => { !!selectedChannelId ) } + tooltip={intl.formatMessage( + commonTooltipMessages.noFilterSelected, + { filterName: filterDependency.label } + )} > - + {gridAttributesFromSettings.map(gridAttributeFromSettings => { const attributeId = getAttributeIdFromColumnValue( @@ -278,7 +286,7 @@ export const ProductList: React.FC = props => { - = props => { disabled={ !canBeSorted(ProductListUrlSortField.price, !!selectedChannelId) } + tooltip={intl.formatMessage( + commonTooltipMessages.noFilterSelected, + { filterName: filterDependency.label } + )} > - + diff --git a/src/products/components/ProductListPage/ProductListPage.tsx b/src/products/components/ProductListPage/ProductListPage.tsx index b61161ee5..75a0bd293 100644 --- a/src/products/components/ProductListPage/ProductListPage.tsx +++ b/src/products/components/ProductListPage/ProductListPage.tsx @@ -6,6 +6,7 @@ import ColumnPicker, { ColumnPickerChoice } from "@saleor/components/ColumnPicker"; import Container from "@saleor/components/Container"; +import { getByName } from "@saleor/components/Filter/utils"; import FilterBar from "@saleor/components/FilterBar"; import LimitReachedAlert from "@saleor/components/LimitReachedAlert"; import PageHeader from "@saleor/components/PageHeader"; @@ -114,6 +115,8 @@ export const ProductListPage: React.FC = props => { const filterStructure = createFilterStructure(intl, filterOpts); + const filterDependency = filterStructure.find(getByName("channel")); + const columns: ColumnPickerChoice[] = [ { label: intl.formatMessage(columnsMessages.price), @@ -241,6 +244,7 @@ export const ProductListPage: React.FC = props => { channelsCount={channelsCount} selectedChannelId={selectedChannelId} onUpdateListSettings={onUpdateListSettings} + filterDependency={filterDependency} />
diff --git a/src/types.ts b/src/types.ts index 938bbf3d2..7bc3f91f4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ import { ConfirmButtonTransitionState } from "@saleor/macaw-ui"; import { MutationResult } from "react-apollo"; -import { IFilter } from "./components/Filter"; +import { IFilter, IFilterElement } from "./components/Filter"; import { MultiAutocompleteChoiceType } from "./components/MultiAutocompleteSelectField"; import { User_userPermissions } from "./fragments/types/User"; @@ -61,6 +61,7 @@ export interface ListProps { value: ListSettings[T] ) => void; onListSettingsReset?: () => void; + filterDependency?: IFilterElement; } export interface SortPage {